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.

4268 lines
134 KiB

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