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.

602 lines
16 KiB

4 months ago
4 months ago
4 months ago
  1. <template>
  2. <!-- <div ref="qxnlzhqEchartsRef" id="qxnlzhqEcharts"></div> -->
  3. <div class="qxjmqbox">
  4. <div ref="KlineCanvs" class="qxjmqEcharts"></div>
  5. </div>
  6. </template>
  7. <script setup>
  8. import { ref, onMounted, onBeforeUnmount, toRef } from "vue";
  9. import * as echarts from "echarts";
  10. import { color } from "echarts/lib/export";
  11. defineExpose({ initQXNLZHEcharts });
  12. const KlineCanvs = ref(null);
  13. let KlineCanvsChart = null;
  14. const heatmapData = ref([]); // 存储热力图数据
  15. // 假数据
  16. const rawData2 = ref([]);
  17. const lineData3 = ref([]);
  18. function initQXNLZHEcharts(kline, qxnlzhqData) {
  19. // qxnlzhqData[9][9] = 1;
  20. // qxnlzhqData[2][9] = 1;
  21. rawData2.value = qxnlzhqData;
  22. // 处理热力图数据
  23. const processedHeatmap = [];
  24. rawData2.value.forEach((item, dateIndex) => {
  25. const [date, d1, d2, d3, d4, d5, d6, d7, d8, d9] = item;
  26. processedHeatmap.push([
  27. dateIndex,
  28. 0,
  29. d4,
  30. d8 == 1 ? "green" : "transparent",
  31. ]); // 数据1
  32. processedHeatmap.push([
  33. dateIndex,
  34. 1,
  35. d3,
  36. d7 == 1 ? "purple" : "transparent",
  37. ]); // 数据2
  38. processedHeatmap.push([dateIndex, 2, d2, d6 == 1 ? "red" : "transparent"]); // 数据3
  39. processedHeatmap.push([
  40. dateIndex,
  41. 3,
  42. d1,
  43. d5 == 1 ? "yellow" : "transparent",
  44. ]); // 数据4
  45. });
  46. heatmapData.value = processedHeatmap;
  47. // 处理折线图数据
  48. lineData3.value = rawData2.value.map((item) => item[9]);
  49. console.log("热力图数据:", heatmapData.value);
  50. console.log("折线图数据:", lineData3.value);
  51. const data = kline;
  52. // 切割数据方法
  53. const spliteDate = (a) => {
  54. const categoryData = [];
  55. let value = [];
  56. for (let i = 0; i < a.length; i++) {
  57. // 使用非破坏性的方法,避免修改原数组
  58. categoryData.push(a[i][0]);
  59. value.push(a[i].slice(1)); // 使用 slice 而不是 splice
  60. }
  61. return { categoryData, value };
  62. };
  63. const dealData = spliteDate(data);
  64. // 给配置项
  65. const KlineOption = {
  66. tooltip: {
  67. trigger: 'axis', // 触发类型 坐标轴触发
  68. confine: true, // 限制tooltip不超出容器
  69. axisPointer: {
  70. type: 'cross', // 十字准星效果
  71. lineStyle: {
  72. color: 'grey',
  73. width: 1,
  74. type: 'dashed'
  75. },
  76. crossStyle: {
  77. color: '#999'
  78. },
  79. label: {
  80. backgroundColor: 'rgba(0, 0, 0, 0.8)',
  81. color: '#fff',
  82. borderColor: '#fff',
  83. borderWidth: 1
  84. }
  85. },
  86. backgroundColor: 'rgba(232, 232, 242, 0.87)',
  87. borderColor: '#fff',
  88. borderWidth: 1,
  89. borderRadius: 8,
  90. padding: 10,
  91. textStyle: {
  92. color: 'black',
  93. fontSize: 12
  94. },
  95. formatter: function (params) {
  96. if (!params || params.length === 0) return ''
  97. // 找到K线数据
  98. const klineParam = params.find(param => param.seriesType === 'candlestick')
  99. if (!klineParam) return ''
  100. const date = klineParam.name
  101. // 开收低高分别取参数的第2到第5个数
  102. const open = klineParam.data[1]
  103. const close = klineParam.data[2]
  104. const low = klineParam.data[3]
  105. const high = klineParam.data[4]
  106. return `<span style="color:black;font-weight:bold;">日期: ${date}</span><br/><span style="color:black;">开盘价: ${open}</span><br/><span style="color:black;">收盘价: ${close}</span><br/><span style="color:black;">最低价: ${low}</span><br/><span style="color:black;">最高价: ${high}</span>`
  107. }
  108. },
  109. //控制坐标轴
  110. grid: [
  111. {
  112. top: "5%",
  113. height: window.innerWidth <= 768 ? "35%" : "40%",
  114. width: window.innerWidth <= 768 ? "70%" : "80%",
  115. left: window.outerWidth <= 768 ? "20%" : "10%",
  116. },
  117. {
  118. top: window.innerWidth <= 768 ? "40%" :"45%",
  119. height: "35%",
  120. width: window.innerWidth <= 768 ? "70%" : "80%",
  121. left: window.outerWidth <= 768 ? "20%" : "10%",
  122. },
  123. {
  124. top: window.innerWidth <= 768 ? "75%" : "80%",
  125. height: "2%",
  126. width: window.innerWidth <= 768 ? "70%" : "80%",
  127. left: window.outerWidth <= 768 ? "20%" : "10%",
  128. },
  129. ],
  130. visualMap: [
  131. {
  132. show: false, // 不显示控制条
  133. seriesIndex: 1, // 作用于热力图的系列
  134. min: 0,
  135. max: 2000, // 根据您的数据范围调整
  136. calculable: true,
  137. orient: "horizontal",
  138. left: "center",
  139. bottom: "15%",
  140. inRange: {
  141. color: ["transparent"],
  142. },
  143. },
  144. ],
  145. // 横坐标内容
  146. xAxis: [
  147. {
  148. type: "category",
  149. gridIndex: 0,
  150. data: dealData.categoryData,
  151. axisPointer: {
  152. show: true,
  153. type: "line",
  154. label: {
  155. show: true,
  156. backgroundColor: 'rgba(0,191,255)',
  157. color: 'black'
  158. },
  159. },
  160. axisTick: { show: false }, // 隐藏刻度线
  161. axisLabel: { show: false, rotate: 45 }, // 隐藏刻度标签
  162. axisLine: {
  163. show: true,
  164. lineStyle: {
  165. color: "white",
  166. },
  167. },
  168. },
  169. {
  170. type: "category",
  171. gridIndex: 1,
  172. data: dealData.categoryData,
  173. axisTick: { show: false }, // 隐藏刻度线
  174. axisLabel: { show: false, rotate: 45 }, // 隐藏刻度标签
  175. splitLine: {
  176. show: true,
  177. lineStyle: {
  178. color: "white",
  179. // 间隔线类型实线
  180. type: "solid",
  181. },
  182. interval: 0,
  183. },
  184. },
  185. {
  186. type: "category",
  187. gridIndex: 2,
  188. data: dealData.categoryData,
  189. axisLine: { lineStyle: { color: "white" } },
  190. axisPointer: {
  191. show: false,
  192. label: {
  193. show: false,
  194. },
  195. type: "line",
  196. },
  197. axisTick: {
  198. show: true,
  199. alignWithLabel: true, // 刻度线与标签对齐
  200. lineStyle: {
  201. color: "white", // 与十字线颜色保持一致
  202. width: 1,
  203. type: "dashed" // 与十字线样式保持一致
  204. }
  205. },
  206. },
  207. ],
  208. yAxis: [
  209. {
  210. scale: true,
  211. axisLabel: {
  212. formatter: function (value) {
  213. return value;
  214. },
  215. },
  216. axisLine: {
  217. show: true,
  218. lineStyle: { color: "white" },
  219. },
  220. splitLine: {
  221. show: false,
  222. },
  223. axisPointer: {
  224. show: true,
  225. label: {
  226. show: true,
  227. backgroundColor: 'rgba(0,255,127)',
  228. color: 'black'
  229. },
  230. type: "line",
  231. },
  232. },
  233. {
  234. gridIndex: 1,
  235. type: "category",
  236. data: [0, 1, 2, 3], // 倒序显示
  237. axisLine: { lineStyle: { color: "white" } },
  238. axisLabel: {
  239. show: false, // 显示刻度标签
  240. color: "#fff", // 白色文字
  241. backgroundColor: "transparent",
  242. fontSize: 12,
  243. margin: 8,
  244. },
  245. axisPointer: {
  246. show: true,
  247. label: {
  248. show: false,
  249. },
  250. type: "line",
  251. },
  252. axisTick: { show: false }, // 隐藏刻度线
  253. splitLine: {
  254. show: true,
  255. lineStyle: {
  256. color: "#8392A5",
  257. width: 1,
  258. type: "solid",
  259. },
  260. interval: 0, // 强制显示间隔
  261. },
  262. },
  263. {
  264. gridIndex: 2,
  265. type: "value",
  266. axisLine: {
  267. show: true,
  268. lineStyle: { color: "white" },
  269. },
  270. splitLine: {
  271. show: !1,
  272. },
  273. axisTick: { show: false }, // 隐藏刻度线
  274. axisLabel: { show: false }, // 隐藏刻度标签
  275. axisPointer: {
  276. show: false,
  277. label: {
  278. show: false,
  279. },
  280. type: "line",
  281. },
  282. },
  283. ],
  284. // 下拉条
  285. dataZoom: [
  286. {
  287. textStyle: {
  288. color: "white", // 字体颜色
  289. },
  290. // handleIcon:
  291. // "M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z",
  292. // handleSize: "80%",
  293. dataBackground: {
  294. areaStyle: {
  295. color: "#8392A5",
  296. },
  297. lineStyle: {
  298. opacity: 0.8,
  299. color: "#8392A5",
  300. },
  301. },
  302. xAxisIndex: [0, 1, 2],
  303. // handleStyle: {
  304. // color: "#fff",
  305. // shadowBlur: 3,
  306. // shadowColor: "rgba(0, 0, 0, 0.6)",
  307. // shadowOffsetX: 2,
  308. // shadowOffsetY: 2,
  309. // },
  310. start: 50, // 默认展示后半部分数据
  311. end: 100,
  312. bottom: window.innerWidth <= 768 ? "5%" : "7%", // 下移数据缩放滑块
  313. },
  314. // {
  315. // show: true,
  316. // type: "slider",
  317. // xAxisIndex: [0, 1, 2],
  318. // bottom: "0%",
  319. // textStyle: {
  320. // color: "white",
  321. // },
  322. // },
  323. {
  324. type: "inside",
  325. xAxisIndex: [0, 1, 2],
  326. filterMode: "filter",
  327. },
  328. ],
  329. series: [
  330. {
  331. type: "candlestick",
  332. name: "\u65e5K",
  333. // 数据
  334. data: dealData.value,
  335. itemStyle: {
  336. normal: {
  337. color0: "red", // 阴线颜色
  338. color: "#0CF49B", // 阳线颜色
  339. borderColor0: "#FD1050", // 阴线边框颜色
  340. borderColor: "#0CF49B", // 阳线边框颜色
  341. },
  342. },
  343. },
  344. // 副图热力矩阵
  345. {
  346. name: "热力矩阵",
  347. type: "heatmap",
  348. gridIndex: 1,
  349. xAxisIndex: 1,
  350. yAxisIndex: 1,
  351. data: processedHeatmap,
  352. coordinateSystem: "cartesian2d",
  353. tooltip: {
  354. trigger: "item",
  355. axisPointer: {
  356. type: 'cross', // 十字准星效果
  357. crossStyle: {
  358. color: '#999'
  359. }
  360. },
  361. // 覆盖全局 tooltip 配置
  362. formatter: function (params) {
  363. return `${params.value[2]}`; // 直接取数据中的第3个元素(数值)
  364. }
  365. },
  366. label: {
  367. normal: {
  368. show: true,
  369. color: "#fff", // 文本颜色
  370. formatter: function (params) {
  371. const value = params.value[2]; // 数值
  372. const colorType = params.value[3]; // 颜色类型
  373. // 使用 rich 富文本格式
  374. return `{${colorType}|${value}}`;
  375. },
  376. rich: {
  377. green: { color: "#27ae60", fontWeight: "bold" },
  378. purple: { color: "#8e44ad", fontWeight: "bold" },
  379. red: { color: "#FF0000", fontWeight: "bold" },
  380. yellow: { color: "#FFFF00", fontWeight: "bold" },
  381. normal: { color: "#fff" },
  382. },
  383. },
  384. },
  385. itemStyle: {
  386. normal: {
  387. color: "transparent",
  388. borderWidth: 2,
  389. },
  390. },
  391. emphasis: {
  392. itemStyle: {
  393. shadowBlur: 10,
  394. shadowColor: "rgba(0, 0, 0, 0.5)",
  395. },
  396. },
  397. },
  398. {
  399. name: "凸起",
  400. type: "line",
  401. xAxisIndex: 2,
  402. yAxisIndex: 2,
  403. data: lineData3.value,
  404. color: "black",
  405. lineStyle: {
  406. normal: {
  407. color: "red",
  408. },
  409. },
  410. symbol: "none",
  411. emphasis: {
  412. showSymbol: true,
  413. },
  414. },
  415. ],
  416. };
  417. // 添加DOM元素检查
  418. if (!KlineCanvs.value) {
  419. console.error('DOM元素未准备好,无法初始化ECharts');
  420. return;
  421. }
  422. // 创造echarts图
  423. if (KlineCanvsChart) {
  424. KlineCanvsChart.dispose();
  425. }
  426. KlineCanvsChart = echarts.init(KlineCanvs.value);
  427. KlineCanvsChart.setOption(KlineOption);
  428. // 添加图表交互事件监听器
  429. if (KlineCanvsChart) {
  430. // 监听数据缩放事件
  431. KlineCanvsChart.on('dataZoom', () => {
  432. if (window.handleChartInteractionStart) {
  433. window.handleChartInteractionStart();
  434. }
  435. });
  436. // 监听鼠标滚轮事件(缩放)
  437. KlineCanvsChart.on('mousewheel', () => {
  438. if (window.handleChartInteractionStart) {
  439. window.handleChartInteractionStart();
  440. }
  441. });
  442. // 监听鼠标按下事件(拖拽开始)
  443. KlineCanvsChart.on('mousedown', () => {
  444. if (window.handleChartInteractionStart) {
  445. window.handleChartInteractionStart();
  446. }
  447. });
  448. // 监听鼠标释放事件(拖拽结束)
  449. KlineCanvsChart.on('mouseup', () => {
  450. if (window.handleChartInteractionEnd) {
  451. window.handleChartInteractionEnd();
  452. }
  453. });
  454. // 监听图表点击事件
  455. KlineCanvsChart.on('click', () => {
  456. if (window.handleChartInteractionStart) {
  457. window.handleChartInteractionStart();
  458. }
  459. });
  460. }
  461. // 防抖函数,避免频繁触发resize
  462. const debounce = (func, wait) => {
  463. let timeout;
  464. return function executedFunction(...args) {
  465. const later = () => {
  466. clearTimeout(timeout);
  467. func(...args);
  468. };
  469. clearTimeout(timeout);
  470. timeout = setTimeout(later, wait);
  471. };
  472. };
  473. // 监听窗口大小变化,参考股市温度计的实现
  474. const resizeHandler = debounce(() => {
  475. if (KlineCanvsChart && !KlineCanvsChart.isDisposed()) {
  476. try {
  477. KlineCanvsChart.resize();
  478. console.log('情绪解码器图表已重新调整大小');
  479. } catch (error) {
  480. console.error('情绪解码器图表resize失败:', error);
  481. }
  482. }
  483. }, 100); // 100ms防抖延迟
  484. // 移除之前的监听器(如果存在)
  485. if (window.emotionDecodResizeHandler) {
  486. window.removeEventListener('resize', window.emotionDecodResizeHandler);
  487. }
  488. // 添加新的监听器
  489. window.addEventListener('resize', resizeHandler);
  490. // 存储resize处理器以便后续清理
  491. window.emotionDecodResizeHandler = resizeHandler;
  492. // 添加容器大小监听器
  493. if (KlineCanvs.value && window.ResizeObserver) {
  494. const containerObserver = new ResizeObserver(debounce(() => {
  495. if (KlineCanvsChart && !KlineCanvsChart.isDisposed()) {
  496. try {
  497. KlineCanvsChart.resize();
  498. console.log('情绪解码器容器大小变化,图表已调整');
  499. } catch (error) {
  500. console.error('情绪解码器容器resize失败:', error);
  501. }
  502. }
  503. }, 100));
  504. containerObserver.observe(KlineCanvs.value);
  505. window.emotionDecodContainerObserver = containerObserver;
  506. }
  507. }
  508. const windowHeight = ref(window.innerHeight);
  509. const updateHeight = () => {
  510. windowHeight.value = window.innerHeight; // 更新响应式变量
  511. };
  512. // 组件挂载时添加窗口高度监听
  513. onMounted(() => {
  514. // 避免重复添加监听器
  515. if (!window.emotionDecodHeightHandler) {
  516. window.addEventListener("resize", updateHeight);
  517. window.emotionDecodHeightHandler = updateHeight;
  518. }
  519. });
  520. onBeforeUnmount(() => {
  521. // 组件卸载时销毁图表
  522. if (KlineCanvsChart) {
  523. KlineCanvsChart.dispose();
  524. KlineCanvsChart = null;
  525. }
  526. // 移除窗口resize监听器
  527. if (window.emotionDecodResizeHandler) {
  528. window.removeEventListener('resize', window.emotionDecodResizeHandler);
  529. window.emotionDecodResizeHandler = null;
  530. }
  531. // 移除窗口高度监听器
  532. if (window.emotionDecodHeightHandler) {
  533. window.removeEventListener('resize', window.emotionDecodHeightHandler);
  534. window.emotionDecodHeightHandler = null;
  535. }
  536. // 清理容器观察器
  537. if (window.emotionDecodContainerObserver) {
  538. window.emotionDecodContainerObserver.disconnect();
  539. window.emotionDecodContainerObserver = null;
  540. }
  541. });
  542. </script>
  543. <style scoped>
  544. .qxjmqbox {
  545. height: auto;
  546. width: 100%;
  547. margin: 0 auto;
  548. }
  549. .qxjmqEcharts {
  550. width: 100%;
  551. height: 542px;
  552. margin: 0;
  553. /* top: 5rem; */
  554. box-sizing: border-box;
  555. overflow: hidden;
  556. }
  557. /* 手机端适配样式 */
  558. @media only screen and (max-width: 768px) {
  559. .qxjmqEcharts {
  560. width: 100%;
  561. height: 270px;
  562. margin: 0;
  563. /* top: 5rem; */
  564. }
  565. .qxjmqbox {
  566. height: auto;
  567. width: 90%;
  568. }
  569. }
  570. </style>