Files
yudao-ui-admin-vben/apps/web-antd/src/components/form-create/components/use-api-select.tsx
2025-10-17 16:15:54 +08:00

321 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ApiSelectProps } from '#/components/form-create/typing';
import { defineComponent, onMounted, ref, useAttrs } from 'vue';
import { isEmpty } from '@vben/utils';
import {
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Select,
SelectOption,
} from 'ant-design-vue';
import { requestClient } from '#/api/request';
export function useApiSelect(option: ApiSelectProps) {
return defineComponent({
name: option.name,
props: {
// 选项标签
labelField: {
type: String,
default: () => option.labelField ?? 'label',
},
// 选项的值
valueField: {
type: String,
default: () => option.valueField ?? 'value',
},
// api 接口
url: {
type: String,
default: () => option.url ?? '',
},
// 请求类型
method: {
type: String,
default: 'GET',
},
// 选项解析函数
parseFunc: {
type: String,
default: '',
},
// 请求参数
data: {
type: String,
default: '',
},
// 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
selectType: {
type: String,
default: 'select',
},
// 是否多选
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;
}
switch (props.method) {
case 'GET': {
let url: string = props.url;
if (props.remote && queryParam.value !== undefined) {
url = url.includes('?')
? `${url}&${props.remoteField}=${queryParam.value}`
: `${url}?${props.remoteField}=${queryParam.value}`;
}
parseOptions(await requestClient.get(url));
break;
}
case 'POST': {
const data: any = JSON.parse(props.data);
if (props.remote) {
data[props.remoteField] = queryParam.value;
}
parseOptions(await requestClient.post(props.url, 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: parseExpression(item, props.labelField),
value: parseExpression(item, props.valueField),
}));
return;
}
console.warn(`接口[${props.url}] 返回结果不是一个数组`);
}
function parseFunc() {
let parse: any = null;
if (props.parseFunc) {
// 解析字符串函数
// eslint-disable-next-line no-new-func
parse = new Function(`return ${props.parseFunc}`)();
}
return parse;
}
function parseExpression(data: any, template: string) {
// 检测是否使用了表达式
if (!template.includes('${')) {
return data[template];
}
// 正则表达式匹配模板字符串中的 ${...}
const pattern = /\$\{([^}]*)\}/g;
// 使用replace函数配合正则表达式和回调函数来进行替换
return template.replaceAll(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 () => {
await getOptions();
});
const buildSelect = () => {
const {
modelValue,
'onUpdate:modelValue': onUpdateModelValue,
...restAttrs
} = attrs;
if (props.multiple) {
// fix多写此步是为了解决 multiple 属性问题
return (
<Select
class="w-full"
loading={loading.value}
mode="multiple"
onUpdate:value={onUpdateModelValue as any}
value={modelValue as any}
{...restAttrs}
// TODO: remote 对等实现
// remote={props.remote}
{...(props.remote && { remoteMethod })}
>
{options.value.map(
(item: { label: any; value: any }, index: any) => (
<SelectOption key={index} value={item.value}>
{item.label}
</SelectOption>
),
)}
</Select>
);
}
return (
<Select
class="w-full"
loading={loading.value}
onUpdate:value={onUpdateModelValue as any}
value={modelValue as any}
{...restAttrs}
// TODO: @dhb52 remote 对等实现, 还是说没作用
// remote={props.remote}
{...(props.remote && { remoteMethod })}
>
{options.value.map(
(item: { label: any; value: any }, index: any) => (
<SelectOption key={index} value={item.value}>
{item.label}
</SelectOption>
),
)}
</Select>
);
};
const buildCheckbox = () => {
const {
modelValue,
'onUpdate:modelValue': onUpdateModelValue,
...restAttrs
} = attrs;
if (isEmpty(options.value)) {
options.value = [
{ label: '选项1', value: '选项1' },
{ label: '选项2', value: '选项2' },
];
}
return (
<CheckboxGroup
class="w-full"
onUpdate:value={onUpdateModelValue as any}
value={modelValue as any}
{...restAttrs}
>
{options.value.map(
(item: { label: any; value: any }, index: any) => (
<Checkbox key={index} value={item.value}>
{item.label}
</Checkbox>
),
)}
</CheckboxGroup>
);
};
const buildRadio = () => {
const {
modelValue,
'onUpdate:modelValue': onUpdateModelValue,
...restAttrs
} = attrs;
if (isEmpty(options.value)) {
options.value = [
{ label: '选项1', value: '选项1' },
{ label: '选项2', value: '选项2' },
];
}
return (
<RadioGroup
class="w-full"
onUpdate:value={onUpdateModelValue as any}
value={modelValue as any}
{...restAttrs}
>
{options.value.map(
(item: { label: any; value: any }, index: any) => (
<Radio key={index} value={item.value}>
{item.label}
</Radio>
),
)}
</RadioGroup>
);
};
return () => (
<>
{(() => {
switch (props.selectType) {
case 'checkbox': {
return buildCheckbox();
}
case 'radio': {
return buildRadio();
}
case 'select': {
return buildSelect();
}
default: {
return buildSelect();
}
}
})()}
</>
);
},
});
}