From e7934d81a1a6568c5e041fffe1d8cab4135673e5 Mon Sep 17 00:00:00 2001 From: dhb52 Date: Tue, 22 Apr 2025 23:31:13 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E6=9E=84=E5=BB=BA=E5=8A=9F=E8=83=BD=20formCreate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/package.json | 6 + apps/web-antd/src/bootstrap.ts | 10 +- .../src/components/FormCreate/index.ts | 3 + .../FormCreate/src/components/DictSelect.vue | 85 +++++ .../src/components/useApiSelect.tsx | 290 ++++++++++++++++++ .../components/FormCreate/src/config/index.ts | 6 + .../FormCreate/src/config/selectRule.ts | 182 +++++++++++ .../src/config/useDictSelectRule.ts | 71 +++++ .../FormCreate/src/config/useEditorRule.ts | 36 +++ .../FormCreate/src/config/useSelectRule.ts | 47 +++ .../src/config/useUploadFileRule.ts | 84 +++++ .../FormCreate/src/config/useUploadImgRule.ts | 93 ++++++ .../src/config/useUploadImgsRule.ts | 89 ++++++ .../components/FormCreate/src/type/index.ts | 52 ++++ .../FormCreate/src/useFormCreateDesigner.ts | 116 +++++++ .../components/FormCreate/src/utils/index.ts | 65 ++++ .../src/components/tinymce/editor.vue | 6 +- .../src/components/upload/images-upload.vue | 272 ++++++++++++++++ apps/web-antd/src/components/upload/index.ts | 1 + .../src/locales/langs/en-US/common.json | 5 + .../src/locales/langs/zh-CN/common.json | 5 + apps/web-antd/src/plugins/formCreate/index.ts | 49 +++ apps/web-antd/src/utils/dict.ts | 72 ++++- apps/web-antd/src/views/infra/build/index.vue | 192 ++++++++++++ pnpm-lock.yaml | 174 ++++++++++- pnpm-workspace.yaml | 3 + 26 files changed, 2003 insertions(+), 11 deletions(-) create mode 100644 apps/web-antd/src/components/FormCreate/index.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/components/DictSelect.vue create mode 100644 apps/web-antd/src/components/FormCreate/src/components/useApiSelect.tsx create mode 100644 apps/web-antd/src/components/FormCreate/src/config/index.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/config/selectRule.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/config/useDictSelectRule.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/config/useEditorRule.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/config/useSelectRule.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/config/useUploadFileRule.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/config/useUploadImgRule.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/config/useUploadImgsRule.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/type/index.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/useFormCreateDesigner.ts create mode 100644 apps/web-antd/src/components/FormCreate/src/utils/index.ts create mode 100644 apps/web-antd/src/components/upload/images-upload.vue create mode 100644 apps/web-antd/src/locales/langs/en-US/common.json create mode 100644 apps/web-antd/src/locales/langs/zh-CN/common.json create mode 100644 apps/web-antd/src/plugins/formCreate/index.ts create mode 100644 apps/web-antd/src/views/infra/build/index.vue diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index d7e71449a..1d08d20dd 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -26,7 +26,11 @@ "#/*": "./src/*" }, "dependencies": { + "@form-create/ant-design-vue": "catalog:", + "@form-create/antd-designer": "catalog:", "@tinymce/tinymce-vue": "catalog:", + "@types/crypto-js": "catalog:", + "@types/lodash.clonedeep": "catalog:", "@vben/access": "workspace:*", "@vben/common-ui": "workspace:*", "@vben/constants": "workspace:*", @@ -47,8 +51,10 @@ "crypto-js": "catalog:", "dayjs": "catalog:", "highlight.js": "catalog:", + "lodash.clonedeep": "catalog:", "pinia": "catalog:", "vue": "catalog:", + "vue-dompurify-html": "catalog:", "vue-router": "catalog:" }, "devDependencies": { diff --git a/apps/web-antd/src/bootstrap.ts b/apps/web-antd/src/bootstrap.ts index e4aaf4057..62779d804 100644 --- a/apps/web-antd/src/bootstrap.ts +++ b/apps/web-antd/src/bootstrap.ts @@ -1,4 +1,5 @@ import { createApp, watchEffect } from 'vue'; +import VueDOMPurifyHTML from 'vue-dompurify-html'; import { registerAccessDirective } from '@vben/access'; import { registerLoadingDirective } from '@vben/common-ui/es/loading'; @@ -10,6 +11,7 @@ import '@vben/styles/antd'; import { useTitle } from '@vueuse/core'; import { $t, setupI18n } from '#/locales'; +import { setupFormCreate } from '#/plugins/formCreate'; import { initComponentAdapter } from './adapter/component'; import App from './app.vue'; @@ -39,7 +41,7 @@ async function bootstrap(namespace: string) { // 国际化 i18n 配置 await setupI18n(app); - // 配置 pinia-tore + // 配置 pinia-store await initStores(app, { namespace }); // 安装权限指令 @@ -52,6 +54,12 @@ async function bootstrap(namespace: string) { // 配置路由及路由守卫 app.use(router); + // formCreate + setupFormCreate(app); + + // vue-dompurify-html + app.use(VueDOMPurifyHTML); + // 配置Motion插件 const { MotionPlugin } = await import('@vben/plugins/motion'); app.use(MotionPlugin); diff --git a/apps/web-antd/src/components/FormCreate/index.ts b/apps/web-antd/src/components/FormCreate/index.ts new file mode 100644 index 000000000..84b612db6 --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/index.ts @@ -0,0 +1,3 @@ +export { useApiSelect } from './src/components/useApiSelect'; + +export { useFormCreateDesigner } from './src/useFormCreateDesigner'; diff --git a/apps/web-antd/src/components/FormCreate/src/components/DictSelect.vue b/apps/web-antd/src/components/FormCreate/src/components/DictSelect.vue new file mode 100644 index 000000000..339c4fb4b --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/components/DictSelect.vue @@ -0,0 +1,85 @@ + + + + diff --git a/apps/web-antd/src/components/FormCreate/src/components/useApiSelect.tsx b/apps/web-antd/src/components/FormCreate/src/components/useApiSelect.tsx new file mode 100644 index 000000000..ee71fa5cd --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/components/useApiSelect.tsx @@ -0,0 +1,290 @@ +import type { ApiSelectProps } from '#/components/FormCreate/src/type'; + +import { defineComponent, onMounted, ref, useAttrs } from 'vue'; + +import { isEmpty } from '@vben/utils'; + +import { + Checkbox, + CheckboxGroup, + Radio, + RadioGroup, + Select, + SelectOption, +} from 'ant-design-vue'; + +import { requestClient } from '#/api/request'; + +export const useApiSelect = (option: ApiSelectProps) => { + return defineComponent({ + name: option.name, + props: { + // 选项标签 + labelField: { + type: String, + default: () => option.labelField ?? 'label', + }, + // 选项的值 + valueField: { + type: String, + default: () => option.valueField ?? 'value', + }, + // api 接口 + url: { + type: String, + default: () => option.url ?? '', + }, + // 请求类型 + method: { + type: String, + default: 'GET', + }, + // 选项解析函数 + parseFunc: { + type: String, + default: '', + }, + // 请求参数 + data: { + type: String, + default: '', + }, + // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio + selectType: { + type: String, + default: 'select', + }, + // 是否多选 + multiple: { + type: Boolean, + default: false, + }, + // 是否远程搜索 + remote: { + type: Boolean, + default: false, + }, + // 远程搜索时携带的参数 + remoteField: { + type: String, + default: 'label', + }, + }, + setup(props) { + const attrs = useAttrs(); + const options = ref([]); // 下拉数据 + const loading = ref(false); // 是否正在从远程获取数据 + const queryParam = ref(); // 当前输入的值 + const getOptions = async () => { + options.value = []; + // 接口选择器 + if (isEmpty(props.url)) { + return; + } + + switch (props.method) { + case 'GET': { + let url: string = props.url; + if (props.remote && queryParam.value !== undefined) { + url = url.includes('?') + ? `${url}&${props.remoteField}=${queryParam.value}` + : `${url}?${props.remoteField}=${queryParam.value}`; + } + parseOptions(await requestClient.get(url)); + break; + } + case 'POST': { + const data: any = JSON.parse(props.data); + if (props.remote) { + data[props.remoteField] = queryParam.value; + } + parseOptions(await requestClient.post(props.url, data)); + break; + } + } + }; + + function parseOptions(data: any) { + // 情况一:如果有自定义解析函数优先使用自定义解析 + if (!isEmpty(props.parseFunc)) { + options.value = parseFunc()?.(data); + return; + } + // 情况二:返回的直接是一个列表 + if (Array.isArray(data)) { + parseOptions0(data); + return; + } + // 情况二:返回的是分页数据,尝试读取 list + data = data.list; + if (!!data && Array.isArray(data)) { + parseOptions0(data); + return; + } + // 情况三:不是 yudao-vue-pro 标准返回 + console.warn( + `接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`, + ); + } + + function parseOptions0(data: any[]) { + if (Array.isArray(data)) { + options.value = data.map((item: any) => ({ + label: parseExpression(item, props.labelField), + value: parseExpression(item, props.valueField), + })); + return; + } + console.warn(`接口[${props.url}] 返回结果不是一个数组`); + } + + function parseFunc() { + let parse: any = null; + if (props.parseFunc) { + // 解析字符串函数 + // eslint-disable-next-line no-new-func + parse = new Function(`return ${props.parseFunc}`)(); + } + return parse; + } + + function parseExpression(data: any, template: string) { + // 检测是否使用了表达式 + if (!template.includes('${')) { + return data[template]; + } + // 正则表达式匹配模板字符串中的 ${...} + const pattern = /\$\{([^}]*)\}/g; + // 使用replace函数配合正则表达式和回调函数来进行替换 + return template.replaceAll(pattern, (_, expr) => { + // expr 是匹配到的 ${} 内的表达式(这里是属性名),从 data 中获取对应的值 + const result = data[expr.trim()]; // 去除前后空白,以防用户输入带空格的属性名 + if (!result) { + console.warn( + `接口选择器选项模版[${template}][${expr.trim()}] 解析值失败结果为[${result}], 请检查属性名称是否存在于接口返回值中,存在则忽略此条!!!`, + ); + } + return result; + }); + } + + const remoteMethod = async (query: any) => { + if (!query) { + return; + } + loading.value = true; + try { + queryParam.value = query; + await getOptions(); + } finally { + loading.value = false; + } + }; + + onMounted(async () => { + await getOptions(); + }); + + const buildSelect = () => { + if (props.multiple) { + // fix:多写此步是为了解决 multiple 属性问题 + return ( + + ); + } + return ( + + ); + }; + const buildCheckbox = () => { + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' }, + ]; + } + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + {item.label} + + ), + )} + + ); + }; + const buildRadio = () => { + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' }, + ]; + } + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + {item.label} + + ), + )} + + ); + }; + return () => ( + <> + {(() => { + switch (props.selectType) { + case 'checkbox': { + return buildCheckbox(); + } + case 'radio': { + return buildRadio(); + } + case 'select': { + return buildSelect(); + } + default: { + return buildSelect(); + } + } + })()} + + ); + }, + }); +}; diff --git a/apps/web-antd/src/components/FormCreate/src/config/index.ts b/apps/web-antd/src/components/FormCreate/src/config/index.ts new file mode 100644 index 000000000..49b9d3bda --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/index.ts @@ -0,0 +1,6 @@ +export { useDictSelectRule } from './useDictSelectRule'; +export { useEditorRule } from './useEditorRule'; +export { useSelectRule } from './useSelectRule'; +export { useUploadFileRule } from './useUploadFileRule'; +export { useUploadImgRule } from './useUploadImgRule'; +export { useUploadImgsRule } from './useUploadImgsRule'; diff --git a/apps/web-antd/src/components/FormCreate/src/config/selectRule.ts b/apps/web-antd/src/components/FormCreate/src/config/selectRule.ts new file mode 100644 index 000000000..2c6cee2ce --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/selectRule.ts @@ -0,0 +1,182 @@ +/* eslint-disable no-template-curly-in-string */ +const selectRule = [ + { + type: 'select', + field: 'selectType', + title: '选择器类型', + value: 'select', + options: [ + { label: '下拉框', value: 'select' }, + { label: '单选框', value: 'radio' }, + { label: '多选框', value: 'checkbox' }, + ], + // 参考 https://www.form-create.com/v3/guide/control 组件联动,单选框和多选框不需要多选属性 + control: [ + { + value: 'select', + condition: '==', + method: 'hidden', + rule: [ + 'multiple', + 'clearable', + 'collapseTags', + 'multipleLimit', + 'allowCreate', + 'filterable', + 'noMatchText', + 'remote', + 'remoteMethod', + 'reserveKeyword', + 'defaultFirstOption', + 'automaticDropdown', + ], + }, + ], + }, + { + type: 'switch', + field: 'filterable', + title: '是否可搜索', + }, + { type: 'switch', field: 'multiple', title: '是否多选' }, + { + type: 'switch', + field: 'disabled', + title: '是否禁用', + }, + { type: 'switch', field: 'clearable', title: '是否可以清空选项' }, + { + type: 'switch', + field: 'collapseTags', + title: '多选时是否将选中值按文字的形式展示', + }, + { + type: 'inputNumber', + field: 'multipleLimit', + title: '多选时用户最多可以选择的项目数,为 0 则不限制', + props: { min: 0 }, + }, + { + type: 'input', + field: 'autocomplete', + title: 'autocomplete 属性', + }, + { type: 'input', field: 'placeholder', title: '占位符' }, + { type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' }, + { + type: 'input', + field: 'noMatchText', + title: '搜索条件无匹配时显示的文字', + }, + { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' }, + { + type: 'switch', + field: 'reserveKeyword', + title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词', + }, + { + type: 'switch', + field: 'defaultFirstOption', + title: '在输入框按下回车,选择第一个匹配项', + }, + { + type: 'switch', + field: 'popperAppendToBody', + title: '是否将弹出框插入至 body 元素', + value: true, + }, + { + type: 'switch', + field: 'automaticDropdown', + title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单', + }, +]; + +const apiSelectRule = [ + { + type: 'input', + field: 'url', + title: 'url 地址', + props: { + placeholder: '/system/user/simple-list', + }, + }, + { + type: 'select', + field: 'method', + title: '请求类型', + value: 'GET', + options: [ + { label: 'GET', value: 'GET' }, + { label: 'POST', value: 'POST' }, + ], + control: [ + { + value: 'GET', + condition: '!=', + method: 'hidden', + rule: [ + { + type: 'input', + field: 'data', + title: '请求参数 JSON 格式', + props: { + autosize: true, + type: 'textarea', + placeholder: '{"type": 1}', + }, + }, + ], + }, + ], + }, + { + type: 'input', + field: 'labelField', + title: 'label 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', + props: { + placeholder: 'nickname', + }, + }, + { + type: 'input', + field: 'valueField', + title: 'value 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', + props: { + placeholder: 'id', + }, + }, + { + type: 'input', + field: 'parseFunc', + title: '选项解析函数', + info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表 + (data: any)=>{ label: string; value: any }[]`, + props: { + autosize: true, + rows: { minRows: 2, maxRows: 6 }, + type: 'textarea', + placeholder: ` + function (data) { + console.log(data) + return data.list.map(item=> ({label: item.nickname,value: item.id})) + }`, + }, + }, + { + type: 'switch', + field: 'remote', + info: '是否可搜索', + title: '其中的选项是否从服务器远程加载', + }, + { + type: 'input', + field: 'remoteField', + title: '请求参数', + info: '远程请求时请求携带的参数名称,如:name', + }, +]; + +export { apiSelectRule, selectRule }; diff --git a/apps/web-antd/src/components/FormCreate/src/config/useDictSelectRule.ts b/apps/web-antd/src/components/FormCreate/src/config/useDictSelectRule.ts new file mode 100644 index 000000000..19469207c --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/useDictSelectRule.ts @@ -0,0 +1,71 @@ +import { onMounted, ref } from 'vue'; + +import { buildUUID } from '@vben/utils'; + +import cloneDeep from 'lodash.clonedeep'; + +import * as DictDataApi from '#/api/system/dict/type'; +import { selectRule } from '#/components/FormCreate/src/config/selectRule'; +import { + localeProps, + makeRequiredRule, +} from '#/components/FormCreate/src/utils'; + +/** + * 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule + */ +export const useDictSelectRule = () => { + const label = '字典选择器'; + const name = 'DictSelect'; + const rules = cloneDeep(selectRule); + const dictOptions = ref<{ label: string; value: string }[]>([]); // 字典类型下拉数据 + onMounted(async () => { + const data = await DictDataApi.getSimpleDictTypeList(); + if (!data || data.length === 0) { + return; + } + dictOptions.value = + data?.map((item: DictDataApi.SystemDictTypeApi.SystemDictType) => ({ + label: item.name, + value: item.type, + })) ?? []; + }); + return { + icon: 'icon-descriptions', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'select', + field: 'dictType', + title: '字典类型', + value: '', + options: dictOptions.value, + }, + { + type: 'select', + field: 'valueType', + title: '字典值类型', + value: 'str', + options: [ + { label: '数字', value: 'int' }, + { label: '字符串', value: 'str' }, + { label: '布尔值', value: 'bool' }, + ], + }, + ...rules, + ]); + }, + }; +}; diff --git a/apps/web-antd/src/components/FormCreate/src/config/useEditorRule.ts b/apps/web-antd/src/components/FormCreate/src/config/useEditorRule.ts new file mode 100644 index 000000000..f7149aa16 --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/useEditorRule.ts @@ -0,0 +1,36 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/FormCreate/src/utils'; + +export const useEditorRule = () => { + const label = '富文本'; + const name = 'Tinymce'; + return { + icon: 'icon-editor', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'input', + field: 'height', + title: '高度', + }, + { type: 'switch', field: 'readonly', title: '是否只读' }, + ]); + }, + }; +}; diff --git a/apps/web-antd/src/components/FormCreate/src/config/useSelectRule.ts b/apps/web-antd/src/components/FormCreate/src/config/useSelectRule.ts new file mode 100644 index 000000000..eee8e8990 --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/useSelectRule.ts @@ -0,0 +1,47 @@ +import type { SelectRuleOption } from '#/components/FormCreate/src/type'; + +import { buildUUID } from '@vben/utils'; + +import cloneDeep from 'lodash.clonedeep'; + +import { selectRule } from '#/components/FormCreate/src/config/selectRule'; +import { + localeProps, + makeRequiredRule, +} from '#/components/FormCreate/src/utils'; + +/** + * 通用选择器规则 hook + * + * @param option 规则配置 + */ +export const useSelectRule = (option: SelectRuleOption) => { + const label = option.label; + const name = option.name; + const rules = cloneDeep(selectRule); + return { + icon: option.icon, + label, + name, + event: option.event, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + if (!option.props) { + option.props = []; + } + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + ...option.props, + ...rules, + ]); + }, + }; +}; diff --git a/apps/web-antd/src/components/FormCreate/src/config/useUploadFileRule.ts b/apps/web-antd/src/components/FormCreate/src/config/useUploadFileRule.ts new file mode 100644 index 000000000..0fafec47a --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/useUploadFileRule.ts @@ -0,0 +1,84 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/FormCreate/src/utils'; + +export const useUploadFileRule = () => { + const label = '文件上传'; + const name = 'FileUpload'; + return { + icon: 'icon-upload', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'select', + field: 'fileType', + title: '文件类型', + value: ['doc', 'xls', 'ppt', 'txt', 'pdf'], + options: [ + { label: 'doc', value: 'doc' }, + { label: 'xls', value: 'xls' }, + { label: 'ppt', value: 'ppt' }, + { label: 'txt', value: 'txt' }, + { label: 'pdf', value: 'pdf' }, + ], + props: { + multiple: true, + }, + }, + { + type: 'switch', + field: 'autoUpload', + title: '是否在选取文件后立即进行上传', + value: true, + }, + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'switch', + field: 'isShowTip', + title: '是否显示提示', + value: true, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'inputNumber', + field: 'limit', + title: '数量限制', + value: 5, + props: { min: 0 }, + }, + { + type: 'switch', + field: 'disabled', + title: '是否禁用', + value: false, + }, + ]); + }, + }; +}; diff --git a/apps/web-antd/src/components/FormCreate/src/config/useUploadImgRule.ts b/apps/web-antd/src/components/FormCreate/src/config/useUploadImgRule.ts new file mode 100644 index 000000000..0eb6dd687 --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/useUploadImgRule.ts @@ -0,0 +1,93 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/FormCreate/src/utils'; + +export const useUploadImgRule = () => { + const label = '单图上传'; + const name = 'ImageUpload'; + return { + icon: 'icon-image', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'select', + field: 'fileType', + title: '图片类型限制', + value: ['image/jpeg', 'image/png', 'image/gif'], + options: [ + { label: 'image/apng', value: 'image/apng' }, + { label: 'image/bmp', value: 'image/bmp' }, + { label: 'image/gif', value: 'image/gif' }, + { label: 'image/jpeg', value: 'image/jpeg' }, + { label: 'image/pjpeg', value: 'image/pjpeg' }, + { label: 'image/svg+xml', value: 'image/svg+xml' }, + { label: 'image/tiff', value: 'image/tiff' }, + { label: 'image/webp', value: 'image/webp' }, + { label: 'image/x-icon', value: 'image/x-icon' }, + ], + props: { + multiple: false, + }, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'input', + field: 'height', + title: '组件高度', + value: '150px', + }, + { + type: 'input', + field: 'width', + title: '组件宽度', + value: '150px', + }, + { + type: 'input', + field: 'borderradius', + title: '组件边框圆角', + value: '8px', + }, + { + type: 'switch', + field: 'disabled', + title: '是否显示删除按钮', + value: true, + }, + { + type: 'switch', + field: 'showBtnText', + title: '是否显示按钮文字', + value: true, + }, + ]); + }, + }; +}; diff --git a/apps/web-antd/src/components/FormCreate/src/config/useUploadImgsRule.ts b/apps/web-antd/src/components/FormCreate/src/config/useUploadImgsRule.ts new file mode 100644 index 000000000..cb8aa8ce0 --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/config/useUploadImgsRule.ts @@ -0,0 +1,89 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/FormCreate/src/utils'; + +export const useUploadImgsRule = () => { + const label = '多图上传'; + const name = 'ImagesUpload'; + return { + icon: 'icon-image', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'select', + field: 'fileType', + title: '图片类型限制', + value: ['image/jpeg', 'image/png', 'image/gif'], + options: [ + { label: 'image/apng', value: 'image/apng' }, + { label: 'image/bmp', value: 'image/bmp' }, + { label: 'image/gif', value: 'image/gif' }, + { label: 'image/jpeg', value: 'image/jpeg' }, + { label: 'image/pjpeg', value: 'image/pjpeg' }, + { label: 'image/svg+xml', value: 'image/svg+xml' }, + { label: 'image/tiff', value: 'image/tiff' }, + { label: 'image/webp', value: 'image/webp' }, + { label: 'image/x-icon', value: 'image/x-icon' }, + ], + props: { + multiple: true, + maxNumber: 5, + }, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'inputNumber', + field: 'limit', + title: '数量限制', + value: 5, + props: { min: 0 }, + }, + { + type: 'input', + field: 'height', + title: '组件高度', + value: '150px', + }, + { + type: 'input', + field: 'width', + title: '组件宽度', + value: '150px', + }, + { + type: 'input', + field: 'borderradius', + title: '组件边框圆角', + value: '8px', + }, + ]); + }, + }; +}; diff --git a/apps/web-antd/src/components/FormCreate/src/type/index.ts b/apps/web-antd/src/components/FormCreate/src/type/index.ts new file mode 100644 index 000000000..cc5e18aae --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/type/index.ts @@ -0,0 +1,52 @@ +import type { Rule } from '@form-create/ant-design-vue'; // 左侧拖拽按钮 +// 左侧拖拽按钮 + +// 左侧拖拽按钮 +export interface MenuItem { + label: string; + name: string; + icon: string; +} + +// 左侧拖拽按钮分类 +export interface Menu { + title: string; + name: string; + list: MenuItem[]; +} + +export type MenuList = Array; + +// 拖拽组件的规则 +export interface DragRule { + icon: string; + name: string; + label: string; + children?: string; + inside?: true; + drag?: string | true; + dragBtn?: false; + mask?: false; + + rule(): Rule; + + props(v: any, v1: any): Rule[]; +} + +// 通用下拉组件 Props 类型 +export interface ApiSelectProps { + name: string; // 组件名称 + labelField?: string; // 选项标签 + valueField?: string; // 选项的值 + url?: string; // url 接口 + isDict?: boolean; // 是否字典选择器 +} + +// 选择组件规则配置类型 +export interface SelectRuleOption { + label: string; // label 名称 + name: string; // 组件名称 + icon: string; // 组件图标 + props?: any[]; // 组件规则 + event?: any[]; // 事件配置 +} diff --git a/apps/web-antd/src/components/FormCreate/src/useFormCreateDesigner.ts b/apps/web-antd/src/components/FormCreate/src/useFormCreateDesigner.ts new file mode 100644 index 000000000..c522c334e --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/useFormCreateDesigner.ts @@ -0,0 +1,116 @@ +import type { Ref } from 'vue'; + +import type { Menu } from '#/components/FormCreate/src/type'; + +import { nextTick, onMounted } from 'vue'; + +import { apiSelectRule } from '#/components/FormCreate/src/config/selectRule'; + +import { + useDictSelectRule, + useEditorRule, + useSelectRule, + useUploadFileRule, + useUploadImgRule, + useUploadImgsRule, +} from './config'; + +/** + * 表单设计器增强 hook + * 新增 + * - 文件上传 + * - 单图上传 + * - 多图上传 + * - 字典选择器 + * - 用户选择器 + * - 部门选择器 + * - 富文本 + */ +export const useFormCreateDesigner = async (designer: Ref) => { + const editorRule = useEditorRule(); + const uploadFileRule = useUploadFileRule(); + const uploadImgRule = useUploadImgRule(); + const uploadImgsRule = useUploadImgsRule(); + + /** + * 构建表单组件 + */ + const buildFormComponents = () => { + // 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代 + designer.value?.removeMenuItem('upload'); + // 移除自带的富文本组件规则,使用 editorRule 替代 + designer.value?.removeMenuItem('fc-editor'); + const components = [ + editorRule, + uploadFileRule, + uploadImgRule, + uploadImgsRule, + ]; + components.forEach((component) => { + // 插入组件规则 + designer.value?.addComponent(component); + // 插入拖拽按钮到 `main` 分类下 + designer.value?.appendMenuItem('main', { + icon: component.icon, + name: component.name, + label: component.label, + }); + }); + }; + + const userSelectRule = useSelectRule({ + name: 'UserSelect', + label: '用户选择器', + icon: 'icon-eye', + }); + const deptSelectRule = useSelectRule({ + name: 'DeptSelect', + label: '部门选择器', + icon: 'icon-tree', + }); + const dictSelectRule = useDictSelectRule(); + const apiSelectRule0 = useSelectRule({ + name: 'ApiSelect', + label: '接口选择器', + icon: 'icon-json', + props: [...apiSelectRule], + event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus'], + }); + + /** + * 构建系统字段菜单 + */ + const buildSystemMenu = () => { + // 移除自带的下拉选择器组件,使用 currencySelectRule 替代 + // designer.value?.removeMenuItem('select') + // designer.value?.removeMenuItem('radio') + // designer.value?.removeMenuItem('checkbox') + const components = [ + userSelectRule, + deptSelectRule, + dictSelectRule, + apiSelectRule0, + ]; + const menu: Menu = { + name: 'system', + title: '系统字段', + list: components.map((component) => { + // 插入组件规则 + designer.value?.addComponent(component); + // 插入拖拽按钮到 `system` 分类下 + return { + icon: component.icon, + name: component.name, + label: component.label, + }; + }), + }; + designer.value?.addMenu(menu); + }; + + onMounted(async () => { + await nextTick(); + buildFormComponents(); + buildSystemMenu(); + }); +}; diff --git a/apps/web-antd/src/components/FormCreate/src/utils/index.ts b/apps/web-antd/src/components/FormCreate/src/utils/index.ts new file mode 100644 index 000000000..816a0f2a3 --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/utils/index.ts @@ -0,0 +1,65 @@ +export function makeRequiredRule() { + return { + type: 'Required', + field: 'formCreate$required', + title: '是否必填', + }; +} + +export const localeProps = ( + t: (msg: string) => any, + prefix: string, + rules: any[], +) => { + return rules.map((rule: { field: string; title: any }) => { + if (rule.field === 'formCreate$required') { + rule.title = t('props.required') || rule.title; + } else if (rule.field && rule.field !== '_optionType') { + rule.title = t(`components.${prefix}.${rule.field}`) || rule.title; + } + return rule; + }); +}; + +/** + * 解析表单组件的 field, title 等字段(递归,如果组件包含子组件) + * + * @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule + * @param fields 解析后表单组件字段 + * @param parentTitle 如果是子表单,子表单的标题,默认为空 + */ +export const parseFormFields = ( + rule: Record, + fields: Array> = [], + parentTitle: string = '', +) => { + const { type, field, $required, title: tempTitle, children } = rule; + if (field && tempTitle) { + let title = tempTitle; + if (parentTitle) { + title = `${parentTitle}.${tempTitle}`; + } + let required = false; + if ($required) { + required = true; + } + fields.push({ + field, + title, + type, + required, + }); + // TODO 子表单 需要处理子表单字段 + // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { + // // 解析子表单的字段 + // rule.props.rule.forEach((item) => { + // parseFields(item, fieldsPermission, title) + // }) + // } + } + if (children && Array.isArray(children)) { + children.forEach((rule) => { + parseFormFields(rule, fields); + }); + } +}; diff --git a/apps/web-antd/src/components/tinymce/editor.vue b/apps/web-antd/src/components/tinymce/editor.vue index 3b0f25ee8..e0e62c2f0 100644 --- a/apps/web-antd/src/components/tinymce/editor.vue +++ b/apps/web-antd/src/components/tinymce/editor.vue @@ -33,7 +33,7 @@ import { type InitOptions = IPropTypes['init']; -defineOptions({ inheritAttrs: false }); +defineOptions({ name: 'Tinymce', inheritAttrs: false }); const props = defineProps({ options: { @@ -157,11 +157,11 @@ const initOptions = computed((): InitOptions => { const { httpRequest } = useUpload(); httpRequest(file) .then((url) => { - console.log('tinymce 上传图片成功:', url); + // console.log('tinymce 上传图片成功:', url); resolve(url); }) .catch((error) => { - console.error('tinymce 上传图片失败:', error); + // console.error('tinymce 上传图片失败:', error); reject(error.message); }); }); diff --git a/apps/web-antd/src/components/upload/images-upload.vue b/apps/web-antd/src/components/upload/images-upload.vue new file mode 100644 index 000000000..35df60cd5 --- /dev/null +++ b/apps/web-antd/src/components/upload/images-upload.vue @@ -0,0 +1,272 @@ + + + + + diff --git a/apps/web-antd/src/components/upload/index.ts b/apps/web-antd/src/components/upload/index.ts index a66b2fca6..e80f44af5 100644 --- a/apps/web-antd/src/components/upload/index.ts +++ b/apps/web-antd/src/components/upload/index.ts @@ -1,2 +1,3 @@ export { default as FileUpload } from './file-upload.vue'; export { default as ImageUpload } from './image-upload.vue'; +export { default as ImagesUpload } from './images-upload.vue'; diff --git a/apps/web-antd/src/locales/langs/en-US/common.json b/apps/web-antd/src/locales/langs/en-US/common.json new file mode 100644 index 000000000..c5cee3ea9 --- /dev/null +++ b/apps/web-antd/src/locales/langs/en-US/common.json @@ -0,0 +1,5 @@ +{ + "copy": "Copy", + "copySuccess": "Copy Success", + "copyError": "Copy Error" +} diff --git a/apps/web-antd/src/locales/langs/zh-CN/common.json b/apps/web-antd/src/locales/langs/zh-CN/common.json new file mode 100644 index 000000000..c2fec61f7 --- /dev/null +++ b/apps/web-antd/src/locales/langs/zh-CN/common.json @@ -0,0 +1,5 @@ +{ + "copy": "复制", + "copySuccess": "复制成功", + "copyError": "复制失败" +} diff --git a/apps/web-antd/src/plugins/formCreate/index.ts b/apps/web-antd/src/plugins/formCreate/index.ts new file mode 100644 index 000000000..e095c4b60 --- /dev/null +++ b/apps/web-antd/src/plugins/formCreate/index.ts @@ -0,0 +1,49 @@ +import type { App } from 'vue'; + +// import install from '@form-create/ant-design-vue/auto-import'; +import FcDesigner from '@form-create/antd-designer'; +import Antd from 'ant-design-vue'; + +// ======================= 自定义组件 ======================= +import { useApiSelect } from '#/components/FormCreate'; +import DictSelect from '#/components/FormCreate/src/components/DictSelect.vue'; +import { Tinymce } from '#/components/tinymce'; +import { FileUpload, ImagesUpload, ImageUpload } from '#/components/upload'; + +const UserSelect = useApiSelect({ + name: 'UserSelect', + labelField: 'nickname', + valueField: 'id', + url: '/system/user/simple-list', +}); +const DeptSelect = useApiSelect({ + name: 'DeptSelect', + labelField: 'name', + valueField: 'id', + url: '/system/dept/simple-list', +}); +const ApiSelect = useApiSelect({ + name: 'ApiSelect', +}); + +const components = [ + ImageUpload, + ImagesUpload, + FileUpload, + Tinymce, + DictSelect, + UserSelect, + DeptSelect, + ApiSelect, +]; + +// TODO: @dhb52 按需导入,而不是app.use(Antd); +// 参考 http://www.form-create.com/v3/ant-design-vue/auto-import.html 文档 +export const setupFormCreate = (app: App) => { + components.forEach((component) => { + app.component(component.name as string, component); + }); + app.use(Antd); + app.use(FcDesigner); + app.use(FcDesigner.formCreate); +}; diff --git a/apps/web-antd/src/utils/dict.ts b/apps/web-antd/src/utils/dict.ts index 9ea9ef757..e647665b9 100644 --- a/apps/web-antd/src/utils/dict.ts +++ b/apps/web-antd/src/utils/dict.ts @@ -6,7 +6,28 @@ import { isObject } from '@vben/utils'; import { useDictStore } from '#/store'; -const dictStore = useDictStore(); +// TODO @dhb52:top-level 调用 导致:"getActivePinia()" was called but there was no active Pinia +// 先临时移入到方法中 +// const dictStore = useDictStore(); + +// TODO @dhb: antd 组件的 color 类型 +type ColorType = 'error' | 'info' | 'success' | 'warning'; + +export interface DictDataType { + dictType: string; + label: string; + value: boolean | number | string; + colorType: ColorType; + cssClass: string; +} + +export interface NumberDictDataType extends DictDataType { + value: number; +} + +export interface StringDictDataType extends DictDataType { + value: string; +} /** * 获取字典标签 @@ -16,8 +37,9 @@ const dictStore = useDictStore(); * @returns 字典标签 */ function getDictLabel(dictType: string, value: any) { + const dictStore = useDictStore(); const dictObj = dictStore.getDictData(dictType, value); - return isObject(dictObj)? dictObj.label : ''; + return isObject(dictObj) ? dictObj.label : ''; } /** @@ -28,6 +50,7 @@ function getDictLabel(dictType: string, value: any) { * @returns 字典对象 */ function getDictObj(dictType: string, value: any) { + const dictStore = useDictStore(); const dictObj = dictStore.getDictData(dictType, value); return isObject(dictObj) ? dictObj : null; } @@ -42,6 +65,7 @@ function getDictOptions( dictType: string, valueType: 'boolean' | 'number' | 'string' = 'string', ) { + const dictStore = useDictStore(); const dictOpts = dictStore.getDictOptions(dictType); const dictOptions: DefaultOptionType = []; if (dictOpts.length > 0) { @@ -71,6 +95,48 @@ function getDictOptions( return dictOptions.length > 0 ? dictOptions : []; } +export const getIntDictOptions = (dictType: string): NumberDictDataType[] => { + // 获得通用的 DictDataType 列表 + const dictOptions = getDictOptions(dictType) as DictDataType[]; + // 转换成 number 类型的 NumberDictDataType 类型 + // why 需要特殊转换:避免 IDEA 在 v-for="dict in getIntDictOptions(...)" 时,el-option 的 key 会告警 + const dictOption: NumberDictDataType[] = []; + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: Number.parseInt(`${dict.value}`), + }); + }); + return dictOption; +}; + +export const getStrDictOptions = (dictType: string) => { + // 获得通用的 DictDataType 列表 + const dictOptions = getDictOptions(dictType) as DictDataType[]; + // 转换成 string 类型的 StringDictDataType 类型 + // why 需要特殊转换:避免 IDEA 在 v-for="dict in getStrDictOptions(...)" 时,el-option 的 key 会告警 + const dictOption: StringDictDataType[] = []; + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: `${dict.value}`, + }); + }); + return dictOption; +}; + +export const getBoolDictOptions = (dictType: string) => { + const dictOption: DictDataType[] = []; + const dictOptions = getDictOptions(dictType) as DictDataType[]; + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: `${dict.value}` === 'true', + }); + }); + return dictOption; +}; + enum DICT_TYPE { AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式 AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态 @@ -205,4 +271,4 @@ enum DICT_TYPE { TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型 USER_TYPE = 'user_type', } -export { DICT_TYPE, getDictObj, getDictLabel, getDictOptions }; +export { DICT_TYPE, getDictLabel, getDictObj, getDictOptions }; diff --git a/apps/web-antd/src/views/infra/build/index.vue b/apps/web-antd/src/views/infra/build/index.vue new file mode 100644 index 000000000..4b7594adc --- /dev/null +++ b/apps/web-antd/src/views/infra/build/index.vue @@ -0,0 +1,192 @@ + + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65c8c0234..f2221f59f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,12 @@ catalogs: '@faker-js/faker': specifier: ^9.6.0 version: 9.7.0 + '@form-create/ant-design-vue': + specifier: ^3.2.22 + version: 3.2.22 + '@form-create/antd-designer': + specifier: ^3.2.11 + version: 3.2.11 '@iconify/json': specifier: ^2.2.324 version: 2.2.330 @@ -498,6 +504,9 @@ catalogs: vitest: specifier: ^2.1.9 version: 2.1.9 + vue-dompurify-html: + specifier: ^5.2.0 + version: 5.2.0 vue-eslint-parser: specifier: ^9.4.3 version: 9.4.3 @@ -665,9 +674,21 @@ importers: apps/web-antd: dependencies: + '@form-create/ant-design-vue': + specifier: 'catalog:' + version: 3.2.22(vue@3.5.13(typescript@5.8.3)) + '@form-create/antd-designer': + specifier: 'catalog:' + version: 3.2.11(vue@3.5.13(typescript@5.8.3)) '@tinymce/tinymce-vue': specifier: 'catalog:' version: 6.1.0(vue@3.5.13(typescript@5.8.3)) + '@types/crypto-js': + specifier: 'catalog:' + version: 4.2.2 + '@types/lodash.clonedeep': + specifier: 'catalog:' + version: 4.5.9 '@vben/access': specifier: workspace:* version: link:../../packages/effects/access @@ -728,19 +749,21 @@ importers: highlight.js: specifier: 'catalog:' version: 11.11.1 + lodash.clonedeep: + specifier: 'catalog:' + version: 4.5.0 pinia: specifier: ^2.3.1 version: 2.3.1(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)) vue: specifier: ^3.5.13 version: 3.5.13(typescript@5.8.3) + vue-dompurify-html: + specifier: 'catalog:' + version: 5.2.0(vue@3.5.13(typescript@5.8.3)) vue-router: specifier: 'catalog:' version: 4.5.0(vue@3.5.13(typescript@5.8.3)) - devDependencies: - '@types/crypto-js': - specifier: 'catalog:' - version: 4.2.2 apps/web-ele: dependencies: @@ -2631,6 +2654,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime-corejs3@7.27.0': + resolution: {integrity: sha512-UWjX6t+v+0ckwZ50Y5ShZLnlk95pP5MyW/pon9tiYzl3+18pkTHTFNTKr7rQbfRXPkowt2QAn30o1b6oswszew==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.27.0': resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} @@ -3548,6 +3575,39 @@ packages: '@floating-ui/vue@1.1.6': resolution: {integrity: sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==} + '@form-create/ant-design-vue@3.2.22': + resolution: {integrity: sha512-A848lhCnNnQCPq/aGLxxJTIhy+vL9h7+YCU2tx1rvqyxmGAYQlJgMy0ET3MpL88eK5DkuB2eyZE5Pt4i4vybeg==} + peerDependencies: + vue: ^3.5.13 + + '@form-create/antd-designer@3.2.11': + resolution: {integrity: sha512-i2MijDIAJeMWfcikqX1LVonWs85K6q5ZoQdOSXAD4MhiFGovQXBTXIenSEzRUlBlGgkwr8u1PeblMO8jCt4sXg==} + peerDependencies: + vue: ^3.5.13 + + '@form-create/component-antdv-frame@3.2.18': + resolution: {integrity: sha512-b6qGkqJnA9JlSnOvEMnkyfcPLg31oSl79i7yytJ3BLCUR8igyNO4O81dhFt4lWoaDe69LQWAtyDLEiF2Ls4OoA==} + + '@form-create/component-antdv-group@3.2.22': + resolution: {integrity: sha512-HfQw5cf7+ikcAXW++T3bLs4yocM9BJH10OgcEy5R0mo2fZfTQ49MCeUhhAUlpoHtJz/4nRijfyhQ6j+D6oAK4g==} + + '@form-create/component-antdv-upload@3.2.18': + resolution: {integrity: sha512-cobjChcblnfO0ph4MunJDUiBLyRwpzekXo6MFRsB5iq9ln73UjLnyLps4YuM2KRZ/Cn9FEoWN1kYvTFf1zKdjg==} + + '@form-create/component-subform@3.1.34': + resolution: {integrity: sha512-OJcFH/7MTHx7JLEjDK/weS27qfuFWAI+OK+gXTJ2jIt9aZkGWF/EWkjetiJLt5a0KMw4Z15wOS2XCY9pVK9vlA==} + + '@form-create/component-wangeditor@3.2.14': + resolution: {integrity: sha512-N/U/hFBdBu2OIguxoKe1Kslq5fW6XmtyhKDImLfKLn1xI6X5WUtt3r7QTaUPcVUl2vntpM9wJ/FBdG17RzF/Dg==} + + '@form-create/core@3.2.22': + resolution: {integrity: sha512-GC3b4Yrpy9TiPLqJFL9fiUFPjEv6ZBcHnOMB+GeF6iLsMV4TpZc0o/oFBPlhZqIYeljaNuxJyO2ABCStceOrZQ==} + peerDependencies: + vue: ^3.5.13 + + '@form-create/utils@3.2.18': + resolution: {integrity: sha512-C98bFPdFVMltiHQvEZqv4rVdhcqthJgvxMbWDlniL03HS5oyusnUvxUE8jf0I9zk5dZRDGmxKOUtzE3JDWP9nQ==} + '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} @@ -5444,6 +5504,10 @@ packages: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} + codemirror@6.65.7: + resolution: {integrity: sha512-HcfnUFJwI2FvH73YWVbbMh7ObWxZiHIycEhv9ZEXy6e8ZKDjtZKbbYFUtsLN46HFXPvU5V2Uvc2d55Z//oFW5A==} + deprecated: This is an accidentally mis-tagged instance of 5.65.7 + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -5616,6 +5680,9 @@ packages: core-js-compat@3.41.0: resolution: {integrity: sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==} + core-js-pure@3.41.0: + resolution: {integrity: sha512-71Gzp96T9YPk63aUvE5Q5qP+DryB4ZloUZPSOebGM88VNw8VNfvdA7z6kGA8iGOTEzAomsRidp4jXSmUIJsL+Q==} + core-js@3.41.0: resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==} @@ -6084,6 +6151,9 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} + dompurify@3.2.5: + resolution: {integrity: sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==} + domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -9671,6 +9741,9 @@ packages: resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sortablejs@1.14.0: + resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==} + sortablejs@1.15.6: resolution: {integrity: sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==} @@ -10704,6 +10777,11 @@ packages: '@vue/composition-api': optional: true + vue-dompurify-html@5.2.0: + resolution: {integrity: sha512-GX+BStkKEJ8wu/+hU1EK2nu/gzXWhb4XzBu6aowpsuU/3nkvXvZ2jx4nZ9M3jtS/Vu7J7MtFXjc7x3cWQ+zbVQ==} + peerDependencies: + vue: ^3.5.13 + vue-eslint-parser@9.4.3: resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} engines: {node: ^14.17.0 || >=16.0.0} @@ -10751,6 +10829,11 @@ packages: typescript: optional: true + vuedraggable@4.1.0: + resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} + peerDependencies: + vue: ^3.5.13 + vueuc@0.4.64: resolution: {integrity: sha512-wlJQj7fIwKK2pOEoOq4Aro8JdPOGpX8aWQhV8YkTW9OgWD2uj2O8ANzvSsIGjx7LTOc7QbS7sXdxHi6XvRnHPA==} peerDependencies: @@ -10762,6 +10845,9 @@ packages: vxe-table@4.13.7: resolution: {integrity: sha512-nwybQ0uPgmAQOvw0gs4oiJ7ifUVCsW0grGyfMC+FdNxFxP2WuiziuKDztjAc16EgHgSDg+KpN4oZoHUAwC55tg==} + wangeditor@4.7.15: + resolution: {integrity: sha512-aPTdREd8BxXVyJ5MI+LU83FQ7u1EPd341iXIorRNYSOvoimNoZ4nPg+yn3FGbB93/owEa6buLw8wdhYnMCJQLg==} + warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} @@ -11889,6 +11975,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/runtime-corejs3@7.27.0': + dependencies: + core-js-pure: 3.41.0 + regenerator-runtime: 0.14.1 + '@babel/runtime@7.27.0': dependencies: regenerator-runtime: 0.14.1 @@ -12892,6 +12983,55 @@ snapshots: - '@vue/composition-api' - vue + '@form-create/ant-design-vue@3.2.22(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@form-create/component-antdv-frame': 3.2.18 + '@form-create/component-antdv-group': 3.2.22 + '@form-create/component-antdv-upload': 3.2.18 + '@form-create/component-subform': 3.1.34 + '@form-create/core': 3.2.22(vue@3.5.13(typescript@5.8.3)) + '@form-create/utils': 3.2.18 + vue: 3.5.13(typescript@5.8.3) + + '@form-create/antd-designer@3.2.11(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@form-create/ant-design-vue': 3.2.22(vue@3.5.13(typescript@5.8.3)) + '@form-create/component-wangeditor': 3.2.14 + '@form-create/utils': 3.2.18 + ant-design-vue: 4.2.6(vue@3.5.13(typescript@5.8.3)) + codemirror: 6.65.7 + element-plus: 2.9.8(vue@3.5.13(typescript@5.8.3)) + js-beautify: 1.15.4 + vue: 3.5.13(typescript@5.8.3) + vuedraggable: 4.1.0(vue@3.5.13(typescript@5.8.3)) + transitivePeerDependencies: + - '@vue/composition-api' + + '@form-create/component-antdv-frame@3.2.18': + dependencies: + '@form-create/utils': 3.2.18 + + '@form-create/component-antdv-group@3.2.22': + dependencies: + '@form-create/utils': 3.2.18 + + '@form-create/component-antdv-upload@3.2.18': + dependencies: + '@form-create/utils': 3.2.18 + + '@form-create/component-subform@3.1.34': {} + + '@form-create/component-wangeditor@3.2.14': + dependencies: + wangeditor: 4.7.15 + + '@form-create/core@3.2.22(vue@3.5.13(typescript@5.8.3))': + dependencies: + '@form-create/utils': 3.2.18 + vue: 3.5.13(typescript@5.8.3) + + '@form-create/utils@3.2.18': {} + '@gar/promisify@1.1.3': {} '@humanfs/core@0.19.1': {} @@ -15137,6 +15277,8 @@ snapshots: cluster-key-slot@1.1.2: {} + codemirror@6.65.7: {} + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -15290,6 +15432,8 @@ snapshots: dependencies: browserslist: 4.24.4 + core-js-pure@3.41.0: {} + core-js@3.41.0: {} core-util-is@1.0.3: {} @@ -15793,6 +15937,10 @@ snapshots: dependencies: domelementtype: 2.3.0 + dompurify@3.2.5: + optionalDependencies: + '@types/trusted-types': 2.0.7 + domutils@2.8.0: dependencies: dom-serializer: 1.4.1 @@ -19703,6 +19851,8 @@ snapshots: ip-address: 9.0.5 smart-buffer: 4.2.0 + sortablejs@1.14.0: {} + sortablejs@1.15.6: {} source-map-js@1.2.1: {} @@ -20919,6 +21069,11 @@ snapshots: dependencies: vue: 3.5.13(typescript@5.8.3) + vue-dompurify-html@5.2.0(vue@3.5.13(typescript@5.8.3)): + dependencies: + dompurify: 3.2.5 + vue: 3.5.13(typescript@5.8.3) + vue-eslint-parser@9.4.3(eslint@9.25.1(jiti@2.4.2)): dependencies: debug: 4.4.0 @@ -20976,6 +21131,11 @@ snapshots: optionalDependencies: typescript: 5.8.3 + vuedraggable@4.1.0(vue@3.5.13(typescript@5.8.3)): + dependencies: + sortablejs: 1.14.0 + vue: 3.5.13(typescript@5.8.3) + vueuc@0.4.64(vue@3.5.13(typescript@5.8.3)): dependencies: '@css-render/vue3-ssr': 0.15.14(vue@3.5.13(typescript@5.8.3)) @@ -20999,6 +21159,12 @@ snapshots: transitivePeerDependencies: - vue + wangeditor@4.7.15: + dependencies: + '@babel/runtime': 7.27.0 + '@babel/runtime-corejs3': 7.27.0 + tslib: 2.8.1 + warning@4.0.3: dependencies: loose-envify: 1.4.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7893e00f9..f0daa2a8a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -39,6 +39,8 @@ catalog: '@tanstack/vue-query': ^5.72.0 '@tanstack/vue-store': ^0.7.0 '@tinymce/tinymce-vue': ^6.1.0 + '@form-create/ant-design-vue': ^3.2.22 + '@form-create/antd-designer': ^3.2.11 '@types/archiver': ^6.0.3 '@types/eslint': ^9.6.1 '@types/html-minifier-terser': ^7.0.2 @@ -182,6 +184,7 @@ catalog: vitepress-plugin-group-icons: ^1.3.8 vitest: ^2.1.9 vue: ^3.5.13 + vue-dompurify-html: ^5.2.0 vue-eslint-parser: ^9.4.3 vue-i18n: ^11.1.3 vue-json-viewer: ^3.0.4 From 1fb5a9e31d2253781d8b4e62a9b7ac0676811d57 Mon Sep 17 00:00:00 2001 From: dhb52 Date: Wed, 23 Apr 2025 02:01:12 +0800 Subject: [PATCH 2/3] =?UTF-8?q?perf:=20=E5=A4=8D=E7=94=A8=20ImageUpload=20?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E6=9E=84=E5=BB=BA=20fc=20=E4=B8=93=E5=B1=9E?= =?UTF-8?q?=20ImagesUpload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/useImagesUpload.tsx | 25 ++ .../src/components/upload/images-upload.vue | 272 ------------------ apps/web-antd/src/components/upload/index.ts | 1 - apps/web-antd/src/plugins/formCreate/index.ts | 4 +- 4 files changed, 28 insertions(+), 274 deletions(-) create mode 100644 apps/web-antd/src/components/FormCreate/src/components/useImagesUpload.tsx delete mode 100644 apps/web-antd/src/components/upload/images-upload.vue diff --git a/apps/web-antd/src/components/FormCreate/src/components/useImagesUpload.tsx b/apps/web-antd/src/components/FormCreate/src/components/useImagesUpload.tsx new file mode 100644 index 000000000..08b27c597 --- /dev/null +++ b/apps/web-antd/src/components/FormCreate/src/components/useImagesUpload.tsx @@ -0,0 +1,25 @@ +import { defineComponent } from 'vue'; + +import ImageUpload from '#/components/upload/image-upload.vue'; + +export const useImagesUpload = () => { + return defineComponent({ + props: { + multiple: { + type: Boolean, + default: true, + }, + maxNumber: { + type: Number, + default: 5, + }, + }, + setup() { + // TODO: @dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递 + return (props: { maxNumber?: number; multiple?: boolean }) => ( + + ); + }, + name: 'ImagesUpload', + }); +}; diff --git a/apps/web-antd/src/components/upload/images-upload.vue b/apps/web-antd/src/components/upload/images-upload.vue deleted file mode 100644 index 35df60cd5..000000000 --- a/apps/web-antd/src/components/upload/images-upload.vue +++ /dev/null @@ -1,272 +0,0 @@ - - - - - diff --git a/apps/web-antd/src/components/upload/index.ts b/apps/web-antd/src/components/upload/index.ts index e80f44af5..a66b2fca6 100644 --- a/apps/web-antd/src/components/upload/index.ts +++ b/apps/web-antd/src/components/upload/index.ts @@ -1,3 +1,2 @@ export { default as FileUpload } from './file-upload.vue'; export { default as ImageUpload } from './image-upload.vue'; -export { default as ImagesUpload } from './images-upload.vue'; diff --git a/apps/web-antd/src/plugins/formCreate/index.ts b/apps/web-antd/src/plugins/formCreate/index.ts index e095c4b60..230ac15b3 100644 --- a/apps/web-antd/src/plugins/formCreate/index.ts +++ b/apps/web-antd/src/plugins/formCreate/index.ts @@ -7,8 +7,9 @@ import Antd from 'ant-design-vue'; // ======================= 自定义组件 ======================= import { useApiSelect } from '#/components/FormCreate'; import DictSelect from '#/components/FormCreate/src/components/DictSelect.vue'; +import { useImagesUpload } from '#/components/FormCreate/src/components/useImagesUpload'; import { Tinymce } from '#/components/tinymce'; -import { FileUpload, ImagesUpload, ImageUpload } from '#/components/upload'; +import { FileUpload, ImageUpload } from '#/components/upload'; const UserSelect = useApiSelect({ name: 'UserSelect', @@ -25,6 +26,7 @@ const DeptSelect = useApiSelect({ const ApiSelect = useApiSelect({ name: 'ApiSelect', }); +const ImagesUpload = useImagesUpload(); const components = [ ImageUpload, From 10514ec1b86c317a273121e4a65e7d4168b21d0d Mon Sep 17 00:00:00 2001 From: dhb52 Date: Wed, 23 Apr 2025 22:37:33 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=E6=8C=89=20codereview=20?= =?UTF-8?q?=E6=84=8F=E8=A7=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web-antd/package.json | 1 - apps/web-antd/src/bootstrap.ts | 2 +- .../src/components/FormCreate/index.ts | 3 - .../components/FormCreate/src/config/index.ts | 6 -- .../components/FormCreate/src/utils/index.ts | 65 -------------- .../components/dict-select.vue} | 13 +-- .../components/use-api-select.tsx} | 2 +- .../components/use-images-upload.tsx} | 0 .../helpers.ts} | 84 +++++++++++++++++-- .../src/components/form-create/index.ts | 3 + .../rules/data.ts} | 0 .../src/components/form-create/rules/index.ts | 6 ++ .../rules/use-dict-select.ts} | 6 +- .../rules/use-editor-rule.ts} | 2 +- .../rules/use-select-rule.ts} | 6 +- .../rules/use-upload-file-rule.ts} | 2 +- .../rules/use-upload-image-rule.ts} | 4 +- .../rules/use-upload-images-rule.ts} | 4 +- .../type/index.ts => form-create/typing.ts} | 11 ++- .../{formCreate => form-create}/index.ts | 6 +- apps/web-antd/src/views/infra/build/index.vue | 2 +- pnpm-lock.yaml | 7 +- 22 files changed, 118 insertions(+), 117 deletions(-) delete mode 100644 apps/web-antd/src/components/FormCreate/index.ts delete mode 100644 apps/web-antd/src/components/FormCreate/src/config/index.ts delete mode 100644 apps/web-antd/src/components/FormCreate/src/utils/index.ts rename apps/web-antd/src/components/{FormCreate/src/components/DictSelect.vue => form-create/components/dict-select.vue} (79%) rename apps/web-antd/src/components/{FormCreate/src/components/useApiSelect.tsx => form-create/components/use-api-select.tsx} (99%) rename apps/web-antd/src/components/{FormCreate/src/components/useImagesUpload.tsx => form-create/components/use-images-upload.tsx} (100%) rename apps/web-antd/src/components/{FormCreate/src/useFormCreateDesigner.ts => form-create/helpers.ts} (56%) create mode 100644 apps/web-antd/src/components/form-create/index.ts rename apps/web-antd/src/components/{FormCreate/src/config/selectRule.ts => form-create/rules/data.ts} (100%) create mode 100644 apps/web-antd/src/components/form-create/rules/index.ts rename apps/web-antd/src/components/{FormCreate/src/config/useDictSelectRule.ts => form-create/rules/use-dict-select.ts} (89%) rename apps/web-antd/src/components/{FormCreate/src/config/useEditorRule.ts => form-create/rules/use-editor-rule.ts} (94%) rename apps/web-antd/src/components/{FormCreate/src/config/useSelectRule.ts => form-create/rules/use-select-rule.ts} (81%) rename apps/web-antd/src/components/{FormCreate/src/config/useUploadFileRule.ts => form-create/rules/use-upload-file-rule.ts} (97%) rename apps/web-antd/src/components/{FormCreate/src/config/useUploadImgRule.ts => form-create/rules/use-upload-image-rule.ts} (96%) rename apps/web-antd/src/components/{FormCreate/src/config/useUploadImgsRule.ts => form-create/rules/use-upload-images-rule.ts} (96%) rename apps/web-antd/src/components/{FormCreate/src/type/index.ts => form-create/typing.ts} (71%) rename apps/web-antd/src/plugins/{formCreate => form-create}/index.ts (84%) diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index 1d08d20dd..43b7d8e05 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -29,7 +29,6 @@ "@form-create/ant-design-vue": "catalog:", "@form-create/antd-designer": "catalog:", "@tinymce/tinymce-vue": "catalog:", - "@types/crypto-js": "catalog:", "@types/lodash.clonedeep": "catalog:", "@vben/access": "workspace:*", "@vben/common-ui": "workspace:*", diff --git a/apps/web-antd/src/bootstrap.ts b/apps/web-antd/src/bootstrap.ts index 62779d804..761e58ed2 100644 --- a/apps/web-antd/src/bootstrap.ts +++ b/apps/web-antd/src/bootstrap.ts @@ -11,7 +11,7 @@ import '@vben/styles/antd'; import { useTitle } from '@vueuse/core'; import { $t, setupI18n } from '#/locales'; -import { setupFormCreate } from '#/plugins/formCreate'; +import { setupFormCreate } from '#/plugins/form-create'; import { initComponentAdapter } from './adapter/component'; import App from './app.vue'; diff --git a/apps/web-antd/src/components/FormCreate/index.ts b/apps/web-antd/src/components/FormCreate/index.ts deleted file mode 100644 index 84b612db6..000000000 --- a/apps/web-antd/src/components/FormCreate/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { useApiSelect } from './src/components/useApiSelect'; - -export { useFormCreateDesigner } from './src/useFormCreateDesigner'; diff --git a/apps/web-antd/src/components/FormCreate/src/config/index.ts b/apps/web-antd/src/components/FormCreate/src/config/index.ts deleted file mode 100644 index 49b9d3bda..000000000 --- a/apps/web-antd/src/components/FormCreate/src/config/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { useDictSelectRule } from './useDictSelectRule'; -export { useEditorRule } from './useEditorRule'; -export { useSelectRule } from './useSelectRule'; -export { useUploadFileRule } from './useUploadFileRule'; -export { useUploadImgRule } from './useUploadImgRule'; -export { useUploadImgsRule } from './useUploadImgsRule'; diff --git a/apps/web-antd/src/components/FormCreate/src/utils/index.ts b/apps/web-antd/src/components/FormCreate/src/utils/index.ts deleted file mode 100644 index 816a0f2a3..000000000 --- a/apps/web-antd/src/components/FormCreate/src/utils/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -export function makeRequiredRule() { - return { - type: 'Required', - field: 'formCreate$required', - title: '是否必填', - }; -} - -export const localeProps = ( - t: (msg: string) => any, - prefix: string, - rules: any[], -) => { - return rules.map((rule: { field: string; title: any }) => { - if (rule.field === 'formCreate$required') { - rule.title = t('props.required') || rule.title; - } else if (rule.field && rule.field !== '_optionType') { - rule.title = t(`components.${prefix}.${rule.field}`) || rule.title; - } - return rule; - }); -}; - -/** - * 解析表单组件的 field, title 等字段(递归,如果组件包含子组件) - * - * @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule - * @param fields 解析后表单组件字段 - * @param parentTitle 如果是子表单,子表单的标题,默认为空 - */ -export const parseFormFields = ( - rule: Record, - fields: Array> = [], - parentTitle: string = '', -) => { - const { type, field, $required, title: tempTitle, children } = rule; - if (field && tempTitle) { - let title = tempTitle; - if (parentTitle) { - title = `${parentTitle}.${tempTitle}`; - } - let required = false; - if ($required) { - required = true; - } - fields.push({ - field, - title, - type, - required, - }); - // TODO 子表单 需要处理子表单字段 - // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { - // // 解析子表单的字段 - // rule.props.rule.forEach((item) => { - // parseFields(item, fieldsPermission, title) - // }) - // } - } - if (children && Array.isArray(children)) { - children.forEach((rule) => { - parseFormFields(rule, fields); - }); - } -}; diff --git a/apps/web-antd/src/components/FormCreate/src/components/DictSelect.vue b/apps/web-antd/src/components/form-create/components/dict-select.vue similarity index 79% rename from apps/web-antd/src/components/FormCreate/src/components/DictSelect.vue rename to apps/web-antd/src/components/form-create/components/dict-select.vue index 339c4fb4b..b4c54fd0f 100644 --- a/apps/web-antd/src/components/FormCreate/src/components/DictSelect.vue +++ b/apps/web-antd/src/components/form-create/components/dict-select.vue @@ -1,5 +1,7 @@