@befly-addon/admin 1.2.8 → 1.2.12

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.
@@ -101,7 +101,6 @@ const $Data = $ref({
101
101
  searchTypeCode: "",
102
102
  searchKeyword: "",
103
103
  columns: withDefaultColumns([
104
- { colKey: "id", title: "ID" },
105
104
  { colKey: "typeName", title: "类型名称" },
106
105
  { colKey: "typeCode", title: "类型代码" },
107
106
  { colKey: "label", title: "标签" },
@@ -96,7 +96,6 @@ const $Data = $ref({
96
96
  currentRow: null,
97
97
  searchKeyword: "",
98
98
  columns: withDefaultColumns([
99
- { colKey: "id", title: "ID" },
100
99
  { colKey: "code", title: "类型代码" },
101
100
  { colKey: "name", title: "类型名称" },
102
101
  { colKey: "description", title: "描述" },
@@ -106,7 +106,6 @@ const $Data = $ref({
106
106
  loading: false,
107
107
  columns: withDefaultColumns([
108
108
  { colKey: "name", title: "配置名称", fixed: "left", width: 150 },
109
- { colKey: "id", title: "序号", width: 80 },
110
109
  { colKey: "code", title: "配置代码", ellipsis: true },
111
110
  { colKey: "value", title: "配置值", ellipsis: true, width: 200 },
112
111
  { colKey: "valueType", title: "值类型", width: 100 },
@@ -107,7 +107,6 @@ const $Data = $ref({
107
107
  loading: false,
108
108
  columns: withDefaultColumns([
109
109
  { colKey: "username", title: "发送人", fixed: "left" },
110
- { colKey: "id", title: "序号" },
111
110
  { colKey: "toEmail", title: "收件人" },
112
111
  { colKey: "subject", title: "主题" },
113
112
  { colKey: "sendTime", title: "发送时间" },
@@ -69,7 +69,6 @@ const $Data = $ref({
69
69
  loading: false,
70
70
  columns: withDefaultColumns([
71
71
  { colKey: "username", title: "用户名", fixed: "left" },
72
- { colKey: "id", title: "序号" },
73
72
  { colKey: "ip", title: "登录IP" },
74
73
  { colKey: "browserName", title: "浏览器" },
75
74
  { colKey: "osName", title: "操作系统" },
@@ -89,7 +89,6 @@ const $Data = $ref({
89
89
  loading: false,
90
90
  columns: withDefaultColumns([
91
91
  { colKey: "username", title: "操作人", fixed: "left", width: 100 },
92
- { colKey: "id", title: "序号", width: 80 },
93
92
  { colKey: "module", title: "模块", width: 100 },
94
93
  { colKey: "action", title: "操作", width: 80 },
95
94
  { colKey: "path", title: "请求路径", ellipsis: true },
@@ -94,7 +94,6 @@ const $Data = $ref({
94
94
  loading: false,
95
95
  columns: withDefaultColumns([
96
96
  { colKey: "username", title: "用户名", fixed: "left" },
97
- { colKey: "id", title: "序号" },
98
97
  { colKey: "nickname", title: "昵称" },
99
98
  { colKey: "roleCode", title: "角色" },
100
99
  { colKey: "state", title: "状态" },
@@ -73,7 +73,6 @@ const $Data = $ref({
73
73
  searchKeyword: "",
74
74
  columns: withDefaultColumns([
75
75
  { colKey: "name", title: "接口名称" },
76
- { colKey: "id", title: "序号" },
77
76
  { colKey: "path", title: "接口路径" },
78
77
  { colKey: "method", title: "请求方法" },
79
78
  { colKey: "addonName", title: "所属组件" }
@@ -60,7 +60,6 @@ const $Data = $ref({
60
60
  loading: false,
61
61
  columns: withDefaultColumns([
62
62
  { colKey: "name", title: "菜单名称" },
63
- { colKey: "id", title: "序号" },
64
63
  { colKey: "path", title: "路由路径" },
65
64
  { colKey: "parentPath", title: "父级路径" },
66
65
  { colKey: "sort", title: "排序" },
@@ -19,13 +19,8 @@
19
19
  <TCheckbox v-for="api in group.apis" :key="api.value" :value="api.value">
20
20
  <div class="api-checkbox-label">
21
21
  <div class="api-label-main">
22
- <div class="api-name" :title="api.name">{{ api.name }}</div>
23
- <div class="api-path" :title="api.path">{{ api.path }}</div>
22
+ <div class="api-name" :title="api.path ? `${api.name}\n${api.path}` : api.name">{{ api.name }}</div>
24
23
  </div>
25
-
26
- <span class="api-copy" title="复制路径" @click.stop="$Method.copyApiPath(api.path)">
27
- <ILucideCopy :size="14" />
28
- </span>
29
24
  </div>
30
25
  </TCheckbox>
31
26
  </TCheckboxGroup>
@@ -47,7 +42,6 @@
47
42
 
48
43
  <script setup>
49
44
  import { Dialog as TDialog, Input as TInput, CheckboxGroup as TCheckboxGroup, Checkbox as TCheckbox, Button as TButton, MessagePlugin } from "tdesign-vue-next";
50
- import ILucideCopy from "~icons/lucide/copy";
51
45
  import ILucideSearch from "~icons/lucide/search";
52
46
  import { $Http } from "@/plugins/http";
53
47
 
@@ -164,47 +158,6 @@ const $Method = {
164
158
  .filter((group) => group.apis.length > 0);
165
159
  },
166
160
 
167
- async copyApiPath(path) {
168
- if (typeof path !== "string" || path.trim().length === 0) {
169
- MessagePlugin.warning("路径为空");
170
- return;
171
- }
172
-
173
- const value = path.trim();
174
-
175
- try {
176
- if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
177
- await navigator.clipboard.writeText(value);
178
- MessagePlugin.success("已复制");
179
- return;
180
- }
181
- } catch (error) {
182
- // ignore and fallback
183
- }
184
-
185
- try {
186
- const textarea = document.createElement("textarea");
187
- textarea.value = value;
188
- textarea.setAttribute("readonly", "readonly");
189
- textarea.style.position = "fixed";
190
- textarea.style.left = "-9999px";
191
- textarea.style.top = "-9999px";
192
- document.body.appendChild(textarea);
193
- textarea.select();
194
- const ok = document.execCommand("copy");
195
- document.body.removeChild(textarea);
196
-
197
- if (ok) {
198
- MessagePlugin.success("已复制");
199
- return;
200
- }
201
-
202
- MessagePlugin.error("复制失败");
203
- } catch (error) {
204
- MessagePlugin.error("复制失败");
205
- }
206
- },
207
-
208
161
  // 提交表单
209
162
  async onSubmit() {
210
163
  try {
@@ -329,30 +282,6 @@ $Method.initData();
329
282
  white-space: nowrap;
330
283
  }
331
284
 
332
- .api-path {
333
- max-width: 100%;
334
- overflow: hidden;
335
- text-overflow: ellipsis;
336
- white-space: nowrap;
337
- opacity: 0.7;
338
- font-size: 12px;
339
- }
340
-
341
- .api-copy {
342
- flex: 0 0 auto;
343
- display: inline-flex;
344
- align-items: center;
345
- justify-content: center;
346
- width: 24px;
347
- height: 24px;
348
- border-radius: 6px;
349
- cursor: pointer;
350
- }
351
-
352
- .api-copy:hover {
353
- background: rgba(0, 0, 0, 0.06);
354
- }
355
-
356
285
  .dialog-footer {
357
286
  width: 100%;
358
287
  display: flex;
@@ -1,17 +1,48 @@
1
1
  <template>
2
- <TDialog v-model:visible="$Data.visible" title="菜单权限" width="600px" :append-to-body="true" :show-footer="true" top="10vh" @close="$Method.onClose">
2
+ <TDialog v-model:visible="$Data.visible" title="菜单权限" width="900px" :append-to-body="true" :show-footer="true" top="5vh" @close="$Method.onClose">
3
3
  <div class="comp-role-menu">
4
- <TTree v-model:value="$Data.menuTreeCheckedKeys" :data="$Data.menuTreeData" value-mode="all" :keys="{ value: 'path', label: 'name', children: 'children' }" checkable expand-all />
4
+ <!-- 搜索框 -->
5
+ <div class="search-box">
6
+ <TInput v-model="$Data.searchText" placeholder="搜索菜单名称或路径" clearable @change="$Method.onSearch">
7
+ <template #prefix-icon>
8
+ <ILucideSearch />
9
+ </template>
10
+ </TInput>
11
+ </div>
12
+
13
+ <div class="menu-container">
14
+ <div class="menu-group" v-for="group in $Data.filteredMenuGroups" :key="group.name">
15
+ <div class="group-header">{{ group.title }}</div>
16
+ <div class="menu-checkbox-list">
17
+ <TCheckboxGroup v-model="$Data.checkedMenuPaths">
18
+ <TCheckbox v-for="menu in group.menus" :key="menu.value" :value="menu.value">
19
+ <div class="menu-checkbox-label">
20
+ <div class="menu-label-main">
21
+ <div class="menu-name" :title="menu.path ? `${menu.name}\n${menu.path}` : menu.name">
22
+ {{ menu.name }}
23
+ </div>
24
+ </div>
25
+ </div>
26
+ </TCheckbox>
27
+ </TCheckboxGroup>
28
+ </div>
29
+ </div>
30
+ </div>
5
31
  </div>
6
32
  <template #footer>
7
- <TButton @click="$Method.onClose">取消</TButton>
8
- <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">保存</TButton>
33
+ <div class="dialog-footer">
34
+ <t-space>
35
+ <TButton theme="default" @click="$Method.onClose">取消</TButton>
36
+ <TButton theme="primary" :loading="$Data.submitting" @click="$Method.onSubmit">保存</TButton>
37
+ </t-space>
38
+ </div>
9
39
  </template>
10
40
  </TDialog>
11
41
  </template>
12
42
 
13
43
  <script setup>
14
- import { Dialog as TDialog, Tree as TTree, Button as TButton, MessagePlugin } from "tdesign-vue-next";
44
+ import { Dialog as TDialog, CheckboxGroup as TCheckboxGroup, Checkbox as TCheckbox, Button as TButton, Input as TInput, MessagePlugin } from "tdesign-vue-next";
45
+ import ILucideSearch from "~icons/lucide/search";
15
46
  import { $Http } from "@/plugins/http";
16
47
  import { arrayToTree } from "befly-shared/utils/arrayToTree";
17
48
 
@@ -31,14 +62,17 @@ const $Emit = defineEmits(["update:modelValue", "success"]);
31
62
  const $Data = $ref({
32
63
  visible: false,
33
64
  submitting: false,
34
- menuTreeData: [],
35
- menuTreeCheckedKeys: []
65
+ searchText: "",
66
+ menuGroups: [],
67
+ filteredMenuGroups: [],
68
+ checkedMenuPaths: []
36
69
  });
37
70
 
38
71
  // 方法集合
39
72
  const $Method = {
40
73
  async initData() {
41
74
  await Promise.all([$Method.apiMenuAll(), $Method.apiRoleMenuDetail()]);
75
+ $Data.filteredMenuGroups = $Data.menuGroups;
42
76
  $Method.onShow();
43
77
  },
44
78
 
@@ -62,7 +96,45 @@ const $Method = {
62
96
  const lists = Array.isArray(res?.data?.lists) ? res.data.lists : [];
63
97
 
64
98
  const treeResult = arrayToTree(lists, "path", "parentPath", "children", "sort");
65
- $Data.menuTreeData = treeResult.tree;
99
+ const roots = Array.isArray(treeResult?.tree) ? treeResult.tree : [];
100
+
101
+ const groups = [];
102
+ for (const root of roots) {
103
+ const rootPath = typeof root?.path === "string" ? root.path : "";
104
+ const rootName = typeof root?.name === "string" ? root.name : "";
105
+
106
+ const menus = [];
107
+
108
+ const walk = (node, depth) => {
109
+ const name = typeof node?.name === "string" ? node.name : "";
110
+ const path = typeof node?.path === "string" ? node.path : "";
111
+ if (path.length > 0) {
112
+ menus.push({
113
+ value: path,
114
+ name: name,
115
+ path: path,
116
+ depth: depth,
117
+ label: `${name} ${path}`.trim()
118
+ });
119
+ }
120
+
121
+ const children = Array.isArray(node?.children) ? node.children : [];
122
+ for (const child of children) {
123
+ walk(child, depth + 1);
124
+ }
125
+ };
126
+
127
+ walk(root, 0);
128
+
129
+ const groupTitle = rootName.length > 0 ? rootName : rootPath;
130
+ groups.push({
131
+ name: rootPath.length > 0 ? rootPath : groupTitle,
132
+ title: groupTitle.length > 0 ? groupTitle : "未命名菜单",
133
+ menus: menus
134
+ });
135
+ }
136
+
137
+ $Data.menuGroups = groups;
66
138
  } catch (error) {
67
139
  MessagePlugin.error("加载菜单失败");
68
140
  }
@@ -78,12 +150,38 @@ const $Method = {
78
150
  });
79
151
 
80
152
  // menus 返回的 data 直接就是菜单 path 数组
81
- $Data.menuTreeCheckedKeys = Array.isArray(res.data) ? res.data : [];
153
+ $Data.checkedMenuPaths = Array.isArray(res.data) ? res.data : [];
82
154
  } catch (error) {
83
155
  MessagePlugin.error("加载数据失败");
84
156
  }
85
157
  },
86
158
 
159
+ // 搜索过滤(按“名称 + 路径”匹配;展示结构与接口弹框一致)
160
+ onSearch() {
161
+ const kw = typeof $Data.searchText === "string" ? $Data.searchText.trim().toLowerCase() : "";
162
+ if (kw.length === 0) {
163
+ $Data.filteredMenuGroups = $Data.menuGroups;
164
+ return;
165
+ }
166
+
167
+ $Data.filteredMenuGroups = $Data.menuGroups
168
+ .map((group) => {
169
+ const menus = Array.isArray(group?.menus)
170
+ ? group.menus.filter((menu) => {
171
+ const label = typeof menu?.label === "string" ? menu.label : "";
172
+ return label.toLowerCase().includes(kw);
173
+ })
174
+ : [];
175
+
176
+ return {
177
+ name: group.name,
178
+ title: group.title,
179
+ menus: menus
180
+ };
181
+ })
182
+ .filter((group) => Array.isArray(group.menus) && group.menus.length > 0);
183
+ },
184
+
87
185
  // 提交表单
88
186
  async onSubmit() {
89
187
  try {
@@ -91,7 +189,7 @@ const $Method = {
91
189
 
92
190
  const res = await $Http("/addon/admin/role/menuSave", {
93
191
  roleCode: $Prop.rowData.code,
94
- menuPaths: $Data.menuTreeCheckedKeys
192
+ menuPaths: $Data.checkedMenuPaths
95
193
  });
96
194
 
97
195
  if (res.code === 0) {
@@ -114,5 +212,95 @@ $Method.initData();
114
212
 
115
213
  <style scoped lang="scss">
116
214
  .comp-role-menu {
215
+ height: 60vh;
216
+ display: flex;
217
+ flex-direction: column;
218
+ gap: 12px;
219
+
220
+ .menu-container {
221
+ flex: 1;
222
+ overflow-y: auto;
223
+
224
+ .menu-group {
225
+ margin-bottom: 16px;
226
+ border: 1px solid var(--border-color);
227
+ border-radius: var(--border-radius-small);
228
+ overflow: hidden;
229
+
230
+ &:last-child {
231
+ margin-bottom: 0;
232
+ }
233
+
234
+ .group-header {
235
+ padding: 12px 16px;
236
+ background-color: var(--bg-color-hover);
237
+ font-weight: 500;
238
+ font-size: var(--font-size-sm);
239
+ color: var(--text-primary);
240
+ display: flex;
241
+ align-items: center;
242
+ gap: 8px;
243
+
244
+ &::before {
245
+ content: "";
246
+ width: 8px;
247
+ height: 8px;
248
+ border-radius: 50%;
249
+ background-color: var(--primary-color);
250
+ opacity: 0.3;
251
+ flex-shrink: 0;
252
+ }
253
+ }
254
+
255
+ .menu-checkbox-list {
256
+ padding: 10px;
257
+
258
+ :deep(.t-checkbox-group) {
259
+ display: flex;
260
+ flex-wrap: wrap;
261
+ gap: 12px;
262
+ width: 100%;
263
+ }
264
+
265
+ :deep(.t-checkbox) {
266
+ flex: 0 0 calc(33.333% - 8px);
267
+ margin: 0;
268
+ min-width: 0;
269
+ }
270
+
271
+ :deep(.t-checkbox__label) {
272
+ min-width: 0;
273
+ }
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ .menu-checkbox-label {
280
+ width: 100%;
281
+ display: flex;
282
+ align-items: center;
283
+ justify-content: space-between;
284
+ gap: 8px;
285
+ }
286
+
287
+ .menu-label-main {
288
+ min-width: 0;
289
+ display: flex;
290
+ flex-direction: column;
291
+ gap: 2px;
292
+ }
293
+
294
+ .menu-name {
295
+ max-width: 100%;
296
+ overflow: hidden;
297
+ text-overflow: ellipsis;
298
+ white-space: nowrap;
299
+ }
300
+
301
+ .dialog-footer {
302
+ width: 100%;
303
+ display: flex;
304
+ justify-content: center;
117
305
  }
118
306
  </style>
@@ -33,6 +33,12 @@
33
33
  <TTag v-else-if="row.state === 2" shape="round" theme="warning" variant="light-outline">禁用</TTag>
34
34
  <TTag v-else shape="round" theme="danger" variant="light-outline">已删除</TTag>
35
35
  </template>
36
+ <template #menuCount="{ row }">
37
+ {{ $Method.getPathCount(row.menus) }}
38
+ </template>
39
+ <template #apiCount="{ row }">
40
+ {{ $Method.getPathCount(row.apis) }}
41
+ </template>
36
42
  <template #operation="{ row }">
37
43
  <TDropdown trigger="click" placement="bottom-right" @click="(data) => $Method.onAction(data.value, row)">
38
44
  <TButton theme="primary" size="small">
@@ -112,12 +118,13 @@ const $Data = $ref({
112
118
  activeRowKeys: [],
113
119
  currentRow: null,
114
120
  columns: withDefaultColumns([
115
- { colKey: "id", title: "ID" },
116
121
  { colKey: "name", title: "角色名称" },
117
- { colKey: "code", title: "角色代码" },
118
- { colKey: "description", title: "描述" },
122
+ { colKey: "code", title: "角色代码",width:150 },
123
+ { colKey: "menuCount", title: "菜单数量", align: "center",width:100 },
124
+ { colKey: "apiCount", title: "接口数量", align: "center",width:100 },
119
125
  { colKey: "sort", title: "排序" },
120
126
  { colKey: "state", title: "状态" },
127
+ { colKey: "description", title: "描述" },
121
128
  { colKey: "operation", title: "操作" }
122
129
  ]),
123
130
  pagerConfig: {
@@ -136,6 +143,10 @@ const $Data = $ref({
136
143
 
137
144
  // 方法
138
145
  const $Method = {
146
+ getPathCount(value) {
147
+ if (!Array.isArray(value)) return 0;
148
+ return value.filter((p) => typeof p === "string" && p.trim().length > 0).length;
149
+ },
139
150
  async initData() {
140
151
  await $Method.apiRoleList();
141
152
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@befly-addon/admin",
3
- "version": "1.2.8",
4
- "gitHead": "f97afb740dabb7d42dd1f9d6bae5ec4744359d1c",
3
+ "version": "1.2.12",
4
+ "gitHead": "9c9fd0cd8165bb5b4025e538a3c5fb228acb39cd",
5
5
  "private": false,
6
6
  "description": "Befly - 管理后台功能组件",
7
7
  "keywords": [
@@ -47,9 +47,9 @@
47
47
  "preview": "vite preview"
48
48
  },
49
49
  "dependencies": {
50
- "befly": "^3.10.8",
51
- "befly-shared": "^1.3.5",
52
- "befly-vite": "^1.2.6",
50
+ "befly": "^3.10.12",
51
+ "befly-shared": "^1.3.8",
52
+ "befly-vite": "^1.2.9",
53
53
  "nodemailer": "^7.0.12",
54
54
  "ua-parser-js": "^2.0.7"
55
55
  },