Files
klp-oa/klp-ui/src/views/wms/report/abnormal.vue
砂糖 12b3f0556d feat(wms/report): 新增修复报告页面和功能
feat(ems/assisted): 添加公辅消耗记录和类型管理功能

fix(eqp): 修改公辅消耗记录接口返回类型为Long

refactor(wms/report): 优化合并模板查询逻辑和统计信息展示

style(wms/report): 调整时间范围选择器组件逻辑

docs(wms/report): 更新配置文件中产线类型配置

feat(wms/report): 新增异常报告页面和功能
2026-04-30 13:57:42 +08:00

374 lines
15 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="app-container" v-loading="loading">
<el-row>
<el-form label-width="80px" inline>
<el-form-item label="时间">
<time-range-picker v-model="timeRangeParams" start-key="startTime" end-key="endTime"
:default-start-time="queryParams.startTime" :default-end-time="queryParams.endTime"
@quick-select="handleQuery" />
</el-form-item>
<el-form-item label="产线" prop="lineId">
<el-select style="width: 200px;" v-model="actionTypes" placeholder="请选择产线" clearable @change="handleQuery">
<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="入场钢卷号" prop="enterCoilNo">
<el-input style="width: 200px; display: inline-block;" v-model="queryParams.enterCoilNo"
placeholder="请输入入场钢卷号" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="当前钢卷号" prop="currentCoilNo">
<el-input style="width: 200px;" v-model="queryParams.currentCoilNo" placeholder="请输入当前钢卷号" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="产品名称" prop="itemName">
<el-input style="width: 200px;" v-model="queryParams.itemName" placeholder="请输入产品名称" clearable
@keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="规格" prop="itemSpecification">
<memo-input style="width: 200px;" v-model="queryParams.itemSpecification" storageKey="coilSpec"
placeholder="请选择规格" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="材质" prop="itemMaterial">
<muti-select style="width: 200px;" v-model="queryParams.itemMaterial" :options="dict.type.coil_material"
placeholder="请选择材质" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item label="厂家" prop="itemManufacturer">
<muti-select style="width: 200px;" v-model="queryParams.itemManufacturer"
:options="dict.type.coil_manufacturer" placeholder="请选择厂家" clearable @keyup.enter.native="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="getList">查询</el-button>
<el-button type="primary" @click="exportData">导出产出钢卷</el-button>
<!-- <el-button type="primary" @click="exportLossData">导出消耗钢卷</el-button> -->
<el-button type="primary" @click="settingVisible = true">列设置</el-button>
</el-form-item>
</el-form>
</el-row>
<el-descriptions title="统计信息" :column="3" border>
<el-descriptions-item label="产出数量">{{ summary.outCount }}</el-descriptions-item>
<el-descriptions-item label="产出总重">{{ summary.outTotalWeight }}t</el-descriptions-item>
<el-descriptions-item label="产出均重">{{ summary.outAvgWeight }}t</el-descriptions-item>
<el-descriptions-item label="消耗数量">{{ summary.lossCount }}</el-descriptions-item>
<el-descriptions-item label="消耗总重">{{ summary.lossTotalWeight }}t</el-descriptions-item>
<el-descriptions-item label="消耗均重">{{ summary.lossAvgWeight }}t</el-descriptions-item>
<el-descriptions-item label="数量差值">{{ summary.countDiff }}</el-descriptions-item>
<el-descriptions-item label="总重差值">{{ summary.weightDiff }}</el-descriptions-item>
<el-descriptions-item label="均重差值">{{ summary.avgWeightDiff }}t</el-descriptions-item>
<!-- <el-descriptions-item label="合计均重">{{ summary.totalAvgWeight }}t</el-descriptions-item> -->
<!-- 成品率 -->
<el-descriptions-item label="成品率">{{ summary.passRate }}</el-descriptions-item>
<el-descriptions-item label="损耗率">{{ summary.lossRate }}</el-descriptions-item>
</el-descriptions>
<!-- 已处理M统计信息 -->
<el-descriptions title="已处理M统计信息" :column="3" border>
<el-descriptions-item label="产出数量">{{ mSummary.outCount }}</el-descriptions-item>
<el-descriptions-item label="产出总重">{{ mSummary.outTotalWeight }}t</el-descriptions-item>
<el-descriptions-item label="产出均重">{{ mSummary.outAvgWeight }}t</el-descriptions-item>
<el-descriptions-item label="消耗数量">{{ mSummary.lossCount }}</el-descriptions-item>
<el-descriptions-item label="消耗总重">{{ mSummary.lossTotalWeight }}t</el-descriptions-item>
<el-descriptions-item label="消耗均重">{{ mSummary.lossAvgWeight }}t</el-descriptions-item>
<el-descriptions-item label="数量差值">{{ mSummary.countDiff }}</el-descriptions-item>
<el-descriptions-item label="总重差值">{{ mSummary.weightDiff }}</el-descriptions-item>
<el-descriptions-item label="均重差值">{{ mSummary.avgWeightDiff }}t</el-descriptions-item>
<!-- 成品率 -->
<el-descriptions-item label="成品率">{{ mSummary.passRate }}</el-descriptions-item>
<el-descriptions-item label="损耗率">{{ mSummary.lossRate }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="明细信息" :column="3" border>
</el-descriptions>
<el-tabs v-model="activeTab">
<el-tab-pane label="投入钢卷" name="loss">
<coil-table :data="lossList" :columns="lossColumns" :loading="loading" height="calc(100vh - 360px)" />
</el-tab-pane>
<el-tab-pane label="产出钢卷" name="output">
<coil-table :data="outList" :columns="outputColumns" :loading="loading" height="calc(100vh - 360px)" />
</el-tab-pane>
</el-tabs>
<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"></columns-setting>
</el-dialog>
</div>
</template>
<script>
import { listCoilWithIds } from "@/api/wms/coil";
import {
listPendingAction,
} from '@/api/wms/pendingAction';
import MemoInput from "@/components/MemoInput";
import MutiSelect from "@/components/MutiSelect";
import ProductInfo from "@/components/KLPService/Renderer/ProductInfo";
import RawMaterialInfo from "@/components/KLPService/Renderer/RawMaterialInfo";
import CoilNo from "@/components/KLPService/Renderer/CoilNo.vue";
import { calcSummary, calcMSummary } from "@/views/wms/report/js/calc";
import CoilTable from "@/views/wms/report/components/coilTable";
import ColumnsSetting from "@/views/wms/report/components/setting/columns";
import TimeRangePicker from "@/views/wms/report/components/timeRangePicker.vue";
export default {
name: 'MergeTemplate',
components: {
MemoInput,
MutiSelect,
ProductInfo,
RawMaterialInfo,
CoilNo,
CoilTable,
ColumnsSetting,
TimeRangePicker
},
dicts: ['product_coil_status', 'coil_material', 'coil_itemname', 'coil_manufacturer'],
data() {
// 工具函数:个位数补零
const addZero = (num) => num.toString().padStart(2, '0')
// 获取当前日期(默认选中当天)
const now = new Date()
const currentDate = `${now.getFullYear()}-${addZero(now.getMonth() + 1)}`
/**
* 生成指定日期/月份的时间范围字符串
* @param {string} dateStr - 支持格式yyyy-MM月份 或 yyyy-MM-dd具体日期
* @returns {object} 包含start开始时间和end结束时间的对象
*/
const getDayTimeRange = (dateStr) => {
// 先校验输入格式是否合法
const monthPattern = /^\d{4}-\d{2}$/; // yyyy-MM 正则
const dayPattern = /^\d{4}-\d{2}-\d{2}$/; // yyyy-MM-dd 正则
if (!monthPattern.test(dateStr) && !dayPattern.test(dateStr)) {
throw new Error('输入格式错误,请传入 yyyy-MM 或 yyyy-MM-dd 格式的字符串');
}
let startDate, endDate;
if (monthPattern.test(dateStr)) {
// 处理 yyyy-MM 格式:获取本月第一天和最后一天
const [year, month] = dateStr.split('-').map(Number);
// 月份是0基的0=1月1=2月...所以要减1
// 第一天yyyy-MM-01
startDate = `${dateStr}-01`;
// 最后一天:通过 new Date(year, month, 0) 计算month是原始月份比如2代表2月传2则取3月0日=2月最后一天
const lastDayOfMonth = new Date(year, month, 0).getDate();
endDate = `${dateStr}-${lastDayOfMonth.toString().padStart(2, '0')}`;
} else {
// 处理 yyyy-MM-dd 格式:直接使用传入的日期
startDate = dateStr;
endDate = dateStr;
}
// 拼接时间部分
return {
start: `${startDate} 00:00:00`,
end: `${endDate} 23:59:59`
};
};
const { start, end } = getDayTimeRange(currentDate)
return {
lossList: [],
outList: [],
activeTab: 'loss',
activeColumnConfig: 'coil-report-loss',
settingVisible: false,
loading: false,
timeRangeParams: {
startTime: start,
endTime: end
},
queryParams: {
startTime: start,
endTime: end,
lineId: '',
enterCoilNo: '',
currentCoilNo: '',
warehouseId: '',
itemName: '',
itemSpecification: '',
itemMaterial: '',
itemManufacturer: '',
pageSize: 9999,
pageNum: 1,
},
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.outList, this.lossList)
},
mSummary() {
return calcMSummary(this.outList, this.lossList)
},
},
watch: {
timeRangeParams: {
handler(newVal) {
this.queryParams.startTime = newVal.startTime
this.queryParams.endTime = newVal.endTime
},
deep: true,
immediate: true
}
},
created() {
this.handleQuery()
this.loadColumns()
},
methods: {
handleQuery() {
this.getList()
},
async getList() {
this.loading = true;
const actions = await listPendingAction({ ...this.queryParams, actionTypes: this.actionTypes, actionStatus: 2 });
const outIds = actions.rows.map(item => item.processedCoilIds).join(',');
if (!outIds) {
this.outList = []
this.loading = false;
return;
}
const outRes = await listCoilWithIds({ ...this.queryParams, coilIds: outIds || '', startTime: '', endTime: '', minAbnormalCount: 1 });
this.outList = outRes.rows.map(item => {
// 计算宽度和厚度,将规格按照*分割,*前的是厚度,*后的是宽度
const [thickness, width] = item.specification?.split('*') || []
return {
...item,
computedThickness: parseFloat(thickness),
computedWidth: parseFloat(width),
}
});
const lossIds = this.outList.map(item => item.parentCoilId).join(',');
if (!lossIds) {
this.lossList = []
this.loading = false;
return;
}
const lossRes = await listCoilWithIds({ ...this.queryParams, coilIds: lossIds || '', startTime: '', endTime: '' });
this.lossList = lossRes.rows.map(item => {
// 计算宽度和厚度,将规格按照*分割,*前的是厚度,*后的是宽度
const [thickness, width] = item.specification?.split('*') || []
return {
...item,
computedThickness: parseFloat(thickness),
computedWidth: parseFloat(width),
}
});
// if (!lossIds) {
// this.lossList = []
// } else {
// const lossRes = await listCoilWithIds({ ...this.queryParams, coilIds: lossIds || '', startTime: '', endTime: '' });
// this.lossList = lossRes.rows.map(item => {
// // 计算宽度和厚度,将规格按照*分割,*前的是厚度,*后的是宽度
// const [thickness, width] = item.specification?.split('*') || []
// return {
// ...item,
// computedThickness: parseFloat(thickness),
// computedWidth: parseFloat(width),
// }
// });
// }
this.loading = false;
// this.loading = true;
// const res1 = await listPendingAction({ ...this.queryParams, actionTypes: '201,202,203,204,205,206', actionStatus: 2 });
// const res = res1.rows;
// // 获取两层数据
// const lossIds = res.map(item => item.coilId);
// // 使用new Set去重
// const outIds = [...new Set(res.map(item => item.processedCoilIds))];
// if (lossIds.length === 0 || outIds.length === 0) {
// this.$message({
// message: '查询结果为空',
// type: 'warning'
// })
// this.loading = false;
// return
// }
// const [lossRes, outRes] = await Promise.all([
// listCoilWithIds({ ...this.queryParams, coilIds: lossIds.join(',') || '', startTime: '', endTime: '' }),
// listCoilWithIds({ ...this.queryParams, coilIds: outIds.join(',') || '', startTime: '', endTime: '' }),
// ]);
// this.lossList = lossRes.rows.map(item => {
// // 计算宽度和厚度,将规格按照*分割,*前的是厚度,*后的是宽度
// const [thickness, width] = item.specification?.split('*') || []
// return {
// ...item,
// computedThickness: parseFloat(thickness),
// computedWidth: parseFloat(width),
// }
// });
// this.outList = outRes.rows.map(item => {
// // 计算宽度和厚度,将规格按照*分割,*前的是厚度,*后的是宽度
// const [thickness, width] = item.specification?.split('*') || []
// return {
// ...item,
// computedThickness: parseFloat(thickness),
// computedWidth: parseFloat(width),
// }
// });
// this.loading = false;
},
// 导出
exportData() {
if (this.outList.length === 0) {
this.$message.warning('暂无数据可导出')
return
}
this.download('wms/materialCoil/export', {
coilIds: this.outList.map(item => item.coilId).join(',')
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
exportLossData() {
if (this.lossList.length === 0) {
this.$message.warning('暂无数据可导出')
return
}
this.download('wms/materialCoil/export', {
coilIds: this.lossList.map(item => item.coilId).join(',')
}, `materialCoil_${new Date().getTime()}.xlsx`)
},
// 加载列设置
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></style>