befly-admin-ui 1.10.18 → 1.11.0

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 (39) hide show
  1. package/README.md +2 -1
  2. package/components/detailPanel.vue +0 -1
  3. package/components/pageDialog.vue +0 -1
  4. package/components/pageTableDetail.vue +1 -1
  5. package/layouts/default.vue +1 -2
  6. package/package.json +2 -2
  7. package/styles/dashboardCard.scss +228 -0
  8. package/views/config/dict/components/edit.vue +1 -1
  9. package/views/config/dict/index.vue +1 -2
  10. package/views/config/dictType/components/edit.vue +1 -1
  11. package/views/config/dictType/index.vue +0 -2
  12. package/views/config/system/components/edit.vue +1 -1
  13. package/views/config/system/index.vue +0 -2
  14. package/views/index/components/environmentInfo.vue +65 -81
  15. package/views/index/components/projectStats.vue +392 -0
  16. package/views/index/components/serviceStatus.vue +28 -105
  17. package/views/index/components/systemOverview.vue +41 -1157
  18. package/views/index/components/systemResources.vue +35 -94
  19. package/views/index/index.vue +10 -7
  20. package/views/log/email/index.vue +1 -2
  21. package/views/log/error/index.vue +1 -2
  22. package/views/log/login/index.vue +0 -1
  23. package/views/log/operate/index.vue +0 -1
  24. package/views/login_1/index.vue +1 -2
  25. package/views/people/admin/components/edit.vue +0 -6
  26. package/views/people/admin/index.vue +0 -2
  27. package/views/permission/api/index.vue +1 -2
  28. package/views/permission/menu/index.vue +1 -1
  29. package/views/permission/role/components/api.vue +1 -2
  30. package/views/permission/role/components/edit.vue +0 -6
  31. package/views/permission/role/components/menu.vue +1 -2
  32. package/views/permission/role/index.vue +0 -2
  33. package/views/resource/gallery/index.vue +1 -2
  34. package/views/index/components/addonList.vue +0 -140
  35. package/views/index/components/operationLogs.vue +0 -120
  36. package/views/index/components/performanceMetrics.vue +0 -223
  37. package/views/index/components/quickActions.vue +0 -30
  38. package/views/index/components/systemNotifications.vue +0 -139
  39. package/views/index/components/userInfo.vue +0 -188
