@363045841yyt/klinechart 0.8.1-alpha.4 → 0.8.2
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/README.md +6 -2
- package/dist/components/CompareSymbolSelector.vue.d.ts +2 -0
- package/dist/components/CompareSymbolSelector.vue.d.ts.map +1 -1
- package/dist/components/KLineAdjustmentDropdown.vue.d.ts +13 -0
- package/dist/components/KLineAdjustmentDropdown.vue.d.ts.map +1 -0
- package/dist/components/KLineChart.vue.d.ts +7 -2
- package/dist/components/KLineChart.vue.d.ts.map +1 -1
- package/dist/components/KLineLevelDropdown.vue.d.ts +1 -1
- package/dist/components/KLineLevelDropdown.vue.d.ts.map +1 -1
- package/dist/components/KLineTooltip.vue.d.ts +6 -0
- package/dist/components/KLineTooltip.vue.d.ts.map +1 -1
- package/dist/components/SymbolSelector.vue.d.ts.map +1 -1
- package/dist/components/TopToolbar.vue.d.ts +5 -0
- package/dist/components/TopToolbar.vue.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.css +1 -1
- package/dist/index.js +766 -687
- package/dist/web-component.d.ts +2 -1
- package/dist/web-component.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/CompareSymbolSelector.vue +29 -0
- package/src/components/KLineAdjustmentDropdown.vue +32 -0
- package/src/components/KLineChart.vue +43 -9
- package/src/components/KLineLevelDropdown.vue +2 -0
- package/src/components/KLineTooltip.vue +16 -9
- package/src/components/SymbolSelector.vue +14 -3
- package/src/components/TopToolbar.vue +26 -17
package/dist/web-component.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SemanticChartConfig, DataFetcher } from '@363045841yyt/klinechart-core/semantic';
|
|
2
2
|
declare const KLineChartElement: import('vue').VueElementConstructor<{
|
|
3
|
-
semanticConfig
|
|
3
|
+
semanticConfig?: SemanticChartConfig;
|
|
4
4
|
dataFetcher: DataFetcher;
|
|
5
5
|
yPaddingPx?: number;
|
|
6
6
|
minKWidth?: number;
|
|
@@ -11,6 +11,7 @@ declare const KLineChartElement: import('vue').VueElementConstructor<{
|
|
|
11
11
|
zoomLevels?: number;
|
|
12
12
|
initialZoomLevel?: number;
|
|
13
13
|
isFullscreen?: boolean;
|
|
14
|
+
timezone?: string;
|
|
14
15
|
}>;
|
|
15
16
|
export { KLineChartElement };
|
|
16
17
|
export default KLineChartElement;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web-component.d.ts","sourceRoot":"","sources":["../src/web-component.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAE9F,QAAA,MAAM,iBAAiB
|
|
1
|
+
{"version":3,"file":"web-component.d.ts","sourceRoot":"","sources":["../src/web-component.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAE9F,QAAA,MAAM,iBAAiB;;;;;;;;;;;;;EAErB,CAAA;AAIF,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,eAAe,iBAAiB,CAAA;AAEhC,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,CAAA"}
|
package/package.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
>
|
|
12
12
|
<span class="compare-chip__icon" aria-hidden="true">+</span>
|
|
13
13
|
<span class="compare-chip__text">比较商品</span>
|
|
14
|
+
<span v-if="comparisonLoading" class="compare-chip__spinner" />
|
|
14
15
|
<span v-if="selected.length > 0" class="compare-chip__badge">{{ selected.length }}</span>
|
|
15
16
|
</button>
|
|
16
17
|
<Transition name="symbol-popover">
|
|
@@ -65,6 +66,10 @@
|
|
|
65
66
|
:key="item.code"
|
|
66
67
|
class="compare-selected__item"
|
|
67
68
|
>
|
|
69
|
+
<span
|
|
70
|
+
class="compare-selected__color"
|
|
71
|
+
:style="{ background: comparisonColors?.get(item.code) ?? '#888' }"
|
|
72
|
+
/>
|
|
68
73
|
<span class="compare-selected__code">{{ item.code }}</span>
|
|
69
74
|
<span class="compare-selected__desc">{{ item.description }}</span>
|
|
70
75
|
<button
|
|
@@ -140,6 +145,8 @@ import type { SymbolItem } from './SymbolSelector.vue'
|
|
|
140
145
|
const props = withDefaults(defineProps<{
|
|
141
146
|
symbols: SymbolItem[]
|
|
142
147
|
selected?: string[]
|
|
148
|
+
comparisonColors?: Map<string, string>
|
|
149
|
+
comparisonLoading?: boolean
|
|
143
150
|
}>(), {
|
|
144
151
|
selected: () => [],
|
|
145
152
|
})
|
|
@@ -280,6 +287,20 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onDocumentClick)
|
|
|
280
287
|
line-height: 1;
|
|
281
288
|
}
|
|
282
289
|
|
|
290
|
+
.compare-chip__spinner {
|
|
291
|
+
display: inline-block;
|
|
292
|
+
width: 12px;
|
|
293
|
+
height: 12px;
|
|
294
|
+
border: 2px solid var(--klc-color-axis-text);
|
|
295
|
+
border-top-color: transparent;
|
|
296
|
+
border-radius: 50%;
|
|
297
|
+
animation: compare-spin 0.6s linear infinite;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
@keyframes compare-spin {
|
|
301
|
+
to { transform: rotate(360deg); }
|
|
302
|
+
}
|
|
303
|
+
|
|
283
304
|
.compare-popover {
|
|
284
305
|
position: absolute;
|
|
285
306
|
top: calc(100% + 8px);
|
|
@@ -406,6 +427,14 @@ onBeforeUnmount(() => document.removeEventListener('mousedown', onDocumentClick)
|
|
|
406
427
|
line-height: 1.3;
|
|
407
428
|
}
|
|
408
429
|
|
|
430
|
+
.compare-selected__color {
|
|
431
|
+
display: inline-block;
|
|
432
|
+
width: 8px;
|
|
433
|
+
height: 8px;
|
|
434
|
+
border-radius: 50%;
|
|
435
|
+
flex-shrink: 0;
|
|
436
|
+
}
|
|
437
|
+
|
|
409
438
|
.compare-selected__code {
|
|
410
439
|
font-weight: 600;
|
|
411
440
|
color: var(--klc-color-foreground);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Dropdown
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
:options="adjustmentOptions"
|
|
5
|
+
label="复权"
|
|
6
|
+
title="复权方式"
|
|
7
|
+
size="md"
|
|
8
|
+
@update:model-value="emit('update:modelValue', $event as KLineAdjustment)"
|
|
9
|
+
/>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import Dropdown from './Dropdown.vue'
|
|
14
|
+
import type { AdjustType } from '@363045841yyt/klinechart-core/semantic'
|
|
15
|
+
|
|
16
|
+
export type KLineAdjustment = AdjustType
|
|
17
|
+
|
|
18
|
+
const adjustmentOptions: Array<{ label: string; value: KLineAdjustment }> = [
|
|
19
|
+
{ label: '前复权', value: 'qfq' },
|
|
20
|
+
{ label: '后复权', value: 'hfq' },
|
|
21
|
+
{ label: '仅拆股', value: 'splits' },
|
|
22
|
+
{ label: '不复权', value: 'none' },
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
defineProps<{
|
|
26
|
+
modelValue?: string
|
|
27
|
+
}>()
|
|
28
|
+
|
|
29
|
+
const emit = defineEmits<{
|
|
30
|
+
(e: 'update:modelValue', adjust: KLineAdjustment): void
|
|
31
|
+
}>()
|
|
32
|
+
</script>
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
<TopToolbar
|
|
4
4
|
:symbol="currentSymbol"
|
|
5
5
|
:k-line-level="kLineLevel"
|
|
6
|
+
:k-line-adjust="kLineAdjust"
|
|
6
7
|
:symbol-loading="symbolLoading"
|
|
7
8
|
:symbol-error="symbolError"
|
|
8
9
|
:overlay-symbols="overlaySymbols"
|
|
10
|
+
:comparison-colors="comparisonColorsMap"
|
|
11
|
+
:comparison-loading="comparisonLoading"
|
|
9
12
|
@add-overlay-symbol="onAddOverlaySymbol"
|
|
10
13
|
@remove-overlay-symbol="onRemoveOverlaySymbol"
|
|
11
14
|
@k-line-level-change="onKLineLevelChange"
|
|
15
|
+
@k-line-adjust-change="onKLineAdjustChange"
|
|
12
16
|
@toggle-indicator="onToggleIndicator"
|
|
13
17
|
@symbol-change="onSymbolChange"
|
|
14
18
|
/>
|
|
@@ -89,6 +93,8 @@
|
|
|
89
93
|
:anchor-placement="tooltipAnchorPlacement"
|
|
90
94
|
:up-color="tooltipColors.upColor"
|
|
91
95
|
:down-color="tooltipColors.downColor"
|
|
96
|
+
:timezone="props.timezone"
|
|
97
|
+
:show-time="isIntraday"
|
|
92
98
|
/>
|
|
93
99
|
<MarkerTooltip
|
|
94
100
|
v-if="hoveredMarker || hoveredCustomMarker"
|
|
@@ -152,6 +158,7 @@ import {
|
|
|
152
158
|
getRegisteredIndicatorDefinitions,
|
|
153
159
|
} from '@363045841yyt/klinechart-core/indicators'
|
|
154
160
|
import type { DrawingObject, DrawingStyle } from '@363045841yyt/klinechart-core/plugin'
|
|
161
|
+
import { SETTINGS_STORAGE_KEY } from '@363045841yyt/klinechart-core/config'
|
|
155
162
|
import type { ChartSettings } from '@363045841yyt/klinechart-core/config'
|
|
156
163
|
import {
|
|
157
164
|
resolveThemeColors,
|
|
@@ -165,8 +172,8 @@ import TopToolbar, { type SymbolItem } from './TopToolbar.vue'
|
|
|
165
172
|
|
|
166
173
|
const props = withDefaults(
|
|
167
174
|
defineProps<{
|
|
168
|
-
/**
|
|
169
|
-
semanticConfig
|
|
175
|
+
/** 语义化配置(可选,唯一控制源) */
|
|
176
|
+
semanticConfig?: SemanticChartConfig
|
|
170
177
|
|
|
171
178
|
/** 数据获取函数(必需)。框架不绑定数据源,由使用者注入。 */
|
|
172
179
|
dataFetcher: DataFetcher
|
|
@@ -187,6 +194,8 @@ const props = withDefaults(
|
|
|
187
194
|
initialZoomLevel?: number
|
|
188
195
|
/** 是否全屏 */
|
|
189
196
|
isFullscreen?: boolean
|
|
197
|
+
/** 时区,默认 Asia/Shanghai */
|
|
198
|
+
timezone?: string
|
|
190
199
|
}>(),
|
|
191
200
|
{
|
|
192
201
|
yPaddingPx: 20,
|
|
@@ -198,6 +207,7 @@ const props = withDefaults(
|
|
|
198
207
|
zoomLevels: 20,
|
|
199
208
|
initialZoomLevel: 3,
|
|
200
209
|
isFullscreen: false,
|
|
210
|
+
timezone: 'Asia/Shanghai',
|
|
201
211
|
},
|
|
202
212
|
)
|
|
203
213
|
|
|
@@ -206,9 +216,12 @@ const emit = defineEmits<{
|
|
|
206
216
|
(e: 'toggleFullscreen'): void
|
|
207
217
|
(e: 'themeChange', theme: 'light' | 'dark'): void
|
|
208
218
|
(e: 'kLineLevelChange', level: string): void
|
|
219
|
+
(e: 'kLineAdjustChange', adjust: 'qfq' | 'hfq' | 'splits' | 'none'): void
|
|
209
220
|
}>()
|
|
210
221
|
|
|
211
|
-
const kLineLevel = ref(props.semanticConfig
|
|
222
|
+
const kLineLevel = ref(props.semanticConfig?.data?.period ?? 'daily')
|
|
223
|
+
const kLineAdjust = ref(props.semanticConfig?.data?.adjust ?? 'none')
|
|
224
|
+
const isIntraday = computed(() => kLineLevel.value.includes('min'))
|
|
212
225
|
const currentSymbol = ref('选择商品')
|
|
213
226
|
const currentSymbolItem = ref<SymbolItem | null>(null)
|
|
214
227
|
const symbolLoading = ref(false)
|
|
@@ -222,6 +235,12 @@ function onKLineLevelChange(level: string) {
|
|
|
222
235
|
syncSymbolsToController()
|
|
223
236
|
}
|
|
224
237
|
|
|
238
|
+
function onKLineAdjustChange(adjust: 'qfq' | 'hfq' | 'splits' | 'none') {
|
|
239
|
+
kLineAdjust.value = adjust
|
|
240
|
+
emit('kLineAdjustChange', adjust)
|
|
241
|
+
syncSymbolsToController()
|
|
242
|
+
}
|
|
243
|
+
|
|
225
244
|
function onSymbolChange(item: SymbolItem) {
|
|
226
245
|
symbolError.value = false
|
|
227
246
|
currentSymbol.value = item.code
|
|
@@ -235,13 +254,13 @@ function onAddOverlaySymbol(item: SymbolItem) {
|
|
|
235
254
|
overlaySymbolItems.value = [...overlaySymbolItems.value, item]
|
|
236
255
|
overlaySymbols.value = overlaySymbolItems.value.map((symbol) => symbol.code)
|
|
237
256
|
forcePercentAxis()
|
|
238
|
-
|
|
257
|
+
controller.value?.addComparisonSymbol(toSymbolSpec(item))
|
|
239
258
|
}
|
|
240
259
|
|
|
241
260
|
function onRemoveOverlaySymbol(code: string) {
|
|
242
261
|
overlaySymbolItems.value = overlaySymbolItems.value.filter((item) => item.code !== code)
|
|
243
262
|
overlaySymbols.value = overlaySymbolItems.value.map((symbol) => symbol.code)
|
|
244
|
-
|
|
263
|
+
controller.value?.removeComparisonSymbol(code)
|
|
245
264
|
}
|
|
246
265
|
|
|
247
266
|
function toSymbolSpec(item: SymbolItem): SymbolSpec {
|
|
@@ -250,9 +269,9 @@ function toSymbolSpec(item: SymbolItem): SymbolSpec {
|
|
|
250
269
|
exchange: item.exchange,
|
|
251
270
|
period: kLineLevel.value,
|
|
252
271
|
source: item.source,
|
|
253
|
-
startDate: props.semanticConfig
|
|
254
|
-
endDate: props.semanticConfig
|
|
255
|
-
adjust:
|
|
272
|
+
startDate: props.semanticConfig?.data?.startDate ?? '',
|
|
273
|
+
endDate: props.semanticConfig?.data?.endDate ?? '',
|
|
274
|
+
adjust: kLineAdjust.value,
|
|
256
275
|
}
|
|
257
276
|
}
|
|
258
277
|
|
|
@@ -269,6 +288,9 @@ function forcePercentAxis() {
|
|
|
269
288
|
const nextSettings = { ...chartSettings.value, axisType: 'percent' as const }
|
|
270
289
|
chartSettings.value = nextSettings
|
|
271
290
|
controller.value?.updateSettingsFacade(nextSettings)
|
|
291
|
+
try {
|
|
292
|
+
localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(nextSettings))
|
|
293
|
+
} catch { /* quota exceeded */ }
|
|
272
294
|
}
|
|
273
295
|
|
|
274
296
|
const containerRef = ref<HTMLDivElement | null>(null)
|
|
@@ -296,6 +318,8 @@ const viewWidth = ref(0)
|
|
|
296
318
|
const paneRatios = ref<Record<string, number>>({})
|
|
297
319
|
const selectedDrawingId = ref<string | null>(null)
|
|
298
320
|
const drawings = ref<DrawingObject[]>([])
|
|
321
|
+
const comparisonColorsMap = ref<Map<string, string>>(new Map())
|
|
322
|
+
const comparisonLoading = ref(false)
|
|
299
323
|
|
|
300
324
|
// 初始化 kWidth / kGap(与 Chart 引擎 zoom→物理值 转换一致)
|
|
301
325
|
const initZoom = zoomLevel.value
|
|
@@ -698,7 +722,7 @@ function clearAllSubPanes(): void {
|
|
|
698
722
|
function initIndicatorsFromConfig(): void {
|
|
699
723
|
const config = props.semanticConfig
|
|
700
724
|
const c = controller.value
|
|
701
|
-
if (!c) return
|
|
725
|
+
if (!config || !c) return
|
|
702
726
|
|
|
703
727
|
const mainIndicators = config.indicators?.main
|
|
704
728
|
if (mainIndicators) {
|
|
@@ -983,6 +1007,14 @@ function setupChartCallbacks(ctrl: ChartController): void {
|
|
|
983
1007
|
indicatorParams.value = nextParams
|
|
984
1008
|
})
|
|
985
1009
|
|
|
1010
|
+
const unsubscribeComparisonColors = ctrl.comparisonColors.subscribe(() => {
|
|
1011
|
+
comparisonColorsMap.value = new Map(ctrl.comparisonColors.peek())
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
const unsubscribeComparisonLoading = ctrl.comparisonLoading.subscribe(() => {
|
|
1015
|
+
comparisonLoading.value = ctrl.comparisonLoading.peek()
|
|
1016
|
+
})
|
|
1017
|
+
|
|
986
1018
|
onUnmounted(() => {
|
|
987
1019
|
unsubscribeViewport()
|
|
988
1020
|
unsubscribeData()
|
|
@@ -992,6 +1024,8 @@ function setupChartCallbacks(ctrl: ChartController): void {
|
|
|
992
1024
|
unsubscribeTheme()
|
|
993
1025
|
unsubscribeIndicators()
|
|
994
1026
|
unsubscribeSubPanes()
|
|
1027
|
+
unsubscribeComparisonColors()
|
|
1028
|
+
unsubscribeComparisonLoading()
|
|
995
1029
|
autoThemeMediaQuery?.removeEventListener('change', onSystemThemeChange)
|
|
996
1030
|
})
|
|
997
1031
|
}
|
|
@@ -18,12 +18,14 @@ export type KLineLevel =
|
|
|
18
18
|
| '15min'
|
|
19
19
|
| '30min'
|
|
20
20
|
| '60min'
|
|
21
|
+
| 'daily'
|
|
21
22
|
| 'weekly'
|
|
22
23
|
| 'monthly'
|
|
23
24
|
| 'quarterly'
|
|
24
25
|
| 'yearly'
|
|
25
26
|
|
|
26
27
|
const kLineLevelOptions: Array<{ label: string; value: KLineLevel }> = [
|
|
28
|
+
{ label: '1day', value: 'daily' },
|
|
27
29
|
{ label: '1min', value: '1min' },
|
|
28
30
|
{ label: '5min', value: '5min' },
|
|
29
31
|
{ label: '15min', value: '15min' },
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
>
|
|
9
9
|
<div class="kline-tooltip__title">
|
|
10
10
|
<span v-if="k.stockCode">{{ k.stockCode }}</span>
|
|
11
|
-
<span>{{
|
|
11
|
+
<span>{{ formattedDate }}</span>
|
|
12
12
|
</div>
|
|
13
13
|
<div class="kline-tooltip__grid">
|
|
14
14
|
<div class="row">
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
<script setup lang="ts">
|
|
52
52
|
import { computed } from 'vue'
|
|
53
53
|
import type { ComponentPublicInstance } from 'vue'
|
|
54
|
+
import { formatTimestamp } from '@363045841yyt/klinechart-core'
|
|
54
55
|
|
|
55
56
|
export interface KLineData {
|
|
56
57
|
timestamp: number
|
|
@@ -79,9 +80,23 @@ const props = withDefaults(defineProps<{
|
|
|
79
80
|
upColor?: string
|
|
80
81
|
/** 跌的颜色(默认绿跌) */
|
|
81
82
|
downColor?: string
|
|
83
|
+
/** 时区,默认 Asia/Shanghai */
|
|
84
|
+
timezone?: string
|
|
85
|
+
/** 是否显示时分,默认 false */
|
|
86
|
+
showTime?: boolean
|
|
82
87
|
}>(), {
|
|
83
88
|
upColor: '#ef4444',
|
|
84
89
|
downColor: '#22c55e',
|
|
90
|
+
timezone: 'Asia/Shanghai',
|
|
91
|
+
showTime: false,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const formattedDate = computed(() => {
|
|
95
|
+
if (!props.k) return ''
|
|
96
|
+
return formatTimestamp(props.k.timestamp, {
|
|
97
|
+
timeZone: props.timezone,
|
|
98
|
+
showTime: props.showTime,
|
|
99
|
+
})
|
|
85
100
|
})
|
|
86
101
|
|
|
87
102
|
const useAnchor = computed(() => props.useAnchor === true)
|
|
@@ -93,14 +108,6 @@ function onRef(el: Element | ComponentPublicInstance | null) {
|
|
|
93
108
|
props.setEl?.(el as HTMLDivElement | null)
|
|
94
109
|
}
|
|
95
110
|
|
|
96
|
-
function formatDate(ts: number): string {
|
|
97
|
-
const d = new Date(ts)
|
|
98
|
-
const y = d.getFullYear()
|
|
99
|
-
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
100
|
-
const day = String(d.getDate()).padStart(2, '0')
|
|
101
|
-
return `${y}-${m}-${day}`
|
|
102
|
-
}
|
|
103
|
-
|
|
104
111
|
function formatVolume(v: number): string {
|
|
105
112
|
if (v >= 1e8) return (v / 1e8).toFixed(2) + '亿'
|
|
106
113
|
if (v >= 1e4) return (v / 1e4).toFixed(2) + '万'
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
type="button"
|
|
5
5
|
class="symbol-chip"
|
|
6
6
|
:class="{ 'is-open': showPopup }"
|
|
7
|
-
:title="
|
|
7
|
+
:title="displayText"
|
|
8
8
|
:aria-expanded="showPopup"
|
|
9
9
|
aria-haspopup="dialog"
|
|
10
10
|
@click="togglePopup"
|
|
11
11
|
>
|
|
12
|
-
<span class="symbol-chip__code">{{
|
|
12
|
+
<span class="symbol-chip__code">{{ displayText }}</span>
|
|
13
13
|
<span v-if="loading" class="symbol-chip__spinner" aria-hidden="true" />
|
|
14
14
|
<IconTablerAlertTriangle v-else-if="error" class="symbol-chip__warn" aria-hidden="true" />
|
|
15
15
|
</button>
|
|
@@ -127,6 +127,16 @@ const searchQuery = ref('')
|
|
|
127
127
|
const searchInputRef = ref<HTMLInputElement | null>(null)
|
|
128
128
|
const chipWrapRef = ref<HTMLElement | null>(null)
|
|
129
129
|
|
|
130
|
+
const currentSymbol = computed<SymbolItem | undefined>(() =>
|
|
131
|
+
props.symbols.find((s) => s.code === props.symbol),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const displayText = computed(() => {
|
|
135
|
+
const cur = currentSymbol.value
|
|
136
|
+
if (cur) return `${cur.code} - ${cur.description}`
|
|
137
|
+
return props.symbol
|
|
138
|
+
})
|
|
139
|
+
|
|
130
140
|
const filteredSymbols = computed<SymbolItem[]>(() => {
|
|
131
141
|
const q = searchQuery.value.trim().toLowerCase()
|
|
132
142
|
if (!q) return props.symbols
|
|
@@ -186,7 +196,6 @@ watch(() => props.symbol, () => {
|
|
|
186
196
|
display: inline-flex;
|
|
187
197
|
align-items: center;
|
|
188
198
|
justify-content: center;
|
|
189
|
-
max-width: 160px;
|
|
190
199
|
padding: 0 10px;
|
|
191
200
|
gap: 5px;
|
|
192
201
|
border: 1px solid transparent;
|
|
@@ -455,6 +464,8 @@ watch(() => props.symbol, () => {
|
|
|
455
464
|
}
|
|
456
465
|
|
|
457
466
|
.symbol-chip__spinner {
|
|
467
|
+
display: inline-block;
|
|
468
|
+
flex-shrink: 0;
|
|
458
469
|
width: 12px;
|
|
459
470
|
height: 12px;
|
|
460
471
|
border: 2px solid var(--klc-color-axis-text);
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
<CompareSymbolSelector
|
|
12
12
|
:symbols="symbolPool"
|
|
13
13
|
:selected="overlaySymbols"
|
|
14
|
+
:comparison-colors="comparisonColors"
|
|
15
|
+
:comparison-loading="comparisonLoading"
|
|
14
16
|
@add="emit('addOverlaySymbol', $event)"
|
|
15
17
|
@remove="emit('removeOverlaySymbol', $event)"
|
|
16
18
|
/>
|
|
@@ -18,6 +20,10 @@
|
|
|
18
20
|
:model-value="kLineLevel"
|
|
19
21
|
@update:model-value="emit('kLineLevelChange', $event)"
|
|
20
22
|
/>
|
|
23
|
+
<KLineAdjustmentDropdown
|
|
24
|
+
:model-value="kLineAdjust"
|
|
25
|
+
@update:model-value="emit('kLineAdjustChange', $event)"
|
|
26
|
+
/>
|
|
21
27
|
<button
|
|
22
28
|
type="button"
|
|
23
29
|
class="indicator-button"
|
|
@@ -34,6 +40,7 @@
|
|
|
34
40
|
<script setup lang="ts">
|
|
35
41
|
import { computed } from 'vue'
|
|
36
42
|
import KLineLevelDropdown, { type KLineLevel } from './KLineLevelDropdown.vue'
|
|
43
|
+
import KLineAdjustmentDropdown, { type KLineAdjustment } from './KLineAdjustmentDropdown.vue'
|
|
37
44
|
import SymbolSelector from './SymbolSelector.vue'
|
|
38
45
|
import CompareSymbolSelector from './CompareSymbolSelector.vue'
|
|
39
46
|
import type { SymbolItem } from './SymbolSelector.vue'
|
|
@@ -43,40 +50,42 @@ export type { SymbolItem }
|
|
|
43
50
|
const props = defineProps<{
|
|
44
51
|
symbol?: string
|
|
45
52
|
kLineLevel?: string
|
|
53
|
+
kLineAdjust?: string
|
|
46
54
|
symbols?: SymbolItem[]
|
|
47
55
|
symbolLoading?: boolean
|
|
48
56
|
symbolError?: boolean
|
|
49
57
|
overlaySymbols?: string[]
|
|
58
|
+
comparisonColors?: Map<string, string>
|
|
59
|
+
comparisonLoading?: boolean
|
|
50
60
|
}>()
|
|
51
61
|
|
|
52
62
|
const emit = defineEmits<{
|
|
53
63
|
(e: 'addOverlaySymbol', item: SymbolItem): void
|
|
54
64
|
(e: 'removeOverlaySymbol', code: string): void
|
|
55
65
|
(e: 'kLineLevelChange', level: KLineLevel): void
|
|
66
|
+
(e: 'kLineAdjustChange', adjust: KLineAdjustment): void
|
|
56
67
|
(e: 'toggleIndicator'): void
|
|
57
68
|
(e: 'symbolChange', symbol: SymbolItem): void
|
|
58
69
|
}>()
|
|
59
70
|
|
|
60
71
|
const MOCK_SYMBOLS: SymbolItem[] = [
|
|
61
72
|
// ── TradingView 全球品种 ──
|
|
62
|
-
{ code: 'XAUUSD',
|
|
63
|
-
{ code: 'BTCUSDT', description: 'Bitcoin / Tether',
|
|
64
|
-
{ code: 'ETHUSDT', description: 'Ethereum / Tether',
|
|
65
|
-
{ code: 'EURUSD',
|
|
66
|
-
{ code: 'SPX',
|
|
67
|
-
{ code: 'AAPL',
|
|
68
|
-
{ code: 'TSLA',
|
|
69
|
-
{ code: '
|
|
70
|
-
|
|
71
|
-
{ code: '
|
|
72
|
-
|
|
73
|
-
{ code: '
|
|
74
|
-
{ code: '
|
|
75
|
-
{ code: '000858', description: '五 粮 液', exchange: 'SZSE', source: 'baostock' },
|
|
76
|
-
{ code: '000001', description: '平安银行', exchange: 'SZSE', source: 'baostock' },
|
|
73
|
+
{ code: 'XAUUSD', description: '现货黄金', exchange: 'OANDA', source: 'tradingview' },
|
|
74
|
+
{ code: 'BTCUSDT', description: 'Bitcoin / Tether', exchange: 'BINANCE', source: 'tradingview' },
|
|
75
|
+
{ code: 'ETHUSDT', description: 'Ethereum / Tether', exchange: 'BINANCE', source: 'tradingview' },
|
|
76
|
+
{ code: 'EURUSD', description: '欧元/美元', exchange: 'OANDA', source: 'tradingview' },
|
|
77
|
+
{ code: 'SPX', description: '标普 500 指数', exchange: 'SP', source: 'tradingview' },
|
|
78
|
+
{ code: 'AAPL', description: 'Apple Inc.', exchange: 'NASDAQ', source: 'tradingview' },
|
|
79
|
+
{ code: 'TSLA', description: 'Tesla, Inc.', exchange: 'NASDAQ', source: 'tradingview' },
|
|
80
|
+
{ code: '1810', description: '小米集团', exchange: 'HKEX', source: 'tradingview' },
|
|
81
|
+
// ── gotdx A 股 ──
|
|
82
|
+
{ code: '600519', description: '贵州茅台', exchange: 'SSE', source: 'gotdx' },
|
|
83
|
+
{ code: '601360', description: '三六零', exchange: 'SSE', source: 'gotdx' },
|
|
84
|
+
{ code: '000858', description: '五 粮 液', exchange: 'SZSE', source: 'gotdx' },
|
|
85
|
+
{ code: '000001', description: '平安银行', exchange: 'SZSE', source: 'gotdx' },
|
|
77
86
|
// ── Mock ──
|
|
78
|
-
{ code: 'MOCK-100',
|
|
79
|
-
{ code: 'MOCK-10000', description: 'Mock 10000 条',
|
|
87
|
+
{ code: 'MOCK-100', description: 'Mock 100 条', exchange: 'MOCK', source: 'mock-100' },
|
|
88
|
+
{ code: 'MOCK-10000', description: 'Mock 10000 条', exchange: 'MOCK', source: 'mock-10000' },
|
|
80
89
|
]
|
|
81
90
|
|
|
82
91
|
const displaySymbol = computed(() => props.symbol?.trim() ?? '')
|