@befly-addon/admin 1.0.54 → 1.0.56

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 (82) hide show
  1. package/apis/admin/cacheRefresh.ts +17 -14
  2. package/apis/admin/ins.ts +11 -15
  3. package/apis/admin/upd.ts +1 -13
  4. package/apis/api/all.ts +1 -1
  5. package/apis/api/list.ts +30 -0
  6. package/apis/auth/login.ts +51 -7
  7. package/apis/dashboard/serviceStatus.ts +2 -2
  8. package/apis/dashboard/systemResources.ts +1 -1
  9. package/apis/dict/all.ts +1 -1
  10. package/apis/dict/del.ts +1 -1
  11. package/apis/dict/detail.ts +2 -2
  12. package/apis/dict/ins.ts +1 -1
  13. package/apis/dict/upd.ts +1 -1
  14. package/apis/email/config.ts +16 -0
  15. package/apis/email/logList.ts +13 -0
  16. package/apis/email/send.ts +42 -0
  17. package/apis/email/verify.ts +12 -0
  18. package/apis/loginLog/list.ts +13 -0
  19. package/apis/menu/all.ts +2 -2
  20. package/apis/menu/list.ts +1 -1
  21. package/apis/operateLog/list.ts +13 -0
  22. package/apis/role/del.ts +2 -2
  23. package/apis/role/save.ts +2 -2
  24. package/apis/sysConfig/all.ts +12 -0
  25. package/apis/sysConfig/del.ts +30 -0
  26. package/apis/sysConfig/get.ts +31 -0
  27. package/apis/sysConfig/ins.ts +38 -0
  28. package/apis/sysConfig/list.ts +14 -0
  29. package/apis/sysConfig/upd.ts +50 -0
  30. package/package.json +14 -3
  31. package/plugins/email.ts +206 -0
  32. package/styles/variables.scss +121 -60
  33. package/tables/admin.json +0 -15
  34. package/tables/emailLog.json +69 -0
  35. package/tables/loginLog.json +96 -0
  36. package/tables/operateLog.json +82 -0
  37. package/tables/role.json +1 -1
  38. package/tables/sysConfig.json +53 -0
  39. package/views/403_1/meta.json +4 -0
  40. package/views/{dict → config/dict}/components/edit.vue +7 -5
  41. package/views/config/dict/index.vue +205 -0
  42. package/views/config/dict/meta.json +4 -0
  43. package/views/config/meta.json +4 -0
  44. package/views/config/system/components/edit.vue +179 -0
  45. package/views/config/system/index.vue +256 -0
  46. package/views/config/system/meta.json +4 -0
  47. package/views/index/index.vue +46 -9
  48. package/views/index/meta.json +4 -0
  49. package/views/log/email/index.vue +285 -0
  50. package/views/log/email/meta.json +4 -0
  51. package/views/log/login/index.vue +180 -0
  52. package/views/log/login/meta.json +4 -0
  53. package/views/log/meta.json +4 -0
  54. package/views/log/operate/index.vue +242 -0
  55. package/views/log/operate/meta.json +4 -0
  56. package/views/login_1/meta.json +4 -0
  57. package/views/{admin → people/admin}/components/edit.vue +23 -35
  58. package/views/{admin → people/admin}/index.vue +37 -96
  59. package/views/people/admin/meta.json +4 -0
  60. package/views/people/meta.json +4 -0
  61. package/views/permission/api/index.vue +143 -0
  62. package/views/permission/api/meta.json +4 -0
  63. package/views/permission/menu/index.vue +146 -0
  64. package/views/permission/menu/meta.json +4 -0
  65. package/views/permission/meta.json +4 -0
  66. package/views/{role → permission/role}/components/api.vue +22 -51
  67. package/views/{role → permission/role}/components/edit.vue +14 -15
  68. package/views/{role → permission/role}/components/menu.vue +11 -38
  69. package/views/permission/role/index.vue +227 -0
  70. package/views/permission/role/meta.json +4 -0
  71. package/apis/menu/del.ts +0 -31
  72. package/apis/menu/ins.ts +0 -19
  73. package/apis/menu/upd.ts +0 -27
  74. package/views/dict/index.vue +0 -162
  75. package/views/menu/components/edit.vue +0 -145
  76. package/views/menu/index.vue +0 -173
  77. package/views/role/index.vue +0 -184
  78. /package/views/{403 → 403_1}/index.vue +0 -0
  79. /package/views/{login → login_1}/components/emailLoginForm.vue +0 -0
  80. /package/views/{login → login_1}/components/registerForm.vue +0 -0
  81. /package/views/{login → login_1}/components/welcomePanel.vue +0 -0
  82. /package/views/{login/index_1.vue → login_1/index.vue} +0 -0
