befly-admin 3.13.7 → 3.13.8
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.
- package/package.json +3 -3
- package/src/layouts/2.vue +4 -6
- package/src/plugins/router.js +17 -3
- package/src/views/test.vue +306 -0
- package/src/components/detailPanel.vue +0 -192
- package/src/components/pageDialog.vue +0 -199
- package/src/components/pageTableDetail.vue +0 -510
- package/src/layouts/1.vue +0 -8
- package/src/layouts/default.vue +0 -481
- package/src/layouts/none.vue +0 -5
- package/src/views/index2.vue +0 -457
package/src/layouts/default.vue
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="layout-wrapper">
|
|
3
|
-
<!-- 左侧边栏:Logo + 菜单 + 底部操作 -->
|
|
4
|
-
<div class="layout-sidebar">
|
|
5
|
-
<!-- Logo 区域 -->
|
|
6
|
-
<div class="sidebar-logo">
|
|
7
|
-
<div class="logo-icon">
|
|
8
|
-
<MenuIcon style="width: 24px; height: 24px; color: var(--primary-color)" />
|
|
9
|
-
</div>
|
|
10
|
-
<h2>{{ $Config.appTitle }}</h2>
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
<!-- 菜单区域 -->
|
|
14
|
-
<div class="sidebar-menu">
|
|
15
|
-
<t-menu v-model:value="$Data.currentMenuKey" v-model:expanded="$Data.expandedKeys" width="220px" @change="onMenuClick">
|
|
16
|
-
<template v-for="menu in $Data.userMenus" :key="menu.id">
|
|
17
|
-
<!-- 无子菜单 -->
|
|
18
|
-
<t-menu-item v-if="!menu.children || menu.children.length === 0" :value="menu.path">
|
|
19
|
-
<template #icon>
|
|
20
|
-
<LinkIcon v-if="menu.path === '/dashboard' || menu.path === '/core/' || menu.path === '/core'" style="margin-right: 8px" />
|
|
21
|
-
<CodeIcon v-else style="margin-right: 8px" />
|
|
22
|
-
</template>
|
|
23
|
-
{{ menu.name }}
|
|
24
|
-
</t-menu-item>
|
|
25
|
-
<!-- 有子菜单 -->
|
|
26
|
-
<t-submenu v-else :value="String(menu.id)" :title="menu.name">
|
|
27
|
-
<template #icon>
|
|
28
|
-
<MenuIcon style="margin-right: 8px" />
|
|
29
|
-
</template>
|
|
30
|
-
<t-menu-item v-for="child in menu.children" :key="child.id" :value="child.path">
|
|
31
|
-
<template #icon>
|
|
32
|
-
<CodeIcon style="margin-right: 8px" />
|
|
33
|
-
</template>
|
|
34
|
-
{{ child.name }}
|
|
35
|
-
</t-menu-item>
|
|
36
|
-
</t-submenu>
|
|
37
|
-
</template>
|
|
38
|
-
</t-menu>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<!-- 底部操作区域 -->
|
|
42
|
-
<div class="sidebar-footer">
|
|
43
|
-
<div class="footer-item" @click="handleSettings">
|
|
44
|
-
<SettingIcon style="width: 18px; height: 18px" />
|
|
45
|
-
<span>系统设置</span>
|
|
46
|
-
</div>
|
|
47
|
-
<div class="footer-user">
|
|
48
|
-
<t-upload :action="$Config.uploadUrl" :headers="uploadHeaders" :show-upload-list="false" accept="image/*" @success="onAvatarUploadSuccess">
|
|
49
|
-
<div class="user-avatar" :class="{ 'has-avatar': $Data.userInfo.avatar }">
|
|
50
|
-
<img v-if="$Data.userInfo.avatar" :src="$Data.userInfo.avatar" alt="avatar" />
|
|
51
|
-
<UserIcon v-else style="width: 16px; height: 16px; color: #fff" />
|
|
52
|
-
<div class="avatar-overlay">
|
|
53
|
-
<CloudIcon style="width: 14px; height: 14px; color: #fff" />
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
</t-upload>
|
|
57
|
-
<div class="user-info">
|
|
58
|
-
<span class="user-name">{{ $Data.userInfo.nickname || "管理员" }}</span>
|
|
59
|
-
<span class="user-role">{{ $Data.userInfo.role || "超级管理员" }}</span>
|
|
60
|
-
</div>
|
|
61
|
-
<t-button theme="default" variant="text" size="small" @click="handleLogout">
|
|
62
|
-
<template #icon>
|
|
63
|
-
<CloseCircleIcon style="width: 16px; height: 16px" />
|
|
64
|
-
</template>
|
|
65
|
-
</t-button>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<!-- 右侧内容区域 -->
|
|
71
|
-
<div class="layout-main">
|
|
72
|
-
<RouterView />
|
|
73
|
-
</div>
|
|
74
|
-
</div>
|
|
75
|
-
</template>
|
|
76
|
-
|
|
77
|
-
<script setup>
|
|
78
|
-
import { arrayToTree } from "befly-admin-ui/utils/arrayToTree";
|
|
79
|
-
import { isString } from "../utils/is.js";
|
|
80
|
-
|
|
81
|
-
import { reactive } from "vue";
|
|
82
|
-
|
|
83
|
-
const router = useRouter();
|
|
84
|
-
const route = useRoute();
|
|
85
|
-
const uploadHeaders = { Authorization: localStorage.getItem("yicode-token") || "" };
|
|
86
|
-
|
|
87
|
-
const loginPath = "/core/login";
|
|
88
|
-
|
|
89
|
-
const normalizePath = (path) => {
|
|
90
|
-
if (!isString(path)) {
|
|
91
|
-
return path;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const normalized = path.replace(/\/+$/, "");
|
|
95
|
-
return normalized.length === 0 ? "/" : normalized;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// parentPath 的归一化规则与 path 不同:
|
|
99
|
-
// - parentPath 为空/"/" 视为根节点(空字符串)
|
|
100
|
-
// - 避免把所有一级菜单挂到 "/"(首页)下面
|
|
101
|
-
const normalizeParentPath = (parentPath) => {
|
|
102
|
-
if (!isString(parentPath)) {
|
|
103
|
-
return "";
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const normalized = parentPath.replace(/\/+$/, "");
|
|
107
|
-
if (normalized.length === 0) {
|
|
108
|
-
return "";
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (normalized === "/") {
|
|
112
|
-
return "";
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return normalized;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// 响应式数据
|
|
119
|
-
const $Data = reactive({
|
|
120
|
-
userMenus: [],
|
|
121
|
-
userMenusFlat: [], // 一维菜单数据
|
|
122
|
-
expandedKeys: [],
|
|
123
|
-
currentMenuKey: "",
|
|
124
|
-
userInfo: {
|
|
125
|
-
nickname: "管理员",
|
|
126
|
-
role: "超级管理员",
|
|
127
|
-
avatar: "" // 用户头像
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
async function fetchUserMenus() {
|
|
132
|
-
try {
|
|
133
|
-
const { data } = await $Http.json("/core/menu/all");
|
|
134
|
-
const lists = Array.isArray(data?.lists) ? data.lists : [];
|
|
135
|
-
|
|
136
|
-
const normalizedLists = lists.map((menu) => {
|
|
137
|
-
const menuPath = normalizePath(menu?.path);
|
|
138
|
-
const menuParentPath = normalizeParentPath(menu?.parentPath);
|
|
139
|
-
return Object.assign({}, menu, { path: menuPath, parentPath: menuParentPath });
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const treeResult = arrayToTree(normalizedLists, "path", "parentPath", "children", "sort");
|
|
143
|
-
|
|
144
|
-
$Data.userMenusFlat = treeResult.flat;
|
|
145
|
-
$Data.userMenus = treeResult.tree;
|
|
146
|
-
setActiveMenu();
|
|
147
|
-
} catch (error) {
|
|
148
|
-
MessagePlugin.error(error.msg || error.message || "获取用户菜单失败");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function setActiveMenu() {
|
|
153
|
-
const currentPath = route.path;
|
|
154
|
-
const normalizedCurrentPath = normalizePath(currentPath);
|
|
155
|
-
|
|
156
|
-
const currentMenu = $Data.userMenusFlat.find((menu) => {
|
|
157
|
-
const menuPath = menu.path;
|
|
158
|
-
const normalizedMenuPath = normalizePath(menuPath);
|
|
159
|
-
return normalizedMenuPath === normalizedCurrentPath;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (!currentMenu) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const expandedKeys = [];
|
|
167
|
-
let menu = currentMenu;
|
|
168
|
-
|
|
169
|
-
while (isString(menu.parentPath) && menu.parentPath.length > 0) {
|
|
170
|
-
const parent = $Data.userMenusFlat.find((m) => {
|
|
171
|
-
const parentMenuPath = normalizePath(m?.path);
|
|
172
|
-
const currentParentPath = normalizeParentPath(menu?.parentPath);
|
|
173
|
-
return parentMenuPath === currentParentPath;
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
if (parent) {
|
|
177
|
-
expandedKeys.unshift(String(parent.id));
|
|
178
|
-
menu = parent;
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
$Data.expandedKeys = expandedKeys;
|
|
186
|
-
$Data.currentMenuKey = currentPath;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function onMenuClick(path) {
|
|
190
|
-
if (isString(path) && path.startsWith("/")) {
|
|
191
|
-
router.push(path);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function handleLogout() {
|
|
196
|
-
let dialog = null;
|
|
197
|
-
let destroyed = false;
|
|
198
|
-
|
|
199
|
-
const destroy = () => {
|
|
200
|
-
if (destroyed) return;
|
|
201
|
-
destroyed = true;
|
|
202
|
-
if (dialog && typeof dialog.destroy === "function") {
|
|
203
|
-
dialog.destroy();
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
dialog = DialogPlugin.confirm({
|
|
208
|
-
header: "确认退出登录",
|
|
209
|
-
body: "确定要退出登录吗?",
|
|
210
|
-
status: "warning",
|
|
211
|
-
onConfirm: async () => {
|
|
212
|
-
if (dialog && typeof dialog.setConfirmLoading === "function") {
|
|
213
|
-
dialog.setConfirmLoading(true);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
localStorage.removeItem("yicode-token");
|
|
218
|
-
await router.push(loginPath);
|
|
219
|
-
MessagePlugin.success("退出成功");
|
|
220
|
-
destroy();
|
|
221
|
-
} catch (error) {
|
|
222
|
-
MessagePlugin.error("退出失败");
|
|
223
|
-
destroy();
|
|
224
|
-
} finally {
|
|
225
|
-
if (dialog && typeof dialog.setConfirmLoading === "function") {
|
|
226
|
-
dialog.setConfirmLoading(false);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
onClose: () => {
|
|
231
|
-
destroy();
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function handleSettings() {
|
|
237
|
-
router.push("/core/settings");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function onAvatarUploadSuccess(res) {
|
|
241
|
-
if (res.response?.code === 0 && res.response?.data?.url) {
|
|
242
|
-
$Data.userInfo.avatar = res.response.data.url;
|
|
243
|
-
MessagePlugin.success("头像上传成功");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
fetchUserMenus();
|
|
248
|
-
|
|
249
|
-
watch(
|
|
250
|
-
() => route.path,
|
|
251
|
-
() => {
|
|
252
|
-
setActiveMenu();
|
|
253
|
-
}
|
|
254
|
-
);
|
|
255
|
-
</script>
|
|
256
|
-
|
|
257
|
-
<style scoped lang="scss">
|
|
258
|
-
.layout-wrapper {
|
|
259
|
-
display: flex;
|
|
260
|
-
height: 100vh;
|
|
261
|
-
width: 100vw;
|
|
262
|
-
background: var(--bg-color-page);
|
|
263
|
-
padding: var(--layout-gap);
|
|
264
|
-
gap: var(--layout-gap);
|
|
265
|
-
overflow: hidden;
|
|
266
|
-
|
|
267
|
-
// 左侧边栏
|
|
268
|
-
.layout-sidebar {
|
|
269
|
-
width: var(--sidebar-width);
|
|
270
|
-
flex-shrink: 0;
|
|
271
|
-
display: flex;
|
|
272
|
-
flex-direction: column;
|
|
273
|
-
background: var(--bg-color-container);
|
|
274
|
-
border-radius: var(--border-radius-large);
|
|
275
|
-
box-shadow: var(--shadow-1);
|
|
276
|
-
overflow: hidden;
|
|
277
|
-
|
|
278
|
-
// Logo 区域
|
|
279
|
-
.sidebar-logo {
|
|
280
|
-
display: flex;
|
|
281
|
-
align-items: center;
|
|
282
|
-
gap: var(--spacing-sm);
|
|
283
|
-
padding: var(--spacing-md) var(--spacing-md);
|
|
284
|
-
border-bottom: 1px solid var(--border-color-light);
|
|
285
|
-
|
|
286
|
-
.logo-icon {
|
|
287
|
-
width: 36px;
|
|
288
|
-
height: 36px;
|
|
289
|
-
min-width: 36px;
|
|
290
|
-
display: flex;
|
|
291
|
-
align-items: center;
|
|
292
|
-
justify-content: center;
|
|
293
|
-
background: var(--primary-color-light);
|
|
294
|
-
border-radius: var(--border-radius);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
h2 {
|
|
298
|
-
margin: 0;
|
|
299
|
-
font-size: var(--font-size-md);
|
|
300
|
-
font-weight: var(--font-weight-semibold);
|
|
301
|
-
color: var(--text-primary);
|
|
302
|
-
white-space: nowrap;
|
|
303
|
-
overflow: hidden;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// 菜单区域
|
|
308
|
-
.sidebar-menu {
|
|
309
|
-
flex: 1;
|
|
310
|
-
overflow-y: auto;
|
|
311
|
-
padding: var(--spacing-xs) 0;
|
|
312
|
-
|
|
313
|
-
:deep(.t-menu) {
|
|
314
|
-
border-right: none;
|
|
315
|
-
background: transparent;
|
|
316
|
-
|
|
317
|
-
// 子菜单项(非父级的菜单项)
|
|
318
|
-
.t-menu__item {
|
|
319
|
-
margin: 2px var(--spacing-sm);
|
|
320
|
-
border-radius: var(--border-radius);
|
|
321
|
-
transition: all var(--transition-fast);
|
|
322
|
-
position: relative;
|
|
323
|
-
|
|
324
|
-
&:hover {
|
|
325
|
-
background-color: var(--bg-color-hover);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
&.t-is-active {
|
|
329
|
-
background-color: var(--primary-color-light);
|
|
330
|
-
color: var(--primary-color);
|
|
331
|
-
font-weight: var(--font-weight-medium);
|
|
332
|
-
|
|
333
|
-
&::before {
|
|
334
|
-
content: "";
|
|
335
|
-
position: absolute;
|
|
336
|
-
left: 0;
|
|
337
|
-
top: 50%;
|
|
338
|
-
transform: translateY(-50%);
|
|
339
|
-
width: var(--menu-active-indicator);
|
|
340
|
-
height: 60%;
|
|
341
|
-
background-color: var(--primary-color);
|
|
342
|
-
border-radius: 0 2px 2px 0;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// 父级菜单样式(有子菜单的)
|
|
348
|
-
.t-submenu {
|
|
349
|
-
// 父级菜单的 header(不显示指示条)
|
|
350
|
-
> .t-menu__item,
|
|
351
|
-
> .t-submenu__header {
|
|
352
|
-
margin: 2px var(--spacing-sm);
|
|
353
|
-
border-radius: var(--border-radius);
|
|
354
|
-
transition: all var(--transition-fast);
|
|
355
|
-
position: relative;
|
|
356
|
-
|
|
357
|
-
&:hover {
|
|
358
|
-
background-color: var(--bg-color-hover);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// 父级菜单不显示指示条和背景
|
|
362
|
-
&::before {
|
|
363
|
-
display: none !important;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
&.t-is-active {
|
|
367
|
-
background-color: transparent !important;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// 底部操作区域
|
|
375
|
-
.sidebar-footer {
|
|
376
|
-
border-top: 1px solid var(--border-color-light);
|
|
377
|
-
padding: var(--spacing-sm);
|
|
378
|
-
|
|
379
|
-
.footer-item {
|
|
380
|
-
display: flex;
|
|
381
|
-
align-items: center;
|
|
382
|
-
gap: var(--spacing-sm);
|
|
383
|
-
padding: var(--spacing-sm) var(--spacing-md);
|
|
384
|
-
border-radius: var(--border-radius);
|
|
385
|
-
color: var(--text-secondary);
|
|
386
|
-
cursor: pointer;
|
|
387
|
-
transition: all var(--transition-fast);
|
|
388
|
-
|
|
389
|
-
&:hover {
|
|
390
|
-
background-color: var(--bg-color-hover);
|
|
391
|
-
color: var(--text-primary);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
span {
|
|
395
|
-
font-size: var(--font-size-sm);
|
|
396
|
-
white-space: nowrap;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
.footer-user {
|
|
401
|
-
display: flex;
|
|
402
|
-
align-items: center;
|
|
403
|
-
gap: var(--spacing-sm);
|
|
404
|
-
padding: var(--spacing-sm);
|
|
405
|
-
margin-top: var(--spacing-xs);
|
|
406
|
-
background: var(--bg-color-secondarycontainer);
|
|
407
|
-
border-radius: var(--border-radius);
|
|
408
|
-
|
|
409
|
-
.user-avatar {
|
|
410
|
-
width: 32px;
|
|
411
|
-
height: 32px;
|
|
412
|
-
min-width: 32px;
|
|
413
|
-
display: flex;
|
|
414
|
-
align-items: center;
|
|
415
|
-
justify-content: center;
|
|
416
|
-
background: var(--primary-color);
|
|
417
|
-
border-radius: 50%;
|
|
418
|
-
flex-shrink: 0;
|
|
419
|
-
cursor: pointer;
|
|
420
|
-
position: relative;
|
|
421
|
-
overflow: hidden;
|
|
422
|
-
|
|
423
|
-
img {
|
|
424
|
-
width: 100%;
|
|
425
|
-
height: 100%;
|
|
426
|
-
object-fit: cover;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.avatar-overlay {
|
|
430
|
-
position: absolute;
|
|
431
|
-
top: 0;
|
|
432
|
-
left: 0;
|
|
433
|
-
right: 0;
|
|
434
|
-
bottom: 0;
|
|
435
|
-
background: rgba(0, 0, 0, 0.5);
|
|
436
|
-
display: flex;
|
|
437
|
-
align-items: center;
|
|
438
|
-
justify-content: center;
|
|
439
|
-
opacity: 0;
|
|
440
|
-
transition: opacity var(--transition-fast);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
&:hover .avatar-overlay {
|
|
444
|
-
opacity: 1;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
.user-info {
|
|
449
|
-
flex: 1;
|
|
450
|
-
min-width: 0;
|
|
451
|
-
display: flex;
|
|
452
|
-
flex-direction: column;
|
|
453
|
-
|
|
454
|
-
.user-name {
|
|
455
|
-
font-size: var(--font-size-sm);
|
|
456
|
-
font-weight: var(--font-weight-medium);
|
|
457
|
-
color: var(--text-primary);
|
|
458
|
-
line-height: 1.3;
|
|
459
|
-
overflow: hidden;
|
|
460
|
-
text-overflow: ellipsis;
|
|
461
|
-
white-space: nowrap;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
.user-role {
|
|
465
|
-
font-size: var(--font-size-xs);
|
|
466
|
-
color: var(--text-placeholder);
|
|
467
|
-
line-height: 1.3;
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// 右侧主内容区域
|
|
475
|
-
.layout-main {
|
|
476
|
-
flex: 1;
|
|
477
|
-
min-width: 0;
|
|
478
|
-
overflow: hidden;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
</style>
|