@363045841yyt/klinechart 0.8.4 → 0.8.6

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.
Files changed (69) hide show
  1. package/README.md +6 -1
  2. package/dist/components/BaseModal.vue.d.ts +54 -0
  3. package/dist/components/BaseModal.vue.d.ts.map +1 -0
  4. package/dist/components/BatchStockDialog.vue.d.ts +13 -0
  5. package/dist/components/BatchStockDialog.vue.d.ts.map +1 -0
  6. package/dist/components/ChartSettingsDialog.vue.d.ts.map +1 -1
  7. package/dist/components/ColorPresetPanel.vue.d.ts +4 -1
  8. package/dist/components/ColorPresetPanel.vue.d.ts.map +1 -1
  9. package/dist/components/CompareSymbolSelector.vue.d.ts.map +1 -1
  10. package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -1
  11. package/dist/components/Dropdown.vue.d.ts.map +1 -1
  12. package/dist/components/ExportProgressDialog.vue.d.ts +15 -0
  13. package/dist/components/ExportProgressDialog.vue.d.ts.map +1 -0
  14. package/dist/components/IndicatorParams.vue.d.ts.map +1 -1
  15. package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
  16. package/dist/components/KLineChart.vue.d.ts +5 -9
  17. package/dist/components/KLineChart.vue.d.ts.map +1 -1
  18. package/dist/components/LeftToolbar.vue.d.ts.map +1 -1
  19. package/dist/components/RangeSelectionExport.vue.d.ts +23 -0
  20. package/dist/components/RangeSelectionExport.vue.d.ts.map +1 -0
  21. package/dist/components/SymbolSelector.vue.d.ts.map +1 -1
  22. package/dist/components/TopToolbar.vue.d.ts.map +1 -1
  23. package/dist/components/common/CanvasToolbar.vue.d.ts +14 -0
  24. package/dist/components/common/CanvasToolbar.vue.d.ts.map +1 -0
  25. package/dist/components/common/CanvasToolbarStack.vue.d.ts +14 -0
  26. package/dist/components/common/CanvasToolbarStack.vue.d.ts.map +1 -0
  27. package/dist/composables/chart/useChartTheme.d.ts +329 -0
  28. package/dist/composables/chart/useChartTheme.d.ts.map +1 -0
  29. package/dist/composables/chart/useDrawingManager.d.ts +86 -0
  30. package/dist/composables/chart/useDrawingManager.d.ts.map +1 -0
  31. package/dist/composables/chart/useIndicatorManager.d.ts +38 -0
  32. package/dist/composables/chart/useIndicatorManager.d.ts.map +1 -0
  33. package/dist/composables/chart/useRangeSelection.d.ts +66 -0
  34. package/dist/composables/chart/useRangeSelection.d.ts.map +1 -0
  35. package/dist/composables/useTeleportedPopup.d.ts +8 -0
  36. package/dist/composables/useTeleportedPopup.d.ts.map +1 -0
  37. package/dist/index.cjs +9 -2
  38. package/dist/index.css +1 -1
  39. package/dist/index.js +2149 -1409
  40. package/dist/tools/calcRangeOverlayPixel.d.ts +15 -0
  41. package/dist/tools/calcRangeOverlayPixel.d.ts.map +1 -0
  42. package/dist/tools/getKLineIndexByTimestamp.d.ts +4 -0
  43. package/dist/tools/getKLineIndexByTimestamp.d.ts.map +1 -0
  44. package/dist/web-component.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/src/components/BaseModal.vue +292 -0
  47. package/src/components/BatchStockDialog.vue +128 -0
  48. package/src/components/ChartSettingsDialog.vue +248 -405
  49. package/src/components/ColorPresetPanel.vue +58 -106
  50. package/src/components/CompareSymbolSelector.vue +37 -10
  51. package/src/components/DrawingStyleToolbar.vue +33 -72
  52. package/src/components/Dropdown.vue +42 -19
  53. package/src/components/ExportProgressDialog.vue +118 -0
  54. package/src/components/IndicatorParams.vue +194 -321
  55. package/src/components/IndicatorSelector.vue +188 -405
  56. package/src/components/KLineChart.vue +228 -403
  57. package/src/components/LeftToolbar.vue +3 -2
  58. package/src/components/RangeSelectionExport.vue +117 -0
  59. package/src/components/SymbolSelector.vue +37 -10
  60. package/src/components/TopToolbar.vue +55 -2
  61. package/src/components/common/CanvasToolbar.vue +70 -0
  62. package/src/components/common/CanvasToolbarStack.vue +32 -0
  63. package/src/composables/chart/useChartTheme.ts +86 -0
  64. package/src/composables/chart/useDrawingManager.ts +67 -0
  65. package/src/composables/chart/useIndicatorManager.ts +307 -0
  66. package/src/composables/chart/useRangeSelection.ts +424 -0
  67. package/src/composables/useTeleportedPopup.ts +46 -0
  68. package/src/tools/calcRangeOverlayPixel.ts +28 -0
  69. package/src/tools/getKLineIndexByTimestamp.ts +40 -0
