|
|
<script setup> 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' 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 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' const gifList = [ AIgif1, AIgif2, AIgif3, AIgif4, AIgif5, AIgif6, AIgif7 ]
const chatStore = useChatStore() const audioStore = useAudioStore() const dataStore = useDataStore() // 随机GIF
const currentGif = ref("");
// 推荐问题飘屏数据
const questionsList = ref([]) const getQuestionsList = async () => { const result = await getQuestionAPI() questionsList.value = result.data }
// 定义自定义事件
const emit = defineEmits(["updateMessage", "sendMessage"]);
// 弹窗控制
const dialogVisible = ref(false); const currentQuestions = ref(""); const showQuestions = async (questions) => {
const click = await qsArpAamClickAPI({ token: localStorage.getItem('localToken'), id: questions.id }) console.log(click);
currentQuestions.value = questions; // 触发自定义事件
emit("updateMessage", questions.title); // emit("sendMessage");
};
// 飘屏控制
const floatingTopMouseEnter = function () {
const userAgent = navigator.userAgent.toLowerCase(); const mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod']; const isMobile = mobileKeywords.some(keyword => userAgent.includes(keyword)); if (isMobile) { return } console.log("鼠标指上去"); const floatTop = document.getElementById("top"); floatTop.style.animationPlayState = "paused"; } const floatingTopMouseLeave = function () { const userAgent = navigator.userAgent.toLowerCase(); const mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod']; const isMobile = mobileKeywords.some(keyword => userAgent.includes(keyword)); if (isMobile) { return } console.log("鼠标指下去"); const floatTop = document.getElementById("top"); floatTop.style.animationPlayState = "running" } const floatingBottomMouseEnter = function () { const userAgent = navigator.userAgent.toLowerCase(); const mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod']; const isMobile = mobileKeywords.some(keyword => userAgent.includes(keyword)); if (isMobile) { return } console.log("鼠标指上去"); const floatBottom = document.getElementById("bottom"); floatBottom.style.animationPlayState = "paused"; } const floatingBottomMouseLeave = function () { const userAgent = navigator.userAgent.toLowerCase(); const mobileKeywords = ['mobile', 'android', 'iphone', 'ipad', 'ipod']; const isMobile = mobileKeywords.some(keyword => userAgent.includes(keyword)); if (isMobile) { return } console.log("鼠标指下去"); const floatBottom = document.getElementById("bottom"); floatBottom.style.animationPlayState = "running" }
// 音频播放方法
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 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 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 params = { content: newVal[newVal.length - 1].content, userData: { token: localStorage.getItem('localToken'), // language: "cn",
// brainPrivilegeState: userStore.brainPerssion,
// swordPrivilegeState: userStore.swordPerssion,
// stockForecastPrivile: userStore.pricePerssion,
// spaceForecastPrivile: userStore.timePerssion,
// aibullPrivilegeState: userStore.aibullPerssion,
// aigoldBullPrivilegeS: userStore.aiGnbullPerssion,
// airadarPrivilegeStat: userStore.airadarPerssion,
// marketList: "hk,cn,usa,my,sg,vi,in,gb"
// token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
language: "cn", brainPrivilegeState: "1", swordPrivilegeState: "1", stockForecastPrivile: "1", spaceForecastPrivile: "1", aibullPrivilegeState: "1", aigoldBullPrivilegeS: "1", airadarPrivilegeStat: "1", marketList: "hk,cn,usa,my,sg,vi,in,gb" } }
try {
chatStore.messages.push({ sender: "ai", content: `<div>
<span>AI正在思考中</span> <el-icon class="is-loading"> <Loading /> </el-icon> </div>`
});
// 调用工作流获取回复
const result = (await getReplyAPI(params)).json(); // console.log(result, "result");
// 获取结果
const ans = ref(); await result.then((res) => { // console.log(res.data, "res");
// 解析 data 字段中的 JSON
ans.value = JSON.parse(res.data); }) console.log(ans.value, "ans"); const AIcontent = ref(""); // 处理不同的 answer 字段
if (ans.value.resp !== "" && ans.value.resp !== null) { console.log('执行回绝话术') AIcontent.value = ans.value.resp;
// 修改后的消息处理逻辑
const processedContent = marked(AIcontent.value); const katexRegex = /\$\$(.*?)\$\$/g; // const plainTextContent = htmlToText(processedContent);
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.pop();
chatStore.messages.push({ sender: "ai", content: aiContent, });
chatStore.setLoading(false);
} else { // 判断是否是股票问题
if (ans.value.answer !== "") { AIcontent.value = ans.value.answer; const type = ans.value.type; const data = JSON.parse(ans.value.data);
console.log("处理 K 线数据 - 开始"); console.log(data, "data");
const Kline20 = { name: data.data.HomePage.StockInformation.Name, Kline: data.data, type: type }
// 打印K线数据结构
console.log("K线数据结构:", Kline20); console.log("K线数据名称:", Kline20.name); console.log("K线数据类型:", Kline20.type); console.log("K线数据:", Kline20.Kline ? Kline20.Kline : null);
// 设置数据有效标志
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: klineMessageId, hasValidData: true // 添加hasValidData标志
});
console.log("K线消息已添加到聊天列表");
// 再推送文字分析内容的消息
chatStore.messages.push({ sender: "ai", content: "AI正在思考中..." });
// 在渲染完成后初始化图表
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线消息"); } }); }
// 修改后的消息处理逻辑
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 { 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].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.AIGoldBull; 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 }; };
var KlineOption = {};
// 检测设备类型
const isMobile = window.innerWidth < 768; const isTablet = window.innerWidth >= 768 && window.innerWidth < 1024; console.log('KLine渲染: 设备类型', isMobile ? '移动设备' : isTablet ? '平板设备' : '桌面设备');
// 给配置项
console.log('KLine渲染: 开始配置图表选项');
if (klineData.type === 1) { console.log('进入第一分类')
const arr1 = [] const arr2 = [] const arr3 = [] const arr4 = []
// 动态K线
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]) } }) }) } } 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)
console.log('dealData', dealData); console.log('dealData1', dealData1); console.log('dealData2', dealData2); console.log('dealData3', dealData3); console.log('dealData4', dealData4);
var dealGnBullData = data.JN //金牛版成交量数据
// 处理MA数据,创建两段重叠的数据
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) console.log('maData', maData) // 牵牛绳的数据
// 如果第一个字段的第一个数据为-1的话,不展示牵牛绳
if (data.FCX[0][1] == '-1') { maData.greenData = [] maData.redData = [] maData.yellowData = [] } // 生成文字标注
const processBarData = (data) => { const barData = [] const markPointData = [] data.forEach((item) => { let color // 根据 item[4] 设置不同的颜色
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 = { // 底部选项
// animation: false,
// 手放上去显示的内容
legend: [ { //图例文字的样式
textStyle: { color: 'black', //图例文字颜色
fontSize: window.innerWidth > 768 ? 15 : vwToPx(2.8) // 响应式字体大小
}, width: '100%', // 确保有足够的宽度容纳图例
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(2.8) // 响应式字体大小
}, orient: 'horizontal', // 设置图例水平布局
top: '5%', 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: '虫线' } ] } ], tooltip: { // 调用接口之后方法
formatter: function (a, b, d) { if (a[0].seriesIndex == 0) { const KlineTag = ref([]) // 判断几根K线
const AIBullTag = ref([])
// 找到第一个满足条件的数据
KlineTag.value = a.find((item) => item.data[1])?.data || []
// 找到第一个满足条件的非 '-' 数据
AIBullTag.value = a.slice(4).find((item) => item.data[1] !== '-')?.data || [] return ( a[0].name + '<br/>' + '开盘价' + ':' + KlineTag.value[1] + '<br/>' + '收盘价' + ':' + KlineTag.value[2] + '<br/>' + '最低价' + ':' + KlineTag.value[3] + '<br/>' + '最高价' + ':' + KlineTag.value[4] + '<br/>' + '牵牛绳' + ':' + AIBullTag.value[1] ) } else { // 格式化成交量显示
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 } }, trigger: 'axis', axisPointer: { //坐标轴指示器配置项
type: 'cross' //‘line’直线指示器,‘cross’十字准星指示器,‘shadow’阴影指示器
}, backgroundColor: 'rgba(119, 120, 125, 0.6)', // 提示框浮层的边框颜色。
borderWidth: 1, // 提示框浮层的边框宽。
borderColor: '#77787D', // 提示框浮层的边框颜色。
padding: 10, // 提示框浮层内边距,
textStyle: { //提示框浮层上的文字样式
color: '#fff' } }, // 手放上去时拉的框
axisPointer: { link: [ { xAxisIndex: 'all' // 同时触发所有图形的 x 坐标轴指示器
} ], label: { backgroundColor: '#77787D' // 文本标签的背景颜色
} }, toolbox: { show: false }, grid: [ { left: window.innerWidth > 768 ? '18%' : '15%', // right: window.innerWidth > 768 ? '4%' : '2.5%',
height: window.innerWidth > 768 ? '60%' : '57%', top: window.innerWidth > 768 ? '8%' : '12%', // height: '33%',
containLabel: false }, { left: window.innerWidth > 768 ? '18%' : '15%', // right: window.innerWidth > 768 ? '4%' : '2.5%',
top: window.innerWidth > 768 ? '72%' : '73%', height: '20%', containLabel: false } ], xAxis: [ { type: 'category', data: dealData.categoryData, boundaryGap: true, // 坐标轴两边是否留空,false表示不留空(通常用于K线图)
axisLine: { onZero: false }, // 设置坐标轴是否通过零点,onZero:false表示不强制穿过零点
splitLine: { show: false }, // 是否显示分隔线,false表示不显示
min: 'dataMin', // 坐标轴最小值,'dataMin'表示从数据的最小值开始
max: 'dataMax', // 坐标轴最大值,'dataMax'表示从数据的最大值开始
axisPointer: { z: 100 // 坐标轴指示器的层级,较大的值会让它显示在其他元素上方
}, axisLine: { lineStyle: { color: 'black' // 坐标轴线的颜色
} }, //
axisLabel: { show: false }, // 隐藏刻度标签
axisTick: { show: false } // 隐藏刻度线
}, // 下方成交量图的X轴
{ type: 'category', gridIndex: 1, data: dealData.categoryData, boundaryGap: true, axisLine: { lineStyle: { color: 'black' } }, axisLabel: { show: true, // fontSize: window.innerWidth > 768 ? 10 : 8,
interval: 'auto' }, axisTick: { show: false } // 隐藏刻度线
} ], // 控制纵坐标展示数据
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 } }, // 下方成交量图的Y轴
{ scale: true, gridIndex: 1, splitNumber: 4, // 增加分割数以获得更好的间距
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%'] // 为坐标轴边界添加内边距
} ], // 下拉条
dataZoom: [ { type: 'inside', xAxisIndex: [0, 1], start: 55, end: 100 }, { show: true, xAxisIndex: [0, 1], type: 'slider', top: window.innerWidth > 768 ? '95%' : '91%', left: window.innerWidth > 768 ? '18%' : '15%', // left: window.innerWidth > 768 ? '10%' : '8%',
start: 98, end: 100 } ], visualMap: [ { type: 'piecewise', show: false, pieces: maData.singleTypeGreen, outOfRange: { color: 'green' }, dimension: 0, seriesIndex: 6 }, { type: 'piecewise', show: false, pieces: maData.singleTypeRed, outOfRange: { color: 'red' }, dimension: 0, seriesIndex: 7 }, { type: 'piecewise', show: false, pieces: maData.singleTypeYellow, outOfRange: { color: 'yellow' }, dimension: 0, seriesIndex: 8 } ], series: [ // 第一条K线
{ name: '进攻K线', type: 'candlestick', barWidth: '50%', // 设置和上方图表一致的柱子宽度
data: dealData1.values, xAxisIndex: 0, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
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, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
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, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
itemStyle: { normal: { color: 'orange', // 默认颜色
color0: 'orange', borderColor: 'orange', borderColor0: 'orange' } }, gridIndex: 0 }, { name: '撤退K线', type: 'candlestick', barWidth: '50%', // 设置和上方图表一致的柱子宽度
data: dealData4.values, xAxisIndex: 0, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
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, // 线图与第一个 K 线图共享 X 轴
yAxisIndex: 0, // 线图与第一个 K 线图共享 Y 轴
showSymbol: false, // 隐藏符号
lineStyle: { opacity: 0 // 使线条透明
}, itemStyle: { normal: { color: 'green' } }, gridIndex: 0 // 确保线图与第一个K线图共享网格
}, { name: '虫线', type: 'line', data: maData.greenData, smooth: true, symbol: 'none', xAxisIndex: 0, // 线图与第一个 K 线图共享 X 轴
yAxisIndex: 0, // 线图与第一个 K 线图共享 Y 轴
itemStyle: { normal: { color: 'green', lineStyle: { // color: 'orange', // 线的颜色
width: 2, // 线宽
type: 'solid' // 线类型
} } }, gridIndex: 0 // 确保线图与第一个K线图共享网格
}, { name: '龙线', type: 'line', data: maData.redData, smooth: true, symbol: 'none', xAxisIndex: 0, // 线图与第一个 K 线图共享 X 轴
yAxisIndex: 0, // 线图与第一个 K 线图共享 Y 轴
itemStyle: { normal: { color: 'red', lineStyle: { // color: 'orange', // 线的颜色
width: 2, // 线宽
type: 'solid' // 线类型
} } }, gridIndex: 0 // 确保线图与第一个K线图共享网格
}, { 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 } ] // graphic: {
// markPointData : generateGraphicmarkPointData(data11111)
// }
} } else if (klineData.type === 2) { console.log('进入第二分类')
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 }] } } ] }
} else if (klineData.type === 3 || klineData.type === 4 || klineData.type === 8 || klineData.type === 9 || klineData.type === 10) { console.log('进入3,4,8,9,10分类')
const arr1 = [] const arr2 = [] const arr3 = [] const arr4 = [] // k线的数据
// 动态K线
const changeColorKline = (QSXH, KLine20) => { console.log(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]) } }) }) } } 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) // 处理MA数据,创建两段重叠的数据
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] } } }) // console.log('greenData', greenData)
// console.log('redData', redData)
// console.log('yellowData', yellowData)
// console.log('green', singleTypeGreen)
// console.log('red', singleTypeRed)
// console.log('yellow', singleTypeYellow)
return { greenData: greenData, redData: redData, yellowData: yellowData, singleTypeGreen: singleTypeGreen, singleTypeRed: singleTypeRed, singleTypeYellow: singleTypeYellow } } const maData = processMAData(data.FCX) // 牵牛绳的数据
// 如果第一个字段的第一个数据为-1的话,不展示牵牛绳
if (data.FCX[0][1] == '-1') { maData.greenData = [] maData.redData = [] maData.yellowData = [] } // K线图距离顶部的距离
// 配置项
KlineOption = { // 底部选项
// animation: false,
// 手放上去显示的内容
legend: [ { //图例文字的样式
textStyle: { color: 'black', //图例文字颜色
fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8) // 响应式字体大小
}, width: '100%', // 确保有足够的宽度容纳图例
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 ? '8%' : '8%', 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: '虫线' } ] } ], tooltip: { // 调用接口之后方法
formatter: function (a, b, d) { if (a[0].seriesIndex == 0) { const KlineTag = ref([]) // 判断几根K线
const AIBullTag = ref([])
// 找到第一个满足条件的数据
KlineTag.value = a.find((item) => item.data[1])?.data || []
// 找到第一个满足条件的非 '-' 数据
AIBullTag.value = a.slice(4).find((item) => item.data[1] !== '-')?.data || [] return ( a[0].name + '<br/>' + '开盘价' + ':' + KlineTag.value[1] + '<br/>' + '收盘价' + ':' + KlineTag.value[2] + '<br/>' + '最低价' + ':' + KlineTag.value[3] + '<br/>' + '最高价' + ':' + KlineTag.value[4] + '<br/>' + '牵牛绳' + ':' + AIBullTag.value[1] ) // }else if(a[0].seriesIndex == 5){
// return a[0].name+ "<br/>" + a[0].seriesName + ":" + a[0].value + "<br/>" + a[1].seriesName + ":" + a[1].value + "<br/>" + a[2].seriesName + ":" + a[2].value + "<br/>" + a[3].seriesName + ":" + a[3].value + "<br/>" + a[4].seriesName + ":" + a[4].value + "<br/>" + a[5].seriesName + ":" + a[5].value
} else { return ( a[0].name + '<br/>' + '开盘价' + ':' + a[0].data[1] + '<br/>' + '收盘价' + ':' + a[0].data[2] + '<br/>' + '最低价' + ':' + a[0].data[3] + '<br/>' + '最高价' + ':' + a[0].data[4] + '<br/>' ) } }, trigger: 'axis', axisPointer: { //坐标轴指示器配置项
type: 'cross' //‘line’直线指示器,‘cross’十字准星指示器,‘shadow’阴影指示器
}, backgroundColor: 'rgba(119, 120, 125, 0.6)', // 提示框浮层的边框颜色。
borderWidth: 1, // 提示框浮层的边框宽。
borderColor: '#77787D', // 提示框浮层的边框颜色。
padding: 10, // 提示框浮层内边距,
textStyle: { //提示框浮层上的文字样式
color: '#fff' } }, // 手放上去时拉的框
axisPointer: { link: [ { xAxisIndex: 'all' // 同时触发所有图形的 x 坐标轴指示器
} ], label: { backgroundColor: '#77787D' // 文本标签的背景颜色
} }, toolbox: { show: false }, grid: [ { // left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '7%' : '2.5%',
height: window.innerWidth > 768 ? '40%' : '37%', top: window.innerWidth > 768 ? '10%' : '12%', containLabel: false }, { // left: window.innerWidth > 768 ? '8%' : '15%',
// right: window.innerWidth > 768 ? '7%' : '2.5%',
top: window.innerWidth > 768 ? '53%' : '52%', height: window.innerWidth > 768 ? '40%' : '37%', containLabel: false } ], xAxis: [ { type: 'category', data: dealData.categoryData, boundaryGap: true, // 坐标轴两边是否留空,false表示不留空(通常用于K线图)
axisLine: { onZero: false }, // 设置坐标轴是否通过零点,onZero:false表示不强制穿过零点
splitLine: { show: false }, // 是否显示分隔线,false表示不显示
min: 'dataMin', // 坐标轴最小值,'dataMin'表示从数据的最小值开始
max: 'dataMax', // 坐标轴最大值,'dataMax'表示从数据的最大值开始
axisPointer: { z: 100 // 坐标轴指示器的层级,较大的值会让它显示在其他元素上方
}, axisLine: { lineStyle: { color: 'black' // 坐标轴线的颜色
} }, //
axisLabel: { show: false }, // 隐藏刻度标签
axisTick: { show: false } // 隐藏刻度线
}, { type: 'category', gridIndex: 1, data: dealData.categoryData, boundaryGap: true, axisLine: { show: false }, axisTick: { show: false } // 隐藏刻度线 // 只保留一个 axisLine 定义
// 对于第二个类别轴,通常也不需要设置 min 和 max
} ], // 控制纵坐标展示数据
yAxis: [ { scale: true, gridIndex: 0, axisLabel: { show: true, fontSize: window.innerWidth > 768 ? 15 : 10 }, axisLine: { lineStyle: { fontSize: '', color: 'black' } }, axisTick: { show: false }, splitLine: { show: false } }, { scale: true, gridIndex: 1, splitNumber: 2, axisLabel: { show: true, fontSize: window.innerWidth > 768 ? 15 : 10 }, axisLine: { lineStyle: { color: 'black' } }, axisTick: { show: false }, splitLine: { show: false } } ], // 下拉条
dataZoom: [ { type: 'inside', xAxisIndex: [0, 1], start: 55, end: 100 }, { show: true, xAxisIndex: [0, 1], type: 'slider', top: window.innerWidth > 768 ? '92%' : '91%', left: window.innerWidth > 768 ? '10%' : '8%', start: 98, end: 100 } ], visualMap: [ { type: 'piecewise', show: false, pieces: maData.singleTypeGreen, outOfRange: { color: 'green' }, dimension: 0, seriesIndex: 6 }, { type: 'piecewise', show: false, pieces: maData.singleTypeRed, outOfRange: { color: 'red' }, dimension: 0, seriesIndex: 7 }, { type: 'piecewise', show: false, pieces: maData.singleTypeYellow, outOfRange: { color: 'yellow' }, dimension: 0, seriesIndex: 8 } ], series: [ // 第一条K线
{ name: '进攻K线', type: 'candlestick', data: dealData1.values, xAxisIndex: 0, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
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', data: dealData2.values, xAxisIndex: 0, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
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', data: dealData3.values, xAxisIndex: 0, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
itemStyle: { normal: { color: 'orange', // 默认颜色
color0: 'orange', borderColor: 'orange', borderColor0: 'orange' } }, gridIndex: 0 }, { name: '撤退K线', type: 'candlestick', data: dealData4.values, xAxisIndex: 0, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
itemStyle: { normal: { color: 'rgb(84,252,252)', // 默认颜色
color0: 'rgb(84,252,252)', borderColor: 'rgb(84,252,252)', borderColor0: 'rgb(84,252,252)' } }, gridIndex: 0 }, // 第二条K线
{ type: 'candlestick', name: '日K', xAxisIndex: 1, // 使用第二个 X 轴
yAxisIndex: 1, // 使用第二个 Y 轴
data: dealData.values, itemStyle: { normal: { color0: 'red', color: 'green', borderColor0: 'red', borderColor: 'green' } }, gridIndex: 1 }, { name: '{green|━}{red|━} ' + '牵牛绳', // 将牵牛绳样式应用到文本前缀
type: 'line', data: [], // 设置为空数组,不显示数据点
smooth: true, symbol: 'none', xAxisIndex: 0, // 线图与第一个 K 线图共享 X 轴
yAxisIndex: 0, // 线图与第一个 K 线图共享 Y 轴
showSymbol: false, // 隐藏符号
lineStyle: { opacity: 0 // 使线条透明
}, itemStyle: { normal: { color: 'green' } }, gridIndex: 0 // 确保线图与第一个K线图共享网格
}, { name: '虫线', type: 'line', data: maData.greenData, smooth: true, symbol: 'none', xAxisIndex: 0, // 线图与第一个 K 线图共享 X 轴
yAxisIndex: 0, // 线图与第一个 K 线图共享 Y 轴
itemStyle: { normal: { color: 'green', lineStyle: { // color: 'orange', // 线的颜色
width: 2, // 线宽
type: 'solid' // 线类型
} } }, gridIndex: 0 // 确保线图与第一个K线图共享网格
}, { name: '龙线', type: 'line', data: maData.redData, smooth: true, symbol: 'none', xAxisIndex: 0, // 线图与第一个 K 线图共享 X 轴
yAxisIndex: 0, // 线图与第一个 K 线图共享 Y 轴
itemStyle: { normal: { color: 'red', lineStyle: { // color: 'orange', // 线的颜色
width: 2, // 线宽
type: 'solid' // 线类型
} } }, gridIndex: 0 // 确保线图与第一个K线图共享网格
}, { 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 } ] }
} else if (klineData.type === 5 || klineData.type === 7) { console.log('进入第7分类')
const upColor = '#00da3c' const downColor = '#ec0000' function calculateMA(index, data) { let result = [] if (data.FTLINE) { data.FTLINE.forEach((item) => { result.push(item[index]) }) } return result } var dealData = splitData(data.KLine20) var bodongliang = splitData(data.WAVEVOL) function bodongliangData(values, i) { return values.map((subArray) => subArray[i]) } KlineOption = { animation: false, legend: { textStyle: { fontSize: window.innerWidth > 768 ? 15 : vwToPx(1.8) }, left: 'center', data: [ { name: '天线' }, { name: '飞线', itemStyle: { color: '#000' } }, { name: '中线' }, { name: '流线' } ] }, tooltip: [ { trigger: 'axis', formatter: function (a, b, d) { if (a[0].seriesIndex == 0) { return ( a[0].name + '<br/>' + '开盘价' + ':' + a[0].data[1] + '<br/>' + '收盘价' + ':' + a[0].data[2] + '<br/>' + '最低价' + ':' + a[0].data[3] + '<br/>' + '最高价' + ':' + a[0].data[4] + '<br/>' + a[3].marker + a[3].seriesName + ':' + a[3].value + '<br/>' + a[1].marker + a[1].seriesName + ':' + a[1].value + '<br/>' + a[2].marker + a[2].seriesName + ':' + a[2].value + '<br/>' + a[4].marker + a[4].seriesName + ':' + a[4].value ) // }else if(a[0].seriesIndex == 5){
// console.log(a[0].seriesIndex,'a[0].seriesIndex')
// return a[0].name+ "<br/>" + a[0].seriesName + ":" + a[0].value + "<br/>" + a[1].seriesName + ":" + a[1].value + "<br/>" + a[2].seriesName + ":" + a[2].value + "<br/>" + a[3].seriesName + ":" + a[3].value + "<br/>" + a[4].seriesName + ":" + a[4].value + "<br/>" + a[5].seriesName + ":" + a[5].value
} else { return ( a[0].name + '<br/>' + a[0].marker + a[0].seriesName + ':' + a[0].value + '<br/>' + a[1].marker + a[1].seriesName + ':' + a[1].value + '<br/>' + a[2].marker + a[2].seriesName + ':' + a[2].value + '<br/>' + a[3].marker + a[3].seriesName + ':' + a[3].value + '<br/>' + a[4].marker + a[4].seriesName + ':' + a[4].value + '<br/>' ) } }, axisPointer: { type: 'cross' }, backgroundColor: '#fff', borderWidth: 1, borderColor: '#ccc', padding: 10, textStyle: { color: '#000' } } ], axisPointer: { link: [ { xAxisIndex: 'all' } ], label: { backgroundColor: '#777' } }, toolbox: { show: false },
visualMap: { show: false, seriesIndex: 5, dimension: 2, pieces: [ { value: 1, color: downColor }, { value: -1, color: upColor } ] }, grid: [ { top: '8%', // left: window.innerWidth > 768 ? '10%' : '17%',
right: '8%', height: '40%', containLabel: false }, { // left: window.innerWidth > 768 ? '10%' : '17%',
right: '8%', top: '10%', height: '1%', containLabel: false }, { // left: window.innerWidth > 768 ? '10%' : '17%',
right: '8%', top: window.innerWidth > 768 ? '60%' : '63%', height: '30%', 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: '#8392A5' } } }, { type: 'category', gridIndex: 1, data: [], boundaryGap: true, axisLine: { onZero: false }, axisTick: { show: false }, splitLine: { show: false }, axisLabel: { show: false }, min: 'dataMin', max: 'dataMax', show: false }, { type: 'category', gridIndex: 2, data: dealData.categoryData, boundaryGap: true, axisLine: { onZero: true, lineStyle: { color: '#8392A5' } }, axisTick: { show: false }, splitLine: { show: false }, axisLabel: { show: false }, min: 'dataMin', max: 'dataMax' } ], yAxis: [ { scale: true, splitArea: { show: false }, splitLine: { show: !1 }, axisLine: { lineStyle: { color: '#8392A5' } } }, { scale: true, gridIndex: 1, splitNumber: 2, min: '0', max: '100', axisLabel: { show: false },
axisLine: { show: false }, axisTick: { show: false }, splitLine: { show: false } }, { scale: true, gridIndex: 2, splitNumber: 2, axisLabel: { show: true }, axisLine: { onZero: true }, axisLine: { show: true, onZero: false, lineStyle: { color: '#8392A5' } }, axisTick: { show: true }, splitLine: { show: false } } ], dataZoom: [ { type: 'inside', xAxisIndex: [0, 1, 2], start: 50, end: 100, textStyle: { color: '#8392A5' } }, { show: true, xAxisIndex: [0, 1, 2], type: 'slider', top: '85%', start: 50, end: 100, textStyle: { color: '#8392A5' } } ], series: [ { name: 'Dow-Jones index', type: 'candlestick', data: dealData.values, itemStyle: { normal: { color0: '#ec0000', color: '#00da3c', borderColor0: '#ec0000', borderColor: '#00da3c' } } }, { name: '飞线', type: 'line', data: calculateMA(1, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#00a32e', lineStyle: { color: '#00a32e', width: 2, type: 'solid' } } } }, { name: '中线', type: 'line', data: calculateMA(2, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#de0000', lineStyle: { color: '#de0000', width: 2, type: 'solid' } } } }, { name: '天线', type: 'line', data: calculateMA(3, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#ffb300', lineStyle: { color: '#ffb300', width: 2, type: 'solid' } } } }, { name: '流线', type: 'line', data: calculateMA(4, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#00c8ff', lineStyle: { color: '#00c8ff', width: 2, type: 'solid' } } } }, { name: '买盘', data: bodongliangData(bodongliang.values, 1), xAxisIndex: 2, yAxisIndex: 2, type: 'bar', stack: '1', itemStyle: { normal: { color: '#ec0000' } } }, { name: '卖盘', data: bodongliangData(bodongliang.values, 0), xAxisIndex: 2, yAxisIndex: 2, type: 'bar', stack: '1', itemStyle: { normal: { color: '#00ffff' } } },
{ name: 'CJ', data: bodongliangData(bodongliang.values, 2), xAxisIndex: 2, yAxisIndex: 2, type: 'line', smooth: !0, showSymbol: false, itemStyle: { normal: { color: '#B0B0B0' } }, lineStyle: { width: 2 } }, { name: 'CD', data: bodongliangData(bodongliang.values, 3), xAxisIndex: 2, yAxisIndex: 2, type: 'line', smooth: !0, showSymbol: false, itemStyle: { normal: { color: '#ffb300' } }, lineStyle: { width: 2 } }, { name: 'CK', data: bodongliangData(bodongliang.values, 4), xAxisIndex: 2, yAxisIndex: 2, type: 'line', smooth: !0, showSymbol: false, itemStyle: { normal: { color: '#ff00ff' } }, lineStyle: { width: 2 } } ] }
} else if (klineData.type === 6) { console.log('进入第6分类')
var bodongliang = splitData(data.WAVEVOL) function bodongliangData(values, i) { return values.map((subArray) => subArray[i]) } function calculateMA(index, data) { let result = [] if (data.FTLINE) { data.FTLINE.forEach((item) => { result.push(item[index]) }) } return result } function vwToPx(vw) { return (window.innerWidth * vw) / 100 } 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]) } }) }) } } 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 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: ,
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(2.8) }, width: '100%', top: '5%', left: 'center', itemGap: window.innerWidth > 768 ? 20 : 10, itemWidth: 10, itemHeight: 10, data: [ { name: '天线' }, { name: '飞线', itemStyle: { color: '#fff' } }, { name: '中线' }, { name: '流线' } ] }, { textStyle: { color: 'black', fontSize: window.innerWidth > 768 ? 15 : vwToPx(2.8) }, orient: 'horizontal', top: window.innerWidth > 768 ? '8%' : '8%', width: '100%', left: 'center', itemGap: 15, data: [ { name: '龙线' }, { name: '虫线' } ] } ], 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 || [] return ( a[0].name + '<br/>' + '开盘价' + ':' + KlineTag.value[1] + '<br/>' + '收盘价' + ':' + KlineTag.value[2] + '<br/>' + '最低价' + ':' + KlineTag.value[3] + '<br/>' + '最高价' + ':' + KlineTag.value[4] + '<br/>' + a[4].seriesName + ':' + a[4].value + '<br/>' + a[5].seriesName + ':' + a[5].value + '<br/>' + a[6].seriesName + ':' + a[6].value + '<br/>' + a[7].seriesName + ':' + a[7].value ) } 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 (a[0].seriesIndex == 9) { return ( a[0].name + '<br/>' + a[0].marker + a[0].seriesName + ':' + a[0].value + '<br/>' + a[1].marker + a[1].seriesName + ':' + a[1].value + '<br/>' + a[2].marker + a[2].seriesName + ':' + a[2].value + '<br/>' + a[3].marker + a[3].seriesName + ':' + a[3].value + '<br/>' + a[4].marker + a[4].seriesName + ':' + a[4].value + '<br/>' ) } }, 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 ? '14%' : '15%', right: window.innerWidth > 768 ? '4%' : '5%', top: window.innerWidth > 768 ? '10%' : '12%', height: window.innerWidth > 768 ? '36%' : '34%', containLabel: false }, { left: window.innerWidth > 768 ? '14%' : '15%', right: window.innerWidth > 768 ? '4%' : '5%', top: window.innerWidth > 768 ? '50%' : '50%', height: window.innerWidth > 768 ? '20%' : '22%', containLabel: false }, { left: window.innerWidth > 768 ? '14%' : '15%', right: window.innerWidth > 768 ? '4%' : '5%', top: window.innerWidth > 768 ? '74%' : '75%', height: window.innerWidth > 768 ? '20%' : '20%', 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, // fontSize: window.innerWidth > 768 ? 10 : 8,
interval: 'auto' }, axisTick: { show: false } }, { type: 'category', gridIndex: 2, data: dealData.categoryData, boundaryGap: true, axisLine: { lineStyle: { color: 'black' } }, axisLabel: { show: true, interval: 'auto' }, axisTick: { show: false } } ], 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%'] }, { scale: true, gridIndex: 2, splitNumber: 2, axisLabel: { show: true, fontSize: window.innerWidth > 768 ? 15 : 10 }, axisLine: { show: true, lineStyle: { color: 'black' } }, axisTick: { show: false }, splitLine: { show: false } } ], dataZoom: [ { type: 'inside', xAxisIndex: [0, 1, 2], start: 55, end: 100 }, { show: true, xAxisIndex: [0, 1, 2], type: 'slider', top: window.innerWidth > 768 ? '95%' : '95%', left: window.innerWidth > 768 ? '14%' : '14%', start: 98, end: 100 } ], 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, // markPoint: { data: dealMarkPointData },
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: '飞线', type: 'line', data: calculateMA(1, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#00a32e', lineStyle: { color: '#00a32e', width: 2, type: 'solid' } } } }, { name: '中线', type: 'line', data: calculateMA(2, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#de0000', lineStyle: { color: '#de0000', width: 2, type: 'solid' } } } }, { name: '天线', type: 'line', data: calculateMA(3, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#ffb300', lineStyle: { color: '#ffb300', width: 2, type: 'solid' } } } }, { name: '流线', type: 'line', data: calculateMA(4, data), smooth: true, symbol: 'none', itemStyle: { normal: { color: '#00c8ff', lineStyle: { color: '#00c8ff', width: 2, type: 'solid' } } } }, { name: '买盘', data: bodongliangData(bodongliang.values, 1), barWidth: '70%', xAxisIndex: 2, yAxisIndex: 2, type: 'bar', stack: '1', itemStyle: { normal: { color: '#ec0000' } } }, { name: '卖盘', data: bodongliangData(bodongliang.values, 0), barWidth: '70%', xAxisIndex: 2, yAxisIndex: 2, type: 'bar', stack: '1', itemStyle: { normal: { color: '#00ffff' } } }, { name: 'CJ', data: bodongliangData(bodongliang.values, 2), xAxisIndex: 2, yAxisIndex: 2, type: 'line', smooth: !0, showSymbol: false, itemStyle: { normal: { color: '#000' } }, lineStyle: { width: 2 } }, { name: 'CD', data: bodongliangData(bodongliang.values, 3), xAxisIndex: 2, yAxisIndex: 2, type: 'line', smooth: !0, showSymbol: false, itemStyle: { normal: { color: '#ffb300' } }, lineStyle: { width: 2 } }, { name: 'CK', data: bodongliangData(bodongliang.values, 4), xAxisIndex: 2, yAxisIndex: 2, type: 'line', smooth: !0, showSymbol: false, itemStyle: { normal: { color: '#ff00ff' } }, lineStyle: { width: 2 } } ] // graphic: {
// markPointData : generateGraphicmarkPointData(data11111)
// }
}
} else { console.log('其他分类')
var dealData = splitData(data.KLine20) console.log('dealData', dealData)
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.values, 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.values.length; d < g; d++) { if (d < a) { MA5.push('-') } else { for (var f = 0, e = 0; e < a; e++) { f += dealData.values[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) => { // 添加状态锁定逻辑
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].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("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 class="marquee-container"> <div id="top" class="marquee-row top" @mouseenter="floatingTopMouseEnter" @mouseleave="floatingTopMouseLeave"> <div v-for="(questions, index) in questionsList.slice(0, 5)" :key="'top' + index" class="marquee-item" @click="showQuestions(questions)"> {{ questions.title }} </div> </div> <div id="bottom" class="marquee-row bottom" @mouseenter="floatingBottomMouseEnter" @mouseleave="floatingBottomMouseLeave"> <div v-for="(questions, index) in questionsList.slice(5, 10)" :key="'bottom' + index" class="marquee-item" @click="showQuestions(questions)"> {{ questions.title }} </div> </div> </div> </div>
<!-- 消息区域 --> <!-- <div class="message-area"> <div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]"> {{ msg.content }} </div> </div> --> <!-- <div class="message-area"> <div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]" v-html="msg.content"> </div> </div> -->
<!-- <div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]" v-html="msg.content"> </div> -->
<div v-for="(msg, index) in chatMsg" :key="index" :class="['message-bubble', msg.sender]"> <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 v-html="msg.content"></div> </div>
<!-- 新闻弹窗 --> <el-dialog v-model="dialogVisible" title="新闻详情" width="60%"> <p>{{ currentQuestions.content }}</p> </el-dialog> </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%); } }
.message-bubble { max-width: 70%; margin: 10px 0px; padding: 15px 25px; border-radius: 10px; position: relative; }
.message-bubble.user { background: #8263f0; color: white; margin-left: auto; margin-right: 20px; /* border-bottom-right-radius: 5px; */ }
.message-bubble.ai { background: #ffffff; color: #333; margin-right: auto; margin-left: 20px; /* border-bottom-left-radius: 5px; */ }
.kline-container { margin-top: 10px; /* 最小高度 */ min-height: 320px; /* 视口高度单位 */ height: 40vh; min-width: 50vw; }
@media (max-width: 768px) { .kline-container {
min-width: 75vw; } }
.kline-container .chart-mount-point { height: 100%; width: 100%; } </style>
|