st-comp 0.0.244 → 0.0.246

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.
Files changed (49) hide show
  1. package/es/ConfigProvider.cjs +1 -1
  2. package/es/ConfigProvider.js +1 -1
  3. package/es/CustomFunction.cjs +2 -2
  4. package/es/CustomFunction.js +262 -263
  5. package/es/FactorWarning.cjs +1 -1
  6. package/es/FactorWarning.js +1 -1
  7. package/es/Kline.cjs +1 -1
  8. package/es/Kline.js +1 -1
  9. package/es/KlineBasic.cjs +1 -1
  10. package/es/KlineBasic.js +574 -606
  11. package/es/KlineNew.cjs +1 -1
  12. package/es/KlineNew.js +1 -1
  13. package/es/KlinePlus.cjs +1 -1
  14. package/es/KlinePlus.js +1 -1
  15. package/es/MonacoEditor.cjs +1 -1
  16. package/es/MonacoEditor.js +3 -3
  17. package/es/Pie.cjs +1 -1
  18. package/es/Pie.js +1 -1
  19. package/es/User.cjs +1 -1
  20. package/es/User.js +1 -1
  21. package/es/VarSelectDialog.cjs +1 -1
  22. package/es/VarSelectDialog.js +2 -2
  23. package/es/VarietyAutoComplete.cjs +1 -1
  24. package/es/VarietyAutoComplete.js +1 -1
  25. package/es/VarietySearch.cjs +20 -20
  26. package/es/VarietySearch.js +2995 -3000
  27. package/es/{VarietySelect-031bf077.cjs → VarietySelect-2fd501da.cjs} +1 -1
  28. package/es/{VarietySelect-ae0c48b2.js → VarietySelect-5a9dd50b.js} +1 -1
  29. package/es/{index-1f939868.cjs → index-2375023e.cjs} +2 -2
  30. package/es/{index-edabe380.js → index-7ed0999e.js} +5487 -5574
  31. package/es/{index-2c456130.cjs → index-8901a38c.cjs} +40 -40
  32. package/es/{index-4b01552e.js → index-ac98a4d8.js} +3 -3
  33. package/es/{python-7ce6f0b1.js → python-a914569a.js} +3 -3
  34. package/es/{python-c8abd4f5.cjs → python-c67c8901.cjs} +1 -1
  35. package/es/style.css +1 -1
  36. package/lib/bundle.js +1 -1
  37. package/lib/bundle.umd.cjs +223 -223
  38. package/lib/{index-200db55b.js → index-2a325d42.js} +34655 -34779
  39. package/lib/{python-9540022d.js → python-eb65d93b.js} +1 -1
  40. package/lib/style.css +1 -1
  41. package/package.json +1 -1
  42. package/packages/CustomFunction/index.vue +2 -4
  43. package/packages/KlineBasic/index.vue +518 -497
  44. package/packages/VarietySearch/components/CompositeOrder/index.vue +2 -4
  45. package/packages/VarietySearch/components/FactorScreen/index.vue +5 -7
  46. package/packages/VarietySearch/index.vue +4 -6
  47. package/src/pages/KlineBasic/api.js +7 -4
  48. package/src/pages/KlineBasic/index.vue +346 -56
  49. package/src/pages/VarietySearch/index.vue +7 -2
@@ -1,108 +1,7 @@
1
- <template>
2
- <div
3
- class="klineBasic"
4
- @mousemove="isHover = true"
5
- @mouseout="isHover = false"
6
- >
7
- <div class="klineBasic-tips">
8
- <KlineTips
9
- :variety="variety"
10
- :data="chartData"
11
- :activeIndex="activeIndex"
12
- />
13
- </div>
14
- <div
15
- class="klineBasic-main"
16
- :style="{ height: config.showSubChart ? '70%' : '100%' }"
17
- >
18
- <Contextmenu @closeContextMenuCallBack="closeContextMenuCallBack">
19
- <div
20
- ref="klineBasicMainRef"
21
- style="height: 100%"
22
- ></div>
23
- <template #popover>
24
- <el-menu
25
- :style="{
26
- borderRadius: '4px',
27
- overflow: 'hidden',
28
- background: '#fff',
29
- borderRight: 0,
30
- }"
31
- >
32
- <el-menu-item
33
- v-for="item in menuData"
34
- style="height: 36px"
35
- :key="item.key"
36
- :index="item.key"
37
- @click="menuClick(item)"
38
- >
39
- {{ item.label }}
40
- </el-menu-item>
41
- </el-menu>
42
- </template>
43
- </Contextmenu>
44
- </div>
45
- <div
46
- class="klineBasic-sub"
47
- v-if="config.showSubChart"
48
- >
49
- <KlineSub
50
- ref="klineSubRef"
51
- v-model="subIndicator"
52
- :data="chartData"
53
- :cycle="cycle"
54
- :activeIndex="activeIndex"
55
- :subIndicatorList="indicatorStore?.subIndicatorList"
56
- />
57
- </div>
58
- <div
59
- class="klineBasic-empty"
60
- v-if="isEmpty"
61
- >
62
- <el-empty
63
- class="klineBasic-empty-content"
64
- description="暂无数据"
65
- />
66
- </div>
67
- <div
68
- class="klineBasic-error"
69
- v-if="isError"
70
- >
71
- <div class="klineBasic-error-content">加载失败,请刷新重试</div>
72
- <div style="text-align: center">
73
- <el-button @click="getMainData">刷新</el-button>
74
- </div>
75
- </div>
76
- </div>
77
- <!-- 画线预警-修改价格弹窗 -->
78
- <el-dialog
79
- v-model="warningLineChangeVisible"
80
- title="画线预警-修改价格"
81
- width="30%"
82
- align-center
83
- >
84
- <span style="margin-right: 10px">预警价格:</span>
85
- <el-input-number
86
- v-model="warningLineChangeValue"
87
- placeholder="输入预警价格"
88
- />
89
- <template #footer>
90
- <span class="dialog-footer">
91
- <el-button @click="warningLineChangeVisible = false">取消</el-button>
92
- <el-button
93
- type="primary"
94
- @click="changeWarningLine"
95
- >确定</el-button
96
- >
97
- </span>
98
- </template>
99
- </el-dialog>
100
- </template>
101
-
102
1
  <script setup>
103
- import { onMounted, onUnmounted, ref, watch, computed } from "vue";
104
- import * as echarts from "echarts";
105
2
  import dayjs from "dayjs";
3
+ import * as echarts from "echarts";
4
+ import { onMounted, onUnmounted, ref, watch, computed, nextTick } from "vue";
106
5
  import { initRequestByEnv, getKlineBasic, getKline, getWarningLine, addWarningLine, updateWarningLine, deleteWarningLine } from "./api";
