Przeglądaj źródła

feat(web-admin): 出入库列表与编辑弹窗展示取送货及完整产品列

列表增加取送货地址;编辑弹窗主信息与产品八列对齐设计。

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 miesięcy temu
rodzic
commit
4d88ceb65e
1 zmienionych plików z 374 dodań i 0 usunięć
  1. 374 0
      web-admin/src/views/pl/_shared/IoOrderListPage.vue

+ 374 - 0
web-admin/src/views/pl/_shared/IoOrderListPage.vue

@@ -0,0 +1,374 @@
+<template>
+  <div class="page-container">
+    <section class="panel">
+      <div class="panel-top">
+        <h2 class="title-bar-heading">{{ title }}</h2>
+      </div>
+
+      <div class="filter-block">
+        <el-form :model="searchForm" class="filter-form" @submit.prevent>
+          <div class="filter-grid">
+            <el-form-item label="单号">
+              <el-input v-model="searchForm.orderNo" clearable placeholder="单号" @keyup.enter="handleSearch" />
+            </el-form-item>
+            <el-form-item label="计划号">
+              <el-input v-model="searchForm.planNo" clearable placeholder="计划号" @keyup.enter="handleSearch" />
+            </el-form-item>
+            <el-form-item label="状态">
+              <el-select v-model="searchForm.status" clearable placeholder="全部">
+                <el-option label="进行中" value="in_progress" />
+                <el-option label="已完成" value="completed" />
+                <el-option label="已作废" value="voided" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="往来单位">
+              <el-input
+                v-model="searchForm.partnerCompany"
+                clearable
+                placeholder="往来单位"
+                @keyup.enter="handleSearch"
+              />
+            </el-form-item>
+          </div>
+          <div class="filter-actions">
+            <div class="filter-actions-end">
+              <el-button class="btn-ghost" :loading="loading" @click="handleReset">
+                <el-icon><RefreshLeft /></el-icon>重置
+              </el-button>
+              <el-button class="btn-main" type="primary" :loading="loading" @click="handleSearch">
+                <el-icon><Search /></el-icon>搜索
+              </el-button>
+            </div>
+          </div>
+        </el-form>
+      </div>
+
+      <div class="list-toolbar">
+        <div class="list-toolbar-row">
+          <div class="list-toolbar-selected">
+            已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
+          </div>
+          <el-button
+            class="btn-main"
+            type="primary"
+            :disabled="selectedRows.length !== 1"
+            @click="handleCopySiteLink"
+          >
+            <el-icon><Link /></el-icon>复制现场链接
+          </el-button>
+          <el-button class="btn-ghost" :disabled="selectedRows.length !== 1" @click="openEdit(selectedRows[0])">
+            <el-icon><Edit /></el-icon>编辑
+          </el-button>
+        </div>
+      </div>
+
+      <div class="list-body list-wrap">
+        <el-table
+          class="list-table"
+          :data="rows"
+          v-loading="loading"
+          stripe
+          row-key="id"
+          @selection-change="(val: PlIoOrder[]) => (selectedRows = val)"
+          @row-dblclick="openEdit"
+        >
+          <el-table-column type="selection" min-width="55" align="center" />
+          <el-table-column
+            type="index"
+            label="序号"
+            min-width="68"
+            align="center"
+            :index="(i: number) => (pagination.page - 1) * pagination.pageSize + i + 1"
+          />
+          <el-table-column prop="orderNo" label="单号" min-width="150" show-overflow-tooltip>
+            <template #default="{ row }">
+              <el-link type="primary" @click="openEdit(row)">{{ row.orderNo }}</el-link>
+            </template>
+          </el-table-column>
+          <el-table-column prop="status" label="状态" min-width="100" align="center">
+            <template #default="{ row }">
+              <el-tag :type="statusTagType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column prop="planNo" label="计划号" min-width="140" show-overflow-tooltip />
+          <el-table-column prop="partnerCompany" label="往来单位" min-width="160" show-overflow-tooltip />
+          <el-table-column prop="vehicleNo" label="车牌" min-width="110" align="center" />
+          <el-table-column
+            prop="pickupAddress"
+            label="取货地址"
+            min-width="160"
+            show-overflow-tooltip
+          />
+          <el-table-column label="送货地址" min-width="160" show-overflow-tooltip>
+            <template #default="{ row }">
+              {{ row.destinationAddress || row.destinationName || '—' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="productSummary" label="产品" min-width="180" show-overflow-tooltip />
+          <el-table-column prop="actualQtyTotal" label="实际数量合计" min-width="120" align="right" />
+          <el-table-column prop="siteFeedbackTime" label="最近提交" min-width="160" show-overflow-tooltip />
+        </el-table>
+
+        <div class="pagination list-pager-bar">
+          <el-pagination
+            v-model:current-page="pagination.page"
+            v-model:page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            @current-change="fetchData"
+            @size-change="fetchData"
+          />
+        </div>
+      </div>
+    </section>
+
+    <el-dialog
+      v-model="dialogVisible"
+      :title="kind === 'inbound' ? '编辑入库单' : '编辑出库单'"
+      width="1080px"
+      align-center
+      destroy-on-close
+      class="dialog-form-layout"
+      :close-on-click-modal="false"
+    >
+      <el-form v-if="editForm" label-position="top" class="form-grid-2">
+        <el-form-item label="单号">
+          <el-input :model-value="editForm.orderNo" disabled />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="editForm.status" :disabled="editForm.status === 'voided'">
+            <el-option label="进行中" value="in_progress" />
+            <el-option label="已完成" value="completed" />
+            <el-option label="已作废" value="voided" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="计划号">
+          <el-input :model-value="editForm.planNo" disabled />
+        </el-form-item>
+        <el-form-item label="车牌">
+          <el-input :model-value="editForm.vehicleNo" disabled />
+        </el-form-item>
+        <el-form-item label="往来单位" class="form-col-span-2">
+          <el-input :model-value="editForm.partnerCompany" disabled />
+        </el-form-item>
+        <el-form-item label="取货地址" class="form-col-span-2">
+          <el-input :model-value="editForm.pickupAddress || '—'" disabled />
+        </el-form-item>
+        <el-form-item label="取货时间">
+          <el-input :model-value="formatDisplayTime(editForm.pickupTime)" disabled />
+        </el-form-item>
+        <el-form-item label="取货注意事项" class="form-col-span-2">
+          <el-input
+            type="textarea"
+            :rows="2"
+            :model-value="editForm.pickupNotes || '—'"
+            disabled
+          />
+        </el-form-item>
+        <el-form-item label="送货地址" class="form-col-span-2">
+          <el-input
+            :model-value="editForm.destinationAddress || editForm.destinationName || '—'"
+            disabled
+          />
+        </el-form-item>
+        <el-form-item label="送达时间">
+          <el-input :model-value="formatDisplayTime(editForm.deliveryTime)" disabled />
+        </el-form-item>
+        <el-form-item label="送货注意事项" class="form-col-span-2">
+          <el-input
+            type="textarea"
+            :rows="2"
+            :model-value="editForm.destinationNotes || '—'"
+            disabled
+          />
+        </el-form-item>
+        <el-form-item label="产品明细" class="form-col-span-2">
+          <el-table :data="editForm.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"
+                  :disabled="editForm.status === 'voided'"
+                />
+              </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="现场备注" class="form-col-span-2">
+          <el-input
+            v-model="editForm.siteFeedbackRemark"
+            type="textarea"
+            :rows="3"
+            maxlength="500"
+            show-word-limit
+            :disabled="editForm.status === 'voided'"
+          />
+        </el-form-item>
+        <el-form-item label="现场附件" class="form-col-span-2">
+          <AttachmentUploadField
+            v-model="editImagesCsv"
+            :disabled="editForm.status === 'voided'"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dialogVisible = false">
+          <el-icon><Close /></el-icon>取消
+        </el-button>
+        <el-button type="primary" :loading="saving" :disabled="editForm?.status === 'voided'" @click="handleSave">
+          <el-icon><Check /></el-icon>确定
+        </el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { onMounted, reactive, ref } from 'vue'
+  import { Check, Close, Edit, Link, RefreshLeft, Search } from '@element-plus/icons-vue'
+  import AttachmentUploadField from '@/components/business/attachment-upload-field/index.vue'
+  import {
+    inboundOrderApi,
+    outboundOrderApi,
+    type PlIoOrder,
+    type PlIoOrderQuery
+  } from '@/api/pl'
+
+  const props = defineProps<{
+    kind: 'inbound' | 'outbound'
+    title: string
+  }>()
+
+  const api = props.kind === 'inbound' ? inboundOrderApi : outboundOrderApi
+
+  const loading = ref(false)
+  const saving = ref(false)
+  const rows = ref<PlIoOrder[]>([])
+  const selectedRows = ref<PlIoOrder[]>([])
+  const dialogVisible = ref(false)
+  const editForm = ref<PlIoOrder | null>(null)
+  const editImagesCsv = ref('')
+  const pagination = reactive({ page: 1, pageSize: 20, total: 0 })
+  const searchForm = reactive<PlIoOrderQuery>({
+    orderNo: '',
+    planNo: '',
+    status: '',
+    partnerCompany: ''
+  })
+
+  function statusLabel(s: string) {
+    const map: Record<string, string> = {
+      in_progress: '进行中',
+      completed: '已完成',
+      voided: '已作废'
+    }
+    return map[s] || s
+  }
+
+  function statusTagType(s: string) {
+    if (s === 'completed') return 'success'
+    if (s === 'voided') return 'info'
+    return 'warning'
+  }
+
+  function formatDisplayTime(value?: string) {
+    if (!value) return '—'
+    const normalized = String(value).replace('T', ' ')
+    return normalized.length > 16 ? normalized.slice(0, 16) : normalized
+  }
+
+  async function fetchData() {
+    loading.value = true
+    try {
+      const res = await api.list({
+        ...searchForm,
+        page: pagination.page,
+        pageSize: pagination.pageSize
+      })
+      rows.value = res.list || []
+      pagination.total = res.total || 0
+    } finally {
+      loading.value = false
+    }
+  }
+
+  function handleSearch() {
+    pagination.page = 1
+    fetchData()
+  }
+
+  function handleReset() {
+    searchForm.orderNo = ''
+    searchForm.planNo = ''
+    searchForm.status = ''
+    searchForm.partnerCompany = ''
+    handleSearch()
+  }
+
+  async function openEdit(row?: PlIoOrder) {
+    if (!row?.id) return
+    const detail = await api.get(row.id)
+    editForm.value = {
+      ...detail,
+      items: (detail.items || []).map(it => ({ ...it }))
+    }
+    editImagesCsv.value = Array.isArray(detail.siteFeedbackImages)
+      ? detail.siteFeedbackImages.filter(Boolean).join(',')
+      : ''
+    dialogVisible.value = true
+  }
+
+  async function handleSave() {
+    if (!editForm.value) return
+    saving.value = true
+    try {
+      const images = editImagesCsv.value
+        ? editImagesCsv.value.split(',').map(s => s.trim()).filter(Boolean)
+        : []
+      await api.patch(editForm.value.id, {
+        status: editForm.value.status,
+        siteFeedbackRemark: editForm.value.siteFeedbackRemark,
+        siteFeedbackImages: images,
+        items: (editForm.value.items || []).map(it => ({
+          id: it.id,
+          actualQty: it.actualQty
+        }))
+      })
+      ElMessage.success('保存成功')
+      dialogVisible.value = false
+      fetchData()
+    } catch (e) {
+      ElMessage.error(e instanceof Error ? e.message : '保存失败')
+    } finally {
+      saving.value = false
+    }
+  }
+
+  async function handleCopySiteLink() {
+    const row = selectedRows.value[0]
+    if (!row) return
+    const data = await api.siteUrl(row.id)
+    const url = data.siteUrl || row.siteUrl
+    if (!url) {
+      ElMessage.warning('暂无现场链接')
+      return
+    }
+    await navigator.clipboard.writeText(url)
+    ElMessage.success('已复制现场链接')
+  }
+
+  onMounted(fetchData)
+</script>