befly-admin-ui 1.9.6 → 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.
@@ -1,11 +1,9 @@
1
1
  <template>
2
- <TDialog v-model:visible="innerVisible" :header="props.title === '' ? true : props.title" :width="props.width" placement="center" attach="body" :close-btn="false" :footer="true" :confirm-loading="props.confirmLoading" :close-on-overlay-click="false" :close-on-esc-keydown="false" @close="onDialogClose" @confirm="onDialogConfirm" @cancel="onDialogCancel">
2
+ <TDialog v-model:visible="innerVisible" :header="props.title === '' ? true : props.title" :width="props.width" :dialog-class-name="props.dialogClassName" placement="center" attach="body" :close-btn="false" :footer="true" :confirm-loading="props.confirmLoading" :close-on-overlay-click="false" :close-on-esc-keydown="false" @close="onDialogClose" @confirm="onDialogConfirm" @cancel="onDialogCancel">
3
3
  <template #footer>
4
4
  <div class="dialog-footer">
5
5
  <TButton variant="outline" @click="onFooterClose">关闭</TButton>
6
- <TPopconfirm v-if="props.isConfirm" content="确定要提交吗?" :disabled="props.confirmLoading" @confirm="onFooterConfirm">
7
- <TButton theme="primary" :loading="props.confirmLoading">确定</TButton>
8
- </TPopconfirm>
6
+ <TButton v-if="props.isConfirm" theme="primary" :loading="props.confirmLoading" @click="onFooterConfirm">确定</TButton>
9
7
  </div>
10
8
  </template>
11
9
  <div class="dialog-wrapper">
@@ -17,7 +15,7 @@
17
15
  <script setup>
18
16
  import { onBeforeUnmount, onMounted, ref, watch } from "vue";
19
17
 
20
- import { Button as TButton, Dialog as TDialog, Popconfirm as TPopconfirm } from "tdesign-vue-next";
18
+ import { Button as TButton, Dialog as TDialog } from "tdesign-vue-next";
21
19
 
