From a164e2be62a34bbf4a9896becd738adf72dd2fd4 Mon Sep 17 00:00:00 2001 From: roymondchen Date: Fri, 28 Jun 2024 17:44:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(dats-source,editor,form,schema,ui):=20?= =?UTF-8?q?=E8=BF=AD=E4=BB=A3=E5=99=A8=E5=AE=B9=E5=99=A8=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=AD=90=E9=A1=B9=E6=98=BE=E7=A4=BA=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/data-source/src/DataSourceManager.ts | 8 +- packages/data-source/src/utils.ts | 66 ++++++-- packages/editor/src/fields/CondOpSelect.vue | 87 +++++++++++ .../DataSourceFieldSelect/FieldSelect.vue | 2 +- .../fields/DataSourceFieldSelect/Index.vue | 13 +- .../editor/src/fields/DataSourceFields.vue | 6 + packages/editor/src/fields/DisplayConds.vue | 145 ++++++++++++++++++ packages/editor/src/index.ts | 6 + packages/editor/src/type.ts | 5 + .../editor/src/utils/data-source/index.ts | 5 +- packages/editor/src/utils/props.ts | 109 +------------ packages/form/src/fields/Cascader.vue | 2 +- packages/form/src/fields/CheckboxGroup.vue | 5 +- packages/form/src/schema.ts | 15 +- packages/form/src/utils/form.ts | 29 ++-- packages/schema/src/index.ts | 12 ++ .../src/IteratorContainer.vue | 37 +++-- .../src/iterator-container/src/formConfig.ts | 7 + 18 files changed, 400 insertions(+), 159 deletions(-) create mode 100644 packages/editor/src/fields/CondOpSelect.vue create mode 100644 packages/editor/src/fields/DisplayConds.vue diff --git a/packages/data-source/src/DataSourceManager.ts b/packages/data-source/src/DataSourceManager.ts index cfdca078..9e3a8ab6 100644 --- a/packages/data-source/src/DataSourceManager.ts +++ b/packages/data-source/src/DataSourceManager.ts @@ -20,13 +20,13 @@ import EventEmitter from 'events'; import { cloneDeep } from 'lodash-es'; -import type { AppCore, DataSourceSchema, Id, MNode } from '@tmagic/schema'; +import type { AppCore, DataSourceSchema, DisplayCond, Id, MNode } from '@tmagic/schema'; import { compiledNode } from '@tmagic/utils'; import { SimpleObservedData } from './observed-data/SimpleObservedData'; import { DataSource, HttpDataSource } from './data-sources'; import type { ChangeEvent, DataSourceManagerData, DataSourceManagerOptions, ObservedDataClass } from './types'; -import { compiledNodeField, compliedConditions, compliedIteratorItems } from './utils'; +import { compiledNodeField, compliedConditions, compliedIteratorItemConditions, compliedIteratorItems } from './utils'; class DataSourceManager extends EventEmitter { private static dataSourceClassMap = new Map(); @@ -214,6 +214,10 @@ class DataSourceManager extends EventEmitter { return compliedConditions(node, this.data); } + public compliedIteratorItemConds(itemData: any, displayConds: DisplayCond[] = []) { + return compliedIteratorItemConditions(displayConds, itemData); + } + public compliedIteratorItems(itemData: any, items: MNode[], dataSourceField: string[] = []) { const [dsId, ...keys] = dataSourceField; const ds = this.get(dsId); diff --git a/packages/data-source/src/utils.ts b/packages/data-source/src/utils.ts index 64e2292e..0c5f5e22 100644 --- a/packages/data-source/src/utils.ts +++ b/packages/data-source/src/utils.ts @@ -1,7 +1,7 @@ import { cloneDeep, template } from 'lodash-es'; import { isDataSourceTemplate, isUseDataSourceField, Target, Watcher } from '@tmagic/dep'; -import type { MApp, MNode, MPage, MPageFragment } from '@tmagic/schema'; +import type { DisplayCond, DisplayCondItem, MApp, MNode, MPage, MPageFragment } from '@tmagic/schema'; import { compiledCond, compiledNode, @@ -15,29 +15,69 @@ import { import type { AsyncDataSourceResolveResult, DataSourceManagerData } from './types'; +/** + * 编译显示条件 + * @param cond 条件配置 + * @param data 上下文数据(数据源数据) + * @returns boolean + */ +export const compiledCondition = (cond: DisplayCondItem[], data: DataSourceManagerData) => { + let result = true; + for (const { op, value, range, field } of cond) { + const [sourceId, ...fields] = field; + + const dsData = data[sourceId]; + + if (!dsData || !fields.length) { + break; + } + + const fieldValue = getValueByKeyPath(fields.join('.'), dsData); + + if (!compiledCond(op, fieldValue, value, range)) { + result = false; + break; + } + } + + return result; +}; + /** * 编译数据源条件组 * @param node dsl节点 * @param data 数据源数据 * @returns boolean */ -export const compliedConditions = (node: MNode, data: DataSourceManagerData) => { +export const compliedConditions = (node: { displayConds?: DisplayCond[] }, data: DataSourceManagerData) => { if (!node.displayConds || !Array.isArray(node.displayConds) || !node.displayConds.length) return true; for (const { cond } of node.displayConds) { if (!cond) continue; + if (compiledCondition(cond, data)) { + return true; + } + } + + return false; +}; + +/** + * 编译迭代器容器子项显示条件 + * @param displayConds 条件组配置 + * @param data 迭代器容器的迭代数据项 + * @returns boolean + */ +export const compliedIteratorItemConditions = (displayConds: DisplayCond[] = [], data: DataSourceManagerData) => { + if (!displayConds || !Array.isArray(displayConds) || !displayConds.length) return true; + + for (const { cond } of displayConds) { + if (!cond) continue; + let result = true; for (const { op, value, range, field } of cond) { - const [sourceId, ...fields] = field; - - const dsData = data[sourceId]; - - if (!dsData || !fields.length) { - break; - } - - const fieldValue = getValueByKeyPath(fields.join('.'), data[sourceId]); + const fieldValue = getValueByKeyPath(field.join('.'), data); if (!compiledCond(op, fieldValue, value, range)) { result = false; @@ -45,9 +85,7 @@ export const compliedConditions = (node: MNode, data: DataSourceManagerData) => } } - if (result) { - return true; - } + return result; } return false; diff --git a/packages/editor/src/fields/CondOpSelect.vue b/packages/editor/src/fields/CondOpSelect.vue new file mode 100644 index 00000000..beb7e768 --- /dev/null +++ b/packages/editor/src/fields/CondOpSelect.vue @@ -0,0 +1,87 @@ + + + diff --git a/packages/editor/src/fields/DataSourceFieldSelect/FieldSelect.vue b/packages/editor/src/fields/DataSourceFieldSelect/FieldSelect.vue index 7f49ea34..85898f61 100644 --- a/packages/editor/src/fields/DataSourceFieldSelect/FieldSelect.vue +++ b/packages/editor/src/fields/DataSourceFieldSelect/FieldSelect.vue @@ -137,7 +137,7 @@ const dsChangeHandler = (v: string) => { emit('change', modelValue.value); }; -const fieldChangeHandler = (v: string[]) => { +const fieldChangeHandler = (v: string[] = []) => { modelValue.value = [selectDataSourceId.value, ...v]; emit('change', modelValue.value); }; diff --git a/packages/editor/src/fields/DataSourceFieldSelect/Index.vue b/packages/editor/src/fields/DataSourceFieldSelect/Index.vue index 6d2a2f2c..12249ed1 100644 --- a/packages/editor/src/fields/DataSourceFieldSelect/Index.vue +++ b/packages/editor/src/fields/DataSourceFieldSelect/Index.vue @@ -129,10 +129,20 @@ const checkStrictly = computed(() => { }); const onChangeHandler = (value: string[]) => { + if (!Array.isArray(value)) { + emit('change', value); + return; + } + const [dsId, ...keys] = value; const dataSource = dataSources.value.find((ds) => ds.id === removeDataSourceFieldPrefix(dsId)); - let fields = dataSource?.fields || []; + if (!dataSource) { + emit('change', value); + return; + } + + let fields = dataSource.fields || []; let field: DataSchema | undefined; (keys || []).forEach((key) => { field = fields.find((f) => f.name === key); @@ -145,7 +155,6 @@ const onChangeHandler = (value: string[]) => { } if ( - !dsId || !keys.length || (field?.type && (field.type === 'any' || dataSourceFieldType.includes('any') || dataSourceFieldType.includes(field.type))) diff --git a/packages/editor/src/fields/DataSourceFields.vue b/packages/editor/src/fields/DataSourceFields.vue index 395e9c85..1b0ee880 100644 --- a/packages/editor/src/fields/DataSourceFields.vue +++ b/packages/editor/src/fields/DataSourceFields.vue @@ -172,6 +172,12 @@ const dataSourceFieldsConfig: FormConfig = [ { text: 'null', value: 'null' }, { text: 'any', value: 'any' }, ], + onChange: (formState, v: string, { model }) => { + if (!['any', 'array', 'object'].includes(v)) { + model.fields = []; + } + return v; + }, }, { name: 'name', diff --git a/packages/editor/src/fields/DisplayConds.vue b/packages/editor/src/fields/DisplayConds.vue new file mode 100644 index 00000000..d15e233e --- /dev/null +++ b/packages/editor/src/fields/DisplayConds.vue @@ -0,0 +1,145 @@ + + + diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index 335534ee..5cc3ea26 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -21,6 +21,7 @@ import Code from './fields/Code.vue'; import CodeLink from './fields/CodeLink.vue'; import CodeSelect from './fields/CodeSelect.vue'; import CodeSelectCol from './fields/CodeSelectCol.vue'; +import CondOpSelect from './fields/CondOpSelect.vue'; import DataSourceFields from './fields/DataSourceFields.vue'; import DataSourceFieldSelect from './fields/DataSourceFieldSelect/Index.vue'; import DataSourceInput from './fields/DataSourceInput.vue'; @@ -28,6 +29,7 @@ import DataSourceMethods from './fields/DataSourceMethods.vue'; import DataSourceMethodSelect from './fields/DataSourceMethodSelect.vue'; import DataSourceMocks from './fields/DataSourceMocks.vue'; import DataSourceSelect from './fields/DataSourceSelect.vue'; +import DisplayConds from './fields/DisplayConds.vue'; import EventSelect from './fields/EventSelect.vue'; import KeyValue from './fields/KeyValue.vue'; import PageFragmentSelect from './fields/PageFragmentSelect.vue'; @@ -84,6 +86,8 @@ export { default as Resizer } from './components/Resizer.vue'; export { default as CodeBlockEditor } from './components/CodeBlockEditor.vue'; export { default as FloatingBox } from './components/FloatingBox.vue'; export { default as PageFragmentSelect } from './fields/PageFragmentSelect.vue'; +export { default as DisplayConds } from './fields/DisplayConds.vue'; +export { default as CondOpSelect } from './fields/CondOpSelect.vue'; const defaultInstallOpt: InstallOptions = { // eslint-disable-next-line no-eval @@ -114,5 +118,7 @@ export default { app.component('m-fields-data-source-method-select', DataSourceMethodSelect); app.component('m-fields-data-source-field-select', DataSourceFieldSelect); app.component('m-fields-page-fragment-select', PageFragmentSelect); + app.component('m-fields-display-conds', DisplayConds); + app.component('m-fields-cond-op-select', CondOpSelect); }, }; diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index b3c03b79..fddbc219 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -677,6 +677,11 @@ export interface DataSourceFieldSelectConfig extends FormItem { notEditable?: boolean | FilterFunction; } +export interface CondOpSelectConfig extends FormItem { + type: 'cond-op'; + parentFields?: string[]; +} + /** 可新增的数据源类型选项 */ export interface DatasourceTypeOption { /** 数据源类型 */ diff --git a/packages/editor/src/utils/data-source/index.ts b/packages/editor/src/utils/data-source/index.ts index 9df6f372..2d17618e 100644 --- a/packages/editor/src/utils/data-source/index.ts +++ b/packages/editor/src/utils/data-source/index.ts @@ -215,7 +215,10 @@ export const getCascaderOptionsFromFields = ( dataSourceFieldType.push('any'); } - const children = getCascaderOptionsFromFields(field.fields, dataSourceFieldType); + let children: CascaderOption[] = []; + if (field.type && ['any', 'array', 'object'].includes(field.type)) { + children = getCascaderOptionsFromFields(field.fields, dataSourceFieldType); + } const item = { label: `${field.title || field.name}(${field.type})`, diff --git a/packages/editor/src/utils/props.ts b/packages/editor/src/utils/props.ts index 97d14cbc..e5521856 100644 --- a/packages/editor/src/utils/props.ts +++ b/packages/editor/src/utils/props.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ /* * Tencent is pleased to support the open source community by making TMagicEditor available. * @@ -18,19 +19,17 @@ import type { FormConfig, FormState, TabPaneConfig } from '@tmagic/form'; -import dataSourceService from '@editor/services/dataSource'; - -const arrayOptions = [ +export const arrayOptions = [ { text: '包含', value: 'include' }, { text: '不包含', value: 'not_include' }, ]; -const eqOptions = [ +export const eqOptions = [ { text: '等于', value: '=' }, { text: '不等于', value: '!=' }, ]; -const numberOptions = [ +export const numberOptions = [ { text: '大于', value: '>' }, { text: '大于等于', value: '>=' }, { text: '小于', value: '<' }, @@ -359,106 +358,10 @@ export const displayTabConfig: TabPaneConfig = { display: (vm: FormState, { model }: any) => model.type !== 'page', items: [ { - type: 'groupList', + type: 'display-conds', name: 'displayConds', titlePrefix: '条件组', - expandAll: true, - items: [ - { - type: 'table', - name: 'cond', - items: [ - { - type: 'data-source-field-select', - name: 'field', - value: 'key', - label: '字段', - checkStrictly: false, - dataSourceFieldType: ['string', 'number', 'boolean', 'any'], - }, - { - type: 'select', - options: (mForm, { model }) => { - const [id, ...fieldNames] = model.field; - - const ds = dataSourceService.getDataSourceById(id); - - let fields = ds?.fields || []; - let type = ''; - (fieldNames || []).forEach((fieldName: string) => { - const field = fields.find((f) => f.name === fieldName); - fields = field?.fields || []; - type = 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', - type: (mForm, { model }) => { - const [id, ...fieldNames] = model.field; - - const ds = dataSourceService.getDataSourceById(id); - - let fields = ds?.fields || []; - let type = ''; - (fieldNames || []).forEach((fieldName: string) => { - const field = fields.find((f) => f.name === fieldName); - fields = field?.fields || []; - type = field?.type || ''; - }); - - if (type === 'number') { - return 'number'; - } - - if (type === 'boolean') { - return 'select'; - } - - return 'text'; - }, - options: [ - { text: 'true', value: true }, - { text: 'false', value: false }, - ], - display: (vm, { model }) => !['between', 'not_between'].includes(model.op), - }, - { - name: 'range', - type: 'number-range', - display: (vm, { model }) => ['between', 'not_between'].includes(model.op), - }, - ], - }, - ], - }, - ], + defaultValue: [], }, ], }; diff --git a/packages/form/src/fields/Cascader.vue b/packages/form/src/fields/Cascader.vue index 0ea59364..fad3a5f6 100644 --- a/packages/form/src/fields/Cascader.vue +++ b/packages/form/src/fields/Cascader.vue @@ -49,7 +49,7 @@ const options = ref([]); const remoteData = ref(null); const checkStrictly = computed(() => filterFunction(mForm, props.config.checkStrictly, props)); -const valueSeparator = computed(() => filterFunction(mForm, props.config.valueSeparator, props)); +const valueSeparator = computed(() => filterFunction(mForm, props.config.valueSeparator, props)); const value = computed({ get() { diff --git a/packages/form/src/fields/CheckboxGroup.vue b/packages/form/src/fields/CheckboxGroup.vue index dd011c77..4990b935 100644 --- a/packages/form/src/fields/CheckboxGroup.vue +++ b/packages/form/src/fields/CheckboxGroup.vue @@ -11,7 +11,7 @@ import { computed, inject } from 'vue'; import { TMagicCheckbox, TMagicCheckboxGroup } from '@tmagic/design'; -import type { CheckboxGroupConfig, FieldProps, FormState } from '../schema'; +import type { CheckboxGroupConfig, CheckboxGroupOption, FieldProps, FormState } from '../schema'; import { filterFunction } from '../utils/form'; import { useAddField } from '../utils/useAddField'; @@ -37,7 +37,8 @@ const changeHandler = (v: Array) => { const mForm = inject('mForm'); const options = computed(() => { if (Array.isArray(props.config.options)) return props.config.options; - if (typeof props.config.options === 'function') return filterFunction(mForm, props.config.options, props); + if (typeof props.config.options === 'function') + return filterFunction(mForm, props.config.options, props) || []; return []; }); diff --git a/packages/form/src/schema.ts b/packages/form/src/schema.ts index 4bef3346..f88df948 100644 --- a/packages/form/src/schema.ts +++ b/packages/form/src/schema.ts @@ -170,6 +170,7 @@ export type FilterFunction = ( formValue: Record; prop: string; config: any; + index?: number; }, ) => T; @@ -433,18 +434,18 @@ export interface ColorPickConfig extends FormItem { type: 'colorPicker'; } +export interface CheckboxGroupOption { + value: any; + text: string; + disabled?: boolean; +} + /** * 多选框组 */ export interface CheckboxGroupConfig extends FormItem { type: 'checkbox-group'; - options: - | { - value: any; - text: string; - disabled?: boolean; - }[] - | Function; + options: CheckboxGroupOption[] | FilterFunction; } /** diff --git a/packages/form/src/utils/form.ts b/packages/form/src/utils/form.ts index 3ca1e445..efe56b98 100644 --- a/packages/form/src/utils/form.ts +++ b/packages/form/src/utils/form.ts @@ -24,6 +24,7 @@ import { ChildConfig, ContainerCommonConfig, DaterangeConfig, + FilterFunction, FormConfig, FormState, FormValue, @@ -181,20 +182,24 @@ const getDefaultValue = function (mForm: FormState | undefined, { defaultValue, return ''; }; -export const filterFunction = (mForm: FormState | undefined, config: T, props: any) => { - if (typeof config !== 'function') { - return config; +export const filterFunction = ( + mForm: FormState | undefined, + config: T | FilterFunction | undefined, + props: any, +) => { + if (typeof config === 'function') { + return (config as FilterFunction)(mForm, { + values: mForm?.initValues || {}, + model: props.model, + parent: mForm?.parentValues || {}, + formValue: mForm?.values || props.model, + prop: props.prop, + config: props.config, + index: props.index, + }); } - return config(mForm, { - values: mForm?.initValues || {}, - model: props.model, - parent: mForm?.parentValues || {}, - formValue: mForm?.values || props.model, - prop: props.prop, - config: props.config, - index: props.index, - }); + return config; }; export const display = function (mForm: FormState | undefined, config: any, props: any) { diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 2064d4b9..aafd8d8f 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -139,6 +139,7 @@ export interface MComponent { style?: { [key: string]: any; }; + displayConds?: DisplayCond[]; [key: string]: any; } @@ -285,3 +286,14 @@ export type HookData = { /** 参数 */ params?: object; }; + +export interface DisplayCondItem { + field: string[]; + op: string; + value?: any; + range?: [number, number]; +} + +export interface DisplayCond { + cond: DisplayCondItem[]; +} diff --git a/packages/ui/src/iterator-container/src/IteratorContainer.vue b/packages/ui/src/iterator-container/src/IteratorContainer.vue index 2b235659..26bb4e12 100644 --- a/packages/ui/src/iterator-container/src/IteratorContainer.vue +++ b/packages/ui/src/iterator-container/src/IteratorContainer.vue @@ -7,7 +7,7 @@ diff --git a/packages/ui/src/iterator-container/src/formConfig.ts b/packages/ui/src/iterator-container/src/formConfig.ts index 212a97c5..7cfc77c3 100644 --- a/packages/ui/src/iterator-container/src/formConfig.ts +++ b/packages/ui/src/iterator-container/src/formConfig.ts @@ -43,6 +43,13 @@ export default [ title: '子项配置', name: 'itemConfig', items: [ + { + type: 'display-conds', + name: 'displayConds', + titlePrefix: '条件组', + parentFields: (formState: any, { formValue }: any) => formValue.dsField, + defaultValue: [], + }, { name: 'layout', text: '容器布局',