st-comp 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,409 +1,612 @@
1
1
  <script setup lang="ts">
2
- import * as echarts from 'echarts'
3
- import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
4
- import Tips from './components/Tips/index.vue'
5
- import Contextmenu from './components/Contextmenu/index.vue'
6
- import { getOption, getLineOption } from './option'
7
- import { printConsole, formatValue, formatPrice } from './utils'
8
- import type { EChartsType, EChartsOption, ECElementEvent, DataZoomComponentOption } from 'echarts'
9
- import type { KlineDataType, LineDataType, InConfig, MenuDataType } from './type.d.ts'
2
+ import * as echarts from "echarts";
3
+ import { ref, computed, watch, onMounted, onUnmounted } from "vue";
4
+ import Tips from "./components/Tips/index.vue";
5
+ import Contextmenu from "./components/Contextmenu/index.vue";
6
+ import { getOption, getLineOption } from "./option";
7
+ import { printConsole, formatValue, formatPrice } from "./utils";
8
+ import type { EChartsType, EChartsOption, ECElementEvent, DataZoomComponentOption } from "echarts";
9
+ import type { KlineDataType, LineDataType, InConfig, MenuDataType } from "./type.d.ts";
10
10
 
11
- /**
12
- * 组件接收参数
13
- * @param {Object} indicator 指标线
14
- * @param {KlineDataType} klineData K线数据
15
- * @param {Array} markData 标注点位数据 [开平点, 买卖点, 信号点,...]
16
- * @param {Array} lineData K线额外画线数据 [预警线, 持仓线, 条件单, ...]
17
- * @param {Array} brushRange 区域刷选范围数据
18
- * @param {Array} defaultMenuData 菜单项数据
19
- *
20
- * @param {InConfig} config 其他配置项数据
21
- */
22
- const props = defineProps({
23
- indicator: {
24
- type: Object,
25
- default: () => ({}),
26
- },
27
- klineData: {
28
- type: Array,
29
- default: () => [],
30
- },
31
- markData: {
32
- type: Array,
33
- default: () => [],
34
- },
35
- lineData: {
36
- type: Array,
37
- default: () => [],
38
- },
39
- brushRange: {
40
- type: Array,
41
- default: () => [],
42
- },
43
- defaultMenuData: {
44
- type: Array,
45
- default: () => [],
46
- },
47
- config: {
48
- type: Object,
49
- default: () => ({}),
50
- },
51
- })
11
+ /**
12
+ * 组件接收参数
13
+ * @param {Object} indicator 指标线
14
+ * @param {KlineDataType} klineData K线数据
15
+ * @param {Array} markData 标注点位数据 [开平点, 买卖点, 信号点,...]
16
+ * @param {Array} lineData K线额外画线数据 [预警线, 持仓线, 条件单, ...]
17
+ * @param {Array} brushRange 区域刷选范围数据
18
+ * @param {Array} defaultMenuData 菜单项数据
19
+ * @param {Boolean} isSelect 组件是否被选中
20
+ *
21
+ * @param {InConfig} config 其他配置项数据
22
+ */
23
+ const props = defineProps({
24
+ indicator: {
25
+ type: Object,
26
+ default: () => ({}),
27
+ },
28
+ klineData: {
29
+ type: Array,
30
+ default: () => [],
31
+ },
32
+ markData: {
33
+ type: Array,
34
+ default: () => [],
35
+ },
36
+ lineData: {
37
+ type: Array,
38
+ default: () => [],
39
+ },
40
+ brushRange: {
41
+ type: Array,
42
+ default: () => [],
43
+ },
44
+ defaultMenuData: {
45
+ type: Array,
46
+ default: () => [],
47
+ },
48
+ isSelect: {
49
+ type: Boolean,
50
+ default: () => false,
51
+ },
52
+ config: {
53
+ type: Object,
54
+ default: () => ({}),
55
+ },
56
+ });
52
57
 
