2025-09-29 17:30:02 +08:00

218 lines
6.6 KiB
Vue
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.

<!--
* SelectTable 下拉表格组件
* @author: w
* @since: 2025-9-28
* SelectTable.vue
*
* 属性(带有[Required]为必填属性)
* data[Required]: 双向绑定数据
* fields[Required]: 表格列对象
* tableData[Required]: 表格数据对象
* value: 双向绑定数据的值(为空时data属性为表格当前行数据对象)
* label: 选择框显示的文本内容(为空时分别查看value和objKey是否为空,若value和objKey都有值,则value优先级大于objKey)
* objKey: 对象key(绑定值的唯一标识,绑定值为对象时必填)
* placeholder: 选择框占位文本
* size: 组件大小
* border: 表格是否带有边框
-->
<template>
<div>
<el-select ref="selectTable" v-model="selectShowValue" :placeholder="props.placeholder" :size="props.size"
:style="{ width: props.width }" @visible-change="visibleChange" @clear="clearHandle" clearable>
<template #empty>
<div class="select-table-dropdown">
<div style="text-align: left;">
<el-input v-model="searchKey" ref="searchInput" size="small" placeholder="快速搜索(支持简拼)" class="mb10" clearable
@clear="filterDataHandle" @input="filterDataHandle" />
</div>
<!-- 数据为空时显示空状态提示 -->
<el-empty v-if="filterData.length === 0" description="没有匹配的数据" :image-size="100" />
<!-- 数据存在时显示表格 -->
<el-table ref="tableRef" v-else :data="filterData" :highlight-current-row="props.isHighlight"
style="width: 100%" :border="props.border" @row-click="handleRowChange" max-height="350px">
<el-table-column v-for="field in props.fields" :prop="field.prop" :label="field.label" :width="field.width"
:show-overflow-tooltip="field.showTooltip" align="center" />
</el-table>
</div>
</template>
</el-select>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, watch, nextTick } from "vue";
// @ts-ignore
import { getFirstLetter } from '@/api/pinyin.js';
import { ElEmpty } from 'element-plus';
interface Field {
prop: string;
label: string;
width: number;
showTooltip?: boolean;
enablePinyinSearch?: boolean; //是否参与拼音搜索
}
interface Props {
data: any;
fields: Field[];
tableData: object[];
label?: string;
value?: string;
objKey?: string;
isHighlight?: boolean;
size?: string;
placeholder?: string;
border?: boolean;
width?: string | number;
}
const props = withDefaults(defineProps<Props>(), {
placeholder: "请选择",
size: "default",
isHighlight: true,
value: undefined,
label: undefined,
border: false,
width: "100%",
});
interface Emits {
(e: "update:data", val: any): void;
(e: "getDataValue", val: any): void;
}
const emits = defineEmits<Emits>();
const searchKey = ref('');
const tableRef = ref();
const selectTable = ref();
const selectShowValue = ref('');
const searchInput = ref();
const filterData = ref<{ [key: string]: any; pinyinMap: Record<string, string> }[]>([]);
// 存储预处理后的带拼音数据
const processedTableData = ref<{ [key: string]: any; pinyinMap: Record<string, string> }[]>([]);
// 预处理数据:生成拼音信息
const processTableData = (data: object[]) => {
return data.map((item: any) => {
const pinyinMap: Record<string, string> = {};
// 处理label字段拼音
if (item[props.label!]) {
pinyinMap.label = getFirstLetter(item[props.label!]).toLowerCase();
}
// 处理value字段拼音
if (props.value && item[props.value]) {
pinyinMap.value = getFirstLetter(String(item[props.value])).toLowerCase();
}
// 处理其他指定字段拼音
props.fields.forEach(field => {
if (field.enablePinyinSearch && item[field.prop]) {
pinyinMap[field.prop] = getFirstLetter(item[field.prop]).toLowerCase();
}
});
return { ...item, pinyinMap };
});
};
// 隐藏显示处理
const visibleChange = (val: any) => {
searchKey.value = '';
if (val) {
nextTick(() => {
searchInput.value.focus();
});
}
filterDataHandle()
};
// 初始化数据
onMounted(() => {
processedTableData.value = processTableData(props.tableData);
filterData.value = [...processedTableData.value];
});
// 监听表格数据变化,重新处理
watch(() => props.tableData, (newVal) => {
processedTableData.value = processTableData(newVal);
// 数据回显
handleDataEcho();
}, { deep: true });
// 数据回显处理
const handleDataEcho = () => {
if (props.data === null || props.data === undefined) return;
// 查找匹配的行数据
const row: any = props.tableData.find((item: any) => props.objKey && item[props.objKey] === props.data)
selectShowValue.value = row ? (props.label ? row[props.label] : (props.value ? row[props.value] : '')) : '';
};
// 搜索过滤逻辑
const filterDataHandle = () => {
const key = searchKey.value.trim().toLowerCase();
if (!key) {
// 空搜索时显示全部预处理数据
filterData.value = [...processedTableData.value];
return;
}
filterData.value = processedTableData.value.filter((item: any) => {
// 1. 原文本匹配
const matchLabel = props.label && item[props.label]?.toString().toLowerCase().includes(key);
const matchValue = props.value && item[props.value]?.toString().toLowerCase().includes(key);
// 2. 拼音匹配
const matchLabelPinyin = props.label && item.pinyinMap.label?.includes(key);
const matchValuePinyin = props.value && item.pinyinMap.value?.includes(key);
// 3. 其他字段匹配
let matchOtherField = false;
props.fields.forEach(field => {
if (field.enablePinyinSearch) {
const textMatch = item[field.prop]?.toString().toLowerCase().includes(key);
const pinyinMatch = item.pinyinMap[field.prop]?.includes(key);
if (textMatch || pinyinMatch) {
matchOtherField = true;
}
}
});
return matchLabel || matchValue || matchLabelPinyin || matchValuePinyin || matchOtherField;
});
};
const handleRowChange = (val: any) => {
setLabel(val);
emits("update:data", props.value ? val[props.value] : val);
};
const setLabel = (val: any) => {
if (props.label) {
selectShowValue.value = val[props.label];
} else if (props.value) {
selectShowValue.value = val[props.value];
}
if (props.objKey) {
emits('getDataValue', val[props.objKey]);
}
selectTable.value.blur();
};
// 清空值
const clearHandle = () => {
selectShowValue.value = '';
emits("update:data", null);
emits('getDataValue', null);
};
</script>
<style scoped>
.select-table-dropdown {
padding: 8px;
}
:deep(.el-table .el-table__cell) {
padding: 0;
}
</style>