k-line/src/end/EndDesign.vue
2025-02-12 18:59:45 +08:00

958 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import {
onMounted,
inject,
ref,
nextTick,
onBeforeUnmount,
} from 'vue'
import {
calculateMA,
calculateStdDeviation,
calculateMACD,
calculateKDJ,
calculateRSI,
calculateVOL,
} from './computedInfo'
let charts1 = ref(null)
let charts2 = ref(null)
let charts3 = ref(null)
let charts4 = ref(null)
// 请求后端数据
let szdata = ref([])
let kdata = ref([])
let xdata = ref([])
let closedata = ref([])
let ma5 = ref()
// 标准差
let StdDev = ref()
let topStdDev = ref()
let downStdDev = ref()
let rsi1 = ref()
let rsi2 = ref()
let rsi3 = ref()
let vol = ref()
let interval = ref()
import request from '@/utils/request'
let baseURL = 'http://127.0.0.1:8015'
const redata = () => {
try {
request({
url: baseURL + '/stock',
method: 'get',
params: {
symbol: 'sz000001',
start_date: '2021-01-01',
end_date: '2022-12-31',
},
}).then((res) => {
xdata.value = Object.values(res.date)
let { open, close, low, high, volume } = res
for (let i = 0; i < Object.keys(open).length; i++) {
kdata.value.push([open[i], close[i], low[i], high[i]])
closedata.value.push(close[i])
}
// 计算5日移动平均线
ma5.value = calculateMA(closedata.value, 5)
StdDev.value = calculateStdDeviation(closedata.value)
topStdDev.value = closedata.value.map((item) =>
parseFloat((item + StdDev.value).toFixed(2))
)
downStdDev.value = closedata.value.map((item) =>
parseFloat((item - StdDev.value).toFixed(2))
)
// MACD数据
let { dif, dea, macd } = calculateMACD(closedata.value)
// 准备数据数组,同时设置颜色
macd = macd.map(function (value) {
return {
value: value,
itemStyle: {
color: value >= 0 ? '#FF0000' : '#00FF00', // 根据值的正负设置颜色
},
}
})
chartConfigs.value[0].options.series[0].data = dif
chartConfigs.value[0].options.series[1].data = dea
chartConfigs.value[0].options.series[2].data = macd
chartConfigs.value[0].options.xAxis.data = xdata.value
// vol数据
vol.value = calculateVOL(close, high, low)
chartConfigs.value[1].options.xAxis.data = xdata.value
chartConfigs.value[1].options.series[0].data = vol.value
// KDJ数据
let { kValues, dValues, jValues } = calculateKDJ(res)
chartConfigs.value[2].options.series[0].data = kValues
chartConfigs.value[2].options.series[1].data = dValues
chartConfigs.value[2].options.series[2].data = jValues
chartConfigs.value[2].options.xAxis.data = xdata.value
// RSI数据
rsi1.value = calculateRSI(closedata.value)
rsi2.value = calculateRSI(closedata.value, 12)
rsi3.value = calculateRSI(closedata.value, 24)
chartConfigs.value[3].options.series[0].data = rsi1.value
chartConfigs.value[3].options.series[1].data = rsi2.value
chartConfigs.value[3].options.series[2].data = rsi3.value
chartConfigs.value[3].options.xAxis.data = xdata.value
dynamicsHeight()
configecharts()
})
} catch (err) {
console.error('请求失败:', err)
}
}
//引入echarts
let echarts = inject('echarts')
// 外层盒子容器,为了计算图形在内部的高度分配问题
const chartsInfo = ref(null)
// K线图的容器
const mKline = ref(null)
// 存储各个图形所分配的高度信息
let heightList = ref([])
// 计算高度、设置高度
const dynamicsHeight = () => {
// 高度
let domHeightList = chartsInfo.value.offsetHeight
// 判断是否进行了底部选项的点击事件,如果点击了则会往数组中添加信息,则长度就会改变
// if (clicklist.value.length) {
// // 先清空数组,避免
// heightList.value = []
// // 将处理过后的数据加入数组
// heightList.value.push(domHeightList * (1 - clicklist.value.length * 0.15))
// // 设置最上面K线图的高度
// mKline.value.style.height = `${heightList.value[0]}px`
// // 设置新添加图表的高度
// heightList.value.push(domHeightList * 0.15)
// console.log(heightList.value)
// } else {
// // 先清空数组,避免之前的数据没有被清空
// heightList.value = []
// // 设置只有K线图的高度的父容器的全部大小
// mKline.value.style.height = `${domHeightList}px`
// }
console.log(chartsInfo.value.children.length)
// 子元素长度
let childrenLength = chartsInfo.value.children.length
if (childrenLength) {
// 先清空数组,避免
heightList.value = []
// 将处理过后的数据加入数组
heightList.value.push(domHeightList * (1 - childrenLength * 0.15))
// 设置最上面K线图的高度
mKline.value.style.height = `${heightList.value[0]}px`
// 设置新添加图表的高度
heightList.value.push(domHeightList * 0.15)
console.log(heightList.value)
} else {
// 先清空数组,避免之前的数据没有被清空
heightList.value = []
// 设置只有K线图的高度的父容器的全部大小
mKline.value.style.height = `${domHeightList}px`
}
}
// 定义图表配置数组
let chartConfigs = ref([
{
id: 1,
name: 'MACD',
attId: 'charts1',
status: false,
options: {
animation: false,
tooltip: {
trigger: 'axis', // 触发类型为坐标轴
axisPointer: {
// 坐标轴指示器
type: 'cross', // 类型为交叉
label: {
backgroundColor: '#6a7985', // 标签背景色
},
},
},
dataZoom: [
{
type: 'inside',
},
{
type: 'slider',
show: false,
},
],
xAxis: {
type: 'category',
boundaryGap: false,
data: [],
},
yAxis: {
type: 'value',
},
series: [
{
name: 'DIF',
type: 'line',
itemStyle: {
normal: {
color: 'rgba(204,102,0,1)',
},
},
symbol: 'none',
data: [],
},
{
name: 'DEA',
type: 'line',
itemStyle: {
normal: {
color: 'rgba(0,128,255,1)',
},
},
symbol: 'none',
data: [],
},
{
name: 'MACD',
type: 'bar',
barWidth: '1',
// symbol: 'none',
data: [],
},
],
},
},
{
id: 2,
name: 'VOL',
attId: 'charts2',
status: false,
options: {
animation: false,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985',
},
},
},
dataZoom: [
{
type: 'inside',
},
{
type: 'slider',
show: false,
},
],
xAxis: {
type: 'category',
data: [],
},
yAxis: {
type: 'value',
// splitNumber: 3 ,
interval: 10,
},
series: [
{
name: 'VOL',
type: 'bar',
data: [],
},
],
},
},
{
id: 3,
name: 'KDJ',
attId: 'charts3',
status: false,
options: {
animation: false,
tooltip: {
trigger: 'axis', // 触发类型为坐标轴
axisPointer: {
// 坐标轴指示器
type: 'cross', // 类型为交叉
label: {
backgroundColor: '#6a7985', // 标签背景色
},
},
},
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 0],
start: 20,
end: 100,
},
{
type: 'slider',
show: false,
},
],
xAxis: {
type: 'category',
boundaryGap: false,
data: [],
},
series: [
{
name: 'K',
type: 'line',
data: [],
symbol: 'none',
smooth: true,
// lineStyle: {
// width: 1,
// },
// areaStyle: {
// opacity: 0.5,
// },
// itemStyle: {
// color: 'rgba(0,128,255,1)',
// },
},
{
name: 'D',
type: 'line',
data: [],
symbol: 'none',
smooth: true,
// lineStyle: {
// width: 1,
// },
// areaStyle: {
// opacity: 0.5,
// },
// itemStyle: {
// color: 'rgba(0,128,255,1)',
// },
},
{
name: 'J',
type: 'line',
data: [],
symbol: 'none',
smooth: true,
// lineStyle: {
// width: 1,
// },
// areaStyle: {
// opacity: 0.5,
// },
// itemStyle: {
// color: 'rgba(0,128,255,1)',
// },
},
],
},
},
{
id: 4,
name: 'RSI',
attId: 'charts4',
status: false,
options: {
animation: false,
tooltip: {
trigger: 'axis', // 触发类型为坐标轴
axisPointer: {
// 坐标轴指示器
type: 'cross', // 类型为交叉
label: {
backgroundColor: '#6a7985', // 标签背景色
},
},
},
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 0],
start: 20,
end: 100,
},
{
type: 'slider',
show: false,
},
],
xAxis: {
type: 'category',
boundaryGap: false,
data: [],
},
series: [
{
name: 'rsi1',
type: 'line',
data: [],
symbol: 'none',
smooth: true,
// lineStyle: {
// width: 1,
// },
// areaStyle: {
// opacity: 0.5,
// },
// itemStyle: {
// color: 'rgba(0,128,255,1)',
// },
},
{
name: 'rsi2',
type: 'line',
data: [],
symbol: 'none',
smooth: true,
// lineStyle: {
// width: 1,
// },
// areaStyle: {
// opacity: 0.5,
// },
// itemStyle: {
// color: 'rgba(0,128,255,1)',
// },
},
{
name: 'rsi3',
type: 'line',
data: [],
symbol: 'none',
smooth: true,
// lineStyle: {
// width: 1,
// },
// areaStyle: {
// opacity: 0.5,
// },
// itemStyle: {
// color: 'rgba(0,128,255,1)',
// },
},
],
},
},
])
// 储存点击底部选项的状态的数据
const clicklist = ref([])
// 处理点击事件并添加图表
const addChart = (item) => {
// 检查数组中是否已经存在具有相同domId的元素
// let domToRemove = chartBoxList.value.find(element => element.domId === item.attId)
// if(domToRemove){
// domToRemove.chart.dispose()
// }
// let indexToRemove = chartBoxList.value.findIndex(element => element.domId === item.attId)
// // 如果找到了,移除那个元素
// if (indexToRemove !== -1) {
// chartBoxList.value.splice(indexToRemove, 1)
// }
// 判断当前点击的图表的状态,如果已经存在则不进行操作
if (clicklist.value.includes(item.name)) {
// 先移除当前的图表信息
// clicklist.value = clicklist.value.filter((content) => content !== item.name)
// if (item.attId === 'charts1') {
// charts1.value.style.display = 'none'
// }
// 调用高度计算函数,重新分配高度
dynamicsHeight()
// 延迟执行,确保图表已经创建
nextTick(() => {
chartConfigs.value.forEach((element) => {
if (element.name == item.name) {
element.status = false
// 调用configecharts函数刷新图表防止图形重叠
configecharts()
}
})
})
} else {
// 往数组里面添加参数信息
// clicklist.value.push(item.name)
// 遍历数组找到对应名称的图表并设置状态为true
chartConfigs.value.forEach((element) => {
if (element.name == item.name) {
element.status = true
}
})
// 调用高度计算函数,重新分配高度
dynamicsHeight()
// 延迟执行,确保图表已经创建
nextTick(() => {
chartConfigs.value.forEach((element) => {
if (element.name == item.name) {
element.status = true
// 调用configecharts函数刷新图表防止图形重叠
configecharts()
// 调用commonecharts函数会根据传入的参数进行图表的绘制
// commonecharts(item.attId, item.options)
if (!clicklist.value.includes(item.name)) {
commonecharts(item.attId, item.options)
clicklist.value.push(item.name)
}
// if (item.attId === 'charts1') {
// charts1.value.style.display = 'block'
// }
}
})
})
}
}
// 绘制初始的Echarts图表始终会在页面中。并返回当前图表的实例化信息便于使用echarts.connect()进行连接
const configecharts = (options = {}) => {
// 先将容器中的_echarts_instance_清除不处理会导致高度变化了但图表不刷新
document.getElementById('main').removeAttribute('_echarts_instance_')
// 初始化k线图图表
let chartBox = echarts.init(document.getElementById('main'))
// 直接使用pinia中储存的option数据
const option = {
animation: false,
tooltip: {
trigger: 'axis', // 触发类型为坐标轴
axisPointer: {
// 坐标轴指示器
type: 'cross', // 类型为交叉
label: {
backgroundColor: '#6a7985', // 标签背景色
},
animation: false,
},
// formatter: (params)=> {
// let str = '<div style="padding: 3px 12px;width: 161px;background: #FFFFFF;border: 1px solid #DCDFE6;box-shadow:none; opacity: 1;border-radius: 4px;"><span style="color:#606062;font-size: 12px;">' + params[0].axisValue + "</span><br />";
// params.forEach((item) => {
// str +=
// '<span style="color:#f00;font-size: 12px;color: #1D1D20;"><span style="float:left; margin-top:8px; margin-right:5px;border-radius:50%;width:6px;height:6px;background-color:'+item.color+'"></span>' + "&nbsp;&nbsp;&nbsp;&nbsp;" + item.data + "</span>";
// });
// str += '</div>'
// return str;
// // console.log(params[0]);
// }
},
legend: {
data: ['KLine', 'MA5', 'BOLL1', 'BOLL2'],
},
grid: [
{
left: '3%',
right: '3%',
bottom: '5%',
},
],
xAxis: [
{
type: 'category',
data: xdata.value,
gridIndex: 0,
scale: true,
boundaryGap: false,
axisLine: { onZero: false },
splitLine: { show: false },
splitNumber: 20,
min: 'dataMin',
max: 'dataMax',
sampling: 'lttb', // 最大程度保证采样后线条的趋势,形状和极值。
},
],
yAxis: [
{
gridIndex: 0,
scale: true,
splitArea: {
show: false,
},
},
],
dataZoom: [
{
type: 'slider',
},
{
type: 'inside',
},
],
series: [
{
name: 'KLine',
type: 'candlestick',
xAxisIndex: 0,
yAxisIndex: 0,
data: kdata.value,
itemStyle: {
normal: {
color: '#ef232a',
color0: '#14b143',
borderColor: '#ef232a',
borderColor0: '#14b143',
},
},
// markArea: {
// silent: true,
// itemStyle: {
// normal: {
// color: 'Honeydew',
// },
// },
// data: [],
// },
// markPoint: {
// data: [
// { type: 'max', name: '最大值' },
// { type: 'min', name: '最小值' },
// ],
// },
// markLine: {
// label: {
// normal: {
// position: 'middle',
// textStyle: { color: 'Blue', fontSize: 12 },
// },
// },
// data: [],
// symbol: ['circle', 'none'],
// },
},
{
name: 'MA5',
type: 'line',
xAxisIndex: 0,
yAxisIndex: 0,
data: ma5.value,
symbol: 'none',
smooth: true,
lineStyle: {
normal: {
opacity: 0.5,
},
},
},
{
name: 'BOLL1',
type: 'line',
xAxisIndex: 0,
yAxisIndex: 0,
data: topStdDev.value,
symbol: 'none',
smooth: true,
lineStyle: {
normal: {
opacity: 0.5,
color: 'red',
},
},
// areaStyle: {
// // 线条样式
// normal: {
// // 右,下,左,上
// color: new echarts.graphic.LinearGradient(0, 0, 0, 1,
// [
// // 渐变色
// {
// offset: 0,
// color: 'rgba(255, 46, 106, 0.21)'
// },
// {
// offset: 1,
// color: 'rgba(255, 46, 106, 0)'
// }
// ],
// false
// )
// }
// }
},
{
name: 'BOLL2',
type: 'line',
xAxisIndex: 0,
yAxisIndex: 0,
data: downStdDev.value,
symbol: 'none',
smooth: true,
lineStyle: {
normal: {
opacity: 0.5,
color: 'green',
},
},
// areaStyle: {
// // 线条样式
// normal: {
// // 右,下,左,上
// color:'white'
// }
// }
},
// {
// name: '',
// type: 'line',
// xAxisIndex: 0,
// yAxisIndex: 0,
// data: ma5.value,
// smooth: true,
// lineStyle: {
// normal: {
// opacity: 0.5,
// },
// },
// },
],
}
// 配置图表数据信息
chartBox.setOption(option)
// mergeOptions方法可以合并两个对象并返回合并后的对象。
// 更新信息,可有可无?,调用时配置
chartBox.setOption(options)
// 图表的分组,用于联动
chartBox.group = 'group1'
// 根据页面大小自动响应图表大小
window.addEventListener('resize', function () {
chartBox.resize()
})
}
// chart实例化数组
let chartBoxList = ref([])
//绘制一个点击事件传参的Echarts图表。并返回当前图表的实例化信息便于使用echarts.connect()进行连接
const commonecharts = (containerId, options = {}) => {
// 先将容器中的_echarts_instance_清除不处理会导致高度变化了但图表不刷新
document.getElementById(containerId).removeAttribute('_echarts_instance_')
// 初始化图表
const chart = echarts.init(document.getElementById(containerId))
chartBoxList.value.push({
chart,
chartid: chart.id,
domId: containerId,
})
console.log(chartBoxList.value)
// 图表的配置信息
const option = {
xAxis: {
type: 'category',
data: xdata.value,
},
yAxis: {
type: 'value',
},
grid: {
top: '10%',
left: '3%',
right: '3%',
bottom: '20%',
},
dataZoom: [
{
type: 'inside',
},
],
series: [
{
data: [1, 2, 3, 4, 5, 6, 7, 2, 12, 12, 34],
type: 'line',
smooth: true,
},
],
}
// 设置图表实例的配置项
chart.setOption(option)
// mergeOptions方法可以合并两个对象并返回合并后的对象。
chart.setOption(options)
// 图表的分组,用于联动
chart.group = 'group1'
// 根据页面大小自动响应图表大小
window.addEventListener('resize', function () {
chart.resize()
})
return chart
}
// 连接所有图表
echarts.connect('group1')
onMounted(() => {
redata()
const chart = echarts.init(charts1.value)
console.log(chart)
charts1.value.style.height = `${200}px`
// 图表的配置信息
const option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
grid: {
top: '10%',
left: '3%',
right: '3%',
bottom: '20%',
},
dataZoom: [
{
type: 'inside',
},
],
series: [
{
data: [1, 2, 3, 4, 5, 6, 7, 2, 12, 12, 34],
type: 'line',
smooth: true,
},
],
}
// 设置图表实例的配置项
chart.setOption(option)
// mergeOptions方法可以合并两个对象并返回合并后的对象。
// chart.setOption(options)
// 图表的分组,用于联动
chart.group = 'group1'
// 根据页面大小自动响应图表大小
window.addEventListener('resize', function () {
chart.resize()
})
})
onBeforeUnmount(() => {
// echarts.disconnect()
})
</script>
<template>
<div class="box">
<div class="chartsinfo" ref="chartsInfo">
<!-- K线图 -->
<div
id="main"
ref="mKline"
class="flex-item"
:style="{ height: `${heightList[0]}px` }"
></div>
<div
id="charts1"
ref="charts1"
:style="{ height: `${heightList[1]}px` }"
></div>
<!-- <div
v-if="chartConfigs[1].status"
id="charts2"
ref="charts2"
:style="{ height: `${heightList[1]}px` }"
></div>
<div
v-if="chartConfigs[2].status"
id="charts3"
ref="charts3"
:style="{ height: `${heightList[1]}px` }"
></div>
<div
v-if="chartConfigs[3].status"
id="charts4"
ref="charts4"
:style="{ height: `${heightList[1]}px` }"
></div> -->
</div>
<!-- 底部的各个图表选项信息 -->
<ul class="typelist">
<li
v-for="(item, index) in chartConfigs"
:key="index"
@click="addChart(item)"
>
{{ item.name }}
</li>
</ul>
</div>
</template>
<style scoped>
.box {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.chartsinfo {
height: calc(100vh - 20px);
}
.typelist {
margin-top: auto;
position: relative;
z-index: 99999999;
width: 100%;
color: #191919;
font-size: 12px;
display: flex;
list-style: none;
padding: 0;
}
.typelist > li {
padding: 2px 8px;
cursor: pointer;
}
.typelist > li:hover {
color: gold;
}
.active {
color: #2d98b9;
}
</style>