@@ -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 :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="calc(100vh - 94px)" active-row-type="single" @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>
@@ -27,7 +27,10 @@
27
27
  </template>
28
28
  <template #operation="{ row }">
29
29
  <TDropdown trigger="click" placement="bottom-right" @click="(data) => $Method.onAction(data.value, row)">
30
- <TButton theme="primary" size="small">操作</TButton>
30
+ <TButton theme="primary" size="small">
31
+ 操作
32
+ <template #suffix> <t-icon name="chevron-down" size="16" /></template>
33
+ </TButton>
31
34
  <TDropdownMenu slot="dropdown">
32
35
  <TDropdownItem value="upd">
33
36
  <ILucidePencil />
@@ -44,45 +47,12 @@
44
47
  </div>
45
48
 
46
49
  <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>
50
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.columns" />
81
51
  </div>
82
52
  </div>
83
53
 
84
54
  <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" />
55
+ <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
56
  </div>
87
57
 
88
58
  <!-- 编辑对话框组件 -->
@@ -97,30 +67,25 @@ import ILucideRotateCw from '~icons/lucide/rotate-cw';
97
67
  import ILucidePencil from '~icons/lucide/pencil';
98
68
  import ILucideTrash2 from '~icons/lucide/trash-2';
99
69
  import EditDialog from './components/edit.vue';
70
+ import DetailPanel from '@/components/DetailPanel.vue';
100
71
  import { $Http } from '@/plugins/http';
72
+ import { withDefaultColumns } from '@/utils';
101
73
 
102
74
  // 响应式数据
