diff --git a/packages/core/src/App.ts b/packages/core/src/App.ts index 6829049e..b0f3b89e 100644 --- a/packages/core/src/App.ts +++ b/packages/core/src/App.ts @@ -18,14 +18,9 @@ import { EventEmitter } from 'events'; -import { cloneDeep, has, isEmpty, template } from 'lodash-es'; +import { has, isEmpty } from 'lodash-es'; -import { - createDataSourceManager, - DataSourceManager, - DataSourceManagerData, - RequestFunction, -} from '@tmagic/data-source'; +import { createDataSourceManager, DataSourceManager, RequestFunction } from '@tmagic/data-source'; import { ActionType, CodeBlockDSL, @@ -36,9 +31,7 @@ import { EventConfig, Id, MApp, - MNode, } from '@tmagic/schema'; -import { compiledNode } from '@tmagic/utils'; import Env from './Env'; import { bindCommonEventListener, isCommonMethod, triggerCommonMethod } from './events'; @@ -174,13 +167,9 @@ class App extends EventEmitter { this.dataSourceManager.destroy(); } - this.dataSourceManager = createDataSourceManager( - config, - (node: MNode, content: DataSourceManagerData) => this.compiledNode(node, content), - { - request, - }, - ); + this.dataSourceManager = createDataSourceManager(config, this.platform, { + request, + }); this.codeDsl = config.codeBlocks; this.setPage(curPage || this.page?.data?.id); @@ -344,23 +333,6 @@ class App extends EventEmitter { } } - public compiledNode(node: MNode, content: DataSourceManagerData, sourceId?: Id) { - return compiledNode( - (value: any) => { - if (typeof value === 'string') { - return template(value)(content); - } - if (value?.isBindDataSource && value.dataSourceId) { - return content[value.dataSourceId]; - } - return value; - }, - cloneDeep(node), - this.dsl?.dataSourceDeps, - sourceId, - ); - } - public destroy() { this.removeAllListeners(); this.page = undefined; diff --git a/packages/data-source/src/DataSourceManager.ts b/packages/data-source/src/DataSourceManager.ts index 43cd7fa5..03dfa39d 100644 --- a/packages/data-source/src/DataSourceManager.ts +++ b/packages/data-source/src/DataSourceManager.ts @@ -18,7 +18,10 @@ import EventEmitter from 'events'; -import { DataSourceSchema } from '@tmagic/schema'; +import { cloneDeep, template } from 'lodash-es'; + +import { DataSourceDeps, DataSourceSchema, Id, MNode } from '@tmagic/schema'; +import { compiledCond, compiledNode } from '@tmagic/utils'; import { DataSource, HttpDataSource } from './data-sources'; import type { DataSourceManagerData, DataSourceManagerOptions, HttpDataSourceSchema, RequestFunction } from './types'; @@ -32,12 +35,17 @@ class DataSourceManager extends EventEmitter { public dataSourceMap = new Map(); public data: DataSourceManagerData = {}; + public dataSourceDeps: DataSourceDeps = {}; + public dataSourceCondDeps: DataSourceDeps = {}; private request?: RequestFunction; constructor(options: DataSourceManagerOptions) { super(); + this.dataSourceDeps = options.dataSourceDeps || {}; + this.dataSourceCondDeps = options.dataSourceCondDeps || {}; + if (options.httpDataSourceOptions?.request) { this.request = options.httpDataSourceOptions.request; } @@ -102,6 +110,50 @@ class DataSourceManager extends EventEmitter { }); } + public compiledNode(node: MNode, sourceId?: Id) { + if (node.condResult === false) return node; + if (node.visible === false) return node; + + return compiledNode( + (value: any) => { + if (typeof value === 'string') { + return template(value)(this.data); + } + if (value?.isBindDataSource && value.dataSourceId) { + return this.data[value.dataSourceId]; + } + return value; + }, + cloneDeep(node), + this.dataSourceDeps, + sourceId, + ); + } + + public compliedConds(node: MNode) { + if (!node.displayConds || !Array.isArray(node.displayConds) || !node.displayConds.length) return true; + + for (const { cond } of node.displayConds) { + if (!cond) continue; + + let result = true; + for (const { op, value, range, field } of cond) { + const [sourceId, fieldKey] = field; + const fieldValue = this.data[sourceId][fieldKey]; + if (!compiledCond(op, fieldValue, value, range)) { + result = false; + break; + } + } + + if (result) { + return true; + } + } + + return false; + } + public destroy() { this.removeAllListeners(); this.data = {}; diff --git a/packages/data-source/src/createDataSourceManager.ts b/packages/data-source/src/createDataSourceManager.ts index a22e6e2d..8fcf9bc8 100644 --- a/packages/data-source/src/createDataSourceManager.ts +++ b/packages/data-source/src/createDataSourceManager.ts @@ -15,11 +15,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { MApp, MNode } from '@tmagic/schema'; +import { cloneDeep } from 'lodash-es'; + +import type { MApp } from '@tmagic/schema'; import { getDepNodeIds, getNodes, replaceChildNode } from '@tmagic/utils'; import DataSourceManager from './DataSourceManager'; -import type { DataSourceManagerData, HttpDataSourceOptions } from './types'; +import type { HttpDataSourceOptions } from './types'; /** * 创建数据源管理器 @@ -29,26 +31,54 @@ import type { DataSourceManagerData, HttpDataSourceOptions } from './types'; */ export const createDataSourceManager = ( dsl: MApp, - compiledNode = (node: MNode, _content: DataSourceManagerData) => node, + platform: string, httpDataSourceOptions?: Partial, ) => { if (!dsl?.dataSources) return; const dataSourceManager = new DataSourceManager({ dataSourceConfigs: dsl.dataSources, + dataSourceDeps: dsl.dataSourceDeps, + dataSourceCondDeps: dsl.dataSourceCondDeps, httpDataSourceOptions, }); + if (dsl.dataSources && dsl.dataSourceCondDeps && platform !== 'editor') { + getNodes(getDepNodeIds(dsl.dataSourceCondDeps), dsl.items).forEach((node) => { + node.condResult = dataSourceManager.compliedConds(node); + replaceChildNode(node, dsl!.items); + }); + } + if (dsl.dataSources && dsl.dataSourceDeps) { getNodes(getDepNodeIds(dsl.dataSourceDeps), dsl.items).forEach((node) => { - replaceChildNode(compiledNode(node, dataSourceManager.data), dsl!.items); + replaceChildNode(dataSourceManager.compiledNode(node), dsl!.items); }); } dataSourceManager.on('change', (sourceId: string) => { const dep = dsl.dataSourceDeps?.[sourceId]; - if (!dep) return; - dataSourceManager.emit('update-data', getNodes(Object.keys(dep), dsl.items), sourceId); + const condDep = dsl.dataSourceCondDeps?.[sourceId]; + + if (condDep) { + dataSourceManager.emit( + 'update-data', + getNodes(Object.keys(condDep), dsl.items).map((node) => { + const newNode = cloneDeep(node); + newNode.condResult = dataSourceManager.compliedConds(node); + return newNode; + }), + sourceId, + ); + } + + if (dep) { + dataSourceManager.emit( + 'update-data', + getNodes(Object.keys(dep), dsl.items).map((node) => dataSourceManager.compiledNode(node)), + sourceId, + ); + } }); return dataSourceManager; diff --git a/packages/data-source/src/types.ts b/packages/data-source/src/types.ts index 036690ae..e2ab4411 100644 --- a/packages/data-source/src/types.ts +++ b/packages/data-source/src/types.ts @@ -1,4 +1,4 @@ -import { DataSourceSchema } from '@tmagic/schema'; +import { DataSourceDeps, DataSourceSchema } from '@tmagic/schema'; export interface DataSourceOptions { schema: DataSourceSchema; @@ -32,6 +32,8 @@ export interface HttpDataSourceOptions { export interface DataSourceManagerOptions { dataSourceConfigs: DataSourceSchema[]; + dataSourceDeps?: DataSourceDeps; + dataSourceCondDeps?: DataSourceDeps; httpDataSourceOptions?: Partial; } diff --git a/packages/editor/src/utils/props.ts b/packages/editor/src/utils/props.ts index bbb56224..706c8ded 100644 --- a/packages/editor/src/utils/props.ts +++ b/packages/editor/src/utils/props.ts @@ -235,7 +235,7 @@ export const displayTabConfig: TabPaneConfig = { items: [ { type: 'groupList', - name: 'showCond', + name: 'displayConds', items: [ { type: 'table', diff --git a/packages/ui-react/src/container/Container.tsx b/packages/ui-react/src/container/Container.tsx index e603403d..ee7fa83a 100644 --- a/packages/ui-react/src/container/Container.tsx +++ b/packages/ui-react/src/container/Container.tsx @@ -46,6 +46,7 @@ const Container: React.FC = ({ config, id }) => { if (!MagicUiComp) return null; if (item.visible === false) return null; + if (item.condResult === false) return null; return ( = ({ config }) => { if (!MagicUiComp) return null; if (item.visible === false) return null; + if (item.condResult === false) return null; return ( { if (props.config.visible === false) return false; + if (props.config.condResult === false) return false; const displayCfg = props.config?.display; diff --git a/packages/ui/src/Component.vue b/packages/ui/src/Component.vue index 3de22964..e970efd2 100644 --- a/packages/ui/src/Component.vue +++ b/packages/ui/src/Component.vue @@ -34,6 +34,7 @@ const style = computed(() => app?.transformStyle(props.config.style)); const display = () => { if (props.config.visible === false) return false; + if (props.config.condResult === false) return false; const displayCfg = props.config?.display; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 4c48ee18..8d1576d2 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -295,3 +295,48 @@ export const compiledNode = ( return node; }; + +export const compiledCond = (op: string, fieldValue: any, value: any, range: [number, number]): boolean => { + switch (op) { + case 'is': + if (!fieldValue) return false; + break; + case 'not': + if (fieldValue) return false; + break; + case '=': + if (fieldValue !== value) return false; + break; + case '!=': + if (fieldValue === value) return false; + break; + case '>': + if (fieldValue <= value) return false; + break; + case '>=': + if (fieldValue < value) return false; + break; + case '<': + if (fieldValue >= value) return false; + break; + case '<=': + if (fieldValue > value) return false; + break; + case 'between': + if (fieldValue < range[0] || fieldValue > range[1]) return false; + break; + case 'not_between': + if (fieldValue >= range[0] && fieldValue <= range[1]) return false; + break; + case 'include': + if (!fieldValue.includes(value)) return false; + break; + case 'not_include': + if (fieldValue.includes(value)) return false; + break; + default: + break; + } + + return true; +}; diff --git a/runtime/react/page/App.tsx b/runtime/react/page/App.tsx index 2295d18a..7e6c7ead 100644 --- a/runtime/react/page/App.tsx +++ b/runtime/react/page/App.tsx @@ -31,10 +31,9 @@ function App() { const [config, setConfig] = useState(app.page.data); - app.dataSourceManager?.on('update-data', (nodes: MNode[], sourceId: string) => { + app.dataSourceManager?.on('update-data', (nodes: MNode[]) => { nodes.forEach((node) => { - const newNode = app.compiledNode(node, app.dataSourceManager?.data || {}, sourceId); - replaceChildNode(newNode, [config]); + replaceChildNode(node, [config]); setConfig(cloneDeep(config)); }); }); diff --git a/runtime/react/playground/main.tsx b/runtime/react/playground/main.tsx index e236b5db..b24bf896 100644 --- a/runtime/react/playground/main.tsx +++ b/runtime/react/playground/main.tsx @@ -108,7 +108,7 @@ const operations = { }, update({ config, root }: UpdateData) { - replaceChildNode(app.compiledNode(config, app.dataSourceManager?.data || {}), root.items); + replaceChildNode(app.dataSourceManager?.compiledNode(config) || config, root.items); updateConfig(cloneDeep(root)); }, diff --git a/runtime/vue2/page/App.vue b/runtime/vue2/page/App.vue index 21cb65bf..a98b54cd 100644 --- a/runtime/vue2/page/App.vue +++ b/runtime/vue2/page/App.vue @@ -16,10 +16,9 @@ export default defineComponent({ const app = inject('app'); const pageConfig = reactive(app?.page?.data || {}); - app?.dataSourceManager?.on('update-data', (nodes: MNode[], sourceId: string) => { + app?.dataSourceManager?.on('update-data', (nodes: MNode[]) => { nodes.forEach((node) => { - const newNode = app.compiledNode(node, app.dataSourceManager?.data || {}, sourceId); - replaceChildNode(reactive(newNode), [pageConfig as MNode]); + replaceChildNode(reactive(node), [pageConfig as MNode]); }); }); diff --git a/runtime/vue2/playground/App.vue b/runtime/vue2/playground/App.vue index 22274198..979171d4 100644 --- a/runtime/vue2/playground/App.vue +++ b/runtime/vue2/playground/App.vue @@ -88,7 +88,7 @@ export default defineComponent({ update({ config, parentId }: UpdateData) { if (!root.value || !app) throw new Error('error'); - const newNode = app.compiledNode(config, app.dataSourceManager?.data || {}); + const newNode = app.dataSourceManager?.compiledNode(config) || config; replaceChildNode(reactive(newNode), [root.value], parentId); const nodeInstance = app.page?.getNode(config.id); diff --git a/runtime/vue3/page/App.vue b/runtime/vue3/page/App.vue index 21cb65bf..a98b54cd 100644 --- a/runtime/vue3/page/App.vue +++ b/runtime/vue3/page/App.vue @@ -16,10 +16,9 @@ export default defineComponent({ const app = inject('app'); const pageConfig = reactive(app?.page?.data || {}); - app?.dataSourceManager?.on('update-data', (nodes: MNode[], sourceId: string) => { + app?.dataSourceManager?.on('update-data', (nodes: MNode[]) => { nodes.forEach((node) => { - const newNode = app.compiledNode(node, app.dataSourceManager?.data || {}, sourceId); - replaceChildNode(reactive(newNode), [pageConfig as MNode]); + replaceChildNode(reactive(node), [pageConfig as MNode]); }); }); diff --git a/runtime/vue3/playground/App.vue b/runtime/vue3/playground/App.vue index 196f7d4a..00817717 100644 --- a/runtime/vue3/playground/App.vue +++ b/runtime/vue3/playground/App.vue @@ -85,7 +85,7 @@ window.magic?.onRuntimeReady({ update({ config, parentId }: UpdateData) { if (!root.value || !app) throw new Error('error'); - const newNode = app.compiledNode(config, app.dataSourceManager?.data || {}); + const newNode = app.dataSourceManager?.compiledNode(config) || config; replaceChildNode(reactive(newNode), [root.value], parentId); const nodeInstance = app.page?.getNode(config.id);