This commit is contained in:
2026-08-13 17:12:05 +08:00
parent 5aa060def2
commit fb6465104d
13 changed files with 776 additions and 220 deletions
@@ -11,7 +11,7 @@ test('routes the confirmed first-leg path to the implemented order view', () =>
assert.match(routerSource, /path: 'orders\/first-leg'[\s\S]*?component: \(\) => import\('\.\/views\/FirstLegOrdersView\.vue'\)[\s\S]*?release: 'active'/)
})
test('opens order creation as a dedicated protected page while keeping draft editing in a drawer', () => {
test('opens order creation as a dedicated protected page while keeping draft editing in a large dialog', () => {
assert.match(routerSource, /path: 'orders\/first-leg\/create'[\s\S]*?name: 'first-leg-order-create'[\s\S]*?release: 'active'[\s\S]*?permission: 'order:first-leg:create'/)
assert.match(routerSource, /name: 'first-leg-order-create'[\s\S]*?props: \{ createPage: true \}/)
assert.match(source, /router\.push\(\{ name: 'first-leg-order-create' \}\)/)
@@ -19,7 +19,10 @@ test('opens order creation as a dedicated protected page while keeping draft edi
assert.match(source, /const isCreatePage = computed\(\(\) => props\.createPage\)/)
assert.match(source, /<FirstLegOrderEditorShell[^>]*:page="isCreatePage"/)
assert.match(editorShellSource, /<section v-if="page" class="first-leg-editor-page">/)
assert.match(editorShellSource, /<el-drawer[\s\S]*v-else/)
assert.match(editorShellSource, /<el-dialog[\s\S]*v-else/)
assert.match(editorShellSource, /class="first-leg-editor-dialog"/)
assert.match(editorShellSource, /width="94vw"/)
assert.doesNotMatch(editorShellSource, /<el-drawer/)
assert.match(editorShellSource, /返回订单列表/)
})
@@ -56,15 +59,24 @@ test('defers approval checks to the server when the member cannot read the compa
assert.match(source, /store\.submit\([^\n]*knownCompanyAuditStatus\)/)
})
test('renders dynamic cargo lines and responsive drawers', () => {
test('renders dynamic cargo lines and a responsive editor dialog', () => {
assert.match(source, /v-for="\(item, index\) in form\.items"/)
assert.match(source, /function addCargoItem\(\)/)
assert.match(source, /function removeCargoItem\(index: number\)/)
assert.match(source, /:global\(\.first-leg-editor\).*width: 100% !important/)
assert.match(editorShellSource, /first-leg-editor-dialog\.el-dialog\) \{[^}]*height: 94vh;/)
assert.match(editorShellSource, /width: 100vw !important; height: 100vh;/)
assert.match(source, /class="mobile-order-list"/)
assert.match(source, /\.desktop-order-table \{ display: none; \}/)
})
test('uses the full editor dialog width for cargo details and cost estimates', () => {
assert.match(source, /:class="\{ 'is-dialog-order-form': !isCreatePage \}"/)
assert.match(source, /\.order-form\.is-dialog-order-form \{ width: 100%; max-width: none; \}/)
assert.match(source, /\.is-dialog-order-form > \.cargo-section, \.is-dialog-order-form > \.cost-estimate-section \{ width: 100%; max-width: none; \}/)
assert.match(source, /\.cost-estimate-cards \{ display: grid; grid-template-columns: repeat\(3, minmax\(0, 1fr\)\);/)
assert.match(source, /\.cargo-table-wrap \{ width: 100%;/)
})
test('keeps primary mobile order actions touch sized', () => {
assert.match(source, /\.editor-actions :deep\(\.el-button\) \{ min-height: 44px;/)
})
@@ -73,15 +85,106 @@ test('uses the shared query toolbar and data table without metric cards', () =>
assert.match(source, /import QueryToolbar, \{ type QueryField, type QueryModel \}/)
assert.match(source, /import DataTable, \{ type DataTableColumn \}/)
assert.match(source, /const filters = ref<QueryModel>/)
assert.match(source, /<QueryToolbar v-model="filters" class="first-leg-query-toolbar" :fields="queryFields" ariaLabel="订单查询" @search="searchOrders" \/>/)
assert.match(source, /<QueryToolbar v-model="filters" class="first-leg-query-toolbar" :fields="queryFields" ariaLabel="订单查询" @search="searchOrders">/)
assert.match(source, /async function searchOrders\(model: QueryModel\)/)
assert.match(source, /await store\.load\(\{/)
assert.match(source, /<DataTable v-model:page="tablePage" v-model:page-size="tablePageSize" :data="pagedOrders" :columns="orderColumns"/)
assert.match(source, /\{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' \}/)
assert.match(source, /<DataTable v-model:page="tablePage" v-model:page-size="tablePageSize" :data="pagedOrders" :columns="visibleOrderColumns"/)
assert.match(source, /\{ key: 'actions', label: '操作', width: 128, fixed: 'left', align: 'center', headerAlign: 'center', slotName: 'actions' \}/)
assert.doesNotMatch(source, /order-metrics|订单概览/)
})
test('shows one unified progress line, C02 business summaries, and optional customer-visible nodes', () => {
test('adds prototype download actions to the order query panel', () => {
assert.match(source, /下载唛头/)
assert.match(source, /导出订单信息/)
assert.match(source, /function downloadShippingMarks\(\)/)
assert.match(source, /downloadMerchantOrderCartonLabelsBatch\(orders\.map\(\(order\) => order\.id\)\)/)
assert.match(source, /error\.traceId \? `,追踪号 \$\{error\.traceId\}`/)
assert.doesNotMatch(source, /order\.businessSummary\.inboundId/)
assert.match(source, /批量箱唛-\$\{orders\.length\}单\.pdf/)
assert.match(source, /function exportOrderInformation\(\)/)
assert.match(source, /selectedOrders\.value\.map/)
assert.match(source, /订单信息\.csv/)
assert.doesNotMatch(source, /exportOrderInformation[\s\S]{0,1200}loadDetail/)
})
test('selects orders before downloading marks or exporting last-mile data', () => {
assert.match(source, /\{ key: 'selection', type: 'selection', width: 48, fixed: 'left', reserveSelection: true \}/)
assert.match(source, /const selectedOrders = ref<FirstLegOrder\[\]>\(\[\]\)/)
assert.match(source, /const selectedOrderCount = computed\(\(\) => selectedOrders\.value\.length\)/)
assert.match(source, /@selection-change="handleOrderSelectionChange"/)
assert.match(source, /:disabled="selectedOrderCount === 0" @click="downloadShippingMarks"/)
assert.match(source, /:disabled="selectedOrderCount === 0" @click="exportOrderInformation"/)
assert.match(source, /const orders = selectedOrders\.value\s*$/m)
assert.match(source, /const rows = selectedOrders\.value\.map/)
assert.doesNotMatch(source, /const orders = filteredOrders\.value\.filter/)
assert.doesNotMatch(source, /const rows = filteredOrders\.value\.map/)
})
test('downloads selected carton labels with one array request', () => {
const downloadStart = source.indexOf('async function downloadShippingMarks()')
const downloadEnd = source.indexOf('function formatExportDateTime', downloadStart)
const downloadSource = source.slice(downloadStart, downloadEnd)
assert.match(downloadSource, /await downloadMerchantOrderCartonLabelsBatch\(orders\.map\(\(order\) => order\.id\)\)/)
assert.match(downloadSource, /saveBlob\(result\.blob/)
assert.match(downloadSource, /result\.failureCount/)
assert.doesNotMatch(downloadSource, /for \(const order of orders\)/)
assert.doesNotMatch(downloadSource, /Promise\.all\(/)
assert.doesNotMatch(downloadSource, /businessSummary\.inboundId\)/)
assert.doesNotMatch(downloadSource, /没有已生成的入库单|未生成入库单/)
})
test('exports every visible order field with normalized dates', () => {
assert.match(source, /function formatExportDateTime\(value: string \| null \| undefined\)/)
assert.match(source, /return `\$\{date\.getFullYear\(\)\}-\$\{pad\(date\.getMonth\(\) \+ 1\)\}-\$\{pad\(date\.getDate\(\)\)\} \$\{pad\(date\.getHours\(\)\)\}:\$\{pad\(date\.getMinutes\(\)\)\}:\$\{pad\(date\.getSeconds\(\)\)\}`/)
for (const label of ['订单号', '入仓码', '渠道', '目的仓', '当前订单状态', '箱数', '快递公司', '快递单号', 'SKUS', '总毛重', '总净重', '总体积', '总数量', '计费单价', '计费体积', '计费重量', '备注', '货件编号', '跟踪编号', '国内仓入库时间', '业务日期', '创建时间']) {
assert.match(source, new RegExp(`'${label}'`))
}
assert.match(source, /formatExportDateTime\(order\.inboundedAt\)/)
assert.match(source, /formatExportDateTime\(order\.businessDate\)/)
assert.match(source, /formatExportDateTime\(order\.createdAt\)/)
})
test('matches the prototype with selectable dense order-list columns', () => {
assert.match(source, /aria-label="设置订单列表显示列"/)
assert.match(source, /<SlidersHorizontal :size="18"/)
assert.match(source, /<el-checkbox-group v-model="visibleColumnKeys"/)
assert.match(source, /draggable="true"/)
assert.match(source, /@drop\.prevent="dropOrderColumn/)
assert.match(source, /function moveOrderColumn/)
assert.match(source, /function resetOrderColumns/)
assert.match(source, />恢复默认<\/el-button>/)
assert.match(source, /const visibleOrderColumns = computed/)
assert.match(source, /:columns="visibleOrderColumns"/)
for (const label of ['入仓码', '渠道', '目的仓', '当前订单状态', '箱数', '快递公司', '快递单号', 'SKUS', '总毛重', '总净重', '总体积', '总数量', '计费单价', '计费体积', '计费重量', '备注', '货件编号', '跟踪编号', '国内仓入库时间', '业务日期']) {
assert.match(source, new RegExp(`label: '${label}'`))
}
assert.match(source, /order\.businessSummary\.totalPackageCount/)
assert.match(source, /summarizeFirstLegCargo\(order\.items\)/)
assert.match(source, /order\.items\.reduce/)
assert.match(source, /<template #chargeableWeight>-<\/template>/)
assert.match(source, /<template #billingUnitPrice>-<\/template>/)
assert.match(source, /<template #chargeableVolume>-<\/template>/)
assert.match(source, /inboundedAt/)
assert.match(source, /businessDate/)
assert.match(source, /<template #businessDate>-<\/template>/)
assert.match(source, /<template #createdAt="\{ row \}">\{\{ formatDateTime\(\(row as FirstLegOrder\)\.createdAt\) \}\}<\/template>/)
assert.match(source, /order\.skuNos\.length/)
assert.doesNotMatch(source, /pagedOrders[\s\S]{0,100}loadDetail/)
})
test('keeps dense order identifiers on one line and aligns columns by content type', () => {
assert.match(source, /key: 'order'[\s\S]{0,160}showOverflowTooltip: true/)
assert.match(source, /key: 'status'[\s\S]{0,100}align: 'center'[\s\S]{0,100}headerAlign: 'center'/)
assert.match(source, /key: 'grossWeight'[\s\S]{0,100}align: 'right'[\s\S]{0,100}headerAlign: 'right'/)
assert.match(source, /class="order-number-link table-code"/)
assert.doesNotMatch(source, /<template #order="\{ row \}">[\s\S]{0,400}class="order-reference"/)
assert.match(source, /\.table-ellipsis, \.table-multivalue \{[^}]*text-overflow: ellipsis;[^}]*white-space: nowrap;/)
assert.match(source, /\.order-number-link \{[^}]*text-overflow: ellipsis;[^}]*white-space: nowrap;/)
assert.match(source, /\.prototype-order-table :deep\(\.el-table__body-wrapper td\.el-table__cell\) \{ height: 50px;/)
})
test('shows one unified progress line and C02 business summaries without detail history', () => {
assert.match(source, /import OrderProgressPanel/)
assert.match(source, /<OrderProgressPanel :order="detailOrder" \/>/)
assert.match(source, /<h3>关键业务摘要<\/h3>/)
@@ -91,9 +194,7 @@ test('shows one unified progress line, C02 business summaries, and optional cust
assert.match(source, /summary\.deliveryTrackingNo/)
assert.match(progressSource, /FIRST_LEG_PROGRESS_STAGES/)
assert.match(progressSource, /国内装柜进度及后续履约同步/)
assert.match(progressSource, /item\.kind === 'CUSTOM_NODE'/)
assert.match(progressSource, /业务节点/)
assert.match(progressSource, /暂无进度记录/)
assert.doesNotMatch(progressSource, /进度记录|el-timeline|timeline/)
assert.match(source, /报关回执/)
assert.match(source, /船期摘要/)
assert.match(source, /柜次进度/)
@@ -103,8 +204,9 @@ test('shows one unified progress line, C02 business summaries, and optional cust
test('uses only fields present on C02 rows before loading full detail', () => {
assert.match(source, /getOrderCargoSummary/)
assert.match(source, /目的地 \/ 渠道/)
assert.match(source, /运输方式待更新|渠道待更新/)
assert.match(source, /label: '渠道'/)
assert.match(source, /label: '目的仓'/)
assert.match(source, /displayListValue/)
assert.doesNotMatch(source, /key: 'operator'/)
assert.doesNotMatch(source, /key: 'transportMode', type: 'select'/)
assert.match(source, /store\.draftLoadError/)
@@ -114,9 +216,9 @@ test('uses only fields present on C02 rows before loading full detail', () => {
})
test('shows and searches the inbound code after a draft is submitted', () => {
assert.match(source, /placeholder: '订单号、入仓码、渠道或仓库'/)
assert.match(source, /label: '入仓码 \/ SKU'/)
assert.match(source, /order\.businessSummary\.inboundCode/)
assert.match(source, /v-if="\(row as FirstLegOrder\)\.businessSummary\.inboundCode"[^>]*>入仓码/)
assert.match(source, /<template #inboundCode="\{ row \}"><span class="table-ellipsis table-code"[^>]*>\{\{ displayListValue\(\(row as FirstLegOrder\)\.businessSummary\.inboundCode\) \}\}<\/span><\/template>/)
assert.match(source, /v-if="order\.businessSummary\.inboundCode"[^>]*>入仓码/)
assert.match(source, /v-if="detailOrder\.businessSummary\.inboundCode"><dt>入仓码<\/dt>/)
})
@@ -135,10 +237,10 @@ test('keeps the first viewport compact and moves create into the table toolbar',
assert.match(source, /client-main:has\(> \.first-leg-page:not\(\.is-create-route\)\) > \.client-page-heading > div\) \{ position: absolute; width: 1px; height: 1px;/)
})
test('keeps desktop query actions on the first row and only shows the qualification notice before approval', () => {
assert.match(source, /\.first-leg-query-toolbar \{ flex-wrap: nowrap; \}/)
assert.match(source, /query-toolbar__filters\) \{ display: grid; min-width: 0; flex: 1 1 auto;/)
assert.match(source, /query-toolbar__actions\) \{ align-self: flex-start; flex-wrap: nowrap; margin-left: auto;/)
test('uses the prototype query grid and only shows the qualification notice before approval', () => {
assert.match(source, /\.first-leg-query-toolbar \{ display: block;/)
assert.match(source, /query-toolbar__filters\) \{ display: grid; min-width: 0; grid-template-columns: repeat\(4,/)
assert.match(source, /query-toolbar__actions\) \{ justify-content: flex-start; margin-top: 14px;/)
assert.match(source, /v-if="companySubmissionBlocked && !supportingDataErrors\.some/)
assert.doesNotMatch(source, /企业资料已审核,可正常提交订单/)
assert.doesNotMatch(source, /order-eligibility\.is-approved/)
@@ -161,9 +263,9 @@ test('requires an enabled platform receiving warehouse in the order form', () =>
assert.match(source, /v-model="form\.receivingWarehouseId"/)
assert.doesNotMatch(source, /selectedWarehouse\.businessHours/)
assert.doesNotMatch(source, /class="selected-warehouse" aria-label="所选收货仓详情"/)
assert.match(source, /first-leg-editor \.el-drawer__header\) \{[^}]*margin-bottom: 0;[^}]*border-bottom: 1px solid var\(--border\)/)
assert.match(source, /first-leg-editor \.el-drawer__body\) \{[^}]*min-height: 0;[^}]*padding: 18px 22px 32px;[^}]*overscroll-behavior: contain/)
assert.match(source, /first-leg-editor \.el-drawer__footer\) \{[^}]*border-top: 1px solid var\(--border\);[^}]*background: #fff/)
assert.match(editorShellSource, /first-leg-editor-dialog \.el-dialog__header\) \{[^}]*border-bottom: 1px solid var\(--border\)/)
assert.match(editorShellSource, /first-leg-editor-dialog \.el-dialog__body\) \{[^}]*min-height: 0;[^}]*overflow-y: auto;[^}]*overscroll-behavior: contain/)
assert.match(editorShellSource, /first-leg-editor-dialog \.el-dialog__footer\) \{[^}]*border-top: 1px solid var\(--border\);[^}]*background: #fff/)
assert.match(source, /detailOrder\.receivingWarehouseSnapshot\?\.name/)
assert.match(source, /detailOrder\.receivingWarehouseSnapshot\.code/)
assert.match(source, /loadSelectedWarehouse\(detailedOrder\.receivingWarehouseId\)/)
@@ -297,7 +399,7 @@ test('shows fulfillment courier information as read-only client detail', () => {
})
test('shows the full customer-safe order detail without platform-only controls', () => {
const detailSource = source.slice(source.indexOf('<el-drawer v-model="detailOpen"'), source.indexOf('</el-drawer>', source.indexOf('<el-drawer v-model="detailOpen"')))
const detailSource = source.slice(source.indexOf('<el-dialog v-model="detailOpen"'), source.indexOf('</el-dialog>', source.indexOf('<el-dialog v-model="detailOpen"')))
for (const heading of ['订单与运输', '收发货信息', '关键业务摘要', '履约快递信息', '货物明细']) {
assert.match(source, new RegExp(`<h3>${heading}<\\/h3>`))
}
@@ -314,6 +416,21 @@ test('shows the full customer-safe order detail without platform-only controls',
assert.doesNotMatch(detailSource, /内部备注|审核通过|添加备注/)
})
test('opens order detail in a large dialog and paginates cargoes as a read-only table', () => {
assert.match(source, /<el-dialog v-model="detailOpen" class="first-leg-detail-dialog" title="订单详情" width="94vw" top="3vh"/)
assert.doesNotMatch(source, /<el-drawer v-model="detailOpen"/)
assert.match(source, /const detailCargoPage = ref\(1\)/)
assert.match(source, /const detailCargoPageSize = ref\(5\)/)
assert.match(source, /const pagedDetailCargoItems = computed/)
assert.match(source, /items\.slice\(start, start \+ detailCargoPageSize\.value\)/)
assert.match(source, /<el-table :data="pagedDetailCargoItems"[^>]*class="cargo-detail-table"/)
assert.match(source, /v-model:current-page="detailCargoPage"/)
assert.match(source, /v-model:page-size="detailCargoPageSize"/)
assert.match(source, /:total="detailOrder\.items\.length"/)
assert.doesNotMatch(source, /class="cargo-detail-list"/)
assert.match(source, /first-leg-detail-dialog\.el-dialog\) \{[^}]*height: 94vh;/)
})
test('loads merchant inbound billing as an isolated read-only detail section', () => {
assert.match(source, /import InboundBillingPanel/)
assert.match(source, /import \{ getMerchantInboundBilling \}/)