优化
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# 海外通用资料与车辆管理
|
||||
|
||||
- 自有业务:`pages/OwnedBusinessView.vue`、`OwnedBusinessDetailView.vue`及`api/ownedBusiness.ts`沿`fleet-owned-business-v1.yaml`,是原排车装车后的自有车辆运营视图,不按货源分类或另建履约流程;共同规则见`../../../../../docs/海外仓业务/自有业务首版开发契约.md`。详情三个页签,运营登记1080px居中、照片760px居中;金额/油耗单位、附加与排车版本分离,`owned-business:list/edit/upload`独立,统计沿完整计划发车日期筛选。
|
||||
- 外配业务:`pages/OutsourcedBusinessView.vue`、`OutsourcedBusinessDetailView.vue`及`api/outsourcedBusiness.ts`沿`fleet-outsourced-business-v1.yaml`;共同规则见`../../../../../docs/海外仓业务/外配业务首版开发契约.md`。读取装车时DISPATCHED归属,包括临时EXTERNAL车辆,不按货源区分。一车一行、三页签详情、整车承运金额除实托数、逐目的仓到达和五个装车照片位;无里程/油耗/油耗照片、成本拆分和新增履约流程。三项`outsourced-business:list/edit/upload`与自有权限独立,缺承运商保留空,vehicleId可空;其余版本、原键、真实来源及完整筛选统计沿共同契约,不能静默把外配写入当成自有权限。
|
||||
- 自有业务:`pages/OwnedBusinessView.vue`、`OwnedBusinessDetailView.vue`及`api/ownedBusiness.ts`沿`fleet-owned-business-v1.yaml`,是原排车装车后未实际接入拼车货物的运营视图,与车辆归属无关,不另建履约流程;共同规则见`../../../../../docs/海外仓业务/自有业务首版开发契约.md`。详情三个页签,运营登记1080px居中、照片760px居中;金额/油耗单位、附加与排车版本分离,`owned-business:list/edit/upload`独立,统计沿完整计划发车日期筛选。
|
||||
- 外配任务:`pages/OutsourcedBusinessView.vue`、`OutsourcedBusinessDetailView.vue`及`api/outsourcedBusiness.ts`沿`fleet-outsourced-business-v1.yaml`;共同规则见`../../../../../docs/海外仓业务/外配业务首版开发契约.md`。按需求池实际接入本车的拼车货物分类,完整判定见外配契约;车辆归属独立,允许OWNED/DISPATCHED及临时EXTERNAL车辆。一车一行、三页签详情、整车承运金额除实托数、逐目的仓到达和五个装车照片位;无里程/油耗/油耗照片、成本拆分和新增履约流程。三项`outsourced-business:list/edit/upload`与自有权限独立,缺承运商保留空,vehicleId可空;其余版本、原键、真实来源及完整筛选统计沿共同契约,不能静默把外配写入当成自有权限。
|
||||
|
||||
- 运输路线地图:`components/TransportRouteDialog.vue`经`index.ts`公开导出,排车装车后、本土散板列表、本土卡车表单共用;来源字段、缺值规则和原型映射唯一见`PROTOTYPE_MATRIX.md`“运输路线地图”。仓库引用经`routeLocations.ts`复用Warehouse列表/详情解析完整地址(warehouse:list);解析失败不加载地图。仅规划路线,不接GPS、不用固定样例,不在车辆主档顶部重复放入口。
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ function operations(value: OutsourcedBusinessOperations): OutsourcedBusinessOper
|
||||
if (value.money && amount == null) throw new Error('承运金额与币种必须同时登记')
|
||||
return { money: value.money ? { currency: value.money.currency, amount: amount! } : null, arrivals: value.arrivals }
|
||||
}
|
||||
function requireSlot(slot: OutsourcedBusinessPhotoSlot) { if (!outsourcedBusinessPhotoRoles.some(role => role.slot === slot)) throw new Error('外配业务不支持该照片分类') }
|
||||
function requireSlot(slot: OutsourcedBusinessPhotoSlot) { if (!outsourcedBusinessPhotoRoles.some(role => role.slot === slot)) throw new Error('外配任务不支持该照片分类') }
|
||||
function row(value: OutsourcedBusinessRow): OutsourcedBusinessRow {
|
||||
if (!value.dispatchNo || !['AFTER_LOADING','DELIVERING','DELIVERED'].includes(value.stage) || value.vehicle?.ownership !== 'DISPATCHED') throw new Error('外配排车来源或阶段不完整,请重新读取')
|
||||
return { ...value, operations: operations(value.operations), unitPrice: decimal(value.unitPrice,2,18), recordVersion: outsourcedBusinessVersion(value.recordVersion), dispatchRecordVersion: outsourcedBusinessVersion(value.dispatchRecordVersion), actualPalletCount: value.actualPalletCount == null ? null : count(value.actualPalletCount), photoSummary: { loadingCount: count(value.photoSummary.loadingCount) } }
|
||||
|
||||
@@ -32,7 +32,7 @@ const columns: DataTableColumn[] = [
|
||||
{ key: 'updatedAt', label: '更新时间', minWidth: 180, slotName: 'time' },
|
||||
]
|
||||
</script>
|
||||
<template><div class="workbench-table"><DataTable :appearance="integrated ? 'integrated' : 'default'" :stripe="false" :results-visible="!failed" preference-key="fleet-capacity-inquiries" column-settings-mode="show" v-model:page="page" v-model:page-size="size" :columns="columns" :data="rows" :total="total" :loading="loading" row-key="bookingNo" empty-text="当前范围暂无意向">
|
||||
<template><div class="workbench-table"><DataTable :appearance="integrated ? 'integrated' : 'default'" :results-visible="!failed" preference-key="fleet-capacity-inquiries" column-settings-mode="show" v-model:page="page" v-model:page-size="size" :columns="columns" :data="rows" :total="total" :loading="loading" row-key="bookingNo" empty-text="当前范围暂无意向">
|
||||
<template v-if="$slots['toolbar-start']" #toolbar-start><slot name="toolbar-start" /></template>
|
||||
<template v-if="$slots.filters" #filters><slot name="filters" /></template>
|
||||
<template v-if="$slots.notices" #notices><slot name="notices" /></template>
|
||||
|
||||
@@ -11,11 +11,11 @@ const emit = defineEmits<{ back: [] }>()
|
||||
|
||||
<template>
|
||||
<AdminPage class="admin-density admin-workbench" spacing="compact">
|
||||
<DetailPageLayout aria-label="外配业务详情" class="workbench-panel is-flush is-page-surface">
|
||||
<DetailPageLayout aria-label="外配任务详情" class="workbench-panel is-flush is-page-surface">
|
||||
<template #header>
|
||||
<div class="outsourced-business-detail-heading">
|
||||
<div><strong>外配业务</strong><span class="outsourced-business-reference">{{ dispatchNo || '—' }}</span><span v-if="statusText">{{ statusText }}</span></div>
|
||||
<div class="density-actions"><slot name="actions" /><el-button class="density-action" @click="emit('back')">返回外配业务</el-button></div>
|
||||
<div><strong>外配任务</strong><span class="outsourced-business-reference">{{ dispatchNo || '—' }}</span><span v-if="statusText">{{ statusText }}</span></div>
|
||||
<div class="density-actions"><slot name="actions" /><el-button class="density-action" @click="emit('back')">返回外配任务</el-button></div>
|
||||
</div>
|
||||
</template>
|
||||
<slot name="state" />
|
||||
|
||||
@@ -8,7 +8,7 @@ import ListPageLayout from '@/shared/components/ListPageLayout.vue'
|
||||
<template>
|
||||
<AdminPage class="admin-density admin-workbench" spacing="compact">
|
||||
<slot name="statistics" />
|
||||
<ListPageLayout aria-label="外配业务任务列表">
|
||||
<ListPageLayout aria-label="外配任务任务列表">
|
||||
<template v-if="$slots.filters" #toolbar><slot name="filters" /></template>
|
||||
<template v-if="$slots.state" #state><slot name="state" /></template>
|
||||
<slot />
|
||||
|
||||
@@ -20,7 +20,7 @@ const emit = defineEmits<{ saved: [value: OutsourcedBusinessDetail]; busy: [valu
|
||||
const auth = useAuthStore(), access = useAccessPreviewStore(), busy = ref(false), pending = ref<OutsourcedBusinessPending | null>(null), error = ref<ApiErrorDetails | null>(null), corrupted = ref(false)
|
||||
const form = reactive({ currency: '', amount: '', reason: '', arrivals: {} as Record<string,string> })
|
||||
const active = computed(() => open.value || Boolean(pending.value))
|
||||
const registration = useUnsavedWorkRegistration({ key: `outsourced-business-operations:${getCurrentInstance()?.uid}`, label: computed(() => '外配业务运营登记'), isOpen: active, model: reactive({ form, pending }), requiredPermission: computed(() => permission.outsourcedBusinessEdit) })
|
||||
const registration = useUnsavedWorkRegistration({ key: `outsourced-business-operations:${getCurrentInstance()?.uid}`, label: computed(() => '外配任务运营登记'), isOpen: active, model: reactive({ form, pending }), requiredPermission: computed(() => permission.outsourcedBusinessEdit) })
|
||||
let actor = '', number = '', baseline = ''
|
||||
const writable = computed(() => !corrupted.value && access.can(permission.outsourcedBusinessEdit) && props.detail?.row.actions.edit.allowed === true)
|
||||
const locked = computed(() => busy.value || Boolean(pending.value))
|
||||
|
||||
@@ -20,7 +20,7 @@ const emit = defineEmits<{ saved: [value: OutsourcedBusinessDetail]; busy: [valu
|
||||
const auth = useAuthStore(), access = useAccessPreviewStore(), busy = ref(false), error = ref<ApiErrorDetails | null>(null), pending = ref<OutsourcedBusinessPending | null>(null), prepared = ref<OutsourcedBusinessPhoto | null>(null), corrupted = ref(false)
|
||||
const urls = ref<Partial<Record<OutsourcedBusinessPhotoSlot,string>>>({}), reading = ref<OutsourcedBusinessPhotoSlot | null>(null)
|
||||
const uploadable = computed(() => !corrupted.value && access.can(permission.outsourcedBusinessUpload) && props.detail?.row.actions.upload.allowed === true)
|
||||
useUnsavedWorkRegistration({ key: `outsourced-business-photos:${getCurrentInstance()?.uid}`, label: computed(() => '外配业务照片作业'), isOpen: computed(() => open.value || Boolean(pending.value) || Boolean(prepared.value)), model: reactive({ pending, prepared }), requiredPermission: computed(() => permission.outsourcedBusinessUpload) })
|
||||
useUnsavedWorkRegistration({ key: `outsourced-business-photos:${getCurrentInstance()?.uid}`, label: computed(() => '外配任务照片作业'), isOpen: computed(() => open.value || Boolean(pending.value) || Boolean(prepared.value)), model: reactive({ pending, prepared }), requiredPermission: computed(() => permission.outsourcedBusinessUpload) })
|
||||
let actor = '', number = '', generation = 0
|
||||
function savedPhoto(slot: OutsourcedBusinessPhotoSlot) { return props.detail?.photos.find(photo => photo.slot === slot && photo.savedAt) }
|
||||
function release() { Object.values(urls.value).forEach(url => { if (url) URL.revokeObjectURL(url) }); urls.value = {} }
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface OutsourcedBusinessDestination { destinationKey: string; warehou
|
||||
export interface OutsourcedBusinessRow {
|
||||
dispatchNo: string; stage: OutsourcedBusinessStage; plannedDepartureAt: string | null; loadingCompletedAt: string; departedAt: string | null; deliveryCompletedAt: string | null
|
||||
origin: { warehouseId: string; warehouseCode: string; warehouseName: string }
|
||||
vehicle: { vehicleId: string | null; plate: string | null; ownership: 'DISPATCHED'; carrierName: string | null }; driver: { name: string | null; phone: string | null }
|
||||
vehicle: { vehicleId: string | null; plate: string | null; ownership: 'OWNED' | 'DISPATCHED'; carrierName: string | null }; driver: { name: string | null; phone: string | null }
|
||||
destinations: OutsourcedBusinessDestination[]; actualPalletCount: number | null; operations: OutsourcedBusinessOperations; unitPrice: string | null; recordVersion: string; dispatchRecordVersion: string
|
||||
photoSummary: { loadingCount: number }; actions: { edit: OutsourcedBusinessGate; upload: OutsourcedBusinessGate }; sourceIssues: string[]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface OwnedBusinessDestination { destinationKey: string; warehouseCod
|
||||
export interface OwnedBusinessRow {
|
||||
dispatchNo: string; stage: OwnedBusinessStage; plannedDepartureAt: string; loadingCompletedAt: string; departedAt: string | null; deliveryCompletedAt: string | null
|
||||
origin: { warehouseId: string; warehouseCode: string; warehouseName: string }
|
||||
vehicle: { vehicleId: string; plate: string; ownership: 'OWNED' }; driver: { name: string | null; phone: string | null }
|
||||
vehicle: { vehicleId: string | null; plate: string | null; ownership: 'OWNED' | 'DISPATCHED' }; driver: { name: string | null; phone: string | null }
|
||||
destinations: OwnedBusinessDestination[]; actualPalletCount: number | null; operations: OwnedBusinessOperations; unitPrice: string | null; recordVersion: string; dispatchRecordVersion: string
|
||||
photoSummary: { loadingCount: number; fuelCount: number }; actions: { edit: OwnedBusinessGate; upload: OwnedBusinessGate }; sourceIssues: string[]
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ onBeforeUnmount(() => { followupSequence++; sequence++; inquirySequence++ })
|
||||
<dl class="density-fields"><div><dt>车次号</dt><dd><el-button v-if="access.can('platform:fleet:trip:list')" link type="primary" @click="router.push({ name: 'overseas-master-data-trip-detail', params: { tripNo: detail.offer.tripNo } })">{{ detail.offer.tripNo }}</el-button><span v-else>{{ detail.offer.tripNo }}</span></dd></div><div><dt>起运仓</dt><dd>{{ detail.offer.warehouseName }}</dd></div><div><dt>计划发车</dt><dd>{{ offerTime(detail.offer.plannedDepartureAt) }}</dd></div><div><dt>最晚送仓</dt><dd>{{ offerTime(detail.offer.cutoffAt) }}</dd></div><div><dt>调度联系人</dt><dd>{{ detail.offer.contactName || '—' }}</dd></div><div><dt>调度联系方式</dt><dd>{{ detail.offer.contactPhone || '—' }}</dd></div></dl>
|
||||
<p v-if="hasCreatePermission && !detail.offer.canCreateInquiry" role="status">无法登记客户货物:{{ detail.offer.blockReason || '当前发布不接受新意向' }}</p>
|
||||
<p v-else>{{ detail.offer.blockReason || '按所选卸货站核对每段余位;登记客户货物不占位,调度确认后占位。' }}</p><p v-if="detail.offer.remark">{{ detail.offer.remark }}</p>
|
||||
<h2 class="capacity-section-title">分段运力</h2><div class="workbench-table"><DataTable :data="detail.offer.segments" :columns="columns" :stripe="false" row-key="fromIndex" empty-text="容量待调度核对"><template #planned="{ row }">{{ loadText(row.planned) }}</template><template #reserved="{ row }">{{ loadText(row.reserved) }}</template><template #remaining="{ row }">{{ detail.offer.capacityKnown ? loadText(row.remaining) : '待调度核对' }}</template></DataTable></div>
|
||||
<h2 class="capacity-section-title">分段运力</h2><div class="workbench-table"><DataTable :data="detail.offer.segments" :columns="columns" row-key="fromIndex" empty-text="容量待调度核对"><template #planned="{ row }">{{ loadText(row.planned) }}</template><template #reserved="{ row }">{{ loadText(row.reserved) }}</template><template #remaining="{ row }">{{ detail.offer.capacityKnown ? loadText(row.remaining) : '待调度核对' }}</template></DataTable></div>
|
||||
<h2 class="capacity-section-title">{{ access.can('platform:fleet:capacity-offer:manage') ? '本发布全部意向' : '我的登记' }}</h2>
|
||||
<LoadErrorState v-if="inquiryError || commands.error.value" title="意向操作未完成" :message="(inquiryError || commands.error.value)?.message" @retry="commands.state.pending ? commands.retry() : loadInquiries()"><template #details><ApiErrorMeta :details="(inquiryError || commands.error.value)!" /></template></LoadErrorState><el-button v-if="commands.state.pending" :loading="commands.busy.value" @click="commands.retry">重试原操作</el-button>
|
||||
<CapacityInquiryTable v-model:page="page" v-model:size="size" :rows="detail.inquiries.records" :total="detail.inquiries.total" :loading="inquiryLoading" :failed="!!inquiryError" :busy="commands.busy.value" @action="action" />
|
||||
|
||||
@@ -129,13 +129,13 @@ watch([tab, page, size], load); onMounted(load); onBeforeUnmount(() => { followu
|
||||
</script>
|
||||
<template><AdminPage class="admin-density admin-workbench"><ListPageLayout aria-label="散板需求">
|
||||
<p v-if="selectedDemand && tab === 'offers'">正在匹配 {{ selectedDemand.orderNo }}:{{ selectedDemand.origin }} → {{ selectedDemand.destination }}。请选择合适车次,保存登记后尚不保位。<el-button link @click="selectedDemand = null">取消选择</el-button></p>
|
||||
<div v-if="tab === 'demands'" class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" preference-key="fleet-looseboard-demands" :results-visible="!error" v-model:page="page" v-model:page-size="size" :data="demands" :columns="demandColumns" :loading="loading" :total="total" row-key="orderNo" empty-text="暂无待匹配散板需求">
|
||||
<div v-if="tab === 'demands'" class="workbench-table is-page-surface"><DataTable appearance="integrated" preference-key="fleet-looseboard-demands" :results-visible="!error" v-model:page="page" v-model:page-size="size" :data="demands" :columns="demandColumns" :loading="loading" :total="total" row-key="orderNo" empty-text="暂无待匹配散板需求">
|
||||
<template #toolbar-start><div class="density-status-tabs workbench-list-tabs" role="tablist" aria-label="需求来源"><button v-if="canCreate" role="tab" aria-selected="true" class="is-active" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="false" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="false" @click="switchTab('inquiries')">我的登记</button></div></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="待匹配散板查询" :loading="loading" @search="search" /></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="需求读取失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template>
|
||||
<template #actions="{ row }"><div class="row-actions density-actions admin-text-actions"><el-button class="density-action" v-if="canCreate" @click="matchDemand(row as LooseboardDemand)">匹配车次</el-button></div></template>
|
||||
</DataTable></div>
|
||||
<div v-if="tab === 'offers'" class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" preference-key="fleet-capacity-offers" column-settings-mode="show" :results-visible="!error" v-model:page="page" v-model:page-size="size" :data="offers" :columns="columns" :loading="loading" :total="total" row-key="offerNo" :empty-text="Object.values(applied).some(Boolean) ? '当前筛选条件下暂无运力发布' : '暂无获准查看的运力发布'">
|
||||
<div v-if="tab === 'offers'" class="workbench-table is-page-surface"><DataTable appearance="integrated" preference-key="fleet-capacity-offers" column-settings-mode="show" :results-visible="!error" v-model:page="page" v-model:page-size="size" :data="offers" :columns="columns" :loading="loading" :total="total" row-key="offerNo" :empty-text="Object.values(applied).some(Boolean) ? '当前筛选条件下暂无运力发布' : '暂无获准查看的运力发布'">
|
||||
<template #toolbar-start><div class="density-status-tabs workbench-list-tabs" role="tablist" aria-label="需求来源"><button v-if="canCreate" role="tab" aria-selected="false" @click="switchTab('demands')">待匹配散板</button><button role="tab" aria-selected="true" class="is-active" @click="switchTab('offers')">可拼车次</button><button role="tab" aria-selected="false" @click="switchTab('inquiries')">我的登记</button></div></template><template #toolbar-end><el-button v-if="manage" @click="switchTab('inquiries'); filters = { scope: 'TODO' }; applied = { scope: 'TODO' }">待我处理</el-button><el-button v-if="manage && access.can('platform:fleet:trip:list')" @click="router.push({ name: 'overseas-master-data-trips' })">选择车次发布余位</el-button></template>
|
||||
<template #filters>
|
||||
<div class="integrated-query-group">
|
||||
|
||||
@@ -113,7 +113,7 @@ onBeforeUnmount(() => { request++ })
|
||||
<template>
|
||||
<AdminPage class="admin-density admin-workbench">
|
||||
<ListPageLayout aria-label="司机管理">
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" preference-key="overseas-drivers-table" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="id" empty-text="暂无符合条件的司机">
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" column-settings-mode="show" :results-visible="true" preference-key="overseas-drivers-table" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="id" empty-text="暂无符合条件的司机">
|
||||
<template #toolbar-start><h1>司机管理</h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="司机查询" preference-key="overseas-drivers-query" :loading="loading" @search="search" /></template>
|
||||
<template #notices>
|
||||
|
||||
@@ -50,7 +50,7 @@ watch([page, pageSize], load); onMounted(load)
|
||||
|
||||
|
||||
|
||||
<section><div class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="month" preference-key="overseas-exchange-rates-table" empty-text="暂无月度汇率">
|
||||
<section><div class="workbench-table is-page-surface"><DataTable appearance="integrated" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="month" preference-key="overseas-exchange-rates-table" empty-text="暂无月度汇率">
|
||||
<template #toolbar-start><h1>汇率管理</h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="汇率月份筛选" preference-key="overseas-exchange-rates-query" :loading="loading" @search="search" /></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="月度汇率加载失败" :message="error.message" @retry="load" /></template>
|
||||
|
||||
@@ -36,7 +36,7 @@ async function load() {
|
||||
if (!active) return
|
||||
const request = ++generation, no = dispatchNo.value; loading.value = true; error.value = null
|
||||
try { if (!no || !access.can(permission.outsourcedBusinessList)) throw new Error('缺少排车业务号或查看权限'); const value = await getOutsourcedBusiness(no); if (active && request === generation) { if (value.row.dispatchNo !== no) throw new Error('当前运营资料与排车业务号不一致'); detail.value = value; if (activeTab.value === 'history') void loadHistory() } }
|
||||
catch (cause) { if (active && request === generation) { detail.value = null; error.value = getApiErrorDetails(cause, '外配业务详情读取失败') } }
|
||||
catch (cause) { if (active && request === generation) { detail.value = null; error.value = getApiErrorDetails(cause, '外配任务详情读取失败') } }
|
||||
finally { if (request === generation) loading.value = false }
|
||||
}
|
||||
async function loadHistory() {
|
||||
@@ -66,7 +66,7 @@ onBeforeUnmount(() => { active = false; generation++; historyGeneration++; previ
|
||||
<template>
|
||||
<OutsourcedBusinessDetailLayout v-model:active-tab="activeTab" :dispatch-no="detail?.row.dispatchNo || dispatchNo" :status-text="detail ? outsourcedBusinessStages[detail.row.stage] : ''" :loading="loading" @back="router.push('/overseas-master-data/outsourced-business')">
|
||||
<template #actions><el-button v-if="detail && access.can(permission.outsourcedBusinessEdit)" class="density-action" :disabled="busy || !detail.row.actions.edit.allowed" @click="editorOpen = true">登记运营信息</el-button><el-button v-if="detail" class="density-action" :disabled="busy" @click="photosOpen = true">{{ access.can(permission.outsourcedBusinessUpload) && detail.row.actions.upload.allowed ? '上传照片' : '查看照片' }}</el-button></template>
|
||||
<template #state><LoadErrorState v-if="error" title="外配业务详情读取失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState><p v-for="issue in detail?.row.sourceIssues || []" :key="issue">{{ issue }}</p></template>
|
||||
<template #state><LoadErrorState v-if="error" title="外配任务详情读取失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState><p v-for="issue in detail?.row.sourceIssues || []" :key="issue">{{ issue }}</p></template>
|
||||
<template #task><dl v-if="detail" class="density-fields outsourced-business-fields"><div v-for="field in taskFields" :key="field[0]"><dt>{{ field[0] }}</dt><dd>{{ field[1] }}</dd></div></dl></template>
|
||||
<template #route><template v-if="detail"><section v-for="destination in detail.row.destinations" :key="destination.destinationKey" class="outsourced-business-route"><h3>{{ destination.warehouseCode || destination.warehouseName || destination.address || '目的地资料缺失' }}</h3><p v-if="destination.address">{{ destination.address }}</p><p v-for="appointment in destination.appointments" :key="appointment.appointmentNo">预约号 {{ appointment.appointmentNo }} · 确认号 {{ text(appointment.confirmationNo) }} · 预约到达 {{ time(appointment.scheduledAt) }}</p><p>实际到达 {{ time(destination.actualArrivedAt) }}</p></section><div class="workbench-table"><DataTable :data="detail.cargo" :columns="cargoColumns" row-key="sourceKey" column-settings-mode="hide" empty-text="暂无真实货物明细"><template #destination="{ row }">{{ destinationLabel(row.destinationKey) }}</template></DataTable></div><section class="outsourced-business-route"><h3>原排车关联单据</h3><p>出库单:{{ detail.dispatch.documents.outbounds.map(item => item.outboundNo).join('、') || '—' }}</p><p>派送单:{{ detail.dispatch.documents.deliveries.map(item => item.deliveryNo).join('、') || '—' }}</p><el-button @click="photosOpen = true">查看分类照片</el-button></section></template></template>
|
||||
<template #history><LoadErrorState v-if="historyError" title="操作记录读取失败" :message="historyError.message" @retry="loadHistory"><template #details><ApiErrorMeta :details="historyError" /></template></LoadErrorState><LoadErrorState v-if="previewError" title="历史照片读取失败" :message="previewError.message" :show-retry="false"><template #details><ApiErrorMeta :details="previewError" /></template></LoadErrorState><div class="workbench-table"><DataTable v-model:page="historyPage" v-model:page-size="historySize" :data="historyRows" :columns="historyColumns" :total="historyTotal" :loading="historyLoading" row-key="eventKey" empty-text="暂无操作记录"><template #source="{ row }">{{ row.eventSource === 'DISPATCH' ? '原排车' : '运营登记' }}</template><template #action="{ row }">{{ outsourcedBusinessActionLabels[row.action] || '其他操作' }}</template><template #occurredAt="{ row }">{{ time(row.occurredAt) }}</template><template #snapshot="{ row }"><template v-if="row.operations"><p>整车承运金额 {{ row.operations.money ? row.operations.money.currency + ' ' + row.operations.money.amount : '未登记' }}</p><div v-for="arrival in row.operations.arrivals" :key="arrival.destinationKey">{{ destinationLabel(arrival.destinationKey) }} · {{ time(arrival.actualArrivedAt) }}</div></template><span v-else>—</span></template><template #photos="{ row }"><el-button v-for="photo in row.photos" :key="photo.slot" link @click="preview(photo, row.recordVersion)">{{ photoLabel(photo) }}</el-button></template></DataTable></div></template>
|
||||
|
||||
@@ -62,10 +62,10 @@ async function load() {
|
||||
const request = ++generation
|
||||
loading.value = true; error.value = null
|
||||
try {
|
||||
if (!access.can(permission.outsourcedBusinessList)) throw new Error('没有外配业务查看权限')
|
||||
if (!access.can(permission.outsourcedBusinessList)) throw new Error('没有外配任务查看权限')
|
||||
const value = await listOutsourcedBusiness({ ...applied, page: page.value, size: size.value })
|
||||
if (active && request === generation) result.value = value
|
||||
} catch (cause) { if (active && request === generation) { result.value = null; error.value = getApiErrorDetails(cause, '外配业务读取失败') } }
|
||||
} catch (cause) { if (active && request === generation) { result.value = null; error.value = getApiErrorDetails(cause, '外配任务读取失败') } }
|
||||
finally { if (request === generation) loading.value = false }
|
||||
}
|
||||
function search() {
|
||||
@@ -102,11 +102,11 @@ onBeforeUnmount(() => { active = false; generation++; openGeneration++ })
|
||||
|
||||
<template>
|
||||
<OutsourcedBusinessListLayout>
|
||||
<template #statistics><section class="outsourced-business-statistics workbench-panel is-flush" aria-label="完整筛选范围外配业务统计" :aria-busy="loading"><div v-for="item in summary" :key="String(item[0])"><span>{{ item[0] }}</span><strong>{{ item[1] ?? '—' }}</strong></div></section></template>
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" v-model:page="page" v-model:page-size="size" :data="result?.records || []" :columns="columns" :total="result?.total || 0" :loading="loading" row-key="dispatchNo" preference-key="outsourced-business-table" empty-text="当前筛选范围暂无已装车的外配车辆排车单">
|
||||
<template #toolbar-start><h1>外配业务<InlineHelp title="查询范围说明">日期和时间按浏览器本地时区显示;统计覆盖同一完整筛选范围。</InlineHelp></h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="外配业务筛选" preference-key="outsourced-business-query" :loading="loading" @search="search" /></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="外配业务读取未完成" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState><p v-if="warehouseError">{{ warehouseError.message }} <el-button link @click="loadWarehouses">重试目录</el-button></p><p v-if="!access.can('platform:warehouse:list')">没有公共仓库目录读取权限,仓库筛选暂不可选。</p></template>
|
||||
<template #statistics><section class="outsourced-business-statistics workbench-panel is-flush" aria-label="完整筛选范围外配任务统计" :aria-busy="loading"><div v-for="item in summary" :key="String(item[0])"><span>{{ item[0] }}</span><strong>{{ item[1] ?? '—' }}</strong></div></section></template>
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" v-model:page="page" v-model:page-size="size" :data="result?.records || []" :columns="columns" :total="result?.total || 0" :loading="loading" row-key="dispatchNo" preference-key="outsourced-business-table" empty-text="当前筛选范围暂无已装车且实际接入拼车货物的排车单">
|
||||
<template #toolbar-start><h1>外配任务<InlineHelp title="查询范围说明">仅统计实际接入拼车货物的已装车排车,与车辆归属无关;日期和时间按浏览器本地时区显示,统计覆盖同一完整筛选范围。</InlineHelp></h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="外配任务筛选" preference-key="outsourced-business-query" :loading="loading" @search="search" /></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="外配任务读取未完成" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState><p v-if="warehouseError">{{ warehouseError.message }} <el-button link @click="loadWarehouses">重试目录</el-button></p><p v-if="!access.can('platform:warehouse:list')">没有公共仓库目录读取权限,仓库筛选暂不可选。</p></template>
|
||||
<template #actions="{ row }"><RowActions :actions="rowActions(row as OutsourcedBusinessRow)" text-actions @action="act(row as OutsourcedBusinessRow, $event)" /></template>
|
||||
<template #carrier="{ row }">{{ text(row.vehicle.carrierName) }}</template>
|
||||
<template #driver="{ row }">{{ text(row.driver.name) }}<br>{{ text(row.driver.phone) }}</template>
|
||||
|
||||
@@ -30,10 +30,21 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
describe('outsourced business views', () => {
|
||||
it('keeps an owned vehicle with pooled cargo in the outsourced task list', async () => {
|
||||
const detail = outsourcedBusinessFixture()
|
||||
detail.row.vehicle.ownership = 'OWNED'
|
||||
calls.list.mockResolvedValue({ records: [detail.row], page: 1, size: 20, total: 1, summary: { dispatchCount: 1, afterLoadingCount: 1, deliveringCount: 0, deliveredCount: 0 } })
|
||||
const wrapper = mount(OutsourcedBusinessView, { global }); await flushPromises()
|
||||
expect(wrapper.findComponent(Table).props('data')[0].vehicle.ownership).toBe('OWNED')
|
||||
expect(wrapper.findComponent(Table).props('total')).toBe(1)
|
||||
expect(wrapper.text()).toContain('外配任务')
|
||||
expect(wrapper.text()).toContain('登记运营信息')
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('uses one list request for all statistics and never hydrates each row', async () => {
|
||||
const wrapper = mount(OutsourcedBusinessView,{global}); await flushPromises()
|
||||
expect(calls.list).toHaveBeenCalledTimes(1); expect(calls.detail).not.toHaveBeenCalled(); expect(calls.content).not.toHaveBeenCalled()
|
||||
expect(wrapper.find('[aria-label="完整筛选范围外配业务统计"]').text()).toContain('排车数41')
|
||||
expect(wrapper.find('[aria-label="完整筛选范围外配任务统计"]').text()).toContain('排车数41')
|
||||
expect(wrapper.text()).not.toContain('今日排车')
|
||||
expect(wrapper.findComponent(Table).props('columns')[0].key).toBe('actions')
|
||||
expect(wrapper.findComponent(Table).props('total')).toBe(41)
|
||||
@@ -46,7 +57,7 @@ describe('outsourced business views', () => {
|
||||
expect(calls.list).toHaveBeenCalledTimes(1)
|
||||
query.vm.$emit('search');await flushPromises()
|
||||
expect(calls.list).toHaveBeenLastCalledWith({page:1,size:20,keyword:'FBA-1',stage:'AFTER_LOADING',warehouseId:'1',plannedDepartureFrom:dayjs('2026-10-20').startOf('day').toISOString(),plannedDepartureTo:dayjs('2026-10-21').add(1,'day').startOf('day').toISOString()})
|
||||
expect(wrapper.find('[aria-label="完整筛选范围外配业务统计"]').text()).toContain('装车完成30')
|
||||
expect(wrapper.find('[aria-label="完整筛选范围外配任务统计"]').text()).toContain('装车完成30')
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('opens the operations modal on demand with auxiliary zero version instead of the dispatch version', async () => {
|
||||
|
||||
@@ -58,7 +58,7 @@ watch([page,pageSize],load);onMounted(()=>{void Promise.all([load(),loadOptions(
|
||||
<template><AdminPage class="admin-density admin-workbench"><ListPageLayout aria-label="海外仓报价列表">
|
||||
|
||||
|
||||
<section><div class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-quotes-table" row-key="id" empty-text="暂无海外仓报价">
|
||||
<section><div class="workbench-table is-page-surface"><DataTable appearance="integrated" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-quotes-table" row-key="id" empty-text="暂无海外仓报价">
|
||||
<template #toolbar-start><h1>海外仓报价</h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="海外仓报价筛选" preference-key="overseas-quotes-query" :loading="loading" @search="search" /></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="海外仓报价加载失败" :message="error.message" @retry="load" /></template>
|
||||
|
||||
@@ -90,8 +90,8 @@ onBeforeUnmount(() => { active = false; generation++; openGeneration++ })
|
||||
<template>
|
||||
<OwnedBusinessListLayout>
|
||||
<template #statistics><section class="owned-business-statistics workbench-panel is-flush" aria-label="完整筛选范围自有业务统计" :aria-busy="loading"><div v-for="item in summary" :key="String(item[0])"><span>{{ item[0] }}</span><strong>{{ item[1] ?? '—' }}</strong></div></section></template>
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" v-model:page="page" v-model:page-size="size" :data="result?.records || []" :columns="columns" :total="result?.total || 0" :loading="loading" row-key="dispatchNo" preference-key="owned-business-table" empty-text="当前筛选范围暂无已装车的自有车辆排车单">
|
||||
<template #toolbar-start><h1>自有业务<InlineHelp title="查询范围说明">日期和时间按浏览器本地时区显示;统计覆盖同一完整筛选范围。</InlineHelp></h1></template>
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" v-model:page="page" v-model:page-size="size" :data="result?.records || []" :columns="columns" :total="result?.total || 0" :loading="loading" row-key="dispatchNo" preference-key="owned-business-table" empty-text="当前筛选范围暂无已装车且未接入拼车货物的排车单">
|
||||
<template #toolbar-start><h1>自有业务<InlineHelp title="查询范围说明">仅统计未实际接入拼车货物的已装车排车,与车辆归属无关;日期和时间按浏览器本地时区显示,统计覆盖同一完整筛选范围。</InlineHelp></h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="自有业务筛选" preference-key="owned-business-query" :loading="loading" @search="search" /></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="自有业务读取未完成" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState><p v-if="warehouseError">{{ warehouseError.message }} <el-button link @click="loadWarehouses">重试目录</el-button></p><p v-if="!access.can('platform:warehouse:list')">没有公共仓库目录读取权限,仓库筛选暂不可选。</p></template>
|
||||
<template #actions="{ row }"><div class="density-actions"><el-button class="density-action" link @click="router.push(`/overseas-master-data/owned-business/${encodeURIComponent(row.dispatchNo)}`)">查看明细</el-button><el-button v-if="access.can(permission.ownedBusinessEdit)" class="density-action" link :disabled="opening || mutating || !row.actions.edit.allowed" @click="openAction(row as OwnedBusinessRow, 'operations')">登记运营信息</el-button><el-button class="density-action" link :disabled="opening || mutating" @click="openAction(row as OwnedBusinessRow, 'photos')">{{ access.can(permission.ownedBusinessUpload) && row.actions.upload.allowed ? '上传照片' : '查看照片' }}</el-button></div></template>
|
||||
|
||||
@@ -30,6 +30,16 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
describe('owned business views', () => {
|
||||
it('keeps a non-pool external vehicle returned by the server without filtering or requiring a vehicle id', async () => {
|
||||
const detail = ownedBusinessFixture()
|
||||
detail.row.vehicle = { vehicleId: null, plate: 'EXTERNAL-NO-POOL', ownership: 'DISPATCHED' }
|
||||
calls.list.mockResolvedValue({ records: [detail.row], page: 1, size: 20, total: 1, summary: { dispatchCount: 1, afterLoadingCount: 1, deliveringCount: 0, deliveredCount: 0 } })
|
||||
const wrapper = mount(OwnedBusinessView, { global }); await flushPromises()
|
||||
expect(wrapper.findComponent(Table).props('data')[0].vehicle).toEqual(detail.row.vehicle)
|
||||
expect(wrapper.findComponent(Table).props('total')).toBe(1)
|
||||
expect(wrapper.text()).toContain('登记运营信息')
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('uses one list request for all statistics and never hydrates each row', async () => {
|
||||
const wrapper = mount(OwnedBusinessView,{global}); await flushPromises()
|
||||
expect(calls.list).toHaveBeenCalledTimes(1); expect(calls.detail).not.toHaveBeenCalled(); expect(calls.content).not.toHaveBeenCalled()
|
||||
|
||||
@@ -93,7 +93,7 @@ watch([page, size], load); onMounted(load); onBeforeUnmount(() => { sequence++ }
|
||||
<template #state>
|
||||
<LoadErrorState v-if="error" title="查询或导出失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
|
||||
</template>
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" preference-key="fleet-operations-table" v-model:page="page" v-model:page-size="size" :data="result?.page.records || []" :columns="columns" :total="result?.page.total || 0" :loading="loading" row-key="tripNo" empty-text="该范围暂无实际出车记录">
|
||||
<div class="workbench-table is-page-surface"><DataTable appearance="integrated" column-settings-mode="show" :results-visible="true" preference-key="fleet-operations-table" v-model:page="page" v-model:page-size="size" :data="result?.page.records || []" :columns="columns" :total="result?.page.total || 0" :loading="loading" row-key="tripNo" empty-text="该范围暂无实际出车记录">
|
||||
<template #toolbar-start><h1>出车记录</h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="运营记录查询" preference-key="fleet-operations-query" :loading="loading" @search="search" @reset="resetFilters" /></template>
|
||||
<template #toolbar-actions><el-button v-if="access.can('platform:fleet:operations:export') && access.can('platform:download:list')" :loading="exporting" @click="download">导出</el-button></template>
|
||||
|
||||
@@ -52,7 +52,7 @@ watch([page, size], load); onMounted(load); onBeforeUnmount(() => { sequence++ }
|
||||
<AdminPage class="admin-density admin-workbench"><ListPageLayout aria-label="车次管理">
|
||||
<el-tabs v-model="workTab" class="density-element-tabs"><el-tab-pane label="车次管理" name="trips" /><el-tab-pane label="待取货" name="pickups" /></el-tabs>
|
||||
<PickupDemandPanel v-if="pickupVisited" v-show="workTab === 'pickups'" />
|
||||
<div class="workbench-table is-page-surface"><DataTable v-show="workTab === 'trips'" appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" preference-key="fleet-trips-table" v-model:page="page" v-model:page-size="size" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="tripNo" empty-text="暂无车次,先建立计划再关联排车单">
|
||||
<div class="workbench-table is-page-surface"><DataTable v-show="workTab === 'trips'" appearance="integrated" column-settings-mode="show" :results-visible="true" preference-key="fleet-trips-table" v-model:page="page" v-model:page-size="size" :data="rows" :columns="columns" :total="total" :loading="loading" row-key="tripNo" empty-text="暂无车次,先建立计划再关联排车单">
|
||||
<template #toolbar-start><el-button v-if="appointmentReturnTarget(route.query)" link @click="router.push(appointmentReturnTarget(route.query)!)">返回预约工作</el-button><h1>车次管理</h1></template>
|
||||
<template #filters><QueryToolbar v-model="filters" :fields="fields" ariaLabel="车次查询" preference-key="fleet-trips-query" :loading="loading" @search="search" @reset="search" /></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="车次加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState></template>
|
||||
|
||||
@@ -39,7 +39,7 @@ function openStatus(row:VehicleDetail){statusRow.value=row;statusTarget.value=ro
|
||||
async function exportCsv(){exporting.value=true;try{const{page:_,size:__,...q}=params();await submitExport('VEHICLE_LIST', '车辆', q)}catch(e){ElMessage.error(masterDataErrorMessage(e,'车辆导出失败'))}finally{exporting.value=false}}
|
||||
watch([page,pageSize],load);onMounted(load)
|
||||
</script>
|
||||
<template><AdminPage class="admin-density admin-workbench"><ListPageLayout aria-label="车辆列表"><section class="vehicle-panel"><div class="workbench-table is-page-surface"><DataTable appearance="integrated" :stripe="false" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-vehicles-table" row-key="id" empty-text="暂无车辆">
|
||||
<template><AdminPage class="admin-density admin-workbench"><ListPageLayout aria-label="车辆列表"><section class="vehicle-panel"><div class="workbench-table is-page-surface"><DataTable appearance="integrated" column-settings-mode="show" :results-visible="true" v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="overseas-vehicles-table" row-key="id" empty-text="暂无车辆">
|
||||
<template #toolbar-start><div class="status-tabs density-status-tabs workbench-list-tabs" role="tablist" aria-label="车辆归属"><button v-for="tab in tabs" :key="tab[1]" role="tab" :aria-selected="ownership===tab[1]" :class="['status-tab',{'is-active':ownership===tab[1]}]" @click="selectOwnership(tab[1])"><span class="density-tab-icon" aria-hidden="true">{{ tab[1] === 'OWNED' ? '🚚' : '🚛' }}</span>{{tab[0]}}</button></div></template>
|
||||
<template #filters><div class="query-panel integrated-query-group"><QueryToolbar v-model="filters" :fields="fields" ariaLabel="车辆筛选" preference-key="overseas-vehicles-query" :loading="loading" @search="search"/></div></template>
|
||||
<template #notices><LoadErrorState v-if="error" title="车辆加载失败" :message="error.message" @retry="load"/></template>
|
||||
|
||||
@@ -30,7 +30,7 @@ export const overseasMasterDataRoutes = [
|
||||
path: 'overseas-master-data/owned-business',
|
||||
name: 'overseas-master-data-owned-business',
|
||||
component: () => import('./pages/OwnedBusinessView.vue'),
|
||||
meta: { title: '自有业务', description: '查看自有车辆装车后的排车与运营记录', permission: permission.ownedBusinessList, showPageHeading: false },
|
||||
meta: { title: '自有业务', description: '查看未接入拼车货物的已装车排车与运营记录', permission: permission.ownedBusinessList, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-master-data/owned-business/:dispatchNo',
|
||||
@@ -42,13 +42,13 @@ export const overseasMasterDataRoutes = [
|
||||
path: 'overseas-master-data/outsourced-business',
|
||||
name: 'overseas-master-data-outsourced-business',
|
||||
component: () => import('./pages/OutsourcedBusinessView.vue'),
|
||||
meta: { title: '外配业务', description: '按原排车查询外配车辆装车后的履约与承运信息', permission: permission.outsourcedBusinessList, showPageHeading: false },
|
||||
meta: { title: '外配任务', description: '查看已实际接入拼车货物的装车后履约与承运信息', permission: permission.outsourcedBusinessList, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-master-data/outsourced-business/:dispatchNo',
|
||||
name: 'overseas-master-data-outsourced-business-detail',
|
||||
component: () => import('./pages/OutsourcedBusinessDetailView.vue'),
|
||||
meta: { title: '外配业务详情', description: '查看原排车及外配承运登记', permission: permission.outsourcedBusinessList, showPageHeading: false },
|
||||
meta: { title: '外配任务详情', description: '查看原排车及外配承运登记', permission: permission.outsourcedBusinessList, showPageHeading: false },
|
||||
},
|
||||
{
|
||||
path: 'overseas-master-data/trips',
|
||||
|
||||
Reference in New Issue
Block a user