数据报表查询改为以站点为单位
This commit is contained in:
parent
06a4324b90
commit
7b1e701ee3
|
|
@ -6,6 +6,7 @@ enum Api {
|
||||||
dataSummary = '/appmana/dataCenter/dataSummary',
|
dataSummary = '/appmana/dataCenter/dataSummary',
|
||||||
deviceAlertStatistic = '/appmana/dataCenter/deviceAlertStatistic',
|
deviceAlertStatistic = '/appmana/dataCenter/deviceAlertStatistic',
|
||||||
deviceList = '/appmana/dataCenter/deviceList',
|
deviceList = '/appmana/dataCenter/deviceList',
|
||||||
|
getStationList = '/appmana/survStationInfo/getStationList',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,3 +37,9 @@ export const getAllDeviceList = (params) => defHttp.get({ url: Api.deviceList, p
|
||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export const getDeviceAlertStatistic = (params) => defHttp.post({ url: Api.deviceAlertStatistic, params }, { isTransformResponse: false });
|
export const getDeviceAlertStatistic = (params) => defHttp.post({ url: Api.deviceAlertStatistic, params }, { isTransformResponse: false });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表接口
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
export const getStationList = (params) => defHttp.get({ url: Api.getStationList, params }, { isTransformResponse: false });
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,46 @@
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<a-card :bordered="false" class="filter-card">
|
<a-card :bordered="false" class="filter-card">
|
||||||
<a-row :gutter="[8, 8]" align="middle">
|
<a-row :gutter="[8, 8]" align="middle">
|
||||||
<!-- 设备 -->
|
<!-- 站点 -->
|
||||||
<a-col :xs="24" :sm="12" :md="8" :lg="7">
|
<a-col :xs="24" :sm="12" :md="6" :lg="5">
|
||||||
<div class="filter-item">
|
<div class="filter-item">
|
||||||
<span class="filter-label">设备</span>
|
<span class="filter-label">站点</span>
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="selectedCategory"
|
v-model:value="selectedSite"
|
||||||
placeholder="请选择"
|
placeholder="请选择站点"
|
||||||
:loading="categoryLoading"
|
:loading="siteLoading"
|
||||||
allow-clear
|
allow-clear
|
||||||
show-search
|
show-search
|
||||||
:filter-option="filterOption"
|
:filter-option="filterOption"
|
||||||
style="flex: 1; min-width: 0"
|
style="flex: 1; min-width: 0"
|
||||||
>
|
>
|
||||||
<a-select-option v-for="item in categoryOptions" :key="item.id" :value="item.id">
|
<a-select-option v-for="item in siteOptions" :key="item.stationCode" :value="item.stationCode">
|
||||||
{{ item.deployDes }}
|
{{ item.stationName }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
|
<!-- 设备 -->
|
||||||
|
<!-- <a-col :xs="24" :sm="12" :md="6" :lg="5">-->
|
||||||
|
<!-- <div class="filter-item">-->
|
||||||
|
<!-- <span class="filter-label">设备</span>-->
|
||||||
|
<!-- <a-select-->
|
||||||
|
<!-- v-model:value="selectedCategory"-->
|
||||||
|
<!-- placeholder="请选择设备"-->
|
||||||
|
<!-- :loading="categoryLoading"-->
|
||||||
|
<!-- allow-clear-->
|
||||||
|
<!-- show-search-->
|
||||||
|
<!-- :filter-option="filterOption"-->
|
||||||
|
<!-- style="flex: 1; min-width: 0"-->
|
||||||
|
<!-- >-->
|
||||||
|
<!-- <a-select-option v-for="item in categoryOptions" :key="item.id" :value="item.id">-->
|
||||||
|
<!-- {{ item.deployDes }}-->
|
||||||
|
<!-- </a-select-option>-->
|
||||||
|
<!-- </a-select>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </a-col>-->
|
||||||
|
|
||||||
<!-- 统计模式 -->
|
<!-- 统计模式 -->
|
||||||
<a-col :xs="24" :sm="12" :md="5" :lg="4">
|
<a-col :xs="24" :sm="12" :md="5" :lg="4">
|
||||||
<div class="filter-item">
|
<div class="filter-item">
|
||||||
|
|
@ -51,7 +71,7 @@
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|
||||||
<!-- 查询按钮 -->
|
<!-- 查询按钮 -->
|
||||||
<a-col :xs="24" :sm="12" :md="5" :lg="4">
|
<a-col :xs="24" :sm="12" :md="5" :lg="5">
|
||||||
<a-button type="primary" @click="handleSearch" :loading="loading" block>
|
<a-button type="primary" @click="handleSearch" :loading="loading" block>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<ReloadOutlined />
|
<ReloadOutlined />
|
||||||
|
|
@ -70,7 +90,6 @@
|
||||||
<template #title>
|
<template #title>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="chart-title">{{ item.title || `图表 ${index + 1}` }}</span>
|
<span class="chart-title">{{ item.title || `图表 ${index + 1}` }}</span>
|
||||||
<!-- 保持您原来的写法,不做任何修改 -->
|
|
||||||
<a-tag color="blue" size="small">{{ item.data?.length || 0 }} 项</a-tag>
|
<a-tag color="blue" size="small">{{ item.data?.length || 0 }} 项</a-tag>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -87,32 +106,50 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, nextTick, computed } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, nextTick, computed } from 'vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import { ReloadOutlined } from '@ant-design/icons-vue';
|
import { ReloadOutlined } from '@ant-design/icons-vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getDeviceAlertStatistic, getAllDeviceList } from './datacenter.api';
|
import { getDeviceAlertStatistic, getAllDeviceList, getStationList } from './datacenter.api';
|
||||||
|
|
||||||
const fetchCategoryOptions = () => {
|
// ==================== 获取站点列表 ====================
|
||||||
|
const fetchSiteOptions = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getStationList({})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.success) {
|
||||||
|
resolve(res.result);
|
||||||
|
} else {
|
||||||
|
reject(new Error('获取站点数据失败'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('获取站点数据失败:', error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchCategoryOptions = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getAllDeviceList({})
|
getAllDeviceList({})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
resolve(res.result);
|
resolve(res.result);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error('获取数据失败'));
|
reject(new Error('获取设备数据失败'));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('获取数据概况失败:', error);
|
console.error('获取设备数据失败:', error);
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 根据类别获取图表数据 ====================
|
// ==================== 根据类别获取图表数据 ====================
|
||||||
const fetchChartDataByCategory = (deployId, summaryMode, startTime) => {
|
const fetchChartDataByCategory = (stationCode, deployId, summaryMode, startTime) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const chartData = [];
|
const chartData = [];
|
||||||
const dataMap = {
|
const dataMap = {
|
||||||
|
|
@ -122,8 +159,8 @@ const fetchChartDataByCategory = (deployId, summaryMode, startTime) => {
|
||||||
xAxis: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
xAxis: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
||||||
data: [180, 250, 220, 310, 280, 360],
|
data: [180, 250, 220, 310, 280, 360],
|
||||||
markLines: [
|
markLines: [
|
||||||
{ name: '月均线', value: 267, color: '#339af0', type: 'dashed' },
|
{ name: '高阈值', value: 350, color: '#fcc419', type: 'dashed' },
|
||||||
{ name: '冲刺目标', value: 350, color: '#fcc419', type: 'dotted' },
|
{ name: '低阈值', value: 150, color: '#ff6b6b', type: 'dotted' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -131,8 +168,8 @@ const fetchChartDataByCategory = (deployId, summaryMode, startTime) => {
|
||||||
xAxis: ['团队A', '团队B', '团队C', '团队D', '团队E'],
|
xAxis: ['团队A', '团队B', '团队C', '团队D', '团队E'],
|
||||||
data: [95, 140, 82, 210, 115],
|
data: [95, 140, 82, 210, 115],
|
||||||
markLines: [
|
markLines: [
|
||||||
{ name: '合格线', value: 100, color: '#ff6b6b', type: 'dashed' },
|
{ name: '高阈值', value: 180, color: '#fcc419', type: 'dashed' },
|
||||||
{ name: '优秀线', value: 180, color: '#69db7c', type: 'dotted' },
|
{ name: '低阈值', value: 80, color: '#ff6b6b', type: 'dotted' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -140,16 +177,17 @@ const fetchChartDataByCategory = (deployId, summaryMode, startTime) => {
|
||||||
xAxis: ['产品线A', '产品线B', '产品线C', '产品线D'],
|
xAxis: ['产品线A', '产品线B', '产品线C', '产品线D'],
|
||||||
data: [420, 380, 560, 290],
|
data: [420, 380, 560, 290],
|
||||||
markLines: [
|
markLines: [
|
||||||
{ name: '平均线', value: 412, color: '#4dabf7', type: 'dashed' },
|
{ name: '高阈值', value: 500, color: '#fcc419', type: 'dashed' },
|
||||||
{ name: '目标线', value: 500, color: '#ff922b', type: 'dotted' },
|
{ name: '低阈值', value: 300, color: '#ff6b6b', type: 'dotted' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构建请求参数 - 使用新的参数名
|
// 构建请求参数 - stationCode
|
||||||
const params = {
|
const params = {
|
||||||
deployId: deployId,
|
stationCode: stationCode,
|
||||||
|
// deployId: deployId,
|
||||||
summaryMode: summaryMode || 'dayhours',
|
summaryMode: summaryMode || 'dayhours',
|
||||||
startTime: startTime,
|
startTime: startTime,
|
||||||
};
|
};
|
||||||
|
|
@ -162,22 +200,25 @@ const fetchChartDataByCategory = (deployId, summaryMode, startTime) => {
|
||||||
Object.entries(valueMap).forEach(([key, value]) => {
|
Object.entries(valueMap).forEach(([key, value]) => {
|
||||||
let markLines = [];
|
let markLines = [];
|
||||||
let itemDetail = itemMap[key];
|
let itemDetail = itemMap[key];
|
||||||
|
|
||||||
|
// ✅ 高阈值:使用 highVal,名称为"高阈值"
|
||||||
if (itemDetail.highVal) {
|
if (itemDetail.highVal) {
|
||||||
markLines.push({
|
markLines.push({
|
||||||
name: itemDetail.itemName,
|
name: itemDetail.itemName +'高阈值',
|
||||||
value: itemDetail.highVal,
|
value: itemDetail.highVal,
|
||||||
color: '#339af0',
|
|
||||||
type: 'dotted',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (itemDetail.lowVal) {
|
|
||||||
markLines.push({
|
|
||||||
name: itemDetail.itemName,
|
|
||||||
value: itemDetail.lowVal,
|
|
||||||
color: '#fcc419',
|
color: '#fcc419',
|
||||||
type: 'dashed',
|
type: 'dashed',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// ✅ 低阈值:使用 lowVal,名称为"低阈值"
|
||||||
|
if (itemDetail.lowVal) {
|
||||||
|
markLines.push({
|
||||||
|
name: itemDetail.itemName +'低阈值',
|
||||||
|
value: itemDetail.lowVal,
|
||||||
|
color: '#ff6b6b',
|
||||||
|
type: 'dotted',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
chartData.push({
|
chartData.push({
|
||||||
title: itemDetail.itemName || key,
|
title: itemDetail.itemName || key,
|
||||||
|
|
@ -196,22 +237,26 @@ const fetchChartDataByCategory = (deployId, summaryMode, startTime) => {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 数据状态 ====================
|
// ==================== 数据状态 ====================
|
||||||
const categoryOptions = ref([]);
|
const siteOptions = ref([]);
|
||||||
const categoryLoading = ref(false);
|
const siteLoading = ref(false);
|
||||||
const selectedCategory = ref('');
|
const selectedSite = ref('');
|
||||||
const chartDataList = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
const chartInstances = [];
|
|
||||||
const chartRefs = {};
|
|
||||||
|
|
||||||
const selectedDimension = ref('dayhours');
|
const categoryOptions = ref([]);
|
||||||
const selectedDate = ref(null);
|
const categoryLoading = ref(false);
|
||||||
|
const selectedCategory = ref('');
|
||||||
|
const chartDataList = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const chartInstances = [];
|
||||||
|
const chartRefs = {};
|
||||||
|
|
||||||
// ==================== 根据统计模式动态计算日期格式 ====================
|
const selectedDimension = ref('dayhours');
|
||||||
const dateFormat = computed(() => {
|
const selectedDate = ref(null);
|
||||||
|
|
||||||
|
// ==================== 根据统计模式动态计算日期格式 ====================
|
||||||
|
const dateFormat = computed(() => {
|
||||||
switch (selectedDimension.value) {
|
switch (selectedDimension.value) {
|
||||||
case 'dayhours':
|
case 'dayhours':
|
||||||
return 'YYYY-MM-DD';
|
return 'YYYY-MM-DD';
|
||||||
|
|
@ -222,9 +267,9 @@ const dateFormat = computed(() => {
|
||||||
default:
|
default:
|
||||||
return 'YYYY-MM-DD';
|
return 'YYYY-MM-DD';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const pickerType = computed(() => {
|
const pickerType = computed(() => {
|
||||||
switch (selectedDimension.value) {
|
switch (selectedDimension.value) {
|
||||||
case 'dayhours':
|
case 'dayhours':
|
||||||
return 'date';
|
return 'date';
|
||||||
|
|
@ -235,9 +280,9 @@ const pickerType = computed(() => {
|
||||||
default:
|
default:
|
||||||
return 'date';
|
return 'date';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const datePlaceholder = computed(() => {
|
const datePlaceholder = computed(() => {
|
||||||
switch (selectedDimension.value) {
|
switch (selectedDimension.value) {
|
||||||
case 'dayhours':
|
case 'dayhours':
|
||||||
return '选择日期';
|
return '选择日期';
|
||||||
|
|
@ -248,46 +293,71 @@ const datePlaceholder = computed(() => {
|
||||||
default:
|
default:
|
||||||
return '选择日期';
|
return '选择日期';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ==================== 下拉框筛选 ====================
|
// ==================== 下拉框筛选 ====================
|
||||||
const filterOption = (input, option) => {
|
const filterOption = (input, option) => {
|
||||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCategoryLabel = (value) => {
|
const getCategoryLabel = (value) => {
|
||||||
const found = categoryOptions.value.find((item) => item.id === value);
|
const found = categoryOptions.value.find((item) => item.id === value);
|
||||||
return found ? found.deployDes : value;
|
return found ? found.deployDes : value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 设置图表 DOM 引用 ====================
|
// ==================== 设置图表 DOM 引用 ====================
|
||||||
const setChartRef = (el, index) => {
|
const setChartRef = (el, index) => {
|
||||||
if (el) {
|
if (el) {
|
||||||
chartRefs[index] = el;
|
chartRefs[index] = el;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 加载下拉选项 ====================
|
// ==================== 加载下拉选项 ====================
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
|
// 先加载站点列表
|
||||||
|
siteLoading.value = true;
|
||||||
|
try {
|
||||||
|
const siteRes = await fetchSiteOptions();
|
||||||
|
siteOptions.value = siteRes;
|
||||||
|
if (siteRes.length > 0) {
|
||||||
|
selectedSite.value = siteRes[0].stationCode;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载站点列表失败:', error);
|
||||||
|
} finally {
|
||||||
|
siteLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 再加载设备列表
|
||||||
categoryLoading.value = true;
|
categoryLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchCategoryOptions();
|
const res = await fetchCategoryOptions();
|
||||||
categoryOptions.value = res;
|
categoryOptions.value = res;
|
||||||
|
|
||||||
if (res.length > 0) {
|
if (res.length > 0) {
|
||||||
selectedCategory.value = res[0].id;
|
selectedCategory.value = res[0].id;
|
||||||
await handleSearch();
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error('加载设备列表失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
categoryLoading.value = false;
|
categoryLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// ==================== 加载图表数据(参数名:deployId, summaryMode, startTime) ====================
|
// 如果有站点和设备,自动查询
|
||||||
const loadChartData = async (deployId, summaryMode, startTime) => {
|
if (selectedSite.value && selectedCategory.value) {
|
||||||
if (!deployId) return;
|
await handleSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 加载图表数据(加入 stationCode 参数) ====================
|
||||||
|
const loadChartData = async (stationCode, deployId, summaryMode, startTime) => {
|
||||||
|
if (!stationCode) {
|
||||||
|
message.warning('请先选择站点');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!deployId) {
|
||||||
|
message.warning('请先选择设备');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
|
|
@ -301,7 +371,7 @@ const loadChartData = async (deployId, summaryMode, startTime) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetchChartDataByCategory(deployId, summaryMode, formattedDate);
|
const res = await fetchChartDataByCategory(stationCode, deployId, summaryMode, formattedDate);
|
||||||
chartDataList.value = res;
|
chartDataList.value = res;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -313,19 +383,23 @@ const loadChartData = async (deployId, summaryMode, startTime) => {
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 查询(收集所有参数) ====================
|
// ==================== 查询(收集所有参数,包含 stationCode) ====================
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
|
if (!selectedSite.value) {
|
||||||
|
message.warning('请先选择站点');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!selectedCategory.value) {
|
if (!selectedCategory.value) {
|
||||||
message.warning('请先选择设备');
|
message.warning('请先选择设备');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loadChartData(selectedCategory.value, selectedDimension.value, selectedDate.value);
|
loadChartData(selectedSite.value, selectedCategory.value, selectedDimension.value, selectedDate.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 初始化所有图表 ====================
|
// ==================== 初始化所有图表 ====================
|
||||||
const initCharts = () => {
|
const initCharts = () => {
|
||||||
chartInstances.forEach((instance) => {
|
chartInstances.forEach((instance) => {
|
||||||
if (instance._resizeHandler) {
|
if (instance._resizeHandler) {
|
||||||
window.removeEventListener('resize', instance._resizeHandler);
|
window.removeEventListener('resize', instance._resizeHandler);
|
||||||
|
|
@ -357,16 +431,16 @@ const initCharts = () => {
|
||||||
window.addEventListener('resize', resizeHandler);
|
window.addEventListener('resize', resizeHandler);
|
||||||
chart._resizeHandler = resizeHandler;
|
chart._resizeHandler = resizeHandler;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 保留两位小数 ====================
|
// ==================== 保留两位小数 ====================
|
||||||
const roundToTwo = (num) => {
|
const roundToTwo = (num) => {
|
||||||
if (num === undefined || num === null || isNaN(num)) return num;
|
if (num === undefined || num === null || isNaN(num)) return num;
|
||||||
return Math.round(num * 100) / 100;
|
return Math.round(num * 100) / 100;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 计算 y 轴范围(确保 markLine 可见) ====================
|
// ==================== 计算 y 轴范围(确保 markLine 可见) ====================
|
||||||
const calculateYAxisRange = (data, markLines) => {
|
const calculateYAxisRange = (data, markLines) => {
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
return { min: null, max: null };
|
return { min: null, max: null };
|
||||||
}
|
}
|
||||||
|
|
@ -411,10 +485,10 @@ const calculateYAxisRange = (data, markLines) => {
|
||||||
min: roundToTwo(dataMin - padding),
|
min: roundToTwo(dataMin - padding),
|
||||||
max: roundToTwo(dataMax + padding),
|
max: roundToTwo(dataMax + padding),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 生成 ECharts 配置 ====================
|
// ==================== 生成 ECharts 配置 ====================
|
||||||
const getChartOption = (item) => {
|
const getChartOption = (item) => {
|
||||||
// 计算 y 轴范围,确保 markLine 可见
|
// 计算 y 轴范围,确保 markLine 可见
|
||||||
const yAxisRange = calculateYAxisRange(item.data, item.markLines);
|
const yAxisRange = calculateYAxisRange(item.data, item.markLines);
|
||||||
|
|
||||||
|
|
@ -491,7 +565,7 @@ const getChartOption = (item) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
// ========== 柱状图(保持不变) ==========
|
// ========== 柱状图 ==========
|
||||||
{
|
{
|
||||||
name: item.title || '数据',
|
name: item.title || '数据',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
|
|
@ -531,50 +605,6 @@ const getChartOption = (item) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// ========== ✅ 新增:折线图 ==========
|
|
||||||
{
|
|
||||||
name: `${item.title || '数据'}趋势`,
|
|
||||||
type: 'line',
|
|
||||||
data: item.data || [],
|
|
||||||
smooth: true,
|
|
||||||
symbol: 'circle',
|
|
||||||
symbolSize: 6,
|
|
||||||
lineStyle: {
|
|
||||||
color: '#ff6b6b',
|
|
||||||
width: 2,
|
|
||||||
},
|
|
||||||
itemStyle: {
|
|
||||||
color: '#ff6b6b',
|
|
||||||
},
|
|
||||||
areaStyle: {
|
|
||||||
color: {
|
|
||||||
type: 'linear',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
x2: 0,
|
|
||||||
y2: 1,
|
|
||||||
colorStops: [
|
|
||||||
{ offset: 0, color: 'rgba(255, 107, 107, 0.25)' },
|
|
||||||
{ offset: 1, color: 'rgba(255, 107, 107, 0.02)' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
show: true,
|
|
||||||
position: 'top',
|
|
||||||
fontSize: 10,
|
|
||||||
color: '#ff6b6b',
|
|
||||||
fontWeight: 500,
|
|
||||||
formatter: (params) => {
|
|
||||||
return params.value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emphasis: {
|
|
||||||
focus: 'series',
|
|
||||||
},
|
|
||||||
xAxisIndex: 0,
|
|
||||||
yAxisIndex: 0,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
legend: {
|
legend: {
|
||||||
show: true,
|
show: true,
|
||||||
|
|
@ -588,14 +618,6 @@ const getChartOption = (item) => {
|
||||||
name: item.title || '数据',
|
name: item.title || '数据',
|
||||||
icon: 'roundRect',
|
icon: 'roundRect',
|
||||||
},
|
},
|
||||||
// ✅ 新增:折线图图例
|
|
||||||
{
|
|
||||||
name: `${item.title || '数据'}趋势`,
|
|
||||||
icon: 'line',
|
|
||||||
itemStyle: {
|
|
||||||
color: '#ff6b6b',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...(item.markLines || []).map((line) => ({
|
...(item.markLines || []).map((line) => ({
|
||||||
name: line.name,
|
name: line.name,
|
||||||
icon: 'line',
|
icon: 'line',
|
||||||
|
|
@ -606,14 +628,14 @@ const getChartOption = (item) => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 生命周期 ====================
|
// ==================== 生命周期 ====================
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadCategories();
|
await loadCategories();
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
chartInstances.forEach((instance) => {
|
chartInstances.forEach((instance) => {
|
||||||
if (instance._resizeHandler) {
|
if (instance._resizeHandler) {
|
||||||
window.removeEventListener('resize', instance._resizeHandler);
|
window.removeEventListener('resize', instance._resizeHandler);
|
||||||
|
|
@ -623,103 +645,105 @@ onBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
chartInstances.length = 0;
|
chartInstances.length = 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
handleSearch,
|
handleSearch,
|
||||||
loadChartData,
|
loadChartData,
|
||||||
|
selectedSite,
|
||||||
|
siteOptions,
|
||||||
selectedCategory,
|
selectedCategory,
|
||||||
categoryOptions,
|
categoryOptions,
|
||||||
selectedDimension,
|
selectedDimension,
|
||||||
selectedDate,
|
selectedDate,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.dashboard-charts {
|
.dashboard-charts {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
background: #f0f2f5;
|
background: #f0f2f5;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 筛选区域样式 ==================== */
|
/* ==================== 筛选区域样式 ==================== */
|
||||||
.filter-section {
|
.filter-section {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-card {
|
.filter-card {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-card :deep(.ant-card-body) {
|
.filter-card :deep(.ant-card-body) {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-item {
|
.filter-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-label {
|
.filter-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #1d2129;
|
color: #1d2129;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
min-width: 32px;
|
min-width: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 图表区域样式 ==================== */
|
/* ==================== 图表区域样式 ==================== */
|
||||||
.chart-grid {
|
.chart-grid {
|
||||||
margin: 0 -8px;
|
margin: 0 -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-col {
|
.chart-col {
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-card {
|
.chart-card {
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-card:hover {
|
.chart-card:hover {
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-card :deep(.ant-card-head) {
|
.chart-card :deep(.ant-card-head) {
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
min-height: 52px;
|
min-height: 52px;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border-radius: 12px 12px 0 0;
|
border-radius: 12px 12px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-card :deep(.ant-card-head-title) {
|
.chart-card :deep(.ant-card-head-title) {
|
||||||
padding: 14px 0;
|
padding: 14px 0;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-card :deep(.ant-card-body) {
|
.chart-card :deep(.ant-card-body) {
|
||||||
padding: 16px 12px 12px 12px;
|
padding: 16px 12px 12px 12px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-title {
|
.chart-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1d2129;
|
color: #1d2129;
|
||||||
|
|
@ -728,32 +752,32 @@ defineExpose({
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 280px;
|
height: 280px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-instance {
|
.chart-instance {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 280px;
|
height: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空状态样式 */
|
/* 空状态样式 */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
padding: 60px 0;
|
padding: 60px 0;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==================== 响应式调整 ==================== */
|
/* ==================== 响应式调整 ==================== */
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
.dashboard-charts {
|
.dashboard-charts {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
@ -790,9 +814,9 @@ defineExpose({
|
||||||
.chart-card :deep(.ant-card-body) {
|
.chart-card :deep(.ant-card-body) {
|
||||||
padding: 12px 8px 8px 8px;
|
padding: 12px 8px 8px 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 480px) {
|
@media screen and (max-width: 480px) {
|
||||||
.chart-container {
|
.chart-container {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
|
@ -809,5 +833,5 @@ defineExpose({
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue