<script setup lang="ts">
|
import { computed, ref } from 'vue';
|
|
import type { DropdownOption } from '../domain/types';
|
|
const props = withDefaults(
|
defineProps<{
|
defaultValue: string;
|
disabled?: boolean;
|
errorField?: 'dropdown-default' | 'dropdown-options';
|
errorId?: string;
|
options: DropdownOption[];
|
}>(),
|
{ disabled: false },
|
);
|
|
const emit = defineEmits<{
|
'update:defaultValue': [value: string];
|
'update:options': [options: DropdownOption[]];
|
}>();
|
|
const rootElement = ref<HTMLElement | null>(null);
|
|
const defaultOptions = computed(() => [
|
{ label: '无默认项', value: '' },
|
...props.options.filter((option) => option.display.trim() && option.value.trim()).map((option) => ({ label: option.display.trim(), value: option.value })),
|
]);
|
|
function getPopupContainer(): HTMLElement {
|
return rootElement.value ?? document.body;
|
}
|
|
function emitOptions(options: DropdownOption[]): void {
|
emit('update:options', options);
|
|
const defaultStillExists = options.some((option) => option.display.trim() && option.value.trim() && option.value === props.defaultValue);
|
if (props.defaultValue && !defaultStillExists) {
|
emit('update:defaultValue', '');
|
}
|
}
|
|
function updateOption(index: number, field: keyof DropdownOption, value: string): void {
|
if (props.disabled) return;
|
|
emitOptions(props.options.map((option, optionIndex) => (optionIndex === index ? { ...option, [field]: value } : { ...option })));
|
}
|
|
function addOption(): void {
|
if (props.disabled) return;
|
emitOptions([...props.options.map((option) => ({ ...option })), { display: '', value: '' }]);
|
}
|
|
function removeOption(index: number): void {
|
if (props.disabled || props.options.length <= 2) return;
|
emitOptions(props.options.filter((_, optionIndex) => optionIndex !== index).map((option) => ({ ...option })));
|
}
|
|
function updateDefaultValue(value: string): void {
|
if (!props.disabled) emit('update:defaultValue', value);
|
}
|
</script>
|
|
<template>
|
<div ref="rootElement" class="dropdown-options" data-test="dropdown-options">
|
<div class="option-heading">
|
<span>下拉选项</span>
|
<a-button aria-label="添加选项" data-test="add-option" :disabled="disabled" size="small" title="添加选项" type="default" @click="addOption">
|
添加选项
|
</a-button>
|
</div>
|
|
<div v-for="(option, index) in options" :key="index" class="option-row">
|
<a-input
|
:aria-describedby="errorField === 'dropdown-options' && index === 0 ? errorId : undefined"
|
:aria-invalid="errorField === 'dropdown-options' && index === 0 ? true : undefined"
|
:aria-label="`第${index + 1}项显示名称`"
|
autocomplete="off"
|
:data-test="`option-display-${index}`"
|
:disabled="disabled"
|
:id="`dropdown-option-display-${index}`"
|
placeholder="显示名称"
|
:value="option.display"
|
@update:value="updateOption(index, 'display', $event)" />
|
<a-input
|
:aria-label="`第${index + 1}项业务值`"
|
autocomplete="off"
|
:data-test="`option-value-${index}`"
|
:disabled="disabled"
|
:id="`dropdown-option-value-${index}`"
|
placeholder="业务值"
|
:value="option.value"
|
@update:value="updateOption(index, 'value', $event)" />
|
<a-button
|
:aria-label="`删除第${index + 1}项`"
|
class="remove-option"
|
:data-test="`remove-option-${index}`"
|
:disabled="disabled || options.length <= 2"
|
size="small"
|
title="删除选项"
|
type="default"
|
@click="removeOption(index)">
|
×
|
</a-button>
|
</div>
|
|
<label class="default-label" for="dropdown-default">默认项</label>
|
<a-select
|
:aria-describedby="errorField === 'dropdown-default' ? errorId : undefined"
|
:aria-invalid="errorField === 'dropdown-default' ? true : undefined"
|
aria-label="默认项"
|
data-test="dropdown-default"
|
:disabled="disabled"
|
:get-popup-container="getPopupContainer"
|
id="dropdown-default"
|
:options="defaultOptions"
|
:value="defaultValue"
|
@update:value="updateDefaultValue" />
|
</div>
|
</template>
|
|
<style scoped>
|
.dropdown-options {
|
display: grid;
|
min-width: 0;
|
gap: 8px;
|
}
|
|
.option-heading,
|
.option-row {
|
display: flex;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.option-heading {
|
justify-content: space-between;
|
}
|
|
.option-row :deep(.ant-input) {
|
min-width: 0;
|
}
|
|
.remove-option {
|
width: 32px;
|
min-width: 32px;
|
padding-inline: 0;
|
}
|
|
.default-label {
|
line-height: 1.4;
|
}
|
</style>
|