diff --git a/src/components/DictSelect/index.ts b/src/components/DictSelect/index.ts deleted file mode 100644 index 164035fd..00000000 --- a/src/components/DictSelect/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import DictSelect from './src/DictSelect.vue' - -export { DictSelect } diff --git a/src/components/DictSelect/src/DictSelect.vue b/src/components/DictSelect/src/DictSelect.vue deleted file mode 100644 index 2d59e23c..00000000 --- a/src/components/DictSelect/src/DictSelect.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - diff --git a/src/components/FormCreate/index.ts b/src/components/FormCreate/index.ts index d50ed3c6..9d32778b 100644 --- a/src/components/FormCreate/index.ts +++ b/src/components/FormCreate/index.ts @@ -1,3 +1,4 @@ import { useFormCreateDesigner } from './src/useFormCreateDesigner' +import { useApiSelect } from './src/components/useApiSelect' -export { useFormCreateDesigner } +export { useFormCreateDesigner, useApiSelect } diff --git a/src/components/FormCreate/src/components/DictSelect.vue b/src/components/FormCreate/src/components/DictSelect.vue new file mode 100644 index 00000000..204746d1 --- /dev/null +++ b/src/components/FormCreate/src/components/DictSelect.vue @@ -0,0 +1,59 @@ + + + + diff --git a/src/components/FormCreate/src/components/useApiSelect.tsx b/src/components/FormCreate/src/components/useApiSelect.tsx new file mode 100644 index 00000000..54c0a33b --- /dev/null +++ b/src/components/FormCreate/src/components/useApiSelect.tsx @@ -0,0 +1,143 @@ +import request from '@/config/axios' +import { isEmpty } from '@/utils/is' +import { ApiSelectProps } from '@/components/FormCreate/src/type' +import { jsonParse } from '@/utils' + +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' + }, + // 请求参数 + data: { + type: String, + default: '' + }, + // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio + selectType: { + type: String, + default: 'select' + }, + // 是否多选 + multiple: { + type: Boolean, + default: false + } + }, + setup(props) { + const attrs = useAttrs() + const options = ref([]) // 下拉数据 + const getOptions = async () => { + options.value = [] + // 接口选择器 + if (isEmpty(props.url)) { + return + } + let data = [] + switch (props.method) { + case 'GET': + data = await request.get({ url: props.url }) + break + case 'POST': + data = await request.post({ url: props.url, data: jsonParse(props.data) }) + break + } + + if (Array.isArray(data)) { + options.value = data.map((item: any) => ({ + label: item[props.labelField], + value: item[props.valueField] + })) + return + } + console.log(`接口[${props.url}] 返回结果不是一个数组`) + } + + onMounted(async () => { + await getOptions() + }) + + const buildSelect = () => { + if (props.multiple) { + // fix:多写此步是为了解决 multiple 属性问题 + return ( + + {options.value.map((item, index) => ( + + ))} + + ) + } + return ( + + {options.value.map((item, index) => ( + + ))} + + ) + } + const buildCheckbox = () => { + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' } + ] + } + return ( + + {options.value.map((item, index) => ( + + ))} + + ) + } + const buildRadio = () => { + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' } + ] + } + return ( + + {options.value.map((item, index) => ( + + {item.label} + + ))} + + ) + } + return () => ( + <> + {props.selectType === 'select' + ? buildSelect() + : props.selectType === 'radio' + ? buildRadio() + : props.selectType === 'checkbox' + ? buildCheckbox() + : buildSelect()} + + ) + } + }) +} diff --git a/src/components/FormCreate/src/config/index.ts b/src/components/FormCreate/src/config/index.ts index c3939159..b1e2ddea 100644 --- a/src/components/FormCreate/src/config/index.ts +++ b/src/components/FormCreate/src/config/index.ts @@ -2,14 +2,14 @@ import { useUploadFileRule } from './useUploadFileRule' import { useUploadImgRule } from './useUploadImgRule' import { useUploadImgsRule } from './useUploadImgsRule' import { useDictSelectRule } from './useDictSelectRule' -import { useUserSelectRule } from './useUserSelectRule' import { useEditorRule } from './useEditorRule' +import { useSelectRule } from './useSelectRule' export { useUploadFileRule, useUploadImgRule, useUploadImgsRule, useDictSelectRule, - useUserSelectRule, - useEditorRule + useEditorRule, + useSelectRule } diff --git a/src/components/FormCreate/src/config/selectRule.ts b/src/components/FormCreate/src/config/selectRule.ts index 0974139e..281d3739 100644 --- a/src/components/FormCreate/src/config/selectRule.ts +++ b/src/components/FormCreate/src/config/selectRule.ts @@ -1,4 +1,24 @@ 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'] + } + ] + }, { type: 'switch', field: 'multiple', title: '是否多选' }, { type: 'switch', @@ -68,4 +88,60 @@ const selectRule = [ } ] -export default selectRule +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 属性', + props: { + placeholder: 'nickname' + } + }, + { + type: 'input', + field: 'valueField', + title: 'value 属性', + props: { + placeholder: 'id' + } + } +] + +export { selectRule, apiSelectRule } diff --git a/src/components/FormCreate/src/config/useDictSelectRule.ts b/src/components/FormCreate/src/config/useDictSelectRule.ts index 7a1afc60..3db630bc 100644 --- a/src/components/FormCreate/src/config/useDictSelectRule.ts +++ b/src/components/FormCreate/src/config/useDictSelectRule.ts @@ -1,8 +1,11 @@ import { generateUUID } from '@/utils' import * as DictDataApi from '@/api/system/dict/dict.type' import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils' -import selectRule from '@/components/FormCreate/src/config/selectRule' +import { selectRule } from '@/components/FormCreate/src/config/selectRule' +/** + * 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule + */ export const useDictSelectRule = () => { const label = '字典选择器' const name = 'DictSelect' @@ -19,7 +22,7 @@ export const useDictSelectRule = () => { })) ?? [] }) return { - icon: 'icon-select', + icon: 'icon-doc-text', label, name, rule() { @@ -43,7 +46,7 @@ export const useDictSelectRule = () => { }, { type: 'select', - field: 'valueType', + field: 'dictValueType', title: '字典值类型', value: 'str', options: [ diff --git a/src/components/FormCreate/src/config/useSelectRule.ts b/src/components/FormCreate/src/config/useSelectRule.ts new file mode 100644 index 00000000..732c5269 --- /dev/null +++ b/src/components/FormCreate/src/config/useSelectRule.ts @@ -0,0 +1,33 @@ +import { generateUUID } from '@/utils' +import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils' +import { selectRule } from '@/components/FormCreate/src/config/selectRule' +import { SelectRuleOption } from '@/components/FormCreate/src/type' + +/** + * 通用选择器规则 hook + * @param option 规则配置 + */ +export const useSelectRule = (option: SelectRuleOption) => { + const label = option.label + const name = option.name + return { + icon: option.icon, + label, + name, + rule() { + return { + type: name, + field: generateUUID(), + title: label, + info: '', + $required: false + } + }, + props(_, { t }) { + if (!option.props) { + option.props = [] + } + return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...selectRule]) + } + } +} diff --git a/src/components/FormCreate/src/config/useUserSelectRule.ts b/src/components/FormCreate/src/config/useUserSelectRule.ts deleted file mode 100644 index dd5e51c3..00000000 --- a/src/components/FormCreate/src/config/useUserSelectRule.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { generateUUID } from '@/utils' -import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils' -import selectRule from '@/components/FormCreate/src/config/selectRule' - -export const useUserSelectRule = () => { - const label = '用户选择器' - const name = 'UserSelect' - return { - icon: 'icon-select', - label, - name, - rule() { - return { - type: name, - field: generateUUID(), - title: label, - info: '', - $required: false - } - }, - props(_, { t }) { - return localeProps(t, name + '.props', [makeRequiredRule(), ...selectRule]) - } - } -} diff --git a/src/components/FormCreate/src/type/index.ts b/src/components/FormCreate/src/type/index.ts new file mode 100644 index 00000000..42dccc76 --- /dev/null +++ b/src/components/FormCreate/src/type/index.ts @@ -0,0 +1,50 @@ +import { Rule } from '@form-create/element-ui' //左侧拖拽按钮 + +// 左侧拖拽按钮 +export interface MenuItem { + label: string + name: string + icon: string +} + +// 左侧拖拽按钮分类 +export interface Menu { + title: string + name: string + list: MenuItem[] +} + +export interface MenuList extends Array {} + +// 拖拽组件的规则 +export interface DragRule { + icon: string + name: string + label: string + children?: string + inside?: true + drag?: true | String + 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[] // 组件规则 +} diff --git a/src/components/FormCreate/src/useFormCreateDesigner.ts b/src/components/FormCreate/src/useFormCreateDesigner.ts index fe42b24f..69c8f314 100644 --- a/src/components/FormCreate/src/useFormCreateDesigner.ts +++ b/src/components/FormCreate/src/useFormCreateDesigner.ts @@ -1,12 +1,14 @@ import { useDictSelectRule, useEditorRule, + useSelectRule, useUploadFileRule, useUploadImgRule, - useUploadImgsRule, - useUserSelectRule + useUploadImgsRule } from './config' import { Ref } from 'vue' +import { Menu } from '@/components/FormCreate/src/type' +import { apiSelectRule } from '@/components/FormCreate/src/config/selectRule' /** * 表单设计器增强 hook @@ -15,30 +17,25 @@ import { Ref } from 'vue' * - 单图上传 * - 多图上传 * - 字典选择器 - * - 系统用户选择器 + * - 用户选择器 + * - 部门选择器 * - 富文本 */ -export const useFormCreateDesigner = (designer: Ref) => { +export const useFormCreateDesigner = async (designer: Ref) => { const editorRule = useEditorRule() const uploadFileRule = useUploadFileRule() const uploadImgRule = useUploadImgRule() const uploadImgsRule = useUploadImgsRule() - const dictSelectRule = useDictSelectRule() - const userSelectRule = useUserSelectRule() - onMounted(() => { + /** + * 构建表单组件 + */ + const buildFormComponents = () => { // 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代 designer.value?.removeMenuItem('upload') // 移除自带的富文本组件规则,使用 editorRule 替代 designer.value?.removeMenuItem('fc-editor') - const components = [ - editorRule, - uploadFileRule, - uploadImgRule, - uploadImgsRule, - dictSelectRule, - userSelectRule - ] + const components = [editorRule, uploadFileRule, uploadImgRule, uploadImgsRule] components.forEach((component) => { // 插入组件规则 designer.value?.addComponent(component) @@ -49,5 +46,55 @@ export const useFormCreateDesigner = (designer: Ref) => { label: component.label }) }) + } + + const userSelectRule = useSelectRule({ + name: 'UserSelect', + label: '用户选择器', + icon: 'icon-user-o' + }) + const deptSelectRule = useSelectRule({ + name: 'DeptSelect', + label: '部门选择器', + icon: 'icon-address-card-o' + }) + const dictSelectRule = useDictSelectRule() + const apiSelectRule0 = useSelectRule({ + name: 'ApiSelect', + label: '接口选择器', + icon: 'icon-server', + props: [...apiSelectRule] + }) + + /** + * 构建系统字段菜单 + */ + 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/src/plugins/formCreate/index.ts b/src/plugins/formCreate/index.ts index 5f8428e6..adc12136 100644 --- a/src/plugins/formCreate/index.ts +++ b/src/plugins/formCreate/index.ts @@ -19,9 +19,25 @@ import formCreate from '@form-create/element-ui' import install from '@form-create/element-ui/auto-import' //======================= 自定义组件 ======================= import { UploadFile, UploadImg, UploadImgs } from '@/components/UploadFile' -import { DictSelect } from '@/components/DictSelect' -import UserSelect from '@/views/system/user/components/UserSelect.vue' +import { useApiSelect } from '@/components/FormCreate' import { Editor } from '@/components/Editor' +import DictSelect from '@/components/FormCreate/src/components/DictSelect.vue' + +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 = [ ElAside, @@ -41,6 +57,8 @@ const components = [ UploadFile, DictSelect, UserSelect, + DeptSelect, + ApiSelect, Editor ] diff --git a/src/styles/FormCreate/fonts/fontello.woff b/src/styles/FormCreate/fonts/fontello.woff new file mode 100644 index 00000000..1e00f499 Binary files /dev/null and b/src/styles/FormCreate/fonts/fontello.woff differ diff --git a/src/styles/FormCreate/index.scss b/src/styles/FormCreate/index.scss new file mode 100644 index 00000000..bb620005 --- /dev/null +++ b/src/styles/FormCreate/index.scss @@ -0,0 +1,22 @@ +// 使用字体图标来源 https://fontello.com/ + +@font-face { + font-family: 'fc-icon'; + src: url('@/styles/FormCreate/fonts/fontello.woff') format('woff'); +} + +.icon-doc-text:before { + content: '\f0f6'; +} + +.icon-server:before { + content: '\f233'; +} + +.icon-address-card-o:before { + content: '\f2bc'; +} + +.icon-user-o:before { + content: '\f2c0'; +} diff --git a/src/styles/index.scss b/src/styles/index.scss index 0952bd07..fbe76f23 100644 --- a/src/styles/index.scss +++ b/src/styles/index.scss @@ -1,4 +1,5 @@ @import './var.css'; +@import './FormCreate/index.scss'; @import 'element-plus/theme-chalk/dark/css-vars.css'; .reset-margin [class*='el-icon'] + span { diff --git a/src/utils/index.ts b/src/utils/index.ts index 274ab7ca..5b29e41a 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -435,3 +435,17 @@ export const areaReplace = (areaName: string) => { .replace('自治区', '') .replace('省', '') } + +/** + * 解析 JSON 字符串 + * + * @param str + */ +export function jsonParse(str: string) { + try { + return JSON.parse(str) + } catch (e) { + console.log(`str[${str}] 不是一个 JSON 字符串`) + return '' + } +} diff --git a/src/views/infra/build/index.vue b/src/views/infra/build/index.vue index 9cee56f0..571acffe 100644 --- a/src/views/infra/build/index.vue +++ b/src/views/infra/build/index.vue @@ -8,11 +8,9 @@ 生成组件 - - - - + + diff --git a/src/views/system/user/components/UserSelect.vue b/src/views/system/user/components/UserSelect.vue deleted file mode 100644 index 4a94a745..00000000 --- a/src/views/system/user/components/UserSelect.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - -