Files
admin-web/src/modules/overseas-warehouse/pages/RelabelOrdersView.vue
T
2026-10-20 17:24:50 +08:00

331 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue'
import { relabelSkuCodes } from '../utils/relabelSkuDisplay'
import { Plus, PackagePlus } from '@lucide/vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import AdminPage from '@/shared/components/AdminPage.vue'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import { changeRelabelOrderStatus, createRelabelOrder, getRelabelOrder, listRelabelOrders, listRelabelWarehouseOptions, getRelabelQuantities, type RelabelQuantityTotals, type RelabelWarehouseOptionDto } from '../api/relabelOrders'
import { overseasWarehousePermissions as permission } from '../permissions'
import { formatOverseasDateTime, relabelBusinessStatusLabel } from '../lastMilePresentation'
import RelabelInventoryItemsEditor from '../components/RelabelInventoryItemsEditor.vue'
import type { CreateRelabelItemPayload } from '../api/relabelOrders'
import type { RelabelOrderListItem } from '../types'
const access = useAccessPreviewStore()
const router = useRouter()
type RelabelRow = Omit<RelabelOrderListItem, 'cartons'> & { cartons: string; labelPieces: string; skuCount: string }
const rows = ref<RelabelRow[]>([])
const loading = ref(false)
const voiding = ref('')
const loadError = ref<ApiErrorDetails | null>(null)
const customers = ref<PlatformTenantResource[]>([])
const customersLoading = ref(false)
const warehouses = ref<RelabelWarehouseOptionDto[]>([])
const warehousesLoading = ref(false)
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const quantities = ref<RelabelQuantityTotals | null>(null)
function dimension(value: number | string | null | undefined) { return value == null ? '—' : String(value) }
const filters = ref<QueryModel>({ relabelNo: '', sku: '', status: '' })
const fields = computed<QueryField[]>(() => [
{ key: 'relabelNo', type: 'input', label: '换标单号', placeholder: '请输入换标单号', width: 220 },
{ key: 'sku', type: 'input', label: '商品SKU', placeholder: '请输入商品SKU', width: 180 },
{ key: 'status', type: 'select', label: '状态', placeholder: '全部状态', width: 160, options: [{ label: '待审核', value: 'PENDING_REVIEW' }, { label: '待处理', value: 'PENDING_PROCESSING' }, { label: '处理中', value: 'PROCESSING' }, { label: '待客户确认', value: 'CUSTOMER_CONFIRM_PENDING' }, { label: '已驳回', value: 'REJECTED' }, { label: '换标完成', value: 'COMPLETED' }, { label: '已作废', value: 'CANCELLED' }] },
])
const columns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' },
{ key: 'selection', type: 'selection', width: 48, fixed: 'left' },
{ key: 'orderNo', label: '换标单号', minWidth: 170 },
{ key: 'sku', label: '商品SKU', minWidth: 150 },
{ key: 'productName', label: '中文品名', minWidth: 180 },
{ key: 'cartons', label: '箱数', width: 90, align: 'right' },
{ key: 'labelPieces', label: '贴标件数', width: 110, align: 'right' },
{ key: 'skuCount', label: 'SKU种数', width: 100, align: 'right' },
{ key: 'budgetTotal', label: '预算总价', width: 120, align: 'right' },
{ key: 'relabelCost', label: '换标费用', width: 120, align: 'right' },
{ key: 'walletStatus', label: '资金状态', width: 120 },
{ key: 'labelRequirement', label: '贴标要求', minWidth: 150 },
{ key: 'fileName', label: '上传文件', minWidth: 160 },
{ key: 'status', label: '状态', width: 110 },
{ key: 'createdAt', label: '创建时间', minWidth: 170 },
]
const dialogOpen = ref(false)
const dialogMode = ref<'create' | 'edit'>('create')
const submitting = ref(false)
const detailLoading = ref(false)
const editSourceStatus = ref('')
const auditLabelPieces = ref('—')
const form = reactive({ orderNo: '', customerId: '', relatedOrderNo: '', sku: '', productName: '', cartons: undefined as number | undefined, budgetTotal: undefined as number | undefined, status: 'PENDING_PROCESSING', labelRequirement: '', relabelCost: undefined as number | undefined, remark: '', warehouseId: '', labelDeadline: '', contactName: '', contactPhone: '', shipmentNo: '', newSkuCode: '' })
const createItems = ref<CreateRelabelItemPayload[]>([])
const createOperation = ref<CreateRelabelItemPayload['operationType']>('CARTON_LABEL')
const createDestination = ref('')
const createDelivery = ref<'TRUCK' | 'EXPRESS' | 'PICKUP' | 'AMAZON_PICKUP' | 'CUSTOMER_PICKUP'>('PICKUP')
const createIdempotencyKey = ref('')
const dialogError = ref<ApiErrorDetails | null>(null), customersError = ref<ApiErrorDetails | null>(null), warehousesError = ref<ApiErrorDetails | null>(null)
const formLocked = computed(() => submitting.value || detailLoading.value || dialogMode.value === 'create' && (customersLoading.value || warehousesLoading.value) || !access.can(dialogMode.value === 'create' ? permission.relabelOrderCreate : permission.relabelOrderStatus))
const selectedRows = ref<RelabelRow[]>([])
const allowedStatusOptions = computed(() => {
if (editSourceStatus.value === 'PENDING_REVIEW') return ['PENDING_PROCESSING', 'REJECTED']
if (editSourceStatus.value === 'PENDING_PROCESSING') return ['PROCESSING', 'REJECTED', 'CANCELLED']
if (editSourceStatus.value === 'PROCESSING') return ['COMPLETED', 'REJECTED', 'CANCELLED']
return []
})
function optionalNumber(value: unknown) { const number = Number(value); return value === '' || value == null || !Number.isFinite(number) ? undefined : number }
function openPackingPlan() {
if (!selectedRows.value.length) { ElMessage.warning('请先勾选订单'); return }
const customerIds = [...new Set(selectedRows.value.map(row => row.customerId).filter(Boolean))]
if (customerIds.length > 1) { ElMessage.warning('合箱请选择同一客户的退货库存'); return }
void router.push({ name: 'overseas-return-inventory', query: { packing: '1', ...(customerIds[0] ? { customerId: customerIds[0] } : {}) } })
}
async function openForm(mode: 'create' | 'edit', source?: object) {
const row = source as RelabelRow | undefined
if (dialogOpen.value || submitting.value || detailLoading.value || !access.can(mode === 'create' ? permission.relabelOrderCreate : permission.relabelOrderStatus) || mode === 'edit' && (!row || !canAudit(row))) return
dialogError.value = null; customersError.value = null; warehousesError.value = null
dialogMode.value = mode
Object.assign(form, { orderNo: '', customerId: '', relatedOrderNo: '', sku: '', productName: '', cartons: undefined, budgetTotal: undefined, status: 'PENDING_PROCESSING', labelRequirement: '', relabelCost: undefined, remark: '', warehouseId: '', labelDeadline: '', contactName: '', contactPhone: '', shipmentNo: '', newSkuCode: '' })
editSourceStatus.value = ''
if (mode === 'edit' && row) {
editSourceStatus.value = row.rawStatus || relabelStatusCode(row.status)
Object.assign(form, { orderNo: String(row.orderNo || ''), relatedOrderNo: String(row.relatedOrderNo || ''), sku: String(row.sku || ''), productName: String(row.productName || ''), cartons: optionalNumber(row.cartons), budgetTotal: optionalNumber(row.budgetTotal), status: '', labelRequirement: String(row.labelRequirement || ''), relabelCost: optionalNumber(row.relabelCost), remark: String(row.remark || '') })
}
if (mode === 'create') {
createItems.value = []; createOperation.value = 'CARTON_LABEL'; createDestination.value = ''; createDelivery.value = 'PICKUP'
createIdempotencyKey.value = globalThis.crypto?.randomUUID?.() || `admin-relabel-${Date.now()}`
warehouses.value = []
void loadCustomers()
void loadWarehouses()
}
dialogOpen.value = true
if (mode === 'edit' && row?.orderNo) {
detailLoading.value = true
try {
const detail = await getRelabelOrder(String(row.orderNo))
Object.assign(form, {
orderNo: String(detail.application?.relabelNo || row.orderNo || ''),
relatedOrderNo: String(detail.application?.lastlegOrderNo || detail.application?.customerOrderNo || row.relatedOrderNo || ''),
sku: [...new Set(detail.items.flatMap(relabelSkuCodes))].join('、') || row.sku || '',
productName: [...new Set(detail.items.map(line => line.productNameZh?.trim()).filter(Boolean))].join('、') || row.productName || '',
cartons: optionalNumber(quantities.value?.orders[String(row.orderNo)]?.cartonCount),
labelRequirement: [...new Set(detail.items.map(line => line.labelRequirement?.trim()).filter(Boolean))].join(';') || row.labelRequirement || '',
shipmentNo: [...new Set(detail.items.map(line => line.shipmentNo?.trim()).filter(Boolean))].join('、'),
newSkuCode: detail.items.flatMap(line => line.skuItems?.length ? line.skuItems.map(sku => `${sku.newSkuCode} × ${sku.quantity} 件`) : [line.newSkuCode || '']).filter(Boolean).join(';'),
relabelCost: optionalNumber(detail.application?.chargeAmount ?? row.relabelCost),
remark: String(detail.remark || detail.application?.reviewRemark || row.remark || ''),
})
auditLabelPieces.value = dimension(quantities.value?.orders[String(row.orderNo)]?.labelPieceCount)
} catch (error) {
ElMessage.error(getApiErrorDetails(error, '换标单详情加载失败,请重试').message)
dialogOpen.value = false
} finally { detailLoading.value = false }
}
}
async function loadCustomers() {
customersError.value = null; customersLoading.value = true
try {
const records: PlatformTenantResource[] = []
let currentPage = 1
let pages = 1
do {
const result = await listPlatformTenants({ page: currentPage, size: 200, status: 'ACTIVE' })
records.push(...result.records.filter((item) => item.status === 'ACTIVE'))
pages = Math.max(1, result.pages)
currentPage += 1
} while (currentPage <= pages)
customers.value = records
} catch (error) { customers.value = []; customersError.value = getApiErrorDetails(error, '客户列表加载失败,请重试') }
finally { customersLoading.value = false }
}
async function loadWarehouses() {
warehousesError.value = null
form.warehouseId = ''
warehouses.value = []
warehousesLoading.value = true
try {
const records = (await listRelabelWarehouseOptions()).records.filter((item) => item.status === 'ACTIVE')
warehouses.value = records
} catch (error) {
warehousesError.value = getApiErrorDetails(error, '公共海外收货仓加载失败,请重试')
} finally {
warehousesLoading.value = false
}
}
async function submitForm() {
if (!dialogOpen.value || formLocked.value || dialogMode.value === 'create' && (customersError.value || warehousesError.value)) return
dialogError.value = null
try {
if (dialogMode.value === 'create') {
if (!form.customerId || !form.warehouseId) { ElMessage.warning('请选择客户和公共海外收货仓'); return }
if (!form.relatedOrderNo.trim() || !createItems.value.length || !form.relabelCost || form.relabelCost <= 0 || !form.labelDeadline || !form.contactName.trim() || !form.contactPhone.trim() || !form.shipmentNo.trim()) {
ElMessage.warning('请完整填写换标单必填信息')
return
}
const marks = createItems.value.map(item => item.cartonMark.trim())
if (marks.some(mark => !mark) || new Set(marks).size !== marks.length) { ElMessage.warning('请填写不重复的我方箱唛'); return }
if (createOperation.value !== 'SKU_LABEL' && (!createDestination.value.trim() || createItems.value.some(item => !item.fbaCartonMark?.trim()))) { ElMessage.warning('请填写 FBA 目的仓和每箱的新箱唛'); return }
if (createOperation.value !== 'CARTON_LABEL' && createItems.value.some(item => !item.skuItems.length || item.skuItems.some(sku => !sku.skuCode.trim() || !sku.newSkuCode.trim() || !Number.isInteger(sku.quantity) || sku.quantity <= 0))) { ElMessage.warning('请完整填写每箱 SKU 换标及件数'); return }
submitting.value = true
await createRelabelOrder(form.customerId, { customerOrderNo: form.relatedOrderNo.trim(), operationWarehouseId: form.warehouseId, labelDeadline: form.labelDeadline, contactName: form.contactName.trim(), contactPhone: form.contactPhone.trim(), chargeAmount: Number(form.relabelCost), remark: form.remark.trim() || undefined, idempotencyKey: createIdempotencyKey.value, deliveryMethod: createOperation.value === 'SKU_LABEL' ? undefined : createDelivery.value, printShippingLabel: false, items: createItems.value })
ElMessage.success('换标单已创建')
} else {
if (!allowedStatusOptions.value.includes(form.status)) { ElMessage.warning('当前状态没有可提交的审核流转'); return }
if (form.status === 'REJECTED' && !form.remark.trim()) { ElMessage.warning('审核驳回请填写原因'); return }
submitting.value = true
await changeRelabelOrderStatus(form.orderNo, form.status, form.remark || undefined)
ElMessage.success('状态已更新')
}
dialogOpen.value = false
createIdempotencyKey.value = ''
await load()
} catch (error) { dialogError.value = getApiErrorDetails(error, '操作失败,请重试') }
finally { submitting.value = false }
}
function openDetail(source: object) {
const row = source as RelabelRow
void router.push({ name: 'overseas-relabel-order-detail', params: { orderId: String(row.orderNo) } })
}
function relabelStatus(value: unknown) {
if (value === 'REJECTED') return '已驳回'
if (value === 'PENDING_REVIEW') return '待审核'
if (value === 'CANCELLED') return '已作废'
if (value === 'CUSTOMER_CONFIRM_PENDING') return '待客户确认'
return relabelBusinessStatusLabel(value)
}
function canVoid(source: object) {
const row = source as RelabelRow
return !['READY_FOR_OUTBOUND', 'COMPLETED', 'CANCELLED'].includes(row.rawStatus || relabelStatusCode(row.status))
}
async function voidOrder(source: object) {
const row = source as RelabelRow
if (voiding.value || !canVoid(row) || !access.can(permission.relabelOrderStatus)) return
voiding.value = String(row.orderNo)
try {
await ElMessageBox.confirm(`确认作废换标单 ${row.orderNo}?作废后停止换标作业,保留原单和操作记录。`, '作废换标单', { customClass: 'admin-density-confirm', type: 'warning', confirmButtonText: '确认作废', cancelButtonText: '取消' })
await changeRelabelOrderStatus(String(row.orderNo), 'CANCELLED', '平台作废换标单')
ElMessage.success('换标单已作废')
await load()
} catch (error) { if (error !== 'cancel' && error !== 'close') ElMessage.error(getApiErrorDetails(error, '换标单作废失败,请重试').message) }
finally { voiding.value = '' }
}
function relabelAuditStatusLabel(value: string) {
return ({
PENDING_PROCESSING: '审核通过',
PROCESSING: '处理中',
REJECTED: '已驳回',
CANCELLED: '已取消',
COMPLETED: '已完成',
} as Record<string, string>)[value] || relabelStatus(value)
}
function relabelStatusCode(value: unknown) {
const codes: Record<string, string> = { 待审核: 'PENDING_REVIEW', 待处理: 'PENDING_PROCESSING', 处理中: 'PROCESSING', 换标完成: 'COMPLETED' }
return codes[String(value || '')] || String(value || 'PENDING_PROCESSING')
}
function walletStatus(value: unknown) { return ({ FREEZE_PENDING: '冻结中', FROZEN: '已冻结', SETTLE_PENDING: '结算中', SETTLED: '已结算', RELEASE_PENDING: '释放中', RELEASED: '已释放', FREEZE_FAILED: '冻结失败', SETTLE_FAILED: '结算失败', RELEASE_FAILED: '释放失败' } as Record<string,string>)[String(value || '')] || String(value || '—') }
function canAudit(source: object) {
const row = source as RelabelRow
return row.reviewChannel !== 'TAIL_ORDER'
&& (row.rawStatus || relabelStatusCode(row.status)) === 'PENDING_REVIEW'
}
let loadGeneration = 0
async function load(model: QueryModel = filters.value) {
const current = ++loadGeneration
Object.assign(filters.value, model)
loading.value = true; loadError.value = null
try {
const query = { page: page.value, size: pageSize.value, relabelNo: String(filters.value.relabelNo || '').trim() || undefined, skuCode: String(filters.value.sku || '').trim() || undefined, status: String(filters.value.status || '').trim() || undefined }
quantities.value = null
const result = await listRelabelOrders(query)
const measured = await getRelabelQuantities(query).catch(() => null)
if (current !== loadGeneration) return
quantities.value = measured
rows.value = result.records.map((item) => ({
customerId: '', orderNo: String(item.relabelNo), relatedOrderNo: String(item.lastlegOrderNo || item.customerOrderNo || '—'), sku: String(item.skuCodes || '—'),
productName: item.productNameZh?.trim() || '—', cartons: dimension(quantities.value?.orders[item.relabelNo]?.cartonCount), labelPieces: dimension(quantities.value?.orders[item.relabelNo]?.labelPieceCount), skuCount: dimension(quantities.value?.orders[item.relabelNo]?.skuCount), budgetTotal: null, relabelCost: Number(item.chargeAmount || 0), walletStatus: walletStatus(item.walletStatus), walletErrorMessage: item.walletErrorMessage || '', labelRequirement: item.labelRequirement?.trim() || '—', fileName: '—', status: relabelStatus(item.status), rawStatus: String(item.status || ''), remark: '',
reviewChannel: item.reviewChannel || undefined,
createdAt: formatOverseasDateTime(item.createdAt),
}))
total.value = result.total
} catch (error) { if (current === loadGeneration) { rows.value = []; total.value = 0; loadError.value = getApiErrorDetails(error, '换标单加载失败,请重试') } }
finally { if (current === loadGeneration) loading.value = false }
}
function search(model: QueryModel) { page.value = 1; void load(model) }
function reset(model: QueryModel) { page.value = 1; void load(model) }
onMounted(load)
onActivated(() => { if (!loading.value) void load() })
watch(dialogOpen, (open) => { if (!open) createIdempotencyKey.value = '' })
</script>
<template>
<AdminPage class="admin-density"><ListPageLayout aria-label="换标单列表">
<section><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="!loadError" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-relabel-orders" row-key="orderNo" empty-text="暂无换标单" @selection-change="selectedRows = $event" @update:page="load()" @update:page-size="page = 1; load()">
<template #toolbar-start><h1>换标单</h1></template>
<template #filters><QueryToolbar v-model="filters" :fields="fields" :loading="loading" ariaLabel="换标单筛选" preference-key="overseas-relabel-orders-query" :search-on-reset="false" @search="search" @reset="reset" /></template>
<template #notices><template v-if="loadError"><LoadErrorState title="换标单加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState></template></template>
<template #toolbar-actions><el-button v-if="selectedRows.length" type="warning" @click="openPackingPlan"><PackagePlus :size="16" />创建合箱计划({{ selectedRows.length }})</el-button><el-button v-if="access.can(permission.relabelOrderCreate)" type="primary" @click="openForm('create')"><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 class="density-action" data-tone="warning" v-if="access.can(permission.relabelOrderStatus) && canAudit(row)" size="small" @click="openForm('edit', row)">审核</el-button><el-button v-if="access.can(permission.relabelOrderStatus) && canVoid(row)" class="density-action" data-tone="danger" size="small" :loading="voiding === row.orderNo" :disabled="!!voiding" @click="voidOrder(row)">作废</el-button></div></template>
<template #pagination-left>共 {{ total }} 条换标单</template>
</DataTable></section>
</ListPageLayout>
<AppDialog class="admin-density-dialog" v-model="dialogOpen" :title="dialogMode === 'create' ? '新增换标单' : '审核换标单 · ' + (form.orderNo || '—')" width="min(760px, calc(100vw - 28px))" fullscreenable :confirm-text="dialogMode === 'create' ? '保存换标单' : '提交审核'" :confirm-loading="submitting || detailLoading || dialogMode === 'create' && (customersLoading || warehousesLoading)" :confirm-disabled="formLocked || dialogMode === 'create' && Boolean(customersError || warehousesError)" @confirm="submitForm">
<div class="density-dialog-sections">
<LoadErrorState v-if="dialogMode === 'create' && customersError" compact title="客户候选读取失败" :message="customersError.message" :loading="customersLoading || submitting" @retry="loadCustomers"><template #details><ApiErrorMeta :details="customersError" /></template></LoadErrorState>
<LoadErrorState v-if="dialogMode === 'create' && warehousesError" compact title="公共海外收货仓读取失败" :message="warehousesError.message" :loading="warehousesLoading || submitting" @retry="loadWarehouses"><template #details><ApiErrorMeta :details="warehousesError" /></template></LoadErrorState>
<LoadErrorState v-if="dialogError" compact title="换标操作未完成" :message="dialogError.message" :show-retry="false"><template #details><ApiErrorMeta :details="dialogError" /></template></LoadErrorState>
<el-form class="density-form density-dialog-sections" label-position="left" label-width="120px" :disabled="formLocked">
<template v-if="dialogMode === 'create'">
<section class="density-dialog-section" data-tone="summary" aria-label="换标单生成方式"><table class="density-table"><tbody><tr><th scope="row">换标单号</th><td>系统自动生成</td></tr></tbody></table></section>
<section class="density-dialog-section" data-tone="work" aria-label="换标基础资料"><h3 class="density-dialog-section__title">订单信息</h3><table class="density-table density-table--editable relabel-editor-table"><tbody><tr><td><el-form-item label="关联订单" required><el-input v-model="form.relatedOrderNo" /></el-form-item></td><td><el-form-item label="客户" required><el-select v-model="form.customerId" filterable :loading="customersLoading" placeholder="请选择客户"><el-option v-for="item in customers" :key="String(item.id)" :label="item.tenantName" :value="String(item.id)" /></el-select></el-form-item></td></tr><tr><td><el-form-item label="公共海外收货仓" required><el-select v-model="form.warehouseId" filterable :loading="warehousesLoading" 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></td><td><el-form-item label="截止时间" required><el-date-picker v-model="form.labelDeadline" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择截止时间" /></el-form-item></td></tr><tr><td colspan="2"><el-form-item label="换标费用(CNY)" required><el-input-number v-model="form.relabelCost" :min="0.01" :precision="2" controls-position="right" /></el-form-item></td></tr><tr><td><el-form-item label="联系人" required><el-input v-model="form.contactName" /></el-form-item></td><td><el-form-item label="联系电话" required><el-input v-model="form.contactPhone" /></el-form-item></td></tr></tbody></table></section>
<section class="density-dialog-section" data-tone="work" aria-label="换标作业要求"><h3 class="density-dialog-section__title">作业要求</h3><table class="density-table density-table--editable relabel-editor-table"><tbody><tr><td><el-form-item label="贴标要求" required><el-input v-model="form.labelRequirement" /></el-form-item></td><td><el-form-item label="货件号" required><el-input v-model="form.shipmentNo" /></el-form-item></td></tr></tbody></table></section>
<section class="density-dialog-section" data-tone="work" aria-label="换标类型与去向">
<el-form-item label="换标类型" required><el-select v-model="createOperation" placeholder="请选择换标类型"><el-option label="只换箱唛" value="CARTON_LABEL" /><el-option label="只换 SKU" value="SKU_LABEL" /><el-option label="箱唛和 SKU 都换" value="CARTON_AND_SKU" /></el-select></el-form-item>
<template v-if="createOperation !== 'SKU_LABEL'"><el-form-item label="FBA 目的仓" required><el-input v-model="createDestination" placeholder="填写正式 FBA 仓码" /></el-form-item><el-form-item label="配送方式" required><el-select v-model="createDelivery" placeholder="请选择配送方式"><el-option label="普通自提" value="PICKUP" /><el-option label="平台卡车" value="TRUCK" /><el-option label="快递" value="EXPRESS" /><el-option label="亚马逊自提" value="AMAZON_PICKUP" /><el-option label="客户自提" value="CUSTOMER_PICKUP" /></el-select></el-form-item></template>
</section>
<RelabelInventoryItemsEditor :customer-id="form.customerId" :warehouse-id="form.warehouseId" :operation-type="createOperation" :shipment-no="form.shipmentNo" :destination="createDestination" :requirement="form.labelRequirement" @update:items="createItems = $event" />
</template>
<template v-else>
<section class="density-dialog-section" data-tone="source" aria-label="换标审核原始资料"><h3 class="density-dialog-section__title">原始资料</h3><table class="density-table"><tbody>
<tr><th scope="row">关联订单</th><td colspan="3">{{ form.relatedOrderNo || '—' }}</td></tr><tr><th scope="row">商品SKU</th><td colspan="3">{{ form.sku || '—' }}</td></tr><tr><th scope="row">中文品名</th><td colspan="3">{{ form.productName || '—' }}</td></tr>
<tr><th scope="row">箱数</th><td>{{ form.cartons ?? '—' }}</td><th scope="row">换标费用(CNY)</th><td>{{ form.relabelCost ?? '—' }}</td></tr>
<tr><th scope="row">贴标件数</th><td colspan="3">{{ auditLabelPieces }}</td></tr>
<tr><th scope="row">贴标要求</th><td colspan="3">{{ form.labelRequirement || '—' }}</td></tr><tr><th scope="row">货件号</th><td colspan="3">{{ form.shipmentNo || '—' }}</td></tr><tr><th scope="row">新SKU</th><td colspan="3">{{ form.newSkuCode || '—' }}</td></tr>
</tbody></table></section>
<section class="density-dialog-section" data-tone="work" aria-label="换标审核结论"><h3 class="density-dialog-section__title">审核结论</h3><table class="density-table density-table--editable relabel-editor-table"><tbody><tr><td colspan="2"><el-form-item label="审核动作"><el-select v-model="form.status" placeholder="请选择审核动作"><el-option v-for="value in allowedStatusOptions" :key="value" :label="relabelAuditStatusLabel(value)" :value="value" /></el-select></el-form-item></td></tr></tbody></table></section>
</template>
<section class="density-dialog-section" data-tone="work" aria-label="换标备注与原因"><h3 class="density-dialog-section__title">{{ dialogMode === 'edit' ? '审核备注' : '备注' }}</h3><table class="density-table density-table--editable relabel-editor-table"><tbody><tr><td colspan="2"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="500" show-word-limit /></el-form-item></td></tr></tbody></table><p v-if="dialogMode === 'edit'" class="density-dialog-note">审核驳回时必须填写原因。</p></section>
</el-form>
</div>
</AppDialog>
</AdminPage>
</template>
<style scoped>
.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), .el-form :deep(.el-date-editor) { width: 100%; }
.relabel-editor-table { table-layout: fixed; }
.relabel-editor-table td:not([colspan]) { width: 50%; }
@media (max-width: 720px) { .relabel-editor-table, .relabel-editor-table tbody { display: block; }.relabel-editor-table tr { display: grid; grid-template-columns: 1fr; }.relabel-editor-table td:not([colspan]) { width: auto; } }
</style>