<template>
|
<div class="areas-editor">
|
<div class="areas-editor__toolbar">
|
<ElSelect
|
v-model="selectedAreaId"
|
class="areas-editor__select"
|
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>
|
<span class="areas-editor__toolbar-tip">
|
已选 {{ selectedAreas.length }} 个库区,可调整顺序
|
</span>
|
</div>
|
|
<div class="areas-editor__panel">
|
<template v-if="selectedAreas.length">
|
<div class="areas-editor__header">
|
<span>序号</span>
|
<span>库区</span>
|
<span>排序</span>
|
<span>操作</span>
|
</div>
|
|
<ElScrollbar max-height="240px">
|
<div class="areas-editor__list">
|
<div v-for="(item, index) in selectedAreas" :key="item.id" class="areas-editor__row">
|
<div class="areas-editor__index">
|
{{ index + 1 }}
|
</div>
|
<div class="areas-editor__name">
|
<div class="areas-editor__name-text">
|
{{ resolveAreaLabel(item) }}
|
</div>
|
<div class="areas-editor__meta"> ID: {{ item.id }} </div>
|
</div>
|
<div class="areas-editor__sort">
|
<ElInputNumber
|
:model-value="item.sort"
|
:min="1"
|
:controls-position="'right'"
|
class="areas-editor__sort-input"
|
@update:model-value="handleSortChange(item.id, $event)"
|
/>
|
</div>
|
<div class="areas-editor__actions">
|
<ElButton text size="small" :disabled="index === 0" @click="handleMoveUp(index)">
|
上移
|
</ElButton>
|
<ElButton
|
text
|
size="small"
|
:disabled="index === selectedAreas.length - 1"
|
@click="handleMoveDown(index)"
|
>
|
下移
|
</ElButton>
|
<ElButton text size="small" type="danger" @click="handleRemove(item.id)">
|
删除
|
</ElButton>
|
</div>
|
</div>
|
</div>
|
</ElScrollbar>
|
</template>
|
|
<div v-else class="areas-editor__empty">
|
<div class="areas-editor__empty-title">暂无可入库区</div>
|
<div class="areas-editor__empty-tip"
|
>从上方选择库区后点击“添加”,再按排序值或上下移动调整顺序</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>
|
|
<style scoped>
|
.areas-editor {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
width: 100%;
|
}
|
|
.areas-editor__toolbar {
|
display: flex;
|
flex-wrap: wrap;
|
align-items: center;
|
gap: 8px;
|
}
|
|
.areas-editor__select {
|
width: 320px;
|
max-width: 100%;
|
}
|
|
.areas-editor__toolbar-tip {
|
color: var(--art-text-secondary);
|
font-size: 12px;
|
line-height: 1.5;
|
}
|
|
.areas-editor__panel {
|
border: 1px solid var(--art-border-color);
|
border-radius: 12px;
|
background: var(--art-bg-color);
|
overflow: hidden;
|
}
|
|
.areas-editor__header {
|
display: grid;
|
grid-template-columns: 56px minmax(0, 1fr) 112px 190px;
|
gap: 12px;
|
align-items: center;
|
padding: 10px 16px;
|
background: var(--el-fill-color-light);
|
color: var(--art-text-secondary);
|
font-size: 12px;
|
line-height: 1;
|
}
|
|
.areas-editor__list {
|
display: flex;
|
flex-direction: column;
|
gap: 10px;
|
padding: 12px 16px;
|
}
|
|
.areas-editor__row {
|
display: grid;
|
grid-template-columns: 56px minmax(0, 1fr) 112px 190px;
|
gap: 12px;
|
align-items: center;
|
border: 1px solid var(--art-border-color);
|
border-radius: 10px;
|
padding: 10px 12px;
|
background: var(--el-bg-color-page);
|
}
|
|
.areas-editor__index {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
color: var(--art-text-secondary);
|
font-size: 13px;
|
}
|
|
.areas-editor__name {
|
min-width: 0;
|
}
|
|
.areas-editor__name-text {
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
color: var(--art-text-primary);
|
font-weight: 500;
|
line-height: 1.4;
|
}
|
|
.areas-editor__meta {
|
margin-top: 2px;
|
color: var(--art-text-secondary);
|
font-size: 12px;
|
line-height: 1.4;
|
}
|
|
.areas-editor__sort {
|
display: flex;
|
justify-content: flex-start;
|
}
|
|
:deep(.areas-editor__sort-input) {
|
width: 100px;
|
}
|
|
.areas-editor__actions {
|
display: flex;
|
flex-wrap: wrap;
|
align-items: center;
|
gap: 4px;
|
}
|
|
.areas-editor__empty {
|
display: flex;
|
min-height: 136px;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
gap: 8px;
|
padding: 20px 16px;
|
text-align: center;
|
}
|
|
.areas-editor__empty-title {
|
color: var(--art-text-primary);
|
font-size: 14px;
|
font-weight: 500;
|
line-height: 1.4;
|
}
|
|
.areas-editor__empty-tip {
|
max-width: 420px;
|
color: var(--art-text-secondary);
|
font-size: 12px;
|
line-height: 1.6;
|
}
|
|
@media (max-width: 900px) {
|
.areas-editor__header {
|
display: none;
|
}
|
|
.areas-editor__row {
|
grid-template-columns: 48px minmax(0, 1fr);
|
}
|
|
.areas-editor__sort,
|
.areas-editor__actions {
|
grid-column: 2;
|
}
|
}
|
</style>
|