st-comp 0.0.62 → 0.0.64

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.
@@ -0,0 +1,317 @@
1
+ <template>
2
+ <div
3
+ class="kline"
4
+ @mousemove="isSelect = true"
5
+ @mouseout="isSelect = false"
6
+ >
7
+ <div class="kline-mainTips">
8
+ <st-kline-tips-new :data="klineData" :activeIndex="activeIndex"/>
9
+ </div>
10
+ <div class="kline-mainChart" ref="mainChartRef"></div>
11
+ <div class="kline-sub">
12
+ <st-kline-sub-new
13
+ ref="klineSubRef"
14
+ v-model="subIndicator"
15
+ :data="klineData"
16
+ :activeIndex="activeIndex"
17
+ :subIndicatorList="subIndicatorList"
18
+ />
19
+ </div>
20
+ <st-kline-utils-new ref="klineUtilsRef" />
21
+ </div>
22
+ </template>
23
+
24
+ <script setup>
25
+ import { ref, onMounted, onUnmounted, watch, computed } from "vue"
26
+ import * as echarts from 'echarts'
27
+ import { getKlineDataApi, formatConfig } from './utils'
28
+ import dayjs from "dayjs"
29
+ import { stMath } from 'st-func'
30
+ const { round } = stMath
31
+
32
+ const props = defineProps({
33
+ code: { type: String, required: true }, // 品种代码
34
+ freqId: { type: String, required: true }, // 周期
35
+ freqDict: { type: Object, required: true }, // 周期字典
36
+ mainIndicator: { type: String, required: true }, // 主图指标
37
+ mainIndicatorList: { type: Array, required: true }, // 主图指标列表
38
+ subIndicatorList: { type: Array, required: true }, // 副图指标列表
39
+ config: { type: Object, default: () => ({}) }, // 配置
40
+ })
41
+
42
+ let mainChart
43
+ let mainDataZoomTimer = null // 主图数据缩放定时器
44
+ let isLoadHistory = false // 是否正在加载历史数据
45
+ let isloadAllHistory = false // 是否加载完全部历史数据
46
+ let resizeRo // dom元素监听事件
47
+ let highlightTimer = null // 高亮处理事件的定时器
48
+ const activeIndex = ref(0) // 显示的index
49
+
50
+ const mainChartRef = ref() // 主图
51
+ const klineSubRef = ref() // 副图
52
+ const klineUtilsRef = ref() // k线辅助函数实例
53
+ const isSelect = ref(false) // 是否选中,选中可通过键盘按键操作
54
+ const klineData = ref({}) // 绘图数据
55
+ const subIndicator = ref('VOL') // 副图指标
56
+
57
+ const config = computed(() => formatConfig(props.config))
58
+
59
+ watch(
60
+ () => [props.code, props.freqId, props.mainIndicator, subIndicator],
61
+ async () => {
62
+ initChart()
63
+ },
64
+ { deep: true }
65
+ )
66
+
67
+ onMounted(async () => {
68
+ // 初始化指标计算方法
69
+ mainChart = echarts.init(mainChartRef.value)
70
+ klineSubRef.value.connect(mainChart)
71
+ addEventListener()
72
+ initChart()
73
+ // 绑定resize事件
74
+ let isFirst = true
75
+ resizeRo = new ResizeObserver(() => {
76
+ if (isFirst) {
77
+ isFirst = null
78
+ return
79
+ }
80
+ mainChart.resize()
81
+ })
82
+ resizeRo.observe(mainChartRef.value)
83
+ window.addEventListener('keydown', keyDownEvent)
84
+ })
85
+
86
+ onUnmounted(() => {
87
+ window.removeEventListener('keydown', keyDownEvent)
88
+ mainChart.off('datazoom')
89
+ mainChart.off('highlight')
90
+ mainChart.off('globalout')
91
+ mainChart.dispose()
92
+ resizeRo.disconnect()
93
+ resizeRo = null
94
+ })
95
+
96
+ // 项目初始化
97
+ const initChart = async () => {
98
+ const { addCount } = config.value
99
+ const res = await getKlineDataApi({
100
+ variety: props.code,
101
+ contractType: 0, // 合约类型 0-主连 1-加权
102
+ right: 1,
103
+ cycle: 5,
104
+ mainIndicator: props.mainIndicator,
105
+ subIndicator: subIndicator.value,
106
+ endTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
107
+ limit: addCount,
108
+ })
109
+ klineData.value = res
110
+ draw('main', { startValue: res.time.length - 1 - config.value.defaultShowCount, endValue: res.time.length - 1 })
111
+ draw('sub', { startValue: res.time.length - 1 - config.value.defaultShowCount, endValue: res.time.length - 1 })
112
+ activeIndex.value = mainChart.getOption().dataZoom[0].endValue
113
+ }
114
+ // 绑定事件
115
+ const addEventListener = () => {
116
+ mainChart.on('datazoom', (params) => {
117
+ clearTimeout(mainDataZoomTimer)
118
+ mainDataZoomTimer = setTimeout(() => {
119
+ const { loadCheckCount, addCount } = config.value
120
+ const { startValue } = mainChart.getOption().dataZoom[0]
121
+ // 加载数据
122
+ if (startValue < loadCheckCount && isLoadHistory === false && isloadAllHistory === false) {
123
+ // 左侧数据小于1000条,加载左侧数据
124
+ isLoadHistory = true
125
+ getMoreData()
126
+ }
127
+ clearTimeout(mainDataZoomTimer)
128
+ }, 100)
129
+ })
130
+ // 高亮事件
131
+ mainChart.on('highlight', (data) => {
132
+ let index = data.dataIndex || -1
133
+ if (data.batch ) {
134
+ index = typeof data?.batch[0]?.dataIndex === 'number' ? data?.batch[0]?.dataIndex : -1
135
+ }
136
+ clearTimeout(highlightTimer)
137
+ highlightTimer = setTimeout(() => {
138
+ activeIndex.value = index
139
+ clearTimeout(highlightTimer)
140
+ }, 20)
141
+ })
142
+ // 移出图表事件
143
+ mainChart.on('globalout', () => {
144
+ const index = mainChart.getOption().dataZoom[0].endValue
145
+ activeIndex.value = index
146
+ })
147
+ }
148
+
149
+ // 键盘事件
150
+ const keyDownEvent = e => {
151
+ // 只有选中或者按ctrl才激活按键
152
+ if (!(e.ctrlKey || isSelect.value)) return
153
+ const option = mainChart.getOption()
154
+ let { startValue, endValue } = option.dataZoom[0]
155
+ mainChart.dispatchAction({
156
+ type: 'dataZoom',
157
+ ...klineUtilsRef.value.handleKeyDown(e, { startValue, endValue, maxIndex: option.xAxis[0].data.length - 1})
158
+ })
159
+ }
160
+
161
+ // 获取更多数据
162
+ const getMoreData = async(params) => {
163
+ const { addCount, maxLoadCount } = config.value
164
+ const res = await getKlineDataApi({
165
+ variety: props.code,
166
+ contractType: 0, // 合约类型 0-主连 1-加权
167
+ right: 1,
168
+ cycle: 5,
169
+ mainIndicator: props.mainIndicator,
170
+ subIndicator: subIndicator.value,
171
+ endTime: klineData.value.time[0],
172
+ limit: addCount,
173
+ })
174
+ klineData.value = klineUtilsRef.value.mergeData(res, klineData.value)
175
+ const { startValue, endValue } = mainChart.getOption().dataZoom[0]
176
+ let dataZoomAddCount = addCount
177
+ if(klineData.value.time.length > maxLoadCount){
178
+ // 超出设置的最长加载数据条数
179
+ isloadAllHistory = true
180
+ } else if (res.time.length < addCount) {
181
+ // 表示历史数据已加载完
182
+ isloadAllHistory = true
183
+ dataZoomAddCount = res.length - 1
184
+ }
185
+ isLoadHistory = false
186
+ draw('main', { startValue: startValue + dataZoomAddCount, endValue: endValue + dataZoomAddCount })
187
+ draw('sub', { startValue: startValue + dataZoomAddCount, endValue: endValue + dataZoomAddCount })
188
+ }
189
+
190
+ // 绘制函数
191
+ const draw = (type, params = {}) => {
192
+ const { startValue, endValue, maxValueSpan } = params
193
+ const callBackMap = new Map([
194
+ [ "main", () => {
195
+ const { data, time } = klineData.value
196
+ const indicatorSeries = klineData.value.mainIndicator.map((item) => {
197
+ return {
198
+ name: item.key,
199
+ type: 'line',
200
+ silent: true,
201
+ symbol: 'none',
202
+ data: item.data,
203
+ lineStyle: {
204
+ width: item.width || 1,
205
+ },
206
+ itemStyle: {
207
+ color: item.color,
208
+ }
209
+ }
210
+ })
211
+ mainChart.setOption({
212
+ animation: false,
213
+ grid: {
214
+ left: `${config.value.gridLeft}px`,
215
+ top: '20px',
216
+ right: `${config.value.gridRight}px`,
217
+ bottom: '6px',
218
+ },
219
+ dataZoom: [
220
+ {
221
+ type: 'inside',
222
+ startValue,
223
+ endValue,
224
+ maxValueSpan: config.value.maxShowCount,
225
+ }
226
+ ],
227
+ tooltip: {
228
+ trigger: 'axis',
229
+ appendToBody: true,
230
+ confine: true,
231
+ axisPointer: {
232
+ type: 'cross',
233
+ label: {
234
+ rich: {},
235
+ formatter: data => {
236
+ const { axisDimension, value } = data
237
+ if(axisDimension === 'x') {
238
+ return ''
239
+ } else {
240
+ return String(round(value))
241
+ }
242
+ }
243
+ },
244
+ },
245
+ formatter: () => '',
246
+ },
247
+ xAxis: {
248
+ show: false,
249
+ type: 'category',
250
+ data: time,
251
+ splitLine: {
252
+ show: false,
253
+ },
254
+ },
255
+ yAxis: {
256
+ type: 'value',
257
+ axisLine: {
258
+ show: true,
259
+ },
260
+ splitLine: {
261
+ show: true,
262
+ lineStyle: {
263
+ type: "dotted",
264
+ color: "#333",
265
+ },
266
+ },
267
+ min: (value) => round(value.min),
268
+ max: (value) => round(value.max),
269
+ },
270
+ series: [
271
+ {
272
+ type: 'candlestick',
273
+ data,
274
+ itemStyle: {
275
+ color: "transparent",
276
+ color0: "#00FFFF",
277
+ borderColor: "#FF0000",
278
+ borderColor0: "#00FFFF",
279
+ borderWidth: 1,
280
+ },
281
+ },
282
+ ...indicatorSeries,
283
+ ]
284
+ }, true)
285
+ }],
286
+ [ "sub", () => {
287
+ klineSubRef.value.draw({ startValue, endValue, maxValueSpan }, config.value)
288
+ }],
289
+ ]);
290
+ const callBack = callBackMap.get(type);
291
+ if (callBack instanceof Function) {
292
+ callBack();
293
+ }
294
+ }
295
+ </script>
296
+
297
+ <style lang="scss" scoped>
298
+ .kline {
299
+ width: 100%;
300
+ height: 100%;
301
+ background: #000;
302
+ &-mainTips {
303
+ height: 26px;
304
+ }
305
+ &-mainChart {
306
+ width: 100%;
307
+ height: calc(65% - 26px);
308
+ }
309
+ &-subTips {
310
+ height: 16px;
311
+ }
312
+ &-sub {
313
+ width: 100%;
314
+ height: calc(35%);
315
+ }
316
+ }
317
+ </style>
@@ -0,0 +1,31 @@
1
+ import axios from 'axios'
2
+
3
+ const defaultConfig = {
4
+ defaultShowCount: 500, // 默认展示天数
5
+ addCount: 2000, // 首屏加载条数,滚动加载条数
6
+ maxLoadCount: 50000, // 最多加载多少条数据
7
+ maxShowCount: 5000, // 一屏幕最多展示多少条数据
8
+ loadCheckCount: 500, // 加载数据检查条数
9
+ gridLeft: 60, // echarts绘图左侧grid距离
10
+ gridRight: 20, // echarts绘图右侧grid距离
11
+ }
12
+
13
+ // 获取配置
14
+ export const formatConfig = (config) => {
15
+ return {
16
+ ...defaultConfig,
17
+ ...config,
18
+ }
19
+ }
20
+
21
+ export const getKlineDataApi = async(params) => {
22
+ const res = await axios({
23
+ method: 'post',
24
+ headers: {
25
+ token: '524164e2c6b6ae23bd25c690a7c6bf96',
26
+ },
27
+ url: 'http://127.0.0.1:7001/middleLayer/kline/getKline',
28
+ data: params,
29
+ })
30
+ return res.data.body
31
+ }