befly-admin-ui 1.9.7 → 1.9.9
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.
- package/layouts/default.vue +182 -129
- package/package.json +2 -2
- package/views/people/admin/index.vue +1 -13
package/layouts/default.vue
CHANGED
|
@@ -37,49 +37,62 @@
|
|
|
37
37
|
</template>
|
|
38
38
|
</t-menu>
|
|
39
39
|
</div>
|
|
40
|
+
</div>
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
<div class="footer-user">
|
|
48
|
-
<t-upload :action="$Config.uploadPath" :headers="uploadHeaders" :show-upload-list="false" accept="image/*" @success="onAvatarUploadSuccess">
|
|
49
|
-
<div class="user-avatar" :class="{ 'has-avatar': $Data.userInfo.avatar }">
|
|
42
|
+
<!-- 右侧内容区域 -->
|
|
43
|
+
<div class="layout-main">
|
|
44
|
+
<div class="main-toolbar">
|
|
45
|
+
<TDropdown trigger="click" placement="bottom-right" @click="onUserDropdownAction">
|
|
46
|
+
<div class="toolbar-user">
|
|
47
|
+
<div class="toolbar-user-avatar">
|
|
50
48
|
<img v-if="$Data.userInfo.avatar" :src="$Data.userInfo.avatar" alt="avatar" />
|
|
51
49
|
<UserIcon v-else style="width: 16px; height: 16px; color: #fff" />
|
|
52
|
-
<div class="avatar-overlay">
|
|
53
|
-
<CloudIcon style="width: 14px; height: 14px; color: #fff" />
|
|
54
|
-
</div>
|
|
55
50
|
</div>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
<div class="toolbar-user-info">
|
|
52
|
+
<span class="user-name">{{ $Data.userInfo.nickname || "管理员" }}</span>
|
|
53
|
+
<span class="user-role">{{ $Data.userInfo.role || "超级管理员" }}</span>
|
|
54
|
+
</div>
|
|
55
|
+
<ChevronDownIcon class="toolbar-user-arrow" style="width: 16px; height: 16px" />
|
|
60
56
|
</div>
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
<TDropdownMenu slot="dropdown">
|
|
58
|
+
<TDropdownItem value="password">
|
|
59
|
+
<LockOnIcon style="width: 14px; height: 14px; margin-right: 6px" />
|
|
60
|
+
修改密码
|
|
61
|
+
</TDropdownItem>
|
|
62
|
+
<TDropdownItem value="logout" :divider="true">
|
|
63
|
+
<CloseCircleIcon style="width: 14px; height: 14px; margin-right: 6px" />
|
|
64
|
+
退出登录
|
|
65
|
+
</TDropdownItem>
|
|
66
|
+
</TDropdownMenu>
|
|
67
|
+
</TDropdown>
|
|
67
68
|
</div>
|
|
68
|
-
</div>
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
<div class="main-content">
|
|
71
|
+
<RouterView />
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<PageDialog v-if="$Data.passwordDialogVisible" v-model="$Data.passwordDialogVisible" title="修改密码" :confirm-loading="$Data.passwordSubmitting" @confirm="onPasswordSubmit">
|
|
75
|
+
<TForm ref="passwordFormRef" :model="$Data.passwordForm" label-width="100px" label-position="left" :rules="passwordFormRules">
|
|
76
|
+
<TFormItem label="新密码" prop="password">
|
|
77
|
+
<TInput v-model="$Data.passwordForm.password" type="password" placeholder="请输入新密码,至少6位" autocomplete="new-password" />
|
|
78
|
+
</TFormItem>
|
|
79
|
+
<TFormItem label="确认密码" prop="confirmPassword">
|
|
80
|
+
<TInput v-model="$Data.passwordForm.confirmPassword" type="password" placeholder="请再次输入新密码" autocomplete="new-password" />
|
|
81
|
+
</TFormItem>
|
|
82
|
+
</TForm>
|
|
83
|
+
</PageDialog>
|
|
73
84
|
</div>
|
|
74
85
|
</div>
|
|
75
86
|
</template>
|
|
76
87
|
|
|
77
88
|
<script setup>
|
|
78
89
|
import { arrayToTree } from "befly-admin-ui/utils/arrayToTree";
|
|
79
|
-
import
|
|
80
|
-
import {
|
|
90
|
+
import PageDialog from "befly-admin-ui/components/pageDialog.vue";
|
|
91
|
+
import { hashPassword } from "befly-admin-ui/utils/hashPassword";
|
|
92
|
+
import { DialogPlugin, Dropdown as TDropdown, DropdownItem as TDropdownItem, DropdownMenu as TDropdownMenu, Form as TForm, FormItem as TFormItem, Input as TInput, Menu as TMenu, MenuItem as TMenuItem, MessagePlugin, Submenu as TSubmenu } from "tdesign-vue-next";
|
|
93
|
+
import { AppIcon, ChevronDownIcon, CloseCircleIcon, ControlPlatformIcon, HomeIcon, LockOnIcon, UserIcon } from "tdesign-icons-vue-next";
|
|
81
94
|
|
|
82
|
-
import { reactive, watch } from "vue";
|
|
95
|
+
import { reactive, ref, watch } from "vue";
|
|
83
96
|
import { useRoute, useRouter } from "vue-router";
|
|
84
97
|
import { $Http } from "@/plugins/http.js";
|
|
85
98
|
import { $Config } from "@/plugins/config.js";
|
|
@@ -87,7 +100,7 @@ import { $Store } from "@/plugins/store.js";
|
|
|
87
100
|
|
|
88
101
|
const router = useRouter();
|
|
89
102
|
const route = useRoute();
|
|
90
|
-
const
|
|
103
|
+
const passwordFormRef = ref(null);
|
|
91
104
|
|
|
92
105
|
function isString(value) {
|
|
93
106
|
return typeof value === "string";
|
|
@@ -122,19 +135,39 @@ const normalizeParentPath = (parentPath) => {
|
|
|
122
135
|
return normalized;
|
|
123
136
|
};
|
|
124
137
|
|
|
138
|
+
function createPasswordForm() {
|
|
139
|
+
return {
|
|
140
|
+
password: "",
|
|
141
|
+
confirmPassword: ""
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
// 响应式数据
|
|
126
146
|
const $Data = reactive({
|
|
127
147
|
userMenus: [],
|
|
128
148
|
userMenusFlat: [], // 一维菜单数据
|
|
129
149
|
expandedKeys: [],
|
|
130
150
|
currentMenuKey: "",
|
|
151
|
+
passwordDialogVisible: false,
|
|
152
|
+
passwordSubmitting: false,
|
|
153
|
+
passwordForm: createPasswordForm(),
|
|
131
154
|
userInfo: {
|
|
155
|
+
id: 0,
|
|
132
156
|
nickname: "管理员",
|
|
133
157
|
role: "超级管理员",
|
|
134
|
-
avatar: ""
|
|
158
|
+
avatar: "",
|
|
159
|
+
roleCode: ""
|
|
135
160
|
}
|
|
136
161
|
});
|
|
137
162
|
|
|
163
|
+
const passwordFormRules = {
|
|
164
|
+
password: [
|
|
165
|
+
{ required: true, message: "请输入新密码", trigger: "blur" },
|
|
166
|
+
{ min: 6, message: "新密码至少6位", trigger: "blur" }
|
|
167
|
+
],
|
|
168
|
+
confirmPassword: [{ required: true, message: "请再次输入新密码", trigger: "blur" }]
|
|
169
|
+
};
|
|
170
|
+
|
|
138
171
|
function normalizeAvatarUrl(value) {
|
|
139
172
|
if (!isString(value) || value.length === 0) {
|
|
140
173
|
return "";
|
|
@@ -263,27 +296,67 @@ async function handleLogout() {
|
|
|
263
296
|
});
|
|
264
297
|
}
|
|
265
298
|
|
|
266
|
-
function
|
|
267
|
-
|
|
299
|
+
function openPasswordDialog() {
|
|
300
|
+
$Data.passwordForm = createPasswordForm();
|
|
301
|
+
$Data.passwordDialogVisible = true;
|
|
268
302
|
}
|
|
269
303
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
304
|
+
function onUserDropdownAction(data) {
|
|
305
|
+
const record = data;
|
|
306
|
+
const rawValue = record && record["value"] ? record["value"] : "";
|
|
307
|
+
const cmd = rawValue ? String(rawValue) : "";
|
|
273
308
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
avatar: avatarUrl
|
|
279
|
-
});
|
|
280
|
-
}
|
|
309
|
+
if (cmd === "password") {
|
|
310
|
+
openPasswordDialog();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
281
313
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
314
|
+
if (cmd === "logout") {
|
|
315
|
+
handleLogout();
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function onPasswordSubmit(context) {
|
|
320
|
+
const form = passwordFormRef.value;
|
|
321
|
+
if (form === null) {
|
|
322
|
+
MessagePlugin.warning("表单未就绪");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const valid = await form.validate();
|
|
327
|
+
if (valid !== true) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if ($Data.passwordForm.password !== $Data.passwordForm.confirmPassword) {
|
|
332
|
+
MessagePlugin.error("两次输入的密码不一致");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!$Data.userInfo.id) {
|
|
337
|
+
MessagePlugin.error("用户信息未就绪");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
$Data.passwordSubmitting = true;
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const hashedPassword = await hashPassword($Data.passwordForm.password);
|
|
345
|
+
const storedPassword = await hashPassword(hashedPassword);
|
|
346
|
+
|
|
347
|
+
await $Http("/core/admin/upd", {
|
|
348
|
+
id: $Data.userInfo.id,
|
|
349
|
+
password: storedPassword
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
MessagePlugin.success("密码修改成功");
|
|
353
|
+
if (context && typeof context.close === "function") {
|
|
354
|
+
context.close();
|
|
286
355
|
}
|
|
356
|
+
} catch (error) {
|
|
357
|
+
MessagePlugin.error(error.msg || error.message || "密码修改失败");
|
|
358
|
+
} finally {
|
|
359
|
+
$Data.passwordSubmitting = false;
|
|
287
360
|
}
|
|
288
361
|
}
|
|
289
362
|
|
|
@@ -413,112 +486,92 @@ watch(
|
|
|
413
486
|
}
|
|
414
487
|
}
|
|
415
488
|
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// 右侧主内容区域
|
|
492
|
+
.layout-main {
|
|
493
|
+
flex: 1;
|
|
494
|
+
min-width: 0;
|
|
495
|
+
display: flex;
|
|
496
|
+
flex-direction: column;
|
|
497
|
+
gap: var(--layout-gap);
|
|
498
|
+
overflow: hidden;
|
|
416
499
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
500
|
+
.main-toolbar {
|
|
501
|
+
flex-shrink: 0;
|
|
502
|
+
display: flex;
|
|
503
|
+
justify-content: flex-end;
|
|
504
|
+
align-items: center;
|
|
505
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
506
|
+
background: var(--bg-color-container);
|
|
507
|
+
border-radius: var(--border-radius-large);
|
|
508
|
+
box-shadow: var(--shadow-1);
|
|
421
509
|
|
|
422
|
-
.
|
|
510
|
+
.toolbar-user {
|
|
423
511
|
display: flex;
|
|
424
512
|
align-items: center;
|
|
425
513
|
gap: var(--spacing-sm);
|
|
426
|
-
padding:
|
|
514
|
+
padding: 4px 8px;
|
|
427
515
|
border-radius: var(--border-radius);
|
|
428
|
-
color: var(--text-secondary);
|
|
429
516
|
cursor: pointer;
|
|
430
|
-
transition:
|
|
517
|
+
transition: background-color var(--transition-fast);
|
|
431
518
|
|
|
432
519
|
&:hover {
|
|
433
520
|
background-color: var(--bg-color-hover);
|
|
434
|
-
color: var(--text-primary);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
span {
|
|
438
|
-
font-size: var(--font-size-sm);
|
|
439
|
-
white-space: nowrap;
|
|
440
521
|
}
|
|
441
522
|
}
|
|
442
523
|
|
|
443
|
-
.
|
|
524
|
+
.toolbar-user-avatar {
|
|
525
|
+
width: 36px;
|
|
526
|
+
height: 36px;
|
|
527
|
+
min-width: 36px;
|
|
444
528
|
display: flex;
|
|
445
529
|
align-items: center;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
.user-avatar {
|
|
453
|
-
width: 32px;
|
|
454
|
-
height: 32px;
|
|
455
|
-
min-width: 32px;
|
|
456
|
-
display: flex;
|
|
457
|
-
align-items: center;
|
|
458
|
-
justify-content: center;
|
|
459
|
-
background: var(--primary-color);
|
|
460
|
-
border-radius: 50%;
|
|
461
|
-
flex-shrink: 0;
|
|
462
|
-
cursor: pointer;
|
|
463
|
-
position: relative;
|
|
464
|
-
overflow: hidden;
|
|
465
|
-
|
|
466
|
-
img {
|
|
467
|
-
width: 100%;
|
|
468
|
-
height: 100%;
|
|
469
|
-
object-fit: cover;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
.avatar-overlay {
|
|
473
|
-
position: absolute;
|
|
474
|
-
top: 0;
|
|
475
|
-
left: 0;
|
|
476
|
-
right: 0;
|
|
477
|
-
bottom: 0;
|
|
478
|
-
background: rgba(0, 0, 0, 0.5);
|
|
479
|
-
display: flex;
|
|
480
|
-
align-items: center;
|
|
481
|
-
justify-content: center;
|
|
482
|
-
opacity: 0;
|
|
483
|
-
transition: opacity var(--transition-fast);
|
|
484
|
-
}
|
|
530
|
+
justify-content: center;
|
|
531
|
+
background: var(--primary-color);
|
|
532
|
+
border-radius: 50%;
|
|
533
|
+
overflow: hidden;
|
|
534
|
+
flex-shrink: 0;
|
|
485
535
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
536
|
+
img {
|
|
537
|
+
width: 100%;
|
|
538
|
+
height: 100%;
|
|
539
|
+
object-fit: cover;
|
|
489
540
|
}
|
|
541
|
+
}
|
|
490
542
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
flex-direction: column;
|
|
543
|
+
.toolbar-user-info {
|
|
544
|
+
min-width: 0;
|
|
545
|
+
display: flex;
|
|
546
|
+
flex-direction: column;
|
|
496
547
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
white-space: nowrap;
|
|
505
|
-
}
|
|
548
|
+
.user-name {
|
|
549
|
+
font-size: var(--font-size-sm);
|
|
550
|
+
font-weight: var(--font-weight-medium);
|
|
551
|
+
color: var(--text-primary);
|
|
552
|
+
line-height: 1.3;
|
|
553
|
+
white-space: nowrap;
|
|
554
|
+
}
|
|
506
555
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
556
|
+
.user-role {
|
|
557
|
+
font-size: var(--font-size-xs);
|
|
558
|
+
color: var(--text-placeholder);
|
|
559
|
+
line-height: 1.3;
|
|
560
|
+
white-space: nowrap;
|
|
512
561
|
}
|
|
513
562
|
}
|
|
563
|
+
|
|
564
|
+
.toolbar-user-arrow {
|
|
565
|
+
color: var(--text-placeholder);
|
|
566
|
+
flex-shrink: 0;
|
|
567
|
+
}
|
|
514
568
|
}
|
|
515
|
-
}
|
|
516
569
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
570
|
+
.main-content {
|
|
571
|
+
flex: 1;
|
|
572
|
+
min-height: 0;
|
|
573
|
+
overflow: hidden;
|
|
574
|
+
}
|
|
522
575
|
}
|
|
523
576
|
}
|
|
524
577
|
</style>
|
package/package.json
CHANGED
|
@@ -8,14 +8,6 @@
|
|
|
8
8
|
</TButton>
|
|
9
9
|
</template>
|
|
10
10
|
|
|
11
|
-
<template #toolRight="scope">
|
|
12
|
-
<TButton shape="circle" @click="onReload(scope.reload)">
|
|
13
|
-
<template #icon>
|
|
14
|
-
<RefreshIcon />
|
|
15
|
-
</template>
|
|
16
|
-
</TButton>
|
|
17
|
-
</template>
|
|
18
|
-
|
|
19
11
|
<template #state="{ row }">
|
|
20
12
|
<TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
|
|
21
13
|
<TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
|
|
@@ -50,7 +42,7 @@
|
|
|
50
42
|
<script setup>
|
|
51
43
|
import { reactive } from "vue";
|
|
52
44
|
import { Button as TButton, Dropdown as TDropdown, DropdownItem as TDropdownItem, DropdownMenu as TDropdownMenu, Tag as TTag } from "tdesign-vue-next";
|
|
53
|
-
import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon
|
|
45
|
+
import { AddIcon, ChevronDownIcon, DeleteIcon, EditIcon } from "tdesign-icons-vue-next";
|
|
54
46
|
import EditDialog from "./components/edit.vue";
|
|
55
47
|
import PageTableDetail from "befly-admin-ui/components/pageTableDetail.vue";
|
|
56
48
|
import { withDefaultColumns } from "befly-admin-ui/utils/withDefaultColumns";
|
|
@@ -88,10 +80,6 @@ function onAdd() {
|
|
|
88
80
|
onAction("add", {});
|
|
89
81
|
}
|
|
90
82
|
|
|
91
|
-
function onReload(reload) {
|
|
92
|
-
reload({ keepSelection: true });
|
|
93
|
-
}
|
|
94
|
-
|
|
95
83
|
function onDialogSuccess(reload) {
|
|
96
84
|
reload({ keepSelection: true });
|
|
97
85
|
}
|