|
|
<script setup> import { ref, onMounted, watch, nextTick, reactive, onUnmounted } from "vue"; import { ElDialog } from "element-plus"; import { getReplyStreamAPI, getReplyAPI, TTSAPI, qsArpAamClickAPI, dbqbFirstAPI, dbqbSecondOneAPI, dbqbSecondTwoAPI, dbqbSecondThreeAPI, dbqbSecondFourAPI, dataListAPI, } from "../api/AIxiaocaishen"; import { useUserStore } from "../store/userPessionCode"; import { useChatStore } from "../store/chat"; import { useAudioStore } from "../store/audio"; import { useDataStore } from "@/store/dataList.js"; import { marked } from "marked"; // 引入marked库
import katex from "katex"; // 引入 KaTeX 库
import { htmlToText } from "html-to-text"; import { Howl, Howler } from "howler"; import * as echarts from "echarts"; import _, { add } from "lodash"; import moment from "moment";
import AIgif1 from "@/assets/img/AIchat/AIgif1.gif"; import AIgif2 from "@/assets/img/AIchat/AIgif2.gif"; import AIgif3 from "@/assets/img/AIchat/AIgif3.gif"; import AIgif4 from "@/assets/img/AIchat/AIgif4.gif"; import AIgif5 from "@/assets/img/AIchat/AIgif5.gif"; import AIgif6 from "@/assets/img/AIchat/AIgif6.gif"; import AIgif7 from "@/assets/img/AIchat/AIgif7.gif"; import title1 from "@/assets/img/AIchat/核心价值评估标题.png"; import title2 from "@/assets/img/AIchat/主力作战.png"; import title3 from "@/assets/img/AIchat/攻防三维.png"; import title4 from "@/assets/img/AIchat/综合作战.png";
const gifList = [AIgif1, AIgif2, AIgif3, AIgif4, AIgif5, AIgif6, AIgif7]; const chatStore = useChatStore(); const audioStore = useAudioStore(); const dataStore = useDataStore(); // 随机GIF
const currentGif = ref("");
const renderer = new marked.Renderer(); // 重写 del 方法,让删除线不生效
renderer.del = function (text) { // 处理各种数据类型
console.log("text", text); return "~" + text.tokens[0].raw + "<br>" + text.tokens[2].raw + "~"; };
// 定义自定义事件
const emit = defineEmits(["updateMessage", "sendMessage"]);
// 音频播放方法
const playAudio = (url) => { // 添加空值校验
if (!url) { console.warn("音频URL为空,跳过播放"); audioStore.isPlaying = false; return; }
const handlePlay = () => { // if (audioStore.soundInstance) {
// Howler.unload(); // 添加清理旧实例
// // audioStore.soundInstance.stop()
// }
if (audioStore.isNewInstance) { const newSound = new Howl({ src: [url], html5: true, // 强制HTML5 Audio解决iOS兼容问题
format: ["mp3", "acc"], rate: 1.2, // 调整播放速度
onplay: () => { audioStore.isPlaying = true; // 改为更新store状态
newSound.volume(1); // 添加音量设置
}, onend: () => (audioStore.isPlaying = false), onstop: () => (audioStore.isPlaying = false), onloaderror: (id, err) => { console.error("音频加载失败:", err); ElMessage.error("音频播放失败,请检查网络连接"); }, }); if (audioStore.nowSound) { audioStore.nowSound.stop(); } audioStore.nowSound = newSound; audioStore.isNewInstance = false;
console.log("新音频"); } else { console.log("已经有音频"); }
const newSound = audioStore.nowSound;
// 添加立即播放逻辑
newSound.play();
audioStore.setAudioInstance(newSound); Howler._howls.push(newSound); // 强制注册到全局管理
// // 处理浏览器自动播放策略
// if (Howler.autoUnlock) {
// Howler.autoUnlock();
// }
};
// if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
// document.addEventListener('touchstart', handlePlay, { once: true });
// ElMessage.info('请轻触屏幕以启用音频播放');
// } else {
// handlePlay();
// }
handlePlay();
// Howler.volume(1.0) // 添加全局音量设置
}; // 新增暂停方法
const pauseAudio = () => { if (audioStore.soundInstance) { // 添加移动端特殊处理
// if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
// Howler.pause(); // iOS需要强制停止音频上下文
// } else {
// audioStore.soundInstance.pause();
// }
audioStore.soundInstance.pause();
audioStore.isPlaying = false;
// // 添加状态同步
// nextTick(() => {
// Howler.unload();
// audioStore.soundInstance = null;
// });
} };
// 音频轮流播放方法
const playAudioSequence = (audioUrls) => { if (!audioUrls || audioUrls.length === 0) { console.warn("音频URL列表为空,跳过播放"); return; }
let currentIndex = 0; const playNext = () => { if (currentIndex >= audioUrls.length) { console.log("所有音频播放完成"); return; }
const currentUrl = audioUrls[currentIndex]; console.log(`正在播放第${currentIndex + 1}个音频:`, currentUrl); // 停止当前播放的音频
if (audioStore.nowSound) { audioStore.nowSound.stop(); }
const sound = new Howl({ src: [currentUrl], html5: true, format: ["mp3", "acc"], rate: 1.2, onplay: () => { audioStore.isPlaying = true; console.log(`开始播放音频 ${currentIndex + 1}`); }, onend: () => { audioStore.isPlaying = false; console.log(`音频 ${currentIndex + 1} 播放完成`); currentIndex++; // 播放下一个音频
setTimeout(() => { playNext(); }, 500); // 间隔500ms播放下一个
}, onstop: () => { audioStore.isPlaying = false; }, onloaderror: (id, err) => { console.error(`音频 ${currentIndex + 1} 加载失败:`, err); currentIndex++; // 跳过失败的音频,播放下一个
setTimeout(() => { playNext(); }, 100); } });
audioStore.nowSound = sound; audioStore.setAudioInstance(sound); sound.play(); };
// 开始播放第一个音频
playNext(); };
// 获取消息
const chatMsg = computed(() => chatStore.messages); const props = defineProps({ messages: Array, chartData: { type: Object, default: null, }, index: { type: Number, required: true, }, });
// 打字机效果
const typewriterContent = ref(""); const isTyping = ref(false);
const typeWriter = (text, callback) => { let index = 0; isTyping.value = true; typewriterContent.value = "";
const typingInterval = setInterval(() => { if (index < text.length) { typewriterContent.value += text.charAt(index); index++; // 自动滚动到底部
nextTick(() => { const container = document.querySelector(".message-area"); if (container) container.scrollTop = container.scrollHeight; }); } else { clearInterval(typingInterval); isTyping.value = false; if (callback) callback(); } }, 50); // 调整速度(毫秒)
};
const fnGetData = (data) => { const YaLiZhiChengLuoPan = data.YaLiZhiChengLuoPan; let sz = ref(5.5); // 进行判断
const yl = YaLiZhiChengLuoPan.Yali; // 压力位
const zc = YaLiZhiChengLuoPan.ZhiCheng; // 支撑位
console.log("yl", yl, "zc", zc); if (yl == "较大" && zc == "较弱") { sz.value = 0.5; // 极高风险
} else if (yl == "一般" && zc == "较弱") { sz.value = 1.5; // 弱撑中压区
} else if (yl == "较弱" && zc == "较弱") { sz.value = 2.5; // 弱撑弱压区
} else if (yl == "较大" && zc == "较大") { sz.value = 3.5; // 强撑强压区
} else if (yl == "一般" && zc == "较大") { sz.value = 4.5; // 强撑中压
} else if (yl == "较弱" && zc == "较大") { sz.value = 5.5; // 强撑弱压区
} else if (yl == "较大" && zc == "一般") { sz.value = 0.2; } else if (yl == "一般" && zc == "一般") { sz.value = 3; } else if (yl == "较弱" && zc == "一般") { sz.value = 5.8; } return sz.value; };
const typingQueue = ref([]); const isTypingInProgress = ref(false);
// 创建打字机效果的Promise函数
const createTypingEffect = (message, content, speed) => { return new Promise((resolve) => { chatStore.messages.push(message); if (content != "") { let index = 0; message.content = ""; message.isTyping = true;
const typingInterval = setInterval(() => { if (index < content.length) { message.content += content.charAt(index); index++; } else { clearInterval(typingInterval); message.isTyping = false;
// 处理KaTeX渲染(如果需要)
nextTick(() => { if (message.content.includes("$$")) { message.content = message.content.replace( katexRegex, (match, formula) => { try { return katex.renderToString(formula, { throwOnError: false, }); } catch (error) { console.error("KaTeX 渲染错误:", error); return match; } } ); } resolve(); // 完成后resolve
}); } }, speed); } else { if (message.kline) { if (message.klineType == 1) { console.log("六色罗盘消息已添加到聊天列表");
// 在渲染完成后初始化图表
nextTick(() => { console.log("nextTick开始 - 准备渲染图表"); console.log("消息列表:", chatStore.messages);
// 寻找最新添加的K线消息索引
let klineIndex = -1; for (let i = 0; i < chatStore.messages.length; i++) { if (chatStore.messages[i].messageId === message.messageId) { klineIndex = i; break; } }
console.log("找到的K线消息索引:", klineIndex);
if (klineIndex !== -1) { const containerId = `kline-container-${klineIndex}`; console.log("图表容器ID:", containerId);
// 确保DOM已经渲染完成
setTimeout(() => { console.log("延时执行,确保DOM已渲染"); KlineCanvsEcharts(containerId); }, 100); // 短暂延时确保DOM已渲染
} else { console.warn("未找到K线消息"); } }); } else { console.log("K线消息已添加到聊天列表");
// 在渲染完成后初始化图表
nextTick(() => { console.log("nextTick开始 - 准备渲染图表"); console.log("消息列表:", chatStore.messages);
// 寻找最新添加的K线消息索引
let klineIndex = -1; for (let i = 0; i < chatStore.messages.length; i++) { if (chatStore.messages[i].messageId === message.messageId) { klineIndex = i; break; } }
console.log("找到的K线消息索引:", klineIndex);
if (klineIndex !== -1) { const containerId = `kline-container-${klineIndex}`; console.log("图表容器ID:", containerId);
// 确保DOM已经渲染完成
setTimeout(() => { console.log("延时执行,确保DOM已渲染"); KlineCanvsEcharts(containerId); }, 100); // 短暂延时确保DOM已渲染
} else { console.warn("未找到K线消息"); } }); } // 延时1秒后resolve
setTimeout(() => { resolve(); }, 1000); } else { // 延时1秒后resolve
setTimeout(() => { resolve(); }, 1000); } } }); };
// 队列处理函数
const processTypingQueue = async () => { if (isTypingInProgress.value || typingQueue.value.length === 0) { return; }
isTypingInProgress.value = true;
while (typingQueue.value.length > 0) { const task = typingQueue.value.shift(); await createTypingEffect(task.message, task.content, task.speed); }
isTypingInProgress.value = false; };
// 添加打字机任务到队列
const addTypingTask = (message, content, speed = 50) => { typingQueue.value.push({ message, content, speed }); processTypingQueue(); };
const hasValidData = ref(false);
// 创建一个非响应式的对象来存储图表实例
const chartInstancesMap = {};
// 存储上一次的消息的length
const previousMessagesLength = ref(0);
watch( () => props.messages, async (newVal, oldVal) => { // console.log(newVal, 'newVal')
// console.log(oldVal, 'oldVal')
// console.log(previousMessagesLength.value, 'previousMessagesLength')
// console.log(newVal.length, 'newVal.length')
// // 添加空值判断
if (!newVal?.length || newVal === previousMessagesLength.value) return;
previousMessagesLength.value = newVal.length; if (newVal.length > 0) { console.log("消息列表已更新,最新消息:", newVal[newVal.length - 1]); chatStore.messages.push(newVal[newVal.length - 1]);
console.log("消息列表已更新,最新消息:", chatMsg[chatMsg.length - 1]);
console.log("token", localStorage.getItem("localToken"));
// 获取权限
const userStore = useUserStore();
const params1 = { content: newVal[newVal.length - 1].content, language: "cn", marketList: "usa,sg,my,hk,cn,can,vi,th,in", token: localStorage.getItem("localToken"), // language: "cn",
// marketList: "hk,cn,usa,my,sg,vi,in,gb"
// token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
}; // 标志
let flag = true; const codeData = ref(); // 第一阶段,意图识别
try { // 调用工作流获取回复
const result = await dbqbFirstAPI(params1); codeData.value = result.data; console.log(codeData.value, "codeData"); // 根据意图识别结果判断
if (result.code == 200) { chatStore.messages.push({ class: "ing", type: "ing", flag: flag, content: result.data.kaishi, }); } else { flag = false; console.log("执行回绝话术"); const AIcontent = ref(result.msg); // 修改后的消息处理逻辑
const processedContent = marked(AIcontent.value); const katexRegex = /\$\$(.*?)\$\$/g; const aiContent = processedContent.replace( katexRegex, (match, formula) => { try { return katex.renderToString(formula, { throwOnError: false }); } catch (error) { console.error("KaTeX 渲染错误:", error); return match; } } ); console.log(aiContent, "aiContent");
chatStore.messages.push({ class: "ing", type: "ing", flag: flag, content: aiContent, });
chatStore.setLoading(false); } } catch (e) { console.log(e, "意图识别失败"); }
if (flag) { const params2 = { content: newVal[newVal.length - 1].content, language: "cn", marketList: "usa,sg,my,hk,cn,can,vi,th,in", token: localStorage.getItem("localToken"), // language: "cn",
// marketList: "hk,cn,usa,my,sg,vi,in,gb"
// token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
name: codeData.value.name, code: codeData.value.code, market: codeData.value.market, };
try { const result20 = await dataListAPI({ token: localStorage.getItem("localToken"), market: codeData.value.market, code: codeData.value.code, language: "cn", //t.value.suoxie,
// brainPrivilegeState: 1,
// swordPrivilegeState: 1,
// stockForecastPrivilegeState: 1,
// spaceForecastPrivilegeState: 1,
// aibullPrivilegeState: 1,
// aigoldBullPrivilegeState: 1,
// airadarPrivilegeState: 1,
// marketList: 1,
brainPrivilegeState: userStore.brainPerssion, swordPrivilegeState: userStore.swordPerssion, stockForecastPrivilegeState: userStore.pricePerssion, spaceForecastPrivilegeState: userStore.timePerssion, aibullPrivilegeState: userStore.aibullPerssion, aigoldBullPrivilegeState: userStore.aiGnbullPerssion, airadarPrivilegeState: userStore.airadarPerssion, marketList: userStore.aiGoldMarketList, });
const HomePage = result20.data.HomePage; const AIGoldBull = result20.data.AIGoldBull;
const result21 = await dbqbSecondOneAPI(params2); const result22 = await dbqbSecondTwoAPI(params2); const result23 = await dbqbSecondThreeAPI(params2); const result24 = await dbqbSecondFourAPI(params2);
// 收集所有音频URL
const audioUrls = []; if (result21.data.url) audioUrls.push(result21.data.url.trim()); if (result22.data.url) audioUrls.push(result22.data.url.trim()); if (result23.data.url) audioUrls.push(result23.data.url.trim()); if (result24.data.url) audioUrls.push(result24.data.url.trim());
// 开始轮流播放音频
if (audioUrls.length > 0 && audioStore.isVoiceEnabled) { playAudioSequence(audioUrls); }
const katexRegex = /\$\$(.*?)\$\$/g; // 删除正在为您生成信息
chatStore.messages.pop(); // 添加报告头和时间
addTypingTask( { sender: "ai", class: "title1", type: "title1", content: codeData.value.name + "全景作战报告", date: moment().format("MM/DD/YYYY"), }, "", 50 ); // chatStore.messages.push({
// sender: "ai",
// class: "title1",
// type: "title1",
// content: codeData.value.name + "全景作战报告",
// date: moment().format("MM/DD/YYYY"),
// });
// 添加股票信息框
const pc1 = marked( result21.data.name + "\n" + result21.data.price + "\n" + result21.data.date ); const ac1 = pc1.replace(katexRegex, (match, formula) => { try { return katex.renderToString(formula, { throwOnError: false }); } catch (error) { console.error("KaTeX 渲染错误:", error); return match; } });
// 先推送初始消息
const aiMessage1 = reactive({ sender: "ai", class: "content1", type: "content1", content: "", isTyping: true, }); // chatStore.messages.push(aiMessage1);
// let index1 = 0;
// const typingInterval1 = setInterval(() => {
// if (index1 < ac1.length) {
// aiMessage1.content += ac1.charAt(index1);
// index1++;
// } else {
// clearInterval(typingInterval1);
// aiMessage1.isTyping = false;
// }
// }, 50); // 调整速度为50ms/字符
addTypingTask(aiMessage1, ac1, 50);
// chatStore.messages.push({
// sender: "ai",
// class: "content1",
// type: "content1",
// content: ac1,
// });
// 添加六色罗盘
const LiuSeData = JSON.parse(JSON.stringify(toRaw(HomePage))); const sz = fnGetData(LiuSeData); if (sz) { hasValidData.value = true; console.log("hasValidData设置为:", hasValidData.value); } // 先推送K线图消息
const klineMessageId1 = `kline-${Date.now()}`; console.log("生成K线消息ID:", klineMessageId1);
addTypingTask( { sender: "ai", class: "content1", type: "content1", kline: true, chartData: sz, messageId: klineMessageId1, hasValidData: true, klineType: 1, }, "", 50 ); // chatStore.messages.push({
// sender: "ai",
// class: "content1",
// type: "content1",
// kline: true,
// chartData: sz,
// messageId: klineMessageId1,
// hasValidData: true,
// klineType: 1,
// });
// console.log("六色罗盘消息已添加到聊天列表");
// // 在渲染完成后初始化图表
// nextTick(() => {
// console.log("nextTick开始 - 准备渲染图表");
// console.log("消息列表:", chatStore.messages);
// // 寻找最新添加的K线消息索引
// let klineIndex = -1;
// for (let i = 0; i < chatStore.messages.length; i++) {
// if (chatStore.messages[i].messageId === klineMessageId1) {
// klineIndex = i;
// break;
// }
// }
// console.log("找到的K线消息索引:", klineIndex);
// if (klineIndex !== -1) {
// const containerId = `kline-container-${klineIndex}`;
// console.log("图表容器ID:", containerId);
// // 确保DOM已经渲染完成
// setTimeout(() => {
// console.log("延时执行,确保DOM已渲染");
// KlineCanvsEcharts(containerId);
// }, 100); // 短暂延时确保DOM已渲染
// } else {
// console.warn("未找到K线消息");
// }
// });
// 度牛尺K线图
const AIGoldBullData = JSON.parse(JSON.stringify(toRaw(AIGoldBull))); const HomePageData = JSON.parse(JSON.stringify(toRaw(HomePage))); console.log("处理 K 线数据 - 开始"); console.log("AIGoldBullData", AIGoldBullData); console.log("HomePageData", HomePageData);
const Kline20 = { name: HomePageData.StockInformation.Name, Kline: AIGoldBullData, };
// 打印K线数据结构
console.log("K线数据结构:", Kline20); console.log("K线数据名称:", Kline20.name); console.log("K线数据:", Kline20.Kline ? Kline20.Kline : null);
// 设置数据有效标志
hasValidData.value = true; console.log("hasValidData设置为:", hasValidData.value);
// chatStore.messages.pop();
// 先推送K线图消息
const klineMessageId2 = `kline-${Date.now() + 1}`; console.log("生成K线消息ID:", klineMessageId2);
// chatStore.messages.push({
// sender: "ai",
// class: "content2",
// type: "content2",
// kline: true,
// chartData: Kline20,
// messageId: klineMessageId2,
// hasValidData: true, // 添加hasValidData标志
// klineType: 2,
// });
addTypingTask( { sender: "ai", class: "content2", type: "content2", kline: true, chartData: Kline20, messageId: klineMessageId2, hasValidData: true, // 添加hasValidData标志
klineType: 2, }, "", 50 );
// console.log("K线消息已添加到聊天列表");
// // 在渲染完成后初始化图表
// nextTick(() => {
// console.log("nextTick开始 - 准备渲染图表");
// console.log("消息列表:", chatStore.messages);
// // 寻找最新添加的K线消息索引
// let klineIndex = -1;
// for (let i = 0; i < chatStore.messages.length; i++) {
// if (chatStore.messages[i].messageId === klineMessageId2) {
// klineIndex = i;
// break;
// }
// }
// console.log("找到的K线消息索引:", klineIndex);
// if (klineIndex !== -1) {
// const containerId = `kline-container-${klineIndex}`;
// console.log("图表容器ID:", containerId);
// // 确保DOM已经渲染完成
// setTimeout(() => {
// console.log("延时执行,确保DOM已渲染");
// KlineCanvsEcharts(containerId);
// }, 100); // 短暂延时确保DOM已渲染
// } else {
// console.warn("未找到K线消息");
// }
// });
// 添加标题2
addTypingTask( { sender: "ai", class: "title2", type: "title2", content: "", }, "", 50 ); // chatStore.messages.push({
// sender: "ai",
// class: "title2",
// type: "title2",
// content: "",
// });
// 添加内容框1
const pc2 = marked(result22.data.hxjzpg); console.log(pc2, "pc2"); const ac2 = pc2.replace(katexRegex, (match, formula) => { try { return katex.renderToString(formula, { throwOnError: false }); } catch (error) { console.error("KaTeX 渲染错误:", error); return match; } });
// 先推送初始消息
const aiMessage2 = reactive({ sender: "ai", class: "content3", type: "content3", content: "", isTyping: true, }); // chatStore.messages.push(aiMessage2);
// let index2 = 0;
// const typingInterval2 = setInterval(() => {
// if (index2 < ac2.length) {
// aiMessage2.content += ac2.charAt(index2);
// index2++;
// } else {
// clearInterval(typingInterval2);
// aiMessage2.isTyping = false;
// }
// }, 50); // 调整速度为50ms/字符
addTypingTask(aiMessage2, ac2, 50);
// chatStore.messages.push({
// sender: "ai",
// class: "content3",
// type: "content3",
// content: ac2,
// });
// 添加标题3-2
addTypingTask( { sender: "ai", class: "title3", type: "title3", content: title2, }, "", 50 ); // chatStore.messages.push({
// sender: "ai",
// class: "title3",
// type: "title3",
// content: title2,
// });
// 添加内容框2
// const pc3 = marked(result23.data.zhuli1+'\n'+result23.data.zhuli2+'\n'+result23.data.zhuli3);
// const ac3 = pc3.replace(
// katexRegex,
// (match, formula) => {
// try {
// return katex.renderToString(formula, { throwOnError: false });
// } catch (error) {
// console.error("KaTeX 渲染错误:", error);
// return match;
// }
// }
// );
const ac3 = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【主力行为】</p><p>${result23.data.zhuli1}</p><p>${result23.data.zhuli2}</p><p>${result23.data.zhuli3}</p>`;
// 先推送初始消息
const aiMessage3 = reactive({ sender: "ai", class: "content3", type: "content3", content: "", isTyping: true, }); // chatStore.messages.push(aiMessage3);
// let index3 = 0;
// const typingInterval3 = setInterval(() => {
// if (index3 < ac3.length) {
// aiMessage3.content += ac3.charAt(index3);
// index3++;
// } else {
// clearInterval(typingInterval3);
// aiMessage3.isTyping = false;
// }
// }, 50); // 调整速度为50ms/字符
addTypingTask(aiMessage3, ac3, 50);
// chatStore.messages.push({
// sender: "ai",
// class: "content3",
// type: "content3",
// content: ac3,
// });
// 添加标题3-3
addTypingTask( { sender: "ai", class: "title3", type: "title3", content: title3, }, "", 50 ); // chatStore.messages.push({
// sender: "ai",
// class: "title3",
// type: "title3",
// content: title3,
// });
// 添加内容框3
const arr = result23.data.kongjian.split(","); const kongjian = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【空间维度】</p><p style="display:flex;justify-content:center;">${arr[0]},${arr[1]}</p><p style="display:flex;justify-content:center;">${arr[2]},${arr[3]}</p>`; const shijian = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【时间维度】</p><p style="display:flex;justify-content:center;">${result23.data.shijian}</p>`; const nengliang = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【能量维度】</p><p>${result23.data.nengliang}</p>`; const ac4 = kongjian + shijian + nengliang;
// const pc4 = marked(
// kongjian +
// "\n" +
// result23.data.shijian +
// "\n" +
// result23.data.nengliang
// );
// const ac4 = pc4.replace(katexRegex, (match, formula) => {
// try {
// return katex.renderToString(formula, { throwOnError: false });
// } catch (error) {
// console.error("KaTeX 渲染错误:", error);
// return match;
// }
// });
// 先推送初始消息
const aiMessage4 = reactive({ sender: "ai", class: "content3", type: "content3", content: "", isTyping: true, }); // chatStore.messages.push(aiMessage4);
// let index4 = 0;
// const typingInterval4 = setInterval(() => {
// if (index4 < ac4.length) {
// aiMessage4.content += ac4.charAt(index4);
// index4++;
// } else {
// clearInterval(typingInterval4);
// aiMessage4.isTyping = false;
// }
// }, 50); // 调整速度为50ms/字符
addTypingTask(aiMessage4, ac4, 50);
// chatStore.messages.push({
// sender: "ai",
// class: "content3",
// type: "content3",
// content: ac4,
// });
// 添加标题3-4
addTypingTask( { sender: "ai", class: "title3", type: "title3", content: title4, }, "", 50 ); chatStore.messages.push({ sender: "ai", class: "title3", type: "title3", content: title4, }); // 添加内容框4
const cftj = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【触发条件】</p><p>${result24.data.cftl}</p>`; const gfzl = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【攻防指令】</p><p>${result24.data.gfzl}</p>`; const ac5 = cftj + gfzl;
// const pc5 = marked(result24.data.cftl + "/n" + result24.data.gfzl);
// const ac5 = pc5.replace(katexRegex, (match, formula) => {
// try {
// return katex.renderToString(formula, { throwOnError: false });
// } catch (error) {
// console.error("KaTeX 渲染错误:", error);
// return match;
// }
// });
// 先推送初始消息
const aiMessage5 = reactive({ sender: "ai", class: "content3", type: "content3", content: "", isTyping: true, }); // chatStore.messages.push(aiMessage5);
// let index5 = 0;
// const typingInterval5 = setInterval(() => {
// if (index5 < ac5.length) {
// aiMessage5.content += ac5.charAt(index5);
// index5++;
// } else {
// clearInterval(typingInterval5);
// aiMessage5.isTyping = false;
// }
// }, 50); // 调整速度为50ms/字符
addTypingTask(aiMessage5, ac5, 50);
// chatStore.messages.push({
// sender: "ai",
// class: "content3",
// type: "content3",
// content: ac5,
// });
const ac6 = "内容由AI生成,请注意甄别"; // 先推送初始消息
const aiMessage6 = reactive({ sender: "ai", class: "mianze", type: "mianze", content: "", isTyping: true, }); // chatStore.messages.push(aiMessage6);
// let index6 = 0;
// const typingInterval6 = setInterval(() => {
// if (index6 < ac6.length) {
// aiMessage6.content += ac6.charAt(index6);
// index6++;
// } else {
// clearInterval(typingInterval6);
// aiMessage6.isTyping = false;
// }
// }, 50); // 调整速度为50ms/字符
addTypingTask(aiMessage6, ac6, 100);
// chatStore.messages.push({
// sender: "ai",
// class: "mianze",
// type: "mianze",
// content: "内容由AI生成,请注意甄别",
// });
// // 修改后的消息处理逻辑
// const processedContent = marked(AIcontent.value);
// const katexRegex = /\$\$(.*?)\$\$/g;
// const plainTextContent = htmlToText(processedContent);
// // 获取音频数据
// const TTSResult = (
// await TTSAPI({
// language: "cn",
// content: plainTextContent,
// })
// ).json();
// const tts = ref();
// await TTSResult.then((res) => {
// tts.value = JSON.parse(res.data);
// });
// const ttsUrl = ref();
// if (tts.value.tts_cn !== null) {
// audioStore.ttsUrl = tts.value.tts_cn.url;
// ttsUrl.value = tts.value.tts_cn.url;
// audioStore.isNewInstance = true;
// } else if (tts.value.tts_en !== null) {
// audioStore.ttsUrl = tts.value.tts_en.url;
// ttsUrl.value = tts.value.tts_en.url;
// audioStore.isNewInstance = true;
// }
// if (ttsUrl.value) {
// nextTick(() => {
// if (audioStore.isVoiceEnabled) {
// console.log("ttsUrl.value", ttsUrl.value);
// // 播放音频
// playAudio(ttsUrl.value);
// }
// });
// }
// // chatStore.messages.pop();
// // 先推送初始消息
// const aiMessage = reactive({
// sender: "ai",
// content: "",
// isTyping: true,
// });
// chatStore.messages.push(aiMessage);
// let index = 0;
// const typingInterval = setInterval(() => {
// if (index < processedContent.length) {
// aiMessage.content += processedContent.charAt(index);
// index++;
// } else {
// clearInterval(typingInterval);
// aiMessage.isTyping = false;
// // 延迟处理KaTeX确保DOM已更新
// nextTick(() => {
// aiMessage.content = aiMessage.content.replace(
// katexRegex,
// (match, formula) => {
// try {
// return katex.renderToString(formula, {
// throwOnError: false,
// });
// } catch (error) {
// console.error("KaTeX 渲染错误:", error);
// return match;
// }
// }
// );
// chatStore.setLoading(false);
// });
// }
// }, 50); // 调整速度为50ms/字符
// // } else {
// // chatStore.messages.pop();
// // chatStore.messages.push({
// // sender: "ai",
// // content: status.msg
// // });
// // chatStore.setLoading(false);
// // }
// }
} catch (e) { console.error("请求失败:", e); hasValidData.value = false; // 请求失败时设置数据无效
// chatStore.messages.pop();
// chatStore.messages.push({
// sender: "ai",
// content: "AI思考失败,请稍后再试... ",
// });
// chatStore.setLoading(false);
} finally { chatStore.setLoading(false); await chatStore.getUserCount(); } } } }, { deep: true } );
function KlineCanvsEcharts(containerId) { function vwToPx(vw) { console.log((window.innerWidth * vw) / 100, "vwToPx"); return (window.innerWidth * vw) / 100; }
console.log("KLine渲染: 开始处理数据, 容器ID:", containerId);
// 从 chatStore 中获取数据
const messages = chatStore.messages; console.log("KLine渲染: 获取到的消息:", messages); let klineMessageIndex = -1; let klineData = null;
// 查找最近的 K线 消息
// for (let i = messages.length - 1; i >= 0; i--) {
// console.log('KLine渲染: 检查消息:', messages[i]);
// if (messages[i].type === 'kline' && messages[i].chartData) {
// klineMessageIndex = i;
// klineData = messages[i].chartData;
// console.log('KLine渲染: 找到K线消息索引:', klineMessageIndex);
// console.log('KLine渲染: 找到K线数据:', klineData);
// break;
// }
// }
klineMessageIndex = containerId.split("-")[2]; console.log("KLine渲染: 找到K线消息索引:", klineMessageIndex); if ( messages[klineMessageIndex].kline && messages[klineMessageIndex].chartData ) { klineData = messages[klineMessageIndex].chartData; }
var KlineOption = {};
// 检测设备类型
const isMobile = window.innerWidth < 768; const isTablet = window.innerWidth >= 768 && window.innerWidth < 1024; console.log( "KLine渲染: 设备类型", isMobile ? "移动设备" : isTablet ? "平板设备" : "桌面设备" );
if (messages[klineMessageIndex].klineType == 1) { if (!klineData) { console.warn("六色罗盘渲染: 数据无效 - 在chatStore中找不到有效的K线数据"); return; }
// 获取容器元素
const container = document.getElementById(containerId); if (!container) { console.error("六色罗盘渲染: 找不到容器元素:", containerId); return; }
// 创建图表实例
console.log("六色罗盘渲染: 创建图表实例");
try { // 如果已有实例,先销毁
if (chartInstancesMap[containerId]) { console.log("六色罗盘渲染: 销毁已有图表实例"); chartInstancesMap[containerId].dispose(); delete chartInstancesMap[containerId]; }
// 使用普通变量存储实例
chartInstancesMap[containerId] = echarts.init(container); console.log("六色罗盘渲染: 图表实例创建成功"); } catch (error) { console.error("六色罗盘渲染: 图表实例创建失败:", error); return; }
const name = ref("六色罗盘"); const size = ref(16); // PC版字体大小
if (window.innerWidth > 768) { size.value = 25; }
KlineOption = { tooltip: { show: !1, }, series: [ { name: "\u4eea\u8868\u76d8", type: "gauge", center: ["50%", "50%"], radius: "90%", startAngle: 140, endAngle: -140, min: 0, max: 6, precision: 0, splitNumber: 30, // 分成30份
axisLine: { show: !0, lineStyle: { color: [ [0.17, "#FC4407"], [0.33, "#FDC404"], [0.5, "#2D8FFD"], [0.67, "#87CCE7"], [0.83, "#C1F478"], [1, "#8FEB8D"], ], width: 20, }, }, axisTick: { show: !0, splitNumber: 9, length: 8, lineStyle: { color: "#eee", width: 1, type: "solid", }, }, axisLabel: { show: true, formatter: function (v) {}, textStyle: { color: "auto", }, }, // 中途切割
splitLine: { show: !0, length: 20, lineStyle: { color: "#eee", width: 2, type: "solid", }, }, pointer: { length: "80%", width: 8, color: "auto", }, title: { show: !0, offsetCenter: ["-65%", -10], textStyle: { color: "#333", fontSize: 15, }, }, detail: { show: !0, backgroundColor: "rgba(0,0,0,0)", borderWidth: 0, borderColor: "#ccc", width: 100, height: 40, offsetCenter: ["-90%", 0], // name位置
formatter: function () { return name.value; }, textStyle: { color: "auto", fontSize: size.value, // 字体尺寸
}, }, data: [{ value: klineData }], }, ], }; } else if (messages[klineMessageIndex].klineType == 2) { if (!klineData || !klineData.Kline) { console.warn("KLine渲染: 数据无效 - 在chatStore中找不到有效的K线数据"); return; }
// 获取容器元素
const container = document.getElementById(containerId); if (!container) { console.error("KLine渲染: 找不到容器元素:", containerId); return; }
// 创建图表实例
console.log("KLine渲染: 创建图表实例"); try { // 如果已有实例,先销毁
if (chartInstancesMap[containerId]) { console.log("KLine渲染: 销毁已有图表实例"); chartInstancesMap[containerId].dispose(); delete chartInstancesMap[containerId]; }
// 使用普通变量存储实例
chartInstancesMap[containerId] = echarts.init(container); console.log("KLine渲染: 图表实例创建成功"); } catch (error) { console.error("KLine渲染: 图表实例创建失败:", error); return; }
const data = klineData.Kline; console.log("KLine渲染: Kline数据", data);
// 切割数据方法
const splitData = (a) => { console.log("KLine渲染: 开始数据切割"); const categoryData = []; let values = []; for (let i = 0; i < a.length; i++) { categoryData.push(a[i][0]); values.push([a[i][1], a[i][2], a[i][3], a[i][4]]); } console.log("KLine渲染: 日期数据点数量", categoryData.length); console.log("KLine渲染: 值数据点数量", values.length); return { categoryData, values }; };
// 给配置项
console.log("KLine渲染: 开始配置图表选项");
const arr1 = []; const arr2 = []; const arr3 = []; const arr4 = []; const changeColorKline = (QSXH, KLine20) => { if (QSXH) { QSXH.map((item) => { KLine20.map((kline_item) => { if (item[1] == 1 && item[0] == kline_item[0]) { arr1.push(kline_item); arr2.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr3.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr4.push([ kline_item[0], null, null, null, null, null, null, null, ]); } if (item[1] == 2 && item[0] == kline_item[0]) { arr2.push(kline_item); arr1.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr3.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr4.push([ kline_item[0], null, null, null, null, null, null, null, ]); } if (item[1] == 3 && item[0] == kline_item[0]) { arr3.push(kline_item); arr2.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr1.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr4.push([ kline_item[0], null, null, null, null, null, null, null, ]); } if (item[1] == 4 && item[0] == kline_item[0]) { arr4.push(kline_item); arr2.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr3.push([ kline_item[0], null, null, null, null, null, null, null, ]); arr1.push([ kline_item[0], null, null, null, null, null, null, null, ]); } }); }); } }; console.log(arr1, arr2, arr3, arr4); changeColorKline(data.QSXH, data.KLine20); var dealData = splitData(data.KLine20); var dealData1 = splitData(arr1); var dealData2 = splitData(arr2); var dealData3 = splitData(arr3); var dealData4 = splitData(arr4); var dealGnBullData = data.JN; function processMAData(data) { let processedData = []; data.forEach((item, idx) => { processedData.push({ date: item[0], value: item[1], type: item[2], }); }); // 当某一种type只存在一天,设置另一种type透明
let singleTypeRed = [{ min: 0, max: 0, color: "#000" }]; let singleTypeYellow = [{ min: 0, max: 0, color: "#000" }]; let singleTypeGreen = [{ min: 0, max: 0, color: "#000" }]; for (let i = 1; i < processedData.length; i++) { if (processedData[i].type !== processedData[i - 1].type) { if ( i == processedData.length - 1 || (processedData[i].type !== processedData[i + 1].type && processedData[i - 1].type === processedData[i + 1].type) ) { if (processedData[i - 1].type === 0) { singleTypeGreen.push({ min: i - 1, max: i, color: "rgba(0,0,0,0)", }); } else if (processedData[i - 1].type === 1) { singleTypeRed.push({ min: i - 1, max: i, color: "rgba(0,0,0,0)", }); } else if (processedData[i - 1].type === 2) { singleTypeYellow.push({ min: i - 1, max: i, color: "rgba(0,0,0,0)", }); } } } if (processedData[i].type !== processedData[i - 1].type) { if (processedData[i].type == 0) { processedData[i - 1].isTransitionGreen = 1; } else if (processedData[i].type == 1) { processedData[i - 1].isTransitionRed = 1; } else if (processedData[i].type == 2) { processedData[i - 1].isTransitionYellow = 1; } // // 创建过渡点,使用前一个点的值
// processedData[i - 1].isTransition = true
} } let greenData = []; let redData = []; let yellowData = [];
processedData.forEach((item, idx) => { const point = [item.date, item.value];
if (item.type === 0) { greenData.push(point); redData.push([item.date, "-"]); yellowData.push([item.date, "-"]);
// if (item.isTransition) {
// redData[redData.length - 1] = [processedData[idx].date, processedData[idx].value]
// yellowData[yellowData.length - 1] = [processedData[idx].date, processedData[idx].value]
// }
if (item.isTransitionGreen) { greenData[greenData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } else if (item.isTransitionRed) { redData[redData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } else if (item.isTransitionYellow) { yellowData[yellowData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } } else if (item.type === 1) { redData.push(point); greenData.push([item.date, "-"]); yellowData.push([item.date, "-"]);
// if (item.isTransition) {
// greenData[greenData.length - 1] = [processedData[idx].date, processedData[idx].value]
// yellowData[yellowData.length - 1] = [processedData[idx].date, processedData[idx].value]
// }
if (item.isTransitionGreen) { greenData[greenData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } else if (item.isTransitionRed) { redData[redData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } else if (item.isTransitionYellow) { yellowData[yellowData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } } else if (item.type === 2) { redData.push([item.date, "-"]); greenData.push([item.date, "-"]); yellowData.push(point);
// if (item.isTransition) {
// greenData[greenData.length - 1] = [processedData[idx].date, processedData[idx].value]
// redData[redData.length - 1] = [processedData[idx].date, processedData[idx].value]
// }
if (item.isTransitionGreen) { greenData[greenData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } else if (item.isTransitionRed) { redData[redData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } else if (item.isTransitionYellow) { yellowData[yellowData.length - 1] = [ processedData[idx].date, processedData[idx].value, ]; } } });
return { greenData: greenData, redData: redData, yellowData: yellowData, singleTypeGreen: singleTypeGreen, singleTypeRed: singleTypeRed, singleTypeYellow: singleTypeYellow, }; } const maData = processMAData(data.FCX); const maDuchiData = processMAData(data.DNC); if (data.FCX[0][1] == "-1") { maData.greenData = []; maData.redData = []; maData.yellowData = []; } const processBarData = (data) => { const barData = []; const markPointData = []; data.forEach((item) => { let color; switch (item[4]) { case 1: color = "#13E113"; break; case 2: color = "#FF0E00"; break; case 3: color = "#0000FE"; break; case 4: color = "#1397FF"; break; } barData.push({ value: item[5], itemStyle: { normal: { color: color, }, }, });
if (item[1] === 1) { markPointData.push({ coord: [item[0], item[5]], symbol: "image://https://d31zlh4on95l9h.cloudfront.net/images/5iujb101000d5si3v3hr7w2vg0h43z1u.png", symbolSize: [30, 30], label: { normal: { color: "rgba(0, 0, 0, 0)", }, }, }); }
if (item[2] === 1) { markPointData.push({ coord: [item[0], item[5] / 2], symbol: "image://https://d31zlh4on95l9h.cloudfront.net/images/5iujaz01000d5si016bxdf6vh0377d2h.png", symbolSize: [30, 30], label: { normal: { color: "rgba(0, 0, 0, 0)", }, }, }); }
if (item[3] === 1) { markPointData.push({ coord: [item[0], 0], symbol: "image://https://d31zlh4on95l9h.cloudfront.net/images/5iujb001000d5shzls0tmd4vs0e5tdrw.png", symbolSize: [30, 30], label: { normal: { color: "rgba(0, 0, 0, 0)", }, }, }); } }); return { barData, markPointData }; }; const { barData, markPointData } = processBarData(dealGnBullData); KlineOption = { legend: [ { textStyle: { color: "black", fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8), }, width: "100%", top: window.innerWidth > 768 ? "0%" : "-1%", left: "center", itemGap: window.innerWidth > 768 ? 20 : 10, itemWidth: 10, itemHeight: 10, data: [ { name: "进攻K线", itemStyle: { color: "rgb(255,0,0)", }, }, { name: "防守K线", itemStyle: { color: "red", }, }, { name: "推进K线", itemStyle: { color: "orange", }, }, { name: "撤退K线", itemStyle: { color: "rgb(84,252,252)", }, }, ], }, { textStyle: { color: "black", fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8), }, orient: "horizontal", top: window.innerWidth > 768 ? "3%" : "2%", width: "100%", left: "center", itemGap: 15, data: [ { name: "{green|━}{red|━} " + "牵牛绳", icon: "none", textStyle: { rich: { green: { color: "green", fontSize: window.innerWidth > 768 ? 20 : 10, }, red: { color: "red", fontSize: window.innerWidth > 768 ? 20 : 10, }, }, }, }, { name: "龙线", }, { name: "虫线", }, ], }, { textStyle: { color: "black", fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8), }, orient: "horizontal", top: window.innerWidth > 768 ? "72%" : "64%", width: "100%", left: "center", itemGap: 15, data: [ { name: "{green|━}{red|━} " + "度牛尺", icon: "none", textStyle: { rich: { green: { color: "green", fontSize: window.innerWidth > 768 ? 20 : 10, }, red: { color: "red", fontSize: window.innerWidth > 768 ? 20 : 10, }, }, }, }, ], }, ], tooltip: { formatter: function (a, b, d) { if (a[0].seriesIndex == 0) { const KlineTag = ref([]); const AIBullTag = ref([]); KlineTag.value = a.find((item) => item.data[1])?.data || []; AIBullTag.value = a.slice(4).find((item) => item.data[1] !== "-")?.data || []; // console.log(AIBullTag.value)
return ( a[0].name + "<br/>" + "开盘价" + ":" + KlineTag.value[1] + "<br/>" + "收盘价" + ":" + KlineTag.value[2] + "<br/>" + "最低价" + ":" + KlineTag.value[3] + "<br/>" + "最高价" + ":" + KlineTag.value[4] + "<br/>" + "牵牛绳" + ":" + AIBullTag.value[1] ); } if (a[0].seriesIndex == 4) { let formattedVolume; if (a[0].data.value >= 10000) { formattedVolume = (a[0].data.value / 10000).toFixed(2) + "w"; } else { formattedVolume = a[0].data.value; } return a[0].name + "<br/>" + "成交量" + ":" + formattedVolume; } if ([10, 11, 12].includes(a[0].seriesIndex)) { const duchiData = a.find( (item) => item.data && item.data[1] !== "-" ); return duchiData ? a[0].axisValue + "<br/>" + "度牛尺" + ":" + duchiData.data[1] : null; } }, trigger: "axis", axisPointer: { type: "cross", }, backgroundColor: "rgba(119, 120, 125, 0.6)", borderWidth: 1, borderColor: "#77787D", padding: 10, textStyle: { color: "#fff", }, }, axisPointer: { link: [ { xAxisIndex: "all", }, ], label: { backgroundColor: "#77787D", }, }, toolbox: { show: false, }, grid: [ { // left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '4%' : '2.5%',
top: window.innerWidth > 768 ? "10%" : "5%", height: window.innerWidth > 768 ? "36%" : "34%", containLabel: false, }, { // left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '4%' : '2.5%',
top: window.innerWidth > 768 ? "50%" : "42%", height: window.innerWidth > 768 ? "20%" : "22%", containLabel: false, }, { // left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '4%' : '2.5%',
top: window.innerWidth > 768 ? "78%" : "70%", height: window.innerWidth > 768 ? "20%" : "22%", containLabel: false, }, ], xAxis: [ { type: "category", data: dealData.categoryData, boundaryGap: true, axisLine: { onZero: false }, splitLine: { show: false }, min: "dataMin", max: "dataMax", axisPointer: { z: 100, }, axisLine: { lineStyle: { color: "black", }, }, //
axisLabel: { show: false }, axisTick: { show: false }, }, { type: "category", gridIndex: 1, data: dealData.categoryData, boundaryGap: true, axisLine: { lineStyle: { color: "black" } }, axisLabel: { show: false, interval: "auto", }, }, { type: "category", gridIndex: 2, data: dealData.categoryData, boundaryGap: true, axisLine: { lineStyle: { color: "black" } }, axisLabel: { show: true, interval: "auto", }, }, ], yAxis: [ { scale: true, gridIndex: 0, position: "left", axisLabel: { inside: false, align: "right", fontSize: window.innerWidth > 768 ? 15 : 10, }, axisLine: { show: true, lineStyle: { fontSize: "", color: "black", }, }, axisTick: { show: false }, splitLine: { show: false }, }, { scale: true, gridIndex: 1, splitNumber: 4, min: 0, minInterval: 1, axisLabel: { show: true, fontSize: window.innerWidth > 768 ? 15 : 10, margin: 8, formatter: (value) => { if (value >= 1000000000) { return (value / 1000000000).toFixed(1) + "B"; } else if (value >= 1000000) { return (value / 1000000).toFixed(1) + "M"; } else if (value >= 10000) { return (value / 10000).toFixed(1) + "W"; } return value.toFixed(0); }, }, axisLine: { show: true, lineStyle: { color: "black" } }, axisTick: { show: false }, splitLine: { show: true, lineStyle: { type: "dashed" } }, boundaryGap: ["20%", "20%"], }, { type: "value", gridIndex: 2, min: 0, max: 100, axisLabel: { show: true, fontSize: window.innerWidth > 768 ? 15 : 10, formatter: function (value) { var customValues = [0, 20, 50, 80, 100]; return customValues.indexOf(value) > -1 ? value : ""; }, }, axisLine: { show: true, lineStyle: { color: "black", }, }, axisTick: { show: false, }, splitNumber: 10, splitLine: { show: true, lineStyle: { type: "dashed", color: "#fff", width: 1, }, interval: function (index, value) { return [20, 50, 80, 100].indexOf(value) > -1; }, }, }, ], dataZoom: [ { type: "inside", xAxisIndex: [0, 1, 2], start: 55, end: 100, }, { show: true, xAxisIndex: [0, 1, 2], type: "slider", top: window.innerWidth > 768 ? "95%" : "96%", left: window.innerWidth > 768 ? "10%" : "8%", start: 98, end: 100, }, ], visualMap: [ { type: "piecewise", show: false, pieces: maData.singleTypeGreen, outOfRange: { color: "green", }, dimension: 0, seriesIndex: 7, }, { type: "piecewise", show: false, pieces: maData.singleTypeRed, outOfRange: { color: "red", }, dimension: 0, seriesIndex: 8, }, { type: "piecewise", show: false, pieces: maData.singleTypeYellow, outOfRange: { color: "yellow", }, dimension: 0, seriesIndex: 9, }, ], series: [ { name: "进攻K线", type: "candlestick", barWidth: "50%", data: dealData1.values, xAxisIndex: 0, yAxisIndex: 0, itemStyle: { normal: { color: "rgb(255,0,0)", color0: "rgb(255,0,0)", borderColor: "rgb(255,0,0)", borderColor0: "rgb(255,0,0)", }, }, gridIndex: 0, }, //
{ name: "推进K线", type: "candlestick", barWidth: "50%", data: dealData2.values, xAxisIndex: 0, yAxisIndex: 0, itemStyle: { normal: { color: "rgb(0,0,252)", color0: "rgb(0,0,252)", borderColor: "rgb(0,0,252)", borderColor0: "rgb(0,0,252)", }, }, gridIndex: 0, }, { name: "防守K线", type: "candlestick", barWidth: "50%", data: dealData3.values, xAxisIndex: 0, yAxisIndex: 0, itemStyle: { normal: { color: "orange", color0: "orange", borderColor: "orange", borderColor0: "orange", }, }, gridIndex: 0, }, { name: "撤退K线", type: "candlestick", barWidth: "50%", data: dealData4.values, xAxisIndex: 0, yAxisIndex: 0, itemStyle: { normal: { color: "rgb(84,252,252)", color0: "rgb(84,252,252)", borderColor: "rgb(84,252,252)", borderColor0: "rgb(84,252,252)", }, }, gridIndex: 0, }, { name: "成交量", type: "bar", barWidth: "70%", xAxisIndex: 1, yAxisIndex: 1, data: barData, markPoint: { data: markPointData, label: { show: false, }, }, }, { name: "{green|━}{red|━} " + "牵牛绳", type: "line", data: [], smooth: true, symbol: "none", xAxisIndex: 0, yAxisIndex: 0, showSymbol: false, lineStyle: { opacity: 0, }, itemStyle: { normal: { color: "green", }, }, gridIndex: 0, }, { name: "{green|━}{red|━} " + "度牛尺", type: "line", data: [], smooth: true, symbol: "none", xAxisIndex: 0, yAxisIndex: 0, showSymbol: false, lineStyle: { opacity: 0, }, itemStyle: { normal: { color: "green", }, }, gridIndex: 0, }, { name: "虫线", type: "line", data: maData.greenData, smooth: true, symbol: "none", xAxisIndex: 0, yAxisIndex: 0, itemStyle: { normal: { color: "green", lineStyle: { width: 2, type: "solid", }, }, }, gridIndex: 0, }, { name: "龙线", type: "line", data: maData.redData, smooth: true, symbol: "none", xAxisIndex: 0, yAxisIndex: 0, itemStyle: { normal: { color: "red", lineStyle: { width: 2, type: "solid", }, }, }, gridIndex: 0, }, { name: "黄色", type: "line", data: maData.yellowData, smooth: true, symbol: "none", xAxisIndex: 0, yAxisIndex: 0, itemStyle: { normal: { color: "yellow", lineStyle: { width: 2, type: "solid", }, }, }, gridIndex: 0, }, { name: "背景区域", type: "line", data: [], xAxisIndex: 2, yAxisIndex: 2, markArea: { silent: true, itemStyle: { normal: { opacity: 1, }, }, label: { normal: { show: true, position: "insideRight", fontSize: window.innerWidth > 768 ? 16 : 12, fontWeight: "bold", color: "#13E113", distance: 10, }, }, data: [ [ { yAxis: 0, itemStyle: { normal: { color: "#CFFFCF", }, }, label: { normal: { formatter: "度牛区", }, }, }, { yAxis: 20, }, ], [ { yAxis: 20, itemStyle: { normal: { color: "#A6FFFF", }, }, }, { yAxis: 40, }, ], [ { yAxis: 40, itemStyle: { normal: { color: "#FFF686", }, }, }, { yAxis: 60, }, ], [ { yAxis: 60, itemStyle: { normal: { color: "#FFD2B3" }, }, }, { yAxis: 80, }, ], [ { yAxis: 80, itemStyle: { normal: { color: "#FFB8B8" }, }, label: { normal: { formatter: "度牛区", color: "#FF0000", position: "insideLeft", distance: 10, }, }, }, { yAxis: 100, }, ], ], }, }, { name: "度牛尺", type: "line", data: maDuchiData.greenData, symbol: "none", xAxisIndex: 2, yAxisIndex: 2, itemStyle: { normal: { color: "green", lineStyle: { width: 2, type: "solid", }, }, }, gridIndex: 2, markPoint: { symbol: "rect", symbolSize: (value, params) => { const width = window.innerWidth; const baseHeight = 36; if (width <= 375) { return [2, 16]; } else if (width <= 768) { return [2, 24]; } return [2, baseHeight]; }, itemStyle: { normal: { label: { show: false, }, }, }, data: [ ...maDuchiData.greenData .map((item) => { if (item[1] === 0) { return { coord: [item[0], 20], symbolOffset: window.innerWidth > 768 ? [0, 20] : [0, 12], itemStyle: { color: "#00ff00", }, }; } }) .filter(Boolean), ], }, }, { type: "line", data: maDuchiData.redData, // smooth: true,
symbol: "none", xAxisIndex: 2, yAxisIndex: 2, itemStyle: { normal: { color: "red", lineStyle: { width: 2, type: "solid", }, }, }, gridIndex: 2, markPoint: { symbol: "rect", symbolSize: (value, params) => { const width = window.innerWidth; const baseHeight = 36; if (width <= 375) { return [2, 16]; } else if (width <= 768) { return [2, 24]; } return [2, baseHeight]; }, itemStyle: { normal: { label: { show: false, }, }, }, data: [ ...maDuchiData.redData .map((item) => { if (item[1] === 100) { return { coord: [item[0], 80], symbolOffset: window.innerWidth > 768 ? [0, -20] : [0, -12], itemStyle: { color: "#ff0000", }, }; } }) .filter(Boolean), ], }, }, { name: "辅助线", type: "line", data: [], xAxisIndex: 2, yAxisIndex: 2, markLine: { silent: true, symbol: "none", lineStyle: { color: "#000000", width: 3, type: "solid", }, data: [{ yAxis: 20 }], }, }, { name: "辅助线", type: "line", data: [], xAxisIndex: 2, yAxisIndex: 2, markLine: { silent: true, symbol: "none", lineStyle: { color: "#000000", width: 3, type: "solid", }, data: [{ yAxis: 50 }], }, }, { name: "辅助线", type: "line", data: [], xAxisIndex: 2, yAxisIndex: 2, markLine: { silent: true, symbol: "none", lineStyle: { color: "#000000", width: 3, type: "solid", }, data: [{ yAxis: 80 }], }, }, { name: "辅助线", type: "line", data: [], xAxisIndex: 2, yAxisIndex: 2, markLine: { silent: true, symbol: "none", lineStyle: { color: "#000000", width: 3,
type: "solid", }, data: [{ yAxis: 100 }], }, }, ], }; }
console.log("KLine渲染: 图表配置完成");
try { // 应用配置
console.log("KLine渲染: 开始设置图表选项"); chartInstancesMap[containerId].setOption(KlineOption); console.log("KLine渲染: 图表选项设置成功");
// 窗口大小变化时重新渲染图表
const resizeFunc = _.throttle( function () { console.log("窗口大小改变,调整图表大小"); if ( chartInstancesMap[containerId] && !chartInstancesMap[containerId].isDisposed() ) { // 如果设备类型发生变化,重新渲染
const newIsMobile = window.innerWidth < 768; const newIsTablet = window.innerWidth >= 768 && window.innerWidth < 1024;
if (newIsMobile !== isMobile || newIsTablet !== isTablet) { console.log("设备类型变化,重新渲染图表"); KlineCanvsEcharts(containerId); return; }
chartInstancesMap[containerId].resize(); } }, 1000, { trailing: false } );
// 给resize事件绑定一个特定的函数名,便于后续移除
window[`resize_${containerId}`] = resizeFunc;
// 绑定resize事件
window.removeEventListener("resize", window[`resize_${containerId}`]); window.addEventListener("resize", window[`resize_${containerId}`]);
console.log("KLine渲染: 图表渲染完成"); } catch (error) { console.error("KLine渲染: 图表渲染出错", error); } }
watch( () => audioStore.isVoiceEnabled, (newVal) => { // 添加状态锁定逻辑
if (newVal === audioStore.lastVoiceState) return; audioStore.lastVoiceState = newVal;
if (newVal) { console.log("开启语音播放"); // 添加重试机制
const tryPlay = () => { if (!audioStore.ttsUrl) return; // 新增空值判断
if (audioStore.soundInstance?.playing()) return; playAudio(audioStore.ttsUrl); setTimeout(() => { if (!audioStore.soundInstance?.playing()) { Howler.unload(); } }, 1000); }; tryPlay(); } else { console.log("关闭语音播放"); pauseAudio(); // 强制停止并释放资源
// Howler.stop();
// Howler.unload();
// if (audioStore.soundInstance) {
// audioStore.soundInstance.off(); // 移除所有事件监听
// audioStore.soundInstance = null;
// }
} }, { immediate: true } );
watch( () => dataStore.activeTabIndex, (newVal) => { setTimeout(() => { console.log("activeTabIndex变化:", newVal); // 当标签页切换回来时,重新渲染所有图表
if (newVal === 0) { console.log("切换到AI聊天页,重新渲染图表"); // 延迟执行以确保DOM已渲染
renderAllKlineCharts(); } }, 300); }, { immediate: true } // 添加immediate属性,确保初始化时执行一次
);
// 添加渲染所有K线图的方法
function renderAllKlineCharts() { console.log("重新渲染所有K线图");
// 查找所有K线消息
const messages = chatStore.messages; for (let i = 0; i < messages.length; i++) { if (messages[i].kline && messages[i].chartData) { const containerId = `kline-container-${i}`; console.log(`尝试渲染K线图: ${containerId}`);
// 确保DOM已经渲染
const container = document.getElementById(containerId); if (container) { // 渲染图表
KlineCanvsEcharts(containerId); } else { console.warn(`找不到容器: ${containerId}`); } } } }
// 初始化随机GIF
onMounted(() => { // 初始化marked组件
marked.setOptions({ breaks: true, // 支持换行符转换为 <br>
gfm: true, // 启用 GitHub Flavored Markdown
sanitize: false, // 不清理 HTML(谨慎使用)
smartLists: true, // 智能列表
smartypants: true, // 智能标点符号
xhtml: false, // 不使用 XHTML 输出
renderer: renderer, });
const random = Math.floor(Math.random() * 6) + 1; currentGif.value = gifList[random];
console.log("组件挂载完成");
// 添加DOM变化监听器
const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === "childList" && mutation.addedNodes.length) { // 检查是否添加了图表容器
const containers = document.querySelectorAll( '[id^="kline-container-"]' ); if (containers.length) { // console.log("DOM变化监听到K线容器:", Array.from(containers).map(el => el.id));
} } }); });
// 开始监听DOM变化
observer.observe(document.body, { childList: true, subtree: true }); });
// 组件卸载时清理所有图表实例和事件监听器
onUnmounted(() => { // 清理所有图表实例
Object.keys(chartInstancesMap).forEach((key) => { if (chartInstancesMap[key]) { // 移除resize事件监听
if (window[`resize_${key}`]) { window.removeEventListener("resize", window[`resize_${key}`]); delete window[`resize_${key}`]; }
// 销毁图表实例
chartInstancesMap[key].dispose(); delete chartInstancesMap[key]; } }); }); </script>
<template> <div class="chat-container"> <!-- GIF区域 --> <div class="gif-area"> <img :src="currentGif" alt="AI动画" /> </div>
<div v-for="(msg, index) in chatMsg" :key="index" :class="{ 'message-bubble': true, [msg.sender]: msg.sender, [msg.class]: msg.class, }" > <div v-if="msg.type === 'kline'" class="kline-container"> <div :id="'kline-container-' + index" class="chart-mount-point"> <div v-if="!msg.hasValidData" class="no-data-message"> <p>暂无K线数据</p> </div> </div> </div> <div v-else-if="msg.type == 'ing'"> <div v-if="msg.flag"> <span>{{ msg.content }}</span> <span class="loading-dots"> <span class="dot">.</span> <span class="dot">.</span> <span class="dot">.</span> <span class="dot">.</span> <span class="dot">.</span> <span class="dot">.</span> </span> </div> <div v-else v-html="msg.content"></div> </div> <div v-else-if="msg.type == 'title1'" style="display: flex; width: 100%"> <div class="mainTitle"> {{ msg.content }} </div> <div class="date"> {{ msg.date }} </div> </div> <div v-else-if="msg.type == 'title2'" class="title2"> <img class="title1Img" :src="title1" alt="出错了" /> </div> <div v-else-if="msg.type == 'title3'" class="title3"> <img class="title2Img" :src="msg.content" alt="出错了" /> </div> <div v-else-if="msg.type == 'content1'" class="content1"> <div v-if="msg.kline" class="kline-container content1chart"> <div :id="'kline-container-' + index" class="chart-mount-point"> <div v-if="!msg.hasValidData" class="no-data-message"> <p>暂无数据</p> </div> </div> </div> <div v-else class="content1Text"> <div v-html="msg.content" class="text1"></div> </div> </div> <div v-else-if="msg.type == 'content2'" class="content2"> <div class="kline-container content2chart"> <div :id="'kline-container-' + index" class="chart-mount-point"> <div v-if="!msg.hasValidData" class="no-data-message"> <p>暂无数据</p> </div> </div> </div> </div> <div v-else-if="msg.type == 'content3'" class="content3"> <div class="content3Text"> <div v-html="msg.content" class="text3"></div> </div> </div> <div v-else-if="msg.type == 'mianze'" class="mianze"> <div v-html="msg.content"></div> </div> <div v-else v-html="msg.content"></div> </div> </div> </template>
<style scoped> .chat-container { display: flex; flex-direction: column; overflow: hidden; }
.gif-area { /* position: relative; */ /* height: 30vh; */ display: flex; flex-direction: column; justify-content: center; align-items: center; flex-shrink: 0; /* 防止GIF区域被压缩 */ }
.gif-area img { width: 30%; /* 改为百分比单位 */ min-width: 200px; /* 最小尺寸 */ max-width: 400px; /* 最大尺寸 */ height: auto; left: 50%; transition: all 0.3s; /* 添加过渡效果 */ }
.message-area { margin-top: 2%; flex: 1; /* 消息区域占据剩余空间 */ overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; }
.marquee-container { /* position: absolute; */ bottom: 0; width: 100%; /* ga */ }
.marquee-row { white-space: nowrap; overflow: visible; padding: 8px 0; width: 100%; }
.marquee-item { display: inline-block; margin: 0 15px; padding: 8px 20px; background: rgba(255, 255, 255, 0.9); /* 白色背景 */ border-radius: 10px; /* 圆角矩形 */ color: #333; /* 文字颜色改为深色 */ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 添加阴影 */ transition: all 0.3s; transition: color 0.3s; }
.top { animation: marquee 25s linear infinite; /* 默认动画是运行状态 */ animation-play-state: running; }
.bottom { animation: marquee 15s linear infinite reverse; /* 默认动画是运行状态 */ animation-play-state: running; }
/* 添加PC端专用速度 */ @media (min-width: 768px) { .top { animation-duration: 35s; /* PC端改为35秒 */ }
.bottom { animation-duration: 35s; /* PC端改为35秒 */ } }
@keyframes marquee { 0% { transform: translateX(100%); }
100% { transform: translateX(-250%); } }
.loading-dots { display: inline-block; }
.dot { opacity: 0.4; animation: loading 1.4s infinite; }
.dot:nth-child(1) { animation-delay: 0s; } .dot:nth-child(2) { animation-delay: 0.2s; } .dot:nth-child(3) { animation-delay: 0.4s; } .dot:nth-child(4) { animation-delay: 0.6s; } .dot:nth-child(5) { animation-delay: 0.8s; } .dot:nth-child(6) { animation-delay: 1s; }
@keyframes loading { 0%, 60%, 100% { opacity: 0.4; } 30% { opacity: 1; } }
.message-bubble { max-width: 80%; margin: 10px 0px; padding: 15px 20px;
position: relative; }
.message-bubble.user { color: #6d22f8; background: white; font-weight: bold; margin-left: auto; border-radius: 10px; margin-right: 20px; /* border-bottom-right-radius: 5px; */ }
.message-bubble.ai { background: #2b378d; color: #ffffff; margin: 0 auto; /* border-bottom-left-radius: 5px; */ }
.message-bubble.ing { background: #ffffff; color: #000000; font-weight: bold; border-radius: 10px; margin-left: 20px; margin-right: auto; /* border-bottom-left-radius: 5px; */ }
.message-bubble.ai.title1 { width: 100%; display: flex; border-radius: 10px 10px 0px 0px; /* border-bottom-left-radius: 5px; */ }
.mainTitle { font-size: 16px; font-weight: bold; background-image: url("@/assets/img/AiEmotion/bk01.png"); background-repeat: no-repeat; background-size: 100% 100%; min-width: 200px; width: 20vw; height: 50px; padding: 5px 0px 0px 0px; display: flex; justify-content: center; align-items: center; }
.date { font-size: 18px; font-weight: bold; margin-left: auto; /* width: 100px; */ display: flex; justify-content: center; align-items: center; }
.message-bubble.ai.title2 { width: 100%; display: flex; justify-content: center; align-items: center; }
.title1Img { max-width: 500px; width: 80vw; }
.message-bubble.ai.title3 { width: 100%; display: flex; justify-content: center; align-items: center; }
.title2Img { max-width: 500px; width: 90vw; }
.message-bubble.ai.content1 { width: 100%; display: flex; justify-content: center; align-items: center; }
.content1chart { background-image: url("@/assets/img/AIchat/罗盘边框.png"); background-repeat: no-repeat; background-size: 100% 100%; width: 50vw; min-width: 350px; display: flex; justify-content: center; align-items: center; }
.content1Text { background-image: url("@/assets/img/AIchat/框.png"); background-repeat: no-repeat; background-size: 100% 100%; width: 50vw; min-width: 350px; /* height: 20vw; */ /* max-height: 400px; */ padding: 5% 0; }
.text1 { font-weight: bold; /* margin-left: 6%; */ /* margin-bottom: 10px; */ margin: 0px 6% 10px 6%; font-size: 30px; }
.message-bubble.ai.content2 { width: 100%; display: flex; justify-content: center; align-items: center; }
.content2chart { background-image: url("@/assets/img/AIchat/PCbackPic.png"); background-repeat: no-repeat; background-size: 100% 100%; width: 50vw; min-width: 350px; display: flex; justify-content: center; align-items: center; }
.message-bubble.ai.content3 { width: 100%; display: flex; justify-content: center; align-items: center; }
.content3Text { background-image: url("@/assets/img/AIchat/边框.png"); background-repeat: no-repeat; background-size: 100% 100%; width: 50vw; min-width: 350px; /* height: 20vw; */ /* max-height: 400px; */ padding: 5% 0px; }
.text3 { /* font-weight: bold; */ /* margin-left: 6%; */ /* margin-bottom: 10px; */ margin: 0px 6% 10px 6%; font-size: 30px; }
.message-bubble.ai.mianze { width: 100%; text-align: center; font-weight: bold; font-size: 24px; border-radius: 0px 0px 10px 10px; }
.kline-container { margin-top: 10px; /* 最小高度 */ min-height: 320px; /* 视口高度单位 */ height: 40vh; width: 50vw; }
@media (max-width: 768px) { .kline-container { min-width: 75vw; }
.content1Text { width: 77vw; min-width: 0px; /* height: 20vw; */ /* min-height: 150px; */ }
.text1 { font-size: 20px; }
.content2chart { background-image: url("@/assets/img/AIchat/new-app-bgc.png") !important; height: 100vw; }
.content3Text { width: 77vw; min-width: 0px; /* height: 20vw; */ /* min-height: 150px; */ }
.text3 { font-size: 20px; }
.message-bubble.ai.mianze { font-size: 18px; } }
.kline-container .chart-mount-point { display: flex; justify-content: center; align-items: center; height: 80%; width: 90%; } </style>
|