@@ -1,199 +1,165 @@
1
1
  <template>
2
2
  <div class="indicator-selector">
3
+ <BaseModal
4
+ :show="menuOpen"
5
+ title="添加指标"
6
+ subtitle=""
7
+ width="90vw"
8
+ max-width="860px"
9
+ max-height="85vh"
10
+ transition-variant="compact"
11
+ footer-align="space-between"
12
+ @close="controller.closeMenu()"
13
+ >
14
+ <template #header>
15
+ <div class="header-title">
16
+ <span class="title-text">添加指标</span>
17
+ <span class="title-sub">{{ catalogLen }} 个可用指标</span>
18
+ </div>
19
+ </template>
20
+
21
+ <template #header-extra>
22
+ <button
23
+ class="view-toggle-btn"
24
+ :class="{ active: isCompactView }"
25
+ @click="isCompactView = !isCompactView"
26
+ title="简洁模式"
27
+ >
28
+ <svg v-if="!isCompactView" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
29
+ <path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z" />
30
+ </svg>
31
+ <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
32
+ <path
33
+ d="M3 3h18v18H3V3zm16 16V5H5v14h14zM7 7h4v4H7V7zm0 6h4v4H7v-4zm6-6h4v4h-4V7zm0 6h4v4h-4v-4z"
34
+ />
35
+ </svg>
36
+ </button>
37
+ </template>
38
+
39
+ <template #subheader>
40
+ <div class="search-box">
41
+ <svg class="search-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
42
+ <path
43
+ d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
44
+ />
45
+ </svg>
46
+ <input
47
+ :value="searchQuery"
48
+ @input="controller.setSearchQuery(($event.target as HTMLInputElement).value)"
49
+ type="text"
50
+ class="search-input"
51
+ placeholder="搜索指标名称..."
52
+ />
53
+ </div>
54
+ </template>
3
55
 
4
- <!-- 添加指标弹窗 -->
5
- <Teleport :to="teleportTarget">
6
- <Transition name="overlay">
7
- <div v-if="menuOpen" class="selector-overlay" @click="controller.closeMenu()">
8
- <Transition name="modal">
9
- <div v-if="menuOpen" class="selector-modal" @click.stop>
10
- <!-- 弹窗头部 -->
11
- <div class="modal-header">
12
- <div class="header-title">
13
- <span class="title-text">添加指标</span>
14
- <span class="title-sub">{{ catalogLen }} 个可用指标</span>
15
- </div>
16
- <div class="header-actions">
56
+ <!-- 主图指标 -->
57
+ <div v-if="filteredMain.length > 0" class="indicator-section">
58
+ <div class="section-header">
59
+ <span class="section-title">主图指标</span>
60
+ <span class="section-count">{{ filteredMain.length }}</span>
61
+ </div>
62
+ <div class="indicator-grid" :class="{ compact: isCompactView }">
63
+ <button
64
+ v-for="indicator in filteredMain"
65
+ :key="indicator.id"
66
+ class="indicator-card"
67
+ :class="{ active: isActive(indicator.id), compact: isCompactView }"
68
+ @click="
69
+ isActive(indicator.id) ? removeIndicator(indicator.id) : addIndicator(indicator.id)
70
+ "
71
+ >
72
+ <template v-if="isCompactView">
73
+ <span class="card-label">{{ indicator.label }}</span>
74
+ <span class="card-tooltip">{{ indicator.name }}</span>
75
+ </template>
76
+ <template v-else>
77
+ <div class="card-header">
78
+ <span class="card-label">{{ indicator.label }}</span>
79
+ <div class="card-header-actions">
17
80
  <button
