feat(fleet): connect operations quotes costs and handover workspaces

This commit is contained in:
2026-10-03 12:43:25 +08:00
parent 863ad5249c
commit 0b5094dd8f
41 changed files with 2091 additions and 22 deletions
@@ -15,6 +15,8 @@ import TripPlanDialog from '../components/TripPlanDialog.vue'
import TripAssignmentPanel from '../components/TripAssignmentPanel.vue'
import TripLoadingPanel from '../components/TripLoadingPanel.vue'
import TripBookingPanel from '../components/TripBookingPanel.vue'
import TripCostPanel from '../components/TripCostPanel.vue'
import TripHandoverPanel from '../components/TripHandoverPanel.vue'
import { getTrip, bindTripDispatch, unbindTripDispatch, cancelTrip, getTripLoading, updateTripPlan,
getTripCompletion, completeTrip, releaseTripResource, listTripHistory, reviewTripDispatch, reviewTripLoading } from '../api/trips'
import { overseasMasterDataPermissions as permission } from '../permissions'
@@ -47,7 +49,7 @@ const historyColumns: DataTableColumn[] = [{ key: 'occurredAt', label: '操作
{ key: 'vehicle', prop: 'snapshot.trip.vehiclePlate', label: '当时车辆', minWidth: 145 }, { key: 'driver', prop: 'snapshot.trip.driverName', label: '当时司机', minWidth: 125 }]
const actionLabels: Record<string, string> = { CREATE: '新建计划', PLAN: '调整计划', ASSIGN: '分配车辆司机', BIND_DISPATCH: '关联排车', UNBIND_DISPATCH: '解除关联',
BOOKING_CREATE: '登记外接询价', BOOKING_EDIT: '更新外接询价', BOOKING_CONFIRM: '确认外接承运', BOOKING_CANCEL: '取消外接预订', BOOKING_INCOMING: '登记外接来货', BOOKING_RECEIPT: '确认外接实收', PLAN_CAPACITY: '核对计划容量', REMARK: '更新备注', DISPATCH_REVIEW: '调度审核', LOADING_REVIEW: '理货审核',
CONFIRM_LOAD: '确认装载', DEPART: '整车发车', COMPLETE: '货物结案', CANCEL: '取消计划', RELEASE_VEHICLE: '结束车辆任务', RELEASE_DRIVER: '结束司机任务' }
CONFIRM_LOAD: '确认装载', DEPART: '整车发车', HANDOVER: '在途交接', COMPLETE: '货物结案', CANCEL: '取消计划', RELEASE_VEHICLE: '结束车辆任务', RELEASE_DRIVER: '结束司机任务' }
function accept(value: TripDetail) {
if (value.trip.status === 'COMPLETED' && detail.value?.trip.status !== 'COMPLETED') actualEndAt.value = new Date().toISOString()
detail.value = value; operationError.value = null; endLocation.value ||= value.trip.stops.at(-1)?.name || ''
@@ -182,7 +184,7 @@ onBeforeUnmount(() => { generation++; inspectionSequence++; historySequence++ })
<FormSection class="density-form-section" title="车次概况"><dl class="density-fields"><div v-for="[label, value] in [['起运仓',detail.trip.warehouseName],['车辆',detail.trip.vehiclePlate],['司机',detail.trip.driverName],['联系电话',detail.trip.driverPhone],['计划发车',time(detail.trip.plannedDepartureAt)],['司机预计结束',time(detail.trip.expectedDriverEndAt)],['实际发车',time(detail.trip.departedAt)],['货物结案',time(detail.trip.completedAt)]]" :key="label || ''"><dt>{{ label }}</dt><dd>{{ value || '—' }}</dd></div></dl>
<p>{{ detail.trip.stops.map(stop => stop.name).join(' → ') }}</p><p v-if="detail.driverQualification">{{ driverQualificationLabels[detail.driverQualification.reason] }}<span v-if="detail.driverQualification.expiringSoon"> · 证照将在 30 天内到期,请留意任务延期</span></p>
</FormSection>
<el-tabs v-model="activeTab" class="density-element-tabs"><el-tab-pane name="overview"><template #label><span>🚚 分配与货物</span></template></el-tab-pane><el-tab-pane name="bookings"><template #label><span>📦 外接预订</span></template></el-tab-pane><el-tab-pane name="capacity"><template #label><span>📋 计划容量</span></template></el-tab-pane><el-tab-pane name="loading"><template #label><span>📦 实际装载</span></template></el-tab-pane><el-tab-pane name="completion"><template #label><span>✅ 结案与任务结束</span></template></el-tab-pane><el-tab-pane name="history"><template #label><span>🕘 操作历史</span></template></el-tab-pane></el-tabs>
<el-tabs v-model="activeTab" class="density-element-tabs"><el-tab-pane name="overview"><template #label><span>🚚 分配与货物</span></template></el-tab-pane><el-tab-pane name="bookings"><template #label><span>📦 外接预订</span></template></el-tab-pane><el-tab-pane name="capacity"><template #label><span>📋 计划容量</span></template></el-tab-pane><el-tab-pane name="loading"><template #label><span>📦 实际装载</span></template></el-tab-pane><el-tab-pane name="completion"><template #label><span>✅ 结案与任务结束</span></template></el-tab-pane><el-tab-pane name="handovers"><template #label><span>在途交接</span></template></el-tab-pane><el-tab-pane name="costs"><template #label><span>直接成本</span></template></el-tab-pane><el-tab-pane name="history"><template #label><span>🕘 操作历史</span></template></el-tab-pane></el-tabs>
<template v-if="activeTab === 'overview'">
<FormSection v-if="planned && access.can(permission.tripAssign)" class="density-form-section" title="车辆与司机"><TripAssignmentPanel :key="detail.trip.tripNo" :detail="detail" @saved="accept" /></FormSection>
<FormSection class="density-form-section" title="本车排车单"><div v-if="editable" class="trip-inline"><el-input v-model="dispatchNo" placeholder="输入待关联的排车单号" :disabled="!!busy" @keyup.enter="bind" /><el-button type="primary" :loading="busy === 'bind'" :disabled="!!busy || !detail.trip.vehicleId" @click="bind">关联排车单</el-button><el-button :loading="busy === 'stops'" :disabled="!!busy || !detail.dispatches.length" @click="matchStops">按货物匹配卸货站</el-button></div>
@@ -191,6 +193,8 @@ onBeforeUnmount(() => { generation++; inspectionSequence++; historySequence++ })
</FormSection>
</template>
<FormSection v-if="activeTab === 'bookings'" class="density-form-section" title="本车外接预订"><TripBookingPanel :key="detail.trip.tripNo" :trip="detail.trip" @changed="detail.trip.recordVersion = $event" @linked="refreshLinked" /></FormSection>
<FormSection v-if="activeTab === 'handovers'" class="density-form-section" title="在途车辆与司机交接"><TripHandoverPanel :key="detail.trip.tripNo" :detail="detail" @saved="refreshLinked" /></FormSection>
<FormSection v-if="activeTab === 'costs'" class="density-form-section" title="本趟直接成本"><TripCostPanel :key="detail.trip.tripNo" :trip-no="detail.trip.tripNo" /></FormSection>
<FormSection v-if="activeTab === 'capacity'" class="density-form-section" title="核对本车计划载荷"><TripLoadingPanel :key="`${detail.trip.tripNo}-capacity`" :trip="detail.trip" :editable="editable" planning @planned="capacityPlanned" /></FormSection>
<FormSection v-if="activeTab === 'loading'" class="density-form-section" title="按实物托盘确认装载"><TripLoadingPanel :key="detail.trip.tripNo" :trip="detail.trip" :editable="editable" @saved="loaded" /><div class="trip-review"><p>理货审核:{{ tripReviewLabels[detail.loadingReview.status] }}<span v-if="detail.loadingReview.reason"> · {{ detail.loadingReview.reason }}</span><span v-if="detail.loadingReview.reviewedAt"> · {{ time(detail.loadingReview.reviewedAt) }}</span></p><div v-if="planned && access.can(permission.tripLoadingReview)" class="density-actions"><el-button type="primary" :disabled="!!busy || detail.loadingReview.status === 'APPROVED'" @click="review('APPROVED')">理货审核通过</el-button><el-button type="danger" plain :disabled="!!busy" @click="review('REJECTED')">理货打回</el-button></div></div></FormSection>
<template v-if="activeTab === 'completion'">