@363045841yyt/klinechart 0.7.6 → 0.7.7

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.
@@ -113,114 +113,15 @@
113
113
  </div>
114
114
  </nav>
115
115
 
116
- <!-- 设置弹窗 -->
117
- <Teleport :to="teleportTarget">
118
- <Transition name="overlay">
119
- <div v-if="showSettings" class="settings-overlay" @click="closeSettings">
120
- <Transition name="modal">
121
- <div class="settings-modal" @click.stop>
122
- <!-- 头部 -->
123
- <div class="settings-header">
124
- <div class="header-left">
125
- <span class="settings-title">图表设置</span>
126
- <span class="settings-subtitle">个性化配置</span>
127
- </div>
128
- <div class="header-right">
129
- <button class="settings-close" @click="closeSettings">
130
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
131
- <path d="M18 6L6 18M6 6l12 12" />
132
- </svg>
133
- </button>
134
- </div>
135
- </div>
136
-
137
- <!-- 体部 -->
138
- <div class="settings-body">
139
- <!-- 主图设置 -->
140
- <template v-if="mainSettings.length > 0">
141
- <div class="settings-section-divider">
142
- <span class="settings-section-label">主图设置</span>
143
- </div>
144
- <template v-for="item in mainSettings" :key="item.key">
145
- <div class="settings-item">
146
- <label class="settings-label">
147
- <span>{{ item.label }}</span>
148
- <template v-if="item.type === 'boolean'">
149
- <input
150
- type="checkbox"
151
- class="settings-checkbox"
152
- v-model="settings[item.key]"
153
- />
154
- </template>
155
- <template v-else-if="item.type === 'select' && item.options">
156
- <select class="settings-select" v-model="settings[item.key]">
157
- <option v-for="opt in item.options" :key="opt.value" :value="opt.value">
158
- {{ opt.label }}
159
- </option>
160
- </select>
161
- </template>
162
- </label>
163
- </div>
164
- </template>
165
- </template>
166
-
167
- <!-- 实验性设置 -->
168
- <template v-if="experimentalSettings.length > 0">
169
- <div class="settings-section-divider">
170
- <span class="settings-section-label">实验性 / 调试设置</span>
171
- </div>
172
- <template v-for="item in experimentalSettings" :key="item.key">
173
- <div class="settings-item experimental">
174
- <label class="settings-label">
175
- <span>{{ item.label }}</span>
176
- <template v-if="item.type === 'boolean'">
177
- <input
178
- type="checkbox"
179
- class="settings-checkbox"
180
- v-model="settings[item.key]"
181
- />
182
- </template>
183
- <template v-else-if="item.type === 'select' && item.options">
184
- <select class="settings-select" v-model="settings[item.key]">
185
- <option v-for="opt in item.options" :key="opt.value" :value="opt.value">
186
- {{ opt.label }}
187
- </option>
188
- </select>
189
- </template>
190
- </label>
191
- </div>
192
- </template>
193
- </template>
194
- </div>
195
-
196
- <!-- 底部 -->
197
- <div class="settings-footer">
198
- <button class="settings-btn reset" @click="resetSettings">
199
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
200
- <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
201
- <path d="M3 3v5h5" />
202
- </svg>
203
- 重置
204
- </button>
205
- <div class="footer-right">
206
- <button class="settings-btn cancel" @click="closeSettings">取消</button>
207
- <button class="settings-btn confirm" @click="confirmSettings">
208
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
209
- <path d="M20 6L9 17l-5-5" />
210
- </svg>
211
- 确定
212
- </button>
213
- </div>
214
- </div>
215
- </div>
216
- </Transition>
217
- </div>
218
- </Transition>
219
- </Teleport>
116
+ <ChartSettingsDialog
117
+ :show="showSettings"
118
+ @close="showSettings = false"
119
+ @confirm="handleConfirmSettings"
120
+ />
220
121
  </template>
221
122
 
222
123
  <script setup lang="ts">
