@befly-addon/admin 1.0.54 → 1.0.55

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.
@@ -4,7 +4,7 @@
4
4
  * 功能:
5
5
  * 1. 刷新接口缓存(apis:all)
6
6
  * 2. 刷新菜单缓存(menus:all)
7
- * 3. 刷新角色权限缓存(role:{code})
7
+ * 3. 刷新角色权限缓存(role:info:{code})
8
8
  *
9
9
  * 使用场景:
10
10
  * - 执行数据库同步后
@@ -12,6 +12,8 @@
12
12
  * - 缓存出现异常需要重建
13
13
  */
14
14
 
15
+ import { RedisKeys } from 'befly-shared/redisKeys';
16
+
15
17
  export default {
16
18
  name: '刷新全部缓存',
17
19
  handler: async (befly, ctx) => {
@@ -30,10 +32,10 @@ export default {
30
32
  orderBy: ['addonName#ASC', 'path#ASC']
31
33
  });
32
34
 
33
- await befly.redis.setObject('apis:all', apis);
35
+ await befly.redis.setObject(RedisKeys.apisAll(), apis);
34
36
  results.apis = { success: true, count: apis.length };
35
37
  } catch (error: any) {
36
- befly.logger.error('刷新接口缓存失败:', error);
38
+ befly.logger.error({ err: error }, '刷新接口缓存失败');
37
39
  results.apis = { success: false, error: error.message };
38
40
  }
39
41
 
@@ -45,7 +47,7 @@ export default {
45
47
  orderBy: ['sort#ASC', 'id#ASC']
46
48
  });
47
49
 
48
- await befly.redis.setObject('menus:all', menus);
50
+ await befly.redis.setObject(RedisKeys.menusAll(), menus);
49
51
 
50
52
  const parentCount = menus.filter((m: any) => m.pid === 0).length;
51
53
  const childCount = menus.filter((m: any) => m.pid !== 0).length;
@@ -57,7 +59,7 @@ export default {
57
59
  childCount: childCount
58
60
  };
59
61
  } catch (error: any) {
60
- befly.logger.error('刷新菜单缓存失败:', error);
62
+ befly.logger.error({ err: error }, '刷新菜单缓存失败');
61
63
  results.menus = { success: false, error: error.message };
62
64
  }
63
65
 
@@ -69,16 +71,17 @@ export default {
69
71
  orderBy: ['id#ASC']
70
72
  });
71
73
 
72
- // 为每个角色单独缓存
73
- let cachedCount = 0;
74
- for (const role of roles) {
75
- await befly.redis.setObject(`role:${role.code}`, role);
76
- cachedCount++;
77
- }
74
+ // 使用 setBatch 批量缓存所有角色(利用 Bun Redis auto-pipeline)
75
+ await befly.redis.setBatch(
76
+ roles.map((role: any) => ({
77
+ key: RedisKeys.roleInfo(role.code),
78
+ value: role
79
+ }))
80
+ );
78
81
 
79
- results.roles = { success: true, count: cachedCount };
82
+ results.roles = { success: true, count: roles.length };
80
83
  } catch (error: any) {
81
- befly.logger.error('刷新角色缓存失败:', error);
84
+ befly.logger.error({ err: error }, '刷新角色缓存失败');
82
85
  results.roles = { success: false, error: error.message };
83
86
  }
84
87
 
@@ -91,7 +94,7 @@ export default {
91
94
  return befly.tool.No('部分缓存刷新失败', results);
92
95
  }
93
96
  } catch (error: any) {
94
- befly.logger.error('刷新全部缓存失败:', error);
97
+ befly.logger.error({ err: error }, '刷新全部缓存失败');
95
98
  return befly.tool.No('刷新全部缓存失败', { error: error.message });
96
99
  }
97
100
  }
package/apis/api/all.ts CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  return befly.tool.Yes('操作成功', { lists: allApis });
18
18
  } catch (error: any) {
19
- befly.logger.error('获取接口列表失败:', error);
19
+ befly.logger.error({ err: error }, '获取接口列表失败');
20
20
  return befly.tool.No('获取接口列表失败');
21
21
  }
