From 35862078b3c86eefa53cea9dada92ded6ccc91c9 Mon Sep 17 00:00:00 2001 From: roymondchen Date: Fri, 4 Aug 2023 15:52:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor,schema):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=98=BE=E7=A4=BA=E6=9D=A1=E4=BB=B6=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/fields/DataSourceFieldSelect.vue | 56 ++ .../src/fields/DataSourceMethodSelect.vue | 2 +- packages/editor/src/index.ts | 3 + packages/editor/src/initService.ts | 52 +- .../sidebar/code-block/CodeBlockList.vue | 4 +- .../sidebar/data-source/DataSourceList.vue | 68 ++- packages/editor/src/services/dep.ts | 38 +- packages/editor/src/type.ts | 20 + packages/editor/src/utils/dep.ts | 40 +- packages/editor/src/utils/props.ts | 487 +++++++++++------- .../editor/tests/unit/services/dep.spec.ts | 28 +- packages/schema/src/index.ts | 6 +- 12 files changed, 537 insertions(+), 267 deletions(-) create mode 100644 packages/editor/src/fields/DataSourceFieldSelect.vue diff --git a/packages/editor/src/fields/DataSourceFieldSelect.vue b/packages/editor/src/fields/DataSourceFieldSelect.vue new file mode 100644 index 00000000..a1ecf4e5 --- /dev/null +++ b/packages/editor/src/fields/DataSourceFieldSelect.vue @@ -0,0 +1,56 @@ + + + diff --git a/packages/editor/src/fields/DataSourceMethodSelect.vue b/packages/editor/src/fields/DataSourceMethodSelect.vue index 9eb3b43e..d3089f61 100644 --- a/packages/editor/src/fields/DataSourceMethodSelect.vue +++ b/packages/editor/src/fields/DataSourceMethodSelect.vue @@ -105,7 +105,7 @@ const cascaderConfig = { dataSources.value ?.filter((ds) => ds.methods?.length) ?.map((ds) => ({ - label: `${ds.title}(${ds.id})`, + label: ds.title || ds.id, value: ds.id, children: ds.methods?.map((method) => ({ label: method.name, diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 61eacc51..a1b1ca4d 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -22,6 +22,7 @@ import CodeLink from './fields/CodeLink.vue'; import CodeSelect from './fields/CodeSelect.vue'; import CodeSelectCol from './fields/CodeSelectCol.vue'; import DataSourceFields from './fields/DataSourceFields.vue'; +import DataSourceFieldSelect from './fields/DataSourceFieldSelect.vue'; import DataSourceInput from './fields/DataSourceInput.vue'; import DataSourceMethods from './fields/DataSourceMethods.vue'; import DataSourceMethodSelect from './fields/DataSourceMethodSelect.vue'; @@ -59,6 +60,7 @@ export { default as DataSourceMethods } from './fields/DataSourceMethods.vue'; export { default as DataSourceInput } from './fields/DataSourceInput.vue'; export { default as DataSourceSelect } from './fields/DataSourceSelect.vue'; export { default as DataSourceMethodSelect } from './fields/DataSourceMethodSelect.vue'; +export { default as DataSourceFieldSelect } from './fields/DataSourceFieldSelect.vue'; export { default as EventSelect } from './fields/EventSelect.vue'; export { default as KeyValue } from './fields/KeyValue.vue'; export { default as CodeBlockList } from './layouts/sidebar/code-block/CodeBlockList.vue'; @@ -98,5 +100,6 @@ export default { app.component('m-fields-data-source-select', DataSourceSelect); app.component('m-fields-data-source-methods', DataSourceMethods); app.component('m-fields-data-source-method-select', DataSourceMethodSelect); + app.component('m-fields-data-source-field-select', DataSourceFieldSelect); }, }; diff --git a/packages/editor/src/initService.ts b/packages/editor/src/initService.ts index f8d31cdb..0759f743 100644 --- a/packages/editor/src/initService.ts +++ b/packages/editor/src/initService.ts @@ -7,9 +7,14 @@ import type { CodeBlockContent, DataSourceSchema, Id, MApp, MNode, MPage } from import { getNodes } from '@tmagic/utils'; import type { Target } from './services/dep'; -import { createCodeBlockTarget, createDataSourceTarget } from './utils/dep'; +import { + createCodeBlockTarget, + createDataSourceCondTarget, + createDataSourceMethodTarget, + createDataSourceTarget, +} from './utils/dep'; import editorProps from './editorProps'; -import type { Services } from './type'; +import { DepTargetType, Services } from './type'; export declare type LooseRequired = { [P in string & keyof T]: T[P]; @@ -139,6 +144,7 @@ export const initServiceEvents = ( if (app.dsl) { app.dsl.dataSourceDeps = root.dataSourceDeps; + app.dsl.dataSourceCondDeps = root.dataSourceCondDeps; app.dsl.dataSources = root.dataSources; } @@ -159,23 +165,34 @@ export const initServiceEvents = ( }; const targetAddHandler = (target: Target) => { - if (target.type !== 'data-source') return; - const root = editorService.get('root'); if (!root) return; - if (!root.dataSourceDeps) { - root.dataSourceDeps = {}; + if (target.type === DepTargetType.DATA_SOURCE) { + if (!root.dataSourceDeps) { + root.dataSourceDeps = {}; + } + root.dataSourceDeps[target.id] = target.deps; } - root.dataSourceDeps[target.id] = target.deps; + if (target.type === DepTargetType.DATA_SOURCE_COND) { + if (!root.dataSourceCondDeps) { + root.dataSourceCondDeps = {}; + } + root.dataSourceCondDeps[target.id] = target.deps; + } }; const targetRemoveHandler = (id: string | number) => { const root = editorService.get('root'); - if (!root?.dataSourceDeps) return; - delete root.dataSourceDeps[id]; + if (root?.dataSourceDeps) { + delete root.dataSourceDeps[id]; + } + + if (root?.dataSourceCondDeps) { + delete root.dataSourceCondDeps[id]; + } }; const depUpdateHandler = (node: MNode) => { @@ -191,6 +208,12 @@ export const initServiceEvents = ( depService.on('dep-update', depUpdateHandler); depService.on('collected', collectedHandler); + const initDataSourceDepTarget = (ds: DataSourceSchema) => { + depService.addTarget(createDataSourceTarget(ds.id)); + depService.addTarget(createDataSourceMethodTarget(ds.id)); + depService.addTarget(createDataSourceCondTarget(ds.id)); + }; + const rootChangeHandler = async (value: MApp, preValue?: MApp | null) => { const nodeId = editorService.get('node')?.id || props.defaultSelected; let node; @@ -218,14 +241,14 @@ export const initServiceEvents = ( codeBlockService.setCodeDsl(value.codeBlocks); dataSourceService.set('dataSources', value.dataSources); - depService.removeTargets('code-block'); + depService.removeTargets(DepTargetType.CODE_BLOCK); Object.entries(value.codeBlocks).forEach(([id, code]) => { depService.addTarget(createCodeBlockTarget(id, code)); }); value.dataSources.forEach((ds) => { - depService.addTarget(createDataSourceTarget(ds.id, ds)); + initDataSourceDepTarget(ds); }); if (value && Array.isArray(value.items)) { @@ -279,17 +302,14 @@ export const initServiceEvents = ( codeBlockService.on('remove', codeBlockRemoveHandler); const dataSourceAddHandler = (config: DataSourceSchema) => { - depService.addTarget(createDataSourceTarget(config.id, config)); + initDataSourceDepTarget(config); getApp()?.dataSourceManager?.addDataSource(config); }; const dataSourceUpdateHandler = (config: DataSourceSchema) => { - if (config.title) { - depService.getTarget(config.id)!.name = config.title; - } const root = editorService.get('root'); - const targets = depService.getTargets('data-source'); + const targets = depService.getTargets(DepTargetType.DATA_SOURCE); const nodes = getNodes(Object.keys(targets[config.id].deps), root?.items); diff --git a/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue b/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue index 6ae5a1ca..bcee5922 100644 --- a/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue +++ b/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue @@ -50,7 +50,7 @@ import type { Id } from '@tmagic/schema'; import Icon from '@editor/components/Icon.vue'; import AppManageIcon from '@editor/icons/AppManageIcon.vue'; import CodeIcon from '@editor/icons/CodeIcon.vue'; -import { CodeDeleteErrorType, CodeDslItem, Services } from '@editor/type'; +import { CodeDeleteErrorType, CodeDslItem, DepTargetType, Services } from '@editor/type'; defineOptions({ name: 'MEditorCodeBlockList', @@ -69,7 +69,7 @@ const { codeBlockService, depService, editorService } = inject('servic // 代码块列表 const codeList = computed(() => - Object.values(depService?.targets['code-block'] || {}).map((target) => { + Object.values(depService?.getTargets(DepTargetType.CODE_BLOCK) || {}).map((target) => { // 组件节点 const compNodes = Object.entries(target.deps).map(([id, dep]) => ({ name: dep.name, diff --git a/packages/editor/src/layouts/sidebar/data-source/DataSourceList.vue b/packages/editor/src/layouts/sidebar/data-source/DataSourceList.vue index 50bcc576..951f4d16 100644 --- a/packages/editor/src/layouts/sidebar/data-source/DataSourceList.vue +++ b/packages/editor/src/layouts/sidebar/data-source/DataSourceList.vue @@ -15,11 +15,11 @@
- {{ data.name }}({{ data.id }}) + {{ + data.type === 'key' ? data.name : `${data.name}(${data.id})` + }} -
+
@@ -39,10 +39,10 @@ import { computed, inject, ref } from 'vue'; import { Aim, Close, Coin, Edit, View } from '@element-plus/icons-vue'; import { tMagicMessageBox, TMagicTooltip, TMagicTree } from '@tmagic/design'; -import { Id } from '@tmagic/schema'; +import { Dep, Id } from '@tmagic/schema'; import Icon from '@editor/components/Icon.vue'; -import type { Services } from '@editor/type'; +import { DepTargetType, Services } from '@editor/type'; defineOptions({ name: 'MEditorDataSourceList', @@ -57,18 +57,52 @@ const { depService, editorService, dataSourceService } = inject('servi const editable = computed(() => dataSourceService?.get('editable') ?? true); +const dataSources = computed(() => dataSourceService?.get('dataSources') || []); + +const dsDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE) || {}); +const dsMethodDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_METHOD) || {}); +const dsCondDep = computed(() => depService?.getTargets(DepTargetType.DATA_SOURCE_COND) || {}); + +const getKeyTreeConfig = (dep: Dep[string], type?: string) => + dep.keys.map((key) => ({ name: key, id: key, type: 'key', isMethod: type === 'method', isCond: type === 'cond' })); + +const getNodeTreeConfig = (id: string, dep: Dep[string], type?: string) => ({ + name: dep.name, + type: 'node', + id, + children: getKeyTreeConfig(dep, type), +}); + const list = computed(() => - Object.values(depService?.targets['data-source'] || {}).map((target) => ({ - id: target.id, - name: target.name, - type: 'code', - children: Object.entries(target.deps).map(([id, dep]) => ({ - name: dep.name, - type: 'node', - id, - children: dep.keys.map((key) => ({ name: key, id: key, type: 'key' })), - })), - })), + dataSources.value.map((ds) => { + const dsDeps = dsDep.value[ds.id].deps; + const dsMethodDeps = dsMethodDep.value[ds.id].deps; + const dsCondDeps = dsCondDep.value[ds.id].deps; + + const children: any[] = []; + + const mergeChildren = (deps: Dep, type?: string) => { + Object.entries(deps).forEach(([id, dep]) => { + const nodeItem = children.find((item) => item.id === id); + if (nodeItem) { + nodeItem.children = nodeItem.children.concat(getKeyTreeConfig(dep, type)); + } else { + children.push(getNodeTreeConfig(id, dep, type)); + } + }); + }; + + mergeChildren(dsDeps); + mergeChildren(dsMethodDeps, 'method'); + mergeChildren(dsCondDeps, 'cond'); + + return { + id: ds.id, + name: ds.title, + type: 'ds', + children, + }; + }), ); const editHandler = (id: string) => { diff --git a/packages/editor/src/services/dep.ts b/packages/editor/src/services/dep.ts index 54d29cb3..e0a5f936 100644 --- a/packages/editor/src/services/dep.ts +++ b/packages/editor/src/services/dep.ts @@ -19,27 +19,24 @@ import { EventEmitter } from 'events'; import { reactive } from 'vue'; -import { Id, MNode } from '@tmagic/schema'; +import type { Dep, Id, MNode } from '@tmagic/schema'; +import { isObject } from '@tmagic/utils'; + +import { DepTargetType } from '@editor/type'; type IsTarget = (key: string | number, value: any) => boolean; interface TargetOptions { isTarget: IsTarget; id: string | number; - type?: string; - name: string; -} - -interface Dep { - [key: string | number]: { - name: string; - keys: (string | number)[]; - }; + /** 类型,数据源、代码块或其他 */ + type?: DepTargetType | string; + name?: string; } interface TargetList { - [key: string]: { - [key: string | number]: Target; + [type: DepTargetType | string]: { + [targetId: string | number]: Target; }; } @@ -60,11 +57,11 @@ export class Target extends EventEmitter { /** * 目标名称,用于显示在依赖列表中 */ - public name: string; + public name?: string; /** * 不同的目标可以进行分类,例如代码块,数据源可以为两个不同的type */ - public type = 'default'; + public type: DepTargetType | string = DepTargetType.DEFAULT; /** * 依赖详情 * 实例:{ 'node_id': { name: 'node_name', keys: [ created, mounted ] } } @@ -156,14 +153,14 @@ export class Target extends EventEmitter { } export class Watcher extends EventEmitter { - public targets = reactive({}); + private targets = reactive({}); /** * 获取指定类型中的所有target * @param type 分类 * @returns Target[] */ - public getTargets(type = 'default') { + public getTargets(type: DepTargetType | string = DepTargetType.DEFAULT) { return this.targets[type] || {}; } @@ -230,7 +227,7 @@ export class Watcher extends EventEmitter { * @param type 分类 * @returns void */ - public removeTargets(type = 'default') { + public removeTargets(type: DepTargetType | string = DepTargetType.DEFAULT) { const targets = this.targets[type]; if (!targets) return; @@ -307,9 +304,11 @@ export class Watcher extends EventEmitter { this.emit('update-dep', node, fullKey); } else if (!keyIsItems && Array.isArray(value)) { value.forEach((item, index) => { - collectTarget(item, `${fullKey}.${index}`); + if (isObject(item)) { + collectTarget(item, `${fullKey}.${index}`); + } }); - } else if (Object.prototype.toString.call(value) === '[object Object]') { + } else if (isObject(value)) { collectTarget(value, fullKey); } @@ -321,6 +320,7 @@ export class Watcher extends EventEmitter { }; Object.entries(config).forEach(([key, value]) => { + if (typeof value === 'undefined' || value === '') return; doCollect(key, value); }); }; diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index 1e6dbb1c..b0ce041e 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -500,3 +500,23 @@ export interface DataSourceMethodSelectConfig { disabled?: boolean | FilterFunction; display?: boolean | FilterFunction; } + +export interface DataSourceFieldSelectConfig { + type: 'data-source-field-select'; + name: string; + labelWidth?: number | string; + disabled?: boolean | FilterFunction; + display?: boolean | FilterFunction; +} + +export enum DepTargetType { + DEFAULT = 'default', + /** 代码块 */ + CODE_BLOCK = 'code-block', + /** 数据源 */ + DATA_SOURCE = 'data-source', + /** 数据源方法 */ + DATA_SOURCE_METHOD = 'data-source-method', + /** 数据源条件 */ + DATA_SOURCE_COND = 'data-source-cond', +} diff --git a/packages/editor/src/utils/dep.ts b/packages/editor/src/utils/dep.ts index a3581d57..63733012 100644 --- a/packages/editor/src/utils/dep.ts +++ b/packages/editor/src/utils/dep.ts @@ -1,13 +1,14 @@ import { isEmpty } from 'lodash-es'; -import { CodeBlockContent, DataSourceSchema, HookType, Id } from '@tmagic/schema'; +import { CodeBlockContent, HookType, Id } from '@tmagic/schema'; +import dataSourceService from '@editor/services/dataSource'; import { Target } from '@editor/services/dep'; -import type { HookData } from '@editor/type'; +import { DepTargetType, HookData } from '@editor/type'; export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent) => new Target({ - type: 'code-block', + type: DepTargetType.CODE_BLOCK, id, name: codeBlock.name, isTarget: (key: string | number, value: any) => { @@ -24,12 +25,37 @@ export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent) => }, }); -export const createDataSourceTarget = (id: Id, ds: DataSourceSchema) => +export const createDataSourceTarget = (id: Id) => new Target({ - type: 'data-source', + type: DepTargetType.DATA_SOURCE, id, - name: ds.title || `${id}`, isTarget: (key: string | number, value: any) => // 关联数据源对象或者在模板在使用数据源 - (value.isBindDataSource && value.dataSourceId) || (typeof value === 'string' && value.includes(`${id}`)), + (value?.isBindDataSource && value.dataSourceId) || (typeof value === 'string' && value.includes(`${id}`)), + }); + +export const createDataSourceCondTarget = (id: string) => + new Target({ + type: DepTargetType.DATA_SOURCE_COND, + id, + isTarget: (key: string | number, value: any) => { + if (!Array.isArray(value) || value[0] !== id) return false; + + const ds = dataSourceService.getDataSourceById(id); + + return Boolean(ds?.fields?.find((field) => field.name === value[1])); + }, + }); + +export const createDataSourceMethodTarget = (id: string) => + new Target({ + type: DepTargetType.DATA_SOURCE_METHOD, + id, + isTarget: (key: string | number, value: any) => { + if (!Array.isArray(value) || value[0] !== id) return false; + + const ds = dataSourceService.getDataSourceById(id); + + return Boolean(ds?.methods?.find((method) => method.name === value[1])); + }, }); diff --git a/packages/editor/src/utils/props.ts b/packages/editor/src/utils/props.ts index b88d47d4..bbb56224 100644 --- a/packages/editor/src/utils/props.ts +++ b/packages/editor/src/utils/props.ts @@ -16,7 +16,300 @@ * limitations under the License. */ -import { FormConfig, FormState } from '@tmagic/form'; +import type { FormConfig, FormState, TabPaneConfig } from '@tmagic/form'; + +import dataSourceService from '@editor/services/dataSource'; + +const arrayOptions = [ + { text: '包含', value: 'include' }, + { text: '不包含', value: 'not_include' }, +]; + +const eqOptions = [ + { text: '等于', value: '=' }, + { text: '不等于', value: '!=' }, +]; + +const numberOptions = [ + { text: '大于', value: '>' }, + { text: '大于等于', value: '>=' }, + { text: '小于', value: '<' }, + { text: '小于等于', value: '<=' }, + { text: '在范围内', value: 'between' }, + { text: '不在范围内', value: 'not_between' }, +]; + +export const styleTabConfig: TabPaneConfig = { + title: '样式', + labelWidth: '80px', + items: [ + { + name: 'style', + items: [ + { + type: 'fieldset', + legend: '位置', + items: [ + { + name: 'position', + type: 'checkbox', + activeValue: 'fixed', + inactiveValue: 'absolute', + defaultValue: 'absolute', + text: '固定定位', + }, + { + name: 'left', + text: 'left', + }, + { + name: 'top', + text: 'top', + disabled: (vm: FormState, { model }: any) => + model.position === 'fixed' && model._magic_position === 'fixedBottom', + }, + { + name: 'right', + text: 'right', + }, + { + name: 'bottom', + text: 'bottom', + disabled: (vm: FormState, { model }: any) => + model.position === 'fixed' && model._magic_position === 'fixedTop', + }, + ], + }, + { + type: 'fieldset', + legend: '盒子', + items: [ + { + name: 'width', + text: '宽度', + }, + { + name: 'height', + text: '高度', + }, + ], + }, + { + type: 'fieldset', + legend: '边框', + items: [ + { + name: 'borderWidth', + text: '宽度', + defaultValue: '0', + }, + { + name: 'borderColor', + text: '颜色', + type: 'colorPicker', + }, + { + name: 'borderStyle', + text: '样式', + type: 'select', + defaultValue: 'none', + options: [ + { text: 'none', value: 'none' }, + { text: 'hidden', value: 'hidden' }, + { text: 'dotted', value: 'dotted' }, + { text: 'dashed', value: 'dashed' }, + { text: 'solid', value: 'solid' }, + { text: 'double', value: 'double' }, + { text: 'groove', value: 'groove' }, + { text: 'ridge', value: 'ridge' }, + { text: 'inset', value: 'inset' }, + { text: 'outset', value: 'outset' }, + ], + }, + ], + }, + { + type: 'fieldset', + legend: '背景', + items: [ + { + name: 'backgroundImage', + text: '背景图', + }, + { + name: 'backgroundColor', + text: '背景颜色', + type: 'colorPicker', + }, + { + name: 'backgroundRepeat', + text: '背景图重复', + type: 'select', + defaultValue: 'no-repeat', + options: [ + { text: 'repeat', value: 'repeat' }, + { text: 'repeat-x', value: 'repeat-x' }, + { text: 'repeat-y', value: 'repeat-y' }, + { text: 'no-repeat', value: 'no-repeat' }, + { text: 'inherit', value: 'inherit' }, + ], + }, + { + name: 'backgroundSize', + text: '背景图大小', + defaultValue: '100% 100%', + }, + ], + }, + { + type: 'fieldset', + legend: '字体', + items: [ + { + name: 'color', + text: '颜色', + type: 'colorPicker', + }, + { + name: 'fontSize', + text: '大小', + }, + { + name: 'fontWeight', + text: '粗细', + }, + ], + }, + { + type: 'fieldset', + legend: '变形', + name: 'transform', + items: [ + { + name: 'rotate', + text: '旋转角度', + }, + { + name: 'scale', + text: '缩放', + }, + ], + }, + ], + }, + ], +}; + +export const eventTabConfig: TabPaneConfig = { + title: '事件', + items: [ + { + name: 'events', + type: 'event-select', + }, + ], +}; + +export const advancedTabConfig: TabPaneConfig = { + title: '高级', + lazy: true, + items: [ + { + name: 'created', + text: 'created', + labelWidth: '100px', + type: 'code-select', + }, + { + name: 'mounted', + text: 'mounted', + labelWidth: '100px', + type: 'code-select', + }, + ], +}; + +export const displayTabConfig: TabPaneConfig = { + title: '显示条件', + display: (vm: FormState, { model }: any) => model.type !== 'page', + items: [ + { + type: 'groupList', + name: 'showCond', + items: [ + { + type: 'table', + name: 'cond', + items: [ + { + type: 'data-source-field-select', + name: 'field', + label: '字段', + }, + { + type: 'select', + options: (mForm: FormState | undefined, { model }: any) => { + const [id, field] = model.field; + + const ds = dataSourceService.getDataSourceById(id); + + const type = ds?.fields.find((f) => f.name === field)?.type; + + if (type === 'array') { + return arrayOptions; + } + + if (type === 'boolean') { + return [ + { text: '是', value: 'is' }, + { text: '不是', value: 'not' }, + ]; + } + + if (type === 'number') { + return [...eqOptions, ...numberOptions]; + } + + if (type === 'string') { + return [...arrayOptions, ...eqOptions]; + } + + return [...arrayOptions, ...eqOptions, ...numberOptions]; + }, + label: '条件', + name: 'op', + }, + { + label: '值', + items: [ + { + name: 'value', + display: (vm: FormState, { model }: any) => + !['between', 'not_between', 'is', 'not'].includes(model.op), + }, + { + name: 'value', + type: 'select', + options: [ + { text: 'true', value: true }, + { text: 'false', value: false }, + ], + display: (vm: FormState, { model }: any) => ['is', 'not'].includes(model.op), + }, + { + name: 'range', + type: 'number-range', + display: (vm: FormState, { model }: any) => + ['between', 'not_between'].includes(model.op) && !['is', 'not'].includes(model.op), + }, + ], + }, + ], + }, + ], + }, + ], +}; /** * 统一为组件属性表单加上事件、高级、样式配置 @@ -50,194 +343,10 @@ export const fillConfig = (config: FormConfig = []) => [ ...config, ], }, - { - title: '样式', - labelWidth: '80px', - items: [ - { - name: 'style', - items: [ - { - type: 'fieldset', - legend: '位置', - items: [ - { - name: 'position', - type: 'checkbox', - activeValue: 'fixed', - inactiveValue: 'absolute', - defaultValue: 'absolute', - text: '固定定位', - }, - { - name: 'left', - text: 'left', - }, - { - name: 'top', - text: 'top', - disabled: (vm: FormState, { model }: any) => - model.position === 'fixed' && model._magic_position === 'fixedBottom', - }, - { - name: 'right', - text: 'right', - }, - { - name: 'bottom', - text: 'bottom', - disabled: (vm: FormState, { model }: any) => - model.position === 'fixed' && model._magic_position === 'fixedTop', - }, - ], - }, - { - type: 'fieldset', - legend: '盒子', - items: [ - { - name: 'width', - text: '宽度', - }, - { - name: 'height', - text: '高度', - }, - ], - }, - { - type: 'fieldset', - legend: '边框', - items: [ - { - name: 'borderWidth', - text: '宽度', - defaultValue: '0', - }, - { - name: 'borderColor', - text: '颜色', - type: 'colorPicker', - }, - { - name: 'borderStyle', - text: '样式', - type: 'select', - defaultValue: 'none', - options: [ - { text: 'none', value: 'none' }, - { text: 'hidden', value: 'hidden' }, - { text: 'dotted', value: 'dotted' }, - { text: 'dashed', value: 'dashed' }, - { text: 'solid', value: 'solid' }, - { text: 'double', value: 'double' }, - { text: 'groove', value: 'groove' }, - { text: 'ridge', value: 'ridge' }, - { text: 'inset', value: 'inset' }, - { text: 'outset', value: 'outset' }, - ], - }, - ], - }, - { - type: 'fieldset', - legend: '背景', - items: [ - { - name: 'backgroundImage', - text: '背景图', - }, - { - name: 'backgroundColor', - text: '背景颜色', - type: 'colorPicker', - }, - { - name: 'backgroundRepeat', - text: '背景图重复', - type: 'select', - defaultValue: 'no-repeat', - options: [ - { text: 'repeat', value: 'repeat' }, - { text: 'repeat-x', value: 'repeat-x' }, - { text: 'repeat-y', value: 'repeat-y' }, - { text: 'no-repeat', value: 'no-repeat' }, - { text: 'inherit', value: 'inherit' }, - ], - }, - { - name: 'backgroundSize', - text: '背景图大小', - defaultValue: '100% 100%', - }, - ], - }, - { - type: 'fieldset', - legend: '字体', - items: [ - { - name: 'color', - text: '颜色', - type: 'colorPicker', - }, - { - name: 'fontSize', - text: '大小', - }, - { - name: 'fontWeight', - text: '粗细', - }, - ], - }, - { - type: 'fieldset', - legend: '变形', - name: 'transform', - items: [ - { - name: 'rotate', - text: '旋转角度', - }, - { - name: 'scale', - text: '缩放', - }, - ], - }, - ], - }, - ], - }, - { - title: '事件', - items: [ - { - name: 'events', - type: 'event-select', - labelWidth: 0, - }, - ], - }, - { - title: '高级', - lazy: true, - items: [ - { - name: 'created', - text: 'created', - labelWidth: '100px', - type: 'code-select', - }, - { - name: 'mounted', - text: 'mounted', - labelWidth: '100px', - type: 'code-select', - }, - ], - }, + { ...styleTabConfig }, + { ...eventTabConfig }, + { ...advancedTabConfig }, + { ...displayTabConfig }, ], }, ]; diff --git a/packages/editor/tests/unit/services/dep.spec.ts b/packages/editor/tests/unit/services/dep.spec.ts index 0e8bf52c..d3da54eb 100644 --- a/packages/editor/tests/unit/services/dep.spec.ts +++ b/packages/editor/tests/unit/services/dep.spec.ts @@ -122,10 +122,10 @@ describe('depService', () => { const target1 = depService.getTarget('collect_1'); const target2 = depService.getTarget('collect_2'); - expect((target1?.deps || {}).node_1.name).toBe('node'); - expect((target2?.deps || {}).node_1.name).toBe('node'); - expect((target1?.deps || {}).node_1.keys).toHaveLength(1); - expect((target2?.deps || {}).node_1.keys).toHaveLength(3); + expect(target1?.deps?.node_1.name).toBe('node'); + expect(target2?.deps?.node_1.name).toBe('node'); + expect(target1?.deps?.node_1.keys).toHaveLength(1); + expect(target2?.deps?.node_1.keys).toHaveLength(3); depService.collect([ { @@ -146,8 +146,8 @@ describe('depService', () => { }, ]); - expect((target1?.deps || {}).node_1).toBeUndefined(); - expect((target2?.deps || {}).node_1.keys).toHaveLength(1); + expect(target1?.deps?.node_1).toBeUndefined(); + expect(target2?.deps?.node_1.keys).toHaveLength(1); depService.collect([ { @@ -158,8 +158,8 @@ describe('depService', () => { }, ]); - expect((target1?.deps || {}).node_1).toBeUndefined(); - expect((target2?.deps || {}).node_1.keys[0]).toBe('text1'); + expect(target1?.deps?.node_1).toBeUndefined(); + expect(target2?.deps?.node_1.keys[0]).toBe('text1'); depService.clear([ { @@ -168,8 +168,8 @@ describe('depService', () => { }, ]); - expect((target1?.deps || {}).node_1).toBeUndefined(); - expect((target2?.deps || {}).node_1).toBeUndefined(); + expect(target1?.deps?.node_1).toBeUndefined(); + expect(target2?.deps?.node_1).toBeUndefined(); }); test('collect deep', () => { @@ -204,8 +204,8 @@ describe('depService', () => { const target1 = depService.getTarget('collect_1'); - expect((target1?.deps || {}).node_1.name).toBe('node'); - expect((target1?.deps || {}).node_2.name).toBe('node2'); + expect(target1?.deps?.node_1.name).toBe('node'); + expect(target1?.deps?.node_2.name).toBe('node2'); depService.clear([ { @@ -221,7 +221,7 @@ describe('depService', () => { }, ]); - expect((target1?.deps || {}).node_1).toBeUndefined(); - expect((target1?.deps || {}).node_2).toBeUndefined(); + expect(target1?.deps?.node_1).toBeUndefined(); + expect(target1?.deps?.node_2).toBeUndefined(); }); }); diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index bf0c537b..928069b0 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -125,6 +125,7 @@ export interface MApp extends MComponent { dataSources?: DataSourceSchema[]; dataSourceDeps?: DataSourceDeps; + dataSourceCondDeps?: DataSourceDeps; } export interface CodeBlockDSL { @@ -191,14 +192,15 @@ export interface DataSourceSchema { /** 字段列表 */ fields: DataSchema[]; /** 方法列表 */ - methods?: CodeBlockContent[]; + methods: CodeBlockContent[]; /** 扩展字段 */ [key: string]: any; } export interface Dep { [nodeId: Id]: { + /** 组件名称 */ name: string; - keys: Id[]; + keys: (string | number)[]; }; }