166 lines
10 KiB
Vue
166 lines
10 KiB
Vue
<script setup lang="ts">
|
|
import { computed, reactive, ref, watch } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { MapPin, Pencil, Plus, Power, RotateCcw, Search, Warehouse as WarehouseIcon } from '@lucide/vue'
|
|
import MetricStrip from '@/components/MetricStrip.vue'
|
|
import { useUnsavedWorkRegistration } from '@/composables/useUnsavedWorkRegistration'
|
|
import { useAccessPreviewStore } from '@/stores/accessPreview'
|
|
import { useAdminDataStore } from '@/stores/adminData'
|
|
import type { Warehouse } from '@/types/admin'
|
|
|
|
const store = useAdminDataStore()
|
|
const access = useAccessPreviewStore()
|
|
const editorOpen = ref(false)
|
|
const page = ref(1)
|
|
const pageSize = 10
|
|
const filters = reactive({ keyword: '', status: '' as Warehouse['status'] | '' })
|
|
|
|
const emptyForm = (): Omit<Warehouse, 'updatedAt'> => ({
|
|
id: '', code: '', name: '', province: '', city: '', address: '', contactName: '', contactPhone: '',
|
|
businessHours: '周一至周六 08:30-18:00', status: 'ACTIVE', remark: '',
|
|
})
|
|
const form = reactive(emptyForm())
|
|
const editorLabel = computed(() => form.id ? '编辑收货仓' : '新增收货仓')
|
|
const editorPermission = computed(() => 'warehouse:write')
|
|
const { captureBaseline } = useUnsavedWorkRegistration({
|
|
key: 'warehouse-editor',
|
|
label: editorLabel,
|
|
isOpen: editorOpen,
|
|
model: form,
|
|
requiredPermission: editorPermission,
|
|
})
|
|
|
|
const filteredRows = computed(() => store.warehouses.filter((warehouse) => {
|
|
const keyword = filters.keyword.trim().toLowerCase()
|
|
const matchesKeyword = !keyword || [warehouse.code, warehouse.name, warehouse.province, warehouse.city, warehouse.address]
|
|
.some((value) => value.toLowerCase().includes(keyword))
|
|
return matchesKeyword && (!filters.status || warehouse.status === filters.status)
|
|
}))
|
|
const pagedRows = computed(() => filteredRows.value.slice((page.value - 1) * pageSize, page.value * pageSize))
|
|
const metrics = computed(() => [
|
|
{ label: '全部收货仓', value: store.warehouses.length, note: '平台仓库档案' },
|
|
{ label: '启用仓库', value: store.activeWarehouses, note: '客户当前可选择' },
|
|
{ label: '暂停接单', value: store.warehouses.filter((item) => item.status === 'INACTIVE').length, note: '不出现在客户下单选项' },
|
|
{ label: '覆盖城市', value: new Set(store.warehouses.map((item) => item.city)).size, note: '国内收货区域' },
|
|
])
|
|
|
|
watch(filters, () => { page.value = 1 })
|
|
watch(
|
|
() => access.can('warehouse:write'),
|
|
(canWrite) => {
|
|
if (canWrite || !editorOpen.value) return
|
|
editorOpen.value = false
|
|
Object.assign(form, emptyForm())
|
|
},
|
|
{ flush: 'sync' },
|
|
)
|
|
|
|
function resetFilters() {
|
|
filters.keyword = ''
|
|
filters.status = ''
|
|
}
|
|
|
|
function openCreate() {
|
|
Object.assign(form, emptyForm())
|
|
captureBaseline()
|
|
editorOpen.value = true
|
|
}
|
|
|
|
function openEdit(warehouse: Warehouse) {
|
|
Object.assign(form, warehouse)
|
|
captureBaseline()
|
|
editorOpen.value = true
|
|
}
|
|
|
|
function saveWarehouse() {
|
|
if (!form.code.trim() || !form.name.trim() || !form.city.trim() || !form.address.trim()) {
|
|
ElMessage.warning('请填写仓库编码、名称、城市和详细地址')
|
|
return
|
|
}
|
|
try {
|
|
store.saveWarehouse({ ...form, id: form.id || undefined })
|
|
editorOpen.value = false
|
|
ElMessage.success(form.id ? '仓库资料已更新' : '收货仓已创建')
|
|
} catch (error) {
|
|
ElMessage.error(error instanceof Error ? error.message : '仓库保存失败')
|
|
}
|
|
}
|
|
|
|
async function toggleStatus(warehouse: Warehouse) {
|
|
const target = warehouse.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE'
|
|
const targetText = target === 'ACTIVE' ? '启用' : '停用'
|
|
try {
|
|
await ElMessageBox.confirm(
|
|
target === 'INACTIVE'
|
|
? `停用“${warehouse.name}”后,客户新建头程订单时将无法选择该仓库。`
|
|
: `确认重新启用“${warehouse.name}”供客户下单选择吗?`,
|
|
`${targetText}收货仓`,
|
|
{ confirmButtonText: `确认${targetText}`, cancelButtonText: '取消', type: target === 'INACTIVE' ? 'warning' : 'info' },
|
|
)
|
|
store.setWarehouseStatus(warehouse.id, target)
|
|
ElMessage.success(`仓库已${targetText}`)
|
|
} catch (error) {
|
|
if (error === 'cancel' || error === 'close') return
|
|
throw error
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="business-page">
|
|
<MetricStrip :items="metrics" />
|
|
|
|
<section class="surface-panel filter-panel" aria-label="仓库筛选">
|
|
<div class="filter-fields">
|
|
<el-input v-model="filters.keyword" clearable placeholder="仓库名称、编码、城市或地址" aria-label="仓库关键词">
|
|
<template #prefix><Search :size="16" /></template>
|
|
</el-input>
|
|
<el-select v-model="filters.status" clearable placeholder="全部状态" aria-label="仓库状态">
|
|
<el-option label="启用" value="ACTIVE" /><el-option label="停用" value="INACTIVE" />
|
|
</el-select>
|
|
</div>
|
|
<div class="filter-actions">
|
|
<el-button @click="resetFilters"><RotateCcw :size="16" />重置</el-button>
|
|
<el-button v-if="access.can('warehouse:write')" type="primary" @click="openCreate"><Plus :size="16" />新增仓库</el-button>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="surface-panel table-panel">
|
|
<div class="panel-heading"><div><h2>平台收货仓</h2><p>仅启用仓库会进入客户头程下单选项</p></div></div>
|
|
<el-table v-if="pagedRows.length" :data="pagedRows" row-key="id">
|
|
<el-table-column label="操作" width="180" fixed="left">
|
|
<template #default="{ row }"><div v-if="access.can('warehouse:write')" class="table-actions"><el-button link type="primary" @click="openEdit(row as Warehouse)"><Pencil :size="15" />编辑</el-button><el-button link :type="(row as Warehouse).status === 'ACTIVE' ? 'danger' : 'success'" @click="toggleStatus(row as Warehouse)"><Power :size="15" />{{ (row as Warehouse).status === 'ACTIVE' ? '停用' : '启用' }}</el-button></div><span v-else class="warehouse-subline">仅查看</span></template>
|
|
</el-table-column>
|
|
<el-table-column label="仓库" min-width="200">
|
|
<template #default="{ row }"><div class="warehouse-cell"><span><WarehouseIcon :size="18" /></span><div><strong>{{ (row as Warehouse).name }}</strong><small>{{ (row as Warehouse).code }}</small></div></div></template>
|
|
</el-table-column>
|
|
<el-table-column label="所在城市" min-width="130"><template #default="{ row }">{{ (row as Warehouse).province }} · {{ (row as Warehouse).city }}</template></el-table-column>
|
|
<el-table-column label="收货地址" min-width="260"><template #default="{ row }"><div class="warehouse-address"><MapPin :size="15" />{{ (row as Warehouse).address }}</div></template></el-table-column>
|
|
<el-table-column label="联系人" min-width="150"><template #default="{ row }"><strong>{{ (row as Warehouse).contactName || '-' }}</strong><span class="warehouse-subline">{{ (row as Warehouse).contactPhone || '-' }}</span></template></el-table-column>
|
|
<el-table-column label="收货时间" prop="businessHours" min-width="190" />
|
|
<el-table-column label="状态" width="100"><template #default="{ row }"><el-tag :type="(row as Warehouse).status === 'ACTIVE' ? 'success' : 'info'" effect="light">{{ (row as Warehouse).status === 'ACTIVE' ? '启用' : '停用' }}</el-tag></template></el-table-column>
|
|
</el-table>
|
|
<el-empty v-else description="没有符合条件的仓库" />
|
|
<div class="table-footer"><span>共 {{ filteredRows.length }} 个收货仓</span><el-pagination v-model:current-page="page" :page-size="pageSize" :total="filteredRows.length" layout="prev, pager, next" /></div>
|
|
</section>
|
|
|
|
<el-drawer v-model="editorOpen" :title="editorLabel" size="620px" class="business-drawer editor-drawer" :close-on-click-modal="false">
|
|
<el-form label-position="top" class="drawer-form">
|
|
<div class="form-section"><h3>仓库标识</h3><div class="form-grid two-cols"><el-form-item label="仓库编码" required><el-input v-model="form.code" maxlength="32" placeholder="例如 CN-NB-01" /></el-form-item><el-form-item label="仓库名称" required><el-input v-model="form.name" maxlength="100" placeholder="客户下单时显示的名称" /></el-form-item><el-form-item label="状态"><el-radio-group v-model="form.status"><el-radio-button value="ACTIVE">启用</el-radio-button><el-radio-button value="INACTIVE">停用</el-radio-button></el-radio-group></el-form-item></div></div>
|
|
<div class="form-section"><h3>地址与收货</h3><div class="form-grid two-cols"><el-form-item label="省份"><el-input v-model="form.province" maxlength="40" /></el-form-item><el-form-item label="城市" required><el-input v-model="form.city" maxlength="40" /></el-form-item><el-form-item label="联系人"><el-input v-model="form.contactName" maxlength="50" /></el-form-item><el-form-item label="联系电话"><el-input v-model="form.contactPhone" maxlength="30" /></el-form-item><el-form-item label="收货时间"><el-input v-model="form.businessHours" maxlength="100" /></el-form-item></div><el-form-item label="详细地址" required><el-input v-model="form.address" maxlength="300" type="textarea" :rows="3" /></el-form-item></div>
|
|
<div class="form-section"><h3>备注</h3><el-form-item label="客户可理解的收货说明"><el-input v-model="form.remark" maxlength="500" type="textarea" :rows="3" show-word-limit /></el-form-item></div>
|
|
</el-form>
|
|
<template #footer><div class="drawer-footer"><el-button @click="editorOpen = false">取消</el-button><el-button type="primary" @click="saveWarehouse">保存仓库</el-button></div></template>
|
|
</el-drawer>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.warehouse-cell { display: flex; align-items: center; gap: 10px; }
|
|
.warehouse-cell > span { display: grid; width: 34px; height: 34px; flex: 0 0 34px; place-items: center; border-radius: 7px; background: #eef3fa; color: var(--cc-primary); }
|
|
.warehouse-cell > div { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
|
|
.warehouse-cell small, .warehouse-subline { display: block; color: var(--cc-muted); font-size: 11px; }
|
|
.warehouse-address { display: flex; align-items: flex-start; gap: 7px; line-height: 1.55; }
|
|
.warehouse-address svg { flex: 0 0 15px; margin-top: 2px; color: var(--cc-muted); }
|
|
</style>
|