koishi-plugin-monetary-bourse 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/templates/holding-card.html +12 -2
- package/lib/templates/stock-chart.html +2 -2
- package/lib/templates/trade-result.html +365 -318
- package/package.json +1 -1
- package/readme.md +48 -40
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
body {
|
|
22
22
|
margin: 0;
|
|
23
23
|
padding: 24px;
|
|
24
|
-
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
24
|
+
font-family: 'Roboto Mono', 'Trebuchet MS', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
25
25
|
background-color: var(--bg-color);
|
|
26
26
|
width: 480px;
|
|
27
27
|
box-sizing: border-box;
|
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
display: flex;
|
|
129
129
|
align-items: center;
|
|
130
130
|
gap: 8px;
|
|
131
|
+
letter-spacing: -0.2px;
|
|
131
132
|
}
|
|
132
133
|
.stock-amount {
|
|
133
134
|
font-size: 13px;
|
|
@@ -136,6 +137,7 @@
|
|
|
136
137
|
color: var(--text-primary);
|
|
137
138
|
padding: 4px 10px;
|
|
138
139
|
border-radius: 6px;
|
|
140
|
+
font-family: 'Roboto Mono', 'Trebuchet MS', monospace;
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
.stock-body { padding: 24px; }
|
|
@@ -151,7 +153,8 @@
|
|
|
151
153
|
font-size: 18px;
|
|
152
154
|
font-weight: 600;
|
|
153
155
|
color: var(--text-primary);
|
|
154
|
-
font-family: 'Roboto Mono', monospace;
|
|
156
|
+
font-family: 'Roboto Mono', 'Trebuchet MS', monospace;
|
|
157
|
+
letter-spacing: -0.3px;
|
|
155
158
|
}
|
|
156
159
|
.stat-value.highlight { color: var(--accent-color); }
|
|
157
160
|
|
|
@@ -253,6 +256,13 @@
|
|
|
253
256
|
// 从 JSON 标签中读取数据
|
|
254
257
|
const DATA = JSON.parse(document.getElementById('data-source').textContent);
|
|
255
258
|
|
|
259
|
+
// 统一字体:数字和金额统一使用与其他页面一致的等宽字体
|
|
260
|
+
const monoTargets = document.querySelectorAll('.stat-value, .profit-value, .pending-amount, .pending-price, .pending-cost, .pending-type, .stock-amount')
|
|
261
|
+
monoTargets.forEach(el => {
|
|
262
|
+
el.style.fontFamily = "'Roboto Mono','Trebuchet MS','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif"
|
|
263
|
+
el.style.letterSpacing = '-0.2px'
|
|
264
|
+
})
|
|
265
|
+
|
|
256
266
|
// 渲染用户信息
|
|
257
267
|
document.getElementById('avatar').textContent = DATA.username.charAt(0).toUpperCase();
|
|
258
268
|
document.getElementById('username').textContent = DATA.username;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
body {
|
|
24
24
|
padding: 32px;
|
|
25
25
|
/* 字体栈优化:优先使用现代系统字体 */
|
|
26
|
-
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
|
|
26
|
+
font-family: 'Roboto Mono', 'Trebuchet MS', 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
|
|
27
27
|
background-color: var(--bg-color);
|
|
28
28
|
|
|
29
29
|
/* 强制 4:3 比例 (1024x768) */
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
.current-price {
|
|
108
|
-
font-family: 'Trebuchet MS', monospace;
|
|
108
|
+
font-family: 'Roboto Mono', 'Trebuchet MS', monospace;
|
|
109
109
|
font-size: 64px;
|
|
110
110
|
font-weight: normal;
|
|
111
111
|
color: var(--main-color);
|
|
@@ -1,28 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
1
2
|
<html>
|
|
2
3
|
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
3
5
|
<style>
|
|
4
6
|
:root {
|
|
5
7
|
--bg-color: #0c0f15;
|
|
6
|
-
--card-bg: #
|
|
7
|
-
--item-bg: #
|
|
8
|
-
--text-primary: #
|
|
9
|
-
--text-secondary: #
|
|
10
|
-
--border-color: #
|
|
8
|
+
--card-bg: #161b22;
|
|
9
|
+
--item-bg: #1f242e;
|
|
10
|
+
--text-primary: #f0f3f5;
|
|
11
|
+
--text-secondary: #8b949e;
|
|
12
|
+
--border-color: #30363d;
|
|
11
13
|
|
|
12
14
|
/* 语义色 */
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
--
|
|
15
|
+
--buy-color: #f87171; /* 涨/买 */
|
|
16
|
+
--sell-color: #4ade80; /* 跌/卖 */
|
|
17
|
+
--buy-bg: rgba(248, 113, 113, 0.15);
|
|
18
|
+
--sell-bg: rgba(74, 222, 128, 0.15);
|
|
17
19
|
|
|
18
|
-
--
|
|
20
|
+
--pending-color: #eab308; /* 黄色 */
|
|
21
|
+
--pending-bg: rgba(234, 179, 8, 0.15);
|
|
22
|
+
|
|
23
|
+
--accent-color: #58a6ff;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
22
27
|
|
|
23
28
|
body {
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
/* 修改 3: 增大页面外间距 (32px -> 64px) */
|
|
30
|
+
padding: 64px;
|
|
31
|
+
/* 还原回原本的字体组合 */
|
|
32
|
+
font-family: 'Roboto Mono', 'Trebuchet MS', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
26
33
|
background-color: var(--bg-color);
|
|
27
34
|
width: 960px;
|
|
28
35
|
height: 720px;
|
|
@@ -36,104 +43,143 @@
|
|
|
36
43
|
background-color: var(--card-bg);
|
|
37
44
|
width: 100%;
|
|
38
45
|
height: 100%;
|
|
39
|
-
padding: 32px 40px;
|
|
40
|
-
border-radius:
|
|
41
|
-
box-shadow: 0
|
|
42
|
-
border: 1px solid var(--border-color);
|
|
46
|
+
padding: 32px 40px; /* 内部左右间距也稍微增加一点 */
|
|
47
|
+
border-radius: 16px;
|
|
48
|
+
box-shadow: 0 32px 64px rgba(0,0,0,0.6), 0 0 0 1px var(--border-color);
|
|
43
49
|
display: flex;
|
|
44
50
|
flex-direction: column;
|
|
45
51
|
position: relative;
|
|
52
|
+
overflow: hidden;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
/*
|
|
49
|
-
.
|
|
55
|
+
/* --- 顶部区域:股票信息 + 价格 --- */
|
|
56
|
+
.top-section {
|
|
50
57
|
display: flex;
|
|
51
58
|
justify-content: space-between;
|
|
52
|
-
align-items: center;
|
|
59
|
+
align-items: center;
|
|
53
60
|
margin-bottom: 24px;
|
|
54
|
-
flex-shrink: 0;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
.stock-
|
|
63
|
+
.stock-group {
|
|
58
64
|
display: flex;
|
|
59
65
|
align-items: center;
|
|
60
66
|
gap: 16px;
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
.stock-icon {
|
|
64
|
-
width:
|
|
65
|
-
height:
|
|
66
|
-
border-radius:
|
|
67
|
-
background: linear-gradient(135deg, #
|
|
70
|
+
width: 64px; /* 稍微加大 */
|
|
71
|
+
height: 64px;
|
|
72
|
+
border-radius: 14px;
|
|
73
|
+
background: linear-gradient(135deg, #2b313a, #1f242e);
|
|
74
|
+
border: 1px solid var(--border-color);
|
|
68
75
|
display: flex;
|
|
69
76
|
align-items: center;
|
|
70
77
|
justify-content: center;
|
|
71
|
-
font-size:
|
|
72
|
-
|
|
73
|
-
color: white;
|
|
78
|
+
font-size: 32px;
|
|
79
|
+
color: var(--text-primary);
|
|
74
80
|
}
|
|
75
81
|
|
|
76
|
-
.stock-
|
|
82
|
+
.stock-meta {
|
|
77
83
|
display: flex;
|
|
78
84
|
flex-direction: column;
|
|
79
|
-
|
|
85
|
+
justify-content: center;
|
|
86
|
+
gap: 8px;
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
.stock-name {
|
|
83
|
-
font-size: 28px;
|
|
90
|
+
font-size: 28px; /* 加大 */
|
|
84
91
|
font-weight: 700;
|
|
85
|
-
|
|
86
|
-
line-height: 1.2;
|
|
92
|
+
line-height: 1.1;
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
|
|
95
|
+
/* 徽标容器 */
|
|
96
|
+
.badges-container {
|
|
97
|
+
display: flex;
|
|
98
|
+
gap: 8px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.badge {
|
|
90
102
|
display: inline-flex;
|
|
91
103
|
align-items: center;
|
|
92
|
-
|
|
93
|
-
padding: 4px 10px;
|
|
104
|
+
padding: 6px 12px; /* 修改 1: 加大内边距 */
|
|
94
105
|
border-radius: 6px;
|
|
95
|
-
font-size:
|
|
106
|
+
font-size: 14px; /* 修改 1: 增大字号 (13px -> 14px) */
|
|
96
107
|
font-weight: 700;
|
|
97
108
|
text-transform: uppercase;
|
|
98
109
|
letter-spacing: 0.5px;
|
|
99
|
-
|
|
110
|
+
line-height: 1;
|
|
100
111
|
}
|
|
101
|
-
.trade-badge.buy { background: var(--up-bg); color: var(--up-color); }
|
|
102
|
-
.trade-badge.sell { background: var(--down-bg); color: var(--down-color); }
|
|
103
112
|
|
|
104
|
-
.
|
|
113
|
+
.badge.buy { background: var(--buy-bg); color: var(--buy-color); border: 1px solid rgba(248, 113, 113, 0.2); }
|
|
114
|
+
.badge.sell { background: var(--sell-bg); color: var(--sell-color); border: 1px solid rgba(74, 222, 128, 0.2); }
|
|
115
|
+
|
|
116
|
+
.badge.pending {
|
|
117
|
+
background: var(--pending-bg);
|
|
118
|
+
color: var(--pending-color);
|
|
119
|
+
border: 1px solid rgba(234, 179, 8, 0.2);
|
|
120
|
+
}
|
|
121
|
+
.badge.pending::before {
|
|
122
|
+
content: '';
|
|
123
|
+
display: inline-block;
|
|
124
|
+
width: 6px;
|
|
125
|
+
height: 6px;
|
|
126
|
+
border-radius: 50%;
|
|
127
|
+
background-color: currentColor;
|
|
128
|
+
margin-right: 6px;
|
|
129
|
+
animation: pulse 2s infinite;
|
|
130
|
+
}
|
|
131
|
+
@keyframes pulse {
|
|
132
|
+
0% { opacity: 1; }
|
|
133
|
+
50% { opacity: 0.4; }
|
|
134
|
+
100% { opacity: 1; }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.price-group {
|
|
105
138
|
text-align: right;
|
|
106
139
|
}
|
|
107
140
|
|
|
108
|
-
.
|
|
109
|
-
font-
|
|
110
|
-
font-
|
|
111
|
-
|
|
141
|
+
.main-price {
|
|
142
|
+
font-size: 56px; /* 加大 */
|
|
143
|
+
font-weight: 700;
|
|
144
|
+
letter-spacing: -2px;
|
|
112
145
|
line-height: 1;
|
|
113
|
-
|
|
146
|
+
margin-bottom: 8px;
|
|
147
|
+
/* 修改 2: 强制使用数字字体 */
|
|
148
|
+
font-family: 'Roboto Mono', monospace;
|
|
114
149
|
}
|
|
115
|
-
.
|
|
116
|
-
.
|
|
150
|
+
.main-price.buy { color: var(--buy-color); }
|
|
151
|
+
.main-price.sell { color: var(--sell-color); }
|
|
117
152
|
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
font-size: 14px;
|
|
153
|
+
.sub-detail {
|
|
154
|
+
font-size: 16px; /* 修改 1: 增大字号 (15px -> 16px) */
|
|
121
155
|
color: var(--text-secondary);
|
|
122
|
-
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
justify-content: flex-end;
|
|
159
|
+
gap: 16px;
|
|
160
|
+
}
|
|
161
|
+
.sub-detail span {
|
|
162
|
+
display: inline-flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: 6px;
|
|
165
|
+
}
|
|
166
|
+
.val-highlight {
|
|
167
|
+
color: var(--text-primary);
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
/* 修改 2: 强制使用数字字体 */
|
|
170
|
+
font-family: 'Roboto Mono', monospace;
|
|
123
171
|
}
|
|
124
|
-
.highlight-val { color: var(--text-primary); font-weight: 600; }
|
|
125
172
|
|
|
126
|
-
/*
|
|
127
|
-
.chart-
|
|
173
|
+
/* --- 图表区域 --- */
|
|
174
|
+
.chart-wrapper {
|
|
128
175
|
flex: 1;
|
|
129
176
|
width: 100%;
|
|
130
177
|
min-height: 0;
|
|
131
|
-
margin:
|
|
132
|
-
border-radius:
|
|
178
|
+
margin-bottom: 32px; /* 增加底部间距 */
|
|
179
|
+
border-radius: 12px;
|
|
133
180
|
background: rgba(0,0,0,0.2);
|
|
134
181
|
border: 1px solid var(--border-color);
|
|
135
182
|
position: relative;
|
|
136
|
-
overflow: hidden;
|
|
137
183
|
}
|
|
138
184
|
|
|
139
185
|
canvas {
|
|
@@ -142,372 +188,373 @@
|
|
|
142
188
|
height: 100%;
|
|
143
189
|
}
|
|
144
190
|
|
|
145
|
-
/*
|
|
146
|
-
.
|
|
191
|
+
/* --- 底部数据卡片 --- */
|
|
192
|
+
.stats-grid {
|
|
147
193
|
display: grid;
|
|
148
|
-
grid-template-columns:
|
|
194
|
+
grid-template-columns: 1fr 1fr 1fr;
|
|
149
195
|
gap: 20px;
|
|
150
|
-
|
|
196
|
+
height: 120px; /* 增加高度以容纳更大的字体 */
|
|
151
197
|
}
|
|
152
198
|
|
|
153
|
-
.stat-
|
|
199
|
+
.stat-item {
|
|
154
200
|
background: var(--item-bg);
|
|
155
|
-
border-radius: 16px;
|
|
156
|
-
padding: 20px;
|
|
157
201
|
border: 1px solid var(--border-color);
|
|
202
|
+
border-radius: 12px;
|
|
203
|
+
padding: 20px 28px;
|
|
158
204
|
display: flex;
|
|
159
205
|
flex-direction: column;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
.stat-card.primary {
|
|
164
|
-
background: rgba(99, 102, 241, 0.05);
|
|
165
|
-
border-color: rgba(99, 102, 241, 0.3);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.stat-card.profit {
|
|
169
|
-
background: rgba(248, 113, 113, 0.05);
|
|
170
|
-
border-color: rgba(248, 113, 113, 0.3);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.stat-card.loss {
|
|
174
|
-
background: rgba(74, 222, 128, 0.05);
|
|
175
|
-
border-color: rgba(74, 222, 128, 0.3);
|
|
206
|
+
justify-content: center;
|
|
207
|
+
position: relative;
|
|
176
208
|
}
|
|
177
209
|
|
|
210
|
+
.stat-item.highlight { background: rgba(88, 166, 255, 0.05); border-color: rgba(88, 166, 255, 0.2); }
|
|
211
|
+
.stat-item.profit { background: rgba(248, 113, 113, 0.05); border-color: rgba(248, 113, 113, 0.2); }
|
|
212
|
+
.stat-item.loss { background: rgba(74, 222, 128, 0.05); border-color: rgba(74, 222, 128, 0.2); }
|
|
213
|
+
|
|
178
214
|
.stat-label {
|
|
179
|
-
font-size:
|
|
180
|
-
font-weight: 600;
|
|
181
|
-
color: var(--text-secondary);
|
|
215
|
+
font-size: 14px; /* 修改 1: 增大字号 (13px -> 14px) */
|
|
182
216
|
text-transform: uppercase;
|
|
217
|
+
color: var(--text-secondary);
|
|
183
218
|
letter-spacing: 0.5px;
|
|
219
|
+
margin-bottom: 10px;
|
|
220
|
+
font-weight: 600;
|
|
184
221
|
}
|
|
185
222
|
|
|
186
223
|
.stat-value {
|
|
187
|
-
font-size:
|
|
224
|
+
font-size: 28px; /* 加大 */
|
|
188
225
|
font-weight: 700;
|
|
189
226
|
color: var(--text-primary);
|
|
227
|
+
/* 修改 2: 强制使用数字字体 */
|
|
190
228
|
font-family: 'Roboto Mono', monospace;
|
|
191
229
|
}
|
|
192
230
|
|
|
193
|
-
.stat-value.profit { color: var(--
|
|
194
|
-
.stat-value.loss { color: var(--
|
|
231
|
+
.stat-value.profit { color: var(--buy-color); }
|
|
232
|
+
.stat-value.loss { color: var(--sell-color); }
|
|
195
233
|
|
|
196
234
|
.stat-sub {
|
|
197
|
-
font-size:
|
|
235
|
+
font-size: 14px; /* 修改 1: 增大字号 */
|
|
198
236
|
color: var(--text-secondary);
|
|
199
237
|
opacity: 0.8;
|
|
238
|
+
margin-top: 6px;
|
|
239
|
+
/* 百分比也属于数字,用一下 Roboto Mono 更好看 */
|
|
240
|
+
font-family: 'Roboto Mono', sans-serif;
|
|
200
241
|
}
|
|
201
242
|
|
|
202
|
-
|
|
243
|
+
/* 底部时间戳 */
|
|
244
|
+
.footer-meta {
|
|
203
245
|
position: absolute;
|
|
204
|
-
bottom:
|
|
205
|
-
right:
|
|
206
|
-
|
|
246
|
+
bottom: 0px;
|
|
247
|
+
right: 0px;
|
|
248
|
+
display: flex;
|
|
249
|
+
gap: 16px;
|
|
250
|
+
align-items: center;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.time-tag {
|
|
254
|
+
font-size: 12px;
|
|
207
255
|
color: var(--text-secondary);
|
|
208
|
-
|
|
256
|
+
padding: 6px 8px;
|
|
257
|
+
border-radius: 6px;
|
|
258
|
+
font-family: 'Roboto Mono', sans-serif; /* 包含时间数字 */
|
|
209
259
|
}
|
|
260
|
+
|
|
210
261
|
</style>
|
|
211
262
|
</head>
|
|
212
263
|
<body>
|
|
213
264
|
<div class="card">
|
|
214
|
-
|
|
215
|
-
|
|
265
|
+
<!-- 顶部信息栏 -->
|
|
266
|
+
<div class="top-section">
|
|
267
|
+
<div class="stock-group">
|
|
216
268
|
<div class="stock-icon">⚡</div>
|
|
217
|
-
<div class="stock-
|
|
218
|
-
<div class="stock-name" id="stock-name"
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
<span class="text"></span>
|
|
222
|
-
</div>
|
|
269
|
+
<div class="stock-meta">
|
|
270
|
+
<div class="stock-name" id="stock-name">--</div>
|
|
271
|
+
<!-- 这里会动态插入徽标 -->
|
|
272
|
+
<div class="badges-container" id="badges-container"></div>
|
|
223
273
|
</div>
|
|
224
274
|
</div>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
<div class="trade-
|
|
228
|
-
|
|
275
|
+
|
|
276
|
+
<div class="price-group">
|
|
277
|
+
<div class="main-price" id="trade-price">--</div>
|
|
278
|
+
<div class="sub-detail">
|
|
279
|
+
<span>数量 <b class="val-highlight" id="trade-amount">--</b></span>
|
|
280
|
+
<span style="opacity: 0.2">|</span>
|
|
281
|
+
<span>总额 <b class="val-highlight" id="trade-total">--</b></span>
|
|
229
282
|
</div>
|
|
230
283
|
</div>
|
|
231
284
|
</div>
|
|
232
285
|
|
|
233
|
-
|
|
286
|
+
<!-- 图表区域 -->
|
|
287
|
+
<div class="chart-wrapper" id="chart-container">
|
|
234
288
|
<canvas id="chart"></canvas>
|
|
235
289
|
</div>
|
|
236
290
|
|
|
237
|
-
|
|
238
|
-
<div class="
|
|
291
|
+
<!-- 底部数据 -->
|
|
292
|
+
<div class="stats-grid" id="stats-grid">
|
|
293
|
+
<!-- JS填充 -->
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<!-- 仅用于显示时间,不干扰布局 -->
|
|
297
|
+
<div class="footer-meta">
|
|
298
|
+
<div class="time-tag" id="trade-time">--</div>
|
|
299
|
+
</div>
|
|
239
300
|
</div>
|
|
240
301
|
|
|
302
|
+
<!-- 数据源 -->
|
|
241
303
|
<script type="application/json" id="data-source">
|
|
242
304
|
{{DATA}}
|
|
243
305
|
</script>
|
|
306
|
+
|
|
244
307
|
<script>
|
|
245
|
-
|
|
308
|
+
// 模拟数据用于测试
|
|
309
|
+
/*
|
|
310
|
+
const TEST_DATA = {
|
|
311
|
+
tradeType: 'buy',
|
|
312
|
+
stockName: 'NVDA · NVIDIA Corp',
|
|
313
|
+
status: 'pending',
|
|
314
|
+
pendingMinutes: 5.5,
|
|
315
|
+
pendingEndTime: '14:35',
|
|
316
|
+
tradePrice: 485.20,
|
|
317
|
+
amount: 10,
|
|
318
|
+
totalCost: 4852.00,
|
|
319
|
+
tradeTime: '2023-10-27 10:30:05',
|
|
320
|
+
newHolding: 150,
|
|
321
|
+
currency: 'USD',
|
|
322
|
+
prices: [480, 482, 481, 483, 485, 484, 486, 485.2, 487, 488, 486],
|
|
323
|
+
timestamps: [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 11000],
|
|
324
|
+
tradeIndex: 7
|
|
325
|
+
};
|
|
326
|
+
*/
|
|
327
|
+
|
|
328
|
+
let DATA;
|
|
329
|
+
try {
|
|
330
|
+
DATA = JSON.parse(document.getElementById('data-source').textContent);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
DATA = typeof TEST_DATA !== 'undefined' ? TEST_DATA : {};
|
|
333
|
+
}
|
|
334
|
+
|
|
246
335
|
const isBuy = DATA.tradeType === 'buy';
|
|
336
|
+
const status = DATA.status || 'settled';
|
|
337
|
+
const isPending = status === 'pending';
|
|
247
338
|
|
|
248
|
-
// 颜色定义
|
|
249
339
|
const colors = {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
grid: '#
|
|
254
|
-
|
|
340
|
+
buy: '#f87171',
|
|
341
|
+
sell: '#4ade80',
|
|
342
|
+
pending: '#eab308',
|
|
343
|
+
grid: '#30363d',
|
|
344
|
+
text: '#f0f3f5',
|
|
345
|
+
textDim: '#8b949e'
|
|
255
346
|
};
|
|
256
347
|
|
|
257
|
-
|
|
258
|
-
// 这里统一:买入=红(Up/Action),卖出=绿(Down/Action)
|
|
259
|
-
const mainColor = isBuy ? colors.up : colors.down;
|
|
260
|
-
|
|
261
|
-
// --- 渲染 DOM ---
|
|
262
|
-
document.getElementById('stock-name').textContent = DATA.stockName;
|
|
263
|
-
|
|
264
|
-
const badge = document.getElementById('trade-badge');
|
|
265
|
-
badge.classList.add(DATA.tradeType);
|
|
266
|
-
badge.querySelector('.icon').textContent = isBuy ? '📈' : '📉';
|
|
267
|
-
badge.querySelector('.text').textContent = isBuy ? '买入成功 BUY' : '卖出成功 SELL';
|
|
268
|
-
|
|
269
|
-
const priceEl = document.getElementById('trade-price');
|
|
270
|
-
priceEl.textContent = DATA.tradePrice.toFixed(2);
|
|
271
|
-
priceEl.classList.add(DATA.tradeType);
|
|
348
|
+
const mainColor = isBuy ? colors.buy : colors.sell;
|
|
272
349
|
|
|
350
|
+
// --- 1. 渲染顶部基本信息 ---
|
|
351
|
+
document.getElementById('stock-name').textContent = DATA.stockName || 'Unknown Stock';
|
|
352
|
+
document.getElementById('trade-price').textContent = DATA.tradePrice.toFixed(2);
|
|
353
|
+
document.getElementById('trade-price').className = `main-price ${DATA.tradeType}`;
|
|
273
354
|
document.getElementById('trade-amount').textContent = DATA.amount;
|
|
274
355
|
document.getElementById('trade-total').textContent = DATA.totalCost.toFixed(2);
|
|
275
|
-
document.getElementById('
|
|
356
|
+
document.getElementById('trade-time').textContent = (isPending ? '下单时间 ' : '交易时间 ') + DATA.tradeTime;
|
|
357
|
+
|
|
358
|
+
// --- 2. 徽标渲染 ---
|
|
359
|
+
const badgesContainer = document.getElementById('badges-container');
|
|
360
|
+
badgesContainer.innerHTML = '';
|
|
361
|
+
|
|
362
|
+
// (A) 交易类型徽标
|
|
363
|
+
const typeBadge = document.createElement('div');
|
|
364
|
+
typeBadge.className = `badge ${DATA.tradeType}`;
|
|
365
|
+
typeBadge.innerHTML = isBuy ? 'BUY / 买入' : 'SELL / 卖出';
|
|
366
|
+
badgesContainer.appendChild(typeBadge);
|
|
367
|
+
|
|
368
|
+
// (B) 状态徽标
|
|
369
|
+
if (isPending) {
|
|
370
|
+
const pendingBadge = document.createElement('div');
|
|
371
|
+
pendingBadge.className = 'badge pending';
|
|
372
|
+
const pendingText = DATA.pendingEndTime
|
|
373
|
+
? `PENDING 预计 ${DATA.pendingEndTime} 完成`
|
|
374
|
+
: 'PENDING 挂单处理中';
|
|
375
|
+
pendingBadge.innerHTML = pendingText;
|
|
376
|
+
badgesContainer.appendChild(pendingBadge);
|
|
377
|
+
}
|
|
276
378
|
|
|
277
|
-
|
|
379
|
+
// --- 3. 底部数据卡片渲染 ---
|
|
380
|
+
const statsGrid = document.getElementById('stats-grid');
|
|
278
381
|
if (isBuy) {
|
|
279
|
-
|
|
280
|
-
<div class="stat-
|
|
281
|
-
<div class="stat-label"
|
|
382
|
+
statsGrid.innerHTML = `
|
|
383
|
+
<div class="stat-item highlight">
|
|
384
|
+
<div class="stat-label">Current Holdings 持仓</div>
|
|
282
385
|
<div class="stat-value">${DATA.newHolding}</div>
|
|
283
386
|
<div class="stat-sub">股</div>
|
|
284
387
|
</div>
|
|
285
|
-
<div class="stat-
|
|
286
|
-
<div class="stat-label"
|
|
388
|
+
<div class="stat-item">
|
|
389
|
+
<div class="stat-label">Avg Price 均价</div>
|
|
287
390
|
<div class="stat-value">${DATA.tradePrice.toFixed(2)}</div>
|
|
288
|
-
<div class="stat-sub"
|
|
391
|
+
<div class="stat-sub">${DATA.currency}</div>
|
|
289
392
|
</div>
|
|
290
|
-
<div class="stat-
|
|
291
|
-
<div class="stat-label"
|
|
393
|
+
<div class="stat-item">
|
|
394
|
+
<div class="stat-label">Total Value 市值</div>
|
|
292
395
|
<div class="stat-value">${(DATA.newHolding * DATA.tradePrice).toFixed(2)}</div>
|
|
293
396
|
<div class="stat-sub">${DATA.currency}</div>
|
|
294
397
|
</div>
|
|
295
398
|
`;
|
|
296
399
|
} else {
|
|
297
|
-
const
|
|
298
|
-
const isProfit =
|
|
299
|
-
const profitClass =
|
|
400
|
+
const profit = DATA.profit;
|
|
401
|
+
const isProfit = profit >= 0;
|
|
402
|
+
const profitClass = isProfit ? 'profit' : 'loss';
|
|
300
403
|
const profitSign = isProfit ? '+' : '';
|
|
301
|
-
const profitColorClass = isProfit ? 'profit' : 'loss';
|
|
302
404
|
|
|
303
|
-
|
|
304
|
-
<div class="stat-
|
|
305
|
-
<div class="stat-label"
|
|
405
|
+
statsGrid.innerHTML = `
|
|
406
|
+
<div class="stat-item highlight">
|
|
407
|
+
<div class="stat-label">Revenue 卖出金额</div>
|
|
306
408
|
<div class="stat-value">${DATA.totalCost.toFixed(2)}</div>
|
|
307
409
|
<div class="stat-sub">${DATA.currency}</div>
|
|
308
410
|
</div>
|
|
309
|
-
<div class="stat-
|
|
310
|
-
<div class="stat-label"
|
|
311
|
-
<div class="stat-value">${
|
|
312
|
-
<div class="stat-sub"
|
|
411
|
+
<div class="stat-item">
|
|
412
|
+
<div class="stat-label">Cost Basis 买入成本</div>
|
|
413
|
+
<div class="stat-value">${DATA.avgBuyPrice ? DATA.avgBuyPrice.toFixed(2) : '--'}</div>
|
|
414
|
+
<div class="stat-sub">每股</div>
|
|
313
415
|
</div>
|
|
314
|
-
<div class="stat-
|
|
315
|
-
<div class="stat-label"
|
|
316
|
-
<div class="stat-value ${
|
|
317
|
-
<div class="stat-sub">${
|
|
416
|
+
<div class="stat-item ${profitClass}">
|
|
417
|
+
<div class="stat-label">P/L 盈亏</div>
|
|
418
|
+
<div class="stat-value ${profitClass}">${profit !== null ? profitSign + profit.toFixed(2) : '--'}</div>
|
|
419
|
+
<div class="stat-sub">${profit !== null ? profitSign + DATA.profitPercent.toFixed(2) + '%' : ''}</div>
|
|
318
420
|
</div>
|
|
319
421
|
`;
|
|
320
422
|
}
|
|
321
423
|
|
|
322
|
-
|
|
424
|
+
|
|
425
|
+
// --- 4. 图表绘制 ---
|
|
323
426
|
const container = document.getElementById('chart-container');
|
|
324
427
|
const canvas = document.getElementById('chart');
|
|
325
|
-
|
|
326
|
-
// 动态设置 Canvas 分辨率以匹配容器大小 (x2 for Retina)
|
|
327
428
|
const rect = container.getBoundingClientRect();
|
|
328
|
-
const dpr = 2;
|
|
429
|
+
const dpr = window.devicePixelRatio || 2;
|
|
430
|
+
|
|
329
431
|
canvas.width = rect.width * dpr;
|
|
330
432
|
canvas.height = rect.height * dpr;
|
|
331
433
|
|
|
332
434
|
const ctx = canvas.getContext('2d');
|
|
333
|
-
ctx.scale(dpr, dpr);
|
|
435
|
+
ctx.scale(dpr, dpr);
|
|
334
436
|
|
|
335
437
|
const W = rect.width;
|
|
336
438
|
const H = rect.height;
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
const prices = DATA.prices;
|
|
340
|
-
const timestamps = DATA.timestamps;
|
|
341
|
-
const tradeIndex = DATA.tradeIndex;
|
|
342
|
-
|
|
343
|
-
// 计算范围
|
|
344
|
-
const maxPrice = Math.max(...prices);
|
|
345
|
-
const minPrice = Math.min(...prices);
|
|
346
|
-
const range = maxPrice - minPrice || 1;
|
|
347
|
-
// 留白
|
|
348
|
-
const yMin = minPrice - range * 0.2;
|
|
349
|
-
const yMax = maxPrice + range * 0.2;
|
|
350
|
-
const yRange = yMax - yMin;
|
|
351
|
-
|
|
352
|
-
const tStart = timestamps[0];
|
|
353
|
-
const tEnd = timestamps[timestamps.length - 1];
|
|
354
|
-
const tRange = tEnd - tStart || 1;
|
|
439
|
+
const pad = { top: 30, bottom: 30, left: 10, right: 60 };
|
|
355
440
|
|
|
356
|
-
|
|
357
|
-
|
|
441
|
+
if (DATA.prices && DATA.prices.length > 0) {
|
|
442
|
+
const prices = DATA.prices;
|
|
443
|
+
const minP = Math.min(...prices);
|
|
444
|
+
const maxP = Math.max(...prices);
|
|
445
|
+
const rangeP = maxP - minP || 1;
|
|
446
|
+
|
|
447
|
+
const yMin = minP - rangeP * 0.2;
|
|
448
|
+
const yMax = maxP + rangeP * 0.2;
|
|
449
|
+
const yRange = yMax - yMin;
|
|
450
|
+
|
|
451
|
+
const stepX = (W - pad.left - pad.right) / (prices.length - 1);
|
|
452
|
+
|
|
453
|
+
const getX = i => pad.left + i * stepX;
|
|
454
|
+
const getY = p => H - pad.bottom - ((p - yMin) / yRange) * (H - pad.top - pad.bottom);
|
|
455
|
+
|
|
456
|
+
// (A) 绘制网格线
|
|
457
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.06)';
|
|
458
|
+
ctx.lineWidth = 1;
|
|
459
|
+
const gridLines = 4;
|
|
460
|
+
for (let i = 0; i <= gridLines; i++) {
|
|
461
|
+
const y = H - pad.bottom - (i / gridLines) * (H - pad.top - pad.bottom);
|
|
462
|
+
ctx.beginPath();
|
|
463
|
+
ctx.moveTo(pad.left, y);
|
|
464
|
+
ctx.lineTo(W - pad.right, y);
|
|
465
|
+
ctx.stroke();
|
|
466
|
+
|
|
467
|
+
// 价格标签 (字体也同步调整为 Roboto Mono 且加大)
|
|
468
|
+
const val = yMin + (i / gridLines) * yRange;
|
|
469
|
+
ctx.fillStyle = colors.textDim;
|
|
470
|
+
// 修改 1 & 2: 增大字号到 14px 并使用 Roboto Mono
|
|
471
|
+
ctx.font = '500 14px "Roboto Mono", monospace';
|
|
472
|
+
ctx.textAlign = 'left';
|
|
473
|
+
ctx.fillText(val.toFixed(2), W - pad.right + 8, y + 4);
|
|
474
|
+
}
|
|
358
475
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
// 横线
|
|
364
|
-
const gridCount = 5;
|
|
365
|
-
for (let i = 0; i <= gridCount; i++) {
|
|
366
|
-
const y = H - padding.bottom - (i / gridCount) * (H - padding.top - padding.bottom);
|
|
476
|
+
// (B) 绘制面积图
|
|
477
|
+
const points = prices.map((p, i) => ({ x: getX(i), y: getY(p) }));
|
|
478
|
+
|
|
367
479
|
ctx.beginPath();
|
|
368
|
-
ctx.moveTo(
|
|
369
|
-
|
|
370
|
-
ctx.
|
|
480
|
+
ctx.moveTo(points[0].x, H - pad.bottom);
|
|
481
|
+
points.forEach(p => ctx.lineTo(p.x, p.y));
|
|
482
|
+
ctx.lineTo(points[points.length-1].x, H - pad.bottom);
|
|
483
|
+
ctx.closePath();
|
|
371
484
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
ctx.
|
|
376
|
-
ctx.
|
|
377
|
-
ctx.textBaseline = 'middle';
|
|
378
|
-
ctx.fillText(val.toFixed(2), W - padding.right + 10, y);
|
|
379
|
-
}
|
|
485
|
+
const gradient = ctx.createLinearGradient(0, pad.top, 0, H - pad.bottom);
|
|
486
|
+
gradient.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.25)' : 'rgba(74, 222, 128, 0.25)');
|
|
487
|
+
gradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
488
|
+
ctx.fillStyle = gradient;
|
|
489
|
+
ctx.fill();
|
|
380
490
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
x: getX(timestamps[i]),
|
|
384
|
-
y: getY(p)
|
|
385
|
-
}));
|
|
386
|
-
|
|
387
|
-
// 3. 渐变区域
|
|
388
|
-
const gradient = ctx.createLinearGradient(0, padding.top, 0, H - padding.bottom);
|
|
389
|
-
gradient.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.2)' : 'rgba(74, 222, 128, 0.2)');
|
|
390
|
-
gradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
391
|
-
|
|
392
|
-
ctx.beginPath();
|
|
393
|
-
ctx.moveTo(points[0].x, H - padding.bottom);
|
|
394
|
-
if (points.length > 1) {
|
|
395
|
-
ctx.lineTo(points[0].x, points[0].y);
|
|
396
|
-
for (let i = 0; i < points.length - 1; i++) {
|
|
397
|
-
const p0 = points[i];
|
|
398
|
-
const p1 = points[i + 1];
|
|
399
|
-
const midX = (p0.x + p1.x) / 2;
|
|
400
|
-
const midY = (p0.y + p1.y) / 2;
|
|
401
|
-
ctx.quadraticCurveTo(p0.x, p0.y, midX, midY);
|
|
402
|
-
}
|
|
403
|
-
ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);
|
|
404
|
-
}
|
|
405
|
-
ctx.lineTo(points[points.length - 1].x, H - padding.bottom);
|
|
406
|
-
ctx.closePath();
|
|
407
|
-
ctx.fillStyle = gradient;
|
|
408
|
-
ctx.fill();
|
|
409
|
-
|
|
410
|
-
// 4. 曲线
|
|
411
|
-
ctx.beginPath();
|
|
412
|
-
ctx.lineWidth = 3;
|
|
413
|
-
ctx.strokeStyle = mainColor;
|
|
414
|
-
ctx.lineCap = 'round';
|
|
415
|
-
ctx.lineJoin = 'round';
|
|
416
|
-
if (points.length > 1) {
|
|
491
|
+
// (C) 绘制折线
|
|
492
|
+
ctx.beginPath();
|
|
417
493
|
ctx.moveTo(points[0].x, points[0].y);
|
|
418
494
|
for (let i = 0; i < points.length - 1; i++) {
|
|
419
|
-
const
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
const midY = (p0.y + p1.y) / 2;
|
|
423
|
-
ctx.quadraticCurveTo(p0.x, p0.y, midX, midY);
|
|
495
|
+
const xc = (points[i].x + points[i + 1].x) / 2;
|
|
496
|
+
const yc = (points[i].y + points[i + 1].y) / 2;
|
|
497
|
+
ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
|
|
424
498
|
}
|
|
425
|
-
ctx.lineTo(points[points.length
|
|
426
|
-
}
|
|
427
|
-
ctx.stroke();
|
|
428
|
-
|
|
429
|
-
// 5. 交易点标记
|
|
430
|
-
const tradePoint = points[tradeIndex];
|
|
431
|
-
|
|
432
|
-
// 垂直虚线
|
|
433
|
-
ctx.beginPath();
|
|
434
|
-
ctx.setLineDash([4, 4]);
|
|
435
|
-
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
|
436
|
-
ctx.lineWidth = 1;
|
|
437
|
-
ctx.moveTo(tradePoint.x, padding.top);
|
|
438
|
-
ctx.lineTo(tradePoint.x, H - padding.bottom);
|
|
439
|
-
ctx.stroke();
|
|
440
|
-
ctx.setLineDash([]);
|
|
441
|
-
|
|
442
|
-
// 扩散光圈
|
|
443
|
-
const glow = ctx.createRadialGradient(tradePoint.x, tradePoint.y, 0, tradePoint.x, tradePoint.y, 20);
|
|
444
|
-
glow.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.6)' : 'rgba(74, 222, 128, 0.6)');
|
|
445
|
-
glow.addColorStop(1, 'rgba(0,0,0,0)');
|
|
446
|
-
ctx.fillStyle = glow;
|
|
447
|
-
ctx.beginPath();
|
|
448
|
-
ctx.arc(tradePoint.x, tradePoint.y, 20, 0, Math.PI * 2);
|
|
449
|
-
ctx.fill();
|
|
450
|
-
|
|
451
|
-
// 实心点
|
|
452
|
-
ctx.fillStyle = '#fff';
|
|
453
|
-
ctx.beginPath();
|
|
454
|
-
ctx.arc(tradePoint.x, tradePoint.y, 5, 0, Math.PI * 2);
|
|
455
|
-
ctx.fill();
|
|
456
|
-
|
|
457
|
-
// 交易价格标签
|
|
458
|
-
const labelText = DATA.tradePrice.toFixed(2);
|
|
459
|
-
ctx.font = 'bold 14px "Roboto Mono", monospace';
|
|
460
|
-
const labelW = ctx.measureText(labelText).width + 16;
|
|
461
|
-
const labelH = 24;
|
|
462
|
-
const labelX = tradePoint.x - labelW / 2;
|
|
463
|
-
const labelY = tradePoint.y - 32;
|
|
464
|
-
|
|
465
|
-
ctx.fillStyle = mainColor;
|
|
466
|
-
ctx.beginPath();
|
|
467
|
-
ctx.roundRect(labelX, labelY, labelW, labelH, 6);
|
|
468
|
-
ctx.fill();
|
|
469
|
-
|
|
470
|
-
// 小三角
|
|
471
|
-
ctx.beginPath();
|
|
472
|
-
ctx.moveTo(tradePoint.x - 6, labelY + labelH);
|
|
473
|
-
ctx.lineTo(tradePoint.x + 6, labelY + labelH);
|
|
474
|
-
ctx.lineTo(tradePoint.x, labelY + labelH + 6);
|
|
475
|
-
ctx.fill();
|
|
476
|
-
|
|
477
|
-
ctx.fillStyle = '#fff';
|
|
478
|
-
ctx.textAlign = 'center';
|
|
479
|
-
ctx.textBaseline = 'middle';
|
|
480
|
-
ctx.fillText(labelText, tradePoint.x, labelY + labelH/2 + 1);
|
|
481
|
-
|
|
482
|
-
// 6. 卖出时的买入成本线
|
|
483
|
-
if (!isBuy && DATA.avgBuyPrice) {
|
|
484
|
-
const buyY = getY(DATA.avgBuyPrice);
|
|
499
|
+
ctx.lineTo(points[points.length-1].x, points[points.length-1].y);
|
|
485
500
|
|
|
501
|
+
ctx.strokeStyle = mainColor;
|
|
502
|
+
ctx.lineWidth = 2.5;
|
|
503
|
+
ctx.lineCap = 'round';
|
|
504
|
+
ctx.stroke();
|
|
505
|
+
|
|
506
|
+
// (D) 绘制交易点
|
|
507
|
+
const tIdx = DATA.tradeIndex !== undefined ? DATA.tradeIndex : points.length - 1;
|
|
508
|
+
const tp = points[tIdx];
|
|
509
|
+
|
|
486
510
|
ctx.beginPath();
|
|
487
|
-
ctx.setLineDash([
|
|
488
|
-
ctx.strokeStyle = 'rgba(
|
|
489
|
-
ctx.lineWidth = 1
|
|
490
|
-
ctx.moveTo(
|
|
491
|
-
ctx.lineTo(
|
|
511
|
+
ctx.setLineDash([4, 4]);
|
|
512
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.4)';
|
|
513
|
+
ctx.lineWidth = 1;
|
|
514
|
+
ctx.moveTo(tp.x, pad.top);
|
|
515
|
+
ctx.lineTo(tp.x, H - pad.bottom);
|
|
492
516
|
ctx.stroke();
|
|
493
517
|
ctx.setLineDash([]);
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
ctx.fillStyle = '#6366f1';
|
|
518
|
+
|
|
519
|
+
const glow = ctx.createRadialGradient(tp.x, tp.y, 0, tp.x, tp.y, 16);
|
|
520
|
+
glow.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.6)' : 'rgba(74, 222, 128, 0.6)');
|
|
521
|
+
glow.addColorStop(1, 'rgba(0,0,0,0)');
|
|
522
|
+
ctx.fillStyle = glow;
|
|
501
523
|
ctx.beginPath();
|
|
502
|
-
ctx.
|
|
524
|
+
ctx.arc(tp.x, tp.y, 16, 0, Math.PI * 2);
|
|
503
525
|
ctx.fill();
|
|
504
|
-
|
|
526
|
+
|
|
505
527
|
ctx.fillStyle = '#fff';
|
|
506
|
-
ctx.
|
|
507
|
-
ctx.
|
|
508
|
-
ctx.
|
|
528
|
+
ctx.beginPath();
|
|
529
|
+
ctx.arc(tp.x, tp.y, 4, 0, Math.PI * 2);
|
|
530
|
+
ctx.fill();
|
|
531
|
+
|
|
532
|
+
// 价格标签 Tag (同步字体和大小)
|
|
533
|
+
const tagText = DATA.tradePrice.toFixed(2);
|
|
534
|
+
// 修改 1 & 2: 增大字号到 14px 并使用 Roboto Mono
|
|
535
|
+
ctx.font = 'bold 14px "Roboto Mono", monospace';
|
|
536
|
+
const tm = ctx.measureText(tagText);
|
|
537
|
+
const tagW = tm.width + 16;
|
|
538
|
+
const tagH = 26; // 稍微增加高度适应大字号
|
|
539
|
+
const tagX = tp.x - tagW / 2;
|
|
540
|
+
const tagY = tp.y - 36;
|
|
541
|
+
|
|
542
|
+
ctx.fillStyle = mainColor;
|
|
543
|
+
ctx.beginPath();
|
|
544
|
+
ctx.roundRect(tagX, tagY, tagW, tagH, 4);
|
|
545
|
+
ctx.fill();
|
|
546
|
+
|
|
547
|
+
ctx.beginPath();
|
|
548
|
+
ctx.moveTo(tp.x - 4, tagY + tagH);
|
|
549
|
+
ctx.lineTo(tp.x + 4, tagY + tagH);
|
|
550
|
+
ctx.lineTo(tp.x, tagY + tagH + 4);
|
|
551
|
+
ctx.fill();
|
|
552
|
+
|
|
553
|
+
ctx.fillStyle = '#1e232e';
|
|
554
|
+
ctx.textAlign = 'center';
|
|
555
|
+
ctx.fillText(tagText, tp.x, tagY + 17); // 微调垂直对齐
|
|
509
556
|
}
|
|
510
557
|
|
|
511
558
|
</script>
|
|
512
559
|
</body>
|
|
513
|
-
</html>
|
|
560
|
+
</html>
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
# koishi-plugin-monetary-bourse
|
|
1
|
+
# koishi-plugin-monetary-bourse
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/koishi-plugin-monetary-bourse)
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-monetary-bourse) [](https://github.com/BYWled/koishi-plugin-monetary-bourse) [](https://gitee.com/BYWled/koishi-plugin-monetary-bourse)
|
|
4
4
|
|
|
5
5
|
为 Koishi 提供基于 `monetary` 通用货币系统的股票交易所功能。
|
|
6
6
|
|
|
7
7
|
本插件模拟了一个具备自动宏观调控、25种经典K线形态、智能概率博弈和可视化交割单的深度拟真股票市场。用户可以使用机器人通用的货币(如信用点)进行股票买卖、炒股理财。
|
|
8
8
|
|
|
9
|
-
> 版本:**2.0.
|
|
9
|
+
> 版本:**2.0.2**
|
|
10
10
|
|
|
11
11
|
## ✨ 特性
|
|
12
12
|
|
|
@@ -141,50 +141,58 @@ A: 股价采用 **"智能期望模型"** 驱动,更贴近真实博弈:
|
|
|
141
141
|
|
|
142
142
|
## 更新记录
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
### 2.0.2
|
|
145
|
+
|
|
146
|
+
- **doc**: 更新同步更新Gitee和Github链接至`README`
|
|
147
|
+
|
|
148
|
+
### 2.0.1
|
|
149
|
+
- **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建未更新静态页面的问题
|
|
150
|
+
|
|
151
|
+
### 2.0.0
|
|
152
|
+
- **perf/alg**: 提升随机噪声强度,实时曲线更具呼吸感,减少单调走势。
|
|
153
|
+
- **ui/layout**: 重构交易详情页布局,分离股票信息与价格展示区域,底部改为 Grid 布局,并将页面外间距增至 64px 以提升视觉呼吸感。
|
|
154
|
+
- **feat(ui/display)**: 优化状态徽标逻辑,支持「交易方向」与「挂单中」状态同时并列显示,并为 Pending 状态增加呼吸动画。
|
|
155
|
+
- **ui/typography**: 显著增大详情页关键数值与标签字号,回滚并强制使用 Roboto Mono 等宽字体,确保金融数据对齐与专业感。
|
|
156
|
+
- **fix(ui/trade)**: 交易结果回单在存在冻结时间时也会即时弹出;修正时间戳位置,将其强制固定于页面右下角。
|
|
157
|
+
|
|
158
|
+
### Alpha.10
|
|
159
|
+
- **feat(core)**: 引入 **25 种** 全新 K 线形态库;新增 `selectPatternByExpectation` 智能调度算法,按价格偏离度动态调整形态概率以实现“估值修复”。
|
|
160
|
+
- **feat(ui)**: 新增交易交割单(成交结算图),并将 HTML 模板模板化重构,支持高分辨率渲染与更友好的样式复用。
|
|
161
|
+
- **perf**: 优化持仓成本计算、挂单排队体验与兼容旧数据的处理逻辑。
|
|
162
|
+
|
|
163
|
+
### Alpha.9
|
|
164
|
+
- **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建遗漏导致的插件无法加载问题。
|
|
155
165
|
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
### Alpha.8
|
|
167
|
+
- **feat(ui)**: 提取并模板化走势图(`src/templates/stock-chart.html`),全面视觉优化(深色玻璃拟态、坐标轴与字体改进、增强可读性)。
|
|
168
|
+
- **feat(queue)**: 将挂单改为用户串行排队,防止拆单规避冻结时长(T+0 防绕过)。
|
|
169
|
+
- **perf/alg**: 提升基础波动率与形态影响力,改进周波浪与均值回归;新增 `enableDebug` 调试开关以便开发验证。
|
|
158
170
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
- **feat(queue)**: 将挂单改为用户串行排队,防止拆单规避冻结时长(T+0 防绕过)。
|
|
162
|
-
- **perf/alg**: 提升基础波动率与形态影响力,改进周波浪与均值回归;新增 `enableDebug` 调试开关以便开发验证。
|
|
171
|
+
### Alpha.7
|
|
172
|
+
- **feat(model)**: 引入几何布朗运动 + 均值回归,添加正态分布噪声与软着陆限幅,改善日内波动特性以提高拟真度。
|
|
163
173
|
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
### Alpha.6
|
|
175
|
+
- **refactor**: 采用百分比波动合成模型,解决长期振幅失控并提升稳定性。
|
|
176
|
+
- **fix**: 修正手动宏观调控在部分场景下重置基准价的问题。
|
|
166
177
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
- **fix**: 修正手动宏观调控在部分场景下重置基准价的问题。
|
|
178
|
+
### Alpha.5
|
|
179
|
+
- **refactor**: 重写走势引擎为绝对价格合成(基准价 + 趋势 + 日内波动 + 周波浪 + 噪声),修复长期运行抖动并强化 ±50% 涨跌幅限幅。
|
|
170
180
|
|
|
171
|
-
|
|
172
|
-
|
|
181
|
+
### 1.1.1 (Alpha.4)
|
|
182
|
+
- **feat**: 引入硬性 ±50% 涨跌幅上限并随机刷新宏观目标以增强稳定性和随机性。
|
|
183
|
+
- **fix**: 避免在更新负载中修改主键;移除过时的手动控制配置;新增调试命令以便验证逻辑。
|
|
173
184
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
### 1.1.0 (Alpha.3)
|
|
186
|
+
- **feat**: 新增持仓盈亏与成本追踪(`totalCost`)及持仓卡片渲染(Puppeteer),提升展示与交互体验。
|
|
187
|
+
- **fix**: 统一金额与价格为两位小数以保证精度与兼容性;当 `maxFreezeTime = 0` 时即时完成交易处理。
|
|
177
188
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
- **fix**: 统一金额与价格为两位小数以保证精度与兼容性;当 `maxFreezeTime = 0` 时即时完成交易处理。
|
|
189
|
+
### 1.0.0
|
|
190
|
+
- **fix**: 修复因更新负载包含主键而导致的 `TypeError: cannot modify primary key`,提高数据库兼容性。
|
|
181
191
|
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
### Alpha.2
|
|
193
|
+
- **feat**: 扩展日内走势剧本并叠加周级波浪,改进 Puppeteer 渲染以减少横轴标签重叠与视觉冲突。
|
|
194
|
+
- **note**: 历史版本曾存在主键相关问题,后续版本已逐步修复。
|
|
184
195
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- **note**: 历史版本曾存在主键相关问题,后续版本已逐步修复。
|
|
196
|
+
### Alpha.1
|
|
197
|
+
- **feat**: 初始发布:实现买卖、挂单、资金冻结、历史行情存储及 `stock` 系列基础指令,支持与 `monetary` / `monetary-bank` 的基础联动。
|
|
188
198
|
|
|
189
|
-
- **Alpha.1**
|
|
190
|
-
- **feat**: 初始发布:实现买卖、挂单、资金冻结、历史行情存储及 `stock` 系列基础指令,支持与 `monetary` / `monetary-bank` 的基础联动。
|