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.

4274 lines
134 KiB

4 months ago
4 months ago
4 months ago
4 months ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
4 months ago
4 months ago
4 months ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
3 months ago
2 days ago
2 days ago
4 days ago
4 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
4 months ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
4 months ago
4 months ago
4 months ago
2 days ago
4 months ago
4 months ago
2 days ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
4 months ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
2 days ago
4 months ago
4 months ago
4 months ago
4 months ago
  1. <script setup>
  2. import { ref, onMounted, watch, nextTick, reactive, onUnmounted } from "vue";
  3. import { ElDialog } from "element-plus";
  4. import {
  5. getReplyStreamAPI,
  6. getReplyAPI,
  7. TTSAPI,
  8. qsArpAamClickAPI,
  9. dbqbFirstAPI,
  10. dbqbSecondOneAPI,
  11. dbqbSecondTwoAPI,
  12. dbqbSecondThreeAPI,
  13. dbqbSecondFourAPI,
  14. dataListAPI,
  15. } from "../api/AIxiaocaishen";
  16. import { useUserStore } from "../store/userPessionCode";
  17. import { useChatStore } from "../store/chat";
  18. import { useAudioStore } from "../store/audio";
  19. import { useDataStore } from "@/store/dataList.js";
  20. import { marked } from "marked"; // 引入marked库
  21. import katex from "katex"; // 引入 KaTeX 库
  22. import { htmlToText } from "html-to-text";
  23. import { Howl, Howler } from "howler";
  24. import * as echarts from "echarts";
  25. import _, { add } from "lodash";
  26. import moment from "moment";
  27. import title1 from "@/assets/img/AIchat/核心价值评估标题.png";
  28. import title2 from "@/assets/img/AIchat/主力作战.png";
  29. import title3 from "@/assets/img/AIchat/攻防三维.png";
  30. import title4 from "@/assets/img/AIchat/综合作战.png";
  31. import logo1 from "@/assets/img/AIchat/夺宝奇兵logo.png";
  32. import logo2 from "@/assets/img/AIchat/开启无限财富.png";
  33. import bgc from "@/assets/img/AIchat/圈.png";
  34. import bgc1 from "@/assets/img/AIchat/圈1.png";
  35. const chatStore = useChatStore();
  36. const audioStore = useAudioStore();
  37. const dataStore = useDataStore();
  38. // 随机GIF
  39. const currentGif = ref("");
  40. const renderer = new marked.Renderer();
  41. // 重写 del 方法,让删除线不生效
  42. renderer.del = function (text) {
  43. // 处理各种数据类型
  44. console.log("text", text);
  45. return "~" + text.tokens[0].raw + "<br>" + text.tokens[2].raw + "~";
  46. };
  47. // 定义自定义事件
  48. const emit = defineEmits(["updateMessage", "sendMessage"]);
  49. // 音频播放方法
  50. const playAudio = (url) => {
  51. // 添加空值校验
  52. if (!url) {
  53. console.warn("音频URL为空,跳过播放");
  54. audioStore.isPlaying = false;
  55. return;
  56. }
  57. const handlePlay = () => {
  58. // if (audioStore.soundInstance) {
  59. // Howler.unload(); // 添加清理旧实例
  60. // // audioStore.soundInstance.stop()
  61. // }
  62. if (audioStore.isNewInstance) {
  63. const newSound = new Howl({
  64. src: [url],
  65. html5: true, // 强制HTML5 Audio解决iOS兼容问题
  66. format: ["mp3", "acc"],
  67. rate: 1.2, // 调整播放速度
  68. onplay: () => {
  69. audioStore.isPlaying = true; // 改为更新store状态
  70. newSound.volume(1); // 添加音量设置
  71. },
  72. onend: () => (audioStore.isPlaying = false),
  73. onstop: () => (audioStore.isPlaying = false),
  74. onloaderror: (id, err) => {
  75. console.error("音频加载失败:", err);
  76. ElMessage.error("音频播放失败,请检查网络连接");
  77. },
  78. });
  79. if (audioStore.nowSound) {
  80. audioStore.nowSound.stop();
  81. }
  82. audioStore.nowSound = newSound;
  83. audioStore.isNewInstance = false;
  84. console.log("新音频");
  85. } else {
  86. console.log("已经有音频");
  87. }
  88. const newSound = audioStore.nowSound;
  89. // 添加立即播放逻辑
  90. newSound.play();
  91. audioStore.setAudioInstance(newSound);
  92. Howler._howls.push(newSound); // 强制注册到全局管理
  93. // // 处理浏览器自动播放策略
  94. // if (Howler.autoUnlock) {
  95. // Howler.autoUnlock();
  96. // }
  97. };
  98. // if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
  99. // document.addEventListener('touchstart', handlePlay, { once: true });
  100. // ElMessage.info('请轻触屏幕以启用音频播放');
  101. // } else {
  102. // handlePlay();
  103. // }
  104. handlePlay();
  105. // Howler.volume(1.0) // 添加全局音量设置
  106. };
  107. // 新增暂停方法
  108. const pauseAudio = () => {
  109. if (audioStore.soundInstance) {
  110. // 添加移动端特殊处理
  111. // if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
  112. // Howler.pause(); // iOS需要强制停止音频上下文
  113. // } else {
  114. // audioStore.soundInstance.pause();
  115. // }
  116. audioStore.soundInstance.pause();
  117. audioStore.isPlaying = false;
  118. // // 添加状态同步
  119. // nextTick(() => {
  120. // Howler.unload();
  121. // audioStore.soundInstance = null;
  122. // });
  123. }
  124. };
  125. // 音频轮流播放方法
  126. const playAudioSequence = (audioUrls) => {
  127. console.log("playAudioSequence被调用,参数:", audioUrls);
  128. if (!audioUrls || audioUrls.length === 0) {
  129. console.warn("音频URL列表为空,跳过播放");
  130. return;
  131. }
  132. let currentIndex = 0;
  133. let audioSequence = [...audioUrls]; // 保存音频序列
  134. const playNext = () => {
  135. if (currentIndex >= audioSequence.length) {
  136. console.log("所有音频播放完成,重置到第一个音频");
  137. // 播放完成后重置到第一个音频,但不自动播放
  138. currentIndex = 0;
  139. audioStore.isPlaying = false;
  140. audioStore.isPaused = false;
  141. audioStore.playbackPosition = 0;
  142. // 清除音频实例,确保下次点击从头开始
  143. audioStore.soundInstance = null;
  144. audioStore.nowSound = null;
  145. if (audioSequence.length > 0) {
  146. audioStore.setCurrentAudioUrl(audioSequence[0]);
  147. }
  148. return;
  149. }
  150. const currentUrl = audioSequence[currentIndex];
  151. console.log(`正在播放第${currentIndex + 1}个音频:`, currentUrl);
  152. console.log(
  153. "音频URL有效性检查:",
  154. !!currentUrl,
  155. "长度:",
  156. currentUrl?.length
  157. );
  158. // 增强URL验证
  159. if (!currentUrl || typeof currentUrl !== 'string' || currentUrl.trim() === '') {
  160. console.error(`音频 ${currentIndex + 1} URL无效,跳过该音频`);
  161. currentIndex++;
  162. setTimeout(() => {
  163. playNext();
  164. }, 100);
  165. return;
  166. }
  167. // 检查URL格式
  168. try {
  169. new URL(currentUrl);
  170. } catch (e) {
  171. console.error(`音频 ${currentIndex + 1} URL格式错误:`, currentUrl);
  172. currentIndex++;
  173. setTimeout(() => {
  174. playNext();
  175. }, 100);
  176. return;
  177. }
  178. // 设置当前音频URL
  179. audioStore.setCurrentAudioUrl(currentUrl);
  180. // 停止当前播放的音频
  181. if (audioStore.nowSound) {
  182. audioStore.nowSound.stop();
  183. }
  184. const sound = new Howl({
  185. src: [currentUrl],
  186. html5: true,
  187. format: ["mp3", "acc"],
  188. rate: 1.2,
  189. onplay: () => {
  190. audioStore.isPlaying = true;
  191. audioStore.isPaused = false;
  192. console.log(`开始播放音频 ${currentIndex + 1}`);
  193. console.log('音频播放状态:', {
  194. duration: sound.duration(),
  195. state: sound.state(),
  196. playing: sound.playing()
  197. });
  198. },
  199. onpause: () => {
  200. audioStore.isPlaying = false;
  201. audioStore.isPaused = true;
  202. audioStore.playbackPosition = sound.seek() || 0;
  203. console.log(`音频 ${currentIndex + 1} 已暂停`);
  204. },
  205. onend: () => {
  206. audioStore.isPlaying = false;
  207. audioStore.isPaused = false;
  208. audioStore.playbackPosition = 0;
  209. console.log(`音频 ${currentIndex + 1} 播放完成`);
  210. currentIndex++;
  211. // 如果是最后一个音频播放完成,立即清除实例
  212. if (currentIndex >= audioSequence.length) {
  213. console.log('最后一个音频播放完成,清除音频实例');
  214. audioStore.soundInstance = null;
  215. audioStore.nowSound = null;
  216. currentIndex = 0; // 立即重置索引
  217. }
  218. // 播放下一个音频
  219. setTimeout(() => {
  220. playNext();
  221. }, 500); // 间隔500ms播放下一个
  222. },
  223. onstop: () => {
  224. audioStore.isPlaying = false;
  225. audioStore.isPaused = false;
  226. audioStore.playbackPosition = 0;
  227. console.log(`音频 ${currentIndex + 1} 已停止`);
  228. },
  229. onloaderror: (id, err) => {
  230. console.error(`音频 ${currentIndex + 1} 加载失败:`, err);
  231. console.error('失败的音频URL:', currentUrl);
  232. console.error('错误详情:', { id, err });
  233. // 增加重试机制
  234. if (!sound.retryCount) {
  235. sound.retryCount = 0;
  236. }
  237. if (sound.retryCount < 2) {
  238. sound.retryCount++;
  239. console.log(`音频 ${currentIndex + 1}${sound.retryCount}次重试加载`);
  240. setTimeout(() => {
  241. sound.load();
  242. }, 1000 * sound.retryCount); // 递增延时重试
  243. } else {
  244. console.warn(`音频 ${currentIndex + 1} 重试失败,跳过该音频`);
  245. currentIndex++;
  246. // 跳过失败的音频,播放下一个
  247. setTimeout(() => {
  248. playNext();
  249. }, 100);
  250. }
  251. },
  252. });
  253. audioStore.nowSound = sound;
  254. audioStore.setAudioInstance(sound);
  255. // 添加播放超时检测
  256. const playTimeout = setTimeout(() => {
  257. if (!audioStore.isPlaying && sound.state() === 'loading') {
  258. console.warn(`音频 ${currentIndex + 1} 播放超时,可能网络问题`);
  259. sound.stop();
  260. currentIndex++;
  261. playNext();
  262. }
  263. }, 10000); // 10秒超时
  264. // 播放成功后清除超时
  265. sound.once('play', () => {
  266. clearTimeout(playTimeout);
  267. });
  268. console.log(`尝试播放音频 ${currentIndex + 1},URL: ${currentUrl}`);
  269. sound.play();
  270. };
  271. // 重写togglePlayPause方法以支持音频序列控制
  272. const originalTogglePlayPause = audioStore.togglePlayPause;
  273. audioStore.togglePlayPause = () => {
  274. console.log('音频控制按钮被点击');
  275. console.log('当前播放状态:', audioStore.isPlaying);
  276. console.log('当前暂停状态:', audioStore.isPaused);
  277. console.log('当前音频实例:', audioStore.soundInstance);
  278. console.log('当前索引:', currentIndex, '音频序列长度:', audioSequence.length);
  279. if (audioStore.soundInstance) {
  280. if (audioStore.isPlaying) {
  281. // 暂停当前音频
  282. console.log('暂停当前音频');
  283. audioStore.pause();
  284. } else if (audioStore.isPaused) {
  285. // 从暂停位置继续播放
  286. console.log('从暂停位置继续播放');
  287. audioStore.play();
  288. } else {
  289. // 重新开始播放当前音频或从头开始播放序列
  290. console.log('重新开始播放,当前索引:', currentIndex);
  291. if (currentIndex >= audioSequence.length) {
  292. console.log('所有音频已播放完成,从头开始');
  293. currentIndex = 0; // 重置到第一个音频
  294. }
  295. playNext();
  296. }
  297. } else {
  298. // 没有音频实例时,从头开始播放
  299. console.log('没有音频实例,从头开始播放');
  300. currentIndex = 0;
  301. playNext();
  302. }
  303. };
  304. // 开始播放第一个音频
  305. playNext();
  306. };
  307. // 获取消息
  308. const chatMsg = computed(() => chatStore.messages);
  309. const props = defineProps({
  310. messages: Array,
  311. chartData: {
  312. type: Object,
  313. default: null,
  314. },
  315. index: {
  316. type: Number,
  317. required: true,
  318. },
  319. });
  320. // 打字机效果
  321. const typewriterContent = ref("");
  322. const isTyping = ref(false);
  323. const typeWriter = (text, callback) => {
  324. let index = 0;
  325. isTyping.value = true;
  326. typewriterContent.value = "";
  327. const typingInterval = setInterval(() => {
  328. if (index < text.length) {
  329. typewriterContent.value += text.charAt(index);
  330. index++;
  331. // 自动滚动到底部
  332. nextTick(() => {
  333. const container = document.querySelector(".message-area");
  334. if (container) container.scrollTop = container.scrollHeight;
  335. });
  336. } else {
  337. clearInterval(typingInterval);
  338. isTyping.value = false;
  339. if (callback) callback();
  340. }
  341. }, 50); // 调整速度(毫秒)
  342. };
  343. const fnGetData = (data) => {
  344. const YaLiZhiChengLuoPan = data.YaLiZhiChengLuoPan;
  345. let sz = ref(5.5); // 进行判断
  346. const yl = YaLiZhiChengLuoPan.Yali; // 压力位
  347. const zc = YaLiZhiChengLuoPan.ZhiCheng; // 支撑位
  348. console.log("yl", yl, "zc", zc);
  349. if (yl == "较大" && zc == "较弱") {
  350. sz.value = 0.5; // 极高风险
  351. } else if (yl == "一般" && zc == "较弱") {
  352. sz.value = 1.5; // 弱撑中压区
  353. } else if (yl == "较弱" && zc == "较弱") {
  354. sz.value = 2.5; // 弱撑弱压区
  355. } else if (yl == "较大" && zc == "较大") {
  356. sz.value = 3.5; // 强撑强压区
  357. } else if (yl == "一般" && zc == "较大") {
  358. sz.value = 4.5; // 强撑中压
  359. } else if (yl == "较弱" && zc == "较大") {
  360. sz.value = 5.5; // 强撑弱压区
  361. } else if (yl == "较大" && zc == "一般") {
  362. sz.value = 0.2;
  363. } else if (yl == "一般" && zc == "一般") {
  364. sz.value = 3;
  365. } else if (yl == "较弱" && zc == "一般") {
  366. sz.value = 5.8;
  367. }
  368. return sz.value;
  369. };
  370. const typingQueue = ref([]);
  371. const isTypingInProgress = ref(false);
  372. // 创建打字机效果的Promise函数
  373. const createTypingEffect = (message, content, speed) => {
  374. return new Promise((resolve) => {
  375. chatStore.messages.push(message);
  376. if (Array.isArray(content) && content.length > 0) {
  377. message.content = "";
  378. message.isTyping = true;
  379. let currentIndex = 0;
  380. const processNextElement = () => {
  381. if (currentIndex >= content.length) {
  382. if (message.isEnd) {
  383. if (message.isEnd == '1') {
  384. apiStatus.one.isEnd = true;
  385. } else if (message.isEnd == '2') {
  386. apiStatus.two.isEnd = true;
  387. } else if (message.isEnd == '3') {
  388. apiStatus.three.isEnd = true;
  389. } else if (message.isEnd == '4') {
  390. apiStatus.four.isEnd = true;
  391. }
  392. }
  393. if (message.error) {
  394. chatStore.messages.push({
  395. class: "ing",
  396. type: "ing",
  397. flag: false,
  398. content: '工作流返回出错,请稍后重试',
  399. })
  400. chatStore.isLoading = false;
  401. if (message.error == '2') {
  402. apiStatus.two.isError = true;
  403. } else if (message.error == '3') {
  404. apiStatus.three.isError = true;
  405. } else if (message.error == '4') {
  406. apiStatus.four.isError = true;
  407. }
  408. }
  409. if (message.end) {
  410. chatStore.getUserCount();
  411. chatStore.isLoading = false;
  412. }
  413. message.isTyping = false;
  414. nextTick(() => {
  415. resolve(); // 完成后resolve
  416. });
  417. return;
  418. }
  419. if (currentIndex % 2 === 0) {
  420. // 偶数下标:直接加入
  421. message.content += content[currentIndex];
  422. currentIndex++;
  423. processNextElement(); // 立即处理下一个元素
  424. } else {
  425. // 奇数下标:打字机效果
  426. const text = content[currentIndex];
  427. let charIndex = 0;
  428. const typingInterval = setInterval(() => {
  429. if (charIndex < text.length) {
  430. message.content += text.charAt(charIndex);
  431. charIndex++;
  432. } else {
  433. clearInterval(typingInterval);
  434. currentIndex++;
  435. processNextElement(); // 处理下一个元素
  436. }
  437. }, speed);
  438. }
  439. };
  440. processNextElement(); // 开始处理
  441. } else {
  442. if (message.kline) {
  443. if (message.klineType == 1) {
  444. console.log("六色罗盘消息已添加到聊天列表");
  445. // 在渲染完成后初始化图表
  446. nextTick(() => {
  447. console.log("nextTick开始 - 准备渲染图表");
  448. console.log("消息列表:", chatStore.messages);
  449. // 寻找最新添加的K线消息索引
  450. let klineIndex = -1;
  451. for (let i = 0; i < chatStore.messages.length; i++) {
  452. if (chatStore.messages[i].messageId === message.messageId) {
  453. klineIndex = i;
  454. break;
  455. }
  456. }
  457. console.log("找到的K线消息索引:", klineIndex);
  458. if (klineIndex !== -1) {
  459. const containerId = `kline-container-${klineIndex}`;
  460. console.log("图表容器ID:", containerId);
  461. // 确保DOM已经渲染完成
  462. setTimeout(() => {
  463. console.log("延时执行,确保DOM已渲染");
  464. KlineCanvsEcharts(containerId);
  465. }, 100); // 短暂延时确保DOM已渲染
  466. } else {
  467. console.warn("未找到K线消息");
  468. }
  469. });
  470. } else {
  471. console.log("K线消息已添加到聊天列表");
  472. // 在渲染完成后初始化图表
  473. nextTick(() => {
  474. console.log("nextTick开始 - 准备渲染图表");
  475. console.log("消息列表:", chatStore.messages);
  476. // 寻找最新添加的K线消息索引
  477. let klineIndex = -1;
  478. for (let i = 0; i < chatStore.messages.length; i++) {
  479. if (chatStore.messages[i].messageId === message.messageId) {
  480. klineIndex = i;
  481. break;
  482. }
  483. }
  484. console.log("找到的K线消息索引:", klineIndex);
  485. if (klineIndex !== -1) {
  486. const containerId = `kline-container-${klineIndex}`;
  487. console.log("图表容器ID:", containerId);
  488. // 确保DOM已经渲染完成
  489. setTimeout(() => {
  490. console.log("延时执行,确保DOM已渲染");
  491. KlineCanvsEcharts(containerId);
  492. }, 100); // 短暂延时确保DOM已渲染
  493. } else {
  494. console.warn("未找到K线消息");
  495. }
  496. });
  497. }
  498. if (message.isEnd) {
  499. if (message.isEnd == '1') {
  500. apiStatus.one.isEnd = true;
  501. } else if (message.isEnd == '2') {
  502. apiStatus.two.isEnd = true;
  503. } else if (message.isEnd == '3') {
  504. apiStatus.three.isEnd = true;
  505. } else if (message.isEnd == '4') {
  506. apiStatus.four.isEnd = true;
  507. }
  508. }
  509. if (message.error) {
  510. chatStore.messages.push({
  511. class: "ing",
  512. type: "ing",
  513. flag: false,
  514. content: '工作流返回出错,请稍后重试',
  515. })
  516. chatStore.isLoading = false;
  517. if (message.error == '2') {
  518. apiStatus.two.isError = true;
  519. } else if (message.error == '3') {
  520. apiStatus.three.isError = true;
  521. } else if (message.error == '4') {
  522. apiStatus.four.isError = true;
  523. }
  524. }
  525. // 延时1秒后resolve
  526. setTimeout(() => {
  527. resolve();
  528. }, 1000);
  529. } else {
  530. if (message.isEnd) {
  531. if (message.isEnd == '1') {
  532. apiStatus.one.isEnd = true;
  533. } else if (message.isEnd == '2') {
  534. apiStatus.two.isEnd = true;
  535. } else if (message.isEnd == '3') {
  536. apiStatus.three.isEnd = true;
  537. } else if (message.isEnd == '4') {
  538. apiStatus.four.isEnd = true;
  539. }
  540. }
  541. if (message.error) {
  542. chatStore.messages.push({
  543. class: "ing",
  544. type: "ing",
  545. flag: false,
  546. content: '工作流返回出错,请稍后重试',
  547. })
  548. chatStore.isLoading = false;
  549. if (message.error == '2') {
  550. apiStatus.two.isError = true;
  551. } else if (message.error == '3') {
  552. apiStatus.three.isError = true;
  553. } else if (message.error == '4') {
  554. apiStatus.four.isError = true;
  555. }
  556. }
  557. // 延时1秒后resolve
  558. setTimeout(() => {
  559. resolve();
  560. }, 1000);
  561. }
  562. }
  563. });
  564. };
  565. let apiStatus = {}
  566. // 队列处理函数
  567. const processTypingQueue = async () => {
  568. if (isTypingInProgress.value || typingQueue.value.length === 0) {
  569. return;
  570. }
  571. isTypingInProgress.value = true;
  572. while (typingQueue.value.length > 0) {
  573. const task = typingQueue.value.shift();
  574. await createTypingEffect(task.message, task.content, task.speed);
  575. }
  576. isTypingInProgress.value = false;
  577. };
  578. // 添加打字机任务到队列
  579. const addTypingTask = (message, content, speed) => {
  580. typingQueue.value.push({ message, content, speed });
  581. processTypingQueue();
  582. };
  583. const hasValidData = ref(false);
  584. // 创建一个非响应式的对象来存储图表实例
  585. const chartInstancesMap = {};
  586. // 存储上一次的消息的length
  587. const previousMessagesLength = ref(0);
  588. watch(
  589. () => props.messages,
  590. async (newVal, oldVal) => {
  591. // console.log(newVal, 'newVal')
  592. // console.log(oldVal, 'oldVal')
  593. // console.log(previousMessagesLength.value, 'previousMessagesLength')
  594. // console.log(newVal.length, 'newVal.length')
  595. // // 添加空值判断
  596. if (!newVal?.length || newVal === previousMessagesLength.value) return;
  597. previousMessagesLength.value = newVal.length;
  598. if (newVal.length > 0) {
  599. console.log("消息列表已更新,最新消息:", newVal[newVal.length - 1]);
  600. chatStore.messages.push(newVal[newVal.length - 1]);
  601. console.log("消息列表已更新,最新消息:", chatMsg[chatMsg.length - 1]);
  602. console.log("token", localStorage.getItem("localToken"));
  603. // 获取权限
  604. const userStore = useUserStore();
  605. const params1 = {
  606. content: newVal[newVal.length - 1].content,
  607. language: "cn",
  608. marketList: "usa,sg,my,hk,cn,can,vi,th,in",
  609. token: localStorage.getItem("localToken"),
  610. // language: "cn",
  611. // marketList: "hk,cn,usa,my,sg,vi,in,gb"
  612. // token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
  613. };
  614. // 标志
  615. let flag = true;
  616. const codeData = ref();
  617. // 第一阶段,意图识别
  618. try {
  619. // 调用工作流获取回复
  620. const result = await dbqbFirstAPI(params1);
  621. codeData.value = result.data;
  622. console.log(codeData.value, "codeData");
  623. // 根据意图识别结果判断
  624. if (result.code == 200) {
  625. chatStore.messages.push({
  626. class: "ing",
  627. type: "ing",
  628. flag: flag,
  629. content: result.data.kaishi,
  630. });
  631. } else {
  632. flag = false;
  633. console.log("执行回绝话术");
  634. const AIcontent = ref(result.msg);
  635. // 修改后的消息处理逻辑
  636. const processedContent = marked(AIcontent.value);
  637. const katexRegex = /\$\$(.*?)\$\$/g;
  638. const aiContent = processedContent.replace(
  639. katexRegex,
  640. (match, formula) => {
  641. try {
  642. return katex.renderToString(formula, { throwOnError: false });
  643. } catch (error) {
  644. console.error("KaTeX 渲染错误:", error);
  645. return match;
  646. }
  647. }
  648. );
  649. console.log(aiContent, "aiContent");
  650. chatStore.messages.push({
  651. class: "ing",
  652. type: "ing",
  653. flag: flag,
  654. content: aiContent,
  655. });
  656. chatStore.isLoading = false;
  657. }
  658. } catch (e) {
  659. console.log(e, "意图识别失败");
  660. chatStore.messages.push({
  661. class: "ing",
  662. type: "ing",
  663. flag: false,
  664. content: '工作流返回出错,请稍后重试',
  665. })
  666. chatStore.isLoading = false;
  667. }
  668. if (flag) {
  669. const params2 = {
  670. content: newVal[newVal.length - 1].content,
  671. language: "cn",
  672. marketList: "usa,sg,my,hk,cn,can,vi,th,in",
  673. token: localStorage.getItem("localToken"),
  674. // language: "cn",
  675. // marketList: "hk,cn,usa,my,sg,vi,in,gb"
  676. // token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
  677. name: codeData.value.name,
  678. code: codeData.value.code,
  679. market: codeData.value.market,
  680. };
  681. try {
  682. const env = import.meta.env.VITE_ENV;
  683. const result20 = await dataListAPI({
  684. token:
  685. // '8Csj5VVX1UbIb4C3oxrnbZi0+fEeMx8pywnIlrmTm45Cb/EllzWACLto9J9+fCFsfdgBOvKvyY94FvqlvM0',
  686. "8nkj4QBV1RPIb4CzoRTnbZi0+fEeMx8pywnIlrmTxdwROKkuwWqAWu9orpkpeXVqL98DPfeonNYpHv+mucA",
  687. market: codeData.value.market,
  688. code: codeData.value.code,
  689. language: "cn", //t.value.suoxie,
  690. brainPrivilegeState: 1,
  691. swordPrivilegeState: 1,
  692. stockForecastPrivilegeState: 1,
  693. spaceForecastPrivilegeState: 1,
  694. aibullPrivilegeState: 1,
  695. aigoldBullPrivilegeState: 1,
  696. airadarPrivilegeState: 1,
  697. // marketList: 1,
  698. // brainPrivilegeState: userStore.brainPerssion,
  699. // swordPrivilegeState: userStore.swordPerssion,
  700. // stockForecastPrivilegeState: userStore.pricePerssion,
  701. // spaceForecastPrivilegeState: userStore.timePerssion,
  702. // aibullPrivilegeState: userStore.aibullPerssion,
  703. // aigoldBullPrivilegeState: userStore.aiGnbullPerssion,
  704. // airadarPrivilegeState: userStore.airadarPerssion,
  705. marketList: userStore.aiGoldMarketList,
  706. });
  707. const HomePage = result20.data.HomePage;
  708. const AIGoldBull = result20.data.AIGoldBull;
  709. const katexRegex = /\$\$(.*?)\$\$/g;
  710. let result21;
  711. let result22;
  712. let result23;
  713. let result24;
  714. // 用于跟踪API完成状态和结果
  715. apiStatus = {
  716. one: { completed: false, result: null, error: null, isError: false, isEnd: false },
  717. two: { completed: false, result: null, error: null, isError: false, isEnd: false },
  718. three: { completed: false, result: null, error: null, isError: false, isEnd: false },
  719. four: { completed: false, result: null, error: null, isError: false, isEnd: false },
  720. };
  721. // 音频预加载状态跟踪
  722. const audioPreloadStatus = {
  723. one: { loaded: false, url: null },
  724. two: { loaded: false, url: null },
  725. three: { loaded: false, url: null },
  726. four: { loaded: false, url: null }
  727. };
  728. // 音频播放队列管理
  729. const audioQueue = ref([]);
  730. const isPlayingAudio = ref(false);
  731. // 播放音频队列
  732. // 防止重复调用的标志
  733. let isCallingPlayNext = false;
  734. // 当前播放索引
  735. let currentPlayIndex = 0;
  736. const playNextAudio = () => {
  737. console.log('=== playNextAudio 被调用 ===');
  738. console.log('当前队列状态:', {
  739. queueLength: audioQueue.value.length,
  740. queueItems: audioQueue.value.map(item => item.name),
  741. currentPlayIndex: currentPlayIndex,
  742. isPlayingAudio: isPlayingAudio.value,
  743. isCallingPlayNext: isCallingPlayNext,
  744. audioStoreIsPlaying: audioStore.isPlaying
  745. });
  746. if (audioQueue.value.length === 0 || isPlayingAudio.value || isCallingPlayNext) {
  747. console.log('❌ 播放条件不满足 - 队列长度:', audioQueue.value.length, '正在播放:', isPlayingAudio.value, '正在调用:', isCallingPlayNext);
  748. return;
  749. }
  750. // 检查是否已播放完所有音频(仅在队列不为空时重置)
  751. if (currentPlayIndex >= audioQueue.value.length && audioQueue.value.length > 0) {
  752. console.log('🔄 所有音频播放完成,重置索引从第一个开始');
  753. currentPlayIndex = 0;
  754. }
  755. isCallingPlayNext = true;
  756. isPlayingAudio.value = true;
  757. const audioInfo = audioQueue.value[currentPlayIndex];
  758. console.log(`✅ 开始播放${audioInfo.name}音频 (索引:${currentPlayIndex}),队列总长度:`, audioQueue.value.length);
  759. console.log('完整队列内容:', audioQueue.value.map((item, index) => `${index === currentPlayIndex ? '▶️' : '⏸️'} ${item.name}`));
  760. // 只有在确实需要停止时才停止之前的音频
  761. if (audioStore.nowSound && (audioStore.nowSound.playing() || audioStore.nowSound.state() === 'loading')) {
  762. console.log('停止之前的音频实例');
  763. audioStore.nowSound.stop();
  764. }
  765. const audio = new Howl({
  766. src: [audioInfo.url],
  767. html5: true,
  768. format: ["mp3", "acc"],
  769. rate: 1.2,
  770. onplay: () => {
  771. audioStore.isPlaying = true;
  772. audioStore.isPaused = false;
  773. isPlayingAudio.value = true; // 确保状态同步
  774. isCallingPlayNext = false; // 重置调用标志
  775. console.log(`${audioInfo.name}音频开始播放,时长:`, audio.duration());
  776. console.log('音频播放状态确认 - isPlayingAudio:', isPlayingAudio.value, 'audioStore.isPlaying:', audioStore.isPlaying);
  777. },
  778. onpause: () => {
  779. audioStore.isPlaying = false;
  780. audioStore.isPaused = true;
  781. audioStore.playbackPosition = audio.seek() || 0;
  782. console.log(`${audioInfo.name}音频已暂停,位置:`, audioStore.playbackPosition);
  783. },
  784. onresume: () => {
  785. audioStore.isPlaying = true;
  786. audioStore.isPaused = false;
  787. console.log(`${audioInfo.name}音频继续播放`);
  788. },
  789. onend: () => {
  790. console.log(`${audioInfo.name}音频播放完成,准备播放下一个`);
  791. console.log('播放完成时的状态 - 当前索引:', currentPlayIndex, '队列长度:', audioQueue.value.length, 'isPlayingAudio:', isPlayingAudio.value);
  792. audioStore.isPlaying = false;
  793. audioStore.isPaused = false;
  794. audioStore.playbackPosition = 0;
  795. isPlayingAudio.value = false;
  796. // 移动到下一个音频索引
  797. currentPlayIndex++;
  798. // 确保只有在音频真正播放完成时才播放下一个
  799. if (currentPlayIndex < audioQueue.value.length) {
  800. console.log(`队列中还有音频,500ms后播放下一个 (索引:${currentPlayIndex}),当前队列:`, audioQueue.value.map((item, index) => `${index === currentPlayIndex ? '▶️' : '⏸️'} ${item.name}`));
  801. setTimeout(() => {
  802. isCallingPlayNext = false; // 在调用前重置标志
  803. playNextAudio();
  804. }, 500);
  805. } else {
  806. console.log('🎉 所有音频播放完成,清除音频实例,队列保持完整,下次播放将从第一个开始');
  807. // 清除音频实例,确保重新播放时不会使用旧实例
  808. audioStore.nowSound = null;
  809. audioStore.soundInstance = null;
  810. isCallingPlayNext = false; // 重置调用标志
  811. }
  812. },
  813. onstop: () => {
  814. console.log(`${audioInfo.name}音频被停止`);
  815. audioStore.isPlaying = false;
  816. audioStore.isPaused = false;
  817. audioStore.playbackPosition = 0;
  818. // 注意:不要在onstop中设置isPlayingAudio.value = false,因为这可能是正常的音频切换
  819. },
  820. onloaderror: (id, err) => {
  821. console.error(`${audioInfo.name}音频播放失败:`, err);
  822. isPlayingAudio.value = false;
  823. isCallingPlayNext = false; // 重置调用标志
  824. // 播放下一个音频
  825. setTimeout(() => {
  826. playNextAudio();
  827. }, 100);
  828. }
  829. });
  830. // 设置当前音频URL到store
  831. audioStore.setCurrentAudioUrl(audioInfo.url);
  832. audioStore.nowSound = audio;
  833. audioStore.setAudioInstance(audio);
  834. console.log(`尝试播放${audioInfo.name}音频`);
  835. audio.play();
  836. };
  837. // 音频队列顺序管理
  838. const audioQueueOrder = {
  839. 'API1-第一个': 1,
  840. 'API2-第二个': 2,
  841. 'API3-第三个': 3,
  842. 'API4-第四个': 4
  843. };
  844. // 添加音频到播放队列(确保顺序)
  845. const addToAudioQueue = (url, name) => {
  846. console.log(`=== 添加音频到队列 ===`);
  847. console.log('URL:', url);
  848. console.log('Name:', name);
  849. console.log('音频启用状态:', audioStore.isVoiceEnabled);
  850. if (url && audioStore.isVoiceEnabled) {
  851. const audioItem = { url, name, order: audioQueueOrder[name] || 999 };
  852. audioQueue.value.push(audioItem);
  853. // 按顺序排序队列
  854. audioQueue.value.sort((a, b) => a.order - b.order);
  855. console.log(`音频${name}已添加到播放队列,顺序:${audioItem.order}`);
  856. console.log('当前队列顺序:', audioQueue.value.map(item => `${item.name}(${item.order})`));
  857. console.log('当前播放状态详情:');
  858. console.log(' - isPlayingAudio:', isPlayingAudio.value);
  859. console.log(' - audioStore.isPlaying:', audioStore.isPlaying);
  860. console.log(' - audioStore.nowSound:', audioStore.nowSound);
  861. console.log(' - isCallingPlayNext:', isCallingPlayNext);
  862. console.log(' - 队列长度:', audioQueue.value.length);
  863. // 只有在确实没有音频在播放且这是第一个音频时才开始播放
  864. if (!isPlayingAudio.value && !audioStore.isPlaying && audioQueue.value.length === 1) {
  865. console.log('✅ 条件满足:没有音频在播放且这是第一个音频,立即开始播放');
  866. playNextAudio();
  867. } else {
  868. console.log('⏳ 等待条件:', {
  869. isPlayingAudio: isPlayingAudio.value,
  870. audioStoreIsPlaying: audioStore.isPlaying,
  871. queueLength: audioQueue.value.length,
  872. reason: audioQueue.value.length > 1 ? '队列中已有其他音频' : '有音频正在播放'
  873. });
  874. }
  875. } else {
  876. console.log('❌ 跳过添加音频:', {
  877. hasUrl: !!url,
  878. voiceEnabled: audioStore.isVoiceEnabled
  879. });
  880. }
  881. console.log(`=== 添加音频完成 ===`);
  882. };
  883. // 重写audioStore的togglePlayPause方法以支持队列播放控制
  884. const originalTogglePlayPause = audioStore.togglePlayPause;
  885. audioStore.togglePlayPause = () => {
  886. console.log('主页音频控制按钮被点击');
  887. console.log('当前音频状态 - isPlaying:', audioStore.isPlaying, 'isPaused:', audioStore.isPaused);
  888. console.log('当前音频实例:', audioStore.soundInstance);
  889. console.log('队列播放状态 - isPlayingAudio:', isPlayingAudio.value, '队列长度:', audioQueue.value.length);
  890. console.log('当前播放索引:', currentPlayIndex, '是否所有音频播放完成:', currentPlayIndex >= audioQueue.value.length);
  891. // 检查是否所有音频都播放完成,如果是则重新开始
  892. if (audioStore.soundInstance && currentPlayIndex < audioQueue.value.length && isPlayingAudio.value) {
  893. if (audioStore.isPlaying) {
  894. // 暂停当前音频
  895. console.log('暂停当前音频');
  896. audioStore.soundInstance.pause();
  897. } else if (audioStore.isPaused && audioStore.playbackPosition > 0) {
  898. // 从暂停位置继续播放
  899. console.log('从暂停位置继续播放音频,位置:', audioStore.playbackPosition);
  900. audioStore.soundInstance.seek(audioStore.playbackPosition);
  901. audioStore.soundInstance.play();
  902. } else {
  903. // 重新开始播放
  904. console.log('重新开始播放音频');
  905. audioStore.soundInstance.play();
  906. }
  907. } else {
  908. console.log('没有音频实例,检查是否需要重新播放队列');
  909. console.log('重新播放条件检查:', {
  910. isPlayingAudio: isPlayingAudio.value,
  911. queueLength: audioQueue.value.length,
  912. currentPlayIndex: currentPlayIndex,
  913. condition1: audioQueue.value.length === 0,
  914. condition2: currentPlayIndex >= audioQueue.value.length,
  915. finalCondition: !isPlayingAudio.value && (audioQueue.value.length === 0 || currentPlayIndex >= audioQueue.value.length)
  916. });
  917. // 没有音频实例时,检查是否所有音频都播放完成
  918. if (!isPlayingAudio.value && (audioQueue.value.length === 0 || currentPlayIndex >= audioQueue.value.length)) {
  919. console.log('所有音频播放完成,重新构建队列从第一个开始播放');
  920. // 重新构建音频队列,按顺序添加所有音频(不自动播放)
  921. const audioItems = [];
  922. if (audioPreloadStatus.one.url) {
  923. audioItems.push({ url: audioPreloadStatus.one.url, name: "API1-第一个", order: 1 });
  924. }
  925. if (audioPreloadStatus.two.url) {
  926. audioItems.push({ url: audioPreloadStatus.two.url, name: "API2-第二个", order: 2 });
  927. }
  928. if (audioPreloadStatus.three.url) {
  929. audioItems.push({ url: audioPreloadStatus.three.url, name: "API3-第三个", order: 3 });
  930. }
  931. if (audioPreloadStatus.four.url) {
  932. audioItems.push({ url: audioPreloadStatus.four.url, name: "API4-第四个", order: 4 });
  933. }
  934. // 按顺序排序并添加到队列
  935. audioItems.sort((a, b) => a.order - b.order);
  936. audioQueue.value = audioItems;
  937. console.log('队列重建完成,队列内容:', audioQueue.value.map(item => item.name));
  938. console.log('开始从第一个音频播放');
  939. // 重置播放状态并开始播放第一个音频
  940. if (audioQueue.value.length > 0) {
  941. // 完全重置所有播放相关状态
  942. isPlayingAudio.value = false;
  943. isCallingPlayNext = false;
  944. currentPlayIndex = 0; // 重置播放索引到第一个音频
  945. audioStore.isPlaying = false;
  946. audioStore.isPaused = false;
  947. audioStore.playbackPosition = 0;
  948. audioStore.nowSound = null;
  949. audioStore.soundInstance = null;
  950. console.log('🔄 状态完全重置完成,准备从第一个音频开始播放');
  951. console.log('✅ 完全重置播放状态,准备播放第一个音频');
  952. console.log('重置后状态检查:', {
  953. isPlayingAudio: isPlayingAudio.value,
  954. isCallingPlayNext: isCallingPlayNext,
  955. currentPlayIndex: currentPlayIndex,
  956. audioStoreIsPlaying: audioStore.isPlaying,
  957. queueLength: audioQueue.value.length
  958. });
  959. setTimeout(() => {
  960. console.log('🚀 延迟后开始播放第一个音频');
  961. console.log('播放前最终状态检查:', {
  962. currentPlayIndex: currentPlayIndex,
  963. queueLength: audioQueue.value.length,
  964. queueItems: audioQueue.value.map((item, index) => `${index}: ${item.name}`),
  965. isPlayingAudio: isPlayingAudio.value,
  966. isCallingPlayNext: isCallingPlayNext,
  967. audioStoreIsPlaying: audioStore.isPlaying,
  968. audioStoreInstance: !!audioStore.soundInstance
  969. });
  970. console.log('🎯 即将调用 playNextAudio,期望播放:', audioQueue.value[currentPlayIndex]?.name || '无音频');
  971. playNextAudio();
  972. }, 200);
  973. }
  974. } else if (!isPlayingAudio.value && audioQueue.value.length > 0) {
  975. console.log('队列中还有音频,继续播放');
  976. playNextAudio();
  977. } else {
  978. console.log('无法播放 - isPlayingAudio:', isPlayingAudio.value, '队列长度:', audioQueue.value.length);
  979. }
  980. }
  981. };
  982. // 预加载音频函数
  983. const preloadAudio = (url, apiKey) => {
  984. if (!url || !audioStore.isVoiceEnabled) {
  985. audioPreloadStatus[apiKey].loaded = true;
  986. return Promise.resolve();
  987. }
  988. // 立即设置URL,确保即使预加载失败也能使用
  989. audioPreloadStatus[apiKey].url = url;
  990. console.log(`设置音频${apiKey}的URL:`, url);
  991. return new Promise((resolve) => {
  992. const audio = new Howl({
  993. src: [url],
  994. html5: true,
  995. format: ["mp3", "acc"],
  996. rate: 1.2,
  997. preload: true,
  998. onload: () => {
  999. console.log(`音频${apiKey}预加载完成:`, url);
  1000. audioPreloadStatus[apiKey].loaded = true;
  1001. resolve();
  1002. },
  1003. onloaderror: (id, err) => {
  1004. console.error(`音频${apiKey}预加载失败:`, err);
  1005. audioPreloadStatus[apiKey].loaded = true; // 标记为已处理,避免阻塞
  1006. // URL已经在上面设置了,即使预加载失败也保留URL
  1007. resolve();
  1008. }
  1009. });
  1010. });
  1011. };
  1012. // 检查第一个接口是否可以开始输出(文本和音频都准备好)
  1013. const canStartFirstOutput = () => {
  1014. return apiStatus.one.completed && audioPreloadStatus.one.loaded;
  1015. };
  1016. // 检查并按顺序执行代码的函数
  1017. const checkAndExecuteInOrder = () => {
  1018. // 检查OneAPI - 只有当文本和音频都准备好时才开始输出
  1019. if (canStartFirstOutput() && !apiStatus.one.executed) {
  1020. if (apiStatus.one.result) {
  1021. apiStatus.one.executed = true;
  1022. console.log("执行OneAPI代码(文本和音频同步开始):", apiStatus.one.result);
  1023. // 将第一个音频添加到播放队列(确保顺序:API1)
  1024. if (audioPreloadStatus.one.url) {
  1025. addToAudioQueue(audioPreloadStatus.one.url, "API1-第一个");
  1026. console.log("音频队列:添加API1音频,当前队列长度:", audioQueue.value.length);
  1027. }
  1028. // 在这里添加OneAPI成功后需要执行的代码
  1029. // 删除正在为您生成信息
  1030. chatStore.messages.pop();
  1031. // 添加报告头和时间
  1032. addTypingTask(
  1033. {
  1034. sender: "ai",
  1035. class: "title1",
  1036. type: "title1",
  1037. content: codeData.value.name + "全景作战报告",
  1038. date: result21.data.date,
  1039. },
  1040. "",
  1041. 50
  1042. );
  1043. // chatStore.messages.push({
  1044. // sender: "ai",
  1045. // class: "title1",
  1046. // type: "title1",
  1047. // content: codeData.value.name + "全景作战报告",
  1048. // date: moment().format("MM/DD/YYYY"),
  1049. // });
  1050. // 添加股票信息框
  1051. const pc1 = marked(
  1052. result21.data.name +
  1053. "\n" +
  1054. result21.data.price +
  1055. "\n" +
  1056. result21.data.date
  1057. );
  1058. const ac1 = pc1.replace(katexRegex, (match, formula) => {
  1059. try {
  1060. return katex.renderToString(formula, {
  1061. throwOnError: false,
  1062. });
  1063. } catch (error) {
  1064. console.error("KaTeX 渲染错误:", error);
  1065. return match;
  1066. }
  1067. });
  1068. // 先推送初始消息
  1069. const aiMessage1 = reactive({
  1070. sender: "ai",
  1071. class: "content1",
  1072. type: "content1",
  1073. content: "",
  1074. isTyping: true,
  1075. });
  1076. // chatStore.messages.push(aiMessage1);
  1077. // let index1 = 0;
  1078. // const typingInterval1 = setInterval(() => {
  1079. // if (index1 < ac1.length) {
  1080. // aiMessage1.content += ac1.charAt(index1);
  1081. // index1++;
  1082. // } else {
  1083. // clearInterval(typingInterval1);
  1084. // aiMessage1.isTyping = false;
  1085. // }
  1086. // }, 50); // 调整速度为50ms/字符
  1087. addTypingTask(aiMessage1, ["", ac1], 130);
  1088. // chatStore.messages.push({
  1089. // sender: "ai",
  1090. // class: "content1",
  1091. // type: "content1",
  1092. // content: ac1,
  1093. // });
  1094. // 添加六色罗盘
  1095. const LiuSeData = JSON.parse(JSON.stringify(toRaw(HomePage)));
  1096. const sz = fnGetData(LiuSeData);
  1097. if (sz) {
  1098. hasValidData.value = true;
  1099. console.log("hasValidData设置为:", hasValidData.value);
  1100. }
  1101. // 先推送K线图消息
  1102. const klineMessageId1 = `kline-${Date.now()}`;
  1103. console.log("生成K线消息ID:", klineMessageId1);
  1104. addTypingTask(
  1105. {
  1106. sender: "ai",
  1107. class: "content1",
  1108. type: "content1",
  1109. kline: true,
  1110. chartData: sz,
  1111. messageId: klineMessageId1,
  1112. hasValidData: true,
  1113. klineType: 1,
  1114. },
  1115. "",
  1116. 50
  1117. );
  1118. // chatStore.messages.push({
  1119. // sender: "ai",
  1120. // class: "content1",
  1121. // type: "content1",
  1122. // kline: true,
  1123. // chartData: sz,
  1124. // messageId: klineMessageId1,
  1125. // hasValidData: true,
  1126. // klineType: 1,
  1127. // });
  1128. // console.log("六色罗盘消息已添加到聊天列表");
  1129. // // 在渲染完成后初始化图表
  1130. // nextTick(() => {
  1131. // console.log("nextTick开始 - 准备渲染图表");
  1132. // console.log("消息列表:", chatStore.messages);
  1133. // // 寻找最新添加的K线消息索引
  1134. // let klineIndex = -1;
  1135. // for (let i = 0; i < chatStore.messages.length; i++) {
  1136. // if (chatStore.messages[i].messageId === klineMessageId1) {
  1137. // klineIndex = i;
  1138. // break;
  1139. // }
  1140. // }
  1141. // console.log("找到的K线消息索引:", klineIndex);
  1142. // if (klineIndex !== -1) {
  1143. // const containerId = `kline-container-${klineIndex}`;
  1144. // console.log("图表容器ID:", containerId);
  1145. // // 确保DOM已经渲染完成
  1146. // setTimeout(() => {
  1147. // console.log("延时执行,确保DOM已渲染");
  1148. // KlineCanvsEcharts(containerId);
  1149. // }, 100); // 短暂延时确保DOM已渲染
  1150. // } else {
  1151. // console.warn("未找到K线消息");
  1152. // }
  1153. // });
  1154. // 度牛尺K线图
  1155. const AIGoldBullData = JSON.parse(
  1156. JSON.stringify(toRaw(AIGoldBull))
  1157. );
  1158. const HomePageData = JSON.parse(
  1159. JSON.stringify(toRaw(HomePage))
  1160. );
  1161. console.log("处理 K 线数据 - 开始");
  1162. console.log("AIGoldBullData", AIGoldBullData);
  1163. console.log("HomePageData", HomePageData);
  1164. const Kline20 = {
  1165. name: HomePageData.StockInformation.Name,
  1166. Kline: AIGoldBullData,
  1167. };
  1168. // 打印K线数据结构
  1169. console.log("K线数据结构:", Kline20);
  1170. console.log("K线数据名称:", Kline20.name);
  1171. console.log("K线数据:", Kline20.Kline ? Kline20.Kline : null);
  1172. // 设置数据有效标志
  1173. hasValidData.value = true;
  1174. console.log("hasValidData设置为:", hasValidData.value);
  1175. // chatStore.messages.pop();
  1176. // 先推送K线图消息
  1177. const klineMessageId2 = `kline-${Date.now() + 1}`;
  1178. console.log("生成K线消息ID:", klineMessageId2);
  1179. // chatStore.messages.push({
  1180. // sender: "ai",
  1181. // class: "content2",
  1182. // type: "content2",
  1183. // kline: true,
  1184. // chartData: Kline20,
  1185. // messageId: klineMessageId2,
  1186. // hasValidData: true, // 添加hasValidData标志
  1187. // klineType: 2,
  1188. // });
  1189. addTypingTask(
  1190. {
  1191. sender: "ai",
  1192. class: "content2",
  1193. type: "content2",
  1194. kline: true,
  1195. chartData: Kline20,
  1196. messageId: klineMessageId2,
  1197. hasValidData: true, // 添加hasValidData标志
  1198. klineType: 2,
  1199. error: apiStatus.two.error ? '2' : '',
  1200. isEnd: '1',
  1201. },
  1202. "",
  1203. 50
  1204. );
  1205. // console.log("K线消息已添加到聊天列表");
  1206. // // 在渲染完成后初始化图表
  1207. // nextTick(() => {
  1208. // console.log("nextTick开始 - 准备渲染图表");
  1209. // console.log("消息列表:", chatStore.messages);
  1210. // // 寻找最新添加的K线消息索引
  1211. // let klineIndex = -1;
  1212. // for (let i = 0; i < chatStore.messages.length; i++) {
  1213. // if (chatStore.messages[i].messageId === klineMessageId2) {
  1214. // klineIndex = i;
  1215. // break;
  1216. // }
  1217. // }
  1218. // console.log("找到的K线消息索引:", klineIndex);
  1219. // if (klineIndex !== -1) {
  1220. // const containerId = `kline-container-${klineIndex}`;
  1221. // console.log("图表容器ID:", containerId);
  1222. // // 确保DOM已经渲染完成
  1223. // setTimeout(() => {
  1224. // console.log("延时执行,确保DOM已渲染");
  1225. // KlineCanvsEcharts(containerId);
  1226. // }, 100); // 短暂延时确保DOM已渲染
  1227. // } else {
  1228. // console.warn("未找到K线消息");
  1229. // }
  1230. // });
  1231. } else {
  1232. chatStore.messages.push({
  1233. class: "ing",
  1234. type: "ing",
  1235. flag: false,
  1236. content: '工作流返回出错,请稍后重试',
  1237. })
  1238. chatStore.isLoading = false;
  1239. }
  1240. }
  1241. // 检查TwoAPI(需要OneAPI已执行)
  1242. if (
  1243. apiStatus.one.executed &&
  1244. apiStatus.two.completed &&
  1245. !apiStatus.two.executed
  1246. ) {
  1247. if (apiStatus.two.result) {
  1248. apiStatus.two.executed = true;
  1249. console.log("执行TwoAPI代码:", apiStatus.two.result);
  1250. // 将第二个音频添加到播放队列(确保顺序:API2)
  1251. if (audioPreloadStatus.two.url) {
  1252. addToAudioQueue(audioPreloadStatus.two.url, "API2-第二个");
  1253. console.log("音频队列:添加API2音频,当前队列长度:", audioQueue.value.length);
  1254. }
  1255. // 在这里添加TwoAPI成功后需要执行的代码
  1256. // 添加标题2
  1257. addTypingTask(
  1258. {
  1259. sender: "ai",
  1260. class: "title2",
  1261. type: "title2",
  1262. content: "",
  1263. },
  1264. "",
  1265. 50
  1266. );
  1267. // chatStore.messages.push({
  1268. // sender: "ai",
  1269. // class: "title2",
  1270. // type: "title2",
  1271. // content: "",
  1272. // });
  1273. // 添加内容框1
  1274. const pc2 = marked(result22.data.hxjzpg);
  1275. console.log(pc2, "pc2");
  1276. const ac2 = pc2.replace(katexRegex, (match, formula) => {
  1277. try {
  1278. return katex.renderToString(formula, {
  1279. throwOnError: false,
  1280. });
  1281. } catch (error) {
  1282. console.error("KaTeX 渲染错误:", error);
  1283. return match;
  1284. }
  1285. });
  1286. // 先推送初始消息
  1287. const aiMessage2 = reactive({
  1288. sender: "ai",
  1289. class: "content3",
  1290. type: "content3",
  1291. content: "",
  1292. isTyping: true,
  1293. error: apiStatus.three.error ? '3' : '',
  1294. isEnd: '2'
  1295. });
  1296. // chatStore.messages.push(aiMessage2);
  1297. // let index2 = 0;
  1298. // const typingInterval2 = setInterval(() => {
  1299. // if (index2 < ac2.length) {
  1300. // aiMessage2.content += ac2.charAt(index2);
  1301. // index2++;
  1302. // } else {
  1303. // clearInterval(typingInterval2);
  1304. // aiMessage2.isTyping = false;
  1305. // }
  1306. // }, 50); // 调整速度为50ms/字符
  1307. addTypingTask(aiMessage2, ["", ac2], 130);
  1308. // chatStore.messages.push({
  1309. // sender: "ai",
  1310. // class: "content3",
  1311. // type: "content3",
  1312. // content: ac2,
  1313. // });
  1314. } else {
  1315. console.log('1111111111111111111')
  1316. if (apiStatus.one.isEnd && apiStatus.two.error && !apiStatus.two.isError) {
  1317. apiStatus.two.isError = true
  1318. chatStore.messages.push({
  1319. class: "ing",
  1320. type: "ing",
  1321. flag: false,
  1322. content: '工作流返回出错,请稍后重试',
  1323. })
  1324. chatStore.isLoading = false;
  1325. }
  1326. }
  1327. }
  1328. // 检查ThreeAPI(需要TwoAPI已执行)
  1329. if (
  1330. apiStatus.two.executed &&
  1331. apiStatus.three.completed &&
  1332. !apiStatus.three.executed
  1333. ) {
  1334. if (apiStatus.three.result) {
  1335. apiStatus.three.executed = true;
  1336. console.log("执行ThreeAPI代码:", apiStatus.three.result);
  1337. // 将第三个音频添加到播放队列(确保顺序:API3)
  1338. if (audioPreloadStatus.three.url) {
  1339. addToAudioQueue(audioPreloadStatus.three.url, "API3-第三个");
  1340. console.log("音频队列:添加API3音频,当前队列长度:", audioQueue.value.length);
  1341. }
  1342. // 在这里添加ThreeAPI成功后需要执行的代码
  1343. // 添加标题3-2
  1344. addTypingTask(
  1345. {
  1346. sender: "ai",
  1347. class: "title3",
  1348. type: "title3",
  1349. content: title2,
  1350. },
  1351. "",
  1352. 50
  1353. );
  1354. // chatStore.messages.push({
  1355. // sender: "ai",
  1356. // class: "title3",
  1357. // type: "title3",
  1358. // content: title2,
  1359. // });
  1360. // 添加内容框2
  1361. // const pc3 = marked(result23.data.zhuli1+'\n'+result23.data.zhuli2+'\n'+result23.data.zhuli3);
  1362. // const ac3 = pc3.replace(
  1363. // katexRegex,
  1364. // (match, formula) => {
  1365. // try {
  1366. // return katex.renderToString(formula, { throwOnError: false });
  1367. // } catch (error) {
  1368. // console.error("KaTeX 渲染错误:", error);
  1369. // return match;
  1370. // }
  1371. // }
  1372. // );
  1373. const ac31 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【主力行为】</p><p>`;
  1374. const ac32 = `${result23.data.zhuli1}</p><p>${result23.data.zhuli2}</p><p>${result23.data.zhuli3}</p>`;
  1375. const ac3 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【主力行为】</p><p>${result23.data.zhuli1}</p><p>${result23.data.zhuli2}</p><p>${result23.data.zhuli3}</p>`;
  1376. // 先推送初始消息
  1377. const aiMessage3 = reactive({
  1378. sender: "ai",
  1379. class: "content3",
  1380. type: "content3",
  1381. content: "",
  1382. isTyping: true,
  1383. });
  1384. // chatStore.messages.push(aiMessage3);
  1385. // let index3 = 0;
  1386. // const typingInterval3 = setInterval(() => {
  1387. // if (index3 < ac3.length) {
  1388. // aiMessage3.content += ac3.charAt(index3);
  1389. // index3++;
  1390. // } else {
  1391. // clearInterval(typingInterval3);
  1392. // aiMessage3.isTyping = false;
  1393. // }
  1394. // }, 50); // 调整速度为50ms/字符
  1395. addTypingTask(aiMessage3, [ac31, ac32], 200);
  1396. // chatStore.messages.push({
  1397. // sender: "ai",
  1398. // class: "content3",
  1399. // type: "content3",
  1400. // content: ac3,
  1401. // });
  1402. // 添加标题3-3
  1403. addTypingTask(
  1404. {
  1405. sender: "ai",
  1406. class: "title3",
  1407. type: "title3",
  1408. content: title3,
  1409. },
  1410. "",
  1411. 50
  1412. );
  1413. // chatStore.messages.push({
  1414. // sender: "ai",
  1415. // class: "title3",
  1416. // type: "title3",
  1417. // content: title3,
  1418. // });
  1419. // 添加内容框3
  1420. const arr = result23.data.kongjian.split(",");
  1421. const kongjian = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【空间维度】</p><p style="display:flex;justify-content:center;">${arr[0]},${arr[1]}</p><p style="display:flex;justify-content:center;">${arr[2]},${arr[3]}</p>`;
  1422. const shijian = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【时间维度】</p><p style="display:flex;justify-content:center;">${result23.data.shijian}</p>`;
  1423. const nengliang = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【能量维度】</p><p>${result23.data.nengliang}</p>`;
  1424. const ac4 = kongjian + shijian + nengliang;
  1425. const ac41 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【空间维度】</p><p style="display:flex;justify-content:center;">`;
  1426. const ac42 = `${arr[0]},${arr[1]}`;
  1427. const ac43 = `</p><p style="display:flex;justify-content:center;">`;
  1428. const ac44 = `${arr[2]},${arr[3]}</p>`;
  1429. const ac45 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【时间维度】</p><p style="display:flex;justify-content:center;">`;
  1430. const ac46 = `${result23.data.shijian}</p>`;
  1431. const ac47 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【能量维度】</p><p>`;
  1432. const ac48 = `${result23.data.nengliang}</p>`;
  1433. // const pc4 = marked(
  1434. // kongjian +
  1435. // "\n" +
  1436. // result23.data.shijian +
  1437. // "\n" +
  1438. // result23.data.nengliang
  1439. // );
  1440. // const ac4 = pc4.replace(katexRegex, (match, formula) => {
  1441. // try {
  1442. // return katex.renderToString(formula, { throwOnError: false });
  1443. // } catch (error) {
  1444. // console.error("KaTeX 渲染错误:", error);
  1445. // return match;
  1446. // }
  1447. // });
  1448. // 先推送初始消息
  1449. const aiMessage4 = reactive({
  1450. sender: "ai",
  1451. class: "content3",
  1452. type: "content3",
  1453. content: "",
  1454. isTyping: true,
  1455. error: apiStatus.four.error ? '4' : '',
  1456. isEnd: '3'
  1457. });
  1458. // chatStore.messages.push(aiMessage4);
  1459. // let index4 = 0;
  1460. // const typingInterval4 = setInterval(() => {
  1461. // if (index4 < ac4.length) {
  1462. // aiMessage4.content += ac4.charAt(index4);
  1463. // index4++;
  1464. // } else {
  1465. // clearInterval(typingInterval4);
  1466. // aiMessage4.isTyping = false;
  1467. // }
  1468. // }, 50); // 调整速度为50ms/字符
  1469. addTypingTask(
  1470. aiMessage4,
  1471. [ac41, ac42, ac43, ac44, ac45, ac46, ac47, ac48],
  1472. 200
  1473. );
  1474. // chatStore.messages.push({
  1475. // sender: "ai",
  1476. // class: "content3",
  1477. // type: "content3",
  1478. // content: ac4,
  1479. // });
  1480. } else {
  1481. if (apiStatus.two.isEnd && apiStatus.three.error && !apiStatus.three.isError) {
  1482. apiStatus.three.isError = true;
  1483. chatStore.messages.push({
  1484. class: "ing",
  1485. type: "ing",
  1486. flag: false,
  1487. content: '工作流返回出错,请稍后重试1111',
  1488. })
  1489. chatStore.isLoading = false;
  1490. }
  1491. }
  1492. }
  1493. // 检查FourAPI(需要ThreeAPI已执行)
  1494. if (
  1495. apiStatus.three.executed &&
  1496. apiStatus.four.completed &&
  1497. !apiStatus.four.executed
  1498. ) {
  1499. if (apiStatus.four.result) {
  1500. apiStatus.four.executed = true;
  1501. console.log("执行FourAPI代码:", apiStatus.four.result);
  1502. // 将第四个音频添加到播放队列(确保顺序:API4)
  1503. if (audioPreloadStatus.four.url) {
  1504. addToAudioQueue(audioPreloadStatus.four.url, "API4-第四个");
  1505. console.log("音频队列:添加API4音频,当前队列长度:", audioQueue.value.length);
  1506. }
  1507. // 在这里添加FourAPI成功后需要执行的代码
  1508. // 添加标题3-4
  1509. addTypingTask(
  1510. {
  1511. sender: "ai",
  1512. class: "title3",
  1513. type: "title3",
  1514. content: title4,
  1515. },
  1516. "",
  1517. 50
  1518. );
  1519. // chatStore.messages.push({
  1520. // sender: "ai",
  1521. // class: "title3",
  1522. // type: "title3",
  1523. // content: title4,
  1524. // });
  1525. // 添加内容框4
  1526. const cftj = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【触发条件】</p><p>${result24.data.cftl}</p>`;
  1527. const gfzl = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【攻防指令】</p><p>${result24.data.gfzl}</p>`;
  1528. const ac5 = cftj + gfzl;
  1529. const ac51 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【触发条件】</p><p>`;
  1530. const ac52 = `${result24.data.cftl}</p>`;
  1531. const ac53 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【攻防指令】</p><p>`;
  1532. const ac54 = `${result24.data.gfzl}</p>`;
  1533. // const pc5 = marked(result24.data.cftl + "/n" + result24.data.gfzl);
  1534. // const ac5 = pc5.replace(katexRegex, (match, formula) => {
  1535. // try {
  1536. // return katex.renderToString(formula, { throwOnError: false });
  1537. // } catch (error) {
  1538. // console.error("KaTeX 渲染错误:", error);
  1539. // return match;
  1540. // }
  1541. // });
  1542. // 先推送初始消息
  1543. const aiMessage5 = reactive({
  1544. sender: "ai",
  1545. class: "content3",
  1546. type: "content3",
  1547. content: "",
  1548. isTyping: true,
  1549. });
  1550. // chatStore.messages.push(aiMessage5);
  1551. // let index5 = 0;
  1552. // const typingInterval5 = setInterval(() => {
  1553. // if (index5 < ac5.length) {
  1554. // aiMessage5.content += ac5.charAt(index5);
  1555. // index5++;
  1556. // } else {
  1557. // clearInterval(typingInterval5);
  1558. // aiMessage5.isTyping = false;
  1559. // }
  1560. // }, 50); // 调整速度为50ms/字符
  1561. addTypingTask(aiMessage5, [ac51, ac52, ac53, ac54], 240);
  1562. // chatStore.messages.push({
  1563. // sender: "ai",
  1564. // class: "content3",
  1565. // type: "content3",
  1566. // content: ac5,
  1567. // });
  1568. const ac6 = "该内容由AI生成,请注意甄别";
  1569. // 先推送初始消息
  1570. const aiMessage6 = reactive({
  1571. sender: "ai",
  1572. class: "mianze",
  1573. type: "mianze",
  1574. content: "",
  1575. isTyping: true,
  1576. end: true
  1577. });
  1578. // chatStore.messages.push(aiMessage6);
  1579. // let index6 = 0;
  1580. // const typingInterval6 = setInterval(() => {
  1581. // if (index6 < ac6.length) {
  1582. // aiMessage6.content += ac6.charAt(index6);
  1583. // index6++;
  1584. // } else {
  1585. // clearInterval(typingInterval6);
  1586. // aiMessage6.isTyping = false;
  1587. // }
  1588. // }, 50); // 调整速度为50ms/字符
  1589. addTypingTask(aiMessage6, ["", ac6], 210);
  1590. // chatStore.isLoading = false;
  1591. // chatStore.messages.push({
  1592. // sender: "ai",
  1593. // class: "mianze",
  1594. // type: "mianze",
  1595. // content: "内容由AI生成,请注意甄别",
  1596. // });
  1597. } else {
  1598. if (apiStatus.three.isEnd && apiStatus.four.error && !apiStatus.four.isError) {
  1599. apiStatus.four.isError = true;
  1600. chatStore.messages.push({
  1601. class: "ing",
  1602. type: "ing",
  1603. flag: false,
  1604. content: '工作流返回出错,请稍后重试',
  1605. })
  1606. chatStore.isLoading = false;
  1607. }
  1608. }
  1609. }
  1610. // 检查是否所有API都已完成并执行
  1611. if (
  1612. apiStatus.one.completed &&
  1613. apiStatus.two.completed &&
  1614. apiStatus.three.completed &&
  1615. apiStatus.four.completed &&
  1616. apiStatus.four.executed
  1617. ) {
  1618. console.log("所有API已完成,开始收集预加载的音频URL");
  1619. // 收集所有预加载的音频URL
  1620. const audioUrls = [];
  1621. console.log("预加载音频状态检查:");
  1622. console.log("audioPreloadStatus:", audioPreloadStatus);
  1623. if (audioPreloadStatus.one.url) {
  1624. console.log("添加预加载音频URL one:", audioPreloadStatus.one.url);
  1625. audioUrls.push(audioPreloadStatus.one.url);
  1626. }
  1627. if (audioPreloadStatus.two.url) {
  1628. console.log("添加预加载音频URL two:", audioPreloadStatus.two.url);
  1629. audioUrls.push(audioPreloadStatus.two.url);
  1630. }
  1631. if (audioPreloadStatus.three.url) {
  1632. console.log("添加预加载音频URL three:", audioPreloadStatus.three.url);
  1633. audioUrls.push(audioPreloadStatus.three.url);
  1634. }
  1635. if (audioPreloadStatus.four.url) {
  1636. console.log("添加预加载音频URL four:", audioPreloadStatus.four.url);
  1637. audioUrls.push(audioPreloadStatus.four.url);
  1638. }
  1639. console.log("收集到的预加载音频URLs:", audioUrls);
  1640. console.log("语音是否启用:", audioStore.isVoiceEnabled);
  1641. // 音频播放逻辑已移至各个接口的执行代码中
  1642. console.log("所有接口执行完成,音频已在各接口中单独播放");
  1643. }
  1644. };
  1645. const handleOneAPI = async () => {
  1646. try {
  1647. result21 = await dbqbSecondOneAPI(params2);
  1648. console.log("OneAPI成功返回:", result21);
  1649. apiStatus.one.completed = true;
  1650. apiStatus.one.result = result21;
  1651. // 预加载第一个接口的音频
  1652. if (result21?.data?.url) {
  1653. await preloadAudio(result21.data.url.trim(), 'one');
  1654. } else {
  1655. audioPreloadStatus.one.loaded = true;
  1656. }
  1657. // 检查是否可以执行
  1658. checkAndExecuteInOrder();
  1659. } catch (error) {
  1660. console.error("OneAPI失败:", error);
  1661. apiStatus.one.completed = true;
  1662. apiStatus.one.error = error;
  1663. audioPreloadStatus.one.loaded = true; // 失败时也标记为已处理
  1664. // 即使失败也要检查后续执行
  1665. checkAndExecuteInOrder();
  1666. }
  1667. };
  1668. const handleTwoAPI = async () => {
  1669. try {
  1670. result22 = await dbqbSecondTwoAPI(params2);
  1671. console.log("TwoAPI成功返回:", result22);
  1672. apiStatus.two.completed = true;
  1673. apiStatus.two.result = result22;
  1674. // 预加载第二个接口的音频
  1675. if (result22?.data?.url) {
  1676. await preloadAudio(result22.data.url.trim(), 'two');
  1677. } else {
  1678. audioPreloadStatus.two.loaded = true;
  1679. }
  1680. // 检查是否可以执行
  1681. checkAndExecuteInOrder();
  1682. } catch (error) {
  1683. console.error("TwoAPI失败:", error);
  1684. apiStatus.two.completed = true;
  1685. apiStatus.two.error = error;
  1686. audioPreloadStatus.two.loaded = true;
  1687. checkAndExecuteInOrder();
  1688. }
  1689. };
  1690. const handleThreeAPI = async () => {
  1691. try {
  1692. result23 = await dbqbSecondThreeAPI(params2);
  1693. // result23 = await dbqbSecondThreeAPI();
  1694. console.log("ThreeAPI成功返回:", result23);
  1695. apiStatus.three.completed = true;
  1696. apiStatus.three.result = result23;
  1697. // 预加载第三个接口的音频
  1698. if (result23?.data?.url) {
  1699. await preloadAudio(result23.data.url.trim(), 'three');
  1700. } else {
  1701. audioPreloadStatus.three.loaded = true;
  1702. }
  1703. // 检查是否可以执行
  1704. checkAndExecuteInOrder();
  1705. } catch (error) {
  1706. console.error("ThreeAPI失败:", error);
  1707. apiStatus.three.completed = true;
  1708. apiStatus.three.error = error;
  1709. audioPreloadStatus.three.loaded = true;
  1710. checkAndExecuteInOrder();
  1711. }
  1712. };
  1713. const handleFourAPI = async () => {
  1714. try {
  1715. result24 = await dbqbSecondFourAPI(params2);
  1716. console.log("FourAPI成功返回:", result24);
  1717. apiStatus.four.completed = true;
  1718. apiStatus.four.result = result24;
  1719. // 预加载第四个接口的音频
  1720. if (result24?.data?.url) {
  1721. await preloadAudio(result24.data.url.trim(), 'four');
  1722. } else {
  1723. audioPreloadStatus.four.loaded = true;
  1724. }
  1725. // 检查是否可以执行
  1726. checkAndExecuteInOrder();
  1727. } catch (error) {
  1728. console.error("FourAPI失败:", error);
  1729. apiStatus.four.completed = true;
  1730. apiStatus.four.error = error;
  1731. audioPreloadStatus.four.loaded = true;
  1732. checkAndExecuteInOrder();
  1733. }
  1734. };
  1735. handleOneAPI();
  1736. handleTwoAPI();
  1737. handleThreeAPI();
  1738. handleFourAPI();
  1739. // 同时发起所有API调用
  1740. // const promises = [
  1741. // dbqbSecondOneAPI(params2),
  1742. // dbqbSecondTwoAPI(params2),
  1743. // dbqbSecondThreeAPI(params2),
  1744. // dbqbSecondFourAPI(params2),
  1745. // ];
  1746. // const results = await Promise.allSettled(promises);
  1747. // let result21;
  1748. // let result22;
  1749. // let result23;
  1750. // let result24;
  1751. // // 检查每个API的结果并执行相应代码
  1752. // console
  1753. // if (results[0].status === "fulfilled") {
  1754. // result21 = results[0].value;
  1755. // // oneAPI成功返回时执行的代码
  1756. // console.log("oneAPI成功:", result21);
  1757. // // 在这里添加你的业务逻辑
  1758. // }
  1759. // if (results[1].status === "fulfilled") {
  1760. // result22 = results[1].value;
  1761. // // twoAPI成功返回时执行的代码
  1762. // console.log("twoAPI成功:", result22);
  1763. // // 在这里添加你的业务逻辑
  1764. // }
  1765. // if (results[2].status === "fulfilled") {
  1766. // result23 = results[2].value;
  1767. // // threeAPI成功返回时执行的代码
  1768. // console.log("threeAPI成功:", result23);
  1769. // }
  1770. // if (results[3].status === "fulfilled") {
  1771. // result24 = results[3].value;
  1772. // // fourAPI成功返回时执行的代码
  1773. // console.log("fourAPI成功:", result24);
  1774. // }
  1775. // const result21 = await dbqbSecondOneAPI(params2);
  1776. // const result22 = await dbqbSecondTwoAPI(params2);
  1777. // const result23 = await dbqbSecondThreeAPI(params2);
  1778. // const result24 = await dbqbSecondFourAPI(params2);
  1779. // 音频收集逻辑已移至checkAndExecuteInOrder函数内部
  1780. // // 修改后的消息处理逻辑
  1781. // const processedContent = marked(AIcontent.value);
  1782. // const katexRegex = /\$\$(.*?)\$\$/g;
  1783. // const plainTextContent = htmlToText(processedContent);
  1784. // // 获取音频数据
  1785. // const TTSResult = (
  1786. // await TTSAPI({
  1787. // language: "cn",
  1788. // content: plainTextContent,
  1789. // })
  1790. // ).json();
  1791. // const tts = ref();
  1792. // await TTSResult.then((res) => {
  1793. // tts.value = JSON.parse(res.data);
  1794. // });
  1795. // const ttsUrl = ref();
  1796. // if (tts.value.tts_cn !== null) {
  1797. // audioStore.ttsUrl = tts.value.tts_cn.url;
  1798. // ttsUrl.value = tts.value.tts_cn.url;
  1799. // audioStore.isNewInstance = true;
  1800. // } else if (tts.value.tts_en !== null) {
  1801. // audioStore.ttsUrl = tts.value.tts_en.url;
  1802. // ttsUrl.value = tts.value.tts_en.url;
  1803. // audioStore.isNewInstance = true;
  1804. // }
  1805. // if (ttsUrl.value) {
  1806. // nextTick(() => {
  1807. // if (audioStore.isVoiceEnabled) {
  1808. // console.log("ttsUrl.value", ttsUrl.value);
  1809. // // 播放音频
  1810. // playAudio(ttsUrl.value);
  1811. // }
  1812. // });
  1813. // }
  1814. // // chatStore.messages.pop();
  1815. // // 先推送初始消息
  1816. // const aiMessage = reactive({
  1817. // sender: "ai",
  1818. // content: "",
  1819. // isTyping: true,
  1820. // });
  1821. // chatStore.messages.push(aiMessage);
  1822. // let index = 0;
  1823. // const typingInterval = setInterval(() => {
  1824. // if (index < processedContent.length) {
  1825. // aiMessage.content += processedContent.charAt(index);
  1826. // index++;
  1827. // } else {
  1828. // clearInterval(typingInterval);
  1829. // aiMessage.isTyping = false;
  1830. // // 延迟处理KaTeX确保DOM已更新
  1831. // nextTick(() => {
  1832. // aiMessage.content = aiMessage.content.replace(
  1833. // katexRegex,
  1834. // (match, formula) => {
  1835. // try {
  1836. // return katex.renderToString(formula, {
  1837. // throwOnError: false,
  1838. // });
  1839. // } catch (error) {
  1840. // console.error("KaTeX 渲染错误:", error);
  1841. // return match;
  1842. // }
  1843. // }
  1844. // );
  1845. // chatStore.setLoading(false);
  1846. // });
  1847. // }
  1848. // }, 50); // 调整速度为50ms/字符
  1849. // // } else {
  1850. // // chatStore.messages.pop();
  1851. // // chatStore.messages.push({
  1852. // // sender: "ai",
  1853. // // content: status.msg
  1854. // // });
  1855. // // chatStore.setLoading(false);
  1856. // // }
  1857. // }
  1858. } catch (e) {
  1859. console.error("请求失败:", e);
  1860. hasValidData.value = false; // 请求失败时设置数据无效
  1861. // chatStore.messages.pop();
  1862. // chatStore.messages.push({
  1863. // sender: "ai",
  1864. // content: "AI思考失败,请稍后再试... ",
  1865. // });
  1866. // chatStore.setLoading(false);
  1867. } finally {
  1868. // chatStore.setLoading(false);
  1869. await chatStore.getUserCount();
  1870. }
  1871. }
  1872. }
  1873. },
  1874. { deep: true }
  1875. );
  1876. function KlineCanvsEcharts(containerId) {
  1877. function vwToPx(vw) {
  1878. console.log((window.innerWidth * vw) / 100, "vwToPx");
  1879. return (window.innerWidth * vw) / 100;
  1880. }
  1881. console.log("KLine渲染: 开始处理数据, 容器ID:", containerId);
  1882. // 从 chatStore 中获取数据
  1883. const messages = chatStore.messages;
  1884. console.log("KLine渲染: 获取到的消息:", messages);
  1885. let klineMessageIndex = -1;
  1886. let klineData = null;
  1887. // 查找最近的 K线 消息
  1888. // for (let i = messages.length - 1; i >= 0; i--) {
  1889. // console.log('KLine渲染: 检查消息:', messages[i]);
  1890. // if (messages[i].type === 'kline' && messages[i].chartData) {
  1891. // klineMessageIndex = i;
  1892. // klineData = messages[i].chartData;
  1893. // console.log('KLine渲染: 找到K线消息索引:', klineMessageIndex);
  1894. // console.log('KLine渲染: 找到K线数据:', klineData);
  1895. // break;
  1896. // }
  1897. // }
  1898. klineMessageIndex = containerId.split("-")[2];
  1899. console.log("KLine渲染: 找到K线消息索引:", klineMessageIndex);
  1900. if (
  1901. messages[klineMessageIndex].kline &&
  1902. messages[klineMessageIndex].chartData
  1903. ) {
  1904. klineData = messages[klineMessageIndex].chartData;
  1905. }
  1906. var KlineOption = {};
  1907. // 检测设备类型
  1908. const isMobile = window.innerWidth < 768;
  1909. const isTablet = window.innerWidth >= 768 && window.innerWidth < 1024;
  1910. console.log(
  1911. "KLine渲染: 设备类型",
  1912. isMobile ? "移动设备" : isTablet ? "平板设备" : "桌面设备"
  1913. );
  1914. if (messages[klineMessageIndex].klineType == 1) {
  1915. if (!klineData) {
  1916. console.warn("六色罗盘渲染: 数据无效 - 在chatStore中找不到有效的K线数据");
  1917. return;
  1918. }
  1919. // 获取容器元素
  1920. const container = document.getElementById(containerId);
  1921. if (!container) {
  1922. console.error("六色罗盘渲染: 找不到容器元素:", containerId);
  1923. return;
  1924. }
  1925. // 创建图表实例
  1926. console.log("六色罗盘渲染: 创建图表实例");
  1927. try {
  1928. // 如果已有实例,先销毁
  1929. if (chartInstancesMap[containerId]) {
  1930. console.log("六色罗盘渲染: 销毁已有图表实例");
  1931. chartInstancesMap[containerId].dispose();
  1932. delete chartInstancesMap[containerId];
  1933. }
  1934. // 使用普通变量存储实例
  1935. chartInstancesMap[containerId] = echarts.init(container);
  1936. console.log("六色罗盘渲染: 图表实例创建成功");
  1937. } catch (error) {
  1938. console.error("六色罗盘渲染: 图表实例创建失败:", error);
  1939. return;
  1940. }
  1941. const name = ref("六色罗盘");
  1942. const size = ref(16);
  1943. // PC版字体大小
  1944. if (window.innerWidth > 768) {
  1945. size.value = 25;
  1946. }
  1947. KlineOption = {
  1948. tooltip: {
  1949. show: !1,
  1950. },
  1951. series: [
  1952. {
  1953. name: "\u4eea\u8868\u76d8",
  1954. type: "gauge",
  1955. center: ["50%", "50%"],
  1956. radius: "90%",
  1957. startAngle: 140,
  1958. endAngle: -140,
  1959. min: 0,
  1960. max: 6,
  1961. precision: 0,
  1962. splitNumber: 30, // 分成30份
  1963. axisLine: {
  1964. show: !0,
  1965. lineStyle: {
  1966. color: [
  1967. [0.17, "#FC4407"],
  1968. [0.33, "#FDC404"],
  1969. [0.5, "#2D8FFD"],
  1970. [0.67, "#87CCE7"],
  1971. [0.83, "#C1F478"],
  1972. [1, "#8FEB8D"],
  1973. ],
  1974. width: 20,
  1975. },
  1976. },
  1977. axisTick: {
  1978. show: !0,
  1979. splitNumber: 9,
  1980. length: 8,
  1981. lineStyle: {
  1982. color: "#eee",
  1983. width: 1,
  1984. type: "solid",
  1985. },
  1986. },
  1987. axisLabel: {
  1988. show: true,
  1989. formatter: function (v) { },
  1990. textStyle: {
  1991. color: "auto",
  1992. },
  1993. },
  1994. // 中途切割
  1995. splitLine: {
  1996. show: !0,
  1997. length: 20,
  1998. lineStyle: {
  1999. color: "#eee",
  2000. width: 2,
  2001. type: "solid",
  2002. },
  2003. },
  2004. pointer: {
  2005. length: "80%",
  2006. width: 8,
  2007. color: "auto",
  2008. },
  2009. title: {
  2010. show: !0,
  2011. offsetCenter: ["-65%", -10],
  2012. textStyle: {
  2013. color: "#333",
  2014. fontSize: 15,
  2015. },
  2016. },
  2017. detail: {
  2018. show: !0,
  2019. backgroundColor: "rgba(0,0,0,0)",
  2020. borderWidth: 0,
  2021. borderColor: "#ccc",
  2022. width: 100,
  2023. height: 40,
  2024. offsetCenter: ["-90%", 0], // name位置
  2025. formatter: function () {
  2026. return name.value;
  2027. },
  2028. textStyle: {
  2029. color: "auto",
  2030. fontSize: size.value, // 字体尺寸
  2031. },
  2032. },
  2033. data: [{ value: klineData }],
  2034. },
  2035. ],
  2036. };
  2037. } else if (messages[klineMessageIndex].klineType == 2) {
  2038. if (!klineData || !klineData.Kline) {
  2039. console.warn("KLine渲染: 数据无效 - 在chatStore中找不到有效的K线数据");
  2040. return;
  2041. }
  2042. // 获取容器元素
  2043. const container = document.getElementById(containerId);
  2044. if (!container) {
  2045. console.error("KLine渲染: 找不到容器元素:", containerId);
  2046. return;
  2047. }
  2048. // 创建图表实例
  2049. console.log("KLine渲染: 创建图表实例");
  2050. try {
  2051. // 如果已有实例,先销毁
  2052. if (chartInstancesMap[containerId]) {
  2053. console.log("KLine渲染: 销毁已有图表实例");
  2054. chartInstancesMap[containerId].dispose();
  2055. delete chartInstancesMap[containerId];
  2056. }
  2057. // 使用普通变量存储实例
  2058. chartInstancesMap[containerId] = echarts.init(container);
  2059. console.log("KLine渲染: 图表实例创建成功");
  2060. } catch (error) {
  2061. console.error("KLine渲染: 图表实例创建失败:", error);
  2062. return;
  2063. }
  2064. const data = klineData.Kline;
  2065. console.log("KLine渲染: Kline数据", data);
  2066. // 切割数据方法
  2067. const splitData = (a) => {
  2068. console.log("KLine渲染: 开始数据切割");
  2069. const categoryData = [];
  2070. let values = [];
  2071. for (let i = 0; i < a.length; i++) {
  2072. categoryData.push(a[i][0]);
  2073. values.push([a[i][1], a[i][2], a[i][3], a[i][4]]);
  2074. }
  2075. console.log("KLine渲染: 日期数据点数量", categoryData.length);
  2076. console.log("KLine渲染: 值数据点数量", values.length);
  2077. return { categoryData, values };
  2078. };
  2079. // 给配置项
  2080. console.log("KLine渲染: 开始配置图表选项");
  2081. const arr1 = [];
  2082. const arr2 = [];
  2083. const arr3 = [];
  2084. const arr4 = [];
  2085. const changeColorKline = (QSXH, KLine20) => {
  2086. if (QSXH) {
  2087. QSXH.map((item) => {
  2088. KLine20.map((kline_item) => {
  2089. if (item[1] == 1 && item[0] == kline_item[0]) {
  2090. arr1.push(kline_item);
  2091. arr2.push([
  2092. kline_item[0],
  2093. null,
  2094. null,
  2095. null,
  2096. null,
  2097. null,
  2098. null,
  2099. null,
  2100. ]);
  2101. arr3.push([
  2102. kline_item[0],
  2103. null,
  2104. null,
  2105. null,
  2106. null,
  2107. null,
  2108. null,
  2109. null,
  2110. ]);
  2111. arr4.push([
  2112. kline_item[0],
  2113. null,
  2114. null,
  2115. null,
  2116. null,
  2117. null,
  2118. null,
  2119. null,
  2120. ]);
  2121. }
  2122. if (item[1] == 2 && item[0] == kline_item[0]) {
  2123. arr2.push(kline_item);
  2124. arr1.push([
  2125. kline_item[0],
  2126. null,
  2127. null,
  2128. null,
  2129. null,
  2130. null,
  2131. null,
  2132. null,
  2133. ]);
  2134. arr3.push([
  2135. kline_item[0],
  2136. null,
  2137. null,
  2138. null,
  2139. null,
  2140. null,
  2141. null,
  2142. null,
  2143. ]);
  2144. arr4.push([
  2145. kline_item[0],
  2146. null,
  2147. null,
  2148. null,
  2149. null,
  2150. null,
  2151. null,
  2152. null,
  2153. ]);
  2154. }
  2155. if (item[1] == 3 && item[0] == kline_item[0]) {
  2156. arr3.push(kline_item);
  2157. arr2.push([
  2158. kline_item[0],
  2159. null,
  2160. null,
  2161. null,
  2162. null,
  2163. null,
  2164. null,
  2165. null,
  2166. ]);
  2167. arr1.push([
  2168. kline_item[0],
  2169. null,
  2170. null,
  2171. null,
  2172. null,
  2173. null,
  2174. null,
  2175. null,
  2176. ]);
  2177. arr4.push([
  2178. kline_item[0],
  2179. null,
  2180. null,
  2181. null,
  2182. null,
  2183. null,
  2184. null,
  2185. null,
  2186. ]);
  2187. }
  2188. if (item[1] == 4 && item[0] == kline_item[0]) {
  2189. arr4.push(kline_item);
  2190. arr2.push([
  2191. kline_item[0],
  2192. null,
  2193. null,
  2194. null,
  2195. null,
  2196. null,
  2197. null,
  2198. null,
  2199. ]);
  2200. arr3.push([
  2201. kline_item[0],
  2202. null,
  2203. null,
  2204. null,
  2205. null,
  2206. null,
  2207. null,
  2208. null,
  2209. ]);
  2210. arr1.push([
  2211. kline_item[0],
  2212. null,
  2213. null,
  2214. null,
  2215. null,
  2216. null,
  2217. null,
  2218. null,
  2219. ]);
  2220. }
  2221. });
  2222. });
  2223. }
  2224. };
  2225. console.log(arr1, arr2, arr3, arr4);
  2226. changeColorKline(data.QSXH, data.KLine20);
  2227. var dealData = splitData(data.KLine20);
  2228. var dealData1 = splitData(arr1);
  2229. var dealData2 = splitData(arr2);
  2230. var dealData3 = splitData(arr3);
  2231. var dealData4 = splitData(arr4);
  2232. var dealGnBullData = data.JN;
  2233. function processMAData(data) {
  2234. let processedData = [];
  2235. data.forEach((item, idx) => {
  2236. processedData.push({
  2237. date: item[0],
  2238. value: item[1],
  2239. type: item[2],
  2240. });
  2241. });
  2242. // 当某一种type只存在一天,设置另一种type透明
  2243. let singleTypeRed = [{ min: 0, max: 0, color: "#000" }];
  2244. let singleTypeYellow = [{ min: 0, max: 0, color: "#000" }];
  2245. let singleTypeGreen = [{ min: 0, max: 0, color: "#000" }];
  2246. for (let i = 1; i < processedData.length; i++) {
  2247. if (processedData[i].type !== processedData[i - 1].type) {
  2248. if (
  2249. i == processedData.length - 1 ||
  2250. (processedData[i].type !== processedData[i + 1].type &&
  2251. processedData[i - 1].type === processedData[i + 1].type)
  2252. ) {
  2253. if (processedData[i - 1].type === 0) {
  2254. singleTypeGreen.push({
  2255. min: i - 1,
  2256. max: i,
  2257. color: "rgba(0,0,0,0)",
  2258. });
  2259. } else if (processedData[i - 1].type === 1) {
  2260. singleTypeRed.push({
  2261. min: i - 1,
  2262. max: i,
  2263. color: "rgba(0,0,0,0)",
  2264. });
  2265. } else if (processedData[i - 1].type === 2) {
  2266. singleTypeYellow.push({
  2267. min: i - 1,
  2268. max: i,
  2269. color: "rgba(0,0,0,0)",
  2270. });
  2271. }
  2272. }
  2273. }
  2274. if (processedData[i].type !== processedData[i - 1].type) {
  2275. if (processedData[i].type == 0) {
  2276. processedData[i - 1].isTransitionGreen = 1;
  2277. } else if (processedData[i].type == 1) {
  2278. processedData[i - 1].isTransitionRed = 1;
  2279. } else if (processedData[i].type == 2) {
  2280. processedData[i - 1].isTransitionYellow = 1;
  2281. }
  2282. // // 创建过渡点,使用前一个点的值
  2283. // processedData[i - 1].isTransition = true
  2284. }
  2285. }
  2286. let greenData = [];
  2287. let redData = [];
  2288. let yellowData = [];
  2289. processedData.forEach((item, idx) => {
  2290. const point = [item.date, item.value];
  2291. if (item.type === 0) {
  2292. greenData.push(point);
  2293. redData.push([item.date, "-"]);
  2294. yellowData.push([item.date, "-"]);
  2295. // if (item.isTransition) {
  2296. // redData[redData.length - 1] = [processedData[idx].date, processedData[idx].value]
  2297. // yellowData[yellowData.length - 1] = [processedData[idx].date, processedData[idx].value]
  2298. // }
  2299. if (item.isTransitionGreen) {
  2300. greenData[greenData.length - 1] = [
  2301. processedData[idx].date,
  2302. processedData[idx].value,
  2303. ];
  2304. } else if (item.isTransitionRed) {
  2305. redData[redData.length - 1] = [
  2306. processedData[idx].date,
  2307. processedData[idx].value,
  2308. ];
  2309. } else if (item.isTransitionYellow) {
  2310. yellowData[yellowData.length - 1] = [
  2311. processedData[idx].date,
  2312. processedData[idx].value,
  2313. ];
  2314. }
  2315. } else if (item.type === 1) {
  2316. redData.push(point);
  2317. greenData.push([item.date, "-"]);
  2318. yellowData.push([item.date, "-"]);
  2319. // if (item.isTransition) {
  2320. // greenData[greenData.length - 1] = [processedData[idx].date, processedData[idx].value]
  2321. // yellowData[yellowData.length - 1] = [processedData[idx].date, processedData[idx].value]
  2322. // }
  2323. if (item.isTransitionGreen) {
  2324. greenData[greenData.length - 1] = [
  2325. processedData[idx].date,
  2326. processedData[idx].value,
  2327. ];
  2328. } else if (item.isTransitionRed) {
  2329. redData[redData.length - 1] = [
  2330. processedData[idx].date,
  2331. processedData[idx].value,
  2332. ];
  2333. } else if (item.isTransitionYellow) {
  2334. yellowData[yellowData.length - 1] = [
  2335. processedData[idx].date,
  2336. processedData[idx].value,
  2337. ];
  2338. }
  2339. } else if (item.type === 2) {
  2340. redData.push([item.date, "-"]);
  2341. greenData.push([item.date, "-"]);
  2342. yellowData.push(point);
  2343. // if (item.isTransition) {
  2344. // greenData[greenData.length - 1] = [processedData[idx].date, processedData[idx].value]
  2345. // redData[redData.length - 1] = [processedData[idx].date, processedData[idx].value]
  2346. // }
  2347. if (item.isTransitionGreen) {
  2348. greenData[greenData.length - 1] = [
  2349. processedData[idx].date,
  2350. processedData[idx].value,
  2351. ];
  2352. } else if (item.isTransitionRed) {
  2353. redData[redData.length - 1] = [
  2354. processedData[idx].date,
  2355. processedData[idx].value,
  2356. ];
  2357. } else if (item.isTransitionYellow) {
  2358. yellowData[yellowData.length - 1] = [
  2359. processedData[idx].date,
  2360. processedData[idx].value,
  2361. ];
  2362. }
  2363. }
  2364. });
  2365. return {
  2366. greenData: greenData,
  2367. redData: redData,
  2368. yellowData: yellowData,
  2369. singleTypeGreen: singleTypeGreen,
  2370. singleTypeRed: singleTypeRed,
  2371. singleTypeYellow: singleTypeYellow,
  2372. };
  2373. }
  2374. const maData = processMAData(data.FCX);
  2375. const maDuchiData = processMAData(data.DNC);
  2376. if (data.FCX[0][1] == "-1") {
  2377. maData.greenData = [];
  2378. maData.redData = [];
  2379. maData.yellowData = [];
  2380. }
  2381. const processBarData = (data) => {
  2382. const barData = [];
  2383. const markPointData = [];
  2384. data.forEach((item) => {
  2385. let color;
  2386. switch (item[4]) {
  2387. case 1:
  2388. color = "#13E113";
  2389. break;
  2390. case 2:
  2391. color = "#FF0E00";
  2392. break;
  2393. case 3:
  2394. color = "#0000FE";
  2395. break;
  2396. case 4:
  2397. color = "#1397FF";
  2398. break;
  2399. }
  2400. barData.push({
  2401. value: item[5],
  2402. itemStyle: {
  2403. normal: {
  2404. color: color,
  2405. },
  2406. },
  2407. });
  2408. if (item[1] === 1) {
  2409. markPointData.push({
  2410. coord: [item[0], item[5]],
  2411. symbol:
  2412. "image://https://d31zlh4on95l9h.cloudfront.net/images/5iujb101000d5si3v3hr7w2vg0h43z1u.png",
  2413. symbolSize: [30, 30],
  2414. label: {
  2415. normal: {
  2416. color: "rgba(0, 0, 0, 0)",
  2417. },
  2418. },
  2419. });
  2420. }
  2421. if (item[2] === 1) {
  2422. markPointData.push({
  2423. coord: [item[0], item[5] / 2],
  2424. symbol:
  2425. "image://https://d31zlh4on95l9h.cloudfront.net/images/5iujaz01000d5si016bxdf6vh0377d2h.png",
  2426. symbolSize: [30, 30],
  2427. label: {
  2428. normal: {
  2429. color: "rgba(0, 0, 0, 0)",
  2430. },
  2431. },
  2432. });
  2433. }
  2434. if (item[3] === 1) {
  2435. markPointData.push({
  2436. coord: [item[0], 0],
  2437. symbol:
  2438. "image://https://d31zlh4on95l9h.cloudfront.net/images/5iujb001000d5shzls0tmd4vs0e5tdrw.png",
  2439. symbolSize: [30, 30],
  2440. label: {
  2441. normal: {
  2442. color: "rgba(0, 0, 0, 0)",
  2443. },
  2444. },
  2445. });
  2446. }
  2447. });
  2448. return { barData, markPointData };
  2449. };
  2450. const { barData, markPointData } = processBarData(dealGnBullData);
  2451. KlineOption = {
  2452. legend: [
  2453. {
  2454. textStyle: {
  2455. color: "black",
  2456. fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8),
  2457. },
  2458. width: "100%",
  2459. top: window.innerWidth > 768 ? "0%" : "-1%",
  2460. left: "center",
  2461. itemGap: window.innerWidth > 768 ? 20 : 10,
  2462. itemWidth: 10,
  2463. itemHeight: 10,
  2464. data: [
  2465. {
  2466. name: "进攻K线",
  2467. itemStyle: {
  2468. color: "rgb(255,0,0)",
  2469. },
  2470. },
  2471. {
  2472. name: "防守K线",
  2473. itemStyle: {
  2474. color: "red",
  2475. },
  2476. },
  2477. {
  2478. name: "推进K线",
  2479. itemStyle: {
  2480. color: "orange",
  2481. },
  2482. },
  2483. {
  2484. name: "撤退K线",
  2485. itemStyle: {
  2486. color: "rgb(84,252,252)",
  2487. },
  2488. },
  2489. ],
  2490. },
  2491. {
  2492. textStyle: {
  2493. color: "black",
  2494. fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8),
  2495. },
  2496. orient: "horizontal",
  2497. top: window.innerWidth > 768 ? "3%" : "2%",
  2498. width: "100%",
  2499. left: "center",
  2500. itemGap: 15,
  2501. data: [
  2502. {
  2503. name: "{green|━}{red|━} " + "牵牛绳",
  2504. icon: "none",
  2505. textStyle: {
  2506. rich: {
  2507. green: {
  2508. color: "green",
  2509. fontSize: window.innerWidth > 768 ? 20 : 10,
  2510. },
  2511. red: {
  2512. color: "red",
  2513. fontSize: window.innerWidth > 768 ? 20 : 10,
  2514. },
  2515. },
  2516. },
  2517. },
  2518. {
  2519. name: "龙线",
  2520. },
  2521. {
  2522. name: "虫线",
  2523. },
  2524. ],
  2525. },
  2526. {
  2527. textStyle: {
  2528. color: "black",
  2529. fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8),
  2530. },
  2531. orient: "horizontal",
  2532. top: window.innerWidth > 768 ? "68%" : "64%",
  2533. width: "100%",
  2534. left: "center",
  2535. itemGap: 15,
  2536. data: [
  2537. {
  2538. name: "{green|━}{red|━} " + "度牛尺",
  2539. icon: "none",
  2540. textStyle: {
  2541. rich: {
  2542. green: {
  2543. color: "green",
  2544. fontSize: window.innerWidth > 768 ? 20 : 10,
  2545. },
  2546. red: {
  2547. color: "red",
  2548. fontSize: window.innerWidth > 768 ? 20 : 10,
  2549. },
  2550. },
  2551. },
  2552. },
  2553. ],
  2554. },
  2555. ],
  2556. tooltip: {
  2557. formatter: function (a, b, d) {
  2558. if (a[0].seriesIndex == 0) {
  2559. const KlineTag = ref([]);
  2560. const AIBullTag = ref([]);
  2561. KlineTag.value = a.find((item) => item.data[1])?.data || [];
  2562. AIBullTag.value =
  2563. a.slice(4).find((item) => item.data[1] !== "-")?.data || [];
  2564. // console.log(AIBullTag.value)
  2565. return (
  2566. a[0].name +
  2567. "<br/>" +
  2568. "开盘价" +
  2569. ":" +
  2570. KlineTag.value[1] +
  2571. "<br/>" +
  2572. "收盘价" +
  2573. ":" +
  2574. KlineTag.value[2] +
  2575. "<br/>" +
  2576. "最低价" +
  2577. ":" +
  2578. KlineTag.value[3] +
  2579. "<br/>" +
  2580. "最高价" +
  2581. ":" +
  2582. KlineTag.value[4] +
  2583. "<br/>" +
  2584. "牵牛绳" +
  2585. ":" +
  2586. AIBullTag.value[1]
  2587. );
  2588. }
  2589. if (a[0].seriesIndex == 4) {
  2590. let formattedVolume;
  2591. if (a[0].data.value >= 10000) {
  2592. formattedVolume = (a[0].data.value / 10000).toFixed(2) + "w";
  2593. } else {
  2594. formattedVolume = a[0].data.value;
  2595. }
  2596. return a[0].name + "<br/>" + "成交量" + ":" + formattedVolume;
  2597. }
  2598. if ([10, 11, 12].includes(a[0].seriesIndex)) {
  2599. const duchiData = a.find(
  2600. (item) => item.data && item.data[1] !== "-"
  2601. );
  2602. return duchiData
  2603. ? a[0].axisValue + "<br/>" + "度牛尺" + ":" + duchiData.data[1]
  2604. : null;
  2605. }
  2606. },
  2607. trigger: "axis",
  2608. axisPointer: {
  2609. type: "cross",
  2610. },
  2611. backgroundColor: "rgba(119, 120, 125, 0.6)",
  2612. borderWidth: 1,
  2613. borderColor: "#77787D",
  2614. padding: 10,
  2615. textStyle: {
  2616. color: "#fff",
  2617. },
  2618. },
  2619. axisPointer: {
  2620. link: [
  2621. {
  2622. xAxisIndex: "all",
  2623. },
  2624. ],
  2625. label: {
  2626. backgroundColor: "#77787D",
  2627. },
  2628. },
  2629. toolbox: {
  2630. show: false,
  2631. },
  2632. grid: [
  2633. {
  2634. left:
  2635. window.innerWidth > 1024
  2636. ? "70vw"
  2637. : window.innerWidth > 768
  2638. ? "65vw"
  2639. : "55vw",
  2640. right:
  2641. window.innerWidth > 1024
  2642. ? "40vw"
  2643. : window.innerWidth > 768
  2644. ? "30vw"
  2645. : "40vw",
  2646. top: window.innerWidth > 768 ? "8%" : "5%",
  2647. height: window.innerWidth > 768 ? "34%" : "34%",
  2648. containLabel: false,
  2649. },
  2650. {
  2651. left:
  2652. window.innerWidth > 1024
  2653. ? "70vw"
  2654. : window.innerWidth > 768
  2655. ? "65vw"
  2656. : "55vw",
  2657. right:
  2658. window.innerWidth > 1024
  2659. ? "40vw"
  2660. : window.innerWidth > 768
  2661. ? "30vw"
  2662. : "40vw",
  2663. top: window.innerWidth > 768 ? "45%" : "42%",
  2664. height: window.innerWidth > 768 ? "22%" : "22%",
  2665. containLabel: false,
  2666. },
  2667. {
  2668. left:
  2669. window.innerWidth > 1024
  2670. ? "70vw"
  2671. : window.innerWidth > 768
  2672. ? "65vw"
  2673. : "55vw",
  2674. right:
  2675. window.innerWidth > 1024
  2676. ? "40vw"
  2677. : window.innerWidth > 768
  2678. ? "30vw"
  2679. : "40vw",
  2680. top: window.innerWidth > 768 ? "73%" : "70%",
  2681. height: window.innerWidth > 768 ? "20%" : "22%",
  2682. containLabel: false,
  2683. },
  2684. ],
  2685. xAxis: [
  2686. {
  2687. type: "category",
  2688. data: dealData.categoryData,
  2689. boundaryGap: true,
  2690. axisLine: { onZero: false },
  2691. splitLine: { show: false },
  2692. min: "dataMin",
  2693. max: "dataMax",
  2694. axisPointer: {
  2695. z: 100,
  2696. },
  2697. axisLine: {
  2698. lineStyle: {
  2699. color: "black",
  2700. },
  2701. }, //
  2702. axisLabel: { show: false },
  2703. axisTick: { show: false },
  2704. },
  2705. {
  2706. type: "category",
  2707. gridIndex: 1,
  2708. data: dealData.categoryData,
  2709. boundaryGap: true,
  2710. axisLine: { lineStyle: { color: "black" } },
  2711. axisLabel: {
  2712. show: false,
  2713. interval: "auto",
  2714. },
  2715. },
  2716. {
  2717. type: "category",
  2718. gridIndex: 2,
  2719. data: dealData.categoryData,
  2720. boundaryGap: true,
  2721. axisLine: { lineStyle: { color: "black" } },
  2722. axisLabel: {
  2723. show: true,
  2724. interval: "auto",
  2725. },
  2726. },
  2727. ],
  2728. yAxis: [
  2729. {
  2730. scale: true,
  2731. gridIndex: 0,
  2732. position: "left",
  2733. axisLabel: {
  2734. inside: false,
  2735. align: "right",
  2736. fontSize: window.innerWidth > 768 ? 15 : 10,
  2737. },
  2738. axisLine: {
  2739. show: true,
  2740. lineStyle: {
  2741. fontSize: "",
  2742. color: "black",
  2743. },
  2744. },
  2745. axisTick: { show: false },
  2746. splitLine: { show: false },
  2747. },
  2748. {
  2749. scale: true,
  2750. gridIndex: 1,
  2751. splitNumber: 4,
  2752. min: 0,
  2753. minInterval: 1,
  2754. axisLabel: {
  2755. show: true,
  2756. fontSize: window.innerWidth > 768 ? 15 : 10,
  2757. margin: 8,
  2758. formatter: (value) => {
  2759. if (value >= 1000000000) {
  2760. return (value / 1000000000).toFixed(1) + "B";
  2761. } else if (value >= 1000000) {
  2762. return (value / 1000000).toFixed(1) + "M";
  2763. } else if (value >= 10000) {
  2764. return (value / 10000).toFixed(1) + "W";
  2765. }
  2766. return value.toFixed(0);
  2767. },
  2768. },
  2769. axisLine: { show: true, lineStyle: { color: "black" } },
  2770. axisTick: { show: false },
  2771. splitLine: { show: true, lineStyle: { type: "dashed" } },
  2772. boundaryGap: ["20%", "20%"],
  2773. },
  2774. {
  2775. type: "value",
  2776. gridIndex: 2,
  2777. min: 0,
  2778. max: 100,
  2779. axisLabel: {
  2780. show: true,
  2781. fontSize: window.innerWidth > 768 ? 15 : 10,
  2782. formatter: function (value) {
  2783. var customValues = [0, 20, 50, 80, 100];
  2784. return customValues.indexOf(value) > -1 ? value : "";
  2785. },
  2786. },
  2787. axisLine: {
  2788. show: true,
  2789. lineStyle: {
  2790. color: "black",
  2791. },
  2792. },
  2793. axisTick: {
  2794. show: false,
  2795. },
  2796. splitNumber: 10,
  2797. splitLine: {
  2798. show: true,
  2799. lineStyle: {
  2800. type: "dashed",
  2801. color: "#fff",
  2802. width: 1,
  2803. },
  2804. interval: function (index, value) {
  2805. return [20, 50, 80, 100].indexOf(value) > -1;
  2806. },
  2807. },
  2808. },
  2809. ],
  2810. dataZoom: [
  2811. {
  2812. type: "inside",
  2813. xAxisIndex: [0, 1, 2],
  2814. start: 55,
  2815. end: 100,
  2816. },
  2817. {
  2818. show: true,
  2819. xAxisIndex: [0, 1, 2],
  2820. type: "slider",
  2821. top: window.innerWidth > 768 ? "95%" : "96%",
  2822. // left: window.innerWidth > 768 ? "10%" : "8%",
  2823. // right: window.innerWidth > 768 ? "4%" : "8%",
  2824. start: 98,
  2825. end: 100,
  2826. },
  2827. ],
  2828. visualMap: [
  2829. {
  2830. type: "piecewise",
  2831. show: false,
  2832. pieces: maData.singleTypeGreen,
  2833. outOfRange: {
  2834. color: "green",
  2835. },
  2836. dimension: 0,
  2837. seriesIndex: 7,
  2838. },
  2839. {
  2840. type: "piecewise",
  2841. show: false,
  2842. pieces: maData.singleTypeRed,
  2843. outOfRange: {
  2844. color: "red",
  2845. },
  2846. dimension: 0,
  2847. seriesIndex: 8,
  2848. },
  2849. {
  2850. type: "piecewise",
  2851. show: false,
  2852. pieces: maData.singleTypeYellow,
  2853. outOfRange: {
  2854. color: "yellow",
  2855. },
  2856. dimension: 0,
  2857. seriesIndex: 9,
  2858. },
  2859. ],
  2860. series: [
  2861. {
  2862. name: "进攻K线",
  2863. type: "candlestick",
  2864. barWidth: "50%",
  2865. data: dealData1.values,
  2866. xAxisIndex: 0,
  2867. yAxisIndex: 0,
  2868. itemStyle: {
  2869. normal: {
  2870. color: "rgb(255,0,0)",
  2871. color0: "rgb(255,0,0)",
  2872. borderColor: "rgb(255,0,0)",
  2873. borderColor0: "rgb(255,0,0)",
  2874. },
  2875. },
  2876. gridIndex: 0,
  2877. },
  2878. //
  2879. {
  2880. name: "推进K线",
  2881. type: "candlestick",
  2882. barWidth: "50%",
  2883. data: dealData2.values,
  2884. xAxisIndex: 0,
  2885. yAxisIndex: 0,
  2886. itemStyle: {
  2887. normal: {
  2888. color: "rgb(0,0,252)",
  2889. color0: "rgb(0,0,252)",
  2890. borderColor: "rgb(0,0,252)",
  2891. borderColor0: "rgb(0,0,252)",
  2892. },
  2893. },
  2894. gridIndex: 0,
  2895. },
  2896. {
  2897. name: "防守K线",
  2898. type: "candlestick",
  2899. barWidth: "50%",
  2900. data: dealData3.values,
  2901. xAxisIndex: 0,
  2902. yAxisIndex: 0,
  2903. itemStyle: {
  2904. normal: {
  2905. color: "orange",
  2906. color0: "orange",
  2907. borderColor: "orange",
  2908. borderColor0: "orange",
  2909. },
  2910. },
  2911. gridIndex: 0,
  2912. },
  2913. {
  2914. name: "撤退K线",
  2915. type: "candlestick",
  2916. barWidth: "50%",
  2917. data: dealData4.values,
  2918. xAxisIndex: 0,
  2919. yAxisIndex: 0,
  2920. itemStyle: {
  2921. normal: {
  2922. color: "rgb(84,252,252)",
  2923. color0: "rgb(84,252,252)",
  2924. borderColor: "rgb(84,252,252)",
  2925. borderColor0: "rgb(84,252,252)",
  2926. },
  2927. },
  2928. gridIndex: 0,
  2929. },
  2930. {
  2931. name: "成交量",
  2932. type: "bar",
  2933. barWidth: "70%",
  2934. xAxisIndex: 1,
  2935. yAxisIndex: 1,
  2936. data: barData,
  2937. markPoint: {
  2938. data: markPointData,
  2939. label: {
  2940. show: false,
  2941. },
  2942. },
  2943. },
  2944. {
  2945. name: "{green|━}{red|━} " + "牵牛绳",
  2946. type: "line",
  2947. data: [],
  2948. smooth: true,
  2949. symbol: "none",
  2950. xAxisIndex: 0,
  2951. yAxisIndex: 0,
  2952. showSymbol: false,
  2953. lineStyle: {
  2954. opacity: 0,
  2955. },
  2956. itemStyle: {
  2957. normal: {
  2958. color: "green",
  2959. },
  2960. },
  2961. gridIndex: 0,
  2962. },
  2963. {
  2964. name: "{green|━}{red|━} " + "度牛尺",
  2965. type: "line",
  2966. data: [],
  2967. smooth: true,
  2968. symbol: "none",
  2969. xAxisIndex: 0,
  2970. yAxisIndex: 0,
  2971. showSymbol: false,
  2972. lineStyle: {
  2973. opacity: 0,
  2974. },
  2975. itemStyle: {
  2976. normal: {
  2977. color: "green",
  2978. },
  2979. },
  2980. gridIndex: 0,
  2981. },
  2982. {
  2983. name: "虫线",
  2984. type: "line",
  2985. data: maData.greenData,
  2986. smooth: true,
  2987. symbol: "none",
  2988. xAxisIndex: 0,
  2989. yAxisIndex: 0,
  2990. itemStyle: {
  2991. normal: {
  2992. color: "green",
  2993. lineStyle: {
  2994. width: 2,
  2995. type: "solid",
  2996. },
  2997. },
  2998. },
  2999. gridIndex: 0,
  3000. },
  3001. {
  3002. name: "龙线",
  3003. type: "line",
  3004. data: maData.redData,
  3005. smooth: true,
  3006. symbol: "none",
  3007. xAxisIndex: 0,
  3008. yAxisIndex: 0,
  3009. itemStyle: {
  3010. normal: {
  3011. color: "red",
  3012. lineStyle: {
  3013. width: 2,
  3014. type: "solid",
  3015. },
  3016. },
  3017. },
  3018. gridIndex: 0,
  3019. },
  3020. {
  3021. name: "黄色",
  3022. type: "line",
  3023. data: maData.yellowData,
  3024. smooth: true,
  3025. symbol: "none",
  3026. xAxisIndex: 0,
  3027. yAxisIndex: 0,
  3028. itemStyle: {
  3029. normal: {
  3030. color: "yellow",
  3031. lineStyle: {
  3032. width: 2,
  3033. type: "solid",
  3034. },
  3035. },
  3036. },
  3037. gridIndex: 0,
  3038. },
  3039. {
  3040. name: "背景区域",
  3041. type: "line",
  3042. data: [],
  3043. xAxisIndex: 2,
  3044. yAxisIndex: 2,
  3045. markArea: {
  3046. silent: true,
  3047. itemStyle: {
  3048. normal: {
  3049. opacity: 1,
  3050. },
  3051. },
  3052. label: {
  3053. normal: {
  3054. show: true,
  3055. position: "insideRight",
  3056. fontSize: window.innerWidth > 768 ? 16 : 12,
  3057. fontWeight: "bold",
  3058. color: "#13E113",
  3059. distance: 10,
  3060. },
  3061. },
  3062. data: [
  3063. [
  3064. {
  3065. yAxis: 0,
  3066. itemStyle: {
  3067. normal: {
  3068. color: "#CFFFCF",
  3069. },
  3070. },
  3071. label: {
  3072. normal: {
  3073. formatter: "度牛区",
  3074. },
  3075. },
  3076. },
  3077. {
  3078. yAxis: 20,
  3079. },
  3080. ],
  3081. [
  3082. {
  3083. yAxis: 20,
  3084. itemStyle: {
  3085. normal: {
  3086. color: "#A6FFFF",
  3087. },
  3088. },
  3089. },
  3090. {
  3091. yAxis: 40,
  3092. },
  3093. ],
  3094. [
  3095. {
  3096. yAxis: 40,
  3097. itemStyle: {
  3098. normal: {
  3099. color: "#FFF686",
  3100. },
  3101. },
  3102. },
  3103. {
  3104. yAxis: 60,
  3105. },
  3106. ],
  3107. [
  3108. {
  3109. yAxis: 60,
  3110. itemStyle: {
  3111. normal: { color: "#FFD2B3" },
  3112. },
  3113. },
  3114. {
  3115. yAxis: 80,
  3116. },
  3117. ],
  3118. [
  3119. {
  3120. yAxis: 80,
  3121. itemStyle: {
  3122. normal: { color: "#FFB8B8" },
  3123. },
  3124. label: {
  3125. normal: {
  3126. formatter: "度牛区",
  3127. color: "#FF0000",
  3128. position: "insideLeft",
  3129. distance: 10,
  3130. },
  3131. },
  3132. },
  3133. {
  3134. yAxis: 100,
  3135. },
  3136. ],
  3137. ],
  3138. },
  3139. },
  3140. {
  3141. name: "度牛尺",
  3142. type: "line",
  3143. data: maDuchiData.greenData,
  3144. symbol: "none",
  3145. xAxisIndex: 2,
  3146. yAxisIndex: 2,
  3147. itemStyle: {
  3148. normal: {
  3149. color: "green",
  3150. lineStyle: {
  3151. width: 2,
  3152. type: "solid",
  3153. },
  3154. },
  3155. },
  3156. gridIndex: 2,
  3157. markPoint: {
  3158. symbol: "rect",
  3159. symbolSize: (value, params) => {
  3160. const width = window.innerWidth;
  3161. const baseHeight = 22;
  3162. // if (width <= 375) {
  3163. // return [2, 16];
  3164. // } else if (width <= 768) {
  3165. // return [2, 22];
  3166. // }
  3167. return [2, baseHeight];
  3168. },
  3169. itemStyle: {
  3170. normal: {
  3171. label: {
  3172. show: false,
  3173. },
  3174. },
  3175. },
  3176. data: [
  3177. ...maDuchiData.greenData
  3178. .map((item) => {
  3179. if (item[1] === 0) {
  3180. return {
  3181. coord: [item[0], 20],
  3182. symbolOffset: [0, 10],
  3183. itemStyle: {
  3184. color: "#00ff00",
  3185. },
  3186. };
  3187. }
  3188. })
  3189. .filter(Boolean),
  3190. ],
  3191. },
  3192. },
  3193. {
  3194. type: "line",
  3195. data: maDuchiData.redData,
  3196. // smooth: true,
  3197. symbol: "none",
  3198. xAxisIndex: 2,
  3199. yAxisIndex: 2,
  3200. itemStyle: {
  3201. normal: {
  3202. color: "red",
  3203. lineStyle: {
  3204. width: 2,
  3205. type: "solid",
  3206. },
  3207. },
  3208. },
  3209. gridIndex: 2,
  3210. markPoint: {
  3211. symbol: "rect",
  3212. symbolSize: (value, params) => {
  3213. const width = window.innerWidth;
  3214. const baseHeight = 22;
  3215. // if (width <= 375) {
  3216. // return [2, 16];
  3217. // } else if (width <= 768) {
  3218. // return [2, 24];
  3219. // }
  3220. return [2, baseHeight];
  3221. },
  3222. itemStyle: {
  3223. normal: {
  3224. label: {
  3225. show: false,
  3226. },
  3227. },
  3228. },
  3229. data: [
  3230. ...maDuchiData.redData
  3231. .map((item) => {
  3232. if (item[1] === 100) {
  3233. return {
  3234. coord: [item[0], 80],
  3235. symbolOffset: [0, -10],
  3236. itemStyle: {
  3237. color: "#ff0000",
  3238. },
  3239. };
  3240. }
  3241. })
  3242. .filter(Boolean),
  3243. ],
  3244. },
  3245. },
  3246. {
  3247. name: "辅助线",
  3248. type: "line",
  3249. data: [],
  3250. xAxisIndex: 2,
  3251. yAxisIndex: 2,
  3252. markLine: {
  3253. silent: true,
  3254. symbol: "none",
  3255. lineStyle: {
  3256. color: "#000000",
  3257. width: 3,
  3258. type: "solid",
  3259. },
  3260. data: [{ yAxis: 20 }],
  3261. },
  3262. },
  3263. {
  3264. name: "辅助线",
  3265. type: "line",
  3266. data: [],
  3267. xAxisIndex: 2,
  3268. yAxisIndex: 2,
  3269. markLine: {
  3270. silent: true,
  3271. symbol: "none",
  3272. lineStyle: {
  3273. color: "#000000",
  3274. width: 3,
  3275. type: "solid",
  3276. },
  3277. data: [{ yAxis: 50 }],
  3278. },
  3279. },
  3280. {
  3281. name: "辅助线",
  3282. type: "line",
  3283. data: [],
  3284. xAxisIndex: 2,
  3285. yAxisIndex: 2,
  3286. markLine: {
  3287. silent: true,
  3288. symbol: "none",
  3289. lineStyle: {
  3290. color: "#000000",
  3291. width: 3,
  3292. type: "solid",
  3293. },
  3294. data: [{ yAxis: 80 }],
  3295. },
  3296. },
  3297. {
  3298. name: "辅助线",
  3299. type: "line",
  3300. data: [],
  3301. xAxisIndex: 2,
  3302. yAxisIndex: 2,
  3303. markLine: {
  3304. silent: true,
  3305. symbol: "none",
  3306. lineStyle: {
  3307. color: "#000000",
  3308. width: 3,
  3309. type: "solid",
  3310. },
  3311. data: [{ yAxis: 100 }],
  3312. },
  3313. },
  3314. ],
  3315. };
  3316. }
  3317. console.log("KLine渲染: 图表配置完成");
  3318. try {
  3319. // 应用配置
  3320. console.log("KLine渲染: 开始设置图表选项");
  3321. chartInstancesMap[containerId].setOption(KlineOption);
  3322. console.log("KLine渲染: 图表选项设置成功");
  3323. // 窗口大小变化时重新渲染图表
  3324. const resizeFunc = _.throttle(
  3325. function () {
  3326. console.log("窗口大小改变,调整图表大小");
  3327. if (
  3328. chartInstancesMap[containerId] &&
  3329. !chartInstancesMap[containerId].isDisposed()
  3330. ) {
  3331. // 如果设备类型发生变化,重新渲染
  3332. const newIsMobile = window.innerWidth < 768;
  3333. const newIsTablet =
  3334. window.innerWidth >= 768 && window.innerWidth < 1024;
  3335. if (newIsMobile !== isMobile || newIsTablet !== isTablet) {
  3336. console.log("设备类型变化,重新渲染图表");
  3337. KlineCanvsEcharts(containerId);
  3338. return;
  3339. }
  3340. chartInstancesMap[containerId].resize();
  3341. }
  3342. },
  3343. 1000,
  3344. { trailing: false }
  3345. );
  3346. // 给resize事件绑定一个特定的函数名,便于后续移除
  3347. window[`resize_${containerId}`] = resizeFunc;
  3348. // 绑定resize事件
  3349. window.removeEventListener("resize", window[`resize_${containerId}`]);
  3350. window.addEventListener("resize", window[`resize_${containerId}`]);
  3351. console.log("KLine渲染: 图表渲染完成");
  3352. } catch (error) {
  3353. console.error("KLine渲染: 图表渲染出错", error);
  3354. }
  3355. }
  3356. watch(
  3357. () => audioStore.isVoiceEnabled,
  3358. (newVal) => {
  3359. // 添加状态锁定逻辑
  3360. if (newVal === audioStore.lastVoiceState) return;
  3361. audioStore.lastVoiceState = newVal;
  3362. if (newVal) {
  3363. console.log("开启语音播放");
  3364. // 添加重试机制
  3365. const tryPlay = () => {
  3366. if (!audioStore.ttsUrl) return; // 新增空值判断
  3367. if (audioStore.soundInstance?.playing()) return;
  3368. playAudio(audioStore.ttsUrl);
  3369. setTimeout(() => {
  3370. if (!audioStore.soundInstance?.playing()) {
  3371. Howler.unload();
  3372. }
  3373. }, 1000);
  3374. };
  3375. tryPlay();
  3376. } else {
  3377. console.log("关闭语音播放");
  3378. pauseAudio();
  3379. // 强制停止并释放资源
  3380. // Howler.stop();
  3381. // Howler.unload();
  3382. // if (audioStore.soundInstance) {
  3383. // audioStore.soundInstance.off(); // 移除所有事件监听
  3384. // audioStore.soundInstance = null;
  3385. // }
  3386. }
  3387. },
  3388. { immediate: true }
  3389. );
  3390. watch(
  3391. () => dataStore.activeTabIndex,
  3392. (newVal) => {
  3393. setTimeout(() => {
  3394. console.log("activeTabIndex变化:", newVal);
  3395. // 当标签页切换回来时,重新渲染所有图表
  3396. if (newVal === 0) {
  3397. console.log("切换到AI聊天页,重新渲染图表");
  3398. // 延迟执行以确保DOM已渲染
  3399. renderAllKlineCharts();
  3400. }
  3401. }, 1000);
  3402. },
  3403. { immediate: true } // 添加immediate属性,确保初始化时执行一次
  3404. );
  3405. // 添加渲染所有K线图的方法
  3406. function renderAllKlineCharts() {
  3407. console.log("重新渲染所有K线图");
  3408. // 查找所有K线消息
  3409. const messages = chatStore.messages;
  3410. for (let i = 0; i < messages.length; i++) {
  3411. if (messages[i].kline && messages[i].chartData) {
  3412. const containerId = `kline-container-${i}`;
  3413. console.log(`尝试渲染K线图: ${containerId}`);
  3414. // 确保DOM已经渲染
  3415. const container = document.getElementById(containerId);
  3416. if (container) {
  3417. // 渲染图表
  3418. KlineCanvsEcharts(containerId);
  3419. } else {
  3420. console.warn(`找不到容器: ${containerId}`);
  3421. }
  3422. }
  3423. }
  3424. }
  3425. // 初始化随机GIF
  3426. onMounted(() => {
  3427. // 初始化marked组件
  3428. marked.setOptions({
  3429. breaks: true, // 支持换行符转换为 <br>
  3430. gfm: true, // 启用 GitHub Flavored Markdown
  3431. sanitize: false, // 不清理 HTML(谨慎使用)
  3432. smartLists: true, // 智能列表
  3433. smartypants: true, // 智能标点符号
  3434. xhtml: false, // 不使用 XHTML 输出
  3435. renderer: renderer,
  3436. });
  3437. renderAllKlineCharts();
  3438. console.log("组件挂载完成");
  3439. // 添加页面可见性变化监听器
  3440. document.addEventListener("visibilitychange", handleVisibilityChange);
  3441. // 添加DOM变化监听器
  3442. const observer = new MutationObserver((mutations) => {
  3443. mutations.forEach((mutation) => {
  3444. if (mutation.type === "childList" && mutation.addedNodes.length) {
  3445. // 检查是否添加了图表容器
  3446. const containers = document.querySelectorAll(
  3447. '[id^="kline-container-"]'
  3448. );
  3449. if (containers.length) {
  3450. // console.log("DOM变化监听到K线容器:", Array.from(containers).map(el => el.id));
  3451. }
  3452. }
  3453. });
  3454. });
  3455. // 开始监听DOM变化
  3456. observer.observe(document.body, { childList: true, subtree: true });
  3457. });
  3458. // 页面可见性变化处理
  3459. let wasPlayingBeforeHidden = false;
  3460. const handleVisibilityChange = () => {
  3461. if (document.hidden) {
  3462. // 页面被隐藏时,如果音频正在播放,则暂停并记录状态
  3463. if (audioStore.isPlaying) {
  3464. wasPlayingBeforeHidden = true;
  3465. audioStore.pause();
  3466. console.log("页面切换离开,音频已暂停");
  3467. } else {
  3468. wasPlayingBeforeHidden = false;
  3469. }
  3470. } else {
  3471. // 页面重新可见时,如果之前在播放,则恢复播放
  3472. if (wasPlayingBeforeHidden && !audioStore.isPlaying) {
  3473. audioStore.play();
  3474. console.log("页面切换回来,音频已恢复播放");
  3475. wasPlayingBeforeHidden = false;
  3476. }
  3477. }
  3478. };
  3479. // 组件卸载时清理所有图表实例和事件监听器
  3480. onUnmounted(() => {
  3481. // 移除页面可见性变化监听器
  3482. document.removeEventListener("visibilitychange", handleVisibilityChange);
  3483. // 停止音频播放
  3484. if (audioStore.isPlaying) {
  3485. audioStore.stop();
  3486. console.log("组件卸载,音频已停止");
  3487. }
  3488. // 清理所有图表实例
  3489. Object.keys(chartInstancesMap).forEach((key) => {
  3490. if (chartInstancesMap[key]) {
  3491. // 移除resize事件监听
  3492. if (window[`resize_${key}`]) {
  3493. window.removeEventListener("resize", window[`resize_${key}`]);
  3494. delete window[`resize_${key}`];
  3495. }
  3496. // 销毁图表实例
  3497. chartInstancesMap[key].dispose();
  3498. delete chartInstancesMap[key];
  3499. }
  3500. });
  3501. });
  3502. </script>
  3503. <template>
  3504. <div class="chat-container">
  3505. <!-- GIF区域 -->
  3506. <div class="gif-area">
  3507. <img :src="bgc1" alt="夺宝奇兵大模型logo" class="bgc" />
  3508. <img :src="logo1" alt="夺宝奇兵大模型logo" class="logo1" />
  3509. <img :src="logo2" alt="夺宝奇兵大模型logo" class="logo2" />
  3510. </div>
  3511. <div v-for="(msg, index) in chatMsg" :key="index" :class="{
  3512. 'message-bubble': true,
  3513. [msg.sender]: msg.sender,
  3514. [msg.class]: msg.class,
  3515. }">
  3516. <div v-if="msg.type === 'kline'" class="kline-container">
  3517. <div :id="'kline-container-' + index" class="chart-mount-point">
  3518. <div v-if="!msg.hasValidData" class="no-data-message">
  3519. <p>暂无K线数据</p>
  3520. </div>
  3521. </div>
  3522. </div>
  3523. <div v-else-if="msg.type == 'ing'">
  3524. <div v-if="msg.flag">
  3525. <span>{{ msg.content }}</span>
  3526. <span class="loading-dots">
  3527. <span class="dot">.</span>
  3528. <span class="dot">.</span>
  3529. <span class="dot">.</span>
  3530. <span class="dot">.</span>
  3531. <span class="dot">.</span>
  3532. <span class="dot">.</span>
  3533. </span>
  3534. </div>
  3535. <div v-else v-html="msg.content"></div>
  3536. </div>
  3537. <div v-else-if="msg.type == 'title1'" style="display: flex; width: 100%">
  3538. <div class="mainTitle">
  3539. {{ msg.content }}
  3540. </div>
  3541. <div class="date">
  3542. {{ msg.date }}
  3543. </div>
  3544. </div>
  3545. <div v-else-if="msg.type == 'title2'" class="title2">
  3546. <img class="title1Img" :src="title1" alt="出错了" />
  3547. </div>
  3548. <div v-else-if="msg.type == 'title3'" class="title3">
  3549. <img class="title2Img" :src="msg.content" alt="出错了" />
  3550. </div>
  3551. <div v-else-if="msg.type == 'content1'" class="content1">
  3552. <div v-if="msg.kline" class="kline-container content1chart">
  3553. <div :id="'kline-container-' + index" class="chart-mount-point">
  3554. <div v-if="!msg.hasValidData" class="no-data-message">
  3555. <p>暂无数据</p>
  3556. </div>
  3557. </div>
  3558. </div>
  3559. <div v-else class="content1Text">
  3560. <div v-html="msg.content" class="text1"></div>
  3561. </div>
  3562. </div>
  3563. <div v-else-if="msg.type == 'content2'" class="content2">
  3564. <div class="kline-container content2chart">
  3565. <div :id="'kline-container-' + index" class="chart-mount-point">
  3566. <div v-if="!msg.hasValidData" class="no-data-message">
  3567. <p>暂无数据</p>
  3568. </div>
  3569. </div>
  3570. </div>
  3571. </div>
  3572. <div v-else-if="msg.type == 'content3'" class="content3">
  3573. <div class="content3Text">
  3574. <div v-html="msg.content" class="text3"></div>
  3575. </div>
  3576. </div>
  3577. <div v-else-if="msg.type == 'mianze'" class="mianze">
  3578. <div v-html="msg.content"></div>
  3579. </div>
  3580. <div v-else v-html="msg.content"></div>
  3581. </div>
  3582. </div>
  3583. </template>
  3584. <style scoped>
  3585. p{
  3586. font-size: 20px;
  3587. }
  3588. .bgc {
  3589. position: absolute;
  3590. z-index: -1;
  3591. max-width: 530px;
  3592. min-width: 340px;
  3593. width: 40%;
  3594. height: auto;
  3595. /* right: 30px; */
  3596. /* top: -30px; */
  3597. }
  3598. .logo1 {
  3599. max-width: 350px;
  3600. min-width: 200px;
  3601. width: 25%;
  3602. }
  3603. .logo2 {
  3604. margin-top: 20px;
  3605. max-width: 350px;
  3606. min-width: 200px;
  3607. width: 30%;
  3608. /* position: relative; */
  3609. }
  3610. .chat-container {
  3611. display: flex;
  3612. flex-direction: column;
  3613. overflow: hidden;
  3614. }
  3615. .gif-area {
  3616. padding: 70px 0px;
  3617. position: relative;
  3618. /* height: 30vh; */
  3619. display: flex;
  3620. flex-direction: column;
  3621. justify-content: center;
  3622. align-items: center;
  3623. flex-shrink: 0;
  3624. /* 防止GIF区域被压缩 */
  3625. }
  3626. .gif-area img {
  3627. /* width: 30%; */
  3628. /* 改为百分比单位 */
  3629. /* min-width: 200px; */
  3630. /* 最小尺寸 */
  3631. /* max-width: 400px; */
  3632. /* 最大尺寸 */
  3633. /* height: auto; */
  3634. /* left: 50%; */
  3635. /* transition: all 0.3s; */
  3636. /* 添加过渡效果 */
  3637. }
  3638. .message-area {
  3639. margin-top: 2%;
  3640. flex: 1;
  3641. /* 消息区域占据剩余空间 */
  3642. overflow-y: auto;
  3643. padding: 20px;
  3644. display: flex;
  3645. flex-direction: column;
  3646. gap: 15px;
  3647. }
  3648. .marquee-container {
  3649. /* position: absolute; */
  3650. bottom: 0;
  3651. width: 100%;
  3652. /* ga */
  3653. }
  3654. .marquee-row {
  3655. white-space: nowrap;
  3656. overflow: visible;
  3657. padding: 8px 0;
  3658. width: 100%;
  3659. }
  3660. .marquee-item {
  3661. display: inline-block;
  3662. margin: 0 15px;
  3663. padding: 8px 20px;
  3664. background: rgba(255, 255, 255, 0.9);
  3665. /* 白色背景 */
  3666. border-radius: 10px;
  3667. /* 圆角矩形 */
  3668. color: #333;
  3669. /* 文字颜色改为深色 */
  3670. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  3671. /* 添加阴影 */
  3672. transition: all 0.3s;
  3673. transition: color 0.3s;
  3674. }
  3675. .top {
  3676. animation: marquee 25s linear infinite;
  3677. /* 默认动画是运行状态 */
  3678. animation-play-state: running;
  3679. }
  3680. .bottom {
  3681. animation: marquee 15s linear infinite reverse;
  3682. /* 默认动画是运行状态 */
  3683. animation-play-state: running;
  3684. }
  3685. /* 添加PC端专用速度 */
  3686. @media (min-width: 768px) {
  3687. .top {
  3688. animation-duration: 35s;
  3689. /* PC端改为35秒 */
  3690. }
  3691. .bottom {
  3692. animation-duration: 35s;
  3693. /* PC端改为35秒 */
  3694. }
  3695. }
  3696. @keyframes marquee {
  3697. 0% {
  3698. transform: translateX(100%);
  3699. }
  3700. 100% {
  3701. transform: translateX(-250%);
  3702. }
  3703. }
  3704. .loading-dots {
  3705. display: inline-block;
  3706. }
  3707. .dot {
  3708. opacity: 0.4;
  3709. animation: loading 1.4s infinite;
  3710. }
  3711. .dot:nth-child(1) {
  3712. animation-delay: 0s;
  3713. }
  3714. .dot:nth-child(2) {
  3715. animation-delay: 0.2s;
  3716. }
  3717. .dot:nth-child(3) {
  3718. animation-delay: 0.4s;
  3719. }
  3720. .dot:nth-child(4) {
  3721. animation-delay: 0.6s;
  3722. }
  3723. .dot:nth-child(5) {
  3724. animation-delay: 0.8s;
  3725. }
  3726. .dot:nth-child(6) {
  3727. animation-delay: 1s;
  3728. }
  3729. @keyframes loading {
  3730. 0%,
  3731. 60%,
  3732. 100% {
  3733. opacity: 0.4;
  3734. }
  3735. 30% {
  3736. opacity: 1;
  3737. }
  3738. }
  3739. .message-bubble {
  3740. max-width: 80%;
  3741. margin: 10px 0px;
  3742. padding: 15px 20px;
  3743. position: relative;
  3744. }
  3745. .message-bubble.user {
  3746. color: #6d22f8;
  3747. background: white;
  3748. font-weight: bold;
  3749. margin-left: auto;
  3750. border-radius: 10px;
  3751. margin-right: 20px;
  3752. /* border-bottom-right-radius: 5px; */
  3753. }
  3754. .message-bubble.ai {
  3755. background: #2b378d;
  3756. color: #ffffff;
  3757. margin: 0 auto;
  3758. /* border-bottom-left-radius: 5px; */
  3759. }
  3760. .message-bubble.ing {
  3761. background: #ffffff;
  3762. color: #000000;
  3763. font-weight: bold;
  3764. border-radius: 10px;
  3765. margin-left: 20px;
  3766. margin-right: auto;
  3767. /* border-bottom-left-radius: 5px; */
  3768. }
  3769. .message-bubble.ai.title1 {
  3770. width: 100%;
  3771. display: flex;
  3772. border-radius: 10px 10px 0px 0px;
  3773. /* border-bottom-left-radius: 5px; */
  3774. }
  3775. .mainTitle {
  3776. font-size: 16px;
  3777. font-weight: bold;
  3778. background-image: url("@/assets/img/AiEmotion/bk01.png");
  3779. background-repeat: no-repeat;
  3780. background-size: 100% 100%;
  3781. min-width: 200px;
  3782. width: 20vw;
  3783. height: 50px;
  3784. padding: 5px 0px 0px 0px;
  3785. display: flex;
  3786. justify-content: center;
  3787. align-items: center;
  3788. }
  3789. .date {
  3790. font-size: 1.5rem;
  3791. font-weight: bold;
  3792. margin-left: auto;
  3793. /* width: 100px; */
  3794. display: flex;
  3795. justify-content: center;
  3796. align-items: center;
  3797. }
  3798. .message-bubble.ai.title2 {
  3799. width: 100%;
  3800. display: flex;
  3801. justify-content: center;
  3802. align-items: center;
  3803. }
  3804. .title1Img {
  3805. max-width: 500px;
  3806. width: 80vw;
  3807. }
  3808. .message-bubble.ai.title3 {
  3809. width: 100%;
  3810. display: flex;
  3811. justify-content: center;
  3812. align-items: center;
  3813. }
  3814. .title2Img {
  3815. max-width: 500px;
  3816. width: 90vw;
  3817. }
  3818. .message-bubble.ai.content1 {
  3819. width: 100%;
  3820. display: flex;
  3821. justify-content: center;
  3822. align-items: center;
  3823. }
  3824. .content1chart {
  3825. background-image: url("@/assets/img/AIchat/罗盘边框.png");
  3826. background-repeat: no-repeat;
  3827. background-size: 100% 100%;
  3828. width: 50vw;
  3829. min-width: 350px;
  3830. display: flex;
  3831. justify-content: center;
  3832. align-items: center;
  3833. }
  3834. .content1Text {
  3835. background-image: url("@/assets/img/AIchat/框.png");
  3836. background-repeat: no-repeat;
  3837. background-size: 100% 100%;
  3838. width: 50vw;
  3839. min-width: 350px;
  3840. /* height: 20vw; */
  3841. /* max-height: 400px; */
  3842. padding: 5% 0;
  3843. }
  3844. .text1 {
  3845. font-weight: bold;
  3846. /* margin-left: 6%; */
  3847. /* margin-bottom: 10px; */
  3848. margin: 0px 6% 10px 6%;
  3849. font-size: 20px;
  3850. }
  3851. .message-bubble.ai.content2 {
  3852. width: 100%;
  3853. display: flex;
  3854. justify-content: center;
  3855. align-items: center;
  3856. }
  3857. .content2chart {
  3858. background-image: url("@/assets/img/AIchat/PCbackPic.png");
  3859. background-repeat: no-repeat;
  3860. background-size: 100% 100%;
  3861. width: 50vw;
  3862. min-width: 350px;
  3863. display: flex;
  3864. justify-content: center;
  3865. align-items: center;
  3866. height: calc(500px + 10vw) !important;
  3867. }
  3868. .message-bubble.ai.content3 {
  3869. width: 100%;
  3870. display: flex;
  3871. justify-content: center;
  3872. align-items: center;
  3873. }
  3874. .content3Text {
  3875. background-image: url("@/assets/img/AIchat/边框.png");
  3876. background-repeat: no-repeat;
  3877. background-size: 100% 100%;
  3878. width: 50vw;
  3879. min-width: 350px;
  3880. /* height: 20vw; */
  3881. /* max-height: 400px; */
  3882. padding: 5% 0px;
  3883. }
  3884. .text3 {
  3885. /* font-weight: bold; */
  3886. /* margin-left: 6%; */
  3887. /* margin-bottom: 10px; */
  3888. margin: 0px 6% 10px 6%;
  3889. font-size: 20px;
  3890. }
  3891. .message-bubble.ai.mianze {
  3892. width: 100%;
  3893. text-align: center;
  3894. font-weight: bold;
  3895. font-size: 24px;
  3896. border-radius: 0px 0px 10px 10px;
  3897. }
  3898. .kline-container {
  3899. margin-top: 10px;
  3900. /* 最小高度 */
  3901. min-height: 320px;
  3902. /* 视口高度单位 */
  3903. height: 40vh;
  3904. width: 50vw;
  3905. }
  3906. @media (max-width: 768px) {
  3907. .kline-container {
  3908. min-width: 75vw;
  3909. }
  3910. .content1Text {
  3911. width: 77vw;
  3912. min-width: 0px;
  3913. /* height: 20vw; */
  3914. /* min-height: 150px; */
  3915. }
  3916. .text1 {
  3917. font-size: 20px;
  3918. }
  3919. .content2chart {
  3920. background-image: url("@/assets/img/AIchat/new-app-bgc.png") !important;
  3921. height: 100vw;
  3922. }
  3923. .content3Text {
  3924. width: 77vw;
  3925. min-width: 0px;
  3926. /* height: 20vw; */
  3927. /* min-height: 150px; */
  3928. }
  3929. .text3 {
  3930. font-size: 20px;
  3931. }
  3932. .message-bubble.ai.mianze {
  3933. font-size: 18px;
  3934. }
  3935. }
  3936. .kline-container .chart-mount-point {
  3937. display: flex;
  3938. justify-content: center;
  3939. align-items: center;
  3940. height: 80%;
  3941. width: 90%;
  3942. }
  3943. </style>