feat(cost/comprehensive): 新增产线自动填充和数据一键获取功能

1.  新增产线名称转换方法,统一展示产线中文名
2.  为详情输入框添加自动获取数据按钮,支持原料、产出、辅料、轧辊四类数据自动查询
3.  改造报表表单的产线选择器,使用正式产线数据而非硬编码选项
4.  完善报表编辑时的产线数据映射逻辑
5.  新增自动加载状态管理,避免重复请求
This commit is contained in:
2026-06-02 14:45:04 +08:00
parent b7d47a5d9d
commit ef04d73e0c

View File

@@ -15,7 +15,7 @@
<el-card class="mb8">
<div slot="header" class="entry-header">
<span class="entry-title">{{ activeReport.reportTitle }}</span>
<el-tag size="mini" style="margin-left:6px">{{ activeReport.lineType==='acid'?'酸轧':'镀锌' }}</el-tag>
<el-tag size="mini" style="margin-left:6px">{{ lineName(activeReport) }}</el-tag>
<span class="entry-meta">{{ parseTime(activeReport.reportDate,'{y}-{m}-{d}') }} 投入{{ activeReport.inputWeight }}t 产出{{ activeReport.outputWeight }}t</span>
<el-button type="primary" size="mini" style="float:right;margin-left:8px" @click="saveGrid" :loading="saving">保存</el-button>
<el-button size="mini" style="float:right" @click="openColCfg">列配置</el-button>
@@ -28,13 +28,15 @@
<template v-for="col in allCols">
<el-table-column v-if="col.$type==='detail' && !col.isShift" :key="'d'+col.itemId" :label="col.itemName+(col.unit?'('+col.unit+')':'')" width="105" align="center">
<template slot-scope="s">
<el-input v-model="s.row['q'+col.itemId]" size="mini" @input="recalcAll" />
<el-input v-model="s.row['q'+col.itemId]" size="mini" @input="recalcAll">
<i slot="suffix" v-if="col.queryCondition" :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" style="cursor:pointer;font-size:13px;line-height:24px;color:#409eff" @click.stop="fetchAutoData(col, s.row)" />
</el-input>
</template>
</el-table-column>
<el-table-column v-else-if="col.$type==='detail' && col.isShift" :key="'ds'+col.itemId" :label="col.itemName+(col.unit?'('+col.unit+')':'')" width="120" align="center">
<template slot-scope="s">
<div class="shift-cell"><span class="shift-tag"></span><el-input v-model="s.row['q'+col.itemId+'_1']" size="mini" class="shift-input" @input="recalcAll" /></div>
<div class="shift-cell"><span class="shift-tag"></span><el-input v-model="s.row['q'+col.itemId+'_2']" size="mini" class="shift-input" @input="recalcAll" /></div>
<div class="shift-cell"><span class="shift-tag"></span><el-input v-model="s.row['q'+col.itemId+'_1']" size="mini" class="shift-input" @input="recalcAll"><i slot="suffix" v-if="col.queryCondition" :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" style="cursor:pointer;font-size:12px;line-height:24px;color:#409eff" @click.stop="fetchAutoData(col, s.row, '1')" /></el-input></div>
<div class="shift-cell"><span class="shift-tag"></span><el-input v-model="s.row['q'+col.itemId+'_2']" size="mini" class="shift-input" @input="recalcAll"><i slot="suffix" v-if="col.queryCondition" :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" style="cursor:pointer;font-size:12px;line-height:24px;color:#409eff" @click.stop="fetchAutoData(col, s.row, '2')" /></el-input></div>
</template>
</el-table-column>
<el-table-column v-else-if="col.$type==='metric' && !col.isShift" :key="'m'+col.mIdx" :label="col.metricName+(col.unit?'('+col.unit+')':'')" width="85" align="center">
@@ -184,7 +186,7 @@
<el-table :data="copyReports" border stripe size="mini" highlight-current-row @current-change="copySrc=$event">
<el-table-column label="报表标题" prop="reportTitle" />
<el-table-column label="日期" width="110"><template slot-scope="s">{{ parseTime(s.row.reportDate,'{y}-{m}-{d}') }}</template></el-table-column>
<el-table-column label="产线" prop="lineType" width="70" />
<el-table-column label="产线" width="70"><template slot-scope="s">{{ lineName(s.row) }}</template></el-table-column>
</el-table>
<div slot="footer">
<el-button type="primary" :disabled="!copySrc" @click="doCopyCfg">确认复制</el-button>
@@ -211,7 +213,7 @@
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="报表标题" prop="reportTitle" />
<el-table-column label="日期" width="120"><template slot-scope="s">{{ parseTime(s.row.reportDate,'{y}-{m}-{d}') }}</template></el-table-column>
<el-table-column label="产线" prop="lineType" width="70" />
<el-table-column label="产线" width="70"><template slot-scope="s">{{ lineName(s.row) }}</template></el-table-column>
<el-table-column label="投入(t)" prop="inputWeight" width="80" />
<el-table-column label="产出(t)" prop="outputWeight" width="80" />
</el-table>
@@ -221,7 +223,7 @@
<el-form ref="rpf" :model="rpForm" :rules="{reportTitle:[{required:true,message:'请输入',trigger:'blur'}]}" label-width="100px">
<el-form-item label="报表标题" prop="reportTitle"><el-input v-model="rpForm.reportTitle" /></el-form-item>
<el-form-item label="报表日期" prop="reportDate"><el-date-picker v-model="rpForm.reportDate" type="date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item>
<el-form-item label="产线类型" prop="lineType"><el-select v-model="rpForm.lineType" style="width:100%"><el-option label="酸轧" value="acid" /><el-option label="镀锌" value="galvanized" /></el-select></el-form-item>
<el-form-item label="产线" prop="lineType"><el-select v-model="rpForm.lineType" style="width:100%" placeholder="请选择产线"><el-option v-for="ln in lineOptions" :key="ln.lineId" :label="ln.lineName" :value="ln.lineId" /></el-select></el-form-item>
<el-form-item label="投入量(吨)" prop="inputWeight"><el-input-number v-model="rpForm.inputWeight" :precision="2" :min="0" style="width:100%" /></el-form-item>
<el-form-item label="产出量(吨)" prop="outputWeight"><el-input-number v-model="rpForm.outputWeight" :precision="2" :min="0" style="width:100%" /></el-form-item>
<el-form-item label="备注" prop="remark"><el-input v-model="rpForm.remark" type="textarea" /></el-form-item>
@@ -249,6 +251,89 @@ import { listProdReport, getProdReport, addProdReport, updateProdReport, delProd
import { listProdDetail, batchSaveProdDetail } from "@/api/cost/prodDetail"
import { listProdMetric, addProdMetric, updateProdMetric, delProdMetric, getProdMetric } from "@/api/cost/prodMetric"
import { listItem } from "@/api/cost/item"
import { listLightPendingAction } from "@/api/wms/pendingAction"
import { getCoilStatisticsList } from "@/api/wms/coil"
import { listAuxiliaryConsume } from "@/api/eqp/auxiliaryConsume"
import { listProductionLine } from "@/api/wms/productionLine"
import { listRollGrindAll } from "@/api/mes/roll/rollGrind"
function parseDateRange(detailDate) {
const d = (detailDate || '').slice(0, 10)
return {
startTime: d + ' 00:00:00',
endTime: d + ' 23:59:59'
}
}
/**
* 自动查询处理器注册表。
* key: item.category, value: async (queryCondition, row, col, report, shift) => fetchedValue
*/
const queryHandlers = {}
export function registerQueryHandler(category, handler) {
queryHandlers[category] = handler
}
const teamMap = { '1': '甲', '2': '乙' }
registerQueryHandler('原料', async (queryCondition, row, col, report, shift) => {
if (!row.detailDate) return null
const { startTime, endTime } = parseDateRange(row.detailDate)
const res = await listLightPendingAction({ actionStatus: 2, actionTypes: queryCondition, startTime, endTime, pageSize: 99999 })
const items = Array.isArray(res.data) ? res.data : (res.rows || [])
const ids = [...new Set(items.map(i => i.coilId).filter(Boolean))]
if (!ids.length) return null
const params = { coilIds: ids.join(',') }
if (shift && teamMap[shift]) params.team = teamMap[shift]
const stat = await getCoilStatisticsList(params)
const net = stat.data && stat.data.total_net_weight
return net != null ? net : null
})
registerQueryHandler('产出', async (queryCondition, row, col, report, shift) => {
if (!row.detailDate) return null
const { startTime, endTime } = parseDateRange(row.detailDate)
const res = await listLightPendingAction({ actionStatus: 2, actionTypes: queryCondition, startTime, endTime, pageSize: 99999 })
const items = Array.isArray(res.data) ? res.data : (res.rows || [])
const ids = []
for (const i of items) {
if (i.processedCoilIds) {
i.processedCoilIds.split(',').forEach(id => { id = id.trim(); if (id) ids.push(id) })
}
}
if (!ids.length) return null
const params = { coilIds: [...new Set(ids)].join(',') }
if (shift && teamMap[shift]) params.team = teamMap[shift]
const stat = await getCoilStatisticsList(params)
const net = stat.data && stat.data.total_net_weight
return net != null ? net : null
})
registerQueryHandler('辅料', async (queryCondition, row, col, report, shift) => {
if (!row.detailDate) return null
const d = (row.detailDate || '').slice(0, 10)
const res = await listAuxiliaryConsume({ recordDate: d, typeId: queryCondition, pageSize: 9999 })
const items = res.rows || []
const total = items.reduce((s, item) => s + (parseFloat(item.consume) || 0), 0)
if (col.isShift) {
const half = total / 2
return [half, half]
}
return total || null
})
registerQueryHandler('轧辊', async (queryCondition, row, col, report, shift) => {
if (!row.detailDate || !report.lineType) return null
const d = (row.detailDate || '').slice(0, 10)
const params = { lineId: report.lineType, beginTime: d + ' 00:00:00', endTime: d + ' 23:59:59', rollType: queryCondition }
if (shift && teamMap[shift]) params.team = teamMap[shift] + '班'
const res = await listRollGrindAll(params)
const items = res.data || []
if (!items.length) return null
let total = 0
items.forEach(item => { total += parseFloat(item.grindAmount) || 0 })
return total || null
})
export default {
name: "CostComprehensive",
@@ -265,7 +350,9 @@ export default {
metricPickOpen: false, metricPickList: [], selMp: [],
mgrOpen: false, mgrList: [], defOpen: false, defTitle: '', defForm: {},
copyCfgOpen: false, copyReports: [], copySrc: null,
configOpen: false
configOpen: false,
autoLoading: {},
lineOptions: []
}
},
computed: {
@@ -286,7 +373,7 @@ export default {
}
},
watch: { configOpen(v) { if (!v) this.rpOpen = false } },
created() { this.getTabList(); this.loadItems() },
created() { this.getTabList(); this.loadItems(); this.loadLines() },
methods: {
/* report */
getList() { this.loading = true; listProdReport(this.q).then(r=>{this.list=r.rows;this.total=r.total}).finally(()=>this.loading=false) },
@@ -294,12 +381,22 @@ export default {
search() { this.q.pageNum = 1; this.getList() },
resetQ() { this.resetForm("qf"); this.search() },
addRp() { this.rpForm = {}; this.rpTitle = "新增"; this.rpOpen = true },
editRp(row) { const id = (row&&row.reportId)||this.selIds[0]; if(!id)return; getProdReport(id).then(r=>{this.rpForm=r.data;this.rpTitle="修改";this.rpOpen=true}) },
editRp(row) {
const id = (row&&row.reportId)||this.selIds[0]; if(!id)return
getProdReport(id).then(r=>{
const d = r.data || {}
if (d.lineType) {
const match = this.lineOptions.find(l => l.lineName.includes(d.lineType === 'acid' ? '酸轧' : '镀锌'))
if (match) d.lineId = match.lineId
}
this.rpForm = d; this.rpTitle = "修改"; this.rpOpen = true
})
},
submitRp() {
this.$refs.rpf.validate(v=>{if(!v)return;this.rpBtnLoading=true;const fn=this.rpForm.reportId?updateProdReport:addProdReport;fn(this.rpForm).then(()=>{
this.$modal.msgSuccess("成功"); this.rpOpen = false
if (this.activeReport && this.activeReport.reportId === this.rpForm.reportId) {
Object.assign(this.activeReport, { reportTitle: this.rpForm.reportTitle, reportDate: this.rpForm.reportDate, lineType: this.rpForm.lineType, inputWeight: this.rpForm.inputWeight, outputWeight: this.rpForm.outputWeight })
Object.assign(this.activeReport, { reportTitle: this.rpForm.reportTitle, reportDate: this.rpForm.reportDate, lineId: this.rpForm.lineId, inputWeight: this.rpForm.inputWeight, outputWeight: this.rpForm.outputWeight })
}
this.getTabList(); this.getList()
}).finally(()=>this.rpBtnLoading=false)})
@@ -356,7 +453,7 @@ export default {
batchAddDetailCols() {
this.selAdd.forEach(item => {
if (!this.allCols.find(c => c.$type === 'detail' && String(c.itemId) === String(item.itemId)))
this.allCols.push({ $type: 'detail', itemId: item.itemId, itemCode: item.itemCode, itemName: item.itemName, unit: item.unit, isShift: false, color: null })
this.allCols.push({ $type: 'detail', itemId: item.itemId, itemCode: item.itemCode, itemName: item.itemName, unit: item.unit, isShift: false, color: null, queryCondition: item.queryCondition, category: item.category })
})
this.showAddDetail = false; this.selAdd = []
},
@@ -456,7 +553,7 @@ export default {
if (c.t === 'd') {
const id = String(c.id)
const item = this.allItems.find(i => String(i.itemId) === id)
if (item) cols.push({ $type: 'detail', itemId: item.itemId, itemCode: item.itemCode, itemName: item.itemName, unit: item.unit, isShift: !!c.s, color: c.c || null })
if (item) cols.push({ $type: 'detail', itemId: item.itemId, itemCode: item.itemCode, itemName: item.itemName, unit: item.unit, isShift: !!c.s, color: c.c || null, queryCondition: item.queryCondition, category: item.category })
} else if (c.t === 'm') {
const id = String(c.id)
let def = (this._allMetricDefs || []).find(m => String(m.metricId) === id)
@@ -554,7 +651,7 @@ export default {
cols.forEach(sc => {
if (sc.t === 'd') {
const sid = String(sc.id)
if (!usedIds.has(sid)) { const item = this.allItems.find(i=>String(i.itemId)===sid); if (item) { this.allCols.push({ $type:'detail', itemId:item.itemId, itemCode:item.itemCode, itemName:item.itemName, unit:item.unit, isShift:!!sc.s, color:sc.c||null }); usedIds.add(sid) } }
if (!usedIds.has(sid)) { const item = this.allItems.find(i=>String(i.itemId)===sid); if (item) { this.allCols.push({ $type:'detail', itemId:item.itemId, itemCode:item.itemCode, itemName:item.itemName, unit:item.unit, isShift:!!sc.s, color:sc.c||null, queryCondition:item.queryCondition, category:item.category }); usedIds.add(sid) } }
}
else if (sc.t === 'm') {
const sid = String(sc.id)
@@ -566,7 +663,41 @@ export default {
this.$modal.msgSuccess('配置已复用')
},
/* auto fetch */
async fetchAutoData(col, row, shift) {
if (!col.queryCondition || this.autoLoading[col.itemId]) return
const handler = queryHandlers[col.category] || queryHandlers['default']
if (!handler) { this.$modal.msgWarning(`类别 "${col.category}" 未注册查询处理器`); return }
this.$set(this.autoLoading, col.itemId, true)
try {
const val = await handler(col.queryCondition, row, col, this.activeReport, shift)
if (val != null) {
const round3 = n => Math.round(n * 1000) / 1000
if (Array.isArray(val)) {
this.$set(row, 'q' + col.itemId + '_1', round3(val[0]))
this.$set(row, 'q' + col.itemId + '_2', round3(val[1]))
} else {
const key = 'q' + col.itemId + (shift ? '_' + shift : '')
this.$set(row, key, round3(val))
}
this.recalcAll()
}
} catch (e) {
this.$modal.msgError('自动获取数据失败')
} finally {
this.$set(this.autoLoading, col.itemId, false)
}
},
/* helpers */
async loadLines() { const r = await listProductionLine({ pageSize: 999 }); this.lineOptions = r.rows || [] },
lineName(row) {
if (row.lineType) {
const found = this.lineOptions.find(l => l.lineId == row.lineType);
if (found) return found.lineName
else return row.lineType || '-'
}
},
async loadItems() { if (!this.allItems.length) { const r = await listItem({ pageNum:1, pageSize:999 }); this.allItems = r.rows || [] } },
async loadAllMetrics(rid) {
const q = { pageNum:1, pageSize:99999 }; if (rid) q.reportId = rid