WholeProcessPlatform/frontend/src/components/fishSearch/index.vue
扈兆增 0817d15258 sjtb 页面修改 表格多选修改
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 10:35:06 +08:00

345 lines
8.5 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
ref="selectRef"
:style="{ width: width }"
:value="modelValue"
:options="options"
:loading="loading"
@change="handleChange"
@search="handleSearch"
placeholder="请选择鱼种类"
:mode="multiple ? 'multiple' : undefined"
show-search
:filter-option="false"
class="custom-fish-select"
:dropdownMatchSelectWidth="false"
:getPopupContainer="(triggerNode: HTMLElement) => triggerNode.parentNode"
@dropdownVisibleChange="handleDropdownVisibleChange"
:max-tag-count="multiple ? 1 : undefined"
:open="open"
@update:open="open = $event"
:field-names="{ label: 'name', value: 'id' }"
>
<!-- 自定义 Tag 显示名称 (仅在多选时生效) -->
<template #tagRender="{ value: tagId, onClose }" v-if="multiple">
<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': isSelected(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="isSelected(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, onMounted, computed, watch } from "vue";
import { getFishDictoryDropdown } from "@/api/select";
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao";
const shuJuTianBaoStore = useShuJuTianBaoStore();
// --- Props & Emits ---
interface Props {
modelValue: string | string[]; // 支持字符串(单选)或数组(多选)
width?: string;
multiple?: boolean; // 控制是否多选
}
const props = withDefaults(defineProps<Props>(), {
multiple: false, // 默认单选,根据需求调整
});
const emit = defineEmits<{
(e: "update:modelValue", value: string | string[], opt: any): void;
}>();
// --- State ---
const loading = ref(false);
const options = ref<any[]>([]);
const searchKeyword = ref<string>("");
const hoveredId = ref<string | null>(null);
const open = ref(false); // 控制下拉框显隐
// --- Computed ---
const filteredOptions = computed(() => {
if (!searchKeyword.value) {
return options.value;
}
const lowerKeyword = searchKeyword.value.toLowerCase();
return options.value.filter((item: any) => {
const nameMatch = item.name?.toLowerCase().includes(lowerKeyword);
const aliasMatch = item.alias?.toLowerCase().includes(lowerKeyword);
return nameMatch || aliasMatch;
});
});
const currentDetailData = computed(() => {
if (hoveredId.value) {
return options.value.find((item: any) => item.id === hoveredId.value);
}
return null;
});
// --- Methods ---
// 辅助函数:判断是否选中
const isSelected = (id: string) => {
if (props.multiple) {
return Array.isArray(props.modelValue) && props.modelValue.includes(id);
} else {
return props.modelValue === id;
}
};
const handleSearch = (value: string) => {
searchKeyword.value = value;
};
const handleDropdownVisibleChange = (val: boolean) => {
open.value = val;
if (!val) {
hoveredId.value = null;
searchKeyword.value = "";
}
};
const handleSelectOption = (opt: any) => {
if (props.multiple) {
// --- 多选逻辑 ---
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, opt);
} else {
// --- 单选逻辑 ---
// 关键:单选模式下,直接发射当前 ID覆盖旧值
// 如果点击的是已选中的项,则清空(可选行为,视需求而定)
if (props.modelValue === opt.id) {
emit("update:modelValue", "", opt); // 取消选中
} else {
emit("update:modelValue", opt.id, opt); // 选中新项
// 单选模式下,选择后通常希望关闭下拉框
open.value = false;
}
}
};
const handleChange = (val: any) => {
// 当 a-select 内部触发 change 时(例如删除 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;
};
// 监听 multiple 变化,确保模式切换时状态正确
watch(
() => props.multiple,
(newVal) => {
// 如果从多选变为单选,且 modelValue 是数组,取第一个值或清空
if (!newVal && Array.isArray(props.modelValue)) {
emit("update:modelValue", props.modelValue || null, options.value);
}
// 如果从单选变为多选,且 modelValue 是字符串,转为数组
if (newVal && typeof props.modelValue === "string") {
emit("update:modelValue", props.modelValue ? [props.modelValue] : [], {});
}
}
);
const init = () => {
let data = shuJuTianBaoStore.getFishOption();
if (data.length === 0) {
loading.value = true;
getFishDictoryDropdown()
.then((res) => {
options.value = res.data || [];
loading.value = false;
shuJuTianBaoStore.setFishOption(options.value);
})
.catch(() => {
loading.value = false;
});
} else {
options.value = data;
}
};
onMounted(() => {
init();
});
</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>