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.

4118 lines
130 KiB

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