107
6
  import { addResizeListener } from "st-func";
108
7
  import { getMainOptions, getWarningLineOptions } from "./utils";
@@ -114,6 +13,9 @@ const defaultMenuData = [{ label: "画线预警", key: "drawWarningLine" }];
114
13
 
115
14
  let resizeRo = null; // dom元素监听事件
116
15
  let mainChartIns = null; // 主图实例
16
+ let renderFrameId = null; // 渲染帧ID
17
+ let drawLineFrameId = null; // 绘线帧ID
18
+ let brushFrameId = null; // 区域框选帧ID
117
19
 
118
20
  let highlightTimer; // 高亮事件定时器
119
21
  let mainDataZoomTimer; // datazoom事件定时器
@@ -127,31 +29,31 @@ const props = defineProps({
127
29
  variety: {
128
30
  type: [String, Number],
129
31
  default: () => null,
130
- }, // 品种代码
32
+ },
131
33
  varietyName: {
132
34
  type: [String, Number],
133
35
  default: () => null,
134
- }, // 品种名称
36
+ },
135
37
  featureId: {
136
38
  type: [String, Number],
137
39
  default: () => null,
138
- }, // 合约id
40
+ },
139
41
  featureType: {
140
42
  type: [String, Number],
141
43
  default: () => null,
142
- }, // 合约类型
44
+ },
143
45
  cycle: {
144
46
  type: [String, Number],
145
47
  default: () => null,
146
- }, // 周期id
48
+ },
147
49
  mainIndicator: {
148
50
  type: String,
149
51
  default: () => "",
150
- }, // 主图指标名称
52
+ },
151
53
  indicatorStore: {
152
54
  type: Object,
153
55
  default: () => null,
154
- }, // 指标配置Store
56
+ },
155
57
  startTime: {
156
58
  type: String,
157
59
  default: () => null,
@@ -163,78 +65,54 @@ const props = defineProps({
163
65
  right: {
164
66
  type: [String, Number],
165
67
  default: 1,
166
- }, // 复权方式,前复权-1,不复权-0
68
+ },
167
69
  config: {
168
70
  type: Object,
169
71
  default: () => ({}),
170
- }, // 配置
72
+ },
171
73
  env: {
172
74
  type: Object,
173
75
  default: () => ({}),
174
- }, // 环境变量
76
+ },
175
77
  brushRange: {
176
78
  type: [Array, null],
177
79
  default: () => null,
178
- }, // 时段框选
80
+ },
179
81
  });
180
82
 
181
- const isHover = ref(false); // 是否选中
182
- const isEmpty = ref(false); // 是否无数据
183
- const isError = ref(false); // 是否异常
83
+ const isHover = ref(false);
84
+ const isEmpty = ref(false);
85
+ const isError = ref(false);
184
86
 
185
- const klineBasicMainRef = ref(null); // 主图Dom元素
186
- const klineSubRef = ref(null); // 副图组件元素
187
- const subIndicator = ref("VOL"); // 副图指标
188
- const activeIndex = ref(0); // 当前选中的k线
87
+ const klineBasicMainRef = ref(null);
88
+ const klineSubRef = ref(null);
89
+ const subIndicator = ref("VOL");
90
+ const activeIndex = ref(0);
189
91
 
190
- const chartData = ref({}); // 图表数据
92
+ const chartData = ref({});
93
+ const warningLineData = ref([]);
94
+ const warningItem = ref({});
95
+ const warningLineChangeVisible = ref(false);
96
+ const warningLineChangeValue = ref("");
191
97
 
192
- const warningLineData = ref([]); // 预警线数据
193
- const warningItem = ref({}); // 右键点击的预警线
194
- const warningLineChangeVisible = ref(false); // 修改预警线价格弹窗visible
195
- const warningLineChangeValue = ref(""); // 修改预警线价格弹窗value
196
-
197
- const menuData = ref([...defaultMenuData]); // 右键菜单
98
+ const menuData = ref([...defaultMenuData]);
198
99
 
199
100
  const config = computed(() => {
200
101
  return {
201
- defaultShowCounts: 500, // 默认展示条数
202
- addCounts: 2000, // 滚动加载条数
203
- maxShowCounts: 5000, // 单页最大展示条数
204
- loadCheckCounts: 500, // 滚动加载检测的条数
205
- showSubChart: true, // 是否展示副图
206
- gridTop: 48, // 主图顶部距离
207
- gridLeft: 80, // 主图左侧距离
208
- gridRight: 50, // 主图右侧距离
209
- showWarningLine: true, // 是否展示预警线
210
- getFactorData: true, // 是否获取因子数据
102
+ defaultShowCounts: 500,
103
+ addCounts: 2000,
104
+ maxShowCounts: 5000,
105
+ loadCheckCounts: 500,
106
+ showSubChart: true,
107
+ gridTop: 48,
108
+ gridLeft: 80,
109
+ gridRight: 50,
110
+ showWarningLine: true,
111
+ getFactorData: true,
211
112
  ...props.config,
212
113
  };
213
114
  });
214
115
 
215
- watch(
216
- () => [props.variety, props.cycle, props.mainIndicator, subIndicator.value, props.indicatorStore?.filterIndicator, props.indicatorStore?.customIndicator],
217
- () => {
218
- getMainData();
219
- },
220
- { deep: true }
221
- );
222
-
223
- onMounted(() => {
224
- initRequestByEnv(props.env);
225
- getMainData();
226
- window.addEventListener("keydown", handleKeyDownEvent);
227
- });
228
-
229
- onUnmounted(() => {
230
- mainChartIns?.off("datazoom");
231
- mainChartIns?.off("highlight");
232
- mainChartIns?.off("globalout");
233
- mainChartIns?.dispose();
234
- resizeRo?.dispose();
235
- window.removeEventListener("keydown", handleKeyDownEvent);
236
- });
237
-
238
116
  // 初始化图表
