@befly-addon/admin 1.0.52 → 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.
@@ -1,93 +1,75 @@
1
1
  <template>
2
2
  <div class="page-menu page-table">
3
3
  <div class="main-tool">
4
- <div class="left">
5
- <TButton theme="primary" @click="$Method.onAction('add', {})">
6
- <template #icon>
7
- <ILucidePlus />
8
- </template>
9
- 添加菜单
10
- </TButton>
11
- </div>
4
+ <div class="left"></div>
12
5
  <div class="right">
13
- <TButton @click="$Method.handleRefresh">
6
+ <TButton shape="circle" @click="$Method.handleRefresh">
14
7
  <template #icon>
15
8
  <ILucideRotateCw />
16
9
  </template>
17
- 刷新
18
10
  </TButton>
19
11
  </div>
20
12
  </div>
21
- <div class="main-table">
22
- <TTable :data="$Data.menuList" :columns="$Data.columns" header-cell-class-name="custom-table-cell-class" size="small" height="100%" row-key="id">
23
- <template #icon="{ row }">
24
- <ILucideSquare v-if="row.icon" />
25
- <span v-else>-</span>
26
- </template>
27
- <template #state="{ row }">
28
- <TTag v-if="row.state === 1" theme="success">正常</TTag>
29
- <TTag v-else-if="row.state === 2" theme="warning">禁用</TTag>
30
- <TTag v-else theme="danger">已删除</TTag>
31
- </template>
32
- <template #operation="{ row }">
33
- <TDropdown trigger="click" min-column-width="120" @click="(data) => $Method.onAction(data.value, row)">
34
- <TButton variant="text" size="small">操作</TButton>
35
- <TDropdownMenu slot="dropdown">
36
- <TDropdownItem value="upd">
37
- <ILucidePencil />
38
- 编辑
39
- </TDropdownItem>
40
- <TDropdownItem value="del" :divider="true">
41
- <ILucideTrash2 style="width: 14px; height: 14px; margin-right: 6px" />
42
- 删除
43
- </TDropdownItem>
44
- </TDropdownMenu>
45
- </TDropdown>
46
- </template>
47
- </TTable>
48
- </div>
49
13
 
50
- <div class="main-page">
51
- <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.pageSize" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @size-change="$Method.handleSizeChange" />
52
- </div>
14
+ <div class="main-content">
15
+ <div class="main-table">
16
+ <TTable v-bind="withTreeTableProps()" :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :selected-row-keys="$Data.selectedRowKeys" :active-row-keys="$Data.activeRowKeys" @select-change="$Method.onSelectChange" @active-change="$Method.onActiveChange">
17
+ <template #state="{ row }">
18
+ <TTag v-if="row.state === 1" shape="round" theme="success" variant="light-outline">正常</TTag>
19
+ <TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
20
+ <TTag v-else-if="row.state === 0" shape="round" theme="danger" variant="light-outline">删除</TTag>
21
+ </template>
22
+ </TTable>
23
+ </div>
53
24
 
54
- <!-- 编辑对话框组件 -->
55
- <EditDialog v-if="$Data.editVisible" v-model="$Data.editVisible" :action-type="$Data.actionType" :row-data="$Data.rowData" @success="$Method.apiMenuList" />
25
+ <div class="main-detail">
26
+ <DetailPanel
27
+ :data="$Data.currentRow"
28
+ :fields="[
29
+ { key: 'id', label: 'ID' },
30
+ { key: 'name', label: '菜单名称' },
31
+ { key: 'path', label: '路由路径' },
32
+ { key: 'icon', label: '图标' },
33
+ { key: 'sort', label: '排序' },
34
+ { key: 'pid', label: '父级ID', default: '顶级菜单' },
35
+ { key: 'state', label: '状态' }
36
+ ]"
37
+ />
38
+ </div>
39
+ </div>
56
40
  </div>
57
41
  </template>
58
42
 
59
43
  <script setup>
60
- import { Button as TButton, Table as TTable, Tag as TTag, Dropdown as TDropdown, DropdownMenu as TDropdownMenu, DropdownItem as TDropdownItem, Pagination as TPagination, MessagePlugin, DialogPlugin } from 'tdesign-vue-next';
61
- import ILucidePlus from '~icons/lucide/plus';
44
+ import { Button as TButton, Table as TTable, Tag as TTag, MessagePlugin } from 'tdesign-vue-next';
62
45
  import ILucideRotateCw from '~icons/lucide/rotate-cw';
63
- import ILucideSquare from '~icons/lucide/square';
64
- import ILucidePencil from '~icons/lucide/pencil';
65
- import ILucideTrash2 from '~icons/lucide/trash-2';
66
- import EditDialog from './components/edit.vue';
46
+ import DetailPanel from '@/components/DetailPanel.vue';
67
47
  import { $Http } from '@/plugins/http';
