提交
This commit is contained in:
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"
|
||||
|
||||
Reference in new issue
Block a user