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.

1201 lines
32 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
4 months ago
3 months ago
4 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
3 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
3 months ago
3 months ago
4 months ago
3 months ago
4 months ago
4 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 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. },
  395. lineStyle: {
  396. type: 'dashed' // 设置为虚线
  397. },
  398. link: [
  399. {
  400. xAxisIndex: [0, 1, 2]
  401. }
  402. ]
  403. },
  404. backgroundColor: 'rgba(232, 232, 242, 0.87)',
  405. borderColor: '#fff',
  406. borderWidth: 1,
  407. borderRadius: 8,
  408. padding: 10,
  409. textStyle: {
  410. color: '#555555',
  411. fontSize: 12
  412. },
  413. extraCssText: window.innerWidth <= 768 ?
  414. 'max-width: 280px; word-wrap: break-word; white-space: normal; box-shadow: 0 2px 8px rgba(0,0,0,0.3);' :
  415. 'max-width: 350px; word-wrap: break-word; white-space: normal; box-shadow: 0 2px 8px rgba(0,0,0,0.3);',
  416. formatter: function (params) {
  417. if (params && params.length > 0) {
  418. const isMobile = window.innerWidth <= 768;
  419. const fontSize = isMobile ? '10px' : '12px';
  420. const lineHeight = isMobile ? '1.3' : '1.5';
  421. const marginBottom = isMobile ? '4px' : '6px';
  422. let result = `<div style="font-weight: bold; color: black; margin-bottom: ${marginBottom}; font-size: ${fontSize}; line-height: ${lineHeight};">日期: ${params[0].name}</div>`;
  423. params.forEach((param) => {
  424. if (param.seriesType === "candlestick") {
  425. const open = param.data[1];
  426. const close = param.data[2];
  427. const low = param.data[3];
  428. const high = param.data[4];
  429. if (isMobile) {
  430. // 移动端简化显示
  431. result += `<div style="margin-bottom: ${marginBottom}; font-size: ${fontSize}; line-height: ${lineHeight};">`;
  432. result += `<div style="color: black; font-weight: bold; margin-bottom: 2px;">${param.seriesName}</div>`;
  433. result += `<div style="color: black; display: flex; justify-content: space-between;"><span>开盘价:</span><span>${open}</span></div>`;
  434. result += `<div style="color: black; display: flex; justify-content: space-between;"><span>收盘价:</span><span>${close}</span></div>`;
  435. result += `<div style="color: black; display: flex; justify-content: space-between;"><span>最低价:</span><span>${low}</span></div>`;
  436. result += `<div style="color: black; display: flex; justify-content: space-between;"><span>最高价:</span><span>${high}</span></div>`;
  437. result += `</div>`;
  438. } else {
  439. // 桌面端完整显示
  440. result += `<div style="margin-bottom: ${marginBottom}; font-size: ${fontSize}; line-height: ${lineHeight};">`;
  441. result += `<div style="color: black; font-weight: bold;">${param.seriesName}</div>`;
  442. result += `<div style="color: black;">开盘价: ${open}</div>`;
  443. result += `<div style="color: black;">收盘价: ${close}</div>`;
  444. result += `<div style="color: black;">最低价: ${low}</div>`;
  445. result += `<div style="color: black;">最高价: ${high}</div>`;
  446. result += `</div>`;
  447. }
  448. } else if (param.seriesType === "line") {
  449. // 根据系列名称设置不同颜色
  450. let textColor = 'black';
  451. if (param.seriesName === '市场温度') {
  452. textColor = 'red';
  453. } else if (param.seriesName === '股票温度') {
  454. textColor = 'blue';
  455. }
  456. result += `<div style="color: ${textColor}; margin-bottom: 2px; font-size: ${fontSize}; line-height: ${lineHeight};">${param.seriesName}: ${param.value}</div>`;
  457. }
  458. });
  459. return result;
  460. }
  461. return "";
  462. },
  463. },
  464. legend: {
  465. data: ["K线", "股票温度", "市场温度"],
  466. textStyle: { color: "white", fontSize: window.innerWidth <= 768 ? 14 : 18 },
  467. },
  468. grid: {
  469. left: window.innerWidth <= 768 ? '15%' : '100px', // 移动端使用较小边距,桌面端为四位数预留足够空间
  470. right: window.innerWidth <= 768 ? '40px' : '100px', // 为右侧y轴预留空间
  471. top: window.innerWidth <= 768 ? '40px' : '60px',
  472. bottom: window.innerWidth <= 768 ? '60px' : '80px', // 为dataZoom预留空间
  473. containLabel: false, // 不自动包含标签,使用固定边距
  474. height: window.innerWidth <= 768 ? '200px' : '300px',
  475. width: window.innerWidth <= 768 ? '70%' : '80%',
  476. },
  477. xAxis: {
  478. type: "category",
  479. data: dateLabels,
  480. axisLine: { lineStyle: { color: "#00BFFF" } },
  481. axisLabel: {
  482. color: "#FFFFFF",
  483. fontSize: 12,
  484. fontWeight: "bold",
  485. },
  486. axisTick: {
  487. show: true,
  488. alignWithLabel: true, // 刻度线与标签对齐
  489. lineStyle: {
  490. color: "white", // 与十字线颜色保持一致
  491. width: 1,
  492. type: "dashed" // 与十字线样式保持一致
  493. }
  494. },
  495. axisPointer: {
  496. show: true,
  497. type: "line",
  498. lineStyle: {
  499. color: "#999",
  500. width: 1,
  501. type: "dashed",
  502. },
  503. label: {
  504. show: true,
  505. color: "black",
  506. },
  507. },
  508. splitLine: {
  509. show: false, // 显示网格线
  510. lineStyle: {
  511. color: "#999",
  512. width: 1,
  513. type: "dashed",
  514. opacity: 0.3 // 设置透明度使网格线不太突出
  515. }
  516. },
  517. },
  518. yAxis: [
  519. {
  520. min: yAxisMin,
  521. max: yAxisMax,
  522. axisLine: { lineStyle: { color: "#00FF7F" } },
  523. axisLabel: {
  524. color: "#FFFFFF",
  525. fontSize: window.innerWidth <= 768 ? 10 : 12, // 移动端使用更小字体
  526. fontWeight: "bold",
  527. formatter: function (value) {
  528. // 确保四位数完整显示
  529. return value.toString();
  530. },
  531. margin: 8 // 增加标签与轴线的距离
  532. },
  533. axisTick: { lineStyle: { color: "#00FF7F" } },
  534. splitLine: {
  535. show: false,
  536. lineStyle: {
  537. color: "#333333",
  538. type: "solid",
  539. opacity: 0.3,
  540. },
  541. },
  542. axisPointer: {
  543. show: true,
  544. type: "line",
  545. label: {
  546. show: true,
  547. color: "black",
  548. },
  549. lineStyle: {
  550. color: "#999",
  551. width: 1,
  552. type: "dashed",
  553. },
  554. },
  555. },
  556. {
  557. min: 0,
  558. max: 100,
  559. position: "right",
  560. axisLabel: {
  561. color: "#FFFF00",
  562. fontSize: window.innerWidth <= 768 ? 10 : 12, // 移动端使用更小字体
  563. fontWeight: "bold",
  564. margin: 8 // 增加标签与轴线的距离
  565. },
  566. axisLine: { lineStyle: { color: "#FF1493", width: 2 } },
  567. axisTick: { lineStyle: { color: "#FF1493" } },
  568. splitLine: {
  569. show: false,
  570. lineStyle: {
  571. color: "#444444",
  572. type: "solid",
  573. opacity: 0.3,
  574. },
  575. },
  576. axisPointer: {
  577. show: true,
  578. type: "line",
  579. lineStyle: {
  580. color: "#999",
  581. width: 1,
  582. type: "dashed",
  583. },
  584. label: {
  585. show: true,
  586. color: "black",
  587. },
  588. },
  589. },
  590. ],
  591. color: ["#f00", "white"],
  592. series: [
  593. {
  594. name: "K线",
  595. type: "candlestick",
  596. data: klineData,
  597. z: 1, // K线在最底层
  598. itemStyle: {
  599. normal: {
  600. color: "#00FF00", // 阳线红色
  601. color0: "#FF0000", // 阴线绿色
  602. borderColor: "#00FF00", // 阳线边框红色
  603. borderColor0: "#FF0000", // 阴线边框绿色
  604. },
  605. },
  606. },
  607. {
  608. name: "股票温度",
  609. type: "line",
  610. yAxisIndex: 1,
  611. data: processedStockData,
  612. z: 2, // 股票温度在中间层
  613. connectNulls: false, // 不连接null值
  614. lineStyle: {
  615. width: 2
  616. },
  617. symbol: 'circle',
  618. symbolSize: 4
  619. },
  620. {
  621. name: "市场温度",
  622. type: "line",
  623. yAxisIndex: 1,
  624. data: marketData,
  625. z: 3, // 市场温度在最上层
  626. lineStyle: {
  627. width: 2
  628. },
  629. symbol: 'circle',
  630. symbolSize: 4
  631. },
  632. ],
  633. // 添加 dataZoom 组件
  634. dataZoom: [
  635. {
  636. type: "slider",
  637. xAxisIndex: 0,
  638. filterMode: "filter",
  639. textStyle: {
  640. color: "white",
  641. },
  642. start: 50, // 默认展示后半部分数据
  643. end: 100,
  644. bottom: "0%", // 下移数据缩放滑块
  645. },
  646. {
  647. type: "inside",
  648. xAxisIndex: 0,
  649. filterMode: "filter",
  650. },
  651. ],
  652. });
  653. // 添加图表交互事件监听器
  654. if (chartInstance) {
  655. // 监听数据缩放事件
  656. chartInstance.on('dataZoom', () => {
  657. if (window.handleChartInteractionStart) {
  658. window.handleChartInteractionStart();
  659. }
  660. });
  661. // 监听鼠标滚轮事件(缩放)
  662. chartInstance.on('mousewheel', () => {
  663. if (window.handleChartInteractionStart) {
  664. window.handleChartInteractionStart();
  665. }
  666. });
  667. // 监听鼠标按下事件(拖拽开始)
  668. chartInstance.on('mousedown', () => {
  669. if (window.handleChartInteractionStart) {
  670. window.handleChartInteractionStart();
  671. }
  672. });
  673. // 监听鼠标释放事件(拖拽结束)
  674. chartInstance.on('mouseup', () => {
  675. if (window.handleChartInteractionEnd) {
  676. window.handleChartInteractionEnd();
  677. }
  678. });
  679. // 监听图表点击事件
  680. chartInstance.on('click', () => {
  681. if (window.handleChartInteractionStart) {
  682. window.handleChartInteractionStart();
  683. }
  684. });
  685. }
  686. // 防抖函数,避免频繁触发resize
  687. const debounce = (func, wait) => {
  688. let timeout;
  689. return function executedFunction(...args) {
  690. const later = () => {
  691. clearTimeout(timeout);
  692. func(...args);
  693. };
  694. clearTimeout(timeout);
  695. timeout = setTimeout(later, wait);
  696. };
  697. };
  698. // 监听窗口大小变化
  699. const resizeHandler = debounce(() => {
  700. if (chartInstance && !chartInstance.isDisposed()) {
  701. try {
  702. chartInstance.resize();
  703. adjustCellFontSize(); // 同时调整表格字体大小
  704. console.log("股市温度计图表已重新调整大小");
  705. } catch (error) {
  706. console.error("股市温度计图表resize失败:", error);
  707. }
  708. }
  709. }, 100); // 100ms防抖延迟
  710. // 移除之前的监听器(如果存在)
  711. if (window.marketTempResizeHandler) {
  712. window.removeEventListener("resize", window.marketTempResizeHandler);
  713. }
  714. // 添加新的监听器
  715. window.addEventListener("resize", resizeHandler);
  716. // 存储resize处理器以便后续清理
  717. window.marketTempResizeHandler = resizeHandler;
  718. // 添加容器大小监听器
  719. const chartContainer = document.querySelector(".KlineClass");
  720. if (chartContainer && window.ResizeObserver) {
  721. const containerObserver = new ResizeObserver(
  722. debounce(() => {
  723. if (chartInstance && !chartInstance.isDisposed()) {
  724. try {
  725. chartInstance.resize();
  726. console.log("股市温度计容器大小变化,图表已调整");
  727. } catch (error) {
  728. console.error("股市温度计容器resize失败:", error);
  729. }
  730. }
  731. }, 100)
  732. );
  733. containerObserver.observe(chartContainer);
  734. window.marketTempContainerObserver = containerObserver;
  735. }
  736. // 初始调整字体大小
  737. adjustCellFontSize();
  738. }
  739. // 调整单元格字体大小
  740. function adjustCellFontSize() {
  741. const table = document.querySelector(".border4 .el-table");
  742. if (table) {
  743. const tableWidth = table.offsetWidth;
  744. const cellWidth = tableWidth / 7; // 假设一周 7 天
  745. const fontSize = Math.min(cellWidth * 0.15, 20); // 根据单元格宽度动态计算字体大小
  746. const dateElements = document.querySelectorAll(".WDRL_date");
  747. const dataElements = document.querySelectorAll(".WDRL_data");
  748. dateElements.forEach((el) => {
  749. el.style.fontSize = `${fontSize}px`;
  750. });
  751. dataElements.forEach((el) => {
  752. el.style.fontSize = `${fontSize * 0.8}px`;
  753. });
  754. }
  755. }
  756. // 组件卸载时清理资源
  757. onBeforeUnmount(() => {
  758. // 销毁图表实例
  759. if (chartInstance) {
  760. chartInstance.dispose();
  761. chartInstance = null;
  762. }
  763. // 移除窗口resize监听器
  764. if (window.marketTempResizeHandler) {
  765. window.removeEventListener("resize", window.marketTempResizeHandler);
  766. window.marketTempResizeHandler = null;
  767. }
  768. // 清理容器观察器
  769. if (window.marketTempContainerObserver) {
  770. window.marketTempContainerObserver.disconnect();
  771. window.marketTempContainerObserver = null;
  772. }
  773. });
  774. defineExpose({ initChart });
  775. </script>
  776. <style scoped>
  777. .WDRL_date {
  778. margin-top: 2px;
  779. text-align: center;
  780. font-size: 1.6vw;
  781. font-weight: bold;
  782. padding-top: 0%;
  783. position: relative;
  784. }
  785. .month-display {
  786. position: absolute;
  787. top: 0;
  788. right: 0;
  789. font-size: 1vw;
  790. color: rgb(58, 58, 58);
  791. }
  792. .WDRL_data {
  793. margin-top: 5px;
  794. text-align: center;
  795. font-size: 1vw;
  796. font-weight: bold;
  797. }
  798. .table_header {
  799. color: white;
  800. background: #2a2a2a;
  801. }
  802. .KlineClass {
  803. width: 100%;
  804. height: 420px;
  805. }
  806. .market-temperature {
  807. /* min-height: 100vh; */
  808. /* background-color: rgb(0, 22, 65); */
  809. }
  810. .container {
  811. margin: 0 auto;
  812. /* padding: 20px; */
  813. max-width: 80vw;
  814. padding-bottom: 10%;
  815. display: flex;
  816. flex-direction: column;
  817. justify-content: center;
  818. align-items: center;
  819. }
  820. .border3 {
  821. /* margin-top: 40px; */
  822. border-radius: 8px;
  823. padding: 20px;
  824. /* margin-left: 0; */
  825. width: 100%;
  826. height: auto;
  827. box-sizing: border-box;
  828. overflow: hidden;
  829. }
  830. .border4 {
  831. /* margin-top: 40px; */
  832. border-radius: 8px;
  833. padding: 20px;
  834. width: 80%;
  835. /* margin-left: 8%; */
  836. height: 35vw;
  837. overflow: visible;
  838. }
  839. .border4 .el-table {
  840. height: auto !important;
  841. max-height: none !important;
  842. }
  843. .border4 .el-table__body-wrapper {
  844. height: auto !important;
  845. max-height: none !important;
  846. overflow: visible !important;
  847. }
  848. .border4 .el-table__body {
  849. height: auto !important;
  850. }
  851. /* 手机端适配样式 */
  852. @media only screen and (max-width: 768px) {
  853. .container {
  854. padding-bottom: 16%
  855. }
  856. .KlineClass {
  857. width: 100%;
  858. height: 300px;
  859. }
  860. .border4 {
  861. margin-top: 0px;
  862. border-radius: 8px;
  863. padding: 0px;
  864. width: 80%;
  865. margin-left: 0%;
  866. height: 80vw;
  867. overflow: visible;
  868. }
  869. .border4 .el-table {
  870. height: auto !important;
  871. max-height: none !important;
  872. }
  873. .border4 .el-table__body-wrapper {
  874. height: auto !important;
  875. max-height: none !important;
  876. overflow: visible !important;
  877. }
  878. .border4 .el-table__body {
  879. height: auto !important;
  880. }
  881. .el-table .hidden-columns {
  882. position: absolute;
  883. visibility: hidden;
  884. z-index: -1;
  885. }
  886. .border3 {
  887. margin-top: 25px;
  888. border-radius: 8px;
  889. padding: 10px 0px;
  890. /* margin-left: -13px; */
  891. width: 100%;
  892. height: 100%;
  893. }
  894. .WDRL_date {
  895. font-size: 4.2vw;
  896. }
  897. .month-display {
  898. font-size: 1.8vw;
  899. }
  900. .WDRL_data {
  901. font-size: 3vw;
  902. }
  903. .el-table .cell {
  904. box-sizing: border-box;
  905. line-height: 23px;
  906. overflow: hidden;
  907. overflow-wrap: break-word;
  908. padding: 0 12px;
  909. text-overflow: ellipsis;
  910. white-space: normal;
  911. text-align: center;
  912. }
  913. .month {
  914. width: 80%;
  915. height: 70% !important;
  916. top: -4vw !important;
  917. right: -0.5vw;
  918. }
  919. .monthContent {
  920. margin-top: 1.8vw !important;
  921. font-weight: bold;
  922. font-size: 1.2vw;
  923. }
  924. .calendarItemTitle {
  925. font-size: 2.2vw !important;
  926. }
  927. .calendarItemContent {
  928. font-size: 2.2vw !important;
  929. }
  930. /* 移动端tooltip优化 */
  931. :deep(.echarts-tooltip) {
  932. max-width: 280px !important;
  933. font-size: 10px !important;
  934. line-height: 1.3 !important;
  935. padding: 8px 10px !important;
  936. word-wrap: break-word !important;
  937. white-space: normal !important;
  938. box-sizing: border-box !important;
  939. }
  940. /* 确保tooltip不会超出屏幕 */
  941. :deep(.echarts-tooltip-content) {
  942. max-width: 100% !important;
  943. overflow: hidden !important;
  944. }
  945. }
  946. .border1 {
  947. border: 2px solid #14bddb;
  948. background-color: #1f669e;
  949. width: 100%;
  950. height: 100%;
  951. border-radius: 1%;
  952. position: relative;
  953. display: flex;
  954. flex-direction: column;
  955. align-items: center;
  956. padding-bottom: 20px;
  957. /* margin: 0px; */
  958. }
  959. .border1::before {
  960. content: "";
  961. position: absolute;
  962. top: -2.5px;
  963. left: -2.5px;
  964. width: 20px;
  965. height: 20px;
  966. border-top: 3px solid #00ffff;
  967. /* 左上角颜色 */
  968. border-left: 3px solid #00ffff;
  969. border-top-left-radius: 8px;
  970. }
  971. .border1::after {
  972. content: "";
  973. position: absolute;
  974. bottom: -2px;
  975. right: -2px;
  976. width: 20px;
  977. height: 20px;
  978. border-bottom: 3px solid #00ffff;
  979. /* 右下角颜色 */
  980. border-right: 3px solid #00ffff;
  981. border-bottom-right-radius: 8px;
  982. }
  983. @font-face {
  984. font-family: "方正新综艺简体";
  985. src: url("../../assets/fonts/方正新综艺简体.ttf") format("truetype");
  986. font-weight: normal;
  987. font-style: normal;
  988. font-display: swap;
  989. }
  990. .title {
  991. width: 100%;
  992. margin-bottom: 0px;
  993. position: relative;
  994. }
  995. .titleImg {
  996. width: 100%;
  997. margin-top: 10px;
  998. }
  999. .titleContent {
  1000. font-family: "方正新综艺简体";
  1001. font-size: calc(10px + 1.5vw);
  1002. color: #00ffff;
  1003. position: absolute;
  1004. bottom: 60%;
  1005. left: 10px;
  1006. }
  1007. .secondTitle {
  1008. width: 100%;
  1009. height: 35px;
  1010. display: flex;
  1011. justify-content: center;
  1012. }
  1013. .secondTitleItems {
  1014. width: 13.5%;
  1015. height: 100%;
  1016. display: flex;
  1017. }
  1018. .secondTitleItem {
  1019. border: 1px solid #03a7ce;
  1020. background-color: #0b3c73;
  1021. width: 100%;
  1022. color: #ffffff;
  1023. display: flex;
  1024. justify-content: center;
  1025. align-items: center;
  1026. @media only screen and (max-width: 768px) {
  1027. font-size: 12px;
  1028. }
  1029. }
  1030. .calendar {
  1031. margin-top: 30px;
  1032. width: 100%;
  1033. height: 70%;
  1034. display: flex;
  1035. justify-content: center;
  1036. flex-direction: column;
  1037. }
  1038. .calendarCol {
  1039. width: 100%;
  1040. height: 100%;
  1041. display: flex;
  1042. justify-content: center;
  1043. }
  1044. .calendarRow {
  1045. width: 13.5%;
  1046. height: 100%;
  1047. }
  1048. .calendarItem {
  1049. border: 1px solid #0060af;
  1050. width: 100%;
  1051. height: 100%;
  1052. display: flex;
  1053. flex-direction: column;
  1054. justify-content: center;
  1055. border-radius: 10px;
  1056. color: #ffffff;
  1057. position: relative;
  1058. /* font-size: 1.5vw; */
  1059. }
  1060. .month {
  1061. position: absolute;
  1062. background-image: url("../../assets/img/AiEmotion/角标.png");
  1063. background-size: 100% 100%;
  1064. background-repeat: no-repeat;
  1065. width: 80%;
  1066. height: 80%;
  1067. display: flex;
  1068. justify-content: center;
  1069. /* align-items: center; */
  1070. top: -2vw;
  1071. right: -0.5vw;
  1072. z-index: 1;
  1073. }
  1074. .monthContent {
  1075. margin-top: 0.5vw;
  1076. font-weight: bold;
  1077. font-size: 1.2vw;
  1078. }
  1079. .calendarItemTitle {
  1080. width: 100%;
  1081. text-align: center;
  1082. font-weight: bold;
  1083. font-size: 1.2vw;
  1084. }
  1085. .calendarItemContent {
  1086. width: 100%;
  1087. /* text-align: center; */
  1088. display: flex;
  1089. justify-content: center;
  1090. align-items: center;
  1091. font-weight: bold;
  1092. font-size: 1.2vw;
  1093. }
  1094. .ciImg {
  1095. width: 20%;
  1096. }
  1097. </style>