@363045841yyt/klinechart 0.7.12 → 0.8.0
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.
- package/dist/components/ChartSettingsDialog.vue.d.ts.map +1 -1
- package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts +29 -0
- package/dist/components/Dropdown.vue.d.ts.map +1 -0
- package/dist/components/IndicatorSelector.vue.d.ts +5 -1
- package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
- package/dist/components/KLineChart.vue.d.ts +4 -0
- package/dist/components/KLineChart.vue.d.ts.map +1 -1
- package/dist/components/KLineLevelDropdown.vue.d.ts +12 -0
- package/dist/components/KLineLevelDropdown.vue.d.ts.map +1 -0
- package/dist/components/TopToolbar.vue.d.ts +17 -0
- package/dist/components/TopToolbar.vue.d.ts.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.css +1 -1
- package/dist/index.js +1017 -888
- package/package.json +9 -9
- package/src/components/ChartSettingsDialog.vue +15 -39
- package/src/components/DrawingStyleToolbar.vue +27 -36
- package/src/components/Dropdown.vue +291 -0
- package/src/components/IndicatorSelector.vue +21 -357
- package/src/components/KLineChart.vue +59 -14
- package/src/components/KLineLevelDropdown.vue +45 -0
- package/src/components/LeftToolbar.vue +4 -4
- package/src/components/TopToolbar.vue +232 -0
|
@@ -1,87 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="indicator-selector">
|
|
3
|
-
<div class="indicator-scroll-container">
|
|
4
|
-
<div class="indicator-list">
|
|
5
|
-
<!-- 已激活的指标 -->
|
|
6
|
-
<template v-for="indicator in activeIndicatorsList" :key="indicator.id">
|
|
7
|
-
<div
|
|
8
|
-
v-if="indicator.id === firstActiveSubIndicatorId"
|
|
9
|
-
class="indicator-divider"
|
|
10
|
-
aria-hidden="true"
|
|
11
|
-
></div>
|
|
12
|
-
|
|
13
|
-
<div
|
|
14
|
-
class="indicator-item"
|
|
15
|
-
:class="{
|
|
16
|
-
draggable: isSubIndicatorId(indicator.id),
|
|
17
|
-
'drag-over': dragOverIndicatorId === indicator.id,
|
|
18
|
-
'is-dragging': draggingIndicatorId === indicator.id,
|
|
19
|
-
}"
|
|
20
|
-
:draggable="isSubIndicatorId(indicator.id)"
|
|
21
|
-
@dragstart="onDragStart($event, indicator.id)"
|
|
22
|
-
@dragover.prevent="onDragOver($event, indicator.id)"
|
|
23
|
-
@drop.prevent="onDrop($event, indicator.id)"
|
|
24
|
-
@dragend="onDragEnd"
|
|
25
|
-
>
|
|
26
|
-
<div
|
|
27
|
-
class="indicator-btn-wrapper"
|
|
28
|
-
@mouseenter="hoveredIndicator = indicator.id"
|
|
29
|
-
@mouseleave="hoveredIndicator = null"
|
|
30
|
-
>
|
|
31
|
-
<button
|
|
32
|
-
class="indicator-btn"
|
|
33
|
-
:class="{ active: true, hovering: hoveredIndicator === indicator.id }"
|
|
34
|
-
>
|
|
35
|
-
<span class="btn-content">
|
|
36
|
-
{{ indicator.label }}
|
|
37
|
-
<span v-if="indicator.params?.length" class="param-hint">
|
|
38
|
-
({{ getParamDisplay(indicator) }})
|
|
39
|
-
</span>
|
|
40
|
-
</span>
|
|
41
|
-
<!-- 悬浮操作层 -->
|
|
42
|
-
<Transition name="fade">
|
|
43
|
-
<div v-if="hoveredIndicator === indicator.id" class="hover-overlay">
|
|
44
|
-
<button
|
|
45
|
-
v-if="indicator.params?.length"
|
|
46
|
-
class="action-btn settings-btn"
|
|
47
|
-
@click.stop="showParams(indicator.id)"
|
|
48
|
-
title="编辑参数"
|
|
49
|
-
>
|
|
50
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
|
|
51
|
-
<path
|
|
52
|
-
d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"
|
|
53
|
-
/>
|
|
54
|
-
</svg>
|
|
55
|
-
</button>
|
|
56
|
-
<span v-if="indicator.params?.length" class="divider"></span>
|
|
57
|
-
<button
|
|
58
|
-
class="action-btn remove-btn"
|
|
59
|
-
@click.stop="removeIndicator(indicator.id)"
|
|
60
|
-
title="移除指标"
|
|
61
|
-
>
|
|
62
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
|
|
63
|
-
<path
|
|
64
|
-
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
|
|
65
|
-
/>
|
|
66
|
-
</svg>
|
|
67
|
-
</button>
|
|
68
|
-
</div>
|
|
69
|
-
</Transition>
|
|
70
|
-
</button>
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
</template>
|
|
74
|
-
|
|
75
|
-
<!-- 添加按钮 -->
|
|
76
|
-
<div class="indicator-item">
|
|
77
|
-
<button ref="addBtnRef" class="add-btn" @click.stop="controller.toggleMenu()" title="添加指标">
|
|
78
|
-
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
79
|
-
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
|
80
|
-
</svg>
|
|
81
|
-
</button>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
3
|
|
|
86
4
|
<!-- 添加指标弹窗 -->
|
|
87
5
|
<Teleport :to="teleportTarget">
|
|
@@ -127,9 +45,8 @@
|
|
|
127
45
|
</div>
|
|
128
46
|
</div>
|
|
129
47
|
|
|
130
|
-
<!--
|
|
131
|
-
<div class="modal-
|
|
132
|
-
<!-- 搜索框 -->
|
|
48
|
+
<!-- 搜索区域 -->
|
|
49
|
+
<div class="modal-search-area">
|
|
133
50
|
<div class="search-box">
|
|
134
51
|
<svg
|
|
135
52
|
class="search-icon"
|
|
@@ -149,6 +66,10 @@
|
|
|
149
66
|
placeholder="搜索指标名称..."
|
|
150
67
|
/>
|
|
151
68
|
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- 弹窗主体 -->
|
|
72
|
+
<div class="modal-body">
|
|
152
73
|
<!-- 主图指标区域 -->
|
|
153
74
|
<div v-if="filteredMain.length > 0" class="indicator-section">
|
|
154
75
|
<div class="section-header">
|
|
@@ -297,7 +218,6 @@ import {
|
|
|
297
218
|
type IndicatorDefinition,
|
|
298
219
|
allIndicators,
|
|
299
220
|
findIndicator,
|
|
300
|
-
isSubIndicatorId,
|
|
301
221
|
type Indicator,
|
|
302
222
|
} from '@363045841yyt/klinechart-core/controllers'
|
|
303
223
|
|
|
@@ -350,35 +270,13 @@ const hasSearchResults = computed(
|
|
|
350
270
|
const catalogLen = controller.catalog.peek().length
|
|
351
271
|
|
|
352
272
|
// ── 本地 UI 状态(非 Controller 管理的纯 UI 状态) ──
|
|
353
|
-
const addBtnRef = ref<HTMLButtonElement | null>(null)
|
|
354
273
|
const paramsVisible = ref(false)
|
|
355
274
|
const currentIndicatorId = ref<string | null>(null)
|
|
356
|
-
const hoveredIndicator = ref<string | null>(null)
|
|
357
|
-
const dragOverIndicatorId = ref<string | null>(null)
|
|
358
|
-
const draggingIndicatorId = ref<string | null>(null)
|
|
359
275
|
const isCompactView = ref(false)
|
|
360
276
|
|
|
361
277
|
// Teleport target for fullscreen modal visibility
|
|
362
278
|
const teleportTarget = useFullscreenTeleportTarget()
|
|
363
279
|
|
|
364
|
-
const activeIndicatorsList = computed(() => {
|
|
365
|
-
if (!props.activeIndicators?.length) return []
|
|
366
|
-
return props.activeIndicators
|
|
367
|
-
.map((id) => findIndicator(id))
|
|
368
|
-
.filter((i): i is Indicator => i !== undefined)
|
|
369
|
-
.sort((a, b) => {
|
|
370
|
-
if (a.pane === b.pane) return 0
|
|
371
|
-
return a.pane === 'main' ? -1 : 1
|
|
372
|
-
})
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
const firstActiveSubIndicatorId = computed(() => {
|
|
376
|
-
const hasMain = activeIndicatorsList.value.some((indicator) => indicator.pane === 'main')
|
|
377
|
-
if (!hasMain) return null
|
|
378
|
-
const firstSub = activeIndicatorsList.value.find((indicator) => indicator.pane === 'sub')
|
|
379
|
-
return firstSub?.id ?? null
|
|
380
|
-
})
|
|
381
|
-
|
|
382
280
|
const currentIndicator = computed(() => {
|
|
383
281
|
if (!currentIndicatorId.value) return null
|
|
384
282
|
return findIndicator(currentIndicatorId.value)
|
|
@@ -396,13 +294,6 @@ function addIndicator(indicatorId: string) {
|
|
|
396
294
|
const indicator = findIndicator(indicatorId)
|
|
397
295
|
if (!indicator) return
|
|
398
296
|
|
|
399
|
-
if (indicator.pane === 'main') {
|
|
400
|
-
const allItems = allIndicators
|
|
401
|
-
allItems
|
|
402
|
-
.filter((i) => i.id !== indicatorId && isActive(i.id) && i.pane === 'main')
|
|
403
|
-
.forEach((i) => emit('toggle', i.id, false))
|
|
404
|
-
}
|
|
405
|
-
|
|
406
297
|
emit('toggle', indicatorId, true)
|
|
407
298
|
}
|
|
408
299
|
|
|
@@ -436,12 +327,6 @@ function getParamValues(indicatorId: string): Record<string, number> {
|
|
|
436
327
|
return result
|
|
437
328
|
}
|
|
438
329
|
|
|
439
|
-
function getParamDisplay(indicator: Indicator): string {
|
|
440
|
-
const values = getParamValues(indicator.id)
|
|
441
|
-
if (!indicator.params) return ''
|
|
442
|
-
return indicator.params.map((p) => values[p.key] ?? '').join(',')
|
|
443
|
-
}
|
|
444
|
-
|
|
445
330
|
function onParamsConfirm(values: Record<string, number>) {
|
|
446
331
|
if (currentIndicatorId.value) {
|
|
447
332
|
emit('updateParams', currentIndicatorId.value, values)
|
|
@@ -449,71 +334,6 @@ function onParamsConfirm(values: Record<string, number>) {
|
|
|
449
334
|
paramsVisible.value = false
|
|
450
335
|
}
|
|
451
336
|
|
|
452
|
-
function onDragStart(event: DragEvent, indicatorId: string) {
|
|
453
|
-
if (!isSubIndicatorId(indicatorId)) {
|
|
454
|
-
event.preventDefault()
|
|
455
|
-
return
|
|
456
|
-
}
|
|
457
|
-
draggingIndicatorId.value = indicatorId
|
|
458
|
-
dragOverIndicatorId.value = null
|
|
459
|
-
event.dataTransfer?.setData('text/plain', indicatorId)
|
|
460
|
-
if (event.dataTransfer) {
|
|
461
|
-
event.dataTransfer.effectAllowed = 'move'
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function onDragOver(event: DragEvent, indicatorId: string) {
|
|
466
|
-
if (
|
|
467
|
-
!draggingIndicatorId.value ||
|
|
468
|
-
!isSubIndicatorId(indicatorId) ||
|
|
469
|
-
draggingIndicatorId.value === indicatorId
|
|
470
|
-
)
|
|
471
|
-
return
|
|
472
|
-
dragOverIndicatorId.value = indicatorId
|
|
473
|
-
if (event.dataTransfer) {
|
|
474
|
-
event.dataTransfer.dropEffect = 'move'
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function onDrop(event: DragEvent, targetIndicatorId: string) {
|
|
479
|
-
const sourceIndicatorId =
|
|
480
|
-
draggingIndicatorId.value || event.dataTransfer?.getData('text/plain') || ''
|
|
481
|
-
if (!sourceIndicatorId || sourceIndicatorId === targetIndicatorId) {
|
|
482
|
-
onDragEnd()
|
|
483
|
-
return
|
|
484
|
-
}
|
|
485
|
-
if (!isSubIndicatorId(sourceIndicatorId) || !isSubIndicatorId(targetIndicatorId)) {
|
|
486
|
-
onDragEnd()
|
|
487
|
-
return
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
const sourceIndex = activeIndicatorsList.value.findIndex((i) => i.id === sourceIndicatorId)
|
|
491
|
-
const targetIndex = activeIndicatorsList.value.findIndex((i) => i.id === targetIndicatorId)
|
|
492
|
-
if (sourceIndex < 0 || targetIndex < 0) {
|
|
493
|
-
onDragEnd()
|
|
494
|
-
return
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const next = [...activeIndicatorsList.value.map((i) => i.id)]
|
|
498
|
-
const [moved] = next.splice(sourceIndex, 1)
|
|
499
|
-
if (!moved) {
|
|
500
|
-
onDragEnd()
|
|
501
|
-
return
|
|
502
|
-
}
|
|
503
|
-
next.splice(targetIndex, 0, moved)
|
|
504
|
-
|
|
505
|
-
emit(
|
|
506
|
-
'reorderSubIndicators',
|
|
507
|
-
next.filter((id) => isSubIndicatorId(id)),
|
|
508
|
-
)
|
|
509
|
-
onDragEnd()
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
function onDragEnd() {
|
|
513
|
-
dragOverIndicatorId.value = null
|
|
514
|
-
draggingIndicatorId.value = null
|
|
515
|
-
}
|
|
516
|
-
|
|
517
337
|
function handleKeydown(event: KeyboardEvent) {
|
|
518
338
|
if (event.key === 'Escape' && controller.menuOpen.peek()) {
|
|
519
339
|
controller.closeMenu()
|
|
@@ -527,184 +347,19 @@ onMounted(() => {
|
|
|
527
347
|
onUnmounted(() => {
|
|
528
348
|
document.removeEventListener('keydown', handleKeydown)
|
|
529
349
|
})
|
|
350
|
+
|
|
351
|
+
defineExpose({
|
|
352
|
+
openMenu: () => controller.openMenu(),
|
|
353
|
+
closeMenu: () => controller.closeMenu(),
|
|
354
|
+
toggleMenu: () => controller.toggleMenu(),
|
|
355
|
+
})
|
|
530
356
|
</script>
|
|
531
357
|
|
|
532
358
|
<style scoped>
|
|
533
359
|
.indicator-selector {
|
|
534
|
-
margin: 20px;
|
|
535
|
-
width: 80%;
|
|
536
|
-
position: relative;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
.indicator-scroll-container {
|
|
540
|
-
width: 100%;
|
|
541
|
-
overflow-x: auto;
|
|
542
|
-
overflow-y: hidden;
|
|
543
|
-
scrollbar-width: none;
|
|
544
|
-
-webkit-overflow-scrolling: touch;
|
|
545
|
-
text-align: center;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
.indicator-scroll-container::-webkit-scrollbar {
|
|
549
360
|
display: none;
|
|
550
361
|
}
|
|
551
362
|
|
|
552
|
-
.indicator-list {
|
|
553
|
-
display: inline-flex;
|
|
554
|
-
gap: 8px;
|
|
555
|
-
padding: 2px;
|
|
556
|
-
margin: 0 auto;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
.indicator-divider {
|
|
560
|
-
width: 1px;
|
|
561
|
-
height: 20px;
|
|
562
|
-
align-self: center;
|
|
563
|
-
background: var(--klc-color-axis-line);
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
.indicator-item {
|
|
567
|
-
display: flex;
|
|
568
|
-
align-items: center;
|
|
569
|
-
gap: 4px;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
.indicator-item.draggable,
|
|
573
|
-
.indicator-item.draggable .indicator-btn,
|
|
574
|
-
.indicator-item.draggable:hover,
|
|
575
|
-
.indicator-item.draggable:hover .indicator-btn {
|
|
576
|
-
cursor: move;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
.indicator-item.is-dragging {
|
|
580
|
-
opacity: 0.6;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
.indicator-item.drag-over .indicator-btn {
|
|
584
|
-
box-shadow: 0 0 0 2px color-mix(in srgb, var(--klc-color-foreground) 12%, transparent);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
.indicator-btn-wrapper {
|
|
588
|
-
position: relative;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
.indicator-btn {
|
|
592
|
-
position: relative;
|
|
593
|
-
flex-shrink: 0;
|
|
594
|
-
padding: 6px 16px;
|
|
595
|
-
border: none;
|
|
596
|
-
border-radius: 16px;
|
|
597
|
-
background: var(--klc-color-tag-bg-white);
|
|
598
|
-
color: var(--klc-color-axis-text);
|
|
599
|
-
font-size: 13px;
|
|
600
|
-
font-weight: 500;
|
|
601
|
-
cursor: pointer;
|
|
602
|
-
transition: all 0.3s ease;
|
|
603
|
-
white-space: nowrap;
|
|
604
|
-
display: flex;
|
|
605
|
-
align-items: center;
|
|
606
|
-
justify-content: center;
|
|
607
|
-
gap: 4px;
|
|
608
|
-
overflow: hidden;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
.indicator-btn:hover:not(.hovering) {
|
|
612
|
-
background: var(--klc-color-tag-bg-hover);
|
|
613
|
-
color: var(--klc-color-foreground);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
.indicator-btn.active {
|
|
617
|
-
background: var(--klc-color-tag-bg-hover);
|
|
618
|
-
color: var(--klc-color-foreground);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
.indicator-btn.active:hover:not(.hovering) {
|
|
622
|
-
background: var(--klc-color-tag-bg-hover);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
.btn-content {
|
|
626
|
-
position: relative;
|
|
627
|
-
z-index: 1;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
.param-hint {
|
|
631
|
-
font-size: 11px;
|
|
632
|
-
opacity: 0.85;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/* 悬浮操作层 */
|
|
636
|
-
.hover-overlay {
|
|
637
|
-
position: absolute;
|
|
638
|
-
top: 0;
|
|
639
|
-
left: 0;
|
|
640
|
-
right: 0;
|
|
641
|
-
bottom: 0;
|
|
642
|
-
display: flex;
|
|
643
|
-
align-items: center;
|
|
644
|
-
justify-content: center;
|
|
645
|
-
gap: 4px;
|
|
646
|
-
background: color-mix(in srgb, var(--klc-color-background) 85%, transparent);
|
|
647
|
-
backdrop-filter: blur(4px);
|
|
648
|
-
border-radius: 16px;
|
|
649
|
-
z-index: 2;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
.action-btn {
|
|
653
|
-
width: 24px;
|
|
654
|
-
height: 24px;
|
|
655
|
-
padding: 0;
|
|
656
|
-
border: none;
|
|
657
|
-
border-radius: 50%;
|
|
658
|
-
background: transparent;
|
|
659
|
-
color: var(--klc-color-axis-text);
|
|
660
|
-
cursor: pointer;
|
|
661
|
-
display: flex;
|
|
662
|
-
align-items: center;
|
|
663
|
-
justify-content: center;
|
|
664
|
-
transition: all 0.2s;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
.action-btn:hover {
|
|
668
|
-
background: var(--klc-color-tag-bg-hover);
|
|
669
|
-
color: var(--klc-color-foreground);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
.settings-btn:hover {
|
|
673
|
-
color: var(--klc-color-foreground);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
.remove-btn:hover {
|
|
677
|
-
color: #ff4d4f;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
.divider {
|
|
681
|
-
width: 1px;
|
|
682
|
-
height: 14px;
|
|
683
|
-
background: var(--klc-color-border-button);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/* 添加按钮 */
|
|
687
|
-
.add-btn {
|
|
688
|
-
flex-shrink: 0;
|
|
689
|
-
width: 32px;
|
|
690
|
-
height: 32px;
|
|
691
|
-
padding: 0;
|
|
692
|
-
border: none;
|
|
693
|
-
border-radius: 50%;
|
|
694
|
-
background: transparent;
|
|
695
|
-
color: var(--klc-color-axis-text);
|
|
696
|
-
cursor: pointer;
|
|
697
|
-
display: flex;
|
|
698
|
-
align-items: center;
|
|
699
|
-
justify-content: center;
|
|
700
|
-
transition: all 0.3s ease;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
.add-btn:hover {
|
|
704
|
-
color: var(--klc-color-foreground);
|
|
705
|
-
background: var(--klc-color-tag-bg-hover);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
363
|
/* ─────────────────────────────────────────────────────────────────
|
|
709
364
|
弹窗样式 - 与其他弹窗保持一致
|
|
710
365
|
───────────────────────────────────────────────────────────────── */
|
|
@@ -823,6 +478,14 @@ onUnmounted(() => {
|
|
|
823
478
|
color: var(--klc-color-foreground);
|
|
824
479
|
}
|
|
825
480
|
|
|
481
|
+
/* 搜索区域 */
|
|
482
|
+
.modal-search-area {
|
|
483
|
+
padding: 16px 20px;
|
|
484
|
+
background: var(--klc-color-tag-bg-white);
|
|
485
|
+
border-bottom: 1px solid var(--klc-color-border-chart);
|
|
486
|
+
flex-shrink: 0;
|
|
487
|
+
}
|
|
488
|
+
|
|
826
489
|
/* 弹窗主体 */
|
|
827
490
|
.modal-body {
|
|
828
491
|
padding: 20px;
|
|
@@ -851,6 +514,7 @@ onUnmounted(() => {
|
|
|
851
514
|
padding: 10px 14px;
|
|
852
515
|
border: 1px solid var(--klc-color-border-button);
|
|
853
516
|
border-radius: 8px;
|
|
517
|
+
background: var(--klc-color-background);
|
|
854
518
|
transition: all 0.2s ease;
|
|
855
519
|
}
|
|
856
520
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="chartWrapperRef" class="chart-wrapper" :data-theme="chartTheme" :style="themeCssVars">
|
|
3
|
+
<TopToolbar
|
|
4
|
+
:symbol="semanticConfig.data.symbol"
|
|
5
|
+
:k-line-level="kLineLevel"
|
|
6
|
+
@add-overlay-symbol="$emit('addOverlaySymbol')"
|
|
7
|
+
@k-line-level-change="onKLineLevelChange"
|
|
8
|
+
@toggle-indicator="onToggleIndicator"
|
|
9
|
+
/>
|
|
3
10
|
<div
|
|
4
11
|
class="chart-stage"
|
|
5
12
|
:class="{
|
|
@@ -99,6 +106,7 @@
|
|
|
99
106
|
</div>
|
|
100
107
|
</div>
|
|
101
108
|
<IndicatorSelector
|
|
109
|
+
ref="indicatorSelectorRef"
|
|
102
110
|
:active-indicators="activeIndicators"
|
|
103
111
|
:indicator-params="indicatorParams"
|
|
104
112
|
@toggle="handleIndicatorToggle"
|
|
@@ -133,14 +141,17 @@ import {
|
|
|
133
141
|
zoomLevelToKWidth,
|
|
134
142
|
kGapFromKWidth,
|
|
135
143
|
getPhysicalKLineConfig,
|
|
136
|
-
SUB_PANE_INDICATOR_CONFIGS,
|
|
137
|
-
SUB_PANE_INDICATORS,
|
|
138
144
|
DrawingInteractionController,
|
|
139
145
|
} from '@363045841yyt/klinechart-core/controllers'
|
|
146
|
+
import {
|
|
147
|
+
getRegisteredIndicatorDefinition,
|
|
148
|
+
getRegisteredIndicatorDefinitions,
|
|
149
|
+
} from '@363045841yyt/klinechart-core/indicators'
|
|
140
150
|
import type { DrawingObject, DrawingStyle } from '@363045841yyt/klinechart-core/plugin'
|
|
141
151
|
import type { ChartSettings } from '@363045841yyt/klinechart-core/config'
|
|
142
152
|
import { resolveThemeColors, themeToCssVars, lightTheme, darkTheme, type ColorPresetSettings } from '@363045841yyt/klinechart-core'
|
|
143
153
|
import LeftToolbar from './LeftToolbar.vue'
|
|
154
|
+
import TopToolbar from './TopToolbar.vue'
|
|
144
155
|
|
|
145
156
|
const props = withDefaults(
|
|
146
157
|
defineProps<{
|
|
@@ -184,13 +195,23 @@ const emit = defineEmits<{
|
|
|
184
195
|
(e: 'zoomLevelChange', level: number, kWidth: number): void
|
|
185
196
|
(e: 'toggleFullscreen'): void
|
|
186
197
|
(e: 'themeChange', theme: 'light' | 'dark'): void
|
|
198
|
+
(e: 'addOverlaySymbol'): void
|
|
199
|
+
(e: 'kLineLevelChange', level: string): void
|
|
187
200
|
}>()
|
|
188
201
|
|
|
202
|
+
const kLineLevel = ref(props.semanticConfig.data.period)
|
|
203
|
+
|
|
204
|
+
function onKLineLevelChange(level: string) {
|
|
205
|
+
kLineLevel.value = level as typeof kLineLevel.value
|
|
206
|
+
emit('kLineLevelChange', level)
|
|
207
|
+
}
|
|
208
|
+
|
|
189
209
|
const containerRef = ref<HTMLDivElement | null>(null)
|
|
190
210
|
const chartMainRef = ref<HTMLDivElement | null>(null)
|
|
191
211
|
const chartWrapperRef = ref<HTMLDivElement | null>(null)
|
|
192
212
|
const tooltipLayerRef = ref<HTMLDivElement | null>(null)
|
|
193
213
|
const toolbarRef = ref<InstanceType<typeof LeftToolbar> | null>(null)
|
|
214
|
+
const indicatorSelectorRef = ref<InstanceType<typeof IndicatorSelector> | null>(null)
|
|
194
215
|
provideFullscreenTeleportTarget(chartWrapperRef)
|
|
195
216
|
|
|
196
217
|
/* ========== 图表控制器 ========== */
|
|
@@ -475,10 +496,15 @@ function handleSelectTool(toolId: string) {
|
|
|
475
496
|
drawingController.value?.setTool(toolId as DrawingToolId)
|
|
476
497
|
}
|
|
477
498
|
|
|
499
|
+
function onToggleIndicator() {
|
|
500
|
+
indicatorSelectorRef.value?.toggleMenu()
|
|
501
|
+
}
|
|
502
|
+
|
|
478
503
|
function onUpdateDrawingStyle(style: Partial<DrawingStyle>) {
|
|
479
504
|
const d = selectedDrawing.value
|
|
480
505
|
if (!d || !drawingController.value) return
|
|
481
506
|
drawingController.value.updateDrawingStyle(d.id, style)
|
|
507
|
+
drawings.value = drawingController.value.getDrawings()
|
|
482
508
|
}
|
|
483
509
|
|
|
484
510
|
function onDeleteDrawing() {
|
|
@@ -614,7 +640,19 @@ function buildPaneLayoutIntent(): PaneSpec[] {
|
|
|
614
640
|
function getDefaultParams(
|
|
615
641
|
indicatorId: SubIndicatorType,
|
|
616
642
|
): Record<string, number | boolean | string> {
|
|
617
|
-
return {
|
|
643
|
+
if (indicatorId === 'VOLUME') return {}
|
|
644
|
+
const meta = getRegisteredIndicatorDefinition(indicatorId)
|
|
645
|
+
if (meta?.runtime?.defaultConfig) {
|
|
646
|
+
return { ...meta.runtime.defaultConfig } as Record<string, number | boolean | string>
|
|
647
|
+
}
|
|
648
|
+
return {}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 副图指标判定(基于 registry category + VOLUME 特例)
|
|
652
|
+
function isSubPaneIndicator(id: string): boolean {
|
|
653
|
+
if (id === 'VOLUME') return true
|
|
654
|
+
const def = getRegisteredIndicatorDefinition(id)
|
|
655
|
+
return !!def && def.category !== 'main'
|
|
618
656
|
}
|
|
619
657
|
|
|
620
658
|
// 副图实例计数器:用于生成 'RSI_0', 'MACD_0' 这样的 paneId
|
|
@@ -712,7 +750,7 @@ function handleIndicatorToggle(indicatorId: string, active: boolean) {
|
|
|
712
750
|
return
|
|
713
751
|
}
|
|
714
752
|
|
|
715
|
-
if (
|
|
753
|
+
if (isSubPaneIndicator(indicatorId)) {
|
|
716
754
|
if (active) {
|
|
717
755
|
const existingPane = subPanes.value.find((p) => p.indicatorId === indicatorId)
|
|
718
756
|
if (existingPane) return
|
|
@@ -740,7 +778,7 @@ function handleUpdateParams(indicatorId: string, params: Record<string, unknown>
|
|
|
740
778
|
controller.value?.updateIndicatorParams(indicatorId, params)
|
|
741
779
|
return
|
|
742
780
|
}
|
|
743
|
-
if (
|
|
781
|
+
if (isSubPaneIndicator(indicatorId)) {
|
|
744
782
|
subPanes.value
|
|
745
783
|
.filter((p) => p.indicatorId === indicatorId)
|
|
746
784
|
.forEach((pane) => {
|
|
@@ -753,7 +791,7 @@ function handleReorderSubIndicators(orderedIndicatorIds: string[]) {
|
|
|
753
791
|
if (!orderedIndicatorIds.length || subPanes.value.length <= 1) return
|
|
754
792
|
|
|
755
793
|
const validOrder = orderedIndicatorIds.filter((id): id is SubIndicatorType =>
|
|
756
|
-
|
|
794
|
+
isSubPaneIndicator(id),
|
|
757
795
|
)
|
|
758
796
|
if (!validOrder.length) return
|
|
759
797
|
|
|
@@ -1161,20 +1199,23 @@ watch(
|
|
|
1161
1199
|
|
|
1162
1200
|
display: flex;
|
|
1163
1201
|
align-items: center;
|
|
1164
|
-
justify-content: center;
|
|
1165
1202
|
width: var(--kmap-width);
|
|
1166
|
-
height: var(--kmap-height);
|
|
1203
|
+
height: calc(var(--kmap-height) - 32px);
|
|
1167
1204
|
min-height: 300px;
|
|
1168
1205
|
flex-direction: column;
|
|
1206
|
+
margin: 16px 0;
|
|
1207
|
+
padding: 0;
|
|
1208
|
+
box-sizing: border-box;
|
|
1209
|
+
gap: 4px;
|
|
1169
1210
|
}
|
|
1170
1211
|
|
|
1171
1212
|
.chart-stage {
|
|
1172
1213
|
width: 95%;
|
|
1173
|
-
|
|
1214
|
+
flex: 1;
|
|
1174
1215
|
min-height: 255px;
|
|
1175
1216
|
display: flex;
|
|
1176
1217
|
align-items: stretch;
|
|
1177
|
-
gap:
|
|
1218
|
+
gap: 4px;
|
|
1178
1219
|
}
|
|
1179
1220
|
|
|
1180
1221
|
.chart-main {
|
|
@@ -1243,7 +1284,7 @@ watch(
|
|
|
1243
1284
|
-ms-overflow-style: none;
|
|
1244
1285
|
border: 1px solid var(--chart-border);
|
|
1245
1286
|
border-right: 0;
|
|
1246
|
-
border-radius:
|
|
1287
|
+
border-radius: 3px 0 0 3px;
|
|
1247
1288
|
box-sizing: border-box;
|
|
1248
1289
|
background: var(--chart-bg);
|
|
1249
1290
|
|
|
@@ -1266,8 +1307,8 @@ watch(
|
|
|
1266
1307
|
background: var(--chart-bg);
|
|
1267
1308
|
overflow: visible;
|
|
1268
1309
|
border: 1px solid var(--chart-border);
|
|
1269
|
-
border-top-right-radius:
|
|
1270
|
-
border-bottom-right-radius:
|
|
1310
|
+
border-top-right-radius: 3px;
|
|
1311
|
+
border-bottom-right-radius: 3px;
|
|
1271
1312
|
|
|
1272
1313
|
-webkit-touch-callout: none;
|
|
1273
1314
|
-webkit-user-select: none;
|
|
@@ -1311,8 +1352,12 @@ watch(
|
|
|
1311
1352
|
}
|
|
1312
1353
|
|
|
1313
1354
|
@media (max-width: 768px), (max-height: 640px) {
|
|
1355
|
+
.chart-wrapper {
|
|
1356
|
+
gap: 4px;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1314
1359
|
.chart-stage {
|
|
1315
|
-
gap:
|
|
1360
|
+
gap: 4px;
|
|
1316
1361
|
}
|
|
1317
1362
|
}
|
|
1318
1363
|
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Dropdown
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
:options="kLineLevelOptions"
|
|
5
|
+
label="级别"
|
|
6
|
+
title="K线级别"
|
|
7
|
+
size="md"
|
|
8
|
+
@update:model-value="emit('update:modelValue', $event as KLineLevel)"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import Dropdown from './Dropdown.vue'
|
|
14
|
+
|
|
15
|
+
export type KLineLevel =
|
|
16
|
+
| '1min'
|
|
17
|
+
| '5min'
|
|
18
|
+
| '15min'
|
|
19
|
+
| '30min'
|
|
20
|
+
| '60min'
|
|
21
|
+
| 'weekly'
|
|
22
|
+
| 'monthly'
|
|
23
|
+
| 'quarterly'
|
|
24
|
+
| 'yearly'
|
|
25
|
+
|
|
26
|
+
const kLineLevelOptions: Array<{ label: string; value: KLineLevel }> = [
|
|
27
|
+
{ label: '1min', value: '1min' },
|
|
28
|
+
{ label: '5min', value: '5min' },
|
|
29
|
+
{ label: '15min', value: '15min' },
|
|
30
|
+
{ label: '30min', value: '30min' },
|
|
31
|
+
{ label: '1小时', value: '60min' },
|
|
32
|
+
{ label: '1周', value: 'weekly' },
|
|
33
|
+
{ label: '1月', value: 'monthly' },
|
|
34
|
+
{ label: '3月', value: 'quarterly' },
|
|
35
|
+
{ label: '12月', value: 'yearly' },
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
defineProps<{
|
|
39
|
+
modelValue?: string
|
|
40
|
+
}>()
|
|
41
|
+
|
|
42
|
+
const emit = defineEmits<{
|
|
43
|
+
(e: 'update:modelValue', level: KLineLevel): void
|
|
44
|
+
}>()
|
|
45
|
+
</script>
|