48
+ import { withDefaultColumns, withTreeTableProps } from '@/utils';
68
49
 
69
50
  // 响应式数据
70
51
  const $Data = $ref({
71
- menuList: [],
72
- columns: [
73
- { colKey: 'index', title: '序号', width: 60, align: 'center' },
74
- { colKey: 'name', title: '菜单名称' },
75
- { colKey: 'path', title: '路径', width: 200 },
76
- { colKey: 'icon', title: '图标', width: 100 },
77
- { colKey: 'sort', title: '排序', width: 80 },
78
- { colKey: 'state', title: '状态', width: 100 },
79
- { colKey: 'operation', title: '操作', width: 120, align: 'right' }
80
- ],
81
- pagerConfig: {
82
- currentPage: 1,
83
- pageSize: 30,
84
- total: 0,
85
- align: 'right',
86
- layout: 'total, prev, pager, next, jumper'
87
- },
88
- editVisible: false,
89
- actionType: 'add',
90
- rowData: {}
52
+ tableData: [],
53
+ loading: false,
54
+ columns: withDefaultColumns([
55
+ {
56
+ colKey: 'row-select',
57
+ type: 'single',
58
+ width: 50,
59
+ fixed: 'left',
60
+ checkProps: { allowUncheck: true },
61
+ ellipsis: false
62
+ },
63
+ { colKey: 'name', title: '菜单名称', width: 200, fixed: 'left' },
64
+ { colKey: 'id', title: '序号', width: 100, align: 'center' },
65
+ { colKey: 'path', title: '路由路径', width: 250 },
66
+ { colKey: 'icon', title: '图标', width: 120 },
67
+ { colKey: 'sort', title: '排序', width: 80, align: 'center' },
68
+ { colKey: 'state', title: '状态', width: 100, ellipsis: false }
69
+ ]),
70
+ currentRow: null,
71
+ selectedRowKeys: [],
72
+ activeRowKeys: []
91
73
  });
92
74
 
93
75
  // 方法
@@ -96,44 +78,67 @@ const $Method = {
96
78
  await $Method.apiMenuList();
97
79
  },
98
80
 
99
- // 加载菜单列表
81
+ // 加载菜单列表(树形结构)
100
82
  async apiMenuList() {
83
+ $Data.loading = true;
101
84
  try {
102
- const res = await $Http('/addon/admin/menu/list', {
103
- page: $Data.pagerConfig.currentPage,
104
- limit: $Data.pagerConfig.pageSize
105
- });
106
- $Data.menuList = res.data.lists || [];
107
- $Data.pagerConfig.total = res.data.total || 0;
85
+ const res = await $Http('/addon/admin/menu/list');
86
+ // 构建树形结构
87
+ $Data.tableData = $Method.buildTree(res.data || []);
88
+
89
+ // 自动选中并高亮第一行
90
+ if ($Data.tableData.length > 0) {
91
+ $Data.currentRow = $Data.tableData[0];
92
+ $Data.selectedRowKeys = [$Data.tableData[0].id];
93
+ $Data.activeRowKeys = [$Data.tableData[0].id];
94
+ } else {
95
+ $Data.currentRow = null;
96
+ $Data.selectedRowKeys = [];
97
+ $Data.activeRowKeys = [];
98
+ }
108
99
  } catch (error) {
109
100
  console.error('加载菜单列表失败:', error);
110
- MessagePlugin.info({
111
- message: '加载数据失败',
112
- status: 'error'
113
- });
101
+ MessagePlugin.error('加载数据失败');
102
+ } finally {
103
+ $Data.loading = false;
114
104
  }
115
105
  },
116
106
 