22
22
  }
@@ -0,0 +1,30 @@
1
+ import type { ApiRoute } from 'befly';
2
+
3
+ export default {
4
+ name: '获取接口列表(分页)',
5
+ handler: async (befly, ctx) => {
6
+ try {
7
+ const { page = 1, limit = 30, keyword = '' } = ctx.body;
8
+
9
+ // 构建查询条件
10
+ const where: Record<string, any> = {};
11
+ if (keyword) {
12
+ where.$or = [{ name: { $like: `%${keyword}%` } }, { path: { $like: `%${keyword}%` } }];
13
+ }
14
+
15
+ const result = await befly.db.getList({
16
+ table: 'addon_admin_api',
17
+ fields: ['id', 'name', 'path', 'method', 'description', 'addon_name', 'addon_title'],
18
+ where: where,
19
+ orderBy: ['addon_name#ASC', 'path#ASC'],
20
+ page: page,
21
+ limit: limit
22
+ });
23
+
24
+ return befly.tool.Yes('操作成功', result);
25
+ } catch (error: any) {
26
+ befly.logger.error({ err: error }, '获取接口列表失败');
27
+ return befly.tool.No('获取接口列表失败');
28
+ }
29
+ }
30
+ } as ApiRoute;
@@ -32,8 +32,8 @@ export default {
32
32
  if (!isValid) {
33
33
  return befly.tool.No('账号或密码错误2');
34
34
  }
35
- } catch (error) {
36
- befly.logger.error('密码验证失败', error);
35
+ } catch (error: any) {
36
+ befly.logger.error({ err: error }, '密码验证失败');
37
37
  return befly.tool.No('密码格式错误,请重新设置密码');
38
38
  }
39
39
 
@@ -14,7 +14,7 @@
14
14
  responseTime: `${responseTime}ms`
15
15
  });
16
16
  } catch (error) {
17
- befly.logger.error('数据库状态检测失败:', error);
17
+ befly.logger.error({ err: error }, '数据库状态检测失败');
18
18
  services.push({
19
19
  name: '数据库',
20
20
  status: 'stopped',
@@ -34,7 +34,7 @@
34
34
  responseTime: `${responseTime}ms`
35
35
  });
36
36
  } catch (error) {
37
- befly.logger.error('Redis状态检测失败:', error);
37
+ befly.logger.error({ err: error }, 'Redis状态检测失败');
38
38
  services.push({
39
39
  name: 'Redis',
40
40
  status: 'stopped',
@@ -87,7 +87,7 @@ export default {
87
87
  befly.logger.warn('fs.statfs 不可用,无法获取磁盘信息');
88
88
  }
89
89
  } catch (error) {
90
- befly.logger.warn('获取磁盘信息失败', error);
90
+ befly.logger.warn({ err: error }, '获取磁盘信息失败');
91
91
  // 获取失败时返回 0
92
92
  diskPercentage = 0;
93
93
  diskUsed = 0;
package/apis/dict/all.ts CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  return befly.tool.Yes('操作成功', dicts);
15
15
  } catch (error) {
16
- befly.logger.error('获取所有字典失败:', error);
16
+ befly.logger.error({ err: error }, '获取所有字典失败');
17
17
  return befly.tool.No('操作失败');
18
18
  }
19
19
  }
package/apis/dict/del.ts CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  return befly.tool.Yes('操作成功');
13
13
  } catch (error) {
14
- befly.logger.error('删除字典失败:', error);
14
+ befly.logger.error({ err: error }, '删除字典失败');
15
15
  return befly.tool.No('操作失败');
16
16
  }
17
17
  }
@@ -11,8 +11,8 @@
11
11
  });
12
12
 
13
13
  return befly.tool.Yes('操作成功', dict);
