@befly-addon/admin 1.0.55 → 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 (61) hide show
  1. package/apis/admin/ins.ts +11 -15
  2. package/apis/admin/upd.ts +1 -13
  3. package/apis/auth/login.ts +49 -5
  4. package/apis/email/config.ts +16 -0
  5. package/apis/email/logList.ts +13 -0
  6. package/apis/email/send.ts +42 -0
  7. package/apis/email/verify.ts +12 -0
  8. package/apis/loginLog/list.ts +13 -0
  9. package/apis/operateLog/list.ts +13 -0
  10. package/apis/sysConfig/all.ts +12 -0
  11. package/apis/sysConfig/del.ts +30 -0
  12. package/apis/sysConfig/get.ts +31 -0
  13. package/apis/sysConfig/ins.ts +38 -0
  14. package/apis/sysConfig/list.ts +14 -0
  15. package/apis/sysConfig/upd.ts +50 -0
  16. package/package.json +14 -3
  17. package/plugins/email.ts +206 -0
  18. package/styles/variables.scss +121 -60
  19. package/tables/admin.json +0 -15
  20. package/tables/emailLog.json +69 -0
  21. package/tables/loginLog.json +96 -0
  22. package/tables/operateLog.json +82 -0
  23. package/tables/role.json +1 -1
  24. package/tables/sysConfig.json +53 -0
  25. package/views/403_1/meta.json +4 -0
  26. package/views/{dict → config/dict}/index.vue +27 -38
  27. package/views/config/dict/meta.json +4 -0
  28. package/views/config/meta.json +4 -0
  29. package/views/config/system/components/edit.vue +179 -0
  30. package/views/config/system/index.vue +256 -0
  31. package/views/config/system/meta.json +4 -0
  32. package/views/index/index.vue +46 -9
  33. package/views/index/meta.json +4 -0
  34. package/views/log/email/index.vue +285 -0
  35. package/views/log/email/meta.json +4 -0
  36. package/views/log/login/index.vue +180 -0
  37. package/views/log/login/meta.json +4 -0
  38. package/views/log/meta.json +4 -0
  39. package/views/log/operate/index.vue +242 -0
  40. package/views/log/operate/meta.json +4 -0
  41. package/views/login_1/meta.json +4 -0
  42. package/views/{admin → people/admin}/components/edit.vue +22 -24
  43. package/views/{admin → people/admin}/index.vue +21 -61
  44. package/views/people/admin/meta.json +4 -0
  45. package/views/people/meta.json +4 -0
  46. package/views/{api → permission/api}/index.vue +17 -55
  47. package/views/permission/api/meta.json +4 -0
  48. package/views/{menu → permission/menu}/index.vue +17 -56
  49. package/views/permission/menu/meta.json +4 -0
  50. package/views/permission/meta.json +4 -0
  51. package/views/{role → permission/role}/components/api.vue +13 -38
  52. package/views/{role → permission/role}/components/edit.vue +9 -8
  53. package/views/{role → permission/role}/components/menu.vue +2 -25
  54. package/views/{role → permission/role}/index.vue +27 -36
  55. package/views/permission/role/meta.json +4 -0
  56. /package/views/{403 → 403_1}/index.vue +0 -0
  57. /package/views/{dict → config/dict}/components/edit.vue +0 -0
  58. /package/views/{login → login_1}/components/emailLoginForm.vue +0 -0
  59. /package/views/{login → login_1}/components/registerForm.vue +0 -0
  60. /package/views/{login → login_1}/components/welcomePanel.vue +0 -0
  61. /package/views/{login/index_1.vue → login_1/index.vue} +0 -0
