183 lines
5.0 KiB
Vue
183 lines
5.0 KiB
Vue
<template>
|
|
<el-select
|
|
:model-value="modelValue"
|
|
:placeholder="placeholder"
|
|
:clearable="clearable"
|
|
:filterable="filterable"
|
|
:allow-create="allowCreate"
|
|
:default-first-option="allowCreate"
|
|
:disabled="disabled || loading"
|
|
:size="size"
|
|
:style="style"
|
|
:multiple="multiple"
|
|
:collapse-tags="multiple"
|
|
:collapse-tags-tooltip="multiple"
|
|
@update:model-value="onSelect"
|
|
>
|
|
<el-option
|
|
v-for="opt in options"
|
|
:key="opt.value"
|
|
:label="opt.label"
|
|
:value="opt.value"
|
|
>
|
|
<div class="dict-select-option">
|
|
<span>{{ opt.label }}</span>
|
|
<el-button
|
|
v-if="dictStore.isSessionCreated(dictType, opt.value)"
|
|
link
|
|
type="danger"
|
|
size="small"
|
|
class="dict-select-option__remove"
|
|
@click.stop="onRemove(opt.value)"
|
|
>
|
|
<el-icon><Delete /></el-icon>
|
|
</el-button>
|
|
</div>
|
|
</el-option>
|
|
</el-select>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { Delete } from '@element-plus/icons-vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { useDictStore } from '@/store/modules/dict'
|
|
import { normalizePackageSpecs } from '@/utils/crm-package-spec'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
dictType: string
|
|
modelValue?: string | string[] | null
|
|
allowCreate?: boolean
|
|
clearable?: boolean
|
|
filterable?: boolean
|
|
disabled?: boolean
|
|
multiple?: boolean
|
|
placeholder?: string
|
|
size?: '' | 'default' | 'small' | 'large'
|
|
style?: string | Record<string, string>
|
|
}>(),
|
|
{
|
|
modelValue: '',
|
|
allowCreate: true,
|
|
clearable: true,
|
|
filterable: true,
|
|
disabled: false,
|
|
multiple: false,
|
|
placeholder: '请选择',
|
|
size: 'default'
|
|
}
|
|
)
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [value: string | string[]]
|
|
change: [value: string | string[]]
|
|
}>()
|
|
|
|
const dictStore = useDictStore()
|
|
const loading = ref(false)
|
|
|
|
const options = computed(() => dictStore.getOptions(props.dictType))
|
|
|
|
function isKnown(value: string) {
|
|
return options.value.some((o) => o.value === value || o.label === value)
|
|
}
|
|
|
|
async function resolveKnownValue(next: string): Promise<string | null> {
|
|
const trimmed = String(next ?? '').trim()
|
|
if (!trimmed) return null
|
|
if (!props.allowCreate || isKnown(trimmed)) {
|
|
const known = options.value.find((o) => o.value === trimmed || o.label === trimmed)
|
|
return known?.value ?? trimmed
|
|
}
|
|
try {
|
|
await ElMessageBox.confirm(`将「${trimmed}」新增到字典,是否继续?`, '新增字典项', {
|
|
type: 'warning',
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消'
|
|
})
|
|
} catch {
|
|
return null
|
|
}
|
|
loading.value = true
|
|
try {
|
|
const data = await dictStore.ensure(props.dictType, trimmed)
|
|
return data.value
|
|
} catch (e: any) {
|
|
ElMessage.error(e?.message || '写入字典失败')
|
|
return null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function onSelect(raw: string | string[] | null | undefined) {
|
|
if (props.multiple) {
|
|
const incoming = Array.isArray(raw) ? raw : raw == null || raw === '' ? [] : [String(raw)]
|
|
const result: string[] = []
|
|
for (const item of incoming) {
|
|
const resolved = await resolveKnownValue(String(item))
|
|
if (resolved) result.push(resolved)
|
|
}
|
|
const deduped = normalizePackageSpecs(result)
|
|
emit('update:modelValue', deduped)
|
|
emit('change', deduped)
|
|
return
|
|
}
|
|
|
|
const next = raw == null ? '' : String(raw).trim()
|
|
if (!next) {
|
|
emit('update:modelValue', '')
|
|
emit('change', '')
|
|
return
|
|
}
|
|
const resolved = await resolveKnownValue(next)
|
|
if (resolved == null) return
|
|
emit('update:modelValue', resolved)
|
|
emit('change', resolved)
|
|
}
|
|
|
|
async function onRemove(value: string) {
|
|
try {
|
|
await ElMessageBox.confirm(`确定从字典中删除「${value}」吗?`, '删除字典项', {
|
|
type: 'warning',
|
|
confirmButtonText: '删除',
|
|
cancelButtonText: '取消'
|
|
})
|
|
} catch {
|
|
return
|
|
}
|
|
loading.value = true
|
|
try {
|
|
await dictStore.ensureRemove(props.dictType, value)
|
|
if (props.multiple) {
|
|
const current = Array.isArray(props.modelValue) ? props.modelValue : []
|
|
const next = current.filter((v) => v !== value)
|
|
emit('update:modelValue', next)
|
|
emit('change', next)
|
|
} else if (props.modelValue === value) {
|
|
emit('update:modelValue', '')
|
|
emit('change', '')
|
|
}
|
|
ElMessage.success('已删除')
|
|
} catch (e: any) {
|
|
ElMessage.error(e?.message || '删除失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dict-select-option {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
|
|
.dict-select-option__remove {
|
|
flex-shrink: 0;
|
|
}
|
|
</style>
|