优化
This commit is contained in:
@@ -3,7 +3,7 @@ import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { http } from '@/shared/api/http'
|
||||
import {
|
||||
adjustSmartLoadingItem, assignSmartLoadingGroup, assignSmartLoadingItem, getContainer,
|
||||
adjustSmartLoadingItem, assignPendingReviewSmartLoadingOrder, assignSmartLoadingGroup, assignSmartLoadingItem, getContainer,
|
||||
listContainers, listSmartLoadingCandidates, listSmartLoadingWorkbenchContainers, removeSmartLoadingGroup, removeSmartLoadingItem,
|
||||
getContainerClearance, downloadContainerClearance, uploadContainerClearance, completeContainerClearance,
|
||||
getContainerOverview, markContainerException, cancelContainerException, uploadContainerDocument, downloadContainerDocument, downloadContainerPdaPhoto,
|
||||
@@ -88,21 +88,39 @@ describe('platform container loading API', () => {
|
||||
expect(vi.mocked(http.delete).mock.calls[0]?.[1]).toMatchObject({ data: { expectedPlanVersion: 9, expectedItemVersion: 10, operationId: expect.stringContaining('remove-') } })
|
||||
})
|
||||
|
||||
it('uses the dedicated provisional assignment command for a pending-review order', async () => {
|
||||
vi.mocked(http.post).mockResolvedValue({ code: '200', data: plan })
|
||||
|
||||
await assignPendingReviewSmartLoadingOrder('10', {
|
||||
customerId: '21', orderId: '31', inboundCode: 'EXXHK2609020361',
|
||||
packageCount: 100, expectedVersion: 5,
|
||||
})
|
||||
|
||||
expect(http.post).toHaveBeenCalledWith(
|
||||
'/platform/v1/first-leg/smart-loading/plans/10/pending-review-assignments',
|
||||
expect.objectContaining({
|
||||
customerId: '21', orderId: '31', inboundCode: 'EXXHK2609020361',
|
||||
packageCount: 100, expectedVersion: 5,
|
||||
operationId: expect.stringContaining('assign-pending-review-'),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('queries workbench containers with the same global filters and excludes the retired booking parameter', async () => {
|
||||
vi.mocked(http.get).mockResolvedValueOnce({ code: '200', data: {
|
||||
records: [{ bookingId: 9007199254740993n, bookingNo: 'BK-1', cabinetNo: 'CAB-1', containerNo: null, warehouseId: 8, warehouseName: '义乌仓', channelCode: 'MATSON_CLX_TRUCK', channelName: '美森正班海卡', containerType: '40HC', planId: 9007199254740994n, planNo: 'LP-1', planStatus: 'EDITING', version: '2' }],
|
||||
page: '1', size: '200', total: '1', pages: '1',
|
||||
} })
|
||||
|
||||
const result = await listSmartLoadingWorkbenchContainers({ channelGroupCodes: ['MATSON_CLX', 'EXX'], customerId: '12', inboundCode: 'IN-1', expectedArrivalStart: '2026-09-01', expectedArrivalEnd: '2026-09-03', orderStatus: 'WAIT_SPLIT', page: 1, size: 200 })
|
||||
const result = await listSmartLoadingWorkbenchContainers({ channelGroupCodes: ['MATSON_CLX', 'EXX'], customerId: '12', inboundCode: 'IN-1', createdStart: '2026-09-01', createdEnd: '2026-09-03', orderStatus: 'WAIT_SPLIT', page: 1, size: 200 })
|
||||
|
||||
expect(result).toMatchObject({ records: [{ bookingId: '9007199254740993', warehouseId: '8', planId: '9007199254740994', version: 2 }], total: 1 })
|
||||
const params = vi.mocked(http.get).mock.calls.at(-1)?.[1]?.params as URLSearchParams
|
||||
expect(params.getAll('channelGroupCodes')).toEqual(['MATSON_CLX', 'EXX'])
|
||||
expect(params.get('customerId')).toBe('12')
|
||||
expect(params.get('inboundCode')).toBe('IN-1')
|
||||
expect(params.get('expectedArrivalStart')).toBe('2026-09-01')
|
||||
expect(params.get('expectedArrivalEnd')).toBe('2026-09-03')
|
||||
expect(params.get('createdStart')).toBe('2026-09-01')
|
||||
expect(params.get('createdEnd')).toBe('2026-09-03')
|
||||
expect(params.get('orderStatus')).toBe('WAIT_SPLIT')
|
||||
expect(params.has('bookingId')).toBe(false)
|
||||
})
|
||||
|
||||
@@ -233,7 +233,7 @@ function smartLoadingQueryParams(query: SmartLoadingCandidateQuery) {
|
||||
for (const channelGroupCode of query.channelGroupCodes) params.append('channelGroupCodes', channelGroupCode)
|
||||
const optionalParams: Array<[string, string | number | undefined]> = [
|
||||
['warehouseId', query.warehouseId], ['customerId', query.customerId], ['inboundCode', query.inboundCode],
|
||||
['expectedArrivalStart', query.expectedArrivalStart], ['expectedArrivalEnd', query.expectedArrivalEnd],
|
||||
['createdStart', query.createdStart], ['createdEnd', query.createdEnd],
|
||||
['orderStatus', query.orderStatus], ['page', query.page], ['size', query.size],
|
||||
]
|
||||
for (const [key, value] of optionalParams) if (value !== undefined && value !== '') params.append(key, String(value))
|
||||
@@ -249,7 +249,7 @@ export async function listSmartLoadingCandidates(query: SmartLoadingCandidateQue
|
||||
page: numberValue(result.page, query.page), size: numberValue(result.size, query.size),
|
||||
total: numberValue(result.total), pages: numberValue(result.pages),
|
||||
records: result.records.map((raw) => ({
|
||||
...raw, accountId: id(raw.accountId), customerId: id(raw.customerId), inboundId: id(raw.inboundId),
|
||||
...raw, accountId: id(raw.accountId), customerId: id(raw.customerId), inboundId: optionalId(raw.inboundId),
|
||||
orderId: id(raw.orderId), warehouseId: id(raw.warehouseId), destinationWarehouseId: optionalId(raw.destinationWarehouseId),
|
||||
coLoadGroupId: optionalId(raw.coLoadGroupId),
|
||||
memberCount: raw.memberCount == null ? null : numberValue(raw.memberCount),
|
||||
@@ -297,6 +297,14 @@ export async function assignSmartLoadingItem(planId: string, payload: { inboundI
|
||||
return plan(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function assignPendingReviewSmartLoadingOrder(planId: string, payload: { customerId: string; orderId: string; inboundCode: string; packageCount: number; expectedVersion: number }) {
|
||||
const response = await http.post<ApiEnvelope<SmartLoadingPlan>, ApiEnvelope<SmartLoadingPlan>>(
|
||||
`${smartLoadingPath}/plans/${encodeURIComponent(planId)}/pending-review-assignments`,
|
||||
{ ...payload, operationId: operationId('assign-pending-review') },
|
||||
)
|
||||
return plan(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function assignSmartLoadingGroup(planId: string, inboundId: string, expectedVersion: number) {
|
||||
const response = await http.post<ApiEnvelope<SmartLoadingPlan>, ApiEnvelope<SmartLoadingPlan>>(
|
||||
`${smartLoadingPath}/plans/${encodeURIComponent(planId)}/group-assignments`,
|
||||
|
||||
@@ -10,6 +10,7 @@ import GlobalSmartLoadingView from './GlobalSmartLoadingView.vue'
|
||||
import { resolveSmartLoadingChannelGroup } from '../smartLoadingChannelGroups'
|
||||
import type { ContainerBookingResource } from '../types'
|
||||
import {
|
||||
assignPendingReviewSmartLoadingOrder,
|
||||
assignSmartLoadingGroup,
|
||||
assignSmartLoadingItem,
|
||||
getSmartLoadingPlanByBooking,
|
||||
@@ -40,6 +41,7 @@ vi.mock('../api/platformContainerLoading', () => ({
|
||||
listSmartLoadingWorkbenchContainers: vi.fn(),
|
||||
createSmartLoadingPlan: vi.fn(),
|
||||
assignSmartLoadingItem: vi.fn(),
|
||||
assignPendingReviewSmartLoadingOrder: vi.fn(),
|
||||
assignSmartLoadingGroup: vi.fn(),
|
||||
recommendSmartLoading: vi.fn(),
|
||||
adjustSmartLoadingItem: vi.fn(),
|
||||
@@ -278,7 +280,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
expect(wrapper.text()).toContain('MSCU-DG')
|
||||
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
|
||||
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', createdRange: [], orderStatus: '' })
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.text()).toContain('MSCU-DG')
|
||||
@@ -305,7 +307,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
await flushPromises()
|
||||
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
|
||||
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', createdRange: [], orderStatus: '' })
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.find('.channel-tags .el-checkbox-button.is-checked').text()).toBe('美森正班')
|
||||
@@ -336,7 +338,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
|
||||
|
||||
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
|
||||
warehouseId: '', customerId: '', inboundCode: 'NYEXX2609030002', expectedArrivalRange: [], orderStatus: '',
|
||||
warehouseId: '', customerId: '', inboundCode: 'NYEXX2609030002', createdRange: [], orderStatus: '',
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
@@ -374,7 +376,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
|
||||
|
||||
wrapper.getComponent(QueryToolbar).vm.$emit('search', {
|
||||
customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', expectedArrivalRange: ['2026-09-10', '2026-09-12'],
|
||||
customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', createdRange: ['2026-09-10', '2026-09-12'],
|
||||
})
|
||||
await flushPromises()
|
||||
|
||||
@@ -382,7 +384,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
expect(candidates).toContain('IN-MATCH')
|
||||
expect(candidates).not.toContain('IN-WRONG-STATUS')
|
||||
expect(candidates).not.toContain('IN-WRONG-DATE')
|
||||
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], expectedArrivalStart: '2026-09-10', expectedArrivalEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 20 })
|
||||
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], createdStart: '2026-09-10', createdEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 20 })
|
||||
expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
|
||||
expect(listSmartLoadingWorkbenchContainers).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], page: 1, size: 200 })
|
||||
})
|
||||
@@ -445,6 +447,37 @@ describe('GlobalSmartLoadingView', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('allows a pending-review order to join through the provisional assignment command', async () => {
|
||||
vi.mocked(listSmartLoadingCandidates).mockResolvedValue(candidatePage([{
|
||||
...candidate,
|
||||
customerId: '1',
|
||||
inboundId: null,
|
||||
orderStatusSnapshot: 'PENDING_REVIEW',
|
||||
inboundCode: 'EXXHK2609020361',
|
||||
blockReason: null,
|
||||
}]))
|
||||
vi.mocked(assignPendingReviewSmartLoadingOrder).mockResolvedValue(plan)
|
||||
const wrapper = mount(GlobalSmartLoadingView, { attachTo: document.body, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
expect(wrapper.get('.candidate-pane').text()).toContain('待审核')
|
||||
const add = wrapper.get('.candidate-pane .el-table__body-wrapper').findAll('button')
|
||||
.find((button) => button.text().includes('加入'))!
|
||||
expect(add.classes()).not.toContain('is-disabled')
|
||||
await add.trigger('click')
|
||||
await flushPromises()
|
||||
;([...document.body.querySelectorAll('button')]
|
||||
.find((button) => button.textContent?.includes('确认加入')) as HTMLButtonElement).click()
|
||||
await flushPromises()
|
||||
|
||||
expect(assignPendingReviewSmartLoadingOrder).toHaveBeenCalledWith(plan.id, {
|
||||
customerId: '1', orderId: '7', inboundCode: 'EXXHK2609020361',
|
||||
packageCount: 12, expectedVersion: plan.version,
|
||||
})
|
||||
expect(assignSmartLoadingItem).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('blocks an incomplete three-member candidate group without exposing member assignment', async () => {
|
||||
const members = ['401', '402', '403'].map((inboundId, index) => ({ accountId: `${index + 1}`, inboundId, orderId: `50${index}`, orderNo: `ORD-${index}`, inboundCode: `IN-${inboundId}`, customerName: `客户${index}`, availablePackageCount: 6, orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: null }))
|
||||
vi.mocked(listSmartLoadingCandidates).mockResolvedValue(candidatePage([{
|
||||
@@ -488,7 +521,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
await flushPromises()
|
||||
|
||||
const fields = wrapper.findComponent(QueryToolbar).props('fields') as Array<{ key: string; options?: Array<{ label: string; value: string }> }>
|
||||
expect(fields.map((field) => field.key)).toEqual(['warehouseId', 'customerId', 'inboundCode', 'expectedArrivalRange', 'orderStatus'])
|
||||
expect(fields.map((field) => field.key)).toEqual(['warehouseId', 'customerId', 'inboundCode', 'createdRange', 'orderStatus'])
|
||||
expect(fields.find((field) => field.key === 'warehouseId')?.options).toEqual([{ label: '义乌仓', value: '8' }])
|
||||
expect(fields.find((field) => field.key === 'orderStatus')?.options).toEqual([
|
||||
{ value: '', label: '全部' },
|
||||
@@ -759,6 +792,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
expect(source).toContain('size="small"')
|
||||
expect(source).toContain('type="danger" plain')
|
||||
expect(source).toContain(':max="adjustmentMax(row)"')
|
||||
expect(source).toContain("{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70, slotName: 'customerName' }")
|
||||
expect(source).not.toContain('link type="danger"')
|
||||
expect(source).toContain('@media (max-width: 720px)')
|
||||
expect(source.match(/<DataTable[\s\S]*?\bstripe\b/g)).toHaveLength(2)
|
||||
@@ -779,7 +813,7 @@ describe('GlobalSmartLoadingView', () => {
|
||||
expect(source).toContain('.candidate-pane,.plan-pane { height: min(68vh, 560px); min-height: 420px; }')
|
||||
expect(source).toContain('.container-pane { max-height: min(58vh, 460px); }')
|
||||
expect(source).toContain('min-height: 112px')
|
||||
expect(source).toContain("label: '预计到货时间'")
|
||||
expect(source).toContain("label: '创建日期'")
|
||||
expect(source).toContain("label: '订单状态'")
|
||||
expect(source).toContain('.smart-loading-query .query-toolbar__filters) { flex-wrap: nowrap; gap: 8px; overflow-x: auto; }')
|
||||
expect(source).toContain('.smart-loading-query .query-toolbar__actions) { flex-wrap: nowrap; gap: 6px; }')
|
||||
|
||||
@@ -9,10 +9,12 @@ 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 LineClampText from '@/shared/components/LineClampText.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import {
|
||||
adjustSmartLoadingItem,
|
||||
assignPendingReviewSmartLoadingOrder,
|
||||
assignSmartLoadingGroup,
|
||||
assignSmartLoadingItem,
|
||||
getSmartLoadingPlanByBooking,
|
||||
@@ -61,7 +63,7 @@ const selectedCandidate = ref<SmartLoadingCandidateRow | null>(null)
|
||||
const assignForm = reactive({ packageCount: 1 })
|
||||
const quantityDrafts = reactive<Record<string, number>>({})
|
||||
const planGroupsByInboundId = ref(new Map<string, SmartLoadingPlanCoLoadGroup>())
|
||||
const query = ref<QueryModel>({ warehouseId: '', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
|
||||
const query = ref<QueryModel>({ warehouseId: '', customerId: '', inboundCode: '', createdRange: [], orderStatus: '' })
|
||||
const appliedQuery = ref<QueryModel>({ ...query.value })
|
||||
const candidatePage = ref(1)
|
||||
const candidatePageSize = ref(20)
|
||||
@@ -120,12 +122,12 @@ function candidateTotalVolume(candidate: SmartLoadingCandidate) {
|
||||
}
|
||||
const displayedCandidates = computed<SmartLoadingCandidateRow[]>(() => {
|
||||
return candidates.value.map((candidate) => {
|
||||
const members: Array<{ accountId: string; inboundId: string; orderId: string; orderNo?: string | null; inboundCode?: string | null }> = candidate.groupMembers.length
|
||||
const members: Array<{ accountId: string; inboundId: string | null; orderId: string; orderNo?: string | null; inboundCode?: string | null }> = candidate.groupMembers.length
|
||||
? candidate.groupMembers
|
||||
: [candidate]
|
||||
return {
|
||||
...candidate,
|
||||
memberInboundIds: members.map((member) => member.inboundId),
|
||||
memberInboundIds: members.map((member) => member.inboundId).filter((id): id is string => Boolean(id)),
|
||||
memberInboundCodes: members.map((member) => member.inboundCode || '入仓码暂缺'),
|
||||
memberOrderSummaries: memberOrderSummaries(members),
|
||||
memberOrderCount: new Set(members.map((member) => member.orderId)).size,
|
||||
@@ -189,11 +191,14 @@ const candidateOrderStatusOptions = [
|
||||
{ value: 'WAIT_SPLIT', label: '待分柜' },
|
||||
{ value: 'LOADING', label: '装柜中' },
|
||||
]
|
||||
function candidateStatusLabel(status: string | null | undefined) {
|
||||
return candidateOrderStatusOptions.find((item) => item.value === status)?.label || '—'
|
||||
}
|
||||
const queryFields = computed<QueryField[]>(() => [
|
||||
{ key: 'warehouseId', type: 'select', label: '发货仓', placeholder: '全部发货仓', width: 160, options: warehouseOptions.value, props: { filterable: true } },
|
||||
{ key: 'customerId', type: 'select', label: '客户', placeholder: '全部客户', width: 180, options: customers.value.filter((customer) => customer.status === 'ACTIVE').map((customer) => ({ label: customer.tenantName, value: String(customer.id) })), props: { filterable: true, loading: customersLoading.value } },
|
||||
{ key: 'inboundCode', type: 'input', label: '入仓码', placeholder: '请输入入仓码', width: 210 },
|
||||
{ key: 'expectedArrivalRange', type: 'daterange', label: '预计到货时间', placeholder: '请选择预计到货时间范围', width: 260 },
|
||||
{ key: 'createdRange', type: 'daterange', label: '创建日期', placeholder: '请选择创建日期范围', width: 260 },
|
||||
{ key: 'orderStatus', type: 'select', label: '订单状态', placeholder: '全部订单状态', width: 160, options: candidateOrderStatusOptions },
|
||||
])
|
||||
|
||||
@@ -216,7 +221,8 @@ const candidateColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 92, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true },
|
||||
{ key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70, slotName: 'customerName' },
|
||||
{ key: 'orderStatusSnapshot', prop: 'orderStatusSnapshot', label: '订单状态', width: 100, slotName: 'orderStatusSnapshot' },
|
||||
{ key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 },
|
||||
// { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' },
|
||||
{ key: 'unitGrossWeightKg', label: '单箱毛重', width: 80, slotName: 'unitGrossWeight' },
|
||||
@@ -228,7 +234,7 @@ const itemColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 80, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true },
|
||||
{ key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70, slotName: 'customerName' },
|
||||
{ key: 'packageCount', label: '分配箱数', width: 100, slotName: 'packageCount' },
|
||||
{ key: 'grossWeightKg', label: '毛重', width: 80, slotName: 'weight' },
|
||||
{ key: 'volumeCbm', label: '体积', width: 80, slotName: 'volume' },
|
||||
@@ -283,8 +289,8 @@ function clearSelectionData() {
|
||||
}
|
||||
|
||||
function candidateQueryParams(): Parameters<typeof listSmartLoadingCandidates>[0] {
|
||||
const arrivalRange = Array.isArray(appliedQuery.value.expectedArrivalRange)
|
||||
? appliedQuery.value.expectedArrivalRange.map((value) => String(value || '').slice(0, 10))
|
||||
const createdRange = Array.isArray(appliedQuery.value.createdRange)
|
||||
? appliedQuery.value.createdRange.map((value) => String(value || '').slice(0, 10))
|
||||
: []
|
||||
const params: Parameters<typeof listSmartLoadingCandidates>[0] = {
|
||||
channelGroupCodes: [...selectedChannelCodes.value],
|
||||
@@ -298,9 +304,9 @@ function candidateQueryParams(): Parameters<typeof listSmartLoadingCandidates>[0
|
||||
if (warehouseId) params.warehouseId = warehouseId
|
||||
if (customerId) params.customerId = customerId
|
||||
if (inboundCode) params.inboundCode = inboundCode
|
||||
if (arrivalRange.length === 2) {
|
||||
params.expectedArrivalStart = arrivalRange[0]
|
||||
params.expectedArrivalEnd = arrivalRange[1]
|
||||
if (createdRange.length === 2) {
|
||||
params.createdStart = createdRange[0]
|
||||
params.createdEnd = createdRange[1]
|
||||
}
|
||||
if (orderStatus) params.orderStatus = orderStatus
|
||||
return params
|
||||
@@ -471,10 +477,18 @@ async function assign() {
|
||||
}
|
||||
await mutate(async () => {
|
||||
const selected = selectedCandidate.value!
|
||||
acceptPlan(selected.coLoadGroupId
|
||||
acceptPlan(selected.orderStatusSnapshot === 'PENDING_REVIEW'
|
||||
? await assignPendingReviewSmartLoadingOrder(plan.value!.id, {
|
||||
customerId: selected.customerId,
|
||||
orderId: selected.orderId,
|
||||
inboundCode: selected.inboundCode || '',
|
||||
packageCount: assignForm.packageCount,
|
||||
expectedVersion: plan.value!.version,
|
||||
})
|
||||
: selected.coLoadGroupId && selected.inboundId
|
||||
? await assignSmartLoadingGroup(plan.value!.id, selected.inboundId, plan.value!.version)
|
||||
: await assignSmartLoadingItem(plan.value!.id, {
|
||||
inboundId: selected.inboundId,
|
||||
inboundId: selected.inboundId!,
|
||||
packageCount: assignForm.packageCount,
|
||||
expectedVersion: plan.value!.version,
|
||||
}))
|
||||
@@ -484,7 +498,13 @@ async function assign() {
|
||||
}, '分配明细失败', { preserveAssign: true })
|
||||
}
|
||||
function candidateBlocked(candidate: SmartLoadingCandidateRow) {
|
||||
if (candidate.orderStatusSnapshot === 'PENDING_REVIEW') {
|
||||
return Boolean(candidate.blockReason) || candidate.locked === true
|
||||
|| !candidate.inboundCode || candidate.availablePackageCount < 1
|
||||
}
|
||||
return Boolean(candidate.blockReason) || candidate.locked === true
|
||||
|| !candidate.inboundId
|
||||
|| !candidate.inboundCode
|
||||
|| candidate.memberInboundIds.some((inboundId) => assignedInboundIds.value.has(inboundId))
|
||||
|| candidate.availablePackageCount < 1
|
||||
}
|
||||
@@ -676,7 +696,9 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
@click="openAssign(row)"
|
||||
><Plus :size="15" />{{ row.coLoadGroupId ? '整组分配' : '加入' }}</el-button>
|
||||
</template>
|
||||
<template #customerName="{ row }"><LineClampText :content="row.customerName" /></template>
|
||||
<template #coLoadGroup="{ row }"><div v-if="row.coLoadGroupId" class="candidate-group"><el-tag size="small" type="warning">{{ row.groupNo }} · {{ row.memberOrderCount }} 票 · {{ row.inboundFragmentCount }} 个入仓片段</el-tag><span v-for="summary in row.memberOrderSummaries" :key="summary">{{ summary }}</span><span>整组 {{ row.groupTotalBoxes }} 箱,必须全量进入同一柜</span><small v-if="row.blockReason" class="candidate-block">{{ row.blockReason }}</small><small v-else-if="row.locked">该组已锁定,不能重复分配</small></div><span v-else>普通单票</span></template>
|
||||
<template #orderStatusSnapshot="{ row }"><el-tag size="small" effect="plain">{{ candidateStatusLabel(row.orderStatusSnapshot) }}</el-tag></template>
|
||||
<template #unitGrossWeight="{ row }">{{ formatNumber(Number(row.unitGrossWeightKg || 0), 1) }} kg</template>
|
||||
<template #candidateTotalWeight="{ row }">{{ formatNumber(Number(row.totalGrossWeightKg || 0), 1) }} kg</template>
|
||||
<template #averageUnitVolume="{ row }">{{ formatNumber(Number(row.averageUnitVolumeCbm || 0)) }} m³</template>
|
||||
@@ -747,6 +769,7 @@ onMounted(() => { void load(); void loadCustomers() })
|
||||
</div>
|
||||
<DataTable :data="displayedPlanItems" :columns="itemColumns" :loading="selectionLoading || mutating" stripe preference-key="global-smart-loading-items" column-settings-mode="hide" empty-text="当前柜次暂无装柜明细">
|
||||
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canDelete && editable && row.canRemove" size="small" type="danger" plain @click="remove(row)"><Trash2 :size="15" />删除</el-button></div></template>
|
||||
<template #customerName="{ row }"><LineClampText :content="row.customerName" /></template>
|
||||
<template #coLoadGroup="{ row }"><div v-if="row.coLoadGroup" class="candidate-group"><el-tag size="small" type="warning">{{ row.coLoadGroup.groupNo }} · {{ groupMemberLabel(row.coLoadGroup) }}</el-tag><span v-for="summary in row.memberOrderSummaries" :key="summary">{{ summary }}</span><span>固定 {{ row.coLoadGroup.fullPackageCount }} 箱 · {{ row.coLoadGroup.lockedPlanId ? '已锁定' : '未锁定' }}</span></div><span v-else>普通单票</span></template>
|
||||
<template #packageCount="{ row }"><strong v-if="row.coLoadGroup">{{ row.packageCount }} 箱(整组固定)</strong><div v-else class="package-count-editor"><el-input-number v-model="quantityDrafts[row.id]" :min="row.minPackageCount" :max="adjustmentMax(row)" :step="1" :disabled="mutating || !canAssign || !editable || !row.canAdjust" controls-position="right" @change="adjust(row)" /><small v-if="row.detailCount > 1 && editable">多规格,需删除后重加</small><small v-if="row.pickedPackageCount > 0">已拣 {{ row.pickedPackageCount }} 箱</small></div></template>
|
||||
<template #weight="{ row }">{{ formatNumber(Number(row.grossWeightKg || 0), 1) }} kg</template>
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 LineClampText from '@/shared/components/LineClampText.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import {
|
||||
adjustSmartLoadingItem, assignSmartLoadingGroup, assignSmartLoadingItem, getContainer,
|
||||
@@ -62,12 +63,12 @@ function groupMemberLabel(group: SmartLoadingPlanCoLoadGroup) {
|
||||
}
|
||||
const displayedCandidates = computed<SmartLoadingCandidateRow[]>(() => {
|
||||
return candidates.value.map((candidate) => {
|
||||
const members: Array<{ inboundId: string; orderId: string; orderNo?: string | null; inboundCode?: string | null }> = candidate.groupMembers.length
|
||||
const members: Array<{ inboundId: string | null; orderId: string; orderNo?: string | null; inboundCode?: string | null }> = candidate.groupMembers.length
|
||||
? candidate.groupMembers
|
||||
: [candidate]
|
||||
return {
|
||||
...candidate,
|
||||
memberInboundIds: members.map((member) => member.inboundId),
|
||||
memberInboundIds: members.map((member) => member.inboundId).filter((id): id is string => Boolean(id)),
|
||||
memberInboundCodes: members.map((member) => member.inboundCode || '入仓码暂缺'),
|
||||
memberOrderSummaries: memberOrderSummaries(members),
|
||||
memberOrderCount: new Set(members.map((member) => member.orderId)).size,
|
||||
@@ -129,7 +130,7 @@ const itemColumns: DataTableColumn[] = [
|
||||
{ key: 'sequence', prop: 'loadSequence', label: '顺序', width: 75 },
|
||||
{ key: 'order', label: '入仓码', minWidth: 190, slotName: 'order' },
|
||||
{ key: 'coLoadGroup', label: '同柜不可拆', minWidth: 210, slotName: 'coLoadGroup' },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 150 },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 150, slotName: 'customerName' },
|
||||
{ key: 'goods', label: '货物', minWidth: 190, slotName: 'goods' },
|
||||
{ key: 'packageCount', label: '分配箱数', width: 150, slotName: 'packageCount' },
|
||||
{ key: 'volumeCbm', label: '体积', width: 120, slotName: 'volume' },
|
||||
@@ -139,7 +140,7 @@ const candidateColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 110, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, fixed: 'left', showOverflowTooltip: true },
|
||||
{ key: 'coLoadGroup', label: '同柜不可拆', minWidth: 210, slotName: 'coLoadGroup' },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 150 },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 150, slotName: 'customerName' },
|
||||
{ key: 'goodsSummary', prop: 'goodsSummary', label: '货物摘要', minWidth: 180, showOverflowTooltip: true },
|
||||
{ key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 110 },
|
||||
{ key: 'cartonIdentityStatus', label: '箱号归属', width: 110, slotName: 'cartonIdentityStatus' },
|
||||
@@ -217,15 +218,17 @@ function openAssign(source: object) {
|
||||
assignOpen.value = true
|
||||
}
|
||||
async function assign() {
|
||||
if (!plan.value || !selectedCandidate.value || assignForm.packageCount < 1) return
|
||||
if (selectedCandidate.value.coLoadGroupId && (selectedCandidate.value.blockReason || selectedCandidate.value.locked)) return
|
||||
const currentPlan = plan.value
|
||||
const selected = selectedCandidate.value
|
||||
const inboundId = selected?.inboundId
|
||||
if (!currentPlan || !selected || !inboundId || assignForm.packageCount < 1) return
|
||||
if (selected.coLoadGroupId && (selected.blockReason || selected.locked)) return
|
||||
mutating.value = true
|
||||
mutationError.value = null
|
||||
try {
|
||||
const selected = selectedCandidate.value
|
||||
const next = selected.coLoadGroupId
|
||||
? await assignSmartLoadingGroup(plan.value.id, selected.inboundId, plan.value.version)
|
||||
: await assignSmartLoadingItem(plan.value.id, { inboundId: selected.inboundId, packageCount: assignForm.packageCount, expectedVersion: plan.value.version })
|
||||
? await assignSmartLoadingGroup(currentPlan.id, inboundId, currentPlan.version)
|
||||
: await assignSmartLoadingItem(currentPlan.id, { inboundId, packageCount: assignForm.packageCount, expectedVersion: currentPlan.version })
|
||||
await acceptPlanWithGroups(next)
|
||||
assignOpen.value = false
|
||||
ElMessage.success(selected.coLoadGroupId ? `同柜不可拆组 ${selected.groupNo} 已整组分配` : '货物已分配')
|
||||
@@ -234,6 +237,7 @@ async function assign() {
|
||||
}
|
||||
function candidateBlocked(candidate: SmartLoadingCandidateRow) {
|
||||
return Boolean(candidate.blockReason) || candidate.locked === true
|
||||
|| !candidate.inboundId
|
||||
|| candidate.memberInboundIds.some((inboundId) => assignedInboundIds.value.has(inboundId))
|
||||
|| candidate.availablePackageCount < 1
|
||||
}
|
||||
@@ -356,6 +360,7 @@ onMounted(load)
|
||||
<template #toolbar-start><div><h2>{{ displayContainerNo(container) }} 装柜方案</h2><p>智能建议必须经人工核对。</p></div></template>
|
||||
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canDelete && editable && row.canRemove" size="small" type="danger" plain @click="remove(row)"><Trash2 :size="15" />删除</el-button></div></template>
|
||||
<template #order="{ row }"><strong>{{ row.inboundCode || '—' }}</strong></template>
|
||||
<template #customerName="{ row }"><LineClampText :content="row.customerName" /></template>
|
||||
<template #coLoadGroup="{ row }"><div v-if="row.coLoadGroup" class="candidate-group"><el-tag size="small" type="warning">{{ row.coLoadGroup.groupNo }} · {{ groupMemberLabel(row.coLoadGroup) }}</el-tag><span v-for="summary in row.memberOrderSummaries" :key="summary">{{ summary }}</span><span>全部入仓片段联动,固定 {{ row.coLoadGroup.fullPackageCount }} 箱</span></div><span v-else>普通单票</span></template>
|
||||
<template #goods="{ row }"><strong>{{ row.productNameZh || '未命名货物' }}</strong><small>{{ row.skuCode || '无 SKU' }}</small></template>
|
||||
<template #packageCount="{ row }"><strong v-if="row.coLoadGroup">{{ row.packageCount }} 箱(整组固定)</strong><div v-else class="package-count-editor"><el-input-number v-model="quantityDrafts[row.id]" :min="row.minPackageCount" :step="1" :disabled="mutating || !canAssign || !editable || !row.canAdjust" controls-position="right" @change="adjust(row)" /><small v-if="row.detailCount > 1 && editable">含 {{ row.detailCount }} 个规格,需删除后重加</small><small v-if="row.pickedPackageCount > 0">已拣 {{ row.pickedPackageCount }} 箱</small></div></template>
|
||||
@@ -364,6 +369,7 @@ onMounted(load)
|
||||
<DataTable :data="displayedCandidates" :columns="candidateColumns" :loading="loading || mutating" stripe preference-key="smart-loading-candidates" column-settings-mode="hide" empty-text="当前仓库暂无可排柜货物">
|
||||
<template #toolbar-start><div><h2>候选货物</h2><p>系统基于装柜仓库、物流渠道、可用箱数及 110% 容量上限校验。</p></div></template>
|
||||
<template #actions="{ row }"><el-button v-if="canAssign && assignable" size="small" type="primary" plain :disabled="candidateBlocked(row as SmartLoadingCandidateRow)" @click="openAssign(row)"><Plus :size="15" />{{ row.coLoadGroupId ? '整组分配' : '分配' }}</el-button></template>
|
||||
<template #customerName="{ row }"><LineClampText :content="row.customerName" /></template>
|
||||
<template #coLoadGroup="{ row }"><div v-if="row.coLoadGroupId" class="candidate-group"><el-tag size="small" type="warning">{{ row.groupNo }} · {{ row.memberOrderCount }} 票 · {{ row.inboundFragmentCount }} 个入仓片段</el-tag><span v-for="summary in row.memberOrderSummaries" :key="summary">{{ summary }}</span><span>整组 {{ row.groupTotalBoxes }} 箱,必须全量进入同一柜</span><small v-if="row.blockReason" class="candidate-block">{{ row.blockReason }}</small><small v-else-if="row.locked">该组已锁定,不能重复分配</small></div><span v-else>普通单票</span></template>
|
||||
<template #averageUnitVolume="{ row }">{{ formatNumber(Number(row.averageUnitVolumeCbm || 0)) }} m³</template><template #unitGrossWeight="{ row }">{{ formatNumber(Number(row.unitGrossWeightKg || 0),1) }} kg</template>
|
||||
<template #cartonIdentityStatus><el-tag size="small" type="info" effect="plain">未核验</el-tag></template>
|
||||
|
||||
@@ -232,7 +232,7 @@ export interface ContainerSaveRequest {
|
||||
export interface SmartLoadingCandidate {
|
||||
accountId: string
|
||||
customerId: string
|
||||
inboundId: string
|
||||
inboundId: string | null
|
||||
orderId: string
|
||||
orderNo: string | null
|
||||
inboundCode: string | null
|
||||
@@ -277,8 +277,8 @@ export interface SmartLoadingCandidateQuery {
|
||||
warehouseId?: string
|
||||
customerId?: string
|
||||
inboundCode?: string
|
||||
expectedArrivalStart?: string
|
||||
expectedArrivalEnd?: string
|
||||
createdStart?: string
|
||||
createdEnd?: string
|
||||
orderStatus?: 'PENDING_REVIEW' | 'WAIT_MEASURE' | 'WAIT_SPLIT' | 'LOADING'
|
||||
page: number
|
||||
size: number
|
||||
|
||||
@@ -12,6 +12,7 @@ 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 LineClampText from '@/shared/components/LineClampText.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import { firstLegBusinessTypeLabel } from '@/shared/utils/firstLegBusinessLabels'
|
||||
import { listPlatformInbounds } from '../core/api'
|
||||
@@ -82,26 +83,26 @@ const queryFields = computed<QueryField[]>(() => [
|
||||
])
|
||||
|
||||
const columns = computed<DataTableColumn[]>(() => [
|
||||
{ key: 'actions', label: '操作', width: 190, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'actions', label: '操作', width: 160, fixed: 'left', slotName: 'actions' },
|
||||
...(isReview.value && access.can(actionPermission.value) ? [{
|
||||
key: 'selection', type: 'selection', width: 48, fixed: 'left',
|
||||
selectable: (row: InboundOrderSummary) => row.measureReviewStatus === 'PENDING' && !batchReviewLoading.value,
|
||||
} satisfies DataTableColumn] : []),
|
||||
{ key: 'inboundCode', label: '入仓码', minWidth: 170, slotName: 'inboundCode' },
|
||||
{ key: 'inboundCode', label: '入仓码', minWidth: 140, slotName: 'inboundCode' },
|
||||
{ key: 'processStatus', label: '流程状态', minWidth: 132, slotName: 'processStatus' },
|
||||
{ key: 'measureReviewStatus', label: '测量审核状态', minWidth: 142, slotName: 'measureReviewStatus' },
|
||||
{ key: 'expectedArrivalAt', label: '预计到达时间', minWidth: 164, slotName: 'expectedArrivalAt' },
|
||||
{ key: 'customerName', label: '客户', minWidth: 160, slotName: 'customerName' },
|
||||
{ key: 'warehouseName', label: '仓库', minWidth: 140, slotName: 'warehouseName' },
|
||||
{ key: 'deliveryWarehouseName', label: '交货仓', minWidth: 140, slotName: 'deliveryWarehouseName' },
|
||||
{ key: 'businessType', label: '业务类型', minWidth: 120, slotName: 'businessType' },
|
||||
{ key: 'channelName', label: '渠道', minWidth: 140, slotName: 'channelName' },
|
||||
{ key: 'totalPackageCount', label: '总箱数', width: 92, slotName: 'totalPackageCount' },
|
||||
{ key: 'palletCount', label: '托盘数', width: 92, slotName: 'palletCount' },
|
||||
{ key: 'actualGrossWeightKg', label: '实测毛重(kg)', minWidth: 128, slotName: 'actualGrossWeightKg' },
|
||||
{ key: 'actualNetWeightKg', label: '货物重量(kg)', minWidth: 128, slotName: 'actualNetWeightKg' },
|
||||
{ key: 'actualVolumeCbm', label: '实测体积(m³)', minWidth: 128, slotName: 'actualVolumeCbm' },
|
||||
{ key: 'measureCompletedAt', label: '提交时间', minWidth: 164, slotName: 'measureCompletedAt' },
|
||||
{ key: 'measureReviewStatus', label: '测量审核状态', minWidth: 120, slotName: 'measureReviewStatus' },
|
||||
{ key: 'expectedArrivalAt', label: '预计到达时间', minWidth: 140, slotName: 'expectedArrivalAt' },
|
||||
{ key: 'customerName', label: '客户', minWidth: 140, slotName: 'customerName' },
|
||||
{ key: 'warehouseName', label: '仓库', minWidth: 120, slotName: 'warehouseName' },
|
||||
{ key: 'deliveryWarehouseName', label: '交货仓', minWidth: 120, slotName: 'deliveryWarehouseName' },
|
||||
{ key: 'businessType', label: '业务类型', minWidth: 100, slotName: 'businessType' },
|
||||
{ key: 'channelName', label: '渠道', minWidth: 100, slotName: 'channelName' },
|
||||
{ key: 'totalPackageCount', label: '总箱数', width: 90, slotName: 'totalPackageCount' },
|
||||
{ key: 'palletCount', label: '托盘数', width: 90, slotName: 'palletCount' },
|
||||
{ key: 'actualGrossWeightKg', label: '实测毛重(kg)', minWidth: 100, slotName: 'actualGrossWeightKg' },
|
||||
{ key: 'actualNetWeightKg', label: '货物重量(kg)', minWidth: 100, slotName: 'actualNetWeightKg' },
|
||||
{ key: 'actualVolumeCbm', label: '实测体积(m³)', minWidth: 100, slotName: 'actualVolumeCbm' },
|
||||
{ key: 'measureCompletedAt', label: '提交时间', minWidth: 140, slotName: 'measureCompletedAt' },
|
||||
])
|
||||
|
||||
function text(value: unknown) {
|
||||
@@ -381,11 +382,11 @@ onBeforeUnmount(() => { disposed = true; requestSerial += 1; optionsRequestSeria
|
||||
<template #processStatus="{ row }"><el-tag effect="light">{{ inboundStatusLabel((row as InboundOrderSummary).processStatus, inboundProcessStatusLabels) }}</el-tag></template>
|
||||
<template #measureReviewStatus="{ row }">{{ inboundStatusLabel((row as InboundOrderSummary).measureReviewStatus, inboundReviewStatusLabels) }}</template>
|
||||
<template #expectedArrivalAt="{ row }">{{ formatDate((row as InboundOrderSummary).expectedArrivalAt) }}</template>
|
||||
<template #customerName="{ row }">{{ text((row as InboundOrderSummary).customerName) }}</template>
|
||||
<template #customerName="{ row }"><LineClampText :content="(row as InboundOrderSummary).customerName" /></template>
|
||||
<template #warehouseName="{ row }">{{ text((row as InboundOrderSummary).warehouseName) }}</template>
|
||||
<template #deliveryWarehouseName="{ row }">{{ text((row as InboundOrderSummary).deliveryWarehouseName) }}</template>
|
||||
<template #businessType="{ row }">{{ firstLegBusinessTypeLabel((row as InboundOrderSummary).businessType) }}</template>
|
||||
<template #channelName="{ row }">{{ text((row as InboundOrderSummary).channelName || (row as InboundOrderSummary).channelCode) }}</template>
|
||||
<template #channelName="{ row }"><LineClampText :content="(row as InboundOrderSummary).channelName || (row as InboundOrderSummary).channelCode" /></template>
|
||||
<template #totalPackageCount="{ row }">{{ text((row as InboundOrderSummary).totalPackageCount) }}</template>
|
||||
<template #palletCount="{ row }">{{ text((row as InboundOrderSummary).palletCount) }}</template>
|
||||
<template #actualGrossWeightKg="{ row }">{{ formatNumber((row as InboundOrderSummary).actualGrossWeightKg) }}</template>
|
||||
|
||||
@@ -11,6 +11,7 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import InboundCodeRelationshipDialog from '@/shared/components/InboundCodeRelationshipDialog.vue'
|
||||
import LineClampText from '@/shared/components/LineClampText.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import { firstLegBusinessTypeLabel, firstLegContainerModeLabel, firstLegDestinationTypeLabel, firstLegTaxModeLabel } from '@/shared/utils/firstLegBusinessLabels'
|
||||
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
||||
@@ -424,7 +425,7 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; customerRequestId +
|
||||
<template #expectedArrivalAt="{ row }">{{ formatDate((row as InboundOrderSummary).expectedArrivalAt) }}</template>
|
||||
<template #feeStatus="{ row }">{{ codeLabel((row as InboundOrderSummary).feeStatus, feeStatusLabels) }}</template>
|
||||
<template #feeReviewStatus="{ row }">{{ codeLabel((row as InboundOrderSummary).feeReviewStatus, inboundReviewStatusLabels) }}</template>
|
||||
<template #customerName="{ row }">{{ text((row as InboundOrderSummary).customerName) }}</template>
|
||||
<template #customerName="{ row }"><LineClampText :content="(row as InboundOrderSummary).customerName" /></template>
|
||||
<template #warehouseName="{ row }">{{ text((row as InboundOrderSummary).warehouseName) }}</template>
|
||||
<template #deliveryWarehouseName="{ row }">{{ text((row as InboundOrderSummary).deliveryWarehouseName) }}</template>
|
||||
<template #loadingStatus="{ row }">{{ codeLabel((row as InboundOrderSummary).loadingStatus, loadingStatusLabels) }}</template>
|
||||
@@ -434,7 +435,7 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; customerRequestId +
|
||||
<template #cabinetNo="{ row }">{{ text((row as InboundOrderSummary).cabinetNo) }}</template>
|
||||
<template #companyName="{ row }">{{ text((row as InboundOrderSummary).companyName) }}</template>
|
||||
<template #businessType="{ row }">{{ firstLegBusinessTypeLabel((row as InboundOrderSummary).businessType) }}</template>
|
||||
<template #channelName="{ row }"><div class="primary-cell"><strong>{{ text((row as InboundOrderSummary).channelName) }}</strong><span>{{ text((row as InboundOrderSummary).channelCode) }}</span></div></template>
|
||||
<template #channelName="{ row }"><div class="primary-cell"><LineClampText :content="(row as InboundOrderSummary).channelName" /><span>{{ text((row as InboundOrderSummary).channelCode) }}</span></div></template>
|
||||
<template #taxMode="{ row }">{{ firstLegTaxModeLabel((row as InboundOrderSummary).taxMode) }}</template>
|
||||
<template #containerMode="{ row }">{{ firstLegContainerModeLabel((row as InboundOrderSummary).containerMode) }}</template>
|
||||
<template #customsInspection="{ row }">{{ (row as InboundOrderSummary).customsInspection === null ? '—' : (row as InboundOrderSummary).customsInspection ? '是' : '否' }}</template>
|
||||
|
||||
@@ -10,6 +10,7 @@ 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 LineClampText from '@/shared/components/LineClampText.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
import { loadingListStatusLabel } from '../status'
|
||||
@@ -50,20 +51,20 @@ const fields = computed<QueryField[]>(() => [
|
||||
])
|
||||
|
||||
const columns = computed<DataTableColumn[]>(() => isPreload.value ? [
|
||||
{ key: 'actions', label: '操作', fixed: 'left', width: 110, slotName: 'actions', hideable: false, movable: false },
|
||||
{ key: 'preloadNo', prop: 'preloadNo', label: '预装单号', minWidth: 165 },
|
||||
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 145 },
|
||||
{ key: 'cabinetNo', prop: 'cabinetNo', label: '柜次号', minWidth: 150 },
|
||||
{ key: 'customers', label: '客户', minWidth: 190, slotName: 'customers' },
|
||||
{ key: 'warehouseName', prop: 'warehouseName', label: '仓库', minWidth: 140 },
|
||||
{ key: 'actions', label: '操作', fixed: 'left', width: 88, slotName: 'actions', hideable: false, movable: false },
|
||||
{ key: 'preloadNo', prop: 'preloadNo', label: '预装单号', minWidth: 120 },
|
||||
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 120 },
|
||||
{ key: 'cabinetNo', prop: 'cabinetNo', label: '柜次号', minWidth: 140 },
|
||||
// { key: 'customers', label: '客户', minWidth: 190, slotName: 'customers' },
|
||||
{ key: 'warehouseName', prop: 'warehouseName', label: '仓库', minWidth: 80 },
|
||||
{ key: 'vesselVoyage', label: '船名 / 航次', minWidth: 175, slotName: 'vesselVoyage' },
|
||||
{ key: 'containerType', prop: 'containerType', label: '箱型', width: 100 },
|
||||
{ key: 'containerType', prop: 'containerType', label: '箱型', width: 80 },
|
||||
{ key: 'status', label: '状态', width: 108, slotName: 'status' },
|
||||
{ key: 'skuCount', prop: 'skuCount', label: 'SKU 数', width: 90, align: 'right' },
|
||||
{ key: 'totalPackageCount', prop: 'totalPackageCount', label: '总箱数', width: 90, align: 'right' },
|
||||
{ key: 'totalVolumeCbm', label: '总体积(m³)', width: 120, align: 'right', slotName: 'volume' },
|
||||
{ key: 'totalGrossWeightKg', label: '总毛重(kg)', width: 125, align: 'right', slotName: 'grossWeight' },
|
||||
{ key: 'createdAt', label: '创建时间', minWidth: 155, slotName: 'createdAt' },
|
||||
{ key: 'skuCount', prop: 'skuCount', label: 'SKU 数', width: 90, align: 'right' },
|
||||
{ key: 'totalVolumeCbm', label: '总体积(m³)', width: 90, align: 'right', slotName: 'volume' },
|
||||
{ key: 'totalGrossWeightKg', label: '总毛重(kg)', width: 90, align: 'right', slotName: 'grossWeight' },
|
||||
{ key: 'createdAt', label: '创建时间', minWidth: 140, slotName: 'createdAt' },
|
||||
] : [
|
||||
{ key: 'actions', label: '操作', fixed: 'left', width: 110, slotName: 'actions', hideable: false, movable: false },
|
||||
{ key: 'loadingListNo', prop: 'loadingListNo', label: '装柜清单号', minWidth: 175 },
|
||||
@@ -211,7 +212,7 @@ onMounted(() => {
|
||||
<el-button v-if="canView" size="small" type="primary" plain @click="openDetail(row)"><Eye :size="15" />查看</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #customers="{ row }">{{ (row as PreloadSummary).customers.map((item) => item.customerName || '客户信息缺失').join('、') || '—' }}</template>
|
||||
<template #customers="{ row }"><LineClampText :content="(row as PreloadSummary).customers.map((item) => item.customerName || '客户信息缺失').join('、')" /></template>
|
||||
<template #vesselVoyage="{ row }">{{ (row as PreloadSummary).vesselName || '—' }} / {{ (row as PreloadSummary).voyageNo || '—' }}</template>
|
||||
<template #status="{ row }"><StatusBadge :status="loadingListStatusLabel(row.status)" /></template>
|
||||
<template #grossWeight="{ row }">{{ formatNumber(row.totalGrossWeightKg) }}</template>
|
||||
|
||||
@@ -93,6 +93,12 @@ enableAutoUnmount(afterEach)
|
||||
afterAll(() => vi.unstubAllGlobals())
|
||||
|
||||
describe('PartnersView real API integration', () => {
|
||||
it('clamps long unit names with the shared component', () => {
|
||||
expect(source).toContain("LineClampText from '@/shared/components/LineClampText.vue'")
|
||||
expect(source).toContain('<LineClampText :content="(row as PlatformPartnerResource).partnerName" />')
|
||||
expect(source).not.toContain('.partner-cell strong')
|
||||
})
|
||||
|
||||
it('keeps operation first and separates the prototype contact columns', () => {
|
||||
const actionsIndex = source.indexOf("{ key: 'actions', label: '操作'")
|
||||
const nameIndex = source.indexOf("{ key: 'partner', label: '单位名称'")
|
||||
|
||||
@@ -11,6 +11,7 @@ import DetailPageLayout from '@/shared/components/DetailPageLayout.vue'
|
||||
import FormSection from '@/shared/components/FormSection.vue'
|
||||
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import LineClampText from '@/shared/components/LineClampText.vue'
|
||||
import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/components/QueryToolbar.vue'
|
||||
import { useUnsavedWorkRegistration } from '@/core/unsaved-work/useUnsavedWorkRegistration'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
@@ -408,7 +409,7 @@ onMounted(() => {
|
||||
<el-button v-if="access.can('platform:counterparty:status')" size="small" plain :disabled="loading" :type="(row as PlatformPartnerResource).status === 'ACTIVE' ? 'danger' : 'success'" :loading="statusLoadingIds.has(String((row as PlatformPartnerResource).id))" @click="toggleStatus(row as PlatformPartnerResource)"><Power :size="15" />{{ (row as PlatformPartnerResource).status === 'ACTIVE' ? '停用' : '启用' }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #partner="{ row }"><div class="partner-cell"><span><Building2 :size="18" /></span><div><strong>{{ (row as PlatformPartnerResource).partnerName }}</strong></div></div></template>
|
||||
<template #partner="{ row }"><div class="partner-cell"><span><Building2 :size="18" /></span><div><LineClampText :content="(row as PlatformPartnerResource).partnerName" /></div></div></template>
|
||||
<template #type="{ row }"><div class="partner-types"><el-tag v-for="type in (row as PlatformPartnerResource).types" :key="type" effect="plain">{{ partnerTypeLabel(type) }}</el-tag></div></template>
|
||||
<template #address="{ row }"><div class="partner-address"><MapPin :size="15" /><span>{{ [(row as PlatformPartnerResource).province, (row as PlatformPartnerResource).city, (row as PlatformPartnerResource).district, (row as PlatformPartnerResource).addressLine].filter(Boolean).join(' ') }}</span></div></template>
|
||||
<template #status="{ row }"><el-tag :type="(row as PlatformPartnerResource).status === 'ACTIVE' ? 'success' : 'info'" effect="light">{{ (row as PlatformPartnerResource).status === 'ACTIVE' ? '启用' : '停用' }}</el-tag></template>
|
||||
@@ -495,7 +496,7 @@ onMounted(() => {
|
||||
.partner-cell { display: flex; align-items: center; gap: 10px; }
|
||||
.partner-cell > span { display: grid; width: 34px; height: 34px; flex: 0 0 34px; place-items: center; border-radius: 7px; background: #eef3fa; color: var(--cc-primary); }
|
||||
.partner-cell > div { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
|
||||
.partner-cell strong { overflow-wrap: anywhere; }
|
||||
.partner-cell :deep(.line-clamp-text) { width: 100%; }
|
||||
.partner-address { display: flex; align-items: flex-start; gap: 7px; line-height: 1.55; overflow-wrap: anywhere; }
|
||||
.partner-address svg { flex: 0 0 15px; margin-top: 3px; color: var(--cc-muted); }
|
||||
.partner-detail { min-height: 180px; padding: 20px 24px 28px; box-sizing: border-box; }
|
||||
|
||||
@@ -8,6 +8,13 @@ const detailSource = readFileSync(resolve(process.cwd(), 'src/modules/suppliers/
|
||||
const routesSource = readFileSync(resolve(process.cwd(), 'src/modules/suppliers/routes.ts'), 'utf8')
|
||||
|
||||
describe('supplier formal pages', () => {
|
||||
it('clamps long supplier and represented-carrier names with the shared component', () => {
|
||||
expect(source).toContain("LineClampText from '@/shared/components/LineClampText.vue'")
|
||||
expect(source).toContain('<template #name="{ row }"><LineClampText :content="row.name" /></template>')
|
||||
expect(source).toContain('<template #carrierCodes="{ row }"><LineClampText')
|
||||
expect(source).not.toContain('class="supplier-name"')
|
||||
})
|
||||
|
||||
it('keeps actions first and fixed left while guarding all operations with backend permissions', () => {
|
||||
expect(source).toContain("{ key: 'actions', label: '操作', width: 260, fixed: 'left'")
|
||||
expect(source).toContain("access.can('platform:supplier:create')")
|
||||
|
||||
@@ -8,6 +8,7 @@ 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 LineClampText from '@/shared/components/LineClampText.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'
|
||||
@@ -43,15 +44,15 @@ const queryFields = computed<QueryField[]>(() => [
|
||||
])
|
||||
|
||||
const columns = computed<DataTableColumn[]>(() => [
|
||||
{ key: 'actions', label: '操作', width: 260, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'name', label: '供应商名称', minWidth: 230, slotName: 'name' },
|
||||
{ key: 'category', label: '类别', width: 90, slotName: 'category' },
|
||||
{ key: 'carrierCodes', label: '可代理船公司', minWidth: 240, slotName: 'carrierCodes' },
|
||||
{ key: 'region', label: '地区', minWidth: 110 },
|
||||
{ key: 'legalRepresentative', label: '法人', minWidth: 120 },
|
||||
{ key: 'contactName', label: '联系人', minWidth: 110 },
|
||||
{ key: 'phone', label: '联系电话', minWidth: 150 },
|
||||
{ key: 'businessLicenseNo', label: '营业执照号', minWidth: 190 },
|
||||
{ key: 'actions', label: '操作', width: 230, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'name', label: '供应商名称', minWidth: 200, slotName: 'name' },
|
||||
{ key: 'category', label: '类别', width: 70, slotName: 'category' },
|
||||
{ key: 'carrierCodes', label: '可代理船公司', minWidth: 200, slotName: 'carrierCodes' },
|
||||
{ key: 'region', label: '地区', minWidth: 100 },
|
||||
{ key: 'legalRepresentative', label: '法人', minWidth: 100 },
|
||||
{ key: 'contactName', label: '联系人', minWidth: 120 },
|
||||
{ key: 'phone', label: '联系电话', minWidth: 120 },
|
||||
{ key: 'businessLicenseNo', label: '营业执照号', minWidth: 120 },
|
||||
{ key: 'license', label: '营业执照', width: 125, slotName: 'license' },
|
||||
{ key: 'status', label: '状态', width: 100, slotName: 'status' },
|
||||
{ key: 'settlementPeriod', label: '结算周期', width: 120, slotName: 'settlement' },
|
||||
@@ -153,9 +154,9 @@ onActivated(() => {
|
||||
<el-button v-if="access.can('platform:supplier:status')" size="small" plain :type="row.status === 'ACTIVE' ? 'danger' : 'success'" :loading="actionId === row.id" @click="toggleStatus(supplierRow(row))"><Power :size="15" />{{ row.status === 'ACTIVE' ? '停用' : '启用' }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template #name="{ row }"><strong class="supplier-name">{{ row.name }}</strong></template>
|
||||
<template #name="{ row }"><LineClampText :content="row.name" /></template>
|
||||
<template #category="{ row }">{{ categoryLabels[row.category as SupplierCategory] }}</template>
|
||||
<template #carrierCodes="{ row }">{{ supplierRow(row).category === 'BOOKING' ? formatCarrierCodes(supplierRow(row).carrierCodes) : '—' }}</template>
|
||||
<template #carrierCodes="{ row }"><LineClampText :content="supplierRow(row).category === 'BOOKING' ? formatCarrierCodes(supplierRow(row).carrierCodes) : null" /></template>
|
||||
<template #license="{ row }"><el-button size="small" plain type="primary" @click="showLicense(supplierRow(row))"><FileText :size="15" />查看</el-button></template>
|
||||
<template #status="{ row }"><StatusBadge :status="statusLabels[row.status as SupplierStatus]" /></template>
|
||||
<template #settlement="{ row }">{{ settlementLabels[supplierRow(row).settlementPeriod] }}</template>
|
||||
@@ -168,7 +169,7 @@ onActivated(() => {
|
||||
|
||||
<style scoped>
|
||||
.supplier-list-top { display: grid; gap: 16px; }.supplier-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }.supplier-metrics button { min-width: 0; padding: 12px 14px; border: 1px solid #e7edf5; border-radius: 8px; background: #f8fafc; color: var(--cc-text); text-align: left; cursor: pointer; }.supplier-metrics button.active { border-color: var(--cc-primary); background: #f7f8ff; box-shadow: inset 0 3px 0 var(--cc-primary); }.supplier-metrics span { display: block; margin-bottom: 6px; color: #64748b; font-size: 12px; }.supplier-metrics strong { color: #1e293b; font-size: 20px; }.supplier-metrics small { margin-left: 3px; color: #64748b; font-size: 12px; }.supplier-query-panel { overflow: hidden; border: 1px solid var(--cc-border); border-radius: 8px; background: #fff; }.supplier-query-toolbar { border: 0; }.supplier-tabs { display: flex; gap: 6px; padding: 10px 14px; overflow-x: auto; border-bottom: 1px solid var(--cc-border); background: #fafbfc; }.supplier-tabs button { min-width: 76px; padding: 7px 20px; border: 0; border-radius: 6px; background: #f1f3f5; color: #555; cursor: pointer; }.supplier-tabs button.active { background: var(--cc-primary); color: #fff; font-weight: 650; }
|
||||
.row-actions { display: flex; align-items: center; white-space: nowrap; }.row-actions :deep(.el-button + .el-button) { margin-left: 8px; }.supplier-name { overflow-wrap: anywhere; }
|
||||
.row-actions { display: flex; align-items: center; white-space: nowrap; }.row-actions :deep(.el-button + .el-button) { margin-left: 8px; }
|
||||
@media (max-width: 720px) { .supplier-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||||
@media (max-width: 380px) { .supplier-metrics { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
|
||||
@@ -2223,7 +2223,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
<template #orderStatus="{ row }"><StatusBadge :status="orderStatusLabel((row as UnifiedOrderResource).internalOrderStatus || (row as UnifiedOrderResource).mainStatus)" /></template>
|
||||
<template #measurementReviewStatus="{ row }"><StatusBadge :status="measurementStatusLabel((row as UnifiedOrderResource).measurement?.reviewStatus)" /></template>
|
||||
<template #channel="{ row }"><div class="channel-cell"><span>{{ (row as UnifiedOrderResource).channelName || '—' }}</span><el-tag v-if="(row as UnifiedOrderResource).channelChanged" size="small" type="warning">曾变更渠道</el-tag></div></template>
|
||||
<template #channel="{ row }"><div class="channel-cell"><LineClampText :content="(row as UnifiedOrderResource).channelName" /><el-tag v-if="(row as UnifiedOrderResource).channelChanged" size="small" type="warning">曾变更渠道</el-tag></div></template>
|
||||
<template #estimatedVolume="{ row }">
|
||||
<div class="planning-cell">
|
||||
<el-input-number
|
||||
@@ -2270,7 +2270,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #field="{ row, column }"><LineClampText v-if="['customerRemark', 'latestInternalNote'].includes(String(column.property))" :content="displayField(row as UnifiedOrderResource, column.property)" :lines="2" /><template v-else>{{ displayField(row as UnifiedOrderResource, column.property) }}</template></template>
|
||||
<template #field="{ row, column }"><LineClampText v-if="column.property === 'customerName'" :content="displayField(row as UnifiedOrderResource, column.property)" /><LineClampText v-else-if="['customerRemark', 'latestInternalNote'].includes(String(column.property))" :content="displayField(row as UnifiedOrderResource, column.property)" :lines="2" /><template v-else>{{ displayField(row as UnifiedOrderResource, column.property) }}</template></template>
|
||||
<template #mainStatus="{ row }"><StatusBadge :status="statusLabel((row as UnifiedOrderResource).mainStatus)" /></template>
|
||||
<template #customer="{ row }"><div class="primary-cell"><strong>{{ customerName(row as UnifiedOrderResource) }}</strong></div></template>
|
||||
<template #receivingWarehouseName="{ row }">{{ (row as UnifiedOrderResource).receivingWarehouseName || '—' }}</template>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const read = (path: string) => readFileSync(resolve(process.cwd(), path), 'utf8')
|
||||
|
||||
describe('LineClampText domestic list consumers', () => {
|
||||
it.each([
|
||||
['统一订单', 'src/modules/unified-orders/pages/UnifiedOrdersView.vue'],
|
||||
['入库任务', 'src/modules/inbounds/components/InboundTaskList.vue'],
|
||||
['入库单', 'src/modules/inbounds/pages/InboundOrdersView.vue'],
|
||||
['装柜清单', 'src/modules/loading-lists/components/LoadingListsView.vue'],
|
||||
['柜次智能装柜', 'src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue'],
|
||||
['全局智能装柜', 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'],
|
||||
])('%s列表复用共享的行数省略组件', (_name, path) => {
|
||||
expect(read(path)).toContain("LineClampText from '@/shared/components/LineClampText.vue'")
|
||||
})
|
||||
|
||||
it('统一订单客户与渠道都使用一行省略,并保留渠道变更标记', () => {
|
||||
const source = read('src/modules/unified-orders/pages/UnifiedOrdersView.vue')
|
||||
expect(source).toContain("column.property === 'customerName'")
|
||||
expect(source).toContain('<LineClampText :content="(row as UnifiedOrderResource).channelName" />')
|
||||
expect(source).toContain('曾变更渠道')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['src/modules/inbounds/components/InboundTaskList.vue', '#customerName', '#channelName'],
|
||||
['src/modules/inbounds/pages/InboundOrdersView.vue', '#customerName', '#channelName'],
|
||||
])('入库列表客户与渠道均使用共享组件:%s', (path, customerSlot, channelSlot) => {
|
||||
const source = read(path)
|
||||
expect(source).toContain(`<template ${customerSlot}`)
|
||||
expect(source).toContain(`<template ${channelSlot}`)
|
||||
expect(source.match(/<LineClampText/g)?.length).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
|
||||
it('装柜清单将多客户汇总限制为一行', () => {
|
||||
const source = read('src/modules/loading-lists/components/LoadingListsView.vue')
|
||||
expect(source).toContain('<template #customers="{ row }"><LineClampText')
|
||||
})
|
||||
|
||||
it.each([
|
||||
'src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue',
|
||||
'src/modules/container-loading/pages/GlobalSmartLoadingView.vue',
|
||||
])('智能装柜候选与已装载客户列使用相同的一行省略:%s', (path) => {
|
||||
const source = read(path)
|
||||
expect(source.match(/slotName: 'customerName'/g)).toHaveLength(2)
|
||||
expect(source.match(/<template #customerName/g)).toHaveLength(2)
|
||||
expect(source.match(/<LineClampText :content=/g)?.length).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user