diff --git a/packages/designer/src/app-providers.ts b/packages/designer/src/app-providers.ts index ffd9aed57029ec6f8cd4481c1ec81a94cf2e8c5a..71db35c8b35eb76621211585f1c3091038ceb993 100644 --- a/packages/designer/src/app-providers.ts +++ b/packages/designer/src/app-providers.ts @@ -1,6 +1,7 @@ -import { FLoadingService, FTooltipDirective, FMessageBoxService, F_MODAL_SERVICE_TOKEN, FModalService, LookupSchemaRepositoryToken, FieldSelectorRepositoryToken, F_NOTIFY_SERVICE_TOKEN, FNotifyService, ControllerSchemaRepositorySymbol, FormSchemaRepositorySymbol } from "@farris/ui-vue/components"; import { App } from "vue"; +import { FLoadingService, FTooltipDirective, FMessageBoxService, F_MODAL_SERVICE_TOKEN, FModalService, LookupSchemaRepositoryToken, FieldSelectorRepositoryToken, F_NOTIFY_SERVICE_TOKEN, FNotifyService, ControllerSchemaRepositorySymbol, FormSchemaRepositorySymbol } from "@farris/ui-vue/components"; +import { LookupSchemaRepositoryToken as MobileLookupSchemaRepositoryToken, FieldSelectorRepositoryToken as MobileFieldSelectorRepositoryToken } from "@farris/mobile-ui-vue"; import { MetadataService } from "./components/composition/metadata.service"; import { MetadataPathToken, MetadataServiceToken } from "./components/types"; import { LookupFieldSelectorService, LookupSchemaService } from "./components/composition/schema-repository"; @@ -26,6 +27,8 @@ export default { app.provide(LookupSchemaRepositoryToken, new LookupSchemaService(metadataService)); app.provide(FieldSelectorRepositoryToken, new LookupFieldSelectorService(metadataService)); + app.provide(MobileLookupSchemaRepositoryToken, new LookupSchemaService(metadataService)); + app.provide(MobileFieldSelectorRepositoryToken, new LookupFieldSelectorService(metadataService)); app.provide(F_NOTIFY_SERVICE_TOKEN, new FNotifyService()); app.provide(ControllerSchemaRepositorySymbol, new ControllerSelectorSchemaService(metadataService,designerContext)); app.provide(FormSchemaRepositorySymbol, new FormSelectorSchemaService(metadataService)); diff --git a/packages/designer/src/components/components/form-designer/form-designer.component.tsx b/packages/designer/src/components/components/form-designer/form-designer.component.tsx index 339ec344900597e7b5e8a6072b330c2da492af7a..96f444f8839e37fe66a5bd09d654415122b92267 100644 --- a/packages/designer/src/components/components/form-designer/form-designer.component.tsx +++ b/packages/designer/src/components/components/form-designer/form-designer.component.tsx @@ -21,7 +21,7 @@ export default defineComponent({ const propertyPanelInstance = ref(); const schema = ref(props.schema); const componentSchema = schema.value.module ? ref(schema.value.module.components[0]) : ref(schema.value); - const componentId = ref(componentSchema['id'] || 'root-component'); + const componentId = ref(componentSchema.value['id'] || 'root-component'); const dragulaCompostion = ref(); const fillTabs = ref(true); const controlTreeRef = ref(); @@ -73,6 +73,9 @@ export default defineComponent({ if (changeObject.needRefreshEntityTree && entityTreeRef.value && entityTreeRef.value.refreshEntityTree) { entityTreeRef.value.refreshEntityTree(); } + if (changeObject.needChangeCanvas && canvasRef.value.changeCanvas) { + canvasRef.value.changeCanvas(); + } const afterPropeControlPropertyChanged = afterPropeControlPropertyChangedService(useFormSchema, designViewModelUtils, schemaUtil); afterPropeControlPropertyChanged.afterPropertyChanged(event); diff --git a/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts b/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts index e3da548d7aefa207d1e761cc10e48fd9c63d6063..037667831498e295c368072fc155fcf4be10c9ef 100644 --- a/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts +++ b/packages/designer/src/components/composition/designer-context/use-mobile-designer-context.ts @@ -3,8 +3,9 @@ import ToolboxItems from '../../types/toolbox/mobile-toolbox.json'; import SupportedControllers from '../../composition/command/supported-controllers/mobile-supported-controller.json'; import { - Component, PageContainer, PageHeaderContainer, PageBodyContainer, PageFooterContainer, Picker, NumberInput, Textarea, DatePicker, DateTimePicker, - ContentContainer, FloatContainer, Navbar, ListView, Form, FormItem, InputGroup, Button, registerDesignerComponents, Switch, CheckboxGroup, RadioGroup, + Component, PageContainer, PageHeaderContainer, PageBodyContainer, PageFooterContainer, Picker, NumberInput, Textarea, DatePicker, DateTimePicker, Lookup, + ContentContainer, Card, FloatContainer, Navbar, ListView, Form, FormItem, InputGroup, Button, registerDesignerComponents, Switch, CheckboxGroup, RadioGroup, + ButtonGroup, } from '@farris/mobile-ui-vue'; import { useMobileControlCreator } from "../control-creator/use-mobile-control-creator"; import { FormComponent, UseFormSchema } from "../../../components/types"; @@ -21,10 +22,10 @@ export function useMobileDesignerContext(): UseDesignerContext { /** 要注册的UI组件 */ const componentsToRegister: any[] = [ Component, PageContainer, PageHeaderContainer, PageBodyContainer, PageFooterContainer, - ContentContainer, FloatContainer,Textarea,DatePicker,DateTimePicker, + ContentContainer, Card, FloatContainer,Textarea,DatePicker,DateTimePicker,Lookup, Navbar, ListView,Picker,NumberInput,Switch,CheckboxGroup,RadioGroup, Form, FormItem, InputGroup, - Button + Button, ButtonGroup, ]; registerDesignerComponents(componentsToRegister); diff --git a/packages/designer/src/components/types/toolbox/mobile-toolbox.json b/packages/designer/src/components/types/toolbox/mobile-toolbox.json index e160cd632e857b784a0e9897dd3412aa280a72b3..fdc942bfbc104417946f069a422db9c442decea6 100644 --- a/packages/designer/src/components/types/toolbox/mobile-toolbox.json +++ b/packages/designer/src/components/types/toolbox/mobile-toolbox.json @@ -105,13 +105,6 @@ "category": "container", "icon": "content-container" }, - { - "id": "PageBodyContainer", - "type": "page-body-container", - "name": "页面主体容器", - "category": "container", - "icon": "content-container" - }, { "id": "PageFooterContainer", "type": "page-footer-container", diff --git a/packages/designer/tsconfig.json b/packages/designer/tsconfig.json index 0b7f4ecc1032fe1419cda7df75df92e306ca5f87..54db16283f1f83e38e9f53039d4a891a46e4a216 100644 --- a/packages/designer/tsconfig.json +++ b/packages/designer/tsconfig.json @@ -19,7 +19,9 @@ "forceConsistentCasingInFileNames": true, "types": ["vitest/globals", "@types/jest"], "baseUrl": "./", - "paths": {} + "paths": { + "@farris/mobile-ui-vue": ["../mobile-ui-vue/components"] + } }, "include": ["components/**/*", "src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "designer/**/*"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/packages/designer/vite.config.dev.ts b/packages/designer/vite.config.dev.ts index 37b7971cdf1d8b0b5c4ffdce89b652f79e10e673..f0595c897cf390bbefae995c9ae404c7f51583af 100644 --- a/packages/designer/vite.config.dev.ts +++ b/packages/designer/vite.config.dev.ts @@ -20,7 +20,7 @@ export default defineConfig({ '@': resolve(__dirname, '../'), '@farris/ui-vue': resolve(__dirname, '../ui-vue'), '@farris/code-editor-vue': resolve(__dirname, '../code-editor'), - '@farris/mobile-ui-vue': resolve(__dirname, '../mobile-ui-vue/components') + '@farris/mobile-ui-vue': resolve(__dirname, '../mobile-ui-vue/components'), } } }); diff --git a/packages/mobile-command-services/lib/ui-services/dialog.service.ts b/packages/mobile-command-services/lib/ui-services/dialog.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..17d65e5e00d41c74c821521ff39b7bac07c27955 --- /dev/null +++ b/packages/mobile-command-services/lib/ui-services/dialog.service.ts @@ -0,0 +1,43 @@ +import { Dialog } from '@farris/mobile-ui-vue'; + +/** + * 轻提示服务 + */ +class DialogService { + + /** + * 构造函数 + */ + constructor() { + } + + /** + * 确认弹窗 + */ + public confirm(options: any): void { + Dialog.confirm(options); + } + + /** + * 输入弹窗 + */ + public prompt(options: any): void { + Dialog.prompt(options); + } + + /** + * 提示弹窗 + */ + public alert(options: any): void { + Dialog.alert(options); + } + + /** + * 清除 + */ + public clear(): void { + Dialog.clear(); + } + +} +export { DialogService }; diff --git a/packages/mobile-command-services/lib/ui-services/index.ts b/packages/mobile-command-services/lib/ui-services/index.ts index cf513ecdf9e71f53151b089385430a933747bb98..60dd4735a27a7f545df2314ebdac51c62c0a3607 100644 --- a/packages/mobile-command-services/lib/ui-services/index.ts +++ b/packages/mobile-command-services/lib/ui-services/index.ts @@ -1 +1,4 @@ -export * from './toast.service'; \ No newline at end of file +export * from './toast.service'; +export * from './dialog.service'; +export * from './loading.service'; +export * from './notify.service'; \ No newline at end of file diff --git a/packages/mobile-command-services/lib/ui-services/loading.service.ts b/packages/mobile-command-services/lib/ui-services/loading.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..0d70378fcd7940dad5f2cd7744fe8fce473ec497 --- /dev/null +++ b/packages/mobile-command-services/lib/ui-services/loading.service.ts @@ -0,0 +1,28 @@ +import { Loading } from '@farris/mobile-ui-vue'; + +/** + * 加载中服务 + */ +class LoadingService { + + /** + * 构造函数 + */ + constructor() { + } + + /** + * 显示加载中 + */ + public show(options: any): void { + Loading.show(options); + } + + /** + * 隐藏加载中 + */ + public hidden(): void { + Loading.hidden(); + } +} +export { LoadingService }; diff --git a/packages/mobile-command-services/lib/ui-services/notify.service.ts b/packages/mobile-command-services/lib/ui-services/notify.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..85a7be05c1fff07c369f3412b575246ad917090d --- /dev/null +++ b/packages/mobile-command-services/lib/ui-services/notify.service.ts @@ -0,0 +1,42 @@ +import { Notify } from '@farris/mobile-ui-vue'; + +/** + * 通知服务 + */ +class NotifyService { + + /** + * 构造函数 + */ + constructor() { + } + + /** + * 消息通知 + */ + public info(message: string): void { + Notify.info(message); + } + + /** + * 成功通知 + */ + public success(message: string): void { + Notify.success(message); + } + + /** + * 警告通知 + */ + public warning(message: string): void { + Notify.warning(message); + } + + /** + * 失败通知 + */ + public error(message: string): void { + Notify.error(message); + } +} +export { NotifyService }; diff --git a/packages/mobile-command-services/package.json b/packages/mobile-command-services/package.json index d2ac3449ed33023b5213da9d41abaa383316c5d4..87af8f80e0e72a264cc1986e510c7dd97599f777 100644 --- a/packages/mobile-command-services/package.json +++ b/packages/mobile-command-services/package.json @@ -26,7 +26,8 @@ "vue": "^3.2.37", "vue-router": "^4.3.0", "@farris/bef-vue": "workspace:^", - "@farris/devkit-vue": "workspace:^" + "@farris/devkit-vue": "workspace:^", + "@farris/mobile-ui-vue": "workspace:*" }, "devDependencies": { "@babel/parser": "^7.19.0", @@ -69,7 +70,6 @@ "vitest": "^0.29.2", "vue-tsc": "^1.2.0", "tslib": "^2.7.0", - "@farris/mobile-ui-vue": "latest", "axios": "^1.7.2", "rollup-plugin-typescript2": "^0.36.0", "rollup-plugin-visualizer": "^5.12.0", diff --git a/packages/mobile-render/farris.config.mjs b/packages/mobile-render/farris.config.mjs new file mode 100644 index 0000000000000000000000000000000000000000..6a1d3210954b6b29b78d3065df3c270d33015092 --- /dev/null +++ b/packages/mobile-render/farris.config.mjs @@ -0,0 +1,40 @@ +import { fileURLToPath, URL } from 'node:url'; + +const externals = []; + +export default { + format: "system", + minify: false, + target: 'es2015', + externalDependencies: true, + externals: { + include: externals, + filter: (externals) => { + return (id) => { + return externals.find((item) => item && id.indexOf(item) === 0); + }; + } + }, + server: { + proxy: { + "/api": { + target: "http://127.0.0.1:5200", + changeOrigin: true, + secure: false + }, + "/apps": { + target: "http://127.0.0.1:5200", + changeOrigin: true, + secure: false + } + } + }, + alias: [ + + { find: "vue", replacement: 'vue/dist/vue.esm-bundler.js' }, + { find: "@farris/devkit-vue", replacement: fileURLToPath(new URL('../devkit/lib', import.meta.url)) }, + { find: "@farris/bef-vue", replacement: fileURLToPath(new URL('../bef/lib', import.meta.url)) }, + { find: "@farris/mobile-command-services-vue", replacement: fileURLToPath(new URL('../mobile-command-services/lib', import.meta.url)) }, + { find: '@farris/mobile-ui-vue', replacement: fileURLToPath(new URL('../mobile-ui-vue/components', import.meta.url)) } + ] +}; diff --git a/packages/mobile-render/index-publish.html b/packages/mobile-render/index-publish.html index 3f5ff874feadddb936ca5f5aa931739fc8c3b2c4..c0f951989d79d34fb893875660d97ac798281280 100644 --- a/packages/mobile-render/index-publish.html +++ b/packages/mobile-render/index-publish.html @@ -4,12 +4,19 @@ Mobile Render - - - - + +
+ + + diff --git a/packages/mobile-render/package.json b/packages/mobile-render/package.json index 2f167a8cdc7ee75ab08561e7722bc7d558a66c90..0b9186b866eed90bd38a3c510c47695e4c227d34 100644 --- a/packages/mobile-render/package.json +++ b/packages/mobile-render/package.json @@ -14,9 +14,9 @@ "url": "git@gitee.com:ubml/farris-vue.git" }, "scripts": { - "dev": "vite", - "build": "vue-tsc --noEmit && vite build", - "preview": "vite preview", + "dev": "farris-cli dev", + "build": "farris-cli build", + "preview": "farris-cli preview", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "format": "prettier --write src/" }, @@ -30,45 +30,26 @@ "vue-router": "^4.3.0" }, "devDependencies": { + "@farris/cli": "workspace:*", "@rushstack/eslint-patch": "^1.8.0", "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^13.0.0", "@vue/tsconfig": "^0.5.1", - "eslint": "^8.57.0", "eslint-plugin-vue": "^9.23.0", "prettier": "^3.2.5", - "vite-plugin-dts": "^2.1.0", - "@babel/parser": "^7.19.0", - "@babel/preset-env": "^7.19.0", - "@babel/preset-typescript": "^7.18.0", - "@babel/traverse": "^7.19.0", "@commitlint/cli": "^17.1.0", "@commitlint/config-conventional": "^17.1.0", "@testing-library/vue": "^7.0.0", "@types/jest": "^26.0.24", "@typescript-eslint/eslint-plugin": "^7.15.0", "@typescript-eslint/parser": "^7.15.0", - "@vitejs/plugin-vue": "^4.0.0", - "@vitejs/plugin-vue-jsx": "^3.0.0", - "@vue/babel-plugin-jsx": "^1.1.1", "@vue/compiler-sfc": "^3.2.0", "@vue/test-utils": "^2.0.0", - "@vuedx/typecheck": "^0.7.5", - "@vuedx/typescript-plugin-vue": "^0.7.5", "babel-jest": "^29.0.3", - "chalk": "^5.0.0", - "commander": "^9.4.0", "conventional-changelog-cli": "^2.2.2", - "happy-dom": "^8.9.0", - "inquirer": "^9.1.1", "jest": "^29.0.0", - "ora": "^6.1.2", - "patch-vue-directive-ssr": "^0.0.1", "shelljs": "^0.8.4", "typescript": "^4.6.4", - "vite": "^4.1.4", - "vite-plugin-banner": "^0.8.0", - "vite-svg-loader": "^4.0.0", "vitest": "^0.29.2", "vue-tsc": "^1.2.0" } diff --git a/packages/mobile-render/public/assets/farris-mobile-ui-vue.css b/packages/mobile-render/public/assets/farris-mobile-ui-vue.css index f0a165ea7eb1bc855fec2b86fa4a764177ca9dd6..f5b06f98f6e3f348953f91f829a0b730d22a88a0 100644 --- a/packages/mobile-render/public/assets/farris-mobile-ui-vue.css +++ b/packages/mobile-render/public/assets/farris-mobile-ui-vue.css @@ -1 +1 @@ -@charset "UTF-8";:root{--fm-black: #000;--fm-white: #fff;--fm-gray-1: #f2f3f5;--fm-gray-2: #eee;--fm-gray-3: #ddd;--fm-gray-4: #ccc;--fm-gray-5: #999;--fm-gray-6: #666;--fm-gray-7: #333;--fm-red: #F24645;--fm-red-light: #f9e8e8;--fm-blue: #3A90FF;--fm-blue-2: #65a7ff;--fm-blue-light: #ecf2fe;--fm-green: #5CC171;--fm-green-2: #5AC1C3;--fm-green-light: #e9f5ed;--fm-orange: #FF9800;--fm-orange-2: #FA6400;--fm-orange-3: #FFB400;--fm-orange-light: #faf0e1;--fm-primary-color: var(--fm-blue);--fm-success-color: var(--fm-green);--fm-danger-color: var(--fm-red);--fm-warning-color: var(--fm-orange);--fm-submit-color: var(--fm-green-2);--fm-primary-color-light: var(--fm-blue-light);--fm-success-color-light: var(--fm-green-light);--fm-danger-color-light: var(--fm-red-light);--fm-warning-color-light: var(--fm-orange-light);--fm-text-color: var(--fm-gray-7);--fm-text-color-light: var(--fm-gray-5);--fm-active-color: var(--fm-gray-1);--fm-disabled-color: var(--fm-gray-4);--fm-readonly-color: var(--fm-gray-6);--fm-active-opacity: .7;--fm-readonly-opacity: .6;--fm-disabled-opacity: .5;--fm-background: var(--fm-gray-1);--fm-background-white: var(--fm-white);--fm-box-shadow-color: var(--fm-gray-1);--fm-padding-base: 4px;--fm-padding-xs: 8px;--fm-padding-sm: 12px;--fm-padding-md: 16px;--fm-padding-lg: 24px;--fm-padding-xl: 32px;--fm-padding-horizontal-xs: 0 var(--fm-padding-xs);--fm-padding-horizontal-sm: 0 var(--fm-padding-sm);--fm-padding-horizontal-md: 0 var(--fm-padding-md);--fm-padding-horizontal-lg: 0 var(--fm-padding-lg);--fm-margin-base: 4px;--fm-margin-xs: 8px;--fm-margin-sm: 12px;--fm-margin-md: 16px;--fm-margin-lg: 24px;--fm-margin-xl: 32px;--fm-margin-horizontal-xs: 0 var(--fm-margin-xs);--fm-margin-horizontal-sm: 0 var(--fm-margin-sm);--fm-margin-horizontal-md: 0 var(--fm-margin-md);--fm-margin-horizontal-lg: 0 var(--fm-margin-lg);--fm-font-bold-light: 500;--fm-font-bold: 600;--fm-font-size: 16px;--fm-line-height: 1.2;--fm-font-size-xs: 10px;--fm-font-size-sm: 12px;--fm-font-size-md: 14px;--fm-font-size-lg: 16px;--fm-gradient-blue: linear-gradient(-45deg,var(--fm-blue-2) 0%, var(--fm-blue) 100%);--fm-gradient-orange: linear-gradient(-45deg,var(--fm-orange) 0%, var(--fm-orange-2) 100%);--fm-base-font: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Segoe UI, Arial, Roboto, "PingFang SC", "miui", "Hiragino Sans GB", "Microsoft Yahei", sans-serif;--fm-price-font: avenir-heavy, "PingFang SC", helvetica neue, arial, sans-serif;--fm-duration-base: .3s;--fm-duration-fast: .2s;--fm-ease-out: ease-out;--fm-ease-in: ease-in;--fm-noborder: none;--fm-radius-sm: 2px;--fm-radius-md: 4px;--fm-radius-lg: 8px;--fm-radius-max: 999px;--fm-zindex-1: 9;--fm-zindex-2: 10;--fm-zindex-3: 99;--fm-zindex-4: 100;--fm-zindex-5: 999;--fm-zindex-6: 1000}*,*:before,*:after{box-sizing:border-box}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}ul[role=list],ol[role=list]{list-style:none}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5;font-family:-apple-system,Noto Sans,Helvetica Neue,Helvetica,Nimbus Sans L,Arial,Liberation Sans,PingFang SC,Hiragino Sans GB,Noto Sans CJK SC,Source Han Sans SC,Source Han Sans CN,Microsoft YaHei,Wenquanyi Micro Hei,WenQuanYi Zen Hei,ST Heiti,SimHei,WenQuanYi Zen Hei Sharp,sans-serif}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@font-face{font-family:farrisMobile;font-style:normal;font-weight:400;src:url(data:font/ttf;base64,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AADmFAAAACUAAOYVAADmFQAAACYAAOYWAADmFgAAACcAAOYXAADmFwAAACgAAOYYAADmGAAAACkAAOYZAADmGQAAAHYAAOYaAADmGgAAAHUAAOYbAADmGwAAAHQAAOYcAADmHAAAAHMAAOYdAADmHQAAACoAAOYeAADmHgAAACsAAOYfAADmHwAAAHAAAOYgAADmIAAAAHEAAOYhAADmIQAAAHIAAOYiAADmIgAAAG8AAOYjAADmIwAAACwAAOYkAADmJAAAAGUAAOYlAADmJQAAAGYAAOYmAADmJgAAAGcAAOYnAADmJwAAAGgAAOYoAADmKAAAAGkAAOYpAADmKQAAAGoAAOYqAADmKgAAAGsAAOYrAADmKwAAAGwAAOYsAADmLAAAAG0AAOYtAADmLQAAAG4AAOYuAADmLgAAAGMAAOYvAADmLwAAAGQAAOYwAADmMAAAAGIAAOYyAADmMgAAADQAAOYzAADmMwAAAC0AAOY0AADmNAAAAC4AAOY2AADmNgAAAC8AAOY3AADmNwAAADAAAOY5AADmOQAAADEAAOY6AADmOgAAADIAAOZVAADmVQAAADUAAOZWAADmVgAAADoAAOZXAADmVwAAADcAAOZYAADmWAAAADgAAOZZAADmWQAAAD0AAOZaAADmWgAAAF4AAOZbAADmWwAAAF8AAOZcAADmXAAAAGAAAOZdAADmXQAAAGEAAOZeAADmXgAAADYAAOZfAADmXwAAADsAAOZgAADmYAAAADwAAOZhAADmYQAAADkAAOZiAADmYgAAAD4AAOZjAADmYwAAAD8AAOZkAADmZAAAAEAAAOZlAADmZQAAAEEAAOZmAADmZgAAAEIAAOZnAADmZwAAAEMAAOZoAADmaAAAAEQAAOZpAADmaQAAAEUAAOZqAADmagAAAEYAAOZrAADmawAAAEcAAOZsAADmbAAAAEgAAOZtAADmbQAAAEkAAOZuAADmbgAAAEoAAOZvAADmbwAAAEsAAOZwAADmcAAAAEwAAOZxAADmcQAAAE0AAOZyAADmcgAAAE4AAOZzAADmcwAAAE8AAOZ0AADmdAAAAFAAAOZ1AADmdQAAAFEAAOZ2AADmdgAAAFIAAOZ3AADmdwAAAFMAAOZ4AADmeAAAAFQAAOZ5AADmeQAAAFUAAOZ6AADmegAAAFYAAOZ7AADmewAAAFcAAOZ8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AV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+AAlzLW1vcmUtbzEIcy1kZWxldGUIcy1iYWNrLW8Gcy1zY2FuEHMtb3JnYW5pemF0aW9uLW8Jcy1ncm91cC1vCXMtZmlsdGVyMQlzLWNsZWFyLW8Ocy1pbGx1c3RyYXRlLW8Icy1tb21lbnQIcy1zaG9wLW8Kcy1tb21lbnQtbwZzLXNob3AKcy1yZXZva2UtbwpzLXN1Ym1pdC1vB3MtYXBwLW8Gcy1ib29rBXMtYXBwBHMtbXkJcy1jb21tZW50C3MtY29tbWVudC1vCHMtYm9vay1vBnMtbXktbwxzLWxvY2F0aW9uLW8Kcy1sb2NhdGlvbgZzLW5ld3MIcy1uZXdzLW8Jcy1TZW5kaW5nCnMtSW1wb3J0LW8Icy1FZGl0LW8Icy1RUmNvZGULcy1CcmllZmNhc2URcy1Ob3RpZmljYXRpb25vZmYIcy1QZW5jaWwIcy1TdGFyLW8Jcy1CYXJDb2RlD3MtU2FsZU1lc3NhZ2UtbwxzLUxpc3R2aWV3LW8Lcy1QaWN0dXJlLW8Ocy1TYXZlRmlsbGV0LW8Ocy1TaG9wcGluZ2NhcnQJcy1QZW5kaW5nDnMtRXhwYW5kdmlldy1vCHMtTG9jay1vCXMtU2VydmljZQpzLVdhbGxldC1vEHMtU2hvcHBpbmdjYXJ0LW8Lcy1TZXR0aW5nLW8Ncy1VbmZvbGRSZWMtbwlzLVByb2ZpbGUGTGluay1vDVVzZXJTZXR0aW5nLW8Jcy1hZ3JlZS1vDHMtYXJyb3ctZG93bgdzLWFkZC1vB3MtYW5uZXgMcy1hcnJvdy1sZWZ0CnMtYXJyb3ctdXAScy1jaGVja2JveC1jaGVja2VkCnMtY2hlY2tib3gHcy1hcnJvdwpzLWV4Y2hhbmdlB3MtY2xlYXIJcy1jaGVja2VkB3MtZXllLW8Lcy1jbG9zZS1leWUEcy1hdAxzLWZlZWRiYWNrLW8Pcy1mb2xkLWNpcmNsZS1vB3MtY3Jvc3MHcy1taW51cwZzLWZpbGUPcy1mb2xkLXRyaWFuZ2xlCHMtZmlsdGVyCXMtZm9yd29yZAZzLXBsdXMJcy1maWxlZGVsCHMtZm9sZC1vCXMtaW1hZ2UtbwhzLW1vcmUtbw5zLXJlZGlvLWNpcmNsZQpzLXJlamVjdC1vCXMtc3VjY2VzcwlzLXJlZnJlc2gGcy1zdGFyDXMtcHJvY2Vzc2Zsb3cRcy11bmZvbGQtY2lyY2xlLW8Icy1yZXdpbmQKcy11bmZvbGQtbwtzLXdhcm5pbmctbxFzLXVuZm9sZC10cmlhbmdsZQlzLXByaW50LW8Kcy1zZWFyY2gtbwhBaXJwbGFuZQVUcmFpbgNCdXMEVGF4aQxvdmVydGltZW1lYWwTZm0taS10cmFmZmljZGV0YWlscw9mbS1pLXRyYWZmaWNzdWITZm0taS1hY2NvbW1vZGF0aW9uMQ1mbS1pLWJ1c2luZXNzC2ZtLWktYnVkZ2V0CWZtLWktZ2lmdBFmbS1pLWNhbmNlbGxhdGlvbgxmbS1pLWludm9pY2UJZm0taS1saXN0CmZtLWktbWVhbHMIZm0taS1wYXkMZm0taS1zdWJzaWR5B3Nob3V6aGUTZm0taS1jb3N0YWxsb2NhdGlvbgxmbS1pLXBheW1lbnQOZm0taS1JdGluZXJhcnkOZm0taS1tb3JleGUwMmQPZm0taS1pbWFnZXhlMDJlEGZtLWktcmVqZWN0eGUwMmYPZm0taS1hZ3JlZXhlMDMxDWZtLWktY2FyZGZvbGQPZm0taS1jYXJkdW5mb2xkEWZtLWktdHJhZmZpY290aGVyD2ZtLWktYm90dG9tc2F2ZRBmbS1pLXRyYWZmaWNib2F0D2ZtLWktdHJhZmZpY2NhchFmbS1pLXRyYWZmaWNwbGFuZRFmbS1pLXRyYWZmaWN0cmFpbg9mbS1pLXRyYWZmaWNidXMMZm0taS1hZGRncmF5CmZtLWktY2xvc2UIZm0taS1hZGQJZm0taS1tb3JlCmZtLWktY2hlY2sPZm0taS1iYWNrY2lyY2xlCmZtLWktYWdyZWUMZm0taS1sb2FkaW5nDGZtLWktZmlsZWRlbAlmbS1pLWZpbGUQZm0taS1wcm9jZXNzZmxvdwxmbS1pLXJlZHN0YXIQZm0taS1yaWdodGNpcmNsZQ9mbS1pLXRpbWVjaXJjbGUOZm0taS1zY3JlZW5pbmcMZm0taS11bmNoZWNrDmZtLWktb3BlcmF0aW9uE2ZtLWktYXBwcm92YWxhbW91bnQLZm0taS1hZ3JlZWQOZm0taS1pbnByb2Nlc3MNZm0taS1mZWVkYmFjawpmbS1pLXByaW50DmZtLWktaW1wb3J0YW50GmZtLWktYW1vdW50b2ZyZWltYnVyc2VtZW50FGZtLWktc2FmZXR5YXNzaXN0YW50C2ZtLWktcmVtaW5kDWZtLWktZXllb3BwZW4NZm0taS1leWVjbG9zZRBmbS1pLVBob3RvZ3JhcGh5D2ZtLWktcGhvdG9ncmFwaA9mbS1pLWFkZHBpY3R1cmUTZm0taS10b3Bfb3RoZXItZmFjZQ5mbS1pLXRvcF9vdGhlcgtmbS1pLWRlbGV0ZQ1mbS1pLW5leHRwYWdlDmZtLWktYWRkYnV0dG9uFmZtLWktd2FpdGZvcnByb2Nlc3NpbmcTZm0taS13YXJuaW5nbWVzc2FnZRJmbS1pLWZhaWx1cmVwcm9tcHQUZm0taS1zdWNjZXNzZnVsaGludHMKZm0taS1vdGhlcgxmbS1pLXJldHJhY3QOZm0taS1kcm9wLWRvd24LZm0taS1jYW5jZWwLZm0taS1zZWFyY2gTZm0taS1tdWx0aXBsZWNob2ljZRFmbS1pLXNpbmdsZWNob2ljZQ9mbS1pLW1pY3JvcGhvbmUXZm0taS1hcnJvdy1jaGV2cm9uLWxlZnQPZm0taS1oZWxwY2VudGVyDmZtLWktc3dlZXBjb2RlDmZtLWktZ3JvdXBjaGF0DmZtLWktYWRkZnJpZW5kC2ZtLWktdG9wX215EGZtLWktdG9wX215LWZhY2UPZm0taS10b3BfZnJpZW5kFGZtLWktdG9wX2ZyaWVuZC1mYWNlFWZtLWktdG9wX3B1YmxpY3ByYWlzZRpmbS1pLXRvcF9wdWJsaWNwcmFpc2UtZmFjZQ1mbS1pLXRvcF9ob21lEmZtLWktdG9wX2hvbWUtZmFjZQAAAAA=) format("truetype")}@keyframes fm-fade-in{0%{opacity:0}to{opacity:1}}@keyframes fm-fade-out{0%{opacity:1}to{opacity:0}}.fm-fade-enter-active{animation:.3s fm-fade-in both ease-out}.fm-fade-leave-active{animation:.3s fm-fade-out both ease-in}.fm-slide-up-enter-from,.fm-slide-up-leave-to{transform:translate3d(0,100%,0)}.fm-slide-down-enter-from,.fm-slide-down-leave-to{transform:translate3d(0,-100%,0)}.fm-slide-left-enter-from,.fm-slide-left-leave-to{transform:translate3d(-100%,0,0)}.fm-slide-right-enter-from,.fm-slide-right-leave-to{transform:translate3d(100%,0,0)}.fm-slide-up-enter-active,.fm-slide-down-enter-active,.fm-slide-left-enter-active,.fm-slide-right-enter-active{transition-timing-function:ease-out}.fm-slide-up-leave-active,.fm-slide-down-leave-active,.fm-slide-left-leave-active,.fm-slide-right-leave-active{transition-timing-function:ease-in}.fm-slide-in-enter-active,.fm-slide-in-leave-active,.fm-slide-out-enter-active,.fm-slide-out-leave-active{will-change:transform;transition:all .3s;height:100%;width:100%;top:0;position:absolute;backface-visibility:hidden;perspective:1000}.fm-slide-in-leave-to,.fm-slide-out-enter-from{transform:translate(-100%);opacity:0}.fm-slide-in-enter-from,.fm-slide-out-leave-to{transform:translate(100%);opacity:0}@keyframes fm-drop-down-in{0%{height:0;opacity:0}to{opacity:1}}@keyframes fm-drop-down-out{0%{opacity:1}to{height:0;opacity:0}}.fm-drop-down-enter-active{animation:.3s fm-drop-down-in both ease-out}.fm-drop-down-leave-active{animation:.3s fm-drop-down-out both ease-in}@keyframes fm-rotate{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}:root{--fm-button-height: 42px;--fm-button-padding: 0 14px;--fm-button-radius: 2px;--fm-button-border-width: 1px;--fm-button-color: var(--fm-white);--fm-button-line-height: var(--fm-line-height);--fm-button-font-size: var(--fm-font-size);--fm-button-plain-background: var(--fm-white);--fm-button-lg-height: 46px;--fm-button-lg-font-size: 18px;--fm-button-md-height: 38px;--fm-button-md-font-size: 14px;--fm-button-sm-height: 34px;--fm-button-sm-padding: 0 8px;--fm-button-sm-font-size: 12px;--fm-button-xs-height: 28px;--fm-button-xs-font-size: 10px;--fm-button-secondary-color: var(--fm-blue-light)}.fm-button{position:relative;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;text-align:center;margin:0;border:none;transition:opacity .2s;color:var(--fm-button-color);height:var(--fm-button-height);padding:var(--fm-button-padding);font-size:var(--fm-button-font-size);line-height:var(--fm-button-line-height);border-radius:var(--fm-button-radius)}.fm-button--primary,.fm-button--info{background:var(--fm-primary-color)}.fm-button--secondary{color:var(--fm-primary-color);background:var(--fm-button-secondary-color)}.fm-button--danger{background-color:var(--fm-danger-color)}.fm-button--warning{background-color:var(--fm-warning-color)}.fm-button--success{background-color:var(--fm-success-color)}.fm-button--plain{box-shadow:none;background:var(--fm-button-plain-background)}.fm-button--plain.fm-button--primary,.fm-button--plain.fm-button--info{color:var(--fm-primary-color);border:var(--fm-button-border-width) solid var(--fm-primary-color)}.fm-button--plain.fm-button--success{color:var(--fm-success-color);border:var(--fm-button-border-width) solid var(--fm-success-color)}.fm-button--plain.fm-button--danger{color:var(--fm-danger-color);border:var(--fm-button-border-width) solid var(--fm-danger-color)}.fm-button--plain.fm-button--warning{color:var(--fm-button-warning-color);border:var(--fm-button-border-width) solid var(--fm-button-warning-color)}.fm-button--noborder{border:none!important}.fm-button--large{height:var(--fm-button-lg-height);font-size:var(--fm-button-lg-font-size)}.fm-button--normal{height:var(--fm-button-md-height);font-size:var(--fm-button-md-font-size)}.fm-button--small{height:var(--fm-button-sm-height);padding:var(--fm-button-sm-padding);font-size:var(--fm-button-sm-font-size)}.fm-button--mini{height:var(--fm-button-xs-height);padding:var(--fm-button-sm-padding);font-size:var(--fm-button-xs-font-size)}.fm-button--block{display:flex;width:100%}.fm-button--disabled{cursor:not-allowed;opacity:var(--fm-disabled-opacity)}.fm-button--loading:before,.fm-button--disabled:before{display:none}.fm-button--round{border-radius:var(--fm-radius-max)}.fm-button--square{border-radius:0}.fm-button__loading{display:inline-block;width:20px;max-width:100%;height:20px;max-height:100%;vertical-align:middle;animation:fm-rotate 2s linear infinite}.fm-button__loading circle{animation:fm-circular 1.5s ease-in-out infinite;stroke:currentColor;stroke-width:4;stroke-linecap:round}.fm-button__loading-icon-circular{color:#fff}.fm-button__loading-text{margin-left:6px}.fm-button__icon:not(:last-child){margin-right:6px}.fm-button:before{position:absolute;top:50%;left:50%;width:100%;height:100%;background-color:#000;border:inherit;border-color:#000;border-radius:inherit;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);opacity:0;content:" "}.fm-button:active:before{opacity:.1}.fm-button-edit__tags{min-height:24px;flex:1;display:flex;overflow:auto}.fm-button-edit__tag:not(:last-child){margin-right:6px}:root{--fm-button-group-gap: 16px}.fm-button-group{display:inline-flex;flex-wrap:nowrap;max-width:100%}.fm-button-group+.fm-button-group{margin-left:var(--fm-button-group-gap)}.fm-button-group__item{margin:0;min-width:0;max-width:100%;white-space:nowrap;flex:0 1 auto;user-select:none}.fm-button-group__item--bare-vertical{display:inline-flex;flex-direction:column;align-items:center;justify-content:space-between;color:#666}.fm-button-group__item--bare-vertical .fm-button-group__item-icon{font-size:20px}.fm-button-group__item--bare-vertical .fm-button-group__item-text{font-size:10px;line-height:16px}.fm-button-group__item--bare-vertical.fm-button-group__item--disabled{cursor:not-allowed;opacity:.5}.fm-button-group--block{display:flex}.fm-button-group--vertical{flex-direction:column}.fm-button-group--block .fm-button-group__item{width:auto;flex:10000 1 0%}.fm-button-group--horizontal.fm-button-group--mode-default .fm-button-group__item+.fm-button-group__item{margin-left:var(--fm-button-group-gap)}.fm-button-group--vertical.fm-button-group--mode-default .fm-button-group__item+.fm-button-group__item{margin-top:var(--fm-button-group-gap)}.fm-button-group--horizontal:not(.fm-button-group--mode-default) .fm-button-group__item:not(:first-child):not(:last-child),.fm-button-group--vertical:not(.fm-button-group--mode-default) .fm-button-group__item:not(:first-child):not(:last-child){border-radius:0}.fm-button-group--horizontal:not(.fm-button-group--mode-default) .fm-button-group__item:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.fm-button-group--vertical:not(.fm-button-group--mode-default) .fm-button-group__item:first-child{border-bottom-left-radius:0;border-bottom-right-radius:0}.fm-button-group--horizontal:not(.fm-button-group--mode-default) .fm-button-group__item:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.fm-button-group--vertical:not(.fm-button-group--mode-default) .fm-button-group__item:last-child{border-top-left-radius:0;border-top-right-radius:0}.fm-button-group--horizontal.fm-button-group--mode-outline .fm-button-group__item:not(:first-child){border-left:0}.fm-button-group--vertical.fm-button-group--mode-outline .fm-button-group__item:not(:first-child){border-top:0}.fm-button-group--horizontal.fm-button-group--mode-text .fm-button-group__item:not(:last-child){border-right:var(--fm-button-border-width) solid currentColor!important}.fm-button-group--vertical.fm-button-group--mode-text .fm-button-group__item:not(:last-child){border-bottom:var(--fm-button-border-width) solid currentColor!important}.fm-button-group--fill{flex:1}:root{--fm-cell-padding: 10px 16px;--fm-cell-margin: 5px;--fm-cell-line-height: 24px;--fm-cell-font-size: var(--fm-font-size);--fm-cell-background: var(--fm-background-white);--fm-cell-color: var(--fm-text-color);--fm-cell-label-font-size: 12px;--fm-cell-label-line-height: 18px;--fm-cell-label-color: var(--fm-text-color-light);--fm-cell-required-color: var(--fm-danger-color)}.fm-cell{width:100%;padding:var(--fm-cell-padding);background-color:var(--fm-cell-background);color:var(--fm-cell-color);font-size:var(--fm-cell-font-size);line-height:var(--fm-cell-line-height);position:relative;display:flex;overflow:hidden}.fm-cell--bottom-border,.fm-cell:not(:last-child){border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-cell--bottom-border:after,.fm-cell:not(:last-child):after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-cell--bottom-border:after,.fm-cell:not(:last-child):after{left:16px!important}.fm-cell.fm-cell-all-width:not(:last-child):after{left:0!important}.fm-cell--clickable{cursor:pointer}.fm-cell--clickable:active{background-color:var(--fm-active-color)}.fm-cell--required .fm-cell-title{position:relative}.fm-cell--required .fm-cell-title .fm-cell-title-text:after{padding-left:2px;color:var(--fm-cell-required-color);font-size:12px;content:"*"}.fm-cell__title,.fm-cell__value{flex:1;font-size:inherit}.fm-cell__title{display:inline-block;overflow:hidden}.fm-cell__title-text{display:flex;word-break:break-all}.fm-cell__title-text span{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-cell__right{display:flex;overflow:hidden;flex-direction:column}.fm-cell__right-content{display:flex;flex:1}.fm-cell__right--fill{flex:1}.fm-cell__value{position:relative;overflow:hidden;text-align:right;vertical-align:middle;word-wrap:break-word}.fm-cell__value--alone{text-align:left}.fm-cell__label{margin-top:var(--fm-cell-margin);color:var(--fm-cell-label-color);font-size:var(--fm-cell-label-font-size);line-height:var(--fm-cell-label-line-height)}.fm-cell--center{align-items:center}.fm-cell__extra,.fm-cell__left-icon,.fm-cell__right-icon{min-width:1em;font-size:var(--fm-cell-font-size);line-height:var(--fm-cell-line-height);display:flex;align-items:center}.fm-cell__left-icon{margin-right:var(--fm-cell-margin)}.fm-cell__extra,.fm-cell__right-icon{margin-left:var(--fm-cell-margin)}.fm-cell--noborder .fm-cell:after{display:none!important}.fm-cell--card{padding-left:0;padding-right:0}.fm-checkbox-group{display:flex;overflow:visible;text-align:left;flex-wrap:wrap}.fm-checkbox-group--vertical{flex-direction:column}.fm-checkbox-group--vertical .fm-checkbox:not(:last-child){margin-bottom:8px}.fm-checkbox{margin-top:4px;margin-bottom:4px}:root{--fm-checker-color: var(--fm-text-color);--fm-checker-font-size: var(--fm-font-size);--fm-checker-icon-color: var(--fm-text-color-light);--fm-checker-icon-background: var(--fm-background-white);--fm-checker-icon-radius: var(--fm-radius-md);--fm-checker-checked-icon-color: var(--fm-primary-color);--fm-checker-button-background: var(--fm-gray-2);--fm-checker-disabled-color: var(--fm-disabled-color);--fm-checker-disabled-icon-color: var(--fm-gray-1)}.fm-checker{display:flex;align-items:center;color:var(--fm-checker-color);font-size:var(--fm-checker-font-size)}.fm-checker__icon{display:inline-block;align-items:center}.fm-checker__icon .fm-icon{display:block;height:20px;width:20px;line-height:18px;font-size:14px;text-align:center;border-radius:var(--fm-checker-icon-radius);border:1px solid var(--fm-checker-icon-color);color:var(--fm-checker-icon-background);background-color:var(--fm-checker-icon-background)}.fm-checker__label{margin-left:8px;margin-right:8px;display:inline-block;line-height:20px}.fm-checker:first-child .fm-checker_button{margin-left:0}.fm-checker--round .fm-checker__icon .fm-icon{border-radius:100%}.fm-checker--button .fm-checker__label{font-size:13px;text-align:center;border-radius:14px;line-height:28px;padding:0 12px;min-width:60px;margin-right:0;color:var(--fm-checker-color);background:var(--fm-checker-button-background)}.fm-checker--checked .fm-checker__icon .fm-icon{color:var(--fm-checker-icon-background);border-color:var(--fm-checker-checked-icon-color);background-color:var(--fm-checker-checked-icon-color)}.fm-checker--checked.fm-checker--button .fm-checker__label{color:var(--fm-checker-icon-background);border-color:var(--fm-checker-color);background-color:var(--fm-checker-checked-icon-color)}.fm-checker--readonly{opacity:var(--fm-readonly-opacity)}.fm-checker--disabled{color:var(--fm-checker-disabled-color)}.fm-checker--disabled .fm-checker__icon .fm-icon{border-color:var(--fm-checker-disabled-color);background-color:var(--fm-checker-disabled-icon-color);color:var(--fm-checker-disabled-icon-color)}.fm-checker--disabled.fm-checker--checked .fm-checker__icon .fm-icon{background-color:var(--fm-checker-disabled-color);border-color:var(--fm-checker-disabled-color)}:root{--fm-icon-font-size: 14px;--fm-icon-color: inherit}.fm-icon{font-family:farrisMobile!important;font-size:var(--fm-icon-font-size);font-style:normal;color:var(--fm-icon-color);display:inline-block;width:1em;height:1em;font-weight:400;line-height:1;vertical-align:middle;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fm-icon:before{display:inline-block}.fm-icon-filter:before{content:"\e697"}.fm-icon-cardfold:before{content:"\e6b7"}.fm-icon-cardunfold:before{content:"\e6b8"}.fm-icon-trafficother:before{content:"\e6b9"}.fm-icon-bottomsave:before{content:"\e6ba"}.fm-icon-trafficboat:before{content:"\e6bb"}.fm-icon-trafficcar:before{content:"\e6bc"}.fm-icon-trafficplane:before{content:"\e6bd"}.fm-icon-traffictrain:before{content:"\e6be"}.fm-icon-trafficbus:before{content:"\e6bf"}.fm-icon-addgray:before{content:"\e6a7"}.fm-icon-close:before{content:"\e6a8"}.fm-icon-add-line:before{content:"\e6a9"}.fm-icon-more:before{content:"\e6aa"}.fm-icon-check:before{content:"\e6ab"}.fm-icon-backcircle:before{content:"\e6ac"}.fm-icon-agree:before{content:"\e6ad"}.fm-icon-loading:before{content:"\e6ae"}.fm-icon-filedel:before{content:"\e6af"}.fm-icon-file:before{content:"\e6b0"}.fm-icon-processflow:before{content:"\e6b1"}.fm-icon-redstar:before{content:"\e6b2"}.fm-icon-rightcircle:before{content:"\e6b3"}.fm-icon-timecircle:before{content:"\e6b4"}.fm-icon-screening:before{content:"\e6b5"}.fm-icon-uncheck:before{content:"\e6b6"}.fm-icon-operation:before{content:"\e02c"}.fm-icon-approvalamount:before{content:"\e02b"}.fm-icon-agreed:before{content:"\e02a"}.fm-icon-inprocess:before{content:"\e029"}.fm-icon-feedback:before{content:"\e028"}.fm-icon-print:before{content:"\e027"}.fm-icon-important:before{content:"\e026"}.fm-icon-amountofreimbursement:before{content:"\e025"}.fm-icon-safetyassistant:before{content:"\e024"}.fm-icon-remind:before{content:"\e023"}.fm-icon-eyeoppen:before{content:"\e022"}.fm-icon-eyeclose:before{content:"\e01e"}.fm-icon-photography:before{content:"\e021"}.fm-icon-photograph:before{content:"\e020"}.fm-icon-addpicture:before{content:"\e01f"}.fm-icon-top_other-face:before{content:"\e01d"}.fm-icon-top_other:before{content:"\e01c"}.fm-icon-delete:before{content:"\e01b"}.fm-icon-nextpage:before{content:"\e01a"}.fm-icon-addbutton:before{content:"\e019"}.fm-icon-waitforprocessing:before{content:"\e018"}.fm-icon-warningmessage:before{content:"\e017"}.fm-icon-failureprompt:before{content:"\e016"}.fm-icon-successfulhints:before{content:"\e015"}.fm-icon-other:before{content:"\e014"}.fm-icon-retract:before{content:"\e013"}.fm-icon-drop-down:before{content:"\e012"}.fm-icon-cancel:before{content:"\e011"}.fm-icon-search:before{content:"\e010"}.fm-icon-multiplechoice:before{content:"\e00f"}.fm-icon-singlechoice:before{content:"\e00e"}.fm-icon-microphone:before{content:"\e00d"}.fm-icon-arrow-chevron-left:before{content:"\e00c"}.fm-icon-helpcenter:before{content:"\e00b"}.fm-icon-sweepcode:before{content:"\e00a"}.fm-icon-groupchat:before{content:"\e009"}.fm-icon-addfriend:before{content:"\e008"}.fm-icon-top_my:before{content:"\e007"}.fm-icon-top_my-face:before{content:"\e006"}.fm-icon-top_friend:before{content:"\e005"}.fm-icon-top_friend-face:before{content:"\e004"}.fm-icon-top_publicpraise:before{content:"\e003"}.fm-icon-top_publicpraise-face:before{content:"\e002"}.fm-icon-top_home:before{content:"\e001"}.fm-icon-top_home-face:before{content:"\e000"}.fm-icon-s-agree-o:before{content:"\e655"}.fm-icon-s-arrow-down:before{content:"\e656"}.fm-icon-s-add-o:before{content:"\e657"}.fm-icon-s-annex:before{content:"\e658"}.fm-icon-s-arrow:before{content:"\e659"}.fm-icon-s-arrow-up:before{content:"\e65e"}.fm-icon-s-checkbox-checked:before{content:"\e65f"}.fm-icon-s-checkbox:before{content:"\e660"}.fm-icon-s-arrow-left:before{content:"\e661"}.fm-icon-s-exchange:before{content:"\e662"}.fm-icon-s-clear:before{content:"\e663"}.fm-icon-s-checked:before{content:"\e664"}.fm-icon-s-eye-o:before{content:"\e665"}.fm-icon-s-close-eye:before{content:"\e666"}.fm-icon-s-at:before{content:"\e667"}.fm-icon-s-feedback-o:before{content:"\e668"}.fm-icon-s-fold-circle-o:before{content:"\e669"}.fm-icon-s-cross:before{content:"\e66a"}.fm-icon-s-minus:before{content:"\e66b"}.fm-icon-s-file:before{content:"\e66c"}.fm-icon-s-fold-triangle:before{content:"\e66d"}.fm-icon-s-filter:before{content:"\e66e"}.fm-icon-s-forword:before{content:"\e66f"}.fm-icon-s-plus:before{content:"\e670"}.fm-icon-s-filedel:before{content:"\e671"}.fm-icon-s-fold-o:before{content:"\e672"}.fm-icon-s-image-o:before{content:"\e673"}.fm-icon-s-more-o:before{content:"\e674"}.fm-icon-s-redio-circle:before{content:"\e675"}.fm-icon-s-reject-o:before{content:"\e676"}.fm-icon-s-success:before{content:"\e677"}.fm-icon-s-refresh:before{content:"\e678"}.fm-icon-s-star:before{content:"\e679"}.fm-icon-s-processflow:before{content:"\e67a"}.fm-icon-s-unfold-circle-o:before{content:"\e67b"}.fm-icon-s-rewind:before{content:"\e67c"}.fm-icon-s-unfold-o:before{content:"\e67d"}.fm-icon-s-save-o:before{content:"\e67e"}.fm-icon-s-warning-o:before{content:"\e67f"}.fm-icon-s-unfold-triangle:before{content:"\e680"}.fm-icon-s-print-o:before{content:"\e681"}.fm-icon-s-search-o:before{content:"\e682"}.fm-icon-s-news:before{content:"\e683"}.fm-icon-s-news-o:before{content:"\e684"}.fm-icon-s-shop:before{content:"\e68c"}.fm-icon-s-sending:before{content:"\e60b"}.fm-icon-s-import-o:before{content:"\e60c"}.fm-icon-s-edit-o:before{content:"\e60d"}.fm-icon-s-qrcode:before{content:"\e60e"}.fm-icon-s-briefcase:before{content:"\e60f"}.fm-icon-s-notificationoff:before{content:"\e610"}.fm-icon-s-pencil:before{content:"\e611"}.fm-icon-s-star-o:before{content:"\e612"}.fm-icon-s-barcode:before{content:"\e613"}.fm-icon-s-salemessage-o:before{content:"\e614"}.fm-icon-s-listview-o:before{content:"\e615"}.fm-icon-s-picture-o:before{content:"\e616"}.fm-icon-s-savefillet-o:before{content:"\e617"}.fm-icon-s-shoppingcart:before{content:"\e618"}.fm-icon-s-pending:before{content:"\e61d"}.fm-icon-s-expandview-o:before{content:"\e61e"}.fm-icon-s-lock-o:before{content:"\e623"}.fm-icon-s-my:before{content:"\e692"}.fm-icon-s-service:before{content:"\e633"}.fm-icon-s-wallet-o:before{content:"\e634"}.fm-icon-s-shoppingcart-o:before{content:"\e636"}.fm-icon-s-setting-o:before{content:"\e637"}.fm-icon-s-my-o:before{content:"\e696"}.fm-icon-s-unfoldrec-o:before{content:"\e639"}.fm-icon-s-profile:before{content:"\e63a"}.fm-icon-s-link-o:before{content:"\e609"}.fm-icon-s-usersetting-o:before{content:"\e632"}.fm-icon-s-shop-o:before{content:"\e68a"}.fm-icon-s-list:before{content:"\e62a"}.fm-icon-s-location-o:before{content:"\e685"}.fm-icon-s-location:before{content:"\e686"}.fm-icon-s-moment:before{content:"\e689"}.fm-icon-s-moment-o:before{content:"\e68b"}.fm-icon-s-revoke-o:before{content:"\e687"}.fm-icon-s-submit-o:before{content:"\e688"}.fm-icon-s-app-o:before{content:"\e68f"}.fm-icon-s-app:before{content:"\e691"}.fm-icon-s-book-o:before{content:"\e695"}.fm-icon-s-book:before{content:"\e690"}.fm-icon-s-comment:before{content:"\e693"}.fm-icon-s-comment-o:before{content:"\e694"}.fm-icon-s-illustrate-o:before{content:"\e68d"}.fm-icon-s-clear-o:before{content:"\e68e"}.fm-icon-friend:before{content:"\e005"}.fm-icon-scan:before{content:"\e69a"}.fm-icon-back:before{content:"\e69b"}.fm-icon-menu:before{content:"\e69d"}:root{--fm-navbar-background: var(--fm-background-white);--fm-navbar-color: var(--fm-text-color);--fm-navbar-height: 44px;--fm-navbar-title-size: 17px;--fm-navbar-side-size: 16px;--fm-navbar-arrow-size: 18px}.fm-navbar{position:relative;z-index:9;display:flex;align-items:center;line-height:1.5;text-align:center;user-select:none;height:var(--fm-navbar-height);color:var(--fm-navbar-color);background-color:var(--fm-navbar-background)}.fm-navbar.fm-navbar-fixed{position:fixed;top:0;left:0;width:100%}.fm-navbar.fm-navbar-border-bottom{border-bottom:1px solid #eee;position:relative;border-bottom:none}.fm-navbar.fm-navbar-border-bottom:after{content:"";position:absolute;background-color:#eee;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-navbar-title{max-width:60%;margin:0 auto;font-weight:500;font-size:var(--fm-navbar-title-size);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-navbar-left,.fm-navbar-right{position:absolute;top:0;bottom:0;display:flex;align-items:center;max-width:30%;padding:0 16px;font-size:var(--fm-navbar-side-size);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;cursor:pointer}.fm-navbar-left{left:0}.fm-navbar-left:active{opacity:var(--fm-active-opacity)}.fm-navbar-left.fm-navbar-left-padding{padding-left:14px}.fm-navbar-right{right:0}.fm-navbar-right .fm-navbar-text:active{opacity:var(--fm-active-opacity)}.fm-navbar .fm-navbar-left-arrow{min-width:1em;margin-right:4px;font-size:var(--fm-navbar-arrow-size)}:root{--fm-input-group-size: var(--fm-font-size);--fm-input-group-color: var(--fm-text-color);--fm-input-group-sub-size: 13px;--fm-input-group-padding: 10px 16px;--fm-input-group-sub-color: var(--fm-text-color-light);--fm-input-group-border-color: var(--fm-gray-2)}.fm-input-group{display:flex;flex-direction:column;background-color:var(--fm-white)}.fm-input-group--padding{padding:var(--fm-input-group-padding)}.fm-input-group__body{flex:1;display:flex;align-items:center;min-height:24px;color:var(--fm-input-group-color);font-size:var(--fm-input-group-size)}.fm-input-group__body--border{border:1px solid var(--fm-input-group-border-color);padding:4px 8px;border-radius:4px}.fm-input-group__control{display:flex;width:100%;min-width:0;margin:0;padding:0;border:0;resize:none;color:inherit;font-size:inherit;outline:none;line-height:inherit;background-color:transparent}.fm-input-group__control::placeholder{color:var(--fm-input-group-sub-color)}.fm-input-group__control:disabled{opacity:1;cursor:not-allowed;color:var(--fm-disabled-color);-webkit-text-fill-color:currentColor}.fm-input-group__control::-webkit-search-cancel-button{display:none}.fm-input-group__control--readonly{cursor:default;color:var(--fm-readonly-color)}.fm-input-group__control--left{justify-content:flex-start;text-align:left}.fm-input-group__control--center{justify-content:center;text-align:center}.fm-input-group__control--right{justify-content:flex-end;text-align:right}.fm-input-group__clear{cursor:pointer;margin-right:-8px;padding:4px 8px;color:var(--fm-input-group-sub-color);flex-shrink:0;box-sizing:content-box}.fm-input-group__left-icon{margin:auto 4px auto 0}.fm-input-group__right-icon{margin:auto 0 auto 4px}.fm-input-group__word-limit{margin-top:4px;line-height:18px;text-align:right;font-size:var(--fm-input-group-sub-size);color:var(--fm-input-group-sub-color)}.fm-radio-group{display:flex;overflow:visible;text-align:left;flex-wrap:wrap}.fm-radio-group--vertical{flex-direction:column}.fm-radio-group--vertical .fm-radio:not(:last-child){margin-bottom:8px}.fm-radio{margin-top:4px;margin-bottom:4px}:root,:host{--fm-rate-icon-size: 20px;--fm-rate-icon-gutter: var(--fm-padding-base);--fm-rate-icon-void-color: var(--fm-gray-4);--fm-rate-icon-full-color: var(--fm-orange-3);--fm-rate-icon-disabled-color: var(--fm-gray-4)}.fm-rate{display:inline-flex;cursor:pointer;user-select:none;flex-wrap:wrap}.fm-rate__item{position:relative}.fm-rate__item:not(:last-child){padding-right:var(--fm-rate-icon-gutter)}.fm-rate__icon{display:block;width:1em;color:var(--fm-rate-icon-void-color);font-size:var(--fm-rate-icon-size)}.fm-rate__icon--half{position:absolute;top:0;left:0;overflow:hidden;pointer-events:none;color:var(--fm-rate-icon-full-color)}.fm-rate__icon--full{color:var(--fm-rate-icon-full-color)}.fm-rate__icon--disabled{color:var(--fm-rate-icon-disabled-color)}.fm-rate--disabled{cursor:not-allowed}.fm-rate--readonly{cursor:default}:root{--fm-form-item-size: var(--fm-font-size);--fm-form-item-color: var(--fm-text-color);--fm-form-item-label-width: 105px;--fm-form-item-sub-size: 13px}.fm-form-item{font-size:var(--fm-form-item-size);color:var(--fm-form-item-color)}.fm-form-item--vertical{flex-direction:column}.fm-form-item--vertical .fm-form-item__label{margin-bottom:4px}.fm-form-item__label{display:flex;flex:none;align-items:flex-start;text-align:left;width:var(--fm-form-item-label-width);margin-right:4px}.fm-form-item__label--left{justify-content:flex-start}.fm-form-item__label--center{justify-content:center}.fm-form-item__label--right{justify-content:flex-end}.fm-form-item__label--required{position:relative}.fm-form-item__label--required .fm-cell__title-text:after{padding-left:2px;color:var(--fm-cell-required-color);font-size:12px;content:"*"}.fm-form-item__content{display:flex}.fm-form-item__content .fm-input-group{padding:0;flex:1}.fm-form-item__content--left{justify-content:flex-start}.fm-form-item__content--left input.fm-input-group__control{justify-content:flex-start;text-align:left}.fm-form-item__content--center{justify-content:center}.fm-form-item__content--center input.fm-input-group__control{justify-content:flex-center;text-align:center}.fm-form-item__content--right{justify-content:flex-end}.fm-form-item__content--right input.fm-input-group__control{justify-content:flex-end;text-align:right}.fm-form-item__error-message{color:var(--fm-danger-color);font-size:var(--fm-form-item-sub-size)}.fm-form-item__error-message--left{text-align:left}.fm-form-item__error-message--center{text-align:center}.fm-form-item__error-message--right{text-align:right}:root{--fm-overlay-background: rgba(0, 0, 0, .4);--fm-overlay-zindex: 98}.fm-overlay{width:100%;position:fixed;top:0;left:0;z-index:var(--fm-overlay-zindex);background-color:var(--fm-overlay-background);bottom:0}@supports ((bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom))) and (-webkit-overflow-scrolling: touch){.fm-overlay{bottom:constant(safe-area-inset-bottom);bottom:env(safe-area-inset-bottom)}}:root{--fm-popup-background: var(--fm-white);--fm-popup-zindex: var(--fm-zindex-3);--fm-popup-radius: 16px}.fm-popup{position:fixed;max-height:100%;overflow-y:auto;background-color:var(--fm-popup-background);transition:all var(--fm-duration-base);z-index:var(--fm-popup-zindex)}.fm-popup--center{top:50%;left:50%;transform:translate(-50%,-50%)}.fm-popup--center.fm-popup--round{border-radius:var(--fm-popup-radius)}.fm-popup--top,.fm-popup--bottom{left:0;right:0}.fm-popup--top{top:0}.fm-popup--top.fm-popup--round{border-radius:0 0 var(--fm-popup-radius) var(--fm-popup-radius)}.fm-popup--bottom{bottom:0}@supports ((bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom))) and (-webkit-overflow-scrolling: touch){.fm-popup--bottom{bottom:constant(safe-area-inset-bottom);bottom:env(safe-area-inset-bottom)}}.fm-popup--bottom.fm-popup--round{border-radius:var(--fm-popup-radius) var(--fm-popup-radius) 0 0}.fm-popup--left,.fm-popup--right{top:0;bottom:0}@supports ((bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom))) and (-webkit-overflow-scrolling: touch){.fm-popup--left,.fm-popup--right{bottom:constant(safe-area-inset-bottom);bottom:env(safe-area-inset-bottom)}}.fm-popup--left{left:0}.fm-popup--left.fm-popup--round{border-radius:0 var(--fm-popup-radius) var(--fm-popup-radius) 0}.fm-popup--right{right:0}.fm-popup--right.fm-popup--round{border-radius:var(--fm-popup-radius) 0 0 var(--fm-popup-radius)}:root{--fm-popover-zindex: var(--fm-zindex-5);--fm-popover-padding: var(--fm-padding-sm);--fm-popover-border-radius: var(--fm-radius-lg);--fm-popover-arrow-size: 8px;--fm-popover-content-font-size: var(--fm-font-size);--fm-popover-content-line-height: var(--fm-line-height);--fm-popover-light-theme-color: var(--fm-gray-7);--fm-popover-light-theme-background: var(--fm-white);--fm-popover-dark-theme-color: var(--fm-white);--fm-popover-dark-theme-background: var(--fm-gray-7);--fm-popover-shadow: 0 6px 30px 5px rgba(0, 0, 0, .05), 0 16px 24px 2px rgba(0, 0, 0, .04), 0 8px 10px -5px rgba(0, 0, 0, .08)}.fm-popover{position:absolute;overflow:visible;top:0;left:0;transition:opacity .15s;background-color:transparent;z-index:var(--fm-popover-zindex)}.fm-popover__wrapper{display:inline-block}.fm-popover__content{position:relative;padding:var(--fm-popover-padding);border-radius:var(--fm-popover-border-radius);font-size:var(--fm-popover-content-font-size);line-height:var(--fm-popover-content-line-height);word-break:break-all}.fm-popover__arrow{position:absolute;width:0;height:0;z-index:1;border-style:solid;border-color:transparent;border-width:var(--fm-popover-arrow-size, 8px)}.fm-popover__arrow--top{border-bottom-width:0;border-top-color:currentColor;margin-bottom:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover__arrow--right{border-left-width:0;border-right-color:currentColor;margin-left:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover__arrow--bottom{border-top-width:0;border-bottom-color:currentColor;margin-top:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover__arrow--left{border-right-width:0;border-left-color:currentColor;margin-right:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover--light .fm-popover__content{background:var(--fm-popover-light-theme-background);color:var(--fm-popover-light-theme-color);box-shadow:var(--fm-popover-shadow)}.fm-popover--light .fm-popover__arrow{color:var(--fm-popover-light-theme-background)}.fm-popover--dark .fm-popover__content{background:var(--fm-popover-dark-theme-background);color:var(--fm-popover-dark-theme-color);box-shadow:var(--fm-popover-shadow)}.fm-popover--dark .fm-popover__arrow{color:var(--fm-popover-dark-theme-background)}:root{--fm-switch-background: var(--fm-text-color-light);--fm-switch-on-color: var(--fm-primary-color)}.fm-switch{position:relative;cursor:pointer;display:inline-block;transition:background-color var(--fm-duration-base);background-color:var(--fm-switch-background)}.fm-switch--disabled{cursor:not-allowed;opacity:var(--fm-disabled-opacity)}.fm-switch--readonly{cursor:default;opacity:var(--fm-readonly-opacity)}.fm-switch--loading{cursor:default}.fm-switch--on{background-color:var(--fm-switch-on-color)}.fm-switch--on .fm-switch__loadding{color:var(--fm-switch-on-color)}.fm-switch__node{display:flex;align-items:center;justify-content:center;position:absolute;top:1px;left:1px;z-index:1;width:28px;height:28px;border-radius:100%;background-color:var(--fm-white);transition:transform var(--fm-duration-base) cubic-bezier(.3,1.05,.4,1.05)}:root{--fm-toast-zindex: var(--fm-zindex-5);--fm-toast-color: var(--fm-white);--fm-toast-font-size: 16px;--fm-toast-background: var(--fm-gray-7);--fm-toast-icon-font-size: 48px}.fm-toast{position:fixed;top:50%;left:50%;display:-webkit-box;display:-webkit-flex;display:flex;flex-direction:column;align-items:center;justify-content:center;box-sizing:content-box;max-width:70%;padding:16px;line-height:20px;white-space:pre-wrap;text-align:center;word-wrap:break-word;border-radius:7px;transform:translate(-50%,-50%);color:var(--fm-toast-color);z-index:var(--fm-toast-zindex);font-size:var(--fm-toast-font-size);background-color:var(--fm-toast-background)}.fm-toast--top{top:50px;transform:translate(-50%)}.fm-toast--bottom{top:auto;bottom:50px}.fm-toast--info,.fm-toast--success,.fm-toast--warning,.fm-toast--error{width:88px;min-height:88px}.fm-toast__icon-wrapper{margin-bottom:8px}.fm-toast--default{padding:8px 16px}.fm-toast--loading{min-width:84px;min-height:84px}.fm-toast--loading-icon{position:relative;display:inline-block;width:30px;height:30px;margin-bottom:8px;vertical-align:middle;animation:fm-rotate 2s linear infinite}.fm-toast--loading-icon circle{animation:fm-circular 1.5s ease-in-out infinite;stroke:currentColor;stroke-width:4;stroke-linecap:round}:root{--fm-notify-color: var(--fm-white);--fm-notify-zindex: var(--fm-zindex-5);--fm-notify-info-background: var(--fm-primary-color);--fm-notify-success-background: var(--fm-success-color);--fm-notify-warning-background: var(--fm-warning-color);--fm-notify-error-background: var(--fm-danger-color)}.fm-notify{position:fixed;left:0;top:0;z-index:var(--fm-notify-zindex);display:flex;align-items:center;justify-content:center;width:100%;max-height:100%;padding:8px 16px;box-sizing:border-box;color:var(--fm-notify-color);font-size:14px;line-height:20px;white-space:pre-wrap;text-align:center;word-wrap:break-word;overflow-y:auto}.fm-notify--info{background-color:var(--fm-notify-info-background)}.fm-notify--success{background-color:var(--fm-notify-success-background)}.fm-notify--warning{background-color:var(--fm-notify-warning-background)}.fm-notify--error{background-color:var(--fm-notify-error-background)}:root{--fm-dialog-background: var(--fm-background-2);--fm-dialog-padding-top: 24px;--fm-dialog-padding-left: 16px;--fm-dialog-header-font-size: 17px;--fm-dialog-header-line-height: 24px;--fm-dialog-content-font-size: 15px;--fm-dialog-content-line-height: 20px;--fm-dialog-footer-height: 50px;--fm-border-color: var(--fm-gray-4)}@media screen and (min-width: 375px){.fm-dialog{width:320px}}@media screen and (min-width: 280px) and (max-width: 375px){.fm-dialog{width:240px}}.fm-dialog{display:flex;flex-direction:column;background-color:var(--fm-dialog-background);overflow:hidden}.fm-dialog__header{padding-top:var(--fm-dialog-padding-top);padding-left:var(--fm-dialog-header-padding-left);padding-right:var(--fm-dialog-header-padding-left);font-weight:var(--fm-font-bold-light);font-size:var(--fm-dialog-header-font-size);color:var(--fm-text-color);line-height:var(--fm-dialog-header-line-height);text-align:center}.fm-dialog__content{max-height:70vh;overflow-y:auto}.fm-dialog__content__message{padding:var(--fm-dialog-padding-top) var(--fm-dialog-padding-left);font-size:var(--fm-dialog-content-font-size);line-height:var(--fm-dialog-content-line-height);white-space:pre-wrap;text-align:center;word-wrap:break-word}.fm-dialog__content__message--has-title{padding-top:11px;color:var(--fm-text-color-light)}.fm-dialog__content--prompt{padding:14px}.fm-dialog__content--prompt__input{display:block}.fm-dialog__footer{display:flex;flex-direction:row;align-items:center;border-top:1px solid var(--fm-border-color);position:relative;border-top:none}.fm-dialog__footer:before{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:0 auto auto 0;width:100%;height:1px;transform-origin:50% 50%;transform:scaleY(.5)}.fm-dialog__footer .fm-button{border:0;height:var(--fm-dialog-footer-height);border-right:1px solid var(--fm-border-color);position:relative;border-right:none}.fm-dialog__footer .fm-button:after{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:0 0 auto auto;width:1px;height:100%;background:var(--fm-border-color);transform-origin:100% 50%;transform:scaleX(.5)}.fm-dialog__footer .fm-button:last-child{border-right:0}.fm-dialog__footer .fm-button:last-child:after{display:none!important}.fm-dialog__footer .fm-button--default{color:var(--fm-text-color)}.fm-dialog__footer--is-column{flex-direction:column}.fm-dialog__footer--is-column__button{border-top:1px solid var(--fm-border-color);position:relative;border-top:none}.fm-dialog__footer--is-column__button:before{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:0 auto auto 0;width:100%;height:1px;transform-origin:50% 50%;transform:scaleY(.5)}.fm-dialog__footer--is-column__button:first-child{border-top:0}.fm-dialog__footer--is-column__button:first-child:before{display:none!important}.fm-dialog.fm-dialog--relative{position:relative}.fm-dialog.fm-dialog--relative__header{padding-right:40px}.fm-dialog__close{position:absolute;right:16px;top:16px;width:22px;height:22px;line-height:22px;text-align:center}.fm-dialog__close .fm-icon{font-size:15px;color:var(--fm-text-color-2)}:root,:host{--fm-loading-color: var(--fm-gray-5);--fm-loading-text-font-size: 14px;--fm-loading-text-line-height: 1.4}.fm-loading{display:flex;align-items:center;position:relative;color:var(--fm-loading-color)}.fm-loading__text{color:var(--fm-loading-color);font-size:var(--fm-loading-text-font-size);line-height:var(--fm-loading-text-line-height);margin-left:var(--fm-margin-xs)}.fm-loading--vertical{flex-direction:column}.fm-loading--vertical .fm-loading__text{margin-top:var(--fm-margin-xs);margin-left:0}.fm-loading__circular{display:block;animation:fm-rotate 2s linear infinite}.fm-loading__circular circle{animation:fm-circular 1.5s ease-in-out infinite;stroke:currentColor;stroke-width:3;stroke-linecap:round}.fm-loading__spinner{display:block;position:relative;animation:fm-rotate 1s linear infinite;animation-timing-function:steps(12)}.fm-loading__line{position:absolute;top:0;left:0;width:100%;height:100%}.fm-loading__line-inner{display:block;width:2px;height:25%;margin:0 auto;background-color:currentColor;border-radius:40%}.fm-loading__ring{display:block;animation:fm-rotate .75s linear infinite;border:4px solid;border-right-color:transparent;border-radius:50%}@keyframes fm-circular{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40}to{stroke-dasharray:90,150;stroke-dashoffset:-120}}.fm-loading__line--1{transform:rotate(30deg);opacity:1}.fm-loading__line--2{transform:rotate(60deg);opacity:.9375}.fm-loading__line--3{transform:rotate(90deg);opacity:.875}.fm-loading__line--4{transform:rotate(120deg);opacity:.8125}.fm-loading__line--5{transform:rotate(150deg);opacity:.75}.fm-loading__line--6{transform:rotate(180deg);opacity:.6875}.fm-loading__line--7{transform:rotate(210deg);opacity:.625}.fm-loading__line--8{transform:rotate(240deg);opacity:.5625}.fm-loading__line--9{transform:rotate(270deg);opacity:.5}.fm-loading__line--10{transform:rotate(300deg);opacity:.4375}.fm-loading__line--11{transform:rotate(330deg);opacity:.375}.fm-loading__line--12{transform:rotate(360deg);opacity:.3125}:root,:host{--fm-pull-refresh-head-height: 50px;--fm-pull-refresh-head-font-size: var(--fm-font-size-md);--fm-pull-refresh-head-text-color: var(--fm-text-color-light);--fm-pull-refresh-animation-duration: .3s}.fm-pull-refresh{overflow:hidden}.fm-pull-refresh__track{position:relative;height:100%}.fm-pull-refresh__track--not-pulling{transition:transform ease var(--fm-pull-refresh-animation-duration)}.fm-pull-refresh__head{position:absolute;left:0;width:100%;height:var(--fm-pull-refresh-head-height);color:var(--fm-pull-refresh-head-text-color);font-size:var(--fm-pull-refresh-head-font-size);line-height:var(--fm-pull-refresh-head-height);text-align:center;transform:translateY(-100%);display:flex;justify-content:center;align-items:center;flex-direction:column;overflow:hidden}:root,:host{--fm-list-text-color: var(--fm-gray-5);--fm-list-text-font-size: var(--fm-font-size-md, 14px);--fm-list-text-line-height: 50px;--fm-list-loading-icon-size: 16px}.fm-list__loading,.fm-list__error,.fm-list__load-more,.fm-list__finished-info{color:var(--fm-list-text-color);font-size:var(--fm-list-text-font-size);line-height:var(--fm-list-text-line-height);text-align:center}.fm-list__loading-container{display:flex;height:var(--fm-list-text-line-height);justify-content:center;align-items:center}:root,:host{--fm-listview-checkbox-container-width: 40px;--fm-listview-toolbar-height: 42px;--fm-listview-split-line-color: rgb(230, 235, 235);--fm-listview-empty-text-color: var(--fm-gray-5);--fm-listview-empty-text-font-size: var(--fm-font-size-md, 14px);--fm-listview-empty-text-line-height: 50px}.fm-list-view--fill{height:100%;flex-grow:1;flex-shrink:1;display:flex;flex-direction:column;overflow:hidden}.fm-list-view--fill .fm-list-view__track{flex:1 1 0;overflow:auto;-webkit-overflow-scrolling:touch}.fm-list-view--fill .fm-pull-refresh{min-height:100%}.fm-list-view .fm-list{user-select:none;-webkit-user-select:none}.fm-list-view__item{display:flex;position:relative;overflow:hidden}.fm-list-view__item-checker{display:flex;position:absolute;height:100%;width:var(--fm-listview-checkbox-container-width);justify-content:center;align-items:center}.fm-list-view__item-content{flex:1;transform:translate(0)}.fm-list-view__content--split .fm-list-view__item:not(:last-child):not(.fm-list-view__item--child){border-bottom:1px solid var(--fm-listview-split-line-color)}.fm-list-view--multi-select .fm-list-view__item-content{transform:translate(var(--fm-listview-checkbox-container-width))}.fm-list-view__item--group{display:block}.fm-list-view__item-group-header{padding:10px 16px;line-height:20px;font-size:var(--fm-font-size-md);color:var(--fm-gray-5)}.fm-list-view__empty-message{display:block;color:var(--fm-listview-empty-text-color);font-size:var(--fm-listview-empty-text-font-size);line-height:var(--fm-listview-empty-text-line-height);text-align:center}.fm-list-view__item-text{padding:13px 20px;color:var(--fm-gray-7);font-size:var(--fm-font-size)}.fm-list-view__toolbar{display:flex;align-items:center;height:var(--fm-listview-toolbar-height)}.fm-list-view__toolbar-item{flex:1}:root,:host{--fm-swipe-cell-button-text-color: var(--fm-white);--fm-swipe-cell-button-bg-color: var(--fm-gray-4);--fm-swipe-cell-button-font-size: var(--fm-font-size);--fm-swipe-cell-button-icon-size: var(--fm-font-size);--fm-swipe-cell-button-padding: var(--fm-padding-md)}.fm-swipe-cell{position:relative;overflow:hidden}.fm-swipe-cell__wrapper{transition-timing-function:cubic-bezier(.18,.89,.32,1);transition-property:transform}.fm-swipe-cell__left,.fm-swipe-cell__right{position:absolute;top:0;height:100%;display:flex}.fm-swipe-cell__left{left:0;transform:translate3d(-100%,0,0);flex-direction:row-reverse}.fm-swipe-cell__right{right:0;transform:translate3d(100%,0,0)}.fm-swipe-cell__button{display:inline-flex;justify-content:center;align-items:center;height:100%;padding:0 var(--fm-swipe-cell-button-padding);color:var(--fm-swipe-cell-button-text-color);background-color:var(--fm-swipe-cell-button-bg-color)}.fm-swipe-cell__icon{font-size:var(--fm-swipe-cell-button-icon-size)}.fm-swipe-cell__text{font-size:var(--fm-swipe-cell-button-font-size)}.fm-swipe-cell__icon+.fm-swipe-cell__text:not(:empty){margin-left:6px}:root,:host{--fm-action-sheet-max-height: 80%;--fm-action-sheet-border-radius: 12px;--fm-action-sheet-description-color: var(--fm-gray-5);--fm-action-sheet-description-line-height: 22px;--fm-action-sheet-description-font-size: var(--fm-font-size-md);--fm-action-sheet-description-padding: 12px 16px;--fm-action-sheet-item-default-background: var(--fm-background-white);--fm-action-sheet-item-active-background: #f2f3f5;--fm-action-sheet-item-padding: 13px 16px;--fm-action-sheet-item-text-color: #1a1a1a;--fm-action-sheet-item-disabled-text-color: #bdbdbd;--fm-action-sheet-item-subtitle-color: var(--fm-gray-5);--fm-action-sheet-item-icon-size: 18px;--fm-action-sheet-item-icon-margin-right: 8px;--fm-action-sheet-item-title-font-size: var(--fm-font-size-lg);--fm-action-sheet-item-title-line-height: 24px;--fm-action-sheet-item-subtitle-font-size: var(--fm-font-size-sm);--fm-action-sheet-item-subtitle-line-height: 18px;--fm-action-sheet-item-subtitle-margin-top: 2px;--fm-action-sheet-footer-gap-color: #f5f5f5;--fm-action-sheet-divider-color: #e7e7e7;--fm-action-sheet-cancel-height: 48px;--fm-action-sheet-cancel-color: #1a1a1a;--fm-action-sheet-cancel-font-size: var(--fm-font-size-lg);--fm-action-sheet-cancel-font-weight: 500}.fm-action-sheet{display:flex;flex-direction:column;max-height:var(--fm-action-sheet-max-height);overflow:hidden;user-select:none}.fm-action-sheet--round{border-top-left-radius:var(--fm-action-sheet-border-radius);border-top-right-radius:var(--fm-action-sheet-border-radius)}.fm-action-sheet__description{flex-shrink:0;color:var(--fm-action-sheet-description-color);line-height:var(--fm-action-sheet-description-line-height);font-size:var(--fm-action-sheet-description-font-size);text-align:center;padding:var(--fm-action-sheet-description-padding);position:relative}.fm-action-sheet__description--left{text-align:left}.fm-action-sheet__content{flex:1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch}.fm-action-sheet__item,.fm-action-sheet__cancel{background-color:var(--fm-action-sheet-item-default-background)}.fm-action-sheet__item:active,.fm-action-sheet__cancel:active{background-color:var(--fm-action-sheet-item-active-background)}.fm-action-sheet__item{display:flex;position:relative;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;padding:var(--fm-action-sheet-item-padding);color:var(--fm-action-sheet-item-text-color);cursor:pointer}.fm-action-sheet__item-icon{font-size:var(--fm-action-sheet-item-icon-size);margin-right:var(--fm-action-sheet-item-icon-margin-right)}.fm-action-sheet__item-title{font-size:var(--fm-action-sheet-item-title-font-size);line-height:var(--fm-action-sheet-item-title-line-height)}.fm-action-sheet__item-subtitle{font-size:var(--fm-action-sheet-item-subtitle-font-size);line-height:var(--fm-action-sheet-item-subtitle-line-height);width:100%;margin-top:var(--fm-action-sheet-item-subtitle-margin-top);color:var(--fm-action-sheet-item-subtitle-color);overflow-wrap:break-word}.fm-action-sheet__item--left{text-align:left;justify-content:flex-start}.fm-action-sheet__item--disabled{color:var(--fm-action-sheet-item-disabled-text-color);cursor:not-allowed}.fm-action-sheet__item--disabled:active{background-color:var(--fm-action-sheet-item-default-background)}.fm-action-sheet__description:after,.fm-action-sheet__item:after{content:"";display:block;position:absolute;height:1px;bottom:0;left:0;right:0;transform:scaleY(.5);background-color:var(--fm-action-sheet-divider-color)}.fm-action-sheet__footer-gap{height:8px;background-color:var(--fm-action-sheet-footer-gap-color)}.fm-action-sheet__cancel{display:flex;flex-direction:column;justify-content:center;align-items:center;height:var(--fm-action-sheet-cancel-height);cursor:pointer;color:var(--fm-action-sheet-cancel-color);font-size:var(--fm-action-sheet-cancel-font-size);font-weight:var(--fm-action-sheet-cancel-font-weight)}:root{--fm-tab-bar-item-color: var(--fm-text-color);--fm-tab-bar-item-active-color: var(--fm-primary-color);--fm-tab-bar-item-font-size: 16px}.fm-tab-bar-item{flex:1 0 auto;position:relative;display:inline-flex;align-items:center;justify-content:center;margin:0 12px;line-height:44px}.fm-tab-bar-item--actived{color:var(--fm-tab-bar-item-active-color)}.fm-tab-bar-item--disabled{color:var(--fm-disabled-color)}.fm-tab-bar-item--dot .fm-tab-bar-item__text:after{content:"";position:absolute;top:0;right:0;width:8px;height:8px;background-color:var(--fm-danger-color);border-radius:100%;transform:translate(100%,100%)}.fm-tab-bar-item__text{position:relative;display:flex;justify-content:center;min-width:40px}.fm-tab-bar-item__ink{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--fm-tab-bar-item-active-color);border-radius:2px}.fm-tab-bar-item__badge{position:absolute;top:2px;right:0;padding:0 4px;max-width:28px;font-weight:500;font-size:12px;line-height:14px;border-radius:7px;color:var(--fm-background-white);background-color:var(--fm-danger-color);transform:translate(50%)}.fm-tab-bar-item__icon{position:relative;display:flex;align-items:center;justify-content:center;padding-right:4px}:root{--fm-tab-bar-height: 44px;--fm-tab-bar-background: var(--fm-background-white);--fm-tab-bar-color: var(--fm-text-color);--fm-tab-bar-active-color: var(--fm-primary-color);--fm-tab-bar-font-size: 16px}.fm-tab-bar{position:relative;background:var(--fm-tab-bar-background);color:var(--fm-tab-bar-color);font-size:var(--fm-tab-bar-font-size)}.fm-tab-bar--bottom-line{border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-tab-bar--bottom-line:after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-tab-bar__scroll-wrapper{position:relative;width:100%;overflow:hidden}.fm-tab-bar__list{display:flex;justify-content:space-between;min-width:100%;transition:all .3s cubic-bezier(.17,.89,.45,1)}.fm-tab-bar__ink{position:absolute;bottom:0;left:0;display:block;height:4px;background-color:var(--fm-tab-bar-active-color);transition:all var(--fm-duration-base);border-radius:2px}.fm-tab-bar__ink--disabled{background-color:var(--fm-disabled-color)}.fm-tabs{overflow:hidden}.fm-tabs__content{display:flex}.fm-tab{display:block;flex-shrink:0;width:100%;height:100%;overflow:auto;word-break:break-all}.fm-tab--collapse{height:0;overflow:visible}:root{--fm-tag-padding: 0 6px;--fm-tag-height: 18px;--fm-tag-font-size: 12px;--fm-tag-border-radius: 2px;--fm-tag-color: var(--fm-white)}.fm-tag{position:relative;display:inline-flex;align-items:center;white-space:nowrap;padding:0 4px;color:var(--fm-tag-color);font-size:var(--fm-tag-font-size);line-height:var(--fm-tag-height);border-radius:var(--fm-tag-border-radius)}.fm-tag--primary{background-color:var(--fm-primary-color)}.fm-tag--success{background-color:var(--fm-success-color)}.fm-tag--danger{background-color:var(--fm-danger-color)}.fm-tag--warning{background-color:var(--fm-warning-color)}.fm-tag--plain{background-color:var(--fm-white)}.fm-tag--plain.fm-tag--primary{border:1px solid var(--fm-primary-color);color:var(--fm-primary-color)}.fm-tag--plain.fm-tag--success{border:1px solid var(--fm-success-color);color:var(--fm-success-color)}.fm-tag--plain.fm-tag--warning{border:1px solid var(--fm-warning-color);color:var(--fm-warning-color)}.fm-tag--plain.fm-tag--danger{border:1px solid var(--fm-danger-color);color:var(--fm-danger-color)}.fm-tag--medium{padding:2px 6px}.fm-tag--large{padding:4px 8px;font-size:14px;border-radius:4px}.fm-tag--round{border-radius:100px}.fm-tag--mark{border-radius:0 100px 100px 0}.fm-tag__close{font-size:8px;margin-left:4px}.fm-tag--large .fm-tag__close{font-size:10px}:root{--fm-picker-background: var(--fm-white);--fm-picker-toolbar-height: 44px}.fm-picker-panel{position:relative;background-color:var(--fm-picker-background);user-select:none}.fm-picker-panel__toolbar{display:flex;justify-content:space-between;align-items:center;height:var(--fm-picker-toolbar-height)}.fm-picker-panel__toolbar-left,.fm-picker-panel__toolbar-right{padding:0 16px}.fm-picker-panel__toolbar-title{font-weight:500;font-size:16px;max-width:50%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-picker-panel__content{position:relative;display:flex;cursor:grab}.fm-picker-panel__columns{display:flex;flex:1;overflow:hidden}.fm-picker-panel__column{flex:1}.fm-picker-panel__column-item{display:flex;align-items:center;justify-content:center;height:44px}.fm-picker-panel__mask{position:absolute;top:0;left:0;z-index:1;width:100%;height:100%;background-image:linear-gradient(180deg,#ffffffe6,#fff6),linear-gradient(0deg,#ffffffe6,#fff6);background-repeat:no-repeat;background-position:top,bottom;transform:translateZ(0);pointer-events:none}.fm-picker-panel__frame{position:absolute;top:50%;left:16px;right:16px;height:88px;z-index:2;pointer-events:none;border-width:1px 0;border-style:solid;border-color:#ebedf0;transform:translateY(-50%) scaleY(.5)}.fm-picker-group__header{text-align:left}.fm-picker-group__toolbar{display:flex;justify-content:space-between;align-items:center;height:var(--fm-picker-toolbar-height)}.fm-picker-group__toolbar-left,.fm-picker-group__toolbar-right{padding:0 16px}.fm-picker-group__toolbar-title{font-weight:500;font-size:16px;max-width:50%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-picker-group__tab-bar{display:inline-block}:root{--fm-lookup-panel-background: var(--fm-background);--fm-lookup-panel-search-list-zindex: var(--fm-zindex-3)}.fm-lookup-panel{height:100%;display:flex;flex-direction:column;overflow:hidden;background-color:var(--fm-lookup-panel-background)}.fm-lookup-panel__header{background-color:var(--fm-background-white)}.fm-lookup-panel__header__search{border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-lookup-panel__header__search:after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-lookup-panel__header__selected-wrapper{display:flex;padding:6px 16px;overflow:auto}.fm-lookup-panel__header__selected-item{font-size:14px;line-height:20px;word-break:keep-all;white-space:nowrap;padding:4px 6px;color:var(--fm-primary-color);background-color:var(--fm-primary-color-light);border-radius:4px}.fm-lookup-panel__header__selected-item:not(:last-child){margin-right:8px}.fm-lookup-panel__footer{padding:0 16px;height:48px;display:flex;align-items:center;justify-content:space-between;box-shadow:0 0 7px 0 var(--fm-box-shadow-color);border-top:1px solid var(--fm-gray-1);background-color:var(--fm-background-white)}.fm-lookup-panel__content{flex:1;display:flex;overflow:hidden;margin-top:10px;position:relative}.fm-lookup-panel__list{flex:1;overflow-y:auto;background-color:var(--fm-background-white)}.fm-lookup-panel__list-item{display:flex;align-items:center;background-color:#fff}.fm-lookup-panel__list-checker{padding-left:16px}.fm-lookup-panel__search-list{position:absolute;inset:0;display:none;overflow-y:auto;z-index:var(--fm-lookup-panel-search-list-zindex);background-color:var(--fm-background-white)}.fm-lookup-panel__popup-selected{width:100%;display:flex;flex-direction:column}.fm-lookup-panel__popup-selected__footer-left{color:var(--fm-primary-color)}.fm-lookup-panel__selected-list{flex:1;overflow:auto}.fm-lookup-panel__empty{padding:12px 16px}.fm-lookup-panel__empty__text{text-align:center;color:var(--fm-text-color-light)}.fm-lookup-panel__breadcrumb{display:flex;align-items:center;height:42px;padding:0 16px;overflow:auto;border-bottom:1px solid var(--fm-border-color);position:relative;border-bottom:none}.fm-lookup-panel__breadcrumb:after{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-lookup-panel__breadcrumb__item{display:flex;align-items:center;color:var(--fm-primary-color);padding-right:8px;flex-shrink:0}.fm-lookup-panel__breadcrumb__item:last-child{color:var(--fm-text-color-light)}.fm-lookup-panel__breadcrumb__icon{font-size:10px;padding-left:4px}.fm-lookup-panel__avatar{display:flex;justify-content:center;flex-shrink:0;width:32px;height:32px;line-height:32px;margin-left:16px;font-size:12px;color:var(--fm-white);background:var(--fm-gradient-blue);border-radius:var(--fm-radius-max)}:root{--fm-search-radius: 17px;--fm-search-color: var(--fm-text-color);--fm-search-background: var(--fm-background-white);--fm-search-input-background: var(--fm-background)}.fm-search{display:flex;align-items:center;padding:8px 12px;color:var(--fm-search-color);background-color:var(--fm-search-background)}.fm-search__input-wrapper{flex:1;padding:5px 8px;border-radius:17px;background-color:var(--fm-search-input-background)}.fm-search__input{background-color:var(--fm-search-input-background)}.fm-search__search-icon{margin-right:4px;color:var(--fm-text-color-light)}.fm-filter-panel{height:100%;display:flex;flex-direction:column;padding:8px 16px}.fm-filter-panel__content{flex:1}.fm-filter-panel__footer{padding:8px 16px}.fm-filter-panel__field:not(:first-child){margin-top:16px}.fm-filter-panel__field__title{display:flex;align-items:center;justify-content:space-between;line-height:22px}.fm-filter-panel__field__content{padding-top:12px}.fm-filter-panel__field__range{display:flex;align-items:center}.fm-filter-panel__field__range__splitter{width:8px;margin:0 8px;border:1px solid #999}.fm-filter-panel__input{padding:6px 8px;line-height:18px;font-size:13px;color:var(--fm-primary-color);background:var(--fm-background);border-radius:var(--fm-radius-md);overflow:hidden}.fm-filter__portal{display:flex;align-items:center;padding:8px 16px}.fm-filter__portal__icon{font-size:20px}.fm-filter__portal__search{flex:1;padding:0 0 0 8px}.fm-page-container{flex:1;display:flex;flex-direction:column;background:#f9fafb;overflow:hidden;-webkit-overflow-scrolling:auto;position:absolute;inset:0}.fm-page-body-container{flex-basis:0;flex-shrink:1;flex-grow:1;padding-bottom:12px;overflow:auto;display:flex;flex-direction:column}.fm-page-header-container{flex-shrink:0}.fm-page-footer-container{background-color:#fff}.fm-float-container{position:absolute!important;bottom:60px;right:30px} +@charset "UTF-8";:root{--fm-black: #000;--fm-white: #fff;--fm-gray-1: #f2f3f5;--fm-gray-2: #eee;--fm-gray-3: #ddd;--fm-gray-4: #ccc;--fm-gray-5: #999;--fm-gray-6: #666;--fm-gray-7: #333;--fm-red: #F24645;--fm-red-light: #f9e8e8;--fm-blue: #3A90FF;--fm-blue-2: #65a7ff;--fm-blue-light: #ecf2fe;--fm-green: #5CC171;--fm-green-2: #5AC1C3;--fm-green-light: #e9f5ed;--fm-orange: #FF9800;--fm-orange-2: #FA6400;--fm-orange-3: #FFB400;--fm-orange-light: #faf0e1;--fm-primary-color: var(--fm-blue);--fm-success-color: var(--fm-green);--fm-danger-color: var(--fm-red);--fm-warning-color: var(--fm-orange);--fm-submit-color: var(--fm-green-2);--fm-primary-color-light: var(--fm-blue-light);--fm-success-color-light: var(--fm-green-light);--fm-danger-color-light: var(--fm-red-light);--fm-warning-color-light: var(--fm-orange-light);--fm-text-color: var(--fm-gray-7);--fm-text-color-light: var(--fm-gray-5);--fm-active-color: var(--fm-gray-1);--fm-disabled-color: var(--fm-gray-4);--fm-readonly-color: var(--fm-gray-6);--fm-active-opacity: .7;--fm-readonly-opacity: .6;--fm-disabled-opacity: .5;--fm-background: var(--fm-gray-1);--fm-background-white: var(--fm-white);--fm-box-shadow-color: var(--fm-gray-1);--fm-padding-base: 4px;--fm-padding-xs: 8px;--fm-padding-sm: 12px;--fm-padding-md: 16px;--fm-padding-lg: 24px;--fm-padding-xl: 32px;--fm-padding-horizontal-xs: 0 var(--fm-padding-xs);--fm-padding-horizontal-sm: 0 var(--fm-padding-sm);--fm-padding-horizontal-md: 0 var(--fm-padding-md);--fm-padding-horizontal-lg: 0 var(--fm-padding-lg);--fm-margin-base: 4px;--fm-margin-xs: 8px;--fm-margin-sm: 12px;--fm-margin-md: 16px;--fm-margin-lg: 24px;--fm-margin-xl: 32px;--fm-margin-horizontal-xs: 0 var(--fm-margin-xs);--fm-margin-horizontal-sm: 0 var(--fm-margin-sm);--fm-margin-horizontal-md: 0 var(--fm-margin-md);--fm-margin-horizontal-lg: 0 var(--fm-margin-lg);--fm-font-bold-light: 500;--fm-font-bold: 600;--fm-font-size: 16px;--fm-line-height: 1.2;--fm-line-height-lg: 1.5;--fm-font-size-xs: 10px;--fm-font-size-sm: 12px;--fm-font-size-md: 14px;--fm-font-size-lg: 16px;--fm-gradient-blue: linear-gradient(-45deg,var(--fm-blue-2) 0%, var(--fm-blue) 100%);--fm-gradient-orange: linear-gradient(-45deg,var(--fm-orange) 0%, var(--fm-orange-2) 100%);--fm-base-font: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Segoe UI, Arial, Roboto, "PingFang SC", "miui", "Hiragino Sans GB", "Microsoft Yahei", sans-serif;--fm-price-font: avenir-heavy, "PingFang SC", helvetica neue, arial, sans-serif;--fm-duration-base: .3s;--fm-duration-fast: .2s;--fm-ease-out: ease-out;--fm-ease-in: ease-in;--fm-noborder: none;--fm-radius-sm: 2px;--fm-radius-md: 4px;--fm-radius-lg: 8px;--fm-radius-max: 999px;--fm-zindex-1: 9;--fm-zindex-2: 10;--fm-zindex-3: 99;--fm-zindex-4: 100;--fm-zindex-5: 999;--fm-zindex-6: 1000}*,*:before,*:after{box-sizing:border-box}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}ul[role=list],ol[role=list]{list-style:none}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5;font-family:-apple-system,Noto Sans,Helvetica Neue,Helvetica,Nimbus Sans L,Arial,Liberation Sans,PingFang SC,Hiragino Sans GB,Noto Sans CJK SC,Source Han Sans SC,Source Han Sans CN,Microsoft YaHei,Wenquanyi Micro Hei,WenQuanYi Zen Hei,ST Heiti,SimHei,WenQuanYi Zen Hei Sharp,sans-serif}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@font-face{font-family:farrisMobile;font-style:normal;font-weight:400;src:url(data:font/ttf;base64,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AADmFAAAACUAAOYVAADmFQAAACYAAOYWAADmFgAAACcAAOYXAADmFwAAACgAAOYYAADmGAAAACkAAOYZAADmGQAAAHYAAOYaAADmGgAAAHUAAOYbAADmGwAAAHQAAOYcAADmHAAAAHMAAOYdAADmHQAAACoAAOYeAADmHgAAACsAAOYfAADmHwAAAHAAAOYgAADmIAAAAHEAAOYhAADmIQAAAHIAAOYiAADmIgAAAG8AAOYjAADmIwAAACwAAOYkAADmJAAAAGUAAOYlAADmJQAAAGYAAOYmAADmJgAAAGcAAOYnAADmJwAAAGgAAOYoAADmKAAAAGkAAOYpAADmKQAAAGoAAOYqAADmKgAAAGsAAOYrAADmKwAAAGwAAOYsAADmLAAAAG0AAOYtAADmLQAAAG4AAOYuAADmLgAAAGMAAOYvAADmLwAAAGQAAOYwAADmMAAAAGIAAOYyAADmMgAAADQAAOYzAADmMwAAAC0AAOY0AADmNAAAAC4AAOY2AADmNgAAAC8AAOY3AADmNwAAADAAAOY5AADmOQAAADEAAOY6AADmOgAAADIAAOZVAADmVQAAADUAAOZWAADmVgAAADoAAOZXAADmVwAAADcAAOZYAADmWAAAADgAAOZZAADmWQAAAD0AAOZaAADmWgAAAF4AAOZbAADmWwAAAF8AAOZcAADmXAAAAGAAAOZdAADmXQAAAGEAAOZeAADmXgAAADYAAOZfAADmXwAAADsAAOZgAADmYAAAADwAAOZhAADmYQAAADkAAOZiAADmYgAAAD4AAOZjAADmYwAAAD8AAOZkAADmZAAAAEAAAOZlAADmZQAAAEEAAOZmAADmZgAAAEIAAOZnAADmZwAAAEMAAOZoAADmaAAAAEQAAOZpAADmaQAAAEUAAOZqAADmagAAAEYAAOZrAADmawAAAEcAAOZsAADmbAAAAEgAAOZtAADmbQAAAEkAAOZuAADmbgAAAEoAAOZvAADmbwAAAEsAAOZwAADmcAAAAEwAAOZxAADmcQAAAE0AAOZyAADmcgAAAE4AAOZzAADmcwAAAE8AAOZ0AADmdAAAAFAAAOZ1AADmdQAAAFEAAOZ2AADmdgAAAFIAAOZ3AADmdwAAAFMAAOZ4AADmeAAAAFQAAOZ5AADmeQAAAFUAAOZ6AADmegAAAFYAAOZ7AADmewAAAFcAAOZ8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AV8BYAFhAWIBYwFkAWUBZgFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+AAlzLW1vcmUtbzEIcy1kZWxldGUIcy1iYWNrLW8Gcy1zY2FuEHMtb3JnYW5pemF0aW9uLW8Jcy1ncm91cC1vCXMtZmlsdGVyMQlzLWNsZWFyLW8Ocy1pbGx1c3RyYXRlLW8Icy1tb21lbnQIcy1zaG9wLW8Kcy1tb21lbnQtbwZzLXNob3AKcy1yZXZva2UtbwpzLXN1Ym1pdC1vB3MtYXBwLW8Gcy1ib29rBXMtYXBwBHMtbXkJcy1jb21tZW50C3MtY29tbWVudC1vCHMtYm9vay1vBnMtbXktbwxzLWxvY2F0aW9uLW8Kcy1sb2NhdGlvbgZzLW5ld3MIcy1uZXdzLW8Jcy1TZW5kaW5nCnMtSW1wb3J0LW8Icy1FZGl0LW8Icy1RUmNvZGULcy1CcmllZmNhc2URcy1Ob3RpZmljYXRpb25vZmYIcy1QZW5jaWwIcy1TdGFyLW8Jcy1CYXJDb2RlD3MtU2FsZU1lc3NhZ2UtbwxzLUxpc3R2aWV3LW8Lcy1QaWN0dXJlLW8Ocy1TYXZlRmlsbGV0LW8Ocy1TaG9wcGluZ2NhcnQJcy1QZW5kaW5nDnMtRXhwYW5kdmlldy1vCHMtTG9jay1vCXMtU2VydmljZQpzLVdhbGxldC1vEHMtU2hvcHBpbmdjYXJ0LW8Lcy1TZXR0aW5nLW8Ncy1VbmZvbGRSZWMtbwlzLVByb2ZpbGUGTGluay1vDVVzZXJTZXR0aW5nLW8Jcy1hZ3JlZS1vDHMtYXJyb3ctZG93bgdzLWFkZC1vB3MtYW5uZXgMcy1hcnJvdy1sZWZ0CnMtYXJyb3ctdXAScy1jaGVja2JveC1jaGVja2VkCnMtY2hlY2tib3gHcy1hcnJvdwpzLWV4Y2hhbmdlB3MtY2xlYXIJcy1jaGVja2VkB3MtZXllLW8Lcy1jbG9zZS1leWUEcy1hdAxzLWZlZWRiYWNrLW8Pcy1mb2xkLWNpcmNsZS1vB3MtY3Jvc3MHcy1taW51cwZzLWZpbGUPcy1mb2xkLXRyaWFuZ2xlCHMtZmlsdGVyCXMtZm9yd29yZAZzLXBsdXMJcy1maWxlZGVsCHMtZm9sZC1vCXMtaW1hZ2UtbwhzLW1vcmUtbw5zLXJlZGlvLWNpcmNsZQpzLXJlamVjdC1vCXMtc3VjY2VzcwlzLXJlZnJlc2gGcy1zdGFyDXMtcHJvY2Vzc2Zsb3cRcy11bmZvbGQtY2lyY2xlLW8Icy1yZXdpbmQKcy11bmZvbGQtbwtzLXdhcm5pbmctbxFzLXVuZm9sZC10cmlhbmdsZQlzLXByaW50LW8Kcy1zZWFyY2gtbwhBaXJwbGFuZQVUcmFpbgNCdXMEVGF4aQxvdmVydGltZW1lYWwTZm0taS10cmFmZmljZGV0YWlscw9mbS1pLXRyYWZmaWNzdWITZm0taS1hY2NvbW1vZGF0aW9uMQ1mbS1pLWJ1c2luZXNzC2ZtLWktYnVkZ2V0CWZtLWktZ2lmdBFmbS1pLWNhbmNlbGxhdGlvbgxmbS1pLWludm9pY2UJZm0taS1saXN0CmZtLWktbWVhbHMIZm0taS1wYXkMZm0taS1zdWJzaWR5B3Nob3V6aGUTZm0taS1jb3N0YWxsb2NhdGlvbgxmbS1pLXBheW1lbnQOZm0taS1JdGluZXJhcnkOZm0taS1tb3JleGUwMmQPZm0taS1pbWFnZXhlMDJlEGZtLWktcmVqZWN0eGUwMmYPZm0taS1hZ3JlZXhlMDMxDWZtLWktY2FyZGZvbGQPZm0taS1jYXJkdW5mb2xkEWZtLWktdHJhZmZpY290aGVyD2ZtLWktYm90dG9tc2F2ZRBmbS1pLXRyYWZmaWNib2F0D2ZtLWktdHJhZmZpY2NhchFmbS1pLXRyYWZmaWNwbGFuZRFmbS1pLXRyYWZmaWN0cmFpbg9mbS1pLXRyYWZmaWNidXMMZm0taS1hZGRncmF5CmZtLWktY2xvc2UIZm0taS1hZGQJZm0taS1tb3JlCmZtLWktY2hlY2sPZm0taS1iYWNrY2lyY2xlCmZtLWktYWdyZWUMZm0taS1sb2FkaW5nDGZtLWktZmlsZWRlbAlmbS1pLWZpbGUQZm0taS1wcm9jZXNzZmxvdwxmbS1pLXJlZHN0YXIQZm0taS1yaWdodGNpcmNsZQ9mbS1pLXRpbWVjaXJjbGUOZm0taS1zY3JlZW5pbmcMZm0taS11bmNoZWNrDmZtLWktb3BlcmF0aW9uE2ZtLWktYXBwcm92YWxhbW91bnQLZm0taS1hZ3JlZWQOZm0taS1pbnByb2Nlc3MNZm0taS1mZWVkYmFjawpmbS1pLXByaW50DmZtLWktaW1wb3J0YW50GmZtLWktYW1vdW50b2ZyZWltYnVyc2VtZW50FGZtLWktc2FmZXR5YXNzaXN0YW50C2ZtLWktcmVtaW5kDWZtLWktZXllb3BwZW4NZm0taS1leWVjbG9zZRBmbS1pLVBob3RvZ3JhcGh5D2ZtLWktcGhvdG9ncmFwaA9mbS1pLWFkZHBpY3R1cmUTZm0taS10b3Bfb3RoZXItZmFjZQ5mbS1pLXRvcF9vdGhlcgtmbS1pLWRlbGV0ZQ1mbS1pLW5leHRwYWdlDmZtLWktYWRkYnV0dG9uFmZtLWktd2FpdGZvcnByb2Nlc3NpbmcTZm0taS13YXJuaW5nbWVzc2FnZRJmbS1pLWZhaWx1cmVwcm9tcHQUZm0taS1zdWNjZXNzZnVsaGludHMKZm0taS1vdGhlcgxmbS1pLXJldHJhY3QOZm0taS1kcm9wLWRvd24LZm0taS1jYW5jZWwLZm0taS1zZWFyY2gTZm0taS1tdWx0aXBsZWNob2ljZRFmbS1pLXNpbmdsZWNob2ljZQ9mbS1pLW1pY3JvcGhvbmUXZm0taS1hcnJvdy1jaGV2cm9uLWxlZnQPZm0taS1oZWxwY2VudGVyDmZtLWktc3dlZXBjb2RlDmZtLWktZ3JvdXBjaGF0DmZtLWktYWRkZnJpZW5kC2ZtLWktdG9wX215EGZtLWktdG9wX215LWZhY2UPZm0taS10b3BfZnJpZW5kFGZtLWktdG9wX2ZyaWVuZC1mYWNlFWZtLWktdG9wX3B1YmxpY3ByYWlzZRpmbS1pLXRvcF9wdWJsaWNwcmFpc2UtZmFjZQ1mbS1pLXRvcF9ob21lEmZtLWktdG9wX2hvbWUtZmFjZQAAAAA=) format("truetype")}@keyframes fm-fade-in{0%{opacity:0}to{opacity:1}}@keyframes fm-fade-out{0%{opacity:1}to{opacity:0}}.fm-fade-enter-active{animation:.3s fm-fade-in both ease-out}.fm-fade-leave-active{animation:.3s fm-fade-out both ease-in}.fm-slide-up-enter-from,.fm-slide-up-leave-to{transform:translate3d(0,100%,0)}.fm-slide-down-enter-from,.fm-slide-down-leave-to{transform:translate3d(0,-100%,0)}.fm-slide-left-enter-from,.fm-slide-left-leave-to{transform:translate3d(-100%,0,0)}.fm-slide-right-enter-from,.fm-slide-right-leave-to{transform:translate3d(100%,0,0)}.fm-slide-up-enter-active,.fm-slide-down-enter-active,.fm-slide-left-enter-active,.fm-slide-right-enter-active{transition-timing-function:ease-out}.fm-slide-up-leave-active,.fm-slide-down-leave-active,.fm-slide-left-leave-active,.fm-slide-right-leave-active{transition-timing-function:ease-in}.fm-slide-in-enter-active,.fm-slide-in-leave-active,.fm-slide-out-enter-active,.fm-slide-out-leave-active{will-change:transform;transition:all .3s;height:100%;width:100%;top:0;position:absolute;backface-visibility:hidden;perspective:1000}.fm-slide-in-leave-to,.fm-slide-out-enter-from{transform:translate(-100%);opacity:0}.fm-slide-in-enter-from,.fm-slide-out-leave-to{transform:translate(100%);opacity:0}@keyframes fm-drop-down-in{0%{height:0;opacity:0}to{opacity:1}}@keyframes fm-drop-down-out{0%{opacity:1}to{height:0;opacity:0}}.fm-drop-down-enter-active{animation:.3s fm-drop-down-in both ease-out}.fm-drop-down-leave-active{animation:.3s fm-drop-down-out both ease-in}@keyframes fm-rotate{0%{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}:root{--fm-button-height: 42px;--fm-button-padding: 0 14px;--fm-button-radius: 2px;--fm-button-border-width: 1px;--fm-button-color: var(--fm-white);--fm-button-line-height: var(--fm-line-height);--fm-button-font-size: var(--fm-font-size);--fm-button-plain-background: var(--fm-white);--fm-button-lg-height: 46px;--fm-button-lg-font-size: 18px;--fm-button-md-height: 38px;--fm-button-md-font-size: 14px;--fm-button-sm-height: 34px;--fm-button-sm-padding: 0 8px;--fm-button-sm-font-size: 12px;--fm-button-xs-height: 28px;--fm-button-xs-font-size: 10px;--fm-button-secondary-color: var(--fm-blue-light)}.fm-button{position:relative;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;text-align:center;margin:0;border:none;transition:opacity .2s;color:var(--fm-button-color);height:var(--fm-button-height);padding:var(--fm-button-padding);font-size:var(--fm-button-font-size);line-height:var(--fm-button-line-height);border-radius:var(--fm-button-radius)}.fm-button--primary,.fm-button--info{background:var(--fm-primary-color)}.fm-button--secondary{color:var(--fm-primary-color);background:var(--fm-button-secondary-color)}.fm-button--danger{background-color:var(--fm-danger-color)}.fm-button--warning{background-color:var(--fm-warning-color)}.fm-button--success{background-color:var(--fm-success-color)}.fm-button--plain{box-shadow:none;background:var(--fm-button-plain-background)}.fm-button--plain.fm-button--primary,.fm-button--plain.fm-button--info{color:var(--fm-primary-color);border:var(--fm-button-border-width) solid var(--fm-primary-color)}.fm-button--plain.fm-button--success{color:var(--fm-success-color);border:var(--fm-button-border-width) solid var(--fm-success-color)}.fm-button--plain.fm-button--danger{color:var(--fm-danger-color);border:var(--fm-button-border-width) solid var(--fm-danger-color)}.fm-button--plain.fm-button--warning{color:var(--fm-button-warning-color);border:var(--fm-button-border-width) solid var(--fm-button-warning-color)}.fm-button--noborder{border:none!important}.fm-button--large{height:var(--fm-button-lg-height);font-size:var(--fm-button-lg-font-size)}.fm-button--normal{height:var(--fm-button-md-height);font-size:var(--fm-button-md-font-size)}.fm-button--small{height:var(--fm-button-sm-height);padding:var(--fm-button-sm-padding);font-size:var(--fm-button-sm-font-size)}.fm-button--mini{height:var(--fm-button-xs-height);padding:var(--fm-button-sm-padding);font-size:var(--fm-button-xs-font-size)}.fm-button--block{display:flex;width:100%}.fm-button--disabled{cursor:not-allowed;opacity:var(--fm-disabled-opacity)}.fm-button--loading:before,.fm-button--disabled:before{display:none}.fm-button--round{border-radius:var(--fm-radius-max)}.fm-button--square{border-radius:0}.fm-button__loading{display:inline-block;width:20px;max-width:100%;height:20px;max-height:100%;vertical-align:middle;animation:fm-rotate 2s linear infinite}.fm-button__loading circle{animation:fm-circular 1.5s ease-in-out infinite;stroke:currentColor;stroke-width:4;stroke-linecap:round}.fm-button__loading-icon-circular{color:#fff}.fm-button__loading-text{margin-left:6px}.fm-button__icon:not(:last-child){margin-right:6px}.fm-button:before{position:absolute;top:50%;left:50%;width:100%;height:100%;background-color:#000;border:inherit;border-color:#000;border-radius:inherit;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);opacity:0;content:" "}.fm-button:active:before{opacity:.1}.fm-button-edit__tags{min-height:24px;flex:1;display:flex;overflow:auto}.fm-button-edit__tag:not(:last-child){margin-right:6px}:root{--fm-button-group-gap: 16px}.fm-button-group{display:inline-flex;flex-wrap:nowrap;max-width:100%}.fm-button-group+.fm-button-group{margin-left:var(--fm-button-group-gap)}.fm-button-group__item{margin:0;min-width:0;max-width:100%;white-space:nowrap;flex:0 1 auto;user-select:none}.fm-button-group__item--bare-vertical{display:inline-flex;flex-direction:column;align-items:center;justify-content:space-between;color:#666}.fm-button-group__item--bare-vertical .fm-button-group__item-icon{font-size:20px}.fm-button-group__item--bare-vertical .fm-button-group__item-text{font-size:10px;line-height:16px}.fm-button-group__item--bare-vertical.fm-button-group__item--disabled{cursor:not-allowed;opacity:.5}.fm-button-group--block{display:flex}.fm-button-group--vertical{flex-direction:column}.fm-button-group--block .fm-button-group__item{width:auto;flex:10000 1 0%}.fm-button-group--horizontal.fm-button-group--mode-default .fm-button-group__item+.fm-button-group__item{margin-left:var(--fm-button-group-gap)}.fm-button-group--vertical.fm-button-group--mode-default .fm-button-group__item+.fm-button-group__item{margin-top:var(--fm-button-group-gap)}.fm-button-group--horizontal:not(.fm-button-group--mode-default) .fm-button-group__item:not(:first-child):not(:last-child),.fm-button-group--vertical:not(.fm-button-group--mode-default) .fm-button-group__item:not(:first-child):not(:last-child){border-radius:0}.fm-button-group--horizontal:not(.fm-button-group--mode-default) .fm-button-group__item:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.fm-button-group--vertical:not(.fm-button-group--mode-default) .fm-button-group__item:first-child{border-bottom-left-radius:0;border-bottom-right-radius:0}.fm-button-group--horizontal:not(.fm-button-group--mode-default) .fm-button-group__item:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.fm-button-group--vertical:not(.fm-button-group--mode-default) .fm-button-group__item:last-child{border-top-left-radius:0;border-top-right-radius:0}.fm-button-group--horizontal.fm-button-group--mode-outline .fm-button-group__item:not(:first-child){border-left:0}.fm-button-group--vertical.fm-button-group--mode-outline .fm-button-group__item:not(:first-child){border-top:0}.fm-button-group--horizontal.fm-button-group--mode-text .fm-button-group__item:not(:last-child):after,.fm-button-group--vertical.fm-button-group--mode-text .fm-button-group__item:not(:last-child):after{content:"";display:block;position:absolute;background-color:var(--fm-gray-3)}.fm-button-group--horizontal.fm-button-group--mode-text .fm-button-group__item:not(:last-child):after{width:1px;right:0;top:var(--fm-padding-xs);bottom:var(--fm-padding-xs)}.fm-button-group--vertical.fm-button-group--mode-text .fm-button-group__item:not(:last-child):after{height:1px;bottom:0;left:var(--fm-padding-xs);right:var(--fm-padding-xs)}.fm-button-group--fill{flex:1}:root{--fm-card-background: var(--fm-white);--fm-card-title-color: var(--fm-gray-7)}.fm-card{background-color:var(--fm-card-background)}.fm-card__header{display:flex;justify-content:space-between;align-items:center;padding:11px 16px 10px;border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-card__header:after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-card__title{position:relative;padding-left:var(--fm-padding-md);color:var(--fm-card-title-color);font-size:var(--fm-font-size-lg);line-height:var(--fm-line-height-lg);font-weight:var(--fm-font-bold-light);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-card__title:before{content:"";position:absolute;left:0;top:50%;width:3px;height:14px;margin:-7px 0 0;background:var(--fm-primary-color)}.fm-card__footer{border-top:1px solid #ddd;position:relative;border-top:none}.fm-card__footer:before{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:0 auto auto 0;width:100%;height:1px;transform-origin:50% 50%;transform:scaleY(.5)}:root{--fm-cell-padding: 10px 16px;--fm-cell-margin: 5px;--fm-cell-line-height: 24px;--fm-cell-font-size: var(--fm-font-size);--fm-cell-background: var(--fm-background-white);--fm-cell-color: var(--fm-text-color);--fm-cell-label-font-size: 12px;--fm-cell-label-line-height: 18px;--fm-cell-label-color: var(--fm-text-color-light);--fm-cell-required-color: var(--fm-danger-color)}.fm-cell{width:100%;padding:var(--fm-cell-padding);background-color:var(--fm-cell-background);color:var(--fm-cell-color);font-size:var(--fm-cell-font-size);line-height:var(--fm-cell-line-height);position:relative;display:flex;overflow:hidden}.fm-cell--bottom-border,.fm-cell:not(:last-child){border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-cell--bottom-border:after,.fm-cell:not(:last-child):after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-cell--bottom-border:after,.fm-cell:not(:last-child):after{left:16px!important}.fm-cell.fm-cell-all-width:not(:last-child):after{left:0!important}.fm-cell--clickable{cursor:pointer}.fm-cell--clickable:active{background-color:var(--fm-active-color)}.fm-cell--required .fm-cell-title{position:relative}.fm-cell--required .fm-cell-title .fm-cell-title-text:after{padding-left:2px;color:var(--fm-cell-required-color);font-size:12px;content:"*"}.fm-cell__title,.fm-cell__value{flex:1;font-size:inherit}.fm-cell__title{display:inline-block;overflow:hidden}.fm-cell__title-text{display:flex;word-break:break-all}.fm-cell__title-text span{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-cell__right{display:flex;overflow:hidden;flex-direction:column}.fm-cell__right-content{display:flex;flex:1}.fm-cell__right--fill{flex:1}.fm-cell__value{position:relative;overflow:hidden;text-align:right;vertical-align:middle;word-wrap:break-word}.fm-cell__value--alone{text-align:left}.fm-cell__label{margin-top:var(--fm-cell-margin);color:var(--fm-cell-label-color);font-size:var(--fm-cell-label-font-size);line-height:var(--fm-cell-label-line-height)}.fm-cell--center{align-items:center}.fm-cell__extra,.fm-cell__left-icon,.fm-cell__right-icon{min-width:1em;font-size:var(--fm-cell-font-size);line-height:var(--fm-cell-line-height);display:flex;align-items:center}.fm-cell__left-icon{margin-right:var(--fm-cell-margin)}.fm-cell__extra,.fm-cell__right-icon{margin-left:var(--fm-cell-margin)}.fm-cell--noborder .fm-cell:after{display:none!important}.fm-cell--card{padding-left:0;padding-right:0}.fm-checkbox-group{display:flex;overflow:visible;text-align:left;flex-wrap:wrap}.fm-checkbox-group--vertical{flex-direction:column}.fm-checkbox-group--vertical .fm-checkbox:not(:last-child){margin-bottom:8px}.fm-checkbox{margin-top:4px;margin-bottom:4px}:root{--fm-checker-color: var(--fm-text-color);--fm-checker-font-size: var(--fm-font-size);--fm-checker-icon-color: var(--fm-text-color-light);--fm-checker-icon-background: var(--fm-background-white);--fm-checker-icon-radius: var(--fm-radius-md);--fm-checker-checked-icon-color: var(--fm-primary-color);--fm-checker-button-background: var(--fm-gray-2);--fm-checker-disabled-color: var(--fm-disabled-color);--fm-checker-disabled-icon-color: var(--fm-gray-1)}.fm-checker{display:flex;align-items:center;color:var(--fm-checker-color);font-size:var(--fm-checker-font-size)}.fm-checker__icon{display:inline-block;align-items:center}.fm-checker__icon .fm-icon{display:block;height:20px;width:20px;line-height:18px;font-size:14px;text-align:center;border-radius:var(--fm-checker-icon-radius);border:1px solid var(--fm-checker-icon-color);color:var(--fm-checker-icon-background);background-color:var(--fm-checker-icon-background)}.fm-checker__label{margin-left:8px;margin-right:8px;display:inline-block;line-height:20px}.fm-checker:first-child .fm-checker_button{margin-left:0}.fm-checker--round .fm-checker__icon .fm-icon{border-radius:100%}.fm-checker--button .fm-checker__label{font-size:13px;text-align:center;border-radius:14px;line-height:28px;padding:0 12px;min-width:60px;margin-right:0;color:var(--fm-checker-color);background:var(--fm-checker-button-background)}.fm-checker--checked .fm-checker__icon .fm-icon{color:var(--fm-checker-icon-background);border-color:var(--fm-checker-checked-icon-color);background-color:var(--fm-checker-checked-icon-color)}.fm-checker--checked.fm-checker--button .fm-checker__label{color:var(--fm-checker-icon-background);border-color:var(--fm-checker-color);background-color:var(--fm-checker-checked-icon-color)}.fm-checker--readonly{opacity:var(--fm-readonly-opacity)}.fm-checker--disabled{color:var(--fm-checker-disabled-color)}.fm-checker--disabled .fm-checker__icon .fm-icon{border-color:var(--fm-checker-disabled-color);background-color:var(--fm-checker-disabled-icon-color);color:var(--fm-checker-disabled-icon-color)}.fm-checker--disabled.fm-checker--checked .fm-checker__icon .fm-icon{background-color:var(--fm-checker-disabled-color);border-color:var(--fm-checker-disabled-color)}:root{--fm-icon-font-size: 14px;--fm-icon-color: inherit}.fm-icon{font-family:farrisMobile!important;font-size:var(--fm-icon-font-size);font-style:normal;color:var(--fm-icon-color);display:inline-block;width:1em;height:1em;font-weight:400;line-height:1;vertical-align:middle;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fm-icon:before{display:inline-block}.fm-icon-filter:before{content:"\e697"}.fm-icon-cardfold:before{content:"\e6b7"}.fm-icon-cardunfold:before{content:"\e6b8"}.fm-icon-trafficother:before{content:"\e6b9"}.fm-icon-bottomsave:before{content:"\e6ba"}.fm-icon-trafficboat:before{content:"\e6bb"}.fm-icon-trafficcar:before{content:"\e6bc"}.fm-icon-trafficplane:before{content:"\e6bd"}.fm-icon-traffictrain:before{content:"\e6be"}.fm-icon-trafficbus:before{content:"\e6bf"}.fm-icon-addgray:before{content:"\e6a7"}.fm-icon-close:before{content:"\e6a8"}.fm-icon-add-line:before{content:"\e6a9"}.fm-icon-more:before{content:"\e6aa"}.fm-icon-check:before{content:"\e6ab"}.fm-icon-backcircle:before{content:"\e6ac"}.fm-icon-agree:before{content:"\e6ad"}.fm-icon-loading:before{content:"\e6ae"}.fm-icon-filedel:before{content:"\e6af"}.fm-icon-file:before{content:"\e6b0"}.fm-icon-processflow:before{content:"\e6b1"}.fm-icon-redstar:before{content:"\e6b2"}.fm-icon-rightcircle:before{content:"\e6b3"}.fm-icon-timecircle:before{content:"\e6b4"}.fm-icon-screening:before{content:"\e6b5"}.fm-icon-uncheck:before{content:"\e6b6"}.fm-icon-operation:before{content:"\e02c"}.fm-icon-approvalamount:before{content:"\e02b"}.fm-icon-agreed:before{content:"\e02a"}.fm-icon-inprocess:before{content:"\e029"}.fm-icon-feedback:before{content:"\e028"}.fm-icon-print:before{content:"\e027"}.fm-icon-important:before{content:"\e026"}.fm-icon-amountofreimbursement:before{content:"\e025"}.fm-icon-safetyassistant:before{content:"\e024"}.fm-icon-remind:before{content:"\e023"}.fm-icon-eyeoppen:before{content:"\e022"}.fm-icon-eyeclose:before{content:"\e01e"}.fm-icon-photography:before{content:"\e021"}.fm-icon-photograph:before{content:"\e020"}.fm-icon-addpicture:before{content:"\e01f"}.fm-icon-top_other-face:before{content:"\e01d"}.fm-icon-top_other:before{content:"\e01c"}.fm-icon-delete:before{content:"\e01b"}.fm-icon-nextpage:before{content:"\e01a"}.fm-icon-addbutton:before{content:"\e019"}.fm-icon-waitforprocessing:before{content:"\e018"}.fm-icon-warningmessage:before{content:"\e017"}.fm-icon-failureprompt:before{content:"\e016"}.fm-icon-successfulhints:before{content:"\e015"}.fm-icon-other:before{content:"\e014"}.fm-icon-retract:before{content:"\e013"}.fm-icon-drop-down:before{content:"\e012"}.fm-icon-cancel:before{content:"\e011"}.fm-icon-search:before{content:"\e010"}.fm-icon-multiplechoice:before{content:"\e00f"}.fm-icon-singlechoice:before{content:"\e00e"}.fm-icon-microphone:before{content:"\e00d"}.fm-icon-arrow-chevron-left:before{content:"\e00c"}.fm-icon-helpcenter:before{content:"\e00b"}.fm-icon-sweepcode:before{content:"\e00a"}.fm-icon-groupchat:before{content:"\e009"}.fm-icon-addfriend:before{content:"\e008"}.fm-icon-top_my:before{content:"\e007"}.fm-icon-top_my-face:before{content:"\e006"}.fm-icon-top_friend:before{content:"\e005"}.fm-icon-top_friend-face:before{content:"\e004"}.fm-icon-top_publicpraise:before{content:"\e003"}.fm-icon-top_publicpraise-face:before{content:"\e002"}.fm-icon-top_home:before{content:"\e001"}.fm-icon-top_home-face:before{content:"\e000"}.fm-icon-s-agree-o:before{content:"\e655"}.fm-icon-s-arrow-down:before{content:"\e656"}.fm-icon-s-add-o:before{content:"\e657"}.fm-icon-s-annex:before{content:"\e658"}.fm-icon-s-arrow:before{content:"\e659"}.fm-icon-s-arrow-up:before{content:"\e65e"}.fm-icon-s-checkbox-checked:before{content:"\e65f"}.fm-icon-s-checkbox:before{content:"\e660"}.fm-icon-s-arrow-left:before{content:"\e661"}.fm-icon-s-exchange:before{content:"\e662"}.fm-icon-s-clear:before{content:"\e663"}.fm-icon-s-checked:before{content:"\e664"}.fm-icon-s-eye-o:before{content:"\e665"}.fm-icon-s-close-eye:before{content:"\e666"}.fm-icon-s-at:before{content:"\e667"}.fm-icon-s-feedback-o:before{content:"\e668"}.fm-icon-s-fold-circle-o:before{content:"\e669"}.fm-icon-s-cross:before{content:"\e66a"}.fm-icon-s-minus:before{content:"\e66b"}.fm-icon-s-file:before{content:"\e66c"}.fm-icon-s-fold-triangle:before{content:"\e66d"}.fm-icon-s-filter:before{content:"\e66e"}.fm-icon-s-forword:before{content:"\e66f"}.fm-icon-s-plus:before{content:"\e670"}.fm-icon-s-filedel:before{content:"\e671"}.fm-icon-s-fold-o:before{content:"\e672"}.fm-icon-s-image-o:before{content:"\e673"}.fm-icon-s-more-o:before{content:"\e674"}.fm-icon-s-redio-circle:before{content:"\e675"}.fm-icon-s-reject-o:before{content:"\e676"}.fm-icon-s-success:before{content:"\e677"}.fm-icon-s-refresh:before{content:"\e678"}.fm-icon-s-star:before{content:"\e679"}.fm-icon-s-processflow:before{content:"\e67a"}.fm-icon-s-unfold-circle-o:before{content:"\e67b"}.fm-icon-s-rewind:before{content:"\e67c"}.fm-icon-s-unfold-o:before{content:"\e67d"}.fm-icon-s-save-o:before{content:"\e67e"}.fm-icon-s-warning-o:before{content:"\e67f"}.fm-icon-s-unfold-triangle:before{content:"\e680"}.fm-icon-s-print-o:before{content:"\e681"}.fm-icon-s-search-o:before{content:"\e682"}.fm-icon-s-news:before{content:"\e683"}.fm-icon-s-news-o:before{content:"\e684"}.fm-icon-s-shop:before{content:"\e68c"}.fm-icon-s-sending:before{content:"\e60b"}.fm-icon-s-import-o:before{content:"\e60c"}.fm-icon-s-edit-o:before{content:"\e60d"}.fm-icon-s-qrcode:before{content:"\e60e"}.fm-icon-s-briefcase:before{content:"\e60f"}.fm-icon-s-notificationoff:before{content:"\e610"}.fm-icon-s-pencil:before{content:"\e611"}.fm-icon-s-star-o:before{content:"\e612"}.fm-icon-s-barcode:before{content:"\e613"}.fm-icon-s-salemessage-o:before{content:"\e614"}.fm-icon-s-listview-o:before{content:"\e615"}.fm-icon-s-picture-o:before{content:"\e616"}.fm-icon-s-savefillet-o:before{content:"\e617"}.fm-icon-s-shoppingcart:before{content:"\e618"}.fm-icon-s-pending:before{content:"\e61d"}.fm-icon-s-expandview-o:before{content:"\e61e"}.fm-icon-s-lock-o:before{content:"\e623"}.fm-icon-s-my:before{content:"\e692"}.fm-icon-s-service:before{content:"\e633"}.fm-icon-s-wallet-o:before{content:"\e634"}.fm-icon-s-shoppingcart-o:before{content:"\e636"}.fm-icon-s-setting-o:before{content:"\e637"}.fm-icon-s-my-o:before{content:"\e696"}.fm-icon-s-unfoldrec-o:before{content:"\e639"}.fm-icon-s-profile:before{content:"\e63a"}.fm-icon-s-link-o:before{content:"\e609"}.fm-icon-s-usersetting-o:before{content:"\e632"}.fm-icon-s-shop-o:before{content:"\e68a"}.fm-icon-s-list:before{content:"\e62a"}.fm-icon-s-location-o:before{content:"\e685"}.fm-icon-s-location:before{content:"\e686"}.fm-icon-s-moment:before{content:"\e689"}.fm-icon-s-moment-o:before{content:"\e68b"}.fm-icon-s-revoke-o:before{content:"\e687"}.fm-icon-s-submit-o:before{content:"\e688"}.fm-icon-s-app-o:before{content:"\e68f"}.fm-icon-s-app:before{content:"\e691"}.fm-icon-s-book-o:before{content:"\e695"}.fm-icon-s-book:before{content:"\e690"}.fm-icon-s-comment:before{content:"\e693"}.fm-icon-s-comment-o:before{content:"\e694"}.fm-icon-s-illustrate-o:before{content:"\e68d"}.fm-icon-s-clear-o:before{content:"\e68e"}.fm-icon-friend:before{content:"\e005"}.fm-icon-scan:before{content:"\e69a"}.fm-icon-back:before{content:"\e69b"}.fm-icon-menu:before{content:"\e69d"}:root{--fm-navbar-background: var(--fm-background-white);--fm-navbar-color: var(--fm-text-color);--fm-navbar-height: 44px;--fm-navbar-title-size: 17px;--fm-navbar-side-size: 16px;--fm-navbar-arrow-size: 16px;--fm-navbar-toolbar-item-height: 46px}.fm-navbar{position:relative;z-index:9;display:flex;align-items:center;line-height:1.5;text-align:center;user-select:none;height:var(--fm-navbar-height);color:var(--fm-navbar-color);background-color:var(--fm-navbar-background)}.fm-navbar--fixed{position:fixed;top:0;left:0;width:100%}.fm-navbar--bottom-border{border-bottom:1px solid #e6e6e6;position:relative;border-bottom:none}.fm-navbar--bottom-border:after{content:"";position:absolute;background-color:#e6e6e6;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-navbar__title{max-width:60%;margin:0 auto;font-weight:500;font-size:var(--fm-navbar-title-size);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-navbar__left,.fm-navbar__right{position:absolute;top:0;bottom:0;display:flex;align-items:center;max-width:30%;padding:0 16px;font-size:var(--fm-navbar-side-size);overflow:hidden;white-space:nowrap;text-overflow:ellipsis;cursor:pointer}.fm-navbar__left{left:0}.fm-navbar__left:active{opacity:var(--fm-active-opacity)}.fm-navbar__left.fm-navbar-left-padding{padding-left:14px}.fm-navbar__right{right:0}.fm-navbar__right .fm-navbar-text:active{opacity:var(--fm-active-opacity)}.fm-navbar__left-arrow{min-width:1em;margin-right:4px;font-size:var(--fm-navbar-arrow-size)}.fm-navbar__toolbar{padding:0!important}.fm-navbar__toolbar-item{padding:0 var(--fm-padding-sm);height:var(--fm-navbar-toolbar-item-height);line-height:var(--fm-navbar-toolbar-item-height);user-select:none;cursor:pointer}.fm-navbar__toolbar-item:not(:last-child){border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-navbar__toolbar-item:not(:last-child):after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-navbar__toolbar-item--disabled{opacity:var(--fm-disabled-opacity);cursor:not-allowed}:root{--fm-input-group-size: var(--fm-font-size);--fm-input-group-color: var(--fm-text-color);--fm-input-group-sub-size: 13px;--fm-input-group-padding: 10px 16px;--fm-input-group-sub-color: var(--fm-text-color-light);--fm-input-group-border-color: var(--fm-gray-2)}.fm-input-group{display:flex;flex-direction:column;background-color:var(--fm-white)}.fm-input-group--padding{padding:var(--fm-input-group-padding)}.fm-input-group__body{flex:1;display:flex;align-items:center;min-height:24px;color:var(--fm-input-group-color);font-size:var(--fm-input-group-size)}.fm-input-group__body--border{border:1px solid var(--fm-input-group-border-color);padding:4px 8px;border-radius:4px}.fm-input-group__control{display:flex;width:100%;min-width:0;margin:0;padding:0;border:0;resize:none;color:inherit;font-size:inherit;outline:none;line-height:inherit;background-color:transparent}.fm-input-group__control::placeholder{color:var(--fm-input-group-sub-color)}.fm-input-group__control:disabled{opacity:1;cursor:not-allowed;color:var(--fm-disabled-color);-webkit-text-fill-color:currentColor}.fm-input-group__control::-webkit-search-cancel-button{display:none}.fm-input-group__control--readonly{cursor:default;color:var(--fm-readonly-color)}.fm-input-group__control--left{justify-content:flex-start;text-align:left}.fm-input-group__control--center{justify-content:center;text-align:center}.fm-input-group__control--right{justify-content:flex-end;text-align:right}.fm-input-group__clear{cursor:pointer;margin-right:-8px;padding:4px 8px;color:var(--fm-input-group-sub-color);flex-shrink:0;box-sizing:content-box}.fm-input-group__left-icon{margin:auto 4px auto 0}.fm-input-group__right-icon{margin:auto 0 auto 4px}.fm-input-group__word-limit{margin-top:4px;line-height:18px;text-align:right;font-size:var(--fm-input-group-sub-size);color:var(--fm-input-group-sub-color)}.fm-radio-group{display:flex;overflow:visible;text-align:left;flex-wrap:wrap}.fm-radio-group--vertical{flex-direction:column}.fm-radio-group--vertical .fm-radio:not(:last-child){margin-bottom:8px}.fm-radio{margin-top:4px;margin-bottom:4px}:root,:host{--fm-rate-icon-size: 20px;--fm-rate-icon-gutter: var(--fm-padding-base);--fm-rate-icon-void-color: var(--fm-gray-4);--fm-rate-icon-full-color: var(--fm-orange-3);--fm-rate-icon-disabled-color: var(--fm-gray-4)}.fm-rate{display:inline-flex;cursor:pointer;user-select:none;flex-wrap:wrap}.fm-rate__item{position:relative}.fm-rate__item:not(:last-child){padding-right:var(--fm-rate-icon-gutter)}.fm-rate__icon{display:block;width:1em;color:var(--fm-rate-icon-void-color);font-size:var(--fm-rate-icon-size)}.fm-rate__icon--half{position:absolute;top:0;left:0;overflow:hidden;pointer-events:none;color:var(--fm-rate-icon-full-color)}.fm-rate__icon--full{color:var(--fm-rate-icon-full-color)}.fm-rate__icon--disabled{color:var(--fm-rate-icon-disabled-color)}.fm-rate--disabled{cursor:not-allowed}.fm-rate--readonly{cursor:default}:root{--fm-form-item-size: var(--fm-font-size);--fm-form-item-color: var(--fm-text-color);--fm-form-item-label-width: 105px;--fm-form-item-sub-size: 13px}.fm-form-item{font-size:var(--fm-form-item-size);color:var(--fm-form-item-color)}.fm-form-item--vertical{flex-direction:column}.fm-form-item--vertical .fm-form-item__label{margin-bottom:4px}.fm-form-item__label{display:flex;flex:none;align-items:flex-start;text-align:left;width:var(--fm-form-item-label-width);margin-right:4px}.fm-form-item__label--left{justify-content:flex-start}.fm-form-item__label--center{justify-content:center}.fm-form-item__label--right{justify-content:flex-end}.fm-form-item__label--required{position:relative}.fm-form-item__label--required .fm-cell__title-text:after{padding-left:2px;color:var(--fm-cell-required-color);font-size:12px;content:"*"}.fm-form-item__content{display:flex}.fm-form-item__content .fm-input-group{padding:0;flex:1}.fm-form-item__content--left{justify-content:flex-start}.fm-form-item__content--left input.fm-input-group__control{justify-content:flex-start;text-align:left}.fm-form-item__content--center{justify-content:center}.fm-form-item__content--center input.fm-input-group__control{justify-content:flex-center;text-align:center}.fm-form-item__content--right{justify-content:flex-end}.fm-form-item__content--right input.fm-input-group__control{justify-content:flex-end;text-align:right}.fm-form-item__error-message{color:var(--fm-danger-color);font-size:var(--fm-form-item-sub-size)}.fm-form-item__error-message--left{text-align:left}.fm-form-item__error-message--center{text-align:center}.fm-form-item__error-message--right{text-align:right}:root{--fm-overlay-background: rgba(0, 0, 0, .4);--fm-overlay-zindex: 98}.fm-overlay{width:100%;position:fixed;top:0;left:0;z-index:var(--fm-overlay-zindex);background-color:var(--fm-overlay-background);bottom:0}@supports ((bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom))) and (-webkit-overflow-scrolling: touch){.fm-overlay{bottom:constant(safe-area-inset-bottom);bottom:env(safe-area-inset-bottom)}}:root{--fm-popup-background: var(--fm-white);--fm-popup-zindex: var(--fm-zindex-3);--fm-popup-radius: 16px}.fm-popup{position:fixed;max-height:100%;overflow-y:auto;background-color:var(--fm-popup-background);transition:all var(--fm-duration-base);z-index:var(--fm-popup-zindex)}.fm-popup--center{top:50%;left:50%;transform:translate(-50%,-50%)}.fm-popup--center.fm-popup--round{border-radius:var(--fm-popup-radius)}.fm-popup--top,.fm-popup--bottom{left:0;right:0}.fm-popup--top{top:0}.fm-popup--top.fm-popup--round{border-radius:0 0 var(--fm-popup-radius) var(--fm-popup-radius)}.fm-popup--bottom{bottom:0}@supports ((bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom))) and (-webkit-overflow-scrolling: touch){.fm-popup--bottom{bottom:constant(safe-area-inset-bottom);bottom:env(safe-area-inset-bottom)}}.fm-popup--bottom.fm-popup--round{border-radius:var(--fm-popup-radius) var(--fm-popup-radius) 0 0}.fm-popup--left,.fm-popup--right{top:0;bottom:0}@supports ((bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom))) and (-webkit-overflow-scrolling: touch){.fm-popup--left,.fm-popup--right{bottom:constant(safe-area-inset-bottom);bottom:env(safe-area-inset-bottom)}}.fm-popup--left{left:0}.fm-popup--left.fm-popup--round{border-radius:0 var(--fm-popup-radius) var(--fm-popup-radius) 0}.fm-popup--right{right:0}.fm-popup--right.fm-popup--round{border-radius:var(--fm-popup-radius) 0 0 var(--fm-popup-radius)}:root{--fm-popover-zindex: var(--fm-zindex-5);--fm-popover-padding: var(--fm-padding-sm);--fm-popover-border-radius: var(--fm-radius-lg);--fm-popover-arrow-size: 8px;--fm-popover-content-font-size: var(--fm-font-size);--fm-popover-content-line-height: var(--fm-line-height);--fm-popover-light-theme-color: var(--fm-gray-7);--fm-popover-light-theme-background: var(--fm-white);--fm-popover-dark-theme-color: var(--fm-white);--fm-popover-dark-theme-background: var(--fm-gray-7);--fm-popover-shadow: 0 6px 30px 5px rgba(0, 0, 0, .05), 0 16px 24px 2px rgba(0, 0, 0, .04), 0 8px 10px -5px rgba(0, 0, 0, .08)}.fm-popover{position:absolute;overflow:visible;top:0;left:0;transition:opacity .15s;background-color:transparent;z-index:var(--fm-popover-zindex)}.fm-popover__wrapper{display:inline-block}.fm-popover__content{position:relative;padding:var(--fm-popover-padding);border-radius:var(--fm-popover-border-radius);font-size:var(--fm-popover-content-font-size);line-height:var(--fm-popover-content-line-height);word-break:break-all}.fm-popover__arrow{position:absolute;width:0;height:0;z-index:1;border-style:solid;border-color:transparent;border-width:var(--fm-popover-arrow-size, 8px)}.fm-popover__arrow--top{border-bottom-width:0;border-top-color:currentColor;margin-bottom:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover__arrow--right{border-left-width:0;border-right-color:currentColor;margin-left:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover__arrow--bottom{border-top-width:0;border-bottom-color:currentColor;margin-top:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover__arrow--left{border-right-width:0;border-left-color:currentColor;margin-right:calc(var(--fm-popover-arrow-size) * -1)}.fm-popover--light .fm-popover__content{background:var(--fm-popover-light-theme-background);color:var(--fm-popover-light-theme-color);box-shadow:var(--fm-popover-shadow)}.fm-popover--light .fm-popover__arrow{color:var(--fm-popover-light-theme-background)}.fm-popover--dark .fm-popover__content{background:var(--fm-popover-dark-theme-background);color:var(--fm-popover-dark-theme-color);box-shadow:var(--fm-popover-shadow)}.fm-popover--dark .fm-popover__arrow{color:var(--fm-popover-dark-theme-background)}:root{--fm-switch-background: var(--fm-text-color-light);--fm-switch-on-color: var(--fm-primary-color)}.fm-switch{position:relative;cursor:pointer;display:inline-block;transition:background-color var(--fm-duration-base);background-color:var(--fm-switch-background)}.fm-switch--disabled{cursor:not-allowed;opacity:var(--fm-disabled-opacity)}.fm-switch--readonly{cursor:default;opacity:var(--fm-readonly-opacity)}.fm-switch--loading{cursor:default}.fm-switch--on{background-color:var(--fm-switch-on-color)}.fm-switch--on .fm-switch__loadding{color:var(--fm-switch-on-color)}.fm-switch__node{display:flex;align-items:center;justify-content:center;position:absolute;top:1px;left:1px;z-index:1;width:28px;height:28px;border-radius:100%;background-color:var(--fm-white);transition:transform var(--fm-duration-base) cubic-bezier(.3,1.05,.4,1.05)}:root{--fm-toast-zindex: var(--fm-zindex-5);--fm-toast-color: var(--fm-white);--fm-toast-font-size: 16px;--fm-toast-background: var(--fm-gray-7);--fm-toast-icon-font-size: 48px}.fm-toast{position:fixed;top:50%;left:50%;display:-webkit-box;display:-webkit-flex;display:flex;flex-direction:column;align-items:center;justify-content:center;box-sizing:content-box;max-width:70%;padding:16px;line-height:20px;white-space:pre-wrap;text-align:center;word-wrap:break-word;border-radius:7px;transform:translate(-50%,-50%);color:var(--fm-toast-color);z-index:var(--fm-toast-zindex);font-size:var(--fm-toast-font-size);background-color:var(--fm-toast-background)}.fm-toast--top{top:50px;transform:translate(-50%)}.fm-toast--bottom{top:auto;bottom:50px}.fm-toast--info,.fm-toast--success,.fm-toast--warning,.fm-toast--error{width:88px;min-height:88px}.fm-toast__icon-wrapper{margin-bottom:8px}.fm-toast--default{padding:8px 16px}.fm-toast--loading{min-width:84px;min-height:84px}.fm-toast--loading-icon{position:relative;display:inline-block;width:30px;height:30px;margin-bottom:8px;vertical-align:middle;animation:fm-rotate 2s linear infinite}.fm-toast--loading-icon circle{animation:fm-circular 1.5s ease-in-out infinite;stroke:currentColor;stroke-width:4;stroke-linecap:round}:root{--fm-notify-color: var(--fm-white);--fm-notify-zindex: var(--fm-zindex-5);--fm-notify-info-background: var(--fm-primary-color);--fm-notify-success-background: var(--fm-success-color);--fm-notify-warning-background: var(--fm-warning-color);--fm-notify-error-background: var(--fm-danger-color)}.fm-notify{position:fixed;left:0;top:0;z-index:var(--fm-notify-zindex);display:flex;align-items:center;justify-content:center;width:100%;max-height:100%;padding:8px 16px;box-sizing:border-box;color:var(--fm-notify-color);font-size:14px;line-height:20px;white-space:pre-wrap;text-align:center;word-wrap:break-word;overflow-y:auto}.fm-notify--info{background-color:var(--fm-notify-info-background)}.fm-notify--success{background-color:var(--fm-notify-success-background)}.fm-notify--warning{background-color:var(--fm-notify-warning-background)}.fm-notify--error{background-color:var(--fm-notify-error-background)}:root{--fm-dialog-background: var(--fm-background-2);--fm-dialog-padding-top: 24px;--fm-dialog-padding-left: 16px;--fm-dialog-header-font-size: 17px;--fm-dialog-header-line-height: 24px;--fm-dialog-content-font-size: 15px;--fm-dialog-content-line-height: 20px;--fm-dialog-footer-height: 50px;--fm-border-color: var(--fm-gray-4)}@media screen and (min-width: 375px){.fm-dialog{width:320px}}@media screen and (min-width: 280px) and (max-width: 375px){.fm-dialog{width:240px}}.fm-dialog{display:flex;flex-direction:column;background-color:var(--fm-dialog-background);overflow:hidden}.fm-dialog__header{padding-top:var(--fm-dialog-padding-top);padding-left:var(--fm-dialog-header-padding-left);padding-right:var(--fm-dialog-header-padding-left);font-weight:var(--fm-font-bold-light);font-size:var(--fm-dialog-header-font-size);color:var(--fm-text-color);line-height:var(--fm-dialog-header-line-height);text-align:center}.fm-dialog__content{max-height:70vh;overflow-y:auto}.fm-dialog__content__message{padding:var(--fm-dialog-padding-top) var(--fm-dialog-padding-left);font-size:var(--fm-dialog-content-font-size);line-height:var(--fm-dialog-content-line-height);white-space:pre-wrap;text-align:center;word-wrap:break-word}.fm-dialog__content__message--has-title{padding-top:11px;color:var(--fm-text-color-light)}.fm-dialog__content--prompt{padding:14px}.fm-dialog__content--prompt__input{display:block}.fm-dialog__footer{display:flex;flex-direction:row;align-items:center;border-top:1px solid var(--fm-border-color);position:relative;border-top:none}.fm-dialog__footer:before{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:0 auto auto 0;width:100%;height:1px;transform-origin:50% 50%;transform:scaleY(.5)}.fm-dialog__footer .fm-button{border:0;height:var(--fm-dialog-footer-height);border-right:1px solid var(--fm-border-color);position:relative;border-right:none}.fm-dialog__footer .fm-button:after{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:0 0 auto auto;width:1px;height:100%;background:var(--fm-border-color);transform-origin:100% 50%;transform:scaleX(.5)}.fm-dialog__footer .fm-button:last-child{border-right:0}.fm-dialog__footer .fm-button:last-child:after{display:none!important}.fm-dialog__footer .fm-button--default{color:var(--fm-text-color)}.fm-dialog__footer--is-column{flex-direction:column}.fm-dialog__footer--is-column__button{border-top:1px solid var(--fm-border-color);position:relative;border-top:none}.fm-dialog__footer--is-column__button:before{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:0 auto auto 0;width:100%;height:1px;transform-origin:50% 50%;transform:scaleY(.5)}.fm-dialog__footer--is-column__button:first-child{border-top:0}.fm-dialog__footer--is-column__button:first-child:before{display:none!important}.fm-dialog.fm-dialog--relative{position:relative}.fm-dialog.fm-dialog--relative__header{padding-right:40px}.fm-dialog__close{position:absolute;right:16px;top:16px;width:22px;height:22px;line-height:22px;text-align:center}.fm-dialog__close .fm-icon{font-size:15px;color:var(--fm-text-color-2)}:root,:host{--fm-loading-color: var(--fm-gray-5);--fm-loading-text-font-size: 14px;--fm-loading-text-line-height: 1.4}.fm-loading{display:flex;align-items:center;position:relative;color:var(--fm-loading-color)}.fm-loading__text{color:var(--fm-loading-color);font-size:var(--fm-loading-text-font-size);line-height:var(--fm-loading-text-line-height);margin-left:var(--fm-margin-xs)}.fm-loading--vertical{flex-direction:column}.fm-loading--vertical .fm-loading__text{margin-top:var(--fm-margin-xs);margin-left:0}.fm-loading__circular{display:block;animation:fm-rotate 2s linear infinite}.fm-loading__circular circle{animation:fm-circular 1.5s ease-in-out infinite;stroke:currentColor;stroke-width:3;stroke-linecap:round}.fm-loading__spinner{display:block;position:relative;animation:fm-rotate 1s linear infinite;animation-timing-function:steps(12)}.fm-loading__line{position:absolute;top:0;left:0;width:100%;height:100%}.fm-loading__line-inner{display:block;width:2px;height:25%;margin:0 auto;background-color:currentColor;border-radius:40%}.fm-loading__ring{display:block;animation:fm-rotate .75s linear infinite;border:4px solid;border-right-color:transparent;border-radius:50%}@keyframes fm-circular{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40}to{stroke-dasharray:90,150;stroke-dashoffset:-120}}.fm-loading__line--1{transform:rotate(30deg);opacity:1}.fm-loading__line--2{transform:rotate(60deg);opacity:.9375}.fm-loading__line--3{transform:rotate(90deg);opacity:.875}.fm-loading__line--4{transform:rotate(120deg);opacity:.8125}.fm-loading__line--5{transform:rotate(150deg);opacity:.75}.fm-loading__line--6{transform:rotate(180deg);opacity:.6875}.fm-loading__line--7{transform:rotate(210deg);opacity:.625}.fm-loading__line--8{transform:rotate(240deg);opacity:.5625}.fm-loading__line--9{transform:rotate(270deg);opacity:.5}.fm-loading__line--10{transform:rotate(300deg);opacity:.4375}.fm-loading__line--11{transform:rotate(330deg);opacity:.375}.fm-loading__line--12{transform:rotate(360deg);opacity:.3125}:root,:host{--fm-pull-refresh-head-height: 50px;--fm-pull-refresh-head-font-size: var(--fm-font-size-md);--fm-pull-refresh-head-text-color: var(--fm-text-color-light);--fm-pull-refresh-animation-duration: .3s}.fm-pull-refresh{overflow:hidden}.fm-pull-refresh__track{position:relative;height:100%}.fm-pull-refresh__track--not-pulling{transition:transform ease var(--fm-pull-refresh-animation-duration)}.fm-pull-refresh__head{position:absolute;left:0;width:100%;height:var(--fm-pull-refresh-head-height);color:var(--fm-pull-refresh-head-text-color);font-size:var(--fm-pull-refresh-head-font-size);line-height:var(--fm-pull-refresh-head-height);text-align:center;transform:translateY(-100%);display:flex;justify-content:center;align-items:center;flex-direction:column;overflow:hidden}:root,:host{--fm-list-text-color: var(--fm-gray-5);--fm-list-text-font-size: var(--fm-font-size-md, 14px);--fm-list-text-line-height: 50px;--fm-list-loading-icon-size: 16px}.fm-list__loading,.fm-list__error,.fm-list__load-more,.fm-list__finished-info{color:var(--fm-list-text-color);font-size:var(--fm-list-text-font-size);line-height:var(--fm-list-text-line-height);text-align:center}.fm-list__loading-container{display:flex;height:var(--fm-list-text-line-height);justify-content:center;align-items:center}:root,:host{--fm-listview-checkbox-container-width: 40px;--fm-listview-toolbar-height: 42px;--fm-listview-split-line-color: rgb(230, 235, 235);--fm-listview-empty-text-color: var(--fm-gray-5);--fm-listview-empty-text-font-size: var(--fm-font-size-md, 14px);--fm-listview-empty-text-line-height: 50px}.fm-list-view--fill{height:100%;flex-grow:1;flex-shrink:1;display:flex;flex-direction:column;overflow:hidden}.fm-list-view--fill .fm-list-view__track{flex:1 1 0;overflow:auto;-webkit-overflow-scrolling:touch}.fm-list-view--fill .fm-pull-refresh{min-height:100%}.fm-list-view .fm-list{user-select:none;-webkit-user-select:none}.fm-list-view__item{display:flex;position:relative;overflow:hidden}.fm-list-view__item-checker{display:flex;position:absolute;height:100%;width:var(--fm-listview-checkbox-container-width);justify-content:center;align-items:center}.fm-list-view__item-content{flex:1;transform:translate(0)}.fm-list-view__content--split .fm-list-view__item:not(:last-child):not(.fm-list-view__item--child){border-bottom:1px solid var(--fm-listview-split-line-color)}.fm-list-view--multi-select .fm-list-view__item-content{transform:translate(var(--fm-listview-checkbox-container-width))}.fm-list-view__item--group{display:block}.fm-list-view__item-group-header{padding:10px 16px;line-height:20px;font-size:var(--fm-font-size-md);color:var(--fm-gray-5)}.fm-list-view__empty-message{display:block;color:var(--fm-listview-empty-text-color);font-size:var(--fm-listview-empty-text-font-size);line-height:var(--fm-listview-empty-text-line-height);text-align:center}.fm-list-view__item-text{padding:13px 20px;color:var(--fm-gray-7);font-size:var(--fm-font-size)}.fm-list-view__toolbar{display:flex;align-items:center;height:var(--fm-listview-toolbar-height)}.fm-list-view__toolbar-item{flex:1}:root,:host{--fm-swipe-cell-button-text-color: var(--fm-white);--fm-swipe-cell-button-bg-color: var(--fm-gray-4);--fm-swipe-cell-button-font-size: var(--fm-font-size);--fm-swipe-cell-button-icon-size: var(--fm-font-size);--fm-swipe-cell-button-padding: var(--fm-padding-md)}.fm-swipe-cell{position:relative;overflow:hidden}.fm-swipe-cell__wrapper{transition-timing-function:cubic-bezier(.18,.89,.32,1);transition-property:transform}.fm-swipe-cell__left,.fm-swipe-cell__right{position:absolute;top:0;height:100%;display:flex}.fm-swipe-cell__left{left:0;transform:translate3d(-100%,0,0);flex-direction:row-reverse}.fm-swipe-cell__right{right:0;transform:translate3d(100%,0,0)}.fm-swipe-cell__button{display:inline-flex;justify-content:center;align-items:center;height:100%;padding:0 var(--fm-swipe-cell-button-padding);color:var(--fm-swipe-cell-button-text-color);background-color:var(--fm-swipe-cell-button-bg-color)}.fm-swipe-cell__icon{font-size:var(--fm-swipe-cell-button-icon-size)}.fm-swipe-cell__text{font-size:var(--fm-swipe-cell-button-font-size)}.fm-swipe-cell__icon+.fm-swipe-cell__text:not(:empty){margin-left:6px}:root,:host{--fm-action-sheet-max-height: 80%;--fm-action-sheet-border-radius: 12px;--fm-action-sheet-description-color: var(--fm-gray-5);--fm-action-sheet-description-line-height: 22px;--fm-action-sheet-description-font-size: var(--fm-font-size-md);--fm-action-sheet-description-padding: 12px 16px;--fm-action-sheet-item-default-background: var(--fm-background-white);--fm-action-sheet-item-active-background: #f2f3f5;--fm-action-sheet-item-padding: 13px 16px;--fm-action-sheet-item-text-color: #1a1a1a;--fm-action-sheet-item-disabled-text-color: #bdbdbd;--fm-action-sheet-item-subtitle-color: var(--fm-gray-5);--fm-action-sheet-item-icon-size: 18px;--fm-action-sheet-item-icon-margin-right: 8px;--fm-action-sheet-item-title-font-size: var(--fm-font-size-lg);--fm-action-sheet-item-title-line-height: 24px;--fm-action-sheet-item-subtitle-font-size: var(--fm-font-size-sm);--fm-action-sheet-item-subtitle-line-height: 18px;--fm-action-sheet-item-subtitle-margin-top: 2px;--fm-action-sheet-footer-gap-color: #f5f5f5;--fm-action-sheet-divider-color: #e7e7e7;--fm-action-sheet-cancel-height: 48px;--fm-action-sheet-cancel-color: #1a1a1a;--fm-action-sheet-cancel-font-size: var(--fm-font-size-lg);--fm-action-sheet-cancel-font-weight: 500}.fm-action-sheet{display:flex;flex-direction:column;max-height:var(--fm-action-sheet-max-height);overflow:hidden;user-select:none}.fm-action-sheet--round{border-top-left-radius:var(--fm-action-sheet-border-radius);border-top-right-radius:var(--fm-action-sheet-border-radius)}.fm-action-sheet__description{flex-shrink:0;color:var(--fm-action-sheet-description-color);line-height:var(--fm-action-sheet-description-line-height);font-size:var(--fm-action-sheet-description-font-size);text-align:center;padding:var(--fm-action-sheet-description-padding);position:relative}.fm-action-sheet__description--left{text-align:left}.fm-action-sheet__content{flex:1 auto;overflow-y:auto;-webkit-overflow-scrolling:touch}.fm-action-sheet__item,.fm-action-sheet__cancel{background-color:var(--fm-action-sheet-item-default-background)}.fm-action-sheet__item:active,.fm-action-sheet__cancel:active{background-color:var(--fm-action-sheet-item-active-background)}.fm-action-sheet__item{display:flex;position:relative;flex-wrap:wrap;align-items:center;justify-content:center;text-align:center;padding:var(--fm-action-sheet-item-padding);color:var(--fm-action-sheet-item-text-color);cursor:pointer}.fm-action-sheet__item-icon{font-size:var(--fm-action-sheet-item-icon-size);margin-right:var(--fm-action-sheet-item-icon-margin-right)}.fm-action-sheet__item-title{font-size:var(--fm-action-sheet-item-title-font-size);line-height:var(--fm-action-sheet-item-title-line-height)}.fm-action-sheet__item-subtitle{font-size:var(--fm-action-sheet-item-subtitle-font-size);line-height:var(--fm-action-sheet-item-subtitle-line-height);width:100%;margin-top:var(--fm-action-sheet-item-subtitle-margin-top);color:var(--fm-action-sheet-item-subtitle-color);overflow-wrap:break-word}.fm-action-sheet__item--left{text-align:left;justify-content:flex-start}.fm-action-sheet__item--disabled{color:var(--fm-action-sheet-item-disabled-text-color);cursor:not-allowed}.fm-action-sheet__item--disabled:active{background-color:var(--fm-action-sheet-item-default-background)}.fm-action-sheet__description:after,.fm-action-sheet__item:after{content:"";display:block;position:absolute;height:1px;bottom:0;left:0;right:0;transform:scaleY(.5);background-color:var(--fm-action-sheet-divider-color)}.fm-action-sheet__footer-gap{height:8px;background-color:var(--fm-action-sheet-footer-gap-color)}.fm-action-sheet__cancel{display:flex;flex-direction:column;justify-content:center;align-items:center;height:var(--fm-action-sheet-cancel-height);cursor:pointer;color:var(--fm-action-sheet-cancel-color);font-size:var(--fm-action-sheet-cancel-font-size);font-weight:var(--fm-action-sheet-cancel-font-weight)}:root{--fm-tab-bar-item-color: var(--fm-text-color);--fm-tab-bar-item-active-color: var(--fm-primary-color);--fm-tab-bar-item-font-size: 16px}.fm-tab-bar-item{flex:1 0 auto;position:relative;display:inline-flex;align-items:center;justify-content:center;margin:0 12px;line-height:44px}.fm-tab-bar-item--actived{color:var(--fm-tab-bar-item-active-color)}.fm-tab-bar-item--disabled{color:var(--fm-disabled-color)}.fm-tab-bar-item--dot .fm-tab-bar-item__text:after{content:"";position:absolute;top:0;right:0;width:8px;height:8px;background-color:var(--fm-danger-color);border-radius:100%;transform:translate(100%,100%)}.fm-tab-bar-item__text{position:relative;display:flex;justify-content:center;min-width:40px}.fm-tab-bar-item__ink{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--fm-tab-bar-item-active-color);border-radius:2px}.fm-tab-bar-item__badge{position:absolute;top:2px;right:0;padding:0 4px;max-width:28px;font-weight:500;font-size:12px;line-height:14px;border-radius:7px;color:var(--fm-background-white);background-color:var(--fm-danger-color);transform:translate(50%)}.fm-tab-bar-item__icon{position:relative;display:flex;align-items:center;justify-content:center;padding-right:4px}:root{--fm-tab-bar-height: 44px;--fm-tab-bar-background: var(--fm-background-white);--fm-tab-bar-color: var(--fm-text-color);--fm-tab-bar-active-color: var(--fm-primary-color);--fm-tab-bar-font-size: 16px}.fm-tab-bar{position:relative;background:var(--fm-tab-bar-background);color:var(--fm-tab-bar-color);font-size:var(--fm-tab-bar-font-size)}.fm-tab-bar--bottom-line{border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-tab-bar--bottom-line:after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-tab-bar__scroll-wrapper{position:relative;width:100%;overflow:hidden}.fm-tab-bar__list{display:flex;justify-content:space-between;min-width:100%;transition:all .3s cubic-bezier(.17,.89,.45,1)}.fm-tab-bar__ink{position:absolute;bottom:0;left:0;display:block;height:4px;background-color:var(--fm-tab-bar-active-color);transition:all var(--fm-duration-base);border-radius:2px}.fm-tab-bar__ink--disabled{background-color:var(--fm-disabled-color)}.fm-tabs{overflow:hidden}.fm-tabs__content{display:flex}.fm-tab{display:block;flex-shrink:0;width:100%;height:100%;overflow:auto;word-break:break-all}.fm-tab--collapse{height:0;overflow:visible}:root{--fm-tag-padding: 0 6px;--fm-tag-height: 18px;--fm-tag-font-size: 12px;--fm-tag-border-radius: 2px;--fm-tag-color: var(--fm-white)}.fm-tag{position:relative;display:inline-flex;align-items:center;white-space:nowrap;padding:0 4px;color:var(--fm-tag-color);font-size:var(--fm-tag-font-size);line-height:var(--fm-tag-height);border-radius:var(--fm-tag-border-radius)}.fm-tag--primary{background-color:var(--fm-primary-color)}.fm-tag--success{background-color:var(--fm-success-color)}.fm-tag--danger{background-color:var(--fm-danger-color)}.fm-tag--warning{background-color:var(--fm-warning-color)}.fm-tag--plain{background-color:var(--fm-white)}.fm-tag--plain.fm-tag--primary{border:1px solid var(--fm-primary-color);color:var(--fm-primary-color)}.fm-tag--plain.fm-tag--success{border:1px solid var(--fm-success-color);color:var(--fm-success-color)}.fm-tag--plain.fm-tag--warning{border:1px solid var(--fm-warning-color);color:var(--fm-warning-color)}.fm-tag--plain.fm-tag--danger{border:1px solid var(--fm-danger-color);color:var(--fm-danger-color)}.fm-tag--medium{padding:2px 6px}.fm-tag--large{padding:4px 8px;font-size:14px;border-radius:4px}.fm-tag--round{border-radius:100px}.fm-tag--mark{border-radius:0 100px 100px 0}.fm-tag__close{font-size:8px;margin-left:4px}.fm-tag--large .fm-tag__close{font-size:10px}:root{--fm-picker-background: var(--fm-white);--fm-picker-toolbar-height: 44px}.fm-picker-panel{position:relative;background-color:var(--fm-picker-background);user-select:none}.fm-picker-panel__toolbar{display:flex;justify-content:space-between;align-items:center;height:var(--fm-picker-toolbar-height)}.fm-picker-panel__toolbar-left,.fm-picker-panel__toolbar-right{padding:0 16px}.fm-picker-panel__toolbar-title{font-weight:500;font-size:16px;max-width:50%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-picker-panel__content{position:relative;display:flex;cursor:grab}.fm-picker-panel__columns{display:flex;flex:1;overflow:hidden}.fm-picker-panel__column{flex:1}.fm-picker-panel__column-item{display:flex;align-items:center;justify-content:center;height:44px}.fm-picker-panel__mask{position:absolute;top:0;left:0;z-index:1;width:100%;height:100%;background-image:linear-gradient(180deg,#ffffffe6,#fff6),linear-gradient(0deg,#ffffffe6,#fff6);background-repeat:no-repeat;background-position:top,bottom;transform:translateZ(0);pointer-events:none}.fm-picker-panel__frame{position:absolute;top:50%;left:16px;right:16px;height:88px;z-index:2;pointer-events:none;border-width:1px 0;border-style:solid;border-color:#ebedf0;transform:translateY(-50%) scaleY(.5)}.fm-picker-group__header{text-align:left}.fm-picker-group__toolbar{display:flex;justify-content:space-between;align-items:center;height:var(--fm-picker-toolbar-height)}.fm-picker-group__toolbar-left,.fm-picker-group__toolbar-right{padding:0 16px}.fm-picker-group__toolbar-title{font-weight:500;font-size:16px;max-width:50%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fm-picker-group__tab-bar{display:inline-block}:root{--fm-lookup-panel-background: var(--fm-background);--fm-lookup-panel-search-list-zindex: var(--fm-zindex-3)}.fm-lookup-panel{height:100%;display:flex;flex-direction:column;overflow:hidden;background-color:var(--fm-lookup-panel-background)}.fm-lookup-panel__header{background-color:var(--fm-background-white)}.fm-lookup-panel__header__search{border-bottom:1px solid #ddd;position:relative;border-bottom:none}.fm-lookup-panel__header__search:after{content:"";position:absolute;background-color:#ddd;display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-lookup-panel__header__selected-wrapper{display:flex;padding:6px 16px;overflow:auto}.fm-lookup-panel__header__selected-item{font-size:14px;line-height:20px;word-break:keep-all;white-space:nowrap;padding:4px 6px;color:var(--fm-primary-color);background-color:var(--fm-primary-color-light);border-radius:4px}.fm-lookup-panel__header__selected-item:not(:last-child){margin-right:8px}.fm-lookup-panel__footer{padding:0 16px;height:48px;display:flex;align-items:center;justify-content:space-between;box-shadow:0 0 7px 0 var(--fm-box-shadow-color);border-top:1px solid var(--fm-gray-1);background-color:var(--fm-background-white)}.fm-lookup-panel__content{flex:1;display:flex;overflow:hidden;margin-top:10px;position:relative}.fm-lookup-panel__list{flex:1;overflow-y:auto;background-color:var(--fm-background-white)}.fm-lookup-panel__list-item{display:flex;align-items:center;background-color:#fff}.fm-lookup-panel__list-checker{padding-left:16px}.fm-lookup-panel__search-list{position:absolute;inset:0;display:none;overflow-y:auto;z-index:var(--fm-lookup-panel-search-list-zindex);background-color:var(--fm-background-white)}.fm-lookup-panel__popup-selected{width:100%;display:flex;flex-direction:column}.fm-lookup-panel__popup-selected__footer-left{color:var(--fm-primary-color)}.fm-lookup-panel__selected-list{flex:1;overflow:auto}.fm-lookup-panel__empty{padding:12px 16px}.fm-lookup-panel__empty__text{text-align:center;color:var(--fm-text-color-light)}.fm-lookup-panel__breadcrumb{display:flex;align-items:center;height:42px;padding:0 16px;overflow:auto;border-bottom:1px solid var(--fm-border-color);position:relative;border-bottom:none}.fm-lookup-panel__breadcrumb:after{content:"";position:absolute;background-color:var(--fm-border-color);display:block;z-index:1;inset:auto auto 0 0;width:100%;height:1px;transform-origin:50% 100%;transform:scaleY(.5)}.fm-lookup-panel__breadcrumb__item{display:flex;align-items:center;color:var(--fm-primary-color);padding-right:8px;flex-shrink:0}.fm-lookup-panel__breadcrumb__item:last-child{color:var(--fm-text-color-light)}.fm-lookup-panel__breadcrumb__icon{font-size:10px;padding-left:4px}.fm-lookup-panel__avatar{display:flex;justify-content:center;flex-shrink:0;width:32px;height:32px;line-height:32px;margin-left:16px;font-size:12px;color:var(--fm-white);background:var(--fm-gradient-blue);border-radius:var(--fm-radius-max)}:root{--fm-search-radius: 17px;--fm-search-color: var(--fm-text-color);--fm-search-background: var(--fm-background-white);--fm-search-input-background: var(--fm-background)}.fm-search{display:flex;align-items:center;padding:8px 12px;color:var(--fm-search-color);background-color:var(--fm-search-background)}.fm-search__input-wrapper{flex:1;padding:5px 8px;border-radius:17px;background-color:var(--fm-search-input-background)}.fm-search__input{background-color:var(--fm-search-input-background)}.fm-search__search-icon{margin-right:4px;color:var(--fm-text-color-light)}.fm-filter-panel{height:100%;display:flex;flex-direction:column;padding:8px 16px}.fm-filter-panel__content{flex:1}.fm-filter-panel__footer{padding:8px 16px}.fm-filter-panel__field:not(:first-child){margin-top:16px}.fm-filter-panel__field__title{display:flex;align-items:center;justify-content:space-between;line-height:22px}.fm-filter-panel__field__content{padding-top:12px}.fm-filter-panel__field__range{display:flex;align-items:center}.fm-filter-panel__field__range__splitter{width:8px;margin:0 8px;border:1px solid #999}.fm-filter-panel__field__list{display:flex;flex-wrap:wrap;margin:0 -5px}.fm-filter-panel__field__list__item{height:30px;width:89px;font-size:13px;text-align:center;overflow:hidden;padding:6px 8px;margin:0 5px 10px;background:var(--fm-background);border-radius:var(--fm-radius-md);color:var(--fm-primary-color)}.fm-filter-panel__field__list__item--selected{background:var(--fm-primary-color);color:var(--fm-background-white)}.fm-filter-panel__field .fm-input-group .fm-input-group__body{padding:6px 8px;line-height:18px;font-size:13px;color:var(--fm-primary-color);background:var(--fm-background);border-radius:var(--fm-radius-md);overflow:hidden}.fm-filter__portal{display:flex;align-items:center;padding:8px 16px}.fm-filter__portal__icon{font-size:20px}.fm-filter__portal__search{flex:1;padding:0 0 0 8px}.fm-page-container{flex:1;display:flex;flex-direction:column;background:#f9fafb;overflow:hidden;-webkit-overflow-scrolling:auto;position:absolute;inset:0}.fm-page-container .drag-container{display:inherit;flex-direction:inherit;flex-shrink:1;flex-grow:1;flex-basis:0%;flex-wrap:inherit;justify-content:inherit;align-items:inherit;width:100%;overflow:inherit}.fm-page-body-container{flex-basis:0;flex-shrink:1;flex-grow:1;padding-bottom:12px;overflow:auto;display:flex;flex-direction:column}.fm-page-header-container{flex-shrink:0}.fm-page-footer-container{background-color:#fff}.fm-float-container{position:absolute!important;bottom:60px;right:30px} diff --git a/packages/mobile-render/scripts/publish.sh b/packages/mobile-render/scripts/publish.sh index 024391d4f69d49c13aef1c014dc7be5f275ca3c0..b6888d76a933b6f03e6608ff7af63cf5c8c2fe94 100644 --- a/packages/mobile-render/scripts/publish.sh +++ b/packages/mobile-render/scripts/publish.sh @@ -14,7 +14,7 @@ mkdir -p $RENDER_DIR # 拷贝文件 cp -r -p ../mobile-ui-vue/package/index.css "${STYLE_DIR}/farris-mobile-ui-vue.css" -cp -r -p ../mobile-ui-vue/public/assets/farris-mobile-page.css "${STYLE_DIR}/farris-mobile-page-vue.css" +cp -r -p ../mobile-ui-vue/lib/page.css "${STYLE_DIR}/farris-mobile-page-vue.css" cp -r -p ../mobile-ui-vue/package/index.systemjs.js "${SCRIPT_DIR}/mobile-ui-vue.js" cp -r -p ../devkit/dist-rollup/@farris/devkit-vue.js "${SCRIPT_DIR}/devkit-vue.js" diff --git a/packages/mobile-render/src/components/index.ts b/packages/mobile-render/src/components/index.ts deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/packages/mobile-render/src/main.ts b/packages/mobile-render/src/main.ts index 4673c4092032f869c43eabb1afa553bdfd9f4c5c..c50637e2bb65b6658dceec9f95dafcbf9a1d7f42 100644 --- a/packages/mobile-render/src/main.ts +++ b/packages/mobile-render/src/main.ts @@ -10,7 +10,7 @@ import { formatUtils } from './utils'; import App from './app.vue'; import router from './router'; import { checkFormRoute, addFormRoute } from './router/use-form-routes'; - +import { PluginContext, PluginManager, createBuildInPlugins } from './services/plugin/index'; // devkit export const devkitProviders: StaticProvider[] = [ @@ -36,6 +36,14 @@ app.use(MobileUI); // 注册组件 registerComponents(); +// 初始化插件 +const pluginConext: PluginContext = { + app, devkit, router +}; +const plugins = createBuildInPlugins(pluginConext); +const pluginManager = PluginManager.getInstance(plugins); + + /** * 全局前置守卫 */ diff --git a/packages/mobile-render/src/services/index.ts b/packages/mobile-render/src/services/index.ts index 7a2b9937c7d91256827df5ca08cdc1e09d4e1778..f406cc625e9f606af7bdf0291a85b7ac6cdb7c58 100644 --- a/packages/mobile-render/src/services/index.ts +++ b/packages/mobile-render/src/services/index.ts @@ -1,6 +1,7 @@ export * from './metadata/index'; export * from './module-config/index'; export * from './page-config/index'; +export * from './plugin/index'; export * from './dynamic-route-service'; export * from './providers'; diff --git a/packages/mobile-render/src/services/metadata/form/converters/component-converters/button-group-converter.ts b/packages/mobile-render/src/services/metadata/form/converters/component-converters/button-group-converter.ts index 0d4e014186804f8598149dd43e549f918baac345..8a50ac68010032bfdd37d7b9b5fb410de46cbd42 100644 --- a/packages/mobile-render/src/services/metadata/form/converters/component-converters/button-group-converter.ts +++ b/packages/mobile-render/src/services/metadata/form/converters/component-converters/button-group-converter.ts @@ -19,7 +19,8 @@ class ButtonGroupConverter extends ComponentConverter { type: 'button-group', appearance, visible, - disabled + disabled, + mode: 'group' }); buttonGroup.items = this.buildButtonItems(items); } diff --git a/packages/mobile-render/src/services/metadata/form/converters/component-converters/datebox-converter.ts b/packages/mobile-render/src/services/metadata/form/converters/component-converters/datebox-converter.ts index d3d4b2e53e452121041db890c8a5ac5e942ff04d..11952970fb36fe4505825006748593a086488ab3 100644 --- a/packages/mobile-render/src/services/metadata/form/converters/component-converters/datebox-converter.ts +++ b/packages/mobile-render/src/services/metadata/form/converters/component-converters/datebox-converter.ts @@ -37,9 +37,12 @@ class DateBoxConverter extends InputConverter { /** * 获取显示格式 + * @summary + * 旧格式:YYYY-MM-dd HH:mm:ss + * 新格式:yyyy-MM-dd HH:mm:ss */ private getDisplayFormat(oldFormat: string): string { - return oldFormat.replace('dd', 'DD'); + return oldFormat.replace('YYYY', 'yyyy'); } } diff --git a/packages/mobile-render/src/services/metadata/form/converters/component-converters/index.ts b/packages/mobile-render/src/services/metadata/form/converters/component-converters/index.ts index dbaf49bf72a3390fb3cd1ee069123440149ceb44..b86d4ca7942bbd208b059dfa4868ebfd76a43bb5 100644 --- a/packages/mobile-render/src/services/metadata/form/converters/component-converters/index.ts +++ b/packages/mobile-render/src/services/metadata/form/converters/component-converters/index.ts @@ -12,3 +12,4 @@ export * from './numeric-box-converter'; export * from './switch-field-converter'; export * from './datebox-converter'; export * from './enum-field-converter'; +export * from './lookup-edit-converter'; diff --git a/packages/mobile-render/src/services/metadata/form/converters/component-converters/lookup-edit-converter.ts b/packages/mobile-render/src/services/metadata/form/converters/component-converters/lookup-edit-converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..eacac091890b7c23d2e21ab311d254db3be9af76 --- /dev/null +++ b/packages/mobile-render/src/services/metadata/form/converters/component-converters/lookup-edit-converter.ts @@ -0,0 +1,33 @@ +import { JsonUtil } from '../../../../../utils/index'; +import { InputConverter } from "./input-converter"; + +/** + * 帮助转换器 + */ +class LookupEditConverter extends InputConverter { + + /** + * 执行转换 + */ + public convert(lookup: any): void { + const oldLookup = JsonUtil.cloneJsonObj(lookup); + super.convert(lookup); + + const { dataSource, helpId, displayType, idField, textField, valueField, pageSize } = oldLookup; + const type = 'lookup'; + const pagination = { + enable: pageSize > 0 ? true : false, + size: pageSize + }; + + const mappingFields = oldLookup.mapFields.replace(/'/g, '"'); + + lookup.editor = Object.assign(lookup.editor, { + type, dataSource, helpId, displayType, mappingFields, + idField, textField, valueField, pagination + }); + + } +} + +export { LookupEditConverter }; diff --git a/packages/mobile-render/src/services/metadata/form/converters/component-converters/navigation-bar.ts b/packages/mobile-render/src/services/metadata/form/converters/component-converters/navigation-bar.ts index 10212074c0cdae2ed2cc75502a57b6c98b5e2c7d..c0fc91f6fdd04f3d0af210cfb5f36b9fe8b09aa8 100644 --- a/packages/mobile-render/src/services/metadata/form/converters/component-converters/navigation-bar.ts +++ b/packages/mobile-render/src/services/metadata/form/converters/component-converters/navigation-bar.ts @@ -1,7 +1,6 @@ import { ComponentConverter } from "./component-converter"; import { JsonUtil } from '../../../../../utils/index'; - /** * 导航条组件转换器 */ @@ -12,13 +11,35 @@ class NavigationBarConverter extends ComponentConverter { */ public convert(navbar: any): void { const originNavbar = JsonUtil.cloneJsonObj(navbar); - const { id, title, visible, leftArrow, leftClick: onClickLeft } = originNavbar; this.clearOriginProps(navbar); + + const { id, title, visible, leftArrow, toolbar, leftClick: onClickLeft } = originNavbar; + const type = 'navbar'; + const rightToolbar = this.convertRightToolbar(toolbar); + Object.assign(navbar, { - id, type: 'navbar', title, visible, leftArrow, onClickLeft + id, type, title, visible, leftArrow, rightToolbar, onClickLeft }); } + /** + * 转换右侧工具栏 + */ + private convertRightToolbar(originToolbar: any): any { + const items = originToolbar.items.map((originItem: any) => { + const { id, text, iconType: icon, visible, disable: disabled, click: onClick } = originItem; + const type = 'toolbar-item'; + const item = { + id, type, text, icon, visible, disabled, onClick + }; + + return item; + }); + const toolbar = { + type: 'toolbar', items: items + }; + return toolbar; + } } export { NavigationBarConverter }; diff --git a/packages/mobile-render/src/services/metadata/form/converters/converter-factory.ts b/packages/mobile-render/src/services/metadata/form/converters/converter-factory.ts index 5b56d4bd1ab10f258f2277d285c46b94303c7a17..220339dc126cda91b8490785a8d193a7f7774774 100644 --- a/packages/mobile-render/src/services/metadata/form/converters/converter-factory.ts +++ b/packages/mobile-render/src/services/metadata/form/converters/converter-factory.ts @@ -5,7 +5,8 @@ import { ViewModelsConverter } from './viewodels-converter'; import { ComponentConverter, PageContentContainerConverter, NavigationBarConverter, ButtonConverter, ButtonGroupConverter, ListViewConverter, - TextBoxConverter, MultiTextBoxConverter, NumericBoxConverter, SwitchFieldConverter, DateBoxConverter, EnumFieldConverter + TextBoxConverter, MultiTextBoxConverter, NumericBoxConverter, SwitchFieldConverter, + DateBoxConverter, EnumFieldConverter, LookupEditConverter } from './component-converters'; type ComponentConverterConstructor = new (context: ConverterContext) => ComponentConverter; @@ -77,6 +78,7 @@ class ConverterFactory { this.cmpConverterCtors.set('SwitchField', SwitchFieldConverter); this.cmpConverterCtors.set('DateBox', DateBoxConverter); this.cmpConverterCtors.set('EnumField', EnumFieldConverter); + this.cmpConverterCtors.set('LookupEdit', LookupEditConverter); } } diff --git a/packages/mobile-render/src/services/metadata/form/form-metadata-converter.ts b/packages/mobile-render/src/services/metadata/form/form-metadata-converter.ts index a42fd77acb595d20f15fb264ae47d2906f9e65ea..fc9da3bbab001c9ca0fcb4788750786b3075c690 100644 --- a/packages/mobile-render/src/services/metadata/form/form-metadata-converter.ts +++ b/packages/mobile-render/src/services/metadata/form/form-metadata-converter.ts @@ -97,12 +97,10 @@ class FormMetadataConverter { * 移除不受支持的组件 */ private removeUnSupportedComponents(): void { - console.group('----------unSupported components----------'); const vmComponents = this.context.getVmComponents(); vmComponents.forEach((vmComponent) => { this.removeUnSupportComponent(vmComponent, null); }); - console.groupEnd(); } /** @@ -113,7 +111,6 @@ class FormMetadataConverter { if (parentComponent && !componentSchema) { const componentIndex = parentComponent.contents.indexOf(component); parentComponent.contents.splice(componentIndex, 1); - console.log('unsupported component: ' + component.type); return; } diff --git a/packages/mobile-render/src/services/metadata/form/form-metadata-data-service.ts b/packages/mobile-render/src/services/metadata/form/form-metadata-data-service.ts index 347642594ada08adbd100af90188dd0a08519807..0ce90a8a833d881b523469cb21120edca53740e3 100644 --- a/packages/mobile-render/src/services/metadata/form/form-metadata-data-service.ts +++ b/packages/mobile-render/src/services/metadata/form/form-metadata-data-service.ts @@ -43,13 +43,9 @@ export class FormMetadataDataService { const metadataConverter = new FormMetadataConverter(metadata); metadataConverter.convert(); - console.log('----------after convert: FormMetadata----------'); - console.log(metadata.content.module); const metadataNormalizer = new FormMetadataNormalizer(metadata); metadataNormalizer.normalize(); - console.log('----------after normalize: FormMetadata----------'); - console.log(metadata.content.module); return metadata; } diff --git a/packages/mobile-render/src/services/metadata/form/form-metadata-query.ts b/packages/mobile-render/src/services/metadata/form/form-metadata-query.ts index 6d82587976dcd5ff118a6de7d934737ec632e12b..bf139524f9eaf5c3b58e9829cca55b10742cd471 100644 --- a/packages/mobile-render/src/services/metadata/form/form-metadata-query.ts +++ b/packages/mobile-render/src/services/metadata/form/form-metadata-query.ts @@ -167,10 +167,6 @@ class FormMetadataQuery { }; this.wrappedComponentMap.set(component.id, wrappedComponent); - if (component.editor) { - this.appendWrappedComponentToMap(component.editor, currentViewModel, currentViewModelComponent); - } - if (Array.isArray(component.contents)) { component.contents.forEach((childComponent: any) => { this.appendWrappedComponentToMap(childComponent, currentViewModel, currentViewModelComponent); diff --git a/packages/mobile-render/src/services/metadata/form/normalizers/button-group-normalizer.ts b/packages/mobile-render/src/services/metadata/form/normalizers/button-group-normalizer.ts index 28922cb849422f31a052eb7b4eab8f18d66e1011..202ed1d8da147079fc9535f46bbff877b8930c80 100644 --- a/packages/mobile-render/src/services/metadata/form/normalizers/button-group-normalizer.ts +++ b/packages/mobile-render/src/services/metadata/form/normalizers/button-group-normalizer.ts @@ -10,21 +10,8 @@ class ButtonGroupNormalizer extends ComponentNormalizer { */ public normalize(component: any, componentSchema: any, viewModelId: string) { super.normalize(component, componentSchema, viewModelId); - - if (Array.isArray(component.items)) { - const itemSchema = componentSchema.properties.items.items; - this.normalizeItems(component.items, itemSchema, viewModelId); - } } - /** - * 规范化Items - */ - public normalizeItems(items: any[], itemSchema: any, viewModelId: string) { - items.forEach((item: any) => { - super.normalize(item, itemSchema, viewModelId); - }); - } } export { ButtonGroupNormalizer }; diff --git a/packages/mobile-render/src/services/metadata/form/normalizers/component-normalizer.ts b/packages/mobile-render/src/services/metadata/form/normalizers/component-normalizer.ts index a7ca25fe972c35359acb27681b57e7f42caced9c..6ea43f243d4ec83a9230a8261b2e4e81e6cd46ff 100644 --- a/packages/mobile-render/src/services/metadata/form/normalizers/component-normalizer.ts +++ b/packages/mobile-render/src/services/metadata/form/normalizers/component-normalizer.ts @@ -1,56 +1,10 @@ +import { isObject, isArray } from '../../../../utils/index'; import { FormMetadataQuery } from '../form-metadata-query'; -import { isObject, isString } from '../../../../utils/index'; +import { PropBindingType } from './types'; +import { NormalizerUtil } from './normalizer-util'; /** * 通用组件标准化器 - * /** - * 属性配置构造器 - * @summary - * -------------------------------------------------------------------------------- - * 问题 - * 1、老表单的组件DOM结构不合理的地方需要转换; - * 2、新表单的组件DOM结构部分内容表达不充分,先进行临时性转换,后续再沉淀; - * - * 目标: - * 1、将老表单中不规范的组件DOM结构进行标准化; - * 2、标准化后组件DOM结构,根据实际情况沉淀成标准,或根据标准调整转换逻辑; - * - * 对于普通的属性值 - * 1、常量: - * 2、字段:{"type": "Form", "field": "guid", "path": "StrField", "bindingPath": "strField" } - * 3、变量:{"path": "strVar", "field": "guid", "fullPath": "strVar", "type": "Variable"} - * 4、状态机:stateMachine['canEdit'] - * 5、表达式:{"type": "Expression", "expressionId": "guid_visible"}, - * 6、界面规则: {"type": "FormRule", "ruleId": "guid", "ruleName": "规则1", "expressionId": "guid_visible"} - * 7、自定义:currentEntityData.xxx/bindingData.xxx/uiState.xxx/stateMachine.xxx以及他们的混合 - * - * 对于需要双向绑定的输入控件 - * 1、字段:{"type": "Form", "path": "code", "field": "guid", "fullPath": "Code"} - * 2、组件变量:{"type": "Variable", "path": "strVar", "field": "guid", "fullPath": "strVar"}, - * 3、表单变量;{"type": "Variable", "path": "root-component.textVoVar", "field": "guid", "fullPath": "textVoVar"} - * - * 对于绑定集合数据的控件 - * 1、ListView:未有明确的配置,生成器根据ViewModle的直接绑定了entityListData直接取的ViewModel的bindingPath - * 2、LightAttachment:同Listview - * - * 对于事件属性 - * 1、当前ViewModle的命令:命令编号,比如LoadAndAddForCard - * 2、移动端暂无跨ViewModel绑定的情况 - * - * 其他情况 - * 1、在Html模板中直接使:currentEntityData、entityListData、uiState、stateMachine等; - * 2、附件的parentDirName使用了:bindingData.id - * - * -------------------------------------------------------------------------------- - * 处理策略 - * 1、针对存在type和field的属性,转换成 { bindingType: 'Entity', bindingPath: 'xxx' } 、{ bindingType: 'Variable', bindingPath: 'xxx' } - * 2、针对stateMachine开头的,转换成 { bindingType: 'StateMachine', bindingPath: 'xxx' } - * 3、识别其他情况:自定义的暂时不支持,统一按静态值处理,设计时控制住不允许配置; - * - * 4、针对ListView和LightAttachment,补上binding属性,结构同情况1; - * - * 5、针对事件属性,提前补全viewModelId - * -------------------------------------------------------------------------------- */ class ComponentNormalizer { @@ -68,104 +22,102 @@ class ComponentNormalizer { /** * 标准化组件属性 - * @todo 需要支持递归处理属性 */ - public normalize(component: any, componentSchema: any, viewModelId: string) { - if (!componentSchema) { - throw new Error(`Can't not find schema for the component(type=${component.type})`); - } - const eventPropNames = componentSchema.events || []; - - Object.keys(component).forEach((propName: string) => { - if (eventPropNames.includes(propName)) { - this.normalizeEventPropValue(propName, component, viewModelId); - } else { - this.normalizeCommonPropValue(propName, component, viewModelId); - } - }); + public normalize(component: any, componentSchema: any, viewModelId: string): any { + this.normalizeComponent(component, componentSchema, viewModelId); } /** - * 标准化事件属性 - * @summary - * 转换后形如{ type: 'Command', name: 'LoadAndAddForCard', viewModelId: 'card-page-component'} + * 标准化组件 */ - protected normalizeEventPropValue(propName: string, component: any, viewModelId: string): void { - const commandName = component[propName]; - if (!commandName) { - component[propName] = null; - } else { - const normalizedPropValue = { - type: 'Command', - name: component[propName], - viewModelId: viewModelId - }; - component[propName] = normalizedPropValue; + protected normalizeComponent(component: any, componentSchema: any, viewModelId: string) { + if (!componentSchema) { + throw new Error(`Can't not find schema for the component(type=${component.type})`); } + Object.keys(component).forEach((propName: string) => { + const propValue = component[propName]; + const normalizedPropValue = this.getNormalizedProppValue(propName, propValue, component, componentSchema, viewModelId); + component[propName] = normalizedPropValue; + }); } /** - * 标准化普通属性 + * 获取标准化的属性值 */ - protected normalizeCommonPropValue(propName: string, component: any, viewModelId: string): void { - const propValue = component[propName]; - let normalizedPropValue = propValue; - if (isObject(propValue) && propValue.type && propValue.field) { - normalizedPropValue = this.getNormalizeObjectPropValue(propValue, viewModelId); - } else if (isString(propValue)) { - normalizedPropValue = this.getNormalizeStringPropValue(propValue, viewModelId); + private getNormalizedProppValue(propName: string, propValue: any, component: any, componentSchema: any, viewModelId: string): any { + let normalizedPropValue: any; + const bindingType = NormalizerUtil.getBindingType(propName, propValue, component, componentSchema); + + if (bindingType === PropBindingType.EntityField) { + normalizedPropValue = this.getEntityFieldBinding(propValue, viewModelId); + } else if (bindingType === PropBindingType.Variable) { + normalizedPropValue = this.getVariableBinding(propValue, viewModelId); + } else if (bindingType === PropBindingType.StateMachine) { + normalizedPropValue = this.getStateMachineBinding(propValue, viewModelId); + } else if (bindingType === PropBindingType.Command) { + normalizedPropValue = this.getCommandBinding(propValue, viewModelId); } else { normalizedPropValue = propValue; - } - component[propName] = normalizedPropValue; + // 针对对象和数组类型的属性,需要继续递归处理 + const propSchema = NormalizerUtil.getPropSchema(propName, componentSchema); + if (propSchema) { + if (propSchema.type === 'object') { + this.normalizeObjectPropValue(propName, propValue, component, componentSchema, viewModelId); + } else if (propSchema.type === 'array') { + this.normalizeArrayPropValue(propName, propValue, component, componentSchema, viewModelId); + } + } + }; + + return normalizedPropValue; } /** - * 规范化对象类型的属性值 - * @summary - * 暂时仅支持Form和Variable - * 字段:{ bindingType: 'EntityField', bindingPath: '/deptInfo/name', isTwoWay: false} - * 变量:{ bindingType: 'Variable', bindingPath: '/filter/name', isTwoWay: false, viewModelId: 'cad-page-component'} + * 标准化对象属性值 */ - private getNormalizeObjectPropValue(propValue: any, currentViewModelId: string): any { - const { type } = propValue; - - let normalizePropValue; - if (type === 'Form') { - normalizePropValue = this.getEntityFieldBinding(propValue, currentViewModelId); - } else if (type === 'Variable') { - normalizePropValue = this.getVariableBinding(propValue, currentViewModelId); - } else { - normalizePropValue = null; + private normalizeObjectPropValue(objPropName: string, objPropValue: any, component: any, componentSchema: any, viewModelId: string): void { + if (!isObject(objPropValue)) { + return; + } + const objectPropSchema = NormalizerUtil.getPropSchema(objPropName, componentSchema); + if (objectPropSchema.type !== 'object' || !objectPropSchema.properties) { + return; } - return normalizePropValue; + this.normalizeComponent(objPropValue, objectPropSchema, viewModelId); } /** - * 标准化字符串类型的属性值 - * @summary - * 暂时仅支持StateMachine - * 转换后形如:{ bindingType: 'StateMachine', bindingPath: '/canInput', isTwoWay: false, viewModelId: 'cad-page-component'} + * 标准化数组属性值 */ - private getNormalizeStringPropValue(propValue: string, currentViewModelId: string): any { - if (!propValue.startsWith('stateMachine')) { - return propValue; + private normalizeArrayPropValue(arrayPropName: string, arrayPropValue: any, component: any, componentSchema: any, viewModelId: string): void { + if (!isArray(arrayPropValue)) { + return; } - const normalizedPropValue = this.getStateMachineBinding(propValue, currentViewModelId); - return normalizedPropValue; + + const arrayPropSchema = NormalizerUtil.getPropSchema(arrayPropName, componentSchema); + if (arrayPropSchema.type !== 'array' || !arrayPropSchema.items || !arrayPropSchema.items.properties) { + return; + } + + const arrayItemSchema = arrayPropSchema.items; + arrayPropValue.forEach((arrayItem: any) => { + this.normalizeComponent(arrayItem, arrayItemSchema, viewModelId); + }); } /** * 获取字段绑定 + * @summary + * 转换前:{ "type": "Form", "field": "guid", "path": "StrField", "bindingPath": "strField" } + * 转换后:{ bindingType: 'EntityField', bindingPath: '/deptInfo/name', isTwoWay: false } */ - private getEntityFieldBinding(originBinding: any, currentViewModelId: string) { - - const { field } = originBinding; + private getEntityFieldBinding(propValue: any, currentViewModelId: string): any { + const { field } = propValue; const wrappedFieldSchema = this.formMetadataQuery.getWrappedFieldSchemaById(field); const path = wrappedFieldSchema.bindingPath; - + const binding = { type: 'EntityField', path: path, @@ -178,8 +130,11 @@ class ComponentNormalizer { /** * 获取变量绑定 + * @summary + * 转换前:{"type": "Variable" "field": "guid", "path": "strVar", "fullPath": "strVar" } + * 转换后:{ bindingType: 'EntityField', bindingPath: '/strVar', isTwoWay: false } */ - private getVariableBinding(propValue: any, currentViewModelId: string) { + private getVariableBinding(propValue: any, currentViewModelId: string): any { const { path } = propValue; const binding = { type: 'Variable', @@ -193,23 +148,43 @@ class ComponentNormalizer { /** * 获取状态机绑定 + * @summary + * 转换前:stateMachine['canInput'] + * 转换后:{ bindingType: 'StateMachine', bindingPath: '/canInput', isTwoWay: false, viewModelId: 'cad-page-component' } */ - private getStateMachineBinding(propValue: any, currentViewModelId: string) { - const matchArray = propValue.match(/stateMachine\['(.*?)'\]/); - if (!matchArray) { - return propValue; - } - - const renderState = matchArray[1]; + private getStateMachineBinding(propValue: any, currentViewModelId: string): any { + const { field } = propValue; const binding = { type: 'StateMachine', - path: `/${renderState}`, + path: `/${field}`, direction: 'OneWay', viewModelId: currentViewModelId }; return binding; } + + /** + * 获取命令绑定 + * @summary + * 转换前:LoadAndAddForCard + * 转换后:{ type: 'Command', name: 'LoadAndAddForCard', viewModelId: 'card-page-component'} + */ + protected getCommandBinding(propValue: any, viewModelId: string): any { + if (!propValue) { + return null; + } + + const commandName = propValue; + const commandBinding = { + type: 'Command', + name: commandName || null, + viewModelId: viewModelId + }; + + return commandBinding; + } + } export { ComponentNormalizer }; diff --git a/packages/mobile-render/src/services/metadata/form/normalizers/index.ts b/packages/mobile-render/src/services/metadata/form/normalizers/index.ts index ba6f3cd69892c27c2c58b3f0b85b32e69a74f43f..589e37fe89a7f7b88eb7aacd33512e3285a1e0c4 100644 --- a/packages/mobile-render/src/services/metadata/form/normalizers/index.ts +++ b/packages/mobile-render/src/services/metadata/form/normalizers/index.ts @@ -1,3 +1,57 @@ +/** + * 通用组件标准化器 + * /** + * 属性配置构造器 + * @summary + * -------------------------------------------------------------------------------- + * 问题: + * 1、控件属性值的表达方式不统一:同样是变量,有的地方是字符串(状态机),有的地方是对象(字段、变量); + * 2、控件的属性值表达不充分:比如字段没有记录完整绑定路径,命令没有记录视图模型ID,到了运行时根据上下文再去解析; + * + * 这两个问题导致了运行时解析时(老版的生成器)需要大量代码去处理这些问题,并且代码被分散到各个位置, + * 处理这些问题时还依赖当前视图模型的绑定路径,导致处理逻辑复杂,容易出错。 + * -------------------------------------------------------------------------------- + * * 目标: + * 1、标准化器的作用是提前对这些绑定信息进行标准化,后续的处理逻辑基于这个统一的标准实现; + * 2、通过标准化器识别不规范的地方,将来考虑在表单DOM层进行沉淀; + * -------------------------------------------------------------------------------- + * 现状: + * + * 对于普通的属性值 + * 1、常量:字符串长度(100)、是否可见(true/false)等 + * 2、字段:{"type": "Form", "field": "guid", "path": "StrField", "bindingPath": "strField" } + * 3、变量:{"type": "Variable" "field": "guid", "path": "strVar", "fullPath": "strVar" } + * 4、状态机:stateMachine['canInput'] + * 5、表达式:{"type": "Expression", "expressionId": "guid_visible"}, + * 6、界面规则: {"type": "FormRule", "ruleId": "guid", "ruleName": "规则1", "expressionId": "guid_visible"} + * 7、自定义:currentEntityData.xxx/bindingData.xxx/uiState.xxx/stateMachine.xxx以及他们的混合 + * + * 对于需要双向绑定的输入控件 + * 1、字段:{"type": "Form", "path": "code", "field": "guid", "fullPath": "Code"} + * 2、组件变量:{"type": "Variable", "path": "strVar", "field": "guid", "fullPath": "strVar"}, + * 3、表单变量;{"type": "Variable", "path": "root-component.textVoVar", "field": "guid", "fullPath": "textVoVar"} + * + * 对于绑定集合数据的控件 + * 1、ListView:未有明确的配置,生成器根据ViewModle的直接绑定了entityListData直接取的ViewModel的bindingPath + * 2、LightAttachment:同Listview + * + * 对于事件属性 + * 1、当前ViewModle的命令:命令编号,比如LoadAndAddForCard + * 2、移动端暂无跨ViewModel绑定的情况 + * + * 其他情况 + * 1、在Html模板中直接使:currentEntityData、entityListData、uiState、stateMachine等; + * 2、附件的parentDirName使用了:bindingData.id + * + * -------------------------------------------------------------------------------- + * 处理策略: + * 1、针对存在type和field的属性,转换成 { bindingType: 'Entity', bindingPath: 'xxx' } 、{ bindingType: 'Variable', bindingPath: 'xxx' } + * 2、针对stateMachine开头的,转换成 { bindingType: 'StateMachine', bindingPath: 'xxx' } + * 3、识别其他情况:自定义的暂时不支持,统一按静态值处理,设计时控制住不允许配置; + * 4、针对ListView和LightAttachment,补上binding属性,结构同情况1; + * 5、针对事件属性,提前补全viewModelId + * -------------------------------------------------------------------------------- + */ export * from './component-normalizer'; export * from './listview-normalizer'; export * from './form-group-normalizer'; diff --git a/packages/mobile-render/src/services/metadata/form/normalizers/normalizer-util.ts b/packages/mobile-render/src/services/metadata/form/normalizers/normalizer-util.ts new file mode 100644 index 0000000000000000000000000000000000000000..edab3c7c1c745dcb20aa39a190e54c123babdefb --- /dev/null +++ b/packages/mobile-render/src/services/metadata/form/normalizers/normalizer-util.ts @@ -0,0 +1,102 @@ +import { isObject } from '../../../../utils/index'; +import { PropBindingType } from './types'; + +/** + * 组件格规范化工具类 + */ +class NormalizerUtil { + + /** + * 获取属性Schema + */ + public static getPropSchema(propName: string, componentSchema: any): any { + if (!componentSchema || !componentSchema.properties) { + return null; + } + const propSchema = componentSchema.properties[propName] || null; + + return propSchema; + } + + /** + * 获取绑定类型 + */ + public static getBindingType(propName: string, propValue: any, component: any, componentSchema: any): PropBindingType { + if (this.isEntityFieldBinding(propValue)) { + return PropBindingType.EntityField; + } else if (NormalizerUtil.isEntityListBinding(propValue)) { + return PropBindingType.EntityList; + } else if (NormalizerUtil.isVariableBinding(propValue)) { + return PropBindingType.Variable; + } else if (NormalizerUtil.isStateMachineBinding(propValue)) { + return PropBindingType.StateMachine; + } else if (NormalizerUtil.isCommandBinding(propName, componentSchema)) { + return PropBindingType.Command; + } else { + return PropBindingType.Static; + + }; + } + + /** + * 是否是实体字段绑定 + */ + public static isEntityFieldBinding(propValue: any): boolean { + if (isObject(propValue) && propValue.type === 'Form') { + return true; + } + return false; + } + + /** + * 是否是实体绑定 + */ + public static isEntityBinding(): boolean { + return false; + } + + /** + * 是否是实体集合绑定 + */ + public static isEntityListBinding(propName: string): boolean { + if (propName === 'dataSource') { + return true; + } + + return false; + } + + /** + * 是否是变量绑定 + */ + public static isVariableBinding(propValue: any): boolean { + if (isObject(propValue) && propValue.type === 'Variable') { + return true; + } + return false; + } + + /** + * 是否是状态机绑定 + */ + public static isStateMachineBinding(propValue: any): boolean { + if (isObject(propValue) && propValue.type === 'StateMachine') { + return true; + } + return false; + } + + /** + * 是否是命令绑定 + */ + public static isCommandBinding(propName: any, componentSchema: any): boolean { + const eventPropNames = componentSchema.events || []; + if (eventPropNames.includes(propName)) { + return true; + } + + return false; + } +} + +export { NormalizerUtil }; diff --git a/packages/mobile-render/src/services/metadata/form/normalizers/types.ts b/packages/mobile-render/src/services/metadata/form/normalizers/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..18efda7c94b5a213d7bdb7768e008886aaafe181 --- /dev/null +++ b/packages/mobile-render/src/services/metadata/form/normalizers/types.ts @@ -0,0 +1,9 @@ +export enum PropBindingType { + EntityField = 'EntityField', + Entity = 'Entity', + EntityList = 'EntityList', + Variable = 'Variable', + StateMachine = 'StateMachine', + Command = 'Command', + Static = 'Static' +}; diff --git a/packages/mobile-render/src/services/module-config/module-config-builder.ts b/packages/mobile-render/src/services/module-config/module-config-builder.ts index 3ba92dd77e86cf78605a946e07523287c2b6992a..e426342c5ef3b761934d3ac6647cf3ba5cb32727 100644 --- a/packages/mobile-render/src/services/module-config/module-config-builder.ts +++ b/packages/mobile-render/src/services/module-config/module-config-builder.ts @@ -87,8 +87,6 @@ class ModuleConfigBuilder { this.moduleConfig.id = this.formMeta.module.code; this.moduleConfig.providers = this.context.moduleProviders; - console.log('--------------------moduleConfig--------------------'); - console.log(this.moduleConfig); return this.moduleConfig; } diff --git a/packages/mobile-render/src/services/page-config/bindings/binding-context.ts b/packages/mobile-render/src/services/page-config/bindings/binding-context.ts index 91df5d2ef0af021224d7110b26e07d85510cd687..766d609b8dd8111f17731b1a6c033f770c656e8c 100644 --- a/packages/mobile-render/src/services/page-config/bindings/binding-context.ts +++ b/packages/mobile-render/src/services/page-config/bindings/binding-context.ts @@ -32,6 +32,13 @@ class BindingContext { return this.module.getViewModel(id); } + /** + * 获取绑定监听器 + */ + public getBindingWatcher(): BindingWatcher { + return this.watcher; + } + /** * 注册状态变更依赖 */ diff --git a/packages/mobile-render/src/services/page-config/bindings/binding-factory.ts b/packages/mobile-render/src/services/page-config/bindings/binding-factory.ts index a0a0bf99cd2dae6413f0ee3126ab4b9f165ffbbe..92647e6159a8235d9215d05d41fc6e4464c15ba7 100644 --- a/packages/mobile-render/src/services/page-config/bindings/binding-factory.ts +++ b/packages/mobile-render/src/services/page-config/bindings/binding-factory.ts @@ -2,6 +2,7 @@ import { Binding } from './binding'; import { EntityFieldBinding } from './entity-field-binding'; import { EntityListBinding } from './entity-list-binding'; import { CommandBinding } from './command-binding'; +import { StateMachineBinding } from './state-machine-binding'; /** * 绑定工厂 @@ -36,7 +37,7 @@ class BindingFactory { * 初始化绑定集合 */ private initBindings() { - const bindingCtors = [EntityFieldBinding, EntityListBinding, CommandBinding]; + const bindingCtors = [EntityFieldBinding, EntityListBinding, StateMachineBinding, CommandBinding]; this.bindings = new Map(); bindingCtors.forEach((bindingCtor) => { diff --git a/packages/mobile-render/src/services/page-config/bindings/binding-util.ts b/packages/mobile-render/src/services/page-config/bindings/binding-util.ts index a85c20c3d096a730bdf99964d8886631e9b2a005..ed71ff5bf451f033a79d382a50b13946c35a6fe3 100644 --- a/packages/mobile-render/src/services/page-config/bindings/binding-util.ts +++ b/packages/mobile-render/src/services/page-config/bindings/binding-util.ts @@ -1,4 +1,5 @@ -import { TypeUtil } from '../../../utils/index'; +import { isObject, isString, TypeUtil } from '../../../utils/index'; +import { BindingType } from './types'; /** * 绑定工具类 @@ -6,17 +7,26 @@ import { TypeUtil } from '../../../utils/index'; class BindingUtil { /** - * 全部绑定类型 + * 获取属性Schema */ - public static supportedBindingTypes = [ - 'EntityField', 'Entity', 'EntityList', - 'Variable', 'StatMachine', 'Command' - ]; + public static getPropSchema(propName: string, componentSchema: any): any { + if (!componentSchema || !componentSchema.properties) { + return null; + } + const propSchema = componentSchema.properties[propName] || null; + + return propSchema; + } /** * 判断是否是绑定 */ public static isBinding(bindingConfig: any): boolean { + const supportedBindingTypes = [ + 'EntityField', 'Entity', 'EntityList', + 'Variable', 'StateMachine', 'Command' + ]; + if (!bindingConfig) { return false; } @@ -25,12 +35,91 @@ class BindingUtil { return false; } - if (!this.supportedBindingTypes.includes(bindingConfig.type)) { + if (!supportedBindingTypes.includes(bindingConfig.type)) { return false; } return true; } + + /** + * 获取绑定类型 + */ + public static getBindingType(propName: string, propValue: any, component: any, componentSchema: any): BindingType { + if (this.isEntityFieldBinding(propValue)) { + return BindingType.EntityField; + } else if (this.isEntityListBinding(propValue)) { + return BindingType.EntityList; + } else if (this.isVariableBinding(propValue)) { + return BindingType.Variable; + } else if (this.isStateMachineBinding(propValue)) { + return BindingType.StateMachine; + } else if (this.isCommandBinding(propName, componentSchema)) { + return BindingType.Command; + } else { + return BindingType.Static; + }; + } + + /** + * 是否是实体字段绑定 + */ + public static isEntityFieldBinding(propValue: any): boolean { + if (isObject(propValue) && propValue.type && propValue.field && propValue.path) { + return true; + } + return false; + } + + /** + * 是否是实体绑定 + */ + public static isEntityBinding(): boolean { + return false; + } + + /** + * 是否是实体集合绑定 + */ + public static isEntityListBinding(propName: string): boolean { + if (propName === 'dataSource') { + return true; + } + + return false; + } + + /** + * 是否是变量绑定 + */ + public static isVariableBinding(propValue: any): boolean { + if (isObject(propValue) && propValue.type && propValue.field && propValue.path) { + return true; + } + return false; + } + + /** + * 是否是状态机绑定 + */ + public static isStateMachineBinding(propValue: any): boolean { + if (isString(propValue) && propValue.startsWith('stateMachine')) { + return true; + } + return false; + } + + /** + * 是否是命令绑定 + */ + public static isCommandBinding(propName: any, componentSchema: any): boolean { + const eventPropNames = componentSchema.events || []; + if (eventPropNames.includes(propName)) { + return true; + } + + return false; + } } export { BindingUtil }; diff --git a/packages/mobile-render/src/services/page-config/bindings/entity-field-binding.ts b/packages/mobile-render/src/services/page-config/bindings/entity-field-binding.ts index 8e595c99f962e97fb43bb1b317b64302ec451575..bb1764f8abb035d3686d840b43f0375914ca224b 100644 --- a/packages/mobile-render/src/services/page-config/bindings/entity-field-binding.ts +++ b/packages/mobile-render/src/services/page-config/bindings/entity-field-binding.ts @@ -1,5 +1,7 @@ import { Binding } from './binding'; import { BindingContext } from './binding-context'; +import { LookupMappingService } from '../../../services/lookup-mapping-service'; +import { Lookup } from '@farris/mobile-ui-vue'; /** * 实体字段绑定 diff --git a/packages/mobile-render/src/services/page-config/bindings/index.ts b/packages/mobile-render/src/services/page-config/bindings/index.ts index 511efd5ab82d944d07ff7a777487ccadae188979..175ac2e4fb0c0f12eb5d7c92c07dbf631c2b05ff 100644 --- a/packages/mobile-render/src/services/page-config/bindings/index.ts +++ b/packages/mobile-render/src/services/page-config/bindings/index.ts @@ -2,7 +2,9 @@ export * from './binding'; export * from './entity-field-binding'; export * from './entity-list-binding'; export * from './command-binding'; +export * from './state-machine-binding'; export * from './binding-util'; export * from './binding-context'; +export * from './binding-watcher'; export * from './binding-factory'; diff --git a/packages/mobile-render/src/services/page-config/bindings/state-machine-binding.ts b/packages/mobile-render/src/services/page-config/bindings/state-machine-binding.ts new file mode 100644 index 0000000000000000000000000000000000000000..82c1122162b967b14c68b1965195bc11b12ecc2f --- /dev/null +++ b/packages/mobile-render/src/services/page-config/bindings/state-machine-binding.ts @@ -0,0 +1,59 @@ +import { Binding } from './binding'; +import { BindingContext } from './binding-context'; + +/** + * 状态机绑定绑定 + */ +class StateMachineBinding extends Binding { + + /** + * 绑定名称 + */ + public name = 'StateMachine'; + + /** + * 初始化 + */ + public init(bindingConfig: any, target: any, propName: string, bindingContext: BindingContext): void { + const { viewModelId } = bindingConfig; + const stateMachine = this.getStateMachine(viewModelId, bindingContext); + + bindingContext.registerDep(stateMachine, this, bindingConfig, target, propName); + this.update(bindingConfig, target, propName, bindingContext); + } + + /** + * 值更新 + */ + public update(bindingConfig: any, target: any, propName: string, bindingContext: BindingContext) { + const value = this.getValue(bindingConfig, bindingContext); + target[propName] = value; + } + + /** + * 获取实体仓库 + */ + private getStateMachine(viewModelId: string, bindingContext: BindingContext): any { + const viewModel = bindingContext.getViewModel(viewModelId); + if (!viewModel) { + return; + } + const { stateMachine } = viewModel; + + return stateMachine; + } + + /** + * 获取属性值 + */ + private getValue(bindingConfig: any, bindingContext: BindingContext): void { + const { path, status, viewModelId } = bindingConfig; + const stateMachine = this.getStateMachine(viewModelId, bindingContext); + const stateName = path.slice(1); + const value = stateMachine.getValue(stateName); + + return status === true ? value : !value; + } +} + +export { StateMachineBinding }; diff --git a/packages/mobile-render/src/services/page-config/bindings/types.ts b/packages/mobile-render/src/services/page-config/bindings/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c2bccde6a390e0ff6b19b85b023abeb99b22a36 --- /dev/null +++ b/packages/mobile-render/src/services/page-config/bindings/types.ts @@ -0,0 +1,9 @@ +export enum BindingType { + EntityField = 'EntityField', + Entity = 'Entity', + EntityList = 'EntityList', + Variable = 'Variable', + StateMachine = 'StateMachine', + Command = 'Command', + Static = 'Static' +}; diff --git a/packages/mobile-render/src/services/page-config/page-config-binder.ts b/packages/mobile-render/src/services/page-config/page-config-binder.ts index 75ef9256ca3b2dc2d959e1eccc0f7322af31fa2c..96c42829d79b48093f99e200419f6dd4da73d31d 100644 --- a/packages/mobile-render/src/services/page-config/page-config-binder.ts +++ b/packages/mobile-render/src/services/page-config/page-config-binder.ts @@ -1,8 +1,9 @@ import { reactive, Reactive } from 'vue'; -import { Module } from '@farris/devkit-vue'; +import { Module, ViewModel, ViewModelState } from '@farris/devkit-vue'; import { schemaMap } from '@farris/mobile-ui-vue'; -import { JsonUtil } from '../../utils/index'; +import { TypeUtil, JsonUtil } from '../../utils/index'; import { BindingUtil, BindingContext, BindingFactory } from './bindings/index'; +import { AfterComponentBindingHookContext, Plugin, PluginManager } from '../plugin/index'; /** * 页面配置绑定器 @@ -14,6 +15,16 @@ class PageConfigBinder { */ private module: Module; + /** + * 视图模型 + */ + private viewModel: ViewModel; + + /** + * 插件管理器 + */ + private pluginManager: PluginManager; + /** * 原始的页面配置 */ @@ -25,23 +36,26 @@ class PageConfigBinder { private pageConfig: Reactive; /** - * 绑定上下文 + * 绑定工厂 */ - private bindingContext: BindingContext; + private bindingFactory: BindingFactory; /** - * 绑定工厂 + * 绑定上下文 */ - private bindingFactory: BindingFactory; + private bindingContext: BindingContext; + /** * 构造函数 */ - constructor(module: Module, pageConfig: any) { + constructor(module: Module, viewModel: ViewModel, pageConfig: any) { this.module = module; + this.viewModel = viewModel; + this.pluginManager = PluginManager.getInstance(); + this.bindingContext = new BindingContext(module); this.bindingFactory = new BindingFactory(); - this.originPageConfig = pageConfig; } @@ -57,93 +71,124 @@ class PageConfigBinder { */ public bind(): Reactive { const pageConfig = reactive(JsonUtil.cloneJsonObj(this.originPageConfig)); - this.bindComponentConfig(pageConfig); + const pageSchema = schemaMap['component']; + this.bindComponentConfig(pageConfig, pageSchema); this.pageConfig = pageConfig; - return this.pageConfig; } /** * 为组件绑定属性值 */ - private bindComponentConfig(componentConfig: any, componentSchema?: any) { - const componentType = componentConfig.type; - componentSchema = componentSchema || this.getComponentSchema(componentType); - const specialProps = ['contents', 'editor', 'items']; - + private bindComponentConfig(componentConfig: any, componentSchema: any) { + const orignComponentConfig = JsonUtil.cloneJsonObj(componentConfig); Object.keys(componentConfig).forEach((propName) => { - if (specialProps.includes(propName)) { - this.bindSpecialProp(propName, componentConfig, componentSchema); - return; - } - - const bindingConfig = componentConfig[propName]; - const isBinding = BindingUtil.isBinding(bindingConfig); - if (!isBinding) { - return; - } - - const binding = this.bindingFactory.create(bindingConfig.type); - binding.init(bindingConfig, componentConfig, propName, this.bindingContext); + const propConfig = componentConfig[propName]; + this.bindProp(propName, propConfig, componentConfig, componentSchema); }); + + const hookContext: AfterComponentBindingHookContext = { + viewModel: this.viewModel, + componentSchema, + componentConfig: orignComponentConfig, + bindedComponentConfig: componentConfig + }; + this.pluginManager.runHooks('afterComponentBinding', hookContext); } /** - * 绑定特殊属性 + * 绑定组件属性 */ - private bindSpecialProp(propName: string, componentConfig: any, componentSchema: any) { - // 特殊属性:contents - if (propName === 'contents') { - this.bindChildComponentsChonfigs(componentConfig.contents); + public bindProp(propName: string, propConfig: any, componentConfig: any, componentSchema: any) { + // 属性值是一个绑定 + const isBinding = BindingUtil.isBinding(propConfig); + if (isBinding) { + this.bindBindingProp(propName, propConfig, componentConfig, componentSchema); + return; } - // 特殊属性:Editor组件 - if (propName === 'editor') { - this.bindEditorConfig(componentConfig.editor); + // 属性值是一个数组 + if (TypeUtil.isArray(propConfig)) { + this.bindArrayProp(propName, propConfig, componentConfig, componentSchema); + return; } - // 特殊属性:items - if (propName === 'items') { - this.bindItemConfigs(componentConfig.items, componentSchema); + // 属性值是个对象 + if (TypeUtil.isObject(propConfig)) { + this.bindObjectProp(propName, propConfig, componentConfig, componentSchema); + return; } } /** - * 绑定子组件配置 + * 绑定值是绑定的属性 + */ + public bindBindingProp(propName: string, propConfig: any, componentConfig: any, componentSchema: any): void { + const binding = this.bindingFactory.create(propConfig.type); + binding.init(propConfig, componentConfig, propName, this.bindingContext); + return; + } + + /** + * 绑定值是数组的属性 */ - private bindChildComponentsChonfigs(childComponentConfigs: any[]): void { - childComponentConfigs.forEach((childComponentConfig) => { - this.bindComponentConfig(childComponentConfig); + private bindArrayProp(arrayPropName: string, arrayPropConfig: any[], componentConfig: any, componentSchema: any): void { + const arrayPropSchema = BindingUtil.getPropSchema(arrayPropName, componentSchema); + arrayPropConfig.forEach((arrayItemConfig: any) => { + const arrayItemSchema = this.getArrayItemSchema(arrayItemConfig, arrayPropSchema); + this.bindComponentConfig(arrayItemConfig, arrayItemSchema); }); } /** - * 绑定编辑器配置 + * 获取数组项的Schema + * @summary + * 子元素可能存在3种情况: + * 1、组件:ContentContainer->contents,从schemaMap中获取子元素Schema; + * 2、类型固定的对象:Toolbar->items,从属性的详细描述中获取子元素Schema; + * 3、类型不固定的数据:Picker->data,没有Schema; */ - private bindEditorConfig(editorConfig: any): void { - this.bindComponentConfig(editorConfig); + private getArrayItemSchema(arrayItemConfig: any, arrayPropSchema: any): any | null { + if (arrayPropSchema && arrayPropSchema.items) { + return arrayPropSchema.items; + } + + if (arrayItemConfig.type) { + const arrayItemType = arrayItemConfig.type; + return schemaMap[arrayItemType] || null; + } + + return null; } /** - * 绑定Items属性 + * 获取对象的Schema */ - private bindItemConfigs(itemConfigs: any[], parentComponentSchema: any) { - itemConfigs.forEach((itemConfig) => { - const itemsPropSchema = parentComponentSchema.properties.items; - this.bindComponentConfig(itemConfig, itemsPropSchema.items); - }); + private bindObjectProp(objPropName: string, objPropConfig: any, componentConfig: any, componentSchema: any): void { + const objPropSchema = BindingUtil.getPropSchema(objPropName, componentSchema); + const objSchema = this.getObjectSchema(objPropConfig, objPropSchema); + this.bindComponentConfig(objPropConfig, objSchema); } /** - * 获取组件描述 + * 获取对象属性值的 + * @summary + * 属性值可能存在3种情况: + * 1、组件:FormGroup->editor,从schemaMap中获取组件的Schema; + * 2、类型固定的对象: FormGroup->appearance,从属性的描述中获取对象的Schema; + * 3、类型不固定的数据:没有Schema; */ - private getComponentSchema(componentType: string): any { - const componentSchema = schemaMap[componentType]; - if (!componentSchema) { - throw new Error(`ComponentScham(type=${componentType}) does not exist`); + private getObjectSchema(objPropConfig: any, objPropSchema: any): any | null { + if (objPropSchema && objPropSchema.properties) { + return objPropSchema; + } + + if (objPropConfig.type) { + const objType = objPropConfig.type; + return schemaMap[objType] || null; } - return componentSchema; + return null; } } diff --git a/packages/mobile-render/src/services/plugin/index.ts b/packages/mobile-render/src/services/plugin/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5a70f28f9152b8a05567ab792d479b07407fa1e6 --- /dev/null +++ b/packages/mobile-render/src/services/plugin/index.ts @@ -0,0 +1,5 @@ +export * from './types'; +export * from './plugin'; +export * from './plugins/index'; +export * from './plugin-creators'; +export * from './plugin-manager'; diff --git a/packages/mobile-render/src/services/plugin/plugin-creators.ts b/packages/mobile-render/src/services/plugin/plugin-creators.ts new file mode 100644 index 0000000000000000000000000000000000000000..a8b71950e91c179c5dc6a2a502559d581c8e2e66 --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugin-creators.ts @@ -0,0 +1,21 @@ +import { PluginContext } from './types'; +import { Plugin } from './plugin'; +import { ListViewPlugin, LookupPlugin } from './plugins'; + +/** + * 内置构件构造函数 + */ +const buildInPluginCtors = [ListViewPlugin, LookupPlugin]; + +/** + * 创建内置插件 + */ +function createBuildInPlugins(context: PluginContext): Plugin[] { + const plugins = buildInPluginCtors.map((pluginCtor) => { + return new pluginCtor(context) as Plugin; + }); + + return plugins; +} + +export { createBuildInPlugins }; diff --git a/packages/mobile-render/src/services/plugin/plugin-manager.ts b/packages/mobile-render/src/services/plugin/plugin-manager.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f578f93976d81a27ec96e683e8d2ba98c085e7f --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugin-manager.ts @@ -0,0 +1,87 @@ +/* eslint-disable no-use-before-define */ +import { Hook, HookName, HOOK_NAMES } from './types'; +import { Plugin } from './plugin'; + +/** + * 插件管理器 + */ +class PluginManager { + + /** + * 静态实例 + */ + private static instance: PluginManager; + + /** + * 获取实例 + */ + public static getInstance(plugins?: Plugin[]): PluginManager { + if (!this.instance && Array.isArray(plugins)) { + this.instance = new PluginManager(plugins); + } + + if (!this.instance) { + throw new Error(`PluginManager instance is used before it is created `); + } + + return this.instance; + } + + /** + * 插件集合 + */ + private plugins: Plugin[]; + + /** + * 构造缓存 + */ + private hooksCache: Map; + + /** + * 构造函数 + */ + private constructor(plugins: Plugin[]) { + this.plugins = plugins; + this.initHookCache(plugins); + } + + /** + * 搜集插件中的钩子 + */ + private initHookCache(plugins: Plugin[]) { + this.hooksCache = new Map(); + + if (!Array.isArray(plugins)) { + return; + } + + plugins.forEach((plugin: any) => { + HOOK_NAMES.forEach((hookName: HookName) => { + if (typeof plugin[hookName] !== 'function') { + return; + } + const hooks = this.hooksCache.get(hookName) || []; + this.hooksCache.set(hookName, hooks); + + const hook = plugin[hookName].bind(plugin); + hooks.push(hook); + }); + }); + } + + /** + * 运行时钩子方法 + */ + public runHooks(hookName: HookName, context: any): void { + const hooks = this.hooksCache.get(hookName); + if (!Array.isArray(hooks)) { + return; + } + + hooks.forEach((hook) => { + hook(context); + }); + } +} + +export { PluginManager }; diff --git a/packages/mobile-render/src/services/plugin/plugin.ts b/packages/mobile-render/src/services/plugin/plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..d37dd2c32d5f7265210cee0b9257b3acf5539519 --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugin.ts @@ -0,0 +1,21 @@ +import { PluginContext } from './types'; + +/** + * 插件基类 + */ +class Plugin { + + /** + * 插件上下文 + */ + protected context: PluginContext; + + /** + * 构造函数 + */ + constructor(context: PluginContext) { + this.context = context; + } +} + +export { Plugin }; diff --git a/packages/mobile-render/src/services/plugin/plugins/index.ts b/packages/mobile-render/src/services/plugin/plugins/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..48ffbb3ce3ed6eec2f05c51c847c33eebb9df92d --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugins/index.ts @@ -0,0 +1,2 @@ +export * from './listview-plugin/index'; +export * from './lookup-plugin/index'; diff --git a/packages/mobile-render/src/services/plugin/plugins/listview-plugin/index.ts b/packages/mobile-render/src/services/plugin/plugins/listview-plugin/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e615416c968e47bcb3dea1bbc1cae76a3c0919d9 --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugins/listview-plugin/index.ts @@ -0,0 +1 @@ +export * from './listview-plugin'; diff --git a/packages/mobile-render/src/services/plugin/plugins/listview-plugin/listview-plugin.ts b/packages/mobile-render/src/services/plugin/plugins/listview-plugin/listview-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4903be29068633a0ac87c0b48c7ada6a2e3273d --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugins/listview-plugin/listview-plugin.ts @@ -0,0 +1,31 @@ +import { PluginHooks, AfterPageInitHookContext, AfterComponentBindingHookContext } from '../../types'; +import { Plugin } from '../../plugin'; + +/** + * 帮助插件 + */ +class ListViewPlugin extends Plugin implements PluginHooks { + + /** + * 页面配置绑定后扩展 + */ + public afterComponentBinding(context: AfterComponentBindingHookContext): void { + if (context.componentConfig.type !== 'list-view') { + return; + } + + const { viewModel, componentConfig, bindedComponentConfig } = context; + const originItemClickHandler = bindedComponentConfig['onItemClick']; + bindedComponentConfig['onItemClick'] = (event: any) => { + const { id } = event.data; + const { path } = componentConfig.dataSource; + viewModel.entityStore.changeCurrentEntityByPath(path, id); + + if (originItemClickHandler) { + originItemClickHandler(event); + } + }; + } +} + +export { ListViewPlugin }; diff --git a/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/index.ts b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5f31c967d05ca943f6d4af2da0197ab5438d45cc --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/index.ts @@ -0,0 +1 @@ +export * from './lookup-plugin'; diff --git a/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-data-service.ts b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-data-service.ts new file mode 100644 index 0000000000000000000000000000000000000000..4d8bcb3e8fb317cf6f422973284189a8ad0ee4d9 --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-data-service.ts @@ -0,0 +1,82 @@ +import { Entity, HttpClient, HttpMethod, HttpMethods, HttpRequestConfig, ViewModel, ViewModelState } from '@farris/devkit-vue'; +import { BefRepository, RequestInfoUtil } from '@farris/bef-vue'; + +/** + * 帮助取数服务 + */ +class LookupDataService { + + /** + * 视图模型上下文 + */ + private viewModel: ViewModel; + + /** + * 实体仓库 + */ + private befRepository: BefRepository; + + /** + * Http客户端 + */ + private httpClient: HttpClient; + + /** + * 帮助上下文 + */ + public context: any; + + /** + * 构造函数 + */ + constructor(viewModel: ViewModel) { + this.viewModel = viewModel; + this.befRepository = this.viewModel.repository as BefRepository; + this.httpClient = this.befRepository.apiProxy.httpClient; + } + + /** + * 帮助取数 + */ + public getData(helpMetadataId: string, queryParam?: any): Promise { + const tableName = helpMetadataId.split('.')[0]; + const labelId = helpMetadataId.split('.')[1]; + queryParam = queryParam || {}; + return this.extendGetHelpData(labelId, tableName, queryParam); + } + + /** + * 自定义请求 + */ + public request(method: HttpMethod, url: string, requestConfig: HttpRequestConfig): Promise { + return this.httpClient.request(method, url, requestConfig); + } + + /** + * 扩展的帮助取数 + */ + private extendGetHelpData(labelId: string, tableName: string, queryParam: any): Promise { + const url = `${this.befRepository.apiProxy.baseUrl}/extension/elementhelps`; + const requestInfo = RequestInfoUtil.buildRequestInfo(this.befRepository); + + const currentEntity = this.viewModel.state.entityState.currentEntity as any; + const currentForm = { id: currentEntity.id }; + + const body = { + labelId: labelId, + nodeCode: tableName, + queryParam: { ...queryParam, currentForm }, + requestInfo: requestInfo + }; + + const requestConfig: HttpRequestConfig = { + body: body + }; + + return this.befRepository.apiProxy.request(HttpMethods.PUT, url, requestConfig); + } + +} + +export { LookupDataService }; + diff --git a/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-mapping-service.ts b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-mapping-service.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0f39580aa2369a8905a1d6e62b5028d1dd06448 --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-mapping-service.ts @@ -0,0 +1,169 @@ +import { ViewModel, ViewModelState } from '@farris/devkit-vue'; + +/** + * 帮助映射服务 + * @summary + * SrcField:帮助实体字段 + * TargetField:表单实体字段 + */ +export class LookupMappingService { + + /** + * 视图模型 + */ + private viewModel: ViewModel; + + /** + * 构造函数 + */ + constructor(viewModelContext: ViewModel) { + this.viewModel = viewModelContext; + } + + /** + * 获取主键值 + */ + public getIdValue(mappingFields: any, srcIdField: string): any { + const targetIdFieldPaths = this.getTargetFieldPaths(srcIdField, mappingFields); + if (!Array.isArray(targetIdFieldPaths) || targetIdFieldPaths.length === 0) { + return; + } + const idValue = this.viewModel.entityStore.getValueByPath(targetIdFieldPaths[0]); + + return idValue; + } + + /** + * 映射字段(帮助控件会直接调用) + * @summary + * mapFields格式形如: + * { + * id: "assoField.assoField", + * code: "assoField.assoField_Code", + * name: "assoField.assoField_Name" + * } + */ + public mapFields(srcDataArray: any[], mappingFields: any, options: any = {}) { + if (!mappingFields) { + return; + } + + const isMultiSelect = options.multiSelect || false; + const isSrcDataArrayEmpty = !Array.isArray(srcDataArray) || srcDataArray.length === 0; + const srcFieldPathArray = this.getSrcFieldPaths(mappingFields, !isSrcDataArrayEmpty); + + if (isSrcDataArrayEmpty === true) { + + // 清空时忽略第一次的主键映射 + const isPrimaryKeyMapping = options.isPrimaryKeyMapping || false; + if (isPrimaryKeyMapping === true) { + return; + } + srcFieldPathArray.forEach((srcFieldPath: string) => { + this.clearTargetFieldValue(srcFieldPath, mappingFields, isMultiSelect); + }); + } else { + + // 设置字段值 + srcFieldPathArray.forEach((srcFieldPath: string) => { + this.setTargetFieldValue(srcFieldPath, mappingFields, srcDataArray, isMultiSelect); + }); + } + } + + /** + * 向目标字段赋值 + */ + private setTargetFieldValue(srcFieldPath: string, mappingFields: any, srcDataArray: any[], isMultiSelect: boolean): void { + + // 获取字段值 + let targetFieldVal: any = ''; + if (isMultiSelect === true) { + targetFieldVal = srcDataArray.map((selectedHelpRow: any) => { + return this.getSrcFieldValue(srcFieldPath, selectedHelpRow); + }).join(','); + } else { + targetFieldVal = this.getSrcFieldValue(srcFieldPath, srcDataArray[0]); + } + + // 更新目标字段值 + const targetFieldBindingPath = this.getTargetFieldPaths(srcFieldPath, mappingFields); + const targetEntityStore = this.viewModel.entityStore; + targetFieldBindingPath.forEach((targetFieldPathArray: any) => { + targetEntityStore.setValueByPath(targetFieldPathArray, targetFieldVal); + }); + } + + /** + * 清空目标字段的值 + */ + private clearTargetFieldValue(srcFieldPath: string, mappingFields: any, isMultiSelect: boolean): void { + const targetFieldBindingPaths = this.getTargetFieldPaths(srcFieldPath, mappingFields); + targetFieldBindingPaths.forEach((targetFieldPathArray: any) => { + if (isMultiSelect === true) { + this.viewModel.entityStore.setValueByPath(targetFieldPathArray, ''); + } else { + // Not Supported + } + }); + } + + /** + * 获取源实体的字段绑定路径 + * @summary + * 1、设置值:需要先设置ID的值 + * 2、清空值:需要最后清空ID的值 + */ + private getSrcFieldPaths(mappingFields: any, isSetValue: boolean): string[] { + let srcFieldPathArray = Object.keys(mappingFields); + + // 交换ID字段顺序 + const idIndex = srcFieldPathArray.findIndex((item) => item === 'id'); + if (srcFieldPathArray.includes('id')) { + srcFieldPathArray.splice(idIndex, 1); + srcFieldPathArray = isSetValue ? ['id', ...srcFieldPathArray] : [...srcFieldPathArray, 'id']; + } + + return srcFieldPathArray; + } + + /** + * 获取目标实体的字段绑定路径 + */ + private getTargetFieldPaths(srcFieldPath: string, mappingFields: any): string[] { + const viewModelBindingPath = this.getViewModelBindingPath(); + const targetFieldPaths = []; + mappingFields[srcFieldPath].split(',').forEach((targetFieldPath: string) => { + if (!targetFieldPath) { + return; + } + targetFieldPath = '/' + targetFieldPath.replace('.', '/'); + targetFieldPath = `${viewModelBindingPath}${targetFieldPath}`; + targetFieldPaths.push(targetFieldPath); + }); + return targetFieldPaths; + } + + /** + * 获取源实体数据中的字段值 + */ + private getSrcFieldValue(srcFieldPath: string, srcData: any): any { + let srcFieldValue = ''; + if (srcFieldPath.indexOf('.') === -1) { + srcFieldValue = srcData[srcFieldPath]; + } else { + srcFieldValue = srcFieldPath.split('.').reduce((data, key) => { + return data[key]; + }, srcData); + } + return srcFieldValue; + } + + /** + * 获取视图模型的绑定路径 + */ + private getViewModelBindingPath(): string { + const { bindingPath } = this.viewModel; + return bindingPath === '/' ? '' : bindingPath; + } +} diff --git a/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-plugin.ts b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-plugin.ts new file mode 100644 index 0000000000000000000000000000000000000000..0cc40d5c9346a7f923166beb23c7381bb093927e --- /dev/null +++ b/packages/mobile-render/src/services/plugin/plugins/lookup-plugin/lookup-plugin.ts @@ -0,0 +1,58 @@ +import { provide } from 'vue'; +import { LOOKUP_HTTP_SERVICE_TOKEN } from '@farris/mobile-ui-vue'; +import { PluginHooks, AfterPageInitHookContext, AfterComponentBindingHookContext } from '../../types'; +import { Plugin } from '../../plugin'; +import { LookupDataService } from './lookup-data-service'; +import { LookupMappingService } from './lookup-mapping-service'; + +/** + * 帮助插件 + */ +class LookupPlugin extends Plugin implements PluginHooks { + + /** + * 页面初始化后 + */ + public afterPageInit(context: AfterPageInitHookContext): void { + const { viewModel } = context; + const lookupDataServce = new LookupDataService(viewModel); + provide(LOOKUP_HTTP_SERVICE_TOKEN, lookupDataServce); + } + + /** + * 页面配置绑定后扩展 + */ + public afterComponentBinding(context: AfterComponentBindingHookContext): void { + if (context.componentConfig.type !== 'lookup') { + return; + } + + const { viewModel, componentConfig, bindedComponentConfig } = context; + const mappingService = new LookupMappingService(viewModel); + const mappingFields = JSON.parse(componentConfig.mappingFields); + + // 注册变化事件,实现字段映射 + const originChangeHandler = bindedComponentConfig['onChange']; + bindedComponentConfig['onChange'] = (event: any) => { + mappingService.mapFields([event], mappingFields); + this.updateIdValue(componentConfig, bindedComponentConfig, mappingFields, mappingService); + + originChangeHandler(event); + }; + + this.updateIdValue(componentConfig, bindedComponentConfig, mappingFields, mappingService); + } + + /** + * 更新idValue属性 + */ + private updateIdValue(componentConfig: any, bindedComponentConfig: any, mappingFields: any, mappingService: any): void { + const idFieldPath = componentConfig.dataSource.idField; + const idValue = mappingService.getIdValue(mappingFields, idFieldPath); + + // 更新idValue属性 + bindedComponentConfig['idValue'] = idValue; + } +} + +export { LookupPlugin }; diff --git a/packages/mobile-render/src/services/plugin/types.ts b/packages/mobile-render/src/services/plugin/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bd63756910d73b80d0816d7ed75f3739ccbc2c4 --- /dev/null +++ b/packages/mobile-render/src/services/plugin/types.ts @@ -0,0 +1,101 @@ +import { App } from 'vue'; +import { Router } from 'vue-router'; +import { Devkit, Module, ViewModel, ViewModelState } from '@farris/devkit-vue'; + +/** + * 钩子名称 + */ +type HookName = 'afterAppInit' | 'afterModuleInit' | 'afterPageInit' | 'afterComponentBinding'; + +/** + * 构造名称集合 + */ +const HOOK_NAMES: HookName[] = [ + 'afterAppInit', 'afterModuleInit', 'afterPageInit', 'afterComponentBinding' +]; + +/** + * 钩子方法 + */ +type Hook = (context: any) => void; + +/** + * 插件Hooks + */ +interface PluginHooks { + + /** + * 应用初始化后 + */ + afterAppInit?: Hook; + + /** + * 模块初始化后 + */ + afterModuleInit?: Hook; + + /** + * 视图上下文初始化后 + */ + afterPageInit?: Hook; + + /** + * 组件绑定后 + */ + afterComponentBinding?: Hook; +} + +/** + * 构造上下文 + */ +interface HookContext { + hookName: string; +} + +/** + * 应用初始化后上下文 + */ +interface AfterAppInitHookContext extends HookContext { + app: App; + router: Router; + devkit: Devkit; +} + +/** + * 模块初始化后上下文 + */ +interface AfterModuleInitHookContext extends HookContext { + module: Module; +} + +/** + * 组件初始化后上下文 + */ +interface AfterPageInitHookContext extends HookContext { + viewModel: ViewModel; +} + +/** + * 组件绑定后上下文 + */ +interface AfterComponentBindingHookContext { + viewModel: ViewModel; + componentSchema: any; + componentConfig: any; + bindedComponentConfig: any; +} + +/** + * 插件上下文 + */ +interface PluginContext { + app: App; + router: Router; + devkit: Devkit; +} + +export { + HookName, HOOK_NAMES, Hook, + AfterAppInitHookContext, AfterModuleInitHookContext, AfterPageInitHookContext, AfterComponentBindingHookContext, + PluginHooks, PluginContext +}; diff --git a/packages/mobile-render/src/utils/type-util.ts b/packages/mobile-render/src/utils/type-util.ts index 3ad67ca041c64a108e13a85e67e0865b32153c57..77106b425f9166aab4669aef2c31c68974cbe82b 100644 --- a/packages/mobile-render/src/utils/type-util.ts +++ b/packages/mobile-render/src/utils/type-util.ts @@ -108,10 +108,17 @@ class TypeUtil { /** * 是否为对象 */ - public static isObject(value: any) { + public static isObject(value: any): boolean { return value !== null && typeof value === 'object'; } + /** + * 是否是数组 + */ + public static isArray(value: any): boolean { + return Array.isArray(value); + } + /** * 是否为字符串 */ diff --git a/packages/mobile-render/src/views/page/page-runner.ts b/packages/mobile-render/src/views/page/page-runner.ts index 63feee192c6f59dbd0d7493a0c44dcb21928fd33..60e4693839e65b367c80b156738bf18933ef6e1d 100644 --- a/packages/mobile-render/src/views/page/page-runner.ts +++ b/packages/mobile-render/src/views/page/page-runner.ts @@ -1,7 +1,7 @@ import { provide, Reactive } from 'vue'; import { Router } from 'vue-router'; import { Devkit, Module, ViewModel, ViewModelState, createViewModel, VIEWMODEL_INJECTION_TOKEN, Injector } from '@farris/devkit-vue'; -import { MetadataCacheService, PageConfigBuilder, PageConfigBinder } from '../../services/index'; +import { MetadataCacheService, PageConfigBuilder, PageConfigBinder, PluginManager } from '../../services/index'; /** * 运行页面 @@ -33,12 +33,18 @@ class PageRunner { */ private pageConfig: Reactive; + /** + * 插件管理器 + */ + private pluginManager: PluginManager; + /** * 构造函数 */ constructor(devkit: Devkit, router: Router) { this.devkit = devkit; this.router = router; + this.pluginManager = PluginManager.getInstance(); } /** @@ -49,6 +55,7 @@ class PageRunner { this.initViewModel(); this.initUIState(); this.initPageConfig(); + this.pluginManager.runHooks('afterPageInit', { viewModel: this.viewModel }); } /** @@ -115,15 +122,9 @@ class PageRunner { const pageConfigBuilder = new PageConfigBuilder(formMetadata, pageId); const pageConfig = pageConfigBuilder.build(); - console.log('----------pageConfig----------'); - console.log(pageConfig); - // 绑定页面配置 - const pageConfigBinder = new PageConfigBinder(this.module, pageConfig); + const pageConfigBinder = new PageConfigBinder(this.module, this.viewModel, pageConfig); this.pageConfig = pageConfigBinder.bind(); - - console.log('----------binded pageConfig----------'); - console.log(this.pageConfig); } /** diff --git a/packages/mobile-render/src/views/page/page.vue b/packages/mobile-render/src/views/page/page.vue index 343955e7f976abb989253545725de78c0bef878a..569dcb20b3eac4ba344805cf8afa64b720ca6efe 100644 --- a/packages/mobile-render/src/views/page/page.vue +++ b/packages/mobile-render/src/views/page/page.vue @@ -1,5 +1,5 @@ diff --git a/packages/mobile-render/tsconfig.node.json b/packages/mobile-render/tsconfig.node.json index d8915f131adf0c7ed78b511e1d8cae09947858d3..7fed214121acaa7dee0c3d91e2a5ebca73829536 100644 --- a/packages/mobile-render/tsconfig.node.json +++ b/packages/mobile-render/tsconfig.node.json @@ -7,8 +7,5 @@ "verbatimModuleSyntax": false, "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true - }, - "include": [ - "vite.config.ts" - ] + } } \ No newline at end of file diff --git a/packages/mobile-render/vite.config.ts b/packages/mobile-render/vite.config.ts deleted file mode 100644 index 4c68bb48c60634d5415ea698456095740e60676b..0000000000000000000000000000000000000000 --- a/packages/mobile-render/vite.config.ts +++ /dev/null @@ -1,52 +0,0 @@ -// / -import path from 'path'; -import { defineConfig } from 'vite'; -import type { InlineConfig } from 'vitest'; -import type { UserConfig } from 'vite'; -import vue from '@vitejs/plugin-vue'; -import vueJsx from '@vitejs/plugin-vue-jsx'; - -interface VitestConfigExport extends UserConfig { - test: InlineConfig; -} - -// https://vitejs.dev/config/ -export default defineConfig({ - // base: process.env.NODE_ENV === 'production' ? '/platform/common/web/mobile-renderer/' : './', - build: { - minify: false - }, - plugins: [vue(), vueJsx()], - test: { - globals: true, - environment: 'happy-dom', - include: ['**/*.test.tsx'] - }, - server: { - proxy: { - "/api": { - target: "http://127.0.0.1:5200", - changeOrigin: true, - secure: false - }, - "/apps": { - target: "http://127.0.0.1:5200", - changeOrigin: true, - secure: false - } - } - }, - resolve: { - alias: { - 'vue': 'vue/dist/vue.esm-bundler.js', - "@farris/devkit-vue": path.resolve(__dirname, "../devkit/lib/index"), - "@farris/bef-vue": path.resolve(__dirname, "../bef/lib/index"), - "@farris/mobile-ui-vue": path.resolve(__dirname, "../mobile-ui-vue/components/index"), - "@farris/mobile-command-services-vue": path.resolve(__dirname, "../mobile-command-services/lib/index"), - "@components": path.resolve(__dirname, "../mobile-ui-vue/components"), - "@/components": path.resolve(__dirname, "../mobile-ui-vue/components"), - - } - }, - logLevel: 'error' -} as VitestConfigExport); diff --git a/packages/mobile-ui-vue/.gitignore b/packages/mobile-ui-vue/.gitignore index bef97693ba64ab5b2b3dea6be3d88776698eee39..1c11aea63f56f621cd6d1755a6733287d1f64328 100644 --- a/packages/mobile-ui-vue/.gitignore +++ b/packages/mobile-ui-vue/.gitignore @@ -9,6 +9,7 @@ lerna-debug.log* node_modules dist +lib package dist-ssr *.local diff --git a/packages/mobile-ui-vue/components/button-group/index.ts b/packages/mobile-ui-vue/components/button-group/index.ts index 4bb2569a6ccba3cf3caf163eafed9e9d93e2abb5..f825aa66e68692463c7fedc11898e3f82fb0579c 100644 --- a/packages/mobile-ui-vue/components/button-group/index.ts +++ b/packages/mobile-ui-vue/components/button-group/index.ts @@ -1,14 +1,14 @@ -import { Plugin } from 'vue'; import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile-ui-vue/common'; import ButtonGroupInstallless from './src/button-group.component'; import { propsResolverGenerator } from './src/button-group.props'; +import ButtonGroupDesign from './src/designer/card.design.component'; const BUTTON_GROUP_REGISTERED_NAME = 'button-group'; const ButtonGroup = withInstall(ButtonGroupInstallless); withRegister(ButtonGroup, { name: BUTTON_GROUP_REGISTERED_NAME, propsResolverGenerator }); -// withRegisterDesigner(ButtonGroup, { name: BUTTON_GROUP_REGISTERED_NAME, propsResolverGenerator, designerComponent: InputGroupDesign }); +withRegisterDesigner(ButtonGroup, { name: BUTTON_GROUP_REGISTERED_NAME, propsResolverGenerator, designerComponent: ButtonGroupDesign }); export * from './src/button-group.props'; export { ButtonGroup }; -export default ButtonGroup as typeof ButtonGroup & Plugin; +export default ButtonGroup; diff --git a/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx b/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx index a4488f4c7eb77f303e1c67fd175b44507e53a4e8..d059f45e8760e1d07af9da3fabcffb180340db96 100644 --- a/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx +++ b/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx @@ -1,4 +1,4 @@ -import { computed, defineComponent } from 'vue'; +import { defineComponent, computed, ref } from 'vue'; import { useBem } from '@farris/mobile-ui-vue/common'; import FButton from '@farris/mobile-ui-vue/button'; import { Icon } from '@farris/mobile-ui-vue/icon'; @@ -10,6 +10,7 @@ export default defineComponent({ emits: ['click'], setup(props: ButtonGroupProps, context) { const { bem } = useBem(BUTTON_GROUP_NAME); + const elementRef = ref(); const buttonItems = computed(() => { return props.items || []; @@ -40,7 +41,7 @@ export default defineComponent({ function isButtonItemPlain(buttonItem: ButtonItem): boolean { if (isDefaultMode.value) { - return buttonItem.plain; + return !!buttonItem.plain; } const shouldDefaultToPlain = props.mode === 'outline' || props.mode === 'text'; return buttonItem.plain ?? shouldDefaultToPlain; @@ -55,7 +56,7 @@ export default defineComponent({ function isButtonItemNoBorder(buttonItem: ButtonItem): boolean { if (isDefaultMode.value) { - return buttonItem.noBorder; + return !!buttonItem.noBorder; } return props.mode === 'text'; } @@ -65,6 +66,7 @@ export default defineComponent({ [bem('item')]: true, [bem('item', 'bare-vertical')]: buttonItem.variant === 'bare-vertical', [bem('item', 'disabled')]: isButtonItemDisabled(buttonItem), + [buttonItem.customClass ?? '']: typeof buttonItem.customClass === 'string' && buttonItem.customClass, }; }; @@ -79,7 +81,9 @@ export default defineComponent({ function renderDefaultButton(buttonItem: ButtonItem) { return ( onButtonItemClick(buttonItem)} > {buttonItem.icon && } @@ -115,8 +121,14 @@ export default defineComponent({ return buttonItem.variant === 'bare-vertical' ? renderBareVerticalButton(buttonItem) : renderDefaultButton(buttonItem); } + context.expose({ elementRef }); + return () => ( -
+
{buttonItems.value.map((buttonItem) => renderButtonItem(buttonItem))}
); diff --git a/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts b/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts index 843146543818f5a09709a87770468dabd5928ea9..ec5f39d8c9551cc608f8c3327c505400b184a641 100644 --- a/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts +++ b/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts @@ -9,12 +9,15 @@ export const BUTTON_GROUP_NAME = 'fm-button-group'; export type ButtonGroupMode = 'default' | 'group' | 'outline' | 'text'; export type ButtonItemVariant = 'default' | 'bare-vertical'; -export type ButtonItem = ButtonProps & { +export type ButtonItem = Partial void; -}; + customClass: string; + customStyle: string; + onClick: (eventParam?: any) => void; +}>; export const buttonGroupProps = { @@ -41,6 +44,9 @@ export const buttonGroupProps = { /** 是否垂直排列 */ vertical: { type: Boolean, default: false }, + + customClass: { type: String, default: '' }, + customStyle: { type: String, default: '' }, }; /** diff --git a/packages/mobile-ui-vue/components/button-group/src/button-group.scss b/packages/mobile-ui-vue/components/button-group/src/button-group.scss index 6a73cfc0a49633e321263e3e1a6b8079bbefdf95..5660c5712956a68075efe1812be00a5bfb55330a 100644 --- a/packages/mobile-ui-vue/components/button-group/src/button-group.scss +++ b/packages/mobile-ui-vue/components/button-group/src/button-group.scss @@ -18,6 +18,7 @@ white-space: nowrap; flex: 0 1 auto; user-select: none; + outline: none !important; &--bare-vertical { display: inline-flex; @@ -68,22 +69,22 @@ border-radius: 0; } - &--horizontal:not(&--mode-default) &__item:first-child { + &--horizontal:not(&--mode-default) &__item:first-child:not(:last-child) { border-top-right-radius: 0; border-bottom-right-radius: 0; } - &--vertical:not(&--mode-default) &__item:first-child { + &--vertical:not(&--mode-default) &__item:first-child:not(:last-child) { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } - &--horizontal:not(&--mode-default) &__item:last-child { + &--horizontal:not(&--mode-default) &__item:last-child:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } - &--vertical:not(&--mode-default) &__item:last-child { + &--vertical:not(&--mode-default) &__item:last-child:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; } @@ -96,12 +97,26 @@ border-top: 0; } - &--horizontal#{&}--mode-text &__item:not(:last-child) { - border-right: var(--fm-button-border-width) solid currentColor !important; + &--horizontal#{&}--mode-text &__item:not(:last-child)::after, + &--vertical#{&}--mode-text &__item:not(:last-child)::after { + content: ''; + display: block; + position: absolute; + background-color: var(--fm-gray-3); } - &--vertical#{&}--mode-text &__item:not(:last-child) { - border-bottom: var(--fm-button-border-width) solid currentColor !important; + &--horizontal#{&}--mode-text &__item:not(:last-child)::after { + width: 1px; + right: 0; + top: var(--fm-padding-xs); + bottom: var(--fm-padding-xs); + } + + &--vertical#{&}--mode-text &__item:not(:last-child)::after { + height: 1px; + bottom: 0; + left: var(--fm-padding-xs); + right: var(--fm-padding-xs); } &--fill { diff --git a/packages/mobile-ui-vue/components/button-group/src/designer/card.design.component.tsx b/packages/mobile-ui-vue/components/button-group/src/designer/card.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..145466524ca798c378ba84348da5d62d9205e5ff --- /dev/null +++ b/packages/mobile-ui-vue/components/button-group/src/designer/card.design.component.tsx @@ -0,0 +1,35 @@ +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { buttonGroupProps, ButtonGroupProps } from '../button-group.props'; +import { useDesignerRulesForButtonGroup } from './use-designer-rules'; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common'; +import { ButtonGroup } from '@farris/mobile-ui-vue/button-group'; + +export default defineComponent({ + name: 'FButtonGroupDesign', + props: buttonGroupProps, + emits: [], + setup(props: ButtonGroupProps, context) { + const buttonGroupRef = ref(); + const elementRef = computed(() => { + return buttonGroupRef.value?.elementRef; + }); + + const designerHostService = inject('designer-host-service'); + const designItemContext = inject('design-item-context') as DesignerItemContext; + const designerRulesComposition = useDesignerRulesForButtonGroup(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + context.expose(componentInstance.value); + + function onClick(): void { + elementRef.value?.click?.(); + } + + return () => ( + + ); + } +}); diff --git a/packages/mobile-ui-vue/components/button-group/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/button-group/src/designer/use-designer-rules.ts new file mode 100644 index 0000000000000000000000000000000000000000..e5c930c80dffe3c3b59366edb432f960a28168b9 --- /dev/null +++ b/packages/mobile-ui-vue/components/button-group/src/designer/use-designer-rules.ts @@ -0,0 +1,41 @@ +import { DesignerItemContext } from "@farris/mobile-ui-vue/common"; +import { ButtonGroupProperty } from "../property-config/button-group.property-config"; +import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; + +export function useDesignerRulesForButtonGroup(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { + + function canAccepts(draggingContext: DraggingResolveContext): boolean { + return false; + } + + function getStyles() { + return ''; + } + + function checkCanMoveComponent() { + return true; + } + + function checkCanDeleteComponent() { + return true; + } + + function hideNestedPaddingInDesginerView() { + return true; + } + + function getPropsConfig(componentId: string) { + const buttonGroupProp = new ButtonGroupProperty(componentId, designerHostService); + const { schema } = designItemContext; + return buttonGroupProp.getPropertyConfig(schema); + } + + return { + canAccepts, + getStyles, + checkCanMoveComponent, + checkCanDeleteComponent, + hideNestedPaddingInDesginerView, + getPropsConfig + }; +} diff --git a/packages/mobile-ui-vue/components/button-group/src/property-config/button-group.property-config.ts b/packages/mobile-ui-vue/components/button-group/src/property-config/button-group.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..bfa8c6387faf68239da925b9a72b72d8b6564083 --- /dev/null +++ b/packages/mobile-ui-vue/components/button-group/src/property-config/button-group.property-config.ts @@ -0,0 +1,98 @@ +import { BaseControlProperty, ToolbarItemProperty } from "@farris/mobile-ui-vue/common"; + +export class ButtonGroupProperty extends BaseControlProperty { + + private toolbarItemProperty: ToolbarItemProperty; + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + this.toolbarItemProperty = new ToolbarItemProperty(componentId, designerHostService); + } + + public getPropertyConfig(propertyData: any) { + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + // 外观 + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + + return this.propertyConfig; + } + + private isDefaultMode(propertyData: any): boolean { + const mode = propertyData?.mode; + return mode !== 'group' && mode !== 'outline' && mode !== 'text'; + } + + public getBehaviorConfig(propertyData: any) { + return super.getBehaviorConfig(propertyData, '', { + items: { + title: '按钮栏', + editor: { + type: "collection-property-editor", + textField: 'text', + modalTitle: '按钮栏编辑器', + onSelectionChange: ({ selectedData, propertyConfig }) => { + propertyConfig.value = this.toolbarItemProperty.getPropertyConfig(selectedData.value, { + showAppearance: true, + showButtonType: true, + showIcon: true, + showPlain: true, + showRound: this.isDefaultMode(propertyData), + showVariant: this.isDefaultMode(propertyData), + }); + }, + defaultComponentSchema: { + id: 'button-group-item', + text: '按钮', + visible: true, + disabled: false, + displayType: 'primary', + } + } + }, + mode: { + title: "显示模式", + description: "按钮组的显示模式", + editor: { + type: 'combo-tree', + textField: 'text', + valueField: 'value', + data: [ + { value: 'default', text: '独立按钮' }, + { value: 'group', text: '组合按钮' }, + { value: 'outline', text: '边框组合' }, + { value: 'text', text: '文本分隔' }, + ], + }, + }, + block: { + title: '是否占满整行', + description: '是否占满整行的宽度', + type: 'boolean', + }, + round: { + title: '是否圆角', + description: '是否为按钮启用圆角', + type: 'boolean', + }, + size: { + title: '按钮大小', + description: '按钮的大小', + editor: { + type: 'combo-tree', + textField: 'text', + valueField: 'value', + data: [ + { value: 'large', text: '大号按钮' }, + { value: 'normal', text: '普通按钮' }, + { value: 'small', text: '小号按钮' }, + { value: 'mini', text: '迷你按钮' }, + ], + }, + } + }); + } + +} diff --git a/packages/mobile-ui-vue/components/button-group/src/schema/button-group.schema.json b/packages/mobile-ui-vue/components/button-group/src/schema/button-group.schema.json index 5688518ead452d58ce7cfbfbb58d78d26fdd50b8..f9f7c17b46d6cec00d8763f2df565f4f78ee252b 100644 --- a/packages/mobile-ui-vue/components/button-group/src/schema/button-group.schema.json +++ b/packages/mobile-ui-vue/components/button-group/src/schema/button-group.schema.json @@ -6,7 +6,7 @@ "type": "object", "properties": { "id": { - "description": "唯一标志", + "description": "唯一标识", "type": "string" }, "type": { @@ -52,20 +52,34 @@ "type": "string", "default": "group" }, + "size": { + "description": "按钮大小", + "type": "string", + "default": "normal" + }, "items": { "description": "按钮项集合", "type": "array", + "default": [ + { + "id": "button-group-item", + "visible": true, + "disabled": false, + "text": "按钮", + "displayType": "primary" + } + ], "items": { "type": "object", "properties": { "id": { - "description": "唯一标志", + "description": "唯一标识", "type": "string" }, "type": { "description": "组件类型", "type": "string", - "default": "button-group" + "default": "button-group-item" }, "visible": { "description": "是否可见", @@ -78,23 +92,19 @@ "default": false }, "displayType": { - "description": "显示类型", + "description": "按钮类型", "type": "string", - "default": "default" + "default": "primary" }, "text": { "description": "文本", - "type": "string" - }, - "block": { - "description": "沾满整行", - "type": "boolean", - "default": false + "type": "string", + "default": "按钮" }, "round": { "description": "圆角按钮", "type": "boolean", - "default": false + "default": true }, "plain": { "description": "朴素按钮", @@ -105,6 +115,10 @@ "description": "图标", "type": "string" }, + "variant": { + "description": "变体", + "type": "string" + }, "onClick": { "description": "点击事件", "type": "string" @@ -116,22 +130,17 @@ "required": [ "id", "type" - ], - "ignore": [ - "id", - "type", - "appearance" ] } } }, "required": [ - "id", - "type" - ], - "ignore": [ "id", "type", - "appearance" + "items", + "round", + "block", + "mode", + "size" ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/button-group/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/button-group/src/schema/schema-mapper.ts index 05f8ba178f90fc79f669a5e3f152c7c8fcddf020..b09130b36133ba5a3a49ab0ef2812bece68a2170 100644 --- a/packages/mobile-ui-vue/components/button-group/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/button-group/src/schema/schema-mapper.ts @@ -1,17 +1,28 @@ import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver'; export function resolveItems(key: string, items: any[]) { - const newItems = items.map((item: any) => { - const newItem = { ...item }; - newItem.type = item.displayType; - + const buttonItems = items.filter(item => { + return !!item && typeof item === 'object'; + }).map(item => { + const newItem: any = {}; + Object.keys(item).forEach(key => { + if (key === 'displayType') { + newItem.type = item[key]; + } else if (key === 'appearance') { + newItem.customClass = item[key]?.class || ''; + newItem.customStyle = item[key]?.style || ''; + } else if (key === 'type') { + return; + } else { + newItem[key] = item[key]; + } + }); return newItem; }); - - return { items: newItems }; + return { items: buttonItems }; } export const schemaMapper = new Map([ ['appearance', resolveAppearance], - ['items', resolveItems] + ['items', resolveItems], ]); diff --git a/packages/mobile-ui-vue/components/button/index.ts b/packages/mobile-ui-vue/components/button/index.ts index f0dcd4d389a941de763a8275e39fd57dc4f8e80c..8084a9042c08e37d0e6155c3a5f1753fe9de2970 100644 --- a/packages/mobile-ui-vue/components/button/index.ts +++ b/packages/mobile-ui-vue/components/button/index.ts @@ -1,28 +1,16 @@ -import { Plugin } from 'vue'; -import { withInstall, RegisterContext } from '@farris/mobile-ui-vue/common'; +import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile-ui-vue/common'; import ButtonInstallless from './src/button.component'; import { propsResolverGenerator } from './src/button.props'; import ButtonDesign from './src/designer/button.design.component'; +export * from './src/button.props'; + const BUTTON_REGISTERED_NAME = 'button'; const Button = withInstall(ButtonInstallless); -Button.register = ( - componentMap: Record, propsResolverMap: Record, - configResolverMap: Record, resolverMap: Record, registerContext: RegisterContext -) => { - componentMap[BUTTON_REGISTERED_NAME] = Button; - propsResolverMap[BUTTON_REGISTERED_NAME] = propsResolverGenerator(registerContext); -}; - -Button.registerDesigner = ( - componentMap: Record, propsResolverMap: Record, - configResolverMap: Record, registerContext: RegisterContext -) => { - componentMap[BUTTON_REGISTERED_NAME] = ButtonDesign; - propsResolverMap[BUTTON_REGISTERED_NAME] = propsResolverGenerator(registerContext); -}; +withRegister(Button, { name: BUTTON_REGISTERED_NAME, propsResolverGenerator }); +withRegisterDesigner(Button, { name: BUTTON_REGISTERED_NAME, propsResolverGenerator, designerComponent: ButtonDesign }); export { Button }; -export default Button as typeof Button & Plugin; +export default Button; diff --git a/packages/mobile-ui-vue/components/button/src/button.component.tsx b/packages/mobile-ui-vue/components/button/src/button.component.tsx index fc8dbfeb2bf0c076e9ff8ae1daba9e9bde88897d..5854499fdef960104520de98b885afdee1d2467c 100644 --- a/packages/mobile-ui-vue/components/button/src/button.component.tsx +++ b/packages/mobile-ui-vue/components/button/src/button.component.tsx @@ -1,13 +1,13 @@ import { CSSProperties, computed, defineComponent } from 'vue'; +import { useBem, stopPropagation } from '@farris/mobile-ui-vue/common'; import { Icon } from '@farris/mobile-ui-vue/icon'; -import { useBem } from '@farris/mobile-ui-vue/common'; -import { BUTTON_NAME, ButtonProps, buttonProps } from './button.props'; +import { BUTTON_NAME, buttonProps } from './button.props'; export default defineComponent({ name: BUTTON_NAME, props: buttonProps, emits: ['click'], - setup(props: ButtonProps, context) { + setup(props, context) { const { slots } = context; const { bem, getModifierClass } = useBem(BUTTON_NAME); @@ -24,7 +24,7 @@ export default defineComponent({ return classList; }); - const applyGradientBorderBehavior = (style: CSSProperties, color: string) =>{ + const applyGradientBorderBehavior = (style: CSSProperties, color: string) => { const isGradientColor = color.includes('gradient'); if (isGradientColor) { style.border = 0; @@ -47,10 +47,11 @@ export default defineComponent({ return style; }); - const onClickButton = ($event: Event) =>{ - $event.stopPropagation(); - if (!props.disabled) { - context.emit('click', $event); + const onClickButton = (event: Event) => { + if (props.disabled) { + stopPropagation(event); + } else { + context.emit('click', event); } }; @@ -73,8 +74,8 @@ export default defineComponent({ ); }; - const renderText = ()=> { - return slots.default && {slots.default()}; + const renderText = () => { + return (slots.default || props.text) && {slots.default ? slots.default() : props.text}; }; return () => ( diff --git a/packages/mobile-ui-vue/components/button/src/button.props.ts b/packages/mobile-ui-vue/components/button/src/button.props.ts index 003aed6d5570c955b3d915a6432fddd4f231972d..e5a1224d2448133b3464b864199bd0d72ecac4e8 100644 --- a/packages/mobile-ui-vue/components/button/src/button.props.ts +++ b/packages/mobile-ui-vue/components/button/src/button.props.ts @@ -1,67 +1,57 @@ import { ExtractPropTypes, PropType } from 'vue'; -import { getPropsResolverGenerator } from '../../dynamic-resolver'; +import { getPropsResolverGenerator } from '@farris/mobile-ui-vue/dynamic-resolver'; import buttonSchema from './schema/button.schema.json'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; -export type ButtonType = 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info'; -export type ButtonSize = 'large' | 'normal' | 'small' | 'mini'; +export enum ButtonType { + primary = 'primary', + secondary = 'secondary', + success = 'success', + warning = 'warning', + danger = 'danger' +} -export const BUTTON_NAME = "fm-button"; +export enum ButtonSize { + large = 'large', + middle = 'middle', + small = 'small', + mini = 'mini' +} + +export const BUTTON_NAME = 'FmButton'; export const buttonProps = { - type: { - type: String as PropType, - default: 'primary' - }, - size: { - type: String as PropType, - default: '' - }, - color: { - type: String, - default: '' - }, - block: { - type: Boolean, - default: false - }, - noBorder: { - type: Boolean, - default: false - }, - plain: { - type: Boolean, - default: false - }, - round: { - type: Boolean, - default: false - }, - disabled: { - type: Boolean, - default: false - }, - icon: { - type: String, - default: '' - }, - loading: { - type: Boolean, - default: false - }, - loadingText: { - type: String, - default: '' - } + type: { type: String as PropType, default: ButtonType.primary }, + + size: { type: String as PropType, default: ButtonSize.middle }, + + color: { type: String, default: '' }, + + block: { type: Boolean, default: false }, + + noBorder: { type: Boolean, default: false }, + + plain: { type: Boolean, default: false }, + + round: { type: Boolean, default: false }, + + disabled: { type: Boolean, default: false }, + + icon: { type: String, default: '' }, + + loading: { type: Boolean, default: false }, + + text: { type: String, default: '' }, + + loadingText: { type: String, default: '' } }; export type ButtonProps = ExtractPropTypes; - -export const propsResolverGenerator = getPropsResolverGenerator( - buttonProps, - buttonSchema, - schemaMapper, - schemaResolver +export const propsResolverGenerator = getPropsResolverGenerator( + buttonProps, + buttonSchema, + schemaMapper, + schemaResolver ); diff --git a/packages/mobile-ui-vue/components/button/src/button.scss b/packages/mobile-ui-vue/components/button/src/button.scss index a578b806053d544fc83ccf564d13db0aceeb4e91..b6c4be58c47ca7ddfbfae2c6fb38cf2a818ebd49 100644 --- a/packages/mobile-ui-vue/components/button/src/button.scss +++ b/packages/mobile-ui-vue/components/button/src/button.scss @@ -39,7 +39,7 @@ line-height: var(--fm-button-line-height); border-radius: var(--fm-button-radius); - @include m((primary, info)) { + @include m((primary)) { background: var(--fm-primary-color); } @@ -63,7 +63,7 @@ @include m(plain) { box-shadow: none; background: var(--fm-button-plain-background); - @include root-m((primary, info)) { + @include root-m((primary)) { color: var(--fm-primary-color); border: var(--fm-button-border-width) solid var(--fm-primary-color); } @@ -90,7 +90,7 @@ font-size: var(--fm-button-lg-font-size); } - @include m(normal) { + @include m(middle) { height: var(--fm-button-md-height); font-size: var(--fm-button-md-font-size); } diff --git a/packages/mobile-ui-vue/components/button/src/designer/button.design.component.tsx b/packages/mobile-ui-vue/components/button/src/designer/button.design.component.tsx index 0e63f594deb0e1297f0cf1a15aa29ad8c4533ff9..740e2c790099fce6ed3b25944de3198128a2750c 100644 --- a/packages/mobile-ui-vue/components/button/src/designer/button.design.component.tsx +++ b/packages/mobile-ui-vue/components/button/src/designer/button.design.component.tsx @@ -15,9 +15,9 @@ * limitations under the License. */ import { computed, defineComponent, inject, onMounted, ref, SetupContext } from 'vue'; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common';; import { ButtonProps, buttonProps } from '../button.props'; -import Button from '../..'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import Button from '../button.component'; import { useDesignerRules } from './use-designer-rules'; export default defineComponent({ @@ -44,6 +44,8 @@ export default defineComponent({ block: props.block, icon: props.icon, type: props.type, + plain: props.plain, + size: props.size, })); return () => { diff --git a/packages/mobile-ui-vue/components/button/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/button/src/designer/use-designer-rules.ts index 07e0e64d7d437b9f5be6dc57dc5790e4bee20e14..da2a3b6fd2bf5d9e7930caf9655f682c305f89dd 100644 --- a/packages/mobile-ui-vue/components/button/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/button/src/designer/use-designer-rules.ts @@ -1,7 +1,7 @@ import { ref } from "vue"; +import { DraggingResolveContext } from "@farris/mobile-ui-vue/common"; +import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { ButtonProperty } from "../property-config/button.property-config"; -import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { DraggingResolveContext } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { @@ -45,13 +45,13 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe return { canAccepts, - triggerBelongedComponentToMoveWhenMoved, - triggerBelongedComponentToDeleteWhenDeleted, - checkCanMoveComponent, - checkCanDeleteComponent, + // triggerBelongedComponentToMoveWhenMoved, + // triggerBelongedComponentToDeleteWhenDeleted, + // checkCanMoveComponent, + // checkCanDeleteComponent, hideNestedPaddingInDesginerView, - getStyles, - getDesignerClass, + // getStyles, + // getDesignerClass, getPropsConfig }; } diff --git a/packages/mobile-ui-vue/components/button/src/property-config/button.property-config.ts b/packages/mobile-ui-vue/components/button/src/property-config/button.property-config.ts index 9188aae389f52eda03ee78bc875809fc7094b4e2..2e3db3fe0451b1a430e2b42c006f1f1f582ffc34 100644 --- a/packages/mobile-ui-vue/components/button/src/property-config/button.property-config.ts +++ b/packages/mobile-ui-vue/components/button/src/property-config/button.property-config.ts @@ -1,4 +1,4 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { BaseControlProperty } from "@farris/mobile-ui-vue/common"; export class ButtonProperty extends BaseControlProperty { constructor(componentId: string, designerHostService: any) { @@ -10,12 +10,12 @@ export class ButtonProperty extends BaseControlProperty { // 外观 this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); // 行为 - this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + this.propertyConfig.categories['behavior'] = this.getButtonBehaviorConfig(propertyData); return this.propertyConfig; } - private getBehaviorConfig(propertyData) { + private getButtonBehaviorConfig(propertyData) { return { description: "基本信息", title: "行为", @@ -29,6 +29,10 @@ export class ButtonProperty extends BaseControlProperty { title: "只读", type: "boolean", }, + text: { + title: "文本", + type: "string", + }, displayType: { title: "按钮类型", type: 'select', @@ -37,12 +41,27 @@ export class ButtonProperty extends BaseControlProperty { textField: 'name', valueField: 'value', editable: false, - data: [{ value:'primary' , name: '主要按钮' }, + data: [ + { value:'primary' , name: '主要按钮' }, { value:'secondary' , name: '次要按钮' }, { value:'success' , name: '成功按钮' }, { value:'warning' , name: '警告按钮' }, - { value:'danger' , name: '危险按钮' }, - { value:'info' , name: '提示按钮' }, + { value:'danger' , name: '危险按钮' } + ] + } + }, + size: { + title: "按钮尺寸", + type: 'select', + editor: { + type: 'combo-list', + textField: 'name', + valueField: 'value', + editable: false, + data: [ + { value:'large' , name: '大号' }, + { value:'middle' , name: '中号' }, + { value:'small' , name: '小号' }, ] } }, @@ -54,8 +73,12 @@ export class ButtonProperty extends BaseControlProperty { title: "启用圆角", type: "boolean", }, - text: { - title: "提示文本", + plain: { + title: "启用朴素按钮", + type: "boolean", + }, + icon: { + title: "图标", type: "string", } } diff --git a/packages/mobile-ui-vue/components/button/src/schema/button.schema.json b/packages/mobile-ui-vue/components/button/src/schema/button.schema.json index 0243498e5cb9e90c43f7144a3cf7311db4f5bf5d..365c21abd54d4cebf3eafdaf7646cc91ad1f786d 100644 --- a/packages/mobile-ui-vue/components/button/src/schema/button.schema.json +++ b/packages/mobile-ui-vue/components/button/src/schema/button.schema.json @@ -61,6 +61,11 @@ "type": "boolean", "default": false }, + "size": { + "description": "", + "type": "string", + "default": "middle" + }, "icon": { "description": "图标", "type": "string" diff --git a/packages/mobile-ui-vue/components/button/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/button/src/schema/schema-mapper.ts index eca9607abe43c13c4f01ef3884aa12c77ea0ba3a..adc95460db64a2defb6fa5783b3c33b2e055a05e 100644 --- a/packages/mobile-ui-vue/components/button/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/button/src/schema/schema-mapper.ts @@ -1,4 +1,4 @@ -import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver'; +import { resolveAppearance, MapperFunction } from '@farris/mobile-ui-vue/dynamic-resolver'; function createTemplatePropResolver(name: string): MapperFunction { return (key: string, content: string) => { diff --git a/packages/mobile-ui-vue/components/button/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/button/src/schema/schema-resolver.ts index c1bf8da88a08c01a3c8d6e9f3d81859d84d87aa6..329b35ae2e0c3d0c503f0e67320ca1e6c8516560 100644 --- a/packages/mobile-ui-vue/components/button/src/schema/schema-resolver.ts +++ b/packages/mobile-ui-vue/components/button/src/schema/schema-resolver.ts @@ -1,4 +1,4 @@ -import { DynamicResolver } from "../../../dynamic-resolver"; +import { DynamicResolver } from "@farris/mobile-ui-vue/dynamic-resolver"; export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { return schema; diff --git a/packages/mobile-ui-vue/components/card/index.ts b/packages/mobile-ui-vue/components/card/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f7c7702a59700945af92635c9c21dc25b70049a --- /dev/null +++ b/packages/mobile-ui-vue/components/card/index.ts @@ -0,0 +1,15 @@ +import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile-ui-vue/common'; +import CardInstallless from './src/card.component'; +import { propsResolverGenerator } from './src/card.props'; +import CardDesign from './src/designer/card.design.component'; + +const CARD_REGISTERED_NAME = 'card'; + +const Card = withInstall(CardInstallless); + +withRegister(Card, { name: CARD_REGISTERED_NAME, propsResolverGenerator }); +withRegisterDesigner(Card, { name: CARD_REGISTERED_NAME, propsResolverGenerator, designerComponent: CardDesign }); + +export * from './src/card.props'; +export { Card }; +export default Card; diff --git a/packages/mobile-ui-vue/components/card/src/card.component.tsx b/packages/mobile-ui-vue/components/card/src/card.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..94e9eee1dfe8bb5cd45c06bd116e12d5f38b65af --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/card.component.tsx @@ -0,0 +1,76 @@ +import { defineComponent, ref, computed } from 'vue'; +import { useBem } from '@farris/mobile-ui-vue/common'; +import { ButtonGroup } from '@farris/mobile-ui-vue/button-group'; +import { CARD_NAME, cardProps, CardProps } from './card.props'; + +export default defineComponent({ + name: CARD_NAME, + props: cardProps, + emits: [], + setup(props: CardProps, context) { + const { bem } = useBem(CARD_NAME); + const { slots } = context; + const elementRef = ref(); + + const toolbarItemCount = computed(() => props.toolbarItems?.length ?? 0); + + const shouldRenderHeader = computed(() => { + const isHeaderEmpty = !slots.header && !props.title; + return props.showHeader && !isHeaderEmpty; + }); + + const shouldRenderFooter = computed(() => { + const isFooterEmpty = !slots.footer && toolbarItemCount.value === 0; + return props.showFooter && !isFooterEmpty; + }); + + context.expose({ elementRef }); + + function renderHeader() { + if (slots.header) { + return slots.header(); + } + return ( +
+ {props.title} +
+ ); + } + + function renderContent() { + return ( +
+ {slots.content ? slots.content?.() : slots.default?.()} +
+ ); + } + + function renderFooter() { + if (slots.footer) { + return slots.footer(); + } + return ( +
+ +
+ ); + } + + return () => ( +
+ {shouldRenderHeader.value && renderHeader()} + {renderContent()} + {shouldRenderFooter.value && renderFooter()} +
+ ); + + } +}); diff --git a/packages/mobile-ui-vue/components/card/src/card.props.ts b/packages/mobile-ui-vue/components/card/src/card.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b5499cb584577680aa7182d3d80513758bb58e2 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/card.props.ts @@ -0,0 +1,35 @@ +import { ExtractPropTypes } from 'vue'; +import { getPropsResolverGenerator } from '@farris/mobile-ui-vue/dynamic-resolver'; +import { ButtonItem } from '@farris/mobile-ui-vue/button-group'; +import cardSchema from './schema/card.schema.json'; +import { schemaMapper } from './schema/schema-mapper'; +import { schemaResolver } from './schema/schema-resolver'; + +export const CARD_NAME = 'fm-card'; + +export const cardProps = { + + /** 标题 */ + title: { type: String, default: '' }, + + /** 是否显示头部区域 */ + showHeader: { type: Boolean, default: true }, + + /** 是否显示尾部区域 */ + showFooter: { type: Boolean, default: true }, + + /** 按钮工具栏 */ + toolbarItems: { type: Array, default: [] }, + + customClass: { type: String, default: '' }, + customStyle: { type: String, default: '' }, +}; + +export const propsResolverGenerator = getPropsResolverGenerator( + cardProps, + cardSchema, + schemaMapper, + schemaResolver, +); + +export type CardProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/card/src/card.scss b/packages/mobile-ui-vue/components/card/src/card.scss new file mode 100644 index 0000000000000000000000000000000000000000..a8106b291a0a333c8d56ddb4dfda610be17f2901 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/card.scss @@ -0,0 +1,43 @@ +@use '../../common/src/style/mixins/index.scss' as *; + +:root { + --fm-card-background: var(--fm-white); + --fm-card-title-color: var(--fm-gray-7); +} + +.fm-card { + background-color: var(--fm-card-background); + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 11px 16px 10px; + @include hairline('bottom'); + } + + &__title { + position: relative; + padding-left: var(--fm-padding-md); + color: var(--fm-card-title-color); + font-size: var(--fm-font-size-lg); + line-height: var(--fm-line-height-lg); + font-weight: var(--fm-font-bold-light); + @include ellipsis(); + + &::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + width: 3px; + height: 14px; + margin: -7px 0 0; + background: var(--fm-primary-color); + } + } + + &__footer { + @include hairline('top'); + } +} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/card/src/designer/card.design.component.tsx b/packages/mobile-ui-vue/components/card/src/designer/card.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..17122cf19c98cf83a4a66a70872f7ad128565572 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/designer/card.design.component.tsx @@ -0,0 +1,38 @@ +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { cardProps, CardProps } from '../card.props'; +import { useDesignerRulesForCard } from './use-designer-rules'; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common'; +import { Card } from '@farris/mobile-ui-vue/card'; + +export default defineComponent({ + name: 'FCardDesign', + props: cardProps, + emits: [], + setup(props: CardProps, context) { + const cardRef = ref(); + const elementRef = computed(() => { + return cardRef.value?.elementRef; + }); + + const designerHostService = inject('designer-host-service'); + const designItemContext = inject('design-item-context') as DesignerItemContext; + const designerRulesComposition = useDesignerRulesForCard(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + context.expose(componentInstance.value); + + return () => ( + + {context.slots.default && context.slots.default()} + + ); + } +}); diff --git a/packages/mobile-ui-vue/components/card/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/card/src/designer/use-designer-rules.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0e932536a5eb3bcb62c651ea5bd3c265ca2c667 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/designer/use-designer-rules.ts @@ -0,0 +1,41 @@ +import { DesignerItemContext } from "@farris/mobile-ui-vue/common"; +import { CardProperty } from "../property-config/card.property-config"; +import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; + +export function useDesignerRulesForCard(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { + + function canAccepts(draggingContext: DraggingResolveContext): boolean { + return true; + } + + function getStyles() { + return ''; + } + + function checkCanMoveComponent() { + return true; + } + + function checkCanDeleteComponent() { + return true; + } + + function hideNestedPaddingInDesginerView() { + return true; + } + + function getPropsConfig(componentId: string) { + const componentProp = new CardProperty(componentId, designerHostService); + const { schema } = designItemContext; + return componentProp.getPropertyConfig(schema); + } + + return { + canAccepts, + getStyles, + checkCanMoveComponent, + checkCanDeleteComponent, + hideNestedPaddingInDesginerView, + getPropsConfig + }; +} diff --git a/packages/mobile-ui-vue/components/card/src/property-config/card.property-config.ts b/packages/mobile-ui-vue/components/card/src/property-config/card.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..4eb35897c3946e7392155156868693ed5817344a --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/property-config/card.property-config.ts @@ -0,0 +1,65 @@ +import { ContainerBaseProperty, ToolbarItemProperty } from "@farris/mobile-ui-vue/common"; +import { toolbarConverter } from "@farris/mobile-ui-vue/dynamic-resolver/src/converter"; + +export class CardProperty extends ContainerBaseProperty { + + private toolbarItemProperty: ToolbarItemProperty; + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + this.toolbarItemProperty = new ToolbarItemProperty(componentId, designerHostService); + } + + public getPropertyConfig(propertyData: any) { + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData, { showTitle: true }); + // 外观 + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + // 滑动工具栏 + this.propertyConfig.categories['toolbar'] = this.getToolbarConfig(propertyData); + + return this.propertyConfig; + } + + protected getAppearanceConfig(propertyData: any) { + return { + title: "外观", + description: "Appearance", + properties: this.getAppearanceProperties(["style", "padding", "margin"], propertyData), + }; + } + + protected getToolbarConfig(propertyData: any) { + return { + title: '底部工具栏', + $converter: toolbarConverter, + properties: { + items: { + title: '底部工具栏', + editor: { + type: "collection-property-editor", + textField: 'text', + modalTitle: '工具栏编辑器', + onSelectionChange: ({ selectedData, propertyConfig }) => { + propertyConfig.value = this.toolbarItemProperty.getPropertyConfig(selectedData.value, { + showAppearance: true, + showButtonType: true, + showIcon: true, + }); + }, + defaultComponentSchema: { + id: 'button', + text: '按钮', + visible: true, + disabled: false, + displayType: 'primary', + } + } + } + } + }; + } + +} diff --git a/packages/mobile-ui-vue/components/card/src/schema/card.schema.json b/packages/mobile-ui-vue/components/card/src/schema/card.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..15bafa49fa721081fbf611239bf511f4227591b5 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/schema/card.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/card.schema.json", + "title": "card", + "description": "", + "type": "object", + "properties": { + "id": { + "description": "唯一标识", + "type": "string" + }, + "type": { + "description": "组件类型", + "type": "string", + "default": "card" + }, + "title": { + "description": "标题", + "type": "string", + "default": "卡片" + }, + "contents": { + "description": "子组件集合", + "type": "array", + "default": [] + }, + "appearance": { + "description": "外观", + "type": "object", + "properties": { + "class": { + "type": "string" + }, + "style": { + "type": "string" + } + }, + "default": {} + }, + "padding": { + "description": "内边距", + "type": "object", + "default": {} + }, + "margin": { + "description": "外边距", + "type": "object", + "default": {} + }, + "showHeader": { + "description": "是否显示头部区域", + "type": "boolean", + "default": true + }, + "showFooter": { + "description": "是否显示尾部区域", + "type": "boolean", + "default": true + }, + "toolbar": { + "description": "底部工具栏", + "type": "object", + "properties": { + "items": { + "type": "array", + "default": [] + } + }, + "default": {} + } + }, + "required": [ + "id", + "type", + "title", + "contents", + "showHeader", + "showFooter" + ] +} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/card/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/card/src/schema/schema-mapper.ts new file mode 100644 index 0000000000000000000000000000000000000000..97c101bfe1aee222ba3beb138b016b1b064e1cc3 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/schema/schema-mapper.ts @@ -0,0 +1,8 @@ +import { resolveAppearance, resolvePadding, resolveMargin, resolveToolbar, MapperFunction } from '../../../dynamic-resolver'; + +export const schemaMapper = new Map([ + ['appearance', resolveAppearance], + ['padding', resolvePadding], + ['margin', resolveMargin], + ['toolbar', resolveToolbar], +]); diff --git a/packages/mobile-ui-vue/components/card/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/card/src/schema/schema-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1bf8da88a08c01a3c8d6e9f3d81859d84d87aa6 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/schema/schema-resolver.ts @@ -0,0 +1,5 @@ +import { DynamicResolver } from "../../../dynamic-resolver"; + +export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { + return schema; +} diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.component.tsx b/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.component.tsx index 844917718c10dc2dc2e174e62f8f84513752decc..ddf46838896c926290dbe18ab95014d9030b6f7f 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.component.tsx +++ b/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.component.tsx @@ -1,34 +1,17 @@ -/** - * 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 { SetupContext, computed, defineComponent, ref, toRaw, watch } from 'vue'; +import { computed, defineComponent, ref, toRaw, watch } from 'vue'; import { isArray, isString, useBem, useLink } from '@farris/mobile-ui-vue/common'; import Checkbox from '@farris/mobile-ui-vue/checkbox'; +import { useGroupItems } from './composition'; import { CHECKBOX_GROUP_NAME, CheckboxGroupContext, - CheckboxGroupProps, checkboxGroupProps } from './checkbox-group.props'; -import { useGroupItems } from './composition'; export default defineComponent({ name: CHECKBOX_GROUP_NAME, props: checkboxGroupProps, - setup(props: CheckboxGroupProps, context: SetupContext) { + setup(props, context) { const { emit, slots } = context; const checkeds = ref<(string | number)[]>([]); diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.props.ts b/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.props.ts index b823c42c2c9ea9cb39abcc83b02a4b8c6618374a..1cdf9b3da8fee460e1e6ea34eb4adc8be694cd04 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.props.ts +++ b/packages/mobile-ui-vue/components/checkbox-group/src/checkbox-group.props.ts @@ -1,5 +1,5 @@ import { ExtractPropTypes, PropType } from 'vue'; -import { CheckerShape, CheckerShapeMap, CheckerType, CheckerTypeMap } from '@farris/mobile-ui-vue/checker'; +import { CheckerShape, CheckerType } from '@farris/mobile-ui-vue/checker'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; import inputSchema from './schema/checkbox-group.schema.json'; @@ -11,13 +11,11 @@ export type CheckboxItem = { [key: string]: any; }; -export const enum CheckboxDirectionMap { +export const enum CheckboxDirection { Vertical = 'vertical', Horizontal = 'horizontal' } -export type CheckboxDirection = `${CheckboxDirectionMap}`; - export const CHECKBOX_GROUP_NAME = 'fm-checkbox-group'; export const checkboxGroupProps = { @@ -27,11 +25,11 @@ export const checkboxGroupProps = { readonly: { type: Boolean, default: false }, - shape: { type: String as PropType, default: CheckerShapeMap.Square }, + shape: { type: String as PropType, default: CheckerShape.Square }, - type: { type: String as PropType, default: CheckerTypeMap.Check }, + type: { type: String as PropType, default: CheckerType.Check }, - direction: { type: String as PropType, default: CheckboxDirectionMap.Vertical }, + direction: { type: String as PropType, default: CheckboxDirection.Vertical }, options: { type: Array as PropType, default: undefined }, @@ -40,7 +38,7 @@ export const checkboxGroupProps = { textField: { type: String, default: 'text' }, showDisabledItem: { type: Boolean, default: true }, -} as Record; +}; export type CheckboxGroupProps = ExtractPropTypes; @@ -50,5 +48,5 @@ export type CheckboxGroupContext = { updateChecked: (value: string | number, checked: boolean) => void; }; -export const propsResolverGenerator = getPropsResolverGenerator(checkboxGroupProps, inputSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator(checkboxGroupProps, inputSchema, schemaMapper, schemaResolver); diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/designer/checkbox-group.design.component.tsx b/packages/mobile-ui-vue/components/checkbox-group/src/designer/checkbox-group.design.component.tsx index 1103be0fd1cfeb80a2057cd8551c772ae90352fe..d394591c00ed7592e3f1ef8246e895970f6fbde3 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/designer/checkbox-group.design.component.tsx +++ b/packages/mobile-ui-vue/components/checkbox-group/src/designer/checkbox-group.design.component.tsx @@ -1,74 +1,74 @@ - -/** - * 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, inject, onMounted, readonly, ref, SetupContext } from 'vue'; -import { CheckboxGroup, checkboxGroupProps, CheckboxGroupProps } from '../..'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas'; -import { useCheckBoxGroupProperty } from './use-designer-rules';; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; +import { useCheckBoxGroupProperty } from './use-designer-rules'; +import { checkboxGroupProps } from '../checkbox-group.props'; +import CheckboxGroup from '../checkbox-group.component'; export default defineComponent({ - name: 'FmEnumFieldInputDesign', - props: checkboxGroupProps, - emits: [] as (string[] & ThisType) | undefined, - setup(props: CheckboxGroupProps, context: SetupContext) { - const elementRef = ref(); - const designerHostService = inject('designer-host-service'); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerRulesComposition = useCheckBoxGroupProperty(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + name: 'FmCheckboxGroupDesign', + props: extractProperties(checkboxGroupProps, [ + 'options', + 'textField', + 'valueField', + 'direction', + 'type' + ]), + setup(props, context) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerRulesComposition = useCheckBoxGroupProperty( + designItemContext, + designerHostService + ); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - /** - * 解决在设计时,数据为空数组,界面不显示内容的问题 - */ - const realEnumData = computed(() => { - if (!props.options || props.options.length === 0) { - const result = [] as any; - [ - { value: 'example1', name: '示例一' }, - { value: 'example2', name: '示例二' } - ].map(item => { - const tempData = {}; - tempData[props.valueField] = item['value']; - tempData[props.textField] = item['name']; - result.push(tempData); - }); - return result; - } - return props.options; + /** + * 解决在设计时,数据为空数组,界面不显示内容的问题 + */ + const realEnumData = computed(() => { + if (!props.options || props.options.length === 0) { + const result = [] as any; + [ + { value: 'example1', name: '示例一' }, + { value: 'example2', name: '示例二' } + ].map((item) => { + const tempData = {}; + tempData[props.valueField] = item['value']; + tempData[props.textField] = item['name']; + result.push(tempData); }); + return result; + } + return props.options; + }); + + context.expose(componentInstance.value); - const inputGroupProps = computed(() => ({ - ...props, - editable: false, - readonly: true, - modelValue:null, - options:realEnumData.value, - type:null - })); - - context.expose(componentInstance.value); + const checkboxGroupProps = computed(() => ({ + ...props, + editable: false, + readonly: true, + options: realEnumData.value + })); - return () => { - return ( - - ); - }; - } + return () => { + return ; + }; + } }); diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/checkbox-group/src/designer/use-designer-rules.ts index e2bb1a7808a33a90e3d0bfb2e505ccc4411ff192..0c4a681f89eea5c5e130bb1e1cb6de38c68c7151 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/checkbox-group/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { CheckBoxGroupProperty } from "../property-config/checkbox-group.property-config"; export function useCheckBoxGroupProperty(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -6,8 +6,8 @@ export function useCheckBoxGroupProperty(designItemContext: DesignerItemContext, // 构造属性配置方法 function getPropsConfig(componentId: string, componentInstance: DesignerComponentInstance) { - const radioGroupProps = new CheckBoxGroupProperty(componentId, designerHostService); - return radioGroupProps.getPropertyConfig(schema, componentInstance); + const radioGroupProps = new CheckBoxGroupProperty(componentId, designerHostService); + return radioGroupProps.getPropertyConfig(schema, componentInstance); } return { getPropsConfig } as UseDesignerRules; diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/property-config/checkbox-group.property-config.ts b/packages/mobile-ui-vue/components/checkbox-group/src/property-config/checkbox-group.property-config.ts index 8d632abab80e3c0714b9535a56cebfb177b21712..66d4c07288cf1ba275aeb5e5631031fee3326f17 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/property-config/checkbox-group.property-config.ts +++ b/packages/mobile-ui-vue/components/checkbox-group/src/property-config/checkbox-group.property-config.ts @@ -1,4 +1,4 @@ -import { InputBaseProperty } from '@farris/mobile-ui-vue/common/src/entity/input-base-property'; +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; export class CheckBoxGroupProperty extends InputBaseProperty { constructor(componentId: string, designerHostService: any) { @@ -9,19 +9,15 @@ export class CheckBoxGroupProperty extends InputBaseProperty { const self = this; const editorProperties = self.getComponentConfig( propertyData, - {}, + { type: 'check-group' }, { placeholder: { visible: false }, - disabled: { - visible: false - }, data: { description: '', title: '数据', type: 'array', - $converter: '/converter/enum-data.converter', ...self.getItemCollectionEditor( propertyData, propertyData.editor.valueField, @@ -43,7 +39,7 @@ export class CheckBoxGroupProperty extends InputBaseProperty { readonly: true } }, - (changeObject, parameters) => { + (changeObject) => { if (!changeObject) { return; } diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/schema/checkbox-group.schema.json b/packages/mobile-ui-vue/components/checkbox-group/src/schema/checkbox-group.schema.json index 0a7a1f7c964b8c65cee01e7301995a5081266b5c..2cc30d0d6c0edd7e24fa24408300262270efbcac 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/schema/checkbox-group.schema.json +++ b/packages/mobile-ui-vue/components/checkbox-group/src/schema/checkbox-group.schema.json @@ -1,88 +1,77 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://farris-design.gitee.io/check-group.schema.json", - "title": "check-group", - "description": "A Farris Input Component", - "type": "object", - "properties": { - "id": { - "description": "The unique identifier for a Input Group", - "type": "string" - }, - "type": { - "description": "The type string of Input Group component", - "type": "string", - "default": "check-group" - }, - "appearance": { - "description": "", - "type": "object", - "properties": { - "class": { - "type": "string" - }, - "style": { - "type": "string" - } - }, - "default": {} - }, - "binding": { - "description": "", - "type": "object", - "default": {} - }, - "readonly": { - "type": "string", - "default": false - }, - "title": { - "description": "", - "type": "string", - "default": "" - }, - "label": { - "description": "", - "type": "string", - "default": "" - }, - "lableWidth": { - "description": "", - "type": "number" - }, - "visible": { - "description": "", - "type": "boolean", - "default": true - }, - "data": { - "description": "", - "type": "array", - "default": [] - }, - "direction": { - "description": "", - "type": "string", - "default": "horizontal" - }, - "textField": { - "description": "", - "type": "string", - "default": "name" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/check-group.schema.json", + "title": "check-group", + "description": "A Farris Input Component", + "type": "object", + "properties": { + "id": { + "description": "The unique identifier for a Input Group", + "type": "string" + }, + "type": { + "description": "The type string of Input Group component", + "type": "string", + "default": "check-group" + }, + "appearance": { + "description": "", + "type": "object", + "properties": { + "class": { + "type": "string" }, - "valueField": { - "description": "", - "type": "string", - "default": "value" + "style": { + "type": "string" } + }, + "default": {} + }, + "binding": { + "description": "", + "type": "object", + "default": {} + }, + "required": { + "description": "必填", + "type": "boolean", + "default": false + }, + "readonly": { + "type": "string", + "default": false + }, + "visible": { + "description": "", + "type": "boolean", + "default": true + }, + "data": { + "description": "", + "type": "array", + "default": [] + }, + "direction": { + "description": "", + "type": "string", + "default": "vertical" + }, + "textField": { + "description": "", + "type": "string", + "default": "name" + }, + "valueField": { + "description": "", + "type": "string", + "default": "value" }, - "required": [ - "type" - ], - "ignore": [ - "id", - "appearance", - "binding", - "visible" - ] -} \ No newline at end of file + "checkerType": { + "description": "", + "type": "string", + "default": "default" + } + }, + "required": ["type"], + "ignore": ["id", "appearance", "binding", "visible"] +} diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-mapper.ts index cbfb41ab58450975f85c6e86786ba4f978d3437c..c414d91b7f5f5f39989f9c4ad92ce87a6e10e169 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-mapper.ts @@ -1,7 +1,10 @@ -import { MapperFunction, resolveAppearance, resolveData } from '@farris/mobile-ui-vue/dynamic-resolver'; +import { + MapperFunction, + resolveAppearance +} from '@farris/mobile-ui-vue/dynamic-resolver'; export const schemaMapper = new Map([ - ['appearance', resolveAppearance], - ['data', resolveData], + ['appearance', resolveAppearance], + ['data', 'options'], + ['checkerType', 'type'] ]); - diff --git a/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-resolver.ts index 7c8af7fc63d68147438fc329e74ebce565df4ef7..1e756819460221c500d1b646dbd318d77d7cfaf0 100644 --- a/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-resolver.ts +++ b/packages/mobile-ui-vue/components/checkbox-group/src/schema/schema-resolver.ts @@ -1,5 +1,9 @@ -import { DynamicResolver } from "@farris/mobile-ui-vue/dynamic-resolver"; +import { DynamicResolver } from '@farris/mobile-ui-vue/dynamic-resolver'; -export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { - return schema; +export function schemaResolver( + resolver: DynamicResolver, + schema: Record, + context: Record +): Record { + return schema; } diff --git a/packages/mobile-ui-vue/components/checkbox/src/checkbox.component.tsx b/packages/mobile-ui-vue/components/checkbox/src/checkbox.component.tsx index 5510e7d5ae285149ae971fa619e98960addc0485..cb4537077d00783d917c722df6fc5b057f7b86f3 100644 --- a/packages/mobile-ui-vue/components/checkbox/src/checkbox.component.tsx +++ b/packages/mobile-ui-vue/components/checkbox/src/checkbox.component.tsx @@ -1,22 +1,6 @@ -/** - * 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 } from 'vue'; -import { useLink } from '@farris/mobile-ui-vue/common'; -import { Checker, CheckerRoleMap, CheckerShapeMap } from '@farris/mobile-ui-vue/checker'; +import { useBem, useLink } from '@farris/mobile-ui-vue/common'; +import { Checker, CheckerRole } from '@farris/mobile-ui-vue/checker'; import { CHECKBOX_GROUP_NAME, CheckboxGroupContext } from '@farris/mobile-ui-vue/checkbox-group'; import { CHECKBOX_NAME, @@ -30,6 +14,8 @@ export default defineComponent({ setup(props: CheckboxProps, context) { const { emit } = context; + const { bem } = useBem(CHECKBOX_NAME); + const { getParent } = useLink(CHECKBOX_GROUP_NAME); const checkboxGroup = getParent(); @@ -37,30 +23,25 @@ export default defineComponent({ checkboxGroup?.updateChecked(props.name, checked); emit('change', checked); }; - const handlerUpdateModelValue = (value: boolean) => { - emit('update:modelValue', value); - }; const innerValue = computed(() => { return checkboxGroup?.getChecked ? checkboxGroup.getChecked(props.name) : props.modelValue; }); - const renderCheckbox = () => { - return ( - - ); - }; + const renderCheckbox = () => ( + + ); - return () =>
{renderCheckbox()}
; + return () =>
{renderCheckbox()}
; } }); diff --git a/packages/mobile-ui-vue/components/checkbox/src/checkbox.props.ts b/packages/mobile-ui-vue/components/checkbox/src/checkbox.props.ts index 6fe9e2469b6ff3b2cecd53c18b629bf03aecb525..dd1666b80fdb3cc1c590b35c7afd5205078373e2 100644 --- a/packages/mobile-ui-vue/components/checkbox/src/checkbox.props.ts +++ b/packages/mobile-ui-vue/components/checkbox/src/checkbox.props.ts @@ -1,7 +1,7 @@ import { ExtractPropTypes } from 'vue'; import { checkerProps } from '@farris/mobile-ui-vue/checker'; -export const CHECKBOX_NAME = 'fm-checkbox'; +export const CHECKBOX_NAME = 'FmCheckbox'; export const checkboxProps = { ...checkerProps, diff --git a/packages/mobile-ui-vue/components/checker/src/checker.component.tsx b/packages/mobile-ui-vue/components/checker/src/checker.component.tsx index 82bb9de3ee55810878225d4f8d548f60626ab6e6..2f18ae5b9b57e766e5817b700e14473e9ed66c0f 100644 --- a/packages/mobile-ui-vue/components/checker/src/checker.component.tsx +++ b/packages/mobile-ui-vue/components/checker/src/checker.component.tsx @@ -17,7 +17,7 @@ import { computed, defineComponent, ref, watch } from 'vue'; import { Icon } from '@farris/mobile-ui-vue/icon'; import { useBem } from '@farris/mobile-ui-vue/common'; -import { CheckerProps, checkerProps, CHECKER_NAME, CheckerTypeMap, CheckerShapeMap, CheckerRoleMap } from './checker.props'; +import { CheckerProps, checkerProps, CHECKER_NAME, CheckerType, CheckerShape, CheckerRole } from './checker.props'; export default defineComponent({ name: CHECKER_NAME, @@ -67,8 +67,8 @@ export default defineComponent({ [bem('', 'readonly')]: props.readonly, [bem('', 'disabled')]: props.disabled, [bem('', 'checked')]: innerValue.value, - [bem('', 'round')]: props.shape === CheckerShapeMap.Round, - [bem('', 'button')]: props.type === CheckerTypeMap.Button + [bem('', 'round')]: props.shape === CheckerShape.Round, + [bem('', 'button')]: props.type === CheckerType.Button }; }); @@ -76,7 +76,7 @@ export default defineComponent({ if (props.readonly || props.disabled) { return; } - if(props.role === CheckerRoleMap.Radio && innerValue.value) { + if(props.role === CheckerRole.Radio && innerValue.value) { return; } innerValue.value = !innerValue.value; @@ -86,7 +86,7 @@ export default defineComponent({ return () => (
- {props.type === CheckerTypeMap.Check && renderIcon()} + {props.type === CheckerType.Check && renderIcon()} {props.label && renderLabel()}
); diff --git a/packages/mobile-ui-vue/components/checker/src/checker.props.ts b/packages/mobile-ui-vue/components/checker/src/checker.props.ts index 9befeef8cf3e3f95aa4e057232d0b0468acfbe40..085de6d4d982dff5133d4f82275a752a7df777aa 100644 --- a/packages/mobile-ui-vue/components/checker/src/checker.props.ts +++ b/packages/mobile-ui-vue/components/checker/src/checker.props.ts @@ -1,26 +1,20 @@ import { ExtractPropTypes, PropType } from 'vue'; -export const enum CheckerRoleMap { +export const enum CheckerRole { Checkbox = 'checkbox', Radio = 'radio' } -export const enum CheckerShapeMap { +export const enum CheckerShape { Square = 'square', Round = 'round' } -export const enum CheckerTypeMap { +export const enum CheckerType { Check = 'default', Button = 'button' } -export type CheckerType = `${CheckerTypeMap}`; - -export type CheckerShape = `${CheckerShapeMap}`; - -export type CheckerRole = `${CheckerRoleMap}`; - export const CHECKER_NAME = 'fm-checker'; export const checkerProps = { @@ -32,11 +26,11 @@ export const checkerProps = { readonly: { type: Boolean, default: false }, - type: { type: String as PropType, default: CheckerTypeMap.Check }, + type: { type: String as PropType, default: CheckerType.Check }, - role: { type: String as PropType, default: CheckerRoleMap.Checkbox }, + role: { type: String as PropType, default: CheckerRole.Checkbox }, - shape: { type: String as PropType, default: CheckerShapeMap.Square }, + shape: { type: String as PropType, default: CheckerShape.Square }, labelLimit: { type: Number, default: undefined }, }; diff --git a/packages/mobile-ui-vue/components/common/index.ts b/packages/mobile-ui-vue/components/common/index.ts index c6917cafadcc154e08e45791d964f58244936ad1..463f7bfd49f73cde6c6433052689d0bafdef3217 100644 --- a/packages/mobile-ui-vue/components/common/index.ts +++ b/packages/mobile-ui-vue/components/common/index.ts @@ -1,6 +1,4 @@ export * from './src/compositions'; export * from './src/utils'; -export * from './types'; - -export const FM_UI_PROVIDER_SERVICE_TOKEN = Symbol('UIProviderService'); - +export * from './src/types'; +export * from './src/properties'; diff --git a/packages/mobile-ui-vue/components/common/src/compositions/index.ts b/packages/mobile-ui-vue/components/common/src/compositions/index.ts index cde065de7d2b1150aa5db1d426d427f785b6bed5..d4ed1c00f4d18a1f3cb8e2ce525d22339fd25c3b 100644 --- a/packages/mobile-ui-vue/components/common/src/compositions/index.ts +++ b/packages/mobile-ui-vue/components/common/src/compositions/index.ts @@ -17,3 +17,4 @@ export * from './use-momentum'; export * from './use-scroll-parent'; export * from './use-resize-observer'; export * from './use-long-press'; +export * from './use-designer-component/use-designer-component'; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-designer-component.ts b/packages/mobile-ui-vue/components/common/src/compositions/use-designer-component/use-designer-component.ts similarity index 95% rename from packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-designer-component.ts rename to packages/mobile-ui-vue/components/common/src/compositions/use-designer-component/use-designer-component.ts index 8d6342d347d4e49979a20af797be96e748d51aed..7d820cfddb95d04b7f1fa2f4fb9b72fd2173bea1 100644 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-designer-component.ts +++ b/packages/mobile-ui-vue/components/common/src/compositions/use-designer-component/use-designer-component.ts @@ -1,7 +1,7 @@ import { Ref, ref } from "vue"; -import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext, UseDesignerRules } from "../types"; -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "../../types"; -import { getSchemaByTypeForDesigner } from '../../../../dynamic-resolver/src/resolver/schema/schema-resolver-design'; +import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext, UseDesignerRules } from "../../types/designer-rule"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "../../types/designer-component"; +import { getSchemaByTypeForDesigner } from '@farris/mobile-ui-vue/dynamic-resolver/src/resolver/schema/schema-resolver-design'; export function useDesignerComponent( elementRef: Ref, @@ -78,6 +78,9 @@ export function useDesignerComponent( } function getDraggableDesignItemElement(context: DesignerItemContext = designItemContext as DesignerItemContext): Ref | null { + if (designerRules?.getDraggableDesignItemElement) { + return designerRules.getDraggableDesignItemElement(context); + } const { componentInstance, designerItemElementRef } = context; if (!componentInstance || !componentInstance.value) { return null; diff --git a/packages/mobile-ui-vue/components/common/src/entity/base-property.ts b/packages/mobile-ui-vue/components/common/src/entity/base-property.ts deleted file mode 100644 index fa1776ca76eb2c7dd5d9761831d682ae3d26aff4..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/common/src/entity/base-property.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { DesignerComponentInstance, DgControl } from "@farris/mobile-ui-vue/designer-canvas"; -import { cloneDeep } from "lodash-es"; - -/** - * 控件属性基类 - */ -export class BaseControlProperty { - public componentId: string; - - public viewModelId: string; - - public eventsEditorUtils: any; - - public formSchemaUtils: any; - public formMetadataConverter: any; - public designViewModelUtils: any; - public designViewModelField: any; - public controlCreatorUtils: any; - public designerHostService: any; - - schemaService: any = null; - - metadataService: any = null; - - protected propertyConfig = { - type: 'object', - categories: {} - }; - - constructor(componentId: string, designerHostService: any) { - this.componentId = componentId; - this.designerHostService = designerHostService; - this.eventsEditorUtils = designerHostService['eventsEditorUtils']; - this.formSchemaUtils = designerHostService['formSchemaUtils']; - this.formMetadataConverter = designerHostService['formMetadataConverter']; - this.viewModelId = this.formSchemaUtils?.getViewModelIdByComponentId(componentId) || ''; - this.designViewModelUtils = designerHostService['designViewModelUtils']; - this.controlCreatorUtils = designerHostService['controlCreatorUtils']; - this.metadataService = designerHostService['metadataService']; - this.schemaService = designerHostService['schemaService']; - } - - getTableInfo() { - return this.schemaService?.getTableInfoByViewModelId(this.viewModelId); - } - - setDesignViewModelField(propertyData: any) { - const bindingFieldId = propertyData.binding && propertyData.binding.type === 'Form' && propertyData.binding.field; - // 视图模型中[字段更新时机]属性现在要在控件上维护,所以在控件上复制一份属性值 - if (bindingFieldId) { - if (!this.designViewModelField) { - const dgViewModel = this.designViewModelUtils.getDgViewModel(this.viewModelId); - this.designViewModelField = dgViewModel.fields.find(f => f.id === bindingFieldId); - } - propertyData.updateOn = this.designViewModelField?.updateOn; - } - } - - getBasicPropConfig(propertyData: any): any { - return { - description: 'Basic Information', - title: '基本信息', - properties: { - id: { - description: '组件标识', - title: '标识', - type: 'string', - readonly: true - }, - type: { - description: '组件类型', - title: '控件类型', - type: 'select', - editor: { - type: 'combo-list', - textField: 'name', - valueField: 'value', - editable: false, - data: [{ value: propertyData.type, name: DgControl[propertyData.type] && DgControl[propertyData.type].name }] - } - } - } - }; - - } - - - protected getAppearanceConfig(propertyData = null): any { - return { - title: "外观", - description: "Appearance", - properties: { - class: { - title: "class样式", - type: "string", - description: "组件的CSS样式", - $converter: "/converter/appearance.converter" - }, - style: { - title: "style样式", - type: "string", - description: "组件的样式", - $converter: "/converter/appearance.converter" - } - } - }; - } - - /** - * - * @param propertyId - * @param componentInstance - * @returns - */ - public updateElementByParentContainer(propertyId: string, componentInstance: DesignerComponentInstance) { - // 1、定位控件父容器 - const parentContainer = componentInstance && componentInstance.parent && componentInstance.parent['schema']; - if (!parentContainer) { - return; - } - const index = parentContainer.contents.findIndex(c => c.id === propertyId); - // 通过cloneDeep方式的触发更新 - const controlSchema = cloneDeep(parentContainer.contents[index]); - // 5、替换控件 - parentContainer.contents.splice(index, 1); - parentContainer.contents.splice(index, 0, controlSchema); - } - -} diff --git a/packages/mobile-ui-vue/components/common/src/properties/base-property.ts b/packages/mobile-ui-vue/components/common/src/properties/base-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..dcd959aaffa23170dab3ad120adcd322db1f385f --- /dev/null +++ b/packages/mobile-ui-vue/components/common/src/properties/base-property.ts @@ -0,0 +1,401 @@ +import { cloneDeep } from "lodash-es"; +import { ExpressionProperty } from "./expression-property"; +import { usePropertyEditor } from "./use-property-editor"; +import { DgControl } from "./dg-control"; +import { DesignerComponentInstance, PropertyChangeObject } from "../types"; +import { FormSchemaEntity } from "../types"; + +/** + * 控件属性基类 + */ +export class BaseControlProperty { + public componentId: string; + + public viewModelId: string; + + public eventsEditorUtils: any; + + public formSchemaUtils: any; + public formMetadataConverter: any; + public designViewModelUtils: any; + public designViewModelField: any; + public controlCreatorUtils: any; + public designerHostService: any; + + schemaService: any = null; + + metadataService: any = null; + + protected propertyConfig = { + type: 'object', + categories: {} + }; + + constructor(componentId: string, designerHostService: any) { + this.componentId = componentId; + this.designerHostService = designerHostService; + this.eventsEditorUtils = designerHostService['eventsEditorUtils']; + this.formSchemaUtils = designerHostService['formSchemaUtils']; + this.formMetadataConverter = designerHostService['formMetadataConverter']; + this.viewModelId = this.formSchemaUtils?.getViewModelIdByComponentId(componentId) || ''; + this.designViewModelUtils = designerHostService['designViewModelUtils']; + this.controlCreatorUtils = designerHostService['controlCreatorUtils']; + this.metadataService = designerHostService['metadataService']; + this.schemaService = designerHostService['schemaService']; + } + + getTableInfo() { + return this.schemaService?.getTableInfoByViewModelId(this.viewModelId); + } + + setDesignViewModelField(propertyData: any) { + const bindingFieldId = propertyData.binding && propertyData.binding.type === 'Form' && propertyData.binding.field; + // 视图模型中[字段更新时机]属性现在要在控件上维护,所以在控件上复制一份属性值 + if (bindingFieldId) { + if (!this.designViewModelField) { + const dgViewModel = this.designViewModelUtils.getDgViewModel(this.viewModelId); + this.designViewModelField = dgViewModel.fields.find(f => f.id === bindingFieldId); + } + propertyData.updateOn = this.designViewModelField?.updateOn; + } + } + + getBasicPropConfig(propertyData: any): any { + return { + description: 'Basic Information', + title: '基本信息', + properties: { + id: { + description: '组件标识', + title: '标识', + type: 'string', + readonly: true + }, + type: { + description: '组件类型', + title: '控件类型', + type: 'select', + editor: { + type: 'combo-list', + textField: 'name', + valueField: 'value', + idField: 'value', + editable: false, + data: [{ value: propertyData.type, name: DgControl[propertyData.type] && DgControl[propertyData.type].name }] + } + } + } + }; + + } + + + protected getAppearanceConfig(propertyData = null, properties = {}, setPropertyRelates?: (changeObject, propertyData, parameters) => any): any { + const appearanceBasic = { + title: "外观", + description: "Appearance", + }; + const appearancePoperties = { + class: { + title: "class样式", + type: "string", + description: "组件的CSS样式", + $converter: "/converter/appearance.converter" + }, + style: { + title: "style样式", + type: "string", + description: "组件的样式", + $converter: "/converter/appearance.converter" + } + + }; + for (const key in properties) { + // 合并属性,保留原属性值 + appearancePoperties[key] = Object.assign(appearancePoperties[key] || {}, properties[key]); + } + return { + ...appearanceBasic, properties: { ...appearancePoperties }, setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + if (!changeObject) { + return; + } + switch (changeObject && changeObject.propertyID) { + case 'class': case 'style': { + changeObject.needChangeCanvas = true; + break; + } + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, propertyData, parameters); + } + } + + }; + } + protected getPropertyEditorParams(propertyData, propertyTypes: any = [], propertyName = 'visible', constInfos = {}, variableInfos = {}) { + const { getVariables, getControlName, getStateMachines } = usePropertyEditor(this.designerHostService); + const targetType = this.getRealTargetType(propertyData); + const newPropertyTypes = propertyTypes && propertyTypes.length > 0 ? propertyTypes : ['Const', 'Variable', 'Custom', 'StateMachine', 'Expression']; + const resultProperty = { + type: "property-editor", + propertyTypes: newPropertyTypes + }; + newPropertyTypes.map(item => { + switch (item) { + case 'Const': + Object.assign(resultProperty, { + constType: 'enum', + constEnums: [{ id: true, name: '是' }, { id: false, name: '否' }] + }, constInfos); + break; + case 'Expression': + resultProperty['expressionConfig'] = this.getExpressionOptions(propertyData, targetType, propertyName); + break; + case 'StateMachine': + resultProperty['stateMachines'] = getStateMachines(this.viewModelId); + break; + case 'Variable': + Object.assign(resultProperty, { + controlName: getControlName(propertyData), + newVariablePrefix: 'is', + newVariableType: 'Boolean', + variables: getVariables(this.viewModelId) + }, variableInfos); + break; + } + }); + return resultProperty; + } + + protected getVisibleProperty(propertyData, position = '') { + const editor = this.getPropertyEditorParams(propertyData, position === 'gridFieldEditor' ? ['Const', 'Expression'] : ['Const', 'Variable', 'Custom', 'StateMachine', 'Expression'], 'visible'); + return { + visible: { + title: "是否可见", + type: "boolean", + description: "运行时组件是否可见", + editor + } + }; + } + /** + * 获取行为 + * @param propertyData + * @param viewModelId + * @returns + */ + public getBehaviorConfig(propertyData: any, position = '', properties = {}, setPropertyRelates?: any): any { + const behaviorBasic = { + title: "行为", + description: "" + }; + const behaviorPoperties = this.getVisibleProperty(propertyData, position); + for (const key in properties) { + // 合并属性,保留原属性值 + behaviorPoperties[key] = Object.assign(behaviorPoperties[key] || {}, properties[key]); + } + const self = this; + + return { + ...behaviorBasic, properties: { ...behaviorPoperties }, setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'disabled': + case 'readonly': + case 'visible': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, parameters); + } + } + + }; + } + /** + * 当多值编辑器变更时 + * @param propertyData + * @param changeObject + */ + public afterMutilEditorChanged(propertyData, changeObject) { + // 变量 + this.addNewVariableToViewModel(changeObject, this.viewModelId); + // 更新表达式 + this.updateExpressionValue(changeObject); + // 清空表达式 + this.clearExpression(changeObject, propertyData); + } + + /** + * 更新element + * @param propertyId + * @param componentInstance + * @returns + */ + public updateElementByParentContainer(propertyId: string, componentInstance: DesignerComponentInstance) { + // 1、定位控件父容器 + const parentContainer = componentInstance && componentInstance.parent && componentInstance.parent['schema']; + if (!parentContainer) { + return; + } + const index = parentContainer.contents.findIndex(c => c.id === propertyId); + // 通过cloneDeep方式的触发更新 + const controlSchema = cloneDeep(parentContainer.contents[index]); + // 5、替换控件 + parentContainer.contents.splice(index, 1); + parentContainer.contents.splice(index, 0, controlSchema); + // refreshCanvas(); + } + + /** + * 属性编辑器,在编辑过程中会新增变量,此处需要将新增的变量追加到ViewModel中 + * @param changeObject + * @param viewModelId + * @returns + */ + public addNewVariableToViewModel(changeObject: PropertyChangeObject, viewModelId: string) { + const newPropertyValue = changeObject.propertyValue; + // 1、判断当前属性值是否为对象 + const isObject = newPropertyValue && typeof newPropertyValue === 'object'; + if (!isObject) { + return; + } + + // 2、判断是否为新变量 + const isNewVariable = newPropertyValue.type === 'Variable' && newPropertyValue.isNewVariable; + if (!isNewVariable) { + return; + } + + // 3、构造变量结构 + const newVariable = { + id: newPropertyValue.field, + category: 'locale', + code: newPropertyValue.fullPath, + name: newPropertyValue.fullPath, + type: newPropertyValue.newVariableType || 'String' + }; + delete newPropertyValue.newVariableType; + delete newPropertyValue.isNewVariable; + + // 4、把新变量添加到ViewModel中 + const existedVariable = this.formSchemaUtils.getVariableByCode(newVariable.code); + if (!existedVariable) { + const viewModel = this.formSchemaUtils.getViewModelById(viewModelId); + viewModel.states.push(newVariable); + } + } + + /** + * 更新表达式到expressions节点 + * @param changeObject + */ + public updateExpressionValue(changeObject: PropertyChangeObject) { + const newPropertyValue = changeObject.propertyValue; + const newValueType = newPropertyValue && newPropertyValue.type; + + // 1、判断是否需要更新表达式 + const needUpdateExpression = newValueType === 'Expression' && newPropertyValue.expressionInfo; + if (!needUpdateExpression) { + return; + } + + const { expressionId, expressionInfo } = newPropertyValue; + const { targetId, targetType, expressionType, value, message } = expressionInfo; + const module = this.formSchemaUtils.getModule(); + module.expressions ??= []; + const { expressions } = module; + + // 2、获取目标表达式,如果不存在,则创建一个空的目标表达式 + let targetExpression = expressions.find(expression => expression.target === targetId); + if (!targetExpression) { + targetExpression = { target: targetId, rules: [], targetType }; + expressions.push(targetExpression); + } + + // 3、更新表达式 + const expressionItem = targetExpression.rules.find(rule => rule.type === expressionType); + if (expressionItem) { + expressionItem.value = value; + expressionItem.message = message; + } else { + const newExpressionRule = { id: expressionId, type: expressionType, value, message }; + targetExpression.rules.push(newExpressionRule); + } + delete newPropertyValue.expressionInfo; + } + + /** + * 属性类型切换为非表达式后,清除原表达式 + * @param changeObject + * @param propertyData + * @returns + */ + clearExpression(changeObject: PropertyChangeObject, propertyData: any) { + const newPropertyValue = changeObject.propertyValue; + const isExpression = newPropertyValue && newPropertyValue.type === 'Expression'; + // 1、如果为当前属性为表达式,则不需要清空 + if (isExpression) { + return; + } + + // 2、属性值不是表达式后,需要清空表达式 + const expressionType = changeObject.propertyID; + const expressions = this.formSchemaUtils.getExpressions(); + + const targetId = propertyData.binding ? propertyData.binding.field : propertyData.id; + const targetExpression = expressions.find(expression => expression.target === targetId); + if (!targetExpression || !targetExpression.rules) { + return; + } + targetExpression.rules = targetExpression.rules.filter(rule => rule.type !== expressionType); + } + + getExpressionOptions(propertyData: any, targetType: 'Field' | 'Button' | 'Container', expressionType: string) { + return new ExpressionProperty(this.formSchemaUtils).getExpressionOptions(propertyData, targetType, expressionType); + } + + getRealTargetType(propertyData: any,) { + if (['response-toolbar-item', 'tab-toolbar-item', 'section-toolbar-item'].indexOf(propertyData.type) > -1) { + return 'Button'; + } + if (propertyData.binding && propertyData.binding.field) { + return 'Field'; + } + return 'Container'; + } + + public getSchemaEntityTreeData(): any[] { + const mainEntity = this.formSchemaUtils.getFormSchema()?.module?.entity[0]?.entities[0]; + const entityTreeData = this.assembleSchemaEntityToTree(mainEntity, 0); + return entityTreeData; + } + + private assembleSchemaEntityToTree( + schemaEntity: FormSchemaEntity, + layer: number, + parent?: FormSchemaEntity, + bindToPath = '', + treeData: any[] = [], + ) { + const bindTo = bindToPath ? `${bindToPath}/${schemaEntity.label}` : '/'; + treeData.push({ + id: schemaEntity.id, + name: schemaEntity.name, + label: schemaEntity.label, + layer, + parent: parent && parent.id, + bindTo: bindTo.replace('//', '/') + }); + if (schemaEntity.type.entities && schemaEntity.type.entities.length) { + schemaEntity.type.entities.map(ele => this.assembleSchemaEntityToTree(ele, layer + 1, schemaEntity, bindTo, treeData)); + } + return treeData; + } +} diff --git a/packages/mobile-ui-vue/components/common/src/properties/container-base-property.ts b/packages/mobile-ui-vue/components/common/src/properties/container-base-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1dc7f9773ac0a51fabdd60e5b27bd7cf642779d --- /dev/null +++ b/packages/mobile-ui-vue/components/common/src/properties/container-base-property.ts @@ -0,0 +1,382 @@ +import { BaseControlProperty } from "./base-property"; +import { DgControl } from "./dg-control"; +import { sizeConverter, paddingConverter, marginConverter, positionConverter, borderRadiusConverter } from "@farris/mobile-ui-vue/dynamic-resolver/src/converter"; +import { PropertyChangeObject } from "../types"; + +interface BasicPropOptions { + showTitle?: boolean; +} + +export class ContainerBaseProperty extends BaseControlProperty { + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + + protected getAppearanceProperties( + propertyNames: string[] = ["style"], + propertyData?: any, + ) { + const propertyName2PropertyGetter = { + style: this.getStyleProperties, + size: this.getSizeProperties, + padding: this.getPaddingProperties, + margin: this.getMarginProperties, + borderRadius: this.getBorderRadiusProperties, + position: this.getPositionProperties, + }; + const properties: any[] = []; + propertyNames.forEach((propertyName) => { + const propertyGetter = propertyName2PropertyGetter[propertyName]; + if (propertyGetter) { + properties.push(propertyGetter(propertyData)); + } + }); + return Object.assign({}, ...properties); + } + + private getStyleProperties(propertyData?: any) { + return { + class: { + title: "class样式", + type: "string", + description: "组件的CSS样式", + $converter: "/converter/appearance.converter" + }, + style: { + title: "style样式", + type: "string", + description: "组件的样式", + $converter: "/converter/appearance.converter" + }, + }; + } + + private getSizeProperties(propertyData?: any) { + return { + size: { + title: "尺寸", + type: "cascade", + description: "组件的尺寸", + isExpand: true, + properties: { + width: { + title: "宽度(px)", + type: "number", + description: "组件的宽度", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: sizeConverter, + }, + height: { + title: "高度(px)", + type: "number", + description: "组件的高度", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: sizeConverter, + } + } + }, + }; + } + + private getPaddingProperties(propertyData?: any) { + return { + padding: { + title: "内边距", + type: "cascade", + description: "组件的内边距", + isExpand: true, + properties: { + top: { + title: "上侧(px)", + type: "number", + description: "组件的上侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + right: { + title: "右侧(px)", + type: "number", + description: "组件的右侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + bottom: { + title: "下侧(px)", + type: "number", + description: "组件的下侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + left: { + title: "左侧(px)", + type: "number", + description: "组件的左侧内边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: paddingConverter, + }, + } + }, + }; + } + + private getMarginProperties(propertyData?: any) { + return { + margin: { + title: "外边距", + type: "cascade", + description: "组件的外边距", + isExpand: true, + properties: { + top: { + title: "上侧(px)", + type: "number", + description: "组件的上侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + right: { + title: "右侧(px)", + type: "number", + description: "组件的右侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + bottom: { + title: "下侧(px)", + type: "number", + description: "组件的下侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + left: { + title: "左侧(px)", + type: "number", + description: "组件的左侧外边距", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: marginConverter, + }, + } + }, + }; + } + + private getBorderRadiusProperties(propertyData?: any) { + return { + borderRadius: { + title: "圆角", + type: "cascade", + description: "组件的圆角", + isExpand: true, + properties: { + topLeft: { + title: "左上角(px)", + type: "number", + description: "组件的左上圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + topRight: { + title: "右上角(px)", + type: "number", + description: "组件的右上圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + bottomRight: { + title: "右下角(px)", + type: "number", + description: "组件的右下圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + bottomLeft: { + title: "左下角(px)", + type: "number", + description: "组件的左下圆角大小", + editor: { + min: 0, + decimals: 0, + nullable: true, + }, + $converter: borderRadiusConverter, + }, + } + }, + }; + } + + private getPositionProperties(propertyData?: any) { + return { + position: { + title: "位置", + type: "cascade", + description: "组件的位置", + isExpand: true, + properties: { + top: { + title: "上侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的上侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + right: { + title: "右侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的右侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + bottom: { + title: "下侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的下侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + left: { + title: "左侧偏移(px)", + type: "number", + description: "组件相对于最近的非 static 定位祖先元素的左侧偏移", + editor: { + decimals: 0, + nullable: true, + }, + $converter: positionConverter, + }, + } + }, + }; + } + + public getBasicPropConfig( + propertyData: any, + options?: BasicPropOptions, + additionalProperties = {}, + setPropertyRelates?: (changeObject: PropertyChangeObject, propertyData: any, parameters: any) => any, + ) { + const optionalProperties = [ + { + id: { + description: '组件标识', + title: '标识', + type: 'string', + readonly: true + } + }, + options?.showTitle && { + title: { + description: '组件名称', + title: "名称", + type: "string", + } + }, + { + type: { + description: '组件类型', + title: '控件类型', + type: 'select', + editor: { + type: 'combo-list', + textField: 'name', + valueField: 'value', + idField: 'value', + editable: false, + data: [{ + value: propertyData.type, + name: DgControl[propertyData.type] && DgControl[propertyData.type].name + }], + } + } + }, + ]; + const properties = optionalProperties.filter((property) => { + return typeof property === 'object' && !!property; + }).reduce((result, newProperty) => { + return Object.assign(result, newProperty); + }, {}); + Object.keys(additionalProperties).forEach((key) => { + properties[key] = Object.assign(properties[key] || {}, additionalProperties[key]); + }); + return { + description: 'Basic Information', + title: '基本信息', + properties, + setPropertyRelates(changeObject: any, parameters: any) { + const propertyID = changeObject?.propertyID; + if (!propertyID) { + return; + } + if (propertyID === 'title') { + changeObject.needRefreshControlTree = true; + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, propertyData, parameters); + } + }, + }; + } + +} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/dg-control.ts b/packages/mobile-ui-vue/components/common/src/properties/dg-control.ts similarity index 91% rename from packages/mobile-ui-vue/components/designer-canvas/src/composition/dg-control.ts rename to packages/mobile-ui-vue/components/common/src/properties/dg-control.ts index e230774a9f909ea9f0adfb04e5a4ec126593b1cc..7686b232834a19b95e8c414a2928b5fc1b1d167f 100644 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/dg-control.ts +++ b/packages/mobile-ui-vue/components/common/src/properties/dg-control.ts @@ -1,4 +1,5 @@ -export const DgControl = { +export const DgControl = { + 'module': { type: 'Module', name: '模块', icon: 'Module' }, 'component': { type: 'component', name: '组件', icon: 'Component' }, @@ -15,10 +16,14 @@ export const DgControl = { 'float-container': { type: 'float-container', name: '浮动容器', icon: 'ContentContainer' }, + 'card': { type: 'card', name: '卡片', icon: 'section' }, + 'list-view': { type: 'list-view', name: '列表', icon: 'ListView' }, 'button': { type: 'button', name: '按钮', icon: 'Button' }, + 'button-group': { type: 'button-group', name: '按钮组', icon: 'ButtonGroup' }, + 'input-group': { type: 'input-group', name: '文本', icon: 'TextBox' }, 'textarea': { type: 'textarea', name: '多行文本', icon: 'MultiTextBox' }, @@ -36,7 +41,7 @@ export const DgControl = { 'radio-group': { type: 'radio-group', name: '单选组', icon: 'RadioGroup' }, 'check-group': { type: 'check-group', name: '多选组', icon: 'CheckGroup' }, - + 'check-box': { type: 'check-box', name: '复选框', icon: 'CheckBox' }, 'combo-list': { type: 'combo-list', name: '下拉列表', icon: 'EnumField' }, @@ -46,5 +51,4 @@ export const DgControl = { 'navbar': { type: 'navbar', name: '导航栏', icon: 'NavBar' }, 'picker': { type: 'picker', name: '选择器', icon: 'EnumField' }, - }; diff --git a/packages/mobile-ui-vue/components/common/src/properties/expression-property.ts b/packages/mobile-ui-vue/components/common/src/properties/expression-property.ts new file mode 100644 index 0000000000000000000000000000000000000000..24b91f61777f5ec22452f8d872d6b9f879d98706 --- /dev/null +++ b/packages/mobile-ui-vue/components/common/src/properties/expression-property.ts @@ -0,0 +1,385 @@ +export interface RuleModel { + id: string; + type: 'compute' | 'dependency' | 'validate' | 'visible' | 'readonly' | 'required' | 'dataPicking' | string; + value: string; + message?: string; + elementId?: string; + messageType?: string; +} + +export interface ExpressionModel { + target: string; + rules: Array; + targetType: string; +} + + +export class ExpressionProperty { + + private sessionVariables = [ + { + key: "CurrentSysOrgName", + name: "当前组织Name", + description: "当前组织Name" + }, + // { + // key: "CurrentSysOrgCode", + // name: "当前组织Code", + // description: "当前组织Code" + // }, + { + key: "CurrentSysOrgId", + name: "当前组织Id", + description: "当前组织Id" + }, + { + key: "CurrentUserName", + name: "当前用户Name", + description: "当前用户Name" + }, + { + key: "CurrentUserCode", + name: "当前用户Code", + description: "当前用户Code" + }, + { + key: "CurrentUserId", + name: "当前用户Id", + description: "当前用户Id" + }, + { + key: "CurrentLanguage", + name: "当前语言编号", + description: "当前登录的语言编号,例如简体中文返回'zh-CHS',英文返回'en',繁体中文'zh-CHT'" + } + ]; + + constructor(private formSchemaService: any) { + } + + private getExpressionRule(expressionId: any, rultType: string) { + const expressions = this.formSchemaService.getExpressions(); + if (!expressions) { + return ''; + } + const expressionItem = expressions.find(item => item.target === expressionId); + if (!expressionItem) { + return ''; + } + + const ruleItem = expressionItem.rules.find(item => item.type === rultType); + if (!ruleItem) { + return ''; + } + + return ruleItem; + } + + // 获取上下文表单变量 + private getContextFormVariables() { + const { module } = this.formSchemaService.getFormSchema(); + if (!module.viewmodels || module.viewmodels.length === 0) { + return []; + } + + const rootViewModelId = this.formSchemaService.getRootViewModelId(); + const viewModel = this.formSchemaService.getViewModelById(rootViewModelId); + + if (!viewModel || !viewModel.states || viewModel.states.length === 0) { + return []; + } + + const contextEntities: any = []; + viewModel.states.forEach(variable => { + if (variable.category === 'remote') { + contextEntities.push({ + key: variable.code, + name: variable.name, + description: variable.name + }); + } + }); + return contextEntities; + } + + private createTreeNode(element: any, parentCodes: string[], fieldName = 'label') { + return { + id: element.id, + name: element.name, + bindingPath: element[fieldName], + parents: parentCodes + }; + } + + + private buildEntityFieldsTreeData(fields: any[] | null = null, parentCodes: string[]): any[] { + const treeData: any[] = []; + fields?.forEach(element => { + const nodeData = this.createTreeNode(element, parentCodes); + + let children: any[] = []; + if (element.type?.fields) { + children = this.buildEntityFieldsTreeData(element.type.fields, [...parentCodes, element.label]); + } + + treeData.push({ + data: nodeData, + children, + expanded: true + }); + }); + return treeData; + } + + private buildChildEntityTreeData(entities: any[] | null = null, parentCodes: string[]): any[] { + const treeData: any[] = []; + entities?.forEach(element => { + const nodeData = this.createTreeNode(element, parentCodes); + + const children = this.buildEntityFieldsTreeData(element.type?.fields, [...parentCodes, element.label]); // 可选链 + const childEntities = this.buildChildEntityTreeData(element.type?.entities, [...parentCodes, element.label]); // 可选链 + + if (childEntities?.length) { // 空值检查 + children?.push(...childEntities); + } + + treeData.push({ + data: nodeData, + children: children || [], // 空值回退 + expanded: true + }); + }); + return treeData; + } + + private getEntitiesTreeData() { + const entities = this.formSchemaService.getSchemaEntities(); + if (!entities?.length) { // 空值检查 + return []; + } + + const mainTable = entities[0]; + if (!mainTable?.type) { return []; } // 空值检查 + + const childFields = this.buildEntityFieldsTreeData(mainTable.type.fields, [mainTable.code]); + const childEntities = this.buildChildEntityTreeData(mainTable.type.entities, [mainTable.code]); + + if (childEntities?.length) { // 空值检查 + childFields?.push(...childEntities); + } + + return { + entityCode: mainTable.code, + fields: [{ + data: this.createTreeNode(mainTable, [], 'code'), + children: childFields || [] + }] + }; + } + + + getEntitiesAndVariables() { + return { + entities: this.getEntitiesTreeData(), + variables: { + session: { + name: '系统变量', + items: this.sessionVariables + }, + forms: { + name: '表单变量', + items: this.getContextFormVariables() + } + } + }; + } + + private onBeforeOpenExpression(propertyData: any, expressionType: string, targetType: string) { + const expressionId = targetType === 'Field' ? propertyData.binding.field : propertyData.id; + const rule = this.getExpressionRule(expressionId, expressionType); + const entitiesAndVariables = this.getEntitiesAndVariables(); + const data: any = { + message: ['validate', 'required','dataPicking'].includes(expressionType) && rule ? rule.message : '', + ...entitiesAndVariables + }; + + if (rule.messageType != null) { + data.messageType = rule.messageType; + } + + return data; + } + + private buildRule(targetId, expressionObject: Record, ruleType: string, controlId?: string) { + const { expression: expressionValue, message, messageType } = expressionObject; + const rule: RuleModel = { + id: `${targetId}_${ruleType}`, + type: ruleType, + value: expressionValue, + message, + messageType + }; + + if (ruleType === 'validate' && controlId) { + rule.elementId = controlId; + } + + return rule; + } + + private getExpressionData(): Array { + const { expressions } = this.formSchemaService.getFormSchema().module; + return expressions || []; + } + + private updateExpression(propertyData: any, + targetType: 'Field' | 'Button' | 'Container', + expressionObject: Record, ruleType: string) { + const targetId = targetType === 'Field' ? propertyData.binding.field : propertyData.id; + const newRule = this.buildRule(targetId, expressionObject, ruleType, propertyData.type === 'form-group' ?propertyData.id: ''); + + const currentExpressiones = this.getExpressionData(); + let expressionItem = currentExpressiones.find((item: ExpressionModel) => { + return item.targetType === targetType && item.target === targetId; + }); + + // 提取重复逻辑:判断 value 是否为空 + const isValueEmpty = (rule: RuleModel): boolean => rule.value.trim() === ''; + + if (expressionItem) { + const ruleItem = expressionItem.rules.find((rule) => rule.id === newRule.id); + if (ruleItem) { + if (isValueEmpty(newRule)) { + expressionItem.rules = expressionItem.rules.filter((rule) => rule.id !== newRule.id); + } else { + ruleItem.value = newRule.value; + ruleItem.message = newRule.message; + + if (ruleType === 'dataPicking') { + ruleItem.messageType = newRule.messageType; + } + + if (ruleType === 'validate' && propertyData.type === 'form-group') { + ruleItem.elementId = propertyData.id; + } + } + } else { + if (isValueEmpty(newRule)) { + return null; + } + + expressionItem.rules = expressionItem.rules || []; + expressionItem.rules.push(newRule); + } + } else { + + if (isValueEmpty(newRule)) { + return null; + } + + expressionItem = { + target: `${targetId}`, + rules: [newRule], + targetType: targetType + }; + + } + return expressionItem; + } + + private expressionNames = { + compute: '计算表达式', + dependency: '依赖表达式', + validate: '验证表达式', + dataPicking: '帮助前表达式', + visible: '可见表达式', + readonly: '只读表达式', + required: '必填表达式' + }; + + private getExpressionConverter = (expressionId: string, ruleType?: string) => { + return { + convertFrom: (schema: Record, propertyKey: string, schemaService, componentId) => { + const rule = schemaService.getExpressionRuleValue(expressionId, ruleType || propertyKey); + return rule && rule.value || ''; + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any[], + schemaService, componentId + ) => { + schemaService.updateExpression(propertyValue); + } + }; + }; + + private getExpressionEditorOptions(propertyData, targetType: 'Field' | 'Button' | 'Container', + expressionTypes: string[], callback?: (rule: Record) => void) { + return expressionTypes.reduce((expressions: Record, name: string) => { + const expressionId = targetType === 'Field' ? propertyData?.binding?.field : propertyData.id; + expressions[name] = { + hide: targetType === 'Field' ? !!propertyData?.binding?.field : false, + description: "", + title: this.expressionNames[name], + type: "string", + $converter: this.getExpressionConverter(expressionId), + editor: { + type: "expression-editor", + singleExpand: false, + dialogTitle: `${this.expressionNames[name]}编辑器`, + showMessage: name === 'validate' || name === 'dataPicking', + showMessageType: name === 'dataPicking', + beforeOpen: () => { + return this.onBeforeOpenExpression(propertyData, name, targetType); + }, + onSubmitModal: (expressionObject: any) => { + const expressionData: any = this.updateExpression(propertyData, targetType, expressionObject, name); + if (callback) { + const rule = this.buildRule(expressionId, expressionObject, name); + callback(rule); + } + return expressionData; + } + } + }; + + return expressions; + }, {}); + } + + private getExpressionInfo(propertyData: any, targetType: 'Field' | 'Button' | 'Container', expressionType: string) { + const targetId = targetType === 'Field' ? propertyData.binding.field : propertyData.id; + const expressionRule = this.getExpressionRule(targetId, expressionType); + const expressionInfo = { + value: expressionRule && expressionRule.value, + message: expressionRule && expressionRule.message, + targetId, + targetType, + expressionType + }; + return expressionInfo; + } + + getExpressionConfig(propertyData: any, type: 'Field' | 'Button' | 'Container', + expressionTypes: string[] = ['compute', 'dependency', 'validate'], + callback?: (rule: Record) => void) { + return { + description: "表达式", + title: "表达式", + properties: { + ...this.getExpressionEditorOptions(propertyData, type, expressionTypes, callback) + } + }; + } + + getExpressionOptions(propertyData: any, targetType: 'Field' | 'Button' | 'Container', expressionType: string) { + const expressionInfo = this.getExpressionInfo(propertyData, targetType, expressionType); + return { + dialogTitle: `${this.expressionNames[expressionType]}编辑器`, + singleExpand: false, + showMessage: expressionType === 'require', + beforeOpen: () => { + return this.onBeforeOpenExpression(propertyData, expressionType, targetType); + }, + expressionInfo + }; + } +} diff --git a/packages/mobile-ui-vue/components/common/src/properties/index.ts b/packages/mobile-ui-vue/components/common/src/properties/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f505773be9053aff387da8cfea5a5a1e66790c1 --- /dev/null +++ b/packages/mobile-ui-vue/components/common/src/properties/index.ts @@ -0,0 +1,6 @@ +export * from './dg-control'; +export { SchemaDOMMapping } from './schema-dom-mapping'; +export { BaseControlProperty } from './base-property'; +export { InputBaseProperty } from './input-base-property'; +export { ContainerBaseProperty } from './container-base-property'; +export { ToolbarItemProperty } from './toolbar-item.property'; diff --git a/packages/mobile-ui-vue/components/common/src/entity/input-base-property.ts b/packages/mobile-ui-vue/components/common/src/properties/input-base-property.ts similarity index 35% rename from packages/mobile-ui-vue/components/common/src/entity/input-base-property.ts rename to packages/mobile-ui-vue/components/common/src/properties/input-base-property.ts index ce09243094574836b7e9edfd00b4621669bb4a8f..c563a8c9562aa52163ea7cefa9ae920a7b88264d 100644 --- a/packages/mobile-ui-vue/components/common/src/entity/input-base-property.ts +++ b/packages/mobile-ui-vue/components/common/src/properties/input-base-property.ts @@ -1,27 +1,43 @@ -import { BaseControlProperty } from './base-property'; -import { DesignerComponentInstance } from "@farris/mobile-ui-vue/designer-canvas"; -import { SchemaDOMMapping } from "@farris/mobile-ui-vue/property-panel"; -import { canvasChanged } from "@farris/mobile-ui-vue/designer-canvas/src/composition/designer-canvas-changed"; -import { FormSchemaEntityFieldType$Type } from './entity-schema'; + +import { BaseControlProperty } from "./base-property"; +import { SchemaDOMMapping } from './schema-dom-mapping'; +import { ExpressionProperty } from "./expression-property"; +import { FormSchemaEntityField$Type, FormSchemaEntityFieldType$Type, FormVariable } from "../types/entity-schema"; +import { DesignerComponentInstance, FormBindingType, FormPropertyChangeObject } from "../types"; +import { DgControl } from "./dg-control"; export class InputBaseProperty extends BaseControlProperty { + /** 控件绑定的变量 */ + public bindingVarible?: FormVariable; + + public labelAlignVisible = true; + public labelAlignReadonly = false; + constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); } - - public getPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance) { + private getCommonPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance | null) { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicProperties(propertyData, componentInstance); // 外观 this.propertyConfig.categories['appearance'] = this.getAppearanceProperties(propertyData, componentInstance); + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); // 编辑器 this.propertyConfig.categories['editor'] = this.getEditorProperties(propertyData); + // 表达式编辑器 + this.propertyConfig.categories['expressons'] = this.getExpressionConfig(propertyData, 'Field'); + } + public getPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance) { + this.getCommonPropertyConfig(propertyData, componentInstance); + // 事件 暂不支持 + // this.propertyConfig.categories['eventsEditor'] = this.getEventPropertyConfig(propertyData); return this.propertyConfig; } public getBasicProperties(propertyData, componentInstance): any { const self = this; this.setDesignViewModelField(propertyData); + const { canChangeControlType, editorTypeList } = this.getAvailableEditorType(propertyData); return { description: 'Basic Information', title: '基本信息', @@ -36,20 +52,16 @@ export class InputBaseProperty extends BaseControlProperty { description: '编辑器类型', title: '编辑器类型', type: 'string', - refreshPanelAfterChanged: true, $converter: '/converter/change-editor.converter', editor: { type: 'combo-list', textField: 'value', valueField: 'key', + idField: 'key', editable: false, - data: self.designViewModelField ? SchemaDOMMapping.getEditorTypesByMDataType(self.designViewModelField.type?.name) : SchemaDOMMapping.getAllInputTypes() - } - }, - label: { - title: "标签", - type: "string", - $converter: '/converter/form-group-label.converter' + data: editorTypeList, + readonly: !canChangeControlType + }, }, binding: { description: "绑定的表单字段", @@ -65,10 +77,34 @@ export class InputBaseProperty extends BaseControlProperty { disableOccupiedFields: true }, textField: 'bindingField' - } + }, + refreshPanelAfterChanged: true + }, + label: { + title: "标签", + type: "string", + $converter: '/converter/form-group-label.converter' + }, + labelAlign: { + title: "标签排列方式", + type: "string", + $converter: '/converter/form-group-label.converter', + visible: this.labelAlignVisible, + editor: { + type: 'combo-list', + data: [{id: 'left', name: '左侧'},{id: 'top', name: '上侧'}], + readonly: this.labelAlignReadonly + }, + refreshPanelAfterChanged: true + }, + labelWidth: { + title: "标签宽度", + type: "string", + $converter: '/converter/form-group-label.converter', + visible: propertyData.labelAlign !== 'top' } }, - setPropertyRelates(changeObject, prop) { + setPropertyRelates(changeObject, prop, paramters: any) { if (!changeObject) { return; } @@ -81,10 +117,72 @@ export class InputBaseProperty extends BaseControlProperty { changeObject.needRefreshControlTree = true; break; } + case 'binding': { + self.changeBindingField(propertyData, changeObject, paramters); + break; + } } } }; } + + /** + * 校验控件是否支持切换类型 + * @param control 控件 + */ + private checkCanChangeControlType(propertyData: any, viewModelId: string): boolean { + // 没有绑定信息的控件 + if (!propertyData.binding) { + return false; + } + + if (propertyData.binding.type === 'Variable') { + this.bindingVarible = this.formSchemaUtils.getVariableById(propertyData.binding.field); + // vm中已移除的变量 + if (!this.bindingVarible) { + return false; + } + } else { + // schema中已移除的字段 或者绑定复杂类型的字段 + if (!this.designViewModelField || this.designViewModelField.$type !== FormSchemaEntityField$Type.SimpleField) { + return false; + } + } + return true; + + } + /** + * 获取可选的编辑器类型 + */ + private getAvailableEditorType(propertyData: any) { + const canChangeControlType = this.checkCanChangeControlType(propertyData, this.viewModelId); + if (!canChangeControlType) { + return { + canChangeControlType: false, + editorTypeList: [{ + key: propertyData.editor.type, + value: DgControl[propertyData.editor.type]?.name || propertyData.editor.type + }] + }; + } + + let editorTypeList: any = []; + if (this.designViewModelField && this.designViewModelField.$type === FormSchemaEntityField$Type.SimpleField) { + // 绑定字段 + editorTypeList = SchemaDOMMapping.getEditorTypesByMDataType(this.designViewModelField.type.name); + } else if (this.bindingVarible) { + // 绑定变量 + editorTypeList = SchemaDOMMapping.getEditorTypesByMDataType(this.bindingVarible.type); + } + return { canChangeControlType, editorTypeList }; + } + + public changeBindingField(propertyData: any, changeObject: FormPropertyChangeObject, paramters?: any): any { + // 刷新实体树 + // changeObject.needRefreshEntityTree = true; + } + + public getAppearanceProperties(propertyData, componentInstance): any { const self = this; @@ -101,9 +199,10 @@ export class InputBaseProperty extends BaseControlProperty { style: { title: "style样式", type: "string", - description: "组件的样式", + description: "组件的内联样式", $converter: "/converter/appearance.converter" } + }, setPropertyRelates(changeObject, prop) { if (!changeObject) { @@ -111,8 +210,13 @@ export class InputBaseProperty extends BaseControlProperty { } switch (changeObject && changeObject.propertyID) { case 'class': - self.updateElementByParentContainer(propertyData.id, componentInstance); + self.updateUnifiedLayoutAfterControlChanged(changeObject.propertyValue, propertyData.id, this.componentId); + // canvasChanged.value++; + break; + case 'style': { + // canvasChanged.value++; break; + } } } @@ -129,7 +233,7 @@ export class InputBaseProperty extends BaseControlProperty { * @param propertyData 控件DOM属性 * @param newControlType 新控件类型 */ - private changeControlType(propertyData, changeObject, componentInstance: DesignerComponentInstance) { + private changeControlType(propertyData, changeObject, componentInstance: DesignerComponentInstance | null) { const newControlType = changeObject.propertyValue; // 1、定位控件父容器 @@ -139,20 +243,24 @@ export class InputBaseProperty extends BaseControlProperty { } const index = parentContainer.contents.findIndex(c => c.id === propertyData.id); + if (index === -1) { + return; + } const oldControl = parentContainer.contents[index]; let newControl; - // 2、记录绑定字段viewModel的变更 + if (this.designViewModelField) { + // 2、记录绑定字段viewModel的变更 + const viewModel = this.formSchemaUtils.getViewModelById(this.viewModelId); + const viewModelField = viewModel.fields.find(field => field.id === this.designViewModelField.id); + const viewModelFieldSchema = viewModelField.fieldSchema || {}; + if (!viewModelFieldSchema.editor) { viewModelFieldSchema.editor = {}; } + viewModelFieldSchema.editor.$type = newControlType; + const dgViewModel = this.designViewModelUtils.getDgViewModel(this.viewModelId); - dgViewModel.changeField(this.designViewModelField.id, { - editor: { - $type: newControlType - }, - name: this.designViewModelField.name, - require: this.designViewModelField.require, - readonly: this.designViewModelField.readonly - }, false); + dgViewModel.changeField(this.designViewModelField.id, viewModelFieldSchema, false); + // 3、创建新控件 newControl = this.controlCreatorUtils.setFormFieldProperty(this.designViewModelField, newControlType); } @@ -165,21 +273,26 @@ export class InputBaseProperty extends BaseControlProperty { appearance: oldControl.appearance, size: oldControl.size, label: oldControl.label, - binding: oldControl.binding, - visible: oldControl.visible - }); - Object.assign(newControl.editor, { - isTextArea: newControl.isTextArea && oldControl.isTextArea, - placeholder: oldControl.editor?.placeholder, - holdPlace: oldControl.editor?.holdPlace, - readonly: oldControl.editor?.readonly, - required: oldControl.editor?.required, + binding: oldControl.binding }); - + // 解决undefined下默认值问题 + if (Object.prototype.hasOwnProperty.call(oldControl, 'visible')) { + Object.assign(newControl, { visible: oldControl.visible }); + } + if (oldControl.editor) { + ['readonly', 'required', 'placeholder'].map((item) => { + if (Object.prototype.hasOwnProperty.call(oldControl.editor, item)) { + newControl.editor[item] = oldControl.editor[item]; + } + }); + } // 5、替换控件 parentContainer.contents.splice(index, 1); parentContainer.contents.splice(index, 0, newControl); componentInstance.schema = Object.assign(oldControl, newControl); + // 重组VM + // this.designViewModelUtils.assembleDesignViewModel(); + Object.assign(propertyData, newControl); // 6、暂时移除旧控件的选中样式(后续考虑更好的方式) Array.from(document.getElementsByClassName('dgComponentSelected') as HTMLCollectionOf).forEach( @@ -190,11 +303,11 @@ export class InputBaseProperty extends BaseControlProperty { (element: HTMLElement) => element.classList.remove('dgComponentFocused') ); // 7、触发刷新 - canvasChanged.value++; + // canvasChanged.value++; } - public getComponentConfig(propertyData, info = {}, properties = {}, setPropertyRelates?: any) { + public getComponentConfig(propertyData, info = {}, properties = {}, setPropertyRelates?: (changeObject, propertyData, parameters) => any) { const editorBasic = Object.assign({ description: "编辑器", title: "编辑器", @@ -202,42 +315,81 @@ export class InputBaseProperty extends BaseControlProperty { $converter: "/converter/property-editor.converter" }, info); - const editorProperties = Object.assign({ + const readonlyEditor = this.getPropertyEditorParams(propertyData, [], 'readonly'); + const requiredEditor = this.getPropertyEditorParams(propertyData, [], 'required'); + const editorProperties = { readonly: { description: "", title: "只读", - type: "boolean", - editor: { - enableClear: true, - editable: true - } - }, - disabled: { - description: "", - title: "禁用", - type: "boolean", - visible: false + editor: readonlyEditor }, required: { description: "", title: "必填", type: "boolean", - visible: false + editor: requiredEditor }, placeholder: { - description: "空值时,输入控件内的占位文本", + description: "当控件没有值时在输入框中显示的文本", title: "提示文本", - type: "string", - visible: false - + type: "string" } - }, properties); + }; + for (const key in properties) { + // 合并属性,保留原属性值 + editorProperties[key] = Object.assign(editorProperties[key] || {}, properties[key]); + } + const self = this; - return { ...editorBasic, properties: { ...editorProperties }, setPropertyRelates }; + return { + ...editorBasic, properties: { ...editorProperties }, setPropertyRelates(changeObject, parameters: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'readonly': + case 'required': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + if (setPropertyRelates) { + setPropertyRelates(changeObject, propertyData, parameters); + } + } + }; } + /** + * 修改某一输入控件的样式后更新Form的统一布局配置 + * @param controlClass 控件样式 + * @param controlId 控件Id + * @param componentId 控件所在组件id + */ + private updateUnifiedLayoutAfterControlChanged(controlClass: string, controlId: string, componentId: string) { + const controlClassArray = controlClass.split(' '); + + let colClass = controlClassArray.find(item => /^col-([1-9]|10|11|12)$/.test(item)); + let colMDClass = controlClassArray.find(item => /^col-md-([1-9]|10|11|12)$/.test(item)); + let colXLClass = controlClassArray.find(item => /^col-xl-([1-9]|10|11|12)$/.test(item)); + let colELClass = controlClassArray.find(item => /^col-el-([1-9]|10|11|12)$/.test(item)); + + colClass = colClass || 'col-12'; + colMDClass = colMDClass || 'col-md-' + colClass.replace('col-', ''); + colXLClass = colXLClass || 'col-xl-' + colMDClass.replace('col-md-', ''); + colELClass = colELClass || 'col-el-' + colXLClass.replace('col-xl-', ''); + + const latestControlLayoutConfig = { + id: controlId, + columnInSM: parseInt(colClass.replace('col-', ''), 10), + columnInMD: parseInt(colMDClass.replace('col-md-', ''), 10), + columnInLG: parseInt(colXLClass.replace('col-xl-', ''), 10), + columnInEL: parseInt(colELClass.replace('col-el-', ''), 10), + }; + + } + /** * 枚举项编辑器 * @param propertyData @@ -253,7 +405,7 @@ export class InputBaseProperty extends BaseControlProperty { columns: [ { field: valueField, title: '值', dataType: 'string' }, { field: textField, title: '名称', dataType: 'string' }, - { field: 'disabled', title: '禁用', visible: false, dataType: 'boolean', editor: { type: 'switch' } }, + { field: 'disabled', title: '禁用', dataType: 'boolean', editor: { type: 'switch' } }, ], type: "item-collection-editor", valueField: valueField, @@ -265,12 +417,12 @@ export class InputBaseProperty extends BaseControlProperty { }; } /** - * 判断枚举数据是否只读 - * 1、没有绑定信息或者绑定变量,可以新增、删除、修改 - * 2、绑定类型为字段,且字段为枚举字段,则不可新增、删除、修改枚举值。只能从be修改然后同步到表单上。 - * @param propertyData 下拉框控件属性值 - */ - private checkEnumDataReadonly(propertyData: any): boolean { + * 判断枚举数据是否只读 + * 1、没有绑定信息或者绑定变量,可以新增、删除、修改 + * 2、绑定类型为字段,且字段为枚举字段,则不可新增、删除、修改枚举值。只能从be修改然后同步到表单上。 + * @param propertyData 下拉框控件属性值 + */ + protected checkEnumDataReadonly(propertyData: any): boolean { // 没有绑定信息或者绑定变量 if (!propertyData.binding || propertyData.binding.type !== 'Form') { return false; @@ -282,4 +434,118 @@ export class InputBaseProperty extends BaseControlProperty { } return false; } + /** + * 将字段值变化前事件、变化后事件追加到交互面板 + * 注意:因为绑定字段值变化后事件与下拉框的值变化事件(valueChanged)重名,所以这里将事件label都重命名下 + */ + appendFieldValueChangeEvents(propertyData: any, eventList: any[]) { + // 若绑定了字段,则需要显示字段变更前后事件 + if (propertyData.binding && propertyData.binding.type === FormBindingType.Form && propertyData.binding.field) { + const valueChangingExist = eventList.find(eventListItem => eventListItem.label === 'fieldValueChanging'); + if (!valueChangingExist) { + eventList.push( + { + label: 'fieldValueChanging', + name: '绑定字段值变化前事件' + } + ); + } + const valueChangedExist = eventList.find(eventListItem => eventListItem.label === 'fieldValueChanged'); + if (!valueChangedExist) { + eventList.push( + { + label: 'fieldValueChanged', + name: '绑定字段值变化后事件' + } + ); + } + if (this.designViewModelField) { + // 因为字段变更属性是存到VM中的,但是这里需要在控件上编辑,所以复制一份数据 + propertyData.fieldValueChanging = this.designViewModelField.valueChanging; + propertyData.fieldValueChanged = this.designViewModelField.valueChanged; + } + } else { + // 未绑定字段,则移除值变化事件 + eventList = eventList.filter(eventListItem => eventListItem.label !== 'fieldValueChanging' && eventListItem.label !== 'fieldValueChanged'); + } + } + private getControlMethodType(propertyData: any) { + if (!propertyData.binding) { + return propertyData.type; + } + switch (propertyData.binding.type) { + case FormBindingType.Form: { + return propertyData.binding.path || propertyData.type; + } + case FormBindingType.Variable: { + return propertyData.binding.fullPath || propertyData.type; + } + } + + return propertyData.type; + } + /** + * 组装输入类控件的交互面板:包含标签超链、绑定字段值变化前后事件等。 + * @param propertyData 属性值 + * @param viewModelId 视图模型id + * @param showPosition 面板展示位置 + * @param customEvent 输入控件特有的事件配置 + */ + public getEventPropertyConfig(propertyData: any, showPosition = 'card', customEventList?: { label: string, name: string }[], setPropertyRelates?: (changeObject, data, parameters) => void) { + const self = this; + // 静态事件 + let eventList = [] as any; + if (customEventList) { + eventList = eventList.concat(customEventList); + } + // 追加值变化 + this.appendFieldValueChangeEvents(propertyData, eventList); + + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, eventList); + const properties = {}; + properties[self.viewModelId] = { + type: 'events-editor', + editor: { + initialData + } + }; + const eventsEditorConfig = { + title: '事件', + hideTitle: true, + properties, + tabId: 'commands', + tabName: '交互', + setPropertyRelates(changeObject: FormPropertyChangeObject, data: any) { + const parameters = changeObject.propertyValue; + delete propertyData[self.viewModelId]; + if (parameters) { + parameters.setPropertyRelates = this.setPropertyRelates; // 添加自定义方法后,调用此回调方法,用于处理联动属性 + parameters.controlInfo = { type: self.getControlMethodType(propertyData), name: propertyData.title }; // 暂存控件信息,用于自动创建新方法的方法编号和名称 + + self.eventsEditorUtils.saveRelatedParameters(propertyData, self.viewModelId, parameters['events'], parameters); + } + + if (setPropertyRelates) { + setPropertyRelates(changeObject, data, parameters); + } + + // 同步视图模型值变化事件 + const designVM = self.designViewModelUtils.getDgViewModel(self.viewModelId); + if (designVM && self.designViewModelField) { + designVM.changeField(self.designViewModelField.id, { valueChanging: propertyData.fieldValueChanging, valueChanged: propertyData.fieldValueChanged }); + } + } + }; + + return eventsEditorConfig; + } + + getExpressionConfig(propertyData: any, type: 'Field' | 'Button' | 'Container', + expressionTypes: string[] = ['compute', 'dependency', 'validate'], + associationCallBack?: (rule: Record) => void + ) { + return new ExpressionProperty(this.formSchemaUtils).getExpressionConfig( + propertyData, type, expressionTypes, associationCallBack); + } + } diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/entity/schema-dom-mapping.ts b/packages/mobile-ui-vue/components/common/src/properties/schema-dom-mapping.ts similarity index 97% rename from packages/mobile-ui-vue/components/property-panel/src/composition/entity/schema-dom-mapping.ts rename to packages/mobile-ui-vue/components/common/src/properties/schema-dom-mapping.ts index 066d549361dc5d754b30fdefcf061a1389703eb8..71f7291318c39c4c8b25616a7b694db330805072 100644 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/entity/schema-dom-mapping.ts +++ b/packages/mobile-ui-vue/components/common/src/properties/schema-dom-mapping.ts @@ -1,5 +1,5 @@ -import { DgControl } from "../../../../designer-canvas/src/composition/dg-control"; +import { DgControl } from "./dg-control"; export class SchemaDOMMapping { diff --git a/packages/mobile-ui-vue/components/common/src/properties/toolbar-item.property.ts b/packages/mobile-ui-vue/components/common/src/properties/toolbar-item.property.ts new file mode 100644 index 0000000000000000000000000000000000000000..16d97a2ee1cbdbee38b0e4d9c2a9b63d09cf4fbf --- /dev/null +++ b/packages/mobile-ui-vue/components/common/src/properties/toolbar-item.property.ts @@ -0,0 +1,182 @@ +import { BaseControlProperty } from "./base-property"; + +export interface ToolbarItemPropOptions { + showAppearance?: boolean; + showButtonType?: boolean; + showIcon?: boolean; + showRound?: boolean; + showPlain?: boolean; + showVariant?: boolean; +} + +export class ToolbarItemProperty extends BaseControlProperty { + + private options: ToolbarItemPropOptions | undefined; + + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + + public getPropertyConfig(propertyData: any, options?: ToolbarItemPropOptions) { + this.options = options; + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + // 外观 + if (this.shouldShowAppearanceConfig()) { + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + } + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + // 事件 + this.getEventPropConfig(propertyData); + + return this.propertyConfig; + } + + private shouldShowAppearanceConfig(): boolean { + return !!this.options?.showAppearance; + } + + protected getAppearanceConfig(propertyData: any) { + const buttonTypeData = [ + { text: '主要按钮', value: 'primary' }, + { text: '危险按钮', value: 'danger' }, + { text: '警告按钮', value: 'warning' }, + { text: '成功按钮', value: 'success' }, + { text: '次要按钮', value: 'secondary' }, + { text: '提示按钮', value: 'info' }, + ]; + const additionalProperties: any = {}; + if (this.options?.showButtonType) { + additionalProperties.displayType = { + description: '按钮的主题风格', + title: '按钮类型', + editor: { + type: 'combo-tree', + textField: 'text', + valueField: 'value', + data: buttonTypeData, + }, + }; + } + if (this.options?.showIcon) { + additionalProperties.icon = { + description: '显示在按钮文本左侧的图标', + title: '按钮图标', + type: 'string', + }; + } + if (this.options?.showRound) { + additionalProperties.round = { + description: '是否为按钮启用圆角', + title: '是否圆角', + type: 'boolean', + }; + } + if (this.options?.showPlain) { + additionalProperties.plain = { + description: '是否朴素按钮', + title: '是否朴素按钮', + type: 'boolean', + }; + } + if (this.options?.showVariant) { + additionalProperties.variant = { + description: '按钮的变体', + title: '变体', + editor: { + type: 'combo-tree', + textField: 'text', + valueField: 'value', + data: [ + { text: '无', value: 'default' }, + { text: '图标在上', value: 'bare-vertical' }, + ], + }, + }; + } + return super.getAppearanceConfig(propertyData, additionalProperties); + } + + getBasicPropConfig(propertyData: any): any { + return { + description: 'Basic Information', + title: '基本信息', + properties: { + id: { + description: '工具栏按钮的标识', + title: '标识', + type: 'string', + readonly: true + }, + text: { + description: '工具栏按钮的标签', + title: '标签', + type: 'string', + readonly: false + } + } + }; + } + + public getBehaviorConfig(propertyData: any) { + const visibleProperty = this.getVisibleProperty(propertyData); + const self = this; + return { + description: "基本信息", + title: "行为", + properties: { + ...visibleProperty, + disabled: { + title: "是否禁用", + type: "boolean", + } + }, + setPropertyRelates(changeObject: any, data: any) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'visible': + self.afterMutilEditorChanged(propertyData, changeObject); + break; + } + } + }; + } + + private getEventPropConfig(propertyData: any) { + const events = [ + { + label: "onClick", + name: "点击事件", + } + ]; + const self = this; + const initialData = self.eventsEditorUtils['formProperties'](propertyData, self.viewModelId, events); + const properties = {}; + properties[self.viewModelId] = { + type: 'events-editor', + editor: { + initialData + } + }; + this.propertyConfig.categories['eventsEditor'] = { + title: '事件', + hideTitle: true, + properties, + refreshPanelAfterChanged: true, + tabId: 'commands', + tabName: '交互', + setPropertyRelates(changeObject: any, data: any) { + const parameters = changeObject.propertyValue; + delete propertyData[self.viewModelId]; + if (parameters) { + parameters.setPropertyRelates = this.setPropertyRelates; + self.eventsEditorUtils.saveRelatedParameters(propertyData, self.viewModelId, parameters['events'], parameters); + } + } + }; + } + +} diff --git a/packages/mobile-ui-vue/components/common/src/properties/use-property-editor.ts b/packages/mobile-ui-vue/components/common/src/properties/use-property-editor.ts new file mode 100644 index 0000000000000000000000000000000000000000..edc1e1d510145b7459b33e306a9346228b7681e8 --- /dev/null +++ b/packages/mobile-ui-vue/components/common/src/properties/use-property-editor.ts @@ -0,0 +1,73 @@ +import { DesignerHostService } from "../types"; + +/** + * PropertyEditor对外提供的接口 + * @param designerHostService + * @returns + */ +export function usePropertyEditor(designerHostService: DesignerHostService): any { + const { formSchemaUtils, formStateMachineUtils } = designerHostService; + + /** + * 把变量视图模型的变量转化为PropertyEditor的变量格式 + * @param variable + * @returns + */ + function convertToEditorVariable(variable: any, pathPrefix: string = ''): any { + return { + path: pathPrefix + variable.code, + field: variable.id, + fullPath: variable.code + }; + } + + /** + * 获取视图模型上的变量 + * @param viewModelId + * @returns + */ + function getVariablesByViewModelId(viewModelId: string, pathPrefix: string = ''): any[] { + const viewModel = formSchemaUtils.getViewModelById(viewModelId); + return viewModel.states.map(state => convertToEditorVariable(state, pathPrefix)); + } + + /** + * 获取PropertyEditor需要的变量数据 + */ + function getVariables(viewModelId: string): any[] { + const rootViewModelId = formSchemaUtils.getRootViewModelId(); + // 1、当前组件的组件变量 + const currentViewModelVariables = getVariablesByViewModelId(viewModelId); + if (viewModelId === rootViewModelId) { + return currentViewModelVariables; + } + + // 2、根组件的组件变量 + const rootViewModelVariables = getVariablesByViewModelId(rootViewModelId, 'root-component.'); + return [...currentViewModelVariables, ...rootViewModelVariables]; + } + + /** + * 获取控件名称 + * @param propertyData + * @returns + */ + function getControlName(propertyData: any): string { + const controlName = propertyData.binding && propertyData.binding.path || propertyData.id || ''; + return controlName; + } + + /** + * 获取PropertyEditor需要的状态机数据 + */ + function getStateMachines(viewModelId:string): any[] { + const renderStates = formStateMachineUtils && formStateMachineUtils.getRenderStates(); + if(renderStates){ + const viewModel = formSchemaUtils.getViewModelById(viewModelId); + return renderStates.filter(renderState => renderState.stateMachineId === viewModel.stateMachine); + } + return renderStates || []; + } + + return { getVariables, getControlName, getStateMachines }; +} diff --git a/packages/mobile-ui-vue/components/common/src/style/variables.scss b/packages/mobile-ui-vue/components/common/src/style/variables.scss index 8d7e07e4e7594c62b37004e8e77411801a9963b0..8bca70a58c56bb2e8fb3b5df64ec334799c2fc96 100644 --- a/packages/mobile-ui-vue/components/common/src/style/variables.scss +++ b/packages/mobile-ui-vue/components/common/src/style/variables.scss @@ -73,6 +73,7 @@ --fm-font-bold: 600; --fm-font-size: 16px; --fm-line-height: 1.2; + --fm-line-height-lg: 1.5; --fm-font-size-xs: 10px; --fm-font-size-sm: 12px; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/types.ts b/packages/mobile-ui-vue/components/common/src/types/designer-component.ts similarity index 96% rename from packages/mobile-ui-vue/components/designer-canvas/src/types.ts rename to packages/mobile-ui-vue/components/common/src/types/designer-component.ts index c63ceb11c799e56767b8962c6e51f34ea487c690..f30ca3c44d414c64c4e6400a2dd641038d6c4736 100644 --- a/packages/mobile-ui-vue/components/designer-canvas/src/types.ts +++ b/packages/mobile-ui-vue/components/common/src/types/designer-component.ts @@ -1,6 +1,6 @@ /* eslint-disable no-use-before-define */ import { Ref, SetupContext } from "vue"; -import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext } from "./composition/types"; +import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext } from "./designer-rule"; export interface ComponentSchema { @@ -78,7 +78,7 @@ export interface DesignerComponentInstance { getCustomButtons?: () => DesignerComponentButton[]; /** 控件属性变更后事件 */ - onPropertyChanged?: (event:any) => void; + onPropertyChanged?: (event: any) => void; } export interface DesignerItemContext { diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/types.ts b/packages/mobile-ui-vue/components/common/src/types/designer-rule.ts similarity index 92% rename from packages/mobile-ui-vue/components/designer-canvas/src/composition/types.ts rename to packages/mobile-ui-vue/components/common/src/types/designer-rule.ts index a3dc5bbfad20596649c87001a8891b6d575a47e7..6375578d0fb1d605890f08c56dc23796fc23e3da 100644 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/types.ts +++ b/packages/mobile-ui-vue/components/common/src/types/designer-rule.ts @@ -1,6 +1,6 @@ -import { DesignFormVariable, DesignViewModelField, FormSchemaEntity, FormSchemaEntityField } from "../../../common/entity/entity-schema"; import { Ref } from "vue"; -import { ComponentSchema, DesignerComponentButton, DesignerComponentInstance } from "../types"; +import { ComponentSchema, DesignerComponentButton, DesignerComponentInstance, DesignerItemContext } from "./designer-component"; +import { DesignFormVariable, DesignViewModelField, FormSchemaEntity, FormSchemaEntityField } from "./entity-schema"; export interface DesignerHTMLElement extends HTMLElement { /** 记录各子元素对应的控件schema json的集合,用于container类dom节点 */ @@ -12,14 +12,6 @@ export interface DesignerHTMLElement extends HTMLElement { schema: ComponentSchema; } - -export interface UseDragula { - attachComponents: (element: HTMLElement, component: ComponentSchema) => void; - - initializeDragula: (containerElement: DesignerHTMLElement) => void; - - getDragulaInstance: () => any; -} export interface DesignerHostService { eventsEditorUtils: any; formSchemaUtils: any; @@ -151,5 +143,8 @@ export interface UseDesignerRules { getCustomButtons?: () => DesignerComponentButton[]; /** 控件属性变更后事件 */ - onPropertyChanged?: (event:any) => void; + onPropertyChanged?: (event: any) => void; + + /** 获取可拖拽的上层容器 */ + getDraggableDesignItemElement?: (context: DesignerItemContext) => Ref | null; } diff --git a/packages/mobile-ui-vue/components/common/src/entity/entity-schema.ts b/packages/mobile-ui-vue/components/common/src/types/entity-schema.ts similarity index 100% rename from packages/mobile-ui-vue/components/common/src/entity/entity-schema.ts rename to packages/mobile-ui-vue/components/common/src/types/entity-schema.ts diff --git a/packages/mobile-ui-vue/components/common/src/types/index.ts b/packages/mobile-ui-vue/components/common/src/types/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..195bea74cd34c250e739f2c5ef58be6524bfa349 --- /dev/null +++ b/packages/mobile-ui-vue/components/common/src/types/index.ts @@ -0,0 +1,5 @@ +export * from './types'; +export * from './designer-component'; +export * from './designer-rule'; +export * from './entity-schema'; +export * from './property-config'; diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/entity/property-entity.ts b/packages/mobile-ui-vue/components/common/src/types/property-config.ts similarity index 79% rename from packages/mobile-ui-vue/components/property-panel/src/composition/entity/property-entity.ts rename to packages/mobile-ui-vue/components/common/src/types/property-config.ts index 5650de17982343f9aae78c1feefd1b04b20894cf..0b525f2bea35eb24f03127225016801ae9b2f1d3 100644 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/entity/property-entity.ts +++ b/packages/mobile-ui-vue/components/common/src/types/property-config.ts @@ -1,4 +1,3 @@ -import { EditorConfig } from "@farris/mobile-ui-vue/dynamic-form"; import { Ref } from "vue"; export interface KeyMap { @@ -6,6 +5,29 @@ export interface KeyMap { value: any; } +export type EditorType = 'button-edit' | 'check-box' | 'check-group' | 'combo-list' | 'combo-lookup' | 'combo-tree' | + 'date-picker' | 'date-range' | 'datetime-picker' | 'datetime-range' | 'events-editor' | 'month-picker' | 'month-range' | + 'year-picker' | 'year-range' | 'input-group' | 'lookup' | 'number-range' | 'number-spinner' | 'radio-group' | 'text' | + 'response-layout-editor-setting' | 'switch' | 'grid-field-editor' | 'field-selector' | 'schema-selector' | 'mapping-editor' | + 'textarea' | 'response-form-layout-setting'|'binding-selector' | 'query-solution-config' | 'solution-preset' | 'item-collection-editor'; + +export interface EditorConfig { + /** 编辑器类型 */ + type: EditorType; + /** 自定义样式 */ + customClass?: string; + /** 禁用 */ + disabled?: boolean; + /** 只读 */ + readonly?: boolean; + /** 必填 */ + required?: boolean; + /** 提示文本 */ + placeholder?: string; + /** 其他属性 */ + [key: string]: any; +} + /** 属性实体 */ export interface PropertyEntity { /** @@ -223,8 +245,8 @@ export interface FormPropertyChangeObject extends PropertyChangeObject { /** 是否需要刷新控件树 */ needRefreshControlTree?: boolean; - /** 是否需要更新控件树节点名称 */ - needUpdateControlTreeNodeName?: boolean; + /** 是否需要刷新控件树 */ + needRefreshEntityTree?: boolean; /** 关联变更的属性集合,用于更新表单DOM属性 */ relateChangeProps?: Array<{ @@ -232,9 +254,13 @@ export interface FormPropertyChangeObject extends PropertyChangeObject { propertyValue: any }>; - /** 属性变更后是否需要向schema字段同步 */ - needSyncToSchemaField?: boolean; - /** 强关联的属性id:在当前属性变更后,页面自动定位到强关联的属性 */ autoLocatedPropertyId?: string; } + + +export enum FormBindingType { + Form = "Form", + Variable = "Variable" +} + diff --git a/packages/mobile-ui-vue/components/common/types.ts b/packages/mobile-ui-vue/components/common/src/types/types.ts similarity index 97% rename from packages/mobile-ui-vue/components/common/types.ts rename to packages/mobile-ui-vue/components/common/src/types/types.ts index 351245ac179f504189fe4585915dc7d14424b87b..b0c10ec0675c8abedb42be0f6383977821cbe5e4 100644 --- a/packages/mobile-ui-vue/components/common/types.ts +++ b/packages/mobile-ui-vue/components/common/src/types/types.ts @@ -1,5 +1,5 @@ import { ComputedRef, Ref } from "vue"; -import { EffectFunction, SchemaResolverFunction } from "../dynamic-resolver"; +import { EffectFunction, SchemaResolverFunction } from "@farris/mobile-ui-vue/dynamic-resolver/src/types"; export interface TextBoxProps { @@ -136,4 +136,4 @@ export interface RegisterContext { schemaResolverMap: Record; propertyConfigSchemaMap: Record; propertyEffectMap: Record; -} \ No newline at end of file +} diff --git a/packages/mobile-ui-vue/components/common/src/utils/src/common.ts b/packages/mobile-ui-vue/components/common/src/utils/src/common.ts index 1010e19020f584c0011c18b95daa19ff85c425ef..d258ec7627ac43f26006460f5b55c34dba3ce2d9 100644 --- a/packages/mobile-ui-vue/components/common/src/utils/src/common.ts +++ b/packages/mobile-ui-vue/components/common/src/utils/src/common.ts @@ -86,3 +86,14 @@ export const setValue = (obj: Record, field: string, val: any) => { obj[field] = val; } }; +export function extractProperties( + obj: T, + props: K[] +): Pick { + return props.reduce((result, prop) => { + if (prop in obj) { + result[prop] = obj[prop]; + } + return result; + }, {} as Pick); +} diff --git a/packages/mobile-ui-vue/components/common/src/utils/src/with-register-designer.ts b/packages/mobile-ui-vue/components/common/src/utils/src/with-register-designer.ts index eb801e50c1bf20afb37fe5ee02f63bb000002a0c..b0b5557958ac4bb5589ef9e47acf776dca5a8a70 100644 --- a/packages/mobile-ui-vue/components/common/src/utils/src/with-register-designer.ts +++ b/packages/mobile-ui-vue/components/common/src/utils/src/with-register-designer.ts @@ -1,4 +1,4 @@ -import { RegisterContext } from '@farris/mobile-ui-vue/common/types'; +import { RegisterContext } from '@farris/mobile-ui-vue/common'; import type { App, Component } from 'vue'; export type WithRegisterDesigner = T & { diff --git a/packages/mobile-ui-vue/components/common/src/utils/src/with-register.ts b/packages/mobile-ui-vue/components/common/src/utils/src/with-register.ts index 94a387f7ed89f2fa61c2463fb9796624d7583f06..15608d737bd99b87dd2a53d032751f640b7da7b5 100644 --- a/packages/mobile-ui-vue/components/common/src/utils/src/with-register.ts +++ b/packages/mobile-ui-vue/components/common/src/utils/src/with-register.ts @@ -1,4 +1,4 @@ -import { RegisterContext } from '@farris/mobile-ui-vue/common/types'; +import { RegisterContext } from '@farris/mobile-ui-vue/common'; import type { App, Component } from 'vue'; export type WithRegister = T & { diff --git a/packages/mobile-ui-vue/components/component/index.ts b/packages/mobile-ui-vue/components/component/index.ts index ba722ade21f9ec81eaec0c9a148c5bd1ad75c43c..bf0f9d3a345668840eef6bac567e82efd5f30834 100644 --- a/packages/mobile-ui-vue/components/component/index.ts +++ b/packages/mobile-ui-vue/components/component/index.ts @@ -42,4 +42,4 @@ Component.registerDesigner = ( export * from './src/component.props'; export { Component }; -export default Component as typeof Component & Plugin; +export default Component; diff --git a/packages/mobile-ui-vue/components/component/src/designer/component.design.component.tsx b/packages/mobile-ui-vue/components/component/src/designer/component.design.component.tsx index b49f4f2b19daec817e99e41dae26802131560539..d2e9b308c7b52ac34a5a2d164867d1431d69fad4 100644 --- a/packages/mobile-ui-vue/components/component/src/designer/component.design.component.tsx +++ b/packages/mobile-ui-vue/components/component/src/designer/component.design.component.tsx @@ -1,7 +1,7 @@ import { SetupContext, defineComponent, inject, ref, onMounted, computed } from 'vue'; import { ComponentPropsType, componentProps } from '../component.props'; import { useDesignerRules } from './use-designer-rules'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas'; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common'; export default defineComponent({ name: 'FComponetDesign', diff --git a/packages/mobile-ui-vue/components/component/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/component/src/designer/use-designer-rules.ts index 96d8098d982b43dbfd68240f9c401356a1a81757..42ed7e2a757f36669b3c797535520d3469086762 100644 --- a/packages/mobile-ui-vue/components/component/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/component/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { ComponentProperty } from "../property-config/component.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { @@ -11,20 +11,29 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } function checkCanDeleteComponent() { - return false; + if (designItemContext?.schema?.componentType === 'page') { + return false; + } + return true; } function checkCanMoveComponent() { + if (designItemContext?.schema?.componentType === 'page') { + return false; + } return true; } - function hideNestedPaddingInDesginerView() { return false; } function getStyles(): string { - return ' '; + const componentType = designItemContext?.schema?.componentType; + if (componentType === 'page') { + return `flex: 1;position: relative;`; + } + return ''; } /** @@ -74,8 +83,8 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe parentComponentInstance['parent']['updateToolbarItems'](); } } - } + /** * 组件删除后事件:移除viewmodel和component */ @@ -91,6 +100,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe formSchemaUtils.deleteComponent(schema.id); } } + /** * 组件删除后事件:移除表达式、界面规则、受控规则等全局配置 */ @@ -111,6 +121,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } }); } + /** * 组件删除后事件 */ @@ -119,5 +130,19 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe removeGlobalConfigs(); removeViewModelComponent(); } - return { canAccepts, checkCanDeleteComponent, checkCanMoveComponent, hideNestedPaddingInDesginerView, getStyles, getPropsConfig, onRemoveComponent }; + + function getDraggableDesignItemElement(context: DesignerItemContext) { + return context.designerItemElementRef; + } + + return { + canAccepts, + checkCanDeleteComponent, + checkCanMoveComponent, + hideNestedPaddingInDesginerView, + getStyles, + getPropsConfig, + onRemoveComponent, + getDraggableDesignItemElement, + }; } diff --git a/packages/mobile-ui-vue/components/component/src/property-config/component.property-config.ts b/packages/mobile-ui-vue/components/component/src/property-config/component.property-config.ts index 3fa6c4ccc37628a68f65f091fd0287a22cb4a0e2..8186280ca0de2a39e5b9d721c93b407a53aa7572 100644 --- a/packages/mobile-ui-vue/components/component/src/property-config/component.property-config.ts +++ b/packages/mobile-ui-vue/components/component/src/property-config/component.property-config.ts @@ -1,10 +1,12 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { BaseControlProperty } from "@farris/mobile-ui-vue/common"; export class ComponentProperty extends BaseControlProperty { + constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); } + public getPropertyConfig(propertyData: any) { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); @@ -12,8 +14,30 @@ export class ComponentProperty extends BaseControlProperty { this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); // 事件 this.getEventPropConfig(propertyData); + return this.propertyConfig; } + + protected getAppearanceConfig(propertyData: any) { + const viewModel = this.formSchemaUtils.getViewModelById(this.viewModelId); + propertyData.name = viewModel?.name ?? null; + return super.getAppearanceConfig(propertyData, { + name: { + title: "组件名称", + type: "string", + description: "组件名称", + defaultValue: propertyData.name, + }, + }, (changeObject) => { + switch (changeObject.propertyID) { + case 'name': { + this.syncChangesToViewModel({ name: changeObject.propertyValue }); + break; + } + } + }); + } + private getEventPropConfig(propertyData: any) { const events = [ { @@ -60,4 +84,13 @@ export class ComponentProperty extends BaseControlProperty { } }; } + + /** 将变更的属性值同步到视图模型中 */ + private syncChangesToViewModel(changeObject: any) { + const viewModel = this.formSchemaUtils.getViewModelById(this.viewModelId); + if (!viewModel) { + return; + } + Object.assign(viewModel, changeObject); + } } diff --git a/packages/mobile-ui-vue/components/content-container/index.ts b/packages/mobile-ui-vue/components/content-container/index.ts index d6c256518cfed069637f42a0834f2808eec5a729..06a7fa82d104e7b8f920453824388139859d786a 100644 --- a/packages/mobile-ui-vue/components/content-container/index.ts +++ b/packages/mobile-ui-vue/components/content-container/index.ts @@ -1,32 +1,15 @@ -import { App, Plugin } from 'vue'; -import FContentContainer from './src/content-container.component'; +import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile-ui-vue/common'; +import ContentContainerInstallless from './src/content-container.component'; import { propsResolverGenerator } from './src/content-container.props'; import ContentContainerDesign from './src/designer/content-container.design.component'; -import { RegisterContext } from '../common'; const CONTENT_CONTAINER_REGISTERED_NAME = 'content-container'; -FContentContainer.install = (app: App) => { - app.component(FContentContainer.name as string, FContentContainer); -}; - -FContentContainer.register = ( - componentMap: Record, propsResolverMap: Record, - configresolverMap: Record, resolverMap: Record,registerContext: RegisterContext -) => { - componentMap[CONTENT_CONTAINER_REGISTERED_NAME] = FContentContainer; - propsResolverMap[CONTENT_CONTAINER_REGISTERED_NAME] = propsResolverGenerator(registerContext); -}; - -FContentContainer.registerDesigner = ( - componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext -) => { - componentMap[CONTENT_CONTAINER_REGISTERED_NAME] = ContentContainerDesign; - propsResolverMap[CONTENT_CONTAINER_REGISTERED_NAME] = propsResolverGenerator(registerContext); -}; +const ContentContainer = withInstall(ContentContainerInstallless); +withRegister(ContentContainer, { name: CONTENT_CONTAINER_REGISTERED_NAME, propsResolverGenerator }); +withRegisterDesigner(ContentContainer, { name: CONTENT_CONTAINER_REGISTERED_NAME, propsResolverGenerator, designerComponent: ContentContainerDesign }); export * from './src/content-container.props'; -export { FContentContainer }; -export default FContentContainer as typeof FContentContainer & Plugin; +export { ContentContainer }; +export default ContentContainer; diff --git a/packages/mobile-ui-vue/components/content-container/src/content-container.component.tsx b/packages/mobile-ui-vue/components/content-container/src/content-container.component.tsx index b1376f278cd5432e94c4856c10fba5821a30d99c..1d8bf1f326b3214dd4a7e3a14a72c765ceb5940f 100644 --- a/packages/mobile-ui-vue/components/content-container/src/content-container.component.tsx +++ b/packages/mobile-ui-vue/components/content-container/src/content-container.component.tsx @@ -1,4 +1,4 @@ -import { SetupContext, defineComponent, computed } from 'vue'; +import { defineComponent, computed, ref, CSSProperties } from 'vue'; import { CONTENT_CONTAINER_NAME, ContentContainerProps, contentContainerProps } from './content-container.props'; import { useBem } from '../../common/index'; @@ -6,20 +6,77 @@ export default defineComponent({ name: CONTENT_CONTAINER_NAME, props: contentContainerProps, emits: [], - setup(props: ContentContainerProps, context: SetupContext) { + setup(props: ContentContainerProps, context) { const { bem } = useBem(CONTENT_CONTAINER_NAME); + const elementRef = ref(); + + function isValidDirection(value: string): boolean { + return ["top", "right", "bottom", "left"].includes(value); + } + + const containerPaddingStyle = computed(() => { + const paddingStyle = {}; + Object.keys(props.padding || {}).filter((direction) => { + return isValidDirection(direction); + }).forEach((direction) => { + if (typeof props.padding[direction] === 'number') { + paddingStyle[`padding-${direction}`] = `${props.padding[direction]}px`; + } + }); + return paddingStyle; + }); + + const containerMarginStyle = computed(() => { + const marginStyle = {}; + Object.keys(props.margin || {}).filter((direction) => { + return isValidDirection(direction); + }).forEach((direction) => { + if (typeof props.margin[direction] === 'number') { + marginStyle[`margin-${direction}`] = `${props.margin[direction]}px`; + } + }); + return marginStyle; + }); + + const containerSizeStyle = computed(() => ({ + width: typeof props.size?.width === 'number' ? `${props.size.width}px` : undefined, + height: typeof props.size?.height === 'number' ? `${props.size.height}px` : undefined, + })); + + const containerStyle = computed(() => { + return { + ...containerPaddingStyle.value, + ...containerMarginStyle.value, + ...containerSizeStyle.value, + }; + }); + + const containerFlexClass = computed(() => { + if (props.display !== 'flex') { + return {}; + } + return { + [`flex-${props.flexBox?.direction}`]: !!props.flexBox?.direction, + [`flex-wrap`]: props.flexBox?.wrap, + [`flex-nowrap`]: !props.flexBox?.wrap, + [`justify-content-${props.flexBox?.justifyContent}`]: !!props.flexBox?.justifyContent, + [`align-items-${props.flexBox?.alignItems}`]: !!props.flexBox?.alignItems, + }; + }); const containerClass = computed(() => ({ [bem()]: true, - [props.customClass || '']: true, + [`d-flex`]: props.display === 'flex', + ...containerFlexClass.value, })); - const customStyle = computed(() => { - return props.customStyle; - }); + context.expose({ elementRef }); return () => ( -
+
{context.slots.default && context.slots.default()}
); diff --git a/packages/mobile-ui-vue/components/content-container/src/content-container.props.ts b/packages/mobile-ui-vue/components/content-container/src/content-container.props.ts index 213f331a3db1f7a5dae38fd426c5d8d7923a7ff0..823e195a8edccbc7846a426e1f32f73af82c7a84 100644 --- a/packages/mobile-ui-vue/components/content-container/src/content-container.props.ts +++ b/packages/mobile-ui-vue/components/content-container/src/content-container.props.ts @@ -1,4 +1,4 @@ -import { ExtractPropTypes } from 'vue'; +import { ExtractPropTypes, PropType } from 'vue'; import { getPropsResolverGenerator } from '../../dynamic-resolver'; import contentContainerSchema from './schema/content-container.schema.json'; import { schemaMapper } from './schema/schema-mapper'; @@ -6,9 +6,42 @@ import { schemaResolver } from './schema/schema-resolver'; export const CONTENT_CONTAINER_NAME = 'fm-content-container'; +export interface ContainerPadding { + top?: number; + right?: number; + bottom?: number; + left?: number; +} + +export interface ContainerMargin { + top?: number; + right?: number; + bottom?: number; + left?: number; +} + +export interface ContainerSize { + width?: number; + height?: number; +} + +export type ContainerDisplay = "flex" | "block"; + +export interface ContainerFlexBox { + direction?: "row" | "column"; + wrap?: boolean; + justifyContent?: "start" | "center" | "end" | "between" | "around"; + alignItems?: "start" | "center" | "end"; +} + export const contentContainerProps = { customClass: { type: String, default: '' }, customStyle: { type: String, default: '' }, + padding: { type: Object as PropType, default: {} }, + margin: { type: Object as PropType, default: {} }, + size: { type: Object as PropType, default: {} }, + display: { type: String as PropType, default: "flex" }, + flexBox: { type: Object as PropType, default: {} }, }; export type ContentContainerProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/content-container/src/designer/content-container.design.component.tsx b/packages/mobile-ui-vue/components/content-container/src/designer/content-container.design.component.tsx index 5164ec64e00bbdd28fc4c6b3ef9015acc4fb7a42..edc18151625f3cdecbad56099ec64d82c0419063 100644 --- a/packages/mobile-ui-vue/components/content-container/src/designer/content-container.design.component.tsx +++ b/packages/mobile-ui-vue/components/content-container/src/designer/content-container.design.component.tsx @@ -1,37 +1,44 @@ -import { SetupContext, computed, defineComponent, inject, onMounted, ref } from 'vue'; -import { ContentContainerPropsType, contentContainerProps } from '../content-container.props'; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { ContentContainerProps, contentContainerProps } from '../content-container.props'; import { useDesignerRulesForContentContainer } from './use-designer-rules'; -import { getCustomClass } from '@farris/mobile-ui-vue/common'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common'; +import { ContentContainer } from '@farris/mobile-ui-vue/content-container'; export default defineComponent({ name: 'FContentContainerDesign', props: contentContainerProps, emits: [], - setup(props: ContentContainerPropsType, context) { - const elementRef = ref(); + setup(props: ContentContainerProps, context) { + const contentContainerRef = ref(); + const elementRef = computed(() => { + return contentContainerRef.value?.elementRef; + }); + const designerHostService = inject('designer-host-service'); const designItemContext = inject('design-item-context') as DesignerItemContext; const designerRulesComposition = useDesignerRulesForContentContainer(designItemContext, designerHostService); const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); - const containerClass = computed(() => { - const classObject = { - 'drag-container': true - } as Record; - return getCustomClass(classObject, props?.customClass); - }); onMounted(() => { elementRef.value.componentInstance = componentInstance; }); - context.expose(componentInstance.value); + const designProps = computed(() => ({ + ...props, + display: 'block', + })); + return () => { return ( -
+ {context.slots.default && context.slots.default()} -
+ ); }; } diff --git a/packages/mobile-ui-vue/components/content-container/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/content-container/src/designer/use-designer-rules.ts index 9520f9793cdbd088f152543b8bbe62cff321f575..6871ed7a654dfdabad5a877278f9fcc9dfc31bb9 100644 --- a/packages/mobile-ui-vue/components/content-container/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/content-container/src/designer/use-designer-rules.ts @@ -1,38 +1,29 @@ -import { ComponentSchema, DesignerItemContext } from "@farris/mobile-ui-vue/designer-canvas"; +import { DesignerItemContext } from "@farris/mobile-ui-vue/common"; import { ContentContainerProperty } from "../property-config/content-container.property-config"; -import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; +import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; export function useDesignerRulesForContentContainer(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { - const schema = designItemContext.schema as ComponentSchema; - /** - * 判断是否可以接收拖拽新增的子级控件 - */ function canAccepts(draggingContext: DraggingResolveContext): boolean { return true; } function getStyles() { - const component = schema; - if (component.componentType) { - return 'display:inherit;flex-direction:inherit;margin-bottom:10px'; - } return ''; } function checkCanMoveComponent() { return true; } + function checkCanDeleteComponent() { return true; } function hideNestedPaddingInDesginerView() { - return false; + return true; } - /** - * 获取属性配置 - */ + function getPropsConfig(componentId: string) { const componentProp = new ContentContainerProperty(componentId, designerHostService); const { schema } = designItemContext; diff --git a/packages/mobile-ui-vue/components/content-container/src/property-config/content-container.property-config.ts b/packages/mobile-ui-vue/components/content-container/src/property-config/content-container.property-config.ts index 6512f2c46593ec8903be317bb33cbd58d5d855fa..eff620c35116bd04cc8e152cd786cb584e2df997 100644 --- a/packages/mobile-ui-vue/components/content-container/src/property-config/content-container.property-config.ts +++ b/packages/mobile-ui-vue/components/content-container/src/property-config/content-container.property-config.ts @@ -1,15 +1,122 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { ContainerBaseProperty } from "@farris/mobile-ui-vue/common"; +import { flexBoxConverter } from "@farris/mobile-ui-vue/dynamic-resolver/src/converter"; + +export class ContentContainerProperty extends ContainerBaseProperty { -export class ContentContainerProperty extends BaseControlProperty { constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); } + public getPropertyConfig(propertyData: any) { // 基本信息 - this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData, { showTitle: true }); // 外观 this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + // 布局 + this.propertyConfig.categories['layout'] = this.getLayoutConfig(propertyData); + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); return this.propertyConfig; } + + protected getAppearanceConfig(propertyData: any) { + return { + title: "外观", + description: "Appearance", + properties: this.getAppearanceProperties(["style", "size", "padding", "margin"], propertyData), + }; + } + + protected getLayoutConfig(propertyData: any) { + return { + title: "布局", + description: "Layout", + properties: { + display: { + title: "布局方式", + type: "select", + description: "运行时组件布局方式", + refreshPanelAfterChanged: true, + editor: { + type: 'combo-list', + textField: 'text', + valueField: 'value', + editable: false, + data: [ + { value: 'flex', text: '弹性布局' }, + { value: 'block', text: '流式布局' }, + ], + }, + }, + flexBox: { + title: "弹性布局", + type: "cascade", + visible: propertyData.display === "flex", + isExpand: true, + properties: { + direction: { + title: "布局方向", + type: "select", + description: "运行时组件弹性布局方向", + editor: { + type: 'combo-list', + textField: 'text', + valueField: 'value', + editable: false, + data: [ + { value: 'row', text: '水平' }, + { value: 'column', text: '竖直' }, + ], + }, + $converter: flexBoxConverter, + }, + wrap: { + title: "是否允许换行", + type: "boolean", + description: "运行时组件内部子元素是否允许换行显示", + $converter: flexBoxConverter, + }, + justifyContent: { + title: "水平对齐方式", + type: "select", + description: "运行时组件布局方式", + editor: { + type: 'combo-list', + textField: 'text', + valueField: 'value', + editable: false, + data: [ + { value: "start", text: "起始位置" }, + { value: "center", text: "居中" }, + { value: "end", text: "末尾位置" }, + { value: "between", text: "均匀排列(贴合边框)" }, + { value: "around", text: "均匀排列" }, + ], + }, + $converter: flexBoxConverter, + }, + alignItems: { + title: "竖直对齐方式", + type: "select", + description: "运行时组件内部子元素纵轴对齐方式", + editor: { + type: 'combo-list', + textField: 'text', + valueField: 'value', + editable: false, + data: [ + { value: "start", text: "起始位置" }, + { value: "center", text: "居中" }, + { value: "end", text: "末尾位置" }, + ], + }, + $converter: flexBoxConverter, + }, + }, + }, + }, + }; + } + } diff --git a/packages/mobile-ui-vue/components/content-container/src/schema/content-container.schema.json b/packages/mobile-ui-vue/components/content-container/src/schema/content-container.schema.json index a082e40914e84b170abda951b6423e84ad8afb0e..cf8cf92d12a3ff80a41aa854e73df9e0a4e23145 100644 --- a/packages/mobile-ui-vue/components/content-container/src/schema/content-container.schema.json +++ b/packages/mobile-ui-vue/components/content-container/src/schema/content-container.schema.json @@ -14,6 +14,16 @@ "type": "string", "default": "content-container" }, + "title": { + "description": "标题", + "type": "string", + "default": "容器" + }, + "contents": { + "description": "子组件集合", + "type": "array", + "default": [] + }, "appearance": { "description": "外观", "type": "object", @@ -27,10 +37,35 @@ }, "default": {} }, - "contents": { - "description": "子组件集合", - "type": "array", - "default": [] + "padding": { + "description": "内边距", + "type": "object", + "default": {} + }, + "margin": { + "description": "外边距", + "type": "object", + "default": {} + }, + "size": { + "description": "尺寸", + "type": "object", + "default": {} + }, + "display": { + "description": "布局方式", + "type": "string", + "default": "flex" + }, + "flexBox": { + "description": "弹性布局", + "type": "object", + "default": {} + }, + "visible": { + "description": "是否可见", + "type": "boolean", + "default": true } }, "required": [ diff --git a/packages/mobile-ui-vue/components/date-picker/src/designer/date-picker.design.component.tsx b/packages/mobile-ui-vue/components/date-picker/src/designer/date-picker.design.component.tsx index d8c19fe65d689d0f13e0c1536a6c354ae20ebd87..6bf49f080e9dc0cdd996dbbdca9b5fbc51b8c630 100644 --- a/packages/mobile-ui-vue/components/date-picker/src/designer/date-picker.design.component.tsx +++ b/packages/mobile-ui-vue/components/date-picker/src/designer/date-picker.design.component.tsx @@ -1,54 +1,42 @@ - -/** - * 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, inject, onMounted, readonly, ref, SetupContext } from 'vue'; - -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; +import InputGroup from '@farris/mobile-ui-vue/input-group'; import { useDesignerRules } from './use-designer-rules'; -import { datePickerProps, DatePickerProps } from '../date-picker.props'; -import DatePickerInput from '../..'; +import { datePickerProps } from '../date-picker.props'; export default defineComponent({ - name: 'FmDatePickerInputDesign', - props: datePickerProps, - emits: [], - setup(props: DatePickerProps, context: SetupContext) { - const elementRef = ref(); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); - const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext,designerRulesComposition); + name: 'FmDatePickerInputDesign', + inheritAttrs: false, + props: extractProperties(datePickerProps, ['placeholder']), + setup(props, context) { + const elementRef = ref(); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + context.expose(componentInstance.value); - context.expose(componentInstance.value); + const inputProps = computed(() => ({ + ...props, + editable: false + })); - const inputProps = computed(() => ({ - ...props, - editable: false, - readonly: true, - modelValue:null - })); - - return () => { - return ( - - ); - }; - } + return () => ; + } }); diff --git a/packages/mobile-ui-vue/components/date-picker/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/date-picker/src/designer/use-designer-rules.ts index 1c41eedaf45b5ce1c4de944d885f9fe121ba4192..8abda334f14b5e9f8da55fc0cf438c8d1b84e7bb 100644 --- a/packages/mobile-ui-vue/components/date-picker/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/date-picker/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { DatePickerProperty } from "../property-config/date-picker.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { diff --git a/packages/mobile-ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts b/packages/mobile-ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts index 205e01d02ba792a76b1926c81da530b9bf7b0779..1ea66076a515b4e20972f5532dbe058505703663 100644 --- a/packages/mobile-ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts +++ b/packages/mobile-ui-vue/components/date-picker/src/property-config/date-picker.property-config.ts @@ -1,4 +1,4 @@ -import { InputBaseProperty } from '@farris/mobile-ui-vue/common/src/entity/input-base-property'; +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; import { DATE_FORMATS } from './date-format'; export class DatePickerProperty extends InputBaseProperty { diff --git a/packages/mobile-ui-vue/components/date-picker/src/schema/date-picker.schema.json b/packages/mobile-ui-vue/components/date-picker/src/schema/date-picker.schema.json index 6bbe0de09fcd86dd9905a6d0045a255b1af4c337..0174321e036b9c9b85295d6dcc839f1baac74faf 100644 --- a/packages/mobile-ui-vue/components/date-picker/src/schema/date-picker.schema.json +++ b/packages/mobile-ui-vue/components/date-picker/src/schema/date-picker.schema.json @@ -82,8 +82,7 @@ ], "ignore": [ "id", - "appearance", - "binding", - "visible" + "type", + "appearance" ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/date-picker/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/date-picker/src/schema/schema-mapper.ts index 70f3c0e7744a5425bfce8cf5f4f3e9b295d9073e..728769ef54e46801be6469e1fab2d96ab2d56948 100644 --- a/packages/mobile-ui-vue/components/date-picker/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/date-picker/src/schema/schema-mapper.ts @@ -3,5 +3,5 @@ import { MapperFunction, resolveAppearance } from '@farris/mobile-ui-vue/dynamic export const schemaMapper = new Map([ ['appearance', resolveAppearance], ['binding', 'modelValue'], - ['displayFormat','format'] + ['valueFormat','format'] ]); diff --git a/packages/mobile-ui-vue/components/date-time-picker/src/date-time-picker.props.ts b/packages/mobile-ui-vue/components/date-time-picker/src/date-time-picker.props.ts index c2a66c2a27857ad4a581e84a3114865e49d7b193..283d4a1f02b1c2d874791ef61779e68eb2ab7400 100644 --- a/packages/mobile-ui-vue/components/date-time-picker/src/date-time-picker.props.ts +++ b/packages/mobile-ui-vue/components/date-time-picker/src/date-time-picker.props.ts @@ -4,7 +4,7 @@ import { dateTimePickerPanelProps } from './date-time-picker-panel.props'; import { getPropsResolverGenerator } from '../../dynamic-resolver'; import { schemaResolver } from './schema/schema-resolver'; import { schemaMapper } from './schema/schema-mapper'; -import dateTimePickerSchema from './schema/datetime-picker.schema.json' +import dateTimePickerSchema from './schema/datetime-picker.schema.json'; export const DATA_TIME_PICKER_NAME = 'FmDateTimePicker'; diff --git a/packages/mobile-ui-vue/components/date-time-picker/src/designer/date-picker.design.component.tsx b/packages/mobile-ui-vue/components/date-time-picker/src/designer/date-picker.design.component.tsx index 76e3afe317645e7082604a0d6eb1dbd059c7c601..1915144983b54691726692c4072d7bfd4feaae2e 100644 --- a/packages/mobile-ui-vue/components/date-time-picker/src/designer/date-picker.design.component.tsx +++ b/packages/mobile-ui-vue/components/date-time-picker/src/designer/date-picker.design.component.tsx @@ -1,37 +1,42 @@ import { computed, defineComponent, inject, onMounted, ref } from 'vue'; - -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; +import InputGroup from '@farris/mobile-ui-vue/input-group'; import { useDesignerRules } from './use-designer-rules'; -import { ButtonEdit, buttonEditProps} from '@farris/mobile-ui-vue/button-edit'; +import { dateTimePickerProps } from '../date-time-picker.props'; export default defineComponent({ - name: 'FmDatePickerDesign', - props: buttonEditProps, - emits: [], - setup(props, context) { - const elementRef = ref(); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); - const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext,designerRulesComposition); + name: 'FmDatePickerDesign', + inheritAttrs: false, + props: extractProperties(dateTimePickerProps, ['placeholder']), + setup(props, context) { + const elementRef = ref(); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + context.expose(componentInstance.value); - context.expose(componentInstance.value); + const inputProps = computed(() => ({ + ...props, + editable: false + })); - const inputProps = computed(() => ({ - ...props, - editable: false, - readonly: true, - modelValue: '' - })); - - return () => { - return ( - - ); - }; - } + return () => ; + } }); diff --git a/packages/mobile-ui-vue/components/date-time-picker/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/date-time-picker/src/designer/use-designer-rules.ts index 6dafcd627e266f140333e17916e8a85786f45405..3219c6ed65d399c1d6ff7c73812ce690654aba5f 100644 --- a/packages/mobile-ui-vue/components/date-time-picker/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/date-time-picker/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { DateTimePickerProperty } from "../property-config/date-picker.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { diff --git a/packages/mobile-ui-vue/components/date-time-picker/src/property-config/date-picker.property-config.ts b/packages/mobile-ui-vue/components/date-time-picker/src/property-config/date-picker.property-config.ts index 990e7b8e3c4a157480b7ce889c6db5618557af5a..482d93fac08ee2edd0b22efcabcd73d1949d208a 100644 --- a/packages/mobile-ui-vue/components/date-time-picker/src/property-config/date-picker.property-config.ts +++ b/packages/mobile-ui-vue/components/date-time-picker/src/property-config/date-picker.property-config.ts @@ -1,4 +1,4 @@ -import { InputBaseProperty } from '@farris/mobile-ui-vue/common/src/entity/input-base-property'; +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; import { DATE_FORMATS } from './date-format'; export class DateTimePickerProperty extends InputBaseProperty { @@ -10,7 +10,7 @@ export class DateTimePickerProperty extends InputBaseProperty { const displayFormatOptions = this.getDateFormatOptions(propertyData?.editor); return this.getComponentConfig( propertyData, - {}, + { type: 'datetime-picker' }, { title: { description: '', diff --git a/packages/mobile-ui-vue/components/date-time-picker/src/schema/datetime-picker.schema.json b/packages/mobile-ui-vue/components/date-time-picker/src/schema/datetime-picker.schema.json index 73f07942acef1b63cab6cbd3e2f8e3783f4bf8cf..1b6328e7cd8b5380f3a3759b9021678be57291cf 100644 --- a/packages/mobile-ui-vue/components/date-time-picker/src/schema/datetime-picker.schema.json +++ b/packages/mobile-ui-vue/components/date-time-picker/src/schema/datetime-picker.schema.json @@ -1,88 +1,88 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://farris-design.gitee.io/date-picker.schema.json", - "title": "datetime-picker", - "description": "日期时间", - "type": "object", - "properties": { - "id": { - "description": "标识", - "type": "string" - }, - "type": { - "description": "控件类型", - "type": "string", - "default": "datetime-picker" - }, - "appearance": { - "description": "外观", - "type": "object", - "properties": { - "class": { - "type": "string" - }, - "style": { - "type": "string" - } - }, - "default": {} - }, - "binding": { - "description": "绑定", - "type": "object", - "default": {} - }, - "required": { - "description": "必填", - "type": "boolean", - "default": false - }, - "readonly": { - "description": "只读", - "type": "boolean", - "default": false - }, - "disabled": { - "description": "禁用", - "type": "boolean", - "default": false - }, - "placeholder": { - "description": "提示文本", - "type": "string" - }, - "title": { - "description": "标题", - "type": "string", - "default": "" - }, - "displayFormat": { - "description": "展示格式", - "type": "enum", - "default": "yyyy-MM-dd HH:mm:ss" - }, - "maxDate": { - "description": "最大日期", - "type": "string" - }, - "minDate": { - "description": "最小日期", - "type": "string" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/date-picker.schema.json", + "title": "datetime-picker", + "description": "日期时间", + "type": "object", + "properties": { + "id": { + "description": "标识", + "type": "string" + }, + "type": { + "description": "控件类型", + "type": "string", + "default": "datetime-picker" + }, + "appearance": { + "description": "外观", + "type": "object", + "properties": { + "class": { + "type": "string" }, - "onUpdate:modelValue": { - "description": "值更新事件", - "type": "string" + "style": { + "type": "string" } + }, + "default": {} + }, + "binding": { + "description": "绑定", + "type": "object", + "default": {} + }, + "required": { + "description": "必填", + "type": "boolean", + "default": false + }, + "readonly": { + "description": "只读", + "type": "boolean", + "default": false + }, + "disabled": { + "description": "禁用", + "type": "boolean", + "default": false + }, + "placeholder": { + "description": "提示文本", + "type": "string" + }, + "title": { + "description": "标题", + "type": "string", + "default": "" + }, + "displayFormat": { + "description": "展示格式", + "type": "enum", + "default": "yyyy-MM-dd HH:mm:ss" + }, + "maxDate": { + "description": "最大日期", + "type": "string" + }, + "minDate": { + "description": "最小日期", + "type": "string" }, - "events": [ - "onUpdate:modelValue" - ], - "required": [ - "type" - ], - "ignore": [ - "id", - "type", - "appearance" - ] + "onUpdate:modelValue": { + "description": "值更新事件", + "type": "string" + } + }, + "events": [ + "onUpdate:modelValue" + ], + "required": [ + "type" + ], + "ignore": [ + "id", + "appearance", + "visible" + ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/date-time-picker/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/date-time-picker/src/schema/schema-mapper.ts index a14506b5dd6337cb6648aab10116a419f8689ebb..35c0af70ce56b5bcd8eea569cd034621da6d74c8 100644 --- a/packages/mobile-ui-vue/components/date-time-picker/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/date-time-picker/src/schema/schema-mapper.ts @@ -3,6 +3,6 @@ import { MapperFunction, resolveAppearance } from '@farris/mobile-ui-vue/dynamic export const schemaMapper = new Map([ ['appearance', resolveAppearance], ['binding', 'modelValue'], - ['displayFormat','format'] + ['valueFormat','format'] ]); diff --git a/packages/mobile-ui-vue/components/designer-canvas/index.ts b/packages/mobile-ui-vue/components/designer-canvas/index.ts deleted file mode 100644 index 6ce6546f37b7992e7cd5fe27f3f9b221c3c3558b..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import FDesignerCanvasInstallless from './src/designer-canvas.component'; -import FDesignerItem from './src/components/designer-item.component'; -import { DgControl } from './src/composition/dg-control'; -import type { DesignerHostService, UseDesignerRules } from './src/composition/types'; -import { withInstall } from '../common'; - -export * from './src/composition/props/designer-canvas.props'; -export * from './src/composition/function/use-designer-component'; -export * from './src/composition/function/use-designer-inner-component'; -export * from './src/types'; -const FDesignerCanvas = withInstall(FDesignerCanvasInstallless); - -export { FDesignerCanvas, FDesignerItem, DgControl, UseDesignerRules, DesignerHostService }; -export default FDesignerCanvas; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-inner-item.component.tsx b/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-inner-item.component.tsx deleted file mode 100644 index 9225b195cbf6ee9e2a52ea21a851accdf8372f28..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-inner-item.component.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import { Ref, SetupContext, computed, defineComponent, inject, onMounted, provide, ref, watch, onBeforeUnmount, withModifiers } from 'vue'; -import { DesignerInnerItemPropsType, designerInnerItemProps } from '../composition/props/designer-inner-item.props'; -import { DraggingResolveContext, UseDragula } from '../composition/types'; -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from '../types'; -import { canvasChanged, setPositionOfButtonGroup } from '../composition/designer-canvas-changed'; -import { useDesignerInnerComponent } from '../composition/function/use-designer-inner-component'; - -const FDesignerInnerItem = defineComponent({ - name: 'FDesignerInnerItem', - props: designerInnerItemProps, - emits: ['selectionChange', 'addComponent', 'removeComponent'], - setup(props: DesignerInnerItemPropsType, context) { - const canMove = ref(props.canMove); - const canAdd = ref(props.canAdd); - const canDelete = ref(props.canDelete); - const canNested = ref(false); - const contentKey = ref(props.contentKey); - const childType = ref(props.childType); - const childLabel = ref(props.childLabel); - const schema = ref(props.modelValue); - const designComponentStyle = ref(''); - const designerItemElementRef = ref(); - const useDragulaComposition = inject('canvas-dragula'); - const componentInstance = ref() as Ref; - const parent = inject('design-item-context'); - const designItemContext = { designerItemElementRef, componentInstance, schema: schema.value, parent, setupContext: context as SetupContext }; - provide('design-item-context', designItemContext); - - const designerItemClass = computed(() => { - const classObject = { - 'farris-component': true, - // 受position-relative影响,整个容器的高度不能被撑起 - 'flex-fill': props.id === 'root-component', - 'position-relative': canMove.value || canDelete.value, - 'farris-nested': canNested.value, - 'can-move': canMove.value, - 'd-none': designerItemElementRef.value && (designerItemElementRef.value as HTMLElement).classList.contains('d-none') - } as Record; - return classObject; - }); - - const desginerItemStyle = computed(() => { - const styleObject = {} as Record; - if (designComponentStyle.value) { - designComponentStyle.value.split(';').reduce((result: Record, styleString: string) => { - const [styleKey, styleValue] = styleString.split(':'); - if (styleKey) { - result[styleKey] = styleValue; - } - return result; - }, styleObject); - } - return styleObject; - }); - - function onClickDeleteButton(payload: MouseEvent, schemaToRemove: ComponentSchema) { - if (parent && parent.schema[contentKey.value]) { - const indexToRemove = parent.schema[contentKey.value].findIndex( - (contentItem: ComponentSchema) => contentItem.id === schemaToRemove.id - ); - // 如果仍然存在子级,点击子级事件,展示属性面板 - if (indexToRemove > -1) { - const childrenSize = parent.schema[contentKey.value].length; - const nextSchema = parent.schema[contentKey.value][indexToRemove % childrenSize]; - const designerItem = parent.designerItemElementRef.value.querySelector( - `#${nextSchema.id}-design-item` - ); - parent.schema[contentKey.value].splice(indexToRemove, 1); - - // 此处删除子组件schame后,会自动选中第一个元素但属性面板不更新,暂时绕过此问题,使用emit removeComponent方式来解决 - canvasChanged.value++; - context.emit('removeComponent'); - context.emit('selectionChange'); - } - } - - } - - function onClickAddButton(payload: MouseEvent) { - if (componentInstance.value.addNewChildComponentSchema) { - const resolveContext = { - componentType: childType.value, - label: childLabel.value, - parentComponentInstance: componentInstance.value, - targetPosition: -1 - } as DraggingResolveContext; - const childComponentSchema = componentInstance.value.addNewChildComponentSchema(resolveContext); - schema.value[contentKey.value].push(childComponentSchema); - context.emit('addComponent'); - } - } - - function renderAddButton() { - return ( - canAdd.value && ( -
{ - onClickAddButton(payload); - }}> - -
- ) - ); - } - - function renderDeleteButton(componentSchema: ComponentSchema) { - return ( - canDelete.value && ( -
- onClickDeleteButton(payload as MouseEvent, componentSchema), - ['stop'])}> - -
- ) - ); - } - function renderMoveButton() { - return ( - canMove.value && ( -
- -
- ) - ); - } - - function renderIconPanel(componentSchema: ComponentSchema) { - return ( -
-
- {renderAddButton()} - {renderMoveButton()} - {renderDeleteButton(componentSchema)} -
-
- ); - } - - watch( - () => props.modelValue, - (value: any) => { - schema.value = value; - designItemContext.schema = value; - } - ); - - function updatePositionOfButtonGroup(e: Event | any) { - const targetEl = e.target as any; - setPositionOfButtonGroup(targetEl); - } - - function bindingScrollEvent() { - if (schema.value?.contents?.length && designerItemElementRef.value) { - designerItemElementRef.value.addEventListener('scroll', updatePositionOfButtonGroup); - } - } - - function createDefaultComponentInstance() { - const designerItemElement = designerItemElementRef.value as HTMLElement; - const innerComponentElementRef = ref(designerItemElement.children[1] as HTMLElement); - const innerComponentInstance = useDesignerInnerComponent(innerComponentElementRef, designItemContext); - return innerComponentInstance.value; - } - - onMounted(() => { - if (designerItemElementRef.value) { - const draggableContainer = designerItemElementRef.value.querySelector( - `[data-dragref='${schema.value.id}-container']` - ); - componentInstance.value = (draggableContainer && draggableContainer.componentInstance) ? - draggableContainer.componentInstance.value : createDefaultComponentInstance(); - - if (useDragulaComposition && draggableContainer) { - useDragulaComposition.attachComponents(draggableContainer, schema.value); - } - canNested.value = componentInstance.value.canNested !== undefined ? componentInstance.value.canNested : canNested.value; - canAdd.value = componentInstance.value.canAdd !== undefined ? componentInstance.value.canAdd : canAdd.value; - canDelete.value = componentInstance.value.canDelete !== undefined ? componentInstance.value.canDelete : canDelete.value; - canMove.value = componentInstance.value.canMove !== undefined ? componentInstance.value.canMove : canMove.value; - designComponentStyle.value = componentInstance.value.styles || ''; - if (designerItemElementRef.value) { - designerItemElementRef.value.componentInstance = componentInstance; - designerItemElementRef.value.designItemContext = designItemContext; - } - } - bindingScrollEvent(); - - canvasChanged.value++; - }); - - onBeforeUnmount(() => { - if (designerItemElementRef.value) { - designerItemElementRef.value.removeEventListener('scroll', updatePositionOfButtonGroup); - } - }); - - function onClickDesignerItem(payload: MouseEvent) { - - if (payload) { - payload.preventDefault(); - payload.stopPropagation(); - } - let draggabledesignerItemElementRef: any = designItemContext.designerItemElementRef; - const designerItemElement = designerItemElementRef.value as HTMLElement; - if (designerItemElement) { - const currentFocusedElements = document.getElementsByClassName('dgComponentFocused') as HTMLCollectionOf; - // 重复点击 - const duplicateClick = - currentFocusedElements && - currentFocusedElements.length === 1 && - currentFocusedElements[0] === designerItemElementRef.value; - if (!duplicateClick) { - Array.from(currentFocusedElements).forEach((element: HTMLElement) => element.classList.remove('dgComponentFocused')); - Array.from(document.getElementsByClassName('dgComponentSelected') as HTMLCollectionOf).forEach( - (element: HTMLElement) => element.classList.remove('dgComponentSelected') - ); - - designerItemElement.classList.add('dgComponentFocused'); - context.emit('selectionChange', schema.value.type, schema.value, props.componentId, componentInstance.value); - if (componentInstance.value.getDraggableDesignItemElement) { - draggabledesignerItemElementRef = componentInstance.value.getDraggableDesignItemElement(designItemContext); - if (draggabledesignerItemElementRef && draggabledesignerItemElementRef.value) { - draggabledesignerItemElementRef.value.classList.add('dgComponentSelected'); - - } - } - } - } - - updatePositionOfButtonGroup({ target: draggabledesignerItemElementRef?.value }); - } - - return () => { - return ( -
- {renderIconPanel(schema.value)} - {context.slots.default && context.slots.default()} -
- ); - }; - } -}); -export default FDesignerInnerItem; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-placeholder.component.tsx b/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-placeholder.component.tsx deleted file mode 100644 index 5dd90cb489ae5a2637354f7ce168bebeaffd8096..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-placeholder.component.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { SetupContext, computed, defineComponent } from 'vue'; -import { DesignerPlaceholderPropsType, designerPlaceholderProps } from '../composition/props/designer-placeholder.props'; - -export default defineComponent({ - name: 'FDesignerPlaceholder', - props: designerPlaceholderProps, - emits: [], - setup(props: DesignerPlaceholderPropsType) { - const designerPlaceholderClass = computed(() => { - const classObject = { - 'drag-and-drop-alert': true, - 'no-drag': true, - 'w-100': true - } as Record; - return classObject; - }); - - const designerPlaceholderStyle = computed(() => { - const styleObject = { - 'height': '60px', - 'display': 'flex', - 'justify-content': 'center', - 'align-items': 'center', - 'margin': 0, - 'padding': '.75rem 1.25rem', - 'border': '1px solid transparent', - 'border-radius': '3px', - 'color': '#315585', - 'background-color': '#dfedff', - 'border-color': '#d2e6ff', - } as Record; - return styleObject; - }); - - return () => ( - - ); - } -}); diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-template-item.component.tsx b/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-template-item.component.tsx deleted file mode 100644 index 9df082c1b83fdb4e00db0ecec29594280820e066..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/components/designer-template-item.component.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { computed, defineComponent, ref } from 'vue'; -import { DesignerItemPropsType, designerItemProps } from '../composition/props/designer-item.props'; -import { canvasChanged } from '../composition/designer-canvas-changed'; - -const FDesignerTemplateItem = defineComponent({ - name: 'FDesignerTemplateItem', - props: designerItemProps, - emits: ['selectionChange'], - setup(props: DesignerItemPropsType, context) { - const designerItemElementRef = ref(); - - const designerItemClass = computed(() => { - const classObject = { - 'farris-component': true - } as Record; - return classObject; - }); - - function onClickDesignerItem(payload: MouseEvent) { - - if (payload) { - payload.preventDefault(); - payload.stopPropagation(); - } - const designerItemElement = designerItemElementRef.value as HTMLElement; - if (designerItemElement) { - const currentSelectedElements = document.getElementsByClassName('dgComponentFocused') as HTMLCollectionOf; - - // 重复点击 - const duplicateClick = - currentSelectedElements && - currentSelectedElements.length === 1 && - currentSelectedElements[0] === designerItemElementRef.value; - if (!duplicateClick) { - Array.from(currentSelectedElements).forEach((element: HTMLElement) => element.classList.remove('dgComponentSelected')); - Array.from(currentSelectedElements).forEach((element: HTMLElement) => element.classList.remove('dgComponentFocused')); - - designerItemElement.classList.add('dgComponentFocused'); - } - } - canvasChanged.value++; - } - - return () => { - return ( -
- {context.slots.default && context.slots.default()} -
- ); - }; - } -}); -export default FDesignerTemplateItem; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/class/control.css b/packages/mobile-ui-vue/components/designer-canvas/src/composition/class/control.css deleted file mode 100644 index 9044cd61f76655fb7d387bb70f82e0a3516a5b7d..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/class/control.css +++ /dev/null @@ -1,30 +0,0 @@ -/** 筛选方案 **/ -.f-section-scheme { - background: #fff; - margin: 0.5625rem 0.5rem 0; -} - -/** 标签页tabs **/ -.farris-component-tabs .farris-tabs-content.f-utils-fill-flex-column .farris-component-tab-page:has(.farris-tab-page-active) { - display: flex !important; - overflow: hidden; - flex-shrink: 1; - flex-grow: 1; - flex-basis: 0; - flex-direction: column !important; -} - -/** 布局容器 ResponseLayout **/ -.response-layout { - border: dotted 2px #e8e8e8; - -} - -.response-layout .response-layout-item { - border-right: dotted 2px #e8e8e8; - -} - -.response-layout .response-layout-item:not(:last-child) { - padding-right: 8px !important; -} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/class/designer-canvas.css b/packages/mobile-ui-vue/components/designer-canvas/src/composition/class/designer-canvas.css deleted file mode 100644 index d92b16e7338c8c4ca9b74ef734ddb3feecb7054a..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/class/designer-canvas.css +++ /dev/null @@ -1,284 +0,0 @@ -.form-group-in-canvas .f-cmp-inputgroup .f-state-readonly .input-group-append{ - display: flex !important; -} -.component-btn-group { - flex-direction: row-reverse; - position: absolute; - z-index: 800; - background: #fff; - display: none; - top: -26px; - right: 0; -} - -.component-btn-group > div { - position: fixed; -} - -.farris-component { - /* border: dotted 2px transparent; */ -} - -.farris-component.farris-nested { - padding: 10px !important; - border: dotted 2px #e8e8e8; -} - -.farris-component.farris-nested.dgComponentFocused { - /* padding: 10px; */ - border: dotted 2px #388fff; -} - -.farris-component.can-move { - padding: 2px; -} - -.farris-component.dgComponentSelected > .component-btn-group { - display: flex; -} - -.component-btn-group .component-settings-button { - display: flex; - cursor: pointer; - float: right; - margin-left: 4px; - padding: 0; - font-size: 10px; - line-height: 1.2em; - border-radius: 2px 2px 0px 0px; - width: 24px !important; - height: 24px !important; - color: #fff !important; - background: #388fff !important; -} - -.component-btn-group .component-settings-button .f-icon { - font-size: 18px; - margin: 0 auto; - line-height: 20px; -} - -.component-btn-group .component-settings-button .f-icon.f-icon-yxs_move { - cursor: move; -} - -.farris-component-content-container .drag-container { - display: inherit; - flex-direction: inherit; - flex-shrink: 1; - flex-grow: 1; - flex-basis: 0%; - flex-wrap: inherit; - justify-content: inherit; - align-items: inherit; - width: 100%; - overflow: inherit; - height: inherit; -} - -/* gu-mirror被添加到镜像中 */ -.gu-mirror { - position: fixed !important; - margin: 0 !important; - z-index: 9999 !important; - opacity: 0.8; - -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; - filter: alpha(opacity=80); -} - -.gu-mirror.undroppable .component-settings-button .f-icon-yxs_move { - cursor: no-drop; -} - -.component-btn-group .component-settings-button .f-icon.f-icon-yxs_delete { - color: #fff !important; - background: #388fff !important; -} - -.gu-hide { - display: none !important; -} - -/** 拖拽时镜像元素的父级元素 */ -.gu-unselectable { - -webkit-user-select: none !important; - -moz-user-select: none !important; - -ms-user-select: none !important; - user-select: none !important; -} - -/* 当拖动源元素的镜像时,gu-transit被添加到源元素中。只是增加了透明度。 */ -.gu-transit { - opacity: 0.2; - -ms-filter: 'progid:DXImageTransform.Microsoft.Alpha(Opacity=20)'; - filter: alpha(opacity=20); -} - -/* 拖拽经过某区域时,为区域增加底色 */ -.drag-over:not(.no-drop) { - background-color: #f3f8ff !important; -} - -/** 拖拽区域内的元素不显示按钮区域 */ -.gu-unselectable .farris-component.dgComponentSelected > .component-btn-group { - display: none; -} - -/** 拖拽区域内的镜像元素显示按钮区域 */ -.gu-unselectable .gu-mirror.farris-component.dgComponentSelected > .component-btn-group { - display: flex; -} - -/** 拖拽过程中的源元素不显示按钮区域 */ -.gu-transit.farris-component.dgComponentSelected > .component-btn-group { - display: none; -} - -/* 镜像元素为fixed定位 */ -.gu-mirror.farris-component.dgComponentSelected { - position: fixed !important; -} - -/* 镜像元素的按钮区域定位 */ -.gu-mirror.farris-component.dgComponentSelected > .component-btn-group > div { - position: relative; - top: 0 !important; - left: 0 !important; -} - -/** 镜像元素的按钮区域设置宽度。是为了适配控件本身宽度比较小,但是操作按钮比较多时,按钮被换行的问题 */ -.gu-mirror.farris-component.dgComponentSelected > .component-btn-group { - width: max-content; -} - -.dgComponentFocused { - border-top: 2px dotted #388fff !important; - border-left: 2px dotted #388fff !important; - border-right: 2px dotted #388fff !important; - border-bottom: 2px dotted #388fff !important; -} - -.dgComponentSelected { - border-top: 2px solid #388fff !important; - border-left: 2px solid #388fff !important; - border-right: 2px solid #388fff !important; - border-bottom: 2px solid #388fff !important; -} - -.editorDiv { - background-color: #dbe6f7; - overflow-x: auto; -} - -.editorPanel { - position: relative; - height: 100%; - min-width: 900px; -} - -/** 卡片区块 **/ -.editorDiv .f-struct-like-card { - display: block; -} - -/** 解决farristab 开启内容填充后切换标签页导致内部列表不显示的问题**/ -.editorDiv .farris-tabs.f-tabs-content-fill .f-struct-is-subgrid .f-grid-is-sub { - width: 100%; -} - -/** 解决在模态框中弹出右键菜单 菜单不显示的问题**/ -.ide-framework .cdk-overlay-container { - z-index: 1100; -} -.editorDiv .f-struct-wrapper { - margin-bottom: 15px; -} - -.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; -} - -/** 解决带导航的列表和带导航的卡片模板中,不显示右侧区域的问题 */ -.editorDiv .f-page-navigate .f-page { - 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; -} - -/** 解决在弹窗中使用smoothDnd时不显示镜像元素的问题 */ -.smooth-dnd-ghost { - z-index: 1100 !important; -} - -/** 解决零代码设计器中OA类卡片表单滚动条位置问题 */ -.editorDiv .farris-oa-page.f-page-card .f-page-main { - display: block !important; - overflow: auto !important; -} - -/** 解决带筛选方案的列表表单中,选中表格组件后没有上边线的问题 */ -.editorDiv .f-page-has-scheme .f-page-main.farris-component { - margin-top: 0; -} - -/** 解决表单设计器中顶层工具栏的下拉面板被属性面板(z-index:850)遮挡的问题 */ -.dropdown-menu.show { - z-index: 860; -} - -/** 解决拟物风下从从表区域的下拉箭头错位的问题 */ -.f-struct-subsub-wrapper { - padding-top: 0 !important; -} - -/** 新拖拽 */ -.gu-mirror.undroppable { - cursor: no-drop; -} - -.gu-insertion { - position: fixed; - z-index: 840 !important; - pointer-events: none !important; - background-color: #388fff; - height: 4px; - overflow: hidden; -} - -/* 在designer canvas中还原嵌套页面样式,解决desinger和canvas各自f-page样式冲突 */ -.editorPanel .f-page::before { - display: initial !important; -} - -.editorPanel .f-page .f-page-main { - margin-right: 0.5rem !important; - margin-left: 0.5rem !important; - margin-bottom: 0.5rem !important; -} - -.editorPanel .f-page.f-page-is-listnav .f-page-main { - margin-top: 0.5rem !important; -} - -.editorPanel .f-page .f-page-main .f-page-main { - margin: 0 !important; -} - -.editorPanel .f-page.f-page-has-scheme .f-page-header{ - margin: 0 .5rem; -} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/designer-canvas-changed.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/designer-canvas-changed.ts deleted file mode 100644 index 11ada793acc2a6cb1f85d72862c14502e86f102e..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/designer-canvas-changed.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { ref } from "vue"; - -/** 用于响应画布发生变更 */ -export const canvasChanged = ref(0); - -/** - * 判断DOM 是否在可视区域内 - * @param el 元素 - * @param containerEl 容器 - */ -function isElementInViewport(el: HTMLElement, containerEl: HTMLElement) { - const container = containerEl.getBoundingClientRect(); - const box = el.getBoundingClientRect(); - const top = box.top >= container.top; - const bottom = box.top <= container.bottom; - return (top && bottom); -} - -function setPositionForSelectedElement(selectElement: HTMLElement) { - const toolbar = selectElement.querySelector('.component-btn-group') as HTMLElement; - if (!toolbar) { - return; - } - toolbar.style.display = ''; - const toolbarRect = toolbar.getBoundingClientRect(); - const divPanel = toolbar.querySelector('div') as HTMLElement; - if (divPanel) { - const divPanelRect = divPanel.getBoundingClientRect(); - divPanel.style.top = toolbarRect.top + 'px'; - - // 若操作按钮的最左边比画布最左边还要靠左,那么操作按钮就以控件的最左边为界 - let left = toolbarRect.left - divPanelRect.width; - const editorDiv = document.querySelector('.editorDiv'); - if (editorDiv) { - const editorDivRect = editorDiv.getBoundingClientRect(); - if (left < editorDivRect.left) { - const elementRect = selectElement.getBoundingClientRect(); - ({ left } = elementRect); - } - } - divPanel.style.left = left + 'px'; - } -} - -/** - * 定位画布中已选控件的操作按钮的位置 - * @param canvasElement 画布父容器 - */ -export function setPositionOfButtonGroup(canvasElement: HTMLElement) { - if (!canvasElement) { - return; - } - let selectElement: HTMLElement; - if (canvasElement.className.includes('dgComponentSelected')) { - selectElement = canvasElement; - } else { - selectElement = canvasElement.querySelector('.dgComponentSelected') as HTMLElement; - } - if (!selectElement) { - return; - } - - const selectDomRect = selectElement.getBoundingClientRect(); - if (selectDomRect.width === 0 && selectDomRect.height === 0) { - return; - } - const toolbar = selectElement.querySelector('.component-btn-group') as HTMLElement; - if (toolbar) { - - const isInView = isElementInViewport(selectElement, canvasElement); - if (!isInView) { - toolbar.style.display = 'none'; - return; - } - // 计算位置 - setPositionForSelectedElement(selectElement); - } -} - - -/** - * 定位页面中选中控件的操作按钮位置。 - * 场景:控件内部点击收折或者切换显示内容后,需要重新计算页面中下方选中控件的按钮位置。 - * 例如点击section控件的收折图标后,需要重新计算section下方已选控件的操作按钮位置 - */ -export function setPositionOfSelectedComponentBtnGroup(canvasElement: HTMLElement) { - 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) { - setPositionForSelectedElement(selectElement); - } - } -} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/entity/builder-element.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/entity/builder-element.ts deleted file mode 100644 index b071db8858797c51e878802f1839262cb96a72ec..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/entity/builder-element.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * 设计器DOM元素结构 - */ -export interface BuilderHTMLElement extends Element{ - /** 记录各子元素对应的控件schema json的集合,用于container类dom节点 */ - childrenContents?: any[]; - - /** 记录element对应的component实例,用于单个component节点 */ - componentInstance?: any; - - component?: any; -}; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-designer-inner-component.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-designer-inner-component.ts deleted file mode 100644 index ac207a9f5114b3898a679a203cca03109f877b70..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-designer-inner-component.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { inject, Ref, ref } from "vue"; -import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext, UseDesignerRules } from "../types"; -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext } from "../../types"; -import { getSchemaByTypeForDesigner } from '../../../../dynamic-resolver/src/resolver/schema/schema-resolver-design'; - -export function useDesignerInnerComponent( - elementRef: Ref, - designItemContext: DesignerItemContext, - designerRules?: UseDesignerRules -): Ref { - const styles = (designerRules && designerRules.getStyles && designerRules.getStyles()) || ''; - const componentInstance = ref(); - /** - * 校验组件是否支持移动 - */ - function checkCanMoveComponent(): boolean { - if (designerRules && designerRules.checkCanMoveComponent) { - return designerRules.checkCanMoveComponent(); - } - return true; - } - - /** - * 校验组件是否支持选中父级 - */ - function checkCanSelectParentComponent(): boolean { - return false; - } - - /** - * 校验组件是否支持删除 - */ - function checkCanDeleteComponent() { - if (designerRules && designerRules.checkCanDeleteComponent) { - return designerRules.checkCanDeleteComponent(); - } - return true; - } - - /** - * 校验组件是否支持添加子元素 - */ - function checkCanAddComponent() { - if (designerRules && designerRules.checkCanAddComponent) { - return designerRules.checkCanAddComponent(); - } - return true; - } - - /** - * 判断在可视化区域中是否隐藏容器间距和线条 - */ - function hideNestedPaddingInDesginerView() { - return true; - } - - /** - * 获取组件在表单DOM中所属的Component的实例 - * @param componentInstance 组件实例 - */ - function getBelongedComponentInstance(componentInstance?: Ref): DesignerComponentInstance | null { - if (!componentInstance || !componentInstance.value) { - return null; - } - const parent = ref(componentInstance?.value.parent) as Ref; - const grandParent = getBelongedComponentInstance(parent); - if (grandParent) { - return grandParent; - } - return null; - } - - function getDraggableDesignItemElement(context: DesignerItemContext = designItemContext): Ref | null { - const { componentInstance, designerItemElementRef } = context; - if (!componentInstance || !componentInstance.value) { - return null; - } - if (componentInstance.value.canMove || componentInstance.value.canAdd || componentInstance.value.canDelete) { - return designerItemElementRef; - } - return getDraggableDesignItemElement(context.parent); - } - - /** - * 判断是否可以接收拖拽新增的子级控件 - * @param data 新控件的类型、所属分类 - * @returns boolean - */ - function canAccepts(draggingContext: DraggingResolveContext) { - return !!designerRules && designerRules.canAccepts(draggingContext); - } - - function getDraggingDisplayText() { - return designItemContext?.schema.label || designItemContext?.schema.title || designItemContext?.schema.name; - } - - /** - * 控件可以拖拽到的最外层容器,用于限制控件向外层容器拖拽的范围。不写则不限制 - */ - function getDragScopeElement(): HTMLElement | undefined { - return undefined; - } - - /** - * 移动控件后事件:在可视化设计器中,将现有的控件移动到容器中 - * @param element 移动的源DOM结构 - */ - function onAcceptMovedChildElement(element: DesignerHTMLElement, sourceContainer?: DesignerHTMLElement) { - if (!element || !sourceContainer) { - return; - } - if (designerRules?.onAcceptMovedChildElement) { - designerRules.onAcceptMovedChildElement(element, sourceContainer); - } - } - - function addNewChildComponentSchema(resolveContext: DraggingResolveContext) { - const { componentType } = resolveContext; - const designerHostServer = inject('designer-host-service') as DesignerHostService; - let componentSchema = getSchemaByTypeForDesigner(componentType, resolveContext, designerHostServer) as ComponentSchema; - if (designerRules && designerRules.onResolveNewComponentSchema) { - componentSchema = designerRules.onResolveNewComponentSchema(resolveContext, componentSchema); - } - - const typePrefix = componentType.toLowerCase().replace(/-/g, '_'); - if (componentSchema && !componentSchema.id && componentSchema.type === componentType) { - componentSchema.id = `${typePrefix}_${Math.random().toString().slice(2, 6)}`; - } - return componentSchema; - } - - /** - * 移动内部控件后事件:在可视化设计器中,将现有的控件移动到容器中 - * @param element 移动的源DOM结构 - */ - function onChildElementMovedOut(element: HTMLElement) { - - } - - /** 属性面板属性 */ - function getPropConfig(...args) { - if (designerRules && designerRules.getPropsConfig) { - return designerRules.getPropsConfig(...args); - } - return []; - } - function onRemoveComponent() { - - } - /** - * 控件属性变更后事件 - */ - function onPropertyChanged(event: any) { - if (designerRules && designerRules.onPropertyChanged) { - return designerRules.onPropertyChanged(event); - } - } - componentInstance.value = { - canMove: checkCanMoveComponent(), - canSelectParent: checkCanSelectParentComponent(), - canAdd: checkCanAddComponent(), - canDelete: checkCanDeleteComponent(), - canNested: !hideNestedPaddingInDesginerView(), - contents: [], - elementRef, - parent: designItemContext.parent?.componentInstance, - schema: designItemContext.schema, - styles, - canAccepts, - getBelongedComponentInstance, - getDraggableDesignItemElement, - getDraggingDisplayText, - getPropConfig, - getDragScopeElement, - onAcceptMovedChildElement, - onChildElementMovedOut, - addNewChildComponentSchema, - onRemoveComponent, - triggerBelongedComponentToMoveWhenMoved: !!designerRules && designerRules.triggerBelongedComponentToMoveWhenMoved || ref(false), - triggerBelongedComponentToDeleteWhenDeleted: !!designerRules && designerRules.triggerBelongedComponentToDeleteWhenDeleted || ref(false), - onPropertyChanged - } as DesignerComponentInstance; - - return componentInstance as Ref; - -} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-dragula.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-dragula.ts deleted file mode 100644 index 1b8b01052bc27ae976cbb2ac0d1ab8ed2e1b9c21..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/function/use-dragula.ts +++ /dev/null @@ -1,358 +0,0 @@ -import dragula from '@farris/designer-dragula'; -import { DesignerHostService, DesignerHTMLElement, DraggingResolveContext, UseDragula } from '../types'; -import { findIndex } from 'lodash-es'; -import { ref } from 'vue'; -import { canvasChanged } from '../designer-canvas-changed'; -import { ComponentSchema } from '../../types'; -import { dragResolveService } from './drag-resolve'; - -export function useDragula(designerHostService: DesignerHostService): UseDragula { - - let dragulaInstance: any; - - /** - * 判断是否可以接收拖拽的新控件 - * @param el 拖拽的新控件元素 - * @param target 目标位置 - * @returns boolean - */ - function checkCanAcceptDrops( - element: DesignerHTMLElement, - target: DesignerHTMLElement, - sourceContainer: DesignerHTMLElement - ): boolean { - if (!!element.contains(target) || target.classList.contains('no-drop')) { - return false; - } - const result = true; - if (element.componentInstance && element.componentInstance.value.getDragScopeElement) { - const dragScopEle = element.componentInstance.value.getDragScopeElement(); - if (dragScopEle) { - if (!dragScopEle.contains(target)) { - return false; - } - } - } - if (target.componentInstance && target.componentInstance.value.canAccepts) { - const dragResolveUtil = dragResolveService(designerHostService); - const draggingContext = dragResolveUtil.getComponentResolveContext(element, sourceContainer, target); - - return target.componentInstance.value.canAccepts(draggingContext); - } - return result; - } - - /** - * 判断DOM 是否在可视区域内 - * @param el 元素 - * @param containerEl 容器 - */ - function isElementInViewport(element: HTMLElement, sourceContainer: HTMLElement) { - const container = sourceContainer.getBoundingClientRect(); - const box = element.getBoundingClientRect(); - const top = box.top >= container.top; - const bottom = box.top < container.bottom; - return (top && bottom); - } - - /** - * 拖拽过程若中产生了页面的上下滚动,需要将已选控件的操作按钮上下移动相等的距离。 - * @param formElement 滚动父容器 - * @param scrollDirection 滚动方向 - * @param scrollHeight 滚动距离 - */ - function scrollInDragging(formElement: HTMLElement, scrollHeight: number) { - const selectedDom = formElement.querySelector('.dgComponentSelected') as HTMLElement; - if (!selectedDom || scrollHeight === 0) { - return; - } - if (isElementInViewport(selectedDom, formElement)) { - const toolbar = selectedDom.querySelector('.component-btn-group'); - if (toolbar) { - const divPanel = toolbar.querySelector('div'); - if (divPanel && divPanel.style.top) { - const top = Number.parseFloat(divPanel.style.top); - divPanel.style.top = (top - scrollHeight) + 'px'; - } - } - } - } - - /** - * 将新控件json添加到新容器schema json中 - * @param target 目标容器元素 - * @param sourceControlSchema 新控件的JSON schema结构 - * @param sibling 目标位置的下一个同级元素 - */ - function addNewControlToTarget( - target: DesignerHTMLElement, - sourceControlSchema: ComponentSchema | null, - sibling: DesignerHTMLElement - ): number { - let index = -1; - if (!sourceControlSchema) { - return -1; - } - if (target.componentInstance.value.contents) { - if (sibling && sibling.componentInstance) { - if (!sibling.getAttribute('data-noattach')) { - // 定位目标位置 - const siblingComponentSchema = sibling.componentInstance.value.schema; - let locatePredicate: any = { id: siblingComponentSchema.id }; - if (siblingComponentSchema.type === 'component') { - locatePredicate = { component: siblingComponentSchema.id }; - } - - index = findIndex(target.componentInstance.value.contents, locatePredicate); - index = (index === -1) ? 0 : index; - } else { - index = Number(sibling.getAttribute('data-position')); - } - if (index !== -1) { - target.componentInstance.value.contents.splice(index, 0, sourceControlSchema); - } - } else { - target.componentInstance.value.contents.push(sourceControlSchema); - } - } - return index; - } - - /** - * 从控件工具箱中拖拽新建控件 - * @param element 拖拽的元素 - * @param target 目标容器元素 - * @param source 原容器元素 - * @param sibling 目标位置的下一个同级元素 - */ - function createControlFromOutside( - element: DesignerHTMLElement, - target: DesignerHTMLElement, - source: DesignerHTMLElement, - sibling: DesignerHTMLElement - ) { - const dragResolveUtil = dragResolveService(designerHostService); - dragResolveUtil.resolveComponentCreationContextByDrop(element, source, target).then(componentResolveContext => { - if (!componentResolveContext) { - return; - } - const sourceControlSchema = componentResolveContext.componentSchema; - if (sourceControlSchema) { - addNewControlToTarget(target, sourceControlSchema, sibling); - } - }); - // 移除拷贝生成的源DOM - if (target.contains(element)) { - target.removeChild(element); - } - } - /** - * 在现有的表单中拖拽移动控件位置 - * @param element 拖拽的元素 - * @param target 目标容器元素 - * @param source 源容器元素 - * @param sibling 目标位置的下一个同级元素 - */ - function dragBetweenCurrentForm( - element: DesignerHTMLElement, - target: DesignerHTMLElement, - source: DesignerHTMLElement, - sibling: DesignerHTMLElement - ) { - let sourceControlSchema; - let index = -1; - // Form、DataGrid等控件在拖拽时,需要连同所属Component一起拖拽。 - if (element.componentInstance && element.componentInstance.value.triggerBelongedComponentToMoveWhenMoved) { - const cmpInstance = element.componentInstance.value.getBelongedComponentInstance(element.componentInstance); - if (cmpInstance) { - // 将拖拽元素替换为所属Component - element = ref(cmpInstance.elementRef).value.parentElement as DesignerHTMLElement; - // 将源容器元素替换为所属Component的父级元素 - source = element.parentElement as DesignerHTMLElement; - } - - } - const elementComponentSchema = element.componentInstance && element.componentInstance.value.schema; - - let locatePredicate: any = { id: elementComponentSchema && elementComponentSchema.id }; - if (elementComponentSchema.type === 'component') { - locatePredicate = { component: elementComponentSchema && elementComponentSchema.id }; - } - - index = findIndex(source.componentInstance.value.contents, locatePredicate); - - // 从源容器schema json中移除 - if (index !== -1 && source.componentInstance.value.contents) { - sourceControlSchema = source.componentInstance.value.contents.splice(index, 1); - sourceControlSchema = sourceControlSchema[0]; - } - - addNewControlToTarget(target, sourceControlSchema as ComponentSchema, sibling); - - // 源容器的控件被移除掉 - if (source.componentInstance && source.componentInstance.value.onChildElementMovedOut) { - source.componentInstance.value.onChildElementMovedOut(element); - } - - // 目标容器接收新控件 - if (target.componentInstance && target.componentInstance.value.onAcceptMovedChildElement) { - target.componentInstance.value.onAcceptMovedChildElement(element, source); - } - } - - /** - * 拖拽结束 - * @param element 拖拽的元素 - * @param target 目标容器元素 - * @param source 原容器元素 - * @param sibling 目标位置的下一个同级元素 - */ - function onDrop(element: DesignerHTMLElement, target: DesignerHTMLElement, source: DesignerHTMLElement, sibling: DesignerHTMLElement) { - if (!target) { - return; - } - if (element.contains(target)) { - return; - } - const sourceType = element.getAttribute('data-sourceType'); - - switch (sourceType) { - case 'control': case 'field': case 'entity': { - createControlFromOutside(element, target, source, sibling); - break; - } - default: { - if (source.componentInstance.value.contents) { - dragBetweenCurrentForm(element, target, source, sibling); - } else { - // 移除拷贝生成的源DOM - if (target.contains(element)) { - target.removeChild(element); - } - } - } - } - canvasChanged.value++; - - } - - function initializeDragula(containerElement: DesignerHTMLElement) { - if (dragulaInstance) { - dragulaInstance.destroy(); - } - - if (!dragula) { - return; - } - - dragulaInstance = dragula([], { - // 镜像容器 - mirrorContainer: containerElement, - direction: 'mixed', - revertOnSpill: true, - // 判断是否可移动 - moves(element: DesignerHTMLElement, container: DesignerHTMLElement, handle: DesignerHTMLElement): boolean { - let moves = true; - - // 包含no-drag样式的元素不允许拖动 - if (element.classList.contains('no-drag')) { - moves = false; - } - // 为防止误操作,可视化区域的控件只能通过移动图标来拖拽 - if (element.componentInstance) { - moves = !!handle.getAttribute('data-dragging-icon'); - } - return moves; - }, - // 判断是否可拷贝 - copy(element: HTMLElement): boolean { - // 工具箱里的div需要配置drag-copy - return element.classList.contains('drag-copy'); - }, - // 获取镜像元素的文本内容 - getMirrorText(element: DesignerHTMLElement): string { - if (element.componentInstance && element.componentInstance.value.getDraggingDisplayText) { - return element.componentInstance.value.getDraggingDisplayText(); - } - return element.innerText || '控件'; - }, - // 判断目标区域是否可接收拖拽的控件 - accepts(element: DesignerHTMLElement, target: DesignerHTMLElement, source: DesignerHTMLElement): boolean { - const canAccept = checkCanAcceptDrops(element, target, source); - const guMirrotElement = containerElement.lastElementChild as Element; - if (canAccept) { - guMirrotElement.className = guMirrotElement.className.replace('undroppable', ''); - } else if (!guMirrotElement.className.includes('undroppable')) { - guMirrotElement.className += ' undroppable'; - } - return canAccept; - } - }).on('over', (el: DesignerHTMLElement, container: DesignerHTMLElement) => { - container.className += ' drag-over'; - }).on('out', (el: DesignerHTMLElement, container: DesignerHTMLElement) => { - container.className = container.className.replace('drag-over', '').replace(' ', ''); - }).on('drop', ( - element: DesignerHTMLElement, target: DesignerHTMLElement, source: DesignerHTMLElement, sibling: DesignerHTMLElement - ) => { - onDrop(element, target, source, sibling); - }).on('dragend', (element: HTMLElement, scrollHeight: number) => { - scrollInDragging(element, scrollHeight); - }); - } - - /** - * 子组件JSON结构和当前组件的实例添加到DOM中并注册拖拽容器。节点class= 'builder-components...' - * @param element dom元素 - * @param childrenComponents 容器内的子组件实例集合 - * @param childrenContents 子组件JSON schema集合 - * @param component 容器组件实例 - * @returns 容器类组件的子组件集合 - */ - function attachComponents(element: HTMLElement, component: Record) { - - // don't attach if no element was found or component doesn't participate in drag'n'drop. - if (!element) { - return; - } - if (component.noDragDrop) { - return element; - } - // 获取容器中的子组件集合节点 - const containerElement: HTMLElement = element.querySelector(`[data-dragref='${component.id}-container']`) || element; - - // 将容器添加到拖拽列表中,dragula控件会监听容器中元素的拖动事件 - if (dragulaInstance && containerElement) { - // containerElement 为页面中的容器节点的builder-components层级 - dragulaInstance.containers.push(containerElement); - } - } - - /** - * 将工具箱各容器添加到dragula的拖拽列表中 - */ - function attachToolbox() { - if (!dragulaInstance) { - return; - } - const controlPanels = document.getElementsByClassName('controlCategory'); - if (!controlPanels) { - return; - } - - dragulaInstance.containers = dragulaInstance.containers.filter( - (element: HTMLElement) => !element.className.includes('controlCategory') - ); - - Array.from(controlPanels).forEach((panelElement) => { - dragulaInstance.containers.push(panelElement); - }); - - } - - function getDragulaInstance() { - return dragulaInstance; - } - - return { attachComponents, initializeDragula,getDragulaInstance }; - -} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-canvas.props.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-canvas.props.ts deleted file mode 100644 index e07d95a7ac998de22c81ccbd545eb0443f2ada7e..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-canvas.props.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ExtractPropTypes } from 'vue'; - -export const designerCanvasProps = { - /** - * 组件值 - */ - modelValue: { type: Object, default: {} }, - componentId: { type: String, default: '' }, - components: { type: Array, default: null }, - -} as Record; - -export type DesignerCanvasPropsType = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-inner-item.props.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-inner-item.props.ts deleted file mode 100644 index 9c449ec8854f48ad5dc61f0a6dbfcb9ec2a0dfd2..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-inner-item.props.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ExtractPropTypes } from "vue"; - -export const designerInnerItemProps = { - id: { type: String, default: '' }, - componentId: { type: String, default: '' }, - canAdd: { type: Boolean, default: false }, - canDelete: { type: Boolean, default: false }, - canMove: { type: Boolean, default: false }, - contentKey: { type: String, default: 'contents' }, - childLabel: { type: String, default: '' }, - childType: { type: String, default: '' }, - /** - * 组件值 - */ - modelValue: { type: Object }, -} as Record; - -export type DesignerInnerItemPropsType = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-item.props.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-item.props.ts deleted file mode 100644 index a674600f00b8102af205824a112d4e6dc8db5238..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-item.props.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ExtractPropTypes } from "vue"; - -export const designerItemProps = { - id: { type: String, default: '' }, - componentId: { type: String, default: '' }, - type: { type: String, default: '' }, - canDelete: { type: Boolean, default: true }, - canMove: { type: Boolean, default: true }, - canSelectParent: { type: Boolean, default: true }, - customButtons: { type: Array, default: [] }, - customClass: { type: String, default: '' }, - customStyle: { type: String, default: '' }, - /** - * 组件值 - */ - modelValue: { type: Object }, - ignore: { type: Boolean, default: false } -} as Record; - -export type DesignerItemPropsType = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-placeholder.props.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-placeholder.props.ts deleted file mode 100644 index 3bde9b168af28b319722ce0dc732bcf312a9455b..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/props/designer-placeholder.props.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ExtractPropTypes } from "vue"; - -export const designerPlaceholderProps = { - id: { type: String } -}; - -export type DesignerPlaceholderPropsType = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/drag-drop-rules.schema.json b/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/drag-drop-rules.schema.json deleted file mode 100644 index f524134d361ffe176334dc26ae2eb8e6a665c0cf..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/drag-drop-rules.schema.json +++ /dev/null @@ -1,646 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://farris-design.gitee.io/dragging-rules.schema.json", - "title": "Dragging Rules", - "description": "The rules of designer canvas", - "type": "object", - "properties": { - "f-page": { - "description": "The root class of page designed by farris.", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-page-header": { - "description": "The class of page header designed by farris.", - "type": "string" - }, - "f-page-main": { - "description": "The class of page body designed by farris.", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-struct-like-card": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-struct-form": { - "description": "The class of form component which has a hierarchical structure of component -> section -> respnse-form.", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-section-form": { - "description": "The class of section which has wrapped a reponse-form component.", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-form-layout": { - "description": "The class of reponse form", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": true - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": false - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": false - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - }, - "f-struct-wrapper": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-section-tabs": { - "description": "The class of section which has wrapped sub-grid tabs.", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-tabs-in-card": { - "description": "The class of tabs which contains sub-grid.", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-struct-data-grid-in-card": { - "description": "The class of sub-grid component", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-grid-is-sub": { - "description": "The class of sub-grid", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": true - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": false - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": false - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - }, - "f-section-in-main": { - "description": "The class of section which has wrapped a reponse-form component.", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": true - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": false - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": false - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": true - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "object", - "const": { - "allOf": [ - { - "sibling": 0, - "parent": { - "f-page-main": true - } - } - ] - } - } - } - } - } - }, - "f-struct-data-grid": { - "description": "", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "object", - "const": true - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - }, - "f-page-main-content": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-page-content-nav": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-struct-data-grid-in-nav": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-page-content-nav-extend": { - "description": "", - "type": "object" - }, - "f-section-in-nav": { - "description": "", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": false - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": true - }, - "fixed": { - "type": "boolean", - "const": false - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - } - } - }, - "f-page-content-main": { - "description": "", - "type": "object", - "properties": {} - } - } - } - } - }, - "f-page-content": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-page-content-nav": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-list-nav": { - "description": "", - "type": "object" - }, - "f-list-nav-left": { - "description": "", - "type": "object" - }, - "f-struct-data-grid-in-nav": { - "description": "", - "type": "object", - "properties": { - "contents": { - "type": "object", - "properties": { - "f-section-form": { - "description": "", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - }, - "f-section-grid": { - "description": "", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - }, - "f-page-content-main": { - "description": "", - "type": "object", - "properties": { - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": false - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "object", - "const": { - "anyOf": [ - { - "children": 0 - }, - { - "children": { - "length": { - "not": 1 - }, - "f-struct-like-card": true - } - }, - { - "children": { - "scroll-spy": false, - "f-page-content": false, - "f-struct-like-card": false - } - } - ] - } - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - }, - "f-page-footer": { - "description": "The class of page footer designed by farris.", - "type": "string" - } - } - }, - "rules": { - "type": "object", - "properties": { - "canAccept": { - "type": "boolean", - "const": false - }, - "fixed": { - "type": "boolean", - "const": true - }, - "hidePadding": { - "type": "boolean", - "const": true - } - } - } - } - }, - "f-page-is-managelist": { - "description": "The root class of mangement list page.", - "type": "string" - }, - "f-page-card": { - "description": "", - "type": "object" - }, - "f-page-is-mainsubcard": { - "description": "", - "type": "object" - } - } -} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-drag-drop-rules.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-drag-drop-rules.ts deleted file mode 100644 index 7af83dea3ad9450ebf4e74b64c2708c7d6335e03..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-drag-drop-rules.ts +++ /dev/null @@ -1,196 +0,0 @@ -import dragAndDropRules from './drag-drop-rules.schema.json'; - -interface DragDropRule { - canAccept: boolean; - fixed: boolean; - hidePadding: boolean; - [index: string]: boolean; -} - -interface Expression { - target: string; - operator: string; - param: any; - value: any; -} - -interface DragAndDropContext { - children: any; - parent: any; - slibing: any; - [index: string]: any; -} - -export type CalculateFunction = (target: string, param: any, value: any, context: DragAndDropContext) => boolean; - -export type RuleFunction = (context: DragAndDropContext) => DragDropRule; - -export interface UseDragAndDropRule { - getRuleValue: (componentToken: string, context: DragAndDropContext) => DragDropRule; -} - -const ruleMap = new Map(); - -export function useDragAndDropRules(): UseDragAndDropRule { - - function judgingElementCount(target: string, param: any, value: any, context: DragAndDropContext) { - if (typeof value === 'number') { - return context[target]?.length === value; - } - if (typeof value === 'object') { - const compare = Object.keys(value)[0]; - const targetValue = value[compare]; - if (compare === 'not') { - return Number(context[target].length) !== Number(targetValue); - } - if (compare === 'moreThan') { - return Number(context[target].length) >= Number(targetValue); - } - if (compare === 'lessThan') { - return Number(context[target].length) <= Number(targetValue); - } - } - return false; - } - - function hasChildren(target: string, param: any, value: any, context: DragAndDropContext) { - if (typeof value === 'boolean') { - return context.childrenClassList.includes(param) === Boolean(value); - } - return false; - } - - function hasParent(target: string, param: any, value: any, context: DragAndDropContext) { - if (typeof value === 'boolean') { - return context.parentClassList.includes(param) === Boolean(value); - } - return false; - } - - function hasSibling(target: string, param: any, value: any, context: DragAndDropContext) { - if (typeof value === 'boolean') { - return context.parentClassList.includes(param) === Boolean(value); - } - return false; - } - - const expressionCalculateFunctions = new Map([ - ['length', judgingElementCount], - ['hasChildren', hasChildren], - ['hasSibling', hasSibling], - ['hasParent', hasParent] - ]); - - function parseExpression(token: string, expression: Record): Expression[] { - const target = token; - if (typeof expression === 'number') { - return [{ target, operator: 'length', param: null, value: Number(expression) }]; - } - if (typeof expression === 'object') { - return Object.keys(expression).map((key: string) => { - if (key === 'length') { - return { target, operator: 'length', param: null, value: expression[key] }; - } - const param = key; - const value = expression[key]; - const operator = token === 'children' ? 'hasChildren' : (token === 'parent' ? 'hasParent' : 'hasSibling'); - return { target, operator, param, value }; - }); - } - return []; - } - - function calculateExpression(expression: Expression, context: DragAndDropContext) { - if (expressionCalculateFunctions.has(expression.operator)) { - const calculateFunction = expressionCalculateFunctions.get(expression.operator); - return calculateFunction && calculateFunction(expression.target, expression.param, expression.value, context) || false; - } - return false; - } - - function calculate(expression: Record, context: DragAndDropContext): boolean { - const expressionTokens = Object.keys(expression); - - const parsedExpression = expressionTokens.reduce((expressions: Expression[], token: string) => { - const result = parseExpression(token, expression[token]); - expressions.push(...result); - return expressions; - }, []); - const result = parsedExpression.reduce((parsingResult: boolean, expression: Expression) => { - return parsingResult && calculateExpression(expression, context); - }, true); - - return result; - } - - function parseValueSchema(valueSchema: Record, context: DragAndDropContext): boolean { - const schemaKeys = Object.keys(valueSchema); - const allOf = schemaKeys.includes('allOf'); - const anyOf = schemaKeys.includes('anyOf'); - const hasLogicalOperatorsInSchemaKey = allOf || anyOf; - const logicalOperator = hasLogicalOperatorsInSchemaKey ? (allOf ? 'allOf' : 'anyOf') : 'allOf'; - const expressions = (hasLogicalOperatorsInSchemaKey ? valueSchema[logicalOperator] : [valueSchema]) as Record[]; - const expressionValues = expressions.map((expression: Record) => calculate(expression, context)); - const result = allOf ? !expressionValues.includes(false) : expressionValues.includes(true); - return result; - } - - function resolveRuleValue(ruleValueSchema: Record, context: DragAndDropContext): boolean { - const valueSchema = ruleValueSchema.const; - if (!valueSchema) { - return false; - } - if (typeof valueSchema === 'boolean') { - return valueSchema; - } - if (typeof valueSchema === 'object') { - return parseValueSchema(valueSchema, context); - } - return false; - } - - function generateRuleFunction(rulesSchema: Record): RuleFunction { - return (context: DragAndDropContext) => { - const rulesInstance = { canAccept: true, fixed: false, hidePadding: false } as DragDropRule; - rulesSchema && rulesSchema.properties && - Object.keys(rulesSchema.properties).reduce((compoentRulesMap: DragDropRule, ruleItemKey: string) => { - const ruleItemSchema = rulesSchema.properties[ruleItemKey]; - compoentRulesMap[ruleItemKey] = resolveRuleValue(ruleItemSchema, context); - return compoentRulesMap; - }, rulesInstance); - return rulesInstance; - }; - } - - function resolveComponentRule(componentToken: string, componentSchema: Record, ruleMap: Map) { - if (componentSchema.type === 'object' && componentSchema.properties) { - const { rules: rulesSchema, contents } = componentSchema.properties; - ruleMap.set(componentToken, generateRuleFunction(rulesSchema)); - if (contents) { - Object.keys(contents.properties).forEach((subComponentToken: string) => - resolveComponentRule(subComponentToken, contents.properties[subComponentToken], ruleMap) - ); - } - } - } - - function resolveRuleSchema() { - const { properties: componentsSchema } = dragAndDropRules as Record; - Object.keys(componentsSchema).forEach((componentToken: string) => { - resolveComponentRule(componentToken, componentsSchema[componentToken], ruleMap); - }); - } - - function getRuleValue(componentToken: string, context: DragAndDropContext): DragDropRule { - const rulesInstance = { canAccept: true, fixed: false, hidePadding: true } as DragDropRule; - if (ruleMap.has(componentToken)) { - const ruleFuntions = ruleMap.get(componentToken) as RuleFunction; - return ruleFuntions(context); - } - return rulesInstance; - } - - resolveRuleSchema(); - - return { getRuleValue }; -} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-dragula-common-rule.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-dragula-common-rule.ts deleted file mode 100644 index 7f6131d69135a84bbe457b8f63572c3598340a36..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-dragula-common-rule.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { DgControl } from "../dg-control"; -import { DesignerHostService, DraggingResolveContext } from "../types"; - -export function useDragulaCommonRule() { - - /** - * 容器类控件的基础控制规则 - */ - function basalDragulaRuleForContainer(draggingContext: DraggingResolveContext, designerHostService?: DesignerHostService): boolean { - if (!draggingContext) { - return false; - } - - /** 目标容器的组件实例 */ - const targetCmpInstance = draggingContext.targetContainer?.componentInstance && - draggingContext.targetContainer.componentInstance.value; - - if (!targetCmpInstance) { - return false; - } - const targetContainerType = targetCmpInstance.schema.type; - const belongedComponent = designerHostService?.formSchemaUtils.getComponentById(targetCmpInstance.belongedComponentId); - - // 限制输入类控件的可接收容器 - if (draggingContext.componentCategory === 'input' || draggingContext.componentType === 'form-group') { - if (![DgControl['response-layout-item'].type, DgControl['response-form'].type].includes(targetContainerType)) { - return false; - } - } - - // 限制标签页区域、分组面板的可接收容器 - if (draggingContext.componentType === DgControl.tabs.type || draggingContext.componentType === DgControl.section.type) { - const belongedComponentType = belongedComponent?.componentType; - if (belongedComponentType !== 'frame') { - return false; - } - if (![DgControl['content-container'].type, DgControl['splitter-pane'].type, DgControl['response-layout-item'].type].includes(targetContainerType)) { - return false; - } - } - // 限制筛选方案 - if (draggingContext.componentType === DgControl['query-solution'].type) { - return false; - } - return true; - } - - return { - basalDragulaRuleForContainer - }; -} diff --git a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-template-rule.ts b/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-template-rule.ts deleted file mode 100644 index 93c1c87835d2f8ac2f0d07ad33aa969e4d5f06b7..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-canvas/src/composition/rule/use-template-rule.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { DesignerItemContext } from "../../types"; -import { DesignerHostService } from "../types"; - -export interface DragDropRule { - canAccept: boolean; - canMove: boolean; - canDelete: boolean; - [index: string]: boolean; -} - -/** - * 解析模板拖拽控制规则 - */ -export class UseTemplateDragAndDropRules { - public getTemplateRule(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): DragDropRule { - - const formSchemaUtils = designerHostService?.formSchemaUtils; - const dragTemplateRules = formSchemaUtils?.getFormTemplateRule(); - const dragDropRule = { canAccept: true, canDelete: true, canMove: true }; - if (!dragTemplateRules) { - return dragDropRule; - } - const componentContext = this.getComponentContext(designItemContext); - const { componentClassList } = componentContext; - - componentClassList.forEach(componentClass => { - if (!componentClass || !dragTemplateRules[componentClass]) { - return; - } - - const { canMove: moveRule, canDelete: deleteRule, canAccept: acceptRule } = dragTemplateRules[componentClass]; - dragDropRule.canMove = dragDropRule.canMove && this.resolveRuleValue(moveRule, componentContext); - dragDropRule.canDelete = dragDropRule.canDelete && this.resolveRuleValue(deleteRule, componentContext); - dragDropRule.canAccept = dragDropRule.canAccept && this.resolveRuleValue(acceptRule, componentContext); - }); - - return dragDropRule; - } - - private resolveRuleValue(ruleSchema: any, componentContext: any): boolean { - - if (typeof ruleSchema === 'boolean') { - return ruleSchema; - } else { - return this.parseRuleValueSchema(ruleSchema, componentContext); - } - } - - private parseRuleValueSchema(ruleSchema: any, componentContext: any) { - const invalidContext = ruleSchema.invalidContext || []; - let isMatched = true; - for (const ruleContext of invalidContext) { - - // 判断子级节点是否匹配 - if (ruleContext.firstLevelChild) { - if (ruleContext.firstLevelChild.class) { - const { firstLevelChildClassList } = componentContext; - if (firstLevelChildClassList && !firstLevelChildClassList.includes(ruleContext.firstLevelChild.class)) { - isMatched = false; - continue; - } - } - if (ruleContext.firstLevelChild.type) { - const { firstLevelChildSchema } = componentContext; - if (!firstLevelChildSchema || firstLevelChildSchema.type !== ruleContext.firstLevelChild.type) { - isMatched = false; - continue; - } - } - - } - // 判断孙子节点是否匹配 - if (ruleContext.secondLevelChild) { - if (ruleContext.secondLevelChild.class) { - const { secondLevelChildClassList } = componentContext; - if (secondLevelChildClassList && !secondLevelChildClassList.includes(ruleContext.secondLevelChild.class)) { - isMatched = false; - continue; - } - } - if (ruleContext.secondLevelChild.type) { - const { secondLevelChildSchema } = componentContext; - if (!secondLevelChildSchema || secondLevelChildSchema.type !== ruleContext.secondLevelChild.type) { - isMatched = false; - continue; - } - } - - } - // 判断父级节点是否匹配 - if (ruleContext.parent) { - if (ruleContext.parent.class) { - const { parentClassList } = componentContext; - if (parentClassList && !parentClassList.includes(ruleContext.parent.class)) { - isMatched = false; - continue; - } - } - if (ruleContext.parent.type) { - const { parentSchema } = componentContext; - if (parentSchema && parentSchema.type !== ruleContext.parent.type) { - isMatched = false; - continue; - } - } - } - isMatched = true; - break; - } - return !isMatched; - - } - public getComponentContext(designItemContext: DesignerItemContext) { - const component = designItemContext.schema; - - // 控件本身 - const componentClass = component.appearance && component.appearance.class || ''; - const componentClassList = componentClass.split(' ') || []; - - // 控件子级节点 - const childContents = component.contents || []; - const firstLevelChildSchema = childContents.length ? childContents[0] : null; - const firstLevelChildClass = firstLevelChildSchema && firstLevelChildSchema.appearance ? firstLevelChildSchema.appearance.class : ''; - const firstLevelChildClassList = firstLevelChildClass ? firstLevelChildClass.split(' ') : []; - - // 控件孙子级节点 - const secondLevelChildSchema = firstLevelChildSchema?.contents?.length ? firstLevelChildSchema?.contents[0] : null; - const secondLevelChildClass = secondLevelChildSchema && secondLevelChildSchema.appearance ? secondLevelChildSchema.appearance.class : ''; - const secondLevelChildClassList = secondLevelChildClass ? secondLevelChildClass.split(' ') : []; - - // 控件父级节点 - const parentSchema = component.type === 'component' ? designItemContext.parent?.parent?.schema : designItemContext.parent?.schema; - const parentClass = parentSchema && parentSchema.appearance && parentSchema.appearance.class || ''; - const parentClassList = parentClass ? parentClass.split(' ') : []; - - return { - componentClass, - componentClassList, - childContents, - firstLevelChildSchema, - firstLevelChildClass, - firstLevelChildClassList, - secondLevelChildSchema, - secondLevelChildClass, - secondLevelChildClassList, - parentSchema, - parentClass, - parentClassList - }; - } -} diff --git a/packages/mobile-ui-vue/components/designer-toolbox/index.ts b/packages/mobile-ui-vue/components/designer-toolbox/index.ts deleted file mode 100644 index 6616581e20bfa48ed03e175249a0639d29b3445c..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-toolbox/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import FDesignerToolbox from './src/toolbox.component'; - -export * from './src/types'; - -export { FDesignerToolbox }; diff --git a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.component.tsx b/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.component.tsx deleted file mode 100644 index b28fc46df04c79399c94d3ae8767d63ee4d793b8..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.component.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { defineComponent, ref, watch } from 'vue'; -import { ToolboxPropsType, toolboxProps } from './toolbox.props'; -import { ToolboxCategory, ToolboxItem } from './types'; - -import toolboxItems from './toolbox.json'; -import './toolbox.css'; - -export default defineComponent({ - name: 'FDesignerToolbox', - props: toolboxProps, - emits: [], - setup(props: ToolboxPropsType) { - const controlCategoryList = props.toolboxItems ? ref(props.toolboxItems as ToolboxCategory[]) : ref(toolboxItems); - const dragularCompostion = ref(props.dragula); - - function onClickCardHeader(payload: MouseEvent, category: any) { - category.isHide = !category.isHide; - } - - function getCardHeaderIconClass(category: any) { - const classObject = { - 'f-icon': true, - 'f-icon-arrow-60-down': !category.isHide, - 'f-icon-arrow-e': category.isHide - } as Record; - return classObject; - } - - function renderCategoryCardHeader(category: ToolboxCategory) { - return ( -
onClickCardHeader(payload, category)}> -
-
-
-
- -
-
- {category.name} -
-
-
-
-
- ); - } - - function getControlTileClass(toolboxItem: ToolboxItem) { - const classObject = { - 'd-none': toolboxItem.dependentParent || toolboxItem.hideInControlBox, - controlPanel: true, - 'drag-copy': true, - 'no-drag': toolboxItem.disable, - 'updating': toolboxItem.updating - } as Record; - return classObject; - } - - function getToolboxItemClass(toolboxItem: ToolboxItem) { - const classObject = { - farrisControlIcon: true, - 'fd-i-Family': true - } as Record; - const toolboxItemTypicalClassName = `fd_pc-${toolboxItem.icon || toolboxItem.type}`; - classObject[toolboxItemTypicalClassName] = true; - return classObject; - } - - function renderControlTile(toolboxItem: ToolboxItem, category: ToolboxCategory) { - return ( - - ); - } - - function renderCategoryCardBody(category: ToolboxCategory) { - return ( -
- {category.items.map((toolboxItem: any) => renderControlTile(toolboxItem, category))} -
- ); - } - - function renderCategoryCard(category: ToolboxCategory) { - return ( - !category.hideInControlBox && ( -
- {renderCategoryCardHeader(category)} - {renderCategoryCardBody(category)} -
- ) - ); - } - - /** - * 将工具箱各容器添加到dragula的拖拽列表中 - */ - function attachToolboxToDragulaContainer(dragulaInstance: any) { - if (!dragulaInstance) { - return; - } - const controlPanels = document.getElementsByClassName('controlCategory'); - if (!controlPanels) { - return; - } - - dragulaInstance.containers = dragulaInstance.containers.filter( - (element: HTMLElement) => !element.className.includes('controlCategory') - ); - - Array.from(controlPanels).forEach((panelElement) => { - dragulaInstance.containers.push(panelElement); - }); - - } - - watch( - () => props.dragula, - (newValue: any) => { - dragularCompostion.value = newValue; - if (dragularCompostion.value?.getDragulaInstance) { - attachToolboxToDragulaContainer(dragularCompostion.value?.getDragulaInstance()); - } - } - ); - - return () => { - return ( -
-
- {controlCategoryList.value.map((category: any) => { - return renderCategoryCard(category); - })} -
-
- ); - }; - } -}); diff --git a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.css b/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.css deleted file mode 100644 index bdc92e873071b54035094c2b119bce297452f7b2..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.css +++ /dev/null @@ -1,75 +0,0 @@ -.controlBox { - font-size: 13px !important; -} - -.controlBox .farris-panel { - border: none; -} - -.controlBox .farris-panel .card-header { - padding: 10px 0px !important; - background-color: #fcfdff !important; -} - -.controlBox .farris-panel .card-header .col-form-label { - font-size: 13px; - margin-bottom: 0px; - color: #3f4764; - opacity: 0.65; -} - -.controlBox .farris-panel .card-header .col-form-label .f-icon { - font-size: 10.8px; - color: #3f4764; -} - -.controlBox .farris-panel .card-body { - display: flex; - flex-wrap: wrap; - background: #fcfdff !important; -} - -.controlPanel:nth-child(-n + 3) { - margin-top: 0 !important; -} - -.controlPanel .farrisControlIcon { - font-size: 27px; -} - -.controlPanel { - font-size: 13px; - cursor: grab; - display: flex; - background: #fff; - align-items: center; - width: 33.333%; - background-color: #fcfdff; - border: 1px solid #edf1f5; - height: 76px !important; - text-align: center; - margin: -1px 0 0 -1px !important; - color: #6080ad; - /* overflow: hidden; */ - text-overflow: ellipsis; - word-break: keep-all; - -webkit-user-select: none; - user-select: none; -} - -.controlPanel.updating { - color: #707070; -} - -.controlPanel > div { - margin: auto; - overflow: hidden; -} - -.gu-mirror.undroppable.controlPanel { - cursor: no-drop; -} - -.controlBox .farris-panel .card-header .col-form-label .icon-panel .f-icon { - font-size: 16px; -} diff --git a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.json b/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.json deleted file mode 100644 index e160cd632e857b784a0e9897dd3412aa280a72b3..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.json +++ /dev/null @@ -1,165 +0,0 @@ -[ - { - "type": "basic", - "name": "基础类控件", - "items": [ - { - "id": "Button", - "type": "button", - "name": "按钮", - "category": "basic", - "icon": "button" - }, - { - "id": "ButtonGroup", - "type": "button-group", - "name": "按钮组", - "category": "basic", - "icon": "button-group" - } - ] - }, - { - "type": "input", - "name": "输入类控件", - "items": [ - { - "id": "TextBox", - "type": "input-group", - "name": "文本框", - "category": "input", - "icon": "input-group" - }, - { - "id": "MultiTextBox", - "type": "textarea", - "name": "多行文本", - "category": "input", - "icon": "textarea" - }, - { - "id": "DateBox", - "type": "date-picker", - "name": "日期选择", - "category": "input", - "icon": "date-picker" - }, - { - "id": "EnumField", - "type": "enum-field", - "name": "选择器", - "category": "input", - "icon": "input-group" - }, - { - "id": "NumericBox", - "type": "number-spinner", - "name": "数值", - "category": "input", - "icon": "number-spinner" - }, - { - "id": "RadioGroup", - "type": "radio-group", - "name": "单选组", - "category": "input", - "icon": "radio-group" - }, - { - "id": "CheckBoxGroup", - "type": "check-group", - "name": "复选框组", - "category": "input", - "icon": "check-group" - }, - { - "id": "SwitchField", - "type": "switch", - "name": "开关", - "category": "input", - "icon": "switch" - } - ] - }, - { - "type": "navigation", - "name": "导航类控件", - "items": [ - { - "id": "NavigationBar", - "type": "navigation-bar", - "name": "导航栏", - "category": "navigation", - "icon": "nav-tab" - } - ] - }, - { - "type": "container", - "name": "容器类控件", - "items": [ - { - "id": "PageHeaderContainer", - "type": "page-header-container", - "name": "页头容器", - "category": "container", - "icon": "content-container" - }, - { - "id": "PageBodyContainer", - "type": "page-body-container", - "name": "页面主体容器", - "category": "container", - "icon": "content-container" - }, - { - "id": "PageFooterContainer", - "type": "page-footer-container", - "name": "页尾容器", - "category": "container", - "icon": "content-container" - }, - { - "id": "ContentContainer", - "type": "content-container", - "name": "通用容器", - "category": "container", - "icon": "content-container" - }, - { - "id": "FloatContainer", - "type": "float-container", - "name": "浮动容器", - "category": "container", - "icon": "content-container" - }, - { - "id": "Form", - "type": "form", - "name": "字段卡片", - "category": "container", - "icon": "response-form" - }, - { - "id": "Card", - "type": "card", - "name": "卡片", - "category": "container", - "icon": "section" - } - ] - }, - { - "type": "display", - "name": "展示类控件", - "items": [ - { - "id": "ListView", - "type": "list-view", - "name": "列表", - "category": "display", - "icon": "list-view" - } - ] - } -] \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.props.ts b/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.props.ts deleted file mode 100644 index ddf9559cc143543589252b546eeb68e7e25dd251..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-toolbox/src/toolbox.props.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ExtractPropTypes } from 'vue'; - -export const toolboxProps = { - id: { type: String, default: '' }, - dragula: { type: Object }, - toolboxItems: { type: Object } - -}; - -export type ToolboxPropsType = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/designer-toolbox/src/types.ts b/packages/mobile-ui-vue/components/designer-toolbox/src/types.ts deleted file mode 100644 index 52436075af57540988492f1f7cca86ef4aaeb42b..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/designer-toolbox/src/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface ToolboxItem { - id: string; - type: string; - name: string; - category: string; - icon?: string; - feature?: any; - dependentParent?: boolean; - hideInControlBox?: boolean; - disable?: boolean; - fieldType?: string; - templateCategory?: string; - updating?: boolean; -} - -export interface ToolboxCategory { - type: string; - name: string; - items: ToolboxItem[]; - hideInControlBox?: boolean; - isHide?: boolean; -}; diff --git a/packages/mobile-ui-vue/components/designer.ts b/packages/mobile-ui-vue/components/designer.ts index 81ea1c807dd4ec3f81bcbbce6a19d3d20cdeac68..49fc635cbdab0f73fd543dff42840fecbc683c07 100644 --- a/packages/mobile-ui-vue/components/designer.ts +++ b/packages/mobile-ui-vue/components/designer.ts @@ -1,6 +1,3 @@ -export * from './designer-canvas'; export * from './dynamic-resolver'; -export { FDesignerToolbox } from './designer-toolbox'; -export { default as FModal, FModalService, FM_MODAL_SERVICE_TOKEN } from './modal'; -export { FM_UI_PROVIDER_SERVICE_TOKEN } from './common'; export * from './register-designer'; +export { LookupSchemaRepositoryToken, FieldSelectorRepositoryToken } from './lookup'; diff --git a/packages/mobile-ui-vue/components/dialog/index.ts b/packages/mobile-ui-vue/components/dialog/index.ts index 311ad77e78772e879844cb453b5ac2a1b268f9e7..75acd2c292f0bb3cad6ddfb48be9eef56568d8c4 100644 --- a/packages/mobile-ui-vue/components/dialog/index.ts +++ b/packages/mobile-ui-vue/components/dialog/index.ts @@ -1,4 +1,5 @@ import Dialog from './src/index'; +export type { DialogAlertOptions, DialogConfirmOptions, DialogPromptOptions } from './src'; export { Dialog }; export default Dialog; diff --git a/packages/mobile-ui-vue/components/dialog/src/dialog.component.tsx b/packages/mobile-ui-vue/components/dialog/src/dialog.component.tsx index d36a898da388ee88391823d179228f78b0b5f0f4..e6982dfeb1cca129d63af7f8052ad97fcc8f2c93 100644 --- a/packages/mobile-ui-vue/components/dialog/src/dialog.component.tsx +++ b/packages/mobile-ui-vue/components/dialog/src/dialog.component.tsx @@ -14,20 +14,19 @@ * limitations under the License. */ import { SetupContext, computed, defineComponent, ref, toRef, watch } from 'vue'; -import { DialogProps, dialogProps } from './dialog.props'; import { preventDefault, useBem } from '@farris/mobile-ui-vue/common'; import Popup from '@farris/mobile-ui-vue/popup'; -import FmButton from '@farris/mobile-ui-vue/button'; +import Button from '@farris/mobile-ui-vue/button'; +import { DIALOG_NAME, DialogButton, DialogProps, dialogProps } from './dialog.props'; -const name = 'fm-dialog'; export default defineComponent({ - name, + name: DIALOG_NAME, props: dialogProps, emits: ['update:show', 'open', 'close'], setup(props: DialogProps, context: SetupContext) { const { attrs, emit, slots } = context; - const { bem } = useBem(name); + const { bem } = useBem(DIALOG_NAME); const hasShown = ref(props.show); watch( @@ -86,13 +85,13 @@ export default defineComponent({ return
{getContent()}
; }; - const onButtonClick = (event: MouseEvent, button: any) => { + const onButtonClick = (event: MouseEvent, button: DialogButton) => { preventDefault(event, true); if (button.disabled || button.loading) { return; } - if (typeof button.handler === 'function') { - button.handler.call(null, button); + if (typeof button.onClick === 'function') { + button.onClick.call(null, button); } else { hasShown.value = false; emit('update:show', false); @@ -112,18 +111,16 @@ export default defineComponent({
{slots.footer ? slots.footer() - : props.buttons?.map((button: any) => ( - ( + ))}
); diff --git a/packages/mobile-ui-vue/components/dialog/src/dialog.props.ts b/packages/mobile-ui-vue/components/dialog/src/dialog.props.ts index d9c1e0a97c9075b3d7d9a20f15829b6676c1f65d..90585a5a7abec687c2ba35904854df99ff04807f 100644 --- a/packages/mobile-ui-vue/components/dialog/src/dialog.props.ts +++ b/packages/mobile-ui-vue/components/dialog/src/dialog.props.ts @@ -13,16 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ExtractPropTypes, PropType } from 'vue'; +import { ButtonType } from '@farris/mobile-ui-vue/button/src/button.props'; +import { ExtractPropTypes, PropType, TeleportProps } from 'vue'; -export type alignType = 'start' | 'end' | 'left' | 'center' | 'right' | 'justify' | 'match-parent'; +export enum MessageAlign { + start= 'start', + end= 'end', + left= 'left', + center= 'center', + right= 'right', + justify= 'justify', + matchParent= 'match-parent', +}; + +export type DialogButton = { + type?: ButtonType, + disabled?: boolean, + loading?: boolean, + text: string, + onClick: (button: DialogButton) => void +}; + +export const DIALOG_NAME = 'FmDialog'; export const dialogProps = { title: { type: String, default: '' }, message: { type: String, default: '' }, - messageAlign: { type: String as PropType, default: 'center' }, + messageAlign: { type: String as PropType, default: MessageAlign.center }, show: { type: Boolean, default: false }, @@ -30,13 +49,13 @@ export const dialogProps = { lockScroll: { type: Boolean, default: true }, - teleport: { type: String, default: '' }, + teleport: { type: [String, Object] as PropType, default: '' }, buttonLayout: { type: String, default: 'row' }, - buttons: { type: Array, default: [] }, + buttons: { type: Array as PropType, default: [] }, - className: { type: String, default: '' }, + className: { type: String, default: undefined }, showClose: { type: Boolean, default: false }, diff --git a/packages/mobile-ui-vue/components/dialog/src/index.tsx b/packages/mobile-ui-vue/components/dialog/src/index.tsx index 0c884b16e7ae8e1601fc756f0b2317a970c8f2cd..06b6a5cfd85c94de90e7eb8eb7c18f1efa070a7b 100644 --- a/packages/mobile-ui-vue/components/dialog/src/index.tsx +++ b/packages/mobile-ui-vue/components/dialog/src/index.tsx @@ -1,31 +1,19 @@ -/** - * 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 { App, ref } from 'vue'; import { mountComponent, usePopupState, isObject, inBrowser } from '@farris/mobile-ui-vue/common'; +import { InputProps, InputGroup } from '@farris/mobile-ui-vue/input-group'; +import { DialogProps, MessageAlign } from './dialog.props'; import FMDialog from './dialog.component'; -import FMInput from '@farris/mobile-ui-vue/input-group'; let instance; -function defaultOptions() { + +function defaultOptions(): DialogProps { return { title: '', message: '', - messageAlign: 'center', + messageAlign: MessageAlign.center, overlay: true, lockScroll: true, + show: false, teleport: 'body', buttonLayout: 'row', buttons: [], @@ -47,7 +35,7 @@ const initInstance = () => { })); }; -function Dialog(options) { +function Dialog(options: Partial) { if (!inBrowser) { return; } @@ -68,11 +56,18 @@ function Dialog(options) { Dialog.clear = () => { if (instance) { - instance.toggle(false); + instance.close(); } }; -Dialog.confirm = (options) => { +export type DialogConfirmOptions = Partial & { + cancelText: string, + confirmText: string, + onConfirm: () => void, + onCancel: () => void, +}> + +Dialog.confirm = (options: DialogConfirmOptions) => { const CancelText = '取消'; const ConfirmText = '确定'; @@ -87,15 +82,14 @@ Dialog.confirm = (options) => { buttons: [ { text: cancelText, - type: 'info', - handler: () => { + onClick: () => { onCancel(); instance.close(); } }, { text: confirmText, - handler: () => { + onClick: () => { onConfirm(); instance.close(); } @@ -106,13 +100,16 @@ Dialog.confirm = (options) => { Dialog(_options); }; -Dialog.prompt = (options) => { - const CancelText = '取消'; - const ConfirmText = '确定'; +export type DialogPromptOptions = Partial, + onConfirm: (value: string)=> void +}> +Dialog.prompt = (options: DialogPromptOptions) => { const { - confirmText = ConfirmText, - cancelText = CancelText, + confirmText = '取消', + cancelText = '确定', onConfirm = () => {}, onCancel = () => {}, defaultText = '', @@ -127,14 +124,14 @@ Dialog.prompt = (options) => { buttons: [ { text: cancelText, - handler: () => { + onClick: () => { onCancel(); instance.close(); } }, { text: confirmText, - handler: () => { + onClick: () => { onConfirm(inputValue.value); instance.close(); } @@ -142,24 +139,29 @@ Dialog.prompt = (options) => { ], content: () => { return ( - + withBorder={true}>
); }, ...others }; Dialog(_options); }; -Dialog.alert = (options) => { - const ConfirmText = '确定'; - const { confirmText = ConfirmText, onConfirm = () => {}, ...others } = options; + +export type DialogAlertOptions = Partial & { + confirmText: string, + onConfirm: () => void +}> + +Dialog.alert = (options: DialogAlertOptions) => { + const { confirmText = '确定', onConfirm = () => {}, ...others } = options; const _options = { buttons: [ { text: confirmText, - handler: () => { + onClick: () => { onConfirm(); instance.close(); } @@ -172,7 +174,7 @@ Dialog.alert = (options) => { Dialog.currentOptions = defaultOptions(); -Dialog.setDefaultOptions = (options) => { +Dialog.setDefaultOptions = (options: DialogProps) => { Object.assign(Dialog.currentOptions, options); }; @@ -181,7 +183,7 @@ Dialog.resetDefaultOptions = () => { }; Dialog.install = (app: App) => { - app.component(FMDialog.name, FMDialog); + app.component((FMDialog.name as string), FMDialog); app.config.globalProperties.$dialog = Dialog; }; diff --git a/packages/mobile-ui-vue/components/dynamic-form/index.ts b/packages/mobile-ui-vue/components/dynamic-form/index.ts deleted file mode 100644 index 4eee3a7964841de2fd0304225a0b81de5b707ff2..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/dynamic-form/index.ts +++ /dev/null @@ -1,4 +0,0 @@ - - -export * from './src/types'; -export * from './src/composition/types'; diff --git a/packages/mobile-ui-vue/components/dynamic-form/src/composition/response-form-component-creator.service.ts b/packages/mobile-ui-vue/components/dynamic-form/src/composition/response-form-component-creator.service.ts deleted file mode 100644 index 296ef9ed7657279978d3ede36021c65900dc636e..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/dynamic-form/src/composition/response-form-component-creator.service.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { inject } from 'vue'; -import { DesignerHostService } from '../../../designer-canvas/src/composition/types'; -import { DynamicResolver } from '../../../../components/dynamic-resolver'; -import { ComponentBuildInfo } from '../../../component/src/composition/inner-component-build-info'; -import { ComponentSchema } from '../../../../components/designer-canvas'; -import { FormSchemaEntityFieldTypeName } from '../../../common/entity/entity-schema'; -import { cloneDeep } from 'lodash-es'; - -const ROOT_VIEW_MODEL_ID = 'root-viewmodel'; -/** - * 创建卡片类组件服务类 - */ -export class ResponseFormComponentCreatorService { - - private formSchemaUtils: any; - private controlCreatorUtils: any; - private designViewModelUtils: any; - - constructor( - private resolver: DynamicResolver, - private designerHostService: DesignerHostService - ) { - this.formSchemaUtils = this.designerHostService.formSchemaUtils; - this.controlCreatorUtils = this.designerHostService.controlCreatorUtils; - this.designViewModelUtils = this.designerHostService.designViewModelUtils; - } - - public createComponent(buildInfo: ComponentBuildInfo) { - const componentRefNode = this.createComponentRefNode(buildInfo); - - const componentNode = this.createComponentNode(buildInfo); - - const viewModelNode = this.createViewModeNode(buildInfo); - - const formSchema = this.formSchemaUtils.getFormSchema(); - formSchema.module.viewmodels.push(viewModelNode); - formSchema.module.components.push(componentNode); - - this.designViewModelUtils.assembleDesignViewModel(); - - return componentRefNode; - } - createComponentRefNode(buildInfo: ComponentBuildInfo): any { - const componentRefNode = this.resolver.getSchemaByType('component-ref') as ComponentSchema; - Object.assign(componentRefNode, { - id: `${buildInfo.componentId}-component-ref`, - component: `${buildInfo.componentId}-component`, - }); - return componentRefNode; - } - - createComponentNode(buildInfo: ComponentBuildInfo): any { - const componentNode = this.resolver.getSchemaByType('component') as ComponentSchema; - const contents = this.createFormComponentContents(buildInfo); - Object.assign(componentNode, { - id: `${buildInfo.componentId}-component`, - viewModel: `${buildInfo.componentId}-component-viewmodel`, - componentType: buildInfo.componentType, - appearance: { - class: this.getFormComponentClass() - }, - formColumns: buildInfo.formColumns, - contents - }); - return componentNode; - } - - - /** - * 获取卡片组件层级的class样式 - */ - private getFormComponentClass(): string { - const {templateId} = this.formSchemaUtils.getFormSchema().module; - - // 双列表标签页模板中拖入卡片 - if (templateId === 'double-list-in-tab-template') { - return 'f-struct-wrapper f-utils-fill-flex-column'; - } - return 'f-struct-wrapper'; - - } - - private createFormComponentContents(buildInfo: ComponentBuildInfo) { - // 1、创建section - const section = this.resolver.getSchemaByType('section') as ComponentSchema; - Object.assign(section, { - id: buildInfo.componentId + '-form-section', - appearance: { - class: 'f-section-form f-section-in-mainsubcard' - }, - mainTitle: buildInfo.componentName - }); - - // 2、创建form(默认控件标签独占一列) - const responseForm = this.resolver.getSchemaByType('response-form') as ComponentSchema; - const controls: any[] = []; - Object.assign(responseForm, { - id: buildInfo.componentId + '-form', - appearance: { - class: 'f-form-layout farris-form farris-form-controls-inline' - }, - contents: controls - }); - section.contents = [responseForm]; - - // 3、创建字段 - const { selectedFields } = buildInfo; - selectedFields?.forEach(field => { - const dgVMField = cloneDeep(field); - const resolvedControlClass = this.resolveControlClassByFormColumns(buildInfo); - const fieldMetadata = this.controlCreatorUtils.setFormFieldProperty(dgVMField, '', resolvedControlClass); - - if (fieldMetadata) { - controls.push(fieldMetadata); - } - }); - - // 双列表标签页模板中拖入卡片,要求卡片要填充 - 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; - } - - return [section]; - } - - private resolveControlClassByFormColumns(buildInfo: ComponentBuildInfo) { - let className = ''; - switch (buildInfo.formColumns) { - case 1: { - className = 'col-12'; - break; - } - case 2: { - className = 'col-12 col-md-6 col-xl-6 col-el-6'; - break; - } - case 3: { - className = 'col-12 col-md-6 col-xl-4 col-el-4'; - break; - } - case 4: { - className = 'col-12 col-md-6 col-xl-3 col-el-2'; - break; - } - } - - return className; - } - /** - * 添加viewModel节点 - */ - 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: this.assembleViewModelFields(buildInfo), - commands: [], - states: [], - enableValidation: true - }; - return viewModelNode; - } - - /** - * 组装viewModel fields 节点 - */ - private assembleViewModelFields(buildInfo: ComponentBuildInfo) { - - const vmFields: any[] = []; - const { selectedFields } = buildInfo; - selectedFields?.forEach(field => { - let updateOn = 'blur'; - const type = field.type.name; - if (type === FormSchemaEntityFieldTypeName.Enum || type === FormSchemaEntityFieldTypeName.Boolean) { - updateOn = 'change'; - } - - vmFields.push({ - type: 'Form', - id: field.id, - fieldName: field.bindingField, - groupId: null, - groupName: null, - updateOn, - fieldSchema: {} - }); - }); - return vmFields; - } - -} diff --git a/packages/mobile-ui-vue/components/dynamic-form/src/types.ts b/packages/mobile-ui-vue/components/dynamic-form/src/types.ts deleted file mode 100644 index e5029eeff4ca03ccb65d39d4ba1832f9333342bd..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/dynamic-form/src/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -export type EditorType = 'button-edit' | 'check-box' | 'check-group' | 'combo-list' | 'combo-lookup' | 'combo-tree' | - 'date-picker' | 'date-range' | 'datetime-picker' | 'datetime-range' | 'events-editor' | 'month-picker' | 'month-range' | - 'year-picker' | 'year-range' | 'input-group' | 'lookup' | 'number-range' | 'number-spinner' | 'radio-group' | 'text' | - 'response-layout-editor-setting' | 'switch' | 'grid-field-editor' | 'field-selector' | 'schema-selector' | 'mapping-editor' | - 'textarea' | 'response-form-layout-setting'|'binding-selector' | 'query-solution-config' | 'solution-preset' | 'item-collection-editor'; - -export interface EditorConfig { - /** 编辑器类型 */ - type: EditorType; - /** 自定义样式 */ - customClass?: string; - /** 禁用 */ - disabled?: boolean; - /** 只读 */ - readonly?: boolean; - /** 必填 */ - required?: boolean; - /** 提示文本 */ - placeholder?: string; - /** 其他属性 */ - [key: string]: any; -} -export interface FormUnifiedColumnLayout { - uniqueColClassInSM: number; - uniqueColClassInMD: number; - uniqueColClassInLG: number; - uniqueColClassInEL: number; -} -export interface UseResponseFormLayoutSetting { - checkIsInFormComponent: (componentId: string) => boolean; - assembleUnifiedLayoutContext: (propertyData: any) => FormUnifiedColumnLayout; - changeFormControlsByUnifiedLayoutConfig: (formNode: any, unifiedLayout: FormUnifiedColumnLayout, formSchemaId: string) => void; -} diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/index.ts b/packages/mobile-ui-vue/components/dynamic-resolver/index.ts index bab138a779328625c97c8a36a46d55602de13e13..11a5de746fed39a034a29a63512e1d874579a2e1 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/index.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/index.ts @@ -3,6 +3,10 @@ export * from './src/props-resolver'; export * from './src/common/appearance-resolver'; export * from './src/common/toolbar-resolver'; export * from './src/common/data-resolver'; +export * from './src/common/position-resolver'; +export * from './src/common/padding-resolver'; +export * from './src/common/margin-resolver'; +export * from './src/common/size-resolver'; export * from './src/binding-resolver'; export * from './src/events-resolver'; export * from './src/selection-item-resolver'; @@ -12,5 +16,9 @@ export * from './src/event-handler-resolver'; export * from './src/resolver/schema/schema-resolver'; export * from './src/resolver/schema/schema-resolver-design'; export * from './src/update-columns-resolver'; +export * from './src/resolver/type-resolver/types'; +export * from './src/resolver/type-resolver/use-type-resolver-design'; +export * from './src/resolver/type-resolver/use-type-resolver'; + export { propertyConfigSchemaMap, getPropertyConfigBySchema } from './src/resolver/property-config/property-config-resolver'; export { propertyConfigSchemaMapForDesigner, getPropertyConfigBySchemaForDesigner } from './src/resolver/property-config/property-config-resolver-design'; diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/appearance-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/appearance-resolver.ts index c5a7cbd2e5ba84ef3b3b6632fb4e63570db540c0..4392ca2d6ccfa6e086a9e2fe6fe874a8ab1b73e3 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/appearance-resolver.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/appearance-resolver.ts @@ -1,3 +1,3 @@ export function resolveAppearance(key: string, appearanceObject: { class: string; style: string }) { - return { customClass: appearanceObject.class,customStyle:appearanceObject.style }; + return { customClass: appearanceObject?.class, customStyle: appearanceObject?.style }; } diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/margin-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/margin-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..485fb4ea3e9ab6f115bd948cdfb5b51ac3af41fb --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/margin-resolver.ts @@ -0,0 +1,6 @@ +import { DirectionalStyleObject, getMarginStyle, concatCustomStyle } from './utils'; + +export function resolveMargin(key: string, marginObject: DirectionalStyleObject, resolvedSchema: any) { + const marginStyle = getMarginStyle(marginObject); + return concatCustomStyle(marginStyle, resolvedSchema); +} diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/padding-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/padding-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1a42dadc8601f4f682e6564b048a6b6f2997bbb --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/padding-resolver.ts @@ -0,0 +1,6 @@ +import { DirectionalStyleObject, getPaddingStyle, concatCustomStyle } from './utils'; + +export function resolvePadding(key: string, paddingObject: DirectionalStyleObject, resolvedSchema: any) { + const paddingStyle = getPaddingStyle(paddingObject); + return concatCustomStyle(paddingStyle, resolvedSchema); +} diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/position-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/position-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..7fdb8bdc42799278535d7df4313b7fefc0dec56d --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/position-resolver.ts @@ -0,0 +1,6 @@ +import { DirectionalStyleObject, getPositionStyle, concatCustomStyle } from './utils'; + +export function resolvePosition(key: string, positionObject: DirectionalStyleObject, resolvedSchema: any) { + const positionStyle = getPositionStyle(positionObject); + return concatCustomStyle(positionStyle, resolvedSchema); +} diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/size-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/size-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..9bea291713c32d216be783d8c7bcd82aedaab8cc --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/size-resolver.ts @@ -0,0 +1,6 @@ +import { SizeStyleObject, getSizeStyle, concatCustomStyle } from './utils'; + +export function resolveSize(key: string, sizeObject: SizeStyleObject, resolvedSchema: any) { + const sizeStyle = getSizeStyle(sizeObject); + return concatCustomStyle(sizeStyle, resolvedSchema); +} diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/toolbar-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/toolbar-resolver.ts index 08ebea5f43f96297aac3e69e04b1355155077378..0a3221cdf4f446618dc6afab951d5f61fd768cb9 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/toolbar-resolver.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/toolbar-resolver.ts @@ -1,15 +1,36 @@ -export function resolveToolbar(key: string, toolbarObject: { buttons: any[],appearance:any }) { - const result = [] as any; - toolbarObject?.buttons.map(button => { - const newButton = {}; - Object.keys(button).map(key => { - if (key === 'appearance') { - newButton['class']=button[key]?.class||''; - }else{ - newButton[key] = button[key]; +function generateResolveToolbarFunction(toolbarPropertyName = "toolbarItems", options?: { defaultButtonType: string }) { + return (key: string, toolbarObject: { items: any[] }) => { + const toolbarItems: any[] = []; + const result = { [toolbarPropertyName]: toolbarItems }; + if (typeof toolbarObject !== 'object' || !toolbarObject) { + return result; + } + const items: any[] = toolbarObject?.items || []; + if (!Array.isArray(items)) { + return result; + } + items.filter(item => typeof item === 'object' && !!item).forEach(item => { + const newItem: any = {}; + Object.keys(item).forEach(key => { + if (key === 'displayType') { + newItem.type = item[key]; + } else if (key === 'appearance') { + newItem.customClass = item[key]?.class || ''; + newItem.customStyle = item[key]?.style || ''; + } else if (key === 'type') { + return; + } else { + newItem[key] = item[key]; + } + }); + if (!newItem.type && !newItem.customClass && !newItem.customStyle && options?.defaultButtonType) { + newItem.type = options.defaultButtonType; } + toolbarItems.push(newItem); }); - result.push(newButton); - }); - return { buttons: result }; + return result; + }; } + +export const resolveToolbar = generateResolveToolbarFunction("toolbarItems", { defaultButtonType: "primary" }); +export const resolveSwipeToolbar = generateResolveToolbarFunction("swipeToolbar", { defaultButtonType: "danger" }); diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/common/utils.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..4aedc6315078208f61b1e1c45180152834b0e2f5 --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/common/utils.ts @@ -0,0 +1,59 @@ +export function concatCustomStyle(newStyle: string, resolvedSchema: any): { customStyle: string } { + newStyle = newStyle || ''; + let customStyle: string = resolvedSchema?.customStyle || ''; + if (typeof customStyle !== 'string') { + customStyle = ''; + } + customStyle = customStyle.trim(); + customStyle = newStyle + customStyle; + return { customStyle }; +} + +function convertStyleObject2String(styleObject: any, validPropNames: string[], cssPropNamePrefix?: string): string { + if (typeof styleObject !== 'object' || !styleObject) { + return ''; + } + return Object.keys(styleObject) + .filter((propName) => { + return validPropNames.includes(propName); + }) + .reduce((styleString, propName) => { + const value = styleObject[propName]; + if (typeof value !== 'number') { + return styleString; + } + const fullPropName = cssPropNamePrefix ? `${cssPropNamePrefix}-${propName}` : propName; + return `${styleString}${fullPropName}: ${value}px;`; + }, ''); +} + +export interface DirectionalStyleObject { + top?: number; + right?: number; + bottom?: number; + left?: number; +} + +export interface SizeStyleObject { + width?: number; + height?: number; +} + +const DIRECTIONAL_STYLE_PROPS = ["top", "right", "bottom", "left"]; +const SIZE_STYLE_PROPS = ["width", "height"]; + +export function getPaddingStyle(paddingObject: DirectionalStyleObject): string { + return convertStyleObject2String(paddingObject, DIRECTIONAL_STYLE_PROPS, 'padding'); +} + +export function getMarginStyle(marginObject: DirectionalStyleObject): string { + return convertStyleObject2String(marginObject, DIRECTIONAL_STYLE_PROPS, 'margin'); +} + +export function getPositionStyle(positionObject: DirectionalStyleObject): string { + return convertStyleObject2String(positionObject, DIRECTIONAL_STYLE_PROPS); +} + +export function getSizeStyle(sizeObject: SizeStyleObject): string { + return convertStyleObject2String(sizeObject, SIZE_STYLE_PROPS); +} diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/appearance.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/appearance.converter.ts index b6ca97b8dff89c0bce1fd44020c8388e552d2428..cf9c14e64b3470662551d4ffa82c7f364cf56d28 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/appearance.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/appearance.converter.ts @@ -1,14 +1 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; -import { PropertyConverter, SchemaService } from "../types"; - -export default { - convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { - if (!schema.appearance) { - schema.appearance={}; - } - schema.appearance[propertyKey] = propertyValue; - }, - convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { - return schema.appearance ? schema.appearance[propertyKey] : schema[propertyKey]; - } -} as PropertyConverter; +export { appearanceConverter as default } from "./cascade.converter"; diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/buttons.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/buttons.converter.ts index abfb60a4d295ffa47437e918cd566e80e7908a4d..1eb5a9f3b6d22c9415a20a4bf91bad058977b592 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/buttons.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/buttons.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; export default { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/cascade.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/cascade.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..dce361318b010de8a91b721adc98b4ab00480e69 --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/cascade.converter.ts @@ -0,0 +1,27 @@ +import { PropertyConverter, SchemaService } from "../types"; + +export function generateCascadePropertyConverter(parentPropertyKey: string, parentPropertyDefaultValue: any = {}): PropertyConverter { + return { + convertTo: (schema: Record, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (!schema[parentPropertyKey] || typeof schema[parentPropertyKey] !== 'object') { + schema[parentPropertyKey] = parentPropertyDefaultValue; + } + schema[parentPropertyKey][propertyKey] = propertyValue; + }, + convertFrom: (schema: Record, propertyKey: string, schemaService: SchemaService) => { + return schema[parentPropertyKey]?.[propertyKey]; + }, + }; +} + +export const appearanceConverter = generateCascadePropertyConverter('appearance'); +export const sizeConverter = generateCascadePropertyConverter('size'); +export const paddingConverter = generateCascadePropertyConverter('padding'); +export const marginConverter = generateCascadePropertyConverter('margin'); +export const borderRadiusConverter = generateCascadePropertyConverter('borderRadius'); +export const positionConverter = generateCascadePropertyConverter('position'); +export const flexBoxConverter = generateCascadePropertyConverter('flexBox'); + +export const toolbarConverter = generateCascadePropertyConverter('toolbar', { items: [] }); +export const swipeToolbarConverter = generateCascadePropertyConverter('swipeToolbar', { items: [] }); +export const rightToolbarConverter = generateCascadePropertyConverter('rightToolbar', { items: [] }); diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/change-editor.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/change-editor.converter.ts index 61e5ef06b48fb0060091dc693d490bb44c31df24..72f511cefe1b2ebbb460330f042b5a2837def0be 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/change-editor.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/change-editor.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; export default { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/enum-data.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/enum-data.converter.ts index 3b1a445ef2fa3bf474069910e903c293a16c3266..aec811359a71ae0e65201aca313583ef1c75b282 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/enum-data.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/enum-data.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; export default { convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/grid-selection.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/grid-selection.converter.ts index b37faa1b46e3dd9b97dd5b8c7183e7202b03f226..7c5e0797417cbe4db98bba063fbc5ce12aa4982f 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/grid-selection.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/grid-selection.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; export default { convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/index.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..060b0343df1d014b6af89dd01f374d9082c45d17 --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/index.ts @@ -0,0 +1 @@ +export * from './cascade.converter'; diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/items-count.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/items-count.converter.ts index 288438ef0f2b17a5b63d26388c4f6ec65589a75e..b23f948d25d240e52160612eecc78a49d7f341b9 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/items-count.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/items-count.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; export default { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/pagination.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/pagination.converter.ts index ebf0e2bb56a5c1011f6ff81f0dffdd9aebe1a5d4..6cdde30711a4a02d2580e005d16a04c6714a4f01 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/pagination.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/pagination.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; /** * NG版,此处pagination类型是布尔 diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/property-editor.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/property-editor.converter.ts index e6e83cae987d6437ee8e7a1879641f845a0b9f0d..4c724568e9f65ce3f198fefc77cbf8925bd33d49 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/property-editor.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/property-editor.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; export default { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/right-toolbar.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/right-toolbar.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..c8047af63d4ce7aff16a299936f8065a3632d383 --- /dev/null +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/right-toolbar.converter.ts @@ -0,0 +1,14 @@ +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; +import { PropertyConverter, SchemaService } from "../types"; + +export default { + convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { + if (!schema.rightToolbar) { + schema.rightToolbar = []; + } + schema.rightToolbar[propertyKey] = propertyValue; + }, + convertFrom: (schema: ComponentSchema, propertyKey: string, schemaService: SchemaService) => { + return schema.rightToolbar ? schema.rightToolbar[propertyKey] : schema[propertyKey]; + } +} as PropertyConverter; diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/row-number.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/row-number.converter.ts index 929fb4e037af6a0ecd1de5b07549c82212a7d849..94a4e5c8711002e22cab3abc23f7d4303fdf0f07 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/row-number.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/row-number.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../types"; export default { convertTo: (schema: ComponentSchema, propertyKey: string, propertyValue: any, schemaService: SchemaService) => { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/type.converter.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/type.converter.ts index f3e2f4c71f0671f8aca0b0dd26fba07948ed1648..805dabf1907bb3469c1ad4ebc3a7a819a703fa55 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/type.converter.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/converter/type.converter.ts @@ -1,5 +1,5 @@ -import { ComponentSchema } from "../../../designer-canvas/src/types"; -import { DgControl } from "../../../designer-canvas/src/composition/dg-control"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; +import { DgControl } from "../../../common/src/properties/dg-control"; import { PropertyConverter, SchemaService } from "../types"; export default { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/property-config-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/property-config-resolver.ts index 78dc7485bf39d0188860cf206a9314eebfa17ab9..ccf969fd64af0944e0a3af6c2baf06d6478caaab 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/property-config-resolver.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/property-config-resolver.ts @@ -1,9 +1,8 @@ import { computed, ref } from "vue"; import { EffectFunction, PropertyConverter, SchemaService } from './types'; -import { EditorConfig } from "../../dynamic-form"; +import { resolveSchemaWithDefaultValue } from "@farris/mobile-ui-vue/dynamic-resolver"; import { useObjectExpression } from './object-expression'; -import { ComponentSchema } from "../../designer-canvas/src/types"; -import { resolveSchemaWithDefaultValue } from "./schema-resolver"; +import { ComponentSchema, EditorConfig } from "@farris/mobile-ui-vue/common"; import appearanceConverter from './converter/appearance.converter'; import buttonsConverter from "./converter/buttons.converter"; import propertyEditorConverter from "./converter/property-editor.converter"; @@ -16,7 +15,7 @@ import gridSelectionConverter from "./converter/grid-selection.converter"; import itemsCountConverter from "./converter/items-count.converter"; import enumDataConverter from "./converter/enum-data.converter"; import formGroupLabelConverter from "./converter/form-group-label.converter"; -import { ElementPropertyConfig, PropertyEntity } from "@farris/mobile-ui-vue/property-panel"; +import { ElementPropertyConfig, PropertyEntity } from "@farris/mobile-ui-vue/common"; const propertyConfigSchemaMap = {} as Record; const propertyConverterMap = new Map([ diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/props-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/props-resolver.ts index ad0cbb6f00d7c6f004487c1525dd639670cb910e..7610eb7c00fdd5c08f0e2d88421a7c6841173653 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/props-resolver.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/props-resolver.ts @@ -1,4 +1,4 @@ -import { DesignerHostService } from './../../designer-canvas/src/composition/types'; +import { DesignerHostService } from '@farris/mobile-ui-vue/common'; import { resolveSchemaToProps } from './resolver/schema/schema-resolver'; import { DynamicResolver, EffectFunction, MapperFunction, SchemaResolverFunction } from './types'; import { RegisterContext } from '@farris/mobile-ui-vue/common'; @@ -44,6 +44,6 @@ export function getPropsResolverGenerator>( return propsObject; }, {}); return Object.assign(defaultProps, resolvedPropsValue); - } + }; }; } diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts index 022933345d633367bc973bf8d957157b052227c1..c2ca508936d7df88967ae9f2750cd9c9bdec4468 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/property-config/use-property-config-resolver.ts @@ -1,8 +1,7 @@ import { computed, ref } from "vue"; import { EffectFunction, PropertyConverter, SchemaService } from '../../types'; -import { EditorConfig } from "../../../../dynamic-form"; import { useObjectExpression } from '../../object-expression'; -import { ComponentSchema } from "../../../../designer-canvas/src/types"; +import { ComponentSchema,EditorConfig } from "@farris/mobile-ui-vue/common"; import { resolveSchemaWithDefaultValue } from "../schema/schema-resolver"; import appearanceConverter from '../../converter/appearance.converter'; import buttonsConverter from "../../converter/buttons.converter"; @@ -13,7 +12,7 @@ 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 { ElementPropertyConfig, PropertyEntity } from "@farris/mobile-ui-vue/common"; import itemsCountConverter from "../../converter/items-count.converter"; import formGroupLabelConverter from "../../converter/form-group-label.converter"; diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/schema/use-schema-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/schema/use-schema-resolver.ts index 24e765c8fbe39d8ab80dc743a4c95ae9ae42254d..da0100222788d15fe5b75538ca6f6845ade39ab1 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/schema/use-schema-resolver.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/schema/use-schema-resolver.ts @@ -1,6 +1,6 @@ import { cloneDeep, isPlainObject } from "lodash-es"; import { MapperFunction, SchemaResolverFunction } from "../../types"; -import { DesignerHostService } from "@farris/mobile-ui-vue/designer-canvas"; +import { DesignerHostService } from "@farris/mobile-ui-vue/common"; export function useSchemaResolver(schemaMap: Record, schemaResolverMap: Record) { @@ -92,6 +92,7 @@ export function useSchemaResolver(schemaMap: Record, schemaResolver } else { const mapperResult = (mapper as MapperFunction)(propKey, resolvedSchema[propKey], resolvedSchema); Object.assign(resolvedProps, mapperResult); + Object.assign(resolvedSchema, mapperResult); } } else { resolvedProps[propKey] = resolvedSchema[propKey]; diff --git a/packages/mobile-ui-vue/components/dynamic-form/src/composition/types.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/types.ts similarity index 79% rename from packages/mobile-ui-vue/components/dynamic-form/src/composition/types.ts rename to packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/types.ts index 6963eb1869721853f90e07126f2d450f0aeca887..1ad88afe1e06c6ac45803b01d679188a7b934333 100644 --- a/packages/mobile-ui-vue/components/dynamic-form/src/composition/types.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/types.ts @@ -1,4 +1,5 @@ -import { EditorConfig } from "../types"; +import { EditorConfig } from "@farris/mobile-ui-vue/common"; + export interface UseTypeResolver { @@ -9,4 +10,4 @@ export interface UseTypeResolver { getChangeFunctionName(type: string): any; getClearFunctionName(type: string): any; -} +}; diff --git a/packages/mobile-ui-vue/components/dynamic-form/src/composition/use-type-resolver-design.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/use-type-resolver-design.ts similarity index 57% rename from packages/mobile-ui-vue/components/dynamic-form/src/composition/use-type-resolver-design.ts rename to packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/use-type-resolver-design.ts index ae5bdac3857c039a0b7db6074b49f19bd299c59e..b8d7996447d268488179fc0e05ba61aa7ee56e19 100644 --- a/packages/mobile-ui-vue/components/dynamic-form/src/composition/use-type-resolver-design.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/use-type-resolver-design.ts @@ -1,18 +1,18 @@ -import { componentMap,componentPropsConverter } from '@farris/mobile-ui-vue/register-designer'; -import FInputGroupDesign from '../../../input-group/src/designer/input-group.design.component'; -import { EditorType, EditorConfig } from "../types"; +import { componentMapForDesigner,componentPropsConverterForDesigner } from '@farris/mobile-ui-vue/register-designer'; +import FInputGroupDesign from '../../../../input-group/src/designer/input-group.design.component'; import { UseTypeResolver } from "./types"; +import { EditorConfig, EditorType } from '@farris/mobile-ui-vue/common'; export function useTypeResolverDesign(): UseTypeResolver { function resolveEditorProps(type: EditorType, config: EditorConfig): Record { - const propsConverter = componentPropsConverter[type]; + const propsConverter = componentPropsConverterForDesigner[type]; const viewProps = propsConverter ? propsConverter(config) : {}; return viewProps; } function resolveEditorType(type: EditorType) { - return componentMap[type] || FInputGroupDesign; + return componentMapForDesigner[type] || FInputGroupDesign; } function getChangeFunctionName(type: EditorType) { diff --git a/packages/mobile-ui-vue/components/dynamic-form/src/composition/use-type-resolver.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/use-type-resolver.ts similarity index 94% rename from packages/mobile-ui-vue/components/dynamic-form/src/composition/use-type-resolver.ts rename to packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/use-type-resolver.ts index 2845948f29e29b93bde7cd46555c1a674e997f77..dc0fb311dd853281d6fe3daa5f9daddc029592fc 100644 --- a/packages/mobile-ui-vue/components/dynamic-form/src/composition/use-type-resolver.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/resolver/type-resolver/use-type-resolver.ts @@ -1,8 +1,8 @@ -import FInputGroup from '../../../input-group/src/input-group.component'; -import { EditorType, EditorConfig } from "../types"; +import FInputGroup from '../../../../input-group/src/input-group.component'; import { UseTypeResolver } from "./types"; -import { componentMap, propsConverterMap, registerComponents } from '../../../register'; +import { componentMap, propsConverterMap, registerComponents } from '../../../../register'; +import { EditorConfig, EditorType } from '@farris/mobile-ui-vue/common'; export function useTypeResolver(): UseTypeResolver { diff --git a/packages/mobile-ui-vue/components/dynamic-resolver/src/types.ts b/packages/mobile-ui-vue/components/dynamic-resolver/src/types.ts index c82d887f40275fab8f939ec07e52a24219feb9f5..6eeaa174453212eaf24b02ecef027aab41dadea0 100644 --- a/packages/mobile-ui-vue/components/dynamic-resolver/src/types.ts +++ b/packages/mobile-ui-vue/components/dynamic-resolver/src/types.ts @@ -1,5 +1,5 @@ -import { DesignerHostService } from "../../designer-canvas/src/composition/types"; +import { DesignerHostService } from "@farris/mobile-ui-vue/common"; export type MapperFunction = (key: string, value: any, resolvedSchema?: any) => Record; diff --git a/packages/mobile-ui-vue/components/dynamic-view/src/composition/use-render-component.ts b/packages/mobile-ui-vue/components/dynamic-view/src/composition/use-render-component.ts index 08134f1b9ea8c9f30f460af430470171c183853f..18742c42428b335b5cf17ce9d3081289102308c9 100644 --- a/packages/mobile-ui-vue/components/dynamic-view/src/composition/use-render-component.ts +++ b/packages/mobile-ui-vue/components/dynamic-view/src/composition/use-render-component.ts @@ -80,7 +80,6 @@ export function useRenderComponent(context: DynamicViewContext) { throw new Error(`Component(type=${componentType}) not found`); } const componentSchema = schemaMap[componentType]; - const resolvedComponentConfig = resolveProps(componentConfig); const slots = resolveSlots(resolvedComponentConfig, componentSchema) || {}; const defaultSlot = slots.default ? slots.default : resolveChildren(resolvedComponentConfig); diff --git a/packages/mobile-ui-vue/components/float-container/index.ts b/packages/mobile-ui-vue/components/float-container/index.ts index 0a6b3139a450c33dd39b43cea7fa34bf42f3d1d5..de739366fafda49b455d868460e4df1409367965 100644 --- a/packages/mobile-ui-vue/components/float-container/index.ts +++ b/packages/mobile-ui-vue/components/float-container/index.ts @@ -10,7 +10,7 @@ const FloatContainer = withInstall(FloatContainerInstallless); FloatContainer.register = ( componentMap: Record, propsResolverMap: Record, - configresolverMap: Record, resolverMap: Record,registerContext: RegisterContext + configresolverMap: Record, resolverMap: Record, registerContext: RegisterContext ) => { componentMap[FLOAT_CONTAINER_REGISTERED_NAME] = FloatContainer; propsResolverMap[FLOAT_CONTAINER_REGISTERED_NAME] = propsResolverGenerator(registerContext); @@ -27,4 +27,4 @@ FloatContainer.registerDesigner = ( export * from './src/float-container.props'; export { FloatContainer }; -export default FloatContainer as typeof FloatContainer & Plugin; +export default FloatContainer; diff --git a/packages/mobile-ui-vue/components/float-container/src/designer/float-container.design.component.tsx b/packages/mobile-ui-vue/components/float-container/src/designer/float-container.design.component.tsx index aa144d9a4b00c310123d4dd7eb90e410bbcfed4f..9188260bc53fe2801b6b1bf536db575c7004b7fe 100644 --- a/packages/mobile-ui-vue/components/float-container/src/designer/float-container.design.component.tsx +++ b/packages/mobile-ui-vue/components/float-container/src/designer/float-container.design.component.tsx @@ -1,7 +1,7 @@ import { SetupContext, defineComponent, inject, onMounted, ref } from 'vue'; import { FloatContainerProps, floatContainerProps } from '../float-container.props'; import { useDesignerRules } from './use-designer-rules'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common';; export default defineComponent({ name: 'FmFloatContainerDesign', diff --git a/packages/mobile-ui-vue/components/float-container/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/float-container/src/designer/use-designer-rules.ts index 4de48fa61ffdb42b416c08f8e61110fdb662a9bd..635dc2a1615b5926e535458d6f839180de827d09 100644 --- a/packages/mobile-ui-vue/components/float-container/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/float-container/src/designer/use-designer-rules.ts @@ -1,22 +1,21 @@ - /** - * 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. - */ +* 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 { ref } from "vue"; -import { ComponentSchema, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { DraggingResolveContext } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; +import { DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { DraggingResolveContext } from "@farris/mobile-ui-vue/common"; import { FloatContainerProperty } from "../property-config/float-container.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -26,33 +25,13 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const schema = ref(designItemContext.schema); function canAccepts(draggingContext: DraggingResolveContext): boolean { - const acceptableControlTypes = [ - 'navbar', - 'content-container', - 'html-template', - ]; - const uniqueControlTypes = [ - 'navbar', - ]; - const { sourceType, parentComponentInstance } = draggingContext; - if (!acceptableControlTypes.includes(sourceType)) { - return false; - } - const shouldBeUnique = uniqueControlTypes.includes(sourceType); - if (shouldBeUnique) { - const parentComponent = parentComponentInstance?.parent?.value; - const contents: any[] = parentComponent?.contents || []; - const hasSameTypeControl = !!contents.find((content) => content.type === sourceType); - if (hasSameTypeControl) { - return false; - } - } return true; } function checkCanMoveComponent() { return false; } + function checkCanDeleteComponent() { return true; } @@ -84,7 +63,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } return style; } - + /** * 获取属性配置 */ @@ -92,7 +71,11 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const componentProp = new FloatContainerProperty(componentId, designerHostService); const { schema } = designItemContext; return componentProp.getPropertyConfig(schema); - } + } + + function getDraggableDesignItemElement(context: DesignerItemContext) { + return context.designerItemElementRef; + } return { canAccepts, @@ -101,6 +84,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe hideNestedPaddingInDesginerView, getDesignerClass, getStyles, - getPropsConfig + getPropsConfig, + getDraggableDesignItemElement, }; } diff --git a/packages/mobile-ui-vue/components/float-container/src/float-container.component.tsx b/packages/mobile-ui-vue/components/float-container/src/float-container.component.tsx index c2718fcdc86a3a178acfbc0359c05872c88b9f73..2c0f2875bed69a42cddafe3d579ca65cfc901549 100644 --- a/packages/mobile-ui-vue/components/float-container/src/float-container.component.tsx +++ b/packages/mobile-ui-vue/components/float-container/src/float-container.component.tsx @@ -1,21 +1,16 @@ -import { SetupContext, defineComponent, computed } from 'vue'; -import { useBem } from '@farris/mobile-ui-vue/common/index'; +import { defineComponent } from 'vue'; +import { useBem } from '@farris/mobile-ui-vue/common'; import { FLOAT_CONTAINER_NAME, floatContainerProps, FloatContainerProps } from './float-container.props'; export default defineComponent({ name: FLOAT_CONTAINER_NAME, props: floatContainerProps, emits: [], - setup(props: FloatContainerProps, context: SetupContext) { + setup(props: FloatContainerProps, context) { const { bem } = useBem(FLOAT_CONTAINER_NAME); - const containerClass = computed(() => ({ - [bem()]: true, - [props.customClass || '']: true, - })); - return () => ( -
+
{context.slots.default?.()}
); diff --git a/packages/mobile-ui-vue/components/float-container/src/float-container.props.ts b/packages/mobile-ui-vue/components/float-container/src/float-container.props.ts index c5da6f3797f321b4fc649d7d07c57954adc00756..6981db95fe679de9c58ce83596c2e02f11b511ba 100644 --- a/packages/mobile-ui-vue/components/float-container/src/float-container.props.ts +++ b/packages/mobile-ui-vue/components/float-container/src/float-container.props.ts @@ -8,6 +8,7 @@ export const FLOAT_CONTAINER_NAME = 'fm-float-container'; export const floatContainerProps = { customClass: { type: String, default: '' }, + customStyle: { type: String, default: '' }, }; export type FloatContainerProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/float-container/src/float-container.scss b/packages/mobile-ui-vue/components/float-container/src/float-container.scss index ccd9b255496b5ef23417a3c1e85d2d999226c2e7..5df63dfc941142fb3672da48ef91dfe439a119f9 100644 --- a/packages/mobile-ui-vue/components/float-container/src/float-container.scss +++ b/packages/mobile-ui-vue/components/float-container/src/float-container.scss @@ -1,5 +1,3 @@ .fm-float-container { position: absolute !important; - bottom: 60px; - right: 30px; } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/float-container/src/property-config/float-container.property-config.ts b/packages/mobile-ui-vue/components/float-container/src/property-config/float-container.property-config.ts index ba88af4a427d0015bf35019d8130a669f1131e1f..99bd9446e7965ce48059f296b5eb7d4e11741493 100644 --- a/packages/mobile-ui-vue/components/float-container/src/property-config/float-container.property-config.ts +++ b/packages/mobile-ui-vue/components/float-container/src/property-config/float-container.property-config.ts @@ -1,9 +1,11 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { ContainerBaseProperty } from "@farris/mobile-ui-vue/common"; + +export class FloatContainerProperty extends ContainerBaseProperty { -export class FloatContainerProperty extends BaseControlProperty { constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); } + public getPropertyConfig(propertyData: any) { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); @@ -12,4 +14,9 @@ export class FloatContainerProperty extends BaseControlProperty { return this.propertyConfig; } + + protected getAppearanceConfig(propertyData: any) { + const properties = this.getAppearanceProperties(["position"], propertyData); + return super.getAppearanceConfig(propertyData, properties); + } } diff --git a/packages/mobile-ui-vue/components/float-container/src/schema/float-container.schema.json b/packages/mobile-ui-vue/components/float-container/src/schema/float-container.schema.json index 473930001e2056457376b20a7405e911aeb73dd0..6189e397e384ade3ad8452b80e2ef18adf74870a 100644 --- a/packages/mobile-ui-vue/components/float-container/src/schema/float-container.schema.json +++ b/packages/mobile-ui-vue/components/float-container/src/schema/float-container.schema.json @@ -31,11 +31,30 @@ "description": "", "type": "array", "default": [] + }, + "position": { + "description": "", + "type": "object", + "default": { + "right": 30, + "bottom": 60 + } + }, + "size": { + "description": "", + "type": "object", + "default": {} + }, + "padding": { + "description": "", + "type": "object", + "default": {} } }, "required": [ "id", "type", - "contents" + "contents", + "position" ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/float-container/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/float-container/src/schema/schema-mapper.ts index 90dc1493e556aff6b291ceb243c0d3e5ab96dc91..e7c0e0a7b6bb7883abb5c60d5ff5719be331fb38 100644 --- a/packages/mobile-ui-vue/components/float-container/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/float-container/src/schema/schema-mapper.ts @@ -1,5 +1,8 @@ -import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver'; +import { resolveAppearance, resolvePosition, resolveSize, resolvePadding, MapperFunction } from '../../../dynamic-resolver'; export const schemaMapper = new Map([ - ['appearance', resolveAppearance] + ['appearance', resolveAppearance], + ['position', resolvePosition], + ['size', resolveSize], + ['padding', resolvePadding], ]); diff --git a/packages/mobile-ui-vue/components/form-item/src/designer/form-item-use-designer-rules.ts b/packages/mobile-ui-vue/components/form-item/src/designer/form-item-use-designer-rules.ts index 0c2b13181ad0c38332593435436ce532446e58ea..6a9344e98d4b39267438febafef14c44e12211fc 100644 --- a/packages/mobile-ui-vue/components/form-item/src/designer/form-item-use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/form-item/src/designer/form-item-use-designer-rules.ts @@ -1,4 +1,4 @@ -import { DesignerItemContext, DesignerHostService, UseDesignerRules, DesignerComponentInstance, ComponentSchema } from "@farris/mobile-ui-vue/designer-canvas"; +import { DesignerItemContext, DesignerHostService, UseDesignerRules, DesignerComponentInstance, ComponentSchema } from "@farris/mobile-ui-vue/common"; import { FormGroupProperty } from "../property-config/form-group.property-config"; export function useDesignerRulesForFormItem(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { diff --git a/packages/mobile-ui-vue/components/form-item/src/designer/form-item.design.component.tsx b/packages/mobile-ui-vue/components/form-item/src/designer/form-item.design.component.tsx index 4130da830904b5229c3a0c8873c09632b9186cbf..6164f934fb19e101267fa3a782ae966c14f04599 100644 --- a/packages/mobile-ui-vue/components/form-item/src/designer/form-item.design.component.tsx +++ b/packages/mobile-ui-vue/components/form-item/src/designer/form-item.design.component.tsx @@ -1,38 +1,20 @@ -/** - * 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, inject, onMounted, ref, SetupContext, watch } from 'vue'; -import { useBem } from '@farris/mobile-ui-vue/common'; -import FmCell from '@farris/mobile-ui-vue/cell'; -import { FORM_ITEM_NAME, FormItemProps, formItemProps } from '../form-item.props'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; -import { propertyConfigSchemaMapForDesigner } from '@farris/mobile-ui-vue/dynamic-resolver'; +import { computed, defineComponent, inject, onMounted, PropType, ref } from 'vue'; +import { DesignerHostService, DesignerItemContext, extractProperties, useDesignerComponent } from '@farris/mobile-ui-vue/common';; +import { propertyConfigSchemaMapForDesigner, useTypeResolverDesign } from '@farris/mobile-ui-vue/dynamic-resolver'; import { useDesignerRulesForFormItem } from './form-item-use-designer-rules'; -import { useTypeResolverDesign } from '@farris/mobile-ui-vue/dynamic-form/src/composition/use-type-resolver-design'; +import { formItemProps } from '../form-item.props'; +import FormItem from '../form-item.component'; export default defineComponent({ name: 'FmFormItemDesign', - props: formItemProps, + inheritAttrs: false, + props: { + ...extractProperties(formItemProps, ['label', 'labelAlign', 'labelWidth']), + editor: { type: Object as PropType, default: ()=>({}) } + }, + setup(props, context) { + const { slots } = context; - setup(props: FormItemProps, context: SetupContext) { - const { slots, expose } = context; - - const labelAlign = ref(props.labelAlign); - const shouldShowRequired = ref(props.required); - const { bem } = useBem(FORM_ITEM_NAME); - const editor = ref(props.editor); const { resolveEditorProps, resolveEditorType } = useTypeResolverDesign(); const elementRef = ref(); @@ -43,53 +25,6 @@ export default defineComponent({ const designerRulesComposition = useDesignerRulesForFormItem(designItemContext, designerHostService); const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); - - const renderLabel = () => { - if (slots.label) { - return [slots.label()]; - } - return ; - }; - - const contentClass = computed(() => { - let contentAlign = props.contentAlign || 'right'; - if (labelAlign.value === 'top') { - contentAlign = 'left'; - } - return { - [bem('content')]: true, - [bem('content', contentAlign)]: true - }; - }); - const renderContent = () => { - const renderConditionEditor = computed(() => { - const editorType = editor.value.type || 'input-group'; - const Component = resolveEditorType(editorType); - const editorProps = resolveEditorProps(editorType, editor.value); - return () => ; - }); - - - return
{renderConditionEditor.value()}
; - }; - - - const formItemClass = computed(() => { - const direction = labelAlign.value === 'top' ? 'vertical' : 'horizontal'; - return { - [bem()]: true, - [bem('', direction)]: true - }; - }); - - const labelClass = computed(() => { - const labelClasses = [bem('label'), bem('label', labelAlign.value)]; - if (shouldShowRequired.value === true) { - labelClasses.push(bem('label', 'required')); - } - return labelClasses.join(' '); - }); - onMounted(() => { elementRef.value.componentInstance = componentInstance; @@ -103,26 +38,38 @@ export default defineComponent({ }; }); - context.expose(componentInstance.value); - return () => { - const innerSlots = { - title: renderLabel, - default: renderContent, - leftIcon: slots.leftIcon, - rightIcon: slots.rightIcon, - extra: slots.extra - }; + const editorProps = computed(()=>{ + const editorType = props.editor.type || 'input-group'; + return resolveEditorProps(editorType, props.editor); + }); + + const renderConditionEditor = () => { + const editorType = props.editor.type || 'input-group'; + const Component = resolveEditorType(editorType); + return ; + }; - return ( - - - ); + const innerSlots = { + ...slots, + default: renderConditionEditor, }; + const formItemProps = computed(()=>{ + return { + ...props, + required: editorProps.value.required + }; + }); + + return () => ( + + + ); + } }); diff --git a/packages/mobile-ui-vue/components/form-item/src/designer/response-form-use-designer-rules.ts b/packages/mobile-ui-vue/components/form-item/src/designer/response-form-use-designer-rules.ts index a3d2c40fb6fd35d090b531197c06b6a38ec61088..f018052888428dc1a17a03236f34f700a3808d2d 100644 --- a/packages/mobile-ui-vue/components/form-item/src/designer/response-form-use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/form-item/src/designer/response-form-use-designer-rules.ts @@ -1,5 +1,5 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { DraggingResolveContext } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; +import { ComponentSchema, DesignerComponentInstance, DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { DraggingResolveContext } from "@farris/mobile-ui-vue/common"; import { getSchemaByTypeForDesigner } from "@farris/mobile-ui-vue/dynamic-resolver"; import { ref } from "vue"; import { ResponseFormProperty } from "../property-config/response-form.property-config"; @@ -58,14 +58,14 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe // 控件若有绑定信息,则根据绑定信息创建控件 if (resolveContext.bindingSourceContext?.entityFieldNode) { const controlCreatorUtils = designerHostService?.controlCreatorUtils; - formGroupElementSchema = controlCreatorUtils.setFormFieldProperty(resolveContext.bindingSourceContext?.entityFieldNode, componentSchema.type); + formGroupElementSchema = controlCreatorUtils.setFormFieldProperty(resolveContext.bindingSourceContext?.entityFieldNode, componentSchema?.type); } else { formGroupElementSchema = getSchemaByTypeForDesigner('form-group') as ComponentSchema; formGroupElementSchema.editor = componentSchema; formGroupElementSchema.label = label; } - syncFieldToViewModel(resolveContext, componentSchema.type); + syncFieldToViewModel(resolveContext, componentSchema?.type); return formGroupElementSchema; } diff --git a/packages/mobile-ui-vue/components/form-item/src/designer/response-form.design.component.tsx b/packages/mobile-ui-vue/components/form-item/src/designer/response-form.design.component.tsx index 117799bedbcd0835fa36e4c2ddd1e67ccd2ae2f8..fdfcaacb9327ec94b37b2269456e14754ce14b3c 100644 --- a/packages/mobile-ui-vue/components/form-item/src/designer/response-form.design.component.tsx +++ b/packages/mobile-ui-vue/components/form-item/src/designer/response-form.design.component.tsx @@ -1,46 +1,50 @@ import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; import { useDesignerRules } from './response-form-use-designer-rules'; -import { FormProps, formProps } from '../form.props'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { formProps } from '../form.props'; +import Form from '../form.component'; export default defineComponent({ - name: 'FResponseFormDesign', - props: formProps, - emits: [], - setup(props: FormProps, context) { - const elementRef = ref(); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); - const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); - - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); - - context.expose(componentInstance.value); - - const responseFormClass = computed(() => { - const customClassArray = []; - const classObject = { - 'drag-container': true - } as Record; - customClassArray.reduce((result: Record, classString: string) => { - result[classString] = true; - return result; - }, classObject); - return classObject; - }); - - - return () => { - return ( - -
- {context.slots.default && context.slots.default()} -
- - ); - }; - } + name: 'FResponseFormDesign', + inheritAttrs: false, + props: extractProperties(formProps, ['labelAlign', 'labelWidth']), + setup(props, context) { + const elementRef = ref(); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + + context.expose(componentInstance.value); + + const formProps = computed(() => { + return { + ...props + }; + }); + + return () => ( +
+ ); + } }); diff --git a/packages/mobile-ui-vue/components/form-item/src/form-item.component.tsx b/packages/mobile-ui-vue/components/form-item/src/form-item.component.tsx index 4dfb55249a2d814a627a1a494c58829b571c570b..1627034d57f70c50aacefd5ad82d2e5928eaecd6 100644 --- a/packages/mobile-ui-vue/components/form-item/src/form-item.component.tsx +++ b/packages/mobile-ui-vue/components/form-item/src/form-item.component.tsx @@ -67,7 +67,7 @@ export default defineComponent({ }; }); const renderContent = () => { - return
{slots.default()}
; + return
{slots.default && slots.default()}
; }; const errorMessageClass = computed(() => { diff --git a/packages/mobile-ui-vue/components/form-item/src/form-item.props.ts b/packages/mobile-ui-vue/components/form-item/src/form-item.props.ts index 7c8c29a4f9a5111508d596715ba0a558af39c943..42952726e032549081340deb02599c3e0b9d3c42 100644 --- a/packages/mobile-ui-vue/components/form-item/src/form-item.props.ts +++ b/packages/mobile-ui-vue/components/form-item/src/form-item.props.ts @@ -35,9 +35,7 @@ export const formItemProps = { showErrorMessage: { type: Boolean, default: undefined }, /** 错误信息位置 */ - errorMessageAlign: { type: String as PropType, deafult: undefined }, - - editor: { type: Object as PropType, default: {} }, + errorMessageAlign: { type: String as PropType, deafult: undefined } }; diff --git a/packages/mobile-ui-vue/components/form-item/src/property-config/form-group.property-config.ts b/packages/mobile-ui-vue/components/form-item/src/property-config/form-group.property-config.ts index 1cb9cfe91a5a4089406d5aa09fd452c3f80f6dd9..a09b668a00e703074b130b1c31e39ae0cc386e09 100644 --- a/packages/mobile-ui-vue/components/form-item/src/property-config/form-group.property-config.ts +++ b/packages/mobile-ui-vue/components/form-item/src/property-config/form-group.property-config.ts @@ -1,5 +1,5 @@ -import { DesignerComponentInstance } from "@farris/mobile-ui-vue/designer-canvas"; -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { DesignerComponentInstance } from "@farris/mobile-ui-vue/common"; +import { BaseControlProperty } from "@farris/mobile-ui-vue/common"; export class FormGroupProperty extends BaseControlProperty { constructor(componentId: string, designerHostService: any) { diff --git a/packages/mobile-ui-vue/components/form-item/src/property-config/response-form.property-config.ts b/packages/mobile-ui-vue/components/form-item/src/property-config/response-form.property-config.ts index 5f1baea84114b12d5562bf9c219d89e18485976c..191cb96397eda9d2706f51e0404a6cf050525fa1 100644 --- a/packages/mobile-ui-vue/components/form-item/src/property-config/response-form.property-config.ts +++ b/packages/mobile-ui-vue/components/form-item/src/property-config/response-form.property-config.ts @@ -1,16 +1,44 @@ -import { DesignerComponentInstance } from "@farris/mobile-ui-vue/designer-canvas"; -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { DesignerComponentInstance } from '@farris/mobile-ui-vue/common'; +import { BaseControlProperty } from '@farris/mobile-ui-vue/common'; export class ResponseFormProperty extends BaseControlProperty { - constructor(componentId: string, designerHostService: any) { - super(componentId, designerHostService); - } - public getPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance) { - // 基本信息 - this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); - // 外观 - this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + public getPropertyConfig(propertyData: any, componentInstance: DesignerComponentInstance) { + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + // 外观 + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); - return this.propertyConfig; - } + this.propertyConfig.categories['label'] = this.getLabelProperties(propertyData); + + return this.propertyConfig; + } + + getLabelProperties(propertyData: any) { + return { + description: '标签配置', + title: '标签', + properties: { + labelAlign: { + title: '标签排列方式', + type: 'string', + editor: { + type: 'combo-list', + data: [ + { id: 'left', name: '左侧' }, + { id: 'top', name: '上侧' } + ] + }, + refreshPanelAfterChanged: true + }, + labelWidth: { + title: "标签宽度", + type: "string", + visible: propertyData.labelAlign !== 'top' + } + } + }; + } } diff --git a/packages/mobile-ui-vue/components/form-item/src/schema/form-item.schema.json b/packages/mobile-ui-vue/components/form-item/src/schema/form-item.schema.json index 82f34c27ccf448a6c0bab0dc8eee66cc390b2758..bc07415d539e06c2503c4bb65df2ab1488c7be08 100644 --- a/packages/mobile-ui-vue/components/form-item/src/schema/form-item.schema.json +++ b/packages/mobile-ui-vue/components/form-item/src/schema/form-item.schema.json @@ -43,7 +43,8 @@ }, "labelAlign": { "description": "标签排列方式", - "type": "string" + "type": "string", + "default": "left" }, "editor": { "description": "编辑器", diff --git a/packages/mobile-ui-vue/components/form-item/src/schema/form.schema.json b/packages/mobile-ui-vue/components/form-item/src/schema/form.schema.json index 1052a2fc0346c68ce17dc9fe9980600843d5cd5b..c1f7489be07d500d9c0aba5f95389c0d44e8e587 100644 --- a/packages/mobile-ui-vue/components/form-item/src/schema/form.schema.json +++ b/packages/mobile-ui-vue/components/form-item/src/schema/form.schema.json @@ -27,6 +27,15 @@ }, "default": {} }, + "labelWidth": { + "description": "标签宽度", + "type": "number" + }, + "labelAlign": { + "description": "标签排列方式", + "type": "string", + "default": "left" + }, "contents": { "description": "", "type": "array", diff --git a/packages/mobile-ui-vue/components/form/index.ts b/packages/mobile-ui-vue/components/form/index.ts index b91f4f8ab504128ee10444cb60901b63ec843091..8df9ae7b199794fa71cff4dedfb9b4a91ca26bc0 100644 --- a/packages/mobile-ui-vue/components/form/index.ts +++ b/packages/mobile-ui-vue/components/form/index.ts @@ -29,4 +29,4 @@ withRegisterDesigner(Form, { name: FORM_REGISTERED_NAME, propsResolverGenerator, export * from '@farris/mobile-ui-vue/form-item/src/form.props'; export { Form }; -export default Form as typeof Form & Plugin; +export default Form; diff --git a/packages/mobile-ui-vue/components/icon/src/icon.component.tsx b/packages/mobile-ui-vue/components/icon/src/icon.component.tsx index c0981a693204712537692722fd1d96c740d8eb6c..942c31ef0eb7a62b337a3b1471e0495158afebe2 100644 --- a/packages/mobile-ui-vue/components/icon/src/icon.component.tsx +++ b/packages/mobile-ui-vue/components/icon/src/icon.component.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { defineComponent, computed, CSSProperties, ComputedRef, SetupContext } from 'vue'; +import { defineComponent, computed, CSSProperties } from 'vue'; import { addUnit } from '@farris/mobile-ui-vue/common'; import { IconProps, iconProps } from './icon.props'; @@ -22,22 +22,25 @@ export default defineComponent({ name: 'FmIcon', props: iconProps, emits: ['click'], - setup(props: IconProps, context: SetupContext) { + setup(props: IconProps, context) { const { emit } = context; - const iconClass: ComputedRef = computed(() => { + + const iconClass = computed(() => { const { classPrefix, name } = props; return [classPrefix, name ? `${classPrefix}-${name}` : '']; }); - const iconStyle: ComputedRef = computed(() => { - return { color: props.color, 'font-size': props.size ? addUnit(props.size) : '' }; - }); + const iconStyle = computed(() => ({ + color: props.color, + 'font-size': props.size ? addUnit(props.size) : '', + })); + + function onClick(event: MouseEvent): void { + emit('click', event); + } return () => ( - emit('click', event)}> + ); } }); diff --git a/packages/mobile-ui-vue/components/index.scss b/packages/mobile-ui-vue/components/index.scss index 5d371b8e9ab82891968130c6048b599f1bc2a310..67be30e79038df79466a14b6469e40551e9695b8 100644 --- a/packages/mobile-ui-vue/components/index.scss +++ b/packages/mobile-ui-vue/components/index.scss @@ -2,6 +2,7 @@ @use './button/src/button.scss'; @use './button-edit/src/button-edit.scss'; @use './button-group/src/button-group.scss'; +@use './card/src/card.scss'; @use './cell/src/cell.scss'; @use './checkbox-group/src/checkbox-group.scss'; @use './checkbox/src/checkbox.scss'; diff --git a/packages/mobile-ui-vue/components/index.ts b/packages/mobile-ui-vue/components/index.ts index 57ca6309e3deeee7f76bc5aa7951255231b5feba..80719d79e13739e6995ff22e6be315386e7e299c 100644 --- a/packages/mobile-ui-vue/components/index.ts +++ b/packages/mobile-ui-vue/components/index.ts @@ -2,6 +2,7 @@ import { App, Plugin } from 'vue'; import Button from './button'; import ButtonEdit from './button-edit'; import ButtonGroup from './button-group'; +import Card from './card'; import Cell from './cell'; import Icon from './icon'; import InputGroup from './input-group'; @@ -32,6 +33,7 @@ import Loading from './loading'; import PullRefresh from './pull-refresh'; import List from './list'; import ListView from './list-view'; +import Lookup from './lookup'; import TabBar from "./tab-bar"; import SwipeCell from './swipe-cell'; import ActionSheet from './action-sheet'; @@ -48,19 +50,19 @@ import FloatContainer from './float-container'; import Filter from './filter'; import * as Utils from './common/src/utils'; -import Modal from './modal'; -import DynamicView from './dynamic-view'; -import FDesignerCanvas from './designer-canvas'; +export type { LoadingOptions } from './loading'; +import DynamicView from './dynamic-view'; export * from './register'; export * from './designer'; -export { schemaMap } from './dynamic-resolver'; export { type DynamicViewContext, DYNAMIC_VIEW_CONTEXT, DefaultDynamicViewContext } from './dynamic-view'; +export { LOOKUP_HTTP_SERVICE_TOKEN } from './lookup'; const components = [ Button, ButtonEdit, ButtonGroup, + Card, Cell, Checkbox, CheckboxGroup, @@ -75,9 +77,6 @@ const components = [ Textarea, Navbar, Switch, - Toast, - Notify, - Dialog, Radio, RadioGroup, Rate, @@ -92,17 +91,17 @@ const components = [ TimePicker, TimePickerPanel, TabBar, - Loading, PullRefresh, List, ListView, + Lookup, SwipeCell, ActionSheet, Tabs, Tab, Tag, Overlay, - + PageContainer, PageBodyContainer, PageHeaderContainer, @@ -111,9 +110,12 @@ const components = [ ContentContainer, FloatContainer, - Modal, - DynamicView, - FDesignerCanvas + Toast, + Notify, + Loading, + Dialog, + + DynamicView ]; const install = (app: App): void => { @@ -126,6 +128,7 @@ export { Button, ButtonEdit, ButtonGroup, + Card, Cell, Checker, Checkbox, @@ -161,6 +164,7 @@ export { PullRefresh, List, ListView, + Lookup, SwipeCell, ActionSheet, Tabs, @@ -176,9 +180,7 @@ export { FloatContainer, Utils, - Modal, DynamicView, - FDesignerCanvas }; export default { diff --git a/packages/mobile-ui-vue/components/input-group/index.ts b/packages/mobile-ui-vue/components/input-group/index.ts index e7494d76fdb338d3f11647902a7b2dd00893dc67..bead9f222b0d909a6f65a097ed87b67580e56135 100644 --- a/packages/mobile-ui-vue/components/input-group/index.ts +++ b/packages/mobile-ui-vue/components/input-group/index.ts @@ -12,5 +12,6 @@ withRegisterDesigner(InputGroup, { name: INPUT_GROUP_REGISTERED_NAME, propsResol export * from './src/input-group.props'; export * from './src/types'; + export { InputGroup }; export default InputGroup; diff --git a/packages/mobile-ui-vue/components/input-group/src/composition/use-number.ts b/packages/mobile-ui-vue/components/input-group/src/composition/use-number.ts index 98ff4314120d3f4415f1328e7abb4a335d56eacc..e6020bade91711af09a269c3f0eabe99540bbb3a 100644 --- a/packages/mobile-ui-vue/components/input-group/src/composition/use-number.ts +++ b/packages/mobile-ui-vue/components/input-group/src/composition/use-number.ts @@ -16,13 +16,13 @@ import { InputProps } from '../input-group.props'; import { formatToNumber, isDef, parseFloat } from '@farris/mobile-ui-vue/common'; import { Instance } from './types'; -import { InputTypeMap, InputUpdateOn, InputUpdateOnMap } from '../types'; +import { InputType, InputUpdateOn, InputUpdateOnMap } from '../types'; export function useNumber(props: InputProps): Instance { - const allowDot = props.type === InputTypeMap.number; + const allowDot = props.type === InputType.number; const updateOn: InputUpdateOn = InputUpdateOnMap.blur; const placeholder = '请输入数字'; - const inputType = allowDot ? InputTypeMap.text : InputTypeMap.number; + const inputType = allowDot ? InputType.text : InputType.number; const inputMode = allowDot ? 'decimal' : 'numeric'; const formatValue = (value: string | number) => { diff --git a/packages/mobile-ui-vue/components/input-group/src/composition/use-textarea.ts b/packages/mobile-ui-vue/components/input-group/src/composition/use-textarea.ts index 62f5441e55d20bd1ac2d062df55ad3953713fd95..b26850af5fbcf3487ff3dad4a6309c9dfb1e9d10 100644 --- a/packages/mobile-ui-vue/components/input-group/src/composition/use-textarea.ts +++ b/packages/mobile-ui-vue/components/input-group/src/composition/use-textarea.ts @@ -1,33 +1,18 @@ -/** - * 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 { Ref, onMounted, unref } from 'vue'; import { addUnit } from '@farris/mobile-ui-vue/common'; import { InputProps } from '../input-group.props'; import { Instance } from './types'; import { useCommon } from './use-common'; -import { InputTypeMap } from '../types'; +import { InputType } from '../types'; export function useTextarea(props: InputProps, inputRef: Ref): Instance { const { placeholder, updateOn, inputMode, formatValue, getModelValue } = useCommon(props); - const inputType = InputTypeMap.textarea; + const inputType = InputType.textarea; const adjustSize = () => { const input = unref(inputRef); - if (!(props.type === InputTypeMap.textarea && props.autoHeight) || !input) { + if (!(props.type === InputType.textarea && props.autoHeight) || !input) { return; } diff --git a/packages/mobile-ui-vue/components/input-group/src/designer/input-group.design.component.tsx b/packages/mobile-ui-vue/components/input-group/src/designer/input-group.design.component.tsx index 77a178a4621ec85a787be5daff4cdbb0916b625d..052a1a13397d38b73b691940c090e6e21e07b933 100644 --- a/packages/mobile-ui-vue/components/input-group/src/designer/input-group.design.component.tsx +++ b/packages/mobile-ui-vue/components/input-group/src/designer/input-group.design.component.tsx @@ -1,4 +1,3 @@ - /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -14,39 +13,57 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { computed, defineComponent, inject, onMounted, readonly, ref, SetupContext } from 'vue'; -import { InputGroup, InputProps, inputProps } from '../..'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { computed, defineComponent, inject, onMounted, ref, SetupContext } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; import { useInputGroupDesignerRules } from './use-designer-rules'; +import { inputProps } from '../input-group.props'; +import InputGroup from '../input-group.component'; export default defineComponent({ - name: 'FmInputGroupDesign', - props: inputProps, - emits: [] as (string[] & ThisType) | undefined, - setup(props: InputProps, context: SetupContext) { - const elementRef = ref(); - const designerHostService = inject('designer-host-service'); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerRulesComposition = useInputGroupDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + name: 'FmInputGroupDesign', + inheritAttrs: false, + props: extractProperties(inputProps, ['placeholder']), + setup(props, context) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerRulesComposition = useInputGroupDesignerRules( + designItemContext, + designerHostService + ); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - const inputGroupProps = { - editable: false, - }; + context.expose(componentInstance.value); + + const inputProps = computed(()=>{ + return { + ...props, + editable: false + }; + }); - context.expose(componentInstance.value); - - return () => { - return ( - - ); - }; - } + return () => { + return ( + + ); + }; + } }); diff --git a/packages/mobile-ui-vue/components/input-group/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/input-group/src/designer/use-designer-rules.ts index 2d3dd89c385b980e964b103bd92b7a70c69c2347..9de599c140bd3205e8759f271a8c7c40a93be7c4 100644 --- a/packages/mobile-ui-vue/components/input-group/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/input-group/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { InputGroupProperty } from "../property-config/input-group.property-config"; export function useInputGroupDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { diff --git a/packages/mobile-ui-vue/components/input-group/src/input-group.component.tsx b/packages/mobile-ui-vue/components/input-group/src/input-group.component.tsx index 06291c23313e4b6db06d78fb67f9c0a4ac089d8e..5b4cd229ec435b93a0a49a9095f0bd542cfcd3a4 100644 --- a/packages/mobile-ui-vue/components/input-group/src/input-group.component.tsx +++ b/packages/mobile-ui-vue/components/input-group/src/input-group.component.tsx @@ -19,7 +19,7 @@ import { preventDefault, stopPropagation, useBem } from '@farris/mobile-ui-vue/c import { Icon } from '@farris/mobile-ui-vue/icon'; import { INPUT_NAME, InputProps, inputProps } from './input-group.props'; import { useInputState, useInputEvent, useInputExpose } from './composition'; -import { InputTypeMap } from './types'; +import { InputType } from './types'; export default defineComponent({ name: INPUT_NAME, @@ -60,7 +60,7 @@ export default defineComponent({ }; }); - const isTextArea = computed(() => props.type === InputTypeMap.textarea); + const isTextArea = computed(() => props.type === InputType.textarea); const renderControl = () => { return isTextArea.value ? ( @@ -108,10 +108,10 @@ export default defineComponent({ }; const renderWordLimit = () => - props.showWordLimit && - props.maxLength && ( + props.showWordLimit && (
- {String(innerValue.value).length}/{props.maxLength} + {String(innerValue.value).length} + { props.maxLength && /{props.maxLength} }
); diff --git a/packages/mobile-ui-vue/components/input-group/src/input-group.props.ts b/packages/mobile-ui-vue/components/input-group/src/input-group.props.ts index 13a26c3e1d64570454e37a03af9389da8104db64..6db6873bb24969641bd4dfb24d6e67886a6d1a89 100644 --- a/packages/mobile-ui-vue/components/input-group/src/input-group.props.ts +++ b/packages/mobile-ui-vue/components/input-group/src/input-group.props.ts @@ -1,5 +1,5 @@ import { ExtractPropTypes, PropType } from 'vue'; -import { InputTextAlign, InputTextAlignMap, InputType, InputTypeMap, InputUpdateOn } from "./types"; +import { InputTextAlign, InputTextAlignMap, InputType, InputUpdateOn } from "./types"; import { getPropsResolverGenerator } from '../../dynamic-resolver'; import inputGroupSchema from './schema/input-group.schema.json'; import { schemaMapper } from './schema/schema-mapper'; @@ -12,7 +12,7 @@ export const inputCommonProps = { name: { type: String, default: '' }, - type: { type: String as PropType, default: InputTypeMap.text }, + type: { type: String as PropType, default: InputType.text }, placeholder: { type: String, default: '' }, diff --git a/packages/mobile-ui-vue/components/input-group/src/property-config/converter/input.converter.ts b/packages/mobile-ui-vue/components/input-group/src/property-config/converter/input.converter.ts index 2e66e4b8e92cf480cece47f77b36bf69fffd274c..183b249eac93868f063993402382e2ed7c7de4b0 100644 --- a/packages/mobile-ui-vue/components/input-group/src/property-config/converter/input.converter.ts +++ b/packages/mobile-ui-vue/components/input-group/src/property-config/converter/input.converter.ts @@ -1,4 +1,4 @@ -import { ComponentSchema } from "../../../../designer-canvas/src/types"; +import { ComponentSchema } from "@farris/mobile-ui-vue/common"; import { PropertyConverter, SchemaService } from "../../../../dynamic-resolver/src/types"; const inputFormatValidationTypes = [ diff --git a/packages/mobile-ui-vue/components/input-group/src/property-config/input-group.property-config.ts b/packages/mobile-ui-vue/components/input-group/src/property-config/input-group.property-config.ts index 33eb86d5708e3edc80b04287f9cf18846c810ad9..27d9818b323ed66272e04daf5618c76f91cbd69c 100644 --- a/packages/mobile-ui-vue/components/input-group/src/property-config/input-group.property-config.ts +++ b/packages/mobile-ui-vue/components/input-group/src/property-config/input-group.property-config.ts @@ -1,5 +1,4 @@ -import { InputBaseProperty } from '@farris/mobile-ui-vue/common/src/entity/input-base-property'; -import { DesignerComponentInstance } from '@farris/mobile-ui-vue/designer-canvas'; +import { DesignerComponentInstance, InputBaseProperty } from '@farris/mobile-ui-vue/common'; import { typeConverter, commonConverter, @@ -26,12 +25,11 @@ export class InputGroupProperty extends InputBaseProperty { { maxLength: { description: '文本字数最大长度', - title: '最大长度', + title: '最大字数', type: 'number', editor: { nullable: true, - min: 0, - useThousands: false + min: 0 } } }, diff --git a/packages/mobile-ui-vue/components/input-group/src/schema/input-group.schema.json b/packages/mobile-ui-vue/components/input-group/src/schema/input-group.schema.json index 658e623f09444cf1cc730960f9338c5b28061872..40b9a9e53c8498592c0f128f2198eca0778fe632 100644 --- a/packages/mobile-ui-vue/components/input-group/src/schema/input-group.schema.json +++ b/packages/mobile-ui-vue/components/input-group/src/schema/input-group.schema.json @@ -1,77 +1,76 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://farris-design.gitee.io/input-group.schema.json", - "title": "input-group", - "description": "", - "type": "object", - "properties": { - "id": { - "description": "标识", - "type": "string" - }, - "type": { - "description": "控件类型", - "type": "string", - "default": "input-group" - }, - "appearance": { - "description": "外观", - "type": "object", - "properties": { - "class": { - "type": "string" - }, - "style": { - "type": "string" - } - }, - "default": {} - }, - "binding": { - "description": "绑定", - "type": "object", - "default": {} - }, - "required": { - "description": "必填", - "type": "boolean", - "default": false - }, - "readonly": { - "description": "只读", - "type": "boolean", - "default": false - }, - "disabled": { - "description": "禁用", - "type": "boolean", - "default": false - }, - "placeholder": { - "description": "提示文本", - "type": "string" - }, - "formatValidation": { - "description": "验证类型", - "type": "object", - "default": {} + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/input-group.schema.json", + "title": "input-group", + "description": "", + "type": "object", + "properties": { + "id": { + "description": "标识", + "type": "string" + }, + "type": { + "description": "控件类型", + "type": "string", + "default": "input-group" + }, + "appearance": { + "description": "外观", + "type": "object", + "properties": { + "class": { + "type": "string" }, - "onUpdate:modelValue": { - "description": "值更新事件", - "type": "string", - "default": "" + "style": { + "type": "string" } + }, + "default": {} + }, + "binding": { + "description": "绑定", + "type": "object", + "default": {} + }, + "required": { + "description": "必填", + "type": "boolean", + "default": false + }, + "readonly": { + "description": "只读", + "type": "boolean", + "default": false + }, + "disabled": { + "description": "禁用", + "type": "boolean", + "default": false + }, + "placeholder": { + "description": "提示文本", + "type": "string" + }, + "formatValidation": { + "description": "验证类型", + "type": "object", + "default": {} }, - "events": [ - "onUpdate:modelValue" - ], - "required": [ - "type" - ], - "ignore": [ - "id", - "appearance", - "binding", - "visible" - ] + "onUpdate:modelValue": { + "description": "值更新事件", + "type": "string", + "default": "" + } + }, + "events": [ + "onUpdate:modelValue" + ], + "required": [ + "type" + ], + "ignore": [ + "id", + "appearance", + "visible" + ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/input-group/src/types/index.ts b/packages/mobile-ui-vue/components/input-group/src/types/index.ts index 0a89559108a4a15db0fa42aebb7a344639281fe1..b6f652a33896d1da073a941422ecabdc2588db10 100644 --- a/packages/mobile-ui-vue/components/input-group/src/types/index.ts +++ b/packages/mobile-ui-vue/components/input-group/src/types/index.ts @@ -9,7 +9,7 @@ export enum InputUpdateOnMap { blur = 'blur' } -export enum InputTypeMap { +export enum InputType { text = 'text', textarea = 'textarea', number = 'number', @@ -25,4 +25,3 @@ export type InputTextAlign = `${InputTextAlignMap}`; export type InputUpdateOn = `${InputUpdateOnMap}`; -export type InputType = `${InputTypeMap}`; diff --git a/packages/mobile-ui-vue/components/list-view/index.ts b/packages/mobile-ui-vue/components/list-view/index.ts index 4205610eb326d4f124e92edd620d55aeda09d648..83bd18b9cc49e7bffb78a08b5d6b2939f06bea76 100644 --- a/packages/mobile-ui-vue/components/list-view/index.ts +++ b/packages/mobile-ui-vue/components/list-view/index.ts @@ -1,29 +1,15 @@ -import { withInstall, RegisterContext } from '@farris/mobile-ui-vue/common'; +import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile-ui-vue/common'; import ListViewInstallless from './src/list-view.component'; -import ListViewDesign from './src/designer/list-view.design.component'; import { propsResolverGenerator } from './src/list-view.props'; +import ListViewDesign from './src/designer/list-view.design.component'; const LIST_VIEW_REGISTER_NAME = 'list-view'; -const ListView = withInstall(ListViewInstallless); - -export * from './src/list-view.props'; - -ListView.register = ( - componentMap: Record, propsResolverMap: Record, - configresolverMap: Record, resolverMap: Record,registerContext: RegisterContext -) => { - componentMap[LIST_VIEW_REGISTER_NAME] = ListView; - propsResolverMap[LIST_VIEW_REGISTER_NAME] = propsResolverGenerator(registerContext); -} -ListView.registerDesigner = ( - componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext -) => { - componentMap[LIST_VIEW_REGISTER_NAME] = ListViewDesign; - propsResolverMap[LIST_VIEW_REGISTER_NAME] = propsResolverGenerator(registerContext); -} +const ListView = withInstall(ListViewInstallless); +withRegister(ListView, { name: LIST_VIEW_REGISTER_NAME, propsResolverGenerator }); +withRegisterDesigner(ListView, { name: LIST_VIEW_REGISTER_NAME, propsResolverGenerator, designerComponent: ListViewDesign }); +export * from './src/list-view.props'; export { ListView }; -export default ListView ; +export default ListView; diff --git a/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx b/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx index 02979089547e7fd9c1f098fbdafbdb7c91f06c95..a32b0543e63b2c46cd747043f232aafed6fb847b 100644 --- a/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx +++ b/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx @@ -1,4 +1,5 @@ -import { SetupContext, toRefs } from "vue"; +import { computed, SetupContext, toRefs } from "vue"; +import { SwipeCell } from '@farris/mobile-ui-vue/swipe-cell'; import { LIST_VIEW_NAME, ListViewProps } from '../list-view.props'; import { UseData, UseSelection } from '../composition/types'; import { useBem } from '@farris/mobile-ui-vue/common'; @@ -18,16 +19,20 @@ export default function ( const { isMultiSelectMode, isItemSelected, toggleSelectItem } = useSelectionComposition; const { checkboxProps } = toRefs(props); + const shouldRenderSwipeCell = computed(() => { + return Array.isArray(props.swipeToolbar) && props.swipeToolbar.length > 0; + }); + const shouldDisableSwipeCell = computed(() => isMultiSelectMode.value); - const onListItemClick = (item: any, index: number) => { + function onListItemClick(item: any, index: number): void { emit('clickItem', { data: item, index }); - }; + } - const onCheckboxClick = (evt: MouseEvent) => { - evt && evt.stopPropagation(); - }; + function onCheckboxClick(event: MouseEvent): void { + event && event.stopPropagation(); + } - const renderCheckbox = (item: any) => { + function renderCheckbox(item: any) { return ( ); - }; + } - const renderItemContent = (item: any, index: number, disabled: boolean) => { + function renderItemContent(item: any, index: number, disabled: boolean) { if (slots.item) { return slots.item({ item, index, disabled }); } return (
{getItemText(item)}
); - }; + } - const renderSingleItem = (item: any, index: number, isChildItem?: boolean, parentItemIndex?: number) => { + function renderSwipeCell(item: any, index: number, disabled: boolean) { + if (!shouldRenderSwipeCell.value) { + return renderItemContent(item, index, disabled); + } + return ( + + {renderItemContent(item, index, disabled)} + + ); + } + + function renderSingleItem(item: any, index: number, isChildItem?: boolean, parentItemIndex?: number) { const itemClass = { [bem('item')]: true, [bem('item', 'child')]: isChildItem, @@ -59,20 +78,20 @@ export default function ( const rootItemIndex = isChildItem ? parentItemIndex : index; return ( -
onListItemClick(item, rootItemIndex)}> +
onListItemClick(item, rootItemIndex!)}> {isMultiSelectMode.value && (
{shouldShowCheckbox && renderCheckbox(item)}
)}
- {renderItemContent(item, index, shouldDisableItem)} + {renderSwipeCell(item, index, shouldDisableItem)}
); - }; + } - const renderItem = (item: any, index: number) => { + function renderItem(item: any, index: number): void { const children = getItemChildren(item); const hasChildren = children.length > 0; @@ -90,7 +109,7 @@ export default function (
); - }; + } return { renderItem }; } diff --git a/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts b/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts index a2fbc44a41d25887e01f1bf5856257e4200873dc..89d58e42e26ee5240e98729d89d8e1867ee6ebca 100644 --- a/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts +++ b/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts @@ -1,22 +1,22 @@ -import { SetupContext, computed } from 'vue'; +import { computed } from 'vue'; import { ListViewProps } from '../list-view.props'; import { UseData } from './types'; -export function useData(props: ListViewProps, _context: SetupContext): UseData { +export function useData(props: ListViewProps): UseData { - const getFieldPathArr = (fullPath: string): string[] => { + function getFieldPathArr(fullPath: string): string[] { if (!fullPath || typeof fullPath !== 'string') { return []; } return fullPath.split('.').filter(path => !!path); - }; + } const idFieldPathArr = computed(() => getFieldPathArr(props.idField)); const textFieldPathArr = computed(() => getFieldPathArr(props.textField)); const disableFieldPathArr = computed(() => getFieldPathArr(props.disableField)); const childFieldPathArr = computed(() => getFieldPathArr(props.childField)); - const getFieldByPathArr = (object: any, fieldPathArr: string[]): any => { + function getFieldByPathArr(object: any, fieldPathArr: string[]): any { if (!object || !fieldPathArr || !fieldPathArr.length) { return undefined; } @@ -24,9 +24,9 @@ export function useData(props: ListViewProps, _context: SetupContext): UseData { return currentObject ? currentObject[path] : currentObject; }, object); return fieldValue; - }; + } - const setFieldByPathArr = (object: any, fieldPathArr: string[], value: any): any => { + function setFieldByPathArr(object: any, fieldPathArr: string[], value: any): any { if (!object || !fieldPathArr || !fieldPathArr.length) { return object; } @@ -42,25 +42,25 @@ export function useData(props: ListViewProps, _context: SetupContext): UseData { } } return object; - }; + } - const getItemID = (item: any): any => { + function getItemID(item: any): any { return getFieldByPathArr(item, idFieldPathArr.value); - }; + } - const getItemText = (item: any): any => { + function getItemText(item: any): any { return getFieldByPathArr(item, textFieldPathArr.value) || ''; - }; + } const isItemDisabled = (item: any): boolean => { return !!getFieldByPathArr(item, disableFieldPathArr.value); }; - const getItemChildren = (item: any): any[] => { + function getItemChildren(item: any): any[] { const children = getFieldByPathArr(item, childFieldPathArr.value); const isChildrenValid = Array.isArray(children); return isChildrenValid ? children : []; - }; + } const listItems = computed(() => { const originalItems = props.data || []; diff --git a/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts b/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts index 0d7f65391401a15e50de1801b0cb91c3270e3db8..3546ab1595d54aa43672a0be500fff8ffb9948d8 100644 --- a/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts +++ b/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts @@ -15,13 +15,13 @@ export function useSelection( return props.enableMultiSelect && props.multiSelect; }); - const toggleMultiSelect = (value: boolean) => { + function toggleMultiSelect(value: boolean): void { emit('update:multiSelect', value); - }; + } const { listItems, getItemID, getItemChildren } = useDataCompostion; - const getId2ItemMap = () => { + function getId2ItemMap(): Map { const id2Item = new Map(); listItems.value.forEach((item) => { id2Item.set(getItemID(item), item); @@ -33,9 +33,9 @@ export function useSelection( }); }); return id2Item; - }; + } - const getItemsFromIDs = (ids: string[]): any[] => { + function getItemsFromIDs(ids: string[]): any[] { const items: any[] = []; const id2Item = getId2ItemMap(); ids.forEach((id) => { @@ -46,7 +46,7 @@ export function useSelection( targetItem && items.push(targetItem); }); return items; - }; + } const selectedItems: Ref = ref(getItemsFromIDs(props.selectedValues)); @@ -54,27 +54,27 @@ export function useSelection( selectedItems.value = ref(getItemsFromIDs(newValues)).value; }); - const getItemIndexFromSelectedItems = (item: any): number => { + function getItemIndexFromSelectedItems(item: any): number { const targetID = getItemID(item); if (!isDef(targetID)) { return -1; } return selectedItems.value.findIndex(i => getItemID(i) === targetID); - }; + } - const isItemSelected = (item: any) => { + function isItemSelected(item: any): boolean { return getItemIndexFromSelectedItems(item) >= 0; - }; + } - const getSelectedItems = () => { + function getSelectedItems(): any[] { return [...selectedItems.value]; - }; + } - const emitSelectionChange = () => { + function emitSelectionChange(): void { emit('selectionChange', getSelectedItems()); - }; + } - const toggleSelectItem = (item: any) => { + function toggleSelectItem(item: any): void { const targetID = getItemID(item); if (!isDef(targetID)) { return; @@ -86,12 +86,12 @@ export function useSelection( selectedItems.value.push(item); } emitSelectionChange(); - }; + } - const clearSelection = () => { + function clearSelection(): void { selectedItems.value = []; emitSelectionChange(); - }; + } return { isMultiSelectMode, diff --git a/packages/mobile-ui-vue/components/list-view/src/designer/list-view.design.component.tsx b/packages/mobile-ui-vue/components/list-view/src/designer/list-view.design.component.tsx index f6134fdff945c2b9c4f5f2fac4700ad65387e3a8..0a55f2f851e2818c19a39870b387eefe8fa3837f 100644 --- a/packages/mobile-ui-vue/components/list-view/src/designer/list-view.design.component.tsx +++ b/packages/mobile-ui-vue/components/list-view/src/designer/list-view.design.component.tsx @@ -1,16 +1,19 @@ -import { SetupContext, defineComponent, inject, onMounted, ref, computed } from 'vue'; -import { LIST_VIEW_NAME, ListViewProps, listViewProps } from '../list-view.props'; +import { defineComponent, inject, onMounted, ref, computed } from 'vue'; +import { ListViewProps, listViewProps } from '../list-view.props'; import { useDesignerRules } from './use-designer-rules'; -import List from '@farris/mobile-ui-vue/list'; -import { useBem } from '@farris/mobile-ui-vue/common'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas'; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common'; +import { ListView } from '@farris/mobile-ui-vue/list-view'; export default defineComponent({ name: 'FmListViewDesign', props: listViewProps, emits: [], - setup(props: ListViewProps, context: SetupContext) { - const elementRef = ref(); + setup(props: ListViewProps, context) { + const listViewRef = ref(); + const elementRef = computed(() => { + return listViewRef.value?.elementRef; + }); + const designerHostService = inject('designer-host-service'); const designItemContext = inject('design-item-context') as DesignerItemContext; const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); @@ -19,41 +22,27 @@ export default defineComponent({ onMounted(() => { elementRef.value.componentInstance = componentInstance; }); - context.expose(componentInstance.value); const listViewProps = computed(() => ({ ...props, + data: [{ id: 'example', name: 'example data' }], + loading: false, finished: true, })); - const { bem } = useBem(LIST_VIEW_NAME); - - const listViewClass = { - [bem()]: true, - [bem('', 'fill')]: true, - }; - - const listContentClass = { - [bem('content')]: true, - [bem('content', 'split')]: false, - }; + function renderItemTemplate() { + return ( +
+ ); + } return () => ( -
-
- -
-
-
- -
-
-
- + + {{ + item: renderItemTemplate, + }} + ); } }); diff --git a/packages/mobile-ui-vue/components/list-view/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/list-view/src/designer/use-designer-rules.ts index 40aeb4c0600ce4b443b1640108525796ad5b08c2..eba98e41793c037032ac1dce3959858ed8de0cc7 100644 --- a/packages/mobile-ui-vue/components/list-view/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/list-view/src/designer/use-designer-rules.ts @@ -1,9 +1,9 @@ import { ref } from "vue"; -import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "../../../designer-canvas/src/composition/types"; -import { ComponentProperty } from "../property-config/component.property-config"; -import { DesignerItemContext } from "@farris/mobile-ui-vue/designer-canvas"; +import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { ListViewProperty } from "../property-config/list-view.property-config"; +import { DesignerItemContext } from "@farris/mobile-ui-vue/common"; -export function useDesignerRules(designItemContext: DesignerItemContext,designerHostService?: DesignerHostService): UseDesignerRules { +export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { const triggerBelongedComponentToMoveWhenMoved = ref(false); @@ -31,20 +31,15 @@ export function useDesignerRules(designItemContext: DesignerItemContext,designer } function getStyles(): string { - return 'flex: 1'; - + return 'flex: 1;'; } - /** - * 获取属性配置 - */ function getPropsConfig(componentId: string) { - const componentProp = new ComponentProperty(componentId, designerHostService); + const listViewProp = new ListViewProperty(componentId, designerHostService); const { schema } = designItemContext; - return componentProp.getPropertyConfig(schema); + return listViewProp.getPropertyConfig(schema); } - return { canAccepts, triggerBelongedComponentToMoveWhenMoved, diff --git a/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx b/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx index 792ddb8527b79ae3fb3327493d9c068e9d00f672..dcf87fd7229dcf4b8fc7b88dc22aa2f6e82ccecb 100644 --- a/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx +++ b/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx @@ -42,9 +42,10 @@ export default defineComponent({ setup(props: ListViewProps, context: SetupContext) { const { bem } = useBem(LIST_VIEW_NAME); const { emit, slots, expose } = context; + const elementRef = ref(); const listComponentRef = ref(); - const useDataComposition = useData(props, context); + const useDataComposition = useData(props); const { listItems, isEmpty } = useDataComposition; const useSelectionComposition = useSelection(props, context, useDataComposition); @@ -90,20 +91,20 @@ export default defineComponent({ return props.emptyMessage || '暂无数据'; }); - const renderEmptyTemplate = () => { + function renderEmptyTemplate() { if (slots.empty) { return slots.empty(); } return ( {emptyMessage.value} ); - }; + } - const handleListContentLongPress = () => { + function handleListContentLongPress(): void { toggleMultiSelect(true); - }; + } - const renderListContent = () => { + function renderListContent() { const listContentClass = { [bem('content')]: true, [bem('content', 'split')]: shouldShowSplitLine.value, @@ -120,9 +121,9 @@ export default defineComponent({ ]])} ); - }; + } - const renderList = () => { + function renderList() { const listSlots = { finished: slots.finished, loading: slots.loading, @@ -149,9 +150,9 @@ export default defineComponent({ {slots.default?.()} ); - }; + } - const renderListWithPullRefresh = () => { + function renderListWithPullRefresh() { if (!enablePullRefresh.value) { return renderList(); } @@ -175,9 +176,9 @@ export default defineComponent({ {renderList()} ); - }; + } - const renderToolbar = () => { + function renderToolbar() { return (
@@ -199,13 +200,14 @@ export default defineComponent({
); - }; + } - const checkListNeedLoadMore = () => { + function checkListNeedLoadMore(): void { listComponentRef.value?.check(); - }; + } expose({ + elementRef, check: checkListNeedLoadMore, getSelectedItems, clearSelection, @@ -218,7 +220,11 @@ export default defineComponent({ [bem('', 'multi-select')]: isMultiSelectMode.value, }; return ( -
+
{slots.header?.()}
{renderListWithPullRefresh()} diff --git a/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts b/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts index 2bdf5bff01b589b322b86289bfefd11ac31cfed5..2023813c19194b4bdb656c61071090817ae9cf07 100644 --- a/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts +++ b/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts @@ -18,11 +18,12 @@ import { ExtractPropTypes, PropType } from 'vue'; import { ListProps } from '@farris/mobile-ui-vue/list'; import { PullRefreshProps } from '@farris/mobile-ui-vue/pull-refresh'; import { CheckboxProps } from '@farris/mobile-ui-vue/checkbox/src/checkbox.props'; +import { SwipeCellButton } from '@farris/mobile-ui-vue/swipe-cell'; import { getPropsResolverGenerator } from '../../dynamic-resolver'; import { schemaMapper } from './schema/schema-mapper'; import listViewSchema from './schema/list-view.schema.json'; -import listViewPropertyConfig from './property-config/list-view.property-config.json'; import { schemaResolver } from './schema/schema-resolver'; +import { SwipeCellButton } from '@farris/mobile-ui-vue/swipe-cell'; export const LIST_VIEW_NAME = 'fm-list-view'; @@ -80,13 +81,22 @@ export const listViewProps = { fill: { type: Boolean, default: false }, /** 是否显示分隔线 */ - split: { type: Boolean, default: true }, + split: { type: Boolean, default: false }, /** 列表数据为空时显示的占位文本 */ emptyMessage: { type: String, default: '' }, /** 多选状态下,勾选框的属性 */ checkboxProps: { type: Object as PropType, default: { shape: 'round' } }, + + /** 列表行的滑动工具栏按钮 */ + swipeToolbar: { type: Array, default: [] }, + + /** 自定义样式类 */ + customClass: { type: String, default: '' }, + + /** 自定义样式 */ + customStyle: { type: String, default: '' }, }; /** 工具栏按钮 */ @@ -111,6 +121,5 @@ export const propsResolverGenerator = getPropsResolverGenerator( listViewProps, listViewSchema, schemaMapper, - schemaResolver, - listViewPropertyConfig, + schemaResolver ); diff --git a/packages/mobile-ui-vue/components/list-view/src/property-config/list-view.property-config.json b/packages/mobile-ui-vue/components/list-view/src/property-config/list-view.property-config.json deleted file mode 100644 index 75c9494bb38f45b67ab5dd580d47377788251bc8..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/list-view/src/property-config/list-view.property-config.json +++ /dev/null @@ -1,143 +0,0 @@ -{ - "title": "list-view", - "description": "A Farris Component", - "type": "object", - "categories": { - "basic": { - "description": "Basic Infomation", - "title": "基本信息", - "properties": { - "id": { - "description": "组件标识", - "title": "标识", - "type": "string", - "readonly": true - }, - "type": { - "description": "组件类型", - "title": "控件类型", - "type": "string" - } - } - }, - "behavior": { - "description": "", - "title": "行为", - "properties": { - "visible": { - "description": "运行时组件是否可见", - "type": "boolean", - "title": "是否可见", - "editor": { - "type": "combo-list", - "data": [ - { - "value": true, - "name": "是" - }, - { - "value": false, - "name": "否" - } - ] - } - }, - "enablePullRefresh": { - "description": "是否启用下拉刷新列表数据", - "type": "boolean", - "title": "是否启用下拉刷新", - "editor": { - "type": "combo-list", - "data": [ - { - "value": true, - "name": "是" - }, - { - "value": false, - "name": "否" - } - ] - } - }, - "enableMultiSelect": { - "description": "是否启用多选功能,启用后长按列表条目进入多选状态", - "type": "boolean", - "title": "是否启用多选功能", - "editor": { - "type": "combo-list", - "data": [ - { - "value": true, - "name": "是" - }, - { - "value": false, - "name": "否" - } - ] - } - }, - "multiSelect": { - "description": "是否默认处于多选状态", - "type": "boolean", - "title": "是否默认处于多选状态", - "editor": { - "type": "combo-list", - "data": [ - { - "value": true, - "name": "是" - }, - { - "value": false, - "name": "否" - } - ] - } - }, - "fill": { - "description": "是否填充页面下方剩余区域", - "type": "boolean", - "title": "是否填充", - "editor": { - "type": "combo-list", - "data": [ - { - "value": true, - "name": "是" - }, - { - "value": false, - "name": "否" - } - ] - } - }, - "split": { - "description": "是否在列表条目之间显示分隔线", - "type": "boolean", - "title": "是否显示分隔线", - "editor": { - "type": "combo-list", - "data": [ - { - "value": true, - "name": "是" - }, - { - "value": false, - "name": "否" - } - ] - } - }, - "emptyMessage": { - "description": "列表数据为空时显示的占位文本", - "title": "空数据提示文本", - "type": "string" - } - } - } - } -} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/list-view/src/property-config/component.property-config.ts b/packages/mobile-ui-vue/components/list-view/src/property-config/list-view.property-config.ts similarity index 31% rename from packages/mobile-ui-vue/components/list-view/src/property-config/component.property-config.ts rename to packages/mobile-ui-vue/components/list-view/src/property-config/list-view.property-config.ts index 073ac3a8fe542f8f6b6c1858fe7c810bea841469..12c220b375b55b1b7a33f0679b1279691ec21174 100644 --- a/packages/mobile-ui-vue/components/list-view/src/property-config/component.property-config.ts +++ b/packages/mobile-ui-vue/components/list-view/src/property-config/list-view.property-config.ts @@ -1,19 +1,121 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { ContainerBaseProperty, ToolbarItemProperty, PropertyChangeObject } from "@farris/mobile-ui-vue/common"; +import { swipeToolbarConverter } from "@farris/mobile-ui-vue/dynamic-resolver/src/converter"; +export class ListViewProperty extends ContainerBaseProperty { + + private toolbarItemProperty: ToolbarItemProperty; -export class ComponentProperty extends BaseControlProperty { constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); + this.toolbarItemProperty = new ToolbarItemProperty(componentId, designerHostService); } + public getPropertyConfig(propertyData: any) { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); // 外观 this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + // 滑动工具栏 + this.propertyConfig.categories['swipeToolbar'] = this.getSwipeToolbarConfig(propertyData); // 事件 this.getEventPropConfig(propertyData); return this.propertyConfig; } + + public getBasicPropConfig(propertyData: any) { + const entityTreeData = this.getSchemaEntityTreeData(); + return super.getBasicPropConfig( + propertyData, + undefined, + { + dataSource: { + description: '绑定数据源', + title: '绑定数据源', + editor: { + type: 'combo-tree', + textField: 'name', + valueField: 'label', + data: entityTreeData, + editable: false, + }, + readonly: true, + }, + }, + (changeObject: PropertyChangeObject) => { + if (changeObject?.propertyID === 'dataSource') { + const viewModelNode = this.formSchemaUtils.getViewModelById(this.viewModelId); + if (viewModelNode) { + const selectedEntity = entityTreeData.find(entityData => entityData.label === changeObject.propertyValue); + viewModelNode.bindTo = selectedEntity.bindTo; + } + this.designViewModelUtils.assembleDesignViewModel(); + } + }, + ); + } + + protected getAppearanceConfig(propertyData: any) { + return super.getAppearanceConfig(propertyData, { + itemTemplate: { + title: '列表项模板', + type: 'string', + description: '列表条目的html模板', + refreshPanelAfterChanged: true, + editor: { + type: "code-editor", + language: "html", + }, + }, + }); + } + + public getBehaviorConfig(propertyData: any) { + return super.getBehaviorConfig(propertyData, '', { + fill: { + title: '是否填充', + type: 'boolean', + description: '是否填充页面下方剩余区域', + }, + enablePullDownRefresh: { + title: '是否启用下拉刷新', + type: 'boolean', + description: '是否启用下拉刷新列表数据', + }, + }); + } + + protected getSwipeToolbarConfig(propertyData: any) { + return { + title: '滑动工具栏', + $converter: swipeToolbarConverter, + properties: { + items: { + title: '滑动工具栏', + editor: { + type: "collection-property-editor", + textField: 'text', + modalTitle: '工具栏编辑器', + onSelectionChange: ({ selectedData, propertyConfig }) => { + propertyConfig.value = this.toolbarItemProperty.getPropertyConfig(selectedData.value, { + showAppearance: true, + showButtonType: true, + }); + }, + defaultComponentSchema: { + id: 'button', + text: '按钮', + visible: true, + disabled: false, + displayType: 'danger', + } + } + } + } + }; + } + private getEventPropConfig(propertyData: any) { const events = [ { diff --git a/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json b/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json index 97e8fc5145a2f852e7a968867c43c6b3fe1f243a..0b54af971c1cf9f9bf293ac8758cf2239a29c774 100644 --- a/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json +++ b/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json @@ -35,10 +35,10 @@ "loading": { "description": "加载中状态", "type": "boolean", - "default": "false" + "default": false }, "dataSource": { - "description": "", + "description": "绑定数据源", "type": "string", "default": "" }, @@ -51,6 +51,37 @@ "description": "列表行模板", "type": "string", "default": "" + }, + "swipeToolbar": { + "description": "列表条目的滑动工具栏", + "type": "object", + "properties": { + "items": { + "type": "array", + "default": [] + } + }, + "default": {} + }, + "visible": { + "description": "是否可见", + "type": "boolean", + "default": true + }, + "fill": { + "description": "是否填充", + "type": "boolean", + "default": false + }, + "split": { + "description": "是否在列表条目之间显示分隔线", + "type": "boolean", + "default": false + }, + "enablePullDownRefresh": { + "description": "是否启用下拉刷新", + "type": "boolean", + "default": false } }, "events": [ diff --git a/packages/mobile-ui-vue/components/list-view/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/list-view/src/schema/schema-mapper.ts index 37dd6b3f6c402ed3cfd2071bb1cd0d1a6eed72ac..c8d356c6c064baa55bc76cafc9cdbe3faaf7cd2e 100644 --- a/packages/mobile-ui-vue/components/list-view/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/list-view/src/schema/schema-mapper.ts @@ -1,4 +1,4 @@ -import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver'; +import { resolveAppearance, resolveSwipeToolbar, MapperFunction } from '../../../dynamic-resolver'; function createTemplatePropResolver(name: string): MapperFunction { return (key: string, content: string) => { @@ -6,7 +6,6 @@ function createTemplatePropResolver(name: string): MapperFunction { return {}; } const itemTemplate = { name, content }; - return { itemTemplate }; }; } @@ -14,6 +13,8 @@ function createTemplatePropResolver(name: string): MapperFunction { export const schemaMapper = new Map([ ['appearance', resolveAppearance], ['dataSource', 'data'], + ['enablePullDownRefresh', 'enablePullRefresh'], ['onItemClick', 'onClickItem'], - ['itemTemplate', createTemplatePropResolver('item')] + ['itemTemplate', createTemplatePropResolver('item')], + ['swipeToolbar', resolveSwipeToolbar], ]); diff --git a/packages/mobile-ui-vue/components/list/src/list.component.tsx b/packages/mobile-ui-vue/components/list/src/list.component.tsx index 530cd7a44edc5c2394f95226df0c113c7c9fca1a..580b39745d8a7028e3d4ab6b4d485b8f0e5ab0ff 100644 --- a/packages/mobile-ui-vue/components/list/src/list.component.tsx +++ b/packages/mobile-ui-vue/components/list/src/list.component.tsx @@ -123,7 +123,7 @@ export default defineComponent({ const renderLoading = () => { const loading = slots.loading ? slots.loading() : (
- +
); return ( diff --git a/packages/mobile-ui-vue/components/loading/index.ts b/packages/mobile-ui-vue/components/loading/index.ts index 9f039c347e7ed94b7168eceae82185246e9b9b9f..8df7ccc5ac826d0ec3a98d11ff4642d2fbc4ca8a 100644 --- a/packages/mobile-ui-vue/components/loading/index.ts +++ b/packages/mobile-ui-vue/components/loading/index.ts @@ -1,9 +1,8 @@ -import { withInstall } from '@farris/mobile-ui-vue/common'; -import LoadingInstallless from './src/loading.component'; +import Loading from './src'; export * from './src/loading.props'; -const Loading = withInstall(LoadingInstallless); +export type { LoadingOptions } from './src/index'; export { Loading }; export default Loading; diff --git a/packages/mobile-ui-vue/components/loading/src/composition/types.ts b/packages/mobile-ui-vue/components/loading/src/composition/types.ts deleted file mode 100644 index d33d910dc1f2f0c5d21bc29d27b04ecae05df680..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/loading/src/composition/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** 加载图标的类型 */ -export type LoadingType = 'default' | 'spinner' | 'ring'; - -/** 加载图标与文本的对齐方向 */ -export type LoadingDirection = 'horizontal' | 'vertical'; diff --git a/packages/mobile-ui-vue/components/loading/src/index.tsx b/packages/mobile-ui-vue/components/loading/src/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7e8b9be2c127cd6db944dfed12fd4aceca995a70 --- /dev/null +++ b/packages/mobile-ui-vue/components/loading/src/index.tsx @@ -0,0 +1,91 @@ +import { App } from 'vue'; +import { isObject, inBrowser, mountComponent, usePopupState } from '@farris/mobile-ui-vue/common'; +import Popup from '@farris/mobile-ui-vue/popup'; +import { LoadingDirection, LoadingProps, LoadingType } from './loading.props'; +import FMLoading from './loading.component'; + +let instance; + +export type LoadingOptions = string | Partial + +function defaultOptions(): Partial { + return { + type: LoadingType.default, + direction: LoadingDirection.horizontal, + text: '', + size: '24px', + duration: undefined, + color: undefined, + textColor: undefined + }; +} + +function parseOptions(options: LoadingOptions) { + return isObject(options) ? options : { text: options }; +} + +const initInstance = () => { + ({ instance } = mountComponent({ + setup() { + const { state } = usePopupState(); + + return () => + + ; + } + })); +}; + +function Loading(options: LoadingOptions) { + if (!inBrowser) { + return; + } + + if (!instance) { + initInstance(); + } + + options = { + ...Loading.currentOptions, + ...parseOptions(options) + }; + + instance.open(options); + + return instance; +} + +Loading.hidden = () => { + if (instance) { + instance.toggle(false); + } +}; + +Loading.show = (options: LoadingOptions = {}) => { + Loading.resetDefaultOptions(); + return Loading(parseOptions(options)); +}; + +Loading.currentOptions = defaultOptions(); + +Loading.setDefaultOptions = (options: Partial) => { + Object.assign(Loading.currentOptions, options); +}; + +Loading.resetDefaultOptions = () => { + Loading.currentOptions = defaultOptions(); +}; + +Loading.install = (app: App) => { + app.component((FMLoading.name as string), FMLoading); + app.config.globalProperties.$loading = Loading; +}; + +Loading.Component = FMLoading; + +export default Loading; diff --git a/packages/mobile-ui-vue/components/loading/src/loading.props.ts b/packages/mobile-ui-vue/components/loading/src/loading.props.ts index 5f02a704b16ea6a47a075f025d14222257000571..2d955e995454c79c71cfe6110929855485160ba1 100644 --- a/packages/mobile-ui-vue/components/loading/src/loading.props.ts +++ b/packages/mobile-ui-vue/components/loading/src/loading.props.ts @@ -14,20 +14,32 @@ * limitations under the License. */ import { ExtractPropTypes, PropType } from 'vue'; -import { LoadingType, LoadingDirection } from './composition/types'; + +/** 加载图标的类型 */ +export enum LoadingType { + default = 'default', + spinner = 'spinner', + ring = 'ring', +}; + +/** 加载图标与文本的对齐方向 */ +export enum LoadingDirection { + horizontal = 'horizontal', + vertical = 'vertical', +}; export const LOADING_NAME = 'fm-loading'; export const loadingProps = { /** 加载图标的类型,可选值:`default`(默认)、`spinner`、`ring` */ - type: { type: String as PropType, default: 'default' }, + type: { type: String as PropType, default: LoadingType.default }, /** 文本内容 */ text: { type: String, default: '' }, /** 图标与文本的对齐方向,默认水平对齐,可选垂直对齐 */ - direction: { type: String as PropType, default: 'horizontal' }, + direction: { type: String as PropType, default: LoadingDirection.horizontal }, /** 图标的大小 */ size: { type: String, default: '24px' }, @@ -47,6 +59,6 @@ export const loadingProps = { /** 加载动画播放一个周期的时长,单位:ms */ duration: { type: Number, default: undefined }, -} as Record; +}; export type LoadingProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/loading/src/loading.scss b/packages/mobile-ui-vue/components/loading/src/loading.scss index 3de6637e9ce838a0c6c275c4ef199262d2b58ff5..27c9f972e300128e4ca4b4e98824543277824c85 100644 --- a/packages/mobile-ui-vue/components/loading/src/loading.scss +++ b/packages/mobile-ui-vue/components/loading/src/loading.scss @@ -71,6 +71,14 @@ border-radius: 50%; } } +.fm-loading-popup { + display: flex; + align-items: center; + justify-content: center; + width: 100px; + height: 100px; + background-color: inherit; +} @keyframes fm-circular { 0% { diff --git a/packages/mobile-ui-vue/components/lookup/index.ts b/packages/mobile-ui-vue/components/lookup/index.ts index f51566db69f31b3d86ab9b0093c3172a4b89e107..885ec8fbd5e56a7be7ba09b423c756c77b331290 100644 --- a/packages/mobile-ui-vue/components/lookup/index.ts +++ b/packages/mobile-ui-vue/components/lookup/index.ts @@ -1,9 +1,20 @@ -import { withInstall } from '@farris/mobile-ui-vue/common'; +import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile-ui-vue/common'; import LookupInstallless from './src/lookup.component'; import { LOOKUP_HTTP_SERVICE_TOKEN } from './src/lookup.props'; import { LookupDisplayType } from './src/composition'; +import { propsResolverGenerator } from './src/lookup.props'; +import LookupDesign from './src/designer/lookup.design.component'; + +export { LookupSchemaRepositoryToken, FieldSelectorRepositoryToken } from './src/property-config/lookup.property-config'; const Lookup = withInstall(LookupInstallless); +// 注册运行时及设计时 + +const LOOKUP_REGISTERED_NAME = 'lookup'; +withRegister(Lookup, { name: LOOKUP_REGISTERED_NAME, propsResolverGenerator }); +withRegisterDesigner(Lookup, { name: LOOKUP_REGISTERED_NAME, propsResolverGenerator, designerComponent: LookupDesign }); + + export { Lookup, LOOKUP_HTTP_SERVICE_TOKEN, LookupDisplayType }; export default Lookup; diff --git a/packages/mobile-ui-vue/components/lookup/src/components/footer.component.tsx b/packages/mobile-ui-vue/components/lookup/src/components/footer.component.tsx index b5da66bcc9e22cb860dbb3fe0731a52025d4aa89..6840cf90720e78f6f334a80fe659e2795e538611 100644 --- a/packages/mobile-ui-vue/components/lookup/src/components/footer.component.tsx +++ b/packages/mobile-ui-vue/components/lookup/src/components/footer.component.tsx @@ -1,6 +1,6 @@ import { computed, defineComponent } from 'vue'; import { useBem } from '@farris/mobile-ui-vue/common'; -import { Checker, CheckerShapeMap } from '@farris/mobile-ui-vue/checker'; +import { Checker, CheckerShape } from '@farris/mobile-ui-vue/checker'; import Button from '@farris/mobile-ui-vue/button'; import { useLookupPanelState } from '../composition'; import { LOOKUP_PANEL_NAME } from '../lookup-panel.props'; @@ -28,7 +28,7 @@ export default defineComponent({ class={bem('footer-checker')} label={isSelectedAllValue.value ? '取消全选' : '全选'} modelValue={isSelectedAllValue.value} - shape={CheckerShapeMap.Round} + shape={CheckerShape.Round} onChange={handleSelecteAll} >}
diff --git a/packages/mobile-ui-vue/components/lookup/src/composition/use-render-component.tsx b/packages/mobile-ui-vue/components/lookup/src/composition/use-render-component.tsx index 3dbd6e6523fd49c9f9cd54a171f793778fbbed00..6616b4583a160d35932d3081c7a4dcaf7d43e118 100644 --- a/packages/mobile-ui-vue/components/lookup/src/composition/use-render-component.tsx +++ b/packages/mobile-ui-vue/components/lookup/src/composition/use-render-component.tsx @@ -1,6 +1,6 @@ import { computed } from "vue"; import { getValue, stopPropagation, useBem } from "@farris/mobile-ui-vue/common"; -import { Checker, CheckerShapeMap } from "@farris/mobile-ui-vue/checker"; +import { Checker, CheckerShape } from "@farris/mobile-ui-vue/checker"; import Icon from "@farris/mobile-ui-vue/icon"; import Cell from "@farris/mobile-ui-vue/cell"; import { ListDataItem, LookupDisplayType, UseRenderComponent } from "./types"; @@ -34,7 +34,7 @@ export const useRenderComponent: UseRenderComponent = (events) => { handleChange(item)} > ); diff --git a/packages/mobile-ui-vue/components/lookup/src/designer/lookup.design.component.tsx b/packages/mobile-ui-vue/components/lookup/src/designer/lookup.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..17407092fa7230fd9ebcba18d7293b1dcf7a41d4 --- /dev/null +++ b/packages/mobile-ui-vue/components/lookup/src/designer/lookup.design.component.tsx @@ -0,0 +1,42 @@ +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; +import { lookupProps } from '../lookup.props'; +import { usePickerDesignerRules } from './use-designer-rules'; +import InputGroup from '@farris/mobile-ui-vue/input-group'; + +export default defineComponent({ + name: 'FmPickerDesign', + inheritAttrs: false, + props: extractProperties(lookupProps, ['placeholder']), + setup(props, context) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerRulesComposition = usePickerDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + + context.expose(componentInstance.value); + + const inputProps = computed(() => ({ + ...props, + editable: false + })); + + return () => ; + } +}); diff --git a/packages/mobile-ui-vue/components/lookup/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/lookup/src/designer/use-designer-rules.ts new file mode 100644 index 0000000000000000000000000000000000000000..9db02ba77e2a1ff8e3414919ec655b91654fb1c7 --- /dev/null +++ b/packages/mobile-ui-vue/components/lookup/src/designer/use-designer-rules.ts @@ -0,0 +1,15 @@ +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { LookupProperty } from "../property-config/lookup.property-config"; +export function usePickerDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { + + const schema = designItemContext.schema as ComponentSchema; + + // 构造属性配置方法 + function getPropsConfig(componentId: string, componentInstance: DesignerComponentInstance) { + const lookupProps = new LookupProperty(componentId, designerHostService); + return lookupProps.getPropertyConfig(schema, componentInstance); + } + + return { getPropsConfig } as UseDesignerRules; + +} diff --git a/packages/mobile-ui-vue/components/lookup/src/lookup.props.ts b/packages/mobile-ui-vue/components/lookup/src/lookup.props.ts index 20fa4fbc3ed0db13e0f015ca42930753bddb22a9..aca051ceb15115485d53c2a576ffc126a3eddae0 100644 --- a/packages/mobile-ui-vue/components/lookup/src/lookup.props.ts +++ b/packages/mobile-ui-vue/components/lookup/src/lookup.props.ts @@ -1,6 +1,10 @@ import { ExtractPropTypes } from 'vue'; import { buttonEditProps } from '@farris/mobile-ui-vue/button-edit'; import { lookupPanelProps } from './lookup-panel.props'; +import { getPropsResolverGenerator } from '@farris/mobile-ui-vue/dynamic-resolver'; +import lookupSchema from './schema/lookup.schema.json'; +import { schemaMapper } from './schema/schema-mapper'; +import { schemaResolver } from './schema/schema-resolver'; export * from './lookup-panel.props'; @@ -12,3 +16,5 @@ export const lookupProps = { }; export type LookupProps = ExtractPropTypes; + +export const propsResolverGenerator = getPropsResolverGenerator(lookupProps, lookupSchema, schemaMapper, schemaResolver); diff --git a/packages/mobile-ui-vue/components/lookup/src/property-config/converters/lookup-property.converter.ts b/packages/mobile-ui-vue/components/lookup/src/property-config/converters/lookup-property.converter.ts new file mode 100644 index 0000000000000000000000000000000000000000..4980fd32460205ae92c589b28cfa995914f3b3c8 --- /dev/null +++ b/packages/mobile-ui-vue/components/lookup/src/property-config/converters/lookup-property.converter.ts @@ -0,0 +1,119 @@ +function generateSourceUri(ctrlId: string) { + let code = 'form_group_' + Date.now(); + if (ctrlId) { + code = ctrlId.replaceAll('-', '_').replaceAll('.', '_'); + } + return 'lookup.' + code; +} + + +export const lookupDataSourceConverter = { + convertFrom: (schema: Record, propertyKey: string) => { + return schema.editor[propertyKey]?.displayName; + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any[]) => { + if (propertyValue && propertyValue.length > 0) { + const datasource = propertyValue[0]; + const { name, id, metadataContent, code } = datasource; + + if (!schema.editor.dataSource) { + schema.editor.dataSource = {}; + } + + schema.editor.dataSource.displayName = `${name}(${code})`; + schema.editor['helpId'] = id; + + const { displayType, idField, textField } = metadataContent; + schema.editor.displayType = displayType; + schema.editor.dataSource.idField = idField; + schema.editor.textField = textField; + schema.editor.dataSource.type = "ViewObject"; + + + const { pageInfo, treeInfo } = metadataContent.dataSource; + // setPageInfo(schema.editor, pageInfo); + + if (treeInfo) { + const { onlySelectLeaf, loadDataType } = treeInfo; + schema.editor.loadTreeDataType = loadDataType || 'default'; + schema.editor.onlySelectLeaf = onlySelectLeaf == null ? false: onlySelectLeaf; + } + + if (!schema.editor.dataSource.uri) { + schema.editor.dataSource.uri = generateSourceUri(schema.id); + } + } + } +}; + +export const lookupIdFieldConverter = { + convertFrom: (schema: Record, propertyKey: string) => { + return schema.editor.dataSource?.idField; + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any) => { + if (propertyValue && propertyValue.length > 0) { + const fieldInfo = propertyValue[0]; + schema.editor.dataSource.idField = fieldInfo?.bindingPath; + } + } +}; + +export const lookupTextFieldConverter = { + convertFrom: (schema: Record, propertyKey: string) => { + return schema.editor?.textField; + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any) => { + if (propertyValue && propertyValue.length > 0) { + const fieldInfo = propertyValue[0]; + schema.editor.textField = fieldInfo?.bindingPath; + } + } +}; + +export const lookupDisplayTypeConverter = { + convertFrom: (schema: Record, propertyKey: string) => { + return schema.editor.displayType ? schema.editor.displayType.toUpperCase() : 'LIST'; + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any) => { + schema.editor.displayType = propertyValue; + } +}; + +export const lookupDialogOptionsConverter = { + convertFrom: (schema: Record, propertyKey: string) => { + const options = schema.editor.dialog || {}; + if (propertyKey === 'title') { + return options[propertyKey] || ''; + } + + if (propertyKey === 'width') { + if (schema.editor.displayType?.toUpperCase().startsWith('NAV')) { + return options[propertyKey] || 960; + } + return options[propertyKey] || 590; + } + + if (propertyKey === 'height') { + return options[propertyKey] || 620; + } + + if (propertyKey === 'rememberSize') { + return options[propertyKey] === undefined ? !!schema.editor.enableUserData : options[propertyKey]; + } + + if (propertyKey === 'navigatorWidth') { + return options[propertyKey] || 300; + } + + const keys = ['showMaxButton', 'showCloseButton', 'resizeable', 'draggable', 'enableEsc']; + if (keys.includes(propertyKey)) { + return options[propertyKey] == null ? true : options[propertyKey]; + } + + }, + convertTo: (schema: Record, propertyKey: string, propertyValue: any) => { + schema.editor.dialog = schema.editor.dialog || {}; + schema.editor.dialog[propertyKey] = propertyValue; + } +}; + diff --git a/packages/mobile-ui-vue/components/lookup/src/property-config/lookup.property-config.ts b/packages/mobile-ui-vue/components/lookup/src/property-config/lookup.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..5600972331da7ee86a4bc0b094a71c215740b80f --- /dev/null +++ b/packages/mobile-ui-vue/components/lookup/src/property-config/lookup.property-config.ts @@ -0,0 +1,219 @@ +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; +import { lookupDataSourceConverter, lookupTextFieldConverter } from './converters/lookup-property.converter'; + +export const LookupSchemaRepositoryToken = Symbol('schema_repository_token'); + +export const FieldSelectorRepositoryToken = Symbol('Field_Selector Component Repository Service Token'); + +export class LookupProperty extends InputBaseProperty { + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + + getEditorProperties(propertyData: any) { + const self = this; + + const editorOptions = propertyData.editor; + + const editorProperties = self.getComponentConfig( + propertyData, + { type: 'lookup' }, + { + dataSource: { + description: "数据源", + title: "数据源", + type: "string", + refreshPanelAfterChanged: true, + editor: { + type: "schema-selector", + title: "选择数据源", + editorParams: { + propertyData: editorOptions, + formBasicInfo: this.formSchemaUtils.getFormMetadataBasicInfo() + }, + displayFormatter: (items) => { + if (items && items.length) { + return items.map((item) => `${item['name']}(${item['code']})`).join(','); + } + return ''; + }, + viewOptions: [ + { + id: 'recommend', title: '推荐', type: 'List', + dataSource: 'Recommand', + enableGroup: true, + groupField: 'category', + groupFormatter: (value, data) => { + return `${value === 'local' ? '本地元数据' : '最近使用'}`; + } + }, + { id: 'total', title: '全部', type: 'List', dataSource: 'Total' } + ], + repositoryToken: LookupSchemaRepositoryToken, + onSubmitModal: (dataSourceSchema: any) => { + if (dataSourceSchema) { + delete editorOptions.mappingFields; + const formInfo = this.formSchemaUtils.getFormMetadataBasicInfo(); + // 获取数据源详细配置信息 + return this.metadataService.getPickMetadata(formInfo.relativePath, dataSourceSchema[0].data) + .then((res: any) => { + const metadata = JSON.parse(res?.metadata.content); + return metadata; + }); + } + } + }, + $converter: lookupDataSourceConverter + }, + displayType: { + description: "类型: 树列表、列表、双列表、左树右列表", + title: "展示类型", + type: "string", + editor: { + type: "combo-list", + disabled: true, + data: [ + { text: '列表', value: 'List' }, + { text: '树列表', value: 'TreeList' }, + { text: '左树右列表', value: 'NavTreeList' } + ], + textField: 'text', + valueField: 'value' + } + }, + idField: { + description: "数据源标识字段", + title: "标识字段", + type: "string", + visible: false + }, + textField: { + description: "显示文本字段", + title: "文本字段", + type: "string", + $converter: lookupTextFieldConverter, + editor: { + type: "field-selector", + textField: 'bindingPath', + idField: 'bindingPath', + editorParams: { + propertyData: editorOptions, + formBasicInfo: this.formSchemaUtils.getFormMetadataBasicInfo() + }, + columns: [ + { field: 'name', title: '名称' }, + { field: 'code', title: '编号' }, + { field: 'bindingPath', title: '绑定字段' }, + ], + repositoryToken: FieldSelectorRepositoryToken + } + }, + mappingFields: { + description: "字段映射", + title: "字段映射", + type: "string", + editor: { + type: "mapping-editor", + modalWidth: 800, + modalHeight: 600, + editable: false, + editorParams: { + propertyData: editorOptions, + formBasicInfo: this.formSchemaUtils.getFormMetadataBasicInfo() + }, + fromData: { + editable: false, + formatter: (cell, data) => { + return `${data.raw['name']} [${data.raw['bindingPath']}]`; + }, + idField: 'id', + textField: 'bindingPath', + valueField: 'bindingPath', + repositoryToken: FieldSelectorRepositoryToken + }, + toData: { + idField: 'id', + textField: 'bindingPath', + valueField: 'bindingPath', + dataSource: this.designViewModelUtils.getAllFields2TreeByVMId(this.viewModelId), + formatter: (cell, data) => { + return `${data.raw['name']} [${data.raw['bindingPath']}]`; + } + } + } + }, + multiSelect: { + description: "启用多选", + title: "启用多选", + type: "boolean", + editor: { + type: 'combo-list', + enableClear: false, + editable: false + }, + refreshPanelAfterChanged: true, + visible: this.isSimpleStringField() + }, + loadTreeDataType: { + description: "树形数据加载方式", + title: "数据加载方式", + type: "string", + editor: { + type: 'combo-list', + editable: false, + data: [ + { value: 'all', text: '全部加载' }, + { value: 'async', text: '分层加载' }, + { value: 'default', text: '默认' } + ], + textField: 'text', + valueField: 'value' + }, + visible: this.showLoadType(editorOptions) + }, + onlySelectLeaf: { + description: "仅选择叶子节点", + title: "仅选择叶子节点", + type: "boolean", + visible: this.showOnlySelectLeaf(editorOptions), + } + }, + (changeObject) => { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'data': { + break; + } + } + } + ); + return editorProperties; + } + + private isSimpleStringField() { + const fullPath = this.designViewModelField?.path || ''; + if (fullPath) { + const field = fullPath.indexOf('.') > -1 ? fullPath.split('.')[0] : fullPath; + const fields = this.designViewModelUtils.getAllFields2TreeByVMId(this.viewModelId); + if (fields && fields.length) { + const fieldInfo = fields.map(node => node.data).find(data => data.code === field); + return fieldInfo?.type?.$type === "StringType" && fieldInfo?.$type === 'SimpleField'; + } + return false; + } + return true; + } + + private getDisplayType(editorOptions: any) { + return editorOptions.displayType ? editorOptions.displayType.toUpperCase() : ''; + } + private showLoadType(editorOptions: any) { + const displayType = this.getDisplayType(editorOptions); + return (displayType === 'TREELIST' || displayType === 'NAVTREELIST'); + } + private showOnlySelectLeaf(editorOptions: any) { + return this.getDisplayType(editorOptions) === 'TREELIST'; + } +} diff --git a/packages/mobile-ui-vue/components/lookup/src/schema/lookup.schema.json b/packages/mobile-ui-vue/components/lookup/src/schema/lookup.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..3fb119a38a678789702203526c11bcf493f6f69d --- /dev/null +++ b/packages/mobile-ui-vue/components/lookup/src/schema/lookup.schema.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/lookup.schema.json", + "title": "lookup", + "description": "A Farris Lookup Component", + "type": "object", + "properties": { + "id": { + "description": "标识", + "type": "string" + }, + "type": { + "description": "控件类型", + "type": "string", + "default": "lookup" + }, + "appearance": { + "description": "外观", + "type": "object", + "properties": { + "class": { + "type": "string" + }, + "style": { + "type": "string" + } + }, + "default": {} + }, + "binding": { + "description": "绑定", + "type": "object", + "default": {} + }, + "idValue": { + "description": "ID值", + "type": "string" + }, + "required": { + "description": "必填", + "type": "boolean", + "default": false + }, + "readonly": { + "description": "只读", + "type": "boolean", + "default": false + }, + "disabled": { + "description": "禁用", + "type": "boolean", + "default": false + }, + "enableClear": { + "description": "", + "type": "boolean", + "default": false + }, + "placeholder": { + "description": "提示文本", + "type": "string" + }, + "displayType": { + "description": "", + "type": "string" + }, + "title": { + "description": "标题", + "type": "string", + "default": "请选择" + }, + "idField": { + "description": "值字段", + "type": "string", + "default": "value" + }, + "textField": { + "description": "显示字段", + "type": "string", + "default": "name" + }, + "mappingFields": { + "description": "帮助字段映射", + "type": "object", + "default": {} + }, + "uri": { + "type": "string", + "default": "" + }, + "dataSource": { + "type": "object", + "default": null + }, + "multiSelect": { + "type": "boolean", + "default": false + }, + "loadTreeDataType": { + "type": "string", + "default": "default" + }, + "onlySelectLeaf": { + "type": "boolean", + "default": false + }, + "onUpdate:modelValue": { + "description": "值更新事件", + "type": "string" + }, + "onChange": { + "description": "选择项变化事件", + "type": "string" + } + }, + "events": [ + "onUpdate:modelValue" + ], + "required": [ + "type" + ], + "ignore": [ + "id", + "appearance", + "visible" + ] +} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/lookup/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/lookup/src/schema/schema-mapper.ts new file mode 100644 index 0000000000000000000000000000000000000000..c443b783015a71e3c2a87ed5265791d293426c94 --- /dev/null +++ b/packages/mobile-ui-vue/components/lookup/src/schema/schema-mapper.ts @@ -0,0 +1,44 @@ +import { MapperFunction, resolveAppearance } from '@farris/mobile-ui-vue/dynamic-resolver'; +import { LookupDisplayType } from '../composition/index'; + +function converMappingFieldsToObject(mappingFields: any) { + if (typeof mappingFields == 'string' && mappingFields.startsWith('{') && mappingFields.endsWith('}')) { + mappingFields = mappingFields.replace(/'/g, '"'); + return {mappingFields: JSON.parse(mappingFields)}; + } + + return {mappingFields}; +} + +export const schemaMapper = new Map([ + ['appearance', resolveAppearance], + ['binding', 'modelValue'], + ['displayType', (key: string, value: any, resolvedSchema) => { + const mappings = { + 'List': LookupDisplayType.List, + 'TreeList': LookupDisplayType.Tree + }; + const displayType = mappings[value]; + return { displayType }; + }], + ['mappingFields', (key: string, value: any, resolvedSchema) => { + if (value) { + return converMappingFieldsToObject(value); + } + + const {mappingFields} = resolvedSchema; + return converMappingFieldsToObject(mappingFields); + }], + ['uri', (key: string, value: any, resolvedSchema) => { + if (value) { + return {uri: value}; + } + return {uri: resolvedSchema?.dataSource?.uri}; + }], + ['idField', (key: string, value: any, resolvedSchema) => { + if (value) { + return {idField: value}; + } + return {idField: resolvedSchema?.dataSource?.idField}; + }] +]); diff --git a/packages/mobile-ui-vue/components/lookup/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/lookup/src/schema/schema-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c8af7fc63d68147438fc329e74ebce565df4ef7 --- /dev/null +++ b/packages/mobile-ui-vue/components/lookup/src/schema/schema-resolver.ts @@ -0,0 +1,5 @@ +import { DynamicResolver } from "@farris/mobile-ui-vue/dynamic-resolver"; + +export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { + return schema; +} diff --git a/packages/mobile-ui-vue/components/modal/index.ts b/packages/mobile-ui-vue/components/modal/index.ts deleted file mode 100644 index ad48a036b87ac274deddb96ce85ae9ffe6236e95..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/index.ts +++ /dev/null @@ -1,32 +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 type { App, Plugin } from 'vue'; -import FModal from './src/modal.component'; -import FModalService from './src/composition/modal.service'; - -export * from './src/modal.props'; - -export const FM_MODAL_SERVICE_TOKEN = Symbol('FModalService'); - -FModal.install = (app: App) => { - app.component(FModal.name as string, FModal); - const modalInstance = new FModalService(app); - app.provide(FM_MODAL_SERVICE_TOKEN, modalInstance); - app.provide('FModalService', modalInstance); -}; - -export { FModal, FModalService }; -export default FModal as typeof FModal & Plugin; diff --git a/packages/mobile-ui-vue/components/modal/src/components/modal-context-holder.component.tsx b/packages/mobile-ui-vue/components/modal/src/components/modal-context-holder.component.tsx deleted file mode 100644 index 4f24d447152ff44f9dd2a7accbb98fc81fa279d4..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/components/modal-context-holder.component.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { defineComponent, SetupContext, shallowRef, VNode } from "vue"; - -export interface ContextHolder { - addModal: (modal: () => VNode) => () => void; -} -export default defineComponent({ - name: 'FModalContextHolder', - setup(props: any, context: SetupContext) { - const modals = shallowRef<(() => VNode)[]>([]); - const addModal = (modal: () => VNode) => { - modals.value.push(modal); - modals.value = modals.value.slice(); - return () => { - // 删除当前模态框 - modals.value = modals.value.filter(currentModal => currentModal !== modal); - }; - }; - context.expose({ addModal }); - return () => { - return modals.value.map(modal => modal()); - }; - } -}); diff --git a/packages/mobile-ui-vue/components/modal/src/composition/destroy.ts b/packages/mobile-ui-vue/components/modal/src/composition/destroy.ts deleted file mode 100644 index e68523fb9e051802488672d432b5cf8554e952c0..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/destroy.ts +++ /dev/null @@ -1 +0,0 @@ -export const destroyFunctionList: any[] = []; diff --git a/packages/mobile-ui-vue/components/modal/src/composition/modal.service.tsx b/packages/mobile-ui-vue/components/modal/src/composition/modal.service.tsx deleted file mode 100644 index ff8a7dc10cf9a99b557d3814e769b5b467ac6435..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/modal.service.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import { ref, h, render, VNode, cloneVNode, shallowRef, nextTick, App, AppContext, createApp, onUnmounted, onMounted, computed } from "vue"; -import { ModalFunctions, ModalOptions } from "./type"; -import FModal from '../modal.component'; -// import FarrisPlugin from '../../../plugin'; - -function getContentRender(props: ModalOptions) { - if (props.content && props.content.render) { - return props.content.render; - } - if (props.render && typeof props.render === 'function') { - return props.render; - } -} - -function createModalInstance(options: ModalOptions) { - const container = document.createElement('div'); - container.style.display = 'contents'; - - const app: App = createApp({ - setup(props, context) { - - onUnmounted(() => { - document.body.removeChild(container); - }); - - const modalRef = ref(); - - const customClass = ref(options.class || ''); - const showButtons = ref(!!options.showButtons); - const showHeader = ref(!!options.showHeader); - const showCloseButton = ref(options.showCloseButton == null ? true : options.showCloseButton); - const showModal = ref(true); - const modalTitle = ref(options.title || ''); - const acceptCallback = options.acceptCallback || (() => { }); - const rejectCallback = options.rejectCallback || (() => { }); - - const modalClosedCallback = options.closedCallback || (($event: Event) => { }); - const modalResizedCallback = options.resizeHandle || (($event: Event) => { }); - - const contentRender = getContentRender(options); - - const onClosed = ($event: Event) => { - showModal.value = false; - app.unmount(); - modalClosedCallback($event); - }; - - onMounted(() => { - // console.log(modalRef); - }); - - context.expose({ - modalRef - }); - - return () => ( - - {contentRender && contentRender(app)} - - ); - } - }); - document.body.appendChild(container); - // if (FarrisPlugin && !!FarrisPlugin.install) { - // app.use(FarrisPlugin); - // } - - app.mount(container); - - return app; -} - -export default class ModalService { - private appContext: AppContext | null = null; - - private modalRef = ref(); - - private activeModalIndex = ref(0); - - private modalRefs: Record = {}; - - private isUseEscCloseModal = ref(false); - - private activeModalInstance = computed(() => { - return this.modalRefs[this.activeModalIndex.value]; - }); - - private app: App; - - constructor(currentApp: App) { - this.app = currentApp; - this.appContext = currentApp ? currentApp._context : null; - } - - getCurrentModal() { - return this.activeModalInstance.value; - } - - private adaptToWindow(width: number, height: number) { - // 可视区域尺寸 - const { width: winWidth, height: winHeight } = { - width: window.innerWidth, - height: window.innerHeight - }; - - if (winWidth < width) { - width = winWidth; - } - - if (winHeight < height) { - height = winHeight; - } - - return { - width, - height - }; - } - - static show(options: ModalOptions) { - const modalOptions = Object.assign({ - title: '', - showButtons: true, - showHeader: true - }, options); - const app: App = createModalInstance(modalOptions); - return app; - } - - open(options: ModalOptions): ModalFunctions { - // 创建一个空白文档作为模态框容器,空白文档不属于DOM树,比创建一般节点性能较好,且仅渲染子元素 - const container = document.createDocumentFragment(); - - if (options.showMaxButton && options.fitContent) { - options.showMaxButton = false; - } - - const modalOptions = shallowRef(Object.assign({ - title: '', - showButtons: true, - showHeader: true, - }, options)); - - const showModal = ref(true); - const acceptCallback = modalOptions.value.acceptCallback || (() => { }); - const rejectCallback = modalOptions.value.rejectCallback || (() => { }); - - const modalClosedCallback = modalOptions.value.closedCallback || (($event?: Event, from?: string) => { }); - const modalResizedCallback = modalOptions.value.resizeHandle || (($event: Event) => { }); - let modalInstance: VNode | null; - - const contentRender = getContentRender(modalOptions.value); - - const onClosed = ($event?: Event) => { - showModal.value = false; - - const isCloseIconClick = ($event?.target as any)?.classList.contains('modal_close'); - - modalClosedCallback($event, this.isUseEscCloseModal.value ? 'esc' : isCloseIconClick ? 'icon' : 'button'); - - }; - - const destroy = ($event?: Event) => { - onClosed($event); - if (modalInstance) { - nextTick(() => { - if (this.modalRefs[this.activeModalIndex.value]) { - delete this.modalRefs[this.activeModalIndex.value]; - } - render(null, container as any); - modalInstance = null; - this.modalRef.value = null; - - if (this.modalRefs) { - const modals = Object.keys(this.modalRefs).map((key: string) => Number(key)); - if (modals.length > 0) { - this.activeModalIndex.value = Math.max(...modals); - } else { - this.activeModalIndex.value = 0; - } - } - - this.isUseEscCloseModal.value = false; - }); - } - - }; - - const onEsc = (payload: any) => { - this.isUseEscCloseModal.value = true; - this.activeModalInstance && this.activeModalInstance.value?.close(payload?.event); - }; - - const { width: modalWidth, height: modalHeight } = modalOptions.value; - const adaptedSize = this.adaptToWindow(modalWidth || 500, modalHeight || 320); - Object.assign(modalOptions.value, adaptedSize); - - const ModalWrapper = () => ( - - {contentRender && contentRender(this.app)} - - ); - - const renderModal = (props: Partial): VNode => { - const vnode = h(ModalWrapper, props); - vnode.appContext = this.appContext; - // if (FarrisPlugin && !!FarrisPlugin.install) { - // vnode.appContext?.app.use(FarrisPlugin); - // } - render(vnode, container as any); - return vnode; - }; - modalInstance = renderModal( - { - ...modalOptions.value, - // 'onUpdate:modelValue': onUpdateModelValue, - } - ); - - this.activeModalIndex.value++; - this.modalRefs[this.activeModalIndex.value] = this.modalRef.value; - - const update = (updateConfig: ModalOptions) => { - modalOptions.value = { - ...modalOptions.value, - ...updateConfig - }; - if (modalInstance) { - render(cloneVNode(modalInstance, { ...modalOptions }), container as any); - } - }; - - return { - update, - destroy, - modalRef: this.activeModalInstance - }; - } -} diff --git a/packages/mobile-ui-vue/components/modal/src/composition/resizeable/position.ts b/packages/mobile-ui-vue/components/modal/src/composition/resizeable/position.ts deleted file mode 100644 index 8aa65d4a5cd4b6cef9b9e09f51fa9b5cd6bfc658..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/resizeable/position.ts +++ /dev/null @@ -1,104 +0,0 @@ -export interface IPosition { - x: number; - y: number; -} - -export class Position implements IPosition { - constructor(public x: number, public y: number) { } - - static getTransformInfo(target: Element) { - const styleInfo = window.getComputedStyle(target); - const transforms = styleInfo.getPropertyValue('transform').replace(/[^-\d,]/g, '').split(','); - if (transforms.length >= 6) { - const translateX = parseInt(transforms[4], 10); - const translateY = parseInt(transforms[5], 10); - return {x: translateX, y: translateY }; - } - - return {x: 0, y: 0 }; - } - - static fromEvent(e: MouseEvent | TouchEvent, el: any = null) { - - if (this.isMouseEvent(e)) { - return new Position(e.clientX, e.clientY); - } - - if (el === null || e.changedTouches.length === 1) { - return new Position(e.changedTouches[0].clientX, e.changedTouches[0].clientY); - } - - for (let i = 0; i < e.changedTouches.length; i++) { - if (e.changedTouches[i].target === el) { - return new Position(e.changedTouches[i].clientX, e.changedTouches[i].clientY); - } - } - } - - static isMouseEvent(e: MouseEvent | TouchEvent): e is MouseEvent { - return Object.prototype.toString.apply(e).indexOf('MouseEvent') === 8; - } - - static isIPosition(obj): obj is IPosition { - return !!obj && ('x' in obj) && ('y' in obj); - } - - static getCurrent(el: Element) { - const pos = new Position(0, 0); - - if (window) { - const computed = window.getComputedStyle(el); - if (computed) { - const x = parseInt(computed.getPropertyValue('left'), 10); - const y = parseInt(computed.getPropertyValue('top'), 10); - pos.x = isNaN(x) ? 0 : x; - pos.y = isNaN(y) ? 0 : y; - } - return pos; - } - - return null; - } - - static copy(p: IPosition) { - return new Position(0, 0).set(p); - } - - get value(): IPosition { - return { x: this.x, y: this.y }; - } - - add(p: IPosition) { - this.x += p.x; - this.y += p.y; - return this; - } - - subtract(p: IPosition) { - this.x -= p.x; - this.y -= p.y; - return this; - } - - multiply(n: number) { - this.x *= n; - this.y *= n; - } - - divide(n: number) { - this.x /= n; - this.y /= n; - } - - reset() { - this.x = 0; - this.y = 0; - return this; - } - - set(p: IPosition) { - this.x = p.x; - this.y = p.y; - return this; - } -} diff --git a/packages/mobile-ui-vue/components/modal/src/composition/resizeable/resize-event.ts b/packages/mobile-ui-vue/components/modal/src/composition/resizeable/resize-event.ts deleted file mode 100644 index 6f3828a241b8f14aabb28ea63a2828b280dc9959..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/resizeable/resize-event.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface IResizeEvent { - size: { - width?: number; - height?: number; - }; - position: { - x?: number; - y?: number; - }; - transform?: string; -} diff --git a/packages/mobile-ui-vue/components/modal/src/composition/resizeable/size.ts b/packages/mobile-ui-vue/components/modal/src/composition/resizeable/size.ts deleted file mode 100644 index 10f028b9fabd2aa8b9264dece948c0c8679e54ca..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/resizeable/size.ts +++ /dev/null @@ -1,33 +0,0 @@ -export interface ISize { - width: number; - height: number; -} - -export class Size implements ISize { - constructor(public width: number, public height: number) {} - - static getCurrent(el: Element) { - const size = new Size(0, 0); - - if (window) { - const computed = window.getComputedStyle(el); - if (computed) { - size.width = parseInt(computed.getPropertyValue('width'), 10); - size.height = parseInt(computed.getPropertyValue('height'), 10); - } - return size; - } - - return null; - } - - static copy(s: Size) { - return new Size(0, 0).set(s); - } - - set(s: ISize) { - this.width = s.width; - this.height = s.height; - return this; - } -} diff --git a/packages/mobile-ui-vue/components/modal/src/composition/type.ts b/packages/mobile-ui-vue/components/modal/src/composition/type.ts deleted file mode 100644 index e39156ec691e10ca43277acca8e54bb1c7d91f8d..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/type.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { App, VNode } from "vue"; -import { JSX } from "vue/jsx-runtime"; -import { IResizeEvent } from "./resizeable/resize-event"; - -/** 自定义按钮结构 */ -export interface ModalButton { - name?: string; - class: string; - focusedByDefault?: boolean; - disable?: boolean; - iconClass?: string; - handle: ($event: MouseEvent, context: any) => any; - text: string; -} - -export interface ModalOptions { - class?: string; - title?: string; - width?: number; - height?: number; - minWidth?: number; - minHeight?: number; - showButtons?: boolean; - showHeader?: boolean; - showFloatingClose?: boolean; - content?: { render(): JSX.Element }; - render?: (app: App) => JSX.Element; - fitContent?: boolean; - buttons?: ModalButton[]; - draggable?: boolean; - showMaxButton?: boolean; - showCloseButton?: boolean; - acceptCallback?: () => void; - rejectCallback?: () => void; - closedCallback?: ($event?: Event, from?: 'esc' | 'icon' | 'button') => void; - resizeHandle?: (event: IResizeEvent) => void; - enableEsc?: boolean; -} - -export interface ModalFunctions { - update: (options: ModalOptions) => void; - destroy: () => void; - modalRef: any; -} - -export interface UseModal { - FModalContextHolder: () => VNode; - -} diff --git a/packages/mobile-ui-vue/components/modal/src/composition/use-draggable.ts b/packages/mobile-ui-vue/components/modal/src/composition/use-draggable.ts deleted file mode 100644 index 2a239d952c9d283574f033124f7768571db7710b..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/use-draggable.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { Ref, ref, watch } from "vue"; -import { Position } from "./resizeable/position"; - -export function useDraggable(props: any, context: any, canMove: Ref) { - - const dragTarget = ref(); - const allowDrag = ref(props.draggable); - const lockAxis = ref(props.lockAxis); - - const dragHandle = ref(); - const boundingElement = ref(); - const moving = ref(false); - - const origPos = ref(new Position(0, 0)); - const oldTranslate = ref(new Position(0, 0)); - const movingPos= ref(new Position(0, 0)); - const currentPos= ref(new Position(0, 0)); - - watch(() => canMove.value, (move) => { - dragHandle.value.style.cursor = move ? 'move' : 'default'; - }); - - function checkHandleTarget(target: EventTarget, element: Element) { - // Checks if the target is the element clicked, then checks each child element of element as well - // Ignores button clicks - - // Ignore elements of type button - if (element.tagName === 'BUTTON') { - return false; - } - - // If the target was found, return true (handle was found) - if (element === target) { - return true; - } - - // Recursively iterate this elements children - - for (const child in element.children) { - if (Object.prototype.hasOwnProperty.call(element.children, child)) { - if (checkHandleTarget(target, element.children[child])) { - return true; - } - } - } - - // Handle was not found in this lineage - // Note: return false is ignore unless it is the parent element - return false; - } - - function transform() { - let translateX = movingPos.value.x + oldTranslate.value.x; - let translateY = movingPos.value.y + oldTranslate.value.y; - - if (lockAxis.value === 'x') { - translateX = origPos.value?.x || 0; - movingPos.value.x = 0; - } else if (lockAxis.value === 'y') { - translateY = origPos.value?.y || 0; - movingPos.value.y = 0; - } - - const value = `translate3d(${Math.round(translateX)}px, ${Math.round(translateY)}px, 0px)`; - if (dragTarget.value) { - dragTarget.value.style.transform = value; - } - - // save current position - currentPos.value.x = translateX; - currentPos.value.y = translateY; - } - - function checkBounds(){ - if(!boundingElement.value || !dragTarget.value){ return null; } - const boundary = boundingElement.value.getBoundingClientRect(); - const targetRect = dragTarget.value.getBoundingClientRect(); - const result = { - 'top': boundary.top < targetRect.top, - 'right': boundary.right > targetRect.right, - 'bottom': boundary.bottom > targetRect.bottom, - 'left': boundary.left < targetRect.left - }; - - if (!result.top) { - movingPos.value.y -= targetRect.top - boundary.top; - } - - if (!result.bottom) { - movingPos.value.y -= targetRect.bottom - boundary.bottom; - } - - if (!result.right) { - movingPos.value.x -= targetRect.right - boundary.right; - } - - if (!result.left) { - movingPos.value.x -= targetRect.left - boundary.left; - } - - transform(); - return result; - } - - function moveTo(p?: Position) { - if (p) { - if (origPos.value) { - p.subtract(origPos.value); - } - - movingPos.value.set(p); - transform(); - checkBounds(); - } - } - - function onMouseMove(event: MouseEvent) { - if (moving.value && allowDrag.value) { - event.stopPropagation(); - event.preventDefault(); - - // Add a transparent helper div: - // helperBlock.add(); - moveTo(Position.fromEvent(event, dragHandle.value)); - } - } - - function stopMove() { - if (moving.value) { - - moving.value = false; - - oldTranslate.value.add(movingPos.value); - movingPos.value.reset(); - - dragTarget.value?.classList.remove('ng-dragging'); - - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', stopMove); - } - } - - function startMove() { - if (!moving.value && dragHandle.value) { - moving.value = true; - dragHandle.value.classList.add('ng-dragging'); - - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', stopMove); - } - } - - function resetTranslate() { - if (dragTarget.value) { - const pos = Position.getTransformInfo(dragTarget.value); - oldTranslate.value.set(pos); - return; - } - - oldTranslate.value.reset(); - } - - function onMouseDown(event: MouseEvent) { - if (!canMove.value) { - return; - } - // 1. skip right click; - if (event instanceof MouseEvent && event.button === 2) { - return; - } - // 2. if handle is set, the element can only be moved by handle - const target = event.target || event.srcElement; - if (dragHandle.value !== undefined && target && !checkHandleTarget(target, dragHandle.value)) { - return; - } - - // 3. if allow drag is set to false, ignore the mousedown - if (allowDrag.value === false) { - return; - } - - // 隐藏其他浮动层 - document.body.click(); - - event.stopPropagation(); - event.preventDefault(); - - origPos.value = Position.fromEvent(event, dragTarget.value); - resetTranslate(); - startMove(); - } - - function registerDraggle(handle: HTMLElement, hostElement: HTMLElement, boundingHost: HTMLElement | null) { - if (allowDrag.value && hostElement) { - if (handle) { - dragHandle.value = handle; - } else if (props.dragHandle) { - if (props.dragHandle instanceof HTMLElement) { - dragHandle.value = props.dragHandle; - } else if (typeof props.dragHandle === 'string') { - const element = hostElement.querySelector(props.dragHandle); - if(element) { - dragHandle.value = element as HTMLElement; - } - } - } - - dragTarget.value = hostElement; - boundingElement.value = boundingHost; - if (dragHandle.value) { - dragHandle.value.classList.add('ng-draggable'); - dragHandle.value.addEventListener('mousedown', onMouseDown); - } else { - allowDrag.value = false; - } - } - } - - return { - registerDraggle, resetTranslate - }; -} diff --git a/packages/mobile-ui-vue/components/modal/src/composition/use-resizeable.tsx b/packages/mobile-ui-vue/components/modal/src/composition/use-resizeable.tsx deleted file mode 100644 index e23782f59266e655aa21db2e46ffb8e28938173c..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/use-resizeable.tsx +++ /dev/null @@ -1,390 +0,0 @@ -import { ref } from "vue"; -import { Position } from "./resizeable/position"; -import { Size } from "./resizeable/size"; -import { IResizeEvent } from "./resizeable/resize-event"; - -export function useResizeable(props: any, context: any) { - - const resizedTarget = ref(); - const boundingElement = ref(); - - const origMousePos = ref(); - const origSize = ref(); - const origPos = ref(); - const currSize = ref(); - const currPos = ref(); - const direction = ref<{ 'n': boolean; 's': boolean; 'w': boolean; 'e': boolean } | null>(); - const resizeHandlerElement = ref(); - const modalBounding = ref(); - const resizedEventParam = ref(); - - const lastSize = ref(); - - const allowDrag = ref(props.draggable); - - const isMaximized = ref(false); - - function initBounding() { - const el = boundingElement.value || document.body; - const computed = window.getComputedStyle(el); - if(!computed){ - return; - } - - if (!resizedTarget.value) { - return; - } - - const transforms = Position.getTransformInfo(resizedTarget.value); - - - const bounding: any = {}; - - if (currPos.value) { - bounding.deltaL = resizedTarget.value.offsetLeft - currPos.value.x; - bounding.deltaT = resizedTarget.value.offsetTop - currPos.value.y; - } - - const p = computed.getPropertyValue('position'); - bounding.width = el.clientWidth; - bounding.height = el.clientHeight; - bounding.pr = parseInt(computed.getPropertyValue('padding-right'), 10); - bounding.pb = parseInt(computed.getPropertyValue('padding-bottom'), 10); - bounding.position = computed.getPropertyValue('position'); - - if (p === 'static') { - el.style.position = 'relative'; - } - - bounding.translateX = transforms.x; - bounding.translateY = transforms.y; - - modalBounding.value = bounding; - } - - function startResize(event: MouseEvent) { - - if (resizedTarget.value) { - origSize.value = Size.getCurrent(resizedTarget.value); - origPos.value = Position.getCurrent(resizedTarget.value); - currSize.value = origSize.value ? Size.copy(origSize.value) : null; - currPos.value = origPos.value ? Position.copy(origPos.value) : null; - - initBounding(); - - const directionType = (event.target as HTMLElement).getAttribute('type') || ''; - direction.value = { - n: !!directionType.match(/n/), - s: !!directionType.match(/s/), - w: !!directionType.match(/w/), - e: !!directionType.match(/e/) - }; - - } - } - - function doResize() { - if (resizedTarget.value) { - const container = resizedTarget.value as HTMLElement; - if (direction.value) { - if ((direction.value.n || direction.value.s) && currSize.value?.height) { - container.style.height = currSize.value.height + 'px'; - } - if ((direction.value.w || direction.value.e) && currSize.value?.width) { - container.style.width = currSize.value.width + 'px'; - } - - if (currPos.value) { - if (currPos.value?.x) { - container.style.left = currPos.value.x + 'px'; - } - if (currPos.value?.y) { - container.style.top = currPos.value.y + 'px'; - } - } - } - } - } - - function checkSize() { - - const minHeight = !props.minHeight ? 1 : props.minHeight; - const minWidth = !props.minWidth ? 1 : props.minWidth; - - if (currSize.value && currPos.value && direction.value && origSize.value) { - if (currSize.value.height < minHeight) { - currSize.value.height = minHeight; - - if (direction.value.n && origPos.value) { - currPos.value.y = origPos.value.y + (origSize.value.height - minHeight); - } - } - - if (currSize.value.width < minWidth) { - currSize.value.width = minWidth; - - if (direction.value.w && origPos.value) { - currPos.value.x = origPos.value.x + (origSize.value.width - minWidth); - } - } - - if (props.maxHeight && currSize.value.height > props.maxHeight) { - currSize.value.height = props.maxHeight; - - if (origPos.value && direction.value.n) { - currPos.value.y = origPos.value.y + (origSize.value.height - props.maxHeight); - } - } - - if (props.maxWidth && currSize.value.width > props.maxWidth) { - currSize.value.width = props.maxWidth; - - if (direction.value.w && origPos.value) { - currPos.value.x = origPos.value.x + (origSize.value.width - props.maxWidth); - } - } - } - - } - - function checkBounds(){ - if (boundingElement.value) { - const bounding = modalBounding.value; - - if (currPos.value && currSize.value && direction.value && origSize.value) { - const maxWidth = bounding.width - bounding.pr - bounding.deltaL - bounding.translateX - currPos.value.x; - const maxHeight = bounding.height - bounding.pb - bounding.deltaT - bounding.translateY - currPos.value.y; - - if (direction.value.n && (currPos.value.y + bounding.translateY < 0) && origPos.value) { - currPos.value.y = -bounding.translateY; - currSize.value.height = origSize.value.height + origPos.value.y + bounding.translateY; - } - - if (direction.value.w && (currPos.value.x + bounding.translateX) < 0 && origPos.value) { - currPos.value.x = -bounding.translateX; - currSize.value.width = origSize.value.width + origPos.value.x + bounding.translateX; - } - - if (currSize.value.width > maxWidth) { - currSize.value.width = maxWidth; - } - - if (currSize.value.height > maxHeight) { - currSize.value.height = maxHeight; - } - } - } - } - - function resizeTo(p: Position) { - - if (!origMousePos.value || !origSize.value || !origPos.value || !direction.value) { - return; - } - - p.subtract(origMousePos.value); - - const tmpX = p.x; - const tmpY = p.y; - - if (direction.value.n) { - // n, ne, nw - currPos.value!.y = origPos.value.y + tmpY; - currSize.value!.height = origSize.value.height - tmpY; - } else if (direction.value.s) { - // s, se, sw - currSize.value!.height = origSize.value.height + tmpY; - } - - if (direction.value.e) { - // e, ne, se - currSize.value!.width = origSize.value.width + tmpX; - } else if (direction.value.w) { - // w, nw, sw - currSize.value!.width = origSize.value.width - tmpX; - currPos.value!.x = origPos.value.x + tmpX; - } - - checkBounds(); - checkSize(); - doResize(); - } - - function onMouseMove(event: MouseEvent) { - if (!resizeHandlerElement.value) { - return; - } - - const pos = Position.fromEvent(event); - if (pos) { - resizeTo(pos); - } - } - - function getResizedEventParam(): IResizeEvent | null { - if (resizedTarget.value) { - const { width, height, x, y } = resizedTarget.value.getBoundingClientRect(); - const transforms = Position.getTransformInfo(resizedTarget.value); - - return { - size: { width, height }, - position: {x: x - transforms.x, y: y - transforms.y } - }; - } - return null; - } - - function onMouseLeave(event: MouseEvent) { - if (resizedTarget.value) { - const resizedParam = getResizedEventParam(); - resizedEventParam.value = resizedParam; - } - - origMousePos.value = undefined; - origSize.value = null; - origPos.value = null; - currSize.value = null; - currPos.value = null; - direction.value = null; - resizeHandlerElement.value = null; - - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseLeave); - } - - function registerMouseEvent() { - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseLeave); - } - - function onMouseDown(event: MouseEvent) { - // skip right click; - if (event instanceof MouseEvent && event.button === 2) { - return; - } - - if (!allowDrag.value) { - return; - } - // 隐藏其他浮动层 - document.body.click(); - // prevent default events - event.stopPropagation(); - event.preventDefault(); - - origMousePos.value = Position.fromEvent(event); - resizeHandlerElement.value = event.target; - startResize(event); - registerMouseEvent(); - } - - function renderResizeBar(hostElement: any) { - resizedTarget.value = hostElement; - return <> -
onMouseDown(e)}>
-
onMouseDown(e)}>
-
onMouseDown(e)}>
-
onMouseDown(e)}>
-
onMouseDown(e)}>
-
onMouseDown(e)}>
-
onMouseDown(e)}>
-
onMouseDown(e)}>
- ; - } - - function maximize(updateLastSize = true) { - // 隐藏其他浮动层 - document.body.click(); - - const container = boundingElement.value || document.body; - const maxSize = Size.getCurrent(container); - const target = resizedTarget.value as HTMLElement; - - if (updateLastSize && target) { - lastSize.value = getResizedEventParam(); - lastSize.value!.transform = target.style.transform; - } - - if (maxSize && target) { - currSize.value = maxSize; - - target.style.height = (currSize.value.height - 14) + 'px'; - target.style.width = (currSize.value.width - 14) + 'px'; - target.style.left = '7px'; - target.style.top = '7px'; - target.style.transform = ''; - - resizedEventParam.value = { - size: currSize.value, - position: {x: 0, y: 0} - }; - - allowDrag.value = false; - isMaximized.value = true; - } - } - - function restore() { - // 隐藏其他浮动层 - document.body.click(); - - if (lastSize.value) { - const size = { width: lastSize.value.size.width || 0, height: lastSize.value.size.height || 0 }; - const position = { x: (window.innerWidth - size.width) / 2, y: (window.innerHeight - size.height) / 2}; - - currSize.value?.set(size); - currPos.value?.set(position); - - const target = resizedTarget.value as HTMLElement; - target.style.height = size.height + 'px'; - target.style.width = size.width + 'px'; - target.style.left = `${position.x}px`; - target.style.top = `${position.y}px`; - target.style.transform = ''; - resizedEventParam.value = { - size, - position - }; - - allowDrag.value = props.draggable; - - isMaximized.value = false; - } - } - - function moveToCenter() { - if (resizedTarget.value) { - const modalSize = Size.getCurrent(resizedTarget.value); - if (modalSize) { - const { width, height } = modalSize; - resizedTarget.value.style.left = `${(window.innerWidth - width) / 2}px`; - resizedTarget.value.style.top = `${(window.innerHeight - height) / 2}px`; - resizedTarget.value.style.transform = ''; - - } - } - } - - function onWindowResize() { - const windowResizeHandle = () => { - if (isMaximized.value) { - maximize(false); - } else { - moveToCenter(); - } - // 隐藏其他浮动层 - document.body.click(); - }; - - window.addEventListener('resize', windowResizeHandle); - return () => { - window.removeEventListener('resize', windowResizeHandle); - }; - } - - const unWindowResizeHandle = onWindowResize(); - - return { - renderResizeBar, boundingElement, resizedEventParam, maximize, restore, allowDrag, isMaximized, unWindowResizeHandle - }; -} diff --git a/packages/mobile-ui-vue/components/modal/src/composition/use-shortcut.ts b/packages/mobile-ui-vue/components/modal/src/composition/use-shortcut.ts deleted file mode 100644 index 52e510aec03aad23d435c7ffad16c9fe82f99608..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/composition/use-shortcut.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { ref } from "vue"; - - -function registrationShortcutKeyEvent(keyCode: string, callBack: (...args) => void) { - if (keyCode) { - const registerKeyDown =(event: KeyboardEvent) =>{ - if (event.key.toLowerCase() === keyCode.toLowerCase()) { - callBack({ event, key: keyCode }); - } - }; - - document.addEventListener('keydown', registerKeyDown); - - return () => { - document.removeEventListener('keydown', registerKeyDown); - }; - } - -} - -export function useEsc(props: any, context: any) { - const enableEsc = ref(props.enableEsc); - - let removeEventListener: any = null; - - if (enableEsc.value) { - removeEventListener = registrationShortcutKeyEvent('Escape', ($event: any) => { - context.emit('esc', { event: $event.event, type: 'esc' }); - }); - - return { - remove: removeEventListener - }; - } - - return null; -} - -export function useEnter(props: any, context: any) { - const enableEnter = ref(props.enableEnter); - - let removeEventListener: any = null; - - if (enableEnter.value) { - removeEventListener = registrationShortcutKeyEvent('Enter', ($event: any) => { - context.emit('enter', { event: $event.event, type: 'enter' }); - }); - - return { - remove: removeEventListener - }; - } - - return null; - -} diff --git a/packages/mobile-ui-vue/components/modal/src/modal.component.tsx b/packages/mobile-ui-vue/components/modal/src/modal.component.tsx deleted file mode 100644 index 31b1e0a72ae5afd0fcc6ed7d8ff1fe69e6150996..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/modal.component.tsx +++ /dev/null @@ -1,375 +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 { computed, defineComponent, ref, SetupContext, Teleport, watch, Transition, onMounted, onUnmounted, provide, nextTick } 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.css'; -import { useEnter, useEsc } from './composition/use-shortcut'; - -export default defineComponent({ - name: 'FModal', - props: modalProps, - emits: ['update:modelValue', 'accept', 'cancel', 'closed', 'resize', 'esc', 'enter'] as (string[] & ThisType) | undefined, - setup(props: ModalProps, context: SetupContext) { - const width = ref(props.width || 300); - const height = ref(props.height || 200); - const modelValue = ref(props.modelValue); - const modalId = ref(''); - const customClass = ref(props.class); - const fitContent = ref(props.fitContent); - const showHeader = ref(props.showHeader); - const headerIconClass = ref(''); - const enableClose = ref(props.showCloseButton); - const enableMaximize = ref(props.showMaxButton); - const enableMinimize = ref(false); - const dialogType = ref(''); - const iframeSrc = ref(''); - const buttonAlignment = ref(''); - const showButtons = ref(props.showButtons); - const title = ref(props.title); - const resizeable = ref(props.reiszeable); - const containment = ref(props.containment || null); - const modalContainerRef = ref(); - - - function close($event: MouseEvent, accept?: boolean) { - modelValue.value = false; - context.emit('update:modelValue', false); - if (accept != null) { - context.emit(accept ? 'accept' : 'cancel'); - } - - context.emit('closed', $event); - } - const defaultButtons: ModalButton[] = [ - { - name: 'cancel', - text: '取消', - class: 'btn btn-secondary', - handle: ($event: MouseEvent) => { - close($event, false); - } - }, - { - name: 'accept', - text: '确定', - class: 'btn btn-primary', - handle: ($event: MouseEvent) => { - close($event, true); - } - } - ]; - const buttons = ref(props.buttons && props.buttons.length ? props.buttons : defaultButtons); - - const showHeaderIcon = computed(() => !!headerIconClass.value); - const showFooter = computed(() => !!showButtons.value && !!buttons.value); - - const modalHeaderRef = ref(); - const modalElementRef = ref(); - const maximized = ref(false); - - const { renderResizeBar, maximize, restore, boundingElement, - resizedEventParam, allowDrag, unWindowResizeHandle } = useResizeable(props, context); - const { registerDraggle } = useDraggable(props, context, allowDrag); - - // 监听modal 标题变化 - watch(() => props.title, (newValue, oldValue) => { - if (newValue !== oldValue) { - title.value = newValue; - } - }); - - // 监听打开关闭状态变化 - watch(() => props.modelValue, (newValue, oldValue) => { - if (newValue !== oldValue) { - modelValue.value = newValue; - } - }); - // 监听是否展示标题变化 - watch(() => props.showHeader, (newValue, oldValue) => { - if (newValue !== oldValue) { - showHeader.value = newValue; - } - }); - // 监听是否展示按钮变化 - // todo: 可以抽出公共方法 - watch(() => props.showButtons, (newValue, oldValue) => { - if (newValue !== oldValue) { - showButtons.value = newValue; - } - }); - - watch(() => resizedEventParam.value, (newSize, oldSize) => { - const newSizeValue = newSize || {}; - const oldSizeValue = oldSize || {}; - - if (JSON.stringify(newSizeValue) !== JSON.stringify(oldSizeValue)) { - context.emit('resize', { newSize, oldSize }); - } - }); - - function removeModalOpenStyle() { - const modalTotal = document.querySelectorAll('.farris-modal').length; - if (!modalTotal || modalTotal - 1 <= 0) { - document.body.classList.remove('modal-open'); - } - - if (modalContainerRef.value) { - modalContainerRef.value.classList.remove('show'); - } - } - - const showModal = computed(() => { - if (modelValue.value) { - document.body.classList.add('modal-open'); - } else { - removeModalOpenStyle(); - } - - return modelValue.value; - }); - - const modalContainerClass = computed(() => { - const classObject = { - modal: true, - 'farris-modal': true, - 'fade': true, - } as Record; - classObject['f-modal-fitContent'] = !!fitContent.value; - classObject.show = !!showModal.value; - return classObject; - }); - - const modalDialogClass = computed(() => { - const classObject = { 'modal-dialog': true }; - const customClassArray = customClass.value?.split(' '); - customClassArray?.reduce((target: any, className: string) => { - target[className] = true; - return target; - }, classObject); - return classObject; - }); - - const modalDialogStyle = computed(() => { - const styleObject = { - 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` - }; - return styleObject; - }); - - const modalContentClass = computed(() => { - const classObject = { - 'modal-content': true, - 'modal-content-has-header': showHeader.value - }; - return classObject; - }); - - const modalHeaderStyle = computed(() => { - const styleObject = { display: showHeader.value ? '' : 'none' }; - styleObject['pointer-events'] = allowDrag.value ? 'auto': 'none'; - return styleObject; - }); - - const headerMaxButtonClass = computed(() => { - const classObject = { 'f-icon': true, modal_maximize: true, modalrevert: maximized.value }; - return classObject; - }); - - const modalBodyClass = computed(() => { - const classObject = { 'modal-body': true, 'f-utils-flex-column': dialogType.value === 'iframe' }; - return classObject; - }); - - function buildFooterStyles(): Record { - return {}; - } - - const modalFooterStyle = computed(() => { - const styleObject = { textAlgin: buttonAlignment.value }; - const footerSytles = buildFooterStyles(); - return Object.assign(styleObject, footerSytles); - }); - - function maxDialog($event: MouseEvent) { - $event.stopPropagation(); - if (maximized.value) { - maximized.value = false; - restore(); - return; - } - maximize(); - maximized.value = true; - } - - async function customButtonClick(button: ModalButton, $event: MouseEvent) { - if (button.handle) { - const result = await button.handle($event, button); - if (result) { - context.emit('closed', $event); - } - } - } - - function updateModalOptions(options: ModalOptions) { - if (options.width) { - width.value = options.width; - } - if (options.height) { - height.value = options.height; - } - if (options.buttons) { - buttons.value = options.buttons; - } - if (options.title) { - title.value = options.title; - } - } - - let escEventHandler: any = null; - let enterEventHandler: any = null; - - onMounted(() => { - if (modalElementRef.value && !containment.value) { - containment.value = (modalElementRef.value as HTMLElement).parentElement; - boundingElement.value = containment.value; - registerDraggle(modalHeaderRef.value, modalElementRef.value, boundingElement.value); - } - if (showModal.value) { - document.body.classList.add('modal-open'); - } - - escEventHandler = useEsc(props, context); - enterEventHandler = useEnter(props, context); - - }); - - onUnmounted(() => { - removeModalOpenStyle(); - if (unWindowResizeHandle) { - unWindowResizeHandle(); - } - - if (escEventHandler) { - escEventHandler.remove(); - } - - if (enterEventHandler) { - enterEventHandler.remove(); - } - }); - - context.expose({ - modalElementRef, - updateModalOptions, - close, - maxDialog - }); - - function renderFloatingCloseButton() { - return
    - {enableMinimize.value && ( -
  • - -
  • - )} - {enableMaximize.value && ( -
  • - -
  • - )} - {enableClose.value && ( -
  • close(payload, false)} style="pointer-events: auto;"> - -
  • - )} -
; - } - - function renderFooterButtons() { - return ; - } - - function renderModalHeader() { - return ; - } - - function onClickModalBackdrop($event) { - $event.stopPropagation(); - } - - return () => { - return ( - - {showModal.value && - -
-
-
- { showHeader.value && renderModalHeader() } - -
- {context.slots.default?.()} - {dialogType.value === 'iframe' && ( - - )} -
- {showFooter.value && renderFooterButtons()} -
- {!fitContent.value && resizeable.value && modalElementRef.value && !maximized.value && renderResizeBar(modalElementRef.value)} -
-
-
} -
- ); - }; - } -}); diff --git a/packages/mobile-ui-vue/components/modal/src/modal.css b/packages/mobile-ui-vue/components/modal/src/modal.css deleted file mode 100644 index 080d52aa71e8e0b9e1fe7b77fdd02b361982cdf3..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/modal.css +++ /dev/null @@ -1,84 +0,0 @@ -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.5s ease; -} - -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.fv-resizable-handle { - position: absolute; - font-size: 0.1px; - display: block; - touch-action: none; - - &:hover { - background: rgba(42, 135, 255, .07); - } - - &.fv-resizable-n { - cursor: n-resize; - height: 0.4375rem; - width: 100%; - top: -0.3125rem; - left: 0; - } - - &.fv-resizable-e { - cursor: e-resize; - width: 0.4375rem; - right: -0.3125rem; - height: 100%; - top: 0; - } - - &.fv-resizable-s { - cursor: s-resize; - height: 0.4375rem; - width: 100%; - bottom: -0.3125rem; - left: 0; - } - - &.fv-resizable-w { - cursor: w-resize; - height: 100%; - width: 0.4375rem; - left: -0.3125rem; - top: 0; - } - - &.fv-resizable-ne { - cursor: ne-resize; - width: .75rem; - height: .75rem; - right: 1px; - top: 1px; - } - - &.fv-resizable-se { - cursor: se-resize; - width: .75rem; - height: .75rem; - right: 1px; - bottom: 1px; - } - - &.fv-resizable-nw { - cursor: nw-resize; - width: .75rem; - height: .75rem; - left: 1px; - top: 1px; - } - - &.fv-resizable-sw { - cursor: sw-resize; - width: .75rem; - height: .75rem; - left: 1px; - bottom: 1px; - } -} diff --git a/packages/mobile-ui-vue/components/modal/src/modal.props.ts b/packages/mobile-ui-vue/components/modal/src/modal.props.ts deleted file mode 100644 index 19fa251526cbd84092470e1af2353ab39b979e8e..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/src/modal.props.ts +++ /dev/null @@ -1,83 +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 { ModalButton } from './composition/type'; - -export type DragHandleType = HTMLElement | string; - -export const modalProps = { - /** - * 自定义类 - */ - class: { type: String, default: '' }, - /** - * 模态框标题 - */ - title: { type: String, default: '' }, - /** - * 模态框宽度 - */ - width: { type: Number, default: 500 }, - /** - * 模态框高度 - */ - height: { type: Number, default: 320 }, - /** - * 自定义按钮列表 - */ - buttons: { - type: Array, - default: [] - }, - /** - * 是否展示模态框 - */ - modelValue: { type: Boolean, default: false }, - /** - * 是否展示头部 - */ - showHeader: { type: Boolean, default: true }, - /** - * 是否展示默认按钮 - */ - showButtons: { type: Boolean, default: true }, - /** - * 是否启用自适应样式 - */ - fitContent: { type: Boolean, default: true }, - /** - * 是否展示右上角按钮 - */ - showCloseButton: { type: Boolean, default: true }, - showMaxButton: { type: Boolean, default: false }, - minHeight: {type: Number}, - maxHeight: {type: Number}, - minWidth: {type: Number}, - maxWidth: {type: Number}, - containment: {type: Object as PropType, default: null}, - reiszeable: { type: Boolean, default: false }, - draggable: { type: Boolean, default: false}, - dragHandle: { type: Object as PropType, default: null}, - closedCallback: { type: Function, default: null}, - resizeHandle: { type: Function, default: null}, - render: { type: Function, default: null}, - acceptCallback: { type: Function, default: null}, - rejectCallback: { type: Function, default: null}, - enableEsc: { type: Boolean, default: true }, - enableEnter: { type: Boolean, default: false } -}; - -export type ModalProps = Partial>; diff --git a/packages/mobile-ui-vue/components/modal/style.ts b/packages/mobile-ui-vue/components/modal/style.ts deleted file mode 100644 index 9fef5f8ed020e3bee1f22913ce5428e931e3a730..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/modal/style.ts +++ /dev/null @@ -1,3 +0,0 @@ -import "@farris/mobile-ui-vue/dependent-base/style"; -import "@farris/mobile-ui-vue/button/style"; -import "@theme-default/components/modal.css"; diff --git a/packages/mobile-ui-vue/components/navbar/README.md b/packages/mobile-ui-vue/components/navbar/README.md deleted file mode 100644 index c8d3bce2fdf63d88b0a773b1e2cb39c8d483de7a..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/navbar/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# fm-navbar -## API - -### Props - -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| title | 标题 | string | `''` | -| left-text | 左侧文案 | string | `''` | -| right-text | 右侧文案 | string | `''` | -| left-arrow | 是否显示左侧箭头 | boolean | `false` | -| border | 是否显示下边框 | boolean | `true` | -| fixed | 是否固定在顶部 | boolean | `false` | - -### Slots - -| 名称 | 说明 | -| ----- | ------------------ | -| title | 自定义标题 | -| left | 自定义左侧区域内容 | -| right | 自定义右侧区域内容 | - -### Events - -| 事件名 | 说明 | 回调参数 | -| ----------- | ------------------ | -------- | -| clickLeft | 点击左侧按钮时触发 | - | -| clickRight | 点击右侧按钮时触发 | - | \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/navbar/index.ts b/packages/mobile-ui-vue/components/navbar/index.ts index b4fef1e79fb6e5abe7aefe93c2897824854ddefe..41cc8f5bd6d8ddd6c3b84d030adcd6cca8261fc9 100644 --- a/packages/mobile-ui-vue/components/navbar/index.ts +++ b/packages/mobile-ui-vue/components/navbar/index.ts @@ -1,22 +1,15 @@ -import { withInstall, RegisterContext } from '@farris/mobile-ui-vue/common'; -import { propsResolverGenerator } from './src/navbar.props'; +import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile-ui-vue/common'; import NavbarInstallless from './src/navbar.component'; +import { propsResolverGenerator } from './src/navbar.props'; import NavbarDesign from './src/designer/nav.design.component'; const NAVBAR_REGISTERED_NAME = 'navbar'; const Navbar = withInstall(NavbarInstallless); -Navbar.register = (componentMap: Record, propsResolverMap: Record, - configresolverMap: Record, resolverMap: Record, registerContext: RegisterContext) => { - componentMap[NAVBAR_REGISTERED_NAME] = Navbar; - propsResolverMap[NAVBAR_REGISTERED_NAME] = propsResolverGenerator(registerContext); -}; +withRegister(Navbar, { name: NAVBAR_REGISTERED_NAME, propsResolverGenerator }); +withRegisterDesigner(Navbar, { name: NAVBAR_REGISTERED_NAME, propsResolverGenerator, designerComponent: NavbarDesign }); -Navbar.registerDesigner = (componentMap: Record, propsResolverMap: Record, - configresolverMap: Record, registerContext: RegisterContext) => { - componentMap[NAVBAR_REGISTERED_NAME] = NavbarDesign; - propsResolverMap[NAVBAR_REGISTERED_NAME] = propsResolverGenerator(registerContext); -}; +export * from './src/navbar.props'; export { Navbar }; export default Navbar; diff --git a/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx b/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx index 6c3581ea4755c769c0b96697244e8cb3d57b1624..ea72d34d8c78d81a874bafd5b85d0486ce622acf 100644 --- a/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx +++ b/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx @@ -1,4 +1,3 @@ - /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -14,38 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { computed, defineComponent, inject, onMounted, ref, SetupContext } from 'vue'; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; -import Navbar from '../..'; +import { Navbar } from '@farris/mobile-ui-vue/navbar'; import { navbarProps, NavbarProps } from '../navbar.props'; import { useDesignerRules } from './use-designer-rules'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common'; export default defineComponent({ - name: 'FNavDesign', - props: navbarProps, - emits: ['nav'] as (string[] & ThisType) | undefined, - setup(props: NavbarProps, context: SetupContext) { - const elementRef = ref(); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); - const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext,designerRulesComposition); - - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + name: 'FNavDesign', + props: navbarProps, + emits: [], + setup(props: NavbarProps, context) { + const navbarRef = ref(); + const elementRef = computed(() => { + return navbarRef.value?.elementRef; + }); - context.expose(componentInstance.value); + const designItemContext = inject('design-item-context') as DesignerItemContext; + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); - const navbarProps = computed(() => ({ - ...props, - })); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + context.expose(componentInstance.value); - return () => { - return ( - - ); - }; - } + const navbarProps = computed(() => ({ + ...props, + showToolbarOverlay: false, + })); + + return () => { + return ( + + ); + }; + } }); diff --git a/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts index afb90976dd732edf852ef0a7f589e31bdc46d029..c89b7e8e6ced24037b894c1d1d4bd0209d383876 100644 --- a/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts @@ -1,6 +1,5 @@ -import { DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { DraggingResolveContext } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; -import { nextTick, ref } from "vue"; +import { DesignerItemContext, UseDesignerRules, DraggingResolveContext } from "@farris/mobile-ui-vue/common"; +import { ref } from "vue"; import { NavBarProperty } from "../property-config/navbar.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -14,14 +13,13 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const isInFixedContextRules = true; function canAccepts(draggingContext: DraggingResolveContext): boolean { - return false; } - function checkCanMoveComponent() { return true; } + function checkCanDeleteComponent() { return true; } @@ -31,7 +29,6 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } function getStyles(): string { - // return 'border-radius: 12px'; return ' '; } @@ -47,7 +44,6 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const { schema } = designItemContext; return componentProp.getPropertyConfig(schema); } - return { canAccepts, diff --git a/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx b/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx index 3b0436073a51480ff0bc9851eca168dbf5661168..821048ba3f6c0dbefdd4bfeba613b4af324bdedc 100644 --- a/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx +++ b/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx @@ -13,54 +13,130 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { SetupContext, defineComponent } from 'vue'; +import { defineComponent, computed, ref } from 'vue'; +import { useBem } from '@farris/mobile-ui-vue/common'; +import { Icon } from '@farris/mobile-ui-vue/icon'; +import { Popover } from '@farris/mobile-ui-vue/popover'; +import { ButtonItem } from '@farris/mobile-ui-vue/button-group'; import { NAVBAR_NAME, NavbarProps, navbarProps } from './navbar.props'; export default defineComponent({ name: NAVBAR_NAME, props: navbarProps, emits: ['click-left', 'click-right'], - setup(props: NavbarProps, context: SetupContext) { + setup(props: NavbarProps, context) { + const { bem } = useBem(NAVBAR_NAME); const { emit, slots } = context; + const elementRef = ref(); + const showToolbarPopover = ref(false); - const handlerClickLeft = (event) => { + function onLeftClick(event: MouseEvent): void { emit('click-left', event); - }; + } - const handlerClickRight = (event) => { + function onRightClick(event: MouseEvent): void { emit('click-right', event); - }; + } - const navbarClass = { - 'fm-navbar': true, - 'fm-navbar-fixed': props.fixed, - 'fm-navbar-border-bottom': props.border - }; + const navbarClass = computed(() => ({ + [bem()]: true, + [bem('', 'fixed')]: props.fixed, + [bem('', 'bottom-border')]: props.showBottomBorder, + })); - return () => ( - <> -
- {(props.leftArrow || props.leftText) && ( -
- {slots.left ? ( - slots.left() - ) : ( - <> - {props.leftArrow && ( - - )} - {props.leftText && {props.leftText}} - - )} + const hasToolbarItems = computed(() => { + return Array.isArray(props.toolbarItems) && props.toolbarItems.length > 0; + }); + const shouldRenderLeft = computed(() => { + return props.leftArrow || !!props.leftText || !!slots.left; + }); + const shouldRenderRight = computed(() => { + return !!props.rightText || hasToolbarItems.value || !!slots.right; + }); + + function toolbarItemClass(item: ButtonItem) { + return { + [bem('toolbar-item')]: true, + [bem('toolbar-item', 'disabled')]: item.disabled, + }; + } + + function onToolbarItemClick(item: ButtonItem): void { + if (item.disabled) { + return; + } + showToolbarPopover.value = false; + item.onClick?.(); + } + + function renderLeftContent() { + return ( +
+ {slots.left ? ( + slots.left() + ) : ( + <> + {props.leftArrow && } + {props.leftText && {props.leftText}} + + )} +
+ ); + } + + function renderToolbar() { + return ( + {{ + triggerElement: () => ( + + ), + content: () => props.toolbarItems.filter((item) => { + return item.visible || item.visible === undefined; + }).map((item) => ( +
onToolbarItemClick(item)}> + {item.text}
+ )), + }}
+ ); + } + + function renderRightContent() { + return ( +
+ {slots.right ? ( + slots.right() + ) : ( + <> + {hasToolbarItems.value && renderToolbar()} + {!hasToolbarItems.value && {props.rightText}} + )} -
{slots.title ? slots.title() : props.title}
-
- {slots.right ? slots.right() : {props.rightText}} -
- + ); + } + + context.expose({ elementRef }); + + return () => ( +
+ {shouldRenderLeft.value && renderLeftContent()} +
{slots.title ? slots.title() : props.title}
+ {shouldRenderRight.value && renderRightContent()} +
); } }); diff --git a/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts b/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts index 33e9db98dfc2497ccb94150c1c3d68d99624daea..30fe551e60f094015866ee3920760280cc5f41a4 100644 --- a/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts +++ b/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts @@ -15,40 +15,47 @@ */ import { ExtractPropTypes } from 'vue'; import { getPropsResolverGenerator } from '../../dynamic-resolver'; +import { ButtonItem } from '@farris/mobile-ui-vue/button-group'; import navbarSchema from './schema/navbar.schema.json'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; -export const NAVBAR_NAME = 'fm-navbar'; +export const NAVBAR_NAME = 'fm-navbar'; export const navbarProps = { - title: { - type: String - }, - fixed: { - type: Boolean, - default: false - }, - leftText: { - type: String - }, - rightText: { - type: String - }, - leftArrow: { - type: Boolean, - default: true - }, - border: { - type: Boolean, - default: false - } + + /** 标题 */ + title: { type: String, default: '' }, + + /** 是否固定在页面顶部 */ + fixed: { type: Boolean, default: false }, + + /** 左侧的文本 */ + leftText: { type: String, default: '' }, + + /** 右侧的文本 */ + rightText: { type: String, default: '' }, + + /** 是否显示左侧的箭头图标 */ + leftArrow: { type: Boolean, default: true }, + + /** 是否在下方显示分隔线 */ + showBottomBorder: { type: Boolean, default: true }, + + /** 右侧的下拉按钮工具栏 */ + toolbarItems: { type: Array, default: [] }, + + /** 是否为工具栏的弹出气泡启用遮罩 */ + showToolbarOverlay: { type: Boolean, default: true }, + + customClass: { type: String, default: '' }, + customStyle: { type: String, default: '' }, }; export const propsResolverGenerator = getPropsResolverGenerator( - navbarProps, - navbarSchema, - schemaMapper, - schemaResolver + navbarProps, + navbarSchema, + schemaMapper, + schemaResolver ); export type NavbarProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/navbar/src/navbar.scss b/packages/mobile-ui-vue/components/navbar/src/navbar.scss index e0e44693bca0189840f6066f60a7a89008113476..2d8988936d287f8b54d9d01af0b0311b62efd5aa 100644 --- a/packages/mobile-ui-vue/components/navbar/src/navbar.scss +++ b/packages/mobile-ui-vue/components/navbar/src/navbar.scss @@ -1,72 +1,104 @@ @use '../../common/src/style/mixins/index.scss' as *; :root { - --fm-navbar-background: var(--fm-background-white); - --fm-navbar-color: var(--fm-text-color); - --fm-navbar-height: 44px; - --fm-navbar-title-size: 17px; - --fm-navbar-side-size: 16px; - --fm-navbar-arrow-size: 18px; + --fm-navbar-background: var(--fm-background-white); + --fm-navbar-color: var(--fm-text-color); + --fm-navbar-height: 44px; + --fm-navbar-title-size: 17px; + --fm-navbar-side-size: 16px; + --fm-navbar-arrow-size: 16px; + --fm-navbar-toolbar-item-height: 46px; } -.fm-navbar{ - position: relative; - z-index: 9; + +.fm-navbar { + position: relative; + z-index: 9; + display: flex; + align-items: center; + line-height: 1.5; + text-align: center; + user-select: none; + height: var(--fm-navbar-height); + color: var(--fm-navbar-color); + background-color: var(--fm-navbar-background); + + &--fixed { + position: fixed; + top: 0; + left: 0; + width: 100%; + } + + &--bottom-border { + @include hairline('bottom', #e6e6e6); + } + + &__title { + max-width: 60%; + margin: 0 auto; + font-weight: 500; + font-size: var(--fm-navbar-title-size); + @include ellipsis(); + } + + &__left, + &__right { + position: absolute; + top: 0; + bottom: 0; display: flex; align-items: center; - line-height: 1.5; - text-align: center; - user-select: none; - height: var(--fm-navbar-height); - color: var(--fm-navbar-color); - background-color: var(--fm-navbar-background); - &.fm-navbar-fixed { - position: fixed; - top: 0; - left: 0; - width: 100%; - } - &.fm-navbar-border-bottom{ - @include hairline('bottom', #eee); - } - &-title { - max-width: 60%; - margin: 0 auto; - font-weight: 500; - font-size: var(--fm-navbar-title-size); - @include ellipsis(); - } + max-width: 30%; + padding: 0 16px; + font-size: var(--fm-navbar-side-size); + @include ellipsis(); + cursor: pointer; + } + + &__left { + left: 0; - &-left, - &-right { - position: absolute; - top: 0; - bottom: 0; - display: flex; - align-items: center; - max-width: 30%; - padding: 0 16px; - font-size: var(--fm-navbar-side-size); - @include ellipsis(); - cursor: pointer; + &:active { + opacity: var(--fm-active-opacity); } - &-left { - left: 0; - &:active{ - opacity: var(--fm-active-opacity); - } - &.fm-navbar-left-padding{ - padding-left: 14px; - } + + &.fm-navbar-left-padding { + padding-left: 14px; } - &-right { - right: 0; - .fm-navbar-text:active { - opacity: var(--fm-active-opacity); - } + } + + &__right { + right: 0; + + .fm-navbar-text:active { + opacity: var(--fm-active-opacity); } - .fm-navbar-left-arrow{ - min-width: 1em; - margin-right: 4px; - font-size: var(--fm-navbar-arrow-size); + } + + &__left-arrow { + min-width: 1em; + margin-right: 4px; + font-size: var(--fm-navbar-arrow-size); + } + + &__toolbar { + padding: 0 !important; + + &-item { + padding: 0 var(--fm-padding-sm); + height: var(--fm-navbar-toolbar-item-height); + line-height: var(--fm-navbar-toolbar-item-height); + user-select: none; + cursor: pointer; + + &:not(:last-child) { + @include hairline('bottom'); + } + + &--disabled { + opacity: var(--fm-disabled-opacity); + cursor: not-allowed; + } } + } } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts b/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts index 4ad90c37cf959ab5cf100725ace670ac5f0105ac..a410ba193658acf58734ac25cbc17f3570a0ea95 100644 --- a/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts +++ b/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts @@ -1,21 +1,52 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { BaseControlProperty, ToolbarItemProperty } from "@farris/mobile-ui-vue/common"; +import { rightToolbarConverter } from "@farris/mobile-ui-vue/dynamic-resolver/src/converter"; export class NavBarProperty extends BaseControlProperty { + + private toolbarItemProperty: ToolbarItemProperty; + constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); + this.toolbarItemProperty = new ToolbarItemProperty(componentId, designerHostService); } + public getPropertyConfig(propertyData: any) { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); // 外观 this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); // 行为 - this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + this.propertyConfig.categories['behavior'] = this.getNavBarBehaviorConfig(propertyData); + this.propertyConfig.categories['rightToolbar'] = { + title: '工具栏', + $converter: rightToolbarConverter, + properties: { + items: { + title: '右侧工具栏', + editor: { + type: "collection-property-editor", + textField: 'text', + modalTitle: '右侧工具栏编辑器', + onSelectionChange: ({ selectedData, propertyConfig }) => { + propertyConfig.value = this.toolbarItemProperty.getPropertyConfig(selectedData.value, { + showAppearance: true, + }); + }, + defaultComponentSchema: { + id: 'button', + text: '按钮', + visible: true, + disabled: false + } + } + } + } + }; return this.propertyConfig; } - private getBehaviorConfig(propertyData) { + private getNavBarBehaviorConfig(propertyData: any) { return { description: "基本信息", title: "行为", diff --git a/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json b/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json index 1d7fac2629ed806801e18d001478c176d3ada77c..2de5cdc461abbab007e8e83a36848ab26da69ba1 100644 --- a/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json +++ b/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json @@ -6,13 +6,13 @@ "type": "object", "properties": { "id": { - "description": "标志", + "description": "标识", "type": "string" }, "type": { "description": "组件类型", "type": "string", - "default": "button-group" + "default": "navbar" }, "appearance": { "description": "外观", @@ -42,8 +42,70 @@ "type": "boolean", "default": true }, + "rightToolbar": { + "description": "右侧工具栏", + "type": "object", + "default": { + "items": [] + }, + "properties": { + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "description": "标识", + "type": "string" + }, + "type": { + "description": "组件类型", + "type": "string" + }, + "visible": { + "description": "是否可见", + "type": "boolean", + "default": true + }, + "disabled": { + "description": "禁用", + "type": "boolean", + "default": false + }, + "text": { + "description": "文本", + "type": "string" + }, + "icon": { + "description": "图标", + "type": "string" + }, + "onClick": { + "description": "点击事件", + "type": "string" + } + }, + "events": [ + "onClick" + ] + }, + "default": [] + } + } + }, "onClickLeft": { + "description": "右侧点击事件", "type": "string" + }, + "showBottomBorder": { + "description": "是否在下方显示分隔线", + "type": "boolean", + "default": true + }, + "showToolbarOverlay": { + "description": "是否为工具栏的弹出气泡启用遮罩", + "type": "boolean", + "default": true } }, "events": [ @@ -54,8 +116,6 @@ "type" ], "ignore": [ - "id", - "type", "appearance" ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts index bc768d5bab9395cbd5d4faa5f1d2f86552f57980..a37812cae58d68eb5d6b8aff1e16acec18707c94 100644 --- a/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts @@ -1,5 +1,6 @@ -import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver'; +import { resolveAppearance, resolveToolbar, MapperFunction } from '../../../dynamic-resolver'; export const schemaMapper = new Map([ - ['appearance', resolveAppearance] + ['appearance', resolveAppearance], + ['rightToolbar', resolveToolbar], ]); diff --git a/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts index b02bdf93eec9060948f579c53aa81e3963a7d706..c1bf8da88a08c01a3c8d6e9f3d81859d84d87aa6 100644 --- a/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts +++ b/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts @@ -1,5 +1,5 @@ import { DynamicResolver } from "../../../dynamic-resolver"; export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { - return schema; + return schema; } diff --git a/packages/mobile-ui-vue/components/notify/src/index.tsx b/packages/mobile-ui-vue/components/notify/src/index.tsx index 26c7dcd03f9751a397f99afabcb1efeccfefea37..0aa07a2eb5423e998e5655c4c2a71c5d6bed6359 100644 --- a/packages/mobile-ui-vue/components/notify/src/index.tsx +++ b/packages/mobile-ui-vue/components/notify/src/index.tsx @@ -1,12 +1,17 @@ -import FMNotify from './notify.component'; -import { isObject, inBrowser } from '@farris/mobile-ui-vue/common'; -import { mountComponent, usePopupState } from '@farris/mobile-ui-vue/common'; import { App } from 'vue'; +import { isObject, inBrowser, mountComponent, usePopupState } from '@farris/mobile-ui-vue/common'; +import FMNotify from './notify.component'; +import { NotifyProps, NotifyType } from './notify.props'; + let instance; let timer; -function defaultOptions() { + +type NotifyOptions = string | Partial + +function defaultOptions(): NotifyProps { return { - type: 'info', + type: NotifyType.info, + show: false, message: '', duration: 3000, color: '#fff', @@ -14,8 +19,8 @@ function defaultOptions() { }; } -function parseOptions(message) { - return isObject(message) ? message : { message }; +function parseOptions(options: NotifyOptions) { + return isObject(options) ? options : { message: options }; } const initInstance = () => { @@ -27,7 +32,7 @@ const initInstance = () => { })); }; -function Notify(options) { +function Notify(options: NotifyOptions) { if (!inBrowser) { return; } @@ -44,7 +49,7 @@ function Notify(options) { instance.open(options); clearTimeout(timer); - if (options.duration > 0) { + if (options.duration && options.duration > 0) { timer = setTimeout(Notify.clear, options.duration); } @@ -57,29 +62,29 @@ Notify.clear = () => { } }; -Notify.info = (options) => { +Notify.info = (options: NotifyOptions) => { Notify.resetDefaultOptions(); - return Notify({ ...parseOptions(options), type: 'info' }); + return Notify({ ...parseOptions(options), type: NotifyType.info }); }; -Notify.success = (options) => { +Notify.success = (options: NotifyOptions) => { Notify.resetDefaultOptions(); - return Notify({ ...parseOptions(options), type: 'success' }); + return Notify({ ...parseOptions(options), type: NotifyType.success }); }; -Notify.error = (options) => { +Notify.error = (options: NotifyOptions) => { Notify.resetDefaultOptions(); - return Notify({ ...parseOptions(options), type: 'error' }); + return Notify({ ...parseOptions(options), type: NotifyType.error }); }; -Notify.warning = (options) => { +Notify.warning = (options: NotifyOptions) => { Notify.resetDefaultOptions(); - return Notify({ ...parseOptions(options), type: 'warning' }); + return Notify({ ...parseOptions(options), type: NotifyType.warning }); }; Notify.currentOptions = defaultOptions(); -Notify.setDefaultOptions = (options) => { +Notify.setDefaultOptions = (options: Partial) => { Object.assign(Notify.currentOptions, options); }; @@ -88,7 +93,7 @@ Notify.resetDefaultOptions = () => { }; Notify.install = (app: App) => { - app.component(FMNotify.name, FMNotify); + app.component((FMNotify.name as string), FMNotify); app.config.globalProperties.$notify = Notify; }; diff --git a/packages/mobile-ui-vue/components/notify/src/notify.component.tsx b/packages/mobile-ui-vue/components/notify/src/notify.component.tsx index 3be1b9f363472e7ca062f4934f0296409680b90c..ce049268cd61f13fb1d786b89513f61dc36cbf79 100644 --- a/packages/mobile-ui-vue/components/notify/src/notify.component.tsx +++ b/packages/mobile-ui-vue/components/notify/src/notify.component.tsx @@ -1,33 +1,16 @@ -/** - * 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 { SetupContext, computed, defineComponent } from 'vue'; -import { NotifyProps, notifyProps } from './notify.props'; +import { computed, defineComponent } from 'vue'; import { useBem } from '@farris/mobile-ui-vue/common'; - -const name = 'fm-notify'; +import { NOTIFY_NAME, NotifyProps, notifyProps } from './notify.props'; export default defineComponent({ - name, + name: NOTIFY_NAME, props: notifyProps, emits: ['click'], - setup(props: NotifyProps, context: SetupContext) { - const { bem } = useBem(name); + setup(props: NotifyProps) { + const { bem } = useBem(NOTIFY_NAME); const notifyClass = computed(() => ({ - [name]: true, + [bem()]: true, [bem('', props.type)]: true, [`${props.className}`]: true })); diff --git a/packages/mobile-ui-vue/components/notify/src/notify.props.ts b/packages/mobile-ui-vue/components/notify/src/notify.props.ts index 110dc36af2927d9c1d891be51884dade54a1ed71..9180fee9102a729927f33e11de4541997281ef96 100644 --- a/packages/mobile-ui-vue/components/notify/src/notify.props.ts +++ b/packages/mobile-ui-vue/components/notify/src/notify.props.ts @@ -1,48 +1,28 @@ -/** - * 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'; -type NotifyType = 'info' | 'success' | 'danger' | 'warning'; +export enum NotifyType { + info= 'info', + success= 'success', + error= 'error', + warning= 'warning' +}; + +export const NOTIFY_NAME = 'FmNotify'; export const notifyProps = { - show: { - type: Boolean, - default: false, - }, - type: { - type: String as PropType, - default: 'info', - }, - color: { - type: String, - default: '#fff', - }, - message: { - type: String, - }, - duration: { - type: Number, - default: 3000, - }, - className: { - type: String, - }, - background: { - type: String, - }, + show: { type: Boolean, default: false }, + + type: { type: String as PropType, default: NotifyType.info }, + + color: { type: String, default: '#fff' }, + + message: { type: String, default: '' }, + + duration: { type: Number, default: 3000 }, + + className: { type: String, default: undefined }, + + background: { type: String, default: undefined } }; export type NotifyProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/number-input/src/designer/number-input.design.component.tsx b/packages/mobile-ui-vue/components/number-input/src/designer/number-input.design.component.tsx index e030ff535714a3aac9aae64afd22fb66095d1ec1..ec373e0d663c1d746f38e7a642c04fe600cc3dd0 100644 --- a/packages/mobile-ui-vue/components/number-input/src/designer/number-input.design.component.tsx +++ b/packages/mobile-ui-vue/components/number-input/src/designer/number-input.design.component.tsx @@ -1,30 +1,45 @@ -import { defineComponent, inject, onMounted, ref, SetupContext } from 'vue'; - -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; import { useDesignerRules } from './use-designer-rules'; +import NumberInput from '../number-input.component'; import { numberInputProps } from '../number-input.props'; -import InputGroup from '../..'; export default defineComponent({ - name: 'FmNunberInputDesign', - props: numberInputProps, - emits: [], - setup(props, context: SetupContext) { - const elementRef = ref(); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); - const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + name: 'FmNunberInputDesign', + inheritAttrs: false, + props: extractProperties(numberInputProps, ['placeholder', 'enableNull']), + setup(props, context) { + const elementRef = ref(); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - context.expose(componentInstance.value); + context.expose(componentInstance.value); + const inputProps = computed(()=>{ + return { + ...props, + editable: false, + enableNull: true + }; + }); - return () => ( - - ); - } + return () => ; + } }); diff --git a/packages/mobile-ui-vue/components/number-input/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/number-input/src/designer/use-designer-rules.ts index 12ebbbcebe3d663c9e3d5b5ab4fdacbea7f34108..4e6377b410c9572c22d3fde92c966e67701d2cf9 100644 --- a/packages/mobile-ui-vue/components/number-input/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/number-input/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { NumberInputProperty } from "../property-config/number-input.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { diff --git a/packages/mobile-ui-vue/components/number-input/src/number-input.component.tsx b/packages/mobile-ui-vue/components/number-input/src/number-input.component.tsx index c17b190c06e536e4b8109eed3d452f3f45e4e4e0..94c12ba6f744f33acf999607f225bd54bbab3d8c 100644 --- a/packages/mobile-ui-vue/components/number-input/src/number-input.component.tsx +++ b/packages/mobile-ui-vue/components/number-input/src/number-input.component.tsx @@ -1,38 +1,16 @@ -/** - * 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, SetupContext } from 'vue'; +import { defineComponent } from 'vue'; import { useBem } from '@farris/mobile-ui-vue/common'; import InputGroup from "@farris/mobile-ui-vue/input-group"; -import { NUMBER_INPUT_NAME, numberInputProps, NumberInputProps } from './number-input.props'; +import { NUMBER_INPUT_NAME, numberInputProps } from './number-input.props'; export default defineComponent({ name: NUMBER_INPUT_NAME, props: numberInputProps, - emits: ['update:modelValue'], - setup(props: NumberInputProps, context: SetupContext) { + setup(props) { const { bem } = useBem(NUMBER_INPUT_NAME); - const { emit } = context; - - const handleValueChange = (value: string | number) => { - emit('update:modelValue', value); - }; return () => ( - + ); } }); diff --git a/packages/mobile-ui-vue/components/number-input/src/number-input.props.ts b/packages/mobile-ui-vue/components/number-input/src/number-input.props.ts index fb1e2812957c89b687d23f527d03946465e89d54..a82429e61fd5a818812824c914b1fe7c5e9d0825 100644 --- a/packages/mobile-ui-vue/components/number-input/src/number-input.props.ts +++ b/packages/mobile-ui-vue/components/number-input/src/number-input.props.ts @@ -1,5 +1,5 @@ import { ExtractPropTypes } from 'vue'; -import { inputCommonProps } from '@farris/mobile-ui-vue/input-group'; +import { inputProps } from '@farris/mobile-ui-vue/input-group'; import { getPropsResolverGenerator } from '@farris/mobile-ui-vue/dynamic-resolver'; import numberInputSchema from './schema/number-input.schema.json'; import { schemaMapper } from './schema/schema-mapper'; @@ -8,11 +8,9 @@ import { schemaResolver } from './schema/schema-resolver'; export const NUMBER_INPUT_NAME = 'FmNumberInput'; export const numberInputProps = { - ...inputCommonProps, - enableNull: { type: Boolean, default: undefined }, - precision: { type: Number, default: 2 } -} as Record; + ...inputProps +}; export type NumberInputProps = ExtractPropTypes; -export const propsResolverGenerator = getPropsResolverGenerator(numberInputProps, numberInputSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator(numberInputProps, numberInputSchema, schemaMapper, schemaResolver); diff --git a/packages/mobile-ui-vue/components/number-input/src/property-config/number-input.property-config.ts b/packages/mobile-ui-vue/components/number-input/src/property-config/number-input.property-config.ts index 63918fbc45a758a726c2a9143a77a7a1b788d1b1..fd36c058e1a9b20c43bd563c3a5408c02ff12265 100644 --- a/packages/mobile-ui-vue/components/number-input/src/property-config/number-input.property-config.ts +++ b/packages/mobile-ui-vue/components/number-input/src/property-config/number-input.property-config.ts @@ -1,30 +1,33 @@ -import { InputBaseProperty } from "@farris/mobile-ui-vue/common/src/entity/input-base-property"; +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; export class NumberInputProperty extends InputBaseProperty { + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } - constructor(componentId: string, designerHostService: any) { - super(componentId, designerHostService); - } - - - getEditorProperties(propertyData: any) { - return this.getComponentConfig(propertyData, {}, { - precision: { - description: "", - title: "精度", - type: "number" - }, - max: { - description: "", - title: "最大值", - type: "number" - }, - min: { - description: "", - title: "最小值", - type: "number" - }, - }); - } - + getEditorProperties(propertyData: any) { + return this.getComponentConfig( + propertyData, + {}, + { + precision: { + description: '', + title: '精度', + type: 'number' + }, + max: { + description: '', + title: '最大值', + type: 'number', + visible: false + }, + min: { + description: '', + title: '最小值', + type: 'number', + visible: false + } + } + ); + } } diff --git a/packages/mobile-ui-vue/components/number-input/src/schema/number-input.schema.json b/packages/mobile-ui-vue/components/number-input/src/schema/number-input.schema.json index 867a2b29ae9ee3a2f9dde810e0e9d8cd9b5869dc..92364f331ddac141874094255319534a48facf69 100644 --- a/packages/mobile-ui-vue/components/number-input/src/schema/number-input.schema.json +++ b/packages/mobile-ui-vue/components/number-input/src/schema/number-input.schema.json @@ -79,7 +79,6 @@ "ignore": [ "id", "appearance", - "binding", "visible" ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/page-body-container/index.ts b/packages/mobile-ui-vue/components/page-body-container/index.ts index 25200201a9dc67263e143b81d4e86e91a080c07e..f13c4f83182167ab019b376a3806a0a0372d626b 100644 --- a/packages/mobile-ui-vue/components/page-body-container/index.ts +++ b/packages/mobile-ui-vue/components/page-body-container/index.ts @@ -22,14 +22,14 @@ import { propsResolverGenerator } from './src/page-body-container.props'; const PAGE_BODY_CONTAINER_REGISTER_NAME = 'page-body-container'; const PageBodyContainer = withInstall(PageBodyContainerInstallless); -PageBodyContainer.register = (componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext): void => { +PageBodyContainer.register = (componentMap: Record, propsResolverMap: Record, + configresolverMap: Record, resolverMap: Record, registerContext: RegisterContext): void => { componentMap[PAGE_BODY_CONTAINER_REGISTER_NAME] = PageBodyContainer; propsResolverMap[PAGE_BODY_CONTAINER_REGISTER_NAME] = propsResolverGenerator(registerContext); }; PageBodyContainer.registerDesigner = (componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext): void => { + configresolverMap: Record, registerContext: RegisterContext): void => { componentMap[PAGE_BODY_CONTAINER_REGISTER_NAME] = PageBodyContainerDesign; propsResolverMap[PAGE_BODY_CONTAINER_REGISTER_NAME] = propsResolverGenerator(registerContext); }; @@ -37,4 +37,3 @@ PageBodyContainer.registerDesigner = (componentMap: Record, propsRe export * from './src/page-body-container.props'; export { PageBodyContainer }; export default PageBodyContainer; - diff --git a/packages/mobile-ui-vue/components/page-body-container/src/designer/page-body-container.design.component.tsx b/packages/mobile-ui-vue/components/page-body-container/src/designer/page-body-container.design.component.tsx index c86873fb74227ad7889de006491233095edd8c27..6a35ae959782306737902bf343caa37898f39846 100644 --- a/packages/mobile-ui-vue/components/page-body-container/src/designer/page-body-container.design.component.tsx +++ b/packages/mobile-ui-vue/components/page-body-container/src/designer/page-body-container.design.component.tsx @@ -1,7 +1,7 @@ import { SetupContext, defineComponent, inject, onMounted, ref } from 'vue'; import { PageBodyContainerProps, pageBodyContainerProps } from '../page-body-container.props'; import { useDesignerRules } from './use-designer-rules'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common';; export default defineComponent({ name: 'FmPageBodyContainerDesign', diff --git a/packages/mobile-ui-vue/components/page-body-container/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/page-body-container/src/designer/use-designer-rules.ts index 6d3cde536e0f2516b3ad2893f797988825d4d86b..c94334a2b7c8a59a8d209a6517dec000975e5c0e 100644 --- a/packages/mobile-ui-vue/components/page-body-container/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/page-body-container/src/designer/use-designer-rules.ts @@ -1,7 +1,7 @@ import { nextTick, ref } from "vue"; import { PAGE_BODY_CONTAINER_NAME } from '../page-body-container.props'; -import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; -import { DesignerItemContext } from "@farris/mobile-ui-vue/designer-canvas"; +import { DesignerHostService, DraggingResolveContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { DesignerItemContext } from "@farris/mobile-ui-vue/common"; import { PageBodyContainerProperty } from "../property-config/page-body-container.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { @@ -31,10 +31,10 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe function checkCanMoveComponent() { - return true; + return !isInFixedContextRules; } function checkCanDeleteComponent() { - return true; + return !isInFixedContextRules; } function hideNestedPaddingInDesginerView() { @@ -52,8 +52,11 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const componentProp = new PageBodyContainerProperty(componentId, designerHostService); const { schema } = designItemContext; return componentProp.getPropertyConfig(schema); - } + } + function getDraggableDesignItemElement(context: DesignerItemContext) { + return context.designerItemElementRef; + } return { canAccepts, @@ -63,6 +66,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe checkCanDeleteComponent, hideNestedPaddingInDesginerView, getDesignerClass, - getPropsConfig + getPropsConfig, + getDraggableDesignItemElement, }; } diff --git a/packages/mobile-ui-vue/components/page-body-container/src/property-config/page-body-container.property-config.ts b/packages/mobile-ui-vue/components/page-body-container/src/property-config/page-body-container.property-config.ts index c439fbc134ce52c51582b74f8eb699d9502c3fdc..4c1c7876ce225099726bb225e223d1c9bb91e18e 100644 --- a/packages/mobile-ui-vue/components/page-body-container/src/property-config/page-body-container.property-config.ts +++ b/packages/mobile-ui-vue/components/page-body-container/src/property-config/page-body-container.property-config.ts @@ -1,9 +1,11 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { ContainerBaseProperty } from "@farris/mobile-ui-vue/common"; + +export class PageBodyContainerProperty extends ContainerBaseProperty { -export class PageBodyContainerProperty extends BaseControlProperty { constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); } + public getPropertyConfig(propertyData: any) { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); diff --git a/packages/mobile-ui-vue/components/page-container/index.ts b/packages/mobile-ui-vue/components/page-container/index.ts index 18325b70fe9fc85f5eb09e223ad35dd534b8304a..b72d84943daebd3bb5d1a9db6adb73d9bea35b2a 100644 --- a/packages/mobile-ui-vue/components/page-container/index.ts +++ b/packages/mobile-ui-vue/components/page-container/index.ts @@ -23,16 +23,16 @@ const PAGE_CONTAINER_REGISTERED_NAME = 'page-container'; const PageContainer = withInstall(PageContainerInstallless); PageContainer.register = ( - componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext + componentMap: Record, propsResolverMap: Record, + configresolverMap: Record, resolverMap: Record, registerContext: RegisterContext ): void => { componentMap[PAGE_CONTAINER_REGISTERED_NAME] = PageContainer; propsResolverMap[PAGE_CONTAINER_REGISTERED_NAME] = propsResolverGenerator(registerContext); }; PageContainer.registerDesigner = ( - componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext + componentMap: Record, propsResolverMap: Record, + configresolverMap: Record, registerContext: RegisterContext ): void => { componentMap[PAGE_CONTAINER_REGISTERED_NAME] = PageContainerDesign; propsResolverMap[PAGE_CONTAINER_REGISTERED_NAME] = propsResolverGenerator(registerContext); diff --git a/packages/mobile-ui-vue/components/page-container/src/designer/page-container.design.component.tsx b/packages/mobile-ui-vue/components/page-container/src/designer/page-container.design.component.tsx index 59f267cd386bdf3dff20f87b87f002483b81407c..c3114429478b7e420040c1ba5b1a42d8bb465534 100644 --- a/packages/mobile-ui-vue/components/page-container/src/designer/page-container.design.component.tsx +++ b/packages/mobile-ui-vue/components/page-container/src/designer/page-container.design.component.tsx @@ -1,7 +1,7 @@ import { SetupContext, defineComponent, inject, onMounted, ref } from 'vue'; import { PageContainerProps, pageContainerProps } from '../page-container.props'; import { useDesignerRules } from './use-designer-rules'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common';; export default defineComponent({ name: 'FmPageContainerDesign', diff --git a/packages/mobile-ui-vue/components/page-container/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/page-container/src/designer/use-designer-rules.ts index f5a8fc603e87dc9a1db0de633a8bae409d629eb6..efe4894171db28ab0a003760025ff884876cecac 100644 --- a/packages/mobile-ui-vue/components/page-container/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/page-container/src/designer/use-designer-rules.ts @@ -1,7 +1,7 @@ import { ref } from "vue"; import { PAGE_CONTAINER_NAME } from '../page-container.props'; -import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { DraggingResolveContext } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; +import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { DraggingResolveContext } from "@farris/mobile-ui-vue/common"; import { PageContainerProperty } from "../property-config/page-container.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { @@ -42,10 +42,10 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe return true; } - function checkCanMoveComponent() { - return true; + return !isInFixedContextRules; } + function checkCanDeleteComponent() { return !isInFixedContextRules; } @@ -55,15 +55,13 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } function getStyles(): string { - // return 'border-radius: 12px'; - return ' '; + return ''; } function getDesignerClass(): string { return PAGE_CONTAINER_NAME; } - /** * 获取属性配置 */ @@ -71,8 +69,11 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const componentProp = new PageContainerProperty(componentId, designerHostService); const { schema } = designItemContext; return componentProp.getPropertyConfig(schema); - } + } + function getDraggableDesignItemElement(context: DesignerItemContext) { + return context.designerItemElementRef; + } return { canAccepts, @@ -83,6 +84,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe hideNestedPaddingInDesginerView, getStyles, getDesignerClass, - getPropsConfig + getPropsConfig, + getDraggableDesignItemElement, }; } diff --git a/packages/mobile-ui-vue/components/page-container/src/property-config/page-container.property-config.ts b/packages/mobile-ui-vue/components/page-container/src/property-config/page-container.property-config.ts index c5d5ade562f76f9b84ed4fdf726e1f81dcf4ac9c..50bce32f8b4946e557cb0a577378007b484107d0 100644 --- a/packages/mobile-ui-vue/components/page-container/src/property-config/page-container.property-config.ts +++ b/packages/mobile-ui-vue/components/page-container/src/property-config/page-container.property-config.ts @@ -1,9 +1,11 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { ContainerBaseProperty } from "@farris/mobile-ui-vue/common"; + +export class PageContainerProperty extends ContainerBaseProperty { -export class PageContainerProperty extends BaseControlProperty { constructor(componentId: string, designerHostService: any) { super(componentId, designerHostService); } + public getPropertyConfig(propertyData: any) { // 基本信息 this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); diff --git a/packages/mobile-ui-vue/components/page-footer-container/index.ts b/packages/mobile-ui-vue/components/page-footer-container/index.ts index bceff3dd66d9a3a7835782ad140988048f806283..6c6a929420e662b2135112e537ea76d10ee405e0 100644 --- a/packages/mobile-ui-vue/components/page-footer-container/index.ts +++ b/packages/mobile-ui-vue/components/page-footer-container/index.ts @@ -24,7 +24,7 @@ const COMPONENT_TYPE = 'page-footer-container'; const PageFooterContainer = withInstall(PageFooterContainerInstallless); PageFooterContainer.register = ( componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext + configresolverMap: Record, resolverMap: Record, registerContext: RegisterContext ): void => { componentMap[COMPONENT_TYPE] = PageFooterContainer; propsResolverMap[COMPONENT_TYPE] = propsResolverGenerator(registerContext); @@ -32,7 +32,7 @@ PageFooterContainer.register = ( PageFooterContainer.registerDesigner = ( componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext + configresolverMap: Record, registerContext: RegisterContext ): void => { componentMap[COMPONENT_TYPE] = PageFooterContainerDesign; propsResolverMap[COMPONENT_TYPE] = propsResolverGenerator(registerContext); diff --git a/packages/mobile-ui-vue/components/page-footer-container/src/designer/page-footer-container.design.component.tsx b/packages/mobile-ui-vue/components/page-footer-container/src/designer/page-footer-container.design.component.tsx index 77cfa6838282f5899aa146a2e65ec715c3ae095d..22c581508dc7b5239827e274180caa3c596cf360 100644 --- a/packages/mobile-ui-vue/components/page-footer-container/src/designer/page-footer-container.design.component.tsx +++ b/packages/mobile-ui-vue/components/page-footer-container/src/designer/page-footer-container.design.component.tsx @@ -1,7 +1,7 @@ import { SetupContext, defineComponent, inject, onMounted, ref } from 'vue'; import { PageFooterContainerProps, pageFooterContainerProps } from '../page-footer-container.props'; import { useDesignerRules } from './use-designer-rules'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common';; export default defineComponent({ name: 'FmPageFooterContainerDesign', diff --git a/packages/mobile-ui-vue/components/page-footer-container/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/page-footer-container/src/designer/use-designer-rules.ts index c8a6911959079321bb5c56a59de6a33fd38e9c42..2020dc0f0eb612bb645cbf54c7da837376467bfc 100644 --- a/packages/mobile-ui-vue/components/page-footer-container/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/page-footer-container/src/designer/use-designer-rules.ts @@ -1,7 +1,7 @@ import { ref } from "vue"; import { PAGE_FOOTER_CONTAINER_NAME } from '../page-footer-container.props'; -import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { DraggingResolveContext } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; +import { DesignerHostService, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { DraggingResolveContext } from "@farris/mobile-ui-vue/common"; import { PageFooterContainerProperty } from "../property-config/page-footer-container.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService?: DesignerHostService): UseDesignerRules { @@ -26,10 +26,10 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe return true; } - function checkCanMoveComponent() { - return true; + return false; } + function checkCanDeleteComponent() { return true; } @@ -39,7 +39,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } function getDesignerClass(): string { - return PAGE_FOOTER_CONTAINER_NAME; + return `${PAGE_FOOTER_CONTAINER_NAME} position-relative`; } /** @@ -49,9 +49,12 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const componentProp = new PageFooterContainerProperty(componentId, designerHostService); const { schema } = designItemContext; return componentProp.getPropertyConfig(schema); - } + } + + function getDraggableDesignItemElement(context: DesignerItemContext) { + return context.designerItemElementRef; + } - return { canAccepts, triggerBelongedComponentToMoveWhenMoved, @@ -60,6 +63,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe checkCanDeleteComponent, hideNestedPaddingInDesginerView, getDesignerClass, - getPropsConfig + getPropsConfig, + getDraggableDesignItemElement, }; } diff --git a/packages/mobile-ui-vue/components/page-footer-container/src/property-config/page-footer-container.property-config.ts b/packages/mobile-ui-vue/components/page-footer-container/src/property-config/page-footer-container.property-config.ts index f37d1c71d3bedb2ffd74163f6b27af03203a9589..0eab391033f6ef87eda2f631dedb631195a43a00 100644 --- a/packages/mobile-ui-vue/components/page-footer-container/src/property-config/page-footer-container.property-config.ts +++ b/packages/mobile-ui-vue/components/page-footer-container/src/property-config/page-footer-container.property-config.ts @@ -1,14 +1,18 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { ContainerBaseProperty } from "@farris/mobile-ui-vue/common"; + +export class PageFooterContainerProperty extends ContainerBaseProperty { -export class PageFooterContainerProperty 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.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); return this.propertyConfig; } diff --git a/packages/mobile-ui-vue/components/page-footer-container/src/schema/page-footer-container.schema.json b/packages/mobile-ui-vue/components/page-footer-container/src/schema/page-footer-container.schema.json index c337e3b0856e5abb58e3b077ebf64bec27fbeb58..e471aa1250e7025ef99b1a11d8884bebb3017cb5 100644 --- a/packages/mobile-ui-vue/components/page-footer-container/src/schema/page-footer-container.schema.json +++ b/packages/mobile-ui-vue/components/page-footer-container/src/schema/page-footer-container.schema.json @@ -46,7 +46,7 @@ "default": null }, "visible": { - "description": "", + "description": "是否可见", "type": "boolean", "default": true } diff --git a/packages/mobile-ui-vue/components/page-header-container/index.ts b/packages/mobile-ui-vue/components/page-header-container/index.ts index d1bbc9022c74fd599f18c2dd8494822b8bf95021..aaf475e50282c386c4e1166c31cc97f359d9a764 100644 --- a/packages/mobile-ui-vue/components/page-header-container/index.ts +++ b/packages/mobile-ui-vue/components/page-header-container/index.ts @@ -24,7 +24,7 @@ const PAGE_HEADER_CONTAINER_REGISTERED_NAME = 'page-header-container'; const PageHeaderContainer = withInstall(PageHeaderContainerInstallless); PageHeaderContainer.register = ( componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext + configresolverMap: Record, resolverMap: Record, registerContext: RegisterContext ): void => { componentMap[PAGE_HEADER_CONTAINER_REGISTERED_NAME] = PageHeaderContainer; propsResolverMap[PAGE_HEADER_CONTAINER_REGISTERED_NAME] = propsResolverGenerator(registerContext); @@ -32,7 +32,7 @@ PageHeaderContainer.register = ( PageHeaderContainer.registerDesigner = ( componentMap: Record, propsResolverMap: Record, - configresolverMap: Record,registerContext: RegisterContext + configresolverMap: Record, registerContext: RegisterContext ): void => { componentMap[PAGE_HEADER_CONTAINER_REGISTERED_NAME] = PageHeaderContainerDesign; propsResolverMap[PAGE_HEADER_CONTAINER_REGISTERED_NAME] = propsResolverGenerator(registerContext); diff --git a/packages/mobile-ui-vue/components/page-header-container/src/designer/page-header-container.design.component.tsx b/packages/mobile-ui-vue/components/page-header-container/src/designer/page-header-container.design.component.tsx index 9401c7123a1c4494d8407a6ac391a432cd6d2e7b..0f354e8dd11850bf48437b2db722ebaf12ef295e 100644 --- a/packages/mobile-ui-vue/components/page-header-container/src/designer/page-header-container.design.component.tsx +++ b/packages/mobile-ui-vue/components/page-header-container/src/designer/page-header-container.design.component.tsx @@ -1,7 +1,7 @@ import { SetupContext, defineComponent, inject, onMounted, ref } from 'vue'; import { PageHeaderContainerProps, pageHeaderContainerProps } from '../page-header-container.props'; import { useDesignerRules } from './use-designer-rules'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/common';; export default defineComponent({ name: 'FmPageHeaderContainerDesign', diff --git a/packages/mobile-ui-vue/components/page-header-container/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/page-header-container/src/designer/use-designer-rules.ts index 77abd9ddfc17314f7b842a1d19713e82e99e6c42..4015500d20a2dcd93b85a190c23e2931d01b8f78 100644 --- a/packages/mobile-ui-vue/components/page-header-container/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/page-header-container/src/designer/use-designer-rules.ts @@ -1,7 +1,7 @@ import { ref } from "vue"; import { PAGE_HEADER_CONTAINER_NAME } from '../page-header-container.props'; -import { ComponentSchema, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { DraggingResolveContext } from "@farris/mobile-ui-vue/designer-canvas/src/composition/types"; +import { DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { DraggingResolveContext } from "@farris/mobile-ui-vue/common"; import { PageHeaderContainerProperty } from "../property-config/page-header-container.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -38,8 +38,9 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } function checkCanMoveComponent() { - return true; + return false; } + function checkCanDeleteComponent() { return true; } @@ -49,9 +50,9 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } function getDesignerClass(): string { - return PAGE_HEADER_CONTAINER_NAME; + return `${PAGE_HEADER_CONTAINER_NAME} position-relative`; } - + /** * 获取属性配置 */ @@ -59,8 +60,11 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const componentProp = new PageHeaderContainerProperty(componentId, designerHostService); const { schema } = designItemContext; return componentProp.getPropertyConfig(schema); - } + } + function getDraggableDesignItemElement(context: DesignerItemContext) { + return context.designerItemElementRef; + } return { canAccepts, @@ -70,6 +74,7 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe checkCanDeleteComponent, hideNestedPaddingInDesginerView, getDesignerClass, - getPropsConfig + getPropsConfig, + getDraggableDesignItemElement, }; } diff --git a/packages/mobile-ui-vue/components/page-header-container/src/property-config/page-header-container.property-config.ts b/packages/mobile-ui-vue/components/page-header-container/src/property-config/page-header-container.property-config.ts index 4303729e44aba2f3fa44a1b47abfc3acf75201b6..fa2d3e105f7e6a9ed5a235ccb854256de552bcee 100644 --- a/packages/mobile-ui-vue/components/page-header-container/src/property-config/page-header-container.property-config.ts +++ b/packages/mobile-ui-vue/components/page-header-container/src/property-config/page-header-container.property-config.ts @@ -1,14 +1,18 @@ -import { BaseControlProperty } from "@farris/mobile-ui-vue/property-panel"; +import { ContainerBaseProperty } from "@farris/mobile-ui-vue/common"; + +export class PageHeaderContainerProperty extends ContainerBaseProperty { -export class PageHeaderContainerProperty 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.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); return this.propertyConfig; } diff --git a/packages/mobile-ui-vue/components/page-header-container/src/schema/page-header-container.schema.json b/packages/mobile-ui-vue/components/page-header-container/src/schema/page-header-container.schema.json index e5066b03bab717c769af9f8492c2a8aac0cd4740..800cfa89b3a2b679b6ac95e010ce70bc8d4febbe 100644 --- a/packages/mobile-ui-vue/components/page-header-container/src/schema/page-header-container.schema.json +++ b/packages/mobile-ui-vue/components/page-header-container/src/schema/page-header-container.schema.json @@ -31,6 +31,11 @@ "description": "", "type": "array", "default": [] + }, + "visible": { + "description": "", + "type": "boolean", + "default": true } }, "required": [ diff --git a/packages/mobile-ui-vue/components/picker/index.ts b/packages/mobile-ui-vue/components/picker/index.ts index ee28dbf6b83d6da87d20474a248be6a58798c9d0..1ae96803550ace5532cf7b3eece4515cf3f09b53 100644 --- a/packages/mobile-ui-vue/components/picker/index.ts +++ b/packages/mobile-ui-vue/components/picker/index.ts @@ -2,7 +2,7 @@ import { withInstall, withRegister, withRegisterDesigner } from '@farris/mobile- import PickerPanelInstallless from "./src/picker-panel.component"; import PickerInstallless from "./src/picker.component"; import { propsResolverGenerator } from './src/picker.props'; -import EnumFieldInputDesign from './src/designer/enum-field-input.design.component'; +import PickerDesign from './src/designer/picker.design.component'; export * from './src/picker-panel.props'; export * from './src/types'; @@ -14,13 +14,13 @@ const PickerPanel = withInstall(PickerPanelInstallless); export * from './src/picker.props'; export * from './src/composition/use-picker-state'; -const PICKER_REGISTERED_NAME = 'picker'; const Picker = withInstall(PickerInstallless); // 注册运行时及设计时 +const PICKER_REGISTERED_NAME = 'picker'; withRegister(Picker, { name: PICKER_REGISTERED_NAME, propsResolverGenerator }); -withRegisterDesigner(Picker, { name: PICKER_REGISTERED_NAME, propsResolverGenerator, designerComponent: EnumFieldInputDesign }); +withRegisterDesigner(Picker, { name: PICKER_REGISTERED_NAME, propsResolverGenerator, designerComponent: PickerDesign }); export { Picker, PickerPanel }; export default Picker; diff --git a/packages/mobile-ui-vue/components/picker/src/designer/picker.design.component.tsx b/packages/mobile-ui-vue/components/picker/src/designer/picker.design.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8f16c721e373e8f193977523ca2864f95ba1bc74 --- /dev/null +++ b/packages/mobile-ui-vue/components/picker/src/designer/picker.design.component.tsx @@ -0,0 +1,62 @@ +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; +import { pickerProps } from '../picker.props'; +import { usePickerDesignerRules } from './use-designer-rules'; +import InputGroup from '@farris/mobile-ui-vue/input-group'; + +export default defineComponent({ + name: 'FmPickerDesign', + inheritAttrs: false, + props: extractProperties(pickerProps, ['placeholder', 'columns', 'valueField', 'textField']), + setup(props, context) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerRulesComposition = usePickerDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); + + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + + /** + * 解决在设计时,数据为空数组,界面不显示内容的问题 + */ + const realEnumData = computed(() => { + if (!props.columns || props.columns.length === 0) { + const result = [] as any; + [ + { value: 'example1', name: '示例一' }, + { value: 'example2', name: '示例二' } + ].map((item) => { + const tempData = {}; + tempData[props.valueField] = item['value']; + tempData[props.textField] = item['name']; + result.push(tempData); + }); + return result; + } + return props.columns; + }); + + context.expose(componentInstance.value); + + const inputProps = computed(() => ({ + ...props, + editable: false + })); + + return () => ; + } +}); diff --git a/packages/mobile-ui-vue/components/picker/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/picker/src/designer/use-designer-rules.ts index c497b5bd7fd8984d66d74f49b28f5665837205df..d889f74f73e232ac66a585b0549c42cf93deb59f 100644 --- a/packages/mobile-ui-vue/components/picker/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/picker/src/designer/use-designer-rules.ts @@ -1,13 +1,13 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; -import { EnumFieldInputProperty } from "../property-config/enum-field-input.property-config"; -export function useEnumFieldDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; +import { PickerInputProperty } from "../property-config/picker.property-config"; +export function usePickerDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { const schema = designItemContext.schema as ComponentSchema; // 构造属性配置方法 function getPropsConfig(componentId: string, componentInstance: DesignerComponentInstance) { - const radioGroupProps = new EnumFieldInputProperty(componentId, designerHostService); - return radioGroupProps.getPropertyConfig(schema, componentInstance); + const radioGroupProps = new PickerInputProperty(componentId, designerHostService); + return radioGroupProps.getPropertyConfig(schema, componentInstance); } return { getPropsConfig } as UseDesignerRules; diff --git a/packages/mobile-ui-vue/components/picker/src/property-config/picker.property-config.ts b/packages/mobile-ui-vue/components/picker/src/property-config/picker.property-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ab366074a6e90f156d3036d010a59e2aaa62cb1 --- /dev/null +++ b/packages/mobile-ui-vue/components/picker/src/property-config/picker.property-config.ts @@ -0,0 +1,51 @@ +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; + +export class PickerInputProperty extends InputBaseProperty { + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + + getEditorProperties(propertyData: any) { + const self = this; + const editorProperties = self.getComponentConfig( + propertyData, + { type: 'radio-group' }, + { + editable: { + description: '', + title: '允许编辑', + type: 'boolean' + }, + enableClear: { + description: '', + title: '启用清空', + type: 'boolean' + }, + data: { + description: '', + title: '数据', + type: 'array', + $converter: '/converter/enum-data.converter', + ...self.getItemCollectionEditor( + propertyData, + propertyData.editor.valueField, + propertyData.editor.textField + ), + // 这个属性,标记当属性变更得时候触发重新更新属性 + refreshPanelAfterChanged: true + } + } + ); + editorProperties['setPropertyRelates'] = function (changeObject) { + if (!changeObject) { + return; + } + switch (changeObject.propertyID) { + case 'data': { + break; + } + } + }; + return editorProperties; + } +} diff --git a/packages/mobile-ui-vue/components/picker/src/schema/picker.schema.json b/packages/mobile-ui-vue/components/picker/src/schema/picker.schema.json index 19a83d6350b111846061733e7c5984442b7755bb..8405701fd98d97c6f6d2f91c201a517b866f4e66 100644 --- a/packages/mobile-ui-vue/components/picker/src/schema/picker.schema.json +++ b/packages/mobile-ui-vue/components/picker/src/schema/picker.schema.json @@ -1,91 +1,100 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://farris-design.gitee.io/enum-field.schema.json", - "title": "picker", - "description": "A Farris Picker Component", - "type": "object", - "properties": { - "id": { - "description": "标志", - "type": "string" - }, - "type": { - "description": "控件类型", - "type": "string", - "default": "picker" - }, - "appearance": { - "description": "外观", - "type": "object", - "properties": { - "class": { - "type": "string" - }, - "style": { - "type": "string" - } - }, - "default": {} - }, - "binding": { - "description": "绑定", - "type": "object", - "default": {} - }, - "required": { - "description": "必填", - "type": "boolean", - "default": false - }, - "readonly": { - "description": "只读", - "type": "boolean", - "default": false - }, - "disabled": { - "description": "禁用", - "type": "boolean", - "default": false - }, - "placeholder": { - "description": "提示文本", - "type": "string" - }, - "title": { - "description": "标题", - "type": "string", - "default": "" - }, - "valueField": { - "description": "值字段", - "type": "string", - "default": "id" - }, - "textField": { - "description": "显示字段", - "type": "string", - "default": "name" - }, - "data": { - "description": "数据", - "type": "array", - "default": [] + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/enum-field.schema.json", + "title": "picker", + "description": "A Farris Picker Component", + "type": "object", + "properties": { + "id": { + "description": "标志", + "type": "string" + }, + "type": { + "description": "控件类型", + "type": "string", + "default": "picker" + }, + "appearance": { + "description": "外观", + "type": "object", + "properties": { + "class": { + "type": "string" }, - "onUpdate:modelValue": { - "description": "值更新事件", - "type": "string" + "style": { + "type": "string" } + }, + "default": {} + }, + "binding": { + "description": "绑定", + "type": "object", + "default": {} + }, + "required": { + "description": "必填", + "type": "boolean", + "default": false + }, + "readonly": { + "description": "只读", + "type": "boolean", + "default": false + }, + "disabled": { + "description": "禁用", + "type": "boolean", + "default": false + }, + "editable": { + "description": "", + "type": "boolean", + "default": false + }, + "enableClear": { + "description": "", + "type": "boolean", + "default": false + }, + "placeholder": { + "description": "提示文本", + "type": "string" + }, + "title": { + "description": "标题", + "type": "string", + "default": "" + }, + "valueField": { + "description": "值字段", + "type": "string", + "default": "value" + }, + "textField": { + "description": "显示字段", + "type": "string", + "default": "name" + }, + "data": { + "description": "数据", + "type": "array", + "default": [] }, - "events": [ - "onUpdate:modelValue" - ], - "required": [ - "type" - ], - "ignore": [ - "id", - "type", - "appearance", - "binding" - ] + "onUpdate:modelValue": { + "description": "值更新事件", + "type": "string" + } + }, + "events": [ + "onUpdate:modelValue" + ], + "required": [ + "type" + ], + "ignore": [ + "id", + "appearance", + "visible" + ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/property-panel/index.ts b/packages/mobile-ui-vue/components/property-panel/index.ts deleted file mode 100644 index 7963be09873589e9a106f4da19789b3c3683afe5..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/index.ts +++ /dev/null @@ -1,24 +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 { BaseControlProperty } from './src/composition/entity/base-property'; -import { SchemaDOMMapping } from './src/composition/entity/schema-dom-mapping'; - -export * from './src/composition/props/property-panel-item.props'; -export * from './src/composition/props/property-panel.props'; - -export * from './src/composition/type'; - -export { BaseControlProperty, SchemaDOMMapping }; diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel-item-list.css b/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel-item-list.css deleted file mode 100644 index 1df3d4efa1d8893c31e9f33890874b4a6359e920..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel-item-list.css +++ /dev/null @@ -1,80 +0,0 @@ -.propertyCascadeItem { - background-color: transparent !important; - border: none !important; -} - -.propertyCascadeItem .card-header { - background-color: transparent !important; - padding: 4px 0px !important; - color: inherit !important; -} - -.propertyCascadeItem .card-header .panel-item-title { - width: 100%; - position: relative; - font-size: inherit !important; -} - -.propertyCascadeItem .card-header .panel-item-title .farris-input-wrap { - margin-left: -5px; - margin-right: -5px; -} - -.propertyCascadeItem .form-group .col-form-label .f-icon { - color: #a3a3a3 !important; -} - -.propertyCascadeItem .f-accordion-collapse, -.propertyCascadeItem .f-accordion-expand { - right: 0; - left: auto !important; - top: 6px; - color: #6b94ec !important; - position: absolute; -} - -.propertyCascadeItem .card-body { - padding: 3px 12px !important; - background: rgba(255, 255, 255, 0.8); - border-radius: 8px; - margin: 4px 0px; -} - -.propertyCascadeItem .card-body.hidden { - display: none; -} - -.landscape { - display: none; - flex-shrink: 0; - align-items: center; - justify-content: flex-end; -} - -.wide-panel .vertical { - display: none; -} - -.wide-panel .landscape { - display: block; - padding: 0px; - overflow: hidden; -} - -.wide-panel .line-item { - display: flex; -} - -.wide-panel .line-item .f-header { - margin-bottom: 0; -} - -.wide-panel .line-item .f-section-formgroup-legend { - width: 89px; - text-align: right; - padding-right: 13px; -} - -.wide-panel .line-item .farris-input-wrap { - flex: 1 1 0; -} diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel-item.css b/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel-item.css deleted file mode 100644 index 50a976a6eab7e26aba701fc87e331785298ebf0b..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel-item.css +++ /dev/null @@ -1,30 +0,0 @@ -.property-item .form-group { - margin-bottom: 2px; -} - -.property-item .col-form-label { - line-height: 26px; -} - -.property-item .row-item { - align-items: center; -} - -.wide-panel .row-item { - display: flex; -} - -.wide-panel .row-item .component { - flex: 1 1 0; -} - -.wide-panel .row-item label { - width: 89px; - text-align: right; - padding-right: 10px; -} - -.wrap { - overflow: hidden; - text-overflow: ellipsis; -} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel.css b/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel.css deleted file mode 100644 index a83de6b5095d92267e8de909e48e6206f00fcb83..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/class/property-panel.css +++ /dev/null @@ -1,222 +0,0 @@ -:host { - width: inherit; - height: inherit; -} - -.property-panel { - position: relative; -} - -.property-panel .switcher { - position: absolute; - top: 9px; - right: 0; - width: 40px; - text-align: center; -} - - -.property-panel .switcher i { - color: #4190FF; - font-size: 12px; - cursor: pointer; -} - -.property-panel .side-panel { - width: 41px; - background-color: #fff; - padding-top: 45px; - text-align: center; -} - -.property-panel .side-panel .icon { - display: block; - text-align: center; - color: #4190FF; - margin-bottom: 10px; -} - -.property-panel .side-panel span { - writing-mode: vertical-rl; - font-size: 14px; -} - -.propertyPanel { - width: 300px; - height: 100%; - color: rgba(0, 0, 0, 0.75); - background: #f3f8ff !important; - border-color: #d8dbe2 !important; - display: flex; -} - -.propertyPanel .title { - position: relative; - background: #d0d8e9 !important; - - flex-wrap: wrap; - border: 0; -} - -.propertyPanel .title.p-right { - padding-right: 40px; -} - -.propertyPanel .title>.title-label { - line-height: 34px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - padding-left: 12px; - padding-right: 12px; - color: #333333; - font-size: 13px; - font-weight: 600; - cursor: pointer; -} - -.propertyPanel .title>.title-label.active { - background: #8fb1df !important -} - -.propertyPanel .title .title-actions { - position: absolute; - right: 10px; - top: 0px; - padding-left: 35px; - height: 35px; - -ms-flex: 1; - flex: 1; - box-sizing: border-box; -} - -.propertyPanel .title .title-actions { - position: absolute; - right: 10px; - top: 0px; -} - -.propertyPanel .property-grid .group-label { - line-height: 30px; - display: block; - padding-left: 13px; - cursor: pointer; - background: rgb(226, 233, 246) !important; - ; - color: #435069 !important; - font-weight: 600 !important; - border-radius: 6px; -} - -.propertyPanel .search .textbox { - padding-left: 10px; -} - -.propertyPanel .search .input-group { - border-radius: 0; -} - -.propertyPanel .panel-body { - overflow: auto; - height: 100%; -} - -.propertyPanel .property-grid { - list-style: none; - margin: 0 10px; - padding: 0; -} - -.propertyPanel .property-grid li { - padding: 2px 0; -} - -.propertyPanel .panel-body .property-grid .group-label+div { - padding-bottom: 14px !important; - padding-top: .25rem !important; -} - -.propertyPanel .action-item { - cursor: pointer; - display: inline-block; - transition: transform 50ms ease; - position: relative; - padding: 0px; -} - -.propertyPanel .action-item>.f-icon { - color: rgba(66, 66, 66, .75); - line-height: 35px; - font-weight: 600; -} - -.propertyPanel .search .input-group-clear { - border-radius: 0 !important; -} - -/***************************白色主题******************************/ - -.white-theme .propertyPanel { - background: #fff !important; - border: 1px solid #D8DCE6 !important; -} - -.white-theme .propertyPanel .title { - overflow: visible; - background: #fff !important; - justify-content: space-around; -} - -.white-theme .propertyPanel { - background: #fff !important; -} - -.white-theme .propertyPanel .title { - overflow: visible; - background: #fff !important; - flex-wrap: wrap; - text-align: center; -} - -.white-theme .propertyPanel .title>.title-label { - border-bottom: 0; - font-weight: 400 !important; - /* margin-left: 20px; - margin-right: 20px; */ - border-bottom: 0; - background: #fff !important; - flex: 1; - color: #83849B; -} - -.white-theme .propertyPanel .title>.title-label>span { - padding: 0 14px 7px 14px; -} - -.white-theme .propertyPanel .title>.title-label.active>span { - border-bottom: 2px solid #5b89fe; - color: #5B89FE; - border-radius: 1.5px; -} - -.white-theme .propertyPanel .title.only { - text-align: left; -} - -.white-theme .propertyPanel .property-grid .group-label { - background: #fff !important; - line-height: 30px; - font-weight: 400 !important; - font-size: 13px; - color: #3F4764 !important; - border-radius: 6px; - background: #EEF4FF !important; - color: #435069 !important; -} - -.white-theme .propertyPanel .property-grid .group-label .f-icon { - color: #5D89FE; - font-size: 13px; -} - -/***************************白色主题 end ******************************/ \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel-item-list.props.ts b/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel-item-list.props.ts deleted file mode 100644 index 40bf0ca77a9f7902125345aec1afa7d3ff8f3cb1..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel-item-list.props.ts +++ /dev/null @@ -1,32 +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 { ElementPropertyConfig } from '../entity/property-entity'; - -export const propertyPanelItemListProps = { - /** 某一分类下的属性配置 */ - // as PropType - category: { type: Object, default: {} }, - - categoryKey: { type: String }, - /** 属性值 */ - propertyData: { type: Object, default: {} }, - - valueChanged: { type: Function }, - - triggerRefreshPanel: { type: Function } -}; -export type PropertyPanelItemListProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel-item.props.ts b/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel-item.props.ts deleted file mode 100644 index c87c39e87b06433bd66629f11c7d54e665f93ee6..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel-item.props.ts +++ /dev/null @@ -1,22 +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'; - -export const propertyPanelItemProps = { - elementConfig: { type: Object, default: {} }, - category: { type: Object, default: {} } -}; -export type PropertyPanelItemProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel.props.ts b/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel.props.ts deleted file mode 100644 index 456a906bfad82d94fe2dd3451190addcb9a578d3..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/props/property-panel.props.ts +++ /dev/null @@ -1,64 +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'; - -type ShowMode = 'panel' | 'sidebar'; - -export const propertyPanelProps = { - - width: { type: String, default: '300px' }, - - height: { type: Number, default: 10 }, - - isWidePanel: { type: Boolean, default: false }, - - /** 是否启用搜索 */ - enableSearch: { type: Boolean, default: true }, - - /** 使用模式 */ - mode: { type: String as PropType, default: 'panel' }, - - /** 是否持有面板的隐藏显示状态 */ - isPersitOpenState: { type: Boolean, default: false }, - - /** isPersitOpenState=true时,控制面板是否隐藏显示 */ - isShowPanel: { type: Boolean, default: false }, - - /** 属性名 */ - propertyName: { type: String, default: '' }, - - /** 属性类型 */ - propertyConfig: { type: Array }, - - /** 属性值 */ - propertyData: { type: Object, default: {} }, - - /** 是否展示关闭按钮 */ - showCloseBtn: { type: Boolean, default: false }, - - /** 当前选中的标签页id */ - selectedTabId: { type: String, default: '' }, - - /** 是否是白色主题 */ - isWhiteTheme: { type: Boolean, default: true }, - - /** dom结构 */ - schema: { type: Object, default: {} }, - - /** 属性变更后事件 */ - propertyChanged: { type: Function } -}; -export type PropertyPanelProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/property-panel/src/composition/type.ts b/packages/mobile-ui-vue/components/property-panel/src/composition/type.ts deleted file mode 100644 index f6727192b3f282af52eb8b82f4e307908a33fa7f..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/composition/type.ts +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 属性类型 - */ -export enum PropertyType { - /** 字符串 */ - string = 'string', - - /** 布尔,下拉选择 */ - boolean = 'boolean', - - /** 数字 */ - number = 'number', - - /** 下拉选择:单选 */ - select = 'select', - - /** 已废弃,请使用editableSelect */ - boolOrExp = 'boolOrExp', - - /** 可编辑的下拉选择:单选,并且可编辑 */ - editableSelect = 'editableSelect', - - /** 下拉多选 */ - multiSelect = 'multiSelect', - - /** 日期 */ - date = 'date', - - /** 日期时间 */ - datetime = 'datetime', - - /** 模态窗,自定义组件 */ - modal = 'modal', - - /** 级联 */ - cascade = 'cascade', - - /** 自定义组件 */ - custom = 'custom', - - /** 多功能属性编辑器,支持常量、变量、自定义、表达式等场景 */ - unity = 'unity', - - /** 事件编辑器集成,支持导入命令、参数编辑等场景 */ - events = 'events', - - /** 开关类编辑器,适用于布尔值属性 */ - switch = 'switch', - - /** 多语言输入框 */ - multiLanguage = 'multiLanguage' -} - -/** 属性值转换器,返回模态框类属性文本框内的显示内容 */ -export interface TypeConverter { - // 由模态框转为属性框中展示的值 - convertTo(data: any, params?: any): string; -} - -export interface KeyMap { - key: any; - value: any; -} - -/** - * binding 类型 - */ -export enum FormBindingType { - Form = "Form", - Variable = "Variable" -} - -export interface FormUnifiedColumnLayout { - uniqueColClassInSM: number; - uniqueColClassInMD: number; - uniqueColClassInLG: number; - uniqueColClassInEL: number; -} - -export interface IPropertyConfig { - getPropertyConfig(propertyData: any, eventsEditorUtils: any); -} - -export * from './entity/property-entity'; diff --git a/packages/mobile-ui-vue/components/property-panel/src/mock.ts b/packages/mobile-ui-vue/components/property-panel/src/mock.ts deleted file mode 100644 index 8266d0cfe1d4cdc4bc725cc7fb687e28393b7f6a..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/property-panel/src/mock.ts +++ /dev/null @@ -1,174 +0,0 @@ -export const propertyConfigTemp = [ - { - categoryId: 'basic', - categoryName: '基本信息', - properties: [ - { - propertyID: 'id', - propertyName: '标识', - propertyType: 'string', - description: '组件的id', - readonly: true - }, - { - propertyID: 'type', - propertyName: '控件类型', - propertyType: 'select', - description: '组件的类型', - } - ] - }, - { - categoryId: 'appearance', - categoryName: '样式', - properties: [ - { - propertyID: 'fill', - propertyName: '填充', - propertyType: 'datetime', - description: 'flex布局下,填充满剩余部分', - modelValue: '2023-4-9', - editor: { - id: 'd152e48d-13d1-4553-94fa-525fa67d4f2b', - type: 'date-picker', - require: false, - format: 'yyyy-MM-dd', - weekSelect: false, - startFieldCode: 'BillDate', - endFieldCode: 'BillDate' - }, - }, - { - propertyID: 'expanded', - propertyName: '展开', - propertyType: 'boolean', - description: '是否展开', - modelValue: 'false', - editor: { - type: 'combo-list', - require: false, - valueType: '1', - multiSelect: false, - data: [ - { - value: 'true', - name: 'true' - }, - { - value: 'false', - name: 'false' - } - ] - } - }, - { - propertyID: 'showHeader', - propertyName: '显示头部区域', - propertyType: 'boolean', - description: '是否显示头部区域', - modelValue: 'true', - editor: { - type: 'combo-list', - require: false, - valueType: '1', - multiSelect: false, - data: [ - { - value: 'true', - name: 'true' - }, - { - value: 'false', - name: 'false' - } - ] - } - }, - { - propertyID: 'mainTitle', - propertyName: '主标题', - propertyType: 'string', - description: '主标题名称', - group: 'header' - }, - { - propertyID: 'subTitle', - propertyName: '副标题', - propertyType: 'string', - description: '副标题名称', - group: 'header' - }, - { - propertyID: 'enableMaximize', - propertyName: '显示最大化', - propertyType: 'boolean', - description: '是否显示最大化', - group: 'header' - }, - { - propertyID: 'enableAccordion', - propertyName: '启用收折功能', - propertyType: 'boolean', - description: '是否启用收折功能', - group: 'header' - }, - { - propertyID: 'accordionMode', - propertyName: '收折模式', - propertyType: 'select', - description: '收折模式选择', - iterator: [{ key: 'default', value: '默认收折' }, { key: 'custom', value: '自定义收折' }], - group: 'header' - } - ] - }, - { - categoryId: 'toolbar', - categoryName: '工具栏', - properties: [ - { - propertyID: 'toolbarCls', - propertyName: '工具栏样式', - propertyType: 'string' - }, - { - propertyID: 'toolbarBtnSize', - propertyName: '按钮尺寸', - propertyType: 'select', - iterator: [ - { key: 'default', value: '标准' }, - { key: 'lg', value: '大号' } - ] - }, - { - propertyID: 'toolbarPopDirection', - propertyName: '弹出方向', - propertyType: 'select', - iterator: [ - { key: 'default', value: '自动' }, - { key: 'top', value: '向上' }, - { key: 'bottom', value: '向下' } - ] - } - ] - } -]; - -export const propertyDataTemp = { - id: 'dataGrid', - testCategoryCascade: { - showSize2: false - }, - language1: { - 'zh-CHS': 'check1', - 'en': 'hhhh' - }, - language2: { - 'en': 'hhhh2', - 'zh-CHS': 'check2', - }, - language3: { - 'en': 'hhhh3', - 'zh-CHS': 'check3', - } -}; diff --git a/packages/mobile-ui-vue/components/pull-refresh/src/pull-refresh.component.tsx b/packages/mobile-ui-vue/components/pull-refresh/src/pull-refresh.component.tsx index be589115acb874a9eb2336f32d63a11de896809f..1201f7899be68f5c1929ebe62530970a1b441277 100644 --- a/packages/mobile-ui-vue/components/pull-refresh/src/pull-refresh.component.tsx +++ b/packages/mobile-ui-vue/components/pull-refresh/src/pull-refresh.component.tsx @@ -176,7 +176,7 @@ export default defineComponent({ } if (status.value === 'loading') { - return ; + return ; } if (['pulling', 'loosing', 'complete'].includes(status.value)) { return
{getStatusText()}
; diff --git a/packages/mobile-ui-vue/components/radio-group/src/designer/radio-group.design.component.tsx b/packages/mobile-ui-vue/components/radio-group/src/designer/radio-group.design.component.tsx index fa99c50f85b18f332ebd88fb41c17f25547e1c06..ea8a890658ca6e79e5de4f29df2ac41a3320945a 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/designer/radio-group.design.component.tsx +++ b/packages/mobile-ui-vue/components/radio-group/src/designer/radio-group.design.component.tsx @@ -1,74 +1,74 @@ - -/** - * 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, inject, onMounted, readonly, ref, SetupContext } from 'vue'; -import { RadioGroup, RadioGroupProps, radioGroupProps } from '../..'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas'; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; import { useRadioGroupDesignerRules } from './use-designer-rules'; +import { radioGroupProps } from '../radio-group.props'; +import RadioGroup from '../radio-group.component'; export default defineComponent({ - name: 'FmRadioGroupDesign', - props: radioGroupProps, - emits: [] as (string[] & ThisType) | undefined, - setup(props: RadioGroupProps, context: SetupContext) { - const elementRef = ref(); - const designerHostService = inject('designer-host-service'); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerRulesComposition = useRadioGroupDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + name: 'FmRadioGroupDesign', + props: extractProperties(radioGroupProps, [ + 'options', + 'textField', + 'valueField', + 'direction', + 'type' + ]), + setup(props, context) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerRulesComposition = useRadioGroupDesignerRules( + designItemContext, + designerHostService + ); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - /** - * 解决在设计时,数据为空数组,界面不显示内容的问题 - */ - const realEnumData = computed(() => { - if (!props.options || props.options.length === 0) { - const result = [] as any; - [ - { value: 'example1', name: '示例一' }, - { value: 'example2', name: '示例二' } - ].map(item => { - const tempData = {}; - tempData[props.valueField] = item['value']; - tempData[props.textField] = item['name']; - result.push(tempData); - }); - return result; - } - return props.options; + /** + * 解决在设计时,数据为空数组,界面不显示内容的问题 + */ + const realEnumData = computed(() => { + if (!props.options || props.options.length === 0) { + const result = [] as any; + [ + { value: 'example1', name: '示例一' }, + { value: 'example2', name: '示例二' } + ].map((item) => { + const tempData = {}; + tempData[props.valueField] = item['value']; + tempData[props.textField] = item['name']; + result.push(tempData); }); + return result; + } + return props.options; + }); - const inputGroupProps = computed(() => ({ - ...props, - editable: false, - readonly: true, - modelValue:null, - options:realEnumData.value, - type:"default" - })); + const radioGroupProps = computed(() => ({ + ...props, + editable: false, + readonly: true, + options: realEnumData.value + })); - context.expose(componentInstance.value); + context.expose(componentInstance.value); - return () => { - return ( - - ); - }; - } + return () => { + return ; + }; + } }); diff --git a/packages/mobile-ui-vue/components/radio-group/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/radio-group/src/designer/use-designer-rules.ts index 3e53eb89631306279106f2f54b65250615bc8fe8..1a9e02c9d13bc1d11339be5bd35e84b06cf34abf 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/radio-group/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { RadioGroupProperty } from "../property-config/radio-group.property-config"; export function useRadioGroupDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -6,8 +6,8 @@ export function useRadioGroupDesignerRules(designItemContext: DesignerItemContex // 构造属性配置方法 function getPropsConfig(componentId: string, componentInstance: DesignerComponentInstance) { - const radioGroupProps = new RadioGroupProperty(componentId, designerHostService); - return radioGroupProps.getPropertyConfig(schema, componentInstance); + const radioGroupProps = new RadioGroupProperty(componentId, designerHostService); + return radioGroupProps.getPropertyConfig(schema, componentInstance); } return { getPropsConfig } as UseDesignerRules; diff --git a/packages/mobile-ui-vue/components/radio-group/src/property-config/radio-group.property-config.ts b/packages/mobile-ui-vue/components/radio-group/src/property-config/radio-group.property-config.ts index 9b69160f2355e5d68637a37f5e527cec3c95bf26..705b099375e496b1cd9509868a0a487f9aa588ae 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/property-config/radio-group.property-config.ts +++ b/packages/mobile-ui-vue/components/radio-group/src/property-config/radio-group.property-config.ts @@ -1,4 +1,4 @@ -import { InputBaseProperty } from '@farris/mobile-ui-vue/common/src/entity/input-base-property'; +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; export class RadioGroupProperty extends InputBaseProperty { constructor(componentId: string, designerHostService: any) { @@ -21,7 +21,6 @@ export class RadioGroupProperty extends InputBaseProperty { description: '', title: '数据', type: 'array', - $converter: '/converter/enum-data.converter', ...self.getItemCollectionEditor( propertyData, propertyData.editor.valueField, diff --git a/packages/mobile-ui-vue/components/radio-group/src/radio-group.component.tsx b/packages/mobile-ui-vue/components/radio-group/src/radio-group.component.tsx index cd552d70c4fb5d61cb1ed4519839e1c9fe74942c..706fc0443ef418700dc9ae5ba3bf4c297a4a3d7e 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/radio-group.component.tsx +++ b/packages/mobile-ui-vue/components/radio-group/src/radio-group.component.tsx @@ -1,19 +1,3 @@ -/** - * 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 { SetupContext, computed, defineComponent, ref, watch } from 'vue'; import { useBem, useLink } from '@farris/mobile-ui-vue/common'; import { useGroupItems } from '@farris/mobile-ui-vue/checkbox-group'; @@ -21,14 +5,13 @@ import Radio from '@farris/mobile-ui-vue/radio'; import { RADIO_GROUP_NAME, RadioGroupContext, - RadioGroupProps, radioGroupProps } from './radio-group.props'; export default defineComponent({ name: RADIO_GROUP_NAME, props: radioGroupProps, - setup(props: RadioGroupProps, context: SetupContext) { + setup(props, context: SetupContext) { const { emit, slots } = context; const innerValue = ref(props.modelValue); diff --git a/packages/mobile-ui-vue/components/radio-group/src/radio-group.props.ts b/packages/mobile-ui-vue/components/radio-group/src/radio-group.props.ts index 2148d888b1354c058b1fa1208e284d5151e5e077..23a3b0f22e018d7a5418b1065b1479a0173b90b0 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/radio-group.props.ts +++ b/packages/mobile-ui-vue/components/radio-group/src/radio-group.props.ts @@ -1,5 +1,5 @@ import { ExtractPropTypes, PropType } from 'vue'; -import {CheckerShape, CheckerShapeMap } from '@farris/mobile-ui-vue/checker'; +import {CheckerShape } from '@farris/mobile-ui-vue/checker'; import { CheckboxGroupContext, checkboxGroupProps } from '@farris/mobile-ui-vue/checkbox-group'; import { getPropsResolverGenerator } from '@farris/mobile-ui-vue/dynamic-resolver'; import inputSchema from './schema/radio-group.schema.json'; @@ -11,10 +11,10 @@ export const RADIO_GROUP_NAME = 'fm-radio-group'; export const radioGroupProps = { ...checkboxGroupProps, - shape: { type: String as PropType, default: CheckerShapeMap.Round }, + shape: { type: String as PropType, default: CheckerShape.Round }, modelValue: { type: [String, Number] , default: '' } -} as Record; +}; export type RadioGroupProps = ExtractPropTypes; @@ -24,4 +24,4 @@ type Merge = { export type RadioGroupContext = Merge void }>; -export const propsResolverGenerator = getPropsResolverGenerator(radioGroupProps, inputSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator(radioGroupProps, inputSchema, schemaMapper, schemaResolver); diff --git a/packages/mobile-ui-vue/components/radio-group/src/schema/radio-group.schema.json b/packages/mobile-ui-vue/components/radio-group/src/schema/radio-group.schema.json index 7fad6675bf4385ad99edf0f0c63a07ac83c3e1d5..d993339cf0d92da9b2d916ee21e66a24c99486a4 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/schema/radio-group.schema.json +++ b/packages/mobile-ui-vue/components/radio-group/src/schema/radio-group.schema.json @@ -32,23 +32,14 @@ "type": "object", "default": {} }, - "readonly": { - "type": "string", + "required": { + "description": "必填", + "type": "boolean", "default": false }, - "title": { - "description": "", - "type": "string", - "default": "" - }, - "label": { - "description": "", + "readonly": { "type": "string", - "default": "" - }, - "lableWidth": { - "description": "", - "type": "number" + "default": false }, "visible": { "description": "", @@ -63,7 +54,7 @@ "direction": { "description": "", "type": "string", - "default": "horizontal" + "default": "vertical" }, "textField": { "description": "", @@ -74,6 +65,11 @@ "description": "", "type": "string", "default": "value" + }, + "checkerType": { + "description": "", + "type": "string", + "default": "default" } }, "required": ["type"], diff --git a/packages/mobile-ui-vue/components/radio-group/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/radio-group/src/schema/schema-mapper.ts index bf5fa0c7c604d94ab7ec1f164e2600ff20912a90..98eb00913b3deb56f695a38e0c7467d64318441b 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/radio-group/src/schema/schema-mapper.ts @@ -1,6 +1,11 @@ -import { MapperFunction, resolveAppearance, resolveData } from '@farris/mobile-ui-vue/dynamic-resolver'; +import { + MapperFunction, + resolveAppearance, + resolveData +} from '@farris/mobile-ui-vue/dynamic-resolver'; export const schemaMapper = new Map([ - ['appearance', resolveAppearance], - ['data', resolveData], + ['appearance', resolveAppearance], + ['data', 'options'], + ['checkerType', 'type'] ]); diff --git a/packages/mobile-ui-vue/components/radio-group/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/radio-group/src/schema/schema-resolver.ts index 7c8af7fc63d68147438fc329e74ebce565df4ef7..1e756819460221c500d1b646dbd318d77d7cfaf0 100644 --- a/packages/mobile-ui-vue/components/radio-group/src/schema/schema-resolver.ts +++ b/packages/mobile-ui-vue/components/radio-group/src/schema/schema-resolver.ts @@ -1,5 +1,9 @@ -import { DynamicResolver } from "@farris/mobile-ui-vue/dynamic-resolver"; +import { DynamicResolver } from '@farris/mobile-ui-vue/dynamic-resolver'; -export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { - return schema; +export function schemaResolver( + resolver: DynamicResolver, + schema: Record, + context: Record +): Record { + return schema; } diff --git a/packages/mobile-ui-vue/components/radio/src/radio.component.tsx b/packages/mobile-ui-vue/components/radio/src/radio.component.tsx index fb7a6bf9ff8197f618396499f7aba94aa2264c6a..1d6c235b1ea2c25527449de459cae83cf7def99e 100644 --- a/packages/mobile-ui-vue/components/radio/src/radio.component.tsx +++ b/packages/mobile-ui-vue/components/radio/src/radio.component.tsx @@ -15,7 +15,7 @@ */ import { defineComponent } from 'vue'; -import { Checker, CheckerRoleMap, CheckerShapeMap } from '@farris/mobile-ui-vue/checker'; +import { Checker, CheckerRole, CheckerShape } from '@farris/mobile-ui-vue/checker'; import { useBem, useLink } from '@farris/mobile-ui-vue/common'; import { RADIO_GROUP_NAME, RadioGroupContext } from '@farris/mobile-ui-vue/radio-group'; import { RADIO_NAME, RadioProps, radioProps } from './radio.props'; @@ -45,10 +45,10 @@ export default defineComponent({ : props.modelValue; return ( , default: CheckerRole.Radio }, + + shape: { type: String as PropType, default: CheckerShape.Round }, }; export type RadioProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/register-designer.ts b/packages/mobile-ui-vue/components/register-designer.ts index dbb9c1503acbbfcb7e63ff6feed442c982e070f9..b20f07ad6995176d3ca61884d9b2506bea14058c 100644 --- a/packages/mobile-ui-vue/components/register-designer.ts +++ b/packages/mobile-ui-vue/components/register-designer.ts @@ -2,9 +2,9 @@ import { RegisterContext } from "./common"; import { propertyConfigSchemaMapForDesigner, schemaMapForDesigner, schemaResolverMapForDesigner } from "./dynamic-resolver"; import { propertyEffectMapForDesigner } from "./dynamic-resolver/src/resolver/property-config/property-config-resolver-design"; -const componentMap: Record = {}; -const componentPropsConverter: Record = {}; -const componentPropertyConfigConverter: Record = {}; +const componentMapForDesigner: Record = {}; +const componentPropsConverterForDesigner: Record = {}; +const componentPropertyConfigConverterForDesigner: Record = {}; let componentsRegistered = false; /** @@ -16,7 +16,7 @@ function registerDesignerComponents(components: any[]) { } componentsRegistered = true; - const registerContext:RegisterContext = { + const registerContext: RegisterContext = { schemaMap: schemaMapForDesigner, propertyConfigSchemaMap: propertyConfigSchemaMapForDesigner, propertyEffectMap: propertyEffectMapForDesigner, @@ -24,8 +24,8 @@ function registerDesignerComponents(components: any[]) { }; components.forEach(component => { - component.registerDesigner(componentMap, componentPropsConverter, componentPropertyConfigConverter, registerContext); + component.registerDesigner && component.registerDesigner(componentMapForDesigner, componentPropsConverterForDesigner, componentPropertyConfigConverterForDesigner, registerContext); }); } -export { registerDesignerComponents, componentMap, componentPropsConverter }; +export { registerDesignerComponents, componentMapForDesigner, componentPropsConverterForDesigner }; diff --git a/packages/mobile-ui-vue/components/register.ts b/packages/mobile-ui-vue/components/register.ts index 02f0fe969571bf9a6428251f84f3c07564bf31c1..785dbbd7fddc24c46993cc291598f698d05616d6 100644 --- a/packages/mobile-ui-vue/components/register.ts +++ b/packages/mobile-ui-vue/components/register.ts @@ -1,26 +1,33 @@ +import { RegisterContext } from "./common"; +import { propertyConfigSchemaMap, schemaMap, schemaResolverMap } from "./dynamic-resolver"; +import { propertyEffectMap } from "./dynamic-resolver/src/resolver/property-config/property-config-resolver"; + +import Button from './button'; +import ButtonGroup from './button-group'; +import InputGroup from './input-group'; +import NumberInput from './number-input'; +import Textarea from './textarea'; +import Navbar from './navbar'; +import Switch from './switch'; +import Form from './form'; +import FormItem from './form-item'; +import { Picker } from './picker'; +import { DatePicker } from './date-picker'; +import { DateTimePicker } from "./date-time-picker"; +import Lookup from './lookup'; +import RadioGroup from './radio-group'; +import CheckboxGroup from './checkbox-group'; + import PageContainer from './page-container'; -import PageHeaderContainer from './page-header-container'; import PageBodyContainer from './page-body-container'; +import PageHeaderContainer from './page-header-container'; import PageFooterContainer from './page-footer-container'; import Component from './component'; import ContentContainer from './content-container'; +import { Card } from './card'; import FloatContainer from './float-container'; - -import Navbar from './navbar'; -import Button from './button'; -import ButtonGroup from './button-group'; import Listview from './list-view'; -import Form from './form'; -import FormItem from './form-item'; -import InputGroup from './input-group'; -import Textarea from './textarea'; -import NumberInput from './number-input'; -import Switch from './switch'; -import DatePicker from './date-picker'; -import DateTimePicker from './date-time-picker'; -import Picker from './picker'; - const componentMap: Record = {}; const propsConverterMap: Record = {}; const propConfigsConverterMap: Record = {}; @@ -32,20 +39,34 @@ let componentsRegistered = false; * 注册组件 */ function registerComponents() { - if (componentsRegistered) { - return; - } - componentsRegistered = true; + if (componentsRegistered) { + return; + } + componentsRegistered = true; + const registerContext: RegisterContext = { + schemaMap: schemaMap, + propertyConfigSchemaMap: propertyConfigSchemaMap, + propertyEffectMap: propertyEffectMap, + schemaResolverMap: schemaResolverMap + }; - const componentsToRegister = [ - Component, PageContainer, PageHeaderContainer, PageBodyContainer, PageFooterContainer, - ContentContainer, FloatContainer, - Navbar, Button, ButtonGroup, Listview, - Form, FormItem, InputGroup, Textarea, NumberInput, Switch, DatePicker, DateTimePicker, Picker - ]; - componentsToRegister.forEach((componentToRegister) => { - componentToRegister.register(componentMap, propsConverterMap, propConfigsConverterMap, resolverMap); - }); + const componentsToRegister = [ + Component, PageContainer, PageHeaderContainer, PageBodyContainer, PageFooterContainer, + ContentContainer, FloatContainer, Card, + Button, ButtonGroup, Navbar, Listview, + Form, FormItem, InputGroup, Textarea, NumberInput, Switch, + DatePicker, DateTimePicker, Picker, + Lookup, RadioGroup, CheckboxGroup + ]; + componentsToRegister.forEach((componentToRegister) => { + componentToRegister.register(componentMap, propsConverterMap, propConfigsConverterMap, resolverMap, registerContext); + }); } -export { componentMap, propsConverterMap, propConfigsConverterMap, resolverMap, registerComponents }; +export { + componentMap, + propsConverterMap, + propConfigsConverterMap, + resolverMap, + registerComponents +}; diff --git a/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts b/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts index e41b9f6c3a974001e99cc4afdfc4f87afcfd0951..8c4225e05d391cb37b91c52ebb3bdf53cd0f3b00 100644 --- a/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts +++ b/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts @@ -1,24 +1,9 @@ -import type { VNode } from 'vue'; +import { ButtonItem } from '@farris/mobile-ui-vue/button-group'; -export interface SwipeCellButton { - - /** 按钮文本 */ - text?: string; - - /** 图标 */ - icon?: string | VNode; - - /** 自定义类名 */ - className?: string; - - /** 自定义样式 */ - style?: string; - - /** 点击事件回调方法 */ - onClick?: (close: (() => void)) => void; - - [key: string]: any; -} +export type SwipeCellButton = Pick< + ButtonItem, + 'id' | 'text' | 'icon' | 'type' | 'visible' | 'disabled' | 'customClass' | 'customStyle' | 'onClick' +>; export type SwipeCellSide = 'left' | 'right'; diff --git a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx index dc966947fb9b5295a0302520c19d398196a83a28..c7eec25ab8baf89700555f284a00738f8da0a087 100644 --- a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx +++ b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx @@ -15,7 +15,6 @@ */ import { defineComponent, - SetupContext, ref, Ref, reactive, @@ -23,6 +22,7 @@ import { h, onMounted, computed, + watch, } from 'vue'; import { SWIPE_CELL_NAME, swipeCellProps, SwipeCellProps } from './swipe-cell.props'; import { @@ -43,7 +43,7 @@ export default defineComponent({ emits: ['click', 'open', 'close'], - setup(props: SwipeCellProps, context: SetupContext) { + setup(props: SwipeCellProps, context) { const { bem } = useBem(SWIPE_CELL_NAME); const { emit, slots, expose } = context; @@ -71,33 +71,33 @@ export default defineComponent({ /** 滑动时禁用点击 */ const forbidClick = ref(false); - const getElementWidth = (elementRef: Ref) => { + function getElementWidth(elementRef: Ref): number { return elementRef.value ? useRect(elementRef).width : 0; - }; + } - const setSideContentWidth = () => { + function setSideContentWidth(): void { state.leftSideWidth = getElementWidth(leftSideRef); state.rightSideWidth = getElementWidth(rightSideRef); - }; + } onMounted(() => { setSideContentWidth(); }); - const keepInRange = (num: number, min: number, max: number) => { + function keepInRange(num: number, min: number, max: number): number { return Math.min(Math.max(num, min), max); - }; + } - const open = (side: SwipeCellSide) => { + function open(side: SwipeCellSide): void { state.offset = side === 'left' ? state.leftSideWidth : -state.rightSideWidth; if (!state.opened) { state.opened = true; emit('open', side); } - }; + } - const close = (clickPosition?: SwipeCellClickPosition) => { + function close(clickPosition?: SwipeCellClickPosition): void { const side: SwipeCellSide = state.offset > 0 ? 'left' : 'right'; state.offset = 0; @@ -105,9 +105,9 @@ export default defineComponent({ state.opened = false; emit('close', { side, clickPosition }); } - }; + } - const handleSwipeEnd = () => { + function handleSwipeEnd(): void { const side: SwipeCellSide = state.offset > 0 ? 'left' : 'right'; const sideWidth = side === 'left' ? state.leftSideWidth : state.rightSideWidth; const offset = Math.abs(state.offset); @@ -119,18 +119,18 @@ export default defineComponent({ } else { close(); } - }; + } - const onTouchStart = (event: TouchEvent) => { + function onTouchStart(event: TouchEvent): void { if (props.disabled) { return; } setSideContentWidth(); state.startOffset = state.offset; touch.start(event); - }; + } - const onTouchMove = (event: TouchEvent) => { + function onTouchMove(event: TouchEvent): void { if (props.disabled) { return; } @@ -149,9 +149,9 @@ export default defineComponent({ } const newOffset = state.startOffset + deltaX.value; state.offset = keepInRange(newOffset, -state.rightSideWidth, state.leftSideWidth); - }; + } - const onTouchEnd = () => { + function onTouchEnd(): void { if (!state.moving) { return; } @@ -161,15 +161,20 @@ export default defineComponent({ setTimeout(() => { forbidClick.value = false; }); - }; + } - const onClick = (position: SwipeCellClickPosition, button?: SwipeCellButton) => { - if (forbidClick.value) { + function onClick(position: SwipeCellClickPosition, button?: SwipeCellButton, event?: MouseEvent): void { + if (forbidClick.value || button?.disabled) { return; } if (button && button.onClick) { - const closeFunc = () => close(position); - button.onClick(closeFunc); + const clickEventParam = { + close: () => close(position), + position, + button, + event, + }; + button.onClick(clickEventParam); } emit('click', { position, button }); const fromSideButton = position === 'left' || position === 'right'; @@ -182,9 +187,9 @@ export default defineComponent({ ) { close(position); } - }; + } - const getClickHandler = (position: SwipeCellClickPosition, button?: SwipeCellButton) => { + function getClickHandler(position: SwipeCellClickPosition, button?: SwipeCellButton) { return (event: MouseEvent) => { if ( position === 'left' @@ -193,11 +198,11 @@ export default defineComponent({ ) { event.stopPropagation(); } - onClick(position, button); + onClick(position, button, event); }; - }; + } - const renderButtonIcon = (button: SwipeCellButton) => { + function renderButtonIcon(button: SwipeCellButton) { const { icon } = button; if (typeof icon === 'string') { return ( @@ -207,13 +212,21 @@ export default defineComponent({ if (isVNode(icon)) { return h(icon, { class: bem('icon') }); } - }; + } - const renderSideButtons = (side: SwipeCellSide, buttons: SwipeCellButton[]) => { - return buttons.map((button, index) => ( + const sideButtonClass = (button: SwipeCellButton) => ({ + [bem('button')]: true, + [bem('button', button.type)]: !!button.type, + }); + + function renderSideButtons(side: SwipeCellSide, buttons: SwipeCellButton[]) { + return buttons.filter((button) => { + return !!button.visible || button.visible === undefined; + }).map((button, index) => (
@@ -221,9 +234,9 @@ export default defineComponent({ {button.text}
)); - }; + } - const renderSideContent = (side: SwipeCellSide, elementRef: Ref) => { + function renderSideContent(side: SwipeCellSide, elementRef: Ref) { const sideContentSlot = slots[side]; const slotParam = { close: () => close(side) }; const sideButtons = side === 'left' ? props.leftButtons : props.rightButtons; @@ -241,19 +254,28 @@ export default defineComponent({ {!sideContentSlot && renderSideButtons(side, sideButtons)} ); - }; + } + + watch( + () => props.disabled, + () => { + if (props.disabled && state.opened) { + close(); + } + }, + ); expose({ open, close: () => close(), }); - useEventListener('touchstart', onTouchStart, { target: rootRef, passive: true }); - useEventListener('touchmove', onTouchMove, { target: rootRef }); + useEventListener('touchstart', onTouchStart as EventListener, { target: rootRef, passive: true }); + useEventListener('touchmove', onTouchMove as EventListener, { target: rootRef }); useEventListener('touchend', onTouchEnd, { target: rootRef }); useEventListener('touchcancel', onTouchEnd, { target: rootRef }); - useEventListener('click', getClickHandler('cell'), { target: rootRef }); + useEventListener('click', getClickHandler('cell') as EventListener, { target: rootRef }); useClickAway(rootRef, () => onClick('outside'), { eventName: 'touchstart' }); diff --git a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts index 71d000b8ac01e90c0df6462ba738e39d2433b073..b0cfafdbef36b9e6977c25cf9237aa2cc7cede83 100644 --- a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts +++ b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts @@ -49,3 +49,4 @@ export const swipeCellProps = { }; export type SwipeCellProps = ExtractPropTypes; +export type { SwipeCellButton }; diff --git a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.scss b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.scss index a67cfcdebf9eee3c57ab7a694fe133e9c4683f10..57e413427e39c1a6b52429b8805ab9a2631bb5bd 100644 --- a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.scss +++ b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.scss @@ -43,6 +43,28 @@ padding: 0 var(--fm-swipe-cell-button-padding); color: var(--fm-swipe-cell-button-text-color); background-color: var(--fm-swipe-cell-button-bg-color); + + &--primary, + &--info { + background: var(--fm-primary-color); + } + + &--secondary { + color: var(--fm-primary-color); + background: var(--fm-button-secondary-color); + } + + &--danger { + background-color: var(--fm-danger-color); + } + + &--warning { + background-color: var(--fm-warning-color); + } + + &--success { + background-color: var(--fm-success-color); + } } &__icon { @@ -53,7 +75,7 @@ font-size: var(--fm-swipe-cell-button-font-size); } - &__icon + &__text:not(:empty) { + &__icon+&__text:not(:empty) { margin-left: 6px; } -} +} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/switch/src/designer/switch.design.component.tsx b/packages/mobile-ui-vue/components/switch/src/designer/switch.design.component.tsx index c8e5c5da540f646a2df08739c3a5f46ceebce514..a56bdc8759e9c2b0e07bbd89424a3faa49b5962d 100644 --- a/packages/mobile-ui-vue/components/switch/src/designer/switch.design.component.tsx +++ b/packages/mobile-ui-vue/components/switch/src/designer/switch.design.component.tsx @@ -1,51 +1,47 @@ - -/** - * 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, inject, onMounted, readonly, ref, SetupContext } from 'vue'; -import { Switch, SwitchProps, switchProps } from '../..'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { Switch, switchProps } from '../..'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; import { useInputGroupDesignerRules } from './use-designer-rules'; export default defineComponent({ - name: 'FmSwitchDesign', - props: switchProps, - emits: [] as (string[] & ThisType) | undefined, - setup(props: SwitchProps, context: SetupContext) { - const elementRef = ref(); - const designerHostService = inject('designer-host-service'); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerRulesComposition = useInputGroupDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); + name: 'FmSwitchDesign', + inheritAttrs: false, + props: extractProperties(switchProps, ['activeColor', 'inactiveColor']), + setup(props, context) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerRulesComposition = useInputGroupDesignerRules( + designItemContext, + designerHostService + ); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - const inputGroupProps = computed(() => ({ - ...props, - editable: false, - modelValue:null - })); + const inputGroupProps = computed(() => ({ + ...props, + editable: false, + modelValue: null + })); - context.expose(componentInstance.value); + context.expose(componentInstance.value); - return () => { - return ( - - ); - }; - } + return () => { + return ; + }; + } }); diff --git a/packages/mobile-ui-vue/components/switch/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/switch/src/designer/use-designer-rules.ts index 95e71077dc030c7108f1740107d100871fa1aff3..f73c931ad4ab562c036a372ab6d0e43cdd4b81c3 100644 --- a/packages/mobile-ui-vue/components/switch/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/switch/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { SwitchProperty } from "../property-config/switch.property-config"; export function useInputGroupDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -6,8 +6,8 @@ export function useInputGroupDesignerRules(designItemContext: DesignerItemContex // 构造属性配置方法 function getPropsConfig(componentId: string, componentInstance: DesignerComponentInstance) { - const inputGroupProps = new SwitchProperty(componentId, designerHostService); - return inputGroupProps.getPropertyConfig(schema, componentInstance); + const inputGroupProps = new SwitchProperty(componentId, designerHostService); + return inputGroupProps.getPropertyConfig(schema, componentInstance); } return { getPropsConfig } as UseDesignerRules; diff --git a/packages/mobile-ui-vue/components/switch/src/property-config/switch.property-config.ts b/packages/mobile-ui-vue/components/switch/src/property-config/switch.property-config.ts index ed4cd54c1f82afdac8ed798ece8f3ca68b1e5b99..8e3ee681a1d02ff4216c1920a8b31a18ec2082ed 100644 --- a/packages/mobile-ui-vue/components/switch/src/property-config/switch.property-config.ts +++ b/packages/mobile-ui-vue/components/switch/src/property-config/switch.property-config.ts @@ -1,4 +1,4 @@ -import { InputBaseProperty } from '@farris/mobile-ui-vue/common/src/entity/input-base-property'; +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; export class SwitchProperty extends InputBaseProperty { constructor(componentId: string, designerHostService: any) { diff --git a/packages/mobile-ui-vue/components/switch/src/switch.component.tsx b/packages/mobile-ui-vue/components/switch/src/switch.component.tsx index 5035a7c20f67e9d9f11b61133c5fa955086a0ee1..404d342baad3a3baf50272fe1950878adcda316b 100644 --- a/packages/mobile-ui-vue/components/switch/src/switch.component.tsx +++ b/packages/mobile-ui-vue/components/switch/src/switch.component.tsx @@ -54,7 +54,7 @@ export default defineComponent({ }; const renderLoading = () => { - return ; + return ; }; const switchClass = computed(() => ({ diff --git a/packages/mobile-ui-vue/components/textarea/src/designer/textarea.design.component.tsx b/packages/mobile-ui-vue/components/textarea/src/designer/textarea.design.component.tsx index e125d0ec7ca3f054cb80eb5526425ae1f606b58b..d346aec2569ac807a04ebc0580321c2e85db7bca 100644 --- a/packages/mobile-ui-vue/components/textarea/src/designer/textarea.design.component.tsx +++ b/packages/mobile-ui-vue/components/textarea/src/designer/textarea.design.component.tsx @@ -1,53 +1,49 @@ - -/** - * 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, inject, onMounted, readonly, ref, SetupContext } from 'vue'; -import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@farris/mobile-ui-vue/designer-canvas';import { TEXTAREA_NAME, textareaProps, TextareaProps } from '../textarea.props'; +import { computed, defineComponent, inject, onMounted, ref } from 'vue'; +import { + DesignerHostService, + DesignerItemContext, + extractProperties, + useDesignerComponent +} from '@farris/mobile-ui-vue/common'; import { useTextareaDesignerRules } from './use-designer-rules'; -import Textarea from '../..'; - +import { textareaProps } from '../textarea.props'; +import Textarea from '../textarea.component'; export default defineComponent({ - name: 'FmTextareaDesign', - props: textareaProps, - emits: [] as (string[] & ThisType) | undefined, - setup(props: TextareaProps, context: SetupContext) { - const elementRef = ref(); - const designerHostService = inject('designer-host-service'); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerRulesComposition = useTextareaDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); - - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + name: 'FmTextareaDesign', + inheritAttrs: false, + props: extractProperties(textareaProps, ['placeholder', 'rows', 'showWordLimit', 'maxLength']), + setup(props, context) { + const elementRef = ref(); + const designerHostService = inject('designer-host-service'); + const designItemContext = inject( + 'design-item-context' + ) as DesignerItemContext; + const designerRulesComposition = useTextareaDesignerRules( + designItemContext, + designerHostService + ); + const componentInstance = useDesignerComponent( + elementRef, + designItemContext, + designerRulesComposition + ); - const inputGroupProps = computed(() => ({ - ...props, - type:"textarea", - editable: false, - modelValue:null - })); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); + + context.expose(componentInstance.value); - context.expose(componentInstance.value); + const inputProps = computed(() => { + return { + ...props, + editable: false, + }; + }); - return () => { - return ( - - ); - }; - } + return () => { + return ; + }; + } }); diff --git a/packages/mobile-ui-vue/components/textarea/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/textarea/src/designer/use-designer-rules.ts index 4686fe300d4000683cf56a3898484d6575c12458..8359c1491343b5846f479d82d999da50655a8a06 100644 --- a/packages/mobile-ui-vue/components/textarea/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/textarea/src/designer/use-designer-rules.ts @@ -1,4 +1,4 @@ -import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/designer-canvas"; +import { ComponentSchema, DesignerComponentInstance, DesignerItemContext, UseDesignerRules } from "@farris/mobile-ui-vue/common"; import { TextareaProperty } from "../property-config/textarea.property-config"; export function useTextareaDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { diff --git a/packages/mobile-ui-vue/components/textarea/src/property-config/textarea.property-config.ts b/packages/mobile-ui-vue/components/textarea/src/property-config/textarea.property-config.ts index b43218b55be92a00305d3c84cdc822d3b4632332..bf92847b94a57f1ce302b44f8e83815fd194dcf5 100644 --- a/packages/mobile-ui-vue/components/textarea/src/property-config/textarea.property-config.ts +++ b/packages/mobile-ui-vue/components/textarea/src/property-config/textarea.property-config.ts @@ -1,39 +1,48 @@ -import { InputBaseProperty } from "@farris/mobile-ui-vue/common/src/entity/input-base-property"; +import { InputBaseProperty } from '@farris/mobile-ui-vue/common'; export class TextareaProperty extends InputBaseProperty { + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + this.labelAlignReadonly = true; + } + getBasicProperties(propertyData, componentInstance){ + propertyData.labelAlign = 'top'; - constructor(componentId: string, designerHostService: any) { - super(componentId, designerHostService); - } - - getEditorProperties(propertyData: any) { - return this.getComponentConfig(propertyData, { type: "textarea" }, { - rows: { - description: "", - title: "文本区域可见的行数", - type: "number", - editor: { - min: 0, - nullable:true - } - }, - showCount: { - description: "", - title: "展示输入文本数量", - type: "boolean" - }, - maxLength: { - description: "文本最大长度", - title: "最大长度", - type: "number" - }, - autoHeight: { - description: "", - title: "自动高度", - type: "boolean" - }, - }); - } - - + return super.getBasicProperties(propertyData, componentInstance); + } + getEditorProperties(propertyData: any) { + return this.getComponentConfig( + propertyData, + { type: 'textarea' }, + { + rows: { + description: '', + title: '文本区域可见的行数', + type: 'number', + editor: { + min: 1, + nullable: true + } + }, + maxLength: { + description: '最大字数', + title: '最大字数', + type: 'number', + editor: { + nullable: true + } + }, + showCount: { + description: '', + title: '展示输入文本数量', + type: 'boolean' + }, + autoHeight: { + description: '', + title: '自动高度', + type: 'boolean' + } + } + ); + } } diff --git a/packages/mobile-ui-vue/components/textarea/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/textarea/src/schema/schema-mapper.ts index 42b3a02141cbb40a799489f47e0c50df2a3e29f9..55ccab23420924c9e0c30a565a9a51c23d2a403d 100644 --- a/packages/mobile-ui-vue/components/textarea/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/textarea/src/schema/schema-mapper.ts @@ -2,5 +2,6 @@ import { MapperFunction, resolveAppearance } from '@farris/mobile-ui-vue/dynamic export const schemaMapper = new Map([ ['appearance', resolveAppearance], - ['binding', 'modelValue'] + ['binding', 'modelValue'], + ['showCount', 'showWordLimit'], ]); diff --git a/packages/mobile-ui-vue/components/textarea/src/schema/textarea.schema.json b/packages/mobile-ui-vue/components/textarea/src/schema/textarea.schema.json index 44635921d58dd44046b62b70c135f6899cf07ba9..d35c71704773613719e774820710c9c7dad2017c 100644 --- a/packages/mobile-ui-vue/components/textarea/src/schema/textarea.schema.json +++ b/packages/mobile-ui-vue/components/textarea/src/schema/textarea.schema.json @@ -62,7 +62,8 @@ }, "rows": { "description": "最大行数", - "type": "number" + "type": "number", + "default": 2 }, "autoHeight": { "description": "自动高度", diff --git a/packages/mobile-ui-vue/components/textarea/src/textarea.component.tsx b/packages/mobile-ui-vue/components/textarea/src/textarea.component.tsx index 3c626226999b867c27f60a10a78c43dfc08bf8aa..8714bcd9b5ee6cb5f2310485c2f2e5dcf72f1133 100644 --- a/packages/mobile-ui-vue/components/textarea/src/textarea.component.tsx +++ b/packages/mobile-ui-vue/components/textarea/src/textarea.component.tsx @@ -1,35 +1,14 @@ -/** - * 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 } from 'vue'; import InputGroup from "@farris/mobile-ui-vue/input-group"; -import { TEXTAREA_NAME, textareaProps, TextareaProps } from './textarea.props'; +import { TEXTAREA_NAME, textareaProps } from './textarea.props'; export default defineComponent({ name: TEXTAREA_NAME, props: textareaProps, - setup(props: TextareaProps, context) { - const { emit } = context; - - const handleValueChange = (value: string | number) => { - emit('update:modelValue', value); - }; + setup(props) { return () => ( - + ); } }); diff --git a/packages/mobile-ui-vue/components/textarea/src/textarea.props.ts b/packages/mobile-ui-vue/components/textarea/src/textarea.props.ts index 4eb0aa16e86e908e071be975a8d1ac9e83c4ebe6..18f8b328dac93fd5082977a2da9f5a2c7a246141 100644 --- a/packages/mobile-ui-vue/components/textarea/src/textarea.props.ts +++ b/packages/mobile-ui-vue/components/textarea/src/textarea.props.ts @@ -1,5 +1,5 @@ -import { ExtractPropTypes } from 'vue'; -import { inputCommonProps } from '@farris/mobile-ui-vue/input-group'; +import { ExtractPropTypes, PropType } from 'vue'; +import { inputProps, InputType } from '@farris/mobile-ui-vue/input-group'; import { getPropsResolverGenerator } from '@farris/mobile-ui-vue/dynamic-resolver'; import textareaSchema from './schema/textarea.schema.json'; import { schemaMapper } from './schema/schema-mapper'; @@ -8,19 +8,11 @@ import { schemaResolver } from './schema/schema-resolver'; export const TEXTAREA_NAME = 'FmTextarea'; export const textareaProps = { - ...inputCommonProps, + ...inputProps, - autoHeight: { type: Boolean, default: undefined }, - - rows: { type: Number, default: undefined }, - - maxHeight: { type: Number, default: undefined }, - - minHeight: { type: Number, default: undefined }, - - showWordLimit: { type: Boolean, default: undefined } -} as Record; + type: { type: String as PropType, default: InputType.textarea }, +}; export type TextareaProps = ExtractPropTypes; -export const propsResolverGenerator = getPropsResolverGenerator(textareaProps, textareaSchema, schemaMapper, schemaResolver); +export const propsResolverGenerator = getPropsResolverGenerator(textareaProps, textareaSchema, schemaMapper, schemaResolver); diff --git a/packages/mobile-ui-vue/demos/button/add.vue b/packages/mobile-ui-vue/demos/button/add.vue index 1004e9dba9a7ce772a8100c51293b2caa8a35b9a..1510603d34fd2339976d024276871094e0eab35f 100644 --- a/packages/mobile-ui-vue/demos/button/add.vue +++ b/packages/mobile-ui-vue/demos/button/add.vue @@ -1,7 +1,3 @@ - - - - diff --git a/packages/mobile-ui-vue/demos/button/base.vue b/packages/mobile-ui-vue/demos/button/base.vue index 336dddcddfb18b59f382a8d39d4403bbbf7747c6..6caa6bce94900cbdc8d9d33ce5342c632b80d919 100644 --- a/packages/mobile-ui-vue/demos/button/base.vue +++ b/packages/mobile-ui-vue/demos/button/base.vue @@ -5,9 +5,5 @@ 成功按钮 警告按钮 危险按钮 - 信息按钮 - - - diff --git a/packages/mobile-ui-vue/demos/button/block.vue b/packages/mobile-ui-vue/demos/button/block.vue index ac1b62ab74951d8f0305fdb27ff6a5721fbb5093..b8cadd0af898c8118e7914ec7e1f37e2f7410628 100644 --- a/packages/mobile-ui-vue/demos/button/block.vue +++ b/packages/mobile-ui-vue/demos/button/block.vue @@ -1,7 +1,3 @@ - - - - diff --git a/packages/mobile-ui-vue/demos/button/color.vue b/packages/mobile-ui-vue/demos/button/color.vue index 954659decfa54001f1d8ddf0e235b09809720d8f..18d1ed76361c1061dfdacb925f208704db5de487 100644 --- a/packages/mobile-ui-vue/demos/button/color.vue +++ b/packages/mobile-ui-vue/demos/button/color.vue @@ -3,7 +3,3 @@ 单色按钮 渐变色按钮 - - - - diff --git a/packages/mobile-ui-vue/demos/button/disabled.vue b/packages/mobile-ui-vue/demos/button/disabled.vue index 6e242fa688c2b692eaa92a1907f697f96bd563d8..6313e98fe33d41ff7b3010dcaa8745fc292520af 100644 --- a/packages/mobile-ui-vue/demos/button/disabled.vue +++ b/packages/mobile-ui-vue/demos/button/disabled.vue @@ -2,7 +2,3 @@ 禁用状态 禁用状态 - - - - diff --git a/packages/mobile-ui-vue/demos/button/icon.vue b/packages/mobile-ui-vue/demos/button/icon.vue index 2f94fe14bc35f3bfd18c4ecc093fcfddc8d94b26..a8c161cde4173fc6831c1d6298c4111406f5e670 100644 --- a/packages/mobile-ui-vue/demos/button/icon.vue +++ b/packages/mobile-ui-vue/demos/button/icon.vue @@ -2,7 +2,3 @@ 按钮 - - - - diff --git a/packages/mobile-ui-vue/demos/button/loading.vue b/packages/mobile-ui-vue/demos/button/loading.vue index 1659578b827af52d90e0da3188b831e1acd58717..eb010eb779f52f75503a6eacc417ca3bb6c7be83 100644 --- a/packages/mobile-ui-vue/demos/button/loading.vue +++ b/packages/mobile-ui-vue/demos/button/loading.vue @@ -3,7 +3,3 @@ - - - - diff --git a/packages/mobile-ui-vue/demos/button/plain.vue b/packages/mobile-ui-vue/demos/button/plain.vue index 200028ea59bddfa68165b058c024e7606a32c2bb..9a82b77133e24bb0602c926ab9948dec65df65bb 100644 --- a/packages/mobile-ui-vue/demos/button/plain.vue +++ b/packages/mobile-ui-vue/demos/button/plain.vue @@ -3,7 +3,3 @@ 线框按钮 线框按钮 - - - - diff --git a/packages/mobile-ui-vue/demos/button/round.vue b/packages/mobile-ui-vue/demos/button/round.vue index c7d8c18833e459455e5e35dcbe378be9a8148627..558edd35c7fe0d58dcad1328b407e4033d0ed020 100644 --- a/packages/mobile-ui-vue/demos/button/round.vue +++ b/packages/mobile-ui-vue/demos/button/round.vue @@ -3,6 +3,3 @@ 圆角按钮 - - - diff --git a/packages/mobile-ui-vue/demos/button/size.vue b/packages/mobile-ui-vue/demos/button/size.vue index b023d231a9105fa89f3c441c9c633719366706cd..3ef91c052595108bbe8ce7bf49759e8998327a69 100644 --- a/packages/mobile-ui-vue/demos/button/size.vue +++ b/packages/mobile-ui-vue/demos/button/size.vue @@ -1,11 +1,6 @@ - - - - diff --git a/packages/mobile-ui-vue/demos/card/base.vue b/packages/mobile-ui-vue/demos/card/base.vue new file mode 100644 index 0000000000000000000000000000000000000000..c9fbc6e741690dcf6ec8bcdf11cf431ea3ad9157 --- /dev/null +++ b/packages/mobile-ui-vue/demos/card/base.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/card/index.vue b/packages/mobile-ui-vue/demos/card/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..3010a50ba932ec7481ab8f015b9322447be5f72b --- /dev/null +++ b/packages/mobile-ui-vue/demos/card/index.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/dialog/base.vue b/packages/mobile-ui-vue/demos/dialog/base.vue index 860847345ce9624a7801d6d9f8af9077ddfbf351..de704010889dd5eb400fe82a0adfc35f3264ac81 100644 --- a/packages/mobile-ui-vue/demos/dialog/base.vue +++ b/packages/mobile-ui-vue/demos/dialog/base.vue @@ -12,7 +12,7 @@ import { Dialog } from '@farris/mobile-ui-vue/dialog'; const onClickAlert = () => { Dialog.alert({ title: '标题', - message: '代码是写出来给人看的,附带能在机器上运行' + message: '代码是写出来给人看的,附带能在机器上运行', }); }; const onClickAlertWithOutTitle = () => { diff --git a/packages/mobile-ui-vue/demos/input/text-area.vue b/packages/mobile-ui-vue/demos/input/text-area.vue index 4be137c5d50f564490d08de90e1a818a56ce0933..1b4af6f69c17960a9d967431461427ca8c68fb8a 100644 --- a/packages/mobile-ui-vue/demos/input/text-area.vue +++ b/packages/mobile-ui-vue/demos/input/text-area.vue @@ -3,5 +3,5 @@ - + diff --git a/packages/mobile-ui-vue/demos/list-view/index.vue b/packages/mobile-ui-vue/demos/list-view/index.vue index 57b2fb963c56eecdfa4ac2a39a7e443019b70dd4..ae9847676d6481f92dd4fa2b0ed1f1dad69f09ff 100644 --- a/packages/mobile-ui-vue/demos/list-view/index.vue +++ b/packages/mobile-ui-vue/demos/list-view/index.vue @@ -6,12 +6,16 @@ + + + diff --git a/packages/mobile-ui-vue/demos/list-view/swipe-cell.vue b/packages/mobile-ui-vue/demos/list-view/swipe-cell.vue new file mode 100644 index 0000000000000000000000000000000000000000..8bfac2dba6fef2ba997a0fd090263e4816b100d6 --- /dev/null +++ b/packages/mobile-ui-vue/demos/list-view/swipe-cell.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/loading/index.vue b/packages/mobile-ui-vue/demos/loading/index.vue index 1257fd20ca40e0c392ddcddef2223bac02652402..4ad8eda3e55dd2c4f69dce72331712ffff59da51 100644 --- a/packages/mobile-ui-vue/demos/loading/index.vue +++ b/packages/mobile-ui-vue/demos/loading/index.vue @@ -17,6 +17,9 @@ + + + diff --git a/packages/mobile-ui-vue/demos/loading/service.vue b/packages/mobile-ui-vue/demos/loading/service.vue new file mode 100644 index 0000000000000000000000000000000000000000..1293e20fbaa00befbdec4285f3e3492e08859673 --- /dev/null +++ b/packages/mobile-ui-vue/demos/loading/service.vue @@ -0,0 +1,13 @@ + + diff --git a/packages/mobile-ui-vue/demos/navbar/index.vue b/packages/mobile-ui-vue/demos/navbar/index.vue index e7afdf085e958252a7436af7e73bfe72049de55c..d7634ae55c07340a932085cd796da12aa1ee3465 100644 --- a/packages/mobile-ui-vue/demos/navbar/index.vue +++ b/packages/mobile-ui-vue/demos/navbar/index.vue @@ -1,19 +1,23 @@ diff --git a/packages/mobile-ui-vue/demos/navbar/toolbar.vue b/packages/mobile-ui-vue/demos/navbar/toolbar.vue new file mode 100644 index 0000000000000000000000000000000000000000..41c86e044cd3eb9ac4c7e90a40a8a4547c1e94fd --- /dev/null +++ b/packages/mobile-ui-vue/demos/navbar/toolbar.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue b/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue index 9fef557c86613ceb1e977b7ebc3953f6d537ee32..642899ee5ca4d2ae39d9ad909b1530085e05f784 100644 --- a/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue +++ b/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue @@ -28,8 +28,8 @@ const handleDelete = () => { const rightSideButtons = [ { text: '删除', - style: 'background-color: #F24645', - onClick: (close: () => void) => { + customStyle: 'background-color: #F24645', + onClick: ({ close }) => { Toast({ message: '正在删除...', overlay: true, duration: 1500 }); setTimeout(() => { close(); diff --git a/packages/mobile-ui-vue/demos/swipe-cell/base.vue b/packages/mobile-ui-vue/demos/swipe-cell/base.vue index 40a1ab896888be968e4c31cde614c2c5466b903b..54c40fa17bfd9d5a2904d75a8ed329a2f137d6ee 100644 --- a/packages/mobile-ui-vue/demos/swipe-cell/base.vue +++ b/packages/mobile-ui-vue/demos/swipe-cell/base.vue @@ -12,7 +12,7 @@ import { Toast } from '../../components/toast'; const leftSideButtons = [ { text: '选择', - style: 'background-color: #3A90FF', + customStyle: 'background-color: #3A90FF', onClick: () => { Toast({ message: '选择' }); } @@ -23,7 +23,7 @@ const rightSideButtons = [ { text: '收藏', icon: 's-star-o', - style: 'background-color: #ED7B2F', + customStyle: 'background-color: #ED7B2F', onClick: () => { Toast({ message: '收藏' }); } @@ -31,7 +31,7 @@ const rightSideButtons = [ { text: '删除', icon: h(TrashIcon), - style: 'background-color: #F24645', + customStyle: 'background-color: #F24645', onClick: () => { Toast({ message: '删除' }); } diff --git a/packages/mobile-ui-vue/farris.config.mjs b/packages/mobile-ui-vue/farris.config.mjs index cf03a371ed0b3cdd4a213037d35bd7c37241a81e..68541185b852dd87bf1996b7da9eaaf4c75d2913 100644 --- a/packages/mobile-ui-vue/farris.config.mjs +++ b/packages/mobile-ui-vue/farris.config.mjs @@ -2,7 +2,7 @@ import { fileURLToPath, URL } from 'node:url'; const { BUILD_TYPE } = process.env; const outDir = BUILD_TYPE === 'app' ? "dist" : 'package'; -const externals = [BUILD_TYPE === 'components' ? "@farris/mobile-ui-vue" : '']; +const externals = BUILD_TYPE === 'components' ? ["@farris/mobile-ui-vue", "vue"] : ["vue"]; const externalDependencies = BUILD_TYPE !== 'app'; export default { @@ -31,8 +31,8 @@ export default { } } }, - externalDependencies, - minify: true, + externalDependencies: false, + minify: false, target: 'es2015', alias: [ { find: '@', replacement: fileURLToPath(new URL('./', import.meta.url)) }, diff --git a/packages/mobile-ui-vue/index.html b/packages/mobile-ui-vue/index.html index 23f687958c77ffa05b66cfceb5f3320d8887e496..e8b75dbc3b81404fbcd6aae64d4da7d9e9691ef1 100644 --- a/packages/mobile-ui-vue/index.html +++ b/packages/mobile-ui-vue/index.html @@ -5,7 +5,7 @@ - + Farris Mobile