You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

4770 lines
137 KiB

<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>