From d5bbe0d0b1a9313b2f85a842a5650ff4f7df4d40 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Thu, 6 Aug 2026 23:18:04 +0800 Subject: [PATCH] feat(pl): support copy-from plan prefill in plan form dialog Co-authored-by: Cursor --- .../business/pl-plan-form-dialog/index.vue | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/web-admin/src/components/business/pl-plan-form-dialog/index.vue b/web-admin/src/components/business/pl-plan-form-dialog/index.vue index b90eb4b9f..0b750aa3e 100644 --- a/web-admin/src/components/business/pl-plan-form-dialog/index.vue +++ b/web-admin/src/components/business/pl-plan-form-dialog/index.vue @@ -367,6 +367,8 @@ const planSelectableContractStatuses = ['approved', 'executing'] const props = defineProps<{ visible: boolean planId?: string + /** 有值且无 planId 时:新增模式 + 从该计划预填(复制) */ + copyFromPlanId?: string }>() const emit = defineEmits<{ @@ -405,6 +407,7 @@ function todayYmd(): string { } const isEdit = computed(() => !!props.planId) +const isCopy = computed(() => !props.planId && !!props.copyFromPlanId) const dialogTitle = computed(() => (isEdit.value ? '编辑运输计划' : '新增运输计划')) const showSaveAndArrange = computed( () => !isEdit.value || loadedTransportStatus.value === 'pending' @@ -621,6 +624,18 @@ function mapPlanProductToFormRow(item: PlanProduct, index: number): PlanFormProd } } +function mapPlanProductToCopyFormRow(item: PlanProduct, index: number): PlanFormProductRow { + const row = mapPlanProductToFormRow(item, index) + return { + ...row, + id: undefined, + actualQty: 0, + purchaseCertUrl: undefined, + transportCertUrl: undefined, + legalUseCertUrl: undefined + } +} + function mapProductsToSubmitPayload() { return formData.products.map(item => { prepareLineAliasAndBrandForSubmit(item) @@ -810,7 +825,7 @@ function resetForm() { watch( () => formData.planDate, (next, prev) => { - if (isEdit.value) return + if (isEdit.value || isCopy.value) return const nextVal = next || '' const prevVal = prev || '' if (!formData.pickupTime || formData.pickupTime === prevVal) { @@ -826,6 +841,8 @@ function handleOpened() { loadMasterData().then(async () => { if (props.planId) { await loadPlanForEdit(props.planId) + } else if (props.copyFromPlanId) { + await loadPlanForCopy(props.copyFromPlanId) } else { resetForm() } @@ -908,6 +925,81 @@ async function loadPlanForEdit(planId: string) { } } +async function loadPlanForCopy(planId: string) { + detailLoading.value = true + try { + resetForm() + const res = await planApi.get(planId) + const data = (res.data ?? res) as Plan + Object.assign(formData, { + createDate: todayYmd(), + planDate: data.planDate || '', + belongsCompany: data.belongsCompany, + planType: data.planType || '', + followUserId: resolveCurrentUserId(), + followUserName: resolveCurrentUserName(), + contractId: data.contractId || '', + contractNo: data.contractNo || '', + supplierId: data.supplierId || '', + supplierName: data.supplierName || '', + destinationId: data.destinationId || '', + destinationName: data.destinationName || '', + customerId: data.customerId || '', + customerName: data.customerName || '', + contactName: data.contactName || '', + contactPhone: data.contactPhone || '', + partyA: data.partyA || '', + partyB: data.partyB || '', + contractStartDate: data.contractStartDate || '', + contractEndDate: data.contractEndDate || '', + settlementMethod: data.settlementMethod || '', + hasFreight: data.hasFreight ?? 0, + pickupAddress: data.pickupAddress || '', + pickupLng: data.pickupLng != null ? Number(data.pickupLng) : null, + pickupLat: data.pickupLat != null ? Number(data.pickupLat) : null, + pickupTime: formatDate(data.pickupTime) || '', + pickupContact: data.pickupContact || '', + pickupNotes: data.pickupNotes || '', + destinationAddress: data.destinationAddress || '', + destinationLng: data.destinationLng != null ? Number(data.destinationLng) : null, + destinationLat: data.destinationLat != null ? Number(data.destinationLat) : null, + deliveryTime: formatDate(data.deliveryTime) || '', + deliveryContact: data.deliveryContact || '', + destinationNotes: data.destinationNotes || '', + remark: data.remark || '', + products: (data.products || []).map(mapPlanProductToCopyFormRow) + }) + loadedTransportStatus.value = 'pending' + + if (data.contractId) { + try { + const contractRes = await getContract(data.contractId) + contractSnapshot.value = (contractRes.data ?? contractRes) as ContractVO + } catch { + contractSnapshot.value = buildContractSnapshotFromPlan(data) + } + } + formData.products = await enrichPlanProductLinesAlias( + await enrichPlanProductsHazardFromCrm( + enrichProductsFromContract(formData.products, contractSnapshot.value) + ) + ) + await refreshContractProductRows() + + formData.vehicleId = '' + formData.carrierId = '' + selectedDriverId.value = '' + vehicleBundle.value = null + freightCalc.resetFreightCalc() + } catch (e) { + const message = e instanceof Error ? e.message : '加载源计划失败' + ElMessage.error(message.includes('加载') ? message : `加载源计划失败:${message}`) + emit('update:visible', false) + } finally { + detailLoading.value = false + } +} + function buildContractSnapshotFromPlan(plan: Plan): ContractVO { return { id: plan.contractId || '',