befly-admin-ui 1.8.14

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 (46) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +188 -0
  3. package/jsconfig.json +14 -0
  4. package/package.json +51 -0
  5. package/styles/variables.scss +148 -0
  6. package/utils/arrayToTree.js +115 -0
  7. package/utils/cleanParams.js +29 -0
  8. package/utils/fieldClear.js +62 -0
  9. package/utils/genShortId.js +12 -0
  10. package/utils/hashPassword.js +9 -0
  11. package/utils/scanViewsDir.js +120 -0
  12. package/utils/withDefaultColumns.js +46 -0
  13. package/views/config/dict/components/edit.vue +120 -0
  14. package/views/config/dict/index.vue +188 -0
  15. package/views/config/dictType/components/edit.vue +110 -0
  16. package/views/config/dictType/index.vue +153 -0
  17. package/views/config/index.vue +3 -0
  18. package/views/config/system/components/edit.vue +184 -0
  19. package/views/config/system/index.vue +188 -0
  20. package/views/index/components/addonList.vue +148 -0
  21. package/views/index/components/environmentInfo.vue +116 -0
  22. package/views/index/components/operationLogs.vue +127 -0
  23. package/views/index/components/performanceMetrics.vue +153 -0
  24. package/views/index/components/quickActions.vue +30 -0
  25. package/views/index/components/serviceStatus.vue +197 -0
  26. package/views/index/components/systemNotifications.vue +144 -0
  27. package/views/index/components/systemOverview.vue +194 -0
  28. package/views/index/components/systemResources.vue +121 -0
  29. package/views/index/components/userInfo.vue +210 -0
  30. package/views/index/index.vue +67 -0
  31. package/views/jsconfig.json +15 -0
  32. package/views/log/email/index.vue +221 -0
  33. package/views/log/index.vue +3 -0
  34. package/views/log/login/index.vue +95 -0
  35. package/views/log/operate/index.vue +169 -0
  36. package/views/login_1/index.vue +400 -0
  37. package/views/people/admin/components/edit.vue +173 -0
  38. package/views/people/admin/index.vue +121 -0
  39. package/views/people/index.vue +3 -0
  40. package/views/permission/api/index.vue +146 -0
  41. package/views/permission/index.vue +3 -0
  42. package/views/permission/menu/index.vue +109 -0
  43. package/views/permission/role/components/api.vue +371 -0
  44. package/views/permission/role/components/edit.vue +143 -0
  45. package/views/permission/role/components/menu.vue +310 -0
  46. package/views/permission/role/index.vue +175 -0