@@ -0,0 +1,392 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <ChartIcon />
5
+ <h2>项目统计</h2>
6
+ </div>
7
+
8
+ <div class="section-content">
9
+ <div class="project-row-list">
10
+ <div v-for="item in $Computed.productRows" :key="item.key" class="product-row">
11
+ <div class="product-row-main">
12
+ <div class="product-row-name">
13
+ <span>{{ item.productName }}</span>
14
+ <span v-if="item.productCode" class="product-row-code">({{ item.productCode }})</span>
15
+ </div>
16
+ <div class="product-row-online">
17
+ <span>在线人数</span>
18
+ <strong>{{ item.onlineCount }}</strong>
19
+ </div>
20
+ </div>
21
+
22
+ <div class="project-metrics">
23
+ <div class="metric-card">
24
+ <div class="metric-label">今日</div>
25
+ <div class="metric-values">
26
+ <div class="metric-value">
27
+ <span>PV</span>
28
+ <strong class="metric-value-pv">{{ item.today.pv }}</strong>
29
+ </div>
30
+ <div class="metric-value">
31
+ <span>UV</span>
32
+ <strong class="metric-value-uv">{{ item.today.uv }}</strong>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="metric-card">
38
+ <div class="metric-label">昨日</div>
39
+ <div class="metric-values">
40
+ <div class="metric-value">
41
+ <span>PV</span>
42
+ <strong class="metric-value-pv">{{ item.yesterday.pv }}</strong>
43
+ </div>
44
+ <div class="metric-value">
45
+ <span>UV</span>
46
+ <strong class="metric-value-uv">{{ item.yesterday.uv }}</strong>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <div class="metric-card">
52
+ <div class="metric-label">前天</div>
53
+ <div class="metric-values">
54
+ <div class="metric-value">
55
+ <span>PV</span>
56
+ <strong class="metric-value-pv">{{ item.dayBeforeYesterday.pv }}</strong>
57
+ </div>
58
+ <div class="metric-value">
59
+ <span>UV</span>
60
+ <strong class="metric-value-uv">{{ item.dayBeforeYesterday.uv }}</strong>
61
+ </div>
62
+ </div>
63
+ </div>
64
+
65
+ <div class="metric-card">
66
+ <div class="metric-label">近30天</div>
67
+ <div class="metric-values">
68
+ <div class="metric-value">
69
+ <span>PV</span>
70
+ <strong class="metric-value-pv">{{ item.recent30.pv }}</strong>
71
+ </div>
72
+ <div class="metric-value">
73
+ <span>UV</span>
74
+ <strong class="metric-value-uv">{{ item.recent30.uv }}</strong>
75
+ </div>
76
+ </div>
77
+ </div>
78
+
79
+ <div class="metric-card">
80
+ <div class="metric-label">本月</div>
81
+ <div class="metric-values">
82
+ <div class="metric-value">
83
+ <span>PV</span>
84
+ <strong class="metric-value-pv">{{ item.month.pv }}</strong>
85
+ </div>
86
+ <div class="metric-value">
87
+ <span>UV</span>
88
+ <strong class="metric-value-uv">{{ item.month.uv }}</strong>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script setup>
100
+ import { computed, reactive } from "vue";
101
+
102
+ import { $Http } from "@/plugins/http.js";
103
+
104
+ const $Data = reactive({
105
+ productState: {
106
+ options: []
107
+ }
108
+ });
109
+
110
+ const $Method = {
111
+ getReportDateNumber: function (timestamp) {
112
+ return Number(
113
+ new Intl.DateTimeFormat("en-CA", {
114
+ year: "numeric",
115
+ month: "2-digit",
116
+ day: "2-digit"
117
+ })
118
+ .format(timestamp)
119
+ .replace(/[^0-9]/g, "")
120
+ );
121
+ },
122
+ buildRecentReportDateList: function (limit) {
123
+ const list = [];
124
+
125
+ for (let index = limit - 1; index >= 0; index -= 1) {
126
+ list.push($Method.getReportDateNumber(Date.now() - index * 24 * 60 * 60 * 1000));
127
+ }
128
+
129
+ return list;
130
+ },
131
+ buildFilledTrendDays: function (list, limit) {
132
+ const dateList = $Method.buildRecentReportDateList(limit);
133
+ const dataMap = new Map();
134
+
135
+ for (const item of list) {
136
+ dataMap.set(Number(item?.reportDate || 0), {
137
+ reportDate: Number(item?.reportDate || 0),
138
+ pv: Number(item?.pv || 0),
139
+ uv: Number(item?.uv || 0)
140
+ });
141
+ }
142
+
143
+ return dateList.map((reportDate) => {
144
+ return (
145
+ dataMap.get(reportDate) || {
146
+ reportDate: reportDate,
147
+ pv: 0,
148
+ uv: 0
149
+ }
150
+ );
151
+ });
152
+ },
153
+ sumTrendRange: function (list) {
154
+ let pv = 0;
155
+ let uv = 0;
156
+
157
+ for (const item of list) {
158
+ pv += Number(item?.pv || 0);
159
+ uv += Number(item?.uv || 0);
160
+ }
161
+
162
+ return {
163
+ pv: pv,
164
+ uv: uv
165
+ };
166
+ },
167
+ buildPeriodStats: function (days) {
168
+ const list = $Method.buildFilledTrendDays(days, 30);
169
+ const yesterday = list[list.length - 2] || { pv: 0, uv: 0 };
170
+ const dayBeforeYesterday = list[list.length - 3] || { pv: 0, uv: 0 };
171
+
172
+ return {
173
+ yesterday: {
174
+ pv: Number(yesterday.pv || 0),
175
+ uv: Number(yesterday.uv || 0)
176
+ },
177
+ dayBeforeYesterday: {
178
+ pv: Number(dayBeforeYesterday.pv || 0),
179
+ uv: Number(dayBeforeYesterday.uv || 0)
180
+ },
181
+ recent30: $Method.sumTrendRange(list)
182
+ };
183
+ },
184
+ buildProjectRow: function (rawItem, statsItem) {
185
+ const periodStats = $Method.buildPeriodStats(Array.isArray(statsItem?.days) ? statsItem.days : []);
186
+
187
+ return {
188
+ key: String(rawItem?.key || ""),
189
+ productName: String(rawItem?.productName || "-"),
190
+ productCode: String(rawItem?.productCode || ""),
191
+ productVersion: String(rawItem?.productVersion || ""),
192
+ onlineCount: Number(statsItem?.onlineCount || 0),
193
+ today: {
194
+ pv: Number(statsItem?.today?.pv || 0),
195
+ uv: Number(statsItem?.today?.uv || 0)
196
+ },
197
+ yesterday: periodStats.yesterday,
198
+ dayBeforeYesterday: periodStats.dayBeforeYesterday,
199
+ recent30: periodStats.recent30,
200
+ month: {
201
+ pv: Number(statsItem?.month?.pv || 0),
202
+ uv: Number(statsItem?.month?.uv || 0)
203
+ }
204
+ };
205
+ },
206
+ mapProjectRows: function (onlineStatsData) {
207
+ if (!Array.isArray(onlineStatsData?.products)) {
208
+ return [];
209
+ }
210
+
211
+ return onlineStatsData.products
212
+ .map((item) => {
213
+ const productName = String(item?.productName || "-");
214
+
215
+ return $Method.buildProjectRow(
216
+ {
217
+ key: String(item?.key || productName),
218
+ productName: productName,
219
+ productCode: String(item?.productCode || ""),
220
+ productVersion: String(item?.productVersion || "")
221
+ },
222
+ item
223
+ );
224
+ })
225
+ .filter((item) => {
226
+ return Boolean(item.key);
227
+ });
228
+ },
229
+ fetchData: async function () {
230
+ try {
231
+ const onlineStatsRes = await $Http("/core/tongJi/onlineStats", {}, [""]);
232
+ $Data.productState.options = $Method.mapProjectRows(onlineStatsRes.data);
233
+ } catch (_error) {
234
+ // 静默失败:不阻断页面展示
235
+ }
236
+ }
237
+ };
238
+
239
+ const $Computed = reactive({
240
+ productRows: computed(() => {
241
+ return Array.isArray($Data.productState.options) ? $Data.productState.options.slice() : [];
242
+ })
243
+ });
244
+
245
+ $Method.fetchData();
246
+ </script>
247
+
248
+ <style scoped lang="scss">
249
+ @use "../../../styles/dashboardCard.scss" as dashboardCard;
250
+
251
+ .project-row-list {
252
+ display: flex;
253
+ flex-direction: column;
254
+ gap: 16px;
255
+ }
256
+
257
+ .product-row {
258
+ display: grid;
259
+ grid-template-columns: minmax(180px, 0.75fr) minmax(0, 2.25fr);
260
+ gap: 16px;
261
+ align-items: stretch;
262
+ }
263
+
264
+ .product-row-main {
265
+ display: flex;
266
+ flex-direction: column;
267
+ align-items: flex-start;
268
+ gap: 10px;
269
+ min-width: 0;
270
+ text-align: left;
271
+
272
+ @include dashboardCard.dashboardCardShell();
273
+
274
+ justify-content: flex-start;
275
+ }
276
+
277
+ .product-row-name {
278
+ display: flex;
279
+ align-items: baseline;
280
+ flex-wrap: wrap;
281
+ gap: 4px;
282
+ font-size: 14px;
283
+ font-weight: 700;
284
+ line-height: 1.2;
285
+ color: var(--text-primary);
286
+ }
287
+
288
+ .product-row-code {
289
+ font-size: 12px;
290
+ font-weight: 600;
291
+ color: var(--text-secondary);
292
+ }
293
+
294
+ .metric-label {
295
+ font-size: 14px;
296
+ font-weight: 700;
297
+ line-height: 1.2;
298
+ color: var(--text-primary);
299
+ }
300
+
301
+ .metric-value span,
302
+ .product-row-online span {
303
+ font-size: 12px;
304
+ font-weight: 500;
305
+ line-height: 1.2;
306
+ color: var(--text-secondary);
307
+ }
308
+
309
+ .product-row-online {
310
+ display: flex;
311
+ flex-direction: column;
312
+ align-items: flex-start;
313
+ gap: 6px;
314
+ white-space: nowrap;
315
+ text-align: left;
316
+ }
317
+
318
+ .product-row-online strong {
319
+ @include dashboardCard.dashboardCardNumericValue(#d54941);
320
+
321
+ color: var(--error-color);
322
+ }
323
+
324
+ .project-metrics {
325
+ display: grid;
326
+ grid-template-columns: repeat(5, minmax(0, 1fr));
327
+ gap: 16px;
328
+ text-align: left;
329
+ }
330
+
331
+ .metric-card {
332
+ display: flex;
333
+ flex-direction: column;
334
+ align-items: flex-start;
335
+ gap: 8px;
336
+ text-align: left;
337
+
338
+ @include dashboardCard.dashboardCardShell();
339
+
340
+ justify-content: flex-start;
341
+ min-height: 100%;
342
+ padding: 12px;
343
+ }
344
+
345
+ .metric-values {
346
+ display: grid;
347
+ grid-template-columns: repeat(2, minmax(0, 1fr));
348
+ gap: 10px;
349
+ align-items: end;
350
+ width: 100%;
351
+ }
352
+
353
+ .metric-value {
354
+ display: flex;
355
+ flex-direction: column;
356
+ align-items: flex-start;
357
+ justify-content: flex-end;
358
+ gap: 6px;
359
+ min-width: 0;
360
+ }
361
+
362
+ .metric-value strong {
363
+ @include dashboardCard.dashboardCardNumericValue();
364
+
365
+ text-align: left;
366
+ white-space: nowrap;
367
+ }
368
+
369
+ .metric-value-pv {
370
+ color: #0052d9;
371
+ }
372
+
373
+ .metric-value-uv {
374
+ color: #2ba471;
375
+ }
376
+
377
+ @media (max-width: 1280px) {
378
+ .project-metrics {
379
+ grid-template-columns: repeat(2, minmax(0, 1fr));
380
+ }
381
+
382
+ .product-row {
383
+ grid-template-columns: 1fr;
384
+ }
385
+ }
386
+
387
+ @media (max-width: 720px) {
388
+ .project-metrics {
389
+ grid-template-columns: 1fr;
390
+ }
391
+ }
392
+ </style>
@@ -5,23 +5,24 @@
5
5
  <h2>服务状态</h2>
6
6
  </div>
7
7
  <div class="section-content">
8
- <div class="config-grid">
9
- <div v-for="service in $Data.services" :key="service.name" class="config-card" :class="`config-${service.status}`">
10
- <div class="config-info">
11
- <div class="config-name">{{ service.name }}</div>
12
- <div class="config-status">
13
- {{ $Method.getStatusText(service.status) }}
14
- <span v-if="service.responseTime && service.responseTime !== '-'" class="latency">{{ service.responseTime }}</span>
8
+ <t-row :gutter="[16, 16]" class="dashboard-card-row">
9
+ <t-col v-for="service in $Data.services" :key="service.name" :span="2" class="dashboard-card-col">
10
+ <div class="dashboard-card" :class="$Const.statusStyleMap[service.status]?.card || ''">
11
+ <div class="dashboard-card-content">
12
+ <div class="dashboard-card-label" :class="$Const.statusStyleMap[service.status]?.label || ''">{{ service.name }}</div>
13
+ <div class="dashboard-card-value-row">
14
+ <div class="dashboard-card-main dashboard-card-main--muted">{{ $Method.getStatusText(service.status) }}</div>
15
+ <span v-if="service.responseTime && service.responseTime !== '-'" class="dashboard-card-accent">{{ service.responseTime }}</span>
16
+ </div>
15
17
  </div>
16
18
  </div>
17
- </div>
18
- </div>
19
+ </t-col>
20
+ </t-row>
19
21
  </div>
20
22
  </div>
21
23
  </template>
22
24
 
23
25
  <script setup>
24
- import { CheckCircleIcon } from "tdesign-icons-vue-next";
25
26
  import { reactive } from "vue";
26
27
 
27
28
  import { $Http } from "@/plugins/http.js";
@@ -36,6 +37,20 @@ const $Const = {
36
37
  running: "正常",
37
38
  stopped: "停止",
38
39
  unconfigured: "未配置"
40
+ },
41
+ statusStyleMap: {
42
+ running: {
43
+ card: "dashboard-card--success",
44
+ label: "dashboard-card-label--success"
45
+ },
46
+ stopped: {
47
+ card: "dashboard-card--error",
48
+ label: "dashboard-card-label--error"
49
+ },
50
+ unconfigured: {
51
+ card: "dashboard-card--warning",
52
+ label: "dashboard-card-label--warning"
53
+ }
39
54
  }
40
55
  };