53
- /**
54
- * K线功能默认配置
55
- * @param {Number} totalBarCount k线总条数
56
- * @param {Number} defaultShowBarCount k线默认展示条数
57
- * @param {Number} preBarCount k线预加载条数,用于计算指标线
58
- * ---------------------------------------------------------
59
- * @param {Number} gridLeft k线组件grid: left
60
- * @param {Number} gridTop k线组件grid: top
61
- * @param {Number} gridRight k线组件grid: right
62
- * @param {Number} gridBottom k线组件grid: bottom
63
- * ---------------------------------------------------------
64
- * @param {Object} warningConfig 预警线配置
65
- * @param {Object} positionConfig 持仓线配置
66
- * @param {Object} conditionConfig 条件单配置
67
- */
68
- const defaultConfig: InConfig = {
69
- totalBarCount: 3000,
70
- defaultShowBarCount: 120,
71
- preBarCount: 800,
72
- gridLeft: 60,
73
- gridTop: 0,
74
- gridRight: 60,
75
- gridBottom: 30,
76
- // 预警线配置
77
- warningConfig: {
78
- draggable: false,
79
- lineColor: '#fff',
80
- textColor: '#fff',
81
- },
82
- // 持仓线配置
83
- positionConfig: {
84
- draggable: false,
85
- lineColor: '#e45d07',
86
- textColor: '#fff',
87
- },
88
- // 条件单配置
89
- conditionConfig: {
90
- draggable: false,
91
- lineColor: '#fff',
92
- textColor: '#fff',
93
- profitLineColor: '#b71e44',
94
- profitTextColor: '#fff',
95
- lossLineColor: '#749b66',
96
- lossTextColor: '#fff',
97
- },
98
- // Tips配置
99
- tipsConfig: {
100
- open: true,
101
- heigh: true,
102
- low: true,
103
- close: true,
104
- business: true,
105
- riseAndFall: true
106
- }
107
- }
58
+ /**
59
+ * K线功能默认配置
60
+ * @param {Number} totalBarCount k线总条数
61
+ * @param {Number} defaultShowBarCount k线默认展示条数
62
+ * @param {Number} preBarCount k线预加载条数,用于计算指标线
63
+ * ---------------------------------------------------------
64
+ * @param {Number} gridLeft k线组件grid: left
65
+ * @param {Number} gridTop k线组件grid: top
66
+ * @param {Number} gridRight k线组件grid: right
67
+ * @param {Number} gridBottom k线组件grid: bottom
68
+ * ---------------------------------------------------------
69
+ * @param {Object} warningConfig 预警线配置
70
+ * @param {Object} positionConfig 持仓线配置
71
+ * @param {Object} conditionConfig 条件单配置
72
+ */
73
+ const defaultConfig: InConfig = {
74
+ totalBarCount: 2000,
75
+ defaultShowBarCount: 200,
76
+ preBarCount: 800,
77
+ gridLeft: 60,
78
+ gridTop: 0,
79
+ gridRight: 60,
80
+ gridBottom: 30,
81
+ // 预警线配置
82
+ warningConfig: {
83
+ draggable: false,
84
+ lineColor: "#fff",
85
+ textColor: "#fff",
86
+ },
87
+ // 持仓线配置
88
+ positionConfig: {
89
+ draggable: false,
90
+ lineColor: "#e45d07",
91
+ textColor: "#fff",
92
+ },
93
+ // 条件单配置
94
+ conditionConfig: {
95
+ draggable: false,
96
+ lineColor: "#fff",
97
+ textColor: "#fff",
98
+ profitLineColor: "#b71e44",
99
+ profitTextColor: "#fff",
100
+ lossLineColor: "#749b66",
101
+ lossTextColor: "#fff",
102
+ },
103
+ // Tips配置
104
+ tipsConfig: {
105
+ open: true,
106
+ heigh: true,
107
+ low: true,
108
+ close: true,
109
+ business: true,
110
+ riseAndFall: true,
111
+ },
112
+ // 动态加载配置
113
+ dynamicLoadConfig: {
114
+ historyVisible: false,
115
+ historyLoadCallBack: () => {},
116
+ futureVisible: false,
117
+ futureLoadCallBack: () => {},
118
+ },
119
+ };
108
120
 
109
- /**
110
- * @description: 合并后的功能配置项
111
- */
112
- const config: { value: InConfig } = computed(() => {
113
- return { ...defaultConfig, ...props.config }
114
- })
121
+ /**
122
+ * @description: 合并后的功能配置项
123
+ */
124
+ const config: { value: InConfig } = computed(() => {
125
+ return { ...defaultConfig, ...props.config };
126
+ });
115
127
 
116
- const option = ref<any>(null) // 处理后的echarts配置
117
- const activeIndex = ref(-1) // 当前鼠标激活的数据索引
128
+ const option = ref<any>(null); // 处理后的echarts配置
129
+ const activeIndex = ref(-1); // 当前鼠标激活的数据索引
118
130
 
