Files
JiaZhiQianYan/pages/geGuCenter/geGuCenter.vue
renzhijun 44d8ecf318 饼图
2026-01-31 11:40:28 +08:00

849 lines
31 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<navBar leftText="个股中心" hideNavBg hideBack></navBar>
<image class="topBg absolute" src="/static/image/index/conceptTopBg.png" mode="widthFix"></image>
<view class="searchC fixed flex" :style="'top:'+navH+'px;'">
<image class="icon" src="/static/icon/home/conceptCenter/search.png" mode="widthFix"></image>
<input class="flex1" type="text" v-model="keywords" placeholder="输入股票代码或名称"
placeholder-style="color:#eeeeee" confirm-type="search" @confirm="clickSearch()" />
</view>
<scroll-view scroll-y class="stockDetailsC fixed" :style="'top:'+contentTop+'px;'">
<view>
<view style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16rpx; padding: 20rpx;">
<view v-for="(item,index) in topLists" :key="index"
style="padding: 20rpx 26rpx 26rpx 35rpx; border: 1rpx dashed #777777; overflow: hidden; position: relative;">
<image
style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; width: 100%; height: 100%;"
:src="item.backIcon" mode="aspectFill"></image>
<view style="position: relative; z-index: 1;">
<view style="font-size: 24rpx; color: #777777; font-weight: 500;">{{item.title}}</view>
<view style="font-size: 30rpx; margin-top: 10rpx; font-weight: bold;"
:style="{color: item.color}">{{item.value}}</view>
</view>
</view>
</view>
<view style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 16rpx; margin: 0 20rpx;">
<view @click="handleTypeClick(index)" v-for="(item,index) in topLists2" :key="index"
style="border: 1rpx solid #D2D2D2; padding: 12rpx;"
:style="{border: `1rpx solid ${list2Index == index ? '#F2C369' : '#D2D2D2'}`}">
<view style="font-size: 24rpx; color: #070707; font-weight: bold; text-align: center;"
:style="{color: (list2Index == index ? '#BB8520' : '#070707'), 'background-color': (list2Index == index ? '#FFFAF1' : '#FFFFFF')}">
{{item.title}}
</view>
<view style="font-size: 20rpx; font-weight: 400; text-align: center;"
:style="{color: (list2Index == index ? '#BB8520' : '#070707')}">{{item.value}}</view>
</view>
</view>
<!-- '股票名称', '涨跌幅', '市值', '成交额', '行业' -->
<view
style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10rpx; background-color: #FAFAFC; line-height: 60rpx; margin: 0 20rpx; margin-top: 20rpx;">
<view v-for="(item,index) in ['股票名称', '涨跌幅', '市值', '成交额', '行业']" :key="index"
style="color: #666666; font-size: 20rpx; font-weight: 500; text-align: center;">
{{item}}
</view>
</view>
<!-- '股票名称', '涨跌幅', '市值', '成交额', '行业' 内容 -->
<view v-for="(obj, j) in filteredData" @click="itemDetails(obj)"
style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 10rpx; min-height: 60rpx; margin: 0 20rpx;"
:style="{'background-color': (j % 2 == 0 ? '#fff' : '#FAFAFC')}">
<!-- 外层循环每一行数据 -->
<view v-for="(item,index) in getTableItem(obj)" :key="index"
style="padding: 10rpx 0; color: #666666; font-size: 20rpx; font-weight: 500; text-align: center; display: flex; justify-content: center; align-items: center; flex-direction: column;"
:style="{ color: (index == 0 ? '#222222' : index == 1 ? (item[2] === 'positive' ? '#EC3440' : '#01AB5D') : '#666666') }">
<view>{{item[0]}}</view>
<view v-if="index == 0" style="color: #666666; font-size: 20rpx; font-weight: 500;">{{item[1]}}
</view>
</view>
</view>
<view @click="moreAction"
style="display: flex;align-items: center;justify-content: center; height: 80rpx;">
<view style="font-size: 24rpx; color: #3D455C; font-weight: 500;">查看更多</view>
<image style="width: 10rpx; height: 19rpx; margin-left: 20rpx;"
src="/static/icon/home/conceptCenter/next.png" mode="widthFix"></image>
</view>
<view style="height: 1rpx; margin: 0 20rpx; background-color: #E7E7E7;"></view>
<view style="height: 78rpx; display: flex; align-items: center; margin: 0 20rpx;">
<image style="width: 40rpx; height: 40rpx;" src="/pages/geGuCenter/icon/ydjk-icon.png"
mode="widthFix"></image>
<view style="font-size: 28rpx; color: #2B2B2B; font-weight: bold; flex: 1; margin-left: 10rpx;">异动监控
</view>
<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;">
<view style="color: #888888; font-size: 22rpx; font-weight: 500;">全部</view>
<image style="width: 11rpx; height: 6rpx; margin-left: 40rpx;"
src="/static/icon/invest/downArrow.png" mode="widthFix"></image>
</view>
<view @click="allAction(2)"
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>
<image style="width: 11rpx; height: 6rpx; margin-left: 20rpx;"
src="/static/icon/invest/downArrow.png" mode="widthFix"></image>
</view>
</view>
<view
style="height: 400rpx; display: flex; align-items: center; justify-content: center; background-color: red;">
折线图占位 </view>
<view style="height: 1rpx; margin: 0 20rpx; background-color: #E7E7E7;"></view>
<view style="height: 88rpx; display: flex; align-items: center; margin: 0 20rpx;">
<image style="width: 40rpx; height: 40rpx;" src="/pages/geGuCenter/icon/ydjk-icon.png"
mode="widthFix"></image>
<view style="font-size: 28rpx; color: #2B2B2B; font-weight: bold; flex: 1; margin-left: 10rpx;">
板块异动明细
</view>
</view>
<view v-for="(item,index) in marketAlertsList" :key="index" @click="bkydAction(item)"
style="margin: 20rpx; margin-top: 0; background-color: #FAFAFC; border-radius: 10rpx; overflow: hidden; padding: 20rpx 30rpx; font-weight: 500;">
<view style="color: #888888; font-size: 20rpx;">{{item.time}}</view>
<view style="display: flex; align-items: center; margin-top: 10rpx;">
<view style="color: #2B2B2B; font-weight: bold; font-size: 26rpx; margin-right: 10rpx;">
{{item.concept_name}}
</view>
<view :style="{
color: alertTypeConfig[item.alert_type]?.color || '#FF7A45',
fontSize: '20rpx',
border: '1rpx solid ' + (alertTypeConfig[item.alert_type]?.color || '#FF7A45'),
borderRadius: '15rpx',
height: '30rpx',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '0 10rpx',
boxSizing: 'border-box'
}">
<image style="width: 18rpx; height: auto;"
:style="{ filter: alertTypeConfig[item.alert_type]?.filter || '' }"
src="/pages/geGuCenter/icon/ydjk-zs.png" mode="widthFix">
</image>
<view style="margin-left: 10rpx;">
{{ alertTypeConfig[item.alert_type]?.text || '异动' }}
</view>
</view>
<view style="flex: 1; font-size: 22rpx; text-align: right;">
<text style="color: #71675D;">板块均涨</text>
<text :style="{
color: Number(item.alpha) > 0 ? '#EC3440' : '#01AB5D',
fontWeight: 'bold',
marginLeft: '5rpx',
marginRight: '25rpx'
}">
{{ item.formattedAvg }}%
</text>
<text :style="{
color: item.upCount > 0 ? '#EC3440' : '#888888',
fontWeight: 'bold'
}">
{{item.upCount}}
</text>
<text style="color: #888888; margin: 0 5rpx;">/</text>
<text :style="{
color: item.downCount > 0 ? '#01AB5D' : '#888888',
fontWeight: 'bold'
}">
{{item.downCount}}
</text>
</view>
</view>
<view
style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20rpx; font-size: 22rpx; color: #71675D; font-weight: 500; margin-top: 15rpx;">
<view style="text-align: left;">
<text>评分</text>
<text
style="color: #EC3440; font-weight: bold; margin-left: 10rpx;">{{ Math.round(item.final_score) }}</text>
</view>
<view style="text-align: center;">
<text>超额收益</text>
<text style="color: #EC3440; font-weight: bold; margin-left: 10rpx;">+ 0%</text>
</view>
<view style="text-align: right;"
v-if="item && Number(item.limit_up_ratio) > 0 && !isNaN(Number(item.limit_up_ratio))">
<text>涨停比</text>
<text style="color: #EC3440; font-weight: bold; margin-left: 10rpx;">
{{ formatLimitUpRatio(item.limit_up_ratio, 0) }}
</text>
</view>
</view>
</view>
<view style="height: 25rpx;"></view>
</view>
</scroll-view>
<uni-popup ref="typePopup" type="bottom" :safeArea="false">
<view class="detailPopup">
<view
style="height: 120rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 500;">
<view style="color: #727A8E; padding: 0 25rpx;" @click="closeAction(1)">取消</view>
<view style="flex: 1; text-align: center; color: #333333; font-size: 36rpx; font-weight: bold;">选择分类
</view>
<view style="color: #D79412; padding: 0 25rpx;" @click="confirmAction(1)">确定</view>
</view>
<view v-for="(item,index) in typeList" :key="index">
<view style="height: 1rpx; background-color: #EAEAEA; margin: 0 20rpx;"></view>
<view style="display: flex; align-items: center; justify-content: center; height: 80rpx;">
<image style="width: 26rpx; height: 26rpx; margin-right: 18rpx;" :src="item.backIcon"
mode="aspectFit"></image>
<view
style="min-width: 100rpx; text-align: center; font-size: 24rpx; font-weight: 500; color: #070707;">
{{item.title}}
</view>
</view>
</view>
</view>
</uni-popup>
<uni-popup ref="datePopup" type="bottom" :safeArea="false">
<view class="detailPopup">
<view
style="height: 120rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 500;">
<view style="color: #727A8E; padding: 0 25rpx;" @click="closeAction(2)">取消</view>
<view style="flex: 1; text-align: center; color: #333333; font-size: 36rpx; font-weight: bold;">选择日期
</view>
<view style="color: #D79412; padding: 0 25rpx;" @click="confirmAction(2)">确定</view>
</view>
<view style="margin: 0 38rpx; padding-bottom: 38rpx;">
<LCCalendar2 @date-change="handleDateChange"></LCCalendar2>
</view>
</view>
</uni-popup>
<uni-popup ref="detailPopup" type="bottom" :safeArea="false">
<view class="detailPopup" style="height: 550rpx;">
<view
style="height: 120rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 500;">
<view style="color: #727A8E; width: 60rpx;"></view>
<view style="flex: 1; text-align: center; color: #333333; font-size: 36rpx; font-weight: bold;">详情
</view>
<view
style="color: #D79412; width: 60rpx; display: flex; align-items: center; justify-content: center;"
@click="closeAction(3)">
<image style="width: 20rpx; height: 20rpx;" src="/static/icon/home/close.png" mode="widthFix">
</image>
</view>
</view>
<view style="height: 1rpx; margin: 0 20rpx; background-color: #E7E7E7;"></view>
<view
style="padding: 0 25rpx; box-sizing: border-box; height: 45rpx; margin: 0 45rpx; margin-top: 15rpx; background-color: #FAFAFC;">
<view style="display: flex; align-items: center; height: 100%;">
<view style="color: #666666; font-weight: 500; font-size: 24rpx; margin-right: 10rpx;">相关股票
</view>
<view style="flex: 1; font-size: 22rpx; text-align: right;">
<text style="color: #71675D;">板块均涨</text>
<text :style="{
color: Number(formattedAvg) > 0 ? '#EC3440' : '#01AB5D',
fontWeight: 'bold',
marginLeft: '10rpx',
marginRight: '20rpx'
}">{{ formattedAvg }}%</text>
<text :style="{
color: upCount > 0 ? '#EC3440' : '#888888',
fontWeight: 'bold'
}">
{{upCount}}
</text>
<text style="color: #888888; margin: 0 5rpx;">/</text>
<text :style="{
color: downCount > 0 ? '#01AB5D' : '#888888',
fontWeight: 'bold'
}">
{{downCount}}
</text>
<text style="color: #71675D; margin-left: 20rpx;">涨停比</text>
<text
style="color: #EC3440; font-weight: bold; margin-left: 10rpx;">{{ formatLimitUpRatio(limit_up_ratio, 0) }}</text>
</view>
</view>
</view>
<scroll-view scroll-y="true" show-scrollbar="false" style="height: 360rpx; ">
<view v-for="(item, index) in conceptStocksList" :key="index"
style="padding: 0 25rpx; box-sizing: border-box; height: 45rpx; margin: 0 45rpx; display: flex; align-items: center; font-weight: 500;"
:style="{ 'background-color': (index % 2 == 0 ? '#fff' : '#FAFAFC')}">
<!-- 股票名称 -->
<view style="color: #222222; font-size: 24rpx; font-weight: bold;">{{ item.name }}</view>
<!-- 股票代码 -->
<view style="flex: 1; color: #888888; font-size: 20rpx; margin: 0 20rpx;">{{ item.code }}</view>
<!-- 涨跌幅动态绑定颜色和格式化显示 -->
<view
:style="{ color: getChangeColor(item.change_pct), fontSize: '22rpx', fontWeight: 'bold' }">
{{ formatChangePct(item.change_pct) }}
</view>
</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
inject
} from 'vue'
import {
conceptsDailyTop,
marketHeatmap,
marketStatistics,
marketHotspotOverview,
conceptStocks
} from '@/request/api'
export default {
data() {
return {
navH: inject('navHeight'),
contentTop: '',
currentDate: '', // 最终要赋值的日期
selectedDate: '', // 临时存储选中的日期
allStockData: [],
filteredData: [],
conceptStocksList: [],
alertTypeConfig: {
'surge': {
text: '异动',
color: '#FF7A45', // rgb(255, 122, 69)
filter: 'brightness(0) saturate(100%) invert(54%) sepia(60%) saturate(467%) hue-rotate(344deg) brightness(102%) contrast(101%)'
},
'shrink_surge_up': {
text: '缩量急涨',
color: '#722ED1', // rgb(114, 46, 209)
filter: 'brightness(0) saturate(100%) invert(24%) sepia(90%) saturate(2865%) hue-rotate(266deg) brightness(87%) contrast(98%)'
},
'volume_surge_up': {
text: '放量急涨',
color: '#EB2F96', // rgb(235, 47, 150)
filter: 'brightness(0) saturate(100%) invert(34%) sepia(82%) saturate(1970%) hue-rotate(313deg) brightness(91%) contrast(94%)'
},
'volume_oscillation': {
text: '放量震荡',
color: '#13C2C2', // rgb(19, 194, 194)
filter: 'brightness(0) saturate(100%) invert(71%) sepia(62%) saturate(487%) hue-rotate(142deg) brightness(91%) contrast(93%)'
},
'surge_up': {
text: '急涨',
color: '#FF4D4F', // rgb(255, 77, 79)
filter: 'brightness(0) saturate(100%) invert(42%) sepia(93%) saturate(727%) hue-rotate(346deg) brightness(102%) contrast(104%)'
},
'surge_down': {
text: '急跌',
color: '#52C41A', // rgb(82, 196, 26)
filter: 'brightness(0) saturate(100%) invert(68%) sepia(65%) saturate(456%) hue-rotate(71deg) brightness(91%) contrast(86%)'
},
'shrink_surge_down': {
text: '缩量急跌',
color: '#FF7A45', // rgb(255, 122, 69)
filter: 'brightness(0) saturate(100%) invert(54%) sepia(60%) saturate(467%) hue-rotate(344deg) brightness(102%) contrast(101%)'
}
},
topLists: [{
title: '大盘涨跌幅',
value: '+0.00%',
color: '#EC3440',
backIcon: '/static/icon/gegu/gg-top-0.png'
},
{
title: '涨停/跌停',
value: '+0.00%',
color: '#070707',
backIcon: '/static/icon/gegu/gg-top-1.png'
},
{
title: '多空对比',
value: '0/0',
color: '#070707',
backIcon: '/static/icon/gegu/gg-top-2.png'
},
{
title: '今日成交额',
value: '0万亿',
color: '#070707',
backIcon: '/static/icon/gegu/gg-top-3.png'
},
{
title: 'A股总市值',
value: '0万亿',
color: '#070707',
backIcon: '/static/icon/gegu/gg-top-4.png'
},
{
title: '连板龙头',
value: '0只',
color: '#F59B38',
backIcon: '/static/icon/gegu/gg-top-5.png'
}
],
topLists2: [{
title: '超大盘股',
value: '>1000亿',
},
{
title: '大盘股',
value: '500-1000亿',
},
{
title: '中盘股',
value: '100-500亿',
}
],
list2Index: 0,
typeList: [{
title: '缩量急涨',
backIcon: '/static/icon/gegu/cate-0.png'
},
{
title: '异动',
backIcon: '/static/icon/gegu/cate-1.png'
},
{
title: '急跌',
backIcon: '/static/icon/gegu/cate-2.png'
},
{
title: '急涨',
backIcon: '/static/icon/gegu/cate-3.png'
},
{
title: '放量震荡',
backIcon: '/static/icon/gegu/cate-4.png'
}
],
marketAlertsList: [],
formattedAvg: 0,
upCount: 0,
downCount: 0,
limit_up_ratio: 0
}
},
onLoad(e) {
this.activeIndex = e.index
this.contentTop = this.navH + (20 + 70 + 25) / 750 * inject('windowWidth')
//this.conceptsDailyTop()
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = now.getDate().toString().padStart(2, '0')
this.currentDate = `${year}-${month}-${day}`
},
onShow() {
this.marketHeatmap();
this.marketStatistics()
this.marketHotspotListOverview()
},
methods: {
formatAlpha(value) {
// 1. 空值/非数字处理
if (value === null || value === undefined || isNaN(Number(value))) {
return '0.0';
}
// 2. 转数字后保留1位小数
return Number(value).toFixed(1);
},
handleTypeClick(index) {
this.list2Index = index;
// 先请求数据,再筛选
this.marketHeatmap(this.currentDate);
},
getTableItem(obj) {
// 1. 处理空值,避免 toFixed 调用时报错
const marketCap = obj.market_cap ? obj.market_cap.toFixed(2) : '0.00';
const amount = obj.amount ? obj.amount.toFixed(2) : '0.00';
// 统一处理涨跌幅空值默认0转数字避免字符串干扰判断
const changePercent = obj.change_percent ? Number(obj.change_percent) : 0;
// 2. 处理涨跌幅的符号和类型标记
let changePercentStr = '';
let changeType = ''; // 标记正负positive/negative/zero
if (changePercent > 0) {
changePercentStr = `+${changePercent}%`; // 正数拼接+号
changeType = 'positive';
} else if (changePercent < 0) {
changePercentStr = `${changePercent}%`; // 负数直接显示
changeType = 'negative';
} else {
changePercentStr = '0%'; // 0值统一显示
changeType = 'zero';
}
// 3. 返回数组:涨跌幅位置新增 changeType 用于模板判断颜色
return [
[obj.stock_name, obj.stock_code],
[changePercentStr, '', changeType], // 第三个元素存类型标记
[`${marketCap}亿元`],
[`${amount}亿元`],
[obj.industry || '暂无'] // 处理行业为空的情况
];
},
// 处理涨停比:转百分比 + 四舍五入(可指定保留小数位数)
formatLimitUpRatio(value, decimalPlaces = 0) {
// 1. 先判断值是否有效无效直接返回空或0%
if (!value || isNaN(Number(value))) {
return '0%';
}
// 2. 正常计算逻辑
const percentValue = Number(value) * 100;
const result = decimalPlaces === 0 ? Math.round(percentValue) : percentValue.toFixed(decimalPlaces);
return `${result}%`;
},
conceptsDailyTop() {
conceptsDailyTop().then(res => {
}).catch(error => {
})
},
marketHeatmap(currentDate) {
let param = {
limit: 500
}
if (currentDate && currentDate !== 'undefined' && currentDate.trim() !== '') {
param.date = currentDate;
}
marketHeatmap(param).then(res => {
this.topLists[2].value = res.statistics.rising_count + "/" + res.statistics.falling_count;
// 存储原始数据
this.allStockData = res.data || [];
// 2. 计算涨停数和跌停数(核心新增逻辑)
// 涨停数:涨幅 >= 9.9% 的股票数量
const limitUpCount = this.allStockData.filter((s) => {
// 做空值/非数字保护避免change_percent异常导致判断错误
const changePercent = Number(s.change_percent);
return !isNaN(changePercent) && changePercent >= 9.9;
}).length;
// 跌停数:跌幅 <= -9.9% 的股票数量
const limitDownCount = this.allStockData.filter((s) => {
const changePercent = Number(s.change_percent);
return !isNaN(changePercent) && changePercent <= -9.9;
}).length;
this.topLists[1].value = limitUpCount + "/" + limitDownCount;
this.topLists[5].value = limitUpCount + "只";
// 调用筛选方法
this.filterStockByMarketCap();
}).catch(error => {
})
},
// 根据市值区间筛选数据
filterStockByMarketCap() {
const {
list2Index,
allStockData
} = this;
let filtered = [];
switch (list2Index) {
case 0: // 超大盘股(>1000亿
filtered = allStockData.filter(item => item.market_cap > 1000);
break;
case 1: // 大盘股500-1000亿
filtered = allStockData.filter(item => item.market_cap >= 500 && item.market_cap <= 1000);
break;
case 2: // 中盘股100-500亿
filtered = allStockData.filter(item => item.market_cap >= 100 && item.market_cap <= 500);
break;
default:
filtered = allStockData;
}
this.filteredData = filtered.slice(0, 10);
},
marketStatistics() {
marketStatistics().then(res => {
this.topLists[3].value = this.formatToTrillion(res.summary.total_amount);
// 格式化 total_market_cap 为 114.7 万亿
this.topLists[4].value = this.formatToTrillion(res.summary.total_market_cap);
}).catch(error => {
this.topLists[3].value = '0.0 万亿';
this.topLists[4].value = '0.0 万亿';
})
},
formatToTrillion(num) {
if (typeof num !== 'number' || isNaN(num)) {
return '0.0 万亿'; // 处理非数字的异常情况
}
// 转换为万亿单位(除以 10000并保留 1 位小数
const trillionValue = (num / 10000).toFixed(1);
return `${trillionValue} 万亿`;
},
marketHotspotListOverview() {
let param = {
date: this.currentDate
}
marketHotspotOverview(param).then(res => {
const alerts = res?.data?.alerts || [];
const changePct = res.data.index.change_pct;
let numPct = 0;
// 校验数值有效性,转成数字类型
if (changePct && !isNaN(Number(changePct))) {
numPct = Number(changePct);
}
const roundedPct = Math.round(numPct * 100) / 100;
const fixedPct = roundedPct.toFixed(2);
// 3. 处理正负符号和百分号
let formattedPct = '';
if (roundedPct > 0) {
formattedPct = `+${fixedPct}%`; // 正数拼接+号
} else if (roundedPct < 0) {
formattedPct = `${fixedPct}%`; // 负数直接显示
} else {
formattedPct = '0.00%'; // 0值统一显示
}
// 4. 根据正负值设置颜色
const color = roundedPct > 0 ? '#EC3440' : (roundedPct < 0 ? '#01AB5D' : '#666666');
// 5. 赋值给topLists
this.topLists[0].value = formattedPct;
this.topLists[0].color = color;
// ========== 新增:处理每个异动条目的均涨/涨数/跌数 ==========
const processedAlerts = alerts.map(alertItem => {
// 1. 获取当前异动条目下的股票列表(假设字段是 stocks需根据实际接口调整
const stocks = alertItem.stocks || [];
// 2. 过滤有效股票change_pct 非空且是数字)
const validStocks = stocks.filter(s => s.change_pct != null && !isNaN(Number(s
.change_pct)));
// 3. 计算板块均涨
const avgChange = validStocks.length > 0 ?
validStocks.reduce((sum, s) => sum + Number(s.change_pct), 0) / validStocks
.length :
0;
// 4. 计算上涨/下跌股票数量
const upCount = validStocks.filter(s => Number(s.change_pct) > 0).length;
const downCount = validStocks.filter(s => Number(s.change_pct) < 0).length;
// 5. 格式化均涨值保留2位小数处理正负号
const roundedAvg = Math.round(avgChange * 100) / 100; // 四舍五入保留2位
const formattedAvg = roundedAvg > 0 ? `+${roundedAvg.toFixed(2)}` : roundedAvg
.toFixed(2);
// 6. 返回原数据 + 新增计算字段
return {
...alertItem,
alpha: avgChange, // 供模板中判断颜色和显示数值
upCount: upCount, // 上涨股票数
downCount: downCount, // 下跌股票数
formattedAvg: formattedAvg // 格式化后的均涨值(带正负号)
};
});
// 2. 定义时间排序函数:将 time 字符串(如 "09:42")转换为分钟数进行比较
const sortByTimeDesc = (a, b) => {
// 把 "HH:MM" 格式的时间转成分钟数(比如 09:42 → 9*60+42=582 分钟)
const timeToMinutes = (timeStr) => {
const [hours, minutes] = timeStr.split(':').map(Number);
return hours * 60 + minutes;
};
// 计算两个条目的分钟数倒序排列b - a 实现时间大的在前)
const minutesA = timeToMinutes(a.time);
const minutesB = timeToMinutes(b.time);
return minutesB - minutesA;
};
// 3. 对 alerts 数组进行排序
const sortedAlerts = processedAlerts.sort(sortByTimeDesc);
// 赋值给页面变量的是处理+排序后的数组
this.marketAlertsList = sortedAlerts;
}).catch(error => {
})
},
itemDetails(item) {
uni.navigateTo({
url: '/pagesStock/stockCenterDetails/stockCenterDetails?code=' + item.stock_code
})
},
moreAction() {
uni.navigateTo({
url: '/pages/geGuCenter/detail?currentDate=' + this.currentDate
})
},
allAction(index) {
if (index == 1) {
this.$refs["typePopup"].open()
} else if (index == 2) {
this.$refs["datePopup"].open()
}
},
closeAction(index) {
if (index == 1) {
this.$refs["typePopup"].close()
} else if (index == 2) {
this.$refs["datePopup"].close()
} else if (index == 3) {
this.$refs["detailPopup"].close()
}
},
handleDateChange(date) {
this.selectedDate = date
console.log('选中的日期:', date)
},
confirmAction(index) {
if (index == 1) {
this.$refs["typePopup"].close()
} else if (index == 2) {
if (this.selectedDate) {
this.currentDate = this.selectedDate
console.log('最终确认的日期:', this.currentDate)
} else {
// 如果没有选择日期,使用当前日期
const now = new Date()
const year = now.getFullYear()
const month = (now.getMonth() + 1).toString().padStart(2, '0')
const day = now.getDate().toString().padStart(2, '0')
this.currentDate = `${year}-${month}-${day}`
}
this.marketHeatmap(this.currentDate)
this.marketStatistics()
this.marketHotspotListOverview()
this.$refs["datePopup"].close()
}
},
bkydAction(item) {
this.$refs["detailPopup"].open()
this.formattedAvg = item.formattedAvg,
this.upCount = item.upCount,
this.downCount = item.downCount,
this.limit_up_ratio = item.limit_up_ratio,
this.conceptStocksDetails(item.concept_id)
},
conceptStocksDetails(concept_id) {
console.log("concept_id", concept_id)
conceptStocks(concept_id, {}).then(res => {
if (res.data && res.data.stocks) {
// 将接口数据赋值给列表数组
let rawData = res.data.stocks;
// 2. 对数据进行排序处理
this.conceptStocksList = rawData.sort((a, b) => {
// 将 None 值转换为 -999
const aValue = a.change_pct === null || a.change_pct === undefined ? -999 :
Number(a.change_pct);
const bValue = b.change_pct === null || b.change_pct === undefined ? -999 :
Number(b.change_pct);
// 降序排列(涨幅高的在前)
return bValue - aValue;
});
} else {
console.warn('接口返回数据格式异常', res);
}
}).catch(error => {
})
},
// 格式化涨跌幅显示(处理正负号、保留两位小数)
formatChangePct(change_pct) {
if (typeof change_pct !== 'number') return '0.00%';
// 正数加+号,负数保留-号,保留两位小数
const symbol = change_pct >= 0 ? '+' : '';
return `${symbol}${change_pct.toFixed(2)}%`;
},
// 获取涨跌幅文字颜色(涨红跌绿,平盘灰色)
getChangeColor(change_pct) {
if (typeof change_pct !== 'number') return '#888888';
if (change_pct > 0) return '#EC3440'; // 上涨:红色
if (change_pct < 0) return '#00B42A'; // 下跌:绿色
return '#888888'; // 平盘:灰色
}
}
}
</script>
<style lang="less">
page {
background-color: #070707;
}
.topBg {
top: 0;
left: 0;
width: 100%;
height: auto;
}
.searchC {
background-color: #292929B3;
left: 0;
right: 0;
margin: 20rpx 25rpx 0;
padding: 0 25rpx;
height: 70rpx;
border-radius: 35rpx;
font-size: 22rpx;
font-weight: 500;
.icon {
margin-right: 12rpx;
width: 25rpx;
height: auto;
}
input {
height: 100%;
color: white;
}
}
.stockDetailsC {
left: 25rpx;
width: calc(100vw - 50rpx);
bottom: calc(55px + env(safe-area-inset-bottom));
background-color: white;
border-radius: 10rpx;
}
.detailPopup {
max-height: 70%;
background-color: white;
color: red;
border-radius: 20rpx 20rpx 0 0;
padding-bottom: env(safe-area-inset-bottom);
}
</style>