feat(RemoteSelect): 新增远程搜索选择组件

This commit is contained in:
john 2026-08-16 09:01:34 +08:00
parent 0870ce1751
commit e9653ebcbc

View File

@ -0,0 +1,147 @@
<template>
<el-select
v-model="selectedValue"
:placeholder="placeholder"
:clearable="clearable"
filterable
:remote="isRemote"
:remote-method="remoteMethod"
:loading="loading"
style="width: 100%"
@change="handleChange"
>
<el-option v-for="item in options" :key="item[valueKey]" :label="item[labelKey]" :value="item[valueKey]" />
</el-select>
</template>
<script setup lang="ts">
import request from '@/utils/request';
interface Props {
/** 列表数据请求地址 */
url: string;
/** 选项显示文字的字段名 */
labelKey?: string;
/** 选项值的字段名 */
valueKey?: string;
/** 远程搜索时的查询参数字段名 */
searchKey?: string;
/** 额外的固定请求参数 */
params?: Record<string, any>;
/** 绑定值 */
modelValue?: string | number;
/** 占位提示文字 */
placeholder?: string;
/** 是否可清空 */
clearable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
labelKey: 'label',
valueKey: 'value',
searchKey: 'keyword',
params: () => ({}),
placeholder: '请选择',
clearable: true
});
const emit = defineEmits(['update:modelValue', 'change']);
const selectedValue = ref<string | number | undefined>(props.modelValue);
const options = ref<Record<string, any>[]>([]);
const originalOptions = ref<Record<string, any>[]>([]);
const loading = ref(false);
const isRemote = ref(false);
let searchTimer: ReturnType<typeof setTimeout> | null = null;
/** 获取列表数据 */
const fetchData = async (keyword?: string) => {
if (!props.url) return;
loading.value = true;
try {
const queryParams: Record<string, any> = {
pageNum: 1,
pageSize: 500,
...props.params
};
if (keyword) {
queryParams[props.searchKey] = keyword;
}
const res: any = await request({
url: props.url,
method: 'get',
params: queryParams
});
const rows = res.data.rows || res.data || [];
options.value = rows;
//
if (keyword === undefined) {
isRemote.value = res.total > 500;
}
//
if (!keyword) {
originalOptions.value = rows;
}
} finally {
loading.value = false;
}
};
/** 远程搜索方法 */
const remoteMethod = (query: string) => {
if (!isRemote.value) return;
//
if (!query) {
options.value = originalOptions.value;
return;
}
if (searchTimer) {
clearTimeout(searchTimer);
}
searchTimer = setTimeout(() => {
fetchData(query);
}, 300);
};
/** 值变化事件 */
const handleChange = (val: any) => {
emit('update:modelValue', val);
const selected = options.value.find((item) => item[props.valueKey] === val);
emit('change', val, selected);
};
/** 监听外部 modelValue 变化 */
watch(
() => props.modelValue,
(newVal) => {
selectedValue.value = newVal;
}
);
/** 监听 url 或 params 变化,重新加载数据 */
watch(
() => `${props.url}|${JSON.stringify(props.params)}`,
() => {
fetchData();
}
);
onMounted(() => {
fetchData();
});
//
defineExpose({
options,
loading
});
</script>