14
- } catch (error) {
15
- befly.logger.error('获取字典详情失败:', error);
14
+ } catch (error: any) {
15
+ befly.logger.error({ err: error }, '获取字典详情失败');
16
16
  return befly.tool.No('操作失败');
17
17
  }
18
18
  }
package/apis/dict/ins.ts CHANGED
@@ -19,7 +19,7 @@ export default {
19
19
 
20
20
  return befly.tool.Yes('操作成功', { id: dictId });
21
21
  } catch (error) {
22
- befly.logger.error('添加字典失败:', error);
22
+ befly.logger.error({ err: error }, '添加字典失败');
23
23
  return befly.tool.No('操作失败');
24
24
  }
25
25
  }
package/apis/dict/upd.ts CHANGED
@@ -23,7 +23,7 @@ export default {
23
23
 
24
24
  return befly.tool.Yes('操作成功');
25
25
  } catch (error) {
26
- befly.logger.error('更新字典失败:', error);
26
+ befly.logger.error({ err: error }, '更新字典失败');
27
27
  return befly.tool.No('操作失败');
28
28
  }
29
29
  }
package/apis/menu/all.ts CHANGED
@@ -53,8 +53,8 @@ export default {
53
53
 
54
54
  // 6. 返回一维数组(由前端构建树形结构)
55
55
  return befly.tool.Yes('获取菜单成功', authorizedMenus);
56
- } catch (error) {
57
- befly.logger.error('获取用户菜单失败:', error);
56
+ } catch (error: any) {
57
+ befly.logger.error({ err: error }, '获取用户菜单失败');
58
58
  return befly.tool.No('获取菜单失败');
59
59
  }
60
60
  }
package/apis/menu/list.ts CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  return befly.tool.Yes('操作成功', menus);
12
12
  } catch (error) {
13
- befly.logger.error('获取菜单列表失败:', error);
13
+ befly.logger.error({ err: error }, '获取菜单列表失败');
14
14
  return befly.tool.No('操作失败');
15
15
  }
16
16
  }
package/apis/role/del.ts CHANGED
@@ -30,8 +30,8 @@
30
30
  }
31
31
 
32
32
  return befly.tool.Yes('操作成功');
33
- } catch (error) {
34
- befly.logger.error('删除角色失败:', error);
33
+ } catch (error: any) {
34
+ befly.logger.error({ err: error }, '删除角色失败');
35
35
  return befly.tool.No('操作失败');
36
36
  }
37
37
  }
package/apis/role/save.ts CHANGED
@@ -36,8 +36,8 @@
36
36
  });
37
37
 
38
38
  return befly.tool.Yes('操作成功');
39
- } catch (error) {
40
- befly.logger.error('保存用户角色失败:', error);
39
+ } catch (error: any) {
40
+ befly.logger.error({ err: error }, '保存用户角色失败');
41
41
  return befly.tool.No('操作失败');
42
42
  }
43
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@befly-addon/admin",
3
- "version": "1.0.54",
3
+ "version": "1.0.55",
4
4
  "title": "管理后台",
5
5
  "description": "Befly - 管理后台功能组件",
6
6
  "type": "module",
@@ -38,8 +38,8 @@
38
38
  "url": "https://github.com/chenbimo/befly.git",
39
39
  "directory": "packages/addon-admin"
40
40
  },
41
- "gitHead": "1f3cacbe4134ee60654b75338bcaeda535898a84",
41
+ "gitHead": "2031550167896390ac58c50aeb52b8c23426844e",
42
42
  "dependencies": {
43
- "befly-util": "^1.0.7"
43
+ "befly-shared": "^1.1.1"
44
44
  }
45
45
  }
@@ -64,7 +64,7 @@
64
64
  --footer-height: 48px;
65
65
  --search-height: 56px;
66
66
  --pagination-height: 56px;
67
- --detail-width: 320px;
67
+ --detail-width: 280px;
68
68
  --transition-duration: 0.3s;
69
69
 
70
70
  // 登录页配色
@@ -3,7 +3,7 @@
3
3
  <div class="dialog-wrapper">
