Files
cn-rdms-web/src/components/custom/dict-select.vue

135 lines
3.7 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, watch } from 'vue';
import { useDictStore } from '@/store/modules/dict';
import { useDict } from '@/hooks/business/dict';
defineOptions({ name: 'DictSelect' });
const ensuredEmptyDictCodes = new Set<string>();
interface Props {
dictCode: string;
placeholder?: string;
disabled?: boolean;
clearable?: boolean;
filterable?: boolean;
onlyEnabled?: boolean;
multiple?: boolean;
collapseTags?: boolean;
collapseTagsTooltip?: boolean;
/** 下拉项右侧追加字典 remark 中文释义(优先级等需要"P0 → 紧急"对照的场景) */
showRemark?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
placeholder: '请选择',
disabled: false,
clearable: true,
filterable: false,
onlyEnabled: true,
multiple: false,
collapseTags: false,
collapseTagsTooltip: false,
showRemark: false
});
const model = defineModel<string | number | Array<string | number> | null | undefined>({
default: undefined
});
const dictStore = useDictStore();
const { enabledDictData, dictData } = useDict(() => props.dictCode);
const dictOptions = computed(() => {
const source = props.onlyEnabled ? enabledDictData.value : dictData.value;
return source.map(item => ({
label: item.label,
value: item.value,
colorType: item.colorType ?? null,
remark: item.remark ?? null
}));
});
// 单选时取当前选中项的 colorType用于触发器 prefix 色块
const selectedColorType = computed<string | null>(() => {
if (props.multiple) return null;
const value = model.value;
if (value === null || value === undefined || value === '') return null;
return dictOptions.value.find(opt => opt.value === value)?.colorType ?? null;
});
watch(
() => [props.dictCode, dictOptions.value.length, dictStore.initialized, dictStore.loading] as const,
async ([dictCode, optionCount, initialized, loading]) => {
if (!dictCode || optionCount > 0 || !initialized || loading || ensuredEmptyDictCodes.has(dictCode)) {
return;
}
ensuredEmptyDictCodes.add(dictCode);
await dictStore.ensureDictData(dictCode, true);
},
{ immediate: true }
);
</script>
<template>
<ElSelect
v-model="model"
class="dict-select w-full"
:placeholder="props.placeholder"
:disabled="props.disabled"
:clearable="props.clearable"
:filterable="props.filterable"
:multiple="props.multiple"
:collapse-tags="props.collapseTags"
:collapse-tags-tooltip="props.collapseTagsTooltip"
>
<template v-if="selectedColorType" #prefix>
<span class="dict-select__color-dot" :style="{ background: selectedColorType }" />
</template>
<ElOption v-for="item in dictOptions" :key="item.value" :label="item.label" :value="item.value">
<span class="dict-select__option">
<span
v-if="item.colorType"
class="dict-select__color-dot dict-select__color-dot--option"
:style="{ background: item.colorType }"
/>
<span class="dict-select__option-label">{{ item.label }}</span>
<span v-if="props.showRemark && item.remark" class="dict-select__option-remark">{{ item.remark }}</span>
</span>
</ElOption>
</ElSelect>
</template>
<style scoped>
.dict-select__color-dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
vertical-align: middle;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
}
.dict-select__color-dot--option {
margin-right: 8px;
}
.dict-select__option {
display: inline-flex;
align-items: center;
width: 100%;
gap: 8px;
}
.dict-select__option-label {
flex: 0 0 auto;
}
.dict-select__option-remark {
margin-left: auto;
color: var(--el-text-color-secondary);
font-size: 12px;
}
</style>