From 40fe87920b075e91ff04e1215e320974b7d25106 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 9 May 2024 14:35:57 +0800 Subject: [PATCH 1/3] =?UTF-8?q?form-create:=20=E4=BC=98=E5=8C=96=20select?= =?UTF-8?q?=20option=20=E9=80=89=E9=A1=B9=E8=A7=A3=E6=9E=90=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20el=20=E8=A1=A8=E8=BE=BE=E5=BC=8F=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E3=80=81=E8=87=AA=E5=AE=9A=E4=B9=89=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=20(data:=20any)=3D>{=20label:=20string;=20va?= =?UTF-8?q?lue:=20any=20}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/useApiSelect.tsx | 41 +++++++++++++++++-- .../FormCreate/src/config/selectRule.ts | 13 +++++- src/components/FormCreate/src/utils/index.ts | 1 - 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/components/FormCreate/src/components/useApiSelect.tsx b/src/components/FormCreate/src/components/useApiSelect.tsx index 54c0a33b..62f25eb3 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, @@ -63,15 +68,43 @@ export const useApiSelect = (option: ApiSelectProps) => { } if (Array.isArray(data)) { - options.value = data.map((item: any) => ({ - label: item[props.labelField], - value: item[props.valueField] - })) + let parse: any = null + if (!!props.parseFunc) { + // 解析字符串函数 + parse = new Function(`return ${props.parseFunc}`)() + } + options.value = data.map( + (item: any) => + parse?.(item) ?? { + label: parseExpression(item, props.labelField), + value: parseExpression(item, props.valueField) + } + ) return } console.log(`接口[${props.url}] 返回结果不是一个数组`) } + 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 + }) + } + onMounted(async () => { await getOptions() }) diff --git a/src/components/FormCreate/src/config/selectRule.ts b/src/components/FormCreate/src/config/selectRule.ts index 281d3739..ec5af7c7 100644 --- a/src/components/FormCreate/src/config/selectRule.ts +++ b/src/components/FormCreate/src/config/selectRule.ts @@ -13,7 +13,7 @@ const selectRule = [ control: [ { value: 'select', - condition: '=', + condition: '==', method: 'hidden', rule: ['multiple'] } @@ -141,6 +141,17 @@ const apiSelectRule = [ props: { placeholder: 'id' } + }, + { + type: 'input', + field: 'parseFunc', + title: '选项解析函数', + info: '(data: any)=>{ label: string; value: any }', + props: { + autosize: true, + rows: { minRows: 2, maxRows: 6 }, + type: 'textarea' + } } ] diff --git a/src/components/FormCreate/src/utils/index.ts b/src/components/FormCreate/src/utils/index.ts index e5480981..c297e841 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', From 9cde4885777576a145c06253ff634cef63c1fdd3 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 9 May 2024 17:30:49 +0800 Subject: [PATCH 2/3] =?UTF-8?q?form-create:=20=E5=AE=8C=E5=96=84=20select?= =?UTF-8?q?=20=E8=BF=9C=E7=A8=8B=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/useApiSelect.tsx | 110 +++++++++++++++--- .../FormCreate/src/config/selectRule.ts | 34 +++++- .../src/config/useDictSelectRule.ts | 4 +- .../FormCreate/src/config/useSelectRule.ts | 4 +- src/components/FormCreate/src/utils/index.ts | 60 ---------- 5 files changed, 125 insertions(+), 87 deletions(-) diff --git a/src/components/FormCreate/src/components/useApiSelect.tsx b/src/components/FormCreate/src/components/useApiSelect.tsx index 62f25eb3..29cd3027 100644 --- a/src/components/FormCreate/src/components/useApiSelect.tsx +++ b/src/components/FormCreate/src/components/useApiSelect.tsx @@ -46,43 +46,88 @@ 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 } + } - if (Array.isArray(data)) { - let parse: any = null - if (!!props.parseFunc) { - // 解析字符串函数 - parse = new Function(`return ${props.parseFunc}`)() - } - options.value = data.map( - (item: any) => - parse?.(item) ?? { - label: parseExpression(item, props.labelField), - value: parseExpression(item, props.valueField) - } - ) + function parseOptions(data: any) { + // 情况一:如果有自定义解析函数优先使用自定义解析 + if (!isEmpty(props.parseFunc)) { + options.value = parseFunc()?.(data) return } - console.log(`接口[${props.url}] 返回结果不是一个数组`) + // 情况二:返回的直接是一个列表 + 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) { + // 解析字符串函数 + parse = new Function(`return ${props.parseFunc}`)() + } + return parse } function parseExpression(data: any, template: string) { @@ -105,6 +150,19 @@ export const useApiSelect = (option: ApiSelectProps) => { }) } + 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() }) @@ -113,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 ec5af7c7..5d24c713 100644 --- a/src/components/FormCreate/src/config/selectRule.ts +++ b/src/components/FormCreate/src/config/selectRule.ts @@ -15,7 +15,20 @@ const selectRule = [ value: 'select', condition: '==', method: 'hidden', - rule: ['multiple'] + rule: [ + 'multiple', + 'clearable', + 'collapseTags', + 'multipleLimit', + 'allowCreate', + 'filterable', + 'noMatchText', + 'remote', + 'remoteMethod', + 'reserveKeyword', + 'defaultFirstOption', + 'automaticDropdown' + ] } ] }, @@ -60,9 +73,10 @@ const selectRule = [ title: '其中的选项是否从服务器远程加载' }, { - type: 'Struct', - field: 'remoteMethod', - title: '自定义远程搜索方法' + type: 'input', + field: 'remoteField', + title: '请求参数', + info: '远程请求时请求携带的参数名称,如:name' }, { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' }, { @@ -130,6 +144,7 @@ const apiSelectRule = [ type: 'input', field: 'labelField', title: 'label 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', props: { placeholder: 'nickname' } @@ -138,6 +153,7 @@ const apiSelectRule = [ type: 'input', field: 'valueField', title: 'value 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', props: { placeholder: 'id' } @@ -146,11 +162,17 @@ const apiSelectRule = [ type: 'input', field: 'parseFunc', title: '选项解析函数', - info: '(data: any)=>{ label: string; value: any }', + info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表 + (data: any)=>{ label: string; value: any }[]`, props: { autosize: true, rows: { minRows: 2, maxRows: 6 }, - type: 'textarea' + type: 'textarea', + placeholder: ` + function (data) { + console.log(data) + return data.list.map(item=> ({label: item.nickname,value: item.id})) + }` } } ] 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 732c5269..3e5f1eb2 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 @@ -10,6 +11,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, @@ -27,7 +29,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 c297e841..2d4a6fd7 100644 --- a/src/components/FormCreate/src/utils/index.ts +++ b/src/components/FormCreate/src/utils/index.ts @@ -16,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 - } -} From 24f2c49e0b04c4e73239ffc11524ed34462b7425 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 9 May 2024 18:02:57 +0800 Subject: [PATCH 3/3] =?UTF-8?q?form-create:=20=E5=AE=8C=E5=96=84=20select?= =?UTF-8?q?=20=E8=BF=9C=E7=A8=8B=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FormCreate/src/config/selectRule.ts | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/components/FormCreate/src/config/selectRule.ts b/src/components/FormCreate/src/config/selectRule.ts index 5d24c713..a6f3841f 100644 --- a/src/components/FormCreate/src/config/selectRule.ts +++ b/src/components/FormCreate/src/config/selectRule.ts @@ -32,6 +32,11 @@ const selectRule = [ } ] }, + { + type: 'switch', + field: 'filterable', + title: '是否可搜索' + }, { type: 'switch', field: 'multiple', title: '是否多选' }, { type: 'switch', @@ -56,28 +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: 'input', - field: 'remoteField', - title: '请求参数', - info: '远程请求时请求携带的参数名称,如:name' - }, { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' }, { type: 'switch', @@ -174,6 +163,18 @@ const apiSelectRule = [ return data.list.map(item=> ({label: item.nickname,value: item.id})) }` } + }, + { + type: 'switch', + field: 'remote', + info: '是否可搜索', + title: '其中的选项是否从服务器远程加载' + }, + { + type: 'input', + field: 'remoteField', + title: '请求参数', + info: '远程请求时请求携带的参数名称,如:name' } ]