feat(pl): 运费结算审批分流与车辆挂车号等 PL 能力完善

拆分运费审批/出款页面,补充结算发票附件与菜单 SQL,并同步前后端模型与车辆相关展示。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-06-28 21:00:31 +08:00
1 parent 66d5a8714b
commit 98aec80d71
37 files changed
+1362 -195

No files matched your search

@@ -2,7 +2,7 @@
<el-dialog
v-model="visible"
title="财务发放登记"
width="640px"
width="1200px"
align-center
append-to-body
destroy-on-close
@@ -10,60 +10,156 @@
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
>
<el-form
ref="formRef"
v-loading="loading"
:model="form"
:rules="rules"
label-position="top"
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
>
<div class="form-grid-2">
<el-form-item label="结算单号">
<el-input :model-value="settlement?.settlementNo" disabled />
</el-form-item>
<el-form-item label="运费合计(元)">
<el-input :model-value="formatAmount(settlement?.totalFreight)" disabled />
</el-form-item>
<el-form-item label="汇款日期" prop="remittanceDate">
<el-date-picker
v-model="form.remittanceDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择汇款日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="汇款金额(元)" prop="remittanceAmount">
<el-input-number
v-model="form.remittanceAmount"
:min="0.01"
:precision="2"
:controls="false"
placeholder="请输入汇款金额"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="汇款凭证" prop="voucherUrls" class="form-col-span-2">
<div v-loading="loading" class="disburse-dialog-body">
<section v-if="settlement" class="dialog-section">
<h3 class="dialog-section-title">结算摘要</h3>
<div class="summary-grid">
<div class="summary-item">
<span class="summary-label">结算单号</span>
<span class="summary-value">{{ settlement.settlementNo }}</span>
</div>
<div class="summary-item">
<span class="summary-label">运费合计(元)</span>
<span class="summary-value summary-value--amount">{{
formatAmount(settlement.totalFreight)
}}</span>
</div>
</div>
</section>
<section v-if="settlement" class="dialog-section">
<h3 class="dialog-section-title">发票信息</h3>
<div class="info-kv-grid info-kv-grid--cols-3">
<div class="info-kv-item">
<span class="info-kv-label">发票号</span>
<span class="info-kv-value">{{ settlement.invoiceNo || '—' }}</span>
</div>
<div class="info-kv-item">
<span class="info-kv-label">开票日期</span>
<span class="info-kv-value">{{ settlement.invoiceDate || '—' }}</span>
</div>
<div class="info-kv-item">
<span class="info-kv-label">发票金额(元)</span>
<span class="info-kv-value">{{
settlement.invoiceAmount ? formatAmount(settlement.invoiceAmount) : '—'
}}</span>
</div>
</div>
<div v-if="settlement.invoiceAttachmentUrls" class="info-kv-item info-kv-item--full">
<span class="info-kv-label">发票附件</span>
<AttachmentUploadField
v-model="voucherUrlsStr"
folder="pl/freight-voucher"
hint="请上传汇款凭证,至少 1 个附件"
:model-value="settlement.invoiceAttachmentUrls"
disabled
/>
</el-form-item>
<el-form-item label="备注" prop="remark" class="form-col-span-2">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
maxlength="500"
show-word-limit
/>
</el-form-item>
</div>
</el-form>
</div>
</section>
<section class="dialog-section">
<h3 class="dialog-section-title">发放登记</h3>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
>
<div class="form-grid-2">
<el-form-item label="汇款日期" prop="remittanceDate">
<el-date-picker
v-model="form.remittanceDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择汇款日期"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="汇款金额(元)" prop="remittanceAmount">
<el-input-number
v-model="form.remittanceAmount"
:min="0.01"
:precision="2"
:controls="false"
placeholder="请输入汇款金额"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="汇款凭证附件" prop="voucherUrls" class="form-col-span-2">
<AttachmentUploadField
ref="attachmentRef"
v-model="form.voucherUrls"
folder="pl/freight-voucher"
hint="请上传银行回单、转账截图等汇款凭证,至少 1 个附件"
@update:model-value="formRef?.validateField('voucherUrls')"
/>
</el-form-item>
<el-form-item label="备注" prop="remark" class="form-col-span-2">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="汇款金额与运费合计不一致时请填写说明"
maxlength="500"
show-word-limit
/>
</el-form-item>
</div>
</el-form>
</section>
<section v-if="settlement" class="dialog-section">
<h3 class="dialog-section-title">关联运输信息</h3>
<div class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
:data="settlement.transports || []"
class="list-table"
stripe
empty-text="暂无数据"
>
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column
prop="transportNo"
label="运输编号"
min-width="160"
show-overflow-tooltip
/>
<el-table-column prop="licensePlate" label="车牌号" min-width="110" align="center">
<template #default="{ row }">{{ row.licensePlate || '—' }}</template>
</el-table-column>
<el-table-column prop="driverName" label="司机" min-width="90" align="center">
<template #default="{ row }">{{ row.driverName || '—' }}</template>
</el-table-column>
<el-table-column
prop="carrierName"
label="承运商"
min-width="120"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.carrierName || '—' }}</template>
</el-table-column>
<el-table-column
prop="transportMedium"
label="运输介质"
min-width="100"
align="center"
>
<template #default="{ row }">{{ row.transportMedium || '—' }}</template>
</el-table-column>
<el-table-column prop="startPoint" label="出发地" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.startPoint || '—' }}</template>
</el-table-column>
<el-table-column prop="endPoint" label="目的地" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.endPoint || '—' }}</template>
</el-table-column>
<el-table-column prop="freightAmount" label="运费(元)" min-width="110" align="right">
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
</el-table-column>
</el-table>
</div>
</div>
</section>
</div>
<template #footer>
<el-button @click="visible = false">
@@ -89,11 +185,12 @@ const loading = ref(false)
const submitLoading = ref(false)
const settlement = ref<FreightSettlement | null>(null)
const formRef = ref<ElFormInstance>()
const voucherUrlsStr = ref('')
const attachmentRef = ref<InstanceType<typeof AttachmentUploadField>>()
const form = reactive({
remittanceDate: '',
remittanceAmount: undefined as number | undefined,
voucherUrls: '',
remark: ''
})
@@ -102,12 +199,12 @@ const rules = {
remittanceAmount: [{ required: true, message: '请输入汇款金额', trigger: 'blur' }],
voucherUrls: [
{
validator: (_rule: unknown, _value: unknown, callback: (err?: Error) => void) => {
const urls = voucherUrlsStr.value
validator: (_rule: unknown, value: string, callback: (err?: Error) => void) => {
const urls = String(value || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean)
if (!urls.length) callback(new Error('请上传至少一个汇款凭证'))
if (!urls.length) callback(new Error('请上传至少一个汇款凭证附件'))
else callback()
},
trigger: 'change'
@@ -120,13 +217,18 @@ function formatAmount(amount?: number) {
return formatMoneyDisplay(amount)
}
function resetForm() {
form.remittanceDate = ''
form.remittanceAmount = undefined
form.voucherUrls = ''
form.remark = ''
}
async function open(id: string) {
visible.value = true
loading.value = true
form.remittanceDate = ''
form.remittanceAmount = undefined
form.remark = ''
voucherUrlsStr.value = ''
resetForm()
settlement.value = null
try {
const res = await freightApi.get(id)
settlement.value = (res.data ?? res) as FreightSettlement
@@ -138,8 +240,12 @@ async function open(id: string) {
async function handleSubmit() {
if (!settlement.value) return
if (attachmentRef.value?.hasUploading?.()) {
ElMessage.warning('附件上传中,请稍候')
return
}
await formRef.value?.validate()
const voucherUrls = voucherUrlsStr.value
const voucherUrls = form.voucherUrls
.split(',')
.map((s) => s.trim())
.filter(Boolean)
@@ -161,3 +267,103 @@ async function handleSubmit() {
defineExpose({ open })
</script>
<style scoped lang="scss">
.disburse-dialog-body {
min-height: 120px;
}
.dialog-section {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
.dialog-section-title {
margin: 0 0 12px;
font-size: 14px;
font-weight: 600;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px 24px;
padding: 16px;
border-radius: 10px;
background: var(--primary-bg, #ecfdf5);
border: 1px solid #a7f3d0;
}
.summary-item {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.summary-label {
font-size: 12px;
color: var(--text-muted, #64748b);
}
.summary-value {
font-size: 14px;
color: var(--text, #1e293b);
word-break: break-all;
&--amount {
font-size: 18px;
font-weight: 700;
color: var(--primary-dark, #059669);
}
}
.info-kv-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px 24px;
&--cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.info-kv-item {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
&--full {
margin-top: 12px;
}
}
.info-kv-label {
font-size: 12px;
color: var(--text-muted, #64748b);
}
.info-kv-value {
font-size: 14px;
color: var(--text, #1e293b);
word-break: break-all;
}
@media (max-width: 900px) {
.info-kv-grid--cols-3 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.summary-grid,
.info-kv-grid,
.info-kv-grid--cols-3 {
grid-template-columns: 1fr;
}
}
</style>
@@ -1,8 +1,8 @@
<template>
<el-dialog
v-model="visible"
title="编辑结算单"
width="720px"
:title="dialogTitle"
width="1200px"
align-center
append-to-body
destroy-on-close
@@ -26,6 +26,8 @@
<el-form-item label="运费合计(元)">
<el-input :model-value="formatAmount(settlement?.totalFreight)" disabled />
</el-form-item>
</div>
<div class="form-grid-3">
<el-form-item label="发票号" prop="invoiceNo">
<el-input v-model="form.invoiceNo" placeholder="请输入发票号" clearable />
</el-form-item>
@@ -48,30 +50,77 @@
style="width: 100%"
/>
</el-form-item>
<el-form-item label="备注" prop="remark" class="form-col-span-2">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
maxlength="500"
show-word-limit
/>
</el-form-item>
</div>
<el-form-item label="发票附件" prop="invoiceAttachmentUrls" class="form-col-span-2">
<AttachmentUploadField
ref="attachmentRef"
v-model="invoiceAttachmentUrlsStr"
folder="pl/freight-invoice"
hint="支持上传发票扫描件或电子发票,可多文件"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-form>
<section class="dialog-section">
<h3 class="dialog-section-title">关联运输明细</h3>
<div class="list-body list-wrap dialog-list-body-only">
<el-table :data="settlement?.transports || []" class="list-table" stripe empty-text="暂无数据">
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column prop="transportNo" label="运输编号" min-width="160" show-overflow-tooltip />
<el-table-column prop="licensePlate" label="车牌号" min-width="120" align="center" />
<el-table-column prop="freightAmount" label="运费(元)" min-width="120" align="right">
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
</el-table-column>
</el-table>
<h3 class="dialog-section-title">关联运输信息</h3>
<div class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
:data="settlement?.transports || []"
class="list-table"
stripe
empty-text="暂无数据"
>
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column
prop="transportNo"
label="运输编号"
min-width="160"
show-overflow-tooltip
/>
<el-table-column prop="licensePlate" label="车牌号" min-width="110" align="center">
<template #default="{ row }">{{ row.licensePlate || '—' }}</template>
</el-table-column>
<el-table-column prop="driverName" label="司机" min-width="90" align="center">
<template #default="{ row }">{{ row.driverName || '—' }}</template>
</el-table-column>
<el-table-column
prop="carrierName"
label="承运商"
min-width="120"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.carrierName || '—' }}</template>
</el-table-column>
<el-table-column
prop="transportMedium"
label="运输介质"
min-width="100"
align="center"
>
<template #default="{ row }">{{ row.transportMedium || '—' }}</template>
</el-table-column>
<el-table-column prop="startPoint" label="出发地" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.startPoint || '—' }}</template>
</el-table-column>
<el-table-column prop="endPoint" label="目的地" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.endPoint || '—' }}</template>
</el-table-column>
<el-table-column prop="freightAmount" label="运费(元)" min-width="110" align="right">
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
</el-table-column>
</el-table>
</div>
</div>
</section>
@@ -89,6 +138,7 @@
<script setup lang="ts">
import { Check, Close } from '@element-plus/icons-vue'
import { freightApi, type FreightSettlement } from '@/api/pl'
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
import { formatMoneyDisplay } from '@/utils/money-format'
const emit = defineEmits<{ success: [] }>()
@@ -98,6 +148,8 @@ const loading = ref(false)
const submitLoading = ref(false)
const settlement = ref<FreightSettlement | null>(null)
const formRef = ref<ElFormInstance>()
const attachmentRef = ref<InstanceType<typeof AttachmentUploadField>>()
const invoiceAttachmentUrlsStr = ref('')
const form = reactive({
invoiceNo: '',
@@ -108,6 +160,11 @@ const form = reactive({
const rules = {}
const dialogTitle = computed(() => {
if (settlement.value?.settlementStatus === 'approved') return '发票信息'
return '编辑结算单'
})
function formatAmount(amount?: number) {
if (!amount) return '¥0.00'
return formatMoneyDisplay(amount)
@@ -116,6 +173,7 @@ function formatAmount(amount?: number) {
async function open(id: string) {
visible.value = true
loading.value = true
invoiceAttachmentUrlsStr.value = ''
try {
const res = await freightApi.get(id)
settlement.value = (res.data ?? res) as FreightSettlement
@@ -123,6 +181,7 @@ async function open(id: string) {
form.invoiceDate = settlement.value.invoiceDate || ''
form.invoiceAmount = settlement.value.invoiceAmount
form.remark = settlement.value.remark || ''
invoiceAttachmentUrlsStr.value = settlement.value.invoiceAttachmentUrls || ''
} finally {
loading.value = false
}
@@ -130,12 +189,17 @@ async function open(id: string) {
async function handleSubmit() {
if (!settlement.value) return
if (attachmentRef.value?.hasUploading?.()) {
ElMessage.warning('附件上传中,请稍候')
return
}
submitLoading.value = true
try {
await freightApi.update(settlement.value.id, {
invoiceNo: form.invoiceNo || undefined,
invoiceDate: form.invoiceDate || undefined,
invoiceAmount: form.invoiceAmount,
invoiceAttachmentUrls: invoiceAttachmentUrlsStr.value || undefined,
remark: form.remark || undefined
})
ElMessage.success('保存成功')
@@ -2,7 +2,7 @@
<el-dialog
v-model="visible"
:title="settlement?.settlementNo || '结算单详情'"
width="calc(100vw - 100px)"
width="1200px"
align-center
append-to-body
destroy-on-close
@@ -25,7 +25,7 @@
<section v-if="settlement" class="view-section">
<h4 class="view-section-title">发票信息</h4>
<div class="view-kv-grid">
<div class="view-kv-grid view-kv-grid--cols-3">
<div class="view-kv-item">
<span class="view-kv-label">发票号</span>
<span class="view-kv-value">{{ settlement.invoiceNo || '—' }}</span>
@@ -40,11 +40,22 @@
settlement.invoiceAmount ? formatAmount(settlement.invoiceAmount) : '—'
}}</span>
</div>
</div>
<div class="view-kv-item view-kv-item--full">
<span class="view-kv-label">发票附件</span>
<AttachmentUploadField
v-if="settlement.invoiceAttachmentUrls"
:model-value="settlement.invoiceAttachmentUrls"
disabled
/>
<span v-else class="view-kv-value">—</span>
</div>
<div class="view-kv-grid view-kv-grid--cols-2 view-kv-grid--mt">
<div class="view-kv-item">
<span class="view-kv-label">创建时间</span>
<span class="view-kv-value">{{ formatDateTime(settlement.createTime) }}</span>
</div>
<div class="view-kv-item view-kv-item--full">
<div class="view-kv-item">
<span class="view-kv-label">备注</span>
<span class="view-kv-value">{{ settlement.remark || '—' }}</span>
</div>
@@ -52,17 +63,55 @@
</section>
<section v-if="settlement" class="view-section">
<h4 class="view-section-title">关联运输明细</h4>
<div class="list-body list-wrap dialog-list-body-only">
<el-table :data="settlement.transports || []" class="list-table" stripe empty-text="暂无数据">
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column prop="transportNo" label="运输编号" min-width="160" show-overflow-tooltip />
<el-table-column prop="licensePlate" label="车牌号" min-width="120" align="center" />
<el-table-column prop="driverName" label="司机" min-width="100" align="center" />
<el-table-column prop="freightAmount" label="运费(元)" min-width="120" align="right">
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
</el-table-column>
</el-table>
<h4 class="view-section-title">关联运输信息</h4>
<div class="panel dialog-list-panel">
<div class="list-body list-wrap dialog-list-body-only">
<el-table
:data="settlement.transports || []"
class="list-table"
stripe
empty-text="暂无数据"
>
<el-table-column type="index" label="序号" min-width="60" align="center" />
<el-table-column
prop="transportNo"
label="运输编号"
min-width="160"
show-overflow-tooltip
/>
<el-table-column prop="licensePlate" label="车牌号" min-width="110" align="center">
<template #default="{ row }">{{ row.licensePlate || '—' }}</template>
</el-table-column>
<el-table-column prop="driverName" label="司机" min-width="90" align="center">
<template #default="{ row }">{{ row.driverName || '—' }}</template>
</el-table-column>
<el-table-column
prop="carrierName"
label="承运商"
min-width="120"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.carrierName || '—' }}</template>
</el-table-column>
<el-table-column
prop="transportMedium"
label="运输介质"
min-width="100"
align="center"
>
<template #default="{ row }">{{ row.transportMedium || '—' }}</template>
</el-table-column>
<el-table-column prop="startPoint" label="出发地" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.startPoint || '—' }}</template>
</el-table-column>
<el-table-column prop="endPoint" label="目的地" min-width="140" show-overflow-tooltip>
<template #default="{ row }">{{ row.endPoint || '—' }}</template>
</el-table-column>
<el-table-column prop="freightAmount" label="运费(元)" min-width="110" align="right">
<template #default="{ row }">{{ formatAmount(row.freightAmount) }}</template>
</el-table-column>
</el-table>
</div>
</div>
</section>
@@ -76,17 +125,42 @@
class="view-section"
>
<h4 class="view-section-title">汇款凭证</h4>
<div class="list-body list-wrap dialog-list-body-only">
<el-table :data="settlement.vouchers" class="list-table" stripe>
<el-table-column prop="remittanceDate" label="汇款日期" min-width="120" align="center" />
<el-table-column prop="remittanceAmount" label="汇款金额" min-width="120" align="right">
<template #default="{ row }">{{ formatAmount(row.remittanceAmount) }}</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
<el-table-column prop="createTime" label="登记时间" min-width="160" align="center">
<template #default="{ row }">{{ formatDateTime(row.createTime) }}</template>
</el-table-column>
</el-table>
<div
v-for="(voucher, index) in settlement.vouchers"
:key="voucher.id || index"
class="voucher-block"
>
<div class="view-kv-grid view-kv-grid--cols-3">
<div class="view-kv-item">
<span class="view-kv-label">汇款日期</span>
<span class="view-kv-value">{{ voucher.remittanceDate || '—' }}</span>
</div>
<div class="view-kv-item">
<span class="view-kv-label">汇款金额</span>
<span class="view-kv-value">{{
voucher.remittanceAmount ? formatAmount(voucher.remittanceAmount) : '—'
}}</span>
</div>
<div class="view-kv-item">
<span class="view-kv-label">登记时间</span>
<span class="view-kv-value">{{
voucher.createTime ? formatDateTime(voucher.createTime) : '—'
}}</span>
</div>
</div>
<div v-if="voucher.remark" class="view-kv-item view-kv-item--full">
<span class="view-kv-label">备注</span>
<span class="view-kv-value">{{ voucher.remark }}</span>
</div>
<div class="view-kv-item view-kv-item--full">
<span class="view-kv-label">汇款凭证附件</span>
<AttachmentUploadField
v-if="normalizeVoucherUrls(voucher.voucherUrls)"
:model-value="normalizeVoucherUrls(voucher.voucherUrls)"
disabled
/>
<span v-else class="view-kv-value">—</span>
</div>
</div>
</section>
</div>
@@ -103,6 +177,8 @@
import { Close } from '@element-plus/icons-vue'
import { freightApi, type FreightSettlement } from '@/api/pl'
import ApprovalFlowView from '@/components/bx/base/ApprovalFlowView.vue'
import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
import { normalizeAttachmentUrls } from '@/utils/download-attachment'
import {
getSettlementStatusLabel,
getSettlementStatusTagType,
@@ -124,6 +200,10 @@ function formatAmount(amount?: number) {
return formatMoneyDisplay(amount)
}
function normalizeVoucherUrls(raw?: string) {
return normalizeAttachmentUrls(raw)
}
async function open(id: string) {
visible.value = true
loading.value = true
@@ -199,16 +279,29 @@ defineExpose({ open })
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px 24px;
&--cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
&--cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
&--mt {
margin-top: 12px;
}
}
.view-kv-item {
display: flex;
flex-direction: column;
gap: 4px;
}
min-width: 0;
.view-kv-item--full {
grid-column: 1 / -1;
&--full {
margin-top: 12px;
}
}
.view-kv-label {
@@ -222,8 +315,27 @@ defineExpose({ open })
word-break: break-all;
}
.voucher-block {
padding: 16px;
border: 1px solid var(--border, #d9e8e2);
border-radius: 10px;
background: #fff;
& + & {
margin-top: 12px;
}
}
@media (max-width: 900px) {
.view-kv-grid--cols-3 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.view-kv-grid {
.view-kv-grid,
.view-kv-grid--cols-3,
.view-kv-grid--cols-2 {
grid-template-columns: 1fr;
}
}
@@ -313,18 +313,20 @@
copyPlanSummary({
pickupAddress: formData.pickupAddress,
destinationAddress: formData.destinationAddress,
pickupNotes: formData.pickupNotes,
destinationNotes: formData.destinationNotes,
licensePlate: vehicleList.find(v => v.id === formData.vehicleId)?.licensePlate,
driverUrl: arrangeResult?.driverUrl
})
"
>
<el-icon><CopyDocument /></el-icon>复制计划简述
<el-icon><CopyDocument /></el-icon>复制给司机
</el-button>
<el-button
v-if="hasSiteInventoryRecords"
@click="copySiteSummary({ licensePlate: vehicleList.find(v => v.id === formData.vehicleId)?.licensePlate })"
>
<el-icon><CopyDocument /></el-icon>复制现场简述
<el-icon><CopyDocument /></el-icon>复制给现场
</el-button>
<el-button
v-if="hasSiteInventoryRecords && siteRecords.length > 1"
@@ -1058,7 +1060,7 @@ async function handleSubmit() {
<style lang="scss">
.el-dialog.pl-plan-arrange-dialog.dialog-form-layout {
width: min(1680px, calc(100vw - 80px)) !important;
width: min(1840px, calc(100vw - 48px)) !important;
max-height: calc(100vh - 48px);
}
</style>
@@ -74,7 +74,7 @@
</div>
<h4 class="pl-plan-subtitle">合同产品预览</h4>
<PlPlanFormProductTable :products="formData.products" />
<PlPlanFormProductTable :products="contractProductRows" />
</template>
</section>
@@ -281,6 +281,7 @@ import PlVehicleViewDialog from '@/components/business/pl-vehicle-view-dialog/in
import PlPlanFormProductTable from './PlPlanFormProductTable.vue'
import {
applyContractToPlanForm,
mapContractProducts,
type PlanFormProductRow
} from '@/composables/usePlContractToPlan'
import { enrichPlanProductsHazardFromCrm } from '@/composables/usePlToxicProductCertCheck'
@@ -464,6 +465,11 @@ const contractBasicFieldRows = computed(() => {
const contractBasicRow1Fields = computed(() => contractBasicFieldRows.value.row1)
const contractBasicRow2Fields = computed(() => contractBasicFieldRows.value.row2)
/** 合同产品预览:固定展示合同原始明细,与下方可编辑的计划产品分离 */
const contractProductRows = computed<PlanFormProductRow[]>(() =>
mapContractProducts(contractSnapshot.value?.products)
)
function resolveSelfCarrierId() {
const matched = carrierList.value.find(item => item.carrierName === SELF_CARRIER_NAME)
formData.carrierId = matched?.id || ''
@@ -65,10 +65,10 @@
<div class="dialog-footer pl-plan-view-footer">
<template v-if="showArrangeActions">
<el-button @click="copyPlanSummary()">
<el-icon><CopyDocument /></el-icon>复制计划简述
<el-icon><CopyDocument /></el-icon>复制给司机
</el-button>
<el-button v-if="hasSiteInventoryRecords" @click="copySiteSummary()">
<el-icon><CopyDocument /></el-icon>复制现场简述
<el-icon><CopyDocument /></el-icon>复制给现场
</el-button>
<el-button
v-if="hasSiteInventoryRecords && siteRecords.length > 1"
@@ -135,7 +135,7 @@
<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>
@@ -374,6 +374,7 @@
buildSiteSummaryFromInventoryLinks
} from '@/utils/db-site-summary'
import {
formatPlanProductListText,
getPlFreightTypeLabel,
getPlPlanCompanyLabel,
getPlPlanTypeLabel,
@@ -492,15 +493,21 @@
ElMessage.warning('暂无现场链接')
return
}
const product = products.value.find(item => item.id === row.link.planProductId)
const productText = product
? formatPlanProductListText(product, unit => dictStore.getLabel('crm_product_unit', unit) || unit, {
usePlannedQuantity: true
})
: row.link.productName || '—'
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 }]
records: [{ ...row.link, siteUrl: url, productText }]
})
await navigator.clipboard.writeText(text)
ElMessage.success('已复制现场简述')
ElMessage.success('已复制给现场')
}
const displayFeedbackTime = computed(() => formatDateTime(transport.value?.feedbackTime) || '—')
@@ -6,6 +6,9 @@
<span class="pl-vehicle-summary-value pl-vehicle-summary-value--strong">
{{ detail.licensePlate || '—' }}
</span>
<span v-if="detail.trailerNo" class="pl-vehicle-summary-value">
{{ detail.trailerNo }}(挂车)
</span>
<el-tag :type="getPlVehicleTypeTagType(detail.vehicleType)" size="small">
{{ getPlVehicleTypeLabel(detail.vehicleType) }}
</el-tag>
@@ -80,6 +80,7 @@ const viewSections = computed<ViewSection[]>(() => {
tableMode: 'quad',
fields: [
{ label: '车牌号', value: d.licensePlate },
{ label: '挂车号', value: d.trailerNo || '—' },
{
label: '车辆类型',
value: getPlVehicleTypeLabel(d.vehicleType),
@@ -67,6 +67,7 @@ const vehicleViewFields = computed<ViewField[]>(() => {
const d = props.detail
return [
{ label: '车牌号', value: d.licensePlate },
{ label: '挂车号', value: d.trailerNo || '—' },
{
label: '车辆类型',
value: getPlVehicleTypeLabel(d.vehicleType),