diff --git a/packages/renderer/src/component-config-dependency-resolver/data-grid-component-config-dependency-resolver.ts b/packages/renderer/src/component-config-dependency-resolver/data-grid-component-config-dependency-resolver.ts index 08e99defc9ae3657c8bb04c2bbafaa075bf43722..b0f93e789106eaf13fcd671154b1738f55dceca1 100644 --- a/packages/renderer/src/component-config-dependency-resolver/data-grid-component-config-dependency-resolver.ts +++ b/packages/renderer/src/component-config-dependency-resolver/data-grid-component-config-dependency-resolver.ts @@ -50,7 +50,6 @@ export class DataGridComponentConfigDependencyResolver extends ComponentConfigDe if (readonlyConfigDeps) { configs.push({ deps: readonlyConfigDeps, config: editor?.readonly, path: `columns/${id}:editor/readonly`, type: ConfigurationType.Readonly }); } - const disabledConfigDeps = this.configDepencencyResolveService.resolve(editor?.disabled, schema); if (disabledConfigDeps) { configs.push({ deps: disabledConfigDeps, config: editor?.disabled, path: `columns/${id}:editor/disabled`, type: ConfigurationType.Disabled }); @@ -66,8 +65,26 @@ export class DataGridComponentConfigDependencyResolver extends ComponentConfigDe configs.push({ deps: allowFreeInputConfigDeps, config: allowFreeInput, path: `columns/${id}:editor/allowFreeInput` }); } } + this.resolveComboListColumn(field, schema, configs); } return configs; }, configs); } + private resolveComboListColumn(column: Record, schema: Record, configs: Configuration[]) { + const { id, editor } = column; + const { dataSourceType, dataSource, type } = editor; + if (type !== 'combo-list') { + return; + } + if (dataSourceType !== 'dynamic') { + return; + } + if (!dataSource) { + return; + } + const dataSourceConfigDeps = this.configDepencencyResolveService.resolve(dataSource, schema); + if (dataSourceConfigDeps) { + configs.push({ deps: dataSourceConfigDeps, config: dataSource, path: `columns/${id}:editor/data` }); + } + } } diff --git a/packages/renderer/src/component-config-dependency-resolver/form-group-component-config-dependency-resolver.ts b/packages/renderer/src/component-config-dependency-resolver/form-group-component-config-dependency-resolver.ts index ca9f4d6690e24e6fda89655d922c7c24e2b62258..fd352865fb92136158e846f99af13c5604141cfd 100644 --- a/packages/renderer/src/component-config-dependency-resolver/form-group-component-config-dependency-resolver.ts +++ b/packages/renderer/src/component-config-dependency-resolver/form-group-component-config-dependency-resolver.ts @@ -45,6 +45,18 @@ export class FormGroupComponentConfigDependencyResolver extends ComponentConfigD configs.push({ deps: allowFreeInputConfigDeps, config: readonly, path: '/editor/allowFreeInput' }); } } + this.resolveComboListConfig(schema, configs); return configs; } + private resolveComboListConfig(schema: Record, configs: Configuration[]) { + const { editor } = schema; + const { type, dataSourceType, dataSource } = editor; + if (type !== 'combo-list' || dataSourceType !== 'dynamic') { + return; + } + const dataConfigDeps = this.configDepencencyResolveService.resolve(dataSource, schema); + if (dataConfigDeps) { + configs.push({ deps: dataConfigDeps, config: dataSource, path: '/editor/data', type: ConfigurationType.Data }); + } + } } diff --git a/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts index d4eba54a1cb3e6b32b3443e1a7e3d51d23598a59..a5e1d0a58defb47a8f3382d8eb064ca9dcc7d9c5 100644 --- a/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts +++ b/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts @@ -174,6 +174,10 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { return; } const { id } = viewSchema; + const { dataSourceType, dataSource } = editor; + if (dataSourceType === 'dynamic' && dataSource) { + editor.data = this.configResolver.resolve(dataSource, id); + } const relatedComponent = this.formMetadataService.getRelatedComponent(id); const viewModel = this.viewModel.getModule().getViewModel(relatedComponent.id); if (beforeOpen) { diff --git a/packages/renderer/src/component-config-resolver/form-group-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/form-group-component-config-resolver.ts index 60914b268cb4914fad8f457bc1c267e7b416cf87..afb7851e1b5696e185d936f03886c19b8f5f71b3 100644 --- a/packages/renderer/src/component-config-resolver/form-group-component-config-resolver.ts +++ b/packages/renderer/src/component-config-resolver/form-group-component-config-resolver.ts @@ -26,15 +26,30 @@ export class FormGroupComponentConfigResolver extends ComponentConfigResolver { if (!isNil(allowFreeInput)) { editor.allowFreeInput = this.configResolver.resolve(allowFreeInput, id); } - if (type === 'language-textbox') { - const languageList: any[] = []; - const languages = this.languageListManager.getLanguageList() || []; - languageList.push(...languages); - if (!languageList || languageList.length < 1) { - languageList.push(...[{ code: 'en', name: 'English' }, { code: 'zh-CHS', name: '中文简体' }]); - } - editor.languages = languageList; - } + this.resolveLanguageTextbox(metadata); + this.resolveComboList(metadata); return metadata; } + private resolveLanguageTextbox(metadata: Record) { + const { editor, id } = metadata; + const { type } = editor; + if (type !== 'language-textbox') { + return; + } + const languageList: any[] = []; + const languages = this.languageListManager.getLanguageList() || []; + languageList.push(...languages); + if (!languageList || languageList.length < 1) { + languageList.push(...[{ code: 'en', name: 'English' }, { code: 'zh-CHS', name: '中文简体' }]); + } + editor.languages = languageList; + } + private resolveComboList(metadata: Record) { + const { editor, id } = metadata; + const { type, dataSourceType, dataSource } = editor; + if (type !== 'combo-list' || dataSourceType !== 'dynamic') { + return; + } + editor.data = this.configResolver.resolve(dataSource, id); + } } diff --git a/packages/renderer/src/composition/index.ts b/packages/renderer/src/composition/index.ts index e429cea29187283341d230e14544ee997aef9ca9..81f6b2854f1582975359280540fdef36454eb9c2 100644 --- a/packages/renderer/src/composition/index.ts +++ b/packages/renderer/src/composition/index.ts @@ -26,3 +26,4 @@ export * from './use-rich-editor-provide'; export * from './use-resource'; export * from './use-command-resolver'; export * from './use-navigation-guard'; +export * from './use-form-state'; diff --git a/packages/renderer/src/composition/use-form-state.ts b/packages/renderer/src/composition/use-form-state.ts new file mode 100644 index 0000000000000000000000000000000000000000..645a599b5defd853b671fa3784b58bf4198d2c68 --- /dev/null +++ b/packages/renderer/src/composition/use-form-state.ts @@ -0,0 +1,69 @@ +import { Injector, Module, StateMachineState } from "@farris/devkit-vue"; +import { useFormMetadataService } from "./use-form-metadata-service"; +import { useRender } from "./use-render"; +import { watch } from "vue"; +import { Ref } from "vue"; +import { Terminal } from "@farris/command-services-vue"; + +export function useFormState(injector: Injector, module: Module, terminal: Ref) { + if (terminal.value !== Terminal.PC) { + return; + } + const formMetadataService = useFormMetadataService(injector); + const frameComponent = formMetadataService.getFrameComponent(); + const render = useRender(injector); + if (!frameComponent) { + return; + } + const viewModel = module.getViewModel(frameComponent?.id); + if (!viewModel) { + return; + } + const rootComponentId = viewModel.id; + // watch(() => render.value, (renderValue) => { + // if (!renderValue) { + // return; + // } + // const rootComponent = renderValue.componentManager.get(rootComponentId).$el; + // if (!rootComponent) { + // return; + // } + // addCssClass(rootComponent, 'f-form-state-default'); + // }); + function addCssClass(element: any, className: string) { + if (!element || !className) { + return; + } + const originalClassName: string = element.className || ''; + if (!originalClassName.includes(className)) { + element.className = `${originalClassName} ${className}`; + } + } + function removeCssClass(element: any, className: string) { + if (!element || !className) { + return; + } + const originalClassName: string = element.className || ''; + if (originalClassName.includes(className)) { + element.className = originalClassName.split(' ').filter(p => p && p !== className).join(' '); + } + } + viewModel.stateMachine?.watchChange((state) => { + const rootComponent = render.value.componentManager.get(rootComponentId).$el; + if (!rootComponent) { + return; + } + const pageState = viewModel.stateMachine?.getState().currentPageState; + if (pageState === 'add') { + addCssClass(rootComponent, 'f-form-state-create'); + removeCssClass(rootComponent, 'f-form-state-default'); + } else if (pageState === 'edit') { + addCssClass(rootComponent, 'f-form-state-edit'); + removeCssClass(rootComponent, 'f-form-state-default'); + } else if (pageState === 'init') { + addCssClass(rootComponent, 'f-form-state-default'); + removeCssClass(rootComponent, 'f-form-state-create'); + removeCssClass(rootComponent, 'f-form-state-edit'); + } + }); +} diff --git a/packages/renderer/src/config/types.ts b/packages/renderer/src/config/types.ts index 585a323a559a9e75e046d73aa295ca5dc4d64fd9..70a7bb98b56cc86f93b1c1392612e4e0e452d0af 100644 --- a/packages/renderer/src/config/types.ts +++ b/packages/renderer/src/config/types.ts @@ -16,7 +16,8 @@ export enum ConfigurationType { Required = 'Required', Visible = 'Visible', Disabled = 'Disabled', - ActiveDate = 'ActiveDate' + ActiveDate = 'ActiveDate', + Data = 'Data' } export interface Configuration { config: string | boolean; diff --git a/packages/renderer/src/event-handler/component-ready-event-handler.ts b/packages/renderer/src/event-handler/component-ready-event-handler.ts index 41e9ec912c6fb719b1d2fd411f85a9fcca88bc25..09b69ec87eba46ce4fdaa7608487beeaf89d072e 100644 --- a/packages/renderer/src/event-handler/component-ready-event-handler.ts +++ b/packages/renderer/src/event-handler/component-ready-event-handler.ts @@ -14,12 +14,32 @@ export class ComponentReadyEventHandler { } dispose(): void { - this.emitter.on('component:ready', (payload: ViewEvent) => this.onComponentReady(payload)); + this.emitter.off('component:ready', (payload: ViewEvent) => this.onComponentReady(payload)); } private onComponentReady(event: ViewEvent) { const { onComponentReady } = useUIBinding(this.injector); const payload = { ref: ref(event.payload), id: event.token, type: event.type }; onComponentReady(payload); + this.bindFrameComponentReadyEvent(event); + } + private bindFrameComponentReadyEvent(event: ViewEvent) { + const frameComponent = this.formMetadataService.getFrameComponent(); + if (!frameComponent) { + return; + } + const { id } = frameComponent; + if (id === event.token) { + this.addCssClass(event.payload.value.$el, 'f-form-state-default'); + } + } + private addCssClass(element: any, className: string) { + if (!element || !className) { + return; + } + const originalClassName: string = element.className || ''; + if (!originalClassName.includes(className)) { + element.className = `${originalClassName} ${className}`; + } } } diff --git a/packages/renderer/src/form-engine/form-engine.ts b/packages/renderer/src/form-engine/form-engine.ts index 73a45f9c87f5c8325ddc298b4dad747de4be9fc9..9004c03f0a1d8b07eab5f265a1ed494cc61920d0 100644 --- a/packages/renderer/src/form-engine/form-engine.ts +++ b/packages/renderer/src/form-engine/form-engine.ts @@ -203,27 +203,37 @@ export class FormEngine { private mergeArray(target: any, source: any) { if (Array.isArray(target) && Array.isArray(source)) { const mergedMap = new Map(); - - // 先将目标数组元素添加到Map + const objectItem = source.find(item => item && typeof item === 'object'); + // 如果全是简单类型,直接覆盖 + if (!objectItem) { + return source; + } + const keys = ['id']; + // 优先使用id,否则按顺序查找 + const key = keys.find(property => objectItem[property] != null); + if (!key) { + return source; + } target.forEach(item => { - if (item.id != null) { - mergedMap.set(item.id, cloneDeep(item)); + if (item[key] != null) { + mergedMap.set(item[key], cloneDeep(item)); } }); // 合并源数组元素到Map,存在则深度合并 source.forEach(item => { - if (item.id != null) { - const existing = mergedMap.get(item.id); + if (item[key] != null) { + const existing = mergedMap.get(item[key]); if (existing) { - mergedMap.set(item.id, merge(existing, mergeWith(existing, item, this.mergeArray.bind(this)))); + // 合并现有元素和当前元素 + mergedMap.set(item[key], mergeWith(existing, item, this.mergeArray.bind(this))); } else { - mergedMap.set(item.id, cloneDeep(item)); + mergedMap.set(item[key], cloneDeep(item)); } } }); - - return Array.from(mergedMap.values()); + const combined = Array.from(mergedMap.values()); + return combined; } } } diff --git a/packages/renderer/src/page.vue b/packages/renderer/src/page.vue index 9a7942070493729fff93bf38918ca9a5dd35a244..2c9d6d3e3ddbd8ad79b461288b9323f15cccb989 100644 --- a/packages/renderer/src/page.vue +++ b/packages/renderer/src/page.vue @@ -7,7 +7,8 @@ import { useNavigation, usePagination, useFormInjector, useEventHandlers, useModuleCreator, useModelValue, useSession, useComponentConfigDependencyResolver, useHtmlTemplateTransformer, useTranslate, useCustomCss, useEditorAssetsPathProvide, useResource, - useNavigationGuard + useNavigationGuard, + useFormState } from './composition/index'; import { useEvent } from './event/index'; import { useCommunication } from './communications/index'; @@ -82,6 +83,7 @@ useResource(metadatas); /** 富文本编辑器资源路径 */ useEditorAssetsPathProvide(formInjector); useNavigationGuard(formInjector, terminal, metadatas); +useFormState(formInjector, module, terminal); // 路由事件 // useRouterEvent(formInjector,pageId) diff --git a/packages/ui-vue/components/data-grid/src/data-grid.component.tsx b/packages/ui-vue/components/data-grid/src/data-grid.component.tsx index 3a55acccc89f41a44f8c910a1ab8131747193ddc..0723a5618365ce884c7184b9fc3ef9da1cc3d12f 100644 --- a/packages/ui-vue/components/data-grid/src/data-grid.component.tsx +++ b/packages/ui-vue/components/data-grid/src/data-grid.component.tsx @@ -195,7 +195,7 @@ export default defineComponent({ }); const gridContentStyle = computed(() => { - const styleObject: { paddingBottom?: string, paddingRight?: string } = {}; + const styleObject: { paddingBottom?: string, paddingRight?: string; } = {}; if (useVirtualScrollComposition.shouldShowHorizontalScrollbar.value) { styleObject.paddingBottom = '10px'; } @@ -449,7 +449,7 @@ export default defineComponent({ } function selectItemById(dataItemId: string, options: { - isCurrent?: boolean + isCurrent?: boolean; } = { isCurrent: true }) { @@ -503,7 +503,7 @@ export default defineComponent({ } function unSelectItemByIds(dataItemIds: string[], options: { - clearCurrent?: boolean + clearCurrent?: boolean; } = { clearCurrent: true }) { @@ -633,12 +633,12 @@ export default defineComponent({ } function saveRow(e: MouseEvent, row: VisualData) { - const oldValues: { [key: string]: any } = {}; - const newValues: { [key: string]: any } = {}; + const oldValues: { [key: string]: any; } = {}; + const newValues: { [key: string]: any; } = {}; const columns: DataColumn[] = []; const cells: VisualDataCell[] = []; // 帮助组件上下文 - const editors: { [key: string]: any } = {}; + const editors: { [key: string]: any; } = {}; Object.keys(row.data) .filter((key: string) => row.data[key].column?.dataType !== COMMAND_COLUMN_DATA_TYPE && row.data[key].column?.dataType !== SETTING_COLUMN_DATA_TYPE) @@ -678,7 +678,7 @@ export default defineComponent({ useEditComposition.showRowEditPanel.value = false; }}>取消 { - saveRow(e, currentRow) + saveRow(e, currentRow); }}>确定 }} @@ -703,8 +703,8 @@ export default defineComponent({ // e.stopPropagation(); mouseInContent.value = false; }} - onMouseover={(e: MouseEvent) => { e.stopPropagation(); mouseInContent.value = true }} - onMouseleave={(e: MouseEvent) => { e.stopPropagation(); mouseInContent.value = false }} + onMouseover={(e: MouseEvent) => { e.stopPropagation(); mouseInContent.value = true; }} + onMouseleave={(e: MouseEvent) => { e.stopPropagation(); mouseInContent.value = false; }} > {gridContentRef.value && renderDataGridSidebar(visibleDatas)} {gridContentRef.value && renderDataArea()} diff --git a/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx b/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx index 951dec2f221479221ee6f03f95d316257e161826..21f26d26464e848fc9604d58e0cbe445c636e810 100644 --- a/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx +++ b/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx @@ -385,21 +385,32 @@ const FDynamicView = defineComponent({ function mergeArray(target: any, source: any) { if (Array.isArray(target) && Array.isArray(source)) { const mergedMap = new Map(); + const objectItem = source.find(item => item && typeof item === 'object'); + // 如果全是简单类型,直接覆盖 + if (!objectItem) { + return source; + } + const keys = ['id']; + // 优先使用id,否则按顺序查找 + const key = keys.find(property => objectItem[property] != null); + if (!key) { + return source; + } target.forEach(item => { - if (item.id != null) { - mergedMap.set(item.id, item); + if (item[key] != null) { + mergedMap.set(item[key], item); } }); // 合并源数组元素到Map,存在则深度合并 source.forEach(item => { - if (item.id != null) { - const existing = mergedMap.get(item.id); + if (item[key] != null) { + const existing = mergedMap.get(item[key]); if (existing) { // 合并现有元素和当前元素 - mergedMap.set(item.id, mergeWith(existing, item, mergeArray)); + mergedMap.set(item[key], mergeWith(existing, item, mergeArray)); } else { - mergedMap.set(item.id, cloneDeep(item)); + mergedMap.set(item[key], cloneDeep(item)); } } });