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.

4511 lines
139 KiB

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