22
20
  defineOptions({
23
21
  inheritAttrs: false
@@ -39,6 +37,10 @@ const props = defineProps({
39
37
  type: String,
40
38
  default: "600px"
41
39
  },
40
+ dialogClassName: {
41
+ type: String,
42
+ default: ""
43
+ },
42
44
  confirmLoading: {
43
45
  type: Boolean,
44
46
  default: false
@@ -190,6 +192,7 @@ defineExpose({
190
192
  background-color: var(--bg-color-page);
191
193
  padding: var(--spacing-md);
192
194
  border-radius: var(--border-radius);
195
+ max-height: 80vh;
193
196
  }
194
197
  .dialog-footer {
195
198
  width: 100%;
@@ -37,49 +37,62 @@
37
37
  </template>
38
38
  </t-menu>
39
39
  </div>
40
+ </div>
40
41
 
41
- <!-- 底部操作区域 -->
42
- <div class="sidebar-footer">
43
- <div class="footer-item" @click="handleSettings">
44
- <SettingIcon style="width: 18px; height: 18px" />
45
- <span>系统设置</span>
46
- </div>
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
- </t-upload>
57
- <div class="user-info">
58
- <span class="user-name">{{ $Data.userInfo.nickname || "管理员" }}</span>
59
- <span class="user-role">{{ $Data.userInfo.role || "超级管理员" }}</span>
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
- <t-button theme="default" variant="text" size="small" @click="handleLogout">
62
- <template #icon>
63
- <CloseCircleIcon style="width: 16px; height: 16px" />
64
- </template>
65
- </t-button>
66
- </div>
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
- <div class="layout-main">
72
- <RouterView />
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 { Button as TButton, DialogPlugin, Menu as TMenu, MenuItem as TMenuItem, MessagePlugin, Submenu as TSubmenu, Upload as TUpload } from "tdesign-vue-next";
80
- import { CloudIcon, CloseCircleIcon, CodeIcon, LinkIcon, MenuIcon, SettingIcon, UserIcon, ControlPlatformIcon, AppIcon, HomeIcon } from "tdesign-icons-vue-next";
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 uploadHeaders = { Authorization: `Bearer ${$Store.local.get($Config.tokenName, "")}` };
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 handleSettings() {
267
- router.push("/core/settings");
299
+ function openPasswordDialog() {
300
+ $Data.passwordForm = createPasswordForm();
301
+ $Data.passwordDialogVisible = true;
268
302
  }
269
303
 
270
- async function onAvatarUploadSuccess(res) {
271
- if (res.response?.code === 0 && res.response?.data?.url) {
272
- const avatarUrl = res.response.data.url;
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
- try {
275
- if ($Data.userInfo.id) {
276
- await $Http("/core/admin/upd", {
277
- id: $Data.userInfo.id,
278
- avatar: avatarUrl
279
- });
280
- }
309
+ if (cmd === "password") {
310
+ openPasswordDialog();
311
+ return;
312
+ }
281
313
 
282
- $Data.userInfo.avatar = avatarUrl;
283
- MessagePlugin.success("头像上传成功");
284
- } catch (error) {
285
- MessagePlugin.error(error.msg || error.message || "头像保存失败");
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
- .sidebar-footer {
419
- border-top: 1px solid var(--border-color-light);
420
- padding: var(--spacing-sm);
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
- .footer-item {
510
+ .toolbar-user {
423
511
  display: flex;
424
512
  align-items: center;
425
513
  gap: var(--spacing-sm);
426
- padding: var(--spacing-sm) var(--spacing-md);
514
+ padding: 4px 8px;
427
515
  border-radius: var(--border-radius);
428
- color: var(--text-secondary);
429
516
  cursor: pointer;
430
- transition: all var(--transition-fast);
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
- .footer-user {
524
+ .toolbar-user-avatar {
525
+ width: 36px;
526
+ height: 36px;
527
+ min-width: 36px;
444
528
  display: flex;
445
529
  align-items: center;
446
- gap: var(--spacing-sm);
447
- padding: var(--spacing-sm);
448
- margin-top: var(--spacing-xs);
449
- background: var(--bg-color-secondarycontainer);
450
- border-radius: var(--border-radius);
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
- &:hover .avatar-overlay {
487
- opacity: 1;
488
- }
536
+ img {
537
+ width: 100%;
538
+ height: 100%;
539
+ object-fit: cover;
489
540
  }
541
+ }
490
542
 
491
- .user-info {
492
- flex: 1;
493
- min-width: 0;
494
- display: flex;
495
- flex-direction: column;
543
+ .toolbar-user-info {
544
+ min-width: 0;
545
+ display: flex;
546
+ flex-direction: column;
496
547
 
497
- .user-name {
498
- font-size: var(--font-size-sm);
499
- font-weight: var(--font-weight-medium);
500
- color: var(--text-primary);
501
- line-height: 1.3;
502
- overflow: hidden;
503
- text-overflow: ellipsis;
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
- .user-role {
508
- font-size: var(--font-size-xs);
509
- color: var(--text-placeholder);
510
- line-height: 1.3;
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
- .layout-main {
519
- flex: 1;
520
- min-width: 0;
521
- overflow: hidden;
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly-admin-ui",
3
- "version": "1.9.6",
4
- "gitHead": "5c05c5ec3616748021723a09de6514d27b0cf577",
3
+ "version": "1.9.9",
4
+ "gitHead": "990aee4744b2c52e24c1a8c7a6cbee06b7d31833",
5
5
  "private": false,
6
6
  "description": "Befly - 管理后台功能组件",
7
7
  "keywords": [
@@ -1,23 +1,22 @@
1
1
  <template>
2
- <PageDialog v-model="dialogVisible" :title="$Prop.actionType === 'upd' ? '编辑管理员' : '添加管理员'" :confirm-loading="$Data.submitting" @confirm="onSubmit">
2
+ <PageDialog v-model="$Computed.dialogVisible" :title="$Computed.dialogTitle" :confirm-loading="$Data.submitting" @confirm="$Method.onSubmit">
3
3
  <div class="dialog-wrapper">
4
- <TForm :model="$Data.formData" label-width="80px" label-position="left" label-align="left" :rules="$Data2.formRules" ref="formRef">
4
+ <TForm :model="$Data.formData" label-width="80px" label-position="left" label-align="left" :rules="$Const.formRules" :ref="(el) => ($From.formRef = el)">
5
5
  <TFormItem label="角色" prop="roleCode">
6
- <TSelect v-model="$Data.formData.roleCode" :options="$Data.allRoleLists" :keys="$Data.keys" placeholder="请选择角色" />
6
+ <TSelect v-model="$Data.formData.roleCode" :options="$Type.roleOptions" placeholder="请选择角色" />
7
7
  </TFormItem>
8
8
  <TFormItem label="用户名" prop="username">
9
- <TInput v-model="$Data.formData.username" placeholder="请输入用户名" :disabled="$Prop.actionType === 'upd'" />
9
+ <TInput v-model="$Data.formData.username" placeholder="请输入用户名" :disabled="$Computed.isUpdate" />
10
10
  </TFormItem>
11
- <TFormItem v-if="$Prop.actionType === 'add'" label="密码" prop="password">
11
+ <TFormItem v-if="$Computed.isAdd" label="密码" prop="password">
12
12
  <TInput v-model="$Data.formData.password" type="password" placeholder="请输入密码,至少6位" />
13
13
  </TFormItem>
14
14
  <TFormItem label="昵称" prop="nickname">
15
15
  <TInput v-model="$Data.formData.nickname" placeholder="请输入昵称" />
16
16
  </TFormItem>
17
- <TFormItem v-if="$Prop.actionType === 'upd'" label="状态" prop="state">
17
+ <TFormItem v-if="$Computed.isUpdate" label="状态" prop="state">
18
18
  <TRadioGroup v-model="$Data.formData.state">
19
- <TRadio :label="1">正常</TRadio>
20
- <TRadio :label="2">禁用</TRadio>
19
+ <TRadio v-for="item in $Type.stateOptions" :key="item.value" :value="item.value">{{ item.label }}</TRadio>
21
20
  </TRadioGroup>
22
21
  </TFormItem>
23
22
  </TForm>
@@ -26,7 +25,7 @@
26
25
  </template>
27
26
 
28
27
  <script setup>
29
- import { computed, reactive, ref } from "vue";
28
+ import { computed, reactive } from "vue";
30
29
 
31
30
  import {
32
31
  //
@@ -54,40 +53,27 @@ const $Prop = defineProps({
54
53
  },
55
54
  rowData: {
56
55
  type: Object,
57
- default: {}
56
+ default: () => ({})
58
57
  }
59
58
  });
60
59
 
61
60
  const $Emit = defineEmits(["update:modelValue", "success"]);
62
61
 
63
- // 表单引用
64
- const formRef = ref(null);
65
-
66
- const dialogVisible = computed({
67
- get: () => $Prop.modelValue,
68
- set: (value) => {
69
- $Emit("update:modelValue", value);
70
- }
71
- });
72
-
73
- const $Data = reactive({
74
- submitting: false,
75
- allRoleLists: [],
76
- keys: {
77
- label: "name",
78
- value: "code"
62
+ const $Const = {
63
+ actionTitleMap: {
64
+ add: "添加管理员",
65
+ upd: "编辑管理员"
66
+ },
67
+ createDefaultFormData() {
68
+ return {
69
+ id: null,
70
+ username: "",
71
+ password: "",
72
+ nickname: "",
73
+ roleCode: "",
74
+ state: 1
75
+ };
79
76
  },
80
- formData: {
81
- id: null,
82
- username: "",
83
- password: "",
84
- nickname: "",
85
- roleCode: null,
86
- state: 1
87
- }
88
- });
89
-
90
- const $Data2 = reactive({
91
77
  formRules: {
92
78
  username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
93
79
  password: [
@@ -97,60 +83,111 @@ const $Data2 = reactive({
97
83
  roleCode: [{ required: true, message: "请选择角色", trigger: "change" }],
98
84
  nickname: [{ min: 2, max: 50, message: "昵称长度在 2 到 50 个字符", trigger: "blur" }]
99
85
  }
86
+ };
87
+
88
+ const $Type = reactive({
89
+ roleOptions: [],
90
+ stateOptions: [
91
+ { label: "正常", value: 1 },
92
+ { label: "禁用", value: 2 }
93
+ ]
100
94
  });
101
95
 
102
- async function initData() {
103
- await apiRoleLists();
104
- if ($Prop.actionType === "upd" && $Prop.rowData.id) {
105
- $Data.formData = Object.assign({}, $Prop.rowData);
106
- }
107
- }
108
-
109
- async function apiRoleLists() {
110
- try {
111
- const result = await $Http("/core/role/all", {}, [""]);
112
- $Data.allRoleLists = result.data || [];
113
- } catch (error) {
114
- MessagePlugin.error(error.msg || error.message || "加载角色列表失败");
115
- }
116
- }
117
-
118
- async function onSubmit(context) {
119
- try {
120
- const form = formRef.value;
121
- if (!form) {
122
- MessagePlugin.warning("表单未就绪");
123
- return;
96
+ const $Computed = reactive({
97
+ isAdd: computed(() => $Prop.actionType === "add"),
98
+ isUpdate: computed(() => $Prop.actionType === "upd"),
99
+ dialogVisible: computed({
100
+ get: () => $Prop.modelValue,
101
+ set: (value) => {
102
+ $Emit("update:modelValue", value);
124
103
  }
104
+ }),
105
+ dialogTitle: computed(() => $Const.actionTitleMap[$Prop.actionType] || "添加管理员")
106
+ });
125
107
 
126
- const valid = await form.validate();
127
- if (!valid) return;
108
+ const $From = {
109
+ formRef: null
110
+ };
128
111
 
129
- $Data.submitting = true;
130
- const formData = $Prop.actionType === "add" ? fieldClear($Data.formData, { omitKeys: ["id", "state"] }) : fieldClear($Data.formData, { omitKeys: ["password"] });
112
+ const $Data = reactive({
113
+ submitting: false,
114
+ formData: $Const.createDefaultFormData()
115
+ });
131
116
 
132
- const password = typeof formData["password"] === "string" ? String(formData["password"]) : "";
133
- if ($Prop.actionType === "add" && password) {
134
- formData["password"] = await hashPassword(password);
117
+ const $Method = {
118
+ resetFormData() {
119
+ Object.assign($Data.formData, $Const.createDefaultFormData());
120
+ },
121
+ assignFormData(rowData) {
122
+ Object.assign($Data.formData, $Const.createDefaultFormData(), {
123
+ id: rowData.id ?? null,
124
+ username: rowData.username || "",
125
+ nickname: rowData.nickname || "",
126
+ roleCode: rowData.roleCode || "",
127
+ state: typeof rowData.state === "number" ? rowData.state : 1
128
+ });
129
+ },
130
+ async apiRoleLists() {
131
+ try {
132
+ const result = await $Http("/core/role/all", {}, [""]);
133
+ const roleList = Array.isArray(result.data?.lists) ? result.data.lists : [];
134
+ $Type.roleOptions = roleList.map((item) => {
135
+ return {
136
+ label: item.name || item.code || "",
137
+ value: item.code || ""
138
+ };
139
+ });
140
+ } catch (error) {
141
+ MessagePlugin.error(error.msg || error.message || "加载角色列表失败");
142
+ }
143
+ },
144
+ buildSubmitData() {
145
+ return $Computed.isAdd ? fieldClear($Data.formData, { omitKeys: ["id", "state"] }) : fieldClear($Data.formData, { omitKeys: ["password"] });
146
+ },
147
+ async initData() {
148
+ await $Method.apiRoleLists();
149
+ if ($Computed.isUpdate && $Prop.rowData.id) {
150
+ $Method.assignFormData($Prop.rowData);
151
+ return;
135
152
  }
136
153
 
137
- const result = await $Http($Prop.actionType === "upd" ? "/core/admin/upd" : "/core/admin/ins", formData);
138
-
139
- MessagePlugin.success(result.msg);
140
- $Emit("success");
141
- if (context && typeof context.close === "function") {
142
- context.close();
154
+ $Method.resetFormData();
155
+ },
156
+ async onSubmit(context) {
157
+ try {
158
+ const form = $From.formRef;
159
+ if (!form) {
160
+ MessagePlugin.warning("表单未就绪");
161
+ return;
162
+ }
163
+
164
+ const valid = await form.validate();
165
+ if (!valid) return;
166
+
167
+ $Data.submitting = true;
168
+ const formData = $Method.buildSubmitData();
169
+
170
+ const password = typeof formData.password === "string" ? String(formData.password) : "";
171
+ if ($Computed.isAdd && password) {
172
+ formData.password = await hashPassword(password);
173
+ }
174
+
175
+ const result = await $Http($Computed.isUpdate ? "/core/admin/upd" : "/core/admin/ins", formData);
176
+
177
+ MessagePlugin.success(result.msg);
178
+ $Emit("success");
179
+ if (context && typeof context.close === "function") {
180
+ context.close();
181
+ }
182
+ } catch (error) {
183
+ MessagePlugin.error(error.msg || error.message || "提交失败");
184
+ } finally {
185
+ $Data.submitting = false;
143
186
  }
144
- } catch (error) {
145
- MessagePlugin.error(error.msg || error.message || "提交失败");
146
- } finally {
147
- $Data.submitting = false;
148
187
  }
149
- }
188
+ };
150
189
 
151
- initData();
190
+ $Method.initData();
152
191
  </script>
153
192
 
154
- <style scoped lang="scss">
155
- // 可根据需要添加样式
156
- </style>
193
+ <style scoped lang="scss"></style>
@@ -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, RefreshIcon } from "tdesign-icons-vue-next";
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
  }