119
- // Tips: [开, 收, 低, 高, 成交额, 昨收]
120
- const kLineTips = computed(() => {
121
- if (option.value && option.value.dataset[0]?.source?.klineData[activeIndex.value]) {
122
- const klineItem = option.value.dataset[0].source.klineData[activeIndex.value]
123
- // 处理结果
124
- const result = []
125
- const { open, heigh, low, close, business, riseAndFall } = config.value.tipsConfig
126
- open && result.push({ label: '', value: formatValue(klineItem[0]), color: 'rgb(153, 153, 153)' })
127
- heigh && result.push({ label: '', value: formatValue(klineItem[3]), color: 'rgb(153, 153, 153)' })
128
- low && result.push({ label: '', value: formatValue(klineItem[2]), color: 'rgb(153, 153, 153)' })
129
- close && result.push({ label: '', value: formatValue(klineItem[1]), color: 'rgb(153, 153, 153)' })
130
- business && result.push({ label: '', value: formatPrice(klineItem[4]), color: 'rgb(153, 153, 153)' })
131
- if (riseAndFall) {
132
- let ratio = (((klineItem[1] - klineItem[5]) / klineItem[1]) * 100).toFixed(2)
133
- let ratioColor = +ratio === 0.0 ? 'white' : +ratio > 0 ? 'red' : '#00ff00'
134
- result.push({ label: '涨跌', value: `${ratio}%`, color: ratioColor })
135
- }
136
- return result
131
+ // Tips: [开, 收, 低, 高, 成交额, 昨收]
132
+ const kLineTips = computed(() => {
133
+ if (option.value && option.value.dataset[0]?.source?.klineData[activeIndex.value]) {
134
+ const klineItem = option.value.dataset[0].source.klineData[activeIndex.value];
135
+ // 处理结果
136
+ const result = [];
137
+ const { open, heigh, low, close, business, riseAndFall } = config.value.tipsConfig;
138
+ open && result.push({ label: "", value: formatValue(klineItem[0]), color: "rgb(153, 153, 153)" });
139
+ heigh && result.push({ label: "", value: formatValue(klineItem[3]), color: "rgb(153, 153, 153)" });
140
+ low && result.push({ label: "", value: formatValue(klineItem[2]), color: "rgb(153, 153, 153)" });
141
+ close && result.push({ label: "", value: formatValue(klineItem[1]), color: "rgb(153, 153, 153)" });
142
+ business && result.push({ label: "", value: formatPrice(klineItem[4]), color: "rgb(153, 153, 153)" });
143
+ if (riseAndFall) {
144
+ let ratio = (((klineItem[1] - klineItem[5]) / klineItem[1]) * 100).toFixed(2);
145
+ let ratioColor = +ratio === 0.0 ? "white" : +ratio > 0 ? "red" : "#00ff00";
146
+ result.push({ label: "涨跌", value: `${ratio}%`, color: ratioColor });
137
147
  }
138
- return []
139
- })
140
- // Tips: 指标
141
- const indicatorTips = computed(() => {
142
- if (option.value && option.value.dataset[0]?.source?.indicatorData) {
143
- return option.value.dataset[0].source.indicatorData.reduce((result: any, next: any) => {
144
- next.data[activeIndex.value] &&
145
- result.push({
146
- label: next.key,
147
- value: formatValue(next.data[activeIndex.value]),
148
- color: next.color,
149
- })
150
- return result
151
- }, [])
152
- }
153
- return []
154
- })
148
+ return result;
149
+ }
150
+ return [];
151
+ });
152
+ // Tips: 指标
153
+ const indicatorTips = computed(() => {
154
+ if (option.value && option.value.dataset[0]?.source?.indicatorData) {
155
+ return option.value.dataset[0].source.indicatorData.reduce((result: any, next: any) => {
156
+ next.data[activeIndex.value] &&
157
+ result.push({
158
+ label: next.key,
159
+ value: formatValue(next.data[activeIndex.value]),
160
+ color: next.color,
161
+ });
162
+ return result;
163
+ }, []);
164
+ }
165
+ return [];
166
+ });
155
167
 
156
- // 监视: props[klineData, indicator, markData] -> 重绘全部
157
- watch(
158
- () => [props.klineData, props.indicator, props.markData],
159
- () => {
160
- draw()
161
- },
162
- {
163
- deep: true,
164
- }
165
- )
166
- // 监视: props.lineData - 额外画线数据 -> 重新绘制额外线
167
- watch(
168
- () => props.lineData,
169
- async () => {
170
- printConsole('st-kline组件消息:props.lineData监控,额外画线数据发生改变,重绘->line', {
171
- color: 'red',
172
- })
173
- draw('line')
174
- },
175
- {
176
- deep: true,
177
- }
178
- )
179
- // 监视: props.brushRange - 区域刷选数据 -> 重新刷选
180
- watch(
181
- () => props.brushRange,
182
- async () => {
183
- printConsole('st-kline组件消息:props.brushRange监控,区域刷选数据发生改变,重绘->brush', {
184
- color: 'red',
185
- })
186
- draw('brush')
187
- },
188
- {
189
- deep: true,
190
- }
191
- )
168
+ // 监视: props[klineData, indicator, markData] -> 重绘全部
169
+ watch(
170
+ () => [props.klineData, props.indicator, props.markData],
171
+ () => {
172
+ draw();
173
+ },
174
+ {
175
+ deep: true,
176
+ }
177
+ );
178
+ // 监视: props.lineData - 额外画线数据 -> 重新绘制额外线
179
+ watch(
180
+ () => props.lineData,
181
+ async () => {
182
+ printConsole("st-kline组件消息:props.lineData监控,额外画线数据发生改变,重绘->line", {
183
+ color: "red",
184
+ });
185
+ draw("line");
186
+ },
187
+ {
188
+ deep: true,
189
+ }
190
+ );
191
+ // 监视: props.brushRange - 区域刷选数据 -> 重新刷选
192
+ watch(
193
+ () => props.brushRange,
194
+ async () => {
195
+ printConsole("st-kline组件消息:props.brushRange监控,区域刷选数据发生改变,重绘->brush", {
196
+ color: "red",
197
+ });
198
+ draw("brush");
199
+ },
200
+ {
201
+ deep: true,
202
+ }
203
+ );
192
204
 
193
- /**
194
- * @description: 图表绘制函数
195
- * @param {?String} type 绘制类型[Kline-K线,line-额外线条,brush-区域刷选,不传-全部]
196
- */
197
- const draw = async (type?: string) => {
198
- const callBackMap = new Map([
199
- // kline-K线绘制
200
- [
201
- 'kline',
202
- async () => {
203
- const newOption = await getOption(
204
- props.klineData as KlineDataType,
205
- props.markData,
206
- props.indicator,
207
- config.value
208
- )
209
- const { graphic } = echartsInstance.getOption() ?? { graphic: [] }
210
- option.value = { ...newOption, graphic }
211
- echartsInstance.setOption(option.value, true)
212
- printConsole('st-kline组件消息:K线绘制完毕-draw', { color: 'green' })
213
- },
214
- ],
215
- // line-额外线条绘制
216
- [
217
- 'line',
218
- () => {
219
- const echartsOptions = echartsInstance.getOption()
220
- const graphic = getLineOption(props.lineData as LineDataType, config.value, echartsInstance)
221
- option.value = { ...echartsOptions, graphic }
222
- echartsInstance.setOption(option.value, {
223
- replaceMerge: ['graphic'],
224
- })
225
- printConsole('st-kline组件消息:额外画线绘制完毕-draw', { color: 'green' })
226
- },
227
- ],
228
- // brush-区域刷选
229
- [
230
- 'brush',
231
- () => {
232
- const { brushRange } = props
233
- if (brushRange instanceof Array && brushRange.length > 0) {
234
- echartsInstance.dispatchAction({
235
- type: 'brush',
236
- areas: [
237
- {
238
- brushType: 'lineX',
239
- coordRange: [brushRange[0] + '', brushRange[1] + ''],
240
- xAxisIndex: 0,
241
- },
242
- ],
243
- })
244
- printConsole('st-kline组件消息:区域刷选绘制完毕-draw', { color: 'green' })
245
- }
246
- },
247
- ],
248
- // 全部绘制
249
- [
250
- undefined,
251
- async () => {
252
- await draw('kline')
253
- await draw('line')
254
- await draw('brush')
255
- },
256
- ],
257
- ])
258
- const callBack = callBackMap.get(type)
259
- if (callBack instanceof Function) {
260
- await callBack()
261
- }
205
+ /**
206
+ * @description: 图表绘制函数
207
+ * @param {?String} type 绘制类型[Kline-K线,line-额外线条,brush-区域刷选,不传-全部]
208
+ */
209
+ const draw = async (type?: string) => {
210
+ const callBackMap = new Map([
211
+ // kline-K线绘制
212
+ [
213
+ "kline",
214
+ async () => {
215
+ const newOption = await getOption(
216
+ props.klineData as KlineDataType,
217
+ props.markData,
218
+ props.indicator,
219
+ config.value
220
+ );
221
+ const { graphic } = echartsInstance.getOption() ?? { graphic: [] };
222
+ option.value = { ...newOption, graphic };
223
+ echartsInstance.setOption(option.value, true);
224
+ printConsole("st-kline组件消息:K线绘制完毕-draw", { color: "green" });
225
+ },
226
+ ],
227
+ // line-额外线条绘制
228
+ [
229
+ "line",
230
+ () => {
231
+ const echartsOptions = echartsInstance.getOption();
232
+ const graphic = getLineOption(props.lineData as LineDataType, config.value, echartsInstance);
233
+ option.value = { ...echartsOptions, graphic };
234
+ echartsInstance.setOption(option.value, {
235
+ replaceMerge: ["graphic"],
236
+ });
237
+ printConsole("st-kline组件消息:额外画线绘制完毕-draw", { color: "green" });
238
+ },
239
+ ],
240
+ // brush-区域刷选
241
+ [
242
+ "brush",
243
+ () => {
244
+ const { brushRange } = props;
245
+ if (brushRange instanceof Array && brushRange.length > 0) {
246
+ echartsInstance.dispatchAction({
247
+ type: "brush",
248
+ areas: [
249
+ {
250
+ brushType: "lineX",
251
+ coordRange: [brushRange[0] + "", brushRange[1] + ""],
252
+ xAxisIndex: 0,
253
+ },
254
+ ],
255
+ });
256
+ printConsole("st-kline组件消息:区域刷选绘制完毕-draw", { color: "green" });
257
+ }
258
+ },
259
+ ],
260
+ // 全部绘制
261
+ [
262
+ undefined,
263
+ async () => {
264
+ await draw("kline");
265
+ await draw("line");
266
+ await draw("brush");
267
+ },
268
+ ],
269
+ // history-K线历史数据绘制
270
+ [
271
+ "history",
272
+ async () => {
273
+ const chartOption = echartsInstance.getOption();
274
+ const newOption = await getOption(
275
+ props.klineData as KlineDataType,
276
+ props.markData,
277
+ props.indicator,
278
+ config.value
279
+ );
280
+ const addDataLength =
281
+ newOption.dataset.source.klineData.length - chartOption.dataset[0].source.klineData.length;
282
+ echartsInstance.setOption(
283
+ {
284
+ ...newOption,
285
+ dataZoom: [
286
+ {
287
+ type: "inside",
288
+ xAxisIndex: [0, 0],
289
+ startValue: chartOption.dataZoom[0].startValue + addDataLength,
290
+ endValue: chartOption.dataZoom[0].endValue + addDataLength,
291
+ },
292
+ ],
293
+ },
294
+ true
295
+ );
296
+ },
297
+ ],
298
+ // future-后续数据绘制
299
+ [
300
+ "future",
301
+ async () => {
302
+ const chartOption = echartsInstance.getOption();
303
+ const newOption = await getOption(
304
+ props.klineData as KlineDataType,
305
+ props.markData,
306
+ props.indicator,
307
+ config.value
308
+ );
309
+ echartsInstance.setOption(
310
+ {
311
+ ...newOption,
312
+ dataZoom: [
313
+ {
314
+ type: "inside",
315
+ xAxisIndex: [0, 0],
316
+ start: ((props.klineData.length - config.value.defaultShowBarCount) / props.klineData.length) * 100, // 保持缩放大小不变
317
+ // start: ((chartOption.dataZoom[0].startValue + 1) / (props.klineData.length - config.value.preBarCount)) * 100, // 保持启始位置不变
318
+ end: 99.99,
319
+ },
320
+ ],
321
+ },
322
+ true
323
+ );
324
+ },
325
+ ],
326
+ ]);
327
+ const callBack = callBackMap.get(type);
328
+ if (callBack instanceof Function) {
329
+ await callBack();
262
330
  }
331
+ };
263
332
 
264
- //----------------------------右键菜单功能相关----------------------------------
265
- const cursorPenVisible = ref(false) // 画笔模式开关
333
+ //----------------------------右键菜单功能相关----------------------------------
334
+ const cursorPenVisible = ref(false); // 画笔模式开关
266
335
 
267
- const menuData = ref<MenuDataType>([])
336
+ const menuData = ref<MenuDataType>([]);
268
337
 
269
- /**
270
- * @description: 点击菜单项
271
- * @param {Object} item 菜单项的数据
272
- */
273
- const menuClick = (item: any) => {
274
- const { callBack } = item
275
- callBack instanceof Function && callBack(echartsInstance, cursorPenVisible)
276
- }
338
+ /**
339
+ * @description: 点击菜单项
340
+ * @param {Object} item 菜单项的数据
341
+ */
342
+ const menuClick = (item: any) => {
343
+ const { callBack } = item;
344
+ callBack instanceof Function && callBack(echartsInstance, cursorPenVisible);
345
+ };
277
346
 
278
- /**
279
- * @description: 菜单关闭的回调
280
- * @todo: 进行菜单内容的初始化
281
- */
282
- const closeContextMenuCallBack = () => {
283
- menuData.value = props.defaultMenuData as MenuDataType
284
- }
347
+ /**
348
+ * @description: 菜单关闭的回调
349
+ * @todo: 进行菜单内容的初始化
350
+ */
351
+ const closeContextMenuCallBack = () => {
352
+ menuData.value = props.defaultMenuData as MenuDataType;
353
+ };
285
354
 
286
- //----------------------------Echarts基座-------------------------------
287
- const echartsRef = ref<HTMLElement>()
288
- let echartsInstance: EChartsType // echarts实例
289
- let chartDomObserver: any // 监视图表DOM变化
355
+ //----------------------------Echarts基座-------------------------------
356
+ const echartsRef = ref<HTMLElement>();
357
+ let echartsInstance: EChartsType; // echarts实例
358
+ let chartDomObserver: any; // 监视图表DOM变化
290
359
 
291
- /**
292
- * @description: echarts数据高亮回调
293
- * @param {any} data echarts数据
294
- * @param {EChartsType} chart echarts实例
295
- */
296
- const highlight = (data: any, chart: EChartsType) => {
297
- if (data) {
298
- // 图表内部移动
299
- activeIndex.value = typeof data?.batch[0]?.dataIndex === 'number' ? data?.batch[0]?.dataIndex : -1
300
- } else {
301
- // 移出图表
302
- const chartOptions: EChartsOption = chart.getOption() as EChartsOption
303
- if (chartOptions.dataZoom instanceof Array) {
304
- activeIndex.value = chartOptions.dataZoom[0].endValue as number
305
- }
360
+ /**
361
+ * @description: echarts数据高亮回调
362
+ * @param {any} data echarts数据
363
+ * @param {EChartsType} chart echarts实例
364
+ */
365
+ const highlight = (data: any, chart: EChartsType) => {
366
+ if (data) {
367
+ // 图表内部移动
368
+ activeIndex.value = typeof data?.batch[0]?.dataIndex === "number" ? data?.batch[0]?.dataIndex : -1;
369
+ } else {
370
+ // 移出图表
371
+ const chartOptions: EChartsOption = chart.getOption() as EChartsOption;
372
+ if (chartOptions.dataZoom instanceof Array) {
373
+ activeIndex.value = chartOptions.dataZoom[0].endValue as number;
306
374
  }
307
375
  }
376
+ };
308
377
 
378
+ /**
379
+ * @description: echarts数据缩放的相关参数
380
+ * @param {any} datazoomTimer 缩放回调函数的延时器
381
+ * @param {Number} datazoomTime 缩放回调函数的延时器的时间
382
+ * @param {Function} datazoom 数据缩放的回调函数
383
+ *
384
+ * 判断具体是拖拽,还是缩放的逻辑
385
+ * 根据start~end的差值前后是否变动
386
+ * 因为拖拽时,他们的差值是恒定的,缩放时是会变动
387
+ */
388
+ let datazoomTimer: any = null;
389
+ const datazoomTime: number = 300;
390
+ let historyIsLoading = false;
391
+ let historyIsAllLoad = ref(false);
392
+ let futureIsLoading = false;
393
+ let futureIsAllLoad = ref(false);
394
+ const datazoom = async (params: any) => {
395
+ // 执行[方向键]时,返回的是startValue和endValue
396
+ // 执行[鼠标]时,返回的是batch
309
397
  /**
310
- * @description: echarts数据缩放的相关参数
311
- * @param {any} datazoomTimer 缩放回调函数的延时器
312
- * @param {Number} datazoomTime 缩放回调函数的延时器的时间
313
- * @param {Function} datazoom 数据缩放的回调函数
398
+ * @description: 历史数据动态加载渲染逻辑
399
+ * 前置条件:
400
+ * 1.允许开启历史动态加载
401
+ * 2.如果剩余K线根数不足100根
402
+ * 3.没有正在加载历史数据
403
+ * 4.历史数据并未已全部获取
314
404
  */
315
- let datazoomTimer: any = null
316
- const datazoomTime: number = 300
317
- const datazoom = () => {
318
- clearTimeout(datazoomTimer)
319
- datazoomTimer = setTimeout(() => {
320
- draw('line')
321
- clearTimeout(datazoomTimer)
322
- datazoomTimer = null
323
- }, datazoomTime)
405
+ // 获取左侧剩余K线根数
406
+ const historyLength = params.batch ? params.batch[0].start * config.value.totalBarCount : params.startValue;
407
+ if (
408
+ config.value.dynamicLoadConfig.historyVisible &&
409
+ historyLength <= 100 &&
410
+ !historyIsLoading &&
411
+ !historyIsAllLoad.value
412
+ ) {
413
+ historyIsLoading = true;
414
+ await config.value.dynamicLoadConfig.historyLoadCallBack(historyIsAllLoad);
415
+ await draw("history");
416
+ historyIsLoading = false;
324
417
  }
325
-
326
418
  /**
327
- * @description: echarts鼠标右击事件回调
419
+ * @description: 后续数据动态加载渲染逻辑
420
+ * 前置条件:
421
+ * 1.允许开启后续动态加载
422
+ * 2.右侧已为图表尽头
423
+ * 3.没有正在加载后续数据
424
+ * 4.后续数据并未已全部获取
425
+ * 5.仅仅在[鼠标拖拽,方向键左右]时
328
426
  */
329
- let echartsContextMenuTimer: any = null
330
- const echartsContextMenu = (params: ECElementEvent) => {
331
- echartsContextMenuTimer = setTimeout(() => {
332
- // 判定是否点击到的元素为额外画线
333
- if (params.componentType === 'graphic') {
334
- const { oncontextmenu } = params.info?.event ?? {}
335
- oncontextmenu instanceof Function && oncontextmenu(params, params.info, menuData)
336
- }
337
- clearTimeout(echartsContextMenuTimer)
338
- echartsContextMenuTimer = null
339
- })
427
+ // 获取右侧K线是否最后一根
428
+ const nowIsEnd = params.batch
429
+ ? params.batch[0].end === 100
430
+ : params.endValue >= props.klineData.length - config.value.preBarCount - 1;
431
+ if (config.value.dynamicLoadConfig.futureVisible && nowIsEnd && !futureIsLoading && !futureIsAllLoad.value) {
432
+ futureIsLoading = true;
433
+ await config.value.dynamicLoadConfig.futureLoadCallBack(futureIsAllLoad);
434
+ await draw("future");
435
+ futureIsLoading = false;
340
436
  }
437
+ clearTimeout(datazoomTimer);
438
+ datazoomTimer = setTimeout(() => {
439
+ draw("line");
440
+ clearTimeout(datazoomTimer);
441
+ datazoomTimer = null;
442
+ }, datazoomTime);
443
+ };
444
+ /**
445
+ * @description: echarts鼠标右击事件回调
446
+ */
447
+ let echartsContextMenuTimer: any = null;
448
+ const echartsContextMenu = (params: ECElementEvent) => {
449
+ echartsContextMenuTimer = setTimeout(() => {
450
+ // 判定是否点击到的元素为额外画线
451
+ if (params.componentType === "graphic") {
452
+ const { oncontextmenu } = params.info?.event ?? {};
453
+ oncontextmenu instanceof Function && oncontextmenu(params, params.info, menuData);
454
+ }
455
+ clearTimeout(echartsContextMenuTimer);
456
+ echartsContextMenuTimer = null;
457
+ });
458
+ };
341
459
 
342
- // 绑定事件
343
- const addEventListener = () => {
344
- echartsInstance.on('highlight', (data: any) => {
345
- highlight(data, echartsInstance)
346
- })
347
- echartsInstance.on('globalout', () => {
348
- highlight(null, echartsInstance)
349
- })
350
- echartsInstance.on('contextmenu', (params: ECElementEvent) => {
351
- echartsContextMenu(params)
352
- })
353
- echartsInstance.on('datazoom', () => {
354
- datazoom()
355
- })
356
- }
460
+ // 绑定事件
461
+ const addEventListener = () => {
462
+ echartsInstance.on("highlight", (data: any) => {
463
+ highlight(data, echartsInstance);
464
+ });
465
+ echartsInstance.on("globalout", () => {
466
+ highlight(null, echartsInstance);
467
+ });
468
+ echartsInstance.on("contextmenu", (params: ECElementEvent) => {
469
+ echartsContextMenu(params);
470
+ });
471
+ echartsInstance.on("datazoom", (params: any) => {
472
+ datazoom(params);
473
+ });
474
+ };
357
475
 
358
- // 解绑事件
359
- const removeEventListener = () => {
360
- echartsInstance.off('highlight')
361
- echartsInstance.off('globalout')
362
- echartsInstance.off('contextmenu')
363
- echartsInstance.off('datazoom')
364
- }
476
+ // 解绑事件
477
+ const removeEventListener = () => {
478
+ echartsInstance.off("highlight");
479
+ echartsInstance.off("globalout");
480
+ echartsInstance.off("contextmenu");
481
+ echartsInstance.off("datazoom");
482
+ };
365
483
 
366
- /**
367
- * @description: K线组件初始化
368
- */
369
- const init = async () => {
370
- let Initializing: boolean | null = true // 正在初始化
371
- // 1.初始化图表
372
- echartsInstance = echarts.init(echartsRef.value) as EChartsType
373
- // 2.绘制
374
- await draw()
375
- // 3.初始化右键菜单
376
- menuData.value = props.defaultMenuData as MenuDataType
377
- // 4.初始化数据激活索引
378
- activeIndex.value =
379
- ((echartsInstance.getOption().dataZoom as DataZoomComponentOption[])[0].endValue as number) ?? -1
380
- // 5.进行图表事件绑定
381
- addEventListener()
382
- // 6.进行DOM监控,图表重加载
383
- chartDomObserver = new ResizeObserver(() => {
384
- if (Initializing) {
385
- Initializing = null
386
- return
484
+ // 键盘事件
485
+ const keyDownEvent = (e: KeyboardEvent) => {
486
+ if (!echartsInstance) return;
487
+ // 只有选中或者按ctrl才激活按键
488
+ if (!(e.ctrlKey || props.isSelect)) return;
489
+ const option = echartsInstance.getOption() as EChartsOption;
490
+ let { startValue, endValue } = (option.dataZoom as DataZoomComponentOption[])[0] as {
491
+ startValue: number;
492
+ endValue: number;
493
+ };
494
+ const handleLeft = () => {
495
+ // 左按键
496
+ if (startValue === 0) {
497
+ return;
498
+ }
499
+ startValue = startValue - 1;
500
+ endValue = endValue - 1;
501
+ echartsInstance.dispatchAction({
502
+ type: "dataZoom",
503
+ startValue,
504
+ endValue,
505
+ });
506
+ };
507
+ const handleRight = () => {
508
+ // 右按键
509
+ // 如果开启了动态后续数据加载
510
+ if (config.value.dynamicLoadConfig.futureVisible && !futureIsLoading && !futureIsAllLoad.value) {
511
+ startValue = startValue + 1;
512
+ endValue = endValue + 1;
513
+ echartsInstance.dispatchAction({
514
+ type: "dataZoom",
515
+ startValue,
516
+ endValue,
517
+ });
518
+ } else {
519
+ if (endValue === option.xAxis[0].data.length - 1) {
520
+ return;
387
521
  }
388
- echartsInstance.resize()
389
- draw('line')
390
- })
391
- chartDomObserver.observe(echartsRef.value)
392
- }
522
+ startValue = startValue + 1;
523
+ endValue = endValue + 1;
524
+ echartsInstance.dispatchAction({
525
+ type: "dataZoom",
526
+ startValue,
527
+ endValue,
528
+ });
529
+ }
530
+ };
531
+ const handleUp = () => {
532
+ // 上按键-放大 最少保持5条数据
533
+ if (endValue - startValue < 5) {
534
+ return;
535
+ }
536
+ const diff = Math.floor((endValue - startValue) / 2) + 1;
537
+ startValue = startValue + diff;
538
+ if (endValue - startValue < 5) {
539
+ startValue = endValue - 4;
540
+ }
541
+ echartsInstance.dispatchAction({
542
+ type: "dataZoom",
543
+ startValue,
544
+ endValue,
545
+ });
546
+ };
547
+ const handleDown = () => {
548
+ // 下按键-缩小
549
+ const diff = Math.min(500, endValue - startValue);
550
+ startValue = startValue - diff - 1;
551
+ echartsInstance.dispatchAction({
552
+ type: "dataZoom",
553
+ startValue,
554
+ endValue,
555
+ });
556
+ };
557
+ const callBackMap = new Map([
558
+ ["ArrowLeft", handleLeft],
559
+ ["ArrowRight", handleRight],
560
+ ["ArrowUp", handleUp],
561
+ ["ArrowDown", handleDown],
562
+ ]);
563
+ const callBack = callBackMap.get(e.code);
564
+ callBack instanceof Function && callBack();
565
+ };
566
+
567
+ /**
568
+ * @description: K线组件初始化
569
+ */
570
+ const init = async () => {
571
+ let Initializing: boolean | null = true; // 正在初始化
572
+ // 1.初始化图表
573
+ echartsInstance = echarts.init(echartsRef.value) as EChartsType;
574
+ // 2.绘制
575
+ await draw();
576
+ // 3.初始化右键菜单
577
+ menuData.value = props.defaultMenuData as MenuDataType;
578
+ // 4.初始化数据激活索引
579
+ activeIndex.value = ((echartsInstance.getOption().dataZoom as DataZoomComponentOption[])[0].endValue as number) ?? -1;
580
+ // 5.进行图表事件绑定
581
+ addEventListener();
582
+ // 6.进行DOM监控,图表重加载
583
+ chartDomObserver = new ResizeObserver(() => {
584
+ if (Initializing) {
585
+ Initializing = null;
586
+ return;
587
+ }
588
+ echartsInstance.resize();
589
+ draw("line");
590
+ });
591
+ chartDomObserver.observe(echartsRef.value);
592
+ // 7.绑定键盘事件
593
+ window.addEventListener("keydown", keyDownEvent);
594
+ };
393
595
 
394
- onMounted(() => {
395
- init()
396
- })
596
+ onMounted(() => {
597
+ init();
598
+ });
397
599
 
398
- onUnmounted(() => {
399
- // 1.解绑
400
- removeEventListener()
401
- // 2.销毁实例
402
- echartsInstance.dispose()
403
- // 3.取消监听图表DOM
404
- chartDomObserver.disconnect()
405
- chartDomObserver = null
406
- })
600
+ onUnmounted(() => {
601
+ // 1.解绑
602
+ removeEventListener();
603
+ window.removeEventListener("keydown", keyDownEvent);
604
+ // 2.销毁实例
605
+ echartsInstance.dispose();
606
+ // 3.取消监听图表DOM
607
+ chartDomObserver.disconnect();
608
+ chartDomObserver = null;
609
+ });
407
610
  </script>
408
611
 
409
612
  <template>
@@ -417,15 +620,9 @@
417
620
  <Tips :data="indicatorTips" />
418
621
  </div>
419
622
  <!-- 图表 + 菜单 -->
420
- <Contextmenu
421
- class="st-kline-body"
422
- @closeContextMenuCallBack="closeContextMenuCallBack"
423
- >
623
+ <Contextmenu class="st-kline-body" @closeContextMenuCallBack="closeContextMenuCallBack">
424
624
  <!-- 图表 -->
425
- <div
426
- ref="echartsRef"
427
- :class="cursorPenVisible ? 'st-kline-chart cursorPen' : 'st-kline-chart'"
428
- />
625
+ <div ref="echartsRef" :class="cursorPenVisible ? 'st-kline-chart cursorPen' : 'st-kline-chart'" />
429
626
  <!-- 菜单 -->
430
627
  <template #popover>
431
628
  <el-menu class="menu">
@@ -445,41 +642,41 @@
445
642
  </template>
446
643
 
447
644
  <style lang="scss">
448
- // 需要写在外面,不然会被Vue唯一标识符顶掉导致样式不生效
449
- .cursorPen canvas {
450
- cursor: url(./images/pen.png) -30 30, auto !important;
451
- }
645
+ // 需要写在外面,不然会被Vue唯一标识符顶掉导致样式不生效
646
+ .cursorPen canvas {
647
+ cursor: url(./images/pen.png) -30 30, auto !important;
648
+ }
452
649
  </style>
453
650
  <style lang="scss" scoped>
454
- .st-kline {
651
+ .st-kline {
652
+ width: 100%;
653
+ height: 100%;
654
+ background: #000;
655
+ overflow-x: hidden;
656
+ position: relative; // 配合提供给菜单定位
657
+ // K线头部区域: Tips
658
+ &-header {
659
+ height: 34px;
660
+ position: absolute;
661
+ left: 66px;
662
+ }
663
+ &-body {
455
664
  width: 100%;
456
665
  height: 100%;
457
- background: #000;
458
- overflow-x: hidden;
459
- position: relative; // 配合提供给菜单定位
460
- // K线头部区域: Tips
461
- &-header {
462
- height: 34px;
463
- position: absolute;
464
- left: 66px;
465
- }
466
- &-body {
467
- width: 100%;
468
- height: 100%;
469
- }
470
- &-chart {
471
- width: 100%;
472
- height: 100%;
473
- }
474
- .menu {
475
- border-radius: 4px;
476
- overflow: hidden;
477
- background-color: white;
478
- border-right: 0;
479
- .menuItem {
480
- font-size: 16px;
481
- height: 32px;
482
- }
666
+ }
667
+ &-chart {
668
+ width: 100%;
669
+ height: 100%;
670
+ }
671
+ .menu {
672
+ border-radius: 4px;
673
+ overflow: hidden;
674
+ background-color: white;
675
+ border-right: 0;
676
+ .menuItem {
677
+ font-size: 16px;
678
+ height: 32px;
483
679
  }
484
680
  }
681
+ }
485
682
  </style>