Merge remote-tracking branch 'origin/release/test' into dev
# Conflicts: # AGENTS.md
This commit is contained in:
@@ -51,7 +51,7 @@
|
||||
| 装柜完成后的退税报关资料 | `src/modules/first-leg/views/FirstLegOrdersView.vue` 的“报关资料”、`components/CustomsDocumentsDialog.vue`、`api/customs.ts` | 只显示登录客户在同柜的退税入仓码;报关开始前可上传、替换、调整同一资料覆盖的一个或多个入仓码及删除误传,全部写入携带幂等键和版本;开始后只读冻结资料,整柜完成后才能下载平台回传的一对一完成文件。Backend `firstleg/customs/container/`、`customs-v1.yaml`,二进制归 `file/` |
|
||||
| 统一订单展示 | `src/modules/order-management/` | `src/modules/first-leg/` 与对应后端订单契约 |
|
||||
| 客户财务账单、入仓码联查、收款、未收余额与客户确认 | `src/modules/finance/`;确认与明细入口 `views/FinanceBillsView.vue`、`api/finance.ts`,入仓码通过 `src/modules/first-leg/public.ts` 复用 `InboundCodeAssociationDialog` | Backend `finance/`;账单行只显示服务端 `inboundCode` 并按登录客户查询业务关系,缺失显示“—”,不得回退内部 ID。收款方式由服务端枚举映射中文。出账或已出账调整后为 `PENDING_CONFIRMATION`,客户确认提交登录成员、`expectedVersion` 和幂等键 |
|
||||
| 客户资金账户与充值申请 | `src/modules/finance/views/FinanceAccountView.vue`、`api/account.ts` | 显示当前企业 CNY 实际、冻结、可用余额和不可变流水;充值只展示服务端启用的微信/支付宝静态收款码,客户上传 JPG/PNG 付款截图后进入待审核。客户范围只取登录态,无收款码时禁止提交,不模拟支付成功 |
|
||||
| 客户资金账户、授信与充值申请 | `src/modules/finance/views/FinanceAccountView.vue`、`api/account.ts` | 显示当前企业 CNY 实际余额、冻结金额、授信额度、已用授信、冻结授信、总可用余额和不可变流水;数据加载前显示“—”,不得先伪装为零。充值只展示服务端启用的微信/支付宝静态收款码,客户上传 JPG/PNG 付款截图后进入待审核。客户范围只取登录态,无收款码时禁止提交,不模拟支付成功 |
|
||||
| 海外采购、美国本土卡与美本土散板 | `src/modules/cross-border/routes.ts`、`components/BusinessOrdersView.vue`、`api/businessOrders.ts` | 三个路由均为 `active`,客户范围只取 Token;采购创建的 `customerName` 只取当前认证会话企业名称,不允许自由输入或冒用其他客户。Backend `lastleg-service/` 的 `procurement/`、`trucking/`、`looseboard/` 客户接口 |
|
||||
| 海外仓入库订单 | `src/modules/order-management/routes.ts`、`views/InboundOrdersView.vue`、`api/inboundPlans.ts` | `orders/inbound` 为只读列表/详情,权限 `merchant:warehouse:inbound:list`;不展示或提交内部 ID |
|
||||
| 海外仓贴标、仓储转运、货物废弃、退货换标 | `src/modules/overseas-warehouse/routes.ts`、`views/WarehouseServiceView.vue`、`api/warehouseServices.ts` | 四个路由均为 `active`;换标申请必须提交正数 CNY `chargeAmount`,页面展示服务端钱包冻结/结算/释放状态及错误,不本地推导余额结果。Backend `lastleg-service/` 的 `relabel/`、`transfer/`、`discard/`,请求统一经 `src/api/overseasHttp.ts` 与 `gateway-overseas/` |
|
||||
|
||||
+11
-2
@@ -39,14 +39,23 @@ export async function overseasApiRequest<T>(config: AxiosRequestConfig) {
|
||||
try {
|
||||
const response = await transport.request<ApiEnvelope<T>>(config)
|
||||
if (!isApiEnvelope(response.data) || response.data.code !== '200') {
|
||||
throw new ApiError(isApiEnvelope(response.data) ? response.data.message : '海外接口响应格式错误', isApiEnvelope(response.data) ? response.data.code : 'INVALID_RESPONSE')
|
||||
throw new ApiError(
|
||||
isApiEnvelope(response.data) ? response.data.message : '海外接口响应格式错误',
|
||||
isApiEnvelope(response.data) ? response.data.code : 'INVALID_RESPONSE',
|
||||
undefined,
|
||||
undefined,
|
||||
isApiEnvelope(response.data) ? response.data.traceId : null,
|
||||
isApiEnvelope(response.data) ? response.data.requestId : null,
|
||||
)
|
||||
}
|
||||
return response.data.data
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError) throw error
|
||||
if (axios.isAxiosError(error)) {
|
||||
const body = error.response?.data
|
||||
if (isApiEnvelope(body)) throw new ApiError(body.message, body.code, error.response?.status)
|
||||
if (isApiEnvelope(body)) {
|
||||
throw new ApiError(body.message, body.code, error.response?.status, undefined, body.traceId, body.requestId)
|
||||
}
|
||||
throw new ApiError(error.code === 'ERR_NETWORK' ? '无法连接海外服务' : error.message, error.code || 'OVERSEAS_HTTP_ERROR', error.response?.status)
|
||||
}
|
||||
throw new ApiError(error instanceof Error ? error.message : '海外接口请求失败')
|
||||
|
||||
Vendored
+1
@@ -54,6 +54,7 @@ declare module 'vue' {
|
||||
QueryToolbar: typeof import('./components/common/QueryToolbar.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SingleLineText: typeof import('./components/common/SingleLineText.vue')['default']
|
||||
WorkspaceTabs: typeof import('./components/WorkspaceTabs.vue')['default']
|
||||
}
|
||||
export interface GlobalDirectives {
|
||||
|
||||
@@ -7,6 +7,9 @@ export interface DataTableColumn {
|
||||
columnKey?: string
|
||||
slotName?: string
|
||||
headerSlotName?: string
|
||||
singleLine?: boolean
|
||||
showOverflowTooltip?: boolean
|
||||
className?: string
|
||||
[attribute: string]: unknown
|
||||
}
|
||||
|
||||
@@ -88,10 +91,34 @@ watch([page, pageCount], ([value, count]) => {
|
||||
if (value !== nextValue) page.value = nextValue
|
||||
}, { immediate: true })
|
||||
|
||||
function columnAttrs({ key, slotName, headerSlotName, ...attrs }: DataTableColumn) {
|
||||
function columnAttrs({ key, slotName, headerSlotName, singleLine, ...attrs }: DataTableColumn) {
|
||||
return attrs
|
||||
}
|
||||
|
||||
function systemColumn(column: DataTableColumn) {
|
||||
return column.type === 'selection'
|
||||
|| column.type === 'index'
|
||||
|| column.type === 'expand'
|
||||
|| column.slotName === 'actions'
|
||||
|| column.label === '操作'
|
||||
}
|
||||
|
||||
function singleLineColumn(column: DataTableColumn) {
|
||||
return column.singleLine !== false && !systemColumn(column)
|
||||
}
|
||||
|
||||
function columnOverflowTooltip(column: DataTableColumn) {
|
||||
return singleLineColumn(column) ? column.showOverflowTooltip ?? true : column.showOverflowTooltip
|
||||
}
|
||||
|
||||
function columnClassName(column: DataTableColumn) {
|
||||
return [
|
||||
column.className,
|
||||
systemColumn(column) ? 'data-table__system-cell' : '',
|
||||
singleLineColumn(column) ? 'data-table__single-line-cell' : '',
|
||||
].filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
function columnRenderKey(column: DataTableColumn, index: number) {
|
||||
return String(column.key ?? column.columnKey ?? column.prop ?? column.property ?? index)
|
||||
}
|
||||
@@ -119,6 +146,8 @@ function setPage(value: number) {
|
||||
v-for="(column, index) in columns"
|
||||
:key="columnRenderKey(column, index)"
|
||||
v-bind="columnAttrs(column)"
|
||||
:class-name="columnClassName(column)"
|
||||
:show-overflow-tooltip="columnOverflowTooltip(column)"
|
||||
>
|
||||
<template v-if="hasSlot(column.headerSlotName)" #header="scope">
|
||||
<slot :name="column.headerSlotName" v-bind="scope" />
|
||||
@@ -168,6 +197,9 @@ function setPage(value: number) {
|
||||
|
||||
<style scoped>
|
||||
.data-table { min-width: 0; }
|
||||
.data-table :deep(.data-table__single-line-cell .cell) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.data-table :deep(.data-table__system-cell .cell) { white-space: nowrap; }
|
||||
.data-table :deep(.data-table__system-cell .el-button + .el-button), .data-table :deep(.data-table__system-cell .el-button + .el-dropdown) { margin-left: 8px; }
|
||||
.data-table__pagination { display: flex; min-height: 58px; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 18px; border-top: 1px solid var(--border); color: var(--muted); font-size: 12px; }
|
||||
.data-table__pagination-left { display: flex; min-width: 0; flex: 1; align-items: center; gap: 8px; }
|
||||
.data-table__pagination-controls, .data-table__page-size, .data-table__page-buttons { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
value?: string | number | null
|
||||
emptyText?: string
|
||||
}>(), {
|
||||
value: null,
|
||||
emptyText: '—',
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span class="single-line-text" :title="value == null || value === '' ? undefined : String(value)">
|
||||
{{ value == null || value === '' ? emptyText : value }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.single-line-text {
|
||||
display: block;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -34,5 +34,7 @@ test('keeps overseas warehouse operations first and renders readable list values
|
||||
assert.match(overseas, /function statusLabel\(value: string\)/)
|
||||
assert.match(overseas, /function deliveryMethodLabel\(value: string\)/)
|
||||
assert.match(overseas, /function formatDateTime\(value: string\)/)
|
||||
assert.match(overseas, /import SingleLineText from '@\/components\/common\/SingleLineText\.vue'/)
|
||||
assert.match(overseas, /<SingleLineText :value="row\[String\(index\)\]"/)
|
||||
assert.match(overseas, /<td><button class="warehouse-link"[^>]*>.*?查看<\/button><\/td><td v-for="\(_, index\) in config\.columns\.slice\(1\)"/s)
|
||||
})
|
||||
|
||||
@@ -17,8 +17,8 @@ export interface InboundPlanSummary {
|
||||
planNo: string
|
||||
inboundCode: string
|
||||
orderType: string | null
|
||||
warehouseCode: string | null
|
||||
warehouseName: string | null
|
||||
overseasWarehouseCode?: string | null
|
||||
overseasWarehouseName?: string | null
|
||||
fbaWarehouseCode: string | null
|
||||
packageCount: number
|
||||
pickedPackageCount: number
|
||||
@@ -52,6 +52,8 @@ export interface InboundPlanPallet {
|
||||
export interface InboundPlanDetail {
|
||||
planNo: string
|
||||
containerNo: string | null
|
||||
overseasWarehouseCode?: string | null
|
||||
overseasWarehouseName?: string | null
|
||||
status: string
|
||||
lineCount: number
|
||||
palletCount: number
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
import { overseasWarehouseLabel } from './inboundPlanDisplay.ts'
|
||||
|
||||
const api = readFileSync(new URL('./api/inboundPlans.ts', import.meta.url), 'utf8')
|
||||
const routes = readFileSync(new URL('./routes.ts', import.meta.url), 'utf8')
|
||||
const view = readFileSync(new URL('./views/InboundOrdersView.vue', import.meta.url), 'utf8')
|
||||
const overseasHttp = readFileSync(new URL('../../api/overseasHttp.ts', import.meta.url), 'utf8')
|
||||
|
||||
test('activates the customer-scoped overseas inbound list and detail', () => {
|
||||
assert.match(routes, /path: 'orders\/inbound'[\s\S]*?release: 'active'[\s\S]*?permission: 'merchant:warehouse:inbound:list'/)
|
||||
assert.match(api, /\/merchant\/v1\/last-leg\/inbound-plans/)
|
||||
assert.match(api, /encodeURIComponent\(planNo\)/)
|
||||
assert.doesNotMatch(api, /\/platform\/|\/internal\/|customerId|tenantId/)
|
||||
assert.doesNotMatch(view, /customerId|tenantId/)
|
||||
})
|
||||
|
||||
test('keeps actions first and exposes filters loading errors empty state pagination and business identifiers', () => {
|
||||
@@ -23,3 +26,49 @@ test('keeps actions first and exposes filters loading errors empty state paginat
|
||||
assert.match(view, /detail\.planNo/)
|
||||
assert.doesNotMatch(view, /planId|orderId|warehouseId|customerId/)
|
||||
})
|
||||
|
||||
test('shows only the server-provided overseas receiving warehouse code and name', () => {
|
||||
assert.equal((api.match(/overseasWarehouseCode\?: string \| null/g) || []).length, 2)
|
||||
assert.equal((api.match(/overseasWarehouseName\?: string \| null/g) || []).length, 2)
|
||||
assert.doesNotMatch(api, /overseasWarehouseId/)
|
||||
assert.match(view, /label="海外接柜仓"/)
|
||||
assert.match(view, /overseasWarehouseLabel\(scope\.row\.overseasWarehouseCode, scope\.row\.overseasWarehouseName\)/)
|
||||
assert.match(view, /overseasWarehouseLabel\(detail\.overseasWarehouseCode, detail\.overseasWarehouseName\)/)
|
||||
assert.doesNotMatch(view, /warehouseName \|\| warehouseCode|destinationWarehouse(?:Id|Code|Name).*overseasWarehouse/i)
|
||||
})
|
||||
|
||||
test('renders a dash when either formal receiving warehouse field is absent', () => {
|
||||
assert.equal(overseasWarehouseLabel('US-LAX-01', '洛杉矶仓'), 'US-LAX-01 · 洛杉矶仓')
|
||||
assert.equal(overseasWarehouseLabel(' US-LAX-01 ', ' 洛杉矶仓 '), 'US-LAX-01 · 洛杉矶仓')
|
||||
for (const pair of [
|
||||
[undefined, undefined], [null, null], ['', '洛杉矶仓'], ['US-LAX-01', ''],
|
||||
[undefined, '洛杉矶仓'], ['US-LAX-01', undefined], [' ', '洛杉矶仓'], ['US-LAX-01', ' '],
|
||||
]) assert.equal(overseasWarehouseLabel(pair[0], pair[1]), '—')
|
||||
})
|
||||
|
||||
test('preserves overseas API diagnostic metadata without rendering it as a business identifier', () => {
|
||||
assert.match(overseasHttp, /response\.data\.traceId/)
|
||||
assert.match(overseasHttp, /response\.data\.requestId/)
|
||||
assert.match(overseasHttp, /body\.traceId, body\.requestId/)
|
||||
assert.doesNotMatch(view, /requestId|traceId/)
|
||||
})
|
||||
|
||||
test('renders inbound workflow statuses in Chinese and keeps desktop values on one line', () => {
|
||||
for (const [status, label] of [
|
||||
['PENDING_ASSIGNMENT', '待分配'], ['PDA_PENDING', '待收货'], ['ASSIGNED', '已分配'],
|
||||
['SIGNED', '已签收'], ['PICKING', '拣货中'], ['PARTIAL_INBOUND', '部分入库'],
|
||||
]) assert.match(view, new RegExp(`${status}: '${label}'`))
|
||||
assert.match(view, /label="状态" width="120" show-overflow-tooltip/)
|
||||
assert.match(view, /\.detail-grid div \{ min-width: 0;/)
|
||||
assert.match(view, /\.detail-grid dd \{[^}]*overflow: hidden;[^}]*text-overflow: ellipsis;[^}]*white-space: nowrap;/)
|
||||
})
|
||||
|
||||
test('uses the formal inbound status selector and presents destination fields without mixing receiving warehouses', () => {
|
||||
assert.match(view, /const inboundStatusOptions = \[/)
|
||||
assert.match(view, /label="作业状态"><el-select v-model="query\.status"/)
|
||||
assert.doesNotMatch(view, /label="作业状态"><el-input/)
|
||||
assert.match(view, /destinationWarehouseLabel\(scope\.row\.fbaWarehouseCode, scope\.row\.destinationWarehouseCode, scope\.row\.destinationWarehouseName\)/)
|
||||
assert.match(view, /destinationWarehouseLabel\(scope\.row\.destinationWarehouseCode, scope\.row\.destinationWarehouseName\)/)
|
||||
assert.doesNotMatch(view, /label="目的仓代码"|label="目的仓名称"/)
|
||||
assert.doesNotMatch(view, /destinationWarehouseLabel\([^)]*overseasWarehouse/)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export function overseasWarehouseLabel(code: unknown, name: unknown) {
|
||||
if (typeof code !== 'string' || !code.trim() || typeof name !== 'string' || !name.trim()) return '—'
|
||||
return `${code.trim()} · ${name.trim()}`
|
||||
}
|
||||
@@ -2,21 +2,42 @@
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getInboundPlan, listInboundPlans, type InboundPlanDetail, type InboundPlanSummary } from '../api/inboundPlans'
|
||||
import { overseasWarehouseLabel } from '../inboundPlanDisplay'
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const rows = ref<InboundPlanSummary[]>([])
|
||||
const detail = ref<InboundPlanDetail | null>(null)
|
||||
const total = ref(0)
|
||||
const inboundStatusOptions = [
|
||||
{ label: '待分配', value: 'PENDING_ASSIGNMENT' },
|
||||
{ label: '已分配', value: 'ASSIGNED' },
|
||||
{ label: '待收货', value: 'PDA_PENDING' },
|
||||
{ label: '已签收', value: 'SIGNED' },
|
||||
{ label: '拣货中', value: 'PICKING' },
|
||||
{ label: '部分入库', value: 'PARTIAL_INBOUND' },
|
||||
{ label: '已完成', value: 'COMPLETED' },
|
||||
]
|
||||
const query = reactive({
|
||||
page: 1, size: 20, planNo: '', inboundCode: '', destinationWarehouseCode: '', status: '',
|
||||
appointmentStatus: '', dispatchStatus: '', createdDates: [] as string[],
|
||||
})
|
||||
|
||||
function text(value: unknown) { return value === null || value === undefined || value === '' ? '—' : String(value) }
|
||||
function destinationWarehouseLabel(...values: unknown[]) {
|
||||
const parts = values
|
||||
.filter((value): value is string => typeof value === 'string' && Boolean(value.trim()))
|
||||
.map((value) => value.trim())
|
||||
return [...new Set(parts)].join(' · ') || '—'
|
||||
}
|
||||
function dateTime(value: unknown) { return value ? new Date(String(value)).toLocaleString('zh-CN', { hour12: false }) : '—' }
|
||||
function statusLabel(value: unknown) {
|
||||
return ({ PENDING: '待处理', PROCESSING: '处理中', COMPLETED: '已完成', CANCELLED: '已取消', UNAPPOINTED: '未预约', SUCCESS: '预约成功', FAILED: '预约失败', SCHEDULED: '已排车', UNSCHEDULED: '未排车' } as Record<string, string>)[String(value)] || text(value)
|
||||
return ({
|
||||
PENDING: '待处理', PROCESSING: '处理中', PENDING_ASSIGNMENT: '待分配', PDA_PENDING: '待收货',
|
||||
ASSIGNED: '已分配', SIGNED: '已签收', PICKING: '拣货中', PARTIAL_INBOUND: '部分入库',
|
||||
COMPLETED: '已完成', CANCELLED: '已取消', UNAPPOINTED: '未预约', SUCCESS: '预约成功',
|
||||
FAILED: '预约失败', SCHEDULED: '已排车', UNSCHEDULED: '未排车',
|
||||
} as Record<string, string>)[String(value)] || text(value)
|
||||
}
|
||||
function errorMessage(cause: unknown) { return cause instanceof Error ? cause.message : '请求失败,请稍后重试' }
|
||||
|
||||
@@ -70,7 +91,7 @@ onMounted(() => void load())
|
||||
<el-form-item label="计划号"><el-input v-model.trim="query.planNo" clearable placeholder="请输入计划号" /></el-form-item>
|
||||
<el-form-item label="入仓码"><el-input v-model.trim="query.inboundCode" clearable placeholder="请输入入仓码" /></el-form-item>
|
||||
<el-form-item label="目的仓"><el-input v-model.trim="query.destinationWarehouseCode" clearable placeholder="请输入仓库代码" /></el-form-item>
|
||||
<el-form-item label="作业状态"><el-input v-model.trim="query.status" clearable placeholder="请输入状态" /></el-form-item>
|
||||
<el-form-item label="作业状态"><el-select v-model="query.status" clearable placeholder="全部"><el-option v-for="option in inboundStatusOptions" :key="option.value" :label="option.label" :value="option.value" /></el-select></el-form-item>
|
||||
<el-form-item label="预约状态"><el-select v-model="query.appointmentStatus" clearable placeholder="全部"><el-option label="未预约" value="UNAPPOINTED" /><el-option label="待处理" value="PENDING" /><el-option label="成功" value="SUCCESS" /><el-option label="失败" value="FAILED" /><el-option label="已取消" value="CANCELLED" /></el-select></el-form-item>
|
||||
<el-form-item label="排车状态"><el-select v-model="query.dispatchStatus" clearable placeholder="全部"><el-option label="未排车" value="UNSCHEDULED" /><el-option label="已排车" value="SCHEDULED" /></el-select></el-form-item>
|
||||
<el-form-item label="创建时间"><el-date-picker v-model="query.createdDates" type="daterange" value-format="YYYY-MM-DD" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item>
|
||||
@@ -82,15 +103,15 @@ onMounted(() => void load())
|
||||
<el-table-column label="操作" width="90" fixed="left"><template #default="scope"><el-button link type="primary" @click="openDetail(scope.row)">查看</el-button></template></el-table-column>
|
||||
<el-table-column prop="planNo" label="计划号" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="inboundCode" label="入仓码" min-width="170" show-overflow-tooltip />
|
||||
<el-table-column label="海外仓" min-width="160" show-overflow-tooltip><template #default="scope">{{ text(scope.row.warehouseName || scope.row.warehouseCode) }}</template></el-table-column>
|
||||
<el-table-column label="海外接柜仓" min-width="220" show-overflow-tooltip><template #default="scope">{{ overseasWarehouseLabel(scope.row.overseasWarehouseCode, scope.row.overseasWarehouseName) }}</template></el-table-column>
|
||||
<el-table-column label="目的仓" min-width="150" show-overflow-tooltip><template #default="scope">{{ text(scope.row.fbaWarehouseCode) }}</template></el-table-column>
|
||||
<el-table-column prop="packageCount" label="计划箱数" width="100" />
|
||||
<el-table-column prop="pickedPackageCount" label="已拣箱数" width="100" />
|
||||
<el-table-column prop="palletCount" label="托盘数" width="90" />
|
||||
<el-table-column label="状态" width="120"><template #default="scope">{{ statusLabel(scope.row.status) }}</template></el-table-column>
|
||||
<el-table-column label="预约状态" width="120"><template #default="scope">{{ statusLabel(scope.row.appointmentStatus) }}</template></el-table-column>
|
||||
<el-table-column label="排车状态" width="110"><template #default="scope">{{ statusLabel(scope.row.dispatchStatus) }}</template></el-table-column>
|
||||
<el-table-column label="创建时间" width="180"><template #default="scope">{{ dateTime(scope.row.createdAt) }}</template></el-table-column>
|
||||
<el-table-column label="状态" width="120" show-overflow-tooltip><template #default="scope">{{ statusLabel(scope.row.status) }}</template></el-table-column>
|
||||
<el-table-column label="预约状态" width="120" show-overflow-tooltip><template #default="scope">{{ statusLabel(scope.row.appointmentStatus) }}</template></el-table-column>
|
||||
<el-table-column label="排车状态" width="110" show-overflow-tooltip><template #default="scope">{{ statusLabel(scope.row.dispatchStatus) }}</template></el-table-column>
|
||||
<el-table-column label="创建时间" width="180" show-overflow-tooltip><template #default="scope">{{ dateTime(scope.row.createdAt) }}</template></el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="query.page" v-model:page-size="query.size" class="pagination" :total="total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next" @change="load" />
|
||||
</el-card>
|
||||
@@ -100,6 +121,7 @@ onMounted(() => void load())
|
||||
<el-card shadow="never">
|
||||
<dl class="detail-grid">
|
||||
<div><dt>计划号</dt><dd>{{ detail.planNo }}</dd></div><div><dt>集装箱号</dt><dd>{{ text(detail.containerNo) }}</dd></div>
|
||||
<div><dt>海外接柜仓</dt><dd>{{ overseasWarehouseLabel(detail.overseasWarehouseCode, detail.overseasWarehouseName) }}</dd></div>
|
||||
<div><dt>状态</dt><dd>{{ statusLabel(detail.status) }}</dd></div><div><dt>入仓码数</dt><dd>{{ detail.lineCount }}</dd></div>
|
||||
<div><dt>托盘数</dt><dd>{{ detail.palletCount }}</dd></div><div><dt>总体积(m³)</dt><dd>{{ detail.totalVolumeCbm }}</dd></div>
|
||||
<div><dt>计划箱数</dt><dd>{{ detail.expectedPackageCount }}</dd></div><div><dt>已拣箱数</dt><dd>{{ detail.pickedPackageCount }}</dd></div>
|
||||
@@ -107,14 +129,14 @@ onMounted(() => void load())
|
||||
</el-card>
|
||||
<el-card shadow="never" header="入仓明细">
|
||||
<el-table :data="detail.lines" border empty-text="暂无入仓明细">
|
||||
<el-table-column prop="inboundCode" label="入仓码" min-width="180" /><el-table-column prop="destinationWarehouseCode" label="目的仓代码" min-width="140" />
|
||||
<el-table-column prop="destinationWarehouseName" label="目的仓名称" min-width="160" /><el-table-column prop="requiredPackageCount" label="计划箱数" width="100" />
|
||||
<el-table-column prop="inboundCode" label="入仓码" min-width="180" /><el-table-column label="目的仓" min-width="220" show-overflow-tooltip><template #default="scope">{{ destinationWarehouseLabel(scope.row.fbaWarehouseCode, scope.row.destinationWarehouseCode, scope.row.destinationWarehouseName) }}</template></el-table-column>
|
||||
<el-table-column prop="requiredPackageCount" label="计划箱数" width="100" />
|
||||
<el-table-column prop="plannedPalletCount" label="计划托数" width="100" /><el-table-column prop="volumeCbm" label="体积(m³)" width="110" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
<el-card shadow="never" header="托盘明细">
|
||||
<el-table :data="detail.pallets" border empty-text="暂无托盘">
|
||||
<el-table-column prop="palletNo" label="托盘码" min-width="180" /><el-table-column prop="destinationWarehouseCode" label="目的仓" min-width="150" />
|
||||
<el-table-column prop="palletNo" label="托盘码" min-width="180" /><el-table-column label="目的仓" min-width="220" show-overflow-tooltip><template #default="scope">{{ destinationWarehouseLabel(scope.row.destinationWarehouseCode, scope.row.destinationWarehouseName) }}</template></el-table-column>
|
||||
<el-table-column prop="capacityCbm" label="容量(m³)" width="120" /><el-table-column prop="allocatedVolumeCbm" label="已分配体积(m³)" width="140" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
@@ -131,7 +153,7 @@ onMounted(() => void load())
|
||||
.filter-card :deep(.el-input), .filter-card :deep(.el-select) { width: 190px; }
|
||||
.pagination { justify-content: flex-end; margin-top: 18px; }
|
||||
.detail-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin: 0; }
|
||||
.detail-grid div { padding: 18px 20px; border-right: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5; }
|
||||
.detail-grid div { min-width: 0; padding: 18px 20px; border-right: 1px solid #ebeef5; border-bottom: 1px solid #ebeef5; }
|
||||
.detail-grid dt { margin-bottom: 7px; color: #8492a6; font-size: 13px; }
|
||||
.detail-grid dd { margin: 0; overflow-wrap: anywhere; color: #1f2937; }
|
||||
.detail-grid dd { margin: 0; overflow: hidden; color: #1f2937; text-overflow: ellipsis; white-space: nowrap; }
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowLeft, Download, Eye, Plus, Search, Trash2, Upload } from '@lucide/vue'
|
||||
import SingleLineText from '@/components/common/SingleLineText.vue'
|
||||
import {
|
||||
createDiscard, createRelabel, createTransfer, getDiscard, getRelabel, getTransfer,
|
||||
listDiscardCandidates, listDiscards, listLastlegOrders, listRelabels, listTransfers,
|
||||
@@ -15,10 +16,10 @@ interface ServiceConfig { title: string; createTitle: string; numberLabel: strin
|
||||
|
||||
const props = defineProps<{ service: ServiceType }>()
|
||||
const configs: Record<ServiceType, ServiceConfig> = {
|
||||
labeling: { title: '贴标申请', createTitle: '新增贴标申请', numberLabel: '贴标申请单号', emptyText: '暂无贴标申请', columns: ['贴标申请单号', '客户单号', '目的海外仓', '贴标要求', 'SKU 数', '总箱数', '状态', '创建时间', '操作'] },
|
||||
transfer: { title: '仓储转运单', createTitle: '新增仓储转运单', numberLabel: '转运单号', emptyText: '暂无仓储转运订单', columns: ['转运单号', '进仓编号', '发货箱数', '操作仓', 'FBA 目的仓', '派送方式', '是否换标', '状态', '创建时间', '操作'] },
|
||||
disposal: { title: '货物废弃', createTitle: '新增货物废弃申请', numberLabel: '废弃申请单号', emptyText: '暂无货物废弃申请', columns: ['废弃申请单号', '进仓编号', '操作仓', 'SKU 汇总', '废弃数量', '预估费用', '状态', '创建时间', '操作'] },
|
||||
relabel: { title: '退货换标', createTitle: '新建退货换标单', numberLabel: '退货换标单号', emptyText: '暂无退货换标订单', columns: ['退货换标单号', '来源 FBA 仓', '目的 FBA 仓', 'SKU 汇总', '退件总数', '是否换标', '换标状态', '尾程状态', '创建时间', '操作'] },
|
||||
labeling: { title: '贴标申请', createTitle: '新增贴标申请', numberLabel: '贴标申请单号', emptyText: '暂无贴标申请', columns: ['操作', '贴标申请单号', '客户单号', '目的海外仓', '贴标要求', 'SKU 数', '总箱数', '状态', '创建时间'] },
|
||||
transfer: { title: '仓储转运单', createTitle: '新增仓储转运单', numberLabel: '转运单号', emptyText: '暂无仓储转运订单', columns: ['操作', '转运单号', '进仓编号', '发货箱数', '操作仓', 'FBA 目的仓', '派送方式', '是否换标', '状态', '创建时间'] },
|
||||
disposal: { title: '货物废弃', createTitle: '新增货物废弃申请', numberLabel: '废弃申请单号', emptyText: '暂无货物废弃申请', columns: ['操作', '废弃申请单号', '进仓编号', '操作仓', 'SKU 汇总', '废弃数量', '预估费用', '状态', '创建时间'] },
|
||||
relabel: { title: '退货换标', createTitle: '新建退货换标单', numberLabel: '退货换标单号', emptyText: '暂无退货换标订单', columns: ['操作', '退货换标单号', '来源 FBA 仓', '目的 FBA 仓', 'SKU 汇总', '退件总数', '是否换标', '换标状态', '尾程状态', '创建时间'] },
|
||||
}
|
||||
const config = computed(() => configs[props.service])
|
||||
const statusOptions = computed(() => props.service === 'labeling'
|
||||
@@ -117,6 +118,21 @@ function warehouseDisplay(value?: string) {
|
||||
const labels: Record<string, string> = { ONT8: '美西海外仓-ONT8', ABE8: '美东海外仓-ABE8', MDW2: '美中海外仓-MDW2' }
|
||||
return value.split(',').map((warehouse) => labels[warehouse] || warehouse).join('、')
|
||||
}
|
||||
function statusLabel(value: string) {
|
||||
const labels: Record<string, string> = {
|
||||
PENDING_PROCESSING: '待处理', PROCESSING: '处理中', COMPLETED: '已完成', CANCELLED: '已取消',
|
||||
PENDING_REVIEW: '待审核', APPROVED: '已通过', REJECTED: '已拒绝', PENDING_ASSIGNMENT: '待分配',
|
||||
ALLOCATED: '已分配', OUTBOUNDED: '已出库', PARTIALLY_RECEIVED: '部分收货', RECEIVED: '已收货',
|
||||
}
|
||||
return labels[value] || value || '—'
|
||||
}
|
||||
function deliveryMethodLabel(value: string) {
|
||||
const labels: Record<string, string> = { TRUCK: '卡车派送', EXPRESS: '快递派送' }
|
||||
return labels[value] || value || '—'
|
||||
}
|
||||
function formatDateTime(value: string) {
|
||||
return value ? value.replace('T', ' ').slice(0, 19) : '—'
|
||||
}
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -124,16 +140,16 @@ async function load() {
|
||||
const result = await listRelabels({ page: 1, size: 20, relabelNo: filters.number || undefined, overseasWarehouseId: filters.warehouse || undefined, status: filters.status || undefined, createdFrom: dateTime(filters.start), createdTo: dateTime(filters.end, true) })
|
||||
submittedRows.value = result.records.map((item) => {
|
||||
const destinationWarehouses = warehouseDisplay(item.overseasWarehouseId)
|
||||
return row([item.relabelNo, item.customerOrderNo, destinationWarehouses, '—', item.itemCount, item.totalPackageCount, item.status, item.createdAt])
|
||||
return row([item.relabelNo, item.customerOrderNo, destinationWarehouses, '—', item.itemCount, item.totalPackageCount, statusLabel(item.status), formatDateTime(item.createdAt)])
|
||||
})
|
||||
} else if (props.service === 'disposal') {
|
||||
const result = await listDiscards({ page: 1, size: 20, discardNo: filters.number || undefined, inboundCode: filters.inboundNo || undefined, overseasWarehouseId: filters.warehouse || undefined, status: filters.status || undefined, createdFrom: dateTime(filters.start), createdTo: dateTime(filters.end, true) })
|
||||
submittedRows.value = result.records.map((item) => row([item.discardNo, item.inboundCodes, item.overseasWarehouseName || '—', '—', item.totalPackages, `${item.currency} ${item.totalFee}`, item.status, item.createdAt]))
|
||||
submittedRows.value = result.records.map((item) => row([item.discardNo, item.inboundCodes, item.overseasWarehouseName || '—', '—', item.totalPackages, `${item.currency} ${item.totalFee}`, statusLabel(item.status), formatDateTime(item.createdAt)]))
|
||||
} else {
|
||||
const result = await listTransfers({ page: 1, size: 20, transferNo: filters.number || undefined, inboundCode: filters.inboundNo || undefined, operatingWarehouseId: filters.warehouse || undefined, fbaWarehouseCode: filters.destination || undefined, status: filters.status || undefined, needRelabel: props.service === 'relabel', createdFrom: dateTime(filters.start), createdTo: dateTime(filters.end, true) })
|
||||
submittedRows.value = result.records.map((item) => props.service === 'transfer'
|
||||
? row([item.transferNo, item.inboundCode, item.packageCount, '—', item.fbaWarehouseCode, item.deliveryMethod, item.needRelabel ? '是' : '否', item.status, item.createdAt])
|
||||
: row([item.transferNo, '—', item.fbaWarehouseCode, '—', item.packageCount, '是', item.relabelNo ? '已生成' : '待生成', item.status, item.createdAt]))
|
||||
? row([item.transferNo, item.inboundCode, item.packageCount, '—', item.fbaWarehouseCode, deliveryMethodLabel(item.deliveryMethod), item.needRelabel ? '是' : '否', statusLabel(item.status), formatDateTime(item.createdAt)])
|
||||
: row([item.transferNo, '—', item.fbaWarehouseCode, '—', item.packageCount, '是', item.relabelNo ? '已生成' : '待生成', statusLabel(item.status), formatDateTime(item.createdAt)]))
|
||||
}
|
||||
} catch (error) { ElMessage.error(error instanceof Error ? error.message : '列表加载失败') }
|
||||
finally { loading.value = false }
|
||||
@@ -215,7 +231,7 @@ async function openDetail(value: Record<string, string>) {
|
||||
<div class="warehouse-filter-actions"><el-button type="primary" @click="query"><Search :size="16" />查询</el-button><el-button @click="resetFilters(); query()">重置</el-button></div>
|
||||
</div>
|
||||
<div v-loading="loading" class="warehouse-card warehouse-table-card">
|
||||
<div class="warehouse-table-scroll"><table><thead><tr><th v-for="column in config.columns" :key="column">{{ column }}</th></tr></thead><tbody><tr v-for="(row, rowIndex) in submittedRows" :key="rowIndex"><td v-for="(_, index) in config.columns.slice(0, -1)" :key="index">{{ row[String(index)] }}</td><td><button class="warehouse-link" type="button" @click="openDetail(row)"><Eye :size="15" />查看</button></td></tr><tr v-if="submittedRows.length === 0"><td :colspan="config.columns.length"><div class="warehouse-empty">{{ config.emptyText }}</div></td></tr></tbody></table></div>
|
||||
<div class="warehouse-table-scroll"><table><thead><tr><th v-for="column in config.columns" :key="column">{{ column }}</th></tr></thead><tbody><tr v-for="(row, rowIndex) in submittedRows" :key="rowIndex"><td><button class="warehouse-link" type="button" @click="openDetail(row)"><Eye :size="15" />查看</button></td><td v-for="(_, index) in config.columns.slice(1)" :key="index"><SingleLineText :value="row[String(index)]" /></td></tr><tr v-if="submittedRows.length === 0"><td :colspan="config.columns.length"><div class="warehouse-empty">{{ config.emptyText }}</div></td></tr></tbody></table></div>
|
||||
<div class="warehouse-pagination"><span>共 <strong>{{ submittedRows.length }}</strong> 条</span><el-button size="small" disabled>上一页</el-button><button type="button" class="warehouse-page-number">1</button><el-button size="small" disabled>下一页</el-button></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -239,19 +255,19 @@ async function openDetail(value: Record<string, string>) {
|
||||
<label v-if="props.service === 'labeling'"><span><i>*</i>联系人</span><el-input v-model="form.contact" placeholder="请输入联系人" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span><i>*</i>联系电话</span><el-input v-model="form.phone" placeholder="请输入联系电话" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span><i>*</i>贴标截止时间</span><el-date-picker v-model="form.labelDeadline" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择截止时间" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span><i>*</i>贴标费用(CNY)</span><el-input-number v-model="form.chargeAmount" :min="0" :precision="2" controls-position="right" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span><i>*</i>贴标费用(CNY)</span><el-input-number v-model="form.chargeAmount" :min="0" :precision="2" controls-position="right" placeholder="请输入贴标费用" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'" class="warehouse-span-2"><span><i>*</i>详细派送地址</span><el-input v-model="form.address" placeholder="请输入派送地址" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>总体积(CBM)</span><el-input-number v-model="form.volumeCbm" :min="0.0001" :precision="4" controls-position="right" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>总体积(CBM)</span><el-input-number v-model="form.volumeCbm" :min="0.0001" :precision="4" controls-position="right" placeholder="请输入总体积" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>预计到达时间</span><el-date-picker v-model="form.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计到达时间" /></label>
|
||||
<label v-if="props.service === 'transfer' || props.service === 'relabel'"><span><i>*</i>FBA 货件编号</span><el-input v-model="form.fbaShipmentNo" placeholder="请输入 FBA 货件编号" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" /></label>
|
||||
<label v-else-if="props.service !== 'disposal'"><span>物流跟踪号</span><el-input v-model="form.trackingNo" /></label>
|
||||
<label v-if="props.service === 'labeling'"><span>贴标要求</span><el-input v-model="form.requirement" placeholder="请输入贴标要求(选填)" /></label>
|
||||
<label v-else-if="props.service !== 'disposal'"><span>物流跟踪号</span><el-input v-model="form.trackingNo" placeholder="请输入物流跟踪号(选填)" /></label>
|
||||
<label class="warehouse-span-2"><span>订单备注</span><el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" /></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="warehouse-card warehouse-form-section"><div class="warehouse-section-heading"><h3>货物明细</h3><div v-if="props.service !== 'disposal'"><template v-if="props.service === 'labeling'"><el-button size="small" @click="downloadRelabelTemplate"><Download :size="15" />下载关联模板</el-button><el-button size="small" @click="templateInput?.click()"><Upload :size="15" />上传关联面单</el-button><input ref="templateInput" class="warehouse-hidden-input" type="file" accept=".csv,text/csv" @change="importRelabelTemplate"></template><el-button type="primary" size="small" @click="addCargoRow"><Plus :size="15" />添加行</el-button><el-button size="small" @click="cargoRows = []">清空</el-button></div></div>
|
||||
<p v-if="props.service === 'labeling'" class="warehouse-attachment-tip">模板每行代表一箱;上传后直接回显到下方明细。明细目的仓优先,留空时使用顶部统一目的海外仓。</p>
|
||||
<div class="warehouse-table-scroll"><table class="warehouse-edit-table" :class="{ 'warehouse-label-table': props.service === 'labeling' }"><thead><tr><th>商品 SKU<span v-if="props.service !== 'labeling'"> *</span></th><th>中文品名 *</th><th v-if="props.service === 'disposal'">可用箱数</th><th v-else>{{ props.service === 'labeling' ? 'FBA 货件编号 *' : 'FNSKU / 箱唛' }}</th><th v-if="props.service === 'labeling'">换标新 SKU *</th><th v-if="props.service !== 'labeling'">{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *</th><template v-if="props.service === 'labeling'"><th>我方箱唛</th><th>FBA 箱唛</th><th>目的仓</th><th>备注</th></template><th v-if="props.service !== 'disposal'">操作</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" placeholder="选填" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-else><el-input v-if="props.service === 'labeling'" v-model="row.shipmentNo" /><el-input v-else v-model="row.shippingMark" /></td><td v-if="props.service === 'labeling'"><el-input v-model="row.newSkuCode" /></td><td v-if="props.service !== 'labeling'"><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="props.service === 'disposal' ? row.availableQuantity : undefined" controls-position="right" /></td><template v-if="props.service === 'labeling'"><td><el-input v-model="row.cartonMark" placeholder="请输入我方箱唛" /></td><td><el-input v-model="row.fbaCartonMark" placeholder="请输入 FBA 箱唛" /></td><td><el-select v-model="row.destinationWarehouseId" clearable placeholder="使用顶部统一目的仓"><el-option label="美西海外仓-ONT8" value="ONT8" /><el-option label="美东海外仓-ABE8" value="ABE8" /><el-option label="美中海外仓-MDW2" value="MDW2" /></el-select></td><td><el-input v-model="row.remark" type="textarea" :rows="2" maxlength="1000" show-word-limit placeholder="请输入明细备注" /></td></template><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" /></button></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'labeling' ? 8 : 5"><div class="warehouse-empty">{{ props.service === 'disposal' ? '请先选择入库单,系统将自动带出 SKU 和可用箱数' : '暂无货物明细,请点击“添加行”' }}</div></td></tr></tbody></table></div>
|
||||
<div class="warehouse-table-scroll"><table class="warehouse-edit-table" :class="{ 'warehouse-label-table': props.service === 'labeling' }"><thead><tr><th>商品 SKU<span v-if="props.service !== 'labeling'"> *</span></th><th>中文品名 *</th><th v-if="props.service === 'disposal'">可用箱数</th><th v-else>{{ props.service === 'labeling' ? 'FBA 货件编号 *' : 'FNSKU / 箱唛' }}</th><th v-if="props.service === 'labeling'">换标新 SKU *</th><th v-if="props.service !== 'labeling'">{{ props.service === 'disposal' ? '本次废弃箱数' : props.service === 'relabel' ? '退件数量' : '箱数' }} *</th><template v-if="props.service === 'labeling'"><th>我方箱唛</th><th>FBA 箱唛</th><th>目的仓</th><th>备注</th></template><th v-if="props.service !== 'disposal'">操作</th></tr></thead><tbody><tr v-for="row in cargoRows" :key="row.id"><td><el-input v-model="row.sku" :readonly="props.service === 'disposal'" placeholder="请输入商品 SKU(选填)" /></td><td><el-input v-model="row.name" :readonly="props.service === 'disposal'" placeholder="请输入中文品名" /></td><td v-if="props.service === 'disposal'"><strong>{{ row.availableQuantity }} 箱</strong></td><td v-else><el-input v-if="props.service === 'labeling'" v-model="row.shipmentNo" placeholder="请输入 FBA 货件编号" /><el-input v-else v-model="row.shippingMark" placeholder="请输入 FNSKU 或箱唛" /></td><td v-if="props.service === 'labeling'"><el-input v-model="row.newSkuCode" placeholder="请输入换标新 SKU" /></td><td v-if="props.service !== 'labeling'"><el-input-number v-model="row.quantity" :min="props.service === 'disposal' ? 0 : 1" :max="props.service === 'disposal' ? row.availableQuantity : undefined" controls-position="right" placeholder="请输入箱数" /></td><template v-if="props.service === 'labeling'"><td><el-input v-model="row.cartonMark" placeholder="请输入我方箱唛" /></td><td><el-input v-model="row.fbaCartonMark" placeholder="请输入 FBA 箱唛" /></td><td><el-select v-model="row.destinationWarehouseId" clearable placeholder="使用顶部统一目的仓"><el-option label="美西海外仓-ONT8" value="ONT8" /><el-option label="美东海外仓-ABE8" value="ABE8" /><el-option label="美中海外仓-MDW2" value="MDW2" /></el-select></td><td><el-input v-model="row.remark" type="textarea" :rows="2" maxlength="1000" show-word-limit placeholder="请输入明细备注" /></td></template><td v-if="props.service !== 'disposal'"><button type="button" class="warehouse-delete" title="删除" @click="removeCargoRow(row.id)"><Trash2 :size="16" /></button></td></tr><tr v-if="cargoRows.length === 0"><td :colspan="props.service === 'labeling' ? 8 : 5"><div class="warehouse-empty">{{ props.service === 'disposal' ? '请先选择入库单,系统将自动带出 SKU 和可用箱数' : '暂无货物明细,请点击“添加行”' }}</div></td></tr></tbody></table></div>
|
||||
</div>
|
||||
<div class="warehouse-submit-bar"><el-button @click="resetForm">重置</el-button><el-button type="primary" native-type="submit" :loading="saving">提交{{ config.title }}</el-button></div>
|
||||
</form>
|
||||
@@ -260,7 +276,7 @@ async function openDetail(value: Record<string, string>) {
|
||||
<template v-else>
|
||||
<div class="warehouse-breadcrumb"><button type="button" @click="mode = 'list'">{{ config.title }}</button><b>/</b><strong>订单详情</strong></div>
|
||||
<div class="warehouse-page-heading"><h2>{{ config.title }}详情</h2><el-button @click="mode = 'list'"><ArrowLeft :size="16" />返回列表</el-button></div>
|
||||
<div class="warehouse-card warehouse-form-section"><h3>订单概览</h3><div class="warehouse-detail-grid"><div v-for="(column, index) in config.columns.slice(0, -1)" :key="column"><span>{{ column }}</span><strong>{{ selectedDetail?.[String(index)] || '—' }}</strong></div></div></div>
|
||||
<div class="warehouse-card warehouse-form-section"><h3>订单概览</h3><div class="warehouse-detail-grid"><div v-for="(column, index) in config.columns.slice(1)" :key="column"><span>{{ column }}</span><strong>{{ selectedDetail?.[String(index)] || '—' }}</strong></div></div></div>
|
||||
<div v-if="props.service === 'labeling'" class="warehouse-card warehouse-form-section">
|
||||
<h3>箱唛换标明细</h3>
|
||||
<div class="warehouse-table-scroll"><table class="warehouse-relabel-detail-table"><thead><tr><th>我方箱唛</th><th>FBA 箱唛</th><th>商品 SKU</th><th>中文品名</th><th>FBA 货件编号</th><th>换标新 SKU</th><th>目的仓</th><th>贴标要求</th><th>备注</th></tr></thead><tbody>
|
||||
|
||||
Reference in New Issue
Block a user