@@ -0,0 +1,242 @@
1
+ <template>
2
+ <div class="page-operate-log page-table">
3
+ <div class="main-tool">
4
+ <div class="left">
5
+ <TSelect v-model="$Data.filter.module" placeholder="操作模块" clearable style="width: 150px" @change="$Method.handleFilter">
6
+ <TOption v-for="item in $Data.moduleOptions" :key="item.value" :label="item.label" :value="item.value" />
7
+ </TSelect>
8
+ <TSelect v-model="$Data.filter.action" placeholder="操作类型" clearable style="width: 150px" @change="$Method.handleFilter">
9
+ <TOption v-for="item in $Data.actionOptions" :key="item.value" :label="item.label" :value="item.value" />
10
+ </TSelect>
11
+ <TSelect v-model="$Data.filter.result" placeholder="操作结果" clearable style="width: 120px" @change="$Method.handleFilter">
12
+ <TOption label="成功" :value="1" />
13
+ <TOption label="失败" :value="0" />
14
+ </TSelect>
15
+ </div>
16
+ <div class="right">
17
+ <TButton shape="circle" @click="$Method.handleRefresh">
18
+ <template #icon>
19
+ <ILucideRotateCw />
20
+ </template>
21
+ </TButton>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="main-content">
26
+ <div class="main-table">
27
+ <TTable :data="$Data.tableData" :columns="$Data.columns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" row-key="id" height="100%" active-row-type="single" @active-change="$Method.onActiveChange">
28
+ <template #result="{ row }">
29
+ <TTag v-if="row.result === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
30
+ <TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
31
+ </template>
32
+ <template #operateTime="{ row }">
33
+ {{ $Method.formatTime(row.operateTime) }}
34
+ </template>
35
+ <template #duration="{ row }">
36
+ <TTag shape="round" :theme="row.duration > 1000 ? 'warning' : 'default'" variant="light-outline">{{ row.duration }}ms</TTag>
37
+ </template>
38
+ <template #action="{ row }">
39
+ <TTag shape="round" variant="light-outline">{{ row.action }}</TTag>
40
+ </template>
41
+ </TTable>
42
+ </div>
43
+
44
+ <div class="main-detail">
45
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.detailFields">
46
+ <template #result="{ value }">
47
+ <TTag v-if="value === 1" shape="round" theme="success" variant="light-outline">成功</TTag>
48
+ <TTag v-else shape="round" theme="danger" variant="light-outline">失败</TTag>
49
+ </template>
50
+ <template #operateTime="{ value }">
51
+ {{ $Method.formatTime(value) }}
52
+ </template>
53
+ <template #duration="{ value }">
54
+ <TTag shape="round" :theme="value > 1000 ? 'warning' : 'default'" variant="light-outline">{{ value }}ms</TTag>
55
+ </template>
56
+ <template #params="{ value }">
57
+ <pre class="json-content">{{ $Method.formatJson(value) }}</pre>
58
+ </template>
59
+ <template #response="{ value }">
60
+ <pre class="json-content">{{ $Method.formatJson(value) }}</pre>
61
+ </template>
62
+ </DetailPanel>
63
+ </div>
64
+ </div>
65
+
66
+ <div class="main-page">
67
+ <TPagination :current-page="$Data.pagerConfig.currentPage" :page-size="$Data.pagerConfig.limit" :total="$Data.pagerConfig.total" @current-change="$Method.onPageChange" @page-size-change="$Method.handleSizeChange" />
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <script setup>
73
+ import { Button as TButton, Table as TTable, Tag as TTag, Pagination as TPagination, Select as TSelect, Option as TOption, MessagePlugin } from 'tdesign-vue-next';
74
+ import ILucideRotateCw from '~icons/lucide/rotate-cw';
75
+ import DetailPanel from '@/components/DetailPanel.vue';
76
+ import { $Http } from '@/plugins/http';
77
+ import { withDefaultColumns } from '@/utils';
78
+
79
+ // 响应式数据
80
+ const $Data = $ref({
81
+ tableData: [],
82
+ loading: false,
83
+ columns: withDefaultColumns([
84
+ { colKey: 'username', title: '操作人', fixed: 'left', width: 100 },
85
+ { colKey: 'id', title: '序号', width: 80 },
86
+ { colKey: 'module', title: '模块', width: 100 },
87
+ { colKey: 'action', title: '操作', width: 80 },
88
+ { colKey: 'path', title: '请求路径', ellipsis: true },
89
+ { colKey: 'ip', title: 'IP地址', width: 130 },
90
+ { colKey: 'duration', title: '耗时', width: 100 },
91
+ { colKey: 'operateTime', title: '操作时间', width: 170 },
92
+ { colKey: 'result', title: '结果', width: 80 }
93
+ ]),
94
+ detailFields: [
95
+ { colKey: 'username', title: '操作人账号' },
96
+ { colKey: 'nickname', title: '操作人昵称' },
97
+ { colKey: 'module', title: '操作模块' },
98
+ { colKey: 'action', title: '操作类型' },
99
+ { colKey: 'method', title: '请求方法' },
100
+ { colKey: 'path', title: '请求路径' },
101
+ { colKey: 'ip', title: 'IP地址' },
102
+ { colKey: 'params', title: '请求参数' },
103
+ { colKey: 'response', title: '响应内容' },
104
+ { colKey: 'duration', title: '耗时' },
105
+ { colKey: 'operateTime', title: '操作时间' },
106
+ { colKey: 'result', title: '操作结果' },
107
+ { colKey: 'remark', title: '备注' }
108
+ ],
109
+ pagerConfig: {
110
+ currentPage: 1,
111
+ limit: 30,
112
+ total: 0
113
+ },
114
+ currentRow: null,
115
+ activeRowKeys: [],
116
+ filter: {
117
+ module: '',
118
+ action: '',
119
+ result: null
120
+ },
121
+ moduleOptions: [
122
+ { label: '管理员', value: '管理员' },
123
+ { label: '角色', value: '角色' },
124
+ { label: '菜单', value: '菜单' },
125
+ { label: '接口', value: '接口' },
126
+ { label: '字典', value: '字典' }
127
+ ],
128
+ actionOptions: [
129
+ { label: '新增', value: '新增' },
130
+ { label: '编辑', value: '编辑' },
131
+ { label: '删除', value: '删除' },
132
+ { label: '查询', value: '查询' }
133
+ ]
134
+ });
135
+
136
+ // 方法
137
+ const $Method = {
138
+ async initData() {
139
+ await $Method.apiOperateLogList();
140
+ },
141
+
142
+ // 加载操作日志列表
143
+ async apiOperateLogList() {
144
+ $Data.loading = true;
145
+ try {
146
+ const res = await $Http('/addon/admin/operateLog/list', {
147
+ page: $Data.pagerConfig.currentPage,
148
+ limit: $Data.pagerConfig.limit
149
+ });
150
+ $Data.tableData = res.data.lists || [];
151
+ $Data.pagerConfig.total = res.data.total || 0;
152
+
153
+ if ($Data.tableData.length > 0) {
154
+ $Data.currentRow = $Data.tableData[0];
155
+ $Data.activeRowKeys = [$Data.tableData[0].id];
156
+ } else {
157
+ $Data.currentRow = null;
158
+ $Data.activeRowKeys = [];
159
+ }
160
+ } catch (error) {
161
+ MessagePlugin.error('加载数据失败');
162
+ } finally {
163
+ $Data.loading = false;
164
+ }
165
+ },
166
+
167
+ // 筛选
168
+ handleFilter() {
169
+ $Data.pagerConfig.currentPage = 1;
170
+ $Method.apiOperateLogList();
171
+ },
172
+
173
+ // 刷新
174
+ handleRefresh() {
175
+ $Method.apiOperateLogList();
176
+ },
177
+
178
+ // 分页改变
179
+ onPageChange(currentPage) {
180
+ $Data.pagerConfig.currentPage = currentPage;
181
+ $Method.apiOperateLogList();
182
+ },
183
+
184
+ // 每页条数改变
185
+ handleSizeChange(pageSize) {
186
+ $Data.pagerConfig.limit = pageSize;
187
+ $Data.pagerConfig.currentPage = 1;
188
+ $Method.apiOperateLogList();
189
+ },
190
+
191
+ // 高亮行变化
192
+ onActiveChange(value, context) {
193
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
194
+ return;
195
+ }
196
+ $Data.activeRowKeys = value;
197
+ if (context.activeRowList && context.activeRowList.length > 0) {
198
+ $Data.currentRow = context.activeRowList[0].row;
199
+ }
200
+ },
201
+
202
+ // 格式化时间
203
+ formatTime(timestamp) {
204
+ if (!timestamp) return '-';
205
+ const date = new Date(timestamp);
206
+ const year = date.getFullYear();
207
+ const month = String(date.getMonth() + 1).padStart(2, '0');
208
+ const day = String(date.getDate()).padStart(2, '0');
209
+ const hours = String(date.getHours()).padStart(2, '0');
210
+ const minutes = String(date.getMinutes()).padStart(2, '0');
211
+ const seconds = String(date.getSeconds()).padStart(2, '0');
212
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
213
+ },
214
+
215
+ // 格式化 JSON
216
+ formatJson(value) {
217
+ if (!value) return '-';
218
+ try {
219
+ const obj = typeof value === 'string' ? JSON.parse(value) : value;
220
+ return JSON.stringify(obj, null, 2);
221
+ } catch {
222
+ return value;
223
+ }
224
+ }
225
+ };
226
+
227
+ $Method.initData();
228
+ </script>
229
+
230
+ <style scoped lang="scss">
231
+ .json-content {
232
+ margin: 0;
233
+ padding: 8px;
234
+ background: var(--td-bg-color-container);
235
+ border-radius: 4px;
236
+ font-size: 12px;
237
+ max-height: 200px;
238
+ overflow: auto;
239
+ white-space: pre-wrap;
240
+ word-break: break-all;
241
+ }
242
+ </style>
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "操作日志",
3
+ "order": 3
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "登录页",
3
+ "order": 100
4
+ }
@@ -3,23 +3,17 @@
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.allRoleLists" placeholder="请选择角色" />
6
+ <TSelect v-model="$Data.formData.roleId" :options="$Data.allRoleLists" :keys="$Data.keys" placeholder="请选择角色" />
7
7
  </TFormItem>
