@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.
@@ -1,6 +1,6 @@
1
1
  import { SemanticChartConfig, DataFetcher } from '@363045841yyt/klinechart-core/semantic';
2
2
  declare const KLineChartElement: import('vue').VueElementConstructor<{
3
- semanticConfig: SemanticChartConfig;
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;;;;;;;;;;;;EAErB,CAAA;AAIF,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,eAAe,iBAAiB,CAAA;AAEhC,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,CAAA"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@363045841yyt/klinechart",
3
- "version": "0.8.1-alpha.4",
3
+ "version": "0.8.2",
4
4
  "description": "Vue 3 bindings for @363045841yyt/klinechart-core. Idiomatic composables, SFC components.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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: SemanticChartConfig
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.data.period)
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
- syncSymbolsToController()
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
- syncSymbolsToController()
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.data.startDate,
254
- endDate: props.semanticConfig.data.endDate,
255
- adjust: props.semanticConfig.data.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>{{ formatDate(k.timestamp) }}</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="symbol"
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">{{ symbol }}</span>
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', description: '现货黄金', exchange: 'OANDA', source: 'tradingview' },
63
- { code: 'BTCUSDT', description: 'Bitcoin / Tether', exchange: 'BINANCE', source: 'tradingview' },
64
- { code: 'ETHUSDT', description: 'Ethereum / Tether', exchange: 'BINANCE', source: 'tradingview' },
65
- { code: 'EURUSD', description: '欧元/美元', exchange: 'OANDA', source: 'tradingview' },
66
- { code: 'SPX', description: '标普 500 指数', exchange: 'SP', source: 'tradingview' },
67
- { code: 'AAPL', description: 'Apple Inc.', exchange: 'NASDAQ', source: 'tradingview' },
68
- { code: 'TSLA', description: 'Tesla, Inc.', exchange: 'NASDAQ', source: 'tradingview' },
69
- { code: '600519', description: '贵州茅台', exchange: 'SSE', source: 'tradingview' },
70
- { code: '000001', description: '平安银行', exchange: 'SZSE', source: 'tradingview' },
71
- { code: '1810', description: '小米集团', exchange: 'HKEX', source: 'tradingview' },
72
- // ── Baostock A ──
73
- { code: 'sh.600519', description: '贵州茅台', exchange: 'SSE', source: 'baostock' },
74
- { code: 'sh.601360', description: '三六零', exchange: 'SSE', source: 'baostock' },
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', description: 'Mock 100 条', exchange: 'MOCK', source: 'mock-100' },
79
- { code: 'MOCK-10000', description: 'Mock 10000 条', exchange: 'MOCK', source: '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() ?? '')