style(admin): unify dialog layouts and dense readonly presentation

This commit is contained in:
2026-10-20 10:11:31 +08:00
parent 7ff7c9914f
commit 3267679758
214 changed files with 10931 additions and 2654 deletions
@@ -51,6 +51,15 @@ const columns: DataTableColumn[] = [
]
const dialogOpen = ref(false)
const dialogMode = ref<'create' | 'edit'>('create')
const detailLoading = ref(false)
const editReady = ref(false)
const editingPallet = ref<Pick<PalletListItem, 'id' | 'warehouseId' | 'code'> | null>(null)
const dialogError = ref<ApiErrorDetails | null>(null)
const dialogErrorKind = ref<'read' | 'save'>('read')
const dialogBusy = computed(() => saving.value || detailLoading.value || contextLoading.value)
const dialogAllowed = computed(() => access.can(dialogMode.value === 'create' ? permission.palletCreate : permission.palletEdit))
const contextFailed = computed(() => Boolean(loadError.value && loadErrorKind.value === 'context'))
const formLocked = computed(() => dialogBusy.value || !dialogAllowed.value || contextFailed.value || (dialogMode.value === 'edit' && !editReady.value))
const form = reactive({ id: '', warehouseId: '', fbaWarehouseCode: '', code: '', type: 'WOOD', volume: 2, purchasePrice: 0, status: 'IDLE', remark: '', version: 0 })
function text(value: unknown) { const normalized = String(value ?? '').trim(); return normalized || '—' }
@@ -96,27 +105,40 @@ function retryLoad() {
}
function openCreate() {
dialogMode.value = 'create'; Object.assign(form, { id: '', warehouseId: '', fbaWarehouseCode: '', code: '', type: 'WOOD', volume: 2, purchasePrice: 0, status: 'IDLE', remark: '', version: 0 }); mutationError.value = null; dialogOpen.value = true
if (dialogOpen.value || dialogBusy.value || !access.can(permission.palletCreate)) return
dialogMode.value = 'create'; editingPallet.value = null; editReady.value = false; dialogError.value = null
Object.assign(form, { id: '', warehouseId: '', fbaWarehouseCode: '', code: '', type: 'WOOD', volume: 2, purchasePrice: 0, status: 'IDLE', remark: '', version: 0 }); mutationError.value = null; dialogOpen.value = true
}
async function openEdit(source: object) {
const row = source as PalletListItem; saving.value = true; mutationError.value = null
if (dialogOpen.value || dialogBusy.value || !access.can(permission.palletEdit)) return
const row = source as PalletListItem
editingPallet.value = { id: row.id, warehouseId: row.warehouseId, code: row.code }
dialogMode.value = 'edit'; editReady.value = false; dialogError.value = null; mutationError.value = null; dialogOpen.value = true
await loadEdit()
}
async function loadEdit() {
const row = editingPallet.value
if (!dialogOpen.value || dialogMode.value !== 'edit' || !row || dialogBusy.value || !dialogAllowed.value) return
detailLoading.value = true; editReady.value = false; dialogError.value = null; dialogErrorKind.value = 'read'
try {
const detail = await getPallet(row.warehouseId, row.id)
dialogMode.value = 'edit'; Object.assign(form, { id: String(detail.id), warehouseId: String(detail.warehouseId), fbaWarehouseCode: detail.fbaWarehouseCode, code: detail.palletCode, type: detail.palletType, volume: number(detail.capacityCbm), purchasePrice: number(detail.unitPrice), status: detail.status, remark: detail.remark || '', version: detail.version }); dialogOpen.value = true
} catch (error) { mutationError.value = getApiErrorDetails(error, '托盘详情加载失败,请重试') }
finally { saving.value = false }
Object.assign(form, { id: String(detail.id), warehouseId: String(detail.warehouseId), fbaWarehouseCode: detail.fbaWarehouseCode, code: detail.palletCode, type: detail.palletType, volume: number(detail.capacityCbm), purchasePrice: number(detail.unitPrice), status: detail.status, remark: detail.remark || '', version: detail.version }); editReady.value = true
} catch (error) { dialogError.value = getApiErrorDetails(error, '托盘详情加载失败,请重试') }
finally { detailLoading.value = false }
}
async function save() {
if (!dialogOpen.value || formLocked.value || !access.can(dialogMode.value === 'create' ? permission.palletCreate : permission.palletEdit)) return
if (!form.warehouseId || !form.code.trim() || !form.type || !form.fbaWarehouseCode || form.volume <= 0 || form.purchasePrice < 0 || !form.status) { ElMessage.warning('请完整填写仓库、托盘编码、类型、容积、采购价格、所属 FBA 仓和状态'); return }
saving.value = true; mutationError.value = null
saving.value = true; dialogError.value = null; dialogErrorKind.value = 'save'
const payload = { palletCode: form.code.trim(), palletType: form.type, fbaWarehouseCode: form.fbaWarehouseCode, capacityCbm: form.volume, unitPrice: form.purchasePrice, status: form.status, locationId: null, remark: form.remark.trim() || null }
try {
if (dialogMode.value === 'edit') await updatePallet(form.warehouseId, form.id, { ...payload, version: form.version })
else await createPallet(form.warehouseId, payload)
dialogOpen.value = false; ElMessage.success(dialogMode.value === 'edit' ? '托盘已更新' : '托盘已新增'); await load()
} catch (error) { mutationError.value = getApiErrorDetails(error, '托盘保存失败,请重试') }
} catch (error) { dialogError.value = getApiErrorDetails(error, '托盘保存失败,请重试') }
finally { saving.value = false }
}
@@ -154,30 +176,35 @@ onMounted(initialize)
<template #notices><template v-if="loadError"><LoadErrorState title="托盘数据加载失败" :message="loadError.message" :loading="loading || contextLoading" @retry="retryLoad"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template></template>
<template #toolbar-actions><el-button v-if="access.can(permission.palletCreate)" type="primary" @click="openCreate"><Plus :size="16" />新增托盘</el-button></template>
<template #actions="{ row }"><div class="density-actions row-actions admin-text-actions"><el-button class="density-action" data-tone="neutral" size="small" @click="openDetail(row)">查看</el-button><el-button v-if="access.can(permission.palletList)" class="density-action" data-tone="neutral" size="small" :loading="printingId === row.id" :disabled="Boolean(printingId) && printingId !== row.id" @click="printPallet(row)">打印</el-button><el-button class="density-action" data-tone="neutral" v-if="access.can(permission.palletEdit)" size="small" @click="openEdit(row)">编辑</el-button></div></template>
<template #toolbar-actions><el-button v-if="access.can(permission.palletCreate)" type="primary" :disabled="dialogOpen || dialogBusy" @click="openCreate"><Plus :size="16" />新增托盘</el-button></template>
<template #actions="{ row }"><div class="density-actions row-actions admin-text-actions"><el-button class="density-action" data-tone="neutral" size="small" @click="openDetail(row)">查看</el-button><el-button v-if="access.can(permission.palletList)" class="density-action" data-tone="neutral" size="small" :loading="printingId === row.id" :disabled="Boolean(printingId) && printingId !== row.id" @click="printPallet(row)">打印</el-button><el-button class="density-action" data-tone="neutral" v-if="access.can(permission.palletEdit)" size="small" :disabled="dialogOpen || dialogBusy" @click="openEdit(row)">编辑</el-button></div></template>
<template #pagination-left>共 {{ total }} 条托盘</template>
</DataTable></section>
</ListPageLayout>
<AppDialog class="admin-density-dialog" v-model="dialogOpen" :title="dialogMode === 'create' ? '新增托盘' : '编辑托盘'" width="min(760px, calc(100vw - 28px))" confirm-text="保存" :confirm-loading="saving" @confirm="save">
<el-form class="density-form" label-position="top"><el-row :gutter="16">
<el-col :span="12"><el-form-item label="托盘编码" required><el-input v-model="form.code" maxlength="128" placeholder="请输入托盘编码" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="托盘类型" required><el-select v-model="form.type" placeholder="请选择托盘类型"><el-option v-for="value in ['WOOD', 'PLASTIC', 'METAL', 'PAPER', 'FUMIGATION_FREE']" :key="value" :label="palletTypeLabel(value)" :value="value" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="容积(m³)" required><el-input-number v-model="form.volume" :min="0.0001" :precision="4" controls-position="right" placeholder="请输入容积" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="采购价格(USD)" required><el-input-number v-model="form.purchasePrice" :min="0" :precision="2" controls-position="right" placeholder="请输入采购价格" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="仓库" required><el-select v-model="form.warehouseId" filterable :disabled="dialogMode === 'edit'" placeholder="请选择海外仓"><el-option v-for="item in warehouses" :key="item.id" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="String(item.id)" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="所属 FBA 仓" required><el-select v-model="form.fbaWarehouseCode" filterable placeholder="请选择所属 FBA 仓"><el-option v-for="item in fbaWarehouses" :key="String(item.id)" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="item.warehouseCode" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="状态" required><el-select v-model="form.status" placeholder="请选择托盘状态"><el-option label="闲置" value="IDLE" /><el-option label="占用" value="OCCUPIED" /><el-option label="已报废" value="SCRAPPED" /></el-select></el-form-item></el-col>
<el-col :span="24"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" placeholder="请输入备注(选填)" /></el-form-item></el-col>
</el-row></el-form>
<AppDialog class="admin-density-dialog" v-model="dialogOpen" :title="dialogMode === 'create' ? '新增托盘' : `编辑托盘 · ${text(editingPallet?.code)}`" width="min(760px, calc(100vw - 28px))" fullscreenable confirm-text="保存" :loading="detailLoading || contextLoading" :confirm-loading="dialogBusy" :confirm-disabled="formLocked" @confirm="save">
<LoadErrorState v-if="contextFailed && loadError" title="托盘候选加载失败" :message="loadError.message" :loading="contextLoading" @retry="retryLoad"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
<LoadErrorState v-if="dialogError" :title="dialogErrorKind === 'read' ? '托盘详情加载失败' : '托盘保存失败'" :message="dialogError.message" :show-retry="dialogErrorKind === 'read'" :loading="detailLoading" @retry="loadEdit"><template #details><ApiErrorMeta :details="dialogError" /></template></LoadErrorState>
<p v-if="detailLoading" class="density-dialog-note" role="status">正在读取托盘资料…</p>
<el-form v-if="dialogMode === 'create' || editReady" class="density-form density-dialog-sections" label-position="left" label-width="120px" :disabled="formLocked">
<section class="density-dialog-section" data-tone="work" aria-label="托盘基础资料">
<h3 class="density-dialog-section__title">托盘资料</h3>
<table class="density-table density-table--editable pallet-editor-table"><tbody>
<tr><td><el-form-item label="托盘编码" required><el-input v-model="form.code" maxlength="128" placeholder="请输入托盘编码" /></el-form-item></td><td><el-form-item label="托盘类型" required><el-select v-model="form.type" placeholder="请选择托盘类型"><el-option v-for="value in ['WOOD', 'PLASTIC', 'METAL', 'PAPER', 'FUMIGATION_FREE']" :key="value" :label="palletTypeLabel(value)" :value="value" /></el-select></el-form-item></td></tr>
<tr><td><el-form-item label="容积(m³)" required><el-input-number v-model="form.volume" :min="0.0001" :precision="4" controls-position="right" placeholder="请输入容积" /></el-form-item></td><td><el-form-item label="采购价格(USD)" required><el-input-number v-model="form.purchasePrice" :min="0" :precision="2" controls-position="right" placeholder="请输入采购价格" /></el-form-item></td></tr>
<tr><td colspan="2"><el-form-item v-if="dialogMode === 'create'" label="仓库" required><el-select v-model="form.warehouseId" filterable placeholder="请选择海外仓"><el-option v-for="item in warehouses" :key="item.id" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="String(item.id)" /></el-select></el-form-item><el-form-item v-else label="仓库"><span>{{ warehouseName(form.warehouseId) }}</span></el-form-item></td></tr>
<tr><td><el-form-item label="所属 FBA 仓" required><el-select v-model="form.fbaWarehouseCode" filterable placeholder="请选择所属 FBA 仓"><el-option v-for="item in fbaWarehouses" :key="String(item.id)" :label="`${item.warehouseName}(${item.warehouseCode})`" :value="item.warehouseCode" /></el-select></el-form-item></td><td><el-form-item label="状态" required><el-select v-model="form.status" placeholder="请选择托盘状态"><el-option label="闲置" value="IDLE" /><el-option label="占用" value="OCCUPIED" /><el-option label="已报废" value="SCRAPPED" /></el-select></el-form-item></td></tr>
<tr><td colspan="2"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="2" maxlength="500" show-word-limit placeholder="请输入备注(选填)" /></el-form-item></td></tr>
</tbody></table>
</section>
</el-form>
</AppDialog>
</AdminPage>
</template>
<style scoped>
.summary-grid span { color:var(--cc-muted); font-size:var(--admin-font-caption); }.summary-grid .idle { color:var(--el-color-success); }.summary-grid .occupied { color:var(--el-color-warning); }
.el-form :deep(.el-row) { margin-left: 0 !important; margin-right: 0 !important; }
.el-form :deep(.el-input), .el-form :deep(.el-select), .el-form :deep(.el-input-number) { width: 100%; }
.pallet-editor-table { table-layout: fixed; }
.pallet-editor-table td:not([colspan]) { width: 50%; }
.summary-grid { display: flex; gap: 0; flex-wrap: wrap; border: 1px solid var(--cc-border); background: var(--admin-surface); }
.summary-grid article { display: flex; gap: var(--admin-space-3); padding: var(--admin-space-3) var(--admin-space-5); border: 0; border-radius: 0; background:#fff; flex: 1 1 200px; align-items: baseline; border-right: 1px solid var(--cc-border); }