2025-09-17 10:19:20 +08:00

229 lines
6.2 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.

<template>
<div class="dict-input-wrapper">
<el-input
v-model="displayValue"
:placeholder="placeholder"
:clearable="clearable"
:disabled="isDictLoading || disabled"
@blur="handleBlur"
@clear="handleClear"
@dblclick="handleDblClick"
>
<template #prefix>
<el-icon v-if="isDictLoading" size="16"><Loading /></el-icon>
</template>
<template #suffix>
<el-icon @click="openDictSelector" size="16" class="select-icon">
<ArrowDown />
</el-icon>
</template>
</el-input>
<!-- 字典选择弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
@close="handleDialogClose"
>
<el-input
v-model="searchKey"
placeholder="搜索(支持名称、编码、简拼)..."
class="mb-4"
clearable
@clear="filterDictData"
@input="filterDictData"
/>
<el-table
:data="filteredDictData"
height="300px"
border
@row-click="selectItem"
@row-dblclick="selectItem"
>
<el-table-column prop="value" label="编码" width="100" />
<el-table-column prop="label" label="名称" width="200" />
<el-table-column prop="pinyin" label="简拼" width="150" /> <!-- 显示简拼便于调试 -->
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button size="small" type="primary" @click="selectItem(scope.row)">选择</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { Loading, ArrowDown } from '@element-plus/icons-vue';
import { getFirstLetter } from '@/api/pinyin.js'; // 引入拼音处理工具
// 组件参数
const props = defineProps({
modelValue: { type: [String, Number], default: '' },
dictType: { type: String, required: true },
placeholder: { type: String, default: '请输入或双击选择' },
clearable: { type: Boolean, default: true },
disabled: {type: Boolean, default: false},
fetchDict: {type: Function, required: true},
dialogTitle: {type: String, default: '选择字典项'},
loadOnMount: { type: Boolean, default: true }
});
// 组件事件
const emit = defineEmits(['update:modelValue', 'update:labelValue', 'select']);
// 内部状态
const tempValue = ref('');
const labelValue = ref('');
const dictData = ref([]); // 存储带简拼的字典数据
const filteredDictData = ref([]);
const isDictLoading = ref(false);
const dialogVisible = ref(false);
const searchKey = ref('');
// 显示值计算
const displayValue = computed({
get() {
return labelValue.value || tempValue.value || props.modelValue || '';
},
set(newValue) {
tempValue.value = newValue;
if (labelValue.value && newValue !== labelValue.value) {
labelValue.value = '';
emit('update:labelValue', '');
}
}
});
// 加载字典数据(并添加简拼字段)
const loadDictData = async () => {
try {
isDictLoading.value = true;
const rawData = await props.fetchDict(props.dictType);
// 为每个字典项添加简拼字段
dictData.value = (rawData || []).map(item => ({
...item,
pinyin: getFirstLetter(item.label) // 新增pinyin字段存储简拼
}));
filteredDictData.value = [...dictData.value];
} catch (error) {
console.error(`加载${props.dictType}字典失败:`, error);
dictData.value = [];
filteredDictData.value = [];
} finally {
isDictLoading.value = false;
}
};
// 核心搜索逻辑(支持模糊搜索+简拼搜索)
const filterDictData = () => {
const key = searchKey.value.trim().toLowerCase();
if (!key) {
filteredDictData.value = [...dictData.value];
return;
}
filteredDictData.value = dictData.value.filter(item => {
// 1. 模糊搜索:匹配label(名称)或value(编码)
const matchLabel = item.label.toLowerCase().includes(key);
const matchValue = String(item.value).toLowerCase().includes(key);
// 2. 简拼搜索:匹配首字母简拼
const matchPinyin = item.pinyin.toLowerCase().includes(key);
// 满足任一条件即匹配
return matchLabel || matchValue || matchPinyin;
});
};
// 双击事件处理
const handleDblClick = () => {
if (!props.disabled && !isDictLoading.value) {
dialogVisible.value = true;
}
};
// 关键修改:打开弹窗时重新加载数据
const openDictSelector = async () => {
if (props.disabled || isDictLoading.value) {
return;
}
// 打开弹窗前先加载数据(确保每次打开都是最新的)
await loadDictData();
// 数据加载完成后再显示弹窗
dialogVisible.value = true;
searchKey.value = '';
filterDictData();
};
// 选择字典项
const selectItem = (item) => {
emit('update:modelValue', item.value);
emit('update:labelValue', item.label);
emit('select', item);
tempValue.value = item.value;
labelValue.value = item.label;
dialogVisible.value = false;
};
// 其他方法(失焦、清空等)保持不变
const handleBlur = () => { /* ... */
};
const handleClear = () => { /* ... */
};
const handleDialogClose = () => {
dialogVisible.value = false;
};
// 监听初始值变化
watch(() => props.modelValue, (newVal) => {
tempValue.value = newVal || '';
if (newVal && !isDictLoading.value) {
const matched = dictData.value.find(item => String(item.value) === String(newVal));
labelValue.value = matched?.label || newVal;
emit('update:labelValue', labelValue.value);
}
}, {immediate: true});
// 组件挂载时加载字典
onMounted(async () => {
if (props.loadOnMount) { // 仅当loadOnMount为true时(默认),才在挂载时加载
await loadDictData();
}
});
defineExpose({
openDictSelector, // 暴露打开弹窗的方法
// 可选:暴露其他可能需要的方法(如刷新字典数据)
loadDictData
});
</script>
<style scoped>
/* 样式保持不变 */
.dict-input-wrapper {
position: relative;
}
.select-icon {
cursor: pointer;
color: #666;
margin-left: 5px;
}
.select-icon:hover {
color: #409eff;
}
:deep(.el-input__suffix) {
gap: 4px;
}
</style>