103
75
  const $Data = $ref({
104
76
  tableData: [],
105
- columns: [
106
- {
107
- colKey: 'row-select',
108
- type: 'single',
109
- width: 50,
110
- fixed: 'left',
111
- checkProps: { allowUncheck: true }
112
- },
113
- { colKey: 'username', title: '用户名', width: 150, fixed: 'left' },
114
- { colKey: 'id', title: '序号', width: 150, align: 'center' },
115
- { colKey: 'email', title: '邮箱', width: 200 },
116
- { colKey: 'nickname', title: '昵称', width: 150 },
117
- { colKey: 'roleCode', title: '角色', width: 120 },
118
- { colKey: 'state', title: '状态', width: 100 },
119
- { colKey: 'operation', title: '操作', width: 80, align: 'center', fixed: 'right' }
120
- ],
77
+ loading: false,
78
+ columns: withDefaultColumns([
79
+ { colKey: 'username', title: '用户名', fixed: 'left' },
80
+ { colKey: 'id', title: '序号' },
81
+ { colKey: 'nickname', title: '昵称' },
82
+ { colKey: 'roleCode', title: '角色' },
83
+ { colKey: 'state', title: '状态' },
84
+ { colKey: 'operation', title: '操作' }
85
+ ]),
121
86
  pagerConfig: {
122
87
  currentPage: 1,
123
- pageSize: 30,
88
+ limit: 30,
124
89
  total: 0,
125
90
  align: 'right',
126
91
  layout: 'total, prev, pager, next, jumper'
@@ -129,7 +94,6 @@ const $Data = $ref({
129
94
  actionType: 'add',
130
95
  rowData: {},
131
96
  currentRow: null,
132
- selectedRowKeys: [],
133
97
  activeRowKeys: []
134
98
  });
135
99
 
@@ -141,30 +105,28 @@ const $Method = {
141
105
 
142
106
  // 加载管理员列表
143
107
  async apiAdminList() {
108
+ $Data.loading = true;
144
109
  try {
145
110
  const res = await $Http('/addon/admin/admin/list', {
146
111
  page: $Data.pagerConfig.currentPage,
147
- limit: $Data.pagerConfig.pageSize
112
+ limit: $Data.pagerConfig.limit
148
113
  });
149
114
  $Data.tableData = res.data.lists || [];
150
115
  $Data.pagerConfig.total = res.data.total || 0;
151
116
 
152
- // 自动选中并高亮第一行
117
+ // 自动高亮第一行
153
118
  if ($Data.tableData.length > 0) {
154
119
  $Data.currentRow = $Data.tableData[0];
155
- $Data.selectedRowKeys = [$Data.tableData[0].id];
156
120
  $Data.activeRowKeys = [$Data.tableData[0].id];
157
121
  } else {
158
122
  $Data.currentRow = null;
159
- $Data.selectedRowKeys = [];
160
123
  $Data.activeRowKeys = [];
161
124
  }
162
125
  } catch (error) {
163
126
  console.error('加载管理员列表失败:', error);
164
- MessagePlugin.info({
165
- message: '加载数据失败',
166
- status: 'error'
167
- });
127
+ MessagePlugin.error('加载数据失败');
128
+ } finally {
129
+ $Data.loading = false;
168
130
  }
169
131
  },
170
132
 
@@ -172,20 +134,20 @@ const $Method = {
172
134
  async apiAdminDel(row) {
173
135
  DialogPlugin.confirm({
174
136
  header: '确认删除',
175
- body: `确定要删除管理员"${row.username}" 吗?`,
137
+ body: `确定要删除管理员“${row.username} 吗?`,
176
138
  status: 'warning'
177
139
  }).then(async () => {
178
140
  try {
179
141
  const res = await $Http('/addon/admin/admin/del', { id: row.id });
180
142
  if (res.code === 0) {
181
- MessagePlugin.info({ message: '删除成功', status: 'success' });
143
+ MessagePlugin.success('删除成功');
182
144
  $Method.apiAdminList();
183
145
  } else {
184
- MessagePlugin.info({ message: res.msg || '删除失败', status: 'error' });
146
+ MessagePlugin.error(res.msg || '删除失败');
185
147
  }
186
148
  } catch (error) {
187
149
  console.error('删除失败:', error);
188
- MessagePlugin.info({ message: '删除失败', status: 'error' });
150
+ MessagePlugin.error('删除失败');
189
151
  }
190
152
  });
191
153
  },
@@ -203,42 +165,21 @@ const $Method = {
203
165
 
204
166
  // 每页条数改变
205
167
  handleSizeChange({ pageSize }) {
206
- $Data.pagerConfig.pageSize = pageSize;
168
+ $Data.pagerConfig.limit = pageSize;
207
169
  $Data.pagerConfig.currentPage = 1;
208
170
  $Method.apiAdminList();
209
171
  },
210
172
 
211
- // 单选变化
212
- onSelectChange(value, { selectedRowData }) {
213
- $Data.selectedRowKeys = value;
214
- $Data.activeRowKeys = value;
215
- // 更新当前选中的行数据
216
- if (selectedRowData && selectedRowData.length > 0) {
217
- $Data.currentRow = selectedRowData[0];
218
- } else if ($Data.tableData.length > 0) {
219
- // 如果取消选中,默认显示第一行
220
- $Data.currentRow = $Data.tableData[0];
221
- $Data.selectedRowKeys = [$Data.tableData[0].id];
222
- $Data.activeRowKeys = [$Data.tableData[0].id];
223
- } else {
224
- $Data.currentRow = null;
225
- }
226
- },
227
-
228
173
  // 高亮行变化
229
- onActiveChange(value, { activeRowData }) {
174
+ onActiveChange(value, context) {
175
+ // 禁止取消高亮:如果新值为空,保持当前选中
176
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
177
+ return;
178
+ }
230
179
  $Data.activeRowKeys = value;
231
- $Data.selectedRowKeys = value;
232
180
  // 更新当前高亮的行数据
233
- if (activeRowData && activeRowData.length > 0) {
234
- $Data.currentRow = activeRowData[0];
235
- } else if ($Data.tableData.length > 0) {
236
- // 如果取消高亮,默认显示第一行
237
- $Data.currentRow = $Data.tableData[0];
238
- $Data.selectedRowKeys = [$Data.tableData[0].id];
239
- $Data.activeRowKeys = [$Data.tableData[0].id];
240
- } else {
241
- $Data.currentRow = null;
181
+ if (context.activeRowList && context.activeRowList.length > 0) {
182
+ $Data.currentRow = context.activeRowList[0].row;
242
183
  }
243
184
  },
244
185
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "管理员",
3
+ "order": 1
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "人员管理",
3
+ "order": 10
4
+ }
@@ -0,0 +1,143 @@
1
+ <template>
2
+ <div class="page-api page-table">
3
+ <div class="main-tool">
4
+ <div class="left">
5
+ <TInput v-model="$Data.searchKeyword" placeholder="搜索接口名称或路径" clearable style="width: 300px" @enter="$Method.handleSearch" @clear="$Method.handleSearch">
6
+ <template #suffix-icon>
7
+ <ILucideSearch />
8
+ </template>
9
+ </TInput>
10
+ <span style="margin-left: 16px; color: var(--text-secondary); font-size: 13px">共 {{ $Data.allData.length }} 个接口</span>
11
+ </div>
12
+ <div class="right">
13
+ <TButton shape="circle" @click="$Method.handleRefresh">
14
+ <template #icon>
15
+ <ILucideRotateCw />
16
+ </template>
17
+ </TButton>
18
+ </div>
19
+ </div>
20
+
21
+ <div class="main-content">
22
+ <div class="main-table">
23
+ <TTable :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="calc(100vh - 94px)" active-row-type="single" @active-change="$Method.onActiveChange">
24
+ <template #method="{ row }">
25
+ <TTag v-if="row.method === 'GET'" shape="round" theme="success" variant="light-outline">GET</TTag>
26
+ <TTag v-else-if="row.method === 'POST'" shape="round" theme="primary" variant="light-outline">POST</TTag>
27
+ <TTag v-else-if="row.method === 'PUT'" shape="round" theme="warning" variant="light-outline">PUT</TTag>
28
+ <TTag v-else-if="row.method === 'DELETE'" shape="round" theme="danger" variant="light-outline">DELETE</TTag>
29
+ <TTag v-else shape="round" variant="light-outline">{{ row.method }}</TTag>
30
+ </template>
31
+ <template #addonName="{ row }">
32
+ <TTag v-if="row.addonName" shape="round" variant="light-outline">{{ row.addonTitle || row.addonName }}</TTag>
33
+ <span v-else>项目</span>
34
+ </template>
35
+ </TTable>
36
+ </div>
37
+
38
+ <div class="main-detail">
39
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.columns">
40
+ <template #method="{ value }">
41
+ <TTag v-if="value === 'GET'" shape="round" theme="success" variant="light-outline">GET</TTag>
42
+ <TTag v-else-if="value === 'POST'" shape="round" theme="primary" variant="light-outline">POST</TTag>
43
+ <TTag v-else-if="value === 'PUT'" shape="round" theme="warning" variant="light-outline">PUT</TTag>
44
+ <TTag v-else-if="value === 'DELETE'" shape="round" theme="danger" variant="light-outline">DELETE</TTag>
45
+ <TTag v-else shape="round" variant="light-outline">{{ value }}</TTag>
46
+ </template>
47
+ </DetailPanel>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </template>
52
+
53
+ <script setup>
54
+ import { Button as TButton, Table as TTable, Tag as TTag, Input as TInput, MessagePlugin } from 'tdesign-vue-next';
55
+ import ILucideRotateCw from '~icons/lucide/rotate-cw';
56
+ import ILucideSearch from '~icons/lucide/search';
57
+ import { $Http } from '@/plugins/http';
58
+ import { withDefaultColumns } from '@/utils';
59
+ import DetailPanel from '@/components/DetailPanel.vue';
60
+
61
+ // 响应式数据
62
+ const $Data = $ref({
63
+ tableData: [],
64
+ allData: [],
65
+ loading: false,
66
+ searchKeyword: '',
67
+ columns: withDefaultColumns([
68
+ { colKey: 'name', title: '接口名称' },
69
+ { colKey: 'id', title: '序号' },
70
+ { colKey: 'path', title: '接口路径' },
71
+ { colKey: 'method', title: '请求方法' },
72
+ { colKey: 'addonName', title: '所属组件' }
73
+ ]),
74
+ currentRow: null,
75
+ activeRowKeys: []
76
+ });
77
+
78
+ // 方法
79
+ const $Method = {
80
+ async initData() {
81
+ await $Method.loadApiAll();
82
+ },
83
+
84
+ // 加载全部接口
85
+ async loadApiAll() {
86
+ $Data.loading = true;
87
+ try {
88
+ const res = await $Http('/addon/admin/api/all');
89
+ const list = res.data?.lists || [];
90
+ $Data.allData = list;
91
+ $Data.tableData = list;
92
+
93
+ // 自动高亮第一行
94
+ if ($Data.tableData.length > 0) {
95
+ $Data.currentRow = $Data.tableData[0];
96
+ $Data.activeRowKeys = [$Data.tableData[0].id];
97
+ } else {
98
+ $Data.currentRow = null;
99
+ $Data.activeRowKeys = [];
100
+ }
101
+ } catch (error) {
102
+ console.error('加载接口列表失败:', error);
103
+ MessagePlugin.error('加载数据失败');
104
+ } finally {
105
+ $Data.loading = false;
106
+ }
107
+ },
108
+
109
+ // 刷新
110
+ handleRefresh() {
111
+ $Method.loadApiAll();
112
+ },
113
+
114
+ // 搜索
115
+ handleSearch() {
116
+ if (!$Data.searchKeyword) {
117
+ $Data.tableData = $Data.allData;
118
+ return;
119
+ }
120
+ const keyword = $Data.searchKeyword.toLowerCase();
121
+ $Data.tableData = $Data.allData.filter((item) => item.name?.toLowerCase().includes(keyword) || item.path?.toLowerCase().includes(keyword));
122
+ },
123
+
124
+ // 高亮行变化
125
+ onActiveChange(value, context) {
126
+ // 禁止取消高亮:如果新值为空,保持当前选中
127
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
128
+ return;
129
+ }
130
+ $Data.activeRowKeys = value;
131
+ // 更新当前高亮的行数据
132
+ if (context.activeRowList && context.activeRowList.length > 0) {
133
+ $Data.currentRow = context.activeRowList[0].row;
134
+ }
135
+ }
136
+ };
137
+
138
+ $Method.initData();
139
+ </script>
140
+
141
+ <style scoped lang="scss">
142
+ // 样式继承自全局 page-table
143
+ </style>
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "接口列表",
3
+ "order": 3
4
+ }
@@ -0,0 +1,146 @@
1
+ <template>
2
+ <div class="page-menu page-table">
3
+ <div class="main-tool">
4
+ <div class="left"></div>
5
+ <div class="right">
6
+ <TButton shape="circle" @click="$Method.handleRefresh">
7
+ <template #icon>
8
+ <ILucideRotateCw />
9
+ </template>
10
+ </TButton>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="main-content">
15
+ <div class="main-table">
16
+ <TTable v-bind="withTreeTableProps()" :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="calc(100vh - 94px)" active-row-type="single" @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>
24
+
25
+ <div class="main-detail">
26
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.columns" />
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script setup>
33
+ import { Button as TButton, Table as TTable, Tag as TTag, MessagePlugin } from 'tdesign-vue-next';
34
+ import ILucideRotateCw from '~icons/lucide/rotate-cw';
35
+ import DetailPanel from '@/components/DetailPanel.vue';
36
+ import { $Http } from '@/plugins/http';
37
+ import { withDefaultColumns, withTreeTableProps } from '@/utils';
38
+
39
+ // 响应式数据
40
+ const $Data = $ref({
41
+ tableData: [],
42
+ loading: false,
43
+ columns: withDefaultColumns([
44
+ { colKey: 'name', title: '菜单名称', fixed: 'left' },
45
+ { colKey: 'id', title: '序号' },
46
+ { colKey: 'path', title: '路由路径' },
47
+ { colKey: 'icon', title: '图标' },
48
+ { colKey: 'sort', title: '排序' },
49
+ { colKey: 'state', title: '状态' }
50
+ ]),
51
+ currentRow: null,
52
+ activeRowKeys: []
53
+ });
54
+
55
+ // 方法
56
+ const $Method = {
57
+ async initData() {
58
+ await $Method.apiMenuList();
59
+ },
60
+
61
+ // 加载菜单列表(树形结构)
62
+ async apiMenuList() {
63
+ $Data.loading = true;
64
+ try {
65
+ const res = await $Http('/addon/admin/menu/list');
66
+ // 构建树形结构
67
+ $Data.tableData = $Method.buildTree(res.data || []);
68
+
69
+ // 自动高亮第一行
70
+ if ($Data.tableData.length > 0) {
71
+ $Data.currentRow = $Data.tableData[0];
72
+ $Data.activeRowKeys = [$Data.tableData[0].id];
73
+ } else {
74
+ $Data.currentRow = null;
75
+ $Data.activeRowKeys = [];
76
+ }
77
+ } catch (error) {
78
+ console.error('加载菜单列表失败:', error);
79
+ MessagePlugin.error('加载数据失败');
80
+ } finally {
81
+ $Data.loading = false;
82
+ }
83
+ },
84
+
85
+ // 构建树形结构
86
+ buildTree(list) {
87
+ const map = {};
88
+ const roots = [];
89
+
90
+ // 先创建所有节点的映射
91
+ for (const item of list) {
92
+ map[item.id] = { ...item, children: [] };
93
+ }
94
+
95
+ // 构建树形结构
96
+ for (const item of list) {
97
+ const node = map[item.id];
98
+ if (item.pid === 0) {
99
+ roots.push(node);
100
+ } else if (map[item.pid]) {
101
+ map[item.pid].children.push(node);
102
+ } else {
103
+ roots.push(node);
104
+ }
105
+ }
106
+
107
+ // 移除空的 children 数组
108
+ const removeEmptyChildren = (nodes) => {
109
+ for (const node of nodes) {
110
+ if (node.children.length === 0) {
111
+ delete node.children;
112
+ } else {
113
+ removeEmptyChildren(node.children);
114
+ }
115
+ }
116
+ };
117
+ removeEmptyChildren(roots);
118
+
119
+ return roots;
120
+ },
121
+
122
+ // 刷新
123
+ handleRefresh() {
124
+ $Method.apiMenuList();
125
+ },
126
+
127
+ // 高亮行变化
128
+ onActiveChange(value, context) {
129
+ // 禁止取消高亮:如果新值为空,保持当前选中
130
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
131
+ return;
132
+ }
133
+ $Data.activeRowKeys = value;
134
+ // 更新当前高亮的行数据
135
+ if (context.activeRowList && context.activeRowList.length > 0) {
136
+ $Data.currentRow = context.activeRowList[0].row;
137
+ }
138
+ }
139
+ };
140
+
141
+ $Method.initData();
142
+ </script>
143
+
144
+ <style scoped lang="scss">
145
+ // 样式继承自全局 page-table
146
+ </style>
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "菜单列表",
3
+ "order": 2
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "权限设置",
3
+ "order": 20
4
+ }