|
@ -1,5 +1,5 @@ |
|
|
<script setup> |
|
|
<script setup> |
|
|
import { ref, onMounted, watch, nextTick } from "vue"; |
|
|
|
|
|
|
|
|
import { ref, onMounted, watch, nextTick, reactive, onUnmounted } from "vue"; |
|
|
import { ElDialog } from "element-plus"; |
|
|
import { ElDialog } from "element-plus"; |
|
|
import { getReplyStreamAPI, getReplyAPI, TTSAPI, getQuestionAPI, qsArpAamClickAPI } from "../api/AIxiaocaishen"; |
|
|
import { getReplyStreamAPI, getReplyAPI, TTSAPI, getQuestionAPI, qsArpAamClickAPI } from "../api/AIxiaocaishen"; |
|
|
import { useUserStore } from '../store/userPessionCode' |
|
|
import { useUserStore } from '../store/userPessionCode' |
|
@ -10,7 +10,6 @@ import { marked } from 'marked'; // 引入marked库 |
|
|
import katex from 'katex'; // 引入 KaTeX 库 |
|
|
import katex from 'katex'; // 引入 KaTeX 库 |
|
|
import { htmlToText } from 'html-to-text'; |
|
|
import { htmlToText } from 'html-to-text'; |
|
|
import { Howl, Howler } from 'howler'; |
|
|
import { Howl, Howler } from 'howler'; |
|
|
import KLine from './Echarts/KLine.vue'; |
|
|
|
|
|
import * as echarts from 'echarts' |
|
|
import * as echarts from 'echarts' |
|
|
|
|
|
|
|
|
import AIgif1 from '@/assets/img/AIchat/AIgif1.gif' |
|
|
import AIgif1 from '@/assets/img/AIchat/AIgif1.gif' |
|
@ -140,6 +139,14 @@ const pauseAudio = () => { |
|
|
const chatMsg = computed(() => chatStore.messages) |
|
|
const chatMsg = computed(() => chatStore.messages) |
|
|
const props = defineProps({ |
|
|
const props = defineProps({ |
|
|
messages: Array, |
|
|
messages: Array, |
|
|
|
|
|
chartData: { |
|
|
|
|
|
type: Object, |
|
|
|
|
|
default: null |
|
|
|
|
|
}, |
|
|
|
|
|
index: { |
|
|
|
|
|
type: Number, |
|
|
|
|
|
required: true |
|
|
|
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 打字机效果 |
|
|
// 打字机效果 |
|
@ -168,6 +175,11 @@ const typeWriter = (text, callback) => { |
|
|
}, 50) // 调整速度(毫秒) |
|
|
}, 50) // 调整速度(毫秒) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const hasValidData = ref(false) |
|
|
|
|
|
|
|
|
|
|
|
// 创建一个非响应式的对象来存储图表实例 |
|
|
|
|
|
const chartInstancesMap = {}; |
|
|
|
|
|
|
|
|
watch( |
|
|
watch( |
|
|
() => props.messages, |
|
|
() => props.messages, |
|
|
async (newVal, oldVal) => { |
|
|
async (newVal, oldVal) => { |
|
@ -258,6 +270,7 @@ watch( |
|
|
const market = ans.value.market; |
|
|
const market = ans.value.market; |
|
|
const data = JSON.parse(ans.value.duobaoData); |
|
|
const data = JSON.parse(ans.value.duobaoData); |
|
|
|
|
|
|
|
|
|
|
|
console.log("处理 K 线数据 - 开始"); |
|
|
console.log(data, "data"); |
|
|
console.log(data, "data"); |
|
|
|
|
|
|
|
|
const Kline20 = { |
|
|
const Kline20 = { |
|
@ -265,24 +278,71 @@ watch( |
|
|
Kline: data.data.AIBull.KLine20 |
|
|
Kline: data.data.AIBull.KLine20 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 打印K线数据结构 |
|
|
|
|
|
console.log("K线数据结构:", Kline20); |
|
|
|
|
|
console.log("K线数据名称:", Kline20.name); |
|
|
|
|
|
console.log("K线数据数组长度:", Kline20.Kline ? Kline20.Kline.length : 0); |
|
|
|
|
|
|
|
|
|
|
|
// 输出第一个数据点作为示例 |
|
|
|
|
|
if (Kline20.Kline && Kline20.Kline.length > 0) { |
|
|
|
|
|
console.log("K线首个数据点:", Kline20.Kline[0]); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置数据有效标志 |
|
|
|
|
|
hasValidData.value = true; |
|
|
|
|
|
console.log("hasValidData设置为:", hasValidData.value); |
|
|
|
|
|
|
|
|
chatStore.messages.pop(); |
|
|
chatStore.messages.pop(); |
|
|
|
|
|
|
|
|
// 先推送K线图消息 |
|
|
// 先推送K线图消息 |
|
|
|
|
|
const klineMessageId = `kline-${Date.now()}`; |
|
|
|
|
|
console.log("生成K线消息ID:", klineMessageId); |
|
|
|
|
|
|
|
|
chatStore.messages.push({ |
|
|
chatStore.messages.push({ |
|
|
sender: "ai", |
|
|
sender: "ai", |
|
|
type: "kline", |
|
|
type: "kline", |
|
|
chartData: Kline20, // 直接保存完整数据 |
|
|
|
|
|
messageId: `kline-${Date.now()}` // 生成唯一ID |
|
|
|
|
|
|
|
|
chartData: Kline20, |
|
|
|
|
|
messageId: klineMessageId, |
|
|
|
|
|
hasValidData: true // 添加hasValidData标志 |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
console.log("K线消息已添加到聊天列表"); |
|
|
|
|
|
|
|
|
// 再推送文字分析内容的消息 |
|
|
// 再推送文字分析内容的消息 |
|
|
chatStore.messages.push({ |
|
|
chatStore.messages.push({ |
|
|
sender: "ai", |
|
|
sender: "ai", |
|
|
content: "AI正在思考中..." |
|
|
content: "AI正在思考中..." |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 将K线数据保存到dataStore,用于其他地方可能的使用 |
|
|
|
|
|
dataStore.setKlineData(Kline20); |
|
|
|
|
|
|
|
|
// 在渲染完成后初始化图表 |
|
|
|
|
|
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 === klineMessageId) { |
|
|
|
|
|
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 if (ans.value.answerN !== "") { |
|
|
} else if (ans.value.answerN !== "") { |
|
|
AIcontent.value = ans.value.answerN; |
|
|
AIcontent.value = ans.value.answerN; |
|
@ -390,6 +450,7 @@ watch( |
|
|
} |
|
|
} |
|
|
catch (e) { |
|
|
catch (e) { |
|
|
console.error('请求失败:', e); |
|
|
console.error('请求失败:', e); |
|
|
|
|
|
hasValidData.value = false; // 请求失败时设置数据无效 |
|
|
chatStore.messages.pop(); |
|
|
chatStore.messages.pop(); |
|
|
chatStore.messages.push({ |
|
|
chatStore.messages.push({ |
|
|
sender: "ai", |
|
|
sender: "ai", |
|
@ -405,25 +466,266 @@ watch( |
|
|
{ deep: true } |
|
|
{ deep: true } |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
// var kLineCharts = document.getElementsByClassName("kLineChart"); |
|
|
|
|
|
// for (var i = 0; i < kLineCharts.length; i++) { |
|
|
|
|
|
// (function (i) { |
|
|
|
|
|
// const data = datatok.Kline |
|
|
|
|
|
// // 切割数据方法 |
|
|
|
|
|
// const spliteDate = (a) => { |
|
|
|
|
|
// const categoryData = [] |
|
|
|
|
|
// let value = [] |
|
|
|
|
|
// for (let i = 0; i < a.length; i++) { |
|
|
|
|
|
// categoryData.push(a[i][0]) |
|
|
|
|
|
// value.push([a[i][1], a[i][2], a[i][3], a[i][4]]) |
|
|
|
|
|
|
|
|
function KlineCanvsEcharts(containerId) { |
|
|
|
|
|
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; |
|
|
// } |
|
|
// } |
|
|
// return { categoryData, value } |
|
|
|
|
|
// } |
|
|
// } |
|
|
// const dealData = spliteDate(data) |
|
|
|
|
|
// var myChart = echarts.init(kLineCharts[i]); |
|
|
|
|
|
|
|
|
klineMessageIndex = containerId.split('-')[2] |
|
|
|
|
|
console.log('KLine渲染: 找到K线消息索引:', klineMessageIndex); |
|
|
|
|
|
if (messages[klineMessageIndex].type === 'kline' && messages[klineMessageIndex].chartData) { |
|
|
|
|
|
klineData = messages[klineMessageIndex].chartData |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.length); |
|
|
|
|
|
|
|
|
|
|
|
// 切割数据方法 |
|
|
|
|
|
const spliteDate = (a) => { |
|
|
|
|
|
console.log('KLine渲染: 开始数据切割'); |
|
|
|
|
|
const categoryData = []; |
|
|
|
|
|
let value = []; |
|
|
|
|
|
for (let i = 0; i < a.length; i++) { |
|
|
|
|
|
categoryData.push(a[i][0]); |
|
|
|
|
|
value.push([a[i][1], a[i][2], a[i][3], a[i][4]]); |
|
|
|
|
|
} |
|
|
|
|
|
console.log('KLine渲染: 日期数据点数量', categoryData.length); |
|
|
|
|
|
console.log('KLine渲染: 值数据点数量', value.length); |
|
|
|
|
|
return { categoryData, value }; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const dealData = spliteDate(data); |
|
|
|
|
|
console.log('KLine渲染: 数据处理完成', dealData); |
|
|
|
|
|
|
|
|
|
|
|
// 检测设备类型 |
|
|
|
|
|
const isMobile = window.innerWidth < 768; |
|
|
|
|
|
const isTablet = window.innerWidth >= 768 && window.innerWidth < 1024; |
|
|
|
|
|
console.log('KLine渲染: 设备类型', isMobile ? '移动设备' : isTablet ? '平板设备' : '桌面设备'); |
|
|
|
|
|
|
|
|
|
|
|
// 给配置项 |
|
|
|
|
|
console.log('KLine渲染: 开始配置图表选项'); |
|
|
|
|
|
const KlineOption = { |
|
|
|
|
|
title: { |
|
|
|
|
|
text: klineData.name, |
|
|
|
|
|
left: 50, |
|
|
|
|
|
}, |
|
|
|
|
|
tooltip: { |
|
|
|
|
|
trigger: 'axis', |
|
|
|
|
|
formatter: function (a, b, d) { |
|
|
|
|
|
let def = |
|
|
|
|
|
a[0].name + |
|
|
|
|
|
'<br/>' + |
|
|
|
|
|
'开盘价' + |
|
|
|
|
|
a[0].data[1] + |
|
|
|
|
|
'<br/>' + |
|
|
|
|
|
'收盘价' + |
|
|
|
|
|
a[0].data[2] + |
|
|
|
|
|
'<br/>' + |
|
|
|
|
|
'最低价' + |
|
|
|
|
|
a[0].data[3] + |
|
|
|
|
|
'<br/>' + |
|
|
|
|
|
'最高价' + |
|
|
|
|
|
a[0].data[4]; |
|
|
|
|
|
|
|
|
|
|
|
if (a[1] && a[1].seriesName) { |
|
|
|
|
|
def += '<br/>' + a[1].seriesName + ':' + a[1].value; |
|
|
|
|
|
} |
|
|
|
|
|
return def; |
|
|
|
|
|
}, |
|
|
|
|
|
axisPointer: { |
|
|
|
|
|
animation: false, |
|
|
|
|
|
type: 'line', |
|
|
|
|
|
lineStyle: { |
|
|
|
|
|
color: '#376df4', |
|
|
|
|
|
width: 2, |
|
|
|
|
|
opacity: 1 |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
confine: true // 确保提示框不超出画布 |
|
|
|
|
|
}, |
|
|
|
|
|
//控制坐标轴 |
|
|
|
|
|
grid: { |
|
|
|
|
|
left: '12%', |
|
|
|
|
|
right: '10%', |
|
|
|
|
|
bottom: '10%', |
|
|
|
|
|
top: '18%' |
|
|
|
|
|
}, |
|
|
|
|
|
xAxis: { |
|
|
|
|
|
type: 'category', |
|
|
|
|
|
data: dealData.categoryData, |
|
|
|
|
|
axisLine: { lineStyle: { color: '#8392A5' } } |
|
|
|
|
|
}, |
|
|
|
|
|
yAxis: { |
|
|
|
|
|
scale: !0, //true |
|
|
|
|
|
// 自定义纵坐标现实的数据 |
|
|
|
|
|
axisLabel: { |
|
|
|
|
|
formatter: function (value) { |
|
|
|
|
|
return value // 返回原始值 |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
axisLine: { lineStyle: { color: '#8392A5' } }, |
|
|
|
|
|
splitLine: { |
|
|
|
|
|
show: !1 |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
dataZoom: [ |
|
|
|
|
|
{ |
|
|
|
|
|
textStyle: { |
|
|
|
|
|
color: '#8392A5' |
|
|
|
|
|
}, |
|
|
|
|
|
handleIcon: 'M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z', |
|
|
|
|
|
handleSize: '80%', |
|
|
|
|
|
dataBackground: { |
|
|
|
|
|
areaStyle: { |
|
|
|
|
|
color: '#8392A5' |
|
|
|
|
|
}, |
|
|
|
|
|
lineStyle: { |
|
|
|
|
|
opacity: 0.8, |
|
|
|
|
|
color: '#8392A5' |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
handleStyle: { |
|
|
|
|
|
color: '#fff', |
|
|
|
|
|
shadowBlur: 3, |
|
|
|
|
|
shadowColor: 'rgba(0, 0, 0, 0.6)', |
|
|
|
|
|
shadowOffsetX: 2, |
|
|
|
|
|
shadowOffsetY: 2 |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
type: 'inside', |
|
|
|
|
|
start: 1, |
|
|
|
|
|
end: 100, |
|
|
|
|
|
zoomOnMouseWheel: true, |
|
|
|
|
|
moveOnMouseMove: true |
|
|
|
|
|
} |
|
|
|
|
|
], |
|
|
|
|
|
animation: false, |
|
|
|
|
|
series: [ |
|
|
|
|
|
{ |
|
|
|
|
|
type: 'candlestick', |
|
|
|
|
|
name: '\u65e5K', |
|
|
|
|
|
data: dealData.value, |
|
|
|
|
|
itemStyle: { |
|
|
|
|
|
normal: { |
|
|
|
|
|
color0: '#FD1050', |
|
|
|
|
|
color: '#0CF49B', |
|
|
|
|
|
borderColor0: '#FD1050', |
|
|
|
|
|
borderColor: '#0CF49B' |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
// barWidth: isMobile ? 6 : isTablet ? 8 : 10 |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
name: 'MA5', |
|
|
|
|
|
type: 'line', |
|
|
|
|
|
data: (function (a) { |
|
|
|
|
|
for (var MA5 = [], d = 0, g = dealData.value.length; d < g; d++) { |
|
|
|
|
|
if (d < a) { |
|
|
|
|
|
MA5.push('-') |
|
|
|
|
|
} else { |
|
|
|
|
|
for (var f = 0, e = 0; e < a; e++) { |
|
|
|
|
|
f += dealData.value[d - e][1] |
|
|
|
|
|
} |
|
|
|
|
|
MA5.push((f / a).toFixed(2)) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return MA5 |
|
|
|
|
|
})(5), |
|
|
|
|
|
smooth: true, |
|
|
|
|
|
lineStyle: { |
|
|
|
|
|
width: isMobile ? 1 : 2, |
|
|
|
|
|
opacity: 0.8 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
] |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
console.log('KLine渲染: 图表配置完成'); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 应用配置 |
|
|
|
|
|
console.log('KLine渲染: 开始设置图表选项'); |
|
|
|
|
|
chartInstancesMap[containerId].setOption(KlineOption); |
|
|
|
|
|
console.log('KLine渲染: 图表选项设置成功'); |
|
|
|
|
|
|
|
|
|
|
|
// 窗口大小变化时重新渲染图表 |
|
|
|
|
|
const resizeFunc = 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(); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 给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( |
|
|
watch( |
|
|
() => audioStore.isVoiceEnabled, |
|
|
() => audioStore.isVoiceEnabled, |
|
|
(newVal) => { |
|
|
(newVal) => { |
|
@ -459,13 +761,90 @@ watch( |
|
|
{ immediate: true } |
|
|
{ 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].type === '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 |
|
|
// 初始化随机GIF |
|
|
onMounted(() => { |
|
|
onMounted(() => { |
|
|
const random = Math.floor(Math.random() * 6) + 1; |
|
|
const random = Math.floor(Math.random() * 6) + 1; |
|
|
currentGif.value = gifList[random]; |
|
|
currentGif.value = gifList[random]; |
|
|
|
|
|
|
|
|
getQuestionsList(); |
|
|
getQuestionsList(); |
|
|
|
|
|
|
|
|
|
|
|
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> |
|
|
</script> |
|
|
|
|
|
|
|
|
<template> |
|
|
<template> |
|
@ -506,7 +885,11 @@ onMounted(() => { |
|
|
|
|
|
|
|
|
<div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]"> |
|
|
<div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]"> |
|
|
<div v-if="msg.type === 'kline'" class="kline-container"> |
|
|
<div v-if="msg.type === 'kline'" class="kline-container"> |
|
|
<KLine :chartData="msg.chartData" /> |
|
|
|
|
|
|
|
|
<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> |
|
|
<div v-else v-html="msg.content"></div> |
|
|
<div v-else v-html="msg.content"></div> |
|
|
</div> |
|
|
</div> |
|
@ -648,28 +1031,22 @@ onMounted(() => { |
|
|
|
|
|
|
|
|
.kline-container { |
|
|
.kline-container { |
|
|
margin-top: 10px; |
|
|
margin-top: 10px; |
|
|
width: 100%; |
|
|
|
|
|
/* 最小移动端尺寸 */ |
|
|
|
|
|
min-width: 320px; |
|
|
|
|
|
/* 最小高度 */ |
|
|
/* 最小高度 */ |
|
|
min-height: 320px; |
|
|
min-height: 320px; |
|
|
/* 视口高度单位 */ |
|
|
/* 视口高度单位 */ |
|
|
height: 40vh; |
|
|
height: 40vh; |
|
|
/* 最大宽度限制 */ |
|
|
|
|
|
max-width: 800px; |
|
|
|
|
|
|
|
|
min-width: 50vw; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@media (min-width: 768px) { |
|
|
|
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.kline-container { |
|
|
.kline-container { |
|
|
height: 40vh; |
|
|
|
|
|
min-height: 400px; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
min-width: 75vw; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@media (min-width: 1024px) { |
|
|
|
|
|
.kline-container { |
|
|
|
|
|
height: 50vh; |
|
|
|
|
|
min-height: 400px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
.kline-container .chart-mount-point { |
|
|
|
|
|
height: 100%; |
|
|
|
|
|
width: 100%; |
|
|
} |
|
|
} |
|
|
</style> |
|
|
</style> |