feat(wms-report): 新增对比报表页面和尺寸异常统计功能
1. 新增comparison.vue对比报表页面,支持基期/当期数据对比、快速环比查询、明细表格查看和列配置 2. 在action.vue报表中添加尺寸异常统计模块,统计长度和厚度异常的卷数、总重及占比 3. 新增告警阈值配置获取逻辑,从系统配置中读取长度和厚度异常阈值
This commit is contained in:
385
klp-ui/src/views/wms/report/comparison.vue
Normal file
385
klp-ui/src/views/wms/report/comparison.vue
Normal 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>
|
||||
@@ -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') || '[]') || []
|
||||
|
||||
Reference in New Issue
Block a user