feat(cost): 新增成本综合页面的辅料能耗反填功能
本次提交新增了全量反填、单行反填、单元格反填的批量/单独反填能力,支持辅料和能耗类型的数据反填入库,同时新增了反填进度弹窗、优化了表格列头布局和输入框后缀操作按钮样式,补充了对应的API引入和反填处理器注册逻辑。
This commit is contained in:
@@ -22,28 +22,38 @@
|
||||
<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;margin-left:8px" @click="openColCfg">列配置</el-button>
|
||||
<el-button size="mini" style="float:right;margin-left:8px" icon="el-icon-money" @click="openPriceMgr">价格管理</el-button>
|
||||
<el-button size="mini" style="float:right;margin-left:8px" icon="el-icon-upload2" @click="backfillCost" :loading="backfilling">反填</el-button>
|
||||
<span style="float:right;margin-right:12px;font-size:12px;color:#606266;display:flex;align-items:center">
|
||||
<span style="margin-right:4px">{{ inputMode ? '录入' : '查看' }}</span>
|
||||
<el-switch v-model="inputMode" size="small" />
|
||||
</span>
|
||||
</div>
|
||||
<el-alert :title="'已配置'+allCols.length+'个列'" type="info" :closable="false" show-icon style="margin-bottom:8px" />
|
||||
<el-table v-loading="gridLoading" height="calc(100vh - 140px)" :data="gridRows" border stripe size="mini" style="width:100%" :header-cell-style="headerStyle" :key="'tbl-'+inputMode">
|
||||
<!-- <el-alert :title="'已配置'+allCols.length+'个列'" type="info" :closable="false" show-icon style="margin-bottom:8px" /> -->
|
||||
<el-table v-loading="gridLoading" height="calc(100vh - 260px)" :data="gridRows" border stripe size="mini" style="width:100%" :header-cell-style="headerStyle" :key="'tbl-'+inputMode">
|
||||
<el-table-column label="日期" width="135" fixed>
|
||||
<template slot-scope="s"><el-date-picker v-model="s.row.detailDate" type="date" value-format="yyyy-MM-dd" size="mini" style="width:124px" @change="sortGrid" /></template>
|
||||
</el-table-column>
|
||||
<template v-for="col in displayCols">
|
||||
<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">
|
||||
<el-table-column v-if="col.$type==='detail' && !col.isShift" :key="'d'+col.itemId" align="center" width="130">
|
||||
<template slot="header">
|
||||
<div class="col-hd">{{ col.itemName }}{{ col.unit ? '('+col.unit+')' : '' }}</div>
|
||||
</template>
|
||||
<template slot-scope="s">
|
||||
<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)" />
|
||||
<span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions">
|
||||
<i title="反填" v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row)" />
|
||||
<i title="自动获取" :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row)" />
|
||||
</span>
|
||||
</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">
|
||||
<el-table-column v-else-if="col.$type==='detail' && col.isShift" :key="'ds'+col.itemId" align="center" width="135">
|
||||
<template slot="header">
|
||||
<div class="col-hd">{{ col.itemName }}{{ col.unit ? '('+col.unit+')' : '' }}</div>
|
||||
</template>
|
||||
<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"><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>
|
||||
<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"><span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions"><i v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row, '1')" /><i :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row, '1')" /></span></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"><span slot="suffix" v-if="col.queryCondition" class="input-suffix-actions"><i v-if="col.category==='辅料'||col.category==='能耗'" :class="backfillLoading[col.itemId]?'el-icon-loading':'el-icon-upload2'" class="ica ica-backfill" @click.stop="backfillCell(col, s.row, '2')" /><i :class="autoLoading[col.itemId]?'el-icon-loading':'el-icon-refresh'" class="ica ica-fetch" @click.stop="fetchAutoData(col, s.row, '2')" /></span></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">
|
||||
@@ -56,8 +66,13 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" width="55" fixed="right" align="center">
|
||||
<template slot-scope="s"><el-button size="mini" type="text" style="padding:0" @click="gridRows.splice(s.$index,1)">删除</el-button></template>
|
||||
<el-table-column label="操作" width="60" fixed="right" align="center">
|
||||
<template slot-scope="s">
|
||||
<!-- <el-button size="mini" type="text" style="padding:0 2px;font-size:11px" @click="saveRow(s.row)">保存</el-button>
|
||||
<el-button size="mini" type="text" style="padding:0 2px;font-size:11px" @click="fetchRow(s.row)">抓取</el-button>
|
||||
<el-button size="mini" type="text" style="padding:0 2px;font-size:11px;color:#67c23a" @click="backfillRow(s.row)">反填</el-button> -->
|
||||
<el-button size="mini" type="text" style="padding:0 2px;font-size:11px;color:#f56c6c" @click="gridRows.splice(s.$index,1)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top:8px;text-align:center"><el-button type="default" icon="el-icon-plus" size="mini" @click="gridRows.push({detailDate:''})">添加日期行</el-button></div>
|
||||
@@ -273,6 +288,23 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
|
||||
<!-- Progress dialog -->
|
||||
<el-dialog :title="progressTitle" :visible.sync="progressOpen" width="580px" append-to-body top="5vh" :close-on-click-modal="false">
|
||||
<el-table :data="progressTasks" border stripe size="mini" max-height="420">
|
||||
<el-table-column label="任务" prop="label" min-width="220" />
|
||||
<el-table-column label="状态" width="110" align="center">
|
||||
<template slot-scope="s">
|
||||
<el-tag v-if="s.row.status==='pending'" type="info" size="mini">等待中</el-tag>
|
||||
<el-tag v-else-if="s.row.status==='running'" type="warning" size="mini"><i class="el-icon-loading" /> 执行中</el-tag>
|
||||
<el-tag v-else-if="s.row.status==='success'" type="success" size="mini">成功</el-tag>
|
||||
<el-tag v-else-if="s.row.status==='fail'" type="danger" size="mini">失败</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="信息" prop="error" min-width="160" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<div slot="footer"><el-button @click="progressOpen=false">关 闭</el-button></div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -285,9 +317,10 @@ import { listProdMetric, addProdMetric, updateProdMetric, delProdMetric, getProd
|
||||
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 { listAuxiliaryConsume, addAuxiliaryConsume, updateAuxiliaryConsume } from "@/api/eqp/auxiliaryConsume"
|
||||
import { listProductionLine } from "@/api/wms/productionLine"
|
||||
import { listRollGrindAll } from "@/api/mes/roll/rollGrind"
|
||||
import { listEnergyRecord, addEnergyRecord, updateEnergyRecord } from "@/api/ems/energyRecord"
|
||||
|
||||
function parseDateRange(detailDate) {
|
||||
const d = (detailDate || '').slice(0, 10)
|
||||
@@ -306,6 +339,11 @@ export function registerQueryHandler(category, handler) {
|
||||
queryHandlers[category] = handler
|
||||
}
|
||||
|
||||
const backfillHandlers = {}
|
||||
export function registerBackfillHandler(category, handler) {
|
||||
backfillHandlers[category] = handler
|
||||
}
|
||||
|
||||
const teamMap = { '1': '甲', '2': '乙' }
|
||||
|
||||
registerQueryHandler('原料', async (queryCondition, row, col, report, shift) => {
|
||||
@@ -367,6 +405,30 @@ registerQueryHandler('轧辊', async (queryCondition, row, col, report, shift) =
|
||||
return total || null
|
||||
})
|
||||
|
||||
registerBackfillHandler('辅料', async (queryCondition, row, col, report, shift, value) => {
|
||||
if (!row.detailDate) return
|
||||
const d = row.detailDate.slice(0, 10)
|
||||
const res = await listAuxiliaryConsume({ recordDate: d, typeId: queryCondition, pageSize: 9999 })
|
||||
const items = res.rows || []
|
||||
if (items.length > 0) {
|
||||
await updateAuxiliaryConsume({ consumeId: items[0].consumeId, consume: value })
|
||||
} else {
|
||||
await addAuxiliaryConsume({ recordDate: d, typeId: queryCondition, consume: value })
|
||||
}
|
||||
})
|
||||
|
||||
registerBackfillHandler('能耗', async (queryCondition, row, col, report, shift, value) => {
|
||||
if (!row.detailDate) return
|
||||
const d = row.detailDate.slice(0, 10)
|
||||
const res = await listEnergyRecord({ recordDate: d, meterId: queryCondition, pageSize: 9999 })
|
||||
const items = res.rows || []
|
||||
if (items.length > 0) {
|
||||
await updateEnergyRecord({ energyRecordId: items[0].energyRecordId, consumption: value })
|
||||
} else {
|
||||
await addEnergyRecord({ recordDate: d, meterId: queryCondition, consumption: value })
|
||||
}
|
||||
})
|
||||
|
||||
export default {
|
||||
name: "CostComprehensive",
|
||||
data() {
|
||||
@@ -383,12 +445,14 @@ export default {
|
||||
mgrOpen: false, mgrList: [], defOpen: false, defTitle: '', defForm: {},
|
||||
copyCfgOpen: false, copyReports: [], copySrc: null,
|
||||
configOpen: false,
|
||||
autoLoading: {},
|
||||
autoLoading: {}, backfillLoading: {},
|
||||
lineOptions: [],
|
||||
lineType: null,
|
||||
noLineType: false,
|
||||
inputMode: false,
|
||||
priceOpen: false, priceList: [], priceSaving: false
|
||||
priceOpen: false, priceList: [], priceSaving: false,
|
||||
backfilling: false,
|
||||
progressOpen: false, progressTitle: '', progressTasks: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -774,6 +838,180 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async backfillCost() {
|
||||
const detailCols = this.allCols.filter(c => c.$type === 'detail' && (c.category === '辅料' || c.category === '能耗') && c.queryCondition)
|
||||
if (!detailCols.length) { this.$modal.msgWarning('无可用反填列(仅辅料/能耗)'); return }
|
||||
this.backfilling = true
|
||||
try {
|
||||
let count = 0
|
||||
for (const row of this.gridRows) {
|
||||
if (!row.detailDate) continue
|
||||
for (const col of detailCols) {
|
||||
const handler = backfillHandlers[col.category]
|
||||
if (!handler) continue
|
||||
if (col.isShift) {
|
||||
const v1 = row['q' + col.itemId + '_1']
|
||||
if (v1 != null && v1 !== '') { await handler(col.queryCondition, row, col, this.activeReport, '1', v1); count++ }
|
||||
const v2 = row['q' + col.itemId + '_2']
|
||||
if (v2 != null && v2 !== '') { await handler(col.queryCondition, row, col, this.activeReport, '2', v2); count++ }
|
||||
} else {
|
||||
const v = row['q' + col.itemId]
|
||||
if (v != null && v !== '') { await handler(col.queryCondition, row, col, this.activeReport, null, v); count++ }
|
||||
}
|
||||
}
|
||||
}
|
||||
this.$modal.msgSuccess(`反填完成,共处理${count}条`)
|
||||
} catch (e) {
|
||||
this.$modal.msgError('反填失败')
|
||||
} finally {
|
||||
this.backfilling = false
|
||||
}
|
||||
},
|
||||
|
||||
async backfillCell(col, row, shift) {
|
||||
if (!col.queryCondition || this.backfillLoading[col.itemId]) return
|
||||
const handler = backfillHandlers[col.category]
|
||||
if (!handler) { this.$modal.msgWarning(`类别 "${col.category}" 未注册反填处理器`); return }
|
||||
const val = shift ? row['q' + col.itemId + '_' + shift] : row['q' + col.itemId]
|
||||
if (val == null || val === '') { this.$modal.msgWarning('当前单元格无数据'); return }
|
||||
this.$set(this.backfillLoading, col.itemId, true)
|
||||
try {
|
||||
await handler(col.queryCondition, row, col, this.activeReport, shift || null, val)
|
||||
this.$modal.msgSuccess('反填成功')
|
||||
} catch (e) {
|
||||
this.$modal.msgError('反填失败')
|
||||
} finally {
|
||||
this.$set(this.backfillLoading, col.itemId, false)
|
||||
}
|
||||
},
|
||||
|
||||
async runBatchTasks(title, tasks) {
|
||||
this.progressTitle = title
|
||||
this.progressTasks = tasks.map(t => ({ label: t.label, status: 'pending', error: '', run: t.run }))
|
||||
this.progressOpen = true
|
||||
await this.$nextTick()
|
||||
for (const task of this.progressTasks) {
|
||||
if (!this.progressOpen) break
|
||||
task.status = 'running'
|
||||
try {
|
||||
await task.run()
|
||||
task.status = 'success'
|
||||
} catch (e) {
|
||||
task.status = 'fail'
|
||||
task.error = e.message || '执行失败'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async batchFetchCol(col) {
|
||||
if (!col.queryCondition) return
|
||||
const handler = queryHandlers[col.category] || queryHandlers['default']
|
||||
if (!handler) { this.$modal.msgWarning(`类别 "${col.category}" 未注册查询处理器`); return }
|
||||
const round3 = n => Math.round(n * 1000) / 1000
|
||||
const tasks = []
|
||||
for (const row of this.gridRows) {
|
||||
if (!row.detailDate) continue
|
||||
tasks.push({
|
||||
label: `${row.detailDate} ${col.itemName}`,
|
||||
run: async () => {
|
||||
const val = await handler(col.queryCondition, row, col, this.activeReport)
|
||||
if (val != null) {
|
||||
if (col.isShift && Array.isArray(val)) {
|
||||
this.$set(row, 'q' + col.itemId + '_1', round3(val[0]))
|
||||
this.$set(row, 'q' + col.itemId + '_2', round3(val[1]))
|
||||
} else {
|
||||
this.$set(row, 'q' + col.itemId, round3(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!tasks.length) { this.$modal.msgWarning('无可用行'); return }
|
||||
this.$set(this.autoLoading, col.itemId, true)
|
||||
await this.runBatchTasks(`批量抓取 - ${col.itemName}`, tasks)
|
||||
this.$set(this.autoLoading, col.itemId, false)
|
||||
this.recalcAll()
|
||||
},
|
||||
|
||||
async batchBackfillCol(col) {
|
||||
if (!col.queryCondition) return
|
||||
const handler = backfillHandlers[col.category]
|
||||
if (!handler) { this.$modal.msgWarning(`类别 "${col.category}" 未注册反填处理器`); return }
|
||||
const tasks = []
|
||||
for (const row of this.gridRows) {
|
||||
if (!row.detailDate) continue
|
||||
if (col.isShift) {
|
||||
const v1 = row['q' + col.itemId + '_1']
|
||||
if (v1 != null && v1 !== '') {
|
||||
tasks.push({ label: `${row.detailDate} ${col.itemName}(甲)`, run: () => handler(col.queryCondition, row, col, this.activeReport, '1', v1) })
|
||||
}
|
||||
const v2 = row['q' + col.itemId + '_2']
|
||||
if (v2 != null && v2 !== '') {
|
||||
tasks.push({ label: `${row.detailDate} ${col.itemName}(乙)`, run: () => handler(col.queryCondition, row, col, this.activeReport, '2', v2) })
|
||||
}
|
||||
} else {
|
||||
const v = row['q' + col.itemId]
|
||||
if (v != null && v !== '') {
|
||||
tasks.push({ label: `${row.detailDate} ${col.itemName}`, run: () => handler(col.queryCondition, row, col, this.activeReport, null, v) })
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!tasks.length) { this.$modal.msgWarning('无可用数据'); return }
|
||||
this.$set(this.backfillLoading, col.itemId, true)
|
||||
await this.runBatchTasks(`批量反填 - ${col.itemName}`, tasks)
|
||||
this.$set(this.backfillLoading, col.itemId, false)
|
||||
},
|
||||
|
||||
async saveRow(row) {
|
||||
if (!row.detailDate) return
|
||||
const rid = this.activeReport.reportId; if (!rid) return
|
||||
try {
|
||||
const exist = await listProdDetail({ reportId: rid, pageNum: 1, pageSize: 9999 })
|
||||
const dateStr = row.detailDate
|
||||
const removeIds = (exist.rows || []).filter(d => d.detailDate === dateStr).map(d => d.detailId)
|
||||
const detailCols = this.allCols.filter(c => c.$type === 'detail')
|
||||
const detailList = []
|
||||
detailCols.forEach(col => {
|
||||
const push = (shift, sfx) => { const qty = row['q' + col.itemId + sfx]; if (qty != null && qty !== '') detailList.push({ reportId: rid, detailDate: dateStr, shift: shift || '0', itemId: col.itemId, quantity: qty }) }
|
||||
if (col.isShift) { push('1', '_1'); push('2', '_2') } else push(null, '')
|
||||
})
|
||||
await batchSaveProdDetail({ detailIds: removeIds, prodDetailList: detailList })
|
||||
this.$modal.msgSuccess('行保存成功')
|
||||
} catch (e) {
|
||||
this.$modal.msgError('行保存失败')
|
||||
}
|
||||
},
|
||||
|
||||
async fetchRow(row) {
|
||||
if (!row.detailDate) return
|
||||
const cols = this.allCols.filter(c => c.$type === 'detail' && c.queryCondition)
|
||||
for (const col of cols) {
|
||||
if (col.isShift) { await this.fetchAutoData(col, row, '1'); await this.fetchAutoData(col, row, '2') }
|
||||
else { await this.fetchAutoData(col, row) }
|
||||
}
|
||||
this.$modal.msgSuccess('行抓取完成')
|
||||
},
|
||||
|
||||
async backfillRow(row) {
|
||||
if (!row.detailDate) return
|
||||
const cols = this.allCols.filter(c => c.$type === 'detail' && (c.category === '辅料' || c.category === '能耗') && c.queryCondition)
|
||||
if (!cols.length) { this.$modal.msgWarning('无可用反填列'); return }
|
||||
for (const col of cols) {
|
||||
const handler = backfillHandlers[col.category]
|
||||
if (!handler) continue
|
||||
if (col.isShift) {
|
||||
const v1 = row['q' + col.itemId + '_1']
|
||||
if (v1 != null && v1 !== '') { await handler(col.queryCondition, row, col, this.activeReport, '1', v1) }
|
||||
const v2 = row['q' + col.itemId + '_2']
|
||||
if (v2 != null && v2 !== '') { await handler(col.queryCondition, row, col, this.activeReport, '2', v2) }
|
||||
} else {
|
||||
const v = row['q' + col.itemId]
|
||||
if (v != null && v !== '') { await handler(col.queryCondition, row, col, this.activeReport, null, v) }
|
||||
}
|
||||
}
|
||||
this.$modal.msgSuccess('行反填完成')
|
||||
},
|
||||
|
||||
/* helpers */
|
||||
async loadLines() { const r = await listProductionLine({ pageSize: 999 }); this.lineOptions = r.rows || [] },
|
||||
lineName(row) {
|
||||
@@ -828,4 +1066,12 @@ export default {
|
||||
.drag-handle { cursor: grab; font-size: 14px; color: #909399; padding: 2px; display: inline-flex; align-items: center; }
|
||||
.drag-handle:active { cursor: grabbing; }
|
||||
.drag-handle:hover { color: #409eff; }
|
||||
.col-hd { font-size: 12px; line-height: 1.3; }
|
||||
.col-hd-actions { display: flex; align-items: center; gap: 4px; margin-top: 1px; font-size: 10px; }
|
||||
.col-hd-actions .el-link { font-size: 10px; }
|
||||
.input-suffix-actions { display: inline-flex; align-items: center; gap: 1px; }
|
||||
.ica { cursor: pointer; font-size: 13px; line-height: 24px; }
|
||||
.ica-fetch { color: #409eff; }
|
||||
.ica-backfill { color: #67c23a; margin-right: 1px; }
|
||||
.ica:hover { opacity: 0.7; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user