This commit is contained in:
zlwu committed 2026-09-03 13:50:25 +08:00
1 parent 67d37a1f0e
commit 54a1ba0c5c
21 files changed
+8646 -7577

No files matched your search

@@ -205,11 +205,25 @@
:bundle="vehicleBundle"
/>
<PlPlanFormDialog
v-model:visible="planEditFormVisible"
:plan-id="planId"
plan-edit-only
@success="handlePlanEditSuccess"
/>
<template #footer>
<div class="dialog-footer pl-arrange-footer">
<el-button @click="innerVisible = false">
<el-icon><Close /></el-icon>取消
</el-button>
<el-button
type="danger"
:disabled="loading || !planId || plan.followStatus === 'cancelled'"
@click="handleOpenPlanEdit"
>
<el-icon><Edit /></el-icon>编辑运输计划
</el-button>
<el-button
v-if="!isAdjustMode"
:loading="draftLoading"
@@ -226,7 +240,7 @@
</template>
<script setup lang="ts">
import { Check, Close, Document } from '@element-plus/icons-vue'
import { Check, Close, Document, Edit } from '@element-plus/icons-vue'
import { fetchProductDetail, type ProductDetail } from '@/api/crm/product'
import {
inboundOrderApi,
@@ -244,6 +258,7 @@
import PlPlanArchiveSegmentBlock, {
type ArchiveSegmentMeta
} from '@/components/business/pl-plan-archive-segment-block/index.vue'
import PlPlanFormDialog from '@/components/business/pl-plan-form-dialog/index.vue'
import PlPlanStopsSplitShell from '@/components/business/pl-plan-stops-split-shell/index.vue'
import PlPlanViewTabs from '@/components/business/pl-plan-view-tabs/index.vue'
import PlVehicleSummaryStrip from '@/components/business/pl-vehicle-summary-strip/index.vue'
@@ -292,6 +307,7 @@
const activeTab = ref<'archive' | 'overview'>('archive')
const tabsRef = ref<InstanceType<typeof PlPlanViewTabs> | null>(null)
const vehicleViewVisible = ref(false)
const planEditFormVisible = ref(false)
const plan = ref<Partial<Plan>>({})
const {
@@ -1193,7 +1209,7 @@
}
}
async function handleOpened() {
async function reloadArchiveContent() {
if (!props.planId) return
loading.value = true
resetState()
@@ -1239,14 +1255,44 @@
await applyFreightPrefill(data)
initFeedbackAttachmentEdits()
} catch (error) {
innerVisible.value = false
const message = error instanceof Error ? error.message : '加载运输计划失败'
ElMessage.error(message.includes('加载') ? message : `加载运输计划失败:${message}`)
throw error
} finally {
loading.value = false
}
}
function handleOpenPlanEdit() {
if (!props.planId || plan.value.followStatus === 'cancelled') return
ElMessage.info({
message: '修改计划信息后,归档填写区将自动刷新,请核对装卸货数量与品牌归档。',
duration: 4000
})
planEditFormVisible.value = true
}
async function handlePlanEditSuccess() {
planEditFormVisible.value = false
activeTab.value = 'archive'
try {
await reloadArchiveContent()
tabsRef.value?.reload()
emit('success')
} catch {
// reloadArchiveContent 已提示错误
}
}
async function handleOpened() {
if (!props.planId) return
try {
await reloadArchiveContent()
} catch {
innerVisible.value = false
}
}
function handleClose() {
// destroy-on-close 由 el-dialog 处理
}
@@ -1450,7 +1496,7 @@
.pl-archive-tabs {
:deep(> .el-tabs__header) {
margin-bottom: 16px;
margin-bottom: 0;
}
/* 仅用顶部导航,内容区由下方面板接管 */
@@ -1461,6 +1507,7 @@
.pl-archive-tab-panel {
min-height: 360px;
margin-top: 20px;
}
.pl-arrange-form-scroll {
@@ -160,13 +160,19 @@
<div class="pl-plan-dibang-row">
<el-checkbox
:model-value="delivery.generateDibang === 1"
disabled
:disabled="readonly || !canManualDibang(delivery.customerName)"
@update:model-value="(checked) => handleDibangToggle(index, checked)"
>
生成地磅单({{ directionLabel(delivery.dibangDirection) }})
</el-checkbox>
<span
v-if="isPlInternalCustomerName(delivery.customerName)"
v-if="canManualDibang(delivery.customerName)"
class="pl-plan-dibang-row__hint"
>
该车辆地磅单可选,请自行勾选
</span>
<span
v-else-if="isPlInternalCustomerName(delivery.customerName)"
class="pl-plan-dibang-row__hint"
>
本段客户为内部客户时默认勾选
@@ -232,6 +238,7 @@
import PlPlanStopProductTable from '@/components/business/pl-plan-stop-product-table/index.vue'
import { resolveAddressNotesFill } from '@/utils/pl-address-notes-fill'
import {
canManualToggleStopDibang,
createEmptyDeliveryForm,
resolveStopDibangDefaults,
sumStopProductsPlanQty,
@@ -254,6 +261,8 @@
excludePlanId?: string
/** 装货段产品,用于「送货产品不在装货列表」软提醒 */
loadProducts?: PlanStopProductFormRow[]
/** 当前计划车牌(主牌),用于地磅可选车例外 */
vehiclePlate?: string
}>(),
{
tabMode: true,
@@ -262,7 +271,8 @@
qtyUnitLabel: '',
showSplitHint: false,
excludePlanId: '',
loadProducts: () => []
loadProducts: () => [],
vehiclePlate: ''
}
)
@@ -535,7 +545,7 @@
(item) => String(item.id) === String(customerId)
)
const customerName = customer?.customerName || ''
const defaults = resolveStopDibangDefaults(customerName, 'delivery')
const defaults = resolveStopDibangDefaults(customerName, 'delivery', props.vehiclePlate)
patchDelivery(index, {
customerId: String(customerId),
customerName,
@@ -573,11 +583,16 @@
}
}
function canManualDibang(customerName?: string | null) {
return canManualToggleStopDibang(customerName, props.vehiclePlate)
}
function handleDibangToggle(index: number, checked: boolean) {
const delivery = props.modelValue[index]
// 仅山峡/云栋可生成地磅单,且由客户类型自动决定,不允许手动勾选
if (!isPlInternalCustomerName(delivery?.customerName)) {
patchDelivery(index, { generateDibang: 0, dibangDirection: 'none' })
if (!canManualDibang(delivery?.customerName)) {
if (!isPlInternalCustomerName(delivery?.customerName)) {
patchDelivery(index, { generateDibang: 0, dibangDirection: 'none' })
}
return
}
patchDelivery(index, {
@@ -192,6 +192,7 @@
v-model="loadForm"
compact-products
:exclude-plan-id="planId || ''"
:vehicle-plate="currentVehiclePlate"
@open-customer-picker="openCustomerSelector('load')"
@add-products="openProductSelector('load')"
/>
@@ -233,6 +234,7 @@
:show-split-hint="showDeliverySplitHint"
:exclude-plan-id="planId || ''"
:load-products="loadForm.products"
:vehicle-plate="currentVehiclePlate"
@active-index-change="deliveryActiveIndex = $event"
@open-customer-picker="openCustomerSelector('delivery', $event)"
@sync-products="syncDeliveryProductsFromLoadManual"
@@ -377,6 +379,7 @@
enrichStopProductsDisplayFromPlan,
mapLoadProductsToPlanFormRows,
mergePlanProductsFromLoadMirror,
resolveStopDibangDefaults,
sumStopProductsPlanQty,
syncDeliveriesProductsFromLoad,
type PlanDeliveryForm,
@@ -426,6 +429,8 @@
copyFromPlanId?: string
/** 从列表/详情「安排运输」「调整运输」进入时展开安排区块 */
openForArrange?: boolean
/** 归档等场景:仅保存计划,不展示暂存/安排运输(已安排计划也显示「保存」) */
planEditOnly?: boolean
}>()
const emit = defineEmits<{
@@ -497,14 +502,19 @@
const selectedDriverId = ref('')
/** 已安排:禁止仅「保存」,须走保存并安排/调整运输,避免计划与地磅等联动字段脱节 */
const showSaveButton = computed(() => loadedTransportStatus.value !== 'arranged')
const showSaveButton = computed(() => {
if (props.planEditOnly && isEdit.value) return true
return loadedTransportStatus.value !== 'arranged'
})
const showDraftButton = computed(() => {
if (props.planEditOnly) return false
if (!isEdit.value) return true
return (
loadedFollowStatus.value === 'drafting' || loadedFollowStatus.value === 'pending_arrange'
)
})
const showSaveAndArrangeButton = computed(() => {
if (props.planEditOnly) return false
if (!isEdit.value) return true
if (props.openForArrange && loadedTransportStatus.value === 'arranged') return false
return loadedTransportStatus.value === 'pending' || loadedTransportStatus.value === 'arranged'
@@ -512,7 +522,7 @@
const isRearrangeMode = computed(
() => loadedTransportStatus.value === 'arranged' && Boolean(props.openForArrange)
)
const showRearrangeButton = computed(() => isRearrangeMode.value)
const showRearrangeButton = computed(() => !props.planEditOnly && isRearrangeMode.value)
const saveButtonLabel = computed(() => (isEdit.value ? '保存' : '创建'))
const saveAndArrangeButtonLabel = computed(() => {
if (!isEdit.value) return '创建并安排运输'
@@ -766,6 +776,35 @@
vehicleList.value.find((item) => item.id === formData.vehicleId)
)
const currentVehiclePlate = computed(() => {
const fromList = (selectedVehicle.value?.licensePlate || '').trim()
if (fromList) return fromList
return (vehicleBundle.value?.detail?.licensePlate || '').trim()
})
/** 按当前车牌重算各段地磅默认勾选(换车时调用;编辑回填勿调用以免覆盖已存值) */
function reapplyStopDibangByVehiclePlate(plate?: string | null) {
const vehiclePlate = (plate ?? currentVehiclePlate.value) || ''
const loadDefaults = resolveStopDibangDefaults(
loadForm.value.customerName,
'load',
vehiclePlate
)
loadForm.value = {
...loadForm.value,
generateDibang: loadDefaults.generateDibang,
dibangDirection: loadDefaults.dibangDirection
}
deliveryForms.value = deliveryForms.value.map((row) => {
const defaults = resolveStopDibangDefaults(row.customerName, 'delivery', vehiclePlate)
return {
...row,
generateDibang: defaults.generateDibang,
dibangDirection: defaults.dibangDirection
}
})
}
const summaryCarrierName = computed(() => {
const carrier = carrierList.value.find((item) => item.id === formData.carrierId)
if (carrier?.carrierName) return carrier.carrierName
@@ -1050,9 +1089,10 @@
function applyStopsFromPlan(data: Plan) {
stopFormsReady.value = false
const plate = data.licensePlate || ''
if (data.load || data.deliveries?.length) {
loadForm.value = mapLoadFromApi(data.load)
deliveryForms.value = mapDeliveriesFromApi(data.deliveries)
loadForm.value = mapLoadFromApi(data.load, plate)
deliveryForms.value = mapDeliveriesFromApi(data.deliveries, plate)
} else {
const legacy = mapLegacyPlanToStops(data)
loadForm.value = legacy.load
@@ -1696,7 +1736,7 @@
if (data.vehicleId) {
await ensureSelectedVehicleOption(data.vehicleId)
await handleVehicleChange(data.vehicleId)
await handleVehicleChange(data.vehicleId, { syncStopDibang: false })
}
if (data.driverId) {
selectedDriverId.value = data.driverId
@@ -1762,6 +1802,7 @@
formData.carrierId = ''
selectedDriverId.value = ''
vehicleBundle.value = null
reapplyStopDibangByVehiclePlate('')
freightCalc.resetFreightCalc()
syncSelectedCustomerOptions()
} catch (e) {
@@ -1790,11 +1831,16 @@
selectedDriverId.value = defaultDriver?.personnelId || ''
}
async function handleVehicleChange(vehicleId: string) {
async function handleVehicleChange(
vehicleId: string,
opts?: { syncStopDibang?: boolean }
) {
const syncStopDibang = opts?.syncStopDibang !== false
vehicleBundle.value = null
selectedDriverId.value = ''
if (!vehicleId) {
formData.carrierId = ''
if (syncStopDibang) reapplyStopDibangByVehiclePlate('')
return
}
@@ -1818,6 +1864,12 @@
} finally {
vehicleLoading.value = false
}
if (syncStopDibang) {
const plate =
(vehicle?.licensePlate || '').trim() ||
(vehicleBundle.value?.detail?.licensePlate || '').trim()
reapplyStopDibangByVehiclePlate(plate)
}
}
async function handleVehicleLibrarySelect(vehicle: Vehicle) {
@@ -108,12 +108,15 @@
<div class="pl-plan-dibang-row">
<el-checkbox
:model-value="modelValue.generateDibang === 1"
disabled
:disabled="readonly || !canManualDibang"
@update:model-value="handleDibangToggle"
>
生成地磅单({{ dibangDirectionLabel }})
</el-checkbox>
<span v-if="isInternal" class="pl-plan-dibang-row__hint">本段客户为内部客户时默认勾选</span>
<span v-if="canManualDibang" class="pl-plan-dibang-row__hint">
该车辆地磅单可选,请自行勾选
</span>
<span v-else-if="isInternal" class="pl-plan-dibang-row__hint">本段客户为内部客户时默认勾选</span>
<span v-else-if="modelValue.customerId" class="pl-plan-dibang-row__hint">
仅安庆山峡/安徽云栋可生成地磅单
</span>
@@ -162,7 +165,11 @@
import PlPlanContractMultiSelect from '@/components/business/pl-plan-contract-multi-select/index.vue'
import PlPlanStopProductTable from '@/components/business/pl-plan-stop-product-table/index.vue'
import { resolveAddressNotesFill } from '@/utils/pl-address-notes-fill'
import { resolveStopDibangDefaults, type PlanLoadForm } from '@/utils/pl-plan-stop-form'
import {
canManualToggleStopDibang,
resolveStopDibangDefaults,
type PlanLoadForm
} from '@/utils/pl-plan-stop-form'
import { isPlInternalCustomerName } from '@/utils/pl-internal-customer'
import { pickBxData } from '@/utils/http/bx'
@@ -173,11 +180,14 @@
compactProducts?: boolean
/** 编辑计划时排除本计划占用 */
excludePlanId?: string
/** 当前计划车牌(主牌),用于地磅可选车例外 */
vehiclePlate?: string
}>(),
{
readonly: false,
compactProducts: true,
excludePlanId: ''
excludePlanId: '',
vehiclePlate: ''
}
)
@@ -198,6 +208,9 @@
const customerOptionsLoading = ref(false)
const isInternal = computed(() => isPlInternalCustomerName(props.modelValue.customerName))
const canManualDibang = computed(() =>
canManualToggleStopDibang(props.modelValue.customerName, props.vehiclePlate)
)
const stopContractId = computed(() => {
const id = String(props.modelValue.contracts?.[0]?.contractId || '').trim()
@@ -365,7 +378,7 @@
}
const customer = customerOptions.value.find((item) => String(item.id) === String(customerId))
const customerName = customer?.customerName || ''
const defaults = resolveStopDibangDefaults(customerName, 'load')
const defaults = resolveStopDibangDefaults(customerName, 'load', props.vehiclePlate)
patch({
customerId: String(customerId),
customerName,
@@ -403,9 +416,11 @@
}
function handleDibangToggle(checked: boolean) {
// 仅山峡/云栋可生成地磅单,且由客户类型自动决定,不允许手动勾选
if (!isInternal.value) {
patch({ generateDibang: 0, dibangDirection: 'none' })
// 非可手动场景:外部客户强制不勾;内部非可选车由默认值锁定,忽略手动切换
if (!canManualDibang.value) {
if (!isInternal.value) {
patch({ generateDibang: 0, dibangDirection: 'none' })
}
return
}
patch({
@@ -2,7 +2,7 @@
<el-dialog
v-model="innerVisible"
title="从地址库选择"
width="1200px"
width="1320px"
align-center
destroy-on-close
append-to-body
@@ -74,6 +74,12 @@
align="center"
header-align="center"
/>
<el-table-column
prop="notes"
label="注意事项"
min-width="160"
show-overflow-tooltip
/>
<el-table-column
label="操作"
min-width="160"