@befly-addon/admin 1.1.35 → 1.2.1

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/apis/admin/cacheRefresh.ts +2 -2
  2. package/apis/menu/all.ts +8 -9
  3. package/apis/menu/list.ts +1 -2
  4. package/apis/role/apiSave.ts +3 -3
  5. package/apis/role/apis.ts +3 -3
  6. package/apis/role/menuSave.ts +2 -2
  7. package/apis/role/menus.ts +3 -3
  8. package/package.json +21 -21
  9. package/plugins/email.ts +3 -8
  10. package/tables/api.json +1 -20
  11. package/tables/menu.json +6 -11
  12. package/tables/role.json +4 -4
  13. package/views/403_1/index.vue +0 -75
  14. package/views/config/dict/components/edit.vue +0 -109
  15. package/views/config/dict/index.vue +0 -266
  16. package/views/config/dictType/components/edit.vue +0 -100
  17. package/views/config/dictType/index.vue +0 -244
  18. package/views/config/index.vue +0 -12
  19. package/views/config/system/components/edit.vue +0 -171
  20. package/views/config/system/index.vue +0 -286
  21. package/views/index/components/addonList.vue +0 -132
  22. package/views/index/components/environmentInfo.vue +0 -100
  23. package/views/index/components/operationLogs.vue +0 -112
  24. package/views/index/components/performanceMetrics.vue +0 -145
  25. package/views/index/components/quickActions.vue +0 -30
  26. package/views/index/components/serviceStatus.vue +0 -192
  27. package/views/index/components/systemNotifications.vue +0 -137
  28. package/views/index/components/systemOverview.vue +0 -190
  29. package/views/index/components/systemResources.vue +0 -111
  30. package/views/index/components/userInfo.vue +0 -204
  31. package/views/index/index.vue +0 -74
  32. package/views/log/email/index.vue +0 -292
  33. package/views/log/index.vue +0 -12
  34. package/views/log/login/index.vue +0 -187
  35. package/views/log/operate/index.vue +0 -249
  36. package/views/login_1/index.vue +0 -415
  37. package/views/people/admin/components/edit.vue +0 -168
  38. package/views/people/admin/index.vue +0 -240
  39. package/views/people/index.vue +0 -12
  40. package/views/permission/api/index.vue +0 -149
  41. package/views/permission/index.vue +0 -12
  42. package/views/permission/menu/index.vue +0 -128
  43. package/views/permission/role/components/api.vue +0 -261
  44. package/views/permission/role/components/edit.vue +0 -142
  45. package/views/permission/role/components/menu.vue +0 -116
  46. package/views/permission/role/index.vue +0 -263
