<template>
|
<div class="space-y-3">
|
<div class="flex flex-wrap items-center gap-2">
|
<ElSelect
|
v-model="selectedAreaId"
|
class="min-w-0 flex-1"
|
clearable
|
filterable
|
placeholder="请选择可入库区"
|
>
|
<ElOption
|
v-for="option in availableAreaOptions"
|
:key="option.value"
|
:label="option.label"
|
:value="option.value"
|
/>
|
</ElSelect>
|
<ElButton :disabled="selectedAreaId === '' || selectedAreaId === void 0" @click="handleAddArea">
|
添加
|
</ElButton>
|
</div>
|
|
<ElEmpty v-if="!selectedAreas.length" description="暂无可入库区" />
|
|
<div v-else class="space-y-2">
|
<div
|
v-for="(item, index) in selectedAreas"
|
:key="item.id"
|
class="flex flex-wrap items-center gap-2 rounded-lg border border-[var(--art-border-color)] px-3 py-2"
|
>
|
<div class="flex w-10 shrink-0 items-center justify-center text-sm text-[var(--art-text-secondary)]">
|
{{ index + 1 }}
|
</div>
|
<div class="min-w-0 flex-1">
|
<div class="truncate font-medium text-[var(--art-text-primary)]">
|
{{ resolveAreaLabel(item) }}
|
</div>
|
<div class="text-xs text-[var(--art-text-secondary)]">
|
ID: {{ item.id }}
|
</div>
|
</div>
|
<div class="flex items-center gap-2">
|
<ElInputNumber
|
:model-value="item.sort"
|
:min="1"
|
:controls-position="'right'"
|
class="!w-24"
|
@update:model-value="handleSortChange(item.id, $event)"
|
/>
|
<ElButton text :disabled="index === 0" @click="handleMoveUp(index)">上移</ElButton>
|
<ElButton text :disabled="index === selectedAreas.length - 1" @click="handleMoveDown(index)">
|
下移
|
</ElButton>
|
<ElButton text type="danger" @click="handleRemove(item.id)">删除</ElButton>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
const props = defineProps({
|
areaOptions: {
|
type: Array,
|
default: () => []
|
}
|
})
|
|
const modelValue = defineModel({ type: Array, default: () => [] })
|
const selectedAreaId = ref('')
|
|
const selectedAreas = computed(() => normalizeSelectedAreas(modelValue.value))
|
const areaOptionMap = computed(() =>
|
new Map(
|
(Array.isArray(props.areaOptions) ? props.areaOptions : [])
|
.map((item) => normalizeAreaOption(item))
|
.filter(Boolean)
|
.map((item) => [String(item.value), item.label])
|
)
|
)
|
const availableAreaOptions = computed(() => {
|
const selectedIds = new Set(selectedAreas.value.map((item) => String(item.id)))
|
return (Array.isArray(props.areaOptions) ? props.areaOptions : [])
|
.map((item) => normalizeAreaOption(item))
|
.filter((item) => item && !selectedIds.has(String(item.value)))
|
})
|
|
function normalizeAreaOption(option) {
|
if (!option || typeof option !== 'object') {
|
return null
|
}
|
const value = option.value ?? option.id ?? option.areaId
|
if (value === void 0 || value === null || value === '') {
|
return null
|
}
|
return {
|
value: Number(value),
|
label: String(option.label || option.name || option.areaName || option.code || option.areaCode || value)
|
}
|
}
|
|
function normalizeSelectedAreas(source = []) {
|
if (!Array.isArray(source)) {
|
return []
|
}
|
return source
|
.map((item, index) => {
|
if (item === null || item === void 0) {
|
return null
|
}
|
if (typeof item === 'object') {
|
const id = Number(item.id ?? item.areaId ?? item.value)
|
if (Number.isNaN(id)) return null
|
return {
|
id,
|
sort: Number(item.sort ?? index + 1) || index + 1
|
}
|
}
|
const id = Number(item)
|
if (Number.isNaN(id)) return null
|
return {
|
id,
|
sort: index + 1
|
}
|
})
|
.filter(Boolean)
|
.sort((left, right) => Number(left.sort) - Number(right.sort))
|
}
|
|
function syncModel(rows) {
|
modelValue.value = rows.map((item, index) => ({
|
id: item.id,
|
sort: index + 1
|
}))
|
}
|
|
function resolveAreaLabel(item) {
|
return areaOptionMap.value.get(String(item.id)) || `库区 ${item.id}`
|
}
|
|
function handleAddArea() {
|
const nextId = Number(selectedAreaId.value)
|
if (!nextId || selectedAreas.value.some((item) => Number(item.id) === nextId)) {
|
return
|
}
|
const nextRows = [...selectedAreas.value, { id: nextId, sort: selectedAreas.value.length + 1 }]
|
syncModel(nextRows)
|
selectedAreaId.value = ''
|
}
|
|
function handleSortChange(id, value) {
|
const nextSort = Number(value) || 1
|
const nextRows = selectedAreas.value.map((item) =>
|
Number(item.id) === Number(id) ? { ...item, sort: nextSort } : { ...item }
|
)
|
nextRows.sort((left, right) => Number(left.sort) - Number(right.sort))
|
syncModel(nextRows)
|
}
|
|
function handleMoveUp(index) {
|
if (index <= 0) return
|
const nextRows = [...selectedAreas.value]
|
;[nextRows[index - 1], nextRows[index]] = [nextRows[index], nextRows[index - 1]]
|
syncModel(nextRows)
|
}
|
|
function handleMoveDown(index) {
|
if (index >= selectedAreas.value.length - 1) return
|
const nextRows = [...selectedAreas.value]
|
;[nextRows[index + 1], nextRows[index]] = [nextRows[index], nextRows[index + 1]]
|
syncModel(nextRows)
|
}
|
|
function handleRemove(id) {
|
syncModel(selectedAreas.value.filter((item) => Number(item.id) !== Number(id)))
|
}
|
</script>
|