befly-admin-ui 1.10.18 → 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 +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
@@ -1,223 +0,0 @@
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
- <div class="section-content">
8
- <div class="performance-grid">
9
- <div class="perf-metric">
10
- <div class="perf-info">
11
- <div class="perf-label">平均响应</div>
12
- <div class="perf-value">{{ $Data.performanceMetrics.avgResponseTime }}ms</div>
13
- </div>
14
- </div>
15
- <div class="perf-metric">
16
- <div class="perf-info">
17
- <div class="perf-label">QPS</div>
18
- <div class="perf-value">{{ $Data.performanceMetrics.qps }}/s</div>
19
- </div>
20
- </div>
21
- <div class="perf-metric">
22
- <div class="perf-info">
23
- <div class="perf-label">错误率</div>
24
- <div class="perf-value">{{ $Data.performanceMetrics.errorRate }}%</div>
25
- </div>
26
- </div>
27
- <div class="perf-metric">
28
- <div class="perf-info">
29
- <div class="perf-label">活跃请求</div>
30
- <div class="perf-value">{{ $Data.performanceMetrics.activeRequests }}</div>
31
- </div>
32
- </div>
33
- </div>
34
- <!-- 最慢接口列表 -->
35
- <div v-if="$Data.slowestApis.length > 0" class="perf-slowest">
36
- <ErrorTriangleIcon />
37
- <div class="perf-slowest-list">
38
- <div v-for="(item, index) in $Data.slowestApis" :key="`${item.path}-${index}`" class="perf-slowest-item">
39
- <span class="perf-slowest-rank">{{ index + 1 }}.</span>
40
- <span class="perf-slowest-path">{{ item.path }}</span>
41
- <span class="perf-slowest-time">{{ item.time }}ms</span>
42
- </div>
43
- </div>
44
- </div>
45
- </div>
46
- </div>
47
- </template>
48
-
49
- <script setup>
50
- import { ChartIcon, ErrorTriangleIcon } from "tdesign-icons-vue-next";
51
- import { reactive } from "vue";
52
-
53
- import { $Http } from "@/plugins/http.js";
54
-
55
- // 组件内部数据
56
- const $Data = reactive({
57
- performanceMetrics: {
58
- avgResponseTime: 0,
59
- qps: 0,
60
- errorRate: 0,
61
- activeRequests: 0,
62
- activeConnections: 0,
63
- slowestApi: null,
64
- slowestApis: []
65
- },
66
- slowestApis: []
67
- });
68
-
69
- const $Method = {
70
- normalizeSlowestApis: function (resData) {
71
- const apiList = [];
72
-
73
- if (Array.isArray(resData?.slowestApis) && resData.slowestApis.length > 0) {
74
- for (const item of resData.slowestApis) {
75
- if (!item || !item.path) {
76
- continue;
77
- }
78
-
79
- apiList.push({
80
- path: String(item.path),
81
- time: Number(item.time || 0)
82
- });
83
- }
84
- } else if (resData?.slowestApi?.path) {
85
- apiList.push({
86
- path: String(resData.slowestApi.path),
87
- time: Number(resData.slowestApi.time || 0)
88
- });
89
- }
90
-
91
- return apiList.slice(0, 3);
92
- },
93
- fetchData: async function () {
94
- try {
95
- const res = await $Http("/core/dashboard/performanceMetrics", {}, [""]);
96
- Object.assign($Data.performanceMetrics, res.data);
97
- $Data.performanceMetrics.activeRequests = Number(res.data?.activeRequests ?? res.data?.activeConnections ?? 0);
98
- $Data.slowestApis = $Method.normalizeSlowestApis(res.data);
99
- } catch (_error) {
100
- // 静默失败:不阻断页面展示
101
- }
102
- }
103
- };
104
-
105
- $Method.fetchData();
106
- </script>
107
-
108
- <style scoped lang="scss">
109
- .performance-grid {
110
- display: grid;
111
- grid-template-columns: repeat(4, minmax(0, 1fr));
112
- gap: 10px;
113
- margin-bottom: 8px;
114
-
115
- .perf-metric {
116
- display: flex;
117
- align-items: flex-start;
118
- justify-content: center;
119
- min-height: 92px;
120
- padding: 14px;
121
- background: linear-gradient(180deg, rgba(var(--primary-color-rgb), 0.018), rgba(255, 255, 255, 0.96));
122
- border-radius: 8px;
123
- border: 1px solid rgba(0, 0, 0, 0.06);
124
- transition: all 0.2s;
125
-
126
- &:hover {
127
- background: rgba(var(--primary-color-rgb), 0.05);
128
- border-color: var(--primary-color);
129
- box-shadow: 0 8px 18px rgba(31, 35, 41, 0.08);
130
- }
131
-
132
- .perf-info {
133
- display: flex;
134
- flex-direction: column;
135
- justify-content: center;
136
- gap: 4px;
137
- flex: 1;
138
- width: 100%;
139
- min-width: 0;
140
-
141
- .perf-label {
142
- font-size: 12px;
143
- color: var(--text-secondary);
144
- line-height: 1.2;
145
- }
146
-
147
- .perf-value {
148
- font-size: 22px;
149
- font-weight: 700;
150
- line-height: 1.1;
151
- letter-spacing: -0.3px;
152
- font-variant-numeric: tabular-nums;
153
- color: var(--primary-color);
154
- }
155
- }
156
- }
157
- }
158
-
159
- @media (max-width: 1200px) {
160
- .performance-grid {
161
- grid-template-columns: repeat(2, minmax(0, 1fr));
162
- }
163
- }
164
-
165
- .perf-slowest {
166
- display: flex;
167
- align-items: flex-start;
168
- gap: 6px;
169
- padding: 8px 10px;
170
- background: rgba(var(--warning-color-rgb), 0.05);
171
- border-radius: 8px;
172
- border: 1px solid rgba(var(--warning-color-rgb), 0.2);
173
- font-size: 12px;
174
- color: var(--warning-color);
175
- word-break: break-word;
176
-
177
- .perf-slowest-list {
178
- display: flex;
179
- flex-direction: column;
180
- gap: 2px;
181
- min-width: 0;
182
- }
183
-
184
- .perf-slowest-item {
185
- display: flex;
186
- align-items: baseline;
187
- gap: 6px;
188
- min-width: 0;
189
- }
190
-
191
- .perf-slowest-rank {
192
- font-weight: 700;
193
- }
194
-
195
- .perf-slowest-path {
196
- min-width: 0;
197
- overflow: hidden;
198
- text-overflow: ellipsis;
199
- white-space: nowrap;
200
- flex: 1;
201
- }
202
-
203
- .perf-slowest-time {
204
- font-weight: 600;
205
- font-variant-numeric: tabular-nums;
206
- }
207
- }
208
-
209
- .warning-tip {
210
- padding: var(--spacing-sm) var(--spacing-md);
211
- background: rgba(var(--warning-color-rgb), 0.05);
212
- border-radius: var(--border-radius-small);
213
- border: 1px solid rgba(var(--warning-color-rgb), 0.2);
214
- font-size: 14px;
215
- color: var(--warning-color);
216
- }
217
-
218
- @media (max-width: 640px) {
219
- .performance-grid {
220
- grid-template-columns: 1fr;
221
- }
222
- }
223
- </style>
@@ -1,30 +0,0 @@
1
- <template>
2
- <div class="section-block">
3
- <div class="section-content">
4
- <TButton theme="primary" size="large" @click="handleClearCache">
5
- <template #prefix>
6
- <RefreshIcon />
7
- </template>
8
- 刷新缓存
9
- </TButton>
10
- </div>
11
- </div>
12
- </template>
13
-
14
- <script setup>
15
- import { RefreshIcon } from "tdesign-icons-vue-next";
16
- import { Button as TButton } from "tdesign-vue-next";
17
-
18
- const handleClearCache = () => {
19
- // TODO: 接入刷新缓存接口
20
- };
21
- </script>
22
-
23
- <style scoped lang="scss">
24
- .section-block {
25
- .section-content {
26
- display: flex;
27
- justify-content: center;
28
- }
29
- }
30
- </style>
@@ -1,139 +0,0 @@
1
- <template>
2
- <div class="section-block">
3
- <div class="section-header flex items-center gap-2">
4
- <NotificationIcon />
5
- <h2>系统通知</h2>
6
- </div>
7
- <div class="section-content">
8
- <div class="notification-compact-list">
9
- <div v-for="notification in $Const.notifications" :key="notification.id" class="notification-compact-item">
10
- <div class="notification-icon" :class="`type-${notification.type}`">
11
- <InfoCircleIcon v-if="notification.type === 'info'" />
12
- <CheckCircleIcon v-else-if="notification.type === 'success'" />
13
- <ErrorTriangleIcon v-else-if="notification.type === 'warning'" />
14
- <CloseCircleIcon v-else-if="notification.type === 'error'" />
15
- <NotificationIcon v-else />
16
- </div>
17
- <div class="notification-content">
18
- <span class="notification-title">{{ notification.title }}</span>
19
- <span class="notification-time">{{ $Method.formatTime(notification.createdAt) }}</span>
20
- </div>
21
- <TTag v-if="!notification.isRead" type="primary" size="small">新</TTag>
22
- </div>
23
- </div>
24
- </div>
25
- </div>
26
- </template>
27
-
28
- <script setup>
29
- import { CheckCircleIcon, CloseCircleIcon, ErrorTriangleIcon, InfoCircleIcon, NotificationIcon } from "tdesign-icons-vue-next";
30
- import { Tag as TTag } from "tdesign-vue-next";
31
- import { reactive } from "vue";
32
-
33
- // 组件内部数据
34
- const $Const = {
35
- notifications: [
36
- { id: 1, type: "warning", title: "系统更新提醒 - v1.1.0 版本已发布", isRead: false, createdAt: Date.now() - 3600000 },
37
- { id: 2, type: "info", title: "数据备份完成 - 今日凌晨自动备份成功", isRead: true, createdAt: Date.now() - 21600000 },
38
- { id: 3, type: "error", title: "SSL证书即将过期 - 请及时更新证书", isRead: false, createdAt: Date.now() - 86400000 },
39
- { id: 4, type: "success", title: "性能优化完成 - 响应速度提升30%", isRead: true, createdAt: Date.now() - 172800000 }
40
- ]
41
- };
42
-
43
- const $Method = {
44
- formatTime: function (timestamp) {
45
- const date = new Date(timestamp);
46
- const now = Date.now();
47
- const diff = now - timestamp;
48
-
49
- if (diff < 3600000) {
50
- return `${Math.floor(diff / 60000)}分钟前`;
51
- }
52
- if (diff < 86400000) {
53
- return `${Math.floor(diff / 3600000)}小时前`;
54
- }
55
-
56
- const month = String(date.getMonth() + 1).padStart(2, "0");
57
- const day = String(date.getDate()).padStart(2, "0");
58
- return `${month}-${day}`;
59
- }
60
- };
61
- </script>
62
-
63
- <style scoped lang="scss">
64
- .notification-compact-list {
65
- display: flex;
66
- flex-direction: column;
67
- gap: var(--spacing-xs);
68
-
69
- .notification-compact-item {
70
- display: flex;
71
- align-items: center;
72
- gap: var(--spacing-sm);
73
- padding: var(--spacing-sm) var(--spacing-md);
74
- background: rgba(var(--primary-color-rgb), 0.02);
75
- border-radius: var(--border-radius-small);
76
- border: 1px solid var(--border-color);
77
- transition: all 0.2s;
78
-
79
- &:hover {
80
- background: rgba(var(--primary-color-rgb), 0.05);
81
- border-color: var(--primary-color);
82
- }
83
-
84
- .notification-icon {
85
- display: flex;
86
- align-items: center;
87
- justify-content: center;
88
- width: 32px;
89
- height: 32px;
90
- border-radius: var(--border-radius-small);
91
- flex-shrink: 0;
92
-
93
- &.type-info {
94
- background: rgba(var(--primary-color-rgb), 0.1);
95
- color: var(--primary-color);
96
- }
97
-
98
- &.type-success {
99
- background: rgba(var(--success-color-rgb), 0.1);
100
- color: var(--success-color);
101
- }
102
-
103
- &.type-warning {
104
- background: rgba(var(--warning-color-rgb), 0.1);
105
- color: var(--warning-color);
106
- }
107
-
108
- &.type-error {
109
- background: rgba(var(--error-color-rgb), 0.1);
110
- color: var(--error-color);
111
- }
112
- }
113
-
114
- .notification-content {
115
- display: flex;
116
- align-items: center;
117
- gap: var(--spacing-sm);
118
- flex: 1;
119
- min-width: 0;
120
-
121
- .notification-title {
122
- font-size: 14px;
123
- color: var(--text-primary);
124
- font-weight: 500;
125
- overflow: hidden;
126
- text-overflow: ellipsis;
127
- white-space: nowrap;
128
- flex: 1;
129
- }
130
-
131
- .notification-time {
132
- font-size: 14px;
133
- color: var(--text-placeholder);
134
- flex-shrink: 0;
135
- }
136
- }
137
- }
138
- }
139
- </style>
@@ -1,188 +0,0 @@
1
- <template>
2
- <div class="section-block user-info-card">
3
- <div class="user-header">
4
- <div class="user-avatar">
5
- <UserIcon />
6
- </div>
7
- <div class="user-basic">
8
- <div class="user-name">
9
- {{ $Data.userInfo.nickname || $Data.userInfo.name || $Data.userInfo.username || "未设置" }}
10
- </div>
11
- <div class="user-role">{{ $Data.userInfo.role?.name || "普通用户" }}</div>
12
- </div>
13
- </div>
14
- <div class="user-details">
15
- <div class="detail-item">
16
- <MailIcon />
17
- <span>{{ $Data.userInfo.email || "未设置" }}</span>
18
- </div>
19
- <div v-if="$Data.userInfo.phone" class="detail-item">
20
- <CallIcon />
21
- <span>{{ $Data.userInfo.phone }}</span>
22
- </div>
23
- <div v-if="$Data.userInfo.lastLoginTime" class="detail-item">
24
- <TimeIcon />
25
- <span>{{ $Method.formatTime($Data.userInfo.lastLoginTime) }}</span>
26
- </div>
27
- </div>
28
-
29
- <!-- 仅 dev 角色显示刷新缓存按钮 -->
30
- <div v-if="$Data.userInfo.roleCode === 'dev'" class="user-actions">
31
- <TButton theme="primary" size="mini" :loading="$Data.refreshing" @click="$Method.handleRefreshCache">
32
- <template #icon>
33
- <RefreshIcon />
34
- </template>
35
- 刷新缓存
36
- </TButton>
37
- </div>
38
- </div>
39
- </template>
40
-
41
- <script setup>
42
- import { CallIcon, MailIcon, RefreshIcon, TimeIcon, UserIcon } from "tdesign-icons-vue-next";
43
- import { Button as TButton, MessagePlugin } from "tdesign-vue-next";
44
- import { reactive } from "vue";
45
-
46
- import { $Http } from "@/plugins/http.js";
47
-
48
- // 响应式数据
49
- const $Data = reactive({
50
- userInfo: {},
51
- refreshing: false
52
- });
53
-
54
- const $Method = {
55
- fetchData: async function () {
56
- try {
57
- const res = await $Http("/core/admin/detail", {}, [""]);
58
- Object.assign($Data.userInfo, res.data);
59
- } catch (error) {
60
- MessagePlugin.error(error.msg || error.message || "获取用户信息失败");
61
- }
62
- },
63
- handleRefreshCache: async function () {
64
- try {
65
- $Data.refreshing = true;
66
- const result = await $Http("/core/admin/cacheRefresh");
67
-
68
- const apis = result.data.apis;
69
- const menus = result.data.menus;
70
- const roles = result.data.roles;
71
- const messages = [];
72
-
73
- if (apis && apis.success) {
74
- messages.push(`接口缓存: ${apis.count} 个`);
75
- }
76
- if (menus && menus.success) {
77
- messages.push(`菜单缓存: ${menus.count} 个`);
78
- }
79
- if (roles && roles.success) {
80
- messages.push(`角色缓存: ${roles.count} 个`);
81
- }
82
-
83
- MessagePlugin.success(`缓存刷新成功!${messages.join(",")}`);
84
- } catch (error) {
85
- MessagePlugin.error(error.msg || error.message || "刷新缓存失败,请稍后重试");
86
- } finally {
87
- $Data.refreshing = false;
88
- }
89
- },
90
- formatTime: function (timestamp) {
91
- if (!timestamp) return "";
92
- const date = new Date(Number(timestamp));
93
- const now = new Date();
94
- const diff = now.getTime() - date.getTime();
95
-
96
- if (diff < 60000) {
97
- return "刚刚";
98
- }
99
- if (diff < 3600000) {
100
- return `${Math.floor(diff / 60000)}分钟前`;
101
- }
102
- if (diff < 86400000) {
103
- return `${Math.floor(diff / 3600000)}小时前`;
104
- }
105
- if (diff < 604800000) {
106
- return `${Math.floor(diff / 86400000)}天前`;
107
- }
108
- return `${date.getMonth() + 1}月${date.getDate()}日`;
109
- }
110
- };
111
-
112
- $Method.fetchData();
113
- </script>
114
-
115
- <style scoped lang="scss">
116
- .user-info-card {
117
- background-color: #fff;
118
- padding: 15px;
119
- .user-header {
120
- display: flex;
121
- align-items: center;
122
- gap: 12px;
123
- padding-bottom: 12px;
124
- border-bottom: 1px solid var(--border-color);
125
-
126
- .user-avatar {
127
- width: 48px;
128
- height: 48px;
129
- background: linear-gradient(135deg, var(--primary-color), #764ba2);
130
- border-radius: 50%;
131
- display: flex;
132
- align-items: center;
133
- justify-content: center;
134
- color: white;
135
- flex-shrink: 0;
136
- }
137
-
138
- .user-basic {
139
- flex: 1;
140
- min-width: 0;
141
-
142
- .user-name {
143
- font-size: 16px;
144
- font-weight: 600;
145
- color: var(--text-primary);
146
- margin-bottom: 4px;
147
- overflow: hidden;
148
- text-overflow: ellipsis;
149
- white-space: nowrap;
150
- }
151
-
152
- .user-role {
153
- font-size: 12px;
154
- color: var(--text-secondary);
155
- }
156
- }
157
- }
158
-
159
- .user-details {
160
- display: flex;
161
- flex-direction: column;
162
- gap: 8px;
163
- margin-top: 12px;
164
-
165
- .detail-item {
166
- display: flex;
167
- align-items: center;
168
- gap: 8px;
169
- font-size: 12px;
170
- color: var(--text-secondary);
171
-
172
- span {
173
- overflow: hidden;
174
- text-overflow: ellipsis;
175
- white-space: nowrap;
176
- }
177
- }
178
- }
179
-
180
- .user-actions {
181
- margin-top: 16px;
182
- padding-top: 12px;
183
- border-top: 1px solid var(--border-color);
184
- display: flex;
185
- justify-content: center;
186
- }
187
- }
188
- </style>