feat(web-admin): 计划查看出入库回传补齐取送货与产品列

查看 KV、只读子表与快捷编辑弹窗与入库/出库单编辑弹窗字段对齐。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-07-19 16:47:03 +08:00
1 parent 4d88ceb65e
commit 3f23feba59
1 file changed
+233 -75
@@ -171,81 +171,102 @@
</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>
<div v-else class="view-subtable-wrap">
<el-table :data="siteFeedbackTableRows" class="view-subtable list-table" stripe>
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
<h4 class="pl-plan-feedback-region__title">
{{ ioOrder ? (ioOrder.kind === 'inbound' ? '入库单回传' : '出库单回传') : '现场回传信息' }}
</h4>
<!-- 新链路:入库/出库单权威展示 -->
<template v-if="ioOrder">
<ViewKvTable :fields="ioOrderViewFields" mode="quint" />
<div class="view-subtable-wrap" style="margin-top: 12px">
<el-table :data="ioOrder.items || []" class="view-subtable list-table" stripe>
<el-table-column type="index" label="序号" min-width="68" align="center" />
<el-table-column prop="productNo" label="产品编号" min-width="110" show-overflow-tooltip />
<el-table-column prop="productName" label="产品名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="specification" label="型号/规格" min-width="100" show-overflow-tooltip />
<el-table-column label="单位" min-width="72" align="center">
<template #default="{ row }">{{ row.unitLabel || row.unit || '—' }}</template>
</el-table-column>
<el-table-column prop="planQty" label="计划数量" min-width="100" align="right" />
<el-table-column prop="actualQty" label="实际数量" min-width="100" align="right" />
<el-table-column prop="packageSpec" label="包装规格" min-width="110" show-overflow-tooltip />
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
</el-table>
</div>
<div style="margin-top: 12px">
<p class="pl-plan-form-empty-hint" style="margin-bottom: 6px">现场备注</p>
<p class="pl-plan-view-remark">{{ ioOrder.siteFeedbackRemark || '—' }}</p>
</div>
<div style="margin-top: 12px">
<p class="pl-plan-form-empty-hint" style="margin-bottom: 6px">现场附件</p>
<AttachmentUploadField
v-if="ioOrderImageCsv"
:model-value="ioOrderImageCsv"
disabled
compact
/>
<el-table-column
prop="productName"
label="产品"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
prop="siteFeedbackTime"
label="保存时间"
min-width="160"
align="center"
/>
<el-table-column label="业务类型" min-width="100" align="center">
<template #default="{ row }">
<el-tag
v-if="row.businessType && row.businessType !== '—'"
:type="businessTypeTagType(row.businessType)"
size="small"
>
{{ row.businessType }}
</el-tag>
<span v-else class="pl-plan-feedback-table__empty-cell">—</span>
</template>
</el-table-column>
<el-table-column
prop="siteFeedbackRemark"
label="现场备注"
min-width="160"
show-overflow-tooltip
/>
<el-table-column label="现场附件" min-width="140" align="center" header-align="center">
<template #default="{ row }">
<div v-if="row.siteFeedbackImages" class="pl-plan-feedback-table__attachment">
<AttachmentUploadField :model-value="row.siteFeedbackImages" disabled compact />
</div>
<span v-else class="pl-plan-feedback-table__empty-cell">—</span>
</template>
</el-table-column>
<el-table-column
label="操作"
min-width="240"
align="center"
fixed="right"
class-name="col-actions"
>
<template #default="{ row }">
<el-button
v-if="feedbackEditable"
class="btn-op-item"
@click="openSiteFeedbackDialog(row)"
>
<el-icon><Edit /></el-icon>
{{ rowHasSiteFeedback(row) ? '编辑' : '补充' }}
</el-button>
<el-button class="btn-op-item" @click="copySiteSummaryForRow(row.sourceRow)">
<el-icon><CopyDocument /></el-icon>
复制给现场
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<span v-else class="pl-plan-feedback-table__empty-cell">—</span>
</div>
<div style="margin-top: 12px; display: flex; gap: 8px">
<el-button v-if="feedbackEditable && ioOrder.status !== 'voided'" @click="openIoOrderEditDialog">
<el-icon><Edit /></el-icon>
{{ ioOrderHasFeedback ? '编辑' : '补充' }}
</el-button>
<el-button v-if="ioOrder.siteUrl" @click="copyArrangeSiteUrl(ioOrder.siteUrl)">
<el-icon><CopyDocument /></el-icon>
复制现场链接
</el-button>
</div>
</template>
<!-- 旧链路:按产品现场 link -->
<template v-else>
<p v-if="!siteInventoryRows.length && !loading" class="pl-plan-form-empty-hint">
暂无现场链接
</p>
<div v-else class="view-subtable-wrap">
<el-table :data="siteFeedbackTableRows" class="view-subtable list-table" stripe>
<el-table-column type="index" label="序号" min-width="68" align="center" />
<el-table-column prop="productName" label="产品" min-width="140" show-overflow-tooltip />
<el-table-column prop="siteFeedbackTime" label="保存时间" min-width="160" align="center" />
<el-table-column label="业务类型" min-width="100" align="center">
<template #default="{ row }">
<el-tag
v-if="row.businessType && row.businessType !== '—'"
:type="businessTypeTagType(row.businessType)"
size="small"
>
{{ row.businessType }}
</el-tag>
<span v-else class="pl-plan-feedback-table__empty-cell">—</span>
</template>
</el-table-column>
<el-table-column prop="siteFeedbackRemark" label="现场备注" min-width="160" show-overflow-tooltip />
<el-table-column label="现场附件" min-width="140" align="center">
<template #default="{ row }">
<div v-if="row.siteFeedbackImages" class="pl-plan-feedback-table__attachment">
<AttachmentUploadField :model-value="row.siteFeedbackImages" disabled compact />
</div>
<span v-else class="pl-plan-feedback-table__empty-cell">—</span>
</template>
</el-table-column>
<el-table-column label="操作" min-width="240" align="center" fixed="right">
<template #default="{ row }">
<el-button
v-if="feedbackEditable"
class="btn-op-item"
@click="openSiteFeedbackDialog(row)"
>
<el-icon><Edit /></el-icon>
{{ rowHasSiteFeedback(row) ? '编辑' : '补充' }}
</el-button>
<el-button class="btn-op-item" @click="copySiteSummaryForRow(row.sourceRow)">
<el-icon><CopyDocument /></el-icon>
复制给现场
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
</div>
<h4 class="pl-plan-subtitle">地磅单信息</h4>
@@ -455,6 +476,46 @@
:site-feedback-time="siteFeedbackTarget.siteFeedbackTime"
@success="handleFeedbackSaved"
/>
<el-dialog
v-model="ioOrderEditVisible"
:title="ioOrder?.kind === 'inbound' ? '编辑入库单回传' : '编辑出库单回传'"
width="1080px"
align-center
destroy-on-close
class="dialog-form-layout"
:close-on-click-modal="false"
>
<el-form v-if="ioOrderEditForm" label-position="top">
<el-form-item label="产品实际数量">
<el-table :data="ioOrderEditForm.items || []" size="small" border>
<el-table-column prop="productNo" label="产品编号" min-width="110" show-overflow-tooltip />
<el-table-column prop="productName" label="产品名称" min-width="120" show-overflow-tooltip />
<el-table-column prop="specification" label="型号/规格" min-width="100" show-overflow-tooltip />
<el-table-column label="单位" min-width="72" align="center">
<template #default="{ row }">{{ row.unitLabel || row.unit || '—' }}</template>
</el-table-column>
<el-table-column prop="planQty" label="计划数量" min-width="100" align="right" />
<el-table-column label="实际数量" min-width="120">
<template #default="{ row }">
<el-input-number v-model="row.actualQty" :min="0" :precision="3" :controls="false" />
</template>
</el-table-column>
<el-table-column prop="packageSpec" label="包装规格" min-width="110" show-overflow-tooltip />
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
</el-table>
</el-form-item>
<el-form-item label="现场备注">
<el-input v-model="ioOrderEditForm.siteFeedbackRemark" type="textarea" :rows="3" maxlength="500" />
</el-form-item>
<el-form-item label="现场附件">
<AttachmentUploadField v-model="ioOrderEditImagesCsv" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="ioOrderEditVisible = false">取消</el-button>
<el-button type="primary" :loading="ioOrderSaving" @click="saveIoOrderEdit">确定</el-button>
</template>
</el-dialog>
<PlVehicleViewDialog
v-model:visible="vehicleViewVisible"
:vehicle-id="plan?.vehicleId"
@@ -480,7 +541,12 @@
} from '@/components/business/db/db-related-view'
import type { ContractVO } from '@/api/crm/contract/types'
import { inventoryApi } from '@/api/db/inventory'
import type { TransportSiteLink } from '@/api/pl'
import {
inboundOrderApi,
outboundOrderApi,
type PlIoOrder,
type TransportSiteLink
} from '@/api/pl'
import { mapContractProducts, type PlanFormProductRow } from '@/composables/usePlContractToPlan'
import { mapPlanProductToFormRow, usePlPlanViewDetail, type PlPlanTransportDibangRow, type PlPlanTransportSiteRow } from '@/composables/usePlPlanViewDetail'
import { resolveFreightUnitLabel } from '@/composables/usePlFreightCalc'
@@ -551,10 +617,102 @@
contractSnapshot,
dibangRows,
siteRows,
ioOrder,
loadDetail,
resetDetail
} = usePlPlanViewDetail()
const ioOrderEditVisible = ref(false)
const ioOrderSaving = ref(false)
const ioOrderEditForm = ref<PlIoOrder | null>(null)
const ioOrderEditImagesCsv = ref('')
const ioOrderImageCsv = computed(() =>
Array.isArray(ioOrder.value?.siteFeedbackImages)
? ioOrder.value!.siteFeedbackImages!.filter(Boolean).join(',')
: ''
)
const ioOrderHasFeedback = computed(() =>
Boolean(
ioOrder.value?.siteFeedbackRemark ||
(ioOrder.value?.siteFeedbackImages && ioOrder.value.siteFeedbackImages.length) ||
ioOrder.value?.siteFeedbackTime
)
)
const ioOrderViewFields = computed((): ViewField[] => {
if (!ioOrder.value) return []
const statusMap: Record<string, string> = {
in_progress: '进行中',
completed: '已完成',
voided: '已作废'
}
const status = ioOrder.value.status
return [
{ label: '单号', value: ioOrder.value.orderNo },
{
label: '状态',
value: statusMap[status] || status,
tagType:
status === 'completed' ? 'success' : status === 'voided' ? 'info' : 'warning'
},
{ label: '计划号', value: ioOrder.value.planNo },
{ label: '车牌', value: ioOrder.value.vehicleNo },
{ label: '往来单位', value: ioOrder.value.partnerCompany },
{
label: '最近提交',
value: formatDateTime(ioOrder.value.siteFeedbackTime) || '—'
},
{ label: '取货地址', value: ioOrder.value.pickupAddress, span: 'full' },
{ label: '取货时间', value: formatDateTime(ioOrder.value.pickupTime) || '—' },
{ label: '取货注意事项', value: ioOrder.value.pickupNotes, span: 'full' },
{
label: '送货地址',
value: ioOrder.value.destinationAddress || ioOrder.value.destinationName,
span: 'full'
},
{ label: '送达时间', value: formatDateTime(ioOrder.value.deliveryTime) || '—' },
{ label: '送货注意事项', value: ioOrder.value.destinationNotes, span: 'full' }
]
})
function openIoOrderEditDialog() {
if (!ioOrder.value) return
ioOrderEditForm.value = {
...ioOrder.value,
items: (ioOrder.value.items || []).map(it => ({ ...it }))
}
ioOrderEditImagesCsv.value = ioOrderImageCsv.value
ioOrderEditVisible.value = true
}
async function saveIoOrderEdit() {
if (!ioOrderEditForm.value) return
const api = ioOrderEditForm.value.kind === 'inbound' ? inboundOrderApi : outboundOrderApi
ioOrderSaving.value = true
try {
const images = ioOrderEditImagesCsv.value
? ioOrderEditImagesCsv.value.split(',').map(s => s.trim()).filter(Boolean)
: []
await api.patch(ioOrderEditForm.value.id, {
siteFeedbackRemark: ioOrderEditForm.value.siteFeedbackRemark,
siteFeedbackImages: images,
items: (ioOrderEditForm.value.items || []).map(it => ({
id: it.id,
actualQty: it.actualQty
}))
})
ElMessage.success('保存成功')
ioOrderEditVisible.value = false
reload()
} catch (e) {
ElMessage.error(e instanceof Error ? e.message : '保存失败')
} finally {
ioOrderSaving.value = false
}
}
const feedbackImageList = computed(() => parsePlFeedbackImages(transport.value?.feedbackImages))
const feedbackImageCsv = computed(() => feedbackImageList.value.join(','))