diff --git a/.gitignore b/.gitignore index 548aec194f204bd072bf7b7a4e7d2bfcc2fa0cd5..f2d2ac5e7c854a1d10405a69d3f8159f7d5832ef 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ dist-ssr /packages/expression-engine/dist-rollup /publish.sh +/extract-i18n.sh +/theme.sh +/dts.sh diff --git a/package.json b/package.json index d919dcf236a1cb63423d03f8a38ff9576ec17a97..af76a17bbd79c62a4de8f4ec1b3e7fa14e210b4f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "build:ui-binding-vue": "pnpm --filter ui-binding-vue run build:lib && pnpm --filter ui-binding-vue run rollup", "build:renderer": "pnpm --filter renderer run build:system", "build:ui-vue": "pnpm --filter ui-vue run build:lib && pnpm --filter ui-vue run build:system", + "build:mobile-ui-vue": "pnpm --filter mobile-ui-vue run build:lib", "build:charts-vue": "pnpm --filter charts-vue run build:lib && pnpm --filter charts-vue run build:system", "build:designer": "pnpm --filter designer run build:system", "build:expression-engine-vue": "pnpm --filter expression-engine-vue run rollup", @@ -125,7 +126,7 @@ }, "dependencies": { "@eslint/js": "^9.31.0", - "axios": "^1.10.0", + "axios": "^1.11.0", "echarts": "^5.6.0", "globals": "^15.15.0", "jsonp": "^0.2.1", diff --git a/packages/bef/lib/bef-proxy.ts b/packages/bef/lib/bef-proxy.ts index 2103e3ddcfaa47c48719963f0d80049c961223b6..67eef0ec3ab6ecea3cb23e7d55e48d758e289738 100644 --- a/packages/bef/lib/bef-proxy.ts +++ b/packages/bef/lib/bef-proxy.ts @@ -342,6 +342,36 @@ export class BefProxy { return this.request(HttpMethods.POST, url, requestConfig); }); } + /** + * 启用 + * @param id 数据id + * @returns + */ + public enable(id: string): Promise { + return this.wrapAsync(() => { + const url = `${this.baseUrl}/service/enable`; + const bodyWithRequestInfo = this.proxyExtend.extendBody({ dataId: id }); + const requestConfig: HttpRequestConfig = { + body: bodyWithRequestInfo + }; + return this.request(HttpMethods.PUT, url, requestConfig); + }); + } + /** + * 停用 + * @param id 数据id + * @returns + */ + public disable(id: string): Promise { + return this.wrapAsync(() => { + const url = `${this.baseUrl}/service/disable`; + const bodyWithRequestInfo = this.proxyExtend.extendBody({ dataId: id }); + const requestConfig: HttpRequestConfig = { + body: bodyWithRequestInfo + }; + return this.request(HttpMethods.PUT, url, requestConfig); + }); + } /** * 父节点分级方式新增同级 * @param id diff --git a/packages/bef/lib/bef-repository.ts b/packages/bef/lib/bef-repository.ts index 9e4c5113b6c40b63ebd809ab13d247a60971ed99..340cc4b1b26fd935bd0706703984bae6b0455901 100644 --- a/packages/bef/lib/bef-repository.ts +++ b/packages/bef/lib/bef-repository.ts @@ -69,7 +69,7 @@ class BefRepository extends Repository { if (config.isDynamic) { this.apiProxy = this.injector.get(BefProxy); this.apiProxy.init(config.baseUrl); - + } else { this.apiProxy = this.injector.get(config.apiProxyType); } @@ -277,8 +277,8 @@ class BefRepository extends Repository { * @param ids * @returns */ - public removeEntitiesAndSave(ids: string[]){ - const removePromise = this.apiProxy.batchDeleteAndSave(ids).then(()=>{ + public removeEntitiesAndSave(ids: string[]) { + const removePromise = this.apiProxy.batchDeleteAndSave(ids).then(() => { return true; }); return removePromise; diff --git a/packages/bef/package.json b/packages/bef/package.json index 1a295e66a92e5742365cd59c5b3c4632aa604ce4..fefd390402ce44d452dc45a3e2c4f35266b63d4c 100644 --- a/packages/bef/package.json +++ b/packages/bef/package.json @@ -43,7 +43,7 @@ "@babel/parser": "^7.19.0", "@babel/plugin-proposal-decorators": "^7.24.7", "@babel/preset-typescript": "^7.18.0", - "@babel/traverse": "^7.19.0", + "@babel/traverse": "^7.23.2", "@commitlint/cli": "^17.1.0", "@commitlint/config-conventional": "^17.1.0", "@testing-library/vue": "^7.0.0", @@ -68,9 +68,9 @@ "ora": "^6.1.2", "patch-vue-directive-ssr": "^0.0.1", "sass": "^1.32.2", - "shelljs": "^0.8.4", + "shelljs": "^0.8.5", "typescript": "^4.6.4", - "vite": "^4.1.4", + "vite": "^4.5.10", "vite-plugin-dts": "^2.1.0", "vite-plugin-md": "^0.20.0", "vite-svg-loader": "^4.0.0", diff --git a/packages/charts-vue/docs/.vitepress/farris-theme/components/FarrisFeatures.vue b/packages/charts-vue/docs/.vitepress/farris-theme/components/FarrisFeatures.vue index 417459b7ffc02129727b7d8a56477805de6510e7..509ec570832c88627b0d5a3eff3eff386f232ccc 100644 --- a/packages/charts-vue/docs/.vitepress/farris-theme/components/FarrisFeatures.vue +++ b/packages/charts-vue/docs/.vitepress/farris-theme/components/FarrisFeatures.vue @@ -1,7 +1,6 @@ diff --git a/packages/renderer/src/callback-handler/before-close-drawer-callback-handler.ts b/packages/renderer/src/callback-handler/before-close-drawer-callback-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..44a835f317ce7d7aab885289c169f4c0993870a5 --- /dev/null +++ b/packages/renderer/src/callback-handler/before-close-drawer-callback-handler.ts @@ -0,0 +1,36 @@ +import { Module } from '@farris/devkit-vue'; +import { CallbackHandler } from './callback-handler'; + +/** + * 抽屉关闭前回调 + */ +export class BeforeCloseDrawerCallbackHandler extends CallbackHandler { + public schemaType: string | null = 'drawer'; + /** + * 回调类型 + */ + public callbackType: string | null = 'beforeClose'; + + /** + * 构造函数 + */ + constructor(module: Module) { + super(module); + } + + /** + * 处理回调 + */ + public handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { + const schema = viewSchema; + const { beforeClose } = schema; + const defaultReturnValue = true; + + if (!beforeClose) { + return defaultReturnValue; + } + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload: null, schema, token, type: schemaType, payloads: args }; + return this.handleCallback(type, callbackArgs, beforeClose, defaultReturnValue); + } +} diff --git a/packages/renderer/src/callback-handler/before-close-modal-callback-handler.ts b/packages/renderer/src/callback-handler/before-close-modal-callback-handler.ts index 6d6e26fd46d90b5cc3c337d90a853615d47d92bb..cd9b57f800c53d41fad64350514177504223ea36 100644 --- a/packages/renderer/src/callback-handler/before-close-modal-callback-handler.ts +++ b/packages/renderer/src/callback-handler/before-close-modal-callback-handler.ts @@ -5,7 +5,7 @@ import { CallbackHandler } from './callback-handler'; * 弹窗关闭前回调 */ export class BeforeCloseModalCallbackHandler extends CallbackHandler { - + public schemaType: string | null = 'modal'; /** * 回调类型 */ @@ -21,17 +21,17 @@ export class BeforeCloseModalCallbackHandler extends CallbackHandler { /** * 处理回调 */ - public handle(type: string, args: any[]): undefined | boolean | Promise { + public handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { const payload = args[0]; - const schema = args[1]; + const schema = viewSchema; const { beforeClose } = schema; const defaultReturnValue = true; if (!beforeClose) { return defaultReturnValue; } - - const callbackArgs = { payload, schema }; + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload, schema, token, type: schemaType, payloads: args }; return this.handleCallback(type, callbackArgs, beforeClose, defaultReturnValue); } } diff --git a/packages/renderer/src/callback-handler/before-edit-cell-callback-handler.ts b/packages/renderer/src/callback-handler/before-edit-cell-callback-handler.ts index 8bdda3ce449cfad203f82ef2c0c0813182fed722..c82f64688b68383fbc4b4f3c08a5c890bd0d150c 100644 --- a/packages/renderer/src/callback-handler/before-edit-cell-callback-handler.ts +++ b/packages/renderer/src/callback-handler/before-edit-cell-callback-handler.ts @@ -10,7 +10,7 @@ import { ConfigType } from '../config-dependency-resolver'; * 单元格编辑前回调 */ export class BeforeEditCallCallbackHandler extends CallbackHandler { - + public schemaType: string | null = 'data-grid'; /** * 回调类型 */ @@ -26,9 +26,8 @@ export class BeforeEditCallCallbackHandler extends CallbackHandler { /** * 回调处理 */ - public handle(callbackType: string, args: any[]): undefined | boolean | Promise { + public handle(callbackType: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { const { column, rawData } = args[0]; - const viewSchema = args[1]; // 确定单元格是否允许编辑 const canEdit = this.canEditCell(column, rawData, viewSchema); if (!canEdit) { diff --git a/packages/renderer/src/callback-handler/before-load-data-callback-handler.ts b/packages/renderer/src/callback-handler/before-load-data-callback-handler.ts index 084634a647e794d25fc0ec24f2dbe66d0ff4d98d..3e972a6209a5cf34112a91c6599e5505b151f679 100644 --- a/packages/renderer/src/callback-handler/before-load-data-callback-handler.ts +++ b/packages/renderer/src/callback-handler/before-load-data-callback-handler.ts @@ -5,7 +5,7 @@ import { CallbackHandler } from "./callback-handler"; * 数据加载前回调 */ export class BeforeLoadDataCallbackHandler extends CallbackHandler { - + public schemaType: string | null = 'lookup'; /** * 回调类型 */ @@ -21,17 +21,16 @@ export class BeforeLoadDataCallbackHandler extends CallbackHandler { /** * 处理回调 */ - public handle(type: string, args: any[]): undefined | boolean | Promise { + public handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { const payload = args[0]; - const viewSchema = args[1]; const { beforeLoadData } = viewSchema; const defaultReturnValue = payload.data; if (!beforeLoadData) { return defaultReturnValue; } - - const callbackArgs = { payload, schema: viewSchema }; + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload, schema: viewSchema, token, type: schemaType, payloads: args }; return this.handleCallback(type, callbackArgs, beforeLoadData, defaultReturnValue); } } diff --git a/packages/renderer/src/callback-handler/before-select-data-callback-handler.ts b/packages/renderer/src/callback-handler/before-select-data-callback-handler.ts index c368cbca590ca8f0b29613355d4b4cefaf93df96..2c27a7c646706c1e373277c2e32bab19e0777824 100644 --- a/packages/renderer/src/callback-handler/before-select-data-callback-handler.ts +++ b/packages/renderer/src/callback-handler/before-select-data-callback-handler.ts @@ -5,7 +5,7 @@ import { CallbackHandler } from "./callback-handler"; * 选择前回调 */ export class BeforeSelectDataCallbackHandler extends CallbackHandler { - + public schemaType: string | null = 'lookup'; /** * 回调类型 */ @@ -24,17 +24,16 @@ export class BeforeSelectDataCallbackHandler extends CallbackHandler { * @param args * @returns */ - public handle(type: string, args: any[]): undefined | boolean | Promise { + public handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { const payload = args[0]; - const viewSchema = args[1]; const { beforeSelectData } = viewSchema; const defaultReturnValue = true; if (!beforeSelectData) { return defaultReturnValue; } - - const callbackArgs = { payload, schema: viewSchema }; + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload, schema: viewSchema, token, type: schemaType, payloads: args }; return this.handleCallback(type, callbackArgs, beforeSelectData, defaultReturnValue); } } diff --git a/packages/renderer/src/callback-handler/before-update-data-grid-callback-handler.ts b/packages/renderer/src/callback-handler/before-update-data-grid-callback-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..1c6fdbfb49c92f8768b14707157952e6f2728b89 --- /dev/null +++ b/packages/renderer/src/callback-handler/before-update-data-grid-callback-handler.ts @@ -0,0 +1,38 @@ +import { ExpressionEvaluator, Module } from '@farris/devkit-vue'; +import { CallbackHandler } from './callback-handler'; +import { FormMetadataService } from '../service'; + +/** + * 单元格编辑前回调 + */ +export class BeforeUpdateDataGridCallCallbackHandler extends CallbackHandler { + public schemaType: string | null = 'data-grid'; + /** + * 回调类型 + */ + public callbackType: string | null = 'beforeUpdate'; + + /** + * 构造函数 + */ + constructor(module: Module, private formMetadataService: FormMetadataService, private expressionEvaluator: ExpressionEvaluator) { + super(module); + } + + /** + * 处理回调 + */ + public handle(type: string, viewSchema: Record, args: any[]): any | Promise { + const schema = viewSchema; + const { beforeUpdate } = schema; + const defaultReturnValue = () => { }; + + if (!beforeUpdate) { + return defaultReturnValue; + } + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload: args[0], schema, token, type: schemaType, payloads: args }; + return this.handleCallback(type, callbackArgs, beforeUpdate, defaultReturnValue); + } + +} diff --git a/packages/renderer/src/callback-handler/callback-handler-registry.ts b/packages/renderer/src/callback-handler/callback-handler-registry.ts index 5cd9cb473bba19541014ba1fbc326a50781e9439..e8746084c22b12f422282d50936e893a46395914 100644 --- a/packages/renderer/src/callback-handler/callback-handler-registry.ts +++ b/packages/renderer/src/callback-handler/callback-handler-registry.ts @@ -24,7 +24,7 @@ export class CallbackHandlerRegistry { * @param callbackType 回调类型 * @returns */ - public getHandler(callbackType: string): CallbackHandler | null { - return this.handlers.find((handler: CallbackHandler) => handler.callbackType === callbackType) || null; + public getHandler(callbackType: string, viewSchema: Record): CallbackHandler | null { + return this.handlers.find((handler: CallbackHandler) => handler.callbackType === callbackType && handler.schemaType === viewSchema.type) || null; } } diff --git a/packages/renderer/src/callback-handler/callback-handler.ts b/packages/renderer/src/callback-handler/callback-handler.ts index b300fa4c7e070bd6f4a69216fae89414b23ef6bb..91bf2092828ac7774e557f047ee81d4bee87fa58 100644 --- a/packages/renderer/src/callback-handler/callback-handler.ts +++ b/packages/renderer/src/callback-handler/callback-handler.ts @@ -6,6 +6,10 @@ import { CallbackArgs } from './types'; * 回调方法处理器 */ export abstract class CallbackHandler { + /** + * schema类型 + */ + public abstract schemaType: string | null; /** * 模块 @@ -29,7 +33,7 @@ export abstract class CallbackHandler { * @param type 回调类型 * @param args 回调参数 */ - public abstract handle(type: string, args: any[]): undefined | boolean | Promise | Promise; + public abstract handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise | Promise; /** * 调用回调命令 diff --git a/packages/renderer/src/callback-handler/combo-list-before-open-callback-handler.ts b/packages/renderer/src/callback-handler/combo-list-before-open-callback-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..51e941a58eb68acbbbe1191102442db3ad11760a --- /dev/null +++ b/packages/renderer/src/callback-handler/combo-list-before-open-callback-handler.ts @@ -0,0 +1,36 @@ +import { Module } from '@farris/devkit-vue'; +import { CallbackHandler } from './callback-handler'; + +/** + * 弹窗关闭前回调 + */ +export class LoadComboListDataCallbackHandler extends CallbackHandler { + public schemaType: string | null = 'combo-list'; + /** + * 回调类型 + */ + public callbackType: string | null = 'beforeOpen'; + + /** + * 构造函数 + */ + constructor(module: Module) { + super(module); + } + + /** + * 处理回调 + */ + public handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { + const schema = viewSchema; + const { beforeOpen } = schema; + const defaultReturnValue = true; + + if (!beforeOpen) { + return defaultReturnValue; + } + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload: args[0], schema, token, type: schemaType, payloads: args }; + return this.handleCallback(type, callbackArgs, beforeOpen, defaultReturnValue); + } +} diff --git a/packages/renderer/src/callback-handler/dict-picked-callback-handler.ts b/packages/renderer/src/callback-handler/dict-picked-callback-handler.ts index 3e1aa5503235a4744866790f54339d714c555b61..9c95b9be781ade29a24e8e74ba1812a1b9d26233 100644 --- a/packages/renderer/src/callback-handler/dict-picked-callback-handler.ts +++ b/packages/renderer/src/callback-handler/dict-picked-callback-handler.ts @@ -5,7 +5,7 @@ import { CallbackHandler } from './callback-handler'; * 帮助后回调 */ export class DictPickedCallbackHandler extends CallbackHandler { - + public schemaType: string | null = 'lookup'; /** * 回调类型 */ @@ -21,17 +21,16 @@ export class DictPickedCallbackHandler extends CallbackHandler { /** * 处理回调 */ - public handle(type: string, args: any[]): undefined | boolean | Promise { + public handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { const payload = args[0]; - const viewSchema = args[1]; const { dictPicked } = viewSchema; const defaultReturnValue = payload.data; if (!dictPicked) { return defaultReturnValue; } - - const callbackArgs = { payload, schema: viewSchema }; + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload, schema: viewSchema, token, type: schemaType, payloads: args }; return this.handleCallback(type, callbackArgs, dictPicked, defaultReturnValue); } } diff --git a/packages/renderer/src/callback-handler/dict-picking-callback-handler.ts b/packages/renderer/src/callback-handler/dict-picking-callback-handler.ts index 1af10496b68f43d6747f80d08908e63323d5dc4f..4041ef68e39d5d04f561746c5a986e97f99ddfbf 100644 --- a/packages/renderer/src/callback-handler/dict-picking-callback-handler.ts +++ b/packages/renderer/src/callback-handler/dict-picking-callback-handler.ts @@ -6,7 +6,7 @@ import { FormNotifyService } from "@farris/command-services-vue"; * 帮助前回调 */ export class DictPickingCallbackHandler extends CallbackHandler { - + public schemaType: string | null = 'lookup'; /** * 回调类型 */ @@ -27,9 +27,8 @@ export class DictPickingCallbackHandler extends CallbackHandler { /** * 处理回调 */ - public handle(type: string, args: any[]): undefined | boolean | Promise { + public handle(type: string, viewSchema: Record, args: any[]): undefined | boolean | Promise { const payload = args[0]; - const viewSchema = args[1]; const { dictPicking, dictPickingExpressionId } = viewSchema; const defaultReturnValue = true; @@ -53,9 +52,9 @@ export class DictPickingCallbackHandler extends CallbackHandler { } if (dictPicking) { - // 冗余了一个viewSchema,兼容老结构 - const callbackArgs = { payload, schema: viewSchema, viewSchema }; + const { id: token, type: schemaType } = viewSchema; + const callbackArgs = { payload, schema: viewSchema, viewSchema, token, type: schemaType, payloads: args }; return this.handleCallback(type, callbackArgs, dictPicking, defaultReturnValue); } diff --git a/packages/renderer/src/callback-handler/index.ts b/packages/renderer/src/callback-handler/index.ts index f7c47b36cf3243562a93b4249091aeb70d8209f7..3d3e4b85ec1cbda5396e061a1ba07d514db48599 100644 --- a/packages/renderer/src/callback-handler/index.ts +++ b/packages/renderer/src/callback-handler/index.ts @@ -7,4 +7,6 @@ export * from './before-select-data-callback-handler'; export * from './dict-picked-callback-handler'; export * from './before-close-modal-callback-handler'; export * from './callback-handler-registry'; +export * from './before-close-drawer-callback-handler'; +export * from './combo-list-before-open-callback-handler'; export * from './providers'; diff --git a/packages/renderer/src/callback-handler/providers.ts b/packages/renderer/src/callback-handler/providers.ts index 49a866e20dc9043b20ed5e50d9b9985e6f4b3f83..4986640bc9820e681eb52682cbeeddd2e45a70fe 100644 --- a/packages/renderer/src/callback-handler/providers.ts +++ b/packages/renderer/src/callback-handler/providers.ts @@ -1,6 +1,7 @@ import { ExpressionEvaluator, ExpressionRegistry, Injector, Module, StaticProvider } from "@farris/devkit-vue"; import { CALLBACK_HANDLER_TOKEN } from "../tokens"; import { BeforeEditCallCallbackHandler } from "./before-edit-cell-callback-handler"; +import { BeforeUpdateDataGridCallCallbackHandler } from './before-update-data-grid-callback-handler'; import { CallbackHandlerRegistry } from "./callback-handler-registry"; import { FormMetadataService } from "../service"; import { DictPickingCallbackHandler } from "./dict-picking-callback-handler"; @@ -8,7 +9,9 @@ import { BeforeLoadDataCallbackHandler } from "./before-load-data-callback-handl import { BeforeSelectDataCallbackHandler } from "./before-select-data-callback-handler"; import { DictPickedCallbackHandler } from "./dict-picked-callback-handler"; import { BeforeCloseModalCallbackHandler } from './before-close-modal-callback-handler'; +import { LoadComboListDataCallbackHandler } from './combo-list-before-open-callback-handler'; import { FormNotifyService } from "@farris/command-services-vue"; +import { BeforeCloseDrawerCallbackHandler } from "./before-close-drawer-callback-handler"; export const callbackHandlerProviders: StaticProvider[] = [ { provide: CALLBACK_HANDLER_TOKEN, useClass: BeforeEditCallCallbackHandler, deps: [Module, FormMetadataService, ExpressionEvaluator], multi: true }, @@ -17,5 +20,8 @@ export const callbackHandlerProviders: StaticProvider[] = [ { provide: CALLBACK_HANDLER_TOKEN, useClass: BeforeSelectDataCallbackHandler, deps: [Module], multi: true }, { provide: CALLBACK_HANDLER_TOKEN, useClass: DictPickedCallbackHandler, deps: [Module], multi: true }, { provide: CALLBACK_HANDLER_TOKEN, useClass: BeforeCloseModalCallbackHandler, deps: [Module], multi: true }, + { provide: CALLBACK_HANDLER_TOKEN, useClass: LoadComboListDataCallbackHandler, deps: [Module], multi: true }, + { provide: CALLBACK_HANDLER_TOKEN, useClass: BeforeCloseDrawerCallbackHandler, deps: [Module], multi: true }, + { provide: CALLBACK_HANDLER_TOKEN, useClass: BeforeUpdateDataGridCallCallbackHandler, deps: [Module], multi: true }, { provide: CallbackHandlerRegistry, useClass: CallbackHandlerRegistry, deps: [Injector] } ]; diff --git a/packages/renderer/src/component-config-dependency-resolver/button-component-config-dependency-resolver.ts b/packages/renderer/src/component-config-dependency-resolver/button-component-config-dependency-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..797c70216a598875f278426b1e11a73afb4ba552 --- /dev/null +++ b/packages/renderer/src/component-config-dependency-resolver/button-component-config-dependency-resolver.ts @@ -0,0 +1,32 @@ +import { isNil } from "lodash-es"; +import { Configuration, ConfigurationType } from "../config"; +import { ConfigDependencyResolveService } from "../config-dependency-resolver"; +import { ComponentConfigDependencyResolver } from "./component-config-dependency-resolver"; + +export class ButtonComponentConfigDependencyResolver extends ComponentConfigDependencyResolver { + + constructor(private configDepencencyResolveService: ConfigDependencyResolveService) { + super(); + } + + public resolve(schema: Record): Configuration[] | null { + if (!schema || schema.type !== 'button') { + return null; + } + const { visible, disabled } = schema; + const configs: Configuration[] = []; + if (!isNil(visible)) { + const visibleConfigDeps = this.configDepencencyResolveService.resolve(visible, schema); + if (visibleConfigDeps) { + configs.push({ deps: visibleConfigDeps, config: visible, path: '/visible', type: ConfigurationType.Visible }); + } + } + if (!isNil(disabled)) { + const disabledConfigDeps = this.configDepencencyResolveService.resolve(disabled, schema); + if (disabledConfigDeps) { + configs.push({ deps: disabledConfigDeps, config: disabled, path: '/disabled', type: ConfigurationType.Disabled }); + } + } + return configs; + } +} diff --git a/packages/renderer/src/component-config-dependency-resolver/calendar-component-config-dependency-resolver.ts b/packages/renderer/src/component-config-dependency-resolver/calendar-component-config-dependency-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..d4e49f5630c7589003f9adac7cf1b5bb69153109 --- /dev/null +++ b/packages/renderer/src/component-config-dependency-resolver/calendar-component-config-dependency-resolver.ts @@ -0,0 +1,28 @@ +import { isNil } from "lodash-es"; +import { Configuration, ConfigurationType } from "../config"; +import { ConfigDependencyResolveService } from "../config-dependency-resolver"; +import { ComponentConfigDependencyResolver } from "./component-config-dependency-resolver"; + +export class CalendarComponentConfigDependencyResolver extends ComponentConfigDependencyResolver { + + constructor(private configDepencencyResolveService: ConfigDependencyResolveService) { + super(); + } + + public resolve(schema: Record): Configuration[] | null { + if (!schema || schema.type !== 'calendar') { + return null; + } + const configs: Configuration[] = []; + const { visible, activeDate } = schema; + const visibleConfigDeps = this.configDepencencyResolveService.resolve(visible, schema); + if (visibleConfigDeps) { + configs.push({ deps: visibleConfigDeps, config: visible, path: '/visible', type: ConfigurationType.Visible }); + } + const activeDateConfigDeps = this.configDepencencyResolveService.resolve(activeDate, schema); + if (activeDateConfigDeps) { + configs.push({ deps: activeDateConfigDeps, config: visible, path: '/activeDate', type: ConfigurationType.ActiveDate }); + } + return configs; + } +} diff --git a/packages/renderer/src/component-config-dependency-resolver/drawer-component-config-dependency-resolver.ts b/packages/renderer/src/component-config-dependency-resolver/drawer-component-config-dependency-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..e41111e54dd8ac0f8ddf4436aab0a8bf80bc5c9c --- /dev/null +++ b/packages/renderer/src/component-config-dependency-resolver/drawer-component-config-dependency-resolver.ts @@ -0,0 +1,72 @@ +import { isNil } from "lodash-es"; +import { Configuration, ConfigurationType } from "../config"; +import { ConfigDependencyResolveService } from "../config-dependency-resolver"; +import { ComponentConfigDependencyResolver } from "./component-config-dependency-resolver"; + +export class DrawerComponentConfigDependencyResolver extends ComponentConfigDependencyResolver { + constructor(private configDepencencyResolveService: ConfigDependencyResolveService) { + super(); + } + + public resolve(schema: Record): Configuration[] | null { + if (!schema || schema.type !== 'drawer') { + return null; + } + const { footerToolbar, headerToolbar, id } = schema; + const { visible: headerToolbarVisible } = headerToolbar; + const { visible: footerToolbarVisible } = footerToolbar; + const headerButtons = headerToolbar && headerToolbar.buttons; + const footerButtons = footerToolbar && footerToolbar.buttons; + const buttons = ([] as any[]).concat(headerButtons || []).concat(footerButtons || []); + const configs: Configuration[] = []; + if (!isNil(headerToolbarVisible)) { + const headerToolbarVisibleConfigDeps = this.configDepencencyResolveService.resolve(headerToolbarVisible, schema); + if (headerToolbarVisibleConfigDeps) { + configs.push({ deps: headerToolbarVisibleConfigDeps, config: headerToolbarVisible, path: '/headerToolbar/visible', type: ConfigurationType.Visible }); + } + } + if (!isNil(footerToolbarVisible)) { + const footerToolbarVisibleConfigDeps = this.configDepencencyResolveService.resolve(footerToolbarVisible, schema); + if (footerToolbarVisibleConfigDeps) { + configs.push({ deps: footerToolbarVisibleConfigDeps, config: footerToolbarVisible, path: '/footerToolbar/visible', type: ConfigurationType.Visible }); + } + } + if (!buttons || !Array.isArray(buttons) || buttons.length < 1) { + return configs; + } + headerButtons.forEach((button: Record) => { + this.resolveButtonConfig(schema, button, '/headerToolbar').forEach(config => configs.push(config)); + }); + footerButtons.forEach((button: Record) => { + this.resolveButtonConfig(schema, button, '/footerToolbar').forEach(config => configs.push(config)); + }); + + return configs; + } + private resolveButtonConfig(schema: Record, button: Record, pathPrefix: string): Configuration[] { + const configs: Configuration[] = []; + const { disabled, visible, children } = button; + const diabledConfigDeps = this.configDepencencyResolveService.resolve(disabled, schema); + if (diabledConfigDeps) { + configs.push({ deps: diabledConfigDeps, config: disabled, path: `${pathPrefix}/buttons/${button.id}:disabled`, type: ConfigurationType.Disabled }); + } + if (!isNil(visible)) { + const visibleConfigDeps = this.configDepencencyResolveService.resolve(visible, schema); + if (visibleConfigDeps) { + configs.push({ deps: visibleConfigDeps, config: visible, path: `${pathPrefix}/buttons/${button.id}:visible`, type: ConfigurationType.Visible }); + } + } + children?.forEach((item: Record) => { + const { disabled, visible, id: itemId } = item; + const diabledConfigDeps = this.configDepencencyResolveService.resolve(disabled, schema); + if (diabledConfigDeps) { + configs.push({ deps: diabledConfigDeps, config: disabled, path: `${pathPrefix}/buttons/${button.id}:children/${itemId}:disabled`, type: ConfigurationType.Disabled }); + } + const visibleConfigDeps = this.configDepencencyResolveService.resolve(visible, schema); + if (visibleConfigDeps) { + configs.push({ deps: visibleConfigDeps, config: visible, path: `${pathPrefix}/buttons/${button.id}:children/${itemId}:visible`, type: ConfigurationType.Visible }); + } + }); + return configs; + } +} diff --git a/packages/renderer/src/component-config-dependency-resolver/index.ts b/packages/renderer/src/component-config-dependency-resolver/index.ts index 281d16d4c0dbd46ede5adecd2bee8a84b727355a..110c111ea289fd4e281429d2496d9c1b766803ac 100644 --- a/packages/renderer/src/component-config-dependency-resolver/index.ts +++ b/packages/renderer/src/component-config-dependency-resolver/index.ts @@ -13,4 +13,7 @@ export * from './content-container-component-config-dependency-resolver'; export * from './response-form-component-config-dependency-resolver'; export * from './tabs-component-config-dependency-resolver'; export * from './filter-bar-component-config-dependency-resolver'; +export * from './drawer-component-config-dependency-resolver'; +export * from './calendar-component-config-dependency-resolver'; +export * from './button-component-config-dependency-resolver'; export * from './providers'; diff --git a/packages/renderer/src/component-config-dependency-resolver/providers.ts b/packages/renderer/src/component-config-dependency-resolver/providers.ts index 89266682d4196a04ca767257ff2c6ff304a6d419..863482ed3cab239581af1f5e5df844de845ebfed 100644 --- a/packages/renderer/src/component-config-dependency-resolver/providers.ts +++ b/packages/renderer/src/component-config-dependency-resolver/providers.ts @@ -15,6 +15,11 @@ import { ContentContainerComponentConfigDependencyResolver } from "./content-con import { ResponseFormComponentConfigDependencyResolver } from "./response-form-component-config-dependency-resolver"; import { TabsComponentConfigDependencyResolver } from "./tabs-component-config-dependency-resolver"; import { FilterBarComponentConfigDependencyResolver } from "./filter-bar-component-config-dependency-resolver"; +import { DrawerComponentConfigDependencyResolver } from "./drawer-component-config-dependency-resolver"; +import { CalendarComponentConfigDependencyResolver } from "./calendar-component-config-dependency-resolver"; + +import { ButtonComponentConfigDependencyResolver } from "./button-component-config-dependency-resolver"; + export const componentConfigDependencyResolverProviders: StaticProvider[] = [ { provide: COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN, useClass: PageHeaderComponentConfigDependencyResolver, deps: [ConfigDependencyResolveService], multi: true }, @@ -29,6 +34,9 @@ export const componentConfigDependencyResolverProviders: StaticProvider[] = [ { provide: COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN, useClass: ResponseFormComponentConfigDependencyResolver, deps: [ConfigDependencyResolveService], multi: true}, { provide: COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN, useClass: TabsComponentConfigDependencyResolver, deps: [ConfigDependencyResolveService], multi: true}, { provide: COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN, useClass: FilterBarComponentConfigDependencyResolver, deps: [ConfigDependencyResolveService], multi: true}, + { provide: COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN, useClass: DrawerComponentConfigDependencyResolver, deps: [ConfigDependencyResolveService], multi: true}, + { provide: COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN, useClass: CalendarComponentConfigDependencyResolver, deps: [ConfigDependencyResolveService], multi: true}, + { provide: COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN, useClass: ButtonComponentConfigDependencyResolver, deps: [ConfigDependencyResolveService], multi: true}, { provide: ComponentConfigDependencyResolverRegistry, useClass: ComponentConfigDependencyResolverRegistry, deps: [Injector] }, { provide: ComponentConfigDependencyResolveService, useClass: ComponentConfigDependencyResolveService, deps: [ComponentConfigDependencyResolverRegistry] } ]; diff --git a/packages/renderer/src/component-config-resolver/button-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/button-component-config-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..c15168fa0c54374e07d21f53817173e27a56b120 --- /dev/null +++ b/packages/renderer/src/component-config-resolver/button-component-config-resolver.ts @@ -0,0 +1,28 @@ +import { ComponentConfigResolver } from "./component-config-resolver"; +import { ConfigResolver } from "../config"; +import { isNil } from "lodash-es"; +import { GlobalTranslate } from "../i18n"; + +export class ButtonComponentConfigResolver extends ComponentConfigResolver { + public type: string = 'button'; + + constructor( + private configResolver: ConfigResolver, + private translate: GlobalTranslate, + private formMetadataId: string + ) { + super(); + } + + public resolve(metadata: Record): Record { + const { visible, disabled, id} = metadata; + if (!isNil(visible)) { + metadata.visible = this.configResolver.resolve(visible, id); + } + if (!isNil(disabled)) { + metadata.disabled = this.configResolver.resolve(disabled, id); + } + + return metadata; + } +} diff --git a/packages/renderer/src/component-config-resolver/calendar-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/calendar-component-config-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..f32b0d4aa3e6026a3b8472299f0dd618f2a2bbb9 --- /dev/null +++ b/packages/renderer/src/component-config-resolver/calendar-component-config-resolver.ts @@ -0,0 +1,30 @@ +import { FormMetadataService } from "../service"; +import { ComponentConfigResolver } from "./component-config-resolver"; +import { ConfigResolver } from "../config"; +import { isNil } from "lodash-es"; +import { LanguageListManager, GlobalTranslate } from "../i18n"; + +export class CalendarComponentConfigResolver extends ComponentConfigResolver { + public type: string = 'calendar'; + + constructor(private formMetadataService: FormMetadataService, private configResolver: ConfigResolver, private languageListManager: LanguageListManager, private translate: GlobalTranslate, private formMetadataId: string) { + super(); + } + + public resolve(metadata: Record): Record { + const { activeDate, visible, id, eventItemFormatter, customEventStyles } = metadata; + if (!isNil(visible)) { + metadata.visible = this.configResolver.resolve(visible, id); + } + if (!isNil(activeDate)) { + metadata.activeDate = this.configResolver.resolve(activeDate, id); + } + if (!isNil(eventItemFormatter) && typeof eventItemFormatter === 'string' && eventItemFormatter.trim().length > 0) { + metadata.eventItemFormatter = (data: any, dateHelper: any) => new Function(`return ${eventItemFormatter.trim()}`)()(data, dateHelper); + } + if (!isNil(customEventStyles) && typeof customEventStyles === 'string' && customEventStyles.trim().length > 0) { + metadata.customEventStyles = (data: any, dateHelper: any) => new Function(`return ${customEventStyles.trim()}`)()(data, dateHelper); + } + return metadata; + } +} diff --git a/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts index 7e069da0cfabcfe83573079fe7ed04d0a983744d..d4eba54a1cb3e6b32b3443e1a7e3d51d23598a59 100644 --- a/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts +++ b/packages/renderer/src/component-config-resolver/data-grid-component-config-resolver.ts @@ -6,6 +6,7 @@ import { ConfigResolver } from "../config"; import { TemplateTransformService } from "../template-transformer"; import { useEvent } from '../event/index'; import { ComponentConfigResolver } from "./component-config-resolver"; +import { useCommandResolver } from "../composition"; export class DataGridComponentConfigResolver extends ComponentConfigResolver { public type: string = 'data-grid'; @@ -43,7 +44,7 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { if (customRowStyle.trim().length < 1) { return {}; } - return new Function(`return ${customRowStyle}`)()(rowData); + return new Function(`return ${customRowStyle.trim()}`)()(rowData); } catch (e) { console.warn(e); @@ -57,7 +58,7 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { if (customCellStyle.trim().length < 1) { return {}; } - return new Function(`return ${customCellStyle}`)()(cell); + return new Function(`return ${customCellStyle.trim()}`)()(cell); } catch (e) { console.warn(e); @@ -74,7 +75,9 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { if (!isNil(visible)) { column.visible = this.configResolver.resolve(visible, id); } + this.resolveDatepickerColumn(metadata, column); this.resolveLookupColumn(metadata, column); + this.resolveComboListColumn(metadata, column); if (!editor) { return; } @@ -109,8 +112,30 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { editor.languages = languageList; } } + + private resolveDatepickerColumn(viewSchema: Record, column: Record) { + const { editor, onDatePicked } = column; + if (!editor || editor.type !== 'date-picker') { + return; + } + + const { id } = viewSchema; + const relatedComponent = this.formMetadataService.getRelatedComponent(id); + const viewModel = this.viewModel.getModule().getViewModel(relatedComponent.id); + + if (onDatePicked) { + editor.onDatePicked = (payload: any) => { + if (!viewModel) { + return; + } + (viewModel as any)[onDatePicked]({ payload, schema: viewSchema }); + }; + + } + } + private resolveLookupColumn(viewSchema: Record, column: Record) { - const { editor, dictPicking, clear, beforeLoadData, beforeSelectData, dictPicked } = column; + const { editor, dictPicking, clear, beforeLoadData, beforeSelectData, dictPicked, load } = column; if (!editor || editor.type !== 'lookup') { return; } @@ -122,22 +147,63 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { const relatedComponent = this.formMetadataService.getRelatedComponent(id); const viewModel = this.viewModel.getModule().getViewModel(relatedComponent.id); if (dictPicking) { - editor.dictPicking = (payload: any) => (viewModel as any)[dictPicking]({ payload, schema: viewSchema }); + const dictPickingEventHandler = this.buildEventHandler(relatedComponent.id, dictPicking, viewSchema); + editor.dictPicking = dictPickingEventHandler; } if (clear) { - editor.onClear = (payload: any) => (viewModel as any)[clear]({ payload, schema: viewSchema }); + const clearEventHandler = this.buildEventHandler(relatedComponent.id, clear, viewSchema, 'clear'); + editor.onClear = clearEventHandler; } if (beforeLoadData) { - editor.beforeLoadData = (payload: any) => (viewModel as any)[beforeLoadData]({ payload, schema: viewSchema }); + const beforeLoadDataEventHandler = this.buildEventHandler(relatedComponent.id, beforeLoadData, viewSchema); + editor.beforeLoadData = beforeLoadDataEventHandler; } if (beforeSelectData) { - editor.beforeSelectData = (payload: any) => (viewModel as any)[beforeSelectData]({ payload, schema: viewSchema }); + const beforeSelectDataEventHandler = this.buildEventHandler(relatedComponent.id, beforeSelectData, viewSchema); + editor.beforeSelectData = beforeSelectDataEventHandler; } if (dictPicked) { - editor.dictPicked = (payload: any) => (viewModel as any)[dictPicked]({ payload, schema: viewSchema }); + const dictPickedEventHandler = this.buildEventHandler(relatedComponent.id, dictPicked, viewSchema); + editor.dictPicked = dictPickedEventHandler; } } + private resolveComboListColumn(viewSchema: Record, column: Record) { + const { beforeOpen, onChange, onClear, editor } = column; + if (!editor || editor.type !== 'combo-list') { + return; + } + const { id } = viewSchema; + const relatedComponent = this.formMetadataService.getRelatedComponent(id); + const viewModel = this.viewModel.getModule().getViewModel(relatedComponent.id); + if (beforeOpen) { + const beforeOpenEventHandler = this.buildEventHandler(relatedComponent.id, beforeOpen, viewSchema); + editor.beforeOpen = beforeOpenEventHandler; + } + if (onChange) { + const onChangeEventHandler = this.buildEventHandler(relatedComponent.id, onChange, viewSchema, 'change'); + editor.onChange = onChangeEventHandler; + } + if (onClear) { + const onClearEventHandler = this.buildEventHandler(relatedComponent.id, onClear, viewSchema, 'clear'); + editor.onClear = onClearEventHandler; + } + } + private buildEventHandler(viewModelId: string, handlerName: string, schema: Record, eventName?: string) { + const commandResolver = useCommandResolver(this.formMetadataService); + const { commandName, commandViewModelId } = commandResolver.resolve(handlerName); + const targetViewModelId = commandViewModelId || viewModelId; + const targetViewModel = this.viewModel.getModule().getViewModel(targetViewModelId); + return (...payloads: any[]) => { + const payload = Array.isArray(payloads) ? payloads[0] : payloads; + const { id: token, type } = schema; + const param: Record = { payload, schema, token, type, payloads }; + if (eventName) { + param.name = eventName; + } + return (targetViewModel as any)[commandName](param); + }; + } /** * 为列操作构造处理方法 */ @@ -163,6 +229,7 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { // 编辑按钮 commandSchema.onClickEditCommand = this.buildColumnCommandHandler('clickEditCommand', commandSchema, gridComponentSchema, viewModel); commandSchema.onClickDeleteCommand = this.buildColumnCommandHandler('clickDeleteCommand', commandSchema, gridComponentSchema, viewModel); + commandSchema.onHandleAction = this.buildColumnCommandHandler('handleAction', commandSchema, gridComponentSchema, viewModel); } /** @@ -173,14 +240,14 @@ export class DataGridComponentConfigResolver extends ComponentConfigResolver { const module = viewModel.getModule(); const { handleEvent } = useEvent(module); - return (cell: any, row: any) => { + return (cell: any, row: any, command: any, payload: any) => { // 为了兼容老格式,冗余cell和rowData const event = { token: gridComponentSchema.id, name: eventType, type: gridComponentSchema.type, - payloads: { cell, rowData: row.raw }, + payloads: { cell, rowData: row.raw, originalEvent: payload, command }, schema: gridComponentSchema, cell: cell, rowData: row.raw diff --git a/packages/renderer/src/component-config-resolver/drawer-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/drawer-component-config-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..fcb8b2d9e4daffa249cca416f0591eebfb007101 --- /dev/null +++ b/packages/renderer/src/component-config-resolver/drawer-component-config-resolver.ts @@ -0,0 +1,50 @@ +import { isNil } from "lodash-es"; +import { ConfigResolver } from "../config"; +import { GlobalTranslate } from "../i18n"; +import { FormMetadataService } from "../service"; +import { ComponentConfigResolver } from "./component-config-resolver"; + +export class DrawerComponentConfigResolver extends ComponentConfigResolver { + public type: string = 'drawer'; + + constructor(private formMetadataService: FormMetadataService, private configResolver: ConfigResolver, private translate: GlobalTranslate, private formMetadataId: string) { + super(); + } + + public resolve(metadata: Record): Record { + const { footerToolbar, headerToolbar, id } = metadata; + const { visible: headerToolbarVisible } = headerToolbar; + const { visible: footerToolbarVisible } = footerToolbar; + const headerButtons = headerToolbar && headerToolbar.buttons; + const footerButtons = footerToolbar && footerToolbar.buttons; + const buttons = ([] as any[]).concat(headerButtons || []).concat(footerButtons || []); + + if (!isNil(headerToolbarVisible)) { + headerToolbar.visible = this.configResolver.resolve(headerToolbarVisible, id); + } + if (!isNil(footerToolbarVisible)) { + footerToolbar.visible = this.configResolver.resolve(footerToolbarVisible, id); + } + if (!buttons || !Array.isArray(buttons) || buttons.length < 1) { + return metadata; + } + buttons.forEach((button: Record) => { + const { disabled, visible, children } = button; + button.disabled = this.configResolver.resolve(disabled, id); + if (!isNil(visible)) { + button.visible = this.configResolver.resolve(visible, id); + } + if (!(children && children.length)) { + return; + } + children.forEach((item: Record) => { + const { disabled, visible } = item; + item.disabled = this.configResolver.resolve(disabled, id); + if (!isNil(visible)) { + item.visible = this.configResolver.resolve(visible, id); + } + }); + }); + return metadata; + } +} diff --git a/packages/renderer/src/component-config-resolver/filter-bar-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/filter-bar-component-config-resolver.ts index 44a776c78c0c80db41df145effd98e5184b682e8..e02c8d5054099d56d2c90ea141ddac7a8cb58cea 100644 --- a/packages/renderer/src/component-config-resolver/filter-bar-component-config-resolver.ts +++ b/packages/renderer/src/component-config-resolver/filter-bar-component-config-resolver.ts @@ -1,22 +1,100 @@ import { ConfigResolver } from "../config"; import { ComponentConfigResolver } from "./component-config-resolver"; import { GlobalTranslate } from "../i18n"; -import { isNil } from "lodash-es"; +import { FormMetadataService } from "../service"; +import { ViewModel, ViewModelState } from "@farris/devkit-vue"; +import { useCommandResolver } from "../composition"; export class FilterBarComponentConfigResolver extends ComponentConfigResolver { public type: string = 'filter-bar'; constructor( private configResolver: ConfigResolver, - private translate: GlobalTranslate, - private formMetadataId: string + private translate: GlobalTranslate, + private formMetadataId: string, + private formMetadataService: FormMetadataService, + private viewModel: ViewModel, ) { super(); } public resolve(schema: Record): Record { - const { defaultValues, id } = schema; + const { defaultValues, id, fields } = schema; if (defaultValues && Object.keys(defaultValues).length > 0) { schema.defaultValues = this.configResolver.resolve(defaultValues, id); } + if (!fields || !Array.isArray(fields) || fields.length < 1) { + return schema; + } + + fields.forEach((field: Record) => { + this.resolveLookup(schema, field); + this.resolveComboList(schema, field); + + }); return schema; } + private resolveLookup(viewSchema: Record, field: Record) { + const { editor } = field; + if (!editor || editor.type !== 'lookup') { + return; + } + const { dictPicking, dictPicked, clear, beforeSelectData, beforeLoadData } = editor; + const { id } = viewSchema; + const relatedComponent = this.formMetadataService.getRelatedComponent(id); + if (dictPicking) { + const dictPickingEventHandler = this.buildEventHandler(relatedComponent.id, dictPicking, viewSchema); + editor.dictPicking = dictPickingEventHandler; + } + if (dictPicked) { + const dictPickedEventHandler = this.buildEventHandler(relatedComponent.id, dictPicked, viewSchema); + editor.dictPicked = dictPickedEventHandler; + } + if (clear) { + const clearEventHandler = this.buildEventHandler(relatedComponent.id, clear, viewSchema, 'clear'); + editor.onClear = clearEventHandler; + } + if (beforeSelectData) { + const beforeSelectDataEventHandler = this.buildEventHandler(relatedComponent.id, beforeSelectData, viewSchema); + editor.beforeSelectData = beforeSelectDataEventHandler; + } + if (beforeLoadData) { + const beforeLoadDataEventHandler = this.buildEventHandler(relatedComponent.id, beforeLoadData, viewSchema); + editor.beforeLoadData = beforeLoadDataEventHandler; + } + } + private resolveComboList(viewSchema: Record, field: Record) { + const { editor } = field; + if (!editor || editor.type !== 'combo-list') { + return; + } + const { onChange, onClear, beforeOpen } = editor; + const { id } = viewSchema; + const relatedComponent = this.formMetadataService.getRelatedComponent(id); + if (onChange) { + const onChangeEventHandler = this.buildEventHandler(relatedComponent.id, onChange, viewSchema, 'change'); + editor.onChange = onChangeEventHandler; + } + if (onClear) { + const onClearEventHandler = this.buildEventHandler(relatedComponent.id, onClear, viewSchema, 'clear'); + editor.onClear = onClearEventHandler; + } + if (beforeOpen) { + const beforeOpenEventHandler = this.buildEventHandler(relatedComponent.id, beforeOpen, viewSchema); + editor.beforeOpen = beforeOpenEventHandler; + } + } + private buildEventHandler(viewModelId: string, handlerName: string, schema: Record, eventName?: string) { + const commandResolver = useCommandResolver(this.formMetadataService); + const { commandName, commandViewModelId } = commandResolver.resolve(handlerName); + const targetViewModelId = commandViewModelId || viewModelId; + const targetViewModel = this.viewModel.getModule().getViewModel(targetViewModelId); + return (...payloads: any[]) => { + const payload = Array.isArray(payloads) ? payloads[0] : payloads; + const { id: token, type } = schema; + const param: Record = { payload, schema, token, type, payloads }; + if (eventName) { + param.name = eventName; + } + return (targetViewModel as any)[commandName](param); + }; + } } diff --git a/packages/renderer/src/component-config-resolver/index.ts b/packages/renderer/src/component-config-resolver/index.ts index d2f12e218f983cd04ec18a0060a99fce31e7ce1c..c550c304fad26b02d8639c72234407124f2d8c18 100644 --- a/packages/renderer/src/component-config-resolver/index.ts +++ b/packages/renderer/src/component-config-resolver/index.ts @@ -14,4 +14,7 @@ export * from './response-form-component-config-resolver'; export * from './tabs-component-config-resolver'; export * from './list-view-component-config-resolver'; export * from './filter-bar-component-config-resolver'; +export * from './drawer-component-config-resolver'; +export * from './calendar-component-config-resolver'; +export * from './button-component-config-resolver'; export * from './providers'; diff --git a/packages/renderer/src/component-config-resolver/providers.ts b/packages/renderer/src/component-config-resolver/providers.ts index 9e8c4d13e9ac87352e86370169e412a38c7a6c7c..afdfc6c2b98eba82874108837292f9c648ede7e0 100644 --- a/packages/renderer/src/component-config-resolver/providers.ts +++ b/packages/renderer/src/component-config-resolver/providers.ts @@ -19,6 +19,9 @@ import { TemplateTransformService } from "../template-transformer"; import { LanguageListManager, GlobalTranslate } from "../i18n"; import { ListViewComponentConfigResolver } from "./list-view-component-config-resolver"; import { FilterBarComponentConfigResolver } from "./filter-bar-component-config-resolver"; +import { DrawerComponentConfigResolver } from "./drawer-component-config-resolver"; +import { CalendarComponentConfigResolver } from "./calendar-component-config-resolver"; +import { ButtonComponentConfigResolver } from "./button-component-config-resolver"; export const componentConfigResolverProviders: StaticProvider[] = [ { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: FormGroupComponentConfigResolver, deps: [FormMetadataService, ConfigResolver, LanguageListManager, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, @@ -28,12 +31,15 @@ export const componentConfigResolverProviders: StaticProvider[] = [ { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: ResponseToolbarConfigResolver, deps: [FormMetadataService, ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: SectionComponentConfigResolver, deps: [FormMetadataService, ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: TreeGridComponentConfigResolver, deps: [FormMetadataService, ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN, ViewModel], multi: true }, - { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: QuerySolutionComponentConfigResolver, deps: [ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, + { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: QuerySolutionComponentConfigResolver, deps: [ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN, FormMetadataService, ViewModel], multi: true }, { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: ContentContainerComponentConfigResolver, deps: [FormMetadataService, ConfigResolver], multi: true }, { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: ResponseFormComponentConfigResolver, deps: [FormMetadataService, ConfigResolver], multi: true }, { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: TabsComponentConfigResolver, deps: [FormMetadataService, ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: ListViewComponentConfigResolver, deps: [FormMetadataService, ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, - { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: FilterBarComponentConfigResolver, deps: [ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, + { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: FilterBarComponentConfigResolver, deps: [ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN, FormMetadataService, ViewModel], multi: true }, + { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: DrawerComponentConfigResolver, deps: [FormMetadataService, ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, + { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: CalendarComponentConfigResolver, deps: [FormMetadataService, ConfigResolver, LanguageListManager, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, + { provide: COMPONENT_CONFIG_RESOLVER_TOKEN, useClass: ButtonComponentConfigResolver, deps: [ConfigResolver, GlobalTranslate, FORM_METADATA_ID_TOKEN], multi: true }, { provide: ComponentConfigResolverRegistry, useClass: ComponentConfigResolverRegistry, deps: [Injector] }, { provide: ComponentConfigResolveService, useClass: ComponentConfigResolveService, deps: [ComponentConfigResolverRegistry, FormMetadataService] } ]; diff --git a/packages/renderer/src/component-config-resolver/query-solution-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/query-solution-component-config-resolver.ts index 4a493619965556f12ef744a56ddf22d206134547..6309bd3a86445c8c65c3227f484e1c95e0f045a3 100644 --- a/packages/renderer/src/component-config-resolver/query-solution-component-config-resolver.ts +++ b/packages/renderer/src/component-config-resolver/query-solution-component-config-resolver.ts @@ -1,27 +1,99 @@ import { ConfigResolver } from "../config"; import { ComponentConfigResolver } from "./component-config-resolver"; import { GlobalTranslate } from "../i18n"; -import { isNil } from "lodash-es"; +import { FormMetadataService } from "../service"; +import { ViewModel, ViewModelState } from "@farris/devkit-vue"; +import { useCommandResolver } from "../composition"; export class QuerySolutionComponentConfigResolver extends ComponentConfigResolver { public type: string = 'query-solution'; constructor( private configResolver: ConfigResolver, - private translate: GlobalTranslate, private formMetadataId: string + private translate: GlobalTranslate, + private formMetadataId: string, + private formMetadataService: FormMetadataService, + private viewModel: ViewModel, ) { super(); } public resolve(schema: Record): Record { - const { defaultValues, id } = schema; + const { defaultValues, id, fields } = schema; if (defaultValues && Object.keys(defaultValues).length > 0) { schema.defaultValues = this.configResolver.resolve(defaultValues, id); } - // if (presetFields && Array.isArray(presetFields) && presetFields.length > 0) { - // presetFields.forEach((field: Record) => { - // const { id: fieldId, name } = field; - // field.name = this.translate.transform(this.formMetadataId, `${id}/presetFields/${fieldId}/name`, name); - // }); - // } + if (!fields || !Array.isArray(fields) || fields.length < 1) { + return schema; + } + + fields.forEach((field: Record) => { + this.resolveLookup(schema, field); + this.resolveComboList(schema, field); + }); return schema; } + private resolveLookup(viewSchema: Record, field: Record) { + const { editor } = field; + if (!editor || editor.type !== 'lookup') { + return; + } + const { dictPicking, dictPicked, clear, beforeSelectData, beforeLoadData } = editor; + const { id } = viewSchema; + const relatedComponent = this.formMetadataService.getRelatedComponent(id); + if (dictPicking) { + const dictPickingEventHandler = this.buildEventHandler(relatedComponent.id, dictPicking, viewSchema); + editor.dictPicking = dictPickingEventHandler; + } + if (dictPicked) { + const dictPickedEventHandler = this.buildEventHandler(relatedComponent.id, dictPicked, viewSchema); + editor.dictPicked = dictPickedEventHandler; + } + if (clear) { + const clearEventHandler = this.buildEventHandler(relatedComponent.id, clear, viewSchema, 'clear'); + editor.onClear = clearEventHandler; + } + if (beforeSelectData) { + const beforeSelectDataEventHandler = this.buildEventHandler(relatedComponent.id, beforeSelectData, viewSchema); + editor.beforeSelectData = beforeSelectDataEventHandler; + } + if (beforeLoadData) { + const beforeLoadDataEventHandler = this.buildEventHandler(relatedComponent.id, beforeLoadData, viewSchema); + editor.beforeLoadData = beforeLoadDataEventHandler; + } + } + private resolveComboList(viewSchema: Record, field: Record) { + const { editor } = field; + if (!editor || editor.type !== 'combo-list') { + return; + } + const { onChange, onClear, beforeOpen } = editor; + const { id } = viewSchema; + const relatedComponent = this.formMetadataService.getRelatedComponent(id); + if (onChange) { + const onChangeEventHandler = this.buildEventHandler(relatedComponent.id, onChange, viewSchema, 'change'); + editor.onChange = onChangeEventHandler; + } + if (onClear) { + const onClearEventHandler = this.buildEventHandler(relatedComponent.id, onClear, viewSchema, 'clear'); + editor.onClear = onClearEventHandler; + } + if (beforeOpen) { + const beforeOpenEventHandler = this.buildEventHandler(relatedComponent.id, beforeOpen, viewSchema); + editor.beforeOpen = beforeOpenEventHandler; + } + } + private buildEventHandler(viewModelId: string, handlerName: string, schema: Record, eventName?: string) { + const commandResolver = useCommandResolver(this.formMetadataService); + const { commandName, commandViewModelId } = commandResolver.resolve(handlerName); + const targetViewModelId = commandViewModelId || viewModelId; + const targetViewModel = this.viewModel.getModule().getViewModel(targetViewModelId); + return (...payloads: any[]) => { + const payload = Array.isArray(payloads) ? payloads[0] : payloads; + const { id: token, type } = schema; + const param: Record = { payload, schema, token, type, payloads }; + if (eventName) { + param.name = eventName; + } + return (targetViewModel as any)[commandName](param); + }; + } } diff --git a/packages/renderer/src/component-config-resolver/tree-grid-component-config-resolver.ts b/packages/renderer/src/component-config-resolver/tree-grid-component-config-resolver.ts index c758e964a03ad8edb5d12074d0f56a6cf10f8641..685e11b857de02c2e5c01d76b839d42a0aff5257 100644 --- a/packages/renderer/src/component-config-resolver/tree-grid-component-config-resolver.ts +++ b/packages/renderer/src/component-config-resolver/tree-grid-component-config-resolver.ts @@ -78,6 +78,7 @@ export class TreeGridComponentConfigResolver extends ComponentConfigResolver { // 编辑按钮 commandSchema.onClickEditCommand = this.buildColumnCommandHandler('clickEditCommand', commandSchema, schema, viewModel); commandSchema.onClickDeleteCommand = this.buildColumnCommandHandler('clickDeleteCommand', commandSchema, schema, viewModel); + commandSchema.onHandleAction = this.buildColumnCommandHandler('handleAction', commandSchema, schema, viewModel); } /** @@ -88,14 +89,14 @@ export class TreeGridComponentConfigResolver extends ComponentConfigResolver { const module = viewModel.getModule(); const { handleEvent } = useEvent(module); - return (cell: any, row: any) => { + return (cell: any, row: any, command: any, payload: any) => { // 为了兼容老格式,冗余cell和rowData const event = { token: viewSchema.id, name: eventType, type: viewSchema.type, - payloads: { cell, rowData: row.raw }, + payloads: { cell, rowData: row.raw, originalEvent: payload, command }, schema: viewSchema, cell: cell, rowData: row.raw diff --git a/packages/renderer/src/composition/index.ts b/packages/renderer/src/composition/index.ts index 16297064f160caabd6cfc3a4ecb3856b968b6022..e429cea29187283341d230e14544ee997aef9ca9 100644 --- a/packages/renderer/src/composition/index.ts +++ b/packages/renderer/src/composition/index.ts @@ -22,3 +22,7 @@ export * from './use-component-config-dependency-resolver'; export * from './use-custom-component-renders'; export * from './use-translate'; export * from './use-custom-css'; +export * from './use-rich-editor-provide'; +export * from './use-resource'; +export * from './use-command-resolver'; +export * from './use-navigation-guard'; diff --git a/packages/renderer/src/composition/use-command-resolver.ts b/packages/renderer/src/composition/use-command-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ec2c5281507c3bab6fcb5764c93891bd0e40722 --- /dev/null +++ b/packages/renderer/src/composition/use-command-resolver.ts @@ -0,0 +1,21 @@ +import { FormMetadataService } from "../service"; + +export function useCommandResolver(formMetadata: FormMetadataService) { + function resolve(commandBinding: string): { commandName: string, commandViewModelId: string | null; } { + let commandViewModelId: string | null = null; + let commandName: string; + if (commandBinding.indexOf('.') !== -1) { + // 绑定其他视图模型命令:viewModelId.commandName + const commandBindingSegs = commandBinding.split('.'); + commandViewModelId = formMetadata.convertViewModelIdToComponentId(commandBindingSegs[commandBindingSegs.length - 2]); + commandName = commandBindingSegs[commandBindingSegs.length - 1]; + } else { + // 绑定当前视图模型命令:commandName + commandName = commandBinding; + } + return { commandName, commandViewModelId }; + } + return { + resolve + }; +} diff --git a/packages/renderer/src/composition/use-component-providers.ts b/packages/renderer/src/composition/use-component-providers.ts index 3892bfb9fd51051d51019960f78707024a8c54b2..a508e642c07758eb0a7c5deb0ecfbad69d88e5ba 100644 --- a/packages/renderer/src/composition/use-component-providers.ts +++ b/packages/renderer/src/composition/use-component-providers.ts @@ -1,5 +1,5 @@ -import { MESSAGE_BOX_SERVICE_TOKEN, NOTIFY_SERVICE_TOKEN, LOADING_SERVICE_TOKEN, MODAL_SERVICE_TOKEN } from '@farris/command-services-vue'; +import { MESSAGE_BOX_SERVICE_TOKEN, NOTIFY_SERVICE_TOKEN, LOADING_SERVICE_TOKEN, MODAL_SERVICE_TOKEN, VERIFY_DETAIL_SERVICE_TOKEN } from '@farris/command-services-vue'; import { StaticProvider } from '@farris/devkit-vue'; import { inject } from 'vue'; @@ -8,12 +8,14 @@ export function useComponentProviders() { const NotifyService: any = inject('FNotifyService'); const LoadingService = inject('FLoadingService'); const ModalService = inject('FModalService'); + const VerifyDetailService = inject('FVerifyDetailService'); const providers: StaticProvider[] = [ { provide: MESSAGE_BOX_SERVICE_TOKEN, useValue: MessageBoxService }, { provide: NOTIFY_SERVICE_TOKEN, useValue: NotifyService }, { provide: LOADING_SERVICE_TOKEN, useValue: LoadingService }, - { provide: MODAL_SERVICE_TOKEN, useValue: ModalService } + { provide: MODAL_SERVICE_TOKEN, useValue: ModalService }, + { provide: VERIFY_DETAIL_SERVICE_TOKEN, useValue: VerifyDetailService } ]; return { providers diff --git a/packages/renderer/src/composition/use-custom-component-renders.ts b/packages/renderer/src/composition/use-custom-component-renders.ts index 180101610d5ec8d2aefa8df7cc5c3f481f449d80..99be2f5e1f52b7d4c9da7ca5894c8de7354571cb 100644 --- a/packages/renderer/src/composition/use-custom-component-renders.ts +++ b/packages/renderer/src/composition/use-custom-component-renders.ts @@ -4,7 +4,7 @@ import Page from '../page.vue'; /** * 创建外部组件节点 */ -function createExternalComponentVNode(externalComponentSchema: any, externalContainerId: string) { +function createExternalComponentVNode(externalComponentSchema: any, externalContainerId: string, parentSchema?: any) { const externalFormMetadataId = externalComponentSchema.id; if (!externalFormMetadataId) { return null; @@ -16,7 +16,8 @@ function createExternalComponentVNode(externalComponentSchema: any, externalCont formMetadataId: externalFormMetadataId, customComponentRenders: {}, formInitParams: externalFormInitParams, - parentContainerId: externalContainerId + parentContainerId: externalContainerId, + parentComponentType: parentSchema?.type } ); return externalComponentVNode; @@ -25,7 +26,7 @@ function createExternalComponentVNode(externalComponentSchema: any, externalCont /** * 创建外部容器节点 */ -function createExternalContainerVnode(externalContainerSchema: any, externalContainerCtor: any) { +function createExternalContainerVnode(externalContainerSchema: any, externalContainerCtor: any, parentSchema?: any) { // 外部容器属性 const externalContainerProps: any = {}; @@ -37,7 +38,7 @@ function createExternalContainerVnode(externalContainerSchema: any, externalCont // 外部容器子组件 const externalComponentSchema = externalContainerSchema.externalComponent; - const externalComponentVNode = createExternalComponentVNode(externalComponentSchema, externalContainerSchema.id); + const externalComponentVNode = createExternalComponentVNode(externalComponentSchema, externalContainerSchema.id, parentSchema); // 创建外部容器节点 const externalContainerVNode = createVNode(externalContainerCtor, externalContainerProps, [externalComponentVNode]); @@ -49,11 +50,11 @@ function createExternalContainerVnode(externalContainerSchema: any, externalCont * 使用自定义组件渲染器 */ export function useCustomComponentRenderers() { - const renderExternalContainer = (externalContainerSchema: any, externalContainerCtor: any) => { + const renderExternalContainer = (externalContainerSchema: any, externalContainerCtor: any, parentSchema?: any) => { if (!externalContainerSchema || !externalContainerSchema.externalComponent) { return null; } - const externalContainerNode = createExternalContainerVnode(externalContainerSchema, externalContainerCtor); + const externalContainerNode = createExternalContainerVnode(externalContainerSchema, externalContainerCtor, parentSchema); return externalContainerNode; }; diff --git a/packages/renderer/src/composition/use-lookup-provide.ts b/packages/renderer/src/composition/use-lookup-provide.ts index e803607f8bf7ddf5d34c4e94f3391194329f2405..dcc8370d0651286d939634bb88dfdb910ecd7ada 100644 --- a/packages/renderer/src/composition/use-lookup-provide.ts +++ b/packages/renderer/src/composition/use-lookup-provide.ts @@ -1,12 +1,14 @@ import { provide } from "vue"; -import { LookupDataService } from "../component-services"; +import { DynamicScriptService } from "@farris/command-services-vue"; import { Injector, Module } from "@farris/devkit-vue"; -import { F_LOOKUP_HTTP_SERVICE_TOKEN } from "@farris/ui-vue"; import { useFormMetadataService } from "./use-form-metadata-service"; +import { LookupDataService } from "../component-services"; export function useLookupProvide(injector: Injector, module: Module) { const formMetadataService = useFormMetadataService(injector); const frameComponent = formMetadataService.getFrameComponent(); + const dynamicScriptService: any = injector.get(DynamicScriptService); + if (!frameComponent) { return; } @@ -16,5 +18,6 @@ export function useLookupProvide(injector: Injector, module: Module) { return; } const lookupDataService = viewModel.getInjector().get(LookupDataService, undefined); - provide(F_LOOKUP_HTTP_SERVICE_TOKEN, lookupDataService); + const uiModule = dynamicScriptService.getModule().ui; + provide(uiModule['F_LOOKUP_HTTP_SERVICE_TOKEN'], lookupDataService); } diff --git a/packages/renderer/src/composition/use-model-value.ts b/packages/renderer/src/composition/use-model-value.ts index 33bd65460aa88c29584cdab169de8e1424e09f94..3c39fc41c003c6b1209cb0f90f178c7a14711954 100644 --- a/packages/renderer/src/composition/use-model-value.ts +++ b/packages/renderer/src/composition/use-model-value.ts @@ -14,7 +14,9 @@ export function useModelValue(formInjector: Injector, module: Module) { const modelValue: Ref = ref({}); const moduleConfigId = formInjector.get(MODULE_CONFIG_ID_TOKEN); const entityStore = module.getEntityStore(moduleConfigId + ENTITY_STORE_SUFFIX); - + const modelValueResolver = useModelValueResolver(formInjector); + modelValue.value = modelValueResolver.resolve(); + // 监听实体变化,更新modelValue entityStore?.watchChange((change: any) => { const modelValueResolver = useModelValueResolver(formInjector); diff --git a/packages/renderer/src/composition/use-module-config.ts b/packages/renderer/src/composition/use-module-config.ts index 9c8a481fb190700da47706a43c8419693f83e6b6..87bb1e3bf65aaf7ae49e2f11cffae8c323c374d9 100644 --- a/packages/renderer/src/composition/use-module-config.ts +++ b/packages/renderer/src/composition/use-module-config.ts @@ -1,7 +1,7 @@ import { befProviders, befRootProviders } from "@farris/bef-vue"; -import { commandServiceModuleProviders, commandServiceViewModelProviders } from "@farris/command-services-vue"; +import { commandServiceModuleProviders, commandServiceViewModelProviders, VUE_ROUTER_TOKEN } from "@farris/command-services-vue"; import { Injector, ModuleConfig, RENDER_ENGINE_TOKEN, StaticProvider } from "@farris/devkit-vue"; -import { Ref } from "vue"; +import { ref, Ref } from "vue"; import { ModuleConfigBuilder } from "../config-builders"; import { RenderEngineImpl } from "../render-engine"; import { FORM_METADATA_ID_TOKEN, FORM_METADATA_TOKEN, MODULE_CONFIG_ID_TOKEN, RENDER_TOKEN } from "../tokens"; @@ -30,8 +30,11 @@ import { i18nModuleProviders } from "../i18n"; import { CloudPrintServiceProviders } from '@gsp-svc/cloudprint-vue'; import { ChgdrProviders } from '@gsp-cmp/chgdr-vue'; import { componentServiceProviders } from "../component-services"; +import { useRouter } from "vue-router"; +import { pipeProviders } from "../pipes"; -export function useModuleConfig(metadata: Ref, uiProviders: StaticProvider[], render: Ref, parentContainerId: string): ModuleConfig { +export function useModuleConfig(metadata: Ref, uiProviders: StaticProvider[],parentContainerId: string): ModuleConfig { + const render = ref(); const moduleMetaContext: any = { parentContainerId: parentContainerId, form: metadata?.value.form.content, @@ -47,6 +50,7 @@ export function useModuleConfig(metadata: Ref, uiProviders: StaticProvider[], re { provide: MODULE_CONFIG_ID_TOKEN, useValue: metadata.value.form.content.module.code, deps: [] }, { provide: RENDER_TOKEN, useValue: render, deps: [] }, { provide: RENDER_ENGINE_TOKEN, useClass: RenderEngineImpl, deps: [Injector] }, + { provide: VUE_ROUTER_TOKEN, useValue: useRouter(), deps: [] }, ...BifDevkitRootProviders, ...UploadDevkitRootProviders, ...FileViewerDevkitRootProviders, @@ -66,6 +70,7 @@ export function useModuleConfig(metadata: Ref, uiProviders: StaticProvider[], re ...serviceProviders, ...eventHanderProviders, ...resolverProviders, + ...pipeProviders, ...callbackHandlerProviders, ...htmlTemplateTransformers, ...effectorProviders, diff --git a/packages/renderer/src/composition/use-module-creator.ts b/packages/renderer/src/composition/use-module-creator.ts index 8069818733a9f2c02dbc144bb5c5708d31d94169..89cebf3e2fdf2a30f137b0b3fc61aed3b290418b 100644 --- a/packages/renderer/src/composition/use-module-creator.ts +++ b/packages/renderer/src/composition/use-module-creator.ts @@ -1,7 +1,15 @@ -import { createDynamicModule, createViewModel, ModuleConfig } from "@farris/devkit-vue"; +import { createDynamicModule, createViewModel, Devkit, ModuleConfig } from "@farris/devkit-vue"; import { Ref } from "vue"; -export function useModuleCreator(metadata: Ref, moduleConfig: ModuleConfig){ +export function useModuleCreator(metadata: Ref, moduleConfig: ModuleConfig, devkit: Devkit, parentContainerId: string) { + // 非子表单时,如果已存在module,不再重新创建 + if (!parentContainerId) { + const module = devkit.getModule(moduleConfig.id); + if (module) { + return module; + } + } + const module = createDynamicModule(moduleConfig); const { components } = metadata.value.form?.content?.module || []; components.forEach((component: any) => { diff --git a/packages/renderer/src/composition/use-navigation-guard.ts b/packages/renderer/src/composition/use-navigation-guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb8f117d4c72dbde4f663dcaf1f498a7dc960a91 --- /dev/null +++ b/packages/renderer/src/composition/use-navigation-guard.ts @@ -0,0 +1,49 @@ +import { Terminal, NavigationService } from "@farris/command-services-vue"; +import { Injector, ViewModel } from "@farris/devkit-vue"; +import { onBeforeUnmount, onMounted, Ref } from "vue"; +import { useCommandResolver } from "./use-command-resolver"; +import { FormMetadataService } from "../service"; + +export function useNavigationGuard(injector: Injector, terminal: Ref, metadataRef: Ref) { + if (terminal.value !== Terminal.Mobile) { + return; + } + const historyChange = function (event: PopStateEvent) { + const { closeModal } = window as any; + if (closeModal) { + history.pushState(null, '', document.URL); + closeModal(); + return; + } + const { components = [] } = metadataRef.value.form.content.module || {}; + const frameComponent = components.find((component: Record) => component.componentType && component.componentType.toLowerCase() === 'frame'); + const goBack = frameComponent?.goBack; + const viewModel = injector.get(ViewModel); + const module = viewModel.getModule(); + const popstateActive = module.getContext().getParam('popstateActive'); + if (popstateActive === false) { + module.getContext().setParam('popstateActive', null); + return; + } + if (!goBack) { + // 正常后退或关闭 + history.go(-2); + return; + } + + const rootViewModel = viewModel.getRoot(); + const formMetadataService = injector.get(FormMetadataService); + const commandResolver = useCommandResolver(formMetadataService); + const { commandName, commandViewModelId } = commandResolver.resolve(goBack); + const viewModelId = commandViewModelId || rootViewModel.id; + const targetViewModel = rootViewModel.getViewModel(viewModelId); + (targetViewModel as any)[commandName](); + }; + onMounted(() => { + history.pushState(null, '', document.URL); + window.addEventListener('popstate', historyChange, false); + }); + onBeforeUnmount(() => { + window.removeEventListener('popstate', historyChange, false); + }); +} diff --git a/packages/renderer/src/composition/use-navigation.ts b/packages/renderer/src/composition/use-navigation.ts index 61486985140ca93abda083ef96b29ba67d904631..4ed86d6fccb460af89fa0d3dbb8c2026091801b2 100644 --- a/packages/renderer/src/composition/use-navigation.ts +++ b/packages/renderer/src/composition/use-navigation.ts @@ -1,9 +1,13 @@ -import { NavigationEventService, TAB_EVENT } from "@farris/command-services-vue"; +import { NavigationEventService, TAB_EVENT, Terminal } from "@farris/command-services-vue"; import { Injector } from "@farris/devkit-vue"; import { useFormMetadataService } from "./use-form-metadata-service"; import { useModule } from "./use-module"; +import { Ref } from "vue"; -export function useNavigation(injector: Injector) { +export function useNavigation(injector: Injector, terminal: Ref, parentComponentType: string) { + if (terminal.value === Terminal.Mobile || parentComponentType && parentComponentType !== 'external-container') { + return; + } const navigationEventService = injector.get(NavigationEventService); const formMetadata = useFormMetadataService(injector); const module = useModule(injector); diff --git a/packages/renderer/src/composition/use-resource.ts b/packages/renderer/src/composition/use-resource.ts new file mode 100644 index 0000000000000000000000000000000000000000..246c9e57854e33032522428325247fa14fae502c --- /dev/null +++ b/packages/renderer/src/composition/use-resource.ts @@ -0,0 +1,28 @@ +import { Ref } from "vue"; + +export function useResource(metadataRef: Ref) { + const { externalResources: resources } = metadataRef.value.form.content.module; + if (!resources || resources.length < 1) { + return; + } + function loadScriptFile(jsFilePath: string) { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = jsFilePath; + document.head.appendChild(script); + } + function loadStyleFile(cssFilePath: string) { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = cssFilePath; + document.head.appendChild(link); + } + resources.forEach((item: any) => { + if (item.type === 'js' && item.value) { + loadScriptFile(item.value); + } else if (item.type === 'css' && item.value) { + loadStyleFile(item.value); + } + }); +} diff --git a/packages/renderer/src/composition/use-rich-editor-provide.ts b/packages/renderer/src/composition/use-rich-editor-provide.ts new file mode 100644 index 0000000000000000000000000000000000000000..edee420ebdb70d2ddb0ecd6152c1fc5f23dcf9ee --- /dev/null +++ b/packages/renderer/src/composition/use-rich-editor-provide.ts @@ -0,0 +1,13 @@ +import { provide } from "vue"; +import { Injector } from "@farris/devkit-vue"; +import { DynamicScriptService } from "@farris/command-services-vue"; + +export function useEditorAssetsPathProvide(injector: Injector) { + const dynamicScriptService = injector.get(DynamicScriptService); + const uiModule = dynamicScriptService.getModule().ui; + if (uiModule.globalRichTextEditorAssetsPath) { + const { key, value } = uiModule.globalRichTextEditorAssetsPath; + provide(key, value); + } + +} diff --git a/packages/renderer/src/composition/use-ui-binding.ts b/packages/renderer/src/composition/use-ui-binding.ts index 96d64720e4354fdbedc2aa3844c199884bb99363..8f5becd724ef9dc2ef77cbcd7f8ecc90efa893bc 100644 --- a/packages/renderer/src/composition/use-ui-binding.ts +++ b/packages/renderer/src/composition/use-ui-binding.ts @@ -1,13 +1,16 @@ /* eslint-disable no-use-before-define */ import { Injector } from '@farris/devkit-vue'; -import { useDataGridBinding, useFormGroupBinding, useListViewBinding, useTreeGridBinding } from '@farris/ui-binding-vue'; +import { useCalendarBinding, useDataGridBinding, useFormGroupBinding, useListViewBinding, useTreeGridBinding, useDiscussionBinding } from '@farris/ui-binding-vue'; import { useFormMetadataService } from './use-form-metadata-service'; import { useModule } from './use-module'; import { DataSourceResolver, FieldResolver } from '../resolvers'; +import { useCommandResolver } from './use-command-resolver'; +const bindingInstances: Map = new Map(); export function useUIBinding(injector: Injector) { const formMetadata = useFormMetadataService(injector); const module = useModule(injector); + const commandResolver = useCommandResolver(formMetadata); function onComponentReady(payload: any) { const { ref, type, id } = payload; @@ -31,32 +34,87 @@ export function useUIBinding(injector: Injector) { return; } if (type === 'data-grid') { - useDataGridBinding(ref, { entityPath, viewModel, multiSelect: multiSelect && showCheckbox }); + const { destory } = useDataGridBinding(ref, { entityPath, viewModel, multiSelect: multiSelect && showCheckbox }); + bindingInstances.set(id, { destory }); } else if (type === 'tree-grid') { const hierarchyKey = viewSchema.udtField; useTreeGridBinding(ref, { entityPath, viewModel, hierarchyKey }); } else if (type === 'list-view') { useListViewBinding(ref, { entityPath, viewModel, multiSelect: multiSelect && showCheckbox }); } - } else if (type === 'form-group') { - const { field } = viewSchema.binding || {}; - if (!field) { - return; - } - const { dataSource } = FieldResolver.resolve(formMetadata.getEntity(), field) || {}; - if (!dataSource) { - return; - } - const entityPath = resolveEntityPath(dataSource); - if (!entityPath) { - return; - } - const viewModel = resolveViewModel(entityPath); - if (!viewModel) { - return; - } - useFormGroupBinding(ref, { entityPath, viewModel }, viewSchema); } + bindingFormGroup(ref, viewSchema); + bindingCalendar(ref, viewSchema); + bindingDiscussion(ref, viewSchema); + } + function onComponentUnmounted(payload: any) { + const { id } = payload; + if (!id) { + return; + } + const bindingInstance = bindingInstances.get(id); + if (bindingInstance && bindingInstance.destory) { + bindingInstance.destory(); + bindingInstances.delete(id); + } + } + function bindingFormGroup(ref: any, viewSchema: Record) { + const { type, id } = viewSchema || {}; + if (type !== 'form-group') { + return; + } + const { field } = viewSchema.binding || {}; + if (!field) { + return; + } + const { dataSource, bindingPath } = FieldResolver.resolve(formMetadata.getEntity(), field) || {}; + if (!dataSource) { + return; + } + const entityPath = resolveEntityPath(dataSource); + if (!entityPath) { + return; + } + const viewModel = resolveViewModel(entityPath); + if (!viewModel) { + return; + } + useFormGroupBinding(ref, { entityPath, viewModel, bindingPath }, viewSchema); + } + + function bindingCalendar(ref: any, viewSchema: Record) { + const { dataSource, id, type } = viewSchema || {}; + if (type !== 'calendar') { + return; + } + const relatedComponent = formMetadata.getRelatedComponent(id); + if (!relatedComponent) { + return; + } + const viewModel = module.getViewModel(relatedComponent.id); + if (!viewModel) { + return; + } + const entityPath = resolveEntityPath(dataSource); + if (!entityPath) { + return; + } + useCalendarBinding(ref, { entityPath, viewModel }); + } + function bindingDiscussion(ref: any, viewSchema: Record) { + const { type, id } = viewSchema; + if (type !== 'comment') { + return; + } + const relatedComponent = formMetadata.getRelatedComponent(id); + if (!relatedComponent) { + return; + } + const viewModel = module.getViewModel(relatedComponent.id); + if (!viewModel) { + return; + } + useDiscussionBinding(ref, { viewSchema, viewModel, commandResolver }); } function resolveEntityPath(dataSource: string) { @@ -85,6 +143,7 @@ export function useUIBinding(injector: Injector) { } return { - onComponentReady + onComponentReady, + onComponentUnmounted }; } diff --git a/packages/renderer/src/config-builders/entity-store-config-builder.ts b/packages/renderer/src/config-builders/entity-store-config-builder.ts index 3b0ec4b1bcc92d44456d2bd39366a3cc5b87ad39..ae4eb8d8e0c2e1367b54d0f6f5bdf3984dc09bcd 100644 --- a/packages/renderer/src/config-builders/entity-store-config-builder.ts +++ b/packages/renderer/src/config-builders/entity-store-config-builder.ts @@ -1,6 +1,7 @@ import { FieldConfig, PrimitiveFieldConfig, EntityFieldConfig, EntityListFieldConfig, EntityConfig, - EntityStateConfig, EntityStoreConfig + EntityStateConfig, EntityStoreConfig, + BigNumberType } from '@farris/devkit-vue'; import { ENTITY_STORE_SUFFIX } from '../types'; @@ -95,10 +96,13 @@ class EntityStoreConfigBuilder { * 构造简单字段配置 */ private buildPrimitiveFieldConfig(fieldSchemaNode: any): PrimitiveFieldConfig { + const { type } = fieldSchemaNode; + const typeName = type && type.$type; const fieldConfig: PrimitiveFieldConfig = { type: 'Primitive', name: fieldSchemaNode.label, - multiLanguage: fieldSchemaNode.multiLanguage + multiLanguage: fieldSchemaNode.multiLanguage, + bigNumber: typeName === BigNumberType }; return fieldConfig; diff --git a/packages/renderer/src/config-builders/form/validation-rule-creator.ts b/packages/renderer/src/config-builders/form/validation-rule-creator.ts index 87891f57faaa9be40008ead94d6ba1538b615f4f..21b83e682002a8ab6d1994f4f5f4cff19c480a25 100644 --- a/packages/renderer/src/config-builders/form/validation-rule-creator.ts +++ b/packages/renderer/src/config-builders/form/validation-rule-creator.ts @@ -99,7 +99,7 @@ class ValidationRuleCreator { * 最大值 */ public maxValue(controlNode: any): MaxValueValidationRule | undefined { - const { max: maxValue } = controlNode.editor; + const { max: maxValue, bigNumber = false } = controlNode.editor; if (!this.isValidValue(maxValue)) { return; } @@ -108,14 +108,14 @@ class ValidationRuleCreator { const originalMessage = LocaleService.translate('maxValue'); const message = originalMessage.replace(/\$property/g, displayName).replace(/\$constraint1/g, maxValue); - return { name: 'maxValue', maxValue, message }; + return { name: 'maxValue', maxValue, message, bigNumber }; } /** * 最小值 */ public minValue(controlNode: any): MinValueValidationRule | undefined { - const { min: minValue } = controlNode.editor; + const { min: minValue, bigNumber = false } = controlNode.editor; if (!this.isValidValue(minValue)) { return; } @@ -124,7 +124,7 @@ class ValidationRuleCreator { const originalMessage = LocaleService.translate('minValue'); const message = originalMessage.replace(/\$property/g, displayName).replace(/\$constraint1/g, minValue); - return { name: 'minValue', minValue: minValue, message }; + return { name: 'minValue', minValue: minValue, message, bigNumber }; } /** diff --git a/packages/renderer/src/config/types.ts b/packages/renderer/src/config/types.ts index 7aafb1425303edd3d2c79a6925cf9242e226b7d9..585a323a559a9e75e046d73aa295ca5dc4d64fd9 100644 --- a/packages/renderer/src/config/types.ts +++ b/packages/renderer/src/config/types.ts @@ -15,7 +15,8 @@ export enum ConfigurationType { Editable = 'Editable', Required = 'Required', Visible = 'Visible', - Disabled = 'Disabled' + Disabled = 'Disabled', + ActiveDate = 'ActiveDate' } export interface Configuration { config: string | boolean; diff --git a/packages/renderer/src/event-handler/component-ready-event-handler.ts b/packages/renderer/src/event-handler/component-ready-event-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..41e9ec912c6fb719b1d2fd411f85a9fcca88bc25 --- /dev/null +++ b/packages/renderer/src/event-handler/component-ready-event-handler.ts @@ -0,0 +1,25 @@ +import { Injector, Module } from "@farris/devkit-vue"; +import { EventEmitter } from "../common"; +import { FormMetadataService } from "../service"; +import { ViewEvent } from "../types"; +import { useUIBinding } from "../composition"; +import { ref } from "vue"; + +export class ComponentReadyEventHandler { + constructor(private emitter: EventEmitter, private formMetadataService: FormMetadataService, private module: Module, private injector: Injector) { + } + + bind(): void { + this.emitter.on('component:ready', (payload: ViewEvent) => this.onComponentReady(payload)); + } + + dispose(): void { + this.emitter.on('component:ready', (payload: ViewEvent) => this.onComponentReady(payload)); + } + + private onComponentReady(event: ViewEvent) { + const { onComponentReady } = useUIBinding(this.injector); + const payload = { ref: ref(event.payload), id: event.token, type: event.type }; + onComponentReady(payload); + } +} diff --git a/packages/renderer/src/event-handler/component-unmounted-event-handler.ts b/packages/renderer/src/event-handler/component-unmounted-event-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..8aaf22b13ee88565e360526cc4c68cfba8b36e0b --- /dev/null +++ b/packages/renderer/src/event-handler/component-unmounted-event-handler.ts @@ -0,0 +1,25 @@ +import { Injector, Module } from "@farris/devkit-vue"; +import { EventEmitter } from "../common"; +import { FormMetadataService } from "../service"; +import { ViewEvent } from "../types"; +import { useUIBinding } from "../composition"; +import { ref } from "vue"; + +export class ComponentUnmountedEventHandler { + constructor(private emitter: EventEmitter, private formMetadataService: FormMetadataService, private module: Module, private injector: Injector) { + } + + bind(): void { + this.emitter.on('component:unmounted', (payload: ViewEvent) => this.onComponentReady(payload)); + } + + dispose(): void { + this.emitter.on('component:unmounted', (payload: ViewEvent) => this.onComponentReady(payload)); + } + + private onComponentReady(event: ViewEvent) { + const { onComponentUnmounted } = useUIBinding(this.injector); + const payload = { ref: ref(event.payload), id: event.token, type: event.type }; + onComponentUnmounted(payload); + } +} diff --git a/packages/renderer/src/event-handler/discussion-list-page-changed-event-handler.ts b/packages/renderer/src/event-handler/discussion-list-page-changed-event-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e8d4ad2c08692645cc072b86755dd7d62f1caa2 --- /dev/null +++ b/packages/renderer/src/event-handler/discussion-list-page-changed-event-handler.ts @@ -0,0 +1,47 @@ +import { Injector, Module, ViewModel, ViewModelState } from "@farris/devkit-vue"; +import { EventEmitter } from "../common"; +import { FormMetadataService } from "../service"; +import { ViewEvent } from "../types"; + +/** + * 评论区列表回复用户事件处理器 + */ +export class DiscussionListPageChangedEventHandler { + + /** + * 构造函数 + */ + constructor(private emitter: EventEmitter, private formMetadataService: FormMetadataService, private module: Module) { + } + + /** + * 绑定事件 + */ + bind(): void { + this.emitter.on('pageChanged', (payload: ViewEvent) => this.pageChanged(payload)); + } + + /** + * 注销事件绑定 + */ + dispose(): void { + this.emitter.off('pageChanged', (payload: ViewEvent) => this.pageChanged(payload)); + } + + /** + * 处理回复用户事件 + */ + private pageChanged(event: ViewEvent) { + if (!event) { + return; + } + const { token, type, payload } = event; + if (type !== 'comment' || !token) { + return; + } + this.module.getEventBus().emit('DiscussionLisPageChanged', { + token, + payload + }); + } +} diff --git a/packages/renderer/src/event-handler/end-edit-row-event-handler.ts b/packages/renderer/src/event-handler/end-edit-row-event-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..0279ed6a31c051990f108477917184bbcccb601e --- /dev/null +++ b/packages/renderer/src/event-handler/end-edit-row-event-handler.ts @@ -0,0 +1,171 @@ +import { EntityPathBuilder, EntityPathNode, EntityPathNodeType, EntityStore, Injector, Module } from "@farris/devkit-vue"; +import { EventEmitter } from "../common"; +import { ComponentService, FormMetadataService } from "../service"; +import { EventHandler } from "./types"; +import { ENTITY_STORE_SUFFIX, ViewEvent } from "../types"; +import { cloneDeep, get } from "lodash-es"; +import { DataSourceResolver, FieldResolver } from "../resolvers"; + +export class EndEditRowEventHandler implements EventHandler { + constructor( + private emitter: EventEmitter, + private formMetadataService: FormMetadataService, + private module: Module, + private componentService: ComponentService, + private injector: Injector) { + } + + private onEndEditRow(payload: ViewEvent) { + if (!payload) { + return; + } + const event: ViewEvent = payload; + const { token, type, payloads } = event; + const eventArgs = payloads[0] || {}; + const component = this.formMetadataService.getMetadataById(token); + if (!component) { + return; + } + const { row, columns, cells, oldValues, newValues, editors } = eventArgs; + const updatedValues = this.filterUpdatedFields(oldValues, newValues); + this.updateFieldValues(row, columns, updatedValues, editors, token); + } + private filterUpdatedFields(oldValues: Record, newValues: Record) { + const updatedValues: Record = {}; + Object.keys(newValues).forEach((field: string) => { + const oldValue = oldValues[field]; + const newValue = newValues[field]; + if (oldValue !== newValue) { + updatedValues[field] = newValue; + } + }); + return updatedValues; + } + private updateFieldValues(row: any, columns: Record[], newValues: Record, editors: Record, token: string) { + Object.keys(newValues).forEach((field: string) => { + const column = columns.find(col => col.field === field); + if (!column) { + return; + } + const newValue = newValues[field]; + const editor = editors ? editors[field] : null; + const fieldId = column?.binding?.field || null; + if (!fieldId) { + return; + } + this.updateFieldValue(fieldId, row, column, newValue, editor, token); + }); + } + private updateFieldValue(fieldId: string, row: any, column: Record, newValue: any, editor: Record, token: string) { + const { bindingPath, dataSource, multiLanguage } = FieldResolver.resolve(this.entitySchema, fieldId) || {}; + if (!dataSource || !bindingPath) { + return; + } + const { bindingPaths = [], primaryKey = 'id' } = DataSourceResolver.resolve(this.entitySchema, dataSource) || {}; + const primaryValue = row?.raw[primaryKey]; + const fieldPath = bindingPath.split('.'); + + const entityStoreId = this.formMetadataService.getModuleCode() + ENTITY_STORE_SUFFIX; + const entityStore = this.module.getEntityStore(entityStoreId); + if (!entityStore) { + return; + } + const entityListPath = `/${bindingPaths.join('/')}`; + const entityList = entityStore?.getEntityListByPath(entityListPath); + const entity = entityList?.getEntityById(primaryValue); + if (!entity) { + return; + } + const entityPathCreator = new EntityPathBuilder(entityStore); + const entityPaths = entityPathCreator.build(entityListPath, true); + entityPaths.appendNode(new EntityPathNode(EntityPathNodeType.IdValue, primaryValue)); + fieldPath.forEach((path: string) => { + entityPaths.appendNode(new EntityPathNode(EntityPathNodeType.PropName, path)); + }); + if (multiLanguage) { + entityStore?.setValueByPath(entityPaths, cloneDeep(newValue)); + } else { + entityStore?.setValueByPath(entityPaths, newValue); + } + // 继续处理帮助场景 + if (!column || !column.editor) { + return; + } + const { type: editorType, separator = ',' } = column.editor; + if (editorType === 'lookup' && editor && Object.prototype.hasOwnProperty.call(editor, 'items')) { + const { mappingFields, items, isFreeInput, options } = editor; + const { textField } = options || {}; + if (!mappingFields || Object.keys(mappingFields).length < 1) { + return; + } + if (isFreeInput) { + if (bindingPath) { + this.clearValue(entityStore, mappingFields, textField, bindingPaths, bindingPath, row); + } + } else { + this.mapping(entityStore, mappingFields, bindingPaths, row, items, separator); + } + + const datagrid = this.componentService.getComponentById(token); + if (datagrid) { + datagrid.acceptDataItem(row); + } + } + } + private clearValue(entityStore: EntityStore, mappingFields: Record, textField: string, bindingPaths: string[], bindingPath: string, row: any) { + const textFieldMapping = mappingFields[textField]; + if (textFieldMapping) { + const targetField = textFieldMapping.split(',').filter((item: any) => item !== bindingPath).join(','); + if (targetField) { + mappingFields[textField] = targetField; + } else { + delete mappingFields[textField]; + } + } + this.clearMappingValue(entityStore, mappingFields, bindingPaths, row); + } + private clearMappingValue(entityStore: EntityStore, mappingFields: Record, bindingPaths: string[], row: any) { + this.mapping(entityStore, mappingFields, bindingPaths, row, null, undefined); + } + private mapping(entityStore: EntityStore, mappingFields: Record, bindingPaths: string[], row: any, items: any, separator: string | undefined) { + Object.keys(mappingFields).forEach((lookupField: string) => { + const targetField = mappingFields[lookupField]; + const value = this.getValue(items, lookupField, separator); + const targetFields = targetField.split(',').filter((p: any) => p); + targetFields.forEach((field: string) => { + const fieldPaths = field.split('.'); + const paths = bindingPaths.concat(fieldPaths); + const path = '/' + paths.join('/'); + entityStore?.setValueByPath(path, value); + // update row data + const columnField = fieldPaths.join('.'); + if (row.data[columnField]) { + row.data[columnField].data = value; + } + }); + }); + } + private getValue(items: any[], field: string, separator: string | undefined) { + if (!items || items.length === 0) { + return null; + } + if (items.length === 1) { + return get(items[0], field); + } else { + return items.map((item: any) => { + return get(item, field); + }).join(separator); + } + } + + bind(): void { + this.emitter.on('endEditRow', (payload: ViewEvent) => this.onEndEditRow(payload)); + } + + dispose(): void { + this.emitter.off('endEditRow', (payload: ViewEvent) => this.onEndEditRow(payload)); + } + private get entitySchema() { + return this.formMetadataService.getEntity(); + } +} diff --git a/packages/renderer/src/event-handler/filter-bar-condition-change-event-handler.ts b/packages/renderer/src/event-handler/filter-bar-condition-change-event-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..6117bc77df642d348af381f932154b2dc8572516 --- /dev/null +++ b/packages/renderer/src/event-handler/filter-bar-condition-change-event-handler.ts @@ -0,0 +1,37 @@ +import { Injector, Module } from "@farris/devkit-vue"; +import { EventEmitter } from "../common"; +import { FormMetadataService } from "../service"; +import { ViewEvent } from "../types"; +import { EventHandler } from "./types"; + +export class FilterBarConditionChangeEventHandler implements EventHandler { + constructor(private emitter: EventEmitter, private formMetadataService: FormMetadataService, private module: Module, private injector: Injector){} + bind(): void { + this.emitter.on('conditionChange', (payload: ViewEvent) => this.onConfigionChange(payload)); + } + dispose(): void { + this.emitter.on('conditionChange', (payload: ViewEvent) => this.onConfigionChange(payload)); + } + + private onConfigionChange(payload: ViewEvent){ + if (!payload) { + return; + } + const event: ViewEvent = payload; + const { token, type } = event; + if ((type !== 'filter-bar') || !token) { + return; + } + const relatedComponent = this.formMetadataService.getRelatedComponent(token); + if (!relatedComponent) { + return; + } + const viewModel = this.module.getViewModel(relatedComponent.id); + if (!viewModel) { + return; + } + const conditions = event.payloads[0]; + viewModel.uiStore?.setValue('originalFilterConditionList', JSON.stringify(conditions)); + } + +} diff --git a/packages/renderer/src/event-handler/index.ts b/packages/renderer/src/event-handler/index.ts index 28a2c3eb9d6969b736e9cb98b313828500c457af..d11e266e3eb5911644eb20af79461f8298414b40 100644 --- a/packages/renderer/src/event-handler/index.ts +++ b/packages/renderer/src/event-handler/index.ts @@ -9,6 +9,12 @@ export * from './data-grid-selection-change-event-handler'; export * from './data-grid-selection-update-event-handler'; export * from './query-solution-condition-change-event-handler'; export * from './data-grid-double-click-row-event-handler'; +export * from './tree-grid-expand-node-event-handler'; export * from './list-view-click-item-event-handler'; export * from './lookup-clear-mapping-event-handler'; +export * from './discussion-list-page-changed-event-handler'; +export * from './filter-bar-condition-change-event-handler'; +export * from './component-unmounted-event-handler'; +export * from './component-ready-event-handler'; +export * from './end-edit-row-event-handler'; export * from './providers'; diff --git a/packages/renderer/src/event-handler/list-view-click-item-event-handler.ts b/packages/renderer/src/event-handler/list-view-click-item-event-handler.ts index 740b19de5a24c2ba7de91c732348c5261dffdad2..233f8da5d98451763bfaf8120c9ce13b01e5244a 100644 --- a/packages/renderer/src/event-handler/list-view-click-item-event-handler.ts +++ b/packages/renderer/src/event-handler/list-view-click-item-event-handler.ts @@ -53,9 +53,11 @@ export class ListViewClickItemEventHandler implements EventHandler { public bind() { this.emitter.on('clickItem', (payload: ViewEvent) => this.onClickRow(payload)); + this.emitter.on('touchItem', (payload: ViewEvent) => this.onClickRow(payload)); } public dispose() { this.emitter.off('clickItem', (payload: ViewEvent) => this.onClickRow(payload)); + this.emitter.off('touchItem', (payload: ViewEvent) => this.onClickRow(payload)); } } diff --git a/packages/renderer/src/event-handler/lookup-clear-event-handler.ts b/packages/renderer/src/event-handler/lookup-clear-event-handler.ts index 765a519ae9db2081e4a3c24671f477a2e0adc73f..54c9849d1d95fe8bd86671012ade3ca8918436d7 100644 --- a/packages/renderer/src/event-handler/lookup-clear-event-handler.ts +++ b/packages/renderer/src/event-handler/lookup-clear-event-handler.ts @@ -22,6 +22,9 @@ export class LookupClearEventHandler implements EventHandler { } const { mappingFields } = eventArgs; const mapFields = typeof mappingFields === 'string' ? JSON.parse(mappingFields) : mappingFields; + if (!mapFields) { + return; + } const { id } = component; const relatedComponent = this.formMetadataService.getRelatedComponent(id); if (!relatedComponent) { diff --git a/packages/renderer/src/event-handler/providers.ts b/packages/renderer/src/event-handler/providers.ts index ec5acc37dccfa02469b1c4c434f0cf614a1a96c6..4c3459b1fe9071386e44fcfaa21f23b7715e7fc0 100644 --- a/packages/renderer/src/event-handler/providers.ts +++ b/packages/renderer/src/event-handler/providers.ts @@ -13,8 +13,14 @@ import { DataGridSelectionUpdateEventHandler } from "./data-grid-selection-updat import { QuerySolutionConditionChangeEventHandler } from "./query-solution-condition-change-event-handler"; import { DataGridDoubleClickRowEventHandler } from './data-grid-double-click-row-event-handler'; import { ModalClosedEventHandler } from './modal-closed-event-handler'; +import { TreeGridExpandNodeEventHandler } from "./tree-grid-expand-node-event-handler"; import { ListViewClickItemEventHandler } from "./list-view-click-item-event-handler"; import { LookupClearMappingEventHandler } from "./lookup-clear-mapping-event-handler"; +import { DiscussionListPageChangedEventHandler } from "./discussion-list-page-changed-event-handler"; +import { FilterBarConditionChangeEventHandler } from "./filter-bar-condition-change-event-handler"; +import { ComponentReadyEventHandler } from "./component-ready-event-handler"; +import { ComponentUnmountedEventHandler } from "./component-unmounted-event-handler"; +import { EndEditRowEventHandler } from "./end-edit-row-event-handler"; export const eventHanderProviders: StaticProvider[] = [ { provide: EVENT_HANDLERS_TOKEN, useClass: LookupDataMappingEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, @@ -28,6 +34,12 @@ export const eventHanderProviders: StaticProvider[] = [ { provide: EVENT_HANDLERS_TOKEN, useClass: QuerySolutionConditionChangeEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, { provide: EVENT_HANDLERS_TOKEN, useClass: DataGridDoubleClickRowEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, { provide: EVENT_HANDLERS_TOKEN, useClass: ModalClosedEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, + { provide: EVENT_HANDLERS_TOKEN, useClass: TreeGridExpandNodeEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, { provide: EVENT_HANDLERS_TOKEN, useClass: ListViewClickItemEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, - { provide: EVENT_HANDLERS_TOKEN, useClass: LookupClearMappingEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true } + { provide: EVENT_HANDLERS_TOKEN, useClass: LookupClearMappingEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, + { provide: EVENT_HANDLERS_TOKEN, useClass: DiscussionListPageChangedEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, + { provide: EVENT_HANDLERS_TOKEN, useClass: FilterBarConditionChangeEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, + { provide: EVENT_HANDLERS_TOKEN, useClass: ComponentReadyEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, + { provide: EVENT_HANDLERS_TOKEN, useClass: ComponentUnmountedEventHandler, deps: [EventEmitter, FormMetadataService, Module, Injector], multi: true }, + { provide: EVENT_HANDLERS_TOKEN, useClass: EndEditRowEventHandler, deps: [EventEmitter, FormMetadataService, Module, ComponentService, Injector], multi: true } ]; diff --git a/packages/renderer/src/event-handler/tree-grid-expand-node-event-handler.ts b/packages/renderer/src/event-handler/tree-grid-expand-node-event-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a850b194eb120f0a2977ad50cb739b0193e3306 --- /dev/null +++ b/packages/renderer/src/event-handler/tree-grid-expand-node-event-handler.ts @@ -0,0 +1,53 @@ +import { Injector, Module, ViewModel, ViewModelState } from "@farris/devkit-vue"; +import { EventEmitter } from "../common"; +import { FormMetadataService } from "../service"; +import { ViewEvent } from "../types"; +import { EventHandler } from "./types"; +import { DataSourceResolver } from "../resolvers"; + +export class TreeGridExpandNodeEventHandler implements EventHandler { + constructor(private emitter: EventEmitter, private formMetadataService: FormMetadataService, private module: Module, private injector: Injector) { + } + + bind(): void { + this.emitter.on('expandNode', (payload: ViewEvent) => this.onExpandNode(payload)); + } + + dispose(): void { + this.emitter.on('expandNode', (payload: ViewEvent) => this.onExpandNode(payload)); + } + + private onExpandNode(event: ViewEvent) { + if (!event) { + return; + } + const { token, type } = event; + if (type !== 'tree-grid' || !token) { + return; + } + const component = this.formMetadataService.getMetadataById(token); + if (!component) { + return; + } + const { id, dataSource } = component; + if (!id || !dataSource) { + return; + } + const relatedComponent = this.formMetadataService.getRelatedComponent(id); + if (!relatedComponent) { + return; + } + const viewModel = this.module.getViewModel(relatedComponent.id); + if (!viewModel) { + return; + } + const { payload } = event; + const resolvedEntity = DataSourceResolver.resolve(this.formMetadataService.getEntity(), dataSource); + if (!resolvedEntity) { + return; + } + const idKey = resolvedEntity.primaryKey; + const rootViewModel = viewModel.getRoot() as ViewModel; + rootViewModel.uiStore?.setValue('__TREE_LATEST_EXPANDED_ID__', payload[idKey]); + } +} diff --git a/packages/renderer/src/event/use-event.ts b/packages/renderer/src/event/use-event.ts index 213fef23221c54b89c49908c13052384ce041017..a9170cdd1568e1f8d19b859ad0ddac910a7fd7bd 100644 --- a/packages/renderer/src/event/use-event.ts +++ b/packages/renderer/src/event/use-event.ts @@ -1,11 +1,11 @@ import { Module, ViewModel, ViewModelState } from '@farris/devkit-vue'; -import { createEventHandlerResolver, EventHandlerResolver, resolverMap } from '@farris/ui-vue'; import { EventEmitter } from '../common/index'; import { FormMetadataService } from '../service/index'; import { CallbackArgs, CallbackHandlerRegistry } from "../callback-handler"; import { useCommunication } from '../communications/index'; import { EventContext } from './types'; import { EventHandlersExecutor } from './event-handlers-executor'; +import { DynamicScriptService } from '@farris/command-services-vue'; /** * 根据组件ID获取对应ViewModel @@ -31,7 +31,8 @@ function useEvent(module: Module) { const eventEmitter = rootViewModelInjector.get(EventEmitter, undefined); const callbackHandlerRegistry = rootViewModelInjector.get(CallbackHandlerRegistry); const { getCommunicationConfigs } = useCommunication(module); - + const dynamicScriptService: DynamicScriptService = rootViewModelInjector.get(DynamicScriptService); + const { createEventHandlerResolver, resolverMap } = dynamicScriptService.getModule().ui; /** * 追加组件通信绑定 */ @@ -132,7 +133,7 @@ function useEvent(module: Module) { eventEmitter.emit(name, event); // 获取绑定处理器 - let bindingResolver: EventHandlerResolver; + let bindingResolver: any; const resolvers = resolverMap[componentType]; if (resolvers && resolvers.eventHandlerResolver) { bindingResolver = resolvers.eventHandlerResolver; @@ -152,13 +153,13 @@ function useEvent(module: Module) { * @summary * 回调方法的执行结果需要返回 */ - function dispatchCallback(type: string, args: any[]): Promise | undefined | boolean { - const handler = callbackHandlerRegistry.getHandler(type); + function dispatchCallback(type: string, viewSchema: Record, args: any[], editorSchema?: Record): Promise | undefined | boolean { + const handler = callbackHandlerRegistry.getHandler(type, editorSchema || viewSchema); if (!handler) { return true; } - return handler.handle(type, args); + return handler.handle(type, viewSchema, args); } /** diff --git a/packages/renderer/src/i18n/resource-manager.ts b/packages/renderer/src/i18n/resource-manager.ts index 4c577c61d75eddcb629e7d77c155768e65b0482f..9fe98feecf16ffa05da183ca489f290bb5ba3d9e 100644 --- a/packages/renderer/src/i18n/resource-manager.ts +++ b/packages/renderer/src/i18n/resource-manager.ts @@ -21,7 +21,7 @@ class ResourceManager { return metadataCache; } public getResourceCaches() { - const metadataCaches = this.resourcesCache.values(); + const metadataCaches = Array.from(this.resourcesCache.values()); const formattedMetadataCaches = metadataCaches.map((metadataCache: any) => { return metadataCache; }); diff --git a/packages/renderer/src/i18n/transformer/data-grid-i18n-transformer.ts b/packages/renderer/src/i18n/transformer/data-grid-i18n-transformer.ts index 70dc766f5b5d552ebde38a1f25140cdfe3e79a97..bcab439f025343413e4f72c6a072e3e815cafc4f 100644 --- a/packages/renderer/src/i18n/transformer/data-grid-i18n-transformer.ts +++ b/packages/renderer/src/i18n/transformer/data-grid-i18n-transformer.ts @@ -4,14 +4,21 @@ import { GlobalTranslate } from "../global-translate"; export class DataGridI18nTransformer { constructor(private metadata: Record, private translate: GlobalTranslate, private formMetadataId: string) { } public transform() { - const { columns, id, rowNumber } = this.metadata; + const { columns, id, rowNumber, command } = this.metadata; if (!isNil(rowNumber)) { const { heading } = rowNumber; if (heading) { rowNumber.heading = this.translate.transform(this.formMetadataId, `${id}/rowNumber/heading`, heading); } } - + const { enable, commands, enableType } = command || {}; + // 启用了自定义操作列 + if (enable && commands && commands.length > 0 && enableType === 'custom') { + commands.forEach((command: Record) => { + const { text, value } = command; + command.text = this.translate.transform(this.formMetadataId, `${id}/command/commands/${value}`, text); + }); + } if (!columns || !Array.isArray(columns) || columns.length < 1) { return; } diff --git a/packages/renderer/src/i18n/transformer/drawer-i18n-transformer.ts b/packages/renderer/src/i18n/transformer/drawer-i18n-transformer.ts new file mode 100644 index 0000000000000000000000000000000000000000..72231597023c8bb53715f9dcf3bcb40cf40327fe --- /dev/null +++ b/packages/renderer/src/i18n/transformer/drawer-i18n-transformer.ts @@ -0,0 +1,32 @@ +import { isNil } from "lodash-es"; +import { GlobalTranslate } from "../global-translate"; + +export class DrawerI18nTransformer { + constructor(private metadata: Record, private translate: GlobalTranslate, private formMetadataId: string) { } + public transform() { + const { footerToolbar, headerToolbar, id, title } = this.metadata; + if (!isNil(title)) { + this.metadata.title = this.translate.transform(this.formMetadataId, id, title); + } + const headerButtons = headerToolbar && headerToolbar.buttons; + const footerButtons = footerToolbar && footerToolbar.buttons; + const buttons = ([] as any[]).concat(headerButtons || []).concat(footerButtons || []); + + if (!buttons || !Array.isArray(buttons) || buttons.length < 1) { + return; + } + buttons.forEach((button: Record) => { + const { children, text, id: buttonId, tipsText } = button; + button.text = this.translate.transform(this.formMetadataId, buttonId, text); + button.tipsText = this.translate.transform(this.formMetadataId, `${buttonId}/tipsText`, tipsText); + if (!(children && children.length)) { + return; + } + children.forEach((item: Record) => { + const { id: itemId, text, tipsText } = item; + item.text = this.translate.transform(this.formMetadataId, itemId, text); + item.tipsText = this.translate.transform(this.formMetadataId, `${itemId}/tipsText`, tipsText); + }); + }); + } +} diff --git a/packages/renderer/src/i18n/transformer/filter-bar-i18n-transformer.ts b/packages/renderer/src/i18n/transformer/filter-bar-i18n-transformer.ts new file mode 100644 index 0000000000000000000000000000000000000000..210814fa9f3959f36720c68e03093bb31c456058 --- /dev/null +++ b/packages/renderer/src/i18n/transformer/filter-bar-i18n-transformer.ts @@ -0,0 +1,45 @@ +import { isNil } from "lodash-es"; +import { GlobalTranslate } from "../global-translate"; + +export class FilterBarI18nTransformer { + constructor(private metadata: Record, private translate: GlobalTranslate, private formMetadataId: string) { } + public transform() { + const { id, filterText, presetQuerySolutionName, fields, resetText } = this.metadata; + if (!isNil(filterText)) { + this.metadata.filterText = this.translate.transform(this.formMetadataId, `${id}/filterText`, filterText); + } + if (!isNil(resetText)) { + this.metadata.resetText = this.translate.transform(this.formMetadataId, `${id}/resetText`, resetText); + } + if (!isNil(presetQuerySolutionName)) { + this.metadata.presetQuerySolutionName = this.translate.transform(this.formMetadataId, id, presetQuerySolutionName); + } + if (fields && Array.isArray(fields) && fields.length > 0) { + fields.forEach((field: Record) => { + const { id: fieldId, name, editor } = field; + field.name = this.translate.transform(this.formMetadataId, `${id}/fields/${fieldId}`, name); + if (editor) { + const { placeholder, data, beginPlaceholder, endPlaceholder, dialog } = editor; + const { title } = dialog || {}; + if (!isNil(placeholder)) { + editor.placeholder = this.translate.transform(this.formMetadataId, `${id}/fields/${fieldId}/editor/placeholder`, placeholder); + } + if (!isNil(beginPlaceholder)) { + editor.beginPlaceholder = this.translate.transform(this.formMetadataId, `${id}/fields/${fieldId}/editor/beginPlaceholder`, beginPlaceholder); + } + if (!isNil(endPlaceholder)) { + editor.endPlaceholder = this.translate.transform(this.formMetadataId, `${id}/fields/${fieldId}/editor/endPlaceholder`, endPlaceholder); + } + if (!isNil(title)) { + dialog.title = this.translate.transform(this.formMetadataId, `${id}/fields/${fieldId}/editor/dialog/title`, title); + } + if (data && Array.isArray(data)) { + data.forEach((item: Record) => { + item.name = this.translate.transform(this.formMetadataId, `${id}/fields/${fieldId}/editor/data/${item.value}`, item.name); + }); + } + } + }); + } + } +} diff --git a/packages/renderer/src/i18n/transformer/form-group-i18n-transformer.ts b/packages/renderer/src/i18n/transformer/form-group-i18n-transformer.ts index 70821d42681b23191a02bf49387923d8a7eefc60..568398b38a5e680c5bcb5ad218ec8d1be39150cf 100644 --- a/packages/renderer/src/i18n/transformer/form-group-i18n-transformer.ts +++ b/packages/renderer/src/i18n/transformer/form-group-i18n-transformer.ts @@ -1,3 +1,4 @@ +import { isNil } from "lodash-es"; import { GlobalTranslate } from "../global-translate"; export class FormGroupI18nTransformer { @@ -11,6 +12,7 @@ export class FormGroupI18nTransformer { this.resolveLookupTitle(this.metadata); this.resolveSwitchLabel(this.metadata); this.resolveCheckboxLabel(this.metadata); + this.resolveImageProps(this.metadata); } private resolveEditorData(viewSchema: Record) { const { editor, id } = viewSchema; @@ -68,7 +70,7 @@ export class FormGroupI18nTransformer { editor.offLabel = this.translate.transform(this.formMetadataId, `${id}/offLabel`, offLabel); } } - private resolveCheckboxLabel(viewSchema: Record){ + private resolveCheckboxLabel(viewSchema: Record) { const { editor, id } = viewSchema; if (!editor) { return; @@ -81,4 +83,20 @@ export class FormGroupI18nTransformer { editor.label = this.translate.transform(this.formMetadataId, `${id}/editor/label`, label); } } + private resolveImageProps(viewSchema: Record) { + const { editor, id } = viewSchema; + if (!editor) { + return; + } + const { type, title, alt } = editor; + if (type !== 'image') { + return; + } + if (!isNil(title)) { + editor.title = this.translate.transform(this.formMetadataId, `${id}/editor/title`, title); + } + if (!isNil(alt)) { + editor.alt = this.translate.transform(this.formMetadataId, `${id}/editor/alt`, alt); + } + } } diff --git a/packages/renderer/src/i18n/transformer/index.ts b/packages/renderer/src/i18n/transformer/index.ts index 71929593b247ee2e6ba8aca3e04df618e4a50a13..7484dc7633aac790960c457ba8ac8517d0aae5f2 100644 --- a/packages/renderer/src/i18n/transformer/index.ts +++ b/packages/renderer/src/i18n/transformer/index.ts @@ -11,4 +11,6 @@ export * from './tree-grid-i18n-transformer'; export * from './expression-i18n-transformer'; export * from './fieldset-i18n-transformer'; export * from './modal-component-i18n-transformer'; +export * from './drawer-i18n-transformer'; export * from './lookup-i18n-transformer'; +export * from './filter-bar-i18n-transformer'; diff --git a/packages/renderer/src/i18n/transformer/module-i18n-transformer.ts b/packages/renderer/src/i18n/transformer/module-i18n-transformer.ts index d1b3e910f92b66d1d2eb029de47ee2e31c9dca3c..c79e25e42ddf0bdf8024c5970c4589db046daed9 100644 --- a/packages/renderer/src/i18n/transformer/module-i18n-transformer.ts +++ b/packages/renderer/src/i18n/transformer/module-i18n-transformer.ts @@ -1,7 +1,9 @@ import { GlobalTranslate } from "../global-translate"; import { DataGridI18nTransformer } from "./data-grid-i18n-transformer"; +import { DrawerI18nTransformer } from "./drawer-i18n-transformer"; import { ExpressionI18nTransformer } from "./expression-i18n-transformer"; import { FieldsetI18nTransformer } from "./fieldset-i18n-transformer"; +import { FilterBarI18nTransformer } from "./filter-bar-i18n-transformer"; import { FormGroupI18nTransformer } from "./form-group-i18n-transformer"; import { LookupI18nTransformer } from "./lookup-i18n-transformer"; import { ModalComponentI18nTransformer } from "./modal-component-i18n-transformer"; @@ -91,6 +93,10 @@ export class ModuleI18nTransformer { return new TreeGridI18nTransformer(metadata, this.translate, this.formMetadataId); case 'fieldset': return new FieldsetI18nTransformer(metadata, this.translate, this.formMetadataId); + case 'drawer': + return new DrawerI18nTransformer(metadata, this.translate, this.formMetadataId); + case 'filter-bar': + return new FilterBarI18nTransformer(metadata, this.translate, this.formMetadataId); } return null; } diff --git a/packages/renderer/src/main.ts b/packages/renderer/src/main.ts index e262f58b38e805ab0033cf5704d0fc9d1e3d869b..e37c6253f0be5a285b2016869bab6852f5be33e8 100644 --- a/packages/renderer/src/main.ts +++ b/packages/renderer/src/main.ts @@ -1,5 +1,4 @@ import { createApp } from 'vue'; -import FarrisVue from '@farris/ui-vue'; import { createDevkit } from '@farris/devkit-vue'; import { commandServicesDevkitProviders } from '@farris/command-services-vue'; import { metadataProviders } from './metadata'; @@ -23,8 +22,4 @@ const devkit = createDevkit({ const app = createApp(App); app.use(router); app.use(devkit); -app.use(FarrisVue, { - locale: localeId, - uri: '/platform/common/web/@farris/i18n/ui' -}); -app.mount('#app'); +app.mount('#app'); \ No newline at end of file diff --git a/packages/renderer/src/metadata/metadata-manager.ts b/packages/renderer/src/metadata/metadata-manager.ts index f038becaee0898b27ee597245ca18e288c68ebd2..ee3ecfef406a80a1dc3a5bcfab98a060e2772a59 100644 --- a/packages/renderer/src/metadata/metadata-manager.ts +++ b/packages/renderer/src/metadata/metadata-manager.ts @@ -58,7 +58,7 @@ class MetadataManager { * 获取全部元数据缓存 */ public getMetadataCaches() { - const metadataCaches = this.metadatasCache.values(); + const metadataCaches = Array.from(this.metadatasCache.values()); const formattedMetadataCaches = metadataCaches.map((metadataCache: any) => { return metadataCache.allMetadatas; }); diff --git a/packages/renderer/src/page.vue b/packages/renderer/src/page.vue index 285beda45c8020f4ffd2bb7095e2c7b0925507ec..9a7942070493729fff93bf38918ca9a5dd35a244 100644 --- a/packages/renderer/src/page.vue +++ b/packages/renderer/src/page.vue @@ -1,15 +1,21 @@ diff --git a/packages/renderer/src/pipes/image.pipe.ts b/packages/renderer/src/pipes/image.pipe.ts new file mode 100644 index 0000000000000000000000000000000000000000..780e5f184e1c016ffde973e869984f33bd4d0b08 --- /dev/null +++ b/packages/renderer/src/pipes/image.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe } from "../types"; +import { DownloadService } from '@gsp-svc/formdoc-upload-vue'; + +export class ImagePipe implements Pipe { + public type = 'image'; + constructor(private downloadService: DownloadService) { } + public transform(metadataId: string, viewSchema: Record): string | null { + if (!metadataId) { + return null; + } + const { rootId } = viewSchema.editor || {}; + if (!metadataId || !rootId) { + return null; + } + return this.downloadService.getDownloadUrl(metadataId, rootId); + } +} diff --git a/packages/renderer/src/pipes/index.ts b/packages/renderer/src/pipes/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c146da64fe8b4c272006b3e45fdf24e349117549 --- /dev/null +++ b/packages/renderer/src/pipes/index.ts @@ -0,0 +1,4 @@ +export * from './image.pipe'; +export * from './providers'; +export * from './tokens'; +export * from './pipe-registry'; diff --git a/packages/renderer/src/pipes/pipe-registry.ts b/packages/renderer/src/pipes/pipe-registry.ts new file mode 100644 index 0000000000000000000000000000000000000000..d255aa432557d91812460269d4c991237cab066f --- /dev/null +++ b/packages/renderer/src/pipes/pipe-registry.ts @@ -0,0 +1,12 @@ +import { Pipe } from "../types"; + +export class PipeRegistry { + constructor(private pipes: Pipe[]) { + } + public getPipe(viewSchema: Record): Pipe | null { + const { type, editor } = viewSchema; + const pipeType = editor && editor.type ? editor.type : type; + const pipe = this.pipes.find(p => p.type === pipeType); + return pipe || null; + } +} diff --git a/packages/renderer/src/pipes/providers.ts b/packages/renderer/src/pipes/providers.ts new file mode 100644 index 0000000000000000000000000000000000000000..90a43782c21eeb29f427feb56bde4ac8c2504059 --- /dev/null +++ b/packages/renderer/src/pipes/providers.ts @@ -0,0 +1,10 @@ +import { StaticProvider } from "@farris/devkit-vue"; +import { ImagePipe } from "./image.pipe"; +import { PIPES_TOKEN } from "./tokens"; +import { PipeRegistry } from "./pipe-registry"; +import { DownloadService } from "@gsp-svc/formdoc-upload-vue"; + +export const pipeProviders: StaticProvider[] = [ + { provide: PIPES_TOKEN, useClass: ImagePipe, deps: [DownloadService], multi: true }, + { provide: PipeRegistry, useClass: PipeRegistry, deps: [PIPES_TOKEN] }, +]; diff --git a/packages/renderer/src/pipes/tokens.ts b/packages/renderer/src/pipes/tokens.ts new file mode 100644 index 0000000000000000000000000000000000000000..6086d48b1cdba6bb2da45a4f6007973ac9545866 --- /dev/null +++ b/packages/renderer/src/pipes/tokens.ts @@ -0,0 +1,4 @@ +import { InjectionToken } from "@farris/devkit-vue"; +import { Pipe } from "../types"; + +export const PIPES_TOKEN = new InjectionToken('@farris/pipes_token'); diff --git a/packages/renderer/src/render-engine/render-engine-impl.ts b/packages/renderer/src/render-engine/render-engine-impl.ts index 652db72be9ee4cc16fa5cde722e1ee4a2739461f..d1dbdf56c8707dba8147ec8bfd806882009405fc 100644 --- a/packages/renderer/src/render-engine/render-engine-impl.ts +++ b/packages/renderer/src/render-engine/render-engine-impl.ts @@ -57,6 +57,9 @@ export class RenderEngineImpl implements RenderEngine { public getControlValue(id: string){ return this.renderer.value.getControlValue(id); } + public setControlValue(controlId: string, value: any) { + this.renderer.value.setControlValue(controlId, value); + } /** * 实体仓库 */ diff --git a/packages/renderer/src/resolvers/model-value-resolver.ts b/packages/renderer/src/resolvers/model-value-resolver.ts index 956bbe5d8c52ae529da731a47c9935d9f49bdfcc..3bc44e684383ebe6059ed3d896a490c05f2edffc 100644 --- a/packages/renderer/src/resolvers/model-value-resolver.ts +++ b/packages/renderer/src/resolvers/model-value-resolver.ts @@ -4,6 +4,7 @@ import { DataSourceResolver } from "./data-source-resolver"; import { FieldResolver } from "./field-resolver"; import { MODULE_CONFIG_ID_TOKEN } from "../tokens"; import { ENTITY_STORE_SUFFIX } from "../types"; +import { PipeRegistry } from "../pipes"; /** * 模型值解析器 @@ -97,6 +98,10 @@ export class ModelValueResolver { let value = null; try { value = this.entityStore?.getValueByPath('/' + fieldBindingPath); + const pipe = this.pipeRegistry.getPipe(viewSchema); + if (pipe) { + value = pipe.transform(value, viewSchema); + } } catch (e) { console.error(e); @@ -136,4 +141,8 @@ export class ModelValueResolver { private get entityMetadata() { return this.formMetadataService.getEntity(); } + + private get pipeRegistry() { + return this.injector.get(PipeRegistry); + } } diff --git a/packages/renderer/src/router/use-form-routes.ts b/packages/renderer/src/router/use-form-routes.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d190d6ae5524439f725e1cec32f5d6c0ddac024 --- /dev/null +++ b/packages/renderer/src/router/use-form-routes.ts @@ -0,0 +1,89 @@ +import { RouteRecordRaw, Router } from 'vue-router'; +import RendererView from '../renderer.vue'; +import DebugView from '../debug.vue'; +import PreviewView from '../preview.vue'; +import { Injector } from '@farris/devkit-vue'; +import { useFormMetadataService } from '../composition'; +import { VUE_ROUTER_TOKEN } from '@farris/command-services-vue'; + + +/** + * 检查路由是否存在 + */ +function checkFormRoute(router: Router, path: string): boolean { + const routes = router.getRoutes(); + const targetRoute = routes.find((route) => { + return route.path === path; + }); + + return !!targetRoute; +} + +/** + * 创建表单页面路由 + */ +function createFormRoute(formMetadata: any, router: Router) { + const formModuleNode = formMetadata.content.module; + const componentNodes = formModuleNode.components as any[]; + const formCode = formModuleNode.code; + const routerName = router.currentRoute.value.name; + let component = RendererView; + if(routerName === 'preview'){ + component = PreviewView; + }else if(routerName === 'debug'){ + component = DebugView; + } + + const formRoutes: RouteRecordRaw[] = []; + componentNodes.forEach((componentNode) => { + if (componentNode.componentType !== 'page') { + return; + } + const path = `/${formCode}/${componentNode.id}` + const pageRoute: RouteRecordRaw = { + path, + component + }; + !checkFormRoute(router, path) && formRoutes.push(pageRoute); + }); + + return formRoutes; +} + +/** + * 添加表单路由配置 + */ +function addFormRoute(formMetadata: any, router: Router) { + const formRoutes = createFormRoute(formMetadata, router); + + formRoutes.forEach(route => { + router.addRoute(route); + }) +} + +/** + * 获取页面ID + */ +function getPageId(injector: Injector): string | undefined | null { + const formMetadata = useFormMetadataService(injector); + const defaultPageComponent = formMetadata.getComponents().find((component: any) => component.componentType === 'page'); + const defaultPageId = defaultPageComponent?.id; + + const router = injector.get(VUE_ROUTER_TOKEN) as Router; + const route = router.currentRoute.value; + const { path } = route; + if (!path) { + return defaultPageId; + } + + const paths = path.split('/').filter(path => path); + + if (paths && paths.length === 2) { + return paths[paths.length - 1]; + } + return defaultPageId; +} + +export { addFormRoute, getPageId }; + + diff --git a/packages/renderer/src/template-transformer/data-grid-column-template-transformer.ts b/packages/renderer/src/template-transformer/data-grid-column-template-transformer.ts index ac284ae19c2e21b26a1a2d00df58ae42726e89c7..1e62793f2b091299c4d1ea356230803222274684 100644 --- a/packages/renderer/src/template-transformer/data-grid-column-template-transformer.ts +++ b/packages/renderer/src/template-transformer/data-grid-column-template-transformer.ts @@ -9,11 +9,11 @@ export class DataGridColumnTemplateTransformer extends TemplateTransformer { super(formMetadataService, viewModel); } public transform(schema: Record): void { - const { id, columns, type, emptyTemplate } = schema; + const { id, columns, type, emptyTemplate, command } = schema; if (type !== 'data-grid') { return; } - if(emptyTemplate && typeof emptyTemplate ==='string'){ + if (emptyTemplate && typeof emptyTemplate === 'string') { schema.emptyTemplate = this.buildTemplateRender(emptyTemplate, this.viewModel); } if (!columns || !Array.isArray(columns) || columns.length < 1) { @@ -23,6 +23,22 @@ export class DataGridColumnTemplateTransformer extends TemplateTransformer { if (!viewModel) { return; } + if (command?.formatter) { + const { formatter } = command; + if (formatter && typeof formatter === 'string') { + const compiledTemplate = compile(formatter); + const templateContext = this.buildTempalteContext(this.viewModel); + const translateService = this.injector.get(TranslateService); + const render = (cell: any, data: any) => { + // if (typeof command.formatter !== 'function') { + return createVNode({ render: compiledTemplate, props: ['cell', 'rowData', 'viewModel', 't'] }, + { viewModel: templateContext, cell, rowData: data.raw, t: translateService.transform.bind(translateService) }); + // } + // return command.formatter(cell, data); + }; + command.formatter = render; + } + } columns.forEach((column: Record) => { const { formatter, columnTemplate } = column; if (columnTemplate && typeof columnTemplate === 'string') { @@ -30,7 +46,7 @@ export class DataGridColumnTemplateTransformer extends TemplateTransformer { const templateContext = this.buildTempalteContext(viewModel); const translateService = this.injector.get(TranslateService); const render = (cell: any, data: any) => { - return createVNode({ render: compiledTemplate, props: ['rowData', 'viewModel', 't'] }, { viewModel: templateContext, rowData: data.raw, t: translateService.transform.bind(translateService) }); + return createVNode({ render: compiledTemplate, props: ['rowData', 'viewModel', 't', 'locale'] }, { viewModel: templateContext, rowData: data.raw, t: translateService.transform.bind(translateService), locale: this.locale }); }; column.columnTemplate = render; } diff --git a/packages/renderer/src/template-transformer/drawer-template-transfromer.ts b/packages/renderer/src/template-transformer/drawer-template-transfromer.ts new file mode 100644 index 0000000000000000000000000000000000000000..0c109d407345ec5c321935315b7c71239471532b --- /dev/null +++ b/packages/renderer/src/template-transformer/drawer-template-transfromer.ts @@ -0,0 +1,28 @@ +import { Injector, ViewModel, ViewModelState } from "@farris/devkit-vue"; +import { FormMetadataService } from "../service"; +import { TemplateTransformer } from "./template-transformer"; + +export class DrawerTemplateTransformer extends TemplateTransformer { + constructor(protected formMetadataService: FormMetadataService, protected viewModel: ViewModel, private injector: Injector) { + super(formMetadataService, viewModel); + } + public transform(schema: Record): void { + const { id, type, headerTemplate, footerTemplate, footerContentType } = schema; + if (type !== 'drawer') { + return; + } + const viewModel = this.getRelatedViewModel(id); + if (!viewModel) { + return; + } + if (headerTemplate && typeof headerTemplate === 'string') { + const render = this.buildTemplateRender(headerTemplate, viewModel); + schema.headerTemplate = render; + } + if (footerContentType === 'html' && footerTemplate && typeof footerTemplate === 'string') { + const render = this.buildTemplateRender(footerTemplate, viewModel); + schema.footerTemplate = render; + + } + } +} \ No newline at end of file diff --git a/packages/renderer/src/template-transformer/html-template-component-template-transformer.ts b/packages/renderer/src/template-transformer/html-template-component-template-transformer.ts index 7d5d565961ba14a01bc0bcd9ebaa6d5230cb68a2..1c4a9c1700ec1215a5501a1ef0205f67d075d553 100644 --- a/packages/renderer/src/template-transformer/html-template-component-template-transformer.ts +++ b/packages/renderer/src/template-transformer/html-template-component-template-transformer.ts @@ -21,7 +21,7 @@ export class HtmlTemplateComponentTemplateTransformer extends TemplateTransforme const templateContext = this.buildTempalteContext(viewModel); const translateService = this.viewModel.getInjector().get(TranslateService); const render = () => { - return createVNode({ render: compiledTemplate, props: ['viewModel', 't'] }, { viewModel: templateContext, t: translateService.transform.bind(translateService) }); + return createVNode({ render: compiledTemplate, props: ['viewModel', 't', 'locale'] }, { viewModel: templateContext, t: translateService.transform.bind(translateService), locale: this.locale }); }; schema.renderFunction = render; } diff --git a/packages/renderer/src/template-transformer/index.ts b/packages/renderer/src/template-transformer/index.ts index 92a660771b0b3eeb5fddbf4372a035d3a9e4c85d..b05f2655887190b3740e56ffa8fdee7af9f83ec4 100644 --- a/packages/renderer/src/template-transformer/index.ts +++ b/packages/renderer/src/template-transformer/index.ts @@ -8,4 +8,5 @@ export * from './section-template-transformer'; export * from './tab-page-template-transformer'; export * from './list-view-template-transformer'; export * from './tree-grid-column-template-transformer'; +export * from './drawer-template-transfromer'; export * from './providers'; diff --git a/packages/renderer/src/template-transformer/list-view-template-transformer.ts b/packages/renderer/src/template-transformer/list-view-template-transformer.ts index 5d4add42cccfd6e8bd36726cef88a41574eb9381..abc4265058b9dfdbc3d2f90d0bbb29a6f50c6c35 100644 --- a/packages/renderer/src/template-transformer/list-view-template-transformer.ts +++ b/packages/renderer/src/template-transformer/list-view-template-transformer.ts @@ -39,7 +39,7 @@ export class ListViewTemplateTransformer extends TemplateTransformer { const translateService = this.injector.get(TranslateService); const render = (payload: { item: any, index: number, selectedItem: any; }) => { const { item, index, selectedItem } = payload; - return createVNode({ render: compiledTemplate, props: ['viewModel', 'item', 'index', 'selectedItem', 't',] }, { viewModel: templateContext, item, index, selectedItem, t: translateService.transform.bind(translateService) }); + return createVNode({ render: compiledTemplate, props: ['viewModel', 'item', 'index', 'selectedItem', 't', 'locale'] }, { viewModel: templateContext, item, index, selectedItem, t: translateService.transform.bind(translateService), locale: this.locale }); }; return render; } diff --git a/packages/renderer/src/template-transformer/providers.ts b/packages/renderer/src/template-transformer/providers.ts index 0bf0d828973d95c4294faa347e51883b12609396..e41746f8c784b42cce0c05e55fbaf493afc53957 100644 --- a/packages/renderer/src/template-transformer/providers.ts +++ b/packages/renderer/src/template-transformer/providers.ts @@ -10,6 +10,7 @@ import { SectionTemplateTransformer } from './section-template-transformer'; import { TabPageTemplateTransformer } from "./tab-page-template-transformer"; import { ListViewTemplateTransformer } from "./list-view-template-transformer"; import { TreeGridColumnTemplateTransformer } from "./tree-grid-column-template-transformer"; +import { DrawerTemplateTransformer } from "./drawer-template-transfromer"; export const htmlTemplateTransformers: StaticProvider[] = [ { provide: TEMPLATE_TRANSFORMERS_TOKEN, useClass: DataGridColumnTemplateTransformer, deps: [FormMetadataService, ViewModel, Injector], multi: true }, @@ -19,6 +20,7 @@ export const htmlTemplateTransformers: StaticProvider[] = [ { provide: TEMPLATE_TRANSFORMERS_TOKEN, useClass: TabPageTemplateTransformer, deps: [FormMetadataService, ViewModel, Injector], multi: true }, { provide: TEMPLATE_TRANSFORMERS_TOKEN, useClass: ListViewTemplateTransformer, deps: [FormMetadataService, ViewModel, Injector], multi: true }, { provide: TEMPLATE_TRANSFORMERS_TOKEN, useClass: TreeGridColumnTemplateTransformer, deps: [FormMetadataService, ViewModel, Injector], multi: true }, + { provide: TEMPLATE_TRANSFORMERS_TOKEN, useClass: DrawerTemplateTransformer, deps: [FormMetadataService, ViewModel, Injector], multi: true }, { provide: TemplateTransformerRegistry, useClass: TemplateTransformerRegistry, deps: [TEMPLATE_TRANSFORMERS_TOKEN] }, { provide: TemplateTransformService, useClass: TemplateTransformService, deps: [TemplateTransformerRegistry] }, ]; diff --git a/packages/renderer/src/template-transformer/template-transformer.ts b/packages/renderer/src/template-transformer/template-transformer.ts index e34d5e9595f3ceb433a8b7ee5ef1be8d5da6f5b4..5737535734e4ce0fd5d5d4d1263d2368adfdd56c 100644 --- a/packages/renderer/src/template-transformer/template-transformer.ts +++ b/packages/renderer/src/template-transformer/template-transformer.ts @@ -45,8 +45,11 @@ export abstract class TemplateTransformer { const templateContext = this.buildTempalteContext(viewModel); const translateService = this.viewModel.getInjector().get(TranslateService); const render = () => { - return createVNode({ render: compiledTemplate, props: ['viewModel', 't'] }, { viewModel: templateContext, t: translateService.transform.bind(translateService) }); + return createVNode({ render: compiledTemplate, props: ['viewModel', 't', 'locale'] }, { viewModel: templateContext, t: translateService.transform.bind(translateService), locale: this.locale }); }; return render; } + protected get locale() { + return this.viewModel.getModule().getLocale(); + } } diff --git a/packages/renderer/src/template-transformer/tree-grid-column-template-transformer.ts b/packages/renderer/src/template-transformer/tree-grid-column-template-transformer.ts index 251729994a8a0f156e7005ce38bc24f2facda43e..8c03df4ed38f671ba308a97162ae5f07a584db05 100644 --- a/packages/renderer/src/template-transformer/tree-grid-column-template-transformer.ts +++ b/packages/renderer/src/template-transformer/tree-grid-column-template-transformer.ts @@ -30,7 +30,7 @@ export class TreeGridColumnTemplateTransformer extends TemplateTransformer { const templateContext = this.buildTempalteContext(viewModel); const translateService = this.injector.get(TranslateService); const render = (cell: any, data: any) => { - return createVNode({ render: compiledTemplate, props: ['rowData', 'viewModel', 't'] }, { viewModel: templateContext, rowData: data.raw, t: translateService.transform.bind(translateService) }); + return createVNode({ render: compiledTemplate, props: ['rowData', 'viewModel', 't', 'locale'] }, { viewModel: templateContext, rowData: data.raw, t: translateService.transform.bind(translateService), locale: this.locale }); }; column.columnTemplate = render; } diff --git a/packages/renderer/src/tokens.ts b/packages/renderer/src/tokens.ts index 9701a02f66edff59be458dcf65b1fd896c8e3822..950d4c331c52dcf07d7686733c95510408d614f9 100644 --- a/packages/renderer/src/tokens.ts +++ b/packages/renderer/src/tokens.ts @@ -1,5 +1,5 @@ import { InjectionToken } from "@farris/devkit-vue"; -import { FormMetadata } from "./types"; +import { FormMetadata, Pipe } from "./types"; import { ComponentConfigResolver } from "./component-config-resolver"; import { ComponentConfigDependencyResolver } from "./component-config-dependency-resolver"; import { ConfigDependencyResolver } from "./config-dependency-resolver"; @@ -18,3 +18,5 @@ export const COMPONENT_CONFIG_DEPENDENCY_RESOLVER_TOKEN = new InjectionToken[]>('@farris/change_effect_resolver_token'); export const CALLBACK_HANDLER_TOKEN = new InjectionToken('@farris/callback_handler_token'); + + diff --git a/packages/renderer/src/types.ts b/packages/renderer/src/types.ts index 6c7dcb7217d520830a5ab5c6372fcce5601f0505..629959b48de252f127bdcccabc62d3497e1d7fd6 100644 --- a/packages/renderer/src/types.ts +++ b/packages/renderer/src/types.ts @@ -279,3 +279,7 @@ export interface ExpressionMetadata { targetType: ExpressionBindingType; rules: RuleMetadata[]; } +export interface Pipe { + type: string; + transform(value: any, viewSchema: Record, ...args: any[]): any; +} diff --git a/packages/ui-binding/lib/compositions/index.ts b/packages/ui-binding/lib/compositions/index.ts index 1fef67a725d55fd3c002c47c1c53fc7ac5b03e25..85df2f0022f4939351f63f4ba845965c3e6645e4 100644 --- a/packages/ui-binding/lib/compositions/index.ts +++ b/packages/ui-binding/lib/compositions/index.ts @@ -10,3 +10,5 @@ export * from './use-command-executor'; export * from './use-entity-state-change'; export * from './use-form-group-binding'; export * from './use-list-view-binding'; +export * from './use-calendar-binding'; +export * from './use-discussion-binding'; diff --git a/packages/ui-binding/lib/compositions/use-calendar-binding.ts b/packages/ui-binding/lib/compositions/use-calendar-binding.ts new file mode 100644 index 0000000000000000000000000000000000000000..f5c193b5d9bda64080c169362da7d0ef41d57cf6 --- /dev/null +++ b/packages/ui-binding/lib/compositions/use-calendar-binding.ts @@ -0,0 +1,51 @@ +import { ref } from 'vue'; +import { EntityChange, EntityChangeType, ViewModelState, ViewModel, EntityPathNode, EntityPathNodeType } from '@farris/devkit-vue'; +import { BindingOptions, ElementRef, UseListViewBinding } from '../types'; +import { useElementRef } from './use-element-ref'; +import { useEntityPath } from './use-entity-path'; +import { cloneDeep } from 'lodash-es'; + +export function useCalendarBinding(elementRef: ElementRef, options: BindingOptions): UseListViewBinding { + const componentRef = useElementRef(elementRef); + const viewModel: ViewModel = options.viewModel; + const { entityPath: entityPaths, toShortPath } = useEntityPath(viewModel, options.entityPath, true); + const data = ref(); + /** + * 渲染Calendar数据 + */ + function render(change: EntityChange) { + const entityPath = toShortPath(); + const bindingPath = `/${entityPath?.join('/')}`; + if (change.type === EntityChangeType.ValueChange || change.type === EntityChangeType.Update) { + const changePaths = change.path.clone(); + const nodes = changePaths.getNodes(); + let node = nodes[nodes.length - 1]; + while (node.getNodeType() !== EntityPathNodeType.IdValue) { + nodes.pop(); + node = nodes[nodes.length - 1]; + } + nodes.pop(); + const changePath = '/' + nodes.filter((node: EntityPathNode) => node.getNodeType() !== EntityPathNodeType.IdValue).map((node: EntityPathNode) => node.getNodeValue()).join('/'); + if (!(changePath === bindingPath || bindingPath.startsWith(changePath))) { + return; + } + } else { + const changePath = change && change.path.toShortPath(); + if (!(changePath === bindingPath || bindingPath.startsWith(changePath))) { + return; + } + } + const entityData: Record[] | undefined = viewModel.entityStore?.getEntityListByPath(options.entityPath).toJSON(); + const datas = cloneDeep(entityData); + const entities = viewModel.entityStore?.getEntityListByPath(options.entityPath).getEntities(); + data.value = datas; + componentRef.value.loadEvents(datas); + }; + + viewModel.entityStore?.watchChange((change: any) => { + render(change); + }); + return { + data + }; +} \ No newline at end of file diff --git a/packages/ui-binding/lib/compositions/use-data-grid-binding.ts b/packages/ui-binding/lib/compositions/use-data-grid-binding.ts index ee43d2198b25dfe465e38b0498775202534ed43a..f7a0c9676c8c25f73923f0102b335f3f28b58294 100644 --- a/packages/ui-binding/lib/compositions/use-data-grid-binding.ts +++ b/packages/ui-binding/lib/compositions/use-data-grid-binding.ts @@ -276,7 +276,7 @@ export function useDataGridBinding(elementRef: ElementRef, options: BindingOptio viewModel.uiStore?.setValue('ids', [id]); } } - viewModel.entityStore?.watchChange((change: any) => { + const destory = viewModel.entityStore?.watchChange((change: any) => { render(change); }); viewModel.getModule().getEventBus().on('endEdit', (event: any) => { @@ -312,6 +312,7 @@ export function useDataGridBinding(elementRef: ElementRef, options: BindingOptio return { data, onClickRow, - pagination + pagination, + destory }; } \ No newline at end of file diff --git a/packages/ui-binding/lib/compositions/use-discussion-binding.ts b/packages/ui-binding/lib/compositions/use-discussion-binding.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ca8513f1f29b632e7d8b2f5b2760576a39e1d93 --- /dev/null +++ b/packages/ui-binding/lib/compositions/use-discussion-binding.ts @@ -0,0 +1,78 @@ +import { ref } from 'vue'; +import { EntityChange, EntityChangeType, ViewModelState, ViewModel, EntityPathNode, EntityPathNodeType, ChangeValueChange, RemoveEntityChange, EntityPath } from '@farris/devkit-vue'; +import { ElementRef } from '../types'; +import { useElementRef } from './use-element-ref'; + +export function useDiscussionBinding(elementRef: ElementRef, options: Record) { + const componentRef = useElementRef(elementRef); + const viewModel: ViewModel = options.viewModel; + const viewSchema: Record = options.viewSchema; + const { onQueryFrequentAtUsers, onQueryComments } = viewSchema; + const commandResolver: { resolve: (commandName: string) => { commandName: string, commandViewModelId: string | null; }; } = options.commandResolver; + + function queryFrequentAtUsers(pageIndex?: number, pageSize?: number) { + const { commandName, commandViewModelId } = commandResolver.resolve(onQueryFrequentAtUsers); + const targetViewModel = commandViewModelId ? viewModel.getModule().getViewModel(commandViewModelId) : viewModel; + if (!targetViewModel || !commandName) { + return; + } + return (targetViewModel as any)[commandName]({ payload: { pageIndex, pageSize } }).then((result: any) => { + if (!result) { + return; + } + componentRef.value.updatePersonnels(result && result.users || []); + }).catch((error: any) => { + if (error) { + console.error(error); + } + }); + } + function queryComments(pageIndex?: number, pageSize?: number) { + const { commandName, commandViewModelId } = commandResolver.resolve(onQueryComments); + const targetViewModel = commandViewModelId ? viewModel.getModule().getViewModel(commandViewModelId) : viewModel; + if (!targetViewModel || !commandName) { + return; + } + return (targetViewModel as any)[commandName]({ payload: { pageIndex, pageSize } }).then((result: any) => { + if (!result) { + return; + } + // 修正pageIndex为0的问题 + result.pageIndex += 1; + componentRef.value.updateComments(result); + }).catch((error: any) => { + if (error) { + console.error(error); + } + }); + } + + viewModel.entityStore?.watchChange((change: any) => { + if ([EntityChangeType.Load, EntityChangeType.CurrentChange, EntityChangeType.Append].includes(change.type)) { + queryComments(); + } + }); + viewModel.getModule().getEventBus().on('DiscussionListPaginationChange', (event: { id: string, pageIndex?: number, pageSize?: number; }) => { + const { id, pageIndex, pageSize } = event; + if (id !== viewSchema.id && !id.startsWith(viewSchema.id)) { + return; + } + queryComments(pageIndex, pageSize); + }); + viewModel.getModule().getEventBus().on('DiscussionEditorAddComment', (event: { id: string, pageIndex?: number, pageSize?: number; }) => { + const { id } = event; + if (id !== viewSchema.id && !id.startsWith(viewSchema.id)) { + return; + } + queryComments(0); + }); + viewModel.getModule().getEventBus().on('DiscussionLisPageChanged', (event: { payload: Record, token: string; }) => { + const { payload, token } = event; + if (token !== viewSchema.id && !token.startsWith(viewSchema.id)) { + return; + } + const { pageIndex, pageSize } = payload; + queryComments(pageIndex - 1, pageSize); + }); + queryFrequentAtUsers(); +} diff --git a/packages/ui-binding/lib/compositions/use-form-group-binding.ts b/packages/ui-binding/lib/compositions/use-form-group-binding.ts index fc2ba55a9dc85c047668b7bfc1a7fc059cf0486c..ae3201ee0b03139bb88ea3642f7c2744a49b53fd 100644 --- a/packages/ui-binding/lib/compositions/use-form-group-binding.ts +++ b/packages/ui-binding/lib/compositions/use-form-group-binding.ts @@ -2,6 +2,7 @@ import { EntityChangeType, EntityPath, ViewModel, ViewModelState } from "@farris import { ElementRef } from "../types"; import { useElementRef } from "./use-element-ref"; import { useEntityPath } from "./use-entity-path"; +import { DownloadService } from '@gsp-svc/formdoc-upload-vue'; export function useFormGroupBinding(elementRef: ElementRef, options: any, viewSchema: Record) { const componentRef = useElementRef(elementRef); @@ -33,7 +34,7 @@ export function useFormGroupBinding(elementRef: ElementRef, options: any, viewSc if (!idFields) { return; } - const dataFields = idFields.split(',').filter((p)=>p); + const dataFields = idFields.split(',').filter((p) => p); const dataField = dataFields[0]; const entityPaths = toShortPath() || []; const paths = entityPaths.concat(dataField.split('.')); diff --git a/packages/ui-binding/lib/compositions/use-list-view-binding.ts b/packages/ui-binding/lib/compositions/use-list-view-binding.ts index 6a4d4a33937471bb83d9879c8300a8c17e25e27a..f96307a1d60b0f6c19165b1fa9ce661a75fc7718 100644 --- a/packages/ui-binding/lib/compositions/use-list-view-binding.ts +++ b/packages/ui-binding/lib/compositions/use-list-view-binding.ts @@ -132,8 +132,6 @@ export function useListViewBinding(elementRef: ElementRef, options: BindingOptio const index = getEntityIndexById(entities, newCurrentId); scrollToRowByIndex(index); } else if (change.type === EntityChangeType.ValueChange) { - - // 更新数据 componentRef.value.updateDataSource(datas); } else if (change.type === EntityChangeType.Update) { diff --git a/packages/ui-binding/lib/types.ts b/packages/ui-binding/lib/types.ts index e9084d806b9060996073760da2b4eda63bba3b52..e5632dcf0c21bd0da8f8f92cc35f4b8e56455cb0 100644 --- a/packages/ui-binding/lib/types.ts +++ b/packages/ui-binding/lib/types.ts @@ -10,6 +10,7 @@ export interface UseDataGridBinding { data: Ref; onClickRow: (index: number, data: any) => void; pagination: Ref; + destory: Function | undefined; } export interface UseTreeGridBinding { onClickRow: (action: Action) => (index: number, data: any) => void; @@ -17,6 +18,10 @@ export interface UseTreeGridBinding { export interface UseListViewBinding { data: Ref; } + +export interface UseDiscussionBinding { + data: Ref; +} /** * 绑定配置 */ diff --git a/packages/ui-binding/package.json b/packages/ui-binding/package.json index a9a689db47f22c76bce78511cd32652692f9a380..e05905e0db45c4c4e7af48faae0aab7487981727 100644 --- a/packages/ui-binding/package.json +++ b/packages/ui-binding/package.json @@ -25,6 +25,7 @@ "@farris/bef-vue": "workspace:^", "@farris/devkit-vue": "workspace:^", "@farris/ui-vue": "workspace:*", + "@gsp-svc/formdoc-upload-vue": "1.0.11", "lodash": "^4.17.21", "vue": "^3.4.37", "vue-router": "^4.3.0" @@ -48,7 +49,7 @@ "rollup-plugin-vue": "^6.0.0", "tslib": "^2.7.0", "typescript": "^5.5.3", - "vite": "^5.4.1", + "vite": "^5.4.15", "vite-plugin-dts": "^2.1.0", "vue-tsc": "^2.0.29" }, diff --git a/packages/ui-binding/rollup.config.js b/packages/ui-binding/rollup.config.js index bb56613f0b7074a672fa66aee14e6153ce4e4c08..a2ef2472ee96c46482f8d6594184dc8d21c4d7d3 100644 --- a/packages/ui-binding/rollup.config.js +++ b/packages/ui-binding/rollup.config.js @@ -17,7 +17,7 @@ const packageName = name.split('/')[1]; export default { input: 'lib/index.ts', - external: ['@farris/devkit-vue', '@farris/bef-vue', '@farris/command-services', 'ui-vue', 'vue', 'tslib', 'lodash'], + external: ['@farris/devkit-vue', '@farris/bef-vue', '@farris/command-services', 'ui-vue', 'vue', 'tslib', 'lodash', '@gsp-svc/formdoc-upload-vue','lodash-es'], output: [{ file: `dist-rollup/@farris/ui-binding-vue.js`, format: 'system', diff --git a/packages/ui-vue/components/avatar/src/avatar.component.tsx b/packages/ui-vue/components/avatar/src/avatar.component.tsx index 9501e61924025c111b081862a2dc95c999b83819..33c7be04c25ce610525f1ee815b8169d39e7e0f6 100644 --- a/packages/ui-vue/components/avatar/src/avatar.component.tsx +++ b/packages/ui-vue/components/avatar/src/avatar.component.tsx @@ -16,15 +16,13 @@ import { defineComponent, computed, ref, SetupContext } from 'vue'; import { avatarProps, AvatarProps } from './avatar.props'; import { useImage } from './composition/use-image'; -import { useI18n } from 'vue-i18n'; - +import { LocaleService } from '@farris/ui-vue/components/locale'; export default defineComponent({ name: 'FAvatar', props: avatarProps, emits: ['change', 'update:modelValue'] as (string[] & ThisType) | undefined, setup(props: AvatarProps, context: SetupContext) { - const { t } = useI18n(); const avatarClass = computed(() => ({ 'f-avatar': true, 'f-avatar-readonly': props.readonly, @@ -59,7 +57,7 @@ export default defineComponent({
{showLoading && (
-
{t('avatar.loading')}
+
{LocaleService.getLocaleValue('avatar.loading')}
)} diff --git a/packages/ui-vue/components/binding-selector/src/components/binding-selector-container.component.tsx b/packages/ui-vue/components/binding-selector/src/components/binding-selector-container.component.tsx index 01a6629f40e2b22fccda66a09cc8b3c4077b91db..7765f4d75a55849a750dd97e2f27433bf0e92205 100644 --- a/packages/ui-vue/components/binding-selector/src/components/binding-selector-container.component.tsx +++ b/packages/ui-vue/components/binding-selector/src/components/binding-selector-container.component.tsx @@ -37,7 +37,7 @@ export default defineComponent({ onMounted(() => { const bindingFieldId = props.editorParams?.componentSchema?.binding?.field; if (bindingFieldId) { - treegridRef.value.selectItemById(bindingFieldId); + treegridRef.value.selectRowById(bindingFieldId); } }); @@ -160,7 +160,7 @@ export default defineComponent({ props.showCustomFooter ? : '' } diff --git a/packages/ui-vue/components/binding-selector/src/composition/use-field-selection.ts b/packages/ui-vue/components/binding-selector/src/composition/use-field-selection.ts index 52e6ff7651fd35ace91aa3e8b71af303175badaa..c1a27559f3ba60bb061aa3e79350e1259f5581fd 100644 --- a/packages/ui-vue/components/binding-selector/src/composition/use-field-selection.ts +++ b/packages/ui-vue/components/binding-selector/src/composition/use-field-selection.ts @@ -1,12 +1,12 @@ import { FNotifyService } from "../../../notify"; -import { FormBindingType, SchemaDOMMapping } from "@farris/ui-vue/components/property-panel"; +import { FormBindingType, SchemaDOMMapping as defaultSchemaDOMMapping } from "@farris/ui-vue/components/property-panel"; import { merge } from "lodash-es"; import { BindingSelectorProps } from "../binding-selector.props"; import { FormSchemaEntityField$Type, FormSchemaEntityFieldTypeName } from "@farris/ui-vue/components/common"; export function useFieldSelection(props: BindingSelectorProps) { - const { designViewModelUtils, schemaService, formSchemaUtils } = props.editorParams.designerHostService; + const { designViewModelUtils, schemaService, formSchemaUtils, designerContext } = props.editorParams.designerHostService; const { viewModelId } = props.editorParams; let fieldTreeData = []; let localeVariableTreeData = []; @@ -217,11 +217,13 @@ export function useFieldSelection(props: BindingSelectorProps) { const occupiedFieldSet = new Set(); const formSchema = formSchemaUtils.getFormSchema().module; const viewModelNode = formSchemaUtils.getViewModelById(viewModelId); - const componentNode = formSchema.components.find(component => component.viewModel === viewModelId); + const targetComponentNode = formSchema.components.find(component => component.viewModel === viewModelId); + // 当前组件是否在侧边栏中 + const targetComponentInDrawer = formSchemaUtils.checkComponentExistInDrawer(targetComponentNode.id); // 当前组件的类型 - let targetComponentType = componentNode.componentType; + let targetComponentType = targetComponentNode.componentType; // 根组件和table组件内的输入控件与form内不能重复 if (targetComponentType === 'frame' || targetComponentType === 'table') { @@ -240,12 +242,26 @@ export function useFieldSelection(props: BindingSelectorProps) { if (componentType !== targetComponentType || viewModel.bindTo !== viewModelNode.bindTo) { return; } + // 组件是否在侧边栏中 + const componentInDrawer = formSchemaUtils.checkComponentExistInDrawer(componentNode.id); + + // 若当前组件在侧边栏中,那么只收集同样在侧边栏中的字段。 + if (targetComponentInDrawer && componentInDrawer) { + viewModel.fields.forEach(field => { + if (field.id !== currentBindingId) { + occupiedFieldSet.add(field.id); + } + }); + } + // 若当前组件不在侧边栏中,那么只收集不在侧边栏中的字段 + if (!targetComponentInDrawer && !componentInDrawer) { + viewModel.fields.forEach(field => { + if (field.id !== currentBindingId) { + occupiedFieldSet.add(field.id); + } + }); + } - viewModel.fields.forEach(field => { - if (field.id !== currentBindingId) { - occupiedFieldSet.add(field.id); - } - }); }); return occupiedFieldSet; } @@ -259,7 +275,8 @@ export function useFieldSelection(props: BindingSelectorProps) { } const controlFieldMapping = {}; - const { fieldControlTypeMapping } = SchemaDOMMapping; + const schemaDOMMapping = designerContext && designerContext.schemaDOMMapping ? designerContext.schemaDOMMapping : defaultSchemaDOMMapping; + const { fieldControlTypeMapping } = schemaDOMMapping; for (const fieldType in fieldControlTypeMapping) { const controlTypes = fieldControlTypeMapping[fieldType]; diff --git a/packages/ui-vue/components/button-edit/src/button-edit.component.tsx b/packages/ui-vue/components/button-edit/src/button-edit.component.tsx index af8ba03b9dd361f25d6ec2420e319c3849662095..67a477a5ecb95590d8874ea14d68beb8586e5283 100644 --- a/packages/ui-vue/components/button-edit/src/button-edit.component.tsx +++ b/packages/ui-vue/components/button-edit/src/button-edit.component.tsx @@ -85,8 +85,8 @@ export default defineComponent({ const containerStyle = computed(() => { return { - paddingLeft: '3px', - backgroundColor: '#fff' + // paddingLeft: '3px', + // backgroundColor: '#fff' }; }); diff --git a/packages/ui-vue/components/button-edit/src/button-edit.props.ts b/packages/ui-vue/components/button-edit/src/button-edit.props.ts index 4a8f66df819b8bda27fef68a967b11b6dac0d0a5..dd5f67041af39d8b543e185bd64242402b2f8480 100644 --- a/packages/ui-vue/components/button-edit/src/button-edit.props.ts +++ b/packages/ui-vue/components/button-edit/src/button-edit.props.ts @@ -153,10 +153,6 @@ export const buttonEditProps = { * 根据空间大小重新调整,原下拉面板内容指定的高度 */ limitContentBySpace:{ type: Boolean, default: false }, - /** - * 关闭前 - */ - beforeClose: { type: Function as PropType, default: null }, beforeClosePopup: { type: Function as PropType, default: null }, diff --git a/packages/ui-vue/components/button-edit/src/components/text-edit.component.tsx b/packages/ui-vue/components/button-edit/src/components/text-edit.component.tsx index daeda612200bea97d438ff8e60c8bfc24f1dc2b0..6984a464cbfd1d4dec4e8e9da27506c36d383b37 100644 --- a/packages/ui-vue/components/button-edit/src/components/text-edit.component.tsx +++ b/packages/ui-vue/components/button-edit/src/components/text-edit.component.tsx @@ -16,8 +16,8 @@ export default function ( const { uuid } = useGuid(); const inputStyle = computed(() => { return { - 'border-top-right-radius': '6px', - 'border-bottom-right-radius': '6px' + // 'border-top-right-radius': '6px', + // 'border-bottom-right-radius': '6px' }; }); onMounted(() => { diff --git a/packages/ui-vue/components/button-group/src/button-group.component.tsx b/packages/ui-vue/components/button-group/src/button-group.component.tsx index 6ea630233bf2f41545a79b4205ac42c35469aa0d..427341918ff7862264fb3574411d78f1f7a83588 100644 --- a/packages/ui-vue/components/button-group/src/button-group.component.tsx +++ b/packages/ui-vue/components/button-group/src/button-group.component.tsx @@ -58,9 +58,21 @@ export default defineComponent({ return classObject; }; + /** + * button group style + * @returns + */ + const buttonGroupStyle = () => { + return { + display: 'inline-flex', + 'vertical-align': 'middle', + 'margin-bottom': '2px' + }; + }; + function onClick(payload: MouseEvent, buttonItem: any) { - context.emit('change', buttonItem.id); - context.emit('click', buttonItem); + context.emit('change', { payload, button: buttonItem }); + context.emit('click', { payload, button: buttonItem }); } function renderFlatButton(buttonItem: ButtonItem) { @@ -87,7 +99,7 @@ export default defineComponent({ return () => (
-
+
{flatButtons.map((buttonItem) => renderFlatButton(buttonItem))}
{!!dpButtons.length && renderDropdownButton(dpButtons)} diff --git a/packages/ui-vue/components/button-group/src/components/button-group-dropdown-menu.component.tsx b/packages/ui-vue/components/button-group/src/components/button-group-dropdown-menu.component.tsx index 07cd9917ddee56fb4c5ef1bdef8177e1e767ec0f..e104942b98241883fd04d4b8635233770178560b 100644 --- a/packages/ui-vue/components/button-group/src/components/button-group-dropdown-menu.component.tsx +++ b/packages/ui-vue/components/button-group/src/components/button-group-dropdown-menu.component.tsx @@ -5,7 +5,7 @@ import FPopover from '@farris/ui-vue/components/popover'; export default function (props: ButtonGroupProps, buttonGroupRef: Ref, usePopupComposition: UsePopup) { - const { hidePopup, onClickDropdownMenuItem, popoverRef } = usePopupComposition; + const { hidePopup, onClickDropdownMenuItem, popoverRef, onMouseenterDropMenu, onMouseleaveDropMenu } = usePopupComposition; function renderDropdownItem(dropdownItem: any) { return ( @@ -25,8 +25,11 @@ export default function (props: ButtonGroupProps, buttonGroupRef: Ref, useP function renderDropdownMenu(dropdownButtons: any[]) { return ( -
+ host={'body'} fitContent={true} onHidden={hidePopup} minWidth={120}> +
{dropdownButtons.map((dropdownItem: any) => renderDropdownItem(dropdownItem))}
diff --git a/packages/ui-vue/components/button-group/src/components/button-group-dropdown.component.tsx b/packages/ui-vue/components/button-group/src/components/button-group-dropdown.component.tsx index 273383204a40d709cecba3d598a84d8249487862..527384d67f12369541139e8c64adb0ac7154eef5 100644 --- a/packages/ui-vue/components/button-group/src/components/button-group-dropdown.component.tsx +++ b/packages/ui-vue/components/button-group/src/components/button-group-dropdown.component.tsx @@ -9,10 +9,21 @@ export default function (props: ButtonGroupProps, context: SetupContext) { const usePopupComposition = usePopup(props, context, dropdownButtonRef); const { onMouseleaveDropdownButton, onMouseenterDropdownButton, onClickDropdownButton, shouldPopupContent } = usePopupComposition; const { renderDropdownMenu } = getDropdownMenu(props, buttonGroupRef, usePopupComposition); + const dropdownStyle = () => { + return { + 'margin-bottom': '2px' + }; + }; function renderDropdownButton(dropdownButtons: any[]) { return ( -
+
diff --git a/packages/ui-vue/components/button-group/src/composition/types.ts b/packages/ui-vue/components/button-group/src/composition/types.ts index 17127af6cb90fcac8a78a5bbcb0a8488b879eba4..3b10d498e135552e090d96f5991cfda8d32252da 100644 --- a/packages/ui-vue/components/button-group/src/composition/types.ts +++ b/packages/ui-vue/components/button-group/src/composition/types.ts @@ -34,6 +34,10 @@ export interface UsePopup { onMouseleaveDropdownButton: (payload: MouseEvent) => void; onMouseenterDropdownButton: (payload: MouseEvent) => void; + + onMouseleaveDropMenu: (payload: MouseEvent) => void; + + onMouseenterDropMenu: (payload: MouseEvent) => void; } export type PlacementDirection = | 'top' diff --git a/packages/ui-vue/components/button-group/src/composition/use-popup.ts b/packages/ui-vue/components/button-group/src/composition/use-popup.ts index 95e1bda4b02a85590988342b9410ed7e55c1f345..ffe2938ad2840313f001362a43b2db8f49e3819e 100644 --- a/packages/ui-vue/components/button-group/src/composition/use-popup.ts +++ b/packages/ui-vue/components/button-group/src/composition/use-popup.ts @@ -25,6 +25,7 @@ export function usePopup( const popoverRef = ref(); const shouldPopupContent = ref(false); const mouseleaveEvent = ref(); + const isOnDropMenu = ref(false); function tryShowPopover() { const popoverInstance = popoverRef.value; @@ -54,14 +55,16 @@ export function usePopup( $event.stopPropagation(); if (menuItem.disabled) {return;} shouldPopupContent.value = false; - context.emit('change', menuItem.id); - context.emit('click', menuItem); + context.emit('change', {payload: $event, button: menuItem}); + context.emit('click', {payload: $event, button: menuItem}); } function onMouseleaveDropdownButton() { if (shouldPopupContent.value) { mouseleaveEvent.value = setTimeout(() => { - hidePopup(); + if(!isOnDropMenu.value) { + hidePopup(); + } }, 1000); } } @@ -72,12 +75,23 @@ export function usePopup( } } + function onMouseenterDropMenu(e: MouseEvent) { + isOnDropMenu.value = true; + } + + function onMouseleaveDropMenu(e: MouseEvent) { + isOnDropMenu.value = false; + onMouseleaveDropdownButton(); + } + return { hidePopup, onClickDropdownButton, onClickDropdownMenuItem, onMouseleaveDropdownButton, onMouseenterDropdownButton, + onMouseenterDropMenu, + onMouseleaveDropMenu, popoverRef, shouldPopupContent, togglePopup diff --git a/packages/ui-vue/components/calendar/index.ts b/packages/ui-vue/components/calendar/index.ts index 4ae57650226567ed7cdede265a2823e602eeadc2..73384fbee26a1c6b64917812a13ae02a6ae0ccb2 100644 --- a/packages/ui-vue/components/calendar/index.ts +++ b/packages/ui-vue/components/calendar/index.ts @@ -1,4 +1,4 @@ - + import type { App, Plugin } from 'vue'; import FCalendar from './src/calendar.component'; import FCalendarDesign from './src/designer/calendar.design.component'; @@ -6,7 +6,9 @@ import FCalendarHeader from './src/components/header/header.component'; import FCalendarDayView from './src/components/day/day-view.component'; import FCalendarMonthView from './src/components/month/month-view.component'; import FCalendarWeekView from './src/components/week/week-view.component'; -import { propsResolver } from './src/calendar.props'; +import { propsResolver, propsResolverGenerator } from './src/calendar.props'; +import { createCalendarBindingResolver } from '../dynamic-resolver'; +import { RegisterContext } from '../common'; export * from './src/calendar.props'; export * from './src/components/day/day-view.props'; @@ -22,6 +24,8 @@ export * from './src/types/schedule'; export { FCalendar, FCalendarDayView, FCalendarHeader, FCalendarMonthView, FCalendarWeekView }; +const bindingResolver = createCalendarBindingResolver(); + FCalendar.install = (app: App) => { app.component(FCalendar.name as string, FCalendar) .component(FCalendarDayView.name as string, FCalendarDayView) @@ -29,13 +33,17 @@ FCalendar.install = (app: App) => { .component(FCalendarMonthView.name as string, FCalendarMonthView) .component(FCalendarWeekView.name as string, FCalendarWeekView); }; -FCalendar.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FCalendar.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext) => { componentMap.calendar = FCalendar; - propsResolverMap.calendar = propsResolver; + propsResolverMap.calendar = propsResolverGenerator(registerContext); + resolverMap.calendar = { bindingResolver }; }; -FCalendar.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FCalendar.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext +) => { componentMap.calendar = FCalendarDesign; - propsResolverMap.calendar = propsResolver; + propsResolverMap.calendar = propsResolverGenerator(registerContext); }; export default FCalendar as typeof FCalendar & Plugin; diff --git a/packages/ui-vue/components/calendar/src/calendar.component.tsx b/packages/ui-vue/components/calendar/src/calendar.component.tsx index a9f97213eb84056fb8e359b19c69eca3336b57c9..03df22df288292b1c6d2e327eac771bbbd4650ce 100644 --- a/packages/ui-vue/components/calendar/src/calendar.component.tsx +++ b/packages/ui-vue/components/calendar/src/calendar.component.tsx @@ -1,34 +1,99 @@ -import { computed, defineComponent, ref } from 'vue'; +import { computed, defineComponent, nextTick, onMounted, onUnmounted, provide, ref, watch } from 'vue'; import { CalendarPropsType, calendarProps } from './calendar.props'; import useCalendar from './composition/use-calendar'; import { useDate } from './composition/use-date'; -import { DayInCalendar } from './types/calendar'; +import { CalendarViews, Compare, DayInCalendar, FilterRelation } from './types/calendar'; import { useCompare } from './composition/use-compare'; import { DateObject, weekDays } from './types/common'; import { ScheduleEvent } from './types/schedule'; -import { defaultNameOfMonths } from './types/month'; import FCalenderHeader from './components/header/header.component'; import FCalenderMonthView from './components/month/month-view.component'; import FCalenderWeekView from './components/week/week-view.component'; import FCalenderDayView from './components/day/day-view.component'; +import FCalendarYearView from './components/year/year-view.component'; + +import { useResizeObserver, getCustomClass, getCustomStyle, useDateFormat } from '@farris/ui-vue/components/common'; +import MoreEventList from './components/event-list/event-list.component'; +import { useCalendarLocale } from './composition/use-locales'; +import { isValid } from "date-fns"; + +import './calendar.scss'; +import { useCalendarEvents } from './composition/use-calendar-events'; export default defineComponent({ name: 'FCalendar', props: calendarProps, - emits: [], - setup(props: CalendarPropsType) { - const { sameDay } = useCompare(); - const { getToday } = useDate(); + emits: ['dayClick', 'dateChange', 'eventClick', 'moreClick', 'viewChange'], + setup(props: CalendarPropsType, context) { + const { titleFormat, today: todayText, prevMonth, nextMonth, dayViewTitle, monthViewTitle, weekViewTitle, weekDayLabels, monthLabels, + prevWeek, nextWeek, prevDay, nextDay, yearViewTitle, yearViewTitleFormat, thisYear, prevYear, nextYear, moreText, locale + } = useCalendarLocale(); + + const useCompareCompsition = useCompare(); + const useDateFormatComposition = useDateFormat(); + const useDateComposition = useDate(); + + const { sameDay } = useCompareCompsition; + const { getToday, convertDateToDateObject } = useDateComposition; + const { parseToDate } = useDateFormatComposition; + const firstDayOfTheWeek = ref(props.firstDayOfTheWeek); const events = ref(props.events); + const enableCrossDay = ref(props.enableCrossDay); const today = getToday(); + const activeDate = ref(props.activeDate); + const activeView = ref(0); + + const visible = ref(props.visible); + + const eventsComposition = useCalendarEvents(props, useDateComposition, useDateFormatComposition, useCompareCompsition, context, activeView, locale); + provide('eventsComposition', eventsComposition); + + const activeDay = ref({ year: today.year || 1, month: today.month || 1, day: today.day || 1 }); - const activeView = ref(3); + + function setActiveDay() { + typeof activeDate.value === 'string' && (activeDate.value = parseToDate(activeDate.value, 'yyyy-MM-dd')); + if (isValid(activeDate.value)) { + activeDay.value = convertDateToDateObject(activeDate.value); + } + } + setActiveDay(); + + watch(() => props.activeDate, () => { + activeDate.value = props.activeDate; + setActiveDay(); + }); + + watch(() => props.firstDayOfTheWeek, (newValue, oldValue) => { + if (newValue !== oldValue) { + firstDayOfTheWeek.value = newValue; + } + }); + + watch(() => props.enableCrossDay, (newValue, oldValue) => { + if (newValue !== oldValue) { + enableCrossDay.value = newValue; + } + }); + + const width = ref(props.width); + const height = ref(props.height); + const calendarRef = ref(); + + function setDefaultView() { + const view = CalendarViews.some(view => view === props.defaultView) ? props.defaultView : 'month'; + activeView.value = CalendarViews.indexOf(view) + 1; + } + + setDefaultView(); + + watch(() => props.defaultView, (newValue, oldValue) => { setDefaultView(); }); const { getMonthlyCalendar, @@ -38,15 +103,41 @@ export default defineComponent({ getDayInPreviousWeek, getDayInNextWeek, getDayInPreviousMonth, - getDayInNextMonth + getDayInNextMonth, + getNextYear, + getPreviousYear } = useCalendar(); const title = computed(() => { - return `${defaultNameOfMonths[activeDay.value.month || 1]} ${activeDay.value.year}`; + if (activeView.value === 1) { + return yearViewTitleFormat.replace('yyyy', '' + activeDay.value.year); + } + return titleFormat.replace('yyyy', '' + activeDay.value.year).replace('MM', '' + activeDay.value.month); + }); + + const currentText = computed(() => { + if (activeView.value === 1) { + return thisYear; + } + return todayText; + }); + + const daysInWeek = computed(() => { + const dayLabels: any = []; + const firstDayIndex = weekDays.indexOf(firstDayOfTheWeek.value); + if (firstDayIndex !== -1) { + let idx: number = firstDayIndex; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < weekDays.length; i++) { + dayLabels.push(weekDayLabels[weekDays[idx]]); + idx = weekDays[idx] === 'Sat' ? 0 : idx + 1; + } + } + return dayLabels; }); const dates = computed(() => { - const weekItems = getMonthlyCalendar(activeDay.value.month || 1, activeDay.value.year || 1, firstDayOfTheWeek.value); + const weekItems = getMonthlyCalendar(activeDay.value.month || 1, activeDay.value.year || 1, firstDayOfTheWeek.value, activeDay.value); return weekItems; }); @@ -55,7 +146,8 @@ export default defineComponent({ activeDay.value.day || 1, activeDay.value.month || 1, activeDay.value.year || 1, - firstDayOfTheWeek.value + firstDayOfTheWeek.value, + activeDay.value ); return weekItem; }); @@ -70,78 +162,312 @@ export default defineComponent({ return weekDays[dayIndexInWeek]; }); - const shouldShowMonthlyView = computed(() => { - return activeView.value === 3; + const isCurrent = computed(() => { + if (activeView.value === 1) { + return activeDay.value.year === today.year; + } + return activeDay.value.month === today.month && activeDay.value.year === today.year; }); - const shouldShowWeeklyView = computed(() => { - return activeView.value === 2; - }); + const searchConditons = (startDate: DateObject, endDate: DateObject) => { + return [{ + filterField: props.eventDateField, + value: `${startDate.year}-${startDate.month}-${startDate.day} 00:00:00`, + lbracket: '', + rbracket: '', + relation: FilterRelation.And, + compare: Compare.GreaterOrEqual, + }, { + filterField: props.eventDateField, + value: `${endDate.year}-${endDate.month}-${endDate.day} 23:59:59`, + lbracket: '', + rbracket: '', + relation: FilterRelation.Empty, + compare: Compare.LessOrEqual, + }]; + }; - const shouldShowDailyView = computed(() => { - return activeView.value === 1; - }); + const emitDateChange = () => { + const start = dates.value[0].days[0].date; + const end = dates.value[5].days[6].date; + context.emit('dateChange', { conditions: searchConditons(start, end), start, end, view: 'month' }); + }; + + const emitYearChange = () => { + const start = { year: activeDay.value.year, month: 1, day: 1 }; + const end = { year: activeDay.value.year, month: 12, day: 31 }; + context.emit('dateChange', { conditions: searchConditons(start, end), start, end, view: 'year' }); + }; - function changeActiveView(viewIndex: number) { - activeView.value = viewIndex; - } function previous(viewIndex: number) { if (viewIndex === 1) { - activeDay.value = getPreviousDay(activeDay.value); + activeDay.value = getPreviousYear(activeDay.value); + emitYearChange(); } if (viewIndex === 2) { - activeDay.value = getDayInPreviousWeek(activeDay.value); + activeDay.value = getDayInPreviousMonth(activeDay.value); + emitDateChange(); } if (viewIndex === 3) { - activeDay.value = getDayInPreviousMonth(activeDay.value); + activeDay.value = getDayInPreviousWeek(activeDay.value); + } + if (viewIndex === 4) { + activeDay.value = getPreviousDay(activeDay.value); } } function next(viewIndex: number) { if (viewIndex === 1) { - activeDay.value = getNextDay(activeDay.value); + activeDay.value = getNextYear(activeDay.value); + emitYearChange(); } if (viewIndex === 2) { - activeDay.value = getDayInNextWeek(activeDay.value); + activeDay.value = getDayInNextMonth(activeDay.value); + emitDateChange(); } if (viewIndex === 3) { - activeDay.value = getDayInNextMonth(activeDay.value); + activeDay.value = getDayInNextWeek(activeDay.value); + } + if (viewIndex === 4) { + activeDay.value = getNextDay(activeDay.value); } } + const emitViewChange = () => { + const viewName = CalendarViews[activeView.value - 1]; + if (viewName === 'year') { + emitYearChange(); + } else { + emitDateChange(); + } + }; + + const prevTitle = computed(() => { + return [prevYear, prevMonth, prevWeek, prevDay][activeView.value - 1]; + }); + const nextTitle = computed(() => { + return [nextYear, nextMonth, nextWeek, nextDay][activeView.value - 1]; + }); + function resetToToday() { activeDay.value = { year: today.year || 1, month: today.month || 1, day: today.day || 1 }; + emitViewChange(); + } + + const onDayClick = (day: DateObject) => { + context.emit('dayClick', day); + }; + + + /** + * 获取容器内容区域尺寸,兼容Firefox浏览器 + * @param element 容器元素 + * @returns 包含宽度和高度的对象 + */ + function getContentSize(element: HTMLElement): { width: number; height: number } { + if (!element) { + return { width: 0, height: 0 }; + } + + // Firefox兼容处理 + if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { + // Firefox中使用getBoundingClientRect获取包含padding的尺寸 + const computedStyle = window.getComputedStyle(element); + const rect = element.getBoundingClientRect(); + + // 获取padding值 + const paddingLeft = parseInt(computedStyle.paddingLeft) || 0; + const paddingRight = parseInt(computedStyle.paddingRight) || 0; + const paddingTop = parseInt(computedStyle.paddingTop) || 0; + const paddingBottom = parseInt(computedStyle.paddingBottom) || 0; + + const borderLeft = parseFloat(computedStyle.borderLeftWidth) || 0; + const borderRight = parseFloat(computedStyle.borderRightWidth) || 0; + const borderTop = parseFloat(computedStyle.borderTopWidth) || 0; + const borderBottom = parseFloat(computedStyle.borderBottomWidth) || 0; + + // 计算内容区域尺寸 + const contentWidth = rect.width - paddingLeft - paddingRight - borderLeft - borderRight; + const contentHeight = rect.height - paddingTop - paddingBottom - borderTop - borderBottom; + + return { + width: contentWidth, + height: contentHeight + }; + } else { + // 其他浏览器可以直接使用clientWidth/clientHeight + return { + width: element.clientWidth, + height: element.clientHeight + }; + } + } + + const setContainerSize = () => { + if (props.fit && calendarRef.value) { + const element = calendarRef.value; + // 安全地获取和解析 minHeight + const computedStyle = window.getComputedStyle(element); + const minHeightStr = computedStyle.minHeight; + let minHeight = 800; // 默认值 + + if (minHeightStr && minHeightStr.endsWith('px')) { + const parsedMinHeight = parseInt(minHeightStr.replace('px', ''), 10); + if (!isNaN(parsedMinHeight)) { + minHeight = parsedMinHeight; + } + } + + // const { width: parentWidth, height: parentHeight } = parentElement.getBoundingClientRect(); + const {width: clientWidth, height: clientHeight} = getContentSize(element); + width.value = clientWidth; + height.value = clientHeight; + + if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { + height.value -= 35; + } else { + // if(element.parentElement) { + // const {height: parentHeight} = getContentSize(element.parentElement); + // height.value = clientHeight - (parentHeight - clientHeight); + // } + + height.value = height.value > minHeight ? height.value : minHeight; + } + } + }; + + function changeActiveView(viewIndex: number) { + activeView.value = viewIndex; + emitViewChange(); + nextTick(() => { + setContainerSize(); + }); + } + + let resizeObserver: any = null; + + watch(() => props.visible, (newValue, oldValue) => { + if (newValue !== oldValue) { + visible.value = newValue; + if (!newValue) { + resizeObserver && resizeObserver(); + } else { + setTimeout(() => { + resizeObserver = useResizeObserver(calendarRef.value, setContainerSize); + }); + } + } + }); + + onMounted(() => { + setTimeout(() => { + + if (visible.value) { + resizeObserver = useResizeObserver(calendarRef.value, setContainerSize); + } + }); + }); + + onUnmounted(() => { + resizeObserver && resizeObserver(); + }); + + const calendarClass = computed(() => { + return getCustomClass({ + 'f-calendar d-flex f-calendar-cross-day h-100 flex-column': true, + 'f-calendar-highlight-weekend': props.highLightWeekend, + ['f-calendar-activeview-' + CalendarViews[activeView.value - 1]]: true + }, props.customClass); + }); + + function loadEvents(data: any) { + events.value = data || []; + } + + const moreEvents = ref([]); + const moreEventsPanelRef = ref(); + function onMonthMoreClick($event: any) { + moreEvents.value = $event.events || []; + context.emit('moreClick', $event); } + function onYearMoreClick(event: any, date: any) { + context.emit('moreClick', date); + } + + const popuperOverRef = computed(() => { + return moreEventsPanelRef.value?.popoverRef; + }); + + const calendarStyles = computed(() => { + const styleObject = { + minHeight: '800px', + } as Record; + return getCustomStyle(styleObject, props?.customStyle); + }); + + context.expose({ loadEvents }); + return () => { - return ( -
+ return visible.value && ( +
changeActiveView(viewIndex)} onPrevious={(viewIndex: number) => previous(viewIndex)} onNext={(viewIndex: number) => next(viewIndex)} onResetToToday={() => resetToToday()}> -
- {shouldShowDailyView.value && ( + +
+ {activeView.value === 4 && ( )} - {shouldShowWeeklyView.value && ( + {activeView.value === 3 && ( )} - {shouldShowMonthlyView.value && ( + {activeView.value === 2 && ( + events={events.value} + width={width.value} + height={height.value - 60} + eventDateField={props.eventDateField} + startDateField={props.startDateField} + endDateField={props.endDateField} + customEventStyles={props.customEventStyles} + enableCrossDay={enableCrossDay.value} + eventItemFormatter={props.eventItemFormatter} + popoverRef={popuperOverRef.value} + moreText={moreText} + onClick={onDayClick} + showBorder={ props.enableWeekdayBorder } + onMoreClick={onMonthMoreClick}> + )} + {activeView.value === 1 && ( + onYearMoreClick($event, date)}> )}
+
); }; diff --git a/packages/ui-vue/components/calendar/src/calendar.props.ts b/packages/ui-vue/components/calendar/src/calendar.props.ts index 88421962ffb8e1afba7f9d34379c3466a1cec9f4..9a34f3ad556f1f7315ebf1dac8581b266d71b56c 100644 --- a/packages/ui-vue/components/calendar/src/calendar.props.ts +++ b/packages/ui-vue/components/calendar/src/calendar.props.ts @@ -1,17 +1,48 @@ -import { ExtractPropTypes } from 'vue'; -import { ScheduleEvent } from './types/schedule'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { ExtractPropTypes, PropType } from 'vue'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; import calendarSchema from './schema/calendar.schema.json'; -import propertyConfig from './property-config/calendar.property-config.json'; +import { JSX } from 'vue/jsx-runtime'; + +export type CalendarCallBackContext = { + parseToDate: (date: string, format: string) => Date, + formatTo: (date: Date, format: string) => string, + getValue: (field: string, data: any) => any, + view: 'year' | 'month' | 'week' | 'day' +}; export const calendarProps = { - events: { Type: Array, default: [] }, - firstDayOfTheWeek: { type: String, default: 'Sun.' } + events: { type: Array>, default: [] }, + firstDayOfTheWeek: { type: String, default: 'Sun' }, + enableMoreView: { type: Boolean, default: false }, + defaultView: { type: String, default: 'month' }, + fit: { type: Boolean, default: true }, + height: { type: Number, default: 600 }, + width: { type: Number, default: 800 }, + highLightWeekend: { type: Boolean, default: false }, + eventItemFormatter: { type: Function as PropType<(event: any, context: CalendarCallBackContext) => JSX.Element> }, + customEventStyles: { type: Function as PropType<(event: any, context: CalendarCallBackContext) => Record> }, + eventDateField: { type: String, default: 'startDate' }, + customClass: { type: String, default: '' }, + startDateField: { type: String, default: 'startDate' }, + endDateField: { type: String, default: 'endDate' }, + enableCrossDay: { type: Boolean, default: false }, + idField: { type: String, default: 'id' }, + activeDate: { type: Date, default: new Date() }, + textField: { type: String, default: 'title' }, + customStyle:{ type: String, default: '' }, + enableWeekdayBorder: { type: Boolean, default: false}, + visible: { type: Boolean, default: true }, } as Record; export type CalendarPropsType = ExtractPropTypes; -export const propsResolver = createPropsResolver(calendarProps, calendarSchema, schemaMapper, schemaResolver, propertyConfig); +export const propsResolver = createPropsResolver(calendarProps, calendarSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator( + calendarSchema, + calendarSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/discussion-list/src/composition/use-discussion-list.ts b/packages/ui-vue/components/calendar/src/calendar.scss similarity index 100% rename from packages/ui-vue/components/discussion-list/src/composition/use-discussion-list.ts rename to packages/ui-vue/components/calendar/src/calendar.scss diff --git a/packages/ui-vue/components/calendar/src/components/day/day-view.component.tsx b/packages/ui-vue/components/calendar/src/components/day/day-view.component.tsx index 59e0062c1c1fb8171fb962f6a8515e8b490a5c54..b9661cd77171abebfd5af423b8501cc9557bc946 100644 --- a/packages/ui-vue/components/calendar/src/components/day/day-view.component.tsx +++ b/packages/ui-vue/components/calendar/src/components/day/day-view.component.tsx @@ -4,6 +4,7 @@ import { useCompare } from '../../composition/use-compare'; import { DateObject } from '../../types/common'; import { DayInCalendar } from '../../types/calendar'; import { ItemInSchedule, ScheduleEvent, TimeInSchedule } from '../../types/schedule'; +import { useDate } from '../../composition/use-date'; export default defineComponent({ name: 'FCalendarDayView', @@ -18,6 +19,8 @@ export default defineComponent({ const events = ref(props.events); const items = ref([]); + const { convertDateToDateObject } = useDate(); + const { equal } = useCompare(); @@ -30,7 +33,7 @@ export default defineComponent({ minute: time.minute, second: time.second }; - const cellEvents = events.value.filter((eventItem: ScheduleEvent) => equal(eventItem.starts, cellDate)) as ScheduleEvent[]; + const cellEvents = events.value.filter((eventItem: ScheduleEvent) => equal(convertDateToDateObject(eventItem[props.dateField]), cellDate)) as ScheduleEvent[]; return [ { day: cellDate, @@ -134,8 +137,8 @@ export default defineComponent({ const marginTop = 2; const borderBottom = 1; const borderTop = 1; - const start = (eventItem.starts.hour || 0) + (eventItem.starts.minute || 0) / 60; - const end = (eventItem.ends.hour || 0) + (eventItem.ends.minute || 0) / 60; + const start = (eventItem.starts?.hour || 0) + (eventItem.starts?.minute || 0) / 60; + const end = (eventItem.ends?.hour || 0) + (eventItem.ends?.minute || 0) / 60; const actualHeight = height * (end - start) - marginTop - marginBottom - borderTop - borderBottom; const styleObject = { height: `${actualHeight}px` diff --git a/packages/ui-vue/components/calendar/src/components/day/day-view.props.ts b/packages/ui-vue/components/calendar/src/components/day/day-view.props.ts index e14427250ab194a02420b75ca8aa47f0729627aa..53c52c6aba0711e620da97e0ef421d1d3f35fa2c 100644 --- a/packages/ui-vue/components/calendar/src/components/day/day-view.props.ts +++ b/packages/ui-vue/components/calendar/src/components/day/day-view.props.ts @@ -2,10 +2,11 @@ import { ExtractPropTypes } from 'vue'; import { DateObject, EventItem } from '../../types/common'; export const dayViewProps = { - day: { Type: Object, default: {} }, - dayInWeek: { Type: String, default: '' }, - enableMarkCurrent: { Type: Boolean, default: true }, - events: { Type: Array, default: [] } + day: { type: Object, default: {} }, + dayInWeek: { type: String, default: '' }, + enableMarkCurrent: { type: Boolean, default: true }, + events: { type: Array, default: [] }, + dateField: { type: String, default: 'date' } }; export type DayViewPropsType = ExtractPropTypes; diff --git a/packages/ui-vue/components/calendar/src/components/event-list/event-list.component.tsx b/packages/ui-vue/components/calendar/src/components/event-list/event-list.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..10169fe0f7d794f5f4fe3752fd7eea69342c9dc2 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/components/event-list/event-list.component.tsx @@ -0,0 +1,44 @@ +import { computed, defineComponent, inject, ref } from "vue"; +import FPopover from '@farris/ui-vue/components/popover'; +import { UseCalendarEvents } from "../../composition/use-calendar-events"; + +export default defineComponent({ + name: "MoreEventList", + props: { + events: Array, + popoverOffsetX: Number + }, + setup(props, context) { + const popoverRef = ref(); + + const popoverInstance = computed(() => { + return popoverRef; + }); + + const events = computed(() => props.events); + + const { renderEventItemForMore } = inject('eventsComposition') as UseCalendarEvents; + + + const popoverOffsetX = ref(0); + + context.expose({ + popoverRef: popoverInstance + }); + + return () => { + return +
+
+
    + {events.value?.map((event: any) => renderEventItemForMore(event))} +
+
+
+
; + }; + } +}); diff --git a/packages/ui-vue/components/calendar/src/components/header/header.component.tsx b/packages/ui-vue/components/calendar/src/components/header/header.component.tsx index 83053eac6e841082207e15a57e19c0e8cbb78828..b07754a3da8e5f98f777d4e5b0630ba8aa4d26f7 100644 --- a/packages/ui-vue/components/calendar/src/components/header/header.component.tsx +++ b/packages/ui-vue/components/calendar/src/components/header/header.component.tsx @@ -1,19 +1,25 @@ import { SetupContext, computed, defineComponent, ref, watch } from 'vue'; import { HeaderProps, headerProps } from './header.props'; +import { CalendarView, CalendarViews } from '../../types/calendar'; export default defineComponent({ name: 'FCalendarHeader', props: headerProps, - emits: ['ViewChange', 'Previous', 'Next', 'ResetToToday'], + emits: ['viewChange', 'previous', 'next', 'resetToToday'], setup(props: HeaderProps, context) { const title = ref(props.title); const dailyViewTitle = ref(props.dailyViewTitle); const weeklyViewTitle = ref(props.weeklyViewTitle); const monthlyViewTitle = ref(props.monthlyViewTitle); + const yearViewTitle = ref(props.yearViewTitle); const switchPadding = 2; const switchButtonWidth = 62; const activeViewIndex = ref(props.activeView); - const todayButtonText = ref('Today'); + // const todayButtonText = ref(props.todayText || 'Today'); + + watch(() => props.activeView, (newValue, oldValue) => { + activeViewIndex.value = newValue; + }); watch( () => props.title, @@ -31,75 +37,72 @@ export default defineComponent({ }); const activeViewTitle = computed(() => { - if (activeViewIndex.value === 1) { + if (activeViewIndex.value === 4) { return dailyViewTitle.value; } - if (activeViewIndex.value === 2) { + if (activeViewIndex.value === 3) { return weeklyViewTitle.value; } + if (activeViewIndex.value === 1) { + return yearViewTitle.value; + } return monthlyViewTitle.value; }); - function changeToDailyView() { - activeViewIndex.value = 1; - context.emit('ViewChange', 1); - } - - function changeToWeeklyView() { - activeViewIndex.value = 2; - context.emit('ViewChange', 2); - } - - function changeToMonthlyView() { - activeViewIndex.value = 3; - context.emit('ViewChange', 3); + function changeCalendarView(view: CalendarView) { + const viewIndex = CalendarViews.indexOf(view) + 1; + activeViewIndex.value = viewIndex; + context.emit('viewChange', viewIndex); } function navigateToPrevious() { - context.emit('Previous', activeViewIndex.value); + context.emit('previous', activeViewIndex.value); } function navigateToNext() { - context.emit('Next', activeViewIndex.value); + context.emit('next', activeViewIndex.value); } function navigateToToday() { - context.emit('ResetToToday'); + context.emit('resetToToday'); } return () => { return ( -
+
{title.value}
-
-
navigateToToday()}> - {todayButtonText.value} -
+
+
-
navigateToPrevious()}> +
navigateToPrevious()} title={props.previousText}>
-
navigateToNext()}> +
navigateToNext()} title={props.nextText}>
-
-
- changeToDailyView()}> - {dailyViewTitle.value} - - changeToWeeklyView()}> - {weeklyViewTitle.value} + {props.enableMoreView &&
+
+ changeCalendarView('year')}> + {yearViewTitle.value} - changeToMonthlyView()}> + changeCalendarView('month')}> {monthlyViewTitle.value} + {/* changeCalendarView('week')}> + {weeklyViewTitle.value} + + changeCalendarView('day')}> + {dailyViewTitle.value} + */}
{activeViewTitle.value}
-
+
}
); }; diff --git a/packages/ui-vue/components/calendar/src/components/header/header.props.ts b/packages/ui-vue/components/calendar/src/components/header/header.props.ts index 1a371b8c8d0c47ba1370cda67c5b2f5d3cf7e185..b05eaf70acaba81dde064e1444af12c5da1dbfe9 100644 --- a/packages/ui-vue/components/calendar/src/components/header/header.props.ts +++ b/packages/ui-vue/components/calendar/src/components/header/header.props.ts @@ -1,11 +1,17 @@ import { ExtractPropTypes } from 'vue'; export const headerProps = { - activeView: { Type: Number, default: 3 }, - title: { Type: String, default: '' }, - dailyViewTitle: { Type: String, default: 'Day' }, - weeklyViewTitle: { Type: String, default: 'Week' }, - monthlyViewTitle: { Type: String, default: 'Month' } + activeView: { type: Number, default: 3 }, + title: { type: String, default: '' }, + dailyViewTitle: { type: String, default: 'Day' }, + weeklyViewTitle: { type: String, default: 'Week' }, + monthlyViewTitle: { type: String, default: 'Month' }, + todayText: { type: String, default: 'Today' }, + previousText: { type: String, default: 'Previous' }, + nextText: { type: String, default: 'Next' }, + enableMoreView: { type: Boolean, default: false }, + isCurrent: { type: Boolean, default: false }, + yearViewTitle: { type: String, default: 'Year' }, }; export type HeaderProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/calendar/src/components/month/month-view.component.tsx b/packages/ui-vue/components/calendar/src/components/month/month-view.component.tsx index 79534bcc7f333e5aef03111a0292edc3f61eb315..991f17bf05eeb11ceaadb78f44e06a11db167437 100644 --- a/packages/ui-vue/components/calendar/src/components/month/month-view.component.tsx +++ b/packages/ui-vue/components/calendar/src/components/month/month-view.component.tsx @@ -1,16 +1,18 @@ -import { SetupContext, defineComponent, ref, watch } from 'vue'; +import { computed, defineComponent, inject, nextTick, onMounted, ref, watch } from 'vue'; import { MonthViewProps, monthViewProps } from './month-view.props'; import { KeyCode, useEvent } from '../../composition/use-event'; import { useCompare } from '../../composition/use-compare'; import { DateObject, EventItem, MonthTag } from '../../types/common'; import { ScheduleEvent } from '../../types/schedule'; import { DayInCalendar, WeekInCalendar } from '../../types/calendar'; -import { defaultNameOfMonths } from '../../types/month'; +import { UseCalendarEvents } from '../../composition/use-calendar-events'; +import { cloneDeep } from 'lodash-es'; + export default defineComponent({ name: 'FCalendarMonthView', props: monthViewProps, - emits: ['click','keyDown'], + emits: ['click', 'keyDown', 'moreClick'], setup(props: MonthViewProps, context) { const daysInWeek = ref(props.daysInWeek); const enableKeyboadNavigate = ref(props.enableKeyboadNavigate); @@ -18,23 +20,28 @@ export default defineComponent({ const activeDay = ref(props.activeDay); const events = ref(props.events); - const { equal, sameDay } = useCompare(); + const { assignEventsToWeeks, assignEventsToDays, renderEventItem, useCompareCompsition } = inject('eventsComposition') as UseCalendarEvents; + + const { equal } = useCompareCompsition; const { getKeyCodeFromEvent } = useEvent(); + const calendarContainerRef = ref(); function fillEvents(dates: WeekInCalendar[], events: EventItem[]) { - dates.forEach((week: WeekInCalendar) => { - week.days.forEach((dayItem: DayInCalendar) => { - const matchedEvent = events.filter((eventItem: EventItem) => sameDay(eventItem.starts, dayItem.date)) as EventItem[]; - if (matchedEvent && matchedEvent.length) { - dayItem.events = [...matchedEvent]; - } - }); - }); - return dates; + if (props.enableCrossDay) { + return assignEventsToWeeks(dates, events); + } + return assignEventsToDays(dates, events); } const dates = ref(fillEvents(props.dates, events.value)); + watch( + () => props.enableCrossDay, + () => { + dates.value = fillEvents(props.dates, props.events); + } + ); + watch( () => props.dates, () => { @@ -50,10 +57,15 @@ export default defineComponent({ watch( () => props.events, () => { - events.value = props.events; + events.value = props.events; + dates.value = fillEvents(props.dates, props.events); } ); + watch(() =>props.daysInWeek, (newValue, oldValue) => { + daysInWeek.value = newValue; + }); + function isDateSame(day: DateObject) { return !!activeDay.value && equal({ year: activeDay.value.year, month: activeDay.value.month, day: activeDay.value.day }, day); } @@ -61,7 +73,10 @@ export default defineComponent({ const dayContainerClass = (currentDay: DayInCalendar, weekIndex: number, dayIndex: number) => { const notInCurrentMonth = currentDay.monthTag === MonthTag.previous || currentDay.monthTag === MonthTag.next; const classObject = { - 'f-datepicker-no-currmonth': notInCurrentMonth + 'f-datepicker-no-currmonth': notInCurrentMonth, + 'f-calendar-weekday': currentDay.weekEnd, + 'f-calendar-workday': !currentDay.weekEnd, + 'f-calendar-today': currentDay.isCurrent } as Record; const className = `d_${weekIndex}_${dayIndex}`; classObject[className] = true; @@ -80,12 +95,27 @@ export default defineComponent({ return classObject; }; - function onClick($event: Event, target: DayInCalendar) { + function onClick($event: Event, target: any) { + if (props.disabled || target.showMore) { + return; + } $event.stopPropagation(); - context.emit('click', target.date); + context.emit('click', {date: target.date, view: 'month'}); + } + + + function onShowMore($event: Event, dayEvents: any) { + if (props.disabled) { + return; + } + // $event.stopPropagation(); + context.emit('moreClick', {date: dayEvents.date, view: 'month'}); } function onKeyDown($event: KeyboardEvent, target: DayInCalendar) { + if (props.disabled) { + return; + } const keyCode: number = getKeyCodeFromEvent($event); if (keyCode !== KeyCode.tab) { $event.preventDefault(); @@ -98,57 +128,170 @@ export default defineComponent({ } } + const tableSizeStyle = computed(() => { + return { + width: `${props.width}px`, + height: `${props.height}px` + }; + }); + + /** 计算日历网格内可容纳的行数 */ + const calendarGridRows = ref(0); + + const getCalendarGridRows = () => { + const container = calendarContainerRef.value; + if (!container) { + return 0; + } + + const rowElement = container.querySelector('.f-calendar--daygrid-row'); + if (!rowElement) { + return 0; + } + + const rowHeight = rowElement['offsetHeight']; + // 确保计算结果不为负数 + return Math.max(0, Math.floor(rowHeight / 19) - 1); + }; + + function rederCalendarHeader() { + return
+ + {daysInWeek.value && + daysInWeek.value.map((day: string) => { + return ( + + ); + })} +
+ {day} +
+
; + } + + function renderCalendarDaygridBg(week: WeekInCalendar, weekIndex: number) { + return
+ + + + {week.days && + week.days.map((item: DayInCalendar, dayIndex: number) => { + return ( + + ); + })} + + +
onClick(e, item)} style="position: relative;"> +
+
; + } + + function renderCalendarDaygridLabels(week: WeekInCalendar, weekIndex: number) { + return week.days && week.days.map((item: DayInCalendar, dayIndex: number) => { + return ( + +
+
{item.date.day}
+
+ {item.date.day === 1 ? props.monthLabels[item.date.month || 1] : ''} +
+
+ + ); + }); + } + + function renderCrossDayEvents(week: WeekInCalendar) { + // 处理边界情况,确保 calendarGridRows.value 有默认值 + const maxVisibleEvents = Math.max(0, calendarGridRows.value || getCalendarGridRows()); + const showMore = !!(week.events && week.events.length > maxVisibleEvents); + const eventItems = cloneDeep([...week.events || []]); + const events = showMore ? eventItems.slice(0, maxVisibleEvents) : eventItems; + + if (showMore && events && events.length > 0) { + events[events.length - 1].forEach(item => { + if (item.data) { + item.showMore = true; + item.moreCount = week.events!.length - maxVisibleEvents + 1; + } + }); + } + + return events.map((weekDayEvents, rowIndex: number) => { + return + {weekDayEvents.map((event, colIndex: number) => { + if (event.showMore) { + // v-popover:toggle={props.popoverRef} + return onClick($event, event)}>{ + onShowMore($event, week.days[colIndex])}> +
{ props.moreText.replace('#count#', event.moreCount)}
+
+ }; + } + return onClick($event, week.days[colIndex])}> + {event.data && renderEventItem(event)} + ; + })} + ; + }); + } + + function renderDaygridContent() { + return
+
+ {dates.value.map((week: WeekInCalendar, weekIndex: number) => { + + return
+ {renderCalendarDaygridBg(week, weekIndex)} +
+ + + + {renderCalendarDaygridLabels(week, weekIndex)} + + + + {renderCrossDayEvents(week)} + +
+
+
; + })} +
+
; + } + + watch(() => props.height, (newValue, oldValue) => { + if (newValue !== oldValue) { + nextTick(() => { + calendarGridRows.value = getCalendarGridRows(); + dates.value = [...dates.value]; + }); + } + }); + + onMounted(() => { + calendarGridRows.value = getCalendarGridRows(); + }); + return () => { return ( -
- +
+
- {daysInWeek.value && - daysInWeek.value.map((day: string) => { - return ( - - ); - })} + - {dates.value && - dates.value.map((week: WeekInCalendar, weekIndex: number) => { - return ( - - {week.days && - week.days.map((item: DayInCalendar, dayIndex: number) => { - return ( - - ); - })} - - ); - })} + + +
- {day} - + {rederCalendarHeader()} +
onClick(payload, item)} - onKeydown={(payload: KeyboardEvent) => onKeyDown(payload, item)}> -
-
{item.date.day}
-
- {item.date.day === 1 ? defaultNameOfMonths[item.date.month || 1] : ''} -
-
- {item.events && - item.events.length && - item.events.map((eventItem: EventItem) => { - return ( -
- {eventItem.title} -
- ); - })} -
+ {renderDaygridContent()} +
diff --git a/packages/ui-vue/components/calendar/src/components/month/month-view.props.ts b/packages/ui-vue/components/calendar/src/components/month/month-view.props.ts index 35fd9c646be0789f1bb3f1cc943a64b8a01eb121..e900a0df8b5b83cc05c4bc55bea90582885a8a30 100644 --- a/packages/ui-vue/components/calendar/src/components/month/month-view.props.ts +++ b/packages/ui-vue/components/calendar/src/components/month/month-view.props.ts @@ -1,15 +1,31 @@ -import { ExtractPropTypes } from 'vue'; +import { ExtractPropTypes, Prop, PropType } from 'vue'; import { weekDays } from '../../types/common'; import { WeekInCalendar } from '../../types/calendar'; import { ScheduleEvent } from '../../types/schedule'; +import { JSX } from 'vue/jsx-runtime'; export const monthViewProps = { - dates: { Type: Array, default: [] }, - daysInWeek: { Type: Array, default: weekDays }, - enableKeyboadNavigate: { Type: Boolean, default: true }, - enableMarkCurrent: { Type: Boolean, default: true }, - events: { Type: Array, default: [] }, - activeDay: { Type: Object, default: null } + dates: { type: Array, default: [] }, + daysInWeek: { type: Array, default: weekDays }, + enableKeyboadNavigate: { type: Boolean, default: true }, + enableMarkCurrent: { type: Boolean, default: true }, + events: { type: Array, default: [] }, + activeDay: { type: Object, default: null }, + width: { type: Number, default: 0 }, + height: { type: Number, default: 0 }, + disabled: { type: Boolean, default: false }, + monthLabels: { type: Object, default: {} }, + eventDateField: { type: String, default: 'startDate' }, + enableCrossDay: { type: Boolean, default: false }, + startDateField: { type: String, default: 'startDate' }, + endDateField: { type: String, default: 'endDate' }, + idField: { type: String, default: 'id' }, + customEventStyles: { type: Function, default: null }, + eventItemFormatter: { type: Function as PropType<(params: any) => string>, default: null }, + popoverRef: { type: Object, default: null }, + /** 月视图头部应用边框线条与背景色,默认不启用 */ + showBorder: { type: Boolean, default: false }, + moreText: { type: String, default: '更多' }, }; export type MonthViewProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/calendar/src/components/week/week-view.component.tsx b/packages/ui-vue/components/calendar/src/components/week/week-view.component.tsx index 2c38f62c038fe2b5c8a909318cd7c1d145476e37..860a21ccaab479f21921cdd0bdd4da2b0ab63e0d 100644 --- a/packages/ui-vue/components/calendar/src/components/week/week-view.component.tsx +++ b/packages/ui-vue/components/calendar/src/components/week/week-view.component.tsx @@ -4,6 +4,7 @@ import { useCompare } from '../../composition/use-compare'; import { DateObject, EventItem } from '../../types/common'; import { ItemInSchedule, ScheduleEvent, TimeInSchedule } from '../../types/schedule'; import { DayInCalendar, WeekInCalendar } from '../../types/calendar'; +import { useDate } from '../../composition/use-date'; export default defineComponent({ name: 'FCalendarWeekView', @@ -15,11 +16,12 @@ export default defineComponent({ const daysInWeek = ref(props.daysInWeek); const week = ref(props.week); const enableMarkCurrent = ref(props.enableMarkCurrent); - const events = ref(props.events); + const events = ref[]>(props.events); const items = ref([]); const { equal } = useCompare(); + const { convertDateToDateObject } = useDate(); function buildWeekViewItemCell(time: DateObject) { const cells = week.value.days.map((day: DayInCalendar) => { @@ -31,7 +33,7 @@ export default defineComponent({ minute: time.minute, second: time.second }; - const cellEvents = events.value.filter((eventItem: ScheduleEvent) => equal(eventItem.starts, cellDate)) as ScheduleEvent[]; + const cellEvents = events.value.filter((eventItem: Record) => equal(convertDateToDateObject(eventItem[props.startDateField]), cellDate)); return { day: cellDate, events: cellEvents @@ -45,8 +47,8 @@ export default defineComponent({ for (let i = 0; i < 24; i++) { const oClock = { hour: i, minute: 0, second: 0 }; const halfPast = { hour: i, minute: 30, second: 0 }; - const oClockCells = buildWeekViewItemCell(oClock); - const halfPastCells = buildWeekViewItemCell(halfPast); + const oClockCells: any = buildWeekViewItemCell(oClock); + const halfPastCells: any = buildWeekViewItemCell(halfPast); result.push({ time: oClock, events: oClockCells, @@ -137,7 +139,7 @@ export default defineComponent({ const marginTop = 2; const borderBottom = 1; const borderTop = 1; - const start = (eventItem.starts.hour || 0) + (eventItem.starts.minute || 0) / 60; + const start = (eventItem[props.startDateField].hour || 0) + (eventItem[props.startDateField].minute || 0) / 60; const end = (eventItem.ends.hour || 0) + (eventItem.ends.minute || 0) / 60; const actualHeight = height * (end - start) - marginTop - marginBottom - borderTop - borderBottom; const styleObject = { diff --git a/packages/ui-vue/components/calendar/src/components/week/week-view.props.ts b/packages/ui-vue/components/calendar/src/components/week/week-view.props.ts index ecaed64c8fa28170498022011cce087b2c35cc80..09a5008237bb92181c31efc6f2a00852ee120e01 100644 --- a/packages/ui-vue/components/calendar/src/components/week/week-view.props.ts +++ b/packages/ui-vue/components/calendar/src/components/week/week-view.props.ts @@ -1,12 +1,15 @@ -import { ExtractPropTypes } from 'vue'; -import { DateObject, weekDays } from '../../types/common'; -import { ScheduleEvent } from '../../types/schedule'; +import { ExtractPropTypes, PropType } from 'vue'; +import { weekDays } from '../../types/common'; +import { WeekInCalendar } from '../../types/calendar'; export const weekViewProps = { - daysInWeek: { Type: Array, default: weekDays }, - enableMarkCurrent: { Type: Boolean, default: true }, - events: { Type: Array, default: [] }, - week: { Type: Object, default: { days: [], weekNumber: 0, year: 0 } } + daysInWeek: { type: Array, default: weekDays }, + enableMarkCurrent: { type: Boolean, default: true }, + events: { type: Array>, default: [] }, + week: { type: Object as PropType, default: { days: [], weekNumber: 0, year: 0 } }, + dateField: { type: String, default: 'date' }, + startDateField: { type: String, default: 'startDate' }, + endDateField: { type: String, default: 'endDate' }, }; export type WeekViewPropsType = ExtractPropTypes; diff --git a/packages/ui-vue/components/calendar/src/components/year/year-view.component.tsx b/packages/ui-vue/components/calendar/src/components/year/year-view.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f3ce92c41e14e4eafb29621550aed36579665f32 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/components/year/year-view.component.tsx @@ -0,0 +1,104 @@ +import { computed, defineComponent, inject, nextTick, onMounted, ref, watch } from "vue"; +import { UseCalendarEvents } from "../../composition/use-calendar-events"; +import { cloneDeep } from "lodash-es"; + +export default defineComponent({ + name: "FCalendarYearView", + props: { + monthLabels: { type: Object, default: null }, + height: { type: Number, default: 300 }, + events: { type: Array, default: [] }, + dateField: { type: String, default: 'date' }, + activeYear: { type: Number, default: 2025 }, + moreText: { type: String, default: '更多' }, + }, + emits: ['moreClick'], + setup(props, context) { + const yearviewContainerRef = ref(); + const monthLabels = ref(props.monthLabels); + const { assignEventsToMonths, renderEventItemForMonths } = inject('eventsComposition') as UseCalendarEvents; + + /** 年视图下每个月最多显示的事件数量 */ + const showEventsTotal = ref(0); + const showEventItemsCount = () => { + const container = yearviewContainerRef.value; + if (!container) { + return 0; + } + + const rowElement = container.querySelector('.yearview-monthitem'); + const rowHeight = rowElement['offsetHeight']; + // 确保计算结果不为负数 + return Math.max(0, Math.floor((rowHeight - 40) / 24)); + }; + + const loadEventsWhithShowMore = (events: Array) => { + // 处理边界情况,确保 calendarGridRows.value 有默认值 + const showMore = !!(events && events.length > showEventsTotal.value); + if (showMore) { + const eventItems = cloneDeep(events); + const visibleEvents = showMore ? eventItems?.slice(0, showEventsTotal.value) : eventItems; + if (visibleEvents && visibleEvents.length > 0) { + const lastItem = visibleEvents[visibleEvents.length - 1]; + if (lastItem.data) { + lastItem.showMore = true; + lastItem.moreCount = events!.length - showEventsTotal.value + 1; + } + } + return visibleEvents; + } + return events; + }; + + const monthEvents = computed(() => { + return assignEventsToMonths(props.activeYear, props.events); + }); + + function onMoreClick(event: any, month: string) { + const date = { year: props.activeYear,month: parseInt(month, 10) }; + context.emit('moreClick', event, {date, view: 'year'}); + } + + watch(() => props.height, (newValue, oldValue) => { + if (newValue !== oldValue) { + nextTick(() => { + showEventsTotal.value = showEventItemsCount(); + monthLabels.value = {...props.monthLabels}; + }); + } + }); + + onMounted(() => { + showEventsTotal.value = showEventItemsCount(); + }); + + return () => { + return
+ +
; + }; + } +}); diff --git a/packages/ui-vue/components/calendar/src/composition/types.ts b/packages/ui-vue/components/calendar/src/composition/types.ts index 4e2de2a03f1519ead0ea8f5a833f293695e46edf..7bd12d234b17627cc79aee714a9ec681a4b94920 100644 --- a/packages/ui-vue/components/calendar/src/composition/types.ts +++ b/packages/ui-vue/components/calendar/src/composition/types.ts @@ -1,7 +1,6 @@ import { WeekInCalendar } from '../types/calendar'; import { DateObject, Period } from '../types/common'; import { NameOfMonths } from '../types/month'; -import { CalendarMonthViewWeekItem } from '../types/month-view'; export interface DateFormatInfo { value: string; @@ -17,7 +16,7 @@ export interface UseCompare { isPoint: (period: Period, date: DateObject) => boolean; - equalOrEarlier: (firstDate: DateObject, secondDate: DateObject) => boolean; + equalOrEarlier: (firstDate: DateObject, secondDate: DateObject, withTime?: boolean) => boolean; isInitializedDate: (date: DateObject) => boolean; @@ -52,6 +51,10 @@ export interface UseDate { getNearDate: (now: DateObject, min: DateObject, max: DateObject) => DateObject; getToday(): DateObject; + + isWeekEnd(date: DateObject): boolean; + + convertDateToDateObject: (date: Date | null) => DateObject; } export interface UseEvent { @@ -69,9 +72,9 @@ export interface UseMonth { } export interface UseCalendar { - getMonthlyCalendar: (month: number, year: number, firstDayOfWeek: string) => WeekInCalendar[]; + getMonthlyCalendar: (month: number, year: number, firstDayOfWeek: string, activeDay: DateObject) => WeekInCalendar[]; - getWeeklyCalendar: (day: number, month: number, year: number, firstDayOfWeek: string) => WeekInCalendar; + getWeeklyCalendar: (day: number, month: number, year: number, firstDayOfWeek: string, activeDay: DateObject) => WeekInCalendar; getPreviousDay: (date: DateObject) => DateObject; @@ -84,6 +87,10 @@ export interface UseCalendar { getDayInPreviousMonth: (date: DateObject) => DateObject; getDayInNextMonth: (date: DateObject) => DateObject; + + getNextYear: (date: DateObject) => DateObject; + + getPreviousYear: (date: DateObject) => DateObject; } export interface UseNumber { diff --git a/packages/ui-vue/components/calendar/src/composition/use-calendar-creator.ts b/packages/ui-vue/components/calendar/src/composition/use-calendar-creator.ts new file mode 100644 index 0000000000000000000000000000000000000000..b74c85bc8c447dcf7c0b00a1cf2a1b330d44dab5 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/composition/use-calendar-creator.ts @@ -0,0 +1,130 @@ +import { ComponentBuildInfo } from "@farris/ui-vue/components/component"; +import { ComponentSchema, DesignerHostService, DgControl } from "@farris/ui-vue/components/designer-canvas"; +import { DynamicResolver } from "@farris/ui-vue/components/dynamic-resolver"; +const ROOT_VIEW_MODEL_ID = 'root-viewmodel'; + +export function useCalendarCreator( + resolver: DynamicResolver, + designerHostService: DesignerHostService) { + + const { + formSchemaUtils, + designViewModelUtils, + } = designerHostService; + + /** + * 添加viewModel节点 + */ + function createViewModeNode(buildInfo: ComponentBuildInfo): any { + const viewModelNode = { + id: `${buildInfo.componentId}-component-viewmodel`, + code: `${buildInfo.componentId}-component-viewmodel`, + name: buildInfo.componentName, + bindTo: buildInfo.bindTo, + parent: ROOT_VIEW_MODEL_ID, + fields: [], + commands: [], + states: [], + enableValidation: true + }; + return viewModelNode; + } + + function createComponentRefNode(buildInfo: ComponentBuildInfo): any { + const componentRefNode = resolver.getSchemaByType('component-ref') as ComponentSchema; + Object.assign(componentRefNode, { + id: `${buildInfo.componentId}-component-ref`, + component: `${buildInfo.componentId}-component`, + }); + return componentRefNode; + } + /** + * 追加父容器 + */ + function wrapContainerSectionForComponent(componentRefNode: any, buildInfo: ComponentBuildInfo, viewModelNode: any) { + const parentContainerType = buildInfo?.parentComponentInstance?.schema?.type; + + // 填充类表单,拖拽日历时,不生成父标题区域 + const templateId = formSchemaUtils.getFormSchema()?.module?.templateId; + if (['double-list-template', 'tree-list-template', 'list-template'].includes(templateId)) { + return componentRefNode; + } + + // 1、将表格拖入无标题的目标区域,需要给表格追加Section容器 + const parentContainerWithoutTitle = [DgControl['content-container'].type, DgControl['response-layout-item'].type, DgControl['splitter-pane'].type, DgControl['drawer'].type]; + if (parentContainerType && parentContainerWithoutTitle.includes(parentContainerType)) { + const containerSection = resolver.getSchemaByType( + 'section', + { + parentComponentInstance: buildInfo.parentComponentInstance, + mainTitle: buildInfo.componentName + }, + designerHostService) as ComponentSchema; + if (containerSection && containerSection.contents && containerSection.contents.length) { + const section = containerSection.contents[0]; + section.contents = [componentRefNode]; + return containerSection; + } + } + return componentRefNode; + } + /** + * 创建日历组件内层级结构 + */ + function createCalendarComponentContents(buildInfo: ComponentBuildInfo) { + const calendar = resolver.getSchemaByType('calendar') as ComponentSchema; + Object.assign(calendar, { + id: buildInfo.componentId + '-calendar', + appearance: { + class: '' + }, + type: 'calendar', + dataSource: buildInfo.dataSource || '', + pagination: { + enable: false + } + }); + return [calendar]; + } + + function createComponentNode(buildInfo: ComponentBuildInfo): any { + const componentNode = resolver.getSchemaByType('component') as ComponentSchema; + const contents = createCalendarComponentContents(buildInfo); + + // 目标区域为页面内容主区域时,日历控件采用填充高度,其余场景日历控件采用固定高度 + let isInListMainContainer = false; + const parentClass = buildInfo.parentComponentInstance?.schema?.appearance?.class; + if (parentClass && parentClass.includes('f-page-main')) { + isInListMainContainer = true; + } + + Object.assign(componentNode, { + id: `${buildInfo.componentId}-component`, + viewModel: `${buildInfo.componentId}-component-viewmodel`, + componentType: buildInfo.componentType, + appearance: { + class: isInListMainContainer ? 'f-utils-fill-flex-column f-utils-overflow-auto' : 'f-grid-is-sub f-utils-overflow-auto' + }, + contents + }); + return componentNode; + } + + function createComponent(buildInfo: ComponentBuildInfo) { + const componentRefNode = createComponentRefNode(buildInfo); + + const componentNode = createComponentNode(buildInfo); + + const viewModelNode = createViewModeNode(buildInfo); + + const formSchema = formSchemaUtils.getFormSchema(); + formSchema.module.viewmodels.push(viewModelNode); + formSchema.module.components.push(componentNode); + + designViewModelUtils.assembleDesignViewModel(); + + return wrapContainerSectionForComponent(componentRefNode, buildInfo, viewModelNode); + } + + return { createComponent }; +} diff --git a/packages/ui-vue/components/calendar/src/composition/use-calendar-events.tsx b/packages/ui-vue/components/calendar/src/composition/use-calendar-events.tsx new file mode 100644 index 0000000000000000000000000000000000000000..50706c3b2eaccf056313b22df1b07504e799d037 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/composition/use-calendar-events.tsx @@ -0,0 +1,505 @@ +import { UseDateFormat, resolveField } from "@farris/ui-vue/components/common"; +import { CalendarPropsType } from "../calendar.props"; +import { CalendarEvent, CalendarViews, WeekInCalendar } from "../types/calendar"; +import { DateObject } from "../types/common"; +import { UseCompare, UseDate } from "./types"; +import { Ref, VNode } from "vue"; +import { isFunction } from 'lodash-es'; + +export interface UseCalendarEvents { + assignEventsToDays: (weeks: Array, events: Array>) => Array; + assignEventsToWeeks: (weeks: Array, events: Array>) => Array; + renderEventItem: (event: Record) => VNode; + useCompareCompsition: UseCompare; + useDateFormatComposition: UseDateFormat; + useDateComposition: UseDate; + renderEventItemForMore: (event: Record) => VNode; + assignEventsToMonths: (year: number, events: Array>) => Record>>; + renderEventItemForMonths: (event: Record) => VNode; + getEventItemStyles: (event: Record) => Record; +} + +export function useCalendarEvents(props: CalendarPropsType, useDate: UseDate, useDateFormat: UseDateFormat, usecompare: UseCompare, context: any, activeView: Ref, locale: string): UseCalendarEvents { + const { equalOrEarlier, sameDay } = usecompare; + const { convertDateToDateObject } = useDate; + const { parseToDate, formatTo } = useDateFormat; + + /** + * 将事件分配到日期中(非跨天模式) + * @param weeks - 周数组 + * @param events - 事件数组 + * @returns 处理后的周数组,事件以二维数组形式组织 + */ + function assignEventsToDays( + weeks: Array, + events: Array> + ): Array { + // 初始化每周的事件数组 + weeks.forEach(week => { + week.days.forEach(day => { + day.events = []; + }); + }); + + // 缓存日期解析结果 + const dateCache = new Map(); + + const parseAndCacheDate = (dateStr: string): DateObject | null => { + if (!dateStr) { return null; } + if (dateCache.has(dateStr)) { + return dateCache.get(dateStr)!; + } + const parsed = parseToDate(dateStr, 'yyyy-MM-dd'); + if (!parsed) { return null; } + const dateObj = convertDateToDateObject(parsed); + dateCache.set(dateStr, dateObj); + return dateObj; + }; + + // 为每个日期分配事件 + events.forEach(event => { + const { eventDateField, startDateField } = props; + // 在非跨天模式下,优先使用 eventDateField,否则使用 startDateField + const eventDateStr = resolveField(event, eventDateField) || resolveField(event, startDateField); + const eventDate = parseAndCacheDate(eventDateStr); + + if (!eventDate) { return; } // 跳过无效日期 + + // 将事件分配到对应的日期 + weeks.forEach(week => { + week.days.forEach(day => { + if (sameDay(day.date, eventDate)) { + day.events?.push({ + id: event[props.idField], + data: { ...event }, + eventDate: eventDate, + isSingleDay: true + }); + } + }); + }); + }); + + // 将每天的事件转换为二维数组结构 + weeks.forEach(week => { + // 创建一个二维数组,每个子数组代表一行事件 + const eventRows: Array = []; + + // 为每一天创建事件行数据 + const dayEvents: Array = week.days.map((day, dayIndex) => { + if (day.events && day.events.length > 0) { + // 每个事件单独占一行 + return day.events.map((event, eventIndex) => ({ + ...event, + // 在非跨天模式下,colspan为1,rowspan为1 + colspan: 1, + rowspan: 1, + // 添加位置信息 + startIndex: dayIndex, + endIndex: dayIndex + })); + } + return []; + }); + + // 转置数组,将按天组织的事件转换为按行组织 + const maxEventsPerDay = Math.max(...dayEvents.map(day => day.length), 0); + + for (let rowIndex = 0; rowIndex < maxEventsPerDay; rowIndex++) { + const row: CalendarEvent[] = []; + + // 为这一行填充7天的数据 + for (let dayIndex = 0; dayIndex < 7; dayIndex++) { + if (dayEvents[dayIndex][rowIndex]) { + row.push(dayEvents[dayIndex][rowIndex]); + } else { + // 没有事件的位置填充空数据 + row.push({ + colspan: 1, + rowspan: 1, + data: null + } as any as CalendarEvent); + } + } + + eventRows.push(row); + } + + week.events = eventRows; + }); + + return weeks; + } + +function assignEventsToWeeks( + weeks: Array, + events: Array> +): Array { + // 重置每周的事件数组 + weeks.forEach(week => { + week.events = []; + }); + + // 缓存日期解析结果 + const dateCache = new Map(); + + const parseAndCacheDate = (dateStr: string): DateObject | null => { + if (!dateStr) { return null; } + if (dateCache.has(dateStr)) { + return dateCache.get(dateStr)!; + } + const parsed = parseToDate(dateStr, 'yyyy-MM-dd'); + if (!parsed) { return null; } + const dateObj = convertDateToDateObject(parsed); + dateCache.set(dateStr, dateObj); + return dateObj; + }; + + // 处理每个事件 + events.forEach(event => { + const { startDateField, endDateField } = props; + const startDate = parseAndCacheDate(resolveField(event, startDateField)); + const endDate = parseAndCacheDate(resolveField(event, endDateField)); + + if (!startDate || !endDate) { return; } // 跳过无效日期 + + weeks.forEach(week => { + const weekStart = week.days[0].date; + const weekEnd = week.days[week.days.length - 1].date; + + const isEventInWeek = !(equalOrEarlier(endDate, weekStart, false) || equalOrEarlier(weekEnd, startDate, false)); + + if (isEventInWeek) { + if (sameDay(startDate, endDate)) { + // 单天事件直接添加 + week.events?.push({ + id: event[props.idField], + data: { ...event }, + eventDate: startDate, + isSingleDay: true + }); + } else { + // 跨天事件需要计算在当前周的起始和结束位置 + const eventStartInWeek = equalOrEarlier(weekStart, startDate, false) ? startDate : weekStart; + const eventEndInWeek = equalOrEarlier(endDate, weekEnd, false) ? endDate : weekEnd; + + let startIndex = -1; + let endIndex = -1; + + // 精确查找事件在当前周的起始和结束索引 + week.days.forEach((day, index) => { + if (sameDay(day.date, eventStartInWeek)) { startIndex = index; } + if (sameDay(day.date, eventEndInWeek)) { endIndex = index; } + }); + + if (startIndex !== -1 && endIndex !== -1) { + week.events?.push({ + id: event[props.idField], + data: { ...event }, + // 修复:eventDate 应该是事件在整个事件范围内的起始日期 + eventDate: startDate, + // 添加在当前周的起始和结束日期 + startDate: eventStartInWeek, + endDate: eventEndInWeek, + // 添加在当前周的起始和结束索引 + startIndex: startIndex, + endIndex: endIndex, + colspan: endIndex - startIndex + 1, + isCrossDay: true + }); + } + } + } else { + // 处理跨周事件在边界的情况 + if (sameDay(startDate, weekEnd) && !sameDay(endDate, weekEnd)) { + week.events?.push({ + id: `${event[props.idField]}_start`, + data: { ...event }, + eventDate: startDate, + isSingleDay: true, + isOverlapping: true, + originalId: event[props.idField] + }); + } + + if (sameDay(endDate, weekStart) && !sameDay(startDate, weekStart)) { + week.events?.push({ + id: `${event[props.idField]}_end`, + data: { ...event }, + eventDate: endDate, + isSingleDay: true, + isOverlapping: true, + originalId: event[props.idField] + }); + } + } + }); + }); + + // 处理重叠事件 + weeks.forEach(week => { + week.days.forEach((day, dayIndex) => { + const eventsForDay = week.events?.filter((event: any) => { + if (event.isCrossDay) { + return dayIndex >= event.startIndex && dayIndex <= event.endIndex; + } else if (event.isSingleDay) { + return sameDay( + { year: event.eventDate.year, month: event.eventDate.month, day: event.eventDate.day }, + { year: day.date.year, month: day.date.month, day: day.date.day } + ); + } + return false; + }); + + const crossDayEvent = eventsForDay?.find(event => event.isCrossDay); + const singleDayEvents = eventsForDay?.filter(event => event.isSingleDay) || []; + + if (crossDayEvent && singleDayEvents.length > 0) { + singleDayEvents.forEach(singleEvent => { + const isDuplicate = week.events?.some(e => + (e.originalId === singleEvent.id || e.id === singleEvent.id) && + sameDay( + { year: e.eventDate.year, month: e.eventDate.month, day: e.eventDate.day }, + { year: day.date.year, month: day.date.month, day: day.date.day } + ) + ); + + if (!isDuplicate) { + week.events?.push({ + id: singleEvent.id, + data: { ...singleEvent.data }, + eventDate: day.date, + isSingleDay: true, + isOverlapping: true, + originalId: singleEvent.id + }); + } + }); + } + }); + }); + + // 构建事件行 - 使用新的算法 + weeks.forEach((week: any) => { + // 创建一个二维网格表示一周的事件布局 + const grid: Array> = []; + + // 初始化网格 + for (let i = 0; i < 10; i++) { // 假设最多10行事件 + grid[i] = Array(7).fill(null); + } + + // 将事件按照起始时间排序 + const sortedEvents = [...week.events].sort((a, b) => { + if (a.isCrossDay && !b.isCrossDay) {return -1;} + if (!a.isCrossDay && b.isCrossDay) {return 1;} + if (a.isCrossDay && b.isCrossDay) { + // 跨天事件按起始索引排序 + return a.startIndex - b.startIndex; + } + // 单天事件按日期排序 + const dateA = a.eventDate; + const dateB = b.eventDate; + if (dateA.year !== dateB.year) {return dateA.year - dateB.year;} + if (dateA.month !== dateB.month) {return dateA.month - dateB.month;} + return dateA.day - dateB.day; + }); + + // 将事件放入网格 + sortedEvents.forEach(event => { + // 查找可以放置事件的行 + let row = 0; + let placed = false; + + // 确定事件在网格中的列位置 + let colStart = 0; + let colSpan = 1; + + if (event.isCrossDay) { + colStart = event.startIndex; + colSpan = event.colspan; + } else { + // 单天事件需要找到它在周内的位置 + colStart = week.days.findIndex(day => + sameDay( + { year: event.eventDate.year, month: event.eventDate.month, day: event.eventDate.day }, + { year: day.date.year, month: day.date.month, day: day.date.day } + ) + ); + colSpan = 1; + } + + // 查找可以放置事件的行 + while (!placed && row < grid.length) { + // 检查这一行是否有足够的空间 + let canPlace = true; + for (let col = colStart; col < colStart + colSpan; col++) { + if (grid[row][col] !== null) { + canPlace = false; + break; + } + } + + if (canPlace) { + // 放置事件 + for (let col = colStart; col < colStart + colSpan; col++) { + grid[row][col] = col === colStart ? event : { + ...event, + isPlaceholder: true, + colspan: col === colStart ? colSpan : 0 + }; + } + placed = true; + } else { + row++; + } + } + }); + + // 将网格转换为事件行 + const weekEventItems: Array = []; + grid.forEach(row => { + // 过滤掉空行 + if (row.some(cell => cell !== null)) { + const eventRow: CalendarEvent[] = []; + for (let i = 0; i < row.length; i++) { + const cell = row[i]; + if (cell === null) { + // 空单元格 + eventRow.push({ + colspan: 1, + rowspan: 1, + data: null + } as any as CalendarEvent); + } else if (!cell['isPlaceholder']) { + // 实际事件 + eventRow.push(cell); + } + // 跳过占位符单元格 + if (cell) { + cell.colspan = cell.colspan || 1; + if (cell.colspan > 1) { + i += cell.colspan - 1; + } + } + } + weekEventItems.push(eventRow); + } + }); + + week.events = weekEventItems; + }); + + return weeks; +} + + function assignEventsToMonths(year: number, events: Array>) { + const months: Record = {}; + events.forEach(event => { + const { eventDateField, startDateField } = props; + // 在非跨天模式下,优先使用 eventDateField,否则使用 startDateField + const eventDateStr = resolveField(event, eventDateField) || resolveField(event, startDateField); + // 添加空值检查 + if (!eventDateStr) { + return; // 跳过无效日期的事件 + } + const eventDateObject = convertDateToDateObject(parseToDate(eventDateStr, 'yyyy-MM-dd')); + if (eventDateObject.year === year) { + const monthNum = ''+eventDateObject.month; + months[monthNum] = months[monthNum] || []; + months[monthNum].push({ + data: {...event}, + eventDate: eventDateObject + }); + } + }); + + return months; + } + + function getCurrentViewName() { + return CalendarViews[activeView.value - 1]; + } + + function onEventItemClick($event, eventItem) { + $event.stopPropagation(); + if (props.disabled) { + return; + } + context.emit('eventClick', { data: eventItem.data, view: getCurrentViewName() }); + } + + const getValue = (data, field, locale) => { + const result = resolveField(data, field); + if (typeof result === 'string') { + return result; + } + if (result !== null && typeof result === 'object' && !Array.isArray(result) && locale) { + return result[locale] || ''; + } + + return result || ''; + }; + + function getEeventItemText(event: Record) { + return isFunction(props.eventItemFormatter) ? props.eventItemFormatter(event.data, {parseToDate, formatTo, getValue, view: getCurrentViewName(), locale}) : resolveField(event.data, props.textField || 'title'); + } + + function getEventTitle(event: Record) { + return isFunction(props.eventItemFormatter) ? '': getValue(event.data, props.textField || 'title', locale); + } + + const getEventItemStyles = (event: Record) => { + if (!event || !event.data) { + return { + }; + } + + if (!isFunction(props.customEventStyles)) { + return { + styles: { + 'background-color': '#f3f3f3' + } + }; + } + + const customObject = props.customEventStyles(event.data, {parseToDate, formatTo, getValue, view: getCurrentViewName(), locale}); + if (customObject && Object.keys(customObject).length > 0) { + return customObject; + } + + return {}; + }; + + function renderEventItem(event: Record) { + const { cls, styles } = getEventItemStyles(event); + return onEventItemClick($event, event)} style={styles || {}} class={'f-calendar-event p-0 mx-1 my-0 ' + (cls || '')}> +
+
; + } + + function renderEventItemForMore(event: Record) { + return
  • {getEeventItemText(event)}
  • ; + } + + function renderEventItemForMonths(event: Record) { + const { cls, styles } = getEventItemStyles(event); + + return
  • onEventItemClick($event, event)}> +
    +
  • ; + } + + return { + assignEventsToDays, + assignEventsToWeeks, + renderEventItem, + useCompareCompsition: usecompare, + useDateComposition: useDate, + useDateFormatComposition: useDateFormat, + renderEventItemForMore, + assignEventsToMonths, + renderEventItemForMonths, + getEventItemStyles + }; +} diff --git a/packages/ui-vue/components/calendar/src/composition/use-calendar.ts b/packages/ui-vue/components/calendar/src/composition/use-calendar.ts index 80f0cfc248fe7e64d84069ceffd65a6a3ca2eb32..e6cdb253588782114edd947f82ad53f66d29823b 100644 --- a/packages/ui-vue/components/calendar/src/composition/use-calendar.ts +++ b/packages/ui-vue/components/calendar/src/composition/use-calendar.ts @@ -20,7 +20,7 @@ import { UseCalendar } from './types'; import { DayInCalendar, WeekInCalendar } from '../types/calendar'; export default function useCalendar(): UseCalendar { - const { getToday, getDayNumber } = useDate(); + const { getToday, getDayNumber, isWeekEnd } = useDate(); const { daysInMonth, daysInPreMonth, getNextMonth, getPreviousMonth } = useMonth(); function getSundayIndex(firstDayOfWeek: string): number { @@ -98,6 +98,22 @@ export default function useCalendar(): UseCalendar { }; } + function getNextYear(date: DateObject): DateObject { + return { + day: date.day, + month: date.month, + year: (date.year || 1) + 1 + }; + } + + function getPreviousYear(date: DateObject): DateObject { + return { + day: date.day, + month: date.month, + year: (date.year || 1) - 1 + }; + } + function getDayInPreviousWeek(date: DateObject): DateObject { const day = date.day || 1; const intendingPreviousDay = day - 7; @@ -126,56 +142,128 @@ export default function useCalendar(): UseCalendar { year: nextDayMonth.year }; } + + function getWeeklyCalendar( + day: number, + month: number, + year: number, + firstDayOfWeek: string, + activeDay + ): WeekInCalendar { + // 参数合法性校验 + if (day < 1 || month < 1 || year < 1) { + throw new Error("Invalid date input"); + } - function getWeeklyCalendar(day: number, month: number, year: number, firstDayOfWeek: string): WeekInCalendar { const date = { day, month, year }; - const sundayIndex = getSundayIndex(firstDayOfWeek); - const offset = sundayIndex === 0 ? 1 : 0; - const currentDayIndexInWeek = getDayNumber(date) + offset; + // 获取一周起始日索引(周日=0, 周一=1, ..., 周六=6) + const weekStartIndex = weekDays.indexOf(firstDayOfWeek); + if (weekStartIndex === -1) { + throw new Error("Invalid firstDayOfWeek value"); + } + + // 获取当前日期是周几(周日=0, 周一=1, ..., 周六=6) + const dayNumber = getDayNumber(date); + + // 计算当前日期在一周中的相对位置(0表示一周的起始日) + const dayIndexInWeek = (dayNumber - weekStartIndex + 7) % 7; + const today: DateObject = getToday(); - let previousDay: DateObject = getPreviousDay(date); - const previousDays: DayInCalendar[] = []; - for (let dayIndexInWeek = currentDayIndexInWeek - 1; dayIndexInWeek >= 1; dayIndexInWeek--) { - const monthTag = previousDay.month !== date.month ? MonthTag.previous : MonthTag.current; - const isCurrent = - previousDay.month === month && - previousDay.day === today.day && - previousDay.month === today.month && - previousDay.year === today.year; - previousDays.push({ date: previousDay, monthTag, isCurrent }); - if (dayIndexInWeek > 1) { - previousDay = getPreviousDay(previousDay); - } + const isCurrentDay = (d: DateObject): boolean => { + return ( + d.day === today.day && + d.month === today.month && + d.year === today.year + ); + }; + + // 构建当前周的日期数组 + const days: DayInCalendar[] = []; + + // 计算一周的起始日期 + let currentDate: DateObject = { ...date }; + // 从当前日期回退到一周的起始日 + for (let i = 0; i < dayIndexInWeek; i++) { + currentDate = getPreviousDay(currentDate); } - let nextDay: DateObject = getNextDay(date); - const nextDays: DayInCalendar[] = []; - for (let dayIndexInWeek = currentDayIndexInWeek + 1; dayIndexInWeek <= 7; dayIndexInWeek++) { - const monthTag = previousDay.month !== date.month ? MonthTag.next : MonthTag.current; - const isCurrent = nextDay.day === today.day && nextDay.month === today.month && nextDay.year === today.year; - nextDays.push({ date: nextDay, monthTag, isCurrent }); - if (dayIndexInWeek < 7) { - nextDay = getNextDay(nextDay); + // 生成一周7天的数据 + for (let i = 0; i < 7; i++) { + // 判断月份标签 + let monthTag: MonthTag; + if (currentDate.month === activeDay.month && currentDate.year === activeDay.year) { + monthTag = MonthTag.current; + } else { + // 确定是上个月还是下个月 + const previousMonth = getPreviousMonth(date.month, date.year); + if (currentDate.month === previousMonth.month && currentDate.year === previousMonth.year) { + monthTag = MonthTag.previous; + } else { + monthTag = MonthTag.next; + } + } + + const isCurrent = isCurrentDay(currentDate); + const weekEnd = isWeekEnd(currentDate); + + days.push({ + date: currentDate, + monthTag, + isCurrent, + weekEnd + }); + + // 只在不是最后一项时才获取下一天 + if (i < 6) { + currentDate = getNextDay(currentDate); } } - const isCurrent = day === today.day && month === today.month && year === today.year; const weekCalendar: WeekInCalendar = { - days: [...previousDays.reverse(), { date, monthTag: MonthTag.current, isCurrent }, ...nextDays], + days, weekNumber: 0, - year + year, }; + return weekCalendar; } - function getMonthlyCalendar(month: number, year: number, firstDayOfWeek: string): WeekInCalendar[] { + function getMonthlyCalendar(month: number, year: number, firstDayOfWeek: string, activeDay: DateObject): WeekInCalendar[] { const weeks: WeekInCalendar[] = []; - const daysInCurrentMonth: number = daysInMonth(month, year); - for (let day = 1; day <= daysInCurrentMonth; day += 7) { - const week = getWeeklyCalendar(day, month, year, firstDayOfWeek); + + // 获取该月的第一天 + const firstDayOfMonth: DateObject = { day: 1, month, year }; + + // 获取该月第一天是星期几 + const dayNumber = getDayNumber(firstDayOfMonth); + + // 获取一周起始日索引 + const weekStartIndex = weekDays.indexOf(firstDayOfWeek); + if (weekStartIndex === -1) { + throw new Error("Invalid firstDayOfWeek value"); + } + + // 计算月历应该从哪一天开始显示(即第一周的起始日) + const dayIndexInWeek = (dayNumber - weekStartIndex + 7) % 7; + + // 计算月历开始日期(需要从第一周的起始日开始) + let calendarStartDate = firstDayOfMonth; + for (let i = 0; i < dayIndexInWeek; i++) { + calendarStartDate = getPreviousDay(calendarStartDate); + } + + // 生成6周的数据 + let currentDate: any = calendarStartDate; + for (let weekIndex = 0; weekIndex < 6; weekIndex++) { + const week = getWeeklyCalendar(currentDate.day, currentDate.month, currentDate.year, firstDayOfWeek, activeDay); weeks.push(week); + // 将currentDate移到下一周的同一天 + for (let i = 0; i < 7; i++) { + currentDate = getNextDay(currentDate); + } } + return weeks; } @@ -187,6 +275,8 @@ export default function useCalendar(): UseCalendar { getDayInPreviousWeek, getDayInNextWeek, getDayInPreviousMonth, - getDayInNextMonth + getDayInNextMonth, + getNextYear, + getPreviousYear }; } diff --git a/packages/ui-vue/components/calendar/src/composition/use-compare.ts b/packages/ui-vue/components/calendar/src/composition/use-compare.ts index 1a5572a07893623bfb650af28fe7aa19522d2571..194daaa5296160989c5e8d6b4f0bab27507842fb 100644 --- a/packages/ui-vue/components/calendar/src/composition/use-compare.ts +++ b/packages/ui-vue/components/calendar/src/composition/use-compare.ts @@ -21,7 +21,14 @@ export function useCompare(): UseCompare { return getTimeInMilliseconds(firstDate) < getTimeInMilliseconds(secondDate); } - function equalOrEarlier(firstDate: DateObject, secondDate: DateObject): boolean { + function getDateInMilliseconds(date: DateObject): number { + return getTimeInMilliseconds({ year: date.year, month: date.month, day: date.day }); + } + + function equalOrEarlier(firstDate: DateObject, secondDate: DateObject, withTime: boolean = true): boolean { + if (!withTime) { + return getDateInMilliseconds(firstDate) <= getDateInMilliseconds(secondDate); + } return getTimeInMilliseconds(firstDate) <= getTimeInMilliseconds(secondDate); } diff --git a/packages/ui-vue/components/calendar/src/composition/use-date.ts b/packages/ui-vue/components/calendar/src/composition/use-date.ts index 0485b7619ad8792a0d63975cf4fcf42fa33a93b5..4cffde0d6855705b5368f4b5b9cc5c678a525c8d 100644 --- a/packages/ui-vue/components/calendar/src/composition/use-date.ts +++ b/packages/ui-vue/components/calendar/src/composition/use-date.ts @@ -91,5 +91,24 @@ export function useDate(): UseDate { }; } - return { emptyDate, getDate, getDate2, getDayNumber, getEpocTime, getNearDate, getWeekdayIndex, getTimeInMilliseconds, getToday }; + function convertDateToDateObject(date: Date | null): DateObject { + if (date) { + return { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + hour: date.getHours(), + minute: date.getMinutes(), + second: date.getSeconds() + }; + } + return emptyDate(); + } + + function isWeekEnd(date: DateObject){ + const dayNumber = getDayNumber(date); + return dayNumber === 0 || dayNumber === 6; + } + + return { convertDateToDateObject, emptyDate, getDate, getDate2, getDayNumber, getEpocTime, getNearDate, getWeekdayIndex, getTimeInMilliseconds, getToday, isWeekEnd}; } diff --git a/packages/ui-vue/components/calendar/src/composition/use-locales.ts b/packages/ui-vue/components/calendar/src/composition/use-locales.ts new file mode 100644 index 0000000000000000000000000000000000000000..26cf5ebba24b924862d2ba8c914ea9a1458c3e59 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/composition/use-locales.ts @@ -0,0 +1,51 @@ +import { LocaleService } from '@farris/ui-vue/components/locale'; + +export function useCalendarLocale() { + const { getLocaleValue: t, getLocale } = LocaleService; + + const monthLabels = { + "1": t('calendar.monthLabels.1'), + "2": t('calendar.monthLabels.2'), + "3": t('calendar.monthLabels.3'), + "4": t('calendar.monthLabels.4'), + "5": t('calendar.monthLabels.5'), + "6": t('calendar.monthLabels.6'), + "7": t('calendar.monthLabels.7'), + "8": t('calendar.monthLabels.8'), + "9": t('calendar.monthLabels.9'), + "10": t('calendar.monthLabels.10'), + "11": t('calendar.monthLabels.11'), + "12": t('calendar.monthLabels.12') + }; + const weekDayLabels = { + "Sun": t('calendar.dayLabels.Sun'), + "Mon": t('calendar.dayLabels.Mon'), + "Tue": t('calendar.dayLabels.Tue'), + "Wed": t('calendar.dayLabels.Wed'), + "Thu": t('calendar.dayLabels.Thu'), + "Fri": t('calendar.dayLabels.Fri'), + "Sat": t('calendar.dayLabels.Sat') + }; + const today = t('calendar.today'); + const thisYear = t('calendar.thisYear'); + const yearViewTitle = t('calendar.yearView'); + const monthViewTitle = t('calendar.monthView'); + const weekViewTitle = t('calendar.weekView'); + const dayViewTitle = t('calendar.dayView'); + const titleFormat = t('calendar.titleFormat'); + const yearViewTitleFormat = t('calendar.yearViewTitleFormat'); + const prevMonth = t('calendar.prevMonth'); + const nextMonth = t('calendar.nextMonth'); + const prevWeek = t('calendar.prevWeek'); + const nextWeek = t('calendar.nextWeek'); + const prevDay = t('calendar.prevDay'); + const nextDay = t('calendar.nextDay'); + const prevYear = t('calendar.prevYear'); + const nextYear = t('calendar.nextYear'); + const moreText = t('calendar.more', '') || '还有 #count# 项...'; + + return { monthLabels, weekDayLabels, today, monthViewTitle, weekViewTitle, dayViewTitle, titleFormat, + prevMonth, nextMonth, prevWeek, nextWeek, prevDay, nextDay, yearViewTitle,yearViewTitleFormat, thisYear, + prevYear, nextYear, moreText, locale: getLocale() + }; +} diff --git a/packages/ui-vue/components/calendar/src/designer/calendar-design.rules.ts b/packages/ui-vue/components/calendar/src/designer/calendar-design.rules.ts new file mode 100644 index 0000000000000000000000000000000000000000..a5aba59937333fd0855e50d2a4685f94d1c3cc15 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/designer/calendar-design.rules.ts @@ -0,0 +1,94 @@ +import { ref } from "vue"; +import { DraggingResolveContext, DesignerHTMLElement, UseDesignerRules, DesignerHostService } from "../../../designer-canvas/src/composition/types"; +import { DgControl } from "../../../designer-canvas/src/composition/dg-control"; +import { ComponentSchema, DesignerItemContext } from "../../../designer-canvas/src/types"; +import { CalendarPropertyConfig } from "../property-config/calendar.property-config"; + +export function useDesignerRulesForCalendar(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { + const schema = designItemContext.schema as ComponentSchema; + /** 组件在拖拽时需要将所属的Component一起拖拽 */ + const triggerBelongedComponentToMoveWhenMoved = ref(true); + /** 组件在删除时需要将所属的Component一起拖拽 */ + const triggerBelongedComponentToDeleteWhenDeleted = ref(true); + + /** + * 判断是否可以接收拖拽新增的子级控件 + */ + function canAccepts(draggingContext: DraggingResolveContext): boolean { + // 接收工具箱中拖拽来的输入类控件 + return false; + } + + /** + * listview不支持删除,需要选中所属组件Component节点删除。 + */ + function checkCanDeleteComponent() { + return false; + } + /** + * listview不支持移动,需要选中所属组件Component节点移动。 + */ + function checkCanMoveComponent() { + return false; + } + + function hideNestedPaddingInDesginerView() { + return true; + } + + + /** + * 构造属性配置方法 + */ + function getPropsConfig(componentId: string) { + const listViewProp = new CalendarPropertyConfig(componentId, designerHostService); + return listViewProp.getPropertyConfig(schema); + } + /** + * 配置表格的路径信息,用于事件交互面板显示“已有方法”的事件路径 + */ + function setComponentBasicInfoMap() { + if (designItemContext && designerHostService) { + const belongedComponentId = designItemContext?.componentInstance?.value.belongedComponentId; + let parentTitle = ''; + let reliedComponentId = ''; + const { formSchemaUtils } = designerHostService; + if (belongedComponentId) { + const rootViewModelId = formSchemaUtils.getRootViewModelId(); + const rootComponent = formSchemaUtils.getComponentByViewModelId(rootViewModelId); + const parentSchemaOfComponent = formSchemaUtils.selectNode(rootComponent, item => { + return item.contents && item.contents.find(childItem => childItem.component === belongedComponentId); + }); + // 父级为tab-page + if (parentSchemaOfComponent?.type === DgControl['tab-page']?.type && parentSchemaOfComponent?.contents?.length) { + parentTitle = parentSchemaOfComponent.title || ''; + } + // 父级为section + if (parentSchemaOfComponent?.type === DgControl.section?.type && parentSchemaOfComponent?.showHeader !== false) { + parentTitle = parentSchemaOfComponent.mainTitle || ''; + } + parentTitle = parentTitle ? `${parentTitle} > ` : ''; + reliedComponentId = parentTitle ? parentSchemaOfComponent.id : ''; + } + + const calendarName = DgControl['calendar'].name; + designerHostService?.formSchemaUtils.getControlBasicInfoMap().set(designItemContext.schema.id, { + componentTitle: calendarName, + parentPathName: `${parentTitle}${calendarName}`, + reliedComponentId + }); + } + + } + return { + canAccepts, + checkCanDeleteComponent, + checkCanMoveComponent, + hideNestedPaddingInDesginerView, + triggerBelongedComponentToMoveWhenMoved, + triggerBelongedComponentToDeleteWhenDeleted, + getPropsConfig, + setComponentBasicInfoMap + } as UseDesignerRules; + +} diff --git a/packages/ui-vue/components/calendar/src/designer/calendar.design.component.tsx b/packages/ui-vue/components/calendar/src/designer/calendar.design.component.tsx index caf089c6b512c23a6245f584d77c613c4ca4756a..fb5ac714087c40c001e838090ecfe6aa1ac366bc 100644 --- a/packages/ui-vue/components/calendar/src/designer/calendar.design.component.tsx +++ b/packages/ui-vue/components/calendar/src/designer/calendar.design.component.tsx @@ -1,157 +1,52 @@ -import { SetupContext, computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; import { CalendarPropsType, calendarProps } from '../calendar.props'; -import useCalendar from '../composition/use-calendar'; -import { useDate } from '../composition/use-date'; -import { DayInCalendar } from '../types/calendar'; -import { useCompare } from '../composition/use-compare'; -import { DateObject, weekDays } from '../types/common'; -import { ScheduleEvent } from '../types/schedule'; -import { defaultNameOfMonths } from '../types/month'; import { DesignerItemContext, useDesignerComponent } from '@farris/ui-vue/components/designer-canvas'; -import FCalenderHeader from '../components/header/header.component'; -import FCalenderMonthView from '../components/month/month-view.component'; -import FCalenderWeekView from '../components/week/week-view.component'; -import FCalenderDayView from '../components/day/day-view.component'; +import FCalendar from '../calendar.component'; + +import { DesignerHostService } from '@farris/ui-vue/components/designer-canvas'; +import { useDesignerRulesForCalendar } from './calendar-design.rules'; +import ZHCHS_LOCALES from '../locales/ui/zh-CHS.json'; +// import { useI18n } from 'vue-i18n'; +import { getCustomStyle } from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FCalendarDesign', props: calendarProps, emits: [], setup(props: CalendarPropsType, context) { - const { sameDay } = useCompare(); - const { getToday } = useDate(); - const firstDayOfTheWeek = ref(props.firstDayOfTheWeek); - const events = ref(props.events); - const today = getToday(); - const activeDay = ref({ - year: today.year || 1, - month: today.month || 1, - day: today.day || 1 - }); - const activeView = ref(3); + // const { t, mergeLocaleMessage } = useI18n(); + // mergeLocaleMessage('zh-CHS', ZHCHS_LOCALES); + const elementRef = ref(); + const designItemContext = inject('design-item-context') as DesignerItemContext; - const componentInstance = useDesignerComponent(elementRef, designItemContext); + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useDesignerRulesForCalendar(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); onMounted(() => { elementRef.value.componentInstance = componentInstance; }); context.expose(componentInstance.value); - - const { - getMonthlyCalendar, - getWeeklyCalendar, - getPreviousDay, - getNextDay, - getDayInPreviousWeek, - getDayInNextWeek, - getDayInPreviousMonth, - getDayInNextMonth - } = useCalendar(); - - const title = computed(() => { - return `${defaultNameOfMonths[activeDay.value.month || 1]} ${activeDay.value.year}`; - }); - - const dates = computed(() => { - const weekItems = getMonthlyCalendar(activeDay.value.month || 1, activeDay.value.year || 1, firstDayOfTheWeek.value); - return weekItems; - }); - - const week = computed(() => { - const weekItem = getWeeklyCalendar( - activeDay.value.day || 1, - activeDay.value.month || 1, - activeDay.value.year || 1, - firstDayOfTheWeek.value - ); - return weekItem; - }); - - const day = computed(() => { - return week.value.days.find((day: DayInCalendar) => sameDay(day.date, activeDay.value)) || week.value.days[1]; + const calendarStyles = computed(() => { + const styleObject = { + minHeight: '800px' + } as Record; + return styleObject; }); - - const dayInWeek = computed(() => { - const matchedIndex = week.value.days.findIndex((day: DayInCalendar) => sameDay(day.date, activeDay.value)); - const dayIndexInWeek = matchedIndex > -1 ? matchedIndex : 1; - return weekDays[dayIndexInWeek]; - }); - - const shouldShowMonthlyView = computed(() => { - return activeView.value === 3; - }); - - const shouldShowWeeklyView = computed(() => { - return activeView.value === 2; - }); - - const shouldShowDailyView = computed(() => { - return activeView.value === 1; - }); - - function changeActiveView(viewIndex: number) { - activeView.value = viewIndex; - } - - function previous(viewIndex: number) { - if (viewIndex === 1) { - activeDay.value = getPreviousDay(activeDay.value); - } - if (viewIndex === 2) { - activeDay.value = getDayInPreviousWeek(activeDay.value); - } - if (viewIndex === 3) { - activeDay.value = getDayInPreviousMonth(activeDay.value); - } - } - - function next(viewIndex: number) { - if (viewIndex === 1) { - activeDay.value = getNextDay(activeDay.value); - } - if (viewIndex === 2) { - activeDay.value = getDayInNextWeek(activeDay.value); - } - if (viewIndex === 3) { - activeDay.value = getDayInNextMonth(activeDay.value); - } - } - - function resetToToday() { - activeDay.value = { - year: today.year || 1, - month: today.month || 1, - day: today.day || 1 - }; - } - return () => { return ( -
    - changeActiveView(viewIndex)} - onPrevious={(viewIndex: number) => previous(viewIndex)} - onNext={(viewIndex: number) => next(viewIndex)} - onResetToToday={() => resetToToday()}> -
    - {shouldShowDailyView.value && ( - - )} - {shouldShowWeeklyView.value && ( - - )} - {shouldShowMonthlyView.value && ( - - )} -
    +
    +
    ); }; diff --git a/packages/ui-vue/components/calendar/src/locales/ui/en.json b/packages/ui-vue/components/calendar/src/locales/ui/en.json new file mode 100644 index 0000000000000000000000000000000000000000..87195ba4900a9749e0dfbe6e44f1fcb1d81ecfb8 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/locales/ui/en.json @@ -0,0 +1,44 @@ +{ + "calendar": { + "dayLabels": { + "Sun": "Sun", + "Mon": "Mon", + "Tue": "Tue", + "Wed": "Wed", + "Thu": "Thu", + "Fri": "Fri", + "Sat": "Sat" + }, + "monthLabels": { + "1": "Jan", + "2": "Feb", + "3": "Mar", + "4": "Apr", + "5": "May", + "6": "Jun", + "7": "Jul", + "8": "Aug", + "9": "Sep", + "10": "Oct", + "11": "Nov", + "12": "Dec" + }, + "titleFormat": "MM yyyy", + "yearViewTitleFormat": "yyyy", + "today": "Today", + "thisYear": "This Year", + "monthView": "Month", + "weekView": "Week", + "dayView": "Day", + "yearView": "Year", + "prevMonth": "Prev Month", + "nextMonth": "Next Month", + "prevWeek": "Prev Week", + "nextWeek": "Next Week", + "prevDay": "Prev Day", + "nextDay": "Next Day", + "prevYear": "Prev Year", + "nextYear": "Next Year", + "more": "Still #count# more items..." + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/calendar/src/locales/ui/zh-CHS.json b/packages/ui-vue/components/calendar/src/locales/ui/zh-CHS.json new file mode 100644 index 0000000000000000000000000000000000000000..f1d41eb3b2016f5afde5a467209a88226ebfbb4b --- /dev/null +++ b/packages/ui-vue/components/calendar/src/locales/ui/zh-CHS.json @@ -0,0 +1,44 @@ +{ + "calendar": { + "dayLabels": { + "Sun": "星期日", + "Mon": "星期一", + "Tue": "星期二", + "Wed": "星期三", + "Thu": "星期四", + "Fri": "星期五", + "Sat": "星期六" + }, + "monthLabels": { + "1": "1月", + "2": "2月", + "3": "3月", + "4": "4月", + "5": "5月", + "6": "6月", + "7": "7月", + "8": "8月", + "9": "9月", + "10": "10月", + "11": "11月", + "12": "12月" + }, + "titleFormat": "yyyy年 MM月", + "yearViewTitleFormat": "yyyy年", + "today": "今天", + "thisYear": "今年", + "yearView": "年", + "monthView": "月", + "weekView": "周", + "dayView": "日", + "prevMonth": "上一月", + "nextMonth": "下一月", + "prevWeek": "上一周", + "nextWeek": "下一周", + "prevDay": "上一天", + "nextDay": "下一天", + "prevYear": "上一年", + "nextYear": "下一年", + "more": "还有 #count# 项..." + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/calendar/src/locales/ui/zh-CHT.json b/packages/ui-vue/components/calendar/src/locales/ui/zh-CHT.json new file mode 100644 index 0000000000000000000000000000000000000000..60bc3d78da64386d532e9c9f1b7c8ef2542e65bb --- /dev/null +++ b/packages/ui-vue/components/calendar/src/locales/ui/zh-CHT.json @@ -0,0 +1,44 @@ +{ + "calendar": { + "dayLabels": { + "Sun": "星期日", + "Mon": "星期一", + "Tue": "星期二", + "Wed": "星期三", + "Thu": "星期四", + "Fri": "星期五", + "Sat": "星期六" + }, + "monthLabels": { + "1": "1月", + "2": "2月", + "3": "3月", + "4": "4月", + "5": "5月", + "6": "6月", + "7": "7月", + "8": "8月", + "9": "9月", + "10": "10月", + "11": "11月", + "12": "12月" + }, + "titleFormat": "yyyy年 MM月", + "yearViewTitleFormat": "yyyy年", + "today": "今天", + "thisYear": "今年", + "yearView": "年", + "monthView": "月", + "weekView": "周", + "dayView": "日", + "prevMonth": "上一月", + "nextMonth": "下一月", + "prevWeek": "上一周", + "nextWeek": "下一周", + "prevDay": "上一天", + "nextDay": "下一天", + "prevYear": "上一年", + "nextYear": "下一年", + "more": "還有 #count# 項..." + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/calendar/src/property-config/calendar.property-config.json b/packages/ui-vue/components/calendar/src/property-config/calendar.property-config.json deleted file mode 100644 index a71104fa0119694662a341a1e80320258baddda1..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/calendar/src/property-config/calendar.property-config.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "title": "calendar", - "description": "A Farris Component", - "type": "object", - "categories": { - "basic": { - "description": "Basic Infomation", - "title": "基本信息", - "properties": { - "id": { - "description": "组件标识", - "title": "标识", - "type": "string", - "readonly": true - }, - "type": { - "description": "组件类型", - "title": "控件类型", - "type": "select", - "editor": { - "type": "waiting for modification", - "enum": [] - } - } - } - } - } -} diff --git a/packages/ui-vue/components/calendar/src/property-config/calendar.property-config.ts b/packages/ui-vue/components/calendar/src/property-config/calendar.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..db0b88815b37c6998160b0e06b65264060a55697 --- /dev/null +++ b/packages/ui-vue/components/calendar/src/property-config/calendar.property-config.ts @@ -0,0 +1,381 @@ +import { FormSchemaEntity } from "@farris/ui-vue/components/common"; +import { BaseControlProperty, PropertyChangeObject } from "@farris/ui-vue/components/property-panel"; +import { customEventStylesCallback, eventItemFormatterCallback } from "./custome-callback"; +import { cloneDeep } from "lodash-es"; + +export class CalendarPropertyConfig extends BaseControlProperty { + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + + getPropertyConfig(propertyData: any) { + // 基本信息 + this.getBasicPropConfig(propertyData); + // 外观 + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + // 日历 + this.propertyConfig.categories['calendar'] = this.getCalendarConfig(propertyData); + // 事件 + this.propertyConfig.categories['eventsEditor'] = this.getEventPropConfig(propertyData); + + return this.propertyConfig; + } + + getBehaviorConfig(propertyData) { + const self = this; + return { + description: "Basic Infomation", + title: "行为", + properties: { + visible: { + description: "运行时组件是否可见", + title: "是否可见", + type: "boolean", + editor: this.getPropertyEditorParams(propertyData, ['Const', 'Variable']) + } + }, + setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'visible': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + } + }; + } + + getBasicPropConfig(propertyData: any) { + const mainEntity = this.formSchemaUtils.getFormSchema()?.module?.entity[0]?.entities[0]; + const entityTreeData = this.assembleSchemaEntityToTree(mainEntity, 0); + const self = this; + const basicConfig = super.getBasicPropConfig(propertyData); + this.propertyConfig.categories['basic'] = { + description: 'Basic Information', + title: '基本信息', + properties: { + ...basicConfig.properties, + dataSource: { + description: '绑定数据源', + title: '绑定数据源', + refreshPanelAfterChanged: true, + editor: { + type: 'combo-tree', + textField: 'name', + valueField: 'label', + data: entityTreeData, + editable: false, + idField: 'label', + enableClear: false + }, + readonly: false + } + }, + setPropertyRelates(changeObject: PropertyChangeObject, data: any) { + switch (changeObject && changeObject.propertyID) { + case 'dataSource': { + const viewModelNode = self.formSchemaUtils.getViewModelById(self.viewModelId); + if (viewModelNode) { + const selectedEntity = entityTreeData.find(entityData => entityData.label === changeObject.propertyValue); + viewModelNode.bindTo = selectedEntity.bindTo; + } + self.designViewModelUtils.assembleDesignViewModel(); + break; + } + } + } + }; + } + + 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; + } + + private onlySelectDateField(fields: any[]) { + fields.filter(field => { + if (field.data.type?.$type === 'DateType' || field.data.type?.$type === 'DateTimeType') { + field.selectable = true; + } else { + field.selectable = false; + } + + if (field.children && field.children.length) { + this.onlySelectDateField(field.children); + } + }); + + return fields; + } + + private treeNodeStatus = (visualData: any) => { + if (visualData.raw.$type !== 'SimpleField' || visualData.raw.selectable === false) { + visualData.disabled = true; + } + return visualData; + }; + + private showOptionsForMonth(propertyData: any) { + if (propertyData.enableMoreView == null) { + propertyData.enableMoreView = false; + } + + return propertyData.defaultView === 'month' || propertyData.enableMoreView; + } + + private getCalendarConfig(propertyData: any) { + const self = this; + const allFields = this.designViewModelUtils.getAllFields2TreeByVMId(this.viewModelId); + const onlyDateFields = this.onlySelectDateField(cloneDeep(allFields)); + + if (propertyData.defaultView == null) { + propertyData.defaultView = 'month'; + } + + const fieldSelector = { + idField: 'bindingPath', + type: 'combo-tree', + textField: 'name', + valueField: 'bindingPath', + data: allFields, + editable: false, + enableClear: false + }; + + return { + description: "日历组件配置", + title: "日历配置", + properties: { + // fit: { + // description: "填充父容器", + // refreshPanelAfterChanged: true, + // title: "填充父容器", + // type: "boolean", + // default: true, + // }, + // width: { + // description: "宽度", + // title: "宽度", + // type: "Number", + // visible: !propertyData.fit && propertyData.fit != null + // }, + // height: { + // description: "高度", + // title: "高度", + // type: "Number", + // default: 500, + // visible: !propertyData.fit && propertyData.fit != null + // }, + activeDate: { + description: "", + title: "默认显示日期", + type: "string", + editor: {...this.getPropertyEditorParams(propertyData, ['Variable','Expression'], '', {}, {newVariablePrefix: '', newVariableType: 'Date' }, 'compute'), + enableClear: false + } + }, + enableMoreView: { + description: "", + title: "启用多视图", + type: "boolean", + default: false, + refreshPanelAfterChanged: true, + }, + defaultView: { + description: "", + title: "默认视图", + type: "string", + refreshPanelAfterChanged: true, + editor: { + type: 'combo-list', + idField: 'value', + valueField: 'value', + textField: 'label', + data: [ + { label: '年', value: 'year' }, + { label: '月', value: 'month' } + ] + }, + }, + highLightWeekend: { + description: "是否高亮周末", + title: "高亮周六日", + type: "boolean", + visible: this.showOptionsForMonth(propertyData), + }, + firstDayOfTheWeek: { + description: "", + title: "每周起始日", + type: "string", + editor: { + type: 'combo-list', + idField: 'value', + valueField: 'value', + textField: 'label', + data: [ + { label: '星期日', value: 'Sun' }, + { label: '星期一', value: 'Mon' }, + ] + }, + visible: this.showOptionsForMonth(propertyData), + }, + enableCrossDay: { + description: "", + title: "允许跨天", + type: "boolean", + refreshPanelAfterChanged: true, + visible: this.showOptionsForMonth(propertyData), + }, + eventDateField: { + description: "", + title: "日期字段", + type: "string", + editor: { ...fieldSelector, data: onlyDateFields, customRowStatus: this.treeNodeStatus }, + visible: !propertyData.enableCrossDay, + }, + startDateField: { + description: "", + title: "开始日期字段", + type: "string", + editor: { ...fieldSelector, data: onlyDateFields, customRowStatus: this.treeNodeStatus }, + visible: !!propertyData.enableCrossDay, + }, + endDateField: { + description: "", + title: "结束日期字段", + type: "string", + editor: { ...fieldSelector, data: onlyDateFields, customRowStatus: this.treeNodeStatus }, + visible: !!propertyData.enableCrossDay, + }, + idField: { + description: "", + title: "标识字段", + type: "string", + editor: { ...fieldSelector }, + }, + textField: { + description: "", + title: "文本字段", + type: "string", + editor: { ...fieldSelector }, + }, + eventItemFormatter: { + description: "", + title: "自定义内容", + type: "string", + editor: { + type: "code-editor", + language: "javascript", + leftTemplate: eventItemFormatterCallback + }, + }, + customEventStyles: { + description: "", + title: "自定义样式", + type: "string", + editor: { + type: "code-editor", + language: "javascript", + leftTemplate: customEventStylesCallback + }, + }, + }, + setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'activeDate': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + case 'enableMoreView': + case 'defaultView': + if (propertyData.enableMoreView && changeObject.defaultView === 'year') { + propertyData.enableCrossDay = false; + } + break; + } + } + }; + } + + private getEventPropConfig(propertyData: any) { + const self = this; + const events = [ + // { + // label: 'onDayClick', + // name: '点击事件' + // }, + { + label: 'onEventClick', + name: '日程点击事件' + }, + { + label: 'onDateChange', + name: '日期切换事件' + }, + { + label: 'onMoreClick', + name: '显示更多点击事件' + }, + { + label: 'onViewChange', + name: '视图切换事件' + } + ]; + + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); + const properties = self.createBaseEventProperty(initialData); + + return { + title: '事件', + hideTitle: true, + properties, + // 这个属性,标记当属性变更得时候触发重新更新属性 + refreshPanelAfterChanged: true, + tabId: 'commands', + tabName: '交互', + setPropertyRelates(changeObject: any, newPropertyData: any) { + const parameters = changeObject.propertyValue; + delete newPropertyData[self.viewModelId]; + if (parameters) { + // parameters.setPropertyRelates = this.setPropertyRelates; // 添加自定义方法后,调用此回调方法,用于处理联动属性 + self.eventsEditorUtils.saveRelatedParameters(newPropertyData, self.viewModelId, parameters['events'], parameters); + } + + // 同步视图模型值变化事件 + const designVM = self.designViewModelUtils.getDgViewModel(self.viewModelId); + if (designVM && self.designViewModelField) { + designVM.changeField(self.designViewModelField.id, { valueChanging: newPropertyData.fieldValueChanging, valueChanged: newPropertyData.fieldValueChanged }); + } + } + }; + } + + +} diff --git a/packages/ui-vue/components/calendar/src/property-config/custome-callback.tsx b/packages/ui-vue/components/calendar/src/property-config/custome-callback.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2350736debdf4077262592645f80193112317cfe --- /dev/null +++ b/packages/ui-vue/components/calendar/src/property-config/custome-callback.tsx @@ -0,0 +1,73 @@ + +function renderexample(editor: any, code: string) { + return
    示例代码:
    ; +} + +const paramsRemarkText = (isFormatter = true) =>`/* +data: 日程数据 +context: 提供一些时间转换方法,以及获取日程数据的方法 + // 将日期转换为Date对象 parseToDate('2021-01-01', 'yyyy-MM-dd') + parseToDate: (date: string, format: string) => Date, + // 将日期转换为指定格式的字符串 fromatTo(new Date(), 'yyyy-MM-dd') + formatTo: (date: Date, format: string) => string, + // 获取日程数据的方法 getValue(data, 'name') + // 如果字段为多语字段,需要指定语言编码 getValue(data, 'name', locale) + getValue: (data: any, field: string, locale?:string) => string, + // 当前视图 + view: 'year' | 'month' | 'week' | 'day', + // 当前语言编码 + locale: string +${!isFormatter? '此方法返回一个对象,包含cls和styles两个属性,cls为事件样式类名,styles为事件样式对象':'此方法返回一个字符串,可包括HTML代码,如:
    ...
    '} +*/`; + +export const customEventStylesCallback = (editor: any) => { + const code = `(data, context) => { + /** + * const {formatTo, view, locale, getValue} = context; + * 多语字段需要指定语言编码,如 getValue(data, 'name', locale) + */ + const clsNames = [ + 'red', 'yellow', 'green', 'blue', 'light-green' + ]; + const level = data.zylevel; + return { + cls: 'event-bg-' + clsNames[level], + styles: { fontWeight: 'bold' } + } +}`; + + return <>{renderexample(editor, code)}
    +         {`${paramsRemarkText(false)}
    +${code}
    +`}
    +        
    +
    ; +}; + +export const eventItemFormatterCallback = (editor: any) => { + const code = `(data, context) => { + const {formatTo, view, locale, getValue} = context; + const timeFmt = view== 'year'? 'MM-dd HH:mm':'HH:mm'; + const timeWidth = view == 'year'? '80px': '40px'; + const styles = 'width:' + timeWidth + ';text-align: right;'; + + /* + 多语字段需要指定语言编码,如 getValue(data, 'name', locale) + */ + // dotted: 展示日程前的圆点 + return \` +
    +
    \${data.title}
    +
    \${formatTo(data.beginTime, timeFmt)}
    +
    \` +}`; + return <>{renderexample(editor, code)}
    +         {`${paramsRemarkText()}
    +${code}
    +`}
    +
    +
    +; +}; diff --git a/packages/ui-vue/components/calendar/src/schema/calendar.schema.json b/packages/ui-vue/components/calendar/src/schema/calendar.schema.json index 4502855a05a6e7563cfef8bd8b0534ce52ef52bf..38a7db1ac36567e39d00467bc8d0b1d7ca21771b 100644 --- a/packages/ui-vue/components/calendar/src/schema/calendar.schema.json +++ b/packages/ui-vue/components/calendar/src/schema/calendar.schema.json @@ -26,10 +26,106 @@ } }, "default": {} + }, + "dataSource": { + "description": "", + "type": "string", + "default": "" + }, + "visible": { + "description": "", + "type": "boolean", + "default": true + }, + "fit": { + "description": "填充父容器", + "type": "boolean", + "default": true + }, + "width": { + "description": "宽度", + "type": "Number", + "default": 600 + }, + "height": { + "description": "高度", + "type": "Number", + "default": 500 + }, + "firstDayOfTheWeek": { + "description": "每周起始日", + "type": "String", + "default": "Sun" + }, + "highLightWeekend": { + "description": "是否高亮周末", + "type": "Boolean", + "default": true + }, + "eventDateField": { + "description": "事件日期字段", + "type": "String", + "default": "" + }, + "activeDate": { + "description": "默认日期", + "type": "String", + "default": "" + }, + "eventItemFormatter": { + "description": "自定义事件内容格式化", + "type": "String", + "default": "" + }, + "customEventStyles": { + "description": "自定义事件样式", + "type": "String", + "default": "" + }, + "textField": { + "description": "事件文本字段", + "type": "String", + "default": "title" + }, + "idField": { + "description": "事件id字段", + "type": "String", + "default": "id" + }, + "enableMoreView": { + "description": "是否启用多视图", + "type": "Boolean", + "default": false + }, + "defaultView": { + "description": "默认视图", + "type": "String", + "default": "month" + }, + "enableCrossDay": { + "description": "是否允许跨天", + "type": "Boolean", + "default": false + }, + "startDateField": { + "description": "事件开始日期字段", + "type": "String", + "default": "" + }, + "endDateField": { + "description": "事件结束日期字段", + "type": "String", + "default": "" } }, "required": [ "id", "type" - ] + ], + "events": { + "onEventClick": "日程点击事件", + "onDateChange": "日期切换事件", + "onMoreClick": "更多点击事件", + "onViewChange": "视图切换事件" + } } \ No newline at end of file diff --git a/packages/ui-vue/components/calendar/src/schema/schema-resolver.ts b/packages/ui-vue/components/calendar/src/schema/schema-resolver.ts index d36ae7457434b7381b796814e9926d352feacff6..01578b74963a2ad6144af3a19f53d56c1fdd9340 100644 --- a/packages/ui-vue/components/calendar/src/schema/schema-resolver.ts +++ b/packages/ui-vue/components/calendar/src/schema/schema-resolver.ts @@ -1,5 +1,30 @@ +import { DesignerComponentInstance, DesignerHostService } from "@farris/ui-vue/components/designer-canvas"; +import { ComponentBuildInfo } from "@farris/ui-vue/components/component"; import { DynamicResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { useCalendarCreator } from "../composition/use-calendar-creator"; -export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { - return schema; +export function schemaResolver( + resolver: DynamicResolver, + schema: Record, + context: Record, + designerHostService?: DesignerHostService): Record { + const parentComponentInstance = context.parentComponentInstance as DesignerComponentInstance; + if (parentComponentInstance && designerHostService) { + const radomNumber = Math.random().toString(36).slice(2, 6); + const componentBuildInfo: ComponentBuildInfo = { + componentId: `calendar-${radomNumber}`, + componentName: context.bindingSourceContext?.entityTitle || context.bindingSourceContext?.bindingEntity?.name || `日历-${radomNumber}`, + componentType: 'calendar', + parentContainerId: parentComponentInstance.schema.id, + parentComponentInstance: parentComponentInstance, + bindTo: context.bindingSourceContext?.bindTo || '', + dataSource: context.bindingSourceContext?.bindingEntity?.label, + }; + const { createComponent } = useCalendarCreator(resolver, designerHostService); + const componentRefNode = createComponent(componentBuildInfo); + + return componentRefNode; + } else { + return schema; + } } diff --git a/packages/ui-vue/components/calendar/src/types/calendar.ts b/packages/ui-vue/components/calendar/src/types/calendar.ts index d10aaefc8bc2ab85d5256cbd9bc1032e7c02e82a..f1e0876688197176735af5f1a5c68cc2ddc4ad34 100644 --- a/packages/ui-vue/components/calendar/src/types/calendar.ts +++ b/packages/ui-vue/components/calendar/src/types/calendar.ts @@ -1,15 +1,61 @@ import { DateObject, MonthTag } from './common'; -import { ScheduleEvent } from './schedule'; export interface DayInCalendar { date: DateObject; - events?: ScheduleEvent[]; + events?: Record[]; isCurrent?: boolean; monthTag: MonthTag; + weekEnd?: boolean; +} + +export interface CalendarEvent { + id: string; + data: Record; + eventDate: DateObject; + isSingleDay?: boolean; + isCrossDay?: boolean; + startDate?: DateObject; + endDate?: DateObject; + startIndex?: number; + endIndex?: number; + colspan?: number; + isOverlapping?: boolean; + originalId?: string; + rowspan?: number; } export interface WeekInCalendar { days: Array; weekNumber: number; year: number; + events?: Array; +} + + +export type CalendarView = 'year'| 'month' | 'week' | 'day'; + +export const CalendarViews: CalendarView[] = ['year', 'month', 'week', 'day']; + +export enum Compare { + 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 = 0, + And = 1, + Or = 2 } diff --git a/packages/ui-vue/components/checkbox-group/designer.ts b/packages/ui-vue/components/checkbox-group/designer.ts index 5aafba46cb52cc7416fdc1542078828d514dd7e4..414767717130213136171411c9f8bf1459c98b8f 100644 --- a/packages/ui-vue/components/checkbox-group/designer.ts +++ b/packages/ui-vue/components/checkbox-group/designer.ts @@ -1,5 +1,5 @@ -import { withInstall } from '@farris/ui-vue/components/common'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { RegisterContext, withInstall } from '@farris/ui-vue/components/common'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import FCheckboxGroup, {checkboxGroupProps, type CheckboxGroupProps} from '@farris/ui-vue/components/checkbox-group'; import FCheckboxGroupDesigner from './src/designer/checkbox-group.design.component'; import { schemaMapper } from './src/schema/schema-mapper'; @@ -8,14 +8,24 @@ import checkGropSchema from './src/schema/check-group.schema.json'; const checkBoxGroupPropsResolver = createPropsResolver(checkboxGroupProps, checkGropSchema, schemaMapper, schemaResolver); + +export const propsResolverGenerator = getPropsResolverGenerator( + checkboxGroupProps, + checkGropSchema, + schemaMapper, + schemaResolver +); -FCheckboxGroupDesigner.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FCheckboxGroupDesigner.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext) => { componentMap['check-group'] = FCheckboxGroup; - propsResolverMap['check-group'] = checkBoxGroupPropsResolver; + propsResolverMap['check-group'] = propsResolverGenerator(registerContext); }; -FCheckboxGroupDesigner.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FCheckboxGroupDesigner.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext +) => { componentMap['check-group'] = FCheckboxGroupDesigner; - propsResolverMap['check-group'] = checkBoxGroupPropsResolver; + propsResolverMap['check-group'] = propsResolverGenerator(registerContext); }; export { FCheckboxGroupDesigner, checkBoxGroupPropsResolver }; diff --git a/packages/ui-vue/components/checkbox/designer.ts b/packages/ui-vue/components/checkbox/designer.ts index 5f92f9f947fab18701a58ada9c6791776571c073..e6320877158bbfc94074c01b6d96920fedf6a407 100644 --- a/packages/ui-vue/components/checkbox/designer.ts +++ b/packages/ui-vue/components/checkbox/designer.ts @@ -1,5 +1,5 @@ -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; -import { withInstall } from '@farris/ui-vue/components/common'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; +import { RegisterContext, withInstall } from '@farris/ui-vue/components/common'; import FCheckbox, { type CheckboxProps, checkboxProps } from '@farris/ui-vue/components/checkbox'; import FCheckboxDesigner from './src/designer/checkbox.design.component'; import checkboxSchema from './src/schema/checkbox.schema.json'; @@ -11,14 +11,23 @@ const checkBoxPropsResolver = createPropsResolver(checkboxProps, schemaMapper, schemaResolver ); +export const propsResolverGenerator = getPropsResolverGenerator( + checkboxProps, + checkboxSchema, + schemaMapper, + schemaResolver +); -FCheckboxDesigner.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FCheckboxDesigner.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext : RegisterContext) => { componentMap['check-box'] = FCheckbox; - propsResolverMap['check-box'] = checkBoxPropsResolver; + propsResolverMap['check-box'] = propsResolverGenerator(registerContext); }; -FCheckboxDesigner.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FCheckboxDesigner.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext : RegisterContext +) => { componentMap['check-box'] = FCheckboxDesigner; - propsResolverMap['check-box'] = checkBoxPropsResolver; + propsResolverMap['check-box'] = propsResolverGenerator(registerContext); }; export { FCheckboxDesigner, checkBoxPropsResolver }; diff --git a/packages/ui-vue/components/checkbox/src/checkbox.css b/packages/ui-vue/components/checkbox/src/checkbox.css index 6a6a9c37f2ef34e7e9874771c4e540e1b3314f32..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/packages/ui-vue/components/checkbox/src/checkbox.css +++ b/packages/ui-vue/components/checkbox/src/checkbox.css @@ -1,139 +0,0 @@ - - -/* .f-checkbox-group button.active { - color: #fff; - background: #2a87ff; - border-color: #2a87ff; -} */ -.f-radio-button, -.f-radio-tag { - color: #2d2f33; - background: #fff; - border: 1px solid #e8ebf2; -} -.f-radio-button-success:hover { - color: #6cc77f; - background: #fff; - border-color: #6cc77f; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-warning:hover { - color: #f5a144; - background: #fff; - border-color: #f5a144; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-danger:hover { - color: #f46160; - background: #fff; - border-color: #f46160; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-primary:hover { - color: #2a87ff; - background: #fff; - border-color: #2a87ff; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-success.active { - color: #fff; - background: #6cc77f; - border-color: #6cc77f; -} - -.f-radio-button-warning.active { - color: #fff; - background: #f5a144; - border-color: #f5a144; -} -.f-radio-button-danger.active { - color: #fff; - background: #f46160; - border-color: #f46160; -} -.f-radio-button-primary.active { - color: #fff; - background: #2a87ff; - border-color: #2a87ff; -} - -.f-radio-tag { - display: inline-block; - position: relative; - margin-right: 8px; - padding: 3px 16px; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 2px; - overflow: hidden; - cursor: pointer; - margin-bottom: 0; -} -.f-radio-icon { - position: absolute; - display: block; - right: -4px; - bottom: -3px; - font-size: 12px; - color: #fff; -} - -.f-radio-tag > .title { -} - -.f-radio-tag > .tip { - position: absolute; - display: block; - right: 0; - bottom: 0; - width: 14px; - height: 14px; - border: 7px solid #dadada; - border-top: 7px solid transparent; - border-left: 7px solid transparent; -} - -.f-radio-tag-success.active { - color: #6cc77f; - border-color: rgb(108, 199, 127); - background: rgba(108, 199, 127, 0.05); -} - -.f-radio-tag-warning.active { - color: #f5a144; - border-color: #f5a144; - background: rgba(245, 161, 68, 0.05); -} -.f-radio-tag-danger.active { - color: #f46160; - border-color: rgb(245, 97, 97); - background: rgba(245, 97, 97, 0.05); -} -.f-radio-tag-primary.active { - color: #2a87ff; - border-color: rgb(42, 135, 255); - background: rgba(42, 135, 255, 0.05); -} - -.f-radio-tag-success.active > .tip { - border-right-color: #6cc77f; - border-bottom-color: #6cc77f; -} - -.f-radio-tag-warning.active > .tip { - border-right-color: #f5a144; - border-bottom-color: #f5a144; -} - -.f-radio-tag-danger.active > .tip { - border-right-color: #f46160; - border-bottom-color: #f46160; -} - -.f-radio-tag-primary.active > .tip { - border-right-color: #2a87ff; - border-bottom-color: #2a87ff; -} diff --git a/packages/ui-vue/components/checkbox/src/property-config/checkbox.property-config.ts b/packages/ui-vue/components/checkbox/src/property-config/checkbox.property-config.ts index c766bd084a2de343805bf81a57306a8b1d79a226..046f884b461ad3fb8a350dda91e0e2aad6523202 100644 --- a/packages/ui-vue/components/checkbox/src/property-config/checkbox.property-config.ts +++ b/packages/ui-vue/components/checkbox/src/property-config/checkbox.property-config.ts @@ -24,7 +24,7 @@ export class CheckBoxProperty extends InputBaseProperty { description: "选中的值", title: "选中的值", type: this.getBindingDataType(), - visible: this.designViewModelField?.type.name !== 'Boolean', + visible: this.designViewModelField != null && this.designViewModelField?.type.name !== 'Boolean', refreshPanelAfterChanged: true, editor: this.getEditor(), $converter: this.getBooleanValueConverter() @@ -33,7 +33,7 @@ export class CheckBoxProperty extends InputBaseProperty { description: "未选中的值", title: "未选中的值", type: this.getBindingDataType(), - visible: this.designViewModelField?.type.name !== 'Boolean', + visible: this.designViewModelField != null && this.designViewModelField?.type.name !== 'Boolean', refreshPanelAfterChanged: true, editor: this.getEditor(), $converter: this.getBooleanValueConverter() diff --git a/packages/ui-vue/components/code-editor/src/code-textbox.component.tsx b/packages/ui-vue/components/code-editor/src/code-textbox.component.tsx index f09d9c35746eb5df1d801ea2d838d310eaceb3e0..320c235434feba6061054e21a39abb6e8125553d 100644 --- a/packages/ui-vue/components/code-editor/src/code-textbox.component.tsx +++ b/packages/ui-vue/components/code-editor/src/code-textbox.component.tsx @@ -118,7 +118,7 @@ export default defineComponent({ props.leftTemplate ?
    { - props.leftTemplate!() + props.leftTemplate!(codeEditorRef) }
    diff --git a/packages/ui-vue/components/code-editor/src/code-textbox.props.ts b/packages/ui-vue/components/code-editor/src/code-textbox.props.ts index ff7790688f17d1d4bd8a5ccf212f2514d8ae4c64..8575a2560c0c8d1472442ba92db440095fd90de0 100644 --- a/packages/ui-vue/components/code-editor/src/code-textbox.props.ts +++ b/packages/ui-vue/components/code-editor/src/code-textbox.props.ts @@ -30,7 +30,7 @@ export const codeTextboxProps = { onSubmitModal: { type: Function as PropType<(newValue: string) => void>, default: null }, /** 左侧模板 */ - leftTemplate: { type: Function as PropType<() => any> } + leftTemplate: { type: Function as PropType<(instance: any) => any> } }; export type CodeTextboxProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/collection-property-editor/src/collection-property-editor.component.tsx b/packages/ui-vue/components/collection-property-editor/src/collection-property-editor.component.tsx index 48f3339ee0b538b555f2b2a3730badf5a04defc7..b48948c6501c80d480e414f6bc11f5b8d5e64aaf 100644 --- a/packages/ui-vue/components/collection-property-editor/src/collection-property-editor.component.tsx +++ b/packages/ui-vue/components/collection-property-editor/src/collection-property-editor.component.tsx @@ -32,13 +32,37 @@ export default defineComponent({ const propertyData = ref(cloneDeep(props.modelValue) || []); /** 编辑器输入框的显示内容 */ const displayText = computed(() => { - return `共 ${propertyData.value?.length || 0} 项`; + let showLength = 0; + if(propertyData.value){ + if(props.visibleCondition){ + showLength = propertyData.value.filter(props.visibleCondition).length; + }else{ + showLength = propertyData.value.length; + } + } + + return `共 ${showLength || 0} 项`; }); + /** 净化data,移除私有属性 */ + function cleanData(data: any[]) { + if(!data){ + return; + } + data.forEach((dataItem: any) => { + Object.keys(dataItem).forEach((key: string) => { + if (key.indexOf('__fv') > -1) { + delete dataItem[key]; + } + }); + }); + } + /** * 确定事件 */ function onSubmit() { + cleanData(propertyData.value); context.emit('valueChange', propertyData.value); return true; } @@ -100,6 +124,7 @@ export default defineComponent({ onSelectionChange={onSelectionChange} useFormCommand={useFormCommand} useFormSchema={useFormSchema} + visibleCondition={props.visibleCondition} >
    ; diff --git a/packages/ui-vue/components/collection-property-editor/src/components/collection-property-container.component.tsx b/packages/ui-vue/components/collection-property-editor/src/components/collection-property-container.component.tsx index 6208c9afa931c08f22883f7cd6f6731b642063fa..c5d69239834009384ac8cf9ef61fecd8633d25b8 100644 --- a/packages/ui-vue/components/collection-property-editor/src/components/collection-property-container.component.tsx +++ b/packages/ui-vue/components/collection-property-editor/src/components/collection-property-container.component.tsx @@ -49,6 +49,12 @@ export default defineComponent({ { field: textField, title: '名称', width: 340, resizable: true, dataType: 'string' }, ]; + const filteredData = computed(() => { + return propertyData.value.filter(item => { + return props.visibleCondition ? props.visibleCondition(item) :true; + }); + }); + /** * 获取新的Schema * @returns @@ -71,7 +77,7 @@ export default defineComponent({ // 1、添加新节点 const newSchema = getNewSchema(); propertyData.value.push(newSchema); - treeViewRef.value.updateDataSource(propertyData.value); + treeViewRef.value.updateDataSource(filteredData.value); // 2、选中新节点 treeViewRef.value.selectItemById(newSchema[idField]); @@ -96,7 +102,7 @@ export default defineComponent({ // 1、删除节点 const indexToDelete = propertyData.value.indexOf(selectedData.value); propertyData.value.splice(indexToDelete, 1); - treeViewRef.value.updateDataSource(propertyData.value); + treeViewRef.value.updateDataSource(filteredData.value); // 2、选中第一个节点 selectFirstNode(); @@ -121,7 +127,7 @@ export default defineComponent({ // 2、下移节点 propertyData.value[indexToMove] = propertyData.value[indexToMove + 1]; propertyData.value[indexToMove + 1] = selectedData.value; - treeViewRef.value.updateDataSource(propertyData.value); + treeViewRef.value.updateDataSource(filteredData.value); } /** @@ -142,7 +148,7 @@ export default defineComponent({ // 2、上移节点 propertyData.value[indexToMove] = propertyData.value[indexToMove - 1]; propertyData.value[indexToMove - 1] = selectedData.value; - treeViewRef.value.updateDataSource(propertyData.value); + treeViewRef.value.updateDataSource(filteredData.value); } /** @@ -161,7 +167,7 @@ export default defineComponent({ * @param propertyData */ function onPropertyChanged() { - treeViewRef.value.updateDataSource(propertyData.value); + treeViewRef.value.updateDataSource(filteredData.value); } @@ -183,8 +189,10 @@ export default defineComponent({ ; diff --git a/packages/ui-vue/components/collection-property-editor/src/schema/collection-property-editor.schema.json b/packages/ui-vue/components/collection-property-editor/src/schema/collection-property-editor.schema.json index cc54c526e69af87989f5e8a0edbaa766932867ab..b8827eb5c97d77c50c043b47ccdcb85e91ec7530 100644 --- a/packages/ui-vue/components/collection-property-editor/src/schema/collection-property-editor.schema.json +++ b/packages/ui-vue/components/collection-property-editor/src/schema/collection-property-editor.schema.json @@ -46,6 +46,10 @@ "modalTitle": { "type": "string", "default": "编辑器" + }, + "visibleCondition": { + "type": "object", + "default": null } }, "required": [ diff --git a/packages/ui-vue/components/combo-list/index.ts b/packages/ui-vue/components/combo-list/index.ts index bd5bbd9ac1d708e04b1535fd944e4e4d402c22af..8303218d25443158a4ae0227535aba8069b87c60 100644 --- a/packages/ui-vue/components/combo-list/index.ts +++ b/packages/ui-vue/components/combo-list/index.ts @@ -16,18 +16,22 @@ */ import FComboList from './src/combo-list.component'; import FComboListDesign from './src/designer/combo-list.design.component'; -import { propsResolver } from './src/combo-list.props'; -import { withInstall } from '@farris/ui-vue/components/common'; - +import { callbackResolver, propsResolver, propsResolverGenerator } from './src/combo-list.props'; +import {RegisterContext, withInstall } from '@farris/ui-vue/components/common'; +import FComboListContainer from './src/components/list-container.component'; export * from './src/combo-list.props'; +export * from './src/composition/use-data-source'; -FComboList.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FComboList.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext : RegisterContext) => { componentMap['combo-list'] = FComboList; - propsResolverMap['combo-list'] = propsResolver; + propsResolverMap['combo-list'] = propsResolverGenerator(registerContext); + resolverMap['combo-list'] = { callbackResolver }; }; -FComboList.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FComboList.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext : RegisterContext) => { componentMap['combo-list'] = FComboListDesign; - propsResolverMap['combo-list'] = propsResolver; + propsResolverMap['combo-list'] = propsResolverGenerator(registerContext); }; -export { FComboList }; +export { FComboList, FComboListContainer }; export default withInstall(FComboList); diff --git a/packages/ui-vue/components/combo-list/src/combo-list.component.tsx b/packages/ui-vue/components/combo-list/src/combo-list.component.tsx index 614dfb01097eb89a88cb1e4b2c0ee1f656ee1d1e..13434c1a24c0fb89323e1eb5a9a5f2b810e75f05 100644 --- a/packages/ui-vue/components/combo-list/src/combo-list.component.tsx +++ b/packages/ui-vue/components/combo-list/src/combo-list.component.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ import { computed, defineComponent, nextTick, Ref, ref, SetupContext, watch } from 'vue'; -import { useI18n } from 'vue-i18n'; import FButtonEdit from '@farris/ui-vue/components/button-edit'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { comboListProps, ComboListProps, Option } from './combo-list.props'; import ComboListContainer from './components/list-container.component'; import { useDataSource } from './composition/use-data-source'; @@ -25,7 +25,6 @@ export default defineComponent({ props: comboListProps, emits: ['clear', 'update:modelValue', 'change', 'input'] as (string[] & ThisType) | undefined, setup(props: ComboListProps, context: SetupContext) { - const { t: getLocaleValue } = useI18n(); const comboListContainerRef = ref(); const comboEditorRef: Ref = ref(); // 只读状态下应该也是禁用,不触发任何事件,如果是只读状态,还会触发click等事件 @@ -34,8 +33,8 @@ export default defineComponent({ const enableSearch = ref(props.enableSearch); const readonly = ref(props.readonly); let displayTextStore = ''; - const { dataSource, displayText, editable, modelValue, getSelectedItemsByDisplayText, - getItemsByDisplayText, getItemsByValue } = useDataSource(props); + const { dataSource, displayText, editable, modelValue, + getItemsByDisplayText, getItemsByValue, beforeOpenList } = useDataSource(props); const dropdownIcon = ref(props.dropDownIcon); if (dropdownIcon.value === '') { @@ -56,7 +55,7 @@ export default defineComponent({ } function onSelectionChange(selectedItems: Option[]) { - displayText.value = selectedItems.map((item: Option) => item[props.textField]).join(props.separator); + displayText.value = selectedItems.map((item: Option) => item[props.textField]).join(props.separator) || modelValue.value; let value = ''; if (selectedItems.length === 1) { value = selectedItems[0][props.valueField]; @@ -89,8 +88,9 @@ export default defineComponent({ function onClear($event: Event) { modelValue.value = ''; comboListContainerRef.value?.activeRowById(''); - context.emit('update:modelValue', ''); - context.emit('change', [], ''); + // 已通过onClearValue触发过change、update事件,此处再触发会导致两次触发 + // context.emit('update:modelValue', ''); + // context.emit('change', [], ''); context.emit('clear'); } @@ -123,6 +123,11 @@ export default defineComponent({ context.emit('update:modelValue', modelValue.value); context.emit('change', itemList, modelValue.value); } + }else if(text!==modelValue.value){ + // 只有单选清空时会走此处 + modelValue.value = text; + context.emit('update:modelValue', modelValue.value); + context.emit('change', [], text); } } @@ -143,10 +148,22 @@ export default defineComponent({ comboEditorRef.value.hidePopup(); } - context.expose({ + function updateDataSource(data: any) { + dataSource.value = data; + } + + function getData() { + return dataSource.value; + } + + const instance = { getDisplayText, - hidePopup - }); + hidePopup, + updateDataSource, + getData + }; + + context.expose(instance); watch( [() => props.disabled, () => props.editable, () => props.enableClear, () => props.enableSearch, () => props.readonly], @@ -169,7 +186,7 @@ export default defineComponent({ forcePlaceholder={props.forcePlaceholder} editable={editable.value} buttonContent={dropdownIcon.value} - placeholder={props.placeholder === '请选择' ? getLocaleValue('comboList.placeholder') : props.placeholder} + placeholder={props.placeholder === '请选择' ? LocaleService.getLocaleValue('comboList.placeholder') : props.placeholder} enableClear={enableClear.value} maxLength={props.maxLength} tabIndex={props.tabIndex} @@ -184,7 +201,7 @@ export default defineComponent({ onChange={onChange} onBlur={onClickOutside} onInput={onInput} - beforeOpen={props.beforeOpen} + beforeOpen={beforeOpenList} placement={props.placement} popupMinWidth={props.minPanelWidth} popupClass={'f-combo-list-wrapper'} diff --git a/packages/ui-vue/components/combo-list/src/combo-list.props.ts b/packages/ui-vue/components/combo-list/src/combo-list.props.ts index 5ce8eca027d28e63606d63b913658373e000da39..e2c39dcc2fd7d8adc8d363f79235a92e32c6cecb 100644 --- a/packages/ui-vue/components/combo-list/src/combo-list.props.ts +++ b/packages/ui-vue/components/combo-list/src/combo-list.props.ts @@ -16,10 +16,11 @@ */ import { ExtractPropTypes, PropType } from 'vue'; import { ButtonEditInputType } from '@farris/ui-vue/components/button-edit'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapper } from './schema/schema-mapper'; import comboListSchema from './schema/combo-list.schema.json'; import { schemaResolver } from './schema/schema-resolver'; +import { createComboListCallbackResolver } from './schema/callback-resolvers'; export interface Option { disabled?: boolean; @@ -212,6 +213,7 @@ export const comboListProps = { * 打开前 */ beforeOpen: { type: Function as PropType, default: null }, + load: { type: Function as PropType<() => Promise>}, searchOption: { type: [Boolean, Function] as PropType boolean)>, default: false @@ -231,3 +233,13 @@ export const comboListDesignProps = Object.assign({}, comboListProps, { export type ComboListDesignProps = ExtractPropTypes; export const propsResolver = createPropsResolver(comboListProps, comboListSchema, schemaMapper, schemaResolver); + + +export const propsResolverGenerator = getPropsResolverGenerator( + comboListProps, + comboListSchema, + schemaMapper, + schemaResolver +); + +export const callbackResolver = createComboListCallbackResolver(); diff --git a/packages/ui-vue/components/combo-list/src/components/list-container.component.tsx b/packages/ui-vue/components/combo-list/src/components/list-container.component.tsx index fc60f0613389dee13623f1c7487e2cdc799759b0..cb53f18b8cf23f4e919da3a888cca794fa44b780 100644 --- a/packages/ui-vue/components/combo-list/src/components/list-container.component.tsx +++ b/packages/ui-vue/components/combo-list/src/components/list-container.component.tsx @@ -29,8 +29,8 @@ export default defineComponent({ const separator = ref(props.separator); const width = ref(props.width); const maxHeight = ref(props.maxHeight); - const selectionValues = ref(String(props.selectedValues).split(separator.value)); const showCheckbox = computed(() => props.multiSelect); + const selectionValues = ref(props.multiSelect? String(props.selectedValues).split(separator.value): [props.selectedValues]); // listview selection const selection = computed(() => { return { @@ -108,7 +108,8 @@ export default defineComponent({ // dataSource的value可能有多种数据类型,应该确保选中行的value全等 const selectedValue = selectionValues.value?.[0]; let realSelectedValue: any = selectedValue; - if (!isUndefined(selectedValue)) { + const valueList = dataSource.value.map(item => item[props.valueField || props.idField]); + if (!isUndefined(selectedValue) && valueList.includes(selectedValue)) { const selectedValueType = typeof selectedValue; // get datasource value type const dataSourceValueType = typeof dataSource.value[0]?.[props.valueField || props.idField]; diff --git a/packages/ui-vue/components/combo-list/src/composition/types.ts b/packages/ui-vue/components/combo-list/src/composition/types.ts index 2d2092b94b518b375d71f35e146da6d3db6fbd06..0ed8f60e8e30ad54581bb7a1a66ce46fdfc47eab 100644 --- a/packages/ui-vue/components/combo-list/src/composition/types.ts +++ b/packages/ui-vue/components/combo-list/src/composition/types.ts @@ -17,4 +17,6 @@ export interface UseDataSource { getSelectedItemsByDisplayText: (text: string) => Option[]; + beforeOpenList: (instance: any) => Promise; + } diff --git a/packages/ui-vue/components/combo-list/src/composition/use-data-source.ts b/packages/ui-vue/components/combo-list/src/composition/use-data-source.ts index af15caa15be38a5118076fa7dbd4b5c19fca3327..f84e93c97bbc76095fdf500abd72f43b7f152d59 100644 --- a/packages/ui-vue/components/combo-list/src/composition/use-data-source.ts +++ b/packages/ui-vue/components/combo-list/src/composition/use-data-source.ts @@ -1,15 +1,15 @@ import { ref, watch } from "vue"; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { ComboListProps, Option } from "../combo-list.props"; import { UseDataSource } from "./types"; export function useDataSource(props: ComboListProps): UseDataSource { - const { t: getLocaleValue } = useI18n(); const displayText = ref(''); const modelValue = ref(props.modelValue); const dataSource = ref(props.data || []); const editable = ref(props.editable); + function getItemsByValue(value: string): Option[] { const valueList = props.multiSelect ? String(value).split(props.separator) : [String(value)]; const valueArray = valueList.map<[string, boolean]>((valueText: string) => { @@ -27,6 +27,7 @@ export function useDataSource(props: ComboListProps): UseDataSource { function updateDisplayTextByValue(value: string) { const matchedValue = getItemsByValue(value).map((item: Option) => item[props.textField]).join(props.separator); displayText.value = editable.value ? (matchedValue || value) : matchedValue; + // displayText.value = matchedValue || value; } function getItemsByDisplayText(text: string): Option[] { @@ -57,8 +58,11 @@ export function useDataSource(props: ComboListProps): UseDataSource { } function loadRemoteData() { - const { url, method = 'GET', headers = {'Content-Type': 'application/json;charset=utf-8;'}, body = null } = props.remote; + const { url, method = 'GET', headers = { 'Content-Type': 'application/json;charset=utf-8;' }, body = null } = props.remote; // const requestInfo = { method, headers, body }; + if (!url) { + return; + } const requestInfo = method.toLowerCase() === 'get' ? { method, headers } : { method, headers, body }; let isFromJson = false; fetch(new Request(url, requestInfo)) @@ -68,8 +72,8 @@ export function useDataSource(props: ComboListProps): UseDataSource { isFromJson = !!response.headers?.get('content-type')?.includes('application/json'); return isFromJson ? response.text() : response.json(); } - if(response.status === 405) { - throw new Error(getLocaleValue('comboList.remoteError')); + if (response.status === 405) { + throw new Error(LocaleService.getLocaleValue('comboList.remoteError')); } const error = new Error(response.statusText); throw error; @@ -85,7 +89,15 @@ export function useDataSource(props: ComboListProps): UseDataSource { } if (props.remote) { - loadRemoteData(); + if (props.load) { + props.load().then((data: any) => { + dataSource.value = data; + }).catch((e: any) => { + console.log(e);; + }); + } else { + loadRemoteData(); + } } watch(() => props.data, () => { @@ -108,7 +120,44 @@ export function useDataSource(props: ComboListProps): UseDataSource { updateDisplayTextByValue(newValue); }); + function updateDataSource(data: any) { + dataSource.value = data; + } + + function getData() { + return dataSource.value; + } + + function getDisplayText() { + return displayText.value; + } + + function beforeOpenList() { + const isFunction = typeof props.beforeOpen === 'function'; + return !props.beforeOpen || !isFunction ? Promise.resolve(true) : + Promise.resolve() + .then(() => { + return props.beforeOpen({ instance: { updateDataSource, getData, getDisplayText } }); + }) + .then((result: { canOpen: boolean, data: any }) => { + return typeof result?.canOpen === 'boolean' ? result?.canOpen : true; + }); + } + + // if (props.beforeOpen) { + // // 兼容 beforeOpen,初始化下拉组件时,应该拿到最新的数据源 + // beforeOpenList().then((value: boolean) => { + // if (value) { + // updateDisplayTextByValue(props.modelValue); + // } + // }); + // } else { + // updateDisplayTextByValue(props.modelValue); + // } updateDisplayTextByValue(props.modelValue); - return { dataSource, displayText, editable, modelValue, getItemsByDisplayText, getItemsByValue, getSelectedItemsByDisplayText }; + return { + dataSource, displayText, editable, modelValue, beforeOpenList, + getItemsByDisplayText, getItemsByValue, getSelectedItemsByDisplayText + }; } diff --git a/packages/ui-vue/components/combo-list/src/property-config/combo-list.property-config.ts b/packages/ui-vue/components/combo-list/src/property-config/combo-list.property-config.ts index 2c890471294fc333ab9f51cbeee48b0e71ecae6d..c091bbfb8725e28effaa892ad873df226f30593a 100644 --- a/packages/ui-vue/components/combo-list/src/property-config/combo-list.property-config.ts +++ b/packages/ui-vue/components/combo-list/src/property-config/combo-list.property-config.ts @@ -94,7 +94,7 @@ export class ComboListProperty extends InputBaseProperty { visible: !propertyData.editor.dataSourceType || propertyData.editor.dataSourceType === "static", ...self.getItemCollectionEditor(propertyData, propertyData.editor.valueField, propertyData.editor.textField), // 这个属性,标记当属性变更得时候触发重新更新属性 - refreshPanelAfterChanged: true, + refreshPanelAfterChanged: true }, url: { visible: propertyData.editor.dataSourceType === "dynamic", @@ -119,7 +119,7 @@ export class ComboListProperty extends InputBaseProperty { // }, // }, body: { - visible: propertyData.editor.dataSourceType === "dynamic", + visible: false, $converter: comboListRemoteConverter, description: "", title: "服务端API参数", @@ -167,10 +167,12 @@ export class ComboListProperty extends InputBaseProperty { propertyData.editor.valueField = 'value'; propertyData.editor.textField = 'name'; propertyData.editor.remote = null; + // propertyData.editor.load = null; } else if (changeObject.propertyValue === 'dynamic') { propertyData.editor.remote = { method: 'GET' }; propertyData.editor.valueField = 'value'; propertyData.editor.textField = 'name'; + // propertyData.editor.load = propertyData.load; } break; } @@ -218,11 +220,24 @@ export class ComboListProperty extends InputBaseProperty { title: "启用清空", type: "boolean" }, + // dataSourceType: { + // description: "", + // title: "数据源类型", + // type: "enum", + // editor: { + // default: "static", + // data: [ + // { id: "static", name: "静态" }, + // { id: "dynamic", name: "动态" } + // ] + // }, + // refreshPanelAfterChanged: true + // }, data: { description: "", title: "数据", type: "array", - visible: true, + visible: !propertyData.editor?.dataSourceType || propertyData.editor?.dataSourceType === "static", ...self.getItemCollectionEditor(propertyData, propertyData.editor?.valueField, propertyData.editor?.textField), // 这个属性,标记当属性变更得时候触发重新更新属性 refreshPanelAfterChanged: true, @@ -270,8 +285,12 @@ export class ComboListProperty extends InputBaseProperty { propertyData.editor.valueField = 'value'; propertyData.editor.textField = 'name'; propertyData.editor.remote = null; + // propertyData.editor.load = null; } else if (changeObject.propertyValue === 'dynamic') { propertyData.editor.remote = { method: 'GET' }; + propertyData.editor.valueField = 'value'; + propertyData.editor.textField = 'name'; + // propertyData.editor.load = propertyData.load; } break; } @@ -333,4 +352,78 @@ export class ComboListProperty extends InputBaseProperty { } } + + /** + * 组装输入类控件的交互面板:包含标签超链、绑定字段值变化前后事件等。 + * @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 = [ + { + label: 'onChange', + name: '值变化事件' + }, + // { + // label: 'onClear', + // name: '清空事件' + // }, + { + label: 'beforeOpen', + name: '下拉面板前事件' + } + ] as any; + if (customEventList) { + eventList = eventList.concat(customEventList); + } + // 追加值变化 + this.appendFieldValueChangeEvents(propertyData, eventList); + + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, eventList); + const properties = self.createBaseEventProperty(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 }); + } + if (propertyData.editor.beforeOpen !== propertyData.beforeOpen) { + propertyData.editor.beforeOpen = propertyData.beforeOpen; + } + if (propertyData.editor.onChange !== propertyData.onChange) { + propertyData.editor.onChange = propertyData.onChange; + } + if (propertyData.editor.onClear !== propertyData.onClear) { + propertyData.editor.onClear = propertyData.onClear; + } + } + }; + + return eventsEditorConfig; + } } diff --git a/packages/ui-vue/components/combo-list/src/schema/callback-resolvers.ts b/packages/ui-vue/components/combo-list/src/schema/callback-resolvers.ts new file mode 100644 index 0000000000000000000000000000000000000000..63dba81817067cf8beb5b9f8ad81cf783dfddadf --- /dev/null +++ b/packages/ui-vue/components/combo-list/src/schema/callback-resolvers.ts @@ -0,0 +1,14 @@ +import { Caller } from "@farris/ui-vue/components/dynamic-resolver"; + +export function createComboListCallbackResolver() { + function resolve(viewSchema: Record, caller: Caller, editorSchema?: Record) { + const callbacks: Record = {}; + callbacks.beforeOpen = (options: { instance: any; }): Promise => { + return caller.call('beforeOpen', viewSchema, [options, viewSchema], editorSchema); + }; + return callbacks; + } + return { + resolve + }; +} diff --git a/packages/ui-vue/components/combo-list/src/schema/combo-list.schema.json b/packages/ui-vue/components/combo-list/src/schema/combo-list.schema.json index 80ee0ae0f36c9cfeb77f98374d04c81d8069f29a..a18b08d2832a3a20590ae8b5f7e30ae5630565be 100644 --- a/packages/ui-vue/components/combo-list/src/schema/combo-list.schema.json +++ b/packages/ui-vue/components/combo-list/src/schema/combo-list.schema.json @@ -184,9 +184,24 @@ "type": "boolean", "default": false }, - "onChange": { - "description": "", + "beforeOpen": { + "description": "打卡面板前回调", "type": "string" + }, + "onChange": { + "description": "值变化事件", + "type": "string", + "default": "" + }, + "onInput": { + "description": "输入事件", + "type": "string", + "default": "" + }, + "onClear": { + "description": "清空事件", + "type": "string", + "default": "" } }, "required": [ @@ -197,5 +212,10 @@ "appearance", "binding", "visible" - ] + ], + "events": { + "onClear": "清空事件", + "onChange": "值变化事件", + "beforeOpen": "打开下拉面板前事件" + } } \ No newline at end of file diff --git a/packages/ui-vue/components/combo-tree/src/combo-tree.component.tsx b/packages/ui-vue/components/combo-tree/src/combo-tree.component.tsx index 901493f19d56f5a2098c63713c2d4fab051ec5c8..62ee599d26d97178cb553fa5a9dab8da74106f64 100644 --- a/packages/ui-vue/components/combo-tree/src/combo-tree.component.tsx +++ b/packages/ui-vue/components/combo-tree/src/combo-tree.component.tsx @@ -20,7 +20,7 @@ export default defineComponent({ const originalValue = ref(); const comboTreeRef = ref(); - const selectedNodes: any = {}; + let selectedNodes: any = {}; const { dataSource, displayText, editable, modelValue, getSelectedItemsByDisplayText, flatTreeNodes } = useDataSource(props); @@ -48,13 +48,14 @@ export default defineComponent({ } const getDisplayText = (selectedItems: any) => { - return !props.displayFormatter ? selectedItems.map((item: Option) => item[props.textField]).join(props.separator) : props.displayFormatter(selectedItems); + const items = selectedItems?.map(item => item.data || item); + return !props.displayFormatter ? items.map((item: Option) => item[props.textField]).join(props.separator) : props.displayFormatter(items); }; function onSelectionChange(selectedItems: any[] = []) { if(!isMultiSelect.value) { displayText.value = getDisplayText(selectedItems); - modelValue.value = selectedItems.map((item: Option) => item[props.valueField]).join(props.separator); + modelValue.value = selectedItems.map((item: Option) => item.data? item.data[props.valueField]: item[props.valueField]).join(props.separator); context.emit('update:modelValue', modelValue.value); context.emit('change', selectedItems, modelValue.value); } else { @@ -70,6 +71,7 @@ export default defineComponent({ function onClear($event: Event) { modelValue.value = ''; + selectedNodes = {}; if (showPopover.value) { comboEditorRef.value?.hidePopup(); } @@ -120,6 +122,13 @@ export default defineComponent({ } }; + function onClearSearch() { + resetDataSource(); + setTimeout(() => { + comboTreeRef.value?.checkItems(modelValue.value.split(props.separator)); + }); + } + return () => { return } ; }; diff --git a/packages/ui-vue/components/combo-tree/src/components/tree-container.component.tsx b/packages/ui-vue/components/combo-tree/src/components/tree-container.component.tsx index 347c9b3a91f1403ab0322ca8e24d7d430f2c7ade..6603763d42cef8f19b7515b3ce6ff8f6db92b8b9 100644 --- a/packages/ui-vue/components/combo-tree/src/components/tree-container.component.tsx +++ b/packages/ui-vue/components/combo-tree/src/components/tree-container.component.tsx @@ -96,7 +96,11 @@ export default defineComponent({ } }); - context.expose({ treeInstance: treeViewRef }); + const checkItems =(ids: string[]) => { + treeViewRef.value.selectItemByIds(ids); + }; + + context.expose({ treeInstance: treeViewRef, checkItems }); return () => { return ( diff --git a/packages/ui-vue/components/combo-tree/src/schema/combo-tree.schema.json b/packages/ui-vue/components/combo-tree/src/schema/combo-tree.schema.json index 54ebc56b7e619f04e37417ee41871348150b96c4..a1932b26b796e206c1c5658b5e6d5b298e822b1f 100644 --- a/packages/ui-vue/components/combo-tree/src/schema/combo-tree.schema.json +++ b/packages/ui-vue/components/combo-tree/src/schema/combo-tree.schema.json @@ -156,6 +156,11 @@ "displayFormatter": { "type": "object", "default": null + }, + "enableClear": { + "description": "", + "type": "boolean", + "default": true } }, "required": [ diff --git a/packages/ui-vue/components/discussion-editor/discussion-editor.props.ts b/packages/ui-vue/components/comment/index.ts similarity index 38% rename from packages/ui-vue/components/discussion-editor/discussion-editor.props.ts rename to packages/ui-vue/components/comment/index.ts index 3d6064c1c35b05ba6572bacd7d69c05baedf3dd8..e0b98db074283d9b3a6c27f378e7683553cf07cd 100644 --- a/packages/ui-vue/components/discussion-editor/discussion-editor.props.ts +++ b/packages/ui-vue/components/comment/index.ts @@ -13,21 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ExtractPropTypes } from 'vue'; +import type { App } from 'vue'; +import FComment from './src/comment.component'; +import { propsResolver } from './src/comment.props' +import FCommentDesign from './src/designer/comment.design.component'; +import { propsDesignResolver } from './src/designer/comment.design.props' +import { withInstall } from '@farris/ui-vue/components/common'; +export * from './src/types/interface'; +export * from './src/comment.props'; -export const discussionEditorProps = { - cancelVisible: { Type: Boolean, default: true }, - personnelsPrimaryKey: { Type: String, default: 'id' }, - personnelsDisplayKey: { Type: String, default: 'name' }, - replyPersonnelsDisplayKey: { Type: String, default: 'userName' }, - editHeight: { Type: Number, default: 130 }, - type: { Type: String, default: 'user' }, - orgUrl: { Type: String, default: '' }, - personSearchUrl: { Type: String, default: '' }, - sectionData: { Type: Object, default: [] }, - replyUser: { Type: Object, default: { id: {} } }, - personnels: { Type: Object, default: [] }, - attachFiles: { Type: Object, default: [] }, +FComment.register = ( + componentMap: Record, propsResolverMap: Record, + configResolverMap: Record, resolverMap: Record): void => { + componentMap['comment'] = FComment; + propsResolverMap['comment'] = propsResolver; }; +FComment.registerDesigner = (componentMap: Record, propsResolverMap: Record, + configResolverMap: Record): void => { + componentMap['comment'] = FCommentDesign; + propsResolverMap['comment'] = propsDesignResolver; +}; +export { FComment }; -export type DiscussionEditorProps = ExtractPropTypes; +export default withInstall(FComment); \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/comment.component.tsx b/packages/ui-vue/components/comment/src/comment.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ddbc39f6a2edba937e4b849943fd3a5c002e1727 --- /dev/null +++ b/packages/ui-vue/components/comment/src/comment.component.tsx @@ -0,0 +1,111 @@ + +/* eslint-disable no-use-before-define */ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { computed, defineComponent, onMounted, ref, SetupContext, watch } from 'vue'; +import { commentProps, CommentProps } from './comment.props'; +import { getCustomClass, getCustomStyle } from '@farris/ui-vue/components/common'; +import './comment.scss'; +import FCommentEditor from './components/comment-editor.component'; +import FCommentList from './components/comment-list.component'; + +export default defineComponent({ + name: 'FComment', + props: commentProps, + emits: ['valueChange', 'replyMessage', 'pageChanged'] as (string[] & ThisType) | undefined, + setup(props: CommentProps, context: SetupContext) { + const editorRef = ref(); + const listRef = ref(); + + const replyUser = ref(props.replyUser); + + // 回复人员信息变更 + watch(() => props.replyUser, (newValue) => { + replyUser.value=newValue; + }); + const discussionGroupClass = computed(() => { + const classObject = { + 'f-comment': true, + } as Record; + return getCustomClass(classObject, props.customClass); + }); + function valueChangeHandler(eventInfo) { + context.emit('valueChange', eventInfo); + } + function pageChangedHandler(eventInfo) { + context.emit('pageChanged', eventInfo); + } + /** + * 用变量更新,在取消和多次回复同一条评论时,不能触发属性变更 + * @param eventInfo + */ + function replayMessageHandler(userInfo) { + // replyUser.value=eventInfo; + editorRef?.value.updateReplyUser(userInfo); + } + const discussionStyle = computed(() => { + return getCustomStyle({}, props?.customStyle); + }); + /** + * 对外暴露更新常用@ 人员的方法 + * @param data + */ + function updatePersonnels(data) { + editorRef?.value.updatePersonnels(data); + } + /** + * 更新评论列表 + * @param data + */ + function updateComments(data) { + listRef?.value.updateComments(data); + } + context.expose({ updatePersonnels, updateComments }); + + return () => { + return
    + + +
    + }; + } +}); diff --git a/packages/ui-vue/components/comment/src/comment.props.ts b/packages/ui-vue/components/comment/src/comment.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..68b3a0cf674a5f9d6248090493630af8de91c763 --- /dev/null +++ b/packages/ui-vue/components/comment/src/comment.props.ts @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ExtractPropTypes, PropType } from 'vue'; +// import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; + +import commentSchema from './schema/comment.schema.json'; +import { schemaMapper } from './schema/schema-mapper'; +import { schemaResolver } from './schema/schema-resolver'; +import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { PermissionType } from './types/type'; + +export const commentEditorProps = { + /** 发表评论的取消按钮是否可见 */ + cancelVisible: { Type: Boolean, default: true }, + /** @人员时,区分人员的标识 */ + personnelsPrimaryKey: { Type: String, default: 'id' }, + /** @人员时,展示的人员信息字段 */ + personnelsDisplayKey: { Type: String, default: 'name' }, + /** 回复人员时,展示的回复人员信息字段 */ + replyPersonnelsDisplayKey: { Type: String, default: 'userName' }, + /** 编辑区高度 */ + editorHeight: { Type: Number, default: 130 }, + type: { Type: String, default: 'user' }, + /** 组织查询链接,应用在组织人员窗口 */ + orgUrl: { Type: String, default: '' }, + /** 人员查询链接,根据组织查询对应的人员 */ + personSearchUrl: { Type: String, default: '' }, + /** 查询所有部门数据 */ + sectionData: { Type: Object, default: [] }, + /** 回复人员数据信息 */ + replyUser: { Type: Object}, + /** 获取@用户|获取常用@用户 */ + personnels: { Type: Object, default: [] }, + /** 评论附加的组件 */ + attachFiles: { Type: Object, default: [] }, + /** 弹出窗口的样式 */ + popupClass: { Type: String, default: '' }, + /** 控制是否可见 */ + visible: { type: Boolean, default: true }, + /** 组件自定义样式 */ + customClass: { type: String, default: '' }, + customStyle: { type: String, default: '' }, + /** 回复评论是所有人可见还是只被回复人可见 */ + permissionType: { type: String as PropType, default: 'ALL' }, + /** 组件id */ + id: { Type: String, default: '' } +} as Record; + +export type CommentEditorProps = ExtractPropTypes; + +export const commentListProps = { + pagerOnServer: { Type: Boolean, default: true }, + /** 是否支持分页 */ + supportPaging: { Type: Boolean, default: true }, + /** 当前页码 */ + pageIndex: { Type: Number, default: 1 }, + /** 总条数 */ + total: { Type: Number, default: 0 }, + /** 每页显示个数 */ + pageSize: { Type: Number, default: 10 }, + /** 评论数据 */ + discussionData: { Type: Object, default: [] }, + replyPersonnelsDisplayKey: { Type: String, default: 'userName' }, + id: { Type: String, default: '' }, + /** 组件自定义样式 */ + customClass: { type: String, default: '' }, + customStyle: { type: String, default: '' }, + /** 控制是否可见 */ + visible: { type: Boolean, default: true } +} as Record; + +export type CommentListProps = ExtractPropTypes; + +export const commentProps = Object.assign({}, commentEditorProps,commentListProps,{ + customClass: { type: String, default: '' }, + /** 讨论区编辑器样式 */ + editorClass:{ type: String, default: '' }, + editorStyle:{ type: String, default: '' }, + editorVisible:{ type: Boolean, default: true }, + /** 讨论区列表样式 */ + listClass:{ type: String, default: '' }, + listStyle:{ type: String, default: '' }, + listVisible:{ type: Boolean, default: true }, + visible:{ type: Boolean, default: true } +}); + + +export type CommentProps = ExtractPropTypes; +export const propsResolver = createPropsResolver(commentProps, commentSchema, schemaMapper, schemaResolver); + diff --git a/packages/ui-vue/components/comment/src/comment.scss b/packages/ui-vue/components/comment/src/comment.scss new file mode 100644 index 0000000000000000000000000000000000000000..101de43d1bac513711e38567b0795f07bf2b2565 --- /dev/null +++ b/packages/ui-vue/components/comment/src/comment.scss @@ -0,0 +1,5 @@ +.fishbone-line { + background: #dae9ffc4; + height: 16px; + border-radius: 6px; +} diff --git a/packages/ui-vue/components/comment/src/components/colleague-panel.component.tsx b/packages/ui-vue/components/comment/src/components/colleague-panel.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..809335d0b37392ec1b8c4c5e79dbbaff02305743 --- /dev/null +++ b/packages/ui-vue/components/comment/src/components/colleague-panel.component.tsx @@ -0,0 +1,263 @@ +import { SetupContext, computed, defineComponent, ref, watch, onUnmounted, inject } from "vue"; +import FPopover from '@farris/ui-vue/components/popover'; +import FInputGroup from '@farris/ui-vue/components/input-group'; +import { useCommentEditor } from "../composition/use-comment-editor"; +import { CommentEditorProps, commentEditorProps } from "../comment.props"; +import { cloneDeep, debounce } from "lodash-es"; +import { useCommentLocale } from "../locale/locale"; + + +export default defineComponent({ + name: 'FColleaguePanel', + props: commentEditorProps, + emits: ['submit', 'cancel'] as (string[] & ThisType) | undefined, + setup(props: CommentEditorProps, context: SetupContext) { + const popoverRef = ref(); + const popoverInstance = computed(() => popoverRef); + const discussionLocale = useCommentLocale(); + const { judgeIsInArray, personnelsDisplayKey, stopBubble, getSearchData, getAvatar } = useCommentEditor(props, context); + /** 选择要发送的人员列表 */ + let selectedPersonnels = [] as any; + // 搜索框提示文本 + const placeholder = discussionLocale.searchPlaceholder; + // 搜索框提示图标 + const groupIcon = ''; + /** 保存查询人员返回的结果 */ + let searchPersonnelList: any = {}; + /** 搜索文本框中绑定的值 */ + const searchText: any = ref(''); + const searchInputText: any = ref(''); + /** 当@ 人员更新的时候,刷新 */ + const personnelsUpdate = ref(0); + /** 避免重复输入的token */ + let token: boolean; + /** 暂存人员信息 */ + const tempPersonnelsValue = ref(''); + /** 暂存文本框的输入,解决焦点问题 tempTextValue 移除*/ + /** 列表绑定数据 */ + let listBindingDatas: any = props.personnels.length ? cloneDeep(props.personnels) : []; + /** 保存常用@ 的人员 */ + let frequentlyTaggedContacts = props.personnels.length ? cloneDeep(props.personnels) : []; + /** 列表实例 */ + const listviewRef = ref(); + // 评论信息 + const textValue = ref(''); + // 解决搜索框文本总不更新的问题 + const searchContentUpdate = ref(0); + + const personnelsPrimaryKey = ref(props.personnelsPrimaryKey); + // 分页信息 + const defaultPagination = { + total: props.personnels.length, + enable: false, + mode: 'server', + index: 1, + size: 20, + showPageInfo: false, + showLimits: false + }; + const paginationInfo = ref(defaultPagination); + + /** + * 更新绑定人员列表的数据 + */ + function updateListBindingData(datas = null) { + if (searchText.value) { + listBindingDatas = searchPersonnelList.users; + } else { + if (datas) { + listBindingDatas = datas; + } else { + // 更新为旧数据 + listBindingDatas = frequentlyTaggedContacts; + } + } + personnelsUpdate.value++; + // listviewRef?.value.updateDataSource(listBindingDatas); + } + /** + * 常用@ 人员更新 + */ + watch(() => props.personnels, () => { + updateListBindingData(); + // personnelsUpdate.value++; + }); + const debouncedSearch = debounce(() => { + if (searchText.value) { + getSearchData(searchText.value, 0).then((d: any) => { + // 服务器端返回结果 + if ("users" in d) { + searchPersonnelList = d; + paginationInfo.value = Object.assign({}, defaultPagination, { enable: searchPersonnelList.pageCount > 1, total: d.pageCount }); + updateListBindingData(); + } + }); + } else { + updateListBindingData(); + } + }, 300) + + onUnmounted(() => { + debouncedSearch.cancel() + }) + /** + * 搜索人员 + */ + function searchPersonnel(text) { + searchText.value = text; + debouncedSearch(); + } + + /** + * 搜索下一页 + */ + function pageIndexChanged(pageInfo) { + // 应该通过分页信息获取 + getSearchData(searchText.value, pageInfo.pageIndex).then((d: any) => { + if ("users" in d) { + searchPersonnelList.pageIndex = d.pageIndex; + searchPersonnelList.users = d.users; + } + }); + } + /** 循环增加人员 */ + function appendPersonnel(listData: any, external = false) { + listData.forEach((item: any) => { + if (selectedPersonnels.length === 0 || !judgeIsInArray(item[personnelsPrimaryKey.value], personnelsPrimaryKey.value, selectedPersonnels)) { + tempPersonnelsValue.value += '@' + item[personnelsDisplayKey.value] + ' '; + selectedPersonnels.push(item); + } + }); + } + function closePersonnelsPanel() { + document.body.click(); + } + /** + * 关闭人事管理弹窗 + */ + function resetPersonnels() { + searchText.value = ''; + searchInputText.value = ''; + searchContentUpdate.value++; + // 清空已选项 + listviewRef.value.clearSelection(); + // 更新绑定数据 + updateListBindingData(); + // 关闭下拉面板 + closePersonnelsPanel(); + } + + /** 增加 @ 人员 */ + function appendPersonnels() { + // 获取列表选中信息 + const selectedList = listviewRef.value.getSelections(); + if (selectedList.length) { + appendPersonnel(selectedList); + } + resetPersonnels(); + context.emit('submit', { selectedPersonnels: selectedPersonnels, newAddedText: tempPersonnelsValue.value }); + tempPersonnelsValue.value = ''; + } + + /** 监听键盘事件, 主要是用于删除@人 */ + function keyUpChangeValueHandler(e: any) { + const { children } = e.target; + const childrenId: any = []; + for (let i = 0; i < children.length; i++) { + childrenId.push(children[i].id); + } + selectedPersonnels = selectedPersonnels.filter((personnel: any, index: any) => { + return childrenId.includes(personnel[personnelsPrimaryKey.value]); + }); + } + function updatePersonnels(datas) { + frequentlyTaggedContacts = datas ? datas : []; + updateListBindingData(datas) + } + /** + * 获取已选人员 + * @returns + */ + function getSelectedPersonnels() { + return selectedPersonnels; + } + /** + * 重置已选人员 + */ + function resetSelectedPersonnels() { + selectedPersonnels = []; + } + + context.expose({ keyUpChangeValueHandler, updatePersonnels, getSelectedPersonnels, resetSelectedPersonnels }); + // listview显示分页条--TODO + return () => { + return
    +
    +
    + @ +
    +
    + +
    { stopBubble(e); }}> + +
    + pageIndexChanged(info)} + > + {{ + content: (itemInfo) => { + return
    + {itemInfo.item.imgData && ( + + )} + {!itemInfo.item.imgData && ( +
    + )} +
    +
    {itemInfo.item[personnelsDisplayKey.value]}
    +
    {itemInfo.item.email}
    +
    +
    ; + } + }} +
    +
    +
    +
    + + +
    +
    +
    +
    +
    ; + }; + } +}); diff --git a/packages/ui-vue/components/comment/src/components/comment-editor.component.tsx b/packages/ui-vue/components/comment/src/components/comment-editor.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2fdcf1c229b7545991c7a9f620db4e3c4c712d86 --- /dev/null +++ b/packages/ui-vue/components/comment/src/components/comment-editor.component.tsx @@ -0,0 +1,253 @@ + +/* eslint-disable no-use-before-define */ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { computed, defineComponent, onMounted, provide, ref, SetupContext, watch } from 'vue'; +import { commentEditorProps, CommentEditorProps } from '../comment.props'; +import { MsgInfo, editAttachFile } from '../types/interface'; +import { getCustomClass, getCustomStyle } from '@farris/ui-vue/components/common'; +import { FNotifyService } from "@farris/ui-vue/components/notify"; +import { LocaleService } from '../../../locale'; +import FColleaguePanel from './colleague-panel.component'; +import { useCommentLocale } from '../locale/locale'; + +export default defineComponent({ + name: 'FCommentEditor', + props: commentEditorProps, + emits: ['selections', 'lineData', 'valueChange', 'filePreview', 'fileRemove', 'fileUploadDone', 'personnelSearch', 'getOutUsers'] as (string[] & ThisType) | undefined, + setup(props: CommentEditorProps, context: SetupContext) { + const notifyService: any = new FNotifyService(); + const personnelsPrimaryKey = ref(props.personnelsPrimaryKey); + const discussionLocale = useCommentLocale(); + const type = ref(props.type); + const orgUrl = ref(props.orgUrl); + const sectionData = ref(props.sectionData); + const colleaguePanelRef = ref(); + let options: any; + + const discussionEditorClass = computed(() => { + const classObject = { + 'f-discussion-editor': true, + } as Record; + return getCustomClass(classObject, props.customClass); + }); + + const discussionEditorStyle = computed(() => { + return getCustomStyle({}, props?.customStyle); + }); + + /** 人事弹窗列表数据 */ + const replyUser = ref(props.replyUser); + + const _attachFiles = ref(props.attachFiles); + + const attachFiles = computed({ + get() { + return _attachFiles.value; + }, + set(val: any) { + if (val) { + _attachFiles.value = val; + } + } + }); + + /** 审批意见 */ + const textValue = ref(''); + + /** 暂存部门 */ + const tempSectionValue = ref(''); + + /** 选择要发送的部门 */ + let selectedSection: any = []; + + /** 上传附件是否显示 */ + const attachFilesModalVisible = ref(false); + + + const permissionList = [ + { value: 'ALL', text: LocaleService.getLocaleValue('comment.all') }, + { value: 'RELATED', text: LocaleService.getLocaleValue('comment.related') } + ]; + const permissionType = ref(props.permissionType); + + onMounted(() => { + options = { maxUploads: 3, maxFileSize: 10240, allowedContentTypes: ['.jpg', '.pdf'] }; + } + ); + /** + * 检查权限 + * @returns + */ + function getPermissionInfo() { + const foundPermission = permissionList.find(item => item.value === permissionType.value); + return foundPermission ? foundPermission['value'] : permissionList[0]['value']; + } + /** + * 文本框失去焦点触发 - 暂时未用到 + * + */ + function setTextValue(e: any) { + if (e) { + textValue.value = e.target.innerHTML; + } + if (tempSectionValue.value) { + textValue.value += tempSectionValue.value; + } + tempSectionValue.value = ''; + } + /** 监听键盘事件, 主要是用于删除@人 */ + function listenEditorValueChange(e: any) { + colleaguePanelRef.value?.keyUpChangeValueHandler(e); + // selectedSection.forEach((section: any, index: any) => { + // if (!childrenId.includes(section[personnelsPrimaryKey.value])) { + // selectedSection.splice(index, 1); + // } + // }); + } + /** + * 提交评语 + */ + function submitApproval() { + if (!textValue.value) { + notifyService.error({ message: discussionLocale.submitEmpty, position: 'top-center' }); + return; + } + const editAttachFiles: editAttachFile[] = []; + if (attachFiles.value && attachFiles.value.length) { + attachFiles.value.forEach((file: any) => { + const { id } = file; + const { name } = file; + const { size } = file; + const { metadataId } = file.extend; + const attachFile = { + id, + name, + size, + metadataId + }; + editAttachFiles.push(attachFile); + }); + } + context.emit('valueChange', { + editorId: props.id, + msgInfo: MsgInfo.Confirm, + text: textValue.value, + mailTos: colleaguePanelRef.value?.getSelectedPersonnels() || [], + mailToSections: selectedSection, + visibility: getPermissionInfo(), + parentId: (replyUser.value && 'id' in replyUser.value) ? replyUser.value.id : null, + attachFiles: editAttachFiles.length ? editAttachFiles : null + }); + textValue.value = ''; + colleaguePanelRef.value?.resetSelectedPersonnels(); + selectedSection = []; + attachFiles.value = []; + replyUser.value = {}; + } + + function cancelApproval() { + context.emit('value', { + msgInfo: MsgInfo.Cancel, + text: null, + mailTos: [], + mailToSections: [], + visibility: null, + parentId: null, + attachFiles: null + }); + textValue.value = ''; + colleaguePanelRef.value?.resetSelectedPersonnels(); + selectedSection = []; + attachFiles.value = []; + replyUser.value = {}; + } + + const editor = ref(null); + /** 获得焦点 */ + function editorFocus() { + editor.value?.focus(); + } + function replyUserUpdated(value) { + replyUser.value = value ? value : {}; + if (value && value.id) { + editorFocus(); + } + } + // 回复人员信息变更 + watch(() => props.replyUser, (newValue) => { + replyUserUpdated(newValue); + }); + /** + * 新@ 的人每次是追加到最后面 + * @param slectedInfo + */ + function colleaguePanelSubmit(slectedInfo) { + const { newAddedText } = slectedInfo; + textValue.value = editor.value?.innerHTML + newAddedText; + } + /** + * 对外暴露更新常用@ 人员的方法 + * @param data + */ + function updatePersonnels(data) { + colleaguePanelRef?.value.updatePersonnels(data); + } + function updateReplyUser(data) { + replyUserUpdated(data); + } + context.expose({ updatePersonnels, updateReplyUser }); + + return () => { + return props.visible ?
    + {replyUser.value && replyUser.value.id && ( +
    + {discussionLocale.reply} + + {replyUser.value[props.replyPersonnelsDisplayKey]} + + : +
    + )} +
    +
    +
    listenEditorValueChange(e)} + onBlur={(e) => setTextValue(e)} + ref={editor} + contenteditable={true} + innerHTML={textValue.value} + >
    +
    + +
    +
    : null; + }; + } +}); diff --git a/packages/ui-vue/components/discussion-list/discussion-list.component.tsx b/packages/ui-vue/components/comment/src/components/comment-list.component.tsx similarity index 31% rename from packages/ui-vue/components/discussion-list/discussion-list.component.tsx rename to packages/ui-vue/components/comment/src/components/comment-list.component.tsx index 8593093bd4a6bea32de003407818de0e763b2ced..9db0c77bb0f21743f6d72edd13663f62104ea461 100644 --- a/packages/ui-vue/components/discussion-list/discussion-list.component.tsx +++ b/packages/ui-vue/components/comment/src/components/comment-list.component.tsx @@ -1,4 +1,3 @@ - /* eslint-disable prefer-const */ /* eslint-disable no-use-before-define */ /** @@ -16,28 +15,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { computed, defineComponent, onMounted, ref, SetupContext } from 'vue'; -import { discussionListProps, DiscussionListProps } from './discussion-list.props'; - -import './discussion-list.scss'; +import { computed, defineComponent, inject, onMounted, ref, SetupContext } from 'vue'; +import { commentListProps, CommentListProps } from '../comment.props'; +import { getCustomClass, getCustomStyle, useDateFormat } from '@farris/ui-vue/components/common'; +import FPagination from '@farris/ui-vue/components/pagination'; +import { CommentListService, F_COMMENT_LIST_SERVICE_TOKEN } from '../types/interface'; +import { UseCommentList } from '../composition/use-comment-list'; +import { useCommentLocale } from '../locale/locale'; export default defineComponent({ name: 'FDiscussionList', - props: discussionListProps, - emits: ['replyMessage', 'page', 'pageSize', 'filePreview', 'fileDownload'] as (string[] & ThisType) | undefined, - setup(props: DiscussionListProps, context: SetupContext) { - const personnelsDisplayKey = ref(props.personnelsDisplayKey); + props: commentListProps, + emits: ['replyMessage', 'pageChanged', 'pageSize', 'filePreview', 'fileDownload'] as (string[] & ThisType) | undefined, + setup(props: CommentListProps, context: SetupContext) { + /** 获取服务器端数据方法 */ + const { getData }: any = inject(F_COMMENT_LIST_SERVICE_TOKEN, {}); + const { formatServerData, getAvatar } = UseCommentList(props); + const personnelsDisplayKey = ref(props.replyPersonnelsDisplayKey); + const discussionLocale = useCommentLocale(); const pagerOnServer = ref(props.pagerOnServer); - /** 是否支持分页 */ - const supportPaging = ref(props.supportPaging); /** 当前页码 */ - const _pageIndex = ref(props.pageIndex); + const pageIndex = ref(props.pageIndex); /** 总条数 */ - const _total = ref(props.total); + const total = ref(props.total); /** 每页显示个数 */ - const _pageSize = ref(props.pageSize); - /** 评论数据 */ - const _discussionData = ref(props.discussionData); + const pageSize = ref(props.pageSize); + + /** 评论数据 更新时触发更新 */ + const commentsUpdate = ref(0); + const discussionListClass = computed(() => { + const classObject = { + 'f-discussion-group-content': true, + } as Record; + return getCustomClass(classObject, props.customClass); + }); + + const discussionListStyle = computed(() => { + return getCustomStyle({}, props?.customStyle); + }); // const labels: any = { // previousLabel: ' ', // nextLabel: ' ' @@ -47,74 +62,35 @@ export default defineComponent({ // const responsive = true; // const autoHide = false; // const pager = ref(null); - let paginationOptions: any; - let innerDiscussionData: any = []; - - const total = computed({ - get() { - return _total.value; - }, - set(val) { - _total.value = val; - initPaginationOptions(); + // let paginationOptions: any; + /** 评论数据 */ + let innerDiscussionData: any = props.discussionData; + /** 格式化日期 */ + const { formatTo } = useDateFormat(); + /** 从服务器端获取数据 */ + function getCommentsFromServer() { + if (getData) { + getData().then((comments) => { + innerDiscussionData = formatServerData(comments); + commentsUpdate.value++; + }); } - }); + } + /** 分页列表 */ + const pageList = [20, 50, 100]; + getCommentsFromServer(); - const pageSize = computed({ - get() { - return _pageSize.value; - }, - set(val) { - _pageSize.value = val; - initPaginationOptions(); - } - }); onMounted(() => { - initPaginationOptions(); + }); - function initPaginationOptions() { - paginationOptions = { - id: 'Farris-discussion-Pagination', - itemsPerPage: _pageSize.value, - currentPage: _pageIndex.value, - pageList: [10, 20, 30, 50, 100], - totalItems: _total, - remote: pagerOnServer - }; - } /** 点击回复留言 */ function reply(item: any) { context.emit('replyMessage', item); } - /** 页码变化 */ - // function onPageChange(page: { pageIndex: number; pageSize: number }) { - // if (_pageIndex.value !== page.pageIndex) { - // _pageIndex.value = page.pageIndex; - // paginationOptions.currentPage = page.pageIndex; - // context.emit('page', { pageInfo: page }); - // } - // } - /** 每页显示条数变化 */ - // function onPageSizeChange(pageSize1: number) { - // if (pageSize.value !== pageSize1 && total) { - // paginationOptions.itemsPerPage = pageSize; - // pageSize.value = pageSize1; - - // const _total = total; - // let pageLength = Math.floor(_total.value / pageSize1); - // if (_total.value % pageSize1 > 0) { - // pageLength += 1; - // } - - // if (pageLength && _pageIndex.value > pageLength) { - // _pageIndex.value = pageLength; - // paginationOptions.currentPage = _pageIndex.value; - // } - - // context.emit('pageSize', { pageInfo: { _pageIndex, pageSize } }); - // } - // } + function formatCommentDate(dateString) { + return dateString ? formatTo(dateString, 'yyyy-MM-dd HH:mm') : ''; + } /** 附件预览 */ // function filePreviewEventHandler(info: any) { // context.emit('filePreview', info); @@ -123,14 +99,6 @@ export default defineComponent({ // function fileDownloadEventHandler(info: any) { // context.emit('fileDownload', info); // } - /** 占位头像文字 */ - // function getAvatar(item: any) { - // if (item && item[personnelsDisplayKey.value]) { - // let str = item[personnelsDisplayKey.value]; - // return str.substring(str.length - 2, str.length); - // } - // return ''; - // } /** 分页函数 */ // function paginateArray(array: any, options: any) { // const { page, pageSize } = options; @@ -139,93 +107,107 @@ export default defineComponent({ // return array.slice(startIndex, endIndex); // } + function onChange(pageInfo: { pageIndex: number; pageSize: number }) { + const { pageIndex, pageSize } = pageInfo; + context.emit('pageChanged', { pageIndex, pageSize }); + } + // 从外部更新数据 + function updateComments(datasInfos: { commentsIds: Array, comments: Array, pageSize?: number, pageIndex?: number, totalCount?: number }) { + innerDiscussionData = formatServerData(datasInfos); + if (props.supportPaging) { + pageSize.value = datasInfos.pageSize; + pageIndex.value = datasInfos.pageIndex; + total.value = datasInfos.totalCount; + if (pageList.indexOf(pageSize.value) < 0) { + pageList.unshift(pageSize.value); + } + } + commentsUpdate.value++; + } + + context.expose({ updateComments }); + return () => { return ( -
    -
    - {// *ngFor="let item of (supportPaging ? (innerDiscussionData | paginate: paginationOptions) : innerDiscussionData)" - innerDiscussionData.map((item: any) => { - return ( -
    -
    -
    - {item.imgData && ( -
    - -
    - )} -
    -
    - {!item.imgData && ( -
    { getAvatar(item) }} - >
    - )} -
    + props.visible ?
    + { + innerDiscussionData.map((item: any) => { + return ( +
    +
    +
    + {item.imgData && ( +
    + +
    + )}
    -
    -
    - {item[personnelsDisplayKey.value]} -
    -
    - -
    - {item.parentData && ( -
    - - {'discussionGroup.reply'} - {item.parentData[personnelsDisplayKey.value]} - - - -
    +
    + {!item.imgData && ( +
    )} - +
    +
    +
    + {item[personnelsDisplayKey.value]} +
    +
    + +
    + {item.parentData && ( +
    + + {discussionLocale.reply} + {item.parentData[personnelsDisplayKey.value]} + + +
    + )} +
    - ); - }) - } - - {supportPaging.value && ( - - )} - -
    -
    +
    + ); + }) + } + {props.supportPaging && innerDiscussionData.length > 0 && ( + + )} +
    : null ); }; } diff --git a/packages/ui-vue/components/comment/src/components/personnel-management.component.tsx b/packages/ui-vue/components/comment/src/components/personnel-management.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..16b62fa3203436bca3bb828e627a32b187d819e3 --- /dev/null +++ b/packages/ui-vue/components/comment/src/components/personnel-management.component.tsx @@ -0,0 +1,345 @@ +import { SetupContext, computed, defineComponent, ref, watch } from "vue"; +import FPopover from '@farris/ui-vue/components/popover'; +import FInputGroup from '@farris/ui-vue/components/input-group'; +import { useCommentEditor } from "../composition/use-comment-editor"; +import { UsePopup } from "../types/composition"; +import { CommentEditorProps,commentEditorProps } from "../comment.props"; +import { cloneDeep } from "lodash-es"; + +/** + * 人员管理弹出窗口 ToDo + */ +export default defineComponent({ + name: 'FPersonnelManagement', + props: commentEditorProps, + emits: ['submit', 'cancel'] as (string[] & ThisType) | undefined, + setup(props: CommentEditorProps, context: SetupContext) { + const popoverRef = ref(); + const popoverInstance = computed(() => popoverRef); + + const { judgeIsInArray, personnelsDisplayKey, stopBubble, getSearchData, getAvatar, ...otherEditorInfo} = useCommentEditor(props, context); + // 搜索框提示文本 + const placeholder = '请输入姓名搜索'; + // 搜索框提示图标 + const groupIcon = ''; + + const showSearchList = ref(false); + const searchPersonnelList: any = {}; + /** 搜索文本框中绑定的值 */ + const personnelText: any = ref(''); + /** 暂存人员信息,用于搜索 */ + let copyPersonnels: any = []; + /** 保存@ 人员的信息 */ + let innerPersonnels: any = props.personnels; + /** 当@ 人员更新的时候,刷新 */ + const personnelsUpdate = ref(0); + + // let el: ElementRef; + /** 避免重复输入的token */ + let token: boolean; + + /** 暂存人员信息 */ + const tempPersonnelsValue = ref(''); + /** 暂存部门 */ + const tempSectionValue = ref(''); + + /** 选择要发送的部门 */ + const selectedSection: any = []; + + /** 暂存文本框的输入,解决焦点问题 */ + let tempTextValue: string; + + // 评论信息 + const textValue = ref(''); + + const popoverVisible = ref(false); + + const personnelsPrimaryKey = ref(props.personnelsPrimaryKey); + + function updatePersonnels() { + copyPersonnels = props.personnels.length ? cloneDeep(props.personnels) : []; + innerPersonnels = props.personnels; + } + watch(() => props.personnels, () => { + updatePersonnels(); + personnelsUpdate.value++; + }); + updatePersonnels(); + /** + * 搜索人员 + */ + function searchPersonnel() { + + if (personnelText.value) { + showSearchList.value = true; + getSearchData(personnelText.value, 0).then((d: any) => { + if ("users" in d) { + searchPersonnelList.value = d; + // setPersonModalPosition(); + } + }); + } + else { + showSearchList.value = false; + } + + } + /** + * 搜索下一页 + */ + function getMoreSearchData() { + getSearchData(personnelText.value, searchPersonnelList.pageIndex + 1).then((d: any) => { + if ("users" in d) { + searchPersonnelList.pageIndex = d.pageIndex; + searchPersonnelList.users = [...searchPersonnelList.users, ...d.users]; + } + }); + } + /** 循环增加人员 */ + function appendPersonnel(listData: any, external = false) { + listData.forEach((item: any) => { + // if (!(otherEditorInfo.selectedPersonnels.length && judgeIsInArray(item[personnelsPrimaryKey.value], personnelsPrimaryKey.value, otherEditorInfo.selectedPersonnels))) { + // tempPersonnelsValue.value += '@' + item[personnelsDisplayKey.value] + ' '; + // otherEditorInfo.selectedPersonnels.push(item); + // } + }); + } + function closePersonnelsPanel() { + document.body.click(); + } + /** + * 关闭人事管理弹窗 + */ + function resetPersonnels() { + showSearchList.value = false; + innerPersonnels = copyPersonnels; + if (innerPersonnels.length) { + innerPersonnels.forEach((item: any) => { item.active = false; }); + } + personnelText.value = ''; + closePersonnelsPanel(); + } /** 文本框失去焦点触发 */ + function setTextValue(e: any) { + if (e) { + tempTextValue = e.target.innerHTML; + textValue.value = tempTextValue; + } + if (tempPersonnelsValue.value) { + textValue.value += tempPersonnelsValue.value; + } + if (tempSectionValue.value) { + textValue.value += tempSectionValue.value; + } + tempTextValue = ''; + tempPersonnelsValue.value = ''; + tempSectionValue.value = ''; + } + + /** 增加 @ 人员 */ + function appendPersonnels() { + let selectedList = []; + if (!showSearchList.value) { + selectedList = innerPersonnels.filter((item: any) => { + return item.active === true; + }); + } + else { + selectedList = searchPersonnelList.users.filter((item: any) => item.active === true); + } + if (selectedList.length) { + appendPersonnel(selectedList); + } + resetPersonnels(); + setTextValue(null); + closePersonnelsPanel(); + context.emit('submit', textValue.value); + } + function activeStateChanged(item: any) { + item.active = !item.active; + return item; + }; + /** 高级搜索人员添加 */ + function appendPersonnelsList(listData: any) { + if (listData.length) { + appendPersonnel(listData, true); + } + setTextValue(null); + } + + function appendSection(listData: any) { + listData.forEach((item: any) => { + if (!(selectedSection.length && judgeIsInArray(item[personnelsPrimaryKey.value], personnelsPrimaryKey.value, selectedSection))) { + tempSectionValue.value += '@' + item.name + ' '; + selectedSection.push(item); + } + }); + } + /** + * 添加部门 + * @param listData + */ + function appendSectionList(listData: any) { + if (listData.length) { + appendSection(listData); + } + setTextValue(null); + } + /** + * 高级人员点确认 + */ + function selectionsChangePar(event: any) { + if (event.data.users.length) { + const userList: any = []; + event.data.users.forEach((user: any) => { + userList.push(user.data); + }); + appendPersonnelsList(userList); + } + if (event.data.section.length) { + const sectionList: any = []; + event.data.section.forEach((sec: any) => { + sectionList.push(sec.data); + }); + appendSectionList(sectionList); + } + context.emit('selections', event); + } + /** 高级人员中选中某行 */ + function lineDataChangePar(event: any) { + context.emit('lineData', event); + } + + function outUsers(event: any) { + context.emit('getOutUsers', event); + } + + /** 监听键盘事件, 主要是用于删除@人 */ + function keyUpChangeValueHandler(e: any) { + tempTextValue = e.target.innerHTML; + const { children } = e.target; + const childrenId: any = []; + for (let i = 0; i < children.length; i++) { + childrenId.push(children[i].id); + } + // otherEditorInfo.selectedPersonnels.forEach((personnel: any, index: any) => { + // if (!childrenId.includes(personnel[personnelsPrimaryKey.value])) { + // otherEditorInfo.selectedPersonnels.splice(index, 1); + // } + // }); + selectedSection.forEach((section: any, index: any) => { + if (!childrenId.includes(section[personnelsPrimaryKey.value])) { + selectedSection.splice(index, 1); + } + }); + if (!tempTextValue) { + tempTextValue = ''; + } + } + + context.expose({keyUpChangeValueHandler}); + + return () => { + return
    +
    + @ + + 同事 + +
    + +
    { stopBubble(e); }}> + +
    + + +
    +
    +
    + + +
    +
    +
    +
    +
    ; + }; + } +}); diff --git a/packages/ui-vue/components/comment/src/composition/build-comment.ts b/packages/ui-vue/components/comment/src/composition/build-comment.ts new file mode 100644 index 0000000000000000000000000000000000000000..5072149d5c2657be6ff82d872d410bc445ebe66a --- /dev/null +++ b/packages/ui-vue/components/comment/src/composition/build-comment.ts @@ -0,0 +1,178 @@ +import { getSchemaByType } from "@farris/ui-vue/components/dynamic-resolver"; +import { DesignerHostService, DgControl } from "@farris/ui-vue/components/designer-canvas"; +import { useGuid } from "@farris/ui-vue/components/common"; + +/** + * 创建筛选条 + */ +export function buildComment(context: Record, designerHostService?: DesignerHostService) { + const formSchemaUtils = designerHostService?.formSchemaUtils; + const targetComponentInstance = context.parentComponentInstance; + /** 评论区命令控制器(ListController)id */ + const commentControllerWebCmdId = '7690ac62-f2fd-43aa-96a2-973f0d450f82'; + + /** + * 组装筛选条容器和筛选条元数据,并插入组件schema + */ + function createCommentContainer(): { comment: any } | any { + const commentMetadata = getSchemaByType(DgControl['comment'].type); + if (!commentMetadata) { + return; + } + const suffixId = Math.random().toString(36).substr(2, 4); + // 写预置的属性 + Object.assign(commentMetadata, { + id: 'comment-' + suffixId, + personSearchUrl:'/api/runtime/comment/v1.0/bill-comment/atUser' + }); + + return { comment: commentMetadata }; + } + + function createSingleCommentCommand(viewModelInfo, commandInfo) { + let newDiscussionCommandId; + // 1、在组件中预置命令 + if (viewModelInfo && viewModelInfo.commands) { + let commentCommand = viewModelInfo.commands.find(command => command.handlerName === commandInfo['handlerName'] && command.cmpId === commentControllerWebCmdId); + if (commentCommand) { + if (commentCommand.params && commentCommand.params.length) { + // 重置参数 + commentCommand.params.forEach((item) => item.value = ''); + } + } else { + newDiscussionCommandId = useGuid().guid(); + commentCommand = { + id: newDiscussionCommandId, + code: `${viewModelInfo.id.replace(/-/g, '').replace('component', '').replace('viewmodel', '')}${commandInfo['handlerName']}1`, + name: commandInfo['name'], + params: commandInfo['params'], + handlerName: commandInfo['handlerName'], + cmpId: commentControllerWebCmdId + }; + viewModelInfo.commands.push(commentCommand); + } + return { commandId: newDiscussionCommandId, commandCode: commentCommand.code }; + } + return { commandId: '', commandCode: '' }; + } + /** + * 为命令添加构件引用 + */ + function addWebCmdForcommentCommand(newCommandId, commandHandlerName) { + const webCmds = formSchemaUtils.getCommands(); + if (webCmds) { + let commentCmd = webCmds.find(cmd => cmd.id === commentControllerWebCmdId); + if (!commentCmd) { + commentCmd = { + id: commentControllerWebCmdId, + path: 'Gsp/Web/WebCmp/bo-webcmp/metadata/webcmd-vue', + name: 'DiscussionController.webcmd', + refedHandlers: [] + }; + formSchemaUtils.getCommands().push(commentCmd); + } + if (newCommandId) { + commentCmd.refedHandlers.push( + { + host: newCommandId, + handler: commandHandlerName + } + ); + } + } + } + /** + * 创建表格筛选查询相关命令 + * @param commentMetadata 讨论区元数据 + */ + function createCommentCommand(commentMetadata) { + const targetComponentId = targetComponentInstance.belongedComponentId; + const viewModelId = formSchemaUtils.getViewModelIdByComponentId(targetComponentId); + const viewModel = formSchemaUtils.getViewModelById(viewModelId); + /** + * { + handlerName: 'queryAllOrgs', + name: "查询所有部门信息", + params: [], + emitEvent: 'onQueryAllOrgs' + }, + */ + const waitForAddCommands = [ + { + handlerName: 'createComment', + name: "提交评论", + params: [ + { + name: 'id', + shownName: '数据ID', + value: '', + defaultValue: null + }, + { + name: 'summary', + shownName: '备注', + value: '', + defaultValue: null + }, + { + name: 'configID', + shownName: '配置ID', + value: '', + defaultValue: null + } + ], + emitEvent: 'onValueChange' + }, { + handlerName: 'queryFrequentMentionUsers', + name: "加载常用@人员", + params: [], + emitEvent: 'onQueryFrequentAtUsers' + }, { + handlerName: 'queryComments', + name: "查询评论", + params: [ + { + name: 'id', + shownName: '数据ID', + value: '', + defaultValue: null + }, + { + name: 'configID', + shownName: '配置ID', + value: '', + defaultValue: null + } + ], + emitEvent: 'onQueryComments' + }]; + waitForAddCommands.forEach(commandInfo => { + const { commandId, commandCode } = createSingleCommentCommand(viewModel, commandInfo); + // 将命令绑定到组件上 + // @用户取数事件onQueryUserCommand 提交评论事件onValueChange 查询部门事件 onQueryAllOrgs 查询常用@用户事件 queryFrequentAtUsers + if (commandCode) { + commentMetadata[commandInfo['emitEvent']] = commandCode; + } + // 为命令添加构件引用 + if (commandId) { + addWebCmdForcommentCommand(commandId, commandInfo['handlerName']); + } + }); + } + function createComment() { + // 1、组装筛选条容器和筛选条 + const { comment } = createCommentContainer(); + + // 2、创建筛选条相关命令 + createCommentCommand(comment); + + // 3、因为涉及到新增【DiscussionGroupController控制器】,所以这里需要获取控制器信息,方便交互面板展示命令数据 + const formCommandService = designerHostService?.useFormCommand; + if (formCommandService) { + formCommandService.checkCommands(); + } + return comment; + } + + return { createComment }; +} diff --git a/packages/ui-vue/components/discussion-editor/src/composition/use-discussion-editor.ts b/packages/ui-vue/components/comment/src/composition/use-comment-editor.ts similarity index 56% rename from packages/ui-vue/components/discussion-editor/src/composition/use-discussion-editor.ts rename to packages/ui-vue/components/comment/src/composition/use-comment-editor.ts index 3f3a43d76d753515a1f025e12ad927eae73edf77..d2488fe6937df1f93473daa855d0a7edcd813593 100644 --- a/packages/ui-vue/components/discussion-editor/src/composition/use-discussion-editor.ts +++ b/packages/ui-vue/components/comment/src/composition/use-comment-editor.ts @@ -1,12 +1,10 @@ import { ref, SetupContext } from 'vue'; -import { DiscussionEditorProps } from '../../discussion-editor.props'; +import { CommentEditorProps } from '../comment.props'; -export function useDiscussionEditor(props: DiscussionEditorProps, context: SetupContext) { - const personSearchUrl = ref(props.personSearchUrl); +export function useCommentEditor(props: CommentEditorProps, context: SetupContext) { + const personSearchUrl = ref(); const personnelsDisplayKey = ref(props.personnelsDisplayKey); const relativeVisible = ref(false); - /** 选择要发送的人员列表 */ - const selectedPersonnels: any = []; /** 搜索人事管理弹窗 */ const personModalVisible = ref(false); /** 阻止冒泡 */ @@ -16,20 +14,31 @@ export function useDiscussionEditor(props: DiscussionEditorProps, context: Setup } } /** 判断是否在数组 */ - function _isInArray(value: any, fieldInArray: string, array: any[]) { + function judgeIsInArray(value: any, fieldInArray: string, array: any[]) { if (!value || !fieldInArray) { return false; } return array.findIndex(item => value === item[fieldInArray]) !== -1; } - function getSearchData(text: string, pageIndex: number):Promise { - if (personSearchUrl.value) { - const url = `${personSearchUrl.value}?param=${text}&pageSize=20&pageIndex=${pageIndex}`; - // return http.get(url); + /** + * 根据查询条件,分页信息 + * @param text + * @param pageIndex + * @returns + */ + async function getSearchData(text: string, pageIndex: number): Promise { + if (props.personSearchUrl) { + const url = `${props.personSearchUrl}?param=${text}&pageSize=20&pageIndex=${pageIndex}`; + return fetch(url).then(response => { + if (!response.ok) { + throw new Error('获取数据异常'); + } + return response.json(); + }); } return new Promise((resolve, reject) => { - resolve(true); - }); + resolve([]); + }); } /** 获得占位头像 */ function getAvatar(item: any) { @@ -40,8 +49,8 @@ export function useDiscussionEditor(props: DiscussionEditorProps, context: Setup return ''; } function setRelativeValue() { - personModalVisible.value = false; - relativeVisible.value = false; + // personModalVisible.value = false; + // relativeVisible.value = false; } return { @@ -50,11 +59,10 @@ export function useDiscussionEditor(props: DiscussionEditorProps, context: Setup /** 搜索人事管理弹窗 */ personModalVisible, relativeVisible, - selectedPersonnels, stopBubble, - _isInArray, + judgeIsInArray, getSearchData, getAvatar, - setRelativeValue, + setRelativeValue }; } diff --git a/packages/ui-vue/components/comment/src/composition/use-comment-list.ts b/packages/ui-vue/components/comment/src/composition/use-comment-list.ts new file mode 100644 index 0000000000000000000000000000000000000000..7def6d709c758b3821f44a976bccc8cc8ea08f50 --- /dev/null +++ b/packages/ui-vue/components/comment/src/composition/use-comment-list.ts @@ -0,0 +1,77 @@ +import { CommentListProps } from "../comment.props"; +import { discussionConfig } from "../types/interface"; + +export function UseCommentList(props: CommentListProps) { + /** + * 从服务器端获取的数据,重构成符合讨论列表接收的格式 + * @param dataFromServer + */ + function formatServerData(dataFromServer) { + const results = [] as any; + if (dataFromServer && dataFromServer.commentIds && dataFromServer.commentIds.length) { + dataFromServer.commentIds.forEach(commentId => { + const item = dataFromServer['comments'].find((comment) => { + return comment["id"] === commentId; + }) + if (item) { + const { id, userId, commentDate, text } = item; + const userName = item['user']["name"]; + // imgData = item['user']["imgData"], + const imgData = Object.prototype.hasOwnProperty.call(item['user'], 'imgData') ? item['user']["imgData"] : ''; + // commentDate = this.dataFormate(item["commentDate"]), + const attachFiles = Object.prototype.hasOwnProperty.call(item, 'attachFiles') ? item["attachFiles"] : []; + let parentData; + if (Object.prototype.hasOwnProperty.call(item, 'parentId')) { + const parentItem = dataFromServer['comments'].find(i => { + return i.id === item.parentId; + }); + if (parentItem) { + const parid = parentItem["id"]; + const paruserId = parentItem["userId"]; + const paruserName = parentItem['user']["name"]; + // parimgData = parentItem['user']["imgData"], + const parimgData = Object.prototype.hasOwnProperty.call(parentItem['user'], 'imgData') ? parentItem['user']["imgData"] : ''; + const parcommentDate = parentItem["commentDate"]; + const partext = parentItem["text"]; + const parattachFiles = Object.prototype.hasOwnProperty.call(parentItem['user'], 'attachFiles') ? parentItem["attachFiles"] : []; + parentData = { + id: parid, + userId: paruserId, + userName: paruserName, + imgData: parimgData, + commentDate: parcommentDate, + text: partext, + attachFiles: parattachFiles + }; + } + } + const thisData: discussionConfig = { + id, + userId, + userName, + imgData, + commentDate, + text, + attachFiles, + parentData + }; + results.push(thisData); + } + else { + return; + } + }); + } + return results; + } + + /** 获得占位头像 */ + function getAvatar(item: any) { + if (item && item[props.replyPersonnelsDisplayKey]) { + const str = item[props.replyPersonnelsDisplayKey]; + return str.substring(str.length - 2, str.length); + } + return ''; + } + return { formatServerData, getAvatar }; +} diff --git a/packages/ui-vue/components/comment/src/designer/comment.design.component.tsx b/packages/ui-vue/components/comment/src/designer/comment.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b6d523cb3a87393679a7289ed37220441ad8fdcf --- /dev/null +++ b/packages/ui-vue/components/comment/src/designer/comment.design.component.tsx @@ -0,0 +1,179 @@ +import { SetupContext, computed, defineComponent, inject, onMounted, provide, ref, watch } from 'vue'; +import { DesignerItemContext } from '@farris/ui-vue/components/designer-canvas'; +import { useDesignerComponent } from '@farris/ui-vue/components/designer-canvas'; +import { CommentDesignProps, commentDesignProps } from './comment.design.props'; +import { useCommentRules } from './use-comment-rules'; +import { getCustomClass, getCustomStyle } from '@farris/ui-vue/components/common'; +export default defineComponent({ + name: 'FCommentDesign', + props: commentDesignProps, + emits: [] as (string[] & ThisType) | undefined, + setup(props: CommentDesignProps, context) { + + const elementRef = ref(); + const designItemContext = inject('design-item-context') as DesignerItemContext; + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useCommentRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + + const discussionGroupClass = computed(() => { + const classObject = { + 'f-discussion-group': true, + } as Record; + return getCustomClass(classObject, props.customClass); + }); + const discussionEditorClass = computed(() => { + const classObject = { + 'f-discussion-group-edit': true, + 'ide-discussion-editor': true, + } as Record; + return getCustomClass(classObject, props.editorClass); + }); + const discussionListClass = computed(() => { + const classObject = { + 'f-discussion-group-content': true, + 'ide-discussion-list': true, + } as Record; + return getCustomClass(classObject, props.listClass); + }); + const discussionEditorStyle = computed(() => { + return getCustomStyle({}, props?.editorStyle); + }); + const discussionListStyle = computed(() => { + return getCustomStyle({}, props.listStyle); + }); + const discussionStyle = computed(() => { + return getCustomStyle({}, props.customStyle); + }); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + + }); + + context.expose(componentInstance.value); + + return () => { + return ( +
    +
    +
    +
    + + +
    +
    + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + + + + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    + + + +
    + + +
    +
    + +
    +
    + ); + }; + } +}); diff --git a/packages/ui-vue/components/comment/src/designer/comment.design.props.ts b/packages/ui-vue/components/comment/src/designer/comment.design.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb7274b867a22916cec9e0e3cbb232fde6f28783 --- /dev/null +++ b/packages/ui-vue/components/comment/src/designer/comment.design.props.ts @@ -0,0 +1,15 @@ + +import { ExtractPropTypes} from "vue"; +import { createPropsResolver } from "../../../dynamic-resolver/src/props-resolver"; +import { schemaMapper } from '../schema/schema-mapper'; +import { schemaResolver } from '../schema/schema-resolver'; +import { commentProps } from "../comment.props"; +import commentSchema from '../schema/comment.schema.json'; + +export const commentDesignProps = Object.assign({}, commentProps, { + componentId: { type: String, default: '' } +}); + +export type CommentDesignProps = ExtractPropTypes; + +export const propsDesignResolver = createPropsResolver(commentDesignProps, commentSchema, schemaMapper, schemaResolver); diff --git a/packages/ui-vue/components/comment/src/designer/use-comment-rules.ts b/packages/ui-vue/components/comment/src/designer/use-comment-rules.ts new file mode 100644 index 0000000000000000000000000000000000000000..303af0d42f4c7ee3a7fc9bfcfc4440054cfe824f --- /dev/null +++ b/packages/ui-vue/components/comment/src/designer/use-comment-rules.ts @@ -0,0 +1,37 @@ +import { ComponentSchema, UseDesignerRules, DesignerItemContext } from "@farris/ui-vue/components/designer-canvas"; +import { DiscussionGroupProperty } from "../property-config/comment.property-config"; + + +export function useCommentRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { + + // 构造属性配置方法 + function getPropsConfig(componentId: string) { + const schema = designItemContext.schema as ComponentSchema; + const discussionProps = new DiscussionGroupProperty(componentId, designerHostService); + return discussionProps.getPropertyConfig(schema); + } + + function canAccepts() { + return false; + } + function checkCanDeleteComponent() { + return true; + } + function checkCanMoveComponent() { + return true; + } + // /** + // * 组件删除后事件 + // */ + // function onRemoveComponent() { + + // } + + return { + getPropsConfig, + canAccepts, + checkCanDeleteComponent, + checkCanMoveComponent + } as UseDesignerRules; + +} diff --git a/packages/ui-vue/components/comment/src/locale/locale.ts b/packages/ui-vue/components/comment/src/locale/locale.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc2f3482c69d58b5889b0d394572a95e962d80e8 --- /dev/null +++ b/packages/ui-vue/components/comment/src/locale/locale.ts @@ -0,0 +1,15 @@ + +import { useI18n } from 'vue-i18n'; + +export function useCommentLocale() { + const { t } = useI18n(); + return { + "reply": t('comment.reply'), + "cancel": t('comment.cancel'), + "submit": t('comment.submit'), + "confirm": t('comment.confirm'), + "colleague": t('comment.colleague'), + "searchPlaceholder": t('comment.searchPlaceholder'), + "submitEmpty": t('comment.submitEmpty') + }; +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/locales/designer/en.json b/packages/ui-vue/components/comment/src/locales/designer/en.json new file mode 100644 index 0000000000000000000000000000000000000000..b11da8d31e110920ecb4cceee6a2ae4f8d4b2f8a --- /dev/null +++ b/packages/ui-vue/components/comment/src/locales/designer/en.json @@ -0,0 +1,3 @@ +{ + "comment": {} +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/locales/designer/zh-CHS.json b/packages/ui-vue/components/comment/src/locales/designer/zh-CHS.json new file mode 100644 index 0000000000000000000000000000000000000000..b11da8d31e110920ecb4cceee6a2ae4f8d4b2f8a --- /dev/null +++ b/packages/ui-vue/components/comment/src/locales/designer/zh-CHS.json @@ -0,0 +1,3 @@ +{ + "comment": {} +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/locales/designer/zh-CHT.json b/packages/ui-vue/components/comment/src/locales/designer/zh-CHT.json new file mode 100644 index 0000000000000000000000000000000000000000..b11da8d31e110920ecb4cceee6a2ae4f8d4b2f8a --- /dev/null +++ b/packages/ui-vue/components/comment/src/locales/designer/zh-CHT.json @@ -0,0 +1,3 @@ +{ + "comment": {} +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/locales/ui/en.json b/packages/ui-vue/components/comment/src/locales/ui/en.json new file mode 100644 index 0000000000000000000000000000000000000000..b3b4f90d1a98d6da5edc5f1008166cd2601c65a8 --- /dev/null +++ b/packages/ui-vue/components/comment/src/locales/ui/en.json @@ -0,0 +1,11 @@ +{ + "comment": { + "reply": "Reply", + "cancel": "Cancel", + "submit": "Submit", + "confirm": "Confirm", + "colleague": "Colleague", + "searchPlaceholder": "Enter name to search", + "submitEmpty": "Submission content cannot be empty" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/locales/ui/zh-CHS.json b/packages/ui-vue/components/comment/src/locales/ui/zh-CHS.json new file mode 100644 index 0000000000000000000000000000000000000000..86bad952df313c6666a27b124634a13be2606f46 --- /dev/null +++ b/packages/ui-vue/components/comment/src/locales/ui/zh-CHS.json @@ -0,0 +1,11 @@ +{ + "comment": { + "reply":"回复", + "cancel":"取消", + "submit":"提交", + "confirm":"确定", + "colleague":"同事", + "searchPlaceholder":"请输入姓名搜索", + "submitEmpty":"提交内容不能为空" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/locales/ui/zh-CHT.json b/packages/ui-vue/components/comment/src/locales/ui/zh-CHT.json new file mode 100644 index 0000000000000000000000000000000000000000..7298f849d949a4c2aaf1c04be10febcbdf5e632a --- /dev/null +++ b/packages/ui-vue/components/comment/src/locales/ui/zh-CHT.json @@ -0,0 +1,11 @@ +{ + "comment": { + "reply": "回覆", + "cancel": "取消", + "submit": "提交", + "confirm": "確定", + "colleague": "同事", + "searchPlaceholder": "請輸入姓名搜尋", + "submitEmpty": "提交內容不能為空" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/property-config/comment.property-config.ts b/packages/ui-vue/components/comment/src/property-config/comment.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..2eb7a883265b07df7cb3a5fbc67699a17c2677e5 --- /dev/null +++ b/packages/ui-vue/components/comment/src/property-config/comment.property-config.ts @@ -0,0 +1,156 @@ +import { BaseControlProperty } from "@farris/ui-vue/components/property-panel"; + +export class DiscussionGroupProperty extends BaseControlProperty { + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + public getPropertyConfig(propertyData: any) { + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + // 外观 + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + + this.getEditorConfig(propertyData); + this.getListConfig(propertyData); + // 事件交互面板 + this.getEventPropConfig(propertyData); + + return this.propertyConfig; + } + + getEditorConfig(propertyData) { + const self=this; + const visibleEditor=this.getPropertyEditorParams(propertyData,['Const'],'editorVisible',{},{},'visible'); + this.propertyConfig.categories['discussionEditor'] = { + title: "评论编辑器", + description: "评论编辑器相关的属性", + properties: { + editorClass: { + title: "class样式", + type: "string", + description: "编辑器的CSS样式" + }, + editorStyle: { + title: "style样式", + type: "string", + description: "编辑器的样式", + }, + editorVisible:{ + title: "是否可见", + type: "string", + description: "评论编辑器是否可见", + editor:visibleEditor + }, + editorHeight: { + title: "编辑器高度", + description: '指定评论编辑器的高度', + type: 'number', + editor: { + nullable: true, + min: 0, + useThousands: false, + needValid:true + }, + defaultValue:130 + }, + personSearchUrl: { + title: "人员查询地址", + description: '指定人员查询时服务器端的地址', + type: 'string' + } + }, + setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'editorVisible': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + } + }; + } + getListConfig(propertyData) { + const self=this; + const visibleEditor=this.getPropertyEditorParams(propertyData,['Const'],'listVisible',{},{},'visible'); + this.propertyConfig.categories['discussionList'] = { + title: "评论列表", + description: "评论列表相关的属性", + properties: { + listClass: { + title: "class样式", + type: "string", + description: "评论列表的CSS样式" + }, + listStyle: { + title: "style样式", + type: "string", + description: "评论列表的样式", + }, + listVisible:{ + title: "是否可见", + type: "string", + description: "评论列表是否可见", + editor:visibleEditor + } + }, + setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'listVisible': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + } + }; + } + private getEventPropConfig(propertyData: any) { + const events = [ + // { + // "label": "onQueryAtUsers", + // "name": "@用户取数事件" + // }, + { + "label": "onValueChange", + "name": "提交评论事件" + }, + // { + // "label": "onQueryAllOrgs", + // "name": "查询部门事件" + // }, + { + "label": "onQueryFrequentAtUsers", + "name": "查询常用@用户事件" + }, + { + "label": "onQueryComments", + "name": "获取评论" + } + ]; + const self = this; + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); + const properties = self.createBaseEventProperty(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/ui-vue/components/comment/src/schema/comment.schema.json b/packages/ui-vue/components/comment/src/schema/comment.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..6c713dc54b6ef4040fdff033f880869305f0cc5f --- /dev/null +++ b/packages/ui-vue/components/comment/src/schema/comment.schema.json @@ -0,0 +1,83 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/comment.schema.json", + "title": "comment", + "description": "A Farris Component", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for comment", + "type": "string" + }, + "type": { + "description": "The type string of comment", + "type": "string", + "default": "comment" + }, + "appearance": { + "description": "", + "type": "object", + "properties": { + "class": { + "type": "string" + }, + "style": { + "type": "string" + } + }, + "default": {} + }, + "personSearchUrl": { + "description": "", + "type": "string" + }, + "replyUser":{ + "description": "", + "type": "object" + }, + "personnels":{ + "description": "", + "type": "object", + "default":[] + }, + "editorHeight":{ + "description": "", + "type": "number" + }, + "editorClass":{ + "description": "", + "type": "string" + }, + "editorStyle":{ + "description": "", + "type": "string" + }, + "editorVisible":{ + "description": "", + "type": "boolean", + "default":true + }, + "listClass":{ + "description": "", + "type": "string" + }, + "listStyle":{ + "description": "", + "type": "string" + }, + "listVisible":{ + "description": "", + "type": "boolean", + "default":true + } + }, + "required": [ + "id", + "type" + ], + "events":{ + "onValueChange":"提交评论事件", + "onQueryFrequentAtUsers":"加载常用@人员事件", + "onQueryComments":"获取评论" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/schema/schema-mapper.ts b/packages/ui-vue/components/comment/src/schema/schema-mapper.ts new file mode 100644 index 0000000000000000000000000000000000000000..97964aee23bbb8b523c7692723ea02db00d4f4c0 --- /dev/null +++ b/packages/ui-vue/components/comment/src/schema/schema-mapper.ts @@ -0,0 +1,5 @@ +import { MapperFunction, resolveAppearance } from '../../../dynamic-resolver'; + +export const schemaMapper = new Map([ + ['appearance', resolveAppearance] +]); diff --git a/packages/ui-vue/components/comment/src/schema/schema-resolver.ts b/packages/ui-vue/components/comment/src/schema/schema-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..b933b2ee97e2214d995dd22cd0f271e4276dbadf --- /dev/null +++ b/packages/ui-vue/components/comment/src/schema/schema-resolver.ts @@ -0,0 +1,15 @@ +import { DesignerComponentInstance, DesignerHostService } from "@farris/ui-vue/components/designer-canvas"; +import { DynamicResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { buildComment } from "../composition/build-comment"; + +export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record, designerHostService?: DesignerHostService): Record { + const parentComponentInstance = context.parentComponentInstance as DesignerComponentInstance; + if (parentComponentInstance && designerHostService) { + const commentCreator = buildComment(context, designerHostService); + const commentContainer = commentCreator.createComment(); + if (commentContainer) { + return commentContainer; + } + } + return schema; +} diff --git a/packages/ui-vue/components/comment/src/types/composition.ts b/packages/ui-vue/components/comment/src/types/composition.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0142132232bb24f64a4bb1f027528a1ce17f341 --- /dev/null +++ b/packages/ui-vue/components/comment/src/types/composition.ts @@ -0,0 +1,8 @@ +import { Ref } from "vue"; + +export interface UsePopup { + + hidePopup(): void; + + popoverRef: Ref; +} \ No newline at end of file diff --git a/packages/ui-vue/components/discussion-list/src/types/interface.ts b/packages/ui-vue/components/comment/src/types/interface.ts similarity index 46% rename from packages/ui-vue/components/discussion-list/src/types/interface.ts rename to packages/ui-vue/components/comment/src/types/interface.ts index 94903b6edf67f110c988d695b597e2e2fe8e2f6e..1edfa936c37a7c3b9e83b3f506ea90c6a50c2319 100644 --- a/packages/ui-vue/components/discussion-list/src/types/interface.ts +++ b/packages/ui-vue/components/comment/src/types/interface.ts @@ -1,3 +1,23 @@ +export enum MsgInfo { + Cancel = 0, + Confirm = 1 +} +export interface editAttachFile { + id?: string; + name: string; + size: number; + metadataId: string; +} +export interface ValueConfig { + msgInfo: MsgInfo; + text: string; + mailTos: Array<{ userId: string; userName: string }>; + mailToSections: Array; + visibility: string; + parentId: string; + attachFiles?: editAttachFile[]; +} + export interface attachFile { id: string; commentId: string; @@ -21,3 +41,9 @@ export interface discussionItem { export interface discussionConfig extends discussionItem { parentData: discussionItem; } + +export const F_COMMENT_LIST_SERVICE_TOKEN = 'F_COMMENT_LIST_SERVICE_TOKEN'; + +export interface CommentListService { + getData?: () => Promise; +} \ No newline at end of file diff --git a/packages/ui-vue/components/comment/src/types/type.ts b/packages/ui-vue/components/comment/src/types/type.ts new file mode 100644 index 0000000000000000000000000000000000000000..d87bcb40d55d8f8e907934ca88cea61976366d43 --- /dev/null +++ b/packages/ui-vue/components/comment/src/types/type.ts @@ -0,0 +1 @@ +export type PermissionType = 'ALL' | 'RELATED' ; \ No newline at end of file diff --git a/packages/ui-vue/components/comment/style.ts b/packages/ui-vue/components/comment/style.ts new file mode 100644 index 0000000000000000000000000000000000000000..7cb85525147e7d25e546b5d78a4ba6971d138831 --- /dev/null +++ b/packages/ui-vue/components/comment/style.ts @@ -0,0 +1,4 @@ +import "@farris/ui-vue/components/dependent-base/style"; +import "@farris/ui-vue/components/list-view/style"; +import "@farris/ui-vue/components/input-group/style"; +import "@farris/ui-vue/theme-default/components/comment.css"; diff --git a/packages/ui-vue/components/common/directive/auto-title.directive.ts b/packages/ui-vue/components/common/directive/auto-title.directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d6b435a295e5a8328c9ef6ec23712aa8cdc7e91 --- /dev/null +++ b/packages/ui-vue/components/common/directive/auto-title.directive.ts @@ -0,0 +1,23 @@ +const autoTitleDirective = { + created(el: HTMLElement, binding: any) { + // 在元素创建时设置初始 title + el.setAttribute('title', el.textContent || binding.value || ''); + }, + mounted(el: HTMLElement, binding: any) { + // 获取绑定值或元素文本内容 + const title = binding.value || el.textContent || ''; + el.setAttribute('title', title); + }, + updated(el: HTMLElement, binding: any) { + // 更新时重新设置 title + const title = binding.value || el.textContent || ''; + el.setAttribute('title', title); + }, + beforeUpdate(el: HTMLElement, binding: any) { + // 在元素更新前设置 title + const title = binding.value || el.textContent || ''; + el.setAttribute('title', title); + } +}; + +export default autoTitleDirective; diff --git a/packages/ui-vue/components/common/index.ts b/packages/ui-vue/components/common/index.ts index b06735b3c4c8a1cf4d8e2908bb4dab5e87be50f2..cb2d25812b64bb86ee4897ffa629b99ab4f639b5 100644 --- a/packages/ui-vue/components/common/index.ts +++ b/packages/ui-vue/components/common/index.ts @@ -1,5 +1,6 @@ import { App } from 'vue'; import areaResponseDirective from './directive/area-response'; +import autoTitleDirective from './directive/auto-title.directive'; export * from './types'; export * from './text-box/composition/use-text-box'; @@ -23,13 +24,18 @@ export * from './utils/use-array-processor'; export * from './utils/use-common-utils'; export * from './utils/use-delayed-ref'; export { default as areaResponseDirective } from './directive/area-response'; +export { default as autoTitleDirective } from './directive/auto-title.directive'; export * from './utils/type'; export * from './utils/exclude-properties'; +export * from './utils/use-third-component'; +export * from './utils/use-resize-observer'; export * from './utils/use-max-zindex'; +export * from './utils/use-mobile'; export default { install(app: App): void { app.directive('area-response', areaResponseDirective); + app.directive('auto-title', autoTitleDirective); }, register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void { diff --git a/packages/ui-vue/components/common/radio-checkbox/use-check.ts b/packages/ui-vue/components/common/radio-checkbox/use-check.ts index 0b7542813e81d675f5c9c1728f014f00c9cf762e..8eb8e4189eae2dab94148f72a6a76aa3b87c24ed 100644 --- a/packages/ui-vue/components/common/radio-checkbox/use-check.ts +++ b/packages/ui-vue/components/common/radio-checkbox/use-check.ts @@ -21,10 +21,36 @@ export function useCheck( const modelValue = ref(props.modelValue); + /** + * 值到数组值的转换 + */ + function transformToArr(value: any): string[] { + if (!value) { + return []; + } + if (!parentProps.isStringValue) { + return value; + } + + return value.split(parentProps.separator); + } + + /** + * 值到字符串值的转换 + */ + function transformToStr(value: Array) { + if (!parentProps.modelValue) { + return value; + } + return parentProps.modelValue + parentProps.separator + value; + } // 如果是group const checked = computed(() => parentProps ? - parentProps.modelValue === props.value || parentProps.modelValue.includes(props.value) : - (props.trueValue != null ? props.modelValue === props.trueValue: !!props.modelValue) || !!props.checked + // radio判断 + parentProps.modelValue === props.value || + // checkbox判断 + transformToArr(parentProps.modelValue).includes(props.value) : + (props.trueValue != null ? props.modelValue === props.trueValue : !!props.modelValue) || !!props.checked ); // 按钮样式 @@ -76,19 +102,22 @@ export function useCheck( if (parentProps) { // 父组件双向绑定 if (!checked.value) { - parentContext.emit('update:modelValue', [...parentProps.modelValue, props.value]); + const value = !parentProps.isStringValue ? [...parentProps.modelValue, props.value] : + transformToStr(props.value); + parentContext.emit('update:modelValue', value); // 值变化事件 - parentContext.emit('changeValue', [...parentProps.modelValue, props.value]); + parentContext.emit('changeValue', value); } else { - const valuesWithoutSelf = parentProps.modelValue?.filter(value => value !== props.value); - parentContext.emit('update:modelValue', valuesWithoutSelf); + const valuesWithoutSelf = transformToArr(parentProps.modelValue).filter(value => value !== props.value); + const valueStrWithoutSelf = !parentProps.isStringValue ? valuesWithoutSelf : valuesWithoutSelf.join(parentProps.separator); + parentContext.emit('update:modelValue', valueStrWithoutSelf); // 值变化事件 - parentContext.emit('changeValue', valuesWithoutSelf); + parentContext.emit('changeValue', valueStrWithoutSelf); } } else { context.emit('update:checked', !checked.value); - context.emit('update:modelValue', !checked.value? props.trueValue: props.falseValu); - context.emit('changeValue', !checked.value? props.trueValue : props.falseValue); + context.emit('update:modelValue', !checked.value ? props.trueValue : props.falseValue); + context.emit('changeValue', !checked.value ? props.trueValue : props.falseValue); context.emit('change', { originalEvent: e, checked: !checked.value }); } }; diff --git a/packages/ui-vue/components/common/text-box/composition/use-text-box.ts b/packages/ui-vue/components/common/text-box/composition/use-text-box.ts index 520e0156bf060cd0b663da2567ecc12fbb21cf28..0cbd4226dea4c9acf6815782a5db08288ad9f1dd 100644 --- a/packages/ui-vue/components/common/text-box/composition/use-text-box.ts +++ b/packages/ui-vue/components/common/text-box/composition/use-text-box.ts @@ -25,7 +25,7 @@ export function useTextBox( ): UseTextBox { const disabled = ref(props.disabled); const focusStatus = ref(false); - const inputType = ref('text'); + const showBorder = ref(props.showBorder); const textAlign = ref(props.textAlign); const updateOn = ref(props.updateOn); @@ -68,7 +68,14 @@ export function useTextBox( const inputGroupStyle = computed(() => { return !showBorder.value ? 'border-width : 0 ' : ''; }); - + /** 替换showType 替换 type */ + function getRealShowType(){ + if(props.type&&props.type!=='text'){ + return props.type; + } + return props.showType||'text'; + } + const inputType = ref(getRealShowType()); function changeTextBoxValue(newValue: string, shouldEmitChangeEvent = true) { // if (modelValue.value !== newValue) { modelValue.value = newValue; @@ -99,6 +106,17 @@ export function useTextBox( } }); + // showType替换type属性 + watch(()=>props.showType,(newValue)=>{ + if(newValue){ + inputType.value=newValue; + } + }); + // type支持旧设置 + watch(()=>props.type,(newValue)=>{ + inputType.value=newValue; + }); + function onBlur(payload: FocusEvent) { payload.stopPropagation(); // if((payload.target as HTMLElement) === inputRef.value?.querySelector('span.input-group-clear')){ diff --git a/packages/ui-vue/components/common/types.ts b/packages/ui-vue/components/common/types.ts index b90534bc11f7e47ebe304cd0cd204260d506c2ff..e56714d257021b13f24d64cb3c051d40e57cedfc 100644 --- a/packages/ui-vue/components/common/types.ts +++ b/packages/ui-vue/components/common/types.ts @@ -25,6 +25,8 @@ export interface TextBoxProps { type: string; + showType?:string; + showBorder: boolean; updateOn: 'blur' | 'change'; diff --git a/packages/ui-vue/components/common/utils/use-appearance.ts b/packages/ui-vue/components/common/utils/use-appearance.ts index 74038b3cfa007abb36698beea9387161dd6d27fc..4c2098ea0386e57ddb9273dc0c8d71b9aecf01b0 100644 --- a/packages/ui-vue/components/common/utils/use-appearance.ts +++ b/packages/ui-vue/components/common/utils/use-appearance.ts @@ -22,12 +22,21 @@ export function getCustomClass(classObject: Record, customClass: st */ export function getCustomStyle(styleObject: Record, customStyle: string) { const styleArray = customStyle?.split(';') || []; - styleArray.reduce((result: Record, styleString: string) => { + + // 使用 forEach 替代 reduce,逻辑更清晰 + styleArray.forEach(styleString => { if (styleString) { const styles = styleString.split(':'); - result[styles[0]] = styles[1]; + + // 验证样式格式是否正确(必须有键和值) + if (styles.length >= 2 && styles[0]?.trim() && styles[1]?.trim()) { + const key = styles[0].trim(); + // 合并除第一个元素外的所有元素作为值,以防值中包含冒号 + const value = styles.slice(1).join(':').trim(); + styleObject[key] = value; + } } - return result; - }, styleObject); + }); + return styleObject; } diff --git a/packages/ui-vue/components/common/utils/use-max-zindex.ts b/packages/ui-vue/components/common/utils/use-max-zindex.ts index b56935e6218b48d78f662b0e9f593f85b034ead0..d2b4e31a0431f4ea8618c61e218cdd9f7f5ddd4d 100644 --- a/packages/ui-vue/components/common/utils/use-max-zindex.ts +++ b/packages/ui-vue/components/common/utils/use-max-zindex.ts @@ -19,7 +19,9 @@ const isElementVisible = (element: Element): boolean => { export const getMaxZIndex = () => { const selectors = [ 'body>.popover.fade.in', - 'body>.modal.fade.in' + 'body>.modal.fade.show', + 'body>.f-filter-panel-wrapper', + 'body>.f-drawer' ] const bodyChildren = document.body.querySelectorAll(selectors.join(',')); let max = 0; @@ -43,4 +45,4 @@ export const getMaxZIndex = () => { } } return max ? max + 1 : 0; -}; \ No newline at end of file +}; diff --git a/packages/ui-vue/components/common/utils/use-mobile.ts b/packages/ui-vue/components/common/utils/use-mobile.ts new file mode 100644 index 0000000000000000000000000000000000000000..723d44f3ac80b37d616d21443d8ac62e363dc0de --- /dev/null +++ b/packages/ui-vue/components/common/utils/use-mobile.ts @@ -0,0 +1,54 @@ +export function isMobilePhone(): boolean { + const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; + + // 检查是否为手机设备的 User Agent 标识 + const phonePatterns = [ + /iPhone/i, + /iPod/i, + /Android.*Mobile/i, // Android 手机通常包含 "Mobile" 字样,而平板不包含 + /webOS/i, + /BlackBerry/i, + /IEMobile/i, + /Opera Mini/i, + /Harmony/i + ]; + + // 检查是否为平板设备的 User Agent 标识 + const tabletPatterns = [ + /iPad/i, + /Android(?!.*Mobile)/i, // Android 平板通常不含 "Mobile" + /Tablet/i, + /PlayBook/i, + /Kindle/i, + /Silk/i + ]; + + // 首先检查是否明确为平板设备 + for (const pattern of tabletPatterns) { + if (pattern.test(userAgent)) { + return false; + } + } + + // 然后检查是否为手机设备 + for (const pattern of phonePatterns) { + if (pattern.test(userAgent)) { + return true; + } + } + + // 对于其他情况,通过触摸支持和屏幕尺寸综合判断 + const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + const screenWidth = screen.width; + const screenHeight = screen.height; + + // 获取较小的尺寸作为判断依据(无论横屏还是竖屏) + const minScreenDimension = Math.min(screenWidth, screenHeight); + + // 如果支持触摸且较小的屏幕尺寸符合手机尺寸,则判断为手机 + if (isTouchDevice && minScreenDimension <= 768) { + return true; + } + + return false; +} diff --git a/packages/ui-vue/components/common/utils/use-resize-observer.ts b/packages/ui-vue/components/common/utils/use-resize-observer.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ab80b92596a4fce4a91333069d34117dc102c8d --- /dev/null +++ b/packages/ui-vue/components/common/utils/use-resize-observer.ts @@ -0,0 +1,7 @@ +export function useResizeObserver(el: HTMLElement , callback: ResizeObserverCallback) { + const resizeObserver = new ResizeObserver(callback); + resizeObserver.observe(el); + return () => { + resizeObserver.unobserve(el); + }; +} diff --git a/packages/ui-vue/components/common/utils/use-third-component.ts b/packages/ui-vue/components/common/utils/use-third-component.ts new file mode 100644 index 0000000000000000000000000000000000000000..3ea1b281bac4314bd1bce7ae3275bc77db68c1f2 --- /dev/null +++ b/packages/ui-vue/components/common/utils/use-third-component.ts @@ -0,0 +1,41 @@ + +interface ComponentConfig { + /** 组件JS 文件路径*/ + url: string; + /** 组件导出的模块名 */ + module: string; +} + +export function useThirdComponent() { + + const globalStorageKey = 'F_THIRD_COMPONENTS'; + const defaultConfigFilePath = '/platform/common/web/renderer/third-party-components.json?v=' + new Date().getTime(); + + async function loadThirdComponents(configFilePath?: string): Promise> { + const response = await fetch(configFilePath || defaultConfigFilePath); + const componentConfigs: ComponentConfig[] = await response.json(); + + if (!componentConfigs?.length) { + return {}; + } + + window[globalStorageKey] = {}; + + await Promise.all(componentConfigs.map(async (config: ComponentConfig) => { + const componentModule = await window['System']?.import(config.url); + window[globalStorageKey][config.module] = componentModule[config.module]; + })); + + return window[globalStorageKey]; + } + + return { loadThirdComponents, globalStorageKey }; +} + +/** + * 富文本编辑器的资源路径 + */ +export const globalRichTextEditorAssetsPath: { key: string; value: string } = { + key: 'F_RICHTEXTEDITOR_ASSETSPATH', + value: '/platform/common/web/@farris/ui-editor-vue3/assets/farris-editor/' +} diff --git a/packages/ui-vue/components/component/src/components/split-form-component.component.tsx b/packages/ui-vue/components/component/src/components/split-form-component.component.tsx index ee544ae2155b0deb5344f094e574d45c8cfdf2ee..04ff62cc9bc32aff0f3287e76b46dabbfeae5cb1 100644 --- a/packages/ui-vue/components/component/src/components/split-form-component.component.tsx +++ b/packages/ui-vue/components/component/src/components/split-form-component.component.tsx @@ -71,7 +71,7 @@ export default defineComponent({ function renderNameCell(row: any) { const controlData = row.raw?.control; return <> - {controlData.label ? {controlData.label} : {controlData.id} } + {controlData.label || controlData.title || controlData.id} ; } return () => { @@ -111,7 +111,7 @@ export default defineComponent({ hierarchy={hierarchyOption}> {{ 'cellTemplate': ({ cell, row }) => { - return cell.field === 'bindingPath' ? renderBindingPathCell(cell, row) :renderNameCell(row); + return cell.field === 'bindingPath' ? renderBindingPathCell(cell, row) : renderNameCell(row); } }} diff --git a/packages/ui-vue/components/component/src/composition/field-tree-builder.ts b/packages/ui-vue/components/component/src/composition/field-tree-builder.ts index cc0137442807423fd7770fc565e28914f3c966f7..b7e124caf4d1cfe41b0d4c78343c85598aafe35c 100644 --- a/packages/ui-vue/components/component/src/composition/field-tree-builder.ts +++ b/packages/ui-vue/components/component/src/composition/field-tree-builder.ts @@ -15,6 +15,7 @@ export interface FieldTreeNode { data: any; expanded: boolean; groupId: string; + groupName?: string; isBindVariable: boolean; isComplexField: boolean; isGroupNode: boolean; @@ -96,13 +97,13 @@ export class FieldTreeBuilder { fieldsetList.push(control); return; } - const treeNode = this.buildFieldTreeNodeFromControl(control, dgViewModelFields, this.componentId, componentType); + const treeNode = this.buildFieldTreeNodeFromControl(control, dgViewModelFields, this.componentId, componentType, controls); if (!treeNode) { return; } if (treeNode.isInGroupNode) { if (!groupData.has(treeNode.groupId)) { - const newGroupNode = this.buildFieldTreeGroupNodeWithBindingEntityField(control, dgViewModelFields, fieldsetList); + const newGroupNode = this.buildFieldTreeGroupNodeWithBindingEntityField(control, dgViewModelFields, fieldsetList, treeNode); if (newGroupNode) { groupData.set(treeNode.groupId, newGroupNode); treeData.push(newGroupNode); @@ -130,11 +131,12 @@ export class FieldTreeBuilder { control: any, dgViewModelFields: DesignViewModelField[], componentId: string, - componentType: string + componentType: string, + controls: any ): FieldTreeNode | undefined { let treeNode: FieldTreeNode | undefined; if (!control.binding) { - treeNode = this.buildFieldTreeNodeWithoutControlBinding(control); + treeNode = this.buildFieldTreeNodeWithoutControlBinding(control, controls); } else if (control.binding.type === FormBindingType.Variable) { treeNode = this.buildFieldTreeNodeWithBindingVariable(control); } else { @@ -146,7 +148,16 @@ export class FieldTreeBuilder { * 构造仅有控件,没有绑定视图模型字段的节点 * @param control UI控件元素 */ - private buildFieldTreeNodeWithoutControlBinding(control: any): FieldTreeNode { + private buildFieldTreeNodeWithoutControlBinding(control: any, controls: any): FieldTreeNode { + const parentFieldSet = controls.find(item => { + if (item.type === DgControl.fieldset.type && item.contents && item.contents.length) { + const result = item.contents.find(childItem => childItem.id === control.id); + if (result) { + return true; + } + } + }); + return { canChangeControlType: false, children: [], @@ -154,12 +165,13 @@ export class FieldTreeBuilder { componentType: '', control, data: { id: control.id, name: control.title || control.text || control.label }, - groupId: '', + groupId: parentFieldSet ? parentFieldSet.id : '', + groupName: parentFieldSet ? parentFieldSet.title : '', expanded: false, isBindVariable: false, isComplexField: false, isGroupNode: false, - isInGroupNode: false, + isInGroupNode: !!parentFieldSet, isNoBinding: true, isRemoved: false, nodeType: '' @@ -253,60 +265,52 @@ export class FieldTreeBuilder { private buildFieldTreeGroupNodeWithBindingEntityField( control: any, dgViewModelFields: DesignViewModelField[], - fieldsetList: any): FieldTreeNode | undefined { - const bindingFieldId = control.binding.field; + fieldsetList: any, + treeNode: FieldTreeNode | undefined): FieldTreeNode | undefined { + let fieldGroupId; let fieldGroupName; - if (control.binding.type === FormBindingType.Form) { + if (control.binding && control.binding.type === FormBindingType.Form) { + const bindingFieldId = control.binding.field; const viewModelFieldElement = dgViewModelFields.find(field => field.id === bindingFieldId); if (!viewModelFieldElement) { return; } - const { groupId, groupName } = viewModelFieldElement; - const fieldSetControl = fieldsetList.find(fieldSet => fieldSet.id === groupId); - - return { - canChangeControlType: false, - children: [], - componentId: '', - componentType: '', - control: fieldSetControl, - data: { id: groupId, name: groupName }, - groupId, - expanded: true, - isBindVariable: false, - isComplexField: false, - isGroupNode: true, - isInGroupNode: false, - isNoBinding: false, - isRemoved: false, - nodeType: 'group' - }; - } else { + fieldGroupId = viewModelFieldElement.groupId; + fieldGroupName = viewModelFieldElement.groupName; + } else if (control.binding && control.binding.type === FormBindingType.Variable) { + const bindingFieldId = control.binding.field; const viewModelNode = this.formSchemaUtils.getViewModelById(this.viewModelId); const viewModelVariableElement = viewModelNode?.fields.find(field => field.id === bindingFieldId);; if (!viewModelVariableElement) { return; } - const { groupId, groupName } = viewModelVariableElement; - const fieldSetControl = fieldsetList.find(fieldSet => fieldSet.id === groupId); - return { - canChangeControlType: false, - children: [], - componentId: '', - componentType: '', - control: fieldSetControl, - data: { id: groupId, name: groupName }, - groupId, - expanded: true, - isBindVariable: false, - isComplexField: false, - isGroupNode: true, - isInGroupNode: false, - isNoBinding: false, - isRemoved: false, - nodeType: 'group' - }; + fieldGroupId = viewModelVariableElement.groupId; + fieldGroupName = viewModelVariableElement.groupName; + } else { + fieldGroupId = treeNode?.groupId; + fieldGroupName = treeNode?.groupName; + } + const fieldSetControl = fieldsetList.find(fieldSet => fieldSet.id === fieldGroupId); + + return { + canChangeControlType: false, + children: [], + componentId: '', + componentType: '', + control: fieldSetControl, + data: { id: fieldGroupId, name: fieldGroupName }, + groupId: fieldGroupId, + expanded: true, + isBindVariable: false, + isComplexField: false, + isGroupNode: true, + isInGroupNode: false, + isNoBinding: false, + isRemoved: false, + nodeType: 'group' + }; + } diff --git a/packages/ui-vue/components/component/src/composition/use-sibling-component.tsx b/packages/ui-vue/components/component/src/composition/use-sibling-component.tsx index f25879cd1c91333f81f3e49c942167b4d1d02c9f..40ac8f61cd7dbfbfb14307485189adbb03dc24fe 100644 --- a/packages/ui-vue/components/component/src/composition/use-sibling-component.tsx +++ b/packages/ui-vue/components/component/src/composition/use-sibling-component.tsx @@ -68,6 +68,7 @@ export function useSiblingComponent(designItemContext: DesignerItemContext, desi bindingEntityId={bindingTargetId} steps={steps} designerHostService={designerHostService} + targetComponentInstance={designItemContext.parent} onSubmit={onSubmitEntitySelctor} onCancel={onCancelEntitySelector} > ); } diff --git a/packages/ui-vue/components/component/src/designer/use-designer-rules.ts b/packages/ui-vue/components/component/src/designer/use-designer-rules.ts index e3ef4d13a194df0f9d2a3368085154dd459a5514..14a722476c86ca042d55a7b447e0a46dc42a2812 100644 --- a/packages/ui-vue/components/component/src/designer/use-designer-rules.ts +++ b/packages/ui-vue/components/component/src/designer/use-designer-rules.ts @@ -174,13 +174,15 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const title = treeGridSchema ? '树表格' : '表格'; return `${title}组件`; } - case ComponentType.attachmentPanel: { + case ComponentType.uploader: { return '附件组件'; } case ComponentType.listView: { return '列表视图组件'; } - + case ComponentType.calendar: { + return '日历组件'; + } default: { return '组件'; } diff --git a/packages/ui-vue/components/component/src/property-config/component.property-config.ts b/packages/ui-vue/components/component/src/property-config/component.property-config.ts index 3310e5688e14384ab5d206e23f1711f1caffd7e4..0aeabd89f9025e13399e2dcd4c2534ed09cea3d4 100644 --- a/packages/ui-vue/components/component/src/property-config/component.property-config.ts +++ b/packages/ui-vue/components/component/src/property-config/component.property-config.ts @@ -28,7 +28,7 @@ export class ComponentProperty extends BaseControlProperty { title: "组件名称", type: "string", description: "组件名称", - defaultValue:propertyData.name + defaultValue: propertyData.name } }, (changeObject, propertyData, parameters) => { switch (changeObject.propertyID) { @@ -53,10 +53,18 @@ export class ComponentProperty extends BaseControlProperty { name: '视图初始化后事件' }, ]; + + if (this.designerContext && this.designerContext.responsiveForm && propertyData.componentType === 'frame') { + events.push({ + label: "goBack", + name: "原生返回事件", + }); + } + const self = this; const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); const properties = self.createBaseEventProperty(initialData); - + this.propertyConfig.categories['eventsEditor'] = { title: '事件', hideTitle: true, diff --git a/packages/ui-vue/components/components.ts b/packages/ui-vue/components/components.ts index 4f027c8734c247418b0169164beacb8b365585f1..c2b73a73286a3a106a0a6a5b8c7c2858c4ee2345 100644 --- a/packages/ui-vue/components/components.ts +++ b/packages/ui-vue/components/components.ts @@ -31,12 +31,12 @@ export type { DataGridColumn, DataGridProps } from './data-grid'; export { default as FDatePicker } from './date-picker'; export { default as FDropdown } from './dropdown'; export type { DropdownProps } from './dropdown'; -export { FDynamicView, registerComponent } from './dynamic-view'; +export { FDynamicView, registerComponent, loadRegister, resolverMap, componentsForRegister, componentMap, componentPropsConverter } from './dynamic-view'; export * from './data-view'; export type { DataColumn, VisualData, VisualDataCell, RowOptions } from './data-view'; export { default as FDrawer } from './drawer'; export type { DrawerProps } from './drawer'; -export { FResponseForm, FResponseForm as FDynamicForm, FDynamicFormGroup, FDynamicFormInput } from './dynamic-form'; +export { FResponseForm as FDynamicForm, FDynamicFormGroup, FDynamicFormInput } from './dynamic-form'; export type { EditorConfig } from './dynamic-form'; export { default as FFilterBar } from './filter-bar'; export type { FilterBarProps } from './filter-bar'; @@ -49,7 +49,7 @@ export { default as FListNav } from './list-nav'; export type { ListNavProps } from './list-nav'; export { default as FListView } from './list-view'; export type { ListViewProps } from './list-view'; -export { default as FLayout } from './layout'; +export { default as FLayout, FLayoutPane } from './layout'; export type { LayoutProps } from './layout'; export { default as FLoading, FLoadingService } from './loading'; export type { LoadingProps } from './loading'; @@ -109,9 +109,16 @@ export type { SearchBoxProps } from './search-box'; export { default as FVerifyDetail, FVerifyDetailService } from './verify-detail'; export type { VerifyDetailProps } from './verify-detail'; export { default as FItemCollectionEditor } from './radio-group/src/designer/item-collection-editor.component'; +export { default as FItemCollectionEditorInner } from './radio-group/src/designer/item-collection-editor-inner.component'; export { default as FSchemaSelectorEditor } from './schema-selector/src/schema-selector-editor.component'; export { default as FPropertyEditor } from './property-editor'; export { default as MenuLookupContainer } from './menu-lookup/src/components/modal-container.component'; export { useMenuTreeGridCoordinator } from './menu-lookup/src/composition/use-tree-grid-coordinator'; export { default as FLookup } from './lookup'; -export { default as Locale, LocaleService} from './locale'; +export { default as FImage } from './image'; +export type { ImageProps } from './image'; +export { default as FComment } from './comment'; +export type { CommentProps } from './comment'; +export { default as Locale, LocaleService, useResourceLoader, LOCALE_SERVICE_INJECTION_TOKEN, type LocaleConfig } from './locale'; +export * from './common'; +export { FieldSelectorRepositoryToken } from './field-selector'; diff --git a/packages/ui-vue/components/condition/index.ts b/packages/ui-vue/components/condition/index.ts index f3a0bd38ce320577157c28e4e673b85842be1402..edd9f7e5eb3e0a49e994cb4d1b8ff53028fdbab4 100644 --- a/packages/ui-vue/components/condition/index.ts +++ b/packages/ui-vue/components/condition/index.ts @@ -25,6 +25,7 @@ export * from './src/composition/condition-value/types'; export * from './src/composition/use-compare'; export * from './src/composition/use-condition'; export * from './src/composition/use-condition-value'; +export * from './src/composition/use-condition-utils'; FConditionList.install = (app: App) => { app.component(FConditionFields.name as string, FConditionFields) diff --git a/packages/ui-vue/components/condition/src/composition/condition-value/check-group-value.ts b/packages/ui-vue/components/condition/src/composition/condition-value/check-group-value.ts new file mode 100644 index 0000000000000000000000000000000000000000..db5af3bff5005fb82ff25aa39689a5eca97e6339 --- /dev/null +++ b/packages/ui-vue/components/condition/src/composition/condition-value/check-group-value.ts @@ -0,0 +1,11 @@ +import { EditorType } from '../../../../dynamic-form'; +import { ComboListValue, ConditionValue } from "./types"; + +export class CheckGroupValue extends ComboListValue { + + editorType: EditorType = 'check-group'; + + constructor(initialData: { value: any, valueList: any[] } = { value: '', valueList: [] }, editor?: any) { + super(initialData,editor); + } +} diff --git a/packages/ui-vue/components/condition/src/composition/condition-value/dropdown-value.ts b/packages/ui-vue/components/condition/src/composition/condition-value/dropdown-value.ts index 66e2794ed270c13b546ad07353d91e427ccd4be0..4f738b73c9fa745a97726025331c633b3950a84e 100644 --- a/packages/ui-vue/components/condition/src/composition/condition-value/dropdown-value.ts +++ b/packages/ui-vue/components/condition/src/composition/condition-value/dropdown-value.ts @@ -43,8 +43,8 @@ export class ComboListValue implements ConditionValue { } clear(): void { - this.value = ''; - this.valueList = []; + const valueType = typeof (this.value); + this.value =valueType==='string'? '':undefined; } getValue() { @@ -59,7 +59,8 @@ export class ComboListValue implements ConditionValue { switch (valueType) { case 'string': valueList = this.value.split(',') || []; - dispalyText = this.valueList.filter(item => valueList.indexOf(item.value as string) > -1).map(item => item.name).join(','); + // 解决true、false的列表时,对应不上问题 + dispalyText = this.valueList.filter(item => valueList.indexOf(item.value+'') > -1).map(item => item.name).join(','); break; case 'boolean': dispalyText = this.valueList.find(item => item.value === this.value)?.name || ''; diff --git a/packages/ui-vue/components/condition/src/composition/condition-value/number-range-value.ts b/packages/ui-vue/components/condition/src/composition/condition-value/number-range-value.ts index 49b4d58eeec60933ccb8444b06fc55f1d68612d3..0b9f5fb885113935ddb95f9ad529f8a1278bf617 100644 --- a/packages/ui-vue/components/condition/src/composition/condition-value/number-range-value.ts +++ b/packages/ui-vue/components/condition/src/composition/condition-value/number-range-value.ts @@ -36,6 +36,9 @@ export class NumberRangeValue implements ConditionValue { } getDisplayText() { + if(this.begin===null&&this.end===null){ + return ''; + } return (this.begin === null ? '' : this.begin) + '~' + (this.end === null ? '' : this.end); } diff --git a/packages/ui-vue/components/condition/src/composition/condition-value/number-spinner-value.ts b/packages/ui-vue/components/condition/src/composition/condition-value/number-spinner-value.ts index 997d05f2e0343b38ed927f8c0eb1c188eb52c075..a11a40740ffb091bb8637784c0066ba35caa04d9 100644 --- a/packages/ui-vue/components/condition/src/composition/condition-value/number-spinner-value.ts +++ b/packages/ui-vue/components/condition/src/composition/condition-value/number-spinner-value.ts @@ -1,5 +1,6 @@ import { EditorType } from '../../../../dynamic-form'; import { ConditionValue } from "./types"; +import { useNumberFormat } from '@farris/ui-vue/components/common'; export class NumberSpinnerValue implements ConditionValue { @@ -8,10 +9,10 @@ export class NumberSpinnerValue implements ConditionValue { value: number | null | undefined; valueType = 'number'; -// 编辑器配置 -editiorConfig; - constructor(initialData: { value: string } = { value: '' },editor?:any) { - this.editiorConfig=Object.assign({},editor); + // 编辑器配置 + editiorConfig; + constructor(initialData: { value: string } = { value: '' }, editor?: any) { + this.editiorConfig = Object.assign({}, editor); const numberSpinnerValue = parseFloat(initialData.value); this.value = isNaN(numberSpinnerValue) ? null : numberSpinnerValue; } @@ -25,7 +26,11 @@ editiorConfig; } getDisplayText() { - return this.getValue(); + if(this.isEmpty()){ + return ''; + } + const { formatTo } = useNumberFormat(); + return formatTo(this.value as any,this.editiorConfig); } setValue(value: any): void { diff --git a/packages/ui-vue/components/condition/src/composition/condition-value/radio-group-value.ts b/packages/ui-vue/components/condition/src/composition/condition-value/radio-group-value.ts index b8e30cf36bdf5c8998d5358e2b2e470c12fd762e..9fd37988e411bf9103370218ee4798cc11bb5344 100644 --- a/packages/ui-vue/components/condition/src/composition/condition-value/radio-group-value.ts +++ b/packages/ui-vue/components/condition/src/composition/condition-value/radio-group-value.ts @@ -9,11 +9,11 @@ export class RadioGroupValue implements ConditionValue { valueType = 'enum'; - valueList:Array<{value: string | number | boolean, name:string}> = []; -// 编辑器配置 -editiorConfig; - constructor(initialData: { value: any, valueList: any[] } = { value: null, valueList: [] },editor?:any) { - this.editiorConfig=Object.assign({},editor); + valueList: Array<{ value: string | number | boolean, name: string }> = []; + // 编辑器配置 + editiorConfig; + constructor(initialData: { value: any, valueList: any[] } = { value: null, valueList: [] }, editor?: any) { + this.editiorConfig = Object.assign({}, editor); this.value = initialData?.value; if (this.editiorConfig.data && this.editiorConfig.data.length) { this.valueList = this.editiorConfig.data; diff --git a/packages/ui-vue/components/condition/src/composition/types.ts b/packages/ui-vue/components/condition/src/composition/types.ts index e917337bc6f5669fb8188c062ba766a9ac3c056f..c6593a4f6517e6c94cd5736f71881ec33ff6397f 100644 --- a/packages/ui-vue/components/condition/src/composition/types.ts +++ b/packages/ui-vue/components/condition/src/composition/types.ts @@ -1,11 +1,11 @@ import { ComputedRef, Ref } from "vue"; import { ConditionGroup, FieldConfig, Condition } from "../types"; -import { EditorType } from "../../../dynamic-form"; +import { EditorConfig, EditorType } from "../../../dynamic-form"; import { ConditionValue } from "./condition-value/types"; export interface UseFieldConfig { - convertToSingleControl(configs: FieldConfig[]): FieldConfig[]; + convertToControls(configs: FieldConfig[]): FieldConfig[]; fields: Ref; @@ -72,3 +72,8 @@ export interface UseSizeValue { conditionClass: ComputedRef, resizeObserver: Ref } + +export interface UseConditionUtils{ + getSingleControlType(field: FieldConfig): EditorConfig; + convertToControls(configs: FieldConfig[]): FieldConfig[]; +} \ No newline at end of file diff --git a/packages/ui-vue/components/condition/src/composition/use-condition-utils.ts b/packages/ui-vue/components/condition/src/composition/use-condition-utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..ece8aad387a56a329a5aee0be962c924827a481f --- /dev/null +++ b/packages/ui-vue/components/condition/src/composition/use-condition-utils.ts @@ -0,0 +1,151 @@ +import { FieldConfig, Condition } from "../types"; +import { EditorConfig, EditorType } from "../../../dynamic-form"; +import { cloneDeep } from "lodash-es"; +import { LocaleService } from '@farris/ui-vue/components/locale'; + +/** + * 筛选条和筛选方案的条件 + * @param sourceType + * @returns + */ +export function useConditionUtils(sourceType: 'query-solution' | 'filter-bar' = 'query-solution') { + + function getRealControlType(controlType): EditorType { + let realType = 'input-group'; + switch (controlType) { + case 'year-picker': + case 'month-picker': + case 'month-range': + case 'date-range': + case 'datetime-range': + case 'datetime-picker': + realType = 'date-picker'; + break; + default: + realType = controlType; + } + return realType as EditorType; + } + function getSingleControlType(field: FieldConfig): EditorConfig { + const currentType = field.editor ? field.editor.type : 'input-group'; + field.editor.type = getRealControlType(currentType); + if (field.editor.type === 'date-picker') { + if (currentType.indexOf('range') > -1) { + field.editor.enablePeriod = true; + } + if (currentType.indexOf('datetime') > -1) { + field.editor.showTime = true; + } + if (currentType.indexOf('year') > -1) { + field.editor.selectMode = "year"; + } + if (currentType.indexOf('month') > -1) { + field.editor.selectMode = "month"; + } + if (field.editor.weekSelect) { + field.editor.selectMode = "week"; + } + } else if (field.editor.type === 'number-spinner' || field.editor.type === 'number-range') { + field.editor.showZero = true; + field.editor.nullable = true; + field.editor.needValid = true; + } else if (field.editor.type === 'check-group' && sourceType === 'filter-bar') { + field.editor.direction = 'vertical'; + } else if(field.editor.type === 'lookup'||field.editor.type === 'combo-list'){ + field.editor.enableClear=Object.prototype.hasOwnProperty.call(field.editor,'enableClear')?field.editor.enableClear:true; + } + if (currentType === 'input-group' && !field.editor.placeholder) { + // input-group组件允许placeholder值是空, 在此处理提示多语言 + field.editor.placeholder = LocaleService.getLocaleValue('input-group.placeholder'); + } + return field.editor; + } + function convertToControls(configs: FieldConfig[]): FieldConfig[] { + const convertedFieldConfigs = configs.map((fieldConfig: FieldConfig) => Object.assign({}, fieldConfig)) + .map((fieldConfig: FieldConfig) => { + // 指定是否可见 + fieldConfig.visible = Object.prototype.hasOwnProperty.call(fieldConfig, 'visible') ? fieldConfig.visible : true; + fieldConfig.editor = getSingleControlType(fieldConfig); + return fieldConfig; + }); + return convertedFieldConfigs; + } + /** + * 当条件变更时 + * @param condition + * @param value + * @param editor + * @param option + */ + function conditionChangeHandler(condition: Condition, value: any, editor?, option?: any) { + switch (condition.value.editorType) { + case 'combo-list': + if (option.newValue) { + condition.value.valueList = option.newValue.map(item => { + return { name: item.name, value: item.value }; + }); + } + break; + case 'radio-group': + condition.value.valueList = [editor.data.find(item => item.value === value)]; + break; + case 'year-range': + case 'month-range': + case 'date-range': + case 'datetime-range': + condition.value.setValue(value); + break; + default: + } + } + /** + * 构造condition fields + */ + function renderFieldConditionEditor(fieldMap, condition, changeHandler) { + const editor = cloneDeep(fieldMap.get(condition.fieldCode)?.editor); + const id = fieldMap.get(condition.fieldCode)?.id; + /** + * 一般条件的可见与字段的课件是一致的 + * 但条件可以单独变更可见,所以此处先用condition + */ + const visible = Object.prototype.hasOwnProperty.call(condition, 'visible') ? condition['visible'] : fieldMap.get(condition.fieldCode)?.visible; + let needEmitChange = true; + if (condition.value?.editorType === 'lookup' && editor) { + editor.idValue = condition.value.mapFields?.map(field => field.id).join(','); + // 绑定了自定义清空事件 + const { onClear } = editor; + editor['onClear'] = (mapFields) => { + condition.value.mapFields = []; + changeHandler(condition, ''); + onClear&&onClear(mapFields); + }; + editor['onUpdate:dataMapping'] = (mapFields) => { + condition.value.mapFields = mapFields.items || []; + changeHandler(condition, condition.value.getValue()); + }; + needEmitChange = false; + } else if (condition.value?.editorType === 'number-range' && editor) { + editor.beginValue = condition.value.begin; + editor['onBeginValueChange'] = (value) => { + condition.value.begin = value; + changeHandler(condition, value); + }; + editor.endValue = condition.value.end; + editor['onEndValueChange'] = (value) => { + condition.value.end = value; + changeHandler(condition, value); + }; + needEmitChange = false; + } else if (editor && ['year-range', 'month-range', 'date-range', 'datetime-range'].find(item => item === condition.value?.editorType)) { + editor.beginValue = condition.value.begin; + editor.endValue = condition.value.end; + } + if (Object.prototype.hasOwnProperty.call(condition, 'disabled')) { + // 条件上指定的禁用状态 + editor.disabled = condition.disabled; + } + return { id, editor, visible, needEmitChange }; + } + + return { getSingleControlType, convertToControls, conditionChangeHandler, renderFieldConditionEditor } +} diff --git a/packages/ui-vue/components/condition/src/composition/use-condition-value.ts b/packages/ui-vue/components/condition/src/composition/use-condition-value.ts index 6111ad3e62423c173a5b5861297761f186f0eadf..fcfb0d3f2f7e3ea0444ad81755d32bdbb4234a2f 100644 --- a/packages/ui-vue/components/condition/src/composition/use-condition-value.ts +++ b/packages/ui-vue/components/condition/src/composition/use-condition-value.ts @@ -17,6 +17,7 @@ import { TextValue } from "./condition-value/text-value"; import { ConditionValue } from "./condition-value/types"; import { UseConditionValue } from "./types"; import { DateTimeRangeValue } from "./condition-value/datetime-range-value"; +import { CheckGroupValue } from "./condition-value/check-group-value"; export function useConditionValue(): UseConditionValue { @@ -57,6 +58,8 @@ export function useConditionValue(): UseConditionValue { return new NumberSpinnerValue(initialValue,editor); case 'radio-group': return new RadioGroupValue(initialValue,editor); + case 'check-group': + return new CheckGroupValue(initialValue,editor); default: return new TextValue(initialValue,editor); } diff --git a/packages/ui-vue/components/condition/src/composition/use-field-config.ts b/packages/ui-vue/components/condition/src/composition/use-field-config.ts index 05509fc0ed58748dcf0fe61522465b8323287cab..f706298da2d77b2272888b10e726a62671dcf0a7 100644 --- a/packages/ui-vue/components/condition/src/composition/use-field-config.ts +++ b/packages/ui-vue/components/condition/src/composition/use-field-config.ts @@ -1,80 +1,27 @@ import { SetupContext, ref } from "vue"; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { FieldConfig, Condition } from "../types"; import { ConditionProps } from "../condition.props"; import { UseFieldConfig } from "./types"; -import { EditorConfig, EditorType } from "../../../dynamic-form"; import { useConditionValue } from "./use-condition-value"; -import { useI18n } from 'vue-i18n'; -export function useFieldConfig(props: ConditionProps, context: SetupContext): UseFieldConfig { - const { t } = useI18n(); +import { useConditionUtils } from "./use-condition-utils"; + + +export function useFieldConfig(props: ConditionProps, context: SetupContext,sourceType: 'query-solution' | 'filter-bar'='query-solution'): UseFieldConfig { + const { convertToControls } = useConditionUtils(sourceType); const fields = ref(props.fields); const fieldConditions = ref([]); const fieldMap = new Map(); const { createConditionValue } = useConditionValue(); - function getRealControlType(controlType): EditorType { - let realType = 'input-group'; - switch (controlType) { - case 'year-picker': - case 'month-picker': - case 'month-range': - case 'date-range': - case 'datetime-range': - case 'datetime-picker': - realType = 'date-picker'; - break; - default: - realType = controlType; - } - return realType as EditorType; - } - function getSingleControlType(field: FieldConfig): EditorConfig { - const currentType = field.editor ? field.editor.type : 'input-group'; - field.editor.type = getRealControlType(currentType); - if (field.editor.type === 'date-picker') { - if (currentType.indexOf('range') > -1) { - field.editor.enablePeriod = true; - } - if (currentType.indexOf('datetime') > -1) { - field.editor.showTime = true; - } - if (currentType.indexOf('year') > -1) { - field.editor.selectMode = "year"; - } - if (currentType.indexOf('month') > -1) { - field.editor.selectMode = "month"; - } - if (field.editor.weekSelect) { - field.editor.selectMode = "week"; - } - } - if (field.editor.type === 'number-spinner') { - // 解决数值控件校验问题 - field.editor.needValid = true; - } - if (currentType === 'input-group' && !field.editor.placeholder) { - // input-group组件允许placeholder值是空, 在此处理提示多语言 - field.editor.placeholder = t('input-group.placeholder'); - } - return field.editor; - } - - function convertToSingleControl(configs: FieldConfig[]): FieldConfig[] { - const convertedFieldConfigs = configs.map((fieldConfig: FieldConfig) => Object.assign({}, fieldConfig)) - .map((fieldConfig: FieldConfig) => { - fieldConfig.editor = getSingleControlType(fieldConfig); - return fieldConfig; - }); - return convertedFieldConfigs; - } - function loadFieldConfigs(useRangeEditor = true) { + if (useRangeEditor) { + fields.value = convertToControls(fields.value); + } fields.value.reduce((result: Map, field: FieldConfig) => { - if (useRangeEditor) { - field.editor = getSingleControlType(field); - } result.set(field.labelCode, field); return result; }, fieldMap); + } function initialConditionValue(conditions: Condition[]) { @@ -87,5 +34,5 @@ export function useFieldConfig(props: ConditionProps, context: SetupContext): Us return conditions; } - return { convertToSingleControl, fields, fieldMap, fieldConditions, loadFieldConfigs, initialConditionValue }; + return { convertToControls, fields, fieldMap, fieldConditions, loadFieldConfigs, initialConditionValue }; } diff --git a/packages/ui-vue/components/condition/src/condition-fields.component.tsx b/packages/ui-vue/components/condition/src/condition-fields.component.tsx index e1ce5a8e70ed336c4b253ad5dc209ce685400f9c..69f53720f78d2221e624ad8a5d08f797c06fe391 100644 --- a/packages/ui-vue/components/condition/src/condition-fields.component.tsx +++ b/packages/ui-vue/components/condition/src/condition-fields.component.tsx @@ -21,7 +21,8 @@ import { FDynamicFormGroup } from '@farris/ui-vue/components/dynamic-form'; import { useFieldConfig } from './composition/use-field-config'; import { useSize } from './composition/use-size'; import { ConditionValue } from './composition/condition-value/types'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; +import { useConditionUtils } from './composition/use-condition-utils'; export default defineComponent({ name: 'FConditionFields', @@ -29,9 +30,10 @@ export default defineComponent({ emits: ['valueChange', 'blur', 'focus', 'click', 'input'] as (string[] & ThisType) | undefined, setup(props: ConditionProps, context: SetupContext) { const fieldsElement = ref(); - const { locale } = useI18n(); + const locale = LocaleService.getLocale(); const key = ref(props.key); const conditions = ref(props.conditions); + const { renderFieldConditionEditor, conditionChangeHandler } = useConditionUtils(); const useFieldComposition = useFieldConfig(props, context); const { initialConditionValue, fieldMap, loadFieldConfigs } = useFieldComposition; const useSizeComposition = useSize(props, context, fieldsElement); @@ -44,6 +46,13 @@ export default defineComponent({ resizeObserver.value?.unobserve(fieldsElement.value); }); + watch( + () => props.fields, + () => { + loadFieldConfigs(true); + } + ); + watch( () => props.conditions, () => { @@ -57,69 +66,23 @@ export default defineComponent({ 'f-utils-flex-row-wrap': true, 'farris-form': true, 'condition-div': true, - 'farris-form-controls-inline': !locale.value || props.isControlInline===true || props.isControlInline === 'auto' && locale.value !== 'en' + 'farris-form-controls-inline': !locale || props.isControlInline === true || props.isControlInline === 'auto' && locale !== 'en' })); function onChange(condition: Condition, value: any, editor?, option?: any) { - switch (condition.value.editorType) { - case 'combo-list': - if (option.newValue) { - condition.value.valueList = option.newValue.map(item => { - return { name: item.name, value: item.value }; - }); - } - break; - case 'radio-group': - condition.value.valueList = [editor.data.find(item => item.value === value)]; - break; - case 'year-range': - case 'month-range': - case 'date-range': - case 'datetime-range': - condition.value.setValue(value); - break; - default: - } + conditionChangeHandler(condition, value, editor, option); context.emit('valueChange', value, condition); } function renderFieldConditions() { return conditions.value.map((condition: Condition) => { - const editor = fieldMap.get(condition.fieldCode)?.editor; - const id = fieldMap.get(condition.fieldCode)?.id; - let needEmitChange = true; - if (condition.value?.editorType === 'lookup' && editor) { - editor.idValue = condition.value.mapFields?.map(field => field.id).join(','); - editor['onClear'] = () => { - condition.value.mapFields = []; - onChange(condition, ''); - }; - editor['onUpdate:dataMapping'] = (mapFields) => { - condition.value.mapFields = mapFields.items; - onChange(condition, condition.value.getValue()); - }; - needEmitChange = false; - } else if (condition.value?.editorType === 'number-range' && editor) { - editor.beginValue = condition.value.begin; - editor['onBeginValueChange'] = (value) => { - condition.value.begin = value; - onChange(condition, value); - }; - editor.endValue = condition.value.end; - editor['onEndValueChange'] = (value) => { - condition.value.end = value; - onChange(condition, value); - }; - needEmitChange = false; - } else if (editor && ['year-range', 'month-range', 'date-range', 'datetime-range'].find(item => item === condition.value?.editorType)) { - editor.beginValue = condition.value.begin; - editor.endValue = condition.value.end; - } - let customClass = editor?.appearance?.class || props.itemClass; + const { id, editor, needEmitChange, visible } = renderFieldConditionEditor(fieldMap, condition, onChange); + let customClass = editor?.appearance?.class || props.itemClass; editor?.multiLineLabel && (customClass = customClass + ' farris-group-multi-label'); return , default: [] }, key: { type: String, default: '' }, /** - * 控间标签同行展示 + * 控件标签同行展示 */ isControlInline: { type: [Boolean, String], default: 'auto', validator: (value) => { diff --git a/packages/ui-vue/components/condition/src/locale/locale.ts b/packages/ui-vue/components/condition/src/locale/locale.ts index 64230c688c049b7cf5ae2039bf397ea296105a82..99e0a97072b19889566f558f6705c20aac15a7f5 100644 --- a/packages/ui-vue/components/condition/src/locale/locale.ts +++ b/packages/ui-vue/components/condition/src/locale/locale.ts @@ -1,11 +1,11 @@ -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; /** * 此处当做静态处理 * @param props * @returns */ export function useConditionListLocale() { - const { t } = useI18n(); + const { getLocaleValue: t } = LocaleService; const conditionListLocale = { // 添加条件 add: t('condition.add'), @@ -19,7 +19,7 @@ export function useConditionListLocale() { return { conditionListLocale }; } export function useCompareLocale() { - const { t } = useI18n(); + const { getLocaleValue: t } = LocaleService; const operatorsLocale = { equal: t('operators.equal'), notEqual: t('operators.notEqual'), diff --git a/packages/ui-vue/components/condition/src/types.ts b/packages/ui-vue/components/condition/src/types.ts index 9f1e3f0dddd3da27e77d714877e8636e44666893..c3d07c1d1d106b4ebe608cd47712bf3abe0b03b2 100644 --- a/packages/ui-vue/components/condition/src/types.ts +++ b/packages/ui-vue/components/condition/src/types.ts @@ -36,7 +36,25 @@ export enum ValueType { */ Express = 3 } +/** + * 用来规范默认值、可见、只读状态的更新 + */ +export interface SimpleCondition{ + /** + * 唯一性标识 + */ + id?: string; + /** + * 字段编号 + */ + fieldCode?: string; + value?: ConditionValue; + /** + * 是否可见 + */ + visible?: boolean; +} /** * 条件实体类 */ @@ -53,7 +71,7 @@ export interface Condition { id: string; /** - * 字段编号 + * 字段编号 与fieldConfig中的labelCode响对应 */ fieldCode: string; @@ -115,8 +133,18 @@ export interface Condition { * 高级模式下的条件id */ conditionId: number; - + /** + * 是否必填 + */ required?: boolean; + /** + * 是否可见 + */ + visible?: boolean; + /** + * 是否禁用 + */ + disabled?:boolean; } /** @@ -169,8 +197,14 @@ export interface FieldConfig { * 16. 智能输入框: InputGroupControl */ editor: EditorConfig; - + /** + * 字段默认值 + */ defaulValue?: ConditionValue; + /** + * 是否可见 + */ + visible?:boolean; } export interface ConditionGroup { diff --git a/packages/ui-vue/components/content-container/index.ts b/packages/ui-vue/components/content-container/index.ts index 6ca5abb0b5effa559acc22dcbc51f2943357446d..cccaa180eb36394c7b79deb63061b9a6c38c50b1 100644 --- a/packages/ui-vue/components/content-container/index.ts +++ b/packages/ui-vue/components/content-container/index.ts @@ -17,7 +17,8 @@ import type { App } from 'vue'; import ContentContainer from './src/content-container.component'; import ContentContainerDesign from './src/designer/content-container.design.component'; -import { propsResolver } from './src/content-container.props'; +import { propsResolver, propsResolverGenerator } from './src/content-container.props'; +import { RegisterContext } from '../common'; export * from './src/content-container.props'; export { ContentContainer, ContentContainerDesign }; @@ -26,12 +27,15 @@ export default { install(app: App): void { app.component(ContentContainer.name as string, ContentContainer); }, - register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void { + register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext): void { componentMap['content-container'] = ContentContainer; - propsResolverMap['content-container'] = propsResolver; + propsResolverMap['content-container'] = propsResolverGenerator(registerContext); }, - registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void { + registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext + ): void { componentMap['content-container'] = ContentContainerDesign; - propsResolverMap['content-container'] = propsResolver; + propsResolverMap['content-container'] = propsResolverGenerator(registerContext); } }; diff --git a/packages/ui-vue/components/content-container/src/content-container.props.ts b/packages/ui-vue/components/content-container/src/content-container.props.ts index 38cd8bf2698a5ed7af837a6d2d6abbc8f1ec4aba..0439b548ec04fc94179d5bc1eb4621dc72e751ba 100644 --- a/packages/ui-vue/components/content-container/src/content-container.props.ts +++ b/packages/ui-vue/components/content-container/src/content-container.props.ts @@ -1,6 +1,6 @@ import { ExtractPropTypes } from 'vue'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapper } from './schema/schema-mapper'; import contentContainerSchema from './schema/content-container.schema.json'; import { schemaResolver } from './schema/schema-resolver'; @@ -15,3 +15,9 @@ export const contentContainerProps = { export type ContentContainerPropsType = ExtractPropTypes; export const propsResolver = createPropsResolver(contentContainerProps, contentContainerSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator( + contentContainerProps, + contentContainerSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/content-container/src/designer/use-designer-rules.ts b/packages/ui-vue/components/content-container/src/designer/use-designer-rules.ts index c36d324fadf7a99c5d579629948ca7414c489e3e..8659595a749a2c35036a1764a67d9b5241342a3d 100644 --- a/packages/ui-vue/components/content-container/src/designer/use-designer-rules.ts +++ b/packages/ui-vue/components/content-container/src/designer/use-designer-rules.ts @@ -22,18 +22,45 @@ export function useDesignerRulesForContentContainer(designItemContext: DesignerI return false; } /** - * 只有不可编辑的表格,才支持启用筛选方案 + * 判断树表格绑定的分级码是否分级码分级信息 + * @param treeGridViewModelId 树表格所在的模型id + * @param udtField 树表格绑定的分级码字段编号 + * @returns */ - function getUnEditableDataGrid() { + function checkPathHierarchyType(treeGridViewModelId: string, udtField: string) { + const schemaService = designerHostService?.schemaService; + const udtFields = schemaService.getTreeGridUdtFields(treeGridViewModelId); + + if (udtFields) { + const bindingUdtFieldInfo = udtFields.find(fieldInfo => fieldInfo.key === udtField); + const bindingUdtField = bindingUdtFieldInfo && bindingUdtFieldInfo.field; + if (bindingUdtField?.type?.name?.includes('PathHierarchyInfo')) { + return true; + } + } + } + /** + * 判断页面是否包含表格,并且要求: + * 1、表格或树表格绑定主表 + * 2、树表格绑定的分级码udt字段必须为分级码分级信息,不能为父节点分级信息 + */ + function checkDataGridExisted() { const formSchemaUtils = designerHostService?.formSchemaUtils; - const matchedDataGridComponentRef = formSchemaUtils.selectNode(schema, (item) => { + const rootComponent = formSchemaUtils.getComponentById('root-component'); + const matchedDataGridComponentRef = formSchemaUtils.selectNode(rootComponent, (item) => { if (item.type === 'component-ref') { const childComponent = formSchemaUtils.getComponentById(item.component); - if (childComponent?.componentType === 'data-grid') { - const dataGrid = formSchemaUtils.selectNode(childComponent, childItem => childItem.type === 'data-grid' && !childItem.fieldEditable); - if (dataGrid) { + const childViewModel = formSchemaUtils.getViewModelById(childComponent?.viewModel); + if (childComponent?.componentType === 'data-grid' && childViewModel?.bindTo === '/') { + const treeGrid = formSchemaUtils.selectNode(childComponent, item => item.type === 'tree-grid'); + if (treeGrid) { + // return checkPathHierarchyType(childComponent?.viewModel, treeGrid.udtField); + // 暂时不支持树表格启用筛选方案 + return false; + } else { return true; } + } } }); @@ -59,20 +86,60 @@ export function useDesignerRulesForContentContainer(designItemContext: DesignerI }); return matchedDataGridComponentRef; } + /** + * 判断当前页面是否已启用筛选方案 + */ + function checkQuerySolutionExisted() { + const formSchemaUtils = designerHostService?.formSchemaUtils; + const rootComponent = formSchemaUtils.getComponentById('root-component'); + + const solution = formSchemaUtils.selectNode(rootComponent, (item) => item.type === DgControl['query-solution'].type); + return !!solution; + } /** * 判断当前容器是否可接收筛选方案 */ function checkCanAcceptQuerySolution(draggingContext: DraggingResolveContext) { const containerClass = schema.appearance?.class; const { sourceType } = draggingContext; - const isPageLayer = containerClass && containerClass.includes('f-page-is-managelist'); + const isPageLayer = containerClass && containerClass.split(' ').includes('f-page'); if (!isPageLayer) { return false; } if (sourceType === 'control') { - // 要求:当前页面不存在筛选方案、表格不可编辑、当前页面不存在筛选条 - const solutionExisted = schema.contents && schema.contents.find(content => content.type === DgControl['section'].type && content.appearance?.class?.includes('f-section-scheme')); - return !solutionExisted && getUnEditableDataGrid() && !checkFilterBarExisted(); + // 要求:当前页面不存在筛选方案、当前页面不存在筛选条、当前页面存在绑定主表的表格控件 + return !checkQuerySolutionExisted() && !checkFilterBarExisted() && checkDataGridExisted(); + } + if (sourceType === 'move') { + return true; + } + return false; + } + /** + * 判断当前移动的控件是否为抽屉 + */ + function checkIfDraggingDrawer(draggingContext: DraggingResolveContext) { + const { componentType, sourceType } = draggingContext; + if (sourceType === 'control' && componentType === DgControl['drawer'].type) { + return true; + } + return false; + } + /** + * 判断当前容器是否可接收抽屉 + */ + function checkCanAcceptDrawer(draggingContext: DraggingResolveContext) { + const containerClass = schema.appearance?.class; + const { sourceType } = draggingContext; + const isPageLayer = containerClass && containerClass.split(' ').includes('f-page'); + if (!isPageLayer) { + return false; + } + + if (sourceType === 'control') { + // 要求:一个页面只存在一个抽屉 + const drawerExisted = schema.contents && schema.contents.find(content => content.type === DgControl['drawer'].type); + return !drawerExisted; } if (sourceType === 'move') { return true; @@ -87,7 +154,9 @@ export function useDesignerRulesForContentContainer(designItemContext: DesignerI if (checkIfDraggingQuerySolution(draggingContext)) { return checkCanAcceptQuerySolution(draggingContext); } - + if (checkIfDraggingDrawer(draggingContext)) { + return checkCanAcceptDrawer(draggingContext); + } const basalRule = useDragulaCommonRule().basalDragulaRuleForContainer(draggingContext, designerHostService); if (!basalRule) { return false; @@ -106,11 +175,17 @@ export function useDesignerRulesForContentContainer(designItemContext: DesignerI } function checkCanMoveComponent() { + if(designItemContext.schema.appearance && designItemContext.schema.appearance.class && designItemContext.schema.appearance.class.includes('fm-page-footer-container')) { + return false; + } const { canMove } = dragAndDropRules.getTemplateRule(designItemContext, designerHostService); return canMove; } function checkCanDeleteComponent() { - const { canDelete } = dragAndDropRules.getTemplateRule(designItemContext, designerHostService); + if(designItemContext.schema.appearance && designItemContext.schema.appearance.class && designItemContext.schema.appearance.class.includes('fm-page-footer-container')) { + return false; + } + const { canDelete } = dragAndDropRules.getTemplateRule(designItemContext, designerHostService); return canDelete; } diff --git a/packages/ui-vue/components/data-grid/designer.ts b/packages/ui-vue/components/data-grid/designer.ts index 47d66585338748f250640ae9ecbe4003a69aa45d..bc121023afa71b91f6563a3f0d149cfa0efac3d4 100644 --- a/packages/ui-vue/components/data-grid/designer.ts +++ b/packages/ui-vue/components/data-grid/designer.ts @@ -14,18 +14,19 @@ * limitations under the License. */ import type { App, ExtractPropTypes } from 'vue'; -import { withInstall } from '@farris/ui-vue/components/common'; +import { RegisterContext, withInstall } from '@farris/ui-vue/components/common'; import FDataGrid, { type DataGridProps, dataGridProps } from '@farris/ui-vue/components/data-grid'; import { createCollectionBindingResolver, createDataGridSelectionItemResolver, createDataViewUpdateColumnsResolver, - createPropsResolver + createPropsResolver, + getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import FDataGridDesign from './src/designer/data-grid.design.component'; import FGridFieldEditor from './src/designer/grid-field-editor.component'; import FDataGridColumnDesign from './src/designer/data-grid-column.design.component'; -import { gridFieldEditorPropsResolver } from './src/designer/grid-field-editor.props'; +import { gridFieldPropsResolverGenerator } from './src/designer/grid-field-editor.props'; import { columnPropsResolver, treeColumnPropsResolver } from './src/designer/data-grid-column.props'; import { schemaMapper } from './src/schema/schema-mapper'; import { schemaResolver } from "./src/schema/schema-resolver"; @@ -33,6 +34,12 @@ import dataGridSchema from './src/schema/data-grid.schema.json'; import { createDataGridCallbackResolver } from './src/schema/callback-resolvers'; import getColumnHeader from './src/designer/column-header.design.component'; +export const propsResolverGenerator = getPropsResolverGenerator( + dataGridProps, + dataGridSchema, + schemaMapper, + schemaResolver +); export const propsResolver = createPropsResolver(dataGridProps, dataGridSchema, schemaMapper, schemaResolver); @@ -46,11 +53,11 @@ FDataGridDesign.install = (app: App) => { app.component(FDataGridDesign.name as string, FDataGridDesign); }; -FDataGridDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FDataGridDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record, registerContext: RegisterContext) => { componentMap['data-grid'] = FDataGrid; - propsResolverMap['data-grid'] = propsResolver; + propsResolverMap['data-grid'] = propsResolverGenerator(registerContext); componentMap['grid-field-editor'] = FGridFieldEditor; - propsResolverMap['grid-field-editor'] = gridFieldEditorPropsResolver; + propsResolverMap['grid-field-editor'] = gridFieldPropsResolverGenerator(registerContext); resolverMap['data-grid'] = { bindingResolver, selectionItemResolver, updateColumnsResolver, callbackResolver }; }; diff --git a/packages/ui-vue/components/data-grid/src/components/data/data-area.component.tsx b/packages/ui-vue/components/data-grid/src/components/data/data-area.component.tsx index e3adc9fdb7c1c9e7d0970346995991ead19e7581..c2798b8b1c332976f2df48131dbb84b9e6385bf7 100644 --- a/packages/ui-vue/components/data-grid/src/components/data/data-area.component.tsx +++ b/packages/ui-vue/components/data-grid/src/components/data/data-area.component.tsx @@ -240,24 +240,22 @@ export default function (
    ; } + function renderPasswordContent() { + return
    + ****** +
    ; + } function renderDefault(cell: VisualDataCell) { const cellText = !isNull(cell.data) && !isUndefined(cell.data) ? cell.data.toString() : cell.data; + const isPassword = cell.column?.editor?.showType === 'password'; + if(isPassword) { + return renderPasswordContent(); + } return cell.showTips ?
    {cellText}
    :
    {cellText}
    ; - // return cell.showTips ? - // - // { - // { - // default: () =>
    {cellText}
    , - // contentTemplate: () => cellText - // } - // } - //
    : - // cellText; } function renderDefaultCellContent(cell: VisualDataCell, row: VisualData) { - const cellText = !isNull(cell.data) && !isUndefined(cell.data) ? cell.data.toString() : cell.data; return cell.mode === CellMode.editing ?
    {cell.getEditor(cell)} diff --git a/packages/ui-vue/components/data-grid/src/components/data/data-grid-area.component.tsx b/packages/ui-vue/components/data-grid/src/components/data/data-grid-area.component.tsx index 4c074f6523f039922763ca7be45cda3f69ff242b..aa713f22ec49258393e7edac2df2059d15aaa9b6 100644 --- a/packages/ui-vue/components/data-grid/src/components/data/data-grid-area.component.tsx +++ b/packages/ui-vue/components/data-grid/src/components/data/data-grid-area.component.tsx @@ -104,7 +104,7 @@ export default defineComponent({ ref={cell.setRef} key={cellKey(groupRow, cell.index)} class="fv-grid-cell" - style={cellPosition(cell, cellPositionMap.value[area])}> + style={cellPosition(groupRow, cell, cellPositionMap.value[area])}> {cell.data}
    ); diff --git a/packages/ui-vue/components/data-grid/src/composition/data-grid-component-creator.service.ts b/packages/ui-vue/components/data-grid/src/composition/data-grid-component-creator.service.ts index eda8851e3101f67060976f211258ce5cb8f25c23..57f75fdae8634939d3456abafd9bc9f1c2d9f921 100644 --- a/packages/ui-vue/components/data-grid/src/composition/data-grid-component-creator.service.ts +++ b/packages/ui-vue/components/data-grid/src/composition/data-grid-component-creator.service.ts @@ -60,7 +60,7 @@ export class DataGridComponentCreatorService { } // 1、将表格拖入无标题的目标区域,需要给表格追加Section容器。并给Section添加新增删除按钮 - const parentContainerWithoutTitle = [DgControl['content-container'].type, DgControl['response-layout-item'].type, DgControl['splitter-pane'].type]; + const parentContainerWithoutTitle = [DgControl['content-container'].type, DgControl['response-layout-item'].type, DgControl['splitter-pane'].type, DgControl['drawer'].type]; if (parentContainerType && parentContainerWithoutTitle.includes(parentContainerType)) { const containerSection = this.resolver.getSchemaByType( 'section', @@ -115,21 +115,29 @@ export class DataGridComponentCreatorService { id: `button-add-${buildInfo.componentId}`, type: btnType, text: '新增', - disabled: stateMachineRenderState.find(d => d.id === 'canAddDetail') ? `!viewModel.stateMachine['canAddDetail']` : false, + disabled: stateMachineRenderState.find(d => d.id === 'canAddDetail') ? { + type: 'StateMachine', + status: false, + field: 'canAddDetail' + } : false, onClick: `root-viewModel.${viewModelNode.id}.${commandPrefix}AddItem1` }), Object.assign({}, btnSchema, { id: `button-remove-${buildInfo.componentId}`, type: btnType, text: '删除', - disabled: stateMachineRenderState.find(d => d.id === 'canRemoveDetail') ? `!viewModel.stateMachine['canRemoveDetail']` : false, + disabled: stateMachineRenderState.find(d => d.id === 'canRemoveDetail') ? { + type: 'StateMachine', + status: false, + field: 'canRemoveDetail' + } : false, onClick: `root-viewModel.${viewModelNode.id}.${commandPrefix}RemoveItem1` })]; if (!resolvedContainerSchema.toolbar) { resolvedContainerSchema.toolbar = { - id: `${resolvedContainerSchema.id}_toolbar`, - type: DgControl['tab-page'].type === resolvedContainerType ? 'tab-toolbar' : 'section-toolbar', - buttons: [] + id: `${resolvedContainerSchema.id}_toolbar`, + type: DgControl['tab-page'].type === resolvedContainerType ? 'tab-toolbar' : 'section-toolbar', + buttons: [] }; } if (!resolvedContainerSchema.toolbar.id) { @@ -280,11 +288,16 @@ export class DataGridComponentCreatorService { */ private getDataGridComponentClass(buildInfo: ComponentBuildInfo): string { const { templateId } = this.formSchemaUtils.getFormSchema().module; - const parentSchemaClass = buildInfo.parentComponentInstance?.schema?.appearance?.class; + const parentSchema = buildInfo.parentComponentInstance?.schema; + const parentSchemaClass = parentSchema?.appearance?.class; // 双列表标签页模板,要求启用自动填充 - if ('double-list-in-tab-template' === templateId) { - return 'f-struct-wrapper f-utils-fill-flex-column'; + if ('double-list-in-tab-template' === templateId && parentSchema?.type === 'tab-page') { + const tabSchema = buildInfo.parentComponentInstance?.parent && buildInfo.parentComponentInstance?.parent['schema']; + + if (tabSchema?.type === 'tabs' && tabSchema?.fill === true) { + return 'f-struct-wrapper f-utils-fill-flex-column'; + } } // 双列表、树列表模板的右侧区域,要求启用自动填充 if (['double-list-template', 'tree-list-template'].includes(templateId) && parentSchemaClass?.includes('f-page-content-main')) { @@ -297,11 +310,20 @@ export class DataGridComponentCreatorService { * 创建表格组件内层级结构 */ private createDateGridComponentContents(buildInfo: ComponentBuildInfo) { - const parentSchemaClass = buildInfo.parentComponentInstance?.schema?.appearance?.class; + const parentSchema = buildInfo.parentComponentInstance?.schema; + const parentSchemaClass = parentSchema?.schema?.appearance?.class; const { templateId } = this.formSchemaUtils.getFormSchema().module; let container; // 根据模板不同,创建不同的容器类型和样式 - if (templateId === 'double-list-in-tab-template') { + let isInFillTab = false; + if ('double-list-in-tab-template' === templateId && parentSchema?.type === 'tab-page') { + const tabSchema = buildInfo.parentComponentInstance?.parent && buildInfo.parentComponentInstance?.parent['schema']; + + if (tabSchema?.type === 'tabs' && tabSchema?.fill === true) { + isInFillTab = true; + } + } + if (isInFillTab) { // 1、创建setion const section = this.resolver.getSchemaByType('section') as ComponentSchema; Object.assign(section, { @@ -350,7 +372,11 @@ export class DataGridComponentCreatorService { columns, fieldEditable, dataSource: buildInfo.dataSource || '', - editable: fieldEditable ? 'viewModel.stateMachine[\'editable\']' : false, + editable: fieldEditable ? { + type: 'StateMachine', + status: true, + field: 'editable' + } : false, pagination: { enable: buildInfo.editable ? false : true } diff --git a/packages/ui-vue/components/data-grid/src/data-grid.component.tsx b/packages/ui-vue/components/data-grid/src/data-grid.component.tsx index ae4f384719306bf218f884f8cab14c1567ee6cc6..3a55acccc89f41a44f8c910a1ab8131747193ddc 100644 --- a/packages/ui-vue/components/data-grid/src/data-grid.component.tsx +++ b/packages/ui-vue/components/data-grid/src/data-grid.component.tsx @@ -24,9 +24,17 @@ import { useGroupColumn, useGroupData, useHierarchy, useIdentify, useNavigation, useRow, useSelection, useSidebar, useSort, useVirtualScroll, useVisualData, useVisualDataBound, useVisualDataCell, useVisualDataRow, useVisualGroupRow, useVisualSummaryRow, useCellContentStyle, useLoading, getEmpty, usePagination, - UpdateDataOption, getGridSettingsIconRender, useSettingColumn + UpdateDataOption, getGridSettingsIconRender, useSettingColumn, + VisualDataCell, + COMMAND_COLUMN_DATA_TYPE, + SETTING_COLUMN_DATA_TYPE, + CellMode, + useMobile } from '@farris/ui-vue/components/data-view'; import { getCustomClass } from '@farris/ui-vue/components/common'; +import FButton from '@farris/ui-vue/components/button'; +import FDrawer from '@farris/ui-vue/components/drawer'; +import FResponseForm, { FDynamicFormLabel } from '@farris/ui-vue/components/dynamic-form'; import './data-grid.css'; export default defineComponent({ @@ -41,8 +49,11 @@ export default defineComponent({ 'pageIndexChanged', 'pageSizeChanged', 'beginEditCell', 'endEditCell', 'filterChanged', 'sortChanged', - 'selectionUpdate'], + 'selectionUpdate', + 'endEditRow' + ], setup(props: DataGridProps, context) { + const { isMobilePhone } = useMobile(); const settingIconRef = ref(); const preloadCount = 0; const rowHeight = props.rowOption?.height || 28; @@ -366,6 +377,10 @@ export default defineComponent({ } } + function emptyCurrentRowId() { + useSelectionComposition.emptyCurrentRowId(); + } + watch( () => props.showSetting, (newValue: boolean, oldValue: boolean) => { @@ -380,9 +395,21 @@ export default defineComponent({ } ); + function updatePagination(pageOptions: Partial) { + usePaginationComposition.updatePagination(pageOptions); + } + function updateDataSource(newData: Record[], options?: UpdateDataOption ) { + // Promise.resolve() + // .then(() => { + // const instance = { updateColumns, updatePagination }; + // props.beforeUpdate && typeof props.beforeUpdate === 'function' ? props.beforeUpdate({ instance }) : () => { }; + // }) + // .then(() => { + + // }); if (newData) { // 重新加载数据时,重置滚动条位置 dataView.load(newData); @@ -406,10 +433,6 @@ export default defineComponent({ useEditComposition.clear(); } - function updatePagination(pageOptions: Partial) { - usePaginationComposition.updatePagination(pageOptions); - } - watch(dataView.shouldGroupingData, (newEnableGroup: boolean, oldEnableGroup: boolean) => { if (newEnableGroup !== oldEnableGroup) { dataView.updateDataView(); @@ -421,8 +444,18 @@ export default defineComponent({ updateColumns(latestColumns); }); + function activeRowById(id: string) { + useRowComposition.activeRowById(id); + } - function selectItemById(dataItemId: string) { + function selectItemById(dataItemId: string, options: { + isCurrent?: boolean + } = { + isCurrent: true + }) { + if (options?.isCurrent) { + activeRowById(dataItemId); + } useSelectionComposition.selectItemById(dataItemId); } @@ -436,9 +469,6 @@ export default defineComponent({ } } - function activeRowById(id: string) { - useRowComposition.activeRowById(id); - } function selectRowById(dataItemId: string) { const idField = useIdentifyComposition.idField.value; @@ -472,7 +502,14 @@ export default defineComponent({ useEditComposition.endEditCell(); } - function unSelectItemByIds(dataItemIds: string[]) { + function unSelectItemByIds(dataItemIds: string[], options: { + clearCurrent?: boolean + } = { + clearCurrent: true + }) { + if (options?.clearCurrent) { + activeRowById(''); + } useSelectionComposition.unSelectItemByIds(dataItemIds); } @@ -512,7 +549,7 @@ export default defineComponent({ acceptDataItem, cancelDataItem, selectItemById, selectItemByIds, updateColumns, updateDataSource, updatePagination, getVisibleData, getVisibleDataByIds, getSelectedItems, getSelectionRow, getCurrentRowId, endEditCell, clickRowItemById, clearSelection, unSelectItemByIds, scrollToBottom, scrollToRowByIndex, - updateDerivedData + updateDerivedData, emptyCurrentRowId }; context.expose(dataGridComponentInstance); @@ -574,36 +611,123 @@ export default defineComponent({ useVirtualScrollComposition, }); + function renderRowEditPanelContent(row: VisualData) { + return + { + Object.keys(row.data) + .filter((key: string) => row.data[key].column?.dataType !== COMMAND_COLUMN_DATA_TYPE && + row.data[key].column?.dataType !== SETTING_COLUMN_DATA_TYPE) + .map((key: string) => { + const cell: VisualDataCell = row.data[key]; + return
    +
    + +
    + {cell.getEditor(cell)} +
    +
    +
    ; + }) + } +
    ; + } + + function saveRow(e: MouseEvent, row: VisualData) { + const oldValues: { [key: string]: any } = {}; + const newValues: { [key: string]: any } = {}; + const columns: DataColumn[] = []; + const cells: VisualDataCell[] = []; + // 帮助组件上下文 + const editors: { [key: string]: any } = {}; + Object.keys(row.data) + .filter((key: string) => row.data[key].column?.dataType !== COMMAND_COLUMN_DATA_TYPE && + row.data[key].column?.dataType !== SETTING_COLUMN_DATA_TYPE) + .forEach((key: string) => { + const cell: VisualDataCell = row.data[key]; + oldValues[key] = cell.data; + useEditComposition.endEditByCell(cell); + newValues[key] = cell.data; + columns.push(cell.column as DataColumn); + cells.push(cell); + editors[key] = cell.column?.editor?.context?.editor; + }); + context.emit('endEditRow', { + row, + cells, + columns, + oldValues, + newValues, + editors + }); + useEditComposition.showRowEditPanel.value = false; + } + + function renderRowEditPanel() { + const currentRowId = useSelectionComposition.currentSelectedDataId.value; + const currentRow = visibleDatas.value.find((visibleData: VisualData) => visibleData.raw[props.idField] === currentRowId); + return currentRow ? + {{ + default: () => renderRowEditPanelContent(currentRow as VisualData), + footerTemplate: () => <> + { + useEditComposition.showRowEditPanel.value = false; + }}>取消 + { + saveRow(e, currentRow) + }}>确定 + + }} + : null; + } + return () => { return ( -
    - {gridContentRef.value && props.enableFilter && renderFilterPanel()} - {gridContentRef.value && renderGroupPanel()} - {gridContentRef.value && shouldShowHeader.value && renderGridHeader()} -
    mouseInContent.value = true} - onMouseleave={() => mouseInContent.value = false}> - {gridContentRef.value && renderDataGridSidebar(visibleDatas)} - {gridContentRef.value && renderDataArea()} - {gridContentRef.value && shouldRenderEmptyContent.value && renderEmpty()} - {gridContentRef.value && renderHorizontalScrollbar()} - {gridContentRef.value && renderVerticalScrollbar()} -
    - {gridContentRef.value && renderDataGridSummary()} - {(shouldRenderPagination.value || showSelection.value) && -
    - {showSelection.value &&
    -
    - {'已选:' + selectedValues.value.length} 条 -
    -
    } - {shouldRenderPagination.value && renderDataGridPagination()} + <> +
    + {gridContentRef.value && props.enableFilter && renderFilterPanel()} + {gridContentRef.value && renderGroupPanel()} + {gridContentRef.value && shouldShowHeader.value && renderGridHeader()} +
    { + e.stopPropagation(); + mouseInContent.value = true; + useVirtualScrollComposition.onTouchstartScrollThumb(e, gridContentRef); + }} + onTouchend={(e: MouseEvent) => { + // e.stopPropagation(); + mouseInContent.value = false; + }} + onMouseover={(e: MouseEvent) => { e.stopPropagation(); mouseInContent.value = true }} + onMouseleave={(e: MouseEvent) => { e.stopPropagation(); mouseInContent.value = false }} + > + {gridContentRef.value && renderDataGridSidebar(visibleDatas)} + {gridContentRef.value && renderDataArea()} + {gridContentRef.value && shouldRenderEmptyContent.value && renderEmpty()} + {gridContentRef.value && renderHorizontalScrollbar()} + {gridContentRef.value && renderVerticalScrollbar()}
    - } - {renderGridColumnResizeOverlay()} - {isDisabled.value && renderDisableMask()} -
    + {gridContentRef.value && renderDataGridSummary()} + {(shouldRenderPagination.value || showSelection.value) && +
    + {showSelection.value &&
    +
    + {'已选:' + selectedValues.value.length} 条 +
    +
    } + {shouldRenderPagination.value && renderDataGridPagination()} +
    + } + {renderGridColumnResizeOverlay()} + {isDisabled.value && renderDisableMask()} +
    + {isMobilePhone() && renderRowEditPanel()} + ); }; } diff --git a/packages/ui-vue/components/data-grid/src/data-grid.css b/packages/ui-vue/components/data-grid/src/data-grid.css index 8ba1e817b769f6c0f80b5a909866af44efb97f94..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/packages/ui-vue/components/data-grid/src/data-grid.css +++ b/packages/ui-vue/components/data-grid/src/data-grid.css @@ -1,53 +0,0 @@ -.f-datagrid-norecords-content { - height: 120px; - padding: 80px 0 0; - text-align: center; - color: rgba(0, 0, 0, 0.25); - background: url(./table-norecords.png) top center no-repeat; - position: absolute; - top: 50%; - margin: -60px 0 0; - line-height: 40px; - font-size: 1rem; -} - -.f-datagrid-norecords { - position: absolute !important; - margin: 0px; - left: 0px; - z-index: 0; - justify-content: center; - align-items: center; - pointer-events: none; - height: 100%; - width: 100%; -} - -.fv-grid-disable { - position: absolute; - height: 100%; - width: 100%; - z-index: 3; - background: #fff; - opacity: 0.3; -} -.fv-grid-header-corner .custom-control.custom-checkbox { - padding-left: 0.5rem; - margin: 0; -} - -.f-checkradio-single .custom-control-label::after, .f-checkradio-single .custom-control-label::before { - top: 3px; -} - -.fv-grid-header-cell.fv-grid-header-cell-border::after { - background-color: transparent; -} - -.fv-grid-cell .custom-control.custom-checkbox { - margin-bottom: 0; -} - -.fv-grid-cell .custom-checkbox .custom-control-label::before { - top:2px; -} diff --git a/packages/ui-vue/components/data-grid/src/data-grid.props.ts b/packages/ui-vue/components/data-grid/src/data-grid.props.ts index 61af658e7bc743a506b88826af397c9c9d2bfe19..daa153da0e270af1fe09aea6e2e4061643fd37ff 100644 --- a/packages/ui-vue/components/data-grid/src/data-grid.props.ts +++ b/packages/ui-vue/components/data-grid/src/data-grid.props.ts @@ -214,6 +214,8 @@ export type RowNumberOptions = ExtractPropTypes; export const rowOptions = { /** 自定义行样式 */ customRowStyle: { type: Function, default: () => { } }, + /** 自定义单元格样式 */ + customCellStyle: { type: Function, default: () => { } }, /** 自定义行状态 */ customRowStatus: { type: Function, default: () => { } }, /** 禁止行选中表达式 */ @@ -385,8 +387,8 @@ export const dataGridProps = { rowNumber: { type: Object as PropType, default: { enable: true, - width: 32, - showEllipsis:true, + width: 36, + showEllipsis: true, heading: '序号' } }, @@ -439,7 +441,8 @@ export const dataGridProps = { /** 宽度 */ width: { type: Number, default: -1 }, /** 空数据模板 */ - emptyTemplate: { type: Object as PropType VNode | string)> } + emptyTemplate: { type: Object as PropType VNode | string)> }, + beforeUpdate: { type: Function as PropType<(context: any) => Promise | any> } } as Record; export type DataGridProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/data-grid/src/designer/grid-field-editor.props.ts b/packages/ui-vue/components/data-grid/src/designer/grid-field-editor.props.ts index 92fd89e779659b3a196397c721cec4aff0df3a60..c20553efe2a9b93dbbd3c18a57a4ed89e27b937b 100644 --- a/packages/ui-vue/components/data-grid/src/designer/grid-field-editor.props.ts +++ b/packages/ui-vue/components/data-grid/src/designer/grid-field-editor.props.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import { ExtractPropTypes } from 'vue'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapper } from '../schema/schema-mapper'; import { schemaResolver } from "../schema/schema-resolver"; import gridFieldEditorSchema from '../schema/grid-field-editor.schema.json'; @@ -33,3 +33,9 @@ export const gridFieldEditorProps = { export type GridFieldEditorProps = ExtractPropTypes; export const gridFieldEditorPropsResolver = createPropsResolver(gridFieldEditorProps, gridFieldEditorSchema, schemaMapper, schemaResolver); +export const gridFieldPropsResolverGenerator = getPropsResolverGenerator( + gridFieldEditorProps, + gridFieldEditorSchema, + schemaMapper, + schemaResolver +); \ No newline at end of file diff --git a/packages/ui-vue/components/data-grid/src/locales/ui/en.json b/packages/ui-vue/components/data-grid/src/locales/ui/en.json index 0a50ec6f7f3569f2503b6deffe8afb5dd3f23b69..b1a62e3dc4148939dc49bcc2cade2bc6e516d225 100644 --- a/packages/ui-vue/components/data-grid/src/locales/ui/en.json +++ b/packages/ui-vue/components/data-grid/src/locales/ui/en.json @@ -45,7 +45,8 @@ "sevenDays": "Seven Days", "oneMonth": "One Month", "threeMonths": "Three Months", - "sixMonths": "Six Months" + "sixMonths": "Six Months", + "searchBoxPlaceholder": "Please enter a keyword" }, "settings": { "visible": "Display Columns", diff --git a/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHS.json b/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHS.json index 9066c3393e6cb723fab5a5410539f61953c9a8d5..a660e1c971a4cd500291b89199604d6b2bbae508 100644 --- a/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHS.json +++ b/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHS.json @@ -45,7 +45,8 @@ "sevenDays": "七天", "oneMonth": "一个月", "threeMonths": "三个月", - "sixMonths": "半年" + "sixMonths": "半年", + "searchBoxPlaceholder": "请输入关键词" }, "settings": { "visible": "显示列", diff --git a/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHT.json b/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHT.json index 39ecc8c2cbb8ed6ec3c73339addc32943edf80b3..623dbab7ad7bbce5ccad4255b02e951f7587b5c5 100644 --- a/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHT.json +++ b/packages/ui-vue/components/data-grid/src/locales/ui/zh-CHT.json @@ -45,7 +45,8 @@ "sevenDays": "七天", "oneMonth": "一個月", "threeMonths": "三個月", - "sixMonths": "半年" + "sixMonths": "半年", + "searchBoxPlaceholder": "請輸入關鍵詞" }, "settings": { "visible": "顯示列", diff --git a/packages/ui-vue/components/data-grid/src/property-config/data-grid-column.property-config.ts b/packages/ui-vue/components/data-grid/src/property-config/data-grid-column.property-config.ts index 478ba3789d7e1952e4c8a90b18597969b68b2af1..8218c194d4cb68fbbdd18286f22565b533fcf7c4 100644 --- a/packages/ui-vue/components/data-grid/src/property-config/data-grid-column.property-config.ts +++ b/packages/ui-vue/components/data-grid/src/property-config/data-grid-column.property-config.ts @@ -39,7 +39,7 @@ export class DataGriColumnProperty extends BaseControlProperty { const extendProperties = { formatterEnumData: { description: "", - title: "数据", + title: "枚举数据", type: "array", visible: !!isVisibleEnum, $converter: '/converter/change-formatter-enum.converter', @@ -70,6 +70,7 @@ export class DataGriColumnProperty extends BaseControlProperty { ); // 编辑器 this.getFieldEditorProperties(propertyData, gridData); + this.propertyConfig.categories['templateFormat'] = this.getColumnTemplate(propertyData); // 列模板或者列格式化 if (propertyData.formatter?.type !== 'multilingual') { this.propertyConfig.categories['formatter'] = this.getTemplateProperties(propertyData); @@ -78,6 +79,60 @@ export class DataGriColumnProperty extends BaseControlProperty { this.getEventPropConfig(propertyData); return this.propertyConfig; } + getColumnTemplate(propertyData: any): any { + const displayColumnTemplate = (propertyData.columnTemplateType && propertyData.columnTemplateType === 'custom') + || (!propertyData.columnTemplateType && propertyData.columnTemplate && !propertyData.onClickLinkCommand); + const self = this; + return { + title: '列模板', + description: '用于配置自定义列模板以及内置的格式化模板,如超链接、图片', + properties: { + columnTemplateType: { + description: '列模板类型', + title: '列模板类型', + $converter: '/converter/change-formatter-type.converter', + type: 'enum', + editor: { + data: [ + { id: 'default', name: '无' }, + { id: 'hyperlink', name: '超链接' }, + { id: 'custom', name: '自定义' } + ] + } + }, + columnTemplate: { + description: '列模板', + title: '列模板', + type: 'string', + visible: !!displayColumnTemplate, + editor: { + type: "code-editor", + language: "html", + } + }, + }, + setPropertyRelates(changeObject, prop, paramters: any) { + if (!changeObject) { + return; + } + switch (changeObject && changeObject.propertyID) { + case 'columnTemplateType': { + if (changeObject.propertyValue === 'custom' && prop.columnTemplate) { + self.notifyService.warning({ position: 'top-center', message: '注意:已自定义列模板,【列格式化】【悬浮提示】【数据水平对齐方式】【数据垂直对齐方式】等列属性会失效' }); + } + break; + }; + case 'columnTemplate': { + if (changeObject.propertyValue) { + self.notifyService.warning({ position: 'top-center', message: '注意:已自定义列模板,【列格式化】【悬浮提示】【数据水平对齐方式】【数据垂直对齐方式】等列属性会失效' }); + } + break; + } + } + self.getFormDesignerInstance()?.reloadPropertyPanel(); + } + }; + } /** * 枚举项编辑器 @@ -123,13 +178,32 @@ export class DataGriColumnProperty extends BaseControlProperty { return false; } private getEventPropConfig(propertyData: any) { - let events: { label: string, name: string }[] = [ - { + const displayHyperlink = propertyData.columnTemplateType === 'hyperlink' || + (!propertyData.columnTemplateType + && propertyData.columnTemplate + && propertyData.onClickLinkCommand); + let events: { label: string, name: string }[] = []; + if (propertyData.editor?.type === 'combo-list') { + events.push( + { + label: 'onChange', + name: '值变化事件' + }, + // { + // label: 'onClear', + // name: '清空事件' + // }, + { + label: 'beforeOpen', + name: '下拉面板前事件' + }); + } + if (displayHyperlink) { + events.push({ label: 'onClickLinkCommand', name: '超链接事件' - } - ]; - + }); + } // 列编辑器事件 if (propertyData?.editor?.type === 'lookup') { events = [...events, ...LookupEvents]; @@ -138,6 +212,10 @@ export class DataGriColumnProperty extends BaseControlProperty { } } + if (propertyData?.editor?.type === 'date-picker') { + events = [{ label: 'onDatePicked', name: '日期选择后事件' }]; + } + const self = this; const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); const properties = self.createBaseEventProperty(initialData); @@ -164,12 +242,25 @@ export class DataGriColumnProperty extends BaseControlProperty { // 同步超链接模板 // 替换自定义模板 propertyData.columnTemplate = ` - {{rowData.${propertyData.field}}} - `; + @click="(ctx)=>viewModel.current.${propertyData.onClickLinkCommand}({id: rowData.id,context: ctx})"> + {{rowData.${propertyData.field}}} + `; // 刷新属性面板 self.getFormDesignerInstance()?.reloadPropertyPanel(); } + if (!propertyData.onClickLinkCommand && propertyData.columnTemplate && propertyData.columnTemplateType === 'hyperlink') { + propertyData.columnTemplate = ` + {{rowData.${propertyData.field}}} + `; + // 刷新属性面板 + self.getFormDesignerInstance()?.reloadPropertyPanel(); + } + if (propertyData.editor?.beforeOpen !== propertyData.beforeOpen) { + propertyData.editor.beforeOpen = propertyData.beforeOpen; + } + if (propertyData.editor?.onChange !== propertyData.onChange) { + propertyData.editor.onChange = propertyData.onChange; + } } }; } @@ -246,11 +337,11 @@ export class DataGriColumnProperty extends BaseControlProperty { type: 'boolean', visible: gridData.type !== 'tree-grid' }, - // draggable: { - // description: '允许列拖拽', - // title: '启用拖拽', - // type: 'boolean' - // }, + draggable: { + description: '允许列拖拽', + title: '启用拖拽', + type: 'boolean' + }, width: { description: '列宽', title: '列宽', @@ -297,16 +388,20 @@ export class DataGriColumnProperty extends BaseControlProperty { ] } }, - columnTemplate: { - description: '列模板', - title: '列模板', - type: 'string', - refreshPanelAfterChanged: true, - editor: { - type: "code-editor", - language: "html", - } - }, + // formatterType: { + // title: '启用列格式化', + // type: 'enum', + // $converter: '/converter/change-formatter-type.converter', + // description: '启用列格式化', + // refreshPanelAfterChanged: true, + // editor: { + // data: [ + // { id: 'default', name: '默认格式化' }, + // { id: 'custom', name: '列模板' }, + // { id: 'hyperlink', name: '超链接模板' } + // ] + // }, + // }, summaryType: { description: '合计类型', title: '合计类型', @@ -320,13 +415,13 @@ export class DataGriColumnProperty extends BaseControlProperty { }, refreshPanelAfterChanged: true }, - // enableGroup: { - // description: '启用分组', - // title: '分组', - // visible: !!this.dataGridProps?.group?.enable, - // type: 'boolean', - // refreshPanelAfterChanged: true - // } + enableGroup: { + description: '启用分组', + title: '分组', + visible: !!this.dataGridProps?.group?.enable, + type: 'boolean', + refreshPanelAfterChanged: true + } }, setPropertyRelates(changeObject, prop, paramters: any) { if (!changeObject) { @@ -351,7 +446,7 @@ export class DataGriColumnProperty extends BaseControlProperty { case 'columnTemplate': { // 提示以列模板为主 if (changeObject.propertyValue) { - self.notifyService.warning({ position: 'top-center', message: '注意:已自定义列模板,【列格式化】【悬浮提示】【数据水平对齐方式】【数据垂直对齐方式】等列属性会失效' }); + // self.notifyService.warning({ position: 'top-center', message: '注意:已自定义列模板,【列格式化】【悬浮提示】【数据水平对齐方式】【数据垂直对齐方式】等列属性会失效' }); } break; }; @@ -365,7 +460,7 @@ export class DataGriColumnProperty extends BaseControlProperty { } } } - }; + } } private getFieldEditorProperties(propertyData: any, gridData: any) { if (gridData.fieldEditable && propertyData.editor) { @@ -379,10 +474,17 @@ export class DataGriColumnProperty extends BaseControlProperty { } } + // private isDefaultFormatter(propertyData: any) { + // return propertyData.formatterType === 'default' || + // // 初始化该属性不存在,代表启用默认格式化 + // !propertyData.formatterType && + // !propertyData.columnTemplate; + // } + private getTemplateProperties(propertyData: any) { const self = this; const hasOwnProps = Object.prototype.hasOwnProperty; - const formatterType = { + const typeFormatter = { enum: [ { id: 'enum', name: '枚举' }, // { id: 'custom', name: '自定义模板' }, @@ -418,45 +520,25 @@ export class DataGriColumnProperty extends BaseControlProperty { ] }; return { - title: '列格式化', + title: '格式化', description: '', properties: { type: { description: '', title: '类型', type: 'enum', + // visible: isDefaultFormatter, $converter: '/converter/change-formatter.converter', refreshPanelAfterChanged: true, editor: { - data: formatterType[propertyData.dataType] + data: typeFormatter[propertyData.dataType] } }, - // data: { - // description: '', - // title: '选项', - // type: 'enum', - // $converter: '/converter/change-formatter.converter', - // visible: propertyData.formatter.type === 'enum', - // // parentPropertyID: 'formatter', - // // refreshPanelAfterChanged: true, - // }, - // customFormat: { - // title: '自定义模板', - // type: 'string', - // visible: !!propertyData.formatter && propertyData.formatter.type === 'custom', - // $converter: '/converter/change-formatter.converter', - // description: '自定义模板', - // refreshPanelAfterChanged: true, - // editor: { - // type: "code-editor", - // language: "html", - // } - // }, trueText: { title: '布尔为true时文本', $converter: '/converter/change-formatter.converter', type: 'string', - visible: !!propertyData.formatter && propertyData.formatter.type === 'boolean', + visible: propertyData.formatter?.type === 'boolean', refreshPanelAfterChanged: true, description: '布尔为true时文本' }, @@ -464,7 +546,7 @@ export class DataGriColumnProperty extends BaseControlProperty { title: '布尔为false时文本', $converter: '/converter/change-formatter.converter', type: 'string', - visible: !!propertyData.formatter && propertyData.formatter.type === 'boolean', + visible: propertyData.formatter?.type === 'boolean', refreshPanelAfterChanged: true, description: '布尔为false时文本' }, @@ -472,7 +554,7 @@ export class DataGriColumnProperty extends BaseControlProperty { title: '前缀', $converter: '/converter/change-formatter.converter', type: 'string', - visible: !!propertyData.formatter && propertyData.formatter.type === 'number', + visible: propertyData.formatter?.type === 'number', refreshPanelAfterChanged: true, description: '前缀' }, @@ -480,7 +562,7 @@ export class DataGriColumnProperty extends BaseControlProperty { title: '后缀', $converter: '/converter/change-formatter.converter', type: 'string', - visible: !!propertyData.formatter && propertyData.formatter.type === 'number', + visible: propertyData.formatter?.type === 'number', refreshPanelAfterChanged: true, description: '后缀' }, @@ -488,7 +570,7 @@ export class DataGriColumnProperty extends BaseControlProperty { title: '精度', $converter: '/converter/change-formatter.converter', type: 'number', - visible: !!propertyData.formatter && propertyData.formatter.type === 'number', + visible: propertyData.formatter?.type === 'number', refreshPanelAfterChanged: true, description: '精度' }, @@ -496,7 +578,7 @@ export class DataGriColumnProperty extends BaseControlProperty { title: '小数分隔符', $converter: '/converter/change-formatter.converter', type: 'string', - visible: !!propertyData.formatter && propertyData.formatter.type === 'number', + visible: propertyData.formatter?.type === 'number', refreshPanelAfterChanged: true, description: '小数分隔符' }, @@ -504,14 +586,15 @@ export class DataGriColumnProperty extends BaseControlProperty { title: '千分位', $converter: '/converter/change-formatter.converter', type: 'string', - visible: !!propertyData.formatter && propertyData.formatter.type === 'number', + visible: propertyData.formatter?.type === 'number', refreshPanelAfterChanged: true, description: '千分位' }, dateFormat: { title: '日期格式', $converter: '/converter/change-formatter.converter', - visible: !!propertyData.formatter && (propertyData.formatter.type === 'date' || propertyData.formatter.type === 'datetime'), + visible: propertyData.formatter?.type === 'date' + || propertyData.formatter?.type === 'datetime', refreshPanelAfterChanged: true, description: '日期格式', type: 'enum', @@ -559,9 +642,9 @@ export class DataGriColumnProperty extends BaseControlProperty { case 'type': { // 清空列模板 if (changeObject.propertyValue !== 'none' && prop.columnTemplate) { - prop.columnTemplate = ''; + // prop.columnTemplate = ''; // 提示以列格式化为主 - self.notifyService.warning({ position: 'top-center', message: '注意:已设置列格式化,【列模板】被清空且会失效' }); + // self.notifyService.warning({ position: 'top-center', message: '注意:已设置列格式化,【列模板】被清空且会失效' }); } if (changeObject.propertyValue === 'boolean') { if (!hasOwnProps.call(prop.formatter, 'trueText')) { @@ -698,7 +781,7 @@ export class DataGriColumnProperty extends BaseControlProperty { } } }; - const notSupportProperty = ['id', 'type', 'class', 'style', 'binding', 'label', 'responseLayout', 'visible']; + const notSupportProperty = ['id', 'type', 'class', 'style', 'binding', 'label', 'responseLayout', 'visible', 'fill']; const categories = editorTypeConfig.getGridFieldEdtiorPropConfig(propertyData); Object.keys(categories).map((categoryId: string) => { const propertyCategory = categories[categoryId]; diff --git a/packages/ui-vue/components/data-grid/src/property-config/data-grid.property-config.ts b/packages/ui-vue/components/data-grid/src/property-config/data-grid.property-config.ts index f2a739f5209b8d659c8599fbe4dd094c3e05ab4e..49a150a23883d8f10717e146b5d8456a7777e70a 100644 --- a/packages/ui-vue/components/data-grid/src/property-config/data-grid.property-config.ts +++ b/packages/ui-vue/components/data-grid/src/property-config/data-grid.property-config.ts @@ -27,7 +27,7 @@ export class DataGridProperty extends BaseControlProperty { // 外观 this.getAppearanceProperties(propertyData); // 操作列 - this.propertyConfig.categories['command'] = useCommandOption().getCommandColumnProperties(propertyData); + this.propertyConfig.categories['command'] = useCommandOption(this).getCommandColumnProperties(propertyData); // 填充列宽,仅支持平分列宽 this.propertyConfig.categories['column'] = useColumnOption().getColumnOptionProperties(propertyData); // 行配置 增加行和单元格自定义样式 @@ -35,7 +35,7 @@ export class DataGridProperty extends BaseControlProperty { // 合计行 this.propertyConfig.categories['summary'] = useSummary().getSummaryProperties(propertyData); // 分组配置 - // this.propertyConfig.categories['group'] = useGroup().getGroupProperties(propertyData); + this.propertyConfig.categories['group'] = useGroup().getGroupProperties(propertyData); // 排序和过滤 this.propertyConfig.categories['sort'] = useSort().getSort(propertyData); this.propertyConfig.categories['filter'] = useFilter().getFilter(propertyData); @@ -156,6 +156,7 @@ export class DataGridProperty extends BaseControlProperty { if (propertyData.command) { propertyData.command.onClickEditCommand = propertyData.onClickEditCommand; propertyData.command.onClickDeleteCommand = propertyData.onClickDeleteCommand; + propertyData.command.onHandleAction = propertyData.onHandleAction; } } diff --git a/packages/ui-vue/components/data-grid/src/schema/callback-resolvers.ts b/packages/ui-vue/components/data-grid/src/schema/callback-resolvers.ts index 9b2ea06da22d4ce47bb297843adc95db3d5ffb86..4c12f788778aa5d9aea56ad17fd0eca45f923168 100644 --- a/packages/ui-vue/components/data-grid/src/schema/callback-resolvers.ts +++ b/packages/ui-vue/components/data-grid/src/schema/callback-resolvers.ts @@ -5,7 +5,10 @@ export function createDataGridCallbackResolver() { function resolve(viewSchema: Record, caller: Caller) { const callbacks: Record = {}; callbacks.beforeEditCell = (context: { row: VisualData, cell: VisualDataCell, rawData: any, column: DataColumn; }): Promise => { - return caller.call('beforeEditCell', [context, viewSchema]); + return caller.call('beforeEditCell', viewSchema, [context, viewSchema]); + }; + callbacks.beforeUpdate = (context: any): Promise => { + return caller.call('beforeUpdate', viewSchema, [context, viewSchema]); }; return callbacks; } diff --git a/packages/ui-vue/components/data-grid/src/schema/data-grid-column.schema.json b/packages/ui-vue/components/data-grid/src/schema/data-grid-column.schema.json index c87b6e682a5cdfbc469db2c2dec2459731173754..e0ed102f1817fa60909914898fca731d1f398d80 100644 --- a/packages/ui-vue/components/data-grid/src/schema/data-grid-column.schema.json +++ b/packages/ui-vue/components/data-grid/src/schema/data-grid-column.schema.json @@ -145,10 +145,10 @@ "default": 120 }, "formatter": { - "description":"列格式化", + "description": "列格式化", "type": "object", "default": { - "type":"none", + "type": "none", "customFormat": "", "trueText": "是", "falseText": "否", @@ -156,7 +156,7 @@ } }, "columnTemplate": { - "description":"列模板", + "description": "列模板", "type": "string", "default": "" }, @@ -182,6 +182,15 @@ "description": "", "type": "boolean", "default": false + }, + "onClickLinkCommand": { + "description": "超链接事件", + "type": "string", + "default": "" + }, + "columnTemplateType": { + "description": "列模板类型", + "type": "string" } }, "required": [ diff --git a/packages/ui-vue/components/data-grid/src/schema/data-grid.schema.json b/packages/ui-vue/components/data-grid/src/schema/data-grid.schema.json index 0c1e6ae86982144a7e8160410214557a59a93e3d..c7d7ae67f77d90693762c680671deb6bd1b02d2c 100644 --- a/packages/ui-vue/components/data-grid/src/schema/data-grid.schema.json +++ b/packages/ui-vue/components/data-grid/src/schema/data-grid.schema.json @@ -147,6 +147,10 @@ "command": { "type": "object", "properties": { + "enableType": { + "type": "string", + "default": "unable" + }, "enable": { "description": "", "type": "boolean", @@ -154,18 +158,7 @@ }, "commands": { "type": "array", - "default": [ - { - "text": "编辑", - "type": "primary", - "command": "edit" - }, - { - "text": "删除", - "type": "danger", - "command": "remove" - } - ] + "default": [] }, "formatter": { "description": "自定义操作列模板", @@ -186,6 +179,16 @@ "description": "", "type": "string", "default": "" + }, + "onHandleAction": { + "description": "", + "type": "string", + "default": "" + }, + "count": { + "description": "", + "type": "number", + "default": 2 } } }, @@ -815,6 +818,11 @@ "description": "", "type": "string", "default": "" + }, + "beforeUpdate": { + "description": "", + "type": "string", + "default": "" } }, "required": [ diff --git a/packages/ui-vue/components/data-view/components/column-filter/column-filter-container.component.tsx b/packages/ui-vue/components/data-view/components/column-filter/column-filter-container.component.tsx index 426441b5000bf9508eaa2547968219763ca0420e..a81e41cb20691c2ef89d3de53081bce097a89810 100644 --- a/packages/ui-vue/components/data-view/components/column-filter/column-filter-container.component.tsx +++ b/packages/ui-vue/components/data-view/components/column-filter/column-filter-container.component.tsx @@ -15,10 +15,9 @@ */ import { computed, Ref, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; import { CapsuleItem } from '@farris/ui-vue/components/capsule'; import { Tag } from '@farris/ui-vue/components/tags'; -import { DataViewOptions } from '@farris/ui-vue/components/data-view'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { HeaderCell, HeaderCellStatus, @@ -31,7 +30,8 @@ import { UseFilter, UseSelection, UseSidebar, - VisualData + VisualData, + DataViewOptions } from '../../composition/types'; import { usePagination } from '../../composition/pagination/use-pagination'; @@ -62,15 +62,14 @@ export default function ( 'Lbracket': string, 'Rbracket': string }[]> = ref([]); - const { t: getLocaleValue } = useI18n(); const items: CapsuleItem[] = [ { - name: getLocaleValue('datagrid.settings.asc'), + name: LocaleService.getLocaleValue('datagrid.settings.asc'), value: 'asc', icon: 'f-icon f-icon-col-ascendingorder' }, - { name: getLocaleValue('datagrid.settings.summaryNone'), value: 'none' }, + { name: LocaleService.getLocaleValue('datagrid.settings.summaryNone'), value: 'none' }, { - name: getLocaleValue('datagrid.settings.desc'), value: 'desc', + name: LocaleService.getLocaleValue('datagrid.settings.desc'), value: 'desc', icon: 'f-icon f-icon-col-descendingorder' } ]; @@ -221,29 +220,29 @@ export default function (
    }
    -
    {getLocaleValue('datagrid.filter.title')}
    +
    {LocaleService.getLocaleValue('datagrid.filter.title')}
    {useColumnFilterComposition.getFilterEditor(headerCell)}
    diff --git a/packages/ui-vue/components/data-view/components/column-filter/date-filter-editor.component.tsx b/packages/ui-vue/components/data-view/components/column-filter/date-filter-editor.component.tsx index cd1a827f51eb561b60f3ebc0abacd07d973ddf0a..dcf209182c720581ca30bcfa9dbe2146b57dd117 100644 --- a/packages/ui-vue/components/data-view/components/column-filter/date-filter-editor.component.tsx +++ b/packages/ui-vue/components/data-view/components/column-filter/date-filter-editor.component.tsx @@ -1,6 +1,6 @@ import { computed, ref } from "vue"; -import { useI18n } from 'vue-i18n'; import FDatePicker from '@farris/ui-vue/components/date-picker'; +// import { LocaleService } from '@farris/ui-vue/components/locale'; import { HeaderCell } from "../../composition/types"; export default function (headerCell: HeaderCell) { const filterValue = ref(headerCell.column?.filter || ''); diff --git a/packages/ui-vue/components/data-view/components/column-filter/numeric-filter-editor.component.tsx b/packages/ui-vue/components/data-view/components/column-filter/numeric-filter-editor.component.tsx index 45da249608502f78c139c91d3611f9f67018e525..d4327ce5660bdcf355f8b8bd324c4b8f58c101eb 100644 --- a/packages/ui-vue/components/data-view/components/column-filter/numeric-filter-editor.component.tsx +++ b/packages/ui-vue/components/data-view/components/column-filter/numeric-filter-editor.component.tsx @@ -21,6 +21,12 @@ export default function (headerCell: HeaderCell) { } return
    - +
    ; } diff --git a/packages/ui-vue/components/data-view/components/column-filter/text-filter-editor.component.tsx b/packages/ui-vue/components/data-view/components/column-filter/text-filter-editor.component.tsx index 84f4a1fdf2a6d08082899d744fcd487c970709dc..de0cff991d2a6139214c41d54ad8d641f0f8b447 100644 --- a/packages/ui-vue/components/data-view/components/column-filter/text-filter-editor.component.tsx +++ b/packages/ui-vue/components/data-view/components/column-filter/text-filter-editor.component.tsx @@ -2,6 +2,7 @@ import { ref, Ref } from "vue"; import { FSearchBox } from "@farris/ui-vue/components/search-box"; import { FTags, Tag } from "@farris/ui-vue/components/tags"; import { HeaderCell, UseDataView, UseFilterHistory, UseVirtualScroll } from "../../composition/types"; +import { LocaleService } from "@farris/ui-vue/components/locale"; export default function ( headerCell: HeaderCell, @@ -52,6 +53,7 @@ export default function ( return
    { return { margin: '0px' @@ -46,7 +44,6 @@ export default function ( useSorterComposition, useVirtualScrollComposition, modalService, - vueI18nComposition ); openSettingPanel(); } diff --git a/packages/ui-vue/components/data-view/components/column-setting/column-setting.component.tsx b/packages/ui-vue/components/data-view/components/column-setting/column-setting.component.tsx index 2c509974032c2c2403e9060977c25585d7032ff6..8a40489f8e6b2659ab73584fb5f3ed028bc75aeb 100644 --- a/packages/ui-vue/components/data-view/components/column-setting/column-setting.component.tsx +++ b/packages/ui-vue/components/data-view/components/column-setting/column-setting.component.tsx @@ -3,13 +3,14 @@ import { cloneDeep } from 'lodash-es'; import FTransfer from '@farris/ui-vue/components/transfer'; import FTabs, { FTabPage } from '@farris/ui-vue/components/tabs'; import { FOrder, OrderedItem, SortType } from '@farris/ui-vue/components/order'; -import FConditionList, { Condition, FieldConfig, ConditionValue } from '@farris/ui-vue/components/condition'; -import { EditorConfig } from '@farris/ui-vue/components/dynamic-form'; +import FConditionList, { Condition, ConditionValue, FieldConfig } from '@farris/ui-vue/components/condition'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { DataColumn, DataViewOptions, UseColumn, UseDataView, UseFilter, UseFitColumn, UseSort, UseVirtualScroll } from '../../composition/types'; import { COMMAND_COLUMN_DATA_TYPE, SETTING_COLUMN_DATA_TYPE } from '../../composition/const'; +import { EditorConfig } from '@farris/ui-vue/components/dynamic-form'; type VisibleColumn = { id: string; name: string }; export default function ( @@ -19,8 +20,7 @@ export default function ( useFitColumnComposition: UseFitColumn, useSorterComposition: UseSort, useVirtualScrollComposition: UseVirtualScroll, - modalService: any, - vueI18nComposition: any + modalService: any ) { const identifyField = 'id'; const conditionListRef = ref(); @@ -146,12 +146,12 @@ export default function ( headerPrefix: () => ( ), default: () => [ - + { @@ -11,7 +10,7 @@ export default function (props: DataViewOptions, context: SetupContext) {
    { - getLocaleValue('datagrid.emptyMessage') + LocaleService.getLocaleValue('datagrid.emptyMessage') }
    } diff --git a/packages/ui-vue/components/data-view/components/editors/commands.component.tsx b/packages/ui-vue/components/data-view/components/editors/commands.component.tsx index a83a86de90409b5df281e1dc975aa632b4944865..06c9623bbc65d3f8fa28f1722d402000b00b7962 100644 --- a/packages/ui-vue/components/data-view/components/editors/commands.component.tsx +++ b/packages/ui-vue/components/data-view/components/editors/commands.component.tsx @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { DataColumn, DataColumnCommand, DataViewOptions, VisualData, VisualDataCell, VisualDataStatus } from '../../composition/types'; +import { DataColumnCommand, DataViewOptions, VisualData, VisualDataCell, VisualDataStatus } from '../../composition/types'; import FButton from '@farris/ui-vue/components/button'; -import { useI18n } from 'vue-i18n'; +import FButtonGroup from '@farris/ui-vue/components/button-group'; +import { LocaleService } from '@farris/ui-vue/components/locale'; export default function (props: DataViewOptions) { - const { t: getLocaleValue } = useI18n(); function shouldShowCurrentCommandButton(command: DataColumnCommand, visualDataRow: VisualData) { switch (command.command) { case 'edit': @@ -37,7 +37,7 @@ export default function (props: DataViewOptions) { case 'edit': visualDataRow.status === VisualDataStatus.editing; // 编辑回调事件 - props.commandOption?.onClickEditCommand?.(cell, visualDataRow); + props.commandOption?.onClickEditCommand?.(cell, visualDataRow, command, payload); break; case 'accept': case 'cancel': @@ -45,37 +45,37 @@ export default function (props: DataViewOptions) { break; case 'remove': // 删除回调事件 - props.commandOption?.onClickDeleteCommand?.(cell, visualDataRow); + props.commandOption?.onClickDeleteCommand?.(cell, visualDataRow, command, payload); break; } } function getCommandLcales(commandText: string) { - if(commandText === '编辑') { - return getLocaleValue('datagrid.commandColumn.edit'); + if (commandText === '编辑') { + return LocaleService.getLocaleValue('datagrid.commandColumn.edit'); } - if(commandText === '确定') { - return getLocaleValue('datagrid.commandColumn.accept'); + if (commandText === '确定') { + return LocaleService.getLocaleValue('datagrid.commandColumn.accept'); } - if(commandText === '取消') { - return getLocaleValue('datagrid.commandColumn.cancel'); + if (commandText === '取消') { + return LocaleService.getLocaleValue('datagrid.commandColumn.cancel'); } - if(commandText === '删除') { - return getLocaleValue('datagrid.commandColumn.remove'); + if (commandText === '删除') { + return LocaleService.getLocaleValue('datagrid.commandColumn.remove'); } return commandText; } - function renderCommandColumn(cell: VisualDataCell, visualDataRow: VisualData) { + function renderDefaultCommand(cell: VisualDataCell, visualDataRow: VisualData) { const { column } = cell; return column!.commands && column!.commands.map((command: DataColumnCommand, index: number) => { - if(!index) { + if (!index) { return shouldShowCurrentCommandButton(command, visualDataRow) && excuteCommand(command, payload, visualDataRow, cell)} - style={{ 'margin-bottom': '3px' }} - > {getCommandLcales(command.text)} ; + type={command.type} + size={command.size || 'small'} + onClick={(payload: MouseEvent) => excuteCommand(command, payload, visualDataRow, cell)} + style={{ 'margin-bottom': '3px' }} + > {getCommandLcales(command.text)} ; } return shouldShowCurrentCommandButton(command, visualDataRow) && { + return columnCommand.command; + }); + if (hasDefaultCommand) { + return renderDefaultCommand(cell, visualDataRow); + } + return { onClickButtonGroup(param.payload, param.button, visualDataRow, cell); }} + >; + } + return { renderCommandColumn }; } diff --git a/packages/ui-vue/components/data-view/components/pagination/data-grid-pagination.component.tsx b/packages/ui-vue/components/data-view/components/pagination/data-grid-pagination.component.tsx index 6464c31edc0a7171b754bba059ed480e636f523c..0653f6c893398f1e6f3d94ba2a077e25e55e40bf 100644 --- a/packages/ui-vue/components/data-view/components/pagination/data-grid-pagination.component.tsx +++ b/packages/ui-vue/components/data-view/components/pagination/data-grid-pagination.component.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ import { computed, onMounted, ref, SetupContext, watch } from 'vue'; -import { DataViewOptions, UseDataView, UsePagination, UseSelection, UseVirtualScroll } from '../../composition/types'; import FPagination from '@farris/ui-vue/components/pagination'; +import { useMobile } from '@farris/ui-vue/components/data-view'; +import { DataViewOptions, UseDataView, UsePagination, UseSelection, UseVirtualScroll } from '../../composition/types'; export default function ( props: DataViewOptions, @@ -25,11 +26,14 @@ export default function ( usePaginationComposition: UsePagination, useSelectionComposition: UseSelection ) { + const { isMobilePhone } = useMobile(); const { clearSelection, keepSelectingOnPaging } = useSelectionComposition; const { pageIndex, totalItems, updatePageSize } = dataView; const paginationRef = ref(); const { pageSize, pageList, showGotoPage, showPageIndex, showPageList, mode, disabled, shouldRenderPagination, showPageInfo } = usePaginationComposition; - + function paginationMode() { + return isMobilePhone() ? 'simple' : 'default'; + } function changePage(pageIndex: number, pageSize: number) { if (shouldRenderPagination.value && mode.value !== 'server') { dataView.navigatePageTo(pageIndex); @@ -63,7 +67,7 @@ export default function ( return ( + style={cellPosition(visualData, cell, cellPositionMap)}>
    {renderTreeNodeCellToggleIcon(visualData, cell)} {renderTreeNodeCellIcon(visualData, cell)} diff --git a/packages/ui-vue/components/data-view/components/sidebar/data-grid-sidebar.component.tsx b/packages/ui-vue/components/data-view/components/sidebar/data-grid-sidebar.component.tsx index d5f1ecec9791f938eb5ee4da56988d99a1bf42c4..1de7fbe5b90e30f6c69dcc3ad514c19c14ef2b6e 100644 --- a/packages/ui-vue/components/data-view/components/sidebar/data-grid-sidebar.component.tsx +++ b/packages/ui-vue/components/data-view/components/sidebar/data-grid-sidebar.component.tsx @@ -71,8 +71,8 @@ export default function ( 'text-truncate': props.rowNumber.showEllipsis, 'w-100': props.rowNumber.showEllipsis }}> - {/* {dataItem.dataIndex} */} - {dataItem.raw.__fv_visible_index__ + 1} + {dataItem.dataIndex} + {/* {dataItem.raw.__fv_visible_index__ + 1} */}
    ); diff --git a/packages/ui-vue/components/data-view/components/summary/data-grid-summary.component.tsx b/packages/ui-vue/components/data-view/components/summary/data-grid-summary.component.tsx index 3be8028bb9adbbb45c4c6a64b462197411737fd7..c83f9309c0f21fd500dc326f1f29fe3e7f2d9fb8 100644 --- a/packages/ui-vue/components/data-view/components/summary/data-grid-summary.component.tsx +++ b/packages/ui-vue/components/data-view/components/summary/data-grid-summary.component.tsx @@ -14,11 +14,10 @@ * limitations under the License. */ import { computed, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { DataColumn, DataViewOptions, UseColumn, UseDataView } from '../../composition/types'; export default function (props: DataViewOptions, dataView: UseDataView, useColumnComposition: UseColumn) { - const { t: getLocaleValue } = useI18n(); const { columnContext } = useColumnComposition; const summaryOptions = ref(props.summary); @@ -28,19 +27,16 @@ export default function (props: DataViewOptions, dataView: UseDataView, useColum return options && options.enable && options.groupFields && options.groupFields.length > 0; }); - const summaryTitle = computed(() => { - return { - width: 'auto' - }; - }); - + const summaryTitleStyle = computed(() => ({ + width: 'auto' + })); function renderDataGridSummary() { return ( shouldShowSummary.value && (
    - - {getLocaleValue('datagrid.summary.title')} + + {LocaleService.getLocaleValue('datagrid.summary.title')}
    {columnContext.value.summaryColumns.map((column: DataColumn) => { diff --git a/packages/ui-vue/components/data-view/composition/column/use-command-column.ts b/packages/ui-vue/components/data-view/composition/column/use-command-column.ts index 4c4d2a63ac9802731b2ccf4142b1878c4c123b5b..a7b4de8c191ca52aaf833c32712192ee1dcc5c4a 100644 --- a/packages/ui-vue/components/data-view/composition/column/use-command-column.ts +++ b/packages/ui-vue/components/data-view/composition/column/use-command-column.ts @@ -13,23 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Ref, ref } from 'vue'; -import { useI18n } from 'vue-i18n'; +import { computed, Ref, ref } from 'vue'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { DataColumn, DataViewOptions, UseCommandColumn, VisualData, VisualDataCell } from '../types'; import { COMMAND_COLUMN_FIELD, COMMAND_COLUMN_WIDTH, COMMAND_COLUMN_DATA_TYPE, COMMAND_COLUMN_FIXED } from '../const'; export function useCommandColumn(props: DataViewOptions): UseCommandColumn { - const { t: getLocaleValue } = useI18n(); const defaultColumnWidth = 150; const enableCommands = ref(props.commandOption?.enable || false); const commands = ref(props.commandOption?.commands || []); - + const commandColumnTitle = computed(() => LocaleService.getLocaleValue('datagrid.commandColumn.title')); function applyCommands(columns: Ref) { if (enableCommands.value) { const hasCommandColumn = columns.value.findIndex((column: DataColumn) => column.dataType === COMMAND_COLUMN_DATA_TYPE) > -1; if (!hasCommandColumn) { const commandColumn = { field: COMMAND_COLUMN_FIELD, - title: getLocaleValue('datagrid.commandColumn.title'), + title: commandColumnTitle.value, width: COMMAND_COLUMN_WIDTH, fixed: COMMAND_COLUMN_FIXED, dataType: COMMAND_COLUMN_DATA_TYPE, @@ -37,9 +36,14 @@ export function useCommandColumn(props: DataViewOptions): UseCommandColumn { resizable: false, halign: props.commandOption?.halign || 'left', visible: true, - formatter: props.commandOption.formatter ? (cell: VisualDataCell, row: VisualData) => + formatter: props.commandOption.formatter && typeof props.commandOption.formatter ? + (cell: VisualDataCell, row: VisualData) => props.commandOption.formatter(cell, row) : null } as DataColumn; + if(props.commandOption.formatter && typeof props.commandOption.formatter !== 'function') { + commandColumn.formatter = (cell: VisualDataCell, row: VisualData) => + props.commandOption.formatter(cell, row); + } columns.value.push(commandColumn as DataColumn); } } diff --git a/packages/ui-vue/components/data-view/composition/column/use-fit-column.ts b/packages/ui-vue/components/data-view/composition/column/use-fit-column.ts index 8cd20b09baf66eba3cb88bb3c7c7a3efea94d00c..f5b8bf73964a02dbad36ec90068f45370b4f5e1b 100644 --- a/packages/ui-vue/components/data-view/composition/column/use-fit-column.ts +++ b/packages/ui-vue/components/data-view/composition/column/use-fit-column.ts @@ -38,7 +38,7 @@ export function useFitColumn( const showRowCheckbox = computed(() => !props.hierarchy && (props.selection?.multiSelect || props.selection?.showCheckbox || false)); const sidebarColumnWidth = computed(() => - 0 + (showRowNumer.value ? props.rowNumber?.width || 32 : 0) + (showRowCheckbox.value ? defaultCheckboxWidth : 0) + 0 + (showRowNumer.value ? props.rowNumber?.width || 36 : 0) + (showRowCheckbox.value ? defaultCheckboxWidth : 0) ); // const fitColumns = ref(props.columnOption?.fitColumns || false); diff --git a/packages/ui-vue/components/data-view/composition/data/use-data-view.ts b/packages/ui-vue/components/data-view/composition/data/use-data-view.ts index 9f5ba6ba3dee0409f9c8d84743886d0a40fbc244..0d62f0588ea44ecc2c6baade783440db3b8f57d7 100644 --- a/packages/ui-vue/components/data-view/composition/data/use-data-view.ts +++ b/packages/ui-vue/components/data-view/composition/data/use-data-view.ts @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { computed, ref, watch } from 'vue'; -import { cloneDeep } from 'lodash-es'; -import { FNotifyService } from '@farris/ui-vue/components/notify'; -import { resolveField, useNumberFormat } from '@farris/ui-vue/components/common'; +import { computed, ref } from 'vue'; +import { cloneDeep, isUndefined } from 'lodash-es'; +import { NumberType, resolveField, useNumberFormat } from '@farris/ui-vue/components/common'; import { DataViewFilter, DataViewOptions, DataViewSorter, DataViewType, UseDataView, UseFilter, UseHierarchy, UseIdentify, VisualData @@ -24,6 +23,7 @@ import { import { useGroupData } from './use-group-data'; import { useHierarchyData } from './use-hierarchy-data'; import { useTreeData } from '../..'; +import { FNotifyService } from '@farris/ui-vue/components/notify'; export function useDataView( props: DataViewOptions, columnMaps: Map, @@ -31,7 +31,7 @@ export function useDataView( useHierarchyCompostion: UseHierarchy, useIdentifyCompostion: UseIdentify ): UseDataView { - const { toNumber } = useNumberFormat(); + const { toNumber, sum, toFixed } = useNumberFormat(); const collapseMap = new Map(); const filterMap = new Map(); const groupFilterMap = new Map(); @@ -64,7 +64,7 @@ export function useDataView( return options?.groupFields || []; }); - let summaries = new Map(); + let summaries = new Map(); const totalItems = ref(totalData.value.length); const pagination = ref(props.pagination); @@ -186,7 +186,7 @@ export function useDataView( } groupSummaryFields.value.forEach((summaryField: string) => { const summaryFieldValue = summaries.get(summaryField) || 0; - summaries.set(summaryField, summaryFieldValue + resolveField(dataItem, summaryField)); + summaries.set(summaryField, sum([summaryFieldValue, resolveField(dataItem, summaryField) || 0])); }); dataViewItems.push(dataItem); } @@ -196,7 +196,7 @@ export function useDataView( } groupSummaryFields.value.forEach((summaryField: string) => { const summaryFieldValue = summaries.get(summaryField) || 0; - summaries.set(summaryField, toNumber(summaryFieldValue.toFixed(2))); + summaries.set(summaryField, toFixed(summaryFieldValue as string, 2)); }); rawView.value = [...dataViewItems]; return dataViewItems; @@ -214,8 +214,18 @@ export function useDataView( }); }); + // function getVisibleDataItems() { + // return dataView.value.filter((dataItem: DataViewType['value'][number]) => { + // if (shouldStratifyData.value) { + // return dataItem.__fv_visible__ !== false && isVisibleInTree(dataItem); + // } + // return dataItem.__fv_visible__ !== false; + // }); + // } + function reOrderVisibleIndex() { - visibleDataItems.value.forEach((dataItem: any, index: number) => { + const visibleItems = visibleDataItems.value; + visibleItems.forEach((dataItem: any, index: number) => { dataItem.__fv_visible_index__ = index; return dataItem; }); @@ -256,6 +266,7 @@ export function useDataView( } return sortResult; }) : groupedData; + const pageSize = getPageSize(); dataView.value = sorteredData .map((dataItem: any, index: number) => { if (dataItem.__fv_data_grid_group_row__) { @@ -264,6 +275,7 @@ export function useDataView( } dataItem.__fv_index__ = index; dataItem.__fv_visible_index__ = index; + dataItem.__fv_data_index__ = index + 1 + (pageIndex.value - 1) * pageSize; return dataItem; }); return dataView.value; @@ -543,12 +555,12 @@ export function useDataView( for (const dataItem of totalData.value) { groupSummaryFields.forEach((summaryField: string) => { const summaryFieldValue = summaries.get(summaryField) || 0; - summaries.set(summaryField, summaryFieldValue + resolveField(dataItem, summaryField)); + summaries.set(summaryField, sum([summaryFieldValue, resolveField(dataItem, summaryField) || 0])); }); } groupSummaryFields.forEach((summaryField: string) => { const summaryFieldValue = summaries.get(summaryField) || 0; - summaries.set(summaryField, toNumber(summaryFieldValue.toFixed(2))); + summaries.set(summaryField, toFixed(summaryFieldValue as string, 2)); }); } function updateVisibleDataSummary(visibleData: VisualData[]) { @@ -560,12 +572,12 @@ export function useDataView( for (const dataItem of visibleData) { groupSummaryFields.forEach((summaryField: string) => { const summaryFieldValue = summaries.get(summaryField) || 0; - summaries.set(summaryField, summaryFieldValue + resolveField(dataItem.raw, summaryField)); + summaries.set(summaryField, sum([summaryFieldValue, resolveField(dataItem.raw, summaryField) || 0])); }); } groupSummaryFields.forEach((summaryField: string) => { const summaryFieldValue = summaries.get(summaryField) || 0; - summaries.set(summaryField, toNumber(summaryFieldValue.toFixed(2))); + summaries.set(summaryField, toFixed(summaryFieldValue as string, 2)); }); } return { @@ -612,6 +624,7 @@ export function useDataView( visibleDataItems, shouldGroupingData, pagination, - updateVisibleDataSummary + updateVisibleDataSummary, + // getVisibleDataItems }; } diff --git a/packages/ui-vue/components/data-view/composition/data/use-group-data.ts b/packages/ui-vue/components/data-view/composition/data/use-group-data.ts index 974b2db03f167906a83fa947749681eff9c87eab..d43061098ce837fb36601be538f0f5d57efa263d 100644 --- a/packages/ui-vue/components/data-view/composition/data/use-group-data.ts +++ b/packages/ui-vue/components/data-view/composition/data/use-group-data.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { computed, ref } from 'vue'; -import { useGuid } from '@farris/ui-vue/components/common'; +import { useGuid, useNumberFormat, NumberType, resolveField } from '@farris/ui-vue/components/common'; import { CollpasableItem, DataViewOptions, UseGroupData, UseIdentify } from '../types'; interface GroupingItem { @@ -32,13 +32,14 @@ interface GroupingItem { // 子级分组 nestGroup?: Map; // 分组合计 - groupSummaries: Map; + groupSummaries: Map; } export function useGroupData( props: DataViewOptions, identifyComposition: UseIdentify ): UseGroupData { + const { sum, toFixed } = useNumberFormat(); const { uuid } = useGuid(); const { idField } = identifyComposition; @@ -87,10 +88,10 @@ export function useGroupData( const groupingValue = rawDataItem[groupField] as any; let groupingItem = groupResult.get(groupingValue); if (!groupingItem) { - const groupSummaries = groupSummaryFields.value.reduce((sumaries: Map, summaryField: string) => { + const groupSummaries = groupSummaryFields.value.reduce((sumaries: Map, summaryField: string) => { sumaries.set(summaryField, 0); return sumaries; - }, new Map()); + }, new Map()); groupingItem = { field: groupField, title: columnMaps.get(groupField)?.title || groupField, @@ -103,7 +104,8 @@ export function useGroupData( groupSummaryFields.value.forEach((summaryField: string) => { if (groupingItem) { const summaryFieldValue = groupingItem.groupSummaries.get(summaryField) || 0; - groupingItem.groupSummaries.set(summaryField, summaryFieldValue + rawDataItem[summaryField]); + groupingItem.groupSummaries.set(summaryField, + sum([summaryFieldValue, resolveField(rawDataItem, summaryField)])); } }); } @@ -112,7 +114,7 @@ export function useGroupData( groupSummaryFields.value.forEach((summaryField: string) => { if (groupingItem) { const summaryFieldValue = groupingItem.groupSummaries.get(summaryField) || 0; - groupingItem.groupSummaries.set(summaryField, Number(summaryFieldValue.toFixed(2))); + groupingItem.groupSummaries.set(summaryField, toFixed(summaryFieldValue as string, 2)); } }); } @@ -194,7 +196,7 @@ export function useGroupData( groupingData.push(virtualSummaryDataItem); } if (groupingItem.groupSummaries && groupingItem.groupSummaries.size) { - groupingItem.groupSummaries.forEach((summaryValue: number, summaryField: string) => { + groupingItem.groupSummaries.forEach((summaryValue: NumberType, summaryField: string) => { virtualDataItem[summaryField] = summaryValue; }); } diff --git a/packages/ui-vue/components/data-view/composition/data/use-loading.ts b/packages/ui-vue/components/data-view/composition/data/use-loading.ts index b8f0a03f58161c4af7aea626cfeace03ab5ad322..6cedad8b3c08fa045ec8465ad57a0ec137505f31 100644 --- a/packages/ui-vue/components/data-view/composition/data/use-loading.ts +++ b/packages/ui-vue/components/data-view/composition/data/use-loading.ts @@ -1,14 +1,13 @@ -import { computed, inject, Ref, ref, watch } from 'vue'; -import { useI18n } from 'vue-i18n'; -import { FLoadingService } from '@farris/ui-vue/components/loading'; +import { computed, inject, Ref, watch } from 'vue'; +import { LocaleService } from '@farris/ui-vue/components/locale'; +import { FLoadingService } from '../../../loading'; import { DataGridProps } from '../../../data-grid/src/data-grid.props'; export function useLoading(props: DataGridProps, gridRef: Ref) { - const { t: getLocaleValue } = useI18n(); const LoadingService: FLoadingService | any = inject('FLoadingService'); const showLoading = computed(() => typeof props.loading === 'object' ? props.loading.show : props.loading); const loadingMessage = computed(() => typeof props.loading === 'object' ? - props.loading.message : `${getLocaleValue('datagrid.loadingMessage')}...`); + props.loading.message : `${LocaleService.getLocaleValue('datagrid.loadingMessage')}...`); let loadingInstance; function renderLoading() { const config: any = { diff --git a/packages/ui-vue/components/data-view/composition/hierarchy/use-cascade.ts b/packages/ui-vue/components/data-view/composition/hierarchy/use-cascade.ts index 3a9e9d575c059147ab776f393a123ce3b22ec404..0b758bf17fa6c60c3864400805646ce5e3a6090a 100644 --- a/packages/ui-vue/components/data-view/composition/hierarchy/use-cascade.ts +++ b/packages/ui-vue/components/data-view/composition/hierarchy/use-cascade.ts @@ -10,7 +10,7 @@ export function useCascade( context: SetupContext ): UseCascade { const { getDifference, isBInA } = useArrayProcessor(); - const { dataView, visibleDataItems } = useDataViewComposition; + const { dataView } = useDataViewComposition; const { idField } = identifyComposition; // 自动勾选当前节点的子节点 const autoCheckChildren = ref(props.hierarchy?.cascadeOption?.autoCheckChildren || false); diff --git a/packages/ui-vue/components/data-view/composition/hierarchy/use-select-hierarchy-item.ts b/packages/ui-vue/components/data-view/composition/hierarchy/use-select-hierarchy-item.ts index 322deaa667e9281c65cf4958dff27b494f57efd9..a3badd4fd1d9d7c1554e8f9b32ee49e1734e02a6 100644 --- a/packages/ui-vue/components/data-view/composition/hierarchy/use-select-hierarchy-item.ts +++ b/packages/ui-vue/components/data-view/composition/hierarchy/use-select-hierarchy-item.ts @@ -14,7 +14,7 @@ export function useSelectHierarchyItem( context: SetupContext, ): UseSelectHierarchyItem { const { getDifference, isBInA } = useArrayProcessor(); - const { dataView, fold, hasRealChildren, unFold, reOrderVisibleIndex } = useDataViewComposition; + const { dataView } = useDataViewComposition; const { clearSelection, selectDataItem, @@ -107,7 +107,7 @@ export function useSelectHierarchyItem( // resetSelectedValuesOnPaging(); visualDataToBeSelected.checked = true; visualDataToBeSelected.indeterminate = false; - currentSelectedDataId.value = visualDataToBeSelected.raw[idField.value]; + // currentSelectedDataId.value = visualDataToBeSelected.raw[idField.value]; } @@ -132,7 +132,7 @@ export function useSelectHierarchyItem( // resetSelectedValuesOnPaging(); visualDataToBeUnSelected.checked = false; visualDataToBeUnSelected.indeterminate = false; - currentSelectedDataId.value = ''; + // currentSelectedDataId.value = ''; } function selectItemById(dataItemId: string) { diff --git a/packages/ui-vue/components/data-view/composition/pagination/use-pagination.ts b/packages/ui-vue/components/data-view/composition/pagination/use-pagination.ts index 4eeafc937ea1253b52496e9cb3e9fbfb18e5ca2a..a98638e48d1840bb1dcd5ecfdc517a2ac1e28498 100644 --- a/packages/ui-vue/components/data-view/composition/pagination/use-pagination.ts +++ b/packages/ui-vue/components/data-view/composition/pagination/use-pagination.ts @@ -3,8 +3,8 @@ import { useCommonUtils } from '@farris/ui-vue/components/common'; import { DataViewOptions, PaginatonOptions, UseData, UseDataView, UsePagination } from '../types'; export function usePagination( - props: DataViewOptions, - dataView: UseDataView + props: DataViewOptions|any, + dataView: UseDataView|any ): UsePagination { const { isUndefined } = useCommonUtils(); const { pageIndex, totalItems, pagination } = dataView; @@ -25,7 +25,7 @@ export function usePagination( const showGotoPage = ref(props.pagination?.showGoto || false); // 是否展示分页总数信息 - const showPageInfo = ref(props.pagination?.showPageInfo || true); + const showPageInfo: any = ref(props.pagination?.showPageInfo || true); watch(() => props.pagination?.disabled, (newValue: boolean, oldValue: boolean) => { if (newValue !== oldValue) { diff --git a/packages/ui-vue/components/data-view/composition/types.ts b/packages/ui-vue/components/data-view/composition/types.ts index 17fd7cca5869250f490037c4d8501465f4cf0677..f76b624e565c84b694b4a170635ed28ef2cfc7ec 100644 --- a/packages/ui-vue/components/data-view/composition/types.ts +++ b/packages/ui-vue/components/data-view/composition/types.ts @@ -1,5 +1,6 @@ /* eslint-disable no-use-before-define */ import { ComputedRef, ExtractPropTypes, PropType, Ref, VNode } from "vue"; +import { NumberType } from "@farris/ui-vue/components/common"; import { Condition } from "@farris/ui-vue/components/condition"; import { EditorConfig, EditorType } from '@farris/ui-vue/components/dynamic-form'; import { ButtonSize } from "@farris/ui-vue/components/button"; @@ -20,6 +21,7 @@ export interface DataColumnCommand { command?: string; icon?: string; hidden?: boolean; + value?: any; size: string; onClick: (e: MouseEvent, dataIndex: number, visualDataRow: VisualData) => void; } @@ -391,6 +393,7 @@ export interface SelectionOptions { }; export interface CommandOptions { + count?: number; /** 启用操作列 */ enable: boolean; /** 操作列命令 */ @@ -400,9 +403,11 @@ export interface CommandOptions { /** 自定义操作列模板,如果设置了列模板,commands属性失效 */ formatter: (cell: VisualDataCell, visualDataRow: VisualData) => VNode | string; /** 编辑回调事件 */ - onClickEditCommand: (cell: VisualDataCell, visualDataRow: VisualData) => void; + onClickEditCommand: (cell: VisualDataCell, visualDataRow: VisualData, command: DataColumnCommand, payload: MouseEvent) => void; /** 删除回调事件 */ - onClickDeleteCommand: (cell: VisualDataCell, visualDataRow: VisualData) => void; + onClickDeleteCommand: (cell: VisualDataCell, visualDataRow: VisualData, command: DataColumnCommand, payload: MouseEvent) => void; + // 操作列按钮点击事件 + onHandleAction: (cell: VisualDataCell, visualDataRow: VisualData, command: DataColumnCommand, payload: MouseEvent) => void; } export interface ColumnCommand { @@ -530,6 +535,8 @@ export interface DataViewOptions { showBorder: boolean; loading: boolean | { show?: boolean, message?: string } emptyTemplate: null | undefined | (() => VNode | string); + // 调用updateDataSource之前触发 + beforeUpdate: (context: any) => Promise | any; } export interface UseDataView { @@ -603,7 +610,7 @@ export interface UseDataView { sorters: Ref; - summaries: Map; + summaries: Map; toggleChildrenVisibiltyByCollapseStatus: (rawDataItem: any) => void; @@ -615,6 +622,9 @@ export interface UseDataView { visibleDataItems: ComputedRef; + + // getVisibleDataItems: () => any[]; + shouldGroupingData: ComputedRef; pagination: Ref; @@ -694,6 +704,8 @@ export interface UseEdit { getEditingSnapshot: (dataIdentify: string) => VisualData | null; endEditCell: () => any; + endEditByCell: (cell: VisualDataCell) => void; + showRowEditPanel: Ref; } export interface UseCellEditor { @@ -755,6 +767,7 @@ export interface UseVirtualScroll { offsetY: Ref; onMouseDownScrollThumb: ($event: MouseEvent, gridContentRef: Ref, thumbType: 'vertical' | 'horizontal') => void; + onTouchstartScrollThumb: ($event: TouchEvent, gridContentRef: Ref) => void; onWheel: (payload: WheelEvent) => void; @@ -1005,7 +1018,7 @@ export interface UseSelection { showCheckBox: Ref; - showSelectAll: Ref; + showSelectAll: ComputedRef; enableSelectRow: Ref; @@ -1037,7 +1050,8 @@ export interface UseSelection { isSingleSelect: ComputedRef; showSelection: ComputedRef; keepSelectingOnPaging: ComputedRef; - emitSelectionChanged: () => void + emitSelectionChanged: () => void, + emptyCurrentRowId: () => void; } export interface UseSelectHierarchyItem { diff --git a/packages/ui-vue/components/data-view/composition/use-edit.tsx b/packages/ui-vue/components/data-view/composition/use-edit.tsx index 6e24149e6752d1a181aeda79489c516c87ad173e..e98b8ebdd16ef66981ea70ab2f45157f099c9d3f 100644 --- a/packages/ui-vue/components/data-view/composition/use-edit.tsx +++ b/packages/ui-vue/components/data-view/composition/use-edit.tsx @@ -16,12 +16,13 @@ import { reactive, Ref, ref, SetupContext, watch } from 'vue'; import { EditorConfig, FDynamicFormInput } from '@farris/ui-vue/components/dynamic-form'; import { resolveField } from '@farris/ui-vue/components/common'; +import { useMobile } from '@farris/ui-vue/components/data-view'; import { CellMode, DataColumn, DataViewOptions, MappedTypeWithNewProperty, UseEdit, UseIdentify, UseRow, VisualData, VisualDataCell, VisualDataStatus } from './types'; -import { cloneDeep, isUndefined } from 'lodash-es'; - +import { COMMAND_COLUMN_FIELD, SETTING_COLUMN_FIELD } from './const'; +import { isUndefined } from 'lodash-es'; export function useEdit( props: DataViewOptions, context: SetupContext, @@ -29,13 +30,14 @@ export function useEdit( useRowComposition: UseRow, visibleDatas: Ref ): UseEdit { + const { isMobilePhone } = useMobile(); + const showRowEditPanel = ref(false); const { idField } = useIdentifyComposition; const editable = ref(props.editable); const editOptions = ref(props.editOption); const wrapContent = ref(props.rowOption?.wrapContent || false); const { onClickRow, onClickRowExcludeDblclick } = useRowComposition; - const typeToEditorName = new Map([ ['boolean', 'check-box'], ['datetime', 'date-picker'], @@ -62,6 +64,9 @@ export function useEdit( const controlData = reactive>({}); + function isValidEditableColumn(column: DataColumn | undefined) { + return column?.field !== COMMAND_COLUMN_FIELD && column?.field !== SETTING_COLUMN_FIELD; + } function beginEditCell(cell: VisualDataCell, row: VisualData, column: Partial = {}) { cell.mode = CellMode.editing; @@ -74,10 +79,14 @@ export function useEdit( } } - function endEditCell(currentEditingCell: VisualDataCell) { + function endEditByCell(currentEditingCell: VisualDataCell) { currentEditingCell.accept(); currentEditingCell.mode = CellMode.editable; editingCell = null; + } + + function endEditCell(currentEditingCell: VisualDataCell) { + endEditByCell(currentEditingCell); const editingRowCloned: MappedTypeWithNewProperty = {}; if (editingRow) { Object.keys(editingRow).forEach((key: string) => { @@ -162,7 +171,16 @@ export function useEdit( }); } - + function registerCloseModal() { + if (!(window as any).closeModal) { + (window as any).closeModal = () => { + showRowEditPanel.value = false; + if ((window as any).closeModal) { + delete (window as any).closeModal; + } + }; + } + } function allowBeginEditCell(row: VisualData, cell: VisualDataCell, column: DataColumn): Promise { return Promise.resolve() .then(() => { @@ -174,7 +192,13 @@ export function useEdit( }) .then(value => { if (value) { - beginEditCell(cell, row, column); + if (isMobilePhone()) { + // cell.mode = CellMode.editing; + showRowEditPanel.value = true; + registerCloseModal(); + } else { + beginEditCell(cell, row, column); + } document.body.removeEventListener('click', onClickOutOfCell, true); document.body.addEventListener('click', onClickOutOfCell, true); } @@ -189,7 +213,11 @@ export function useEdit( ) { payload.stopPropagation(); onClickRowExcludeDblclick(payload, row); - if (editable.value && editOptions.value.editMode === 'cell' && cell.mode === CellMode.editable) { + if ( + isValidEditableColumn(cell.column) && + editable.value && + editOptions.value.editMode === 'cell' && + cell.mode === CellMode.editable) { allowEndEditing(editingCell) .then(() => { if (!editingCell) { @@ -205,10 +233,15 @@ export function useEdit( row: VisualData, column: Partial = {}, ) { - payload.stopPropagation(); + if (!isMobilePhone()) { + payload.stopPropagation(); + } // 点击行选中行 onClickRow(payload, row); - if (editable.value && editOptions.value.editMode === 'cell' && cell.mode === CellMode.editable) { + if (isValidEditableColumn(cell.column) && + editable.value && + editOptions.value.editMode === 'cell' && + cell.mode === CellMode.editable) { allowEndEditing(editingCell) .then(() => { if (!editingCell) { @@ -232,7 +265,7 @@ export function useEdit( function beginEditRow(visualDataRow: VisualData) { Object.values(visualDataRow.data) - .filter((cell: VisualDataCell) => cell.mode === CellMode.editable && cell.field !== '__commands__') + .filter((cell: VisualDataCell) => cell.mode === CellMode.editable && isValidEditableColumn(cell.column)) .forEach((editableCell: VisualDataCell) => { editableCell.mode = CellMode.editing; }); @@ -282,7 +315,7 @@ export function useEdit( if (!wrapContent.value && editor.type === 'text') { editor.type = 'input-group'; } - if (editor) { + if (editor && isValidEditableColumn(cell.column)) { if (editor.type === 'input-group') { editor.textAlign = column.align !== 'center' && column.align !== 'right' ? 'left' : column.align; @@ -352,6 +385,8 @@ export function useEdit( getEditor, endEditCell: onEndEditCell, onMousedownCell, - onMouseupCell + onMouseupCell, + showRowEditPanel, + endEditByCell }; } diff --git a/packages/ui-vue/components/data-view/composition/use-mobile.ts b/packages/ui-vue/components/data-view/composition/use-mobile.ts new file mode 100644 index 0000000000000000000000000000000000000000..a691339b05f6303c94bb891909f5e74138445a9d --- /dev/null +++ b/packages/ui-vue/components/data-view/composition/use-mobile.ts @@ -0,0 +1,56 @@ +export function useMobile() { + function isMobilePhone(): boolean { + const userAgent = navigator.userAgent || navigator.vendor || (window as any).opera; + + // 检查是否为手机设备的 User Agent 标识 + const phonePatterns = [ + /iPhone/i, + /iPod/i, + /Android.*Mobile/i, // Android 手机通常包含 "Mobile" 字样,而平板不包含 + /webOS/i, + /BlackBerry/i, + /IEMobile/i, + /Opera Mini/i + ]; + + // 检查是否为平板设备的 User Agent 标识 + const tabletPatterns = [ + /iPad/i, + /Android(?!.*Mobile)/i, // Android 平板通常不含 "Mobile" + /Tablet/i, + /PlayBook/i, + /Kindle/i, + /Silk/i + ]; + + // 首先检查是否明确为平板设备 + for (const pattern of tabletPatterns) { + if (pattern.test(userAgent)) { + return false; + } + } + + // 然后检查是否为手机设备 + for (const pattern of phonePatterns) { + if (pattern.test(userAgent)) { + return true; + } + } + + // 对于其他情况,通过触摸支持和屏幕尺寸综合判断 + const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + const screenWidth = screen.width; + const screenHeight = screen.height; + + // 获取较小的尺寸作为判断依据(无论横屏还是竖屏) + const minScreenDimension = Math.min(screenWidth, screenHeight); + + // 如果支持触摸且较小的屏幕尺寸符合手机尺寸,则判断为手机 + if (isTouchDevice && minScreenDimension <= 768) { + return true; + } + + return false; + } + return { isMobilePhone }; +} \ No newline at end of file diff --git a/packages/ui-vue/components/data-view/composition/use-selection.ts b/packages/ui-vue/components/data-view/composition/use-selection.ts index 5e23a25f19beb5570c562e3458638a53db6f8b10..b20cc8d82705b162fd5834491f13a12ecbfb8fa4 100644 --- a/packages/ui-vue/components/data-view/composition/use-selection.ts +++ b/packages/ui-vue/components/data-view/composition/use-selection.ts @@ -26,26 +26,55 @@ export function useSelection( ): UseSelection { const { idField } = identifyComposition; const { dataView, getSelectionItems } = dataViewComposition; + // 焦点行 const currentSelectedDataId = ref(''); + // 允许点击行选中行 const enableSelectRow = computed(() => props.selection.enableSelectRow); + // 启用多选 const enableMultiSelect = ref(props.selection.multiSelect ?? false); + // 多选模式 const multiSelectMode: Ref = ref(props.selection.multiSelectMode as MultiSelectMode || 'DependOnCheck'); + // 显示复选框 const showCheckBox = ref(props.selection.showCheckbox); - const showSelectAll = ref(props.selection.showSelectAll); + // 显示全选复选框 + const showSelectAll = computed(() => !!props.selection?.showSelectAll); + // 选中行标识集合 const selectedValues = ref(props.selectionValues || []); + // 显示已选记录 const showSelection = computed(() => props.selection.showSelection); + // 行选中状态是否保持 const keepSelectingOnPaging = computed(() => props.selection?.keepSelectingOnPaging === undefined ? true : props.selection.keepSelectingOnPaging); - // 全选状态 const selectAllStatus = ref(!!selectedValues.value.length && visibleDatas.value.length === selectedValues.value.length); // 半选状态 const indeterminateStatus = ref(!!selectedValues.value.length && visibleDatas.value.length !== selectedValues.value.length); - watch(() => props.selection.showSelectAll, (newShowSelectAllValue, oldShowSelectAllValue) => { - if (newShowSelectAllValue !== oldShowSelectAllValue) { - showSelectAll.value = newShowSelectAllValue; - } + // 允许点击行选中行且允许多选 + const inMultiSelect = computed(() => enableSelectRow.value && enableMultiSelect.value); + + // 单选 + const isSingleSelect = computed(() => + enableSelectRow.value && + !enableMultiSelect.value && + !showCheckBox.value + ); + // 是否树表 + const isSelectingHirarchyItem = computed(() => !!props.hierarchy); + + // 仅允许使用复选框选中行 + const multiSelectOnlyOnCheck = computed(() => { + const dependsAndHasCheckBox = showCheckBox.value && multiSelectMode.value === 'DependOnCheck'; + return inMultiSelect.value && dependsAndHasCheckBox; + }); + + // + const multiSelectOnClickRowWithShift = computed(() => { + return inMultiSelect.value && !showCheckBox.value; + }); + + const multiSelectOnClickRow = computed(() => { + return inMultiSelect.value && showCheckBox.value && multiSelectMode.value === 'OnCheckAndClick'; }); function resetSelectedValuesOnPaging() { @@ -111,31 +140,6 @@ export function useSelection( } - const inMultiSelect = computed(() => enableSelectRow.value && enableMultiSelect.value); - - const isSingleSelect = computed(() => - enableSelectRow.value && - !enableMultiSelect.value && - !showCheckBox.value - ); - - const isSelectingHirarchyItem = computed(() => !!props.hierarchy); - - const multiSelectOnlyOnCheck = computed(() => { - const dependsAndHasCheckBox = showCheckBox.value && multiSelectMode.value === 'DependOnCheck'; - return inMultiSelect.value && dependsAndHasCheckBox; - }); - - const multiSelectOnClickRowWithShift = computed(() => { - return inMultiSelect.value && !showCheckBox.value; - }); - - const multiSelectOnClickRow = computed(() => { - return inMultiSelect.value && showCheckBox.value && multiSelectMode.value === 'OnCheckAndClick'; - }); - - const shouldToggleSelectOnClickRow = computed(() => multiSelectOnClickRow.value); - function emitSelectionChanged() { const selectedItems = getSelectionItems(selectedValues.value); context.emit('selectionUpdate', selectedItems, selectedValues.value); @@ -208,7 +212,7 @@ export function useSelection( const selectedItems = getSelectionItems(selectedValues.value); selectedItems.forEach((selectionItem: any) => { selectionItem.__fv_checked__ = false; }); selectedValues.value = []; - currentSelectedDataId.value = ''; + // currentSelectedDataId.value = ''; updateSelectAllStatus(); } @@ -260,15 +264,21 @@ export function useSelection( /** 勾选指定节点 */ function select(visualDataToBeSelected: VisualData) { + if (visualDataToBeSelected.disabled) { + return; + } selectWithoutRow(visualDataToBeSelected); // emitSelectionChanged(); - currentSelectedDataId.value = visualDataToBeSelected.raw[idField.value]; + // currentSelectedDataId.value = visualDataToBeSelected.raw[idField.value]; } /** 取消勾选指定节点 */ function unSelect(visualDataToBeUnSelected: VisualData) { + if (visualDataToBeUnSelected.disabled) { + return; + } unSelectWithoutRow(visualDataToBeUnSelected); - currentSelectedDataId.value = ''; + // currentSelectedDataId.value = ''; } function indeterminateDataItem(dataItem: any) { @@ -313,13 +323,13 @@ export function useSelection( } function selectItem(visualData: VisualData) { - const selectingNewItem = visualData.raw[idField.value] !== currentSelectedDataId.value; - if (selectingNewItem) { - // 不取消其他已经勾选的行数据 - clearSelection(); - select(visualData); - // emitSelectionChanged(); - } + // const selectingNewItem = visualData.raw[idField.value] !== currentSelectedDataId.value; + // if (selectingNewItem) { + // 不取消其他已经勾选的行数据 + clearSelection(); + select(visualData); + // emitSelectionChanged(); + // } } function selectItemById(dataItemId: string) { @@ -354,9 +364,9 @@ export function useSelection( // todo: should not clear,should append selected values // clearSelection(); // if single select row, active current row - if (isSingleSelect.value) { - currentSelectedDataId.value = dataItemIds[0]; - } + // if (isSingleSelect.value) { + // currentSelectedDataId.value = dataItemIds[0]; + // } } visibleItemToBeSelected.forEach((visibleData: VisualData) => { select(visibleData); @@ -408,7 +418,7 @@ export function useSelection( visualDataToBeSelected.indeterminate = false; }); // 选中所有节点时,不需要指定当前选中节点 - emitSelectionChanged(); + // emitSelectionChanged(); } /** 取消勾选所有节点 */ @@ -424,7 +434,7 @@ export function useSelection( visualDataToBeUnSelected.checked = false; visualDataToBeUnSelected.indeterminate = false; }); - emitSelectionChanged(); + // emitSelectionChanged(); } function getSelectionRow(): VisualData { @@ -433,6 +443,11 @@ export function useSelection( }) as VisualData; } + /** 清空当前选中行 */ + function emptyCurrentRowId() { + currentSelectedDataId.value = ''; + } + return { clearSelection, currentSelectedDataId, @@ -469,6 +484,7 @@ export function useSelection( isSingleSelect, showSelection, keepSelectingOnPaging, - emitSelectionChanged + emitSelectionChanged, + emptyCurrentRowId }; } diff --git a/packages/ui-vue/components/data-view/composition/use-sidebar.ts b/packages/ui-vue/components/data-view/composition/use-sidebar.ts index 0b6d8fa1448e7757e28bfc3edbfedb5926869f5e..078a897256434f473c8004fca487c4cfb640b260 100644 --- a/packages/ui-vue/components/data-view/composition/use-sidebar.ts +++ b/packages/ui-vue/components/data-view/composition/use-sidebar.ts @@ -14,15 +14,14 @@ * limitations under the License. */ import { computed, ref, watch } from 'vue'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { DataViewOptions, RowNumberOptions, UseSelection, UseSidebar, VisualData } from './types'; export function useSidebar(props: DataViewOptions, useSelectionCompostion: UseSelection): UseSidebar { - const { t: getLocaleValue } = useI18n(); const timeStamp = String(Date.now()); // const defaultCheckboxWidth = 24; const defaultCheckboxWidth = 50; - const { showCheckBox, showSelectAll } = useSelectionCompostion; + const { showCheckBox } = useSelectionCompostion; const showRowNumer = ref(props.rowNumber?.enable ?? false); const showSidebarCheckBox = computed(() => { const showNodeIconCheckBox = props.hierarchy && showCheckBox.value && @@ -30,11 +29,12 @@ export function useSidebar(props: DataViewOptions, useSelectionCompostion: UseSe return showNodeIconCheckBox ? false : showCheckBox.value; }); - const rowNumberWidth = ref(showRowNumer.value ? props.rowNumber?.width ?? 32 : 0); + const rowNumberWidth = ref(showRowNumer.value ? props.rowNumber?.width ?? 36 : 0); const checkboxWidth = ref(showSidebarCheckBox.value ? defaultCheckboxWidth : 0); // const sidebarTitle = computed(() => showRowNumer.value ? props.rowNumber?.heading ?? '序号' : '');getLocaleValue('datagrid.lineNumberTitle') - const sidebarTitle = computed(() => showRowNumer.value ? props.rowNumber?.heading === '序号' ? getLocaleValue('datagrid.lineNumberTitle') : props.rowNumber?.heading : ''); + const sidebarTitle = computed(() => showRowNumer.value ? props.rowNumber?.heading === '序号' ? + LocaleService.getLocaleValue('datagrid.lineNumberTitle') : props.rowNumber?.heading : ''); const sidebarWidth = computed(() => { return ((showSidebarCheckBox.value && !props.hierarchy) ? Number(checkboxWidth.value) : 0) + (showRowNumer.value ? Number(rowNumberWidth.value) : 0); diff --git a/packages/ui-vue/components/data-view/composition/visualization/use-virtual-scroll.ts b/packages/ui-vue/components/data-view/composition/visualization/use-virtual-scroll.ts index eb56485c8729387715494e3aa3ef3e45f37c56af..30589155f7fd5f02f756f705c67f3c9980ff61be 100644 --- a/packages/ui-vue/components/data-view/composition/visualization/use-virtual-scroll.ts +++ b/packages/ui-vue/components/data-view/composition/visualization/use-virtual-scroll.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { - ColumnContext, DataViewOptions, UseDataView, UseEdit, + ColumnContext, DataViewOptions, DataViewType, UseDataView, UseEdit, UseVirtualScroll, UseVisualData, VisualData } from '../types'; import { DebouncedFunc, throttle } from 'lodash-es'; @@ -49,6 +49,8 @@ export function useVirtualScroll( const zoomFactorOfThumbScope = 1.5; let onDragThumbHandler: DebouncedFunc<(...args: any[]) => any> | null | any = null; + let onDragHorizontalThumbHandler: DebouncedFunc<(...args: any[]) => any> | null | any = null; + let onDragVerticalThumbHandler: DebouncedFunc<(...args: any[]) => any> | null | any = null; function getLatestGridViewHeight(): number { const visibleDataViewItems = visibleDataItems.value; @@ -71,7 +73,6 @@ export function useVirtualScroll( const scrollThumbHeight = computed(() => { const latestGridViewHeight = getLatestGridViewHeight(); - const visibleDataViewItems = visibleDataItems.value; // let thumbScopeInScrollbar = visibleDataViewItems.length > visibleCapacity.value + preloadCount // ? (visibleCapacity.value + preloadCount) / visibleDataViewItems.length // : viewPortHeight.value / Math.max(latestGridViewHeight, viewPortHeight.value); @@ -421,6 +422,7 @@ export function useVirtualScroll( let gridContentElement: any; function onDragHorizontalScroll($event: MouseEvent) { + const pageX = $event instanceof MouseEvent ? $event.pageX : ($event as TouchEvent).touches[0].pageX; if (onDraggingScrollThumb.value && onDraggingStartX.value !== -1) { const mouseOffset = onDraggingStartX.value - $event.pageX; const mouseMoveRange = dataGridWidth.value - scrollThumbWidth.value; @@ -448,6 +450,44 @@ export function useVirtualScroll( } } + function onTouchDragHorizontalScroll($event: TouchEvent) { + const { pageX, pageY } = $event.touches[0]; + // const diffX = Math.abs(pageX - onDraggingStartX.value); + // const diffY = Math.abs(pageY - onDraggingStartY.value); + // if (diffX > diffY && diffX > 5) { + if (onDraggingScrollThumb.value && onDraggingStartX.value !== -1) { + const mouseOffset = onDraggingStartX.value - pageX; + const mouseMoveRange = dataGridWidth.value - scrollThumbWidth.value; + if (Math.abs(mouseOffset) <= mouseMoveRange) { + const deltaOffsetX = + (mouseOffset / (viewPortWidth.value - scrollThumbWidth.value)) * (gridViewWidth.value - viewPortWidth.value); + updateOffsetX(deltaOffsetX); + onDraggingStartX.value = pageX; + } + } + // } + } + + function onTouchDragVerticalScroll($event: TouchEvent) { + const { pageX, pageY } = $event.touches[0]; + // const diffX = Math.abs(pageX - onDraggingStartX.value); + // const diffY = Math.abs(pageY - onDraggingStartY.value); + // if (diffY > diffX && diffY > 5) { + if (onDraggingScrollThumb.value && onDraggingStartY.value !== -1) { + const mouseOffset = onDraggingStartY.value - pageY; + const latestGridViewHeight = getLatestGridViewHeight(); + const mouseMoveRange = latestGridViewHeight - scrollThumbHeight.value; + if (Math.abs(mouseOffset) <= mouseMoveRange) { + const deltaOffsetY = + (mouseOffset / (viewPortHeight.value - scrollThumbHeight.value)) * (latestGridViewHeight - viewPortHeight.value); + + updateOffsetY(deltaOffsetY); + onDraggingStartY.value = pageY; + } + } + // } + } + function releaseDragScroll($event: MouseEvent) { onDraggingScrollThumb.value = false; onDraggingStartY.value = -1; @@ -463,6 +503,25 @@ export function useVirtualScroll( } } + function releaseTouchDragScroll($event: TouchEvent) { + onDraggingScrollThumb.value = false; + onDraggingStartY.value = -1; + onDraggingStartX.value = -1; + document.removeEventListener('touchend', releaseTouchDragScroll); + if (onDragHorizontalThumbHandler) { + document.removeEventListener('touchmove', onDragHorizontalThumbHandler); + onDragHorizontalThumbHandler = null; + } + if (onDragVerticalThumbHandler) { + document.removeEventListener('touchmove', onDragVerticalThumbHandler); + onDragHorizontalThumbHandler = null; + } + document.body.style.userSelect = ''; + if (scrollThumbContainer.value) { + scrollThumbContainer.value.style.opacity = null; + } + } + function onMouseDownScrollThumb($event: MouseEvent, gridContentRef: Ref, thumbType: 'vertical' | 'horizontal') { useEditComposition?.onClickOutOfCell($event); onDraggingScrollThumb.value = true; @@ -478,6 +537,8 @@ export function useVirtualScroll( // 节流,解决纵向滚动条卡顿问题 onDragThumbHandler = draggingHandler; } + const { pageX } = $event; + const { pageY } = $event; if (thumbType === 'vertical') { onDraggingStartY.value = $event.pageY; } @@ -492,6 +553,46 @@ export function useVirtualScroll( } } + function onTouchmoveScroll($event: TouchEvent) { + const { pageX, pageY } = $event.touches[0]; + const diffX = Math.abs(pageX - onDraggingStartX.value); + const diffY = Math.abs(pageY - onDraggingStartY.value); + if (diffY > diffX && diffY > 5) { + if (!onDragVerticalThumbHandler) { + onDragVerticalThumbHandler = throttle(onTouchDragVerticalScroll, 50); + } + document.addEventListener('touchmove', onDragVerticalThumbHandler); + document.removeEventListener('touchmove', onTouchmoveScroll); + } else if (diffX > diffY && diffX > 5) { + if (!onDragHorizontalThumbHandler) { + onDragHorizontalThumbHandler = onTouchDragHorizontalScroll; + } + document.addEventListener('touchmove', onDragHorizontalThumbHandler); + document.removeEventListener('touchmove', onTouchmoveScroll); + } + onDraggingStartX.value = pageX; + onDraggingStartY.value = pageY; + } + + function onTouchstartScrollThumb($event: TouchEvent, gridContentRef: Ref) { + useEditComposition?.onClickOutOfCell($event as unknown as MouseEvent); + onDraggingScrollThumb.value = true; + const scrollThumbParent = ($event.target as Element)?.parentElement; + if (scrollThumbParent) { + scrollThumbParent.style.opacity = '1'; + scrollThumbContainer.value = scrollThumbParent; + } + const { pageX, pageY } = $event.touches[0]; + onDraggingStartX.value = pageX; + onDraggingStartY.value = pageY; + if (gridContentRef.value) { + gridContentElement = gridContentRef.value; + document.addEventListener('touchmove', onTouchmoveScroll); + document.addEventListener('touchend', releaseTouchDragScroll); + document.body.style.userSelect = 'none'; + } + } + // 滚动到rowIndex行,从rowIndex行开始展示 function scrollToRowByIndex(rowIndex: number) { const maxScrolledHeight = dataView.value.length * rowHeight - viewPortHeight.value; @@ -542,6 +643,7 @@ export function useVirtualScroll( viewPortHeight, scrollToBottom, scrollToRowByIndex, - scrollTo + scrollTo, + onTouchstartScrollThumb }; } diff --git a/packages/ui-vue/components/data-view/designer/property-config/use-command-option.ts b/packages/ui-vue/components/data-view/designer/property-config/use-command-option.ts index 92e246d0ae563680e9a23b4201e1d0847ac939fb..0d10f6b7d58a16d7d1b6c3c6d465df11893fbd2e 100644 --- a/packages/ui-vue/components/data-view/designer/property-config/use-command-option.ts +++ b/packages/ui-vue/components/data-view/designer/property-config/use-command-option.ts @@ -1,38 +1,82 @@ -export function useCommandOption() { +import { PropertyChangeObject } from "@farris/ui-vue/components/property-panel"; +export function useCommandOption(dataGridPropertyInstance: any) { function getCommandColumnProperties(propertyData: any) { return { title: '操作列', description: '', properties: { - enable: { - title: '启用', - type: 'boolean', + // enable: { + // title: '启用', + // type: 'boolean', + // $converter: '/converter/column-command.converter', + // description: '启用操作列', + // refreshPanelAfterChanged: true + // }, + enableType: { + title: '操作列', + type: 'enum', $converter: '/converter/column-command.converter', description: '启用操作列', - refreshPanelAfterChanged: true + refreshPanelAfterChanged: true, + editor: { + data: [ + { id: 'unable', name: '无' }, + { id: 'default', name: '编辑和删除' }, + { id: 'custom', name: '自定义' } + ] + } }, - // commands: { - // description: '', - // title: '命令', - // type: 'enum', - // editor: { - // data: [ - // { id: 'edit', name: '编辑' }, - // { id: 'remove', name: '删除' } - // ] - // } - // }, - // formatter: { - // title: '操作列模板', - // type: 'string', - // visible: false, - // description: '自定义操作列模板', - // refreshPanelAfterChanged: true, - // editor: { - // type: "code-editor", - // language: "html", - // } - // } + count: { + description: '平铺操作按钮数量', + title: '平铺按钮数量', + type: "number", + $converter: '/converter/column-command.converter', + visible: propertyData.command?.enableType === 'custom', + editor: { + needValid: true, + min: 0 + } + }, + commands: { + description: '操作列按钮集合', + title: '操作项', + type: "array", + visible: propertyData.command?.enableType === 'custom', + $converter: '/converter/column-command.converter', + editor: { + columns: [ + { field: 'value', title: '值', dataType: 'string' }, + { field: 'text', title: '名称', dataType: 'string' } + ], + type: "item-collection-editor", + valueField: 'value', + nameField: 'text', + requiredFields: ['value', 'text'], + uniqueFields: ['value', 'text'] + } + }, + formatter: { + title: '操作列模板', + $converter: '/converter/column-command.converter', + type: 'string', + visible: propertyData.command?.enableType === 'custom' && !!propertyData.command.formatter, + description: '操作列模板', + refreshPanelAfterChanged: true, + editor: { + type: "code-editor", + language: "html", + } + } + }, + setPropertyRelates(changeObject: PropertyChangeObject, data: any) { + switch (changeObject && changeObject.propertyID) { + case 'enableType': { + if (changeObject.propertyValue === 'custom' || changeObject.propertyValue === 'default') { + dataGridPropertyInstance.getFormDesignerInstance()?.reloadPropertyPanel(); + } + break; + } + } } }; } diff --git a/packages/ui-vue/components/data-view/designer/property-config/use-event.ts b/packages/ui-vue/components/data-view/designer/property-config/use-event.ts index e01a3939b5445b0b9295a153ee38e580c22100fd..7192ec01079cac6b79b04838a2fba8a1bea89830 100644 --- a/packages/ui-vue/components/data-view/designer/property-config/use-event.ts +++ b/packages/ui-vue/components/data-view/designer/property-config/use-event.ts @@ -1,6 +1,10 @@ export function useDataGridEvent() { function initEvent(propertyData: any) { const events = [ + // { + // label: 'beforeUpdate', + // name: '更新数据前事件' + // }, { label: 'onClickRow', name: '行点击事件' @@ -18,27 +22,27 @@ export function useDataGridEvent() { name: '过滤事件' } ]; - if(propertyData.type !== 'tree-grid') { + if (propertyData.type !== 'tree-grid') { events.push({ label: 'onSortChanged', name: '排序事件' }, - { - label: 'onSelectItem', - name: '选中行事件' - }, - { - label: 'onUnSelectItem', - name: '取消选中行事件' - }, - { - label: 'onSelectAll', - name: '全选事件' - }, - { - label: 'onUnSelectAll', - name: '取消全选事件' - }); + { + label: 'onSelectItem', + name: '选中行事件' + }, + { + label: 'onUnSelectItem', + name: '取消选中行事件' + }, + { + label: 'onSelectAll', + name: '全选事件' + }, + { + label: 'onUnSelectAll', + name: '取消全选事件' + }); } if (propertyData.pagination?.enable !== false && propertyData.type !== 'tree-grid') { // 如果没有设置,默认是按照true处理的 @@ -51,8 +55,30 @@ export function useDataGridEvent() { "name": "分页条数变化事件" }); } - if (propertyData.command?.enable) { + if (propertyData.command?.enable && propertyData.command?.enableType === 'custom') { // 如果没有设置,默认是按照true处理的 + events.push({ + "label": "onHandleAction", + "name": "自定义操作列点击事件" + }); + } + const displayDefaultCommandEvent = propertyData.command?.enable && + (propertyData.command?.enableType === 'default' || + (!propertyData.command?.enableType && + JSON.stringify(propertyData.command.commands) === JSON.stringify([ + { + "text": "编辑", + "type": "primary", + "command": "edit" + }, + { + "text": "删除", + "type": "danger", + "command": "remove" + } + ])) + ); + if (displayDefaultCommandEvent) { events.push({ "label": "onClickEditCommand", "name": "操作列编辑事件" @@ -60,7 +86,7 @@ export function useDataGridEvent() { { "label": "onClickDeleteCommand", "name": "操作列删除事件" - }); + },); } return events; } diff --git a/packages/ui-vue/components/data-view/designer/property-config/use-row-option.tsx b/packages/ui-vue/components/data-view/designer/property-config/use-row-option.tsx index affd4deda80c2f2109b9d23540109a2020451d9d..cb55ae5486d6426239119b37e14535cca892eaec 100644 --- a/packages/ui-vue/components/data-view/designer/property-config/use-row-option.tsx +++ b/packages/ui-vue/components/data-view/designer/property-config/use-row-option.tsx @@ -18,23 +18,23 @@ export function useRowOption() { { ` (dataItem) => { - if(dataItem.code === '0001') { - return { - class: { - 'text-align': true, - 'text-red': true - }, - style: { - 'background-color': '#000', - 'font-size': '1rem' - } - }; - } - return {}; +if(dataItem.code === '0001') { + return { + class: { + 'text-align': true, + 'text-red': true + }, + style: { + 'background-color': '#000', + 'font-size': '1rem' + } + }; } - +return {}; +} + 注: dataItem 行数据 - ` + ` } diff --git a/packages/ui-vue/components/data-view/designer/use-visual-data.ts b/packages/ui-vue/components/data-view/designer/use-visual-data.ts index cddb9782d0b8b3a3eb04a5782c1f7d9e49cac863..7a334c4593dedd9abdb8c6ca0d9be80bc4ec5aec 100644 --- a/packages/ui-vue/components/data-view/designer/use-visual-data.ts +++ b/packages/ui-vue/components/data-view/designer/use-visual-data.ts @@ -50,7 +50,6 @@ export function useDesignerVisualData( function getVisualData(start: number, end: number, pre?: any, forceToRefresh?: boolean): VisualData[] { const { dataView } = dataViewComposition; const visualDatas: VisualData[] = []; - // const itemsToVisualize = visibleDataItems.value; // 数据视图中有数据 if (dataView.value.length > 0) { // 是否强制刷新 diff --git a/packages/ui-vue/components/data-view/index.ts b/packages/ui-vue/components/data-view/index.ts index e8b41d50b43df0d7dc879d7cd4cede01b866ba25..665e31b11a26413328748e85b89c0304f54b36ac 100644 --- a/packages/ui-vue/components/data-view/index.ts +++ b/packages/ui-vue/components/data-view/index.ts @@ -49,6 +49,7 @@ export * from './composition/pagination/use-pagination'; export * from './composition/data/use-tree-data'; export * from './composition/column/use-setting-column'; export * from './composition/const'; +export * from './composition/use-mobile'; export { diff --git a/packages/ui-vue/components/date-picker/index.ts b/packages/ui-vue/components/date-picker/index.ts index 887cea951dba71ab0b00ed0a28f742cb57c68ea0..a771a049129aaa71b4c51e8f3b4c91e0a1208eff 100644 --- a/packages/ui-vue/components/date-picker/index.ts +++ b/packages/ui-vue/components/date-picker/index.ts @@ -33,7 +33,7 @@ export * from './src/types/month'; export * from './src/types/year'; export * from './src/property-config/date-format'; -DatePicker.install = (app: App) => { +DatePicker.install = (app: App) => { app.component(DatePickerCalendar.name as string, DatePickerCalendar) .component(DatePickerCalendarNavBar.name as string, DatePickerCalendarNavBar) .component(DatePickerContainer.name as string, DatePickerContainer) diff --git a/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.component.tsx b/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.component.tsx index 1d266177e7d6baf3eb9db3ca074ddcaf881b47b7..69719480c80cda595dca6d3de0fdf00e603eb956 100644 --- a/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.component.tsx +++ b/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.component.tsx @@ -16,6 +16,8 @@ import { computed, defineComponent, ref, SetupContext, watch } from 'vue'; import { CalendarNavbarProps, calendarNavbarProps } from './calendar-navbar.props'; import { ActiveMonth } from '../../types/month'; +import { useDisableMonth } from '../../composition/use-disable-month'; +import { useMonth } from '../../composition/use-month'; export default defineComponent({ name: 'FDatePickerCalendarNavbar', @@ -39,6 +41,8 @@ export default defineComponent({ const yearSelector = ref(true); const monthSelector = ref(true); + const {isMonthDisabledByDisableSince, isMonthDisabledByDisableUntil} = useDisableMonth(); + const { daysInMonth } = useMonth(); watch(() => props.selectingMonth, (newValue, oldValue) => { selectingMonth.value = newValue; @@ -48,20 +52,130 @@ export default defineComponent({ selectingYear.value = newValue; }); + // 提取公共函数用于生成前后月份/年份的时间对象 + const getPrevMonth = (year: number, month: number) => ({ + year: month === 1 ? year - 1 : year, + month: month === 1 ? 12 : month - 1, + day: daysInMonth(month === 1 ? 12 : month - 1, month === 1 ? year - 1 : year) + }); + + const getNextMonth = (year: number, month: number) => ({ + year: month === 12 ? year + 1 : year, + month: month === 12 ? 1 : month + 1, + day: 1 + }); + + const getPrevYearLastDay = (year: number) => ({ year: year - 1, month: 12, day: 31 }); + const getNextYearFirstDay = (year: number) => ({ year: year + 1, month: 1, day: 1 }); + // 统一设置 header button disabled 状态的核心函数 + function updateHeaderBtnDisabledState( + prevDisabledCheck: boolean, + nextDisabledCheck: boolean, + isMinBoundary: boolean, + isMaxBoundary: boolean, + target: 'page' | 'record' = 'page' + ): void { + if (target === 'page') { + disablePrePageButton.value = isMinBoundary || prevDisabledCheck; + disableNextPageButton.value = isMaxBoundary || nextDisabledCheck; + } else if (target === 'record') { + disablePreRecordButton.value = isMinBoundary || prevDisabledCheck; + disableNextRecordButton.value = isMaxBoundary || nextDisabledCheck; + } + } + + function setYearViewHeaderBtnDisabledState(previousYear: number, nextYear: number): void { + const { minDate, maxDate, minYear, maxYear } = props; + + const isPrevYearDisabled = minDate ? isMonthDisabledByDisableUntil(getPrevYearLastDay(previousYear), minDate) : true; + const isNextYearDisabled = maxDate ? isMonthDisabledByDisableSince(getNextYearFirstDay(nextYear), maxDate) : true; + + updateHeaderBtnDisabledState( + isPrevYearDisabled, + isNextYearDisabled, + previousYear <= minYear, + nextYear >= maxYear + ); + } + + function updateYearBtnDisabledState(prevYear?: number, nextYear?: number) { + const firstYearInView = prevYear || (years.value[0][1] as any)?.year; + const lastYearInView = nextYear || (years.value[3][1] as any)?.year; + + if (firstYearInView !== undefined && lastYearInView !== undefined) { + setYearViewHeaderBtnDisabledState(firstYearInView, lastYearInView); + } + } + function setDateViewHeaderBtnDisabledState(month: number, year: number) { + const { minDate, maxDate, minYear, maxYear } = props; + + const previousMonth = getPrevMonth(year, month); + const nextMonth = getNextMonth(year, month); + + const isPrevMonthDisabled = minDate ? isMonthDisabledByDisableUntil(previousMonth, {year: minDate.year, month: minDate.month, day: minDate.day}) : true; + const isNextMonthDisabled = maxDate ? isMonthDisabledByDisableSince(nextMonth, {year: maxDate.year, month: maxDate.month, day: maxDate.day}) : true; + + updateHeaderBtnDisabledState( + isPrevMonthDisabled, + isNextMonthDisabled, + month === 1 && year === minYear, + month === 12 && year === maxYear, + 'record' + ); + updateYearBtnDisabledState(year, year); + } + + function setMonthViewHeaderBtnDisabledState(year: number): void { + const { minDate, maxDate, minYear, maxYear } = props; + + const isPrevYearDisabled = minDate ? isMonthDisabledByDisableUntil(getPrevYearLastDay(year), minDate) : true; + const isNextYearDisabled = maxDate ? isMonthDisabledByDisableSince(getNextYearFirstDay(year), maxDate) : true; + + updateHeaderBtnDisabledState(isPrevYearDisabled, isNextYearDisabled, year === minYear, year === maxYear); + } + + watch(() => props.selectingMonth, (newValue, oldValue) => { + if (newValue) { + setMonthViewHeaderBtnDisabledState(activeMonth.value.year); + } + }); + + watch(() => props.selectingYear, (newValue, oldValue) => { + if (newValue) { + updateYearBtnDisabledState(); + } + }); + watch(() => props.years, (newValue, oldValue) => { years.value = newValue; + if (selectingYear.value) { + updateYearBtnDisabledState(); + } }); + watch( () => props.activeMonth, () => { + const monthData = props.activeMonth; + if (!monthData) {return;} // 增加防御性判断防止空指针 + activeMonth.value = { - month: props.activeMonth?.month, - year: props.activeMonth?.year, - displayTextOfMonth: props.activeMonth?.displayTextOfMonth, - displayTextOfYear: props.activeMonth?.displayTextOfYear + month: monthData.month, + year: monthData.year, + displayTextOfMonth: monthData.displayTextOfMonth, + displayTextOfYear: monthData.displayTextOfYear }; - } + + if (!props.selectingMonth && !props.selectingYear) { + setDateViewHeaderBtnDisabledState(activeMonth.value.month, activeMonth.value.year); + } else if (props.selectingMonth) { + setMonthViewHeaderBtnDisabledState(activeMonth.value.year); + } else { + updateYearBtnDisabledState(); + } + }, + {immediate: true} ); const navbarClass = computed(() => { @@ -134,15 +248,21 @@ export default defineComponent({ function navigateToPreviousPage($event: MouseEvent) { $event.stopPropagation(); + if (disablePrePageButton.value) { + return; + } context.emit('prePage'); } function navigateToPreviousRecord($event: MouseEvent) { $event.stopPropagation(); + if (disablePreRecordButton.value) { + return; + } context.emit('preRecord'); } - function onClearActiveYear($event: MouseEvent) { + function onClickActiveYear($event: MouseEvent) { $event.stopPropagation(); context.emit('clickYear'); } @@ -154,11 +274,17 @@ export default defineComponent({ function navigateToNextRecord($event: MouseEvent) { $event.stopPropagation(); + if (disableNextRecordButton.value) { + return; + } context.emit('nextRecord'); } function navigateToNextPage($event: MouseEvent) { $event.stopPropagation(); + if (disableNextPageButton.value) { + return; + } context.emit('nextPage'); } @@ -177,7 +303,7 @@ export default defineComponent({ style={yearViewButtonStyle.value} class={navbarSelectYearButtonClass.value} onClick={(payload: MouseEvent) => { - yearSelector.value && onClearActiveYear(payload); + yearSelector.value && onClickActiveYear(payload); }} tabindex={yearSelector.value ? '0' : '-1'} disabled={selectMode.value === 'year'}> @@ -207,15 +333,19 @@ export default defineComponent({ ); } + const disabledStyles = {opacity: 0.4,cursor: 'default' }; + return () => { return (
    - {shouldShowNavRecordButton.value && ( - )} - diff --git a/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.props.ts b/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.props.ts index f73ab9aeb8de6eb576010bf8b2cbed416aa6faa3..39fce1faadfe411d32e28c9e18355d2faa8594db 100644 --- a/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.props.ts +++ b/packages/ui-vue/components/date-picker/src/components/calendar-navbar/calendar-navbar.props.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ExtractPropTypes, PropType } from 'vue'; -import { type SelectMode } from '../../types/common'; +import { DateObject, type SelectMode } from '../../types/common'; export const calendarNavbarProps = { activeMonth: { type: Object, require: true }, @@ -28,7 +28,11 @@ export const calendarNavbarProps = { years: { type: Array, default: [[{}]] }, selectingMonth: { type: Boolean, default: false }, selectingYear: { type: Boolean, default: false }, - selectMode: { type: String as PropType, default: 'day' } + selectMode: { type: String as PropType, default: 'day' }, + minDate: { type: Object as PropType }, + maxDate: { type: Object as PropType }, + minYear: { type: Number, default: 1900 }, + maxYear: { type: Number, default: 9999 }, }; export type CalendarNavbarProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.component.tsx b/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.component.tsx index e06069a4285b2320a8aec09fac264c1cfb217079..cf8d91fd8eefc61250f131b2b68bbdd704c39b66 100644 --- a/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.component.tsx +++ b/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.component.tsx @@ -120,7 +120,7 @@ export default defineComponent({ const { quickSelectorList } = useQuickSelector(props, context); - const {getToday, getDateObject, getTimeValue, getEndTimeValue, emptyDate, convertDateToDateObject } = useDate(); + const {getToday, getDateObject, getTimeValue, getEndTimeValue, emptyDate, convertDateToDateObject, getNearDate } = useDate(); const { setNewDateRange, getActiveMonth, getTimeStr, getMonthAndYear } = useNormalizeDate(props); const selectedPeriod = ref({ from: { ...emptyDate() }, to: { ...emptyDate() } }); @@ -141,6 +141,10 @@ export default defineComponent({ weekDays.value = getWeekDays(); + if (props.locales?.weekTitle) { + weekTitle.value = props.locales.weekTitle; + } + const refDisableDate: UseDisableDate = useDisableDate( minYear.value, maxYear.value, @@ -164,7 +168,10 @@ export default defineComponent({ const { generateYears } = useYear(); const { equalOrEarlier, isInitializedDate, isDateEarlier } = useCompare(); - const today = getToday(); + let today = getToday(); + const todayIsDisabled = refDisableDate.isDisabledDate(today); + today = !todayIsDisabled ? today : getNearDate(today, disableUntil.value, disableSince.value); + const selectedDateObj = computed(() => { const todayClone = cloneDeep(today); const parsedDate = dateValue.value && !enablePeriod.value ? parseToDate(dateValue.value, valueFormat.value) : null; @@ -323,7 +330,9 @@ export default defineComponent({ }; }); - function getWidth() { return enablePeriod.value && selectMode.value !== 'week' ? 575 : props.showTime && selectMode.value !== 'week' ? 487 : 287; } + function getWidth() { + return enablePeriod.value && selectMode.value !== 'week' ? 575 : props.showTime && selectMode.value !== 'week' && props.inlineDateTime ? 487 : 287; + } const containerStyle = computed(() => { const styleObject = { @@ -526,7 +535,9 @@ export default defineComponent({ 'years': currentYears.value, 'selecting-month': selectingMonth.value, 'selecting-year': selectingYear.value, - 'select-mode': selectMode.value + 'select-mode': selectMode.value, + minDate: disableUntil.value, + maxDate: disableSince.value }; }); @@ -541,7 +552,9 @@ export default defineComponent({ 'years': secondaryYears.value, 'selecting-month': selectingSecondaryMonth.value, 'selecting-year': selectingSecondaryYear.value, - 'select-mode': selectMode.value + 'select-mode': selectMode.value, + minDate: disableUntil.value, + maxDate: disableSince.value }; }); @@ -633,7 +646,7 @@ export default defineComponent({ } else { const { year, month, day } = currentDate; selectedDate.value = Object.assign(selectedDate.value || {}, { year, month, day }); - timePicker.value.refresh(); + timePicker.value?.refresh(); } } @@ -1212,12 +1225,12 @@ export default defineComponent({ return isButton? {...styles}: { ...styles, "font-size": "15px" - } + }; }; }); const btnMonthStyle = computed(() => { - const { year, month } = today; + const { year, month } = getToday(); const maxDate = { year: disableSince.value.year, month: disableSince.value.month, day: disableSince.value.day }; const minDate = { year: disableUntil.value.year, month: disableUntil.value.month, day: disableUntil.value.day }; const disable: boolean = isMonthDisabledByDisableSince({ year, month, day: 1 }, maxDate) || @@ -1231,6 +1244,7 @@ export default defineComponent({ }); const btnCurrentYearStyle = computed(() => { + const today = getToday(); const canSelected = today.year ? today.year >= minYear.value && today.year <= maxYear.value : false; return { @@ -1263,8 +1277,13 @@ export default defineComponent({ const canConfirm = computed(() => { if (enablePeriod.value) { if (props.showTime) { + let canClick = true; const { begin, end } = updateRangeDate(); - const canClick = equalOrEarlier(begin, end); + if (refDisableDate.isDisabledDate(begin) || refDisableDate.isDisabledDate(end)) { + canClick = false; + } else { + canClick = equalOrEarlier(begin, end); + } return { "pointer-events": canClick ? "auto" : "none", @@ -1320,7 +1339,7 @@ export default defineComponent({ if (props.showTime) { timeValue.value = '00:00:00'; - timeRangeValue.value = '23:59:59' + timeRangeValue.value = '23:59:59'; } } else { selectedDate.value = { ...convertDateToDateObject(value) }; @@ -1385,6 +1404,7 @@ export default defineComponent({ return res; }; + // eslint-disable-next-line complexity return () => { return (
    - {props.showTime && !enablePeriod.value &&
    @@ -1434,10 +1455,10 @@ export default defineComponent({ } { showConfirmButton.value &&
    - {!enablePeriod.value && shouldShowCalendarView.value(false) && } + {!enablePeriod.value && shouldShowCalendarView.value(false) && } {!enablePeriod.value && shouldShowMonthView.value && } {!enablePeriod.value && shouldShowYearView.value && } - {enablePeriod.value && selectMode.value === 'day' &&
    + {(enablePeriod.value || !props.inlineDateTime) && selectMode.value === 'day' &&
    } {props.locales.buttons.commit} @@ -1445,7 +1466,7 @@ export default defineComponent({ } {!enablePeriod.value && !showConfirmButton.value &&
    - {shouldShowCalendarView.value(false) && } + {shouldShowCalendarView.value(false) && } {shouldShowMonthView.value && } {shouldShowYearView.value && }
    } diff --git a/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.props.ts b/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.props.ts index 2d8cf2aeb8996153d7dc8ce58682c972d9893f83..03210e1d8f3a2e08efed63ba54ea6026c87617a0 100644 --- a/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.props.ts +++ b/packages/ui-vue/components/date-picker/src/components/date-picker-container/date-picker-container.props.ts @@ -94,6 +94,8 @@ export const datePickerContainerProps = { value: { type: String, default: null }, /** 是否展示时分秒 */ showTime: {type: Boolean, default: false}, + + inlineDateTime: {type: Boolean, default: true}, /** 启用快捷选择 */ enableQuickSelect: {type: Boolean, default: false} } as Record; diff --git a/packages/ui-vue/components/date-picker/src/components/date-range/date-range.component.tsx b/packages/ui-vue/components/date-picker/src/components/date-range/date-range.component.tsx index 9bc57e4a2d09fcc9ba8ccf060b8be5442d7e740c..d6334b85bc3080b60bd85bd0ceac045573a5d86f 100644 --- a/packages/ui-vue/components/date-picker/src/components/date-range/date-range.component.tsx +++ b/packages/ui-vue/components/date-picker/src/components/date-range/date-range.component.tsx @@ -5,7 +5,7 @@ import { useDateFormat } from "@farris/ui-vue/components/common"; import { DateRangeProps, dateRangeProps } from "./date-range.props"; import FDatePickerContainer from '../date-picker-container/date-picker-container.component'; import { DateObject } from "../../types/common"; -import { useDate } from "../../composition/use-date"; +import { FDatePicker } from "@farris/ui-vue/components/components"; export default defineComponent({ name: 'FDateRange', @@ -16,8 +16,6 @@ export default defineComponent({ const maxYear = ref(props.maxYear); /** 最早年限 */ const minYear = ref(props.minYear); - /** 是否允许日期范围 */ - // const enablePeriod = ref(props.enablePeriod); /** 是否高亮周六 */ const highlightSaturday = ref(props.highlightSaturday); /** 是否高亮周日 */ @@ -94,6 +92,18 @@ export default defineComponent({ watch(modelValue, (newValue) => { setBeginAndEndValueFromModelValue(newValue); }); + + function setMaxAndMinYear() { + minYear.value = disableUntil.value.year ?? minYear.value; + maxYear.value = disableSince.value.year ?? maxYear.value; + + // 确保 minYear.value 不大于 maxYear.value + if (minYear.value > maxYear.value) { + maxYear.value = minYear.value; + } + } + + const buttonEditClass = computed(() => { const classObject = { 'f-button-edit': true, @@ -108,7 +118,7 @@ export default defineComponent({ const classObject = { 'f-cmp-inputgroup': true, 'input-group': true, - 'f-state-disable': props.disabled, + 'f-state-disabled': props.disabled, 'f-state-editable': true, 'align-items-center': true, 'f-state-readonly': props.readonly @@ -137,6 +147,7 @@ export default defineComponent({ shouldPopupContent.value = !shouldPopupContent.value; setTimeout(() => { if (shouldPopupContent.value) { + setMaxAndMinYear(); showDateRangePanel(); } }); @@ -178,7 +189,7 @@ export default defineComponent({ const formatEndDate = formatTo(endDateValueString, props.valueFormat); const resultDate = formatStartDate + '~' + formatEndDate; modelValue.value = resultDate; - context.emit('update:modeValue', resultDate); + context.emit('update:modelValue', resultDate); context.emit('confirm', resultDate); closeCalendarPanel(); }; @@ -190,12 +201,13 @@ export default defineComponent({ const onClear = () => { modelValue.value = ''; closeCalendarPanel(); - context.emit('update:modeValue', ''); + context.emit('update:modelValue', ''); context.emit('clear'); }; onMounted(() => { nameOfMonths.value = props.locales.monthLabels; + setMaxAndMinYear(); }); function onMouseEnter() { @@ -206,9 +218,58 @@ export default defineComponent({ showClearButton.value = false; } + function formatDateRange(startValue: any, endValue: any) { + const formatStartDate = formatTo(startValue, props.valueFormat); + const formatEndDate = formatTo(endValue, props.valueFormat); + let result = ''; + if (formatStartDate || formatEndDate) { + result = formatStartDate + '~' + formatEndDate; + } + modelValue.value = result; + context.emit('update:modelValue', result); + + if (!result) { + context.emit('clear'); + } else { + context.emit('confirm', result); + } + } + + function onBeginDatePicked(dateValue: any) { + formatDateRange(dateValue, newEndValue.value); + } + + function onEndDatePicked(dateValue: any) { + formatDateRange(newBeginValue.value, dateValue); + } + function renderFlexibleRange() { + return
    +
    +
    + onBeginDatePicked(dateValue)} + v-model={newBeginValue.value} displayFormat={props.displayFormat} valueFormat={props.valueFormat}> + + onEndDatePicked(dateValue)} + v-model={newEndValue.value} displayFormat={props.displayFormat} valueFormat={props.valueFormat}> +
    +
    +
    ; + } + return () => { return <> -
    + { props.splitSelection && renderFlexibleRange()} + { !props.splitSelection && <>
    -
    + {!props.disabled&&!props.readonly&&
    {props.enableClear && } '} onClick={onClickButton} > -
    +
    }
    {shouldPopupContent.value && } + } ; }; } diff --git a/packages/ui-vue/components/date-picker/src/components/date-range/date-range.props.ts b/packages/ui-vue/components/date-picker/src/components/date-range/date-range.props.ts index 70f61dccc6321076534d472237e43fee2464cc50..a6f798e45f969b1fec206a2995e013f3365154df 100644 --- a/packages/ui-vue/components/date-picker/src/components/date-range/date-range.props.ts +++ b/packages/ui-vue/components/date-picker/src/components/date-range/date-range.props.ts @@ -5,6 +5,8 @@ import { DatePickerLocale } from '../../composition/use-locales'; export const dateRangeProps = { ...datePickerProps, locales: { type: Object as PropType, default: {}}, + minYear: { type: Number, default: 1900 }, + maxYear: { type: Number, default: 9999 }, } as Record; export type DateRangeProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/date-picker/src/components/year/year.component.tsx b/packages/ui-vue/components/date-picker/src/components/year/year.component.tsx index 902d828e16f00c5a4f638c6bf5434b9071e89c1a..f6ceb0f9213e262b1dff4f866ba2fbd0bdcc7e16 100644 --- a/packages/ui-vue/components/date-picker/src/components/year/year.component.tsx +++ b/packages/ui-vue/components/date-picker/src/components/year/year.component.tsx @@ -52,7 +52,19 @@ export default defineComponent({ }; function isYearInPeriod(year: DateObject): boolean { - return inPeriod(year, selectedYearPeriod.value); + // return inPeriod(year, selectedYearPeriod.value); + if (!selectedYearPeriod.value) { + return false; + } + const { from, to } = selectedYearPeriod.value; + if (!isInitializedDate(to) || !isInitializedDate(from)) { + return false; + } + if (year?.year && from?.year && to?.year) { + return year.year >= from.year && year.year <= to.year; + } + return false; + } function isSelectedYear(year: DateObject): boolean { diff --git a/packages/ui-vue/components/date-picker/src/composition/types.ts b/packages/ui-vue/components/date-picker/src/composition/types.ts index a7a413540fc7cb7fa940adf4b40efe247c31fe98..bd03ed3079dd4b1ecf4aaae5963d0fa149f02005 100644 --- a/packages/ui-vue/components/date-picker/src/composition/types.ts +++ b/packages/ui-vue/components/date-picker/src/composition/types.ts @@ -52,7 +52,7 @@ export interface UseDate { getNearDate: (now: DateObject, min: DateObject, max: DateObject) => DateObject; - getToday(): Required; + getToday(): DateObject; getDateObject(dateString: string, dateFormat: string): DateObject; diff --git a/packages/ui-vue/components/date-picker/src/composition/use-date.ts b/packages/ui-vue/components/date-picker/src/composition/use-date.ts index b7f26d145d3270858a8d98184754f3b6f2953c5d..aef5eb8149084f5b66c072af597fa08f12a1d45c 100644 --- a/packages/ui-vue/components/date-picker/src/composition/use-date.ts +++ b/packages/ui-vue/components/date-picker/src/composition/use-date.ts @@ -98,7 +98,7 @@ export function useDate(): UseDate { return max; } - function getToday(): Required { + function getToday(): DateObject { const date: Date = new Date(); return { year: date.getFullYear(), diff --git a/packages/ui-vue/components/date-picker/src/composition/use-locales.ts b/packages/ui-vue/components/date-picker/src/composition/use-locales.ts index c41bb42758a95000a60d22d7b62e982101d75c1d..850bddae8a271a9a17452377542a3bbacb126140 100644 --- a/packages/ui-vue/components/date-picker/src/composition/use-locales.ts +++ b/packages/ui-vue/components/date-picker/src/composition/use-locales.ts @@ -1,5 +1,5 @@ +import { LocaleService } from '@farris/ui-vue/components/locale'; import { DatePickerProps } from "../date-picker.props"; -import { useI18n } from 'vue-i18n'; export interface DatePickerLocale { weekTitle: string; @@ -26,10 +26,9 @@ export interface DatePickerLocale { export function useDatePickerLocale(props: DatePickerProps): DatePickerLocale { - const { t: getLocaleValue, messages } = useI18n(); function getValue(localeKey, propertyValue?: string, defaultValue?: string): any { if (propertyValue === defaultValue) { - return getLocaleValue(localeKey); + return LocaleService.getLocaleValue(localeKey); } return propertyValue || ''; } diff --git a/packages/ui-vue/components/date-picker/src/composition/use-normalize-date.ts b/packages/ui-vue/components/date-picker/src/composition/use-normalize-date.ts index 3667b4f4eb7d2d0a0db54abb2fab79f8fe15f5a7..59f057ef1829dfd38a8b82a5f190e9141a181de5 100644 --- a/packages/ui-vue/components/date-picker/src/composition/use-normalize-date.ts +++ b/packages/ui-vue/components/date-picker/src/composition/use-normalize-date.ts @@ -49,22 +49,25 @@ export function useNormalizeDate(props: DatePickerProps): UseNormalizeDate { function getMonthAndYear() { const today = getToday(); + const thisYear: any = today.year; + const thisMonth: any = today.month; + switch (selectMode) { case 'month': return { - startMonth: { month: 0, year: today.year }, - endMonth: { month: 0, year: today.year + 1 } + startMonth: { month: 0, year: thisYear }, + endMonth: { month: 0, year: thisYear + 1 } }; case 'year': return { - startMonth: { month: 0, year: today.year }, - endMonth: { month: 0, year: today.year + 10 } + startMonth: { month: 0, year: thisYear }, + endMonth: { month: 0, year: thisYear + 10 } }; default: { - const startMonth = { month: today.month, year: today.year }; - let endMonth = { month: today.month + 1, year: today.year }; - if (today.month >= 12) { - endMonth = { month: 1, year: today.year + 1 }; + const startMonth = { month: thisMonth, year: thisYear }; + let endMonth = { month: thisMonth + 1, year: thisYear }; + if (thisMonth >= 12) { + endMonth = { month: 1, year: thisYear + 1 }; } return { startMonth, endMonth }; diff --git a/packages/ui-vue/components/date-picker/src/date-picker.component.tsx b/packages/ui-vue/components/date-picker/src/date-picker.component.tsx index 385e1b4d0fa82aa49a6d835d8912537e4603e633..08ac4dacfe2d936ed3cc56e350d232e02598e864 100644 --- a/packages/ui-vue/components/date-picker/src/date-picker.component.tsx +++ b/packages/ui-vue/components/date-picker/src/date-picker.component.tsx @@ -16,20 +16,18 @@ import { computed, defineComponent, onMounted, ref, SetupContext, watch } from 'vue'; import { DatePickerProps, datePickerProps, MAX_DATE, MIN_DATE } from './date-picker.props'; import FButtonEdit from '@farris/ui-vue/components/button-edit'; -import { useDateFormat } from '@farris/ui-vue/components/common'; +import { useDateFormat, isMobilePhone } from '@farris/ui-vue/components/common'; import FDatePickerContainer from './components/date-picker-container/date-picker-container.component'; import FDateRange from './components/date-range/date-range.component'; import { DateObject } from './types/common'; -import './date-picker.css'; import { useDate } from './composition/use-date'; - import { useDatePickerLocale } from './composition/use-locales'; export default defineComponent({ name: 'FDatePicker', props: datePickerProps, - emits: ['update:modelValue', 'datePicked'] as (string[] & ThisType) | undefined, + emits: ['update:modelValue', 'datePicked', 'clear'] as (string[] & ThisType) | undefined, setup(props: DatePickerProps, context: SetupContext) { const groupIcon = ''; const buttonEdit = ref(null); @@ -80,6 +78,20 @@ export default defineComponent({ const { formatTo, parseToDate } = useDateFormat(); + const inlineDateTime = computed(() => { + if (isMobilePhone()) { + return false; + } + return props.inlineDateTime; + }); + + const splitSelection = computed(() => { + if (isMobilePhone()) { + return true; + } + return props.splitSelection; + }) + /** * 可能只有beginValue或者endValue的值,modelValue没有设置值 * @returns @@ -104,13 +116,15 @@ export default defineComponent({ return formatTo(dateObj, props.displayFormat); }); - function setModelValue(dateValue: string) { + function setModelValue(dateValue: string, emit = true) { if (enablePeriod.value) { if (modelValue.value !== dateValue) { modelValue.value = dateValue; realValue.value = dateValue; - context.emit('update:modelValue', dateValue); - context.emit('datePicked', dateValue); + if (emit) { + context.emit('update:modelValue', dateValue); + context.emit('datePicked', dateValue); + } } return; } @@ -118,8 +132,10 @@ export default defineComponent({ if (modelValue.value !== formattedValue) { modelValue.value = formattedValue; realValue.value = formattedValue; - context.emit('update:modelValue', formattedValue); - context.emit('datePicked', formattedValue); + if (emit) { + context.emit('update:modelValue', formattedValue); + context.emit('datePicked', formattedValue); + } } } @@ -156,8 +172,15 @@ export default defineComponent({ closeCalendarPanel(); } + function clear() { + modelValue.value = ''; + realValue.value = ''; + context.emit('update:modelValue', ''); + context.emit('clear'); + } + function onClearDate() { - setModelValue(''); + clear(); closeCalendarPanel(); } @@ -173,7 +196,7 @@ export default defineComponent({ watch(() => props.modelValue, (newValue, oldValue) => { if (newValue !== oldValue) { - setModelValue(newValue); + setModelValue(newValue, false); } }); @@ -233,10 +256,6 @@ export default defineComponent({ }); onMounted(() => { - // if (modelValue.value) { - // setModelValue(modelValue.value); - // } - setMaxAndMinYear(); }); @@ -251,6 +270,7 @@ export default defineComponent({ disableSince={maxDate.value} disableUntil={minDate.value} locales={datePickerLocales} + splitSelection={splitSelection.value && selectMode.value !== 'week'} onConfirm={onConfirm} onClear={onClearDate} > : @@ -271,8 +291,8 @@ export default defineComponent({ focusOnCreated={props.focusOnCreated} selectOnCreated={props.selectOnCreated} keepWidthWithReference={false} - placement={'auto'}> - + placement={'auto'} + enableTitle={true}> {{ default: () => onDatePicked(dateValue)} diff --git a/packages/ui-vue/components/date-picker/src/date-picker.css b/packages/ui-vue/components/date-picker/src/date-picker.css deleted file mode 100644 index 90eea9f0289109068aca0b50f0663231a9fe162d..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/date-picker/src/date-picker.css +++ /dev/null @@ -1,48 +0,0 @@ -.f-datepicker-container .time-picker-panel .time-picker-panel-inner { - box-shadow:none; -} - -.f-datepicker-current.f-datepicker-selected.f-datepicker-disabled, -.f-datepicker-date.f-datepicker-selected.f-datepicker-disabled { - opacity: 0.4; - color: white !important; -} - -.f-datepicker-container .f-datepicker-content -.f-datepicker-table-wrapper .f-datepicker-table td .f-datepicker-disabled { - cursor: default; -} - -.f-datepicker-content.datepicker-content-has-timer .time-picker-panel-select ul li:last-child::after { - height: 198px !important; -} - -.f-datepicker-quick-selector { - overflow: hidden; - width: 90px!important; - border-right: 1px #E8EBF2 solid; -} - -.f-datepicker-quick-selector:hover{ - overflow: overlay; -} - -.f-datepicker-quick-selector li{ - font-size: 13px; - color: #2D2F33; - cursor: pointer; - padding: 3px 6px; - line-height: 22px; - word-wrap: normal; - white-space: nowrap; -} - -.f-datepicker-quick-selector li:hover{ - background-color: #EEF5FF; - border-radius: 6px; -} - -.f-datepicker-content.datepicker-content-has-timer ul li.time-picker-panel-select-option-disabled { - opacity: .5; - cursor: not-allowed; -} \ No newline at end of file diff --git a/packages/ui-vue/components/date-picker/src/date-picker.props.ts b/packages/ui-vue/components/date-picker/src/date-picker.props.ts index d883d444f7d4f83c2a69bad63f77269f98bcb7f5..715cdcd4ee1385f2d8d348a750c3655b2ca23ee0 100644 --- a/packages/ui-vue/components/date-picker/src/date-picker.props.ts +++ b/packages/ui-vue/components/date-picker/src/date-picker.props.ts @@ -65,6 +65,8 @@ export const datePickerProps = { displayFormat: { Type: String, default: 'yyyy-MM-dd' }, /** 是否允许日期范围 */ enablePeriod: { type: Boolean, default: false }, + /** 日期区间是否启用分开选择开始、结束日期,默认为 false */ + splitSelection: { type: Boolean, default: false }, /** * 作为内嵌编辑器被创建后默认获得焦点 */ @@ -89,6 +91,9 @@ export const datePickerProps = { * 是否展示时分秒 */ showTime: { type: Boolean, default: false }, + /** 时间显示模式,与日期并列显示*/ + inlineDateTime : { type: Boolean, default: true }, + showWeekNumber: {type: Boolean, default: false}, weekTitle: { type: String, default: '周' }, /** 显示中间图标 */ diff --git a/packages/ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts b/packages/ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts index f0a35ba5a438eb81f3ad386a37715f90cfd07dc1..0217ad97513c20b8502f9932510ecd004afabc73 100644 --- a/packages/ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts +++ b/packages/ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts @@ -65,7 +65,7 @@ export class DatePickerProperty extends InputBaseProperty { getEditorProperties(propertyData: any) { const displayFormatOptions = this.getDateFormatOptions(propertyData?.editor); const valueFormatOptions = this.getValueFormatOptions(propertyData?.editor); - const bindingFieldType = this.designViewModelField.type.name; + const bindingFieldType = this.designViewModelField?.type?.name; const selectModes = this.getDatePickerModes(bindingFieldType); const extremeValueFormat = this.getExtremeDateFormat(propertyData); @@ -195,13 +195,12 @@ export class DatePickerProperty extends InputBaseProperty { } } - private getEventPropConfig(propertyData: any) { + getEventPropertyConfig(propertyData: any) { const self = this; const events = [ { label: 'onDatePicked', name: '日期选择后事件' } ]; - const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); const properties = self.createBaseEventProperty(initialData); diff --git a/packages/ui-vue/components/date-picker/src/schema/date-picker.schema.json b/packages/ui-vue/components/date-picker/src/schema/date-picker.schema.json index e50a59febf430822ecdccc49d9f6b9d6d34a4fa7..910d0846776555e839df07ea7a5f30f896bc9936 100644 --- a/packages/ui-vue/components/date-picker/src/schema/date-picker.schema.json +++ b/packages/ui-vue/components/date-picker/src/schema/date-picker.schema.json @@ -33,7 +33,8 @@ "default": {} }, "disabled": { - "type": "string", + "description": "", + "type": "boolean", "default": false }, "editable": { @@ -150,5 +151,8 @@ "appearance", "binding", "visible" - ] + ], + "events": { + "onDatePicked": "日期选择后事件" + } } \ No newline at end of file diff --git a/packages/ui-vue/components/designer-canvas/src/components/designer-item.component.tsx b/packages/ui-vue/components/designer-canvas/src/components/designer-item.component.tsx index 644e7f7620f36d5ebe1ee2ad884f49d066126237..4f7b6ef02b2232d8ba5ef49180b147277b351f38 100644 --- a/packages/ui-vue/components/designer-canvas/src/components/designer-item.component.tsx +++ b/packages/ui-vue/components/designer-canvas/src/components/designer-item.component.tsx @@ -7,6 +7,7 @@ import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from import FDesignerPlaceholder from './designer-placeholder.component'; import { canvasChanged, setPositionOfButtonGroup, setPositionOfButtonGroupInContainer } from '../composition/designer-canvas-changed'; import { getCustomClass } from '@farris/ui-vue/components/common'; +import { BuilderHTMLElement } from '../composition/entity/builder-element'; const FDesignerItem = defineComponent({ name: 'FDesignerItem', @@ -199,6 +200,7 @@ const FDesignerItem = defineComponent({ const propsConverter = componentPropsConverter[componentKey]; const viewProps = propsConverter ? propsConverter(viewSchema) : {}; viewProps.customClass = props.ignore ? viewProps.customClass : ''; + viewProps.customStyle = props.ignore ? viewProps.customStyle : ''; viewProps.componentId = componentId.value; viewProps.id = viewSchema.id; const shouldShowPlaceholder = viewSchema.contents && viewSchema.contents.length === 0; @@ -267,12 +269,15 @@ const FDesignerItem = defineComponent({ /** * 记录滚动区域 */ - function recordScrollContainer(element: HTMLElement) { + function recordScrollContainer(element: BuilderHTMLElement) { if (!window['scrollContainerList']) { window['scrollContainerList'] = new Set(); } const id = element.getAttribute('id'); if (id) { window['scrollContainerList'].add(id); + if (element.componentInstance.value) { + element.componentInstance.value.scrollElementId = id; + } } } @@ -316,6 +321,7 @@ const FDesignerItem = defineComponent({ componentInstance.value.belongedComponentId = componentId.value; componentInstance.value.setComponentBasicInfoMap(designerHostService); + componentInstance.value.externalContainerId = externalContainerId; } bindingScrollEvent(); @@ -358,17 +364,15 @@ const FDesignerItem = defineComponent({ designerItemElement.classList.add('dgComponentFocused'); context.emit('selectionChange', schema.value.type, schema.value, componentId.value, componentInstance.value); - if (componentInstance.value.getDraggableDesignItemElement) { - draggabledesignerItemElementRef = componentInstance.value.getDraggableDesignItemElement(designItemContext); - if (draggabledesignerItemElementRef && draggabledesignerItemElementRef.value) { - draggabledesignerItemElementRef.value.classList.add('dgComponentSelected'); - - } - } - } } + if (componentInstance.value.getDraggableDesignItemElement) { + draggabledesignerItemElementRef = componentInstance.value.getDraggableDesignItemElement(designItemContext); + if (draggabledesignerItemElementRef && draggabledesignerItemElementRef.value) { + draggabledesignerItemElementRef.value.classList.add('dgComponentSelected'); + } + } setPositionOfButtonGroup(draggabledesignerItemElementRef?.value); } diff --git a/packages/ui-vue/components/designer-canvas/src/components/maps.ts b/packages/ui-vue/components/designer-canvas/src/components/maps.ts index e7415c192da93f51f02de5377ab7b10ea3df1209..77853e6c092e6bc9c7f7a827294584e4774c9875 100644 --- a/packages/ui-vue/components/designer-canvas/src/components/maps.ts +++ b/packages/ui-vue/components/designer-canvas/src/components/maps.ts @@ -57,82 +57,105 @@ import FTreeGrid from '@farris/ui-vue/components/tree-grid/designer'; import FFieldset from '@farris/ui-vue/components/fieldset'; import FDrawer from '@farris/ui-vue/components/drawer/designer'; import FHtmlTemplate from '@farris/ui-vue/components/html-template'; -import { RegisterContext } from '@farris/ui-vue/components/common'; -import { propertyConfigSchemaMapForDesigner, propertyEffectMapForDesigner } from '@farris/ui-vue/components/dynamic-resolver'; +import FImage from '@farris/ui-vue/components/image'; +import FComment from '@farris/ui-vue/components/comment'; +import { RegisterContext, useThirdComponent } from '@farris/ui-vue/components/common'; +import { createPropsResolver, propertyConfigSchemaMapForDesigner, propertyEffectMapForDesigner } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapForDesigner, schemaResolverMapForDesigner } from '@farris/ui-vue/components/dynamic-resolver'; +// import { FHtmlEditor} from '@farris/ui-editor-vue3'; const componentMap: Record = {}; const componentPropsConverter: Record = {}; const componentPropertyConfigConverter: Record = {}; +const { globalStorageKey } = useThirdComponent(); + +const hasLoaded = false; -let hasLoaded = false; /** * 加载设计时组件,后续移除该方法,通过registerDesignerComponents注册组件 */ function loadDesignerRegister() { if (!hasLoaded) { - hasLoaded = true; - FAvatar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FAccordion.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FButtonEdit.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FButtonGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FCalendar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FCapsule.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FCheckbox.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FCheckboxGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FCheckboxGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FColorPicker.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FComboList.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FContentContainer.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FDataGrid.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FDatePicker.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + const registerContext: RegisterContext = { + schemaMap: schemaMapForDesigner, + propertyConfigSchemaMap: propertyConfigSchemaMapForDesigner, + propertyEffectMap: propertyEffectMapForDesigner, + schemaResolverMap: schemaResolverMapForDesigner + }; + // hasLoaded = true; + FAvatar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FAccordion.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FButtonEdit.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FButtonGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FCalendar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FCapsule.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FCheckbox.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FCheckboxGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FCheckboxGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FColorPicker.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FComboList.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FContentContainer.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FDataGrid.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FDatePicker.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FDropdown.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FDynamicForm.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FDynamicForm.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FExternalContainer.registerDesigner(componentMap, componentPropsConverter,componentPropertyConfigConverter); - FFilterBar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FImageCropper.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FInputGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FLayout.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FListView.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FFilterBar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FImageCropper.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FInputGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FLayout.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FListView.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FListNav.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); FNav.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FLookup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FLanguageTextbox.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FNumberRange.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FNumberSpinner.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FLookup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FLanguageTextbox.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FNumberRange.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FNumberSpinner.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FOrder.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FPageHeader.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FPageHeader.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FPageFooter.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FPagination.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FProgress.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FQuerySolution.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FRadioGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FResponseLayout.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FResponseLayoutEditorSetting.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FResponseToolbar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FRate.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FSearchBox.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FSection.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FPagination.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FProgress.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FQuerySolution.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FRadioGroup.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FResponseLayout.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FResponseLayoutEditorSetting.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FResponseToolbar.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FRate.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FSearchBox.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FSection.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FSmokeDetector.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FSplitter.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FStep.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FSwitch.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FTab.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FTags.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FSplitter.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FStep.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FSwitch.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FTab.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FTags.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FText.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FTimePicker.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FTransfer.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FTreeview.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FUploader.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FTimePicker.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FTransfer.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FTreeview.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FUploader.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FVerifyDetail.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); FComponent.registerDesigner(componentMap, componentPropsConverter); FVideo.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); FTextArea.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); FTreeGrid.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FFieldset.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FFieldset.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); FDrawer.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); - FHtmlTemplate.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FHtmlTemplate.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + FImage.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + FComment.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + + // FHtmlEditor.createPropsResolver = createPropsResolver; + // FHtmlEditor.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + const thirdComponents = window[globalStorageKey]; + if (thirdComponents) { + for(const key in thirdComponents) { + thirdComponents[key].createPropsResolver = createPropsResolver; + thirdComponents[key].registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter); + } + } } } @@ -144,7 +167,7 @@ function registerDesignerComponents(components: any[]) { return; } - hasLoaded = true; + // hasLoaded = true; const registerContext:RegisterContext = { schemaMap: schemaMapForDesigner, propertyConfigSchemaMap: propertyConfigSchemaMapForDesigner, @@ -155,6 +178,7 @@ function registerDesignerComponents(components: any[]) { components.forEach(component => { component.registerDesigner && component.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); }); + } diff --git a/packages/ui-vue/components/designer-canvas/src/composition/class/control.css b/packages/ui-vue/components/designer-canvas/src/composition/class/control.css index 9044cd61f76655fb7d387bb70f82e0a3516a5b7d..db6414aefd3f48e947e180f7e45aa13252de7f29 100644 --- a/packages/ui-vue/components/designer-canvas/src/composition/class/control.css +++ b/packages/ui-vue/components/designer-canvas/src/composition/class/control.css @@ -5,7 +5,7 @@ } /** 标签页tabs **/ -.farris-component-tabs .farris-tabs-content.f-utils-fill-flex-column .farris-component-tab-page:has(.farris-tab-page-active) { +.farris-component-tabs .farris-tabs-content.f-utils-fill-flex-column .farris-component-tab-page:has(.f-tab-active) { display: flex !important; overflow: hidden; flex-shrink: 1; diff --git a/packages/ui-vue/components/designer-canvas/src/composition/class/designer-canvas.css b/packages/ui-vue/components/designer-canvas/src/composition/class/designer-canvas.css index da77ba920d56bddfc7dec164369f87d75fd183a7..31f0b5a1ab1df1bcabd87bfcebf228cb59fb7947 100644 --- a/packages/ui-vue/components/designer-canvas/src/composition/class/designer-canvas.css +++ b/packages/ui-vue/components/designer-canvas/src/composition/class/designer-canvas.css @@ -85,7 +85,7 @@ align-items: inherit; width: 100%; overflow: inherit; - height: inherit; + height: 100%; } /* gu-mirror被添加到镜像中 */ @@ -216,14 +216,9 @@ margin-bottom: 15px; } -.f-struct-wrapper+.f-struct-wrapper { +.editorDiv .f-struct-wrapper+.f-struct-wrapper { position: relative; display: inherit; - margin-bottom: 15px; -} - -.f-page-is-mainsubcard .f-page-main .f-struct-wrapper { - background: #fff; } /** 解决带导航的列表和带导航的卡片模板中,不显示右侧区域的问题 */ @@ -231,12 +226,6 @@ position: absolute !important; } -/** 解决带导航的列表和带导航的卡片模板中,右侧区域滚动条位置问题 */ -.editorDiv .f-page-navigate .f-page.f-page-card .f-page-main>.drag-container { - display: block; - overflow: unset; -} - /* 解决OA模板(带页头的导航类模板) 中,右侧滚动条位置问题 */ .editorDiv .f-page.f-page-navigate.f-page-is-listnav-with-header .f-page-main .f-page-content-main { display: block; @@ -295,20 +284,30 @@ /** 若没有这个,struct-wrapper内部放任意容器的时候,无法出现滚动条 **/ .f-page-child-fill .f-struct-wrapper.f-struct-wrapper-child { - overflow: hidden; + display: flex !important; +} + +.f-page-child-fill .f-struct-wrapper.f-struct-wrapper-child.farris-component { + overflow: hidden; } /** 设计器section的适配样式 **/ .f-page-child-fill .f-struct-wrapper.f-struct-wrapper-child .f-section-in-mainsubcard { - height: 100% + height: 100%; } .f-page-child-fill .f-struct-wrapper.f-struct-wrapper-child .f-section-in-main { - height: 100% + height: 100%; } .f-page-child-fill .f-struct-wrapper.f-struct-wrapper-child .f-section.f-section-fill>.f-section-content { - flex-basis: 0%; + flex-basis: 0% !important; +} + +.f-page-child-fill .f-struct-wrapper.f-struct-wrapper-child:has(.drag-container .f-section-accordion.f-state-collapse) { + min-height: 70px; + display: block; + flex: initial; } /** 标签页的滚动条要放到tab-body上,所以整个tab上设置overflow:hidden **/ @@ -335,3 +334,7 @@ font-size:13px; line-height: unset; } + +.editorDiv .editorPanel .f-drawer-entry { + z-index: 799; +} diff --git a/packages/ui-vue/components/designer-canvas/src/composition/designer-canvas-changed.ts b/packages/ui-vue/components/designer-canvas/src/composition/designer-canvas-changed.ts index 867da936d1b060e718f9ffafd7d62fc6527dd999..22ffdeea95451729ea4761dfccdeb39e2145d84d 100644 --- a/packages/ui-vue/components/designer-canvas/src/composition/designer-canvas-changed.ts +++ b/packages/ui-vue/components/designer-canvas/src/composition/designer-canvas-changed.ts @@ -1,5 +1,5 @@ -import { unset } from "lodash"; import { ref } from "vue"; +import { BuilderHTMLElement } from "./entity/builder-element"; /** 用于响应画布发生变更 */ export const canvasChanged = ref(0); @@ -12,7 +12,7 @@ export function changeCanvas() { * @param el 元素 * @param containerEl 容器 */ -function isElementInViewport(el: HTMLElement, containerEl: HTMLElement) { +function isElementInViewport(el: BuilderHTMLElement, containerEl: BuilderHTMLElement) { const container = containerEl.getBoundingClientRect(); const box = el.getBoundingClientRect(); const top = box.top >= container.top; @@ -22,7 +22,7 @@ function isElementInViewport(el: HTMLElement, containerEl: HTMLElement) { /** * 计算选中控件的绝对位置 */ -export function setPositionForSelectedElement(selectElement: HTMLElement) { +export function setPositionForSelectedElement(selectElement: BuilderHTMLElement) { const toolbar = selectElement.querySelector('.component-btn-group') as HTMLElement; if (!toolbar) { return; @@ -52,7 +52,7 @@ export function setPositionForSelectedElement(selectElement: HTMLElement) { /** * 获取选中控件所在的滚动区域 */ -function getScrollParentElementWhenClick(selectElement: HTMLElement) { +function getScrollParentElementWhenClick(selectElement: BuilderHTMLElement): BuilderHTMLElement | undefined { if (!window['scrollContainerList']) { return; } @@ -69,13 +69,28 @@ function getScrollParentElementWhenClick(selectElement: HTMLElement) { return scrollContainer; } + } else { + // 2、若当前有多个滚动区域:需要定位到当前点击的控件是属于哪个滚动区域 + const { componentInstance: selectComponentInstance } = selectElement; + if (selectComponentInstance?.value?.scrollElementId === selectComponentInstance?.value.schema?.id) { + // ①滚动条出现在drag-container层级 + return selectElement; + } + if (selectComponentInstance?.value?.scrollElementId === `${selectComponentInstance?.value.schema?.id}-design-item`) { + // ②滚动条出现在组件层级 + return selectElement.parentElement as BuilderHTMLElement; + } + + if (selectComponentInstance.value.parent?.elementRef) { + return getScrollParentElementWhenClick(selectComponentInstance.value.parent.elementRef); + } } } /** * 定位画布中已选控件的操作按钮的位置 * @param selectElement 选中的控件元素 */ -export function setPositionOfButtonGroup(selectElement: HTMLElement) { +export function setPositionOfButtonGroup(selectElement: BuilderHTMLElement) { if (!selectElement) { return; } @@ -136,7 +151,7 @@ export function setPositionOfButtonGroupInContainer(containerElement: HTMLElemen if (!containerElement) { return; } - let selectElement: HTMLElement; + let selectElement: BuilderHTMLElement; if (containerElement.className.includes('dgComponentSelected')) { selectElement = containerElement; } else { @@ -151,24 +166,21 @@ export function setPositionOfButtonGroupInContainer(containerElement: HTMLElemen /** * 定位页面中选中控件的操作按钮位置。 - * 场景:控件内部点击收折或者切换显示内容后,需要重新计算页面中下方选中控件的按钮位置。 - * 例如点击section控件的收折图标后,需要重新计算section下方已选控件的操作按钮位置 + * 场景:控件内部点击收折或者切换显示内容后,需要重新计算页面中上方或下方选中控件的按钮位置。 + * 例如点击section控件的收折图标后,需要重新计算页面中已选控件的操作按钮位置 */ -export function setPositionOfSelectedComponentBtnGroup(canvasElement: HTMLElement) { +export function setPositionOfSelectedComponentBtnGroup() { const selectElement = document.querySelector('.dgComponentSelected') as HTMLElement; if (!selectElement) { return; } - const selectedElementRect = selectElement.getBoundingClientRect(); - const elementRect = canvasElement.getBoundingClientRect(); const toolbar = selectElement.querySelector('.component-btn-group') as HTMLElement; if (toolbar) { const toolbarRect = toolbar.getBoundingClientRect(); - const isBelow = elementRect.top < selectedElementRect.top; - // 选中控件已显示并且在基准位置的下方 - if (toolbarRect.top !== 0 && isBelow) { + // 选中控件已显示 + if (toolbarRect.top !== 0) { setPositionForSelectedElement(selectElement); } } diff --git a/packages/ui-vue/components/designer-canvas/src/composition/dg-control.ts b/packages/ui-vue/components/designer-canvas/src/composition/dg-control.ts index be537516382551067ce6cd89586c9f281407bf46..b1183a14779d69bc78a56a286f146a5feb8d80a5 100644 --- a/packages/ui-vue/components/designer-canvas/src/composition/dg-control.ts +++ b/packages/ui-vue/components/designer-canvas/src/composition/dg-control.ts @@ -50,6 +50,7 @@ export const DgControl = { 'tab-page': { type: 'tab-page', name: '标签页项', dependentParentControl: 'Tab' }, 'tab-toolbar-item': { type: 'tab-toolbar-item', name: '标签页工具栏按钮' }, + 'drawer-toolbar-item': { type: 'drawer-toolbar-item', name: '抽屉工具栏按钮' }, 'html-template': { type: 'html-template', name: '模板容器' }, @@ -74,6 +75,7 @@ export const DgControl = { 'page-footer': { type: 'page-footer', name: '页脚' }, 'tab-toolbar': { type: 'tab-toolbar', name: '标签页工具栏' }, + 'drawer-toolbar': { type: 'drawer-toolbar', name: '抽屉工具栏' }, 'fieldset': { type: 'fieldset', name: '分组' }, @@ -86,8 +88,15 @@ export const DgControl = { 'list-nav': { type: 'list-nav', name: '列表导航' }, 'list-view': { type: 'list-view', name: '列表' }, - + 'filter-bar': { type: 'filter-bar', name: '筛选条' }, 'language-textbox': { type: 'language-textbox', name: '多语输入框' }, + + 'image': { type: 'image', name: '图像' }, + 'discussion-editor': { type: 'discussion-editor', name: '评论编辑区' }, + 'discussion-list': { type: 'discussion-list', name: '评论列表' }, + 'comment': { type: 'comment', name: '评论区' }, + 'rich-text-editor': { type: 'rich-text-editor', name: '富文本', icon: 'rich-text-box' }, + 'calendar': { type: 'calendar', name: '日历', icon: 'date-picker' }, }; diff --git a/packages/ui-vue/components/designer-canvas/src/composition/function/drag-resolve.tsx b/packages/ui-vue/components/designer-canvas/src/composition/function/drag-resolve.tsx index 6bed99f1019ef2fe2a5f3ec5f9788a7f15d6e894..8b43a86242c09d1e136e49867a12966d4423cff8 100644 --- a/packages/ui-vue/components/designer-canvas/src/composition/function/drag-resolve.tsx +++ b/packages/ui-vue/components/designer-canvas/src/composition/function/drag-resolve.tsx @@ -68,12 +68,12 @@ export function dragResolveService(designerHostService: DesignerHostService) { * 选择绑定实体窗口 */ function renderEntityComponent() { - const { componentType, bindingTargetId } = componentResolveContext; + const { componentType, bindingTargetId, parentComponentInstance } = componentResolveContext; let steps: string[] = []; // 从控件树拖拽:已确定控件类型,需要选择实体以及实体中的字段。 if (componentType) { - // listview 不选择字段 - if (componentType === 'list-view') { + // listview 、日历只选择实体,不选择字段 + if (componentType === 'list-view' || componentType === 'calendar') { steps = ['selectEntity']; } else { steps = ['selectEntity', 'selectFields']; @@ -88,6 +88,7 @@ export function dragResolveService(designerHostService: DesignerHostService) { bindingEntityId={bindingTargetId} steps={steps} designerHostService={designerHostService} + targetComponentInstance={parentComponentInstance} onSubmit={onSubmitEntitySelctor} onCancel={onCancelEntitySelector}> ); } @@ -97,10 +98,11 @@ export function dragResolveService(designerHostService: DesignerHostService) { function triggerBindingEntity() { return new Promise((resolve, reject) => { const { componentType } = componentResolveContext; + const selectEntityOnly = componentType === 'list-view' || componentType === 'calendar'; modalEditorRef = designerHostService.modalService.open({ title: '选择绑定', - width: componentType === 'list-view' ? 550 : 800, - height: componentType === 'list-view' ? 400 : 600, + width: selectEntityOnly ? 550 : 800, + height: selectEntityOnly ? 400 : 600, fitContent: false, showButtons: false, render: renderEntityComponent(), @@ -234,6 +236,8 @@ export function dragResolveService(designerHostService: DesignerHostService) { // 当前组件的类型 let targetComponentType = targetComponentNode.componentType; + // 当前组件是否在侧边栏中 + const targetComponentInDrawer = formSchemaUtils.checkComponentExistInDrawer(targetComponentNode.id); // 根组件和table组件内的输入控件与form内不能重复 if (targetComponentType === 'frame' || targetComponentType === 'table') { @@ -252,10 +256,22 @@ export function dragResolveService(designerHostService: DesignerHostService) { if (componentType !== targetComponentType || viewModel.bindTo !== targetViewModelNode.bindTo) { return; } + // 组件是否在侧边栏中 + const componentInDrawer = formSchemaUtils.checkComponentExistInDrawer(componentNode.id); - viewModel.fields.forEach(field => { - occupiedFieldSet.add(field.id); - }); + // 若当前组件在侧边栏中,那么只收集同样在侧边栏中的字段。 + if (targetComponentInDrawer && componentInDrawer) { + viewModel.fields.forEach(field => { + occupiedFieldSet.add(field.id); + }); + } + + // 若当前组件不在侧边栏中,那么只收集不在侧边栏中的字段 + if (!targetComponentInDrawer && !componentInDrawer) { + viewModel.fields.forEach(field => { + occupiedFieldSet.add(field.id); + }); + } }); return occupiedFieldSet; } diff --git a/packages/ui-vue/components/designer-canvas/src/composition/function/use-dragula.ts b/packages/ui-vue/components/designer-canvas/src/composition/function/use-dragula.ts index fc243576dba6248f4643b57cb35f445b2a6fd8a4..86a57513be4067c67293bbc2d54569e5182a182a 100644 --- a/packages/ui-vue/components/designer-canvas/src/composition/function/use-dragula.ts +++ b/packages/ui-vue/components/designer-canvas/src/composition/function/use-dragula.ts @@ -33,6 +33,10 @@ export function useDragula(designerHostService: DesignerHostService, designerCon } } } + // 外部容器内渲染出的组件层级,不接收拖拽的控件 + if (target.componentInstance?.value?.externalContainerId) { + return false; + } if (target.componentInstance && target.componentInstance.value.canAccepts) { const dragResolveUtil = dragResolveService(designerHostService); const draggingContext = dragResolveUtil.getComponentResolveContext(element, sourceContainer, target); diff --git a/packages/ui-vue/components/designer-canvas/src/composition/rule/use-dragula-common-rule.ts b/packages/ui-vue/components/designer-canvas/src/composition/rule/use-dragula-common-rule.ts index 2a618e6b6d206c7aca73760c245e2e9a7ec7478c..f81e7fa1f9f6addb8895419e3dddc9eab9d8cdff 100644 --- a/packages/ui-vue/components/designer-canvas/src/composition/rule/use-dragula-common-rule.ts +++ b/packages/ui-vue/components/designer-canvas/src/composition/rule/use-dragula-common-rule.ts @@ -34,18 +34,25 @@ export function useDragulaCommonRule() { if (belongedComponentType !== 'frame') { return false; } - if (![DgControl['content-container'].type, DgControl['splitter-pane'].type, DgControl['response-layout-item'].type].includes(targetContainerType)) { + if (![DgControl['content-container'].type, DgControl['splitter-pane'].type, DgControl['response-layout-item'].type, DgControl['drawer'].type].includes(targetContainerType)) { return false; } } // 限制筛选方案、筛选条 - if ([DgControl['query-solution'].type, DgControl['filter-bar'].type].includes(draggingContext.componentType)) { + if ([DgControl['query-solution'].type, DgControl['filter-bar'].type, DgControl['drawer'].type].includes(draggingContext.componentType)) { return false; } // 限制小分组 if (draggingContext.componentType === DgControl['fieldset'].type && targetContainerType !== DgControl['response-form'].type) { return false; } + // 集合类控件,只能放在根组件 + if (draggingContext.componentCategory === 'dataCollection') { + const belongedComponentType = belongedComponent?.componentType; + if (belongedComponentType !== 'frame') { + return false; + } + } return true; } diff --git a/packages/ui-vue/components/designer-canvas/src/designer-canvas.component.tsx b/packages/ui-vue/components/designer-canvas/src/designer-canvas.component.tsx index 7f541e6569f714db0f9fde3d8cb7aaf3012d9f8b..b5aa9b4cf6393b77ba155be470803bdc96592210 100644 --- a/packages/ui-vue/components/designer-canvas/src/designer-canvas.component.tsx +++ b/packages/ui-vue/components/designer-canvas/src/designer-canvas.component.tsx @@ -26,7 +26,7 @@ export default defineComponent({ const designerItemElementRef = ref(); const componentInstance = ref(); const componentId = ref(props.componentId); - const { canvasMode } = props; + const canvasMode = ref(props.canvasMode); let resizeObserver: ResizeObserver | null; let resizeObserverTimer; @@ -55,9 +55,8 @@ export default defineComponent({ // 后需统一采用registerDesignerComponents注册控件 if (props.components) { registerDesignerComponents(props.components); - } else { - loadDesignerRegister(); } + loadDesignerRegister(); provide('canvas-dragula', useDragulaComposition); provide('design-item-context', { @@ -82,15 +81,16 @@ export default defineComponent({ 'd-flex': true, 'flex-fill': true, 'flex-column': true, - [canvasMode]: true + [canvasMode.value]: true } as Record; return classObject; }); - watch([() => props.modelValue, () => props.componentId], - ([newSchema, newComponentId]) => { + watch([() => props.modelValue, () => props.componentId, () => props.canvasMode], + ([newSchema, newComponentId, newCanvasMode]) => { schema.value = newSchema; componentId.value = newComponentId; + canvasMode.value = newCanvasMode; } ); watch(canvasChanged, () => { diff --git a/packages/ui-vue/components/designer-canvas/src/types.ts b/packages/ui-vue/components/designer-canvas/src/types.ts index d3f2990e220aa9ea0730e25c605477011751edef..567e0a005811ea8cfef5bb421bd59c3342e3f708 100644 --- a/packages/ui-vue/components/designer-canvas/src/types.ts +++ b/packages/ui-vue/components/designer-canvas/src/types.ts @@ -82,6 +82,9 @@ export interface DesignerComponentInstance { /** 配置控件的基础信息(展示标题、路径) */ setComponentBasicInfoMap: (designerHostService?: DesignerHostService) => void; updateContextSchema?: (newSchema: any) => void; + + /** 组件所属的外部容器id,用于组合界面 */ + externalContainerId?: string; } export interface DesignerItemContext { diff --git a/packages/ui-vue/components/designer-outline/src/composition/use-data-view.ts b/packages/ui-vue/components/designer-outline/src/composition/use-data-view.ts index 09c7190ad5f643aa09373dfac36635d79beb56c6..93816fab422df5cab184ef9e29eb0db644f7424b 100644 --- a/packages/ui-vue/components/designer-outline/src/composition/use-data-view.ts +++ b/packages/ui-vue/components/designer-outline/src/composition/use-data-view.ts @@ -25,7 +25,9 @@ export function useDataView( /** 处理数据 */ function handleInputData(components: any, findComponents: any, number: number, parentComponent: any, componentId: string) { components.forEach((componentsItem: any) => { - + if (componentsItem.type === 'component') { + componentId = componentsItem.id; + } if (componentsItem.type === 'component-ref') { handleChildComponent(componentsItem, findComponents, number, parentComponent, componentId); return; diff --git a/packages/ui-vue/components/designer-outline/src/composition/use-outline-node.ts b/packages/ui-vue/components/designer-outline/src/composition/use-outline-node.ts index 0ba58675248ed39d7a89cc11993eeb9f7d8054c4..a04228add4d75a8ee1db8961bfb7f24e95faad3e 100644 --- a/packages/ui-vue/components/designer-outline/src/composition/use-outline-node.ts +++ b/packages/ui-vue/components/designer-outline/src/composition/use-outline-node.ts @@ -1,12 +1,14 @@ -import { DgControl, DesignerHostService } from '@farris/ui-vue/components/designer-canvas'; -import { ref, SetupContext } from 'vue'; +import { DgControl as defaultDgControl, DesignerHostService } from '@farris/ui-vue/components/designer-canvas'; +import { inject, ref, SetupContext } from 'vue'; import { ComponentType } from '../types'; import { UseOutlineNode } from './types'; /** * 组装控件树节点的展示文本、图标 */ export function useOutlineNode(designerHostService: DesignerHostService, context: SetupContext): UseOutlineNode { + const designerContext = inject('designerContext') as any; + const dgControl = designerContext && designerContext.dgControl ? designerContext.dgControl : defaultDgControl; /** 当前选中的树节点id */ const currentSelectedNodeId = ref(''); @@ -17,20 +19,23 @@ export function useOutlineNode(designerHostService: DesignerHostService, context */ function getIcon(componentsItem: any) { let classString = 'controlIcon fd-i-Family '; + let iconType = ''; - let controlIcon = ''; switch (componentsItem?.type) { // 输入类控件,根据editor来区分 case 'form-group': { - controlIcon = componentsItem.editor && componentsItem.editor.type ? `fd_pc-${componentsItem.editor.type}` : ''; + iconType = dgControl[componentsItem.type]?.icon || componentsItem.editor?.type || ''; + if (dgControl[componentsItem.editor.type]?.icon) { + iconType = dgControl[componentsItem.editor.type].icon; + } break; } default: { - const iconType = DgControl[componentsItem.type]?.icon || componentsItem.type; - classString += `fd_pc-${iconType}`; + iconType = dgControl[componentsItem.type]?.icon || componentsItem.type; } } + const controlIcon = `fd_pc-${iconType}`; classString += controlIcon; return classString; } @@ -108,13 +113,13 @@ export function useOutlineNode(designerHostService: DesignerHostService, context return '根组件'; } case ComponentType.dataGrid: { - const treeGrid = designerHostService?.formSchemaUtils.selectNode(componentsItem, (item) => item.type === (DgControl['tree-grid'] && DgControl['tree-grid'].type)); + const treeGrid = designerHostService?.formSchemaUtils.selectNode(componentsItem, (item) => item.type === (dgControl['tree-grid'] && dgControl['tree-grid'].type)); if (treeGrid) { return '树表格组件'; } return '表格组件'; } - case ComponentType.attachmentPanel: { + case ComponentType.uploader: { return '附件组件'; } case ComponentType.listView: { @@ -123,6 +128,12 @@ export function useOutlineNode(designerHostService: DesignerHostService, context case ComponentType.form: { return '卡片组件'; } + case ComponentType.calendar: { + return '日历组件'; + } + case ComponentType.page: { + return componentsItem.name || '页面'; + } default: { return '组件'; } @@ -146,7 +157,7 @@ export function useOutlineNode(designerHostService: DesignerHostService, context case 'component': return getComponentShowName(componentsItem); default: { - return DgControl[componentsItem.type] && DgControl[componentsItem.type].name || componentsItem.id; + return dgControl[componentsItem.type] && dgControl[componentsItem.type].name || componentsItem.id; } } diff --git a/packages/ui-vue/components/designer-outline/src/designer-outline.component.tsx b/packages/ui-vue/components/designer-outline/src/designer-outline.component.tsx index 3244a1ca981ab32d20bd9b24f1937fd21b42e343..015768536a38055cc5069f4a518d9bcc9f4d6d69 100644 --- a/packages/ui-vue/components/designer-outline/src/designer-outline.component.tsx +++ b/packages/ui-vue/components/designer-outline/src/designer-outline.component.tsx @@ -49,23 +49,22 @@ export default defineComponent({ treeViewData = getData(); if (treeRef.value && treeRef.value.updateDataSource) { treeRef.value.updateDataSource(treeViewData); - if (currentSelectedOutsideNodeId.value && treeRef.value.clearSelection) { - treeRef.value.clearSelection(); + if (currentSelectedOutsideNodeId.value) { + treeRef.value.emptyCurrentRowId?.(); triggerOutsideClick(currentSelectedOutsideNodeId.value); return; } - if (currentSelectedNodeId && treeRef.value.clearSelection) { - treeRef.value.clearSelection(); + if (currentSelectedNodeId) { + treeRef.value.emptyCurrentRowId?.(); selectControlTreeNode({ id: currentSelectedNodeId.value }); - } } } /** 触发选中控件树的指定节点 */ function selectControlTreeNode(schemaValue: any) { - if (!schemaValue && treeRef.value && treeRef.value.clearSelection) { - treeRef.value.clearSelection(); + if (!schemaValue && treeRef.value) { + treeRef.value.emptyCurrentRowId?.(); } if (schemaValue && treeRef.value && treeRef.value.selectItem) { const selectedItem = treeViewData.find(data => data.originalId === schemaValue.id); @@ -112,8 +111,10 @@ export default defineComponent({ newDataItem={newDataItem.value} lineColor={lineColor.value} cellHeight={cellHeight.value} - fit={true} + fit={false} + autoHeight={true} iconField="controlIcon" + virtualized={true} onSelectionChange={onChanged}>
    ; }; diff --git a/packages/ui-vue/components/designer-outline/src/designer-outline.css b/packages/ui-vue/components/designer-outline/src/designer-outline.css index 1119249816507470596413f9f3ca79b5cc6d896f..1ca920106cd2366f74874160cf77bb466848fd20 100644 --- a/packages/ui-vue/components/designer-outline/src/designer-outline.css +++ b/packages/ui-vue/components/designer-outline/src/designer-outline.css @@ -3,11 +3,10 @@ height: 100%; color: #212529 !important; background: #fcfdff; -} - -.designer-control-tree .fv-grid { - height: 100% !important; - overflow: hidden; + flex-shrink: 1; + flex-grow: 1; + flex-basis: 0; + overflow: auto; } .designer-control-tree .fv-grid .fv-grid-content { @@ -36,4 +35,4 @@ .designer-control-tree .fv-grid .fv-grid-row-hover { border-radius: 10px; -} +} \ No newline at end of file diff --git a/packages/ui-vue/components/designer-outline/src/designer-outline.props.ts b/packages/ui-vue/components/designer-outline/src/designer-outline.props.ts index a983ffc33228009368f0a4971ff11abfa34eaec2..3c845f31fcb65c8f6467ace58d5baf8ccb074f77 100644 --- a/packages/ui-vue/components/designer-outline/src/designer-outline.props.ts +++ b/packages/ui-vue/components/designer-outline/src/designer-outline.props.ts @@ -19,6 +19,8 @@ export const designerOutlineProps = { lineColor: { type: String, default: '#9399a0' }, /** 单元格高度 */ cellHeight: { type: Number, default: 28 }, + /** 控件信息 */ + dgControl: { type: Object, default: {}}, }; export type DesignerOutlineProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/designer-outline/src/types.ts b/packages/ui-vue/components/designer-outline/src/types.ts index be7789b776b92d0e63f3bcc110d8a5e4db567303..858e5fab0f5e5a5907d5af5dbd25ee9b003a71f7 100644 --- a/packages/ui-vue/components/designer-outline/src/types.ts +++ b/packages/ui-vue/components/designer-outline/src/types.ts @@ -25,7 +25,17 @@ export enum ComponentType { /** * 附件 */ - attachmentPanel = 'attachment-panel' + uploader = 'uploader', + + /** + * 日历 + */ + calendar = 'calendar', + + /** + * 页面 + */ + page = 'page' } /** diff --git a/packages/ui-vue/components/designer-toolbox/src/toolbox.css b/packages/ui-vue/components/designer-toolbox/src/toolbox.css index bdc92e873071b54035094c2b119bce297452f7b2..dfc9e19b63d99b39d9718d5aab943731c510901b 100644 --- a/packages/ui-vue/components/designer-toolbox/src/toolbox.css +++ b/packages/ui-vue/components/designer-toolbox/src/toolbox.css @@ -35,6 +35,7 @@ .controlPanel .farrisControlIcon { font-size: 27px; + height: 38px; } .controlPanel { diff --git a/packages/ui-vue/components/designer.ts b/packages/ui-vue/components/designer.ts index 160dd529b6fc79773961f13bd0af7d2784a4855e..892e340447c95650ae2baef7bec5590006d50eb6 100644 --- a/packages/ui-vue/components/designer.ts +++ b/packages/ui-vue/components/designer.ts @@ -4,12 +4,12 @@ export { FDesignerOutline } from './designer-outline'; export { FDesignerToolbox } from './designer-toolbox'; export { FFlowCanvas } from './flow-canvas'; export { F_LOOKUP_HTTP_SERVICE_TOKEN, LookupSchemaRepositoryToken, ExternalLookupPropertyConfig, lookupDataSourceConverter} from './lookup'; -export { FPropertyPanel, SchemaDOMMapping, type FormPropertyChangeObject } from './property-panel'; +export { FPropertyPanel, SchemaDOMMapping, type FormPropertyChangeObject, InputBaseProperty } from './property-panel'; export { FEventParameter } from './event-parameter'; export * from './schema-selector'; export * from './dynamic-resolver'; // export * from './field-selector'; -export { resolverMap } from './dynamic-view'; +export { componentMap, componentPropsConverter, loadRegister, resolverMap } from './dynamic-view'; export { ModalProperty } from "./modal"; export { MappingEditor } from './mapping-editor'; diff --git a/packages/ui-vue/components/discussion-editor/discussion-editor.component.tsx b/packages/ui-vue/components/discussion-editor/discussion-editor.component.tsx deleted file mode 100644 index b1af9644d9296f0307091d81b044380356a7cd31..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/discussion-editor/discussion-editor.component.tsx +++ /dev/null @@ -1,552 +0,0 @@ - -/* eslint-disable no-use-before-define */ -/** - * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { computed, defineComponent, onMounted, ref, SetupContext } from 'vue'; -import { discussionEditorProps, DiscussionEditorProps } from './discussion-editor.props'; -import { MsgInfo, editAttachFile } from './src/types/interface'; -import FInputGroup from '@farris/ui-vue/components/input-group'; -import { useDiscussionEditor } from './src/composition/use-discussion-editor'; -import './discussion-editor.scss'; -import { LocaleService } from '../locale'; - -export default defineComponent({ - name: 'FDiscussionEditor', - props: discussionEditorProps, - emits: ['selections', 'lineData', 'value', 'filePreview', 'fileRemove', 'fileUploadDone', 'personnelSearch', 'getOutUsers'] as (string[] & ThisType) | undefined, - setup(props: DiscussionEditorProps, context: SetupContext) { - const cancelVisible = ref(props.cancelVisible); - const personnelsPrimaryKey = ref(props.personnelsPrimaryKey); - const replyPersonnelsDisplayKey = ref(props.replyPersonnelsDisplayKey); - const editHeight = ref(props.editHeight); - const type = ref(props.type); - const orgUrl = ref(props.orgUrl); - const sectionData = ref(props.sectionData); - - let options: any; - let placeholder: string; - /** 暂存文本框的输入,解决焦点问题 */ - let tempTextValue: string; - - /** 人事弹窗列表数据 */ - const _personnels = ref(props.personnels); - - const personnels = computed({ - get() { - return _personnels.value; - }, - set(val) { - if (val) { - _personnels.value = val; - innerPersonnels = _personnels.value; - copyPersonnels = _personnels.value; - } - } - }); - - /** 人事弹窗列表数据 */ - const _replyUser = ref(props.replyUser); - - const replyUser = computed({ - get() { - return _replyUser.value; - }, - set(val: any) { - if (val) { - _replyUser.value = val; - if (_replyUser.value.id) { - editorFocus(); - } - } - } - }); - - const _attachFiles = ref(props.attachFiles); - - const attachFiles = computed({ - get() { - return _attachFiles.value; - }, - set(val: any) { - if (val) { - _attachFiles.value = val; - } - } - }); - - /** 审批意见 */ - const textValue = ref(''); - - /** 暂存人员信息 */ - const tempPersonnelsValue = ref(''); - /** 暂存部门 */ - const tempSectionValue = ref(''); - - /** 选择要发送的部门 */ - const selectedSection: any = []; - /** 搜索文本框中绑定的值 */ - let personnelText: any; - /** 暂存人员信息,用于搜索 */ - let copyPersonnels: any = []; - let innerPersonnels: any = []; - // let el: ElementRef; - /** 避免重复输入的token */ - let token: boolean; - let permission: any; - /** 上传附件是否显示 */ - const attachFilesModalVisible = ref(false); - const searchPersonnelList: any = {}; - const showSearchList = ref(false); - const permissionList = ref(); - const groupIcon = ''; - - const { personSearchUrl, personnelsDisplayKey, personModalVisible, relativeVisible, selectedPersonnels, - stopBubble, _isInArray, getSearchData, getAvatar, setRelativeValue } = - useDiscussionEditor(props, context); - onMounted(() => { - document.addEventListener('click', setRelativeValue); - permissionList.value = [ - { value: 'ALL', text: LocaleService.getLocaleValue('discussionGroup.all') }, - { value: 'RELATED', text: LocaleService.getLocaleValue('discussionGroup.related') } - ]; - permission = permissionList.value[0]; - options = { maxUploads: 3, maxFileSize: 10240, allowedContentTypes: ['.jpg', '.pdf'] }; - placeholder = LocaleService.getLocaleValue('discussionGroup.placeholder') as string; - } - ); - - /** 文本框失去焦点触发 */ - function setTextValue(e: any) { - if (e) { - tempTextValue = e.target.innerHTML; - textValue.value = tempTextValue; - } - if (tempPersonnelsValue.value) { - textValue.value += tempPersonnelsValue.value; - } - if (tempSectionValue.value) { - textValue.value += tempSectionValue.value; - } - tempTextValue = ''; - tempPersonnelsValue.value = ''; - tempSectionValue.value = ''; - } - /** 监听键盘事件, 主要是用于删除@人 */ - function listenEditorValueChange(e: any) { - tempTextValue = e.target.innerHTML; - const { children } = e.target; - const childrenId: any = []; - for (let i = 0; i < children.length; i++) { - childrenId.push(children[i].id); - } - selectedPersonnels.value.forEach((personnel: any, index: any) => { - if (!childrenId.includes(personnel[personnelsPrimaryKey.value])) { - selectedPersonnels.value.splice(index, 1); - } - }); - selectedSection.value.forEach((section: any, index: any) => { - if (!childrenId.includes(section[personnelsPrimaryKey.value])) { - selectedSection.value.splice(index, 1); - } - }); - if (!tempTextValue) { - tempTextValue = ''; - } - } - /** - * 搜索人员 - */ - function searchPersonnel() { - - if (personnelText) { - showSearchList.value = true; - getSearchData(personnelText, 0).then((d: any) => { - if ("users" in d) { - searchPersonnelList.value = d; - setPersonModalPosition(); - } - }); - } - else { - showSearchList.value = false; - } - - } - /** - * 搜索下一页 - */ - function getMoreSearchData() { - getSearchData(personnelText, searchPersonnelList.pageIndex + 1).then((d: any) => { - if ("users" in d) { - searchPersonnelList.pageIndex = d.pageIndex; - searchPersonnelList.users = [...searchPersonnelList.users, ...d.users]; - } - }); - } - - /** 增加 @ 人员 */ - function appendPersonnels() { - let selectedList = []; - if (!showSearchList.value) { - selectedList = innerPersonnels.filter((item: any) => { item.active === true; }); - } - else { - selectedList = searchPersonnelList.users.filter((item: any) => item.active === true); - } - if (selectedList.length) { - appendPersonnel(selectedList); - } - resetPersonnels(); - setTextValue(null); - personModalVisible.value = false; - } - /** 高级搜索人员添加 */ - function appendPersonnelsList(listData: any) { - if (listData.length) { - appendPersonnel(listData, true); - } - setTextValue(null); - } - /** 循环增加人员 */ - function appendPersonnel(listData: any, external = false) { - listData.forEach((item: any) => { - if (!(selectedPersonnels.value.length && _isInArray(item[personnelsPrimaryKey.value], personnelsPrimaryKey.value, selectedPersonnels))) { - tempPersonnelsValue.value += '@' + item[personnelsDisplayKey.value] + ' '; - selectedPersonnels.value.push(item); - } - }); - } - /** - * 添加部门 - * @param listData - */ - function appendSectionList(listData: any) { - if (listData.length) { - appendSection(listData); - } - setTextValue(null); - } - - function appendSection(listData: any) { - listData.forEach((item: any) => { - if (!(selectedSection.value.length && _isInArray(item[personnelsPrimaryKey.value], personnelsPrimaryKey.value, selectedSection))) { - tempSectionValue.value += '@' + item.name + ' '; - selectedSection.value.push(item); - } - }); - } - - /** - * 关闭人事管理弹窗 - */ - function resetPersonnels() { - showSearchList.value = false; - personModalVisible.value = false; - innerPersonnels = copyPersonnels; - if (innerPersonnels.length) { - innerPersonnels.forEach((item: any) => { item.active = false; }); - } - personnelText = ''; - } - - /** - * 打开人员管理 - * @param e 事件 - */ - function openModalPerson(e: any) { - personModalVisible.value = !personModalVisible.value; - setTimeout(() => { - if (personModalVisible.value) { - setPersonModalPosition(); - } - }, 0); - stopBubble(e); - } - const personModal = ref(null); - function setPersonModalPosition() { - const winH = window.innerHeight; - const ModalBottom = personModal.value?.getBoundingClientRect().bottom; - if (ModalBottom && winH < ModalBottom) { - personModal.value?.scrollIntoView(false); - } - } - - /** - * 提交评语 - */ - function submitApproval() { - if (!textValue.value) { - const notEmptyText = LocaleService.getLocaleValue('discussionGroup.notEmpty'); - if (notEmptyText) { - // notifyService.error(notEmptyText); - } - return; - } - const editAttachFiles: editAttachFile[] = []; - if (attachFiles.value && attachFiles.value.length) { - attachFiles.value.forEach((file: any) => { - const { id } = file; - const { name } = file; - const { size } = file; - const { metadataId } = file.extend; - const attachFile = { - id, - name, - size, - metadataId - }; - editAttachFiles.push(attachFile); - }); - } - context.emit('value', { - msgInfo: MsgInfo.Confirm, - text: textValue, - mailTos: selectedPersonnels, - mailToSections: selectedSection, - visibility: permission.value, - parentId: (replyUser.value && 'id' in replyUser.value) ? replyUser.value.id : null, - attachFiles: editAttachFiles.length ? editAttachFiles : null - }); - textValue.value = ''; - selectedPersonnels.value = []; - selectedSection.value = []; - attachFiles.value = []; - replyUser.value = {}; - } - - function cancel() { - context.emit('value', { - msgInfo: MsgInfo.Cancel, - text: null, - mailTos: [], - mailToSections: [], - visibility: null, - parentId: null, - attachFiles: null - }); - textValue.value = ''; - selectedPersonnels.value = []; - selectedSection.value = []; - attachFiles.value = []; - replyUser.value = {}; - } - - const editor = ref(null); - /** 获得焦点 */ - function editorFocus() { - editor.value?.focus(); - } - /** - * 高级人员点确认 - */ - function selectionsChangePar(event: any) { - if (event.data.users.length) { - const userList: any = []; - event.data.users.forEach((user: any) => { - userList.push(user.data); - }); - appendPersonnelsList(userList); - } - if (event.data.section.length) { - const sectionList: any = []; - event.data.section.forEach((sec: any) => { - sectionList.push(sec.data); - }); - appendSectionList(sectionList); - } - context.emit('selections', event); - } - /** 高级人员中选中某行 */ - function lineDataChangePar(event: any) { - context.emit('lineData', event); - } - - function outUsers(event: any) { - context.emit('getOutUsers', event); - } - - function activeStateChanged(item: any) { - item.active = !item.active; - return item; - }; - - return () => { - return ( -
    - {(replyUser.value && replyUser.value.id) && ( -
    - {'discussionGroup.reply'} - - {replyUser.value[replyPersonnelsDisplayKey.value]} - - : -
    - )} -
    -
    -
    listenEditorValueChange(e)} - onBlur={(e) => setTextValue(e)} - ref={editor} - contenteditable={true} - innerHTML={textValue.value} - >
    -
    - -
    -
    - ); - }; - } -}); diff --git a/packages/ui-vue/components/discussion-editor/src/types/interface.ts b/packages/ui-vue/components/discussion-editor/src/types/interface.ts deleted file mode 100644 index e77291d74606df902c4dd19cccdeac5857015ae1..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/discussion-editor/src/types/interface.ts +++ /dev/null @@ -1,19 +0,0 @@ -export enum MsgInfo { - Cancel = 0, - Confirm = 1 -} -export interface editAttachFile { - id?: string; - name: string; - size: number; - metadataId: string; -} -export interface ValueConfig { - msgInfo: MsgInfo; - text: string; - mailTos: Array<{ userId: string; userName: string }>; - mailToSections: Array; - visibility: string; - parentId: string; - attachFiles?: editAttachFile[]; -} diff --git a/packages/ui-vue/components/discussion-list/discussion-list.scss b/packages/ui-vue/components/discussion-list/discussion-list.scss deleted file mode 100644 index dcd16208b2d2dd1981b9334735dde8f99d0c8c29..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/discussion-list/discussion-list.scss +++ /dev/null @@ -1,152 +0,0 @@ -.f-discussion-group-content { - &-item { - display: flex; - flex-direction: row; - margin-bottom: 14px; - - .discussion-item-avatar { - width: 32px; - height: 32px; - margin-right: 10px; - overflow: hidden; - cursor: pointer; - flex-shrink: 0; - - &-img { - display: block; - width: 32px; - height: 32px; - border-radius: 50%; - } - - &-tip { - width: 32px; - height: 32px; - font-size: 12px; - color: #fff; - text-align: center; - line-height: 32px; - border-radius: 50%; - background-color: #4796FF; - } - } - - .discussion-item-inner { - flex-shrink: 1; - flex-grow: 1; - flex-basis: 0; - padding-bottom: 14px; - border-bottom: 1px solid #e3e3e3; - overflow: hidden; - - .discussion-item-username { - margin-bottom: 4px; - font-size: 16px; - color: rgba(0, 0, 0, 0.85); - } - - .discussion-item-text { - font-size: 14px; - color: rgba(0, 0, 0, 0.75); - - .discussion-item-text-message { - // bugNum: 454035; - // a{ - // color: #3487E3!important; - // } - word-break: break-all; - } - - } - - .discussion-item-text-reply { - margin-top: 11px; - padding: 6px 8px; - font-size: 0; - background: #F5F5F5; - border-radius: 2px; - - &-title, - &-content { - font-size: 14px; - color: rgba(0, 0, 0, 0.75); - line-height: 20px; - } - - &-title { - .discussion-item-text-reply-name { - color: #3487E3; - } - } - - &-content { - word-break: break-all; - } - } - - .discussion-item-files { - margin-top: 11px; - - .ffilepreview--filetype-icon { - width: 40px; - height: 40px; - } - } - - .discussion-item-footer { - margin-top: 10px; - padding-right: 20px; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - - .discussion-item-time { - font-size: 14px; - color: #B1B6C2; - } - - .discussion-item-btns { - display: flex; - align-items: center; - - .discussion-item-btns-start { - display: flex; - align-items: center; - position: relative; - cursor: pointer; - - .f-icon { - color: rgba(0, 0, 0, 0.45); - font-size: 16px; - - &::before { - vertical-align: bottom; - } - } - - .discussion-item-btns-start-text { - margin-left: 14px; - font-size: 14px; - color: rgba(0, 0, 0, 0.45); - } - - &:hover { - - .f-icon, - .discussion-item-btns-start-text { - color: #3487E3; - } - } - } - } - } - } - - &:last-child { - .discussion-item-inner { - border-bottom: none; - } - } - } -} \ No newline at end of file diff --git a/packages/ui-vue/components/drawer/designer.ts b/packages/ui-vue/components/drawer/designer.ts index 93490f132cc1a670fd475b901d7d008b394e82cf..0b571c9e74ed10ddb95b3102f9dfe9942d040ee8 100644 --- a/packages/ui-vue/components/drawer/designer.ts +++ b/packages/ui-vue/components/drawer/designer.ts @@ -1,4 +1,4 @@ - + /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -22,18 +22,27 @@ import { schemaResolver } from './src/schema/schema-resolver'; import { schemaMapper } from './src/schema/schema-mapper'; import drawerSchema from './src/schema/drawer.schema.json'; import { drawerPropsDesignerProps } from './src/designer/drawer.design.props'; +import { eventHandlerResolver } from './src/drawer.props'; +import { createDrawerCallbackResolver } from './src/schema/callback-resolvers'; +import { FResponseToolbarDesign, FResponseToolbarItemDesign } from '@farris/ui-vue/components/response-toolbar/designer'; +import { drawerToolbarItemDesignResolver, drawerToolbarDesignResolver } from './src/designer/drawer-toolbar.design.props'; export * from './src/drawer.props'; export const propsResolver = createPropsResolver(drawerPropsDesignerProps, drawerSchema, schemaMapper, schemaResolver); - -FDrawerDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +export const callbackResolver = createDrawerCallbackResolver(); +FDrawerDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { componentMap['drawer'] = FDrawer; propsResolverMap['drawer'] = propsResolver; + resolverMap['drawer'] = { eventHandlerResolver, callbackResolver }; }; FDrawerDesign.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { componentMap['drawer'] = FDrawerDesign; propsResolverMap['drawer'] = propsResolver; + componentMap['drawer-toolbar-item'] = FResponseToolbarItemDesign; + propsResolverMap['drawer-toolbar-item'] = drawerToolbarItemDesignResolver; + componentMap['drawer-toolbar'] = FResponseToolbarDesign; + propsResolverMap['drawer-toolbar'] = drawerToolbarDesignResolver; }; export default withInstall(FDrawerDesign); diff --git a/packages/ui-vue/components/drawer/index.ts b/packages/ui-vue/components/drawer/index.ts index 648212efc152311d44a34c1a98785d2ff6f4b314..63f9c12dcf26a24c3ff4baa131d0c2b53034c956 100644 --- a/packages/ui-vue/components/drawer/index.ts +++ b/packages/ui-vue/components/drawer/index.ts @@ -1,4 +1,4 @@ - + /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -14,9 +14,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { withInstall } from '@farris/ui-vue/components/common'; import FDrawer from './src/drawer.component'; +import { withInstall } from '@farris/ui-vue/components/common'; export * from './src/drawer.props'; export { FDrawer }; + export default withInstall(FDrawer); diff --git a/packages/ui-vue/components/drawer/src/designer/drawer-toolbar.design.props.ts b/packages/ui-vue/components/drawer/src/designer/drawer-toolbar.design.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f5c3deee0f7364117f70cac9140f5029f45f6d9 --- /dev/null +++ b/packages/ui-vue/components/drawer/src/designer/drawer-toolbar.design.props.ts @@ -0,0 +1,19 @@ +import { ExtractPropTypes } from 'vue'; +import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { toolbarSchemaMapper } from '../schema/schema-mapper'; +import drawerToolbarItemSchema from '../schema/drawer-toolbar-item.schema.json'; +import drawerToolbarSchema from '../schema/drawer-toolbar.schema.json'; +import { commonToolbarItemDesignProps } from '@farris/ui-vue/components/response-toolbar/designer'; +import { responseToolbarProps } from '@farris/ui-vue/components/response-toolbar'; +import { toolbarSchemaResolver } from '../schema/schema-resolver'; + +export const drawerToolbarItemDesignProps = Object.assign({}, commonToolbarItemDesignProps); +export type DrawerToolbarItemDesignProps = ExtractPropTypes; +export const drawerToolbarItemDesignResolver = createPropsResolver(drawerToolbarItemDesignProps, drawerToolbarItemSchema, undefined, toolbarSchemaResolver); +export const drawerToolbarDesignProps = Object.assign({}, responseToolbarProps, { + componentId: { type: String, default: '' } +}); +export type DrawerToolbarDesignProps = ExtractPropTypes; +export const drawerToolbarDesignResolver = createPropsResolver( + drawerToolbarDesignProps, drawerToolbarSchema, toolbarSchemaMapper, toolbarSchemaResolver +); diff --git a/packages/ui-vue/components/drawer/src/designer/drawer.design.component.tsx b/packages/ui-vue/components/drawer/src/designer/drawer.design.component.tsx index 31b0cc8cf68aae7ed4e07056af648e6e6cfd176a..f2609196461e1026aac1a39b9271b515ec406e8b 100644 --- a/packages/ui-vue/components/drawer/src/designer/drawer.design.component.tsx +++ b/packages/ui-vue/components/drawer/src/designer/drawer.design.component.tsx @@ -14,52 +14,75 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { defineComponent, ref, inject, onMounted, Teleport, Transition, withModifiers, computed, watch, CSSProperties } from 'vue'; -import { DesignerItemContext, DesignerHostService, useDesignerComponent, FDesignerInnerItem } from '@farris/ui-vue/components/designer-canvas'; -import FResponseToolbarDesignComponent, { responseToolbarResolver } from '@farris/ui-vue/components/response-toolbar/designer'; +import { defineComponent, ref, inject, onMounted, Transition, withModifiers, computed, watch, CSSProperties } from 'vue'; +import { DesignerItemContext, DesignerHostService, useDesignerComponent, FDesignerInnerItem, ComponentSchema } from '@farris/ui-vue/components/designer-canvas'; +import { FResponseToolbarDesign } from '@farris/ui-vue/components/response-toolbar/designer'; import { useDesignerRules } from './use-designer-rules'; import { drawerPropsDesignerProps } from './drawer.design.props'; +import './drawer.design.scss'; +import { canvasChanged } from '@farris/ui-vue/components/designer-canvas/src/composition/designer-canvas-changed'; +import { drawerToolbarDesignResolver } from './drawer-toolbar.design.props'; export default defineComponent({ name: 'FDrawerDesign', props: drawerPropsDesignerProps, setup(props, context) { - const elementRef = ref(); + /** 可拖拽区域 */ + const contextElementRef = ref(); + /** 可拖拽区域的父容器 */ + const containerElementRef = ref(); + + const footerToolbarId = props.id + '_footer-toolbar'; + const headerToolbarId = props.id + '_header-toolbar'; + 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); - const toolbarSchema = ref(designItemContext.schema.toolbar || { 'type': 'response-toolbar', 'buttons': [] }); - const responseToolbarPropsRef = ref(responseToolbarResolver(toolbarSchema.value)); - - watch(() => designItemContext.schema.toolbar, () => { - toolbarSchema.value = designItemContext.schema.toolbar || { 'type': 'response-toolbar', 'buttons': [] }; - responseToolbarPropsRef.value = responseToolbarResolver(toolbarSchema.value); + const designerRulesComposition = useDesignerRules(designItemContext, containerElementRef, designerHostService); + const componentInstance = useDesignerComponent(contextElementRef, designItemContext, designerRulesComposition); + + designItemContext.schema.footerToolbar = designItemContext.schema.footerToolbar || { id: footerToolbarId, 'type': 'drawer-toolbar', 'buttons': [] }; + const toolbarSchema = ref(designItemContext.schema.footerToolbar); + const responseToolbarPropsRef = ref(drawerToolbarDesignResolver(toolbarSchema.value)); + watch(() => designItemContext.schema.footerToolbar, () => { + toolbarSchema.value = designItemContext.schema.footerToolbar; + responseToolbarPropsRef.value = drawerToolbarDesignResolver(toolbarSchema.value); }, { deep: true }); + const parent = inject('design-item-context'); const items = computed(() => { return [...responseToolbarPropsRef.value.items]; }); + designItemContext.schema.headerToolbar = designItemContext.schema.headerToolbar || { id: headerToolbarId, 'type': 'drawer-toolbar', 'buttons': [] }; + const headertoolbarSchema = ref(designItemContext.schema.headerToolbar); + const responseHeaderToolbarPropsRef = ref(drawerToolbarDesignResolver(headertoolbarSchema.value)); + watch(() => designItemContext.schema.headerToolbar, () => { + headertoolbarSchema.value = designItemContext.schema.headerToolbar; + responseHeaderToolbarPropsRef.value = drawerToolbarDesignResolver(headertoolbarSchema.value); + }, { deep: true }); + const headerToolbars = computed(() => { + return [...responseHeaderToolbarPropsRef.value.items]; + }); + + onMounted(() => { - elementRef.value.componentInstance = componentInstance; + contextElementRef.value.componentInstance = componentInstance; }); - // watch(()=>props.title, ()=>{ - // console.log(props); - // }); context.expose(componentInstance.value); - const innerValue = ref(props.modelValue); + const innerValue = ref(props.modelValue || designItemContext.schema.showWrapperInCanvas); watch(() => props.modelValue, () => { innerValue.value = props.modelValue; }); const wrapperClass = computed(() => { - // console.log(props.position); - return ['f-drawer-wrapper', `f-drawer-wrapper-${props.position}`, props.wrapperClass]; + const classList = ['f-drawer-wrapper', `f-drawer-wrapper-${props.position}`, props.wrapperClass]; + if (!innerValue.value) { + classList.push('d-none'); + } + return classList; }); const wrapperStyle = computed(() => { const widthParam = props.width; @@ -70,7 +93,8 @@ export default defineComponent({ return { width: wrapperWidth, height: wrapperHeight, - backgroundColor: props.backgroundColor + backgroundColor: props.backgroundColor, + maxWidth: '100%' }; }); @@ -89,16 +113,14 @@ export default defineComponent({ return `f-icon-arrow-chevron-${iconName}`; }); - const transitionName = computed(() => { - return `f-drawer-${props.position}`; - }); - const onClose = () => { innerValue.value = false; + designItemContext.schema.showWrapperInCanvas = false; }; const onShow = () => { innerValue.value = true; + designItemContext.schema.showWrapperInCanvas = true; }; const renderEntry = () => { @@ -110,9 +132,13 @@ export default defineComponent({ ); }; + const maskStyles = computed(() => { + return { opacity: props.transparent ? 0 : 1 }; + }); + const renderDrawerMask = () => ( - {innerValue.value && props.showMask &&
    } + {innerValue.value &&
    }
    ); @@ -121,19 +147,18 @@ export default defineComponent({ designItemContext.setupContext.emit('selectionChange', schemaType, schemaValue, componentId, componentInstance); } - const renderResponseToolbar = () => { - if (items.value && items.value.length > 0) { - return - - ; - } + const renderHeaderToolbar = (toolbarId, toolbarItems, model) => { + return + + ; }; const renderDrawerCloseIcon = () => { @@ -145,44 +170,85 @@ export default defineComponent({ }; const renderDrawerHeader = () => { + if (!props.showHeader) { + return null; + } return ( -
    -
    -
    - {context.slots.title ? context.slots.title() : props.title} -
    -
    -
    - {renderResponseToolbar()} - {props.showClose && renderDrawerCloseIcon()} -
    +
    + {props.customHeader ?
    : + <> +
    + {context.slots.title ? context.slots.title() : props.title} +
    +
    + {renderHeaderToolbar(headerToolbarId, headerToolbars.value, headertoolbarSchema.value)} +
    +
    + {props.showClose && renderDrawerCloseIcon()} +
    + }
    ); }; + /** + * 删除抽屉组件 + */ + function onRemoveDrawer(payload: MouseEvent) { + if (payload) { + payload.preventDefault(); + payload.stopPropagation(); + } + componentInstance.value.onRemoveComponent(designerHostService); + + const parentContext = parent?.parent; + const locatePredicate: any = (contentItem: ComponentSchema) => contentItem.id === designItemContext.schema.id; + + if (parentContext && parentContext.schema.contents) { + const indexToRemove = parentContext.schema.contents.findIndex(locatePredicate); + parentContext.schema.contents.splice(indexToRemove, 1); + + canvasChanged.value++; + context.emit('selectionChange'); + } + } + + /** 渲染删除抽屉的图标 */ + const renderRemoveIcon = () => { + return
    +
    +
    +
    +
    +
    ; + }; + + const renderDrawerContent = () => ( - - {innerValue.value &&
    -
    - {renderDrawerHeader()} -
    - {context.slots.default?.()} -
    +
    +
    + {renderRemoveIcon()} + {renderDrawerHeader()} +
    + {context.slots.default?.()}
    -
    } - + {props.showFooter && } +
    +
    ); const renderDrawer = () => ( - -
    - {renderDrawerMask()} - {renderDrawerContent()} -
    -
    +
    + {renderDrawerMask()} + {renderDrawerContent()} +
    ); return () => { - return
    + return
    {renderDrawer()} {renderEntry()}
    ; diff --git a/packages/ui-vue/components/drawer/src/designer/drawer.design.props.ts b/packages/ui-vue/components/drawer/src/designer/drawer.design.props.ts index 0b63627747905f9e5883bb11c58127483d52052c..ac13f9e836a6c854a4289880c59b3625c126bb0c 100644 --- a/packages/ui-vue/components/drawer/src/designer/drawer.design.props.ts +++ b/packages/ui-vue/components/drawer/src/designer/drawer.design.props.ts @@ -2,6 +2,9 @@ import { ExtractPropTypes } from "vue"; import { drawerProps } from "../drawer.props"; export const drawerPropsDesignerProps = Object.assign({}, drawerProps, { - componentId: { type: String, default: '' } + componentId: { type: String, default: '' }, + footerContentType: { type: String, default: 'toolbar' }, + customHeader: { type: Boolean, default: false }, + showWrapperInCanvas: { type: Boolean, default: false } }); export type DrawerDesignerProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/drawer/src/designer/drawer.design.scss b/packages/ui-vue/components/drawer/src/designer/drawer.design.scss new file mode 100644 index 0000000000000000000000000000000000000000..4812fff7688c2d91e1a544b19947830ade6b0410 --- /dev/null +++ b/packages/ui-vue/components/drawer/src/designer/drawer.design.scss @@ -0,0 +1,28 @@ +.farris-component-drawer { + .f-drawer-container>.component-btn-group { + display: none; + top: -24px; + } + + .f-drawer-container.dgComponentSelected>.component-btn-group { + display: flex; + } + + .f-drawer-wrapper { + transition: none; + } +} + +.farris-component-drawer.dgComponentFocused { + + .f-drawer-container>.component-btn-group { + display: flex; + } + + .f-drawer-container { + border-top: 2px solid #388fff !important; + border-left: 2px solid #388fff !important; + border-right: 2px solid #388fff !important; + border-bottom: 2px solid #388fff !important; + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/drawer/src/designer/use-designer-rules.ts b/packages/ui-vue/components/drawer/src/designer/use-designer-rules.ts index c1e399eff4b1155cd20c638c4554089e2769f311..3a8fa689af3a1ff2051d6109a933420305d6e2fb 100644 --- a/packages/ui-vue/components/drawer/src/designer/use-designer-rules.ts +++ b/packages/ui-vue/components/drawer/src/designer/use-designer-rules.ts @@ -1,14 +1,21 @@ -import { UseTemplateDragAndDropRules, DesignerItemContext, DesignerHostService, UseDesignerRules } from "@farris/ui-vue/components/designer-canvas"; +import { UseTemplateDragAndDropRules, DesignerItemContext, DesignerHostService, UseDesignerRules, useDragulaCommonRule, DraggingResolveContext, setPositionOfButtonGroup, DgControl } from "@farris/ui-vue/components/designer-canvas"; +import { nextTick } from "vue"; import { DrawerProperty } from "../property-config/drawer.property-config"; -export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { +export function useDesignerRules(designItemContext: DesignerItemContext, containerElementRef: any, designerHostService?: DesignerHostService): UseDesignerRules { const dragAndDropRules = new UseTemplateDragAndDropRules(); /** * 判断是否可以接收拖拽新增的子级控件 */ - function canAccepts(): boolean { - return false; + function canAccepts(draggingContext: DraggingResolveContext): boolean { + const basalRule = useDragulaCommonRule().basalDragulaRuleForContainer(draggingContext, designerHostService); + if (!basalRule) { + return false; + } + const { canAccept } = dragAndDropRules.getTemplateRule(designItemContext, designerHostService); + + return canAccept; } function checkCanDeleteComponent() { @@ -24,7 +31,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe return true; } function getStyles(): string { - return `position: absolute !important;border: none !important;left: 0;right: 0;padding: 0;`; + return `position: initial !important;border: none !important;left: 0;right: 0;padding: 0;`; } /** * 获取属性配置 @@ -35,5 +42,73 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe return componentProp.getPropertyConfig(schema); } - return { canAccepts, checkCanDeleteComponent, checkCanMoveComponent, hideNestedPaddingInDesginerView, getStyles, getPropsConfig }; + /** 返回抽屉内可拖拽的容器父节点,用于显示选中的蓝色边框 */ + function getDraggableDesignItemElement(context: DesignerItemContext) { + return containerElementRef; + } + /** + * 修改宽度、展示位置后,重新计算操作按钮位置 + */ + function updatePositionOfButtonGroup() { + if (designItemContext.designerItemElementRef.value) { + nextTick(() => { + setPositionOfButtonGroup(containerElementRef.value); + }); + } + } + function onPropertyChanged(event: any) { + if (!event) { + return; + } + const { changeObject } = event; + if (changeObject && ['width', 'position'].includes(changeObject.propertyID)) { + updatePositionOfButtonGroup(); + } + } + function setChildButtonBasicInfoMap(toolbar: any, drawerName: string, controlBasicInfoMap: any) { + if (toolbar?.buttons?.length) { + toolbar.buttons.map(button => { + controlBasicInfoMap.set(button.id, { + componentTitle: button.text, + parentPathName: `${drawerName} > ${button.text}` + }); + + if (button.children?.length) { + button.children.map(childButton => { + controlBasicInfoMap.set(childButton.id, { + componentTitle: childButton.text, + parentPathName: `${drawerName} > ${childButton.text}` + }); + }); + } + }); + } + } + /** + * 配置页签以及页签下按钮的路径信息,用于事件交互面板显示“已有方法”的事件路径 + */ + function setComponentBasicInfoMap() { + if (designItemContext && designerHostService) { + const { formSchemaUtils } = designerHostService; + const controlBasicInfoMap = formSchemaUtils.getControlBasicInfoMap(); + const { schema } = designItemContext; + const drawerName = schema.title || DgControl.drawer.name; + // 配置抽屉的路径 + controlBasicInfoMap.set(schema.id, { + componentTitle: drawerName, + parentPathName: drawerName + }); + + // 配置页头页脚按钮的路径 + setChildButtonBasicInfoMap(schema.headerToolbar, drawerName, controlBasicInfoMap); + setChildButtonBasicInfoMap(schema.footerToolbar, drawerName, controlBasicInfoMap) + } + + } + return { + canAccepts, checkCanDeleteComponent, checkCanMoveComponent, hideNestedPaddingInDesginerView, getStyles, getPropsConfig, + getDraggableDesignItemElement, + onPropertyChanged, + setComponentBasicInfoMap + }; } diff --git a/packages/ui-vue/components/drawer/src/drawer.component.tsx b/packages/ui-vue/components/drawer/src/drawer.component.tsx index 852ea07294d30503a44e9c7ec907ca33ed10c705..ed4e1dd1c0957d44bd6cd2b66d4fa5f6dd450d12 100644 --- a/packages/ui-vue/components/drawer/src/drawer.component.tsx +++ b/packages/ui-vue/components/drawer/src/drawer.component.tsx @@ -15,42 +15,33 @@ * limitations under the License. */ -import { computed, defineComponent, ref, SetupContext, Teleport, Transition, watch, withModifiers } from "vue"; -import FButton from '@farris/ui-vue/components/button'; +import { computed, defineComponent, Ref, ref, SetupContext, Teleport, Transition, watch, watchEffect, withModifiers } from "vue"; +import FResponseToolbar from '@farris/ui-vue/components/response-toolbar'; +import { getCustomStyle, getMaxZIndex } from '@farris/ui-vue/components/common'; import { DrawerButton, DrawerProps, drawerProps } from "./drawer.props"; -import './drawer.css'; import { useDrawerLocale } from "./locale/locale"; export default defineComponent({ name: 'FDrawer', props: drawerProps, - emits: ['afterClose', 'update:modelValue'] as (string[] & ThisType) | undefined, + emits: ['afterClose', 'update:modelValue', 'click'] as (string[] & ThisType) | undefined, setup(props: DrawerProps, context: SetupContext) { const { drawerLocale } = useDrawerLocale(); const modelValue = ref(props.modelValue); const buttons = ref(props.buttons); - // const host = computed(() => { - // if (typeof props.host === 'string') { - // const container = document.querySelector(props.host); - // return container; - // } - // return props.host; - // }); const drawerContainerClass = computed(() => { const showNotInBody = typeof props.host === 'string' ? props.host !== 'body' : document.querySelector(props.host) !== document.body; return { - 'f-drawer': true, + 'f-utils-absolute-all f-drawer': true, // 在某个DOM内部打开抽屉 - 'f-drawer-inline': showNotInBody + 'f-drawer-inline': showNotInBody, }; }); - const wrapperClassObject = computed(() => { - return { - - }; + const maskStyles = computed(() => { + return { opacity: props.transparent? 0: 1 }; }); const maskClass = computed(() => { @@ -61,15 +52,15 @@ export default defineComponent({ }); } return { - 'f-drawer-mask': true, + 'f-drawer-mask f-utils-absolute-all': true, ...customMaskClass }; }); const drawerClass = computed(() => { const customClass = {}; - if (props.maskClass) { - props.class.split(' ').forEach((klass: string) => { + if (props.customClass) { + props.customClass.split(' ').forEach((klass: string) => { customClass[klass] = true; }); } @@ -90,11 +81,42 @@ export default defineComponent({ return typeof heightParam === 'string' ? heightParam : typeof heightParam === 'number' ? `${heightParam}px` : '20%'; }); + const drawerStyle = computed(() => { + let shadowPosition = '-6px 0'; + switch (props.position) { + case 'left': + shadowPosition = '6px 0'; + break; + case 'right': + shadowPosition = '-6px 0'; + break + case 'top': + shadowPosition = '0 6px'; + break; + case 'bottom': + shadowPosition = '0 -6px'; + break; + } + + const styles = { + width: width.value, + height: height.value, + backgroundColor: props.backgroundColor, + maxWidth: '100%' + }; + + if (props.transparent) { + styles['boxShadow'] = `${shadowPosition} 8px 0 rgba(31, 35, 41, .1)`; + } + + return getCustomStyle(styles, props?.customStyle); + }); + const transitionName = computed(() => { return `f-drawer-${props.position}`; }); - const onClose = (e: MouseEvent) => { + const onClose = (e?: MouseEvent) => { Promise.resolve() .then(() => { return props.beforeClose?.(); @@ -106,11 +128,6 @@ export default defineComponent({ context.emit('afterClose'); } }); - - }; - - const onConfirm = (e: MouseEvent) => { - onClose(e); }; watch(() => props.modelValue, (newModelValue: boolean, oldModelValue: boolean) => { @@ -119,6 +136,10 @@ export default defineComponent({ } }); + const onClickToolbarItem = (payload: any, itemId: string) => { + context.emit('click', payload, itemId); + }; + const buttonClickParams = { close: () => { modelValue.value = false; @@ -133,84 +154,121 @@ export default defineComponent({ } } } - - const getFooterButtons = () => { - if (!buttons.value || !buttons.value.length) { - return [ - { id: 'drawer-footer-cancel', text: drawerLocale.cancel, class: 'btn btn-secondary mr-2', handle: onClose }, - { id: 'drawer-footer-confirm', text: drawerLocale.confirm, class: 'btn btn-primary', handle: onConfirm } - ]; - } - return buttons.value; + const renderToolbar = (buttons, toolbarCustomClass: Record) => { + return ; }; function renderFooterButtons() { - - const footerButtons = getFooterButtons(); - return <>{ - footerButtons && footerButtons.map((button: DrawerButton) => { - const isDisabled = ref(button.disabled); - return ( - - ); - } - ) + if (props.footerToolbar?.buttons?.length &&(props.footerToolbar.visible===undefined|| props.footerToolbar.visible)) { + const customClass = {'f-utils-fill': true}; + if (props.footerToolbar?.appearance?.class) { + customClass[props.footerToolbar.appearance.class] = true; + } + return renderToolbar(props.footerToolbar.buttons, customClass); } - ; + return buttons.value && !!buttons.value.length && buttons.value.map((button: DrawerButton) => { + const isDisabled = ref(button.disabled); + return ( + + ); + }); } function renderFooter() { + if(props.footerTemplate && typeof props.footerTemplate === 'function') { + return props.footerTemplate(); + } return context.slots.footerTemplate? context.slots.footerTemplate() : renderFooterButtons(); } + function renderHeader() { + const customClass = {'f-utils-fill': true}; + if (props.headerToolbar?.appearance?.class) { + customClass[props.headerToolbar.appearance.class] = true; + } + + if (props.headerTemplate && typeof props.headerTemplate === 'function') { + return props.headerTemplate(); + } + + return context.slots.headerTemplate? context.slots.headerTemplate() : <> +
    + {props.title} +
    + { props.headerToolbar &&(props.headerToolbar.visible===undefined|| props.headerToolbar.visible)&&
    + { renderToolbar(props.headerToolbar.buttons || [], customClass)} +
    } + {props.showClose && +
    + { + onClose(e as MouseEvent); + }, ['stop'])}> +
    + } + ; + } + + + const zIndexValue = ref({}); + function updateZIndex() { + const maxZindex = getMaxZIndex(); + zIndexValue.value = { + zIndex: maxZindex < 1000 ? 1000 : maxZindex + }; + } + + + watchEffect(() => { + if (modelValue.value) { + updateZIndex(); + } + }); + + context.expose({ + open: () => { + modelValue.value = true; + }, + close: () => { onClose(); } + }); + + return () => { return ( -
    + {
    - {modelValue.value &&
    { + {modelValue.value &&
    { props.allowClickMaskToClose && onClose(e as MouseEvent); }, ['stop'])}>
    } -
    + class={drawerClass.value} style={drawerStyle.value} v-show={modelValue.value}>
    - {props.showHeader &&
    -
    - {context.slots.headerTemplate ? context.slots.headerTemplate() : props.title} -
    - {props.showClose && -
    - { - onClose(e as MouseEvent); - }, ['stop'])}> -
    - } + {props.showHeader &&
    + {renderHeader()}
    } -
    - {context.slots.content?.()} +
    + {context.slots.default?.()}
    - {props.showFooter &&
    -
    +
    } ); }; diff --git a/packages/ui-vue/components/drawer/src/drawer.props.ts b/packages/ui-vue/components/drawer/src/drawer.props.ts index ae3dbc1d0b8687f7592dfb93b074e4587b96f337..c5b6bd8a0a4988cc1576992ed449c9ca5b587e82 100644 --- a/packages/ui-vue/components/drawer/src/drawer.props.ts +++ b/packages/ui-vue/components/drawer/src/drawer.props.ts @@ -1,4 +1,4 @@ - + /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -15,6 +15,10 @@ * limitations under the License. */ import { ExtractPropTypes, PropType } from 'vue'; +import { schemaMapper } from "./schema/schema-mapper"; +import pageHeaderSchema from './schema/drawer.schema.json'; +import { schemaResolver } from './schema/schema-resolver'; +import { createDrawerEventHandlerResolver, createPropsResolver } from '../../dynamic-resolver'; export interface DrawerButton { name?: string; @@ -27,6 +31,7 @@ export interface DrawerButton { } export const drawerProps = { + id: { type: String, required: true }, /** 背景色 */ backgroundColor: { type: String, default: '#fff' }, /** @@ -34,13 +39,14 @@ export const drawerProps = { */ beforeClose: { type: Function, default: () => { return true; } }, /** - * 自定义抽屉样式 + * 自定义抽屉样式 + * @deprecated 请使用 customClass */ - class: { type: String, default: ''}, + class: { type: String, default: '' }, /** * 自定义抽屉蒙层样式 */ - modalClass: { type: String, default: ''}, + modalClass: { type: String, default: '' }, /** 高度 */ height: { type: String as PropType, default: 300 }, /** 打开关闭抽屉 */ @@ -54,9 +60,11 @@ export const drawerProps = { /** 是否展示关闭按钮 */ showClose: { type: Boolean, default: true }, /** 是否展示遮罩层 */ - // mask: { type: Boolean, default: true }, + // showMask: { type: Boolean, default: true }, /** 点击遮罩是否关闭抽屉 */ allowClickMaskToClose: { type: Boolean, default: true }, + /** 抽屉是否透明 */ + transparent: { type: Boolean, default: false }, /** 标题 */ title: { type: String, default: '' }, /** 宽度 */ @@ -67,8 +75,19 @@ export const drawerProps = { host: { type: Object as PropType, default: 'body' }, showHeader: { type: Boolean, default: true }, + headerToolbar: { type: Object as PropType, default: {} }, + headerTemplate: { type: Object as PropType, default: null }, showFooter: { type: Boolean, default: true }, - buttons: { type: Array, default: [] } + footerTemplate: { type: Object as PropType, default: null }, + footerToolbar: { type: Object as PropType, default: {} }, + footerHeight: { type: Number, default: 60 }, + buttons: { type: Array, default: [] }, + customClass: { type: String, default: '' }, + customStyle:{ type: String, default: '' }, + } as Record; export type DrawerProps = ExtractPropTypes; +export const propsResolver = createPropsResolver(drawerProps, pageHeaderSchema, schemaMapper, schemaResolver); + +export const eventHandlerResolver = createDrawerEventHandlerResolver(); diff --git a/packages/ui-vue/components/drawer/src/locale/locale.ts b/packages/ui-vue/components/drawer/src/locale/locale.ts index ad2657274a49cd43d01ae5231ab5c7c162efca12..45e7d16e41b56afd2042797960e7afbacdb2600e 100644 --- a/packages/ui-vue/components/drawer/src/locale/locale.ts +++ b/packages/ui-vue/components/drawer/src/locale/locale.ts @@ -1,14 +1,13 @@ -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; /** * 此处当做静态处理 * @param props * @returns */ export function useDrawerLocale() { - const { t } = useI18n(); const drawerLocale = { - cancel: t('drawer.cancel') || '取消', - confirm: t('drawer.confirm') || '确定' + cancel: LocaleService.getLocaleValue('drawer.cancel') || '取消', + confirm: LocaleService.getLocaleValue('drawer.confirm') || '确定' }; return { drawerLocale }; } diff --git a/packages/ui-vue/components/drawer/src/property-config/drawer.property-config.ts b/packages/ui-vue/components/drawer/src/property-config/drawer.property-config.ts index 7a2800654955e58bf0cec35cc3c9cadb07e5cc95..18adfea19a8c361e71eae216cb4cdc7eb2916f36 100644 --- a/packages/ui-vue/components/drawer/src/property-config/drawer.property-config.ts +++ b/packages/ui-vue/components/drawer/src/property-config/drawer.property-config.ts @@ -12,19 +12,146 @@ export class DrawerProperty extends BaseControlProperty { this.getBehaviorConfig(); + this.propertyConfig.categories['header'] = this.headerPropertyConfig(propertyData); + this.propertyConfig.categories['footer'] = this.footerPropertyConfig(propertyData); + + this.propertyConfig.categories['eventsEditor'] = this.eventPropertyConfig(propertyData); + return this.propertyConfig; } - getBehaviorConfig() { - this.propertyConfig.categories['behavior'] = { - title: "行为", + private headerPropertyConfig(propertyData) { + return { + title: "页头", description: "Behavior", properties: { + showHeader: { + title: "显示页头", + description: '是否显示页头', + type: 'boolean', + refreshPanelAfterChanged: true, + }, + customHeader:{ + title: "自定义页头", + description: '是否自定义页头', + type: 'boolean', + refreshPanelAfterChanged: true, + visible: propertyData.showHeader !== false + }, + showClose: { + title: "显示关闭按钮", + description: '是否显示关闭按钮', + type: 'boolean', + visible: propertyData.showHeader !== false && !propertyData.customHeader + }, title: { title: "标题", description: '标题', - type: 'string' + type: 'string', + visible: propertyData.showHeader !== false && !propertyData.customHeader + }, + headerTemplate: { + title: "页头模板", + type:'string', + description: '设置标题HTML模板,替代整个页头区域', + editor: { + type: "code-editor", + language: "html", + }, + visible: propertyData.showHeader !== false && propertyData.customHeader === true + } + }, + setPropertyRelates: (changeObject, prop) => { + if (!changeObject) { + return; + } + switch (changeObject && changeObject.propertyID) { + case 'title': { + changeObject.needRefreshControlTree = true; + break; + } + } + } + }; + } + + private footerPropertyConfig(propertyData) { + return { + title: "页脚", + description: "Behavior", + properties: { + showFooter: { + title: "显示页脚", + description: '是否显示页脚', + type: 'boolean', + default: true, + refreshPanelAfterChanged: true, + }, + footerHeight:{ + title: "页脚高度(px)", + description: '页脚高度, 单位px; 最小30px, 最大1999px', + type: 'number', + editor: { + type: "number-spinner", + useThousands: false, + min: 30, + max: 1999, + needValid: true, + }, + visible: propertyData.showFooter !== false }, + footerContentType: { + title: "页脚内容类型", + description: '页脚内容类型', + type: 'select', + refreshPanelAfterChanged: true, + editor: { + type: 'combo-list', + textField: 'name', + valueField: 'value', + idField: 'value', + editable: false, + data: [{ value: 'toolbar', name: '工具栏'}, { value: 'html', name: '自定义模板' }, ] + }, + visible: propertyData.showFooter !== false + }, + footerTemplate: { + title: "页脚模板", + type: 'string', + description: '设置页脚HTML模板,替换页脚工具栏', + editor: { + type: "code-editor", + language: "html", + }, + visible: propertyData.footerContentType === 'html' && propertyData.showFooter !== false + } + }, + setPropertyRelates: (changeObject, prop) => { + if (!changeObject) { + return; + } + switch (changeObject && changeObject.propertyID) { + case 'showFooter': { + prop.footerToolbar = { id: prop.id + '_footer-toolbar', 'type': 'drawer-toolbar', 'buttons': [] }; + prop.footerTemplate = ''; + prop.footerContentType = 'toolbar'; + break; + } + case 'footerContentType': { + this.resetFooterContent(prop, changeObject.propertyValue); + break; + } + } + } + }; + } + + getBehaviorConfig() { + this.propertyConfig.categories['behavior'] = { + title: "行为", + description: "Behavior", + properties: { + position: { title: "显示位置", description: '抽屉显示位置', @@ -42,36 +169,79 @@ export class DrawerProperty extends BaseControlProperty { } }, width: { - title: "宽度", - description: '抽屉宽度', - type: 'number' + title: "宽度(px)", + description: '抽屉宽度, 单位px; 最小200px, 最大1999px', + type: 'number', + editor: { + type: "number-spinner", + useThousands: false, + min: 200, + max: 1999, + needValid: true, + }, }, - showMask: { - title: "显示遮罩", - description: '是否显示遮罩', + transparent: { + title: "透明遮罩层", + description: '透明遮罩层', type: 'boolean', - default: true, - visible: false }, - showFooter: { - title: "显示底部", - description: '是否显示底部', + allowClickMaskToClose: { + title: "点击遮罩是否关闭", + description: '点击抽屉以外区域是否关闭抽屉', type: 'boolean', - default: true - } - }, - setPropertyRelates(changeObject, prop) { - if (!changeObject) { - return; + }, + } + }; + } + + + private resetFooterContent(propertyData, contentType = 'toolbar') { + switch (contentType) { + case 'toolbar': { + propertyData.footerTemplate = ''; + break; + } + case 'html': { + propertyData.footerToolbar = { id: propertyData.id + '_footer-toolbar', 'type': 'drawer-toolbar', 'buttons': [] }; + break; + } + } + } + + eventPropertyConfig(propertyData) { + const self = this; + const events = [ + { + label: 'beforeClose', + name: '关闭前事件' + } + ]; + + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); + const properties = self.createBaseEventProperty(initialData); + + return { + title: '事件', + hideTitle: true, + properties, + // 这个属性,标记当属性变更得时候触发重新更新属性 + refreshPanelAfterChanged: true, + tabId: 'commands', + tabName: '交互', + setPropertyRelates(changeObject: any, newPropertyData: any) { + const parameters = changeObject.propertyValue; + delete newPropertyData[self.viewModelId]; + if (parameters) { + // parameters.setPropertyRelates = this.setPropertyRelates; // 添加自定义方法后,调用此回调方法,用于处理联动属性 + self.eventsEditorUtils.saveRelatedParameters(newPropertyData, self.viewModelId, parameters['events'], parameters); } - switch (changeObject && changeObject.propertyID) { - case 'title': { - changeObject.needRefreshControlTree = true; - break; - } + + // 同步视图模型值变化事件 + const designVM = self.designViewModelUtils.getDgViewModel(self.viewModelId); + if (designVM && self.designViewModelField) { + designVM.changeField(self.designViewModelField.id, { valueChanging: newPropertyData.fieldValueChanging, valueChanged: newPropertyData.fieldValueChanged }); } } }; } - } diff --git a/packages/ui-vue/components/drawer/src/schema/callback-resolvers.ts b/packages/ui-vue/components/drawer/src/schema/callback-resolvers.ts new file mode 100644 index 0000000000000000000000000000000000000000..5dfebc6d5471517677a78ebd5ce7ca552cb18c6c --- /dev/null +++ b/packages/ui-vue/components/drawer/src/schema/callback-resolvers.ts @@ -0,0 +1,15 @@ +import { DataColumn, VisualData, VisualDataCell } from "@farris/ui-vue/components/data-view"; +import { Caller } from "../../../dynamic-resolver"; + +export function createDrawerCallbackResolver() { + function resolve(viewSchema: Record, caller: Caller) { + const callbacks: Record = {}; + callbacks.beforeClose = (context: any): Promise => { + return caller.call('beforeClose', viewSchema, [viewSchema]); + }; + return callbacks; + } + return { + resolve + }; +} diff --git a/packages/ui-vue/components/drawer/src/schema/drawer-toolbar-item.schema.json b/packages/ui-vue/components/drawer/src/schema/drawer-toolbar-item.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..21e83276b0ea8f7afcf783d8e979c0e15c1626ec --- /dev/null +++ b/packages/ui-vue/components/drawer/src/schema/drawer-toolbar-item.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/section-toolbar-item.schema.json", + "title": "drawer-toolbar-item", + "description": "A Farris Toolbar Item Component", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a Response Toolbar", + "type": "string" + }, + "type": { + "description": "The type string of Response Toolbar", + "type": "string", + "default": "drawer-toolbar-item" + }, + "appearance": { + "description": "", + "type": "object", + "properties": { + "class": { + "type": "string" + }, + "style": { + "type": "string" + } + }, + "default": {} + }, + "text": { + "description": "按钮", + "type": "string", + "default": "按钮" + }, + "icon": { + "description": "图标", + "type": "string", + "default": "" + }, + "disabled": { + "description": "禁用", + "type": "boolean", + "default": false + }, + "visible": { + "description": "是否可见", + "type": "boolean", + "default": true + }, + "onClick": { + "description": "点击事件", + "type": "string", + "default": "" + }, + "tipsEnable": { + "description": "", + "type": "boolean", + "default": false + }, + "tipsText": { + "description": "", + "type": "string", + "default": "" + }, + "dropdownClass": { + "description": "处于下拉菜单时的样式", + "type": "string", + "default": "" + }, + "split": { + "description": "", + "type": "boolean", + "default": false + } + }, + "required": [ + "id", + "type", + "text" + ], + "events": { + "onClick": "点击事件" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/drawer/src/schema/drawer-toolbar.schema.json b/packages/ui-vue/components/drawer/src/schema/drawer-toolbar.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..e6c17c37a2e13ffa9f14fb25f3606ffe2b78165e --- /dev/null +++ b/packages/ui-vue/components/drawer/src/schema/drawer-toolbar.schema.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/section-toolbar.schema.json", + "title": "drawer-toolbar", + "description": "A Farris Toolbar Component", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a Section Toolbar", + "type": "string" + }, + "type": { + "description": "The type string of Section Toolbar", + "type": "string", + "default": "drawer-toolbar" + }, + "appearance": { + "description": "", + "type": "object", + "properties": { + "class": { + "type": "string", + "default": "" + } + } + }, + "alignment": { + "description": "The alignment of Section Toolbar Button.", + "type": "string", + "default": "right" + }, + "buttons": { + "description": "The items of Section Toolbar.", + "type": "array", + "default": [] + }, + "buttonSize": { + "type": "string", + "default": "" + }, + "visible": { + "description": "", + "type": "boolean", + "default": true + } + }, + "required": [ + "id", + "type", + "buttons" + ], + "events": { + "onClick": "点击事件" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/drawer/src/schema/drawer.schema.json b/packages/ui-vue/components/drawer/src/schema/drawer.schema.json index 23769c37495f7099eb85ede069db44c8633e66c7..b738c9c8b8d20215a2a3439f4ab7db01765bde39 100644 --- a/packages/ui-vue/components/drawer/src/schema/drawer.schema.json +++ b/packages/ui-vue/components/drawer/src/schema/drawer.schema.json @@ -44,7 +44,8 @@ }, "width": { "description": "", - "type": "number" + "type": "number", + "default": 500 }, "showMask": { "description": "", @@ -56,12 +57,66 @@ "type": "boolean", "default": true }, + "showClose": { + "description": "", + "type": "boolean", + "default": true + }, + "customHeader": { + "description": "", + "type": "boolean", + "default": false + }, + "headerTemplate": { + "description": "", + "type": "object" + }, + "headerToolbar": { + "description": "", + "type": "object", + "properties": { + "appearance": { + "description": "", + "type": "object", + "properties": { + "class": { + "type": "string", + "default": "" + } + } + }, + "id": { + "description": "", + "type": "string" + }, + "type": { + "description": "", + "type": "string", + "default": "drawer-toolbar" + }, + "alignment": { + "description": "The alignment of Response Toolbar Button.", + "type": "string", + "default": "right" + }, + "buttons": { + "description": "The items of Response Toolbar.", + "type": "array", + "default": [] + } + } + }, "showFooter": { "description": "", "type": "boolean", "default": true }, - "toolbar": { + "showEntry": { + "description": "", + "type": "boolean", + "default": false + }, + "footerToolbar": { "description": "", "type": "object", "properties": { @@ -71,7 +126,7 @@ "properties": { "class": { "type": "string", - "default":"col-3" + "default": "" } } }, @@ -82,7 +137,7 @@ "type": { "description": "", "type": "string", - "default": "response-toolbar" + "default": "drawer-toolbar" }, "alignment": { "description": "The alignment of Response Toolbar Button.", @@ -95,11 +150,43 @@ "default": [] } } + }, + "contents": { + "description": "", + "type": "array", + "default": [] + }, + "footerTemplate": { + "description": "", + "type": "object" + }, + "footerContentType": { + "description": "", + "type": "string", + "default": "toolbar" + }, + "footerHeight": { + "description": "", + "type": "number", + "default": 60 + }, + "allowClickMaskToClose": { + "description": "", + "type": "boolean", + "default": true + }, + "transparent": { + "description": "遮罩层是否透明", + "type": "boolean", + "default": false } }, "required": [ "id", "type", "contents" - ] + ], + "events": { + "beforeClose": "关闭前事件" + } } \ No newline at end of file diff --git a/packages/ui-vue/components/drawer/src/schema/schema-mapper.ts b/packages/ui-vue/components/drawer/src/schema/schema-mapper.ts index 2ca33ca080647b9edc66a133396282d6a3b1698f..642bf1cc12053eb194030d38574160b886bba1ce 100644 --- a/packages/ui-vue/components/drawer/src/schema/schema-mapper.ts +++ b/packages/ui-vue/components/drawer/src/schema/schema-mapper.ts @@ -3,3 +3,9 @@ import { MapperFunction, resolveAppearance } from '@farris/ui-vue/components/dyn export const schemaMapper = new Map([ ['appearance', resolveAppearance] ]); + + +export const toolbarSchemaMapper = new Map([ + ['buttons', 'items'], + ['appearance', resolveAppearance] +]); \ No newline at end of file diff --git a/packages/ui-vue/components/drawer/src/schema/schema-resolver.ts b/packages/ui-vue/components/drawer/src/schema/schema-resolver.ts index d36ae7457434b7381b796814e9926d352feacff6..68c71c012159cda49bb3b000c1a92b249570d72b 100644 --- a/packages/ui-vue/components/drawer/src/schema/schema-resolver.ts +++ b/packages/ui-vue/components/drawer/src/schema/schema-resolver.ts @@ -1,5 +1,23 @@ import { DynamicResolver } from "@farris/ui-vue/components/dynamic-resolver"; export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { + Object.assign(schema, { + title: '标题', + headerToolbar: { + id: schema.id + '_header_toolbar', + type: 'drawer-toolbar', + buttons: [] + }, + footerToolbar: { + id: schema.id + '_footer_toolbar', + type: 'drawer-toolbar', + buttons: [] + }, + }); + return schema; +} + + +export function toolbarSchemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { return schema; } diff --git a/packages/ui-vue/components/drawer/style.ts b/packages/ui-vue/components/drawer/style.ts index a623e12d7525fae066fa05271299097557c7fa21..0c7c1b41e8df586d0fa4c4225c012f8c46b8db77 100644 --- a/packages/ui-vue/components/drawer/style.ts +++ b/packages/ui-vue/components/drawer/style.ts @@ -1,2 +1,2 @@ import "@farris/ui-vue/components/dependent-base/style"; -import "@farris/ui-vue/theme-default/components/list-view.css"; +import "@farris/ui-vue/theme-default/components/drawer.css"; diff --git a/packages/ui-vue/components/dynamic-form/index.ts b/packages/ui-vue/components/dynamic-form/index.ts index f4f80771a9e1035ddf55d95f734d005608b43778..1223bc9ed031ddd8a4a0e59ef89990a24d0fa5a1 100644 --- a/packages/ui-vue/components/dynamic-form/index.ts +++ b/packages/ui-vue/components/dynamic-form/index.ts @@ -11,6 +11,7 @@ export * from './src/response-form.props'; export * from './src/component/dynamic-form-group/dynamic-form-group.props'; export * from './src/component/dynamic-form-input/dynamic-form-input.props'; export * from './src/designer/response-form-use-designer-rules'; +export * from './src/composition/use-type-resolver' export { FResponseForm, FDynamicFormGroup, FDynamicFormInput, FDynamicFormLabel }; diff --git a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.component.tsx b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.component.tsx index fbf508b68f649dbf439f894fcf74cc282df5a9e4..8c2bb1ad1c235903aed4f10d99cb40b7936f15e3 100644 --- a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.component.tsx +++ b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.component.tsx @@ -4,6 +4,7 @@ import { DynamicFormGroupPropsType, dynamicFormGroupProps } from './dynamic-form import Label from '../dynamic-form-label/dynamic-form-label.component'; import { useTypeResolver } from '../../composition/use-type-resolver'; import FValidationMessage from '../validation-message/validation-message.component'; +import {isMobilePhone} from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FDynamicFormGroup', @@ -22,6 +23,9 @@ export default defineComponent({ const type = ref(props.type); const editorRef = ref(); const errors = ref(props.errors); + const isMobilePhoneState=isMobilePhone(); + const editorType=ref(props.editor.type||'input-group'); + /** * showLabel属性和showLabelType属性,应该只会修改其中一个属性 * 优先识别showLable属性的修改 @@ -35,16 +39,26 @@ export default defineComponent({ const { resolveEditorProps, resolveEditorType, getChangeFunctionName, getClearFunctionName } = useTypeResolver(); - const conditionItemClass = computed(() => { + const conditionItemClass = computed(() => { const classObject = { 'form-group': true, 'farris-form-group': true, 'common-group': true, - 'q-state-readonly': readonly.value + 'q-state-readonly': readonly.value, + 'form-group--in-mobile':isMobilePhoneState } as Record; + classObject['form-group--has-'+editorType.value]=true; return classObject; }); + const autoWidthStyle = computed(() => { + if (props.fill) { + return { + maxWidth: 'none' + }; + } + }); + function onChange(newValue: any, newModelValue: any, parameters: any) { modelValue.value = newModelValue !== undefined ? newModelValue : newValue; context.emit('update:modelValue', modelValue.value); @@ -54,7 +68,22 @@ export default defineComponent({ function onClear() { context.emit('update:modelValue', ''); } - + function interceptClearFunction(originalClearHandler?: (...args: any[]) => any) { + return (...args: any[]) => { + onClear(); + if (typeof originalClearHandler === 'function') { + originalClearHandler(...args); + } + }; + } + function interceptChangeFunction(originalChangeHandler?: (...args: any[]) => any) { + return (newValue: any, newModelValue: any, parameters: any) => { + onChange(newValue, newModelValue, parameters); + if (typeof originalChangeHandler === 'function') { + originalChangeHandler(newValue, newModelValue, parameters); + } + }; + } const renderConditionEditor = computed(() => { const editorType = editor.value.type || 'input-group'; const Component = resolveEditorType(editorType); @@ -62,12 +91,15 @@ export default defineComponent({ const changeFunctionName = getChangeFunctionName(editorType); const clearFunctionName = getClearFunctionName(editor.value.type); if (clearFunctionName) { - editorProps[clearFunctionName] = onClear; + editorProps[clearFunctionName] = interceptClearFunction(editorProps[clearFunctionName]); + } + if (changeFunctionName) { + editorProps[changeFunctionName] = interceptChangeFunction(editorProps[changeFunctionName]); } - editorProps[changeFunctionName] = onChange; if (editorProps.id == null || editorProps.id === '') { editorProps.id = id.value; } + if (editorType === 'number-range' && editor.value.onBeginValueChange && typeof editor.value.onBeginValueChange === 'function') { if (editor.value.onBeginValueChange && typeof editor.value.onBeginValueChange === 'function') { editorProps.onBeginValueChange = editor.value.onBeginValueChange; @@ -126,7 +158,7 @@ export default defineComponent({ return ( visible.value && (
    -
    +
    diff --git a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.design.component.tsx b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.design.component.tsx index f56c5993408d7ff100c460f79548596ad5f04410..363a4e17c9b5fdea9a591e3f2e49396dd778a9fc 100644 --- a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.design.component.tsx +++ b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.design.component.tsx @@ -81,12 +81,20 @@ export default defineComponent({ return ; } + const autoWidthStyle = computed(() => { + if (props.fill) { + return { + maxWidth: 'none' + }; + } + }); + context.expose({ editorRef }); return () => { return (
    -
    +
    {renderLabel()}
    {renderConditionEditor.value()}
    diff --git a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.props.ts b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.props.ts index 16f19efd11e9ef5f941c523b9c982c92148e68d5..e2c9d298ce160e890b0c102a0787a0b17d5d052d 100644 --- a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.props.ts +++ b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-group/dynamic-form-group.props.ts @@ -1,10 +1,14 @@ import { ExtractPropTypes, PropType } from 'vue'; import { EditorType, EditorConfig } from '../../types'; +import { createPropsResolver } from '../../../../dynamic-resolver'; +import { schemaMapper } from '../../schema/schema-mapper'; +import formGroupSchema from '../../schema/form-group.schema.json'; +import { createFormGroupEditorResolver } from '../../../../dynamic-resolver/src/editor-resolver'; import { FormValidationInfo } from '../validation-message/validation-message.props'; /** - * 显示且占位visible、占位不显示reserve-space、不占位不显示none + * 显示且占位visible、占位不显示reserve-space、不占位不显示none、强制不显示(不管是否必填) */ -export type FormGroupLabelType = 'visible' | 'reserve-space' | 'none'; +export type FormGroupLabelType = 'visible' | 'reserve-space' | 'none' | 'force-none'; export const dynamicFormGroupProps = { id: { type: String, default: '' }, @@ -26,6 +30,7 @@ export const dynamicFormGroupProps = { type: { type: String as PropType, default: 'input-group' }, componentId: { type: String, default: '' }, errors: { type: Object as PropType, default: null }, + fill: { type: Boolean, default: false }, } as Record; export type DynamicFormGroupPropsType = ExtractPropTypes; diff --git a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-input/dynamic-form-input.component.tsx b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-input/dynamic-form-input.component.tsx index 6fe0e58e14dcda596fcff4198f17b7daad950afd..1701e67fb8e3f73b3ac93ed4660b20f0aae4eeb3 100644 --- a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-input/dynamic-form-input.component.tsx +++ b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-input/dynamic-form-input.component.tsx @@ -1,6 +1,6 @@ import { SetupContext, computed, defineComponent, ref, watch } from 'vue'; -import { DynamicFormInputProps, dynamicFormInputProps } from '@farris/ui-vue/components/dynamic-form'; import { useTypeResolver } from '../../composition/use-type-resolver'; +import { DynamicFormInputProps, dynamicFormInputProps } from './dynamic-form-input.props'; export default defineComponent({ name: 'FDynamicFormInput', @@ -11,30 +11,56 @@ export default defineComponent({ const editor = ref(props.editor); const modelValue = ref(props.modelValue); - const { resolveEditorProps, resolveEditorType, getChangeFunctionName, getMousedownFunctionName, getMouseupFunctionName } = useTypeResolver(); + const { resolveEditorProps, resolveEditorType, getChangeFunctionName, + getMousedownFunctionName, getMouseupFunctionName, + getClearFunctionName } = useTypeResolver(); - function onChange(newValue: any, newModelValue: any) { + function onChange(newValue: any, newModelValue: any, parameters: any) { modelValue.value = newModelValue !== undefined ? newModelValue : newValue; context.emit('update:modelValue', modelValue.value); - context.emit('change', modelValue.value); + context.emit('change', modelValue.value, { newValue, newModelValue, parameters }); } function onMousedown(e: MouseEvent) { - context.emit('mousedown',e); + context.emit('mousedown', e); } - function onMouseup(e: MouseEvent){ + function onMouseup(e: MouseEvent) { context.emit('mouseup', e); } - - + function onClear() { + context.emit('update:modelValue', ''); + } + function interceptClearFunction(originalClearHandler?: (...args: any[]) => any) { + return (...args: any[]) => { + onClear(); + if (typeof originalClearHandler === 'function') { + originalClearHandler(...args); + } + }; + } + function interceptChangeFunction(originalChangeHandler?: (...args: any[]) => any) { + return (newValue: any, newModelValue: any, parameters: any) => { + onChange(newValue, newModelValue, parameters); + if (typeof originalChangeHandler === 'function') { + originalChangeHandler(newValue, newModelValue, parameters); + } + }; + } const renderConditionEditor = computed(() => { const Component = resolveEditorType(editor.value.type); const editorProps = resolveEditorProps(editor.value.type, editor.value); editorProps.focusOnCreated = props.focusOnCreated; editorProps.selectOnCreated = props.selectOnCreated; const changeFunctionName = getChangeFunctionName(editor.value.type); - editorProps[changeFunctionName] = onChange; + const clearFunctionName = getClearFunctionName(editor.value.type); + if (clearFunctionName) { + editorProps[clearFunctionName] = interceptClearFunction(editorProps[clearFunctionName]); + } + if (changeFunctionName) { + editorProps[changeFunctionName] = interceptChangeFunction(editorProps[changeFunctionName]); + } + // editorProps[changeFunctionName] = onChange; const mousedownFunctionName = getMousedownFunctionName(editor.value.type); editorProps[mousedownFunctionName] = onMousedown; diff --git a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-label/dynamic-form-label.component.tsx b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-label/dynamic-form-label.component.tsx index a1e1858db73b470d99ab276e3daf66694579b346..b3b40e421b37c5556001d43c63daa76e1d3c1f79 100644 --- a/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-label/dynamic-form-label.component.tsx +++ b/packages/ui-vue/components/dynamic-form/src/component/dynamic-form-label/dynamic-form-label.component.tsx @@ -39,8 +39,11 @@ export default defineComponent({ 'f-width-auto': required.value && (props.showType === 'none' || !text.value) }; }); - const showLabel=computed(()=>{ - return props.showType === 'reserve-space' || required.value || text.value && props.showType !== 'none'; + const showLabel = computed(() => { + if (props.showType === 'force-none') { + return false; + } + return props.showType === 'reserve-space' || required.value || !!text.value && props.showType !== 'none'; }); // 显示模式是保留空间或者指定了必填或者text.value有值 return () => { diff --git a/packages/ui-vue/components/dynamic-form/src/composition/form-binding-resolver-design.ts b/packages/ui-vue/components/dynamic-form/src/composition/form-binding-resolver-design.ts index 2927dc119f64d177857c54a750eca39bdf8a36c8..51ddabb2f8e0c7f8a7dcfde2a5aa144ea9c0a5c6 100644 --- a/packages/ui-vue/components/dynamic-form/src/composition/form-binding-resolver-design.ts +++ b/packages/ui-vue/components/dynamic-form/src/composition/form-binding-resolver-design.ts @@ -1,7 +1,8 @@ -import { FormBindingType } from "@farris/ui-vue/components/property-panel"; -import { DesignerHostService, DesignerItemContext } from "@farris/ui-vue/components/designer-canvas"; -import { FormSchemaEntityField$Type } from "@farris/ui-vue/components/common"; -import { DynamicFormGroupPropsType } from "@farris/ui-vue/components/dynamic-form"; +import { FormBindingType } from "../../../../components/property-panel"; +import { DesignerHostService } from "../../../../components/designer-canvas/src/composition/types"; +import { DesignerItemContext } from "../../../../components/designer-canvas"; +import { FormSchemaEntityField$Type } from "../../../../components/common/entity/entity-schema"; +import { DynamicFormGroupPropsType } from "../component/dynamic-form-group/dynamic-form-group.props"; export function useFormBindingResolverDesign(designerHostService: DesignerHostService, designItemContext: DesignerItemContext, props: DynamicFormGroupPropsType) { diff --git a/packages/ui-vue/components/dynamic-form/src/composition/response-form-component-creator.service.ts b/packages/ui-vue/components/dynamic-form/src/composition/response-form-component-creator.service.ts index 2f079162fd828f1a6d8e14a541a7555486c403c2..af0d243a784f1c1f51ccadcd516545a64cd2f380 100644 --- a/packages/ui-vue/components/dynamic-form/src/composition/response-form-component-creator.service.ts +++ b/packages/ui-vue/components/dynamic-form/src/composition/response-form-component-creator.service.ts @@ -55,7 +55,7 @@ export class ResponseFormComponentCreatorService { viewModel: `${buildInfo.componentId}-component-viewmodel`, componentType: buildInfo.componentType, appearance: { - class: this.getFormComponentClass() + class: this.getFormComponentClass(buildInfo) }, formColumns: buildInfo.formColumns, contents @@ -67,12 +67,15 @@ export class ResponseFormComponentCreatorService { /** * 获取卡片组件层级的class样式 */ - private getFormComponentClass(): string { + private getFormComponentClass(buildInfo: ComponentBuildInfo): string { const { templateId } = this.formSchemaUtils.getFormSchema().module; - // 双列表标签页模板中拖入卡片 - if (templateId === 'double-list-in-tab-template') { - return 'f-struct-wrapper f-utils-fill-flex-column'; + // 在双列表标签页模板的内置标签页(启用填充)中拖入卡片面板 + if (templateId === 'double-list-in-tab-template' && buildInfo.parentComponentInstance?.schema?.type === 'tab-page') { + const tabSchema = buildInfo.parentComponentInstance.parent && buildInfo.parentComponentInstance.parent['schema']; + if (tabSchema?.type === 'tabs' && tabSchema?.fill === true) { + return 'f-struct-wrapper f-utils-fill-flex-column'; + } } return 'f-struct-wrapper'; @@ -97,7 +100,8 @@ export class ResponseFormComponentCreatorService { appearance: { class: 'f-form-layout farris-form farris-form-controls-inline' }, - contents: controls + contents: controls, + controlsInline: true }); section.contents = [responseForm]; @@ -115,11 +119,14 @@ export class ResponseFormComponentCreatorService { } }); - // 双列表标签页模板中拖入卡片,要求卡片要填充 + // 在双列表标签页模板的内置标签页(启用填充)中拖入卡片面板,要求面板启用填充 const { templateId } = this.formSchemaUtils.getFormSchema().module; - if (templateId === 'double-list-in-tab-template') { - section.appearance.class = 'f-section-grid f-section-in-main px-0 pt-0'; - section.fill = true; + if (templateId === 'double-list-in-tab-template' && buildInfo.parentComponentInstance?.schema?.type === 'tab-page') { + const tabSchema = buildInfo.parentComponentInstance.parent && buildInfo.parentComponentInstance.parent['schema']; + if (tabSchema?.type === 'tabs' && tabSchema?.fill === true) { + section.appearance.class = 'f-section-grid f-section-in-main px-0 pt-0'; + section.fill = true; + } } return [section]; @@ -129,7 +136,7 @@ export class ResponseFormComponentCreatorService { let className = ''; switch (buildInfo.formColumns) { case 1: { - className = 'col-12'; + className = 'col-12 col-md-12 col-xl-12 col-el-12'; break; } case 2: { diff --git a/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver-design.ts b/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver-design.ts index a84ab3488eba5d3458f2c91dc6c075f4d0dd6f41..5a8aa7649ebc0a48e1d5874d495b096b7c7e1f79 100644 --- a/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver-design.ts +++ b/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver-design.ts @@ -1,5 +1,5 @@ -import { componentMap, componentPropsConverter } from '@farris/ui-vue/components/designer-canvas'; -import FInputGroupDesign from '@farris/ui-vue/components/input-group/designer'; +import { componentMap, componentPropsConverter } from '../../../../components/designer-canvas/src/components/maps'; +import FInputGroupDesign from '../../../input-group/src/designer/input-group.design.component'; import { EditorType, EditorConfig } from "../types"; import { UseTypeResolver } from "./types"; diff --git a/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver.ts b/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver.ts index d15ab84e1e1af0680873db2787e2dbc225f0848a..40bdaf821bef5739128d60a6035b618acc833da3 100644 --- a/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver.ts +++ b/packages/ui-vue/components/dynamic-form/src/composition/use-type-resolver.ts @@ -1,7 +1,7 @@ /* eslint-disable complexity */ -import { componentMap, componentPropsConverter, loadRegister } from '@farris/ui-vue/components/dynamic-view'; import { EditorType, EditorConfig } from "../types"; import { UseTypeResolver } from "./types"; +import { componentMap, componentPropsConverter, loadRegister } from '../../../dynamic-view/src/components/maps'; export function useTypeResolver(): UseTypeResolver { @@ -14,7 +14,7 @@ export function useTypeResolver(): UseTypeResolver { } return editorType; } - + function resolveEditorProps(type: EditorType, config: EditorConfig): Record { const propsConverter = componentPropsConverter[type]; const viewProps = propsConverter ? propsConverter(config) : {}; @@ -42,6 +42,7 @@ export function useTypeResolver(): UseTypeResolver { case 'date-range': case 'datetime-range': case 'datetime-picker': + return 'onDatePicked'; case 'date-picker': return 'onUpdate:modelValue'; case 'time-picker': @@ -53,6 +54,7 @@ export function useTypeResolver(): UseTypeResolver { case 'filter-condition-editor': case 'code-editor': case 'language-textbox': + case 'rich-text-editor': return 'onUpdate:modelValue'; case 'number-range': return 'onValueChange'; @@ -82,6 +84,7 @@ export function useTypeResolver(): UseTypeResolver { return 'onChange'; case 'query-solution-config': case 'solution-preset': + case 'filter-bar-config': return 'onFieldsChanged'; case 'property-editor': return 'onValueChange'; diff --git a/packages/ui-vue/components/dynamic-form/src/designer/response-form-use-designer-rules.ts b/packages/ui-vue/components/dynamic-form/src/designer/response-form-use-designer-rules.ts index f86225c2adc80226cd2b96d9d596158b78abb5c5..79a390222ded417cc19a214e1cb7b5102d27fac2 100644 --- a/packages/ui-vue/components/dynamic-form/src/designer/response-form-use-designer-rules.ts +++ b/packages/ui-vue/components/dynamic-form/src/designer/response-form-use-designer-rules.ts @@ -1,13 +1,14 @@ -import { - DesignerHostService, - DesignerHTMLElement, - DraggingResolveContext, - UseDesignerRules, - useDragulaCommonRule, - UseTemplateDragAndDropRules, - DgControl, - ComponentSchema, - DesignerItemContext } from "@farris/ui-vue/components/designer-canvas"; +import { + DesignerHostService, + DesignerHTMLElement, + DraggingResolveContext, + UseDesignerRules, + useDragulaCommonRule, + UseTemplateDragAndDropRules, + DgControl, + ComponentSchema, + DesignerItemContext +} from "@farris/ui-vue/components/designer-canvas"; import { schemaMap } from "@farris/ui-vue/components/dynamic-resolver"; import { FormBindingType } from "@farris/ui-vue/components/property-panel"; import { useGuid } from "@farris/ui-vue/components/common"; @@ -480,7 +481,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe let className = ''; switch (formColumns) { case 1: { - className = 'col-12'; + className = 'col-12 col-md-12 col-xl-12 col-el-12'; break; } case 2: { @@ -499,7 +500,26 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe return className; } + /** + * 根据卡片面板的配置,确定控件是否添加标签换行样式 + */ + function resolveMultiLabelClassByForm(appearanceClass: string, formComponentSchema: any) { + let isLabelAutoOverflow = false; + if (designItemContext.schema && Object.prototype.hasOwnProperty.call(designItemContext.schema, 'labelAutoOverflow')) { + isLabelAutoOverflow = designItemContext.schema.labelAutoOverflow; + } else { + const formSchemaUtils = designerHostService?.formSchemaUtils; + const responseFormSchema = formSchemaUtils?.selectNode(formComponentSchema, item => item.type === DgControl['response-form'].type); + if (responseFormSchema) { + isLabelAutoOverflow = responseFormSchema.labelAutoOverflow; + } + } + if (isLabelAutoOverflow) { + appearanceClass += ' farris-group-multi-label'; + } + return appearanceClass; + } /** * 根据当前卡片面板的布局配置,获取输入控件的样式 */ @@ -509,10 +529,11 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const { formColumns } = belongedComponentInstance.schema; let appearanceClass = resolveControlClassByComponentType(formColumns); appearanceClass = designerHostService?.formSchemaUtils.getControlClassByFormUnifiedLayout(appearanceClass, belongedComponentInstance.schema.id, designItemContext.schema); - + appearanceClass = resolveMultiLabelClassByForm(appearanceClass, belongedComponentInstance.schema); if (!formGroupElementSchema.appearance) { formGroupElementSchema.appearance = {}; } + formGroupElementSchema.appearance.class = appearanceClass; } } diff --git a/packages/ui-vue/components/dynamic-form/src/property-config/response-form.property-config.ts b/packages/ui-vue/components/dynamic-form/src/property-config/response-form.property-config.ts index 062199d66d6effc3122c6655b8f7ae9c05cf30bd..125ac01b0568684022aa2ac2d7b1fab3bf09e033 100644 --- a/packages/ui-vue/components/dynamic-form/src/property-config/response-form.property-config.ts +++ b/packages/ui-vue/components/dynamic-form/src/property-config/response-form.property-config.ts @@ -1,5 +1,5 @@ -import { DesignerComponentInstance } from "@farris/ui-vue/components/designer-canvas"; +import { DesignerComponentInstance, DgControl } from "@farris/ui-vue/components/designer-canvas"; import { BaseControlProperty } from "@farris/ui-vue/components/property-panel"; import { refreshCanvas } from "@farris/ui-vue/components/designer-canvas"; import { useResponseFormLayoutSetting } from "../composition/use-response-form-layout-setting"; @@ -16,18 +16,18 @@ export class ResponseFormProperty extends BaseControlProperty { // 外观 const { checkIsInFormComponent, assembleUnifiedLayoutContext, changeFormControlsByUnifiedLayoutConfig } = useResponseFormLayoutSetting(this.formSchemaUtils, this.componentId); const appearanceCategory = this.getAppearanceConfig(propertyData); - const issInFormComponent= checkIsInFormComponent(this.componentId); - appearanceCategory.properties['adaptForLanguage']={ + const issInFormComponent = checkIsInFormComponent(this.componentId); + appearanceCategory.properties['adaptForLanguage'] = { title: "控件布局响应国际化", description: "启用国际化后:简体中文、繁体中文环境下控件标签与输入框在一行展示,其他语言环境下控件标签与输入框上下排列。", - visible:issInFormComponent, - type:"boolean" + visible: issInFormComponent, + type: "boolean" }; - appearanceCategory.properties['labelAutoOverflow']={ + appearanceCategory.properties['labelAutoOverflow'] = { title: "控件标签换行", description: "控件标签字数超长时,换行显示。控件标签与输入框在一行展示时,此属性有效。", - visible:issInFormComponent, - type:"boolean" + visible: issInFormComponent, + type: "boolean" }; appearanceCategory.properties['unifiedLayout'] = { title: "统一布局配置", @@ -39,7 +39,7 @@ export class ResponseFormProperty extends BaseControlProperty { initialState: assembleUnifiedLayoutContext(propertyData) } }; - + appearanceCategory.setPropertyRelates = function (changeObject, prop) { if (!changeObject) { return; @@ -52,6 +52,7 @@ export class ResponseFormProperty extends BaseControlProperty { } case 'labelAutoOverflow': { self.setLabelAutoOverflow(propertyData, changeObject.propertyValue); + refreshCanvas(); break; } } @@ -62,12 +63,18 @@ export class ResponseFormProperty extends BaseControlProperty { this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); return this.propertyConfig; } - private setLabelAutoOverflow(propertyData: any, enableLableAutoOverflow: boolean) { - // const formNode = this.domService.domDgMap.get(propertyData.id); - const componentNode = this.formSchemaUtils.getComponentById(this.componentId); - const formNode = this.formSchemaUtils.selectNode(componentNode, item => item.id === propertyData.id); + private setLabelAutoOverflow(propertyData: any, enableLableAutoOverflow: boolean, formNode?: any) { + if (!formNode) { + const componentNode = this.formSchemaUtils.getComponentById(this.componentId); + formNode = this.formSchemaUtils.selectNode(componentNode, item => item.id === propertyData.id); + } if (formNode && formNode.contents && formNode.contents.length) { formNode.contents.forEach(control => { + // 处理小分组下的控件 + if (control.type === DgControl['fieldset'].type) { + this.setLabelAutoOverflow(null, enableLableAutoOverflow, control); + return; + } if (!control.appearance) { control.appearance = { class: '' @@ -84,7 +91,7 @@ export class ResponseFormProperty extends BaseControlProperty { control.appearance.class = control.appearance.class.replace('farris-group-multi-label', '').trim(); } }); - refreshCanvas(); + } } } diff --git a/packages/ui-vue/components/dynamic-form/src/response-form.component.tsx b/packages/ui-vue/components/dynamic-form/src/response-form.component.tsx index 32ddf94210bf391056eaffe799dda4c835b0c425..dd8952a066e8b50584caa4c772710ff60c639c32 100644 --- a/packages/ui-vue/components/dynamic-form/src/response-form.component.tsx +++ b/packages/ui-vue/components/dynamic-form/src/response-form.component.tsx @@ -1,6 +1,6 @@ import { computed, defineComponent, ref } from 'vue'; import { ResponseFormPropsType, responseFormProps } from './response-form.props'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; export default defineComponent({ name: 'FResponseForm', @@ -8,8 +8,9 @@ export default defineComponent({ emits: [], setup(props: ResponseFormPropsType, context) { const elementRef = ref(); - const { locale } = useI18n(); + const responseFormClass = computed(() => { + const locale = LocaleService.getLocale(); const customClassArray = props.customClass.split(' '); const classObject = { 'drag-container': true @@ -19,8 +20,8 @@ export default defineComponent({ return result; }, classObject); // 支持多国际化布局,并且存在多语言资源 - if (props.adaptForLanguage && locale.value) { - classObject['farris-form-controls-inline'] = locale.value !== 'en'; + if (props.adaptForLanguage && locale) { + classObject['farris-form-controls-inline'] = locale !== 'en'; } return classObject; }); diff --git a/packages/ui-vue/components/dynamic-form/src/schema/form-group.schema.json b/packages/ui-vue/components/dynamic-form/src/schema/form-group.schema.json index 2b37fe9533a1b6fee33d079fc3051f79e7902312..0529e3e7808d9f0e6ce3d90d2511f9c23e25ef3c 100644 --- a/packages/ui-vue/components/dynamic-form/src/schema/form-group.schema.json +++ b/packages/ui-vue/components/dynamic-form/src/schema/form-group.schema.json @@ -73,8 +73,11 @@ }, "showLabelType":{ "description": "", - "type": "string", - "default": "visible" + "type": "string" + }, + "fill": { + "type": "boolean", + "default": false } }, "required": [ diff --git a/packages/ui-vue/components/dynamic-form/src/schema/response-form-schema-resolver.ts b/packages/ui-vue/components/dynamic-form/src/schema/response-form-schema-resolver.ts index 6e2e1822f02b0590ec86c2a4f1fe6a07fbbf4ead..9a3d6916406a7d8bc2dde6fefd1879af5af641e2 100644 --- a/packages/ui-vue/components/dynamic-form/src/schema/response-form-schema-resolver.ts +++ b/packages/ui-vue/components/dynamic-form/src/schema/response-form-schema-resolver.ts @@ -15,6 +15,7 @@ export function reponseFormSchemaResolver(resolver: DynamicResolver, schema: Rec componentType: 'form', formColumns: parentComponentType === 'splitter-pane' ? 1 : 4, parentContainerId: parentComponentInstance.schema.id, + parentComponentInstance, bindTo: context.bindingSourceContext?.bindTo || '/', selectedFields: context.bindingSourceContext?.bindingEntityFields }; diff --git a/packages/ui-vue/components/dynamic-form/src/types.ts b/packages/ui-vue/components/dynamic-form/src/types.ts index b82cec43461d0b465579f7cf85d50f0b55927d94..2e2c1a839a8a08432fef36225bc721c185874579 100644 --- a/packages/ui-vue/components/dynamic-form/src/types.ts +++ b/packages/ui-vue/components/dynamic-form/src/types.ts @@ -2,9 +2,9 @@ export type EditorType = 'button-edit' | 'check-box' | 'check-group' | 'combo-li 'date-picker' | 'date-range' | 'datetime-picker' | 'time-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' | + 'textarea' | 'response-form-layout-setting' | 'binding-selector' | 'query-solution-config' | 'solution-preset' | 'filter-bar-config'|'item-collection-editor' | 'menu-lookup' | 'response-layout-splitter' | 'json-editor' | 'property-editor' | 'sort-condition-editor' | - 'filter-condition-editor' | 'expression-editor' | 'code-editor' | 'collection-property-editor' | 'language-textbox'; + 'filter-condition-editor' | 'expression-editor' | 'code-editor' | 'collection-property-editor' | 'language-textbox'| 'image' | 'rich-text-editor'; export interface EditorConfig { /** 编辑器类型 */ diff --git a/packages/ui-vue/components/dynamic-resolver/src/binding-resolver.ts b/packages/ui-vue/components/dynamic-resolver/src/binding-resolver.ts index 1c1c7525f8cab0bf0cb6248073d337734fed67d7..96c2d21e0a34dc81e97d2067bb1bb86fcec408a4 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/binding-resolver.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/binding-resolver.ts @@ -3,11 +3,7 @@ import { BindingData, BindingResolver } from "./types"; export function createFormBindingResolver(): BindingResolver { function resolve(schema: Record, bindingData: BindingData) { const { id } = schema || {}; - // 支持空绑定 - if (schema.binding === undefined) { - return {}; - } - if (schema.binding && Object.keys(schema.binding).length < 1) { + if (schema.binding === undefined || typeof schema.binding !== 'object') { return {}; } const { field } = schema.binding || {}; @@ -73,3 +69,19 @@ export function createDataMappingBindingResolver(): BindingResolver { resolve }; } + +export function createCalendarBindingResolver(): BindingResolver { + function resolve(schema: Record, bindingData: BindingData) { + const { id } = schema || {}; + const { dataSource } = schema || {}; + if (dataSource === undefined) { + return {}; + } + return { + 'events': bindingData.getValue(id) + }; + }; + return { + resolve + }; +} diff --git a/packages/ui-vue/components/dynamic-resolver/src/converter/change-formatter-type.converter.ts b/packages/ui-vue/components/dynamic-resolver/src/converter/change-formatter-type.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..4de28f3990bfaba9a299f35196baefcd7a83b9a9 --- /dev/null +++ b/packages/ui-vue/components/dynamic-resolver/src/converter/change-formatter-type.converter.ts @@ -0,0 +1,50 @@ +import { ComponentSchema } from "../../../designer-canvas/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 + if (schema.type === 'data-grid-column' || schema.type === 'tree-grid-column') { + schema[propertyKey] = propertyValue; + if (propertyKey === 'columnTemplateType' && propertyValue === 'hyperlink') { + if(schema.onClickLinkCommand) { + schema.columnTemplate = ` + + {{rowData.${schema.field}}} + `; + } else { + schema.columnTemplate = ` + + {{rowData.${schema.field}}} + `; + } + } + if (propertyKey === 'columnTemplateType' && propertyValue === 'default') { + schema.columnTemplate = ''; + } + if (propertyKey === 'columnTemplateType' && propertyValue === 'custom') { + schema.columnTemplate = ''; + } + } + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + if (propertyKey === 'columnTemplate') { + return schema.columnTemplate; + } + if (propertyKey === 'columnTemplateType') { + if (!schema.columnTemplateType) { + // 当该属性不存在的时候,兼容以前的场景 + if (schema.columnTemplate && !schema.onClickLinkCommand) { + return 'custom'; + } + if (schema.columnTemplate && schema.onClickLinkCommand) { + return 'hyperlink'; + } + return 'default'; + } + return schema.columnTemplateType || 'default'; + } + return ''; + } +} as PropertyConverter; diff --git a/packages/ui-vue/components/dynamic-resolver/src/converter/column-command.converter.ts b/packages/ui-vue/components/dynamic-resolver/src/converter/column-command.converter.ts index a796743b58b2ea13a62bb3825bd62be737d1c3a2..db5b75aa64dd081500b9ba83eeefbb90b8c244ed 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/converter/column-command.converter.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/converter/column-command.converter.ts @@ -12,27 +12,92 @@ export default { [propertyKey]: propertyValue }; } - if (propertyKey === 'enable' && propertyValue) { - if (!schema.command.commands) { - schema.command.commands = [ - { - "text": "编辑", - "type": "primary", - "command": "edit" + // if (propertyKey === 'enable' && propertyValue) { + // if (!schema.command.commands) { + // schema.command.commands = [ + // { + // "text": "编辑", + // "type": "primary", + // "command": "edit" + // }, + // { + // "text": "删除", + // "type": "danger", + // "command": "remove" + // } + // ]; + // } + // } + if (propertyKey === 'enableType' && propertyValue === 'default') { + schema.command.enable = true; + schema.command.commands = [ + { + "text": "编辑", + "type": "primary", + "command": "edit" + }, + { + "text": "删除", + "type": "danger", + "command": "remove" + } + ]; + } + if (propertyKey === 'enableType' && propertyValue === 'custom') { + schema.command.enable = true; + schema.command.commands = [ + { + "value": "add", + "text": { + "en": "add", + "zh-CHS": "增加", + "zh-CHT": "增加" }, - { - "text": "删除", - "type": "danger", - "command": "remove" - } - ]; - } + "type": "link" + }, + { + "value": "edit", + "text": { + "en": "edit", + "zh-CHS": "编辑", + "zh-CHT": "編輯" + + }, + "type": "danger", + }, + { + "value": "remove", + "text": { + "en": "remove", + "zh-CHS": "删除", + "zh-CHT": "刪除" + + }, + "type": "remove" + } + ]; + } + if (propertyKey === 'enableType' && propertyValue === 'unable') { + schema.command.enable = false; } }, convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { if (schema.command) { - if (propertyKey === 'enable') { - return schema.command.enable; + if (propertyKey === 'enableType') { + const foundDefault = schema.command?.commands?.find(x => x.command === 'edit' && x.type === 'primary'); + return schema.command.enable ? foundDefault ? 'default' : 'custom' : 'unable'; + } + if (propertyKey === 'commands') { + if (schema.command.enableType === 'custom') { + return schema.command.commands; + } + return []; + } + if (propertyKey === 'formatter') { + return schema.command.formatter; + } + if (propertyKey === 'count') { + return schema.command.count; } } return ''; diff --git a/packages/ui-vue/components/dynamic-resolver/src/converter/type.converter.ts b/packages/ui-vue/components/dynamic-resolver/src/converter/type.converter.ts index 774c240617edbba5f374c5090cbc7a33db95aba4..f3e2f4c71f0671f8aca0b0dd26fba07948ed1648 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/converter/type.converter.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/converter/type.converter.ts @@ -1,4 +1,5 @@ -import { ComponentSchema, DgControl } from "@farris/ui-vue/components/designer-canvas"; +import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { DgControl } from "../../../designer-canvas/src/composition/dg-control"; import { PropertyConverter, SchemaService } from "../types"; export default { diff --git a/packages/ui-vue/components/dynamic-resolver/src/event-handler-resolver.ts b/packages/ui-vue/components/dynamic-resolver/src/event-handler-resolver.ts index 83d40a9a7fd99e22dfe6c475ba0e2532ab7f69bc..f5e5abcb655d6f7ff5fdbb220c5a1cdd1cb0742d 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/event-handler-resolver.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/event-handler-resolver.ts @@ -109,3 +109,22 @@ export function createSectionEventHandlerResolver(): EventHandlerResolver { resolve }; } + +export function createDrawerEventHandlerResolver(): EventHandlerResolver { + function resolve(schema: Record, event: ViewEvent) { + const { footerToolbar, headerToolbar } = schema; + const buttons = (footerToolbar?.buttons || []).concat(headerToolbar?.buttons || []); + if (!buttons || buttons.length < 1) { + return null; + } + const [domEvent, buttonId] = event.payloads; + const buttonConfig = findButtonConfig(buttons, buttonId); + if (!buttonConfig) { + return null; + } + return buttonConfig.onClick || buttonConfig.click; + }; + return { + resolve + }; +} diff --git a/packages/ui-vue/components/dynamic-resolver/src/property-config-resolver.ts b/packages/ui-vue/components/dynamic-resolver/src/property-config-resolver.ts index 704c45fd61d98259f8472bc5d76eeae3ca2cabd0..b6634e70305c15a31f0d32349db9695bc8cd21be 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/property-config-resolver.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/property-config-resolver.ts @@ -1,9 +1,8 @@ import { computed, ref } from "vue"; -import { EditorConfig } from "@farris/ui-vue/components/dynamic-form"; -import { ComponentSchema } from "@farris/ui-vue/components/designer-canvas"; -import { ElementPropertyConfig, PropertyEntity } from "@farris/ui-vue/components/property-panel"; import { EffectFunction, PropertyConverter, SchemaService } from './types'; +import { EditorConfig } from "../../dynamic-form"; import { useObjectExpression } from './object-expression'; +import { ComponentSchema } from "../../designer-canvas/src/types"; import { resolveSchemaWithDefaultValue } from "./resolver/schema/schema-resolver"; import appearanceConverter from './converter/appearance.converter'; import buttonsConverter from "./converter/buttons.converter"; @@ -19,12 +18,14 @@ 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 "../../property-panel/src/composition/entity/property-entity"; import itemsCountConverter from "./converter/items-count.converter"; import formGroupLabelConverter from "./converter/form-group-label.converter"; import changeFormatterEnumConverter from './converter/change-formatter-enum.converter'; import gridSortConverter from './converter/grid-sort.converter'; import gridFilterConverter from './converter/grid-filter.converter'; import gridRowOption from './converter/row-option.converter'; +import changeFormatterType from './converter/change-formatter-type.converter'; const propertyConfigSchemaMap = {} as Record; const propertyConverterMap = new Map([ @@ -47,7 +48,8 @@ const propertyConverterMap = new Map([ ['/converter/change-formatter-enum.converter', changeFormatterEnumConverter], ['/converter/grid-sort.converter', gridSortConverter], ['/converter/grid-filter.converter', gridFilterConverter], - ['/converter/row-option.converter', gridRowOption] + ['/converter/row-option.converter', gridRowOption], + ['/converter/change-formatter-type.converter', changeFormatterType] ]); const propertyEffectMap = {} as Record; const propertyEditorMap = new Map([ @@ -165,12 +167,12 @@ function getPropertyEntities( } // 获取属性时,如果没有convertForm,并且通过在Schema上获取得值是空,那就获取defaultValue属性值或者是空 const editingSchemaValue = editingSchema[propertyKey]; - if(Object.prototype.hasOwnProperty.call(propertySchema, 'defaultValue') && (editingSchemaValue === undefined || typeof editingSchemaValue == 'string' && editingSchemaValue === '')){ - return propertySchema['type']==='boolean'? propertySchema['defaultValue']:propertySchema['defaultValue']||''; - }else{ + if (Object.prototype.hasOwnProperty.call(propertySchema, 'defaultValue') && (editingSchemaValue === undefined || typeof editingSchemaValue == 'string' && editingSchemaValue === '')) { + return propertySchema['type'] === 'boolean' ? propertySchema['defaultValue'] : propertySchema['defaultValue'] || ''; + } else { return editingSchemaValue; } - + } return null; }, @@ -234,15 +236,17 @@ function tryToResolveReference(categoryId: string, propertyCategory: Record): ElementPropertyConfig[] { +function getPropertyConfigBySchema(rawSchema: ComponentSchema, schemaService: SchemaService, designerItem1: 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); + const hasSimplePropertyConfig = propertyConfigSchema && Object.keys(propertyConfigSchema).length > 0; + + if (!hasSimplePropertyConfig && designerItem1 && designerItem1.getPropConfig) { + propertyConfigSchema = designerItem1.getPropConfig(componentId); } if (propertyConfigSchema && propertyConfigSchema.categories) { const propertyConfigs = [] as Array; diff --git a/packages/ui-vue/components/dynamic-resolver/src/props-resolver.ts b/packages/ui-vue/components/dynamic-resolver/src/props-resolver.ts index 1bbb34b6dc8b457bb870b2b7ee9eb392dd3b33ee..39ec045e14bf512b2c63c8ec68eab63db64b1220 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/props-resolver.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/props-resolver.ts @@ -1,9 +1,10 @@ -import { DesignerHostService } from '@farris/ui-vue/components/designer-canvas'; +import { DesignerHostService } from './../../designer-canvas/src/composition/types'; import { mappingSchemaToProps, resolveSchemaToProps, schemaMap, schemaResolverMap } from './resolver/schema/schema-resolver'; import { DynamicResolver, EffectFunction, MapperFunction, SchemaResolverFunction } from './types'; import { propertyConfigSchemaMap, propertyEffectMap } from './resolver/property-config/property-config-resolver'; import { schemaMapForDesigner, schemaResolverMapForDesigner } from './resolver/schema/schema-resolver-design'; import { propertyConfigSchemaMapForDesigner as propertyConfigSchemaMapForDesigner, propertyEffectMapForDesigner } from './resolver/property-config/property-config-resolver-design'; +import { RegisterContext } from '../../common'; export function createPropsResolver>( componentPropsObject: T, @@ -40,3 +41,53 @@ export function createPropsResolver>( return Object.assign(defaultProps, resolvedPropsValue); }; } + + +/** + * 注册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 = {}, mergeDefaults: boolean = true) => { + if (!mergeDefaults) { + return mappingSchemaToProps(schemaValue, schemaMapper); + } + const resolvedPropsValue = resolveSchemaToProps(schemaValue, defaultSchema, schemaMapper); + const defaultProps = Object.keys(componentPropsObject).reduce((propsObject: Record, propKey: string) => { + propsObject[propKey] = componentPropsObject[propKey].default; + return propsObject; + }, {}); + return Object.assign(defaultProps, resolvedPropsValue); + }; + }; +} + diff --git a/packages/ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts b/packages/ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts index b4ebb9a21db3918dd73814d5db772c2d91e25408..1c2edc48ac8856860d6ff1b6d1a7d1b0b646f65d 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts @@ -25,6 +25,7 @@ import changeFormatterEnumConverter from '../../converter/change-formatter-enum. import gridSortConverter from '../../converter/grid-sort.converter'; import gridFilterConverter from '../../converter/grid-filter.converter'; import gridRowOption from '../../converter/row-option.converter'; +import changeFormatterType from '../../converter/change-formatter-type.converter'; export function usePropertyConfigResolver(propertyConfigSchemaMap: Record, propertyEffectMap: Record, resolveSchemaWithDefaultValue: (schemaValue: Record) => Record) { @@ -49,7 +50,8 @@ export function usePropertyConfigResolver(propertyConfigSchemaMap: Record([ ['string', { type: 'input-group', enableClear: false }], @@ -240,9 +242,11 @@ export function usePropertyConfigResolver(propertyConfigSchemaMap: Record 0; + if (!hasSimplePropertyConfig && designerItem && designerItem.getPropConfig) { propertyConfigSchema = designerItem.getPropConfig(componentId); } + if (propertyConfigSchema && propertyConfigSchema.categories) { const propertyConfigs = [] as Array; Object.keys(propertyConfigSchema.categories).map((categoryId: string) => { diff --git a/packages/ui-vue/components/dynamic-resolver/src/types.ts b/packages/ui-vue/components/dynamic-resolver/src/types.ts index 3b5d9e5add530ea39b7b0c7ce173b67e96f802f5..88186a94e945a77b742394bf1d87b4341d682fa1 100644 --- a/packages/ui-vue/components/dynamic-resolver/src/types.ts +++ b/packages/ui-vue/components/dynamic-resolver/src/types.ts @@ -1,5 +1,5 @@ - -import { DesignerHostService } from "@farris/ui-vue/components/designer-canvas"; + +import { DesignerHostService } from "../../designer-canvas/src/composition/types"; export type MapperFunction = (key: string, value: any, resolvedSchema?: any) => Record; @@ -55,7 +55,7 @@ export interface BindingModel { export interface BindingData { getValue(elementId: string); - setValue(elementId: string, field: string, value: any); + setValue(elementId: string, field: string | undefined, value: any); } export interface BindingResolver { resolve(schema: Record, bindingData: BindingData); @@ -70,7 +70,7 @@ export interface EditorResolver { } export interface UpdateColumnsResolver { - updateColumns(component: any ,schema: Record); + updateColumns(component: any, schema: Record); } export interface ViewEvent { @@ -83,5 +83,5 @@ export interface EventHandlerResolver { resolve(schema: Record, event: ViewEvent); } export interface Caller { - call: (methodName: string, ...payloads: any[]) => any; + call: (methodName: string, viewSchema: Record, payloads: any[], editorSchema?: Record) => any; } diff --git a/packages/ui-vue/components/dynamic-view/src/callback-deliver.ts b/packages/ui-vue/components/dynamic-view/src/callback-deliver.ts index 547241e14bc743a2ff587e199968495dd562e89b..5aca455910d1bd6bca8b774553c6e76c4b401cb8 100644 --- a/packages/ui-vue/components/dynamic-view/src/callback-deliver.ts +++ b/packages/ui-vue/components/dynamic-view/src/callback-deliver.ts @@ -1,8 +1,8 @@ import { Caller } from "@farris/ui-vue/components/dynamic-resolver"; -export function createCallbackDeliver(callback: (type: string, args: any[]) => any): Caller { - function call(methodName: string, payloads: any[]): any { - return callback(methodName, payloads); +export function createCallbackDeliver(callback: (type: string, viewSchema: Record, args: any[], editorSchema?: Record) => any): Caller { + function call(methodName: string, viewSchema: Record, payloads: any[], editorSchema?: Record): any { + return callback(methodName, viewSchema, payloads, editorSchema); } return { call diff --git a/packages/ui-vue/components/dynamic-view/src/components/maps.ts b/packages/ui-vue/components/dynamic-view/src/components/maps.ts index 4718946d7f61c9206ef3f48a1f1d654d13998b02..639bcad93563e290ff8ea5590c8a888f3ac66d12 100644 --- a/packages/ui-vue/components/dynamic-view/src/components/maps.ts +++ b/packages/ui-vue/components/dynamic-view/src/components/maps.ts @@ -73,95 +73,147 @@ import FCollectionPropertyEditor from '@farris/ui-vue/components/collection-prop import FModal from '@farris/ui-vue/components/modal/designer'; import FExternalContainer from '@farris/ui-vue/components/external-container'; import FLanguageTextbox from '@farris/ui-vue/components/language-textbox'; +import FImage from '@farris/ui-vue/components/image'; +import FComment from '@farris/ui-vue/components/comment'; +import { createPropsResolver, propertyConfigSchemaMap, propertyEffectMap, schemaMap, schemaResolverMap } from '@farris/ui-vue/components/dynamic-resolver'; +import { RegisterContext, useThirdComponent } from '@farris/ui-vue/components/common'; + +const { globalStorageKey } = useThirdComponent(); const componentMap: Record = {}; const componentPropsConverter: Record = {}; const componentPropertyConfigConverter: Record = {}; const resolverMap: Record = {}; -let hasLoaded = false; +const hasLoaded = false; + +const componentsForRegister = [ + FDataGrid, + FQuerySolution, + FFieldset, + FInputGroup, + FNumberSpinner, + FDatePicker, + FLanguageTextbox, + FLookup, + FDynamicForm, + FImage, + FCheckbox, + FCheckbox, + FComboList, + FRadioGroup, + FSwitch, + FTextArea, + FTimePicker, + FResponseLayout, + FSection, + FPageHeader, + FTabs, + FListView, + FContentContainer, + FHtmlTemplate, + FCalendar +] -// jumphere -function loadRegister() { +async function loadRegister() { if (!hasLoaded) { - hasLoaded = true; + + const registerContext: RegisterContext = { + schemaMap: schemaMap, + propertyConfigSchemaMap: propertyConfigSchemaMap, + propertyEffectMap: propertyEffectMap, + schemaResolverMap: schemaResolverMap + }; + + // hasLoaded = true; FAvatar.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FAccordion.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FButtonEdit.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FButtonGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FCalendar.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FCalendar.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FCapsule.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FCheckbox.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FCheckboxGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FCheckbox.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FCheckboxGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FComboList.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FCheckbox.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FCheckboxGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FCheckbox.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FCheckboxGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FComboList.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FComboTree.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FComponent.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FContentContainer.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FContentContainer.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FColorPicker.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FDatePicker.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FDataGrid.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FDatePicker.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FDataGrid.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FDropdown.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FDynamicForm.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FDynamicForm.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FEventsEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FExpressionEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FFilterBar.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FFieldSelector.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FFilterConditionEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FImageCropper.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FInputGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FLayout.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FListView.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FInputGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FLayout.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FListView.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FListNav.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FLookup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FLookup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FMappingEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FNav.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FNumberRange.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FNumberSpinner.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FNumberSpinner.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FOrder.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FPageHeader.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FPageHeader.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FPageFooter.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FPagination.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FProgress.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FQuerySolution.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FRadioGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FQuerySolution.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FRadioGroup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FRate.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FResponseLayout.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FResponseLayout.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FResponseLayoutEditorSetting.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FResponseToolbar.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FSchemaSelector.register(componentMap, componentPropsConverter, componentPropertyConfigConverter); FSearchBox.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FSection.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FSection.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FSmokeDetector.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FSplitter.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FStep.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FSwitch.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FSwitch.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FSortConditionEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FTabs.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FTabs.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FTags.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FText.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FTimePicker.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FTimePicker.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FTransfer.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FTreeview.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FVerifyDetail.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FUploader.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FVideo.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FTextArea.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FTextArea.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FTreeGrid.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FBindingSelector.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FEventParameter.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FFieldset.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FFieldset.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FMenuLookup.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FDrawer.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FJsonEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FPropertyEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FCodeEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FHtmlTemplate.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FHtmlTemplate.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); FCollectionPropertyEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FModal.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); FExternalContainer.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); - FLanguageTextbox.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + FLanguageTextbox.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FImage.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap, registerContext); + FComment.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + // FHtmlEditor.createPropsResolver = createPropsResolver; + // FHtmlEditor.register(componentMap, componentPropsConverter, componentPropertyConfigConverter, resolverMap); + const thirdComponents = window[globalStorageKey]; + if (thirdComponents) { + for (const key in thirdComponents) { + thirdComponents[key].createPropsResolver = createPropsResolver; + thirdComponents[key].register(componentMap, componentPropsConverter, componentPropertyConfigConverter); + } + } } } @@ -171,4 +223,4 @@ function registerComponent(component: any): void { } } -export { componentMap, componentPropsConverter, loadRegister, resolverMap, registerComponent }; +export { componentMap, componentPropsConverter, loadRegister, resolverMap, registerComponent, componentsForRegister }; diff --git a/packages/ui-vue/components/dynamic-view/src/composition/use-binding-data.ts b/packages/ui-vue/components/dynamic-view/src/composition/use-binding-data.ts index 3fbc825a58830eb7b03b5745d8afb93b3740d5e6..a70e26e735271f3814bf66e3416ea63f0a1e87eb 100644 --- a/packages/ui-vue/components/dynamic-view/src/composition/use-binding-data.ts +++ b/packages/ui-vue/components/dynamic-view/src/composition/use-binding-data.ts @@ -1,7 +1,7 @@ import { BindingData } from "@farris/ui-vue/components/dynamic-resolver"; import { Ref, SetupContext } from "vue"; -export function useBindingData(modelValue: Ref>, setupContext: SetupContext): BindingData { +export function useBindingData(modelValue: Ref>, setupContext?: SetupContext): BindingData { function getValue(elementId: string) { return modelValue.value && modelValue.value[elementId]; } @@ -13,6 +13,9 @@ export function useBindingData(modelValue: Ref>, setupContex if (!field) { return; } + if (!setupContext) { + return; + } setupContext.emit('update:modelValue', { elementId, field, value, modelValue: modelValue.value }); } diff --git a/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx b/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx index 24edd02f3bf339bf18bf30b3281588b2c7e3c508..951dec2f221479221ee6f03f95d316257e161826 100644 --- a/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx +++ b/packages/ui-vue/components/dynamic-view/src/dynamic-view.component.tsx @@ -10,11 +10,12 @@ import { useComponentManager } from './composition/use-component-manager'; import { useBindingData } from './composition/use-binding-data'; import { useEntityState } from './composition/use-entity-state'; import { createCallbackDeliver } from './callback-deliver'; +import { DynamicViewEventTypes } from './types'; const FDynamicView = defineComponent({ name: 'FDynamicView', props: dynamicViewProps, - emits: ['update:modelValue', 'event', 'componentReady'], + emits: ['update:modelValue', 'event'], setup(props: DynamicViewProps, setupContext: SetupContext) { const schema = ref(props.schema); const modelValue = ref(props.modelValue); @@ -26,6 +27,8 @@ const FDynamicView = defineComponent({ loadRegister(); const componentManager = useComponentManager(); const bindingData = useBindingData(modelValue, setupContext); + const controlData = ref({}); + const controlBindingData = useBindingData(controlData); const entityState = useEntityState(schema.value); entityState.setup(); // const componentState = new Map>(); @@ -33,16 +36,22 @@ const FDynamicView = defineComponent({ function resolveModels(viewSchema: Record) { const componentType = viewSchema.type; - const { dataSource, binding = undefined } = viewSchema; - if (!dataSource && binding === undefined) { + const { dataSource = undefined, binding = undefined } = viewSchema; + // 无数据源且无绑定 + if (dataSource === undefined && binding === undefined) { return {}; } + let dataView = bindingData; + // 空绑定场景 + if (binding === null || dataSource === null) { + dataView = controlBindingData; + } if (dataSource) { dataSourceMap.set(dataSource, viewSchema); } const resolver = resolverMap[componentType]; const bindingResolver = resolver && resolver.bindingResolver ? resolver.bindingResolver : createFormBindingResolver(); - return bindingResolver.resolve(viewSchema, bindingData); + return bindingResolver.resolve(viewSchema, dataView); } function renderSlots(slots?: Record) { @@ -77,7 +86,7 @@ const FDynamicView = defineComponent({ if (!callbackResolver) { return {}; } - return callbackResolver.resolve(viewSchema, callbackDeliver); + return callbackResolver.resolve(viewSchema, callbackDeliver, editor); } if (!callbackResolver) { return {}; @@ -140,20 +149,28 @@ const FDynamicView = defineComponent({ ref: (componentRef: any) => { if (componentRef && viewSchema.id && !componentManager.has(viewSchema.id)) { componentManager.register(viewSchema.id, componentRef); - setupContext.emit('componentReady', { ref: ref(componentRef), id: viewSchema.id, type: viewSchema.type }); + const payload = { token: viewSchema.id, name: DynamicViewEventTypes.COMPONENT_READY, type: viewSchema.type, payloads: [ref(componentRef)], schema: viewSchema }; + setupContext.emit('event', payload); + } + }, + onVnodeUnmounted: (node: any) => { + if (viewSchema.id && componentManager.has(viewSchema.id)) { + componentManager.remove(viewSchema.id); + const payload = { token: viewSchema.id, name: DynamicViewEventTypes.COMPONENT_UNMOUNTED, type: viewSchema.type, payloads: [node], schema: viewSchema }; + setupContext.emit('event', payload); } } }; return { props, eventProps, callbackProps }; } - function render(viewSchema: Record) { + function render(viewSchema: Record, parentSchema?: Record) { const componentKey = viewSchema.type; const Component = componentMap[componentKey]; // 使用自定义渲染器渲染组件 if (customComponentRenders && customComponentRenders[componentKey]) { - return customComponentRenders[componentKey](viewSchema, Component); + return customComponentRenders[componentKey](viewSchema, Component, parentSchema); } // 渲染ComponentRef组件 @@ -180,7 +197,7 @@ const FDynamicView = defineComponent({ if (typeof viewSchema.contents === 'string') { return viewSchema.contents; } - return viewSchema.contents.map((schema: Record) => render(schema)); + return viewSchema.contents.map((schema: Record) => render(schema, viewSchema)); }; // const resolver = resolverMap[componentKey]; // const editorResolver: EditorResolver = resolver ? resolver.editorResolver : null; @@ -218,7 +235,15 @@ const FDynamicView = defineComponent({ } } function getControlValue(id: string) { - return bindingData.getValue(id); + return controlBindingData.getValue(id); + } + function setControlValue(id: string, value: any) { + controlBindingData.setValue(id, undefined, value); + const viewSchema = getSchema(id); + if (!viewSchema) { + return; + } + updateComponentModelProps(viewSchema); } function getSchema(id: string) { return schemaMap.get(id); @@ -337,21 +362,7 @@ const FDynamicView = defineComponent({ if (!Component) { return; } - const modelProps = resolveModels(viewSchema); - if (modelProps && Object.keys(modelProps).length > 0) { - const currentState = state.get(viewSchema.id); - if (!currentState) { - state.set(viewSchema.id, reactive({ props: modelProps })); - } else { - const currentProps = { ...currentState?.props }; - Object.keys(modelProps).forEach(key => { - currentProps[key] = modelProps[key]; - }); - Object.assign(currentState?.props, currentProps); - - } - // state.set(viewSchema.id, reactive({ props: { ...currentProps, ...modelProps } })); - } + updateComponentModelProps(viewSchema); if (!viewSchema.contents || !Array.isArray(viewSchema.contents)) { return; } @@ -396,7 +407,21 @@ const FDynamicView = defineComponent({ return combined; } } - + function updateComponentModelProps(viewSchema: Record) { + const modelProps = resolveModels(viewSchema); + if (modelProps && Object.keys(modelProps).length > 0) { + const currentState = state.get(viewSchema.id); + if (!currentState) { + state.set(viewSchema.id, reactive({ props: modelProps })); + } else { + const currentProps = { ...currentState?.props }; + Object.keys(modelProps).forEach(key => { + currentProps[key] = modelProps[key]; + }); + Object.assign(currentState?.props, currentProps); + } + } + } watch(() => props.modelValue, (newModelValue) => { modelValue.value = newModelValue; const viewSchema = getFrameComponentSchema(); @@ -405,6 +430,13 @@ const FDynamicView = defineComponent({ } convertModelValueToProps(viewSchema); }); + watch(() => controlData.value, (newControlData) => { + const viewSchema = getFrameComponentSchema(); + if (!viewSchema) { + return; + } + convertModelValueToProps(viewSchema); + }, { deep: true }); function resolveInitialSchema(newSchema: any) { if (newSchema) { schema.value = newSchema; @@ -421,7 +453,7 @@ const FDynamicView = defineComponent({ }); resolveInitialSchema(props.schema); - setupContext.expose({ componentManager, rerender, getProps, invoke, setProps, selectItemById, getSchema, setSchema, convertPartialSchemaToProps, getControlValue }); + setupContext.expose({ componentManager, rerender, getProps, invoke, setProps, selectItemById, getSchema, setSchema, convertPartialSchemaToProps, getControlValue, setControlValue }); return () => { const components: Record[] = schema.value?.module?.components; diff --git a/packages/ui-vue/components/dynamic-view/src/types.ts b/packages/ui-vue/components/dynamic-view/src/types.ts index 07c3f181906886e3a1de4e3a1b38bed6367fff67..500f83e347edf65f4cb85ecd960601297f7ad4e8 100644 --- a/packages/ui-vue/components/dynamic-view/src/types.ts +++ b/packages/ui-vue/components/dynamic-view/src/types.ts @@ -81,3 +81,8 @@ export interface UseComponentInstanceManager { getAll(): Map; clear(): void; } + +export const DynamicViewEventTypes = { + COMPONENT_READY: 'component:ready', + COMPONENT_UNMOUNTED: 'component:unmounted', +}; diff --git a/packages/ui-vue/components/entity-binding-selector/composition/use-entity-tree.ts b/packages/ui-vue/components/entity-binding-selector/composition/use-entity-tree.ts index 37183ac33772ee1dd890d6dbd956aae64a518233..3de6103af754025191e512f72f07b763cf3ba4cc 100644 --- a/packages/ui-vue/components/entity-binding-selector/composition/use-entity-tree.ts +++ b/packages/ui-vue/components/entity-binding-selector/composition/use-entity-tree.ts @@ -99,14 +99,14 @@ export function useEntityTree(props: EntityBindingSelectorProps) { } function checkAndGetSelectedEntity() { - const selectedItems = entityTreeGridRef.value.getSelectedItems(); - if (!selectedItems.length) { + const selectedRow = entityTreeGridRef.value.getSelectionRow(); + if (!selectedRow || !selectedRow.raw?.data) { const notifyService: any = new FNotifyService(); notifyService.globalConfig = { position: 'top-center' }; notifyService.warning({ message: '请先选择实体' }); return; } - return selectedItems[0].data; + return selectedRow.raw.data; } /** * 配置实体列表的行禁用效果 diff --git a/packages/ui-vue/components/entity-binding-selector/composition/use-field-tree.ts b/packages/ui-vue/components/entity-binding-selector/composition/use-field-tree.ts index 715383f6626a1a546f95e7d80e67729c27d0b198..fd253d5d8e69a8afc855fbf236ff5858cf8e26cc 100644 --- a/packages/ui-vue/components/entity-binding-selector/composition/use-field-tree.ts +++ b/packages/ui-vue/components/entity-binding-selector/composition/use-field-tree.ts @@ -4,6 +4,7 @@ import { EntityBindingSelectorProps } from "../entity-binding-selector.props"; import { ref } from "vue"; import { RowOptions, VisualData } from "../../data-view"; import { FNotifyService } from "../../notify"; +import { DgControl } from "../../designer-canvas"; export function useFieldTree(props: EntityBindingSelectorProps) { @@ -70,6 +71,24 @@ export function useFieldTree(props: EntityBindingSelectorProps) { } } } + + /** + * 检查控件是否在侧边栏中,在侧边栏中的字段可以与非侧边栏中的字段重复。 + */ + function checkControlExistInDrawer(targetComponentInstance: any) { + if (!targetComponentInstance) { + return; + } + if (targetComponentInstance.schema?.type === DgControl.drawer?.type) { + return true; + } + if (targetComponentInstance.parent) { + const result = checkControlExistInDrawer(targetComponentInstance.parent); + if (result) { + return true; + } + } + } /** * 获取指定实体中已在当前表单中被占用的字段,场景:创建卡片组件时将已在其他组件中添加的字段排除掉 */ @@ -78,6 +97,9 @@ export function useFieldTree(props: EntityBindingSelectorProps) { if (!bindingEntity) { return; } + // 目标区域是否在侧边栏中 + const targetComponentInDrawer = checkControlExistInDrawer(props.targetComponentInstance); + // 根组件和table组件内的输入控件与form内不能重复 let targetComponentType = getComponentTypeByControlType(currentComponentType.value); if (['frame', 'table', 'form'].includes(targetComponentType)) { @@ -100,10 +122,21 @@ export function useFieldTree(props: EntityBindingSelectorProps) { if (sourceComponentType !== targetComponentType || !entityInfo || entityInfo.id !== bindingEntity.id) { return; } + // 组件是否在侧边栏中 + const componentInDrawer = formSchemaUtils.checkComponentExistInDrawer(componentNode.id); + // 若当前组件在侧边栏中,那么只收集同样在侧边栏中的字段。 + if (targetComponentInDrawer && componentInDrawer) { + viewModel.fields.forEach(field => { + occupiedFieldMap.set(field.id, true); + }); + } + // 若当前组件不在侧边栏中,那么只收集不在侧边栏中的字段 + if (!targetComponentInDrawer && !componentInDrawer) { + viewModel.fields.forEach(field => { + occupiedFieldMap.set(field.id, true); + }); + } - viewModel.fields.forEach(field => { - occupiedFieldMap.set(field.id, true); - }); }); } diff --git a/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.component.tsx b/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.component.tsx index 3fcda1af6799869f4f49c0167572ffc9538c9917..81ce5e1ed9ba0721a472d93ec140cdb4e8e9d8cd 100644 --- a/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.component.tsx +++ b/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.component.tsx @@ -4,7 +4,7 @@ import { entityBindingSelectorProps, EntityBindingSelectorProps } from "./entity import { FTreeGrid } from '../tree-grid'; import { useEntityTree } from "./composition/use-entity-tree"; import { ComponentBindingSourceContext } from "@farris/ui-vue/components/designer-canvas"; -import { Step } from '../step'; +import { FStep } from '../step'; import { useFieldTree } from "./composition/use-field-tree"; import { FDynamicFormGroup } from "../dynamic-form"; @@ -73,7 +73,7 @@ export default defineComponent({ onMounted(() => { if (entityTreeGridRef && initialSelectedEntity.value?.id) { - entityTreeGridRef.value.selectItemById(initialSelectedEntity.value?.id); + entityTreeGridRef.value.selectRowById(initialSelectedEntity.value?.id); } // 若没有选择实体的步骤,则需要初始字段树 if (steps.value.length === 1 && steps.value[0].id === 'selectFields') { @@ -82,8 +82,8 @@ export default defineComponent({ }); function onComponentTypeChanged(newValue: string) { - if (fieldTreeGridRef?.value?.clearSelection) { - fieldTreeGridRef.value.clearSelection(); + if (fieldTreeGridRef?.value?.emptyCurrentRowId) { + fieldTreeGridRef.value.emptyCurrentRowId(); } } /** 选择实体 */ @@ -192,7 +192,7 @@ export default defineComponent({ return () => { return (
    - {steps.value?.length > 1 ? : ''} + {steps.value?.length > 1 ? : ''}
    {renderEntityTreeGrid()}
    {renderComponentSelector()}
    {renderFieldTreeGrid()}
    diff --git a/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.props.ts b/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.props.ts index c7a1d393caea036fc80766af5fb1f8bff77f0f2e..06f9713a143236ca64dca84527aa3350eec26d38 100644 --- a/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.props.ts +++ b/packages/ui-vue/components/entity-binding-selector/entity-binding-selector.props.ts @@ -7,7 +7,9 @@ export const entityBindingSelectorProps = { /** 预设绑定的实体id */ bindingEntityId: { type: String, default: '' }, /** 绑定步骤 */ - steps: { type: Array, default: ['selectEntity', 'selectFields'] } + steps: { type: Array, default: ['selectEntity', 'selectFields'] }, + /** 目标区域的控件实例 */ + targetComponentInstance: { type: Object, default: {} } } as Record; export type EntityBindingSelectorProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/event-parameter/src/composition/editors/use-combo-tree.ts b/packages/ui-vue/components/event-parameter/src/composition/editors/use-combo-tree.ts index e370faebdd24f39c12d319439475d6eafc7bef1f..d7df241adc6915af4aae6f5fb69989ca57804a6e 100644 --- a/packages/ui-vue/components/event-parameter/src/composition/editors/use-combo-tree.ts +++ b/packages/ui-vue/components/event-parameter/src/composition/editors/use-combo-tree.ts @@ -1,13 +1,11 @@ import { computed, watch } from "vue"; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { editorMap, EditorType, EventParameterProps } from "../../event-parameter.props"; import { UseEditorInput } from "../type"; -import { VisualData } from "@farris/ui-vue/components/data-view"; export default function ( props: EventParameterProps, ): UseEditorInput { - const { t: getLocaleValue } = useI18n(); const shouldRenderAppendButton = computed(() => props.editor.type === EditorType.Default || props.editorType === EditorType.Default); @@ -30,7 +28,7 @@ export default function ( type: 'combo-tree', componentProps: { data: props.data, - placeholder: getLocaleValue('eventParameter.comboTree.placeholder'), + placeholder: LocaleService.getLocaleValue('eventParameter.comboTree.placeholder'), enableSearch: false, enableClear: true, editable: false, diff --git a/packages/ui-vue/components/event-parameter/src/composition/editors/use-json-editor.ts b/packages/ui-vue/components/event-parameter/src/composition/editors/use-json-editor.ts index 45934a54966a122b071ee57fe813c3e84d0d5192..8df2e4bd45f5b8e4aa1b7c3f02e34487add525e3 100644 --- a/packages/ui-vue/components/event-parameter/src/composition/editors/use-json-editor.ts +++ b/packages/ui-vue/components/event-parameter/src/composition/editors/use-json-editor.ts @@ -1,11 +1,10 @@ -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { editorMap, EventParameterProps, EditorControlSource } from "../../event-parameter.props"; import { UseBaseEditor } from "../type"; export default function ( props: EventParameterProps ): UseBaseEditor { - const { t: getLocaleValue } = useI18n(); function getParameterDescriptors(): any[] { const controlSource = props.editorControlSource as EditorControlSource; const schemaValue = controlSource?.context?.schema?.value; @@ -19,7 +18,7 @@ export default function ( } return []; } catch (error) { - console.error(`${getLocaleValue('eventParameter.jsonEditor.error')}`, error); + console.error(`${LocaleService.getLocaleValue('eventParameter.jsonEditor.error')}`, error); return []; } } @@ -46,11 +45,11 @@ export default function ( formData: props.formData, }, beforeOpen, - dialogTitle: getLocaleValue('eventParameter.jsonEditor.dialogTitle'), - keyColumnTitle: getLocaleValue('eventParameter.jsonEditor.keyColumnTitle'), - valueColumnTitle: getLocaleValue('eventParameter.jsonEditor.valueColumnTitle'), - addButtonText: getLocaleValue('eventParameter.jsonEditor.addButtonText'), - keyColumnPlaceholder: getLocaleValue('eventParameter.jsonEditor.keyColumnPlaceholder'), + dialogTitle: LocaleService.getLocaleValue('eventParameter.jsonEditor.dialogTitle'), + keyColumnTitle: LocaleService.getLocaleValue('eventParameter.jsonEditor.keyColumnTitle'), + valueColumnTitle: LocaleService.getLocaleValue('eventParameter.jsonEditor.valueColumnTitle'), + addButtonText: LocaleService.getLocaleValue('eventParameter.jsonEditor.addButtonText'), + keyColumnPlaceholder: LocaleService.getLocaleValue('eventParameter.jsonEditor.keyColumnPlaceholder'), }, }; } diff --git a/packages/ui-vue/components/event-parameter/src/composition/use-general-editor.ts b/packages/ui-vue/components/event-parameter/src/composition/use-general-editor.ts index 13211dadc90b015c0d0c8c61ce31730a835403eb..d177e32822f1ed0645c8ba454ed093e86e1abe6a 100644 --- a/packages/ui-vue/components/event-parameter/src/composition/use-general-editor.ts +++ b/packages/ui-vue/components/event-parameter/src/composition/use-general-editor.ts @@ -1,15 +1,14 @@ import { reactive, ref, watch } from "vue"; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { EventParameterProps } from "../event-parameter.props"; export function useGeneralEditor( props: EventParameterProps, ) { - const { t: getLocaleValue } = useI18n(); const tabs = reactive([ { id: 'tabField', - title: getLocaleValue('eventParameter.generalEditor.field'), + title: LocaleService.getLocaleValue('eventParameter.generalEditor.field'), treeConfigs: { id: 'tabFieldTree', columns: [{ field: 'name' }], @@ -21,7 +20,7 @@ export function useGeneralEditor( }, { id: 'tabVar', - title: getLocaleValue('eventParameter.generalEditor.tabVar'), + title: LocaleService.getLocaleValue('eventParameter.generalEditor.tabVar'), treeConfigs: { id: 'tabVarTree', data: props.varData, @@ -30,7 +29,7 @@ export function useGeneralEditor( }, { id: 'tabForm', - title: getLocaleValue('eventParameter.generalEditor.form'), + title: LocaleService.getLocaleValue('eventParameter.generalEditor.form'), treeConfigs: { id: 'tabFormTree', data: props.formData, diff --git a/packages/ui-vue/components/events-editor/src/composition/use-events-editor-util.ts b/packages/ui-vue/components/events-editor/src/composition/use-events-editor-util.ts index 988217fcac5faf4be31d8aa19e8f3193bdd722b7..c09e3533e459861be0417c482abb251d3b456bb8 100644 --- a/packages/ui-vue/components/events-editor/src/composition/use-events-editor-util.ts +++ b/packages/ui-vue/components/events-editor/src/composition/use-events-editor-util.ts @@ -228,6 +228,11 @@ export function useEventsEditorUtil(): UseEventsEditorUtil { name: '上传并批量新增行', id: 'e6fc25ca-853b-0b2d-76c9-a1f7a253679b', handlerName: 'UploadAndBatchAddRows', + }, + { + name: '通过属性名上传并批量更新行', + id: 'e00b70db-9de3-8e3e-eb59-1c550a255fec', + handlerName: 'uploadAndBatchAddRowsWithPropertyName', } ] }, diff --git a/packages/ui-vue/components/events-editor/src/events-editor.css b/packages/ui-vue/components/events-editor/src/events-editor.css index f9fbc2ffd66a22b089dbfe9da347f988af2d5120..f5d73ed9c3f0d3ad5d60f005102a792f8a9a1796 100644 --- a/packages/ui-vue/components/events-editor/src/events-editor.css +++ b/packages/ui-vue/components/events-editor/src/events-editor.css @@ -119,31 +119,31 @@ margin-bottom: 0 !important; } -.f-icon.f-icon-arrow-chevron-down:hover { +.f-page-events-editor-bound-event .f-icon.f-icon-arrow-chevron-down:hover { color: #2a75ee; cursor: pointer; } -.f-icon.f-icon-arrow-chevron-left:hover { +.f-page-events-editor-bound-event .f-icon.f-icon-arrow-chevron-left:hover { color: #2a75ee; cursor: pointer; } -.f-icon.f-icon-yxs_delete:hover { +.f-page-events-editor-bound-event .f-icon.f-icon-yxs_delete:hover { color: #2a75ee; cursor: pointer; } -.f-icon.f-icon-home-add:hover { +.f-page-events-editor-bound-event .f-icon.f-icon-home-add:hover { color: #2a75ee; cursor: pointer; } -.f-icon.f-icon-yxs_delete { +.f-page-events-editor-bound-event .f-icon.f-icon-yxs_delete { color: #a7b0bb; } -.f-icon.f-icon-home-add { +.f-page-events-editor-bound-event .f-icon.f-icon-home-add { color: #a7b0bb; } diff --git a/packages/ui-vue/components/expression-editor/src/components/entities-variable/entity-variable.component.tsx b/packages/ui-vue/components/expression-editor/src/components/entities-variable/entity-variable.component.tsx index 0a82edb4465bb976b42607c5472ae2b2292ab89e..13da4cdfd64f01cc264f7c35659e0f6539cacdce 100644 --- a/packages/ui-vue/components/expression-editor/src/components/entities-variable/entity-variable.component.tsx +++ b/packages/ui-vue/components/expression-editor/src/components/entities-variable/entity-variable.component.tsx @@ -128,7 +128,8 @@ export default defineComponent({ {variables[key].items.map((item) => { return
  • onVariableItemClick(payload, item)} - onDblclick={(payload) => onVariableItemDblClick(payload, item)}> + onDblclick={(payload) => onVariableItemDblClick(payload, item)} + title={`${item.name} [${item.key}]`}> {item.name}
  • ; })} diff --git a/packages/ui-vue/components/external-container/src/schema/schema-resolver.ts b/packages/ui-vue/components/external-container/src/schema/schema-resolver.ts index e4854b4da0e6e5ebb6218ef0ca1a3f2531f97755..3cd7d91e9a8fddc73a723154a2f9730b9c95191d 100644 --- a/packages/ui-vue/components/external-container/src/schema/schema-resolver.ts +++ b/packages/ui-vue/components/external-container/src/schema/schema-resolver.ts @@ -15,7 +15,7 @@ export function schemaResolver(resolver: DynamicResolver, schema: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void { + register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext): void { componentMap['fieldset'] = Fieldset; - propsResolverMap['fieldset'] = propsResolver; + propsResolverMap['fieldset'] = propsResolverGenerator(registerContext); }, - registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void { + registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext + ): void { componentMap['fieldset'] = FieldSetDesign; - propsResolverMap['fieldset'] = propsResolver; + propsResolverMap['fieldset'] = propsResolverGenerator(registerContext); } }; diff --git a/packages/ui-vue/components/fieldset/src/designer/fieldset.design.component.tsx b/packages/ui-vue/components/fieldset/src/designer/fieldset.design.component.tsx index 47b2b1811522c388e031178e7ad10b51be5712f4..4857a8b12452c2e870921ba0839e12bb6d84cec1 100644 --- a/packages/ui-vue/components/fieldset/src/designer/fieldset.design.component.tsx +++ b/packages/ui-vue/components/fieldset/src/designer/fieldset.design.component.tsx @@ -1,6 +1,6 @@ -import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { computed, defineComponent, inject, nextTick, onMounted, ref } from 'vue'; import { FieldsetDesignerProps, fieldsetDesignerProps } from '../fieldset.props'; -import { useDesignerComponent, DesignerItemContext, DesignerHostService } from '@farris/ui-vue/components/designer-canvas'; +import { useDesignerComponent, DesignerItemContext, DesignerHostService, setPositionOfSelectedComponentBtnGroup } from '@farris/ui-vue/components/designer-canvas'; import { useDesignerRules } from './use-designer-rules'; export default defineComponent({ @@ -11,10 +11,11 @@ export default defineComponent({ const fieldsetRef = ref(); const elementRef = ref(); const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); + const designerHostService = inject('designer-host-service') as DesignerHostService; const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); context.expose(componentInstance.value); + const { designerContext } = designerHostService; onMounted(() => { elementRef.value.componentInstance = componentInstance; @@ -40,9 +41,11 @@ export default defineComponent({ return classObject; }); const dragContainerStyle = computed(() => { + const isMobile = designerContext && designerContext.designerMode === 'Mobile'; return { 'display': expandStatus.value ? 'flex' : 'none', - 'flex-wrap': 'wrap' + 'flex-wrap': 'wrap', + 'flex-direction': isMobile ? 'column' : 'row' }; }); const expandCollapseIcon = computed(() => { @@ -53,8 +56,13 @@ export default defineComponent({ 'f-state-expand': expandStatus.value } as Record; }); - function onClickHeader() { + function onClickHeader(event: PointerEvent) { + event.stopPropagation(); expandStatus.value = !expandStatus.value; + + nextTick(() => { + setPositionOfSelectedComponentBtnGroup(); + }); } return () => { diff --git a/packages/ui-vue/components/fieldset/src/fieldset.props.ts b/packages/ui-vue/components/fieldset/src/fieldset.props.ts index 4a7e3696f1d90b67b951d7391d50a2769fb33776..b3ee93777a53a989e5440ab0b979d78204069357 100644 --- a/packages/ui-vue/components/fieldset/src/fieldset.props.ts +++ b/packages/ui-vue/components/fieldset/src/fieldset.props.ts @@ -1,6 +1,6 @@ import { ExtractPropTypes } from "vue"; -import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { createPropsResolver, getPropsResolverGenerator } from "@farris/ui-vue/components/dynamic-resolver"; import { schemaMapper } from "./schema/schema-mapper"; import fieldsetSchema from './schema/fieldset.schema.json'; import { schemaResolver } from './schema/schema-resolver'; @@ -21,3 +21,10 @@ export const fieldsetDesignerProps = Object.assign({}, fieldsetProps, { export type FieldsetDesignerProps = ExtractPropTypes; export const propsResolver = createPropsResolver(fieldsetProps, fieldsetSchema, schemaMapper, schemaResolver); + +export const propsResolverGenerator = getPropsResolverGenerator( + fieldsetProps, + fieldsetSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/filter-bar/index.ts b/packages/ui-vue/components/filter-bar/index.ts index 5c5ca0375821649e01f7835cbabfeb6cd1b0999f..14057b2806bf6be0c905355e103b73b6c998bd80 100644 --- a/packages/ui-vue/components/filter-bar/index.ts +++ b/packages/ui-vue/components/filter-bar/index.ts @@ -2,9 +2,8 @@ import type { App, Plugin } from 'vue'; import FFilterBar from './src/filter-bar.component'; import FFilterBarDesign from './src/designer/filter-bar.design.component'; -import FFilterBarConfig from '../query-solution/src/designer/query-solution-config/query-solution-config.component'; import { propsResolver } from './src/filter-bar.props'; -import { configPropsResolver } from './src/designer/filter-bar-config/filter-bar-config.props'; +import { designPropsResolver } from './src/designer/filter-bar.props'; export * from './src/types'; export * from './src/filter-bar.props'; @@ -17,13 +16,11 @@ FFilterBar.register = ( configResolverMap: Record, resolverMap: Record): void => { componentMap['filter-bar'] = FFilterBar; propsResolverMap['filter-bar'] = propsResolver; - componentMap['filter-bar-config'] = FFilterBarConfig; - propsResolverMap['filter-bar-config'] = configPropsResolver; }; FFilterBar.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void => { componentMap['filter-bar'] = FFilterBarDesign; - propsResolverMap['filter-bar'] = propsResolver; + propsResolverMap['filter-bar'] = designPropsResolver; }; export { FFilterBar }; diff --git a/packages/ui-vue/components/filter-bar/src/components/drawer.component.tsx b/packages/ui-vue/components/filter-bar/src/components/drawer.component.tsx index ad71d4b7c9420823f68698609042196ed24f59cc..36de07dcaa0f9f6dcab8239c886c94b6242c3e33 100644 --- a/packages/ui-vue/components/filter-bar/src/components/drawer.component.tsx +++ b/packages/ui-vue/components/filter-bar/src/components/drawer.component.tsx @@ -4,12 +4,14 @@ import { FilterItem } from '../types'; import { FDrawer } from '@farris/ui-vue/components/drawer'; import { FConditionFields } from '@farris/ui-vue/components/condition'; import { cloneDeep } from 'lodash-es'; +import { useFilterBarLocale } from '../locale/locale'; export default function (props: FilterBarProps, context: SetupContext, openDrawer: Ref, collapsedFilterList: Ref, useFilterItemsComposition) { const { filterFields, handleQuery } = useFilterItemsComposition; const conditions = ref(cloneDeep(collapsedFilterList.value)); - const title = ref('更多筛选'); + const filterBarLocale = useFilterBarLocale(); + const title = ref(filterBarLocale.advancedFilter); function drawerCancel() { openDrawer.value = false; @@ -22,8 +24,7 @@ export default function (props: FilterBarProps, context: SetupContext, openDrawe const modifyFilter = conditions.value.find(condition => condition.id === filter.id); modifyFilter?.value && (filter.value = modifyFilter.value); }); - const queryFilters = handleQuery(); - context.emit('conditionChange', queryFilters); + handleQuery(); } function renderDrawerConditions() { return
    @@ -32,8 +33,8 @@ export default function (props: FilterBarProps, context: SetupContext, openDrawe conditions={conditions.value} >
    - - + +
    ; } diff --git a/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.component.tsx b/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.component.tsx index bd3c540c7d638045a1bbcd9921d76f61fb88453a..47378182615225bff294d2a264c458247c481bd9 100644 --- a/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.component.tsx +++ b/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.component.tsx @@ -1,90 +1,183 @@ -import { ref, defineComponent } from 'vue'; +import { ref, defineComponent, inject, onMounted, computed } from 'vue'; import { cloneDeep } from 'lodash-es'; import { FDynamicFormGroup } from '@farris/ui-vue/components/dynamic-form'; import { FilterItemConditionProps, filterItemConditionProps } from './filter-item-condition.props'; +import { FilterItemUtils, UseFilterItems } from '../../composition/types'; +import { useConditionUtils } from '@farris/ui-vue/components/condition'; +import { useFilterBarLocale } from '../../locale/locale'; +import FFilterRadioGroup from '../filter-radio-group/filter-radio-group.component'; +import { getMaxZIndex} from '@farris/ui-vue/components/common'; export default defineComponent({ - name: 'FFilterBarItem', + name: 'FFilterBarItemCondition', props: filterItemConditionProps, - emits: ['cancel', 'confirm'], + emits: ['cancel', 'confirm', 'clearFilter'], setup(props: FilterItemConditionProps, context) { const filterItem = ref(cloneDeep(props.filterItem)); - const position = ref(props.position); - const customStyle = { - left: position.value.left, - top: position.value.top, - }; + const filterPanelOverlay = ref(); + const filterBarLocale = useFilterBarLocale(); + const positionLeft = ref('0px'); + const positionTop = ref('0px'); + const positionWidth = ref('0px') + const arrowPosition = ref('auto'); + const arrowPositionStyle = computed(() => { + return { + left: arrowPosition.value === 'auto' ? '26px' : 'auto', + right: arrowPosition.value + } + }) + const customStyle = computed(() => { + return { + left: positionLeft.value, + top: positionTop.value + } + }); + + const innerCustomStyle=computed(() => { + return { + width: positionWidth.value === '0px' ? null : positionWidth.value, + maxWidth: positionWidth.value === '0px' ? null : positionWidth.value, + } + }); + + const filterPanelRef = ref(); + const { useFilterItemsComposition, resolveEditorProps } = inject('useFilterItemsComposition') as FilterItemUtils; + const { fieldMap } = useFilterItemsComposition; + const { renderFieldConditionEditor, conditionChangeHandler } = useConditionUtils('filter-bar'); + const editorTypeClass = ref(''); + // 标记是否变更过 + let conditionChanged = false; + let conditionChangedValue: any = {}; + // 显示底部工具栏 + const showFooter = ref(true); + + function updatePosition() { + if (!props.reference || !filterPanelRef.value) { + return; + } + const positionInfo = (props.reference as HTMLElement)?.getBoundingClientRect(); + // 此处计算用的固定值 + let filterItemConditionWidth = filterPanelRef.value?.getBoundingClientRect().width; - function onChange(condition, value, option?:any) { - if(condition.value.editorType === 'combo-list' && option.newValue) { - condition.value.valueList = option.newValue.map(item => { - return {name: item[condition.editor.textField || 'name'], value: item[condition.editor.valueField || 'value']}; + const leftAlignBoundarWidth=document.body.clientWidth - positionInfo.x - 30; + const rightAlignBoundarWidth=document.body.clientWidth - positionInfo.y - 30 + + if (filterItemConditionWidth > leftAlignBoundarWidth&&filterItemConditionWidth>rightAlignBoundarWidth) { + // 解决超宽的问题 + filterItemConditionWidth = Math.max(leftAlignBoundarWidth,rightAlignBoundarWidth); + } + + if (positionInfo.left > document.body.clientWidth - filterItemConditionWidth - 20) { + positionLeft.value = positionInfo.left + positionInfo.width - filterItemConditionWidth + 'px'; + positionTop.value = positionInfo.top + positionInfo.height + 'px'; + arrowPosition.value = '26px'; + } else { + positionLeft.value = positionInfo.left + 'px'; + positionTop.value = positionInfo.top + positionInfo.height + 'px'; + arrowPosition.value = 'auto'; + } + positionWidth.value = filterItemConditionWidth + 'px'; + } + + function onChange(condition, value: any, editor?, option?: any) { + conditionChanged = true; + conditionChangedValue = { + value: value, + editor: editor, + option: option + }; + } + /** + * 判断是否存在已弹出的弹出窗口 + * @returns + */ + function hasOpendPopoverElement() { + if (!filterPanelOverlay.value) { return false; } + const filterPanelParent = filterPanelOverlay.value.parentElement; + const children = Array.from(filterPanelParent.children); + if (children) { + const opendPopoverElement = children.find((child: any) => { + if (!child || !child.classList) { return false; } + return child.classList.contains('popover') && child.style.visibility === 'visible'; }); - } else if(condition.value.editorType === 'radio-group') { - condition.value.valueList = [condition.editor.data.find(item => item.value === value)]; + return opendPopoverElement ? true : false; } + return false; + } + + function radioGroupChangeHandler(condition, value: any, editor?, option?: any) { + conditionChangeHandler(condition, value, editor, option); + context.emit('confirm', filterItem.value); } + function renderFieldConditions() { - const { editor } = filterItem.value; - const condition = filterItem.value; - let needEmitChange = true; - if (condition.value?.editorType === 'lookup' && editor) { - editor.idValue = condition.value.mapFields?.map(field => field.id).join(','); - editor['onClear'] = () => { - condition.value.mapFields = []; - onChange(condition, ''); - }; - editor['onUpdate:dataMapping'] = (mapFields) => { - condition.value.mapFields = mapFields.items; - onChange(condition, condition.value.getValue()); - }; - needEmitChange = false; - } else if (condition.value?.editorType === 'number-range' && editor) { - editor.beginValue = condition.value.begin; - editor['onBeginValueChange'] = (value) => { - condition.value.begin = value; - onChange(condition, value); - }; - editor.endValue = condition.value.end; - editor['onEndValueChange'] = (value) => { - condition.value.end = value; - onChange(condition, value); - }; - needEmitChange = false; - }; + const { id, editor, needEmitChange } = renderFieldConditionEditor(fieldMap, filterItem.value, onChange); + editorTypeClass.value = 'filter-panel--' + editor.type; + if (editor.type === 'radio-group') { + const editorProps = resolveEditorProps(editor.type, editor); + showFooter.value = false; + return { radioGroupChangeHandler(filterItem.value, value, editor) }}> + } return onChange(condition, value, option)} + v-model={(filterItem.value.value).value} + onChange={(value, option) => { needEmitChange && onChange(filterItem.value, value, editor, option) }} >; } + onMounted(() => { + updatePosition(); + }); function cancel(event) { - context.emit('cancel'); + const editorType = editorTypeClass.value.replace('filter-panel--', ''); + if (['input', 'radio-group', 'switch'].findIndex(item => item === editorType) > -1) { + context.emit('cancel'); + } else if (!hasOpendPopoverElement()) { + // 存在弹出窗口了 + context.emit('cancel'); + } } function confirm(event: MouseEvent) { event.stopPropagation(); + if (conditionChanged) { + conditionChangeHandler(filterItem.value, conditionChangedValue.value, conditionChangedValue.editor, conditionChangedValue.option); + } context.emit('confirm', filterItem.value); } + function panelFilterClear(event: MouseEvent) { + filterItem.value.value.clear(); + event.stopPropagation(); + context.emit('confirm', filterItem.value); + } + + function getZIndex(){ + const zIndex=getMaxZIndex(); + return {zIndex:Math.max(zIndex,1050)}; + } + const panelZIndex=getZIndex(); + return () => { return ( -
    -
    -
    e.stopPropagation()}> -
    +
    +
    +
    e.stopPropagation()} > +
    {renderFieldConditions()} - + {showFooter.value && + }
    -
    ); diff --git a/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.props.ts b/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.props.ts index a1dbe2f9b08026aabbf76562d2048e83b83631fb..4a9d1ba9089b937706fcce2d8e91d4071b757ebb 100644 --- a/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.props.ts +++ b/packages/ui-vue/components/filter-bar/src/components/filter-item-condition/filter-item-condition.props.ts @@ -4,7 +4,13 @@ import { FilterItem } from '../../types'; export const filterItemConditionProps = { filterItem: { type: Object as PropType, default: null }, - position: { type: Object as PropType, default: null} + position: { type: Object as PropType, default: null}, + /** + * 位置参照元素 + */ + reference: { + type: Object as PropType + } } as Record; export type FilterItemConditionProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.component.tsx b/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.component.tsx index dd3a2b498e00fc3da7d5df534d0896bd272cf8f2..c7634091d083ddd95caac32902130a568aa164ae 100644 --- a/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.component.tsx +++ b/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.component.tsx @@ -1,7 +1,8 @@ -import { computed, ref, defineComponent } from 'vue'; +import { computed, ref, defineComponent, inject, Teleport, onMounted,nextTick } from 'vue'; import { FilterItem } from '../../types'; import FilterItemCondition from '../filter-item-condition/filter-item-condition.component'; import { FilterItemProps, filterItemProps } from './filter-item.props'; +import { FilterItemUtils, UseFilterItems } from '../../composition/types'; export default defineComponent({ name: 'FFilterBarItem', @@ -9,26 +10,32 @@ export default defineComponent({ emits: ['confirm'], setup(props: FilterItemProps, context) { const filterItem = ref(props.filterItem); - const { clearFilterItem, currentFilterId, removeFilterItem, shouldShowClearButtonInFilterItem } = props.useFilterItemsComposition; - const disabled = ref(props.disabled); - const position = ref(); + const { useFilterItemsComposition } = inject('useFilterItemsComposition') as FilterItemUtils; + const { clearFilterItem, currentFilterId, removeFilterItem, shouldShowClearButtonInFilterItem } = useFilterItemsComposition; const showFilterItemCondition = ref(false); + // 是否能显示清空按钮,清空按钮是在鼠标滑过后 + const canShowClearBtn = ref(false); + const filterItemRef = ref(); + const titleTips = ref(""); + const displayTextElementRef = ref(); const filterItemClass = function () { const classObject = { 'f-filter-item': true, + 'f-state-disabled': props.disabled, 'f-filter-item-actived': !filterItem.value.value.isEmpty(), // 'f-filter-item-last': index === filterItems.value.length - 1, - 'f-filter-item-edit': filterItem.value.id === currentFilterId.value + 'f-filter-item-edit': filterItem.value.id === currentFilterId.value || showFilterItemCondition.value } as Record; return classObject; }; const shouldShowArrowButton = computed(() => { - return !disabled.value && props.mode === 'editable'; + return !props.disabled && props.mode === 'editable'; }); function onClearFilter($event: MouseEvent) { + if (props.disabled) { return; } if (props.mode === 'display-only') { removeFilterItem(filterItem.value); } else { @@ -37,23 +44,7 @@ export default defineComponent({ } function onClickFilter($event: MouseEvent,) { - const positionInfo = ($event.currentTarget as HTMLElement)?.getBoundingClientRect(); - const filterItemConditionWidth = 380; - if(positionInfo.left > document.body.clientWidth - filterItemConditionWidth - 20) { - position.value = { - left: positionInfo.left + positionInfo.width - filterItemConditionWidth + 'px', - top: positionInfo.top + positionInfo.height + 'px', - arrowLeft: 'auto', - arrowRight: '26px' - }; - } else { - position.value = { - left: positionInfo.left + 'px', - top: positionInfo.top + positionInfo.height + 'px', - arrowLeft: '26px', - arrowRight: 'auto' - }; - } + if (props.disabled) { return; } showFilterItemCondition.value = true; } @@ -61,12 +52,31 @@ export default defineComponent({ showFilterItemCondition.value = false; } + // 文字超长时,给出提示 + function changeTitleTips() { + if (displayTextElementRef.value && displayTextElementRef.value.scrollWidth > displayTextElementRef.value.clientWidth) { + titleTips.value = displayTextElementRef.value.innerHTML; + } else { + titleTips.value = ''; + } + } function confirm(newFilterItem) { showFilterItemCondition.value = false; filterItem.value.value = newFilterItem.value; context.emit('confirm', newFilterItem); - + nextTick(()=>{ + changeTitleTips(); + }); + } + function mouseEnterHandler() { + canShowClearBtn.value = props.disabled ? false : shouldShowClearButtonInFilterItem(filterItem.value); } + function mouseLeaveHandler() { + canShowClearBtn.value = false; + } + onMounted(() => { + changeTitleTips(); + }); return () => { return ( @@ -74,31 +84,33 @@ export default defineComponent({ key={filterItem.value.id} id={filterItem.value.id} class={filterItemClass()} - > -
    onClickFilter(payload)}> - {filterItem.value.editor.required && *} + onMouseenter={() => mouseEnterHandler()} + onMouseleave={() => mouseLeaveHandler()} + > +
    onClickFilter(payload)}> + {filterItem.value.required && *} {filterItem.value.fieldName} { - filterItem.value.value.getDisplayText() && <> + !filterItem.value.value.isEmpty() && <> : - {filterItem.value.value.getDisplayText()} + {filterItem.value.value.getDisplayText()} } - {!disabled.value && } + {!props.disabled && }
    - {shouldShowClearButtonInFilterItem(filterItem.value) && ( + {canShowClearBtn.value && ( onClearFilter(payload)}> )} - { - showFilterItemCondition.value && + {showFilterItemCondition.value && + }
    ); diff --git a/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.props.ts b/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.props.ts index b2360685af41001676a738b6eeb07124382282bc..102a9157e6a936e1b3631ad73f854fe4250cacc9 100644 --- a/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.props.ts +++ b/packages/ui-vue/components/filter-bar/src/components/filter-item/filter-item.props.ts @@ -5,7 +5,6 @@ import { FilterItem } from '../../types'; export const filterItemProps = { filterItem: { type: Object as PropType, default: null }, - useFilterItemsComposition: {type: Object as PropType, default: null}, disabled: { type: Boolean, default: false } } as Record; diff --git a/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/filter-radio-group.component.tsx b/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/filter-radio-group.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..01027195048a6a057b61c1fb312f1864992a1126 --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/filter-radio-group.component.tsx @@ -0,0 +1,47 @@ +import { ref, defineComponent, inject } from 'vue'; +import { FilterRadioGroupProps, filterRadioGroupProps } from './filter-radio-group.props'; +import {FComboListContainer,useDataSource,Option} from '@farris/ui-vue/components/combo-list'; + +export default defineComponent({ + name: 'FFilterRadioGroup', + props: filterRadioGroupProps, + emits: ['update:modelValue', 'change'], + setup(props: FilterRadioGroupProps, context) { + const comboListContainerRef = ref(); + + const { dataSource, displayText, modelValue} = useDataSource(props); + function onSelectionChange(selectedItems: Option[]) { + displayText.value = selectedItems.map((item: Option) => item[props.textField]).join(props.separator); + let value = ''; + if (selectedItems.length === 1) { + value = selectedItems[0][props.valueField]; + } else { + value = selectedItems.map((item: Option) => item[props.valueField]).join(props.separator); + } + if (modelValue.value !== value) { + // 数据变化后 触发双向绑定和change事件 + modelValue.value = value; + context.emit('update:modelValue', modelValue.value); + context.emit('change', modelValue.value); + } + } + + return () => { + return ( + { + onSelectionChange(values); + }}> + ); + }; + } +}); \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/filter-radio-group.props.ts b/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/filter-radio-group.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1cf0f2cc0b6f87b65db78802ce9073115c4c29a --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/filter-radio-group.props.ts @@ -0,0 +1,9 @@ + +import { ExtractPropTypes, PropType } from "vue"; +import { radioGroupProps } from '@farris/ui-vue/components/radio-group'; + + +export const filterRadioGroupProps=Object.assign({},radioGroupProps); + +export type FilterRadioGroupProps = ExtractPropTypes; + diff --git a/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/use-data-source.ts b/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/use-data-source.ts new file mode 100644 index 0000000000000000000000000000000000000000..dfe51e44108cc9a74283a0ed6774d2954b277baa --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/components/filter-radio-group/use-data-source.ts @@ -0,0 +1,123 @@ +import { ref, watch } from "vue"; +import {Option} from '@farris/ui-vue/components/combo-list'; +import { useI18n } from "vue-i18n"; +import { FilterRadioGroupProps } from "./filter-radio-group.props"; +export function useDataSource(props: FilterRadioGroupProps) { + const { t: getLocaleValue } = useI18n(); + const displayText = ref(''); + const modelValue = ref(props.modelValue); + const dataSource = ref(props.data || []); + const editable = ref(props.editable); + function getItemsByValue(value: string): Option[] { + const valueList = props.multiSelect ? String(value).split(props.separator) : [String(value)]; + const valueArray = valueList.map<[string, boolean]>((valueText: string) => { + return [valueText, true]; + }); + const valueMap = new Map(valueArray); + const selectedItems = dataSource.value.filter((item: Option) => valueMap.has(String(item[props.valueField]))); + return selectedItems.sort((previous: Option, next: Option) => { + const previousItem = valueList.indexOf(previous[props.valueField]); + const nextItem = valueList.indexOf(next[props.valueField]); + return previousItem - nextItem; + }); + } + + function updateDisplayTextByValue(value: string) { + const matchedValue = getItemsByValue(value).map((item: Option) => item[props.textField]).join(props.separator); + displayText.value = editable.value ? (matchedValue || value) : matchedValue; + } + + function getItemsByDisplayText(text: string): Option[] { + if (props.multiSelect) { + const displayTextArray = text.split(props.separator).map<[string, boolean]>((optionText: string) => { + return [optionText, true]; + }); + const displayTextMap = new Map(displayTextArray); + return dataSource.value.filter((item: Option) => displayTextMap.has(item[props.textField])); + } + return dataSource.value.filter((item: Option) => '' + item[props.textField] === text); + } + + function buildSelectedItemByDisplayText(displayTextString: string) { + const changedValue = {} as Record; + changedValue[props.idField] = displayTextString; + changedValue[props.textField] = displayTextString; + return [changedValue]; + } + + function getSelectedItemsByDisplayText(displayTextString: string) { + let selectedItems = getItemsByDisplayText(displayTextString); + const hasMatchedItems = selectedItems && selectedItems.length > 0; + if (editable.value && !hasMatchedItems) { + selectedItems = buildSelectedItemByDisplayText(displayTextString); + } + return selectedItems; + } + + function loadRemoteData() { + const { url, method = 'GET', headers = {'Content-Type': 'application/json;charset=utf-8;'}, body = null } = props.remote; + // const requestInfo = { method, headers, body }; + if(!url) { + return; + } + const requestInfo = method.toLowerCase() === 'get' ? { method, headers } : { method, headers, body }; + let isFromJson = false; + fetch(new Request(url, requestInfo)) + .then((response: Response) => { + if (response.status === 200) { + // 如果是 JSON 数据,直接解析 + isFromJson = !!response.headers?.get('content-type')?.includes('application/json'); + return isFromJson ? response.text() : response.json(); + } + if(response.status === 405) { + throw new Error(getLocaleValue('comboList.remoteError')); + } + const error = new Error(response.statusText); + throw error; + }) + .then((data: string) => { + if (data.length) { + dataSource.value = isFromJson ? JSON.parse(data) : data; + } + }) + .catch((err) => { + console.warn(err); + }); + } + + if (props.remote) { + if (props.load) { + props.load().then((data: any) => { + dataSource.value = data; + }).catch((e: any) => { + console.log(e);; + }); + } else { + loadRemoteData(); + } + } + + watch(() => props.data, () => { + dataSource.value = props.data; + }); + + watch([dataSource], ([dataSourceValue]) => { + // modelValue可能是空或者是0 + if (props.modelValue != null) { + const currentItem = dataSourceValue.find((item: any) => item[props.valueField] === props.modelValue); + if (currentItem) { + displayText.value = currentItem[props.textField]; + } + } + }); + + watch(() => props.modelValue, (newValue) => { + // 开启搜索时,匹配下拉面板 + modelValue.value = newValue; + updateDisplayTextByValue(newValue); + }); + + // updateDisplayTextByValue(props.modelValue); + + return { dataSource, displayText, editable, modelValue, getItemsByDisplayText, getItemsByValue }; +} diff --git a/packages/ui-vue/components/filter-bar/src/components/toolbar.component.tsx b/packages/ui-vue/components/filter-bar/src/components/toolbar.component.tsx index 67fffe7ed5b77a9ccc7042feac9f2796c7bcf8c1..3622d8a452fc0edf328ef0f21689814ce3202439 100644 --- a/packages/ui-vue/components/filter-bar/src/components/toolbar.component.tsx +++ b/packages/ui-vue/components/filter-bar/src/components/toolbar.component.tsx @@ -1,29 +1,31 @@ -import { SetupContext, computed, ref, watch } from 'vue'; +import { Ref, SetupContext, computed, ref, watch } from 'vue'; import { FilterBarProps } from '../filter-bar.props'; import { UseFilterItems } from '../composition/types'; import { FilterItem } from '../types'; import getDrawerRender from './drawer.component'; +import { getLocaleResetText, useFilterBarLocale } from '../locale/locale'; -export default function (props: FilterBarProps, context, useFilterItemsComposition: UseFilterItems) { +export default function (props: FilterBarProps, context, useFilterItemsComposition: UseFilterItems, resetButtonState: Ref) { const collapsedFilterList = ref([]); - const advancedFilterText = ref('更多'); - const resetText = ref(props.resetText); - const shownResetButton = ref(props.showReset); + const filterBarLocale = useFilterBarLocale(); + // 高级按钮文本 + const advancedFilterText = ref(filterBarLocale.advancedFilter); + + // 清空文本 + const resetText = ref(getLocaleResetText(props.resetText)); + const shownDrawer = ref(false); - const { clearAll, reset, filterItems, filterFields } = useFilterItemsComposition; + const { clearAll, reset, filterItems } = useFilterItemsComposition; function initToolbar() { - collapsedFilterList.value = filterItems.value.filter(filterItem => filterItem.editor.isExtend); + collapsedFilterList.value = filterItems.value.filter(filterItem => filterItem.isExtend); } + initToolbar(); + // 是否显示高级按钮 const shouldShowAdvancedFilter = computed(() => { return collapsedFilterList.value && collapsedFilterList.value.length > 0; }); - - const shouldShowResetButton = computed(() => { - return shownResetButton.value; - }); - function onClickAdvancedFilter($event: MouseEvent) { shownDrawer.value = true; } @@ -35,8 +37,12 @@ export default function (props: FilterBarProps, context, useFilterItemsCompositi reset(); } } - - const renderDrawer = getDrawerRender(props, context, shownDrawer, collapsedFilterList, useFilterItemsComposition); + // 暂时不支持 + // const renderDrawer = getDrawerRender(props, context, shownDrawer, collapsedFilterList, useFilterItemsComposition); + // 结构: + /** + * 清空已选、高级按钮 + */ return () => { return (
    @@ -45,15 +51,12 @@ export default function (props: FilterBarProps, context, useFilterItemsCompositi {advancedFilterText.value} )} - {shouldShowResetButton.value && ( + {resetButtonState.value && ( )} - { - renderDrawer() - } - + {/* {renderDrawer()} */}
    ); }; diff --git a/packages/ui-vue/components/filter-bar/src/composition/build-filter-bar.ts b/packages/ui-vue/components/filter-bar/src/composition/build-filter-bar.ts index d153dc2b23e2b5286d3e3122339054ddca02acf9..6a3cacd91dde56a6239cf69d513dbc8f8f27ec71 100644 --- a/packages/ui-vue/components/filter-bar/src/composition/build-filter-bar.ts +++ b/packages/ui-vue/components/filter-bar/src/composition/build-filter-bar.ts @@ -11,8 +11,6 @@ export function buildFilterBar(context: Record, designerHostService /** 数据加载控制器(LoadCommands)id*/ const loadCommandsWebCmdId = '54bddc89-5f7e-4b91-9c45-80dd6606cfe9'; - /** 列表控制器(ListController)id */ - const listControllerWebCmdId = '70b4abd4-9f2c-4b7c-90e9-6ac6f4b74c72'; /** * 组装筛选条容器和筛选条元数据,并插入组件schema @@ -37,36 +35,43 @@ export function buildFilterBar(context: Record, designerHostService fields: [] }); + // 3、预置筛选条相关的变量 + const targetComponentId = targetComponentInstance.belongedComponentId; + const viewModelId = formSchemaUtils.getViewModelIdByComponentId(targetComponentId); + const viewModel = formSchemaUtils.getViewModelById(viewModelId); + if (viewModel?.states) { + if (!viewModel.states.find(state => state.code === 'originalFilterConditionList')) { + viewModel.states.push( + { + id: useGuid().guid(), + category: 'locale', + code: 'originalFilterConditionList', + name: '筛选条过滤条件', + type: 'String' + } + ); + } + } return { filterBar: filterBarMetadata, filterBarContainer }; } - function createFilterCommand(): { filterInGridViewModel: any, filterInRootViewModel: any } { - - const rootViewModel = formSchemaUtils.getViewModelById('root-viewmodel'); - let filterInRootViewModel: any; - - // 1、在根组件中预置过滤并加载命令 - if (rootViewModel && rootViewModel.commands) { - const filterCommand = rootViewModel.commands.find(command => command.handlerName === 'Filter' && command.cmpId === loadCommandsWebCmdId); - if (filterCommand) { - filterInRootViewModel = filterCommand; - if (filterCommand.params && filterCommand.params.length) { - const filterParam = filterCommand.params.find(p => p.name === 'filter'); - if (filterParam) { - filterParam.value = ''; - } - } - } else { - filterInRootViewModel = { + function createFilterCommand(): { filterInGridViewModel: any } { + const viewModelId = formSchemaUtils.getViewModelIdByComponentId(targetComponentInstance.belongedComponentId); + const belongViewModel = formSchemaUtils.getViewModelById(viewModelId); + let filterInGridViewModelCommand: any; + if (belongViewModel && belongViewModel.commands) { + filterInGridViewModelCommand = belongViewModel.commands.find(command => command.handlerName === 'Filter' && command.cmpId === loadCommandsWebCmdId); + if (!filterInGridViewModelCommand) { + filterInGridViewModelCommand = { id: useGuid().guid(), - code: `${rootViewModel.id.replace(/-/g, '').replace('component', '').replace('viewmodel', '')}Filter1`, + code: `${belongViewModel.id.replace(/-/g, '').replace('component', '').replace('viewmodel', '')}Filter1`, name: '过滤并加载数据1', params: [ { name: 'filter', shownName: '过滤条件', - value: '', + value: `{UISTATE~/#{${targetComponentInstance.belongedComponentId}}/originalFilterConditionList}`, defaultValue: null }, { @@ -79,69 +84,34 @@ export function buildFilterBar(context: Record, designerHostService handlerName: 'Filter', cmpId: loadCommandsWebCmdId }; - rootViewModel.commands.push(filterInRootViewModel); - } - } - - // 2、在表格所在的组件中预置过滤列表数据命令 - const viewModelId = formSchemaUtils.getViewModelIdByComponentId(targetComponentInstance.belongedComponentId); - const viewModel = formSchemaUtils.getViewModelById(viewModelId); - let filterInGridViewModel: any; - if (viewModel && viewModel.commands) { - const filterCommand = viewModel.commands.find(command => command.handlerName === 'Filter' && command.cmpId === listControllerWebCmdId); - if (filterCommand) { - filterInGridViewModel = filterCommand; - if (filterCommand.params && filterCommand.params.length && filterInRootViewModel) { - const commandName = filterCommand.params.find(p => p.name === 'commandName'); - if (commandName) { - commandName.value = filterInRootViewModel.code; - } - const frameId = filterCommand.params.find(p => p.name === 'frameId'); - if (frameId) { - frameId.value = '#{root-component}'; + belongViewModel.commands.push(filterInGridViewModelCommand); + } else { + if (filterInGridViewModelCommand.params && filterInGridViewModelCommand.params.length) { + const filterParam = filterInGridViewModelCommand.params.find(p => p.name === 'filter'); + if (filterParam) { + filterParam.value = `{UISTATE~/#{${targetComponentInstance.belongedComponentId}}/originalFilterConditionList}`; } } - } else { - filterInGridViewModel = { - id: useGuid().guid(), - code: `${viewModel.id.replace(/-/g, '').replace('component', '').replace('viewmodel', '')}Filter1`, - name: '过滤列表数据1', - params: [ - { - name: 'commandName', - shownName: '过滤回调方法', - value: filterInRootViewModel.code - }, - { - name: 'frameId', - shownName: '目标组件', - value: '#{root-component}' - } - ], - handlerName: 'Filter', - cmpId: listControllerWebCmdId - }; - viewModel.commands.push(filterInGridViewModel); } } - return { filterInGridViewModel, filterInRootViewModel }; + return { filterInGridViewModel:filterInGridViewModelCommand }; } /** * 为命令添加构件引用(ListController.webcmd、LoadCommands.webcmd) */ - function addWebCmdForFilterCommand(filterInRootViewModel: any, filterInGridViewModel: any) { + function addWebCmdForFilterCommand(filterInGridViewModel: any) { const webCmds = formSchemaUtils.getCommands(); if (!webCmds) { return; } - let listCmd = webCmds.find(cmd => cmd.id === listControllerWebCmdId); + let listCmd = webCmds.find(cmd => cmd.id === loadCommandsWebCmdId); if (!listCmd) { listCmd = { - id: listControllerWebCmdId, - path: 'Gsp/Web/webcmp/bo-webcmp/metadata/webcmd', - name: 'ListController.webcmd', + id: loadCommandsWebCmdId, + path: 'igix/Web/WebCmp/bo-webcmp/metadata/webcmd/data-commands', + name: 'LoadCommands.webcmd', refedHandlers: [] }; formSchemaUtils.getCommands().push(listCmd); @@ -153,36 +123,14 @@ export function buildFilterBar(context: Record, designerHostService handler: 'Filter' } ); - - } - let loadCmd = webCmds.find(cmd => cmd.id === loadCommandsWebCmdId); - if (!loadCmd) { - loadCmd = { - id: loadCommandsWebCmdId, - path: 'Gsp/Web/WebCmp/bo-webcmp/metadata/webcmd/data-commands', - name: 'LoadCommands.webcmd', - refedHandlers: [] - }; - formSchemaUtils.getCommands().push(loadCmd); - } - if (filterInRootViewModel && !loadCmd.refedHandlers.find(item => item.host === filterInRootViewModel.id)) { - loadCmd.refedHandlers.push( - { - host: filterInRootViewModel.id, - handler: 'Filter' - } - ); - } - } function createFilterBar() { // 1、组装筛选条容器和筛选条 const { filterBar, filterBarContainer } = createFilterContainer(); - // 2、创建筛选条相关命令 - const { filterInRootViewModel, filterInGridViewModel } = createFilterCommand(); + const { filterInGridViewModel } = createFilterCommand(); // 3、将查询命令绑定到筛选条组件 if (filterBar && filterInGridViewModel) { @@ -190,9 +138,9 @@ export function buildFilterBar(context: Record, designerHostService } // 4、预置筛选条查询数据的构件 - addWebCmdForFilterCommand(filterInRootViewModel, filterInGridViewModel); + addWebCmdForFilterCommand(filterInGridViewModel); - // 5、因为涉及到新增【ListController、LoadCommands控制器】,所以这里需要获取控制器信息,方便交互面板展示命令数据 + // 5、因为涉及到新增【LoadCommands控制器】,所以这里需要获取控制器信息,方便交互面板展示命令数据 const formCommandService = designerHostService?.useFormCommand; if (formCommandService) { formCommandService.checkCommands(); @@ -201,9 +149,5 @@ export function buildFilterBar(context: Record, designerHostService return filterBarContainer; } - function checkCanAcceptFilterBar() { - - } - return { createFilterBar }; } diff --git a/packages/ui-vue/components/filter-bar/src/composition/types.ts b/packages/ui-vue/components/filter-bar/src/composition/types.ts index 8f399a858f64742dc2a38ac2b9c1eb9da5dd0dc7..efa8e06e05341254d156910855ea770890d65440 100644 --- a/packages/ui-vue/components/filter-bar/src/composition/types.ts +++ b/packages/ui-vue/components/filter-bar/src/composition/types.ts @@ -14,8 +14,10 @@ export interface UseFilterItems { filterFields: Ref; filterItems: Ref; + + fieldMap: Map; - loadFilterItems: (fields: FieldConfig[], conditions: Condition[]) => void; + loadFilterItems: (fields: FieldConfig[], conditions: Condition[],canQuery?:boolean) => void; removeFilterItem: (itemToBeRemoved: FilterItem) => void; @@ -23,5 +25,28 @@ export interface UseFilterItems { shouldShowClearButtonInFilterItem: (filterItem: FilterItem) => boolean; - handleQuery: () => QueryItem[]; + handleQuery: () => void; +} + +export interface UseFilterItemsDesign { + + currentFilterId: Ref; + + filterFields: Ref; + + filterItems: Ref; + + fieldMap: Map; + + loadFilterItems: (fields: FieldConfig[], conditions: Condition[]) => void; + + shouldShowClearButtonInFilterItem: (filterItem: FilterItem) => boolean; +} + +export interface FilterItemUtils{ + useFilterItemsComposition:UseFilterItems; + resolveEditorProps:any; } +export interface FilterUtils{ + resizeFilerBar:()=>void; +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/composition/use-condition.ts b/packages/ui-vue/components/filter-bar/src/composition/use-condition.ts index 7319d27a114ed8d53234d02d39f5f46dcc854a15..e8d5e28e7ba3ab7014b22e4df45c4ee90b4ba32d 100644 --- a/packages/ui-vue/components/filter-bar/src/composition/use-condition.ts +++ b/packages/ui-vue/components/filter-bar/src/composition/use-condition.ts @@ -137,6 +137,23 @@ export function useCondition(props: FilterBarProps, context: SetupContext): UseC } return querys; } + function convertCheckGroup(condition) { + const querys: QueryItem[] = []; + const checkgroupValue = condition.value.getValue(); + checkgroupValue.split(',').forEach(value => { + querys.push({ + 'FilterField': condition.fieldCode, + 'Compare': (condition.compareType || condition.compareType === 0) ? condition.compareType : CompareType.Equal, + 'Value': value, + 'Relation': condition.compareType === CompareType.NotEqual ? RelationType.And : RelationType.Or, + 'Expresstype': ValueType.Value + }); + }); + querys[0]['Lbracket'] = condition.Lbracket ? (condition.Lbracket + '(') : '('; + querys[querys.length - 1]['Rbracket'] = condition.Rbracket ? (condition.Rbracket + ')') : ')'; + querys[querys.length - 1]['Relation'] = (condition.relation || condition.relation === 0) ? condition.relation : RelationType.And; + return querys; + } function convertRadioGroup(condition) { const querys: QueryItem[] = []; @@ -170,6 +187,9 @@ export function useCondition(props: FilterBarProps, context: SetupContext): UseC case 'date-picker': querysResult = convertDatePicker(condition); break; + case 'check-group': + querysResult=convertCheckGroup(condition); + break; case 'combo-list': querysResult = convertComboList(condition, field); break; diff --git a/packages/ui-vue/components/filter-bar/src/composition/use-filter-items.ts b/packages/ui-vue/components/filter-bar/src/composition/use-filter-items.ts index 111f81810378b911ec5ad2b43903e52d5e902697..dd569c6a9e40ec5b6ae18e1ed97560497ea03ec5 100644 --- a/packages/ui-vue/components/filter-bar/src/composition/use-filter-items.ts +++ b/packages/ui-vue/components/filter-bar/src/composition/use-filter-items.ts @@ -1,81 +1,60 @@ -import { SetupContext, ref } from 'vue'; +import { Ref, SetupContext, ref } from 'vue'; import { FilterBarProps } from '../filter-bar.props'; import { Condition, FieldConfig, FilterItem } from '../types'; -import { UseFilterItems } from './types'; -import { useConditionValue } from '@farris/ui-vue/components/condition'; -import { useCondition } from '@farris/ui-vue/components/query-solution'; -import { cloneDeep } from 'lodash-es'; - -export function useFilterItems(props: FilterBarProps, context): UseFilterItems { +import { FilterUtils, UseFilterItems } from './types'; +import { useConditionValue, useConditionUtils } from '@farris/ui-vue/components/condition'; +import { useCondition, useSolutionUtils } from '@farris/ui-vue/components/query-solution'; +import { FNotifyService } from "@farris/ui-vue/components/notify"; +import { useFilterBarLocale } from '../locale/locale'; +export function useFilterItems(props: FilterBarProps, context, resetButtonState: Ref, filterUtils: FilterUtils): UseFilterItems { const filterConditions = ref([]); const filterFields = ref([]); const filterItems = ref([]); const currentFilterId = ref(''); - const disabled = ref(false); const mode = ref(props.mode); const fieldMap = new Map(); const { createConditionValue } = useConditionValue(); - const useConditionComposition = useCondition(props ,context); + const { convertToControls } = useConditionUtils('filter-bar'); + const useConditionComposition = useCondition(props, context); const { getFilterConditions } = useConditionComposition; - + const { fieldToCondition, judgeLoadNewValue } = useSolutionUtils(); + const notifyService: any = new FNotifyService(); + const filterBarLocale = useFilterBarLocale(); + // 备份条件 + let cloneQueryConditionsSting = '[]'; function shouldShowClearButtonInFilterItem(filterItem: FilterItem) { - return !filterItem.value.isEmpty() && !!filterItem.value.getDisplayText(); - } - - function fieldToCondition(field: FieldConfig) { - const condition = { - id: field.id, - fieldCode: field.labelCode, - fieldName: field.name, - required: field.editor.required, - editor: field.editor, - value: createConditionValue(field.controlType || 'text') - }; - if(field.controlType === 'lookup') { - condition.value.valueField = field.editor.valueField; - condition.value.helpId = field.editor.helpId; - } else if(field.controlType === 'date-picker') { - condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM-dd'; - condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM-dd'; - } else if(field.controlType === 'datetime-picker') { - condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM-dd HH:mm:ss'; - condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM-dd HH:mm:ss'; - } - return condition; + return !props.disabled && !filterItem.value.isEmpty(); } - + /** + * 更新字段信息 + * @param fields + */ function loadFieldConfigs(fields: FieldConfig[]) { - filterFields.value = props.fields.map((field) => { - const currentField = cloneDeep(field); - if (!currentField.editor.type) { - currentField.editor.type = field.controlType; - } - if(currentField.editor.type === 'number-spinner' || currentField.editor.type === 'number-range') { - currentField.editor.showZero = true; - currentField.editor.nullable = true; - } else if(currentField.editor.type === 'combo-list') { - currentField.editor.enableClear = true; - } else if(currentField.editor.type === 'datetime-picker') { - currentField.editor.type = 'date-picker'; - currentField.editor.showTime = true; - } - return currentField; - }); + filterFields.value = convertToControls(fields); filterFields.value.reduce((result: Map, field: FieldConfig) => { + result.set(field.labelCode, field); return result; }, fieldMap); } - + /** + * 更新条件 + * @param conditions + */ function loadConditions(conditions: Condition[]) { - filterItems.value = filterFields.value.map((field:FieldConfig) => { - return fieldToCondition(field); + // 会出现conditions异常问题 + const newDefaultValues = Array.isArray(conditions) ? conditions : []; + filterItems.value = filterFields.value.map((targetField: FieldConfig) => { + const defaultValue = newDefaultValues.find(defaultValue => defaultValue.id === targetField.id); + if (defaultValue) { + // 特殊情况,需要检测数字输入控件精度与默认值是否匹配,否则可能出现实际值与展示值不一致的情况,如果默认值超过精度控制,值清空 + const currentCondition = fieldToCondition(targetField, defaultValue.value); + const loadNewValue = judgeLoadNewValue(currentCondition, targetField); + loadNewValue && currentCondition.value.clear(); + return currentCondition; + } + return fieldToCondition(targetField) as any; }); - } - function loadFilterItems(fields: FieldConfig[], conditions: Condition[]) { - loadFieldConfigs(props.fields); - loadConditions(props.data); - // filterItems.value = buildFilterItems(); } function removeFilterItem(itemToBeRemoved: FilterItem) { @@ -86,37 +65,96 @@ export function useFilterItems(props: FilterBarProps, context): UseFilterItems { filterFields.value = filterFields.value.filter((field: FieldConfig) => field.id !== itemToBeRemoved.id); context.emit('remove', itemToBeRemoved.fieldCode); } + /** + * 更新是否可见按钮的状态 + */ + function updateResetButtonState() { + if (props.disabled) { + resetButtonState.value = false; + } else { + resetButtonState.value = filterItems.value?.findIndex(filterItem => { + return filterItem.value && !filterItem.value?.isEmpty(); + }) > -1; + } + } + /** + * 判断条件是否变更 + * @param querys + */ + function judgeEmitEvent(querys, requiredFields = []) { + const newCondtionsString = JSON.stringify(querys || []); + if (newCondtionsString !== cloneQueryConditionsSting) { + // 条件变更,未必能查询 + context.emit('conditionChange', querys); + cloneQueryConditionsSting = newCondtionsString; + if (requiredFields.length === 0) { + // 抛出查询事件 + context.emit('query', querys); + } + } + } + function handleQuery() { + updateResetButtonState(); + const requiredFields = [] as any; + (filterItems.value || []).map(condition => { + if (condition.required && (!condition.value || condition.value.isEmpty())) { + requiredFields.push(condition.fieldName || ''); + } + }); + const queryItems = filterItems.value?.filter(filterItem => { + return filterItem.value && !filterItem.value?.isEmpty(); + }); + if (requiredFields.length > 0) { + // 存在非空查询条件并且存在必填字段未填 + notifyService.warning({ message: filterBarLocale.require.replace('[fields]', requiredFields.join(",")), position: 'top-center' }); + return; + } + const querys = getFilterConditions(queryItems, filterFields.value); + judgeEmitEvent(querys, requiredFields); + } + /** + * 存在初始没有默认值的情况,这个时候不需要查询 + * @param fields + * @param conditions + * @param canQuery + */ + function loadFilterItems(fields: FieldConfig[], conditions: Condition[], canQuery = true) { + loadFieldConfigs(fields); + loadConditions(conditions); + canQuery && handleQuery(); + // filterItems.value = buildFilterItems(); + } + /** + * 清空某个筛选项条件 + * @param itemToBeClear + */ function clearFilterItem(itemToBeClear: FilterItem) { itemToBeClear.value.clear(); filterConditions.value = filterConditions.value.filter((condition: Condition) => { return condition.fieldCode !== itemToBeClear.fieldCode; }); context.emit('clear', itemToBeClear.fieldCode); + handleQuery(); } function clearAll() { - filterItems.value.forEach((filterItem:FilterItem) => filterItem.value.clear()); + filterItems.value.forEach((filterItem: FilterItem) => filterItem.value.clear()); } - + /** + * 重置所有筛选项条件 + */ function reset() { filterItems.value.forEach((itemToBeClear: FilterItem) => { itemToBeClear.value.clear(); }); context.emit('reset'); + handleQuery(); + filterUtils.resizeFilerBar(); } - loadFilterItems(props.fields, props.data); - - function handleQuery() { - const queryItems = filterItems.value?.filter(filterItem => { - return !filterItem.value?.isEmpty(); - }); - const querys = getFilterConditions(queryItems, filterFields.value); - return querys; - } return { - clearAll, clearFilterItem, currentFilterId, filterFields, filterItems, - loadFilterItems, removeFilterItem, reset, shouldShowClearButtonInFilterItem, handleQuery + clearAll, clearFilterItem, currentFilterId, filterFields, filterItems, fieldMap, + loadFilterItems, removeFilterItem, reset, shouldShowClearButtonInFilterItem, handleQuery, }; } diff --git a/packages/ui-vue/components/filter-bar/src/composition/use-size.ts b/packages/ui-vue/components/filter-bar/src/composition/use-size.ts index eb3e27cc3dc9e637693d0576c64d743eb72ade33..98880cc9b23e36dbcb09ed8bf4c3a820d45b10d0 100644 --- a/packages/ui-vue/components/filter-bar/src/composition/use-size.ts +++ b/packages/ui-vue/components/filter-bar/src/composition/use-size.ts @@ -13,82 +13,116 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UseCondition, ValueType, RelationType, CompareType, QueryItem } from '@farris/ui-vue/components/query-solution'; -import { ref, Ref, SetupContext } from 'vue'; -import { FilterBarProps } from '../filter-bar.props'; - -export function useSize(filterItemsContainerRef:Ref, filterItemsRef:Ref, shownCollapseButton: Ref) { +import { ref, Ref } from 'vue'; +/** + * + * @param filterItemsContainerRef 外层容器,包含筛选项展示列表、省略号、工具栏 + * @param filterItemsRef 筛选项展示列表 + * @param shownCollapseButton + * @returns + */ +export function useSize(filterItemsContainerRef: Ref, shownCollapseButton: Ref) { const observeInstance = ref(null); + + // 阈值 + const distanceThreshold = 15; + // 记录旧的宽度数据 + let containerWidthRecord = 0; /** * 根据父元素宽度动态控制子元素显示数量 * @param {HTMLElement} filterItemsElement - 父元素(必须设置overflow:hidden) * @param {NodeList|Array} children - 子元素集合 */ - function handleVisibleChildren() { - let ifShownCollapseButton = false; - const childElements = filterItemsRef.value?.children; + function handleVisibleChildren(currentWidth = -1, widthRecord = -1) { + if (!filterItemsContainerRef.value) { + return; + } - // 检查参数有效性 - if (!filterItemsRef.value || !filterItemsContainerRef.value || !childElements || childElements.length === 0){ + // 查找筛选项的外层父元素 + const filterItemsParent = filterItemsContainerRef.value?.querySelector('.f-filter-list'); + if (!filterItemsParent) { return; } - const toolbarWidth = filterItemsContainerRef.value.children[1]?.clientWidth; + const childElements = filterItemsParent.children; + // 检查是否有子元素 + if (!childElements || childElements.length === 0) { + return; + } + // 获取父元素可用宽度 - const filterItemsElementWidth = filterItemsContainerRef.value.clientWidth; - let totalWidth = 0; - let visibleCount = 0; + if (currentWidth < 0) { + currentWidth = filterItemsContainerRef.value.clientWidth; + } + containerWidthRecord = widthRecord < 0 ? containerWidthRecord : widthRecord; + if (Math.abs(currentWidth - containerWidthRecord) < distanceThreshold) { + return; + } // 重置所有子元素的可见性 - Array.from(childElements).forEach((child:any) => { - child.style.display = 'block'; + Array.from(childElements).forEach((child: any) => { + child.style.display = 'flex'; }); + + // 存在滚动条,显示收折和省略号 + shownCollapseButton.value = filterItemsParent.clientWidth < filterItemsParent.scrollWidth; + + let ifShownCollapseButton = false; + let totalWidth = 0; // 计算可以完整显示多少个子元素 for (let i = 0; i < childElements.length; i++) { - const child:any = childElements[i]; - const childWidth = child.offsetWidth; - // console.log(i, filterItemsElementWidth, totalWidth, childWidth) + const child: any = childElements[i]; + const childWidth = Math.ceil(child.offsetWidth + (parseFloat(getComputedStyle(child).marginRight) || 0) + parseFloat(getComputedStyle(child).marginLeft)); // 如果加上当前子元素不会超出父元素宽度 - if (totalWidth + childWidth <= filterItemsElementWidth - 30 - toolbarWidth) { + if (!ifShownCollapseButton && totalWidth + childWidth <= currentWidth) { totalWidth += childWidth; - visibleCount++; } else { // 超出宽度的子元素隐藏 child.style.display = 'none'; ifShownCollapseButton = true; } } - shownCollapseButton.value = ifShownCollapseButton; - return visibleCount; + // 记录 + containerWidthRecord = currentWidth; } - + /** + * 取消宽度变更监听 + * @returns + */ function cancelWidthObserver() { - if(!filterItemsContainerRef.value || !filterItemsRef.value) { + if (!filterItemsContainerRef.value) { return; } observeInstance.value?.unobserve(filterItemsContainerRef.value); - const childElements = filterItemsRef.value.children; - Array.from(childElements).forEach((child:any) => { - child.style.display = 'block'; + const childElements = filterItemsContainerRef.value.querySelector('.f-filter-list')?.children || []; + Array.from(childElements).forEach((child: any) => { + child.style.display = 'flex'; }); + // 旧宽度重置 + containerWidthRecord = 0; } - + /** + * 监听 宽度变更 + * @returns + */ function setupWidthObserver() { - if(!filterItemsRef.value || !filterItemsContainerRef.value) { + if (!filterItemsContainerRef.value) { return; } const observer = new ResizeObserver((entries) => { - handleVisibleChildren(); + const tempWidth = entries[0].contentRect.width; + handleVisibleChildren(tempWidth); }); - + // 同时监听父元素和子元素的变化 observer.observe(filterItemsContainerRef.value); observeInstance.value = observer; return observer; - } + } return { setupWidthObserver, - cancelWidthObserver + cancelWidthObserver, + handleVisibleChildren }; } diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/converter/filter-bar-config-property.converter.ts b/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/converter/filter-bar-config-property.converter.ts deleted file mode 100644 index 16342c4e0a76a1ab41007e2f2016baa6ae90e72d..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/converter/filter-bar-config-property.converter.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const queryEnumDefaultConverter = { - convertFrom: (schema: Record, propertyKey: string) => { - return schema[propertyKey]; - }, - convertTo: (schema: Record, propertyKey: string, propertyValue: any) => { - schema[propertyKey] = propertyValue; - } -}; - diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/filter-bar-config.component.tsx b/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/filter-bar-config.component.tsx deleted file mode 100644 index 7f7d60370cb7e7162cb7cc4fd12b90ae0e823b91..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/filter-bar-config.component.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { defineComponent, computed, ref, SetupContext } from 'vue'; -import { filterBarConfigProps, FilterBarConfigProps } from './filter-bar-config.props'; -import { usePanel, useTransfer, PropertyItem } from '@farris/ui-vue/components/query-solution'; -import { FButtonEdit } from '@farris/ui-vue/components/button-edit'; - -export default defineComponent({ - name: 'FFilterBarConfig', - props: filterBarConfigProps, - emits: ['update:modelValue'] as (string[] & ThisType) | undefined, - setup(props: FilterBarConfigProps, context: SetupContext) { - const activePanel = ref(null); - const textValue = computed(() => { - return '共' + props.modelValue.length + '项'; - }); - // fieldsData 为打开弹窗后所用的数据,包含所有字段,点击确定时针对穿梭框selectPanels进行赋值操作 - const fieldsData = new Map(); - const usePanelComposition = usePanel(props, context, activePanel, fieldsData); - const { renderPanel } = usePanelComposition; - const { renderTransfer, initData, confirm, cancel } = useTransfer(props, context, activePanel, fieldsData); - - function acceptCallback() { - confirm(); - } - - function rejectCallback() { - cancel(); - } - - const modalOptions = { - fitContent: false, - width: 900, - height: 600, - draggable: true, - title: '筛选方案配置', - acceptCallback, - rejectCallback - }; - - function renderFilterBarConfig() { - return <> - {renderTransfer()} - {renderPanel()} - ; - } - - return () => ( - - initData()} - > -
    - {renderFilterBarConfig()} -
    -
    - ); - } -}); diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/filter-bar-config.props.ts b/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/filter-bar-config.props.ts deleted file mode 100644 index e21ffdb0f8b588542bd969227a34c5e3237e51a4..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/filter-bar-config.props.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** -* Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -import { ExtractPropTypes, PropType } from 'vue'; - -import { FieldConfig } from '../../../../condition/src/types'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; -import filterBarConfigSchema from './schema/filter-bar-config.schema.json'; -import { schemaMapper } from './schema/schema-mapper'; -import { schemaResolver } from './schema/schema-resolver'; - - -export type Source = 'query-solution' | 'filter-bar' -export const filterBarConfigProps = { - fieldsConfig: { type: Array, default: [] }, - modelValue: { type: Array, default: [] }, - source: { type: String as PropType, default: 'filter-bar' }, - onFieldsChanged: { type: Function, default: null}, - formSchemaUtils: { type: Object, default: {}}, - metadataService: { type: Object, default: {} }, - designViewModelUtils: { type: Object, default: {} }, - eventsEditorUtils: { type: Object, default: {} }, - viewModelId: { type: String, default: '' }, - -} as Record; - -export type FilterBarConfigProps = ExtractPropTypes; - -export const configPropsResolver = createPropsResolver(filterBarConfigProps, filterBarConfigSchema, schemaMapper, schemaResolver); diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/property-config/filter-bar-config-extend.json b/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/property-config/filter-bar-config-extend.json deleted file mode 100644 index 4826045587c9634a86307b2f56742b024a7d418a..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/property-config/filter-bar-config-extend.json +++ /dev/null @@ -1,292 +0,0 @@ -{ - "input-group": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - } - }, - "number-spinner": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - }, - "precision": { - "description": "精度", - "title": "精度", - "type": "number" - }, - "max": { - "description": "最大值", - "title": "最大值", - "type": "number", - "editor": { - "type": "number-spinner", - "nullable": true - } - }, - "min": { - "description": "最小值", - "title": "最小值", - "type": "number", - "editor": { - "type": "number-spinner", - "nullable": true - } - } - }, - "number-range": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - }, - "precision": { - "description": "精度", - "title": "精度", - "type": "number" - }, - "max": { - "description": "最大值", - "title": "最大值", - "type": "number", - "editor": { - "type": "number-spinner", - "nullable": true - } - }, - "min": { - "description": "最小值", - "title": "最小值", - "type": "number", - "editor": { - "type": "number-spinner", - "nullable": true - } - } - }, - "combo-list": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - }, - "enumValueType": { - "description": "值类型", - "title": "值类型", - "type": "select", - "editor": { - "type": "combo-list", - "data": [ - { - "value": "int", - "name": "整型" - }, - { - "value": "string", - "name": "字符串" - }, - { - "value": "boolean", - "name": "布尔" - } - ], - "valueField":"value", - "textField":"name", - "idField":"value" - } - }, - "multiSelect": { - "description": "是否多选", - "title": "是否多选", - "type": "boolean" - } - }, - "lookup": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - }, - "helpId": { - "description": "帮助元数据ID", - "title": "帮助元数据ID", - "type": "string" - }, - "displayName": { - "description": "帮助元数据名称", - "title": "帮助元数据名称", - "type": "string", - "readonly": true - }, - "uri": { - "description": "uri", - "title": "uri", - "type": "string", - "readonly": true - }, - "textField": { - "description": "文本字段", - "title": "文本字段", - "type": "string" - }, - "valueField": { - "description": "值字段", - "title": "值字段", - "type": "string" - }, - "idField": { - "description": "标识字段", - "title": "标识字段", - "type": "string", - "readonly": true - }, - "displayType": { - "description": "类型: 树列表、列表、双列表、左树右列表", - "title": "展示类型", - "type": "string", - "editor": { - "type": "combo-list", - "editable": false, - "disabled": true, - "data": [ - { "text": "列表", "value": "List" }, - { "text": "树列表", "value": "TreeList" }, - { "text": "双列表", "value": "NavList" }, - { "text": "左树右列表", "value": "NavTreeList" } - ], - "textField": "text", - "idField": "value", - "valueField": "value" - } - }, - "multiSelect": { - "description": "是否多选", - "title": "是否多选", - "type": "boolean" - } - }, - "date-picker": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - } - }, - "datetime-picker": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - } - }, - - "radio-group": { - "class": { - "description": "样式", - "title": "样式", - "type": "string" - }, - "required": { - "description": "必填", - "title": "是否必填", - "type": "boolean" - }, - "isExtend": { - "description": "是否默认收起到右侧", - "title": "是否默认收起到右侧", - "type": "boolean" - }, - "direction": { - "description": "布局方向", - "title": "布局方向", - "type": "select", - "editor": { - "type": "combo-list", - "data": [ - { "text": "水平", "value": "horizontal" }, - { "text": "垂直", "value": "vertical" } - ], - "textField": "text", - "idField": "value", - "valueField": "value" - } - }, - "showLabel": { - "description": "是否显示标签", - "title": "是否显示标签", - "type": "boolean" - } - } -} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/property-config/filter-bar-config.property-config.json b/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/property-config/filter-bar-config.property-config.json deleted file mode 100644 index c061f8f1a992bfe7d58818c0a79e0be589f417af..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/property-config/filter-bar-config.property-config.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "title": "query-solution-config", - "description": "A Farris Component", - "type": "object", - "categories": { - "basic": { - "description": "Basic Infomation", - "title": "基本信息", - "properties": { - "id": { - "description": "标识", - "title": "标识", - "type": "string", - "readonly": true - }, - "code": { - "description": "编号", - "title": "控件编号", - "type": "string", - "readonly": true - }, - "labelCode": { - "description": "标签", - "title": "标签", - "type": "string", - "readonly": true - }, - "name": { - "description": "控件名称", - "title": "控件名称", - "type": "string" - }, - "controlType": { - "description": "控件类型", - "title": "控件类型", - "type": "select", - "editor": { - "type": "combo-list", - "data": [ - { - "value": "input-group", - "name": "文本" - }, - { - "value": "number-spinner", - "name": "数值" - }, - { - "value": "number-range", - "name": "数值区间" - }, - { - "value": "combo-list", - "name": "下拉列表" - }, - { - "value": "lookup", - "name": "弹出帮助" - }, - { - "value": "combo-lookup", - "name": "下拉帮助" - }, - { - "value": "date-range", - "name": "日期区间" - }, - { - "value": "date-picker", - "name": "日期" - }, - { - "value": "radio-group", - "name": "单选组" - } - ], - "valueField":"value", - "textField":"name", - "idField":"value" - } - } - } - }, - "control": { - "description": "", - "title": "控件", - "properties": { - } - } - } -} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/filter-bar-config.schema.json b/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/filter-bar-config.schema.json deleted file mode 100644 index 96f36d7cbcf57fcc648f9f75bd4f6b4bb5517dcd..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/filter-bar-config.schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://farris-design.gitee.io/query-solution-config.schema.json", - "title": "filter-bar-config", - "description": "A Farris Container Component", - "properties": { - "fieldsConfig": { - "description": "所有字段(树结构)", - "type": "array" - }, - "designViewModelUtils": { - "description": "", - "type": "object" - }, - "formSchemaUtils": { - "description": "", - "type": "object" - }, - "metadataService": { - "description": "", - "type": "object" - }, - "viewModelId": { - "description": "", - "type": "array" - }, - "eventsEditorUtils": { - "description": "", - "type": "object" - } - - } -} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar.design.component.tsx b/packages/ui-vue/components/filter-bar/src/designer/filter-bar.design.component.tsx index 667c057b3380ae910461765b925ce832f87d02b2..5344edbd83763288e09c50ac79c5d0476acdfc3e 100644 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar.design.component.tsx +++ b/packages/ui-vue/components/filter-bar/src/designer/filter-bar.design.component.tsx @@ -1,46 +1,59 @@ -import { SetupContext, computed, defineComponent, inject, onMounted, ref, watch } from 'vue'; -import { FilterBarProps, filterBarProps } from '../filter-bar.props'; +import { SetupContext, computed, defineComponent, inject, onMounted, onUnmounted, provide, ref, watch } from 'vue'; import { FilterItem } from '../types'; -import { useFilterItems } from '../composition/use-filter-items'; -import getEllipsisButtonRender from '../components/ellipsis-button.component'; -import getToolbarRender from '../components/toolbar.component'; -import FFilterBarItem from '../components/filter-item/filter-item.component'; +import { useFilterItemsDesign } from './use-filter-items.design'; +import getToolbarRender from './toolbar.design.component'; +import FFilterBarItemDesign from './filter-item.design.component'; import { DesignerItemContext } from '@farris/ui-vue/components/designer-canvas'; import { useDesignerComponent } from '@farris/ui-vue/components/designer-canvas'; -import { useFilterBarDesignerRules } from './filter-bar-config/composition/use-filterbar-rules'; +import { useFilterBarDesignerRules } from './use-filterbar-rules'; +import { FilterBarPropsDesign, filterBarPropsDesign } from './filter-bar.props'; +import { useSize } from '../composition/use-size'; +import { useTypeResolver } from '@farris/ui-vue/components/dynamic-form'; +import { getCustomStyle } from '@farris/ui-vue/components/common'; + export default defineComponent({ name: 'FFilterBarDesign', - props: filterBarProps, + props: filterBarPropsDesign, emits: ['Clear', 'Remove', 'Reset'], - setup(props: FilterBarProps, context) { + setup(props: FilterBarPropsDesign, context) { const enableExpand = ref(false); const expanded = ref(false); - const shownEllipsis = ref(false); const shownCollapseButton = ref(false); - const useFilterItemsComposition = useFilterItems(props, context as SetupContext); + const filterItemsContainerRef = ref(null); + const useFilterItemsComposition = useFilterItemsDesign(props, context as SetupContext); const { filterFields, filterItems, loadFilterItems } = useFilterItemsComposition; - + const refreshFilterBarFlag = ref(0); const elementRef = ref(); const designItemContext = inject('design-item-context') as DesignerItemContext; const designerHostService = inject('designer-host-service'); const designerRulesComposition = useFilterBarDesignerRules(designItemContext, designerHostService); const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + const useSizeComposition = useSize(filterItemsContainerRef, shownCollapseButton); + const { cancelWidthObserver, setupWidthObserver, handleVisibleChildren } = useSizeComposition; + const { resolveEditorProps } = useTypeResolver(); + provide('useFilterItemsComposition', {useFilterItemsComposition,resolveEditorProps}); const filterWrapperStyle = computed(() => { const styleObject = { display: filterFields.value && filterFields.value.length ? '' : 'none' } as Record; - return styleObject; + return getCustomStyle(styleObject,props?.customStyle); }); onMounted(() => { elementRef.value.componentInstance = componentInstance; + setupWidthObserver(); + }); + onUnmounted(()=>{ + cancelWidthObserver(); }); - context.expose(componentInstance.value); - watch([() => props.data, () => props.fields], ([latestData, latestFields]) => { - loadFilterItems(latestFields, latestData); + watch(() => props.fields, (latestFields) => { + loadFilterItems(latestFields, []); + refreshFilterBarFlag.value++; + }, { + immediate: true }); const shouldShowExpandedArea = computed(() => expanded.value && enableExpand.value); @@ -52,33 +65,43 @@ export default defineComponent({ return classObject; }); - const shouldShowFilterItems = computed(() => filterItems.value.length > 0); function renderFilterItems() { return ( -
    +
    {filterItems.value.map((data: FilterItem, index: number) => { - return ; + return ; })}
    ); } - const shouldShowEllipsis = computed(() => shownEllipsis.value && !expanded.value && shownCollapseButton.value); - const renderEllipsisButton = getEllipsisButtonRender(props, context as SetupContext); + /** + * 显示省略号 + * @returns + */ + function renderEllipsisButton() { + return shownCollapseButton.value &&
    ...
    + } - const shouldShowToolbar = computed(() => !expanded.value); const renderToolbar = getToolbarRender(props, context as SetupContext, useFilterItemsComposition); return () => { return ( -
    +
    -
    - {shouldShowFilterItems.value && renderFilterItems()} - {shouldShowEllipsis.value && renderEllipsisButton()} - {shouldShowToolbar.value && renderToolbar()} +
    + {renderFilterItems()}
    + {renderEllipsisButton()} + {renderToolbar()} +
    + {shownCollapseButton.value &&
    +
    + }
    ); diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar.props.ts b/packages/ui-vue/components/filter-bar/src/designer/filter-bar.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d4c371c2f8b37af8e0880697585e3aafa8af75d --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/designer/filter-bar.props.ts @@ -0,0 +1,15 @@ + +import { ExtractPropTypes} from "vue"; +import { createPropsResolver } from "../../../dynamic-resolver/src/props-resolver"; +import { schemaMapper } from '../schema/schema-mapper'; +import { schemaResolver } from '../schema/schema-resolver'; +import filterBarSchema from '../schema/filter-bar.schema.json'; +import { filterBarProps } from "../filter-bar.props"; + +export const filterBarPropsDesign = Object.assign({}, filterBarProps, { + componentId: { type: String, default: '' } +}); + +export type FilterBarPropsDesign = ExtractPropTypes; + +export const designPropsResolver = createPropsResolver(filterBarPropsDesign, filterBarSchema, schemaMapper, schemaResolver); diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-item.design.component.tsx b/packages/ui-vue/components/filter-bar/src/designer/filter-item.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..962befefe32d9630e1cf83df296a27dc4dbcbe03 --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/designer/filter-item.design.component.tsx @@ -0,0 +1,53 @@ +import { computed, ref, defineComponent,inject} from 'vue'; +import { FilterItem } from '../types'; +import { FilterItemProps, filterItemProps } from '../components/filter-item/filter-item.props'; +import { FilterItemUtils, UseFilterItems } from '../composition/types'; + +export default defineComponent({ + name: 'FFilterBarItemDesign', + props: filterItemProps, + emits: ['confirm'], + setup(props: FilterItemProps, context) { + const filterItem = ref(props.filterItem); + const {useFilterItemsComposition} = inject('useFilterItemsComposition') as FilterItemUtils; + const { currentFilterId, shouldShowClearButtonInFilterItem } =useFilterItemsComposition; + const disabled = ref(props.disabled); + + const filterItemClass = function () { + const classObject = { + 'f-filter-item': true, + 'f-filter-item-actived': !filterItem.value.value.isEmpty(), + // 'f-filter-item-last': index === filterItems.value.length - 1, + 'f-filter-item-edit': filterItem.value.id === currentFilterId.value + } as Record; + return classObject; + }; + + return () => { + return ( +
    +
    + {filterItem.value.required && *} + {filterItem.value.fieldName} + { + filterItem.value.value.getDisplayText() && <> + : + {filterItem.value.value.getDisplayText()} + + } + {!disabled.value && } +
    + {shouldShowClearButtonInFilterItem(filterItem.value) && ( + + + + )} +
    + ); + }; + } +}); diff --git a/packages/ui-vue/components/filter-bar/src/designer/toolbar.design.component.tsx b/packages/ui-vue/components/filter-bar/src/designer/toolbar.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5dde1be4129f63df19b4d04e4869a9ab1be2cd47 --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/designer/toolbar.design.component.tsx @@ -0,0 +1,29 @@ +import { SetupContext, computed, ref, watch } from 'vue'; +import { FilterBarProps } from '../filter-bar.props'; +import { UseFilterItemsDesign } from '../composition/types'; +import { FilterItem } from '../types'; + +export default function (props: FilterBarProps, context, useFilterItemsComposition: UseFilterItemsDesign) { + const collapsedFilterList = ref([]); + const advancedFilterText = ref('更多筛选'); + const { filterItems } = useFilterItemsComposition; + + function initToolbar() { + collapsedFilterList.value = filterItems.value.filter(filterItem => filterItem.isExtend); + } + initToolbar(); + const shouldShowAdvancedFilter = computed(() => { + return collapsedFilterList.value && collapsedFilterList.value.length > 0; + }); + + /** + * 清空已选、 + */ + return () => { + return ( +
    + {shouldShowAdvancedFilter.value && } +
    + ); + }; +} diff --git a/packages/ui-vue/components/filter-bar/src/designer/use-filter-items.design.ts b/packages/ui-vue/components/filter-bar/src/designer/use-filter-items.design.ts new file mode 100644 index 0000000000000000000000000000000000000000..10e80f433ec2a28e86fe8b2fb55e951a090153d0 --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/designer/use-filter-items.design.ts @@ -0,0 +1,49 @@ +import { SetupContext, ref } from 'vue'; +import { FilterBarProps } from '../filter-bar.props'; +import { Condition, FieldConfig, FilterItem } from '../types'; +import { UseFilterItemsDesign } from '../composition/types'; +import { useConditionUtils } from '@farris/ui-vue/components/condition'; +import { useSolutionUtils } from '@farris/ui-vue/components/query-solution'; +import { cloneDeep } from 'lodash-es'; + + +export function useFilterItemsDesign(props: FilterBarProps, context): UseFilterItemsDesign { + const filterConditions = ref([]); + const filterFields = ref([]); + const filterItems = ref([]); + const currentFilterId = ref(''); + const disabled = ref(false); + const mode = ref(props.mode); + const fieldMap = new Map(); + const { convertToControls } = useConditionUtils('filter-bar'); + const { fieldToCondition } = useSolutionUtils(); + function shouldShowClearButtonInFilterItem(filterItem: FilterItem) { + return !filterItem.value.isEmpty() ; + } + function loadFieldConfigs(fields: FieldConfig[]) { + filterFields.value = cloneDeep(fields); + filterFields.value.reduce((result: Map, field: FieldConfig) => { + result.set(field.labelCode, field); + return result; + }, fieldMap); + } + + function loadConditions(conditions: Condition[]) { + filterItems.value = filterFields.value.map((targetField: FieldConfig) => { + return fieldToCondition(targetField) as any; + }); + } + + function loadFilterItems(fields: FieldConfig[], conditions: Condition[]) { + loadFieldConfigs(fields); + loadConditions(conditions); + // filterItems.value = buildFilterItems(); + } + + loadFilterItems(props.fields, []); + + return { + currentFilterId, filterFields, filterItems, fieldMap, + loadFilterItems, shouldShowClearButtonInFilterItem, + }; +} diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/composition/use-filterbar-rules.ts b/packages/ui-vue/components/filter-bar/src/designer/use-filterbar-rules.ts similarity index 39% rename from packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/composition/use-filterbar-rules.ts rename to packages/ui-vue/components/filter-bar/src/designer/use-filterbar-rules.ts index a9a8ddc3a232d2378316585c510282c334f024d1..21d908604ecd1f1e6174d7c1d487bdb0d16b2128 100644 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/composition/use-filterbar-rules.ts +++ b/packages/ui-vue/components/filter-bar/src/designer/use-filterbar-rules.ts @@ -1,6 +1,6 @@ -import { UseDesignerRules } from "@farris/ui-vue/components/designer-canvas/"; -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "@farris/ui-vue/components/designer-canvas"; -import { FilterBarPropertyConfig } from "../../../property-config/filter-bar.property-config";; +import { DgControl, UseDesignerRules } from "@farris/ui-vue/components/designer-canvas/"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "../../../designer-canvas/src/types"; +import { FilterBarPropertyConfig } from "../property-config/filter-bar.property-config";; export function useFilterBarDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -21,36 +21,38 @@ export function useFilterBarDesignerRules(designItemContext: DesignerItemContext return false; } /** - * 组件删除后事件 + * 配置筛选条中各个筛选项的路径信息,用于事件交互面板显示“已有方法”的事件路径 */ - function onRemoveComponent() { - // 1、移除根节点对应的样式 - if (designItemContext?.parent?.parent?.schema) { - const sectionParentSchema = designItemContext?.parent?.parent.schema; - if (sectionParentSchema.appearance?.class && sectionParentSchema.appearance?.class.includes('f-page-has-scheme')) { - sectionParentSchema.appearance.class = sectionParentSchema.appearance?.class.replace('f-page-has-scheme', '').replace(' ', ''); - } - } + function setComponentBasicInfoMap() { + if (designItemContext && designerHostService) { + const { formSchemaUtils } = designerHostService; + const controlBasicInfoMap = formSchemaUtils.getControlBasicInfoMap(); + const { schema } = designItemContext; + // 配置筛选条的路径 + const barName = DgControl['filter-bar'].name; + controlBasicInfoMap.set(schema.id, { + componentTitle: barName, + parentPathName: barName + }); - // 2、移除筛选方案相关的变量 - const { formSchemaUtils } = designerHostService; - const targetComponentId = designItemContext.componentInstance.value.belongedComponentId; - const viewModelId = formSchemaUtils.getViewModelIdByComponentId(targetComponentId); - const viewModel = formSchemaUtils.getViewModelById(viewModelId); - if (viewModel && viewModel.states) { - const filterVariableIndex = viewModel.states.findIndex(state => state.code === 'originalFilterConditionList'); - if (filterVariableIndex > -1) { - viewModel.states.splice(filterVariableIndex, 1); + // 配置筛选项的路径 + if (schema.fields?.length) { + schema.fields.map(filterItem => { + controlBasicInfoMap.set(filterItem.id, { + componentTitle: filterItem.name, + parentPathName: `${barName} > ${filterItem.name}` + }); + }); } } - } + } return { getPropsConfig, canAccepts, checkCanDeleteComponent, checkCanMoveComponent, - onRemoveComponent + setComponentBasicInfoMap } as UseDesignerRules; } diff --git a/packages/ui-vue/components/filter-bar/src/filter-bar.component.tsx b/packages/ui-vue/components/filter-bar/src/filter-bar.component.tsx index 1f543b38a3baa84d8bb0dc117d119eebacb49e9c..64d8373045531bcaa840977b4dfd7a9853ca3b4d 100644 --- a/packages/ui-vue/components/filter-bar/src/filter-bar.component.tsx +++ b/packages/ui-vue/components/filter-bar/src/filter-bar.component.tsx @@ -1,119 +1,143 @@ -import { SetupContext, computed, defineComponent, ref, watch, onMounted } from 'vue'; +import { computed, defineComponent, ref, watch, onMounted, provide, nextTick, onUnmounted } from 'vue'; import { FilterBarProps, filterBarProps } from './filter-bar.props'; import { FilterItem } from './types'; import { useFilterItems } from './composition/use-filter-items'; -import getEllipsisButtonRender from './components/ellipsis-button.component'; import getToolbarRender from './components/toolbar.component'; import FFilterBarItem from './components/filter-item/filter-item.component'; import { useSize } from './composition/use-size'; +import { useFilterBarLocale } from './locale/locale'; +import { useTypeResolver } from '@farris/ui-vue/components/dynamic-form'; +import { getCustomStyle } from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FFilterBar', props: filterBarProps, - emits: ['clear', 'remove', 'reset', 'conditionChange', 'change'], + emits: ['clear', 'remove', 'reset', 'conditionChange', 'change', 'query'], setup(props: FilterBarProps, context) { - const enableExpand = ref(true); const expanded = ref(false); - const shownEllipsis = ref(false); const shownCollapseButton = ref(false); - const filterItemsRef = ref(null); const filterItemsContainerRef = ref(null); - const useFilterItemsComposition = useFilterItems(props, context); - const useSizeComposition = useSize(filterItemsContainerRef, filterItemsRef, shownCollapseButton); - const { cancelWidthObserver, setupWidthObserver } = useSizeComposition; + // 是否显示清空已选按钮 + const shownResetButton = ref(false); + const refreshFilterBarFlag = ref(0); + const useSizeComposition = useSize(filterItemsContainerRef, shownCollapseButton); + const { cancelWidthObserver, setupWidthObserver, handleVisibleChildren } = useSizeComposition; + const filterUtils = { + resizeFilerBar: () => { + if (!expanded.value) { + // + nextTick(() => { + handleVisibleChildren(-1, 0); + }); + } + } + } + const useFilterItemsComposition = useFilterItems(props, context, shownResetButton, filterUtils); const { filterFields, filterItems, loadFilterItems, handleQuery } = useFilterItemsComposition; - + const { resolveEditorProps } = useTypeResolver(); + provide('useFilterItemsComposition', { useFilterItemsComposition, resolveEditorProps }); + const filterBarLocale = useFilterBarLocale(); const filterWrapperStyle = computed(() => { const styleObject = { display: filterFields.value && filterFields.value.length ? '' : 'none' } as Record; - return styleObject; + return getCustomStyle(styleObject, props?.customStyle); }); + // 初始 + loadFilterItems(props.fields, props.defaultValues, props.defaultValues && props.defaultValues.length > 0 ? true : false); - watch([() => props.data, () => props.fields], ([latestData, latestFields]) => { + watch([() => props.defaultValues, () => props.fields], ([latestData, latestFields]) => { loadFilterItems(latestFields, latestData); + refreshFilterBarFlag.value++; + filterUtils.resizeFilerBar(); }); onMounted(() => { setupWidthObserver(); }); - - const shouldShowExpandedArea = computed(() => expanded.value && enableExpand.value); + onUnmounted(()=>{ + cancelWidthObserver(); + }); const filterListClass = computed(() => { const classObject = { 'f-filter-list': true, - - 'f-filter-list-extend': shouldShowExpandedArea.value, + 'f-filter-list-extend': expanded.value, // 'f-utils-overflow-hidden': true } as Record; return classObject; }); const expandClass = computed(() => { const classObject = { - '"extend-btn-arrow': true, + 'extend-btn-arrow': true, 'f-icon': true, 'f-icon-arrow-chevron-up': expanded.value, 'f-icon-arrow-chevron-down': !expanded.value, } as Record; return classObject; }); - - const shouldShowFilterItems = computed(() => filterItems.value.length > 0); - const shouldShowEllipsis = computed(() => shownEllipsis.value && !expanded.value && shownCollapseButton.value); - const renderEllipsisButton = getEllipsisButtonRender(props, context); - + const shouldShowEllipsis = computed(() => !expanded.value && shownCollapseButton.value); + /** + * 更改展开收起状态 + * 只有收起的是时候,才会监听宽度计算 + */ + function changeExpandState() { + expanded.value = !expanded.value; + if (expanded.value) { + cancelWidthObserver(); + } else { + setupWidthObserver(); + } + } // const shouldShowToolbar = computed(() => !expanded.value); - const renderToolbar = getToolbarRender(props, context, useFilterItemsComposition); + const renderToolbar = getToolbarRender(props, context, useFilterItemsComposition, shownResetButton); + function conditionChange(changeItem) { context.emit('change', changeItem); - const queryFilters = handleQuery(); - context.emit('conditionChange', queryFilters); + handleQuery(); + filterUtils.resizeFilerBar(); } function renderFilterItems() { return ( -
    +
    {filterItems.value.map((data: FilterItem, index: number) => { - return !data.editor.isExtend && + return !data.isExtend && ; })} - { expanded.value && renderToolbar() } + {expanded.value&&renderToolbar()}
    ); } - - - function handleExpand() { - expanded.value = !expanded.value; - if(expanded.value) { - cancelWidthObserver(); - } else { - setupWidthObserver(); - } + /** + * 显示省略号 + * @returns + */ + function renderEllipsisButton() { + return shouldShowEllipsis.value &&
    changeExpandState()}> + ... +
    } return () => { return ( -
    +
    -
    -
    - {shouldShowFilterItems.value && renderFilterItems()} +
    +
    + {renderFilterItems()}
    - {/* {shouldShowEllipsis.value && renderEllipsisButton()} */} - { !expanded.value && renderToolbar() } + {renderEllipsisButton()} + {!expanded.value && renderToolbar()}
    - { - shownCollapseButton.value &&
    -
    } -
    ); diff --git a/packages/ui-vue/components/filter-bar/src/filter-bar.props.ts b/packages/ui-vue/components/filter-bar/src/filter-bar.props.ts index c58355219b0ccc46cf75b7c1ed1a0eda72dc8b5f..12658317334e5f08a806033daf19f175a4dbd2d7 100644 --- a/packages/ui-vue/components/filter-bar/src/filter-bar.props.ts +++ b/packages/ui-vue/components/filter-bar/src/filter-bar.props.ts @@ -10,23 +10,22 @@ export type FilterMode = 'editable' | 'display-only'; export const filterBarProps = { - /** 被绑定数据 */ - data: { + /** 指定已设的筛选条件列表 */ + defaultValues: { type: Array, default: [] }, - /** 筛选分类 */ + /** 指定参与筛选的字段列表 */ fields: {type: Array, default: [] }, /** 是否可选择 */ mode: { type: String as PropType, default: 'editable' }, /** 是否可见 */ visible: { type: Boolean, default: true }, /** 是否禁用 */ - disable: { type: Boolean, default: false }, + disabled: { type: Boolean, default: false }, /** 清空值文字 */ - resetText: { type: String, default: '清空' }, - /** 是否显示重置 */ - showReset: { type: Boolean, default: false }, - + resetText: { type: String, default: '清空筛选' }, + customClass: { type: String, default: '' }, + customStyle: { type: String, default: '' } } as Record; export type FilterBarProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/filter-bar/src/locale/locale.ts b/packages/ui-vue/components/filter-bar/src/locale/locale.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b8d30c9e2aa9f0e390cb7f06fca186e310948c8 --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/locale/locale.ts @@ -0,0 +1,20 @@ + +import { LocaleService } from '@farris/ui-vue/components/locale'; + +export function useFilterBarLocale() { + const { getLocaleValue: t } = LocaleService; + const filterBarLocale = { + confirm: t('filterBar.confirm'), + cancel: t('filterBar.cancel'), + advancedFilter: t('filterBar.advancedFilter'), + clearAll: t('filterBar.clearAll'), + expand: t('filterBar.expand'), + fold: t('filterBar.fold'), + require:t('filterBar.require') + }; + return filterBarLocale; +} +/** 筛选文字 */ +export function getLocaleResetText(filterText) { + return LocaleService.getRealPropertyValue(filterText, '清空筛选', 'filterBar.reset'); +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/locales/designer/en.json b/packages/ui-vue/components/filter-bar/src/locales/designer/en.json new file mode 100644 index 0000000000000000000000000000000000000000..d6c454165d1dcfcef6154d08a37caa3dab5b4d1d --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/locales/designer/en.json @@ -0,0 +1,3 @@ +{ + "filterBar": {} +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/locales/designer/zh-CHS.json b/packages/ui-vue/components/filter-bar/src/locales/designer/zh-CHS.json new file mode 100644 index 0000000000000000000000000000000000000000..d6c454165d1dcfcef6154d08a37caa3dab5b4d1d --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/locales/designer/zh-CHS.json @@ -0,0 +1,3 @@ +{ + "filterBar": {} +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/locales/designer/zh-CHT.json b/packages/ui-vue/components/filter-bar/src/locales/designer/zh-CHT.json new file mode 100644 index 0000000000000000000000000000000000000000..d6c454165d1dcfcef6154d08a37caa3dab5b4d1d --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/locales/designer/zh-CHT.json @@ -0,0 +1,3 @@ +{ + "filterBar": {} +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/locales/ui/en.json b/packages/ui-vue/components/filter-bar/src/locales/ui/en.json new file mode 100644 index 0000000000000000000000000000000000000000..e25ea696099f00249c7772b50526d536a05e858e --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/locales/ui/en.json @@ -0,0 +1,12 @@ +{ + "filterBar": { + "confirm": "OK", + "cancel": "Cancel", + "advancedFilter": "More Conditions", + "clearAll": "Clear", + "reset": "Clear", + "expand": "Expand", + "fold": "Fold", + "require": "Please fill in [fields] before filtering" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/locales/ui/zh-CHS.json b/packages/ui-vue/components/filter-bar/src/locales/ui/zh-CHS.json new file mode 100644 index 0000000000000000000000000000000000000000..c592af19525baac8c6d24346ddd989616aa588c3 --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/locales/ui/zh-CHS.json @@ -0,0 +1,12 @@ +{ + "filterBar": { + "confirm": "确定", + "cancel": "取消", + "advancedFilter": "更多筛选", + "clearAll": "清空筛选", + "reset": "清空筛选", + "expand": "展开", + "fold": "收起", + "require": "请填写[fields]再进行筛选" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/locales/ui/zh-CHT.json b/packages/ui-vue/components/filter-bar/src/locales/ui/zh-CHT.json new file mode 100644 index 0000000000000000000000000000000000000000..01cd68fa8f2b2d720b14ecc3b4542183d6e00584 --- /dev/null +++ b/packages/ui-vue/components/filter-bar/src/locales/ui/zh-CHT.json @@ -0,0 +1,12 @@ +{ + "filterBar": { + "confirm": "確定", + "cancel": "取消", + "advancedFilter": "更多篩選", + "clearAll": "清空篩選", + "reset": "清空篩選", + "expand": "展開", + "fold": "收起", + "require": "請填寫[fields]再進行篩選" + } +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/property-config/filter-bar.property-config.ts b/packages/ui-vue/components/filter-bar/src/property-config/filter-bar.property-config.ts index a1ed9fb2e0837b6cb35c56d0649ab894e496e3af..6927ee3151b5e2cb53bb153bb221b693edb95406 100644 --- a/packages/ui-vue/components/filter-bar/src/property-config/filter-bar.property-config.ts +++ b/packages/ui-vue/components/filter-bar/src/property-config/filter-bar.property-config.ts @@ -5,7 +5,7 @@ import { DesignerComponentInstance } from "@farris/ui-vue/components/designer-ca export class FilterBarPropertyConfig extends InputBaseProperty { getPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance) { this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); - this.getAppearanceConfig(); + this.propertyConfig.categories['appearance']=this.getAppearanceConfig(); this.getBehaviorConfig(propertyData); this.getFilterBarConfig(propertyData); this.getEventPropConfig(propertyData); @@ -17,12 +17,12 @@ export class FilterBarPropertyConfig extends InputBaseProperty { description: "Basic Infomation", title: "行为", properties: { - showExtendSiderbar: { - description: "是否在侧边栏中展示收起的控件", - type: "boolean", - title: "启用侧边栏收折", - refreshPanelAfterChanged: true - }, + // showExtendSiderbar: { + // description: "是否在侧边栏中展示收起的控件", + // type: "boolean", + // title: "启用侧边栏收折", + // refreshPanelAfterChanged: true + // }, resetText: { description: "选择筛选条件后,按钮“清空筛选”的文本", type: "string", @@ -35,6 +35,8 @@ export class FilterBarPropertyConfig extends InputBaseProperty { private getFilterBarConfig(propertyData: any) { const allFields = this.designViewModelUtils.getAllFields2TreeByVMId(this.viewModelId); const self = this; + const visibleEditor = this.getPropertyEditorParams(propertyData, [], 'visible'); + const disableEditor = this.getPropertyEditorParams(propertyData, [], 'disable'); this.propertyConfig.categories['control'] = { description: "Basic Infomation", title: "编辑器", @@ -43,37 +45,50 @@ export class FilterBarPropertyConfig extends InputBaseProperty { description: "筛选条字段设置", title: "筛选条字段", type: "", - refreshPanelAfterChanged: true, editor: { - type: "filter-bar-config", + type: "query-solution-config", + source:"filter-bar", + title:"筛选条字段", fieldsConfig: allFields, formSchemaUtils: this.formSchemaUtils, metadataService: this.metadataService, viewModelId: this.viewModelId, designViewModelUtils: this.designViewModelUtils, - eventsEditorUtils: this.eventsEditorUtils - }, - }, - visible: { - description: "运行时组件是否可见", - type: "boolean", - title: "是否可见", - refreshPanelAfterChanged: true - }, - disable: { - description: "是否禁用", - type: "boolean", - title: "是否禁用" + eventsEditorUtils: this.eventsEditorUtils, + schemaService:this.schemaService + } }, + // visible: { + // description: "运行时组件是否可见", + // title: "是否可见", + // editor: visibleEditor + // }, + // disable: { + // description: "是否禁用", + // title: "是否禁用", + // editor: disableEditor + // }, defaultValues: { description: "默认值绑定字段", type: "object", title: "默认值绑定字段", - editor: this.getPropertyEditorParams(propertyData, ['Variable']) + editor: this.getPropertyEditorParams(propertyData, ['Variable'], '', {}, { newVariableType: 'Object' }) } }, setPropertyRelates(changeObject, prop) { - self.addNewVariableToViewModel(changeObject, self.viewModelId); + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'visible': + case 'disable': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + case 'defaultValues': + self.addNewVariableToViewModel(changeObject, self.viewModelId); + break; + } + } }; } @@ -88,7 +103,7 @@ export class FilterBarPropertyConfig extends InputBaseProperty { const self = this; const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); const properties = self.createBaseEventProperty(initialData); - + this.propertyConfig.categories['eventsEditor'] = { title: '事件', hideTitle: true, diff --git a/packages/ui-vue/components/filter-bar/src/schema/filter-bar.schema.json b/packages/ui-vue/components/filter-bar/src/schema/filter-bar.schema.json index 7009b7d632c10a0dc507e0f9cfe5f8758bc6f174..8042a1b67459ffffbaa70062c8b08ed1ad64f93a 100644 --- a/packages/ui-vue/components/filter-bar/src/schema/filter-bar.schema.json +++ b/packages/ui-vue/components/filter-bar/src/schema/filter-bar.schema.json @@ -31,8 +31,8 @@ "description": "", "type": "array" }, - "disable": { - "type": "string", + "disabled": { + "type": "boolean", "default": false }, "visible": { @@ -43,10 +43,17 @@ "fields": { "description": "", "type": "array" - } + }, + "resetText": { + "description": "", + "type": "string" + } }, "required": [ "id", "type" - ] + ], + "events":{ + "onQuery":"查询事件" + } } \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/types.ts b/packages/ui-vue/components/filter-bar/src/types.ts index 4a86f6ea6930ed146aa7fa3052b768211a0c8e21..4dccc7a18387fa8cb10518f9155b7740b96039ea 100644 --- a/packages/ui-vue/components/filter-bar/src/types.ts +++ b/packages/ui-vue/components/filter-bar/src/types.ts @@ -7,5 +7,8 @@ export type FilterItem = { id: string; fieldCode: string; fieldName: string; + required?: boolean; + disabled?: boolean; + isExtend?: boolean; editor: EditorConfig; } & Condition; diff --git a/packages/ui-vue/components/html-template/index.ts b/packages/ui-vue/components/html-template/index.ts index 547a0b5247096b4e0849b0325b8158f9106cca40..68dc4c5669bc039cc59d42b90a954e15270ac55d 100644 --- a/packages/ui-vue/components/html-template/index.ts +++ b/packages/ui-vue/components/html-template/index.ts @@ -14,8 +14,8 @@ * limitations under the License. */ import FHtmlTemplate from './src/html-template.component'; -import { propsResolver, designPropsResolver } from './src/html-template.props'; -import { withInstall } from '@farris/ui-vue/components/common'; +import { propsResolver, designPropsResolver, propsResolverGenerator, designPropsResolverGenerator } from './src/html-template.props'; +import { RegisterContext, withInstall } from '@farris/ui-vue/components/common'; import FHtmlTemplateDesign from './src/designer/html-template.design.component'; import './src/html-template.scss'; @@ -26,18 +26,20 @@ FHtmlTemplate.register = ( propsResolverMap: Record, configResolverMap: Record, resolverMap: Record, + registerContext: RegisterContext ) => { componentMap['html-template'] = FHtmlTemplate; - propsResolverMap['html-template'] = propsResolver; + propsResolverMap['html-template'] = propsResolverGenerator(registerContext); }; FHtmlTemplate.registerDesigner = ( componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext ) => { componentMap['html-template'] = FHtmlTemplateDesign; - propsResolverMap['html-template'] = designPropsResolver; + propsResolverMap['html-template'] = designPropsResolverGenerator(registerContext); }; export { FHtmlTemplate }; diff --git a/packages/ui-vue/components/html-template/src/designer/use-designer-rules.ts b/packages/ui-vue/components/html-template/src/designer/use-designer-rules.ts index 36247467e02de360de8cb0258c43ae0b8c393844..aa7a509c3c24814e18fc4bfacc5c31fb616dbf60 100644 --- a/packages/ui-vue/components/html-template/src/designer/use-designer-rules.ts +++ b/packages/ui-vue/components/html-template/src/designer/use-designer-rules.ts @@ -23,12 +23,15 @@ export function useDesignerRules(schema: ComponentSchema, designerHostService: a const htmlTemplateProperty = new HtmlTemplateProperty(componentId, designerHostService); return htmlTemplateProperty.getPropertyConfig(schema); } - + function getDesignerClass() { + return '';// fv-html-template-design + } return { canAccepts, hideNestedPaddingInDesginerView, getPropsConfig, checkCanDeleteComponent, - checkCanMoveComponent + checkCanMoveComponent, + getDesignerClass }; } diff --git a/packages/ui-vue/components/html-template/src/html-template.props.ts b/packages/ui-vue/components/html-template/src/html-template.props.ts index 10e199333da8a6c939fb56c028852195398cbc11..37cbc9c3c2c9b6604de1a8fe3ca094900ff02c8b 100644 --- a/packages/ui-vue/components/html-template/src/html-template.props.ts +++ b/packages/ui-vue/components/html-template/src/html-template.props.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { ExtractPropTypes, PropType, RenderFunction } from 'vue'; -import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { createPropsResolver, getPropsResolverGenerator } from "@farris/ui-vue/components/dynamic-resolver"; import htmlTemplateSchema from './schema/html-template.schema.json'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; @@ -38,3 +38,16 @@ export const htmlTemplateDesignProps = { export type HtmlTemplateDesignProps = ExtractPropTypes; export const designPropsResolver = createPropsResolver(htmlTemplateDesignProps, htmlTemplateSchema, schemaMapper, schemaResolver); + +export const propsResolverGenerator = getPropsResolverGenerator( + htmlTemplateProps, + htmlTemplateSchema, + schemaMapper, + schemaResolver +); +export const designPropsResolverGenerator = getPropsResolverGenerator( + htmlTemplateDesignProps, + htmlTemplateSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/discussion-list/index.ts b/packages/ui-vue/components/image/index.ts similarity index 43% rename from packages/ui-vue/components/discussion-list/index.ts rename to packages/ui-vue/components/image/index.ts index 4b75135d76faab05bdf91a3c1940c027a2e7996b..9fe480907b8d55050b2c73dfec8acd112332db75 100644 --- a/packages/ui-vue/components/discussion-list/index.ts +++ b/packages/ui-vue/components/image/index.ts @@ -1,3 +1,5 @@ + + /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -13,15 +15,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { App } from 'vue'; -import DiscussionListProps from './discussion-list.component'; +import FImage from './src/image.component'; +import { propsResolver } from './src/image.props'; +import FImageDesign from './src/designer/image.design.component'; +import { withInstall } from '@farris/ui-vue/components/common'; -export * from './discussion-list.props'; +export * from './src/image.props'; -export { DiscussionListProps }; +export { FImage }; -export default { - install(app: App): void { - app.component(DiscussionListProps.name as string, DiscussionListProps); - } +FImage.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { + componentMap['image'] = FImage; + propsResolverMap['image'] = propsResolver; }; +FImage.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { + componentMap['image'] = FImageDesign; + propsResolverMap['image'] = propsResolver; +}; + +export default withInstall(FImage); diff --git a/packages/ui-vue/components/image/src/designer/image.design.component.tsx b/packages/ui-vue/components/image/src/designer/image.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..518e9a005a54a73c1423ae8611342eef178be639 --- /dev/null +++ b/packages/ui-vue/components/image/src/designer/image.design.component.tsx @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { defineComponent, computed, ref, SetupContext, inject, onMounted } from 'vue'; +import { imageDesignProps, ImageDesignProps } from '../image.props'; +import { DesignerItemContext, useDesignerComponent } from '@farris/ui-vue/components/designer-canvas'; +import { useImageDesignerRules } from './use-rules'; +import { getCustomClass } from '@farris/ui-vue/components/common'; + +export default defineComponent({ + name: 'FImageDesign', + props: imageDesignProps, + emits: ['change', 'update:modelValue'] as (string[] & ThisType) | undefined, + setup(props: ImageDesignProps, context: SetupContext) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject('design-item-context') as DesignerItemContext; + const designerRulesComposition = useImageDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + + context.expose(componentInstance.value); + + const imageClass = computed(() => { + const classObject = { + 'f-imgcontainer-in-form': true, + }; + return getCustomClass(classObject, props.customClass); + }); + + const imageStyle = computed(() => { + const customStyle = {}; + if (props.width && props.width > 0) { + customStyle['width'] = props.width + 'px'; + } + if (props.height && props.height > 0) { + customStyle['height'] = props.height + 'px'; + } else { + customStyle['height'] = '100px'; + } + return customStyle; + }); + + const designImageSrc = "data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5AIaBxkjOE3ZQAAAC55JREFUeNrtndlvHFkVh79bS6+2E9tJZjIBJhMGECAkFolFIIEYIYF4QCPe5xWJPwEED/N3IPGARkg8sEiAQEIg4GlAQuwSmZmEJHbsuPeluqpruYeHbhsn6W4v1d3VTt1PsmS7qqvr1vnVueeee24VGAwGg8FgMBhyh5q1sdlsHv2utSYIAsIwRESyPm/DBJRSFAoFisUitm0f/X9ra2v6Z6ZtaDabXL58mWazycbGRqnf738wCIL3a60vi4iVdWMNExHbtjvFYvF2tVr9d7/f96vVKr7vTxXBRAEcGn9/f59yufzSYDD4lud5r3qed1lrbWNYWSzL0pVKpbO2tvbLSqXyuu/7tzc3N6eKYKoAtNbYtv1St9v9QaPR+GwYhlm3zXAGCoUCW1tbf9rY2HgtSZL/2LY9UQBTXfn6+nppMBh8yxj/YhKGIc1m85O+77++trZWnrbfRAForfE874Oe571qjH9xCcOQTqfzymAw+FCSJBP3mSiAIAgIguADnudtZt0IQzqGw+FWFEWf73a7E7dPFEAYhiRJcllrbaL9C47WWonIrf39/YnbJxp4PM6fmSMwXBxExDpTF2DID07aAygUShlnkRVadKrPpxKAQjGIB+z199GkOxHD2XEthxtrN3Cs85sxnQCUYq+/z/f+/n0iHWV9PXKFIGyWNvnmR7/BVmkT4XzzM6m7AI0m0hGhDlEmblwaIkKsI0BG4fo55+fmFgQa42dB+mtuRgE5xwgg5xgB5JzUQeA8eTKOMVHF4slcAAIIiiIJJZXgKo1GEYqFLw4JCoUYMSyIzARweLdfsYZ8wOnwMbfB85ZPUY1SSn1xuR1v8Ndoi3vJGr44WOcd6ximkokABCgqzSfdGl8u7vK87eOMjXto4ucJeK/d4wuFff4Rb/Lz4N3cT6rGE8yZpQtAgIpK+FrpPl8s7lEYJ5H1hP0AyirhU26d99gebwxu8c/YlCjMk6WPAlw0Xy094EvFXdxTzCAII3Fct3xeq7zDy07XdARzZKkCEBQfcVu8UtjD4mzZSw1cswK+XrrHmoqNCObE0gQgwLqK+Epxl7JKzmVADbzX6fJxt7G0C/Sss0QBKN5le7zb9lJNHBcQPl04oKSSFEcxHLI0ASiEl+xeasMJ8JwVsKEikx2YA0sTgINw0+nPxWRlFbOuIhMHzIGlCcBSwpqKUx9HABvBVaYCaR4sTQBaFJ6kTzsoQKOIVmweK4kS4jC9wJfN0q5ijOJeUp2L2/bFoa/dlYkAdKJp3GvQuNtAxxfLMy11FHAnXmeYcmW5Ag50iY64qBWJAnoHPbymx6A9oL3XPnd5VhYsdRSwk1R5oKupvjRC8efoCoGsxip1v+PTftg+Mnrv0UgMF4UlCgC6UuDXwQ0Csc7lvi3gTrzOn8IrS7tAsxoUBRHN+83H3L6ONa2dFqF3MRbVLjWSUgh/i7f4Y/gcwtkKPiygrov8OHiRnmTf/+tY095tEw6eMLSCyB8JI4lWP1m19FA6FIufBi/yh/A5YtSJJ6DGJ3mgS7zh3+I/8aXMjQ/Qq/XwGlNcvQK/69Peba/885SWPh2sgL44/NC/xU5S5ZXiHtcsn8MeXY7tBzAQm9vxJX4WvIc7ydpKGN/v+HR2Oycat1frUawWWLu6nvUpTyWTghAFBGLzm+EL/DXa4hNugw+7LTZVeFQR5InDXlLhzegqt+MNAnGyj/oVxMEx936CGkULrZ02brlAca2Y7blPIbOSsMNrV9MlfjW8wW/D65QY1QQKiqFYBGITY41rArN3paMAb9zvn9IVxcOY5v0m116+hl1YjZHLcTIvCj28jpFYhFiPjaFH/X/2hj+kP6vfn9HAoBfQ2mmxfXMbZa1CJ/Z/Viqfqp74WSX8jk/7FP3+NPqNPr1aL+tmPMVKCWAlUWM3/iDdsE600N5tE3SDrFv0GEYAJ6BjTetBi2F/iCAjD3AWJyCjlbwiQhzGNO41iILVWUqfeQyw6iRhguVYbFzbGP1j7BH8rn+iEJRSFDeKOK792K6hN8QtuVk3DTACOBG37LJ9c/vob9FC/W79VF5ARHCKDlduXhkFf6sTzx5huoAz0m/0zzQS8JoeXmu8/wpGuEYAp0VB6IVnTu9KPAr+In91+v3jGAGcEh1rWrst4uEZq37Gk0Ot3RaiV68PMAI4Jb1aj0F7cO7PD1oD+vV+1s14CiOAkxhn8joPO6mCONFC++GE6eOMMQI4gSRMaO20SML0c/txENN60EInq1M3aAQwC4Huo+4oezePyF2NUsq9g9VJCRsBTGNc1NF91E1/rGOICJ39DkF/TqJKiRHAFOLh2F1H83fXyTAZHzv7kjEjgAmICJ29DkNvuJi79DCw3J+vdzkPRgATGLQGi++nZVRC7nf8TNs6UwCHs1gnNeRZIgoiWjstJFl8w5JoTiOMKad6mozlxMkgy7JQSuG6oxmrae8DsJSF4zoo+9l4Z4DokeuPggjs5bQnHIR0HnXYfnH7zN2NZSsc18V13YlPCxcRHMd5zJZPMlEA6+vrKKW4fv36TBXZlk2n3KW8U8ZO7Av/wOj2XodgGGCvOUtriQC+HxCTsH5l7dSf06KpVqrcuPECVytXp744QimFbdvcunVr4vapHgDAcWbPFtvKxnEcLNvCusDhhFIQ9IY0HzRHC1aWWLenGC8uvd+gfLmEW3ZP1a1aWCh75IFd1z3xzSHTPEBKqz0bAUASaep36kSDjGbsFAz7Q+p3G6ePPeT4r+e3w8W9beeFQPthm169f1TydRT8LlLfx0rFDn+6j7pzTzydRO4rgpI4IYk1l65f4sk4Ng4TvKY3/2lcBeVLZQpl93GNyWgUohONZS/n3sy9AGzX5uqtp1cb60Rz8NbBYubwBSzH4tr7rmG79lPblhlLmy5gCu2HHTp7i3PHXsOj8d/G06OsJQ+kjAAm0K/3JxtnzrQfduhmnA42AjjOOBo/eLs2l/n/k9DxaPThd/zMZgaNAI6RhAkH79RGT/dYhkEUREHMwVu1zIpGjQDGiBYa95qjku9l3o3juoPaO/VMKoWMAMZ09ru0dlqZfX/voEdrp7307zUCUKPp3/qdeqZl2yJC835z6ZXD+RaAgnAQcfB27ez1/gsgCRMO3q4x7C+oEGUCuRaAjjS1d2oEvdWozztcfbSsUQjkWAAiQnOntXoPbVCj9YT1u4vPQ0COBdCr9Wnea67shGZnb7GZyEPyJwAFQTeg9nZtpR/srGNN/W6dQXuxNYO5mwyKhzG1O3XiMEalLPs66eUnaR9nnEQJ9Ts1XvjwCzjFxZgqdwJQSrF9c5tttlIdx/KFS2/62P5kL5KULTqfKqPLaaPLxdZb5k4AtmtT3aykOoYocHqazSjBDvTTIwiBxLFINirE6xYqZZyxyGAwdwKA+VxQGRdiydTt41ItEVb5ccH5CwINj2EEkHOMAHKOEUDOSRkEqqNSai36wq8MOi0CWCKIaEQ0Tw0DxoHf6LpI6lHAJDQ61XqAQ1IJQBAKVoGrlSuEyWo+Bm0hKLBFs3m1gl2RycPAqkJVqyQVayHpZkHYLm1hqXTZplQC0KK5eelFvvOZb7OySfVFocH6nGbaC0zFAl2yFtrJWsrmcvFSqmFt6jyAa7lcrazAW7yy4PRrORdG2pzGXBJBq/5iJMN0zCgg5xgB5BwjgJxjBJBzjAByjhFAzjECyDlGADnHCCDnGAHkHCOAnGMEkHOMAHKOEUDOMQLIOUYAOccIIOcYAeQcI4CcYwSQc2YJwFR6PjtMfRTKLAF0Zn3QcKG4N23DLAHcZiQCw8WmBfx+2sZZAvg38Iusz96Qmt8B/5y2cZYAfOB14M2sW2A4N38BvgsMpu1w0ijgLeA14EdAAxMYXhSawE8Y2e5fs3acup672Wwe/7MMfAj4PPASkPIBaIYFoRkFfL9n5PaP7vytrclPRZu5oP8JERguKNOMbzAYDAaDwWDIKf8DldhpSDT69G8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMTItMjhUMDk6MzE6NTYrMDA6MDC4axO5AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAxLTA4VDE5OjQzOjIyKzAwOjAwlXCP7wAAACB0RVh0c29mdHdhcmUAaHR0cHM6Ly9pbWFnZW1hZ2ljay5vcme8zx2dAAAAGHRFWHRUaHVtYjo6RG9jdW1lbnQ6OlBhZ2VzADGn/7svAAAAGHRFWHRUaHVtYjo6SW1hZ2U6OkhlaWdodAA1MTKPjVOBAAAAF3RFWHRUaHVtYjo6SW1hZ2U6OldpZHRoADUxMhx8A9wAAAAZdEVYdFRodW1iOjpNaW1ldHlwZQBpbWFnZS9wbmc/slZOAAAAF3RFWHRUaHVtYjo6TVRpbWUAMTU0Njk3NjYwMobft/wAAAASdEVYdFRodW1iOjpTaXplADI1ODIxQl184icAAABadEVYdFRodW1iOjpVUkkAZmlsZTovLy9kYXRhL3d3d3Jvb3Qvd3d3LmVhc3lpY29uLm5ldC9jZG4taW1nLmVhc3lpY29uLmNuL2ZpbGVzLzExOC8xMTgzNzA3LnBuZ2fPs9sAAAAASUVORK5CYII="; + + return () => ( +
    + {props.alt} +
    + ); + } +}); diff --git a/packages/ui-vue/components/image/src/designer/use-rules.ts b/packages/ui-vue/components/image/src/designer/use-rules.ts new file mode 100644 index 0000000000000000000000000000000000000000..3aac5efdef9836b0ab2b7557f179a7a4c0ed71de --- /dev/null +++ b/packages/ui-vue/components/image/src/designer/use-rules.ts @@ -0,0 +1,20 @@ + +import { UseDesignerRules } from "../../../designer-canvas/src/composition/types"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "../../../designer-canvas/src/types"; +import { ImageProperty } from "../property-config/image.property-config"; + +export function useImageDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { + + const schema = designItemContext.schema as ComponentSchema; + + // 构造属性配置方法 + function getPropsConfig(componentId: string, componentInstance: DesignerComponentInstance) { + const inputGroupProps = new ImageProperty(componentId, designerHostService); + return inputGroupProps.getPropertyConfig(schema, componentInstance); + } + + return { + getPropsConfig + } as UseDesignerRules; + +} diff --git a/packages/ui-vue/components/image/src/image.component.tsx b/packages/ui-vue/components/image/src/image.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9da5870062f6ed242218719fe06f42f0d8350526 --- /dev/null +++ b/packages/ui-vue/components/image/src/image.component.tsx @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { defineComponent, computed, ref, SetupContext, watch } from 'vue'; +import { imageProps, ImageProps } from './image.props'; +import { getCustomClass } from '@farris/ui-vue/components/common'; + +export default defineComponent({ + name: 'FImage', + props: imageProps, + emits: ['change', 'update:modelValue'] as (string[] & ThisType) | undefined, + setup(props: ImageProps, context: SetupContext) { + const modelValue = ref(props.modelValue); + const imageClass = computed(() => { + const classObject = { + 'f-imgcontainer-in-form': true + }; + return getCustomClass(classObject, props.customClass); + }); + const imageStyle = computed(() => { + const customStyle = {}; + if (props.width && props.width > 0) { + customStyle['width'] = props.width + 'px'; + } + if (props.height && props.height > 0) { + customStyle['height'] = props.height + 'px'; + } + return customStyle; + }); + // 设置为undefined不生成alt属性,空值影响裂图显示 + const realAlt = ref(props.alt ? props.alt : undefined); + const realTitle = ref(props.title ? props.title : undefined); + + const defaultErrorImage = props.errorSrc ? props.errorSrc : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAABWCAYAAAE1c8GoAAAAAXNSR0IArs4c6QAACEZJREFUeAHtnIt23DQQhgkkodzKrVxKaJqkSd7/eUoJp7QNDRBaaAulLSnzOTs+Y62klWzLu954zlEsS6PRr9+yNJK12XjnUo5n10WXuyikKle67y4y6ea7BagtWqNbwDU4d7/ppFSNctIat4tqmIMYK2DbUsdtAWutVjB4qrQNSfBlGr1m9L3Z7YfN5PAdNbwv4XZYpc55SywZjm1sbSIUcZWjtbjKIaN1eszajVpLIm6fsXlq5ANJfEBGCIYqooPyHSJqeVvieyQEhAd4rJb3AkqNZFVuJIZueNxAOQgpdEm3hhe+bZkV1ZyEymnPD+VH0/Xp+JTs42zVqpjxVgYtyphxq+eLa8ueSeYvPoU2xtWo2vtEIoTXEn7SRK4+43TPIwkuItco5a1syQ06jFA/kIEhLfRY4t+S2JfYN65XwwAEOaLoL+/6+ftUje+LPTjrS/4UQ2eW874M13a0t3R+YWqLl5GKCftAnfxOt/9RupTxCllR48q5ywGv881Z4qlcn7sKKfch5GoYG9+lGHJ0qi4eMm4HoBOnYPJtiBZGuM7dM4Q8Bd3CIaOt8cqDEQTRCtoYvyVG7cQdrCDXOF4dvpcr3gpyjONYf+FaNfdzFfiMo+Qq0qu+N4ZC0UY517jNJE4+L8RByJonvbZh+3mdaAocmnhOlOVGY4LOKZyk69KSVChVqbjxyoFJRZOhd7caGqXAjoSPMgouW/UewHclXJsh+UeuZ7P4ql0Ydb5RULxCCpq0B5qxgtdXgulTCRXeop28ZONHC9yOtqkEMV/YnZ4Xcv8otXBfermMM5VY0OBgNPJNMeT1LToKlnXM+0Zt7eV2FZzSjyVYd5puQncZVHKBA47FRWevumsr2wDvWueRGNC++lTiv7YxOCTwPQG47YD8TO4J2SPTEMBZarKWjYmOTP+K0v2YouaVBM70XPsWWuGCK942Q+sbCdElb8447hvDfTi08lzQ1haEUt+hTbTxRYyzwNQVLOV0lXUh8XskGIGEYEVGLyeKTRqANEYy3m7NsJm4kKyMU+RUlOy4nlKms47LOKvt2KLYV+HgoAHh9vFc0L6GDJIGcPrr6ATg7ks2hkac69QL2K8lMPbaNNJXRdh55APOSwABkoD/MBYB+M90lb7H3tIEsFi+xnBou8ZDuWe6XTUBI5Of7rjetOM4Ds7fq4bY4PlR4jpZbrnjuNFb7egEfOjnMzE+FsZ14hoab12fHQ7rxEjEXVigiq8zuKOW28ftakjbd6iRIa85wH2f8xSr3WPXtKLXHOAxcLG8Ig3IAR77rs8JnUElBzj+cHVY0UHIi1mdyXDSi97mAAcIn/+s90hjGFUGl9zhEIAng6P0VJjLuMfEcpKWAZwNUPWrW7d6aODMBXpQCPB29ZXViCGBbwsyd1vPbvKvJHB8nL0AMsBnE5hdIFB5LJk6fD6OLYO/k4UlS9nWlBinD6c6YejxZJKkNPDcjSaezFYK8pLA2w55+wKcFzkqpYDnMu2C3JOEqMeZCvxLMbToBdPK6autx2c1ItddCcE1QApwJgyA8+Lw+GNleMyxfMnOEsZ975GrRZXQavcbJYz6jKGb9GKJXo7siPJ1t8Cmm2DuYS8EBGMsLE5n+nwHivbJmV7bCwf0GyTz+OkGCIsBzq4jsBprFDq8+Z9LwAYfBEpL4yn7wNGPUwUWAD+4NOiX2nNADw7WVmiBV6dWbeYqxy3wVcY5h20CPkdJ2YSLsTL+BODnZckpYv2cyYMTzFxLznx9omdTquHF4dF9JYHDXZP0x8BfYoqTfheYVLcZd0VdFtIn6Z8BiH+svuFEdv8EuxZxqDbxaX0+Kbv3JxIG362XOtdBmFdYDrpynVnTN+W/kvSJbJeu9HtOMFXHr5wiGz6yHZ3ptk8GJsL7ZDPB1kR4Akl9qkyE98lmgq2J8ASS+lTxbXX2aV9tsavNPq79rsDSnN9bPZOwjqKLykbbShPOrn9jM9vUDvn6Qd9+GTAq6xctNaTwdO9ICJHtMsnvQg8keHuFqzzm+1KEM4SwbZAjvG3uR72c8qPQLUW472BfCiHVjlqK4lh1ShHOzljuT8WZPGPHR8fKcQN3KcKp5JGEs0Zt4Rt++kVYeyntpXAUgMCD5V9U4JUwtrMxhlvIW9B2+JGi45PShCsjjM0MF2s/ZGiDQ9eSQ0qozmWl8+lw6T/AHqqHL4tk6mUtwAJM5YZEnkj4TROGvK4z4Xx14Qe2vreYr1wEvCkm9sHmkXUknMOFtySktI0FGoG5BS+pOPEpoATHKISeDNF8rM0VthaOJPCrck57FluArQPh7L9wFDZ130ZUg4LreiiB75GsI3r/rjt2wvl/HiUOLjH+s/nGx/SHEuzP+OS2vYyVcCY8TomVFs5lH0h4LQHiuXaSsRHu/nu/To3PKMxEvC+BIQbi+S8iraQE4cz6LNs79wbTIrYEcPGWvV/OtsRtCUyqjPG0M0v6IhwifAdBWVywyGgr9KxdCbl7623rSy2nHhFuJMQn/8+croRTMT9hCnkIPAQCryDuVmqvh2BcPMbQVRY6Gm8egh+/8PtsW8IpR0WphOAbMwYiv0v4o4rN/6EBOxJwz8YmdDwCK1d2SL1CA489OfTI+550iIPoPl5xXC56PVeEn30x/q+LMNzAb0NSezjeAU9vzkDDWt4Nb8deXpFRaXu5WkT4UP7uqJjsApan4BtSuticykYYwMuYZEAGJsIHJJuqJsKXQLjvpxEDw7gy1b2kh+MLF//ScWUoDTcUjk/VV+Ta1yZ+uMqrm/MCsiW8VcItFawmOcvN/gh+uk9HkicJMEBPfiMBklnis2qv5X8shM3uwwEEhgAAAABJRU5ErkJggg=='; + const imageSrc = ref(props.modelValue || props.src); + function resetTitleByProps() { + // 可能经过错误,需要重置 + if (props.title) { + realTitle.value = props.title; + } + } + // 新路径 + watch(() => props.src, (newValue) => { + resetTitleByProps(); + imageSrc.value = newValue; + }); + + watch(() => props.modelValue, (newValue) => { + resetTitleByProps(); + modelValue.value = newValue; + imageSrc.value = newValue || props.src || defaultErrorImage; + }); + + function errorSrc() { + if (imageSrc.value === defaultErrorImage) { + // 应用了错误图片,错误图片也不能正常加载导致错误 + // src错误,title属性存在,显示裂图需要指定宽度和高度 + realTitle.value = undefined; + } + imageSrc.value = defaultErrorImage; + } + /** + * 浏览器默认:src错误,alt为空字符串,不显示裂图。src错误,title属性存在,显示裂图需要指定宽度和高度。src错误,title不存在,alt不存在,会显示裂图。 + * 修改之后,src错误,会有裂图显示 + */ + return () => { + return (props.visible ?
    + {realAlt.value} +
    : null + ); + }; + } +}); diff --git a/packages/ui-vue/components/discussion-list/discussion-list.props.ts b/packages/ui-vue/components/image/src/image.props.ts similarity index 34% rename from packages/ui-vue/components/discussion-list/discussion-list.props.ts rename to packages/ui-vue/components/image/src/image.props.ts index 62c4add70bd2ead518b69691e980dd90e94fe3e5..36f09b1192d8c8d8777196a840d2cbf89431fbec 100644 --- a/packages/ui-vue/components/discussion-list/discussion-list.props.ts +++ b/packages/ui-vue/components/image/src/image.props.ts @@ -1,3 +1,4 @@ + /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -14,20 +15,35 @@ * limitations under the License. */ import { ExtractPropTypes, PropType } from 'vue'; +import { createPropsResolver } from '../../dynamic-resolver/src/props-resolver'; +import { schemaResolver } from './schema/schema-resolver'; +import { schemaMapper } from './schema/schema-mapper'; +import imageSchema from './schema/image.schema.json'; + + +export const imageProps = { + // 绑定特殊字段,辅助构造src,指定服务器端图像Id + modelValue: { type: String, default: '' }, + // 指定直接路径 + src: { type: String, default: '' }, + errorSrc: { type: String, default: '' }, + alt: { type: String, default: '' }, + title:{ type: String, default: '' }, + // 指定自定义样式 + customClass: { type: Object, default: '' }, + id: { type: String, default: '' }, + width: { type: Number }, + height: { type: Number }, + // 是否只读 + readonly: { type: Boolean, default: false }, + visible:{type:Boolean,default:true} +} as Record; + +export type ImageProps = ExtractPropTypes; -export const discussionListProps = { - pagerOnServer: { Type: Boolean, default: true }, - /** 是否支持分页 */ - supportPaging: { Type: Boolean, default: true }, - /** 当前页码 */ - pageIndex: { Type: Number, default: 1 }, - /** 总条数 */ - total: { Type: Number, default: 0 }, - /** 每页显示个数 */ - pageSize: { Type: Number, default: 10 }, - /** 评论数据 */ - discussionData: { Type: Object, default: [] }, - personnelsDisplayKey: { Type: String, default: 'userName' }, -}; +export const propsResolver = createPropsResolver(imageProps, imageSchema, schemaMapper, schemaResolver); -export type DiscussionListProps = ExtractPropTypes; +export const imageDesignProps = Object.assign({}, imageProps, { + componentId: { type: String, default: '' } +}); +export type ImageDesignProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/image/src/property-config/image.property-config.ts b/packages/ui-vue/components/image/src/property-config/image.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..8e1062a232f0eee1eb68b3b8f1e65d620d7ea49f --- /dev/null +++ b/packages/ui-vue/components/image/src/property-config/image.property-config.ts @@ -0,0 +1,80 @@ +import { DesignerComponentInstance } from "@farris/ui-vue/components/designer-canvas"; +import { InputBaseProperty } from "../../../property-panel/src/composition/entity/input-base-property"; + +export class ImageProperty extends InputBaseProperty { + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + getPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance) { + this.getCommonPropertyConfig(propertyData, componentInstance, 'Card'); + // 编辑器 + this.propertyConfig.categories['editor'] = this.getEditorProperties(propertyData); + return this.propertyConfig; + } + getGridFieldEdtiorPropConfig(propertyData: any, componentInstance: DesignerComponentInstance | null) { + this.propertyConfig.categories = {}; + this.getCommonPropertyConfig(propertyData, componentInstance, 'Grid'); + // 编辑器 + this.propertyConfig.categories['editor'] = this.getEditorProperties(propertyData); + return this.propertyConfig.categories; + } + getEditorProperties(propertyData) { + return { + description: "编辑器", + title: "编辑器", + type: "avatar", + $converter: "/converter/property-editor.converter", + parentPropertyID: "editor", + properties: { + rootId: { + description: "服务器根目录的Id", + title: "服务器根目录", + type: "string" + }, + width: { + description: "", + title: "图像宽度", + type: "number", + editor:{ + nullable:true, + min: 0, + useThousands: false, + needValid:true + } + }, + height: { + description: "", + title: "图像高度", + type: "number", + editor:{ + nullable:true, + min: 0, + useThousands: false, + needValid:true + } + }, + src: { + description: "设置图片初始地址,可以是绝对路径或者是图片base64转码后的字符串。比如/platform/xxx/xxx.png或者是data:image/png;base64,xxx。", + title: "图片地址", + type: "string" + }, + errorSrc: { + description: "加载图像错误的替代图片,可以是绝对路径或者是图片base64转码后的字符串。比如/platform/xxx/xxx.png或者是data:image/png;base64,xxx。", + title: "加载图像错误的替代图片地址", + type: "string" + }, + alt: { + description: "图像最终错误图片未能正确显示时,会显示设置的替代文本。", + title: "替代文本", + type: "string" + }, + title: { + description: "鼠标滑过图像时的提示文本", + title: "提示文本", + type: "string" + } + } + }; + } +} diff --git a/packages/ui-vue/components/image/src/schema/image.schema.json b/packages/ui-vue/components/image/src/schema/image.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..5c3d4cddeb1d6f37359e45f7672b9899b3f27a63 --- /dev/null +++ b/packages/ui-vue/components/image/src/schema/image.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/image.schema.json", + "title": "image", + "description": "A Farris Component", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for image", + "type": "string" + }, + "type": { + "description": "The type string of image", + "type": "string", + "default": "image" + }, + "appearance": { + "description": "", + "type": "object", + "properties": { + "class": { + "type": "string" + }, + "style": { + "type": "string" + } + }, + "default": {} + }, + "binding": { + "description": "", + "type": "object", + "default": {} + }, + "rootId": { + "description": "", + "type": "string", + "default": "default-root" + }, + "src": { + "description": "", + "type": "string", + "default": "" + }, + "errorSrc": { + "description": "", + "type": "string", + "default": "" + }, + "alt": { + "description": "", + "type": "string", + "default": "" + }, + "title": { + "description": "", + "type": "string", + "default": "" + }, + "visible": { + "description": "", + "type": "boolean", + "default": true + }, + "width": { + "description": "", + "type": "Number" + }, + "height": { + "description": "", + "type": "Number" + } + }, + "required": [ + "id", + "type" + ], + "ignore": [ + "id", + "appearance", + "binding", + "visible" + ] +} \ No newline at end of file diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/schema-mapper.ts b/packages/ui-vue/components/image/src/schema/schema-mapper.ts similarity index 58% rename from packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/schema-mapper.ts rename to packages/ui-vue/components/image/src/schema/schema-mapper.ts index 74c1e9dd6c759f4e28696f6891a9def5f31eee76..2ca33ca080647b9edc66a133396282d6a3b1698f 100644 --- a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/schema-mapper.ts +++ b/packages/ui-vue/components/image/src/schema/schema-mapper.ts @@ -1,7 +1,5 @@ import { MapperFunction, resolveAppearance } from '@farris/ui-vue/components/dynamic-resolver'; -// import {resolvePreset} from './preset-resolver'; export const schemaMapper = new Map([ - ['appearance', resolveAppearance], - // ['preset', resolvePreset] + ['appearance', resolveAppearance] ]); diff --git a/packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/schema-resolver.ts b/packages/ui-vue/components/image/src/schema/schema-resolver.ts similarity index 100% rename from packages/ui-vue/components/filter-bar/src/designer/filter-bar-config/schema/schema-resolver.ts rename to packages/ui-vue/components/image/src/schema/schema-resolver.ts diff --git a/packages/ui-vue/components/discussion-list/style.ts b/packages/ui-vue/components/image/style.ts similarity index 46% rename from packages/ui-vue/components/discussion-list/style.ts rename to packages/ui-vue/components/image/style.ts index a7f20456e56bfe292e9ce9f30be254c86d450c73..236d30166cfb3a58f75d49657a3778bc693fae22 100644 --- a/packages/ui-vue/components/discussion-list/style.ts +++ b/packages/ui-vue/components/image/style.ts @@ -1,2 +1,2 @@ import "@farris/ui-vue/components/dependent-base/style"; -import "@farris/ui-vue/theme-default/components/discussion.css"; +import "@farris/ui-vue/components/dependent-icon/style"; diff --git a/packages/ui-vue/components/index.ts b/packages/ui-vue/components/index.ts index aac1ce65d31375384cb83e65e61b74f09eaa155c..234bf0c16aa7ec987d9ca0312c31ba66139ef06c 100644 --- a/packages/ui-vue/components/index.ts +++ b/packages/ui-vue/components/index.ts @@ -16,6 +16,7 @@ import { App } from 'vue'; import Accordion from './accordion'; import Avatar from './avatar'; +import Image from './image'; import BorderEditor from './border-editor'; import Button from './button'; import ButtonGroup from './button-group'; @@ -32,8 +33,7 @@ import ComboTree from './combo-tree'; import Component from './component'; import DataGrid from './data-grid'; import DatePicker from './date-picker'; -import DiscussionList from './discussion-list'; -import DiscussionEditor from './discussion-editor'; +import Comment from './comment'; import Dropdown from './dropdown'; import DynamicForm from './dynamic-form'; import FilterBar from './filter-bar'; @@ -113,6 +113,7 @@ export default { app.use(Locale, options) .use(Accordion) .use(Avatar) + .use(Image) .use(BorderEditor) .use(Button) .use(ButtonGroup) @@ -130,8 +131,7 @@ export default { .use(Condition) .use(DataGrid) .use(DatePicker) - .use(DiscussionEditor) - .use(DiscussionList) + .use(Comment) .use(Dropdown) .use(DynamicForm) .use(DynamicView) diff --git a/packages/ui-vue/components/input-group/designer.ts b/packages/ui-vue/components/input-group/designer.ts index b8a2a36e0e85a1e56e8ebe8a61d9fdf38c5d06f8..921ed34c6e342a7e9aa694a0491e6451cdd2cf6e 100644 --- a/packages/ui-vue/components/input-group/designer.ts +++ b/packages/ui-vue/components/input-group/designer.ts @@ -1,5 +1,5 @@ -import { withInstall } from '@farris/ui-vue/components/common'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { RegisterContext, withInstall } from '@farris/ui-vue/components/common'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import FInputGroup, { inputGroupProps, type InputGroupProps } from '@farris/ui-vue/components/input-group'; import FInputGroupDesgin from './src/designer/input-group.design.component'; import { schemaMapper } from './src/schema/schema-mapper'; @@ -8,20 +8,29 @@ import inputGroupSchema from './src/schema/input-group.schema.json'; const propsResolver = createPropsResolver(inputGroupProps, inputGroupSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator( + inputGroupProps, + inputGroupSchema, + schemaMapper, + schemaResolver +); FInputGroupDesgin.register = ( componentMap: Record, propsResolverMap: Record, configResolverMap: Record, - resolverMap: Record) => { + resolverMap: Record, + registerContext: RegisterContext) => { + componentMap['input-group'] = FInputGroup; - propsResolverMap['input-group'] = propsResolver; + propsResolverMap['input-group'] = propsResolverGenerator(registerContext); }; FInputGroupDesgin.registerDesigner = ( componentMap: Record, propsResolverMap: Record, - configResolverMap: Record) => { + configResolverMap: Record, + registerContext: RegisterContext) => { componentMap['input-group'] = FInputGroupDesgin; - propsResolverMap['input-group'] = propsResolver; + propsResolverMap['input-group'] = propsResolverGenerator(registerContext); }; export { FInputGroupDesgin, propsResolver }; diff --git a/packages/ui-vue/components/input-group/src/components/appended-button.component.tsx b/packages/ui-vue/components/input-group/src/components/appended-button.component.tsx index 13f3aa103a6adc3404364bfa30a5cd98486882a5..32d89e7735234da94218db7a44ba9729957733f4 100644 --- a/packages/ui-vue/components/input-group/src/components/appended-button.component.tsx +++ b/packages/ui-vue/components/input-group/src/components/appended-button.component.tsx @@ -1,4 +1,4 @@ -import { SetupContext, computed } from 'vue'; +import { SetupContext, computed, ref, watch } from 'vue'; import { InputGroupProps } from '../input-group.props'; import { UseAppendedButton, UseClear, UsePassword } from '../composition/types'; @@ -16,6 +16,11 @@ export default function ( const shouldShowTemplateAppendButton = computed(() => !!context.slots.groupTextTemplate); const { clearButtonClass, clearButtonStyle, onClearValue, shouldShowClearButton } = useClearComposition; + const updateFlag=ref(0) + + watch(appendedContent,(newValue)=>{ + updateFlag.value++; + }); function renderClearButton() { return ( @@ -38,8 +43,8 @@ export default function ( } event.stopPropagation(); } - - const onClickHandle = props.type === 'password' ? usePasswordComposition.onClickAppendedButton : onClickAppendedButton; + + const onClickHandle = props.type === 'password'|| props.showType==='password' ? usePasswordComposition.onClickAppendedButton : onClickAppendedButton; function renderStaticAppendedButton() { return ( @@ -60,15 +65,14 @@ export default function ( } function getAppendedButtonRender() { - return shouldShowStaticAppendButton.value ? renderStaticAppendedButton : - shouldShowTemplateAppendButton.value ? renderTemplateAppendedButton : ''; + return shouldShowStaticAppendButton.value ? renderStaticAppendedButton() : + shouldShowTemplateAppendButton.value ? renderTemplateAppendedButton() : ''; } - const renderAppendedButton = getAppendedButtonRender(); return () => -
    +
    {shouldShowClearButton.value && renderClearButton()} - {renderAppendedButton && renderAppendedButton()} + {getAppendedButtonRender()}
    ; } diff --git a/packages/ui-vue/components/input-group/src/components/number.component.tsx b/packages/ui-vue/components/input-group/src/components/number.component.tsx index 7d8bd6105f0760f481c520adc8794e275df1f12d..d64068b437838ef3b3a91e3e224edc1775a05f73 100644 --- a/packages/ui-vue/components/input-group/src/components/number.component.tsx +++ b/packages/ui-vue/components/input-group/src/components/number.component.tsx @@ -50,7 +50,7 @@ export default function ( ); return () => ( -
    +
    {renderNumberTextBox()} {shouldShowSpinner.value && renderSpinner()}
    diff --git a/packages/ui-vue/components/input-group/src/composition/use-appended-button.ts b/packages/ui-vue/components/input-group/src/composition/use-appended-button.ts index 7998801e35fb5b7f7a5120c3af5c4b965846dc3d..e1f2ebe49c99acdd91a4f79b75e9916f86a48342 100644 --- a/packages/ui-vue/components/input-group/src/composition/use-appended-button.ts +++ b/packages/ui-vue/components/input-group/src/composition/use-appended-button.ts @@ -10,7 +10,7 @@ export function useAppendedButton( const forcedToShowAppendedButton = computed(() => props.showButtonWhenDisabled && (props.readonly || props.disabled)); - const shouldShowAppendedButton = computed(() => props.type === 'password' || props.enableClear || !!props.groupText || forcedToShowAppendedButton.value); + const shouldShowAppendedButton = computed(() => props.type === 'password' || props.showType==='password' || props.enableClear || !!props.groupText || forcedToShowAppendedButton.value); const appendedButtonClass = computed(() => { const classObject = { diff --git a/packages/ui-vue/components/input-group/src/composition/use-password.ts b/packages/ui-vue/components/input-group/src/composition/use-password.ts index 0758d81733574868076f5cd7579cc8703f19fe13..9a1c300c43e295228ffd5318877bdaf4a98363fa 100644 --- a/packages/ui-vue/components/input-group/src/composition/use-password.ts +++ b/packages/ui-vue/components/input-group/src/composition/use-password.ts @@ -10,7 +10,7 @@ export function usePassword( ): UsePassword { const { appendedContent } = useAppendedButtonComposition; const enableViewPassword = ref(props.enableViewPassword); - const isPassword = computed(() => props.type === 'password'); + const isPassword = computed(() => props.type === 'password'|| props.showType==='password'); const shownPasswordContent = ''; const hiddenPasswordContent = ''; @@ -19,7 +19,7 @@ export function usePassword( watch(() => [props.readonly, props.disabled], ([readonly, disabled]) => { if (isPassword.value) { inputType.value = (readonly || disabled) ? 'password' : inputType.value; - appendedContent.value = (readonly || disabled) ? hiddenPasswordContent : appendedContent.value; + appendedContent.value=props.readonly||props.disabled?'':(enableViewPassword.value ? hiddenPasswordContent : ''); } }); @@ -31,8 +31,10 @@ export function usePassword( } function resetPaaswordOptions() { - inputType.value = isPassword.value ? 'password' : 'text'; - appendedContent.value = isPassword.value ? (enableViewPassword.value ? hiddenPasswordContent : '') : appendedContent.value; + inputType.value = isPassword.value ? 'password' : inputType.value; + if(isPassword.value){ + appendedContent.value=props.readonly||props.disabled?'':(enableViewPassword.value ? hiddenPasswordContent : ''); + } } resetPaaswordOptions(); diff --git a/packages/ui-vue/components/input-group/src/composition/use-text-box.ts b/packages/ui-vue/components/input-group/src/composition/use-text-box.ts index f89dce16c0a4f49aacd796532a9e5cf77055071f..d2954ec3c644853cef65dcfae04f8373092aa6ab 100644 --- a/packages/ui-vue/components/input-group/src/composition/use-text-box.ts +++ b/packages/ui-vue/components/input-group/src/composition/use-text-box.ts @@ -2,11 +2,11 @@ import { SetupContext, computed, ref, watch } from "vue"; import { InputGroupProps } from "../input-group.props"; import { UseTextBox } from "./types"; import { LocaleService } from '@farris/ui-vue/components/locale'; +/** 这个没有被使用 */ export function useTextBox( props: InputGroupProps, context: SetupContext ): UseTextBox { - const inputType = ref('text'); const modelValue = ref(props.modelValue); const displayText = ref(props.modelValue); /** 文本在输入框中的对齐方式 */ @@ -38,7 +38,14 @@ export function useTextBox( } as Record; return classObject; }); - + /** 替换showType 替换 type */ + function getRealShowType(){ + if(props.type&&props.type!=='text'){ + return props.type; + } + return props.showType||'text'; + } + const inputType = ref(getRealShowType()); function onBlur(event: FocusEvent) { document.documentElement.classList.remove(focusCls); context.emit('blurHandle', event); @@ -97,14 +104,21 @@ export function useTextBox( $event.stopPropagation(); changeTextBoxValue(newValue); } - + watch( () => props.modelValue, (value: string) => { modelValue.value = value; } ); - + // showType替换type属性 + watch(()=>props.showType,(newValue)=>{ + inputType.value=newValue; + }); + // type支持旧设置 + watch(()=>props.type,(newValue)=>{ + inputType.value=newValue; + }); return { changeTextBoxValue, disabled, diff --git a/packages/ui-vue/components/input-group/src/designer/input-group.design.component.tsx b/packages/ui-vue/components/input-group/src/designer/input-group.design.component.tsx index bb1508a20481a5194a880b3dd2078c9edcb48590..7dd67c3a09c08cf3c96719bab9aca5a3a8058a0e 100644 --- a/packages/ui-vue/components/input-group/src/designer/input-group.design.component.tsx +++ b/packages/ui-vue/components/input-group/src/designer/input-group.design.component.tsx @@ -1,4 +1,4 @@ - + /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -15,13 +15,10 @@ * limitations under the License. * defination */ -import { defineComponent, inject, ref, SetupContext, onMounted } from 'vue'; +import { defineComponent, inject, ref, SetupContext, onMounted, computed } from 'vue'; import { TextBoxProps, useClear, useTextBoxDesign } from '@farris/ui-vue/components/common'; import { DesignerItemContext, useDesignerComponent } from '@farris/ui-vue/components/designer-canvas'; import { InputGroupProps, inputGroupProps } from '../input-group.props'; -import { useAppendedButton } from '../composition/use-appended-button'; -import { usePassword } from '../composition/use-password'; -import getAppendedButtonRender from '../components/appended-button.component'; import { useInputGroupDesignerRules } from './use-input-group-rules'; export default defineComponent({ @@ -46,11 +43,6 @@ export default defineComponent({ const displayText = ref(props.modelValue); const useTextBoxComposition = useTextBoxDesign(props as TextBoxProps, context, modelValue, displayText); const { inputGroupClass, inputType, inputGroupStyle } = useTextBoxComposition; - const useAppendedButtonComposition = useAppendedButton(props, context); - const { shouldShowAppendedButton } = useAppendedButtonComposition; - const useClearComposition = useClear(props as TextBoxProps, context, useTextBoxComposition); - const { onMouseEnter, onMouseLeave } = useClearComposition; - const usePasswordComposition = usePassword(props, context, inputType, useAppendedButtonComposition); const inputGroupRef = ref(); const designerHostService = inject('designer-host-service'); @@ -58,28 +50,18 @@ export default defineComponent({ const designerRulesComposition = useInputGroupDesignerRules(designItemContext, designerHostService); const componentInstance = useDesignerComponent(inputGroupRef, designItemContext, designerRulesComposition); + const inputValue = computed(() => props.showType === 'password' ? 'password' : ''); onMounted(() => { inputGroupRef.value.componentInstance = componentInstance; }); context.expose(componentInstance.value); - - const renderAppendedButton = getAppendedButtonRender( - props, - context, - useAppendedButtonComposition, - useClearComposition, - usePasswordComposition - ); - const inputElementRef = ref(); return () => { return ( -
    - - {shouldShowAppendedButton.value && renderAppendedButton()} +
    + + {props.showType === 'password' && props.enableViewPassword && + }
    ); }; diff --git a/packages/ui-vue/components/input-group/src/input-group.component.tsx b/packages/ui-vue/components/input-group/src/input-group.component.tsx index a4a2d17862a5fc4a9c9915c9c7a57111b441157e..96c92e29ecc282727bf9216a49b2a2a8b09d9a1a 100644 --- a/packages/ui-vue/components/input-group/src/input-group.component.tsx +++ b/packages/ui-vue/components/input-group/src/input-group.component.tsx @@ -73,8 +73,8 @@ export default defineComponent({ const renderExtend = getExtendRender(props, context); - const shouldRenderNumber = computed(() => props.type === 'number'); - const shouldRenderTextarea = computed(() => props.type === 'textarea'); + const shouldRenderNumber = computed(() => inputType.value === 'number'); + const shouldRenderTextarea = computed(() => inputType.value === 'textarea'); watch( () => [props.value], ([newValue]) => { diff --git a/packages/ui-vue/components/input-group/src/input-group.props.ts b/packages/ui-vue/components/input-group/src/input-group.props.ts index 7d2c1b491ecea49737638f6956400206387b9827..3fba6fe6ebfa434f18d48b49e27183e2a56fb3df 100644 --- a/packages/ui-vue/components/input-group/src/input-group.props.ts +++ b/packages/ui-vue/components/input-group/src/input-group.props.ts @@ -30,13 +30,15 @@ export const inputGroupProps = { enableClear: { Type: Boolean, default: true }, /** 启用提示文本 */ enableTitle: { Type: Boolean, default: true }, - /** 启用密码 */ + /** 能否查看密码 */ enableViewPassword: { Type: Boolean, default: true }, /** 始终显示占位符文本 */ forcePlaceholder: { Type: Boolean, default: false }, /** 扩展按钮 */ groupText: { Type: String, default: '' }, /** 密码模式 */ + showType: { Type: String, default: 'text' }, + /** 密码模式 --废弃*/ type: { Type: String, default: 'text' }, /** 最大值 */ max: { type: [Number, String] }, diff --git a/packages/ui-vue/components/input-group/src/property-config/input-group.property-config.ts b/packages/ui-vue/components/input-group/src/property-config/input-group.property-config.ts index 59bbfcac94e8f133f8d130d38209492dc0d0cd3a..f4950e169beabcdeb8205ce58af3270399316fd6 100644 --- a/packages/ui-vue/components/input-group/src/property-config/input-group.property-config.ts +++ b/packages/ui-vue/components/input-group/src/property-config/input-group.property-config.ts @@ -37,24 +37,79 @@ export class InputGroupProperty extends InputBaseProperty { needValid: true } }, + showType:{ + description: "指定是文本框还是密码框", + title: "展示类型", + type: "enum", + editor: { + type:'combo-list', + data: [{ "id": "text", "name": "文本框" }, + { "id": "password", "name": "密码框" }] + }, + refreshPanelAfterChanged: true, + defaultValue:'text' + }, enableViewPassword: { description: "", - title: "启用密码", - visible: false, + title: "能否查看密码", + visible: propertyData.editor.showType==='password', type: "boolean" + } + }); + } + getGridFieldEdtiorProperties(propertyData: any) { + // 获取绑定字段schema中的长度属性 + let maxLength; + if (propertyData?.binding?.type === 'Form') { + const fieldInfo = this.schemaService.getFieldByIDAndVMID(propertyData.binding.field, this.viewModelId); + if (fieldInfo?.schemaField?.type) { + maxLength = fieldInfo.schemaField.type.length; + } + } + + return this.getComponentConfig(propertyData, {}, { + maxLength: { + description: "文本字数最大长度", + title: "最大长度", + type: "number", + editor: { + nullable: true, + min: 0, + useThousands: false, + max: maxLength + } + }, + showType:{ + description: "指定是文本框还是密码框", + title: "展示类型", + type: "enum", + editor: { + type:'combo-list', + data: [{ "id": "text", "name": "文本框" }, + { "id": "password", "name": "密码框" }] + }, + refreshPanelAfterChanged: true, + defaultValue:'text' }, + enableViewPassword: { + description: "", + title: "能否查看密码", + visible: propertyData.editor.showType==='password', + type: "boolean" + } }); } public getFormatValidation(propertyData) { const formatValidationSchema: any = { title: "输入格式校验", - description: "输入格式校验", + description: "输入校验", + hide: !propertyData.binding?.field, properties: { type: { - title: "输入类型", + title: "格式类型", type: "enum", - description: "输入类型", + description: "格式类型", defaultValue: '', editor: { type: 'combo-list', @@ -68,8 +123,7 @@ export class InputGroupProperty extends InputBaseProperty { $converter: typeConverter, parentPropertyID: 'formatValidation' } - - }, + } }; if (propertyData.editor.formatValidation?.type && propertyData.editor.formatValidation?.type !== 'none') { formatValidationSchema.properties.message = { diff --git a/packages/ui-vue/components/input-group/src/schema/input-group.schema.json b/packages/ui-vue/components/input-group/src/schema/input-group.schema.json index ff584f6dc0c3deecd71de5b55d0cf1d8148226b3..31ff92ed40cc405867d9738ed2245520c0e50f6c 100644 --- a/packages/ui-vue/components/input-group/src/schema/input-group.schema.json +++ b/packages/ui-vue/components/input-group/src/schema/input-group.schema.json @@ -75,7 +75,7 @@ "description": "", "type": "boolean", "default": false - }, + }, "tabIndex": { "description": "", "type": "number", @@ -148,7 +148,7 @@ "enableViewPassword": { "description": "", "type": "boolean", - "default": false + "default": true }, "inputType": { "description": "", @@ -160,6 +160,15 @@ ], "default": "input" }, + "showType": { + "description": "", + "type": "string", + "enum": [ + "password", + "text" + ], + "default": "text" + }, "maxHeight": { "description": "", "type": "number", @@ -230,6 +239,36 @@ "description": "", "type": "string", "default": "blur" + }, + "onChange": { + "description": "值变化事件", + "type": "string", + "default": "" + }, + "onClick": { + "description": "点击事件", + "type": "string", + "default": "" + }, + "onFocus": { + "description": "获取焦点事件", + "type": "string", + "default": "" + }, + "onInput": { + "description": "输入事件", + "type": "string", + "default": "" + }, + "onKeydown": { + "description": "按键按下事件", + "type": "string", + "default": "" + }, + "onKeyup": { + "description": "按键松开事件", + "type": "string", + "default": "" } }, "required": [ diff --git a/packages/ui-vue/components/language-textbox/index.ts b/packages/ui-vue/components/language-textbox/index.ts index 9b8ce851759089791cb7df63977aabb2e9b0c5f0..5d8f7a514a0140d69b5547774c0026d3ad26e563 100644 --- a/packages/ui-vue/components/language-textbox/index.ts +++ b/packages/ui-vue/components/language-textbox/index.ts @@ -17,7 +17,8 @@ import type { App, Plugin } from 'vue'; import FLanguageTextboxDesign from './src/designer/language-textbox.design.component'; import FLanguageTextbox from './src/language-textbox.component'; -import { propsResolver } from './src/language-textbox.props'; +import { propsResolver, propsResolverGenerator } from './src/language-textbox.props'; +import { RegisterContext } from '../common'; export * from './src/language-textbox.props'; export * from './src/types'; @@ -25,14 +26,17 @@ export * from './src/types'; FLanguageTextbox.install = (app: App) => { app.component(FLanguageTextbox.name as string, FLanguageTextbox); }; -FLanguageTextbox.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FLanguageTextbox.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext) => { componentMap['language-textbox'] = FLanguageTextbox; - propsResolverMap['language-textbox'] = propsResolver; + propsResolverMap['language-textbox'] = propsResolverGenerator(registerContext); }; -FLanguageTextbox.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FLanguageTextbox.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext +) => { componentMap['language-textbox'] = FLanguageTextboxDesign; - propsResolverMap['language-textbox'] = propsResolver; + propsResolverMap['language-textbox'] = propsResolverGenerator(registerContext); }; export { FLanguageTextbox }; diff --git a/packages/ui-vue/components/language-textbox/src/language-textbox.component.tsx b/packages/ui-vue/components/language-textbox/src/language-textbox.component.tsx index 156004d7858e37d606543a03d406d04f99f6e695..1f33f7411b0d64653553076ba98e795aea3d8980 100644 --- a/packages/ui-vue/components/language-textbox/src/language-textbox.component.tsx +++ b/packages/ui-vue/components/language-textbox/src/language-textbox.component.tsx @@ -5,17 +5,19 @@ import FButtonEdit from '@farris/ui-vue/components/button-edit'; import { languageTextBoxProps } from "./language-textbox.props"; import LanguageContents from "./components/language-contents.component"; import { LanguageItem } from "./types"; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; +import { isMobilePhone } from '@farris/ui-vue/components/common'; +import { FInputGroup } from "../../input-group"; export default defineComponent({ name: 'FLanguageTextbox', props: languageTextBoxProps, emits: ['update:modelValue'], setup(props, context) { - const {locale: currentLang} = useI18n(); + const currentLang = LocaleService.getLocale(); const { disabled, readonly, editable, modelValue } = toRefs(props); - const displayText = computed(() => modelValue.value?.[currentLang.value]); + const displayText = computed(() => modelValue.value?.[currentLang]); const buttonEditorRef = ref(); watch(() => props.modelValue, (newValue) => { @@ -29,7 +31,7 @@ export default defineComponent({ let currentLangIndex = -1; const curritem = langValue.find((lang, index) => { - const result = lang.code === currentLang.value; + const result = lang.code === currentLang; if (result) { currentLangIndex = index; } @@ -54,8 +56,8 @@ export default defineComponent({ function onDisplayTextChange($event: any) { modelValue.value = modelValue.value || {}; - if (modelValue.value[currentLang.value] !== $event) { - modelValue.value[currentLang.value] = $event; + if (modelValue.value[currentLang] !== $event) { + modelValue.value[currentLang] = $event; context.emit('update:modelValue', modelValue.value); } } @@ -86,14 +88,28 @@ export default defineComponent({ const onClear = () => { modelValue.value = modelValue.value || {}; - if (modelValue.value[currentLang.value]) { - modelValue.value[currentLang.value] = ''; + if (modelValue.value[currentLang]) { + modelValue.value[currentLang] = ''; context.emit('update:modelValue', modelValue.value); } }; + function onTextChange($event: any) { + modelValue.value = modelValue.value || {}; + const txt = $event.target.value; + if (modelValue.value[currentLang] !== txt) { + modelValue.value[currentLang] = txt; + context.emit('update:modelValue', modelValue.value); + } + } + + function renderInput() { + return ; + } + return () => { - return ; export const propsResolver = createPropsResolver(languageTextBoxProps, languageTextboxSchema, schemaMapper, schemaResolver); + +export const propsResolverGenerator = getPropsResolverGenerator( + languageTextBoxProps, + languageTextboxSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/list-view/designer.ts b/packages/ui-vue/components/list-view/designer.ts index b127cef3b0c3f5f3dc8a672f3ff6486d4cb39750..59d3456896b99f460f9930604dd7aec1652fead0 100644 --- a/packages/ui-vue/components/list-view/designer.ts +++ b/packages/ui-vue/components/list-view/designer.ts @@ -14,29 +14,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { withInstall } from '@farris/ui-vue/components/common'; +import { RegisterContext, withInstall } from '@farris/ui-vue/components/common'; import FListView, { FListViewTable } from '@farris/ui-vue/components/list-view'; import { createCollectionBindingResolver } from '@farris/ui-vue/components/dynamic-resolver'; import FListViewDesign from './src/designer/list-view.design.component'; -import { propsResolver } from './src/designer/list-view.design.props'; +import { propsResolver, propsResolverGenerator } from './src/designer/list-view.design.props'; import FListViewTableDesign from './src/designer/list-view-table.design.component'; -import { propsResolver2 } from './src/list-view-table.props'; +import { propsResolver2, propsResolverGenerator2 } from './src/list-view-table.props'; export const bindingResolver = createCollectionBindingResolver(); export * from './src/designer/list-view.design.props'; -FListViewDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FListViewDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext) => { componentMap['list-view'] = FListView; - propsResolverMap['list-view'] = propsResolver; + propsResolverMap['list-view'] = propsResolverGenerator(registerContext); componentMap['list-view-table'] = FListViewTable; - propsResolverMap['list-view-table'] = propsResolver2; + propsResolverMap['list-view-table'] = propsResolverGenerator2(registerContext); resolverMap['list-view'] = { bindingResolver }; }; -FListViewDesign.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FListViewDesign.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext +) => { componentMap['list-view'] = FListViewDesign; - propsResolverMap['list-view'] = propsResolver; + propsResolverMap['list-view'] = propsResolverGenerator(registerContext); componentMap['list-view-table'] = FListViewTableDesign; - propsResolverMap['list-view-table'] = propsResolver2; + propsResolverMap['list-view-table'] = propsResolverGenerator2(registerContext); }; export { FListViewDesign }; diff --git a/packages/ui-vue/components/list-view/src/components/data/data-area.component.tsx b/packages/ui-vue/components/list-view/src/components/data/data-area.component.tsx index 628365b81f2f12e79434cd76ad23ce1774c9d9f2..03e901740d074fc9d6b75a8e9344fa1bf19960ee 100644 --- a/packages/ui-vue/components/list-view/src/components/data/data-area.component.tsx +++ b/packages/ui-vue/components/list-view/src/components/data/data-area.component.tsx @@ -1,7 +1,6 @@ import { computed, Ref, ref, SetupContext } from 'vue'; -import { UseDataView, UseGroupData, UseSelection, UseVisualData, VisualData } from '@farris/ui-vue/components/data-view'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { ListViewProps } from '../../list-view.props'; import getSingleItem from '../item/single-item.component'; import getContentItem from '../item/content-item.component'; @@ -9,6 +8,7 @@ import getDraggableItem from '../item/draggable-item.component'; import getGroupItem from '../item/group-item.component'; import { useRemove } from '../../composition/use-remove'; import { UseDraggable, UseHover, UseItem } from '../../composition/types'; +import { UseDataView, UseGroupData, UseSelection, UseVisualData, VisualData } from '@farris/ui-vue/components/data-view'; export default function ( props: ListViewProps, @@ -22,17 +22,9 @@ export default function ( useItemCompostion: UseItem, useDraggableComposition: UseDraggable ) { - const { t: getLocaleValue } = useI18n(); const listViewType = ref(props.view); const cardView = ref(props.view === 'CardView'); const clickItem = ref({}); - const emptyMessage = ref(getLocaleValue('listView.emptyMessage')); - - // const useDraggableComposition = useDraggable(props, context, dataViewComposition, - // useHoverComposition); - - // const useItemCompostion = useItem(props, context, visibleDatas, useDraggableComposition, useHoverComposition, useSelectionComposition); - const useRemoveComposition = useRemove(props, context, dataViewComposition, useItemCompostion); @@ -91,7 +83,7 @@ export default function (

    { props.emptyTemplate ? props.emptyTemplate() : context.slots.empty ? context.slots.empty() : - emptyMessage.value + LocaleService.getLocaleValue('listView.emptyMessage') }

    ); diff --git a/packages/ui-vue/components/list-view/src/components/item/content-item.component.tsx b/packages/ui-vue/components/list-view/src/components/item/content-item.component.tsx index c3486312191f1d1444f2ed6ba3968c0570960c05..dada42784486fa88435d82178c54429863839c06 100644 --- a/packages/ui-vue/components/list-view/src/components/item/content-item.component.tsx +++ b/packages/ui-vue/components/list-view/src/components/item/content-item.component.tsx @@ -20,7 +20,7 @@ export default function ( const disableField = ref(props.disableField); const textField = ref(props.textField); const { onMouseenterItem, onMouseoverItem, onMouseoutItem } = useHoverComposition; - const { getKey, listViewItemClass, listViewItemStyle, onCheckItem, onClickItem } = useItemCompostion; + const { getKey, listViewItemClass, listViewItemStyle, onCheckItem, onClickItem, updateSelectedItems } = useItemCompostion; function renderListViewItemContent(item: VisualData, index: number, selectedItem: any) { if (props.contentTemplate) { @@ -47,8 +47,11 @@ export default function ( {enableMultiSelect.value && (
    payload.stopPropagation()}> onCheckItem(item, index,!$event.checked)}> + disabled={item[disableField.value]} v-model:checked={item.checked} + onChange={($event: any) => { + onCheckItem(item, index, !$event.checked); + updateSelectedItems(); + }}>
    )}
    diff --git a/packages/ui-vue/components/list-view/src/composition/use-draggable.ts b/packages/ui-vue/components/list-view/src/composition/use-draggable.ts index 4946632d9c95be6314993fb888ba728ba65f4da1..f4689ad2404690b8c9a4f437773baf7c0e94dcdf 100644 --- a/packages/ui-vue/components/list-view/src/composition/use-draggable.ts +++ b/packages/ui-vue/components/list-view/src/composition/use-draggable.ts @@ -32,15 +32,14 @@ export function useDraggable( function createDragImage(originalElement: any) { const { left, top } = originalElement.getBoundingClientRect(); const customDragIcon = (originalElement.cloneNode(true)) as any; + customDragIcon.className=customDragIcon.className+' f-listview-drag-moving' // top:${document.documentElement.scrollTop + top}px; customDragIcon.style.cssText = ` position:absolute; left:${left}px; top:${document.documentElement.scrollTop ? top + document.documentElement.scrollTop: top}px; z-index: 999999; - border: 1px solid #e2e3e5; pointer-events: none; - background-color: #edf5ff; border-radius: 10px; margin: 4px 2px; display: flex; @@ -55,7 +54,7 @@ export function useDraggable( margin: 10px 0px 10px 14px; overflow: hidden;text-overflow: ellipsis;white-space: nowrap; `; - customDragIcon.children[1].style.cssText=`width: 30px;color: #f4625f;padding: 0 14px 0 0`; + customDragIcon.children[1].style.cssText=`width: 30px;padding: 0 14px 0 0`; customDragIcon.children[2].style.cssText=`padding: 0 14px 0 0;`; document.body.appendChild(customDragIcon); return customDragIcon; diff --git a/packages/ui-vue/components/list-view/src/composition/use-item.ts b/packages/ui-vue/components/list-view/src/composition/use-item.ts index d54ae7fd461ac58e17e07e5e72b6545659872c4d..a312b5542b7c4955b9467bb888c98d82dc4bcfb9 100644 --- a/packages/ui-vue/components/list-view/src/composition/use-item.ts +++ b/packages/ui-vue/components/list-view/src/composition/use-item.ts @@ -78,14 +78,14 @@ export function useItem( }; function listViewItemStyle(item: VisualData, index: number) { - const isSelectedRow = // 多选 - enableMultiSelect.value && isSelected(getKey(item, index)) || - // 单选 - (!enableMultiSelect.value && item.raw[identifyField.value] === currentSelectedDataId.value); + // const isSelectedRow = // 多选 + // enableMultiSelect.value && isSelected(getKey(item, index)) || + // // 单选 + // (!enableMultiSelect.value && item.raw[identifyField.value] === currentSelectedDataId.value); const styleObject: { [key: string]: any } = {}; - if (isSelectedRow) { - styleObject.backgroundColor = '#dae9ff'; - } + // if (isSelectedRow) { + // styleObject.backgroundColor = '#dae9ff'; + // } if (typeof props.itemStyle === 'string') { return getCustomStyle(styleObject, props.itemStyle); } @@ -130,6 +130,7 @@ export function useItem( $event?.stopPropagation(); return; } + currentSelectedDataId.value = item.raw[identifyField.value]; focusedItemId.value = item.raw[identifyField.value]; activeIndex.value = index; if (shouldClearSelectionOnClick.value) { diff --git a/packages/ui-vue/components/list-view/src/designer/list-view.design.props.ts b/packages/ui-vue/components/list-view/src/designer/list-view.design.props.ts index d9f8db6116b2a34c5f363e930cd8c1d6e84be743..7c6d1b553e8aad15e33c3e54edd3d2edb141f973 100644 --- a/packages/ui-vue/components/list-view/src/designer/list-view.design.props.ts +++ b/packages/ui-vue/components/list-view/src/designer/list-view.design.props.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import { ExtractPropTypes } from 'vue'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { listViewProps } from '@farris/ui-vue/components/list-view'; import { excludeProperties } from '@farris/ui-vue/components/common'; import { schemaMapper } from '../schema/schema-mapper'; @@ -25,3 +25,9 @@ import listViewSchema from '../schema/list-view.schema.json'; export const listViewDesignProps = excludeProperties(listViewProps, 'headerTemplate'); export type ListViewDesignProps = ExtractPropTypes; export const propsResolver = createPropsResolver(listViewDesignProps, listViewSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator( + listViewDesignProps, + listViewSchema, + schemaMapper, + schemaResolver +); \ No newline at end of file diff --git a/packages/ui-vue/components/list-view/src/designer/use-list-view-creator.ts b/packages/ui-vue/components/list-view/src/designer/use-list-view-creator.ts index 3473e271544910b18493b0537b8ffc4f3dd7bdbc..b71a31be4b71a2ea9013c90a83e87664f1fb5a0b 100644 --- a/packages/ui-vue/components/list-view/src/designer/use-list-view-creator.ts +++ b/packages/ui-vue/components/list-view/src/designer/use-list-view-creator.ts @@ -54,7 +54,7 @@ export function useListViewCreator( } // 1、将表格拖入无标题的目标区域,需要给表格追加Section容器。并给Section添加新增删除按钮 - const parentContainerWithoutTitle = [DgControl['content-container'].type, DgControl['response-layout-item'].type, DgControl['splitter-pane'].type]; + const parentContainerWithoutTitle = [DgControl['content-container'].type, DgControl['response-layout-item'].type, DgControl['splitter-pane'].type, DgControl['drawer'].type]; if (parentContainerType && parentContainerWithoutTitle.includes(parentContainerType)) { const containerSection = resolver.getSchemaByType( 'section', @@ -76,11 +76,17 @@ export function useListViewCreator( */ function createListViewComponentContents(buildInfo: ComponentBuildInfo) { const listView = resolver.getSchemaByType('list-view') as ComponentSchema; - // listview 采用固定高度 + + // 目标区域为页面内容主区域时,listview控件采用填充高度,其余场景listview控件采用固定高度 + let isInListMainContainer = false; + const parentClass = buildInfo.parentComponentInstance?.schema?.appearance?.class; + if (parentClass && parentClass.includes('f-page-main')) { + isInListMainContainer = true; + } Object.assign(listView, { id: buildInfo.componentId + '-listView', appearance: { - class: 'f-grid-is-sub' + class: isInListMainContainer ? '' : 'f-grid-is-sub' }, type: 'list-view', dataSource: buildInfo.dataSource || '', @@ -94,11 +100,21 @@ export function useListViewCreator( function createComponentNode(buildInfo: ComponentBuildInfo): any { const componentNode = resolver.getSchemaByType('component') as ComponentSchema; const contents = createListViewComponentContents(buildInfo); + + // 目标区域为页面内容主区域时,listview控件采用填充高度,其余场景listview控件采用固定高度 + let isInListMainContainer = false; + const parentClass = buildInfo.parentComponentInstance?.schema?.appearance?.class; + if (parentClass && parentClass.includes('f-page-main')) { + isInListMainContainer = true; + } Object.assign(componentNode, { id: `${buildInfo.componentId}-component`, viewModel: `${buildInfo.componentId}-component-viewmodel`, componentType: buildInfo.componentType, - contents + contents, + appearance: { + class: isInListMainContainer ? 'f-struct-wrapper f-utils-fill-flex-column' : '' + } }); return componentNode; } diff --git a/packages/ui-vue/components/list-view/src/list-view-table.props.ts b/packages/ui-vue/components/list-view/src/list-view-table.props.ts index 5a320e081ab6fde529f226e78cad4f921104aa36..ec40848df5ac1350eed57a77905165e7cc646523 100644 --- a/packages/ui-vue/components/list-view/src/list-view-table.props.ts +++ b/packages/ui-vue/components/list-view/src/list-view-table.props.ts @@ -15,7 +15,7 @@ * limitations under the License. */ import { ExtractPropTypes } from 'vue'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; import listViewTableSchema from './schema/list-view-table.schema.json'; @@ -27,3 +27,9 @@ export const listViewTableProps = { export type ListViewTableProps = ExtractPropTypes; export const propsResolver2 = createPropsResolver(listViewTableProps, listViewTableSchema, schemaMapper, schemaResolver, propertyConfig); +export const propsResolverGenerator2 = getPropsResolverGenerator( + listViewTableProps, + listViewTableSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/list-view/src/list-view.component.tsx b/packages/ui-vue/components/list-view/src/list-view.component.tsx index d96230270ae89a8eb2d0ae134ea0b57b30ff4a1d..51c04ece571cc894c6a920d60230b03a9ba3d8f5 100644 --- a/packages/ui-vue/components/list-view/src/list-view.component.tsx +++ b/packages/ui-vue/components/list-view/src/list-view.component.tsx @@ -53,7 +53,7 @@ export default defineComponent({ const dataView = useDataView(props as DataViewOptions, new Map(), useFilterComposition, useHierarchyCompostion, useIdentifyComposition); const useSelectionComposition = useSelection(props as DataViewOptions, dataView, useIdentifyComposition, visibleDatas, context); const useSearchComposition = useSearch(props, listViewContentRef); - const usePaginationComposition = usePagination(props, dataView); + const usePaginationComposition = usePagination(props as DataViewOptions, dataView); const visibleCapacity = computed(() => { return dataView.dataView.value.length; }); @@ -264,7 +264,7 @@ export default defineComponent({ context.slots.footer && context.slots.footer() }
    } { - // shouldRenderPagination.value && renderDataGridPagination() + // shouldRenderPagination.value && renderDataGridPagination() }
    ); diff --git a/packages/ui-vue/components/loading/src/loading.component.tsx b/packages/ui-vue/components/loading/src/loading.component.tsx index 0577508a869b5f53b75b48c3d611a946d4466071..65a3f07af7fbc9b96e78226f0c56b7e8bad85d39 100644 --- a/packages/ui-vue/components/loading/src/loading.component.tsx +++ b/packages/ui-vue/components/loading/src/loading.component.tsx @@ -19,8 +19,6 @@ import { defineComponent, SetupContext, ref, onMounted, watch, computed } from ' import { LoadingProps, loadingProps } from './loading.props'; import { LOADING_STYLES } from './composition/types'; -import { useI18n } from 'vue-i18n'; - export default defineComponent({ name: 'FLoading', props: loadingProps, diff --git a/packages/ui-vue/components/loading/src/loading.props.ts b/packages/ui-vue/components/loading/src/loading.props.ts index 4b3f81bae7630fcb519047cfcd2d26c58fa65099..d7dfdec049ee63af096f0a34ac71f447611d03ca 100644 --- a/packages/ui-vue/components/loading/src/loading.props.ts +++ b/packages/ui-vue/components/loading/src/loading.props.ts @@ -14,6 +14,7 @@ * limitations under the License. */ import { ExtractPropTypes, PropType } from 'vue'; +import { useMobile } from '../../data-view/composition/use-mobile'; export const loadingProps = { /** 是否展示文案 */ @@ -37,9 +38,11 @@ export const loadingProps = { export type LoadingProps = Partial>; +const { isMobilePhone } = useMobile(); + export const DefaultLoadingProps: LoadingProps = { showMessage: true, - message: '正在加载,请稍候...', + message: isMobilePhone() ? '正在加载...' : '正在加载,请稍候...', isActive: false, width: 30, type: 0, diff --git a/packages/ui-vue/components/loading/src/loading.service.tsx b/packages/ui-vue/components/loading/src/loading.service.tsx index eb0e91562efe7773cebe5d18a45fb6baba4833ce..e52c0a117c260bf7d783ab92feb0b88cf0098471 100644 --- a/packages/ui-vue/components/loading/src/loading.service.tsx +++ b/packages/ui-vue/components/loading/src/loading.service.tsx @@ -3,7 +3,6 @@ import { App, Ref, createApp, onMounted, onUnmounted, ref } from 'vue'; import FLoading from './loading.component'; import { DefaultLoadingProps, LoadingProps } from './loading.props'; import { LocaleService } from '@farris/ui-vue/components/locale'; -import { useI18n } from 'vue-i18n'; let currentLoadingInstanceID = -1; const loadingInstances: { [key: number]: Ref } = {}; @@ -23,10 +22,9 @@ function initInstance(props?: any): Ref> { currentLoadingInstanceID = newInstanceId; const appInstance = createApp({ setup() { - const { t } = useI18n(); const message = ref(props.message); if (message.value === '正在加载,请稍候...') { - message.value = t('loading.message'); + message.value = LocaleService.getLocaleValue('loading.message'); } const options = { ...props, message: message.value}; diff --git a/packages/ui-vue/components/locale/index.ts b/packages/ui-vue/components/locale/index.ts index 6554e3800dcd868c8933e30bfccd6984e5615c1b..321b1e51bb2f7e14ca678810a4d5495230fdee38 100644 --- a/packages/ui-vue/components/locale/index.ts +++ b/packages/ui-vue/components/locale/index.ts @@ -14,9 +14,9 @@ * limitations under the License. */ import type { App, Plugin } from 'vue'; -import { LocaleService, LOCALE_SERVICE_INJECTION_TOKEN, LocaleConfig } from './src/lib'; +import { LocaleService, LOCALE_SERVICE_INJECTION_TOKEN, LocaleConfig, useResourceLoader } from './src/lib'; -export { LocaleService, LOCALE_SERVICE_INJECTION_TOKEN, type LocaleConfig }; +export { LocaleService, LOCALE_SERVICE_INJECTION_TOKEN, type LocaleConfig, useResourceLoader }; export default { async install(app: App, config?: LocaleConfig): Promise { diff --git a/packages/ui-vue/components/locale/src/lib/index.ts b/packages/ui-vue/components/locale/src/lib/index.ts index b0123d2090b1fb0f1c742add2b9301249a0879a9..c413c7b823ecfe37e27525b46b3521581dc42c3d 100644 --- a/packages/ui-vue/components/locale/src/lib/index.ts +++ b/packages/ui-vue/components/locale/src/lib/index.ts @@ -1,2 +1,3 @@ export * from './types'; -export * from './locale.service'; \ No newline at end of file +export * from './locale.service'; +export * from './composition'; diff --git a/packages/ui-vue/components/locale/src/lib/locale.service.ts b/packages/ui-vue/components/locale/src/lib/locale.service.ts index fb9f0ce45e44ed7a6e1cce379edecce03118800f..a8287b8b6c7cc6224fe74ea86347f61421c2fd33 100644 --- a/packages/ui-vue/components/locale/src/lib/locale.service.ts +++ b/packages/ui-vue/components/locale/src/lib/locale.service.ts @@ -13,10 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createI18n, I18n, useI18n } from 'vue-i18n'; -import { DEFAULT_LOCALE_CONFIG, LocaleConfig, LocaleResources } from './types'; import { App } from 'vue'; +import { Composer, createI18n, I18n } from 'vue-i18n'; +import { DEFAULT_LOCALE_CONFIG, LocaleConfig, LocaleResources } from './types'; import { useResourceLoader } from './composition'; +import zhsLocale from '../../../../public/assets/i18n/ui/zh-CHS.json'; +import enLocale from '../../../../public/assets/i18n/ui/en.json'; +import zhtLocale from '../../../../public/assets/i18n/ui/zh-CHT.json'; +const defaultLocaleResource = { + 'zh-CHS': zhsLocale, + en: enLocale, + 'zh-CHT': zhtLocale +}; export class LocaleService { public static i18n: I18n; private static config: LocaleConfig; @@ -30,16 +38,41 @@ export class LocaleService { legacy: false, globalInjection: true, silentTranslationWarn: true, - silentFallbackWarn: true + silentFallbackWarn: true, + missing: (defaultLocale: string, key: string) => { + return LocaleService.getDefaultLocaleValue(locale, key); + } }); app.use(LocaleService.i18n); await this.loadResources(); } + /** + * 获取当前语言 + * @returns + */ public static getLocale() { - return LocaleService.i18n && LocaleService.i18n.global.locale; + return LocaleService.i18n && (LocaleService.i18n.global as Composer).locale.value || 'zh-CHS'; } - public static getLocaleValue(key: string) { - return LocaleService.i18n && LocaleService.i18n.global.te(key) || key; + + /** + * 默认加载的多语言值,返回中文值 + * @param key + * @returns + */ + public static getDefaultLocaleValue(locale: string, key: string) { + const resource = defaultLocaleResource[locale] || defaultLocaleResource['zh-CHS']; + const keyList = key.split('.'); + const result = keyList.reduce((total: any, next: any) => { + return total[next]; + }, resource); + return result; + } + public static getLocaleValue(key: string, options?: any) { + if (LocaleService.i18n) { + return (LocaleService.i18n.global as Composer).t(key, options); + } + const locale = LocaleService.getLocale(); + return LocaleService.getDefaultLocaleValue(locale, key); } /** * 属性空值或者有值但等于默认值,返回默认值多语言属性;有值但不相等返回当前属性自有值 @@ -49,9 +82,8 @@ export class LocaleService { * @returns */ public static getRealPropertyValue(propertyValue, defaultValue, localeKey) { - const { t } = useI18n(); if (!propertyValue || propertyValue === defaultValue) { - return t(localeKey); + return LocaleService.getLocaleValue(localeKey); } return propertyValue } diff --git a/packages/ui-vue/components/lookup/index.ts b/packages/ui-vue/components/lookup/index.ts index 6ca15df391a1b1feeb47b724b8c6942490d534e3..065b7cdb1df835adfa04126faec6241ee728aeac 100644 --- a/packages/ui-vue/components/lookup/index.ts +++ b/packages/ui-vue/components/lookup/index.ts @@ -17,10 +17,11 @@ import type { App, Plugin } from 'vue'; import FLookupDesign from './src/designer/lookup.design.component'; import FLookup from './src/lookup.component'; -import { callbackResolver, propsResolver } from './src/lookup.props'; +import { callbackResolver, propsResolver, propsResolverGenerator } from './src/lookup.props'; import { LookupSchemaRepositoryToken, getLookupEditorCommonProperties, getLookupDialogCommonProperties, getLookupPaginationProperties } from './src/property-config/lookup.property-config'; import './lookup-style.scss'; import { lookupDataSourceConverter } from './src/property-config/converters/lookup-property.converter'; +import { RegisterContext } from '../common'; export { ExternalLookupPropertyConfig } from './src/property-config/external-lookup.property-config'; export * from './src/lookup.props'; @@ -29,15 +30,18 @@ export * from './src/composition/types'; FLookup.install = (app: App) => { app.component(FLookup.name as string, FLookup); }; -FLookup.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FLookup.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext) => { componentMap.lookup = FLookup; - propsResolverMap.lookup = propsResolver; + propsResolverMap.lookup = propsResolverGenerator(registerContext); resolverMap.lookup = { callbackResolver }; }; -FLookup.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FLookup.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext +) => { componentMap.lookup = FLookupDesign; - propsResolverMap.lookup = propsResolver; + propsResolverMap.lookup = propsResolverGenerator(registerContext); }; export { FLookup, LookupSchemaRepositoryToken, lookupDataSourceConverter, getLookupEditorCommonProperties, getLookupDialogCommonProperties, getLookupPaginationProperties }; diff --git a/packages/ui-vue/components/lookup/lookup-style.scss b/packages/ui-vue/components/lookup/lookup-style.scss index 94f25299746f7bbafaceebf243e4466d4cdd14e7..fbb307e05bb559a7f39fff4b900a91e0dc5e7f0a 100644 --- a/packages/ui-vue/components/lookup/lookup-style.scss +++ b/packages/ui-vue/components/lookup/lookup-style.scss @@ -21,9 +21,9 @@ } &-tabs-container { - background: #EFF5FF; + background: var(--f-aid-04); border-radius: 17px; - color: #003f90; + color: var(--f-theme-03); cursor: pointer; align-items: center; @@ -36,7 +36,7 @@ margin: 2px; height: 28px; background: rgba(255, 255, 255, .9); - box-shadow: 0 4px 18px 0 rgba(42, 135, 255, .2); + box-shadow: 0 4px 18px 0 rgba(var(--f-theme-03-rgb), .2); border-radius: 15px } diff --git a/packages/ui-vue/components/lookup/src/components/lookup-container.component.tsx b/packages/ui-vue/components/lookup/src/components/lookup-container.component.tsx index a48d087248bae30c7531a869347e1061c8456849..a72b4cc48ace3a514fb2af0a637373a3405e6a23 100644 --- a/packages/ui-vue/components/lookup/src/components/lookup-container.component.tsx +++ b/packages/ui-vue/components/lookup/src/components/lookup-container.component.tsx @@ -12,6 +12,7 @@ import { useNavigation } from "../composition/use-navigation"; import { LOOKUP_HTTP_COMPOSITION, UseHttpComposition } from "../composition/use-http"; import { DefaultDialogTitle, LOOKUP_ACTIVE_TAB, LookupHttpResult, LookupTabs } from "../composition/types"; import { LOOKUP_LOCALES, LookupLocaleData } from "../composition/use-locales"; +import { isMobilePhone } from '@farris/ui-vue/components/common'; export default defineComponent({ name: "LookupContainer", @@ -35,8 +36,10 @@ export default defineComponent({ const renderDataComponent = computed(() => { if (isTreeList()) { + // unWatchDatagrid(); return renderTreeGrid; } + // unWatchTreegrid(); return renderDataGrid; }); @@ -89,7 +92,7 @@ export default defineComponent({ params.search = {field: '*', value: queryState.value, type: 'like'}; params.action = 'search'; - searchState.default = {field: '*', value: queryState.value, type: 'like'}; + // searchState.default = {field: '*', value: queryState.value, type: 'like'}; } useHttpComposition?.loadData(params, (result: LookupHttpResult) => { @@ -112,7 +115,7 @@ export default defineComponent({ return () => { return ( - {isDoubleList() && props.showNavigation && + {isDoubleList() && props.showNavigation && !isMobilePhone() && {renderNavigation()} } diff --git a/packages/ui-vue/components/lookup/src/components/modal-container.component.tsx b/packages/ui-vue/components/lookup/src/components/modal-container.component.tsx index f13766ef2134b810967fe3416fa127a23dccb16d..df4c4d9067fd507d68d835b359d82bae6f076dda 100644 --- a/packages/ui-vue/components/lookup/src/components/modal-container.component.tsx +++ b/packages/ui-vue/components/lookup/src/components/modal-container.component.tsx @@ -11,6 +11,7 @@ import CascadeControl from './cascade/tree-cascade.component'; import { LOOKUP_TREEROW_OPTIONS, useTreeRowOptions } from "../composition/use-treegrid-row-options"; import { LOOKUP_LOCALES } from "../composition/use-locales"; +import { isMobilePhone } from "@farris/ui-vue/components/common"; export default defineComponent({ name: 'FLookupModalContainer', @@ -63,7 +64,7 @@ export default defineComponent({ const showIncludeChildNodes = computed(() => { return includeChildNodesInfo.show && activeTab.value === LookupTabs.dataList - && lookupOptions.displayType && isNavigation.value; + && lookupOptions.displayType && isNavigation.value && !isMobilePhone(); }); const popoverOffsetX = computed(() => { @@ -171,7 +172,7 @@ export default defineComponent({ {(dialogOptions.showMaxButton || dialogOptions.showCloseButton) &&
    event.stopPropagation()}> - {dialogOptions.showMaxButton && + {dialogOptions.showMaxButton && !isMobilePhone() && } {dialogOptions.showCloseButton && } diff --git a/packages/ui-vue/components/lookup/src/composition/use-datagrid.tsx b/packages/ui-vue/components/lookup/src/composition/use-datagrid.tsx index e992544333ab0a5a34cddfee4f0ba7949abe052d..da0b7c7ec0207e8fa6a6953773d74a8e6a49c2af 100644 --- a/packages/ui-vue/components/lookup/src/composition/use-datagrid.tsx +++ b/packages/ui-vue/components/lookup/src/composition/use-datagrid.tsx @@ -19,7 +19,7 @@ export function useDatagrid(props: LookupProps, context: SetupContext, navigatio const lookupSelectionsManager = inject(LOOKUP_SELECTIONS_MANAGER) as LookupSelectionsManager; const { lookupStates, loadData, includeChilds, getPathCode, getChildNodes } = useHttpComposition; - const { lookupState, navigationState, pageInfoState, searchState, lookupOptions } = lookupStates; + const { lookupState, navigationState, pageInfoState, searchState, lookupOptions, queryState } = lookupStates; const { checkPaination, checkMultiSelect, checkColumnOptions, navIsTreeList, isLoadAll, isPathCodeTree } = useCheckProps(props, lookupStates); @@ -43,7 +43,7 @@ export function useDatagrid(props: LookupProps, context: SetupContext, navigatio const currentPaginationOptions = ref(pageInfoState.default); const { setColumns } = useFavorite(props, lookupOptions.idField); - const lookupDatagridClass = ref({ 'lookup-datagrid': true, 'lookup-datagrid-empty': false }); + const lookupDatagridClass = ref('lookup-datagrid'); watch(() => pageInfoState.default, (newPageInfo) => { currentPaginationOptions.value = newPageInfo; @@ -62,7 +62,7 @@ export function useDatagrid(props: LookupProps, context: SetupContext, navigatio } updatePageInfo(value); loadAndSelect(datagridRef.value, items || []); - lookupDatagridClass.value['lookup-datagrid-empty'] = !(items && items.length); + lookupDatagridClass.value = 'lookup-datagrid' + (!(items && items.length) ? ' lookup-datagrid-empty' : ''); } const relation = computed(() => { @@ -121,6 +121,9 @@ export function useDatagrid(props: LookupProps, context: SetupContext, navigatio function httpRequest() { const queryParams = { search: searchState.default, action: 'list' }; + if (!searchState.default && queryState.value) { + queryParams.search = {field: '*', value: queryState.value, type: 'like'}; + } if (props.openType === 'Modal' && relation.value) { Object.assign(queryParams, relation.value); } @@ -190,7 +193,7 @@ export function useDatagrid(props: LookupProps, context: SetupContext, navigatio } function renderDataGrid() { - return props.textChangeType === 'blur' || props.textChangeType === 'any'); const changeOnEnter = computed(() => props.textChangeType === 'enter' || props.textChangeType === 'any'); - const { beforeOpenDialog, updateModelValue, selectedItems, openDialog, lookupOptions, useHttpComposition, isPopuped, usePopupComposition } = options; + const { beforeOpenDialog, updateModelValue, selectedItems, openDialog, lookupOptions, useHttpComposition, isPopuped, usePopupComposition, modelValue } = options; const { updateSearchFieldTitle } = useHttpComposition; const { lookupState, queryState, searchValueChanged } = useHttpComposition.lookupStates; const isClear = ref(false); @@ -59,6 +59,15 @@ export function useInputChange(props: LookupProps, context: any, options: Lookup }; if (searchValueChanged.value) { + if (props.openType === 'Modal') { + const continueOpen = await beforeOpenDialog(); + if(!continueOpen) { + modelValue.value = props.modelValue; + queryState.value = null; + return; + } + } + document.body.classList.add("lookup-modal-open"); useHttpComposition.loadData(searchParams, async (data: LookupHttpResult) => { if (data.searchFields && data.columns) { @@ -71,7 +80,7 @@ export function useInputChange(props: LookupProps, context: any, options: Lookup if (onlyOne) { selectedItems.value = [onlyOne]; updateModelValue(); - queryState.value = ''; + queryState.value = null; document.body.classList.remove('lookup-modal-open'); return; } @@ -109,8 +118,15 @@ export function useInputChange(props: LookupProps, context: any, options: Lookup } if (props.openType !== 'Modal' && !isPopuped.value) { - openDialog(); - searchValueChanged.value = false; + beforeOpenDialog().then((continueOpen) => { + if(!continueOpen) { + modelValue.value = props.modelValue; + queryState.value = null; + } else { + openDialog(); + searchValueChanged.value = false; + } + }); return; } diff --git a/packages/ui-vue/components/lookup/src/composition/use-locales.ts b/packages/ui-vue/components/lookup/src/composition/use-locales.ts index a132c9a4fc7086f52aaa4b123a442f9bedba98c3..22ee4dff1ceda5a30bbe36ed53356db95a473fee 100644 --- a/packages/ui-vue/components/lookup/src/composition/use-locales.ts +++ b/packages/ui-vue/components/lookup/src/composition/use-locales.ts @@ -1,5 +1,5 @@ import { LookupProps } from "../lookup.props"; -import { useI18n } from 'vue-i18n'; +import { LocaleService} from '@farris/ui-vue/components/locale'; export const LOOKUP_LOCALES = Symbol('FARRIS_LOOKUP_LOCALES'); @@ -39,7 +39,7 @@ export interface LookupLocaleData { } export function useLookupLocales(props: LookupProps): LookupLocaleData { - const { t: getLocaleValue } = useI18n(); + const { getLocaleValue } = LocaleService; function getValue(propertyValue, defaultValue, localeKey) { if (propertyValue === defaultValue) { diff --git a/packages/ui-vue/components/lookup/src/composition/use-navigation.tsx b/packages/ui-vue/components/lookup/src/composition/use-navigation.tsx index 52df55d1ac28c8ce5792118d1382b39664632427..c1e3099a7b0ec52045d6b7563542cb8bd4cdc425 100644 --- a/packages/ui-vue/components/lookup/src/composition/use-navigation.tsx +++ b/packages/ui-vue/components/lookup/src/composition/use-navigation.tsx @@ -98,10 +98,16 @@ export function useNavigation(props: LookupProps, context) { function onTreegridSelectionChange(items: any[]) { selectedItems.value = items; + if (!items || !items.length) { + leftTreegridRef.value?.activeRowById(''); + } } function onDatagridSelectionChange(items: any[]) { selectedItems.value = items; + if (!items || !items.length) { + leftDatagridRef.value?.activeRowById(''); + } } function httpRequest() { diff --git a/packages/ui-vue/components/lookup/src/composition/use-pageinfo.ts b/packages/ui-vue/components/lookup/src/composition/use-pageinfo.ts index f90a7d79bf5649975ab8cc88ed19f1a9b5a4525d..5baf87a5689d050191afad43ab8ae881492e9ec2 100644 --- a/packages/ui-vue/components/lookup/src/composition/use-pageinfo.ts +++ b/packages/ui-vue/components/lookup/src/composition/use-pageinfo.ts @@ -3,12 +3,16 @@ import { LookupProps } from "../lookup.props"; export function usePageInfo(props: LookupProps, pageInfoState){ - watch(() => props.pagination, (newValue) => { - pageInfoState.default = newValue; + watch(() => props.pagination, (newValue, oldValue) => { + if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) { + pageInfoState.default = newValue; + } }); - watch(() => props.navigation?.pagination, (newValue) => { - pageInfoState.navigation = newValue; + watch(() => props.navigation?.pagination, (newValue, oldValue) => { + if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) { + pageInfoState.navigation = newValue; + } }); function updatePageInfo(newPageInfo: Record, isNavigation = false) { diff --git a/packages/ui-vue/components/lookup/src/composition/use-popup.tsx b/packages/ui-vue/components/lookup/src/composition/use-popup.tsx index d54c8c5c482aa9260a9a0667901f8e506945f57b..f09f5b0ecbcc0c12144923e0d217af306c486efd 100644 --- a/packages/ui-vue/components/lookup/src/composition/use-popup.tsx +++ b/packages/ui-vue/components/lookup/src/composition/use-popup.tsx @@ -99,7 +99,7 @@ export function usePopup(props: LookupProps, context, elementRef: Ref, { /** 弹窗隐藏后调用 */ async function onHiddenPopup() { isPopuped.value && hidePopup(); - queryState.value = ''; + queryState.value = null; destroyed(); context.emit('popupClosed'); popupState.canClose = true; diff --git a/packages/ui-vue/components/lookup/src/composition/use-treegrid.tsx b/packages/ui-vue/components/lookup/src/composition/use-treegrid.tsx index 7c5738fb72aba6df1c15326948a926ef64dba1d3..cdc134693feb468f41b27fd7fa50474f1ac48c7c 100644 --- a/packages/ui-vue/components/lookup/src/composition/use-treegrid.tsx +++ b/packages/ui-vue/components/lookup/src/composition/use-treegrid.tsx @@ -22,7 +22,7 @@ export function useTreegrid(props: LookupProps, context: SetupContext) { const useHttpComposition = inject(LOOKUP_HTTP_COMPOSITION) as UseHttpComposition; const { lookupStates } = useHttpComposition; - const { lookupState, searchState, lookupOptions } = lookupStates; + const { lookupState, searchState, lookupOptions, queryState } = lookupStates; // const treeRowOptions = useTreeRowOptions(lookupOptions, false); const treeRowOptions = inject(LOOKUP_TREEROW_OPTIONS) as any; @@ -123,8 +123,18 @@ export function useTreegrid(props: LookupProps, context: SetupContext) { } } + function getQueryParams(action: string) { + const queryParams = { search: searchState.default, action }; + if (!searchState.default && queryState.value) { + queryParams.search = {field: '*', value: queryState.value, type: 'like'}; + } + + return queryParams; + } + function httpRequest(action = 'list') { - loadData({ search: searchState.default, action }, (result: LookupHttpResult) => { + const queryParams =getQueryParams(action); + loadData(queryParams, (result: LookupHttpResult) => { treeGridLoadData(result.items, action === 'search'); }); } @@ -155,7 +165,7 @@ export function useTreegrid(props: LookupProps, context: SetupContext) { // GET CHILDREN DATA const parentId = treeNode.raw.id; - const params = buildGetChildrenQueryParams(treeNode, searchState.default as any); + const params = buildGetChildrenQueryParams(treeNode, searchState.default as any || (queryState.value ? {field: '*', value: queryState.value, type: 'like'} : undefined)); return getData(params).then((result: LookupHttpResult) => { loadChildNodes(result?.items ?? [], parentId, treegridRef.value); }); diff --git a/packages/ui-vue/components/lookup/src/composition/use-treenode.ts b/packages/ui-vue/components/lookup/src/composition/use-treenode.ts index f1f459c6df1141ec2518fa12f9816b425bcc2dde..db53096fffe077aa415697b5a47a9cd4640ab264 100644 --- a/packages/ui-vue/components/lookup/src/composition/use-treenode.ts +++ b/packages/ui-vue/components/lookup/src/composition/use-treenode.ts @@ -147,7 +147,7 @@ export function useTreeNode(lookupStates: LookupStates, useCheckPropsComposition let parentNode: TreeNode | null = null; for (const id of parentIds) { - const foundNode = currentLevel.find(node => node.data.id === id); + const foundNode = currentLevel.find(node => node.data[lookupOptions.idField] === id); if (!foundNode) { return null; // 路径不存在,返回null } diff --git a/packages/ui-vue/components/lookup/src/lookup.component.tsx b/packages/ui-vue/components/lookup/src/lookup.component.tsx index e192b6b351d5fbf8467afc107aec5b4fb9eab704..bb08fba707db0dce9f536a7346442ad793eb2f9b 100644 --- a/packages/ui-vue/components/lookup/src/lookup.component.tsx +++ b/packages/ui-vue/components/lookup/src/lookup.component.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ import { computed, defineComponent, inject, onMounted, onUnmounted, reactive, ref, watch } from 'vue'; - import { F_MODAL_SERVICE_TOKEN, FModalService } from '@farris/ui-vue/components/modal'; import { FButtonEdit } from '@farris/ui-vue/components/button-edit'; @@ -35,13 +34,13 @@ import { useContext } from './composition/use-context'; import { usePopup } from './composition/use-popup'; import { debounce } from 'lodash-es'; import { useLookupLocales } from './composition/use-locales'; - +import { isMobilePhone } from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FLookup', props: lookupProps, emits: ['update:modelValue', 'update:idValue', 'search', 'navSelectionsChanged', 'pageIndexChanged', 'pageSizeChanged', - 'resize', 'dialogClosed', 'textChanged', 'clear', 'update:dataMapping', 'clearMapping'], + 'resize', 'dialogClosed', 'textChanged', 'clear', 'update:dataMapping', 'clearMapping', 'popupClosed'], setup(props: LookupProps, context) { const modalIcon = ''; const popupIcon = ''; @@ -54,6 +53,9 @@ export default defineComponent({ const openType = computed(() => { + if (isMobilePhone()) { + return 'Modal'; + } return props.openType === 'Popup' ? 'Popup' : 'Modal'; }); @@ -98,7 +100,7 @@ export default defineComponent({ const { renderPopup, showPopup, isPopuped, hidePopup, onBeforeClosePopup } = usePopupComposition; function openDialog() { - if (props.openType === 'Popup') { + if (openType.value === 'Popup') { showPopup(); return; } @@ -158,7 +160,7 @@ export default defineComponent({ async function onClear() { context.emit('update:modelValue', ''); updateIdValue(''); - queryState.value = ''; + queryState.value = null; if (openType.value === 'Popup' && isPopuped.value) { // isPopuped.value && elementRef.value?.forceClosePopup(); diff --git a/packages/ui-vue/components/lookup/src/lookup.props.ts b/packages/ui-vue/components/lookup/src/lookup.props.ts index c33a611d680adc762d1e57fcda3b19e0e6bf69eb..ed3139876bf59561a92b6bab5f45bb5b3b3927a2 100644 --- a/packages/ui-vue/components/lookup/src/lookup.props.ts +++ b/packages/ui-vue/components/lookup/src/lookup.props.ts @@ -1,5 +1,5 @@ import { ExtractPropTypes, PropType } from "vue"; -import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { createPropsResolver, getPropsResolverGenerator } from "@farris/ui-vue/components/dynamic-resolver"; // import lookupDefaultConfig from './property-config/lookup.property-config.json'; import { schemaMapper } from './schema/schema-mapper'; @@ -64,7 +64,7 @@ export const lookupProps = { userDataKey: {type: String, default: ''}, onReady: { type: Function, default: null }, context: { type: Object, default: {} }, - placeholder: { type: String, default: '' }, + placeholder: { type: String, default: '请选择' }, loader: { type: Function, default: null }, allowFreeInput: { type: Boolean, default: false }, onlySelectLeaf: { type: Boolean, default: false }, @@ -92,3 +92,9 @@ export type LookupProps = ExtractPropTypes; export const propsResolver = createPropsResolver(lookupProps, lookupSchema,schemaMapper, schemaResolver); export const callbackResolver = createLookupCallbackResolver(); +export const propsResolverGenerator = getPropsResolverGenerator( + lookupProps, + lookupSchema, + schemaMapper, + schemaResolver +); \ No newline at end of file diff --git a/packages/ui-vue/components/lookup/src/property-config/converters/lookup-property.converter.ts b/packages/ui-vue/components/lookup/src/property-config/converters/lookup-property.converter.ts index f7eb2471d8b6f1723fa3fc9c33053d5bbb65837a..914f446fafe1d864d7cc2f037eee9475c3338c93 100644 --- a/packages/ui-vue/components/lookup/src/property-config/converters/lookup-property.converter.ts +++ b/packages/ui-vue/components/lookup/src/property-config/converters/lookup-property.converter.ts @@ -1,4 +1,5 @@ import { DefaultDialogTitle } from "../../composition/types"; +import { isNil } from 'lodash-es'; export function getPageList(limitStr) { let pageSizeList: any = [10, 20, 30, 50, 100]; @@ -35,7 +36,7 @@ export const lookupDefaultConverter = { } if (propertyKey === 'placeholder') { - return options['placeholder'] || '请选择'; + return isNil(options[propertyKey]) ? '请选择' : options[propertyKey]; } if (propertyKey === 'openType') { diff --git a/packages/ui-vue/components/lookup/src/property-config/lookup.property-config.ts b/packages/ui-vue/components/lookup/src/property-config/lookup.property-config.ts index 19776e1abc06669ef539a26ce5218a33f0c76978..fc0fa3758276b746ba662e13fcbbf8afe67a1779 100644 --- a/packages/ui-vue/components/lookup/src/property-config/lookup.property-config.ts +++ b/packages/ui-vue/components/lookup/src/property-config/lookup.property-config.ts @@ -52,7 +52,7 @@ export function getLookupEditorCommonProperties(editorOptions, propsUtilsService repositoryToken: LookupSchemaRepositoryToken, }, dataSourceConverter: lookupDataSourceConverter, - defaultConverter:lookupDefaultConverter, + defaultConverter: lookupDefaultConverter, idFieldEditor: { type: "field-selector", textField: 'bindingPath', @@ -180,24 +180,24 @@ export function getLookupDialogCommonProperties(editorOptions) { $converter: lookupDialogOptionsConverter }, width: { - description: "窗口宽度,最小值:300px", + description: "窗口宽度,单位:px,最小值:300px", title: "宽度", type: "number", editor: { ...numberEditor, min: showNavigatiorWidth() ? 520 : 300, - needValid:true + needValid: true }, $converter: lookupDialogOptionsConverter }, height: { - description: "窗口高度,最小值:200px", + description: "窗口高度,单位:px,最小值:200px", title: "高度", type: "number", editor: { ...numberEditor, min: 200, - needValid:true + needValid: true }, $converter: lookupDialogOptionsConverter }, @@ -218,7 +218,7 @@ export function getLookupDialogCommonProperties(editorOptions) { ...numberEditor, min: 200, max: (editorOptions?.dialog?.width ?? 960) - 200, - needValid:true + needValid: true }, $converter: lookupDialogOptionsConverter }, @@ -278,7 +278,7 @@ export function getLookupPaginationProperties(editorOptions, refreshPanelAfterCh }; return { - hide: getDisplayType() === 'TREELIST' || !editorOptions.helpId || disablePager, + hide: !showPagerProperty(editorOptions) || disablePager, description: "分页配置", title: "分页", parentPropertyID: 'pagination', @@ -566,7 +566,8 @@ export class LookupPropertyConfig extends InputBaseProperty { title: "必填", type: "boolean", $converter: "/converter/property-editor.converter", - editor: this.requiredEditor + editor: this.requiredEditor, + visible: !!propertyData.binding?.field }, placeholder: { description: "当控件没有值时在输入框中显示的文本", @@ -971,7 +972,7 @@ export class LookupPropertyConfig extends InputBaseProperty { ...this.numberEditor, min: -1, max: 99, - needValid:true + needValid: true } } } diff --git a/packages/ui-vue/components/lookup/src/schema/callback-resolvers.ts b/packages/ui-vue/components/lookup/src/schema/callback-resolvers.ts index 2d2b18bd6c330767e0ab0b8413204bb75434cd13..9af40e576b5b1edbf67b913307fbca4ebc08c7fd 100644 --- a/packages/ui-vue/components/lookup/src/schema/callback-resolvers.ts +++ b/packages/ui-vue/components/lookup/src/schema/callback-resolvers.ts @@ -2,19 +2,19 @@ import { Caller } from "@farris/ui-vue/components/dynamic-resolver"; import { LookupHttpResult } from "../composition/types"; export function createLookupCallbackResolver() { - function resolve(viewSchema: Record, caller: Caller) { + function resolve(viewSchema: Record, caller: Caller, editorSchema?: Record) { const callbacks: Record = {}; - callbacks.dictPicking = (context: { customData: any, options: any; }): Promise => { - return caller.call('dictPicking', [context, viewSchema]); + callbacks.dictPicking = (context: { customData: any, options: any; }): Promise => { + return caller.call('dictPicking', viewSchema, [context, viewSchema], editorSchema); }; callbacks.beforeLoadData = (data: any): Promise => { - return caller.call('beforeLoadData', [data, viewSchema]); + return caller.call('beforeLoadData', viewSchema, [data, viewSchema], editorSchema); }; - callbacks.beforeSelectData = (items: any): boolean | {canSelect: boolean, message?: string} => { - return caller.call('beforeSelectData', [items, viewSchema]); + callbacks.beforeSelectData = (items: any): boolean | { canSelect: boolean, message?: string; } => { + return caller.call('beforeSelectData', viewSchema, [items, viewSchema], editorSchema); }; callbacks.dictPicked = (items: any): void => { - return caller.call('dictPicked', [items, viewSchema]); + return caller.call('dictPicked', viewSchema, [items, viewSchema], editorSchema); }; return callbacks; } diff --git a/packages/ui-vue/components/message-box/exception-message.scss b/packages/ui-vue/components/message-box/exception-message.scss index f74503b85df9404b53c297ed95cda14b417bd7fe..a2cc2001e8ff93ad8fbb2f5f46953baf06e8bead 100644 --- a/packages/ui-vue/components/message-box/exception-message.scss +++ b/packages/ui-vue/components/message-box/exception-message.scss @@ -9,4 +9,17 @@ line-height: 1.5; overflow: hidden; min-height: 66px; + + .exception_toggle_detail { + position: absolute; + right: 0; + color: var(--f-theme-03); + cursor: pointer; + } +} + +.copy-exception-info { + width: 100%; + color: var(--f-theme-03); + padding-left: 37px; } \ No newline at end of file diff --git a/packages/ui-vue/components/message-box/src/components/footer/copy-button.component.tsx b/packages/ui-vue/components/message-box/src/components/footer/copy-button.component.tsx index 218153f6a59ee3514212a71da9184548a548f54c..84544db60b5d7d751132c78f05227198983d062e 100644 --- a/packages/ui-vue/components/message-box/src/components/footer/copy-button.component.tsx +++ b/packages/ui-vue/components/message-box/src/components/footer/copy-button.component.tsx @@ -35,7 +35,7 @@ export default function ( return () => { return ( - + {copyButtonText.value} diff --git a/packages/ui-vue/components/message-box/src/components/message-detail/exception-message.component.tsx b/packages/ui-vue/components/message-box/src/components/message-detail/exception-message.component.tsx index 72ca505dc407747c94c2e300476b00d491104dd3..9778320b689690bc7c3a1447effb89336f8f7d87 100644 --- a/packages/ui-vue/components/message-box/src/components/message-detail/exception-message.component.tsx +++ b/packages/ui-vue/components/message-box/src/components/message-detail/exception-message.component.tsx @@ -78,11 +78,7 @@ export default function (exception: Ref, locales: MessageB const toggleDetailButtonStyles = computed(() => { return { - position: 'absolute', - right: 0, - bottom: showDetailInfo.value?0 : '2px', - color: '#2A87FF', - cursor: 'pointer' + bottom: showDetailInfo.value?0 : '2px' }; }); @@ -92,13 +88,13 @@ export default function (exception: Ref, locales: MessageB return () => { return ( -
    +
    {shouldShowExceptionDate.value &&
    {exceptionDateMessage.value}
    } {shouldShowExceptionMessage.value && (
    - {shouldShowExpandHandle.value && {toggleDetailText.value}} + {shouldShowExpandHandle.value && {toggleDetailText.value}}
    )} diff --git a/packages/ui-vue/components/message-box/src/components/message-detail/static-message.component.tsx b/packages/ui-vue/components/message-box/src/components/message-detail/static-message.component.tsx index dc466e55ce2eb4ee180331267a72bc6d70107e0d..0bcbbbd747edea7de93516a87704ae1b46b41cce 100644 --- a/packages/ui-vue/components/message-box/src/components/message-detail/static-message.component.tsx +++ b/packages/ui-vue/components/message-box/src/components/message-detail/static-message.component.tsx @@ -2,6 +2,6 @@ import { ComputedRef } from "vue"; export default function (safeMessageDetail: ComputedRef) { return () => { - return

    ; + return

    ; }; } diff --git a/packages/ui-vue/components/message-box/src/composition/use-locales.ts b/packages/ui-vue/components/message-box/src/composition/use-locales.ts index 68407c165ef5147d997cb87809e847c14a5f85aa..520b3a9405332ca3c8ecee2d9e78945260ded6fd 100644 --- a/packages/ui-vue/components/message-box/src/composition/use-locales.ts +++ b/packages/ui-vue/components/message-box/src/composition/use-locales.ts @@ -1,4 +1,4 @@ -import { useI18n } from "vue-i18n"; +import { LocaleService } from '@farris/ui-vue/components/locale'; import { MessageBoxProps } from "../message-box.props"; export interface MessageBoxLocaleData { @@ -24,7 +24,8 @@ export interface MessageBoxLocaleData { export function useMessagerLocales(props: MessageBoxProps): MessageBoxLocaleData{ - const { t:getLocaleValue, locale } = useI18n(); + const { getLocaleValue } = LocaleService; + const locale = LocaleService.getLocale(); function getValue(localeKey, propertyValue?: string, defaultValue?: string) { if (propertyValue === defaultValue) { return getLocaleValue(localeKey); @@ -50,6 +51,6 @@ export function useMessagerLocales(props: MessageBoxProps): MessageBoxLocaleData copyFailed: getValue('messageBox.exception.copyFailed') || '复制失败', roger: getValue('messageBox.exception.roger') || '知道了' }, - locale: locale.value + locale }; } diff --git a/packages/ui-vue/components/modal/src/composition/modal.service.tsx b/packages/ui-vue/components/modal/src/composition/modal.service.tsx index 5c1a03610e5f07709042244a36e5b97f478b1c7c..ab09f9994db52bc4ceeadd3763b40e67a8f7871d 100644 --- a/packages/ui-vue/components/modal/src/composition/modal.service.tsx +++ b/packages/ui-vue/components/modal/src/composition/modal.service.tsx @@ -228,6 +228,7 @@ export default class ModalService { -1 } onAccept={acceptCallback} onCancel={rejectCallback} onClosed={destroy} diff --git a/packages/ui-vue/components/modal/src/modal.component.tsx b/packages/ui-vue/components/modal/src/modal.component.tsx index b4ec5f1b96b068e0b2b890302a5a4f704bbfffba..4493ed3feae1338e28ac89aa04cf325ac7fc9d33 100644 --- a/packages/ui-vue/components/modal/src/modal.component.tsx +++ b/packages/ui-vue/components/modal/src/modal.component.tsx @@ -14,15 +14,15 @@ * limitations under the License. */ -import { computed, defineComponent, ref, SetupContext, Teleport, watch, Transition, onMounted, onUnmounted, provide, nextTick, onBeforeMount } from 'vue'; +import { computed, defineComponent, ref, SetupContext, Teleport, watch, Transition, onMounted, onUnmounted, provide, nextTick, onBeforeMount, watchEffect } from 'vue'; import { ModalButton, ModalOptions } from './composition/type'; import { ModalProps, modalProps } from './modal.props'; import { useResizeable } from './composition/use-resizeable'; import { useDraggable } from './composition/use-draggable'; import './modal.scss'; import { useEnter, useEsc } from './composition/use-shortcut'; -import { useI18n } from 'vue-i18n'; -import { getMaxZIndex } from '@farris/ui-vue/components/common'; +import { LocaleService } from '@farris/ui-vue/components/locale'; +import { getMaxZIndex, isMobilePhone } from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FModal', @@ -47,10 +47,8 @@ export default defineComponent({ const title = ref(props.title); const containment = ref(props.containment || null); const modalContainerRef = ref(); - - const { t } = useI18n(); if(title.value === '错误提示') { - title.value = t('messageBox.errorTitle'); + title.value = LocaleService.getLocaleValue('messageBox.errorTitle'); } @@ -76,7 +74,7 @@ export default defineComponent({ const defaultButtons: ModalButton[] = [ { name: 'cancel', - text: t('messageBox.cancel') || '取消', + text: LocaleService.getLocaleValue('messageBox.cancel') || '取消', class: 'btn btn-secondary', handle: ($event: MouseEvent) => { close($event, false); @@ -84,7 +82,7 @@ export default defineComponent({ }, { name: 'accept', - text: t('messageBox.ok') || '确定', + text: LocaleService.getLocaleValue('messageBox.ok') || '确定', class: 'btn btn-primary', handle: ($event: MouseEvent) => { close($event, true); @@ -180,6 +178,17 @@ export default defineComponent({ } }); + watch([()=> props.width,()=> props.height],(newValues,oldValues)=>{ + if(newValues[0] !== oldValues[0] || newValues[1] !== oldValues[1]){ + width.value = newValues[0] || 500; + height.value = newValues[1] || 600; + } + }); + + watch(() => props.class, (newValue, oldValue) => { + customClass.value = newValue; + }); + const showModal = computed(() => { if (modelValue.value) { document.body.classList.add('modal-open'); @@ -216,28 +225,43 @@ export default defineComponent({ return classObject; }); + const isFullScreen = () => isMobilePhone() && !props.isMessager; const modalDialogStyle = computed(() => { + // 获取文档宽高 + const documentWidth = document.documentElement.clientWidth; + const documentHeight = document.documentElement.clientHeight; + + // 确保模态框尺寸不超过文档尺寸 + const modalWidth = Math.min(width.value, documentWidth); + const modalHeight = fitContent.value ? 'auto' : `${Math.min(height.value, documentHeight - 10)}px`; + const styleObject: any = { position: 'absolute', - top: `${(window.innerHeight - height.value) / 2}px`, - left: `${(window.innerWidth - width.value) / 2}px`, - width: `${width.value}px`, - height: fitContent.value ? 'auto' : `${height.value}px` + top: `${(window.innerHeight - parseInt(modalHeight === 'auto' ? '0' : modalHeight)) / 2}px`, + left: `${(window.innerWidth - modalWidth) / 2}px`, + width: `${modalWidth}px`, + height: modalHeight }; + + if (isFullScreen()) { + styleObject.top = '0px'; + styleObject.left = '0px'; + styleObject.width = `${window.innerWidth}px`; + styleObject.height = `${window.innerHeight}px`; + } + if (!props.mask) { styleObject.pointerEvents = 'auto'; } return styleObject; }); - const zIndex = () => { - return getMaxZIndex() || 1040; - }; + const zIndex = ref(getMaxZIndex() || 1050); const modalBackdropStyle = computed(() => { const styleObject: any = { display: 'block', - zIndex: zIndex(), + overflow: 'hidden' }; if (!props.mask) { styleObject.pointerEvents = 'none'; @@ -248,13 +272,15 @@ export default defineComponent({ styleObject.backgroundColor = 'transparent'; } + styleObject.zIndex = zIndex.value; return styleObject; }); const modalContentClass = computed(() => { const classObject = { 'modal-content': true, - 'modal-content-has-header': showHeader.value + 'modal-content-has-header': showHeader.value, + 'is-mobile': isFullScreen() }; return classObject; }); @@ -474,6 +500,12 @@ export default defineComponent({ return host; } + watchEffect(() => { + if (showModal.value) { + zIndex.value= getMaxZIndex() || 1050; + } + }); + return () => { return ( diff --git a/packages/ui-vue/components/modal/src/modal.props.ts b/packages/ui-vue/components/modal/src/modal.props.ts index 92272db84605dea7796c82f7827ccddf614c7670..01a58a0232e213d6a88f2b9f04c9562a45cda917 100644 --- a/packages/ui-vue/components/modal/src/modal.props.ts +++ b/packages/ui-vue/components/modal/src/modal.props.ts @@ -97,7 +97,8 @@ export const modalProps = { dialogType: { type: String, default: '' }, src: { type: String, default: '' }, footerHeight: { type: Number, default: 60 }, - host: { type: Object as PropType, default: 'body' } + host: { type: Object as PropType, default: 'body' }, + isMessager: { type: Boolean, default: false } }; export type ModalProps = Partial>; diff --git a/packages/ui-vue/components/modal/src/property-config/modal.property-config.ts b/packages/ui-vue/components/modal/src/property-config/modal.property-config.ts index 4d61c8bca679c7a92d82f276b6a9e3ecf4890d59..8f80a16a019ef7dfd3a47a1e4a3c1bfbf338e8e5 100644 --- a/packages/ui-vue/components/modal/src/property-config/modal.property-config.ts +++ b/packages/ui-vue/components/modal/src/property-config/modal.property-config.ts @@ -32,7 +32,7 @@ export class ModalProperty extends BaseControlProperty { visible: true }, width: { - description: "窗口宽度,最小值:300px,最大值:3000px", + description: "窗口宽度,单位:px,最小值:300px,最大值:3000px", title: "宽度", type: "number", editor: { @@ -42,7 +42,7 @@ export class ModalProperty extends BaseControlProperty { } }, height: { - description: "窗口高度,最小值:200px,最大值:2000px", + description: "窗口高度,单位:px,最小值:200px,最大值:2000px", title: "高度", type: "number", editor: { diff --git a/packages/ui-vue/components/modal/src/schema/callback-resolvers.ts b/packages/ui-vue/components/modal/src/schema/callback-resolvers.ts index 13dd20e54b2b8575dd964a42a05f198c352f3b9a..c967ec4859df61ee8761535876b6bbf50fc0cbf4 100644 --- a/packages/ui-vue/components/modal/src/schema/callback-resolvers.ts +++ b/packages/ui-vue/components/modal/src/schema/callback-resolvers.ts @@ -1,12 +1,12 @@ import { Caller } from "@farris/ui-vue/components/dynamic-resolver"; export function createModalCallbackResolver() { - function resolve(viewSchema: Record, caller: Caller) { + function resolve(viewSchema: Record, caller: Caller, editorSchema?: Record) { const callbacks: Record = {}; // 关闭前事件 callbacks.beforeClose = (data: any): Promise => { - return caller.call('beforeClose', [data, viewSchema]); + return caller.call('beforeClose', viewSchema, [data, viewSchema], editorSchema); }; return callbacks; diff --git a/packages/ui-vue/components/notify/src/components/toast.component.tsx b/packages/ui-vue/components/notify/src/components/toast.component.tsx index a5b43cb3c0c87096e852a3b31da9be8f023d11ba..136597e0b5b5df94279d4a2e98f1ee778ce60a60 100644 --- a/packages/ui-vue/components/notify/src/components/toast.component.tsx +++ b/packages/ui-vue/components/notify/src/components/toast.component.tsx @@ -16,7 +16,7 @@ import { computed, defineComponent, ref, SetupContext, watch } from 'vue'; import { NotifyButton, NotifyData } from '../notify.props'; import { ToastProps, toastProps } from './toast.props'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; /** * 1、只有描述内容时,则显示图标、描述内容 @@ -27,7 +27,6 @@ export default defineComponent({ props: toastProps, emits: ['close', 'click'] as (string[] & ThisType) | undefined, setup: (props: ToastProps, context: SetupContext) => { - const { locale, t } = useI18n(); const animateIn = ref(props.animate); const animateEnd = 'fadeOut'; const toast = computed(() => { @@ -69,7 +68,8 @@ export default defineComponent({ const shouldShowButtonsInTitle = computed(() => !!toast.value.buttons || !!context.slots.default); const wrapStyles = computed(() => { - if (locale.value === 'en') { + const locale = LocaleService.getLocale(); + if (locale === 'en') { return { wordBreak: 'keep-all', overflowWrap: 'break-word' }; } return {}; @@ -119,7 +119,7 @@ export default defineComponent({ return (
    {shouldShowCloseButton.value && ( - )} diff --git a/packages/ui-vue/components/number-spinner/src/components/text-box.component.tsx b/packages/ui-vue/components/number-spinner/src/components/text-box.component.tsx index e62d4da0e622d6fba41d84006f966a6e80910e37..5bab911cd9eba167aa77a081c70c421c079adfed 100644 --- a/packages/ui-vue/components/number-spinner/src/components/text-box.component.tsx +++ b/packages/ui-vue/components/number-spinner/src/components/text-box.component.tsx @@ -67,6 +67,7 @@ export default function ( onFocus={onFocus} onInput={onInput} onKeydown={onKeyDown} + title={textBoxValue.value} /> ); }; diff --git a/packages/ui-vue/components/number-spinner/src/composition/types.ts b/packages/ui-vue/components/number-spinner/src/composition/types.ts index 53157c5ce7d36e354d32af7421d07b365071810d..3884a2d8ecaf47d382385d9338588ccd01ac481d 100644 --- a/packages/ui-vue/components/number-spinner/src/composition/types.ts +++ b/packages/ui-vue/components/number-spinner/src/composition/types.ts @@ -47,8 +47,8 @@ export interface UseNumber { isEmpty: (value: string | number | undefined) => boolean; onNumberValueChanged: (numberValue: string | number) => void; - precision: ComputedRef; + toFixed: (numberObject: BigNumber) => string; } export interface UseSpinner { diff --git a/packages/ui-vue/components/number-spinner/src/composition/use-locales.ts b/packages/ui-vue/components/number-spinner/src/composition/use-locales.ts index 087c0ab537ea89dd2b802571171888633d0e11fa..922200b4975be3f93292808697c0d58571611561 100644 --- a/packages/ui-vue/components/number-spinner/src/composition/use-locales.ts +++ b/packages/ui-vue/components/number-spinner/src/composition/use-locales.ts @@ -1,5 +1,5 @@ import { NumberSpinnerProps } from "../number-spinner.props"; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; export interface NumberLocaleData { placeholder: string; @@ -10,10 +10,9 @@ export interface NumberLocaleData { } export function useNumberLocales(props: NumberSpinnerProps): NumberLocaleData { - const {t: getLocaleValue} = useI18n(); function getValue(localeKey, propertyValue?: string, defaultValue?: string): any { if (propertyValue === defaultValue) { - return getLocaleValue(localeKey); + return LocaleService.getLocaleValue(localeKey); } return propertyValue || ''; } diff --git a/packages/ui-vue/components/number-spinner/src/composition/use-number.ts b/packages/ui-vue/components/number-spinner/src/composition/use-number.ts index b38c4200510148d274a5252aaa7abc950cad4622..ce7cbfcd303652e3057adb9c0809272898c3c6bc 100644 --- a/packages/ui-vue/components/number-spinner/src/composition/use-number.ts +++ b/packages/ui-vue/components/number-spinner/src/composition/use-number.ts @@ -9,7 +9,7 @@ export function useNumber(props: NumberSpinnerProps, context: SetupContext): Use const precision = computed(() => Number(props.precision) || 0); const enableBigNumber = ref(props.bigNumber); - enableBigNumber.value = false; + // enableBigNumber.value = false; /** * 基于精度参数修改toFixed方法 @@ -104,5 +104,5 @@ export function useNumber(props: NumberSpinnerProps, context: SetupContext): Use } } - return { displayValue, getRealValue, modelValue, isEmpty, onNumberValueChanged, precision, getValidNumberObject }; + return { displayValue, getRealValue, modelValue, isEmpty, onNumberValueChanged, precision, getValidNumberObject, toFixed }; } diff --git a/packages/ui-vue/components/number-spinner/src/composition/use-text-box.ts b/packages/ui-vue/components/number-spinner/src/composition/use-text-box.ts index 4e096f588b0c6a0c5488e8f83d272263b09e7533..476c126b681d7e6734436d69350156a323e61114 100644 --- a/packages/ui-vue/components/number-spinner/src/composition/use-text-box.ts +++ b/packages/ui-vue/components/number-spinner/src/composition/use-text-box.ts @@ -1,4 +1,5 @@ import { SetupContext, computed, ref } from "vue"; +import BigNumber from "bignumber.js"; import { NumberSpinnerProps } from "../number-spinner.props"; import { UseFormat, UseNumber, UseSpinner, UseTextBox } from "./types"; @@ -10,7 +11,7 @@ export function useTextBox( useSpinnerComposition: UseSpinner ): UseTextBox { const { cleanFormat, format } = useFormatComposition; - const { displayValue, getRealValue, modelValue, isEmpty, onNumberValueChanged } = useNumberComposition; + const { displayValue, getRealValue, modelValue, isEmpty, onNumberValueChanged, getValidNumberObject, toFixed } = useNumberComposition; const { downward, upward } = useSpinnerComposition; const isFocus = ref(false); const textBoxValue = computed(() => displayValue.value); @@ -28,13 +29,47 @@ export function useTextBox( // inputValue = inputValue || 0; // } const textValue = cleanFormat(inputValue); - displayValue.value = format(getRealValue(textValue, props.needValid), props.needValid); + let realValue = getRealValue(textValue, props.needValid); + + if (props.needValid && realValue != null) { + realValue = toFixed(getValidNumberObject(new BigNumber(realValue, 10))); + } + + displayValue.value = format(realValue, props.needValid); if (props.updateOn === 'blur') { - onNumberValueChanged(getRealValue(textValue, props.needValid)); + onNumberValueChanged(realValue); } context.emit('blur', { event: $event, formatted: displayValue.value, value: modelValue.value }); } + function getEditingValue() { + const numValue = modelValue.value; + if (isEmpty(numValue)) { + return ''; + } + if (!props.showZero && numValue === 0) { + return ''; + } + const realValue = getRealValue(numValue, props.needValid); + + // 提前判断 realValue 是否为空或无效值 + if (!realValue && realValue !== 0) { + return ''; + } + + try { + const bigNumber = new BigNumber(realValue, 10); + // 显式判断是否为有效数值(排除 NaN 和 Infinity) + if (bigNumber.isNaN() || !bigNumber.isFinite()) { + return ''; + } + return bigNumber.toFixed(); + } catch (e) { + // 捕获 BigNumber 构造异常,返回空字符串以保证健壮性 + return ''; + } + } + /** * 输入框获取焦点时执行的方法 */ @@ -44,8 +79,9 @@ export function useTextBox( if (props.readonly || props.disabled) { return; } - displayValue.value = isEmpty(modelValue.value) ? '' : !props.showZero && '' + modelValue.value === '0' ? '' : String(modelValue.value); - context.emit('focus', { event: $event, formatted: displayValue.value, value: modelValue.value }); + const realValue = getRealValue(modelValue.value, props.needValid); + displayValue.value = getEditingValue(); + context.emit('focus', { event: $event, formatted: realValue, value: modelValue.value }); } /** @@ -58,9 +94,13 @@ export function useTextBox( inputValue = inputValue || 0; } const textValue = cleanFormat(inputValue); - const realNum = getRealValue(textValue, props.needValid); + let realNum = getRealValue(textValue, props.needValid); if (props.updateOn === 'change') { - displayValue.value = textValue; + if (props.needValid) { + realNum = toFixed(getValidNumberObject(new BigNumber(realNum, 10))); + } + // displayValue.value = format(realNum, props.needValid); + displayValue.value = inputValue; onNumberValueChanged(realNum); } diff --git a/packages/ui-vue/components/number-spinner/src/property-config/number-spinner.property-config.ts b/packages/ui-vue/components/number-spinner/src/property-config/number-spinner.property-config.ts index 824109f6fda75aa2329a6fb8e2c3db95554ea160..1b7bc8e966d9b4b757fbfffa89de92f5e86fefe8 100644 --- a/packages/ui-vue/components/number-spinner/src/property-config/number-spinner.property-config.ts +++ b/packages/ui-vue/components/number-spinner/src/property-config/number-spinner.property-config.ts @@ -48,9 +48,12 @@ export class NumberSpinnerProperty extends InputBaseProperty { nullable: true, max: propertyData?.editor?.max, precision: propertyData?.editor?.precision, - needValid: true + bigNumber: isBigNumber, + needValid: true, }, - refreshPanelAfterChanged: true + refreshPanelAfterChanged: true, + visible: this.designViewModelField != null, + }, max: { description: "", @@ -60,9 +63,11 @@ export class NumberSpinnerProperty extends InputBaseProperty { nullable: true, min: propertyData?.editor?.min === undefined ? undefined : propertyData?.editor?.min, precision: propertyData?.editor?.precision, + bigNumber: isBigNumber, needValid: true }, - refreshPanelAfterChanged: true + refreshPanelAfterChanged: true, + visible: this.designViewModelField != null, }, textAlign: { description: "", @@ -88,10 +93,10 @@ export class NumberSpinnerProperty extends InputBaseProperty { }, bigNumber: { description: "", - title: "启用大数字", + title: "支持大数字", type: "boolean", readonly: true, - visible: false // isBigNumber, + visible: isBigNumber, }, showZero: { description: "", diff --git a/packages/ui-vue/components/page-header/index.ts b/packages/ui-vue/components/page-header/index.ts index 670b3084cf0997a7931f547006279e3cda83f556..02ec9180ff5c7abbfe72e52aec07cac689697321 100644 --- a/packages/ui-vue/components/page-header/index.ts +++ b/packages/ui-vue/components/page-header/index.ts @@ -2,7 +2,8 @@ import { App } from 'vue'; import PageHeader from './src/page-header.component'; import PageHeaderDesign from './src/designer/page-header.design.component'; -import { eventHandlerResolver, propsDesignResolver, propsResolver } from './src/page-header.props'; +import { eventHandlerResolver, propsDesignResolver, propsResolver, propsResolverGenerator, propsDesignResolverGenerator } from './src/page-header.props'; +import { RegisterContext } from '../common'; export * from './src/page-header.props'; export { PageHeader }; @@ -11,13 +12,16 @@ export default { install(app: App): void { app.component(PageHeader.name as string, PageHeader); }, - register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void { + register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext): void { componentMap['page-header'] = PageHeader; - propsResolverMap['page-header'] = propsResolver; + propsResolverMap['page-header'] = propsResolverGenerator(registerContext); resolverMap['page-header'] = { eventHandlerResolver }; }, - registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void { + registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext + ): void { componentMap['page-header'] = PageHeaderDesign; - propsResolverMap['page-header'] = propsDesignResolver; + propsResolverMap['page-header'] = propsDesignResolverGenerator(registerContext); } }; diff --git a/packages/ui-vue/components/page-header/src/designer/page-header.design.component.tsx b/packages/ui-vue/components/page-header/src/designer/page-header.design.component.tsx index 1aa74ca9dbf5a9e28233f5231babf2577eeb342a..f9a4b7e4ba553073b097455f234890f546d0aafa 100644 --- a/packages/ui-vue/components/page-header/src/designer/page-header.design.component.tsx +++ b/packages/ui-vue/components/page-header/src/designer/page-header.design.component.tsx @@ -4,6 +4,7 @@ import { PageHeaderDesignerProps, pageHeaderDesignerProps } from '../page-header import { useDesignerComponent, FDesignerInnerItem, DesignerItemContext } from '@farris/ui-vue/components/designer-canvas'; import { useDesignerRules } from './use-designer-rules'; import { getCustomClass } from "@farris/ui-vue/components/common"; +import { DesignerHostService } from '@farris/ui-vue/components/designer-canvas'; export default defineComponent({ name: 'FPageHeaderDesign', @@ -12,7 +13,7 @@ export default defineComponent({ setup(props: PageHeaderDesignerProps, context) { // const items = ref(props.buttons); const elementRef = ref(); - const designerHostService = inject('designer-host-service'); + const designerHostService = inject('designer-host-service') as DesignerHostService; const designItemContext = inject('design-item-context') as DesignerItemContext; const designerRulesComposition = useDesignerRules(designItemContext.schema, designerHostService); const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); @@ -23,6 +24,7 @@ export default defineComponent({ elementRef.value.componentInstance = componentInstance; }); + watch(() => designItemContext.schema.toolbar, () => { toolbarSchema.value = designItemContext.schema.toolbar || { 'type': 'response-toolbar', 'buttons': [] }; responseToolbarPropsRef.value = responseToolbarResolver(toolbarSchema.value); @@ -89,10 +91,10 @@ export default defineComponent({ return getCustomClass(classObject, props.downContentClass); }); function renderTitleArea() { - // 模板HTML - if (props.titleContentHtml) { + // 模板HTML + if (props.titleContentHtml) { return
    ; - } + } return
    {props.showIcon && props.icon ? diff --git a/packages/ui-vue/components/page-header/src/page-header.props.ts b/packages/ui-vue/components/page-header/src/page-header.props.ts index 8fa60f099ee42aa58de505a284e15cda27b89896..5c7526d834bfc7aac9929ee1aa4c4f9801cd7428 100644 --- a/packages/ui-vue/components/page-header/src/page-header.props.ts +++ b/packages/ui-vue/components/page-header/src/page-header.props.ts @@ -1,5 +1,6 @@ + import { ExtractPropTypes, PropType, RenderFunction } from "vue"; -import { createPageHeaderEventHandlerResolver, createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { createPageHeaderEventHandlerResolver, createPropsResolver, getPropsResolverGenerator } from "@farris/ui-vue/components/dynamic-resolver"; import { schemaMapper } from "./schema/schema-mapper"; import pageHeaderSchema from './schema/page-header.schema.json'; // import pageHeaderPropertyConfig from './property-config/page-header.property-config.json'; @@ -78,3 +79,17 @@ export const propsResolver = createPropsResolver(pageHeaderProp export const propsDesignResolver = createPropsResolver(pageHeaderDesignerProps, pageHeaderSchema, schemaMapper, schemaResolver); export const eventHandlerResolver = createPageHeaderEventHandlerResolver(); + +export const propsResolverGenerator = getPropsResolverGenerator( + pageHeaderProps, + pageHeaderSchema, + schemaMapper, + schemaResolver +); + +export const propsDesignResolverGenerator = getPropsResolverGenerator( + pageHeaderDesignerProps, + pageHeaderSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/pagination/src/components/buttons/goto-buttons.component.tsx b/packages/ui-vue/components/pagination/src/components/buttons/goto-buttons.component.tsx index c5bf1dc533a3ed20b42ba9a7067bfbeaadec40e6..2e5cc724927c8487f161410953f53f147afe9848 100644 --- a/packages/ui-vue/components/pagination/src/components/buttons/goto-buttons.component.tsx +++ b/packages/ui-vue/components/pagination/src/components/buttons/goto-buttons.component.tsx @@ -13,46 +13,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComputedRef, ref, Ref, SetupContext, watch } from 'vue'; -import { useI18n } from 'vue-i18n'; +import { computed, ComputedRef, ref, Ref, SetupContext, watch } from 'vue'; +import { LocaleService } from '@farris/ui-vue/components/locale'; export default function ( currentPage: Ref, lastPage: ComputedRef, currentPageSize: Ref, context: SetupContext ) { - const { t:getLocaleValue } = useI18n(); - const gotoPrefix = ref(getLocaleValue('pagination.goto.prefix')); + const gotoPrefix = computed(() => LocaleService.getLocaleValue('pagination.goto.prefix')); const gotoSuffix = ref(''); const pageNumber = ref(currentPage.value); - watch(pageNumber, (value: number, previousValue: number) => { - if (value >= 1 && value <= lastPage.value) { - currentPage.value = value; - } else { - pageNumber.value = previousValue; - } - }); // 实现pageNumber响应式变化 watch(currentPage, (newValue: number) => { if (pageNumber.value !== newValue) { pageNumber.value = newValue; } }); + function isValidPageNumber (currentPage: number) { + return !isNaN(currentPage) && currentPage >= 1 && currentPage <= lastPage.value; + } // 失去焦点 触发变更 - function onBlur($event: any) { + function onBlur($event: MouseEvent) { + const oldCurrentPage = currentPage.value; pageNumber.value = ($event.target as any).valueAsNumber; - context.emit('update:currentPage', pageNumber.value); - context.emit('changed', { pageIndex: pageNumber.value, pageSize: currentPageSize.value }); - context.emit('pageIndexChanged', { pageIndex: pageNumber.value, pageSize: currentPageSize.value }); + if (isValidPageNumber(pageNumber.value)) { + currentPage.value = pageNumber.value; + } else { + pageNumber.value = oldCurrentPage; + } + context.emit('update:currentPage', currentPage.value); + context.emit('changed', { pageIndex: currentPage.value, pageSize: currentPageSize.value }); + context.emit('pageIndexChanged', { pageIndex: currentPage.value, pageSize: currentPageSize.value }); } function goto($event: KeyboardEvent) { if ($event.key === 'Enter') { - pageNumber.value = ($event.target as any).valueAsNumber; - // 回车 触发变更 - context.emit('update:currentPage', pageNumber.value); - context.emit('changed', { pageIndex: pageNumber.value, pageSize: currentPageSize.value }); - context.emit('pageIndexChanged', { pageIndex: pageNumber.value, pageSize: currentPageSize.value }); + onBlur($event as unknown as MouseEvent); } } diff --git a/packages/ui-vue/components/pagination/src/components/pages/page-info.component.tsx b/packages/ui-vue/components/pagination/src/components/pages/page-info.component.tsx index b10e26dd077c9b8c3ae1370f84e7fe15b8082170..f49282e98282f19bc541e51dfa495382ba8a6221 100644 --- a/packages/ui-vue/components/pagination/src/components/pages/page-info.component.tsx +++ b/packages/ui-vue/components/pagination/src/components/pages/page-info.component.tsx @@ -14,11 +14,10 @@ * limitations under the License. */ import { computed, ref, Ref } from 'vue'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; export default function (position: Ref, totalItems: Ref) { - const { t: getLocaleValue } = useI18n(); - const prefixTotalItems = ref(getLocaleValue('pagination.totalInfo.firstText')); - const suffixTotalItems = ref(getLocaleValue('pagination.totalInfo.lastText')); + const prefixTotalItems = computed(() => LocaleService.getLocaleValue('pagination.totalInfo.firstText')); + const suffixTotalItems = computed(() => LocaleService.getLocaleValue('pagination.totalInfo.lastText')); const pageInfoClass = computed(() => { const classObject = { diff --git a/packages/ui-vue/components/pagination/src/components/pages/page-list.component.tsx b/packages/ui-vue/components/pagination/src/components/pages/page-list.component.tsx index bba2119309a3aec717dc4e6ca33ac4e3a813c803..28a2e9174f62616c594ff886e3f1fc307aeace98 100644 --- a/packages/ui-vue/components/pagination/src/components/pages/page-list.component.tsx +++ b/packages/ui-vue/components/pagination/src/components/pages/page-list.component.tsx @@ -14,17 +14,16 @@ * limitations under the License. */ import { computed, ref, Ref, SetupContext } from 'vue'; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; export default function ( currentPage: Ref, currentPageSize: Ref, pageList: Ref, totalItems: Ref, context: SetupContext) { - const { t: getLocaleValue } = useI18n(); const shouldShowPagePanel = ref(false); - const prefixPageSize = ref(getLocaleValue('pagination.show')); - const suffixPageSize = ref(getLocaleValue('pagination.totalInfo.lastText')); + const prefixPageSize = computed(() => LocaleService.getLocaleValue('pagination.show')); + const suffixPageSize = computed(() => LocaleService.getLocaleValue('pagination.totalInfo.lastText')); const pageListClass = computed(() => { const classObject = { diff --git a/packages/ui-vue/components/pagination/src/pagination.components.tsx b/packages/ui-vue/components/pagination/src/pagination.components.tsx index 02b7d1e6362dd5942fc02e275c15bb5ab0742e8e..ec6285a7b56512cc17cce1732e2adb59e94b256b 100644 --- a/packages/ui-vue/components/pagination/src/pagination.components.tsx +++ b/packages/ui-vue/components/pagination/src/pagination.components.tsx @@ -22,7 +22,6 @@ import getPageList from './components/pages/page-list.component'; import getPageNumber from './components/pages/page-number.component'; import getPreviousButtons from './components/buttons/previous-buttons.component'; import { usePages } from './composition/use-pages'; -import './pagination.css'; export default defineComponent({ name: 'FPagination', @@ -31,7 +30,6 @@ export default defineComponent({ 'update:currentPage', 'update:pageSize'] as (string[] & ThisType) | undefined, setup(props: PaginationPropsType, context: SetupContext) { const responsive = ref(false); - const viewModel = ref(props.mode); const position = ref(''); const autoHide = ref(false); const currentPageSize = ref(props.pageSize); @@ -148,8 +146,8 @@ export default defineComponent({ 'ngx-pagination': true, pagination: true, responsive: responsive.value, - 'pager-viewmode-default': viewModel.value === 'default', - 'pager-viewmode-simple': viewModel.value === 'simple' + 'pager-viewmode-default': props.mode === 'default', + 'pager-viewmode-simple': props.mode === 'simple' } as Record; return classObject; }); @@ -206,7 +204,7 @@ export default defineComponent({ } {shouldShowNavigation.value && ( )}
    diff --git a/packages/ui-vue/components/pagination/src/pagination.css b/packages/ui-vue/components/pagination/src/pagination.css deleted file mode 100644 index ef8b249b5b7aee2233621146c67694fd471f4533..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/pagination/src/pagination.css +++ /dev/null @@ -1,4 +0,0 @@ -.pagination-container .page-item.current.disabled .page-link { - /* 禁止点击时 当前页码的样式 */ - opacity: .4; -} \ No newline at end of file diff --git a/packages/ui-vue/components/popover/index.ts b/packages/ui-vue/components/popover/index.ts index 027a6d74f668e5caf56a24d796960fdc30b7e7f4..9a91b12dee2860bff502cebd4e117849e1ebc27d 100644 --- a/packages/ui-vue/components/popover/index.ts +++ b/packages/ui-vue/components/popover/index.ts @@ -18,7 +18,6 @@ import Popover from './src/popover.component'; import PopoverDirective from './src/popover.directive'; export * from './src/popover.props'; -import './src/popover.css'; export { Popover, PopoverDirective }; // export default { diff --git a/packages/ui-vue/components/popover/src/composition/use-position.ts b/packages/ui-vue/components/popover/src/composition/use-position.ts index 31020294c399d0bcc53d404d22b3e563248494a4..50719d36abb8a34e938b619c1566bdf0b945ced3 100644 --- a/packages/ui-vue/components/popover/src/composition/use-position.ts +++ b/packages/ui-vue/components/popover/src/composition/use-position.ts @@ -93,7 +93,8 @@ export function usePosition( } function calculatePopoverTopLimit(popoverRect: DOMRect, intendingToPopoverTop: number) { - const topLimit = hostHeight.value - popoverRect.height - 4; + // 顶部具有margin-top间距 + const topLimit = hostHeight.value - popoverRect.height - 12; return topLimit > 0 ? topLimit : intendingToPopoverTop; } /** @@ -219,7 +220,7 @@ export function usePosition( } popoverRef.value.classList.add('popover-' + popoverPosition, 'bs-popover-' + popoverPosition); - return { popoverLeft, popoverTop, arrowLeft, maxHeight }; + return { popoverLeft: popoverLeft < 0 ? 5 : popoverLeft, popoverTop, arrowLeft, maxHeight }; } function calculatePopoverPositionOnRight(popoverRect: DOMRect, referenceRect: DOMRect, arrowRect: DOMRect) { diff --git a/packages/ui-vue/components/popover/src/composition/use-resize.ts b/packages/ui-vue/components/popover/src/composition/use-resize.ts index f74a5240ff4742626df65d1fed15c8c9c98f9704..a2570995f897c963c4295c59f045791b3b9c6762 100644 --- a/packages/ui-vue/components/popover/src/composition/use-resize.ts +++ b/packages/ui-vue/components/popover/src/composition/use-resize.ts @@ -7,12 +7,15 @@ export function useResize( context: SetupContext, reference: Ref, shouldFitWidthToReference: Ref, - positionComposition: UsePosition + positionComposition: UsePosition, + popoverRef: Ref ): UseResize { const { popoverWidth, fitToReference, followToReferencePosition } = positionComposition; function onResize() { + if (!popoverRef.value) { return; } + if (popoverRef.value.style?.display === 'none') { return; } if (reference.value) { followToReferencePosition(reference.value); const referenceRect = reference.value.getBoundingClientRect() as DOMRect; diff --git a/packages/ui-vue/components/popover/src/popover.component.tsx b/packages/ui-vue/components/popover/src/popover.component.tsx index 23565cc9f40e13f566c34655a12414cd7c85f46d..e862a2cec7e571337292e1645f024e3d8236a49e 100644 --- a/packages/ui-vue/components/popover/src/popover.component.tsx +++ b/packages/ui-vue/components/popover/src/popover.component.tsx @@ -42,7 +42,7 @@ export default defineComponent({ const { showPopover, hidePopverOnClickBodyHandler } = usePopup(props, context, arrowRef, popoverRef, reference, shouldFitWidthToReference, positionComposition); - const { onResize } = useResize(props, context, reference, shouldFitWidthToReference, positionComposition); + const { onResize } = useResize(props, context, reference, shouldFitWidthToReference, positionComposition,popoverRef); const popoverClass = computed(() => { let placement = position.value; diff --git a/packages/ui-vue/components/popover/src/popover.css b/packages/ui-vue/components/popover/src/popover.css index 402a0333de84a99a551dce9c32e90cfc639fe91b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/packages/ui-vue/components/popover/src/popover.css +++ b/packages/ui-vue/components/popover/src/popover.css @@ -1,27 +0,0 @@ -.popover-content .f-datepicker-container { - box-shadow: none!important; -} - -.popover.bs-popover-top .arrow::before { - bottom: 0; - border-top-color: white; - filter: drop-shadow(0px 1px 2px #e8e8e8); -} - -.popover.bs-popover-top .arrow::after { - border-top-color: white; -} - -.popover.bs-popover-bottom-left .arrow::before { - top: 0; - border-bottom-color: #ffffff !important; - filter: drop-shadow(0px -2px 2px #e8e8e8); -} - -.popver.fade { - visibility: hidden; -} - -.popover { - transition: opacity 0.12s ease-in; -} \ No newline at end of file diff --git a/packages/ui-vue/components/property-editor/src/components/expression/expression-property.component.tsx b/packages/ui-vue/components/property-editor/src/components/expression/expression-property.component.tsx index c675623ad679469ecfca715a8ec77348d3ce0ab3..05aa35c261fb1e9978f48ce181dd1ca684aa32a2 100644 --- a/packages/ui-vue/components/property-editor/src/components/expression/expression-property.component.tsx +++ b/packages/ui-vue/components/property-editor/src/components/expression/expression-property.component.tsx @@ -48,7 +48,7 @@ export default function ( }); function onSubmitModal({ expression, message }) { - if (!expression) { + if (!expression && !props.enableClear) { return; } expressionValue.value.expressionInfo.value = expression; @@ -74,7 +74,7 @@ export default function ( {...expressionConfig.value} v-model={expressionRule.value} validateMessage={expressionMessage.value} - enableClear={false} + enableClear={props.enableClear} readonly={readOnly.value} beforeSubmit={beforeSubmit} onSubmitModal={onSubmitModal} diff --git a/packages/ui-vue/components/property-editor/src/components/expression/expression-property.props.ts b/packages/ui-vue/components/property-editor/src/components/expression/expression-property.props.ts index 0671b5b5790c48c091fac7c72f23f756cf531d04..7047d7d94949fcc9332e16bd81a5f5e1e77a060e 100644 --- a/packages/ui-vue/components/property-editor/src/components/expression/expression-property.props.ts +++ b/packages/ui-vue/components/property-editor/src/components/expression/expression-property.props.ts @@ -18,6 +18,7 @@ import { ExtractPropTypes } from 'vue'; export const expressionPropertyProps = { expressionConfig: { type: Object , default: {} }, + enableClear: { type: Boolean, default: false }, } as Record; export type ExpressionPropertyProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/property-editor/src/components/variable/variable-property.props.ts b/packages/ui-vue/components/property-editor/src/components/variable/variable-property.props.ts index f5a4658f24c9e92ce0aa0ac538e6ed039d48b44f..1a55ec37930bd14b03fa99427a92e72db1a3b893 100644 --- a/packages/ui-vue/components/property-editor/src/components/variable/variable-property.props.ts +++ b/packages/ui-vue/components/property-editor/src/components/variable/variable-property.props.ts @@ -25,7 +25,9 @@ export const variablePropertyProps = { /** 新增变量名称的前缀 */ newVariablePrefix: { type: String, default: '' }, /** 新增变量的类型 */ - newVariableType: { type: String, default: '' } + newVariableType: { type: String, default: '' }, + /** 新增变量的父组件id */ + parentComponentId: { type: String, default: '' }, } as Record; diff --git a/packages/ui-vue/components/property-editor/src/composition/use-property-editor.ts b/packages/ui-vue/components/property-editor/src/composition/use-property-editor.ts index 602aa723b9768ddfb1f7ee1a6a6fec3d3ec49839..d8e9f3356c1951ee9d4c89593be207d4502e9499 100644 --- a/packages/ui-vue/components/property-editor/src/composition/use-property-editor.ts +++ b/packages/ui-vue/components/property-editor/src/composition/use-property-editor.ts @@ -30,6 +30,10 @@ export function usePropertyEditor(designerHostService: DesignerHostService): Use */ function getVariablesByViewModelId(viewModelId: string, pathPrefix: string = ''): VariableItem[] { const viewModel = formSchemaUtils.getViewModelById(viewModelId); + // 智能化场景 + if(!viewModel){ + return []; + } return viewModel.states.map(state => convertToEditorVariable(state, pathPrefix)); } diff --git a/packages/ui-vue/components/property-editor/src/composition/use-variable.ts b/packages/ui-vue/components/property-editor/src/composition/use-variable.ts index f0559c77f712596258a780d872f81b644c808560..d760d172c8e5b3835c93ca00b052dd23d372c539 100644 --- a/packages/ui-vue/components/property-editor/src/composition/use-variable.ts +++ b/packages/ui-vue/components/property-editor/src/composition/use-variable.ts @@ -52,20 +52,21 @@ export function useVariable( */ function generateVariable(): VariableValue { // 1、变量名的中间部分 - const splicingNameMiddle = newVariablePrefix.value ? setFirstLetterUpperCase(controlName.value) : - setFirstLetterLowerCase(controlName.value); + const splicingNameMiddle = newVariablePrefix.value && !newVariablePrefix.value.endsWith('_') ? + setFirstLetterUpperCase(controlName.value) : setFirstLetterLowerCase(controlName.value); // 2、变量名的右侧内容 const splicingNameRight = setFirstLetterUpperCase(propertyName.value); // 3、拼接成新的变量名 - let newVariablePath = `${newVariablePrefix.value}${splicingNameMiddle}${splicingNameRight}`; + let newVariablePath = `${splicingNameMiddle}${splicingNameRight}`; newVariablePath = removeAndCapitalize(newVariablePath); + newVariablePath = `${newVariablePrefix.value}${newVariablePath}`; const { guid } = useGuid(); const newVariable: VariableValue = { type: 'Variable', - path: newVariablePath, + path: props.parentComponentId ? `${props.parentComponentId}.${newVariablePath}` : newVariablePath, field: guid(), fullPath: newVariablePath, isNewVariable: true, diff --git a/packages/ui-vue/components/property-editor/src/schema/property-editor.schema.json b/packages/ui-vue/components/property-editor/src/schema/property-editor.schema.json index 0df573b5ad9e14cbbedc59c95252d23807a26d56..8080308b38ae5a3e01a9386b40d1313313725f01 100644 --- a/packages/ui-vue/components/property-editor/src/schema/property-editor.schema.json +++ b/packages/ui-vue/components/property-editor/src/schema/property-editor.schema.json @@ -110,6 +110,16 @@ "description": "", "type": "object", "default": {} + }, + "parentComponentId": { + "description": "", + "type": "string", + "default": "" + }, + "enableClear": { + "description": "", + "type": "boolean", + "default": false } }, "required": [ diff --git a/packages/ui-vue/components/property-panel/src/composition/entity/base-property.ts b/packages/ui-vue/components/property-panel/src/composition/entity/base-property.ts index 883dffcbc929c1eebe005b079eb1b6114fa98703..0289b232e2e71e856550ea3c2c07e74f668c2606 100644 --- a/packages/ui-vue/components/property-panel/src/composition/entity/base-property.ts +++ b/packages/ui-vue/components/property-panel/src/composition/entity/base-property.ts @@ -114,7 +114,6 @@ export class BaseControlProperty { $converter: "/converter/appearance.converter", parentPropertyID: 'appearance' } - }; for (const key in properties) { // 合并属性,保留原属性值 @@ -152,7 +151,7 @@ export class BaseControlProperty { const { getVariables, getControlName, getStateMachines } = usePropertyEditor(this.designerHostService); const targetType = this.getRealTargetType(propertyData); const newPropertyTypes = propertyTypes && propertyTypes.length > 0 ? propertyTypes : ['Const', 'Variable', 'StateMachine', 'Expression']; - const resultProperty = { + const resultProperty: any = { type: "property-editor", propertyTypes: newPropertyTypes }; @@ -165,7 +164,7 @@ export class BaseControlProperty { }, constInfos); break; case 'Expression': - resultProperty['expressionConfig'] = this.getExpressionOptions(propertyData, targetType, expressionType||propertyName); + resultProperty['expressionConfig'] = this.getExpressionOptions(propertyData, targetType, expressionType || propertyName); break; case 'StateMachine': resultProperty['stateMachines'] = getStateMachines(); @@ -176,10 +175,16 @@ export class BaseControlProperty { newVariablePrefix: 'is', newVariableType: 'Boolean', variables: getVariables(this.viewModelId), + parentComponentId: this.componentId === 'root-component' ? '' : 'root-component', onBeforeOpenVariables: (variables) => { variables.value = getVariables(this.viewModelId); } }, variableInfos); + + // 运行时定制新增的变量,以ext_开头 + if (this.designerContext.designerMode === 'PC_RTC') { + resultProperty.newVariablePrefix = 'ext_' + resultProperty.newVariablePrefix; + } break; } }); @@ -300,7 +305,8 @@ export class BaseControlProperty { category: 'locale', code: newPropertyValue.fullPath, name: newPropertyValue.fullPath, - type: newPropertyValue.newVariableType || 'String' + type: newPropertyValue.newVariableType || 'String', + isRtcVariable: this.designerContext.designerMode === 'PC_RTC' ? true : undefined }; delete newPropertyValue.newVariableType; delete newPropertyValue.isNewVariable; @@ -308,7 +314,7 @@ export class BaseControlProperty { // 4、把新变量添加到ViewModel中 const existedVariable = this.formSchemaUtils.getVariableByCode(newVariable.code); if (!existedVariable) { - const viewModel = this.formSchemaUtils.getViewModelById(viewModelId); + const viewModel = this.formSchemaUtils.getViewModelById('root-viewmodel'); viewModel.states.push(newVariable); } } @@ -383,7 +389,7 @@ export class BaseControlProperty { } getRealTargetType(propertyData: any,) { - if (['response-toolbar-item', 'tab-toolbar-item', 'section-toolbar-item'].indexOf(propertyData.type) > -1) { + if (['response-toolbar-item', 'tab-toolbar-item', 'section-toolbar-item','drawer-toolbar-item'].indexOf(propertyData.type) > -1) { return 'Button'; } if (propertyData.binding && propertyData.binding.field) { diff --git a/packages/ui-vue/components/property-panel/src/composition/entity/expression-property.ts b/packages/ui-vue/components/property-panel/src/composition/entity/expression-property.ts index 5c695885bc6202e3c47b3337caaae1cd8e867c6d..a308c8d259f43b3ecf2353cd96f26ea560d2cc8e 100644 --- a/packages/ui-vue/components/property-panel/src/composition/entity/expression-property.ts +++ b/packages/ui-vue/components/property-panel/src/composition/entity/expression-property.ts @@ -90,7 +90,7 @@ export class ExpressionProperty { } const contextEntities: any = []; - viewModel.states.forEach(variable => { + viewModel.states.filter(item => item.category === 'remote').forEach(variable => { contextEntities.push({ key: variable.code, name: variable.name, diff --git a/packages/ui-vue/components/property-panel/src/composition/entity/input-base-property.ts b/packages/ui-vue/components/property-panel/src/composition/entity/input-base-property.ts index cbbb06d0ecfdf1bb8b7d1513f94f9021b865718a..49dcc994d48e8a763332a726461c63eb953913a7 100644 --- a/packages/ui-vue/components/property-panel/src/composition/entity/input-base-property.ts +++ b/packages/ui-vue/components/property-panel/src/composition/entity/input-base-property.ts @@ -22,7 +22,7 @@ export class InputBaseProperty extends BaseControlProperty { super(componentId, designerHostService); this.responseLayoutEditorFunction = useResponseLayoutEditorSetting(this.formSchemaUtils); } - private getCommonPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance | null, showPosition = 'Card') { + public getCommonPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance | null, showPosition = 'Card') { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicProperties(propertyData, componentInstance, showPosition); // 外观 @@ -36,7 +36,7 @@ export class InputBaseProperty extends BaseControlProperty { // 表达式编辑器 this.propertyConfig.categories['expressions'] = this.getExpressionConfig(propertyData, 'Field'); // 事件 暂不支持 - // this.propertyConfig.categories['eventsEditor'] = this.getEventPropertyConfig(propertyData); + this.propertyConfig.categories['eventsEditor'] = this.getEventPropertyConfig(propertyData); return this.propertyConfig; } public getGridFieldEdtiorPropConfig(propertyData: any, componentInstance: DesignerComponentInstance | null) { @@ -96,7 +96,8 @@ export class InputBaseProperty extends BaseControlProperty { type: "enum", editor: { data: [{ "id": "visible", "name": "显示" }, { "id": "reserve-space", "name": "占位" }, { "id": "none", "name": "不显示" }] - } + }, + defaultValue: propertyData.editor?.type === 'image' ? 'none' : 'visible' }, binding: { description: "绑定的表单字段", @@ -222,6 +223,12 @@ export class InputBaseProperty extends BaseControlProperty { $converter: "/converter/appearance.converter", parentPropertyID: 'appearance' }, + fill:{ + title: "填充宽度", + description: "启用后,控件占满父容器宽度", + type:"boolean", + parentPropertyID: 'appearance', + }, responseLayout: { description: "响应式列宽", title: "响应式列宽", @@ -370,7 +377,8 @@ export class InputBaseProperty extends BaseControlProperty { description: "", title: "必填", type: "boolean", - editor: requiredEditor + editor: requiredEditor, + visible: !!propertyData.binding?.field }, placeholder: { description: "当控件没有值时在输入框中显示的文本", @@ -537,21 +545,21 @@ export class InputBaseProperty extends BaseControlProperty { 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: '绑定字段值变化前事件' - } - ); + // eventList.push( + // { + // label: 'fieldValueChanging', + // name: '绑定字段值变化前事件' + // } + // ); } const valueChangedExist = eventList.find(eventListItem => eventListItem.label === 'fieldValueChanged'); if (!valueChangedExist) { - eventList.push( - { - label: 'fieldValueChanged', - name: '绑定字段值变化后事件' - } - ); + // eventList.push( + // { + // label: 'fieldValueChanged', + // name: '绑定字段值变化后事件' + // } + // ); } if (this.designViewModelField) { // 因为字段变更属性是存到VM中的,但是这里需要在控件上编辑,所以复制一份数据 @@ -592,6 +600,17 @@ export class InputBaseProperty extends BaseControlProperty { if (customEventList) { eventList = eventList.concat(customEventList); } + // 如果没有事件就不显示交互标签 + if(eventList.length===0){ + return { + title: '事件', + hideTitle: true, + properties:{}, + tabId: 'commands', + tabName: '交互', + hide:true + }; + } // 追加值变化 this.appendFieldValueChangeEvents(propertyData, eventList); diff --git a/packages/ui-vue/components/property-panel/src/composition/entity/schema-dom-mapping.ts b/packages/ui-vue/components/property-panel/src/composition/entity/schema-dom-mapping.ts index 943ad54d44bd11806745aa9fbb799b451a621633..e3deb6f63df430e9400d4134ebd9e39f6708d452 100644 --- a/packages/ui-vue/components/property-panel/src/composition/entity/schema-dom-mapping.ts +++ b/packages/ui-vue/components/property-panel/src/composition/entity/schema-dom-mapping.ts @@ -11,6 +11,7 @@ export class SchemaDOMMapping { String: [ { key: DgControl['input-group'].type, value: DgControl['input-group'].name }, { key: DgControl['lookup'].type, value: DgControl['lookup'].name }, + { key: DgControl['image'].type, value: DgControl['image'].name }, { key: DgControl['date-picker'].type, value: DgControl['date-picker'].name }, { key: DgControl['switch'].type, value: DgControl['switch'].name }, { key: DgControl['check-box'].type, value: DgControl['check-box'].name }, @@ -22,7 +23,9 @@ export class SchemaDOMMapping { ], Text: [ { key: DgControl['textarea'].type, value: DgControl['textarea'].name }, - { key: DgControl['lookup'].type, value: DgControl['lookup'].name } + { key: DgControl['lookup'].type, value: DgControl['lookup'].name }, + { key: DgControl['image'].type, value: DgControl['image'].name }, + { key: DgControl['rich-text-editor'].type, value: DgControl['rich-text-editor'].name } ], Decimal: [ { key: DgControl['number-spinner'].type, value: DgControl['number-spinner'].name } @@ -69,7 +72,7 @@ export class SchemaDOMMapping { // 列表列编辑器支持的控件类型有限 if (controlType === 'data-grid-column' && editorTypes?.length) { - const notAllowedType = [DgControl['check-group'].type, DgControl['radio-group'].type]; + const notAllowedType = [DgControl['check-group'].type, DgControl['radio-group'].type,DgControl['image'].type, DgControl['rich-text-editor'].type]; editorTypes = editorTypes.filter(type => !notAllowedType.includes(type.key)); } return editorTypes; diff --git a/packages/ui-vue/components/query-solution/index.ts b/packages/ui-vue/components/query-solution/index.ts index 97689c11d988da6c5d7d23ebd5c951357fb56c75..d74f038b3ed82cbf1f36996b9743573e57d5d4cb 100644 --- a/packages/ui-vue/components/query-solution/index.ts +++ b/packages/ui-vue/components/query-solution/index.ts @@ -19,16 +19,18 @@ import FQuerySolution from './src/query-solution.component'; import FQuerySolutionDesign from './src/designer/query-solution.design.component'; import FQuerySolutionConfig from './src/designer/query-solution-config/query-solution-config.component'; import FSolutionPreset from './src/designer/solution-preset-config/solution-preset.component'; -import { propsResolver } from './src/query-solution.props'; -import { configPropsResolver } from './src/designer/query-solution-config/query-solution-config.props'; -import { solutionPresetPropsResolver } from './src/designer/solution-preset-config/solution-preset.props'; +import { propsResolver, propsResolverGenerator } from './src/query-solution.props'; +import { configPropsResolver, configPropsResolverGenerator } from './src/designer/query-solution-config/query-solution-config.props'; +import { solutionPresetPropsResolver, solutionPropsResolverGenerator } from './src/designer/solution-preset-config/solution-preset.props'; import { useSolutionValidation } from './src/designer/composition/use-validation'; import { useTransfer } from './src/designer/query-solution-config/composition/use-transfer'; import { usePanel } from './src/designer/query-solution-config/composition/use-panel'; +import { RegisterContext } from '../common'; export * from './src/query-solution.props'; export * from './src/composition/types'; export * from './src/composition/use-condition'; export * from './src/designer/query-solution-config/composition/types'; +export * from './src/composition/use-solution-utils'; export { FQuerySolution, useSolutionValidation, useTransfer, usePanel}; @@ -37,16 +39,18 @@ export default { app.component(FQuerySolution.name as string, FQuerySolution); app.component(FQuerySolutionConfig.name as string, FQuerySolutionConfig); }, - register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void { + register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record, registerContext : RegisterContext): void { componentMap['query-solution'] = FQuerySolution; componentMap['query-solution-config'] = FQuerySolutionConfig; componentMap['solution-preset'] = FSolutionPreset; - propsResolverMap['query-solution'] = propsResolver; - propsResolverMap['query-solution-config'] = configPropsResolver; - propsResolverMap['solution-preset'] = solutionPresetPropsResolver; + propsResolverMap['query-solution'] = propsResolverGenerator(registerContext); + propsResolverMap['query-solution-config'] = configPropsResolverGenerator(registerContext); + propsResolverMap['solution-preset'] = solutionPropsResolverGenerator(registerContext); }, - registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void { + registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext + ): void { componentMap['query-solution'] = FQuerySolutionDesign; - propsResolverMap['query-solution'] = propsResolver; + propsResolverMap['query-solution'] = propsResolverGenerator(registerContext); } }; diff --git a/packages/ui-vue/components/query-solution/src/composition/build-solution.ts b/packages/ui-vue/components/query-solution/src/composition/build-solution.ts index 9f2650c855b391af85d79535448cb14475aae876..18d1ad3ad0fbd5e9e4628ea376e924ed0c7c106d 100644 --- a/packages/ui-vue/components/query-solution/src/composition/build-solution.ts +++ b/packages/ui-vue/components/query-solution/src/composition/build-solution.ts @@ -13,10 +13,10 @@ export function querySolutionCreatorService(context: Record, design const loadCommandCmpId = '54bddc89-5f7e-4b91-9c45-80dd6606cfe9'; /** - * 创建查询相关命令 + * 创建表格筛选查询相关命令 * @param solutionMetadata 筛选方案元数据 */ - function createCommandsForSolution(solutionMetadata: any) { + function createDataGridCommandsForSolution(solutionMetadata: any) { const targetComponentId = targetComponentInstance.belongedComponentId; const viewModelId = formSchemaUtils.getViewModelIdByComponentId(targetComponentId); const viewModel = formSchemaUtils.getViewModelById(viewModelId); @@ -86,6 +86,185 @@ export function querySolutionCreatorService(context: Record, design } } } + /** + * 创建树表格筛选查询相关命令 + * @param solutionMetadata 筛选方案元数据 + * @param treeGridComponent 树表格所在的组件 + */ + function createTreeGridCommandsForSolution(solutionMetadata: any, treeGridComponent: any) { + const treeGridViewModel = formSchemaUtils.getViewModelById(treeGridComponent.viewModel); + const rootViewModel = formSchemaUtils.getViewModelById('root-viewmodel'); + + const treeCardControllerId = '8fe977a1-2b32-4f0f-a6b3-2657c4d03574'; + + const addedCommand: any = []; + + // 命令1:以完整树形结构加载数据 + let loadFullTreeCommand = treeGridViewModel.commands.find(command => command.handlerName === 'loadFullTree' && command.cmpId === treeCardControllerId); + if (!loadFullTreeCommand) { + const id = useGuid().guid(); + loadFullTreeCommand = { + id, + code: 'treegridLoadFullTree1', + name: '以完整树形结构加载数据1', + params: [ + { + name: 'fullTreeType', + shownName: '是否是完整树加载', + value: '0' + }, + { + name: 'loadType', + shownName: '是否分级加载', + value: '1' + }, + { + name: 'filters', + shownName: '过滤条件', + value: '{UISTATE~/#{root-component}/originalFilterConditionList}', + defaultValue: null + } + + ], + handlerName: 'loadFullTree', + cmpId: treeCardControllerId + }; + treeGridViewModel.commands.push(loadFullTreeCommand); + addedCommand.push({ + host: id, + handler: 'loadFullTree' + }); + } else { + if (loadFullTreeCommand.params && loadFullTreeCommand.params.length) { + loadFullTreeCommand.params.map(p => { + if (p.name === 'fullTreeType') { + p.value = '0'; + } + if (p.name === 'loadType') { + p.value = '1'; + } + if (p.name === 'filters') { + p.value = '{UISTATE~/#{root-component}/originalFilterConditionList}'; + } + }); + + } + } + // 命令2:以完整树形结构加载下级数据 + if (!treeGridViewModel.commands.find(command => command.handlerName === 'LoadFullTreeChildren' && command.cmpId === treeCardControllerId)) { + const id = useGuid().guid(); + treeGridViewModel.commands.push({ + id, + code: 'treegridLoadFullTreeChildren1', + name: '以完整树形结构加载下级数据1', + params: [ + { + name: 'commandName', + shownName: '数据加载的命令编号', + value: loadFullTreeCommand.code + }, + { + name: 'frameId', + shownName: '目标组件', + value: treeGridComponent ? `#{${treeGridComponent.id}}` : '' + } + ], + handlerName: 'LoadFullTreeChildren', + cmpId: treeCardControllerId + }); + addedCommand.push({ + host: id, + handler: 'LoadFullTreeChildren' + }); + } + + // 命令3: 加载完整树初始化 + if (rootViewModel && rootViewModel.commands && !rootViewModel.commands.find(command => command.handlerName === 'LoadFullTreeInit' && command.cmpId === treeCardControllerId)) { + const id = useGuid().guid(); + rootViewModel.commands.push({ + id, + code: 'treegridLoadFullTreeInit1', + name: '加载完整树初始化1', + params: [ + { + name: 'fullTreeType', + shownName: '完整树类型', + value: '0', + defaultValue: null + }, + { + name: 'loadType', + shownName: '分级加载方式', + value: '1', + defaultValue: null + }, + ], + handlerName: 'LoadFullTreeInit', + cmpId: treeCardControllerId + }); + addedCommand.push({ + host: id, + handler: 'LoadFullTreeInit' + }); + } + + + // 将命令1绑定到筛选方案控件上 + solutionMetadata.onQuery = `root-viewModel.${treeGridViewModel.id}.${loadFullTreeCommand.code}`; + + + // 预置筛选方案查询数据的构件 + const webCmds = formSchemaUtils.getFormSchema().module.webcmds; + if (webCmds) { + let treeCardController = webCmds.find(cmd => cmd.id === treeCardControllerId); + if (!treeCardController) { + treeCardController = { + id: treeCardControllerId, + path: '/projects/packages/Inspur.GS.Gsp.Web.WebCmp/webcmd', + name: 'TreeCardController.webcmd', + refedHandlers: [] + }; + formSchemaUtils.getFormSchema().module.webcmds.push(treeCardController); + } + addedCommand.map(command => { + if (!treeCardController.refedHandlers.find(handler => handler.host === command.host)) { + treeCardController.refedHandlers.push(command); + } + }); + + } + } + /** + * 定位表格或树表格组件,并分别添加控制器命令 + */ + function createCommandsForSolution(solutionMetadata: any) { + // 1、定位当前页面中表格或树表格的组件 + const rootComponent = formSchemaUtils.getComponentById('root-component'); + let dataGridComponent; + formSchemaUtils.selectNode(rootComponent, (item) => { + if (item.type === 'component-ref') { + const childComponent = formSchemaUtils.getComponentById(item.component); + const childViewModel = formSchemaUtils.getViewModelById(childComponent?.viewModel); + if (childComponent?.componentType === 'data-grid' && childViewModel?.bindTo === '/') { + dataGridComponent = childComponent; + return true; + } + } + }); + + if (!dataGridComponent) { + return; + } + const dataGrid = formSchemaUtils.selectNode(dataGridComponent, item => item.type === 'data-grid'); + const treeGrid = formSchemaUtils.selectNode(dataGridComponent, item => item.type === 'tree-grid'); + // 2、分别添加控制器命令 + if (dataGrid) { + createDataGridCommandsForSolution(solutionMetadata); + } + if (treeGrid) { + createTreeGridCommandsForSolution(solutionMetadata, dataGridComponent); + } + } function createQuerySolution(): any { // 1、组装筛选方案容器和筛选方案dom结构 @@ -113,7 +292,6 @@ export function querySolutionCreatorService(context: Record, design filterText: '筛选' }); - // 2、关联DOM处理--新增样式 const targetContainer = targetComponentInstance.schema; if (targetContainer) { diff --git a/packages/ui-vue/components/query-solution/src/composition/types.ts b/packages/ui-vue/components/query-solution/src/composition/types.ts index cb2b857b0af93bea9821a14f0ca7cf34abc2cecf..84b2f833fb9ec972c5e7c06083dbaa9b9a4eb36c 100644 --- a/packages/ui-vue/components/query-solution/src/composition/types.ts +++ b/packages/ui-vue/components/query-solution/src/composition/types.ts @@ -19,7 +19,7 @@ import { AxiosResponse } from 'axios'; import { Ref } from 'vue'; import { Condition, FieldConfig } from '@farris/ui-vue/components/condition'; -export enum ValueType{ +export enum ValueType { /** *值类型 */ @@ -30,7 +30,7 @@ export enum ValueType{ Express = 1 } -export enum RelationType{ +export enum RelationType { Empty = 0, /** @@ -48,7 +48,7 @@ export enum RelationType{ /** * 比较符 */ - export enum CompareType{ +export enum CompareType { Equal = 0, NotEqual = 1, Greater = 2, @@ -87,12 +87,14 @@ export interface UseSolution { * 生成默认筛选方案 */ getDefaultSolution: () => QuerySolution; - loadAllSolution: (loadSolutionOptions:{openSolutionID?: string, enableQuery?: boolean}) => void; + loadAllSolution: (loadSolutionOptions: { openSolutionID?: string, enableQuery?: boolean }) => void; getGuid: () => string; handleQuery: () => void; - fieldToCondition: (field: FieldConfig) => Pick; + fieldToCondition: (field: FieldConfig, defaultValue?) => Condition; handleSolution: (solution) => QuerySolution; - query: () => void; + judgeLoadNewValue: (condition: Condition, field: FieldConfig) => boolean; + defaultValues:Ref; + query: (queryCallback?) => void; } export interface UseHeader { @@ -113,3 +115,7 @@ export interface UseHttp { } +export interface MobileUtils{ + renderContent:()=>JSX.Element; + modalInstance:any; +} \ No newline at end of file diff --git a/packages/ui-vue/components/query-solution/src/composition/use-condition.ts b/packages/ui-vue/components/query-solution/src/composition/use-condition.ts index aa73a0f6a04391c89b840576b22ab8632a84b422..458bb5f95a27d373d2014e008e6025bfb2bcb455 100644 --- a/packages/ui-vue/components/query-solution/src/composition/use-condition.ts +++ b/packages/ui-vue/components/query-solution/src/composition/use-condition.ts @@ -120,7 +120,7 @@ export function useCondition(props: QuerySolutionProps, context: SetupContext): 'Relation': RelationType.And, 'Expresstype': ValueType.Value }); - !!dateRangeValue.end&& querys.push({ + !!dateRangeValue.end && querys.push({ 'FilterField': condition.fieldCode, 'Compare': CompareType.LessOrEqual, 'Value': dateRangeValue.end, @@ -158,7 +158,23 @@ export function useCondition(props: QuerySolutionProps, context: SetupContext): } return querys; } - + function convertCheckGroup(condition) { + const querys: QueryItem[] = []; + const checkgroupValue = condition.value.getValue(); + checkgroupValue.split(',').forEach(value => { + querys.push({ + 'FilterField': condition.fieldCode, + 'Compare': (condition.compareType || condition.compareType === 0) ? condition.compareType : CompareType.Equal, + 'Value': value, + 'Relation': condition.compareType === CompareType.NotEqual ? RelationType.And : RelationType.Or, + 'Expresstype': ValueType.Value + }); + }); + querys[0]['Lbracket'] = condition.Lbracket ? (condition.Lbracket + '(') : '('; + querys[querys.length - 1]['Rbracket'] = condition.Rbracket ? (condition.Rbracket + ')') : ')'; + querys[querys.length - 1]['Relation'] = (condition.relation || condition.relation === 0) ? condition.relation : RelationType.And; + return querys; + } function convertRadioGroup(condition) { const querys: QueryItem[] = []; const radioGroupValue = condition.value.getValue(); @@ -199,6 +215,9 @@ export function useCondition(props: QuerySolutionProps, context: SetupContext): case 'datetime-range': querysResult = convertDateRange(condition); break; + case 'check-group': + querysResult = convertCheckGroup(condition); + break; case 'combo-list': querysResult = convertComboList(condition, field); break; @@ -210,13 +229,26 @@ export function useCondition(props: QuerySolutionProps, context: SetupContext): } return querysResult; } + /** + * 为了保证条件顺序,方便比对,以fields为参照,按照顺序查找conditions + * @param conditions + * @param fields + * @returns + */ function getFilterConditions(conditions, fields): QueryItem[] { const filterConditionList: QueryItem[] = []; - conditions.forEach(condition => { - if (!condition.value.isEmpty()) { - const conditionField = fields.find(field => field.id === condition.id); - const convertResult = convertToQueryCondition(condition, conditionField); - filterConditionList.push(...convertResult); + if (!conditions || conditions.length === 0) { + return filterConditionList; + } + fields.forEach((currentField) => { + if (currentField.visible) { + // 字段可见 + const foundCondtion = conditions.find(condition => currentField.id === condition.id); + // 找到当前字段对应的条件 + if (foundCondtion && !foundCondtion.value.isEmpty()) { + const convertResult = convertToQueryCondition(foundCondtion, currentField); + filterConditionList.push(...convertResult); + } } }); return filterConditionList; diff --git a/packages/ui-vue/components/query-solution/src/composition/use-header.tsx b/packages/ui-vue/components/query-solution/src/composition/use-header.tsx index 1af6e1a009d21dbc9e31a7404eaffee24fcf9de2..4182b9c45746a5bd1a71991298cb00adfe48ca38 100644 --- a/packages/ui-vue/components/query-solution/src/composition/use-header.tsx +++ b/packages/ui-vue/components/query-solution/src/composition/use-header.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UseHeader, TransferItme, UseHttp, UseSolution } from './types'; +import { UseHeader, TransferItme, UseHttp, UseSolution, MobileUtils } from './types'; import { computed, inject, ref, Ref, SetupContext } from 'vue'; import { QuerySolutionProps } from '../query-solution.props'; import { QuerySolution } from '../query-solution'; @@ -28,7 +28,8 @@ import FSolutionManager from '../components/manager/solution-manager.component'; import { FTooltip } from '@farris/ui-vue/components/tooltip'; import { FCheckboxGroup } from '@farris/ui-vue/components/checkbox-group'; import { getLocaleFilterText, useHeaderLocale } from '../locale/locale'; - +import { isMobilePhone } from '@farris/ui-vue/components/common'; +import { JSX } from "vue/jsx-runtime"; export function useHeader( props: QuerySolutionProps, @@ -38,10 +39,12 @@ export function useHeader( solutions: Ref, changingSolutions: Ref, currentSolution: Ref, - fields: Ref + fields: Ref, + mobileUtils: MobileUtils ): UseHeader { const { saveAsDialogLocale, manageDialogLocale, configDialogLocale } = useHeaderLocale(); - + // 是否在小屏的移动设备上 + const isMobilePhoneState = isMobilePhone(); const querysolutionTypeRadios = [ { id: 'private', text: saveAsDialogLocale.private }, { id: 'public', text: saveAsDialogLocale.system } @@ -55,9 +58,9 @@ export function useHeader( const defaultSolutionId = ref(null); const configConditions = ref([]); const initQuery = ref(props.initQuery); - const expanded = ref(props.expanded); + const expanded = ref(isMobilePhoneState ? false : props.expanded); const { createSolution, updateSolution, getAuth, batchUpdate } = useHttpComposition; - const { getGuid, loadAllSolution, handleQuery, fieldToCondition, handleSolution, query } = useSolutionComposition; + const { getGuid, loadAllSolution, handleQuery, fieldToCondition, handleSolution, query, judgeLoadNewValue, defaultValues } = useSolutionComposition; const modalService: FModalService | null = inject(F_MODAL_SERVICE_TOKEN, null); const title = computed(() => currentSolution.value ? currentSolution.value.name : ''); const filterText = computed(() => getLocaleFilterText(props.filterText)); @@ -72,7 +75,6 @@ export function useHeader( const solutionListCloseModakClass = computed(() => ({ 'z-index': 99 })); - function onSelectionChange(solution: QuerySolution) { if (solution.id === currentSolution.value?.id) { return; @@ -254,7 +256,7 @@ export function useHeader( const saveModal = modalService?.open({ width: 600, height: 210, - draggable:true, + draggable: true, title: saveAsDialogLocale.caption, fitContent: false, buttons: [ @@ -334,7 +336,6 @@ export function useHeader( } function renderSummary() { - if (!summaryConditions.value.length) { return null; } @@ -352,22 +353,43 @@ export function useHeader( }
    - ); } - + /** + * 点击确定按钮 + */ function configConfirm() { if (currentSolution.value) { + /** + * 使用默认值:当默认值指定应用范围是scope或者指定默认范围是default,当前是系统预制方案 + */ + const useDefaultValue = defaultValues.value?.data.length && (defaultValues.value.scope === 'all' || (defaultValues.value.scope === 'default' && currentSolution.value.type === 'preset')); const currentConditions = currentSolution.value.conditions || []; const newConditions: Partial[] = []; configConditions.value.forEach(configCondition => { + // 如果当前方案条件中已有该字段,就使用之前的设置 const existCondition = currentConditions.find(condition => condition.id === configCondition.id); if (existCondition) { newConditions.push(existCondition); } else { - const newCondition = fields.value.find(condition => condition.id === configCondition.id); - if (newCondition) { - newConditions.push(fieldToCondition(newCondition)); + // 如果是新新添加的字段 + const targetField = fields.value.find(condition => condition.id === configCondition.id); + if (targetField) { + const defaultValue = useDefaultValue && defaultValues.value.data.find(defaultValue => defaultValue.id === targetField.id || defaultValue.labelCode === targetField.labelCode); + // 此处可能通过更新字段,字段中存在值 + if (defaultValue || targetField.defaulValue) { + // 存在默认值 或者字段存储值 + const currentCondition = fieldToCondition(targetField, defaultValue?.value || targetField.defaulValue); + // 校验值 + const loadNewValue = judgeLoadNewValue(currentCondition, targetField); + // 清空值 + loadNewValue && currentCondition.value.clear(); + newConditions.push(currentCondition); + } else { + const currentCondition = fieldToCondition(targetField); + newConditions.push(currentCondition); + } + } } }); @@ -393,13 +415,32 @@ export function useHeader( function changeConfigConditions(transferData) { configConditions.value = transferData; } - + /** + * 避免解除样式的删除 + * @param item + * @returns + */ + function checkBeforeRemoveItemHandler(item){ + const foundedCondition=currentSolution.value?.conditions.find(condition=>condition['id']===item['id']); + return foundedCondition?.visible===false?false:true; + } + function selectedItemClassHandler(item){ + const foundedCondition=currentSolution.value?.conditions.find(condition=>condition['id']===item['id']); + return foundedCondition?.visible===false?'query-solution-config--hidden-item':''; + } function showConfig() { configConditions.value = []; - const selections = currentSolution.value?.conditions.map((condition) => { + // filter(condition => condition.visible === undefined ? true : condition.visible) + const visibleFields=fields.value.filter(condition => condition.visible === undefined ? true : condition.visible); + const selections = currentSolution.value?.conditions.filter(condition => { + const found=visibleFields.find(item=>item.id===condition.id); + return found?true:false; + }).map((condition) => { + return { id: condition.id || '', name: condition.fieldName || '' }; }) || []; - const dataSource = fields.value.map((field) => { + + const dataSource = visibleFields.map((field) => { return { id: field.id, name: field.name }; }) || []; configConditions.value = selections; @@ -420,20 +461,75 @@ export function useHeader( data-source={dataSource} class="query-solution-config-transfer ml-3 h-100" onChange={changeConfigConditions} + checkBeforeRemoveItem={checkBeforeRemoveItemHandler} + selectedItemClass={selectedItemClassHandler} style="height:auto;" >; } }); } - + /** + * 关闭小屏弹窗 + */ + function closeMobileModal() { + mobileUtils.modalInstance?.destroy(); + mobileUtils.modalInstance = null; + summaryConditions.value = currentSolution.value?.conditions + .filter(condition => !condition.value?.isEmpty()) + .map(condition => condition.fieldName + ':' + condition.value?.getDisplayText()) || []; + } + function queryMobileModal() { + query(() => { + closeMobileModal(); + }); + } + // 解决校验问题 + let renderMobileModalContent: () => JSX.Element = function () { return <>; }; function handleExpand() { + if (isMobilePhoneState) { + // 打开小屏弹窗 + mobileUtils.modalInstance = modalService?.open({ + width: 900, + height: 600, + showHeader: false, + showButtons: false, + fitContent: false, + render: () => { + return renderMobileModalContent(); + } + }); + return; + } expanded.value = !expanded.value; - if(!expanded.value){ + if (!expanded.value) { summaryConditions.value = currentSolution.value?.conditions - .filter(condition => !condition.value?.isEmpty()) - .map(condition => condition.fieldName + ':' + condition.value?.getDisplayText()) || []; + .filter(condition => !condition.value?.isEmpty()) + .map(condition => condition.fieldName + ':' + condition.value?.getDisplayText()) || []; } } + + function renderMobileSolutionToolbar() { + return ( +
    +
    + +
    + {shouldShowClearButton.value && ( +
    + +
    + )} +
    + +
    +
    + +
    +
    + ); + } function renderSolutionToolbar() { return (
    @@ -443,21 +539,21 @@ export function useHeader(
    } {shouldShowClearButton.value && ( -
    +
    )} - {expanded.value &&
    + {expanded.value &&
    } -
    +
    ); } - function renderHeader() { + function renderHeader(toolbarType: 'default' | 'mobile' = 'default') { return (
    @@ -472,15 +568,23 @@ export function useHeader(
    }
    -
    + {toolbarType === 'default' &&
    {!expanded.value && renderSummary()} -
    - - {renderSolutionToolbar()} +
    } + {toolbarType === 'default' && renderSolutionToolbar()} + {toolbarType === 'mobile' && renderMobileSolutionToolbar()}
    ); } - + renderMobileModalContent = function () { + return
    + {renderHeader('mobile')} + {mobileUtils.renderContent()} +
    ; + }; + /** + * 字段配置数据源进行规范化,根据字段编辑器类型,初始化一些必要属性 + */ function loadFields() { // 部分属性不需要进行配置且使用的值与组件默认值不一致需要在此处理 // editor.type为运行时dynamicFormGroup实际的控件type, controlType 为筛选方案分化的type @@ -492,14 +596,14 @@ export function useHeader( if (currentField.editor.type === 'number-spinner' || currentField.editor.type === 'number-range') { currentField.editor.showZero = true; currentField.editor.nullable = true; - } else if (currentField.editor.type === 'combo-list') { - currentField.editor.enableClear = true; } else if (currentField.editor.type === 'datetime-picker') { currentField.editor.type = 'date-picker'; currentField.editor.showTime = true; } else if (currentField.editor.type === 'lookup') { currentField.editor.enableTitle = true; } + // 指定是否可见 + currentField.visible = Object.prototype.hasOwnProperty.call(currentField, 'visible') ? currentField.visible : true; return currentField; }); } diff --git a/packages/ui-vue/components/query-solution/src/composition/use-http.ts b/packages/ui-vue/components/query-solution/src/composition/use-http.ts index c73eb6b43df72afb88a2889e7453113d2282ce42..197ab101cc4d993e843970f6bb65480f4b97a72b 100644 --- a/packages/ui-vue/components/query-solution/src/composition/use-http.ts +++ b/packages/ui-vue/components/query-solution/src/composition/use-http.ts @@ -36,9 +36,9 @@ export function useHttp(props: QuerySolutionProps, context: SetupContext): UseHt } /** - * 根据表单ID查询当前表单当前登录用户的查询方案列表 - * @param formId 表单ID - */ + * 根据表单ID查询当前表单当前登录用户的查询方案列表 + * @param formId 表单ID + */ function loadSolution() { const options = { headers: { diff --git a/packages/ui-vue/components/query-solution/src/composition/use-solution-utils.ts b/packages/ui-vue/components/query-solution/src/composition/use-solution-utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9d55915d3071056e1757f21481aabbd479a6ded --- /dev/null +++ b/packages/ui-vue/components/query-solution/src/composition/use-solution-utils.ts @@ -0,0 +1,249 @@ +import { Condition, FieldConfig, useConditionValue } from '@farris/ui-vue/components/condition'; +import { EditorType } from '@farris/ui-vue/components/dynamic-form'; +import { parse, format } from 'date-fns'; +import { cloneDeep } from 'lodash-es'; +export function useSolutionUtils() { + + const { createConditionValue } = useConditionValue(); + function fieldToCondition(field: FieldConfig, initValue?): Condition { + const condition = { + id: field.id, + fieldCode: field.labelCode, + fieldName: field.name, + required: field.editor.required, + visible: field.visible === undefined ? true : field.visible, + // 只读和禁用属性 + disabled: field.editor.disabled === undefined ? (field.editor.readonly === undefined ? false : field.editor.readonly) : field.editor.disabled, + value: initValue ? createConditionValue(field.controlType as EditorType, initValue, field.editor) : createConditionValue(field.controlType as EditorType, undefined, field.editor) // 使用undefined调用默认值 + }; + switch (field.controlType) { + case 'lookup': + condition.value.valueField = field.editor.valueField; + condition.value.helpId = field.editor.helpId; + break; + case 'date-range': + case 'date-picker': + condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM-dd'; + condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM-dd'; + break; + case 'datetime-range': + case 'datetime-picker': + condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM-dd HH:mm:ss'; + condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM-dd HH:mm:ss'; + break; + case 'month-range': + case 'month-picker': + condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM'; + condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM'; + break; + case 'year-picker': + condition.value.displayFormat = field.editor.displayFormat || 'yyyy'; + condition.value.valueFormat = field.editor.valueFormat || 'yyyy'; + break; + case 'input-group': + if (field.editor.queryIgnoreCase) { + // 忽略大小写 + condition['compareType'] = 16; + } + break; + + } + return condition as Condition; + } + /** + * 从旧格式转换到新日期 + * @param oldValue + * @param oldValueFormat + * @param newValueFormat + * @returns + */ + function transferDateToNewValueFormat(oldValue, oldValueFormat, newValueFormat) { + const parsedDate = parse(oldValue, oldValueFormat, new Date()); + return format(parsedDate, newValueFormat); + } + function judgePrecisionValid(value: number | null, precision: number) { + let isValid = true; + if (value !== null) { + const decimalPart = value.toString().split('.')[1]; + const nowPrecision = decimalPart?.length || 0; + if (nowPrecision > precision) { + isValid = false; + } + } + return isValid; + } + function judgeLoadNewValue(condition: Condition, field: FieldConfig) { + // undo: 优化枚举格式 + let loadNewValue = false; + // datetime-picker date-picker本质都为date-picker只是editior的showTime属性不一致 + if (condition.value.editorType !== field.controlType) { + loadNewValue = true; + return loadNewValue; + } + switch (field.controlType) { + case 'lookup': + if (condition.value.valueField !== field.editor.valueField || condition.value.helpId !== field.editor.helpId) { + loadNewValue = true; + } + break; + case 'year-picker': + case 'month-picker': + case 'date-picker': + case 'datetime-picker': { + // 针对文本类型的日期控件,可以修改日期格式需要兼容 + if (field.editor.valueFormat && condition.value.valueFormat && field.editor.valueFormat !== condition.value.valueFormat) { + if (condition.value.value) { + condition.value.value = transferDateToNewValueFormat(condition.value.value, condition.value.valueFormat, field.editor.valueFormat); + } + loadNewValue = condition.value.valueFormat === field.editor.valueFormat; + + } + break; + } + case 'date-range': + case 'datetime-range': + case 'month-range': { + // 针对文本类型的日期控件,可以修改日期格式需要兼容 + if (field.editor.valueFormat && condition.value.valueFormat && field.editor.valueFormat !== condition.value.valueFormat) { + if (condition.value.begin) { + condition.value.begin = transferDateToNewValueFormat(condition.value.begin, condition.value.valueFormat, field.editor.valueFormat); + } + if (condition.value.end) { + condition.value.end = transferDateToNewValueFormat(condition.value.begin, condition.value.valueFormat, field.editor.valueFormat); + } + loadNewValue = condition.value.valueFormat === field.editor.valueFormat; + } + break; + } + case 'check-group': + case 'combo-list': { + const valids = field.editor.data.map(enumValue => enumValue.value); + if (condition.value.valueList?.find(value => valids.indexOf(value.value) === -1)) { + loadNewValue = true; + } + break; + } + case 'number-spinner': + loadNewValue = !judgePrecisionValid(condition.value.value, field.editor.precision); + break; + case 'number-range': + loadNewValue = !judgePrecisionValid(condition.value.begin, field.editor.precision) || !judgePrecisionValid(condition.value.end, field.editor.precision); + break; + default: + } + return loadNewValue; + } + /** + * 提取变动属性比较,避免指定多次更改方法问题 + * @param arrayList + * ---TODO---item.value是个复杂对象,此处可能通过item.value.getDisplayText()只比较值 + * @returns + */ + function getComparedList(arrayList) { + return arrayList.map(item => { + return { + fieldCode:item.fieldCode, + visible: Object.prototype.hasOwnProperty.call(item, 'visible') ? item.visible : true, + disabled: Object.prototype.hasOwnProperty.call(item, 'disabled') ? item.visible : false, + value: item.value + }; + }); + } + /** + * + * @param solution 需要更新配置的筛选方案 + * @param fieldConfigs 更新配置 + * @returns 已更新配置的筛选方案 + */ + function updateConditionValue(conditions: Partial[], fieldConfigs: Array, currentSolution: any) { + let tempCondition: Partial; + conditions = conditions ? conditions : []; + if (conditions.length > 0) { + const oldConditions = cloneDeep(getComparedList(conditions)); + fieldConfigs.forEach(field => { + tempCondition = conditions.find(conditionItem => { + if (field['labelCode']) { + return conditionItem['fieldCode'] === field['labelCode']; + } + if (field['id']) { + return conditionItem.id === field['id']; + } + return false; + }); + if (!tempCondition) { + return; + } + if (Object.prototype.hasOwnProperty.call(field, 'visible')) { + // 处理条件的可见 + tempCondition.visible = field['visible']; + } + if (field.editor) { + // 处理条件的禁用状态 + if (Object.prototype.hasOwnProperty.call(field.editor, 'readonly')) { + tempCondition['disabled'] = field.editor['readonly']; + } + if (Object.prototype.hasOwnProperty.call(field.editor, 'disabled')) { + tempCondition['disabled'] = field.editor['disabled']; + } + } + // 更新值相关属性 + if (tempCondition.value && field.value) { + for (const prop in field.value) { + tempCondition.value[prop] = field.value[prop]; + } + } + if (!tempCondition.visible && tempCondition.value) { + // 不可见,清空value值 + tempCondition.value.clear(); + } + }); + const changedConditions=getComparedList(conditions); + if(JSON.stringify(oldConditions)!==JSON.stringify(changedConditions)){ + currentSolution.value.hasChanged = true; + } + conditions=cloneDeep(conditions); + } + + } + function updateFieldsValue(fieldConfigList: Array, fieldConfigs: Array) { + let tempField: FieldConfig | undefined; + fieldConfigList = fieldConfigList ? fieldConfigList : []; + if (fieldConfigList.length > 0) { + fieldConfigs.forEach(field => { + tempField = fieldConfigList.find(fieldItem => { + if (field['labelCode']) { + return fieldItem['labelCode'] === field['labelCode']; + } + if (field['id']) { + return fieldItem['id'] === field['id']; + } + return false; + }); + if (tempField && Object.prototype.hasOwnProperty.call(field, 'visible')) { + tempField.visible = field['visible']; + } + // 更新编辑器相关属性 + if (tempField && tempField.editor && field.editor) { + for (const prop in field.editor) { + if (['readonly', 'disabled'].indexOf(prop) > -1) { + // 不同组件处理只读、禁用不一致情况 + tempField.editor['readonly'] = field.editor['readonly']; + tempField.editor['disabled'] = field.editor['disabled']; + } else { + tempField.editor[prop] = field.editor[prop]; + } + } + } + if (tempField && field.value) { + // 存储默认值 + tempField.defaulValue = field.value; + } + }); + if(fieldConfigs.length>0){ + fieldConfigs=cloneDeep(fieldConfigs); + } + } + } + + return { fieldToCondition, judgeLoadNewValue, updateConditionValue, updateFieldsValue }; +} diff --git a/packages/ui-vue/components/query-solution/src/composition/use-solution.ts b/packages/ui-vue/components/query-solution/src/composition/use-solution.ts index a588400c2c9d89b9f7b362de8a08b73d40d8b605..121eb509db1c1aba0f8fdbd30f5abf90da482c35 100644 --- a/packages/ui-vue/components/query-solution/src/composition/use-solution.ts +++ b/packages/ui-vue/components/query-solution/src/composition/use-solution.ts @@ -20,11 +20,9 @@ import { QuerySolution } from '../query-solution'; import { Condition, FieldConfig } from '@farris/ui-vue/components/condition'; import { cloneDeep } from 'lodash-es'; import { useCondition } from './use-condition'; -import { useConditionValue } from '@farris/ui-vue/components/condition'; -import { EditorType } from '@farris/ui-vue/components/dynamic-form'; -import { parse, format } from 'date-fns'; import { FNotifyService } from "@farris/ui-vue/components/notify"; import { getLocalePresetQuerySolutionName, useSolutionLocale } from '../locale/locale'; +import { useSolutionUtils } from './use-solution-utils'; export function useSolution( @@ -38,7 +36,6 @@ export function useSolution( ): UseSolution { const { containerLocale } = useSolutionLocale(); const notifyService: any = new FNotifyService(); - const { createConditionValue } = useConditionValue(); const { getFilterConditions } = useCondition(props, context); const { loadSolution } = useHttpComposition; const defaultValues = ref(props.defaultValues); @@ -50,7 +47,7 @@ export function useSolution( let willSetDefaultValues = false; const queryData = ref([]); const defaultSolutionName = getLocalePresetQuerySolutionName(props.presetQuerySolutionName); - + const { fieldToCondition, judgeLoadNewValue } = useSolutionUtils(); function getRandomId() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); } @@ -58,80 +55,29 @@ export function useSolution( return (getRandomId() + getRandomId() + "-" + getRandomId() + "-" + getRandomId() + "-" + getRandomId() + "-" + getRandomId() + getRandomId() + getRandomId()); } - function query() { - try { - currentSolution.value?.conditions.map(condition => { - if (condition.required && (!condition.value || condition.value.isEmpty())) { - throw containerLocale.require.replace('{fields}', condition.fieldName || ''); - } - }); - context.emit('query', queryData.value); - } catch (error) { - notifyService.warning({ message: error, position: 'top-center' }); - } - - } - function judgePrecisionValid(value: number | null, precision: number) { - let isValid = true; - if (value !== null) { - const decimalPart = value.toString().split('.')[1]; - const nowPrecision = decimalPart?.length || 0; - if (nowPrecision > precision) { - isValid = false; + function query(queryCallback=()=>{}) { + const requiredFields = [] as any; + currentSolution.value?.conditions.map(condition => { + if (condition.visible && condition.required && (!condition.value || condition.value.isEmpty())) { + requiredFields.push(condition.fieldName || ''); } + }); + if (requiredFields.length > 0) { + notifyService.warning({ message: containerLocale.require.replace('[fields]', requiredFields.join(",")), position: 'top-center' }); + return; } - return isValid; - } - - - function fieldToCondition(field: FieldConfig, initValue?): Condition { - const condition = { - id: field.id, - fieldCode: field.labelCode, - fieldName: field.name, - required: field.editor.required, - value: initValue ? createConditionValue(field.controlType as EditorType, initValue, field.editor) : createConditionValue(field.controlType as EditorType, undefined, field.editor) // 使用undefined调用默认值 - }; - switch (field.controlType) { - case 'lookup': - condition.value.valueField = field.editor.valueField; - condition.value.helpId = field.editor.helpId; - break; - case 'date-range': - case 'date-picker': - condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM-dd'; - condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM-dd'; - break; - case 'datetime-range': - case 'datetime-picker': - condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM-dd HH:mm:ss'; - condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM-dd HH:mm:ss'; - break; - case 'month-range': - case 'month-picker': - condition.value.displayFormat = field.editor.displayFormat || 'yyyy-MM'; - condition.value.valueFormat = field.editor.valueFormat || 'yyyy-MM'; - break; - case 'year-picker': - condition.value.displayFormat = field.editor.displayFormat || 'yyyy'; - condition.value.valueFormat = field.editor.valueFormat || 'yyyy'; - break; - case 'input-group': - if(field.editor.queryIgnoreCase){ - // 忽略大小写 - condition['compareType'] = 16; - } - break; - + context.emit('query', queryData.value); + if (queryCallback && typeof queryCallback === 'function') { + queryCallback(); } - return condition as Condition; } + /** * 获取默认筛选方案 * @returns */ function getDefaultSolution(): QuerySolution { - const presetFields = props.presetFields.map(presetField => fields.value.find(field => field.id === presetField.id)); + const presetFields = props.presetFields.filter(presetField => fields.value.find(field => field.visible && field.id === presetField.id)); const defaultSolution = { id: getGuid(), code: 'Default', @@ -142,95 +88,32 @@ export function useSolution( isDefault: false, hasChanged: false, isSystem: true, - conditions: presetFields.map((field: FieldConfig) => { + conditions: (presetFields || []).map((field: FieldConfig) => { return fieldToCondition(field); }) }; return defaultSolution; } + function judgeEmitEvent(querys) { + const newCondtionsString = JSON.stringify(querys || []); + if (newCondtionsString !== JSON.stringify(queryData.value || [])) { + queryData.value = querys; + // 条件变更 + context.emit('conditionChange', querys); + } + } /** * 通知外部条件变更 * 常被用来保持展示与服务里的条件一致 */ function handleQuery() { const queryItems = currentSolution.value?.conditions.filter(condition => { - return !condition.value?.isEmpty(); + return condition.visible && !condition.value?.isEmpty(); }); - const querys = getFilterConditions(queryItems, fields.value); - queryData.value = querys; - context.emit('conditionChange', querys); + const querys = getFilterConditions(queryItems || [], fields.value); + judgeEmitEvent(querys); } - /** - * 从旧格式转换到新日期 - * @param oldValue - * @param oldValueFormat - * @param newValueFormat - * @returns - */ - function transferDateToNewValueFormat(oldValue, oldValueFormat, newValueFormat) { - const parsedDate = parse(oldValue, oldValueFormat, new Date()); - return format(parsedDate, newValueFormat); - } - function judgeLoadNewValue(condition: Condition, field: FieldConfig) { - // undo: 优化枚举格式 - let loadNewValue = false; - // datetime-picker date-picker本质都为date-picker只是editior的showTime属性不一致 - if (condition.value.editorType !== field.controlType) { - loadNewValue = true; - return loadNewValue; - } - switch (field.controlType) { - case 'lookup': - if (condition.value.valueField !== field.editor.valueField || condition.value.helpId !== field.editor.helpId) { - loadNewValue = true; - } - break; - case 'year-picker': - case 'month-picker': - case 'date-picker': - case 'datetime-picker': { - // 针对文本类型的日期控件,可以修改日期格式需要兼容 - if (field.editor.valueFormat && condition.value.valueFormat && field.editor.valueFormat !== condition.value.valueFormat) { - if (condition.value.value) { - condition.value.value = transferDateToNewValueFormat(condition.value.value, condition.value.valueFormat, field.editor.valueFormat); - } - loadNewValue = condition.value.valueFormat === field.editor.valueFormat; - } - break; - } - case 'date-range': - case 'datetime-range': - case 'month-range': { - // 针对文本类型的日期控件,可以修改日期格式需要兼容 - if (field.editor.valueFormat && condition.value.valueFormat && field.editor.valueFormat !== condition.value.valueFormat) { - if (condition.value.begin) { - condition.value.begin = transferDateToNewValueFormat(condition.value.begin, condition.value.valueFormat, field.editor.valueFormat); - } - if (condition.value.end) { - condition.value.end = transferDateToNewValueFormat(condition.value.begin, condition.value.valueFormat, field.editor.valueFormat); - } - loadNewValue = condition.value.valueFormat === field.editor.valueFormat; - } - break; - } - case 'combo-list': { - const valids = field.editor.data.map(enumValue => enumValue.value); - if (condition.value.valueList?.find(value => valids.indexOf(value.value) === -1)) { - loadNewValue = true; - } - break; - } - case 'number-spinner': - loadNewValue = !judgePrecisionValid(condition.value.value, field.editor.precision); - break; - case 'number-range': - loadNewValue = !judgePrecisionValid(condition.value.begin, field.editor.precision) || !judgePrecisionValid(condition.value.end, field.editor.precision); - break; - default: - } - return loadNewValue; - } /** * @@ -243,25 +126,46 @@ export function useSolution( if (existChangingsolution && isInit) { return existChangingsolution; } + /** + * 使用默认值:当默认值指定应用范围是scope或者指定默认范围是default,当前是系统预制方案 + */ const useDefaultValue = defaultValues.value?.data.length && (defaultValues.value.scope === 'all' || (defaultValues.value.scope === 'default' && solution.type === 'preset')); const conditions = (!isInit || solution.type === 'preset') ? solution.conditions : JSON.parse(solution.queryConditionString || ''); solution.conditions = []; conditions.forEach((condition: Condition) => { - const targetField = fields.value.find((field: FieldConfig) => field.labelCode === condition.fieldCode); + const targetField = fields.value.find((field: FieldConfig) => field.visible && field.labelCode === condition.fieldCode); if (targetField) { - const defaultValue = useDefaultValue && defaultValues.value.data.find(defaultValue => defaultValue.id === condition.id); + const defaultValue = useDefaultValue && defaultValues.value.data.find(defaultValue => defaultValue.id === condition.id || defaultValue.labelCode === condition.fieldCode); if (defaultValue) { // 特殊情况,需要检测数字输入控件精度与默认值是否匹配,否则可能出现实际值与展示值不一致的情况,如果默认值超过精度控制,值清空 const currentCondition = fieldToCondition(targetField, defaultValue.value); const loadNewValue = judgeLoadNewValue(currentCondition, targetField); + // // 条件里存储了可见和兼用 + // if( Object.prototype.hasOwnProperty.call(condition, 'visible')){ + // currentCondition.visible=condition.visible + // } + // if( Object.prototype.hasOwnProperty.call(condition, 'disabled')){ + // currentCondition.disabled=condition.disabled + // } loadNewValue && currentCondition.value.clear(); solution.conditions.push(currentCondition); - if (defaultValues.value.readonly) { - targetField.editor.readonly = true; - } + // 默认值的readonly原只能指定修改条件的只读属性,是不能修改整个fieldConfig。 + // 但目前editor都是通过fieldConfig控制,后续处理 + // if (defaultValues.value.hasOwnProperty('readonly')) { + // // 默认值指定筛选方案的只读属性 + // targetField.editor.disabled = defaultValues.value.readonly; + // targetField.editor.readonly = defaultValues.value.readonly; + // } } else { - const loadNewValue = judgeLoadNewValue(condition, targetField); - const currentCondition = fieldToCondition(targetField, loadNewValue ? null : condition.value); + let loadNewValue = judgeLoadNewValue(condition, targetField); + let currentCondition = fieldToCondition(targetField, loadNewValue ? null : condition.value); + if(currentCondition.value.isEmpty()&&targetField.defaulValue){ + // 计算有值 + condition.value=Object.assign({},condition.value,targetField.defaulValue); + loadNewValue = judgeLoadNewValue(condition, targetField); + currentCondition = fieldToCondition(targetField, loadNewValue ? null : condition.value); + solution.hasChanged=true; + } solution.conditions.push(currentCondition); } } @@ -281,7 +185,7 @@ export function useSolution( return [...solutionsUpdated, ...solutionsAppend]; } /** - * + * 获取props.formId关联的筛选方案列表 * @param loadSolutionOptions openSolutionID:指定打开的方案id,enableQuery:是否立即启用查询,willSetDefaultValues:是否将要设置默认值 */ function loadAllSolution(loadSolutionOptions: { openSolutionID?: string, enableQuery?: boolean, willSetDefaultValues?: boolean, isSaveAs?: boolean }) { @@ -347,7 +251,7 @@ export function useSolution( if (!newDefaultValues || !newDefaultValues.scope) { return; } - defaultValues.value = Object.assign({}, props.defaultValues, newDefaultValues); + defaultValues.value = Object.assign({}, newDefaultValues); // 已设置默认值 doneSetDefaultValue = true; // 此时方案列表可能还没有赋值,没有当前方案,如果有当前方案执行赋值 @@ -370,7 +274,9 @@ export function useSolution( handleQuery, fieldToCondition, handleSolution, - query + query, + judgeLoadNewValue, + defaultValues }; } diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/types.ts b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/types.ts index 7d704e692c20c7c196f86515ce3d801d0b660ec8..197629629445cfad52d4d33335f4646224856b42 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/types.ts +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/types.ts @@ -19,7 +19,7 @@ import { TransferItem } from '../query-solution-config.props'; import { JSX } from "vue/jsx-runtime"; export type ControlType = 'number-spinner' | 'number-range' | 'combo-list' | 'lookup' | 'combo-lookup' | 'date-range' | 'date-picker' - | 'radio-group' | 'input-group' | 'datetime-picker'|'datetime-range'|'month-range'|'month-picker'|'year-picker'; + | 'radio-group'| 'check-group' | 'input-group' | 'datetime-picker'|'datetime-range'|'month-range'|'month-picker'|'year-picker'; export interface UseTransfer { @@ -29,7 +29,7 @@ export interface UseTransfer { */ renderTransfer: () => JSX.Element | null; initData: () => void; - confirm: () => void; + confirm: (data?) => void; cancel: () => void; } @@ -94,9 +94,11 @@ export interface PropertyConfig { export interface UsePanel { renderPanel: () => JSX.Element | null; + updateDialogActions:(actions:any)=>void; } export interface UseProperty { getPropertyConfig: (propertyData: PropertyItem) => PropertyConfig; - refreshPanelFlag:Ref + refreshPanelFlag:Ref; + updateDialogActions:(actions:any)=>void; } diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-panel.tsx b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-panel.tsx index 4782163380c74ecc6d4e09e942f8110a11223334..52d63c5b5930f9090704c7710d6564d3bd0555b8 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-panel.tsx +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-panel.tsx @@ -19,6 +19,7 @@ import { QuerySolutionConfigProps, Source } from '../query-solution-config.props import { FPropertyPanel } from '@farris/ui-vue/components/property-panel'; import { useProperty } from './use-property'; import { useUtil } from './use-util'; +import { DgControl } from '@farris/ui-vue/components/designer-canvas'; export function usePanel(props: QuerySolutionConfigProps, context: SetupContext, activePanel: Ref, fieldsData: Map): UsePanel { @@ -31,9 +32,9 @@ export function usePanel(props: QuerySolutionConfigProps, context: SetupContext, const propertyConfig = ref(); // 当前选中的筛选项双向绑定的值 const currentPanelValue = ref(null); - const { getPropertyConfig,refreshPanelFlag } = useProperty(props, context); - const { getExtendSchemaProerty } = useUtil('query-solution'); - + const { getPropertyConfig, refreshPanelFlag, updateDialogActions } = useProperty(props, context); + const { getExtendSchemaProerty } = useUtil(props.source); + watch(() => activePanel.value?.id, (newId, oldId) => { if (!newId) { currentPanelValue.value = null; @@ -59,7 +60,7 @@ export function usePanel(props: QuerySolutionConfigProps, context: SetupContext, /** * 根据当前属性刷新属性面板 */ - function updatePropertyConfig(refreshFlag = true){ + function updatePropertyConfig(refreshFlag = true) { const newPropertyConfig = getPropertyConfig(currentPanelValue.value); PropertyPanelInstance.value.updatePropertyConfig(newPropertyConfig, currentPanelValue.value, refreshFlag); } @@ -68,19 +69,20 @@ export function usePanel(props: QuerySolutionConfigProps, context: SetupContext, */ watch(() => [currentPanelValue.value?.id, currentPanelValue.value?.controlType], ([newId, newControlType], [oldId, oldControlType]) => { if (!newId) { - PropertyPanelInstance.value.updatePropertyConfig({}, {}, true); + PropertyPanelInstance.value?.updatePropertyConfig({}, {}, true); } else if (newId !== oldId) { // 切换字段后 const newPropertyConfig = getPropertyConfig(currentPanelValue.value); - PropertyPanelInstance.value.updatePropertyConfig(newPropertyConfig, currentPanelValue.value, true); + PropertyPanelInstance.value?.updatePropertyConfig(newPropertyConfig, currentPanelValue.value, true); } else if (newControlType !== oldControlType) { // 控件类型变更后 const currentField = fieldsData.get(newId); const basicSchema = getBasicSchemaProerty(currentField); basicSchema.controlType = newControlType; - const newField:PropertyItem = { ...basicSchema, ...getExtendSchemaProerty(newControlType, currentField) }; + // 构造新属性------此处可能会存在问题 + const newField: PropertyItem = { ...basicSchema, ...getExtendSchemaProerty(newControlType, currentField,currentField?.$typeInfo.name) }; - if(newControlType === 'number-spinner' || newControlType === 'number-range') { + if (newControlType === 'number-spinner' || newControlType === 'number-range') { newField.precision = currentField?.$typeInfo.precision; } fieldsData.set(newId, newField); @@ -88,22 +90,43 @@ export function usePanel(props: QuerySolutionConfigProps, context: SetupContext, updatePropertyConfig(); } }); - function hangdlePropertyChange(changeItem) { + /** + * 事件变更以及控件名称变更后,同步控件的路径信息 + */ + function updateControlBasicInfoMap(changeObject: any) { + const { categoryId, propertyID } = changeObject; + if (!props.source || !DgControl[props.source]) { + return; + } + if (categoryId === 'eventsEditor' || propertyID === 'name') { + const solutionName = DgControl[props.source].name; + + props.formSchemaUtils.getControlBasicInfoMap().set(currentPanelValue.value.id, { + showName: currentPanelValue.value.name, + parentPathName: `${solutionName} > ${currentPanelValue.value.name}` + }); + } + } + function handlePropertyChange(changeItem) { const { propertyID } = changeItem.changeObject; - if(propertyID === 'precision' || propertyID === 'max' || propertyID === 'min') { + if (propertyID === 'precision' || propertyID === 'max' || propertyID === 'min') { updatePropertyConfig(false); } - if(propertyID === 'valueFormat'){ + if (propertyID === 'valueFormat') { const newPropertyConfig = getPropertyConfig(currentPanelValue.value); const displayFormatData = newPropertyConfig.categories.control.properties.displayFormat.editor.data; - if(!displayFormatData.find(format => currentPanelValue.value.displayFormat === format.id)) { + if (!displayFormatData.find(format => currentPanelValue.value.displayFormat === format.id)) { currentPanelValue.value.displayFormat = displayFormatData[0].id; } PropertyPanelInstance.value.updatePropertyConfig(newPropertyConfig, currentPanelValue.value, false); } + + // 事件变更后,同步控件的路径信息 + updateControlBasicInfoMap(changeItem.changeObject); + } - watch(()=>refreshPanelFlag.value,(newValue)=>{ + watch(() => refreshPanelFlag.value, (newValue) => { updatePropertyConfig(false); }); /** @@ -115,9 +138,9 @@ export function usePanel(props: QuerySolutionConfigProps, context: SetupContext, propertyName={propertyName.value} enableSearch={false} schema={focusingSchema.value} - onPropertyChanged={hangdlePropertyChange}> ; + onPropertyChanged={handlePropertyChange}> ; } return { - renderPanel + renderPanel, updateDialogActions }; } diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-property.ts b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-property.ts index 9880eb290fd3efcfab979a205194c172f0dc93e5..2e7e25fc5e6e67b58a01074e7851239ba3afda95 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-property.ts +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-property.ts @@ -17,19 +17,31 @@ import { PropertyItem, UseProperty } from './types'; import { SetupContext, ref } from 'vue'; import { QuerySolutionConfigProps } from '../query-solution-config.props'; import querySolutionConfigExtend from "../property-config/query-solution-config-extend.json"; -import filterBarConfigExtend from "../property-config/filter-bar-config-extend.json"; import { getLookupEditorCommonProperties, getLookupDialogCommonProperties, getLookupPaginationProperties } from '@farris/ui-vue/components/lookup'; import { multiLineLabelConverter, placeholderConverter, queryEnumDefaultConverter } from '../converter/query-solution-config-property.converter'; import { useUtil } from './use-util'; import { DATE_FORMATS } from '@farris/ui-vue/components/date-picker'; import { useGuid } from '@farris/ui-vue/components/common'; +import { FMessageBoxService } from '@farris/ui-vue/components/message-box'; +import { cloneDeep } from 'lodash-es'; export function useProperty( props: QuerySolutionConfigProps, context: SetupContext ): UseProperty { - const { getControlTypeListByFieldType, getRealControlType } = useUtil('query-solution'); + const { getControlTypeListByFieldType, getRealControlType } = useUtil(props.source); const { uuid } = useGuid(); + const dialogActions = { + save: (datas) => { }, + close: () => { } + }; + /** + * 对外提供更新弹窗相关的操作 + * @param newActions + */ + function updateDialogActions(newActions) { + Object.assign(dialogActions, newActions); + } const refreshPanelFlag = ref(0); function getBasicConfig(propertyData) { return { @@ -187,7 +199,7 @@ export function useProperty( if (['showLimits', 'sizeLimits'].indexOf(changeObject.propertyID) > -1) { refreshPanelFlag.value++; } - } + }; return { dialog: { @@ -476,11 +488,24 @@ export function useProperty( } } - const baseProperties = Object.assign({}, commonProperties, querySolutionConfigExtend[getRealControlType(controlType)]); + const baseProperties = Object.assign({}, commonProperties, cloneDeep(querySolutionConfigExtend[getRealControlType(controlType)])); if (controlType === "combo-list" && propertyData.$typeInfo?.$type === 'BooleanType') { // 布尔类型通过下拉展示,隐藏多选属性 baseProperties.multiSelect.visible = false; } + // 筛选条类型时,去掉某些属性 + if (props.source === 'filter-bar') { + delete (baseProperties as any).multiLineLabel; + delete (baseProperties as any).class; + if (["number-range", "number-spinner"].indexOf(controlType) > -1) { + delete (baseProperties as any).textAlign; + } + if (["radio-group", "check-group"].indexOf(controlType) > -1) { + delete (baseProperties as any).placeholder; + delete (baseProperties as any).direction; + delete (baseProperties as any).showLabel; + } + } return baseProperties; } function getControlConfig(propertyData: any) { @@ -509,12 +534,13 @@ export function useProperty( // baseControlConfig.properties.dialog=dialogProperties; break; } + case 'check-group': case 'radio-group': case 'combo-list': { const { enumValues } = getEnumControlConfig(propertyData); baseControlConfig.properties.data = enumValues; - } break; + } case 'year-picker': case 'month-picker': case 'month-range': @@ -573,42 +599,107 @@ export function useProperty( } return baseControlConfig; - } + }; - function getEventsByControlType(controlType) { + function getEventsByControlType(controlType, propertyData) { const eventList = [ - { - label: "valueChangedCmd", - name: "值变化事件" - } - ]; + // { + // label: "valueChangedCmd", + // name: "值变化事件" + // } + ] as any; switch (controlType) { - case "combo-lookup": - case "lookup": - eventList.push({ - label: "preEventCmd", - name: "帮助前事件" - }, { - label: "preEventCmd", - name: "帮助前事件" - }); + case "lookup": { + const lookupEvents = [{ + label: 'clear', + name: '清空事件' + }, + { + label: 'dictPicking', + name: '帮助前事件' + }, + { + label: 'beforeLoadData', + name: '数据加载前事件' + }, + { + label: 'beforeSelectData', + name: '选择数据确认前事件' + }, + { + label: 'dictPicked', + name: '帮助后事件' + }]; + if (propertyData.openType && propertyData.openType === 'Popup') { + // 删除事件 beforeSelectData + lookupEvents.splice(3, 1); + } + eventList.push(...lookupEvents); break; + } case "combo-list": eventList.push({ - label: "beforeShow", - name: "面板展开前事件" + label: 'onClear', + name: '清空事件' }, { - label: "afterShow", - name: "面板关闭后事件" - }); + label: 'onChange', + name: '值变化事件' + }, + { + label: 'beforeOpen', + name: '打开下拉面板前' + }); break; } return eventList; } + // 通过添加新方法绑定命令时,会出现更新绑定方法名的异步差异 + // 第一调用setPropertyRelates创建命令,第二次调用setPropertyRelates更新命令 + const forAddControllerMethod = ref(false); + + function switchEvents(propertyData, eventList) { + if (["lookup", "combo-list"].findIndex(item => item === propertyData.controlType) < 0) { + return eventList;; + } + if (propertyData.controlType === 'lookup') { + if (propertyData.openType === 'Popup') { + eventList = eventList.filter(eventListItem => eventListItem.label !== 'beforeSelectData'); + } else { + const eventListExist = eventList.find(eventListItem => eventListItem.label === 'beforeSelectData'); + if (!eventListExist) { + eventList.push({ label: 'beforeSelectData', name: '选择数据确认前事件' }); + } + } + if (propertyData.enableClear||!Object.prototype.hasOwnProperty.call(propertyData, 'enableClear')) { + // 帮助默认启用清空 + const eventListExist = eventList.find(eventListItem => eventListItem.label === 'clear'); + if (!eventListExist) { + eventList.unshift({ label: 'clear', name: '清空事件' }); + } + } else { + eventList = eventList.filter(eventListItem => eventListItem.label !== 'clear'); + } + } + if(propertyData.controlType ==='combo-list'){ + if (propertyData.enableClear||!Object.prototype.hasOwnProperty.call(propertyData, 'enableClear')) { + // 下拉默认启用清空 + const eventListExist = eventList.find(eventListItem => eventListItem.label === 'onClear'); + if (!eventListExist) { + eventList.unshift({ label: 'onClear', name: '清空事件' }); + } + } else { + eventList = eventList.filter(eventListItem => eventListItem.label !== 'onClear'); + } + } + return eventList; + } function getEventPropConfig(propertyData: any) { - const events = getEventsByControlType(propertyData.controlType); - const initialData = props.eventsEditorUtils['formProperties'](propertyData, props.viewModelId, events); + const events = getEventsByControlType(propertyData.controlType, propertyData); + if (events.length === 0) { + return null; + } + const initialData = props.eventsEditorUtils['formProperties'](propertyData, props.viewModelId, events, switchEvents); const properties = {}; properties[props.viewModelId] = { type: 'events-editor', @@ -627,12 +718,27 @@ export function useProperty( setPropertyRelates(changeObject: any, data: any) { const parameters = changeObject.propertyValue; delete propertyData[props.viewModelId]; + if (forAddControllerMethod.value) { + forAddControllerMethod.value = false; + dialogActions.save(changeObject); + } if (parameters) { - parameters.setPropertyRelates = this.setPropertyRelates; // 添加自定义方法后,调用此回调方法,用于处理联动属性 - props.eventsEditorUtils.saveRelatedParameters(propertyData, props.viewModelId, parameters['events'], parameters); + if (parameters.isAddControllerMethod) { + // 暂存控件信息,用于自动创建新方法的方法编号和名称 + parameters.controlInfo = { type: propertyData.code, name: propertyData.name }; + FMessageBoxService.question('确定关闭当前编辑器并跳转到代码视图吗?', '', () => { + dialogActions.close(); + forAddControllerMethod.value = true; + parameters.setPropertyRelates = this.setPropertyRelates; + // 添加自定义方法后,调用此回调方法,用于处理联动属性 + props.eventsEditorUtils.saveRelatedParameters(propertyData, props.viewModelId, parameters['events'], parameters); + }, () => { }); + } else { + parameters.setPropertyRelates = this.setPropertyRelates; + // 添加自定义方法后,调用此回调方法,用于处理联动属性 + props.eventsEditorUtils.saveRelatedParameters(propertyData, props.viewModelId, parameters['events'], parameters); + } } - // 联动修改排序开关 - propertyData.remoteSort = propertyData.columnSorted ? true : false; } }; return eventsEditor; @@ -655,22 +761,25 @@ export function useProperty( const control = getControlConfig(propertyData); const eventsEditor = getEventPropConfig(propertyData); const otherPropertyConfig = getOtherPropertyConfig(propertyData); + const categories = { + basic, + control, + ...otherPropertyConfig + }; + if (eventsEditor !== null) { + categories['eventsEditor'] = eventsEditor; + } return { title: "query-solution-config", description: "A Farris Component", type: 'object', - categories: { - basic, - control, - ...otherPropertyConfig, - // eventsEditor - } + categories: categories }; } return { - getPropertyConfig, refreshPanelFlag + getPropertyConfig, refreshPanelFlag, updateDialogActions }; } diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-querysolution-rules.ts b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-querysolution-rules.ts index 578818962cb9f7b8a5cc85a8897b82fcfd14a8a9..b5d453961ac70fad83a0011b2a6726da3ca3b22f 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-querysolution-rules.ts +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-querysolution-rules.ts @@ -1,4 +1,4 @@ -import { UseDesignerRules } from "@farris/ui-vue/components/designer-canvas/"; +import { DgControl, UseDesignerRules } from "@farris/ui-vue/components/designer-canvas/"; import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "@farris/ui-vue/components/designer-canvas"; import { QuerySolutionPropertyConfig } from "../../../property-config/query-solution.property-config";; @@ -44,13 +44,40 @@ export function useQuerysolutionDesignerRules(designItemContext: DesignerItemCon } } } + /** + * 配置筛选方案中各个筛选项的路径信息,用于事件交互面板显示“已有方法”的事件路径 + */ + function setComponentBasicInfoMap() { + if (designItemContext && designerHostService) { + const { formSchemaUtils } = designerHostService; + const controlBasicInfoMap = formSchemaUtils.getControlBasicInfoMap(); + const { schema } = designItemContext; + // 配置方案的路径 + const solutionName = DgControl['query-solution'].name; + controlBasicInfoMap.set(schema.id, { + componentTitle: solutionName, + parentPathName: solutionName + }); + + // 配置方案下筛选项的路径 + if (schema.fields?.length) { + schema.fields.map(filterItem => { + controlBasicInfoMap.set(filterItem.id, { + componentTitle: filterItem.name, + parentPathName: `${solutionName} > ${filterItem.name}` + }); + }); + } + } + } return { getPropsConfig, canAccepts, checkCanDeleteComponent, checkCanMoveComponent, - onRemoveComponent + onRemoveComponent, + setComponentBasicInfoMap } as UseDesignerRules; } diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-transfer.tsx b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-transfer.tsx index 734789621af306d103c0dd564ddcce411ba719e2..08d56f5a1441e0cba36dd399d52c25f5ed3172cb 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-transfer.tsx +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-transfer.tsx @@ -34,14 +34,14 @@ export function useTransfer( const firstSelectPanels = ref([]); const selectPanels = ref([]); const dataSource = ref([]); - const { getSolutionField } = useUtil('query-solution'); + const { getSolutionField } = useUtil(props.source); const rowOptions = { customRowStatus: (visualData: any) => { const rawData = visualData['raw']; visualData['disabled'] = (rawData.$type !== 'SimpleField' || rawData.$typeInfo.name === 'Text'); } }; - let presetFieldsIds=[]; + let presetFieldsIds = []; function onActiveChange(transFerItem: PropertyItem[]) { activePanel.value = transFerItem.length ? transFerItem[0] : null; } @@ -118,16 +118,30 @@ export function useTransfer( finishInit.value = true; firstInitData.value = cloneDeep(data); firstSelectPanels.value = cloneDeep(selectPanels.value); - if(typeof props.presetFields==='function'){ - presetFieldsIds=props.presetFields(); - }else{ - presetFieldsIds=props.presetFields; + if (typeof props.presetFields === 'function') { + presetFieldsIds = props.presetFields(); + } else { + presetFieldsIds = props.presetFields; } } - - function confirm() { - + /** + * 有两种关闭窗口的方式 + * 1. 点击关闭按钮,此处参数为null + * 2. 通过添加新方法绑定命令时,会出现更新绑定方法名的异步差异。 + * fieldsData不能自动更新,此处通过绑定方法后的数据,强制更新 + * @param changedPropertyData + */ + function confirm(changedPropertyData = null) { + if (changedPropertyData) { + const { id, code, dataSource, labelCode, name, controlType, labelName, $type, $typeInfo, ...editor } = changedPropertyData as any; + const currentField = fieldsData.get(id); + if (currentField) { + for (const editorKey in editor) { + currentField[editorKey] = editor[editorKey]; + } + } + } const selectPanelsData: FieldConfig[] = []; selectPanels.value.forEach((panel: PropertyItem) => { const currentField = fieldsData.get(panel.id); @@ -160,10 +174,10 @@ export function useTransfer( finishInit.value = false; activePanel.value = null; } - function customSelectedItemClass(itemInfo){ - let itemClass='draggable-item--text-truncate'; - if(presetFieldsIds.find(fieldId=>fieldId===itemInfo.raw.id)){ - itemClass+=' query-solution-selected-fields'; + function customSelectedItemClass(itemInfo) { + let itemClass = 'draggable-item--text-truncate'; + if (presetFieldsIds.find(fieldId => fieldId === itemInfo.raw.id)) { + itemClass += ' query-solution-selected-fields'; } return itemClass; } @@ -185,7 +199,7 @@ export function useTransfer( text: (itemInfo) => { return
    {itemInfo.data['name'].data} - +
    ; } }} diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-util.ts b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-util.ts index 95da1ea4bdc6bf4927468813ef184e5e4aba7996..8f37992f7ceae40f164c86f1bff4534036315e6b 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-util.ts +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/composition/use-util.ts @@ -33,10 +33,6 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { key: "lookup", value: "帮助" }, - "combo-lookup": { - key: "combo-lookup", - value: "下拉帮助" - }, "combo-list": { key: "combo-list", value: "下拉列表" @@ -45,6 +41,14 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { key: "radio-group", value: "单选组" }, + "check-box": { + key: "check-box", + value: "单选组" + }, + "check-group": { + key: "check-group", + value: "复选框组" + }, "year-picker": { key: "year-picker", value: "年度" @@ -74,13 +78,31 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { value: "单日期时间" } }; - - function getQuerySolutionControlTypeListByFieldType(fieldType) { + /** + * 根据字段类型,获取支持的控件类型列表 + * @param fieldType + * @returns + */ + function getControlTypeListByFieldType(fieldType) { let ControlTypeList: Array<{ key, value }> = []; if (fieldType) { switch (fieldType) { - case 'Enum': case 'Boolean': - ControlTypeList = [CONTROL_TYPE_LIST['radio-group'], CONTROL_TYPE_LIST['combo-list']]; + case 'Enum': + ControlTypeList = [ + CONTROL_TYPE_LIST['combo-list'], + CONTROL_TYPE_LIST['check-group'], + CONTROL_TYPE_LIST['radio-group']]; + break; + case 'Boolean': + // ControlTypeList = [ + // CONTROL_TYPE_LIST['combo-list'], + // CONTROL_TYPE_LIST['check-box'] + // ] + + ControlTypeList = [ + CONTROL_TYPE_LIST['combo-list'], + CONTROL_TYPE_LIST['radio-group'] + ]; break; case 'Number': case 'Integer': case 'Decimal': case 'BigNumber': ControlTypeList = [CONTROL_TYPE_LIST['number-spinner'], CONTROL_TYPE_LIST['number-range']]; @@ -88,8 +110,10 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { case 'String': ControlTypeList = [CONTROL_TYPE_LIST['input-group'], CONTROL_TYPE_LIST['lookup'], - CONTROL_TYPE_LIST['radio-group'], CONTROL_TYPE_LIST['combo-list'], + // CONTROL_TYPE_LIST['check-box'], + CONTROL_TYPE_LIST['check-group'], + CONTROL_TYPE_LIST['radio-group'], CONTROL_TYPE_LIST['year-picker'], CONTROL_TYPE_LIST['month-picker'], CONTROL_TYPE_LIST['month-range'], @@ -119,56 +143,21 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { }]; } } - function getFilterBarControlTypeListByFieldType(fieldType) { - - let ControlTypeList: Array<{ key, value }> = []; - if (fieldType) { - switch (fieldType) { - case 'Enum': case 'Boolean': - ControlTypeList = [CONTROL_TYPE_LIST['radio-group'], CONTROL_TYPE_LIST['combo-list']]; - break; - case 'Number': case 'Integer': case 'Decimal': case 'BigNumber': - ControlTypeList = [CONTROL_TYPE_LIST['number-spinner'], CONTROL_TYPE_LIST['number-range']]; - break; - case 'String': - ControlTypeList = [CONTROL_TYPE_LIST['input-group'], CONTROL_TYPE_LIST['lookup'], CONTROL_TYPE_LIST['radio-group'], CONTROL_TYPE_LIST['combo-list'], CONTROL_TYPE_LIST['date-picker'], CONTROL_TYPE_LIST['datetime-picker']]; - break; - case 'Date': - ControlTypeList = [CONTROL_TYPE_LIST['date-picker']]; - break; - case 'DateTime': - ControlTypeList = [CONTROL_TYPE_LIST['date-picker'], CONTROL_TYPE_LIST['datetime-picker']]; - break; - } - return ControlTypeList; - } else { - return [{ - key: "input-group", - value: "文本" - }]; - } - } /** - * 根据字段类型,获取支持的控件类型列表 - * @param fieldType + * 根据控件类型获取扩展属性schema + * @param controlType + * @param options * @returns */ - function getControlTypeListByFieldType(fieldType) { - if (sourceType === 'query-solution') { - // 筛选方案 - return getQuerySolutionControlTypeListByFieldType(fieldType); - } else { - // 筛选条 - return getFilterBarControlTypeListByFieldType(fieldType); - } - } - function getQuerySolutionExtendSchemaProerty(controlType: ControlType, options?) { + function getExtendSchemaProerty(controlType: ControlType, options?, fieldType?) { const basicSchema = { // class: '', readonly: false, - required: false, - multiLineLabel: false + required: false }; + if (sourceType === 'query-solution') { + basicSchema['multiLineLabel'] = false; + } let resultSchema = {}; switch (controlType) { case 'input-group': @@ -181,6 +170,17 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { min: null }; break; + case 'check-group': + resultSchema = { + direction: sourceType === 'filter-bar' ? 'vertical' : 'horizontal', + data: options?.data || [], + valueField: 'value', + textField: 'name' + }; + if (sourceType === 'query-solution') { + resultSchema['showLabel'] = false; + } + break; case 'combo-list': resultSchema = { multiSelect: false, @@ -188,7 +188,8 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { valueField: 'value', textField: 'name', idField: 'value', - enumValueType: options?.enumValueType || 'string' + enableClear: Object.prototype.hasOwnProperty.call(options, 'enableClear') ? options.enableClear : true, + enumValueType: options?.enumValueType || fieldType && fieldType === 'Boolean' ? 'boolean' : 'string' }; break; case 'lookup': @@ -201,6 +202,7 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { mapFields: '', multiSelect: false, panelHeight: null, + enableClear: Object.prototype.hasOwnProperty.call(options, 'enableClear') ? options.enableClear : true, }; break; case 'date-range': @@ -233,93 +235,19 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { case 'radio-group': resultSchema = { direction: 'horizontal', - showLabel: false, - data: options?.data || [], - enumValueType: options?.enumValueType - }; - break; - }; - return { ...basicSchema, ...resultSchema }; - } - - function getFilterBarionExtendSchemaProerty(controlType: ControlType, options?) { - const basicSchema = { - // class: '', - readonly: false, - required: false, - isExtend: false - }; - let resultSchema = {}; - switch (controlType) { - case 'input-group': - break; - case 'number-range': - case 'number-spinner': - resultSchema = { - precision: options.precision || 0, - max: null, - min: null - }; - break; - case 'combo-list': - resultSchema = { - multiSelect: false, - data: options?.data || [], - valueField: 'value', - textField: 'name', - idField: 'value', - enumValueType: options?.enumValueType || 'string' - }; - break; - case 'lookup': - resultSchema = { - helpId: '', - uri: '', - textField: '', - valueField: '', - idField: '', - mapFields: '', - multiSelect: false, - panelHeight: null, - }; - break; - case 'date-range': - case 'date-picker': - resultSchema = { - valueFormat: 'yyyy-MM-dd', - displayFormat: 'yyyy-MM-dd', - }; - break; - case 'datetime-picker': - resultSchema = { - valueFormat: 'yyyy-MM-dd HH:mm:ss', - displayFormat: 'yyyy-MM-dd HH:mm:ss', - }; - break; - case 'radio-group': - resultSchema = { - direction: 'horizontal', - showLabel: false, data: options?.data || [], enumValueType: options?.enumValueType }; + if (sourceType === 'query-solution') { + resultSchema['showLabel'] = false; + } break; }; return { ...basicSchema, ...resultSchema }; } - /** - * 根据控件类型获取扩展属性schema - * @param controlType - * @param options - * @returns - */ - function getExtendSchemaProerty(controlType: ControlType, options?) { - return sourceType === 'query-solution' ? getQuerySolutionExtendSchemaProerty(controlType, options) : getFilterBarionExtendSchemaProerty(controlType, options); - } function getEditorByType(type, options?: any) { - const editor = getExtendSchemaProerty(type, options); - return editor; + return getExtendSchemaProerty(type, options); } function getSolutionField(schemaField: any) { @@ -341,12 +269,12 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { break; } case 'Date': - editorType = 'date-picker'; - editor = getEditorByType('date-picker'); + editorType = 'date-range'; + editor = getEditorByType('date-range'); break; case 'DateTime': { - editorType = 'datetime-picker'; - editor = getEditorByType('datetime-picker'); + editorType = 'datetime-range'; + editor = getEditorByType('datetime-range'); break; } case 'Enum': { @@ -359,12 +287,20 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { break; } case 'Boolean': { - editorType = 'combo-list'; - const options = { - data: schemaField.type.enumValues || [{ name: 'true', value: true }, { name: 'false', value: false }], - enumValueType: 'boolean' - }; - editor = getEditorByType('combo-list', options); + let options = {}; + if (sourceType === 'filter-bar') { + editorType = 'radio-group'; + options = { + data: schemaField.type.enumValues || [{ name: '是', value: true }, { name: '否', value: false }] + }; + } else { + editorType = 'combo-list'; + options = { + data: schemaField.type.enumValues || [{ name: '是', value: true }, { name: '否', value: false }], + enumValueType: 'boolean' + }; + } + editor = getEditorByType(editorType, options); break; } default: { @@ -384,7 +320,6 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { code: schemaField.code, name: schemaField.name, labelName: schemaField.name, - placeHolder: '', controlType: editorType, $type: schemaField.$type, $typeInfo: schemaField.type, @@ -420,7 +355,7 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { case 'date-range': case 'datetime-range': case 'datetime-picker': - realType = 'date-picker' + realType = 'date-picker'; break; default: realType = controlType; @@ -433,7 +368,7 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { const lookupDialogOptionsConverter = { convertFrom: (properDataEditor: Record, propertyKey: string) => { const options = properDataEditor.dialog || {}; - const {openType}= properDataEditor; + const { openType } = properDataEditor; if (propertyKey === 'title') { return options[propertyKey] || ''; } @@ -478,10 +413,7 @@ export function useUtil(sourceType: 'query-solution' | 'filter-bar') { properDataEditor.dialog[propertyKey] = propertyValue; } }; - return { - getQuerySolutionExtendSchemaProerty, - getFilterBarionExtendSchemaProerty, getExtendSchemaProerty, getEditorByType, getSolutionField, diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/property-config/query-solution-config-extend.json b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/property-config/query-solution-config-extend.json index 4791d3a6d849a95d0a09663ec344448f28a2e745..e1c493b52cf1d3a83cbebf55362a9432d10effc6 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/property-config/query-solution-config-extend.json +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/property-config/query-solution-config-extend.json @@ -182,6 +182,12 @@ "idField":"value" } }, + "enableClear": { + "description": "", + "title": "启用清除按钮", + "type": "boolean", + "defaultValue":true + }, "multiSelect":{ "description": "是否启用多选", "title": "多选", @@ -243,15 +249,21 @@ "valueField": "value" } }, - "multiSelect": { - "description": "是否多选", - "title": "是否多选", + "editable": { + "description": "", + "title": "允许编辑", "type": "boolean", "defaultValue":false }, - "editable": { + "enableClear": { "description": "", - "title": "允许编辑", + "title": "启用清除按钮", + "type": "boolean", + "defaultValue":true + }, + "multiSelect": { + "description": "是否多选", + "title": "是否多选", "type": "boolean", "defaultValue":false }, @@ -271,7 +283,7 @@ "direction": { "description": "布局方向", "title": "布局方向", - "type": "select", + "type": "enum", "editor": { "type": "combo-list", "data": [ @@ -288,5 +300,27 @@ "title": "是否显示标签", "type": "boolean" } + }, + "check-group": { + "direction": { + "description": "排列方向", + "title": "排列方向", + "type": "enum", + "editor": { + "type": "combo-list", + "data": [ + { "text": "水平", "value": "horizontal" }, + { "text": "垂直", "value": "vertical" } + ], + "textField": "text", + "idField": "value", + "valueField": "value" + } + }, + "showLabel": { + "description": "是否显示标签", + "title": "是否显示标签", + "type": "boolean" + } } } \ No newline at end of file diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.component.tsx b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.component.tsx index 65e7724e9e60437f8f20fa8da14de57549ef89c2..602764d01f132cd4a7723e58004b13c5bfbc45f2 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.component.tsx +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.component.tsx @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { defineComponent, computed, ref, SetupContext } from 'vue'; +import { defineComponent, computed, ref, SetupContext, inject, provide } from 'vue'; import { querySolutionConfigProps, QuerySolutionConfigProps } from './query-solution-config.props'; import { usePanel } from './composition/use-panel'; import { useTransfer } from './composition/use-transfer'; import FButtonEdit from '../../../../button-edit/src/button-edit.component'; - import './query-solution-config.css'; import { PropertyItem } from './composition/types'; +import { F_MODAL_SERVICE_TOKEN } from '@farris/ui-vue/components/modal'; +import { cloneDeep } from 'lodash-es'; export default defineComponent({ name: 'FQuerySolutionConfig', @@ -31,18 +32,51 @@ export default defineComponent({ const textValue = computed(() => { return '共' + props.modelValue.length + '项'; }); + + const modalService = inject(F_MODAL_SERVICE_TOKEN) as any; + const designViewModelUtils = inject('designViewModelUtils') as any; + const formSchemaUtils = inject('useFormSchema') as any; + const formCommandUtils = inject('useFormCommand') as any; + let modelInstance; // fieldsData 为打开弹窗后所用的数据,包含所有字段,点击确定时针对穿梭框selectPanels进行赋值操作 const fieldsData = new Map(); const usePanelComposition = usePanel(props, context, activePanel, fieldsData); - const { renderPanel } = usePanelComposition; + const { renderPanel, updateDialogActions } = usePanelComposition; const { renderTransfer, initData, confirm, cancel } = useTransfer(props, context, activePanel, fieldsData); + /** 记录编辑器刚打开时的DOM actions数据。编辑器内部交互面板在操作过程中可能会修改actions节点,导致点击【取消】按钮时无法还原actions数据。所以这里记录一份原始数据,方便还原。 */ + const previousActions: any = ref(); + /** 记录编辑器刚打开时的DOM viewmodels数据。编辑器内部交互面板在操作过程中可能会修改viewmodels节点,导致点击【取消】按钮时无法还原viewmodels数据。所以这里记录一份原始数据,方便还原。 */ + const previousViewModels: any = ref(); + /** 记录编辑器刚打开时的DOM 控件路径信息 */ + const previousFormBasicMap: any = ref(); + function acceptCallback() { confirm(); + modelInstance?.destroy(); } + updateDialogActions({ save: (data) => { confirm(data); }, close: () => { modelInstance?.destroy(); } }); + /** + * 取消窗口或者关闭窗口时,将表单DOM的改动还原。 + */ + function restoreFormSchema() { + formSchemaUtils.getModule().actions = previousActions.value; + formSchemaUtils.getModule().viewmodels = previousViewModels.value; + + previousFormBasicMap.value.forEach((value, key) => { + const clonedValue = cloneDeep(value); + formSchemaUtils.getControlBasicInfoMap().set(key, clonedValue); + }); + previousActions.value = null; + previousViewModels.value = null; + previousFormBasicMap.value = null; + } function rejectCallback() { cancel(); + modelInstance?.destroy(); + + restoreFormSchema(); } const modalOptions = { @@ -51,32 +85,38 @@ export default defineComponent({ height: 600, draggable: true, showMaxButton: true, - title: '筛选方案配置', + title: props.title, acceptCallback, - rejectCallback + rejectCallback, + render: () => { + return
    + {renderTransfer()} + {renderPanel()} +
    + } }; + function buttonClickHandler() { + initData(); + + // 备份表单DOM片段 + previousActions.value = cloneDeep(formSchemaUtils.getModule().actions); + previousViewModels.value = cloneDeep(formSchemaUtils.getModule().viewmodels); + previousFormBasicMap.value = cloneDeep(formSchemaUtils.getControlBasicInfoMap()); - function renderQuerySolutionConfig() { - return <> - {renderTransfer()} - {renderPanel()} - ; + // 弹窗导致上下文变更,重新注入 + modalService.app.provide("designer-host-service", designViewModelUtils); + modalService.app.provide("useFormSchema", formSchemaUtils); + modalService.app.provide("useFormCommand", formCommandUtils); + modelInstance = modalService.open(modalOptions); } return () => ( - initData()} - > -
    - {renderQuerySolutionConfig()} -
    -
    + onClickButton={() => buttonClickHandler()} + > ); } }); diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.props.ts b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.props.ts index 66c1da8a6fe251c9dfd5eb30def2b2bda823cdf4..5b116fdc0d61043fc5145b7086cae7ecfe77343a 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.props.ts +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/query-solution-config.props.ts @@ -16,7 +16,7 @@ import { ExtractPropTypes, PropType } from 'vue'; import { FieldConfig } from '../../../../condition/src/types'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import querySolutionConfigSchema from './schema/query-solution-config.schema.json'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; @@ -38,9 +38,18 @@ export const querySolutionConfigProps = { schemaService: { type: Object, default: {} }, viewModelId: { type: String, default: '' }, // 已预设字段 - presetFields:{ type: [Array, Function] as PropType | (() => Array)>, default: []} + presetFields:{ type: [Array, Function] as PropType | (() => Array)>, default: []}, + title: { type: String, default: '筛选方案配置' } } as Record; export type QuerySolutionConfigProps = ExtractPropTypes; export const configPropsResolver = createPropsResolver(querySolutionConfigProps, querySolutionConfigSchema, schemaMapper, schemaResolver); + +export const configPropsResolverGenerator = getPropsResolverGenerator( + querySolutionConfigProps, + querySolutionConfigSchema, + schemaMapper, + schemaResolver +); + diff --git a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/schema/query-solution-config.schema.json b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/schema/query-solution-config.schema.json index e3e4ecff1167d4c4761be81f1cf9d2063d6aceba..0d576af4fb4761388b6e7e8d9bc1e2d77a906a93 100644 --- a/packages/ui-vue/components/query-solution/src/designer/query-solution-config/schema/query-solution-config.schema.json +++ b/packages/ui-vue/components/query-solution/src/designer/query-solution-config/schema/query-solution-config.schema.json @@ -4,6 +4,15 @@ "title": "query-solution-config", "description": "A Farris Container Component", "properties": { + "id": { + "description": "The unique identifier for a Input Group", + "type": "string" + }, + "type": { + "description": "The type string of Input Group component", + "type": "string", + "default":"query-solution-config" + }, "fieldsConfig": { "description": "所有字段(树结构)", "type": "array" @@ -35,7 +44,20 @@ "presetFields": { "description": "", "type": "object" + }, + "resetDesignerUtils":{ + "description": "", + "type": "function" + }, + "title": { + "description": "", + "type": "string" + }, + "source": { + "description": "", + "type": "string" } - - } + }, + "required": [ + ] } \ No newline at end of file diff --git a/packages/ui-vue/components/query-solution/src/designer/solution-preset-config/solution-preset.props.ts b/packages/ui-vue/components/query-solution/src/designer/solution-preset-config/solution-preset.props.ts index af15a912f8be946978584e79522157e242a7cf66..d8e2d3ba568fb92ba9541c59217269f144978d23 100644 --- a/packages/ui-vue/components/query-solution/src/designer/solution-preset-config/solution-preset.props.ts +++ b/packages/ui-vue/components/query-solution/src/designer/solution-preset-config/solution-preset.props.ts @@ -2,7 +2,7 @@ import { ExtractPropTypes } from "vue"; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import solutionPresetSchema from './schema/query-solution-config.schema.json'; import { FieldConfig } from "@farris/ui-vue/components/condition"; @@ -18,3 +18,12 @@ export const solutionPresetProps = { export type SolutionPresetProps = ExtractPropTypes; export const solutionPresetPropsResolver = createPropsResolver(solutionPresetProps, solutionPresetSchema, schemaMapper, schemaResolver); + + +export const solutionPropsResolverGenerator = getPropsResolverGenerator( + solutionPresetProps, + solutionPresetSchema, + schemaMapper, + schemaResolver +); + diff --git a/packages/ui-vue/components/query-solution/src/locale/locale.ts b/packages/ui-vue/components/query-solution/src/locale/locale.ts index 88312cddde2900b56d40dee2ae51368a66c18cc9..9c9a0327587b7e297b66f1bc552c9aeced06a1d9 100644 --- a/packages/ui-vue/components/query-solution/src/locale/locale.ts +++ b/packages/ui-vue/components/query-solution/src/locale/locale.ts @@ -1,9 +1,8 @@ import { LocaleService } from '@farris/ui-vue/components/locale'; -import { useI18n } from 'vue-i18n'; export function useHeaderLocale() { - const { t } = useI18n(); + const { getLocaleValue: t } = LocaleService; const saveAsDialogLocale = { // 您暂无权限修改公共类型方案。 authNotify: t('querySolution.saveAsDialog.authNotify'), @@ -48,7 +47,7 @@ export function useHeaderLocale() { return { saveAsDialogLocale, manageDialogLocale, configDialogLocale }; } export function solutionManagerLocale() { - const { t } = useI18n(); + const { getLocaleValue: t } = LocaleService; const manageDialogLocale = { // 名称 code: t('querySolution.manageDialog.code'), @@ -68,7 +67,7 @@ export function solutionManagerLocale() { return { manageDialogLocale }; } export function useSolutionLocale() { - const { t } = useI18n(); + const { getLocaleValue: t } = LocaleService; const containerLocale = { // 请填写{fields}再进行筛选 require: t('querySolution.container.require') @@ -82,4 +81,4 @@ export function getLocaleFilterText(filterText) { /** 默认筛选方案 */ export function getLocalePresetQuerySolutionName(defaultName) { return LocaleService.getRealPropertyValue(defaultName, '默认筛选方案', 'querySolution.container.default'); -} \ No newline at end of file +} diff --git a/packages/ui-vue/components/query-solution/src/locales/ui/en.json b/packages/ui-vue/components/query-solution/src/locales/ui/en.json index 0a1ec6c498e8cdad201ff4e3a1e8644c0cdc7b2f..60fa653c37118769d2e277f4fe4d6047b1d033fe 100644 --- a/packages/ui-vue/components/query-solution/src/locales/ui/en.json +++ b/packages/ui-vue/components/query-solution/src/locales/ui/en.json @@ -35,7 +35,7 @@ "filter": "Filter", "default": "Default Filter Solution", "clear": "Clear", - "require": "Please fill in {fields} before filtering" + "require": "Please fill in [fields] before filtering" } } } \ No newline at end of file diff --git a/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHS.json b/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHS.json index db33ab54e674d686a3057b0b7f24781c7c31f35f..ac12eaee15798a5706cc932cc8b6f2ae72beb7ef 100644 --- a/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHS.json +++ b/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHS.json @@ -35,7 +35,7 @@ "filter": "筛选", "default": "默认筛选方案", "clear": "清空", - "require": "请填写{fields}再进行筛选" + "require": "请填写[fields]再进行筛选" } } } \ No newline at end of file diff --git a/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHT.json b/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHT.json index be6f52f5790413f974b06776f706e3165fd61f59..3c003a6464432a1b5bd4f45dd2226eb899717b00 100644 --- a/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHT.json +++ b/packages/ui-vue/components/query-solution/src/locales/ui/zh-CHT.json @@ -35,7 +35,7 @@ "filter": "篩選", "default": "默認篩選方案", "clear": "清空", - "require": "請填寫{fields}再進行篩選" + "require": "請填寫[fields]再進行篩選" } } } \ No newline at end of file diff --git a/packages/ui-vue/components/query-solution/src/property-config/query-solution.property-config.ts b/packages/ui-vue/components/query-solution/src/property-config/query-solution.property-config.ts index 363016fc7e316d1bd36cc08e2a16e2314cced3b9..f5251e29fd231386d0d0122d067a3e80b7c11a07 100644 --- a/packages/ui-vue/components/query-solution/src/property-config/query-solution.property-config.ts +++ b/packages/ui-vue/components/query-solution/src/property-config/query-solution.property-config.ts @@ -131,7 +131,7 @@ export class QuerySolutionPropertyConfig extends InputBaseProperty { description: "默认值绑定字段", type: "object", title: "默认值绑定字段", - editor: this.getPropertyEditorParams(propertyData, ['Variable'], '', {}, { newVariableType: 'Object' }) + editor: this.getPropertyEditorParams(propertyData, ['Variable'], '', {}, { newVariablePrefix: '', newVariableType: 'Object' }) } }, setPropertyRelates(changeObject, prop) { diff --git a/packages/ui-vue/components/query-solution/src/query-solution.component.tsx b/packages/ui-vue/components/query-solution/src/query-solution.component.tsx index a2c5406b5053fc0bc9abd007b552500ad2414733..d9061304bf8d98779c306032572dc16bcce10912 100644 --- a/packages/ui-vue/components/query-solution/src/query-solution.component.tsx +++ b/packages/ui-vue/components/query-solution/src/query-solution.component.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { defineComponent, computed, ref, SetupContext, onMounted } from 'vue'; +import { defineComponent, computed, ref, SetupContext, onMounted, provide } from 'vue'; import { querySolutionProps, QuerySolutionProps } from './query-solution.props'; import { QuerySolution } from './query-solution'; import { FConditionFields } from '@farris/ui-vue/components/condition'; @@ -23,6 +23,8 @@ import { Condition } from '@farris/ui-vue/components/condition'; import { useSolution } from './composition/use-solution'; import { useHeader } from './composition/use-header'; import { useHttp } from './composition/use-http'; +import { useSolutionUtils } from './composition/use-solution-utils'; +import { isMobilePhone } from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FQuerySolution', @@ -30,25 +32,37 @@ export default defineComponent({ emits: ['save', 'saveAs', 'delete', 'change', 'conditionChange', 'query'] as (string[] & ThisType) | undefined, setup(props: QuerySolutionProps, context: SetupContext) { const fields = ref(); + // 是否在小屏的移动设备上 + const isMobilePhoneState=isMobilePhone(); const isControlInline = ref(props.isControlInline); const solutions = ref([]); const changingSolutions = ref([]); + // 当前筛选方案:标识、条件等 const currentSolution = ref(null); + // 当前筛选方案的条件中可见的条件 + const currentSolutionVisibleCondition = ref([]); const changeQuery = ref(props.changeQuery); const useHttpComposition = useHttp(props, context); - const useSolutionComposition = useSolution(props, context, useHttpComposition, solutions,changingSolutions, currentSolution, fields); - const useHeaderComposition = useHeader(props, context, useHttpComposition, useSolutionComposition, solutions,changingSolutions, currentSolution, fields); + const useSolutionComposition = useSolution(props, context, useHttpComposition, solutions, changingSolutions, currentSolution, fields); + const mobileUtils={ + renderContent:()=><>, + modalInstance:null + }; + const useHeaderComposition = useHeader(props, context, useHttpComposition, useSolutionComposition, solutions, changingSolutions, currentSolution, fields,mobileUtils); const queryTimer = ref(); const { loadAllSolution, handleQuery, query } = useSolutionComposition; - const { renderHeader, loadFields, expanded } = useHeaderComposition; - - onMounted(() => { + const { renderHeader, loadFields, expanded } = useHeaderComposition; + const { updateConditionValue, updateFieldsValue } = useSolutionUtils(); + + onMounted(() => { + // 字段配置数据源进行规范化,根据字段编辑器类型,初始化一些必要属性 loadFields(); const loadSolutionOptions = { enableQuery: props.initQuery, - willSetDefaultValues:props.willSetDefaultValues + willSetDefaultValues: props.willSetDefaultValues }; + // 获取props.formId关联的筛选方案列表, 处理是否初始查询、筛选方案默认值等逻辑 loadAllSolution(loadSolutionOptions); }); @@ -77,8 +91,8 @@ export default defineComponent({ function throttleQuery() { if (!queryTimer.value) { - query(); queryTimer.value = setTimeout(() => { + query(); queryTimer.value = null; }, 500); } else { @@ -93,11 +107,13 @@ export default defineComponent({ function onConditionChange(e: any, condition: Condition) { context.emit('change', e, condition); handleQuery(); - currentSolution.value && (currentSolution.value.hasChanged = true); - if(changeQuery.value) { + if (currentSolution.value) { + currentSolution.value.hasChanged = true; + } + if (changeQuery.value) { throttleQuery(); } - + } function onLabelCodeChange(condition: Condition) { context.emit('change', condition); @@ -107,10 +123,10 @@ export default defineComponent({ context.emit('change', e, condition); context.emit('conditionChange', cloneDeep(currentSolution.value?.conditions)); } - const getConditionStyle=computed(()=>{ - const styleObject={}; - if(!expanded.value){ - styleObject['display']='none!important'; + const getConditionStyle = computed(() => { + const styleObject = {}; + if (!isMobilePhoneState&&!expanded.value) { + styleObject['display'] = 'none!important'; } return styleObject; }); @@ -129,19 +145,59 @@ export default defineComponent({ onCompareTypeChange={(e: any, condition: Condition) => onCompareTypeChange(e, condition)} >; } - + mobileUtils.renderContent=renderCondition; + /** + * 对外提供获取条件 + * @returns + */ function getConditions() { - return currentSolution.value?.conditions || []; + return currentSolution.value?.conditions.filter(conditionItem => conditionItem.visible) || []; + } + /** + * 更新配置字段 + * [ + { + id: 编号,labelCode或者id,用来区分字段,必有一个, + labelCode:标签,labelCode或者id,用来区分字段,必有一个 + visible: 指定是否可见, + editor:{ + disabled:指定禁用装填 + }, + value:{ + 指定值相关的属性 + } + },... + ] + * @param resetConditions + */ + function updateFields(resetConditions: Array) { + // 更新配置fields + updateFieldsValue(fields.value, resetConditions); + // 更新当前的筛选方案条件 + updateConditionValue(currentSolution.value?.conditions || [], resetConditions,currentSolution); + // 通知条件变更 + handleQuery(); + } + /** + * 更新当前条件 + * + * @param resetConditions + */ + function updateConditions(resetConditions: Array) { + // 更新当前的筛选方案条件 + updateConditionValue(currentSolution.value?.conditions || [], resetConditions,currentSolution); + // 通知条件变更 + handleQuery(); } context.expose({ - getConditions + getConditions, updateConditions, updateFields }); return () => (
    {renderHeader()} - {renderCondition()} + {!isMobilePhoneState&&renderCondition()}
    ); } diff --git a/packages/ui-vue/components/query-solution/src/query-solution.props.ts b/packages/ui-vue/components/query-solution/src/query-solution.props.ts index 625255ab2b4d1c740713f04787f921a1589819a0..6daf57aa81c1f5889acadb2823a18d612fc5447e 100644 --- a/packages/ui-vue/components/query-solution/src/query-solution.props.ts +++ b/packages/ui-vue/components/query-solution/src/query-solution.props.ts @@ -17,7 +17,7 @@ import { ExtractPropTypes } from 'vue'; import { QuerySolution } from './query-solution'; import { FieldConfig } from '@farris/ui-vue/components/condition'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapper } from './schema/schema-mapper'; import querySolutionSchema from './schema/query-solution.schema.json'; import { schemaResolver } from './schema/schema-resolver'; @@ -34,7 +34,7 @@ const SCOPE_OF_IMPACT = { DEFAULT: 'default' }; type DefaultValues = { - readonly: boolean, + readonly: boolean,// 控制所有data编辑器是否只读 data: Array<{ key: string, value: any }>, scope: keyof typeof SCOPE_OF_IMPACT } @@ -43,11 +43,33 @@ export const querySolutionProps = { * 表单id */ formId: { type: String, default: '' }, + /** + * 字段配置数据源,用于选择字段配置 + */ fields: { type: Array, default: [] }, + /** + * 预置查询字段:系统筛选方案的筛选条件 + */ presetFields: { type: Array, default: [] }, + /** + * 指定筛选方案的数组。 + * 一般是从服务器端获取 + */ solutions: { type: Array, default: [] }, /** * 筛选方案的默认值 + * { + readonly: true, + scope: 'all', + data: [ + { + "id": "34557581-8e38-41eb-ac6f-678545f0a7e0", + "value": { + "value": "2025/03/05", + } + },... + ] + } */ defaultValues: { type: Array, default: { readonly: false, data: [], scope: SCOPE_OF_IMPACT.DEFAULT } }, /** @@ -96,3 +118,10 @@ export const querySolutionProps = { export type QuerySolutionProps = ExtractPropTypes; export const propsResolver = createPropsResolver(querySolutionProps, querySolutionSchema, schemaMapper, schemaResolver); + +export const propsResolverGenerator = getPropsResolverGenerator( + querySolutionProps, + querySolutionSchema, + schemaMapper, + schemaResolver +); diff --git a/packages/ui-vue/components/radio-button/src/radio-button.component.tsx b/packages/ui-vue/components/radio-button/src/radio-button.component.tsx index e85bde2964dfe8376b48b6e1e03c3870ebcaa55d..cb297ffbbccef10f137b48d62334db74c5e31a20 100644 --- a/packages/ui-vue/components/radio-button/src/radio-button.component.tsx +++ b/packages/ui-vue/components/radio-button/src/radio-button.component.tsx @@ -16,8 +16,6 @@ import { computed, defineComponent, SetupContext, ref, withModifiers, onMounted, watch } from 'vue'; import { RadioButtonProps, radioButtonProps } from './radio-button.props'; -import './radio-button.css'; - export default defineComponent({ name: 'FRadioButton', props: radioButtonProps, diff --git a/packages/ui-vue/components/radio-button/src/radio-button.css b/packages/ui-vue/components/radio-button/src/radio-button.css deleted file mode 100644 index 5437aba07f5a4e0533c108e1a9dd905e5463442f..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/radio-button/src/radio-button.css +++ /dev/null @@ -1,39 +0,0 @@ -/* .f-radio-button { */ -/* width: 40px; - height: 22px; - border: 1px solid red; - display: inline-block; - border-radius: 0; - width: 40px; - font-family: PingFangSC-Regular; - font-size: 10px; - text-align: center; - color: rgba(45, 47, 51, .46);*/ -/* } */ - -.f-radio-button .btn { - border-radius: 0; -} - -.f-radio-button .btn:not(:last-child) { - border-right-color: transparent; -} - -.f-radio-button .btn:not(:last-child):hover { - border-right-color: inherit; -} - -.f-radio-button .btn:first-child { - border-top-left-radius: 6px !important; - border-bottom-left-radius: 6px !important; -} - -.f-radio-button .btn:last-child { - border-top-right-radius: 6px !important; - border-bottom-right-radius: 6px !important; -} - -.f-radio-button-text { - margin-left: -5px; - /* color: #a39e9e; */ -} \ No newline at end of file diff --git a/packages/ui-vue/components/radio-button/style.ts b/packages/ui-vue/components/radio-button/style.ts index 1335c5b58cfcaf6fa1a82f309aa0fbe9d8dba314..298647144aeb578201aa7fe3077174aebaf20ac9 100644 --- a/packages/ui-vue/components/radio-button/style.ts +++ b/packages/ui-vue/components/radio-button/style.ts @@ -1,2 +1 @@ -import "@farris/ui-vue/components/dependent-base/style"; -import "@farris/ui-vue/components/checkbox/style"; +import "@farris/ui-vue/components/radio/style"; diff --git a/packages/ui-vue/components/radio-group/designer.ts b/packages/ui-vue/components/radio-group/designer.ts index 62cea14ef19a051ed9e9c4e97a2f0bec3d641982..c972e439d0167a06e826c7521e335a454609d3d1 100644 --- a/packages/ui-vue/components/radio-group/designer.ts +++ b/packages/ui-vue/components/radio-group/designer.ts @@ -1,25 +1,33 @@ -import { withInstall } from '@farris/ui-vue/components/common'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { RegisterContext, withInstall } from '@farris/ui-vue/components/common'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import FRadioGroup, { radioGroupProps, type RadioGroupProps } from '@farris/ui-vue/components/radio-group'; import FRadioGroupDesign from './src/designer/radio-group.design.component'; import FItemCollectionEditor from './src/designer/item-collection-editor.component'; -import { itemCollectionEditorPropsResolver } from './src/designer/item-collection-editor.props'; +import { itemCollectionEditorPropsResolver, itemCollectionEditorPropsResolverGenerator } from './src/designer/item-collection-editor.props'; import { schemaResolver } from './src/schema/schema-resolver'; import { schemaMapper } from './src/schema/schema-mapper'; import radioGroupSchema from './src/schema/radio-group.schema.json'; const propsResolver = createPropsResolver(radioGroupProps, radioGroupSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator( + radioGroupProps, + radioGroupSchema, + schemaMapper, + schemaResolver +); -FRadioGroupDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { +FRadioGroupDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext) => { componentMap['radio-group'] = FRadioGroup; - propsResolverMap['radio-group'] = propsResolver; + propsResolverMap['radio-group'] = propsResolverGenerator(registerContext); componentMap['item-collection-editor'] = FItemCollectionEditor; - propsResolverMap['item-collection-editor'] = itemCollectionEditorPropsResolver; + propsResolverMap['item-collection-editor'] = itemCollectionEditorPropsResolverGenerator(registerContext); }; -FRadioGroupDesign.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { +FRadioGroupDesign.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext) => { componentMap['radio-group'] = FRadioGroupDesign; - propsResolverMap['radio-group'] = propsResolver; + propsResolverMap['radio-group'] = propsResolverGenerator(registerContext); }; export { FRadioGroupDesign, propsResolver }; diff --git a/packages/ui-vue/components/radio-group/src/composition/change-radio.ts b/packages/ui-vue/components/radio-group/src/composition/change-radio.ts index 08b1f6d7e85ea9e7542f69d9fd88da9dd72889ed..3ef904d650ee153475ca0b53174dd02a33d29a44 100644 --- a/packages/ui-vue/components/radio-group/src/composition/change-radio.ts +++ b/packages/ui-vue/components/radio-group/src/composition/change-radio.ts @@ -34,9 +34,8 @@ export function changeRadio(props: RadioGroupProps, context: SetupContext, model const newValue = getValue(item); if (modelValue.value !== newValue) { modelValue.value = newValue; - + context.emit('change', newValue); context.emit('changeValue', newValue); - context.emit('update:modelValue', newValue); } } diff --git a/packages/ui-vue/components/radio-group/src/designer/item-collection-editor-inner.component.tsx b/packages/ui-vue/components/radio-group/src/designer/item-collection-editor-inner.component.tsx index ae2bbb45be41efb15ea857a151fd52c16ba327ff..080d88274e863095874a5b85952118b0b680dcf7 100644 --- a/packages/ui-vue/components/radio-group/src/designer/item-collection-editor-inner.component.tsx +++ b/packages/ui-vue/components/radio-group/src/designer/item-collection-editor-inner.component.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { defineComponent, onMounted, ref } from 'vue'; +import { defineComponent, onMounted, ref, watch } from 'vue'; import { itemCollectionEditorInnerProps, ItemCollectionEditorInnerProps } from './item-collection-editor.props'; import FDataGrid from '@farris/ui-vue/components/data-grid'; import { useGuid } from '@farris/ui-vue/components/common'; @@ -47,6 +47,11 @@ export default defineComponent({ const compactColumns = props.columns; let datagridDatas = props.datas; + watch(() => [props.datas], () => { + datagridDatas = props.datas; + gridComponentInstance.value.updateDataSource(datagridDatas); + }); + /** * 创建新数据 * @returns @@ -70,7 +75,7 @@ export default defineComponent({ */ function checkMappingKeys() { if (props.dynamicMappingKeys) { - const msg = '请先填写XXX'; + const msg = '请先填写XXX。'; const enumValueFieldLabel = !valueField.value.trim() ? '枚举值字段' : !nameField.value.trim() ? '枚举名称字段' : ''; notifyService.warning({ position: 'top-center', message: msg.replace('XXX', enumValueFieldLabel) }); return false; @@ -89,14 +94,14 @@ export default defineComponent({ // 1、空数组 if (!data || data.length === 0) { if (!props.canEmpty) { - notifyService.warning({ position: 'top-center', message: '请添加值' }); + notifyService.warning({ position: 'top-center', message: '请添加值。' }); return false; } return true; } // 2、非空,则校验每个的键值是否为空;(区分枚举值为布尔型的场景;排除枚举值为整数0的场景) const requiredFields = props.requiredFields || []; - const emptyNotAllowed = '不允许为空'; + const emptyNotAllowed = '不允许为空。'; for (const item of data) { for (const itemKey of Object.keys(item)) { const column = compactColumns.find(col => col.field === itemKey); @@ -147,25 +152,35 @@ export default defineComponent({ /** * 设置表格选中项 */ - function setGridSelectedItem() { + function setGridSelectedItem(rowId?: string) { if (datagridDatas && datagridDatas.length > 0) { - gridComponentInstance.value.selectRowById(datagridDatas[0].hId); + if (!rowId) { + rowId = datagridDatas[0].hId; + } + gridComponentInstance.value.selectRowById(rowId); + } else { + currentRowId = ''; } } /** * 删除当 */ function removeItem() { - // 获取选中行 - // const selectedItems = gridComponentInstance.value.getSelectedItems(); if (!currentRowId) { notifyService.warning({ position: 'top-center', message: '请先选中行!' }); return; } + const currentIndex = datagridDatas.findIndex(item => item.hId === currentRowId); const otherDatas = datagridDatas.filter(item => item.hId !== currentRowId); datagridDatas = [...otherDatas]; gridComponentInstance.value.updateDataSource(datagridDatas); - setGridSelectedItem(); + + // 选中下一行数据 + let nextRowId = ''; + if (currentIndex < datagridDatas.length && datagridDatas.length > 0) { + nextRowId = datagridDatas[currentIndex].hId; + } + setGridSelectedItem(nextRowId); } /** * 按照指定的字段名转换value和name @@ -226,9 +241,11 @@ export default defineComponent({ } return true; } + function getDataGridDatas() { + return datagridDatas; + } - - context.expose({ clickConfirm, getItems }); + context.expose({ clickConfirm, getItems, getDataGridDatas }); onMounted(() => { setGridSelectedItem(); @@ -239,7 +256,7 @@ export default defineComponent({ } return () => ( -
    +
    {!props.readonly &&
    @@ -253,6 +270,7 @@ export default defineComponent({ editable={!props.readonly} edit-option={editOption} columnOption={columnOption} + commandOption={props.commandOption} fit={true} onSelectionChange={onSelectionChange} > diff --git a/packages/ui-vue/components/radio-group/src/designer/item-collection-editor.props.ts b/packages/ui-vue/components/radio-group/src/designer/item-collection-editor.props.ts index 101cccafd83f9a1acdf5525e5f3c7d166bdc819b..51bad6e2b1cc2f261036cf19fd268452e4768919 100644 --- a/packages/ui-vue/components/radio-group/src/designer/item-collection-editor.props.ts +++ b/packages/ui-vue/components/radio-group/src/designer/item-collection-editor.props.ts @@ -19,7 +19,7 @@ import { itemCollectionEditorSchemaResolver } from '../schema/schema-resolver'; import { itemCollectionEditorSchemaMapper } from '../schema/schema-mapper'; import itemCollectionEditorSchema from '../schema/item-collection-editor.schema.json'; import { DataGridColumn } from '@farris/ui-vue/components/data-grid'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; export const itemCollectionEditorProps = { /** @@ -68,7 +68,7 @@ export const itemCollectionEditorProps = { * 列表只有一列时,将结果集转化为字符串数组 */ // isSimpleArray: { type: Boolean, default: false }, - beforeSubmit:{ type: Function, default: null }, + beforeSubmit: { type: Function, default: null }, title: { type: String, default: '项编辑器' }, /** * 模态框宽度 @@ -84,6 +84,12 @@ export const itemCollectionEditorProps = { export type ItemCollectionEditorProps = ExtractPropTypes; export const itemCollectionEditorPropsResolver = createPropsResolver(itemCollectionEditorProps, itemCollectionEditorSchema, itemCollectionEditorSchemaMapper, itemCollectionEditorSchemaResolver); +export const itemCollectionEditorPropsResolverGenerator = getPropsResolverGenerator( + itemCollectionEditorProps, + itemCollectionEditorSchema, + itemCollectionEditorSchemaMapper, + itemCollectionEditorSchemaResolver +); export const itemCollectionEditorInnerProps = { /** @@ -118,7 +124,9 @@ export const itemCollectionEditorInnerProps = { /** * 是否进入只读状态 */ - readonly: { type: Boolean, default: false } + readonly: { type: Boolean, default: false }, + + commandOption: { type: Object, default: { enable: false } }, } as Record; export type ItemCollectionEditorInnerProps = ExtractPropTypes; diff --git a/packages/ui-vue/components/radio-group/src/radio-group.component.tsx b/packages/ui-vue/components/radio-group/src/radio-group.component.tsx index 37c0738885e5b266073796b305159e7cc590c88e..0e19be70118a406a6594a8d9daa36308169efb76 100644 --- a/packages/ui-vue/components/radio-group/src/radio-group.component.tsx +++ b/packages/ui-vue/components/radio-group/src/radio-group.component.tsx @@ -22,7 +22,7 @@ import { changeRadio } from './composition/change-radio'; export default defineComponent({ name: 'FRadioGroup', props: radioGroupProps, - emits: ['changeValue', 'update:modelValue'] as (string[] & ThisType) | undefined, + emits: ['changeValue', 'update:modelValue', 'change'] as (string[] & ThisType) | undefined, setup(props: RadioGroupProps, context: SetupContext) { const { guid } = useGuid(); const defaultRadioName = `radio_${guid().slice(0,8)}`; diff --git a/packages/ui-vue/components/radio-group/src/schema/radio-group.schema.json b/packages/ui-vue/components/radio-group/src/schema/radio-group.schema.json index c5483c039a41e12fd1cda2fcc42df1396c86dcf8..2da8a096ee1a950de1085157ef19a412327b7753 100644 --- a/packages/ui-vue/components/radio-group/src/schema/radio-group.schema.json +++ b/packages/ui-vue/components/radio-group/src/schema/radio-group.schema.json @@ -84,6 +84,10 @@ "description": "", "type": "string", "default": "horizontal" + }, + "onChange": { + "description": "值变化事件", + "type": "string" } }, "required": [ diff --git a/packages/ui-vue/components/radio-group/style.ts b/packages/ui-vue/components/radio-group/style.ts index 251946a38f4ff7480bfd3fcba21caa01b99ed15d..bcce5d43fd14aa1f6f5f2864c729d8de2b86df98 100644 --- a/packages/ui-vue/components/radio-group/style.ts +++ b/packages/ui-vue/components/radio-group/style.ts @@ -1,3 +1 @@ -import "@farris/ui-vue/components/dependent-base/style"; -import "@farris/ui-vue/components/dependent-icon/style"; -import "@farris/ui-vue/theme-default/components/radio.css"; +import "@farris/ui-vue/components/radio/style"; \ No newline at end of file diff --git a/packages/ui-vue/components/radio/src/radio.component.tsx b/packages/ui-vue/components/radio/src/radio.component.tsx index 016df9f2e4859de5910f0c85d8b4a81adcc2ab3e..6700ba76d7704361951acb7dd3245066851e2ee9 100644 --- a/packages/ui-vue/components/radio/src/radio.component.tsx +++ b/packages/ui-vue/components/radio/src/radio.component.tsx @@ -17,7 +17,6 @@ import { defineComponent, computed, ref, inject, onMounted } from 'vue'; import type { InjectionKey, Ref, SetupContext } from 'vue'; import { RADIOGROUP_CONTEXT, useCheck } from '@farris/ui-vue/components/common'; import { RadioProps, radioProps } from './radio.props'; -import './radio.css'; export default defineComponent({ name: 'FRadio', diff --git a/packages/ui-vue/components/radio/src/radio.css b/packages/ui-vue/components/radio/src/radio.css deleted file mode 100644 index dca9510c05186e58b1a879827262d283bdbd66f0..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/radio/src/radio.css +++ /dev/null @@ -1,132 +0,0 @@ -.f-radio-button, -.f-radio-tag { - color: #2d2f33; - background: #fff; - border: 1px solid #e8ebf2; -} -.f-radio-button-success:hover { - color: #6cc77f; - background: #fff; - border-color: #6cc77f; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-warning:hover { - color: #f5a144; - background: #fff; - border-color: #f5a144; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-danger:hover { - color: #f46160; - background: #fff; - border-color: #f46160; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-primary:hover { - color: #2a87ff; - background: #fff; - border-color: #2a87ff; - box-shadow: 0 2px 6px 0 rgba(31, 35, 41, 0.06); -} - -.f-radio-button-success.active { - color: #fff; - background: #6cc77f; - border-color: #6cc77f; -} - -.f-radio-button-warning.active { - color: #fff; - background: #f5a144; - border-color: #f5a144; -} -.f-radio-button-danger.active { - color: #fff; - background: #f46160; - border-color: #f46160; -} -.f-radio-button-primary.active { - color: #fff; - background: #2a87ff; - border-color: #2a87ff; -} - -.f-radio-tag { - display: inline-block; - position: relative; - margin-right: 8px; - padding: 3px 16px; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 2px; - overflow: hidden; - cursor: pointer; - margin-bottom: 0; -} -.f-radio-icon { - position: absolute; - display: block; - right: -4px; - bottom: -3px; - font-size: 12px; - color: #fff; -} - -.f-radio-tag > .title { -} - -.f-radio-tag > .tip { - position: absolute; - display: block; - right: 0; - bottom: 0; - width: 14px; - height: 14px; - border: 7px solid #dadada; - border-top: 7px solid transparent; - border-left: 7px solid transparent; -} - -.f-radio-tag-success.active { - color: #6cc77f; - border-color: rgb(108, 199, 127); - background: rgba(108, 199, 127, 0.05); -} - -.f-radio-tag-warning.active { - color: #f5a144; - border-color: #f5a144; - background: rgba(245, 161, 68, 0.05); -} -.f-radio-tag-danger.active { - color: #f46160; - border-color: rgb(245, 97, 97); - background: rgba(245, 97, 97, 0.05); -} -.f-radio-tag-primary.active { - color: #2a87ff; - border-color: rgb(42, 135, 255); - background: rgba(42, 135, 255, 0.05); -} - -.f-radio-tag-success.active > .tip { - border-right-color: #6cc77f; - border-bottom-color: #6cc77f; -} - -.f-radio-tag-warning.active > .tip { - border-right-color: #f5a144; - border-bottom-color: #f5a144; -} - -.f-radio-tag-danger.active > .tip { - border-right-color: #f46160; - border-bottom-color: #f46160; -} - -.f-radio-tag-primary.active > .tip { - border-right-color: #2a87ff; - border-bottom-color: #2a87ff; -} diff --git a/packages/ui-vue/components/radio/style.ts b/packages/ui-vue/components/radio/style.ts index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..380823d145756dfbbe93b586e579e578f06d6a2a 100644 --- a/packages/ui-vue/components/radio/style.ts +++ b/packages/ui-vue/components/radio/style.ts @@ -0,0 +1,3 @@ +import "@farris/ui-vue/components/dependent-base/style"; +import "@farris/ui-vue/components/dependent-icon/style"; +import "@farris/ui-vue/theme-default/components/radio.css"; \ No newline at end of file diff --git a/packages/ui-vue/components/response-layout/index.ts b/packages/ui-vue/components/response-layout/index.ts index 8851c5957d1c38097e0da8950212077a8df16e20..1c831400d6dcc95d5e7cb8b03a49de4fae4164d7 100644 --- a/packages/ui-vue/components/response-layout/index.ts +++ b/packages/ui-vue/components/response-layout/index.ts @@ -19,10 +19,11 @@ import FResponseLayout from './src/response-layout.component'; import FResponseLayoutItem from './src/component/response-layout-item.component'; import FResponseLayoutDesign from './src/designer/response-layout.design.component'; import FResponseLayoutItemDesign from './src/designer/response-layout-item.design.component'; -import { responseLayoutPropsResolver } from './src/response-layout.props'; -import { responseLayoutItemPropsResolver } from './src/component/response-layout-item.props'; +import { responseLayoutPropsResolver, responseLayoutPropsResolverGenerator } from './src/response-layout.props'; +import { responseLayoutItemPropsResolver, responseLayoutItemPropsResolverGenerator } from './src/component/response-layout-item.props'; import FResponseLayoutSplitter from './src/property-config/response-layout-splitter/response-layout-splitter.component'; -import { responseLayoutSplitterPropsResolver } from './src/property-config/response-layout-splitter/response-layout-splitter.props'; +import { responseLayoutSplitterPropsResolver, responseLayoutSplitterPropsResolverGenerator } from './src/property-config/response-layout-splitter/response-layout-splitter.props'; +import { RegisterContext } from '../common'; export * from './src/response-layout.props'; export { FResponseLayout, FResponseLayoutItem, FResponseLayoutSplitter }; @@ -32,20 +33,22 @@ export default { app.component(FResponseLayout.name as string, FResponseLayout) .component(FResponseLayoutItem.name as string, FResponseLayoutItem); }, - register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void { + register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + resolverMap: Record, registerContext: RegisterContext): void { componentMap['response-layout'] = FResponseLayout; componentMap['response-layout-item'] = FResponseLayoutItem; - propsResolverMap['response-layout'] = responseLayoutPropsResolver; - propsResolverMap['response-layout-item'] = responseLayoutItemPropsResolver; + propsResolverMap['response-layout'] = responseLayoutPropsResolverGenerator(registerContext); + propsResolverMap['response-layout-item'] = responseLayoutItemPropsResolverGenerator(registerContext); componentMap['response-layout-splitter'] = FResponseLayoutSplitter; - propsResolverMap['response-layout-splitter'] = responseLayoutSplitterPropsResolver; + propsResolverMap['response-layout-splitter'] = responseLayoutSplitterPropsResolverGenerator(registerContext); }, - registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void { + registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, + registerContext: RegisterContext): void { componentMap['response-layout'] = FResponseLayoutDesign; componentMap['response-layout-item'] = FResponseLayoutItemDesign; - propsResolverMap['response-layout'] = responseLayoutPropsResolver; - propsResolverMap['response-layout-item'] = responseLayoutItemPropsResolver; + propsResolverMap['response-layout'] = responseLayoutPropsResolverGenerator(registerContext); + propsResolverMap['response-layout-item'] = responseLayoutItemPropsResolverGenerator(registerContext); } }; diff --git a/packages/ui-vue/components/response-layout/src/component/response-layout-item.component.tsx b/packages/ui-vue/components/response-layout/src/component/response-layout-item.component.tsx index 0eef4ca90e76a4a38513d28d0822c563f69c51d2..5e03a2bf5eacb8b1e9f5deb6ccda0e0e325495b1 100644 --- a/packages/ui-vue/components/response-layout/src/component/response-layout-item.component.tsx +++ b/packages/ui-vue/components/response-layout/src/component/response-layout-item.component.tsx @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue'; +import { defineComponent, computed } from 'vue'; import { ResponseLayoutItemPropsType, responseLayoutItemProps } from './response-layout-item.props'; export default defineComponent({ @@ -6,8 +6,13 @@ export default defineComponent({ props: responseLayoutItemProps, emits: [], setup(props: ResponseLayoutItemPropsType, context) { + const layoutItem = computed(() => ({ + 'f-response-layout-item': true, + [props.customClass]: !!props.customClass, + })); + return () => { - return
    {context.slots.default && context.slots.default()}
    ; + return
    {context.slots.default && context.slots.default()}
    ; }; } }); diff --git a/packages/ui-vue/components/response-layout/src/component/response-layout-item.props.ts b/packages/ui-vue/components/response-layout/src/component/response-layout-item.props.ts index 80db6d33f4baa4ddbccf89242464e1548a16e64b..682386127abdb33c3892ea59c74d25b610c28272 100644 --- a/packages/ui-vue/components/response-layout/src/component/response-layout-item.props.ts +++ b/packages/ui-vue/components/response-layout/src/component/response-layout-item.props.ts @@ -1,6 +1,6 @@ import { ExtractPropTypes } from "vue"; -import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { createPropsResolver, getPropsResolverGenerator } from "@farris/ui-vue/components/dynamic-resolver"; import { schemaMapper } from "../schema/schema-mapper"; import responseLayoutItemSchema from '../schema/response-layout-item.schema.json'; @@ -12,3 +12,10 @@ export const responseLayoutItemProps = { export type ResponseLayoutItemPropsType = ExtractPropTypes; export const responseLayoutItemPropsResolver = createPropsResolver(responseLayoutItemProps, responseLayoutItemSchema, schemaMapper); + +export const responseLayoutItemPropsResolverGenerator = getPropsResolverGenerator( + responseLayoutItemProps, + responseLayoutItemSchema, + schemaMapper, + undefined +); \ No newline at end of file diff --git a/packages/ui-vue/components/response-layout/src/designer/response-layout-item-designer-rules.ts b/packages/ui-vue/components/response-layout/src/designer/response-layout-item-designer-rules.ts index cf730e017a0ef3776866104c2116d5904d2f5323..3009635c29eca11cda557100a780196272d6a317 100644 --- a/packages/ui-vue/components/response-layout/src/designer/response-layout-item-designer-rules.ts +++ b/packages/ui-vue/components/response-layout/src/designer/response-layout-item-designer-rules.ts @@ -17,7 +17,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const currentViewModel = formSchemaUtils.getViewModelById(currentViewModelId); // 布局容器组件不能嵌套,不接收筛选方案、小分组 - const notAllowedControlTypes = [DgControl['response-layout'].type, DgControl['query-solution'].type, DgControl['fieldset'].type]; + const notAllowedControlTypes = [DgControl['response-layout'].type, DgControl['query-solution'].type, DgControl['fieldset'].type, DgControl['filter-bar'].type, DgControl['drawer'].type]; if (notAllowedControlTypes.includes(componentType)) { return false; } diff --git a/packages/ui-vue/components/response-layout/src/designer/response-layout-item.design.component.tsx b/packages/ui-vue/components/response-layout/src/designer/response-layout-item.design.component.tsx index 4964a0679912bdfe4b8488e9899f3a02a3f8cc22..38247903070d3eaf918cabd5abb8a01219611bb5 100644 --- a/packages/ui-vue/components/response-layout/src/designer/response-layout-item.design.component.tsx +++ b/packages/ui-vue/components/response-layout/src/designer/response-layout-item.design.component.tsx @@ -1,4 +1,4 @@ -import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { computed, CSSProperties, defineComponent, inject, onMounted, ref } from 'vue'; import { DesignerItemContext, useDesignerComponent } from '@farris/ui-vue/components/designer-canvas'; import { ResponseLayoutItemPropsType, responseLayoutItemProps } from '../component/response-layout-item.props'; import { useDesignerRules } from './response-layout-item-designer-rules'; @@ -10,19 +10,31 @@ export default defineComponent({ setup(props: ResponseLayoutItemPropsType, context) { const elementRef = ref(); const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); + const designerHostService = inject('designer-host-service') as any; const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + const { designerContext } = designerHostService; + const isMobileDesigner = !!designerContext && !!designerContext.responsiveForm && designerContext.designerMode === 'Mobile'; onMounted(() => { elementRef.value.componentInstance = componentInstance; }); context.expose(componentInstance.value); - + + const layoutItemClass = computed(() => ({ + 'f-response-layout-item': true, + [props.customClass]: !!props.customClass, + })); + const layoutItemStyle = computed(() => ({ + display: 'inherit', + flex: 1, + paddingRight: isMobileDesigner ? undefined : `8px`, + })); + return () => { return ( -
    {context.slots.default && context.slots.default()}
    diff --git a/packages/ui-vue/components/response-layout/src/property-config/response-layout-splitter/response-layout-splitter.props.ts b/packages/ui-vue/components/response-layout/src/property-config/response-layout-splitter/response-layout-splitter.props.ts index d971193382770272f5527ccb1be870edb0c617cf..5ee6969c059dbc4d1e0a5f4d861d69bb3646b79d 100644 --- a/packages/ui-vue/components/response-layout/src/property-config/response-layout-splitter/response-layout-splitter.props.ts +++ b/packages/ui-vue/components/response-layout/src/property-config/response-layout-splitter/response-layout-splitter.props.ts @@ -1,5 +1,5 @@ -import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { createPropsResolver, getPropsResolverGenerator } from "@farris/ui-vue/components/dynamic-resolver"; import { ExtractPropTypes } from "vue"; import responseLayoutSplitterSchema from './schema/response-layout-splitter.schema.json'; @@ -11,4 +11,11 @@ export type ResponseLayoutSplitterProps = ExtractPropTypes(responseLayoutSplitterProps, responseLayoutSplitterSchema); +export const responseLayoutSplitterPropsResolverGenerator = getPropsResolverGenerator( + responseLayoutSplitterProps, + responseLayoutSplitterSchema, + undefined, + undefined +); + diff --git a/packages/ui-vue/components/response-layout/src/response-layout.component.tsx b/packages/ui-vue/components/response-layout/src/response-layout.component.tsx index c12df263d4982db2bef37efc027f05ee978b1cf8..00fc15b3613a892895713633ecde6f87b7c6d3f7 100644 --- a/packages/ui-vue/components/response-layout/src/response-layout.component.tsx +++ b/packages/ui-vue/components/response-layout/src/response-layout.component.tsx @@ -17,7 +17,8 @@ export default defineComponent({ } const layoutClass = computed(() => { const classObject = { - 'd-flex': true + 'd-flex': true, + 'f-response-layout': true, } as Record; classConverter(classObject, props.customClass); return classObject; diff --git a/packages/ui-vue/components/response-layout/src/response-layout.props.ts b/packages/ui-vue/components/response-layout/src/response-layout.props.ts index bfac4b34d0cc1a2ac730350811538960f1b44d73..eaf8afb0230effcd8796005f2d22e4681fd4741f 100644 --- a/packages/ui-vue/components/response-layout/src/response-layout.props.ts +++ b/packages/ui-vue/components/response-layout/src/response-layout.props.ts @@ -1,6 +1,6 @@ import { ExtractPropTypes } from "vue"; -import { createPropsResolver } from "@farris/ui-vue/components/dynamic-resolver"; +import { createPropsResolver, getPropsResolverGenerator } from "@farris/ui-vue/components/dynamic-resolver"; import { schemaMapper } from "./schema/schema-mapper"; import { schemaResolver } from "./schema/schema-resolver"; import responseLayoutSchema from './schema/response-layout.schema.json'; @@ -13,3 +13,10 @@ export const responseLayoutProps = { export type ResponseLayoutPropsType = ExtractPropTypes; export const responseLayoutPropsResolver = createPropsResolver(responseLayoutProps, responseLayoutSchema, schemaMapper, schemaResolver); + +export const responseLayoutPropsResolverGenerator = getPropsResolverGenerator( + responseLayoutProps, + responseLayoutSchema, + schemaMapper, + schemaResolver +); \ No newline at end of file diff --git a/packages/ui-vue/components/response-toolbar/designer.ts b/packages/ui-vue/components/response-toolbar/designer.ts index ae46adbdb27ee380cf73d8b7101a3c6583e4355e..1819c5c8eae744e7175e47c4c5b7e2e9ec334b3d 100644 --- a/packages/ui-vue/components/response-toolbar/designer.ts +++ b/packages/ui-vue/components/response-toolbar/designer.ts @@ -1,5 +1,5 @@ import { withInstall } from '@farris/ui-vue/components/common'; -import { eventHandlerResolver, itemPropsResolver, propsResolver } from './src/designer/response-toolbar.design.props'; +import { eventHandlerResolver, itemPropsResolver, propsResolver, commonToolbarItemDesignProps } from './src/designer/response-toolbar.design.props'; import FResponseToolbar from './src/response-toolbar.component'; import FResponseToolbarDesign from './src/designer/response-toolbar.design.component'; import FResponseToolbarItemDesign from './src/designer/response-toolbar-item.design.component'; @@ -17,5 +17,5 @@ FResponseToolbarDesign.registerDesigner = (componentMap: Record, pr componentMap['response-toolbar-item'] = FResponseToolbarItemDesign; propsResolverMap['response-toolbar-item'] = itemPropsResolver; }; -export { responseToolbarResolver, FResponseToolbarDesign, FResponseToolbarItemDesign }; +export { responseToolbarResolver, FResponseToolbarDesign, FResponseToolbarItemDesign, commonToolbarItemDesignProps}; export default withInstall(FResponseToolbarDesign); diff --git a/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown-menu.component.tsx b/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown-menu.component.tsx index d3576b2ce32fc0725e049fc9d95cf613a5a1872b..7b645a16491306052de728c0f5de5ca68089d6e3 100644 --- a/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown-menu.component.tsx +++ b/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown-menu.component.tsx @@ -38,7 +38,8 @@ export default function (context, useIconComposition: UseIcon) { 'text-truncate': true, 'f-rt-toggle': !item.split, 'btn-icontext': !!(item.icon && item.icon.trim()), - 'dropdown-toggle': !item.split + 'dropdown-toggle': !item.split, + 'btn-secondary':true } as Record; return getCustomClass(classObject, item.dropdownClass); } diff --git a/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown.component.tsx b/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown.component.tsx index acaa83ba3eba6b07e722a051156a29754ac46991..9dac45c868f2b7e6ec700b60c48249cc3b67b4f0 100644 --- a/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown.component.tsx +++ b/packages/ui-vue/components/response-toolbar/src/components/dropdown/toolbar-dropdown.component.tsx @@ -24,9 +24,10 @@ export default function (props: ResponseToolbarProps, context, useIconCompositio 'btn': true, 'disabled': !item.enable, 'f-rt-btn': true, - 'btn-icontext': !!(item.icon && item.icon.trim()), + 'btn-icontext': !!(item.icon && item.icon.trim())&&item.text,// 图标+文本 'f-rt-toggle':!item.split, - 'dropdown-toggle':!item.split + 'dropdown-toggle':!item.split, + 'f-btn-icon':item.icon&&!item.text// 只有图标 } as Record; return getCustomClass(classObject, item.class); } @@ -91,7 +92,7 @@ export default function (props: ResponseToolbarProps, context, useIconCompositio style={dropdownButtonStyle(item)} onClick={(payload: MouseEvent) => item.enable && dropdownButtnClickHandler(payload, item)}> {useIconComposition.shouldShowIcon(item) && } - {item.text} + {item.text&&{item.text}}
    {item.split&&} {renderMenu && renderDropdownMenu(item)} @@ -103,7 +104,7 @@ export default function (props: ResponseToolbarProps, context, useIconCompositio style={dropdownButtonStyle(item)} onClick={(payload: MouseEvent) => item.enable && dropdownButtnClickHandler(payload, item)}> {useIconComposition.shouldShowIcon(item) && } - {item.text} + {item.text&&{item.text}}
    {item.split&&} {renderMenu && renderDropdownMenu(item)} diff --git a/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.component.tsx b/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.component.tsx index c1a0f259389cba316353f63d285a2f508ec04ce2..da072daed464540994e87a51a7468aef86127109 100644 --- a/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.component.tsx +++ b/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.component.tsx @@ -246,10 +246,23 @@ export default defineComponent({ if (!schema.buttons) { schema.buttons = []; } - const toolbarItemSchema = getSchemaByType('response-toolbar-item') as ComponentSchema; + let itemType=''; + switch(actionHandlers.toolbarPosition){ + case 'drawer-header-toolbar': + case 'drawer-footer-toolbar': + itemType='drawer-toolbar-item'; + break; + case 'tabs': + itemType='tab-toolbar-item'; + break; + case 'section': + itemType='section-toolbar-item'; + break; + default: + itemType='response-toolbar-item'; + } + const toolbarItemSchema = getSchemaByType(itemType) as ComponentSchema; toolbarItemSchema.id = `toolbar_item_${Math.random().toString().slice(2, 6)}`; - toolbarItemSchema.appearance = { class: 'btn btn-secondary' }; - schema.buttons.push(toolbarItemSchema); } diff --git a/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.props.ts b/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.props.ts index 385af76ba4bf4f555b6f0982632fdbbdbf13975d..482d374b038392a5cd5defd9ecb7b4aa5701419b 100644 --- a/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.props.ts +++ b/packages/ui-vue/components/response-toolbar/src/designer/response-toolbar.design.props.ts @@ -15,19 +15,18 @@ export const propsResolver = createPropsResolver( responseToolbarProps, responseToolbarSchema, schemaMapper, schemaResolver ); -export const responseToolbarItemDesignProps = { +export const commonToolbarItemDesignProps= { id: { type: String, default: '' }, - item:{type: Object as PropType, default: {} }, - items: { type: Object as PropType, default: {} }, - class: { type: String, default: 'btn-secondary' }, + item:{ type: Object, default: {} }, + items: { type: Object, default: {} }, + class: { type: String, default: '' }, text: { type: String, default: '' }, disabled: {type: Boolean, default: false}, icon: { type: String, default: '' }, componentId: { type: String, default: '' }, // 是否展开子级 expanded: { type: Boolean, default: false }, - alignment: { Type: String as PropType, default: 'right' }, - // 下拉按钮分离 + alignment: { Type: String, default: 'right' }, split: { type: Boolean, default: false }, // 是否下拉 isDP: { type: Boolean, default: false }, @@ -35,6 +34,13 @@ export const responseToolbarItemDesignProps = { isDPItem: { type: Boolean, default: false } } as Record; +export const responseToolbarItemDesignProps =Object.assign({},commonToolbarItemDesignProps,{ + item:{type: Object as PropType, default: {} }, + items: { type: Object as PropType, default: {} }, + class: { type: String, default: 'btn-secondary' }, + alignment: { Type: String as PropType, default: 'right'} +})as Record; + export type ResponseToolbarItemDesignProps = ExtractPropTypes; export const itemPropsResolver = createPropsResolver( responseToolbarItemDesignProps, responseToolbarItemSchema, undefined, schemaResolver diff --git a/packages/ui-vue/components/response-toolbar/src/designer/toolbar-dropdown-menu.design.component.tsx b/packages/ui-vue/components/response-toolbar/src/designer/toolbar-dropdown-menu.design.component.tsx index 14275b4b9a77f02fcfe22c05c397487449212969..4b47976a5c41296e7c0e17c096386555b9468ef2 100644 --- a/packages/ui-vue/components/response-toolbar/src/designer/toolbar-dropdown-menu.design.component.tsx +++ b/packages/ui-vue/components/response-toolbar/src/designer/toolbar-dropdown-menu.design.component.tsx @@ -127,7 +127,9 @@ export default function (useIconComposition: UseIcon, toolbarItems: Ref item === resultType) === -1) { + if (resultType === 'drawer') { + if (designItemContext.parent?.schema.headerToolbar.id === schema.id) { + resultType = 'drawer-header-toolbar'; + } else if (designItemContext.parent?.schema.footerToolbar.id === schema.id) { + resultType = 'drawer-footer-toolbar'; + } + } + if (["page-header", "tabs", "section", 'drawer-header-toolbar', 'drawer-footer-toolbar'].findIndex(item => item === resultType) === -1) { resultType = ""; } actionHandlers.toolbarPosition = resultType; @@ -16,8 +23,8 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe // 存储工具栏的位置 getToolbarPosition(); /** - * 判断是否可以接收拖拽新增的子级控件 - */ + * 判断是否可以接收拖拽新增的子级控件 + */ function canAccepts(draggingContext: DraggingResolveContext): boolean { return false; } @@ -44,7 +51,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe // 构造属性配置方法 function getPropsConfig(componentId: string) { - const responseToolbarProp = new ResponseToolbarProperty(componentId, designerHostService,schema.type); + const responseToolbarProp = new ResponseToolbarProperty(componentId, designerHostService, schema.type); return responseToolbarProp.getPropertyConfig(schema); } @@ -66,7 +73,19 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe itemSchemaType = schema.type + '-item'; } else { const parentSchema = designItemContext.parent?.schema.type; - itemSchemaType = parentSchema === 'tabs' ? 'tab-toolbar-item' : parentSchema === 'section' ? 'section-toolbar-item' : 'response-toolbar-item'; + switch(parentSchema){ + case 'tabs': + itemSchemaType='tab-toolbar-item'; + break; + case 'section': + itemSchemaType='section-toolbar-item'; + break; + case 'drawer': + itemSchemaType='drawer-toolbar-item'; + break; + default: + itemSchemaType='response-toolbar-item'; + } } const toolbarItemSchema = getSchemaByType(itemSchemaType) as ComponentSchema; toolbarItemSchema.id = `toolbar_item_${Math.random().toString().slice(2, 6)}`; diff --git a/packages/ui-vue/components/response-toolbar/src/property-config/response-toolbar-item.property-config.ts b/packages/ui-vue/components/response-toolbar/src/property-config/response-toolbar-item.property-config.ts index 610d30a05dcfcb021f477cb96980af72d9675923..140c99f14350ceacb9b69393211cde742aacfc96 100644 --- a/packages/ui-vue/components/response-toolbar/src/property-config/response-toolbar-item.property-config.ts +++ b/packages/ui-vue/components/response-toolbar/src/property-config/response-toolbar-item.property-config.ts @@ -52,6 +52,7 @@ export class ResponseToolbarItemProperty extends BaseControlProperty { private getAppearanceProperties(propertyData: any, isDropdownItem = false) { const self = this; + this.propertyConfig.categories['appearance'] = { "title": "外观", "description": "Appearance", @@ -78,7 +79,7 @@ export class ResponseToolbarItemProperty extends BaseControlProperty { "title": "下拉按钮分离", "type": "boolean", "description": "启用下拉按钮分离,可以单独点击按钮。单独点击下拉按钮展开下拉面板", - "visible":propertyData.children&&propertyData.children.length>0?true:false + "visible": propertyData.children && propertyData.children.length > 0 ? true : false }, "tipsEnable": { "title": "启用提示", diff --git a/packages/ui-vue/components/response-toolbar/src/response-toolbar.component.tsx b/packages/ui-vue/components/response-toolbar/src/response-toolbar.component.tsx index d92b2dae2d34c1621e56bc6d8f15624b7bb5dd9d..22d2a060dcbfb4cc7f8f8a6cfc945719d49b7124 100644 --- a/packages/ui-vue/components/response-toolbar/src/response-toolbar.component.tsx +++ b/packages/ui-vue/components/response-toolbar/src/response-toolbar.component.tsx @@ -1,13 +1,13 @@ import { computed, defineComponent, ref, onMounted, onUnmounted, watch, nextTick, reactive } from 'vue'; import { getCustomClass, useGuid } from '@farris/ui-vue/components/common'; -import { useI18n } from 'vue-i18n'; import { ResponseToolbarProps, responseToolbarProps } from './response-toolbar.props'; import { ResponseToolbarDropDownItem } from './types/response-toolbar-dropdwon-item'; import { ResponseToolbarItem } from './types/response-toolbar-item'; import { useIcon } from './composition/use-icon'; import { useToolbarItem } from './composition/use-toolbar-item'; import getDropdown from './components/dropdown/toolbar-dropdown.component'; - +import { LocaleService } from '@farris/ui-vue/components/locale'; +import {isMobilePhone} from '@farris/ui-vue/components/common'; const { buildResponseToolbarItems } = useToolbarItem(); export default defineComponent({ @@ -15,14 +15,15 @@ export default defineComponent({ props: responseToolbarProps, emits: ['click'], setup(props: ResponseToolbarProps, context) { - const { t } = useI18n(); const toolbarItems = ref(buildResponseToolbarItems(props.items)); const responseToolBarContainer = ref(); const resizedContainer = ref(); const resizedContent = ref(); - const defaultDropdownOptions = { id: '__more_buttons__', text: t('responseToolbar.more') }; + // 是否在小屏的移动设备上 + const isMobilePhoneState=isMobilePhone(); + const defaultDropdownOptions = { id: '__more_buttons__', text:isMobilePhoneState?'':LocaleService.getLocaleValue('responseToolbar.more'),icon:isMobilePhoneState?'f-icon-more-horizontal':''}; const defaultDropdown = ref(new ResponseToolbarDropDownItem(defaultDropdownOptions)); - const moreButtonOptions = { id: defaultDropdownOptions.id + 'width', text: defaultDropdownOptions.text }; + const moreButtonOptions =Object.assign({},defaultDropdownOptions,{id: defaultDropdownOptions.id + 'width'}); // 计算更多按钮的宽度 const moreButtonWidthObtained = ref(-1); const useIconComposition = useIcon(); @@ -35,6 +36,7 @@ export default defineComponent({ // body内的下拉容器Id const toolbarDropdownContainerId = 'dropdown-container-' + toolbarId + '-' + uuid(4); const { renderToolbarDropdown, clearAllDropDown, updateContainerId } = getDropdown(props, context, useIconComposition); + // 传递工具栏按钮ID updateContainerId(toolbarDropdownContainerId); const shouldShowDefaultDropdown = computed(() => defaultDropdown.value.children.length > 0); @@ -255,8 +257,47 @@ export default defineComponent({ } } return availableSpace; + } + /** + * 重置计算相关数据 + */ + function resetHideDatas() { + itemsToHide = new Map(); + hiddenItems = []; + } + /** + * 更新状态 + */ + function updateItemsState(inMobile=false){ + const currentDropdownOptions = Object.assign({}, defaultDropdown.value); + currentDropdownOptions.children = []; + const dropdownItem = new ResponseToolbarDropDownItem(currentDropdownOptions); + const availableItems = toolbarItems.value.reduce((items: any, toolbarItem: any) => { + const originalItem = props.items.find(item => item.id === toolbarItem.id); + if(inMobile){ + dropdownItem.children.push(toolbarItem); + toolbarItem.shown = false; + return items; + } + if (itemsToHide.has(toolbarItem.id)) { + dropdownItem.children.push(toolbarItem); + toolbarItem.shown = false; + } else { + // 存在按钮的状态外部指定 + toolbarItem.shown = originalItem.visible === false ? false : true; + } + items.push(toolbarItem); + return items; + }, []); + defaultDropdown.value = dropdownItem; + toolbarItems.value = availableItems; } function toResizeToolbarItems(containerWidth: number) { + if(isMobilePhoneState){ + resetHideDatas(); + updateItemsState(true); + return; + } let availableSpace = computedAvailableSpace(containerWidth); if (hiddenItems.length) { // 注意从隐藏元素中取出按钮的顺序 @@ -280,24 +321,24 @@ export default defineComponent({ } hiddenItems.splice(0, index - 1); } - const currentDropdownOptions = Object.assign({}, defaultDropdown.value); - currentDropdownOptions.children = []; - const dropdownItem = new ResponseToolbarDropDownItem(currentDropdownOptions); - const availableItems = toolbarItems.value.reduce((items: any, toolbarItem: any) => { - const originalItem = props.items.find(item => item.id === toolbarItem.id); - if (itemsToHide.has(toolbarItem.id)) { - dropdownItem.children.push(toolbarItem); - toolbarItem.shown = false; + updateItemsState(); + } + + /** + * 提前计算更多按钮宽度,解决初始化没有更多按钮不能准确计算尺寸问题 + */ + function computeMoreButtonWidth() { + if (responseToolBarContainer.value && moreButtonWidthObtained.value < 0) { + const moreButtonElement = (responseToolBarContainer.value as HTMLElement).querySelector("#" + moreButtonOptions.id); + if (!moreButtonElement) { return; } + if ((moreButtonElement as HTMLElement).getBoundingClientRect().width === 0) { + moreButtonWidthObtained.value = -1; } else { - // 存在按钮的状态外部指定 - toolbarItem.shown = originalItem.visible === false ? false : true; + moreButtonWidthObtained.value = getItemWidth(moreButtonElement); } - items.push(toolbarItem); - return items; - }, []); - defaultDropdown.value = dropdownItem; - toolbarItems.value = availableItems; + } } + function resetToolbarItemsForWidthChanged(containerWidth: number = -1) { if (containerWidth === 0) { return; @@ -309,8 +350,14 @@ export default defineComponent({ containerWidth = resizedContainer.value.getBoundingClientRect().width; } const contentWidth = resizedContent.value.getBoundingClientRect().width; + if(isMobilePhoneState){ + toResizeToolbarItems(containerWidth); + collapseAllDropdownMenu(); + return; + } // 内容超过容器 或者 存在下拉元素 if (containerWidth >= 0 && containerWidth < contentWidth || hiddenItems.length) { + computeMoreButtonWidth(); toResizeToolbarItems(containerWidth); collapseAllDropdownMenu(); } @@ -320,22 +367,16 @@ export default defineComponent({ * @returns */ function renderMoreButton() { - return moreButtonWidthObtained.value < 0 &&
    {renderToolbarDropdown(new ResponseToolbarDropDownItem(moreButtonOptions))}
    ; + return moreButtonWidthObtained.value < 0 &&
    {renderToolbarDropdown(new ResponseToolbarDropDownItem(moreButtonOptions))}
    ; } const observer = new ResizeObserver((entries: ResizeObserverEntry[]) => { if (entries.length) { + if(isMobilePhoneState){return;} const responseContainerEntry = entries[0]; const containerWidth = responseContainerEntry.contentRect.width; resetToolbarItemsForWidthChanged(containerWidth); } }); - /** - * 重置计算相关数据 - */ - function resetHideDatas() { - itemsToHide = new Map(); - hiddenItems = []; - } /** * 为元素绑定监听 */ @@ -345,17 +386,6 @@ export default defineComponent({ hasBoundObserver = true; } } - /** - * 提前计算更多按钮宽度,解决初始化没有更多按钮不能准确计算尺寸问题 - */ - function computeMoreButtonWidth() { - if (responseToolBarContainer.value && moreButtonWidthObtained.value < 0) { - const moreButtonElement = (responseToolBarContainer.value as HTMLElement).querySelector("#" + moreButtonOptions.id); - if (moreButtonElement) { - moreButtonWidthObtained.value = getItemWidth(moreButtonElement); - } - } - } onMounted(() => { // 计算模拟的更多按钮宽度 @@ -381,6 +411,8 @@ export default defineComponent({ toolbarItems.value = buildResponseToolbarItems(props.items); defaultDropdown.value.children = []; nextTick(() => { + // 计算模拟的更多按钮宽度 + computeMoreButtonWidth(); // 重置计算相关数据,解决数据更新下拉里面还有值的问题 resetHideDatas(); // 解决工具栏项数据变更,之前的下拉等数据消失问题 diff --git a/packages/ui-vue/components/schema-selector/src/components/card-view.component.tsx b/packages/ui-vue/components/schema-selector/src/components/card-view.component.tsx index a5c4b6b751cfd3faa8778149b60eb808fa511dcb..5e80efd3fe4c26d4519920e43a45cd05f6e73c2e 100644 --- a/packages/ui-vue/components/schema-selector/src/components/card-view.component.tsx +++ b/packages/ui-vue/components/schema-selector/src/components/card-view.component.tsx @@ -109,9 +109,7 @@ export default defineComponent({ }); function clearSelection() { - listViewRef.value.clearSelection(); - listViewRef.value.updateSelectionByIds([]); - + listViewRef.value.activeRowById(''); } context.expose({ clearSelection }); diff --git a/packages/ui-vue/components/schema-selector/src/components/list-view.component.tsx b/packages/ui-vue/components/schema-selector/src/components/list-view.component.tsx index 6047df141c3cf0e12b59f5ca07123305a0b64db7..0cd14eca6170f3c9c9e884e79f8b58fa0eff8490 100644 --- a/packages/ui-vue/components/schema-selector/src/components/list-view.component.tsx +++ b/packages/ui-vue/components/schema-selector/src/components/list-view.component.tsx @@ -53,8 +53,7 @@ export default defineComponent({ loadData(); }); function clearSelection() { - listViewRef.value.clearSelection(); - listViewRef.value.updateSelectionByIds([]); + listViewRef.value.activeRowById(''); } context.expose({ clearSelection }); diff --git a/packages/ui-vue/components/schema-selector/src/components/nav-list-view.component.tsx b/packages/ui-vue/components/schema-selector/src/components/nav-list-view.component.tsx index 26657c1865446dee5de2abe5ce6eae2365bf1586..be0377ae4cab5533794db757860a81d8e18c7007 100644 --- a/packages/ui-vue/components/schema-selector/src/components/nav-list-view.component.tsx +++ b/packages/ui-vue/components/schema-selector/src/components/nav-list-view.component.tsx @@ -58,9 +58,8 @@ export default defineComponent({ function updateListViewDataSource(items: any[]) { if (controllerListViewRef.value) { - controllerListViewRef.value.clearSelection(); controllerListViewRef.value.updateDataSource(items); - controllerListViewRef.value.updateSelectionByIds([]); + controllerListViewRef.value.activeRowById(''); } } function filterListViewData() { diff --git a/packages/ui-vue/components/section/designer.ts b/packages/ui-vue/components/section/designer.ts index adecb4e5a1b3f578d21b94ef7443886be80f42d8..d0fa36419604c43e856125cb3196281431b6541f 100644 --- a/packages/ui-vue/components/section/designer.ts +++ b/packages/ui-vue/components/section/designer.ts @@ -18,7 +18,7 @@ import Section from '@farris/ui-vue/components/section'; import { withInstall } from '@farris/ui-vue/components/common'; import { FResponseToolbarDesign } from '@farris/ui-vue/components/response-toolbar/designer'; import { sectionToolbarItemResolver } from './src/designer/section-toolbar-item.design.props'; -import { eventHandlerResolver, propsResolver,sectionToolbarPropsResolver } from './src/designer/section.design.props'; +import { eventHandlerResolver, propsDesignResolver, propsResolver,sectionToolbarPropsResolver } from './src/designer/section.design.props'; import SectionDesign from './src/designer/section.design.component'; SectionDesign.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { @@ -28,7 +28,7 @@ SectionDesign.register = (componentMap: Record, propsResolverMap: R }; SectionDesign.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record) => { componentMap.section = SectionDesign; - propsResolverMap.section = propsResolver; + propsResolverMap.section = propsDesignResolver; propsResolverMap['section-toolbar-item'] = sectionToolbarItemResolver; componentMap['section-toolbar'] = FResponseToolbarDesign; propsResolverMap['section-toolbar'] = sectionToolbarPropsResolver; diff --git a/packages/ui-vue/components/section/src/designer/section-header.design.component.tsx b/packages/ui-vue/components/section/src/designer/section-header.design.component.tsx index 19ffe57c971bcd9e421a6d0d08fefcecf0beb23f..08398dbc0f63706e2fc39e904245d9db399926f2 100644 --- a/packages/ui-vue/components/section/src/designer/section-header.design.component.tsx +++ b/packages/ui-vue/components/section/src/designer/section-header.design.component.tsx @@ -40,7 +40,7 @@ export default function ( expandStatus.value = !expandStatus.value; nextTick(() => { - setPositionOfSelectedComponentBtnGroup(designItemContext.designerItemElementRef.value); + setPositionOfSelectedComponentBtnGroup(); }); } } diff --git a/packages/ui-vue/components/section/src/designer/section-toolbar.design.component.tsx b/packages/ui-vue/components/section/src/designer/section-toolbar.design.component.tsx index 00ed2c4632a136a701394a3aec9de64b254cfba7..5389c8ed1748554988f65005ed0b3c5d90f47a9b 100644 --- a/packages/ui-vue/components/section/src/designer/section-toolbar.design.component.tsx +++ b/packages/ui-vue/components/section/src/designer/section-toolbar.design.component.tsx @@ -1,7 +1,7 @@ import { computed, ref } from 'vue'; import { DesignerItemContext, FDesignerInnerItem } from '@farris/ui-vue/components/designer-canvas'; -import FResponseToolbarDesign from '@farris/ui-vue/components/response-toolbar'; +import FResponseToolbarDesign from '@farris/ui-vue/components/response-toolbar/designer'; import { SectionDesignProps } from './section.design.props'; export default function ( diff --git a/packages/ui-vue/components/section/src/designer/section.design.props.ts b/packages/ui-vue/components/section/src/designer/section.design.props.ts index 2c3fbb88d9588963cf74fe14aeb48f31c89b225f..4af04385054048c989ac781b9b27e189018deb2d 100644 --- a/packages/ui-vue/components/section/src/designer/section.design.props.ts +++ b/packages/ui-vue/components/section/src/designer/section.design.props.ts @@ -10,9 +10,18 @@ import sectionToolbarSchema from '../schema/section-toolbar.schema.json'; export const propsResolver = createPropsResolver(sectionProps, sectionSchema, schemaMapper, schemaResolver); export const eventHandlerResolver = createSectionEventHandlerResolver(); -export const sectionDesignProps = Object.assign({}, sectionProps, { componentId: { type: String, default: '' } }); +export const sectionDesignProps = Object.assign({}, sectionProps, + { + componentId: { type: String, default: '' }, + headerTitleHtml: { type: String, default: '' }, + headerHtml: { type: String, default: '' }, + headerContentHtml: { type: String, default: '' }, + toolbarHtml: { type: String, default: '' }, + }); export type SectionDesignProps = ExtractPropTypes; export const sectionToolbarPropsResolver = createPropsResolver( responseToolbarProps, sectionToolbarSchema, schemaMapper, responseSchemaResolver ); +export const propsDesignResolver = createPropsResolver(sectionDesignProps, sectionSchema, schemaMapper, schemaResolver); + diff --git a/packages/ui-vue/components/section/src/designer/use-designer-rules.ts b/packages/ui-vue/components/section/src/designer/use-designer-rules.ts index b635d16125ed33f074b700de66f81a9a15d2c0d9..b0e2ba697da8947e3ac2e73d4774bbb1753e43e4 100644 --- a/packages/ui-vue/components/section/src/designer/use-designer-rules.ts +++ b/packages/ui-vue/components/section/src/designer/use-designer-rules.ts @@ -36,6 +36,24 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } return false; } + + /** + * 判断树表格绑定的分级码是否分级码分级信息 + * @param treeGridViewModelId 树表格所在的模型id + * @param udtField 树表格绑定的分级码字段编号 + */ + function checkPathHierarchyType(treeGridViewModelId: string, udtField: string) { + const schemaService = designerHostService?.schemaService; + const udtFields = schemaService.getTreeGridUdtFields(treeGridViewModelId); + + if (udtFields) { + const bindingUdtFieldInfo = udtFields.find(fieldInfo => fieldInfo.key === udtField); + const bindingUdtField = bindingUdtFieldInfo && bindingUdtFieldInfo.field; + if (bindingUdtField?.type?.name?.includes('PathHierarchyInfo')) { + return true; + } + } + } /** * 判断当前容器是否可接收筛选条 */ @@ -46,17 +64,23 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const sectionClass = schema.appearance?.class; const { sourceType } = draggingContext; const { formSchemaUtils } = designerHostService; - // 分组面板必须包含特定样式 + // 分组面板必须包含特定样式(表格或树表格相关) + // const isGridSection = sectionClass && (sectionClass.includes('f-section-grid') || sectionClass.includes('f-section-treegrid')); const isGridSection = sectionClass && sectionClass.includes('f-section-grid'); if (!isGridSection) { return false; } - // 表格必须绑定主表 + // 表格、树表格必须绑定主表 const viewModelId = formSchemaUtils.getViewModelIdByComponentId(designItemContext.componentInstance.value.belongedComponentId); const viewModel = formSchemaUtils.getViewModelById(viewModelId); if (viewModel && viewModel.bindTo !== '/') { return false; } + // 树表格绑定的分级码udt字段必须为分级码分级信息,不能为父节点分级信息 + const treeGrid = formSchemaUtils.selectNode(schema, item => item.type === 'tree-grid'); + if (treeGrid && !checkPathHierarchyType(viewModelId, treeGrid.udtField)) { + return false; + } if (sourceType === 'move') { return true; @@ -117,7 +141,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } const { schema } = designItemContext; if (!schema.toolbar || !schema.toolbar.buttons) { - schema.toolbar = { id: `${schema.id}_toolbar`, type:'section-toolbar',buttons: [] }; + schema.toolbar = { id: `${schema.id}_toolbar`, type: 'section-toolbar', buttons: [] }; } const sectionToolbarItemSchema = getSchemaByType('section-toolbar-item') as ComponentSchema; sectionToolbarItemSchema.id = `section_toolbar_item_${Math.random().toString().slice(2, 6)}`; diff --git a/packages/ui-vue/components/section/src/property-config/section.property-config.ts b/packages/ui-vue/components/section/src/property-config/section.property-config.ts index b3946969f2d38aa0a8b18e7a400cb1ef56bb4642..a46b9e34d6b75026194ab415d5330290b71f00ee 100644 --- a/packages/ui-vue/components/section/src/property-config/section.property-config.ts +++ b/packages/ui-vue/components/section/src/property-config/section.property-config.ts @@ -159,7 +159,7 @@ export class SectionProperty extends BaseControlProperty { headerTitleHtml: { title: '标题模板', type: 'string', - description: '设置标题HTML模板', + description: '为标题区域指定HTML模板', refreshPanelAfterChanged: true, editor: { type: "code-editor", diff --git a/packages/ui-vue/components/section/src/schema/schema-resolver.ts b/packages/ui-vue/components/section/src/schema/schema-resolver.ts index ca598e42ee404a697b920798c506143f6503aa5b..733541a5ade594bcfe68059e9a7d1a0c2452025f 100644 --- a/packages/ui-vue/components/section/src/schema/schema-resolver.ts +++ b/packages/ui-vue/components/section/src/schema/schema-resolver.ts @@ -1,13 +1,24 @@ import { ComponentSchema, DesignerComponentInstance, DgControl } from "@farris/ui-vue/components/designer-canvas"; import { DynamicResolver } from "@farris/ui-vue/components/dynamic-resolver"; +/** + * 若父容器是flex布局,那么需要将容器设置为display:block,否则容器宽度有问题 + */ +function adaptStructContainer(containerSchema: any, parentComponentInstance: DesignerComponentInstance) { + if (!parentComponentInstance?.schema) { + return; + } + const targetComponentClassName = parentComponentInstance.schema?.appearance?.class || ''; + + const parentElementRef: any = parentComponentInstance.elementRef; + const computedStyle = window.getComputedStyle(parentElementRef); + // css文件中规定f-struct-wrapper继承父级的display,但若父级是flex,container集成flex后,section的宽度会有问题 + if (computedStyle && computedStyle.display === 'flex' && !targetComponentClassName.includes('f-page-child-fill')) { + containerSchema.appearance.class += ' d-block'; + } +} /** * 从工具箱拖拽创建分组面板,自动创建container-section两层结构 - * @param resolver - * @param sectionSchema - * @param context - * @param designerHostService - * @returns */ function wrapContainerForSection(resolver: DynamicResolver, context: Record) { const radomNum = Math.random().toString().slice(2, 6); @@ -33,19 +44,30 @@ function wrapContainerForSection(resolver: DynamicResolver, context: Record; export const propsResolver = createPropsResolver(sectionProps, sectionSchema, schemaMapper, schemaResolver); export const eventHandlerResolver = createSectionEventHandlerResolver(); -export const sectionDesignProps = Object.assign({}, sectionProps, - { - componentId: { type: String, default: '' }, - headerTitleHtml: { type: String, default: '' }, - headerHtml: { type: String, default: '' }, - headerContentHtml: { type: String, default: '' }, - toolbarHtml: { type: String, default: '' }, - }); -export type SectionDesignProps = ExtractPropTypes; -export const sectionToolbarPropsResolver = createPropsResolver( - responseToolbarProps, sectionToolbarSchema, schemaMapper, responseSchemaResolver -); - -export const propsDesignResolver = createPropsResolver(sectionDesignProps, sectionSchema, schemaMapper, schemaResolver); + + diff --git a/packages/ui-vue/components/splitter/src/splitter.css b/packages/ui-vue/components/splitter/src/splitter.css index d723652d9f3ebbd2c7f977be1279709e89307bfa..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/packages/ui-vue/components/splitter/src/splitter.css +++ b/packages/ui-vue/components/splitter/src/splitter.css @@ -1,108 +0,0 @@ -.f-splitter { - flex-shrink: 1; - flex-grow: 1; - flex-basis: 0; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - overflow: hidden; - position: relative; -} - -.f-splitter-pane.f-splitter-pane-main { - flex-shrink: 1; - flex-grow: 1; - flex-basis: 0; - overflow: auto; - display: flex; -} - -.f-splitter-pane.f-splitter-pane-column { - display: flex; - flex-direction: column; - box-shadow: 0 0 8px 0 rgba(0, 28, 64, 0.08); - padding: 0; - position: relative; - z-index: 100; -} - -.f-splitter-pane.f-splitter-pane-row { - display: flex; - flex-direction: column; - box-shadow: 0 0 8px 0 rgba(0, 28, 64, 0.08); - padding: 0; - position: relative; - z-index: 100; -} - -.f-splitter-pane>.f-splitter-resize-bar.f-splitter-resize-bar-e { - cursor: e-resize; - width: 0.4375rem; - right: -0.3125rem; - height: 100%; - top: 0; -} - -.f-splitter-pane>.f-splitter-resize-bar.f-splitter-resize-bar-w { - cursor: w-resize; - width: 0.4375rem; - left: -0.3125rem; - height: 100%; - top: 0; -} - -.f-splitter-pane>.f-splitter-resize-bar.f-splitter-resize-bar-s { - cursor: s-resize; - height: 0.4375rem; - bottom: -0.3125rem; - width: 100%; - left: 0; -} - -.f-splitter-pane>.f-splitter-resize-bar.f-splitter-resize-bar-n { - cursor: n-resize; - height: 0.4375rem; - top: -0.3125rem; - width: 100%; - left: 0; -} - -.f-splitter-pane>.f-splitter-resize-bar { - position: absolute; - font-size: 0.1px; - display: block; - touch-action: none; -} - -.f-splitter-pane>.f-splitter-resize-bar:hover { - background: rgba(42, 135, 255, 0.07); -} - -.f-splitter-resize-overlay { - z-index: 98; - width: 100%; - height: 100%; - cursor: e-resize; - background: 0 0; - position: absolute; -} - -.f-splitter-horizontal-resize-proxy { - width: 0.4375rem; - background: rgba(42, 135, 255, 0.07); - left: 0; - display: none; - position: absolute; - height: 100%; - z-index: 100; -} - -.f-splitter-vertical-resize-proxy { - height: 0.4375rem; - background: rgba(42, 135, 255, 0.07); - top: 0; - display: none; - position: absolute; - width: 100%; - z-index: 100; -} \ No newline at end of file diff --git a/packages/ui-vue/components/step/index.ts b/packages/ui-vue/components/step/index.ts index 88752b82c184820d95877e922290bf954d240e92..cf641ffec75b1b535c0988c8e83f8ad7add196f3 100644 --- a/packages/ui-vue/components/step/index.ts +++ b/packages/ui-vue/components/step/index.ts @@ -1,23 +1,22 @@ -import type { App } from 'vue'; -import Step from './src/step.component'; +import type { App, Plugin } from 'vue'; +import FStep from './src/step.component'; import { propsResolver } from './src/step.props'; import FStepDesign from './src/designer/step.design.component'; export * from './src/step.props'; -export { Step }; +export { FStep }; -export default { - install(app: App): void { - app.component(Step.name as string, Step); - }, - register(componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void { - componentMap.step = Step; - propsResolverMap.step = propsResolver; - }, - registerDesigner(componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void { - componentMap.step = FStepDesign; - propsResolverMap.step = propsResolver; - } +FStep.install = (app: App): void => { + app.component(FStep.name as string, FStep); }; +FStep.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record): void => { + componentMap.step = FStep; + propsResolverMap.step = propsResolver; +}; +FStep.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record): void => { + componentMap.step = FStepDesign; + propsResolverMap.step = propsResolver; +}; +export default FStep as typeof FStep & Plugin; diff --git a/packages/ui-vue/components/switch/src/property-config/switch.property-config.ts b/packages/ui-vue/components/switch/src/property-config/switch.property-config.ts index 7dc109cd34190fc5ab671af123e0af8c4e26c096..a8372da6e6f2c2689560bdf5dd1b7c95f56dd277 100644 --- a/packages/ui-vue/components/switch/src/property-config/switch.property-config.ts +++ b/packages/ui-vue/components/switch/src/property-config/switch.property-config.ts @@ -62,7 +62,7 @@ export class SwitchProperty extends InputBaseProperty { description: "打开时的值", title: "打开的值", type: this.getBindingDataType(), - visible: this.designViewModelField?.type.name !== 'Boolean', + visible: this.designViewModelField != null && this.designViewModelField?.type.name !== 'Boolean', refreshPanelAfterChanged: true, editor: this.getEditor(), $converter: this.getBooleanValueConverter() @@ -71,7 +71,7 @@ export class SwitchProperty extends InputBaseProperty { description: "关闭时的值", title: "关闭的值", type: this.getBindingDataType(), - visible: this.designViewModelField?.type.name !== 'Boolean', + visible: this.designViewModelField != null && this.designViewModelField?.type.name !== 'Boolean', refreshPanelAfterChanged: true, editor: this.getEditor(), $converter: this.getBooleanValueConverter() diff --git a/packages/ui-vue/components/switch/src/switch.component.tsx b/packages/ui-vue/components/switch/src/switch.component.tsx index fb8970c20ee31f6989d55eeffc1ee246983a57df..22a8f3d4a58f039911af0e6e66bd96efe0eef57b 100644 --- a/packages/ui-vue/components/switch/src/switch.component.tsx +++ b/packages/ui-vue/components/switch/src/switch.component.tsx @@ -15,7 +15,6 @@ */ import { computed, defineComponent, onMounted, ref, SetupContext, toRefs, watch, nextTick } from 'vue'; import { switchProps, SwitchProps } from './switch.props'; -import './switch.css'; export default defineComponent({ name: 'FSwitch', diff --git a/packages/ui-vue/components/switch/src/switch.css b/packages/ui-vue/components/switch/src/switch.css deleted file mode 100644 index fb476873bc6a4cf6b7547ab0a27f6695f2248ee8..0000000000000000000000000000000000000000 --- a/packages/ui-vue/components/switch/src/switch.css +++ /dev/null @@ -1,23 +0,0 @@ -span.switch.checked.disabled { - background: rgba(42, 135, 255, .5); -} - -span.switch.disabled{ - background: rgba(217,222,231,.5); -} - - -.switch.switch-small { - height: 1rem!important; -} - -.switch.switch-small small { - top: 1px!important; - left: 1px!important; -} - -.switch.switch-small.checked small { - top: 1px; - right: 1px!important; - left: auto!important; -} diff --git a/packages/ui-vue/components/tabs/src/components/tab-header-dropdown-menu.component.tsx b/packages/ui-vue/components/tabs/src/components/tab-header-dropdown-menu.component.tsx index 4c6151c5eb337d660ea7a82699b8fcc066236fca..ea293b930c4e78d90b15dc25d1524fd0b132a964 100644 --- a/packages/ui-vue/components/tabs/src/components/tab-header-dropdown-menu.component.tsx +++ b/packages/ui-vue/components/tabs/src/components/tab-header-dropdown-menu.component.tsx @@ -1,7 +1,7 @@ import { computed, nextTick, ref, watch } from "vue"; import { TabPageContext, UseDropdown, UseNav, UseTabs } from "../composition/types"; import { TabsProps } from "../tabs.props"; -import { useI18n } from 'vue-i18n'; +import { LocaleService } from '@farris/ui-vue/components/locale'; export default function ( props: TabsProps, @@ -9,7 +9,6 @@ export default function ( useNavComposition: UseNav, useTabsComposition: UseTabs ) { - const { t } = useI18n(); const navDropdownMenu = ref(); const { activeId, removeTab, tabPages, selectTab } = useTabsComposition; const { hideDropDown, searchTabText } = useDropDownComposition; @@ -163,7 +162,7 @@ export default function ( })} ) : ( - + )}
    ); diff --git a/packages/ui-vue/components/tabs/src/components/tab-page.component.tsx b/packages/ui-vue/components/tabs/src/components/tab-page.component.tsx index 8fd5f1420dfbd03dd20f112e257838d5a235c1b9..4bbc8ead66872ee94a59abbdb283c0dee019322b 100644 --- a/packages/ui-vue/components/tabs/src/components/tab-page.component.tsx +++ b/packages/ui-vue/components/tabs/src/components/tab-page.component.tsx @@ -18,13 +18,16 @@ import { JSX } from 'vue/jsx-runtime'; import FSection from '@farris/ui-vue/components/section'; import { TabPageContext, TabsContext } from '../composition/types'; import { TabPageProps, tabPageProps } from './tab-page.props'; +import {isMobilePhone} from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FTabPage', props: tabPageProps, emits: [] as (string[] & ThisType) | undefined, setup(props: TabPageProps, context: SetupContext) { - const tabsContext = inject('tabs'); + const tabsContext = inject('tabs'); + // 是否在小屏的移动设备上 + const isMobilePhoneState=isMobilePhone(); const showContent = ref(props.show); const tabPageContext: TabPageContext = { slots: context.slots, @@ -82,13 +85,26 @@ export default defineComponent({ ); } + function sectionToolbarItemClick(ev,itemId){ + if(tabsContext){ + tabsContext.activeId.value=props.id; + tabsContext['clickToolbarItemHandler']&& tabsContext['clickToolbarItemHandler'](ev,itemId); + } + } + function mobileSectionRender(){ + const content = context.slots.default?.() as any; + return ( + + {content} + + ); + } const tabPageClass = computed(() => { const classObject = { - 'farris-tab-page': true, 'f-tab-d-none': !showContent.value, - 'f-utils-fill-flex-column': isActivePage.value && tabsContext && tabsContext.shouldFillParentContaner?.value + 'f-tab-active': isActivePage.value && tabsContext && tabsContext.shouldFillParentContaner?.value } as Record; return classObject; }); @@ -104,10 +120,11 @@ export default defineComponent({ const pageContentRenderMap = new Map JSX.Element>([ ['default', defaultRender], - ['one-page', sectionRender] + ['one-page', sectionRender], + ['mobile',mobileSectionRender] ]); - const pageContentRender = pageContentRenderMap.get(tabType.value) || defaultRender; + const pageContentRender =isMobilePhoneState?mobileSectionRender: (pageContentRenderMap.get(tabType.value) || defaultRender); return () => { // const content = context.slots.default?.() as any; diff --git a/packages/ui-vue/components/tabs/src/composition/types.ts b/packages/ui-vue/components/tabs/src/composition/types.ts index ab0ecaae0c8c4dee67a42d2e1a8d5009a2d3d1aa..45621219b6a6b9d5c7c27bdd3c4b278bfa86d66f 100644 --- a/packages/ui-vue/components/tabs/src/composition/types.ts +++ b/packages/ui-vue/components/tabs/src/composition/types.ts @@ -30,6 +30,7 @@ export interface TabsContext { toggleShowTab: (targetTabId: string, closeDropdown?: boolean) => void; shouldFillParentContaner?: ComputedRef; toolbarVisible:Ref; + clickToolbarItemHandler?: (payload: any, itemId: string)=>void; } export type ToolbarPosition = 'inHead' | 'inContent'; diff --git a/packages/ui-vue/components/tabs/src/composition/use-tabs.ts b/packages/ui-vue/components/tabs/src/composition/use-tabs.ts index 7cca3e8698436e45585a2bd53f2134c940e9609a..6fd6f788d23585a2e8501e360283a3f6f9269077 100644 --- a/packages/ui-vue/components/tabs/src/composition/use-tabs.ts +++ b/packages/ui-vue/components/tabs/src/composition/use-tabs.ts @@ -120,7 +120,7 @@ export function useTabs( } // 监听选中页签属性的变化,实现响应式变化 watch(() => props.activeId, (newValue, oldValue) => { - if (newValue !== oldValue) { + if (newValue !== oldValue && props.activeId !== activeId.value) { // 更新并选中 checkActiveIdAndSelect(newValue); } diff --git a/packages/ui-vue/components/tabs/src/designer/tab-page.design.component.tsx b/packages/ui-vue/components/tabs/src/designer/tab-page.design.component.tsx index 2e002a2b35c62e8af86a264dbe272b0107e2bf5e..c1c75c91847b980ef412b5219ae2bcb42b3b28a9 100644 --- a/packages/ui-vue/components/tabs/src/designer/tab-page.design.component.tsx +++ b/packages/ui-vue/components/tabs/src/designer/tab-page.design.component.tsx @@ -94,7 +94,7 @@ export default defineComponent({ const classObject = { 'farris-tab-page': true, 'drag-container': true, - 'farris-tab-page-active': isActivePage.value + 'f-tab-active': isActivePage.value } as Record; return classObject; }); @@ -104,6 +104,7 @@ export default defineComponent({ return (
    diff --git a/packages/ui-vue/components/tabs/src/designer/tab-toolbar-item.props.ts b/packages/ui-vue/components/tabs/src/designer/tab-toolbar-item.props.ts index 15cea407c184b492d3de328e4d899cb907e53aee..babb2088e774c5088273ff9ee03685c85b6a12cc 100644 --- a/packages/ui-vue/components/tabs/src/designer/tab-toolbar-item.props.ts +++ b/packages/ui-vue/components/tabs/src/designer/tab-toolbar-item.props.ts @@ -16,7 +16,7 @@ */ // 暂未使用 import { ExtractPropTypes } from 'vue'; -import { createPropsResolver } from '@farris/ui-vue/components/dynamic-resolver'; +import { createPropsResolver, getPropsResolverGenerator } from '@farris/ui-vue/components/dynamic-resolver'; import { schemaMapper } from '../schema/schema-mapper'; import { tabToolbarItemSchemaResolver } from '../schema/schema-resolver'; import tabToolbarItemSchema from '../schema/tab-toolbar-item.schema.json'; @@ -41,3 +41,10 @@ export const tabToolbarItemProps = { export type TabToolbarItemProps = ExtractPropTypes; export const tabToolbarItemResolver = createPropsResolver(tabToolbarItemProps, tabToolbarItemSchema, schemaMapper, tabToolbarItemSchemaResolver); + +export const tabToolbarItemPropsResolverGenerator = getPropsResolverGenerator( + tabToolbarItemProps, + tabToolbarItemSchema, + schemaMapper, + tabToolbarItemSchemaResolver +); \ No newline at end of file diff --git a/packages/ui-vue/components/tabs/src/designer/tab-toolbar.design.component.tsx b/packages/ui-vue/components/tabs/src/designer/tab-toolbar.design.component.tsx index b7fbeab86439ca3be3646abd655444da4e0b4a72..8d5a96314433493fda3a25f6657c9061e448f45c 100644 --- a/packages/ui-vue/components/tabs/src/designer/tab-toolbar.design.component.tsx +++ b/packages/ui-vue/components/tabs/src/designer/tab-toolbar.design.component.tsx @@ -1,6 +1,6 @@ import { Ref } from 'vue'; import { DesignerComponentInstance, DesignerItemContext, UseDesignerRules, FDesignerInnerItem } from '@farris/ui-vue/components/designer-canvas'; -import FResponseToolbarDesign from '@farris/ui-vue/components/response-toolbar'; +import FResponseToolbarDesign from '@farris/ui-vue/components/response-toolbar/designer'; import { UseDesignTabs } from '../composition/types'; export default function ( diff --git a/packages/ui-vue/components/tabs/src/designer/tabs.design.component.tsx b/packages/ui-vue/components/tabs/src/designer/tabs.design.component.tsx index 982c93e988ba518b136fd59835f538fc904a9b7e..9582dda2b52b8900f4896664d5483eb771265b39 100644 --- a/packages/ui-vue/components/tabs/src/designer/tabs.design.component.tsx +++ b/packages/ui-vue/components/tabs/src/designer/tabs.design.component.tsx @@ -150,7 +150,8 @@ export default defineComponent({ 'flex-column-reverse': props.position === 'bottom', 'flex-row': props.position === 'left', 'flex-row-reverse': props.position === 'right', - 'one-page': props.tabType === 'one-page' + 'one-page': props.tabType === 'one-page', + 'f-tabs-content-fill': shouldFillParentContaner.value, })); const { initHeaderDragula, dragulaInstance } = tabHeaderItemDragula(designItemContext, tabPages); diff --git a/packages/ui-vue/components/tabs/src/property-config/tabs.property-config.ts b/packages/ui-vue/components/tabs/src/property-config/tabs.property-config.ts index bc3b91f60b703c38af21bd6ec0909061f2f2d6f7..ee5909a9d713fde81e8023de12b6cc4b0a4a7880 100644 --- a/packages/ui-vue/components/tabs/src/property-config/tabs.property-config.ts +++ b/packages/ui-vue/components/tabs/src/property-config/tabs.property-config.ts @@ -65,7 +65,7 @@ export class TabsProperty extends BaseControlProperty { const activeIdEditor = this.getPropertyEditorParams(propertyData, ['Const', 'Variable'], 'activeId', { constType: 'enum', constEnums: tabPages - }, { newVariableType: 'String' }); + }, { newVariablePrefix: '', newVariableType: 'String' }); const self = this; return { description: '', @@ -117,7 +117,7 @@ export class TabsProperty extends BaseControlProperty { const self = this; const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); const properties = self.createBaseEventProperty(initialData); - + this.propertyConfig.categories['eventsEditor'] = { title: '事件', hideTitle: true, diff --git a/packages/ui-vue/components/tabs/src/schema/schema-resolver.ts b/packages/ui-vue/components/tabs/src/schema/schema-resolver.ts index 60ea8956b53ef2bd7ef7eb34673959e13ad0cbc5..9d7fce2ca1f3272ea0eb9ef8c8b34a9bd71b07da 100644 --- a/packages/ui-vue/components/tabs/src/schema/schema-resolver.ts +++ b/packages/ui-vue/components/tabs/src/schema/schema-resolver.ts @@ -1,6 +1,22 @@ import { DgControl, ComponentSchema, DesignerComponentInstance } from "@farris/ui-vue/components/designer-canvas"; import { DynamicResolver } from "@farris/ui-vue/components/dynamic-resolver"; +/** + * 若父容器是flex布局,那么需要将容器设置为display:block,否则容器宽度有问题 + */ +function adaptStructContainer(containerSchema: any, parentComponentInstance: DesignerComponentInstance) { + if (!parentComponentInstance?.schema) { + return; + } + const targetComponentClassName = parentComponentInstance.schema?.appearance?.class || ''; + + const parentElementRef: any = parentComponentInstance.elementRef; + const computedStyle = window.getComputedStyle(parentElementRef); + if (computedStyle && computedStyle.display === 'flex' && !targetComponentClassName.includes('f-page-child-fill')) { + containerSchema.appearance.class += ' d-block'; + } +} + /** * 从工具箱拖拽创建标签页,自动创建container-section-tabs三层结构 */ @@ -46,22 +62,33 @@ function wrapContainerSectionForTabs(resolver: DynamicResolver, context: Record< // 判断拖拽的目标容器 const targetComponentSchema = parentComponentInstance.schema; + const targetComponentClassName = targetComponentSchema.appearance?.class || ''; + switch (targetComponentSchema && targetComponentSchema.type) { case DgControl['splitter-pane'].type: { // 左列右卡的模板:修改容器样式 sectionSchema.appearance.class = 'f-section-tabs f-section-in-main'; tabsSchema.appearance.class = 'f-component-tabs'; + // 字典类模板:若页面启用填充,则将新容器也设置填充类样式 + if (targetComponentClassName.includes('f-page-child-fill')) { + containerSchema.appearance.class += ' f-struct-wrapper-child'; + sectionSchema.fill = true; + tabsSchema.fill = true; + } + break; } + case DgControl['content-container'].type: { + // 卡片类模板:若页面启用填充,则将新容器也设置填充类样式 + if (targetComponentSchema.isLikeCardContainer && targetComponentClassName.includes('f-struct-like-card-child-fill')) { + containerSchema.appearance.class += ' f-struct-wrapper-child'; + sectionSchema.fill = true; + tabsSchema.fill = true; + } + } } - - // 父容器是flex布局的场景,需要将容器设置为display:block,否则容器宽度有问题 - const parentElementRef: any = parentComponentInstance.elementRef; - const computedStyle = window.getComputedStyle(parentElementRef); - if (computedStyle && computedStyle.display === 'flex') { - containerSchema.appearance.class += ' d-block'; - } + adaptStructContainer(containerSchema, parentComponentInstance); return containerSchema; } diff --git a/packages/ui-vue/components/tabs/src/tabs.component.tsx b/packages/ui-vue/components/tabs/src/tabs.component.tsx index e7b5ea508638aca04af80d68b1be2b8930e85ee5..48f62bf2eb916016e4de0bdca4709f061768fa73 100644 --- a/packages/ui-vue/components/tabs/src/tabs.component.tsx +++ b/packages/ui-vue/components/tabs/src/tabs.component.tsx @@ -14,9 +14,7 @@ * limitations under the License. */ import { computed, defineComponent, provide, SetupContext, onMounted, shallowRef, nextTick, ref, watch } from 'vue'; -import { JSX } from 'vue/jsx-runtime'; import { useI18n } from 'vue-i18n'; -import FResponseToolbar from '@farris/ui-vue/components/response-toolbar'; import { TabsProps, tabsProps } from './tabs.props'; import { useTabs } from './composition/use-tabs'; import { TabPageContext, TabsContext } from './composition/types'; @@ -26,22 +24,27 @@ import getMoreButtonRender from './components/more-pages-button.component'; import { useNav } from './composition/use-nav'; import { useDropdown } from './composition/use-dropdown'; import { useOnePage } from './composition/use-one-page'; +import FResponseToolbar from '@farris/ui-vue/components/response-toolbar'; +import { JSX } from 'vue/jsx-runtime'; +import { LocaleService } from '@farris/ui-vue/components/locale'; +import { isMobilePhone } from '@farris/ui-vue/components/common'; export default defineComponent({ name: 'FTabs', props: tabsProps, emits: ['tabChange', 'tabRemove', 'update:activeId', 'Click'] as (string[] & ThisType) | undefined, setup(props: TabsProps, context: SetupContext) { - const { t } = useI18n(); const tabType = ref(props.tabType); const tabsElement = shallowRef(); + // 是否在小屏的移动设备上 + const isMobilePhoneState = isMobilePhone(); const customClass = ref(props.customClass); // 标题Ul元素 const tabNavigationElementRef = shallowRef(); const tabContentElementRef = shallowRef(); const tabHeaderContainer = ref(); const useTabsComposition = useTabs(props, context, tabNavigationElementRef); - const { activeId, changeTitleStyle, tabPages, addTab, updateTab, toolbarItems, checkActiveIdAndSelect, toggleShowTab, hasInHeadClass,toolbarVisible } = useTabsComposition; + const { activeId, changeTitleStyle, tabPages, addTab, updateTab, toolbarItems, checkActiveIdAndSelect, toggleShowTab, hasInHeadClass, toolbarVisible } = useTabsComposition; const useOnePageComposition = useOnePage(props, tabContentElementRef, useTabsComposition); @@ -88,8 +91,12 @@ export default defineComponent({ const shouldFillParentContaner = computed(() => { return props.fill; }); + + const clickToolbarItemHandler = (payload: any, itemId: string) => { + context.emit('Click', payload, itemId, activeId.value); + }; // 提供者tabs,供增加、修改tab标题用 - provide('tabs', { activeId, addTab, updateTab, tabPages, tabType, shouldFillParentContaner, toggleShowTab, toolbarVisible }); + provide('tabs', { activeId, addTab, updateTab, tabPages, tabType, shouldFillParentContaner, toggleShowTab, toolbarVisible, clickToolbarItemHandler}); let hasBoundObserver = false; @@ -115,7 +122,7 @@ export default defineComponent({ const tabsContainerClass = computed(() => { const classObject = { 'farris-tabs': true, - 'f-utils-fill-flex-column': shouldFillParentContaner.value, + 'f-tabs-content-fill': shouldFillParentContaner.value, 'flex-column': props.position === 'top', 'flex-column-reverse': props.position === 'bottom', 'flex-row': props.position === 'left', @@ -165,10 +172,6 @@ export default defineComponent({ changeTitleStyle(tabNavigationElementRef); }); }); - const tabsLocale={ - leftButton:t('tabs.leftButton'), - rightButton:t('tabs.rightButton') - }; onMounted(() => { if (props.visible) { if (tabPages.value.length) { @@ -186,7 +189,7 @@ export default defineComponent({ function renderPreviousButton() { return (