From 890be2e3e9b240561fcaccc30363ab85bf958226 Mon Sep 17 00:00:00 2001
From: renzhijun <3051619471@qq.com>
Date: Fri, 6 Feb 2026 17:55:17 +0800
Subject: [PATCH] =?UTF-8?q?=E4=B8=AA=E8=82=A1=E4=B8=AD=E5=BF=83=EF=BC=8C?=
=?UTF-8?q?=E6=8A=98=E7=BA=BF=E5=9B=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pages/geGuCenter/geGuCenter.vue | 310 +++++++++++++++++++++++++++++++-
1 file changed, 303 insertions(+), 7 deletions(-)
diff --git a/pages/geGuCenter/geGuCenter.vue b/pages/geGuCenter/geGuCenter.vue
index 979b111..4bfa81e 100644
--- a/pages/geGuCenter/geGuCenter.vue
+++ b/pages/geGuCenter/geGuCenter.vue
@@ -80,12 +80,12 @@
mode="widthFix">
异动监控
-
+
{{currentDate}}
@@ -94,9 +94,25 @@
-
+
+
+
+
+
+
+
+ {{y2MaxText}}
+
+
+
+
+ {{y2MinText}}
+
+
@@ -317,6 +333,7 @@
searchStockInfo,
stockBasicInfo
} from '@/request/api'
+ const echarts = require('../../uni_modules/lime-echart/static/echarts.min.js');
export default {
data() {
@@ -448,7 +465,13 @@
searchShow:false, //是否展示搜索结果
searchResultList:[], //搜索结果
selectSearchStockInfo:null, //选中的搜索股票信息
- isShowTime:false
+ isShowTime:false,
+ ec: { lazyLoad: true }, // 延迟加载 ECharts
+ chart: null,
+ y2MaxText: '', // 右侧顶部最大值文本(例:2.36% / -0.89%)
+ y2MinText: '' // 右侧底部最小值文本(例:-3.12% / 0.56%)
+
+
}
},
onLoad(e) {
@@ -706,8 +729,9 @@
date: this.currentDate
}
marketHotspotOverview(param).then(res => {
+ const data = res?.data;
const alerts = res?.data?.alerts || [];
-
+// ========== 处理指数涨跌 ==========
const changePct = res.data.index.change_pct;
let numPct = 0;
// 校验数值有效性,转成数字类型
@@ -788,10 +812,282 @@
const sortedAlerts = processedAlerts.sort(sortByTimeDesc);
// 赋值给页面变量的是处理+排序后的数组
this.marketAlertsList = sortedAlerts;
+ // ========== 初始化折线图 ==========
+ this.initChart(data.index.timeline, processedAlerts);
}).catch(error => {
})
},
+
+async initChart(timeline, alerts) {
+ // 无数据直接return
+ if (!timeline || timeline.length === 0) return;
+
+ const chart = await this.$refs.chartRef.init(echarts);
+ this.chartInstance = chart;
+
+ // 1. 提取基础数据:X轴时间、Y轴价格、change_pct(用于右侧最值)
+ const xAxisTime = timeline.map(item => item.time?.trim() || ''); // 时间去空格,统一格式
+ const yAxisPrice = timeline.map(item => Number(item.price) || 0); // 价格兜底0,避免NaN
+ // 提取timeline中的change_pct并转数字,过滤无效值
+ const changePctList = timeline
+ .map(item => Number(item.change_pct))
+ .filter(val => !isNaN(val) && val !== null && val !== undefined);
+
+ // 2. 处理第一个Y轴(上证指数价格):动态计算最值+上下缓冲(增加空值判断,避免报错)
+ const validPrices = yAxisPrice.filter(val => val !== 0 && !isNaN(val));
+ const priceMin = validPrices.length > 0 ? Math.min(...validPrices) : 0;
+ const priceMax = validPrices.length > 0 ? Math.max(...validPrices) : 0;
+ const priceRange = priceMax - priceMin;
+ const yAxisMin = priceRange > 0 ? (priceMin - priceRange * 0.1) : priceMin;
+ const yAxisMax = priceRange > 0 ? (priceMax + priceRange * 0.25) : priceMax;
+
+ // 3. 处理change_pct最值:格式化(保留2位+带%+保留负号)赋值给页面变量
+ let y2Min = 0, y2Max = 0;
+ if (changePctList.length > 0) {
+ y2Min = Math.min(...changePctList);
+ y2Max = Math.max(...changePctList);
+ this.y2MaxText = Number(y2Max).toFixed(2) + '%';
+ this.y2MinText = Number(y2Min).toFixed(2) + '%';
+ } else {
+ this.y2MaxText = '0.00%';
+ this.y2MinText = '0.00%';
+ }
+
+ // 4. 告警点基础处理:同时间保留最高评分(小程序全兼容,带统计)
+ const alertObj = {};
+ let totalAlert = 0; // 过滤后有效告警总数量
+ let sameTimeCount = 0; // 相同时间的告警去重数量
+ let sameScoreCount = 0; // 相同时间且相同评分数量
+
+ alerts.forEach(alert => {
+ if (!alert) return; // 跳过空对象
+ const alertTime = alert.time?.trim() || ''; // 时间去空格,兜底空字符串
+ const alertScore = Number(alert.importance_score); // 转数字
+ if (alertTime === '' || isNaN(alertScore)) return; // 过滤空时间/非数字评分
+
+ // 时间匹配:xAxisTime也做去空格匹配
+ const idx = xAxisTime.findIndex(t => t?.trim() === alertTime);
+ if (idx === -1) return; // 时间不匹配直接跳过
+
+ totalAlert++;
+ // 同时间保留最高评分核心逻辑
+ if (!alertObj[alertTime]) {
+ alertObj[alertTime] = { ...alert, idx, importance_score: alertScore };
+ } else {
+ sameTimeCount++;
+ const existAlert = alertObj[alertTime];
+ if (alertScore > existAlert.importance_score) {
+ alertObj[alertTime] = { ...alert, idx, importance_score: alertScore };
+ } else if (alertScore === existAlert.importance_score) {
+ sameScoreCount++;
+ }
+ }
+ });
+
+ // ===== 核心修改:10分钟时间段分组(如09:40-09:50),组内按评分降序取第一条 =====
+ // 辅助函数1:将时间字符串(09:42)转为分钟数(便于计算分组)
+ const timeToMinutes = (timeStr) => {
+ const [hour, minute] = timeStr.split(':').map(Number);
+ return hour * 60 + minute;
+ };
+ // 辅助函数2:根据分钟数,生成所属的10分钟时间段(如09:42→09:40-09:50)
+ const get10MinGroup = (minutes) => {
+ // 取整10分钟作为分组起点(如42分→40分,35分→30分,5分→0分)
+ const startMin = Math.floor(minutes / 10) * 10;
+ const endMin = startMin + 9; // 分组终点(起点+9分钟)
+ // 转成时间字符串并补0,返回「HH:MM-HH:MM」格式
+ const formatTime = (m) => {
+ const h = Math.floor(m / 60).toString().padStart(2, '0');
+ const mi = (m % 60).toString().padStart(2, '0');
+ return `${h}:${mi}`;
+ };
+ return `${formatTime(startMin)}-${formatTime(endMin)}`;
+ };
+ // 辅助函数3:10分钟分组核心逻辑→分组归类→组内降序→取第一条
+ const filterBy10MinGroup = (alertObj) => {
+ // 步骤1:提取alertObj中所有有效时间,转为[{group: '09:40-09:50', score: 90, data: 告警数据}, ...]
+ const alertGroupList = Object.keys(alertObj)
+ .filter(time => time && time.includes(':')) // 过滤无效时间
+ .map(time => {
+ const minutes = timeToMinutes(time);
+ return {
+ group: get10MinGroup(minutes), // 所属10分钟分组
+ score: alertObj[time].importance_score, // 告警评分
+ data: alertObj[time] // 原始告警数据
+ };
+ });
+
+ if (alertGroupList.length === 0) return {}; // 无有效告警,直接返回空
+
+ // 步骤2:按分组聚合,key=分组名,value=该组下所有告警
+ const groupMap = {};
+ alertGroupList.forEach(item => {
+ if (!groupMap[item.group]) {
+ groupMap[item.group] = [];
+ }
+ groupMap[item.group].push(item);
+ });
+
+ // 步骤3:遍历每个分组,按评分**降序**排序,取第一条(评分最高的)
+ const finalAlertObj = {};
+ Object.keys(groupMap).forEach(groupName => {
+ const groupItems = groupMap[groupName];
+ // 降序排序(评分高的在前,相同评分保留原顺序)
+ const sortedItems = groupItems.sort((a, b) => b.score - a.score);
+ // 取分组内第一条(评分最高的),按原始时间作为key
+ const topItem = sortedItems[0];
+ finalAlertObj[topItem.data.time] = topItem.data;
+ });
+
+ return finalAlertObj;
+ };
+
+ // 先执行基础去重,再执行10分钟分组筛选
+ const filteredAlertObj = filterBy10MinGroup(alertObj);
+
+ // ===== 多维度日志打印(更新分组统计,更直观)=====
+ const originalKeyLen = Object.keys(alertObj).length; // 基础去重后数量
+ const filteredKeyLen = Object.keys(filteredAlertObj).length; // 10分钟分组后数量
+ // 新增:打印分组详情(便于排查分组是否正确)
+ const groupDetail = Object.keys(filteredAlertObj).map(time => {
+ const minutes = timeToMinutes(time);
+ return { time, group: get10MinGroup(minutes), score: filteredAlertObj[time].importance_score };
+ });
+ console.log('===== 告警点处理全统计(10分钟分组版)=====');
+ console.log('1. 过滤后有效告警总数量:', totalAlert);
+ console.log('2. 相同时间的告警去重数量:', sameTimeCount);
+ console.log('3. 相同时间且相同评分数量:', sameScoreCount);
+ console.log('4. 基础去重后(同时间最高评分)数量:', originalKeyLen);
+ console.log('5. 10分钟分组后(每组取最高评分)数量:', filteredKeyLen);
+ console.log('6. 分组详情(时间→所属分组→评分):', groupDetail);
+ console.log('7. 分组后最终告警详情:', filteredAlertObj);
+
+ // 5. 格式化ECharts所需的markPoint数据(保留原逻辑,兼容已修复的显示配置)
+ const alertPoints = Object.values(filteredAlertObj).map(alert => {
+ // 新增:校验索引有效性,避免x/y轴匹配失败(核心修复显示问题)
+ const validIdx = !isNaN(alert.idx) && alert.idx >= 0 && alert.idx < xAxisTime.length ? alert.idx : 0;
+ const xVal = xAxisTime[validIdx] || '';
+ const yVal = !isNaN(yAxisPrice[validIdx]) ? yAxisPrice[validIdx] : 0;
+ return {
+ name: alert.concept_name || '未知概念', // 概念名兜底
+ coord: [xVal, yVal], // 确保x轴值严格匹配xAxis.data,y轴值有效
+ value:yVal,
+ itemStyle: { color: '#FF4444' }, // 告警点红色
+ label: {
+ formatter(){
+ return alert.concept_name
+ },
+ show: true,
+ position: 'top',
+ fontSize: 10,
+ color: '#FF4444',
+ fontWeight: '500',
+ distance: 5
+ }
+
+ };
+ }).filter(Boolean); // 最终兜底过滤无效项
+ console.log('8. 最终ECharts告警点数据(10分钟分组):', alertPoints);
+
+ // 6. ECharts核心配置项(保留所有显示修复:show/z/描边等)
+ const option = {
+ grid: { left: '4%', right: '8%', bottom: '8%', top: '10%', containLabel: true },
+ xAxis: {
+ type: 'category',
+ boundaryGap: false,
+ data: xAxisTime,
+ axisLabel: {
+ fontSize: 12,
+ rotate: 30,
+ interval: Math.floor(xAxisTime.length / 6)
+ },
+ axisTick: {
+ alignWithLabel: true,
+ interval: Math.floor(xAxisTime.length / 6)
+ }
+ },
+ yAxis: [
+ {
+ type: 'value',
+ min: yAxisMin,
+ max: yAxisMax,
+ nameTextStyle: { fontSize: 12 },
+ axisLabel: {
+ formatter: (val) => val.toFixed(0),
+ fontSize: 12
+ },
+ splitLine: { lineStyle: { type: 'dashed', color: '#EEEEEE' } },
+ boundaryGap: [0.05, 0.05] // 上下留5%缓冲,避免顶点告警点被裁剪
+ }
+ ],
+ dataZoom: [],
+ series: [
+ {
+ name: '上证指数',
+ type: 'line',
+ smooth: true,
+ symbol: 'circle',
+ symbolSize: 5,
+ itemStyle: { color: '#0092FF' },
+ lineStyle: {
+ width: 2,
+ color: '#0092FF',
+ shadowColor: 'rgba(0,146,255,0.5)',
+ shadowBlur: 8,
+ shadowOffsetY: 3,
+ shadowOffsetX: 0
+ },
+ areaStyle: {
+ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: 'rgba(0,146,255,0.25)' },
+ { offset: 1, color: 'rgba(0,146,255,0)' }
+ ])
+ },
+ data: yAxisPrice,
+ // 保留所有markPoint显示修复配置(强制显示/层级/样式)
+ markPoint: {
+ show: true, // 强制开启显示(关键!)
+ symbol: 'circle',
+ symbolSize:5, // 比折线大,避免被遮挡
+ z: 10, // 层级置顶,不被任何元素遮挡
+
+ data: alertPoints,
+ itemStyle: {
+ color: '#FF4444',
+ borderColor: '#fff', // 白色描边,更醒目
+ borderWidth: 1,
+
+ },
+ label: {
+ show: true,
+ position: 'top',
+ fontSize: 10,
+ color: '#FF4444',
+ fontWeight: '500',
+ distance: 6,
+ backgroundColor: 'rgba(255,255,255,0.8)', // 标签白色背景,防融合
+ padding: [2, 4],
+ borderRadius: 2,
+ borderColor: '#FF4444', // 白色描边,更醒目
+ borderWidth: 1,
+ }
+ },
+ yAxisIndex: 0
+ }
+ ],
+
+
+ };
+console.log('7. 分组后最终告警详情:', JSON.stringify(option.series));
+ chart.setOption(option, true);
+ // 窗口自适应(优化:避免重复监听,页面销毁时可移除)
+ uni.onWindowResize(() => {
+ this.chartInstance && this.chartInstance.resize();
+ });
+},
+
+
itemDetails(item) {
uni.navigateTo({
url: '/pagesStock/stockCenterDetails/stockCenterDetails?code=' + item.stock_code