18
- class="view-toggle-btn"
19
- :class="{ active: isCompactView }"
20
- @click="isCompactView = !isCompactView"
21
- title="简洁模式"
81
+ v-if="indicator.params?.length"
82
+ class="card-settings-btn"
83
+ @click.stop="showParams(indicator.id)"
84
+ title="编辑参数"
22
85
  >
23
- <svg
24
- v-if="!isCompactView"
25
- viewBox="0 0 24 24"
26
- width="16"
27
- height="16"
28
- fill="currentColor"
29
- >
30
- <path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z" />
31
- </svg>
32
- <svg v-else viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
86
+ <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
33
87
  <path
34
- d="M3 3h18v18H3V3zm16 16V5H5v14h14zM7 7h4v4H7V7zm0 6h4v4H7v-4zm6-6h4v4h-4V7zm0 6h4v4h-4v-4z"
88
+ 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"
35
89
  />
36
90
  </svg>
37
91
  </button>
38
- <button class="modal-close" @click="controller.closeMenu()" title="关闭">
92
+ </div>
93
+ </div>
94
+ <div class="card-name">{{ indicator.name }}</div>
95
+ </template>
96
+ </button>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- 无匹配 -->
101
+ <div v-if="!hasSearchResults && searchQuery.trim()" class="no-results">
102
+ <svg viewBox="0 0 24 24" width="40" height="40" fill="currentColor">
103
+ <path
104
+ d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
105
+ />
106
+ </svg>
107
+ <p>未找到匹配的指标</p>
108
+ <span class="no-results-hint">请尝试其他关键词</span>
109
+ </div>
110
+
111
+ <!-- 副图指标 -->
112
+ <div v-if="filteredSub.length > 0" class="indicator-section">
113
+ <div class="section-header">
114
+ <span class="section-title">副图指标</span>
115
+ <span class="section-count">{{ filteredSub.length }}</span>
116
+ </div>
117
+ <div class="indicator-grid" :class="{ compact: isCompactView }">
118
+ <button
119
+ v-for="indicator in filteredSub"
120
+ :key="indicator.id"
121
+ class="indicator-card"
122
+ :class="{ active: isActive(indicator.id), compact: isCompactView }"
123
+ @click="
124
+ isActive(indicator.id) ? removeIndicator(indicator.id) : addIndicator(indicator.id)
125
+ "
126
+ >
127
+ <template v-if="isCompactView">
128
+ <span class="card-label">{{ indicator.label }}</span>
129
+ <span class="card-tooltip">{{ indicator.name }}</span>
130
+ </template>
131
+ <template v-else>
132
+ <div class="card-header">
133
+ <span class="card-label">{{ indicator.label }}</span>
134
+ <div class="card-header-actions">
135
+ <button
136
+ v-if="indicator.params?.length"
137
+ class="card-settings-btn"
138
+ @click.stop="showParams(indicator.id)"
139
+ title="编辑参数"
140
+ >
39
141
  <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
40
142
  <path
41
- 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"
143
+ 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"
42
144
  />
43
145
  </svg>
44
146
  </button>
45
147
  </div>
46
148
  </div>
149
+ <div class="card-name">{{ indicator.name }}</div>
150
+ </template>
151
+ </button>
152
+ </div>
153
+ </div>
47
154
 
