218 lines
6.6 KiB
Vue
Raw Normal View History

2025-09-26 17:26:06 +08:00
<!--
* SelectTable 下拉表格组件
2025-09-29 17:30:02 +08:00
* @author: w
* @since: 2025-9-28
2025-09-26 17:26:06 +08:00
* 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>
2025-09-29 17:30:02 +08:00
<el-select ref="selectTable" v-model="selectShowValue" :placeholder="props.placeholder" :size="props.size"
:style="{ width: props.width }" @visible-change="visibleChange" @clear="clearHandle" clearable>
2025-09-26 17:26:06 +08:00
<template #empty>
2025-09-29 17:30:02 +08:00
<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>
2025-09-26 17:26:06 +08:00
</template>
</el-select>
</div>
</template>
<script setup lang="ts">
2025-09-29 17:30:02 +08:00
import { reactive, ref, onMounted, watch, nextTick } from "vue";
// @ts-ignore
import { getFirstLetter } from '@/api/pinyin.js';
import { ElEmpty } from 'element-plus';
2025-09-26 17:26:06 +08:00
interface Field {
prop: string;
label: string;
width: number;
showTooltip?: boolean;
2025-09-29 17:30:02 +08:00
enablePinyinSearch?: boolean; //是否参与拼音搜索
2025-09-26 17:26:06 +08:00
}
interface Props {
data: any;
fields: Field[];
tableData: object[];
label?: string;
value?: string;
objKey?: string;
isHighlight?: boolean;
size?: string;
placeholder?: string;
border?: boolean;
2025-09-29 17:30:02 +08:00
width?: string | number;
2025-09-26 17:26:06 +08:00
}
2025-09-29 17:30:02 +08:00
const props = withDefaults(defineProps<Props>(), {
2025-09-26 17:26:06 +08:00
placeholder: "请选择",
size: "default",
isHighlight: true,
value: undefined,
label: undefined,
border: false,
2025-09-29 17:30:02 +08:00
width: "100%",
2025-09-26 17:26:06 +08:00
});
interface Emits {
(e: "update:data", val: any): void;
2025-09-29 17:30:02 +08:00
(e: "getDataValue", val: any): void;
2025-09-26 17:26:06 +08:00
}
2025-09-29 17:30:02 +08:00
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> }[]>([]);
2025-09-26 17:26:06 +08:00
2025-09-29 17:30:02 +08:00
// 预处理数据:生成拼音信息
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();
2025-09-26 17:26:06 +08:00
}
2025-09-29 17:30:02 +08:00
// 处理其他指定字段拼音
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();
});
2025-09-26 17:26:06 +08:00
}
2025-09-29 17:30:02 +08:00
filterDataHandle()
};
// 初始化数据
onMounted(() => {
processedTableData.value = processTableData(props.tableData);
filterData.value = [...processedTableData.value];
2025-09-26 17:26:06 +08:00
});
2025-09-29 17:30:02 +08:00
// 监听表格数据变化,重新处理
watch(() => props.tableData, (newVal) => {
processedTableData.value = processTableData(newVal);
// 数据回显
handleDataEcho();
}, { deep: true });
2025-09-26 17:26:06 +08:00
2025-09-29 17:30:02 +08:00
// 数据回显处理
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] : '')) : '';
};
2025-09-26 17:26:06 +08:00
2025-09-29 17:30:02 +08:00
// 搜索过滤逻辑
const filterDataHandle = () => {
const key = searchKey.value.trim().toLowerCase();
if (!key) {
// 空搜索时显示全部预处理数据
filterData.value = [...processedTableData.value];
return;
2025-09-26 17:26:06 +08:00
}
2025-09-29 17:30:02 +08:00
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);
2025-09-26 17:26:06 +08:00
};
const setLabel = (val: any) => {
2025-09-29 17:30:02 +08:00
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]);
2025-09-26 17:26:06 +08:00
}
2025-09-29 17:30:02 +08:00
selectTable.value.blur();
};
// 清空值
const clearHandle = () => {
selectShowValue.value = '';
emits("update:data", null);
emits('getDataValue', null);
2025-09-26 17:26:06 +08:00
};
</script>
2025-09-29 17:30:02 +08:00
<style scoped>
.select-table-dropdown {
padding: 8px;
}
:deep(.el-table .el-table__cell) {
padding: 0;
}
</style>