223
- import { ref, computed, onMounted, onUnmounted } from 'vue'
124
+ import { ref, onMounted, onUnmounted } from 'vue'
224
125
  import IconTablerPointer from '~icons/tabler/pointer'
225
126
  import IconTablerChartLine from '~icons/tabler/chart-line'
226
127
  import IconTablerArrowUpRight from '~icons/tabler/arrow-up-right'
@@ -241,10 +142,10 @@ import IconTablerBrackets from '~icons/tabler/brackets'
241
142
  import {
242
143
  DEFAULT_SETTINGS,
243
144
  SETTINGS_STORAGE_KEY,
244
- type SettingItem,
145
+ type ChartSettings,
245
146
  } from '@363045841yyt/klinechart-core/config'
246
- import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
247
147
  import { setCanvasProfilerEnabled } from '../debug/canvasProfiler'
148
+ import ChartSettingsDialog from './ChartSettingsDialog.vue'
248
149
 
249
150
  export interface ToolDef {
250
151
  id: string
@@ -291,49 +192,39 @@ const emit = defineEmits<{
291
192
  (e: 'toggleFullscreen'): void
292
193
  (e: 'zoomIn'): void
293
194
  (e: 'zoomOut'): void
294
- (e: 'settingsChange', settings: Record<string, boolean | string>): void
195
+ (e: 'settingsChange', settings: ChartSettings): void
295
196
  }>()
296
197
 
297
198
  const selectedToolId = ref('cursor')
298
199
  const openGroupId = ref<string | null>(null)
299
200
  const showSettings = ref(false)
300
201
 
301
- const teleportTarget = useFullscreenTeleportTarget()
302
-
303
- const mainSettings = computed(
304
- () => DEFAULT_SETTINGS.filter((s) => s.group === 'main') as unknown as SettingItem[],
305
- )
306
- const experimentalSettings = computed(
307
- () => DEFAULT_SETTINGS.filter((s) => s.group === 'experimental') as unknown as SettingItem[],
308
- )
309
-
310
- function loadSettings(): Record<string, boolean | string> {
202
+ function loadSettings(): ChartSettings {
311
203
  try {
312
204
  const saved = localStorage.getItem(SETTINGS_STORAGE_KEY)
313
205
  if (saved) {
314
206
  const parsed = JSON.parse(saved)
315
- const result: Record<string, boolean | string> = {}
207
+ const result: ChartSettings = { ...parsed }
316
208
  DEFAULT_SETTINGS.forEach((item) => {
317
209
  result[item.key] = parsed[item.key] ?? item.default
318
210
  })
319
211
  return result
320
212
  }
321
213
  } catch {}
322
- const defaults: Record<string, boolean | string> = {}
214
+ const defaults: ChartSettings = {}
323
215
  DEFAULT_SETTINGS.forEach((item) => {
324
216
  defaults[item.key] = item.default
325
217
  })
326
218
  return defaults
327
219
  }
328
220
 
329
- function saveSettings(settings: Record<string, boolean | string>) {
221
+ function saveSettings(settings: ChartSettings) {
330
222
  try {
331
223
  localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(settings))
332
224
  } catch {}
333
225
  }
334
226
 
335
- const appliedSettings = ref<Record<string, boolean | string>>(loadSettings())
336
- const settings = ref<Record<string, boolean | string>>({ ...appliedSettings.value })
227
+ const appliedSettings = ref<ChartSettings>(loadSettings())
337
228
 
338
229
  function isActive(tool: ToolDef): boolean {
339
230
  if (selectedToolId.value === tool.id) return true
@@ -370,38 +261,25 @@ function toggleExpand(groupId: string) {
370
261
  }
371
262
 
372
263
  function openSettings() {
373
- settings.value = { ...appliedSettings.value }
374
264
  showSettings.value = true
375
265
  }
376
266
 
377
- function closeSettings() {
378
- showSettings.value = false
267
+ function getCurrentSettings(): ChartSettings {
268
+ return { ...appliedSettings.value }
379
269
  }
380
270
 
381
- function resetSettings() {
382
- const defaults: Record<string, boolean | string> = {}
383
- DEFAULT_SETTINGS.forEach((item) => {
384
- defaults[item.key] = item.default
385
- })
386
- settings.value = defaults
387
- }
271
+ defineExpose({
272
+ getSettings: getCurrentSettings,
273
+ })
388
274
 
389
- function confirmSettings() {
390
- appliedSettings.value = { ...settings.value }
275
+ function handleConfirmSettings(draft: ChartSettings) {
276
+ appliedSettings.value = { ...draft }
391
277
  saveSettings(appliedSettings.value)
392
278
  setCanvasProfilerEnabled(!!appliedSettings.value['enableCanvasProfiler'])
393
279
  emit('settingsChange', { ...appliedSettings.value })
394
- closeSettings()
395
- }
396
-
397
- function getCurrentSettings(): Record<string, boolean | string> {
398
- return { ...appliedSettings.value }
280
+ showSettings.value = false
399
281
  }
400
282
 
401
- defineExpose({
402
- getSettings: getCurrentSettings,
403
- })
404
-
405
283
  function handleClickOutside(e: MouseEvent) {
406
284
  const target = e.target as HTMLElement
407
285
  if (!target.closest('.tool-item')) {
@@ -428,9 +306,9 @@ onUnmounted(() => {
428
306
  align-items: center;
429
307
  gap: 6px;
430
308
  padding: 8px 5px;
431
- border: 1px solid #e5e7eb;
309
+ border: 1px solid var(--klc-color-border-chart);
432
310
  border-radius: 6px;
433
- background: #fafbfc;
311
+ background: var(--klc-color-background);
434
312
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
435
313
  box-sizing: border-box;
436
314
  user-select: none;
@@ -445,7 +323,7 @@ onUnmounted(() => {
445
323
  .left-toolbar__divider {
446
324
  width: 18px;
447
325
  height: 1px;
448
- background: #e5e7eb;
326
+ background: var(--klc-color-border-chart);
449
327
  }
450
328
 
451
329
  /* --- 工具按钮 --- */
@@ -457,7 +335,7 @@ onUnmounted(() => {
457
335
  border: 1px solid transparent;
458
336
  border-radius: 4px;
459
337
  background: transparent;
460
- color: #6b7280;
338
+ color: var(--klc-color-axis-text);
461
339
  cursor: pointer;
462
340
  display: inline-flex;
463
341
  align-items: center;
@@ -469,20 +347,20 @@ onUnmounted(() => {
469
347
  }
470
348
 
471
349
  .left-toolbar__button:hover {
472
- border-color: #d1d5db;
473
- background: #f3f4f6;
474
- color: #374151;
350
+ border-color: var(--klc-color-axis-line);
351
+ background: var(--klc-color-tag-bg-hover);
352
+ color: var(--klc-color-foreground);
475
353
  }
476
354
 
477
355
  .left-toolbar__button.active {
478
- border-color: #9ca3af;
479
- background: #e5e7eb;
480
- color: #1f2937;
356
+ border-color: var(--klc-color-border-chart);
357
+ background: var(--klc-color-grid-major);
358
+ color: var(--klc-color-foreground);
481
359
  }
482
360
 
483
361
  .left-toolbar__button:focus-visible {
484
362
  outline: none;
485
- border-color: #6b7280;
363
+ border-color: var(--klc-color-axis-text);
486
364
  }
487
365
 
488
366
  .tool-icon {
@@ -538,10 +416,10 @@ onUnmounted(() => {
538
416
  gap: 4px;
539
417
  padding: 0 5px;
540
418
  height: 40px;
541
- background: rgba(250, 251, 252, 0.82);
419
+ background: var(--klc-color-tag-bg-white);
542
420
  backdrop-filter: blur(8px);
543
421
  -webkit-backdrop-filter: blur(8px);
544
- border: 1px solid #e5e7eb;
422
+ border: 1px solid var(--klc-color-border-chart);
545
423
  border-radius: 6px;
546
424
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
547
425
  box-sizing: border-box;
@@ -605,240 +483,4 @@ onUnmounted(() => {
605
483
  }
606
484
  }
607
485
 
608
- /* ═══ 设置弹窗样式(参考 IndicatorParams.vue)═══ */
609
- .settings-overlay {
610
- position: fixed;
611
- inset: 0;
612
- background: rgba(0, 0, 0, 0.3);
613
- backdrop-filter: blur(4px);
614
- display: flex;
615
- align-items: center;
616
- justify-content: center;
617
- z-index: 1000;
618
- }
619
-
620
- .settings-modal {
621
- background: #ffffff;
622
- border: 1px solid #e0e0e0;
623
- border-radius: 12px;
624
- box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
625
- min-width: 340px;
626
- max-width: 420px;
627
- width: 90vw;
628
- overflow: hidden;
629
- }
630
-
631
- .settings-header {
632
- display: flex;
633
- justify-content: space-between;
634
- align-items: center;
635
- padding: 16px 20px;
636
- background: #f8f8f8;
637
- border-bottom: 1px solid #e8e8e8;
638
- }
639
-
640
- .header-left {
641
- display: flex;
642
- align-items: baseline;
643
- gap: 8px;
644
- }
645
-
646
- .header-right {
647
- display: flex;
648
- align-items: center;
649
- gap: 8px;
650
- }
651
-
652
- .settings-title {
653
- font-size: 14px;
654
- font-weight: 600;
655
- color: #1a1a1a;
656
- letter-spacing: 0.2px;
657
- }
658
-
659
- .settings-subtitle {
660
- font-size: 11px;
661
- color: #999;
662
- }
663
-
664
- .settings-close {
665
- background: #fff;
666
- border: 1px solid #e0e0e0;
667
- border-radius: 6px;
668
- width: 28px;
669
- height: 28px;
670
- display: flex;
671
- align-items: center;
672
- justify-content: center;
673
- cursor: pointer;
674
- color: #888;
675
- transition:
676
- background 0.15s,
677
- color 0.15s,
678
- border-color 0.15s;
679
- padding: 0;
680
- }
681
-
682
- .settings-close:hover {
683
- background: #f0f0f0;
684
- color: #333;
685
- border-color: #ccc;
686
- }
687
-
688
- .settings-close svg {
689
- width: 14px;
690
- height: 14px;
691
- }
692
-
693
- .settings-body {
694
- padding: 16px 20px;
695
- display: flex;
696
- flex-direction: column;
697
- gap: 10px;
698
- }
699
-
700
- .settings-item {
701
- padding: 8px 12px;
702
- border-radius: 8px;
703
- background: #f8f8f8;
704
- border: 1px solid #e8e8e8;
705
- }
706
-
707
- .settings-label {
708
- display: flex;
709
- align-items: center;
710
- justify-content: space-between;
711
- font-size: 13px;
712
- color: #333;
713
- cursor: pointer;
714
- }
715
-
716
- .settings-checkbox {
717
- width: 16px;
718
- height: 16px;
719
- cursor: pointer;
720
- accent-color: #1a1a1a;
721
- }
722
-
723
- .settings-select {
724
- padding: 4px 8px;
725
- border: 1px solid #d0d0d0;
726
- border-radius: 6px;
727
- background: #fff;
728
- color: #333;
729
- font-size: 12px;
730
- cursor: pointer;
731
- outline: none;
732
- min-width: 140px;
733
- }
734
-
735
- .settings-select:hover {
736
- border-color: #9ca3af;
737
- }
738
-
739
- .settings-select:focus {
740
- border-color: #6b7280;
741
- box-shadow: 0 0 0 2px rgba(107, 114, 128, 0.15);
742
- }
743
-
744
- .settings-section-divider {
745
- display: flex;
746
- align-items: center;
747
- gap: 8px;
748
- margin-top: 4px;
749
- }
750
-
751
- .settings-section-divider::before,
752
- .settings-section-divider::after {
753
- content: '';
754
- flex: 1;
755
- border-top: 1px solid #e0e0e0;
756
- }
757
-
758
- .settings-section-label {
759
- font-size: 11px;
760
- color: #999;
761
- white-space: nowrap;
762
- }
763
-
764
- .settings-item.experimental {
765
- border-color: #f0e0d0;
766
- background: #fdf8f3;
767
- }
768
-
769
- .settings-footer {
770
- display: flex;
771
- align-items: center;
772
- justify-content: space-between;
773
- padding: 12px 20px;
774
- background: #f8f8f8;
775
- border-top: 1px solid #e8e8e8;
776
- }
777
-
778
- .footer-right {
779
- display: flex;
780
- gap: 8px;
781
- }
782
-
783
- .settings-btn {
784
- display: flex;
785
- align-items: center;
786
- gap: 5px;
787
- padding: 6px 14px;
788
- border-radius: 7px;
789
- font-size: 13px;
790
- font-weight: 500;
791
- cursor: pointer;
792
- border: 1px solid transparent;
793
- transition: all 0.15s;
794
- line-height: 1.4;
795
- }
796
-
797
- .settings-btn svg {
798
- width: 12px;
799
- height: 12px;
800
- flex-shrink: 0;
801
- }
802
-
803
- .settings-btn.reset {
804
- background: transparent;
805
- border-color: #d0d0d0;
806
- color: #666;
807
- }
808
-
809
- .settings-btn.reset:hover {
810
- border-color: #c0392b;
811
- color: #e74c3c;
812
- background: rgba(231, 76, 60, 0.08);
813
- }
814
-
815
- .settings-btn.cancel {
816
- background: transparent;
817
- border-color: #d0d0d0;
818
- color: #666;
819
- }
820
-
821
- .settings-btn.cancel:hover {
822
- background: #f0f0f0;
823
- color: #333;
824
- border-color: #bbb;
825
- }
826
-
827
- .settings-btn.confirm {
828
- background: #1a1a1a;
829
- border-color: #1a1a1a;
830
- color: #fff;
831
- }
832
-
833
- .settings-btn.confirm:hover {
834
- background: #333;
835
- border-color: #333;
836
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
837
- transform: translateY(-1px);
838
- }
839
-
840
- .settings-btn.confirm:active {
841
- transform: translateY(0);
842
- box-shadow: none;
843
- }
844
486
  </style>
@@ -91,10 +91,10 @@ function formatValue(value: unknown): string {
91
91
  max-width: 260px;
92
92
  padding: 10px 12px;
93
93
  border-radius: 8px;
94
- background: rgba(255, 255, 255, 0.92);
95
- border: 1px solid rgba(0, 0, 0, 0.12);
94
+ background: var(--klc-color-tooltip-bg);
95
+ border: 1px solid var(--klc-color-tooltip-border);
96
96
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
97
- color: rgba(0, 0, 0, 0.78);
97
+ color: var(--klc-color-tooltip-text);
98
98
  font-size: 12px;
99
99
  line-height: 1.4;
100
100
  pointer-events: none;
@@ -122,7 +122,7 @@ function formatValue(value: unknown): string {
122
122
  }
123
123
 
124
124
  .marker-tooltip__content .row span:first-child {
125
- color: rgba(0, 0, 0, 0.56);
125
+ color: color-mix(in srgb, var(--klc-color-tooltip-text) 70%, transparent);
126
126
  }
127
127
 
128
128
  @supports (anchor-name: --kmap-anchor) and (position-anchor: --kmap-anchor) {
@@ -1,3 +1,4 @@
1
+ export { default as ColorPresetPanel } from './ColorPresetPanel.vue'
1
2
  export { default as DrawingStyleToolbar } from './DrawingStyleToolbar.vue'
2
3
  export { default as IndicatorParams } from './IndicatorParams.vue'
3
4
  export { default as IndicatorSelector } from './IndicatorSelector.vue'