diff --git a/src/components/FormCreate/src/components/useApiSelect.tsx b/src/components/FormCreate/src/components/useApiSelect.tsx index a67e6f68..29cd3027 100644 --- a/src/components/FormCreate/src/components/useApiSelect.tsx +++ b/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([]) // 下拉数据 + const loading = ref(false) // 是否正在从远程获取数据 + const queryParam = 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 }) + 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 ( - + {options.value.map((item, index) => ( ))} ) } + debugger return ( - + {options.value.map((item, index) => ( ))} diff --git a/src/components/FormCreate/src/config/selectRule.ts b/src/components/FormCreate/src/config/selectRule.ts index 281d3739..a6f3841f 100644 --- a/src/components/FormCreate/src/config/selectRule.ts +++ b/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' } ] diff --git a/src/components/FormCreate/src/config/useDictSelectRule.ts b/src/components/FormCreate/src/config/useDictSelectRule.ts index 3db630bc..5c5e8cad 100644 --- a/src/components/FormCreate/src/config/useDictSelectRule.ts +++ b/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 ]) } } diff --git a/src/components/FormCreate/src/config/useSelectRule.ts b/src/components/FormCreate/src/config/useSelectRule.ts index bf20588d..ff21a223 100644 --- a/src/components/FormCreate/src/config/useSelectRule.ts +++ b/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]) } } } diff --git a/src/components/FormCreate/src/utils/index.ts b/src/components/FormCreate/src/utils/index.ts index e5480981..2d4a6fd7 100644 --- a/src/components/FormCreate/src/utils/index.ts +++ b/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 - } -}