447 lines
16 KiB
Vue
447 lines
16 KiB
Vue
<template>
|
||
<view>
|
||
<navBar leftText="板块异动明细" :hideNavBg="true"></navBar>
|
||
<image class="topBg absolute" src="/static/image/index/conceptTopBg.png" mode="widthFix"></image>
|
||
|
||
<view class="stockDetailsC fixed" style="background-color: white; border-radius: 10rpx; overflow: hidden;"
|
||
:style="'top:'+contentTop+'px;'">
|
||
|
||
<view style="height: 86rpx;">
|
||
<scroll-view scroll-x
|
||
style="white-space: nowrap; height: 100%; padding: 0 20rpx; box-sizing: border-box;"
|
||
scroll-with-animation :scroll-into-view="'tab-' + activeIndex">
|
||
<view style="display: flex; align-items: center; height: 100%; font-weight: 500;">
|
||
<view :id="'tab-' + index" @click="activeIndex = index" v-for="(item,index) in bkList"
|
||
:key="index"
|
||
style="display: flex; align-items: center; justify-content: center; line-height: 85rpx; margin: 0 20rpx;"
|
||
:style="{color: (activeIndex == index ? '#2B2B2B' : '#999999'), 'border-bottom': (activeIndex == index ? '1rpx solid #F2C369' : 'none'), 'font-size' : (activeIndex == index ? '28rpx' : '26rpx')}">
|
||
{{item.title}}
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<view style="height: 1rpx; background-color: #E7E7E7; margin: 0 20rpx;"></view>
|
||
|
||
|
||
<view
|
||
style="height: 48rpx; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10rpx; margin: 23rpx 40rpx;">
|
||
<view @click="handleFilterChange(index)"
|
||
style="height: 45rpx; display: flex; align-items: center; justify-content: center; color: #939393; font-size: 24rpx; font-weight: 500; border-radius: 5rpx;"
|
||
:style="{color: (filterIndex == index ? '#070707' : '#939393'), 'border': (filterIndex == index ? '1rpx solid #F2C369' : '1rpx solid #E5E5E5'), 'background-color' : (filterIndex == index ? '#F2C369' : '#fff')}"
|
||
v-for="(item,index) in bkFilters" :key="index">
|
||
{{item}}
|
||
</view>
|
||
</view>
|
||
|
||
|
||
<view
|
||
style="margin: 0 20rpx; background-color: #FAFAFC; display: grid; grid-template-columns: 35% 20% 20% 25%;">
|
||
<view v-for="(item,index) in ['名称', '涨幅', '连板', '板块']" :key="index"
|
||
style="font-size: 22rpx; color: #666666; padding: 0 15rpx; box-sizing: border-box; font-weight: 500; line-height: 60rpx;"
|
||
:style="{'text-align' : index == 0 ? 'left' : 'center'}">
|
||
{{item}}
|
||
</view>
|
||
</view>
|
||
|
||
|
||
<scroll-view scroll-y
|
||
style="position: absolute; top: 241rpx; left: 0; right: 0; bottom: 0; font-size: 20rpx; font-weight: 500;">
|
||
<!-- 真实股票数据渲染 -->
|
||
<view v-for="(item, index) in filteredStocks" :key="item.scode"
|
||
style="margin: 0 20rpx; display: grid; grid-template-columns: 35% 20% 20% 25%;"
|
||
:style="{'background-color': (index % 2 == 0 ? '#fff' : '#FAFAFC')}">
|
||
<!-- 股票名称 + 角色标签 -->
|
||
<view style="display: flex; align-items: center; color: #666666; height: 60rpx;">
|
||
<!-- 角色标签 -->
|
||
<view v-if="item.stockRole"
|
||
style="display: flex; align-items: center; border-radius: 5rpx; padding: 0 10rpx; margin-left: 14rpx;"
|
||
:style="getRoleTagStyle(item.stockRole)">
|
||
<image v-if="item.stockRole.icon" style="width: 15rpx; height: 17rpx; margin-right: 5rpx;"
|
||
:src="item.stockRole.icon" mode="widthFix"></image>
|
||
<!-- <image style="width: 15rpx; height: 17rpx;" src="/pagesStock/static/icon/all-icon-4.png" mode="widthFix"></image> -->
|
||
<view :style="{'color': item.stockRole.color}">{{item.stockRole.text}}</view>
|
||
</view>
|
||
<view style="margin-left: 10rpx;">{{item.sname}}</view>
|
||
</view>
|
||
|
||
<!-- 涨幅(硬编码+10%) -->
|
||
<view style="display: flex; align-items: center; justify-content: center;">
|
||
<view style="font-size: 24rpx; color: #EC3440; font-weight: bold;">+10.00%</view>
|
||
</view>
|
||
|
||
<!-- 连板数 -->
|
||
<view style="display: flex; align-items: center; justify-content: center;">
|
||
<view
|
||
style="padding: 0 10rpx; border-radius: 5rpx; display: flex; align-items: center; justify-content: center;"
|
||
:style="getBoardTagStyleByLevel(item.continuous_days)">
|
||
{{formatBoardText(item.continuous_days)}}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 核心板块(按关键词匹配颜色) -->
|
||
<view style="display: flex; align-items: center; justify-content: center;">
|
||
<view style="background-color: #F4EFFF; border-radius: 5rpx; padding: 0 10rpx; white-space: nowrap; max-width: 120rpx; overflow: hidden;text-overflow: ellipsis;" :style="{color: getSectorTextColor(item.core_sectors[0] || '未知板块')}">
|
||
{{item.core_sectors[0] || '未知板块'}}
|
||
</view>
|
||
</view>
|
||
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
inject
|
||
} from 'vue'
|
||
import {
|
||
getBaseURL1
|
||
} from '@/request/http.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
navH: inject('navHeight'),
|
||
contentTop: '',
|
||
activeIndex: 0,
|
||
bkList: [],
|
||
bkFilters: [
|
||
|
||
'按连板数',
|
||
'只看龙头'
|
||
],
|
||
filterIndex: 0,
|
||
selectedFullDate: '', // 年-月-日
|
||
originData: null, // 原始接口数据
|
||
allStocks: [], // 所有股票数据(带角色标签)
|
||
// 角色配置
|
||
STOCK_ROLES: {
|
||
dragon: {
|
||
text: '龙头',
|
||
color: '#EC3440',
|
||
bgColor: '#FFE8E9',
|
||
icon: '/pagesStock/static/icon/first-icon1.png'
|
||
},
|
||
follow: {
|
||
text: '跟风',
|
||
color: '#F97316',
|
||
bgColor: '#FFF0E6',
|
||
icon: '/pagesStock/static/icon/first-icon2.png'
|
||
},
|
||
first: {
|
||
text: '首板',
|
||
color: '#01AB5D',
|
||
bgColor: '#E4F9EF',
|
||
icon: '/pagesStock/static/icon/first-icon.png'
|
||
},
|
||
normal: {
|
||
text: '',
|
||
color: '',
|
||
bgColor: '',
|
||
icon: ''
|
||
}
|
||
},
|
||
// 连板层级样式配置(新规则)
|
||
BOARD_LEVEL_STYLES: {
|
||
dragon: { // 5板及以上 龙头
|
||
|
||
color: '#ef4444',
|
||
bgColor: '#FFE8E9',
|
||
borderColor: '#ef4444'
|
||
},
|
||
high: { // 3-4板 高位
|
||
|
||
color: '#f97316',
|
||
bgColor: '#FFF0E6',
|
||
borderColor: '#f97316'
|
||
},
|
||
mid: { // 2板 中位
|
||
|
||
color: '#eab308',
|
||
bgColor: '#FFF9E6',
|
||
borderColor: '#eab308'
|
||
},
|
||
first: { // 1板 首板
|
||
|
||
color: '#22c55e',
|
||
bgColor: '#E4F9EF',
|
||
borderColor: '#22c55e'
|
||
}
|
||
},
|
||
// 板块文字颜色配置(新规则)
|
||
SECTOR_COLOR_RULES: [
|
||
{ keyword: '公告', color: '#D4AF37' }, // 金色
|
||
{ keyword: '其他', color: '#9CA3AF' }, // 灰色
|
||
{ keyword: ['AI', '人工智能', '芯片'], color: '#8B5CF6' }, // 紫色
|
||
{ keyword: ['锂电', '电池', '新能源'], color: '#10B981' }, // 翠绿
|
||
{ keyword: ['医药', '医疗'], color: '#EC4899' }, // 粉色
|
||
{ keyword: ['金融', '银行'], color: '#F59E0B' }, // 橙黄
|
||
{ keyword: ['军工', '航空'], color: '#EF4444' }, // 红色
|
||
],
|
||
DEFAULT_SECTOR_COLOR: '#06B6D4' // 默认 青色
|
||
|
||
}
|
||
},
|
||
computed: {
|
||
// 筛选后的股票列表:按板块codes匹配 + 连板排序/筛选
|
||
filteredStocks() {
|
||
if (!this.originData?.stocks || !this.bkList.length) return [];
|
||
|
||
// 1. 获取当前选中板块的股票代码集合
|
||
const currentBk = this.bkList[this.activeIndex];
|
||
if (!currentBk?.codes || currentBk.codes.length === 0) return [];
|
||
const targetCodes = new Set(currentBk.codes); // 转Set提升匹配效率
|
||
|
||
// 2. 从stocks中筛选出scode在targetCodes中的股票
|
||
let stocks = this.originData.stocks.filter(stock => targetCodes.has(stock.scode));
|
||
|
||
// 3. 保留原有筛选/排序逻辑
|
||
switch (this.filterIndex) {
|
||
case 0: // 按连板数从高到低排序
|
||
stocks.sort((a, b) => {
|
||
const aDays = this.parseContinuousDays(a.continuous_days);
|
||
const bDays = this.parseContinuousDays(b.continuous_days);
|
||
return bDays - aDays;
|
||
});
|
||
break;
|
||
case 1: // 只看龙头(≥2连板),并按连板数从高到低排序
|
||
stocks = stocks.filter(stock => this.parseContinuousDays(stock.continuous_days) >= 2);
|
||
stocks.sort((a, b) => {
|
||
const aDays = this.parseContinuousDays(a.continuous_days);
|
||
const bDays = this.parseContinuousDays(b.continuous_days);
|
||
return bDays - aDays;
|
||
});
|
||
break;
|
||
}
|
||
|
||
return stocks;
|
||
}
|
||
},
|
||
|
||
onLoad(e) {
|
||
this.activeIndex = e.index
|
||
this.selectedFullDate = e.data
|
||
console.log("selectedFullDate", this.selectedFullDate)
|
||
this.contentTop = this.navH + 20 / 750 * inject('windowWidth')
|
||
|
||
this.fetchData()
|
||
},
|
||
methods: {
|
||
|
||
// 解析连板数
|
||
parseContinuousDays(continuousDaysStr) {
|
||
if (!continuousDaysStr) return 0;
|
||
const match = continuousDaysStr.match(/(\d+)天/);
|
||
return match ? Number(match[1]) : 0;
|
||
},
|
||
|
||
// 格式化连板文本(适配新层级)
|
||
formatBoardText(continuousDaysStr) {
|
||
const boardDays = this.parseContinuousDays(continuousDaysStr);
|
||
|
||
if (boardDays === 1) return '首板'; // 1板显示首板
|
||
if (boardDays > 1) return `${boardDays}连板`; // 2板及以上显示 X连板
|
||
|
||
return ''; // 无连板数时返回空
|
||
},
|
||
|
||
// 获取连板标签样式(按新层级规则)
|
||
getBoardTagStyleByLevel(continuousDaysStr) {
|
||
const boardDays = this.parseContinuousDays(continuousDaysStr);
|
||
let styleConfig = {};
|
||
|
||
if (boardDays >= 5) {
|
||
styleConfig = this.BOARD_LEVEL_STYLES.dragon;
|
||
} else if (boardDays >= 3 && boardDays <= 4) {
|
||
styleConfig = this.BOARD_LEVEL_STYLES.high;
|
||
} else if (boardDays === 2) {
|
||
styleConfig = this.BOARD_LEVEL_STYLES.mid;
|
||
} else if (boardDays === 1) {
|
||
styleConfig = this.BOARD_LEVEL_STYLES.first;
|
||
}
|
||
|
||
return {
|
||
'color': styleConfig.color || '#FFFFFF',
|
||
'background-color': styleConfig.bgColor || '#eab308',
|
||
'border': `1rpx solid ${styleConfig.borderColor || '#eab308'}`
|
||
};
|
||
},
|
||
|
||
// 获取板块文字颜色(按关键词匹配)
|
||
getSectorTextColor(sectorName) {
|
||
if (!sectorName) return this.DEFAULT_SECTOR_COLOR;
|
||
|
||
// 遍历匹配规则
|
||
for (const rule of this.SECTOR_COLOR_RULES) {
|
||
if (Array.isArray(rule.keyword)) {
|
||
// 多关键词匹配
|
||
const isMatch = rule.keyword.some(key => sectorName.includes(key));
|
||
if (isMatch) return rule.color;
|
||
} else {
|
||
// 单关键词完全匹配
|
||
if (sectorName === rule.keyword) return rule.color;
|
||
}
|
||
}
|
||
|
||
// 无匹配返回默认色
|
||
return this.DEFAULT_SECTOR_COLOR;
|
||
},
|
||
|
||
// 获取股票角色
|
||
getStockRole(stock, sectorStocks, sectorIndex) {
|
||
const boardDays = this.parseContinuousDays(stock.continuous_days);
|
||
|
||
// 5板以上直接是龙头
|
||
if (boardDays >= 5) {
|
||
return this.STOCK_ROLES.dragon;
|
||
}
|
||
|
||
// 首板判断:连板数为1
|
||
if (boardDays === 1) {
|
||
return this.STOCK_ROLES.first;
|
||
}
|
||
|
||
// 跟风判断:热门板块(前3) + 2-4连板
|
||
if (sectorIndex < 3 && boardDays >= 2 && boardDays < 5) {
|
||
// 特殊情况:板块内涨停最早 + 3板以上 → 龙头
|
||
const sortedByTime = [...sectorStocks].sort((a, b) =>
|
||
(a.zt_time || "").localeCompare(b.zt_time || "")
|
||
);
|
||
if (sortedByTime[0]?.scode === stock.scode && boardDays >= 3) {
|
||
return this.STOCK_ROLES.dragon;
|
||
}
|
||
return this.STOCK_ROLES.follow;
|
||
}
|
||
|
||
// 其他:普通
|
||
return this.STOCK_ROLES.normal;
|
||
},
|
||
|
||
// 获取角色标签样式
|
||
getRoleTagStyle(role) {
|
||
return {
|
||
'background-color': role.bgColor
|
||
|
||
};
|
||
},
|
||
|
||
// 处理板块切换
|
||
handleTabChange(index) {
|
||
this.activeIndex = index;
|
||
// 切换板块后重新计算股票角色(可选)
|
||
this.setStockRoles();
|
||
},
|
||
|
||
// 处理筛选切换
|
||
handleFilterChange(index) {
|
||
this.filterIndex = index;
|
||
},
|
||
|
||
|
||
|
||
// 为所有股票添加角色标签
|
||
setStockRoles() {
|
||
if (!this.originData || !this.originData.stocks || !this.bkList.length) return;
|
||
this.allStocks = this.originData.stocks.map(stock => {
|
||
// 找到股票所属板块的热度排名
|
||
let sectorIndex = -1;
|
||
const stockSectors = Array.isArray(stock.sector_category) ? stock.sector_category : [stock.sector_category];
|
||
// 匹配板块列表中的位置
|
||
this.bkList.some((bk, idx) => {
|
||
const match = stockSectors.some(s => s.includes(bk.title));
|
||
if (match) {
|
||
sectorIndex = idx;
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
// 获取同板块的所有股票
|
||
const sectorStocks = this.originData.stocks.filter(s => {
|
||
const sSectors = Array.isArray(s.sector_category) ? s.sector_category : [s.sector_category];
|
||
return sSectors.some(ss => stockSectors.includes(ss));
|
||
});
|
||
// 获取股票角色
|
||
const stockRole = this.getStockRole(stock, sectorStocks, sectorIndex);
|
||
return {
|
||
...stock,
|
||
stockRole: stockRole.text ? stockRole : null
|
||
};
|
||
});
|
||
},
|
||
|
||
|
||
|
||
/**
|
||
* 请求接口数据(优化:动态日期+自动时间戳)
|
||
*/
|
||
// 请求接口数据
|
||
async fetchData() {
|
||
try {
|
||
const timestamp = new Date().getTime();
|
||
const formattedDate = this.selectedFullDate;
|
||
const baseURL = getBaseURL1();
|
||
const requestUrl = `${baseURL}/data/zt/daily/${formattedDate}.json?t=${timestamp}`;
|
||
|
||
console.log('请求URL:', requestUrl);
|
||
const res = await uni.request({
|
||
url: requestUrl,
|
||
method: 'GET'
|
||
});
|
||
|
||
if (res.statusCode === 200 && res.data) {
|
||
this.originData = res.data;
|
||
const { sector_data } = this.originData;
|
||
|
||
// 解析sector_data生成板块列表:剔除「其他」,格式[{title: 板块名, codes: 股票代码数组}]
|
||
this.bkList = Object.entries(sector_data)
|
||
.filter(([sectorName]) => sectorName !== '其他') // 去掉其他板块
|
||
.map(([sectorName, sectorInfo]) => ({
|
||
title: sectorName,
|
||
codes: sectorInfo.stock_codes || [] // 取板块对应的股票代码
|
||
}));
|
||
|
||
console.log('生成板块列表:', this.bkList);
|
||
// 为股票添加角色标签
|
||
this.setStockRoles();
|
||
} else {
|
||
uni.showToast({
|
||
title: '数据请求失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('请求异常:', error);
|
||
uni.showToast({
|
||
title: '网络异常',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less">
|
||
page {
|
||
background-color: #070707;
|
||
}
|
||
|
||
.topBg {
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: auto;
|
||
}
|
||
|
||
.stockDetailsC {
|
||
left: 25rpx;
|
||
right: 25rpx;
|
||
width: calc(100vw - 50rpx);
|
||
bottom: env(safe-area-inset-bottom);
|
||
}
|
||
</style> |