8
8
  <TFormItem label="用户名" prop="username">
9
9
  <TInput v-model="$Data.formData.username" placeholder="请输入用户名" :disabled="$Prop.actionType === 'upd'" />
10
10
  </TFormItem>
11
- <TFormItem label="邮箱" prop="email">
12
- <TInput v-model="$Data.formData.email" placeholder="请输入邮箱" />
13
- </TFormItem>
14
11
  <TFormItem v-if="$Prop.actionType === 'add'" label="密码" prop="password">
15
12
  <TInput v-model="$Data.formData.password" type="password" placeholder="请输入密码,至少6位" />
16
13
  </TFormItem>
17
14
  <TFormItem label="昵称" prop="nickname">
18
15
  <TInput v-model="$Data.formData.nickname" placeholder="请输入昵称" />
19
16
  </TFormItem>
20
- <TFormItem label="手机号" prop="phone">
21
- <TInput v-model="$Data.formData.phone" placeholder="请输入手机号" />
22
- </TFormItem>
23
17
  <TFormItem v-if="$Prop.actionType === 'upd'" label="状态" prop="state">
24
18
  <TRadioGroup v-model="$Data.formData.state">
25
19
  <TRadio :label="1">正常</TRadio>
@@ -81,15 +75,16 @@ const $Data = $ref({
81
75
  visible: false,
82
76
  submitting: false,
83
77
  allRoleLists: [],
78
+ keys: {
79
+ label: 'name',
80
+ value: 'id'
81
+ },
84
82
  formData: {
85
- id: 0,
83
+ id: null,
86
84
  username: '',
87
- email: '',
88
85
  password: '',
89
- name: '',
90
86
  nickname: '',
91
- phone: '',
92
- roleId: 0,
87
+ roleId: null,
93
88
  state: 1
94
89
  }
95
90
  });
