feat(web-admin): 出入库列表与编辑弹窗展示取送货及完整产品列
列表增加取送货地址;编辑弹窗主信息与产品八列对齐设计。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8b2ebe28e6
commit
4d88ceb65e
1 file changed
+374
@@ -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>
|
||||
Reference in new issue
Block a user