<template>
|
<div class="report-management">
|
<!-- 图表区域 -->
|
<div class="charts-container">
|
<el-row :gutter="20">
|
<!-- 各类型完成数量 -->
|
<el-col :span="9">
|
<el-card class="chart-card"
|
shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<div class="chart-title-line"></div>
|
<span>各类型完成数量</span>
|
</div>
|
</template>
|
<div class="top-container">
|
<div class="typeNum">
|
<div class="typeNum-left">
|
<img src="~@/assets/images/chartCard.svg"
|
alt="图表"
|
style="width: 40px; height: 40px; object-fit: contain;">
|
<div class="typeNum-left-text">原材料</div>
|
</div>
|
<div class="typeNum-center">
|
<div class="typeNum-leftLine">-</div>
|
<div class="typeNum-rightLine"></div>
|
</div>
|
<div class="typeNum-right">
|
<div class="typeNum-right-top">
|
<div class="typeNum-right-top-name">总数量</div>
|
<div class="typeNum-right-top-text">{{ getInspectStatValue(0, 'totalCount') }} <span class="unit">个</span></div>
|
</div>
|
<div class="typeNum-right-bottom">
|
<div class="typeNum-right-top-name">已完成数</div>
|
<div class="typeNum-right-top-text">{{ getInspectStatValue(0, 'completedCount') }} <span class="unit">个</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="typeNum">
|
<div class="typeNum-left">
|
<img src="~@/assets/images/chartCard2.svg"
|
alt="图表"
|
style="width: 40px; height: 40px; object-fit: contain;">
|
<div class="typeNum-left-text"
|
style="color: #5EB334;">半成品</div>
|
</div>
|
<div class="typeNum-center">
|
<div class="typeNum-leftLine2">-</div>
|
<div class="typeNum-rightLine2"></div>
|
</div>
|
<div class="typeNum-right">
|
<div class="typeNum-right-top">
|
<div class="typeNum-right-top-name">总数量</div>
|
<div class="typeNum-right-top-text">{{ getInspectStatValue(1, 'totalCount') }} <span class="unit">个</span></div>
|
</div>
|
<div class="typeNum-right-bottom">
|
<div class="typeNum-right-top-name">已完成数</div>
|
<div class="typeNum-right-top-text">{{ getInspectStatValue(1, 'completedCount') }} <span class="unit">个</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div class="typeNum">
|
<div class="typeNum-left">
|
<img src="~@/assets/images/chartCard3.svg"
|
alt="图表"
|
style="width: 40px; height: 40px; object-fit: contain;">
|
<div class="typeNum-left-text"
|
style="color: #8000FF;">成品</div>
|
</div>
|
<div class="typeNum-center">
|
<div class="typeNum-leftLine3">-</div>
|
<div class="typeNum-rightLine3"></div>
|
</div>
|
<div class="typeNum-right">
|
<div class="typeNum-right-top">
|
<div class="typeNum-right-top-name">总数量</div>
|
<div class="typeNum-right-top-text">{{ getInspectStatValue(2, 'totalCount') }} <span class="unit">个</span></div>
|
</div>
|
<div class="typeNum-right-bottom">
|
<div class="typeNum-right-top-name">已完成数</div>
|
<div class="typeNum-right-top-text">{{ getInspectStatValue(2, 'completedCount') }} <span class="unit">个</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<!-- 质检合格率 -->
|
<el-col :span="15">
|
<el-card class="chart-card"
|
shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<div class="chart-title-line"></div>
|
<span>质检合格率</span>
|
</div>
|
</template>
|
<div class="top-container flex-center">
|
<div class="quality-card blue-card">
|
<div class="quality-card-title">
|
<img src="~@/assets/images/chartCard.svg"
|
alt="原材料"
|
style="width: 24px; height: 24px; margin-right: 8px;">
|
原材料合格率
|
</div>
|
<div class="quality-card-content">
|
<div class="quality-item">
|
<div>
|
<div class="quality-item-label blue-label">完成率</div>
|
<div class="quality-item-tip">占比</div>
|
<div class="quality-item-value">{{ getPassRateStatValue(0, 'completionRate') }}</div>
|
</div>
|
<div class="quality-item-chart"
|
ref="materialCompletionChart"></div>
|
</div>
|
<div class="quality-item">
|
<div>
|
<div class="quality-item-label green-label">合格率</div>
|
<div class="quality-item-tip">占比</div>
|
<div class="quality-item-value">{{ getPassRateStatValue(0, 'passRate') }}</div>
|
</div>
|
<div class="quality-item-chart"
|
ref="materialQualityChart"></div>
|
</div>
|
</div>
|
</div>
|
<div class="quality-card green-card">
|
<div class="quality-card-title">
|
<img src="~@/assets/images/chartCard2.svg"
|
alt="半成品"
|
style="width: 24px; height: 24px; margin-right: 8px;">
|
半成品合格率
|
</div>
|
<div class="quality-card-content">
|
<div class="quality-item">
|
<div>
|
<div class="quality-item-label blue-label">完成率</div>
|
<div class="quality-item-tip">占比</div>
|
<div class="quality-item-value">{{ getPassRateStatValue(1, 'completionRate') }}</div>
|
</div>
|
<div class="quality-item-chart"
|
ref="semiCompletionChart"></div>
|
</div>
|
<div class="quality-item">
|
<div>
|
<div class="quality-item-label green-label">合格率</div>
|
<div class="quality-item-tip">占比</div>
|
<div class="quality-item-value">{{ getPassRateStatValue(1, 'passRate') }}</div>
|
</div>
|
<div class="quality-item-chart"
|
ref="semiQualityChart"></div>
|
</div>
|
</div>
|
</div>
|
<div class="quality-card purple-card">
|
<div class="quality-card-title">
|
<img src="~@/assets/images/chartCard3.svg"
|
alt="成品"
|
style="width: 24px; height: 24px; margin-right: 8px;">
|
成品合格率
|
</div>
|
<div class="quality-card-content">
|
<div class="quality-item">
|
<div>
|
<div class="quality-item-label blue-label">完成率</div>
|
<div class="quality-item-tip">占比</div>
|
<div class="quality-item-value">{{ getPassRateStatValue(2, 'completionRate') }}</div>
|
</div>
|
<div class="quality-item-chart"
|
ref="finalCompletionChart"></div>
|
</div>
|
<div class="quality-item">
|
<div>
|
<div class="quality-item-label green-label">合格率</div>
|
<div class="quality-item-tip">占比</div>
|
<div class="quality-item-value">{{ getPassRateStatValue(2, 'passRate') }}</div>
|
</div>
|
<div class="quality-item-chart"
|
ref="finalQualityChart"></div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
<div class="charts-container">
|
<el-row :gutter="20">
|
<!-- 质检合格率 -->
|
<el-col :span="24">
|
<el-card class="chart-card"
|
shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<div class="chart-title-line"></div>
|
<span>质检合格率</span>
|
</div>
|
</template>
|
<div class="chart-container-line">
|
<div class="container-line-left">
|
<div style="height: 100%; width: 100%;"
|
ref="usageChartRef">
|
</div>
|
</div>
|
<div class="container-line-right">
|
<div style="height: 80%; width: 100%;"
|
ref="inspectionChartRef">
|
</div>
|
<div class="container-line-right-bottom">
|
<div class="inspection-chart-box">
|
<div class="chart-box-title">原材料总数</div>
|
<div class="chart-box-num">{{ getYearlyStatValue(0, 'totalCount') }}</div>
|
</div>
|
<div class="inspection-chart-box">
|
<div class="chart-box-title">半成品总数</div>
|
<div class="chart-box-num">{{ getYearlyStatValue(1, 'totalCount') }}</div>
|
</div>
|
<div class="inspection-chart-box">
|
<div class="chart-box-title">成品总数</div>
|
<div class="chart-box-num">{{ getYearlyStatValue(2, 'totalCount') }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<!-- </div> -->
|
<!-- <div ref="sampleChartRef"
|
class="chart-container"></div> -->
|
<div class="yearchange">
|
<div style="margin-right: 8px;font-size: 14px;">年份:</div>
|
<el-date-picker v-model="value3"
|
size="mini"
|
:clearable="false"
|
style="width: 60px;"
|
type="year"
|
:disabled-date="disabledDate"
|
placeholder="">
|
</el-date-picker>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
<div class="charts-container">
|
<el-row :gutter="20">
|
<!-- 样品进度图表 -->
|
<el-col :span="12">
|
<el-card class="chart-card"
|
shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<div class="chart-title-line"></div>
|
<span>质量完成明细</span>
|
</div>
|
</template>
|
<div ref="equipmentChartRef"
|
class="chart-container"></div>
|
</el-card>
|
</el-col>
|
<!-- 设备使用图表 -->
|
<el-col :span="12">
|
<el-card class="chart-card"
|
shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<div class="chart-title-line"></div>
|
<span>检测项目分类</span>
|
</div>
|
</template>
|
<div class="chart-container-line">
|
<div class="container-line2-left">
|
<div class="info-box">
|
<div class="info-box-header">项目分布</div>
|
<div class="info-line"
|
v-for="(item, index) in topParametersData.list"
|
:key="index">
|
<div class="info-icon"
|
:style="{ backgroundColor: getParameterColor(index) }"></div>
|
<div class="info-line-title">{{ item.name }}</div>
|
<div class="info-line-value1">{{ item.percentage }}%</div>
|
<div class="info-line-value2">{{ item.count }}</div>
|
</div>
|
</div>
|
</div>
|
<div ref="sampleChartRef"
|
style="height: 100%; width: 50%;"
|
class="chart-container"></div>
|
</div>
|
<!-- Tab 选择器 -->
|
<div class="tab-selector">
|
<div class="tab-item"
|
:class="{ active: activeTab === 'raw' }"
|
@click="activeTab = 'raw'">原材料</div>
|
<div class="tab-item"
|
:class="{ active: activeTab === 'semi' }"
|
@click="activeTab = 'semi'">半成品</div>
|
<div class="tab-item"
|
:class="{ active: activeTab === 'final' }"
|
@click="activeTab = 'final'">成品</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, onMounted, nextTick } from "vue";
|
import { ElMessage } from "element-plus";
|
import * as echarts from "echarts";
|
import {
|
getInspectStatistics,
|
getPassRateStatistics,
|
getMonthlyPassRateStatistics,
|
getYearlyPassRateStatistics,
|
getMonthlyCompletionDetails,
|
getTopParameters,
|
} from "@/api/reportAnalysis/qualityReport";
|
|
// 响应式数据
|
const filterForm = reactive({
|
dateRange: [],
|
reportType: "sample",
|
});
|
|
const inspectStatisticsData = ref([]);
|
const passRateStatisticsData = ref([]);
|
const monthlyPassRateData = ref([]);
|
const yearlyPassRateData = ref([]);
|
const monthlyCompletionDetailsData = ref([]);
|
const topParametersData = ref({ totalCount: 0, list: [] });
|
const activeTab = ref("raw");
|
|
const getParameterColor = index => {
|
const colors = [
|
"#165DFF",
|
"#14C9C9",
|
"#F7BA1E",
|
"#722ED1",
|
"#3491FA",
|
"#FF7D00",
|
"#F53F3F",
|
];
|
return colors[index % colors.length];
|
};
|
|
const getYearlyStatValue = (type, field) => {
|
const stat = yearlyPassRateData.value.find(item => item.inspectType === type);
|
return stat ? stat[field] : 0;
|
};
|
|
const getInspectStatValue = (type, field) => {
|
const stat = inspectStatisticsData.value.find(
|
item => item.inspectType === type
|
);
|
return stat ? stat[field] : 0;
|
};
|
|
const getPassRateStatValue = (type, field) => {
|
const stat = passRateStatisticsData.value.find(
|
item => item.inspectType === type
|
);
|
if (stat) {
|
if (field === "completionRate" || field === "passRate") {
|
return stat[field] ? Number(stat[field]).toFixed(0) + "%" : "0%";
|
}
|
return stat[field];
|
}
|
return field === "completionRate" || field === "passRate" ? "0%" : 0;
|
};
|
|
const fetchInspectStatisticsData = async () => {
|
try {
|
const res = await getInspectStatistics();
|
if (res.code === 200) {
|
inspectStatisticsData.value = res.data;
|
}
|
} catch (error) {
|
console.error("Failed to fetch inspect statistics:", error);
|
}
|
};
|
|
const fetchPassRateStatisticsData = async () => {
|
try {
|
const res = await getPassRateStatistics();
|
if (res.code === 200) {
|
passRateStatisticsData.value = res.data;
|
// 数据获取后重新初始化图表
|
initAllQualityCharts();
|
}
|
} catch (error) {
|
console.error("Failed to fetch pass rate statistics:", error);
|
}
|
};
|
|
const fetchMonthlyPassRateData = async () => {
|
try {
|
const year = value3.value.getFullYear().toString();
|
const res = await getMonthlyPassRateStatistics(year);
|
if (res.code === 200) {
|
monthlyPassRateData.value = res.data;
|
initUsageChart();
|
}
|
} catch (error) {
|
console.error("Failed to fetch monthly pass rate statistics:", error);
|
}
|
};
|
|
const fetchYearlyPassRateData = async () => {
|
try {
|
const year = value3.value.getFullYear().toString();
|
const res = await getYearlyPassRateStatistics(year);
|
if (res.code === 200) {
|
yearlyPassRateData.value = res.data;
|
initInspectionChart();
|
}
|
} catch (error) {
|
console.error("Failed to fetch yearly pass rate statistics:", error);
|
}
|
};
|
|
const fetchMonthlyCompletionDetailsData = async () => {
|
try {
|
const year = value3.value.getFullYear().toString();
|
const res = await getMonthlyCompletionDetails(year);
|
if (res.code === 200) {
|
monthlyCompletionDetailsData.value = res.data;
|
initEquipmentChart();
|
}
|
} catch (error) {
|
console.error("Failed to fetch monthly completion details:", error);
|
}
|
};
|
|
const fetchTopParametersData = async () => {
|
try {
|
const typeMap = { raw: 0, semi: 1, final: 2 };
|
const res = await getTopParameters(typeMap[activeTab.value]);
|
if (res.code === 200) {
|
topParametersData.value = res.data;
|
sumnum.value = topParametersData.value.list.reduce(
|
(acc, cur) => acc + cur.count,
|
0
|
);
|
console.log(sumnum.value);
|
initSampleChart();
|
}
|
} catch (error) {
|
console.error("Failed to fetch top parameters:", error);
|
}
|
};
|
|
const tableData = ref([]);
|
const tableLoading = ref(false);
|
const pagination = reactive({
|
currentPage: 1,
|
pageSize: 20,
|
total: 0,
|
});
|
|
// 初始化年份为当前年份(使用Date对象)
|
const currentYear = new Date().getFullYear();
|
const value3 = ref(new Date(currentYear, 0, 1));
|
|
// 限制日期选择,不允许选择今年之后的年份
|
const disabledDate = time => {
|
const currentYear = new Date().getFullYear();
|
return time.getFullYear() > currentYear;
|
};
|
const sumnum = ref(0);
|
// 监听年份变化
|
import { watch } from "vue";
|
watch(value3, () => {
|
fetchMonthlyPassRateData();
|
fetchYearlyPassRateData();
|
fetchMonthlyCompletionDetailsData();
|
});
|
|
watch(activeTab, () => {
|
fetchTopParametersData();
|
});
|
|
// 图表引用
|
const sampleChartRef = ref(null);
|
const equipmentChartRef = ref(null);
|
const inspectionChartRef = ref(null);
|
const usageChartRef = ref(null);
|
|
// 质检合格率图表引用
|
const materialCompletionChart = ref(null);
|
const materialQualityChart = ref(null);
|
const semiCompletionChart = ref(null);
|
const semiQualityChart = ref(null);
|
const finalCompletionChart = ref(null);
|
const finalQualityChart = ref(null);
|
|
// 图表实例
|
let sampleChart = null;
|
let equipmentChart = null;
|
let inspectionChart = null;
|
let usageChart = null;
|
|
// 质检合格率图表实例
|
let materialCompletionChartInstance = null;
|
let materialQualityChartInstance = null;
|
let semiCompletionChartInstance = null;
|
let semiQualityChartInstance = null;
|
let finalCompletionChartInstance = null;
|
let finalQualityChartInstance = null;
|
|
// 初始化样品进度图表
|
const initSampleChart = () => {
|
if (sampleChartRef.value) {
|
sampleChart = echarts.init(sampleChartRef.value);
|
const option = {
|
title: {
|
show: false,
|
},
|
// tooltip: {
|
// trigger: "item",
|
// formatter: "{a} <br/>{b}: {c} ({d}%)",
|
// },
|
series: [
|
{
|
name: "",
|
type: "pie",
|
radius: ["40%", "80%"],
|
avoidLabelOverlap: false,
|
label: {
|
show: true,
|
position: "center",
|
formatter: function () {
|
return `{a|检测数量}\n{b|${sumnum.value}}`;
|
},
|
disabled: true,
|
rich: {
|
a: {
|
fontSize: 14,
|
color: "#606266",
|
fontWeight: "normal",
|
lineHeight: 20,
|
},
|
b: {
|
fontSize: 20,
|
color: "#303133",
|
fontWeight: "bold",
|
lineHeight: 24,
|
padding: [5, 0, 0, 0],
|
},
|
},
|
},
|
labelLine: {
|
show: false,
|
},
|
data: topParametersData.value.list.map((item, index) => ({
|
value: item.count,
|
name: item.name,
|
itemStyle: { color: getParameterColor(index) },
|
})),
|
},
|
],
|
};
|
sampleChart.setOption(option);
|
}
|
};
|
|
// 初始化设备使用图表
|
const initEquipmentChart = () => {
|
if (equipmentChartRef.value) {
|
equipmentChart = echarts.init(equipmentChartRef.value);
|
const option = {
|
title: {
|
show: false,
|
},
|
tooltip: {
|
trigger: "axis",
|
axisPointer: {
|
type: "shadow",
|
},
|
},
|
grid: {
|
left: "1%",
|
right: "1%",
|
bottom: "1%",
|
containLabel: true,
|
},
|
legend: {
|
data: ["原材料", "半成品", "成品"], // 图例数据
|
icon: ["circle", "circle", "circle"],
|
itemWidth: 10, // 设置图标宽度
|
itemHeight: 10,
|
itemGap: 30,
|
right: 10,
|
},
|
xAxis: {
|
type: "category",
|
data: [
|
value3.value.getFullYear() + "-1",
|
value3.value.getFullYear() + "-2",
|
value3.value.getFullYear() + "-3",
|
value3.value.getFullYear() + "-4",
|
value3.value.getFullYear() + "-5",
|
value3.value.getFullYear() + "-6",
|
value3.value.getFullYear() + "-7",
|
value3.value.getFullYear() + "-8",
|
value3.value.getFullYear() + "-9",
|
value3.value.getFullYear() + "-10",
|
value3.value.getFullYear() + "-11",
|
value3.value.getFullYear() + "-12",
|
], // 改为十二个月
|
},
|
yAxis: {
|
type: "value",
|
name: "数(个)",
|
},
|
series: [
|
{
|
name: "原材料",
|
type: "bar",
|
barWidth: "15%",
|
data: monthlyCompletionDetailsData.value.map(
|
item => item.rawMaterialCount
|
),
|
itemStyle: {
|
color: "#409EFF",
|
},
|
},
|
{
|
name: "半成品",
|
type: "bar",
|
barWidth: "15%",
|
|
data: monthlyCompletionDetailsData.value.map(
|
item => item.processCount
|
),
|
itemStyle: {
|
color: "#67C23A",
|
},
|
},
|
{
|
name: "成品",
|
type: "bar",
|
barWidth: "15%",
|
|
data: monthlyCompletionDetailsData.value.map(
|
item => item.outgoingCount
|
),
|
itemStyle: {
|
color: "#E6A23C",
|
},
|
},
|
],
|
};
|
equipmentChart.setOption(option);
|
}
|
};
|
|
// 初始化检测项目图表
|
const initInspectionChart = () => {
|
if (inspectionChartRef.value) {
|
inspectionChart = echarts.init(inspectionChartRef.value);
|
const option = {
|
title: {
|
show: false,
|
},
|
tooltip: {
|
trigger: "item",
|
},
|
series: [
|
{
|
type: "pie",
|
radius: "70%",
|
data: [
|
{
|
value: getYearlyStatValue(0, "totalCount"),
|
name: "原材料",
|
itemStyle: { color: "#1890FF" },
|
},
|
{
|
value: getYearlyStatValue(1, "totalCount"),
|
name: "半成品",
|
itemStyle: { color: "#F7BA1E" },
|
},
|
{
|
value: getYearlyStatValue(2, "totalCount"),
|
name: "成品",
|
itemStyle: { color: "#14C9C9" },
|
},
|
],
|
label: {
|
show: true,
|
formatter: "{b}: {c}",
|
},
|
emphasis: {
|
itemStyle: {
|
shadowBlur: 10,
|
shadowOffsetX: 0,
|
shadowColor: "rgba(0, 0, 0, 0.5)",
|
},
|
},
|
},
|
],
|
};
|
inspectionChart.setOption(option);
|
}
|
};
|
|
// 初始化领用记录图表
|
const initUsageChart = () => {
|
// 检查图表容器是否存在
|
if (usageChartRef.value) {
|
// 初始化 ECharts 实例
|
usageChart = echarts.init(usageChartRef.value);
|
// 配置图表选项
|
const option = {
|
// 标题配置(隐藏)
|
title: {
|
show: false,
|
},
|
|
// 网格配置(调整边距)
|
grid: {
|
left: "1%",
|
right: "4%",
|
bottom: "3%",
|
top: "14%",
|
containLabel: true,
|
},
|
// 提示框配置
|
tooltip: {
|
trigger: "axis", // 触发类型为坐标轴触发
|
},
|
// 图例配置
|
legend: {
|
data: ["原材料", "半成品", "成品"], // 图例数据
|
icon: ["circle", "circle", "circle"],
|
itemWidth: 10, // 设置图标宽度
|
itemHeight: 10,
|
itemGap: 30,
|
},
|
// X轴配置
|
xAxis: {
|
type: "category", // 类别轴
|
boundaryGap: false, // 坐标轴两边留白策略
|
data: [
|
value3.value.getFullYear() + "-1",
|
value3.value.getFullYear() + "-2",
|
value3.value.getFullYear() + "-3",
|
value3.value.getFullYear() + "-4",
|
value3.value.getFullYear() + "-5",
|
value3.value.getFullYear() + "-6",
|
value3.value.getFullYear() + "-7",
|
value3.value.getFullYear() + "-8",
|
value3.value.getFullYear() + "-9",
|
value3.value.getFullYear() + "-10",
|
value3.value.getFullYear() + "-11",
|
value3.value.getFullYear() + "-12",
|
], // X轴数据
|
},
|
// Y轴配置
|
yAxis: {
|
type: "value", // 数值轴
|
name: "单位:%",
|
},
|
// 系列数据
|
series: [
|
{
|
name: "原材料", // 系列名称
|
type: "line", // 图表类型为折线图
|
// stack: "Total", // 堆叠名称
|
symbol: "circle",
|
itemStyle: {
|
color: "#1890FF", // 设置这条线的颜色
|
},
|
data: monthlyPassRateData.value.map(
|
item => item.rawMaterial.passRate
|
),
|
},
|
{
|
name: "半成品", // 系列名称
|
type: "line", // 图表类型为折线图
|
// stack: "Total", // 堆叠名称
|
symbol: "circle",
|
itemStyle: {
|
color: "#F7BA1E", // 设置这条线的颜色
|
},
|
data: monthlyPassRateData.value.map(item => item.process.passRate),
|
},
|
{
|
name: "成品", // 系列名称
|
type: "line", // 图表类型为折线图
|
// stack: "Total", // 堆叠名称
|
symbol: "circle",
|
itemStyle: {
|
color: "#14C9C9", // 设置这条线的颜色
|
},
|
data: monthlyPassRateData.value.map(item => item.outgoing.passRate),
|
},
|
],
|
};
|
// 将配置应用到图表
|
usageChart.setOption(option);
|
}
|
};
|
|
// 初始化质检合格率图表
|
const initQualityChart = (chartRef, color, value = 0.8) => {
|
if (chartRef.value) {
|
let chart = echarts.getInstanceByDom(chartRef.value);
|
if (!chart) {
|
chart = echarts.init(chartRef.value);
|
}
|
const numericValue =
|
typeof value === "string" ? parseFloat(value) / 100 : value / 100;
|
const option = {
|
series: [
|
{
|
type: "pie",
|
radius: ["45%", "90%"],
|
itemStyle: {
|
borderColor: "#f5f5f5",
|
// borderWidth: 2,
|
},
|
labelLine: {
|
show: false,
|
},
|
data: [
|
{ value: numericValue, itemStyle: { color: color } },
|
{ value: 1 - numericValue, itemStyle: { color: "#f5f5f5" } },
|
],
|
},
|
],
|
};
|
chart.setOption(option);
|
return chart;
|
}
|
return null;
|
};
|
|
// 初始化所有质检合格率图表
|
const initAllQualityCharts = () => {
|
materialCompletionChartInstance = initQualityChart(
|
materialCompletionChart,
|
"#1890ff",
|
getPassRateStatValue(0, "completionRate")
|
);
|
materialQualityChartInstance = initQualityChart(
|
materialQualityChart,
|
"#52c41a",
|
getPassRateStatValue(0, "passRate")
|
);
|
semiCompletionChartInstance = initQualityChart(
|
semiCompletionChart,
|
"#1890ff",
|
getPassRateStatValue(1, "completionRate")
|
);
|
semiQualityChartInstance = initQualityChart(
|
semiQualityChart,
|
"#52c41a",
|
getPassRateStatValue(1, "passRate")
|
);
|
finalCompletionChartInstance = initQualityChart(
|
finalCompletionChart,
|
"#1890ff",
|
getPassRateStatValue(2, "completionRate")
|
);
|
finalQualityChartInstance = initQualityChart(
|
finalQualityChart,
|
"#722ed1",
|
getPassRateStatValue(2, "passRate")
|
);
|
};
|
|
// 事件处理函数
|
const handleFilterChange = () => {
|
ElMessage.success("筛选条件已更新");
|
// 这里可以根据筛选条件重新加载数据
|
};
|
|
const resetFilter = () => {
|
filterForm.dateRange = [];
|
filterForm.reportType = "sample";
|
ElMessage.info("筛选条件已重置");
|
};
|
|
const exportReport = () => {
|
ElMessage.success("报表导出功能开发中...");
|
};
|
|
const refreshSampleChart = () => {
|
initSampleChart();
|
ElMessage.success("样品进度图表已刷新");
|
};
|
|
const refreshEquipmentChart = () => {
|
initEquipmentChart();
|
ElMessage.success("设备使用图表已刷新");
|
};
|
|
const refreshInspectionChart = () => {
|
initInspectionChart();
|
ElMessage.success("检测项目图表已刷新");
|
};
|
|
const refreshUsageChart = () => {
|
initUsageChart();
|
ElMessage.success("领用记录图表已刷新");
|
};
|
|
const refreshTable = () => {
|
tableLoading.value = true;
|
setTimeout(() => {
|
tableLoading.value = false;
|
ElMessage.success("表格数据已刷新");
|
}, 1000);
|
};
|
|
const exportTable = () => {
|
ElMessage.success("表格导出功能开发中...");
|
};
|
|
const handleSizeChange = val => {
|
pagination.pageSize = val;
|
// 重新加载数据
|
};
|
|
const handleCurrentChange = val => {
|
pagination.currentPage = val;
|
// 重新加载数据
|
};
|
|
const getStatusType = status => {
|
const statusMap = {
|
已完成: "success",
|
检测中: "warning",
|
待检测: "info",
|
已暂停: "danger",
|
使用中: "primary",
|
空闲: "info",
|
};
|
return statusMap[status] || "info";
|
};
|
|
const getProgressStatus = progress => {
|
if (progress === 100) return "success";
|
if (progress >= 80) return "warning";
|
if (progress >= 50) return "";
|
return "exception";
|
};
|
|
const viewDetail = row => {
|
ElMessage.info(`查看详情: ${row.name}`);
|
};
|
|
const editItem = row => {
|
ElMessage.info(`编辑项目: ${row.name}`);
|
};
|
|
// 生命周期
|
onMounted(() => {
|
fetchInspectStatisticsData();
|
fetchPassRateStatisticsData();
|
fetchMonthlyPassRateData();
|
fetchYearlyPassRateData();
|
fetchMonthlyCompletionDetailsData();
|
fetchTopParametersData();
|
nextTick(() => {
|
initSampleChart();
|
initEquipmentChart();
|
initInspectionChart();
|
initUsageChart();
|
initAllQualityCharts();
|
});
|
// 监听窗口大小变化,重新调整图表大小
|
window.addEventListener("resize", () => {
|
sampleChart?.resize();
|
equipmentChart?.resize();
|
inspectionChart?.resize();
|
usageChart?.resize();
|
|
// 调整质检合格率图表大小
|
materialCompletionChartInstance?.resize();
|
materialQualityChartInstance?.resize();
|
semiCompletionChartInstance?.resize();
|
semiQualityChartInstance?.resize();
|
finalCompletionChartInstance?.resize();
|
finalQualityChartInstance?.resize();
|
});
|
});
|
</script>
|
|
<style scoped>
|
.report-management {
|
padding: 20px;
|
background-color: #f5f5f5;
|
min-height: 100vh;
|
/* height: 87vh;
|
overflow: hidden; */
|
}
|
|
.page-header {
|
margin-bottom: 20px;
|
text-align: center;
|
}
|
|
.page-header h2 {
|
color: #303133;
|
margin-bottom: 8px;
|
font-size: 24px;
|
font-weight: 600;
|
}
|
|
.page-header p {
|
color: #909399;
|
font-size: 14px;
|
margin: 0;
|
}
|
|
.filter-card {
|
margin-bottom: 20px;
|
}
|
|
.statistics-cards {
|
margin-bottom: 20px;
|
}
|
|
.stat-card {
|
height: 120px;
|
}
|
|
.stat-content {
|
display: flex;
|
align-items: center;
|
height: 100%;
|
}
|
|
.stat-icon {
|
width: 60px;
|
height: 60px;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 20px;
|
font-size: 24px;
|
color: white;
|
}
|
|
.stat-card:nth-child(1) .stat-icon {
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
}
|
|
.stat-card:nth-child(2) .stat-icon {
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
}
|
|
.stat-card:nth-child(3) .stat-icon {
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
}
|
|
.stat-card:nth-child(4) .stat-icon {
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
}
|
|
.stat-info {
|
flex: 1;
|
}
|
|
.stat-number {
|
font-size: 28px;
|
font-weight: bold;
|
color: #303133;
|
margin-bottom: 8px;
|
}
|
|
.stat-label {
|
font-size: 14px;
|
color: #909399;
|
}
|
|
.charts-container {
|
/* margin-bottom: 20px; */
|
position: relative;
|
}
|
|
.chart-card {
|
margin-bottom: 20px;
|
}
|
|
.container-line-right-bottom {
|
height: 20%;
|
width: 100%;
|
display: flex;
|
justify-content: space-evenly;
|
align-items: center;
|
/* background-color: #5b3f3f; */
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: flex-start;
|
align-items: center;
|
font-family: Source Han Sans, Source Han Sans;
|
font-weight: 700;
|
font-size: 18px;
|
color: #000000;
|
/* line-height: 27px; */
|
text-align: left;
|
font-style: normal;
|
text-transform: none;
|
}
|
|
.chart-title-line {
|
width: 6px;
|
height: 22px;
|
background-color: #161a9a;
|
margin-right: 16px;
|
border-radius: 3px;
|
}
|
|
.chart-container {
|
height: 250px;
|
width: 100%;
|
}
|
|
.chart-container-line {
|
height: 250px;
|
width: 100%;
|
display: flex;
|
position: relative;
|
}
|
|
/* Tab 选择器样式 */
|
.tab-selector {
|
position: absolute;
|
top: 20px;
|
right: 40px;
|
display: flex;
|
border: 1px solid #dcdfe6;
|
border-radius: 4px;
|
overflow: hidden;
|
}
|
|
.tab-item {
|
padding: 4px 12px;
|
cursor: pointer;
|
font-size: 14px;
|
color: #606266;
|
background-color: #fff;
|
border-right: 1px solid #dcdfe6;
|
transition: all 0.3s;
|
}
|
|
.tab-item:last-child {
|
border-right: none;
|
}
|
|
.tab-item:hover {
|
color: #409eff;
|
}
|
|
.tab-item.active {
|
color: #fff;
|
background-color: #409eff;
|
}
|
|
.container-line-left {
|
height: 100%;
|
width: 66%;
|
}
|
|
.container-line-right {
|
height: 100%;
|
width: 34%;
|
}
|
|
.container-line2-left {
|
height: 100%;
|
width: 50%;
|
}
|
|
.info-box {
|
width: 92%;
|
margin-left: 4%;
|
height: 100%;
|
background-color: #f7f8fa;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: space-around;
|
}
|
|
.info-box-header {
|
width: 100%;
|
margin-left: 20px;
|
color: #1d2129;
|
font-size: 16px;
|
font-weight: 500;
|
line-height: 37px;
|
}
|
|
.info-line {
|
width: 100%;
|
display: flex;
|
padding-left: 20px;
|
align-items: center;
|
flex: 1;
|
}
|
|
.info-icon {
|
width: 7px;
|
height: 7px;
|
border-radius: 50%;
|
margin-right: 8px;
|
}
|
|
.info-line-title {
|
font-size: 12px;
|
color: #4e5969;
|
flex: 1;
|
}
|
|
.info-line-value1 {
|
font-size: 12px;
|
color: #3d3d3d;
|
color: #1d2129;
|
font-weight: 500;
|
margin-right: 15%;
|
}
|
|
.info-line-value2 {
|
font-size: 12px;
|
color: #3d3d3d;
|
color: #1d2129;
|
font-weight: 500;
|
margin-right: 10%;
|
}
|
|
.top-container {
|
height: 130px;
|
width: 100%;
|
display: flex;
|
}
|
|
.typeNum {
|
height: 100%;
|
width: 33.33%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.typeNum-left {
|
font-size: 12px;
|
color: #909399;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.typeNum-left-text {
|
font-size: 12px;
|
color: #3491fa;
|
font-weight: 500;
|
margin-top: 5px;
|
}
|
|
.table-card {
|
margin-bottom: 20px;
|
}
|
|
.typeNum-center {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-left: 10px;
|
}
|
|
.typeNum-leftLine {
|
color: #3491fa;
|
font-size: 12px;
|
}
|
|
.typeNum-rightLine {
|
border-top: 1px solid #3491fa;
|
border-left: 1px solid #3491fa;
|
border-bottom: 1px solid #3491fa;
|
height: 80px;
|
width: 8px;
|
}
|
|
.typeNum-leftLine2 {
|
color: #5eb334;
|
font-size: 12px;
|
}
|
|
.typeNum-rightLine2 {
|
border-top: 1px solid #3491fa;
|
border-left: 1px solid #5eb334;
|
border-bottom: 1px solid #5eb334;
|
height: 80px;
|
width: 8px;
|
}
|
|
.typeNum-leftLine3 {
|
color: #8000ff;
|
font-size: 12px;
|
}
|
|
.typeNum-rightLine3 {
|
border-top: 1px solid #8000ff;
|
border-left: 1px solid #8000ff;
|
border-bottom: 1px solid #8000ff;
|
height: 80px;
|
width: 8px;
|
}
|
|
.typeNum-right {
|
margin-left: 10px;
|
display: flex;
|
flex-direction: column;
|
height: 90%;
|
justify-content: space-between;
|
}
|
|
.typeNum-right-top-name {
|
font-weight: 400;
|
font-size: 12px;
|
color: #3d3d3d;
|
}
|
|
.typeNum-right-top-text {
|
font-weight: 400;
|
font-size: 16px;
|
color: rgba(0, 0, 0, 0.85);
|
margin-top: 5px;
|
}
|
|
.unit {
|
font-size: 12px;
|
color: #3d3d3d;
|
}
|
|
.inspection-chart-box {
|
height: 50px;
|
width: 30%;
|
background-color: #f7f8fa;
|
border-radius: 8px;
|
padding-left: 15px;
|
}
|
|
.chart-box-title {
|
font-size: 12px;
|
color: #4e5969;
|
margin-top: 5px;
|
}
|
|
.unit {
|
font-size: 12px;
|
color: #3d3d3d;
|
}
|
|
.chart-box-num {
|
font-size: 18px;
|
color: #000;
|
margin-top: 5px;
|
font-weight: 500;
|
}
|
|
/* 质检合格率卡片样式 */
|
.top-container合格率 {
|
height: 130px;
|
width: 100%;
|
display: flex;
|
gap: 15px;
|
align-items: center;
|
justify-content: space-between;
|
}
|
|
.flex-center {
|
justify-content: space-evenly;
|
}
|
|
.quality-card {
|
/* flex: 1; */
|
width: 32%;
|
/* height: 100px; */
|
border-radius: 8px;
|
padding: 12px;
|
display: flex;
|
flex-direction: column;
|
}
|
|
.blue-card {
|
background-color: #e6f7ff;
|
}
|
|
.green-card {
|
background-color: #f6ffed;
|
color: #000000;
|
}
|
|
.purple-card {
|
background-color: #f9f0ff;
|
}
|
|
.quality-card-title {
|
font-size: 14px;
|
font-weight: 500;
|
margin-bottom: 10px;
|
display: flex;
|
align-items: center;
|
}
|
|
.quality-item-tip {
|
font-size: 12px;
|
color: #666666;
|
margin-bottom: 3px;
|
}
|
|
.blue-label {
|
color: #1890ff;
|
}
|
|
.green-label {
|
color: #52c41a;
|
}
|
|
.quality-card-title {
|
color: #000;
|
font-weight: bold;
|
}
|
|
.quality-card-content {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
flex: 1;
|
}
|
|
.quality-item {
|
display: flex;
|
/* flex-direction: column; */
|
align-items: center;
|
justify-content: center;
|
margin-top: 5px;
|
flex: 1;
|
}
|
|
.quality-item-label {
|
font-size: 12px;
|
/* color: #666; */
|
margin-bottom: 4px;
|
}
|
|
.quality-item-value {
|
font-size: 20px;
|
font-weight: 500;
|
margin-bottom: 4px;
|
}
|
|
.quality-item-chart {
|
width: 60px;
|
height: 60px;
|
margin-left: 10px;
|
}
|
|
/* .flex-center {
|
justify-content: space-evenly;
|
} */
|
|
.blue-chart {
|
/* background-color: rgba(24, 144, 255, 0.1); */
|
}
|
|
.green-chart {
|
/* background-color: rgba(82, 196, 26, 0.1); */
|
}
|
|
.purple-chart {
|
/* background-color: rgba(114, 46, 209, 0.1); */
|
}
|
|
.chart-ring {
|
width: 60px;
|
height: 60px;
|
border-radius: 50%;
|
border: 15px solid transparent;
|
position: relative;
|
}
|
|
.blue-chart .chart-ring {
|
border-top-color: #1890ff;
|
border-right-color: #1890ff;
|
border-bottom-color: #1890ff;
|
transform: rotate(45deg);
|
}
|
|
.green-chart .chart-ring {
|
border-top-color: #52c41a;
|
border-right-color: #52c41a;
|
border-bottom-color: #52c41a;
|
transform: rotate(45deg);
|
}
|
|
.purple-chart .chart-ring {
|
border-top-color: #722ed1;
|
border-right-color: #722ed1;
|
border-bottom-color: #722ed1;
|
transform: rotate(45deg);
|
}
|
|
.pagination-container {
|
margin-top: 20px;
|
text-align: right;
|
}
|
|
.yearchange {
|
position: absolute;
|
right: 40px;
|
top: 20px;
|
display: flex;
|
align-items: center;
|
/* width: 60px; */
|
}
|
|
:deep(.el-card__header) {
|
padding: 15px 20px;
|
border-bottom: 1px solid #ffffff;
|
background-color: #ffffff;
|
}
|
|
:deep(.el-card__body) {
|
padding: 20px;
|
}
|
|
:deep(.el-table) {
|
margin-bottom: 20px;
|
}
|
|
:deep(.el-progress) {
|
margin: 0;
|
}
|
|
:deep(.el-tag) {
|
margin: 0;
|
}
|
|
:deep(.el-input__prefix) {
|
display: none !important;
|
}
|
</style>
|