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.
3262 lines
91 KiB
3262 lines
91 KiB
<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>
|