个股中心、涨停分析接口对接

This commit is contained in:
renzhijun
2026-01-31 16:59:33 +08:00
parent 44d8ecf318
commit 1c13386dfc
3 changed files with 459 additions and 611 deletions

View File

@@ -20,14 +20,26 @@
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>
<view v-if="item.change > 0"
style="margin-left: 10rpx; background-color: #F59B38; border-radius: 5rpx; color: white; padding: 0 5rpx; font-size: 24rpx; font-weight: bold;">
+{{item.change}}</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>
@@ -46,9 +58,9 @@
<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;">35个板块</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;">102只涨停</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>
@@ -81,24 +93,28 @@
</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" @click="bkydAction(index)"
style="background-color: #EF4444; border-radius: 5rpx; padding: 15rpx; color: white; font-size: 24rpx; font-weight: 500;">
<view>{{item.title}}</view>
<view style="font-size: 22rpx;">{{item.count}}</view>
<view v-for="(item, index) in bkList" :key="index" @click="bkydAction(index)" :style="{
backgroundColor: item.bgColor,
borderRadius: '5rpx',
padding: '15rpx',
color: 'white',
fontSize: '24rpx',
fontWeight: '500'
}">
<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"
style="display: flex; align-items: center; justify-content: center; padding: 10rpx 20rpx;"
:style="{color: (index == 0 ? '#BB8520' : '#999999'), border: `1rpx solid ${index == 0 ? '#F2C369' : '#D2D2D2'}`, 'background-color' : (index == 0 ? '#FFFAF1' : '#FFF')}">
{{item}}
</view> -->
<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="{
@@ -178,7 +194,9 @@
</template>
<script>
import {getBaseURL1 } from '@/request/http.js'
import {
getBaseURL1
} from '@/request/http.js'
import {
inject
} from 'vue'
@@ -314,69 +332,28 @@
}
],
wordData: [],
bkList: [{
title: '存储芯片',
count: 8
}, {
title: '存储芯片',
count: 8
},
bkList: [],
number_limit_stocks: '',
HEAT_LEVELS: [{
threshold: 0.7,
color: '#EF4444',
level: '高热度'
}, // >70%
{
title: '存储芯片',
count: 8
},
threshold: 0.4,
color: '#F97316',
level: '中热度'
}, // 40%~70%
{
title: '存储芯片',
count: 8
},
threshold: 0.2,
color: '#F3B800',
level: '低热度'
}, // 20%~40%
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
},
{
title: '存储芯片',
count: 8
}
threshold: 0,
color: '#01AB5D',
level: '无热度'
} // ≤20%
],
bkTypes: [
'板块关联图',
@@ -389,13 +366,13 @@
tooltip: {
trigger: 'item'
},
animation:false,
animation: false,
legend: {
top: '5%',
left: 'center',
show:false
show: false
},
series: [{
name: 'Access From',
type: 'pie',
@@ -405,18 +382,18 @@
itemStyle: {
borderRadius: 8
},
emphasis: {
label: {
show: true,
fontSize: 10,
}
},
labelLine: {
length:1,
length2:5,
length: 1,
length2: 5,
},
data: []
}]
@@ -460,7 +437,7 @@
this.activeIndex = e.index
this.contentTop = this.navH + 20 / 750 * inject('windowWidth')
this.analyseHighStocks()
},
@@ -469,12 +446,20 @@
this.fetchData()
// 页面就绪后,若默认选中的是板块分布,初始化饼图
//if (this.activeType === 0) {
this.initGraphChart(); // 初始化关系图
this.initGraphChart(); // 初始化关系图
//} else if (this.activeType === 1) {
// 初始化饼图
// 初始化饼图
//}
},
methods: {
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;
@@ -493,484 +478,188 @@
}
},
getPreviousDayDate(dateStr) {
// 校验输入日期格式是否正确
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() - 2);
// 格式化前一天的日期为 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}`;
// 校验输入日期格式是否正确
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() - 2);
// 格式化前一天的日期为 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 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();
} else {
uni.showToast({
title: '数据请求失败',
icon: 'none'
});
}
} catch (error) {
console.error('请求异常:', error);
uni.showToast({
title: '网络异常',
icon: 'none'
});
}
},
/**
* 请求接口数据(优化:动态日期+自动时间戳)
*/
async fetchData() {
try {
// 1. 自动生成当前时间戳(替代固定值)
const timestamp = new Date().getTime();
// 调用上面的函数获取前一天的格式化日期YYYYMMDD
const formattedDate = this.getPreviousDayDate(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;
console.log('接口数据请求成功', this.originData.chart_data );
this.initPieChart();
} 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);
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);
}
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
}];
}
// this.wordData = [{
// name: "脑机",
// value: 10000
// },
// {
// name: "航天",
// value: 3428
// },
// {
// name: "商业",
// value: 1747
// },
// {
// name: "智能",
// value: 1692
// },
// {
// name: "量产",
// value: 1589
// },
// {
// name: "落地",
// value: 1555
// },
// {
// name: "存储芯片",
// value: 1487
// },
// {
// name: "医疗",
// value: 1348
// },
// {
// name: "马斯克",
// value: 1346
// },
// {
// name: "业绩",
// value: 1234
// },
// {
// name: "康复",
// value: 1143
// },
// {
// name: "机器人",
// value: 1127
// },
// {
// name: "洁净室",
// value: 1078
// },
// {
// name: "标的",
// value: 1072
// },
// {
// name: "设备",
// value: 1071
// },
// {
// name: "算力",
// value: 1015
// },
// {
// name: "材料",
// value: 983
// },
// {
// name: "卫星",
// value: 970
// },
// {
// name: "科技",
// value: 947
// },
// {
// name: "资产",
// value: 828
// },
// {
// name: "半导体",
// value: 774
// },
// {
// name: "重估",
// value: 750
// },
// {
// name: "人脑",
// value: 747
// },
// {
// name: "平台",
// value: 737
// },
// {
// name: "产业链",
// value: 726
// },
// {
// name: "赛道",
// value: 715
// },
// {
// name: "电池",
// value: 694
// },
// {
// name: "估值",
// value: 689
// },
// {
// name: "景气",
// value: 682
// },
// {
// name: "A股",
// value: 662
// },
// {
// name: "商业化",
// value: 643
// },
// {
// name: "固态",
// value: 642
// },
// {
// name: "工程",
// value: 642
// },
// {
// name: "军工",
// value: 642
// },
// {
// name: "芯片",
// value: 615
// },
// {
// name: "医疗器械",
// value: 606
// },
// {
// name: "供应链",
// value: 585
// },
// {
// name: "弹性",
// value: 573
// },
// {
// name: "蓝箭",
// value: 551
// },
// {
// name: "市值",
// value: 541
// },
// {
// name: "高端",
// value: 527
// },
// {
// name: "植入",
// value: 523
// },
// {
// name: "耗材",
// value: 523
// },
// {
// name: "逻辑",
// value: 519
// },
// {
// name: "数据",
// value: 512
// },
// {
// name: "服务器",
// value: 504
// },
// {
// name: "供应商",
// value: 503
// },
// {
// name: "电子",
// value: 483
// },
// {
// name: "芳纶",
// value: 458
// },
// {
// name: "传闻",
// value: 454
// },
// {
// name: "国产化",
// value: 453
// },
// {
// name: "营销",
// value: 452
// },
// {
// name: "涨价",
// value: 450
// },
// {
// name: "临床",
// value: 449
// },
// {
// name: "转型",
// value: 444
// },
// {
// name: "强脑",
// value: 441
// },
// {
// name: "储能",
// value: 441
// },
// {
// name: "智能家居",
// value: 438
// },
// {
// name: "场景",
// value: 435
// },
// {
// name: "港股",
// value: 423
// },
// {
// name: "柔性",
// value: 422
// },
// {
// name: "人形",
// value: 414
// },
// {
// name: "国产",
// value: 411
// },
// {
// name: "接口技术",
// value: 401
// },
// {
// name: "消费",
// value: 399
// },
// {
// name: "创板",
// value: 397
// },
// {
// name: "全球",
// value: 389
// },
// {
// name: "替代",
// value: 389
// },
// {
// name: "融资",
// value: 388
// },
// {
// name: "补贴",
// value: 369
// },
// {
// name: "管线",
// value: 368
// },
// {
// name: "电极",
// value: 367
// },
// {
// name: "模态",
// value: 364
// },
// {
// name: "国家",
// value: 361
// },
// {
// name: "盈利",
// value: 359
// },
// {
// name: "测试",
// value: 356
// },
// {
// name: "子公司",
// value: 354
// },
// {
// name: "实控",
// value: 353
// },
// {
// name: "八院",
// value: 353
// },
// {
// name: "价格",
// value: 352
// },
// {
// name: "旗下",
// value: 351
// },
// {
// name: "组件",
// value: 346
// },
// {
// name: "电解液",
// value: 342
// },
// {
// name: "中标",
// value: 340
// }
// ];
// 直接赋值接口返回的词频数据
this.wordData = this.originData.word_freq_data;
console.log('词云数据赋值完成', this.wordData);
} else {
// 兜底默认数据
this.wordData = [{
name: "脑机",
value: 10000
}, {
name: "航天",
value: 3428
}];
}
//console.log('父页面设置词云数据:', JSON.stringify(this.wordData));
},
handleDateChange(data) {
console.log('从日历组件接收的参数:', data.item?.zt_count);
console.log('从日历组件接收的参数:', {
currentZtCount: data.item?.zt_count,
prevZtCount: data.prevItem?.zt_count
});
// 赋值到父页面的变量中
this.selectedYearMonth = data.yearMonth;
this.selectedFullDate = data.fullDate;
this.selectedItem = data.item;
// 2. 格式化日期:年-月-日 → 月日(如 2026-01-14 → 1月14日
if (data.fullDate) {
const [year, month, day] = data.fullDate.split('-').map(Number);
@@ -980,12 +669,22 @@
// 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;
// =======================================
this.fetchData()
// this.analyseHighStocks()
},
analyseHighStocks() {
const formatDate = this.getPreviousDayDate(this.selectedFullDate);
const formatDate = this.getPreviousDayDate(this.selectedFullDate);
let param = {
date: formatDate
}
@@ -1024,4 +723,21 @@
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>