优化
This commit is contained in:
@@ -0,0 +1,565 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
|
||||
import { Boxes, Eye, Pencil, Plus, Trash2, Warehouse as WarehouseIcon } from '@lucide/vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
import { listPlatformTenants as listPlatformCustomers, type PlatformTenantResource as PlatformCustomerResource } from '@/modules/iam'
|
||||
import { listPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import {
|
||||
createPlatformPallet,
|
||||
deletePlatformPallet,
|
||||
getPlatformPallet,
|
||||
listPlatformPallets,
|
||||
updatePlatformPallet,
|
||||
type PlatformPalletResource,
|
||||
type SavePlatformPalletRequest,
|
||||
} from '../api/platformPallets'
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
const customers = ref<PlatformCustomerResource[]>([])
|
||||
const customerLoading = ref(false)
|
||||
const customerError = ref<ApiErrorDetails | null>(null)
|
||||
const selectedCustomerId = ref('')
|
||||
const selectedWarehouseId = ref('')
|
||||
const warehouses = ref<PlatformWarehouseResource[]>([])
|
||||
const warehouseLoading = ref(false)
|
||||
const warehouseError = ref<ApiErrorDetails | null>(null)
|
||||
const rows = ref<PlatformPalletResource[]>([])
|
||||
const loading = ref(false)
|
||||
const loadError = ref<ApiErrorDetails | null>(null)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const editorOpen = ref(false)
|
||||
const detailOpen = ref(false)
|
||||
const detail = ref<PlatformPalletResource | null>(null)
|
||||
const detailLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
const deletingIds = ref(new Set<string>())
|
||||
const formRef = ref<FormInstance>()
|
||||
let warehouseSequence = 0
|
||||
let palletSequence = 0
|
||||
|
||||
type PalletFilters = QueryModel & { customerId: string; warehouseId: string; palletCode: string; palletType: string }
|
||||
type AppliedPalletFilters = Pick<PalletFilters, 'palletCode' | 'palletType'>
|
||||
const filters = ref<PalletFilters>({ customerId: '', warehouseId: '', palletCode: '', palletType: '' })
|
||||
const appliedFilters = ref<AppliedPalletFilters>({ palletCode: '', palletType: '' })
|
||||
const palletTypeOptions = ['木托盘', '塑料托盘', '金属托盘', '纸托盘', '免熏蒸托盘']
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{
|
||||
key: 'customerId',
|
||||
type: 'select',
|
||||
width: 260,
|
||||
placeholder: customerLoading.value ? '正在加载客户' : customerError.value ? '客户列表加载失败' : '全部客户',
|
||||
options: customers.value.map((customer) => ({
|
||||
label: `${customer.tenantName}(${customer.tenantCode || customer.id})`,
|
||||
value: String(customer.id),
|
||||
})),
|
||||
props: { filterable: true, disabled: contextLocked.value || customerLoading.value || Boolean(customerError.value), 'aria-label': '所属企业' },
|
||||
},
|
||||
{
|
||||
key: 'warehouseId',
|
||||
type: 'select',
|
||||
width: 240,
|
||||
placeholder: warehouseLoading.value ? '正在加载所属仓库' : warehouseError.value ? '所属仓库加载失败' : '全部仓库',
|
||||
options: warehouses.value.map((warehouse) => ({
|
||||
label: `${warehouse.warehouseName}(${warehouse.warehouseCode})`,
|
||||
value: String(warehouse.id),
|
||||
disabled: warehouse.status !== 'ACTIVE',
|
||||
})),
|
||||
props: {
|
||||
filterable: true,
|
||||
disabled: contextLocked.value || !selectedCustomerId.value || warehouseLoading.value || Boolean(warehouseError.value),
|
||||
'aria-label': '所属仓库',
|
||||
},
|
||||
},
|
||||
{ key: 'palletCode', type: 'input', width: 240, placeholder: '托盘编码(模糊查询)', props: { maxlength: 50, 'aria-label': '托盘编码' } },
|
||||
{
|
||||
key: 'palletType',
|
||||
type: 'select',
|
||||
width: 180,
|
||||
placeholder: '全部托盘类型',
|
||||
options: palletTypeOptions.map((value) => ({ label: value, value })),
|
||||
props: { filterable: true, allowCreate: true, defaultFirstOption: true, 'aria-label': '托盘类型' },
|
||||
},
|
||||
])
|
||||
|
||||
const emptyForm = () => ({ id: '', customerId: '', warehouseId: '', palletCode: '', palletType: '', capacityCbm: null as number | null, remark: '' })
|
||||
const form = reactive(emptyForm())
|
||||
const rules: FormRules<typeof form> = {
|
||||
palletCode: [
|
||||
{ required: true, message: '请输入托盘编码', trigger: 'blur' },
|
||||
{ max: 50, message: '托盘编码不能超过 50 个字符', trigger: 'blur' },
|
||||
],
|
||||
palletType: [
|
||||
{ required: true, message: '请输入托盘类型', trigger: 'blur' },
|
||||
{ max: 32, message: '托盘类型不能超过 32 个字符', trigger: 'blur' },
|
||||
],
|
||||
capacityCbm: [{
|
||||
validator: (_rule, value, callback) => {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) callback(new Error('请输入大于 0 的托盘容积'))
|
||||
else callback()
|
||||
},
|
||||
trigger: 'change',
|
||||
}],
|
||||
remark: [{ max: 500, message: '备注不能超过 500 个字符', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const currentWarehouse = computed(() => warehouses.value.find((item) => String(item.id) === selectedWarehouseId.value) || null)
|
||||
const editorWarehouse = computed(() => warehouses.value.find((item) => String(item.id) === form.warehouseId) || null)
|
||||
const currentCustomer = computed(() => customers.value.find((item) => String(item.id) === selectedCustomerId.value) || null)
|
||||
const contextReady = computed(() => Boolean(selectedCustomerId.value && selectedWarehouseId.value && currentWarehouse.value))
|
||||
const editorTitle = computed(() => form.id ? '编辑托盘' : '新增托盘')
|
||||
const editorPermission = computed(() => form.id ? 'platform:pallet:edit' : 'platform:pallet:create')
|
||||
const contextLocked = computed(() => editorOpen.value || detailOpen.value || saving.value || deletingIds.value.size > 0)
|
||||
const filtered = computed(() => Object.values(appliedFilters.value).some((value) => String(value || '').trim()))
|
||||
|
||||
const { captureBaseline } = useUnsavedWorkRegistration({
|
||||
key: 'pallet-editor',
|
||||
label: editorTitle,
|
||||
isOpen: editorOpen,
|
||||
model: form,
|
||||
requiredPermission: editorPermission,
|
||||
})
|
||||
|
||||
const columns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', minWidth: 224, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'pallet', label: '托盘编码', minWidth: 190, slotName: 'pallet' },
|
||||
{ prop: 'palletType', label: '托盘类型', minWidth: 140 },
|
||||
{ key: 'capacity', label: '托盘容积(m³)', minWidth: 160, slotName: 'capacity' },
|
||||
{ key: 'warehouse', label: '所属仓库', minWidth: 210, slotName: 'warehouse' },
|
||||
{ key: 'remark', label: '备注', minWidth: 220, slotName: 'remark' },
|
||||
{ prop: 'updatedAt', label: '更新时间', minWidth: 160, formatter: (row: PlatformPalletResource) => formatDateTime(row.updatedAt) },
|
||||
]
|
||||
|
||||
function apiId(value: string) {
|
||||
const numeric = Number(value)
|
||||
return Number.isSafeInteger(numeric) ? numeric : value
|
||||
}
|
||||
|
||||
function formatDateTime(value?: string | null) {
|
||||
return value?.replace('T', ' ').slice(0, 16) || '—'
|
||||
}
|
||||
|
||||
function formatCapacity(value: number | string) {
|
||||
const numeric = Number(value)
|
||||
if (!Number.isFinite(numeric)) return String(value)
|
||||
return `${numeric.toLocaleString('zh-CN', { maximumFractionDigits: 6 })} m³`
|
||||
}
|
||||
|
||||
function warehouseFor(warehouseId: number | string) {
|
||||
return warehouses.value.find((item) => String(item.id) === String(warehouseId)) || null
|
||||
}
|
||||
|
||||
function customerLabel(customerId: number | string) {
|
||||
const customer = customers.value.find((item) => String(item.id) === String(customerId))
|
||||
return customer ? `${customer.tenantName}(${customer.tenantCode || customer.id})` : '—'
|
||||
}
|
||||
|
||||
function errorMessage(details: ApiErrorDetails) {
|
||||
return details.requestId ? `${details.message}(requestId: ${details.requestId})` : details.message
|
||||
}
|
||||
|
||||
async function loadCustomers() {
|
||||
customerLoading.value = true
|
||||
customerError.value = null
|
||||
try {
|
||||
const records: PlatformCustomerResource[] = []
|
||||
let currentPage = 1
|
||||
let pages = 1
|
||||
do {
|
||||
const result = await listPlatformCustomers({ page: currentPage, size: 200 })
|
||||
records.push(...result.records)
|
||||
pages = Math.max(1, result.pages)
|
||||
currentPage += 1
|
||||
} while (currentPage <= pages)
|
||||
customers.value = records
|
||||
} catch (error) {
|
||||
customers.value = []
|
||||
customerError.value = getApiErrorDetails(error, '所属企业列表加载失败,请稍后重试')
|
||||
} finally {
|
||||
customerLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWarehouses(customerId = selectedCustomerId.value) {
|
||||
const sequence = ++warehouseSequence
|
||||
selectedWarehouseId.value = ''
|
||||
filters.value = { ...filters.value, warehouseId: '' }
|
||||
warehouses.value = []
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
warehouseError.value = null
|
||||
if (!customerId) return
|
||||
warehouseLoading.value = true
|
||||
try {
|
||||
const result = await listPlatformWarehouses(apiId(customerId), 1, 200)
|
||||
if (sequence !== warehouseSequence) return
|
||||
warehouses.value = result.records
|
||||
} catch (error) {
|
||||
if (sequence !== warehouseSequence) return
|
||||
warehouseError.value = getApiErrorDetails(error, '仓库列表加载失败,请稍后重试')
|
||||
} finally {
|
||||
if (sequence === warehouseSequence) warehouseLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPallets() {
|
||||
const customerId = selectedCustomerId.value
|
||||
const warehouseId = selectedWarehouseId.value
|
||||
const sequence = ++palletSequence
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
loadError.value = null
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await listPlatformPallets({
|
||||
page: page.value,
|
||||
size: pageSize.value,
|
||||
...(customerId ? { tenantId: apiId(customerId) } : {}),
|
||||
...(warehouseId ? { warehouseId: apiId(warehouseId) } : {}),
|
||||
...(appliedFilters.value.palletCode ? { palletCode: appliedFilters.value.palletCode } : {}),
|
||||
...(appliedFilters.value.palletType ? { palletType: appliedFilters.value.palletType } : {}),
|
||||
})
|
||||
if (sequence !== palletSequence) return
|
||||
rows.value = result.records
|
||||
total.value = result.total
|
||||
} catch (error) {
|
||||
if (sequence !== palletSequence) return
|
||||
loadError.value = getApiErrorDetails(error, '托盘列表加载失败,请稍后重试')
|
||||
} finally {
|
||||
if (sequence === palletSequence) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilters(model: QueryModel) {
|
||||
selectedCustomerId.value = String(model.customerId || '')
|
||||
selectedWarehouseId.value = String(model.warehouseId || '')
|
||||
appliedFilters.value = { palletCode: String(model.palletCode || ''), palletType: String(model.palletType || '') }
|
||||
if (page.value === 1) void loadPallets()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
function resetFilters() {
|
||||
selectedCustomerId.value = ''
|
||||
selectedWarehouseId.value = ''
|
||||
appliedFilters.value = { palletCode: '', palletType: '' }
|
||||
if (page.value === 1) void loadPallets()
|
||||
else page.value = 1
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
if (!contextReady.value) {
|
||||
ElMessage.warning('请先选择所属企业和仓库')
|
||||
return
|
||||
}
|
||||
Object.assign(form, emptyForm())
|
||||
form.customerId = selectedCustomerId.value
|
||||
form.warehouseId = selectedWarehouseId.value
|
||||
editorOpen.value = true
|
||||
captureBaseline()
|
||||
void formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
function assignForm(resource: PlatformPalletResource) {
|
||||
Object.assign(form, {
|
||||
id: String(resource.id),
|
||||
customerId: String(resource.tenantId),
|
||||
warehouseId: String(resource.warehouseId),
|
||||
palletCode: resource.palletCode,
|
||||
palletType: resource.palletType,
|
||||
capacityCbm: Number(resource.capacityCbm),
|
||||
remark: resource.remark || '',
|
||||
})
|
||||
editorOpen.value = true
|
||||
captureBaseline()
|
||||
void formRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
async function fetchDetail(row: PlatformPalletResource) {
|
||||
return getPlatformPallet(apiId(String(row.tenantId)), apiId(String(row.warehouseId)), row.id)
|
||||
}
|
||||
|
||||
async function openDetail(row: PlatformPalletResource) {
|
||||
detailLoading.value = true
|
||||
detailOpen.value = true
|
||||
detail.value = null
|
||||
try {
|
||||
detail.value = await fetchDetail(row)
|
||||
} catch (error) {
|
||||
detailOpen.value = false
|
||||
ElMessage.error(errorMessage(getApiErrorDetails(error, '托盘详情加载失败,请稍后重试')))
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openEdit(row: PlatformPalletResource) {
|
||||
try {
|
||||
assignForm(await fetchDetail(row))
|
||||
} catch (error) {
|
||||
ElMessage.error(errorMessage(getApiErrorDetails(error, '托盘详情加载失败,请稍后重试')))
|
||||
}
|
||||
}
|
||||
|
||||
async function savePallet() {
|
||||
if ((!form.id && !contextReady.value) || !form.customerId || !form.warehouseId || !await formRef.value?.validate().catch(() => false) || form.capacityCbm === null) return
|
||||
saving.value = true
|
||||
const payload: SavePlatformPalletRequest = {
|
||||
warehouseId: apiId(form.warehouseId),
|
||||
palletCode: form.palletCode.trim(),
|
||||
palletType: form.palletType.trim(),
|
||||
capacityCbm: form.capacityCbm,
|
||||
remark: form.remark.trim() || null,
|
||||
}
|
||||
try {
|
||||
const customerId = apiId(form.customerId)
|
||||
if (form.id) await updatePlatformPallet(customerId, apiId(form.warehouseId), form.id, payload)
|
||||
else await createPlatformPallet(customerId, payload)
|
||||
ElMessage.success(form.id ? '托盘已更新' : '托盘已新增')
|
||||
editorOpen.value = false
|
||||
await loadPallets()
|
||||
} catch (error) {
|
||||
ElMessage.error(errorMessage(getApiErrorDetails(error, '托盘保存失败,请稍后重试')))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function removePallet(row: PlatformPalletResource) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除托盘“${row.palletCode}”吗?已预占或已占用的托盘无法删除。`,
|
||||
'删除托盘',
|
||||
{ confirmButtonText: '删除', cancelButtonText: '取消', type: 'warning', confirmButtonClass: 'el-button--danger' },
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
const id = String(row.id)
|
||||
deletingIds.value = new Set([...deletingIds.value, id])
|
||||
try {
|
||||
await deletePlatformPallet(apiId(String(row.tenantId)), apiId(String(row.warehouseId)), row.id)
|
||||
ElMessage.success('托盘已删除')
|
||||
if (rows.value.length === 1 && page.value > 1) page.value -= 1
|
||||
else await loadPallets()
|
||||
} catch (error) {
|
||||
ElMessage.error(errorMessage(getApiErrorDetails(error, '托盘删除失败,请稍后重试')))
|
||||
} finally {
|
||||
const next = new Set(deletingIds.value)
|
||||
next.delete(id)
|
||||
deletingIds.value = next
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => filters.value.customerId, (customerId) => {
|
||||
selectedCustomerId.value = String(customerId || '')
|
||||
page.value = 1
|
||||
void loadWarehouses(selectedCustomerId.value)
|
||||
})
|
||||
watch(() => filters.value.warehouseId, (warehouseId) => {
|
||||
selectedWarehouseId.value = String(warehouseId || '')
|
||||
page.value = 1
|
||||
rows.value = []
|
||||
total.value = 0
|
||||
loadError.value = null
|
||||
})
|
||||
watch([page, pageSize], ([nextPage, nextSize], [previousPage, previousSize]) => {
|
||||
if (nextPage === previousPage && nextSize === previousSize) return
|
||||
void loadPallets()
|
||||
})
|
||||
watch(() => access.can(editorPermission.value), (canWrite) => {
|
||||
if (!canWrite && editorOpen.value) editorOpen.value = false
|
||||
}, { flush: 'sync' })
|
||||
onMounted(() => {
|
||||
void loadCustomers()
|
||||
void loadPallets()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="pallet-page">
|
||||
<LoadErrorState
|
||||
v-if="customerError"
|
||||
title="所属企业列表暂时无法加载"
|
||||
:message="customerError.message"
|
||||
:loading="customerLoading"
|
||||
@retry="loadCustomers"
|
||||
/>
|
||||
|
||||
<QueryToolbar
|
||||
v-model="filters"
|
||||
class="surface-panel pallet-filter-toolbar"
|
||||
ariaLabel="托盘筛选"
|
||||
:fields="queryFields"
|
||||
:search-on-reset="false"
|
||||
@search="applyFilters"
|
||||
@reset="resetFilters"
|
||||
/>
|
||||
|
||||
<LoadErrorState
|
||||
v-if="warehouseError"
|
||||
title="仓库列表暂时无法加载"
|
||||
:message="warehouseError.message"
|
||||
:loading="warehouseLoading"
|
||||
@retry="loadWarehouses()"
|
||||
/>
|
||||
|
||||
<LoadErrorState
|
||||
v-if="loadError"
|
||||
title="托盘列表暂时无法加载"
|
||||
:message="loadError.message"
|
||||
:loading="loading"
|
||||
@retry="loadPallets"
|
||||
/>
|
||||
|
||||
<section class="table-panel surface-panel" aria-label="托盘列表区域">
|
||||
<div class="panel-heading">
|
||||
<div><h2>托盘列表</h2><p>{{ currentCustomer?.tenantName || '全部客户' }} · {{ currentWarehouse?.warehouseName || '全部仓库' }} · 共 {{ total }} 个托盘</p></div>
|
||||
<el-button v-if="access.can('platform:pallet:create')" type="primary" :disabled="!contextReady" @click="openCreate"><Plus :size="16" />新增托盘</el-button>
|
||||
</div>
|
||||
<DataTable
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:data="rows"
|
||||
:columns="columns"
|
||||
:loading="loading || warehouseLoading"
|
||||
:total="total"
|
||||
row-key="id"
|
||||
:empty-text="filtered ? '暂无符合筛选条件的托盘' : selectedCustomerId ? '当前客户暂无托盘' : '暂无托盘数据'"
|
||||
aria-label="托盘列表"
|
||||
>
|
||||
<template #actions="{ row }">
|
||||
<div class="table-actions pallet-row-actions">
|
||||
<el-button link type="primary" title="查看托盘" @click="openDetail(row as PlatformPalletResource)"><Eye :size="15" />查看</el-button>
|
||||
<el-button v-if="access.can('platform:pallet:edit')" link type="primary" title="编辑托盘" @click="openEdit(row as PlatformPalletResource)"><Pencil :size="15" />编辑</el-button>
|
||||
<el-button
|
||||
v-if="access.can('platform:pallet:delete')"
|
||||
link
|
||||
type="danger"
|
||||
title="删除托盘"
|
||||
:loading="deletingIds.has(String((row as PlatformPalletResource).id))"
|
||||
@click="removePallet(row as PlatformPalletResource)"
|
||||
><Trash2 :size="15" />删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #pallet="{ row }">
|
||||
<div class="pallet-cell"><span><Boxes :size="18" /></span><strong>{{ (row as PlatformPalletResource).palletCode }}</strong></div>
|
||||
</template>
|
||||
<template #capacity="{ row }"><strong>{{ formatCapacity((row as PlatformPalletResource).capacityCbm) }}</strong></template>
|
||||
<template #warehouse="{ row }"><span>{{ warehouseFor((row as PlatformPalletResource).warehouseId)?.warehouseName || '—' }}</span></template>
|
||||
<template #remark="{ row }"><span class="remark-text">{{ (row as PlatformPalletResource).remark || '—' }}</span></template>
|
||||
</DataTable>
|
||||
</section>
|
||||
|
||||
<el-drawer v-model="detailOpen" title="托盘详情" size="520px" class="business-drawer">
|
||||
<div v-loading="detailLoading" class="detail-body">
|
||||
<template v-if="detail">
|
||||
<div class="detail-hero"><span><Boxes :size="22" /></span><div><h2>{{ detail.palletCode }}</h2><p>{{ detail.palletType }}</p></div></div>
|
||||
<h3 class="detail-section-title">归属信息</h3>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="所属企业">{{ customerLabel(detail.tenantId) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属仓库">{{ warehouseFor(detail.warehouseId)?.warehouseName || '—' }}({{ warehouseFor(detail.warehouseId)?.warehouseCode || '—' }})</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<h3 class="detail-section-title">托盘规格</h3>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="托盘编码">{{ detail.palletCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="托盘类型">{{ detail.palletType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="托盘容积">{{ formatCapacity(detail.capacityCbm) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注">{{ detail.remark || '—' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<h3 class="detail-section-title">记录信息</h3>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="创建时间">{{ formatDateTime(detail.createdAt) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">{{ formatDateTime(detail.updatedAt) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer><el-button @click="detailOpen = false">关闭</el-button></template>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer v-model="editorOpen" :title="editorTitle" size="520px" class="business-drawer" :close-on-click-modal="false">
|
||||
<div class="editor-context">
|
||||
<WarehouseIcon :size="18" />
|
||||
<div><strong>{{ editorWarehouse?.warehouseName || '当前仓库' }}({{ editorWarehouse?.warehouseCode || form.warehouseId }})</strong><span>所属企业 {{ customerLabel(form.customerId) }} · 托盘保存后不可跨仓移动</span></div>
|
||||
</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="editor-form">
|
||||
<el-form-item label="托盘编码" prop="palletCode" required>
|
||||
<el-input v-model="form.palletCode" maxlength="50" show-word-limit placeholder="例如 TP-001" />
|
||||
</el-form-item>
|
||||
<el-form-item label="托盘类型" prop="palletType" required>
|
||||
<el-select
|
||||
v-model="form.palletType"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
placeholder="请选择或输入托盘类型"
|
||||
aria-label="编辑托盘类型"
|
||||
>
|
||||
<el-option v-for="type in palletTypeOptions" :key="type" :label="type" :value="type" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="托盘容积(m³)" prop="capacityCbm" required>
|
||||
<el-input-number v-model="form.capacityCbm" :min="0.000001" :max="999999999999.999999" :precision="6" :step="0.1" controls-position="right" aria-label="标准容积" />
|
||||
<p class="field-help">用于服务端按总体积计算所需托盘数量;请输入托盘实际可用容积。</p>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="可填写规格、材质或现场识别说明" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="drawer-footer"><el-button :disabled="saving" @click="editorOpen = false">取消</el-button><el-button type="primary" :loading="saving" @click="savePallet">保存托盘</el-button></div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pallet-page { display: grid; min-width: 0; gap: 16px; }
|
||||
.surface-panel { overflow: hidden; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; }
|
||||
.table-panel { min-width: 0; }
|
||||
.panel-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 14px 18px; border-bottom: 1px solid var(--cc-border); }
|
||||
.panel-heading h2 { margin: 0; font-size: 16px; }
|
||||
.panel-heading p { margin: 4px 0 0; color: var(--cc-muted); font-size: 12px; }
|
||||
.context-empty { padding: 56px 20px; color: var(--cc-muted); text-align: center; }
|
||||
.pallet-row-actions { gap: 8px; white-space: nowrap; }
|
||||
.pallet-row-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
||||
.pallet-cell { display: flex; align-items: center; gap: 10px; }
|
||||
.pallet-cell > span, .detail-hero > span { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; border-radius: 8px; background: var(--el-color-primary-light-9); color: var(--cc-primary); }
|
||||
.pallet-cell > span { width: 34px; height: 34px; }
|
||||
.remark-text { display: -webkit-box; overflow: hidden; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-height: 1.55; }
|
||||
.detail-body { min-height: 180px; }
|
||||
.detail-hero { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
||||
.detail-hero > span { width: 46px; height: 46px; }
|
||||
.detail-hero h2, .detail-hero p { margin: 0; }
|
||||
.detail-hero h2 { font-size: 18px; }
|
||||
.detail-hero p { margin-top: 4px; color: var(--cc-muted); font-size: 13px; }
|
||||
.detail-section-title { margin: 20px 0 8px; color: var(--cc-text); font-size: 14px; }
|
||||
.detail-section-title:first-of-type { margin-top: 0; }
|
||||
.editor-context { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; padding: 12px; border: 1px solid var(--el-color-primary-light-7); border-radius: 8px; background: var(--el-color-primary-light-9); color: var(--cc-primary); }
|
||||
.editor-context div { display: grid; gap: 2px; }
|
||||
.editor-context span { color: var(--cc-muted); font-size: 12px; }
|
||||
.editor-form :deep(.el-input-number), .editor-form :deep(.el-select) { width: 100%; }
|
||||
.field-help { margin: 6px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }
|
||||
.drawer-footer { display: flex; justify-content: flex-end; gap: 8px; }
|
||||
.drawer-footer :deep(.el-button + .el-button) { margin-left: 0; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.panel-heading { align-items: stretch; flex-direction: column; }
|
||||
.panel-heading > .el-button { width: 100%; min-height: 44px; }
|
||||
.pallet-row-actions :deep(.el-button) { min-height: 36px; padding-inline: 5px; }
|
||||
.drawer-footer { display: grid; grid-template-columns: 1fr 1fr; }
|
||||
.drawer-footer :deep(.el-button) { min-height: 44px; margin-left: 0; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.pallet-filter-toolbar :deep(.query-toolbar__filters) { width: 100%; flex-direction: column; }
|
||||
.pallet-filter-toolbar :deep(.query-toolbar__filters .el-input),
|
||||
.pallet-filter-toolbar :deep(.query-toolbar__filters .el-select),
|
||||
.pallet-filter-toolbar :deep(.query-toolbar__filters .el-date-editor) { width: 100% !important; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user