Files
yudao-dev/src/views/quality/qualityIsra/defectVisualization.vue
‘937886381’ b9f286005c 生产
2025-12-09 13:07:09 +08:00

675 lines
24 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="status-timegraph-container" style="background: #f2f4f9; flex: 1; display: flex; flex-direction: column">
<el-row class="" style="
height: 1px;
flex: 1;
margin-bottom: 12px;
background: #fff;
padding: 16px 16px 32px;
border-radius: 8px;
display: flex;
flex-direction: column;
">
<el-row :gutter="20">
<el-col :span="24">
<div class="blue-title">
图表时间维度
<div style="position: relative; display: flex; align-items: center; gap: 10px; margin-top: 10px;">
<el-button type="primary" @click="handleExport"
style="position: absolute;top: 0px;right: 10px;z-index: 9999;">
导出
</el-button>
<el-tabs v-model="activeName" type="card" @tab-click="handleClick" style="flex: 1;">
<!-- 班组标签页 -->
<el-tab-pane label="班组" name="group">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapGroupMain" class="chart-container" style="height: 400px;"></div>
<div id="mapGroupMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listGroupMain" class="chart-container" style="height: 400px;"></div>
<div id="listGroupMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
<!-- 日标签页 -->
<el-tab-pane label="日" name="day">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapDayMain" class="chart-container" style="height: 400px;"></div>
<div id="mapDayMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listDayMain" class="chart-container" style="height: 400px;"></div>
<div id="listDayMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
<!-- 周标签页 -->
<el-tab-pane label="周" name="week">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapWeekMain" class="chart-container" style="height: 400px;"></div>
<div id="mapWeekMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listWeekMain" class="chart-container" style="height: 400px;"></div>
<div id="listWeekMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
<!-- 月标签页 -->
<el-tab-pane label="月" name="month">
<!-- 修改使用四宫格布局容器 -->
<div class="chart-grid">
<div id="mapMonthMain" class="chart-container" style="height: 400px;"></div>
<div id="mapMonthMainScrap" class="chart-container" style="height: 400px;"></div>
<div id="listMonthMain" class="chart-container" style="height: 400px;"></div>
<div id="listMonthMainScrap" class="chart-container" style="height: 400px;"></div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</el-col>
</el-row>
</el-row>
</div>
</template>
<script>
import {
getQualityIsraPage,
} from '@/api/monitoring/qualityIsra';
import moment from 'moment';
import * as echarts from 'echarts';
import tableHeightMixin from '@/mixins/lb/tableHeightMixin';
import { parseTime } from '@/utils/ruoyi';
import { getPdList } from '@/api/core/monitoring/auto';
import html2canvas from 'html2canvas';
export default {
name: 'QualityIsra',
mixins: [tableHeightMixin],
data() {
return {
isFold: false,
list: [],
dynamicProps: [],
activeName: 'group', // 默认激活“日”标签页
// 新增班组图表接口URL与后端确认一致
groupMapUrl: '/extend/check-isra-statistics/groupMap',
groupListUrl: '/extend/check-isra-statistics/groupList',
// 原有接口URL保持不变
dayMapUrl: '/extend/check-isra-statistics/dayMap',
weekMapUrl: '/extend/check-isra-statistics/weekMap',
monthMapUrl: '/extend/check-isra-statistics/monthMap',
dayListUrl: '/extend/check-isra-statistics/dayList',
weekListUrl: '/extend/check-isra-statistics/weekList',
monthListUrl: '/extend/check-isra-statistics/monthList',
searchBarFormConfig: [
{
type: 'select',
label: '是否报废',
placeholder: '请选择是否报废',
param: 'checkDiscard',
selectOptions: [
{ name: '否', id: 0 },
{ name: '是', id: 1 }
],
},
{
type: 'select',
label: '缺陷类型',
placeholder: '请选择缺陷类型',
param: 'checkType',
selectOptions: [],
labelField: 'name',
valueField: 'name',
defaultSelect: [],
filterable: true
},
{
type: 'select',
label: '产线',
selectOptions: [],
labelField: 'name',
valueField: 'name',
param: 'lineName',
filterable: true,
defaultSelect: []
},
{
type: 'datePicker',
label: '时间段',
dateType: 'datetimerange',
format: 'yyyy-MM-dd HH:mm:ss',
valueFormat: 'yyyy-MM-dd HH:mm:ss',
rangeSeparator: '-',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
defaultTime: ['00:00:00', '23:59:59'],
param: 'checkTime',
},
{
type: 'button',
btnName: '查询',
name: 'search',
color: 'primary',
},
],
// 查询参数
queryParams: {
checkDiscard: undefined,
// checkType: undefined,
// lineName: undefined,
startTime: undefined,
endTime: undefined,
},
};
},
created() {
// 初始化折叠状态(如需使用可取消注释)
// this.isFold = this.searchBarWidth('QualityIsraBox', 1198);
},
computed: {
tableProps() {
return [
{
prop: 'checkType',
label: '缺陷类型',
},
{
prop: 'sumNum',
label: '缺陷总数',
},
...this.dynamicProps,
];
},
},
mounted() {
// 从路由参数获取产线信息并回显
if (this.$route.query.lineName) {
this.queryParams.lineName = this.$route.query.lineName;
this.searchBarFormConfig[2].defaultSelect = this.$route.query.lineName;
}
// 从路由参数获取时间信息并回显
if (this.$route.query.originalGlassOutputTime) {
const time = new Date(Number(this.$route.query.originalGlassOutputTime));
this.queryParams.startTime = parseTime(time);
this.queryParams.endTime = parseTime(time);
this.searchBarFormConfig[3].defaultSelect = [
this.queryParams.startTime,
this.queryParams.endTime,
];
}
// 初始化数据
this.getData();
this.getDict();
},
methods: {
/** 标签页切换事件 */
handleClick() {
this.getData();
},
/** 核心:获取并渲染图表数据(支持班组/日/周/月) */
getData() {
// 1. 根据当前激活的标签页选择对应的接口URL和图表容器ID
let mapUrl, listUrl, mapDomId, listDomId, mapDomIdScrap, listDomIdScrap;
switch (this.activeName) {
case 'group':
mapUrl = this.groupMapUrl;
listUrl = this.groupListUrl;
mapDomId = 'mapGroupMain';
listDomId = 'listGroupMain';
mapDomIdScrap = 'mapGroupMainScrap';
listDomIdScrap = 'listGroupMainScrap';
break;
case 'day':
mapUrl = this.dayMapUrl;
listUrl = this.dayListUrl;
mapDomId = 'mapDayMain';
listDomId = 'listDayMain';
mapDomIdScrap = 'mapGroupDayScrap';
listDomIdScrap = 'listGroupDayScrap';
break;
case 'week':
mapUrl = this.weekMapUrl;
listUrl = this.weekListUrl;
mapDomId = 'mapWeekMain';
listDomId = 'listWeekMain';
mapDomIdScrap = 'mapGroupWeekScrap';
listDomIdScrap = 'listGroupWeekScrap';
break;
case 'month':
mapUrl = this.monthMapUrl;
listUrl = this.monthListUrl;
mapDomId = 'mapMonthMain';
listDomId = 'listMonthMain';
mapDomIdScrap = 'mapGroupMonthScrap';
listDomIdScrap = 'listGroupMonthScrap';
break;
}
// 2. 请求“各类型缺陷对比图”数据
this.$axios({
url: mapUrl,
method: 'get',
params: this.queryParams
}).then((res) => {
const mapArr = [];
const mapLegendData = [];
// 处理接口返回数据(确保与后端数据格式匹配)
for (const type in res.data) {
const dataArr = [];
const xAxisData = [];
res.data[type].forEach(ele => {
dataArr.push(ele.num); // 缺陷数量
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
xAxisData.push(ele.checkTime);
});
mapArr.push({
name: type, // 缺陷类型名称
type: 'line', // 折线图
data: dataArr,
xAxisData: xAxisData // 存储当前系列的X轴数据
});
mapLegendData.push(type);
}
// 渲染“各类型缺陷对比图”
const mapChartDom = document.getElementById(mapDomId);
if (mapChartDom) {
const myChart = echarts.init(mapChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷对比图' },
tooltip: { trigger: 'axis' },
legend: { data: mapLegendData, top: '10%', y: 'top', x: 'left' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: mapArr.length > 0 ? mapArr[0].xAxisData : [] // 使用第一个系列的X轴数据
},
yAxis: { type: 'value', name: '缺陷数量' },
series: mapArr
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
this.$axios({
url: mapUrl,
method: 'get',
params: {
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
checkDiscard:true
}
}).then((res) => {
const mapArr = [];
const mapLegendData = [];
// 处理接口返回数据(确保与后端数据格式匹配)
for (const type in res.data) {
const dataArr = [];
const xAxisData = [];
res.data[type].forEach(ele => {
dataArr.push(ele.num); // 缺陷数量
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
xAxisData.push(ele.checkTime);
});
mapArr.push({
name: type, // 缺陷类型名称
type: 'line', // 折线图
data: dataArr,
xAxisData: xAxisData // 存储当前系列的X轴数据
});
mapLegendData.push(type);
}
// 渲染“各类型缺陷对比图”
const mapChartDom = document.getElementById(mapDomIdScrap);
if (mapChartDom) {
const myChart = echarts.init(mapChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷对比图' },
tooltip: { trigger: 'axis' },
legend: { data: mapLegendData, top: '10%', y: 'top', x: 'left' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: mapArr.length > 0 ? mapArr[0].xAxisData : [] // 使用第一个系列的X轴数据
},
yAxis: { type: 'value', name: '缺陷数量' },
series: mapArr
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
// 3. 请求“缺陷率趋势图”数据
this.$axios({
url: listUrl,
method: 'get',
params: this.queryParams
}).then((res) => {
const listNumArr = []; // 缺陷数量
const listRatioArr = []; // 缺陷率
const listXAxisData = []; // X轴数据
// 处理接口返回数据(确保与后端数据格式匹配)
res.data.forEach(ele => {
listNumArr.push(ele.num);
listRatioArr.push(ele.ratio); // 缺陷率(百分比)
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
listXAxisData.push(this.activeName === 'group' ? ele.groupName : ele.checkTime);
});
// 渲染“缺陷率趋势图”
const listChartDom = document.getElementById(listDomId);
if (listChartDom) {
const myChart = echarts.init(listChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷率趋势图' },
tooltip: { trigger: 'axis' },
legend: { data: ['缺陷数量', '缺陷率'], top: '10%' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: listXAxisData
},
yAxis: [
{
type: 'value',
name: '缺陷数量',
axisLabel: { formatter: '{value}' }
},
{
type: 'value',
name: '缺陷率',
axisLabel: { formatter: '{value} %' },
position: 'right' // 缺陷率Y轴放在右侧
}
],
series: [
{
name: '缺陷数量',
type: 'bar',
barWidth: '3%',
data: listNumArr,
itemStyle: { color: 'rgba(40, 138, 255, 1)' } // 柱状图颜色
},
{
name: '缺陷率',
type: 'line',
yAxisIndex: 1, // 关联右侧Y轴
data: listRatioArr,
itemStyle: { color: 'rgba(115, 222, 147, 1)' }, // 折线图颜色
symbol: 'circle', // 标记点样式
symbolSize: 6
}
]
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
this.$axios({
url: listUrl,
method: 'get',
params: {
startTime: this.queryParams.startTime,
endTime: this.queryParams.endTime,
checkDiscard: true
}
}).then((res) => {
const listNumArr = []; // 缺陷数量
const listRatioArr = []; // 缺陷率
const listXAxisData = []; // X轴数据
// 处理接口返回数据(确保与后端数据格式匹配)
res.data.forEach(ele => {
listNumArr.push(ele.num);
listRatioArr.push(ele.ratio); // 缺陷率(百分比)
// 班组标签页使用“班组名称”作为X轴其他标签页使用“时间”
listXAxisData.push(this.activeName === 'group' ? ele.groupName : ele.checkTime);
});
// 渲染“缺陷率趋势图”
const listChartDom = document.getElementById(listDomIdScrap);
if (listChartDom) {
const myChart = echarts.init(listChartDom);
myChart.clear(); // 清除原有图表
myChart.setOption({
title: { text: '所有缺陷率趋势图' },
tooltip: { trigger: 'axis' },
legend: { data: ['缺陷数量', '缺陷率'], top: '10%' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '20%', containLabel: true },
xAxis: {
type: 'category',
data: listXAxisData
},
yAxis: [
{
type: 'value',
name: '缺陷数量',
axisLabel: { formatter: '{value}' }
},
{
type: 'value',
name: '缺陷率',
axisLabel: { formatter: '{value} %' },
position: 'right' // 缺陷率Y轴放在右侧
}
],
series: [
{
name: '缺陷数量',
type: 'bar',
barWidth: '3%',
data: listNumArr,
itemStyle: { color: 'rgba(40, 138, 255, 1)' } // 柱状图颜色
},
{
name: '缺陷率',
type: 'line',
yAxisIndex: 1, // 关联右侧Y轴
data: listRatioArr,
itemStyle: { color: 'rgba(115, 222, 147, 1)' }, // 折线图颜色
symbol: 'circle', // 标记点样式
symbolSize: 6
}
]
});
// 监听窗口 resize自动调整图表大小
window.addEventListener('resize', () => myChart.resize());
}
});
},
/** 新增:导出数据功能 */
handleExport() {
// 1. 找到当前激活标签页内的 .chart-wrapper 容器
const container = document.querySelector(`.el-tab-pane[aria-selected="true"] .chart-wrapper`);
if (!container) {
this.$message.warning('未找到图表容器');
console.error('导出失败:未找到 .chart-wrapper 容器');
return;
}
this.$message.info('正在生成图片,请稍候...');
// 2. 获取当前标签页的两个图表实例,并强制刷新它们
// 这是解决问题的关键步骤
const chartIds = this.getChartIdsByActiveName();
chartIds.forEach(id => {
const chartDom = document.getElementById(id);
if (chartDom) {
const chartInstance = echarts.getInstanceByDom(chartDom);
if (chartInstance) {
chartInstance.resize(); // 强制图表重新渲染
}
}
});
// 3. 使用 setTimeout 等待图表渲染完成后再进行截图
// 延迟时间可以根据你的图表复杂度调整,一般 100-300ms 足够
setTimeout(() => {
html2canvas(container, {
scale: 2,
useCORS: true,
logging: false,
backgroundColor: null,
// 增加一个配置,确保能捕获到所有元素
windowWidth: container.scrollWidth,
windowHeight: container.scrollHeight
}).then(canvas => {
const imgBase64 = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = imgBase64;
const titleMap = { group: '班组', day: '日', week: '周', month: '月' };
const title = titleMap[this.activeName] || '数据';
link.download = `${title}度缺陷分析报告.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.$message.success('图表导出成功!');
}).catch(error => {
this.$message.error('图表导出失败!');
console.error('html2canvas 截图失败:', error);
});
}, 200); // 等待 200 毫秒
},
// 新增一个辅助方法,根据当前激活的标签页获取对应的图表 ID
getChartIdsByActiveName() {
switch (this.activeName) {
case 'group':
return ['mapGroupMain', 'listGroupMain'];
case 'day':
return ['mapDayMain', 'listDayMain'];
case 'week':
return ['mapWeekMain', 'listWeekMain'];
case 'month':
return ['mapMonthMain', 'listMonthMain'];
default:
return [];
}
},
/** 获取搜索栏的字典数据(缺陷类型、产线列表) */
async getDict() {
// 获取缺陷类型列表
const defectRes = await this.$axios({
url: '/extend/check-isra-standards/page',
method: 'get',
params: { pageSize: 100, pageNo: 1 }
});
this.searchBarFormConfig[1].selectOptions = defectRes.data.list || [];
// 获取产线列表
const lineRes = await getPdList();
this.searchBarFormConfig[2].selectOptions = lineRes.data || [];
},
/** 查询列表数据(如需使用表格可取消注释) */
// getList() {
// this.getDataList();
// },
/** 表格数据查询(如需使用表格可取消注释) */
// async getDataList() {
// this.loading = true;
// try {
// const { data } = await getQualityIsraPage(this.queryParams);
// this.dynamicProps = this.filterNameData(data.nameData);
// this.list = this.filterData(data.data);
// } catch (error) {
// console.error('获取表格数据失败:', error);
// } finally {
// this.loading = false;
// }
// },
/** 过滤动态表格列名(如需使用表格可取消注释) */
// filterNameData(nameData) {
// const nameSet = new Set(nameData.map(nd => nd.name));
// return Array.from(nameSet).sort().map(name => ({ prop: name, label: name }));
// },
/** 过滤表格数据(如需使用表格可取消注释) */
// filterData(data) {
// return data.map(item => {
// const keyValuePairs = {};
// item.data.forEach(d => {
// keyValuePairs[d.dynamicName] = d.dynamicValue;
// });
// return { ...keyValuePairs, sumNum: item.sumNum, checkType: item.checkType };
// });
// },
/** 搜索按钮点击事件(如需使用搜索栏可取消注释) */
// handleSearchBarBtnClick(val) {
// if (val.btnName === 'search') {
// this.queryParams.checkDiscard = (val?.checkDiscard === 0 || val?.checkDiscard === 1) ? val?.checkDiscard : undefined;
// this.queryParams.lineName = val.lineName ? val.lineName : undefined;
// this.queryParams.checkType = val.checkType ? val.checkType : undefined;
// this.queryParams.startTime = val.checkTime ? val.checkTime[0] : undefined;
// this.queryParams.endTime = val.checkTime ? val.checkTime[1] : undefined;
// this.getList();
// this.getData();
// }
// }
},
};
</script>
<style lang="scss">
.blue-title {
position: relative;
padding: 4px 0;
padding-left: 12px;
font-size: 14px;
color: #606266;
font-weight: 700;
margin-bottom: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 6px;
height: 16px;
width: 4px;
border-radius: 1px;
background: #0b58ff;
}
}
/* 新增:四宫格布局样式 */
.chart-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
/* 创建两列,宽度相等 */
grid-template-rows: repeat(2, auto);
/* 创建两行,高度自适应内容 */
gap: 20px;
/* 图表之间的间距 */
width: 100%;
}
.chart-container {
width: 100%;
border: 1px solid #f0f0f0;
border-radius: 4px;
box-sizing: border-box;
}
</style>