个股中心,折线图
This commit is contained in:
@@ -80,12 +80,12 @@
|
|||||||
mode="widthFix"></image>
|
mode="widthFix"></image>
|
||||||
<view style="font-size: 28rpx; color: #2B2B2B; font-weight: bold; flex: 1; margin-left: 10rpx;">异动监控
|
<view style="font-size: 28rpx; color: #2B2B2B; font-weight: bold; flex: 1; margin-left: 10rpx;">异动监控
|
||||||
</view>
|
</view>
|
||||||
<!-- <view @click="allAction(1)"
|
<view @click="allAction(1)"
|
||||||
style="border: 1rpx solid #DCDCDC; border-radius: 5rpx; padding: 2rpx 10rpx; display: flex; align-items: center; justify-content: center; margin: 0 10rpx;">
|
style="border: 1rpx solid #DCDCDC; border-radius: 5rpx; padding: 2rpx 10rpx; display: flex; align-items: center; justify-content: center; margin: 0 10rpx;">
|
||||||
<view style="color: #888888; font-size: 22rpx; font-weight: 500;">全部</view>
|
<view style="color: #888888; font-size: 22rpx; font-weight: 500;">全部</view>
|
||||||
<image style="width: 11rpx; height: 6rpx; margin-left: 40rpx;"
|
<image style="width: 11rpx; height: 6rpx; margin-left: 40rpx;"
|
||||||
src="/static/icon/invest/downArrow.png" mode="widthFix"></image>
|
src="/static/icon/invest/downArrow.png" mode="widthFix"></image>
|
||||||
</view> -->
|
</view>
|
||||||
<view @click="allAction(2)"
|
<view @click="allAction(2)"
|
||||||
style="border: 1rpx solid #DCDCDC; border-radius: 5rpx; padding: 2rpx 10rpx; display: flex; align-items: center; justify-content: center;">
|
style="border: 1rpx solid #DCDCDC; border-radius: 5rpx; padding: 2rpx 10rpx; display: flex; align-items: center; justify-content: center;">
|
||||||
<view style="color: #888888; font-size: 22rpx; font-weight: 500;">{{currentDate}}</view>
|
<view style="color: #888888; font-size: 22rpx; font-weight: 500;">{{currentDate}}</view>
|
||||||
@@ -94,9 +94,25 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- <view
|
|
||||||
style="height: 400rpx; display: flex; align-items: center; justify-content: center; background-color: red;">
|
<!-- 图表容器:相对定位(作为右侧最值的定位父级),保留原有宽高 -->
|
||||||
折线图占位 </view> -->
|
<view style="width: 100%; height: 600rpx; position: relative; box-sizing: border-box;">
|
||||||
|
<l-echart ref="chartRef"></l-echart>
|
||||||
|
|
||||||
|
<!-- 右侧最上方:最大值(红色#EC3440,带负号、2位小数+%) -->
|
||||||
|
<view
|
||||||
|
style="position: absolute; top: 0; right: 10rpx; font-size: 24rpx; color: #EC3440; z-index: 99; line-height: 1.2;margin-top: 60rpx;margin-right: 30rpx;"
|
||||||
|
>
|
||||||
|
{{y2MaxText}}
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧最下方:最小值(绿色#01AB5D,带负号、2位小数+%) -->
|
||||||
|
<view
|
||||||
|
style="position: absolute; bottom: 0; right: 10rpx; font-size: 24rpx; color: #01AB5D; z-index: 99; line-height: 1.2;margin-bottom: 110rpx;margin-right: 30rpx;"
|
||||||
|
>
|
||||||
|
{{y2MinText}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view style="height: 1rpx; margin: 0 20rpx; background-color: #E7E7E7;"></view>
|
<view style="height: 1rpx; margin: 0 20rpx; background-color: #E7E7E7;"></view>
|
||||||
<view style="height: 88rpx; display: flex; align-items: center; margin: 0 20rpx;">
|
<view style="height: 88rpx; display: flex; align-items: center; margin: 0 20rpx;">
|
||||||
@@ -317,6 +333,7 @@
|
|||||||
searchStockInfo,
|
searchStockInfo,
|
||||||
stockBasicInfo
|
stockBasicInfo
|
||||||
} from '@/request/api'
|
} from '@/request/api'
|
||||||
|
const echarts = require('../../uni_modules/lime-echart/static/echarts.min.js');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -448,7 +465,13 @@
|
|||||||
searchShow:false, //是否展示搜索结果
|
searchShow:false, //是否展示搜索结果
|
||||||
searchResultList:[], //搜索结果
|
searchResultList:[], //搜索结果
|
||||||
selectSearchStockInfo:null, //选中的搜索股票信息
|
selectSearchStockInfo:null, //选中的搜索股票信息
|
||||||
isShowTime:false
|
isShowTime:false,
|
||||||
|
ec: { lazyLoad: true }, // 延迟加载 ECharts
|
||||||
|
chart: null,
|
||||||
|
y2MaxText: '', // 右侧顶部最大值文本(例:2.36% / -0.89%)
|
||||||
|
y2MinText: '' // 右侧底部最小值文本(例:-3.12% / 0.56%)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad(e) {
|
onLoad(e) {
|
||||||
@@ -706,8 +729,9 @@
|
|||||||
date: this.currentDate
|
date: this.currentDate
|
||||||
}
|
}
|
||||||
marketHotspotOverview(param).then(res => {
|
marketHotspotOverview(param).then(res => {
|
||||||
|
const data = res?.data;
|
||||||
const alerts = res?.data?.alerts || [];
|
const alerts = res?.data?.alerts || [];
|
||||||
|
// ========== 处理指数涨跌 ==========
|
||||||
const changePct = res.data.index.change_pct;
|
const changePct = res.data.index.change_pct;
|
||||||
let numPct = 0;
|
let numPct = 0;
|
||||||
// 校验数值有效性,转成数字类型
|
// 校验数值有效性,转成数字类型
|
||||||
@@ -788,10 +812,282 @@
|
|||||||
const sortedAlerts = processedAlerts.sort(sortByTimeDesc);
|
const sortedAlerts = processedAlerts.sort(sortByTimeDesc);
|
||||||
// 赋值给页面变量的是处理+排序后的数组
|
// 赋值给页面变量的是处理+排序后的数组
|
||||||
this.marketAlertsList = sortedAlerts;
|
this.marketAlertsList = sortedAlerts;
|
||||||
|
// ========== 初始化折线图 ==========
|
||||||
|
this.initChart(data.index.timeline, processedAlerts);
|
||||||
}).catch(error => {
|
}).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) {
|
itemDetails(item) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pagesStock/stockCenterDetails/stockCenterDetails?code=' + item.stock_code
|
url: '/pagesStock/stockCenterDetails/stockCenterDetails?code=' + item.stock_code
|
||||||
|
|||||||
Reference in New Issue
Block a user