4
4
  <TForm :model="$Data.formData" label-width="80px" label-position="left" label-align="left" :rules="$Data2.formRules" :ref="(el) => ($From.form = el)">
5
5
  <TFormItem label="角色" prop="roleId">
6
- <TSelect v-model="$Data.formData.roleId" :options="$Data.roleOptions" placeholder="请选择角色" />
6
+ <TSelect v-model="$Data.formData.roleId" :options="$Data.allRoleLists" placeholder="请选择角色" />
7
7
  </TFormItem>
8
8
  <TFormItem label="用户名" prop="username">
9
9
  <TInput v-model="$Data.formData.username" placeholder="请输入用户名" :disabled="$Prop.actionType === 'upd'" />
@@ -32,7 +32,7 @@
32
32
  <div class="dialog-footer">
33
33
  <t-space>
34
34
  <TButton theme="default" @click="$Method.onClose">取消</TButton>
35
- <TButton theme="primary" @click="$Method.onSubmit">确定</TButton>
35
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">确定</TButton>
36
36
  </t-space>
37
37
  </div>
38
38
  </template>
@@ -53,7 +53,7 @@ import {
53
53
  MessagePlugin
54
54
  } from 'tdesign-vue-next';
55
55
  import { $Http } from '@/plugins/http';
56
- import { fieldClear } from 'befly-util/fieldClear';
56
+ import { fieldClear } from 'befly-shared/fieldClear';
57
57
 
