1051 lines
32 KiB
Vue
1051 lines
32 KiB
Vue
<template>
|
||
<view>
|
||
<navBar leftText="涨停分析" hideNavBg hideBack></navBar>
|
||
<image class="topBg absolute" src="/static/image/index/conceptTopBg.png" mode="widthFix"></image>
|
||
|
||
<scroll-view scroll-y class="stockDetailsC fixed" :style="'top:'+contentTop+'px;'">
|
||
<view style="color: white; font-weight: 500; display: flex; align-items: center; margin: 35rpx 25rpx;">
|
||
<image style="width: 40rpx; height: 40rpx;" src="/pagesStock/static/icon/ai-icon.png" mode="widthFix">
|
||
</image>
|
||
<text style="font-size: 36rpx; margin-left: 10rpx; margin-right: 20rpx;">AI总结</text>
|
||
<text style="font-size: 28rpx;"></text>
|
||
</view>
|
||
|
||
<view style="background-color: white; border-radius: 10rpx; overflow: hidden; margin: 25rpx;">
|
||
<view
|
||
style="color: #2B2B2B; font-weight: 500; display: flex; align-items: center; margin: 25rpx 20rpx;">
|
||
<image style="width: 40rpx; height: 40rpx;" src="/pagesStock/static/icon/ai-icon-1.png"
|
||
mode="widthFix"></image>
|
||
<text
|
||
style="font-size: 30rpx; font-weight: bold; margin-left: 10rpx; margin-right: 20rpx;">核心指标</text>
|
||
</view>
|
||
|
||
|
||
<view style="display: grid; gap: 15rpx; grid-template-columns: repeat(3, 1fr); margin: 20rpx;">
|
||
<view v-for="(item,index) in tabTypes" :key="index"
|
||
style="display: flex; align-items: center; justify-content: center; flex-direction: column; background-color: #FFF8F0; border: 1rpx solid #F59B38; border-radius: 5rpx; padding: 20rpx; box-sizing: border-box;">
|
||
<view style="display: flex;align-items: center;justify-content: center;">
|
||
<view style="color: #F59B38; font-size: 30rpx;">{{item.data}}</view>
|
||
<!-- 优化:兼容正数、负数、0的显示 -->
|
||
<view v-if="item.change !== 0" :style="[
|
||
{
|
||
marginLeft: '10rpx',
|
||
borderRadius: '5rpx',
|
||
color: 'white',
|
||
padding: '0 5rpx',
|
||
fontSize: '24rpx',
|
||
fontWeight: 'bold'
|
||
},
|
||
item.change > 0 ? { backgroundColor: '#F59B38' } : { backgroundColor: '#EF4444' }
|
||
]">
|
||
{{item.change > 0 ? '+' + item.change : item.change}}
|
||
</view>
|
||
</view>
|
||
<view style="color: #555555; font-size: 20rpx; margin-top: 5rpx;">{{item.title}}</view>
|
||
</view>
|
||
</view>
|
||
|
||
|
||
<view style="margin: 25rpx;">
|
||
<LCCalendar @date-change="handleDateChange"></LCCalendar>
|
||
</view>
|
||
|
||
|
||
<view style="color: #2B2B2B; font-weight: 500; display: flex; margin: 25rpx 20rpx;">
|
||
<image style="width: 40rpx; height: 42rpx;" src="/pagesStock/static/icon/all-icon-3.png"
|
||
mode="widthFix"></image>
|
||
<view style="margin-left: 10rpx;">
|
||
<view style="font-size: 30rpx; font-weight: bold;">市场全景</view>
|
||
<view style="font-size: 24rpx; font-weight: 500; margin-top: 10rpx;">
|
||
<text
|
||
style="color: #F3C368; border: 1rpx solid #F3C368; border-radius: 5rpx; text-align: center; padding: 0 10rpx;">{{bkList.length}}个板块</text>
|
||
<text
|
||
style="color: #EC3440; border: 1rpx solid #EC3440; border-radius: 5rpx; text-align: center; padding: 0 10rpx; margin: 0 10rpx;">{{number_limit_stocks}}只涨停</text>
|
||
<text
|
||
style="color: #F59B38; border: 1rpx solid #F59B38; border-radius: 5rpx; text-align: center; padding: 0 10rpx;">高位股风险:
|
||
低</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view
|
||
style="background-color: #FAFAFC; border-radius: 10rpx; padding: 30rpx 20rpx; box-sizing: border-box; margin: 25rpx;">
|
||
<view style="display: flex; align-items: center; font-size: 22rpx; font-weight: 500;">
|
||
<view style="flex: 1; color: #2B2B2B; font-weight: bold; font-size: 28rpx;">板块热力图</view>
|
||
<view style="color: #EF4444; display: flex;align-items: center;">
|
||
<view
|
||
style="margin: 0 10rpx; box-sizing: border-box; border-radius: 15rpx; background-color: #EF4444; overflow: hidden; width: 15rpx; height: 15rpx;">
|
||
</view>高热度
|
||
</view>
|
||
<view style="color: #F97316; display: flex;align-items: center;">
|
||
<view
|
||
style="margin: 0 10rpx; box-sizing: border-box; border-radius: 15rpx; background-color: #F97316; overflow: hidden; width: 15rpx; height: 15rpx;">
|
||
</view>中热度
|
||
</view>
|
||
<view style="color: #F3B800; display: flex;align-items: center;">
|
||
<view
|
||
style="margin: 0 10rpx; box-sizing: border-box; border-radius: 15rpx; background-color: #F3B800; overflow: hidden; width: 15rpx; height: 15rpx;">
|
||
</view>低热度
|
||
</view>
|
||
<view style="color: #01AB5D; display: flex;align-items: center;">
|
||
<view
|
||
style="margin: 0 10rpx; box-sizing: border-box; border-radius: 15rpx; background-color: #01AB5D; overflow: hidden; width: 15rpx; height: 15rpx;">
|
||
</view>冷门
|
||
</view>
|
||
</view>
|
||
<view style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 7rpx; margin-top: 25rpx;">
|
||
<view v-for="(item, index) in bkList" :key="index" :style="{
|
||
backgroundColor: item.bgColor,
|
||
borderRadius: '5rpx',
|
||
padding: '15rpx',
|
||
color: 'white',
|
||
fontSize: '24rpx',
|
||
fontWeight: '500'
|
||
}" @click="bkydAction(index)" >
|
||
<view class="single-line-ellipsis">{{item.title}}</view>
|
||
<view class="count-text">{{item.count}}只</view>
|
||
|
||
</view>
|
||
</view>
|
||
|
||
|
||
</view>
|
||
|
||
|
||
<view
|
||
style="margin: 25rpx; display: grid; grid-template-columns: repeat(3, 1fr); gap: 20rpx; color: #999999; font-size: 22rpx; font-weight: 500;">
|
||
|
||
<view v-for="(item, index) in bkTypes" @click="switchTab(index)"
|
||
style="display: flex; align-items: center; justify-content: center; padding: 10rpx 20rpx; border-radius: 8rpx;"
|
||
:style="{
|
||
color: (activeType == index ? '#BB8520' : '#999999'),
|
||
border: `1rpx solid ${activeType == index ? '#F2C369' : '#D2D2D2'}`,
|
||
'background-color' : (activeType == index ? '#FFFAF1' : '#FFF'),
|
||
'cursor': 'pointer'
|
||
}">
|
||
{{item}}
|
||
</view>
|
||
</view>
|
||
|
||
<view style=" display: flex; align-items: center; justify-content: center;padding: 0 25rpx; ">
|
||
<!-- <view v-if="activeType === 0"
|
||
style="width: 100%; height: 500rpx; display: flex; align-items: center; justify-content: center; color: #999;">
|
||
板块关联图内容区域
|
||
</view> -->
|
||
<!-- <view v-show="activeType === 0" style="width: 100%; height: 500rpx;">
|
||
<l-echart ref="graphChartRef"></l-echart>
|
||
</view> -->
|
||
<view v-show="activeType === 0" style="width: 100%; height: 500rpx;">
|
||
<l-echart ref="chartRef"></l-echart>
|
||
</view>
|
||
<WordCloud v-show="activeType === 1" :wordData="wordData" :width="330" :height="330" @rendered="onWordCloudRendered" />
|
||
</view>
|
||
|
||
|
||
<view style="color: #2B2B2B; font-weight: 500; display: flex; margin: 25rpx 20rpx;">
|
||
<image style="width: 40rpx; height: 42rpx;" src="/pagesStock/static/icon/all-icon-3.png"
|
||
mode="widthFix"></image>
|
||
<view style="margin-left: 10rpx;">
|
||
<view style="font-size: 30rpx; font-weight: bold;">高位股统计</view>
|
||
<view style="font-size: 24rpx; font-weight: 500; margin-top: 10rpx;">
|
||
<text
|
||
style="color: #F3C368; border: 1rpx solid #F3C368; border-radius: 5rpx; text-align: center; padding: 0 10rpx;">
|
||
高位股{{highPositionStats.total_count}}只
|
||
</text>
|
||
<text
|
||
style="color: #EC3440; border: 1rpx solid #EC3440; border-radius: 5rpx; text-align: center; padding: 0 10rpx; margin: 0 10rpx;">
|
||
平均{{highPositionStats.avg_continuous_days}}
|
||
</text>
|
||
<text
|
||
style="color: #F59B38; border: 1rpx solid #F59B38; border-radius: 5rpx; text-align: center; padding: 0 10rpx;">
|
||
最高{{highPositionStats.max_continuous_days}}板
|
||
</text>
|
||
<text :style="{
|
||
color: '#FFF',
|
||
backgroundColor: riskAssessment.color,
|
||
borderRadius: '5rpx',
|
||
textAlign: 'center',
|
||
padding: '0 10rpx',
|
||
marginLeft: '10rpx'
|
||
}">
|
||
{{riskAssessment.level}}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
|
||
<view v-for="(stock,index) in highPositionStockList" :key="index"
|
||
style="background-color: #FAFAFC; border-radius: 10rpx; padding: 15rpx 20rpx; display: flex; align-items: center; margin: 10rpx 25rpx;">
|
||
<view style="flex: 1;">
|
||
<view style="color: #2B2B2B; font-weight: bold; font-size: 26rpx;">{{stock.sname}}</view>
|
||
<view :style="{
|
||
color: '#999999',
|
||
fontWeight: 500,
|
||
fontSize: '24rpx',
|
||
marginTop: '5rpx'
|
||
}">
|
||
({{stock.risk_info.status}})
|
||
</view>
|
||
</view>
|
||
|
||
<view :style="{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
padding: '4rpx 10rpx',
|
||
backgroundColor: stock.risk_info.bg,
|
||
borderRadius: '5rpx',
|
||
border: `1rpx solid ${stock.risk_info.border}`,
|
||
color: stock.risk_info.color,
|
||
fontSize: '20rpx',
|
||
fontWeight: 400,
|
||
margin: '0 10rpx'
|
||
}">
|
||
<image v-if="['high', 'medium'].includes(getStockHeatType(stock))" :style="{
|
||
width: '15rpx',
|
||
height: '17rpx',
|
||
marginRight: '10rpx',
|
||
tintColor: heatIconMap[getStockHeatType(stock)].icon4Color
|
||
}" :src="heatIconMap[getStockHeatType(stock)].icon4" mode="widthFix"></image>
|
||
<view>{{stock.continuous_days_num}}连板</view>
|
||
</view>
|
||
|
||
<image :style="{width: '27rpx', height: '25rpx'}" :src="heatIconMap[getStockHeatType(stock)].icon5"
|
||
mode="widthFix"></image>
|
||
</view>
|
||
|
||
|
||
<view
|
||
style="margin: 20rpx 30rpx; color: #EF4444; font-size: 24rpx; font-weight: 500; display: flex; align-items: center;">
|
||
<image style="width: 27rpx; height: 25rpx; margin-right: 10rpx;"
|
||
src="/pagesStock/static/icon/all-icon-5.png" mode="widthFix"></image>
|
||
<text>高位股风险较高,追涨需谨慎</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
getBaseURL1
|
||
} from '@/request/http.js'
|
||
import {
|
||
inject
|
||
} from 'vue'
|
||
import {
|
||
calendarCombinedData,
|
||
analyseHighStocks
|
||
} from '@/request/api'
|
||
const echarts = require('../../uni_modules/lime-echart/static/echarts.min.js');
|
||
import WordCloud from '@/components/WordCloud/WordCloud.vue';
|
||
|
||
|
||
// 模拟关系图数据(替代接口请求的les-miserables.json)
|
||
const mockGraphData = {
|
||
categories: [{
|
||
name: '板块'
|
||
},
|
||
{
|
||
name: '概念'
|
||
},
|
||
{
|
||
name: '个股'
|
||
}
|
||
],
|
||
nodes: [{
|
||
name: '科技板块',
|
||
symbolSize: 50,
|
||
category: 0
|
||
},
|
||
{
|
||
name: '人工智能',
|
||
symbolSize: 30,
|
||
category: 1
|
||
},
|
||
{
|
||
name: '大数据',
|
||
symbolSize: 25,
|
||
category: 1
|
||
},
|
||
{
|
||
name: '科大讯飞',
|
||
symbolSize: 20,
|
||
category: 2
|
||
},
|
||
{
|
||
name: '百度',
|
||
symbolSize: 20,
|
||
category: 2
|
||
},
|
||
{
|
||
name: '金融板块',
|
||
symbolSize: 50,
|
||
category: 0
|
||
},
|
||
{
|
||
name: '数字货币',
|
||
symbolSize: 30,
|
||
category: 1
|
||
},
|
||
{
|
||
name: '招商银行',
|
||
symbolSize: 20,
|
||
category: 2
|
||
},
|
||
{
|
||
name: '平安银行',
|
||
symbolSize: 20,
|
||
category: 2
|
||
}
|
||
],
|
||
links: [{
|
||
source: '科技板块',
|
||
target: '人工智能',
|
||
value: 10
|
||
},
|
||
{
|
||
source: '科技板块',
|
||
target: '大数据',
|
||
value: 8
|
||
},
|
||
{
|
||
source: '人工智能',
|
||
target: '科大讯飞',
|
||
value: 6
|
||
},
|
||
{
|
||
source: '人工智能',
|
||
target: '百度',
|
||
value: 6
|
||
},
|
||
{
|
||
source: '金融板块',
|
||
target: '数字货币',
|
||
value: 9
|
||
},
|
||
{
|
||
source: '数字货币',
|
||
target: '招商银行',
|
||
value: 5
|
||
},
|
||
{
|
||
source: '数字货币',
|
||
target: '平安银行',
|
||
value: 5
|
||
}
|
||
]
|
||
};
|
||
|
||
export default {
|
||
components: {
|
||
WordCloud
|
||
},
|
||
data() {
|
||
return {
|
||
navH: inject('navHeight'),
|
||
contentTop: '',
|
||
selectedYearMonth: '', // 年-月(去掉日)
|
||
selectedFullDate: '', // 年-月-日
|
||
selectedItem: null, // 选中的item完整数据
|
||
tabTypes: [{
|
||
data: '',
|
||
change: 0,
|
||
title: '当前日期'
|
||
},
|
||
{
|
||
data: '',
|
||
change: 3,
|
||
title: '涨停家数'
|
||
},
|
||
{
|
||
data: '22%',
|
||
change: 0,
|
||
title: '炸板率'
|
||
}
|
||
],
|
||
wordData: [],
|
||
bkList: [],
|
||
number_limit_stocks: '',
|
||
HEAT_LEVELS: [{
|
||
threshold: 0.7,
|
||
color: '#EF4444',
|
||
level: '高热度'
|
||
}, // >70%
|
||
{
|
||
threshold: 0.4,
|
||
color: '#F97316',
|
||
level: '中热度'
|
||
}, // 40%~70%
|
||
{
|
||
threshold: 0.2,
|
||
color: '#F3B800',
|
||
level: '低热度'
|
||
}, // 20%~40%
|
||
{
|
||
threshold: 0,
|
||
color: '#01AB5D',
|
||
level: '无热度'
|
||
} // ≤20%
|
||
],
|
||
// bkTypes: [
|
||
// '板块关联图',
|
||
// '板块分布',
|
||
// '热门概念词云'
|
||
// ],
|
||
highPositionStats: {
|
||
total_count: 0, // 高位股数量
|
||
avg_continuous_days: 0, // 平均连板数
|
||
max_continuous_days: 0 // 最高连板数
|
||
},
|
||
riskAssessment: { // 风险评估结果
|
||
level: "正常",
|
||
color: "#22c55e"
|
||
},
|
||
highPositionStockList: [], // 新增:存储筛选后的高位股列表
|
||
// 风险阈值常量(对应参考代码)
|
||
RISK_THRESHOLDS: {
|
||
CRITICAL: 7,
|
||
HIGH: 5,
|
||
MEDIUM: 3,
|
||
LOW: 2,
|
||
},
|
||
// 风险颜色常量
|
||
RISK_COLORS: {
|
||
critical: {
|
||
color: "#ef4444",
|
||
bg: "rgba(239, 68, 68, 0.2)",
|
||
border: "rgba(239, 68, 68, 0.4)",
|
||
},
|
||
high: {
|
||
color: "#f97316",
|
||
bg: "rgba(249, 115, 22, 0.2)",
|
||
border: "rgba(249, 115, 22, 0.4)",
|
||
},
|
||
medium: {
|
||
color: "#eab308",
|
||
bg: "rgba(234, 179, 8, 0.2)",
|
||
border: "rgba(234, 179, 8, 0.4)",
|
||
},
|
||
low: {
|
||
color: "#22c55e",
|
||
bg: "rgba(34, 197, 94, 0.2)",
|
||
border: "rgba(34, 197, 94, 0.4)",
|
||
},
|
||
},
|
||
heatIconMap: {
|
||
high: {
|
||
icon4: '/pagesStock/static/icon/all-icon-4.png',
|
||
icon4Color: '#EF4444', // 高热度图标颜色
|
||
icon5: '/pagesStock/static/icon/all-icon-5.png' // 高热度右侧图标
|
||
},
|
||
medium: {
|
||
icon4: '/pagesStock/static/icon/all-icon-9.png',
|
||
icon4Color: '#F97316', // 中热度图标颜色
|
||
icon5: '/pagesStock/static/icon/all-icon-6.png' // 中热度右侧图标
|
||
},
|
||
low: {
|
||
icon4: '', // 低热度不显示icon4
|
||
icon4Color: '#F3B800',
|
||
icon5: '/pagesStock/static/icon/all-icon-7.png' // 低热度右侧图标
|
||
},
|
||
none: {
|
||
icon4: '', // 冷门
|
||
icon4Color: '#01AB5D',
|
||
icon5: '/pagesStock/static/icon/all-icon-8.png' // 无热度右侧图标
|
||
}
|
||
},
|
||
originData: null, // 原始接口数据
|
||
bkTypes: [
|
||
|
||
'板块分布',
|
||
'热门概念词云'
|
||
],
|
||
activeType: 0, // 默认选中第一个标签
|
||
// 饼图配置项
|
||
pieOption: {
|
||
tooltip: {
|
||
trigger: 'item'
|
||
},
|
||
animation: false,
|
||
legend: {
|
||
top: '5%',
|
||
left: 'center',
|
||
show: false
|
||
},
|
||
|
||
series: [{
|
||
name: 'Access From',
|
||
type: 'pie',
|
||
radius: ['40%', '70%'],
|
||
avoidLabelOverlap: false,
|
||
padAngle: 2,
|
||
itemStyle: {
|
||
borderRadius: 8
|
||
},
|
||
|
||
emphasis: {
|
||
label: {
|
||
show: true,
|
||
fontSize: 10,
|
||
|
||
}
|
||
},
|
||
labelLine: {
|
||
length: 1,
|
||
length2: 5,
|
||
|
||
},
|
||
data: []
|
||
}]
|
||
},
|
||
// 关系图配置项
|
||
graphOption: {
|
||
tooltip: {},
|
||
legend: [],
|
||
series: [{
|
||
name: '板块关联',
|
||
type: 'graph',
|
||
layout: 'none',
|
||
data: [],
|
||
links: [],
|
||
categories: [],
|
||
roam: true, // 允许拖拽和缩放
|
||
label: {
|
||
show: true,
|
||
position: 'right',
|
||
formatter: '{b}'
|
||
},
|
||
labelLayout: {
|
||
hideOverlap: true
|
||
},
|
||
scaleLimit: {
|
||
min: 0.4,
|
||
max: 2
|
||
},
|
||
lineStyle: {
|
||
color: 'source',
|
||
curveness: 0.3
|
||
}
|
||
}]
|
||
},
|
||
|
||
|
||
|
||
}
|
||
},
|
||
onLoad(e) {
|
||
this.activeIndex = e.index
|
||
this.contentTop = this.navH + 20 / 750 * inject('windowWidth')
|
||
|
||
|
||
},
|
||
|
||
onReady() {
|
||
|
||
|
||
|
||
|
||
// 页面就绪后,若默认选中的是板块分布,初始化饼图
|
||
//if (this.activeType === 0) {
|
||
//this.initPieChart(); // 初始化关系图
|
||
//} else if (this.activeType === 1) {
|
||
// 初始化饼图
|
||
//}
|
||
},
|
||
methods: {
|
||
// 词云绘制完成
|
||
onWordCloudRendered() {
|
||
uni.hideLoading();
|
||
},
|
||
getStockHeatType(stock) {
|
||
// 假设通过连板数计算热度(可根据实际业务逻辑调整)
|
||
const days = stock.continuous_days_num || 0;
|
||
|
||
|
||
if (days >= this.RISK_THRESHOLDS.CRITICAL) { // ≥5连板 → 高热度
|
||
return 'high';
|
||
} else if (days >= this.RISK_THRESHOLDS.HIGH) { // 3-4连板 → 中热度
|
||
return 'medium';
|
||
} else if (days >= this.RISK_THRESHOLDS.MEDIUM) { // 2连板 → 低热度
|
||
return 'low';
|
||
} else { // <2连板 → 冷门(无热度)
|
||
return 'none';
|
||
}
|
||
},
|
||
|
||
|
||
//解析连板数(从"2天2板"格式中提取数字)
|
||
parseContinuousDays(continuousDaysStr) {
|
||
if (!continuousDaysStr) return 0;
|
||
const match = continuousDaysStr.match(/(\d+)天/);
|
||
return match ? Number(match[1]) : 0;
|
||
},
|
||
// 风险等级判断函数
|
||
getRiskLevel(days) {
|
||
const {
|
||
RISK_THRESHOLDS,
|
||
RISK_COLORS
|
||
} = this;
|
||
if (days >= RISK_THRESHOLDS.CRITICAL) {
|
||
return {
|
||
level: "极高",
|
||
color: RISK_COLORS.critical.color,
|
||
bg: RISK_COLORS.critical.bg,
|
||
border: RISK_COLORS.critical.border,
|
||
status: "缩量一字,高风险",
|
||
};
|
||
}
|
||
if (days >= RISK_THRESHOLDS.HIGH) {
|
||
return {
|
||
level: "高",
|
||
color: RISK_COLORS.high.color,
|
||
bg: RISK_COLORS.high.bg,
|
||
border: RISK_COLORS.high.border,
|
||
status: "放量分歧,需观察",
|
||
};
|
||
}
|
||
if (days >= RISK_THRESHOLDS.MEDIUM) {
|
||
return {
|
||
level: "中",
|
||
color: RISK_COLORS.medium.color,
|
||
bg: RISK_COLORS.medium.bg,
|
||
border: RISK_COLORS.medium.border,
|
||
status: "正常波动",
|
||
};
|
||
}
|
||
return {
|
||
level: "低",
|
||
color: RISK_COLORS.low.color,
|
||
bg: RISK_COLORS.low.bg,
|
||
border: RISK_COLORS.low.border,
|
||
status: "健康",
|
||
};
|
||
},
|
||
// 计算高位股统计数据
|
||
calculateHighPositionStats() {
|
||
if (!this.originData || !this.originData.stocks) return;
|
||
|
||
// 1. 筛选高位股:连板数 >= 2 的股票 + 补充连板数和风险信息
|
||
const highPositionStocks = this.originData.stocks
|
||
.filter(stock => {
|
||
const days = this.parseContinuousDays(stock.continuous_days);
|
||
return days >= 2;
|
||
})
|
||
.map(stock => {
|
||
const days = this.parseContinuousDays(stock.continuous_days);
|
||
const riskInfo = this.getRiskLevel(days);
|
||
return {
|
||
...stock,
|
||
continuous_days_num: days, // 提取纯数字的连板数
|
||
risk_info: riskInfo // 风险等级信息
|
||
};
|
||
})
|
||
// 2. 按连板天数降序排列
|
||
.sort((a, b) => b.continuous_days_num - a.continuous_days_num);
|
||
|
||
// 3. 赋值给页面渲染用的列表(
|
||
this.highPositionStockList = highPositionStocks
|
||
|
||
// 4. 原有统计数据计算逻辑(保持不变)
|
||
const totalCount = highPositionStocks.length;
|
||
const maxDays = highPositionStocks.length ?
|
||
Math.max(...highPositionStocks.map(s => this.parseContinuousDays(s.continuous_days))) :
|
||
0;
|
||
const totalDays = highPositionStocks.reduce((sum, stock) => {
|
||
return sum + this.parseContinuousDays(stock.continuous_days);
|
||
}, 0);
|
||
const avgDays = totalCount > 0 ? (totalDays / totalCount).toFixed(1) : 0;
|
||
|
||
// 5. 更新统计数据
|
||
this.highPositionStats = {
|
||
total_count: totalCount,
|
||
avg_continuous_days: avgDays,
|
||
max_continuous_days: maxDays
|
||
};
|
||
|
||
// 6. 计算风险等级
|
||
this.calculateRiskAssessment();
|
||
},
|
||
|
||
|
||
// 计算风险评估
|
||
calculateRiskAssessment() {
|
||
const {
|
||
avg_continuous_days,
|
||
max_continuous_days,
|
||
total_count
|
||
} = this.highPositionStats;
|
||
const avgDays = Number(avg_continuous_days) || 0;
|
||
const maxDays = Number(max_continuous_days) || 0;
|
||
const totalCount = Number(total_count) || 0;
|
||
|
||
// 计算风险评分(和参考代码一致)
|
||
const score = avgDays * 2 + maxDays * 0.5 + totalCount * 0.3;
|
||
|
||
// 根据评分确定风险等级和颜色
|
||
if (score >= 20) {
|
||
this.riskAssessment = {
|
||
level: "高风险",
|
||
color: "#ef4444"
|
||
};
|
||
} else if (score >= 12) {
|
||
this.riskAssessment = {
|
||
level: "中风险",
|
||
color: "#f97316"
|
||
};
|
||
} else if (score >= 6) {
|
||
this.riskAssessment = {
|
||
level: "偏高",
|
||
color: "#eab308"
|
||
};
|
||
} else {
|
||
this.riskAssessment = {
|
||
level: "正常",
|
||
color: "#22c55e"
|
||
};
|
||
}
|
||
},
|
||
|
||
getHeatColor(value, max) {
|
||
// 处理边界:最大值为0时直接返回绿色
|
||
if (max === 0) return '#01AB5D';
|
||
const ratio = value / max;
|
||
// 找到第一个满足「占比 > 阈值」的等级(因数组从高到低排序,匹配第一个即最高等级)
|
||
const matchedLevel = this.HEAT_LEVELS.find(level => ratio > level.threshold);
|
||
return matchedLevel ? matchedLevel.color : '#01AB5D';
|
||
},
|
||
// 切换标签
|
||
async switchTab(index) {
|
||
this.activeType = index;
|
||
|
||
|
||
switch (index) {
|
||
case 0:
|
||
//this.$refs.graphChartRef && this.initGraphChart(); // 增加存在性判断
|
||
this.$refs.chartRef && this.initPieChart(); // 增加存在性判断
|
||
break;
|
||
case 1:
|
||
//this.$refs.chartRef && this.initPieChart(); // 增加存在性判断
|
||
uni.showLoading({
|
||
title: '词云生成中...',
|
||
mask: true // 防止用户误触
|
||
});
|
||
|
||
this.initWordCloud();
|
||
break;
|
||
case 2:
|
||
uni.showLoading({
|
||
title: '词云生成中...',
|
||
mask: true // 防止用户误触
|
||
});
|
||
|
||
this.initWordCloud();
|
||
break;
|
||
}
|
||
},
|
||
|
||
|
||
getPreviousDayDate() {
|
||
const now = new Date();
|
||
|
||
// 步骤2:将系统时间格式化为 YYYY-MM-DD 格式(适配后续校验逻辑)
|
||
const currentYear = now.getFullYear();
|
||
const currentMonth = String(now.getMonth() + 1).padStart(2, '0');
|
||
const currentDay = String(now.getDate()).padStart(2, '0');
|
||
const dateStr = `${currentYear}-${currentMonth}-${currentDay}`; // 示例:2026-02-04
|
||
|
||
|
||
// 校验输入日期格式是否正确
|
||
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
||
console.error('日期格式错误,请传入 YYYY-MM-DD 格式的日期');
|
||
return '';
|
||
}
|
||
|
||
// 创建日期对象(注意:月份是 0 开始的,所以需要处理)
|
||
const [year, month, day] = dateStr.split('-').map(Number);
|
||
const date = new Date(year, month - 1, day);
|
||
|
||
// 将日期减一天
|
||
date.setDate(date.getDate() - 1);
|
||
|
||
// 格式化前一天的日期为 YYYYMMDD 格式(补零处理)
|
||
const prevYear = date.getFullYear();
|
||
const prevMonth = String(date.getMonth() + 1).padStart(2, '0');
|
||
const prevDay = String(date.getDate()).padStart(2, '0');
|
||
|
||
return `${prevYear}${prevMonth}${prevDay}`;
|
||
},
|
||
/**
|
||
* 请求接口数据(优化:动态日期+自动时间戳)
|
||
*/
|
||
async fetchData() {
|
||
try {
|
||
// 1. 自动生成当前时间戳(替代固定值)
|
||
const timestamp = new Date().getTime();
|
||
|
||
|
||
// 调用上面的函数,获取前一天的格式化日期(YYYYMMDD)
|
||
// const formattedDate = this.getPreviousDayDate(this.selectedFullDate);
|
||
const formattedDate = this.selectedFullDate;
|
||
const baseURL = getBaseURL1();
|
||
const requestUrl = `${baseURL}/data/zt/daily/${formattedDate}.json?t=${timestamp}`;
|
||
|
||
console.log('请求URL:', requestUrl); // 打印URL便于调试
|
||
|
||
const res = await uni.request({
|
||
url: requestUrl,
|
||
method: 'GET'
|
||
});
|
||
|
||
if (res.statusCode === 200 && res.data) {
|
||
this.originData = res.data;
|
||
|
||
|
||
const chartData = this.originData.chart_data || {};
|
||
const labels = chartData.labels || [];
|
||
const counts = chartData.counts || [];
|
||
|
||
// 1. 找到counts中的最大值(用于计算热度颜色)
|
||
const maxCount = counts.length > 0 ? Math.max(...counts) : 0;
|
||
|
||
// 2. 遍历组装bkList(包含标题、数量、背景色、占比),先保证labels和counts长度一致
|
||
let bkList = [];
|
||
const maxLen = Math.min(labels.length, counts.length); // 取两者较短的长度,避免越界
|
||
for (let i = 0; i < maxLen; i++) {
|
||
const title = labels[i];
|
||
const count = counts[i] || 0;
|
||
// 计算背景色(当前count / 最大count → 匹配颜色)
|
||
const bgColor = this.getHeatColor(count, maxCount);
|
||
// 计算占比(保留2位小数,可选)
|
||
const ratio = maxCount === 0 ? 0 : ((count / maxCount) * 100).toFixed(2);
|
||
|
||
bkList.push({
|
||
title, // 板块名称
|
||
count, // 数量
|
||
bgColor, // 背景色
|
||
ratio // 占比(百分比,可选)
|
||
});
|
||
}
|
||
|
||
// 3. 核心:限制最多显示16条(切片操作放在最后)
|
||
this.bkList = bkList.slice(0, 16);
|
||
this.initPieChart();
|
||
|
||
this.calculateHighPositionStats();
|
||
|
||
} else {
|
||
uni.showToast({
|
||
title: '数据请求失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('请求异常:', error);
|
||
uni.showToast({
|
||
title: '网络异常',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
|
||
// 初始化关系图(增加容错)
|
||
async initGraphChart() {
|
||
|
||
const chart = await this.$refs.graphChartRef.init(echarts);
|
||
this.graphOption.legend = [{
|
||
data: mockGraphData.categories.map(a => a.name)
|
||
}];
|
||
this.graphOption.series[0].data = mockGraphData.nodes;
|
||
this.graphOption.series[0].links = mockGraphData.links;
|
||
this.graphOption.series[0].categories = mockGraphData.categories;
|
||
chart.setOption(this.graphOption);
|
||
|
||
},
|
||
// 初始化饼图(核心修复)
|
||
async initPieChart() {
|
||
|
||
// const Piechart = await this.$refs.chartRef.init(echarts);
|
||
// console.log("Piechart", Piechart);
|
||
// Piechart.setOption(this.pieOption);
|
||
try {
|
||
// 处理饼图数据:将labels和counts组合成name/value格式
|
||
let pieData = [];
|
||
const chartData = this.originData.chart_data || {};
|
||
const labels = chartData.labels || [];
|
||
const counts = chartData.counts || [];
|
||
|
||
// 遍历组合数据,确保数组长度一致
|
||
const maxLen = Math.min(labels.length, counts.length);
|
||
for (let i = 0; i < maxLen; i++) {
|
||
pieData.push({
|
||
name: labels[i], // 板块名称
|
||
value: counts[i] // 对应数量
|
||
});
|
||
}
|
||
|
||
// 更新饼图配置的data
|
||
this.pieOption.series[0].data = pieData.length > 0 ? pieData : [{
|
||
value: 10,
|
||
name: '科技板块'
|
||
},
|
||
{
|
||
value: 8,
|
||
name: '人脑工程'
|
||
},
|
||
{
|
||
value: 9,
|
||
name: '商业航天'
|
||
}
|
||
];
|
||
|
||
// 初始化ECharts并设置配置
|
||
if (this.$refs.chartRef) {
|
||
const Piechart = await this.$refs.chartRef.init(echarts);
|
||
console.log("Piechart实例创建成功", Piechart);
|
||
Piechart.setOption(this.pieOption);
|
||
}
|
||
} catch (error) {
|
||
console.error('饼图初始化失败:', error);
|
||
}
|
||
|
||
},
|
||
// 初始化词云
|
||
initWordCloud() {
|
||
if (this.originData.word_freq_data && Array.isArray(this.originData.word_freq_data)) {
|
||
// 直接赋值接口返回的词频数据
|
||
this.wordData = this.originData.word_freq_data;
|
||
console.log('词云数据赋值完成', this.wordData);
|
||
|
||
} else {
|
||
// 兜底默认数据
|
||
this.wordData = [{
|
||
name: "脑机",
|
||
value: 10000
|
||
}, {
|
||
name: "航天",
|
||
value: 3428
|
||
}];
|
||
}
|
||
|
||
setTimeout(() => {
|
||
uni.hideLoading();
|
||
}, 2000);
|
||
|
||
//console.log('父页面设置词云数据:', JSON.stringify(this.wordData));
|
||
},
|
||
handleDateChange(data) {
|
||
console.log('从日历组件接收的参数:', {
|
||
currentZtCount: data.item?.zt_count,
|
||
prevZtCount: data.prevItem?.zt_count
|
||
});
|
||
// 赋值到父页面的变量中
|
||
this.selectedYearMonth = data.yearMonth;
|
||
this.selectedFullDate = data.fullDate ? data.fullDate.replace(/-/g, '') : '';
|
||
this.selectedItem = data.item;
|
||
|
||
// 2. 格式化日期:年-月-日 → 月日(如 2026-01-14 → 1月14日)
|
||
if (data.fullDate) {
|
||
const [year, month, day] = data.fullDate.split('-').map(Number);
|
||
this.tabTypes[0].data = `${month}月${day}日`;
|
||
}
|
||
|
||
// 3. 赋值涨停家数(优先取item中的zt_count,无数据则显示0)
|
||
const ztCount = data.item?.zt_count ?? 0;
|
||
this.tabTypes[1].data = ztCount.toString(); // 转为字符串保证格式统一
|
||
this.number_limit_stocks = ztCount.toString();
|
||
|
||
// ===== 核心修改:新增0值判断逻辑 =====
|
||
const prevZtCount = data.prevItem?.zt_count ?? 0; // 上一天的涨停家数
|
||
// 条件:当天或上一天的zt_count为0 → 差值直接赋值0;否则计算真实差值
|
||
const changeValue = (ztCount === 0 || prevZtCount === 0) ?
|
||
0 :
|
||
ztCount - prevZtCount;
|
||
this.tabTypes[1].change = changeValue;
|
||
// =======================================
|
||
// ===== 新增:判断选中日期是否为系统当天,若是则日期减一天 =====
|
||
if (this.selectedFullDate) {
|
||
// 获取系统当前日期并格式化为 YYYYMMDD
|
||
const today = new Date();
|
||
const todayYear = today.getFullYear();
|
||
const todayMonth = String(today.getMonth() + 1).padStart(2, '0');
|
||
const todayDay = String(today.getDate()).padStart(2, '0');
|
||
const todayFormatted = `${todayYear}${todayMonth}${todayDay}`;
|
||
|
||
// 判断选中日期是否等于系统当天
|
||
if (this.selectedFullDate === todayFormatted) {
|
||
// 创建选中日期的Date对象
|
||
const selectedDate = new Date(
|
||
parseInt(this.selectedFullDate.substring(0, 4)), // 年
|
||
parseInt(this.selectedFullDate.substring(4, 6)) - 1, // 月(月份从0开始)
|
||
parseInt(this.selectedFullDate.substring(6, 8)) // 日
|
||
);
|
||
|
||
// 将日期减一天
|
||
selectedDate.setDate(selectedDate.getDate() - 1);
|
||
|
||
// 格式化前一天的日期为 YYYYMMDD 格式(补零处理)
|
||
const prevYear = selectedDate.getFullYear();
|
||
const prevMonth = String(selectedDate.getMonth() + 1).padStart(2, '0');
|
||
const prevDay = String(selectedDate.getDate()).padStart(2, '0');
|
||
const prevDateFormatted = `${prevYear}${prevMonth}${prevDay}`;
|
||
|
||
// 更新选中的日期为前一天
|
||
this.selectedFullDate = prevDateFormatted;
|
||
|
||
console.log(`选中日期为当天(${todayFormatted}),已自动调整为前一天:`, prevDateFormatted);
|
||
}
|
||
}
|
||
this.fetchData()
|
||
|
||
},
|
||
|
||
|
||
|
||
|
||
bkydAction(index) {
|
||
uni.navigateTo({
|
||
url: `/pagesStock/stockCenterDetails/bkydmx?index=${index}&data=${this.selectedFullDate}`
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less">
|
||
page {
|
||
background-color: #070707;
|
||
}
|
||
|
||
.topBg {
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.stockDetailsC {
|
||
left: 0;
|
||
right: 0;
|
||
bottom: calc(55px + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
/* 单行省略样式类 */
|
||
.single-line-ellipsis {
|
||
max-width: 100%;
|
||
width: 120rpx;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
text-align: center;
|
||
}
|
||
|
||
/* 数量行样式(可选抽离) */
|
||
.count-text {
|
||
font-size: 22rpx;
|
||
margin-top: 4rpx;
|
||
text-align: center;
|
||
}
|
||
</style> |