Files
klp-oa/klp-ui/src/views/erp/purchaseDelivery/index.vue
2026-06-27 14:10:56 +08:00

353 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>