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.

1152 lines
31 KiB

2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
2 months ago
  1. <template>
  2. <div class="market-temperature">
  3. <div class="container">
  4. <div class="border3">
  5. <section class="chart-section">
  6. <div>
  7. <!-- <div class="trapezoid">
  8. <span>{{ companyName }}</span>
  9. <span>{{ stockCode }}</span>
  10. </div> -->
  11. <div ref="KlineCanvs" class="KlineClass"></div>
  12. </div>
  13. </section>
  14. </div>
  15. <div class="border4">
  16. <div class="border1">
  17. <div class="title">
  18. <img :src="biaoti" alt="标题" class="titleImg" />
  19. <div class="titleContent">
  20. {{ calendarDate }}
  21. </div>
  22. </div>
  23. <div class="secondTitle">
  24. <div v-for="item in week" :key="item" class="secondTitleItems">
  25. <div class="secondTitleItem">{{ item }}</div>
  26. </div>
  27. </div>
  28. <div class="calendar">
  29. <div v-for="row in 5" :key="row" class="calendarCol">
  30. <div v-for="item in currentData.slice((row - 1) * 7, row * 7)" :key="item" class="calendarRow">
  31. <div class="calendarItem" :style="{ backgroundColor: item.color }">
  32. <div v-if="item.month" class="month">
  33. <div class="monthContent">
  34. {{ months[item.month - 1] }}
  35. </div>
  36. </div>
  37. <div class="calendarItemTitle">{{ item.day }}</div>
  38. <div class="calendarItemContent" v-if="item.stock_temperature == ''">
  39. <img :src="suoding" alt="锁定" class="ciImg" />
  40. </div>
  41. <div class="calendarItemContent" v-else-if="item.stock_temperature == '休市'">
  42. 休市
  43. </div>
  44. <div class="calendarItemContent" v-else>
  45. <div v-if="isIndexCode">
  46. {{ item.market_temperature }}
  47. </div>
  48. <div v-else>
  49. {{ item.market_temperature }} |
  50. {{ item.stock_temperature }}
  51. </div>
  52. </div>
  53. </div>
  54. </div>
  55. </div>
  56. </div>
  57. </div>
  58. <!-- <el-table :data="groupedWDRL" border :row-style="tableRowStyle" header-cell-class-name="table_header"
  59. :cell-style="tableCellStyle" :column-width="cellWidth">
  60. <el-table-column v-for="(day, colIndex) in ['一', '二', '三', '四', '五', '六', '日']" :key="colIndex" :label="day">
  61. <template #default="{ $index: rowIndex }">
  62. <div v-if="getDayData(rowIndex, colIndex + 1)">
  63. <p class="WDRL_date">
  64. {{ formatDate(getDayData(rowIndex, colIndex + 1).date) }}
  65. <span class="month-display">{{ formatMonth(getDayData(rowIndex, colIndex + 1).date) }}</span>
  66. </p>
  67. <p class="WDRL_data">
  68. <template v-if="isIndexCode">
  69. <span v-if="getDayData(rowIndex, colIndex + 1).market_temperature">
  70. {{ getDayData(rowIndex, colIndex + 1).market_temperature }}
  71. </span>
  72. </template>
  73. <template v-else>
  74. <template v-if="isBothRest(rowIndex, colIndex + 1)">休市</template>
  75. <template v-else>
  76. <span v-if="getDayData(rowIndex, colIndex + 1).stock_temperature">
  77. {{ getDayData(rowIndex, colIndex + 1).stock_temperature }}
  78. </span>
  79. <span v-if="shouldShowDivider(rowIndex, colIndex + 1)"> | </span>
  80. <span v-if="getDayData(rowIndex, colIndex + 1).market_temperature">
  81. {{ getDayData(rowIndex, colIndex + 1).market_temperature }}
  82. </span>
  83. </template>
  84. </template>
  85. </p>
  86. </div>
  87. <div v-else-if="shouldShowRest(rowIndex, colIndex + 1)">
  88. <p class="WDRL_date">休市</p>
  89. </div>
  90. </template>
  91. </el-table-column>
  92. </el-table> -->
  93. </div>
  94. </div>
  95. </div>
  96. </template>
  97. <script setup>
  98. import {
  99. ref,
  100. computed,
  101. onMounted,
  102. defineExpose,
  103. defineProps,
  104. onUnmounted,
  105. onBeforeUnmount,
  106. } from "vue";
  107. import * as echarts from "echarts";
  108. import biaoti from "../../assets/img/AiEmotion/标题.png";
  109. import jiaobiao from "../../assets/img/AiEmotion/角标.png";
  110. import suoding from "../../assets/img/AiEmotion/锁定.png";
  111. import moment from "moment";
  112. const props = defineProps({
  113. companyName: {
  114. type: String,
  115. default: "",
  116. },
  117. stockCode: {
  118. type: String,
  119. default: "",
  120. },
  121. });
  122. const color = ref([
  123. "#32A3FF",
  124. "#2BD977",
  125. "#A239FF",
  126. "#FF7945",
  127. "#FF5289",
  128. "#5791CB",
  129. ]);
  130. const months = ref([
  131. "1月",
  132. "2月",
  133. "3月",
  134. "4月",
  135. "5月",
  136. "6月",
  137. "7月",
  138. "8月",
  139. "9月",
  140. "10月",
  141. "11月",
  142. "12月",
  143. ]);
  144. const calendarDate = ref();
  145. const week = ref(["周一", "周二", "周三", "周四", "周五", "周六", "周日"]);
  146. const KlineCanvs = ref();
  147. const WDRL = ref([]);
  148. const klineDataRaw = ref([]); // 用于存储 K 线图数据
  149. let chartInstance = null; // 存储图表实例
  150. const indexCodes = [
  151. "NDX",
  152. "DJIA",
  153. "SPX",
  154. "STI",
  155. "KLSE",
  156. "TSX",
  157. "N225",
  158. "KS11",
  159. "JKSE",
  160. "1A0001",
  161. "HSI",
  162. "I63",
  163. "VNINDE",
  164. ];
  165. const isIndexCode = computed(() => indexCodes.includes(props.stockCode));
  166. const currentData = ref([]);
  167. const fetchCalendarData = () => {
  168. const monthTitle = ref([]);
  169. currentData.value = JSON.parse(JSON.stringify(WDRL.value));
  170. for (let i = 0; i < currentData.value.length; i++) {
  171. const day = moment(currentData.value[i].date).date();
  172. currentData.value[i].day = day;
  173. if (i == 0 || day == 1) {
  174. monthTitle.value.push({
  175. year: moment(currentData.value[i].date).year(),
  176. month: moment(currentData.value[i].date).month() + 1,
  177. });
  178. currentData.value[i].month =
  179. moment(currentData.value[i].date).month() + 1;
  180. }
  181. const tp = currentData.value[i].stock_temperature;
  182. if (tp == "" || tp == null || tp == "休市") {
  183. currentData.value[i].color = color.value[5];
  184. } else {
  185. if (tp <= "20") {
  186. currentData.value[i].color = color.value[0];
  187. } else if (parseInt(tp) <= "50") {
  188. currentData.value[i].color = color.value[1];
  189. } else if (parseInt(tp) <= "70") {
  190. currentData.value[i].color = color.value[2];
  191. } else if (parseInt(tp) <= "90") {
  192. currentData.value[i].color = color.value[3];
  193. } else {
  194. currentData.value[i].color = color.value[4];
  195. }
  196. }
  197. }
  198. const stDate = monthTitle.value[0];
  199. const edDate = monthTitle.value[monthTitle.value.length - 1];
  200. calendarDate.value = `${stDate.year}${stDate.month}月 ~`;
  201. if (stDate.year != edDate.year) {
  202. calendarDate.value += `${edDate.year}${edDate.month}`;
  203. } else {
  204. calendarDate.value += `${edDate.month}`;
  205. }
  206. console.log(currentData.value);
  207. };
  208. // 分组 WDRL 数据
  209. // const groupedWDRL = computed(() => {
  210. // const result = [];
  211. // for (let i = 0; i < WDRL.value.length; i += 7) {
  212. // result.push(WDRL.value.slice(i, i + 7));
  213. // }
  214. // return result;
  215. // });
  216. // 获取指定日期的数据
  217. // function getDayData(rowIndex, dayIndex) {
  218. // const weekData = groupedWDRL.value[rowIndex];
  219. // if (weekData && weekData.length >= dayIndex) {
  220. // return weekData[dayIndex - 1] || {};
  221. // }
  222. // return {};
  223. // }
  224. // 判断是否显示分隔符
  225. // function shouldShowDivider(rowIndex, dayIndex) {
  226. // const data = getDayData(rowIndex, dayIndex);
  227. // return data?.market_temperature && data?.stock_temperature;
  228. // }
  229. // 判断是否都休市
  230. // function isBothRest(rowIndex, colIndex) {
  231. // const data = getDayData(rowIndex, colIndex);
  232. // return (
  233. // data &&
  234. // data.stock_temperature === "休市" &&
  235. // data.market_temperature === "休市"
  236. // );
  237. // }
  238. // 判断是否显示休市信息
  239. // function shouldShowRest(rowIndex, dayIndex) {
  240. // const data = getDayData(rowIndex, dayIndex);
  241. // if (data && (data.stock_temperature || data.market_temperature)) return false;
  242. // const flatIndex = rowIndex * 7 + (dayIndex - 1);
  243. // const targetDay = WDRL.value[flatIndex];
  244. // if (!targetDay || !targetDay.date) return false;
  245. // const [year, month, day] = targetDay.date.split("/").map(Number);
  246. // if (!year || !month || !day) return false;
  247. // const dateObj = new Date(year, month - 1, day);
  248. // const today = new Date();
  249. // if (
  250. // dateObj.getMonth() !== today.getMonth() ||
  251. // dateObj.getFullYear() !== today.getFullYear()
  252. // )
  253. // return false;
  254. // const weekday = dateObj.getDay();
  255. // return weekday >= 1 && weekday <= 5;
  256. // }
  257. // 格式化月份
  258. // function formatMonth(dateStr) {
  259. // if (!dateStr) return "";
  260. // const month = dateStr.split("/")[1];
  261. // const map = {
  262. // "01": "一月",
  263. // "02": "二月",
  264. // "03": "三月",
  265. // "04": "四月",
  266. // "05": "五月",
  267. // "06": "六月",
  268. // "07": "七月",
  269. // "08": "八月",
  270. // "09": "九月",
  271. // 10: "十月",
  272. // 11: "十一月",
  273. // 12: "十二月",
  274. // };
  275. // return map[month] || "";
  276. // }
  277. // 格式化日期
  278. // function formatDate(dateStr) {
  279. // if (!dateStr) return "";
  280. // return dateStr.split("/")[2];
  281. // }
  282. // 设置表格单元格样式
  283. // function tableCellStyle({ row, column, rowIndex, columnIndex }) {
  284. // const data = getDayData(rowIndex, columnIndex + 1);
  285. // let value = isIndexCode.value
  286. // ? Number(data?.market_temperature)
  287. // : Number(data?.stock_temperature);
  288. // if (isNaN(value)) return { backgroundColor: "#4b759f", color: "white" };
  289. // if (value >= 90) return { backgroundColor: "#BD0000", color: "white" };
  290. // else if (value >= 70) return { backgroundColor: "#FF5638", color: "white" };
  291. // else if (value >= 50) return { backgroundColor: "#C929E6", color: "white" };
  292. // else if (value >= 20) return { backgroundColor: "#00AB00", color: "white" };
  293. // else if (value > 0) return { backgroundColor: "#87CEEB", color: "white" };
  294. // else return { backgroundColor: "#4b759f", color: "white" };
  295. // }
  296. // function tableRowStyle() {
  297. // // 动态调整行高
  298. // const containerWidth = document.querySelector(".border4")?.offsetWidth || 0;
  299. // const rowHeight = containerWidth * 0.1; // 根据容器宽度的比例调整行高
  300. // return { height: `${rowHeight}px` };
  301. // }
  302. // // 动态计算单元格宽度
  303. // const containerWidth = document.querySelector(".border4")?.offsetWidth || 0;
  304. // const cellWidth = containerWidth / 7;
  305. // 初始化图表
  306. function initChart(raw, klineDataRawValue, WDRLValue) {
  307. if (!raw || !klineDataRawValue || !WDRLValue) {
  308. console.error(
  309. "initChart: raw, klineDataRawValue or WDRLValue is undefined"
  310. );
  311. return;
  312. }
  313. console.log(props)
  314. // 如果已存在图表实例,先销毁
  315. if (chartInstance) {
  316. chartInstance.dispose();
  317. chartInstance = null;
  318. }
  319. // 处理 K 线图数据
  320. const klineData = klineDataRawValue.map((item) => {
  321. const open = item[1];
  322. const close = item[2];
  323. const low = item[3];
  324. const high = item[4];
  325. return [open, close, low, high];
  326. });
  327. // 计算K线数据的最小值和最大值
  328. let minPrice = Infinity;
  329. let maxPrice = -Infinity;
  330. klineDataRawValue.forEach((item) => {
  331. const low = item[3];
  332. const high = item[4];
  333. minPrice = Math.min(minPrice, low);
  334. maxPrice = Math.max(maxPrice, high);
  335. });
  336. // 计算小于最小值的整数作为y轴最小值
  337. const yAxisMin = Math.floor(minPrice);
  338. // 计算大于最大值的整数作为y轴最大值
  339. const yAxisMax = Math.ceil(maxPrice);
  340. // 温度日历
  341. WDRL.value = WDRLValue;
  342. klineDataRaw.value = klineDataRawValue;
  343. fetchCalendarData();
  344. const dateLabels = raw.map((item) => item[0]);
  345. const marketData = raw.map((item) => Math.round(item[1]));
  346. const stockData = raw.map((item) => Math.round(item[2]));
  347. // 处理数据重合时只显示市场温度的逻辑
  348. // 检查是否所有数据都重合
  349. const allDataMatch = stockData.every((stockTemp, index) => {
  350. const marketTemp = marketData[index];
  351. return stockTemp === marketTemp;
  352. });
  353. // 只有当全部数据都重合时,才只显示市场温度(股票温度设为null)
  354. const processedStockData = allDataMatch ? stockData.map(() => null) : stockData;
  355. // 创建新的图表实例
  356. chartInstance = echarts.init(KlineCanvs.value);
  357. chartInstance.setOption({
  358. tooltip: {
  359. trigger: "axis",
  360. confine: true, // 限制tooltip在图表区域内
  361. position: function (point, params, dom, rect, size) {
  362. // 获取图表容器大小
  363. const chartWidth = size.viewSize[0];
  364. const chartHeight = size.viewSize[1];
  365. const tooltipWidth = size.contentSize[0];
  366. const tooltipHeight = size.contentSize[1];
  367. // 检测是否为移动设备
  368. const isMobile = window.innerWidth <= 768;
  369. if (isMobile) {
  370. // 移动端:固定在顶部中央
  371. return {
  372. top: 10,
  373. left: Math.max(10, (chartWidth - tooltipWidth) / 2)
  374. };
  375. } else {
  376. // 桌面端:智能定位
  377. let x = point[0];
  378. let y = point[1];
  379. // 防止tooltip超出右边界
  380. if (x + tooltipWidth > chartWidth) {
  381. x = chartWidth - tooltipWidth - 10;
  382. }
  383. // 防止tooltip超出下边界
  384. if (y + tooltipHeight > chartHeight) {
  385. y = chartHeight - tooltipHeight - 10;
  386. }
  387. return [Math.max(10, x), Math.max(10, y)];
  388. }
  389. },
  390. axisPointer: {
  391. type: "cross",
  392. crossStyle: {
  393. color: "#999",
  394. width: 1,
  395. type: "dashed",
  396. },
  397. lineStyle: {
  398. color: "#999",
  399. width: 1,
  400. type: "dashed",
  401. },
  402. },
  403. backgroundColor: '#646E71',
  404. borderColor: '#fff',
  405. borderWidth: 1,
  406. padding: [8, 12],
  407. textStyle: {
  408. color: '#fff',
  409. fontSize: window.innerWidth <= 768 ? 10 : 12 // 移动端使用更小字体
  410. },
  411. extraCssText: window.innerWidth <= 768 ?
  412. 'max-width: 280px; word-wrap: break-word; white-space: normal; box-shadow: 0 2px 8px rgba(0,0,0,0.3);' :
  413. 'max-width: 350px; word-wrap: break-word; white-space: normal; box-shadow: 0 2px 8px rgba(0,0,0,0.3);',
  414. formatter: function (params) {
  415. if (params && params.length > 0) {
  416. const isMobile = window.innerWidth <= 768;
  417. const fontSize = isMobile ? '10px' : '12px';
  418. const lineHeight = isMobile ? '1.3' : '1.5';
  419. const marginBottom = isMobile ? '4px' : '6px';
  420. let result = `<div style="font-weight: bold; color: #fff; margin-bottom: ${marginBottom}; font-size: ${fontSize}; line-height: ${lineHeight};">日期: ${params[0].name}</div>`;
  421. params.forEach((param) => {
  422. if (param.seriesType === "candlestick") {
  423. const open = param.data[1];
  424. const close = param.data[2];
  425. const low = param.data[3];
  426. const high = param.data[4];
  427. if (isMobile) {
  428. // 移动端简化显示
  429. result += `<div style="margin-bottom: ${marginBottom}; font-size: ${fontSize}; line-height: ${lineHeight};">`;
  430. result += `<div style="color: #fff; font-weight: bold; margin-bottom: 2px;">${param.seriesName}</div>`;
  431. result += `<div style="color: #fff; display: flex; justify-content: space-between;"><span>开盘价:</span><span>${open}</span></div>`;
  432. result += `<div style="color: #fff; display: flex; justify-content: space-between;"><span>收盘价:</span><span>${close}</span></div>`;
  433. result += `<div style="color: #fff; display: flex; justify-content: space-between;"><span>最低价:</span><span>${low}</span></div>`;
  434. result += `<div style="color: #fff; display: flex; justify-content: space-between;"><span>最高价:</span><span>${high}</span></div>`;
  435. result += `</div>`;
  436. } else {
  437. // 桌面端完整显示
  438. result += `<div style="margin-bottom: ${marginBottom}; font-size: ${fontSize}; line-height: ${lineHeight};">`;
  439. result += `<div style="color: #fff; font-weight: bold;">${param.seriesName}</div>`;
  440. result += `<div style="color: #fff;">开盘价: ${open}</div>`;
  441. result += `<div style="color: #fff;">收盘价: ${close}</div>`;
  442. result += `<div style="color: #fff;">最低价: ${low}</div>`;
  443. result += `<div style="color: #fff;">最高价: ${high}</div>`;
  444. result += `</div>`;
  445. }
  446. } else if (param.seriesType === "line") {
  447. result += `<div style="color: #fff; margin-bottom: 2px; font-size: ${fontSize}; line-height: ${lineHeight};">${param.seriesName}: ${param.value}</div>`;
  448. }
  449. });
  450. return result;
  451. }
  452. return "";
  453. },
  454. },
  455. legend: {
  456. data: ["K线", "股票温度", "市场温度"],
  457. textStyle: { color: "white", fontSize: window.innerWidth <= 768 ? 14 : 18 },
  458. },
  459. grid: {
  460. left: window.innerWidth <= 768 ? '50px' : '100px', // 移动端使用较小边距,桌面端为四位数预留足够空间
  461. right: window.innerWidth <= 768 ? '40px' : '100px', // 为右侧y轴预留空间
  462. top: window.innerWidth <= 768 ? '40px' : '60px',
  463. bottom: window.innerWidth <= 768 ? '60px' : '80px', // 为dataZoom预留空间
  464. containLabel: false, // 不自动包含标签,使用固定边距
  465. height: window.innerWidth <= 768 ? '200px' : '300px',
  466. width: window.innerWidth <= 768 ? '70%' : '80%',
  467. },
  468. xAxis: {
  469. type: "category",
  470. data: dateLabels,
  471. axisLine: { lineStyle: { color: "#00BFFF" } },
  472. axisLabel: {
  473. color: "#FFFFFF",
  474. fontSize: 12,
  475. fontWeight: "bold",
  476. },
  477. axisTick: {
  478. show: true,
  479. alignWithLabel: true, // 刻度线与标签对齐
  480. lineStyle: {
  481. color: "white", // 与十字线颜色保持一致
  482. width: 1,
  483. type: "dashed" // 与十字线样式保持一致
  484. }
  485. },
  486. axisPointer: {
  487. show: true,
  488. type: "line",
  489. lineStyle: {
  490. color: "#999",
  491. width: 1,
  492. type: "dashed",
  493. },
  494. label: {
  495. show: true,
  496. color: "black",
  497. },
  498. },
  499. splitLine: {
  500. show: false, // 显示网格线
  501. lineStyle: {
  502. color: "#999",
  503. width: 1,
  504. type: "dashed",
  505. opacity: 0.3 // 设置透明度使网格线不太突出
  506. }
  507. },
  508. },
  509. yAxis: [
  510. {
  511. min: yAxisMin,
  512. max: yAxisMax,
  513. axisLine: { lineStyle: { color: "#00FF7F" } },
  514. axisLabel: {
  515. color: "#FFFFFF",
  516. fontSize: window.innerWidth <= 768 ? 10 : 12, // 移动端使用更小字体
  517. fontWeight: "bold",
  518. formatter: function (value) {
  519. // 确保四位数完整显示
  520. return value.toString();
  521. },
  522. margin: 8 // 增加标签与轴线的距离
  523. },
  524. axisTick: { lineStyle: { color: "#00FF7F" } },
  525. splitLine: {
  526. show: false,
  527. lineStyle: {
  528. color: "#333333",
  529. type: "solid",
  530. opacity: 0.3,
  531. },
  532. },
  533. axisPointer: {
  534. show: true,
  535. type: "line",
  536. label: {
  537. show: true,
  538. color: "black",
  539. },
  540. lineStyle: {
  541. color: "#999",
  542. width: 1,
  543. type: "dashed",
  544. },
  545. },
  546. },
  547. {
  548. min: 0,
  549. max: 100,
  550. position: "right",
  551. axisLabel: {
  552. color: "#FFFF00",
  553. fontSize: window.innerWidth <= 768 ? 10 : 12, // 移动端使用更小字体
  554. fontWeight: "bold",
  555. margin: 8 // 增加标签与轴线的距离
  556. },
  557. axisLine: { lineStyle: { color: "#FF1493", width: 2 } },
  558. axisTick: { lineStyle: { color: "#FF1493" } },
  559. splitLine: {
  560. show: false,
  561. lineStyle: {
  562. color: "#444444",
  563. type: "solid",
  564. opacity: 0.3,
  565. },
  566. },
  567. axisPointer: {
  568. show: true,
  569. type: "line",
  570. lineStyle: {
  571. color: "#999",
  572. width: 1,
  573. type: "dashed",
  574. },
  575. label: {
  576. show: true,
  577. color: "black",
  578. },
  579. },
  580. },
  581. ],
  582. color: ["#f00", "white"],
  583. series: [
  584. {
  585. name: "K线",
  586. type: "candlestick",
  587. data: klineData,
  588. z: 1, // K线在最底层
  589. itemStyle: {
  590. normal: {
  591. color: "#00FF00", // 阳线红色
  592. color0: "#FF0000", // 阴线绿色
  593. borderColor: "#00FF00", // 阳线边框红色
  594. borderColor0: "#FF0000", // 阴线边框绿色
  595. },
  596. },
  597. },
  598. {
  599. name: "股票温度",
  600. type: "line",
  601. yAxisIndex: 1,
  602. data: processedStockData,
  603. z: 2, // 股票温度在中间层
  604. connectNulls: false, // 不连接null值
  605. lineStyle: {
  606. width: 2
  607. },
  608. symbol: 'circle',
  609. symbolSize: 4
  610. },
  611. {
  612. name: "市场温度",
  613. type: "line",
  614. yAxisIndex: 1,
  615. data: marketData,
  616. z: 3, // 市场温度在最上层
  617. lineStyle: {
  618. width: 2
  619. },
  620. symbol: 'circle',
  621. symbolSize: 4
  622. },
  623. ],
  624. // 添加 dataZoom 组件
  625. dataZoom: [
  626. {
  627. type: "slider",
  628. xAxisIndex: 0,
  629. filterMode: "filter",
  630. textStyle: {
  631. color: "white",
  632. },
  633. start: 50, // 默认展示后半部分数据
  634. end: 100,
  635. bottom: "0%", // 下移数据缩放滑块
  636. },
  637. {
  638. type: "inside",
  639. xAxisIndex: 0,
  640. filterMode: "filter",
  641. },
  642. ],
  643. });
  644. // 防抖函数,避免频繁触发resize
  645. const debounce = (func, wait) => {
  646. let timeout;
  647. return function executedFunction(...args) {
  648. const later = () => {
  649. clearTimeout(timeout);
  650. func(...args);
  651. };
  652. clearTimeout(timeout);
  653. timeout = setTimeout(later, wait);
  654. };
  655. };
  656. // 监听窗口大小变化
  657. const resizeHandler = debounce(() => {
  658. if (chartInstance && !chartInstance.isDisposed()) {
  659. try {
  660. chartInstance.resize();
  661. adjustCellFontSize(); // 同时调整表格字体大小
  662. console.log("股市温度计图表已重新调整大小");
  663. } catch (error) {
  664. console.error("股市温度计图表resize失败:", error);
  665. }
  666. }
  667. }, 100); // 100ms防抖延迟
  668. // 移除之前的监听器(如果存在)
  669. if (window.marketTempResizeHandler) {
  670. window.removeEventListener("resize", window.marketTempResizeHandler);
  671. }
  672. // 添加新的监听器
  673. window.addEventListener("resize", resizeHandler);
  674. // 存储resize处理器以便后续清理
  675. window.marketTempResizeHandler = resizeHandler;
  676. // 添加容器大小监听器
  677. const chartContainer = document.querySelector(".KlineClass");
  678. if (chartContainer && window.ResizeObserver) {
  679. const containerObserver = new ResizeObserver(
  680. debounce(() => {
  681. if (chartInstance && !chartInstance.isDisposed()) {
  682. try {
  683. chartInstance.resize();
  684. console.log("股市温度计容器大小变化,图表已调整");
  685. } catch (error) {
  686. console.error("股市温度计容器resize失败:", error);
  687. }
  688. }
  689. }, 100)
  690. );
  691. containerObserver.observe(chartContainer);
  692. window.marketTempContainerObserver = containerObserver;
  693. }
  694. // 初始调整字体大小
  695. adjustCellFontSize();
  696. }
  697. // 调整单元格字体大小
  698. function adjustCellFontSize() {
  699. const table = document.querySelector(".border4 .el-table");
  700. if (table) {
  701. const tableWidth = table.offsetWidth;
  702. const cellWidth = tableWidth / 7; // 假设一周 7 天
  703. const fontSize = Math.min(cellWidth * 0.15, 20); // 根据单元格宽度动态计算字体大小
  704. const dateElements = document.querySelectorAll(".WDRL_date");
  705. const dataElements = document.querySelectorAll(".WDRL_data");
  706. dateElements.forEach((el) => {
  707. el.style.fontSize = `${fontSize}px`;
  708. });
  709. dataElements.forEach((el) => {
  710. el.style.fontSize = `${fontSize * 0.8}px`;
  711. });
  712. }
  713. }
  714. // 组件卸载时清理资源
  715. onBeforeUnmount(() => {
  716. // 销毁图表实例
  717. if (chartInstance) {
  718. chartInstance.dispose();
  719. chartInstance = null;
  720. }
  721. // 移除窗口resize监听器
  722. if (window.marketTempResizeHandler) {
  723. window.removeEventListener("resize", window.marketTempResizeHandler);
  724. window.marketTempResizeHandler = null;
  725. }
  726. // 清理容器观察器
  727. if (window.marketTempContainerObserver) {
  728. window.marketTempContainerObserver.disconnect();
  729. window.marketTempContainerObserver = null;
  730. }
  731. });
  732. defineExpose({ initChart });
  733. </script>
  734. <style scoped>
  735. .WDRL_date {
  736. margin-top: 2px;
  737. text-align: center;
  738. font-size: 1.6vw;
  739. font-weight: bold;
  740. padding-top: 0%;
  741. position: relative;
  742. }
  743. .month-display {
  744. position: absolute;
  745. top: 0;
  746. right: 0;
  747. font-size: 1vw;
  748. color: rgb(58, 58, 58);
  749. }
  750. .WDRL_data {
  751. margin-top: 5px;
  752. text-align: center;
  753. font-size: 1vw;
  754. font-weight: bold;
  755. }
  756. .table_header {
  757. color: white;
  758. background: #2a2a2a;
  759. }
  760. .KlineClass {
  761. width: 100%;
  762. height: 420px;
  763. }
  764. .market-temperature {
  765. /* min-height: 100vh; */
  766. /* background-color: rgb(0, 22, 65); */
  767. }
  768. .container {
  769. margin: 0 auto;
  770. /* padding: 20px; */
  771. max-width: 80vw;
  772. padding-bottom: 10%;
  773. display: flex;
  774. flex-direction: column;
  775. justify-content: center;
  776. align-items: center;
  777. }
  778. .border3 {
  779. /* margin-top: 40px; */
  780. border-radius: 8px;
  781. padding: 20px;
  782. /* margin-left: 0; */
  783. width: 100%;
  784. height: auto;
  785. box-sizing: border-box;
  786. overflow: hidden;
  787. }
  788. .border4 {
  789. /* margin-top: 40px; */
  790. border-radius: 8px;
  791. padding: 20px;
  792. width: 80%;
  793. /* margin-left: 8%; */
  794. height: 35vw;
  795. overflow: visible;
  796. }
  797. .border4 .el-table {
  798. height: auto !important;
  799. max-height: none !important;
  800. }
  801. .border4 .el-table__body-wrapper {
  802. height: auto !important;
  803. max-height: none !important;
  804. overflow: visible !important;
  805. }
  806. .border4 .el-table__body {
  807. height: auto !important;
  808. }
  809. /* 手机端适配样式 */
  810. @media only screen and (max-width: 768px) {
  811. .container {
  812. padding-bottom: 16%
  813. }
  814. .KlineClass {
  815. width: 100%;
  816. height: 300px;
  817. }
  818. .border4 {
  819. margin-top: 0px;
  820. border-radius: 8px;
  821. padding: 0px;
  822. width: 80%;
  823. margin-left: 0%;
  824. height: 80vw;
  825. overflow: visible;
  826. }
  827. .border4 .el-table {
  828. height: auto !important;
  829. max-height: none !important;
  830. }
  831. .border4 .el-table__body-wrapper {
  832. height: auto !important;
  833. max-height: none !important;
  834. overflow: visible !important;
  835. }
  836. .border4 .el-table__body {
  837. height: auto !important;
  838. }
  839. .el-table .hidden-columns {
  840. position: absolute;
  841. visibility: hidden;
  842. z-index: -1;
  843. }
  844. .border3 {
  845. margin-top: 25px;
  846. border-radius: 8px;
  847. padding: 10px 0px;
  848. /* margin-left: -13px; */
  849. width: 100%;
  850. height: 100%;
  851. }
  852. .WDRL_date {
  853. font-size: 4.2vw;
  854. }
  855. .month-display {
  856. font-size: 1.8vw;
  857. }
  858. .WDRL_data {
  859. font-size: 3vw;
  860. }
  861. .el-table .cell {
  862. box-sizing: border-box;
  863. line-height: 23px;
  864. overflow: hidden;
  865. overflow-wrap: break-word;
  866. padding: 0 12px;
  867. text-overflow: ellipsis;
  868. white-space: normal;
  869. text-align: center;
  870. }
  871. .month {
  872. width: 80%;
  873. height: 70% !important;
  874. top: -4vw !important;
  875. right: -0.5vw;
  876. }
  877. .monthContent {
  878. margin-top: 1.8vw !important;
  879. font-weight: bold;
  880. font-size: 1.2vw;
  881. }
  882. .calendarItemTitle {
  883. font-size: 2.2vw !important;
  884. }
  885. .calendarItemContent {
  886. font-size: 2.2vw !important;
  887. }
  888. /* 移动端tooltip优化 */
  889. :deep(.echarts-tooltip) {
  890. max-width: 280px !important;
  891. font-size: 10px !important;
  892. line-height: 1.3 !important;
  893. padding: 8px 10px !important;
  894. word-wrap: break-word !important;
  895. white-space: normal !important;
  896. box-sizing: border-box !important;
  897. }
  898. /* 确保tooltip不会超出屏幕 */
  899. :deep(.echarts-tooltip-content) {
  900. max-width: 100% !important;
  901. overflow: hidden !important;
  902. }
  903. }
  904. .border1 {
  905. border: 2px solid #14bddb;
  906. background-color: #1f669e;
  907. width: 100%;
  908. height: 100%;
  909. border-radius: 1%;
  910. position: relative;
  911. display: flex;
  912. flex-direction: column;
  913. align-items: center;
  914. padding-bottom: 20px;
  915. /* margin: 0px; */
  916. }
  917. .border1::before {
  918. content: "";
  919. position: absolute;
  920. top: -2.5px;
  921. left: -2.5px;
  922. width: 20px;
  923. height: 20px;
  924. border-top: 3px solid #00ffff;
  925. /* 左上角颜色 */
  926. border-left: 3px solid #00ffff;
  927. border-top-left-radius: 8px;
  928. }
  929. .border1::after {
  930. content: "";
  931. position: absolute;
  932. bottom: -2px;
  933. right: -2px;
  934. width: 20px;
  935. height: 20px;
  936. border-bottom: 3px solid #00ffff;
  937. /* 右下角颜色 */
  938. border-right: 3px solid #00ffff;
  939. border-bottom-right-radius: 8px;
  940. }
  941. @font-face {
  942. font-family: "方正新综艺简体";
  943. src: url("../../assets/fonts/方正新综艺简体.ttf") format("truetype");
  944. font-weight: normal;
  945. font-style: normal;
  946. font-display: swap;
  947. }
  948. .title {
  949. width: 100%;
  950. margin-bottom: 0px;
  951. position: relative;
  952. }
  953. .titleImg {
  954. width: 100%;
  955. margin-top: 10px;
  956. }
  957. .titleContent {
  958. font-family: "方正新综艺简体";
  959. font-size: calc(10px + 1.5vw);
  960. color: #00ffff;
  961. position: absolute;
  962. bottom: 42%;
  963. left: 30px;
  964. }
  965. .secondTitle {
  966. width: 100%;
  967. height: 35px;
  968. display: flex;
  969. justify-content: center;
  970. }
  971. .secondTitleItems {
  972. width: 13.5%;
  973. height: 100%;
  974. display: flex;
  975. }
  976. .secondTitleItem {
  977. border: 1px solid #03a7ce;
  978. background-color: #0b3c73;
  979. width: 100%;
  980. color: #ffffff;
  981. display: flex;
  982. justify-content: center;
  983. align-items: center;
  984. @media only screen and (max-width: 768px) {
  985. font-size: 12px;
  986. }
  987. }
  988. .calendar {
  989. margin-top: 30px;
  990. width: 100%;
  991. height: 70%;
  992. display: flex;
  993. justify-content: center;
  994. flex-direction: column;
  995. }
  996. .calendarCol {
  997. width: 100%;
  998. height: 100%;
  999. display: flex;
  1000. justify-content: center;
  1001. }
  1002. .calendarRow {
  1003. width: 13.5%;
  1004. height: 100%;
  1005. }
  1006. .calendarItem {
  1007. border: 1px solid #0060af;
  1008. width: 100%;
  1009. height: 100%;
  1010. display: flex;
  1011. flex-direction: column;
  1012. justify-content: center;
  1013. border-radius: 10px;
  1014. color: #ffffff;
  1015. position: relative;
  1016. /* font-size: 1.5vw; */
  1017. }
  1018. .month {
  1019. position: absolute;
  1020. background-image: url("../../assets/img/AiEmotion/角标.png");
  1021. background-size: 100% 100%;
  1022. background-repeat: no-repeat;
  1023. width: 80%;
  1024. height: 80%;
  1025. display: flex;
  1026. justify-content: center;
  1027. /* align-items: center; */
  1028. top: -2vw;
  1029. right: -0.5vw;
  1030. z-index: 1;
  1031. }
  1032. .monthContent {
  1033. margin-top: 0.5vw;
  1034. font-weight: bold;
  1035. font-size: 1.2vw;
  1036. }
  1037. .calendarItemTitle {
  1038. width: 100%;
  1039. text-align: center;
  1040. font-weight: bold;
  1041. font-size: 1.2vw;
  1042. }
  1043. .calendarItemContent {
  1044. width: 100%;
  1045. /* text-align: center; */
  1046. display: flex;
  1047. justify-content: center;
  1048. align-items: center;
  1049. font-weight: bold;
  1050. font-size: 1.2vw;
  1051. }
  1052. .ciImg {
  1053. width: 20%;
  1054. }
  1055. </style>