239
117
  const initChart = () => {
240
118
  if (mainChartIns) return;
@@ -246,136 +124,225 @@ const initChart = () => {
246
124
  resizeRo = addResizeListener(klineBasicMainRef.value);
247
125
  resizeRo.listen(() => {
248
126
  requestAnimationFrame(() => {
249
- mainChartIns.resize();
127
+ mainChartIns?.resize();
128
+ drawLine(); // resize后重绘预警线
250
129
  });
251
130
  });
252
131
  };
253
- // 键盘事件
254
- const handleKeyDownEvent = ({ code, ctrlKey }) => {
255
- if (!(ctrlKey || isHover.value)) return;
256
- const { xAxis, dataZoom } = mainChartIns.getOption();
257
- const { data: xAxisData } = xAxis?.[0] ?? { data: [] };
258
- let { startValue, endValue } = dataZoom?.[0] ?? {};
259
- if (!xAxisData?.length) return;
260
- // 键位cb
261
- switch (code) {
262
- // ↑ 放大
263
- case "ArrowUp": {
264
- if (endValue - startValue < 5) return;
265
- const diff = Math.floor((endValue - startValue) / 2) + 1;
266
- startValue = startValue + diff;
267
- if (endValue - startValue < 5) startValue = endValue - 4;
268
- break;
269
- }
270
- // ↓ 缩小
271
- case "ArrowDown": {
272
- const diff = Math.min(500, endValue - startValue);
273
- startValue = startValue - diff - 1;
274
- break;
275
- }
276
- // ← 左移
277
- case "ArrowLeft": {
278
- if (startValue > 0) {
279
- startValue -= 1;
280
- endValue -= 1;
281
- }
282
- if (activeIndex.value > 0) {
283
- activeIndex.value -= 1;
284
- }
285
- break;
286
- }
287
- // → 右移
288
- case "ArrowRight": {
289
- if (endValue < xAxisData.length - 1) {
290
- startValue += 1;
291
- endValue += 1;
292
- }
293
- if (isHover.value && activeIndex.value < xAxisData.length - 1) {
294
- activeIndex.value += 1;
295
- }
296
- break;
297
- }
132
+ // 隐藏loading
133
+ const hideLoading = () => {
134
+ if (mainChartIns && !mainChartIns.isDisposed()) {
135
+ mainChartIns.hideLoading();
298
136
  }
299
- // 1. 更新图表当屏位置
300
- mainChartIns.dispatchAction({
301
- type: "dataZoom",
302
- startValue,
303
- endValue,
304
- });
305
- // 2. 更新图表指示器位置
306
- mainChartIns.dispatchAction({
307
- type: "updateAxisPointer",
308
- seriesIndex: 0,
309
- dataIndex: isHover.value ? activeIndex.value : null,
310
- });
311
- // 3. 更新图表高亮索引
312
- mainChartIns.dispatchAction({
313
- type: "highlight",
314
- dataIndex: isHover.value ? activeIndex.value : endValue,
137
+ };
138
+
139
+ // 图表渲染: 全部
140
+ const scheduleRender = (keepDataZoom = false) => {
141
+ // 取消正在进行的渲染
142
+ if (renderFrameId) {
143
+ cancelAnimationFrame(renderFrameId);
144
+ renderFrameId = null;
145
+ }
146
+ // 第一帧:渲染主图
147
+ renderFrameId = requestAnimationFrame(() => {
148
+ drawMain(keepDataZoom);
149
+ // 第二帧:渲染副图
150
+ renderFrameId = requestAnimationFrame(() => {
151
+ drawSub();
152
+ // 第三帧:渲染预警线
153
+ renderFrameId = requestAnimationFrame(() => {
154
+ drawLine();
155
+ renderFrameId = null;
156
+ });
157
+ });
315
158
  });
316
159
  };
317
- // 图表事件
318
- const addEventListener = () => {
319
- // datazoom事件
320
- mainChartIns?.on("datazoom", (params) => {
321
- clearTimeout(mainDataZoomTimer);
322
- mainDataZoomTimer = setTimeout(() => {
323
- const { loadCheckCounts } = config.value;
324
- if (mainChartIns?.getOption()?.dataZoom?.[0]) {
325
- const { startValue } = mainChartIns?.getOption()?.dataZoom?.[0];
326
- // 加载历史数据
327
- if (startValue < loadCheckCounts && isLoadHistory === false && isloadAllHistory === false) {
328
- // 左侧数据小于检测条数,加载左侧数据
329
- isLoadHistory = true;
330
- getMoreData("history");
160
+ // 图表渲染: 主图
161
+ const drawMain = (keepDataZoom = false) => {
162
+ if (!mainChartIns || mainChartIns.isDisposed()) return;
163
+
164
+ const { time } = chartData.value;
165
+ const { defaultShowCounts } = config.value;
166
+
167
+ let startValue = time.length - 1 - defaultShowCounts;
168
+ let endValue = time.length - 1;
169
+
170
+ // 保持缩放位置
171
+ if (keepDataZoom && mainChartIns) {
172
+ const originOption = mainChartIns.getOption();
173
+ const originTime = originOption?.xAxis?.[0]?.data;
174
+ const originDataZoom = originOption?.dataZoom?.[0];
175
+
176
+ if (originTime?.length && originDataZoom) {
177
+ const originStartTime = originTime[originDataZoom.startValue];
178
+ const originEndTime = originTime[originDataZoom.endValue];
179
+ if (originStartTime && originEndTime) {
180
+ // startValue: 从前往后找第一个 >= 原始开始时间的索引
181
+ const foundStartIndex = time.findIndex((item) => new Date(item).getTime() >= new Date(originStartTime).getTime());
182
+ // endValue: 从后往前找最后一个 <= 原始结束时间的索引
183
+ const foundEndIndex = time.findLastIndex((item) => new Date(item).getTime() <= new Date(originEndTime).getTime());
184
+ // 使用找到的索引,如果没找到则使用默认值
185
+ startValue = foundStartIndex !== -1 ? foundStartIndex : 0;
186
+ endValue = foundEndIndex !== -1 ? foundEndIndex : time.length - 1;
187
+ // 确保 startValue 不大于 endValue
188
+ if (startValue > endValue) {
189
+ // 如果范围异常,以结束索引为准,向前取一定数量
190
+ startValue = Math.max(0, endValue - Math.min(100, time.length - 1));
331
191
  }
332
- drawLine();
333
192
  }
334
- clearTimeout(mainDataZoomTimer);
335
- }, 100);
336
- });
337
- // 高亮事件
338
- mainChartIns?.on("highlight", (data) => {
339
- let index = data.dataIndex || -1;
340
- if (data.batch) {
341
- index = typeof data?.batch[0]?.dataIndex === "number" ? data?.batch[0]?.dataIndex : -1;
342
193
  }
343
- clearTimeout(highlightTimer);
344
- highlightTimer = setTimeout(() => {
345
- activeIndex.value = index;
346
- clearTimeout(highlightTimer);
347
- }, 20);
348
- });
349
- // 移出图表事件
350
- mainChartIns?.on("globalout", () => {
351
- const timer = setTimeout(() => {
352
- clearTimeout(timer)
353
- const index = mainChartIns?.getOption()?.dataZoom?.[0]?.endValue;
354
- activeIndex.value = index;
355
- }, 30);
194
+ }
195
+ const options = getMainOptions(chartData.value, config.value, startValue, endValue, props.cycle);
196
+
197
+ requestAnimationFrame(() => {
198
+ mainChartIns?.setOption(options, true);
199
+ activeIndex.value = endValue;
200
+
201
+ // 下一帧绘制brush
202
+ requestAnimationFrame(() => {
203
+ drawBrush();
204
+ });
356
205
  });
357
- // 右键点击事件
358
- let echartsContextMenuTimer = null;
359
- mainChartIns?.on("contextmenu", (params) => {
360
- echartsContextMenuTimer = setTimeout(() => {
361
- if (params.componentType === "graphic") {
362
- warningItem.value = params.info;
363
- menuData.value = [
364
- { label: "删除画线", key: "deleteWarningLine" },
365
- { label: "修改画线", key: "changeWarningLine" },
366
- ];
206
+ };
207
+ // 图表渲染: 副图
208
+ const drawSub = () => {
209
+ if (!config.value.showSubChart || !klineSubRef.value) return;
210
+
211
+ const { time } = chartData.value;
212
+ const { gridLeft, gridRight, maxShowCounts, defaultShowCounts } = config.value;
213
+ let startValue = time.length - 1 - defaultShowCounts;
214
+ let endValue = time.length - 1;
215
+
216
+ // 从主图获取当前的dataZoom位置
217
+ if (mainChartIns && !mainChartIns.isDisposed()) {
218
+ const option = mainChartIns.getOption();
219
+ if (option?.dataZoom?.[0]) {
220
+ const { startValue: cacheStart, endValue: cacheEnd } = option.dataZoom[0];
221
+ if (cacheStart !== undefined && cacheEnd !== undefined) {
222
+ startValue = cacheStart;
223
+ endValue = cacheEnd;
367
224
  }
368
- clearTimeout(echartsContextMenuTimer);
369
- echartsContextMenuTimer = null;
370
- });
225
+ }
226
+ }
227
+
228
+ requestAnimationFrame(() => {
229
+ klineSubRef.value.draw({ startValue, endValue, maxValueSpan: maxShowCounts }, { gridLeft, gridRight });
371
230
  });
372
231
  };
232
+ // 图表渲染: 预警线
233
+ const drawLine = () => {
234
+ if (!mainChartIns) return;
373
235
 
374
- const getMainData = async () => {
236
+ // 取消正在进行的绘线
237
+ if (drawLineFrameId) {
238
+ cancelAnimationFrame(drawLineFrameId);
239
+ drawLineFrameId = null;
240
+ }
241
+
242
+ drawLineFrameId = requestAnimationFrame(() => {
243
+ const mainChartOption = mainChartIns?.getOption();
244
+ if (!mainChartOption) return;
245
+ const width = mainChartIns?.getWidth();
246
+ const height = mainChartIns?.getHeight();
247
+ if (!width || !height) return;
248
+
249
+ mainChartIns?.setOption(
250
+ {
251
+ graphic: getWarningLineOptions(mainChartIns, warningLineData.value, props, config.value, () => {
252
+ updateWarningLineAndDraw();
253
+ }),
254
+ },
255
+ {
256
+ notMerge: false,
257
+ replaceMerge: ["graphic"],
258
+ lazyUpdate: true,
259
+ },
260
+ );
261
+ drawLineFrameId = null;
262
+ });
263
+ };
264
+ // 图表渲染: 区域框选
265
+ const drawBrush = () => {
266
+ if (brushFrameId) {
267
+ cancelAnimationFrame(brushFrameId);
268
+ }
269
+
270
+ brushFrameId = requestAnimationFrame(() => {
271
+ const { time } = chartData.value;
272
+ if (!props.brushRange || !time?.length || !mainChartIns) return;
273
+
274
+ let startTime = dayjs(props.brushRange[0]).format("YYYY-MM-DD 00:00:00");
275
+ let endTime = dayjs(props.brushRange[1]).format("YYYY-MM-DD 23:59:59");
276
+ let brushStartTime = null;
277
+ let brushEndTime = null;
278
+
279
+ switch (String(props.cycle)) {
280
+ case "6": {
281
+ brushStartTime = time.find((item) => dayjs(item).format("YYYY-MM-DD") === dayjs(startTime).format("YYYY-MM-DD"));
282
+ brushEndTime = brushStartTime;
283
+ break;
284
+ }
285
+ case "7": {
286
+ brushStartTime = time.find((item) => dayjs(item).day(5).format("YYYY-MM-DD") === dayjs(startTime).day(5).format("YYYY-MM-DD"));
287
+ brushEndTime = time.findLast((item) => dayjs(item).day(5).format("YYYY-MM-DD") === dayjs(endTime).day(5).format("YYYY-MM-DD"));
288
+ break;
289
+ }
290
+ case "8": {
291
+ brushStartTime = time.find((item) => dayjs(item).endOf("month").format("YYYY-MM-DD") === dayjs(startTime).endOf("month").format("YYYY-MM-DD"));
292
+ brushEndTime = time.findLast((item) => dayjs(item).endOf("month").format("YYYY-MM-DD") === dayjs(endTime).endOf("month").format("YYYY-MM-DD"));
293
+ break;
294
+ }
295
+ default: {
296
+ brushStartTime = time.find((item) => {
297
+ const condition1 = dayjs(item).format("YYYY-MM-DD") === dayjs(startTime).format("YYYY-MM-DD");
298
+ const condition2 = new Date(item).getTime() >= new Date(startTime).getTime();
299
+ return condition1 && condition2;
300
+ });
301
+ brushEndTime = time.findLast((item) => {
302
+ const condition1 = dayjs(item).format("YYYY-MM-DD") === dayjs(endTime).format("YYYY-MM-DD");
303
+ const condition2 = new Date(item).getTime() <= new Date(endTime).getTime();
304
+ return condition1 && condition2;
305
+ });
306
+ }
307
+ }
308
+
309
+ if (brushStartTime && brushEndTime) {
310
+ mainChartIns.dispatchAction({
311
+ type: "brush",
312
+ areas: [
313
+ {
314
+ brushType: "lineX",
315
+ coordRange: [brushStartTime, brushEndTime],
316
+ xAxisIndex: 0,
317
+ },
318
+ ],
319
+ });
320
+ }
321
+ brushFrameId = null;
322
+ });
323
+ };
324
+
325
+ // 获取主数据
326
+ const getMainData = async () => {
375
327
  try {
376
328
  if (!props.variety || !props.cycle) return;
329
+
377
330
  const { variety, featureId, cycle, indicatorStore, mainIndicator, right, startTime, endTime } = props;
378
331
  const { defaultShowCounts, addCounts, showWarningLine, getFactorData } = config.value;
332
+
333
+ // 显示loading
334
+ initChart();
335
+
336
+ if (mainChartIns && !mainChartIns.isDisposed()) {
337
+ mainChartIns.showLoading({
338
+ text: "加载中...",
339
+ color: "#409eff",
340
+ textColor: "#fff",
341
+ maskColor: "rgba(0, 0, 0, 0.7)",
342
+ zlevel: 0,
343
+ });
344
+ }
345
+
379
346
  const params = {
380
347
  variety,
381
348
  featureId,
@@ -386,53 +353,58 @@ const getMainData = async () => {
386
353
  showWarningLine,
387
354
  getFactorData,
388
355
  };
356
+
389
357
  if (startTime && endTime) {
390
- // 开始时间+结束时间
391
358
  params.startTime = startTime;
392
359
  params.endTime = endTime;
393
360
  } else if (startTime) {
394
- // 开始时间
395
361
  params.startTime = startTime;
396
362
  params.limit = defaultShowCounts + addCounts;
397
363
  } else if (endTime) {
398
- // 结束时间
399
364
  params.endTime = endTime;
400
365
  params.limit = defaultShowCounts + addCounts;
401
366
  } else {
402
- // 未传入时间,使用最新时间作为结束时间请求数据
403
367
  params.endTime = dayjs().add(1, "hour").format("YYYY-MM-DD HH:mm:ss");
404
368
  params.limit = defaultShowCounts + addCounts;
405
369
  }
370
+
406
371
  const res = await getKlineBasic(params);
372
+
407
373
  if (!res?.body?.kline?.time?.length) {
408
374
  isEmpty.value = true;
409
375
  isError.value = false;
376
+ hideLoading();
410
377
  return;
411
- } else {
412
- isEmpty.value = false;
413
- isError.value = false;
414
378
  }
379
+
380
+ isEmpty.value = false;
381
+ isError.value = false;
415
382
  chartData.value = res?.body?.kline;
416
383
  warningLineData.value = res?.body?.warningLine || [];
417
- draw();
418
- drawLine();
384
+
385
+ // 使用RAF链式渲染
386
+ await nextTick();
387
+ scheduleRender(false);
388
+
419
389
  if (getFactorData) {
420
390
  emit("getFactorData", res?.body?.factor);
421
391
  }
392
+
393
+ hideLoading();
422
394
  } catch (err) {
395
+ hideLoading();
423
396
  isError.value = true;
424
397
  isEmpty.value = false;
425
398
  throw new Error(err);
426
399
  }
427
400
  };
428
-
429
- // 加载历史/未来数据
401
+ // 加载历史数据
430
402
  const getMoreData = async (type) => {
431
403
  const { variety, cycle, indicatorStore, mainIndicator, right } = props;
432
404
  const { addCounts } = config.value;
433
405
  const { time } = chartData.value;
406
+
434
407
  if (type === "history") {
435
- // 加载历史数据
436
408
  const res = await getKline({
437
409
  variety,
438
410
  cycle,
@@ -446,261 +418,180 @@ const getMoreData = async (type) => {
446
418
  chartData.value = {
447
419
  time: [...res.body.time, ...chartData.value.time.slice(1)],
448
420
  data: [...res.body.data, ...chartData.value.data.slice(1)],
449
- mainIndicator: chartData.value.mainIndicator.map((item, index) => {
450
- return {
451
- ...item,
452
- data: [...(res.body.mainIndicator[index]?.data ?? []), ...item.data.slice(1)],
453
- };
454
- }),
455
- subIndicator: chartData.value.subIndicator.map((item, index) => {
456
- return {
457
- ...item,
458
- data: [...(res.body.subIndicator[index]?.data ?? []), ...item.data.slice(1)],
459
- };
460
- }),
421
+ mainIndicator: chartData.value.mainIndicator.map((item, index) => ({
422
+ ...item,
423
+ data: [...(res.body.mainIndicator[index]?.data ?? []), ...item.data.slice(1)],
424
+ })),
425
+ subIndicator: chartData.value.subIndicator.map((item, index) => ({
426
+ ...item,
427
+ data: [...(res.body.subIndicator[index]?.data ?? []), ...item.data.slice(1)],
428
+ })),
461
429
  };
462
- // 绘制
463
- draw(true);
464
- // 判断是否加载完全部数据
430
+ console.log("触发了加载更多数据");
431
+ // 使用RAF链式渲染,保持当前缩放位置
432
+ await nextTick();
433
+ scheduleRender(true);
465
434
  if (res.body.data.length < addCounts) {
466
435
  isloadAllHistory = true;
436
+ console.log("历史数据已经全部加载完毕");
467
437
  }
468
438
  isLoadHistory = false;
469
- } else {
470
- // 加载未来数据
471
439
  }
472
440
  };
441
+ // 键盘事件
442
+ const handleKeyDownEvent = ({ code, ctrlKey }) => {
443
+ if (!(ctrlKey || isHover.value)) return;
444
+ const { xAxis, dataZoom } = mainChartIns.getOption();
445
+ const { data: xAxisData } = xAxis?.[0] ?? { data: [] };
446
+ let { startValue, endValue } = dataZoom?.[0] ?? {};
447
+ if (!xAxisData?.length) return;
473
448
 
474
- const draw = (keepDataZoom = false) => {
475
- initChart();
476
- const { time } = chartData.value;
477
- const { gridRight, gridLeft, defaultShowCounts, maxShowCounts, showSubChart } = config.value;
478
- let startValue = time.length - 1 - defaultShowCounts;
479
- let endValue = time.length - 1;
480
- // 保持缩放位置
481
- if (keepDataZoom) {
482
- const originOption = mainChartIns.getOption();
483
- const originTime = originOption?.xAxis?.[0]?.data;
484
- const originDataZoom = originOption?.dataZoom?.[0];
485
- const originStartTime = originTime[originDataZoom?.startValue];
486
- const originEndTime = originTime[originDataZoom?.endValue];
487
- startValue = time.findIndex((item) => item === originStartTime);
488
- endValue = time.findIndex((item) => item === originEndTime);
489
- }
490
- const options = getMainOptions(chartData.value, config.value, startValue, endValue, props.cycle);
491
- mainChartIns.setOption(options, true);
492
- activeIndex.value = endValue;
493
- // 如果传入了刷选时间段
494
- if (props.brushRange) {
495
- let brushStartTime = null;
496
- let brushEndTime = null;
497
- switch (props.cycle) {
498
- // 日
499
- case "6": {
500
- brushStartTime = time.find((item) => {
501
- return new Date(item).getTime() >= new Date(props.brushRange[0]).getTime() || dayjs(item).format("YYYY-MM-DD") === dayjs(props.brushRange[0]).format("YYYY-MM-DD");
502
- });
503
- brushEndTime = time.findLast((item) => {
504
- return new Date(item).getTime() <= new Date(props.brushRange[1]).getTime() || dayjs(item).format("YYYY-MM-DD") === dayjs(props.brushRange[1]).format("YYYY-MM-DD");
505
- });
506
- break;
449
+ switch (code) {
450
+ case "ArrowUp": {
451
+ if (endValue - startValue < 5) return;
452
+ const diff = Math.floor((endValue - startValue) / 2) + 1;
453
+ startValue = startValue + diff;
454
+ if (endValue - startValue < 5) startValue = endValue - 4;
455
+ break;
456
+ }
457
+ case "ArrowDown": {
458
+ const diff = Math.min(500, endValue - startValue);
459
+ startValue = startValue - diff - 1;
460
+ break;
461
+ }
462
+ case "ArrowLeft": {
463
+ if (startValue > 0) {
464
+ startValue -= 1;
465
+ endValue -= 1;
507
466
  }
508
- //
509
- case "7": {
510
- brushStartTime = time.find((item) => {
511
- return dayjs(item).day(5).format("YYYY-MM-DD") === dayjs(props.brushRange[0]).day(5).format("YYYY-MM-DD");
512
- });
513
- brushEndTime = time.findLast((item) => {
514
- return dayjs(item).day(5).format("YYYY-MM-DD") === dayjs(props.brushRange[1]).day(5).format("YYYY-MM-DD");
515
- });
516
- break;
467
+ if (activeIndex.value > 0) {
468
+ activeIndex.value -= 1;
517
469
  }
518
- // 月
519
- case "8": {
520
- brushStartTime = time.find((item) => {
521
- return dayjs(item).endOf("month").format("YYYY-MM-DD") === dayjs(props.brushRange[0]).endOf("month").format("YYYY-MM-DD");
522
- });
523
- brushEndTime = time.findLast((item) => {
524
- return dayjs(item).endOf("month").format("YYYY-MM-DD") === dayjs(props.brushRange[1]).endOf("month").format("YYYY-MM-DD");
525
- });
526
- break;
470
+ break;
471
+ }
472
+ case "ArrowRight": {
473
+ if (endValue < xAxisData.length - 1) {
474
+ startValue += 1;
475
+ endValue += 1;
527
476
  }
528
- default: {
529
- brushStartTime = time.find((item) => {
530
- return new Date(item).getTime() >= new Date(props.brushRange[0]).getTime();
531
- });
532
- brushEndTime = time.findLast((item) => {
533
- return new Date(item).getTime() <= new Date(props.brushRange[1]).getTime();
534
- });
477
+ if (isHover.value && activeIndex.value < xAxisData.length - 1) {
478
+ activeIndex.value += 1;
535
479
  }
480
+ break;
536
481
  }
537
- console.log(brushStartTime, brushEndTime);
482
+ }
483
+
484
+ requestAnimationFrame(() => {
485
+ mainChartIns.dispatchAction({ type: "dataZoom", startValue, endValue });
538
486
  mainChartIns.dispatchAction({
539
- type: "brush",
540
- areas: [
541
- {
542
- brushType: "lineX",
543
- coordRange: [brushStartTime, brushEndTime],
544
- xAxisIndex: 0,
545
- },
546
- ],
487
+ type: "updateAxisPointer",
488
+ seriesIndex: 0,
489
+ dataIndex: isHover.value ? activeIndex.value : null,
547
490
  });
548
- }
549
- if (showSubChart) {
550
- klineSubRef.value.draw({ startValue, endValue, maxValueSpan: maxShowCounts }, { gridLeft, gridRight });
551
- }
552
- drawBrush();
491
+ mainChartIns.dispatchAction({
492
+ type: "highlight",
493
+ dataIndex: isHover.value ? activeIndex.value : endValue,
494
+ });
495
+ });
553
496
  };
497
+ // 图表事件
498
+ const addEventListener = () => {
499
+ mainChartIns?.on("datazoom", (params) => {
500
+ clearTimeout(mainDataZoomTimer);
501
+ mainDataZoomTimer = setTimeout(() => {
502
+ const { loadCheckCounts } = config.value;
503
+ if (mainChartIns?.getOption()?.dataZoom?.[0]) {
504
+ const { startValue } = mainChartIns?.getOption()?.dataZoom?.[0];
505
+ if (startValue < loadCheckCounts && !isLoadHistory && !isloadAllHistory) {
506
+ isLoadHistory = true;
507
+ getMoreData("history");
508
+ }
509
+ drawLine(); // 使用RAF优化的绘线
510
+ }
511
+ clearTimeout(mainDataZoomTimer);
512
+ }, 100);
513
+ });
554
514
 
555
- // 绘制线
556
- const drawLine = () => {
557
- if (!mainChartIns) return;
558
- const mainChartOption = mainChartIns?.getOption();
559
- if (!mainChartOption) return;
560
- mainChartIns?.setOption(
561
- {
562
- ...mainChartOption,
563
- graphic: [
564
- ...getWarningLineOptions(mainChartIns, warningLineData.value, props, config.value, () => {
565
- updateWarningLineAndDraw();
566
- }),
567
- ],
568
- },
569
- true
570
- );
571
- drawBrush();
572
- };
515
+ mainChartIns?.on("highlight", (data) => {
516
+ let index = data.dataIndex || -1;
517
+ if (data.batch) {
518
+ index = typeof data?.batch[0]?.dataIndex === "number" ? data?.batch[0]?.dataIndex : -1;
519
+ }
520
+ clearTimeout(highlightTimer);
521
+ highlightTimer = setTimeout(() => {
522
+ activeIndex.value = index;
523
+ clearTimeout(highlightTimer);
524
+ }, 20);
525
+ });
573
526
 
574
- // 区域框选
575
- const drawBrush = () => {
576
- const { time } = chartData.value;
577
- // 如果传入了刷选时间段
578
- if (props.brushRange && time?.length) {
579
- // 传入时段的始末时间
580
- let startTime = dayjs(props.brushRange[0]).format("YYYY-MM-DD 00:00:00");
581
- let endTime = dayjs(props.brushRange[1]).format("YYYY-MM-DD 23:59:59");
582
- // 匹配到对应K线的时间段始末时间
583
- let brushStartTime = null;
584
- let brushEndTime = null;
585
- switch (props.cycle) {
586
- //
587
- case "6": {
588
- brushStartTime = time.find((item) => dayjs(item).format("YYYY-MM-DD") === dayjs(startTime).format("YYYY-MM-DD"));
589
- brushEndTime = brushStartTime;
590
- break;
591
- }
592
- // 周
593
- case "7": {
594
- brushStartTime = time.find((item) => {
595
- return dayjs(item).day(5).format("YYYY-MM-DD") === dayjs(startTime).day(5).format("YYYY-MM-DD");
596
- });
597
- brushEndTime = time.findLast((item) => {
598
- return dayjs(item).day(5).format("YYYY-MM-DD") === dayjs(endTime).day(5).format("YYYY-MM-DD");
599
- });
600
- break;
601
- }
602
- // 月
603
- case "8": {
604
- brushStartTime = time.find((item) => {
605
- return dayjs(item).endOf("month").format("YYYY-MM-DD") === dayjs(startTime).endOf("month").format("YYYY-MM-DD");
606
- });
607
- brushEndTime = time.findLast((item) => {
608
- return dayjs(item).endOf("month").format("YYYY-MM-DD") === dayjs(endTime).endOf("month").format("YYYY-MM-DD");
609
- });
610
- break;
611
- }
612
- default: {
613
- brushStartTime = time.find((item) => {
614
- const condition1 = dayjs(item).format("YYYY-MM-DD") === dayjs(startTime).format("YYYY-MM-DD");
615
- const condition2 = new Date(item).getTime() >= new Date(startTime).getTime();
616
- return condition1 && condition2;
617
- });
618
- brushEndTime = time.findLast((item) => {
619
- const condition1 = dayjs(item).format("YYYY-MM-DD") === dayjs(endTime).format("YYYY-MM-DD");
620
- const condition2 = new Date(item).getTime() <= new Date(endTime).getTime();
621
- return condition1 && condition2;
622
- });
527
+ mainChartIns?.on("globalout", () => {
528
+ const timer = setTimeout(() => {
529
+ clearTimeout(timer);
530
+ const index = mainChartIns?.getOption()?.dataZoom?.[0]?.endValue;
531
+ activeIndex.value = index;
532
+ }, 30);
533
+ });
534
+
535
+ let echartsContextMenuTimer = null;
536
+ mainChartIns?.on("contextmenu", (params) => {
537
+ echartsContextMenuTimer = setTimeout(() => {
538
+ if (params.componentType === "graphic") {
539
+ warningItem.value = params.info;
540
+ menuData.value = [
541
+ { label: "删除画线", key: "deleteWarningLine" },
542
+ { label: "修改画线", key: "changeWarningLine" },
543
+ ];
623
544
  }
624
- }
625
- mainChartIns.dispatchAction({
626
- type: "brush",
627
- areas: [
628
- {
629
- brushType: "lineX",
630
- coordRange: [brushStartTime, brushEndTime],
631
- xAxisIndex: 0,
632
- },
633
- ],
545
+ clearTimeout(echartsContextMenuTimer);
546
+ echartsContextMenuTimer = null;
634
547
  });
635
- }
548
+ });
636
549
  };
637
550
 
638
- // 更新预警线并绘制
551
+ // 预警线相关辅助函数
639
552
  const updateWarningLineAndDraw = async () => {
640
553
  const res = await getWarningLine({ featureId: props.featureId });
641
554
  warningLineData.value = res?.body || [];
642
555
  drawLine();
643
556
  emit("change", "warningLine", warningLineData.value);
644
557
  };
645
-
646
558
  const menuClick = async (item) => {
647
559
  const { variety, varietyName, featureId, featureType } = props;
560
+
648
561
  if (item.key === "drawWarningLine") {
649
- // 画线预警
650
- // 拿到当前主图配置项
651
562
  const mainChartOption = mainChartIns?.getOption();
652
563
  const oldTooltip = mainChartOption.tooltip[0];
564
+
653
565
  mainChartIns?.setOption({
654
566
  ...mainChartOption,
655
- // 指示器样式
656
567
  tooltip: {
657
568
  ...oldTooltip,
658
- // 坐标轴指示器
659
569
  axisPointer: {
660
- // 保留原有部分配置
661
570
  ...oldTooltip.axisPointer,
662
- // X轴指示线的宽度
663
- lineStyle: {
664
- width: 0,
665
- },
666
- // Y轴指示线的宽度
667
- crossStyle: {
668
- width: 2,
669
- },
670
- // 文本标签
571
+ lineStyle: { width: 0 },
572
+ crossStyle: { width: 2 },
671
573
  label: {
672
- // 保留原有部分配置
673
574
  ...oldTooltip.axisPointer.label,
674
- // 颜色改为透明
675
575
  backgroundColor: "transparent",
676
- // 仅显示Y轴值
677
- formatter: (data) => "",
576
+ formatter: () => "",
678
577
  },
679
578
  },
680
579
  },
681
580
  });
682
- // 编写点击事件:获取到数据发送给后端
581
+
683
582
  const handleClick = async (el) => {
684
- // (1).在触发点击事件后,第一时间将图表绑定的点击事件清除
685
583
  mainChartIns?.getZr().off("mousedown", handleClick);
686
- // (2).根据点击位置获取到Y轴具体数据
687
584
  const yAxisValue = mainChartIns?.convertFromPixel({ yAxisIndex: 0 }, el.offsetY);
688
- // (3).格式化画线预警价格
689
585
  const newWarnPrice = Math.round(yAxisValue * 1000) / 1000;
690
- // (4).将指示器恢复成原始样式
586
+
691
587
  mainChartIns?.setOption({
692
588
  ...mainChartOption,
693
- // 指示器样式
694
589
  tooltip: {
695
590
  ...oldTooltip,
696
591
  axisPointer: {
697
592
  ...oldTooltip.axisPointer,
698
- lineStyle: {
699
- width: 1,
700
- },
701
- crossStyle: {
702
- width: 1,
703
- },
593
+ lineStyle: { width: 1 },
594
+ crossStyle: { width: 1 },
704
595
  label: {
705
596
  ...oldTooltip.axisPointer.label,
706
597
  backgroundColor: null,
@@ -708,21 +599,20 @@ const menuClick = async (item) => {
708
599
  },
709
600
  },
710
601
  });
711
- // (6).发送新增预警线请求
602
+
712
603
  await addWarningLine({
713
- featureCode: variety, //品种代码
714
- featureName: varietyName, //品种名称
715
- featureId, //品种id
716
- featureType, //品种类型
604
+ featureCode: variety,
605
+ featureName: varietyName,
606
+ featureId,
607
+ featureType,
717
608
  warnPrice: newWarnPrice,
718
609
  });
719
610
  ElMessage.success("画线预警成功!");
720
611
  updateWarningLineAndDraw();
721
612
  };
722
- // 4.绑定echarts点击事件
613
+
723
614
  mainChartIns?.getZr().on("mousedown", handleClick);
724
615
  } else if (item.key === "deleteWarningLine") {
725
- // 删除预警线
726
616
  await deleteWarningLine({ id: warningItem.value.id });
727
617
  ElMessage.success("画线预警删除成功");
728
618
  updateWarningLineAndDraw();
@@ -731,25 +621,55 @@ const menuClick = async (item) => {
731
621
  warningLineChangeValue.value = warningItem.value.warnPrice;
732
622
  }
733
623
  };
734
-
735
624
  const changeWarningLine = async () => {
736
625
  const { variety, varietyName, featureId, featureType } = props;
737
626
  await updateWarningLine({
738
627
  id: warningItem.value.id,
739
- featureCode: variety, //品种代码
740
- featureName: varietyName, //品种名称
741
- featureId, //品种id
742
- featureType, //品种类型
628
+ featureCode: variety,
629
+ featureName: varietyName,
630
+ featureId,
631
+ featureType,
743
632
  warnPrice: warningLineChangeValue.value,
744
633
  });
745
634
  ElMessage.success("画线预警修改成功");
746
635
  updateWarningLineAndDraw();
747
636
  };
748
-
749
637
  const closeContextMenuCallBack = () => {
750
638
  menuData.value = [...defaultMenuData];
751
639
  };
752
640
 
641
+ onMounted(() => {
642
+ initRequestByEnv(props.env);
643
+ getMainData();
644
+ window.addEventListener("keydown", handleKeyDownEvent);
645
+ });
646
+ watch(
647
+ () => [props.variety, props.cycle, props.mainIndicator, subIndicator.value, props.indicatorStore?.filterIndicator, props.indicatorStore?.customIndicator],
648
+ () => {
649
+ // 重置加载状态
650
+ isLoadHistory = false;
651
+ isloadAllHistory = false;
652
+ getMainData();
653
+ },
654
+ { deep: true },
655
+ );
656
+ onUnmounted(() => {
657
+ // 取消所有动画帧
658
+ if (renderFrameId) {
659
+ cancelAnimationFrame(renderFrameId);
660
+ renderFrameId = null;
661
+ }
662
+ if (drawLineFrameId) {
663
+ cancelAnimationFrame(drawLineFrameId);
664
+ drawLineFrameId = null;
665
+ }
666
+ mainChartIns?.off("datazoom");
667
+ mainChartIns?.off("highlight");
668
+ mainChartIns?.off("globalout");
669
+ mainChartIns?.dispose();
670
+ resizeRo?.dispose();
671
+ window.removeEventListener("keydown", handleKeyDownEvent);
672
+ });
753
673
  defineExpose({
754
674
  draw: (type, data) => {
755
675
  if (type === "warningLine") {
@@ -760,6 +680,107 @@ defineExpose({
760
680
  });
761
681
  </script>
762
682
 
683
+ <template>
684
+ <div
685
+ class="klineBasic"
686
+ @mousemove="isHover = true"
687
+ @mouseout="isHover = false"
688
+ >
689
+ <div class="klineBasic-tips">
690
+ <KlineTips
691
+ :variety="variety"
692
+ :data="chartData"
693
+ :activeIndex="activeIndex"
694
+ />
695
+ </div>
696
+ <div
697
+ class="klineBasic-main"
698
+ :style="{ height: config.showSubChart ? '70%' : '100%' }"
699
+ >
700
+ <Contextmenu @closeContextMenuCallBack="closeContextMenuCallBack">
701
+ <div
702
+ ref="klineBasicMainRef"
703
+ style="height: 100%"
704
+ ></div>
705
+ <template #popover>
706
+ <el-menu
707
+ :style="{
708
+ borderRadius: '4px',
709
+ overflow: 'hidden',
710
+ background: '#fff',
711
+ borderRight: 0,
712
+ }"
713
+ >
714
+ <el-menu-item
715
+ v-for="item in menuData"
716
+ style="height: 36px"
717
+ :key="item.key"
718
+ :index="item.key"
719
+ @click="menuClick(item)"
720
+ >
721
+ {{ item.label }}
722
+ </el-menu-item>
723
+ </el-menu>
724
+ </template>
725
+ </Contextmenu>
726
+ </div>
727
+ <div
728
+ class="klineBasic-sub"
729
+ v-if="config.showSubChart"
730
+ >
731
+ <KlineSub
732
+ ref="klineSubRef"
733
+ v-model="subIndicator"
734
+ :data="chartData"
735
+ :cycle="cycle"
736
+ :activeIndex="activeIndex"
737
+ :subIndicatorList="indicatorStore?.subIndicatorList"
738
+ />
739
+ </div>
740
+ <div
741
+ class="klineBasic-empty"
742
+ v-if="isEmpty"
743
+ >
744
+ <el-empty
745
+ class="klineBasic-empty-content"
746
+ description="暂无数据"
747
+ />
748
+ </div>
749
+ <div
750
+ class="klineBasic-error"
751
+ v-if="isError"
752
+ >
753
+ <div class="klineBasic-error-content">加载失败,请刷新重试</div>
754
+ <div style="text-align: center">
755
+ <el-button @click="getMainData">刷新</el-button>
756
+ </div>
757
+ </div>
758
+ </div>
759
+ <!-- 画线预警-修改价格弹窗 -->
760
+ <el-dialog
761
+ v-model="warningLineChangeVisible"
762
+ title="画线预警-修改价格"
763
+ width="30%"
764
+ align-center
765
+ >
766
+ <span style="margin-right: 10px">预警价格:</span>
767
+ <el-input-number
768
+ v-model="warningLineChangeValue"
769
+ placeholder="输入预警价格"
770
+ />
771
+ <template #footer>
772
+ <span class="dialog-footer">
773
+ <el-button @click="warningLineChangeVisible = false">取消</el-button>
774
+ <el-button
775
+ type="primary"
776
+ @click="changeWarningLine"
777
+ >确定</el-button
778
+ >
779
+ </span>
780
+ </template>
781
+ </el-dialog>
782
+ </template>
783
+
763
784
  <style lang="scss" scoped>
764
785
  .klineBasic {
765
786
  width: 100%;