From 1c13386dfc4b155998c949d7edc31fb8c73c5f87 Mon Sep 17 00:00:00 2001 From: renzhijun <3051619471@qq.com> Date: Sat, 31 Jan 2026 16:59:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AA=E8=82=A1=E4=B8=AD=E5=BF=83=E3=80=81?= =?UTF-8?q?=E6=B6=A8=E5=81=9C=E5=88=86=E6=9E=90=E6=8E=A5=E5=8F=A3=E5=AF=B9?= =?UTF-8?q?=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/LCCalendar/LCCalendar.vue | 212 +++++-- components/WordCloud/WordCloud.vue | 52 +- pages/ztfx/ztfx.vue | 806 +++++++++------------------ 3 files changed, 459 insertions(+), 611 deletions(-) diff --git a/components/LCCalendar/LCCalendar.vue b/components/LCCalendar/LCCalendar.vue index 18a4679..9b01209 100644 --- a/components/LCCalendar/LCCalendar.vue +++ b/components/LCCalendar/LCCalendar.vue @@ -125,14 +125,45 @@ this.getYesterdayDateData() this.generateMonthDateListData() // 新增:初始化时触发一次事件,传递当天数据给父页面 - this.emitDateChange(currentYear, currentMonth, currentDay, this.getTodayItem(currentYear, currentMonth, currentDay)) - + //this.emitDateChange(currentYear, currentMonth, currentDay, this.getTodayItem(currentYear, currentMonth, currentDay)) +this.emitDateChange( + currentYear, + currentMonth, + currentDay, + this.getTodayItem(currentYear, currentMonth, currentDay), + this.getPrevDayItem(currentYear, currentMonth, currentDay) // 新增上一天数据 +) }, mounted() { this.getCalendarCombinedData() }, - methods: { + methods: { + // 3. 在日历组件methods中新增getPrevDayItem方法 + /** + * 获取指定日期的上一天数据 + */ + getPrevDayItem(year, month, day) { + // 计算上一天日期 + const currentDate = new Date(`${year}-${month}-${day}`); + const prevDate = new Date(currentDate.getTime() - 24 * 60 * 60 * 1000); + const prevYear = prevDate.getFullYear(); + const prevMonth = prevDate.getMonth() + 1; + const prevDay = prevDate.getDate(); + + // 获取上一天的完整数据 + const targetDate = `${prevYear}-${prevMonth > 9 ? prevMonth : '0' + prevMonth}-${prevDay > 9 ? prevDay : '0' + prevDay}`; + const currentMonthList = this.monthDateList[this.selectMonthIndex] || []; + const localItem = currentMonthList.find(item => item.date === targetDate) || null; + const apiData = this.getCalendarItemByDate(targetDate) || {}; + + return { + ...localItem, + zt_count: apiData.zt_count || 0, + top_sector: apiData.top_sector || '-', + zaban_rate: apiData.zaban_rate || '0%' + }; + }, /** * 获取当天的item数据(合并接口数据) */ @@ -156,26 +187,52 @@ /** * 触发日期变更事件(传递包含接口数据的item) */ - emitDateChange(year, month, day, item) { - const yearMonth = `${year}-${month > 9 ? month : '0' + month}`; - const fullDate = `${year}-${month > 9 ? month : '0' + month}-${day > 9 ? day : '0' + day}`; - this.$emit('date-change', { - yearMonth, - fullDate, - item: item || { // 兜底:无item时赋值空对象+默认值 - date: fullDate, - year, - month, - day, - zt_count: 0, - top_sector: '-', - zaban_rate: '0%' - }, - year, - month, - day - }); - }, + // emitDateChange(year, month, day, item) { + // const yearMonth = `${year}-${month > 9 ? month : '0' + month}`; + // const fullDate = `${year}-${month > 9 ? month : '0' + month}-${day > 9 ? day : '0' + day}`; + // this.$emit('date-change', { + // yearMonth, + // fullDate, + // item: item || { // 兜底:无item时赋值空对象+默认值 + // date: fullDate, + // year, + // month, + // day, + // zt_count: 0, + // top_sector: '-', + // zaban_rate: '0%' + // }, + // year, + // month, + // day + // }); + // }, + emitDateChange(year, month, day, item, prevItem = { zt_count: 0 }) { // 新增prevItem参数,设置默认值兜底 + const yearMonth = `${year}-${month > 9 ? month : '0' + month}`; + const fullDate = `${year}-${month > 9 ? month : '0' + month}-${day > 9 ? day : '0' + day}`; + this.$emit('date-change', { + yearMonth, + fullDate, + item: item || { // 兜底:无当前item时赋值空对象+默认值 + date: fullDate, + year, + month, + day, + zt_count: 0, + top_sector: '-', + zaban_rate: '0%' + }, + prevItem: prevItem || { // 新增:传递上一天数据,兜底默认值 + zt_count: 0, + top_sector: '-', + zaban_rate: '0%' + }, + year, + month, + day + }); + }, + /** * 获取当前时间前一天的数据 */ @@ -259,7 +316,14 @@ // 接口数据加载后,重新触发一次当前选中日期的事件,更新数据 if (this.selectDateStr) { const [year, month, day] = this.selectDateStr.split('-').map(Number); - this.emitDateChange(year, month, day, this.getTodayItem(year, month, day)); + //this.emitDateChange(year, month, day, this.getTodayItem(year, month, day)); + this.emitDateChange( + year, + month, + day, + this.getTodayItem(year, month, day), + this.getPrevDayItem(year, month, day) // 新增上一天数据 + ) } } else { this.calendarApiData = []; @@ -460,28 +524,84 @@ /** * 点击选择开始日期和结束日期 * @param {Object} item - */ - clickSelectDate(item, index) { // 新增index参数 - if (!item.isCurrentMonth) return - if (this.selectDateStr != item.date) { - this.selectDateStr = item.date - // 1. 获取该日期的接口数据 - const apiData = this.getCalendarItemByDate(item.date) || {}; - // 2. 合并本地item和接口数据,补充默认值 - const mergedItem = { - ...item, // 本地日期基础数据 - zt_count: apiData.zt_count || 0, // 涨停家数,默认0 - top_sector: apiData.top_sector || '-', // 热门板块,默认'-' - zaban_rate: apiData.zaban_rate || '0%', // 炸板率,示例字段 - isWeekend: index % 7 === 0 || index % 7 === 6 // 是否周末 - }; - this.chgStockData = mergedItem; - // 3. 解析日期,触发事件传递合并后的item - const [year, month, day] = item.date.split('-').map(Number); - this.emitDateChange(year, month, day, mergedItem); - console.log('点击某天(含接口数据)', mergedItem); - } - } + */ + clickSelectDate(item, index) { // 新增index参数 + if (!item.isCurrentMonth) return + if (this.selectDateStr != item.date) { + this.selectDateStr = item.date + // 1. 获取该日期的接口数据 + const apiData = this.getCalendarItemByDate(item.date) || {}; + // 2. 合并本地item和接口数据,补充默认值 + const mergedItem = { + ...item, // 本地日期基础数据 + zt_count: apiData.zt_count || 0, // 涨停家数,默认0 + top_sector: apiData.top_sector || '-', // 热门板块,默认'-' + zaban_rate: apiData.zaban_rate || '0%', // 炸板率,示例字段 + isWeekend: index % 7 === 0 || index % 7 === 6 // 是否周末 + }; + + // ===== 新增:获取上一天的日期和数据 ===== + // 2.1 计算上一天的日期 + const currentDate = new Date(item.date); + const prevDate = new Date(currentDate.getTime() - 24 * 60 * 60 * 1000); // 减一天 + const prevYear = prevDate.getFullYear(); + const prevMonth = prevDate.getMonth() + 1; + const prevDay = prevDate.getDate(); + const prevDateStr = `${prevYear}-${prevMonth > 9 ? prevMonth : '0' + prevMonth}-${prevDay > 9 ? prevDay : '0' + prevDay}`; + + // 2.2 获取上一天的接口数据 + const prevApiData = this.getCalendarItemByDate(prevDateStr) || {}; + // 2.3 查找上一天的本地item(用于补全基础信息) + let prevLocalItem = null; + // 先在当前月份列表找 + const currentMonthList = this.monthDateList[this.selectMonthIndex] || []; + prevLocalItem = currentMonthList.find(i => i.date === prevDateStr); + // 如果当前月份没有(跨月),找对应月份的列表 + if (!prevLocalItem) { + const prevMonthIndex = this.selectMonthIndex - (prevMonth < item.month ? 1 : 0); + const prevMonthList = this.monthDateList[prevMonthIndex] || []; + prevLocalItem = prevMonthList.find(i => i.date === prevDateStr); + } + + // 2.4 合并上一天的完整数据 + const prevMergedItem = { + ...(prevLocalItem || {}), + zt_count: prevApiData.zt_count || 0, + top_sector: prevApiData.top_sector || '-', + zaban_rate: prevApiData.zaban_rate || '0%', + isWeekend: false // 兜底默认值 + }; + // ======================================= + + this.chgStockData = mergedItem; + // 3. 解析日期,触发事件传递【当前数据+上一天数据】 + const [year, month, day] = item.date.split('-').map(Number); + this.emitDateChange(year, month, day, mergedItem, prevMergedItem); // 新增传递上一天数据 + console.log('点击某天(含接口数据)', { current: mergedItem, prev: prevMergedItem }); + } + } + + // clickSelectDate(item, index) { + // if (!item.isCurrentMonth) return + // if (this.selectDateStr != item.date) { + // this.selectDateStr = item.date + + // const apiData = this.getCalendarItemByDate(item.date) || {}; + + // const mergedItem = { + // ...item, + // zt_count: apiData.zt_count || 0, + // top_sector: apiData.top_sector || '-', + // zaban_rate: apiData.zaban_rate || '0%', + // isWeekend: index % 7 === 0 || index % 7 === 6 + // }; + // this.chgStockData = mergedItem; + + // const [year, month, day] = item.date.split('-').map(Number); + // this.emitDateChange(year, month, day, mergedItem); + // console.log('点击某天(含接口数据)', mergedItem); + // } + // } } } diff --git a/components/WordCloud/WordCloud.vue b/components/WordCloud/WordCloud.vue index 575bd5d..82ce1d1 100644 --- a/components/WordCloud/WordCloud.vue +++ b/components/WordCloud/WordCloud.vue @@ -35,6 +35,15 @@ export default { colorList: { type: Array, default: () => ['#60A5FA', '#FEC200', '#EF4444'] // 外圈、中间、中心 + }, + // 新增:字号配置,让组件更灵活 + fontSizeConfig: { + type: Object, + default: () => ({ + minSize: 12, // 最小字号 + maxSize: 40, // 最大字号 + scaleFactor: 0.1 // 缩放因子,越大字号差异越明显 + }) } }, data() { @@ -113,6 +122,11 @@ export default { // 按权重排序(权重越大,文字越大) const sortedWords = [...this.wordData].sort((a, b) => b.value - a.value); + // 计算value的最大值和最小值,用于归一化 + const values = sortedWords.map(item => item.value); + this.valueMax = Math.max(...values); + this.valueMin = Math.min(...values); + // 逐个绘制文字 sortedWords.forEach((word, index) => { this.placeWord(word, index); @@ -124,24 +138,22 @@ export default { const ctx = this.ctx; // 优化1:提高最大尝试次数(从50→150),让更多文字能找到位置 const maxAttempts = 150; - const baseFontSize = 12; // 基础字体大小 + const { minSize, maxSize, scaleFactor } = this.fontSizeConfig; - // 分段计算分母(适配不同value区间) - const value = word.value; - const baseDenominator = 1000; // 基础分母 - const interval = 500; // 区间步长 - const step = 500; // 分母每次增加的步长 + // ===== 核心优化:重新设计字号计算逻辑 ===== + // 1. 归一化value(0-1区间) + let normalizedValue = 1; + if (this.valueMax !== this.valueMin) { + normalizedValue = (word.value - this.valueMin) / (this.valueMax - this.valueMin); + } - // 计算当前value所属区间,动态确定分母 - const intervalNum = Math.floor(value / interval); - let denominator = baseDenominator + (intervalNum * step); - - // 兜底:避免分母过小(比如value为0时) - denominator = Math.max(denominator, baseDenominator); - - // 根据分段分母计算字体大小,限制最大值 - const maxFontSize = 28; // 优化2:适当减小最大字体(从32→28),节省空间 - const fontSize = Math.min(baseFontSize + (value / denominator) * 16, maxFontSize); + // 2. 计算字号:基于归一化值,确保最小到最大的平滑过渡 + // 公式:最小字号 + (最大字号 - 最小字号) * 归一化值 * 缩放因子 + const fontSize = Math.min( + minSize + (maxSize - minSize) * normalizedValue * scaleFactor, + maxSize + ); + // ========================================== // 旋转角度:-60° 到 60° const rotateAngle = (Math.random() - 0.5) * 120 * Math.PI / 180; @@ -150,7 +162,7 @@ export default { ctx.font = `${fontSize}px sans-serif`; // 获取文字宽度和高度(用于碰撞检测) - const textWidth = ctx.measureText(word.name).width; + const textWidth = ctx.measureText(word.text || word.name).width; // 兼容text/name字段 // 优化3:更精准的文字高度估算(从1.2→1.05),减少无效空间 const textHeight = fontSize * 1.05; @@ -193,7 +205,7 @@ export default { ctx.fillStyle = color; // 无重叠,绘制文字 - this.drawTextAtPosition(word.name, x, y, rotateAngle, fontSize); + this.drawTextAtPosition(word.text || word.name, x, y, rotateAngle, fontSize); // 记录已放置的文字信息(用于后续碰撞检测) this.placedWords.push({ @@ -238,9 +250,9 @@ export default { // 四个顶点坐标(相对于中心) const points = [ { x: -halfW, y: -halfH }, - { x: halfW, y: -halfH }, + { x: -halfW, y: halfH }, { x: halfW, y: halfH }, - { x: -halfW, y: halfH } + { x: halfW, y: -halfH } ]; // 旋转并平移到实际位置 diff --git a/pages/ztfx/ztfx.vue b/pages/ztfx/ztfx.vue index 8a7b5f7..3264f23 100644 --- a/pages/ztfx/ztfx.vue +++ b/pages/ztfx/ztfx.vue @@ -20,14 +20,26 @@ style="font-size: 30rpx; font-weight: bold; margin-left: 10rpx; margin-right: 20rpx;">核心指标 + {{item.data}} - - +{{item.change}} + + + {{item.change > 0 ? '+' + item.change : item.change}} + {{item.title}} @@ -46,9 +58,9 @@ 市场全景 35个板块 + style="color: #F3C368; border: 1rpx solid #F3C368; border-radius: 5rpx; text-align: center; padding: 0 10rpx;">{{bkList.length}}个板块 102只涨停 + style="color: #EC3440; border: 1rpx solid #EC3440; border-radius: 5rpx; text-align: center; padding: 0 10rpx; margin: 0 10rpx;">{{number_limit_stocks}}只涨停 高位股风险: 低 @@ -81,24 +93,28 @@ 冷门 - - - {{item.title}} - {{item.count}}只 + + {{item.title}} + {{item.count}}只 + + + - + 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; + } \ No newline at end of file