173 lines
24 KiB
Vue
173 lines
24 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||
import { Eye, FileUp, Pencil, RotateCcw } from '@lucide/vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||
import { useRouter } from 'vue-router'
|
||
import AdminPage from '@/shared/components/AdminPage.vue'
|
||
import AppDialog from '@/shared/components/AppDialog.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 StatusBadge from '@/shared/components/StatusBadge.vue'
|
||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||
import { formatOverseasDateTime } from '../lastMilePresentation'
|
||
import { getDeliveryOrder, listDeliveryOrders, listDeliveryReturnInboundOptions, markDeliveryReturn, type DeliveryOrderRow, type DeliveryReturnRelation, type DeliveryReturnSource, type MarkDeliveryReturnPayload } from '../api/deliveryOrders'
|
||
import { updateDeliveryOrderStatus } from '../api/customerVisits'
|
||
import { overseasWarehousePermissions as permission } from '../permissions'
|
||
import { listReceivingOverseasWarehouses, type ReceivingOverseasWarehouseDto } from '../api/pallets'
|
||
|
||
import DeliveryDocumentsDialog from '../components/DeliveryDocumentsDialog.vue'
|
||
import { deliveryDocumentCategories } from '../api/deliveryDocuments'
|
||
|
||
const documentsOpen = ref(false)
|
||
const documentsRow = ref<DeliveryOrderRow | null>(null)
|
||
function openDocuments(row: DeliveryOrderRow) { if (!access.can(permission.deliveryList)) return; documentsRow.value = row; documentsOpen.value = true }
|
||
const router = useRouter()
|
||
const access = useAccessPreviewStore()
|
||
const rows = ref<DeliveryOrderRow[]>([]); const page = ref(1); const pageSize = ref(10); const total = ref(0); const loading = ref(false); const error = ref<ApiErrorDetails | null>(null)
|
||
const warehouses = ref<ReceivingOverseasWarehouseDto[]>([]); const warehousesLoading = ref(false)
|
||
const filters = ref<QueryModel>({ documentNo: '', dispatchNo: '', waybillNo: '', warehouseId: '', status: '', vehiclePlate: '', driverName: '', deliveryMethod: '', plannedAt: [] })
|
||
const fields = computed<QueryField[]>(() => [
|
||
{ key: 'status', type: 'select', label: '状态', width: 160, placeholder: '全部状态', options: [{ label: '待派送', value: 'WAIT_DELIVERY' }, { label: '派送中', value: 'DELIVERING' }, { label: '派送完成', value: 'DELIVERED' }, { label: '已签收(历史)', value: 'SIGNED' }, { label: '已拒签', value: 'REJECTED' }, { label: '退货处理中', value: 'RETURN_PENDING' }, { label: '已取消', value: 'CANCELLED' }] },
|
||
{ key: 'documentNo', type: 'input', label: '派送单号', width: 190, placeholder: '请输入派送单号' },
|
||
{ key: 'dispatchNo', type: 'input', label: '排车单号', width: 190, placeholder: '请输入排车单号' },
|
||
{ key: 'waybillNo', type: 'input', label: '运单号', width: 180, placeholder: '请输入运单号' },
|
||
{ key: 'warehouseId', type: 'select', label: '海外仓', width: 220, placeholder: '全部海外仓', options: warehouses.value.map((item) => ({ label: `${item.warehouseName}(${item.warehouseCode})`, value: String(item.id) })), props: { filterable: true, clearable: true, loading: warehousesLoading.value } },
|
||
{ key: 'deliveryMethod', type: 'select', label: '派送方式', width: 170, placeholder: '全部方式', options: [{ label: 'FBA自提', value: 'FBA_PICKUP' }, { label: '客户自提', value: 'CUSTOMER_PICKUP' }, { label: '卡派打托', value: 'TRUCK_PALLET' }, { label: '卡派地板', value: 'TRUCK_FLOOR' }, { label: '快递', value: 'EXPRESS' }, { label: '外配', value: 'OUTSOURCED' }, { label: '专车直送', value: 'DEDICATED_TRUCK' }, { label: '零担派送', value: 'LTL' }] },
|
||
{ key: 'vehiclePlate', type: 'input', label: '车牌号', width: 160, placeholder: '请输入车牌号' },
|
||
{ key: 'driverName', type: 'input', label: '司机', width: 180, placeholder: '请输入司机或车队' },
|
||
{ key: 'plannedAt', type: 'daterange', label: '预计派送时间', width: 280 },
|
||
])
|
||
const columns: DataTableColumn[] = [
|
||
{ key: 'actions', label: '操作', width: 300, fixed: 'left', slotName: 'actions' },
|
||
{ key: 'deliveryNo', prop: 'deliveryNo', label: '派送单号', minWidth: 180, fixed: 'left' },
|
||
{ key: 'documents', label: '所需资料', minWidth: 380, slotName: 'documents' },
|
||
{ key: 'bolNo', prop: 'bolNo', label: 'BOL号', minWidth: 170 },
|
||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 160 },
|
||
{ key: 'overseasWarehouseName', prop: 'overseasWarehouseName', label: '仓库', minWidth: 150 },
|
||
{ key: 'dispatchNo', prop: 'dispatchNo', label: '排车单号', minWidth: 170 },
|
||
{ key: 'waybillNo', prop: 'waybillNo', label: '运单号', minWidth: 150 },
|
||
{ key: 'status', prop: 'status', label: '订单状态', width: 110, slotName: 'status' },
|
||
{ key: 'deliveryMethod', prop: 'deliveryMethod', label: '派送方式', width: 120, formatter: (row: DeliveryOrderRow) => deliveryMethodLabel(row.deliveryMethod) },
|
||
{ key: 'carrierName', prop: 'carrierName', label: '派送人', minWidth: 150 },
|
||
{ key: 'vehiclePlate', prop: 'vehiclePlate', label: '车牌', minWidth: 120 },
|
||
{ key: 'driverName', prop: 'driverName', label: '司机/车队', minWidth: 140 },
|
||
{ key: 'plannedDeliveryAt', prop: 'plannedDeliveryAt', label: '期望派送时间', minWidth: 170, formatter: (row: DeliveryOrderRow) => formatOverseasDateTime(row.plannedDeliveryAt) },
|
||
{ key: 'dispatchedAt', prop: 'dispatchedAt', label: '派送时间', minWidth: 170, formatter: (row: DeliveryOrderRow) => formatOverseasDateTime(row.dispatchedAt) },
|
||
{ key: 'pickupMethod', prop: 'pickupMethod', label: '提货方式', width: 110, formatter: (row: DeliveryOrderRow) => pickupMethodLabel(row.pickupMethod) },
|
||
{ key: 'loadingType', prop: 'loadingType', label: '装车类型', width: 110, formatter: (row: DeliveryOrderRow) => loadingTypeLabel(row.loadingType) },
|
||
{ key: 'packageCount', prop: 'packageCount', label: '总箱数', width: 90, align: 'right' },
|
||
{ key: 'totalWeightKg', prop: 'totalWeightKg', label: '总重量(kg)', width: 120, align: 'right' },
|
||
{ key: 'totalVolumeCbm', prop: 'totalVolumeCbm', label: '总体积(m³)', width: 120, align: 'right' },
|
||
{ key: 'expectedArrivalAt', prop: 'expectedArrivalAt', label: '预计到达时间', minWidth: 170, formatter: (row: DeliveryOrderRow) => formatOverseasDateTime(row.expectedArrivalAt) },
|
||
{ key: 'signedAt', prop: 'signedAt', label: '签收时间', minWidth: 170, formatter: (row: DeliveryOrderRow) => formatOverseasDateTime(row.signedAt) },
|
||
{ key: 'remark', prop: 'remark', label: '备注', minWidth: 160 },
|
||
]
|
||
const statusLabels: Record<string, string> = { WAIT_DELIVERY: '待派送', DELIVERING: '派送中', DELIVERED: '派送完成', SIGNED: '已签收', REJECTED: '已拒签', RETURN_PENDING: '退货处理中', CANCELLED: '已取消' }
|
||
function deliveryMethodLabel(value?: string | null) { return ({ FBA_PICKUP: 'FBA自提', CUSTOMER_PICKUP: '客户自提', TRUCK_PALLET: '卡派打托', TRUCK_FLOOR: '卡派地板', EXPRESS: '快递', OUTSOURCED: '外配', DEDICATED_TRUCK: '专车直送', LTL: '零担派送', SELF_DELIVERY: '自送', THIRD_PARTY: '第三方配送' } as Record<string, string>)[String(value)] || value || '—' }
|
||
function pickupMethodLabel(value?: string | null) { return ({ ONE_PICKUP_ONE_DROP: '一提一卸', ONE_PICKUP_MULTI_DROP: '一提多卸', MULTI_PICKUP_ONE_DROP: '多提一卸', MULTI_PICKUP_MULTI_DROP: '多提多卸', WAREHOUSE_PICKUP: '仓库自提', DOOR_PICKUP: '上门提货', SELF_PICKUP: '自提' } as Record<string, string>)[String(value)] || value || '—' }
|
||
function loadingTypeLabel(value?: string | null) { return ({ PALLET: '托盘', FLOOR: '地板', MIXED: '混装', CARTON: '散箱' } as Record<string, string>)[String(value)] || value || '—' }
|
||
async function load(model: QueryModel = filters.value) { Object.assign(filters.value, model); loading.value = true; error.value = null; try { const range = Array.isArray(filters.value.plannedAt) ? filters.value.plannedAt.map(String) : []; const result = await listDeliveryOrders({ page: page.value, size: pageSize.value, documentNo: text('documentNo'), dispatchNo: text('dispatchNo'), waybillNo: text('waybillNo'), warehouseId: text('warehouseId'), status: text('status'), vehiclePlate: text('vehiclePlate'), driverName: text('driverName'), deliveryMethod: text('deliveryMethod'), plannedFrom: range[0] ? `${range[0]}T00:00:00` : undefined, plannedTo: range[1] ? `${range[1]}T23:59:59` : undefined }); rows.value = result.records; total.value = result.total } catch (cause) { rows.value = []; total.value = 0; error.value = getApiErrorDetails(cause, '派送单列表加载失败') } finally { loading.value = false } }
|
||
function text(key: string) { return String(filters.value[key] || '').trim() || undefined }
|
||
function open(row: DeliveryOrderRow) { void router.push({ name: 'overseas-delivery-order-detail', params: { deliveryNo: row.deliveryNo }, query: { customerId: row.customerId } }) }
|
||
const returnOpen = ref(false); const returnLoading = ref(false); const returnSaving = ref(false); const returnRow = ref<DeliveryOrderRow | null>(null); const returnOptions = ref<DeliveryReturnRelation[]>([])
|
||
const returnError = ref<ApiErrorDetails | null>(null)
|
||
const returnForm = reactive({ inboundOrderId: '', reason: '', returnDirection: 'OVERSEAS_WAREHOUSE' as MarkDeliveryReturnPayload['returnDirection'], returnLogisticsNo: '' })
|
||
const returnItems = ref<Array<DeliveryReturnSource & { selected: boolean; packageCount: number | undefined }>>([])
|
||
let returnSequence = 0
|
||
let returnAttempt: { body: string; key: string } | null = null
|
||
function canReturn(row: DeliveryOrderRow) { return access.can(permission.deliveryReturn) && row.allowedActions?.includes('MARK_RETURN') }
|
||
watch(() => returnForm.inboundOrderId, () => {
|
||
returnItems.value = (returnOptions.value.find(option => option.inboundOrderId === returnForm.inboundOrderId)?.returnSources || []).map(source => ({ ...source, selected: false, packageCount: undefined }))
|
||
})
|
||
watch(returnOpen, visible => { if (!visible) returnSequence++ })
|
||
async function openReturn(row: DeliveryOrderRow) {
|
||
if (!canReturn(row) || returnSaving.value) return
|
||
const sequence = ++returnSequence
|
||
returnRow.value = row; Object.assign(returnForm, { inboundOrderId: '', reason: '', returnDirection: 'OVERSEAS_WAREHOUSE', returnLogisticsNo: '' })
|
||
returnOptions.value = []; returnItems.value = []; returnError.value = null; returnAttempt = null; returnOpen.value = true; returnLoading.value = true
|
||
try {
|
||
const options = await listDeliveryReturnInboundOptions(row.deliveryNo, row.customerId)
|
||
if (sequence !== returnSequence || !returnOpen.value) return
|
||
returnOptions.value = options
|
||
if (options.length === 1) returnForm.inboundOrderId = options[0]!.inboundOrderId
|
||
} catch (cause) { if (sequence === returnSequence) returnError.value = getApiErrorDetails(cause, '退货入仓单加载失败') }
|
||
finally { if (sequence === returnSequence) returnLoading.value = false }
|
||
}
|
||
async function submitReturn() {
|
||
if (!returnOpen.value || !returnRow.value || !canReturn(returnRow.value) || returnLoading.value || returnSaving.value) return
|
||
if (!returnOptions.value.some(option => option.inboundOrderId === returnForm.inboundOrderId) || !returnForm.reason.trim()) { ElMessage.warning('请选择入仓单并填写退货原因'); return }
|
||
const selected = returnItems.value.filter(item => item.selected)
|
||
if (!selected.length || selected.some(item => !Number.isSafeInteger(item.packageCount) || !item.packageCount || item.packageCount < 1 || item.packageCount > item.maxReturnPackages)) { ElMessage.warning('请选择原出库明细,并填写不超过可退上限的整数箱数'); return }
|
||
const body = { inboundOrderId: returnForm.inboundOrderId, reason: returnForm.reason.trim(), returnDirection: returnForm.returnDirection, returnLogisticsNo: returnForm.returnLogisticsNo.trim() || undefined, items: selected.map(item => ({ outboundPalletItemId: item.outboundPalletItemId, inventoryPositionId: item.inventoryPositionId, packageCount: item.packageCount! })) }
|
||
const serialized = JSON.stringify(body)
|
||
if (returnAttempt?.body !== serialized) returnAttempt = { body: serialized, key: crypto.randomUUID() }
|
||
returnSaving.value = true; returnError.value = null
|
||
try {
|
||
const result = await markDeliveryReturn(returnRow.value.deliveryNo, returnRow.value.customerId, { ...body, idempotencyKey: returnAttempt.key })
|
||
returnOpen.value = false; ElMessage.success(`已生成异常退货单 ${result.exceptionNo}`); await load()
|
||
} catch (cause) { returnError.value = getApiErrorDetails(cause, '标记退货失败') }
|
||
finally { returnSaving.value = false }
|
||
}
|
||
const statusOpen = ref(false)
|
||
const statusSaving = ref(false)
|
||
const statusLoading = ref(false)
|
||
const statusError = ref<ApiErrorDetails | null>(null)
|
||
const statusConflict = ref(false)
|
||
const statusRow = ref<DeliveryOrderRow | null>(null)
|
||
const statusForm = reactive({ status: '', remark: '' })
|
||
let statusSequence = 0
|
||
const statusTargets = [
|
||
{ action: 'START_DELIVERY', value: 'DELIVERING', label: '派送中' },
|
||
{ action: 'COMPLETE_DELIVERY', value: 'DELIVERED', label: '派送完成' },
|
||
{ action: 'REJECT_DELIVERY', value: 'REJECTED', label: '已拒签' },
|
||
{ action: 'CANCEL_DELIVERY', value: 'CANCELLED', label: '已取消' },
|
||
]
|
||
function availableStatuses(row: DeliveryOrderRow) { return statusTargets.filter(option => row.allowedActions?.includes(option.action)) }
|
||
const statusOptions = computed(() => statusRow.value ? availableStatuses(statusRow.value) : [])
|
||
async function refreshStatus() {
|
||
const row = statusRow.value
|
||
if (!row || statusSaving.value) return
|
||
const sequence = ++statusSequence
|
||
statusLoading.value = true; statusError.value = null
|
||
try {
|
||
const latest = await getDeliveryOrder(row.deliveryNo, row.customerId)
|
||
if (sequence !== statusSequence || !statusOpen.value) return
|
||
if (!Number.isSafeInteger(latest.order.version) || latest.order.version < 0) throw new Error('派送单缺少有效版本,请刷新后重试')
|
||
statusRow.value = latest.order
|
||
if (!availableStatuses(latest.order).some(option => option.value === statusForm.status)) statusForm.status = ''
|
||
statusConflict.value = false
|
||
} catch (cause) { if (sequence === statusSequence) { statusError.value = getApiErrorDetails(cause, '派送单最新状态加载失败'); statusConflict.value = true } }
|
||
finally { if (sequence === statusSequence) statusLoading.value = false }
|
||
}
|
||
async function openStatus(row: DeliveryOrderRow) {
|
||
if (!access.can(permission.deliveryStatus) || statusSaving.value || !availableStatuses(row).length) return
|
||
statusRow.value = row; Object.assign(statusForm, { status: '', remark: '' }); statusConflict.value = true; statusOpen.value = true
|
||
await refreshStatus()
|
||
}
|
||
async function submitStatus() {
|
||
if (!statusOpen.value || !statusRow.value || statusLoading.value || statusSaving.value || statusConflict.value || !access.can(permission.deliveryStatus) || !statusOptions.value.some(option => option.value === statusForm.status)) return
|
||
statusSaving.value = true; statusError.value = null
|
||
try {
|
||
const result = await updateDeliveryOrderStatus(statusRow.value.deliveryNo, statusRow.value.customerId, { status: statusForm.status, expectedVersion: statusRow.value.version, remark: statusForm.remark.trim() || undefined })
|
||
statusOpen.value = false
|
||
ElMessage.success(result.deliveryStatus === 'DELIVERED' ? '派送已完成,待回访记录已生成' : '派送状态已更新')
|
||
await load()
|
||
} catch (cause) {
|
||
const details = getApiErrorDetails(cause, '派送状态更新失败')
|
||
statusConflict.value = details.code === 'A0409'
|
||
statusError.value = statusConflict.value ? { ...details, message: '派送单已被更新,请刷新最新状态并重新确认后提交。' } : details
|
||
} finally { statusSaving.value = false }
|
||
}
|
||
function search(model: QueryModel) { page.value = 1; void load(model) }
|
||
function reset(model: QueryModel) { page.value = 1; void load(model) }
|
||
async function loadWarehouses() { if (!access.can(permission.warehouseList)) return; warehousesLoading.value = true; try { warehouses.value = (await listReceivingOverseasWarehouses()).records } catch { warehouses.value = [] } finally { warehousesLoading.value = false } }
|
||
onMounted(() => { void load(); void loadWarehouses() })
|
||
</script>
|
||
<template><AdminPage><ListPageLayout aria-label="派送单列表"><template #toolbar><QueryToolbar v-model="filters" class="surface-panel" :fields="fields" :loading="loading" ariaLabel="派送单筛选" preference-key="overseas-delivery-orders-query" :search-on-reset="false" @search="search" @reset="reset" /></template><template v-if="error" #state><LoadErrorState title="派送单列表加载失败" :message="error.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template><section v-if="!error" class="surface-panel table-panel"><DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :loading="loading" :total="total" preference-key="overseas-delivery-orders" row-key="id" empty-text="暂无派送单" @update:page="load()" @update:page-size="page = 1; load()"><template #actions="{ row }"><el-button size="small" type="primary" plain @click="open(row as DeliveryOrderRow)"><Eye :size="15" />详情</el-button><el-button v-if="access.can(permission.deliveryStatus) && availableStatuses(row as DeliveryOrderRow).length > 0" size="small" plain @click="openStatus(row as DeliveryOrderRow)"><Pencil :size="15" />编辑状态</el-button><el-button v-if="canReturn(row as DeliveryOrderRow)" size="small" type="warning" plain @click="openReturn(row as DeliveryOrderRow)"><RotateCcw :size="15" />标记退货</el-button></template><template #documents="{ row }"><div class="delivery-document-summary"><div><strong>{{ row.documentSummary ? `${row.documentSummary.uploadedCategoryCount}/3 已上传` : '资料摘要暂无' }}</strong><el-button v-if="access.can(permission.deliveryList)" size="small" plain @click="openDocuments(row as DeliveryOrderRow)"><FileUp :size="15" />管理资料</el-button></div><div class="delivery-document-categories"><span v-for="category in deliveryDocumentCategories" :key="category.category" :class="{ uploaded: row.documentSummary?.categories.some((item: { category: string; status: string }) => item.category === category.category && item.status === 'UPLOADED') }">{{ category.label }} · {{ row.documentSummary?.categories.find((item: { category: string }) => item.category === category.category)?.status === 'UPLOADED' ? '已上传' : '未上传' }}</span></div></div></template><template #status="{ row }"><StatusBadge :status="statusLabels[String(row.status)] || String(row.status || '—')" /></template><template #pagination-left>共 {{ total }} 条派送单</template></DataTable></section></ListPageLayout></AdminPage><AppDialog v-model="statusOpen" title="编辑派送状态" width="min(520px, calc(100vw - 28px))" confirm-text="保存状态" :confirm-loading="statusSaving" :loading="statusLoading" :confirm-disabled="!statusForm.status || statusConflict || statusLoading" @confirm="submitStatus"><el-form label-position="top"><el-form-item label="派送单号"><el-input :model-value="statusRow?.deliveryNo" readonly /></el-form-item><el-form-item label="派送状态" required><el-select v-model="statusForm.status" placeholder="请选择派送状态"><el-option v-for="option in statusOptions" :key="option.value" :label="option.label" :value="option.value" /></el-select></el-form-item><el-form-item label="备注"><el-input v-model="statusForm.remark" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入状态备注(选填)" /></el-form-item><el-alert v-if="statusForm.status === 'DELIVERED'" title="保存为派送完成后,将自动生成一条待回访记录。" type="info" :closable="false" /></el-form><LoadErrorState v-if="statusError" compact title="派送状态操作失败" :message="statusError.message" :show-retry="statusConflict" @retry="refreshStatus"><template #details><ApiErrorMeta :details="statusError" /></template></LoadErrorState></AppDialog><AppDialog v-model="returnOpen" title="标记退货" width="min(720px, calc(100vw - 28px))" confirm-text="确认转入退货" :loading="returnLoading" :confirm-loading="returnSaving" @confirm="submitReturn"><el-form label-position="top"><el-form-item label="派送单号"><el-input :model-value="returnRow?.deliveryNo" readonly /></el-form-item><el-form-item label="对应入仓单" required><el-select v-model="returnForm.inboundOrderId" placeholder="请选择该派送单对应的入仓单"><el-option v-for="option in returnOptions" :key="String(option.inboundOrderId)" :value="String(option.inboundOrderId)" :label="`${option.inboundCode} · ${option.lastMileOrderNo || '无尾程单号'}`" /></el-select></el-form-item><el-form-item label="退货原因" required><el-input v-model="returnForm.reason" type="textarea" :rows="4" maxlength="1000" show-word-limit placeholder="请输入退货原因" /></el-form-item><div class="return-grid"><el-form-item label="退货去向" required><el-select v-model="returnForm.returnDirection" placeholder="请选择退货去向"><el-option label="退回海外仓" value="OVERSEAS_WAREHOUSE" /><el-option label="退回客户" value="CUSTOMER" /><el-option label="其他" value="OTHER" /></el-select></el-form-item><el-form-item label="退回物流单号"><el-input v-model="returnForm.returnLogisticsNo" placeholder="可安排物流后补充" /></el-form-item></div><el-form-item label="原出库明细" required><div class="return-sources"><p v-if="!returnItems.length">暂无可退原出库明细</p><div v-for="item in returnItems" :key="`${item.outboundPalletItemId}:${item.inventoryPositionId}`" class="return-source"><el-checkbox v-model="item.selected" :disabled="returnSaving || item.maxReturnPackages < 1">{{ item.palletNo || '—' }} · {{ item.skuCode || '—' }}(最多 {{ item.maxReturnPackages }} 箱)</el-checkbox><el-input-number v-model="item.packageCount" placeholder="请输入退货箱数" :disabled="!item.selected || returnSaving" :min="1" :max="item.maxReturnPackages" :precision="0" :aria-label="`退货箱数 ${item.palletNo || '—'} ${item.skuCode || '—'}`" /></div></div></el-form-item><LoadErrorState v-if="returnError" compact title="标记退货失败" :message="returnError.message" :show-retry="false"><template #details><ApiErrorMeta :details="returnError" /></template></LoadErrorState><el-alert title="确认后会生成异常退货单,并将派送单标记为退货处理中。" type="warning" :closable="false" /></el-form></AppDialog><DeliveryDocumentsDialog v-if="documentsRow" v-model="documentsOpen" :delivery-no="documentsRow.deliveryNo" :customer-id="documentsRow.customerId" @saved="load()" /></template>
|
||
|
||
<style scoped>.delivery-document-summary{display:grid;gap:8px;padding:6px 0}.delivery-document-summary>div:first-child{display:flex;align-items:center;justify-content:space-between;gap:12px}.delivery-document-categories{display:flex;flex-wrap:wrap;gap:6px 12px;font-size:12px;color:var(--cc-muted)}.delivery-document-categories .uploaded{color:var(--el-color-success)}.return-sources { width:100%; }.return-source { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:12px; padding:8px 0; }.return-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 18px; }.return-grid :deep(.el-input),.return-grid :deep(.el-select),.return-grid :deep(.el-input-number){width:100%} @media (max-width: 720px) { .return-grid { grid-template-columns: 1fr; } }</style>
|