117
- // 删除菜单
118
- async apiMenuDel(row) {
119
- DialogPlugin.confirm({
120
- header: '确认删除',
121
- body: `确定要删除菜单"${row.name}" 吗?`,
122
- status: 'warning'
123
- }).then(async () => {
124
- try {
125
- const res = await $Http('/addon/admin/menu/del', { id: row.id });
126
- if (res.code === 0) {
127
- MessagePlugin.info({ message: '删除成功', status: 'success' });
128
- $Method.apiMenuList();
107
+ // 构建树形结构
108
+ buildTree(list) {
109
+ const map = {};
110
+ const roots = [];
111
+
112
+ // 先创建所有节点的映射
113
+ for (const item of list) {
114
+ map[item.id] = { ...item, children: [] };
115
+ }
116
+
117
+ // 构建树形结构
118
+ for (const item of list) {
119
+ const node = map[item.id];
120
+ if (item.pid === 0) {
121
+ roots.push(node);
122
+ } else if (map[item.pid]) {
123
+ map[item.pid].children.push(node);
124
+ } else {
125
+ roots.push(node);
126
+ }
127
+ }
128
+
129
+ // 移除空的 children 数组
130
+ const removeEmptyChildren = (nodes) => {
131
+ for (const node of nodes) {
132
+ if (node.children.length === 0) {
133
+ delete node.children;
129
134
  } else {
130
- MessagePlugin.info({ message: res.msg || '删除失败', status: 'error' });
135
+ removeEmptyChildren(node.children);
131
136
  }
132
- } catch (error) {
133
- console.error('删除失败:', error);
134
- MessagePlugin.info({ message: '删除失败', status: 'error' });
135
137
  }
136
- });
138
+ };
139
+ removeEmptyChildren(roots);
140
+
141
+ return roots;
137
142
  },
138
143
 
139
144
  // 刷新
@@ -141,20 +146,33 @@ const $Method = {
141
146
  $Method.apiMenuList();
142
147
  },
143
148
 
144
- // 分页改变
145
- onPageChange({ currentPage }) {
146
- $Data.pagerConfig.currentPage = currentPage;
147
- $Method.apiMenuList();
149
+ // 单选变化
150
+ onSelectChange(value, { selectedRowData }) {
151
+ $Data.selectedRowKeys = value;
152
+ $Data.activeRowKeys = value;
153
+ if (selectedRowData && selectedRowData.length > 0) {
154
+ $Data.currentRow = selectedRowData[0];
155
+ } else if ($Data.tableData.length > 0) {
156
+ $Data.currentRow = $Data.tableData[0];
157
+ $Data.selectedRowKeys = [$Data.tableData[0].id];
158
+ $Data.activeRowKeys = [$Data.tableData[0].id];
159
+ } else {
160
+ $Data.currentRow = null;
161
+ }
148
162
  },
149
163
 
150
- // 操作菜单点击
151
- onAction(command, rowData) {
152
- $Data.actionType = command;
153
- $Data.rowData = rowData;
154
- if (command === 'add' || command === 'upd') {
155
- $Data.editVisible = true;
156
- } else if (command === 'del') {
157
- $Method.apiMenuDel(rowData);
164
+ // 高亮行变化
165
+ onActiveChange(value, { activeRowData }) {
166
+ $Data.activeRowKeys = value;
167
+ $Data.selectedRowKeys = value;
168
+ if (activeRowData && activeRowData.length > 0) {
169
+ $Data.currentRow = activeRowData[0];
170
+ } else if ($Data.tableData.length > 0) {
171
+ $Data.currentRow = $Data.tableData[0];
172
+ $Data.selectedRowKeys = [$Data.tableData[0].id];
173
+ $Data.activeRowKeys = [$Data.tableData[0].id];
174
+ } else {
175
+ $Data.currentRow = null;
158
176
  }
159
177
  }
160
178
  };
@@ -162,12 +180,6 @@ const $Method = {
162
180
  $Method.initData();
163
181
  </script>
164
182
 
165
- <route lang="yaml">
166
- meta:
167
- layout: default
168
- title: 菜单管理
169
- </route>
170
-
171
183
  <style scoped lang="scss">
172
184
  // 样式继承自全局 page-table
173
185
  </style>
@@ -30,7 +30,7 @@
30
30
  </div>
31
31
  <div class="footer-right">
32
32
  <TButton @click="$Method.onClose">取消</TButton>
33
- <TButton theme="primary" @click="$Method.onSubmit">保存</TButton>
33
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">保存</TButton>
34
34
  </div>
35
35
  </template>
36
36
  </TDialog>
@@ -56,6 +56,7 @@ const $Emit = defineEmits(['update:modelValue', 'success']);
56
56
 
57
57
  const $Data = $ref({
58
58
  visible: false,
59
+ submitting: false,
59
60
  apiData: [],
60
61
  filteredApiData: [],
61
62
  searchText: '',
@@ -113,7 +114,7 @@ const $Method = {
113
114
  $Data.apiData = Array.from(apiMap.values());
114
115
  } catch (error) {
115
116
  console.error('加载接口失败:', error);
116
- MessagePlugin.info({ message: '加载接口失败', status: 'error' });
117
+ MessagePlugin.error('加载接口失败');
117
118
  }
118
119
  },
119
120
 
@@ -167,30 +168,25 @@ const $Method = {
167
168
  // 提交表单
168
169
  async onSubmit() {
169
170
  try {
171
+ $Data.submitting = true;
172
+
170
173
  const res = await $Http('/addon/admin/role/apiSave', {
171
174
  roleId: $Prop.rowData.id,
172
175
  apiIds: $Data.checkedApiIds
173
176
  });
174
177
 
175
178
  if (res.code === 0) {
176
- MessagePlugin.info({
177
- message: '保存成功',
178
- status: 'success'
179
- });
179
+ MessagePlugin.success('保存成功');
180
180
  $Data.visible = false;
181
181
  $Emit('success');
182
182
  } else {
183
- MessagePlugin.info({
184
- message: res.msg || '保存失败',
185
- status: 'error'
186
- });
183
+ MessagePlugin.error(res.msg || '保存失败');
187
184
  }
188
185
  } catch (error) {
189
186
  console.error('保存失败:', error);
190
- MessagePlugin.info({
191
- message: '保存失败',
192
- status: 'error'
193
- });
187
+ MessagePlugin.error('保存失败');
188
+ } finally {
189
+ $Data.submitting = false;
194
190
  }
195
191
  }
196
192
  };
@@ -24,7 +24,7 @@
24
24
  </div>
25
25
  <template #footer>
26
26
  <TButton @click="$Method.onClose">取消</TButton>
27
- <TButton theme="primary" @click="$Method.onSubmit">确定</TButton>
27
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">确定</TButton>
28
28
  </template>
29
29
  </TDialog>
30
30
  </template>
@@ -70,6 +70,7 @@ const $Computed = {};
70
70
 
71
71
  const $Data = $ref({
72
72
  visible: false,
73
+ submitting: false,
73
74
  formData: {
74
75
  id: 0,
75
76
  name: '',
@@ -116,20 +117,17 @@ const $Method = {
116
117
  const valid = await $From.form.validate();
117
118
  if (!valid) return;
118
119
 
120
+ $Data.submitting = true;
119
121
  const res = await $Http($Prop.actionType === 'upd' ? '/addon/admin/roleUpd' : '/addon/admin/roleIns', $Data.formData);
120
122
 
121
- MessagePlugin.info({
122
- message: $Prop.actionType === 'upd' ? '更新成功' : '添加成功',
123
- status: 'success'
124
- });
123
+ MessagePlugin.success($Prop.actionType === 'upd' ? '更新成功' : '添加成功');
125
124
  $Data.visible = false;
126
125
  $Emit('success');
127
126
  } catch (error) {
128
127
  console.error('提交失败:', error);
129
- MessagePlugin.info({
130
- message: '提交失败',
131
- status: 'error'
132
- });
128
+ MessagePlugin.error('提交失败');
129
+ } finally {
130
+ $Data.submitting = false;
133
131
  }
134
132
  }
135
133
  };
@@ -5,7 +5,7 @@
5
5
  </div>
6
6
  <template #footer>
7
7
  <TButton @click="$Method.onClose">取消</TButton>
8
- <TButton theme="primary" @click="$Method.onSubmit">保存</TButton>
8
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">保存</TButton>
9
9
  </template>
10
10
  </TDialog>
11
11
  </template>
@@ -36,6 +36,7 @@ const $From = $shallowRef({
36
36
 
37
37
  const $Data = $ref({
38
38
  visible: false,
39
+ submitting: false,
39
40
  menuTreeData: [],
40
41
  menuTreeCheckedKeys: []
41
42
  });
@@ -69,7 +70,7 @@ const $Method = {
69
70
  $Data.menuTreeData = arrayToTree(menuList);
70
71
  } catch (error) {
71
72
  console.error('加载菜单失败:', error);
72
- MessagePlugin.info({ message: '加载菜单失败', status: 'error' });
73
+ MessagePlugin.error('加载菜单失败');
73
74
  }
74
75
  },
75
76
 
@@ -100,10 +101,12 @@ const $Method = {
100
101
  async onSubmit() {
101
102
  try {
102
103
  if (!$From.tree) {
103
- MessagePlugin.info({ message: '菜单树未初始化', status: 'error' });
104
+ MessagePlugin.error('菜单树未初始化');
104
105
  return;
105
106
  }
106
107
 
108
+ $Data.submitting = true;
109
+
107
110
  // 获取选中的节点(包括半选中的父节点)
108
111
  const checkedKeys = $From.tree.getCheckedKeys();
109
112
  const halfCheckedKeys = $From.tree.getHalfCheckedKeys();
@@ -115,24 +118,17 @@ const $Method = {
115
118
  });
116
119
 
117
120
  if (res.code === 0) {
118
- MessagePlugin.info({
119
- message: '保存成功',
120
- status: 'success'
121
- });
121
+ MessagePlugin.success('保存成功');
122
122
  $Data.visible = false;
123
123
  $Emit('success');
124
124
  } else {
125
- MessagePlugin.info({
126
- message: res.msg || '保存失败',
127
- status: 'error'
128
- });
125
+ MessagePlugin.error(res.msg || '保存失败');
129
126
  }
130
127
  } catch (error) {
131
128
  console.error('保存失败:', error);
132
- MessagePlugin.info({
133
- message: '保存失败',
134
- status: 'error'
135
- });
129
+ MessagePlugin.error('保存失败');
130
+ } finally {
131
+ $Data.submitting = false;
136
132
  }
137
133
  }
138
134
  };