|
|
<template> <div class="market-temperature"> <div class="container"> <div class="border3"> <section class="chart-section"> <div> <div class="trapezoid"> <span>{{ companyName }}</span> <span>{{ stockCode }}</span> </div> <div ref="KlineCanvs" class="KlineClass"></div> </div> </section> </div>
<div class="border4"> <div class="border1"> <div class="title"> <img :src="biaoti" alt="标题" class="titleImg" /> <div class="titleContent"> {{ calendarDate }} </div> </div> <div class="secondTitle"> <div v-for="item in week" :key="item" class="secondTitleItems"> <div class="secondTitleItem">{{ item }}</div> </div> </div> <div class="calendar"> <div v-for="row in 5" :key="row" class="calendarCol"> <div v-for="item in currentData.slice((row - 1) * 7, row * 7)" :key="item" class="calendarRow" > <div class="calendarItem" :style="{ backgroundColor: item.color }" > <div v-if="item.month" class="month"> <div class="monthContent"> {{ months[item.month - 1] }} </div> </div> <div class="calendarItemTitle">{{ item.day }}</div> <div class="calendarItemContent" v-if="item.stock_temperature == ''" > <img :src="suoding" alt="锁定" class="ciImg" /> </div> <div class="calendarItemContent" v-else-if="item.stock_temperature == '休市'" > 休市 </div> <div class="calendarItemContent" v-else> <div v-if="isIndexCode"> {{ item.market_temperature }} </div> <div v-else> {{ item.market_temperature }} | {{ item.stock_temperature }} </div> </div> </div> </div> </div> </div> </div> <!-- <el-table :data="groupedWDRL" border :row-style="tableRowStyle" header-cell-class-name="table_header" :cell-style="tableCellStyle" :column-width="cellWidth"> <el-table-column v-for="(day, colIndex) in ['一', '二', '三', '四', '五', '六', '日']" :key="colIndex" :label="day"> <template #default="{ $index: rowIndex }"> <div v-if="getDayData(rowIndex, colIndex + 1)"> <p class="WDRL_date"> {{ formatDate(getDayData(rowIndex, colIndex + 1).date) }} <span class="month-display">{{ formatMonth(getDayData(rowIndex, colIndex + 1).date) }}</span> </p> <p class="WDRL_data"> <template v-if="isIndexCode"> <span v-if="getDayData(rowIndex, colIndex + 1).market_temperature"> {{ getDayData(rowIndex, colIndex + 1).market_temperature }} </span> </template> <template v-else> <template v-if="isBothRest(rowIndex, colIndex + 1)">休市</template> <template v-else> <span v-if="getDayData(rowIndex, colIndex + 1).stock_temperature"> {{ getDayData(rowIndex, colIndex + 1).stock_temperature }} </span> <span v-if="shouldShowDivider(rowIndex, colIndex + 1)"> | </span> <span v-if="getDayData(rowIndex, colIndex + 1).market_temperature"> {{ getDayData(rowIndex, colIndex + 1).market_temperature }} </span> </template> </template> </p> </div> <div v-else-if="shouldShowRest(rowIndex, colIndex + 1)"> <p class="WDRL_date">休市</p> </div> </template> </el-table-column> </el-table> --> </div> </div> </div> </template>
<script setup> import { ref, computed, onMounted, defineExpose, defineProps, onUnmounted, onBeforeUnmount, } from "vue"; import * as echarts from "echarts"; import biaoti from "../../assets/img/AiEmotion/标题.png"; import jiaobiao from "../../assets/img/AiEmotion/角标.png"; import suoding from "../../assets/img/AiEmotion/锁定.png"; import moment from "moment";
const props = defineProps({ companyName: { type: String, default: "", }, stockCode: { type: String, default: "", }, });
const color = ref([ "#32A3FF", "#2BD977", "#A239FF", "#FF7945", "#FF5289", "#5791CB", ]);
const months = ref([ "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月", ]);
const calendarDate = ref(); const week = ref(["周一", "周二", "周三", "周四", "周五", "周六", "周日"]);
const KlineCanvs = ref(); const WDRL = ref([]); const klineDataRaw = ref([]); // 用于存储 K 线图数据
let chartInstance = null; // 存储图表实例
const indexCodes = [ "NDX", "DJIA", "SPX", "STI", "KLSE", "TSX", "N225", "KS11", "JKSE", "1A0001", "HSI", "I63", "VNINDE", ]; const isIndexCode = computed(() => indexCodes.includes(props.code));
const currentData = ref([]); const fetchCalendarData = () => { const monthTitle = ref([]); currentData.value = JSON.parse(JSON.stringify(WDRL.value)); for (let i = 0; i < currentData.value.length; i++) { const day = moment(currentData.value[i].date).date(); currentData.value[i].day = day; if (i == 0 || day == 1) { monthTitle.value.push({ year: moment(currentData.value[i].date).year(), month: moment(currentData.value[i].date).month() + 1, }); currentData.value[i].month = moment(currentData.value[i].date).month() + 1; } const tp = currentData.value[i].stock_temperature; if (tp == "" || tp == null || tp == "休市") { currentData.value[i].color = color.value[5]; } else { if (tp <= "20") { currentData.value[i].color = color.value[0]; } else if (parseInt(tp) <= "50") { currentData.value[i].color = color.value[1]; } else if (parseInt(tp) <= "70") { currentData.value[i].color = color.value[2]; } else if (parseInt(tp) <= "90") { currentData.value[i].color = color.value[3]; } else { currentData.value[i].color = color.value[4]; } } }
const stDate = monthTitle.value[0]; const edDate = monthTitle.value[monthTitle.value.length - 1]; calendarDate.value = `${stDate.year}年${stDate.month}月 ~`; if (stDate.year != edDate.year) { calendarDate.value += `${edDate.year}年${edDate.month}月`; } else { calendarDate.value += `${edDate.month}月`; }
console.log(currentData.value); };
// 分组 WDRL 数据
// const groupedWDRL = computed(() => {
// const result = [];
// for (let i = 0; i < WDRL.value.length; i += 7) {
// result.push(WDRL.value.slice(i, i + 7));
// }
// return result;
// });
// 获取指定日期的数据
// function getDayData(rowIndex, dayIndex) {
// const weekData = groupedWDRL.value[rowIndex];
// if (weekData && weekData.length >= dayIndex) {
// return weekData[dayIndex - 1] || {};
// }
// return {};
// }
// 判断是否显示分隔符
// function shouldShowDivider(rowIndex, dayIndex) {
// const data = getDayData(rowIndex, dayIndex);
// return data?.market_temperature && data?.stock_temperature;
// }
// 判断是否都休市
// function isBothRest(rowIndex, colIndex) {
// const data = getDayData(rowIndex, colIndex);
// return (
// data &&
// data.stock_temperature === "休市" &&
// data.market_temperature === "休市"
// );
// }
// 判断是否显示休市信息
// function shouldShowRest(rowIndex, dayIndex) {
// const data = getDayData(rowIndex, dayIndex);
// if (data && (data.stock_temperature || data.market_temperature)) return false;
// const flatIndex = rowIndex * 7 + (dayIndex - 1);
// const targetDay = WDRL.value[flatIndex];
// if (!targetDay || !targetDay.date) return false;
// const [year, month, day] = targetDay.date.split("/").map(Number);
// if (!year || !month || !day) return false;
// const dateObj = new Date(year, month - 1, day);
// const today = new Date();
// if (
// dateObj.getMonth() !== today.getMonth() ||
// dateObj.getFullYear() !== today.getFullYear()
// )
// return false;
// const weekday = dateObj.getDay();
// return weekday >= 1 && weekday <= 5;
// }
// 格式化月份
// function formatMonth(dateStr) {
// if (!dateStr) return "";
// const month = dateStr.split("/")[1];
// const map = {
// "01": "一月",
// "02": "二月",
// "03": "三月",
// "04": "四月",
// "05": "五月",
// "06": "六月",
// "07": "七月",
// "08": "八月",
// "09": "九月",
// 10: "十月",
// 11: "十一月",
// 12: "十二月",
// };
// return map[month] || "";
// }
// 格式化日期
// function formatDate(dateStr) {
// if (!dateStr) return "";
// return dateStr.split("/")[2];
// }
// 设置表格单元格样式
// function tableCellStyle({ row, column, rowIndex, columnIndex }) {
// const data = getDayData(rowIndex, columnIndex + 1);
// let value = isIndexCode.value
// ? Number(data?.market_temperature)
// : Number(data?.stock_temperature);
// if (isNaN(value)) return { backgroundColor: "#4b759f", color: "white" };
// if (value >= 90) return { backgroundColor: "#BD0000", color: "white" };
// else if (value >= 70) return { backgroundColor: "#FF5638", color: "white" };
// else if (value >= 50) return { backgroundColor: "#C929E6", color: "white" };
// else if (value >= 20) return { backgroundColor: "#00AB00", color: "white" };
// else if (value > 0) return { backgroundColor: "#87CEEB", color: "white" };
// else return { backgroundColor: "#4b759f", color: "white" };
// }
// function tableRowStyle() {
// // 动态调整行高
// const containerWidth = document.querySelector(".border4")?.offsetWidth || 0;
// const rowHeight = containerWidth * 0.1; // 根据容器宽度的比例调整行高
// return { height: `${rowHeight}px` };
// }
// // 动态计算单元格宽度
// const containerWidth = document.querySelector(".border4")?.offsetWidth || 0;
// const cellWidth = containerWidth / 7;
// 初始化图表
function initChart(raw, klineDataRawValue, WDRLValue) { if (!raw || !klineDataRawValue || !WDRLValue) { console.error( "initChart: raw, klineDataRawValue or WDRLValue is undefined" ); return; }
// 如果已存在图表实例,先销毁
if (chartInstance) { chartInstance.dispose(); chartInstance = null; }
// 处理 K 线图数据
const klineData = klineDataRawValue.map((item) => { const open = item[1]; const close = item[2]; const low = item[3]; const high = item[4]; return [open, close, low, high]; });
// 计算K线数据的最小值和最大值
let minPrice = Infinity; let maxPrice = -Infinity; klineDataRawValue.forEach((item) => { const low = item[3]; const high = item[4]; minPrice = Math.min(minPrice, low); maxPrice = Math.max(maxPrice, high); });
// 计算小于最小值的整数作为y轴最小值
const yAxisMin = Math.floor(minPrice); // 计算大于最大值的整数作为y轴最大值
const yAxisMax = Math.ceil(maxPrice);
// 温度日历
WDRL.value = WDRLValue; klineDataRaw.value = klineDataRawValue;
fetchCalendarData();
const dateLabels = raw.map((item) => item[0]); const marketData = raw.map((item) => Math.round(item[1])); const stockData = raw.map((item) => Math.round(item[2]));
// 创建新的图表实例
chartInstance = echarts.init(KlineCanvs.value); chartInstance.setOption({ tooltip: { trigger: "axis", axisPointer: { type: "cross", crossStyle: { color: "#999", width: 1, type: "dashed", }, lineStyle: { color: "#999", width: 1, type: "dashed", }, }, formatter: function (params) { if (params && params.length > 0) { let result = `日期: ${params[0].name}<br/>`; params.forEach((param) => { if (param.seriesType === "candlestick") { const open = param.data[1]; const close = param.data[2]; const low = param.data[3]; const high = param.data[4]; result += `${param.seriesName}<br/>开: ${open}<br/>收: ${close}<br/>低: ${low}<br/>高: ${high}<br/>`; } else if (param.seriesType === "line") { result += `${param.seriesName}: ${param.value}<br/>`; } }); return result; } return ""; }, }, legend: { data: ["K线", "市场温度", "股票温度"], textStyle: { color: "white", fontSize: 18 }, }, xAxis: { type: "category", data: dateLabels, axisLine: { lineStyle: { color: "#00BFFF" } }, axisLabel: { color: "#FFFFFF", fontSize: 12, fontWeight: "bold", },
axisTick: { lineStyle: { color: "#00BFFF" } }, axisPointer: { show: true, type: "line", lineStyle: { color: "#999", width: 1, type: "dashed", }, label: { show: true, color: "black", }, }, }, yAxis: [ { min: yAxisMin, max: yAxisMax, axisLine: { lineStyle: { color: "#00FF7F" } }, axisLabel: { color: "#FFFFFF", fontSize: 12, fontWeight: "bold", }, axisTick: { lineStyle: { color: "#00FF7F" } }, splitLine: { show: false, lineStyle: { color: "#333333", type: "solid", opacity: 0.3, }, }, axisPointer: { show: true, type: "line", label: { show: true, color: "black", }, lineStyle: { color: "#999", width: 1, type: "dashed", }, }, }, { min: 0, max: 100, position: "right", axisLabel: { color: "#FFFF00", fontSize: 12, fontWeight: "bold", }, axisLine: { lineStyle: { color: "#FF1493", width: 2 } }, axisTick: { lineStyle: { color: "#FF1493" } }, splitLine: { show: false, lineStyle: { color: "#444444", type: "solid", opacity: 0.3, }, }, axisPointer: { show: true, type: "line", lineStyle: { color: "#999", width: 1, type: "dashed", }, label: { show: true, color: "black", }, }, }, ], color: ["#f00", "white"], series: [ { name: "K线", type: "candlestick", data: klineData, itemStyle: { normal: { color: "#00FF00", // 阳线红色
color0: "#FF0000", // 阴线绿色
borderColor: "#00FF00", // 阳线边框红色
borderColor0: "#FF0000", // 阴线边框绿色
}, }, }, { name: "市场温度", type: "line", yAxisIndex: 1, data: marketData, }, { name: "股票温度", type: "line", yAxisIndex: 1, data: stockData, }, ], // 添加 dataZoom 组件
dataZoom: [ { type: "slider", xAxisIndex: 0, filterMode: "filter", textStyle: { color: "white", }, bottom: "0%", // 下移数据缩放滑块
}, { type: "inside", xAxisIndex: 0, filterMode: "filter", }, ], }); // 防抖函数,避免频繁触发resize
const debounce = (func, wait) => { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; };
// 监听窗口大小变化
const resizeHandler = debounce(() => { if (chartInstance && !chartInstance.isDisposed()) { try { chartInstance.resize(); adjustCellFontSize(); // 同时调整表格字体大小
console.log("股市温度计图表已重新调整大小"); } catch (error) { console.error("股市温度计图表resize失败:", error); } } }, 100); // 100ms防抖延迟
// 移除之前的监听器(如果存在)
if (window.marketTempResizeHandler) { window.removeEventListener("resize", window.marketTempResizeHandler); }
// 添加新的监听器
window.addEventListener("resize", resizeHandler);
// 存储resize处理器以便后续清理
window.marketTempResizeHandler = resizeHandler;
// 添加容器大小监听器
const chartContainer = document.querySelector(".KlineClass"); if (chartContainer && window.ResizeObserver) { const containerObserver = new ResizeObserver( debounce(() => { if (chartInstance && !chartInstance.isDisposed()) { try { chartInstance.resize(); console.log("股市温度计容器大小变化,图表已调整"); } catch (error) { console.error("股市温度计容器resize失败:", error); } } }, 100) );
containerObserver.observe(chartContainer); window.marketTempContainerObserver = containerObserver; }
// 初始调整字体大小
adjustCellFontSize(); }
// 调整单元格字体大小
function adjustCellFontSize() { const table = document.querySelector(".border4 .el-table"); if (table) { const tableWidth = table.offsetWidth; const cellWidth = tableWidth / 7; // 假设一周 7 天
const fontSize = Math.min(cellWidth * 0.15, 20); // 根据单元格宽度动态计算字体大小
const dateElements = document.querySelectorAll(".WDRL_date"); const dataElements = document.querySelectorAll(".WDRL_data"); dateElements.forEach((el) => { el.style.fontSize = `${fontSize}px`; }); dataElements.forEach((el) => { el.style.fontSize = `${fontSize * 0.8}px`; }); } } // 组件卸载时清理资源
onBeforeUnmount(() => { // 销毁图表实例
if (chartInstance) { chartInstance.dispose(); chartInstance = null; } // 移除窗口resize监听器
if (window.marketTempResizeHandler) { window.removeEventListener("resize", window.marketTempResizeHandler); window.marketTempResizeHandler = null; }
// 清理容器观察器
if (window.marketTempContainerObserver) { window.marketTempContainerObserver.disconnect(); window.marketTempContainerObserver = null; } });
defineExpose({ initChart }); </script>
<style scoped> .WDRL_date { margin-top: 2px; text-align: center; font-size: 1.6vw; font-weight: bold; padding-top: 0%; position: relative; }
.month-display { position: absolute; top: 0; right: 0; font-size: 1vw; color: rgb(58, 58, 58); }
.WDRL_data { margin-top: 5px; text-align: center; font-size: 1vw; font-weight: bold; }
.table_header { color: white; background: #2a2a2a; }
.KlineClass { width: 100%; height: 600px; }
.market-temperature { min-height: 100vh; /* background-color: rgb(0, 22, 65); */ }
.container { margin: 0 auto; /* padding: 20px; */ max-width: 80vw; padding-bottom: 10%; }
.border3 { margin-top: 40px; border-radius: 8px; padding: 20px; margin-left: 0; width: 100%; height: 100%; box-sizing: border-box; overflow: hidden; }
.border4 { margin-top: 40px; border-radius: 8px; padding: 20px; width: 80%; margin-left: 8%; height: 48vw; overflow: visible; }
.border4 .el-table { height: auto !important; max-height: none !important; }
.border4 .el-table__body-wrapper { height: auto !important; max-height: none !important; overflow: visible !important; }
.border4 .el-table__body { height: auto !important; }
/* 手机端适配样式 */ @media only screen and (max-width: 768px) { .KlineClass { width: 100%; height: 300px; }
.border4 { margin-top: 0px; border-radius: 8px; padding: 0px; width: 100%; margin-left: 0%; height: 80vw; overflow: visible; }
.border4 .el-table { height: auto !important; max-height: none !important; }
.border4 .el-table__body-wrapper { height: auto !important; max-height: none !important; overflow: visible !important; }
.border4 .el-table__body { height: auto !important; }
.el-table .hidden-columns { position: absolute; visibility: hidden; z-index: -1; }
.border3 { margin-top: 25px; border-radius: 8px; padding: 20px; margin-left: -13px; width: 100%; height: 100%; }
.WDRL_date { font-size: 4.2vw; }
.month-display { font-size: 1.8vw; }
.WDRL_data { font-size: 3vw; }
.el-table .cell { box-sizing: border-box; line-height: 23px; overflow: hidden; overflow-wrap: break-word; padding: 0 12px; text-overflow: ellipsis; white-space: normal; text-align: center; }
.month { width: 80%; height: 70% !important; top: -4vw !important; right: -0.5vw; }
.monthContent { margin-top: 1.8vw !important; font-weight: bold; font-size: 1.2vw; }
.calendarItemTitle { font-size: 2.2vw !important; }
.calendarItemContent { font-size: 2.2vw !important; } }
.border1 { border: 2px solid #14bddb; background-color: #1f669e; width: 100%; height: 100%; border-radius: 1%; position: relative; display: flex; flex-direction: column; align-items: center; padding-bottom: 20px; /* margin: 0px; */ }
.border1::before { content: ""; position: absolute; top: -2.5px; left: -2.5px; width: 20px; height: 20px; border-top: 3px solid #00ffff; /* 左上角颜色 */ border-left: 3px solid #00ffff; border-top-left-radius: 8px; }
.border1::after { content: ""; position: absolute; bottom: -2px; right: -2px; width: 20px; height: 20px; border-bottom: 3px solid #00ffff; /* 右下角颜色 */ border-right: 3px solid #00ffff; border-bottom-right-radius: 8px; }
@font-face { font-family: "方正新综艺简体"; src: url("../../assets/fonts/方正新综艺简体.ttf") format("truetype"); font-weight: normal; font-style: normal; font-display: swap; } .title { width: 100%; margin-bottom: 0px; position: relative; }
.titleImg { width: 100%; margin-top: 10px; }
.titleContent { font-family: "方正新综艺简体"; font-size: calc(10px + 1.5vw); color: #00ffff; position: absolute; bottom: 42%; left: 30px; }
.secondTitle { width: 100%; height: 35px; display: flex; justify-content: center; }
.secondTitleItems { width: 13.5%; height: 100%; display: flex; }
.secondTitleItem { border: 1px solid #03a7ce; background-color: #0b3c73; width: 100%; color: #ffffff; display: flex; justify-content: center; align-items: center; }
.calendar { margin-top: 30px; width: 100%; height: 70%; display: flex; justify-content: center; flex-direction: column; }
.calendarCol { width: 100%; height: 100%; display: flex; justify-content: center; }
.calendarRow { width: 13.5%; height: 100%; } .calendarItem { border: 1px solid #0060af; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; border-radius: 10px; color: #ffffff; position: relative; /* font-size: 1.5vw; */ }
.month { position: absolute; background-image: url("../../assets/img/AiEmotion/角标.png"); background-size: 100% 100%; background-repeat: no-repeat; width: 80%; height: 80%; display: flex; justify-content: center; /* align-items: center; */ top: -2vw; right: -0.5vw;
z-index: 1; }
.monthContent { margin-top: 1.2vw; font-weight: bold; font-size: 1.2vw; }
.calendarItemTitle { width: 100%; text-align: center; font-weight: bold; font-size: 1.2vw; }
.calendarItemContent { width: 100%; /* text-align: center; */ display: flex; justify-content: center; align-items: center; font-weight: bold; font-size: 1.2vw; }
.ciImg { width: 20%; } </style>
|