<script lang="ts" setup>
|
import { computed, ref } from 'vue';
|
|
import { ChevronDown, Search } from '@vben/icons';
|
|
import { Modal as AModal } from 'ant-design-vue';
|
|
defineOptions({ name: 'StabilityAnalysisOptionPopup' });
|
|
const props = withDefaults(
|
defineProps<{
|
columns: AnalysisOptionColumn[];
|
disabled?: boolean;
|
mode?: 'multiple' | 'single';
|
options: AnalysisPopupOption[];
|
placeholder?: string;
|
popupTitle: string;
|
value?: string | string[];
|
width?: number;
|
}>(),
|
{
|
disabled: false,
|
mode: 'multiple',
|
placeholder: '请选择',
|
value: undefined,
|
width: 900,
|
},
|
);
|
|
const emit = defineEmits<{
|
change: [value: string | string[] | undefined];
|
'update:value': [value: string | string[] | undefined];
|
}>();
|
|
interface AnalysisOptionColumn {
|
customRender?: ({ index }: { index: number }) => number;
|
dataIndex: string;
|
key: string;
|
title: string;
|
width?: number;
|
}
|
|
interface AnalysisPopupOption {
|
[key: string]: unknown;
|
key: string;
|
name: string;
|
}
|
|
const open = ref(false);
|
const keyword = ref('');
|
const draftKeys = ref<string[]>([]);
|
|
const normalizedValue = computed(() => {
|
if (Array.isArray(props.value)) return props.value.map(String);
|
return props.value ? [String(props.value)] : [];
|
});
|
const displayText = computed(() => {
|
const optionMap = new Map(props.options.map((option) => [option.key, option.name]));
|
return normalizedValue.value.map((key) => optionMap.get(key) || key).join(', ');
|
});
|
const filteredOptions = computed(() => {
|
const search = keyword.value.trim().toLocaleLowerCase();
|
if (!search) return props.options;
|
return props.options.filter((option) =>
|
props.columns.some((column) =>
|
String(option[column.dataIndex] ?? '')
|
.toLocaleLowerCase()
|
.includes(search),
|
),
|
);
|
});
|
const pagination = computed(() => ({
|
pageSize: 25,
|
pageSizeOptions: ['10', '25', '50', '100'],
|
showSizeChanger: true,
|
showTotal: (total: number) => `共 ${total} 条`,
|
}));
|
const rowSelection = computed(() => {
|
return {
|
onChange: (keys: Array<number | string>) => {
|
const normalizedKeys = keys.map(String);
|
draftKeys.value = props.mode === 'single' ? normalizedKeys.slice(-1) : normalizedKeys;
|
},
|
preserveSelectedRowKeys: true,
|
selectedRowKeys: draftKeys.value,
|
type: props.mode === 'single' ? ('radio' as const) : ('checkbox' as const),
|
};
|
});
|
|
function openPopup() {
|
if (props.disabled) return;
|
keyword.value = '';
|
draftKeys.value = [...normalizedValue.value];
|
open.value = true;
|
}
|
|
function closePopup() {
|
open.value = false;
|
}
|
|
function toggleRow(key: string) {
|
if (props.mode === 'single') {
|
draftKeys.value = [key];
|
return;
|
}
|
draftKeys.value = draftKeys.value.includes(key) ? draftKeys.value.filter((item) => item !== key) : [...draftKeys.value, key];
|
}
|
|
function customRow(record: AnalysisPopupOption) {
|
return {
|
onClick: (event: MouseEvent) => {
|
const target = event.target as Element | null;
|
if (target?.closest('.ant-checkbox-wrapper, .ant-radio-wrapper, input, button, a')) return;
|
toggleRow(record.key);
|
},
|
onDblclick: () => {
|
if (props.mode !== 'single') return;
|
draftKeys.value = [record.key];
|
confirmSelection();
|
},
|
};
|
}
|
|
function confirmSelection() {
|
const value = props.mode === 'single' ? draftKeys.value[0] : [...draftKeys.value];
|
emit('update:value', value);
|
emit('change', value);
|
closePopup();
|
}
|
</script>
|
|
<template>
|
<div class="analysis-option-popup">
|
<a-input :disabled="disabled" :placeholder="placeholder" :title="displayText" :value="displayText" class="popup-trigger" readonly @click="openPopup">
|
<template #suffix><ChevronDown :size="16" class="dropdown-icon" /></template>
|
</a-input>
|
|
<AModal
|
v-model:open="open"
|
:body-style="{ padding: '16px' }"
|
:destroy-on-close="true"
|
:mask-closable="false"
|
:title="popupTitle"
|
:width="width"
|
@cancel="closePopup"
|
@ok="confirmSelection">
|
<div class="table-toolbar">
|
<a-input v-model:value="keyword" allow-clear placeholder="请输入名称或编码搜索">
|
<template #prefix><Search :size="16" /></template>
|
</a-input>
|
</div>
|
<a-table
|
class="fixed-height-table"
|
:columns="columns"
|
:custom-row="customRow"
|
:data-source="filteredOptions"
|
:pagination="pagination"
|
row-key="key"
|
:row-selection="rowSelection"
|
:scroll="{ x: 'max-content', y: 420 }"
|
size="middle" />
|
</AModal>
|
</div>
|
</template>
|
|
<style lang="scss" scoped>
|
.analysis-option-popup,
|
.popup-trigger {
|
width: 100%;
|
}
|
|
.popup-trigger {
|
cursor: pointer;
|
|
:deep(input) {
|
overflow: hidden;
|
text-overflow: ellipsis;
|
cursor: pointer;
|
}
|
}
|
|
.dropdown-icon {
|
color: rgb(0 0 0 / 45%);
|
pointer-events: none;
|
}
|
|
.table-toolbar {
|
margin-bottom: 12px;
|
}
|
|
.fixed-height-table {
|
:deep(.ant-table-tbody > tr) {
|
cursor: pointer;
|
}
|
|
:deep(.ant-table-body) {
|
height: calc(min(440px, 55vh) - 104px);
|
overflow-y: auto !important;
|
}
|
}
|
</style>
|