Add chart business block data model
This commit is contained in:
@@ -0,0 +1,71 @@
|
|||||||
|
import { buildDefaultChartInstance, isChartDefinition } from './chartBlock.js'
|
||||||
|
|
||||||
|
export const businessBlockCanvas = 'business-blocks-v1'
|
||||||
|
|
||||||
|
export function parseBusinessBlockLayout(value) {
|
||||||
|
if (!value) {
|
||||||
|
return { canvas: businessBlockCanvas, blocks: [] }
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = typeof value === 'string' ? JSON.parse(value) : value
|
||||||
|
return {
|
||||||
|
...parsed,
|
||||||
|
canvas: businessBlockCanvas,
|
||||||
|
blocks: Array.isArray(parsed?.blocks) ? parsed.blocks : []
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { canvas: businessBlockCanvas, blocks: [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addBlockInstance(layout, definition, idFactory = defaultIdFactory) {
|
||||||
|
const next = cloneLayout(layout)
|
||||||
|
const id = idFactory(definition.code)
|
||||||
|
next.blocks.push(isChartDefinition(definition)
|
||||||
|
? buildDefaultChartInstance(definition, id)
|
||||||
|
: {
|
||||||
|
id,
|
||||||
|
blockCode: definition.code,
|
||||||
|
name: definition.name || definition.code,
|
||||||
|
config: {}
|
||||||
|
})
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateBlockConfig(layout, blockId, config) {
|
||||||
|
const next = cloneLayout(layout)
|
||||||
|
next.blocks = next.blocks.map((block) => (
|
||||||
|
block.id === blockId ? { ...block, config: { ...(config || {}) } } : block
|
||||||
|
))
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
export function reorderBlockInstance(layout, fromIndex, toIndex) {
|
||||||
|
const next = cloneLayout(layout)
|
||||||
|
if (fromIndex < 0 || toIndex < 0 || fromIndex >= next.blocks.length || toIndex >= next.blocks.length) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
const [moved] = next.blocks.splice(fromIndex, 1)
|
||||||
|
next.blocks.splice(toIndex, 0, moved)
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeBlockInstance(layout, blockId) {
|
||||||
|
const next = cloneLayout(layout)
|
||||||
|
next.blocks = next.blocks.filter((block) => block.id !== blockId)
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateRequiredConfig(definition, config) {
|
||||||
|
return (definition?.configs || [])
|
||||||
|
.filter((item) => item.required && !config?.[item.key])
|
||||||
|
.map((item) => item.label || item.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneLayout(layout) {
|
||||||
|
return JSON.parse(JSON.stringify(parseBusinessBlockLayout(layout)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultIdFactory(code) {
|
||||||
|
return `${code}_${Date.now()}`
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
export const allowedChartSpans = [12, 6, 4]
|
||||||
|
|
||||||
|
export function isChartDefinition(definition) {
|
||||||
|
return definition?.kind === 'chart'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDefaultChartInstance(definition, id) {
|
||||||
|
const chartType = definition?.chartType || 'line'
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
blockCode: definition.code,
|
||||||
|
name: definition.name || definition.code,
|
||||||
|
span: allowedChartSpans.includes(definition.defaultSpan) ? definition.defaultSpan : 12,
|
||||||
|
config: {},
|
||||||
|
dataset: {
|
||||||
|
schema: definition.datasetSchema || 'single-table-aggregate-v1',
|
||||||
|
table: '',
|
||||||
|
dimension: chartType === 'metric' ? null : { field: '', timeUnit: '' },
|
||||||
|
metrics: [{
|
||||||
|
field: '',
|
||||||
|
aggregate: chartType === 'metric' ? 'count' : 'sum',
|
||||||
|
alias: 'metric1',
|
||||||
|
label: '指标 1'
|
||||||
|
}],
|
||||||
|
filters: [],
|
||||||
|
dateRange: null,
|
||||||
|
sort: chartType === 'metric' ? null : { by: 'dimension', order: 'asc' },
|
||||||
|
limit: chartType === 'pie' ? 10 : chartType === 'bar' ? 20 : chartType === 'metric' ? 1 : 100
|
||||||
|
},
|
||||||
|
display: {
|
||||||
|
showTitle: true,
|
||||||
|
showLegend: chartType !== 'metric',
|
||||||
|
emptyText: '暂无数据',
|
||||||
|
smooth: chartType === 'line',
|
||||||
|
donut: chartType === 'pie',
|
||||||
|
horizontal: false,
|
||||||
|
stacked: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateChartDataset(block, dataset) {
|
||||||
|
return { ...block, dataset: structuredClone(dataset || {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateChartDisplay(block, display) {
|
||||||
|
return { ...block, display: structuredClone(display || {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateChartInstance(definition, block, tables) {
|
||||||
|
const errors = []
|
||||||
|
const dataset = block?.dataset || {}
|
||||||
|
const table = tables.find((item) => item.tableName === dataset.table)
|
||||||
|
if (!table) {
|
||||||
|
errors.push({ key: 'table', message: '请选择数据表' })
|
||||||
|
}
|
||||||
|
if (definition?.chartType !== 'metric' && !dataset.dimension?.field) {
|
||||||
|
errors.push({ key: 'dimension.field', message: '请选择维度字段' })
|
||||||
|
}
|
||||||
|
if (!Array.isArray(dataset.metrics)
|
||||||
|
|| !dataset.metrics.length
|
||||||
|
|| dataset.metrics.some((item) => !item.field)) {
|
||||||
|
errors.push({ key: 'metrics', message: '请配置指标字段' })
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { existsSync, readFileSync } from 'node:fs'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { dirname, resolve } from 'node:path'
|
||||||
|
|
||||||
|
import {
|
||||||
|
addBlockInstance,
|
||||||
|
businessBlockCanvas,
|
||||||
|
parseBusinessBlockLayout,
|
||||||
|
reorderBlockInstance,
|
||||||
|
updateBlockConfig,
|
||||||
|
validateRequiredConfig
|
||||||
|
} from './business-blocks/blockLayout.js'
|
||||||
|
import {
|
||||||
|
buildDefaultChartInstance,
|
||||||
|
updateChartDataset,
|
||||||
|
updateChartDisplay,
|
||||||
|
validateChartInstance
|
||||||
|
} from './business-blocks/chartBlock.js'
|
||||||
|
|
||||||
|
const currentDir = dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
function readSource(relativePath) {
|
||||||
|
return readFileSync(resolve(currentDir, relativePath), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
const cartDefinition = {
|
||||||
|
code: 'cart',
|
||||||
|
name: 'Cart',
|
||||||
|
configs: [
|
||||||
|
{ key: 'cartTable', label: 'Cart table', type: 'table', required: true },
|
||||||
|
{ key: 'productName', label: 'Product name field', type: 'field', table: 'productTable', required: true }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineDefinition = {
|
||||||
|
code: 'admin_line_chart',
|
||||||
|
name: 'Line chart',
|
||||||
|
kind: 'chart',
|
||||||
|
chartType: 'line',
|
||||||
|
datasetSchema: 'single-table-aggregate-v1',
|
||||||
|
defaultSpan: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
test('business block helpers create an empty business block canvas', () => {
|
||||||
|
const layout = parseBusinessBlockLayout('')
|
||||||
|
|
||||||
|
assert.equal(layout.canvas, businessBlockCanvas)
|
||||||
|
assert.deepEqual(layout.blocks, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('business block helpers add a block instance with stable generated id', () => {
|
||||||
|
const layout = addBlockInstance(parseBusinessBlockLayout(''), cartDefinition, () => 'cart_001')
|
||||||
|
|
||||||
|
assert.equal(layout.blocks[0].id, 'cart_001')
|
||||||
|
assert.equal(layout.blocks[0].blockCode, 'cart')
|
||||||
|
assert.deepEqual(layout.blocks[0].config, {})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('business block helpers update block config without replacing other blocks', () => {
|
||||||
|
let layout = addBlockInstance(parseBusinessBlockLayout(''), cartDefinition, () => 'cart_001')
|
||||||
|
layout = addBlockInstance(layout, { ...cartDefinition, code: 'notice', name: 'Notice' }, () => 'notice_001')
|
||||||
|
|
||||||
|
layout = updateBlockConfig(layout, 'cart_001', { cartTable: 'shop_cart' })
|
||||||
|
|
||||||
|
assert.equal(layout.blocks[0].config.cartTable, 'shop_cart')
|
||||||
|
assert.deepEqual(layout.blocks[1].config, {})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('business block helpers reorder block instances', () => {
|
||||||
|
let layout = addBlockInstance(parseBusinessBlockLayout(''), cartDefinition, () => 'cart_001')
|
||||||
|
layout = addBlockInstance(layout, { ...cartDefinition, code: 'notice', name: 'Notice' }, () => 'notice_001')
|
||||||
|
|
||||||
|
layout = reorderBlockInstance(layout, 1, 0)
|
||||||
|
|
||||||
|
assert.equal(layout.blocks[0].id, 'notice_001')
|
||||||
|
assert.equal(layout.blocks[1].id, 'cart_001')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('business block helpers report missing required config labels', () => {
|
||||||
|
const missing = validateRequiredConfig(cartDefinition, { cartTable: 'shop_cart' })
|
||||||
|
|
||||||
|
assert.deepEqual(missing, ['Product name field'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('chart helper builds a deterministic line chart instance', () => {
|
||||||
|
const block = buildDefaultChartInstance(lineDefinition, 'line_001')
|
||||||
|
|
||||||
|
assert.equal(block.span, 12)
|
||||||
|
assert.equal(block.dataset.schema, 'single-table-aggregate-v1')
|
||||||
|
assert.deepEqual(block.dataset.metrics, [
|
||||||
|
{ field: '', aggregate: 'sum', alias: 'metric1', label: '指标 1' }
|
||||||
|
])
|
||||||
|
assert.equal(block.display.showLegend, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('chart helper updates dataset and display immutably', () => {
|
||||||
|
const block = buildDefaultChartInstance(lineDefinition, 'line_001')
|
||||||
|
const dataset = { ...block.dataset, table: 'shop_order' }
|
||||||
|
const display = { ...block.display, smooth: false }
|
||||||
|
|
||||||
|
const withDataset = updateChartDataset(block, dataset)
|
||||||
|
const withDisplay = updateChartDisplay(withDataset, display)
|
||||||
|
|
||||||
|
assert.equal(block.dataset.table, '')
|
||||||
|
assert.equal(withDataset.dataset.table, 'shop_order')
|
||||||
|
assert.equal(withDisplay.display.smooth, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('chart helper reports a missing dimension for line charts', () => {
|
||||||
|
const block = buildDefaultChartInstance(lineDefinition, 'line_001')
|
||||||
|
const errors = validateChartInstance(lineDefinition, block, [])
|
||||||
|
|
||||||
|
assert.equal(errors.some((item) => item.key === 'dimension.field'), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('business block designer is composed from focused subcomponents', () => {
|
||||||
|
assert.equal(existsSync(resolve(currentDir, 'business-blocks/BusinessBlockDesigner.vue')), true)
|
||||||
|
assert.equal(existsSync(resolve(currentDir, 'business-blocks/BlockPalette.vue')), true)
|
||||||
|
assert.equal(existsSync(resolve(currentDir, 'business-blocks/BlockCanvas.vue')), true)
|
||||||
|
assert.equal(existsSync(resolve(currentDir, 'business-blocks/BlockConfigForm.vue')), true)
|
||||||
|
|
||||||
|
const source = readSource('business-blocks/BusinessBlockDesigner.vue')
|
||||||
|
assert.equal(source.includes("import BlockPalette from './BlockPalette.vue'"), true)
|
||||||
|
assert.equal(source.includes("import BlockCanvas from './BlockCanvas.vue'"), true)
|
||||||
|
assert.equal(source.includes("import BlockConfigForm from './BlockConfigForm.vue'"), true)
|
||||||
|
assert.equal(source.includes('update:layout-json'), true)
|
||||||
|
assert.equal(source.includes('select-block'), true)
|
||||||
|
assert.equal(source.includes('showConfig'), true)
|
||||||
|
assert.equal(source.includes('showPalette'), true)
|
||||||
|
assert.equal(source.includes('v-if="showPalette"'), true)
|
||||||
|
assert.equal(source.includes('v-if="showConfig"'), true)
|
||||||
|
assert.equal(source.includes('defineExpose'), true)
|
||||||
|
assert.equal(source.includes('<BlockConfigForm'), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('business block schema form renders table and field controls', () => {
|
||||||
|
const source = readSource('business-blocks/BlockConfigForm.vue')
|
||||||
|
|
||||||
|
assert.equal(source.includes("field.type === 'table'"), true)
|
||||||
|
assert.equal(source.includes("field.type === 'field'"), true)
|
||||||
|
assert.equal(source.includes('<el-select'), true)
|
||||||
|
assert.equal(source.includes('update:modelValue'), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('business block canvas supports drag reorder and block removal', () => {
|
||||||
|
const source = readSource('business-blocks/BlockCanvas.vue')
|
||||||
|
|
||||||
|
assert.equal(source.includes('draggable="true"'), true)
|
||||||
|
assert.equal(source.includes('reorder-block'), true)
|
||||||
|
assert.equal(source.includes('remove-block'), true)
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BusinessBlockConfigField
|
||||||
|
{
|
||||||
|
private String key;
|
||||||
|
private String label;
|
||||||
|
private String type;
|
||||||
|
private String table;
|
||||||
|
private Boolean required;
|
||||||
|
private List<String> options;
|
||||||
|
|
||||||
|
public String getKey() { return key; }
|
||||||
|
public void setKey(String key) { this.key = key; }
|
||||||
|
public String getLabel() { return label; }
|
||||||
|
public void setLabel(String label) { this.label = label; }
|
||||||
|
public String getType() { return type; }
|
||||||
|
public void setType(String type) { this.type = type; }
|
||||||
|
public String getTable() { return table; }
|
||||||
|
public void setTable(String table) { this.table = table; }
|
||||||
|
public Boolean getRequired() { return required; }
|
||||||
|
public void setRequired(Boolean required) { this.required = required; }
|
||||||
|
public List<String> getOptions() { return options; }
|
||||||
|
public void setOptions(List<String> options) { this.options = options; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class BusinessBlockInstance
|
||||||
|
{
|
||||||
|
private String id;
|
||||||
|
private String blockCode;
|
||||||
|
private String name;
|
||||||
|
private Map<String, String> config;
|
||||||
|
private Integer span;
|
||||||
|
private ChartDatasetConfig dataset;
|
||||||
|
private Map<String, Object> display;
|
||||||
|
|
||||||
|
public String getId() { return id; }
|
||||||
|
public void setId(String id) { this.id = id; }
|
||||||
|
public String getBlockCode() { return blockCode; }
|
||||||
|
public void setBlockCode(String blockCode) { this.blockCode = blockCode; }
|
||||||
|
public String getName() { return name; }
|
||||||
|
public void setName(String name) { this.name = name; }
|
||||||
|
public Map<String, String> getConfig() { return config; }
|
||||||
|
public void setConfig(Map<String, String> config) { this.config = config; }
|
||||||
|
public Integer getSpan() { return span; }
|
||||||
|
public void setSpan(Integer span) { this.span = span; }
|
||||||
|
public ChartDatasetConfig getDataset() { return dataset; }
|
||||||
|
public void setDataset(ChartDatasetConfig dataset) { this.dataset = dataset; }
|
||||||
|
public Map<String, Object> getDisplay() { return display; }
|
||||||
|
public void setDisplay(Map<String, Object> display) { this.display = display; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
public class BusinessBlockTemplateFile
|
||||||
|
{
|
||||||
|
private String key;
|
||||||
|
private String template;
|
||||||
|
private String outputPath;
|
||||||
|
|
||||||
|
public String getKey() { return key; }
|
||||||
|
public void setKey(String key) { this.key = key; }
|
||||||
|
public String getTemplate() { return template; }
|
||||||
|
public void setTemplate(String template) { this.template = template; }
|
||||||
|
public String getOutputPath() { return outputPath; }
|
||||||
|
public void setOutputPath(String outputPath) { this.outputPath = outputPath; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChartDatasetConfig
|
||||||
|
{
|
||||||
|
private String schema;
|
||||||
|
private String table;
|
||||||
|
private ChartDimensionConfig dimension;
|
||||||
|
private List<ChartMetricConfig> metrics;
|
||||||
|
private List<ChartFilterConfig> filters;
|
||||||
|
private ChartDateRangeConfig dateRange;
|
||||||
|
private ChartSortConfig sort;
|
||||||
|
private Integer limit;
|
||||||
|
|
||||||
|
public String getSchema() { return schema; }
|
||||||
|
public void setSchema(String schema) { this.schema = schema; }
|
||||||
|
public String getTable() { return table; }
|
||||||
|
public void setTable(String table) { this.table = table; }
|
||||||
|
public ChartDimensionConfig getDimension() { return dimension; }
|
||||||
|
public void setDimension(ChartDimensionConfig dimension) { this.dimension = dimension; }
|
||||||
|
public List<ChartMetricConfig> getMetrics() { return metrics; }
|
||||||
|
public void setMetrics(List<ChartMetricConfig> metrics) { this.metrics = metrics; }
|
||||||
|
public List<ChartFilterConfig> getFilters() { return filters; }
|
||||||
|
public void setFilters(List<ChartFilterConfig> filters) { this.filters = filters; }
|
||||||
|
public ChartDateRangeConfig getDateRange() { return dateRange; }
|
||||||
|
public void setDateRange(ChartDateRangeConfig dateRange) { this.dateRange = dateRange; }
|
||||||
|
public ChartSortConfig getSort() { return sort; }
|
||||||
|
public void setSort(ChartSortConfig sort) { this.sort = sort; }
|
||||||
|
public Integer getLimit() { return limit; }
|
||||||
|
public void setLimit(Integer limit) { this.limit = limit; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
public class ChartDateRangeConfig
|
||||||
|
{
|
||||||
|
private String field;
|
||||||
|
private String preset;
|
||||||
|
private String start;
|
||||||
|
private String end;
|
||||||
|
|
||||||
|
public String getField() { return field; }
|
||||||
|
public void setField(String field) { this.field = field; }
|
||||||
|
public String getPreset() { return preset; }
|
||||||
|
public void setPreset(String preset) { this.preset = preset; }
|
||||||
|
public String getStart() { return start; }
|
||||||
|
public void setStart(String start) { this.start = start; }
|
||||||
|
public String getEnd() { return end; }
|
||||||
|
public void setEnd(String end) { this.end = end; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
public class ChartDimensionConfig
|
||||||
|
{
|
||||||
|
private String field;
|
||||||
|
private String timeUnit;
|
||||||
|
|
||||||
|
public String getField() { return field; }
|
||||||
|
public void setField(String field) { this.field = field; }
|
||||||
|
public String getTimeUnit() { return timeUnit; }
|
||||||
|
public void setTimeUnit(String timeUnit) { this.timeUnit = timeUnit; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
public class ChartFilterConfig
|
||||||
|
{
|
||||||
|
private String field;
|
||||||
|
private String operator;
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
public String getField() { return field; }
|
||||||
|
public void setField(String field) { this.field = field; }
|
||||||
|
public String getOperator() { return operator; }
|
||||||
|
public void setOperator(String operator) { this.operator = operator; }
|
||||||
|
public Object getValue() { return value; }
|
||||||
|
public void setValue(Object value) { this.value = value; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
public class ChartMetricConfig
|
||||||
|
{
|
||||||
|
private String field;
|
||||||
|
private String aggregate;
|
||||||
|
private String alias;
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
public String getField() { return field; }
|
||||||
|
public void setField(String field) { this.field = field; }
|
||||||
|
public String getAggregate() { return aggregate; }
|
||||||
|
public void setAggregate(String aggregate) { this.aggregate = aggregate; }
|
||||||
|
public String getAlias() { return alias; }
|
||||||
|
public void setAlias(String alias) { this.alias = alias; }
|
||||||
|
public String getLabel() { return label; }
|
||||||
|
public void setLabel(String label) { this.label = label; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
public class ChartSortConfig
|
||||||
|
{
|
||||||
|
private String by;
|
||||||
|
private String order;
|
||||||
|
|
||||||
|
public String getBy() { return by; }
|
||||||
|
public void setBy(String by) { this.by = by; }
|
||||||
|
public String getOrder() { return order; }
|
||||||
|
public void setOrder(String order) { this.order = order; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ruoyi.generator.domain.front.dto.block;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PageBusinessBlockLayout
|
||||||
|
{
|
||||||
|
private String canvas;
|
||||||
|
private List<BusinessBlockInstance> blocks;
|
||||||
|
|
||||||
|
public String getCanvas() { return canvas; }
|
||||||
|
public void setCanvas(String canvas) { this.canvas = canvas; }
|
||||||
|
public List<BusinessBlockInstance> getBlocks() { return blocks; }
|
||||||
|
public void setBlocks(List<BusinessBlockInstance> blocks) { this.blocks = blocks; }
|
||||||
|
}
|
||||||
@@ -81,6 +81,24 @@ public class BusinessBlockRegistryServiceTest
|
|||||||
assertEquals("cart", layout.getBlocks().get(0).getBlockCode());
|
assertEquals("cart", layout.getBlocks().get(0).getBlockCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseLayoutReadsChartDatasetAndSpan()
|
||||||
|
{
|
||||||
|
PageBusinessBlockLayout layout = service.parseLayout(
|
||||||
|
"{\"canvas\":\"business-blocks-v1\",\"blocks\":[{"
|
||||||
|
+ "\"id\":\"line_001\",\"blockCode\":\"admin_line_chart\",\"span\":6,"
|
||||||
|
+ "\"dataset\":{\"schema\":\"single-table-aggregate-v1\",\"table\":\"shop_order\","
|
||||||
|
+ "\"dimension\":{\"field\":\"create_time\",\"timeUnit\":\"day\"},"
|
||||||
|
+ "\"metrics\":[{\"field\":\"amount\",\"aggregate\":\"sum\",\"alias\":\"salesAmount\"}]},"
|
||||||
|
+ "\"display\":{\"showLegend\":true}}]}");
|
||||||
|
|
||||||
|
BusinessBlockInstance instance = layout.getBlocks().get(0);
|
||||||
|
assertEquals(Integer.valueOf(6), instance.getSpan());
|
||||||
|
assertEquals("shop_order", instance.getDataset().getTable());
|
||||||
|
assertEquals("amount", instance.getDataset().getMetrics().get(0).getField());
|
||||||
|
assertEquals(Boolean.TRUE, instance.getDisplay().get("showLegend"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void validateInstanceConfigRejectsMissingRequiredConfig()
|
public void validateInstanceConfigRejectsMissingRequiredConfig()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user