WholeProcessPlatform/frontend/src/components/fishSearch/index.vue
2026-04-22 17:53:20 +08:00

271 lines
6.1 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>
<a-select
:style="{ width: width }"
:value="modelValue"
@change="handleChange"
placeholder="请选择鱼名称"
mode="multiple"
show-search
:filter-option="filterOption"
class="custom-fish-select"
:dropdownMatchSelectWidth="false"
:getPopupContainer="(triggerNode: HTMLElement) => triggerNode.parentNode"
@dropdownVisibleChange="handleDropdownVisibleChange"
>
<!-- 自定义 Tag 显示名称 -->
<template #tagRender="{ value: tagId, onClose }">
<a-tag
closable
@close="onClose"
style="margin-right: 3px; max-width: 120px"
>
{{ getFishNameById(tagId) }}
</a-tag>
</template>
<template #dropdownRender>
<div class="custom-dropdown-container">
<!-- 左侧可滚动的选项列表 -->
<div class="dropdown-left-list">
<div
v-for="opt in filteredOptions"
:key="opt.id"
class="dropdown-item"
:class="{
'is-active': Array.isArray(modelValue) && modelValue.includes(opt.id),
'is-hovered': opt.id === hoveredId,
}"
@click.stop="handleSelectOption(opt)"
@mouseenter="hoveredId = opt.id"
>
<span class="item-name">{{ opt.name }}</span>
<!-- 选中对勾 -->
<span v-if="Array.isArray(modelValue) && modelValue.includes(opt.id)" class="check-icon">✓</span>
</div>
<div v-if="filteredOptions.length === 0" class="empty-tip">
无匹配数据
</div>
</div>
<!-- 中间分割线 -->
<div class="dropdown-divider"></div>
<!-- 右侧:固定显示的别名/详情 -->
<div class="dropdown-right-detail">
<div v-if="currentDetailData" class="detail-content">
<div class="detail-title">{{ currentDetailData.name }}</div>
<div class="detail-alias" :title="currentDetailData.alias">
{{ currentDetailData.alias || "暂无别名" }}
</div>
</div>
<div v-else class="detail-placeholder">请选择或悬停查看</div>
</div>
</div>
</template>
</a-select>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
// --- Props & Emits ---
interface Props {
modelValue: string[]; // 接收选中的 ID 数组
options: any[];
width: string;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: "update:modelValue", value: string[]): void;
}>();
// --- State ---
// 这里可以改为从 API 获取,目前先保留静态数据作为示例
const options = ref<any>(props.options || []);
const hoveredId = ref<string | null>(null);
// --- Computed ---
const filteredOptions = computed(() => {
return options.value;
});
const currentDetailData = computed(() => {
if (hoveredId.value) {
return options.value.find((item: any) => item.id === hoveredId.value);
}
return null;
});
// --- Methods ---
const handleDropdownVisibleChange = (open: boolean) => {
if (!open) {
hoveredId.value = null;
}
};
const filterOption = (input: string, option: any) => {
if (!input) return true;
const targetOpt = options.value.find((item: any) => item.id === option.value);
if (!targetOpt) return false;
const lowerInput = input.toLowerCase();
const nameMatch = targetOpt.name?.toLowerCase().includes(lowerInput);
const aliasMatch = targetOpt.alias?.toLowerCase().includes(lowerInput);
return nameMatch || aliasMatch;
};
const handleSelectOption = (opt: any) => {
let newValues: string[] = Array.isArray(props.modelValue) ? [...props.modelValue] : [];
const index = newValues.indexOf(opt.id);
if (index > -1) {
newValues.splice(index, 1);
} else {
newValues.push(opt.id);
}
// 触发更新
emit("update:modelValue", newValues);
};
const handleChange = (val: any) => {
// 处理直接通过 a-select 内部机制触发的变化(如删除 tag
emit("update:modelValue", val);
};
const getFishNameById = (id: string) => {
if (!id) return "";
const fish = options.value.find((item: any) => item.id === id);
return fish ? fish.name : id;
};
</script>
<style lang="scss" scoped>
.custom-fish-select {
:deep(.ant-select-dropdown) {
padding: 0 !important;
min-width: 400px !important;
width: auto !important;
}
}
.custom-dropdown-container {
display: flex;
background: #fff;
border-radius: 4px;
overflow: hidden;
height: 300px; /* 固定高度 */
}
.dropdown-left-list {
width: 150px;
height: 100%;
overflow-y: auto;
border-right: 1px solid #f0f0f0;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
}
.dropdown-item {
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
color: #333;
transition: background-color 0.3s;
display: flex;
justify-content: space-between;
align-items: center;
&:hover {
background-color: #f5f5f5;
}
&.is-active {
background-color: #e6f7ff;
color: #1890ff;
font-weight: 500;
}
&.is-hovered {
background-color: #fafafa;
}
.item-name {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.check-icon {
color: #1890ff;
font-weight: bold;
margin-left: 8px;
}
}
.dropdown-divider {
width: 1px;
background-color: #e8e8e8;
height: 100%;
}
.dropdown-right-detail {
width: 250px;
height: 100%;
padding: 16px;
box-sizing: border-box;
background-color: #fff;
overflow-y: auto;
display: flex;
flex-direction: column;
position: relative;
}
.detail-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.detail-alias {
font-size: 14px;
color: #666;
line-height: 1.5;
word-break: break-all;
}
.detail-placeholder {
color: #999;
font-size: 14px;
text-align: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.empty-tip {
padding: 10px;
color: #999;
text-align: center;
font-size: 12px;
}
</style>