@363045841yyt/klinechart 0.7.6 → 0.7.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@363045841yyt/klinechart",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "description": "Vue 3 bindings for @363045841yyt/klinechart-core. Idiomatic composables, SFC components.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,624 @@
1
+ <template>
2
+ <Teleport :to="teleportTarget">
3
+ <Transition name="overlay">
4
+ <div v-if="show" class="settings-overlay" @click="closeSettings">
5
+ <Transition name="modal">
6
+ <div class="settings-modal" @click.stop>
7
+ <div class="settings-header">
8
+ <div class="header-left">
9
+ <span class="settings-title">图表设置</span>
10
+ <span class="settings-subtitle">个性化配置</span>
11
+ </div>
12
+ <div class="header-right">
13
+ <button class="settings-close" @click="closeSettings">
14
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
15
+ <path d="M18 6L6 18M6 6l12 12" />
16
+ </svg>
17
+ </button>
18
+ </div>
19
+ </div>
20
+
21
+ <div class="settings-body">
22
+ <template v-if="mainSettings.length > 0">
23
+ <div class="settings-section-divider">
24
+ <span class="settings-section-label">主图设置</span>
25
+ </div>
26
+ <template v-for="item in mainSettings" :key="item.key">
27
+ <div class="settings-item">
28
+ <label class="settings-label">
29
+ <span>{{ item.label }}</span>
30
+ <template v-if="item.type === 'boolean'">
31
+ <input
32
+ type="checkbox"
33
+ class="settings-checkbox"
34
+ v-model="settings[item.key]"
35
+ />
36
+ </template>
37
+ <template v-else-if="item.type === 'select' && item.options">
38
+ <select class="settings-select" v-model="settings[item.key]">
39
+ <option v-for="opt in item.options" :key="opt.value" :value="opt.value">
40
+ {{ opt.label }}
41
+ </option>
42
+ </select>
43
+ </template>
44
+ </label>
45
+ </div>
46
+ </template>
47
+ </template>
48
+
49
+ <div class="settings-section-divider">
50
+ <span class="settings-section-label">样式 / 颜色</span>
51
+ </div>
52
+ <div class="settings-item nav-item" @click="showColorPresetModal = true">
53
+ <label class="settings-label">
54
+ <span>颜色配置</span>
55
+ <svg
56
+ viewBox="0 0 24 24"
57
+ fill="none"
58
+ stroke="currentColor"
59
+ stroke-width="2"
60
+ width="16"
61
+ height="16"
62
+ class="nav-arrow"
63
+ >
64
+ <path d="M9 18l6-6-6-6" />
65
+ </svg>
66
+ </label>
67
+ </div>
68
+
69
+ <template v-if="experimentalSettings.length > 0">
70
+ <div class="settings-section-divider">
71
+ <span class="settings-section-label">实验性 / 调试设置</span>
72
+ </div>
73
+ <template v-for="item in experimentalSettings" :key="item.key">
74
+ <div class="settings-item experimental">
75
+ <label class="settings-label">
76
+ <span>{{ item.label }}</span>
77
+ <template v-if="item.type === 'boolean'">
78
+ <input
79
+ type="checkbox"
80
+ class="settings-checkbox"
81
+ v-model="settings[item.key]"
82
+ />
83
+ </template>
84
+ <template v-else-if="item.type === 'select' && item.options">
85
+ <select class="settings-select" v-model="settings[item.key]">
86
+ <option v-for="opt in item.options" :key="opt.value" :value="opt.value">
87
+ {{ opt.label }}
88
+ </option>
89
+ </select>
90
+ </template>
91
+ </label>
92
+ </div>
93
+ </template>
94
+ </template>
95
+ </div>
96
+
97
+ <div class="settings-footer">
98
+ <button class="settings-btn reset" @click="resetSettings">
99
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
100
+ <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
101
+ <path d="M3 3v5h5" />
102
+ </svg>
103
+ 重置
104
+ </button>
105
+ <div class="footer-right">
106
+ <button class="settings-btn cancel" @click="closeSettings">取消</button>
107
+ <button class="settings-btn confirm" @click="confirmSettings">
108
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
109
+ <path d="M20 6L9 17l-5-5" />
110
+ </svg>
111
+ 确定
112
+ </button>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </Transition>
117
+ </div>
118
+ </Transition>
119
+
120
+ <Transition name="overlay">
121
+ <div
122
+ v-if="showColorPresetModal"
123
+ class="settings-overlay nested-overlay"
124
+ @click="showColorPresetModal = false"
125
+ >
126
+ <Transition name="modal">
127
+ <div class="settings-modal" @click.stop>
128
+ <div class="settings-header">
129
+ <div class="header-left">
130
+ <span class="settings-title">颜色预设</span>
131
+ <span class="settings-subtitle">自定义图表颜色</span>
132
+ </div>
133
+ <div class="header-right">
134
+ <button class="settings-close" @click="showColorPresetModal = false">
135
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
136
+ <path d="M18 6L6 18M6 6l12 12" />
137
+ </svg>
138
+ </button>
139
+ </div>
140
+ </div>
141
+ <div class="settings-body">
142
+ <ColorPresetPanel
143
+ :color-preset-settings="settings.colorPresetSettings"
144
+ @update:color-preset-settings="
145
+ settings = { ...settings, colorPresetSettings: $event }
146
+ "
147
+ />
148
+ </div>
149
+ </div>
150
+ </Transition>
151
+ </div>
152
+ </Transition>
153
+ </Teleport>
154
+ </template>
155
+
156
+ <script setup lang="ts">
157
+ import { ref, computed, watch } from 'vue'
158
+ import {
159
+ DEFAULT_SETTINGS,
160
+ SETTINGS_STORAGE_KEY,
161
+ type ChartSettings,
162
+ type SettingItem,
163
+ } from '@363045841yyt/klinechart-core/config'
164
+ import { normalizeColorPresetSettings } from '@363045841yyt/klinechart-core'
165
+ import ColorPresetPanel from './ColorPresetPanel.vue'
166
+ import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
167
+
168
+ const props = defineProps<{
169
+ show: boolean
170
+ }>()
171
+
172
+ const emit = defineEmits<{
173
+ (e: 'close'): void
174
+ (e: 'confirm', settings: ChartSettings): void
175
+ }>()
176
+
177
+ const teleportTarget = useFullscreenTeleportTarget()
178
+
179
+ const mainSettings = computed(
180
+ () => DEFAULT_SETTINGS.filter((s) => s.group === 'main') as unknown as SettingItem[],
181
+ )
182
+ const experimentalSettings = computed(
183
+ () => DEFAULT_SETTINGS.filter((s) => s.group === 'experimental') as unknown as SettingItem[],
184
+ )
185
+
186
+ const showColorPresetModal = ref(false)
187
+
188
+ function loadSettings(): ChartSettings {
189
+ try {
190
+ const saved = localStorage.getItem(SETTINGS_STORAGE_KEY)
191
+ if (saved) {
192
+ const parsed = JSON.parse(saved)
193
+ const result: ChartSettings = {}
194
+ DEFAULT_SETTINGS.forEach((item) => {
195
+ result[item.key] = parsed[item.key] ?? item.default
196
+ })
197
+ result.colorPresetSettings = normalizeColorPresetSettings(parsed.colorPresetSettings)
198
+ return result
199
+ }
200
+ } catch {}
201
+ const defaults: ChartSettings = {}
202
+ DEFAULT_SETTINGS.forEach((item) => {
203
+ defaults[item.key] = item.default
204
+ })
205
+ defaults.colorPresetSettings = {}
206
+ return defaults
207
+ }
208
+
209
+ const settings = ref<ChartSettings>(loadSettings())
210
+
211
+ watch(
212
+ () => props.show,
213
+ (val) => {
214
+ if (val) {
215
+ settings.value = loadSettings()
216
+ }
217
+ },
218
+ )
219
+
220
+ function closeSettings() {
221
+ emit('close')
222
+ }
223
+
224
+ function resetSettings() {
225
+ const defaults: ChartSettings = {}
226
+ DEFAULT_SETTINGS.forEach((item) => {
227
+ defaults[item.key] = item.default
228
+ })
229
+ defaults.colorPresetSettings = {}
230
+ settings.value = defaults
231
+ }
232
+
233
+ function confirmSettings() {
234
+ emit('confirm', { ...settings.value })
235
+ }
236
+ </script>
237
+
238
+ <style scoped>
239
+ .settings-overlay {
240
+ position: fixed;
241
+ inset: 0;
242
+ background: rgba(0, 0, 0, 0.3);
243
+ backdrop-filter: blur(4px);
244
+ padding: 24px;
245
+ display: flex;
246
+ align-items: center;
247
+ justify-content: center;
248
+ z-index: 1000;
249
+ }
250
+
251
+ .settings-modal {
252
+ background: var(--klc-color-tag-bg-white);
253
+ border: 1px solid var(--klc-color-border-button);
254
+ border-radius: 10px;
255
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.15);
256
+ min-width: 360px;
257
+ max-width: 460px;
258
+ width: min(92vw, 460px);
259
+ max-height: min(720px, calc(100vh - 48px));
260
+ overflow: hidden;
261
+ display: flex;
262
+ flex-direction: column;
263
+ }
264
+
265
+ .settings-header {
266
+ display: flex;
267
+ justify-content: space-between;
268
+ align-items: center;
269
+ padding: 14px 18px 14px 20px;
270
+ background: var(--klc-color-background);
271
+ border-bottom: 1px solid var(--klc-color-grid-major);
272
+ flex-shrink: 0;
273
+ }
274
+
275
+ .header-left {
276
+ display: flex;
277
+ flex-direction: column;
278
+ gap: 2px;
279
+ min-width: 0;
280
+ }
281
+
282
+ .header-right {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 8px;
286
+ }
287
+
288
+ .settings-title {
289
+ font-size: 15px;
290
+ font-weight: 600;
291
+ color: var(--klc-color-foreground);
292
+ line-height: 1.35;
293
+ }
294
+
295
+ .settings-subtitle {
296
+ font-size: 11px;
297
+ color: var(--klc-color-axis-text);
298
+ line-height: 1.3;
299
+ }
300
+
301
+ .settings-close {
302
+ background: var(--klc-color-tag-bg-white);
303
+ border: 1px solid var(--klc-color-border-button);
304
+ border-radius: 7px;
305
+ width: 30px;
306
+ height: 30px;
307
+ display: flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ cursor: pointer;
311
+ color: var(--klc-color-axis-text);
312
+ transition:
313
+ background 0.15s,
314
+ color 0.15s,
315
+ border-color 0.15s;
316
+ padding: 0;
317
+ }
318
+
319
+ .settings-close:hover {
320
+ background: var(--klc-color-tag-bg-hover);
321
+ color: var(--klc-color-foreground);
322
+ border-color: var(--klc-color-axis-line);
323
+ }
324
+
325
+ .settings-close svg {
326
+ width: 14px;
327
+ height: 14px;
328
+ }
329
+
330
+ .settings-body {
331
+ padding: 16px 20px 18px;
332
+ display: flex;
333
+ flex-direction: column;
334
+ gap: 8px;
335
+ overflow-y: auto;
336
+ overscroll-behavior: contain;
337
+ }
338
+
339
+ .settings-body::-webkit-scrollbar {
340
+ width: 8px;
341
+ }
342
+
343
+ .settings-body::-webkit-scrollbar-thumb {
344
+ background: var(--klc-color-axis-line);
345
+ border: 2px solid var(--klc-color-tag-bg-white);
346
+ border-radius: 999px;
347
+ }
348
+
349
+ .settings-item {
350
+ padding: 0;
351
+ border-radius: 8px;
352
+ background: var(--klc-color-background);
353
+ border: 1px solid var(--klc-color-grid-major);
354
+ transition:
355
+ border-color 0.15s,
356
+ background 0.15s,
357
+ box-shadow 0.15s;
358
+ }
359
+
360
+ .settings-item:hover {
361
+ border-color: var(--klc-color-axis-line);
362
+ background: var(--klc-color-tag-bg-white);
363
+ }
364
+
365
+ .settings-label {
366
+ display: flex;
367
+ align-items: center;
368
+ justify-content: space-between;
369
+ gap: 16px;
370
+ min-height: 42px;
371
+ padding: 9px 12px;
372
+ font-size: 13px;
373
+ color: var(--klc-color-foreground);
374
+ cursor: pointer;
375
+ }
376
+
377
+ .settings-label > span {
378
+ min-width: 0;
379
+ line-height: 1.35;
380
+ }
381
+
382
+ .settings-checkbox {
383
+ flex: 0 0 auto;
384
+ width: 17px;
385
+ height: 17px;
386
+ cursor: pointer;
387
+ accent-color: var(--klc-color-foreground);
388
+ }
389
+
390
+ .settings-select {
391
+ flex: 0 0 auto;
392
+ height: 30px;
393
+ padding: 4px 28px 4px 9px;
394
+ border: 1px solid var(--klc-color-axis-line);
395
+ border-radius: 6px;
396
+ background: var(--klc-color-tag-bg-white);
397
+ color: var(--klc-color-foreground);
398
+ font-size: 12px;
399
+ cursor: pointer;
400
+ outline: none;
401
+ min-width: 150px;
402
+ max-width: 190px;
403
+ }
404
+
405
+ .settings-select:hover {
406
+ border-color: var(--klc-color-axis-text);
407
+ }
408
+
409
+ .settings-select:focus {
410
+ border-color: var(--klc-color-axis-text);
411
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--klc-color-axis-text) 15%, transparent);
412
+ }
413
+
414
+ .settings-section-divider {
415
+ display: flex;
416
+ align-items: center;
417
+ gap: 8px;
418
+ margin: 10px 0 2px;
419
+ }
420
+
421
+ .settings-section-divider:first-child {
422
+ margin-top: 0;
423
+ }
424
+
425
+ .settings-section-divider::before,
426
+ .settings-section-divider::after {
427
+ content: '';
428
+ flex: 1;
429
+ border-top: 1px solid var(--klc-color-border-button);
430
+ }
431
+
432
+ .settings-section-label {
433
+ font-size: 11px;
434
+ color: var(--klc-color-axis-text);
435
+ white-space: nowrap;
436
+ line-height: 1;
437
+ }
438
+
439
+ .nested-overlay {
440
+ z-index: 1100;
441
+ }
442
+
443
+ .settings-item.nav-item {
444
+ cursor: pointer;
445
+ }
446
+
447
+ .settings-item.nav-item:hover .nav-arrow {
448
+ color: var(--klc-color-foreground);
449
+ }
450
+
451
+ .nav-arrow {
452
+ color: var(--klc-color-axis-text);
453
+ transition: color 0.15s;
454
+ flex-shrink: 0;
455
+ }
456
+
457
+ .settings-footer {
458
+ display: flex;
459
+ align-items: center;
460
+ justify-content: space-between;
461
+ gap: 12px;
462
+ padding: 12px 20px;
463
+ background: var(--klc-color-background);
464
+ border-top: 1px solid var(--klc-color-grid-major);
465
+ flex-shrink: 0;
466
+ }
467
+
468
+ .footer-right {
469
+ display: flex;
470
+ gap: 8px;
471
+ justify-content: flex-end;
472
+ }
473
+
474
+ .settings-btn {
475
+ display: flex;
476
+ align-items: center;
477
+ justify-content: center;
478
+ gap: 5px;
479
+ min-width: 68px;
480
+ height: 32px;
481
+ padding: 0 14px;
482
+ border-radius: 7px;
483
+ font-size: 13px;
484
+ font-weight: 500;
485
+ cursor: pointer;
486
+ border: 1px solid transparent;
487
+ transition:
488
+ background 0.15s,
489
+ border-color 0.15s,
490
+ color 0.15s,
491
+ box-shadow 0.15s,
492
+ transform 0.15s;
493
+ line-height: 1;
494
+ white-space: nowrap;
495
+ }
496
+
497
+ .settings-btn svg {
498
+ width: 12px;
499
+ height: 12px;
500
+ flex-shrink: 0;
501
+ }
502
+
503
+ .settings-btn.reset {
504
+ background: transparent;
505
+ border-color: var(--klc-color-axis-line);
506
+ color: var(--klc-color-axis-text);
507
+ min-width: 76px;
508
+ }
509
+
510
+ .settings-btn.reset:hover {
511
+ border-color: #c0392b;
512
+ color: #e74c3c;
513
+ background: rgba(231, 76, 60, 0.08);
514
+ }
515
+
516
+ .settings-btn.cancel {
517
+ background: transparent;
518
+ border-color: var(--klc-color-axis-line);
519
+ color: var(--klc-color-axis-text);
520
+ }
521
+
522
+ .settings-btn.cancel:hover {
523
+ background: var(--klc-color-tag-bg-hover);
524
+ color: var(--klc-color-foreground);
525
+ border-color: var(--klc-color-axis-text);
526
+ }
527
+
528
+ .settings-btn.confirm {
529
+ background: var(--klc-color-foreground);
530
+ border-color: var(--klc-color-foreground);
531
+ color: var(--klc-color-background);
532
+ }
533
+
534
+ .settings-btn.confirm:hover {
535
+ background: var(--klc-color-foreground);
536
+ border-color: var(--klc-color-foreground);
537
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
538
+ transform: translateY(-1px);
539
+ }
540
+
541
+ .settings-btn.confirm:active {
542
+ transform: translateY(0);
543
+ box-shadow: none;
544
+ }
545
+
546
+ .overlay-enter-active,
547
+ .overlay-leave-active {
548
+ transition: opacity 0.2s ease;
549
+ }
550
+
551
+ .overlay-enter-from,
552
+ .overlay-leave-to {
553
+ opacity: 0;
554
+ }
555
+
556
+ .modal-enter-active {
557
+ transition: all 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
558
+ }
559
+
560
+ .modal-leave-active {
561
+ transition: all 0.16s ease-in;
562
+ }
563
+
564
+ .modal-enter-from {
565
+ opacity: 0;
566
+ transform: scale(0.96) translateY(-10px);
567
+ }
568
+
569
+ .modal-leave-to {
570
+ opacity: 0;
571
+ transform: scale(0.98) translateY(8px);
572
+ }
573
+
574
+ @media (max-width: 480px) {
575
+ .settings-overlay {
576
+ padding: 12px;
577
+ align-items: flex-end;
578
+ }
579
+
580
+ .settings-modal {
581
+ min-width: 0;
582
+ width: 100%;
583
+ max-height: calc(100vh - 24px);
584
+ border-radius: 10px;
585
+ }
586
+
587
+ .settings-header,
588
+ .settings-body,
589
+ .settings-footer {
590
+ padding-left: 16px;
591
+ padding-right: 16px;
592
+ }
593
+
594
+ .settings-label {
595
+ align-items: flex-start;
596
+ flex-direction: column;
597
+ gap: 8px;
598
+ }
599
+
600
+ .settings-checkbox {
601
+ align-self: flex-end;
602
+ margin-top: -26px;
603
+ }
604
+
605
+ .settings-select {
606
+ width: 100%;
607
+ max-width: none;
608
+ }
609
+
610
+ .settings-footer {
611
+ align-items: stretch;
612
+ flex-direction: column-reverse;
613
+ }
614
+
615
+ .footer-right {
616
+ display: grid;
617
+ grid-template-columns: 1fr 1fr;
618
+ }
619
+
620
+ .settings-btn {
621
+ width: 100%;
622
+ }
623
+ }
624
+ </style>