提交
This commit is contained in:
1 parent
c90f72fd34
commit
6b6c80f182
100 files changed
+18631
-6278
No files matched your search
@@ -18,7 +18,20 @@
|
||||
<div v-loading="loading" class="pl-arrange-form-scroll">
|
||||
<section v-if="arrangeSuccess" class="pl-plan-form-section pl-arrange-success">
|
||||
<h3 class="pl-plan-form-section-title">安排成功</h3>
|
||||
<el-alert type="success" :closable="false" show-icon title="运输已安排,可复制司机链接或计划简述" />
|
||||
<el-alert
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
:title="
|
||||
isRearrange
|
||||
? hasSiteInventoryRecords
|
||||
? '运输已调整,司机与现场链接已更新,旧链接已失效'
|
||||
: '运输已调整,司机链接已更新,旧链接已失效'
|
||||
: hasSiteInventoryRecords
|
||||
? '运输已安排,可复制司机链接、现场链接或简述发给相关人员'
|
||||
: '运输已安排,可复制司机链接或计划简述'
|
||||
"
|
||||
/>
|
||||
<div class="pl-arrange-link-row">
|
||||
<span class="pl-arrange-link-label">司机链接</span>
|
||||
<el-input :model-value="driverUrl || arrangeResult?.driverUrl || ''" readonly />
|
||||
@@ -26,11 +39,34 @@
|
||||
<el-icon><CopyDocument /></el-icon>复制链接
|
||||
</el-button>
|
||||
</div>
|
||||
<template v-if="hasSiteInventoryRecords">
|
||||
<div v-if="siteRecords.length === 1" class="pl-arrange-link-row">
|
||||
<span class="pl-arrange-link-label">现场链接</span>
|
||||
<el-input :model-value="siteRecords[0].siteUrl || ''" readonly />
|
||||
<el-button @click="copySiteUrl(siteRecords[0].siteUrl)">
|
||||
<el-icon><CopyDocument /></el-icon>复制链接
|
||||
</el-button>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="record in siteRecords"
|
||||
:key="record.siteLinkId"
|
||||
class="pl-arrange-link-row"
|
||||
>
|
||||
<span class="pl-arrange-link-label">{{ record.productName || '现场链接' }}</span>
|
||||
<el-input :model-value="record.siteUrl || ''" readonly />
|
||||
<el-button @click="copySiteUrl(record.siteUrl)">
|
||||
<el-icon><CopyDocument /></el-icon>复制本条
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<section v-else class="pl-plan-form-section">
|
||||
<h3 class="pl-plan-form-section-title">运输安排</h3>
|
||||
<h3 class="pl-plan-form-section-title">{{ isRearrange ? '调整运输' : '运输安排' }}</h3>
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||
<h4 class="pl-plan-subtitle">车辆信息</h4>
|
||||
<div class="form-grid-4">
|
||||
<el-form-item label="选择车辆" prop="vehicleId">
|
||||
<el-select
|
||||
@@ -75,12 +111,132 @@
|
||||
<el-option v-for="c in carrierList" :key="c.id" :label="c.carrierName" :value="c.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="运输起点" prop="startPoint">
|
||||
<el-input v-model="formData.startPoint" placeholder="请输入运输起点" />
|
||||
</div>
|
||||
|
||||
<PlVehicleSummaryStrip
|
||||
v-if="vehicleBundle"
|
||||
:detail="vehicleBundle.detail"
|
||||
:personnel-list="vehicleBundle.personnelList"
|
||||
:carrier-name="summaryCarrierName"
|
||||
@view="vehicleViewVisible = true"
|
||||
/>
|
||||
|
||||
<div class="pl-plan-logistics-row">
|
||||
<div class="pl-plan-logistics-block">
|
||||
<h4 class="pl-plan-subtitle">取货信息</h4>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="取货地址">
|
||||
<el-input v-model="formData.pickupAddress" placeholder="取货地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="取货时间">
|
||||
<el-date-picker
|
||||
v-model="formData.pickupTime"
|
||||
type="datetime"
|
||||
placeholder="选择取货时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注意事项">
|
||||
<el-input v-model="formData.pickupNotes" placeholder="取货注意事项" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pl-plan-flow-bridge" aria-hidden="true">
|
||||
<span class="pl-plan-flow-bridge__label">运输</span>
|
||||
<span class="pl-plan-flow-bridge__arrow">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path
|
||||
d="M5 12h11.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.75"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M13.5 8.5 18.5 12l-5 3.5"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.75"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="pl-plan-logistics-block">
|
||||
<h4 class="pl-plan-subtitle">送货信息</h4>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="送货地址">
|
||||
<el-input v-model="formData.destinationAddress" placeholder="送货地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="送达时间">
|
||||
<el-date-picker
|
||||
v-model="formData.deliveryTime"
|
||||
type="datetime"
|
||||
placeholder="选择送达时间"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="注意事项">
|
||||
<el-input v-model="formData.destinationNotes" placeholder="送货注意事项" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="pl-plan-subtitle">运费信息</h4>
|
||||
<div v-if="formData.hasFreight !== 1" class="pl-arrange-freight-muted">本计划无运输费用</div>
|
||||
<div v-else class="form-grid-4">
|
||||
<el-form-item label="运输公里数">
|
||||
<el-input-number v-model="formData.distanceKm" :min="0" :precision="2" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="运输终点" prop="endPoint">
|
||||
<el-input v-model="formData.endPoint" placeholder="请输入运输终点" />
|
||||
<el-form-item label="运费计算方式">
|
||||
<el-select v-model="formData.freightType">
|
||||
<el-option label="按重量" value="by_weight" />
|
||||
<el-option label="按趟" value="by_trip" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.freightType === 'by_weight'"
|
||||
:label="`运输量(${freightUnitLabel})`"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="freightCalc.transportQty.value"
|
||||
:min="0"
|
||||
:precision="3"
|
||||
style="width: 100%"
|
||||
@change="freightCalc.markTransportQtyTouched()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="formData.freightType === 'by_weight'"
|
||||
:label="`运输单价(元/${freightUnitLabel})`"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="freightCalc.freightPrice.value"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="运费金额(元)">
|
||||
<el-input-number
|
||||
v-model="freightCalc.freightAmount.value"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
@change="freightCalc.markFreightAmountTouched()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否开票">
|
||||
<el-switch v-model="formData.hasInvoice" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<h4 class="pl-plan-subtitle">安排项</h4>
|
||||
<div class="form-grid-4">
|
||||
<el-form-item label="运输介质">
|
||||
<el-input :model-value="transportMediumText" readonly placeholder="—" />
|
||||
</el-form-item>
|
||||
@@ -89,26 +245,39 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item class="form-col-span-2 pl-arrange-dibang-item">
|
||||
<template #label>
|
||||
是否生成地磅计划
|
||||
<span v-if="plan.planType === 'direct'" class="form-label-tip">直销单不涉及地磅</span>
|
||||
</template>
|
||||
<div class="pl-arrange-dibang-row">
|
||||
<el-tooltip v-if="plan.planType === 'direct'" content="直销单不涉及地磅" placement="top">
|
||||
<el-checkbox v-model="formData.generateDibangPlan" disabled>生成地磅计划与出入库单</el-checkbox>
|
||||
</el-tooltip>
|
||||
<el-checkbox v-else v-model="formData.generateDibangPlan">生成地磅计划与出入库单</el-checkbox>
|
||||
<p v-if="showDibangHint" class="pl-arrange-dibang-hint">
|
||||
<template v-if="isRearrangeWithDibang">
|
||||
将作废本运输单此前待处理的出入库单,并按当前计划产品重新生成;已关联磅单或已完成的单据不会被自动作废,需先在地磅管理中处理。
|
||||
</template>
|
||||
<template v-else>
|
||||
将按当前计划产品生成地磅月计划(当月已有则复用)及待处理出入库单。
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="pl-arrange-dibang-row">
|
||||
<span class="pl-arrange-dibang-label">
|
||||
是否生成地磅单
|
||||
<span v-if="dibangPlanDisabledTip" class="form-label-tip">{{ dibangPlanDisabledTip }}</span>
|
||||
</span>
|
||||
<el-tooltip v-if="dibangPlanDisabledTip" :content="dibangPlanDisabledTip" placement="top">
|
||||
<el-checkbox v-model="formData.generateDibangPlan" disabled>生成地磅单</el-checkbox>
|
||||
</el-tooltip>
|
||||
<el-checkbox v-else v-model="formData.generateDibangPlan">生成地磅单</el-checkbox>
|
||||
<p
|
||||
v-if="planSupportsSiteInventory || showDibangHint || (isRearrange && (hasHistoricalDibang || hasHistoricalSiteLinks))"
|
||||
class="pl-arrange-dibang-hint"
|
||||
>
|
||||
<template v-if="planSupportsSiteInventory && !showDibangHint && !hasHistoricalDibang">
|
||||
入库/出库安排运输时将自动生成工厂现场链接(与是否勾选地磅单无关),不会在地磅出入库管理中产生记录。
|
||||
</template>
|
||||
<template v-else-if="hasHistoricalDibang && formData.generateDibangPlan">
|
||||
将作废本运输单此前待处理的地磅出入库单,并按当前计划产品重新进行地磅单生成;已关联磅单或已完成的单据不会被自动作废,需先在地磅管理中处理。
|
||||
</template>
|
||||
<template v-else-if="hasHistoricalDibang">
|
||||
将作废本运输单此前待处理的地磅出入库单,且不会重新生成地磅联动单据;已关联磅单或已完成的单据不会被自动作废,需先在地磅管理中处理。
|
||||
</template>
|
||||
<template v-else-if="isRearrange && hasHistoricalSiteLinks && planSupportsSiteInventory">
|
||||
调整运输时现场链接将轮换更新,旧现场链接将失效。
|
||||
</template>
|
||||
<template v-else-if="showDibangHint">
|
||||
勾选后将按产品进行地磅单生成(当月已有则复用),并在地磅出入库管理中产生对应记录。
|
||||
</template>
|
||||
</p>
|
||||
<p v-if="planSupportsSiteInventory" class="pl-arrange-dibang-hint">
|
||||
入库/出库计划安排运输时将自动生成工厂现场链接(与是否勾选地磅单无关),不会在地磅出入库管理中产生记录。
|
||||
</p>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
</div>
|
||||
@@ -127,6 +296,12 @@
|
||||
ref="qrcodeDialogRef"
|
||||
/>
|
||||
|
||||
<PlVehicleViewDialog
|
||||
v-model:visible="vehicleViewVisible"
|
||||
:vehicle-id="formData.vehicleId"
|
||||
:bundle="vehicleBundle"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer pl-arrange-footer">
|
||||
<template v-if="arrangeSuccess">
|
||||
@@ -136,8 +311,8 @@
|
||||
<el-button
|
||||
@click="
|
||||
copyPlanSummary({
|
||||
startPoint: formData.startPoint,
|
||||
endPoint: formData.endPoint,
|
||||
pickupAddress: formData.pickupAddress,
|
||||
destinationAddress: formData.destinationAddress,
|
||||
licensePlate: vehicleList.find(v => v.id === formData.vehicleId)?.licensePlate,
|
||||
driverUrl: arrangeResult?.driverUrl
|
||||
})
|
||||
@@ -146,7 +321,19 @@
|
||||
<el-icon><CopyDocument /></el-icon>复制计划简述
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="inventoryRecords.length > 0"
|
||||
v-if="hasSiteInventoryRecords"
|
||||
@click="copySiteSummary({ licensePlate: vehicleList.find(v => v.id === formData.vehicleId)?.licensePlate })"
|
||||
>
|
||||
<el-icon><CopyDocument /></el-icon>复制现场简述
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasSiteInventoryRecords && siteRecords.length > 1"
|
||||
@click="copyAllSiteUrls()"
|
||||
>
|
||||
<el-icon><CopyDocument /></el-icon>复制全部现场链接
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasDibangInventoryRecords"
|
||||
type="primary"
|
||||
@click="copyPoundSlipQrcode()"
|
||||
>
|
||||
@@ -158,7 +345,7 @@
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>确认安排
|
||||
<el-icon><Check /></el-icon>{{ isRearrange ? '确认调整' : '确认安排' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
@@ -179,16 +366,20 @@ import {
|
||||
type Transport,
|
||||
type TransportArrangeResult,
|
||||
type Vehicle,
|
||||
type VehicleDetail,
|
||||
type VehiclePersonnelRow
|
||||
} from '@/api/pl'
|
||||
import DbPoundSlipQrcodeDialog from '@/components/business/db/DbPoundSlipQrcodeDialog.vue'
|
||||
import PlVehicleSummaryStrip from '@/components/business/pl-vehicle-summary-strip/index.vue'
|
||||
import PlVehicleViewDialog from '@/components/business/pl-vehicle-view-dialog/index.vue'
|
||||
import PlPlanViewTabs from '@/components/business/pl-plan-view-tabs/index.vue'
|
||||
import type { PlanFormProductRow } from '@/composables/usePlContractToPlan'
|
||||
import { usePlFreightCalc } from '@/composables/usePlFreightCalc'
|
||||
import { usePlFreightCalc, resolveFreightUnitLabel } from '@/composables/usePlFreightCalc'
|
||||
import { loadVehicleViewBundle, type VehicleViewBundle } from '@/composables/usePlVehicleViewDetail'
|
||||
import { buildMissingHazardCertMessage } from '@/composables/usePlToxicProductCertCheck'
|
||||
import { usePlPlanArrangeActions } from '@/composables/usePlPlanArrangeActions'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { isTonProductUnit } from '@/utils/crm-product-display'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { pickBxData, pickBxList } from '@/utils/http/bx'
|
||||
|
||||
const SELF_CARRIER_NAME = '安庆市山峡化工有限公司'
|
||||
@@ -217,7 +408,11 @@ const arrangeSuccess = ref(false)
|
||||
const arrangeResult = ref<TransportArrangeResult | null>(null)
|
||||
const {
|
||||
driverUrl,
|
||||
siteRecords,
|
||||
inventoryRecords,
|
||||
hasSiteInventoryRecords,
|
||||
hasSiteLinks,
|
||||
hasDibangInventoryRecords,
|
||||
qrcodeVisible,
|
||||
selectedInventoryId,
|
||||
selectedInventoryUnit,
|
||||
@@ -225,6 +420,9 @@ const {
|
||||
resetArrangeActions,
|
||||
applyArrangeResult,
|
||||
copyDriverUrl,
|
||||
copySiteUrl,
|
||||
copyAllSiteUrls,
|
||||
copySiteSummary,
|
||||
copyPlanSummary,
|
||||
copyPoundSlipQrcode
|
||||
} = usePlPlanArrangeActions()
|
||||
@@ -236,13 +434,22 @@ const vehicleList = ref<Vehicle[]>([])
|
||||
const carrierList = ref<Carrier[]>([])
|
||||
const vehicleDriverList = ref<VehiclePersonnelRow[]>([])
|
||||
const prefilledDriverId = ref('')
|
||||
const vehicleBundle = ref<VehicleViewBundle | null>(null)
|
||||
const vehicleViewVisible = ref(false)
|
||||
const hasHistoricalDibang = ref(false)
|
||||
const hasHistoricalSiteLinks = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
vehicleId: '',
|
||||
driverId: '',
|
||||
carrierId: '',
|
||||
startPoint: '',
|
||||
endPoint: '',
|
||||
pickupAddress: '',
|
||||
pickupTime: '',
|
||||
pickupNotes: '',
|
||||
destinationAddress: '',
|
||||
deliveryTime: '',
|
||||
destinationNotes: '',
|
||||
hasFreight: 0,
|
||||
distanceKm: null as number | null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0,
|
||||
@@ -255,9 +462,14 @@ const formRules = {
|
||||
driverId: [{ required: true, message: '请选择司机', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const dialogTitle = computed(() =>
|
||||
plan.value.planNo ? `安排运输 · ${plan.value.planNo}` : '安排运输'
|
||||
)
|
||||
const dialogTitle = computed(() => {
|
||||
const prefix = isRearrange.value ? '调整运输' : '安排运输'
|
||||
return plan.value.planNo ? `${prefix} · ${plan.value.planNo}` : prefix
|
||||
})
|
||||
|
||||
const isRearrange = computed(() => plan.value.transportStatus === 'arranged')
|
||||
|
||||
const freightUnitLabel = computed(() => resolveFreightUnitLabel(productRows.value))
|
||||
|
||||
function getUnitLabel(unit: string) {
|
||||
return dictStore.getLabel('crm_product_unit', unit) || unit
|
||||
@@ -311,13 +523,40 @@ const freightCalc = usePlFreightCalc({
|
||||
const selectedVehicle = computed(() => vehicleList.value.find(item => item.id === formData.vehicleId))
|
||||
const isSelfVehicle = computed(() => selectedVehicle.value?.vehicleType === 'self')
|
||||
|
||||
const summaryCarrierName = computed(() => {
|
||||
if (isSelfVehicle.value) return SELF_CARRIER_NAME
|
||||
const carrier = carrierList.value.find(item => item.id === formData.carrierId)
|
||||
return carrier?.carrierName || vehicleBundle.value?.detail?.carrierName || ''
|
||||
})
|
||||
|
||||
const showDibangHint = computed(
|
||||
() => formData.generateDibangPlan && plan.value.planType && plan.value.planType !== 'direct'
|
||||
() => formData.generateDibangPlan && canGenerateDibangPlan.value
|
||||
)
|
||||
|
||||
const isRearrangeWithDibang = computed(
|
||||
() => showDibangHint.value && Boolean(plan.value.transportId)
|
||||
)
|
||||
const canGenerateDibangPlan = computed(() => {
|
||||
if (plan.value.planType === 'direct') return false
|
||||
if (!products.value.length) return false
|
||||
return products.value.every(item => isTonProductUnit(item.unit))
|
||||
})
|
||||
|
||||
const planSupportsSiteInventory = computed(() => {
|
||||
const planType = plan.value.planType
|
||||
return planType === 'inbound' || planType === 'outbound'
|
||||
})
|
||||
|
||||
const dibangPlanDisabledTip = computed(() => {
|
||||
if (plan.value.planType === 'direct') return '直销单不涉及地磅单'
|
||||
if (products.value.length && !products.value.every(item => isTonProductUnit(item.unit))) {
|
||||
return '产品计量单位非吨时不支持生成地磅单'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
watch(canGenerateDibangPlan, allowed => {
|
||||
if (!allowed) {
|
||||
formData.generateDibangPlan = false
|
||||
}
|
||||
})
|
||||
|
||||
function formatDriverLabel(d: VehiclePersonnelRow) {
|
||||
const phone = d.phoneNo ? ` (${d.phoneNo})` : ''
|
||||
@@ -347,8 +586,13 @@ function applyVehiclePersonnel(personnelList: VehiclePersonnelRow[], preferredDr
|
||||
}
|
||||
|
||||
async function applyPrefillFromPlan(data: Plan) {
|
||||
formData.startPoint = data.pickupAddress || ''
|
||||
formData.endPoint = data.destinationAddress || data.destinationName || ''
|
||||
formData.pickupAddress = data.pickupAddress || ''
|
||||
formData.pickupTime = formatDateTime(data.pickupTime) || ''
|
||||
formData.pickupNotes = data.pickupNotes || ''
|
||||
formData.destinationAddress = data.destinationAddress || ''
|
||||
formData.deliveryTime = formatDateTime(data.deliveryTime) || ''
|
||||
formData.destinationNotes = data.destinationNotes || ''
|
||||
formData.hasFreight = data.hasFreight ?? 0
|
||||
formData.remark = data.remark || ''
|
||||
formData.vehicleId = data.vehicleId || ''
|
||||
prefilledDriverId.value = data.driverId || ''
|
||||
@@ -359,6 +603,23 @@ async function applyPrefillFromPlan(data: Plan) {
|
||||
freightCalc.freightAmountTouched.value = true
|
||||
}
|
||||
|
||||
hasHistoricalDibang.value = false
|
||||
hasHistoricalSiteLinks.value = false
|
||||
if (data.transportStatus === 'arranged' && data.id) {
|
||||
try {
|
||||
const infoRes = await planApi.getArrangeInfo(data.id)
|
||||
const info = pickBxData<{
|
||||
inventoryRecords?: { dbInventoryId: string }[]
|
||||
siteRecords?: { siteLinkId: string }[]
|
||||
}>(infoRes)
|
||||
hasHistoricalDibang.value = (info?.inventoryRecords?.length || 0) > 0
|
||||
hasHistoricalSiteLinks.value = (info?.siteRecords?.length || 0) > 0
|
||||
} catch {
|
||||
hasHistoricalDibang.value = false
|
||||
hasHistoricalSiteLinks.value = false
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.transportId) return
|
||||
|
||||
try {
|
||||
@@ -366,12 +627,10 @@ async function applyPrefillFromPlan(data: Plan) {
|
||||
const transport = pickBxData<Transport>(transportRes)
|
||||
formData.vehicleId = transport.vehicleId || formData.vehicleId
|
||||
formData.carrierId = transport.carrierId || ''
|
||||
formData.startPoint = transport.startPoint || formData.startPoint
|
||||
formData.endPoint = transport.endPoint || formData.endPoint
|
||||
formData.remark = transport.remark || formData.remark
|
||||
formData.distanceKm = transport.distanceKm != null ? Number(transport.distanceKm) : null
|
||||
formData.freightType = transport.freightType || 'by_weight'
|
||||
formData.hasInvoice = transport.hasInvoice ?? 0
|
||||
formData.remark = transport.remark || formData.remark
|
||||
prefilledDriverId.value = transport.driverId || prefilledDriverId.value
|
||||
|
||||
if (transport.transportQty != null) {
|
||||
@@ -398,12 +657,20 @@ function resetState() {
|
||||
products.value = []
|
||||
vehicleDriverList.value = []
|
||||
prefilledDriverId.value = ''
|
||||
vehicleBundle.value = null
|
||||
vehicleViewVisible.value = false
|
||||
hasHistoricalDibang.value = false
|
||||
Object.assign(formData, {
|
||||
vehicleId: '',
|
||||
driverId: '',
|
||||
carrierId: '',
|
||||
startPoint: '',
|
||||
endPoint: '',
|
||||
pickupAddress: '',
|
||||
pickupTime: '',
|
||||
pickupNotes: '',
|
||||
destinationAddress: '',
|
||||
deliveryTime: '',
|
||||
destinationNotes: '',
|
||||
hasFreight: 0,
|
||||
distanceKm: null,
|
||||
freightType: 'by_weight',
|
||||
hasInvoice: 0,
|
||||
@@ -428,6 +695,7 @@ async function handleVehicleChange(vehicleId: string, preferredDriverId?: string
|
||||
formData.driverId = ''
|
||||
formData.carrierId = ''
|
||||
vehicleDriverList.value = []
|
||||
vehicleBundle.value = null
|
||||
return
|
||||
}
|
||||
|
||||
@@ -440,18 +708,13 @@ async function handleVehicleChange(vehicleId: string, preferredDriverId?: string
|
||||
formData.carrierId = vehicle.carrierId
|
||||
}
|
||||
|
||||
if (!formData.startPoint && vehicle?.carrierName) {
|
||||
formData.startPoint = vehicle.carrierName
|
||||
}
|
||||
|
||||
vehicleLoading.value = true
|
||||
try {
|
||||
const res = await vehicleApi.get(vehicleId)
|
||||
const detail = pickBxData<VehicleDetail>(res)
|
||||
if (detail?.vehicleType === 'self') {
|
||||
vehicleBundle.value = await loadVehicleViewBundle(vehicleId)
|
||||
if (vehicleBundle.value.detail?.vehicleType === 'self') {
|
||||
resolveSelfCarrierId()
|
||||
}
|
||||
applyVehiclePersonnel(detail?.personnelList || [], preferredDriverId)
|
||||
applyVehiclePersonnel(vehicleBundle.value.personnelList || [], preferredDriverId)
|
||||
} finally {
|
||||
vehicleLoading.value = false
|
||||
}
|
||||
@@ -501,7 +764,25 @@ async function handleSubmit() {
|
||||
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (formData.generateDibangPlan && plan.value.transportId) {
|
||||
if (isRearrange.value) {
|
||||
let confirmMessage = '司机链接将更新,旧链接将失效。是否继续?'
|
||||
if (hasHistoricalDibang.value) {
|
||||
confirmMessage = formData.generateDibangPlan
|
||||
? '将作废此前自动生成的地磅出入库单,并按当前计划产品重新进行地磅单生成。司机链接将更新,旧链接将失效。已执行或已关联磅单的单据不会被自动作废。是否继续?'
|
||||
: '将作废此前自动生成的地磅出入库单,且不会重新生成地磅联动单据。司机链接将更新,旧链接将失效。已执行或已关联磅单的单据不会被自动作废。是否继续?'
|
||||
} else if (hasHistoricalSiteLinks.value && planSupportsSiteInventory.value) {
|
||||
confirmMessage =
|
||||
'司机与现场链接将更新,旧链接将失效。是否继续?'
|
||||
} else if (formData.generateDibangPlan && planSupportsSiteInventory.value) {
|
||||
confirmMessage =
|
||||
'将按当前计划产品进行地磅单生成。司机链接将更新,旧链接将失效。是否继续?'
|
||||
}
|
||||
await ElMessageBox.confirm(confirmMessage, '调整运输确认', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '继续调整',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
} else if (formData.generateDibangPlan && plan.value.transportId) {
|
||||
await ElMessageBox.confirm(
|
||||
'将作废本运输单此前待处理的出入库单,并按当前计划产品重新生成。已执行或已关联磅单的单据不会被自动作废。是否继续?',
|
||||
'地磅联动确认',
|
||||
@@ -517,15 +798,19 @@ async function handleSubmit() {
|
||||
vehicleId: formData.vehicleId,
|
||||
driverId: formData.driverId,
|
||||
carrierId: formData.carrierId || undefined,
|
||||
startPoint: formData.startPoint || undefined,
|
||||
endPoint: formData.endPoint || undefined,
|
||||
pickupAddress: formData.pickupAddress || undefined,
|
||||
pickupTime: formData.pickupTime || undefined,
|
||||
pickupNotes: formData.pickupNotes || undefined,
|
||||
destinationAddress: formData.destinationAddress || undefined,
|
||||
deliveryTime: formData.deliveryTime || undefined,
|
||||
destinationNotes: formData.destinationNotes || undefined,
|
||||
transportMedium: transportMediumText.value || undefined,
|
||||
distanceKm: formData.distanceKm ?? undefined,
|
||||
freightType: formData.freightType,
|
||||
freightPrice:
|
||||
formData.freightType === 'by_weight' ? freightCalc.freightPrice.value ?? undefined : undefined,
|
||||
freightAmount:
|
||||
plan.value.hasFreight === 1
|
||||
formData.hasFreight === 1
|
||||
? freightCalc.calculatedFreight.value || freightCalc.freightAmount.value || undefined
|
||||
: undefined,
|
||||
hasInvoice: formData.hasInvoice,
|
||||
@@ -538,7 +823,7 @@ async function handleSubmit() {
|
||||
arrangeResult.value = result
|
||||
applyArrangeResult(result, plan.value, products.value)
|
||||
arrangeSuccess.value = true
|
||||
ElMessage.success('运输安排成功')
|
||||
ElMessage.success(isRearrange.value ? '运输调整成功' : '运输安排成功')
|
||||
tabsRef.value?.reload()
|
||||
emit('success')
|
||||
} catch (e: unknown) {
|
||||
@@ -561,16 +846,123 @@ async function handleSubmit() {
|
||||
|
||||
.pl-arrange-form-panel {
|
||||
flex: 0 0 auto;
|
||||
max-height: 42vh;
|
||||
max-height: 52vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pl-arrange-form-scroll {
|
||||
max-height: 42vh;
|
||||
max-height: 52vh;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.pl-plan-subtitle {
|
||||
margin: 16px 0 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.pl-plan-subtitle:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.pl-arrange-freight-muted {
|
||||
margin-bottom: 12px;
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-row {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 0;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.pl-plan-logistics-row .pl-plan-logistics-block {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pl-plan-flow-bridge {
|
||||
flex: 0 0 88px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.pl-plan-flow-bridge__label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--primary-dark, #059669);
|
||||
letter-spacing: 0.06em;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: #ecfdf5;
|
||||
border: 1px solid #a7f3d0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.pl-plan-flow-bridge__arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
color: var(--primary-dark, #059669);
|
||||
background: linear-gradient(145deg, #ecfdf5 0%, #d1fae5 100%);
|
||||
border: 1px solid #a7f3d0;
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.14);
|
||||
|
||||
svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.pl-plan-logistics-block {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
|
||||
.pl-plan-subtitle {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-grid-3 {
|
||||
gap: 8px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.pl-plan-logistics-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pl-plan-flow-bridge {
|
||||
flex: 0 0 auto;
|
||||
flex-direction: row;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.pl-plan-flow-bridge__arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.pl-arrange-view-panel {
|
||||
flex: 1 1 auto;
|
||||
min-height: 280px;
|
||||
@@ -613,7 +1005,16 @@ async function handleSubmit() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.pl-arrange-dibang-label {
|
||||
flex-shrink: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--mint-text, #1e293b);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pl-arrange-dibang-hint {
|
||||
@@ -621,6 +1022,8 @@ async function handleSubmit() {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #64748b;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pl-arrange-remark-item {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="单据类型" prop="planType">
|
||||
<el-select v-model="formData.planType" placeholder="请选择单据类型" clearable style="width: 100%">
|
||||
<el-select v-model="formData.planType" placeholder="请选择单据类型" style="width: 100%">
|
||||
<el-option label="直销单" value="direct" />
|
||||
<el-option label="入库单" value="inbound" />
|
||||
<el-option label="出库单" value="outbound" />
|
||||
@@ -286,9 +286,10 @@ import {
|
||||
import { enrichPlanProductsHazardFromCrm } from '@/composables/usePlToxicProductCertCheck'
|
||||
import { usePlFreightCalc, resolveFreightUnitLabel } from '@/composables/usePlFreightCalc'
|
||||
import { loadVehicleViewBundle, type VehicleViewBundle } from '@/composables/usePlVehicleViewDetail'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
const ContractDetailDialog = defineAsyncComponent(
|
||||
() => import('@/views/crm/contract/components/ContractDetailDialog.vue')
|
||||
@@ -393,7 +394,8 @@ const formData = reactive({
|
||||
|
||||
const basicRules = {
|
||||
planDate: [{ required: true, message: '请选择计划日期', trigger: 'change' }],
|
||||
belongsCompany: [{ required: true, message: '请选择归属公司', trigger: 'change' }]
|
||||
belongsCompany: [{ required: true, message: '请选择归属公司', trigger: 'change' }],
|
||||
planType: [{ required: true, message: '请选择单据类型', trigger: 'change' }]
|
||||
}
|
||||
|
||||
const productsRef = computed(() => formData.products)
|
||||
@@ -728,10 +730,10 @@ async function loadPlanForEdit(planId: string) {
|
||||
settlementMethod: data.settlementMethod || '',
|
||||
hasFreight: data.hasFreight ?? 0,
|
||||
pickupAddress: data.pickupAddress || '',
|
||||
pickupTime: data.pickupTime || '',
|
||||
pickupTime: formatDateTime(data.pickupTime) || '',
|
||||
pickupNotes: data.pickupNotes || '',
|
||||
destinationAddress: data.destinationAddress || '',
|
||||
deliveryTime: data.deliveryTime || '',
|
||||
deliveryTime: formatDateTime(data.deliveryTime) || '',
|
||||
destinationNotes: data.destinationNotes || '',
|
||||
vehicleId: data.vehicleId || '',
|
||||
remark: data.remark || '',
|
||||
@@ -842,8 +844,7 @@ async function handleContractConfirm(contract: ContractVO) {
|
||||
Object.assign(formData, {
|
||||
...mapped,
|
||||
followUserId: resolveCurrentUserId(),
|
||||
followUserName: resolveCurrentUserName(),
|
||||
destinationAddress: contract.partyBAddress || formData.destinationAddress
|
||||
followUserName: resolveCurrentUserName()
|
||||
})
|
||||
formData.products = await enrichPlanProductsHazardFromCrm(
|
||||
enrichProductsFromContract(formData.products, contract)
|
||||
@@ -889,10 +890,6 @@ async function handleVehicleChange(vehicleId: string) {
|
||||
}
|
||||
|
||||
function validateBusinessRules() {
|
||||
if (!formData.planType) {
|
||||
ElMessage.warning('请选择单据类型')
|
||||
return false
|
||||
}
|
||||
if (!formData.contractId) {
|
||||
ElMessage.warning('请选择合同')
|
||||
return false
|
||||
|
||||
@@ -22,6 +22,34 @@
|
||||
<el-icon><CopyDocument /></el-icon>复制链接
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<p v-if="!hasSiteInventoryRecords && planSupportsInventory" class="pl-plan-arrange-summary-hint">
|
||||
暂无现场链接
|
||||
</p>
|
||||
|
||||
<template v-if="hasSiteInventoryRecords">
|
||||
<h4 class="pl-plan-arrange-summary-subtitle">现场链接</h4>
|
||||
<div v-if="siteRecords.length === 1" class="pl-arrange-link-row">
|
||||
<span class="pl-arrange-link-label">现场链接</span>
|
||||
<el-input :model-value="siteRecords[0].siteUrl || ''" readonly />
|
||||
<el-button @click="copySiteUrl(siteRecords[0].siteUrl)">
|
||||
<el-icon><CopyDocument /></el-icon>复制链接
|
||||
</el-button>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="record in siteRecords"
|
||||
:key="record.siteLinkId"
|
||||
class="pl-arrange-link-row"
|
||||
>
|
||||
<span class="pl-arrange-link-label">{{ record.productName || '现场链接' }}</span>
|
||||
<el-input :model-value="record.siteUrl || ''" readonly />
|
||||
<el-button @click="copySiteUrl(record.siteUrl)">
|
||||
<el-icon><CopyDocument /></el-icon>复制本条
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<PlPlanViewTabs v-if="planId" ref="tabsRef" :plan-id="planId" />
|
||||
@@ -39,8 +67,17 @@
|
||||
<el-button @click="copyPlanSummary()">
|
||||
<el-icon><CopyDocument /></el-icon>复制计划简述
|
||||
</el-button>
|
||||
<el-button v-if="hasSiteInventoryRecords" @click="copySiteSummary()">
|
||||
<el-icon><CopyDocument /></el-icon>复制现场简述
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="inventoryRecords.length > 0"
|
||||
v-if="hasSiteInventoryRecords && siteRecords.length > 1"
|
||||
@click="copyAllSiteUrls()"
|
||||
>
|
||||
<el-icon><CopyDocument /></el-icon>复制全部现场链接
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="hasDibangInventoryRecords"
|
||||
type="primary"
|
||||
@click="copyPoundSlipQrcode()"
|
||||
>
|
||||
@@ -81,8 +118,12 @@ const planNo = ref('')
|
||||
|
||||
const {
|
||||
showArrangeActions,
|
||||
hasSiteInventoryRecords,
|
||||
hasDibangInventoryRecords,
|
||||
driverUrl,
|
||||
siteRecords,
|
||||
inventoryRecords,
|
||||
plan,
|
||||
qrcodeVisible,
|
||||
selectedInventoryId,
|
||||
selectedInventoryUnit,
|
||||
@@ -90,10 +131,18 @@ const {
|
||||
resetArrangeActions,
|
||||
loadArrangeActions,
|
||||
copyDriverUrl,
|
||||
copySiteUrl,
|
||||
copyAllSiteUrls,
|
||||
copySiteSummary,
|
||||
copyPlanSummary,
|
||||
copyPoundSlipQrcode
|
||||
} = usePlPlanArrangeActions()
|
||||
|
||||
const planSupportsInventory = computed(() => {
|
||||
const planType = plan.value?.planType
|
||||
return planType === 'inbound' || planType === 'outbound'
|
||||
})
|
||||
|
||||
const dialogTitle = computed(() =>
|
||||
planNo.value ? `查看运输计划 · ${planNo.value}` : '查看运输计划'
|
||||
)
|
||||
@@ -143,15 +192,33 @@ function handleClosed() {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pl-plan-arrange-summary-subtitle {
|
||||
margin: 12px 0 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.pl-arrange-link-row {
|
||||
display: grid;
|
||||
grid-template-columns: 72px 1fr auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
& + & {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.pl-arrange-link-label {
|
||||
font-size: 13px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pl-plan-arrange-summary-hint {
|
||||
margin: 10px 0 0;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
@@ -107,22 +107,63 @@
|
||||
<h4 class="pl-plan-subtitle">运费信息</h4>
|
||||
<ViewKvTable :fields="freightViewFields" mode="quad" />
|
||||
|
||||
<h4 class="pl-plan-subtitle">司机回传信息</h4>
|
||||
<p v-if="!hasDriverFeedback" class="pl-plan-form-empty-hint">暂无司机回传</p>
|
||||
<template v-else>
|
||||
<ViewKvTable :fields="driverFeedbackViewFields" mode="quad" />
|
||||
<div v-if="feedbackImageCsv" class="pl-plan-feedback-attachments">
|
||||
<span class="pl-plan-feedback-attachments__label">回传附件</span>
|
||||
<AttachmentUploadField :model-value="feedbackImageCsv" disabled />
|
||||
</div>
|
||||
</template>
|
||||
<div class="pl-plan-feedback-region">
|
||||
<h4 class="pl-plan-feedback-region__title">司机回传信息</h4>
|
||||
<p v-if="!hasDriverFeedback" class="pl-plan-form-empty-hint">暂无司机回传</p>
|
||||
<template v-else>
|
||||
<ViewKvTable :fields="driverFeedbackViewFields" mode="quad" />
|
||||
<div v-if="feedbackImageCsv" class="pl-plan-feedback-attachments">
|
||||
<span class="pl-plan-feedback-attachments__label">回传附件</span>
|
||||
<AttachmentUploadField :model-value="feedbackImageCsv" disabled />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="pl-plan-feedback-region">
|
||||
<h4 class="pl-plan-feedback-region__title">现场回传信息</h4>
|
||||
<p v-if="!siteInventoryRows.length && !loading" class="pl-plan-form-empty-hint">
|
||||
暂无现场链接
|
||||
</p>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(row, idx) in siteInventoryRows"
|
||||
:key="`site-feedback-${row.link.siteLinkId || idx}`"
|
||||
class="pl-plan-site-feedback-block"
|
||||
>
|
||||
<div class="pl-plan-site-feedback-head">
|
||||
<span class="pl-plan-dibang-product">{{ row.link.productName || '—' }}</span>
|
||||
<div class="pl-plan-dibang-head-actions">
|
||||
<el-button class="btn-op-item" @click="copySiteSummaryForRow(row)">
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
复制现场简述
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="copySiteLinkForRow(row)">
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
复制现场链接
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="hasSiteFeedback(row)">
|
||||
<ViewKvTable :fields="siteFeedbackViewFields(row)" mode="quad" />
|
||||
<div
|
||||
v-if="siteFeedbackImageCsv(row)"
|
||||
class="pl-plan-feedback-attachments pl-plan-site-feedback-attachments"
|
||||
>
|
||||
<span class="pl-plan-feedback-attachments__label">现场附件</span>
|
||||
<AttachmentUploadField :model-value="siteFeedbackImageCsv(row)" disabled />
|
||||
</div>
|
||||
</template>
|
||||
<p v-else class="pl-plan-form-empty-hint pl-plan-site-feedback-empty">暂无现场回传</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<h4 class="pl-plan-subtitle">地磅单信息</h4>
|
||||
<p v-if="!dibangRows.length && !loading" class="pl-plan-form-empty-hint"
|
||||
<p v-if="!dibangPlanRows.length && !loading" class="pl-plan-form-empty-hint"
|
||||
>暂无关联地磅单</p
|
||||
>
|
||||
<div
|
||||
v-for="(row, idx) in dibangRows"
|
||||
v-for="(row, idx) in dibangPlanRows"
|
||||
:key="row.link.dbInventoryId || idx"
|
||||
class="pl-plan-dibang-block"
|
||||
>
|
||||
@@ -130,52 +171,18 @@
|
||||
<span class="pl-plan-dibang-product">{{
|
||||
row.link.productName || row.inventory?.product || '—'
|
||||
}}</span>
|
||||
<el-tag
|
||||
v-if="row.inventory?.status"
|
||||
:type="inventoryStatusTagType(row.inventory.status)"
|
||||
size="small"
|
||||
>
|
||||
{{ row.inventory.status }}
|
||||
</el-tag>
|
||||
<div class="pl-plan-dibang-head-actions">
|
||||
<el-tag
|
||||
v-if="row.inventory?.status"
|
||||
:type="inventoryStatusTagType(row.inventory.status)"
|
||||
size="small"
|
||||
>
|
||||
{{ row.inventory.status }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<dl class="pl-plan-kv-grid pl-plan-kv-grid-2">
|
||||
<div class="pl-plan-kv">
|
||||
<dt>出入库单号</dt>
|
||||
<dd>{{ row.inventory?.batchNo || row.link.batchNo || '—' }}</dd>
|
||||
</div>
|
||||
<div class="pl-plan-kv">
|
||||
<dt>业务类型</dt>
|
||||
<dd>
|
||||
<el-tag
|
||||
v-if="row.inventory?.businessType"
|
||||
:type="businessTypeTagType(row.inventory.businessType)"
|
||||
size="small"
|
||||
>
|
||||
{{ row.inventory.businessType }}
|
||||
</el-tag>
|
||||
<span v-else>—</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div class="pl-plan-kv">
|
||||
<dt>往来单位</dt>
|
||||
<dd>{{ row.inventory?.partnerCompany || '—' }}</dd>
|
||||
</div>
|
||||
<div class="pl-plan-kv">
|
||||
<dt>车牌号</dt>
|
||||
<dd>{{
|
||||
row.inventory?.vehicleNo || plan?.licensePlate || transport?.licensePlate || '—'
|
||||
}}</dd>
|
||||
</div>
|
||||
<div class="pl-plan-kv">
|
||||
<dt>产品</dt>
|
||||
<dd>{{ row.inventory?.product || row.link.productName || '—' }}</dd>
|
||||
</div>
|
||||
<div class="pl-plan-kv">
|
||||
<dt>数量(吨)</dt>
|
||||
<dd>{{ row.inventory?.quantity ?? '—' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<ViewKvTable :fields="dibangBasicViewFields(row)" mode="quad" />
|
||||
|
||||
<template v-if="row.weighing">
|
||||
<h5 class="pl-plan-dibang-weighing-title">地磅单</h5>
|
||||
@@ -232,7 +239,11 @@
|
||||
label="毛重时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
>
|
||||
<template #default="{ row: weighingRow }">
|
||||
{{ formatDateTime(weighingRow.grossWeightTime) || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="tareWeight"
|
||||
label="皮重(kg)"
|
||||
@@ -244,7 +255,11 @@
|
||||
label="皮重时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
>
|
||||
<template #default="{ row: weighingRow }">
|
||||
{{ formatDateTime(weighingRow.tareWeightTime) || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="deduction" label="扣杂(kg)" min-width="100" align="right">
|
||||
<template #default="{ row: weighingRow }">
|
||||
{{ weighingRow.deduction ?? 0 }}
|
||||
@@ -336,7 +351,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { View } from '@element-plus/icons-vue'
|
||||
import { CopyDocument, View } from '@element-plus/icons-vue'
|
||||
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
|
||||
import PlPlanFormProductTable from '@/components/business/pl-plan-form-dialog/PlPlanFormProductTable.vue'
|
||||
import PlVehicleSummaryStrip from '@/components/business/pl-vehicle-summary-strip/index.vue'
|
||||
@@ -348,11 +363,16 @@
|
||||
inventoryStatusTagType
|
||||
} from '@/components/business/db/db-related-view'
|
||||
import type { ContractVO } from '@/api/crm/contract/types'
|
||||
import { inventoryApi } from '@/api/db/inventory'
|
||||
import { mapContractProducts, type PlanFormProductRow } from '@/composables/usePlContractToPlan'
|
||||
import { mapPlanProductToFormRow, usePlPlanViewDetail } from '@/composables/usePlPlanViewDetail'
|
||||
import { mapPlanProductToFormRow, usePlPlanViewDetail, type PlPlanTransportDibangRow, type PlPlanTransportSiteRow } from '@/composables/usePlPlanViewDetail'
|
||||
import { isHazardProduct } from '@/composables/usePlToxicProductCertCheck'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import { formatDateTime } from '@/utils/format'
|
||||
import { pickBxData } from '@/utils/http/bx'
|
||||
import {
|
||||
buildSiteSummaryFromInventoryLinks
|
||||
} from '@/utils/db-site-summary'
|
||||
import {
|
||||
getPlFreightTypeLabel,
|
||||
getPlPlanCompanyLabel,
|
||||
@@ -391,6 +411,7 @@
|
||||
vehicleBundle,
|
||||
contractSnapshot,
|
||||
dibangRows,
|
||||
siteRows,
|
||||
loadDetail,
|
||||
resetDetail
|
||||
} = usePlPlanViewDetail()
|
||||
@@ -408,6 +429,80 @@
|
||||
)
|
||||
)
|
||||
|
||||
function siteFeedbackImageCsv(row: PlPlanTransportSiteRow) {
|
||||
const imgs = row.link.siteFeedbackImages
|
||||
return Array.isArray(imgs) ? imgs.filter(Boolean).join(',') : ''
|
||||
}
|
||||
|
||||
function hasSiteFeedback(row: PlPlanTransportSiteRow) {
|
||||
return Boolean(
|
||||
row.link.siteFeedbackRemark ||
|
||||
siteFeedbackImageCsv(row) ||
|
||||
row.link.siteFeedbackTime
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function siteFeedbackViewFields(row: PlPlanTransportSiteRow): ViewField[] {
|
||||
return [
|
||||
{ label: '保存时间', value: formatDateTime(row.link.siteFeedbackTime) || '—' },
|
||||
{ label: '业务类型', value: row.link.businessType || '—' },
|
||||
{ label: '现场备注', value: row.link.siteFeedbackRemark, span: 'full' }
|
||||
]
|
||||
}
|
||||
|
||||
function dibangBasicViewFields(row: PlPlanTransportDibangRow): ViewField[] {
|
||||
const businessType = row.inventory?.businessType
|
||||
return [
|
||||
{ label: '出入库单号', value: row.inventory?.batchNo || row.link.batchNo },
|
||||
{
|
||||
label: '业务类型',
|
||||
value: businessType,
|
||||
tagType: businessType ? businessTypeTagType(businessType) : undefined
|
||||
},
|
||||
{ label: '往来单位', value: row.inventory?.partnerCompany },
|
||||
{
|
||||
label: '车牌号',
|
||||
value: row.inventory?.vehicleNo || plan.value?.licensePlate || transport.value?.licensePlate
|
||||
},
|
||||
{ label: '产品', value: row.inventory?.product || row.link.productName },
|
||||
{ label: '数量(吨)', value: row.inventory?.quantity }
|
||||
]
|
||||
}
|
||||
|
||||
const dibangPlanRows = computed(() =>
|
||||
dibangRows.value.filter(row => Boolean(row.link.dbPurchasePlanId?.trim()))
|
||||
)
|
||||
|
||||
const siteInventoryRows = computed(() => siteRows.value)
|
||||
|
||||
async function copySiteLinkForRow(row: PlPlanTransportSiteRow) {
|
||||
const url = row.link.siteUrl?.trim()
|
||||
if (!url) {
|
||||
ElMessage.warning('暂无现场链接')
|
||||
return
|
||||
}
|
||||
await navigator.clipboard.writeText(url)
|
||||
ElMessage.success('已复制现场链接')
|
||||
}
|
||||
|
||||
async function copySiteSummaryForRow(row: PlPlanTransportSiteRow) {
|
||||
const url = row.link.siteUrl?.trim()
|
||||
if (!url) {
|
||||
ElMessage.warning('暂无现场链接')
|
||||
return
|
||||
}
|
||||
const text = buildSiteSummaryFromInventoryLinks({
|
||||
planNo: row.link.planNo || plan.value?.planNo,
|
||||
licensePlate: plan.value?.licensePlate || transport.value?.licensePlate,
|
||||
pickupTime: plan.value?.pickupTime,
|
||||
planDate: plan.value?.planDate,
|
||||
records: [{ ...row.link, siteUrl: url }]
|
||||
})
|
||||
await navigator.clipboard.writeText(text)
|
||||
ElMessage.success('已复制现场简述')
|
||||
}
|
||||
|
||||
const displayFeedbackTime = computed(() => formatDateTime(transport.value?.feedbackTime) || '—')
|
||||
|
||||
const displayCarrierName = computed(() => {
|
||||
@@ -474,7 +569,7 @@
|
||||
|
||||
const pickupViewFields = computed<ViewField[]>(() => [
|
||||
{ label: '取货地址', value: plan.value?.pickupAddress, span: 'full' },
|
||||
{ label: '取货时间', value: plan.value?.pickupTime },
|
||||
{ label: '取货时间', value: formatDateTime(plan.value?.pickupTime) || '—' },
|
||||
{ label: '注意事项', value: plan.value?.pickupNotes, span: 'full' }
|
||||
])
|
||||
|
||||
@@ -484,7 +579,7 @@
|
||||
value: plan.value?.destinationAddress || plan.value?.destinationName,
|
||||
span: 'full'
|
||||
},
|
||||
{ label: '送达时间', value: plan.value?.deliveryTime },
|
||||
{ label: '送达时间', value: formatDateTime(plan.value?.deliveryTime) || '—' },
|
||||
{ label: '注意事项', value: plan.value?.destinationNotes, span: 'full' }
|
||||
])
|
||||
|
||||
@@ -812,6 +907,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pl-plan-feedback-region {
|
||||
margin-top: 16px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid #d9e8e2;
|
||||
border-radius: 10px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.pl-plan-feedback-region__title {
|
||||
margin: 0 0 12px;
|
||||
padding-left: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
border-left: 3px solid #10b981;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pl-plan-feedback-attachments {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
@@ -867,13 +980,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pl-plan-site-feedback-block {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.pl-plan-site-feedback-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.pl-plan-site-feedback-empty {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pl-plan-site-feedback-batch {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pl-plan-site-feedback-attachments {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.pl-plan-dibang-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.pl-plan-dibang-head-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pl-plan-dibang-product {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
|
||||
Reference in new issue
Block a user