You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1003 lines
25 KiB

<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>