48
- <!-- 搜索区域 -->
49
- <div class="modal-search-area">
50
- <div class="search-box">
51
- <svg
52
- class="search-icon"
53
- viewBox="0 0 24 24"
54
- width="16"
55
- height="16"
56
- fill="currentColor"
57
- >
58
- <path
59
- d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
60
- />
61
- </svg>
62
- <input
63
- :value="searchQuery" @input="controller.setSearchQuery(($event.target as HTMLInputElement).value)"
64
- type="text"
65
- class="search-input"
66
- placeholder="搜索指标名称..."
67
- />
68
- </div>
69
- </div>
70
-
71
- <!-- 弹窗主体 -->
72
- <div class="modal-body">
73
- <!-- 主图指标区域 -->
74
- <div v-if="filteredMain.length > 0" class="indicator-section">
75
- <div class="section-header">
76
- <span class="section-title">主图指标</span>
77
- <span class="section-count">{{ filteredMain.length }}</span>
78
- </div>
79
- <div class="indicator-grid" :class="{ compact: isCompactView }">
80
- <button
81
- v-for="indicator in filteredMain"
82
- :key="indicator.id"
83
- class="indicator-card"
84
- :class="{ active: isActive(indicator.id), compact: isCompactView }"
85
- @click="
86
- isActive(indicator.id)
87
- ? removeIndicator(indicator.id)
88
- : addIndicator(indicator.id)
89
- "
90
- >
91
- <template v-if="isCompactView">
92
- <span class="card-label">{{ indicator.label }}</span>
93
- <span class="card-tooltip">{{ indicator.name }}</span>
94
- </template>
95
- <template v-else>
96
- <div class="card-header">
97
- <span class="card-label">{{ indicator.label }}</span>
98
- <div class="card-header-actions">
99
- <button
100
- v-if="indicator.params?.length"
101
- class="card-settings-btn"
102
- @click.stop="showParams(indicator.id)"
103
- title="编辑参数"
104
- >
105
- <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
106
- <path
107
- 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"
108
- />
109
- </svg>
110
- </button>
111
- </div>
112
- </div>
113
- <div class="card-name">{{ indicator.name }}</div>
114
- </template>
115
- </button>
116
- </div>
117
- </div>
118
-
119
- <!-- 分隔线 -->
120
- <div
121
- v-if="filteredMain.length > 0 && filteredSub.length > 0"
122
- class="section-divider"
123
- ></div>
124
-
125
- <!-- 无匹配结果提示 -->
126
- <div v-if="!hasSearchResults && searchQuery.trim()" class="no-results">
127
- <svg viewBox="0 0 24 24" width="48" height="48" fill="currentColor">
128
- <path
129
- d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"
130
- />
131
- </svg>
132
- <p>未找到匹配的指标</p>
133
- <span class="no-results-hint">请尝试其他关键词</span>
134
- </div>
135
-
136
- <!-- 副图指标区域 -->
137
- <div v-if="filteredSub.length > 0" class="indicator-section">
138
- <div class="section-header">
139
- <span class="section-title">副图指标</span>
140
- <span class="section-count">{{ filteredSub.length }}</span>
141
- </div>
142
- <div class="indicator-grid" :class="{ compact: isCompactView }">
143
- <button
144
- v-for="indicator in filteredSub"
145
- :key="indicator.id"
146
- class="indicator-card"
147
- :class="{ active: isActive(indicator.id), compact: isCompactView }"
148
- @click="
149
- isActive(indicator.id)
150
- ? removeIndicator(indicator.id)
151
- : addIndicator(indicator.id)
152
- "
153
- >
154
- <template v-if="isCompactView">
155
- <span class="card-label">{{ indicator.label }}</span>
156
- <span class="card-tooltip">{{ indicator.name }}</span>
157
- </template>
158
- <template v-else>
159
- <div class="card-header">
160
- <span class="card-label">{{ indicator.label }}</span>
161
- <div class="card-header-actions">
162
- <button
163
- v-if="indicator.params?.length"
164
- class="card-settings-btn"
165
- @click.stop="showParams(indicator.id)"
166
- title="编辑参数"
167
- >
168
- <svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor">
169
- <path
170
- 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"
171
- />
172
- </svg>
173
- </button>
174
- </div>
175
- </div>
176
- <div class="card-name">{{ indicator.name }}</div>
177
- </template>
178
- </button>
179
- </div>
180
- </div>
181
- </div>
182
-
183
- <!-- 弹窗底部 -->
184
- <div class="modal-footer">
185
- <div class="footer-info">
186
- <span class="info-text">已激活 {{ activeCount }} 个指标</span>
187
- </div>
188
- <button class="btn btn-confirm" @click="controller.closeMenu()">确认</button>
189
- </div>
190
- </div>
191
- </Transition>
155
+ <template #footer>
156
+ <div class="footer-info">
157
+ <span class="info-text">已激活 {{ activeCount }} 个指标</span>
192
158
  </div>
