This commit is contained in:
2026-08-26 11:10:22 +08:00
parent 53c92d4d37
commit d03f7b3f5d
3 changed files with 168 additions and 77 deletions
@@ -110,6 +110,36 @@ export interface LastMileStatusHistoryDto {
createdAt?: string | null
}
export interface LastMileFeeDto {
id: string
customerId: string
orderId: string
feeItem: string
currency: string
amount: number
status: string
supplierDescription?: string | null
createdBy: string
createdAt: string
updatedAt: string
}
export interface LastMileOperationDto {
id: string
operationNode: string
operatorName: string
result: string
createdAt: string
}
export interface CreateLastMileFeePayload {
feeItem: string
currency: string
amount: number
status: string
supplierDescription?: string
}
export interface TransferDestinationPayload {
orderIds: string[]
warehouseId: string
@@ -126,6 +156,8 @@ export interface LastMileOrderDetailDto {
order: LastMileOrderDto
cargo: LastMileCargoDto[]
statusHistory: LastMileStatusHistoryDto[]
fees: LastMileFeeDto[]
operationHistory: LastMileOperationDto[]
}
export async function listLastMileOrders(query: LastMileOrderQuery) {
@@ -143,6 +175,13 @@ export async function getLastMileOrder(orderId: string) {
return unwrapApiResponse(response)
}
export async function createLastMileOrderFee(orderId: string, payload: CreateLastMileFeePayload) {
const response = await overseasHttp.post<ApiEnvelope<LastMileFeeDto>, ApiEnvelope<LastMileFeeDto>>(
`/platform/v1/last-leg/orders/${encodeURIComponent(orderId)}/fees`, payload,
)
return unwrapApiResponse(response)
}
export async function transferLastMileOrders(payload: TransferDestinationPayload) {
const response = await overseasHttp.put<ApiEnvelope<number>, ApiEnvelope<number>>(
'/platform/v1/last-leg/orders/destination', payload,
@@ -1,66 +1,71 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, reactive, ref } from 'vue'
import { Plus } from '@lucide/vue'
import { ElMessage } from 'element-plus'
import { useRoute, useRouter } from 'vue-router'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { getLastMileOrder, type LastMileOrderDetailDto } from '../api/lastMileOrders'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import { createLastMileOrderFee, getLastMileOrder, type LastMileOrderDetailDto } from '../api/lastMileOrders'
import { formatOverseasDateTime, lastMileOrderTypeLabel, lastMileStatusLabel } from '../lastMilePresentation'
import { overseasWarehousePermissions as permission } from '../permissions'
import OverseasDetailShell from '../components/OverseasDetailShell.vue'
interface DetailField { label: string; value: string | number }
interface DocumentRow { type: string; number: string; createdAt: string; quote: string }
interface CargoRow { id: string; sku: string; productName: string; pallets: number; cartons: number; quantity: number; weight: number; volume: number }
interface LogRow { id: string; occurredAt: string; node: string; operator: string; result: string }
interface DocumentRow { type: string; number: string; createdAt: string; quote: string }
interface FeeRow { id: string; item: string; currency: string; amount: string; status: string; description: string }
interface LogRow { id: string; occurredAt: string; node: string; operator: string; result: string; sortAt: string }
const route = useRoute()
const router = useRouter()
const access = useAccessPreviewStore()
const orderId = computed(() => String(route.params.orderId || ''))
const detail = ref<LastMileOrderDetailDto | null>(null)
const loading = ref(false)
const saving = ref(false)
const loadError = ref<ApiErrorDetails | null>(null)
const mutationError = ref<ApiErrorDetails | null>(null)
const activeTab = ref('basic')
const feeDialogOpen = ref(false)
const feeForm = reactive({ item: '', currency: 'USD', amount: 0, status: 'PENDING_REVIEW', description: '' })
function text(value: unknown) { return String(value ?? '').trim() || '—' }
function yesNo(value: boolean | null | undefined) { return value == null ? '—' : value ? '是' : '否' }
function feeStatusLabel(value: string) { return ({ PENDING_REVIEW: '待审核', APPROVED: '审核通过', REJECTED: '已驳回' } as Record<string, string>)[value] || value }
const referenceValue = computed(() => text(detail.value?.order.orderNo || orderId.value))
const orderFields = computed<DetailField[]>(() => {
const order = detail.value?.order
return [
{ label: '订单类型', value: text(order?.orderTypeName || lastMileOrderTypeLabel(order?.orderType)) },
{ label: '拒收时间', value: '—' },
{ label: '实际入库时间', value: '—' },
{ label: '头程单号', value: text(order?.sourceOrderNo) },
{ label: '入仓码', value: text(order?.inboundCode) },
{ label: '货主', value: text(order?.customerName) },
{ label: '海外仓', value: text(order?.overseasWarehouseName) },
{ label: '转仓目的仓', value: text(order?.transferWarehouseName) },
{ label: 'FBA仓库', value: text(order?.fbaWarehouseCode) },
{ label: '集装箱号', value: text(order?.containerNo) },
{ label: '货柜异常', value: yesNo(order?.abnormal) },
{ label: '目的仓', value: text(order?.fbaWarehouseCode || order?.transferWarehouseName) },
{ label: '是否暂扣', value: yesNo(order?.detained) },
{ label: '是否异常', value: yesNo(order?.abnormal) },
{ label: '异常原因', value: order?.abnormal ? text(order.subOrderStatus) : '—' },
{ label: '运输方式', value: '—' },
{ label: '预计到仓', value: formatOverseasDateTime(order?.estimatedWarehouseAt) },
{ label: '清关状态', value: text(order?.containerStatus) },
{ label: '备注(客户)', value: text(order?.customerRemark) },
]
})
const detailFields = computed<DetailField[]>(() => {
const containerFields = computed<DetailField[]>(() => {
const order = detail.value?.order
return [
{ label: '订单类型', value: text(order?.orderTypeName || lastMileOrderTypeLabel(order?.orderType)) },
{ label: '入仓码', value: text(order?.inboundCode) },
{ label: '目的仓', value: text(order?.transferWarehouseName) },
{ label: '运输渠道', value: text(order?.orderTypeName || lastMileOrderTypeLabel(order?.orderType)) },
{ label: '集装箱号', value: text(order?.containerNo) },
{ label: '托数', value: Number(order?.palletCount || 0) },
{ label: '箱数', value: Number(order?.packageCount || 0) },
{ label: '数量', value: Number(order?.productQuantity || 0) },
{ label: '体积重量', value: Number(order?.volumeWeightKg || 0) },
{ label: '体积(m³)', value: Number(order?.volumeCbm || 0) },
{ label: '预计到港时间', value: formatOverseasDateTime(order?.estimatedArrivalAt) },
{ label: '实际到港时间', value: formatOverseasDateTime(order?.actualArrivalAt) },
{ label: '预计到仓', value: formatOverseasDateTime(order?.estimatedWarehouseAt) },
{ label: '是否暂扣', value: yesNo(order?.detained) },
{ label: '备注(仅自己)', value: text(order?.privateRemark) },
{ label: '备注(客户)', value: text(order?.customerRemark) },
{ label: '备注(仓库)', value: text(order?.warehouseRemark) },
{ label: '预计到仓时间', value: formatOverseasDateTime(order?.estimatedWarehouseAt) },
{ label: '清关状态', value: text(order?.containerStatus) },
]
})
const planFields = computed<DetailField[]>(() => {
@@ -69,89 +74,132 @@ const planFields = computed<DetailField[]>(() => {
{ label: '入仓码', value: text(order?.inboundCode) },
{ label: '计划入库仓', value: text(order?.transferWarehouseName || order?.overseasWarehouseName) },
{ label: '合托批次', value: text(order?.palletMergeFlag) },
{ label: '合托后托数', value: Number(order?.palletCount || 0) },
{ label: 'FBA预约号', value: '—' },
{ label: '预约状态', value: '—' },
{ label: '计划托数', value: Number(order?.palletCount || 0) },
{ label: '入库单号', value: text(order?.inboundOrderNo) },
{ label: '换标状态', value: lastMileStatusLabel(order?.relabelStatus) },
]
})
const documents = computed<DocumentRow[]>(() => {
const order = detail.value?.order
if (!order) return []
return [
['入库单', order.inboundOrderNo],
['出库单(只读关联)', order.outboundOrderNo],
['派送单(只读关联)', order.deliveryOrderNo],
['换标单', order.relabelOrderNo],
].filter((item) => item[1]).map(([type, number]) => ({ type: String(type), number: String(number), createdAt: '—', quote: '—' }))
return [['入库单', order.inboundOrderNo], ['出库单(只读关联)', order.outboundOrderNo], ['派送单(只读关联)', order.deliveryOrderNo], ['换标单', order.relabelOrderNo]]
.filter((item) => item[1]).map(([type, number]) => ({ type: String(type), number: String(number), createdAt: '—', quote: '—' }))
})
const cargo = computed<CargoRow[]>(() => (detail.value?.cargo || []).map((item) => ({
id: String(item.id), sku: text(item.skuCode), productName: text(item.productNameZh), pallets: Number(item.palletCount || 0),
cartons: Number(item.packageCount || 0), quantity: Number(item.productQuantity || 0), weight: Number(item.grossWeightKg || 0), volume: Number(item.volumeCbm || 0),
})))
const logs = computed<LogRow[]>(() => (detail.value?.statusHistory || []).map((item) => ({
id: String(item.id), occurredAt: formatOverseasDateTime(item.createdAt), node: lastMileStatusLabel(item.toStatus), operator: '—',
result: text(item.remark || `${lastMileStatusLabel(item.fromStatus)} → ${lastMileStatusLabel(item.toStatus)}`),
const fees = computed<FeeRow[]>(() => (detail.value?.fees || []).map((item) => ({
id: String(item.id), item: item.feeItem, currency: item.currency, amount: `${item.currency} ${Number(item.amount).toFixed(2)}`,
status: feeStatusLabel(item.status), description: text(item.supplierDescription),
})))
const logs = computed<LogRow[]>(() => {
const statusRows = (detail.value?.statusHistory || []).map((item) => ({
id: `status-${item.id}`, occurredAt: formatOverseasDateTime(item.createdAt), sortAt: item.createdAt || '', node: lastMileStatusLabel(item.toStatus), operator: '系统',
result: text(item.remark || `${lastMileStatusLabel(item.fromStatus)} → ${lastMileStatusLabel(item.toStatus)}`),
}))
const operationRows = (detail.value?.operationHistory || []).map((item) => ({
id: `operation-${item.id}`, occurredAt: formatOverseasDateTime(item.createdAt), sortAt: item.createdAt || '', node: item.operationNode, operator: text(item.operatorName), result: text(item.result),
}))
return [...statusRows, ...operationRows].sort((left, right) => right.sortAt.localeCompare(left.sortAt))
})
const documentColumns: DataTableColumn[] = [
{ key: 'type', prop: 'type', label: '单据类型', minWidth: 120 }, { key: 'number', prop: 'number', label: '单据编号', minWidth: 180 },
{ key: 'createdAt', prop: 'createdAt', label: '生成时间', minWidth: 160 }, { key: 'quote', prop: 'quote', label: '报价', minWidth: 140 },
]
const cargoColumns: DataTableColumn[] = [
{ key: 'sku', prop: 'sku', label: 'SKU', minWidth: 150 }, { key: 'productName', prop: 'productName', label: '中文品名', minWidth: 180 },
{ key: 'pallets', prop: 'pallets', label: '托数', width: 90, align: 'right' }, { key: 'cartons', prop: 'cartons', label: '箱数', width: 90, align: 'right' },
{ key: 'quantity', prop: 'quantity', label: '件数', width: 90, align: 'right' }, { key: 'weight', prop: 'weight', label: '重量(kg)', width: 110, align: 'right' },
{ key: 'volume', prop: 'volume', label: '体积(m³)', width: 110, align: 'right' },
{ key: 'pallets', prop: 'pallets', label: '托数', width: 100, align: 'right' }, { key: 'cartons', prop: 'cartons', label: '箱数', width: 100, align: 'right' },
{ key: 'quantity', prop: 'quantity', label: '件数', width: 100, align: 'right' }, { key: 'weight', prop: 'weight', label: '重量(kg)', width: 120, align: 'right' },
{ key: 'volume', prop: 'volume', label: '体积(m³)', width: 120, align: 'right' },
]
const documentColumns: DataTableColumn[] = [
{ key: 'type', prop: 'type', label: '单据类型', minWidth: 150 }, { key: 'number', prop: 'number', label: '单据编号', minWidth: 200 },
{ key: 'createdAt', prop: 'createdAt', label: '生成时间', minWidth: 170 }, { key: 'quote', prop: 'quote', label: '报价', minWidth: 150 },
]
const feeColumns: DataTableColumn[] = [
{ key: 'item', prop: 'item', label: '费用项目', minWidth: 180 }, { key: 'currency', prop: 'currency', label: '币种', width: 120 },
{ key: 'amount', prop: 'amount', label: '金额', minWidth: 160 }, { key: 'status', prop: 'status', label: '状态', width: 140 },
{ key: 'description', prop: 'description', label: '供应商/说明', minWidth: 260 },
]
const logColumns: DataTableColumn[] = [
{ key: 'occurredAt', prop: 'occurredAt', label: '时间', minWidth: 160 }, { key: 'node', prop: 'node', label: '操作节点', minWidth: 140 },
{ key: 'operator', prop: 'operator', label: '操作人', minWidth: 130 }, { key: 'result', prop: 'result', label: '处理结果', minWidth: 220 },
{ key: 'occurredAt', prop: 'occurredAt', label: '时间', minWidth: 180 }, { key: 'node', prop: 'node', label: '操作节点', minWidth: 180 },
{ key: 'operator', prop: 'operator', label: '操作人', minWidth: 150 }, { key: 'result', prop: 'result', label: '处理结果', minWidth: 320 },
]
async function load() {
if (!orderId.value) {
loadError.value = { message: '详情缺少尾程订单号' }
return
}
loading.value = true
loadError.value = null
if (!orderId.value) { loadError.value = { message: '详情缺少尾程订单号' }; return }
loading.value = true; loadError.value = null
try { detail.value = await getLastMileOrder(orderId.value) }
catch (error) {
loadError.value = error instanceof Error && error.message === '当前环境未签发海外访问令牌'
? { message: error.message }
: getApiErrorDetails(error, '尾程订单详情加载失败,请重试')
}
catch (error) { loadError.value = error instanceof Error && error.message === '当前环境未签发海外访问令牌' ? { message: error.message } : getApiErrorDetails(error, '尾程订单详情加载失败,请重试') }
finally { loading.value = false }
}
function openFeeDialog() {
Object.assign(feeForm, { item: '', currency: 'USD', amount: 0, status: 'PENDING_REVIEW', description: '' })
mutationError.value = null; feeDialogOpen.value = true
}
async function saveFee() {
if (!feeForm.item.trim() || feeForm.amount <= 0) { ElMessage.warning('请填写费用项目和大于 0 的金额'); return }
saving.value = true; mutationError.value = null
try {
await createLastMileOrderFee(orderId.value, { feeItem: feeForm.item.trim(), currency: feeForm.currency, amount: feeForm.amount, status: feeForm.status, supplierDescription: feeForm.description.trim() || undefined })
feeDialogOpen.value = false; ElMessage.success('费用已新增'); await load(); activeTab.value = 'fees'
} catch (error) { mutationError.value = getApiErrorDetails(error, '费用新增失败,请重试') }
finally { saving.value = false }
}
function back() { void router.push({ name: 'overseas-last-mile-orders' }) }
onMounted(load)
</script>
<template>
<OverseasDetailShell title="尾程订单详情" reference-label="尾程单号" :reference-value="referenceValue" back-label="返回尾程订单" domain="尾程订单详情" :pending="false" :status-text="detail ? lastMileStatusLabel(detail.order.currentStatus) : '加载中'" @back="back">
<OperationErrorToast :details="mutationError" />
<LoadErrorState v-if="loadError" title="尾程订单详情加载失败" :message="loadError.message" :loading="loading" @retry="load"><template #details><ApiErrorMeta :details="loadError" /></template></LoadErrorState>
<div v-else v-loading="loading" class="detail-grid" :aria-busy="loading">
<section class="detail-section" aria-labelledby="last-mile-order-heading"><h2 id="last-mile-order-heading">订单信息</h2><dl class="detail-fields"><div v-for="field in orderFields" :key="field.label"><dt>{{ field.label }}</dt><dd>{{ field.value }}</dd></div></dl></section>
<section class="detail-section" aria-labelledby="last-mile-lines-heading"><h2 id="last-mile-lines-heading">订单明细</h2><dl class="detail-fields"><div v-for="field in detailFields" :key="field.label"><dt>{{ field.label }}</dt><dd>{{ field.value }}</dd></div></dl></section>
<section class="detail-section" aria-labelledby="last-mile-plan-heading"><h2 id="last-mile-plan-heading">计划信息</h2><dl class="detail-fields"><div v-for="field in planFields" :key="field.label"><dt>{{ field.label }}</dt><dd>{{ field.value }}</dd></div></dl></section>
<section class="detail-section table-section" aria-labelledby="last-mile-documents-heading"><h2 id="last-mile-documents-heading">关联单据</h2><DataTable :data="documents" :columns="documentColumns" column-settings-mode="hide" empty-text="暂无关联单据" /></section>
<section class="detail-section table-section is-wide" aria-labelledby="last-mile-cargo-heading"><h2 id="last-mile-cargo-heading">货物信息</h2><DataTable :data="cargo" :columns="cargoColumns" column-settings-mode="hide" row-key="id" empty-text="暂无货物明细" /></section>
<section class="detail-section table-section" aria-labelledby="last-mile-logs-heading"><h2 id="last-mile-logs-heading">操作记录</h2><DataTable :data="logs" :columns="logColumns" column-settings-mode="hide" row-key="id" empty-text="暂无操作记录" /></section>
</div>
<section v-else v-loading="loading" class="detail-surface" :aria-busy="loading">
<el-tabs v-model="activeTab" class="detail-tabs">
<el-tab-pane label="基本信息" name="basic">
<div class="basic-grid">
<section class="info-panel"><h2>订单信息</h2><dl class="detail-fields"><div v-for="field in orderFields" :key="field.label"><dt>{{ field.label }}</dt><dd>{{ field.value }}</dd></div></dl></section>
<section class="info-panel"><h2>货柜信息</h2><dl class="detail-fields"><div v-for="field in containerFields" :key="field.label"><dt>{{ field.label }}</dt><dd>{{ field.value }}</dd></div></dl></section>
</div>
</el-tab-pane>
<el-tab-pane label="货物信息" name="cargo"><section class="table-panel"><DataTable :data="cargo" :columns="cargoColumns" column-settings-mode="hide" row-key="id" empty-text="暂无货物明细"><template #toolbar-start><h2>货物信息</h2></template></DataTable></section></el-tab-pane>
<el-tab-pane label="计划信息" name="plan">
<section class="info-panel plan-panel"><h2>计划信息</h2><dl class="detail-fields plan-fields"><div v-for="field in planFields" :key="field.label"><dt>{{ field.label }}</dt><dd>{{ field.value }}</dd></div></dl></section>
<section class="table-panel related-panel"><DataTable :data="documents" :columns="documentColumns" column-settings-mode="hide" empty-text="暂无关联单据"><template #toolbar-start><h2>关联单据</h2></template></DataTable></section>
</el-tab-pane>
<el-tab-pane label="费用明细" name="fees"><section class="table-panel"><DataTable :data="fees" :columns="feeColumns" column-settings-mode="hide" row-key="id" empty-text="暂无费用明细"><template #toolbar-start><h2>费用明细</h2></template><template #toolbar-actions><el-button v-if="access.can(permission.lastMileOrderTransfer)" type="primary" @click="openFeeDialog"><Plus :size="16" />新增费用</el-button></template></DataTable></section></el-tab-pane>
<el-tab-pane label="操作记录" name="logs"><section class="table-panel"><DataTable :data="logs" :columns="logColumns" column-settings-mode="hide" row-key="id" empty-text="暂无操作记录"><template #toolbar-start><h2>操作记录</h2></template></DataTable></section></el-tab-pane>
</el-tabs>
</section>
<AppDialog v-model="feeDialogOpen" title="新增费用" width="min(720px, calc(100vw - 28px))" confirm-text="保存费用" :confirm-loading="saving" @confirm="saveFee">
<el-form label-position="top"><el-row :gutter="20">
<el-col :span="12"><el-form-item label="费用项目" required><el-input v-model="feeForm.item" maxlength="128" placeholder="如 派送费" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="供应商/说明"><el-input v-model="feeForm.description" maxlength="500" placeholder="请输入供应商或费用说明" /></el-form-item></el-col>
<el-col :span="12"><el-form-item label="币种" required><el-select v-model="feeForm.currency"><el-option v-for="currency in ['USD', 'CNY', 'EUR', 'GBP', 'CAD', 'MXN']" :key="currency" :label="currency" :value="currency" /></el-select></el-form-item></el-col>
<el-col :span="12"><el-form-item label="金额" required><el-input-number v-model="feeForm.amount" :min="0" :precision="2" :step="10" controls-position="right" /></el-form-item></el-col>
<el-col :span="24"><el-form-item label="状态" required><el-select v-model="feeForm.status"><el-option label="待审核" value="PENDING_REVIEW" /><el-option label="审核通过" value="APPROVED" /><el-option label="已驳回" value="REJECTED" /></el-select></el-form-item></el-col>
</el-row></el-form>
</AppDialog>
</OverseasDetailShell>
</template>
<style scoped>
.detail-grid { display: grid; min-width: 0; min-height: 240px; grid-template-columns: repeat(2, minmax(0, 1fr)); border: 1px solid var(--cc-border); background: #fff; }
.detail-section { min-width: 0; padding: 16px 18px 18px; border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }
.detail-section:nth-child(even) { border-right: 0; }
.detail-section.is-wide { grid-column: 1 / -1; border-right: 0; }
.detail-section h2 { margin: 0 0 12px; color: var(--cc-text); font-size: 15px; }
.detail-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; border-top: 1px solid var(--cc-border); border-left: 1px solid var(--cc-border); }
.detail-fields > div { min-width: 0; min-height: 58px; padding: 9px 11px; border-right: 1px solid var(--cc-border); border-bottom: 1px solid var(--cc-border); }
.detail-fields dt { color: var(--cc-muted); font-size: 12px; }
.detail-fields dd { margin: 4px 0 0; color: var(--cc-text); overflow-wrap: anywhere; }
.table-section { padding-right: 0; padding-bottom: 0; padding-left: 0; overflow: hidden; }
.table-section h2 { margin-right: 18px; margin-left: 18px; }
.detail-surface { min-width: 0; min-height: 300px; margin-top: 16px; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; overflow: hidden; }
.detail-tabs :deep(.el-tabs__header) { margin: 0; padding: 0 20px; background: #f8fafc; }
.detail-tabs :deep(.el-tabs__nav-wrap::after) { height: 1px; background: var(--cc-border); }
.detail-tabs :deep(.el-tabs__item) { height: 54px; padding: 0 22px; color: var(--cc-muted); font-size: 15px; }
.detail-tabs :deep(.el-tabs__item.is-active) { color: var(--el-color-primary); }
.detail-tabs :deep(.el-tabs__content) { padding: 20px; }
.basic-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; }
.info-panel, .table-panel { min-width: 0; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; overflow: hidden; }
.info-panel h2 { margin: 0; padding: 16px 20px; border-bottom: 1px solid var(--cc-border); color: var(--cc-text); font-size: 16px; }
.detail-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 0; padding: 8px 20px 16px; }
.detail-fields > div { display: grid; min-width: 0; min-height: 58px; grid-template-columns: minmax(100px, 34%) minmax(0, 1fr); align-items: center; border-bottom: 1px solid var(--cc-border); }
.detail-fields dt { color: var(--cc-muted); font-size: 13px; }
.detail-fields dd { min-width: 0; margin: 0; color: var(--cc-text); font-weight: 600; overflow-wrap: anywhere; }
.plan-panel { margin-bottom: 20px; }
.plan-fields { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.plan-fields > div { grid-template-columns: minmax(90px, 42%) minmax(0, 1fr); }
.related-panel { margin-top: 20px; }
.table-panel :deep(.data-table__toolbar) { min-height: 58px; }
@media (max-width: 1100px) { .basic-grid { grid-template-columns: 1fr; } .plan-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 720px) { .detail-tabs :deep(.el-tabs__header) { padding: 0 10px; } .detail-tabs :deep(.el-tabs__content) { padding: 12px; } .detail-tabs :deep(.el-tabs__item) { padding: 0 12px; } .detail-fields, .plan-fields { grid-template-columns: 1fr; } }
</style>
@@ -111,7 +111,8 @@ describe('overseas warehouse first-batch prototype contract', () => {
expect(value).toContain('<OverseasDetailShell')
expect(value).toContain('@back="back"')
expect(value).not.toContain('AppDrawer')
expect(value).not.toContain('AppDialog')
if (fileName === 'LastMileOrderDetailView.vue') expect(value).toContain('title="新增费用"')
else expect(value).not.toContain('AppDialog')
}
})
@@ -128,7 +129,10 @@ describe('overseas warehouse first-batch prototype contract', () => {
expect(tailLeg).toContain('getLastMileOrder')
expect(tailLeg).toContain(':data="cargo"')
expect(tailLeg).toContain('<LoadErrorState')
expect(tailLeg).not.toContain('ElMessage.success')
expect(tailLeg).toContain('createLastMileOrderFee')
expect(tailLeg).toContain('label="费用明细"')
expect(tailLeg).toContain('label="操作记录"')
expect(tailLeg).toContain('title="新增费用"')
const inbound = source('InboundOrderDetailView.vue')
expect(inbound).toContain('getInboundPlan')
expect(inbound).toContain('sourceOrderId')