feat(wms-report): 新增对比报表页面和尺寸异常统计功能

1. 新增comparison.vue对比报表页面,支持基期/当期数据对比、快速环比查询、明细表格查看和列配置
2. 在action.vue报表中添加尺寸异常统计模块,统计长度和厚度异常的卷数、总重及占比
3. 新增告警阈值配置获取逻辑,从系统配置中读取长度和厚度异常阈值
This commit is contained in:
2026-06-13 11:15:38 +08:00
parent 12ea9b0b83
commit 6edc6e1100
2 changed files with 450 additions and 1 deletions

View File

@@ -0,0 +1,385 @@
<template>
<div class="app-container comparison-report" v-loading="loading">
<!-- 查询条件 -->
<el-card shadow="never" class="filter-card">
<el-form label-width="70px" inline class="filter-form">
<el-form-item label="产线">
<el-select style="width: 150px;" v-model="actionTypes" placeholder="产线" clearable @change="handleQuery" size="small">
<el-option label="全部" value="" />
<el-option v-for="line in lineOptions" :key="line.value" :label="line.label" :value="line.value" />
</el-select>
</el-form-item>
<el-form-item label="品质">
<muti-select v-model="queryParams.qualityStatusCsv" :options="dict.type.coil_quality_status"
placeholder="品质" clearable style="width: 150px;" size="small" />
</el-form-item>
<el-form-item label="规格">
<memo-input style="width: 130px;" v-model="queryParams.itemSpecification" storageKey="coilSpec"
placeholder="规格" clearable size="small" @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质">
<muti-select style="width: 150px;" v-model="queryParams.itemMaterial" :options="dict.type.coil_material"
placeholder="材质" clearable size="small" />
</el-form-item>
<el-form-item label="厂家">
<muti-select style="width: 150px;" v-model="queryParams.itemManufacturer"
:options="dict.type.coil_manufacturer" placeholder="厂家" clearable size="small" />
</el-form-item>
<el-form-item label="入场号">
<el-input style="width: 150px;" v-model="queryParams.enterCoilNo" placeholder="入场钢卷号" clearable size="small"
@keyup.enter.native="handleQuery" />
</el-form-item>
</el-form>
<div class="time-row">
<span class="time-label">基期</span>
<el-date-picker style="width: 170px;" v-model="baseStartTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始" size="small" />
<span class="time-sep"></span>
<el-date-picker style="width: 170px;" v-model="baseEndTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束" size="small" />
<span class="time-label current-label">当期</span>
<el-date-picker style="width: 170px;" v-model="currentStartTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="开始" size="small" />
<span class="time-sep"></span>
<el-date-picker style="width: 170px;" v-model="currentEndTime" type="datetime"
value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束" size="small" />
<el-button-group size="small" class="quick-btns">
<el-button @click="setQuickCompare('prevDay')">日环比</el-button>
<el-button @click="setQuickCompare('prevWeek')">周环比</el-button>
<el-button @click="setQuickCompare('prevMonth')">月环比</el-button>
<el-button @click="setQuickCompare('prevYear')">年环比</el-button>
</el-button-group>
<el-button type="primary" size="small" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button size="small" icon="el-icon-setting" @click="settingVisible = true">列配置</el-button>
</div>
</el-card>
<!-- 口径说明 -->
<el-alert type="info" :closable="false" class="explain-alert">
<span slot="title">
<strong>口径</strong>数值环比 = (当期基期)/|基期|×100%<span class="color-up">红增</span>/<span class="color-down">绿降</span>百分比环比 = 当期百分点基期百分点 M卷currentCoilNo含M且不在前五位不计入产出
</span>
</el-alert>
<!-- 指标卡片 -->
<div class="card-group">
<div class="group-title">环比对比统计</div>
<el-row :gutter="8">
<el-col :span="6" v-for="item in comparisonItems" :key="item.key">
<div class="kpi-card">
<div class="kpi-label">{{ item.label }}</div>
<div class="kpi-body">
<span class="kpi-base">{{ item.base }}</span>
<span class="kpi-arrow"></span>
<span class="kpi-current">{{ item.current }}</span>
</div>
<div class="kpi-rate" :style="{ color: item.rateColor }">{{ item.rate }}</div>
</div>
</el-col>
</el-row>
</div>
<div class="card-group">
<div class="group-title">已处理M统计信息对比</div>
<el-row :gutter="8">
<el-col :span="6" v-for="item in mComparisonItems" :key="item.key">
<div class="kpi-card">
<div class="kpi-label">{{ item.label }}</div>
<div class="kpi-body">
<span class="kpi-base">{{ item.base }}</span>
<span class="kpi-arrow"></span>
<span class="kpi-current">{{ item.current }}</span>
</div>
<div class="kpi-rate" :style="{ color: item.rateColor }">{{ item.rate }}</div>
</div>
</el-col>
</el-row>
</div>
<div class="card-group">
<div class="group-title">异常库位统计对比</div>
<el-row :gutter="8">
<el-col :span="6" v-for="item in abComparisonItems" :key="item.key">
<div class="kpi-card">
<div class="kpi-label">{{ item.label }}</div>
<div class="kpi-body">
<span class="kpi-base">{{ item.base }}</span>
<span class="kpi-arrow"></span>
<span class="kpi-current">{{ item.current }}</span>
</div>
<div class="kpi-rate" :style="{ color: item.rateColor }">{{ item.rate }}</div>
</div>
</el-col>
</el-row>
</div>
<!-- 明细信息 -->
<div class="detail-section">
<div class="group-title">明细信息</div>
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="产出(当期)" name="currentOutput" />
<el-tab-pane label="投入(当期)" name="currentLoss" />
<el-tab-pane label="产出(基期)" name="baseOutput" />
<el-tab-pane label="投入(基期)" name="baseLoss" />
</el-tabs>
<coil-table :data="activeDetailList" :total="activeDetailTotal" :page-num="activeDetailPageNum"
:page-size="detailPageSize" :columns="activeDetailColumns" :loading="detailLoading"
@size-change="handleDetailSizeChange"
@current-change="handleDetailPageChange"
height="calc(100vh - 500px)" />
</div>
<el-dialog title="列设置" :visible.sync="settingVisible" width="50%">
<el-radio-group v-model="activeColumnConfig">
<el-radio-button label="coil-report-loss">投入明细</el-radio-button>
<el-radio-button label="coil-report-output">产出明细</el-radio-button>
</el-radio-group>
<columns-setting :reportType="activeColumnConfig" />
</el-dialog>
</div>
</template>
<script>
import { listLightCoil, listCoilWithIds } from "@/api/wms/coil";
import { listLightPendingAction } from '@/api/wms/pendingAction';
import MemoInput from "@/components/MemoInput";
import MutiSelect from "@/components/MutiSelect";
import { calcSummary, calcMSummary, calcAbSummary } from "@/views/wms/report/js/calc";
import CoilTable from "@/views/wms/report/components/coilTable";
import ColumnsSetting from "@/views/wms/report/components/setting/columns";
export default {
name: 'ComparisonReport',
components: { MemoInput, MutiSelect, CoilTable, ColumnsSetting },
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer', 'coil_quality_status'],
data() {
const addZero = (num) => num.toString().padStart(2, '0')
const now = new Date()
const currentMonthStart = `${now.getFullYear()}-${addZero(now.getMonth() + 1)}-01 00:00:00`
const currentMonthEnd = `${now.getFullYear()}-${addZero(now.getMonth() + 1)}-${addZero(now.getDate())} 23:59:59`
const prevMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1)
const prevMonthLastDay = new Date(now.getFullYear(), now.getMonth(), 0)
const baseMonthStart = `${prevMonth.getFullYear()}-${addZero(prevMonth.getMonth() + 1)}-01 00:00:00`
const baseMonthEnd = `${prevMonth.getFullYear()}-${addZero(prevMonth.getMonth() + 1)}-${addZero(Math.min(now.getDate(), prevMonthLastDay.getDate()))} 23:59:59`
return {
currentOutList: [], currentLossList: [], baseOutList: [], baseLossList: [],
currentOutDetailList: [], currentOutDetailTotal: 0, currentOutPageNum: 1,
currentLossDetailList: [], currentLossDetailTotal: 0, currentLossPageNum: 1,
baseOutDetailList: [], baseOutDetailTotal: 0, baseOutPageNum: 1,
baseLossDetailList: [], baseLossDetailTotal: 0, baseLossPageNum: 1,
detailPageSize: 20,
currentOutIds: '', currentLossActionIds: '', baseOutIds: '', baseLossActionIds: '',
activeTab: 'currentOutput', activeColumnConfig: 'coil-report-output',
settingVisible: false, loading: false, detailLoading: false,
currentStartTime: currentMonthStart, currentEndTime: currentMonthEnd,
baseStartTime: baseMonthStart, baseEndTime: baseMonthEnd,
queryParams: { enterCoilNo: '', currentCoilNo: '', itemName: '', itemSpecification: '', itemMaterial: '', itemManufacturer: '', qualityStatusCsv: '' },
lossColumns: [], outputColumns: [],
actionTypes: '',
lineOptions: [
{ label: '酸轧线', value: '11,120,201,520' }, { label: '镀锌线', value: '202,501,521' },
{ label: '双机架', value: '205,504,524' }, { label: '镀铬线', value: '206,505,525' },
{ label: '拉矫线', value: '204,503,523' }, { label: '脱脂线', value: '203,502,522' },
]
}
},
computed: {
summary() { return calcSummary(this.currentOutList, this.currentLossList) },
baseSummary() { return calcSummary(this.baseOutList, this.baseLossList) },
currentMSummary() { return calcMSummary(this.currentOutList, this.currentLossList) },
baseMSummary() { return calcMSummary(this.baseOutList, this.baseLossList) },
currentAbSummary() { return calcAbSummary(this.currentOutList) },
baseAbSummary() { return calcAbSummary(this.baseOutList) },
comparisonItems() {
return this.buildComparisonItems(this.summary, this.baseSummary, [
{ key: 'outCount', label: '产出数量' }, { key: 'outTotalWeight', label: '产出总重(t)' }, { key: 'outAvgWeight', label: '产出均重(t)' },
{ key: 'lossCount', label: '消耗数量' }, { key: 'lossTotalWeight', label: '消耗总重(t)' }, { key: 'lossAvgWeight', label: '消耗均重(t)' },
{ key: 'totalCount', label: '合计数量' }, { key: 'totalWeight', label: '合计总重(t)' }, { key: 'totalAvgWeight', label: '合计均重(t)' },
{ key: 'countDiff', label: '数量差值' }, { key: 'weightDiff', label: '总重差值' }, { key: 'avgWeightDiff', label: '均重差值(t)' },
{ key: 'passRate', label: '成品率', isPercent: true }, { key: 'lossRate', label: '损耗率', isPercent: true },
{ key: 'abRate', label: '异常率', isPercent: true }, { key: 'passRate2', label: '正品率', isPercent: true },
])
},
mComparisonItems() {
return this.buildComparisonItems(this.currentMSummary, this.baseMSummary, [
{ key: 'outCount', label: 'M-产出数量' }, { key: 'outTotalWeight', label: 'M-产出总重(t)' }, { key: 'outAvgWeight', label: 'M-产出均重(t)' },
{ key: 'lossCount', label: 'M-消耗数量' }, { key: 'lossTotalWeight', label: 'M-消耗总重(t)' }, { key: 'lossAvgWeight', label: 'M-消耗均重(t)' },
{ key: 'countDiff', label: 'M-数量差值' }, { key: 'weightDiff', label: 'M-总重差值' },
{ key: 'passRate', label: 'M-成品率', isPercent: true }, { key: 'lossRate', label: 'M-损耗率', isPercent: true },
{ key: 'abRate', label: 'M-异常率', isPercent: true }, { key: 'passRate2', label: 'M-正品率', isPercent: true },
])
},
abComparisonItems() {
const cur = this.currentAbSummary, base = this.baseAbSummary
const cm = {}, bm = {}
cur.forEach(i => { cm[i.label] = i.value })
base.forEach(i => { bm[i.label] = i.value })
return ['技术部钢卷数','小钢卷库钢卷数','废品库钢卷数','退货库钢卷数',
'技术部钢卷重量','小钢卷库钢卷重量','废品库钢卷重量','退货库钢卷重量',
'技术部占比','小钢卷库占比','废品库占比','退货库占比'].map(label => {
const { rate, rateColor } = this.calcSingleRate(cm[label] || 0, bm[label] || 0, label.includes('占比'))
return { key: label, label, base: bm[label] || 0, current: cm[label] || 0, rate, rateColor }
})
},
activeDetailList() {
const m = { currentOutput: 'currentOutDetailList', currentLoss: 'currentLossDetailList', baseOutput: 'baseOutDetailList', baseLoss: 'baseLossDetailList' }
return this[m[this.activeTab]] || []
},
activeDetailTotal() {
const m = { currentOutput: 'currentOutDetailTotal', currentLoss: 'currentLossDetailTotal', baseOutput: 'baseOutDetailTotal', baseLoss: 'baseLossDetailTotal' }
return this[m[this.activeTab]] || 0
},
activeDetailPageNum() {
const m = { currentOutput: 'currentOutPageNum', currentLoss: 'currentLossPageNum', baseOutput: 'baseOutPageNum', baseLoss: 'baseLossPageNum' }
return this[m[this.activeTab]] || 1
},
activeDetailColumns() {
return this.activeTab.endsWith('Output') ? this.outputColumns : this.lossColumns
},
},
created() { this.handleQuery(); this.loadColumns() },
watch: {
activeTab() { this.loadDetailData() }
},
methods: {
calcSingleRate(curVal, baseVal, isPercent) {
if (isPercent) {
const cp = parseFloat(curVal) || 0, bp = parseFloat(baseVal) || 0, diff = (cp - bp).toFixed(2)
return { rate: (diff >= 0 ? '+' : '') + diff + '%', rateColor: diff > 0 ? '#f56c6c' : diff < 0 ? '#67c23a' : '' }
}
const cv = parseFloat(curVal) || 0, bv = parseFloat(baseVal) || 0, diff = cv - bv
let label = '0.00%'
if (bv !== 0) { const r = ((cv - bv) / Math.abs(bv) * 100).toFixed(2); label = (r >= 0 ? '+' : '') + r + '%' }
else if (cv !== 0) label = '+∞'
return { rate: label, rateColor: diff > 0 ? '#f56c6c' : diff < 0 ? '#67c23a' : '' }
},
buildComparisonItems(current, base, itemDefs) {
return itemDefs.map(item => {
const { rate, rateColor } = this.calcSingleRate(current[item.key], base[item.key], item.isPercent)
return { key: item.key, label: item.label, base: base[item.key], current: current[item.key], rate, rateColor }
})
},
setQuickCompare(type) {
const now = new Date(), addZero = n => n.toString().padStart(2, '0'), fd = d => `${d.getFullYear()}-${addZero(d.getMonth()+1)}-${addZero(d.getDate())}`
switch (type) {
case 'prevDay': {
const y = new Date(now); y.setDate(y.getDate()-1)
this.currentStartTime = `${fd(y)} 00:00:00`; this.currentEndTime = `${fd(y)} 23:59:59`
const by = new Date(y); by.setDate(by.getDate()-1)
this.baseStartTime = `${fd(by)} 00:00:00`; this.baseEndTime = `${fd(by)} 23:59:59`; break
}
case 'prevWeek': {
const wa = new Date(now); wa.setDate(wa.getDate()-7)
this.currentStartTime = `${fd(wa)} 00:00:00`; this.currentEndTime = `${fd(now)} 23:59:59`
const twa = new Date(wa); twa.setDate(twa.getDate()-7)
this.baseStartTime = `${fd(twa)} 00:00:00`; this.baseEndTime = `${fd(wa)} 23:59:59`; break
}
case 'prevMonth': {
this.currentStartTime = `${fd(new Date(now.getFullYear(),now.getMonth(),1))} 00:00:00`; this.currentEndTime = `${fd(now)} 23:59:59`
this.baseStartTime = `${fd(new Date(now.getFullYear(),now.getMonth()-1,1))} 00:00:00`; this.baseEndTime = `${fd(new Date(now.getFullYear(),now.getMonth(),0))} 23:59:59`; break
}
case 'prevYear': {
this.currentStartTime = `${fd(new Date(now.getFullYear(),0,1))} 00:00:00`; this.currentEndTime = `${fd(now)} 23:59:59`
this.baseStartTime = `${fd(new Date(now.getFullYear()-1,0,1))} 00:00:00`; this.baseEndTime = `${fd(new Date(now.getFullYear()-1,now.getMonth(),now.getDate()))} 23:59:59`; break
}
}
this.handleQuery()
},
handleQuery() { this.currentOutPageNum = this.currentLossPageNum = this.baseOutPageNum = this.baseLossPageNum = 1; this.fetchData() },
async fetchData() {
this.loading = true
const baseQuery = { enterCoilNo: this.queryParams.enterCoilNo, currentCoilNo: this.queryParams.currentCoilNo,
itemName: this.queryParams.itemName, itemSpecification: this.queryParams.itemSpecification,
itemMaterial: this.queryParams.itemMaterial, itemManufacturer: this.queryParams.itemManufacturer,
qualityStatusCsv: this.queryParams.qualityStatusCsv }
const mapItems = list => (list || []).map(item => {
const [th, w] = item.specification?.split('*') || []
return { ...item, computedThickness: parseFloat(th), computedWidth: parseFloat(w) }
})
const fetchPeriod = async (st, et) => {
const p = { ...baseQuery, startTime: st, endTime: et, actionTypes: this.actionTypes, actionStatus: 2 }
const acts = await listLightPendingAction(p)
const oIds = acts.data.map(i => i.processedCoilIds).filter(Boolean).join(',')
const lIds = acts.data.filter(i => i.actionId).map(i => i.actionId).join(',')
if (!oIds || !lIds) return { outList: [], lossList: [], outIds: '', lossActionIds: '' }
const [oRes, lRes] = await Promise.all([
listLightCoil({ ...p, coilIds: oIds, startTime: '', endTime: '', byCreateTimeStart: st, byCreateTimeEnd: et, selectType: 'product', pageSize: 99999, pageNum: 1 }),
listLightCoil({ ...p, actionIds: lIds, startTime: '', endTime: '', selectType: 'raw_material', pageSize: 99999, pageNum: 1 }),
])
return { outList: mapItems(oRes), lossList: mapItems(lRes), outIds: oIds, lossActionIds: lIds }
}
const [cr, br] = await Promise.all([
fetchPeriod(this.currentStartTime, this.currentEndTime),
fetchPeriod(this.baseStartTime, this.baseEndTime),
])
this.currentOutList = cr.outList; this.currentLossList = cr.lossList; this.baseOutList = br.outList; this.baseLossList = br.lossList
this.currentOutIds = cr.outIds; this.currentLossActionIds = cr.lossActionIds; this.baseOutIds = br.outIds; this.baseLossActionIds = br.lossActionIds
this.loading = false
this.loadDetailData()
},
async loadDetailData() {
const cfgMap = {
currentOutput: { ids: 'currentOutIds', list: 'currentOutDetailList', total: 'currentOutDetailTotal', pn: 'currentOutPageNum', type: 'product' },
currentLoss: { ids: 'currentLossActionIds', list: 'currentLossDetailList', total: 'currentLossDetailTotal', pn: 'currentLossPageNum', type: 'raw_material' },
baseOutput: { ids: 'baseOutIds', list: 'baseOutDetailList', total: 'baseOutDetailTotal', pn: 'baseOutPageNum', type: 'product' },
baseLoss: { ids: 'baseLossActionIds', list: 'baseLossDetailList', total: 'baseLossDetailTotal', pn: 'baseLossPageNum', type: 'raw_material' },
}
const c = cfgMap[this.activeTab]
if (!c) return
this.detailLoading = true
if (!this[c.ids]) {
this[c.list] = []; this[c.total] = 0; this.detailLoading = false; return
}
const isOut = c.type === 'product'
const res = await listCoilWithIds(isOut
? { ...this.queryParams, coilIds: this[c.ids], selectType: 'product', pageSize: this.detailPageSize, pageNum: this[c.pn] }
: { ...this.queryParams, actionIds: this[c.ids], selectType: 'raw_material', pageSize: this.detailPageSize, pageNum: this[c.pn] })
this[c.list] = (res.rows || []).map(item => {
const [th, w] = item.specification?.split('*') || []
return { ...item, computedThickness: parseFloat(th), computedWidth: parseFloat(w) }
})
this[c.total] = res.total || 0; this.detailLoading = false
},
handleDetailPageChange(page) { this[this.activeTab + 'PageNum'] = page; this.loadDetailData() },
handleDetailSizeChange(size) { this.detailPageSize = size; this[this.activeTab + 'PageNum'] = 1; this.loadDetailData() },
loadColumns() {
this.lossColumns = JSON.parse(localStorage.getItem('preference-tableColumns-coil-report-loss') || '[]') || []
this.outputColumns = JSON.parse(localStorage.getItem('preference-tableColumns-coil-report-output') || '[]') || []
}
}
}
</script>
<style scoped>
.comparison-report { min-width: 900px; }
.filter-card { margin-bottom: 8px; }
.filter-card >>> .el-card__body { padding: 8px 12px 8px; }
.filter-form { margin-bottom: 0; }
.filter-form .el-form-item { margin-bottom: 6px; margin-right: 4px; }
.filter-form >>> .el-form-item__label { font-size: 12px; }
.time-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding-top: 6px; border-top: 1px solid #eee; }
.time-label { font-size: 12px; color: #909399; font-weight: 600; }
.current-label { color: #409eff; }
.time-sep { color: #c0c4cc; font-size: 12px; }
.quick-btns { margin-left: auto; }
.explain-alert { margin-bottom: 8px; }
.explain-alert >>> .el-alert__title { font-size: 12px; line-height: 1.5; }
.color-up { color: #f56c6c; }
.color-down { color: #67c23a; }
.card-group { margin-bottom: 10px; }
.group-title { font-size: 13px; font-weight: 600; color: #303133; padding: 5px 0; margin-bottom: 6px; border-bottom: 2px solid #409eff; }
.kpi-card { background: #fafbfc; border: 1px solid #e8eaed; border-radius: 4px; padding: 10px 12px; text-align: center; transition: box-shadow .15s; }
.kpi-card:hover { box-shadow: 0 2px 6px rgba(0,0,0,.06); }
.kpi-label { font-size: 12px; color: #909399; margin-bottom: 4px; }
.kpi-body { font-size: 15px; font-weight: 600; margin-bottom: 2px; display: flex; align-items: center; justify-content: center; gap: 6px; }
.kpi-arrow { color: #c0c4cc; font-size: 12px; }
.kpi-base { color: #909399; }
.kpi-current { color: #409eff; }
.kpi-rate { font-size: 13px; font-weight: 600; }
.detail-section { margin-top: 8px; }
.detail-section >>> .el-tabs__header { margin-bottom: 4px; }
.detail-section >>> .el-tabs__item { font-size: 12px; height: 32px; line-height: 32px; }
</style>

View File

@@ -265,6 +265,17 @@
<!-- 分条信息统计 -->
<split-summary v-if="productionLine == '分条线'" :origin-outputlist="outList" :origin-loss-list="lossList"
:common-coil-ids="commonCoilIds"></split-summary>
<el-descriptions title="尺寸异常统计" :column="4" border>
<el-descriptions-item label="长度异常卷数">{{ sizeAbnormalSummary.length.count }}</el-descriptions-item>
<el-descriptions-item label="长度异常总重">{{ sizeAbnormalSummary.length.weight }}t</el-descriptions-item>
<el-descriptions-item label="长度异常数量占比">{{ sizeAbnormalSummary.length.countRate }}</el-descriptions-item>
<el-descriptions-item label="长度异常重量占比">{{ sizeAbnormalSummary.length.weightRate }}</el-descriptions-item>
<el-descriptions-item label="厚度异常卷数">{{ sizeAbnormalSummary.thickness.count }}</el-descriptions-item>
<el-descriptions-item label="厚度异常总重">{{ sizeAbnormalSummary.thickness.weight }}t</el-descriptions-item>
<el-descriptions-item label="厚度异常数量占比">{{ sizeAbnormalSummary.thickness.countRate }}</el-descriptions-item>
<el-descriptions-item label="厚度异常重量占比">{{ sizeAbnormalSummary.thickness.weightRate }}</el-descriptions-item>
</el-descriptions>
</template>
<!-- team, day, month, year 类型的统计信息 -->
@@ -344,6 +355,17 @@
<div ref="monthChart" style="width: 100%; height: 350px;"></div>
</el-card>
</div>
<el-descriptions title="尺寸异常统计" :column="4" border>
<el-descriptions-item label="长度异常卷数">{{ sizeAbnormalSummary.length.count }}</el-descriptions-item>
<el-descriptions-item label="长度异常总重">{{ sizeAbnormalSummary.length.weight }}t</el-descriptions-item>
<el-descriptions-item label="长度异常数量占比">{{ sizeAbnormalSummary.length.countRate }}</el-descriptions-item>
<el-descriptions-item label="长度异常重量占比">{{ sizeAbnormalSummary.length.weightRate }}</el-descriptions-item>
<el-descriptions-item label="厚度异常卷数">{{ sizeAbnormalSummary.thickness.count }}</el-descriptions-item>
<el-descriptions-item label="厚度异常总重">{{ sizeAbnormalSummary.thickness.weight }}t</el-descriptions-item>
<el-descriptions-item label="厚度异常数量占比">{{ sizeAbnormalSummary.thickness.countRate }}</el-descriptions-item>
<el-descriptions-item label="厚度异常重量占比">{{ sizeAbnormalSummary.thickness.weightRate }}</el-descriptions-item>
</el-descriptions>
</template>
<!-- 明细信息和标签页 -->
@@ -547,7 +569,9 @@ export default {
],
lossColumns: [],
outputColumns: [],
actionIds: ''
actionIds: '',
lengthThreshold: 0,
thicknessThreshold: 0
}
},
computed: {
@@ -610,6 +634,41 @@ export default {
customExportStorageKey() {
return `coil-report-action-${this.actionType}`
},
sizeAbnormalSummary() {
const totalCount = this.outList.length
const totalWeight = this.outList.reduce((acc, cur) => acc + (parseFloat(cur.netWeight) || 0), 0)
const lengthAbnormal = this.outList.filter(row => {
const lengthDiff = (row.actualLength || 0) - (row.theoreticalLength || 0)
const theoreticalLength = row.theoreticalLength || 1
return Math.abs(lengthDiff) / theoreticalLength > this.lengthThreshold
})
const thicknessAbnormal = this.outList.filter(row => {
const thicknessDiff = (row.theoreticalThickness || 0) - (row.computedThickness || 0)
return thicknessDiff > this.thicknessThreshold
})
const laCount = lengthAbnormal.length
const laWeight = lengthAbnormal.reduce((acc, cur) => acc + (parseFloat(cur.netWeight) || 0), 0)
const taCount = thicknessAbnormal.length
const taWeight = thicknessAbnormal.reduce((acc, cur) => acc + (parseFloat(cur.netWeight) || 0), 0)
return {
length: {
count: laCount,
weight: laWeight.toFixed(2),
countRate: totalCount > 0 ? (laCount / totalCount * 100).toFixed(2) + '%' : '0.00%',
weightRate: totalWeight > 0 ? (laWeight / totalWeight * 100).toFixed(2) + '%' : '0.00%',
},
thickness: {
count: taCount,
weight: taWeight.toFixed(2),
countRate: totalCount > 0 ? (taCount / totalCount * 100).toFixed(2) + '%' : '0.00%',
weightRate: totalWeight > 0 ? (taWeight / totalWeight * 100).toFixed(2) + '%' : '0.00%',
}
}
},
},
watch: {
warehouseOptions: {
@@ -625,6 +684,7 @@ export default {
this.initDateByReportType()
this.handleQuery()
this.loadColumns()
this.getAlarmThreshold()
},
methods: {
initDateByReportType() {
@@ -1102,6 +1162,10 @@ export default {
this.loading = false
})
},
getAlarmThreshold() {
this.getConfigKey('material.warning.length').then(response => { this.lengthThreshold = parseFloat(response.msg) || 0 })
this.getConfigKey('material.warning.thickness').then(response => { this.thicknessThreshold = parseFloat(response.msg) || 0 })
},
loadColumns() {
this.lossColumns = JSON.parse(localStorage.getItem('preference-tableColumns-coil-report-loss') || '[]') || []
this.outputColumns = JSON.parse(localStorage.getItem('preference-tableColumns-coil-report-output') || '[]') || []