@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.
- package/README.md +6 -1
- package/dist/components/BaseModal.vue.d.ts +54 -0
- package/dist/components/BaseModal.vue.d.ts.map +1 -0
- package/dist/components/BatchStockDialog.vue.d.ts +13 -0
- package/dist/components/BatchStockDialog.vue.d.ts.map +1 -0
- package/dist/components/ChartSettingsDialog.vue.d.ts.map +1 -1
- package/dist/components/ColorPresetPanel.vue.d.ts +4 -1
- package/dist/components/ColorPresetPanel.vue.d.ts.map +1 -1
- package/dist/components/CompareSymbolSelector.vue.d.ts.map +1 -1
- package/dist/components/DrawingStyleToolbar.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/ExportProgressDialog.vue.d.ts +15 -0
- package/dist/components/ExportProgressDialog.vue.d.ts.map +1 -0
- package/dist/components/IndicatorParams.vue.d.ts.map +1 -1
- package/dist/components/IndicatorSelector.vue.d.ts.map +1 -1
- package/dist/components/KLineChart.vue.d.ts +5 -9
- package/dist/components/KLineChart.vue.d.ts.map +1 -1
- package/dist/components/LeftToolbar.vue.d.ts.map +1 -1
- package/dist/components/RangeSelectionExport.vue.d.ts +23 -0
- package/dist/components/RangeSelectionExport.vue.d.ts.map +1 -0
- package/dist/components/SymbolSelector.vue.d.ts.map +1 -1
- package/dist/components/TopToolbar.vue.d.ts.map +1 -1
- package/dist/components/common/CanvasToolbar.vue.d.ts +14 -0
- package/dist/components/common/CanvasToolbar.vue.d.ts.map +1 -0
- package/dist/components/common/CanvasToolbarStack.vue.d.ts +14 -0
- package/dist/components/common/CanvasToolbarStack.vue.d.ts.map +1 -0
- package/dist/composables/chart/useChartTheme.d.ts +329 -0
- package/dist/composables/chart/useChartTheme.d.ts.map +1 -0
- package/dist/composables/chart/useDrawingManager.d.ts +86 -0
- package/dist/composables/chart/useDrawingManager.d.ts.map +1 -0
- package/dist/composables/chart/useIndicatorManager.d.ts +38 -0
- package/dist/composables/chart/useIndicatorManager.d.ts.map +1 -0
- package/dist/composables/chart/useRangeSelection.d.ts +66 -0
- package/dist/composables/chart/useRangeSelection.d.ts.map +1 -0
- package/dist/composables/useTeleportedPopup.d.ts +8 -0
- package/dist/composables/useTeleportedPopup.d.ts.map +1 -0
- package/dist/index.cjs +9 -2
- package/dist/index.css +1 -1
- package/dist/index.js +2149 -1409
- package/dist/tools/calcRangeOverlayPixel.d.ts +15 -0
- package/dist/tools/calcRangeOverlayPixel.d.ts.map +1 -0
- package/dist/tools/getKLineIndexByTimestamp.d.ts +4 -0
- package/dist/tools/getKLineIndexByTimestamp.d.ts.map +1 -0
- package/dist/web-component.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/BaseModal.vue +292 -0
- package/src/components/BatchStockDialog.vue +128 -0
- package/src/components/ChartSettingsDialog.vue +248 -405
- package/src/components/ColorPresetPanel.vue +58 -106
- package/src/components/CompareSymbolSelector.vue +37 -10
- package/src/components/DrawingStyleToolbar.vue +33 -72
- package/src/components/Dropdown.vue +42 -19
- package/src/components/ExportProgressDialog.vue +118 -0
- package/src/components/IndicatorParams.vue +194 -321
- package/src/components/IndicatorSelector.vue +188 -405
- package/src/components/KLineChart.vue +228 -403
- package/src/components/LeftToolbar.vue +3 -2
- package/src/components/RangeSelectionExport.vue +117 -0
- package/src/components/SymbolSelector.vue +37 -10
- package/src/components/TopToolbar.vue +55 -2
- package/src/components/common/CanvasToolbar.vue +70 -0
- package/src/components/common/CanvasToolbarStack.vue +32 -0
- package/src/composables/chart/useChartTheme.ts +86 -0
- package/src/composables/chart/useDrawingManager.ts +67 -0
- package/src/composables/chart/useIndicatorManager.ts +307 -0
- package/src/composables/chart/useRangeSelection.ts +424 -0
- package/src/composables/useTeleportedPopup.ts +46 -0
- package/src/tools/calcRangeOverlayPixel.ts +28 -0
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
@click="
|
|
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="
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
194
|
-
|
|
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:
|
|
336
|
+
font-size: 16px;
|
|
420
337
|
font-weight: 600;
|
|
421
338
|
color: var(--klc-color-foreground);
|
|
422
|
-
|
|
339
|
+
line-height: 1.3;
|
|
423
340
|
}
|
|
424
341
|
|
|
425
342
|
.title-sub {
|
|
426
|
-
font-size:
|
|
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-
|
|
350
|
+
background: var(--klc-color-background);
|
|
464
351
|
border: 1px solid var(--klc-color-border-button);
|
|
465
352
|
border-radius: 6px;
|
|
466
|
-
width:
|
|
467
|
-
height:
|
|
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
|
-
|
|
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:
|
|
375
|
+
padding: 8px 14px;
|
|
523
376
|
border: 1px solid var(--klc-color-border-button);
|
|
524
|
-
border-radius:
|
|
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-
|
|
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:
|
|
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:
|
|
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:
|
|
595
|
-
font-weight:
|
|
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(
|
|
611
|
-
gap:
|
|
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
|
|
520
|
+
padding: 10px 12px;
|
|
664
521
|
border: 1px solid var(--klc-color-border-chart);
|
|
665
|
-
border-radius:
|
|
666
|
-
background: var(--klc-color-
|
|
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
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
|
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>
|