!446 form-create: 优化 select options 解析,增加 el 表达式解析、自定义函数解析
Merge pull request !446 from puhui999/dev-crm
This commit is contained in:
commit
be13eb1d0d
@ -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} />
|
||||||
))}
|
))}
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user