befly-admin-ui 1.10.17 → 1.10.19

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 +66 -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 -1065
  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 -158
  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>