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.

4994 lines
151 KiB

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