diff --git a/packages/charts-vue/components/charts-bar/index.ts b/packages/charts-vue/components/charts-bar/index.ts index a489598686f94f4f701686a33f62a6a5ec7c93bb..84327bb5076181b70c1b99a0136702d0f6b15920 100644 --- a/packages/charts-vue/components/charts-bar/index.ts +++ b/packages/charts-vue/components/charts-bar/index.ts @@ -1,13 +1,20 @@ -import FChartsBar from './src/charts-bar.component'; -// import { propsResolver } from './src/charts-bar.props'; -import { withInstall } from '@farris/charts-vue/components/charts-common'; +import FChartsBarInstallless from './src/charts-bar.component'; +import { propsResolverGenerator } from './src/charts-bar.props'; +import { withInstall, withRegister, withRegisterDesigner } from '@farris/charts-vue/components/charts-common'; +import ChartsBarDesign from './src/designer/charts-bar.design.component'; export * from './src/charts-bar.props'; +const CHARTS_BAR_REGISTERED_NAME = 'charts-bar'; // FChartsBar.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { // componentMap['charts-bar'] = FChartsBar; // propsResolverMap['charts-bar'] = propsResolver; // }; +const FChartsBar = withInstall(FChartsBarInstallless) + +withRegister(FChartsBar, {name: CHARTS_BAR_REGISTERED_NAME, propsResolverGenerator}) +withRegisterDesigner(FChartsBar, { name: CHARTS_BAR_REGISTERED_NAME, propsResolverGenerator, designerComponent: ChartsBarDesign }); + export { FChartsBar }; -export default withInstall(FChartsBar); +export default FChartsBar diff --git a/packages/charts-vue/components/charts-bar/src/charts-bar.component.tsx b/packages/charts-vue/components/charts-bar/src/charts-bar.component.tsx index c67badb15a894f0d0bf8435f55f1ce796c878560..b9d16577008326196b780bf0bde3cad93cac863a 100644 --- a/packages/charts-vue/components/charts-bar/src/charts-bar.component.tsx +++ b/packages/charts-vue/components/charts-bar/src/charts-bar.component.tsx @@ -46,10 +46,12 @@ export default defineComponent({ const barseries = { ...series }; barseries.type = 'bar'; return barseries; - }) + }), + color: props.color }; chartInstance.setOption(baseOption, true); + } const initChart = () => { @@ -60,8 +62,8 @@ export default defineComponent({ } const theme = getTheme(); - chartInstance = echarts.init(chartRef.value, theme, {}); + updateChart(); @@ -123,7 +125,8 @@ export default defineComponent({ width: props.width, height: props.height }} - /> + /> + ); } }); diff --git a/packages/charts-vue/components/charts-bar/src/charts-bar.props.ts b/packages/charts-vue/components/charts-bar/src/charts-bar.props.ts index bb0ff26eab252b2189311b3bdde9cb9a88307c89..e70dd80f13f122266d058948bf802acbab69d316 100644 --- a/packages/charts-vue/components/charts-bar/src/charts-bar.props.ts +++ b/packages/charts-vue/components/charts-bar/src/charts-bar.props.ts @@ -5,6 +5,10 @@ import { SelectProps, TooltipProps, SelectedModeType, SamplingType, TextStyleProps, LegendProps, TitleProps, GridProps, DataZoomProps, EmphasisProps } from '@farris/charts-vue/components/charts-common'; +import { getPropsResolverGenerator } from '@farris/charts-vue/components/dynamic-resolver'; +import chartsBarSchema from './schema/charts-bar.schema.json'; +import { schemaMapper } from './schema/schema-mapper'; +import { schemaResolver } from './schema/schema-resolver'; export const barSeriesProps = { @@ -112,14 +116,14 @@ export type BarSeriesProps = ExtractPropTypes; // Bar组件的属性 export const chartsBarProps = { - // /** - // * 组件标识 - // */ - // id: { type: String as PropType, default: 'charts-bar', required: true }, - // /** - // * 组件类型,默认是 'bar' - // */ - // type: { type: String as PropType, default: 'bar', required: true }, + /** + * 组件标识 + */ + id: { type: String as PropType, default: 'charts-bar', required: true }, + /** + * 组件类型,默认是 'bar' + */ + type: { type: String as PropType, default: 'bar', required: true }, title: { type: Object as PropType }, @@ -137,15 +141,15 @@ export const chartsBarProps = { backgroundColor: { type: String }, - width: { type: [String, Number] }, + width: { type: [String, Number], default: '100%'}, - height: { type: [String, Number] }, + height: { type: [String, Number]}, textStyle: { type: Object as PropType }, - xAxis: { type: Object as PropType }, + xAxis: { type: Object as PropType}, - yAxis: { type: Object as PropType }, + yAxis: { type: Object as PropType}, /** * 数据系列 */ @@ -158,3 +162,10 @@ export const chartsBarProps = { } as Record; export type ChartsBarProps = ExtractPropTypes; + +export const propsResolverGenerator = getPropsResolverGenerator( + chartsBarProps, + chartsBarSchema, + schemaMapper, + schemaResolver +); \ No newline at end of file diff --git a/packages/charts-vue/components/charts-bar/src/designer/charts-bar.design.component.tsx b/packages/charts-vue/components/charts-bar/src/designer/charts-bar.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d75ee3c3708697b316498e2e1e9d3056491aa60a --- /dev/null +++ b/packages/charts-vue/components/charts-bar/src/designer/charts-bar.design.component.tsx @@ -0,0 +1,53 @@ +import { computed, defineComponent, inject, onMounted, ref, SetupContext, watch } from 'vue'; +import { DesignerHostService, DesignerItemContext } from '@farris/charts-vue/components/charts-common/src/types'; +import { useDesignerComponent } from '@farris/charts-vue/components/charts-common/src/compositions/use-designer-component'; + +import { ChartsBarProps, chartsBarProps } from '../charts-bar.props'; +import FChartsBar from '../charts-bar.component'; +import { useDesignerRules } from './use-designer-rules'; + +export default defineComponent({ + name: 'FmChartsBarDesign', + props: chartsBarProps, + emits: ['click'], + setup(props: ChartsBarProps, context: SetupContext) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject('design-item-context') as DesignerItemContext; + const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + + context.expose(componentInstance.value); + + const chartsBarProps = computed(() => ({ + name: props.name, + color: props.color, + grid: props.grid, + height: props.height, + width: props.width, + title: props.title, + xAxis: props.xAxis, + tooltip: props.tooltip, + legend: props.legend, + toolbox: props.toolbox, + animation: props.animation, + animationDuration: props.animationDuration, + animationEasingUpdate: props.animationEasingUpdate, + series: props.series.map((series) => { + const barseries = { ...series }; + barseries.type = 'bar'; + return barseries; + }), + })); + + return () => { + return ( + + ); + }; + } +}); diff --git a/packages/charts-vue/components/charts-bar/src/designer/use-designer-rules.ts b/packages/charts-vue/components/charts-bar/src/designer/use-designer-rules.ts new file mode 100644 index 0000000000000000000000000000000000000000..c967e21117e861ef301d043dfd3238eca7991a35 --- /dev/null +++ b/packages/charts-vue/components/charts-bar/src/designer/use-designer-rules.ts @@ -0,0 +1,57 @@ +import { ref } from "vue"; +import { DraggingResolveContext } from "@farris/charts-vue/components/charts-common/src/types"; +import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/charts-vue/components/charts-common/src/types"; +import { ChartsBarProperty } from "../property-config/charts-bar.property-config"; + +export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { + + const triggerBelongedComponentToMoveWhenMoved = ref(false); + + const triggerBelongedComponentToDeleteWhenDeleted = ref(false); + + function canAccepts(draggingContext: DraggingResolveContext): boolean { + + return false; + } + + + function checkCanMoveComponent() { + return true; + } + function checkCanDeleteComponent() { + return true; + } + + function hideNestedPaddingInDesginerView() { + return true; + } + + function getStyles(): string { + return ' '; + } + + function getDesignerClass(): string { + return ' '; + } + + /** + * 获取属性配置 + */ + function getPropsConfig(componentId: string) { + const componentProp = new ChartsBarProperty(componentId, designerHostService); + const { schema } = designItemContext; + return componentProp.getPropertyConfig(schema); + } + + return { + canAccepts, + // triggerBelongedComponentToMoveWhenMoved, + // triggerBelongedComponentToDeleteWhenDeleted, + // checkCanMoveComponent, + // checkCanDeleteComponent, + hideNestedPaddingInDesginerView, + // getStyles, + // getDesignerClass, + getPropsConfig + }; +} diff --git a/packages/charts-vue/components/charts-bar/src/property-config/charts-bar.property-config.ts b/packages/charts-vue/components/charts-bar/src/property-config/charts-bar.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..f37769a1f9bfab3a4698c1484f4863ed86910cf4 --- /dev/null +++ b/packages/charts-vue/components/charts-bar/src/property-config/charts-bar.property-config.ts @@ -0,0 +1,105 @@ +import { BaseControlProperty, ToolbarItemProperty } from "@farris/charts-vue/components/charts-common/src/props"; +import { chartsBarGridConverter } from "@farris/charts-vue/components/dynamic-resolver/src/converter"; + + +export class ChartsBarProperty extends BaseControlProperty { + + private toolbarItemProperty: ToolbarItemProperty; + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + this.toolbarItemProperty = new ToolbarItemProperty(componentId, designerHostService); + } + public getPropertyConfig(propertyData: any) { + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + + this.propertyConfig.categories['behavior'] = this.getChartsBarBehaviorConfig(propertyData); + + this.propertyConfig.categories['grid'] = this.getChartsBarGridConfig(propertyData); + + return this.propertyConfig; + } + private getChartsBarGridConfig(propertyData) { + return{ + title: 'Grid', + $converter: chartsBarGridConverter, + properties: { + height: { + title: 'height', + type: 'string', + // editor: { + // type: "collection-property-editor", + // textField: 'text', + // modalTitle: '右侧工具栏编辑器', + // onSelectionChange: ({ selectedData, propertyConfig }) => { + // propertyConfig.value = this.toolbarItemProperty.getPropertyConfig(selectedData.value, { + // showAppearance: true, + // }); + // }, + // defaultComponentSchema: { + // id: 'button', + // text: '按钮', + // visible: true, + // disabled: false + // } + // } + } + } + }; + } + + private getChartsBarBehaviorConfig(propertyData) { + return { + description: "基本信息", + title: "行为", + properties: { + height:{ + title: "height", + type: 'string', + }, + width:{ + title: "width", + type: 'string', + }, + name: { + title: "name", + type: 'string', + }, + xAxis: { + title: "xAxis", + type: 'string', + }, + yAxis: { + title: "yAxis", + type: 'string', + }, + color: { + title: "color", + type: 'string', + }, + + legend: { + title: "legend", + type: 'string', + }, + title: { + title: "title", + type: 'string', + }, + textStyle: { + title: "textStyle", + type: 'string', + } + , + series: { + title: "series", + type: 'array', + } + + } + }; + } + +} diff --git a/packages/charts-vue/components/charts-bar/src/schema/charts-bar.schema.json b/packages/charts-vue/components/charts-bar/src/schema/charts-bar.schema.json index 4650e6a7580d64bf0f42e408db50caf0723234b1..9be01c459795a2a6c76b2b7be2696b180bb48998 100644 --- a/packages/charts-vue/components/charts-bar/src/schema/charts-bar.schema.json +++ b/packages/charts-vue/components/charts-bar/src/schema/charts-bar.schema.json @@ -12,10 +12,11 @@ "type": { "description": "The type string of Input Group component", "type": "string", - "default": "bar" + "default": "charts-bar" }, "name": { - "type": "string" + "type": "string", + "default": "charts-bar" }, "code": { "type": "string" @@ -828,7 +829,7 @@ }, "data": { "type": "array", - "default": [] + "default": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] } } }, @@ -882,7 +883,17 @@ } }, - "default": [] + "default": [{"data": [120, 200, 150, 80, 70, 110, 130]}] + }, + "height":{ + "description": "div de height", + "type": "string", + "default": "300px" + }, + "width":{ + "description": "div de width", + "type": "string", + "default": "100%" } }, diff --git a/packages/charts-vue/components/charts-bar/src/schema/schema-mapper.ts b/packages/charts-vue/components/charts-bar/src/schema/schema-mapper.ts new file mode 100644 index 0000000000000000000000000000000000000000..04d7b384ed9dca71de8e817004a00ed2c7ce6209 --- /dev/null +++ b/packages/charts-vue/components/charts-bar/src/schema/schema-mapper.ts @@ -0,0 +1,17 @@ +import { resolveAppearance, MapperFunction } from '@farris/charts-vue/components/dynamic-resolver'; + + +function createTemplatePropResolver(name: string): MapperFunction { + return (key: string, content: string) => { + if (!content) { + return {}; + } + const templateInfo = { name, content }; + + return { text: templateInfo }; + }; +} + +export const schemaMapper = new Map([ + ['appearance', resolveAppearance] +]); diff --git a/packages/charts-vue/components/charts-bar/src/schema/schema-resolver.ts b/packages/charts-vue/components/charts-bar/src/schema/schema-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..6fb4684c3ddced57ba172fb60a6cd101f85079ec --- /dev/null +++ b/packages/charts-vue/components/charts-bar/src/schema/schema-resolver.ts @@ -0,0 +1,5 @@ +import { DynamicResolver } from '@farris/charts-vue/components/dynamic-resolver'; + +export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { + return schema; +} diff --git a/packages/charts-vue/components/charts-common/index.ts b/packages/charts-vue/components/charts-common/index.ts index a3091d74020c3318a65d61da5bc720f8d77062b8..32a8ffcd81fbe724ba8ba47b2a9a851616318181 100644 --- a/packages/charts-vue/components/charts-common/index.ts +++ b/packages/charts-vue/components/charts-common/index.ts @@ -30,4 +30,9 @@ export * from './src/compositions/use-parallel-event'; export * from './src/compositions/use-render-event'; export * from './src/compositions/use-timeline-event'; export * from './src/compositions/use-theme'; -export * from './src/utils/with-install'; +export * from './src/utils'; + +// export * from './src/utils/with-install'; +// export * from './src/utils/src/with-register'; +// export * from './src/utils/src/with-register-designer'; + diff --git a/packages/charts-vue/components/charts-common/src/compositions/use-designer-component.ts b/packages/charts-vue/components/charts-common/src/compositions/use-designer-component.ts new file mode 100644 index 0000000000000000000000000000000000000000..27e458d96d9227c07ad585c930682de5b3d16fc7 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/compositions/use-designer-component.ts @@ -0,0 +1,229 @@ +import { Ref, ref } from "vue"; +import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext, UseDesignerRules } from "../types/designer-rule"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "../types/designer-component"; +import { getSchemaByTypeForDesigner } from '@farris/charts-vue/components/dynamic-resolver/src/resolver/schema/schema-resolver-design'; + +export function useDesignerComponent( + elementRef: Ref, + designItemContext?: DesignerItemContext, + designerRules?: UseDesignerRules +): Ref { + + const styles = (designerRules && designerRules.getStyles && designerRules.getStyles()) || ''; + const designerClass = (designerRules && designerRules.getDesignerClass && designerRules.getDesignerClass()) || ''; + const componentInstance = ref(); + /** + * 校验组件是否支持移动 + */ + function checkCanMoveComponent(): boolean { + if (designItemContext?.schema.componentType === 'frame') { + return false; + } + if (designerRules && designerRules.checkCanMoveComponent) { + return designerRules.checkCanMoveComponent(); + } + return true; + } + + /** + * 校验组件是否支持选中父级 + */ + function checkCanSelectParentComponent(): boolean { + return false; + } + + /** + * 校验组件是否支持删除 + */ + function checkCanDeleteComponent() { + if (designItemContext?.schema.componentType === 'frame') { + return false; + } + if (designerRules && designerRules.checkCanDeleteComponent) { + return designerRules.checkCanDeleteComponent(); + } + return true; + } + + /** + * 判断在可视化区域中是否隐藏容器间距和线条 + */ + function hideNestedPaddingInDesginerView() { + if (designItemContext?.schema.componentType === 'frame') { + return true; + } + if (designerRules && designerRules.hideNestedPaddingInDesginerView) { + return designerRules.hideNestedPaddingInDesginerView(); + } + return false; + } + + /** + * 获取组件在表单DOM中所属的Component的实例 + * @param componentInstance 组件实例 + */ + function getBelongedComponentInstance(currentComponentInstance?: Ref): DesignerComponentInstance | null { + if (!currentComponentInstance || !currentComponentInstance.value) { + return null; + } + if (currentComponentInstance.value.schema && currentComponentInstance.value.schema.type === 'component') { + return currentComponentInstance.value; + } + const parent = ref(currentComponentInstance?.value.parent) as Ref; + const grandParent = getBelongedComponentInstance(parent); + if (grandParent) { + return grandParent; + } + return null; + } + + function getDraggableDesignItemElement(context: DesignerItemContext = designItemContext as DesignerItemContext): Ref | null { + if (designerRules?.getDraggableDesignItemElement) { + return designerRules.getDraggableDesignItemElement(context); + } + const { componentInstance, designerItemElementRef } = context; + if (!componentInstance || !componentInstance.value) { + return null; + } + const { getCustomButtons } = componentInstance.value; + if (componentInstance.value.canMove || (getCustomButtons && getCustomButtons()?.length)) { + return designerItemElementRef; + } + return getDraggableDesignItemElement(context.parent); + } + + /** + * 判断是否可以接收拖拽新增的子级控件 + * @param data 新控件的类型、所属分类 + * @returns boolean + */ + function canAccepts(draggingContext: DraggingResolveContext) { + return !!designerRules && designerRules.canAccepts(draggingContext); + } + + function getDraggingDisplayText() { + return designItemContext?.schema.label || designItemContext?.schema.title || designItemContext?.schema.name; + } + + /** + * 控件可以拖拽到的最外层容器,用于限制控件向外层容器拖拽的范围。不写则不限制 + */ + function getDragScopeElement(): HTMLElement | undefined { + return undefined; + } + + /** + * 移动控件后事件:在可视化设计器中,将现有的控件移动到容器中 + * @param element 移动的源DOM结构 + */ + function onAcceptMovedChildElement(element: DesignerHTMLElement, sourceContainer?: DesignerHTMLElement) { + if (!element || !sourceContainer) { + return; + } + if (designerRules?.onAcceptMovedChildElement) { + designerRules.onAcceptMovedChildElement(element, sourceContainer); + } + } + /** + * 当前容器接收新创建的子控件,返回子控件schema结构 + */ + function addNewChildComponentSchema(resolveContext: DraggingResolveContext, designerHostService: DesignerHostService) { + const { componentType } = resolveContext; + let componentSchema = getSchemaByTypeForDesigner(componentType, resolveContext, designerHostService) as ComponentSchema; + if (designerRules && designerRules.onResolveNewComponentSchema) { + componentSchema = designerRules.onResolveNewComponentSchema(resolveContext, componentSchema); + } + + const typePrefix = componentType.toLowerCase().replace(/-/g, '_'); + if (componentSchema && !componentSchema.id && componentSchema.type === componentType) { + componentSchema.id = `${typePrefix}_${Math.random().toString().slice(2, 6)}`; + } + return componentSchema; + } + + /** + * 移动内部控件后事件:在可视化设计器中,将现有的控件移动到容器中 + * @param element 移动的源DOM结构 + */ + function onChildElementMovedOut(element: HTMLElement) { + + } + + /** + * 获取控件属性配置 + */ + function getPropConfig(...args) { + if (designerRules && designerRules.getPropsConfig) { + return designerRules.getPropsConfig(...args); + } + } + /** + * 控件删除后事件 + */ + function onRemoveComponent() { + // 调用当前控件的删除后事件 + if (designerRules && designerRules.onRemoveComponent) { + designerRules.onRemoveComponent(); + } + // 递归触发子级控件的删除后事件 + if (designItemContext?.schema.contents) { + designItemContext.schema.contents.map(content => { + let contentSchemaId = content.id; + if (content.type === 'component-ref') { + contentSchemaId = content.component; + } + const contentElement: any = elementRef.value.querySelector(`#${contentSchemaId}-design-item`); + if (contentElement?.componentInstance?.value.onRemoveComponent) { + contentElement.componentInstance.value.onRemoveComponent(); + } + }); + } + } + /** + * 校验组件是否支持删除 + */ + function getCustomButtons() { + if (designerRules && designerRules.getCustomButtons) { + return designerRules.getCustomButtons(); + } + + } + + /** + * 控件属性变更后事件 + */ + function onPropertyChanged(event: any) { + if (designerRules && designerRules.onPropertyChanged) { + return designerRules.onPropertyChanged(event); + } + } + componentInstance.value = { + canMove: checkCanMoveComponent(), + canSelectParent: checkCanSelectParentComponent(), + canDelete: checkCanDeleteComponent(), + canNested: !hideNestedPaddingInDesginerView(), + contents: designItemContext?.schema.contents, + elementRef, + parent: designItemContext?.parent?.componentInstance, + schema: designItemContext?.schema, + styles, + designerClass, + canAccepts, + getBelongedComponentInstance, + getDraggableDesignItemElement, + getDraggingDisplayText, + getPropConfig, + getDragScopeElement, + onAcceptMovedChildElement, + onChildElementMovedOut, + addNewChildComponentSchema, + triggerBelongedComponentToMoveWhenMoved: !!designerRules && designerRules.triggerBelongedComponentToMoveWhenMoved || ref(false), + triggerBelongedComponentToDeleteWhenDeleted: !!designerRules && designerRules.triggerBelongedComponentToDeleteWhenDeleted || ref(false), + onRemoveComponent, + getCustomButtons, + onPropertyChanged + } as DesignerComponentInstance; + + return componentInstance as any; + +} diff --git a/packages/charts-vue/components/charts-common/src/props/base-property.ts b/packages/charts-vue/components/charts-common/src/props/base-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..ce8c8017928e7f4b19c7342e0f57fd0cf2ca8c93 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/base-property.ts @@ -0,0 +1,400 @@ +import { cloneDeep } from "lodash-es"; +import { ExpressionProperty } from "./expression-property"; +import { usePropertyEditor } from "./use-property-editor"; +import { DgControl } from "./dg-control"; +import { DesignerComponentInstance, PropertyChangeObject } from "../types"; +import { FormSchemaEntity } from "../types"; + +/** + * 控件属性基类 + */ +export class BaseControlProperty { + public componentId: string; + + public viewModelId: string; + + public eventsEditorUtils: any; + + public formSchemaUtils: any; + public formMetadataConverter: any; + public designViewModelUtils: any; + public designViewModelField: any; + public controlCreatorUtils: any; + public designerHostService: any; + + schemaService: any = null; + + metadataService: any = null; + + protected propertyConfig = { + type: 'object', + categories: {} + }; + + constructor(componentId: string, designerHostService: any) { + this.componentId = componentId; + this.designerHostService = designerHostService; + this.eventsEditorUtils = designerHostService['eventsEditorUtils']; + this.formSchemaUtils = designerHostService['formSchemaUtils']; + this.formMetadataConverter = designerHostService['formMetadataConverter']; + this.viewModelId = this.formSchemaUtils?.getViewModelIdByComponentId(componentId) || ''; + this.designViewModelUtils = designerHostService['designViewModelUtils']; + this.controlCreatorUtils = designerHostService['controlCreatorUtils']; + this.metadataService = designerHostService['metadataService']; + this.schemaService = designerHostService['schemaService']; + } + + getTableInfo() { + return this.schemaService?.getTableInfoByViewModelId(this.viewModelId); + } + + setDesignViewModelField(propertyData: any) { + const bindingFieldId = propertyData.binding && propertyData.binding.type === 'Form' && propertyData.binding.field; + // 视图模型中[字段更新时机]属性现在要在控件上维护,所以在控件上复制一份属性值 + if (bindingFieldId) { + if (!this.designViewModelField) { + const dgViewModel = this.designViewModelUtils.getDgViewModel(this.viewModelId); + this.designViewModelField = dgViewModel.fields.find(f => f.id === bindingFieldId); + } + propertyData.updateOn = this.designViewModelField?.updateOn; + } + } + + getBasicPropConfig(propertyData: any): any { + return { + description: 'Basic Information', + title: '基本信息', + properties: { + id: { + description: '组件标识', + title: '标识', + type: 'string', + readonly: true + }, + type: { + description: '组件类型', + title: '控件类型', + type: 'select', + editor: { + type: 'combo-list', + textField: 'name', + valueField: 'value', + idField: 'value', + editable: false, + data: [{ value: propertyData.type, name: DgControl[propertyData.type] && DgControl[propertyData.type].name }] + } + } + } + }; + + } + + + protected getAppearanceConfig(propertyData = null, properties = {}, setPropertyRelates?: (changeObject, propertyData, parameters) => any): any { + const appearanceBasic = { + title: "外观", + description: "Appearance", + }; + const appearancePoperties = { + class: { + title: "class样式", + type: "string", + description: "组件的CSS样式", + $converter: "/converter/appearance.converter" + }, + style: { + title: "style样式", + type: "string", + description: "组件的样式", + $converter: "/converter/appearance.converter" + } + + }; + for (const key in properties) { + // 合并属性,保留原属性值 + appearancePoperties[key] = Object.assign(appearancePoperties[key] || {}, properties[key]); + } + return { + ...appearanceBasic, properties: { ...appearancePoperties }, setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + if (!changeObject) { + return; + } + switch (changeObject && changeObject.propertyID) { + case 'class': case 'style': { + changeObject.needChangeCanvas = true; + break; + } + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, propertyData, parameters); + } + } + + }; + } + protected getPropertyEditorParams(propertyData, propertyTypes: any = [], propertyName = 'visible', constInfos = {}, variableInfos = {}) { + const { getVariables, getControlName, getStateMachines } = usePropertyEditor(this.designerHostService); + const targetType = this.getRealTargetType(propertyData); + const newPropertyTypes = propertyTypes && propertyTypes.length > 0 ? propertyTypes : ['Const', 'Variable', 'Custom', 'StateMachine', 'Expression']; + const resultProperty = { + type: "property-editor", + propertyTypes: newPropertyTypes + }; + newPropertyTypes.map(item => { + switch (item) { + case 'Const': + Object.assign(resultProperty, { + constType: 'enum', + constEnums: [{ id: true, name: '是' }, { id: false, name: '否' }] + }, constInfos); + break; + case 'Expression': + resultProperty['expressionConfig'] = this.getExpressionOptions(propertyData, targetType, propertyName); + break; + case 'StateMachine': + resultProperty['stateMachines'] = getStateMachines(this.viewModelId); + break; + case 'Variable': + Object.assign(resultProperty, { + controlName: getControlName(propertyData), + newVariablePrefix: 'is', + newVariableType: 'Boolean', + variables: getVariables(this.viewModelId) + }, variableInfos); + break; + } + }); + return resultProperty; + } + + protected getVisibleProperty(propertyData, position = '') { + const editor = this.getPropertyEditorParams(propertyData, position === 'gridFieldEditor' ? ['Const', 'Expression'] : ['Const', 'Variable', 'Custom', 'StateMachine', 'Expression'], 'visible'); + return { + visible: { + title: "是否可见", + type: "boolean", + description: "运行时组件是否可见", + editor + } + }; + } + /** + * 获取行为 + * @param propertyData + * @param viewModelId + * @returns + */ + public getBehaviorConfig(propertyData: any, position = '', properties = {}, setPropertyRelates?: any): any { + const behaviorBasic = { + title: "行为", + description: "" + }; + const behaviorPoperties = this.getVisibleProperty(propertyData, position); + for (const key in properties) { + // 合并属性,保留原属性值 + behaviorPoperties[key] = Object.assign(behaviorPoperties[key] || {}, properties[key]); + } + const self = this; + + return { + ...behaviorBasic, properties: { ...behaviorPoperties }, setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'disabled': + case 'readonly': + case 'visible': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, parameters); + } + } + + }; + } + /** + * 当多值编辑器变更时 + * @param propertyData + * @param changeObject + */ + public afterMutilEditorChanged(propertyData, changeObject) { + // 变量 + this.addNewVariableToViewModel(changeObject, this.viewModelId); + // 更新表达式 + this.updateExpressionValue(changeObject); + // 清空表达式 + this.clearExpression(changeObject, propertyData); + } + /** + * 更新element + * @param propertyId + * @param componentInstance + * @returns + */ + public updateElementByParentContainer(propertyId: string, componentInstance: DesignerComponentInstance) { + // 1、定位控件父容器 + const parentContainer = componentInstance && componentInstance.parent && componentInstance.parent['schema']; + if (!parentContainer) { + return; + } + const index = parentContainer.contents.findIndex(c => c.id === propertyId); + // 通过cloneDeep方式的触发更新 + const controlSchema = cloneDeep(parentContainer.contents[index]); + // 5、替换控件 + parentContainer.contents.splice(index, 1); + parentContainer.contents.splice(index, 0, controlSchema); + // refreshCanvas(); + } + + /** + * 属性编辑器,在编辑过程中会新增变量,此处需要将新增的变量追加到ViewModel中 + * @param changeObject + * @param viewModelId + * @returns + */ + public addNewVariableToViewModel(changeObject: PropertyChangeObject, viewModelId: string) { + const newPropertyValue = changeObject.propertyValue; + // 1、判断当前属性值是否为对象 + const isObject = newPropertyValue && typeof newPropertyValue === 'object'; + if (!isObject) { + return; + } + + // 2、判断是否为新变量 + const isNewVariable = newPropertyValue.type === 'Variable' && newPropertyValue.isNewVariable; + if (!isNewVariable) { + return; + } + + // 3、构造变量结构 + const newVariable = { + id: newPropertyValue.field, + category: 'locale', + code: newPropertyValue.fullPath, + name: newPropertyValue.fullPath, + type: newPropertyValue.newVariableType || 'String' + }; + delete newPropertyValue.newVariableType; + delete newPropertyValue.isNewVariable; + + // 4、把新变量添加到ViewModel中 + const existedVariable = this.formSchemaUtils.getVariableByCode(newVariable.code); + if (!existedVariable) { + const viewModel = this.formSchemaUtils.getViewModelById(viewModelId); + viewModel.states.push(newVariable); + } + } + + /** + * 更新表达式到expressions节点 + * @param changeObject + */ + public updateExpressionValue(changeObject: PropertyChangeObject) { + const newPropertyValue = changeObject.propertyValue; + const newValueType = newPropertyValue && newPropertyValue.type; + + // 1、判断是否需要更新表达式 + const needUpdateExpression = newValueType === 'Expression' && newPropertyValue.expressionInfo; + if (!needUpdateExpression) { + return; + } + + const { expressionId, expressionInfo } = newPropertyValue; + const { targetId, targetType, expressionType, value, message } = expressionInfo; + const module = this.formSchemaUtils.getModule(); + module.expressions ??= []; + const { expressions } = module; + + // 2、获取目标表达式,如果不存在,则创建一个空的目标表达式 + let targetExpression = expressions.find(expression => expression.target === targetId); + if (!targetExpression) { + targetExpression = { target: targetId, rules: [], targetType }; + expressions.push(targetExpression); + } + + // 3、更新表达式 + const expressionItem = targetExpression.rules.find(rule => rule.type === expressionType); + if (expressionItem) { + expressionItem.value = value; + expressionItem.message = message; + } else { + const newExpressionRule = { id: expressionId, type: expressionType, value, message }; + targetExpression.rules.push(newExpressionRule); + } + delete newPropertyValue.expressionInfo; + } + + /** + * 属性类型切换为非表达式后,清除原表达式 + * @param changeObject + * @param propertyData + * @returns + */ + clearExpression(changeObject: PropertyChangeObject, propertyData: any) { + const newPropertyValue = changeObject.propertyValue; + const isExpression = newPropertyValue && newPropertyValue.type === 'Expression'; + // 1、如果为当前属性为表达式,则不需要清空 + if (isExpression) { + return; + } + + // 2、属性值不是表达式后,需要清空表达式 + const expressionType = changeObject.propertyID; + const expressions = this.formSchemaUtils.getExpressions(); + + const targetId = propertyData.binding ? propertyData.binding.field : propertyData.id; + const targetExpression = expressions.find(expression => expression.target === targetId); + if (!targetExpression || !targetExpression.rules) { + return; + } + targetExpression.rules = targetExpression.rules.filter(rule => rule.type !== expressionType); + } + + getExpressionOptions(propertyData: any, targetType: 'Field' | 'Button' | 'Container', expressionType: string) { + return new ExpressionProperty(this.formSchemaUtils).getExpressionOptions(propertyData, targetType, expressionType); + } + + getRealTargetType(propertyData: any,) { + if (['response-toolbar-item', 'tab-toolbar-item', 'section-toolbar-item'].indexOf(propertyData.type) > -1) { + return 'Button'; + } + if (propertyData.binding && propertyData.binding.field) { + return 'Field'; + } + return 'Container'; + } + + public getSchemaEntityTreeData(): any[] { + const mainEntity = this.formSchemaUtils.getFormSchema()?.module?.entity[0]?.entities[0]; + const entityTreeData = this.assembleSchemaEntityToTree(mainEntity, 0); + return entityTreeData; + } + + private assembleSchemaEntityToTree( + schemaEntity: FormSchemaEntity, + layer: number, + parent?: FormSchemaEntity, + bindToPath = '', + treeData: any[] = [], + ) { + const bindTo = bindToPath ? `${bindToPath}/${schemaEntity.label}` : '/'; + treeData.push({ + id: schemaEntity.id, + name: schemaEntity.name, + label: schemaEntity.label, + layer, + parent: parent && parent.id, + bindTo: bindTo.replace('//', '/') + }); + if (schemaEntity.type.entities && schemaEntity.type.entities.length) { + schemaEntity.type.entities.map(ele => this.assembleSchemaEntityToTree(ele, layer + 1, schemaEntity, bindTo, treeData)); + } + return treeData; + } +} diff --git a/packages/charts-vue/components/charts-common/src/props/container-base-property.ts b/packages/charts-vue/components/charts-common/src/props/container-base-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a0cf9ea520b598a9dde4b247eabd2774fbc2504 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/container-base-property.ts @@ -0,0 +1,382 @@ +import { BaseControlProperty } from "./base-property"; +import { DgControl } from "./dg-control"; +import { sizeConverter, paddingConverter, marginConverter, positionConverter, borderRadiusConverter } from "@farris/charts-vue/components/dynamic-resolver/src/converter"; +import { PropertyChangeObject } from "../types"; + +interface BasicPropOptions { + showTitle?: boolean; +} + +export class ContainerBaseProperty extends BaseControlProperty { + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + + protected getAppearanceProperties( + propertyNames: string[] = ["style"], + propertyData?: any, + ) { + const propertyName2PropertyGetter = { + style: this.getStyleProperties, + size: this.getSizeProperties, + padding: this.getPaddingProperties, + margin: this.getMarginProperties, + borderRadius: this.getBorderRadiusProperties, + position: this.getPositionProperties, + }; + const properties: any[] = []; + propertyNames.forEach((propertyName) => { + const propertyGetter = propertyName2PropertyGetter[propertyName]; + if (propertyGetter) { + properties.push(propertyGetter(propertyData)); + } + }); + return Object.assign({}, ...properties); + } + + private getStyleProperties(propertyData?: any) { + return { + class: { + title: "class样式", + type: "string", + description: "组件的CSS样式", + $converter: "/converter/appearance.converter" + }, + style: { + title: "style样式", + type: "string", + description: "组件的样式", + $converter: "/converter/appearance.converter" + }, + }; + } + + private getSizeProperties(propertyData?: any) { + return { + size: { + title: "尺寸", + type: "cascade", + description: "组件的尺寸", + isExpand: true, + properties: { + width: { + title: "宽度(px)", + type: "number", + description: "组件的宽度", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: sizeConverter, + }, + height: { + title: "高度(px)", + type: "number", + description: "组件的高度", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: sizeConverter, + } + } + }, + }; + } + + private getPaddingProperties(propertyData?: any) { + return { + padding: { + title: "内边距", + type: "cascade", + description: "组件的内边距", + isExpand: true, + properties: { + top: { + title: "上侧(px)", + type: "number", + description: "组件的上侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + right: { + title: "右侧(px)", + type: "number", + description: "组件的右侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + bottom: { + title: "下侧(px)", + type: "number", + description: "组件的下侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + left: { + title: "左侧(px)", + type: "number", + description: "组件的左侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + } + }, + }; + } + + private getMarginProperties(propertyData?: any) { + return { + margin: { + title: "外边距", + type: "cascade", + description: "组件的外边距", + isExpand: true, + properties: { + top: { + title: "上侧(px)", + type: "number", + description: "组件的上侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + right: { + title: "右侧(px)", + type: "number", + description: "组件的右侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + bottom: { + title: "下侧(px)", + type: "number", + description: "组件的下侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + left: { + title: "左侧(px)", + type: "number", + description: "组件的左侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + } + }, + }; + } + + private getBorderRadiusProperties(propertyData?: any) { + return { + borderRadius: { + title: "圆角", + type: "cascade", + description: "组件的圆角", + isExpand: true, + properties: { + topLeft: { + title: "左上角(px)", + type: "number", + description: "组件的左上圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + topRight: { + title: "右上角(px)", + type: "number", + description: "组件的右上圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + bottomRight: { + title: "右下角(px)", + type: "number", + description: "组件的右下圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + bottomLeft: { + title: "左下角(px)", + type: "number", + description: "组件的左下圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + } + }, + }; + } + + private getPositionProperties(propertyData?: any) { + return { + position: { + title: "位置", + type: "cascade", + description: "组件的位置", + isExpand: true, + properties: { + top: { + title: "上侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的上侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + right: { + title: "右侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的右侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + bottom: { + title: "下侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的下侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + left: { + title: "左侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的左侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + } + }, + }; + } + + public getBasicPropConfig( + propertyData: any, + options?: BasicPropOptions, + additionalProperties = {}, + setPropertyRelates?: (changeObject: PropertyChangeObject, propertyData: any, parameters: any) => any, + ) { + const optionalProperties = [ + { + id: { + description: '组件标识', + title: '标识', + type: 'string', + readonly: true + } + }, + options?.showTitle && { + title: { + description: '组件名称', + title: "名称", + type: "string", + } + }, + { + type: { + description: '组件类型', + title: '控件类型', + type: 'select', + editor: { + type: 'combo-list', + textField: 'name', + valueField: 'value', + idField: 'value', + editable: false, + data: [{ + value: propertyData.type, + name: DgControl[propertyData.type] && DgControl[propertyData.type].name + }], + } + } + }, + ]; + const properties = optionalProperties.filter((property) => { + return typeof property === 'object' && !!property; + }).reduce((result, newProperty) => { + return Object.assign(result, newProperty); + }, {}); + Object.keys(additionalProperties).forEach((key) => { + properties[key] = Object.assign(properties[key] || {}, additionalProperties[key]); + }); + return { + description: 'Basic Information', + title: '基本信息', + properties, + setPropertyRelates(changeObject: any, parameters: any) { + const propertyID = changeObject?.propertyID; + if (!propertyID) { + return; + } + if (propertyID === 'title') { + changeObject.needRefreshControlTree = true; + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, propertyData, parameters); + } + }, + }; + } + +} diff --git a/packages/charts-vue/components/charts-common/src/props/dg-control.ts b/packages/charts-vue/components/charts-common/src/props/dg-control.ts new file mode 100644 index 0000000000000000000000000000000000000000..c51a73e733b736442218b7030d6d908dedd75e04 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/dg-control.ts @@ -0,0 +1,55 @@ +export const DgControl = { + 'charts-bar': { type: 'charts-bar', name: '柱状图', icon: 'ChartsBar' }, + + 'module': { type: 'Module', name: '模块', icon: 'Module' }, + + 'component': { type: 'component', name: '组件', icon: 'Component' }, + + 'content-container': { type: 'content-container', name: '容器', icon: 'ContentContainer' }, + + 'page-container': { type: 'page-container', name: '页面容器', icon: 'ContentContainer' }, + + 'page-header-container': { type: 'page-header-container', name: '页头容器', icon: 'ContentContainer' }, + + 'page-body-container': { type: 'page-body-container', name: '页面主体容器', icon: 'ContentContainer' }, + + 'page-footer-container': { type: 'page-footer-container', name: '页尾容器', icon: 'ContentContainer' }, + + 'float-container': { type: 'float-container', name: '浮动容器', icon: 'ContentContainer' }, + + 'card': { type: 'card', name: '卡片', icon: 'section' }, + + 'list-view': { type: 'list-view', name: '列表', icon: 'ListView' }, + + 'button': { type: 'button', name: '按钮', icon: 'Button' }, + + 'button-group': { type: 'button-group', name: '按钮组', icon: 'ButtonGroup' }, + + 'input-group': { type: 'input-group', name: '文本', icon: 'TextBox' }, + + 'textarea': { type: 'textarea', name: '多行文本', icon: 'MultiTextBox' }, + + 'lookup': { type: 'lookup', name: '帮助', icon: 'LookupEdit' }, + + 'number-input': { type: 'number-input', name: '数值', icon: 'NumericBox' }, + + 'date-picker': { type: 'date-picker', name: '日期', icon: 'DateBox' }, + + 'datetime-picker': { type: 'datetime-picker', name: '日期时间', icon: 'DateBox' }, + + 'switch': { type: 'switch', name: '开关', icon: 'SwitchField' }, + + 'radio-group': { type: 'radio-group', name: '单选组', icon: 'RadioGroup' }, + + 'check-group': { type: 'check-group', name: '多选组', icon: 'CheckGroup' }, + + 'check-box': { type: 'check-box', name: '复选框', icon: 'CheckBox' }, + + 'combo-list': { type: 'combo-list', name: '下拉列表', icon: 'EnumField' }, + + 'form': { type: 'form', name: '卡片面板', icon: 'Form' }, + + 'navbar': { type: 'navbar', name: '导航栏', icon: 'NavBar' }, + + 'picker': { type: 'picker', name: '选择器', icon: 'EnumField' }, +}; diff --git a/packages/charts-vue/components/charts-common/src/props/expression-property.ts b/packages/charts-vue/components/charts-common/src/props/expression-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..24b91f61777f5ec22452f8d872d6b9f879d98706 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/expression-property.ts @@ -0,0 +1,385 @@ +export interface RuleModel { + id: string; + type: 'compute' | 'dependency' | 'validate' | 'visible' | 'readonly' | 'required' | 'dataPicking' | string; + value: string; + message?: string; + elementId?: string; + messageType?: string; +} + +export interface ExpressionModel { + target: string; + rules: Array; + targetType: string; +} + + +export class ExpressionProperty { + + private sessionVariables = [ + { + key: "CurrentSysOrgName", + name: "当前组织Name", + description: "当前组织Name" + }, + // { + // key: "CurrentSysOrgCode", + // name: "当前组织Code", + // description: "当前组织Code" + // }, + { + key: "CurrentSysOrgId", + name: "当前组织Id", + description: "当前组织Id" + }, + { + key: "CurrentUserName", + name: "当前用户Name", + description: "当前用户Name" + }, + { + key: "CurrentUserCode", + name: "当前用户Code", + description: "当前用户Code" + }, + { + key: "CurrentUserId", + name: "当前用户Id", + description: "当前用户Id" + }, + { + key: "CurrentLanguage", + name: "当前语言编号", + description: "当前登录的语言编号,例如简体中文返回'zh-CHS',英文返回'en',繁体中文'zh-CHT'" + } + ]; + + constructor(private formSchemaService: any) { + } + + private getExpressionRule(expressionId: any, rultType: string) { + const expressions = this.formSchemaService.getExpressions(); + if (!expressions) { + return ''; + } + const expressionItem = expressions.find(item => item.target === expressionId); + if (!expressionItem) { + return ''; + } + + const ruleItem = expressionItem.rules.find(item => item.type === rultType); + if (!ruleItem) { + return ''; + } + + return ruleItem; + } + + // 获取上下文表单变量 + private getContextFormVariables() { + const { module } = this.formSchemaService.getFormSchema(); + if (!module.viewmodels || module.viewmodels.length === 0) { + return []; + } + + const rootViewModelId = this.formSchemaService.getRootViewModelId(); + const viewModel = this.formSchemaService.getViewModelById(rootViewModelId); + + if (!viewModel || !viewModel.states || viewModel.states.length === 0) { + return []; + } + + const contextEntities: any = []; + viewModel.states.forEach(variable => { + if (variable.category === 'remote') { + contextEntities.push({ + key: variable.code, + name: variable.name, + description: variable.name + }); + } + }); + return contextEntities; + } + + private createTreeNode(element: any, parentCodes: string[], fieldName = 'label') { + return { + id: element.id, + name: element.name, + bindingPath: element[fieldName], + parents: parentCodes + }; + } + + + private buildEntityFieldsTreeData(fields: any[] | null = null, parentCodes: string[]): any[] { + const treeData: any[] = []; + fields?.forEach(element => { + const nodeData = this.createTreeNode(element, parentCodes); + + let children: any[] = []; + if (element.type?.fields) { + children = this.buildEntityFieldsTreeData(element.type.fields, [...parentCodes, element.label]); + } + + treeData.push({ + data: nodeData, + children, + expanded: true + }); + }); + return treeData; + } + + private buildChildEntityTreeData(entities: any[] | null = null, parentCodes: string[]): any[] { + const treeData: any[] = []; + entities?.forEach(element => { + const nodeData = this.createTreeNode(element, parentCodes); + + const children = this.buildEntityFieldsTreeData(element.type?.fields, [...parentCodes, element.label]); // 可选链 + const childEntities = this.buildChildEntityTreeData(element.type?.entities, [...parentCodes, element.label]); // 可选链 + + if (childEntities?.length) { // 空值检查 + children?.push(...childEntities); + } + + treeData.push({ + data: nodeData, + children: children || [], // 空值回退 + expanded: true + }); + }); + return treeData; + } + + private getEntitiesTreeData() { + const entities = this.formSchemaService.getSchemaEntities(); + if (!entities?.length) { // 空值检查 + return []; + } + + const mainTable = entities[0]; + if (!mainTable?.type) { return []; } // 空值检查 + + const childFields = this.buildEntityFieldsTreeData(mainTable.type.fields, [mainTable.code]); + const childEntities = this.buildChildEntityTreeData(mainTable.type.entities, [mainTable.code]); + + if (childEntities?.length) { // 空值检查 + childFields?.push(...childEntities); + } + + return { + entityCode: mainTable.code, + fields: [{ + data: this.createTreeNode(mainTable, [], 'code'), + children: childFields || [] + }] + }; + } + + + getEntitiesAndVariables() { + return { + entities: this.getEntitiesTreeData(), + variables: { + session: { + name: '系统变量', + items: this.sessionVariables + }, + forms: { + name: '表单变量', + items: this.getContextFormVariables() + } + } + }; + } + + private onBeforeOpenExpression(propertyData: any, expressionType: string, targetType: string) { + const expressionId = targetType === 'Field' ? propertyData.binding.field : propertyData.id; + const rule = this.getExpressionRule(expressionId, expressionType); + const entitiesAndVariables = this.getEntitiesAndVariables(); + const data: any = { + message: ['validate', 'required','dataPicking'].includes(expressionType) && rule ? rule.message : '', + ...entitiesAndVariables + }; + + if (rule.messageType != null) { + data.messageType = rule.messageType; + } + + return data; + } + + private buildRule(targetId, expressionObject: Record, ruleType: string, controlId?: string) { + const { expression: expressionValue, message, messageType } = expressionObject; + const rule: RuleModel = { + id: `${targetId}_${ruleType}`, + type: ruleType, + value: expressionValue, + message, + messageType + }; + + if (ruleType === 'validate' && controlId) { + rule.elementId = controlId; + } + + return rule; + } + + private getExpressionData(): Array { + const { expressions } = this.formSchemaService.getFormSchema().module; + return expressions || []; + } + + private updateExpression(propertyData: any, + targetType: 'Field' | 'Button' | 'Container', + expressionObject: Record, ruleType: string) { + const targetId = targetType === 'Field' ? propertyData.binding.field : propertyData.id; + const newRule = this.buildRule(targetId, expressionObject, ruleType, propertyData.type === 'form-group' ?propertyData.id: ''); + + const currentExpressiones = this.getExpressionData(); + let expressionItem = currentExpressiones.find((item: ExpressionModel) => { + return item.targetType === targetType && item.target === targetId; + }); + + // 提取重复逻辑:判断 value 是否为空 + const isValueEmpty = (rule: RuleModel): boolean => rule.value.trim() === ''; + + if (expressionItem) { + const ruleItem = expressionItem.rules.find((rule) => rule.id === newRule.id); + if (ruleItem) { + if (isValueEmpty(newRule)) { + expressionItem.rules = expressionItem.rules.filter((rule) => rule.id !== newRule.id); + } else { + ruleItem.value = newRule.value; + ruleItem.message = newRule.message; + + if (ruleType === 'dataPicking') { + ruleItem.messageType = newRule.messageType; + } + + if (ruleType === 'validate' && propertyData.type === 'form-group') { + ruleItem.elementId = propertyData.id; + } + } + } else { + if (isValueEmpty(newRule)) { + return null; + } + + expressionItem.rules = expressionItem.rules || []; + expressionItem.rules.push(newRule); + } + } else { + + if (isValueEmpty(newRule)) { + return null; + } + + expressionItem = { + target: `${targetId}`, + rules: [newRule], + targetType: targetType + }; + + } + return expressionItem; + } + + private expressionNames = { + compute: '计算表达式', + dependency: '依赖表达式', + validate: '验证表达式', + dataPicking: '帮助前表达式', + visible: '可见表达式', + readonly: '只读表达式', + required: '必填表达式' + }; + + private getExpressionConverter = (expressionId: string, ruleType?: string) => { + return { + convertFrom: (schema: Record, propertyKey: string, schemaService, componentId) => { + const rule = schemaService.getExpressionRuleValue(expressionId, ruleType || propertyKey); + return rule && rule.value || ''; + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any[], + schemaService, componentId + ) => { + schemaService.updateExpression(propertyValue); + } + }; + }; + + private getExpressionEditorOptions(propertyData, targetType: 'Field' | 'Button' | 'Container', + expressionTypes: string[], callback?: (rule: Record) => void) { + return expressionTypes.reduce((expressions: Record, name: string) => { + const expressionId = targetType === 'Field' ? propertyData?.binding?.field : propertyData.id; + expressions[name] = { + hide: targetType === 'Field' ? !!propertyData?.binding?.field : false, + description: "", + title: this.expressionNames[name], + type: "string", + $converter: this.getExpressionConverter(expressionId), + editor: { + type: "expression-editor", + singleExpand: false, + dialogTitle: `${this.expressionNames[name]}编辑器`, + showMessage: name === 'validate' || name === 'dataPicking', + showMessageType: name === 'dataPicking', + beforeOpen: () => { + return this.onBeforeOpenExpression(propertyData, name, targetType); + }, + onSubmitModal: (expressionObject: any) => { + const expressionData: any = this.updateExpression(propertyData, targetType, expressionObject, name); + if (callback) { + const rule = this.buildRule(expressionId, expressionObject, name); + callback(rule); + } + return expressionData; + } + } + }; + + return expressions; + }, {}); + } + + private getExpressionInfo(propertyData: any, targetType: 'Field' | 'Button' | 'Container', expressionType: string) { + const targetId = targetType === 'Field' ? propertyData.binding.field : propertyData.id; + const expressionRule = this.getExpressionRule(targetId, expressionType); + const expressionInfo = { + value: expressionRule && expressionRule.value, + message: expressionRule && expressionRule.message, + targetId, + targetType, + expressionType + }; + return expressionInfo; + } + + getExpressionConfig(propertyData: any, type: 'Field' | 'Button' | 'Container', + expressionTypes: string[] = ['compute', 'dependency', 'validate'], + callback?: (rule: Record) => void) { + return { + description: "表达式", + title: "表达式", + properties: { + ...this.getExpressionEditorOptions(propertyData, type, expressionTypes, callback) + } + }; + } + + getExpressionOptions(propertyData: any, targetType: 'Field' | 'Button' | 'Container', expressionType: string) { + const expressionInfo = this.getExpressionInfo(propertyData, targetType, expressionType); + return { + dialogTitle: `${this.expressionNames[expressionType]}编辑器`, + singleExpand: false, + showMessage: expressionType === 'require', + beforeOpen: () => { + return this.onBeforeOpenExpression(propertyData, expressionType, targetType); + }, + expressionInfo + }; + } +} diff --git a/packages/charts-vue/components/charts-common/src/props/index.ts b/packages/charts-vue/components/charts-common/src/props/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f505773be9053aff387da8cfea5a5a1e66790c1 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/index.ts @@ -0,0 +1,6 @@ +export * from './dg-control'; +export { SchemaDOMMapping } from './schema-dom-mapping'; +export { BaseControlProperty } from './base-property'; +export { InputBaseProperty } from './input-base-property'; +export { ContainerBaseProperty } from './container-base-property'; +export { ToolbarItemProperty } from './toolbar-item.property'; diff --git a/packages/charts-vue/components/charts-common/src/props/input-base-property.ts b/packages/charts-vue/components/charts-common/src/props/input-base-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..44a5dc3a65712be6aac5f48774b0afd4a810fb10 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/input-base-property.ts @@ -0,0 +1,563 @@ + +import { BaseControlProperty } from "./base-property"; +import { SchemaDOMMapping } from './schema-dom-mapping'; +import { ExpressionProperty } from "./expression-property"; +import { FormSchemaEntityField$Type, FormSchemaEntityFieldType$Type, FormVariable } from "../types/entity-schema"; +import { DesignerComponentInstance, FormBindingType, FormPropertyChangeObject } from "../types"; +import { DgControl } from "./dg-control"; + +export class InputBaseProperty extends BaseControlProperty { + /** 控件绑定的变量 */ + public bindingVarible?: FormVariable; + + public labelAlignVisible = true; + public labelAlignReadonly = false; + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + private getCommonPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance | null) { + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicProperties(propertyData, componentInstance); + // 外观 + this.propertyConfig.categories['appearance'] = this.getAppearanceProperties(propertyData, componentInstance); + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + // 编辑器 + this.propertyConfig.categories['editor'] = this.getEditorProperties(propertyData); + // 表达式编辑器 + this.propertyConfig.categories['expressons'] = this.getExpressionConfig(propertyData, 'Field'); + } + public getPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance) { + this.getCommonPropertyConfig(propertyData, componentInstance); + // 事件 暂不支持 + // this.propertyConfig.categories['eventsEditor'] = this.getEventPropertyConfig(propertyData); + return this.propertyConfig; + } + + public getBasicProperties(propertyData, componentInstance): any { + const self = this; + this.setDesignViewModelField(propertyData); + const { canChangeControlType, editorTypeList } = this.getAvailableEditorType(propertyData); + return { + description: 'Basic Information', + title: '基本信息', + properties: { + id: { + description: '组件标识', + title: '标识', + type: 'string', + readonly: true + }, + type: { + description: '编辑器类型', + title: '编辑器类型', + type: 'string', + $converter: '/converter/change-editor.converter', + editor: { + type: 'combo-list', + textField: 'value', + valueField: 'key', + idField: 'key', + editable: false, + data: editorTypeList, + readonly: !canChangeControlType + }, + }, + binding: { + description: "绑定的表单字段", + title: "绑定", + editor: { + type: "binding-selector", + bindingType: { "enable": false }, + editorParams: { + componentSchema: propertyData, + needSyncToViewModel: true, + viewModelId: this.viewModelId, + designerHostService: this.designerHostService, + disableOccupiedFields: true + }, + textField: 'bindingField' + }, + refreshPanelAfterChanged: true + }, + label: { + title: "标签", + type: "string", + $converter: '/converter/form-group-label.converter' + }, + labelAlign: { + title: "标签排列方式", + type: "string", + $converter: '/converter/form-group-label.converter', + visible: this.labelAlignVisible, + editor: { + type: 'combo-list', + data: [{ id: 'left', name: '左侧' }, { id: 'top', name: '上侧' }], + readonly: this.labelAlignReadonly + }, + refreshPanelAfterChanged: true + }, + labelWidth: { + title: "标签宽度", + type: "string", + $converter: '/converter/form-group-label.converter', + visible: propertyData.labelAlign !== 'top' + } + }, + setPropertyRelates(changeObject, prop, paramters: any) { + if (!changeObject) { + return; + } + switch (changeObject && changeObject.propertyID) { + case 'type': { + self.changeControlType(propertyData, changeObject, componentInstance); + break; + } + case 'label': { + changeObject.needRefreshControlTree = true; + break; + } + case 'binding': { + self.changeBindingField(propertyData, changeObject, paramters); + break; + } + } + } + }; + } + + /** + * 校验控件是否支持切换类型 + * @param control 控件 + */ + private checkCanChangeControlType(propertyData: any, viewModelId: string): boolean { + // 没有绑定信息的控件 + if (!propertyData.binding) { + return false; + } + + if (propertyData.binding.type === 'Variable') { + this.bindingVarible = this.formSchemaUtils.getVariableById(propertyData.binding.field); + // vm中已移除的变量 + if (!this.bindingVarible) { + return false; + } + } else { + // schema中已移除的字段 或者绑定复杂类型的字段 + if (!this.designViewModelField || this.designViewModelField.$type !== FormSchemaEntityField$Type.SimpleField) { + return false; + } + } + return true; + + } + /** + * 获取可选的编辑器类型 + */ + private getAvailableEditorType(propertyData: any) { + const canChangeControlType = this.checkCanChangeControlType(propertyData, this.viewModelId); + if (!canChangeControlType) { + return { + canChangeControlType: false, + editorTypeList: [{ + key: propertyData.editor.type, + value: DgControl[propertyData.editor.type]?.name || propertyData.editor.type + }] + }; + } + + let editorTypeList: any = []; + if (this.designViewModelField && this.designViewModelField.$type === FormSchemaEntityField$Type.SimpleField) { + // 绑定字段 + editorTypeList = SchemaDOMMapping.getEditorTypesByMDataType(this.designViewModelField.type.name); + } else if (this.bindingVarible) { + // 绑定变量 + editorTypeList = SchemaDOMMapping.getEditorTypesByMDataType(this.bindingVarible.type); + } + return { canChangeControlType, editorTypeList }; + } + + public changeBindingField(propertyData: any, changeObject: FormPropertyChangeObject, paramters?: any): any { + // 刷新实体树 + // changeObject.needRefreshEntityTree = true; + } + + + public getAppearanceProperties(propertyData, componentInstance): any { + + const self = this; + return { + title: "外观", + description: "Appearance", + properties: { + class: { + title: "class样式", + type: "string", + description: "组件的CSS样式", + $converter: "/converter/appearance.converter" + }, + style: { + title: "style样式", + type: "string", + description: "组件的内联样式", + $converter: "/converter/appearance.converter" + } + + }, + setPropertyRelates(changeObject, prop) { + if (!changeObject) { + return; + } + switch (changeObject && changeObject.propertyID) { + case 'class': + self.updateUnifiedLayoutAfterControlChanged(changeObject.propertyValue, propertyData.id, this.componentId); + // canvasChanged.value++; + break; + case 'style': { + // canvasChanged.value++; + break; + } + } + + } + }; + }; + + public getEditorProperties(propertyData): any { + return this.getComponentConfig(propertyData); + } + + + /** + * 卡片控件:切换控件类型后事件 + * @param propertyData 控件DOM属性 + * @param newControlType 新控件类型 + */ + private changeControlType(propertyData, changeObject, componentInstance: DesignerComponentInstance | null) { + const newControlType = changeObject.propertyValue; + + // 1、定位控件父容器 + const parentContainer = componentInstance && componentInstance.parent && componentInstance.parent['schema']; + if (!parentContainer) { + return; + } + + const index = parentContainer.contents.findIndex(c => c.id === propertyData.id); + if (index === -1) { + return; + } + const oldControl = parentContainer.contents[index]; + + let newControl; + + if (this.designViewModelField) { + // 2、记录绑定字段viewModel的变更 + const viewModel = this.formSchemaUtils.getViewModelById(this.viewModelId); + const viewModelField = viewModel.fields.find(field => field.id === this.designViewModelField.id); + const viewModelFieldSchema = viewModelField.fieldSchema || {}; + if (!viewModelFieldSchema.editor) { viewModelFieldSchema.editor = {}; } + viewModelFieldSchema.editor.$type = newControlType; + + const dgViewModel = this.designViewModelUtils.getDgViewModel(this.viewModelId); + dgViewModel.changeField(this.designViewModelField.id, viewModelFieldSchema, false); + + // 3、创建新控件 + newControl = this.controlCreatorUtils.setFormFieldProperty(this.designViewModelField, newControlType); + } + if (!newControl) { + newControl = this.controlCreatorUtils.createFormGroupWithoutField(newControlType); + } + // 4、保留原id样式等属性 + Object.assign(newControl, { + id: oldControl.id, + appearance: oldControl.appearance, + size: oldControl.size, + label: oldControl.label, + binding: oldControl.binding + }); + // 解决undefined下默认值问题 + if (Object.prototype.hasOwnProperty.call(oldControl, 'visible')) { + Object.assign(newControl, { visible: oldControl.visible }); + } + if (oldControl.editor) { + ['readonly', 'required', 'placeholder'].map((item) => { + if (Object.prototype.hasOwnProperty.call(oldControl.editor, item)) { + newControl.editor[item] = oldControl.editor[item]; + } + }); + } + // 5、替换控件 + parentContainer.contents.splice(index, 1); + parentContainer.contents.splice(index, 0, newControl); + componentInstance.schema = Object.assign(oldControl, newControl); + // 重组VM + // this.designViewModelUtils.assembleDesignViewModel(); + Object.assign(propertyData, newControl); + + // 6、暂时移除旧控件的选中样式(后续考虑更好的方式) + Array.from(document.getElementsByClassName('dgComponentSelected') as HTMLCollectionOf).forEach( + (element: HTMLElement) => element.classList.remove('dgComponentSelected') + ); + + Array.from(document.getElementsByClassName('dgComponentFocused') as HTMLCollectionOf).forEach( + (element: HTMLElement) => element.classList.remove('dgComponentFocused') + ); + // 7、触发刷新 + // canvasChanged.value++; + + } + + public getComponentConfig(propertyData, info = {}, properties = {}, setPropertyRelates?: (changeObject, propertyData, parameters) => any) { + const editorBasic = Object.assign({ + description: "编辑器", + title: "编辑器", + type: "input-group", + $converter: "/converter/property-editor.converter" + }, info); + + const readonlyEditor = this.getPropertyEditorParams(propertyData, [], 'readonly'); + const requiredEditor = this.getPropertyEditorParams(propertyData, [], 'required'); + const editorProperties = { + readonly: { + description: "", + title: "只读", + editor: readonlyEditor + }, + required: { + description: "", + title: "必填", + type: "boolean", + editor: requiredEditor + }, + placeholder: { + description: "当控件没有值时在输入框中显示的文本", + title: "提示文本", + type: "string" + } + }; + for (const key in properties) { + // 合并属性,保留原属性值 + editorProperties[key] = Object.assign(editorProperties[key] || {}, properties[key]); + } + const self = this; + + return { + ...editorBasic, properties: { ...editorProperties }, setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'readonly': + case 'required': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, propertyData, parameters); + } + } + }; + + } + + + /** + * 修改某一输入控件的样式后更新Form的统一布局配置 + * @param controlClass 控件样式 + * @param controlId 控件Id + * @param componentId 控件所在组件id + */ + private updateUnifiedLayoutAfterControlChanged(controlClass: string, controlId: string, componentId: string) { + const controlClassArray = controlClass.split(' '); + + let colClass = controlClassArray.find(item => /^col-([1-9]|10|11|12)$/.test(item)); + let colMDClass = controlClassArray.find(item => /^col-md-([1-9]|10|11|12)$/.test(item)); + let colXLClass = controlClassArray.find(item => /^col-xl-([1-9]|10|11|12)$/.test(item)); + let colELClass = controlClassArray.find(item => /^col-el-([1-9]|10|11|12)$/.test(item)); + + colClass = colClass || 'col-12'; + colMDClass = colMDClass || 'col-md-' + colClass.replace('col-', ''); + colXLClass = colXLClass || 'col-xl-' + colMDClass.replace('col-md-', ''); + colELClass = colELClass || 'col-el-' + colXLClass.replace('col-xl-', ''); + + const latestControlLayoutConfig = { + id: controlId, + columnInSM: parseInt(colClass.replace('col-', ''), 10), + columnInMD: parseInt(colMDClass.replace('col-md-', ''), 10), + columnInLG: parseInt(colXLClass.replace('col-xl-', ''), 10), + columnInEL: parseInt(colELClass.replace('col-el-', ''), 10), + }; + + } + + /** + * 枚举项编辑器 + * @param propertyData + * @param valueField + * @param textField + * @returns + */ + protected getItemCollectionEditor(propertyData, valueField, textField) { + valueField = valueField || 'value'; + textField = textField || 'name'; + console.log('input-base-property--403 \n', 'propertyData:', propertyData); + return { + editor: { + columns: [ + { field: valueField, title: '值', dataType: 'string' }, + { field: textField, title: '名称', dataType: 'string' }, + { field: 'disabled', title: '禁用', dataType: 'boolean', editor: { type: 'switch' } }, + ], + type: "item-collection-editor", + valueField: valueField, + nameField: textField, + requiredFields: [valueField, textField], + uniqueFields: [valueField, textField], + readonly: this.checkEnumDataReadonly(propertyData) + } + }; + } + /** + * 判断枚举数据是否只读 + * 1、没有绑定信息或者绑定变量,可以新增、删除、修改 + * 2、绑定类型为字段,且字段为枚举字段,则不可新增、删除、修改枚举值。只能从be修改然后同步到表单上。 + * @param propertyData 下拉框控件属性值 + */ + protected checkEnumDataReadonly(propertyData: any): boolean { + // 没有绑定信息或者绑定变量 + if (!propertyData.binding || propertyData.binding.type !== 'Form') { + return false; + } + if (this.designViewModelField && this.designViewModelField.type && + this.designViewModelField.type.$type === FormSchemaEntityFieldType$Type.EnumType) { + // 低代码、零代码,枚举字段均不可以改 + return true; + } + return false; + } + /** + * 将字段值变化前事件、变化后事件追加到交互面板 + * 注意:因为绑定字段值变化后事件与下拉框的值变化事件(valueChanged)重名,所以这里将事件label都重命名下 + */ + appendFieldValueChangeEvents(propertyData: any, eventList: any[]) { + // 若绑定了字段,则需要显示字段变更前后事件 + if (propertyData.binding && propertyData.binding.type === FormBindingType.Form && propertyData.binding.field) { + const valueChangingExist = eventList.find(eventListItem => eventListItem.label === 'fieldValueChanging'); + if (!valueChangingExist) { + eventList.push( + { + label: 'fieldValueChanging', + name: '绑定字段值变化前事件' + } + ); + } + const valueChangedExist = eventList.find(eventListItem => eventListItem.label === 'fieldValueChanged'); + if (!valueChangedExist) { + eventList.push( + { + label: 'fieldValueChanged', + name: '绑定字段值变化后事件' + } + ); + } + if (this.designViewModelField) { + // 因为字段变更属性是存到VM中的,但是这里需要在控件上编辑,所以复制一份数据 + propertyData.fieldValueChanging = this.designViewModelField.valueChanging; + propertyData.fieldValueChanged = this.designViewModelField.valueChanged; + } + } else { + // 未绑定字段,则移除值变化事件 + eventList = eventList.filter(eventListItem => eventListItem.label !== 'fieldValueChanging' && eventListItem.label !== 'fieldValueChanged'); + } + } + private getControlMethodType(propertyData: any) { + if (!propertyData.binding) { + return propertyData.type; + } + switch (propertyData.binding.type) { + case FormBindingType.Form: { + return propertyData.binding.path || propertyData.type; + } + case FormBindingType.Variable: { + return propertyData.binding.fullPath || propertyData.type; + } + } + + return propertyData.type; + } + /** + * 组装输入类控件的交互面板:包含标签超链、绑定字段值变化前后事件等。 + * @param propertyData 属性值 + * @param viewModelId 视图模型id + * @param showPosition 面板展示位置 + * @param customEvent 输入控件特有的事件配置 + */ + public getEventPropertyConfig(propertyData: any, showPosition = 'card', customEventList?: { label: string, name: string }[], setPropertyRelates?: (changeObject, data, parameters) => void) { + const self = this; + // 静态事件 + let eventList = [] as any; + if (customEventList) { + eventList = eventList.concat(customEventList); + } + // 追加值变化 + this.appendFieldValueChangeEvents(propertyData, eventList); + + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, eventList); + const properties = {}; + properties[self.viewModelId] = { + type: 'events-editor', + editor: { + initialData + } + }; + const eventsEditorConfig = { + title: '事件', + hideTitle: true, + properties, + tabId: 'commands', + tabName: '交互', + setPropertyRelates(changeObject: FormPropertyChangeObject, data: any) { + const parameters = changeObject.propertyValue; + delete propertyData[self.viewModelId]; + if (parameters) { + parameters.setPropertyRelates = this.setPropertyRelates; // 添加自定义方法后,调用此回调方法,用于处理联动属性 + parameters.controlInfo = { type: self.getControlMethodType(propertyData), name: propertyData.title }; // 暂存控件信息,用于自动创建新方法的方法编号和名称 + + self.eventsEditorUtils.saveRelatedParameters(propertyData, self.viewModelId, parameters['events'], parameters); + } + + if (setPropertyRelates) { + setPropertyRelates(changeObject, data, parameters); + } + + // 同步视图模型值变化事件 + const designVM = self.designViewModelUtils.getDgViewModel(self.viewModelId); + if (designVM && self.designViewModelField) { + designVM.changeField(self.designViewModelField.id, { valueChanging: propertyData.fieldValueChanging, valueChanged: propertyData.fieldValueChanged }); + } + } + }; + + return eventsEditorConfig; + } + + getExpressionConfig(propertyData: any, type: 'Field' | 'Button' | 'Container', + expressionTypes: string[] = ['compute', 'dependency', 'validate'], + associationCallBack?: (rule: Record) => void + ) { + return new ExpressionProperty(this.formSchemaUtils).getExpressionConfig( + propertyData, type, expressionTypes, associationCallBack); + } + + getFieldType(propertyData: any) { + let fieldType = ''; + if (propertyData?.binding?.type === 'Form') { + const fieldInfo = this.schemaService.getFieldByIDAndVMID(propertyData.binding.field, this.viewModelId); + if (fieldInfo?.schemaField?.type) { + fieldType = fieldInfo.schemaField.type.$type; + } + } + return fieldType; + } + +} diff --git a/packages/charts-vue/components/charts-common/src/props/schema-dom-mapping.ts b/packages/charts-vue/components/charts-common/src/props/schema-dom-mapping.ts new file mode 100644 index 0000000000000000000000000000000000000000..71f7291318c39c4c8b25616a7b694db330805072 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/schema-dom-mapping.ts @@ -0,0 +1,81 @@ + +import { DgControl } from "./dg-control"; + +export class SchemaDOMMapping { + + /** + * <字段类型,可配置的控件类型列表>的映射 + */ + static fieldControlTypeMapping = { + String: [ + { key: DgControl['input-group'].type, value: DgControl['input-group'].name }, + { key: DgControl['textarea'].type, value: DgControl['textarea'].name }, + { key: DgControl['lookup'].type, value: DgControl['lookup'].name }, + { key: DgControl['date-picker'].type, value: DgControl['date-picker'].name }, + { key: DgControl['datetime-picker'].type, value: DgControl['datetime-picker'].name }, + { key: DgControl['radio-group'].type, value: DgControl['radio-group'].name }, + { key: DgControl['check-group'].type, value: DgControl['check-group'].name }, + { key: DgControl['picker'].type, value: DgControl['picker'].name }, + ], + Text: [ + { key: DgControl['textarea'].type, value: DgControl['textarea'].name }, + { key: DgControl['lookup'].type, value: DgControl['lookup'].name } + ], + Decimal: [ + { key: DgControl['number-input'].type, value: DgControl['number-input'].name } + ], + Integer: [ + { key: DgControl['number-input'].type, value: DgControl['number-input'].name } + ], + Number: [ + { key: DgControl['number-input'].type, value: DgControl['number-input'].name } + ], + BigNumber: [ + { key: DgControl['number-input'].type, value: DgControl['number-input'].name } + ], + Date: [ + { key: DgControl['date-picker'].type, value: DgControl['date-picker'].name } + ], + DateTime: [ + { key: DgControl['date-picker'].type, value: DgControl['date-picker'].name }, + { key: DgControl['datetime-picker'].type, value: DgControl['datetime-picker'].name } + ], + Boolean: [ + { key: DgControl['switch'].type, value: DgControl['switch'].name }, + { key: DgControl['check-box'].type, value: DgControl['check-box'].name }, + ], + Enum: [ + { key: DgControl['picker'].type, value: DgControl['picker'].name }, + { key: DgControl['radio-group'].type, value: DgControl['radio-group'].name }, + ], + Object: [ + { key: DgControl['lookup'].type, value: DgControl['lookup'].name }, + { key: DgControl['picker'].type, value: DgControl['picker'].name }, + { key: DgControl['radio-group'].type, value: DgControl['radio-group'].name }, + ] + }; + /** + * 根据绑定字段类型获取可用的输入类控件 + */ + static getEditorTypesByMDataType(fieldType: string) { + const foundTypes = SchemaDOMMapping.fieldControlTypeMapping[fieldType]; + return foundTypes ? foundTypes : [{ key: "", value: "" }]; + + } + /** + * 获取所有输入类控件 + */ + static getAllInputTypes() { + const allControlTypes: Array<{ key: string, value: string }> = []; + for (const fieldType in SchemaDOMMapping.fieldControlTypeMapping) { + SchemaDOMMapping.fieldControlTypeMapping[fieldType].forEach(control => { + if (!allControlTypes.find(controlKeyValue => controlKeyValue.key === control.key && controlKeyValue.value === control.value)) { + allControlTypes.push({ key: control.key, value: control.value }); + } + }); + } + + return allControlTypes; + } + +} diff --git a/packages/charts-vue/components/charts-common/src/props/toolbar-item.property.ts b/packages/charts-vue/components/charts-common/src/props/toolbar-item.property.ts new file mode 100644 index 0000000000000000000000000000000000000000..16d97a2ee1cbdbee38b0e4d9c2a9b63d09cf4fbf --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/toolbar-item.property.ts @@ -0,0 +1,182 @@ +import { BaseControlProperty } from "./base-property"; + +export interface ToolbarItemPropOptions { + showAppearance?: boolean; + showButtonType?: boolean; + showIcon?: boolean; + showRound?: boolean; + showPlain?: boolean; + showVariant?: boolean; +} + +export class ToolbarItemProperty extends BaseControlProperty { + + private options: ToolbarItemPropOptions | undefined; + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + + public getPropertyConfig(propertyData: any, options?: ToolbarItemPropOptions) { + this.options = options; + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + // 外观 + if (this.shouldShowAppearanceConfig()) { + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + } + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + // 事件 + this.getEventPropConfig(propertyData); + + return this.propertyConfig; + } + + private shouldShowAppearanceConfig(): boolean { + return !!this.options?.showAppearance; + } + + protected getAppearanceConfig(propertyData: any) { + const buttonTypeData = [ + { text: '主要按钮', value: 'primary' }, + { text: '危险按钮', value: 'danger' }, + { text: '警告按钮', value: 'warning' }, + { text: '成功按钮', value: 'success' }, + { text: '次要按钮', value: 'secondary' }, + { text: '提示按钮', value: 'info' }, + ]; + const additionalProperties: any = {}; + if (this.options?.showButtonType) { + additionalProperties.displayType = { + description: '按钮的主题风格', + title: '按钮类型', + editor: { + type: 'combo-tree', + textField: 'text', + valueField: 'value', + data: buttonTypeData, + }, + }; + } + if (this.options?.showIcon) { + additionalProperties.icon = { + description: '显示在按钮文本左侧的图标', + title: '按钮图标', + type: 'string', + }; + } + if (this.options?.showRound) { + additionalProperties.round = { + description: '是否为按钮启用圆角', + title: '是否圆角', + type: 'boolean', + }; + } + if (this.options?.showPlain) { + additionalProperties.plain = { + description: '是否朴素按钮', + title: '是否朴素按钮', + type: 'boolean', + }; + } + if (this.options?.showVariant) { + additionalProperties.variant = { + description: '按钮的变体', + title: '变体', + editor: { + type: 'combo-tree', + textField: 'text', + valueField: 'value', + data: [ + { text: '无', value: 'default' }, + { text: '图标在上', value: 'bare-vertical' }, + ], + }, + }; + } + return super.getAppearanceConfig(propertyData, additionalProperties); + } + + getBasicPropConfig(propertyData: any): any { + return { + description: 'Basic Information', + title: '基本信息', + properties: { + id: { + description: '工具栏按钮的标识', + title: '标识', + type: 'string', + readonly: true + }, + text: { + description: '工具栏按钮的标签', + title: '标签', + type: 'string', + readonly: false + } + } + }; + } + + public getBehaviorConfig(propertyData: any) { + const visibleProperty = this.getVisibleProperty(propertyData); + const self = this; + return { + description: "基本信息", + title: "行为", + properties: { + ...visibleProperty, + disabled: { + title: "是否禁用", + type: "boolean", + } + }, + setPropertyRelates(changeObject: any, data: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'visible': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + } + }; + } + + private getEventPropConfig(propertyData: any) { + const events = [ + { + label: "onClick", + name: "点击事件", + } + ]; + const self = this; + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); + const properties = {}; + properties[self.viewModelId] = { + type: 'events-editor', + editor: { + initialData + } + }; + this.propertyConfig.categories['eventsEditor'] = { + title: '事件', + hideTitle: true, + properties, + refreshPanelAfterChanged: true, + tabId: 'commands', + tabName: '交互', + setPropertyRelates(changeObject: any, data: any) { + const parameters = changeObject.propertyValue; + delete propertyData[self.viewModelId]; + if (parameters) { + parameters.setPropertyRelates = this.setPropertyRelates; + self.eventsEditorUtils.saveRelatedParameters(propertyData, self.viewModelId, parameters['events'], parameters); + } + } + }; + } + +} diff --git a/packages/charts-vue/components/charts-common/src/props/use-property-editor.ts b/packages/charts-vue/components/charts-common/src/props/use-property-editor.ts new file mode 100644 index 0000000000000000000000000000000000000000..edc1e1d510145b7459b33e306a9346228b7681e8 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/props/use-property-editor.ts @@ -0,0 +1,73 @@ +import { DesignerHostService } from "../types"; + +/** + * PropertyEditor对外提供的接口 + * @param designerHostService + * @returns + */ +export function usePropertyEditor(designerHostService: DesignerHostService): any { + const { formSchemaUtils, formStateMachineUtils } = designerHostService; + + /** + * 把变量视图模型的变量转化为PropertyEditor的变量格式 + * @param variable + * @returns + */ + function convertToEditorVariable(variable: any, pathPrefix: string = ''): any { + return { + path: pathPrefix + variable.code, + field: variable.id, + fullPath: variable.code + }; + } + + /** + * 获取视图模型上的变量 + * @param viewModelId + * @returns + */ + function getVariablesByViewModelId(viewModelId: string, pathPrefix: string = ''): any[] { + const viewModel = formSchemaUtils.getViewModelById(viewModelId); + return viewModel.states.map(state => convertToEditorVariable(state, pathPrefix)); + } + + /** + * 获取PropertyEditor需要的变量数据 + */ + function getVariables(viewModelId: string): any[] { + const rootViewModelId = formSchemaUtils.getRootViewModelId(); + // 1、当前组件的组件变量 + const currentViewModelVariables = getVariablesByViewModelId(viewModelId); + if (viewModelId === rootViewModelId) { + return currentViewModelVariables; + } + + // 2、根组件的组件变量 + const rootViewModelVariables = getVariablesByViewModelId(rootViewModelId, 'root-component.'); + return [...currentViewModelVariables, ...rootViewModelVariables]; + } + + /** + * 获取控件名称 + * @param propertyData + * @returns + */ + function getControlName(propertyData: any): string { + const controlName = propertyData.binding && propertyData.binding.path || propertyData.id || ''; + return controlName; + } + + /** + * 获取PropertyEditor需要的状态机数据 + */ + function getStateMachines(viewModelId:string): any[] { + const renderStates = formStateMachineUtils && formStateMachineUtils.getRenderStates(); + if(renderStates){ + const viewModel = formSchemaUtils.getViewModelById(viewModelId); + return renderStates.filter(renderState => renderState.stateMachineId === viewModel.stateMachine); + } + return renderStates || []; + } + + return { getVariables, getControlName, getStateMachines }; +} diff --git a/packages/charts-vue/components/charts-common/src/types/designer-component.ts b/packages/charts-vue/components/charts-common/src/types/designer-component.ts new file mode 100644 index 0000000000000000000000000000000000000000..f30ca3c44d414c64c4e6400a2dd641038d6c4736 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/types/designer-component.ts @@ -0,0 +1,107 @@ +/* eslint-disable no-use-before-define */ +import { Ref, SetupContext } from "vue"; +import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext } from "./designer-rule"; + +export interface ComponentSchema { + + /** 设计时使用 */ + key?: string; + + id: string; + + type: string; + + contents?: ComponentSchema[]; + + // 其他属性 + [propName: string]: any; +} + +export interface DesignerComponentInstance { + + canAdd?: boolean; + + canMove: boolean; + + canSelectParent: boolean; + + canDelete: boolean; + + canNested: boolean; + + contents?: ComponentSchema[]; + + elementRef: Ref; + + parent: Ref | undefined; + + schema: ComponentSchema; + + styles?: string; + + designerClass?: string; + + canAccepts: (draggingContext: DraggingResolveContext) => boolean; + + getBelongedComponentInstance: (componentInstance: Ref) => DesignerComponentInstance | null; + + /** 获取可拖拽的上层容器 */ + getDraggableDesignItemElement: (context: DesignerItemContext) => Ref | null; + + getDraggingDisplayText: () => string; + + getDragScopeElement: () => HTMLElement | undefined; + + /** 移动内部控件后事件:在可视化设计器中,容器接收控件后事件 */ + onAcceptMovedChildElement: (element: DesignerHTMLElement, soureElement?: DesignerHTMLElement) => void; + + onChildElementMovedOut: (element: HTMLElement) => void; + + addNewChildComponentSchema: (resolveContext: DraggingResolveContext, designerHostService?: DesignerHostService) => ComponentSchema; + + /** 组件在拖拽时是否需要将所属的Component一起拖拽,用于form、data-grid等控件的拖拽 */ + triggerBelongedComponentToMoveWhenMoved?: Ref; + + /** 组件在被移除时是否需要将所属的Component一起移除,用于form、data-grid等控件的拖拽 */ + triggerBelongedComponentToDeleteWhenDeleted?: Ref; + + /** 获取属性配置 */ + getPropConfig: (...args) => any; + + /** 控件所属Component的标识*/ + belongedComponentId?: string; + + /** 控件删除后事件 */ + onRemoveComponent: () => void; + + /** 获取控件自定义操作按钮 */ + getCustomButtons?: () => DesignerComponentButton[]; + + /** 控件属性变更后事件 */ + onPropertyChanged?: (event: any) => void; +} + +export interface DesignerItemContext { + + designerItemElementRef: Ref; + + componentInstance: Ref; + + schema: ComponentSchema; + + parent?: DesignerItemContext; + + setupContext?: SetupContext; + +} + +/** + * 控件自定义操作按钮 + */ +export interface DesignerComponentButton { + id: string; + title: string; + icon: string; + class?: string; + onClick: (payload: MouseEvent) => void; +} diff --git a/packages/charts-vue/components/charts-common/src/types/designer-rule.ts b/packages/charts-vue/components/charts-common/src/types/designer-rule.ts new file mode 100644 index 0000000000000000000000000000000000000000..6375578d0fb1d605890f08c56dc23796fc23e3da --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/types/designer-rule.ts @@ -0,0 +1,150 @@ +import { Ref } from "vue"; +import { ComponentSchema, DesignerComponentButton, DesignerComponentInstance, DesignerItemContext } from "./designer-component"; +import { DesignFormVariable, DesignViewModelField, FormSchemaEntity, FormSchemaEntityField } from "./entity-schema"; + +export interface DesignerHTMLElement extends HTMLElement { + /** 记录各子元素对应的控件schema json的集合,用于container类dom节点 */ + contents?: ComponentSchema[]; + + /** 记录element对应的component实例,用于单个component节点 */ + componentInstance: Ref; + + schema: ComponentSchema; +} + +export interface DesignerHostService { + eventsEditorUtils: any; + formSchemaUtils: any; + formMetadataConverter: any; + designViewModelUtils: any; + controlCreatorUtils: any; + metadataService?: any; + formStateMachineUtils: any; + uiProviderService?: any; + [key: string]: any; +} +/** + * 绑定上下文 + */ +export interface ComponentBindingSourceContext { + /** 控件绑定类型:字段|实体 */ + bindingType: 'field' | 'entity'; + /** 控件绑定的实体schema字段 */ + entityFieldNode?: FormSchemaEntityField; + /** 控件绑定的实体schema字段若是关联带出字段,此属性记录关联字段所属的根字段 */ + entityRootFieldNode?: FormSchemaEntityField; + /** 实体schema字段对应的DesignViewModel结构 */ + designViewModelField?: DesignViewModelField; + /** 变量字段节点 */ + variableFieldNode?: DesignFormVariable; + /** 要绑定的实体 */ + bindingEntity?: FormSchemaEntity; + /** 要绑定的字段集合 */ + bindingEntityFields?: FormSchemaEntityField[]; + /** 实体在视图模型中的绑定信息 */ + bindTo?: string +} + +/** + * 拖拽上下文 + */ +export interface DraggingResolveContext { + + /** 拖拽的源元素 */ + sourceElement: DesignerHTMLElement; + /** 拖拽的源元素父容器 */ + sourceContainer: DesignerHTMLElement; + /** 拖拽的目标容器 */ + targetContainer: DesignerHTMLElement; + + /** 拖拽的控件类型 */ + componentType: string; + /** 拖拽的控件名称 */ + label: string; + /** 拖拽目标区域的组件实例 */ + parentComponentInstance: DesignerComponentInstance; + /** 拖拽位置在目标区域的索引值 */ + targetPosition?: number; + /** 拖拽控件的类别 */ + componentCategory?: string; + /** 拖拽来源:控件工具箱control / 实体树字段field / 实体树实体entity / 现有控件移动位置move */ + sourceType: string; + /** 工具箱中的控件,启用的特性 */ + componentFeature?: string; + /** 要添加的控件Schema */ + componentSchema?: any; + + /** 绑定信息 */ + bindingSourceContext?: ComponentBindingSourceContext | null; + + // [propName: string]: any; +} + +export interface UseDesignerRules { + /** + * 判断是否可以接收拖拽新增的子级控件 + */ + canAccepts(draggingContext: DraggingResolveContext): boolean; + + /** + * 判断当前容器是否是固定的上下文的中间层级 + */ + checkIsInFixedContextRules?(): boolean; + + getStyles?(): string; + + getDesignerClass?(): string; + /** + * 容器接收新创建的子控件 + */ + onResolveNewComponentSchema?: (resolveContext: DraggingResolveContext, componentSchema: ComponentSchema) => ComponentSchema; + + /** + * 移动控件后事件:在可视化设计器中,容器接收控件后调用此事件 + */ + onAcceptMovedChildElement?: (sourceElement: DesignerHTMLElement, sourceContainer: DesignerHTMLElement) => void; + + /** + * 判断是否支持移动组件 + */ + checkCanMoveComponent?(): boolean; + + /** + * 判断是否支持删除组件 + */ + checkCanDeleteComponent?(): boolean; + + /** + * 判断是否支持增加组件 + */ + checkCanAddComponent?(): boolean; + + /** + * 判断是否隐藏组件间距和线条 + */ + hideNestedPaddingInDesginerView?(): boolean; + + /** 接收控件属性信息 */ + getPropsConfig?(schema?: any, componentInstance?: any): any; + + /** + * 组件在拖拽时是否需要将所属的Component一起拖拽,用于form、data-grid等控件的拖拽 + */ + triggerBelongedComponentToMoveWhenMoved?: Ref; + /** + * 组件在被移除时是否需要将所属的Component一起移除,用于form、data-grid等控件的拖拽 + */ + triggerBelongedComponentToDeleteWhenDeleted?: Ref; + + /** 组件删除后事件 */ + onRemoveComponent?(): void; + + /** 获取控件自定义操作按钮 */ + getCustomButtons?: () => DesignerComponentButton[]; + + /** 控件属性变更后事件 */ + onPropertyChanged?: (event: any) => void; + + /** 获取可拖拽的上层容器 */ + getDraggableDesignItemElement?: (context: DesignerItemContext) => Ref | null; +} diff --git a/packages/charts-vue/components/charts-common/src/types/entity-schema.ts b/packages/charts-vue/components/charts-common/src/types/entity-schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..30e62037ec6450743f129494bdc652d9bf0b999c --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/types/entity-schema.ts @@ -0,0 +1,274 @@ +/* eslint-disable no-use-before-define */ +export interface FormSchema { + sourceUri: string; + id: string; + code: string; + name: string; + entities: FormSchemaEntity[]; + variables: FormSchemaEntityField[]; + eapiId: string; + extendProperties: { enableStdTimeFormat: boolean }; + eapiCode?: string; + eapiName?: string; + eapiNameSpace?: string; + voPath?: string; + voNameSpace?: string; +} + +/** + * 实体 + */ +export interface FormSchemaEntity { + id: string; + code: string; + name: string; + label: string; + type: FormSchemaEntityType; +} + +/** + * 字段类型枚举 + */ +export enum FormSchemaEntityField$Type { + /** + * 简单类型字段 + */ + SimpleField = "SimpleField", + /** + * 关联/UDT类型字段 + */ + ComplexField = "ComplexField" +} +/** + * 字段编辑器对象 + */ +export interface FormSchemaEntityFieldEditor { + $type: string; + [propName: string]: any; +} +export interface DesignViewModelField extends FormSchemaEntityField { + valueChanging: string; + valueChanged: string; + groupId: string; + groupName: string; + isSchemaRemoved?: boolean; + updateOn?: string; +} +/** + * 字段 + */ +export interface FormSchemaEntityField { + $type: FormSchemaEntityField$Type; + id: string; + originalId: string; + code: string; + label: string; + bindingField: string; + name: string; + defaultValue?: any; + require?: boolean; + readonly?: boolean; + type: FormSchemaEntityFieldType; + editor?: FormSchemaEntityFieldEditor; + path?: string; + bindingPath?: string; + multiLanguage?: boolean; + expression?: any; +} +/** + * 字段类型对象中的类型枚举 + */ +export enum FormSchemaEntityFieldType$Type { + /** + * 字符串 + */ + StringType = "StringType", + /** + * 备注 + */ + TextType = "TextType", + /** + * 数字(整数、浮点数) + */ + NumericType = "NumericType", + /** + * 布尔 + */ + BooleanType = "BooleanType", + /** + * 日期 + */ + DateType = "DateType", + /** + * 日期时间 + */ + DateTimeType = "DateTimeType", + /** + * 枚举 + */ + EnumType = "EnumType", + /** + * 实体类 + */ + EntityType = "EntityType", + /** + * 分级码 + */ + HierarchyType = "HierarchyType", + /** + * 对象 + */ + ObjectType = "ObjectType", + /** + * 数字(大数据) + */ + BigNumericType = "BigNumericType" +} +/** + * 字段类型中的名称 + */ +export enum FormSchemaEntityFieldTypeName { + /** + * 简单类型字段 + */ + String = "String", + /** + * 日期时间 + */ + DateTime = "DateTime", + /** + * 日期 + */ + Date = "Date", + /** + * 枚举 + */ + Enum = "Enum", + /** + * 布尔 + */ + Boolean = "Boolean", + /** + * 数字 + */ + Number = "Number", + /** + * 备注 + */ + Text = "Text", + /** + * 大数字 + */ + BigNumber = "BigNumber" + /** + * 人员 + */ +} +/** + * 枚举类型 + */ +export interface EnumData { + value: string; + name: string; +} +/** + * 实体 + */ +export interface FormSchemaEntity { + id: string; + code: string; + name: string; + label: string; + type: FormSchemaEntityType; +} +/** + * 实体类型对象 + */ +export interface FormSchemaEntityType { + $type: string; + name: string; + primary: string; + fields: FormSchemaEntityField[]; + entities?: FormSchemaEntity[]; + displayName?: string; +} +/** + * 字段类型对象 + */ +export interface FormSchemaEntityFieldType { + $type: FormSchemaEntityFieldType$Type; + name: FormSchemaEntityFieldTypeName | any; + length?: number; + precision?: number; + valueType?: FormSchemaEntityFieldType; + enumValues?: EnumData[]; + fields?: FormSchemaEntityField[]; + displayName?: string; + primary?: string; + entities?: FormSchemaEntity[]; + elementType?: GSPElementDataType; + extendProperty?: any; +} +/** + * 字段数据类型 + */ +export enum GSPElementDataType { + /** + * 文本 + */ + String = "String", + /** + * 备注 + */ + Text = "Text", + /** + * 整数 + */ + Integer = "Integer", + /** + * 浮点数 + */ + Decimal = "Decimal", + /** + * 布尔型 + */ + Boolean = "Boolean", + /** + * 日期型 + */ + Date = "Date", + /** + * 日期时间型 + */ + DateTime = "DateTime", + /** + * 二进制 + */ + Binary = "Binary" +} +/** + * dom Json ViewModel 节点中states实体 + */ +export interface FormVariable { + id: string; + code: string; + name: string; + value?: any; + type: string; + category: string; + fields?: any[]; + defaultValue?: any; +} +/** + * 表单变量 + */ +export interface DesignFormVariable extends FormVariable { + /** + * 分组ID + */ + groupId: string; + /** + * 分组名称 + */ + groupName: string; +} diff --git a/packages/charts-vue/components/charts-common/src/types/index.ts b/packages/charts-vue/components/charts-common/src/types/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..195bea74cd34c250e739f2c5ef58be6524bfa349 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/types/index.ts @@ -0,0 +1,5 @@ +export * from './types'; +export * from './designer-component'; +export * from './designer-rule'; +export * from './entity-schema'; +export * from './property-config'; diff --git a/packages/charts-vue/components/charts-common/src/types/property-config.ts b/packages/charts-vue/components/charts-common/src/types/property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b525f2bea35eb24f03127225016801ae9b2f1d3 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/types/property-config.ts @@ -0,0 +1,266 @@ +import { Ref } from "vue"; + +export interface KeyMap { + key: any; + value: any; +} + +export type EditorType = 'button-edit' | 'check-box' | 'check-group' | 'combo-list' | 'combo-lookup' | 'combo-tree' | + 'date-picker' | 'date-range' | 'datetime-picker' | 'datetime-range' | 'events-editor' | 'month-picker' | 'month-range' | + 'year-picker' | 'year-range' | 'input-group' | 'lookup' | 'number-range' | 'number-spinner' | 'radio-group' | 'text' | + 'response-layout-editor-setting' | 'switch' | 'grid-field-editor' | 'field-selector' | 'schema-selector' | 'mapping-editor' | + 'textarea' | 'response-form-layout-setting'|'binding-selector' | 'query-solution-config' | 'solution-preset' | 'item-collection-editor'; + +export interface EditorConfig { + /** 编辑器类型 */ + type: EditorType; + /** 自定义样式 */ + customClass?: string; + /** 禁用 */ + disabled?: boolean; + /** 只读 */ + readonly?: boolean; + /** 必填 */ + required?: boolean; + /** 提示文本 */ + placeholder?: string; + /** 其他属性 */ + [key: string]: any; +} + +/** 属性实体 */ +export interface PropertyEntity { + /** + * 属性ID + */ + propertyID: string; + + /** + * 属性显示的名称 + */ + propertyName?: string; + + /** + * 属性的类型 + */ + propertyType: any; + + /** + * 属性描述 + */ + description?: string; + + /** + * 属性的默认值 + */ + defaultValue?: any; + + propertyValue?: Ref; + + /** + * 是否只读,默认false + */ + readonly?: () => boolean; + + /** + * 是否可见,默认true + */ + visible: boolean | (() => boolean); + + /** + * 最小值 + */ + min?: any; + + /** + * 最大值 + */ + max?: any; + + /** + * 数字类型属性的小数位数 + */ + decimals?: number; + + /** + * 是否大数字 + */ + isBigNumber?: boolean; + + /** + * 属性改变后是否需要刷新整个面板:用于属性有联动修改的场景 + */ + refreshPanelAfterChanged?: boolean; + + /** + * 下拉框的枚举值 + */ + iterator?: KeyMap[]; + + /** + * 下拉多选类型:属性值的类型:string(多值以逗号分隔)/array(多值组装成数组) + */ + multiSelectDataType?: string; + + /** + * 文本控件限制输入的字符,支持字符和正则表达式 + */ + notAllowedChars?: any[]; + + /** + * 级联属性配置 + */ + cascadeConfig?: PropertyEntity[]; + + /** + * 级联属性是否默认收起 + */ + isExpand?: boolean; + + /** + * 是否隐藏级联属性的头部 + */ + hideCascadeTitle?: boolean; + + /** + * 模态框属性自定义编辑器参数 + */ + editorParams?: any; + + /** 模态框属性是否展示清除图标 */ + showClearButton?: boolean; + + /** 点击清除按钮后的方法,参数为清除前的属性值 */ + afterClickClearButton?(value: any): void; + + /** 点击清除按钮时是否需要弹窗确认 */ + showQuestionBeforeClear?: boolean; + + /** 点击清除按钮时弹窗确认的文案 */ + questionMessage?: string; + + editor?: EditorConfig; +} + +export interface ElementPropertyConfig { + /** + * 分类ID + */ + categoryId: string; + + /** + * 分类显示的名称 + */ + categoryName: string; + + /** + * 分类是否隐藏,默认false + */ + hide?: boolean; + + /** + * 是否隐藏分类标题 + */ + hideTitle?: boolean; + + /** + * 分类下的属性配置 + */ + properties: PropertyEntity[]; + + /** + * 是否启用级联特性,默认false + */ + enableCascade?: boolean; + + /** + * 属性值:分类启用级联特性时必填 + */ + propertyData?: any; + + /** + * 父级属性ID:分类启用级联特性时必填 + */ + parentPropertyID?: string; + + /** + * 属性关联关系,用于属性变更后修改其他属性配置或属性值 + */ + setPropertyRelates?: (changeObject: any, propertyData: any, parameters?: any) => void; + + /** + * 分类以标签页展示时,标签页的ID。若只需平铺展示,则不需要传入。 + */ + tabId?: string; + + /** + * 分类以标签页展示时,标签页的名称。若只需平铺展示,则不需要传入。 + */ + tabName?: string; + + status?: string; +} + +/** + * 属性变更集 + */ +export interface PropertyChangeObject { + /** + * 属性ID + */ + propertyID: string; + + /** + * 变更后的属性值 + */ + propertyValue: any; + + /** + * 属性所在分类ID + */ + categoryId?: string; + + /** + * 级联属性的父路径,以.分隔 + */ + propertyPath?: string; + + /** + * 级联属性的父属性ID + */ + parentPropertyID?: string; + +}; +/** + * 属性变更实体类 + */ +export interface FormPropertyChangeObject extends PropertyChangeObject { + + /** 属性变更后是否需要整体刷新表单 */ + needRefreshForm?: boolean; + + /** 属性变更后需要局部刷新的组件id */ + needRefreshedComponentId?: string; + + /** 是否需要刷新控件树 */ + needRefreshControlTree?: boolean; + + /** 是否需要刷新控件树 */ + needRefreshEntityTree?: boolean; + + /** 关联变更的属性集合,用于更新表单DOM属性 */ + relateChangeProps?: Array<{ + propertyID: string, + propertyValue: any + }>; + + /** 强关联的属性id:在当前属性变更后,页面自动定位到强关联的属性 */ + autoLocatedPropertyId?: string; +} + + +export enum FormBindingType { + Form = "Form", + Variable = "Variable" +} + diff --git a/packages/charts-vue/components/charts-common/src/types/types.ts b/packages/charts-vue/components/charts-common/src/types/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..171900117296bfd948a46fc14ee3286ce6dd2a10 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/types/types.ts @@ -0,0 +1,139 @@ +import { ComputedRef, Ref } from "vue"; +import { EffectFunction, SchemaResolverFunction } from "@farris/charts-vue/components/dynamic-resolver/src/types"; + +export interface TextBoxProps { + + customClass: string; + + disabled: boolean; + + editable: boolean; + + enableClear: boolean; + + enableTitle: boolean; + + forcePlaceholder: boolean; + + modelValue: string; + + placeholder: string; + + readonly: boolean; + + textAlign: string; + + showBorder: boolean; + + updateOn: 'blur' | 'change'; +} + +export interface UseClear { + + clearButtonClass: ComputedRef>; + + clearButtonStyle: ComputedRef>; + + hasShownClearButton: Ref; + + onClearValue: ($event: MouseEvent) => void; + + onMouseEnter: ($event: MouseEvent) => void; + + onMouseLeave: ($event: MouseEvent) => void; + + shouldShowClearButton: ComputedRef; +} + +export interface UseTextBox { + + changeTextBoxValue: (newValue: string, shouldEmitChangeEvent: boolean) => void; + + disabled?: Ref; + + displayText?: Ref; + + editable?: Ref; + + hasFocused?: ComputedRef; + + inputGroupClass: ComputedRef>; + + inputType: Ref; + + isEmpty: ComputedRef; + + modelValue?: Ref; + + readonly?: ComputedRef; + + onBlur?: (payload: FocusEvent) => boolean; + + onClick?: (payload: MouseEvent) => void; + + onFocus?: (payload: FocusEvent) => void; + + onInput?: (payload: Event) => void; + + onKeydown?: (payload: KeyboardEvent) => void; + + onKeyup?: (payload: KeyboardEvent) => void; + + onMousedown?: (payload: MouseEvent) => void; + + onTextBoxValueChange?: (payload: Event) => void; + + placeholder: ComputedRef; + + textBoxClass: ComputedRef>; + + textBoxTitle: ComputedRef; + + inputGroupStyle: ComputedRef; +} + +export interface UseDateFormat { + formatTo: (date: string | Date, format: string) => string; +} +export type NumberType = string | number; +export interface NumberOption { + prefix?: string; + suffix?: string; + decimalSeparator?: string; + groupSeparator?: string; + [key: string]: any; +} +export interface UseNumberFormat { + formatTo: (value: NumberType, opts: any) => string; + removeFormat: (value: NumberType | null | undefined, opts: any) => string; + convertCurrency: (money: string) => string; + toFixed: (value: string, precision: number) => string; + toNumber: (value: string) => number; + greaterThan: (firstValue: NumberType, secondValue: NumberType) => boolean; + lessThan: (firstValue: NumberType, secondValue: NumberType) => boolean; + equal: (firstValue: NumberType, secondValue: NumberType) => boolean; + minus: (firstValue: NumberType, secondValue: NumberType) => string; + multiplied: (firstValue: NumberType, secondValue: NumberType) => string; + plus: (firstValue: NumberType, secondValue: NumberType) => string; + average: (total: NumberType, len: number) => string; + sum: (numberArray: NumberType[]) => string; + min: (firstValue: Array | NumberType, secondValue: NumberType | null) => string; + max: (firstValue: Array | NumberType, secondValue: NumberType | null) => string; +} + +export type TimeAgoDate = Date | string | number; +export type TimeAgoOptions = { + // 可能后期有其他属性 + /** 相对的日期 */ + readonly relativeDate?: TimeAgoDate; +}; +export interface UseTimeAgoFormat { + formatTo(date: TimeAgoDate, opts?: TimeAgoOptions): string +} + +export interface RegisterContext { + schemaMap: Record; + schemaResolverMap: Record; + propertyConfigSchemaMap: Record; + propertyEffectMap: Record; +} diff --git a/packages/charts-vue/components/charts-common/src/utils/index.ts b/packages/charts-vue/components/charts-common/src/utils/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0954f8cc6fc91732528ccd9880065ca270b285b3 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/index.ts @@ -0,0 +1,15 @@ +export * from './src/dom/event'; +export * from './src/common'; +export * from './src/hook'; +export * from './src/number'; +export * from './src/resove-asset'; +export * from './src/throttle'; +export * from './src/transition'; +export * from './src/type'; +export * from './src/date'; +export * from './src/string'; +export * from './src/use-appearance'; +export * from './src/query-filter'; +export * from './src/with-install'; +export * from './src/with-register'; +export * from './src/with-register-designer'; diff --git a/packages/charts-vue/components/charts-common/src/utils/src/common.ts b/packages/charts-vue/components/charts-common/src/utils/src/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..d258ec7627ac43f26006460f5b55c34dba3ce2d9 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/common.ts @@ -0,0 +1,99 @@ +import { isDef, isNumeric, isObject } from './type'; + +export function noop() { } + +export const omitProperty = >(obj: T, key: keyof T): Omit => { + const { [key]: omitted, ...rest } = obj; + return rest as Omit; +}; + +export function addUnit(value: string | number) { + if (!isDef(value)) { + return undefined; + } + value = String(value); + return isNumeric(value) ? `${value}px` : value; +} + +export const escapeHtml = (str: string) => { + if (str === null || str === undefined || str === '') { + return ''; + } + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/\//g, '/'); +}; + +export const unescapeHtml = (str: string) => { + if (str === null || str === undefined || str === '') { + return ''; + } + return str + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(///g, '/'); +}; + +export const getValue = (field: string, data: any, safe = false) => { + if (!data) { + return ''; + } + let resultVal = ''; + if (field.indexOf('.') === -1) { + resultVal = data[field]; + } else { + resultVal = field.split('.').reduce((obj, key) => { + if (obj) { + return obj[key]; + } + return null; + }, data); + } + + if (safe) { + return escapeHtml(resultVal); + } + return resultVal; +}; + +export const setValue = (obj: Record, field: string, val: any) => { + if(!obj || !field){ + return; + } + if (field.indexOf('.') > 0) { + const fieldMap = field.split('.'); + const target = fieldMap.slice(0, -1).reduce((record, fieldItem) => { + if(!isObject(record[fieldItem])) { + record[fieldItem] = {}; + } + return record[fieldItem]; + }, obj); + + if (target) { + const targetField = fieldMap.pop(); + if (targetField) { + target[targetField] = val; + } + } + } else { + obj[field] = val; + } +}; +export function extractProperties( + obj: T, + props: K[] +): Pick { + return props.reduce((result, prop) => { + if (prop in obj) { + result[prop] = obj[prop]; + } + return result; + }, {} as Pick); +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/date.ts b/packages/charts-vue/components/charts-common/src/utils/src/date.ts new file mode 100644 index 0000000000000000000000000000000000000000..14fbaec4b97eb902cb6ca52c56565b5fd3a1bbbe --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/date.ts @@ -0,0 +1,134 @@ +import { isArray, isString } from './type'; + +export const getDateRecord = (target: Date) => { + const year = target.getFullYear(); + const month = target.getMonth() + 1; + const day = target.getDate(); + const hours = target.getHours(); + const minutes = target.getMinutes(); + const seconds = target.getSeconds(); + + return { + year, + month, + day, + hours, + minutes, + seconds + }; +}; + +export const getTimeRecord = (value: string | number[]) => { + const [hours, minutes, seconds] = isString(value) + ? value.split(':').map((item) => Number(item)) + : isArray(value) + ? value + : [0, 0, 0]; + + return { + hours, + minutes, + seconds + }; +}; + +export const isLeap = (year: number) => { + return year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0); +}; + +export const formatTime = ( + value: { hours: number; minutes: number; seconds: number }, + format: string +) => { + const { hours, minutes, seconds } = value; + return format + .replace('HH', String(hours).padStart(2, '0')) + .replace('mm', String(minutes).padStart(2, '0')) + .replace('ss', String(seconds).padStart(2, '0')); +}; + +export const parseTime = (value: string) => { + const timeRegex = /^(\d{2}):(\d{2}):(\d{2})$/; + const match = value.match(timeRegex); + + if (!match) { + throw new Error('Invalid time format'); + } + + const [_, hours, minutes, seconds] = match; + + return new Date(0, 0, 0, parseInt(hours, 10), parseInt(minutes, 10), parseInt(seconds, 10)); +}; + +export const formatDate = (value: Date, format: string) => { + const { year, month, day, hours, minutes, seconds } = getDateRecord(value); + return format + .replace('yyyy', String(year)) + .replace('MM', String(month).padStart(2, '0')) + .replace('dd', String(day).padStart(2, '0')) + .replace('HH', String(hours).padStart(2, '0')) + .replace('mm', String(minutes).padStart(2, '0')) + .replace('ss', String(seconds).padStart(2, '0')); +}; + +export const parseDate = (value: string, format: string) => { + const [dateValue, timeValue] = value.split(' '); + const [dateFormat, timeFormat] = format.split(' '); + + const dateRegex = /年|月|日|-|\//g; + + const formatParts = dateFormat.replace(dateRegex, '-').split('-'); + const formatMap = formatParts.reduce((map, part, index) => { + map[part] = index; + return map; + }, {} as Record); + + const dateValueParts = dateValue + .replace(dateRegex, '-') + .split('-') + .map((item) => Number(item)); + + const year = dateValueParts[formatMap.yyyy]; + const month = dateValueParts[formatMap.MM] ? dateValueParts[formatMap.MM] - 1 : 0; + const day = dateValueParts[formatMap.dd] || 1; + + const { hours, minutes, seconds } = getTimeRecord(timeValue); + + // 创建Date对象 + const parsedDate = new Date(year, month, day, hours, minutes, seconds); + + // 检查是否有效日期 + if (isNaN(parsedDate as any)) { + throw new Error('Invalid date format'); + } + + return parsedDate; +}; + +export const compareDate = (firstDate: Date, secondDate: Date) => { + return firstDate.getTime() - secondDate.getTime(); +}; + +export const isEqualDate = (firstDate: Date, secondDate: Date) => { + const { + year: firstDateYear, + month: firstDateMonth, + day: firstDateDay + } = getDateRecord(firstDate); + const { + year: secondDateYear, + month: secondDateMonth, + day: secondDateDay + } = getDateRecord(secondDate); + + return ( + compareDate( + new Date(firstDateYear, firstDateMonth, firstDateDay), + new Date(secondDateYear, secondDateMonth, secondDateDay) + ) === 0 + ); +}; + +export const isEqualDateTime = (firstDate: Date, secondDate: Date) => { + return compareDate(firstDate, secondDate) === 0; +}; diff --git a/packages/charts-vue/components/charts-common/src/utils/src/dom/event.ts b/packages/charts-vue/components/charts-common/src/utils/src/dom/event.ts new file mode 100644 index 0000000000000000000000000000000000000000..07e07fc6addf078dea31511cf1389709648a88c0 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/dom/event.ts @@ -0,0 +1,14 @@ +export function stopPropagation(event: Event) { + event.stopPropagation(); +} + +export function preventDefault(event: Event, isStopPropagation?: boolean) { + /* istanbul ignore else */ + if (typeof event.cancelable !== 'boolean' || event.cancelable) { + event.preventDefault(); + } + + if (isStopPropagation) { + stopPropagation(event); + } +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/hook.ts b/packages/charts-vue/components/charts-common/src/utils/src/hook.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b748aaa8d788177f0b77b1e143ab3b7c24b9e15 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/hook.ts @@ -0,0 +1,18 @@ +import { nextTick, onMounted, onActivated } from 'vue'; + +export function onMountedOrActivated(hook: () => any) { + let mounted: boolean; + + onMounted(() => { + hook(); + nextTick(() => { + mounted = true; + }); + }); + + onActivated(() => { + if (mounted) { + hook(); + } + }); +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/number.ts b/packages/charts-vue/components/charts-common/src/utils/src/number.ts new file mode 100644 index 0000000000000000000000000000000000000000..dab735dafe482737f6c2e73bfa26ab27472ef5a1 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/number.ts @@ -0,0 +1,26 @@ +import { trimExtraChar } from './string'; +import { isDef } from './type'; + +export function formatToNumber(value: string, allowDot: boolean) { + value = isDef(value) ? value : ''; + let regExp = /[^-0-9]/g; + value = trimExtraChar(value, '-', /-/g); + if (allowDot) { + value = trimExtraChar(value, '.', /\./g); + regExp = /[^-0-9.]/g; + } else { + value = value.split('.')[0]; + } + return value.replace(regExp, ''); +} + +export function parseFloat(value: string | number, precision = 0) { + return Number.parseFloat(value ? String(value) : '0').toFixed(precision); +} + +export function range(num: number, min: number, max: number): number { + return Math.min(Math.max(num, min), max); +} +export function random(min: number, max: number) { + return Math.round(Math.random() * (max - min) + min); +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/query-filter.ts b/packages/charts-vue/components/charts-common/src/utils/src/query-filter.ts new file mode 100644 index 0000000000000000000000000000000000000000..90717af9bf94215134ef4ca23f78a035a20b831a --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/query-filter.ts @@ -0,0 +1,40 @@ + +export enum FilterCompareType { + Equal = 0, + NotEqual = 1, + Greater = 2, + GreaterOrEqual = 3, + Less = 4, + LessOrEqual = 5, + Like = 6, + LikeStartWith = 7, + LikeEndWith = 8, + NotLike = 9, + NotLikeStartWith = 10, + NotLikeEndWith = 11, + Is = 12, + IsNot = 13, + In = 14, + NotIn = 15, +} + +export enum FilterRelation { + Empty, + And, + Or, +} + +export enum FilterExpressType { + Value = 0, + Expression = 1, +} + +export interface FilterCondition { + FilterField: string + Compare: FilterCompareType + Value: string + Relation: FilterRelation + Expresstype: FilterExpressType + Lbracket: string + Rbracket: string +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/resove-asset.ts b/packages/charts-vue/components/charts-common/src/utils/src/resove-asset.ts new file mode 100644 index 0000000000000000000000000000000000000000..63a8585c23af908949e9bdc38aa56480aef0557b --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/resove-asset.ts @@ -0,0 +1,33 @@ +const getKebabCase = (str: string) => { + return str.replace(/[A-Z]/g, function (i) { + return "-" + i.toLowerCase(); + }); +}; +const getCamelCase = (str: string) => { + return str.replace(/-([a-z])/g, function (all, i) { + return i.toUpperCase(); + }); +}; + +const hasOwn = (v: object, s: string)=> Object.prototype.hasOwnProperty.call(v, s); + +export function resolveAsset ( + assets: Record, + id: string +): any { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return; + } + // check local registration variations first + if (hasOwn(assets, id)) {return assets[id];} + const camelizedId = getKebabCase(id); + if (hasOwn(assets, camelizedId)) {return assets[camelizedId];} + const PascalCaseId = getCamelCase(camelizedId); + if (hasOwn(assets, PascalCaseId)) {return assets[PascalCaseId];} + + // fallback to prototype chain + const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + + return res; +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/string.ts b/packages/charts-vue/components/charts-common/src/utils/src/string.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d2519bfed67125ad1f581891e35423a33cdffa3 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/string.ts @@ -0,0 +1,18 @@ +export function trimExtraChar(value: string, char: string, regExp: RegExp) { + const index = value.indexOf(char); + + if (index === -1) { + return value; + } + + if (char === '-' && index !== 0) { + return value.slice(0, index); + } + + return value.slice(0, index + 1) + value.slice(index).replace(regExp, ''); +}; + +export function camelToKebabCase(camelStr: string) { + // 首字母转小写,其余每个大写字母前加下划线并转小写 + return camelStr.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/throttle.ts b/packages/charts-vue/components/charts-common/src/utils/src/throttle.ts new file mode 100644 index 0000000000000000000000000000000000000000..b10e67cb439c19ca7c223c4c8e65e9f74242962c --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/throttle.ts @@ -0,0 +1,41 @@ +export const throttle = ( + fn: (...args: any)=> any, + wait: number, + options: { leading?: boolean; trailing?: boolean } = {} +) => { + let timeout: any = null; + let previous = 0; + + const throttled = (...args: any) => { + const nowDate = +new Date(); + + if (!previous && options.leading === false) { + previous = nowDate; + } + + const remaining = wait - (nowDate - previous); + + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = nowDate; + fn(...args); + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(() => { + previous = options.leading === false ? 0 : +new Date(); + timeout = null; + fn(...args); + }, remaining); + } + }; + + // 手动取消 + throttled.cancel = function () { + clearTimeout(timeout); + previous = 0; + timeout = null; + }; + return throttled; +}; diff --git a/packages/charts-vue/components/charts-common/src/utils/src/transition.ts b/packages/charts-vue/components/charts-common/src/utils/src/transition.ts new file mode 100644 index 0000000000000000000000000000000000000000..9faa49dec92d9dce259fdc96cfa504033f47cf59 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/transition.ts @@ -0,0 +1,14 @@ +export const getSlideTransitionName = (position: string | undefined) => { + switch (position) { + case 'bottom': + return 'fm-slide-up'; + case 'top': + return 'fm-slide-down'; + case 'left': + return 'fm-slide-left'; + case 'right': + return 'fm-slide-right'; + default: + return 'fm-fade'; + } +}; diff --git a/packages/charts-vue/components/charts-common/src/utils/src/type.ts b/packages/charts-vue/components/charts-common/src/utils/src/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..93805dd9eac71720f033ba09214d273d09c583ce --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/type.ts @@ -0,0 +1,105 @@ +export const inBrowser = typeof window !== 'undefined'; + +export const inIOS = () => /(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent); + +export const inAndroid = () => /(Android|Adr)/i.test(navigator.userAgent); + +export function isDef(val: T): val is NonNullable { + return val !== undefined && val !== null; +} + +export function isNull(val: unknown): val is null { + return val === null; +} + +export function isUndefined(val: unknown): val is undefined { + return val === undefined; +} + +export function isString(val: unknown): val is string { + return typeof val === 'string'; +} + +export function isNumber(val: unknown): val is number { + return typeof val === 'number'; +} + +export function isBoolean(val: unknown): val is null { + return typeof val === 'boolean'; +} + +export const { isArray } = Array; + +export function isSymbol(val: unknown): val is symbol { + return typeof val === 'symbol'; +} + +export function isFunction(val: unknown): val is FunctionConstructor { + return typeof val === 'function'; +} + +export function isObject(val: unknown): val is Record { + return val !== null && typeof val === 'object'; +} + +export function isPromise(val: unknown): val is Promise { + return isObject(val) && isFunction(val.then) && isFunction(val.catch); +} + +export function isNumeric(val: string) { + return /^-?\d+(\.\d+)?$/.test(val); +} + +export function isDate(val: unknown): val is Date { + return typeof val === 'object' && val instanceof Date; +} + +export function getType(val: unknown) { + let type: string; + switch (val) { + case isString(val):{ + type = 'string'; + break; + } + case isNumber(val):{ + type = 'number'; + break; + } + case isDate(val):{ + type = 'date'; + break; + } + case isNull(val):{ + type = 'null'; + break; + } + case isUndefined(val):{ + type = 'undefined'; + break; + } + case isArray(val):{ + type = 'array'; + break; + } + case isSymbol(val):{ + type = 'symbol'; + break; + } + case isFunction(val):{ + type = 'function'; + break; + } + case isPromise(val):{ + type = 'promise'; + break; + } + case isObject(val):{ + type = 'object'; + break; + } + default: + type = typeof val; + break; + } + return type; +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/use-appearance.ts b/packages/charts-vue/components/charts-common/src/utils/src/use-appearance.ts new file mode 100644 index 0000000000000000000000000000000000000000..74038b3cfa007abb36698beea9387161dd6d27fc --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/use-appearance.ts @@ -0,0 +1,33 @@ +/** + * 根据传递过来已有的类对象和自定义类名,构造新的样式对象 + * @param classObject + * @param customClass + * @returns + */ +export function getCustomClass(classObject: Record, customClass: string) { + const customClassArray = customClass?.split(' ') || []; + customClassArray.reduce((result: Record, classString: string) => { + if (classString) { + result[classString] = true; + } + return result; + }, classObject); + return classObject; +} +/** + * 根据传递过来已有的style对象和自定义style,构造新的style对象 + * @param styleObject + * @param customStyle + * @returns + */ +export function getCustomStyle(styleObject: Record, customStyle: string) { + const styleArray = customStyle?.split(';') || []; + styleArray.reduce((result: Record, styleString: string) => { + if (styleString) { + const styles = styleString.split(':'); + result[styles[0]] = styles[1]; + } + return result; + }, styleObject); + return styleObject; +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/with-install.ts b/packages/charts-vue/components/charts-common/src/utils/src/with-install.ts new file mode 100644 index 0000000000000000000000000000000000000000..86c106954880cb679976026670a6a815e3f20a7b --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/with-install.ts @@ -0,0 +1,16 @@ +import type { App, Component } from 'vue'; + +export type WithInstall = T & { + install(app: App): void; +}; + +export function withInstall(options: T) { + (options as Record).install = (app: App) => { + const { name } = options; + if (name) { + app.component(name, options); + } + }; + + return options as WithInstall; +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/with-register-designer.ts b/packages/charts-vue/components/charts-common/src/utils/src/with-register-designer.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b4460284bc8d7c79c961278f16f6684f480ee90 --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/with-register-designer.ts @@ -0,0 +1,22 @@ +import { RegisterContext } from '@farris/charts-vue/components/charts-common/src/types'; +import type { App, Component } from 'vue'; + +export type WithRegisterDesigner = T & { + registerDesigner(app: App): void; +}; + +export function withRegisterDesigner(component: T, options: { name: string, designerComponent: Component, propsResolverGenerator: (registerContext: RegisterContext) => (schemaValue?: Record) => Record }) { + const { name, designerComponent, propsResolverGenerator } = options; + + (component as Record).registerDesigner = ( + componentMap: Record, + propsResolverMap: Record, + configResolverMap: Record, + registerContext: RegisterContext + ) => { + componentMap[name] = designerComponent; + propsResolverMap[name] = propsResolverGenerator(registerContext); + }; + + return component as WithRegisterDesigner; +} diff --git a/packages/charts-vue/components/charts-common/src/utils/src/with-register.ts b/packages/charts-vue/components/charts-common/src/utils/src/with-register.ts new file mode 100644 index 0000000000000000000000000000000000000000..5420647935ed8d87ccea1504572c41eefd60d47d --- /dev/null +++ b/packages/charts-vue/components/charts-common/src/utils/src/with-register.ts @@ -0,0 +1,23 @@ +import { RegisterContext } from '@farris/charts-vue/components/charts-common/src/types'; +import type { App, Component } from 'vue'; + +export type WithRegister = T & { + register(app: App): void; +}; + +export function withRegister(component: T, options: { name: string, propsResolverGenerator: (registerContext: RegisterContext) => (schemaValue?: Record) => Record }) { + const { name, propsResolverGenerator } = options; + + (component as Record).register = ( + componentMap: Record, + propsResolverMap: Record, + configResolverMap: Record, + resolverMap: Record, + registerContext: RegisterContext + ) => { + componentMap[name] = component; + propsResolverMap[name] = propsResolverGenerator(registerContext); + }; + + return component as WithRegister; +} diff --git a/packages/charts-vue/components/dynamic-resolver/index.ts b/packages/charts-vue/components/dynamic-resolver/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..75573a8aa9037fce5ba0a1b6759ac85c92f75373 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/index.ts @@ -0,0 +1,21 @@ +export * from './src/types'; +export * from './src/props-resolver'; +export * from './src/common/appearance-resolver'; +export * from './src/common/toolbar-resolver'; +export * from './src/common/data-resolver'; +export * from './src/common/position-resolver'; +export * from './src/common/padding-resolver'; +export * from './src/common/margin-resolver'; +export * from './src/common/size-resolver'; +export * from './src/binding-resolver'; +export * from './src/events-resolver'; +export * from './src/selection-item-resolver'; +export * from './src/editor-resolver'; +export * from './src/visible-prop-resolver'; +export * from './src/event-handler-resolver'; +export * from './src/resolver/schema/schema-resolver'; +export * from './src/resolver/schema/schema-resolver-design'; +export * from './src/update-columns-resolver'; + +export { propertyConfigSchemaMap, getPropertyConfigBySchema } from './src/resolver/property-config/property-config-resolver'; +export { propertyConfigSchemaMapForDesigner, getPropertyConfigBySchemaForDesigner } from './src/resolver/property-config/property-config-resolver-design'; diff --git a/packages/charts-vue/components/dynamic-resolver/src/binding-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/binding-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..9428dca63fb04f52c32112effe780fe22bdf2f61 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/binding-resolver.ts @@ -0,0 +1,68 @@ +import { BindingData, BindingResolver } from "./types"; + +export function createFormBindingResolver(): BindingResolver { + function resolve(schema: Record, bindingData: BindingData) { + const { id } = schema || {}; + const { field } = schema.binding || {}; + return { + 'modelValue': bindingData.getValue(id), + 'onUpdate:modelValue': (value: any) => { + bindingData.setValue(id, field, value); + } + }; + }; + return { + resolve + }; +} + +export function createCollectionBindingResolver(): BindingResolver { + function resolve(schema: Record, bindingData: BindingData) { + const { id } = schema || {}; + const { dataSource } = schema || {}; + if (dataSource === undefined) { + return {}; + } + return { + 'data': bindingData.getValue(id), + 'onUpdate:data': (...payload: any[]) => { + // bindingData.updateData(...payload); + } + }; + }; + return { + resolve + }; +} + +export function createTreeGridBindingResolver(): BindingResolver{ + function resolve(schema: Record, bindingData: BindingData) { + const { id } = schema || {}; + const { dataSource } = schema || {}; + if (dataSource === undefined) { + return {}; + } + return { + }; + }; + return { + resolve + }; +} + +export function createDataMappingBindingResolver(): BindingResolver { + function resolve(schema: Record, bindingData: BindingData) { + const { path } = schema.binding || {}; + if (!path) { + return {}; + } + return { + 'onUpdate:dataMapping': (...payloads: any[]) => { + // bindingModel.dataMapping(...payloads); + } + }; + }; + return { + resolve + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/appearance-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/common/appearance-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..4392ca2d6ccfa6e086a9e2fe6fe874a8ab1b73e3 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/appearance-resolver.ts @@ -0,0 +1,3 @@ +export function resolveAppearance(key: string, appearanceObject: { class: string; style: string }) { + return { customClass: appearanceObject?.class, customStyle: appearanceObject?.style }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/data-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/common/data-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7481164927ff36a39a368eb778cc7a84d5c073f --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/data-resolver.ts @@ -0,0 +1,52 @@ + +export function resolveData(key: string, datas: { name: string; value: string }[]) { + const result:{ text: string; value: string }[] = []; + + if(datas && datas.length > 0){ + datas.forEach((data) => { + const newData: { text: string; value: string } = { + text: '', + value: '' + }; + Object.keys(data).map(key => { + if (key === 'name') { + newData['text'] = data[key]; + }else{ + newData[key] = data[key]; + } + }); + + result.push(newData); + }); + } + + return { + options:result + }; +} + +export function resolveDataPicker(key: string, datas: { name: string; value: string }[]) { + const result:{ text: string; value: string }[] = []; + + if(datas && datas.length > 0){ + datas.forEach((data) => { + const newData: { text: string; value: string } = { + text: '', + value: '' + }; + Object.keys(data).map(key => { + if (key === 'name') { + newData['text'] = data[key]; + }else{ + newData[key] = data[key]; + } + }); + + result.push(newData); + }); + } + + return { + columns:result + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/margin-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/common/margin-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..485fb4ea3e9ab6f115bd948cdfb5b51ac3af41fb --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/margin-resolver.ts @@ -0,0 +1,6 @@ +import { DirectionalStyleObject, getMarginStyle, concatCustomStyle } from './utils'; + +export function resolveMargin(key: string, marginObject: DirectionalStyleObject, resolvedSchema: any) { + const marginStyle = getMarginStyle(marginObject); + return concatCustomStyle(marginStyle, resolvedSchema); +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/padding-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/common/padding-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1a42dadc8601f4f682e6564b048a6b6f2997bbb --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/padding-resolver.ts @@ -0,0 +1,6 @@ +import { DirectionalStyleObject, getPaddingStyle, concatCustomStyle } from './utils'; + +export function resolvePadding(key: string, paddingObject: DirectionalStyleObject, resolvedSchema: any) { + const paddingStyle = getPaddingStyle(paddingObject); + return concatCustomStyle(paddingStyle, resolvedSchema); +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/position-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/common/position-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fdb8bdc42799278535d7df4313b7fefc0dec56d --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/position-resolver.ts @@ -0,0 +1,6 @@ +import { DirectionalStyleObject, getPositionStyle, concatCustomStyle } from './utils'; + +export function resolvePosition(key: string, positionObject: DirectionalStyleObject, resolvedSchema: any) { + const positionStyle = getPositionStyle(positionObject); + return concatCustomStyle(positionStyle, resolvedSchema); +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/size-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/common/size-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..9bea291713c32d216be783d8c7bcd82aedaab8cc --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/size-resolver.ts @@ -0,0 +1,6 @@ +import { SizeStyleObject, getSizeStyle, concatCustomStyle } from './utils'; + +export function resolveSize(key: string, sizeObject: SizeStyleObject, resolvedSchema: any) { + const sizeStyle = getSizeStyle(sizeObject); + return concatCustomStyle(sizeStyle, resolvedSchema); +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/toolbar-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/common/toolbar-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a3221cdf4f446618dc6afab951d5f61fd768cb9 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/toolbar-resolver.ts @@ -0,0 +1,36 @@ +function generateResolveToolbarFunction(toolbarPropertyName = "toolbarItems", options?: { defaultButtonType: string }) { + return (key: string, toolbarObject: { items: any[] }) => { + const toolbarItems: any[] = []; + const result = { [toolbarPropertyName]: toolbarItems }; + if (typeof toolbarObject !== 'object' || !toolbarObject) { + return result; + } + const items: any[] = toolbarObject?.items || []; + if (!Array.isArray(items)) { + return result; + } + items.filter(item => typeof item === 'object' && !!item).forEach(item => { + const newItem: any = {}; + Object.keys(item).forEach(key => { + if (key === 'displayType') { + newItem.type = item[key]; + } else if (key === 'appearance') { + newItem.customClass = item[key]?.class || ''; + newItem.customStyle = item[key]?.style || ''; + } else if (key === 'type') { + return; + } else { + newItem[key] = item[key]; + } + }); + if (!newItem.type && !newItem.customClass && !newItem.customStyle && options?.defaultButtonType) { + newItem.type = options.defaultButtonType; + } + toolbarItems.push(newItem); + }); + return result; + }; +} + +export const resolveToolbar = generateResolveToolbarFunction("toolbarItems", { defaultButtonType: "primary" }); +export const resolveSwipeToolbar = generateResolveToolbarFunction("swipeToolbar", { defaultButtonType: "danger" }); diff --git a/packages/charts-vue/components/dynamic-resolver/src/common/utils.ts b/packages/charts-vue/components/dynamic-resolver/src/common/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..4aedc6315078208f61b1e1c45180152834b0e2f5 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/common/utils.ts @@ -0,0 +1,59 @@ +export function concatCustomStyle(newStyle: string, resolvedSchema: any): { customStyle: string } { + newStyle = newStyle || ''; + let customStyle: string = resolvedSchema?.customStyle || ''; + if (typeof customStyle !== 'string') { + customStyle = ''; + } + customStyle = customStyle.trim(); + customStyle = newStyle + customStyle; + return { customStyle }; +} + +function convertStyleObject2String(styleObject: any, validPropNames: string[], cssPropNamePrefix?: string): string { + if (typeof styleObject !== 'object' || !styleObject) { + return ''; + } + return Object.keys(styleObject) + .filter((propName) => { + return validPropNames.includes(propName); + }) + .reduce((styleString, propName) => { + const value = styleObject[propName]; + if (typeof value !== 'number') { + return styleString; + } + const fullPropName = cssPropNamePrefix ? `${cssPropNamePrefix}-${propName}` : propName; + return `${styleString}${fullPropName}: ${value}px;`; + }, ''); +} + +export interface DirectionalStyleObject { + top?: number; + right?: number; + bottom?: number; + left?: number; +} + +export interface SizeStyleObject { + width?: number; + height?: number; +} + +const DIRECTIONAL_STYLE_PROPS = ["top", "right", "bottom", "left"]; +const SIZE_STYLE_PROPS = ["width", "height"]; + +export function getPaddingStyle(paddingObject: DirectionalStyleObject): string { + return convertStyleObject2String(paddingObject, DIRECTIONAL_STYLE_PROPS, 'padding'); +} + +export function getMarginStyle(marginObject: DirectionalStyleObject): string { + return convertStyleObject2String(marginObject, DIRECTIONAL_STYLE_PROPS, 'margin'); +} + +export function getPositionStyle(positionObject: DirectionalStyleObject): string { + return convertStyleObject2String(positionObject, DIRECTIONAL_STYLE_PROPS); +} + +export function getSizeStyle(sizeObject: SizeStyleObject): string { + return convertStyleObject2String(sizeObject, SIZE_STYLE_PROPS); +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/appearance.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/appearance.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf9c14e64b3470662551d4ffa82c7f364cf56d28 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/appearance.converter.ts @@ -0,0 +1 @@ +export { appearanceConverter as default } from "./cascade.converter"; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/buttons.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/buttons.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b3d4f389d4fa5fbc885f2195b3ac5dd98f4069d --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/buttons.converter.ts @@ -0,0 +1,8 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; + +export default { + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return (schema.buttons && schema.buttons.length) ? `共 ${schema.buttons.length} 项` : '无'; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/cascade.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/cascade.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..334412627c9464cd2894a15ed0f69992f6cf8443 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/cascade.converter.ts @@ -0,0 +1,29 @@ +import { PropertyConverter, SchemaService } from "../types"; + +export function generateCascadePropertyConverter(parentPropertyKey: string, parentPropertyDefaultValue: any = {}): PropertyConverter { + return { + convertTo: (schema: Record, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (!schema[parentPropertyKey] || typeof schema[parentPropertyKey] !== 'object') { + schema[parentPropertyKey] = parentPropertyDefaultValue; + } + schema[parentPropertyKey][propertyKey] = propertyValue; + }, + convertFrom: (schema: Record, propertyKey: string, schemaService: SchemaService) => { + return schema[parentPropertyKey]?.[propertyKey]; + }, + }; +} + +export const appearanceConverter = generateCascadePropertyConverter('appearance'); +export const sizeConverter = generateCascadePropertyConverter('size'); +export const paddingConverter = generateCascadePropertyConverter('padding'); +export const marginConverter = generateCascadePropertyConverter('margin'); +export const borderRadiusConverter = generateCascadePropertyConverter('borderRadius'); +export const positionConverter = generateCascadePropertyConverter('position'); +export const flexBoxConverter = generateCascadePropertyConverter('flexBox'); + +export const toolbarConverter = generateCascadePropertyConverter('toolbar', { items: [] }); +export const swipeToolbarConverter = generateCascadePropertyConverter('swipeToolbar', { items: [] }); +export const rightToolbarConverter = generateCascadePropertyConverter('rightToolbar', { items: [] }); +export const chartsBarGridConverter = generateCascadePropertyConverter('grid', { items: [] }); + diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/change-editor.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/change-editor.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..f81cdec930a9276cece74ba135daa6231d0ec9eb --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/change-editor.converter.ts @@ -0,0 +1,15 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; + +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + // eslint-disable-next-line no-self-assign + schema[propertyKey] = schema[propertyKey]; + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + if (schema.editor) { + return schemaService.getRealEditorType(schema.editor.type); + } + return ''; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/enum-data.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/enum-data.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..e065deb1c51656601c7460be2465b0d33ee62517 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/enum-data.converter.ts @@ -0,0 +1,11 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + schema.editor[propertyKey] = propertyValue.value; + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return schema.editor && Object.prototype.hasOwnProperty.call(schema.editor, propertyKey) ? + schema.editor[propertyKey] : schema[propertyKey]; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/field-selector.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/field-selector.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..a9e96534d003f010e45722683b757d6100a50b20 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/field-selector.converter.ts @@ -0,0 +1,24 @@ +import { PropertyConverter } from "../types"; + +export default { + convertFrom: (schema: Record, propertyKey: string) => { + // return schema['binding'] ? schema['binding']['type'] + ":" + schema['binding']['path'] : ''; + return schema['binding'] ? schema['binding']['path'] : ''; + + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any) => { + if (propertyValue && propertyValue.length > 0) { + const bindingData = propertyValue[0]; + + if (!schema.binding) { + schema.binding = {}; + } + + schema.binding.type = 'Form'; + schema.binding.path = bindingData.bindingField; + schema.binding.field = bindingData.id; + schema.binding.fullPath = bindingData.path; + schema.path = bindingData.bindingPath; + } + } +} as PropertyConverter;; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/form-group-label.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/form-group-label.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac8aec8d00a6149d900a69d2b39c45c755400355 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/form-group-label.converter.ts @@ -0,0 +1,10 @@ +import { PropertyConverter } from "../types"; + +export default { + convertFrom: (schema: Record, propertyKey: string) => { + return schema[propertyKey] || ''; + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any) => { + schema[propertyKey] = propertyValue; + } +} as PropertyConverter;; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/grid-selection.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/grid-selection.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..c39616ec84b798aee8b937c8327b4dc0fc1db21a --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/grid-selection.converter.ts @@ -0,0 +1,13 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (!schema.selection) { + schema.selection = {}; + } + schema.selection[propertyKey] = propertyValue; + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return schema.selection ? schema.selection[propertyKey] : schema[propertyKey]; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/index.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..060b0343df1d014b6af89dd01f374d9082c45d17 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/index.ts @@ -0,0 +1 @@ +export * from './cascade.converter'; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/items-count.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/items-count.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..5d1007fc73fc8534fb25a39e6d8b047fd3d00815 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/items-count.converter.ts @@ -0,0 +1,8 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; + +export default { + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return (schema[propertyKey] && schema[propertyKey].length) ? `共 ${schema[propertyKey].length} 项` : ''; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/pagination.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/pagination.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..d95e640631fa049fa2b3cbb2ee80c8a044cc6a31 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/pagination.converter.ts @@ -0,0 +1,16 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; +/** + * NG版,此处pagination类型是布尔 + */ +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (!schema.pagination) { + schema.pagination = {}; + } + schema.pagination[propertyKey] = propertyValue; + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return schema.pagination ? schema.pagination[propertyKey] : schema[propertyKey]; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/property-editor.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/property-editor.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..39113f08df33e05d67a8f89e116ce966b3ed8b5d --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/property-editor.converter.ts @@ -0,0 +1,14 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; + +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (schema.editor) { + schema.editor[propertyKey] = propertyValue; + } + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return schema.editor && Object.prototype.hasOwnProperty.call(schema.editor, propertyKey) ? + schema.editor[propertyKey] : schema[propertyKey]; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/right-toolbar.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/right-toolbar.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d9417d773d5117ceb4556cb8a569b7cce414027 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/right-toolbar.converter.ts @@ -0,0 +1,14 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; + +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (!schema.rightToolbar) { + schema.rightToolbar = []; + } + schema.rightToolbar[propertyKey] = propertyValue; + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return schema.rightToolbar ? schema.rightToolbar[propertyKey] : schema[propertyKey]; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/converter/row-number.converter.ts b/packages/charts-vue/components/dynamic-resolver/src/converter/row-number.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ca72dfbcfbd32253211d979e8a719d3050f2256 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/converter/row-number.converter.ts @@ -0,0 +1,13 @@ +import { ComponentSchema } from "@farris/charts-vue/components/charts-common/src/types"; +import { PropertyConverter, SchemaService } from "../types"; +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (!schema.rowNumber) { + schema.rowNumber = {}; + } + schema.rowNumber[propertyKey] = propertyValue; + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return schema.rowNumber ? schema.rowNumber[propertyKey] : schema[propertyKey]; + } +} as PropertyConverter; diff --git a/packages/charts-vue/components/dynamic-resolver/src/editor-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/editor-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a539482064e22f33f89beb10f766ca41f156623 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/editor-resolver.ts @@ -0,0 +1,10 @@ +import { EditorResolver } from "./types"; + +export function createFormGroupEditorResolver(): EditorResolver { + function resolve(viewSchema: Record) { + return viewSchema.editor; + } + return { + resolve + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/event-handler-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/event-handler-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d9954e304f7c38649d7cfdce289b6d051ef2187 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/event-handler-resolver.ts @@ -0,0 +1,90 @@ +import { EventHandlerResolver, ViewEvent } from "./types"; + +export function createEventHandlerResolver(): EventHandlerResolver { + function resolve(schema: Record, event: ViewEvent) { + const { name } = event; + return schema[`on${name.charAt(0).toUpperCase()}${name.slice(1)}`] || schema[name]; + }; + return { + resolve + }; +} + +export function createPageHeaderEventHandlerResolver(): EventHandlerResolver { + function resolve(schema: Record, event: ViewEvent) { + const buttons = schema.toolbar?.buttons as any[]; + if (!buttons || buttons.length < 1) { + return null; + } + const [payload, buttonId] = event.payloads; + const button = buttons.find((button: Record) => button.id === buttonId); + if (!button) { + return null; + } + return button.onClick || button.click; + }; + return { + resolve + }; +} + +export function createTabsEventHandlerResolver(): EventHandlerResolver { + function resolve(schema: Record, event: ViewEvent) { + const tabPages = schema.contents; + if (!tabPages || tabPages.length < 1) { + return null; + } + + const buttons = tabPages.reduce((tabsButtons: any[], tabPage: any) => { + const tabPageButtons = tabPage.toolbar && tabPage.toolbar.buttons || []; + tabsButtons.push(...tabPageButtons); + return tabsButtons; + }, []); + if (!buttons || buttons.length < 1) { + return; + } + const [payload, buttonId] = event.payloads; + const button = buttons.find((button: Record) => button.id === buttonId); + if (!button) { + return null; + } + return button.onClick || button.click; + }; + return { + resolve + }; +} +export function createResponseToolbarEventHandlerResolver(): EventHandlerResolver { + function resolve(schema: Record, event: ViewEvent) { + const buttons = schema.buttons as any[]; + if (!buttons || buttons.length < 1) { + return null; + } + const [payload, buttonId] = event.payloads; + const button = buttons.find((button: Record) => button.id === buttonId); + if (!button) { + return null; + } + return button.onClick || button.click; + }; + return { + resolve + }; +} +export function createSectionEventHandlerResolver(): EventHandlerResolver { + function resolve(schema: Record, event: ViewEvent) { + const buttons = schema.toolbar?.buttons as any[]; + if (!buttons || buttons.length < 1) { + return null; + } + const [payload, buttonId] = event.payloads; + const button = buttons.find((button: Record) => button.id === buttonId); + if (!button) { + return null; + } + return button.onClick || button.click; + }; + return { + resolve + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/events-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/events-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd66d0c5d42f000c43db1cd7c236f2d5e63ea3b2 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/events-resolver.ts @@ -0,0 +1,20 @@ + +import { EventDispatcher } from "./types"; + +export function createEventsResolver() { + return (component: any, schema: Record, dispatcher: EventDispatcher) => { + const token = schema.id; + const { type } = schema; + const events = component.emits as any[]; + if (!events) { + return {}; + } + return events.filter((eventName: string) => eventName !== 'update:modelValue').reduce((mergedEvents: Record, eventName: string) => { + const eventNameProp = `on${eventName.charAt(0).toUpperCase()}${eventName.slice(1)}`; + mergedEvents[eventNameProp] = (...payloads) => { + dispatcher.dispatch(token, eventName, type, payloads); + }; + return mergedEvents; + }, {}); + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/object-expression.ts b/packages/charts-vue/components/dynamic-resolver/src/object-expression.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4104b316de78d1013c9a2be6e700ca7dc07ed58 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/object-expression.ts @@ -0,0 +1,169 @@ +export type ObjectExpressionFunction = (target: string, param: any, value: any, context: Record) => boolean; + +interface Expression { + target: string; + operator: string; + param: any; + value: any; +} + +/** + * 解析对象表达式描述的布尔值 + * @param valueSchema 布尔值对象描述 + * @param context 解析表达式对象值所需的上下文对象 + * @returns boolean + * @example + * // 简化形式表示数组长度为0 + * {"sibling":0} + * @example + * // 数组长度不等于1 + * { + * "children":{ + * "length":{ + * "not":1 + * } + * } + * } + * @example + * // 存在某元素 + * { + * "children":{ + * "f-page-content":false + * } + * } + * { + * "parent":{ + * "f-page-main":true + * } + * } + * @example + * // 与条件 + * { + * "allOf":[ + * { + * "sibling":0, + * "parent":{ + * "f-page-main":true + * } + * } + * ] + * } + * // 或条件 + * { + * "anyOf":[ + * { + * "children":{ + * "length":{ + * "equal":1 + * }, + * "f-struct-like-card":true + * } + * }, + * { + * "children":{ + * "scroll-spy":false, + * "f-page-content":false, + * "f-struct-like-card":false + * } + * } + * ] + * } + */ +export function useObjectExpression(extendFunctions: Record = {}) { + + function judgingElementCount(target: string, param: any, value: any, context: Record) { + if (typeof value === 'number') { + return context[target].length === value; + } + if (typeof value === 'object') { + const compare = Object.keys(value)[0]; + const targetValue = value[compare]; + if (compare === 'not') { + return Number(context[target].length) !== Number(targetValue); + } + if (compare === 'moreThan') { + return Number(context[target].length) >= Number(targetValue); + } + if (compare === 'lessThan') { + return Number(context[target].length) <= Number(targetValue); + } + } + return false; + } + + function getProperty(target: string, param: any, value: any, context: Record) { + return context[target] && context[target].propertyValue && String(context[target].propertyValue.value) === String(value); + } + + const expressionCalculateFunctions = new Map([ + ['length', judgingElementCount], + ['getProperty', getProperty] + ]); + + Object.keys(extendFunctions).reduce((functionMaps: Map, operator: string) => { + functionMaps.set(operator, extendFunctions[operator]); + return functionMaps; + }, expressionCalculateFunctions); + + function generateExpressions(token: string, valueObject: any): Expression[] { + const target = token; + if (typeof valueObject === 'number') { + return [{ target, operator: 'length', param: null, value: Number(valueObject) }]; + } + if (typeof valueObject === 'boolean') { + return [{ target, operator: 'getProperty', param: token, value: Boolean(valueObject) }]; + } + if (typeof valueObject === 'object') { + return Object.keys(valueObject).map((key: string) => { + if (key === 'length') { + return { target, operator: 'length', param: null, value: valueObject[key] }; + } + const param = key; + const value = valueObject[key]; + const operator = 'getProperty'; + return { target, operator, param, value }; + }); + } + return []; + } + + function parseValueObjectToExpression(valueObject: Record): Expression[] { + const parsedExpression = Object.keys(valueObject).reduce((result: Expression[], token: string) => { + const expressions = generateExpressions(token, valueObject[token]); + result.push(...expressions); + return result; + }, []); + return parsedExpression; + } + + function calculateExpression(expression: Expression, context: Record) { + if (expressionCalculateFunctions.has(expression.operator)) { + const calculateFunction = expressionCalculateFunctions.get(expression.operator); + return calculateFunction && calculateFunction(expression.target, expression.param, expression.value, context) || false; + } + return false; + } + + function calculate(valueObject: Record, context: Record): boolean { + const parsedExpression = parseValueObjectToExpression(valueObject); + const result = parsedExpression.reduce((parsingResult: boolean, expression: Expression) => { + return parsingResult && calculateExpression(expression, context); + }, true); + + return result; + } + + function parseValueSchema(valueSchema: Record, context: Record): boolean { + const schemaKeys = Object.keys(valueSchema); + const allOf = schemaKeys.includes('allOf'); + const anyOf = schemaKeys.includes('anyOf'); + const hasLogicalOperatorsInSchemaKey = allOf || anyOf; + const logicalOperator = hasLogicalOperatorsInSchemaKey ? (allOf ? 'allOf' : 'anyOf') : 'allOf'; + const valueObjects = (hasLogicalOperatorsInSchemaKey ? valueSchema[logicalOperator] : [valueSchema]) as Record[]; + const expressionValues = valueObjects.map((valueObject: Record) => calculate(valueObject, context)); + const result = allOf ? !expressionValues.includes(false) : expressionValues.includes(true); + return result; + } + + return { parseValueSchema }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/property-config-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/property-config-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..02372dee1f7f128d53b6be5309e9043ddae5ac44 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/property-config-resolver.ts @@ -0,0 +1,247 @@ +import { computed, ref } from "vue"; +import { EffectFunction, PropertyConverter, SchemaService } from './types'; +import { resolveSchemaWithDefaultValue } from "@farris/charts-vue/components/dynamic-resolver/"; +import { useObjectExpression } from './object-expression'; +import { ComponentSchema, EditorConfig } from "@farris/charts-vue/components/charts-common/src/types"; +import appearanceConverter from './converter/appearance.converter'; +import buttonsConverter from "./converter/buttons.converter"; +import propertyEditorConverter from "./converter/property-editor.converter"; +// import typeConverter from "./converter/type.converter"; +import changeEditorConverter from "./converter/change-editor.converter"; +import fieldSelectorConverter from "./converter/field-selector.converter"; +import paginationConverter from "./converter/pagination.converter"; +import rowNumberConverter from "./converter/row-number.converter"; +import gridSelectionConverter from "./converter/grid-selection.converter"; +import itemsCountConverter from "./converter/items-count.converter"; +import enumDataConverter from "./converter/enum-data.converter"; +import formGroupLabelConverter from "./converter/form-group-label.converter"; +import { ElementPropertyConfig, PropertyEntity } from "@farris/charts-vue/components/charts-common/src/types"; + +const propertyConfigSchemaMap = {} as Record; +const propertyConverterMap = new Map([ + ['/converter/appearance.converter', appearanceConverter], + ['/converter/buttons.converter', buttonsConverter], + ['/converter/property-editor.converter', propertyEditorConverter], + ['/converter/items-count.converter', itemsCountConverter], + // ['/converter/type.converter', typeConverter], + ['/converter/change-editor.converter', changeEditorConverter], + ['/converter/form-group-label.converter', formGroupLabelConverter], + ['/converter/field-selector.converter', fieldSelectorConverter], + ['/converter/pagination.converter', paginationConverter], + ['/converter/row-number.converter', rowNumberConverter], + ['/converter/grid-selection.converter', gridSelectionConverter], + ['/converter/enum-data.converter',enumDataConverter] +]); +const propertyEffectMap = {} as Record; +const propertyEditorMap = new Map([ + ['string', { type: 'input-group', enableClear: false }], + ['boolean', { + "type": "combo-list", + "textField": "name", + "valueField": "value", + "idField": "value", + "enableClear": false, + "editable": false, + "maxHeight": 64, + "data": [ + { + "value": true, + "name": "是" + }, + { + "value": false, + "name": "否" + } + ] + }], + ['enum', { "type": "combo-list", "maxHeight": 128, "enableClear": false, "editable": false }], + ['array', { "type": "button-edit" }], + ['number', { "type": "number-spinner","placeholder":"" }], + ['events-editor', { "type": "events-editor", hide: true }] +]); + +const useObjectExpressionComponstion = useObjectExpression(); + +function generateBooleanValue(pvalueSchema: Record, propertyConfigMap: Record) { + return () => useObjectExpressionComponstion.parseValueSchema(pvalueSchema, propertyConfigMap); +} + +function isVisible(propertySchemaKeys: string[], propertySchema: Record, propertyConfigMap: Record) + : boolean | (() => boolean) { + if (propertySchemaKeys.includes('visible') && propertySchema.visible !== undefined) { + return typeof propertySchema.visible === 'boolean' ? + () => Boolean(propertySchema.visible) : + propertySchema.visible === undefined ? true : generateBooleanValue(propertySchema.visible, propertyConfigMap); + } + return () => true; +} + +function isReadonly(propertySchemaKeys: string[], propertySchema: Record, propertyConfigMap: Record) { + if (propertySchemaKeys.includes('readonly') && propertySchema.readonly !== undefined) { + return typeof propertySchema.readonly === 'boolean' ? + () => Boolean(propertySchema.readonly) : + generateBooleanValue(propertySchema.readonly, propertyConfigMap); + } + return () => false; +} + +function tryGetPropertyConverter(propertySchema: Record, categoryConverter): PropertyConverter | null { + const $converter = propertySchema['$converter'] || categoryConverter; + if (typeof $converter === 'string') { + if ($converter && propertyConverterMap.has($converter)) { + return propertyConverterMap.get($converter) || null; + } + } + return $converter || null; +} + +/** + * + * @param propertiesInCategory + * 举例: + * visible: { + description: "运行时组件是否可见", + title: "是否可见", + type: "boolean" + } + 其中type属性 这个属性用来控制编辑器是哪一种,对应关系在propertyEditorMap中定义,boolean指定了下拉 + * @param propertyConfigMap + * @param editingSchema + * @param rawSchema + * @param schemaService + * @returns + */ +function getPropertyEntities( + propertiesInCategory: Record, + propertyConfigMap: Record, + editingSchema: ComponentSchema, + rawSchema: ComponentSchema, + schemaService: SchemaService, + componentId = '', + categoryConverter: any = '' +): PropertyEntity[] { + const propertyEntities = Object.keys(propertiesInCategory).map((propertyKey: string) => { + const updateCount = ref(1); + const propertyID = propertyKey; + const propertySchema = propertiesInCategory[propertyKey]; + const propertySchemaKeys = Object.keys(propertySchema); + const propertyName = propertySchema.title; + const propertyType = propertySchema.type; + const defaultEditor = propertyEditorMap.get(propertyType) || { type: 'input-group', enableClear: false }; + const editor = propertySchema.editor ? Object.assign({}, defaultEditor, propertySchema.editor) as EditorConfig : Object.assign({}, defaultEditor); + const visible = isVisible(propertySchemaKeys, propertySchema, propertyConfigMap); + const readonly = isReadonly(propertySchemaKeys, propertySchema, propertyConfigMap); + editor.readonly = editor.readonly===undefined?readonly():editor.readonly; + const cascadeConfig = propertySchema.type === 'cascade' ? getPropertyEntities(propertySchema.properties, propertyConfigMap, editingSchema, rawSchema, schemaService, componentId, categoryConverter) : []; + const hideCascadeTitle = true; + let converter = tryGetPropertyConverter(propertySchema, categoryConverter); + // const propertyValue = ref(converter ? converter.convertFrom(schema, propertyKey) : schema[propertyKey]); + const propertyValue = computed({ + get() { + if (updateCount.value) { + // class、style 统一处理 + if (['class', 'style'].find(id => id === propertyID) && !converter) { + converter = propertyConverterMap.get('/converter/appearance.converter') || null; + } + if (converter && converter.convertFrom) { + return converter.convertFrom(editingSchema, propertyKey, schemaService, componentId); + } + // 获取属性时,如果没有convertForm,并且通过在Schema上获取得值是空,那就获取defaultValue属性值或者是空 + const editingSchemaValue = editingSchema[propertyKey]; + return typeof editingSchemaValue == 'string' && editingSchemaValue === '' ? propertySchema['defaultValue'] || '' : editingSchemaValue; + } + return null; + }, + set(newValue) { + updateCount.value += 1; + if (converter && converter.convertTo) { + converter.convertTo(rawSchema, propertyKey, newValue, schemaService, componentId); + converter.convertTo(editingSchema, propertyKey, newValue, schemaService, componentId); + } else { + rawSchema[propertyKey] = newValue; + editingSchema[propertyKey] = newValue; + } + } + }); + const { refreshPanelAfterChanged } = propertySchema; + const propertyEntity = { propertyID, propertyName, propertyType, propertyValue, editor, visible, readonly, cascadeConfig, hideCascadeTitle, refreshPanelAfterChanged }; + propertyConfigMap[propertyID] = propertyEntity; + return propertyEntity; + }); + return propertyEntities; +} + +function getPropertyConfigByType(schemaType: string, schemaService: SchemaService, schema = {} as ComponentSchema): ElementPropertyConfig[] { + const propertyConfigMap = {} as Record; + const propertyConfigSchema = propertyConfigSchemaMap[schemaType]; + if (propertyConfigSchema && propertyConfigSchema.categories) { + const propertyConfigs = Object.keys(propertyConfigSchema.categories).map((categoryId: string) => { + const propertyCategory = propertyConfigSchema.categories[categoryId]; + const categoryName = propertyCategory?.title; + const properties = getPropertyEntities(propertyCategory.properties || {}, propertyConfigMap, {} as ComponentSchema, schema, schemaService); + return { categoryId, categoryName, properties }; + }); + return propertyConfigs; + } + return []; +} + +function tryToResolveReference(categoryId: string, propertyCategory: Record, rawSchema: ComponentSchema, schemaService: SchemaService, componentId = '') { + const refSchemaPath = propertyCategory.$ref.schema; + const $converter = propertyCategory.$ref.converter; + const refSchema = rawSchema[refSchemaPath]; + + const schemaType = refSchema.type; + const editingSchema = resolveSchemaWithDefaultValue(refSchema) as ComponentSchema; + const propertyConfigMap = {} as Record; + const propertyConfigSchema = propertyConfigSchemaMap[schemaType]; + if (propertyConfigSchema && propertyConfigSchema.categories) { + const propertyCategory = propertyConfigSchema.categories[categoryId]; + const categoryName = propertyCategory?.title; + if ($converter) { + Object.keys(propertyCategory.properties).forEach((propertyKey: any) => { + propertyCategory.properties[propertyKey].$converter = $converter; + }); + } + const propertiesInCategory = propertyCategory?.properties || {}; + const properties = getPropertyEntities(propertiesInCategory, propertyConfigMap, editingSchema, refSchema, schemaService, componentId); + return { categoryId, categoryName, properties }; + + } + return { categoryId, categoryName: '', properties: [] }; +} + +// jumphere +function getPropertyConfigBySchema(rawSchema: ComponentSchema, schemaService: SchemaService, designerItem: any, componentId: string, propertyConfig?: Record): ElementPropertyConfig[] { + const schemaType = rawSchema.type; + const editingSchema = resolveSchemaWithDefaultValue(rawSchema) as ComponentSchema; + const propertyConfigMap = {} as Record; + + // 先从ConfigMap中取简单类属性,若找不到,则在控件实例中取复杂属性 + let propertyConfigSchema = propertyConfig || propertyConfigSchemaMap[schemaType]; + if (propertyConfigSchema && Object.keys(propertyConfigSchema).length === 0 && designerItem && designerItem.getPropConfig) { + propertyConfigSchema = designerItem.getPropConfig(componentId); + } + if (propertyConfigSchema && propertyConfigSchema.categories) { + const propertyConfigs = [] as Array; + Object.keys(propertyConfigSchema.categories).map((categoryId: string) => { + const propertyCategory = propertyConfigSchema.categories[categoryId]; + if (propertyCategory.$ref) { + propertyConfigs.push(tryToResolveReference(categoryId, propertyCategory, rawSchema, schemaService, componentId) as ElementPropertyConfig); + return; + } + const categoryName = propertyCategory?.title; + const tabId = propertyCategory?.tabId; + const tabName = propertyCategory?.tabName; + const hide = propertyCategory?.hide; + const hideTitle = propertyCategory?.hideTitle; + const properties = getPropertyEntities(propertyCategory.properties || {}, propertyConfigMap, editingSchema, rawSchema, schemaService, componentId, propertyCategory['$converter']); + const { setPropertyRelates } = propertyCategory; + propertyConfigs.push({ categoryId, categoryName, tabId, tabName, hide, properties, hideTitle, setPropertyRelates }); + }); + return propertyConfigs; + } + return []; +} + +export { getPropertyConfigBySchema, getPropertyConfigByType, propertyConfigSchemaMap, propertyConverterMap, propertyEffectMap }; diff --git a/packages/charts-vue/components/dynamic-resolver/src/props-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/props-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa27e0c9254ef48f3f8d0bc0373d22f6b6759e87 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/props-resolver.ts @@ -0,0 +1,52 @@ +import { DesignerHostService } from "@farris/charts-vue/components/charts-common/src/types"; +import { isFunction } from "@farris/charts-vue/components/charts-common/src/utils/src/type"; + +import { resolveSchemaToProps } from './resolver/schema/schema-resolver'; +import { DynamicResolver, EffectFunction, MapperFunction, SchemaResolverFunction } from './types'; +import { RegisterContext } from "@farris/charts-vue/components/charts-common/src/types"; + +/** + * 注册schema和props + */ +function registerSchemaAndProps( + defaultSchema: Record, + schemaResolver: SchemaResolverFunction, + propertyConfig: Record, + propertyEffect: EffectFunction, + registerContext: any +) { + const { schemaMap, schemaResolverMap, propertyConfigSchemaMap, propertyEffectMap } = registerContext; + schemaMap[defaultSchema.title] = defaultSchema; + schemaResolverMap[defaultSchema.title] = schemaResolver; + propertyConfigSchemaMap[defaultSchema.title] = propertyConfig; + propertyEffectMap[defaultSchema.title] = propertyEffect; +} + +export function getPropsResolverGenerator>( + componentPropsObject: T, + defaultSchema: Record, + schemaMapper: Map = new Map(), + schemaResolver: SchemaResolverFunction = ( + dynamicResolver: DynamicResolver, + schema: Record, + resolveContext: Record, + designerHostService?: DesignerHostService + ) => schema, + propertyConfig: Record = {}, + propertyEffect: EffectFunction = (properties: Record) => properties +) { + + return (registerContext: RegisterContext) => { + registerSchemaAndProps(defaultSchema, schemaResolver, propertyConfig, propertyEffect, registerContext); + + return (schemaValue: Record = {}) => { + const resolvedPropsValue = resolveSchemaToProps(schemaValue, defaultSchema, schemaMapper); + const defaultProps = Object.keys(componentPropsObject).reduce((propsObject: Record, propKey: string) => { + const defaultValue = componentPropsObject[propKey].default; + propsObject[propKey] = isFunction(defaultValue) ? defaultValue() : defaultValue; + return propsObject; + }, {}); + return Object.assign(defaultProps, resolvedPropsValue); + }; + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/property-config-resolver-design.ts b/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/property-config-resolver-design.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4fba29a4a83b4e77fca6b4c9903f0b8e05d06ad --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/property-config-resolver-design.ts @@ -0,0 +1,13 @@ +import { EffectFunction } from '../../types'; +import { usePropertyConfigResolver } from './use-property-config-resolver'; + +const propertyConfigSchemaMapForDesigner = {} as Record; +const propertyEffectMapForDesigner = {} as Record; + +const { getPropertyConfigBySchema, getPropertyConfigByType, propertyConverterMap } = + usePropertyConfigResolver(propertyConfigSchemaMapForDesigner, propertyEffectMapForDesigner); + +export { + getPropertyConfigBySchema as getPropertyConfigBySchemaForDesigner, getPropertyConfigByType as getPropertyConfigByTypeForDesigner, + propertyConfigSchemaMapForDesigner, propertyConverterMap as propertyConverterMapForDesigner, propertyEffectMapForDesigner +}; diff --git a/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/property-config-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/property-config-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..199cbf45c5e922f3b483cc0b9f5d50b30b45c1f8 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/property-config-resolver.ts @@ -0,0 +1,10 @@ +import { EffectFunction} from '../../types'; +import { usePropertyConfigResolver } from './use-property-config-resolver'; + +const propertyConfigSchemaMap = {} as Record; +const propertyEffectMap = {} as Record; + +const { getPropertyConfigBySchema, getPropertyConfigByType, propertyConverterMap } = + usePropertyConfigResolver(propertyConfigSchemaMap, propertyEffectMap); + +export { getPropertyConfigBySchema, getPropertyConfigByType, propertyConfigSchemaMap, propertyConverterMap, propertyEffectMap }; diff --git a/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..bfb27d31cf0440d702a99e78c494dfeec1c5748e --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts @@ -0,0 +1,247 @@ +import { computed, ref } from "vue"; +import { EffectFunction, PropertyConverter, SchemaService } from '../../types'; +import { useObjectExpression } from '../../object-expression'; +import { ComponentSchema,EditorConfig } from "@farris/charts-vue/components/charts-common/src/types"; +import { resolveSchemaWithDefaultValue } from "../schema/schema-resolver"; +import appearanceConverter from '../../converter/appearance.converter'; +import buttonsConverter from "../../converter/buttons.converter"; +import propertyEditorConverter from "../../converter/property-editor.converter"; +import changeEditorConverter from "../../converter/change-editor.converter"; +import fieldSelectorConverter from "../../converter/field-selector.converter"; +import paginationConverter from "../../converter/pagination.converter"; +import rowNumberConverter from "../../converter/row-number.converter"; +import gridSelectionConverter from "../../converter/grid-selection.converter"; +import { ElementPropertyConfig, PropertyEntity } from "@farris/charts-vue/components/charts-common/src/types"; +import itemsCountConverter from "../../converter/items-count.converter"; +import formGroupLabelConverter from "../../converter/form-group-label.converter"; + +export function usePropertyConfigResolver(propertyConfigSchemaMap: Record, propertyEffectMap: Record) { + const propertyConverterMap = new Map([ + ['/converter/appearance.converter', appearanceConverter], + ['/converter/buttons.converter', buttonsConverter], + ['/converter/property-editor.converter', propertyEditorConverter], + ['/converter/items-count.converter', itemsCountConverter], + // ['/converter/type.converter', typeConverter], + ['/converter/change-editor.converter', changeEditorConverter], + ['/converter/form-group-label.converter', formGroupLabelConverter], + ['/converter/field-selector.converter', fieldSelectorConverter], + ['/converter/pagination.converter', paginationConverter], + ['/converter/row-number.converter', rowNumberConverter], + ['/converter/grid-selection.converter', gridSelectionConverter] + ]); + const propertyEditorMap = new Map([ + ['string', { type: 'input-group', enableClear: false }], + ['boolean', { + "type": "combo-list", + "textField": "name", + "valueField": "value", + "idField": "value", + "enableClear": false, + "editable": false, + "maxHeight": 64, + "data": [ + { + "value": true, + "name": "是" + }, + { + "value": false, + "name": "否" + } + ] + }], + ['enum', { "type": "combo-list", "maxHeight": 350, "enableClear": false, "editable": false }], + ['array', { "type": "button-edit" }], + ['number', { "type": "number-spinner", "placeholder": "" }], + ['events-editor', { "type": "events-editor", hide: true }] + ]); + + const useObjectExpressionComponstion = useObjectExpression(); + + function generateBooleanValue(pvalueSchema: Record, propertyConfigMap: Record) { + return () => useObjectExpressionComponstion.parseValueSchema(pvalueSchema, propertyConfigMap); + } + + function isVisible(propertySchemaKeys: string[], propertySchema: Record, propertyConfigMap: Record) + : boolean | (() => boolean) { + if (propertySchemaKeys.includes('visible') && propertySchema.visible !== undefined) { + return typeof propertySchema.visible === 'boolean' ? + () => Boolean(propertySchema.visible) : + propertySchema.visible === undefined ? true : generateBooleanValue(propertySchema.visible, propertyConfigMap); + } + return () => true; + } + + function isReadonly(propertySchemaKeys: string[], propertySchema: Record, propertyConfigMap: Record) { + if (propertySchemaKeys.includes('readonly') && propertySchema.readonly !== undefined) { + return typeof propertySchema.readonly === 'boolean' ? + () => Boolean(propertySchema.readonly) : + generateBooleanValue(propertySchema.readonly, propertyConfigMap); + } + return () => false; + } + + function tryGetPropertyConverter(propertySchema: Record, categoryConverter): PropertyConverter | null { + const $converter = propertySchema['$converter'] || categoryConverter; + if (typeof $converter === 'string') { + if ($converter && propertyConverterMap.has($converter)) { + return propertyConverterMap.get($converter) || null; + } + } + return $converter || null; + } + + /** + * + * @param propertiesInCategory + * 举例: + * visible: { + description: "运行时组件是否可见", + title: "是否可见", + type: "boolean" + } + 其中type属性 这个属性用来控制编辑器是哪一种,对应关系在propertyEditorMap中定义,boolean指定了下拉 + * @param propertyConfigMap + * @param editingSchema + * @param rawSchema + * @param schemaService + * @returns + */ + function getPropertyEntities( + propertiesInCategory: Record, + propertyConfigMap: Record, + editingSchema: ComponentSchema, + rawSchema: ComponentSchema, + schemaService: SchemaService, + componentId = '', + categoryConverter: any = '' + ): PropertyEntity[] { + const propertyEntities = Object.keys(propertiesInCategory).map((propertyKey: string) => { + const updateCount = ref(1); + const propertyID = propertyKey; + const propertySchema = propertiesInCategory[propertyKey]; + const propertySchemaKeys = Object.keys(propertySchema); + const propertyName = propertySchema.title; + const propertyType = propertySchema.type; + const defaultEditor = propertyEditorMap.get(propertyType) || { type: 'input-group', enableClear: false }; + const editor = propertySchema.editor ? Object.assign({}, defaultEditor, propertySchema.editor) as EditorConfig : Object.assign({}, defaultEditor); + const visible = isVisible(propertySchemaKeys, propertySchema, propertyConfigMap); + const readonly = isReadonly(propertySchemaKeys, propertySchema, propertyConfigMap); + editor.readonly = editor.readonly === undefined ? readonly() : editor.readonly; + const cascadeConfig = propertySchema.type === 'cascade' ? getPropertyEntities(propertySchema.properties, propertyConfigMap, editingSchema, rawSchema, schemaService, componentId, categoryConverter) : []; + const hideCascadeTitle = true; + let converter = tryGetPropertyConverter(propertySchema, categoryConverter); + // const propertyValue = ref(converter ? converter.convertFrom(schema, propertyKey) : schema[propertyKey]); + const propertyValue = computed({ + get() { + if (updateCount.value) { + // class、style 统一处理 + if (['class', 'style'].find(id => id === propertyID) && !converter) { + converter = propertyConverterMap.get('/converter/appearance.converter') || null; + } + if (converter && converter.convertFrom) { + return converter.convertFrom(editingSchema, propertyKey, schemaService, componentId); + } + // 获取属性时,如果没有convertForm,并且通过在Schema上获取得值是空,那就获取defaultValue属性值或者是空 + const editingSchemaValue = editingSchema[propertyKey]; + return Object.prototype.hasOwnProperty.call(propertySchema, 'defaultValue') && (editingSchemaValue === undefined || typeof editingSchemaValue == 'string' && editingSchemaValue === '') ? propertySchema['defaultValue'] || '' : editingSchemaValue; + } + return null; + }, + set(newValue) { + updateCount.value += 1; + if (converter && converter.convertTo) { + converter.convertTo(rawSchema, propertyKey, newValue, schemaService, componentId); + converter.convertTo(editingSchema, propertyKey, newValue, schemaService, componentId); + } else { + rawSchema[propertyKey] = newValue; + editingSchema[propertyKey] = newValue; + } + } + }); + const { refreshPanelAfterChanged, description, isExpand } = propertySchema; + const propertyEntity = { propertyID, propertyName, propertyType, propertyValue, editor, visible, readonly, cascadeConfig, hideCascadeTitle, refreshPanelAfterChanged, description, isExpand }; + propertyConfigMap[propertyID] = propertyEntity; + return propertyEntity; + }); + return propertyEntities; + } + + function getPropertyConfigByType(schemaType: string, schemaService: SchemaService, schema = {} as ComponentSchema): ElementPropertyConfig[] { + const propertyConfigMap = {} as Record; + const propertyConfigSchema = propertyConfigSchemaMap[schemaType]; + if (propertyConfigSchema && propertyConfigSchema.categories) { + const propertyConfigs = Object.keys(propertyConfigSchema.categories).map((categoryId: string) => { + const propertyCategory = propertyConfigSchema.categories[categoryId]; + const categoryName = propertyCategory?.title; + const properties = getPropertyEntities(propertyCategory.properties || {}, propertyConfigMap, {} as ComponentSchema, schema, schemaService); + return { categoryId, categoryName, properties }; + }); + return propertyConfigs; + } + return []; + } + + function tryToResolveReference(categoryId: string, propertyCategory: Record, rawSchema: ComponentSchema, schemaService: SchemaService, componentId = '') { + const refSchemaPath = propertyCategory.$ref.schema; + const $converter = propertyCategory.$ref.converter; + const refSchema = rawSchema[refSchemaPath]; + + const schemaType = refSchema.type; + const editingSchema = resolveSchemaWithDefaultValue(refSchema) as ComponentSchema; + const propertyConfigMap = {} as Record; + const propertyConfigSchema = propertyConfigSchemaMap[schemaType]; + if (propertyConfigSchema && propertyConfigSchema.categories) { + const propertyCategory = propertyConfigSchema.categories[categoryId]; + const categoryName = propertyCategory?.title; + if ($converter) { + Object.keys(propertyCategory.properties).forEach((propertyKey: any) => { + propertyCategory.properties[propertyKey].$converter = $converter; + }); + } + const propertiesInCategory = propertyCategory?.properties || {}; + const properties = getPropertyEntities(propertiesInCategory, propertyConfigMap, editingSchema, refSchema, schemaService, componentId); + return { categoryId, categoryName, properties }; + + } + return { categoryId, categoryName: '', properties: [] }; + } + + // jumphere + function getPropertyConfigBySchema(rawSchema: ComponentSchema, schemaService: SchemaService, designerItem: any, componentId: string, propertyConfig?: Record): ElementPropertyConfig[] { + const schemaType = rawSchema.type; + const editingSchema = resolveSchemaWithDefaultValue(rawSchema) as ComponentSchema; + const propertyConfigMap = {} as Record; + + // 先从ConfigMap中取简单类属性,若找不到,则在控件实例中取复杂属性 + let propertyConfigSchema = propertyConfig || propertyConfigSchemaMap[schemaType]; + if (propertyConfigSchema && Object.keys(propertyConfigSchema).length === 0 && designerItem && designerItem.getPropConfig) { + propertyConfigSchema = designerItem.getPropConfig(componentId); + } + if (propertyConfigSchema && propertyConfigSchema.categories) { + const propertyConfigs = [] as Array; + Object.keys(propertyConfigSchema.categories).map((categoryId: string) => { + const propertyCategory = propertyConfigSchema.categories[categoryId]; + if (propertyCategory.$ref) { + propertyConfigs.push(tryToResolveReference(categoryId, propertyCategory, rawSchema, schemaService, componentId) as ElementPropertyConfig); + return; + } + const categoryName = propertyCategory?.title; + const tabId = propertyCategory?.tabId; + const tabName = propertyCategory?.tabName; + const hide = propertyCategory?.hide; + const hideTitle = propertyCategory?.hideTitle; + const properties = getPropertyEntities(propertyCategory.properties || {}, propertyConfigMap, editingSchema, rawSchema, schemaService, componentId, propertyCategory['$converter']); + const { setPropertyRelates } = propertyCategory; + propertyConfigs.push({ categoryId, categoryName, tabId, tabName, hide, properties, hideTitle, setPropertyRelates }); + }); + return propertyConfigs; + } + return []; + } + + return { + getPropertyConfigBySchema, getPropertyConfigByType, propertyConverterMap + }; +} + diff --git a/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/schema-resolver-design.ts b/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/schema-resolver-design.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5fe3e9316233858d0ab20262ba6ada7421b656d --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/schema-resolver-design.ts @@ -0,0 +1,14 @@ +import { SchemaResolverFunction } from "../../types"; +import { useSchemaResolver } from "./use-schema-resolver"; + +const schemaMapForDesigner = {} as Record; +const schemaResolverMapForDesigner = {} as Record; + +const { getSchemaByType, resolveSchemaWithDefaultValue, resolveSchemaToProps, mappingSchemaToProps } = + useSchemaResolver(schemaMapForDesigner, schemaResolverMapForDesigner); + +export { + getSchemaByType as getSchemaByTypeForDesigner, resolveSchemaWithDefaultValue as resolveSchemaWithDefaultValueForDesigner, + resolveSchemaToProps as resolveSchemaToPropsForDesigner, schemaMapForDesigner, schemaResolverMapForDesigner, + mappingSchemaToProps as mappingSchemaToPropsForDesigner +}; diff --git a/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/schema-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/schema-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..083e2015cd35a9579a6cccce71c5cf2120ae0367 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/schema-resolver.ts @@ -0,0 +1,10 @@ +import { SchemaResolverFunction } from "../../types"; +import { useSchemaResolver } from "./use-schema-resolver"; + +const schemaMap = {} as Record; +const schemaResolverMap = {} as Record; + +const { getSchemaByType, resolveSchemaWithDefaultValue, resolveSchemaToProps, mappingSchemaToProps } = + useSchemaResolver(schemaMap, schemaResolverMap); + +export { getSchemaByType, resolveSchemaWithDefaultValue, resolveSchemaToProps, schemaMap, schemaResolverMap, mappingSchemaToProps }; diff --git a/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/use-schema-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/use-schema-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4bed12e043e2603f663b5e63c492483752e9c29 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/resolver/schema/use-schema-resolver.ts @@ -0,0 +1,138 @@ +import { cloneDeep, isPlainObject } from "lodash-es"; +import { MapperFunction, SchemaResolverFunction } from "../../types"; +import { DesignerHostService } from "@farris/charts-vue/components/charts-common/src/types"; + + +export function useSchemaResolver(schemaMap: Record, schemaResolverMap: Record) { + + function getSchemaValueByDefault(defaultSchema: Record): Record { + const { properties, title, ignore: ignoreList } = defaultSchema as Record; + const canIgnoreProperty = ignoreList && Array.isArray(ignoreList); + const resolvedSchema = Object.keys(properties).reduce((propsObject: Record, propKey: string) => { + if (!canIgnoreProperty || !ignoreList.find(item => item === propKey)) { + propsObject[propKey] = (properties[propKey].type === 'object' && !!properties[propKey].properties) ? + getSchemaValueByDefault(properties[propKey]) : cloneDeep(properties[propKey].default); + } + return propsObject; + }, {}); + if (title && (!canIgnoreProperty || !ignoreList.find(item => item === 'id'))) { + const typePrefix = title.toLowerCase().replace(/-/g, '_'); + resolvedSchema.id = `${typePrefix}_${Math.random().toString().slice(2, 6)}`; + } + return resolvedSchema; + } + + /** + * 获取控件元数据,只组装必填的字段 + */ + function getRequiredSchemaValueByDefault(defaultSchema: Record): Record { + const { properties, title, required: requiredProperty } = defaultSchema as Record; + if (requiredProperty && Array.isArray(requiredProperty)) { + const resolvedSchema = requiredProperty.reduce((propsObject: Record, propKey: string) => { + + propsObject[propKey] = (properties[propKey].type === 'object' && !!properties[propKey].properties) ? + getSchemaValueByDefault(properties[propKey]) : cloneDeep(properties[propKey].default); + + return propsObject; + }, {}); + if (title && requiredProperty.find(item => item === 'id')) { + const typePrefix = title.toLowerCase().replace(/-/g, '_'); + resolvedSchema.id = `${typePrefix}_${Math.random().toString().slice(2, 6)}`; + } + return resolvedSchema; + } + return { + type: title + }; + + } + + function getSchemaByType(componentType: string, resolveContext: Record = {}, designerHostService?: DesignerHostService) + : Record | null { + const defaultSchema = schemaMap[componentType]; + if (defaultSchema) { + let componentSchema = getRequiredSchemaValueByDefault(defaultSchema); + const schemaResolver = schemaResolverMap[componentType]; + componentSchema = schemaResolver ? schemaResolver({ getSchemaByType }, componentSchema, resolveContext, designerHostService) + : componentSchema; + return componentSchema; + } + return null; + } + + function resolveSchema(schemaValue: Record, defaultSchema: Record): Record { + + const resolvedSchema = getSchemaValueByDefault(defaultSchema); + + Object.keys(resolvedSchema).reduce((resolvedSchema: Record, propKey: string) => { + if (Object.prototype.hasOwnProperty.call(schemaValue, propKey)) { + // 解决属性是对象类型,默认值被冲掉的情况 + // 增加非判断,解决针对属性时对象,但是schemaValue=null或者undefined情况 + if (resolvedSchema[propKey] && isPlainObject(resolvedSchema[propKey]) && (isPlainObject(schemaValue[propKey] || !schemaValue[propKey]))) { + Object.assign(resolvedSchema[propKey], schemaValue[propKey] || {}); + } else { + resolvedSchema[propKey] = schemaValue[propKey]; + } + } + + return resolvedSchema; + }, resolvedSchema); + + return resolvedSchema; + }; + + function mappingSchemaToProps(resolvedSchema: Record, schemaMapper: Map) { + const props = Object.keys(resolvedSchema) + .filter((propKey: string) => resolvedSchema[propKey] != null) + .reduce((resolvedProps: Record, propKey: string) => { + if (schemaMapper.has(propKey)) { + const mapper = schemaMapper.get(propKey) as string | MapperFunction; + if (typeof mapper === 'string') { + resolvedProps[mapper] = resolvedSchema[propKey]; + } else { + const mapperResult = (mapper as MapperFunction)(propKey, resolvedSchema[propKey], resolvedSchema); + Object.assign(resolvedProps, mapperResult); + Object.assign(resolvedSchema, mapperResult); + } + } else { + resolvedProps[propKey] = resolvedSchema[propKey]; + } + return resolvedProps; + }, {}); + return props; + } + + function resolveSchemaToProps( + schemaValue: Record, + defaultSchema: Record, + schemaMapper: Map = new Map() + ): Record { + const resolvedSchema = resolveSchema(schemaValue, defaultSchema); + const props = mappingSchemaToProps(resolvedSchema, schemaMapper); + return props; + } + + function resolveSchemaWithDefaultValue(schemaValue: Record): Record { + const componentType = schemaValue.type; + if (componentType) { + const defaultSchema = schemaMap[componentType]; + if (!defaultSchema) { + return schemaValue; + } + const resolvedSchema = resolveSchema(schemaValue, defaultSchema); + const editorType = schemaValue.editor?.type || ''; + /* 解决schemeValue结构如下图场景,在editor下,获取不到date-picker类型的默认值的问题 + * {type:'input-group',...,editor:{type:'date-picker',...}} + */ + if (editorType) { + const defaulEditorSchema = schemaMap[editorType]; + const resolvedEditorSchema = resolveSchema(schemaValue.editor, defaulEditorSchema); + resolvedSchema.editor = resolvedEditorSchema; + } + return resolvedSchema; + } + return schemaValue; + } + + return { getSchemaByType, resolveSchemaWithDefaultValue, resolveSchemaToProps, mappingSchemaToProps }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/selection-item-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/selection-item-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b946eae93f403d069a1e4b3a325c95b8fc80cb0 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/selection-item-resolver.ts @@ -0,0 +1,19 @@ +import { SelectionItemResolver } from "./types"; + +export function createDataGridSelectionItemResolver(): SelectionItemResolver { + function selectItemById(component: any, id: string) { + return component.selectItemById(id); + } + return { + selectItemById + }; +} + +export function createTreeGridSelectionItemResolver(): SelectionItemResolver { + function selectItemById(component: any, id: string) { + return component.selectItemById(id); + } + return { + selectItemById + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/types.ts b/packages/charts-vue/components/dynamic-resolver/src/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d34468fc87fbce631b90ea1768aeaacd44b9fb1 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/types.ts @@ -0,0 +1,83 @@ + +import { DesignerHostService } from "@farris/charts-vue/components/charts-common/src/types"; + +export type MapperFunction = (key: string, value: any, resolvedSchema?: any) => Record; + +export interface SchemaService { + + closest: (componentId: string, componentType: string) => Record | null; + + getSchemaById: (string) => Record; + + load: (componentSchema: Record) => void; + + select: (root: Record, predicate: (child: Record) => boolean) => Record; + + getRealEditorType: (editorType: string) => string; +} + +export interface PropertyConverter { + + convertTo: (schema: Record, propertyKey: string, propertyValue: any, schemaService: SchemaService, componentId?: string) => void; + + convertFrom: (schema: Record, propertyKey: string, schemaService: SchemaService, componentId?: string) => any; +} + +export interface DynamicResolver { + getSchemaByType: (componentType: string, resolveContext?: Record, designerHostService?: DesignerHostService) => Record | null; + getPropertyConfigByType?: (componentType: string) => Record | null; +}; + +export type SchemaResolverFunction = ( + dynamicResolver: DynamicResolver, + schema: Record, + resolveContext: Record, + designerHostService?: DesignerHostService +) => Record; + +export type EffectFunction = (source: Record) => Record; + +export interface EventDispatcher { + dispatch(token: string, eventName: string, ...payloads); +} + +export interface BindingModel { + getValue(field: string); + setValue(field: string, value: any); + getList(dataSource: string); + updateData(...payloads); + dataMapping(...payloads); +} + +export interface BindingData { + getValue(elementId: string); + setValue(elementId: string, field: string, value: any); +} +export interface BindingResolver { + resolve(schema: Record, bindingData: BindingData); +} + +export interface SelectionItemResolver { + selectItemById(component: any, id: string); +} + +export interface EditorResolver { + resolve(schema: Record); +} + +export interface UpdateColumnsResolver { + updateColumns(component: any ,schema: Record); +} + +export interface ViewEvent { + token: string; + type: string; + name: string; + payloads: any[]; +} +export interface EventHandlerResolver { + resolve(schema: Record, event: ViewEvent); +} +export interface Caller { + call: (methodName: string, ...payloads: any[]) => any; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/update-columns-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/update-columns-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6556e0fc80bcb97fa3ac22da125ff8ba2294f8a --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/update-columns-resolver.ts @@ -0,0 +1,11 @@ +import { UpdateColumnsResolver } from "./types"; + +export function createDataViewUpdateColumnsResolver(): UpdateColumnsResolver { + function updateColumns(component: any, schema: Record) { + const { columns } = schema; + return component.updateColumns(columns); + } + return { + updateColumns + }; +} diff --git a/packages/charts-vue/components/dynamic-resolver/src/visible-prop-resolver.ts b/packages/charts-vue/components/dynamic-resolver/src/visible-prop-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..94a6e09666e91d34882ce144b98f53f80fb06788 --- /dev/null +++ b/packages/charts-vue/components/dynamic-resolver/src/visible-prop-resolver.ts @@ -0,0 +1,8 @@ +export function createVisiblePropResolver() { + function resolve(schema: Record) { + return Object.prototype.hasOwnProperty.call(schema, 'visible') ? schema.visible : null; + } + return { + resolve + }; +} diff --git a/packages/designer/farris.config.mjs b/packages/designer/farris.config.mjs index 0a6a9141f9545249f03afa7efcaaf99f25505466..bc9e0ff32b681e28cf2d440771c634ab23909ce2 100644 --- a/packages/designer/farris.config.mjs +++ b/packages/designer/farris.config.mjs @@ -13,6 +13,8 @@ const replaceUIVueComponentsPath = function () { if (chunk.type === 'chunk') { chunk.code = chunk.code.replace(/@farris\/ui-vue\/components/g, '@farris/ui-vue'); chunk.code = chunk.code.replace(/@farris\/mobile-ui-vue\/components/g, '@farris/mobile-ui-vue'); + chunk.code = chunk.code.replace(/@farris\/mobile-ui-vue\/components/g, '@farris/charts-vue'); + } } } @@ -24,7 +26,7 @@ export default { // outDir: fileURLToPath(new URL('./dist', import.meta.url)), // 外部依赖排除项 默认值 { include: [], exclude: [] } externals: { - include: ['jsonp', 'echarts', 'lodash-es', 'lodash', 'axios', '@farris/ui-vue', '@farris/mobile-ui-vue', 'vue','rxjs','rxjs/operators'], + include: ['jsonp', 'echarts', 'lodash-es', 'lodash', 'axios', '@farris/ui-vue', '@farris/mobile-ui-vue', '@farris/charts-vue', 'vue','rxjs','rxjs/operators'], filter: (externals) => { return (id) => { return externals.find((item) => { @@ -40,6 +42,7 @@ export default { { find: '@', replacement: fileURLToPath(new URL('./src', import.meta.url)) }, { find: '@farris/ui-vue/components', replacement: fileURLToPath(new URL('../ui-vue/components', import.meta.url)) }, { find: '@farris/mobile-ui-vue', replacement: fileURLToPath(new URL('../mobile-ui-vue/components', import.meta.url)) }, + { find: '@farris/charts-vue', replacement: fileURLToPath(new URL('../mobile-ui-vue/components', import.meta.url)) }, { find: '@farris/code-editor-vue/components', replacement: fileURLToPath(new URL('../code-editor/components', import.meta.url)) } ], // 插件 默认值 [vue(), vueJsx()] 不要重复添加 diff --git a/packages/designer/package.json b/packages/designer/package.json index 55b1cc00c2493b2c3fea119e6fa7f70f99179448..2590165301fdee6322afc116f0784c8846333761 100644 --- a/packages/designer/package.json +++ b/packages/designer/package.json @@ -12,6 +12,7 @@ "dependencies": { "@farris/ui-vue": "workspace:^", "@farris/mobile-ui-vue": "workspace:^", + "@farris/charts-vue": "workspace:^", "@farris/code-editor-vue":"workspace:^", "@monaco-editor/loader": "^1.4.0", "monaco-editor": "^0.52.2", diff --git a/packages/designer/public/designer-canvas/empty1.json b/packages/designer/public/designer-canvas/empty1.json index 7d8fa548d5ac220d752e907c12941db59a7dab4b..4c180628078ac2cec1c7fd9676eb448098decd03 100644 --- a/packages/designer/public/designer-canvas/empty1.json +++ b/packages/designer/public/designer-canvas/empty1.json @@ -1,63 +1,69 @@ { "Header": { - "Code": "CommonDictionary", - "Type": "Form", - "NameSpace": "Inspur.GS.SagiDemo.SagiDemeModule.CommonDictionaries.CommonDictionaries.Front", + "Code": "MobileVueTest", + "Type": "MobileForm", + "NameSpace": "Inspur.GS.MyDev.FarrisVueTest.VueFormTest.VueFormTest.Front", "CertId": null, - "Name": "通用字典", - "FileName": "CommonDictionary.frm", - "BizobjectID": "d8b09de6-5e7c-bfdf-d308-468827ba0d6b", + "Name": "移动Vue测试", + "FileName": "MobileVueTest.mfrm", + "BizobjectID": "e34f063d-9c31-1e20-4ad7-c6b4f57ddfc9", "Language": null, "Extendable": false, "NameLanguage": { - "zh-CHS": "通用字典", - "en": "Common Dictionary", + "zh-CHS": "移动Vue测试", + "en": "", "zh-CHT": "" }, - "ID": "35dd45be-60c7-4ae8-a827-533424031b76", + "ID": "af47af33-345b-471e-9a00-49db59c222a1", "IsTranslating": false }, - "refs": [ + "Refs": [ { "DependentMetadata": { - "ID": "4fdfc52d-8ac3-4f56-b431-d371c1fcf3ec", + "ID": "1cfb0d2a-7300-4fc7-8d01-cd09a5df9198", "CertId": null, - "NameSpace": "Inspur.GS.SagiDemo.SagiDemeModule.CommonDictionaries.CommonDictionaries.Front", - "Code": "CommonDictionary.frm", - "Name": "CommonDictionary.frm", + "NameSpace": "Inspur.GS.MyDev.FarrisVueTest.VueFormTest.VueFormTest.Front", + "Code": "MobileVueTest.mfrm", + "Name": "MobileVueTest.mfrm", "Type": "ResourceMetadata", - "BizobjectID": "d8b09de6-5e7c-bfdf-d308-468827ba0d6b" + "BizobjectID": "e34f063d-9c31-1e20-4ad7-c6b4f57ddfc9" } } ], "Content": { "code": null, "name": null, + "Id": "af47af33-345b-471e-9a00-49db59c222a1", "Contents": { "module": { - "id": "35dd45be-60c7-4ae8-a827-533424031b76", - "code": "CommonDictionary", - "name": "通用字典", + "id": "af47af33-345b-471e-9a00-49db59c222a1", + "code": "MobileVueTest", + "name": "移动Vue测试", "type": "Module", - "creator": "Sagi", - "creationDate": "2025-02-07T07:43:41.186Z", + "creator": "lijiangkun", + "creationDate": "2025-04-17T03:50:32.907Z", "templateId": "list-card-template", "templateRule": "list-card-template", "entity": [ { - "eapiId": "626b3ac9-e713-4cf2-95bd-9d01e401b51e", - "eapiCode": "CommonDictionary_frm", - "eapiName": "通用字典_frm", - "eapiNameSpace": "Inspur.GS.SagiDemo.SagiDemeModule.CommonDictionaries.CommonDictionaries.Front", - "voPath": "SagiDemo/SagiDemeModule/CommonDictionaries/bo-commondictionaries-front/metadata/components", - "voNameSpace": "Inspur.GS.SagiDemo.SagiDemeModule.CommonDictionaries.CommonDictionaries.Front", - "name": "通用字典_frm", - "id": "52c7e73a-4797-4547-a772-fd50edf39f63", + "eapiId": "29245968-6379-4c6c-a812-72e3411917c0", + "eapiCode": null, + "eapiName": null, + "eapiNameSpace": null, + "voPath": null, + "voNameSpace": null, + "name": "移动Vue测试_mfrm", + "id": "0d20adf7-9ed1-44cf-a8f5-e9714353c174", + "extendProperties": { + "enableStdTimeFormat": true + }, + "sourceType": "vo", + "variables": [], + "code": "MobileVueTest_mfrm", "entities": [ { - "label": "commonDictionarys", - "name": "通用字典", - "id": "57a6ad1b-0dc2-4617-b820-a973da18887c", + "name": "Vue测试", + "id": "fa4b4e84-11b2-40ce-b071-7b2cd5b93eed", "type": { "$type": "EntityType", "fields": [ @@ -67,21 +73,21 @@ "editor": { "$type": "TextBox" }, - "require": true, - "multiLanguage": false, "readonly": false, - "label": "id", + "multiLanguage": false, + "require": true, "name": "主键", - "id": "740c1cff-164d-4f47-96d9-cf7c8075cddb", + "id": "389ab039-9b60-4dd0-8cdc-a23c0feccf31", "type": { "$type": "StringType", - "displayName": "字符串", "length": 36, - "name": "String" + "name": "String", + "displayName": "字符串" }, "path": "ID", + "originalId": "389ab039-9b60-4dd0-8cdc-a23c0feccf31", "code": "ID", - "originalId": "740c1cff-164d-4f47-96d9-cf7c8075cddb", + "label": "id", "bindingField": "id", "bindingPath": "id" }, @@ -92,44 +98,155 @@ "$type": "DateBox", "format": "'yyyy-MM-dd'" }, - "require": false, - "multiLanguage": false, "readonly": false, - "label": "version", + "multiLanguage": false, + "require": false, "name": "版本", - "id": "43a7c79e-56e0-442e-b39d-8f9f45e8b0c8", + "id": "e3ca9955-1155-4a4b-a164-e48c31928c24", "type": { "$type": "DateTimeType", - "displayName": "日期时间", - "name": "DateTime" + "name": "DateTime", + "displayName": "日期时间" }, "path": "Version", + "originalId": "e3ca9955-1155-4a4b-a164-e48c31928c24", "code": "Version", - "originalId": "43a7c79e-56e0-442e-b39d-8f9f45e8b0c8", + "label": "version", "bindingField": "version", "bindingPath": "version" }, + { + "$type": "ComplexField", + "name": "状态", + "id": "9fefd0ed-7594-43f7-a6f2-10828a4d00ae", + "type": { + "$type": "ObjectType", + "name": "BillState9fef", + "fields": [ + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "EnumField" + }, + "readonly": false, + "multiLanguage": false, + "require": false, + "name": "状态", + "id": "9fefd0ed-0101-468f-ae3f-40c76c0f06b0", + "type": { + "$type": "EnumType", + "name": "Enum", + "displayName": "枚举", + "enumValues": [ + { + "disabled": false, + "name": "制单", + "value": "Billing" + }, + { + "disabled": false, + "name": "提交审批", + "value": "SubmitApproval" + }, + { + "disabled": false, + "name": "审批通过", + "value": "Approved" + }, + { + "disabled": false, + "name": "审批不通过", + "value": "ApprovalNotPassed" + } + ], + "valueType": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + } + }, + "path": "BillStatus.BillState", + "originalId": "a0b19650-0101-468f-ae3f-40c76c0f06b0", + "code": "BillState", + "label": "billState", + "bindingField": "billStatus_BillState", + "bindingPath": "billStatus.billState" + } + ], + "displayName": "状态" + }, + "path": "BillStatus", + "originalId": "9fefd0ed-7594-43f7-a6f2-10828a4d00ae", + "code": "BillStatus", + "label": "billStatus", + "bindingField": "billStatus", + "bindingPath": "billStatus" + }, + { + "$type": "ComplexField", + "name": "流程实例", + "id": "12ef9c5c-4ba3-4df4-bd0f-5672c3e0ecff", + "type": { + "$type": "ObjectType", + "name": "ProcessInstance12ef", + "fields": [ + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": false, + "name": "流程实例", + "id": "12ef9c5c-ad8f-4da3-a430-c8a7f2162135", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "ProcessInstance.ProcessInstance", + "originalId": "2e1beb7d-ad8f-4da3-a430-c8a7f2162135", + "code": "ProcessInstance", + "label": "processInstance", + "bindingField": "processInstance_ProcessInstance", + "bindingPath": "processInstance.processInstance" + } + ], + "displayName": "流程实例" + }, + "path": "ProcessInstance", + "originalId": "12ef9c5c-4ba3-4df4-bd0f-5672c3e0ecff", + "code": "ProcessInstance", + "label": "processInstance", + "bindingField": "processInstance", + "bindingPath": "processInstance" + }, { "$type": "SimpleField", "defaultValue": "", "editor": { "$type": "TextBox" }, - "require": false, - "multiLanguage": false, "readonly": false, - "label": "code", + "multiLanguage": false, + "require": false, "name": "编号", - "id": "ddfc72d4-0c9b-49bf-8f41-8047264f6edc", + "id": "8537ca17-502f-4b90-ac87-ae1dde9a7f03", "type": { "$type": "StringType", - "displayName": "字符串", "length": 36, - "name": "String" + "name": "String", + "displayName": "字符串" }, - "path": "Code", - "code": "Code", - "originalId": "ddfc72d4-0c9b-49bf-8f41-8047264f6edc", + "path": "code", + "originalId": "8537ca17-502f-4b90-ac87-ae1dde9a7f03", + "code": "code", + "label": "code", "bindingField": "code", "bindingPath": "code" }, @@ -139,21 +256,21 @@ "editor": { "$type": "TextBox" }, - "require": false, - "multiLanguage": false, "readonly": false, - "label": "name", + "multiLanguage": false, + "require": false, "name": "名称", - "id": "1ecd5759-6df4-41c8-9526-0108a5e2d9b6", + "id": "6a051d6e-f870-4a9f-84df-043cfbc26cf2", "type": { "$type": "StringType", - "displayName": "字符串", "length": 36, - "name": "String" + "name": "String", + "displayName": "字符串" }, - "path": "Name", - "code": "Name", - "originalId": "1ecd5759-6df4-41c8-9526-0108a5e2d9b6", + "path": "name", + "originalId": "6a051d6e-f870-4a9f-84df-043cfbc26cf2", + "code": "name", + "label": "name", "bindingField": "name", "bindingPath": "name" }, @@ -161,222 +278,546 @@ "$type": "SimpleField", "defaultValue": "", "editor": { - "$type": "TextBox" + "$type": "SwitchField" }, - "require": false, - "multiLanguage": false, "readonly": false, - "label": "category", - "name": "分类", - "id": "8e00ef40-17ba-4cbb-8a3c-709cf40b7939", + "multiLanguage": false, + "require": false, + "name": "布尔字段", + "id": "f51c1e5d-fe58-4591-8cd1-25a119c9f4c1", "type": { - "$type": "StringType", - "displayName": "字符串", - "length": 36, - "name": "String" + "$type": "BooleanType", + "name": "Boolean", + "displayName": "布尔" }, - "path": "Category", - "code": "Category", - "originalId": "8e00ef40-17ba-4cbb-8a3c-709cf40b7939", - "bindingField": "category", - "bindingPath": "category" + "path": "booleanField", + "originalId": "f51c1e5d-fe58-4591-8cd1-25a119c9f4c1", + "code": "booleanField", + "label": "booleanField", + "bindingField": "booleanField", + "bindingPath": "booleanField" }, { "$type": "SimpleField", "defaultValue": "", "editor": { - "$type": "EnumField" + "$type": "NumericBox" }, - "require": false, + "readonly": false, "multiLanguage": false, + "require": false, + "name": "数值字段", + "id": "1f816b38-2a72-4252-8eb6-760b8f97fcea", + "type": { + "$type": "NumericType", + "length": 0, + "name": "Number", + "displayName": "数字", + "precision": 0 + }, + "path": "numberField", + "originalId": "1f816b38-2a72-4252-8eb6-760b8f97fcea", + "code": "numberField", + "label": "numberField", + "bindingField": "numberField", + "bindingPath": "numberField" + }, + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "DateBox", + "format": "'yyyy-MM-dd'" + }, "readonly": false, - "label": "enableStatus", - "name": "启用状态", - "id": "df3b84c2-dc6b-419c-85e3-0006b0535e61", + "multiLanguage": false, + "require": false, + "name": "日期字段", + "id": "4911913b-1868-4b34-b4e0-84939b14ab6e", + "type": { + "$type": "DateType", + "name": "Date", + "displayName": "日期" + }, + "path": "dateField", + "originalId": "4911913b-1868-4b34-b4e0-84939b14ab6e", + "code": "dateField", + "label": "dateField", + "bindingField": "dateField", + "bindingPath": "dateField" + } + ], + "primary": "id", + "entities": [ + { + "name": "子表1", + "id": "97347b0b-1c54-432f-891a-4d7b139d6dbc", "type": { - "$type": "EnumType", - "displayName": "枚举", - "name": "Enum", - "enumValues": [ + "$type": "EntityType", + "fields": [ { - "disabled": false, - "name": "未启用", - "value": "0" + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": true, + "name": "主键", + "id": "7d1363dc-bbb0-41ac-b52d-5cc12832bf58", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "ID", + "originalId": "7d1363dc-bbb0-41ac-b52d-5cc12832bf58", + "code": "ID", + "label": "id", + "bindingField": "id", + "bindingPath": "id" }, { - "disabled": false, - "name": "已启用", - "value": "1" + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": true, + "name": "上级对象主键", + "id": "750cc77b-6562-4c83-9e5b-80ad5abb7bb8", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "ParentID", + "originalId": "750cc77b-6562-4c83-9e5b-80ad5abb7bb8", + "code": "ParentID", + "label": "parentID", + "bindingField": "parentID", + "bindingPath": "parentID" + }, + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": false, + "name": "编号", + "id": "3c2b560e-5b8d-4781-b31f-719f83443df1", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "code", + "originalId": "3c2b560e-5b8d-4781-b31f-719f83443df1", + "code": "code", + "label": "code", + "bindingField": "code", + "bindingPath": "code" + }, + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": false, + "name": "名称", + "id": "f89eb4ab-8d41-4fb5-a99d-013bd9242150", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "name", + "originalId": "f89eb4ab-8d41-4fb5-a99d-013bd9242150", + "code": "name", + "label": "name", + "bindingField": "name", + "bindingPath": "name" } ], - "valueType": { - "$type": "StringType", - "displayName": "字符串", - "length": 36, - "name": "String" - } + "primary": "id", + "entities": [], + "name": "Child1", + "displayName": "子表1" + }, + "code": "Child1", + "label": "child1s" + }, + { + "name": "子表2", + "id": "e1c8145b-15d6-4a54-9d79-9149af8d6dc4", + "type": { + "$type": "EntityType", + "fields": [ + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": true, + "name": "主键", + "id": "3a1e8cf0-9ad2-4eeb-b16b-e898a8b24be4", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "ID", + "originalId": "3a1e8cf0-9ad2-4eeb-b16b-e898a8b24be4", + "code": "ID", + "label": "id", + "bindingField": "id", + "bindingPath": "id" + }, + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": true, + "name": "上级对象主键", + "id": "22649c9f-2483-4907-8a67-9fd70ce9b5e7", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "ParentID", + "originalId": "22649c9f-2483-4907-8a67-9fd70ce9b5e7", + "code": "ParentID", + "label": "parentID", + "bindingField": "parentID", + "bindingPath": "parentID" + }, + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": false, + "name": "编号", + "id": "bdc235f6-eded-443a-b95d-cf07fb4a741a", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "code", + "originalId": "bdc235f6-eded-443a-b95d-cf07fb4a741a", + "code": "code", + "label": "code", + "bindingField": "code", + "bindingPath": "code" + }, + { + "$type": "SimpleField", + "defaultValue": "", + "editor": { + "$type": "TextBox" + }, + "readonly": false, + "multiLanguage": false, + "require": false, + "name": "名称", + "id": "b5c97bc2-94e3-483c-8002-1352e103e9f6", + "type": { + "$type": "StringType", + "length": 36, + "name": "String", + "displayName": "字符串" + }, + "path": "name", + "originalId": "b5c97bc2-94e3-483c-8002-1352e103e9f6", + "code": "name", + "label": "name", + "bindingField": "name", + "bindingPath": "name" + } + ], + "primary": "id", + "entities": [], + "name": "Child2", + "displayName": "子表2" }, - "path": "EnableStatus", - "code": "EnableStatus", - "originalId": "df3b84c2-dc6b-419c-85e3-0006b0535e61", - "bindingField": "enableStatus", - "bindingPath": "enableStatus" + "code": "Child2", + "label": "child2s" } ], - "entities": [], - "primary": "id", - "displayName": "通用字典", - "name": "CommonDictionary" + "name": "VueTest", + "displayName": "Vue测试" }, - "code": "CommonDictionary" + "code": "VueTest", + "label": "vueTests" } ], - "sourceUri": "api/sagidemo/sagidememodule/v1.0/CommonDictionary_frm", - "code": "CommonDictionary_frm", - "variables": [], - "sourceType": "vo", - "extendProperties": { - "enableStdTimeFormat": true - } + "sourceUri": "api/mydev/farrisvuetest/v1.0/MobileVueTest_mfrm" } ], "states": [], "stateMachines": [ { - "id": "CommonDictionary_state_machine", - "name": "通用字典", - "uri": "cde50e74-98bb-484d-8c21-247c84e615e0", - "code": "CommonDictionary_frm", - "nameSpace": "Inspur.GS.SagiDemo.SagiDemeModule.CommonDictionaries.CommonDictionaries.Front" + "id": "MobileVueTest_list-page-component_state_machine", + "name": "移动Vue测试_list-page-component状态机", + "uri": "74843186-76b9-4ab3-8fad-c8ba64390c3c" + }, + { + "id": "MobileVueTest_card-page-component_state_machine", + "name": "移动Vue测试_card-page-component状态机", + "uri": "a39bf6b2-330a-4000-9d5e-82aa1c457edf" } ], "viewmodels": [ { - "id": "root-viewmodel", - "code": "root-viewmodel", - "name": "通用字典", + "id": "list-page-viewmodel", + "code": "list-page-viewmodel", + "name": "Vue测试", "fields": [], - "stateMachine": "CommonDictionary_state_machine", - "serviceRefs": [], - "commands": [], - "states": [], - "bindTo": "/", - "enableValidation": false, - "enableUnifiedSession": false - }, - { - "id": "data-grid-component-viewmodel", - "code": "data-grid-component-viewmodel", - "name": "通用字典", - "fields": [ + "stateMachine": "MobileVueTest_list-page-component_state_machine", + "commands": [ { - "type": "Form", - "id": "ddfc72d4-0c9b-49bf-8f41-8047264f6edc", - "fieldName": "code", - "groupId": null, - "groupName": null, - "updateOn": "blur" + "id": "LoadPageForList", + "code": "LoadPageForList", + "name": "页面加载命令", + "params": [ + { + "name": "commandName", + "shownName": "命令名称", + "value": "LoadDataForList" + }, + { + "name": "viewModelId", + "shownName": "视图模型id", + "value": "" + } + ], + "handlerName": "LoadPageForList", + "cmpId": "cf5e568a-5243-4539-94ea-e195eb4d6736", + "extensions": [], + "isInvalid": false }, { - "type": "Form", - "id": "1ecd5759-6df4-41c8-9526-0108a5e2d9b6", - "fieldName": "name", - "groupId": null, - "groupName": null, - "updateOn": "blur" + "id": "LoadDataForList", + "code": "LoadDataForList", + "name": "列表取数命令", + "params": [ + { + "name": "filters", + "shownName": "过滤条件", + "value": "[]" + }, + { + "name": "sorts", + "shownName": "排序条件", + "value": "[]" + }, + { + "name": "autoMerge", + "shownName": "是否合并过滤条件", + "value": true + } + ], + "handlerName": "LoadForList", + "cmpId": "0a68799b-48c6-4c9f-b0d7-140683c62b58", + "extensions": [], + "isInvalid": false }, { - "type": "Form", - "id": "df3b84c2-dc6b-419c-85e3-0006b0535e61", - "fieldName": "enableStatus", - "groupId": null, - "groupName": null, - "updateOn": "change" - } - ], - "commands": [ - { - "id": "fda876c8-7230-46e7-af3d-d38233642275", - "code": "loadList1", - "name": "加载列表数据", - "params": [], - "handlerName": "loadList", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "id": "OpenCardAndAdd", + "code": "OpenCardAndAdd", + "name": "打开卡片并新增命令", + "params": [ + { + "name": "path", + "shownName": "路由地址", + "value": "/MobileVueTest/card-page-component" + }, + { + "name": "queryParams", + "shownName": "携带参数", + "value": "{ \"action\": \"LoadAndAddForCard\" }" + } + ], + "handlerName": "Navigate", + "cmpId": "910661bd-963a-4287-aa32-441c95b8720f", "extensions": [], "isInvalid": false }, { - "id": "22576fc1-08fb-49a9-b132-295c7392b481", - "code": "remove1", - "name": "删除当前数据", + "id": "OpenCardAndEdit", + "code": "OpenCardAndEdit", + "name": "打开卡片并编辑命令", "params": [ { - "name": "id", - "shownName": "待删除数据的标识", - "value": "{DATA~/id}" + "name": "path", + "shownName": "路由地址", + "value": "/MobileVueTest/card-page-component" + }, + { + "name": "queryParams", + "shownName": "携带参数", + "value": "{ \"action\": \"LoadAndEditForCard\", \"id\":\"{DATA~/id}\"}" } ], - "handlerName": "remove", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "handlerName": "Navigate", + "cmpId": "910661bd-963a-4287-aa32-441c95b8720f", "extensions": [], "isInvalid": false }, { - "id": "8788c27e-722a-4b98-9d57-98eafb526fe5", - "code": "loadCard1", - "name": "加载卡片数据", - "params": [], - "handlerName": "loadCard", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "id": "OpenCardAndView", + "code": "OpenCardAndView", + "name": "打开卡片并查看命令", + "params": [ + { + "name": "path", + "shownName": "路由地址", + "value": "/MobileVueTest/card-page-component" + }, + { + "name": "queryParams", + "shownName": "携带参数", + "value": "{ \"action\": \"LoadAndViewForCard\", \"id\":\"{DATA~/id}\"}" + } + ], + "handlerName": "Navigate", + "cmpId": "910661bd-963a-4287-aa32-441c95b8720f", "extensions": [], "isInvalid": false }, { - "id": "ef281c13-4480-4256-901e-4bef5f92bd9e", - "code": "add1", - "name": "新增一条数据", - "params": [], - "handlerName": "add", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "id": "RemoveById", + "code": "RemoveById", + "name": "删除命令", + "params": [ + { + "name": "id", + "shownName": "数据id", + "value": "{DATA~/id}" + } + ], + "handlerName": "RemoveById", + "cmpId": "dab6b7f1-f56f-490a-879c-3d74232cd3ba", "extensions": [], "isInvalid": false }, { - "id": "e7cf83c2-e52d-4dce-aded-047a819c8068", - "code": "changePage1", - "name": "切换页码", + "id": "RemoveByIds", + "code": "RemoveByIds", + "name": "批量删除命令", "params": [ { - "name": "loadCommandName", - "shownName": "切换页面后回调方法", - "value": "loadList1" - }, + "name": "ids", + "shownName": "数据id的数组", + "value": "" + } + ], + "handlerName": "RemoveByIds", + "cmpId": "dab6b7f1-f56f-490a-879c-3d74232cd3ba", + "extensions": [], + "isInvalid": false + }, + { + "id": "GoBack", + "code": "GoBack", + "name": "返回命令", + "params": [ { - "name": "loadCommandFrameId", - "shownName": "目标组件", - "value": "data-grid-component" + "name": "params", + "shownName": "携带参数", + "value": "{}" } ], - "handlerName": "changePage", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "handlerName": "GoBack", + "cmpId": "910661bd-963a-4287-aa32-441c95b8720f", + "extensions": [], + "isInvalid": false + }, + { + "id": "77a277bf-6db3-4f7c-9418-1c12212ccab0", + "code": "componentOnBeforeInit", + "name": "列表页面初始化前事件", + "params": [], + "handlerName": "componentOnBeforeInit", + "cmpId": "791cc5d9-65f9-40d0-b40c-444c7bc1afa3", + "shortcut": {}, "extensions": [], "isInvalid": false } ], - "serviceRefs": [], "states": [], "bindTo": "/", - "parent": "root-viewmodel", + "parent": null, "enableValidation": false }, { - "id": "detail-form-component-viewmodel", - "code": "detail-form-component-viewmodel", - "name": "通用字典", + "id": "card-page-viewmodel", + "code": "card-page-viewmodel", + "name": "卡片页面视图模型", "fields": [ { "type": "Form", - "id": "ddfc72d4-0c9b-49bf-8f41-8047264f6edc", + "id": "389ab039-9b60-4dd0-8cdc-a23c0feccf31", + "fieldName": "id", + "groupId": null, + "groupName": null, + "updateOn": "blur" + }, + { + "type": "Form", + "id": "e3ca9955-1155-4a4b-a164-e48c31928c24", + "fieldName": "version", + "groupId": null, + "groupName": null, + "updateOn": "blur" + }, + { + "type": "Form", + "id": "9fefd0ed-0101-468f-ae3f-40c76c0f06b0", + "fieldName": "billStatus_BillState", + "groupId": null, + "groupName": null, + "updateOn": "change" + }, + { + "type": "Form", + "id": "12ef9c5c-ad8f-4da3-a430-c8a7f2162135", + "fieldName": "processInstance_ProcessInstance", + "groupId": null, + "groupName": null, + "updateOn": "blur" + }, + { + "type": "Form", + "id": "8537ca17-502f-4b90-ac87-ae1dde9a7f03", "fieldName": "code", "groupId": null, "groupName": null, @@ -384,7 +825,7 @@ }, { "type": "Form", - "id": "1ecd5759-6df4-41c8-9526-0108a5e2d9b6", + "id": "6a051d6e-f870-4a9f-84df-043cfbc26cf2", "fieldName": "name", "groupId": null, "groupName": null, @@ -392,443 +833,420 @@ }, { "type": "Form", - "id": "8e00ef40-17ba-4cbb-8a3c-709cf40b7939", - "fieldName": "category", + "id": "f51c1e5d-fe58-4591-8cd1-25a119c9f4c1", + "fieldName": "booleanField", + "groupId": null, + "groupName": null, + "updateOn": "blur" + }, + { + "type": "Form", + "id": "1f816b38-2a72-4252-8eb6-760b8f97fcea", + "fieldName": "numberField", + "groupId": null, + "groupName": null, + "updateOn": "blur" + }, + { + "type": "Form", + "id": "4911913b-1868-4b34-b4e0-84939b14ab6e", + "fieldName": "dateField", "groupId": null, "groupName": null, "updateOn": "blur" } ], + "stateMachine": "MobileVueTest_card-page-component_state_machine", "commands": [ { - "id": "d12acc4e-6274-44dc-95e6-cedeb5e66707", - "code": "edit1", - "name": "编辑数据", + "id": "LoadPageForCard", + "code": "LoadPageForCard", + "name": "卡片页面加载命令", + "params": [ + { + "name": "commandName", + "shownName": "命令名称", + "value": "{UISTATE~/#{card-page-component}/routerState/queryParams/action}" + }, + { + "name": "viewModelId", + "shownName": "视图模型id", + "value": "" + } + ], + "handlerName": "LoadPageForCard", + "cmpId": "cf5e568a-5243-4539-94ea-e195eb4d6736", + "extensions": [], + "isInvalid": false + }, + { + "id": "LoadAndAddForCard", + "code": "LoadAndAddForCard", + "name": "卡片加载并新增命令", + "params": [ + { + "name": "action", + "shownName": "状态迁移动作", + "value": "editAction" + } + ], + "handlerName": "LoadAndAddForCard", + "cmpId": "0a68799b-48c6-4c9f-b0d7-140683c62b58", + "extensions": [], + "isInvalid": false + }, + { + "id": "LoadAndEditForCard", + "code": "LoadAndEditForCard", + "name": "卡片加载并编辑命令", "params": [ { "name": "id", - "shownName": "待编辑数据的标识", - "value": "{DATA~/id}" + "shownName": "数据id", + "value": "{UISTATE~/#{card-page-component}/routerState/queryParams/id}" }, { - "name": "transitionAction", - "shownName": "状态机动作", - "value": "Edit" + "name": "action", + "shownName": "状态迁移动作", + "value": "editAction" } ], - "handlerName": "edit", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "handlerName": "LoadAndEditForCard", + "cmpId": "0a68799b-48c6-4c9f-b0d7-140683c62b58", "extensions": [], "isInvalid": false }, { - "id": "5707d460-c441-45c4-8fe1-f77abd9f75b1", - "code": "save1", - "name": "保存变更", - "params": [], - "handlerName": "save", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "id": "LoadAndViewForCard", + "code": "LoadAndViewForCard", + "name": "卡片加载并查看命令", + "params": [ + { + "name": "id", + "shownName": "数据id", + "value": "{UISTATE~/#{card-page-component}/routerState/queryParams/id}" + }, + { + "name": "action", + "shownName": "状态迁移动作", + "value": "viewAction" + } + ], + "handlerName": "LoadAndViewForCard", + "cmpId": "0a68799b-48c6-4c9f-b0d7-140683c62b58", "extensions": [], "isInvalid": false }, { - "id": "b3897b4b-a37f-48e3-afb3-8489cec02806", - "code": "cancel1", - "name": "取消变更", - "params": [], - "handlerName": "cancel", - "cmpId": "7c48ef46-339c-42d4-8365-a21236c63044", + "id": "Save", + "code": "Save", + "name": "保存命令", + "params": [ + { + "name": "action", + "shownName": "状态迁移动作", + "value": null + } + ], + "handlerName": "Save", + "cmpId": "f863c66a-bf93-4d1f-9f99-bcd76009609d", + "extensions": [], + "isInvalid": false + }, + { + "id": "Cancel", + "code": "Cancel", + "name": "取消命令", + "params": [ + { + "name": "id", + "shownName": "数据id", + "value": "{DATA~/id}" + }, + { + "name": "action", + "shownName": "迁移动作", + "value": null + } + ], + "handlerName": "Cancel", + "cmpId": "05592163-fd45-474e-b0ab-61d7dc02e5c0", + "extensions": [], + "isInvalid": false + }, + { + "id": "GoBackAndCheck", + "code": "GoBackAndCheck", + "name": "返回并检查", + "params": [ + { + "name": "params", + "shownName": "返回携带参数", + "value": "{}" + } + ], + "handlerName": "GoBackAndCheck", + "cmpId": "910661bd-963a-4287-aa32-441c95b8720f", "extensions": [], "isInvalid": false } ], - "serviceRefs": [], "states": [], "bindTo": "/", - "parent": "root-viewmodel", - "enableValidation": true + "parent": null, + "enableValidation": false } ], "components": [ { - "id": "root-component", + "id": "card-page-component", "type": "component", - "componentType": "frame", - "viewModel": "root-viewmodel", + "title": "卡片页面", + "componentType": "page", + "pageType": "Card", + "viewModel": "card-page-viewmodel", + "appearance": null, + "visible": true, + "onBeforeInit": null, + "onInit": "LoadPageForCard", + "onLoadData": null, + "goBack": "GoBackAndCheck", "contents": [ { - "id": "root-layout", - "type": "content-container", - "appearance": { - "class": "f-page f-page-navigate f-page-is-grid-card" - }, + "id": "card-page-container", + "type": "page-container", + "title": "页面根容器", + "appearance": null, + "visible": true, "contents": [ { - "id": "page-header", - "type": "page-header", - "appearance": { - "class": "f-page-header" - }, - "iconClass": "f-title-icon f-text-orna-dict", - "icon": "f-icon f-icon-page-title-dictionary", - "title": "通用字典", - "toolbar": { - "type": "response-toolbar", - "buttons": [ - { - "id": "button-add", - "type": "response-toolbar-item", - "appearance": { - "class": "btn-primary" - }, - "disabled": "!viewModel.stateMachine['canAdd']", - "onClick": "root-viewmodel.data-grid-component-viewmodel.add1", - "text": "新增" - }, - { - "id": "button-edit", - "type": "response-toolbar-item", - "text": "编辑", - "disabled": "!viewModel.stateMachine['canEdit']", - "onClick": "root-viewmodel.detail-form-component-viewmodel.edit1" - }, - { - "id": "button-save", - "type": "response-toolbar-item", - "text": "保存", - "disabled": "!viewModel.stateMachine['canSave']", - "onClick": "root-viewmodel.detail-form-component-viewmodel.save1" - }, - { - "id": "button-cancel", - "type": "response-toolbar-item", - "text": "取消", - "disabled": "!viewModel.stateMachine['canCancel']", - "onClick": "root-viewmodel.detail-form-component-viewmodel.cancel1" - }, - { - "id": "button-delete", - "type": "response-toolbar-item", - "text": "删除", - "disabled": "!viewModel.stateMachine['canRemove']", - "onClick": "root-viewmodel.data-grid-component-viewmodel.remove1" - } - ] - } - }, - { - "id": "main-container", - "type": "content-container", - "appearance": { - "class": "f-page-main" - }, + "id": "card-page-header-container", + "type": "page-header-container", + "title": "页头容器", + "appearance": null, + "visible": true, "contents": [ { - "id": "content-splitter", - "type": "splitter", - "appearance": { - "class": "f-page-content" - }, - "contents": [ - { - "id": "content-list", - "type": "splitter-pane", - "appearance": { - "class": "f-col-w6 f-page-content-nav" - }, - "resizable": true, - "contents": [ - { - "id": "data-grid-component-ref", - "type": "component-ref", - "component": "data-grid-component" - } - ] - }, - { - "id": "content-main", - "type": "splitter-pane", - "appearance": { - "class": "f-page-content-main" - }, - "contents": [ - { - "id": "detail-component-ref", - "type": "component-ref", - "component": "detail-form-component" - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "id": "data-grid-component", - "type": "component", - "componentType": "data-grid", - "viewModel": "data-grid-component-viewmodel", - "appearance": { - "class": "f-struct-wrapper f-utils-fill-flex-column" - }, - "onInit": "loadList1", - "contents": [ - { - "id": "data-grid-section", - "type": "section", - "appearance": { - "class": "f-section-grid f-section-in-nav" - }, - "fill": true, - "showHeader": false, - "contents": [ - { - "id": "dataGrid", - "type": "data-grid", - "appearance": { - "class": "f-component-grid" - }, - "dataSource": "commonDictionarys", - "columns": [ - { - "id": "code_ddfc72d4_hxcr", - "type": "data-grid-column", - "title": "编号", - "field": "code", - "dataType": "string", - "binding": { - "type": "Form", - "path": "code", - "field": "ddfc72d4-0c9b-49bf-8f41-8047264f6edc", - "fullPath": "Code" - }, - "width": 120, - "visible": true, - "filter": "", - "showSetting": false, - "actualWidth": 120 - }, - { - "id": "name_1ecd5759_yj0s", - "type": "data-grid-column", - "title": "名称", - "field": "name", - "dataType": "string", - "binding": { - "type": "Form", - "path": "name", - "field": "1ecd5759-6df4-41c8-9526-0108a5e2d9b6", - "fullPath": "Name" - }, - "width": 120, + "id": "card-page-navigation-bar", + "type": "navbar", + "appearance": null, + "title": "卡片页面", + "text": "导航栏", "visible": true, - "filter": "", - "showSetting": false, - "actualWidth": 120 - }, - { - "id": "enableStatus_df3b84c2_jhvi", - "type": "data-grid-column", - "title": "启用状态", - "field": "enableStatus", - "dataType": "enum", - "binding": { - "type": "Form", - "path": "enableStatus", - "field": "df3b84c2-dc6b-419c-85e3-0006b0535e61", - "fullPath": "EnableStatus" - }, - "width": 120, - "formatter": { - "type": "enum", - "data": [ + "onLeftClick": "GoBackAndCheck", + "rightToolbar": { + "items": [ { + "id": "add-btn", + "type": "tool-bar-item", + "appearance": null, + "iconType": "", "disabled": false, - "name": "未启用", - "value": "0" + "text": "新增", + "visible": true, + "onClick": "LoadAndAddForCard" }, { + "id": "cancel-btn", + "type": "tool-bar-item", + "appearance": null, + "iconType": "", "disabled": false, - "name": "已启用", - "value": "1" + "text": "取消", + "visible": true, + "onClick": "Cancel" } ] - }, - "visible": true, - "filter": "", - "showSetting": true, - "actualWidth": 120 + } } - ], - "fieldEditable": false, - "onClickRow": "loadCard1", - "onPageIndexChanged": "changePage1", - "onPageSizeChanged": "changePage1", - "pagination": { - "enable": true, - "mode": "server" - }, - "disabled": "viewModel.stateMachine['editable']" - } - ] - } - ] - }, - { - "id": "detail-form-component", - "type": "component", - "componentType": "form", - "formColumns": 1, - "viewModel": "detail-form-component-viewmodel", - "appearance": { - "class": "f-struct-wrapper" - }, - "contents": [ - { - "id": "detail-form-section", - "type": "section", - "appearance": { - "class": "f-section-form f-section-in-main" - }, - "mainTitle": "基本信息", - "contents": [ + ] + }, { - "id": "detail-form-layout", - "type": "response-form", - "appearance": { - "class": "f-form-layout farris-form farris-form-controls-inline" - }, + "id": "card-page-body-container", + "type": "page-body-container", + "title": "主内容容器", + "appearance": null, + "visible": true, "contents": [ - { - "id": "code_ddfc72d4_njzh", - "type": "form-group", - "appearance": { - "class": "col-12" - }, - "label": "编号", - "binding": { - "type": "Form", - "path": "code", - "field": "ddfc72d4-0c9b-49bf-8f41-8047264f6edc", - "fullPath": "Code" - }, - "editor": { - "type": "input-group", - "readonly": "!viewModel.stateMachine['editable']", - "maxLength": 36 - }, - "path": "code" - }, - { - "id": "name_1ecd5759_onbj", - "type": "form-group", - "appearance": { - "class": "col-12" - }, - "label": "名称", - "binding": { - "type": "Form", - "path": "name", - "field": "1ecd5759-6df4-41c8-9526-0108a5e2d9b6", - "fullPath": "Name" - }, - "editor": { - "type": "input-group", - "readonly": "!viewModel.stateMachine['editable']", - "maxLength": 36 - }, - "path": "name" - }, - { - "id": "category_8e00ef40_wdtg", - "type": "form-group", - "appearance": { - "class": "col-12" - }, - "label": "分类", - "binding": { - "type": "Form", - "path": "category", - "field": "8e00ef40-17ba-4cbb-8a3c-709cf40b7939", - "fullPath": "Category" - }, - "editor": { - "type": "input-group", - "readonly": "!viewModel.stateMachine['editable']", - "maxLength": 36 - }, - "path": "category" - } - ], - "controlsInline": true, - "formAutoIntl": true + + ] + }, + { + "id": "card-page-footer-container", + "type": "page-footer-container", + "title": "页尾容器", + "appearance": null, + "size": null, + "position": null, + "visible": true, + "contents": [ + { + "id": "card-page-save-button", + "type": "button", + "title": "保存按钮", + "visible": true, + "disabled": false, + "text": "按钮", + "icon": "图标" + } + + + ] } ] } - ] + ], + "route": { + "id": "card-page-component", + "uri": "card-page-component", + "name": "card-page-component", + "params": [] + } } ], "webcmds": [ { - "id": "7c48ef46-339c-42d4-8365-a21236c63044", - "path": "/projects/packages/Inspur.GS.Gsp.Web.WebCmp/webcmd", - "name": "ListCardController.webcmd", + "id": "cf5e568a-5243-4539-94ea-e195eb4d6736", + "path": "Gsp/Mobile/MobileCmp/bo-mobilecmp/metadata/webcmd", + "name": "LoadPageCommands.webcmd", "refedHandlers": [ { - "host": "fda876c8-7230-46e7-af3d-d38233642275", - "handler": "loadList" + "host": "LoadPageForList", + "handler": "LoadPageForList" }, { - "host": "8788c27e-722a-4b98-9d57-98eafb526fe5", - "handler": "loadCard" + "host": "LoadPageForCard", + "handler": "LoadPageForCard" + } + ], + "code": "LoadPageCommands", + "nameSpace": "Inspur.GS.Gsp.Mobile.MobileCmp" + }, + { + "id": "0a68799b-48c6-4c9f-b0d7-140683c62b58", + "path": "Gsp/Mobile/MobileCmp/bo-mobilecmp/metadata/webcmd", + "name": "LoadCommands.webcmd", + "refedHandlers": [ + { + "host": "LoadDataForList", + "handler": "LoadForList" + }, + { + "host": "LoadAndAddForCard", + "handler": "LoadAndAddForCard" }, { - "host": "ef281c13-4480-4256-901e-4bef5f92bd9e", - "handler": "add" + "host": "LoadAndEditForCard", + "handler": "LoadAndEditForCard" }, { - "host": "d12acc4e-6274-44dc-95e6-cedeb5e66707", - "handler": "edit" + "host": "LoadAndViewForCard", + "handler": "LoadAndViewForCard" + } + ], + "code": "LoadCommands", + "nameSpace": "Inspur.GS.Gsp.Mobile.MobileCmp" + }, + { + "id": "910661bd-963a-4287-aa32-441c95b8720f", + "path": "Gsp/Mobile/MobileCmp/bo-mobilecmp/metadata/webcmd", + "name": "NavigateCommands.webcmd", + "refedHandlers": [ + { + "host": "OpenCardAndAdd", + "handler": "Navigate" }, { - "host": "5707d460-c441-45c4-8fe1-f77abd9f75b1", - "handler": "save" + "host": "OpenCardAndEdit", + "handler": "Navigate" }, { - "host": "b3897b4b-a37f-48e3-afb3-8489cec02806", - "handler": "cancel" + "host": "OpenCardAndView", + "handler": "Navigate" }, { - "host": "22576fc1-08fb-49a9-b132-295c7392b481", - "handler": "remove" + "host": "GoBack", + "handler": "GoBack" }, { - "host": "e7cf83c2-e52d-4dce-aded-047a819c8068", - "handler": "changePage" + "host": "GoBackAndCheck", + "handler": "GoBackAndCheck" + } + ], + "code": "NavigateCommands", + "nameSpace": "Inspur.GS.Gsp.Mobile.MobileCmp" + }, + { + "id": "dab6b7f1-f56f-490a-879c-3d74232cd3ba", + "path": "Gsp/Mobile/MobileCmp/bo-mobilecmp/metadata/webcmd", + "name": "RemoveCommands.webcmd", + "refedHandlers": [ + { + "host": "RemoveById", + "handler": "RemoveById" + }, + { + "host": "RemoveByIds", + "handler": "RemoveByIds" + } + ], + "code": "RemoveCommands", + "nameSpace": "Inspur.GS.Gsp.Mobile.MobileCmp" + }, + { + "id": "f863c66a-bf93-4d1f-9f99-bcd76009609d", + "path": "Gsp/Mobile/MobileCmp/bo-mobilecmp/metadata/webcmd", + "name": "SaveCommands.webcmd", + "refedHandlers": [ + { + "host": "Save", + "handler": "Save" + } + ], + "code": "SaveCommands", + "nameSpace": "Inspur.GS.Gsp.Mobile.MobileCmp" + }, + { + "id": "05592163-fd45-474e-b0ab-61d7dc02e5c0", + "path": "Gsp/Mobile/MobileCmp/bo-mobilecmp/metadata/webcmd", + "name": "CancelCommands.webcmd", + "refedHandlers": [ + { + "host": "Cancel", + "handler": "Cancel" + } + ], + "code": "CancelCommands", + "nameSpace": "Inspur.GS.Gsp.Mobile.MobileCmp" + }, + { + "id": "791cc5d9-65f9-40d0-b40c-444c7bc1afa3", + "path": "MyDev/FarrisVueTest/VueFormTest/bo-vueformtest-front/metadata/components", + "name": "MobileVueTest_frm_Controller.webcmd", + "refedHandlers": [ + { + "host": "77a277bf-6db3-4f7c-9418-1c12212ccab0", + "handler": "componentOnBeforeInit" } ], - "code": "ListCardController", - "nameSpace": "Inspur.GS.Gsp.Web.WebCmp" + "code": "MobileVueTest_frm_Controller", + "nameSpace": "Inspur.GS.MyDev.FarrisVueTest.VueFormTest.VueFormTest.Front" } ], - "projectName": "bo-commondictionaries-front", + "projectName": "bo-vueformtest-front", + "customClass": {}, "actions": [] } }, - "Id": "35dd45be-60c7-4ae8-a827-533424031b76", "CreationDate": null }, - "extendRule": null, - "relativePath": "SagiDemo/SagiDemeModule/CommonDictionaries/bo-commondictionaries-front/metadata/components", - "extendProperty": "", - "extended": false, - "previousVersion": null, - "version": null, - "properties": { + "ExtendRule": null, + "RelativePath": "MyDev/FarrisVueTest/VueFormTest/bo-vueformtest-front/metadata/components", + "ExtendProperty": "", + "Extended": false, + "PreviousVersion": null, + "Version": null, + "Properties": { "SchemaVersion": null, "CacheVersion": null, "Framework": "Vue" diff --git a/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts b/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts index ca3121935cc43b52022bcd3707cd9348958f0deb..e6af180b98213fead2795bb80cbb1b2ddabc110f 100644 --- a/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts +++ b/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts @@ -10,7 +10,7 @@ import { import { useMobileControlCreator } from "../control-creator/use-mobile-control-creator"; import { PageComponent, UsePageSchema } from "../../../components/types"; import ControllCategories from '../schema-repository/controller/mobile-categories'; - +import { FChartsBar } from '@farris/charts-vue/components'; export function useMobileDesignerContext(): UseDesignerContext { /** 设计器模式 */ @@ -25,8 +25,9 @@ export function useMobileDesignerContext(): UseDesignerContext { ContentContainer, Card, FloatContainer, Textarea, DatePicker, DateTimePicker, Lookup, Navbar, ListView, Picker, NumberInput, Switch, CheckboxGroup, RadioGroup, Form, FormItem, InputGroup, - Button, ButtonGroup, + Button, ButtonGroup, FChartsBar ]; + registerDesignerComponents(componentsToRegister); /** 支持的控制器 */ diff --git a/packages/designer/src/components/types/toolbox/mobile-toolbox.json b/packages/designer/src/components/types/toolbox/mobile-toolbox.json index fdc942bfbc104417946f069a422db9c442decea6..a017f97b814852ae92501e6ff71e87b5d32b28e5 100644 --- a/packages/designer/src/components/types/toolbox/mobile-toolbox.json +++ b/packages/designer/src/components/types/toolbox/mobile-toolbox.json @@ -1,4 +1,17 @@ [ +{ + "type": "charts", + "name": "图表控件", + "items": [ + { + "id": "ChartsBar", + "type": "charts-bar", + "name": "图表", + "category": "basic", + "icon": "button" + } + ] + }, { "type": "basic", "name": "基础类控件", diff --git a/packages/designer/vite.config.dev.ts b/packages/designer/vite.config.dev.ts index d6be077ad102a93b9cd98650fec754cd3879d437..bbf9e0f98fb3884c34967a326768bc02b9e7bc0c 100644 --- a/packages/designer/vite.config.dev.ts +++ b/packages/designer/vite.config.dev.ts @@ -20,6 +20,8 @@ export default defineConfig({ '@farris/ui-vue': resolve(__dirname, '../ui-vue'), '@farris/code-editor-vue': resolve(__dirname, '../code-editor'), '@farris/mobile-ui-vue': resolve(__dirname, '../mobile-ui-vue/components'), + '@farris/charts-vue': resolve(__dirname, '../charts-vue'), + } } }); diff --git a/packages/mobile-ui-vue/components/button/index.ts b/packages/mobile-ui-vue/components/button/index.ts index 8084a9042c08e37d0e6155c3a5f1753fe9de2970..0021e31a6e2dfbeb71375928efa79311b07165a2 100644 --- a/packages/mobile-ui-vue/components/button/index.ts +++ b/packages/mobile-ui-vue/components/button/index.ts @@ -11,6 +11,5 @@ const Button = withInstall(ButtonInstallless); withRegister(Button, { name: BUTTON_REGISTERED_NAME, propsResolverGenerator }); withRegisterDesigner(Button, { name: BUTTON_REGISTERED_NAME, propsResolverGenerator, designerComponent: ButtonDesign }); - export { Button }; export default Button;