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