@363045841yyt/klinechart 0.7.4 → 0.7.5-alpha.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,200 +1,200 @@
1
- <template>
2
- <div
3
- v-if="k"
4
- :ref="onRef"
5
- class="kline-tooltip"
6
- :class="[{ 'use-anchor': useAnchor }, anchorPlacementClass]"
7
- :style="useAnchor ? undefined : { left: `${pos.x}px`, top: `${pos.y}px` }"
8
- >
9
- <div class="kline-tooltip__title">
10
- <span v-if="k.stockCode">{{ k.stockCode }}</span>
11
- <span>{{ formatDate(k.timestamp) }}</span>
12
- </div>
13
- <div class="kline-tooltip__grid">
14
- <div class="row">
15
- <span>开</span><span :style="{ color: openColor }">{{ k.open.toFixed(2) }}</span>
16
- </div>
17
- <div class="row">
18
- <span>高</span><span>{{ k.high.toFixed(2) }}</span>
19
- </div>
20
- <div class="row">
21
- <span>低</span><span>{{ k.low.toFixed(2) }}</span>
22
- </div>
23
- <div class="row">
24
- <span>收</span><span :style="{ color: closeColor }">{{ k.close.toFixed(2) }}</span>
25
- </div>
26
-
27
- <div v-if="typeof k.volume === 'number'" class="row">
28
- <span>成交量</span><span>{{ formatVolume(k.volume) }}</span>
29
- </div>
30
- <div v-if="typeof k.turnover === 'number'" class="row">
31
- <span>成交额</span><span>{{ formatVolume(k.turnover) }}</span>
32
- </div>
33
- <div v-if="typeof k.amplitude === 'number'" class="row">
34
- <span>振幅</span><span>{{ k.amplitude }}%</span>
35
- </div>
36
- <div v-if="typeof k.changePercent === 'number'" class="row">
37
- <span>涨跌幅</span>
38
- <span :style="{ color: changeColor }">{{ formatSigned(k.changePercent, '%') }}</span>
39
- </div>
40
- <div v-if="typeof k.changeAmount === 'number'" class="row">
41
- <span>涨跌额</span>
42
- <span :style="{ color: changeColor }">{{ formatSigned(k.changeAmount, '') }}</span>
43
- </div>
44
- <div v-if="typeof k.turnoverRate === 'number'" class="row">
45
- <span>换手率</span><span>{{ k.turnoverRate.toFixed(2) }}%</span>
46
- </div>
47
- </div>
48
- </div>
49
- </template>
50
-
51
- <script setup lang="ts">
52
- import { computed } from 'vue'
53
- import type { ComponentPublicInstance } from 'vue'
54
-
55
- export interface KLineData {
56
- timestamp: number
57
- open: number
58
- high: number
59
- low: number
60
- close: number
61
- volume?: number
62
- turnover?: number
63
- amplitude?: number
64
- changePercent?: number
65
- changeAmount?: number
66
- turnoverRate?: number
67
- stockCode?: string
68
- }
69
-
70
- const props = defineProps<{
71
- k: KLineData | null
72
- index: number | null
73
- data: KLineData[]
74
- pos: { x: number; y: number }
75
- useAnchor?: boolean
76
- anchorPlacement?: 'right-bottom' | 'left-bottom'
77
- setEl?: (el: HTMLDivElement | null) => void
78
- }>()
79
-
80
- const useAnchor = computed(() => props.useAnchor === true)
81
- const anchorPlacementClass = computed(() =>
82
- props.anchorPlacement === 'left-bottom' ? 'anchor-left-bottom' : 'anchor-right-bottom',
83
- )
84
-
85
- function onRef(el: Element | ComponentPublicInstance | null) {
86
- props.setEl?.(el as HTMLDivElement | null)
87
- }
88
-
89
- function formatDate(ts: number): string {
90
- const d = new Date(ts)
91
- const y = d.getFullYear()
92
- const m = String(d.getMonth() + 1).padStart(2, '0')
93
- const day = String(d.getDate()).padStart(2, '0')
94
- return `${y}-${m}-${day}`
95
- }
96
-
97
- function formatVolume(v: number): string {
98
- if (v >= 1e8) return (v / 1e8).toFixed(2) + '亿'
99
- if (v >= 1e4) return (v / 1e4).toFixed(2) + '万'
100
- return v.toFixed(2)
101
- }
102
-
103
- function formatSigned(val: number, unit: string): string {
104
- const sign = val >= 0 ? '+' : ''
105
- return `${sign}${val.toFixed(2)}${unit}`
106
- }
107
-
108
- const UP_COLOR = '#ef4444'
109
- const DOWN_COLOR = '#22c55e'
110
- const NEUTRAL_COLOR = '#6b7280'
111
-
112
- function calcDirection(k: KLineData, data: KLineData[], idx: number | null): number {
113
- if (k.close >= k.open) return 1
114
- const prev = typeof idx === 'number' && idx > 0 ? data[idx - 1] : undefined
115
- if (prev && k.close > prev.close) return 1
116
- if (prev && k.close < prev.close) return -1
117
- return 0
118
- }
119
-
120
- const openColor = computed(() => {
121
- const k = props.k
122
- if (!k) return NEUTRAL_COLOR
123
- const dir = calcDirection(k, props.data, props.index)
124
- return dir > 0 ? UP_COLOR : dir < 0 ? DOWN_COLOR : NEUTRAL_COLOR
125
- })
126
-
127
- const closeColor = computed(() => {
128
- const k = props.k
129
- if (!k) return NEUTRAL_COLOR
130
- const diff = k.close - k.open
131
- return diff > 0 ? UP_COLOR : diff < 0 ? DOWN_COLOR : NEUTRAL_COLOR
132
- })
133
-
134
- const changeColor = computed(() => {
135
- const k = props.k
136
- if (!k) return NEUTRAL_COLOR
137
- const pct = k.changePercent ?? (k.close - k.open) / k.open * 100
138
- return pct > 0 ? UP_COLOR : pct < 0 ? DOWN_COLOR : NEUTRAL_COLOR
139
- })
140
- </script>
141
-
142
- <style scoped>
143
- .kline-tooltip {
144
- position: absolute;
145
- z-index: 10;
146
- min-width: 200px;
147
- max-width: 260px;
148
- padding: 10px 12px;
149
- border-radius: 8px;
150
- background: rgba(255, 255, 255, 0.92);
151
- border: 1px solid rgba(0, 0, 0, 0.12);
152
- box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
153
- color: rgba(0, 0, 0, 0.78);
154
- font-size: 12px;
155
- line-height: 1.4;
156
- pointer-events: none;
157
- backdrop-filter: blur(6px);
158
- }
159
-
160
- .kline-tooltip__title {
161
- display: flex;
162
- justify-content: space-between;
163
- gap: 10px;
164
- font-weight: 600;
165
- margin-bottom: 6px;
166
- }
167
-
168
- .kline-tooltip__grid {
169
- display: grid;
170
- grid-template-columns: 1fr;
171
- gap: 2px;
172
- }
173
-
174
- .kline-tooltip__grid .row {
175
- display: flex;
176
- justify-content: space-between;
177
- gap: 10px;
178
- }
179
-
180
- .kline-tooltip__grid .row span:first-child {
181
- color: rgba(0, 0, 0, 0.56);
182
- }
183
-
184
- @supports (anchor-name: --kmap-anchor) and (position-anchor: --kmap-anchor) {
185
- .kline-tooltip.use-anchor {
186
- position: absolute;
187
- position-anchor: --kline-tooltip-anchor;
188
- left: anchor(left);
189
- top: anchor(top);
190
- }
191
-
192
- .kline-tooltip.use-anchor.anchor-right-bottom {
193
- transform: translate(14px, 14px);
194
- }
195
-
196
- .kline-tooltip.use-anchor.anchor-left-bottom {
197
- transform: translate(calc(-100% - 14px), 14px);
198
- }
199
- }
200
- </style>
1
+ <template>
2
+ <div
3
+ v-if="k"
4
+ :ref="onRef"
5
+ class="kline-tooltip"
6
+ :class="[{ 'use-anchor': useAnchor }, anchorPlacementClass]"
7
+ :style="useAnchor ? undefined : { left: `${pos.x}px`, top: `${pos.y}px` }"
8
+ >
9
+ <div class="kline-tooltip__title">
10
+ <span v-if="k.stockCode">{{ k.stockCode }}</span>
11
+ <span>{{ formatDate(k.timestamp) }}</span>
12
+ </div>
13
+ <div class="kline-tooltip__grid">
14
+ <div class="row">
15
+ <span>开</span><span :style="{ color: openColor }">{{ k.open.toFixed(2) }}</span>
16
+ </div>
17
+ <div class="row">
18
+ <span>高</span><span>{{ k.high.toFixed(2) }}</span>
19
+ </div>
20
+ <div class="row">
21
+ <span>低</span><span>{{ k.low.toFixed(2) }}</span>
22
+ </div>
23
+ <div class="row">
24
+ <span>收</span><span :style="{ color: closeColor }">{{ k.close.toFixed(2) }}</span>
25
+ </div>
26
+
27
+ <div v-if="typeof k.volume === 'number'" class="row">
28
+ <span>成交量</span><span>{{ formatVolume(k.volume) }}</span>
29
+ </div>
30
+ <div v-if="typeof k.turnover === 'number'" class="row">
31
+ <span>成交额</span><span>{{ formatVolume(k.turnover) }}</span>
32
+ </div>
33
+ <div v-if="typeof k.amplitude === 'number'" class="row">
34
+ <span>振幅</span><span>{{ k.amplitude }}%</span>
35
+ </div>
36
+ <div v-if="typeof k.changePercent === 'number'" class="row">
37
+ <span>涨跌幅</span>
38
+ <span :style="{ color: changeColor }">{{ formatSigned(k.changePercent, '%') }}</span>
39
+ </div>
40
+ <div v-if="typeof k.changeAmount === 'number'" class="row">
41
+ <span>涨跌额</span>
42
+ <span :style="{ color: changeColor }">{{ formatSigned(k.changeAmount, '') }}</span>
43
+ </div>
44
+ <div v-if="typeof k.turnoverRate === 'number'" class="row">
45
+ <span>换手率</span><span>{{ k.turnoverRate.toFixed(2) }}%</span>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ import { computed } from 'vue'
53
+ import type { ComponentPublicInstance } from 'vue'
54
+
55
+ export interface KLineData {
56
+ timestamp: number
57
+ open: number
58
+ high: number
59
+ low: number
60
+ close: number
61
+ volume?: number
62
+ turnover?: number
63
+ amplitude?: number
64
+ changePercent?: number
65
+ changeAmount?: number
66
+ turnoverRate?: number
67
+ stockCode?: string
68
+ }
69
+
70
+ const props = defineProps<{
71
+ k: KLineData | null
72
+ index: number | null
73
+ data: KLineData[]
74
+ pos: { x: number; y: number }
75
+ useAnchor?: boolean
76
+ anchorPlacement?: 'right-bottom' | 'left-bottom'
77
+ setEl?: (el: HTMLDivElement | null) => void
78
+ }>()
79
+
80
+ const useAnchor = computed(() => props.useAnchor === true)
81
+ const anchorPlacementClass = computed(() =>
82
+ props.anchorPlacement === 'left-bottom' ? 'anchor-left-bottom' : 'anchor-right-bottom',
83
+ )
84
+
85
+ function onRef(el: Element | ComponentPublicInstance | null) {
86
+ props.setEl?.(el as HTMLDivElement | null)
87
+ }
88
+
89
+ function formatDate(ts: number): string {
90
+ const d = new Date(ts)
91
+ const y = d.getFullYear()
92
+ const m = String(d.getMonth() + 1).padStart(2, '0')
93
+ const day = String(d.getDate()).padStart(2, '0')
94
+ return `${y}-${m}-${day}`
95
+ }
96
+
97
+ function formatVolume(v: number): string {
98
+ if (v >= 1e8) return (v / 1e8).toFixed(2) + '亿'
99
+ if (v >= 1e4) return (v / 1e4).toFixed(2) + '万'
100
+ return v.toFixed(2)
101
+ }
102
+
103
+ function formatSigned(val: number, unit: string): string {
104
+ const sign = val >= 0 ? '+' : ''
105
+ return `${sign}${val.toFixed(2)}${unit}`
106
+ }
107
+
108
+ const UP_COLOR = '#ef4444'
109
+ const DOWN_COLOR = '#22c55e'
110
+ const NEUTRAL_COLOR = '#6b7280'
111
+
112
+ function calcDirection(k: KLineData, data: KLineData[], idx: number | null): number {
113
+ if (k.close >= k.open) return 1
114
+ const prev = typeof idx === 'number' && idx > 0 ? data[idx - 1] : undefined
115
+ if (prev && k.close > prev.close) return 1
116
+ if (prev && k.close < prev.close) return -1
117
+ return 0
118
+ }
119
+
120
+ const openColor = computed(() => {
121
+ const k = props.k
122
+ if (!k) return NEUTRAL_COLOR
123
+ const dir = calcDirection(k, props.data, props.index)
124
+ return dir > 0 ? UP_COLOR : dir < 0 ? DOWN_COLOR : NEUTRAL_COLOR
125
+ })
126
+
127
+ const closeColor = computed(() => {
128
+ const k = props.k
129
+ if (!k) return NEUTRAL_COLOR
130
+ const diff = k.close - k.open
131
+ return diff > 0 ? UP_COLOR : diff < 0 ? DOWN_COLOR : NEUTRAL_COLOR
132
+ })
133
+
134
+ const changeColor = computed(() => {
135
+ const k = props.k
136
+ if (!k) return NEUTRAL_COLOR
137
+ const pct = k.changePercent ?? (k.close - k.open) / k.open * 100
138
+ return pct > 0 ? UP_COLOR : pct < 0 ? DOWN_COLOR : NEUTRAL_COLOR
139
+ })
140
+ </script>
141
+
142
+ <style scoped>
143
+ .kline-tooltip {
144
+ position: absolute;
145
+ z-index: 10;
146
+ min-width: 200px;
147
+ max-width: 260px;
148
+ padding: 10px 12px;
149
+ border-radius: 8px;
150
+ background: rgba(255, 255, 255, 0.92);
151
+ border: 1px solid rgba(0, 0, 0, 0.12);
152
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
153
+ color: rgba(0, 0, 0, 0.78);
154
+ font-size: 12px;
155
+ line-height: 1.4;
156
+ pointer-events: none;
157
+ backdrop-filter: blur(6px);
158
+ }
159
+
160
+ .kline-tooltip__title {
161
+ display: flex;
162
+ justify-content: space-between;
163
+ gap: 10px;
164
+ font-weight: 600;
165
+ margin-bottom: 6px;
166
+ }
167
+
168
+ .kline-tooltip__grid {
169
+ display: grid;
170
+ grid-template-columns: 1fr;
171
+ gap: 2px;
172
+ }
173
+
174
+ .kline-tooltip__grid .row {
175
+ display: flex;
176
+ justify-content: space-between;
177
+ gap: 10px;
178
+ }
179
+
180
+ .kline-tooltip__grid .row span:first-child {
181
+ color: rgba(0, 0, 0, 0.56);
182
+ }
183
+
184
+ @supports (anchor-name: --kmap-anchor) and (position-anchor: --kmap-anchor) {
185
+ .kline-tooltip.use-anchor {
186
+ position: absolute;
187
+ position-anchor: --kline-tooltip-anchor;
188
+ left: anchor(left);
189
+ top: anchor(top);
190
+ }
191
+
192
+ .kline-tooltip.use-anchor.anchor-right-bottom {
193
+ transform: translate(14px, 14px);
194
+ }
195
+
196
+ .kline-tooltip.use-anchor.anchor-left-bottom {
197
+ transform: translate(calc(-100% - 14px), 14px);
198
+ }
199
+ }
200
+ </style>