This commit is contained in:
‘937886381’
2025-12-09 13:07:09 +08:00
parent 9f3cdcb1c4
commit b9f286005c
43 changed files with 7142 additions and 729 deletions

View File

@@ -0,0 +1,560 @@
<template>
<div class="app-container">
<!-- 搜索工作栏 -->
<SearchBar :formConfigs="searchBarFormConfig" ref="search-bar" @headBtnClick="handleSearchBarBtnClick" />
<!-- 列表 -->
<base-table :table-props="tableProps" :page="queryParams.pageNo" :limit="queryParams.pageSize" :table-data="list"
@emitFun="handleEmitFun">
<method-btn v-if="tableBtn.length" slot="handleBtn" label="操作" :width="120" :method-list="tableBtn"
@clickBtn="handleTableBtnClick" />
</base-table>
<!-- 分页组件 -->
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
@pagination="getList" />
<div v-if="chartData" class="charts-container">
<div v-for="(lineData, lineName) in chartData" :key="lineName" class="chart-wrapper">
<div class="blue-block"></div>
<h3 class="chart-title">{{ lineName }}</h3>
<div :id="`chart-${lineName}`" class="chart" style="width: 100%; height: 300px;"></div>
</div>
</div>
<defect-summary-det ref="defectSummaryDetRef" v-if="defectVis" />
</div>
</template>
<script>
import moment from 'moment';
import {
getPdList,
} from '@/api/core/monitoring/auto';
import { getFactoryPage } from '@/api/core/base/factory';
import { getDefectSummaryTable, getDefectSummaryChart } from '@/api/monitoring/defectSummary';
import * as echarts from 'echarts';
import basicPageMixin from '@/mixins/lb/basicPageMixin';
import defectSummaryDet from './defectSummaryDet.vue'
export default {
name: 'QualityInspectionType',
mixins: [basicPageMixin],
components: {
defectSummaryDet
},
data() {
return {
tableBtn: [
this.$auth.hasPermi('base:quality-inspection-type:update')
? {
type: 'detail',
btnName: '缺陷详情',
}
: undefined,
// this.$auth.hasPermi('base:quality-inspection-type:delete')
// ? {
// type: 'delete',
// btnName: '删除',
// }
// : undefined,
].filter((v) => v),
tableProps: [
// {
// prop: 'createTime',
// label: '添加时间',
// fixed: true,
// width: 180,
// filter: (val) => moment(val).format('yyyy-MM-DD HH:mm:ss'),
// },
{ prop: 'factoryName', label: '工厂' },
{ prop: 'lineName', label: '产线' },
{ prop: 'glassNum', label: '玻璃总数' },
{
prop: 'okNum',
label: '合格品数',
},
{ prop: 'okRate', label: '合格百分比' },
{ prop: 'repairNum', label: '返修品数' },
{ prop: 'repairRate', label: '返修百分比' },
{ prop: 'ngNum', label: '废片数' },
{ prop: 'ngRate', label: '废片百分比' },
{ prop: 'oneNgNum', label: '1类缺陷玻璃数量' },
{ prop: 'twoNgNum', label: '2类缺陷玻璃数量' },
{ prop: 'threeNgNum', label: '3类缺陷玻璃数量' },
// {
// label: '操作',
// alignt: 'center',
// subcomponent: {
// render: function (h) {
// return h('div', null, [
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// h(
// 'el-button',
// {
// props: {
// icon: 'el-icon-edit',
// size: 'mini',
// type: 'text',
// },
// },
// ' 修改'
// ),
// ]);
// },
// },
// },
],
//
searchBarFormConfig: [
{
type: 'select',
label: '工厂',
selectOptions: [],
param: 'factoryId',
},
{
type: 'select',
label: '产线',
selectOptions: [],
multiple: true,
param: 'lineId',
},
// {
// type: 'input',
// label: '缺陷数>',
// // selectOptions: [],
// param: 'number',
// },
{
type: 'datePicker',
label: '时间范围',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
// valueFormat: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'timestamp',
defaultTime: ['00:00:00', '23:59:59'],
rangeSeparator: '-',
startPlaceholder: '开始时间',
endPlaceholder: '结束时间',
param: 'timeVal',
width: 350
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
// {
// type: 'button',
// btnName: '重置',
// name: 'reset',
// },
{
type: 'separate',
},
// {
// type: this.$auth.hasPermi('base:quality-inspection-type:create')
// ? 'button'
// : '',
// btnName: '新增',
// name: 'add',
// plain: true,
// color: 'success',
// },
{
type: this.$auth.hasPermi('base:quality-inspection-type:export')
? 'button'
: '',
btnName: '导出',
name: 'export',
color: 'warning',
},
],
// 表单配置
// formRows: [
// [
// {
// input: true,
// label: '检测类型名称',
// prop: 'name',
// rules: [{ required: true, message: '不能为空', trigger: 'blur' }],
// // bind: {
// // disabled: true, // some condition, like detail mode...
// // }
// },
// ],
// [{ input: true, label: '检测类型编码', prop: 'code' }],
// [{ input: true, label: '备注', prop: 'remark' }],
// ],
// 是否显示弹出层
open: false,
chartData: {},
charts: {},
defectVis:false,
// 查询参数
queryParams: {
pageNo: 1,
pageSize: 10,
lineId: undefined,
factoryId: undefined,
checkNum: undefined,
startTime: undefined,
},
// 表单参数
form: {},
};
},
// watch: {
// form: {
// handler: (val) => {
// console.log('form changed', val);
// },
// deep: true
// },
// },
created() {
this.getList();
this.getDict()
},
methods: {
getDict() {
getPdList().then(res => {
this.searchBarFormConfig[1].selectOptions = res.data || [];
});
getFactoryPage({ pageSize: 100, pageNo: 1 }).then(res => {
this.searchBarFormConfig[0].selectOptions = res.data.list || [];
});
},
/** base table related */
handleTableBtnClick({ data, type }) {
switch (type) {
case 'detail':
this.defectVis = true
this.$nextTick(() => {
this.$refs.defectSummaryDetRef.init({
factoryId:data.factoryId,
lineId: [data.lineId],
factoryName: data.factoryName,
lineName: data.lineName,
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
})
})
break;
case 'delete':
this.handleDelete(data);
break;
}
},
beforeDestroy() {
// 销毁所有 ECharts 实例
Object.values(this.charts).forEach(chart => {
if (chart && chart.dispose) {
chart.dispose();
}
});
this.charts = {};
},
/**
* 初始化单个图表
* @param {string} lineName - 产线名称
* @param {Array} lineData - 该产线的原始数据
*/
initSingleChart(lineName, lineData) {
// 如果已有实例,先销毁
if (this.charts[lineName]) {
this.charts[lineName].dispose();
}
const chartDom = document.getElementById(`chart-${lineName}`);
if (!chartDom || !lineData || lineData.length === 0) {
console.warn(`图表容器 chart-${lineName} 未找到或数据为空`);
return;
}
const myChart = echarts.init(chartDom);
// --- 动态处理数据逻辑 ---
const startTime = this.queryParams.startTime;
const endTime = this.queryParams.endTime;
if (!startTime || !endTime) {
console.error("起始时间或结束时间为空,无法计算时间差。");
return;
}
const durationDays = (endTime - startTime) / (1000 * 60 * 60 * 24);
const isHourly = durationDays < 7;
// 使用Map来保证顺序并能存储聚合后的数据和原始显示标签
const aggregatedMap = new Map();
lineData.forEach(item => {
let groupKey; // 用于分组的键
let displayLabel; // 用于显示的标签
if (isHourly) {
// --- 方法一:使用字符串分割 ---
const timePart = item.timePoint.split('T')[1];
groupKey = timePart ? timePart : item.timePoint; // 安全处理
displayLabel = item.timePoint.replace('T', ' ');
} else {
// 按天聚合:分组键和显示标签都到天
groupKey = item.timePoint.split('T')[0];
displayLabel = groupKey;
}
if (!aggregatedMap.has(groupKey)) {
aggregatedMap.set(groupKey, {
displayLabel: displayLabel,
okNum: 0,
repairNum: 0,
ngNum: 0
});
}
const aggregatedItem = aggregatedMap.get(groupKey);
aggregatedItem.okNum += item.okNum || 0;
aggregatedItem.repairNum += item.repairNum || 0;
aggregatedItem.ngNum += item.ngNum || 0;
});
// 准备 ECharts 所需的数据格式
const xAxisData = [];
const okNumData = [];
const repairNumData = [];
const ngNumData = [];
aggregatedMap.forEach(item => {
xAxisData.push(item.displayLabel);
okNumData.push(item.okNum);
repairNumData.push(item.repairNum);
ngNumData.push(item.ngNum);
});
// --- ECharts 配置项 ---
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'cross' },
},
legend: {
data: ['合格品数', '返修品数', '废片数'],
itemHeight: 2,
itemWidth: 20,
},
grid: {
left: '3%',
right: '4%',
bottom: '20%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: xAxisData,
axisLabel: {
rotate: 45,
interval: 0, // 强制显示所有标签
formatter: function (value) {
if (isHourly) {
// 从 "2025-10-30 00:00" 中提取 "00:00" 进行显示
return value.split(' ')[1];
} else {
// 天级别,显示 "MM-DD"
const dateParts = value.split('-');
return `${dateParts[1]}-${dateParts[2]}`;
}
}
}
},
yAxis: {
type: 'value',
min: 0
},
series: [
{
name: '合格品数',
type: 'line',
showSymbol: false,
data: okNumData,
itemStyle: { color: 'rgba(40, 138, 255, 1)' },
},
{
name: '返修品数',
type: 'line',
showSymbol: false,
data: repairNumData,
itemStyle: { color: 'rgba(115, 222, 147, 1)' },
},
{
name: '废片数',
type: 'line',
showSymbol: false,
data: ngNumData,
itemStyle: { color: 'rgba(255, 206, 106, 1)' },
}
]
};
myChart.setOption(option);
this.charts[lineName] = myChart;
// 监听容器大小变化
const resizeObserver = new ResizeObserver(() => {
myChart.resize();
});
resizeObserver.observe(chartDom);
myChart._resizeObserver = resizeObserver;
},
/**
* 根据获取到的 chartData 更新所有图表
*/
updateAllCharts() {
if (!this.chartData) {
return;
}
// 使用 nextTick 确保 v-for 生成的 DOM 已存在
this.$nextTick(() => {
const lineNames = Object.keys(this.chartData);
lineNames.forEach(lineName => {
this.initSingleChart(lineName, this.chartData[lineName]);
});
});
},
/** search bar related */
handleSearchBarBtnClick(btn) {
switch (btn.btnName) {
case 'search':
this.queryParams.lineId = btn.lineId ? btn.lineId : undefined
this.queryParams.factoryId = btn.factoryId ? btn.factoryId : undefined
this.queryParams.startTime = btn.timeVal ? btn.timeVal[0] : undefined
this.queryParams.endTime = btn.timeVal ? btn.timeVal[1] : undefined
this.getList();
break;
case 'add':
this.handleAdd();
break;
case 'export':
this.handleExport();
break;
// case 'reset':
// this.$refs['search-bar'].resetForm();
// this.resetQuery();
// break;
}
},
/** 查询列表 */
getList() {
this.loading = true;
// 执行查询
getDefectSummaryTable(this.queryParams).then((response) => {
this.list = response.data;
// this.total = response.data.total;
this.loading = false;
})
getDefectSummaryChart(this.queryParams).then((res) => {
console.log('res.data', res.data);
this.chartData = res.data || {};
// 手动调用更新图表的方法
this.updateAllCharts();
// // this.total = response.data.total;
// this.loading = false;
})
},
/** 表单重置 */
reset() {
this.form = {
id: undefined,
name: undefined,
code: undefined,
remark: undefined,
};
this.resetForm('form');
},
/** 导出按钮操作 */
handleExport() {
// 处理查询参数
let tables = document.querySelector('.el-table').cloneNode(true)
const fix = tables.querySelector('.el-table__fixed')
const fixRight = tables.querySelector('.el-table__fixed-right')
if (fix) {
tables.removeChild(tables.querySelector('.el-table__fixed'))
}
if (fixRight) {
tables.removeChild(tables.querySelector('.el-table__fixed-right'))
}
let exportTable = XLSX.utils.table_to_book(tables)
var exportTableOut = XLSX.write(exportTable, {
bookType: 'xlsx', bookSST: true, type: 'array'
})
// sheetjs.xlsx为导出表格的标题名称
try {
FileSaver.saveAs(new Blob([exportTableOut], {
type: 'application/octet-stream'
}), '缺陷汇总.xlsx')
} catch (e) {
if (typeof console !== 'undefined') console.log(e, exportTableOut)
}
return exportTableOut
},
},
};
</script>
<style scoped>
.charts-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.chart-wrapper {
flex: 1;
min-width: 300px;
margin-top: 10px;
/* 最小宽度,防止缩得太小 */
/* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-radius: 4px; */
/* padding: 10px; */
}
.blue-block {
display: inline-block;
float: left;
width: 4px;
height: 16px;
background-color: #0B58FF;
border-radius: 1px;
margin-right: 8px;
margin-top: 16px;
}
.chart-title {
text-align: left;
color: #333;
font-size: 16px;
margin-bottom: 10px;
}
</style>