41
56
 
@@ -58,101 +73,9 @@ $Method.fetchData();
58
73
  </script>
59
74
 
60
75
  <style scoped lang="scss">
61
- .config-grid {
62
- display: grid;
63
- grid-template-columns: repeat(4, minmax(0, 1fr));
64
- gap: 10px;
65
-
66
- .config-card {
67
- background: linear-gradient(180deg, rgba(var(--primary-color-rgb), 0.018), rgba(255, 255, 255, 0.96));
68
- border: 1px solid rgba(0, 0, 0, 0.06);
69
- border-radius: 8px;
70
- padding: 14px;
71
- display: flex;
72
- align-items: flex-start;
73
- justify-content: center;
74
- min-height: 92px;
75
- position: relative;
76
- overflow: hidden;
77
- transition: all 0.3s;
78
-
79
- &:hover {
80
- background: rgba(var(--primary-color-rgb), 0.05);
81
- border-color: var(--primary-color);
82
- transform: translateY(-2px);
83
- box-shadow: 0 8px 18px rgba(31, 35, 41, 0.08);
84
- }
85
-
86
- .config-info {
87
- display: flex;
88
- flex-direction: column;
89
- justify-content: center;
90
- gap: 4px;
91
- flex: 1;
92
- width: 100%;
93
- min-width: 0;
94
-
95
- .config-name {
96
- font-size: 14px;
97
- font-weight: 600;
98
- line-height: 1.2;
99
- }
100
-
101
- .config-status {
102
- font-size: 12px;
103
- display: flex;
104
- align-items: baseline;
105
- flex-wrap: wrap;
106
- gap: 4px;
107
- line-height: 1.2;
108
- color: var(--text-secondary);
109
-
110
- .latency {
111
- margin-left: 4px;
112
- font-size: 14px;
113
- font-weight: 700;
114
- line-height: 1.1;
115
- font-variant-numeric: tabular-nums;
116
- color: var(--primary-color);
117
- }
118
- }
119
- }
120
-
121
- &.config-running {
122
- border-color: var(--success-color);
123
-
124
- .config-name {
125
- color: var(--success-color);
126
- }
127
- }
76
+ @use "../../../styles/dashboardCard.scss" as dashboardCard;
128
77
 
129
- &.config-unconfigured {
130
- border-color: var(--warning-color);
131
-
132
- .config-name {
133
- color: var(--warning-color);
134
- }
135
- }
136
-
137
- &.config-stopped {
138
- border-color: var(--error-color);
139
-
140
- .config-name {
141
- color: var(--error-color);
142
- }
143
- }
144
- }
145
- }
146
-
147
- @media (max-width: 1200px) {
148
- .config-grid {
149
- grid-template-columns: repeat(2, minmax(0, 1fr));
150
- }
151
- }
152
-
153
- @media (max-width: 640px) {
154
- .config-grid {
155
- grid-template-columns: 1fr;
156
- }
78
+ .dashboard-card-row {
79
+ @include dashboardCard.dashboardCardStandard();
157
80
  }
158
81
  </style>