353 lines
16 KiB
Vue
353 lines
16 KiB
Vue
<template>
|
||
<div class="pd-wb">
|
||
<div class="pd-main">
|
||
<!-- 左:审核通过的计划 -->
|
||
<aside class="pd-col pd-plans">
|
||
<div class="pd-col-tool">
|
||
<el-input
|
||
v-model="queryParams.keyword"
|
||
size="small"
|
||
clearable
|
||
placeholder="搜索计划号 / 合同号 / 供货商"
|
||
prefix-icon="el-icon-search"
|
||
@keyup.enter.native="handleQuery"
|
||
@clear="handleQuery"
|
||
/>
|
||
</div>
|
||
<ul class="pd-list" v-loading="loading">
|
||
<li
|
||
v-for="p in planList"
|
||
:key="p.planId"
|
||
class="pd-li"
|
||
:class="{ active: current.planId === p.planId }"
|
||
@click="selectPlan(p)"
|
||
>
|
||
<div class="pd-li-r1">
|
||
<span class="pd-no">{{ p.planNo }}</span>
|
||
<span class="pd-pct" :class="{ done: Number(p.progress) >= 100 }">{{ (Number(p.progress) || 0).toFixed(0) }}%</span>
|
||
</div>
|
||
<div class="pd-li-r2">{{ (p.contractCodes || []).join('、') || '无合同号' }}</div>
|
||
<el-progress :percentage="Number(p.progress) || 0" :stroke-width="4" :show-text="false" :color="progressColor" />
|
||
<div class="pd-li-r3">
|
||
<span>{{ p.supplier || '—' }}</span>
|
||
<span>{{ p.arrivedCount || 0 }}/{{ p.planQty || 0 }} 卷<i v-if="p.planStatus === '1'" class="pd-arch">已齐</i></span>
|
||
</div>
|
||
</li>
|
||
<li v-if="!loading && !planList.length" class="pd-empty">暂无审核通过的计划</li>
|
||
</ul>
|
||
<pagination
|
||
v-show="total > 0"
|
||
:total="total"
|
||
:page.sync="queryParams.pageNum"
|
||
:limit.sync="queryParams.pageSize"
|
||
:pager-count="5"
|
||
layout="prev, pager, next"
|
||
@pagination="getList"
|
||
/>
|
||
</aside>
|
||
|
||
<!-- 右:到货跟踪 -->
|
||
<section class="pd-col pd-detail">
|
||
<div v-if="!current.planId" class="pd-placeholder">
|
||
<i class="el-icon-truck"></i>
|
||
<p>从左侧选择一条审核通过的计划,上传到货表格按卷号比对</p>
|
||
</div>
|
||
|
||
<div v-else>
|
||
<div class="pd-d-head">
|
||
<div>
|
||
<span class="pd-d-title">{{ current.planNo }}</span>
|
||
<span class="pd-badge" :class="current.planStatus === '1' ? 'p1' : 'p0'">{{ current.planStatus === '1' ? '已到齐' : '到货中' }}</span>
|
||
</div>
|
||
<div class="pd-head-act">
|
||
<el-button size="small" icon="el-icon-refresh" :loading="refreshing" @click="doRefreshArrival">刷新到货</el-button>
|
||
<el-upload
|
||
:headers="upload.headers"
|
||
:action="uploadUrl"
|
||
:show-file-list="false"
|
||
accept=".xlsx,.xls"
|
||
:on-success="handleUploadSuccess"
|
||
:on-error="handleUploadError"
|
||
>
|
||
<el-button type="primary" size="small" icon="el-icon-upload2">上传到货表格</el-button>
|
||
</el-upload>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pd-meta">
|
||
<div class="pd-meta-i"><label>合同号</label><span>{{ (current.contractCodes || []).join('、') || '—' }}</span></div>
|
||
<div class="pd-meta-i"><label>供货商</label><span>{{ current.supplier || '—' }}</span></div>
|
||
<div class="pd-meta-i"><label>已到货 / 要求</label><span>{{ current.arrivedCount || 0 }} / {{ current.planQty || 0 }} 卷 · {{ fmt(current.arrivedWeight) }} / {{ fmt(current.planWeight) }} T</span></div>
|
||
<div class="pd-meta-i wide">
|
||
<label>到货百分比(按重量)</label>
|
||
<el-progress
|
||
:percentage="Number(current.progress) || 0"
|
||
:stroke-width="14" :text-inside="true" :color="progressColor"
|
||
:format="p => p.toFixed(1) + '%'"
|
||
/>
|
||
</div>
|
||
<div class="pd-meta-i wide">
|
||
<label>到货百分比(按卷数)</label>
|
||
<el-progress
|
||
:percentage="Number(current.progressQty) || 0"
|
||
:stroke-width="14" :text-inside="true" color="#3a8a4d"
|
||
:format="p => p.toFixed(1) + '%'"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<el-tabs v-model="activeTab" class="pd-tabs">
|
||
<el-tab-pane name="items">
|
||
<span slot="label">采购要求(到货 重量{{ (Number(current.progress) || 0).toFixed(0) }}% / 卷数{{ (Number(current.progressQty) || 0).toFixed(0) }}%)</span>
|
||
<p class="pd-req-tip">采购要求只说明买什么、买多少;到货按上传表格的卷号在钢卷表确认,进度见上方与「到货记录」。</p>
|
||
<el-table :data="current.items" border size="mini" max-height="420">
|
||
<el-table-column label="#" type="index" width="40" align="center" />
|
||
<el-table-column label="产品" prop="productType" min-width="70" show-overflow-tooltip />
|
||
<el-table-column label="材质" prop="material" min-width="78" show-overflow-tooltip />
|
||
<el-table-column label="牌号" prop="grade" min-width="72" />
|
||
<el-table-column label="宽度" prop="width" min-width="68" />
|
||
<el-table-column label="厚度" prop="thickness" min-width="68" />
|
||
<el-table-column label="宽公差" prop="widthTolerance" min-width="70" />
|
||
<el-table-column label="厚公差" prop="thicknessTolerance" min-width="70" />
|
||
<el-table-column label="重量(T)" prop="weight" min-width="76" align="right" />
|
||
<el-table-column label="数量" prop="quantity" min-width="58" align="right" />
|
||
<el-table-column label="供货商" prop="supplier" min-width="88" show-overflow-tooltip />
|
||
<template slot="empty"><span>无采购明细</span></template>
|
||
</el-table>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane name="batches">
|
||
<span slot="label">到货记录({{ batches.length }} 次上传)</span>
|
||
<div class="pd-batch-wrap">
|
||
<el-table
|
||
:data="batches"
|
||
border size="mini" max-height="200" v-loading="batchLoading"
|
||
highlight-current-row @row-click="viewBatch"
|
||
>
|
||
<el-table-column label="上传时间" prop="createTime" width="150" align="center" />
|
||
<el-table-column label="文件" prop="fileName" min-width="160" show-overflow-tooltip />
|
||
<el-table-column label="行数" prop="rowCount" width="64" align="center" />
|
||
<el-table-column label="匹配卷数" prop="matchedCount" width="84" align="center" />
|
||
<el-table-column label="上传后到货%" width="110" align="center">
|
||
<template slot-scope="s">{{ (Number(s.row.arrivedPercent) || 0).toFixed(0) }}%</template>
|
||
</el-table-column>
|
||
<el-table-column label="录入人" prop="createBy" width="90" align="center" />
|
||
<el-table-column label="操作" width="64" align="center">
|
||
<template slot-scope="s"><el-button type="text" size="mini" @click.stop="viewBatch(s.row)">查看</el-button></template>
|
||
</el-table-column>
|
||
<template slot="empty"><span>暂无上传记录,点右上角「上传到货表格」</span></template>
|
||
</el-table>
|
||
|
||
<div class="pd-batch-rows" v-if="currentBatch.batchId">
|
||
<div class="pd-sub-title">「{{ currentBatch.fileName }}」到货明细(卷号在钢卷表确认到货的高亮为已到货)</div>
|
||
<el-table :data="batchRows" border stripe size="mini" max-height="300" v-loading="batchRowsLoading"
|
||
:row-class-name="rowClass">
|
||
<el-table-column label="日期" prop="arrivalDate" width="100" align="center" />
|
||
<el-table-column label="牌号" prop="grade" width="78" align="center" />
|
||
<el-table-column label="规格" prop="spec" width="105" align="center" />
|
||
<el-table-column label="卷号" prop="coilNo" width="120" align="center" />
|
||
<el-table-column label="单卷(T)" prop="coilWeight" width="78" align="right" />
|
||
<el-table-column label="车号" prop="truckNo" width="95" align="center" />
|
||
<el-table-column label="件数" prop="pieceCount" width="56" align="center" />
|
||
<el-table-column label="销售" prop="salesCode" width="80" align="center" />
|
||
<el-table-column label="到货" width="72" align="center">
|
||
<template slot-scope="s">
|
||
<span class="pd-mtag" :class="s.row.arrived === 1 ? 'yes' : 'no'">{{ s.row.arrived === 1 ? '已到货' : '未到' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<template slot="empty"><span>该批次无明细</span></template>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
getPurchasePlan,
|
||
listDeliveryPlans,
|
||
listDeliveryBatches,
|
||
listDeliveryByBatch,
|
||
refreshArrival
|
||
} from '@/api/erp/purchasePlan'
|
||
import { getToken } from '@/utils/auth'
|
||
|
||
export default {
|
||
name: 'ErpPurchaseDelivery',
|
||
data() {
|
||
return {
|
||
loading: true,
|
||
total: 0,
|
||
planList: [],
|
||
queryParams: { pageNum: 1, pageSize: 20, keyword: undefined },
|
||
current: {},
|
||
activeTab: 'items',
|
||
batches: [],
|
||
batchLoading: false,
|
||
currentBatch: {},
|
||
batchRows: [],
|
||
batchRowsLoading: false,
|
||
refreshing: false,
|
||
upload: { headers: { Authorization: 'Bearer ' + getToken() } },
|
||
progressColor: '#5b8db8'
|
||
}
|
||
},
|
||
computed: {
|
||
uploadUrl() {
|
||
return process.env.VUE_APP_BASE_API + '/erp/purchasePlan/' + (this.current.planId || '') + '/importDelivery'
|
||
}
|
||
},
|
||
created() {
|
||
this.getList()
|
||
},
|
||
methods: {
|
||
getList(keepCurrent) {
|
||
this.loading = true
|
||
listDeliveryPlans(this.queryParams).then(res => {
|
||
this.planList = res.rows || []
|
||
this.total = res.total || 0
|
||
this.loading = false
|
||
if (!keepCurrent) {
|
||
if (this.planList.length) this.selectPlan(this.planList[0])
|
||
else this.current = {}
|
||
}
|
||
})
|
||
},
|
||
handleQuery() {
|
||
this.queryParams.pageNum = 1
|
||
this.getList()
|
||
},
|
||
selectPlan(p) {
|
||
this.activeTab = 'items'
|
||
this.current = { ...p }
|
||
this.currentBatch = {}
|
||
this.batchRows = []
|
||
// 打开即静默复核一次 WMS 到货状态,保证看到的是最新
|
||
const planId = p.planId
|
||
refreshArrival(planId).catch(() => {}).finally(() => {
|
||
if (this.current.planId === planId) this.refreshDetail()
|
||
})
|
||
},
|
||
refreshDetail() {
|
||
const planId = this.current.planId
|
||
if (!planId) return
|
||
getPurchasePlan(planId).then(res => { this.current = { ...this.current, ...(res.data || {}) } })
|
||
this.loadBatches()
|
||
},
|
||
doRefreshArrival() {
|
||
if (!this.current.planId) return
|
||
this.refreshing = true
|
||
refreshArrival(this.current.planId).then(() => {
|
||
this.$modal.msgSuccess('已按钢卷表刷新到货状态')
|
||
this.refreshDetail()
|
||
if (this.currentBatch.batchId) this.viewBatch(this.currentBatch)
|
||
this.getList(true)
|
||
}).finally(() => { this.refreshing = false })
|
||
},
|
||
loadBatches() {
|
||
this.batchLoading = true
|
||
listDeliveryBatches(this.current.planId).then(res => {
|
||
this.batches = res.data || []
|
||
}).finally(() => { this.batchLoading = false })
|
||
},
|
||
viewBatch(b) {
|
||
this.currentBatch = { ...b }
|
||
this.batchRowsLoading = true
|
||
listDeliveryByBatch(b.batchId).then(res => { this.batchRows = res.data || [] }).finally(() => { this.batchRowsLoading = false })
|
||
},
|
||
rowClass({ row }) {
|
||
return row.arrived === 1 ? 'pd-row-matched' : ''
|
||
},
|
||
handleUploadSuccess(res) {
|
||
if (res.code === 200) {
|
||
const data = res.data || {}
|
||
if (data.kgConverted) {
|
||
this.$alert(res.msg, '导入完成(含单位纠正)', { dangerouslyUseHTMLString: true, type: 'warning' })
|
||
} else {
|
||
this.$modal.msgSuccess(res.msg || '导入成功')
|
||
}
|
||
this.activeTab = 'batches'
|
||
this.refreshDetail()
|
||
this.getList(true)
|
||
} else {
|
||
this.$alert(res.msg || '导入失败', '到货文件校验未通过', { dangerouslyUseHTMLString: true, type: 'error' })
|
||
}
|
||
},
|
||
handleUploadError() {
|
||
this.$modal.msgError('上传失败,请检查文件后重试')
|
||
},
|
||
fmt(v) {
|
||
return Number(v || 0).toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 3 })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
$accent: #5b8db8;
|
||
$line: #e4e7ed;
|
||
$ink: #303133;
|
||
$sub: #909399;
|
||
|
||
.pd-wb { height: calc(100vh - 84px); display: flex; flex-direction: column; background: #f5f6f8; padding: 12px; box-sizing: border-box; }
|
||
.pd-main { flex: 1; display: flex; gap: 12px; min-height: 0; }
|
||
.pd-col { background: #fff; border: 1px solid $line; display: flex; flex-direction: column; min-height: 0; }
|
||
.pd-plans { width: 300px; flex-shrink: 0; }
|
||
.pd-detail { flex: 1; min-width: 0; overflow-y: auto; }
|
||
|
||
.pd-col-tool { padding: 10px; border-bottom: 1px solid $line; ::v-deep .el-input { width: 100%; } }
|
||
.pd-list { flex: 1; overflow-y: auto; margin: 0; padding: 0; list-style: none; }
|
||
.pd-li {
|
||
padding: 10px 12px; border-bottom: 1px solid #f0f2f5; cursor: pointer; border-left: 3px solid transparent;
|
||
&:hover { background: #f7f9fb; }
|
||
&.active { background: #eef3f8; border-left-color: $accent; }
|
||
}
|
||
.pd-li-r1 { display: flex; justify-content: space-between; align-items: center; }
|
||
.pd-no { font-size: 13px; font-weight: 600; color: $ink; }
|
||
.pd-pct { font-size: 12px; font-weight: 600; color: $sub; &.done { color: #3a8a4d; } }
|
||
.pd-li-r2 { font-size: 12px; color: $sub; margin: 4px 0 6px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.pd-li-r3 { display: flex; justify-content: space-between; font-size: 11px; color: $sub; margin-top: 4px; }
|
||
.pd-arch { font-style: normal; color: #3a8a4d; margin-left: 6px; }
|
||
.pd-empty { text-align: center; color: $sub; padding: 36px 12px; font-size: 13px; }
|
||
|
||
.pd-placeholder {
|
||
height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; color: $sub;
|
||
i { font-size: 46px; margin-bottom: 12px; color: #d6dce1; }
|
||
p { font-size: 13px; }
|
||
}
|
||
.pd-d-head { display: flex; justify-content: space-between; align-items: center; padding: 14px 18px; border-bottom: 1px solid $line; }
|
||
.pd-head-act { display: flex; gap: 10px; align-items: center; ::v-deep .el-button { margin: 0; } }
|
||
.pd-d-title { font-size: 15px; font-weight: 600; color: $ink; margin-right: 8px; }
|
||
.pd-badge {
|
||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub; background: #fafafa;
|
||
&.p1 { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||
}
|
||
.pd-meta { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px 24px; padding: 16px 18px; }
|
||
.pd-meta-i {
|
||
display: flex; flex-direction: column; font-size: 13px;
|
||
label { color: $sub; font-size: 12px; margin-bottom: 3px; }
|
||
span { color: $ink; }
|
||
&.wide { grid-column: span 3; }
|
||
}
|
||
.pd-tabs { padding: 0 18px 18px; }
|
||
.pd-nocoil { color: #c0c4cc; }
|
||
.pd-req-tip { font-size: 12px; color: #909399; margin: 0 0 10px; line-height: 1.6; }
|
||
.pd-istat {
|
||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub;
|
||
&.s2 { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||
}
|
||
.pd-batch-rows { margin-top: 14px; }
|
||
.pd-sub-title { font-size: 13px; font-weight: 600; color: $ink; border-left: 3px solid $accent; padding-left: 8px; margin: 6px 0 10px; }
|
||
.pd-mtag {
|
||
font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: 2px; border: 1px solid #dcdfe6; color: $sub;
|
||
&.yes { color: #3a8a4d; border-color: #b7d9bf; background: #f0f9f1; }
|
||
&.no { color: $sub; }
|
||
}
|
||
::v-deep .pd-row-matched { background: #f3f9f4; }
|
||
</style>
|