58
58
  const $Prop = defineProps({
59
59
  modelValue: {
@@ -79,7 +79,8 @@ const $From = $shallowRef({
79
79
 
80
80
  const $Data = $ref({
81
81
  visible: false,
82
- roleOptions: [],
82
+ submitting: false,
83
+ allRoleLists: [],
83
84
  formData: {
84
85
  id: 0,
85
86
  username: '',
@@ -115,7 +116,7 @@ const $Data2 = $shallowRef({
115
116
  const $Method = {
116
117
  async initData() {
117
118
  $Method.onShow();
118
- await $Method.loadRoles();
119
+ $Data.allRoleLists = await $Http('/addon/admin/role/all');
119
120
  if ($Prop.actionType === 'upd' && $Prop.rowData.id) {
120
121
  // 编辑模式:直接赋值
121
122
  $Data.formData = { ...$Prop.rowData };
@@ -135,25 +136,12 @@ const $Method = {
135
136
  }, 300);
136
137
  },
137
138
 
138
- async loadRoles() {
139
- try {
140
- const result = await $Http('/addon/admin/role/all');
141
- $Data.roleOptions = result
142
- .filter((role) => role.state === 1)
143
- .map((role) => ({
144
- label: role.name,
145
- value: role.id
146
- }));
147
- } catch (error) {
148
- MessagePlugin.error(error.msg || '加载角色列表失败');
149
- }
150
- },
151
-
152
139
  async onSubmit() {
153
140
  try {
154
141
  const valid = await $From.form.validate();
155
142
  if (!valid) return;
156
143
 
144
+ $Data.submitting = true;
157
145
  const submitData = $Prop.actionType === 'add' ? fieldClear($Data.formData, { omitKeys: ['id', 'state'] }) : fieldClear($Data.formData, { omitKeys: ['password'] });
158
146
 
159
147
  await $Http($Prop.actionType === 'upd' ? '/addon/admin/admin/upd' : '/addon/admin/admin/ins', submitData);
@@ -163,6 +151,8 @@ const $Method = {
163
151
  $Method.onClose();
164
152
  } catch (error) {
165
153
  MessagePlugin.error(error.msg || '提交失败');
154
+ } finally {
155
+ $Data.submitting = false;
166
156
  }
167
157
  }
168
158
  };
@@ -19,7 +19,7 @@
19
19
 
20
20
  <div class="main-content">
21
21
  <div class="main-table">
22
- <TTable :data="$Data.tableData" :columns="$Data.columns" row-key="id" :selected-row-keys="$Data.selectedRowKeys" active-row-type="single" :active-row-keys="$Data.activeRowKeys" @select-change="$Method.onSelectChange" @active-change="$Method.onActiveChange">
22
+ <TTable v-bind="withTableProps()" :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" select-on-row-click :selected-row-keys="$Data.selectedRowKeys" :active-row-keys="$Data.activeRowKeys" @select-change="$Method.onSelectChange" @active-change="$Method.onActiveChange">
23
23
  <template #state="{ row }">
24
24
  <TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
25
25
  <TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
@@ -44,45 +44,22 @@
44
44
  </div>
45
45
 
46
46
  <div class="main-detail">
47
- <div class="detail-content">
48
- <div v-if="$Data.currentRow">
49
- <div style="margin-bottom: 16px">
50
- <div style="color: var(--text-secondary); margin-bottom: 4px">ID</div>
51
- <div>{{ $Data.currentRow.id }}</div>
52
- </div>
53
- <div style="margin-bottom: 16px">
54
- <div style="color: var(--text-secondary); margin-bottom: 4px">用户名</div>
55
- <div>{{ $Data.currentRow.username }}</div>
56
- </div>
57
- <div style="margin-bottom: 16px">
58
- <div style="color: var(--text-secondary); margin-bottom: 4px">邮箱</div>
59
- <div>{{ $Data.currentRow.email }}</div>
60
- </div>
61
- <div style="margin-bottom: 16px">
62
- <div style="color: var(--text-secondary); margin-bottom: 4px">昵称</div>
63
- <div>{{ $Data.currentRow.nickname || '-' }}</div>
64
- </div>
65
- <div style="margin-bottom: 16px">
66
- <div style="color: var(--text-secondary); margin-bottom: 4px">角色</div>
67
- <div>{{ $Data.currentRow.roleCode || '-' }}</div>
68
- </div>
69
- <div style="margin-bottom: 16px">
70
- <div style="color: var(--text-secondary); margin-bottom: 4px">状态</div>
71
- <TTag v-if="$Data.currentRow.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
72
- <TTag v-else-if="$Data.currentRow.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
73
- <TTag v-else-if="$Data.currentRow.state === 0" shape="round" theme="danger" variant="light-outline">已删除</TTag>
74
- </div>
75
- </div>
76
- <div v-else style="text-align: center; padding: 48px 0; color: var(--text-placeholder)">
77
- <div style="font-size: 48px; margin-bottom: 8px">📋</div>
78
- <div>暂无数据</div>
79
- </div>
80
- </div>
47
+ <DetailPanel
48
+ :data="$Data.currentRow"
49
+ :fields="[
50
+ { key: 'id', label: 'ID' },
51
+ { key: 'username', label: '用户名' },
52
+ { key: 'email', label: '邮箱' },
53
+ { key: 'nickname', label: '昵称' },
54
+ { key: 'roleCode', label: '角色' },
55
+ { key: 'state', label: '状态' }
56
+ ]"
57
+ />
81
58
  </div>
82
59
  </div>
83
60
 
84
61
  <div class="main-page">
85
- <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.pageSize" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @size-change="$Method.handleSizeChange" />
62
+ <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.limit" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @page-size-change="$Method.handleSizeChange" />
86
63
  </div>
87
64
 
88
65
  <!-- 编辑对话框组件 -->
@@ -97,30 +74,34 @@ import ILucideRotateCw from '~icons/lucide/rotate-cw';
97
74
  import ILucidePencil from '~icons/lucide/pencil';
98
75
  import ILucideTrash2 from '~icons/lucide/trash-2';
99
76
  import EditDialog from './components/edit.vue';
77
+ import DetailPanel from '@/components/DetailPanel.vue';
100
78
  import { $Http } from '@/plugins/http';
79
+ import { withDefaultColumns, withTableProps } from '@/utils';
101
80
 
102
81
  // 响应式数据
103
82
  const $Data = $ref({
104
83
  tableData: [],
105
- columns: [
84
+ loading: false,
85
+ columns: withDefaultColumns([
106
86
  {
107
87
  colKey: 'row-select',
108
88
  type: 'single',
109
89
  width: 50,
110
90
  fixed: 'left',
111
- checkProps: { allowUncheck: true }
91
+ checkProps: { allowUncheck: true },
92
+ ellipsis: false
112
93
  },
113
94
  { colKey: 'username', title: '用户名', width: 150, fixed: 'left' },
114
95
  { colKey: 'id', title: '序号', width: 150, align: 'center' },
115
96
  { colKey: 'email', title: '邮箱', width: 200 },
116
97
  { colKey: 'nickname', title: '昵称', width: 150 },
117
98
  { colKey: 'roleCode', title: '角色', width: 120 },
118
- { colKey: 'state', title: '状态', width: 100 },
119
- { colKey: 'operation', title: '操作', width: 80, align: 'center', fixed: 'right' }
120
- ],
99
+ { colKey: 'state', title: '状态', width: 100, ellipsis: false },
100
+ { colKey: 'operation', title: '操作', width: 80, align: 'center', fixed: 'right', ellipsis: false }
101
+ ]),
121
102
  pagerConfig: {
122
103
  currentPage: 1,
123
- pageSize: 30,
104
+ limit: 30,
124
105
  total: 0,
125
106
  align: 'right',
126
107
  layout: 'total, prev, pager, next, jumper'
@@ -141,10 +122,11 @@ const $Method = {
141
122
 
142
123
  // 加载管理员列表
143
124
  async apiAdminList() {
125
+ $Data.loading = true;
144
126
  try {
145
127
  const res = await $Http('/addon/admin/admin/list', {
146
128
  page: $Data.pagerConfig.currentPage,
147
- limit: $Data.pagerConfig.pageSize
129
+ limit: $Data.pagerConfig.limit
148
130
  });
149
131
  $Data.tableData = res.data.lists || [];
150
132
  $Data.pagerConfig.total = res.data.total || 0;
@@ -161,10 +143,9 @@ const $Method = {
161
143
  }
162
144
  } catch (error) {
163
145
  console.error('加载管理员列表失败:', error);
164
- MessagePlugin.info({
165
- message: '加载数据失败',
166
- status: 'error'
167
- });
146
+ MessagePlugin.error('加载数据失败');
147
+ } finally {
148
+ $Data.loading = false;
168
149
  }
169
150
  },
170
151
 
@@ -172,20 +153,20 @@ const $Method = {
172
153
  async apiAdminDel(row) {
173
154
  DialogPlugin.confirm({
174
155
  header: '确认删除',
175
- body: `确定要删除管理员"${row.username}" 吗?`,
156
+ body: `确定要删除管理员“${row.username} 吗?`,
176
157
  status: 'warning'
177
158
  }).then(async () => {
178
159
  try {
179
160
  const res = await $Http('/addon/admin/admin/del', { id: row.id });
180
161
  if (res.code === 0) {
181
- MessagePlugin.info({ message: '删除成功', status: 'success' });
162
+ MessagePlugin.success('删除成功');
182
163
  $Method.apiAdminList();
183
164
  } else {
184
- MessagePlugin.info({ message: res.msg || '删除失败', status: 'error' });
165
+ MessagePlugin.error(res.msg || '删除失败');
185
166
  }
186
167
  } catch (error) {
187
168
  console.error('删除失败:', error);
188
- MessagePlugin.info({ message: '删除失败', status: 'error' });
169
+ MessagePlugin.error('删除失败');
189
170
  }
190
171
  });
191
172
  },
@@ -203,7 +184,7 @@ const $Method = {
203
184
 
204
185
  // 每页条数改变
205
186
  handleSizeChange({ pageSize }) {
206
- $Data.pagerConfig.pageSize = pageSize;
187
+ $Data.pagerConfig.limit = pageSize;
207
188
  $Data.pagerConfig.currentPage = 1;
208
189
  $Method.apiAdminList();
209
190
  },