!446 form-create: 优化 select options 解析,增加 el 表达式解析、自定义函数解析

Merge pull request !446 from puhui999/dev-crm
This commit is contained in:
芋道源码 2024-05-10 00:31:00 +00:00 committed by Gitee
commit be13eb1d0d
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 170 additions and 88 deletions

View File

@ -27,6 +27,11 @@ export const useApiSelect = (option: ApiSelectProps) => {
type: String, type: String,
default: 'GET' default: 'GET'
}, },
// 选项解析函数
parseFunc: {
type: String,
default: ''
},
// 请求参数 // 请求参数
data: { data: {
type: String, type: String,
@ -41,35 +46,121 @@ export const useApiSelect = (option: ApiSelectProps) => {
multiple: { multiple: {
type: Boolean, type: Boolean,
default: false default: false
},
// 是否远程搜索
remote: {
type: Boolean,
default: false
},
// 远程搜索时携带的参数
remoteField: {
type: String,
default: 'label'
} }
}, },
setup(props) { setup(props) {
const attrs = useAttrs() const attrs = useAttrs()
const options = ref<any[]>([]) // 下拉数据 const options = ref<any[]>([]) // 下拉数据
const loading = ref(false) // 是否正在从远程获取数据
const queryParam = ref<any>() // 当前输入的值
const getOptions = async () => { const getOptions = async () => {
options.value = [] options.value = []
// 接口选择器 // 接口选择器
if (isEmpty(props.url)) { if (isEmpty(props.url)) {
return return
} }
let data = []
switch (props.method) { switch (props.method) {
case 'GET': 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 break
case 'POST': 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 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)) { if (Array.isArray(data)) {
options.value = data.map((item: any) => ({ options.value = data.map((item: any) => ({
label: item[props.labelField], label: parseExpression(item, props.labelField),
value: item[props.valueField] value: parseExpression(item, props.valueField)
})) }))
return 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 () => { onMounted(async () => {
@ -80,15 +171,29 @@ export const useApiSelect = (option: ApiSelectProps) => {
if (props.multiple) { if (props.multiple) {
// fix多写此步是为了解决 multiple 属性问题 // fix多写此步是为了解决 multiple 属性问题
return ( 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) => ( {options.value.map((item, index) => (
<el-option key={index} label={item.label} value={item.value} /> <el-option key={index} label={item.label} value={item.value} />
))} ))}
</el-select> </el-select>
) )
} }
debugger
return ( 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) => ( {options.value.map((item, index) => (
<el-option key={index} label={item.label} value={item.value} /> <el-option key={index} label={item.label} value={item.value} />
))} ))}

View File

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

View File

@ -2,6 +2,7 @@ import { generateUUID } from '@/utils'
import * as DictDataApi from '@/api/system/dict/dict.type' import * as DictDataApi from '@/api/system/dict/dict.type'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils' import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
import { selectRule } from '@/components/FormCreate/src/config/selectRule' import { selectRule } from '@/components/FormCreate/src/config/selectRule'
import { cloneDeep } from 'lodash-es'
/** /**
* 使使 useSelectRule * 使使 useSelectRule
@ -9,6 +10,7 @@ import { selectRule } from '@/components/FormCreate/src/config/selectRule'
export const useDictSelectRule = () => { export const useDictSelectRule = () => {
const label = '字典选择器' const label = '字典选择器'
const name = 'DictSelect' const name = 'DictSelect'
const rules = cloneDeep(selectRule)
const dictOptions = ref<{ label: string; value: string }[]>([]) // 字典类型下拉数据 const dictOptions = ref<{ label: string; value: string }[]>([]) // 字典类型下拉数据
onMounted(async () => { onMounted(async () => {
const data = await DictDataApi.getSimpleDictTypeList() const data = await DictDataApi.getSimpleDictTypeList()
@ -55,7 +57,7 @@ export const useDictSelectRule = () => {
{ label: '布尔值', value: 'bool' } { label: '布尔值', value: 'bool' }
] ]
}, },
...selectRule ...rules
]) ])
} }
} }

View File

@ -2,6 +2,7 @@ import { generateUUID } from '@/utils'
import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils' import { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'
import { selectRule } from '@/components/FormCreate/src/config/selectRule' import { selectRule } from '@/components/FormCreate/src/config/selectRule'
import { SelectRuleOption } from '@/components/FormCreate/src/type' import { SelectRuleOption } from '@/components/FormCreate/src/type'
import { cloneDeep } from 'lodash-es'
/** /**
* hook * hook
@ -11,6 +12,7 @@ import { SelectRuleOption } from '@/components/FormCreate/src/type'
export const useSelectRule = (option: SelectRuleOption) => { export const useSelectRule = (option: SelectRuleOption) => {
const label = option.label const label = option.label
const name = option.name const name = option.name
const rules = cloneDeep(selectRule)
return { return {
icon: option.icon, icon: option.icon,
label, label,
@ -28,7 +30,7 @@ export const useSelectRule = (option: SelectRuleOption) => {
if (!option.props) { if (!option.props) {
option.props = [] option.props = []
} }
return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...selectRule]) return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...rules])
} }
} }
} }

View File

@ -1,4 +1,3 @@
// TODO puhui999: 借鉴一下 form-create-designer utils 方法 🤣 (导入不了只能先 copy 过来用下)
export function makeRequiredRule() { export function makeRequiredRule() {
return { return {
type: 'Required', type: 'Required',
@ -17,63 +16,3 @@ export const localeProps = (t, prefix, rules) => {
return rule 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
}
}