1226 lines
37 KiB
Vue
1226 lines
37 KiB
Vue
<script setup lang='ts'>
|
||
import { Download, Search } from '@element-plus/icons-vue';
|
||
import * as echarts from 'echarts';
|
||
import { ElLoading, ElMessage, type FormInstance, type FormRules, type UploadProps } from 'element-plus';
|
||
import { ref } from 'vue';
|
||
import { useI18n } from 'vue-i18n';
|
||
import { getConcChartData, getSampleStat, getStationList, exportData } from '@/utils/axios/sjfx';
|
||
import { exportCharts } from '@/utils/index';
|
||
// 下载图表图片
|
||
const { t } = useI18n();
|
||
const sampleTypeList = ref<any>([
|
||
{ label: '颗粒物', value: 'P' },
|
||
{ label: '惰性气体', value: 'B' },
|
||
]);
|
||
const dataSourceList = ref<any>([
|
||
{ label: 'MDC自动处理', value: '1' },
|
||
{ label: 'MDC交互分析', value: '2' },
|
||
{ label: 'IDC自动处理', value: '3' },
|
||
{ label: 'IDC交互分析', value: '4' },
|
||
]);
|
||
const form = ref<any>({
|
||
// sampleType: 'P',
|
||
// station: 122,
|
||
// dataSource: '1',
|
||
// startDate: '2025-01-01',
|
||
// endDate: '2025-10-15',
|
||
// createTime: ['2025-01-01', '2025-10-15'],
|
||
});
|
||
const stationList = ref<any>([]);
|
||
function getStation() {
|
||
getStationList({ systemType: form.value.sampleType, dataSource: form.value.dataSource }, (res) => {
|
||
const list = res.result;
|
||
stationList.value = list.map((item: { stationCode: any; stationId: any }) => {
|
||
return {
|
||
label: item.stationCode,
|
||
value: item.stationId,
|
||
};
|
||
});
|
||
// console.log(res, 'res');
|
||
});
|
||
}
|
||
const clickPieItem = ref('');
|
||
const nuclideData = ref<any>({});
|
||
const yTitle = ref('uBq)');
|
||
const loading = ref(false);
|
||
watch(
|
||
() => form.value.sampleType,
|
||
() => {
|
||
if (form.value.sampleType === 'P') {
|
||
yTitle.value = `uBq`;
|
||
}
|
||
else {
|
||
yTitle.value = `mBq`;
|
||
}
|
||
},
|
||
);
|
||
// watch(
|
||
// () => clickPieItem.value,
|
||
// () => {
|
||
// const list = nuclideData.value[clickPieItem.value].sort((a: any, b: any) => {
|
||
// return new Date(a.collectStop) - new Date(b.collectStop);
|
||
// });
|
||
// setClickPieData(list);
|
||
// },
|
||
// );
|
||
|
||
const formRef = ref<FormInstance>();
|
||
const rules = {
|
||
dataSource: [{ required: true, message: '请选择数据来源', trigger: 'change' }],
|
||
station: [{ required: true, message: '请选择台站名称', trigger: 'change' }],
|
||
createTime: [{
|
||
required: true, message: '请选择创建时间范围', trigger: 'change', validator: (rule, value, callback) => {
|
||
if (!value || value.length === 0) {
|
||
callback(new Error('请选择创建时间范围'));
|
||
}
|
||
else {
|
||
callback();
|
||
}
|
||
}
|
||
}],
|
||
};
|
||
function setTime(val: any[]) {
|
||
form.value.startDate = val[0];
|
||
form.value.endDate = val[1];
|
||
console.log(val);
|
||
}
|
||
function handleSearch() {
|
||
getData();
|
||
}
|
||
function handleSampleTypeChange() {
|
||
if (!form.value.dataSource) {
|
||
ElMessage.warning('请选择数据来源!!!');
|
||
return;
|
||
}
|
||
form.value.station = '';
|
||
getStation();
|
||
initPieCharts();
|
||
initLineCharts();
|
||
}
|
||
// 饼图相关
|
||
const pieData = ref<any>([
|
||
]);
|
||
// 模拟数据
|
||
const lineData = ref<any>({
|
||
// xAxisData: [],
|
||
// seriesData: [
|
||
// ],
|
||
});
|
||
const pieRef = ref<HTMLDivElement | null>(null);
|
||
let pieChart: echarts.ECharts | null = null;
|
||
// 折线图相关
|
||
const lineRef = ref<HTMLDivElement | null>(null);
|
||
let lineChart: echarts.ECharts | null = null;
|
||
// 饼图点击
|
||
function handlePieClick(params: { componentType: string; seriesType: string; data: { name: string } }) {
|
||
if (params.componentType === 'series' && params.seriesType === 'pie') {
|
||
// console.log('饼图被点击了', params);
|
||
if (params.data.name === 'other') {
|
||
ElMessage.info('点击了其他核素类别,无法显示具体数据');
|
||
return;
|
||
}
|
||
clickPieItem.value = params.data.name;
|
||
console.log(clickPieItem.value, '点中的');
|
||
clickPieInfo();
|
||
}
|
||
}
|
||
|
||
// 查询饼图数据
|
||
async function getData() {
|
||
const formEl = formRef.value;
|
||
if (!formEl)
|
||
return;
|
||
await formEl.validate((valid, fields) => {
|
||
if (valid) {
|
||
form.value.stationIds = [form.value.station]
|
||
console.log(form.value, 'form');
|
||
getSampleStat(form.value, (res) => {
|
||
console.log(res, 'res');
|
||
const data = res.result;
|
||
nuclideData.value = data?.nuclideActConcIntvlList;
|
||
if (Object.entries(nuclideData.value).length === 0) {
|
||
pieData.value = [];
|
||
initPieCharts();
|
||
initLineCharts();
|
||
ElMessage.warning('当前未查询到相关核素数据');
|
||
return;
|
||
}
|
||
const stationList: { name: string; value: any[] }[] = [];
|
||
Object.entries(nuclideData.value).forEach(([key, value]) => {
|
||
// console.log(`Key: ${key}, Value: ${value.length}`);
|
||
stationList.push({
|
||
name: key,
|
||
value: value?.length || 0,
|
||
});
|
||
});
|
||
console.log(stationList, 'stationList');
|
||
if (stationList.length > 10) {
|
||
const firstList = stationList.slice(0, 10);
|
||
// 第二个数组:从第10个开始到结束
|
||
const secondList = stationList.slice(10);
|
||
const secondListLength = secondList.reduce((sum, item) => sum + item.value, 0);
|
||
const newArr: any = { name: 'other', value: secondListLength };
|
||
const newStationList = firstList.concat(newArr);
|
||
pieData.value = newStationList;
|
||
}
|
||
else {
|
||
pieData.value = stationList;
|
||
}
|
||
renderPieChart();
|
||
});
|
||
}
|
||
// else {
|
||
// console.log('表单验证失败:', fields);
|
||
// ElMessage.error('搜索信息未填写');
|
||
// }
|
||
});
|
||
}
|
||
// --- 图表核心逻辑 ---
|
||
|
||
/**
|
||
* 提取通用的 Custom Series 渲染器
|
||
* @param color 颜色
|
||
* @param shapeType 中心的形状 'rect' | 'polygon' (三角形)
|
||
*/
|
||
function createErrorBarRenderItem(color: string, shapeType: 'rect' | 'triangle' | 'circle') {
|
||
return function (params: any, api: any) {
|
||
const xValue = api.value(0);
|
||
const yValue = api.value(1);
|
||
const errorTop = api.value(2); // 顶部误差
|
||
const errorBottom = api.value(3); // 底部误差
|
||
|
||
const point = api.coord([xValue, yValue]);
|
||
const top = api.coord([xValue, yValue + errorTop]);
|
||
const bottom = api.coord([xValue, yValue - errorBottom]);
|
||
|
||
const halfSize = 6; // 图形半径
|
||
|
||
const children: any[] = [
|
||
// 垂直线
|
||
{
|
||
type: 'line',
|
||
shape: { x1: point[0], y1: top[1], x2: point[0], y2: bottom[1] },
|
||
style: { stroke: color, lineWidth: 1 },
|
||
},
|
||
// 顶部横线
|
||
{
|
||
type: 'line',
|
||
shape: { x1: point[0] - 3, y1: top[1], x2: point[0] + 3, y2: top[1] },
|
||
style: { stroke: color, lineWidth: 1 },
|
||
},
|
||
// 底部横线
|
||
{
|
||
type: 'line',
|
||
shape: { x1: point[0] - 3, y1: bottom[1], x2: point[0] + 3, y2: bottom[1] },
|
||
style: { stroke: color, lineWidth: 1 },
|
||
},
|
||
];
|
||
|
||
// 中心图形
|
||
if (shapeType === 'rect') {
|
||
children.push({
|
||
type: 'rect',
|
||
shape: {
|
||
x: point[0] - halfSize,
|
||
y: point[1] - halfSize,
|
||
width: halfSize * 2,
|
||
height: halfSize * 2,
|
||
},
|
||
style: { fill: color },
|
||
});
|
||
}
|
||
else if (shapeType === 'triangle') {
|
||
children.push({
|
||
type: 'polygon',
|
||
shape: {
|
||
points: [
|
||
[point[0] - halfSize, point[1] + halfSize - 1], // 左下
|
||
[point[0] + halfSize, point[1] + halfSize - 1], // 右下
|
||
[point[0], point[1] - halfSize - 1], // 上
|
||
],
|
||
},
|
||
style: { fill: color },
|
||
});
|
||
}
|
||
else if (shapeType === 'circle') {
|
||
children.push({
|
||
type: 'circle',
|
||
shape: {
|
||
cx: point[0],
|
||
cy: point[1],
|
||
r: halfSize,
|
||
},
|
||
style: { fill: color },
|
||
});
|
||
}
|
||
|
||
return { type: 'group', children };
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 生成系列数据配置
|
||
*/
|
||
function generateSeries(name: string, data: any[], color: string, shape: 'rect' | 'triangle' | 'circle') {
|
||
if (!data || data.length === 0)
|
||
return null;
|
||
|
||
// 按照时间排序
|
||
const sortedData = data
|
||
.sort((a: any, b: any) => new Date(a[0]).getTime() - new Date(b[0]).getTime())
|
||
.map((item: any) => [
|
||
item[0], // x: 时间
|
||
item[1], // y: 值
|
||
item[3] || 0.1, // errorTop (对应原代码 item[3])
|
||
item[2] || 0.1, // errorBottom (对应原代码 item[2], 注意顺序需匹配renderItem里的获取)
|
||
]);
|
||
|
||
return {
|
||
name: `${name}(${data.length})`,
|
||
type: 'custom',
|
||
itemStyle: { color },
|
||
renderItem: createErrorBarRenderItem(color, shape),
|
||
data: sortedData,
|
||
z: 10,
|
||
};
|
||
}
|
||
|
||
// 简单的去重工具函数
|
||
function uniqueData(data: any[]) {
|
||
if (!data)
|
||
return [];
|
||
// 假设根据时间(item[0])去重,使用 Map 效率更高
|
||
const map = new Map();
|
||
data.forEach((item) => {
|
||
if (Array.isArray(item))
|
||
map.set(item[0], item);
|
||
});
|
||
return Array.from(map.values());
|
||
}
|
||
// 点击饼图详情
|
||
async function clickPieInfo() {
|
||
const formEl = formRef.value;
|
||
if (!formEl)
|
||
return;
|
||
await formEl.validate((valid, fields) => {
|
||
if (valid) {
|
||
// console.log(form.value, 'form');
|
||
getConcChartData({ ...form.value, nuclideName: clickPieItem.value }, (res) => {
|
||
console.log(res, 'res');
|
||
const data = res.result;
|
||
if (Object.entries(data).length === 0) {
|
||
lineData.value.xAxisData = [];
|
||
lineData.value.seriesData = [];
|
||
renderLineChart();
|
||
ElMessage.warning('当前核素无浓度数据');
|
||
return;
|
||
}
|
||
const mdcList = uniqueData(data.mdc);// mdc数据Y轴数据
|
||
const threshold = uniqueData(data.threshold);// 异常阀值Y轴数据
|
||
// 基础系列 (MDC 和 阈值)
|
||
const seriesArr: any[] = [
|
||
{
|
||
name: 'MDC',
|
||
type: 'line',
|
||
step: 'start',
|
||
symbolSize: 0,
|
||
data: mdcList,
|
||
},
|
||
{
|
||
name: '异常阀值',
|
||
type: 'line',
|
||
lineStyle: { color: 'red', width: 4, type: 'dashed' },
|
||
itemStyle: { color: 'red' },
|
||
data: threshold,
|
||
},
|
||
];
|
||
|
||
// 根据 sampleType 动态添加自定义系列
|
||
const levelsConfig = [];
|
||
|
||
if (form.value.sampleType === 'P') {
|
||
// 颗粒物:Level 3 (黄, rect), Level 4 (红, triangle) - 原代码 Level4 是 triangle
|
||
levelsConfig.push(
|
||
{ name: '级别3', data: data.category3, color: 'yellow', shape: 'rect' },
|
||
{ name: '级别4', data: data.category4, color: 'red', shape: 'triangle' },
|
||
);
|
||
}
|
||
else {
|
||
// 惰性气体:Level 1 (圆点), Level 2 (黄, rect), Level 3 (红, triangle)
|
||
levelsConfig.push(
|
||
{ name: '级别1', data: data.category1, color: 'green', shape: 'circle' },
|
||
{ name: '级别2', data: data.category2, color: 'yellow', shape: 'rect' },
|
||
{ name: '级别3', data: data.category3, color: 'red', shape: 'triangle' },
|
||
);
|
||
}
|
||
|
||
// 批量生成并过滤空数据
|
||
levelsConfig.forEach((conf) => {
|
||
const s = generateSeries(conf.name, conf.data, conf.color, conf.shape as any);
|
||
if (s)
|
||
seriesArr.push(s);
|
||
});
|
||
|
||
// 提取 X 轴 (取阈值的时间点,也可以合并所有数据的时间点)
|
||
const xAxisData = threshold.map((item: any) => item[0]);
|
||
|
||
console.log(xAxisData, seriesArr, 'xAxisData, seriesArr');
|
||
lineData.value.xAxisData = xAxisData;
|
||
lineData.value.seriesData = seriesArr;
|
||
renderLineChart(xAxisData, seriesArr);
|
||
});
|
||
}
|
||
// else {
|
||
// console.log('表单验证失败:', fields);
|
||
// ElMessage.error('搜索信息未填写');
|
||
// }
|
||
});
|
||
}
|
||
// async function getData() {
|
||
// const loading = ElLoading.service({
|
||
// background: 'rgba(0, 0, 0, 0.7)',
|
||
// fullscreen: true,
|
||
// text: 'Loading...',
|
||
// target: document.querySelector('.pie-container'),
|
||
// });
|
||
// const res = await fetch('../../../../public/mock/threeData.json');
|
||
// const list = await res.json();
|
||
// console.log(list, 'res');
|
||
// const data = list.result;
|
||
// nuclideData.value = data?.nuclideActConcIntvlList;
|
||
// // const threshold = data?.thresholdResultHisDataList; // 为空
|
||
// // const sampleLevel = data?.sampleLevelDataList;
|
||
// // 按照日期排序
|
||
// // const sortSampleLevelList = sampleLevel.sort((a: any, b: any) => {
|
||
// // return new Date(a.collectStop) - new Date(b.collectStop);
|
||
// // });
|
||
// const stationList: { name: string; value: any[] }[] = [];
|
||
// Object.entries(nuclideData.value).forEach(([key, value]) => {
|
||
// // console.log(`Key: ${key}, Value: ${value.length}`);
|
||
// stationList.push({
|
||
// name: key,
|
||
// value: value?.length || 0,
|
||
// });
|
||
// });
|
||
// console.log(stationList, 'stationList');
|
||
// if (stationList.length > 10) {
|
||
// const firstList = stationList.slice(0, 10);
|
||
// // 第二个数组:从第10个开始到结束
|
||
// const secondList = stationList.slice(10);
|
||
// // console.log(firstList, 'newStationList');
|
||
// // console.log(secondList, 'newStationList2');
|
||
// const secondListLength = secondList.reduce((sum, item) => sum + item.value, 0);
|
||
// // console.log(secondListLength, 'secondListLength');
|
||
// const newArr = { name: 'other', value: secondListLength };
|
||
// const newStationList = firstList.concat(newArr);
|
||
// // console.log(newStationList, 'newStationListFinal');
|
||
// pieData.value = newStationList;
|
||
// }
|
||
// else {
|
||
// pieData.value = stationList;
|
||
// }
|
||
// // loading.close();
|
||
// renderPieChart();
|
||
// // renderLineChart();
|
||
// }
|
||
// async function clickPieInfo() {
|
||
// // const loading = ElLoading.service({
|
||
// // background: 'rgba(0, 0, 0, 0.7)',
|
||
// // fullscreen: true,
|
||
// // text: 'Loading...',
|
||
// // target: document.querySelector('.line-container'),
|
||
// // });
|
||
// const res = await fetch('../../../../public/mock/three1.json');
|
||
// const list = await res.json();
|
||
// console.log(list, 'res');
|
||
// const data = list.result;
|
||
// const mdcList = data.mdc; // mdc数据Y轴数据
|
||
// const threshold = data.threshold; // 异常阀值Y轴数据
|
||
// const level3List = data.category3; // 级别3浓度数据Y轴数据
|
||
// const level4List = data.category4; // 级别4浓度数据Y轴数据
|
||
// // const a = level4List
|
||
// // .sort((a: any, b: any) => new Date(a.x).getTime() - new Date(b.x).getTime())
|
||
// // .map((item: any, index: number) => [
|
||
// // item[0], // 使用索引作为x值
|
||
// // item[1], // y值
|
||
// // item[2] || 0.1, // 底部误差,如果为0则给个最小值
|
||
// // item[3] || 0.1, // 顶部误差,如果为0则给个最小值
|
||
// // ]);
|
||
// // console.log(a, 'a');
|
||
|
||
// lineData.value.xAxisData = [...new Set(threshold.map(item => JSON.stringify(item)))].map(str => JSON.parse(str)).map((item: any) => item[0]); ; // X轴数据
|
||
// // console.log(lineData.value.xAxisData, 'xAxisData');
|
||
// lineData.value.seriesData = [
|
||
// {
|
||
// name: 'MDC',
|
||
// type: 'line',
|
||
// step: 'start',
|
||
// symbolSize: 0,
|
||
// // lineStyle: {
|
||
// // color: '#878787',
|
||
// // },
|
||
// itemStyle: {
|
||
// // width: 0,
|
||
// // borderColor: 'red',
|
||
// // color: '#878787',
|
||
// },
|
||
// data: [...new Set(mdcList.map(item => JSON.stringify(item)))].map(str => JSON.parse(str)),
|
||
// },
|
||
// {
|
||
// name: '异常阀值',
|
||
// type: 'line',
|
||
// lineStyle: {
|
||
// color: 'red',
|
||
// width: 4,
|
||
// type: 'dashed',
|
||
// },
|
||
// itemStyle: {
|
||
// color: 'red',
|
||
// },
|
||
// data: [...new Set(threshold.map(item => JSON.stringify(item)))].map(str => JSON.parse(str)),
|
||
// },
|
||
// {
|
||
// name: `级别3(${level3List.length})`,
|
||
// // type: 'line',
|
||
// symbol: 'circle',
|
||
// symbolSize: 10,
|
||
// itemStyle: {
|
||
// color: 'yellow',
|
||
// },
|
||
// type: 'custom',
|
||
// renderItem(params: any, api: any) {
|
||
// const xValue: any = api.value(0);
|
||
// const yValue: any = api.value(1);
|
||
// const error: any = api.value(2);
|
||
// const error2: any = api.value(3);
|
||
|
||
// const point: any = api.coord([xValue, yValue]);
|
||
// const top = api.coord([xValue, yValue + error]);
|
||
// const bottom = api.coord([xValue, yValue - error2]);
|
||
// // 正方形的大小
|
||
// const squareSize = 12;
|
||
// return {
|
||
// type: 'group',
|
||
// children: [
|
||
// // 完整垂直误差线(从顶部到底部)
|
||
// {
|
||
// type: 'line',
|
||
// shape: {
|
||
// x1: point[0],
|
||
// y1: top[1],
|
||
// x2: point[0],
|
||
// y2: bottom[1],
|
||
// },
|
||
// style: {
|
||
// stroke: 'yellow',
|
||
// lineWidth: 1,
|
||
// },
|
||
// },
|
||
// // 顶部标记(水平短线)
|
||
// {
|
||
// type: 'line',
|
||
// shape: {
|
||
// x1: point[0] - 3,
|
||
// y1: top[1],
|
||
// x2: point[0] + 3,
|
||
// y2: top[1],
|
||
// },
|
||
// style: {
|
||
// stroke: 'yellow',
|
||
// lineWidth: 1,
|
||
// },
|
||
// },
|
||
// // 底部标记(水平短线)
|
||
// {
|
||
// type: 'line',
|
||
// shape: {
|
||
// x1: point[0] - 3,
|
||
// y1: bottom[1],
|
||
// x2: point[0] + 3,
|
||
// y2: bottom[1],
|
||
// },
|
||
// style: {
|
||
// stroke: 'yellow',
|
||
// lineWidth: 1,
|
||
// },
|
||
// },
|
||
// // 正方形
|
||
// {
|
||
// type: 'rect',
|
||
// shape: {
|
||
// x: point[0] - squareSize / 2,
|
||
// y: point[1] - squareSize / 2,
|
||
// width: squareSize,
|
||
// height: squareSize,
|
||
// },
|
||
// style: {
|
||
// fill: 'yellow',
|
||
// // stroke: '#fff',
|
||
// lineWidth: 0.5,
|
||
// },
|
||
// },
|
||
// ],
|
||
// };
|
||
// },
|
||
// data: level3List
|
||
// .sort((a: any, b: any) => new Date(a.x).getTime() - new Date(b.x).getTime())
|
||
// .map((item: any) => [
|
||
// item[0], // 使用索引作为x值
|
||
// item[1], // y值
|
||
// item[2] || 0.1, // 底部误差,如果为0则给个最小值
|
||
// item[3] || 0.1, // 顶部误差,如果为0则给个最小值
|
||
// ]),
|
||
// z: 10,
|
||
// // data: level3List,
|
||
// },
|
||
// {
|
||
// name: `级别4(${level4List.length})`,
|
||
// // type: 'line',
|
||
// symbol: 'circle',
|
||
// symbolSize: 10,
|
||
// itemStyle: {
|
||
// color: 'red',
|
||
// },
|
||
// type: 'custom',
|
||
// renderItem(params: any, api: any) {
|
||
// const xValue: any = api.value(0);
|
||
// const yValue: any = api.value(1);
|
||
// const error: any = api.value(2);
|
||
// const error2: any = api.value(3);
|
||
|
||
// const point: any = api.coord([xValue, yValue]);
|
||
// const top = api.coord([xValue, yValue + error]);
|
||
// const bottom = api.coord([xValue, yValue - error2]);
|
||
// return {
|
||
// type: 'group',
|
||
// children: [
|
||
// {
|
||
// type: 'line',
|
||
// shape: {
|
||
// x1: point[0],
|
||
// y1: top[1],
|
||
// x2: point[0],
|
||
// y2: bottom[1],
|
||
// },
|
||
// style: {
|
||
// stroke: 'red',
|
||
// lineWidth: 3,
|
||
// },
|
||
// },
|
||
// {
|
||
// type: 'line',
|
||
// shape: {
|
||
// x1: point[0] - 5,
|
||
// y1: top[1],
|
||
// x2: point[0] + 5,
|
||
// y2: top[1],
|
||
// },
|
||
// style: {
|
||
// stroke: 'red',
|
||
// lineWidth: 2,
|
||
// },
|
||
// },
|
||
// {
|
||
// type: 'line',
|
||
// shape: {
|
||
// x1: point[0] - 5,
|
||
// y1: bottom[1],
|
||
// x2: point[0] + 5,
|
||
// y2: bottom[1],
|
||
// },
|
||
// style: {
|
||
// stroke: 'red',
|
||
// lineWidth: 2,
|
||
// },
|
||
// },
|
||
// // 中心点正三角形
|
||
// {
|
||
// type: 'polygon',
|
||
// shape: {
|
||
// points: [
|
||
// [point[0] - 6, point[1] + 5], // 左下角
|
||
// [point[0] + 6, point[1] + 5], // 右下角
|
||
// [point[0], point[1] - 5], // 上顶点
|
||
// ],
|
||
// },
|
||
// style: {
|
||
// fill: 'red',
|
||
// // stroke: '#fff',
|
||
// lineWidth: 0.5,
|
||
// },
|
||
// },
|
||
// ],
|
||
// };
|
||
// },
|
||
// data: level4List
|
||
// .sort((a: any, b: any) => new Date(a.x).getTime() - new Date(b.x).getTime())
|
||
// .map((item: any) => [
|
||
// item[0], // 使用索引作为x值
|
||
// item[1], // y值
|
||
// item[2] || 0.1, // 底部误差,如果为0则给个最小值
|
||
// item[3] || 0.1, // 顶部误差,如果为0则给个最小值
|
||
// ]),
|
||
// z: 10,
|
||
// // data: level4List,
|
||
// },
|
||
// ];
|
||
// console.log(lineData.value.seriesData, 'seriesData');
|
||
// loading.value.close();
|
||
// renderLineChart();
|
||
// }
|
||
function renderPieChart() {
|
||
const option = {
|
||
tooltip: { trigger: 'item' },
|
||
legend: { type: 'scroll', orient: 'vertical', top: '20%', left: 'right', textStyle: { color: '#9ab1bc' }, icon: 'circle' },
|
||
series: [
|
||
{
|
||
type: 'pie',
|
||
radius: ['40%', '70%'],
|
||
data: pieData.value,
|
||
},
|
||
],
|
||
};
|
||
pieChart.setOption(option);
|
||
// 绑定点击事件
|
||
pieChart.on('click', handlePieClick);
|
||
}
|
||
function renderLineChart() {
|
||
const option = {
|
||
tooltip: {
|
||
trigger: 'axis', backgroundColor: '#03aa7f', borderColor: '#03aa7f', // 边框颜色
|
||
borderWidth: 1, // 边框宽度
|
||
borderRadius: 4, // 圆角
|
||
textStyle: {
|
||
color: '#fff', // 文字颜色
|
||
fontSize: 12,
|
||
}
|
||
},
|
||
toolbox: {
|
||
feature: {
|
||
dataZoom: {
|
||
show: true,
|
||
title: {
|
||
zoom: '区域缩放',
|
||
back: '区域缩放还原',
|
||
},
|
||
},
|
||
},
|
||
},
|
||
legend: {
|
||
type: 'plain',
|
||
// data: lineData.legendData,
|
||
top: 0,
|
||
textStyle: { color: '#9ab1bc' },
|
||
},
|
||
grid: {
|
||
left: '5%',
|
||
right: '2%',
|
||
bottom: '5%',
|
||
top: '10%',
|
||
containLabel: true,
|
||
},
|
||
xAxis: {
|
||
type: 'time',
|
||
name: '时间',
|
||
nameLocation: 'center',
|
||
nameTextStyle: { padding: 20, color: '#9ab1bc' },
|
||
axisLabel: {
|
||
color: '#9ab1bc',
|
||
formatter: (value: string | number) => {
|
||
const d = new Date(value as any);
|
||
if (isNaN(d.getTime()))
|
||
return String(value);
|
||
const y = d.getFullYear();
|
||
const m = (`0${d.getMonth() + 1}`).slice(-2);
|
||
const day = (`0${d.getDate()}`).slice(-2);
|
||
return `${y}-${m}-${day}`;
|
||
},
|
||
rotate: 0,
|
||
},
|
||
// data: lineData.value.xAxisData,
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
name: `浓度 (${yTitle.value}/m³)`,
|
||
nameLocation: 'center',
|
||
nameTextStyle: { padding: 60, color: '#9ab1bc' },
|
||
axisLabel: { color: '#9ab1bc' },
|
||
splitLine: {
|
||
show: true,
|
||
lineStyle: { color: '#406979', width: 1, type: 'solid' },
|
||
},
|
||
},
|
||
scale: true,
|
||
series: lineData.value.seriesData,
|
||
};
|
||
lineChart.setOption(option);
|
||
// 绑定点击事件
|
||
// lineChart.on('click', handlePieClick);
|
||
}
|
||
// 初始化图表
|
||
function initPieCharts() {
|
||
// 先销毁已存在的图表实例,防止内存泄漏
|
||
if (pieChart) {
|
||
pieChart.dispose();
|
||
}
|
||
nextTick(() => {
|
||
// 初始化柱状图
|
||
if (pieRef.value) {
|
||
pieChart = echarts.init(pieRef.value);
|
||
pieChart.setOption({
|
||
// tooltip: { trigger: 'item' },
|
||
// legend: { type: 'scroll', orient: 'vertical', top: '20%', left: 'right', textStyle: { color: '#9ab1bc' }, icon: 'circle' },
|
||
// series: [
|
||
// {
|
||
// type: 'pie',
|
||
// radius: ['40%', '70%'],
|
||
// data: pieData.value,
|
||
// },
|
||
// ],
|
||
});
|
||
}
|
||
});
|
||
}
|
||
// 初始化图表
|
||
function initLineCharts() {
|
||
// 先销毁已存在的图表实例,防止内存泄漏
|
||
if (lineChart) {
|
||
lineChart.dispose();
|
||
}
|
||
nextTick(() => {
|
||
// 初始化柱状图
|
||
if (lineRef.value) {
|
||
lineChart = echarts.init(lineRef.value);
|
||
lineChart.setOption({
|
||
// tooltip: { trigger: 'axis' },
|
||
// legend: {
|
||
// type: 'plain',
|
||
// // data: lineData.value.legendData,
|
||
// top: 0,
|
||
// textStyle: { color: '#9ab1bc' },
|
||
// },
|
||
// grid: {
|
||
// left: '5%',
|
||
// right: '2%',
|
||
// bottom: '5%',
|
||
// top: '10%',
|
||
// containLabel: true,
|
||
// },
|
||
// xAxis: {
|
||
// type: 'category',
|
||
// nameTextStyle: { color: '#9ab1bc' },
|
||
// axisLabel: { color: '#9ab1bc' },
|
||
// axisLine: {
|
||
// lineStyle: {
|
||
// color: '#406979', // 线条颜色
|
||
// width: 1, // 线条宽度
|
||
// type: 'solid', // 线条类型: solid, dashed, dotted
|
||
// },
|
||
// },
|
||
// // data: lineData.value.xAxisData,
|
||
// name: '时间',
|
||
// nameLocation: 'center',
|
||
// },
|
||
// yAxis: {
|
||
// type: 'value',
|
||
// name: '浓度(uBq/m3)',
|
||
// nameLocation: 'center',
|
||
// nameTextStyle: { color: '#9ab1bc' },
|
||
// axisLabel: { color: '#9ab1bc' },
|
||
// splitLine: {
|
||
// show: true,
|
||
// lineStyle: {
|
||
// color: '#406979', // 网格线颜色
|
||
// width: 1, // 网格线宽度
|
||
// type: 'solid', // 线条类型: solid, dashed, dotted
|
||
// },
|
||
// },
|
||
// },
|
||
// series: [
|
||
// {
|
||
// name: 'MDC',
|
||
// type: 'line',
|
||
// step: 'start',
|
||
// symbolSize: 0,
|
||
// lineStyle: {
|
||
// color: '#878787',
|
||
// },
|
||
// itemStyle: {
|
||
// // width: 0,
|
||
// // borderColor: 'red',
|
||
// color: '#878787',
|
||
// },
|
||
// // data: lineData.value.seriesData[0]?.data,
|
||
// },
|
||
// {
|
||
// name: '异常阀值',
|
||
// type: 'line',
|
||
// lineStyle: {
|
||
// color: 'red',
|
||
// width: 4,
|
||
// type: 'dashed',
|
||
// },
|
||
// itemStyle: {
|
||
// color: 'red',
|
||
// },
|
||
// // data: lineData.value.seriesData[1]?.data,
|
||
// },
|
||
// {
|
||
// name: '级别4(10)',
|
||
// type: 'line',
|
||
// // data: lineData.value.seriesData[2]?.data,
|
||
// symbol:
|
||
// 'path://M-0.4,-35 L0.4,-35 L0.4,-8 L-0.4,-8 Z M-0.4,8 L0.4,8 L0.4,35 L-0.4,35 Z M-5,6 L0,-6 L5,6 L0,6 Z',
|
||
// symbolSize: [20, 70],
|
||
// lineStyle: {
|
||
// type: 'dashed',
|
||
// color: 'red',
|
||
// width: 0,
|
||
// },
|
||
// itemStyle: {
|
||
// borderColor: 'red',
|
||
// color: 'red',
|
||
// },
|
||
// },
|
||
// {
|
||
// name: '级别3(40)',
|
||
// type: 'line',
|
||
// symbol:
|
||
// 'path://M-0.4,-35 L0.4,-35 L0.4,-8 L-0.4,-8 Z M-0.4,8 L0.4,8 L0.4,35 L-0.4,35 Z M-4,-6 L4,-6 L4,6 L-4,6 Z',
|
||
// symbolSize: [12, 70],
|
||
// // data: lineData.value.seriesData[3]?.data,
|
||
// lineStyle: {
|
||
// type: 'solid',
|
||
// color: 'yellow',
|
||
// width: 0,
|
||
// },
|
||
// itemStyle: {
|
||
// borderWidth: 0.5,
|
||
// borderColor: 'yellow',
|
||
// color: 'yellow',
|
||
// opacity: 1, // 透明度
|
||
// },
|
||
// },
|
||
// ],
|
||
});
|
||
}
|
||
});
|
||
}
|
||
const exportChartsData2 = async () => {
|
||
try {
|
||
console.log('开始下载...');
|
||
form.value.type = 'sampleStat'
|
||
const { filename, blob } = await exportData(form.value);
|
||
// const filename = '模版'
|
||
console.log('文件信息:', {
|
||
filename,
|
||
blob,
|
||
blobType: blob.type,
|
||
blobSize: `${blob.size} bytes`,
|
||
});
|
||
|
||
// 覆盖 Blob 类型为 Excel 标准 MIME 类型(避免后端返回类型错误)
|
||
const excelBlob = new Blob([blob], {
|
||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' // xlsx 标准类型
|
||
// 如果是 xls 格式,用:'application/vnd.ms-excel'
|
||
});
|
||
|
||
// 验证 Blob 是否有效
|
||
if (!blob || blob.size === 0) {
|
||
throw new Error('获取到的文件为空');
|
||
}
|
||
|
||
// 创建下载链接
|
||
const url = window.URL.createObjectURL(excelBlob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.download = filename; // 使用从响应头获取的文件名
|
||
link.style.display = 'none';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
|
||
console.log('文件下载成功:', filename);
|
||
ElMessage.success('导出成功');
|
||
}
|
||
catch (error) {
|
||
console.error('文件下载失败:', error);
|
||
}
|
||
finally {
|
||
}
|
||
}
|
||
const exportChartsData = async () => {
|
||
try {
|
||
console.log('开始下载...');
|
||
form.value.type = 'nuclideTime'
|
||
const { filename, blob } = await exportData(form.value);
|
||
// const filename = '模版'
|
||
console.log('文件信息:', {
|
||
filename,
|
||
blob,
|
||
blobType: blob.type,
|
||
blobSize: `${blob.size} bytes`,
|
||
});
|
||
|
||
// 覆盖 Blob 类型为 Excel 标准 MIME 类型(避免后端返回类型错误)
|
||
const excelBlob = new Blob([blob], {
|
||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' // xlsx 标准类型
|
||
// 如果是 xls 格式,用:'application/vnd.ms-excel'
|
||
});
|
||
|
||
// 验证 Blob 是否有效
|
||
if (!blob || blob.size === 0) {
|
||
throw new Error('获取到的文件为空');
|
||
}
|
||
|
||
// 创建下载链接
|
||
const url = window.URL.createObjectURL(excelBlob);
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.download = filename; // 使用从响应头获取的文件名
|
||
link.style.display = 'none';
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
|
||
console.log('文件下载成功:', filename);
|
||
ElMessage.success('导出成功');
|
||
}
|
||
catch (error) {
|
||
console.error('文件下载失败:', error);
|
||
}
|
||
finally {
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
// getStation();
|
||
initPieCharts();
|
||
initLineCharts();
|
||
window.addEventListener('resize', () => {
|
||
pieChart?.resize();
|
||
lineChart.resize();
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="sjfx-two-content">
|
||
<div class="head">
|
||
<el-form ref="formRef" :model="form" label-width="100px" class="queryForm" :rules="rules">
|
||
<el-row :gutter="10">
|
||
<el-col :span="4">
|
||
<el-form-item :label="t('dataSource')" prop="dataSource">
|
||
<el-select v-model="form.dataSource" :placeholder="t('pleaseSelect')" style="width: 300px;">
|
||
<el-option v-for="item in dataSourceList" :key="item.value"
|
||
style="background-color: transparent; height: 30px;color: #fff;" :label="item.label"
|
||
:value="item.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="4">
|
||
<el-form-item :label="t('sampleType')">
|
||
<el-select v-model="form.sampleType" :placeholder="t('pleaseSelect')" style="width: 100%;"
|
||
popper-style="background-color:transparent;border: solid 1px #0da397;width:100px;overflow: hidden;border-radius: 0;"
|
||
@change="handleSampleTypeChange">
|
||
<el-option v-for="item in sampleTypeList" :key="item.value"
|
||
style="background-color: transparent; height: 30px;color: #fff;" :label="item.label"
|
||
:value="item.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="5">
|
||
<el-form-item :label="t('stationName')" prop="station">
|
||
<el-select-v2 v-model="form.station" :placeholder="t('pleaseSelect')" style="width: 160px;" filterable
|
||
:options="stationList" clearable />
|
||
</el-form-item>
|
||
</el-col>
|
||
<!-- <el-col :span="3">
|
||
<el-form-item :label="t('nuclideName')">
|
||
<el-input v-model="form.nuclideName" style="width: 100%;" />
|
||
</el-form-item>
|
||
</el-col> -->
|
||
|
||
<el-col :span="8">
|
||
<el-form-item :label="t('createTime')" prop="createTime">
|
||
<el-date-picker v-model="form.createTime" type="daterange" unlink-panels :default-time="new Date()"
|
||
style="width: 100%;" range-separator="--" :start-placeholder="t('startDate')"
|
||
:end-placeholder="t('endDate')" value-format="YYYY-MM-DD" @change="setTime" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="2">
|
||
<el-form-item>
|
||
<el-button :icon="Search" @click="handleSearch">
|
||
{{ t('refresh') }}
|
||
</el-button>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</div>
|
||
<div class="echars">
|
||
<div class="pie">
|
||
<div class="echars-head">
|
||
<h3>{{ t('NuclearIsotopeStatisticalAnalysis') }}</h3>
|
||
<div style="flex: 1; text-align: right;">
|
||
<el-button :icon="Download" @click="exportChartsData2">
|
||
{{ t('exportData') }}
|
||
</el-button>
|
||
<el-button :icon="Download" @click="exportCharts(pieChart, '核素统计分析')">
|
||
{{ t('download') }}
|
||
</el-button>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="chart-card">
|
||
<div ref="pieRef" class="pie-container" />
|
||
<!-- <button class="update-btn" @click="handleUpdatePie">
|
||
更新数据
|
||
</button> -->
|
||
</div>
|
||
</div>
|
||
<div class="line">
|
||
<div class="echars-head">
|
||
<h3>{{ t('IsotopeGradeTimeSeriesAnalysis') }}</h3>
|
||
<div style="flex: 1; text-align: right;">
|
||
<el-button :icon="Download" @click="exportChartsData">
|
||
{{ t('exportData') }}
|
||
</el-button>
|
||
<el-button :icon="Download" @click="exportCharts(lineChart, '核素等级时序分析')">
|
||
{{ t('download') }}
|
||
</el-button>
|
||
</div>
|
||
|
||
</div>
|
||
<div class="chart-card">
|
||
<div ref="lineRef" class="line-container" :loading="loading" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang='less'>
|
||
.head {
|
||
width: 100%;
|
||
height: 50px;
|
||
line-height: 50px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin: 10px 0 0 0;
|
||
padding-top: 5px;
|
||
|
||
.queryForm {
|
||
display: flex;
|
||
align-items: center;
|
||
|
||
:deep(.el-form) {
|
||
padding-top: 20px;
|
||
}
|
||
|
||
:deep(.el-form-item__content) {
|
||
&:nth-last-child(1) {
|
||
margin-left: 0 !important;
|
||
}
|
||
}
|
||
|
||
:deep(.el-range-separator) {
|
||
color: #fff;
|
||
}
|
||
|
||
:deep(.el-select__wrapper),
|
||
:deep(.el-date-editor) {
|
||
background-color: rgba(3, 53, 63, 0.51) !important;
|
||
border-radius: 0%;
|
||
border: solid 1px #0b8c82 !important;
|
||
}
|
||
}
|
||
|
||
:deep(.el-button) {
|
||
width: 90px;
|
||
height: 32px;
|
||
margin: 0 5px;
|
||
background-color: #1397a3;
|
||
font-size: 16px;
|
||
font-weight: normal;
|
||
color: #ffffff;
|
||
border-radius: 0;
|
||
border: 1px solid #1397a3;
|
||
}
|
||
}
|
||
|
||
.echars-head {
|
||
position: relative;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
height: 42px;
|
||
// border-bottom: 2px solid #0cebc9;
|
||
border-top: 1px solid #0cebc9;
|
||
margin-bottom: 6px;
|
||
|
||
// &::before {
|
||
// content: '';
|
||
// position: absolute;
|
||
// top: 0;
|
||
// left: 0;
|
||
// width: 100%;
|
||
// height: 0.5px;
|
||
// width: 100%;
|
||
// transform: scaleY(0.5);
|
||
// // transform-origin: top;
|
||
// background-color: #0cebc9;
|
||
// }
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 6px;
|
||
width: 100%;
|
||
transform: scaleY(0.5);
|
||
transform-origin: bottom;
|
||
background: rgba(12, 235, 201, 0.2);
|
||
}
|
||
|
||
h3 {
|
||
height: 40px;
|
||
padding-right: 20px;
|
||
font-size: 18px;
|
||
font-weight: bold;
|
||
font-stretch: normal;
|
||
line-height: 40px;
|
||
letter-spacing: 1px;
|
||
color: #0cebc9;
|
||
background: rgba(12, 235, 201, 0.05);
|
||
margin-left: 18px;
|
||
}
|
||
|
||
:deep(.el-button) {
|
||
width: 85px;
|
||
// height: 24px;
|
||
// line-height: 42px;
|
||
background-color: #097c42;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 0;
|
||
}
|
||
}
|
||
|
||
.chart-card {
|
||
background-color: transparent;
|
||
border-radius: 8px;
|
||
padding: 16px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
|
||
.pie-container {
|
||
width: 100%;
|
||
height: 266px;
|
||
}
|
||
|
||
.line-container {
|
||
width: 100%;
|
||
height: 440px;
|
||
}
|
||
}
|
||
</style>
|