@@ -0,0 +1,194 @@
1
+ <template>
2
+ <div class="section-block">
3
+ <div class="section-header flex items-center gap-2">
4
+ <InfoCircleIcon />
5
+ <h2>系统概览</h2>
6
+ </div>
7
+ <div class="section-content">
8
+ <div class="info-block">
9
+ <div class="stats-grid">
10
+ <div class="stat-box stat-primary">
11
+ <MenuIcon />
12
+ <div class="stat-content">
13
+ <div class="stat-value">{{ permissionStats.menuCount }}</div>
14
+ <div class="stat-label">菜单总数</div>
15
+ </div>
16
+ </div>
17
+ <div class="stat-box stat-success">
18
+ <LinkIcon />
19
+ <div class="stat-content">
20
+ <div class="stat-value">{{ permissionStats.apiCount }}</div>
21
+ <div class="stat-label">接口总数</div>
22
+ </div>
23
+ </div>
24
+ <div class="stat-box stat-warning">
25
+ <UsergroupIcon />
26
+ <div class="stat-content">
27
+ <div class="stat-value">{{ permissionStats.roleCount }}</div>
28
+ <div class="stat-label">角色总数</div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import { reactive } from "vue";
39
+ import { InfoCircleIcon, LinkIcon, MenuIcon, UsergroupIcon } from "tdesign-icons-vue-next";
40
+ import { $Http } from "@/plugins/http";
41
+
42
+ // 组件内部数据
43
+ const permissionStats = reactive({
44
+ menuCount: 0,
45
+ apiCount: 0,
46
+ roleCount: 0
47
+ });
48
+
49
+ // 获取数据
50
+ const fetchData = async () => {
51
+ try {
52
+ const { data } = await $Http.post(
53
+ "/core/dashboard/systemOverview",
54
+ {},
55
+ {
56
+ dropValues: [""]
57
+ }
58
+ );
59
+ Object.assign(permissionStats, data);
60
+ } catch (_error) {
61
+ // 静默失败:不阻断页面展示
62
+ }
63
+ };
64
+
65
+ fetchData();
66
+ </script>
67
+
68
+ <style scoped lang="scss">
69
+ .info-block {
70
+ background: transparent;
71
+ border: none;
72
+ padding: 0;
73
+ height: 100%;
74
+
75
+ .info-header {
76
+ display: flex;
77
+ align-items: center;
78
+ gap: 6px;
79
+ padding-bottom: 8px;
80
+ margin-bottom: 12px;
81
+ border-bottom: 2px solid var(--primary-color);
82
+
83
+ .info-title {
84
+ font-size: 14px;
85
+ font-weight: 600;
86
+ color: var(--text-primary);
87
+ }
88
+ }
89
+
90
+ .info-grid-compact {
91
+ display: grid;
92
+ grid-template-columns: repeat(3, 1fr);
93
+ gap: 10px;
94
+
95
+ .info-grid-item {
96
+ display: flex;
97
+ justify-content: space-between;
98
+ align-items: center;
99
+ padding: 10px 12px;
100
+ background: rgba(var(--primary-color-rgb), 0.02);
101
+ border-radius: var(--border-radius-small);
102
+ border: 1px solid var(--border-color);
103
+ transition: all 0.2s ease;
104
+
105
+ &:hover {
106
+ background: rgba(var(--primary-color-rgb), 0.05);
107
+ border-color: var(--primary-color);
108
+ }
109
+
110
+ .label {
111
+ font-size: 14px;
112
+ color: var(--text-secondary);
113
+ font-weight: 500;
114
+ }
115
+
116
+ .value {
117
+ font-size: 14px;
118
+ color: var(--text-primary);
119
+ font-weight: 600;
120
+
121
+ &.highlight {
122
+ color: var(--primary-color);
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ .stats-grid {
130
+ display: grid;
131
+ grid-template-columns: repeat(3, 1fr);
132
+ gap: 10px;
133
+
134
+ .stat-box {
135
+ background: rgba(var(--primary-color-rgb), 0.02);
136
+ border: 1px solid var(--border-color);
137
+ border-radius: 6px;
138
+ padding: 12px;
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 10px;
142
+ transition: all 0.3s;
143
+
144
+ &:hover {
145
+ background: rgba(var(--primary-color-rgb), 0.05);
146
+ border-color: var(--primary-color);
147
+ transform: translateY(-2px);
148
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
149
+ }
150
+
151
+ .stat-content {
152
+ flex: 1;
153
+
154
+ .stat-value {
155
+ font-size: 20px;
156
+ font-weight: 700;
157
+ margin-bottom: 2px;
158
+ }
159
+
160
+ .stat-label {
161
+ font-size: 14px;
162
+ color: var(--text-secondary);
163
+ }
164
+ }
165
+
166
+ &.stat-primary {
167
+ border-color: var(--primary-color);
168
+ background: linear-gradient(135deg, rgba(var(--primary-color-rgb), 0.05), white);
169
+
170
+ .stat-value {
171
+ color: var(--primary-color);
172
+ }
173
+ }
174
+
175
+ &.stat-success {
176
+ border-color: var(--success-color);
177
+ background: linear-gradient(135deg, rgba(var(--success-color-rgb), 0.05), white);
178
+
179
+ .stat-value {
180
+ color: var(--success-color);
181
+ }
182
+ }
183
+
184
+ &.stat-warning {
185
+ border-color: var(--warning-color);
186
+ background: linear-gradient(135deg, rgba(var(--warning-color-rgb), 0.05), white);
187
+
188
+ .stat-value {
189
+ color: var(--warning-color);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ </style>
@@ -0,0 +1,121 @@
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="resource-compact-list">
9
+ <div class="resource-compact-item">
10
+ <div class="resource-compact-header">
11
+ <CpuIcon />
12
+ <span class="resource-label">CPU</span>
13
+ <span class="resource-value">{{ systemResources.cpu.usage }}%</span>
14
+ <span class="resource-desc">{{ systemResources.cpu.cores }}核心</span>
15
+ </div>
16
+ <TProgress :percentage="systemResources.cpu.usage" :status="getProgressColor(systemResources.cpu.usage)" />
17
+ </div>
18
+ <div class="resource-compact-item">
19
+ <div class="resource-compact-header">
20
+ <SystemStorageIcon />
21
+ <span class="resource-label">内存</span>
22
+ <span class="resource-value">{{ systemResources.memory.percentage }}%</span>
23
+ <span class="resource-desc">{{ systemResources.memory.used }}GB / {{ systemResources.memory.total }}GB</span>
24
+ </div>
25
+ <TProgress :percentage="systemResources.memory.percentage" :status="getProgressColor(systemResources.memory.percentage)" />
26
+ </div>
27
+ <div class="resource-compact-item">
28
+ <div class="resource-compact-header">
29
+ <HardDiskStorageIcon />
30
+ <span class="resource-label">磁盘</span>
31
+ <span class="resource-value">{{ systemResources.disk.percentage }}%</span>
32
+ <span class="resource-desc">{{ systemResources.disk.used }}GB / {{ systemResources.disk.total }}GB</span>
33
+ </div>
34
+ <TProgress :percentage="systemResources.disk.percentage" :status="getProgressColor(systemResources.disk.percentage)" />
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ </template>
40
+
41
+ <script setup lang="ts">
42
+ import { reactive } from "vue";
43
+ import { Progress as TProgress } from "tdesign-vue-next";
44
+ import { ChartIcon, CpuIcon, HardDiskStorageIcon, SystemStorageIcon } from "tdesign-icons-vue-next";
45
+ import { $Http } from "@/plugins/http";
46
+
47
+ type SystemResourcesInfo = {
48
+ cpu: { usage: number; cores: number };
49
+ memory: { used: number; total: number; percentage: number };
50
+ disk: { used: number; total: number; percentage: number };
51
+ };
52
+
53
+ // 组件内部数据
54
+ const systemResources = reactive<SystemResourcesInfo>({
55
+ cpu: { usage: 0, cores: 0 },
56
+ memory: { used: 0, total: 0, percentage: 0 },
57
+ disk: { used: 0, total: 0, percentage: 0 }
58
+ });
59
+
60
+ // 获取数据
61
+ const fetchData = async () => {
62
+ try {
63
+ const { data } = await $Http.post(
64
+ "/core/dashboard/systemResources",
65
+ {},
66
+ {
67
+ dropValues: [""]
68
+ }
69
+ );
70
+ Object.assign(systemResources, data);
71
+ } catch (_error) {
72
+ // 静默失败:不阻断页面展示
73
+ }
74
+ };
75
+
76
+ fetchData();
77
+
78
+ // 工具函数
79
+ const getProgressColor = (percentage: number): string => {
80
+ if (percentage < 50) return "success";
81
+ if (percentage < 80) return "warning";
82
+ return "danger";
83
+ };
84
+ </script>
85
+
86
+ <style scoped lang="scss">
87
+ .resource-compact-list {
88
+ display: grid;
89
+ grid-template-columns: repeat(3, 1fr);
90
+ gap: var(--spacing-md);
91
+
92
+ .resource-compact-item {
93
+ .resource-compact-header {
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 10px;
97
+ margin-bottom: 8px;
98
+
99
+ .resource-label {
100
+ font-size: 14px;
101
+ font-weight: 600;
102
+ color: var(--text-secondary);
103
+ min-width: 50px;
104
+ }
105
+
106
+ .resource-value {
107
+ font-size: 16px;
108
+ font-weight: 700;
109
+ color: var(--primary-color);
110
+ min-width: 60px;
111
+ }
112
+
113
+ .resource-desc {
114
+ font-size: 14px;
115
+ color: var(--text-placeholder);
116
+ flex: 1;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ </style>
@@ -0,0 +1,210 @@
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>{{ 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="handleRefreshCache">
32
+ <template #icon>
33
+ <RefreshIcon />
34
+ </template>
35
+ 刷新缓存
36
+ </TButton>
37
+ </div>
38
+ </div>
39
+ </template>
40
+
41
+ <script setup lang="ts">
42
+ import { reactive } from "vue";
43
+ import { Button as TButton, MessagePlugin } from "tdesign-vue-next";
44
+ import { CallIcon, MailIcon, RefreshIcon, TimeIcon, UserIcon } from "tdesign-icons-vue-next";
45
+ import { $Http } from "@/plugins/http";
46
+
47
+ type UserInfo = {
48
+ nickname?: string;
49
+ name?: string;
50
+ username?: string;
51
+ role?: {
52
+ name?: string;
53
+ };
54
+ roleCode?: string;
55
+ email?: string;
56
+ phone?: string;
57
+ lastLoginTime?: number | string;
58
+ };
59
+
60
+ // 响应式数据
61
+ const $Data = reactive({
62
+ userInfo: {} as UserInfo,
63
+ refreshing: false
64
+ });
65
+
66
+ async function fetchData(): Promise<void> {
67
+ try {
68
+ const { data } = await $Http.post(
69
+ "/core/admin/detail",
70
+ {},
71
+ {
72
+ dropValues: [""]
73
+ }
74
+ );
75
+ Object.assign($Data.userInfo, data as UserInfo);
76
+ } catch (_error) {
77
+ MessagePlugin.error("获取用户信息失败");
78
+ }
79
+ }
80
+
81
+ async function handleRefreshCache(): Promise<void> {
82
+ try {
83
+ $Data.refreshing = true;
84
+ const result = await $Http.post("/core/admin/cacheRefresh");
85
+
86
+ if (result.code === 0) {
87
+ const apis = result.data.apis;
88
+ const menus = result.data.menus;
89
+ const roles = result.data.roles;
90
+ const messages: string[] = [];
91
+
92
+ if (apis && apis.success) {
93
+ messages.push(`接口缓存: ${apis.count} 个`);
94
+ }
95
+ if (menus && menus.success) {
96
+ messages.push(`菜单缓存: ${menus.count} 个`);
97
+ }
98
+ if (roles && roles.success) {
99
+ messages.push(`角色缓存: ${roles.count} 个`);
100
+ }
101
+
102
+ MessagePlugin.success(`缓存刷新成功!${messages.join(",")}`);
103
+ } else {
104
+ MessagePlugin.warning(result.msg || "部分缓存刷新失败");
105
+ }
106
+ } catch (_error) {
107
+ MessagePlugin.error("刷新缓存失败,请稍后重试");
108
+ } finally {
109
+ $Data.refreshing = false;
110
+ }
111
+ }
112
+
113
+ function formatTime(timestamp: unknown): string {
114
+ if (!timestamp) return "";
115
+ const date = new Date(Number(timestamp));
116
+ const now = new Date();
117
+ const diff = now.getTime() - date.getTime();
118
+
119
+ if (diff < 60000) {
120
+ return "刚刚";
121
+ }
122
+ if (diff < 3600000) {
123
+ return `${Math.floor(diff / 60000)}分钟前`;
124
+ }
125
+ if (diff < 86400000) {
126
+ return `${Math.floor(diff / 3600000)}小时前`;
127
+ }
128
+ if (diff < 604800000) {
129
+ return `${Math.floor(diff / 86400000)}天前`;
130
+ }
131
+ return `${date.getMonth() + 1}月${date.getDate()}日`;
132
+ }
133
+
134
+ fetchData();
135
+ </script>
136
+
137
+ <style scoped lang="scss">
138
+ .user-info-card {
139
+ background-color: #fff;
140
+ padding: 15px;
141
+ .user-header {
142
+ display: flex;
143
+ align-items: center;
144
+ gap: 12px;
145
+ padding-bottom: 12px;
146
+ border-bottom: 1px solid var(--border-color);
147
+
148
+ .user-avatar {
149
+ width: 48px;
150
+ height: 48px;
151
+ background: linear-gradient(135deg, var(--primary-color), #764ba2);
152
+ border-radius: 50%;
153
+ display: flex;
154
+ align-items: center;
155
+ justify-content: center;
156
+ color: white;
157
+ flex-shrink: 0;
158
+ }
159
+
160
+ .user-basic {
161
+ flex: 1;
162
+ min-width: 0;
163
+
164
+ .user-name {
165
+ font-size: 16px;
166
+ font-weight: 600;
167
+ color: var(--text-primary);
168
+ margin-bottom: 4px;
169
+ overflow: hidden;
170
+ text-overflow: ellipsis;
171
+ white-space: nowrap;
172
+ }
173
+
174
+ .user-role {
175
+ font-size: 12px;
176
+ color: var(--text-secondary);
177
+ }
178
+ }
179
+ }
180
+
181
+ .user-details {
182
+ display: flex;
183
+ flex-direction: column;
184
+ gap: 8px;
185
+ margin-top: 12px;
186
+
187
+ .detail-item {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 8px;
191
+ font-size: 12px;
192
+ color: var(--text-secondary);
193
+
194
+ span {
195
+ overflow: hidden;
196
+ text-overflow: ellipsis;
197
+ white-space: nowrap;
198
+ }
199
+ }
200
+ }
201
+
202
+ .user-actions {
203
+ margin-top: 16px;
204
+ padding-top: 12px;
205
+ border-top: 1px solid var(--border-color);
206
+ display: flex;
207
+ justify-content: center;
208
+ }
209
+ }
210
+ </style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <div class="dashboard-container">
3
+ <!-- 第一行:系统概览(全宽) -->
4
+ <div class="dashboard-row full-width">
5
+ <SystemOverview />
6
+ </div>
7
+
8
+ <!-- 第二行:服务状态 + 系统资源 -->
9
+ <div class="dashboard-row two-columns">
10
+ <ServiceStatus />
11
+ <SystemResources />
12
+ </div>
13
+
14
+ <!-- 第三行:性能指标 + 环境信息 -->
15
+ <div class="dashboard-row two-columns">
16
+ <PerformanceMetrics />
17
+ <EnvironmentInfo />
18
+ </div>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import SystemOverview from "./components/systemOverview.vue";
24
+ import ServiceStatus from "./components/serviceStatus.vue";
25
+ import SystemResources from "./components/systemResources.vue";
26
+ import PerformanceMetrics from "./components/performanceMetrics.vue";
27
+ import EnvironmentInfo from "./components/environmentInfo.vue";
28
+ </script>
29
+
30
+ <style scoped lang="scss">
31
+ .dashboard-container {
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: var(--layout-gap);
35
+ overflow-y: auto;
36
+ height: 100%;
37
+ padding: 0;
38
+
39
+ .dashboard-row {
40
+ display: flex;
41
+ gap: var(--layout-gap);
42
+
43
+ &.full-width {
44
+ > * {
45
+ flex: 1;
46
+ }
47
+ }
48
+
49
+ &.two-columns {
50
+ > * {
51
+ flex: 1;
52
+ min-width: 0;
53
+ }
54
+ }
55
+ }
56
+
57
+ // 每个组件都是独立的卡片
58
+ :deep(.section-block) {
59
+ background: var(--bg-color-container);
60
+ border-radius: var(--card-radius);
61
+ box-shadow: var(--shadow-1);
62
+ padding: var(--spacing-md);
63
+ border: none;
64
+ height: 100%;
65
+ }
66
+ }
67
+ </style>
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../../jsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "verbatimModuleSyntax": true,
6
+ "allowJs": true,
7
+ "types": ["vite/client"],
8
+ "baseUrl": ".",
9
+ "paths": {
10
+ "@/*": ["../../admin/src/*"]
11
+ }
12
+ },
13
+ "include": ["**/*.vue", "**/*.js"],
14
+ "exclude": ["node_modules", "dist", "logs", "temp"]
15
+ }