193
- </Transition>
194
- </Teleport>
159
+ <button class="btn btn-confirm" @click="controller.closeMenu()">确认</button>
160
+ </template>
161
+ </BaseModal>
195
162
 
196
- <!-- 参数编辑弹窗 -->
197
163
  <IndicatorParams
198
164
  v-if="currentIndicator"
199
165
  :visible="paramsVisible"
@@ -210,8 +176,8 @@
210
176
 
211
177
  <script setup lang="ts">
212
178
  import { ref, computed, onMounted, onUnmounted } from 'vue'
179
+ import BaseModal from './BaseModal.vue'
213
180
  import IndicatorParams from './IndicatorParams.vue'
214
- import { useFullscreenTeleportTarget } from '../composables/useFullscreenTeleportTarget'
215
181
  import { coreSignalToVueRef } from '../index'
216
182
  import {
217
183
  createIndicatorSelectorController,
@@ -234,7 +200,6 @@ const emit = defineEmits<{
234
200
  reorderSubIndicators: [orderedIndicatorIds: string[]]
235
201
  }>()
236
202
 
237
- // ── 将 Indicator[] 转换为 IndicatorDefinition[] ──
238
203
  function toIndicatorDefinitions(source: Indicator[]): IndicatorDefinition[] {
239
204
  return source.map((i) => ({
240
205
  id: i.id,
@@ -254,10 +219,8 @@ function toIndicatorDefinitions(source: Indicator[]): IndicatorDefinition[] {
254
219
  }))
255
220
  }
256
221
 
257
- // ── Controller ──
258
222
  const controller = createIndicatorSelectorController()
259
223
 
260
- // ── 从 Controller Signal 桥接的 Vue 响应式状态 ──
261
224
  const menuOpen = coreSignalToVueRef(controller.menuOpen)
262
225
  const searchQuery = coreSignalToVueRef(controller.searchQuery)
263
226
  const filteredMain = coreSignalToVueRef(controller.filteredMain)
@@ -277,14 +240,10 @@ onMounted(async () => {
277
240
  controller.catalog.set(toIndicatorDefinitions(allIndicators()))
278
241
  })
279
242
 
280
- // ── 本地 UI 状态(非 Controller 管理的纯 UI 状态) ──
281
243
  const paramsVisible = ref(false)
282
244
  const currentIndicatorId = ref<string | null>(null)
283
245
  const isCompactView = ref(false)
284
246
 
285
- // Teleport target for fullscreen modal visibility
286
- const teleportTarget = useFullscreenTeleportTarget()
287
-
288
247
  const currentIndicator = computed(() => {
289
248
  if (!currentIndicatorId.value) return null
290
249
  return findIndicator(currentIndicatorId.value)
@@ -298,10 +257,8 @@ function isActive(indicatorId: string): boolean {
298
257
 
299
258
  function addIndicator(indicatorId: string) {
300
259
  if (isActive(indicatorId)) return
301
-
302
260
  const indicator = findIndicator(indicatorId)
303
261
  if (!indicator) return
304
-
305
262
  emit('toggle', indicatorId, true)
306
263
  }
307
264
 
@@ -368,47 +325,7 @@ defineExpose({
368
325
  display: none;
369
326
  }
370
327
 
371
- /* ─────────────────────────────────────────────────────────────────
372
- 弹窗样式 - 与其他弹窗保持一致
373
- ───────────────────────────────────────────────────────────────── */
374
-
375
- /* 遮罩层 */
376
- .selector-overlay {
377
- position: fixed;
378
- inset: 0;
379
- background: rgba(0, 0, 0, 0.3);
380
- backdrop-filter: blur(4px);
381
- display: flex;
382
- align-items: center;
383
- justify-content: center;
384
- z-index: 1000;
385
- }
386
-
387
- /* 弹窗容器 */
388
- .selector-modal {
389
- background: var(--klc-color-tag-bg-white);
390
- border: 1px solid var(--klc-color-axis-line);
391
- border-radius: 12px;
392
- box-shadow: 0 8px 40px rgba(0, 0, 0, 0.15);
393
- width: 90vw;
394
- max-width: 860px;
395
- max-height: 85vh;
396
- overflow: hidden;
397
- display: flex;
398
- flex-direction: column;
399
- }
400
-
401
- /* 弹窗头部 */
402
- .modal-header {
403
- display: flex;
404
- justify-content: space-between;
405
- align-items: center;
406
- padding: 16px 20px;
407
- background: var(--klc-color-background);
408
- border-bottom: 1px solid var(--klc-color-border-chart);
409
- flex-shrink: 0;
410
- }
411
-
328
+ /* ── 头部 ── */
412
329
  .header-title {
413
330
  display: flex;
414
331
  flex-direction: column;
@@ -416,61 +333,31 @@ defineExpose({
416
333
  }
417
334
 
418
335
  .title-text {
419
- font-size: 14px;
336
+ font-size: 16px;
420
337
  font-weight: 600;
421
338
  color: var(--klc-color-foreground);
422
- letter-spacing: 0.2px;
339
+ line-height: 1.3;
423
340
  }
424
341
 
425
342
  .title-sub {
426
- font-size: 11px;
343
+ font-size: 12px;
427
344
  color: var(--klc-color-axis-text);
428
- }
429
-
430
- .modal-close {
431
- background: var(--klc-color-tag-bg-white);
432
- border: 1px solid var(--klc-color-border-button);
433
- border-radius: 6px;
434
- width: 28px;
435
- height: 28px;
436
- display: flex;
437
- align-items: center;
438
- justify-content: center;
439
- cursor: pointer;
440
- color: var(--klc-color-axis-text);
441
- transition: all 0.15s;
442
- padding: 0;
443
- }
444
-
445
- .modal-close:hover {
446
- background: var(--klc-color-tag-bg-hover);
447
- color: var(--klc-color-foreground);
448
- border-color: var(--klc-color-axis-line);
449
- }
450
-
451
- .modal-close svg {
452
- width: 14px;
453
- height: 14px;
454
- }
455
-
456
- .header-actions {
457
- display: flex;
458
- align-items: center;
459
- gap: 8px;
345
+ font-weight: 400;
346
+ line-height: 1.3;
460
347
  }
461
348
 
462
349
  .view-toggle-btn {
463
- background: var(--klc-color-tag-bg-white);
350
+ background: var(--klc-color-background);
464
351
  border: 1px solid var(--klc-color-border-button);
465
352
  border-radius: 6px;
466
- width: 28px;
467
- height: 28px;
353
+ width: 34px;
354
+ height: 34px;
468
355
  display: flex;
469
356
  align-items: center;
470
357
  justify-content: center;
471
358
  cursor: pointer;
472
359
  color: var(--klc-color-axis-text);
473
- transition: all 0.15s;
360
+ transition: all 0.15s ease;
474
361
  padding: 0;
475
362
  }
476
363
 
@@ -480,54 +367,20 @@ defineExpose({
480
367
  border-color: var(--klc-color-axis-line);
481
368
  }
482
369
 
483
- .view-toggle-btn.active {
484
- background: var(--klc-color-tag-bg-white);
485
- border-color: var(--klc-color-border-button);
486
- color: var(--klc-color-foreground);
487
- }
488
-
489
- /* 搜索区域 */
490
- .modal-search-area {
491
- padding: 16px 20px;
492
- background: var(--klc-color-tag-bg-white);
493
- border-bottom: 1px solid var(--klc-color-border-chart);
494
- flex-shrink: 0;
495
- }
496
-
497
- /* 弹窗主体 */
498
- .modal-body {
499
- padding: 20px;
500
- overflow-y: auto;
501
- flex: 1;
502
- display: flex;
503
- flex-direction: column;
504
- gap: 20px;
505
- }
506
-
507
- .modal-body::-webkit-scrollbar {
508
- width: 8px;
509
- }
510
-
511
- .modal-body::-webkit-scrollbar-thumb {
512
- background: var(--klc-color-axis-line);
513
- border: 2px solid var(--klc-color-tag-bg-white);
514
- border-radius: 999px;
515
- }
516
-
517
- /* 搜索框 */
370
+ /* ── 搜索 ── */
518
371
  .search-box {
519
372
  display: flex;
520
373
  align-items: center;
521
374
  gap: 10px;
522
- padding: 10px 14px;
375
+ padding: 8px 14px;
523
376
  border: 1px solid var(--klc-color-border-button);
524
- border-radius: 8px;
377
+ border-radius: 6px;
525
378
  background: var(--klc-color-background);
526
379
  transition: all 0.2s ease;
527
380
  }
528
381
 
529
382
  .search-box:focus-within {
530
- background: var(--klc-color-tag-bg-white);
383
+ background: var(--klc-color-background);
531
384
  border-color: var(--klc-color-foreground);
532
385
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--klc-color-foreground) 8%, transparent);
533
386
  }
@@ -550,7 +403,7 @@ defineExpose({
550
403
  color: var(--klc-color-axis-text);
551
404
  }
552
405
 
553
- /* 无结果提示 */
406
+ /* ── 无匹配 ── */
554
407
  .no-results {
555
408
  display: flex;
556
409
  flex-direction: column;
@@ -558,7 +411,7 @@ defineExpose({
558
411
  justify-content: center;
559
412
  padding: 48px 20px;
560
413
  color: var(--klc-color-axis-text);
561
- gap: 12px;
414
+ gap: 10px;
562
415
  }
563
416
 
564
417
  .no-results svg {
@@ -577,23 +430,30 @@ defineExpose({
577
430
  color: var(--klc-color-axis-text);
578
431
  }
579
432
 
580
- /* 指标区域 */
433
+ /* ── 指标区域 ── */
581
434
  .indicator-section {
582
435
  display: flex;
583
436
  flex-direction: column;
584
- gap: 12px;
437
+ gap: 10px;
438
+ }
439
+
440
+ .indicator-section + .indicator-section {
441
+ margin-top: 18px;
585
442
  }
586
443
 
587
444
  .section-header {
588
445
  display: flex;
589
446
  align-items: center;
590
447
  gap: 8px;
448
+ margin-top: 6px;
591
449
  }
592
450
 
593
451
  .section-title {
594
- font-size: 13px;
595
- font-weight: 600;
452
+ font-size: 12px;
453
+ font-weight: 500;
596
454
  color: var(--klc-color-foreground);
455
+ letter-spacing: 0.3px;
456
+ line-height: 1;
597
457
  }
598
458
 
599
459
  .section-count {
@@ -604,14 +464,12 @@ defineExpose({
604
464
  border-radius: 10px;
605
465
  }
606
466
 
607
- /* 自适应列数网格 */
608
467
  .indicator-grid {
609
468
  display: grid;
610
- grid-template-columns: repeat(auto-fill, minmax(195px, 1fr));
611
- gap: 10px;
469
+ grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
470
+ gap: 8px;
612
471
  }
613
472
 
614
- /* 紧凑模式 - 标签形式 */
615
473
  .indicator-grid.compact {
616
474
  display: flex;
617
475
  flex-wrap: wrap;
@@ -655,17 +513,16 @@ defineExpose({
655
513
  font-weight: 500;
656
514
  }
657
515
 
658
- /* 指标卡片 */
659
516
  .indicator-card {
660
517
  display: flex;
661
518
  flex-direction: column;
662
519
  gap: 4px;
663
- padding: 12px 14px;
520
+ padding: 10px 12px;
664
521
  border: 1px solid var(--klc-color-border-chart);
665
- border-radius: 8px;
666
- background: var(--klc-color-tag-bg-white);
522
+ border-radius: 6px;
523
+ background: var(--klc-color-background);
667
524
  cursor: pointer;
668
- transition: all 0.15s;
525
+ transition: all 0.15s ease;
669
526
  text-align: left;
670
527
  }
671
528
 
@@ -726,52 +583,28 @@ defineExpose({
726
583
  line-height: 1.4;
727
584
  }
728
585
 
729
- .card-params {
730
- font-size: 10px;
731
- color: var(--klc-color-axis-text);
732
- margin-top: 2px;
733
- }
734
-
735
- /* 区域分隔线 */
736
- .section-divider {
737
- height: 1px;
738
- background: linear-gradient(90deg, transparent, var(--klc-color-border-button), transparent);
739
- margin: 4px 0;
740
- }
741
-
742
- /* 弹窗底部 */
743
- .modal-footer {
744
- display: flex;
745
- align-items: center;
746
- justify-content: space-between;
747
- padding: 12px 20px;
748
- background: var(--klc-color-background);
749
- border-top: 1px solid var(--klc-color-border-chart);
750
- flex-shrink: 0;
751
- }
752
-
586
+ /* ── 底部 ── */
753
587
  .footer-info {
754
588
  font-size: 12px;
755
589
  color: var(--klc-color-axis-text);
756
590
  }
757
591
 
758
- .info-text {
759
- color: var(--klc-color-axis-text);
760
- }
761
-
762
- /* 按钮样式 */
763
592
  .btn {
764
- display: flex;
593
+ display: inline-flex;
765
594
  align-items: center;
766
- gap: 5px;
767
- padding: 6px 16px;
768
- border-radius: 7px;
595
+ justify-content: center;
596
+ gap: 6px;
597
+ min-width: 68px;
598
+ height: 34px;
599
+ padding: 0 16px;
600
+ border-radius: 6px;
769
601
  font-size: 13px;
770
602
  font-weight: 500;
771
603
  cursor: pointer;
772
604
  border: 1px solid transparent;
773
605
  transition: all 0.15s;
774
- line-height: 1.4;
606
+ line-height: 1;
607
+ white-space: nowrap;
775
608
  }
776
609
 
777
610
  .btn-confirm {
@@ -787,60 +620,10 @@ defineExpose({
787
620
  transform: translateY(-1px);
788
621
  }
789
622
 
790
- /* 过渡动画 */
791
- .fade-enter-active,
792
- .fade-leave-active {
793
- transition: opacity 0.2s ease;
794
- }
795
-
796
- .fade-enter-from,
797
- .fade-leave-to {
798
- opacity: 0;
799
- }
800
-
801
- /* 遮罩层动画 */
802
- .overlay-enter-active,
803
- .overlay-leave-active {
804
- transition: opacity 0.2s ease;
805
- }
806
-
807
- .overlay-enter-from,
808
- .overlay-leave-to {
809
- opacity: 0;
810
- }
811
-
812
- /* 弹窗动画 */
813
- .modal-enter-active {
814
- transition: all 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
815
- }
816
-
817
- .modal-leave-active {
818
- transition: all 0.16s ease-in;
819
- }
820
-
821
- .modal-enter-from {
822
- opacity: 0;
823
- transform: scale(0.88) translateY(-16px);
824
- }
825
-
826
- .modal-leave-to {
827
- opacity: 0;
828
- transform: scale(0.94) translateY(8px);
829
- }
830
-
831
- /* 响应式适配 */
623
+ /* ── 响应式 ── */
832
624
  @media (max-width: 640px) {
833
- .selector-modal {
834
- width: 95vw;
835
- max-height: 90vh;
836
- }
837
-
838
625
  .indicator-grid {
839
626
  grid-template-columns: 1fr;
840
627
  }
841
-
842
- .modal-body {
843
- padding: 16px;
844
- }
845
628
  }
846
629
  </style>