Browse Source

K线图样式和交互问题解决

hxl
hongxilin 2 months ago
parent
commit
eb43794d49
  1. 10
      src/store/dataList.js
  2. 449
      src/views/AIchat.vue
  3. 484
      src/views/Echarts/KLine.vue
  4. 22
      src/views/homePage.vue

10
src/store/dataList.js

@ -24,11 +24,15 @@ export const useDataStore = defineStore('data', () => {
const AIRadar = ref(null)
const loading = ref(false)
const klineData = ref(null)
const activeTabIndex = ref(null)
const setKlineData = (data) => {
klineData.value = data
}
const setActiveTabIndex = (index) => {
activeTabIndex.value = index
}
const getQueryVariable = (variable) => {
const query = window.location.search.substring(1)
// console.log(query,'query')
@ -179,10 +183,12 @@ export const useDataStore = defineStore('data', () => {
// loading,
// AIRadar,
// fetchChartData,
activeTabIndex,
klineData,
setKlineData,
initData,
getMarket,
getQueryVariable
getQueryVariable,
setActiveTabIndex
}
})

449
src/views/AIchat.vue

@ -1,5 +1,5 @@
<script setup>
import { ref, onMounted, watch, nextTick } from "vue";
import { ref, onMounted, watch, nextTick, reactive, onUnmounted } from "vue";
import { ElDialog } from "element-plus";
import { getReplyStreamAPI, getReplyAPI, TTSAPI, getQuestionAPI, qsArpAamClickAPI } from "../api/AIxiaocaishen";
import { useUserStore } from '../store/userPessionCode'
@ -10,7 +10,6 @@ import { marked } from 'marked'; // 引入marked库
import katex from 'katex'; // KaTeX
import { htmlToText } from 'html-to-text';
import { Howl, Howler } from 'howler';
import KLine from './Echarts/KLine.vue';
import * as echarts from 'echarts'
import AIgif1 from '@/assets/img/AIchat/AIgif1.gif'
@ -140,6 +139,14 @@ const pauseAudio = () => {
const chatMsg = computed(() => chatStore.messages)
const props = defineProps({
messages: Array,
chartData: {
type: Object,
default: null
},
index: {
type: Number,
required: true
}
});
//
@ -168,6 +175,11 @@ const typeWriter = (text, callback) => {
}, 50) // ()
}
const hasValidData = ref(false)
//
const chartInstancesMap = {};
watch(
() => props.messages,
async (newVal, oldVal) => {
@ -258,6 +270,7 @@ watch(
const market = ans.value.market;
const data = JSON.parse(ans.value.duobaoData);
console.log("处理 K 线数据 - 开始");
console.log(data, "data");
const Kline20 = {
@ -265,24 +278,71 @@ watch(
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();
// K线
const klineMessageId = `kline-${Date.now()}`;
console.log("生成K线消息ID:", klineMessageId);
chatStore.messages.push({
sender: "ai",
type: "kline",
chartData: Kline20, //
messageId: `kline-${Date.now()}` // ID
chartData: Kline20,
messageId: klineMessageId,
hasValidData: true // hasValidData
});
console.log("K线消息已添加到聊天列表");
//
chatStore.messages.push({
sender: "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 !== "") {
AIcontent.value = ans.value.answerN;
@ -390,6 +450,7 @@ watch(
}
catch (e) {
console.error('请求失败:', e);
hasValidData.value = false; //
chatStore.messages.pop();
chatStore.messages.push({
sender: "ai",
@ -405,25 +466,266 @@ watch(
{ 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(
() => audioStore.isVoiceEnabled,
(newVal) => {
@ -459,13 +761,90 @@ watch(
{ 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
onMounted(() => {
const random = Math.floor(Math.random() * 6) + 1;
currentGif.value = gifList[random];
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("DOMK线:", 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>
@ -506,7 +885,11 @@ onMounted(() => {
<div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]">
<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 v-else v-html="msg.content"></div>
</div>
@ -648,28 +1031,22 @@ onMounted(() => {
.kline-container {
margin-top: 10px;
width: 100%;
/* 最小移动端尺寸 */
min-width: 320px;
/* 最小高度 */
min-height: 320px;
/* 视口高度单位 */
height: 40vh;
/* 最大宽度限制 */
max-width: 800px;
min-width: 50vw;
}
@media (min-width: 768px) {
@media (max-width: 768px) {
.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>

484
src/views/Echarts/KLine.vue

@ -1,7 +1,7 @@
<template>
<div ref="chartContainer" class="KlineClass">
<div ref="KlineCanvs" class="KlineClass">
<div v-if="!hasValidData" class="no-data-message">
<p>暂无K线数据</p>
暂无K线数据
</div>
</div>
</template>
@ -10,160 +10,181 @@
import * as echarts from 'echarts'
import { useDataStore } from '@/store/dataList'
import { onMounted, watch, ref, computed, nextTick, onUnmounted } from 'vue'
const dataStore = useDataStore()
const hasValidData = ref(false)
//
const props = defineProps({
chartData: {
type: Object,
default: null
}
})
const chartContainer = ref() //
const chartInstance = ref(null) //
const hasValidData = ref(false) //
const chartOptions = ref(null) //
//
const KlineCanvs = ref(null)
const chartInstance = ref(null)
const chartOptions = ref(null)
//
const checkDataValid = (data) => {
return data &&
data.Kline &&
Array.isArray(data.Kline) &&
data.Kline.length > 0 &&
data.Kline.every(item =>
Array.isArray(item) &&
item.length >= 5 &&
!isNaN(Number(item[1])) &&
!isNaN(Number(item[2])) &&
!isNaN(Number(item[3])) &&
!isNaN(Number(item[4]))
);
// MA
function calculateMA(dayCount, data) {
const result = [];
for (let i = 0; i < data.length; i++) {
if (i < dayCount - 1) {
result.push('-');
continue;
}
let sum = 0;
for (let j = 0; j < dayCount; j++) {
sum += parseFloat(data[i - j][1]);
}
result.push((sum / dayCount).toFixed(2));
}
return result;
}
//
//
watch(
() => props.chartData,
dataStore.KLineData,
(newValue) => {
console.log('KLine组件收到数据:', newValue)
// 使
const isValid = checkDataValid(newValue);
hasValidData.value = isValid;
console.log('KLine组件: 监听到数据变化', newValue);
if (!newValue) {
console.warn('KLine组件: 数据为null或undefined');
hasValidData.value = false;
return;
}
if (isValid) {
nextTick(() => {
try {
//
if (chartContainer.value && !chartInstance.value) {
// 使try-catch
chartInstance.value = echarts.init(chartContainer.value)
// 使
const instanceData = JSON.parse(JSON.stringify(newValue))
KlineCanvsEcharts(instanceData, chartInstance.value)
const currentData = JSON.parse(JSON.stringify(toRaw(newValue))); //
if (currentData) {
nextTick(() => {
KlineCanvsEcharts(currentData);
});
} else {
console.warn('KLine组件: 深拷贝后数据为空');
hasValidData.value = false;
}
} catch (error) {
console.error('初始化K线图表时出错:', error)
console.error('KLine组件: 数据处理错误', error);
hasValidData.value = false;
}
})
} else {
console.error('无效的K线数据格式:', newValue)
}
},
{ immediate: true }
)
{ immediate: true, deep: true }
);
function KlineCanvsEcharts(datatok, instance) {
console.log('传入的K线数据:', datatok);
//
onMounted(() => {
console.log('KLine组件挂载完成');
//
if (!datatok) {
console.error('K线数据为空');
return;
// resize
const resizeHandler = () => {
if (chartInstance.value) {
chartInstance.value.resize();
}
};
if (!datatok.Kline) {
console.error('K线数据中没有Kline属性:', datatok);
return;
window.addEventListener('resize', resizeHandler);
//
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler);
if (chartInstance.value) {
chartInstance.value.dispose();
chartInstance.value = null;
}
});
});
// K线
function KlineCanvsEcharts(datatok) {
console.log('KLine组件: 开始处理数据', datatok);
if (!Array.isArray(datatok.Kline)) {
console.error('Kline数据不是数组:', typeof datatok.Kline);
//
if (!datatok) {
console.warn('KLine组件: 数据无效,为null或undefined');
hasValidData.value = false;
return;
}
if (datatok.Kline.length === 0) {
console.error('Kline数组为空');
//
let klineData = null;
let chartName = '';
if (datatok.Kline && Array.isArray(datatok.Kline)) {
klineData = datatok.Kline;
chartName = datatok.name || '股票K线图';
} else if (datatok.KLine20 && Array.isArray(datatok.KLine20)) {
klineData = datatok.KLine20;
chartName = datatok.name || '股票K线图';
} else {
console.warn('KLine组件: 找不到有效的K线数据');
hasValidData.value = false;
return;
}
//
const isValidKlineItem = datatok.Kline.every(item =>
Array.isArray(item) && item.length >= 5 &&
!isNaN(Number(item[1])) && !isNaN(Number(item[2])) &&
!isNaN(Number(item[3])) && !isNaN(Number(item[4]))
);
if (!isValidKlineItem) {
console.error('Kline数据格式不正确, 期望格式: [[date, open, close, low, high], ...]');
console.error('实际数据样例:', datatok.Kline[0]);
if (klineData.length === 0) {
console.warn('KLine组件: K线数据为空数组');
hasValidData.value = false;
return;
}
//
const data = datatok.Kline;
const spliteDate = (a) => {
//
const categoryData = [];
let value = [];
const values = [];
try {
for (let i = 0; i < a.length; i++) {
//
if (Array.isArray(a[i]) && a[i].length >= 5) {
categoryData.push(a[i][0]);
//
value.push([
Number(a[i][1]),
Number(a[i][2]),
Number(a[i][3]),
Number(a[i][4])
for (let i = 0; i < klineData.length; i++) {
const item = klineData[i];
if (!Array.isArray(item) || item.length < 5) {
console.warn(`KLine组件: 第${i + 1}个数据点格式无效`, item);
continue; //
}
categoryData.push(item[0]); //
values.push([
parseFloat(item[1]), //
parseFloat(item[2]), //
parseFloat(item[3]), //
parseFloat(item[4]) //
]);
}
if (categoryData.length === 0 || values.length === 0) {
console.warn('KLine组件: 处理后无有效数据点');
hasValidData.value = false;
return;
}
} catch (error) {
console.error('处理K线数据时出错:', error);
return { categoryData: [], value: [] };
}
return { categoryData, value };
};
const dealData = spliteDate(data);
// MA5
const ma5Data = calculateMA(5, klineData);
console.log('处理后的K线数据:', dealData);
if (dealData.value.length === 0 || dealData.categoryData.length === 0) {
console.error('空数据,无法渲染图表');
//
if (!KlineCanvs.value) {
console.warn('KLine组件: 找不到容器元素');
hasValidData.value = false;
return;
}
//
//
if (chartInstance.value) {
chartInstance.value.dispose();
}
chartInstance.value = echarts.init(KlineCanvs.value);
//
const KlineOption = {
title: {
text: datatok.name || '股票K线图',
top: 20,
left: 20
text: chartName,
left: 0
},
tooltip: {
trigger: 'axis',
show: true,
formatter: function (params) {
if (!params || params.length === 0) return '';
const param = params[0];
return `
<div style="font-weight:bold;margin-bottom:5px">${param.name}</div>
<div style="margin:3px 0">开盘价: ${param.data[1]}</div>
<div style="margin:3px 0">收盘价: ${param.data[2]}</div>
<div style="margin:3px 0">最低价: ${param.data[3]}</div>
<div style="margin:3px 0">最高价: ${param.data[4]}</div>
${params[1] && params[1].value !== '-' ? '<div style="margin:3px 0">MA5: ' + params[1].value + '</div>' : ''}
`;
let result = param.name + '<br/>' +
'开盘价' + param.data[0] + '<br/>' +
'收盘价' + param.data[1] + '<br/>' +
'最低价' + param.data[2] + '<br/>' +
'最高价' + param.data[3];
// MA5
if (params[1] && params[1].value !== '-') {
result += '<br/>' + params[1].seriesName + ':' + params[1].value;
}
return result;
},
axisPointer: {
animation: false,
@ -173,254 +194,95 @@ function KlineCanvsEcharts(datatok, instance) {
width: 2,
opacity: 1
}
},
confine: true,
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderColor: '#ccc',
borderWidth: 1,
padding: 10,
textStyle: {
color: '#333'
}
},
//
xAxis: {
type: 'category',
data: dealData.categoryData,
axisLine: { lineStyle: { color: '#8392A5' } }
},
//
grid: {
left: '12%',
right: '10%',
bottom: '10%',
bottom: '15%',
top: '18%'
},
xAxis: {
type: 'category',
data: categoryData,
axisLine: { lineStyle: { color: '#8392A5' } }
},
yAxis: {
scale: !0, //true
//
scale: true,
axisLabel: {
formatter: function (value) {
return value //
return value; //
}
},
axisLine: { lineStyle: { color: '#8392A5' } },
splitLine: {
show: !1
}
splitLine: { show: false }
},
//
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
}
},
{
show: !1,
type: 'slider'
type: 'inside',
start: 50,
end: 100
},
{
type: 'inside'
show: true,
type: 'slider',
top: '90%',
start: 50,
end: 100
}
],
animation: !1, //false
// 线
series: [
{
name: chartName,
type: 'candlestick',
name: '\u65e5K',
//
data: dealData.value,
data: values,
itemStyle: {
normal: {
color0: '#FD1050',
color: '#0CF49B',
borderColor0: '#FD1050',
borderColor: '#0CF49B'
}
color: '#ef232a',
color0: '#14b143',
borderColor: '#ef232a',
borderColor0: '#14b143'
}
},
{
name: 'MA5',
type: 'line',
//
// MA5
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))
}
data: ma5Data,
smooth: true,
lineStyle: {
opacity: 0.5
}
return MA5
})(5),
smooth: !0
}
]
}
//
instance.setOption(KlineOption, true);
};
// ref使
//
chartInstance.value.setOption(KlineOption);
chartOptions.value = KlineOption;
//
instance.on('click', function(params) {
//
chartInstance.value.off('click');
chartInstance.value.on('click', function(params) {
console.log('图表点击事件:', params);
if (params.componentType === 'series') {
const date = params.name;
const data = params.data;
if (Array.isArray(data) && data.length >= 5) {
console.log(`点击了 ${date} 的K线数据: 开盘${data[1]}, 收盘${data[2]}, 最低${data[3]}, 最高${data[4]}`);
// 使
}
}
});
//
instance.on('datazoom', function(params) {
console.log('图表缩放事件:', params);
//
});
instance.on('legendselectchanged', function(params) {
console.log('图例选择变化:', params);
});
// resize
const resizeHandler = () => {
if (instance) {
instance.resize();
}
};
window.addEventListener('resize', resizeHandler);
// 使
return () => {
window.removeEventListener('resize', resizeHandler);
instance.off('click');
instance.off('datazoom');
instance.off('legendselectchanged');
};
console.log(`点击了 ${date} 的K线数据:`, data);
}
//
onUnmounted(() => {
if (chartInstance.value) {
//
chartInstance.value.off('click');
chartInstance.value.off('datazoom');
chartInstance.value.off('legendselectchanged');
//
chartInstance.value.dispose();
chartInstance.value = null;
}
})
onMounted(() => {
console.log('KLine组件挂载完成')
})
// 访
defineExpose({
getChartInstance: () => chartInstance.value,
getOptions: () => chartOptions.value,
resize: () => {
if (chartInstance.value) {
chartInstance.value.resize()
}
},
//
zoomToDateRange: (startDate, endDate) => {
if (!chartInstance.value || !chartOptions.value) return;
const categoryData = chartOptions.value.xAxis[0].data;
if (!Array.isArray(categoryData)) return;
const startIndex = categoryData.findIndex(date => date >= startDate);
const endIndex = categoryData.findIndex(date => date >= endDate);
if (startIndex === -1 || endIndex === -1) return;
//
const total = categoryData.length;
const start = (startIndex / total) * 100;
const end = (endIndex / total) * 100;
//
chartInstance.value.dispatchAction({
type: 'dataZoom',
start: start,
end: end
});
},
// K线
highlightDate: (date) => {
if (!chartInstance.value || !chartOptions.value) return;
const categoryData = chartOptions.value.xAxis[0].data;
if (!Array.isArray(categoryData)) return;
const index = categoryData.findIndex(d => d === date);
if (index !== -1) {
chartInstance.value.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: index
});
// tooltip
chartInstance.value.dispatchAction({
type: 'showTip',
seriesIndex: 0,
dataIndex: index
});
}
},
//
clearHighlight: () => {
if (chartInstance.value) {
chartInstance.value.dispatchAction({
type: 'downplay'
});
//
hasValidData.value = true;
console.log('KLine组件: 图表渲染完成');
chartInstance.value.dispatchAction({
type: 'hideTip'
});
} catch (error) {
console.error('KLine组件: 图表渲染错误', error);
hasValidData.value = false;
}
}
})
</script>
<style scoped>
.KlineClass {
.chart-mount-point {
position: relative;
width: 100%;
min-height: 400px;
@ -451,7 +313,7 @@ defineExpose({
/* 确保图表容器在移动设备上也有足够的空间 */
@media (max-width: 768px) {
.KlineClass {
.chart-mount-point {
min-height: 450px;
/* 给移动设备增加额外高度确保dataZoom可见 */
}

22
src/views/homePage.vue

@ -26,10 +26,10 @@ import msgBtn from "../assets/img/homePage/tail/msg.png";
// import { useUserStore } from "../store/userPessionCode.js";
const { getQueryVariable } = useDataStore()
const { getQueryVariable, setActiveTabIndex } = useDataStore()
const dataStore = useDataStore()
const chatStore = useChatStore()
//
//
const audioStore = useAudioStore()
@ -69,6 +69,7 @@ const setActiveTab = (tab, index, forceAIchat = false) => {
sessionStorage.setItem("activeTabAI", tab);
sessionStorage.setItem("activeIndexAI", index.toString());
}
setActiveTabIndex(index)
console.log(tab, index, "tab, index");
setHeight(document.getElementById("testId")); //
};
@ -118,17 +119,17 @@ const updateMessage = (title) => {
// console.log("updateMessage :", title);
};
const sendMessage = async () => {
if (localStorage.getItem('localToken') == null||localStorage.getItem('localToken') == '') {
ElMessage.error('请先登录');
return ;
}
// if (localStorage.getItem('localToken') == null||localStorage.getItem('localToken') == '') {
// ElMessage.error('');
// return ;
// }
isScrolling.value = false;
// ensureAIchat AIchat
ensureAIchat();
console.log(chatStore.isLoading, 'isLoading.value1111');
if (!message.value) return;
if (chatStore.isLoading) return;
console.log(chatStore.isLoading, 'isLoading.value1111');
chatStore.setLoading(true);
console.log(chatStore.isLoading, 'isLoading.value2222');
@ -143,6 +144,7 @@ const sendMessage = async () => {
}
];
// console.log(messages.value, 'messages.value');
//
message.value = "";
@ -175,7 +177,7 @@ const showCount = () => {
};
//
const chatStore = useChatStore()
const tabContent = ref(null);
const isScrolling = ref(false); //

Loading…
Cancel
Save