Переглянути джерело

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

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

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 місяців тому
батько
коміт
3f23feba59
1 змінених файлів з 233 додано та 75 видалено
  1. 233 75
      web-admin/src/components/business/pl-plan-view-tabs/index.vue

+ 233 - 75
web-admin/src/components/business/pl-plan-view-tabs/index.vue

@@ -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"
-                />
-                <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
+            <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 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(','))