@@ -97,18 +92,12 @@ const $Data = $ref({
97
92
  const $Data2 = $shallowRef({
98
93
  formRules: {
99
94
  username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
100
- email: [
101
- { required: true, message: '请输入邮箱', trigger: 'blur' },
102
- { type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
103
- ],
104
95
  password: [
105
96
  { required: true, message: '请输入密码', trigger: 'blur' },
106
97
  { min: 6, message: '密码至少6位', trigger: 'blur' }
107
98
  ],
108
99
  roleId: [{ required: true, message: '请选择角色', trigger: 'change' }],
109
- name: [{ min: 2, max: 50, message: '姓名长度在 2 到 50 个字符', trigger: 'blur' }],
110
- nickname: [{ min: 2, max: 50, message: '昵称长度在 2 到 50 个字符', trigger: 'blur' }],
111
- phone: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }]
100
+ nickname: [{ min: 2, max: 50, message: '昵称长度在 2 到 50 个字符', trigger: 'blur' }]
112
101
  }
113
102
  });
114
103
 
@@ -116,9 +105,8 @@ const $Data2 = $shallowRef({
116
105
  const $Method = {
117
106
  async initData() {
118
107
  $Method.onShow();
119
- $Data.allRoleLists = await $Http('/addon/admin/role/all');
108
+ await $Method.apiRoleLists();
120
109
  if ($Prop.actionType === 'upd' && $Prop.rowData.id) {
121
- // 编辑模式:直接赋值
122
110
  $Data.formData = { ...$Prop.rowData };
123
111
  }
124
112
  },
@@ -136,17 +124,27 @@ const $Method = {
136
124
  }, 300);
137
125
  },
138
126
 
127
+ async apiRoleLists() {
128
+ try {
129
+ const result = await $Http('/addon/admin/role/all');
130
+ $Data.allRoleLists = result.data || [];
131
+ } catch (error) {
132
+ console.error('加载角色列表失败:', error);
133
+ MessagePlugin.error('加载角色列表失败');
134
+ }
135
+ },
136
+
139
137
  async onSubmit() {
140
138
  try {
141
139
  const valid = await $From.form.validate();
142
140
  if (!valid) return;
143
141
 
144
142
  $Data.submitting = true;
145
- const submitData = $Prop.actionType === 'add' ? fieldClear($Data.formData, { omitKeys: ['id', 'state'] }) : fieldClear($Data.formData, { omitKeys: ['password'] });
143
+ const formData = $Prop.actionType === 'add' ? fieldClear($Data.formData, { omitKeys: ['id', 'state'] }) : fieldClear($Data.formData, { omitKeys: ['password'] });
146
144
 
147
- await $Http($Prop.actionType === 'upd' ? '/addon/admin/admin/upd' : '/addon/admin/admin/ins', submitData);
145
+ const result = await $Http($Prop.actionType === 'upd' ? '/addon/admin/admin/upd' : '/addon/admin/admin/ins', formData);
148
146
 
149
- MessagePlugin.success($Prop.actionType === 'upd' ? '编辑成功' : '添加成功');
147
+ MessagePlugin.success(result.msg);
150
148
  $Emit('success');
151
149
  $Method.onClose();
152
150
  } catch (error) {
@@ -19,7 +19,7 @@
19
19
 
20
20
  <div class="main-content">
21
21
  <div class="main-table">
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">
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,17 +47,7 @@
44
47
  </div>
45
48
 
46
49
  <div class="main-detail">
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
- />
50
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.columns" />
58
51
  </div>
59
52
  </div>
60
53
 
@@ -76,28 +69,19 @@ import ILucideTrash2 from '~icons/lucide/trash-2';
76
69
  import EditDialog from './components/edit.vue';
77
70
  import DetailPanel from '@/components/DetailPanel.vue';
78
71
  import { $Http } from '@/plugins/http';
79
- import { withDefaultColumns, withTableProps } from '@/utils';
72
+ import { withDefaultColumns } from '@/utils';
80
73
 
81
74
  // 响应式数据
82
75
  const $Data = $ref({
83
76
  tableData: [],
84
77
  loading: false,
85
78
  columns: withDefaultColumns([
86
- {
87
- colKey: 'row-select',
88
- type: 'single',
89
- width: 50,
90
- fixed: 'left',
91
- checkProps: { allowUncheck: true },
92
- ellipsis: false
93
- },
94
- { colKey: 'username', title: '用户名', width: 150, fixed: 'left' },
95
- { colKey: 'id', title: '序号', width: 150, align: 'center' },
96
- { colKey: 'email', title: '邮箱', width: 200 },
97
- { colKey: 'nickname', title: '昵称', width: 150 },
98
- { colKey: 'roleCode', title: '角色', width: 120 },
99
- { colKey: 'state', title: '状态', width: 100, ellipsis: false },
100
- { colKey: 'operation', title: '操作', width: 80, align: 'center', fixed: 'right', ellipsis: false }
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: '操作' }
101
85
  ]),
102
86
  pagerConfig: {
103
87
  currentPage: 1,
@@ -110,7 +94,6 @@ const $Data = $ref({
110
94
  actionType: 'add',
111
95
  rowData: {},
112
96
  currentRow: null,
113
- selectedRowKeys: [],
114
97
  activeRowKeys: []
115
98
  });
116
99
 
@@ -131,14 +114,12 @@ const $Method = {
131
114
  $Data.tableData = res.data.lists || [];
132
115
  $Data.pagerConfig.total = res.data.total || 0;
133
116
 
134
- // 自动选中并高亮第一行
117
+ // 自动高亮第一行
135
118
  if ($Data.tableData.length > 0) {
136
119
  $Data.currentRow = $Data.tableData[0];
137
- $Data.selectedRowKeys = [$Data.tableData[0].id];
138
120
  $Data.activeRowKeys = [$Data.tableData[0].id];
139
121
  } else {
140
122
  $Data.currentRow = null;
141
- $Data.selectedRowKeys = [];
142
123
  $Data.activeRowKeys = [];
143
124
  }
144
125
  } catch (error) {
@@ -189,37 +170,16 @@ const $Method = {
189
170
  $Method.apiAdminList();
190
171
  },
191
172
 
192
- // 单选变化
193
- onSelectChange(value, { selectedRowData }) {
194
- $Data.selectedRowKeys = value;
195
- $Data.activeRowKeys = value;
196
- // 更新当前选中的行数据
197
- if (selectedRowData && selectedRowData.length > 0) {
198
- $Data.currentRow = selectedRowData[0];
199
- } else if ($Data.tableData.length > 0) {
200
- // 如果取消选中,默认显示第一行
201
- $Data.currentRow = $Data.tableData[0];
202
- $Data.selectedRowKeys = [$Data.tableData[0].id];
203
- $Data.activeRowKeys = [$Data.tableData[0].id];
204
- } else {
205
- $Data.currentRow = null;
206
- }
207
- },
208
-
209
173
  // 高亮行变化
210
- onActiveChange(value, { activeRowData }) {
174
+ onActiveChange(value, context) {
175
+ // 禁止取消高亮:如果新值为空,保持当前选中
176
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
177
+ return;
178
+ }
211
179
  $Data.activeRowKeys = value;
212
- $Data.selectedRowKeys = value;
213
180
  // 更新当前高亮的行数据
214
- if (activeRowData && activeRowData.length > 0) {
215
- $Data.currentRow = activeRowData[0];
216
- } else if ($Data.tableData.length > 0) {
217
- // 如果取消高亮,默认显示第一行
218
- $Data.currentRow = $Data.tableData[0];
219
- $Data.selectedRowKeys = [$Data.tableData[0].id];
220
- $Data.activeRowKeys = [$Data.tableData[0].id];
221
- } else {
222
- $Data.currentRow = null;
181
+ if (context.activeRowList && context.activeRowList.length > 0) {
182
+ $Data.currentRow = context.activeRowList[0].row;
223
183
  }
224
184
  },
225
185
 
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "管理员",
3
+ "order": 1
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "人员管理",
3
+ "order": 10
4
+ }
@@ -20,7 +20,7 @@
20
20
 
21
21
  <div class="main-content">
22
22
  <div class="main-table">
23
- <TTable v-bind="withTableProps()" :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">
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
24
  <template #method="{ row }">
25
25
  <TTag v-if="row.method === 'GET'" shape="round" theme="success" variant="light-outline">GET</TTag>
26
26
  <TTag v-else-if="row.method === 'POST'" shape="round" theme="primary" variant="light-outline">POST</TTag>
@@ -36,17 +36,7 @@
36
36
  </div>
37
37
 
38
38
  <div class="main-detail">
39
- <DetailPanel
40
- :data="$Data.currentRow"
41
- :fields="[
42
- { key: 'id', label: 'ID' },
43
- { key: 'name', label: '接口名称' },
44
- { key: 'path', label: '接口路径' },
45
- { key: 'method', label: '请求方法' },
46
- { key: 'addonName', label: '所属组件', default: '项目' },
47
- { key: 'description', label: '接口描述' }
48
- ]"
49
- >
39
+ <DetailPanel :data="$Data.currentRow" :fields="$Data.columns">
50
40
  <template #method="{ value }">
51
41
  <TTag v-if="value === 'GET'" shape="round" theme="success" variant="light-outline">GET</TTag>
52
42
  <TTag v-else-if="value === 'POST'" shape="round" theme="primary" variant="light-outline">POST</TTag>
@@ -65,7 +55,7 @@ import { Button as TButton, Table as TTable, Tag as TTag, Input as TInput, Messa
65
55
  import ILucideRotateCw from '~icons/lucide/rotate-cw';
66
56
  import ILucideSearch from '~icons/lucide/search';
67
57
  import { $Http } from '@/plugins/http';
68
- import { withDefaultColumns, withTableProps } from '@/utils';
58
+ import { withDefaultColumns } from '@/utils';
69
59
  import DetailPanel from '@/components/DetailPanel.vue';
70
60
 
71
61
  // 响应式数据
@@ -75,22 +65,13 @@ const $Data = $ref({
75
65
  loading: false,
76
66
  searchKeyword: '',
77
67
  columns: withDefaultColumns([
78
- {
79
- colKey: 'row-select',
80
- type: 'single',
81
- width: 50,
82
- fixed: 'left',
83
- checkProps: { allowUncheck: true },
84
- ellipsis: false
85
- },
86
- { colKey: 'name', title: '接口名称', width: 200, fixed: 'left' },
87
- { colKey: 'id', title: '序号', width: 80, align: 'center' },
88
- { colKey: 'path', title: '接口路径', width: 350 },
89
- { colKey: 'method', title: '请求方法', width: 100, align: 'center', ellipsis: false },
90
- { colKey: 'addonName', title: '所属组件', width: 120, ellipsis: false }
68
+ { colKey: 'name', title: '接口名称' },
69
+ { colKey: 'id', title: '序号' },
70
+ { colKey: 'path', title: '接口路径' },
71
+ { colKey: 'method', title: '请求方法' },
72
+ { colKey: 'addonName', title: '所属组件' }
91
73
  ]),
92
74
  currentRow: null,
93
- selectedRowKeys: [],
94
75
  activeRowKeys: []
95
76
  });
96
77
 
@@ -109,14 +90,12 @@ const $Method = {
109
90
  $Data.allData = list;
110
91
  $Data.tableData = list;
111
92
 
112
- // 自动选中并高亮第一行
93
+ // 自动高亮第一行
113
94
  if ($Data.tableData.length > 0) {
114
95
  $Data.currentRow = $Data.tableData[0];
115
- $Data.selectedRowKeys = [$Data.tableData[0].id];
116
96
  $Data.activeRowKeys = [$Data.tableData[0].id];
117
97
  } else {
118
98
  $Data.currentRow = null;
119
- $Data.selectedRowKeys = [];
120
99
  $Data.activeRowKeys = [];
121
100
  }
122
101
  } catch (error) {
@@ -142,33 +121,16 @@ const $Method = {
142
121
  $Data.tableData = $Data.allData.filter((item) => item.name?.toLowerCase().includes(keyword) || item.path?.toLowerCase().includes(keyword));
143
122
  },
144
123
 
145
- // 单选变化
146
- onSelectChange(value, { selectedRowData }) {
147
- $Data.selectedRowKeys = value;
148
- $Data.activeRowKeys = value;
149
- if (selectedRowData && selectedRowData.length > 0) {
150
- $Data.currentRow = selectedRowData[0];
151
- } else if ($Data.tableData.length > 0) {
152
- $Data.currentRow = $Data.tableData[0];
153
- $Data.selectedRowKeys = [$Data.tableData[0].id];
154
- $Data.activeRowKeys = [$Data.tableData[0].id];
155
- } else {
156
- $Data.currentRow = null;
157
- }
158
- },
159
-
160
124
  // 高亮行变化
161
- onActiveChange(value, { activeRowData }) {
125
+ onActiveChange(value, context) {
126
+ // 禁止取消高亮:如果新值为空,保持当前选中
127
+ if (value.length === 0 && $Data.activeRowKeys.length > 0) {
128
+ return;
129
+ }
162
130
  $Data.activeRowKeys = value;
163
- $Data.selectedRowKeys = value;
164
- if (activeRowData && activeRowData.length > 0) {
165
- $Data.currentRow = activeRowData[0];
166
- } else if ($Data.tableData.length > 0) {
167
- $Data.currentRow = $Data.tableData[0];
168
- $Data.selectedRowKeys = [$Data.tableData[0].id];
169
- $Data.activeRowKeys = [$Data.tableData[0].id];
170
- } else {
171
- $Data.currentRow = null;
131
+ // 更新当前高亮的行数据
132
+ if (context.activeRowList && context.activeRowList.length > 0) {
133
+ $Data.currentRow = context.activeRowList[0].row;
172
134
  }
173
135
  }
174
136
  };
@@ -0,0 +1,4 @@
1
+ {
2
+ "name": "接口列表",
3
+ "order": 3
4
+ }