Ver Fonte

!446 form-create: 优化 select options 解析,增加 el 表达式解析、自定义函数解析
Merge pull request !446 from puhui999/dev-crm

芋道源码 há 11 meses atrás
pai
commit
be13eb1d0d

+ 113 - 8
src/components/FormCreate/src/components/useApiSelect.tsx

@@ -27,6 +27,11 @@ export const useApiSelect = (option: ApiSelectProps) => {
         type: String,
         default: 'GET'
       },
+      // 选项解析函数
+      parseFunc: {
+        type: String,
+        default: ''
+      },
       // 请求参数
       data: {
         type: String,
@@ -41,35 +46,121 @@ export const useApiSelect = (option: ApiSelectProps) => {
       multiple: {
         type: Boolean,
         default: false
+      },
+      // 是否远程搜索
+      remote: {
+        type: Boolean,
+        default: false
+      },
+      // 远程搜索时携带的参数
+      remoteField: {
+        type: String,
+        default: 'label'
       }
     },
     setup(props) {
       const attrs = useAttrs()
       const options = ref<any[]>([]) // 下拉数据
+      const loading = ref(false) // 是否正在从远程获取数据
+      const queryParam = ref<any>() // 当前输入的值
       const getOptions = async () => {
         options.value = []
         // 接口选择器
         if (isEmpty(props.url)) {
           return
         }
-        let data = []
         switch (props.method) {
           case 'GET':
-            data = await request.get({ url: props.url })
+            let url: string = props.url
+            if (props.remote) {
+              url = `${url}?${props.remoteField}=${queryParam.value}`
+            }
+            parseOptions(await request.get({ url: url }))
             break
           case 'POST':
-            data = await request.post({ url: props.url, data: jsonParse(props.data) })
+            const data: any = jsonParse(props.data)
+            if (props.remote) {
+              data[props.remoteField] = queryParam.value
+            }
+            parseOptions(await request.post({ url: props.url, data: 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: item[props.labelField],
-            value: item[props.valueField]
+            label: parseExpression(item, props.labelField),
+            value: parseExpression(item, props.valueField)
           }))
           return
         }
-        console.error(`接口[${props.url}] 返回结果不是一个数组`)
+        console.warn(`接口[${props.url}] 返回结果不是一个数组`)
+      }
+
+      function parseFunc() {
+        let parse: any = null
+        if (!!props.parseFunc) {
+          // 解析字符串函数
+          parse = new Function(`return ${props.parseFunc}`)()
+        }
+        return parse
+      }
+
+      function parseExpression(data: any, template: string) {
+        // 检测是否使用了表达式
+        if (template.indexOf('${') === -1) {
+          return data[template]
+        }
+        // 正则表达式匹配模板字符串中的 ${...}
+        const pattern = /\$\{([^}]*)}/g
+        // 使用replace函数配合正则表达式和回调函数来进行替换
+        return template.replace(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 () => {
@@ -80,15 +171,29 @@ export const useApiSelect = (option: ApiSelectProps) => {
         if (props.multiple) {
           // fix:多写此步是为了解决 multiple 属性问题
           return (
-            <el-select class="w-1/1" {...attrs} multiple>
+            <el-select
+              class="w-1/1"
+              multiple
+              loading={loading.value}
+              {...attrs}
+              remote={props.remote}
+              {...(props.remote && { remoteMethod: remoteMethod })}
+            >
               {options.value.map((item, index) => (
                 <el-option key={index} label={item.label} value={item.value} />
               ))}
             </el-select>
           )
         }
+        debugger
         return (
-          <el-select class="w-1/1" {...attrs}>
+          <el-select
+            class="w-1/1"
+            loading={loading.value}
+            {...attrs}
+            remote={props.remote}
+            {...(props.remote && { remoteMethod: remoteMethod })}
+          >
             {options.value.map((item, index) => (
               <el-option key={index} label={item.label} value={item.value} />
             ))}

+ 51 - 17
src/components/FormCreate/src/config/selectRule.ts

@@ -13,12 +13,30 @@ const selectRule = [
     control: [
       {
         value: 'select',
-        condition: '=',
+        condition: '==',
         method: 'hidden',
-        rule: ['multiple']
+        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',
@@ -43,27 +61,12 @@ const selectRule = [
     title: 'autocomplete 属性'
   },
   { type: 'input', field: 'placeholder', title: '占位符' },
-  {
-    type: 'switch',
-    field: 'filterable',
-    title: '是否可搜索'
-  },
   { type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' },
   {
     type: 'input',
     field: 'noMatchText',
     title: '搜索条件无匹配时显示的文字'
   },
-  {
-    type: 'switch',
-    field: 'remote',
-    title: '其中的选项是否从服务器远程加载'
-  },
-  {
-    type: 'Struct',
-    field: 'remoteMethod',
-    title: '自定义远程搜索方法'
-  },
   { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' },
   {
     type: 'switch',
@@ -130,6 +133,7 @@ const apiSelectRule = [
     type: 'input',
     field: 'labelField',
     title: 'label 属性',
+    info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}',
     props: {
       placeholder: 'nickname'
     }
@@ -138,9 +142,39 @@ const apiSelectRule = [
     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'
   }
 ]
 

+ 3 - 1
src/components/FormCreate/src/config/useDictSelectRule.ts

@@ -2,6 +2,7 @@ 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 { cloneDeep } from 'lodash-es'
 
 /**
  * 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule
@@ -9,6 +10,7 @@ import { selectRule } from '@/components/FormCreate/src/config/selectRule'
 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()
@@ -55,7 +57,7 @@ export const useDictSelectRule = () => {
             { label: '布尔值', value: 'bool' }
           ]
         },
-        ...selectRule
+        ...rules
       ])
     }
   }

+ 3 - 1
src/components/FormCreate/src/config/useSelectRule.ts

@@ -2,6 +2,7 @@ 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'
+import { cloneDeep } from 'lodash-es'
 
 /**
  * 通用选择器规则 hook
@@ -11,6 +12,7 @@ import { SelectRuleOption } from '@/components/FormCreate/src/type'
 export const useSelectRule = (option: SelectRuleOption) => {
   const label = option.label
   const name = option.name
+  const rules = cloneDeep(selectRule)
   return {
     icon: option.icon,
     label,
@@ -28,7 +30,7 @@ export const useSelectRule = (option: SelectRuleOption) => {
       if (!option.props) {
         option.props = []
       }
-      return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...selectRule])
+      return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...rules])
     }
   }
 }

+ 0 - 61
src/components/FormCreate/src/utils/index.ts

@@ -1,4 +1,3 @@
-// TODO puhui999: 借鉴一下 form-create-designer utils 方法 🤣 (导入不了只能先 copy 过来用下)
 export function makeRequiredRule() {
   return {
     type: 'Required',
@@ -17,63 +16,3 @@ export const localeProps = (t, prefix, rules) => {
     return rule
   })
 }
-
-export function upper(str) {
-  return str.replace(str[0], str[0].toLocaleUpperCase())
-}
-
-export function makeOptionsRule(t, to, userOptions) {
-  console.log(userOptions[0])
-  const options = [
-    { label: t('props.optionsType.struct'), value: 0 },
-    { label: t('props.optionsType.json'), value: 1 },
-    { label: '用户数据', value: 2 }
-  ]
-
-  const control = [
-    {
-      value: 0,
-      rule: [
-        {
-          type: 'TableOptions',
-          field: 'formCreate' + upper(to).replace('.', '>'),
-          props: { defaultValue: [] }
-        }
-      ]
-    },
-    {
-      value: 1,
-      rule: [
-        {
-          type: 'Struct',
-          field: 'formCreate' + upper(to).replace('.', '>'),
-          props: { defaultValue: [] }
-        }
-      ]
-    },
-    {
-      value: 2,
-      rule: [
-        {
-          type: 'TableOptions',
-          field: 'formCreate' + upper(to).replace('.', '>'),
-          props: { modelValue: [] }
-        }
-      ]
-    }
-  ]
-  options.splice(0, 0)
-  control.push()
-
-  return {
-    type: 'radio',
-    title: t('props.options'),
-    field: '_optionType',
-    value: 0,
-    options,
-    props: {
-      type: 'button'
-    },
-    control
-  }
-}