@@ -1,249 +0,0 @@
1
- <template>
2
- <div class="page-operate-log page-table">
3
- <div class="main-tool">
4
- <div class="left">
5
- <TSelect v-model="$Data.filter.module" placeholder="操作模块" clearable style="width: 150px" @change="$Method.handleFilter">
6
- <TOption v-for="item in $Data.moduleOptions" :key="item.value" :label="item.label" :value="item.value" />
7
- </TSelect>
8
- <TSelect v-model="$Data.filter.action" placeholder="操作类型" clearable style="width: 150px" @change="$Method.handleFilter">
9
- <TOption v-for="item in $Data.actionOptions" :key="item.value" :label="item.label" :value="item.value" />
10
- </TSelect>
11
- <TSelect v-model="$Data.filter.result" placeholder="操作结果" clearable style="width: 120px" @change="$Method.handleFilter">
12
- <TOption label="成功" :value="1" />
13
- <TOption label="失败" :value="0" />
14
- </TSelect>
15
- </div>
16
- <div class="right">
17
- <TButton shape="circle" @click="$Method.handleRefresh">
18
- <template #icon>
19
- <ILucideRotateCw />
20
- </template>
21
- </TButton>
22
- </div>
23
- </div>
24
-
25
- <div class="main-content">
26
- <div class="main-table">
27
- <TTable :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="100%" active-row-type="single" @active-change="$Method.onActiveChange">
28
- <template #result="{ row }">
29
- <TTag v-if="row.result === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
30
- <TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
31
- </template>
32
- <template #operateTime="{ row }">
33
- {{ $Method.formatTime(row.operateTime) }}
34
- </template>
35
- <template #duration="{ row }">
36
- <TTag shape="round" :theme="row.duration > 1000 ? 'warning' : 'default'" variant="light-outline">{{ row.duration }}ms</TTag>
37
- </template>
38
- <template #action="{ row }">
39
- <TTag shape="round" variant="light-outline">{{ row.action }}</TTag>
40
- </template>
41
- </TTable>
42
- </div>
43
-
44
- <div class="main-detail">
45
- <DetailPanel :data="$Data.currentRow" :fields="$Data.detailFields">
46
- <template #result="{ value }">
47
- <TTag v-if="value === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
48
- <TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
49
- </template>
50
- <template #operateTime="{ value }">
51
- {{ $Method.formatTime(value) }}
52
- </template>
53
- <template #duration="{ value }">
54
- <TTag shape="round" :theme="value > 1000 ? 'warning' : 'default'" variant="light-outline">{{ value }}ms</TTag>
55
- </template>
56
- <template #params="{ value }">
57
- <pre class="json-content">{{ $Method.formatJson(value) }}</pre>
58
- </template>
59
- <template #response="{ value }">
60
- <pre class="json-content">{{ $Method.formatJson(value) }}</pre>
61
- </template>
62
- </DetailPanel>
63
- </div>
64
- </div>
65
-
66
- <div class="main-page">
67
- <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.limit" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @page-size-change="$Method.handleSizeChange" />
68
- </div>
69
- </div>
70
- </template>
71
-
72
- <script setup>
73
- import { Button as TButton, Table as TTable, Tag as TTag, Pagination as TPagination, Select as TSelect, Option as TOption, MessagePlugin } from "tdesign-vue-next";
74
- import ILucideRotateCw from "~icons/lucide/rotate-cw";
75
- import DetailPanel from "@/components/DetailPanel.vue";
76
- import { $Http } from "@/plugins/http";
77
- import { withDefaultColumns } from "befly-vite/utils/withDefaultColumns";
78
-
79
- definePage({
80
- meta: {
81
- title: "操作日志",
82
- order: 3
83
- }
84
- });
85
-
86
- // 响应式数据
87
- const $Data = $ref({
88
- tableData: [],
89
- loading: false,
90
- columns: withDefaultColumns([
91
- { colKey: "username", title: "操作人", fixed: "left", width: 100 },
92
- { colKey: "id", title: "序号", width: 80 },
93
- { colKey: "module", title: "模块", width: 100 },
94
- { colKey: "action", title: "操作", width: 80 },
95
- { colKey: "path", title: "请求路径", ellipsis: true },
96
- { colKey: "ip", title: "IP地址", width: 130 },
97
- { colKey: "duration", title: "耗时", width: 100 },
98
- { colKey: "operateTime", title: "操作时间", width: 170 },
99
- { colKey: "result", title: "结果", width: 80 }
100
- ]),
101
- detailFields: [
102
- { colKey: "username", title: "操作人账号" },
103
- { colKey: "nickname", title: "操作人昵称" },
104
- { colKey: "module", title: "操作模块" },
105
- { colKey: "action", title: "操作类型" },
106
- { colKey: "method", title: "请求方法" },
107
- { colKey: "path", title: "请求路径" },
108
- { colKey: "ip", title: "IP地址" },
109
- { colKey: "params", title: "请求参数" },
110
- { colKey: "response", title: "响应内容" },
111
- { colKey: "duration", title: "耗时" },
112
- { colKey: "operateTime", title: "操作时间" },
113
- { colKey: "result", title: "操作结果" },
114
- { colKey: "remark", title: "备注" }
115
- ],
116
- pagerConfig: {
117
- currentPage: 1,
118
- limit: 30,
119
- total: 0
120
- },
121
- currentRow: null,
122
- activeRowKeys: [],
123
- filter: {
124
- module: "",
125
- action: "",
126
- result: null
127
- },
128
- moduleOptions: [
129
- { label: "管理员", value: "管理员" },
130
- { label: "角色", value: "角色" },
131
- { label: "菜单", value: "菜单" },
132
- { label: "接口", value: "接口" },
133
- { label: "字典", value: "字典" }
134
- ],
135
- actionOptions: [
136
- { label: "新增", value: "新增" },
137
- { label: "编辑", value: "编辑" },
138
- { label: "删除", value: "删除" },
139
- { label: "查询", value: "查询" }
140
- ]
141
- });
142
-
143
- // 方法
144
- const $Method = {
145
- async initData() {
146
- await $Method.apiOperateLogList();
147
- },
148
-
149
- // 加载操作日志列表
150
- async apiOperateLogList() {
151
- $Data.loading = true;
152
- try {
153
- const res = await $Http("/addon/admin/operateLog/list", {
154
- page: $Data.pagerConfig.currentPage,
155
- limit: $Data.pagerConfig.limit
156
- });
157
- $Data.tableData = res.data.lists || [];
158
- $Data.pagerConfig.total = res.data.total || 0;
159
-
160
- if ($Data.tableData.length > 0) {
161
- $Data.currentRow = $Data.tableData[0];
162
- $Data.activeRowKeys = [$Data.tableData[0].id];
163
- } else {
164
- $Data.currentRow = null;
165
- $Data.activeRowKeys = [];
166
- }
167
- } catch (error) {
168
- MessagePlugin.error("加载数据失败");
169
- } finally {
170
- $Data.loading = false;
171
- }
172
- },
173
-
174
- // 筛选
175
- handleFilter() {
176
- $Data.pagerConfig.currentPage = 1;
177
- $Method.apiOperateLogList();
178
- },
179
-
180
- // 刷新
181
- handleRefresh() {
182
- $Method.apiOperateLogList();
183
- },
184
-
185
- // 分页改变
186
- onPageChange(currentPage) {
187
- $Data.pagerConfig.currentPage = currentPage;
188
- $Method.apiOperateLogList();
189
- },
190
-
191
- // 每页条数改变
192
- handleSizeChange(pageSize) {
193
- $Data.pagerConfig.limit = pageSize;
194
- $Data.pagerConfig.currentPage = 1;
195
- $Method.apiOperateLogList();
196
- },
197
-
198
- // 高亮行变化
199
- onActiveChange(value, context) {
200
- if (value.length === 0 && $Data.activeRowKeys.length > 0) {
201
- return;
202
- }
203
- $Data.activeRowKeys = value;
204
- if (context.activeRowList && context.activeRowList.length > 0) {
205
- $Data.currentRow = context.activeRowList[0].row;
206
- }
207
- },
208
-
209
- // 格式化时间
210
- formatTime(timestamp) {
211
- if (!timestamp) return "-";
212
- const date = new Date(timestamp);
213
- const year = date.getFullYear();
214
- const month = String(date.getMonth() + 1).padStart(2, "0");
215
- const day = String(date.getDate()).padStart(2, "0");
216
- const hours = String(date.getHours()).padStart(2, "0");
217
- const minutes = String(date.getMinutes()).padStart(2, "0");
218
- const seconds = String(date.getSeconds()).padStart(2, "0");
219
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
220
- },
221
-
222
- // 格式化 JSON
223
- formatJson(value) {
224
- if (!value) return "-";
225
- try {
226
- const obj = typeof value === "string" ? JSON.parse(value) : value;
227
- return JSON.stringify(obj, null, 2);
228
- } catch {
229
- return value;
230
- }
231
- }
232
- };
233
-
234
- $Method.initData();
235
- </script>
236
-
237
- <style scoped lang="scss">
238
- .json-content {
239
- margin: 0;
240
- padding: 8px;
241
- background: var(--td-bg-color-container);
242
- border-radius: 4px;
243
- font-size: 12px;
244
- max-height: 200px;
245
- overflow: auto;
246
- white-space: pre-wrap;
247
- word-break: break-all;
248
- }
249
- </style>
@@ -1,415 +0,0 @@
1
- <template>
2
- <div class="login-container">
3
- <!-- 左侧装饰区域 -->
4
- <div class="left-section">
5
- <div class="bg-decoration">
6
- <div class="circle circle-1"></div>
7
- <div class="circle circle-2"></div>
8
- <div class="circle circle-3"></div>
9
- </div>
10
- <div class="welcome-content">
11
- <h1 class="brand-title">Befly</h1>
12
- <p class="brand-subtitle">轻量级业务快速开发框架</p>
13
- </div>
14
- </div>
15
-
16
- <!-- 右侧登录区域 -->
17
- <div class="right-section">
18
- <div class="login-box">
19
- <div class="login-header">
20
- <h2 class="login-title">欢迎回来</h2>
21
- <p class="login-subtitle">请登录您的账户</p>
22
- </div>
23
-
24
- <TForm :model="$Data.formData" :rules="$Data2.formRules" :ref="(el) => ($From.form = el)" class="login-form" :show-message="false" label-width="0">
25
- <TFormItem prop="account">
26
- <TInputAdornment>
27
- <template #prepend>
28
- <TSelect v-model="$Data.formData.loginType" :style="{ width: '110px' }" size="large" :popup-props="{ overlayClassName: 'login-type-select-popup' }">
29
- <TOption value="username" label="用户名" />
30
- <TOption value="email" label="邮箱" />
31
- <TOption value="phone" label="手机号" />
32
- </TSelect>
33
- </template>
34
- <TInput v-model="$Data.formData.account" :placeholder="$Data.formData.loginType === 'username' ? '请输入用户名' : $Data.formData.loginType === 'email' ? '请输入邮箱' : '请输入手机号'" size="large" clearable @enter="$Method.apiLogin">
35
- <template #prefix-icon>
36
- <ILucideUser />
37
- </template>
38
- </TInput>
39
- </TInputAdornment>
40
- </TFormItem>
41
-
42
- <TFormItem prop="password">
43
- <TInput v-model="$Data.formData.password" type="password" placeholder="密码" size="large" clearable @enter="$Method.apiLogin">
44
- <template #prefix-icon>
45
- <ILucideLock />
46
- </template>
47
- </TInput>
48
- </TFormItem>
49
-
50
- <div class="form-options">
51
- <TCheckbox v-model="$Data.rememberMe">记住我</TCheckbox>
52
- <a href="#" class="link-text">忘记密码?</a>
53
- </div>
54
-
55
- <TButton theme="primary" class="login-btn" size="large" block :loading="$Data.loading" @click="$Method.apiLogin"> 登录 </TButton>
56
- </TForm>
57
-
58
- <div class="login-footer">
59
- <p class="copyright">© 2024 Befly. All rights reserved.</p>
60
- </div>
61
- </div>
62
- </div>
63
- </div>
64
- </template>
65
-
66
- <script setup>
67
- import { useRouter } from "vue-router";
68
- import { Form as TForm, FormItem as TFormItem, Input as TInput, Button as TButton, Checkbox as TCheckbox, InputAdornment as TInputAdornment, Select as TSelect, Option as TOption, MessagePlugin } from "tdesign-vue-next";
69
- import ILucideUser from "~icons/lucide/user";
70
- import ILucideLock from "~icons/lucide/lock";
71
- import { $Http } from "@/plugins/http";
72
- import { $Storage } from "@/plugins/storage";
73
- import { hashPassword } from "befly-vite/utils/hashPassword";
74
-
75
- definePage({
76
- meta: {
77
- title: "登录页",
78
- order: 100
79
- }
80
- });
81
-
82
- const router = useRouter();
83
-
84
- // 表单引用
85
- const $From = $shallowRef({
86
- form: null
87
- });
88
-
89
- // 数据定义
90
- const $Data = $ref({
91
- loading: false,
92
- rememberMe: false,
93
- formData: {
94
- loginType: "username",
95
- account: "",
96
- password: ""
97
- }
98
- });
99
-
100
- const $Data2 = $shallowRef({
101
- formRules: {
102
- account: [{ required: true, message: "请输入账号", trigger: "blur" }],
103
- password: [{ required: true, message: "请输入密码", trigger: "blur" }]
104
- }
105
- });
106
-
107
- // 方法定义
108
- const $Method = {
109
- async apiLogin() {
110
- try {
111
- const valid = await $From.form.validate();
112
-
113
- $Data.loading = true;
114
-
115
- // 对密码进行 SHA-256 加密
116
- const hashedPassword = await hashPassword($Data.formData.password);
117
-
118
- const res = await $Http("/addon/admin/auth/login", {
119
- loginType: $Data.formData.loginType,
120
- account: $Data.formData.account,
121
- password: hashedPassword
122
- });
123
-
124
- // 先保存 token
125
- $Storage.local.set("token", res.data.token);
126
-
127
- // 如果返回用户信息,也可以存储
128
- if (res.data.userInfo) {
129
- $Storage.local.set("userInfo", res.data.userInfo);
130
- }
131
-
132
- MessagePlugin.success(res.msg || "登录成功");
133
-
134
- // 跳转到首页,路由守卫会自动加载菜单
135
- await router.push("/");
136
- } catch (error) {
137
- MessagePlugin.error("登录失败");
138
- } finally {
139
- $Data.loading = false;
140
- }
141
- }
142
- };
143
- </script>
144
-
145
- <style scoped lang="scss">
146
- .login-container {
147
- display: flex;
148
- width: 100%;
149
- min-height: 100vh;
150
- background: var(--login-bg);
151
- }
152
-
153
- // 左侧装饰区域
154
- .left-section {
155
- flex: 1;
156
- position: relative;
157
- display: flex;
158
- align-items: center;
159
- justify-content: center;
160
- background: linear-gradient(135deg, var(--login-left-gradient-start) 0%, var(--login-left-gradient-end) 100%);
161
- overflow: hidden;
162
- }
163
-
164
- .bg-decoration {
165
- position: absolute;
166
- width: 100%;
167
- height: 100%;
168
- overflow: hidden;
169
-
170
- .circle {
171
- position: absolute;
172
- border-radius: 50%;
173
- background: var(--login-circle-bg);
174
- animation: float 20s infinite ease-in-out;
175
-
176
- &.circle-1 {
177
- width: 400px;
178
- height: 400px;
179
- top: -100px;
180
- left: -100px;
181
- animation-delay: 0s;
182
- }
183
-
184
- &.circle-2 {
185
- width: 300px;
186
- height: 300px;
187
- bottom: -50px;
188
- right: -50px;
189
- animation-delay: 7s;
190
- }
191
-
192
- &.circle-3 {
193
- width: 200px;
194
- height: 200px;
195
- top: 50%;
196
- right: 10%;
197
- animation-delay: 14s;
198
- }
199
- }
200
- }
201
-
202
- @keyframes float {
203
- 0%,
204
- 100% {
205
- transform: translateY(0) scale(1);
206
- }
207
- 50% {
208
- transform: translateY(-20px) scale(1.05);
209
- }
210
- }
211
-
212
- .welcome-content {
213
- position: relative;
214
- z-index: 1;
215
- color: #fff;
216
- text-align: center;
217
- padding: 2rem;
218
- }
219
-
220
- .brand-title {
221
- font-size: 4rem;
222
- font-weight: 700;
223
- margin-bottom: 1rem;
224
- letter-spacing: 2px;
225
- color: var(--login-brand-title);
226
- }
227
-
228
- .brand-subtitle {
229
- font-size: 1.25rem;
230
- margin-bottom: 3rem;
231
- color: var(--login-brand-subtitle);
232
- }
233
-
234
- // 右侧登录区域
235
- .right-section {
236
- flex: 1;
237
- display: flex;
238
- align-items: center;
239
- justify-content: center;
240
- padding: 2rem;
241
- }
242
-
243
- .login-box {
244
- width: 100%;
245
- max-width: 440px;
246
- background: var(--login-card-bg);
247
- border: 1px solid var(--login-card-border);
248
- border-radius: 16px;
249
- box-shadow: 0 10px 40px var(--login-card-shadow);
250
- padding: 3rem 2.5rem;
251
- }
252
-
253
- .login-header {
254
- text-align: center;
255
- margin-bottom: 2.5rem;
256
- }
257
-
258
- .login-title {
259
- font-size: 2rem;
260
- font-weight: 600;
261
- color: var(--login-title);
262
- margin-bottom: 0.5rem;
263
- }
264
-
265
- .login-subtitle {
266
- font-size: 1rem;
267
- color: var(--login-subtitle);
268
- }
269
-
270
- .login-footer {
271
- margin-top: 2rem;
272
- text-align: center;
273
- }
274
-
275
- .copyright {
276
- font-size: 0.875rem;
277
- color: var(--login-subtitle);
278
- }
279
-
280
- .login-form {
281
- width: 100%;
282
-
283
- :deep(.t-form__item) {
284
- margin-bottom: 1.25rem;
285
- }
286
-
287
- :deep(.t-form__controls) {
288
- width: 100%;
289
- }
290
-
291
- :deep(.t-input-adornment) {
292
- width: 100%;
293
- }
294
-
295
- :deep(.t-input-adornment__prepend) {
296
- padding: 0;
297
- border-right: 1px solid var(--login-card-border);
298
- }
299
-
300
- :deep(.t-input) {
301
- width: 100%;
302
- border-radius: 8px;
303
- transition: all 0.3s;
304
-
305
- &:hover {
306
- border-color: #667eea;
307
- }
308
-
309
- &:focus-within {
310
- border-color: #667eea;
311
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
312
- }
313
- }
314
-
315
- :deep(.t-input__wrap) {
316
- width: 100%;
317
- }
318
- }
319
-
320
- .form-options {
321
- display: flex;
322
- justify-content: space-between;
323
- align-items: center;
324
- margin-bottom: 1.5rem;
325
- font-size: 0.875rem;
326
-
327
- .link-text {
328
- color: var(--login-link);
329
- text-decoration: none;
330
- transition: color 0.3s;
331
-
332
- &:hover {
333
- color: var(--login-link-hover);
334
- }
335
- }
336
- }
337
-
338
- .login-btn {
339
- width: 100%;
340
- height: 48px;
341
- border-radius: 8px;
342
- font-size: 1rem;
343
- font-weight: 600;
344
- background: linear-gradient(135deg, var(--login-btn-gradient-start) 0%, var(--login-btn-gradient-end) 100%);
345
- border: none;
346
- transition: all 0.3s;
347
-
348
- &:hover {
349
- transform: translateY(-2px);
350
- box-shadow: 0 8px 20px var(--login-btn-shadow);
351
- }
352
-
353
- &:active {
354
- transform: translateY(0);
355
- }
356
-
357
- :deep(.t-button__text) {
358
- color: #fff;
359
- }
360
- }
361
-
362
- // 响应式设计
363
- @media (max-width: 1024px) {
364
- .login-container {
365
- flex-direction: column;
366
- }
367
-
368
- .left-section {
369
- min-height: 300px;
370
- flex: none;
371
- }
372
-
373
- .brand-title {
374
- font-size: 3rem;
375
- }
376
-
377
- .right-section {
378
- flex: 1;
379
- }
380
- }
381
-
382
- @media (max-width: 768px) {
383
- .left-section {
384
- min-height: 200px;
385
- }
386
-
387
- .brand-title {
388
- font-size: 2.5rem;
389
- }
390
-
391
- .brand-subtitle {
392
- font-size: 1rem;
393
- margin-bottom: 2rem;
394
- }
395
-
396
- .login-box {
397
- padding: 2rem 1.5rem;
398
- }
399
-
400
- .login-title {
401
- font-size: 1.75rem;
402
- }
403
- }
404
-
405
- @media (max-width: 480px) {
406
- .right-section {
407
- padding: 1rem;
408
- }
409
-
410
- .login-box {
411
- padding: 1.5rem 1rem;
412
- border-radius: 12px;
413
- }
414
- }
415
- </style>