diff --git a/pages/ztfx/ztfx.vue b/pages/ztfx/ztfx.vue index c86fbac..58760a9 100644 --- a/pages/ztfx/ztfx.vue +++ b/pages/ztfx/ztfx.vue @@ -142,42 +142,77 @@ - + @@ -360,6 +395,69 @@ // '板块分布', // '热门概念词云' // ], + 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: [ '板块分布', @@ -442,9 +540,6 @@ this.activeIndex = e.index this.contentTop = this.navH + 20 / 750 * inject('windowWidth') - //this.selectedFullDate = this.getPreviousDayDate(); - //console.log("selectedFullDate", this.selectedFullDate) - //this.fetchData() }, @@ -461,6 +556,155 @@ //} }, methods: { +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'; @@ -575,6 +819,9 @@ // 3. 核心:限制最多显示16条(切片操作放在最后) this.bkList = bkList.slice(0, 16); this.initPieChart(); + + this.calculateHighPositionStats(); + } else { uni.showToast({ title: '数据请求失败', @@ -702,39 +949,39 @@ 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); - } - } + // ===== 新增:判断选中日期是否为系统当天,若是则日期减一天 ===== + 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() }, diff --git a/pagesStock/static/icon/all-icon-6.png b/pagesStock/static/icon/all-icon-6.png new file mode 100644 index 0000000..73a97eb Binary files /dev/null and b/pagesStock/static/icon/all-icon-6.png differ diff --git a/pagesStock/static/icon/all-icon-7.png b/pagesStock/static/icon/all-icon-7.png new file mode 100644 index 0000000..55686eb Binary files /dev/null and b/pagesStock/static/icon/all-icon-7.png differ diff --git a/pagesStock/static/icon/all-icon-8.png b/pagesStock/static/icon/all-icon-8.png new file mode 100644 index 0000000..6ac1bd0 Binary files /dev/null and b/pagesStock/static/icon/all-icon-8.png differ diff --git a/pagesStock/static/icon/all-icon-9.png b/pagesStock/static/icon/all-icon-9.png new file mode 100644 index 0000000..d4f5126 Binary files /dev/null and b/pagesStock/static/icon/all-icon-9.png differ diff --git a/pagesStock/static/icon/first-icon.png b/pagesStock/static/icon/first-icon.png new file mode 100644 index 0000000..8dfea95 Binary files /dev/null and b/pagesStock/static/icon/first-icon.png differ diff --git a/pagesStock/static/icon/first-icon1.png b/pagesStock/static/icon/first-icon1.png new file mode 100644 index 0000000..0c947dc Binary files /dev/null and b/pagesStock/static/icon/first-icon1.png differ diff --git a/pagesStock/static/icon/first-icon2.png b/pagesStock/static/icon/first-icon2.png new file mode 100644 index 0000000..f9080ec Binary files /dev/null and b/pagesStock/static/icon/first-icon2.png differ diff --git a/pagesStock/stockCenterDetails/bkydmx.vue b/pagesStock/stockCenterDetails/bkydmx.vue index 42f7466..0240c4e 100644 --- a/pagesStock/stockCenterDetails/bkydmx.vue +++ b/pagesStock/stockCenterDetails/bkydmx.vue @@ -2,64 +2,95 @@ - - - - - - - - {{item.title}} - - - - - - - - - - {{item}} - - - - - - - {{item}} - - - - - - - - - 跟风 - - 康强电子 - - - - +10.00% - - - - 2连板 - - - - 芯片(封装材料) - - - - - - - - - + + + + + + + + {{item.title}} + + + + + + + + + + {{item}} + + + + + + + {{item}} + + + + + + + + + + + + + + {{item.stockRole.text}} + + {{item.sname}} + + + + + +10.00% + + + + + + {{formatBoardText(item.continuous_days)}} + + + + + + + {{item.core_sectors[0] || '未知板块'}} + + + + + + + + @@ -68,73 +99,306 @@ import { inject } from 'vue' -import { + import { getBaseURL1 } from '@/request/http.js' - + export default { data() { return { navH: inject('navHeight'), - contentTop: '', + 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: { + // 筛选后的股票列表 + filteredStocks() { + if (!this.allStocks.length) return []; + + let stocks = [...this.allStocks]; + + // 先筛选当前选中板块的股票 + if (this.activeIndex >= 0 && this.bkList.length) { + const currentSector = this.bkList[this.activeIndex]?.title; + if (currentSector) { + stocks = stocks.filter(stock => { + // 匹配核心板块或板块分类 + const sectorMatch = stock.core_sectors.some(s => s.includes(currentSector)) || + (Array.isArray(stock.sector_category) ? stock.sector_category.includes(currentSector) : stock.sector_category === currentSector); + return sectorMatch; + }); + } + } + + // 根据筛选类型排序/过滤 + 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 + this.selectedFullDate = e.data + console.log("selectedFullDate", this.selectedFullDate) this.contentTop = this.navH + 20 / 750 * inject('windowWidth') - - this.fetchData() + + this.fetchData() }, methods: { - getPreviousDayDate(dateStr) { - // 校验输入日期格式是否正确 - if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) { - console.error('日期格式错误,请传入 YYYY-MM-DD 格式的日期'); - return ''; - } + // 解析连板数 + 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 + + }; + }, - // 创建日期对象(注意:月份是 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}`; + // 处理板块切换 + handleTabChange(index) { + this.activeIndex = index; + // 切换板块后重新计算股票角色(可选) + this.setStockRoles(); }, + + // 处理筛选切换 + handleFilterChange(index) { + this.filterIndex = index; + }, + + + // 为所有股票添加角色标签 + setStockRoles() { + if (!this.originData || !this.originData.stocks || !this.bkList.length) return; + console.log("setStockRoles",JSON.stringify(this.originData.stocks)) + 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 { - // 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便于调试 + console.log('请求URL:', requestUrl); const res = await uni.request({ url: requestUrl, @@ -144,30 +408,28 @@ import { 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长度一致 + const maxLen = Math.min(labels.length, counts.length); 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; - bkList.push({ - title, // 板块名称 - count, // 数量 - }); + title, + count + }); } - this.bkList = bkList; - + + // 为股票添加角色标签 + this.setStockRoles(); } else { uni.showToast({ title: '数据请求失败', @@ -200,7 +462,7 @@ import { .stockDetailsC { left: 25rpx; - right: 25rpx; + right: 25rpx; width: calc(100vw - 50rpx); bottom: env(safe-area-inset-bottom); }