befly-admin 3.4.55 → 3.4.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.
- package/.env +1 -1
- package/package.json +5 -5
- package/src/components/DetailPanel.vue +53 -13
- package/src/config/index.js +1 -1
- package/src/layouts/default.vue +270 -120
- package/src/main.js +0 -4
- package/src/styles/global.scss +94 -101
- package/src/types/auto-imports.d.ts +0 -4
- package/src/types/components.d.ts +8 -0
- package/src/types/typed-router.d.ts +87 -35
- package/src/utils/index.js +52 -19
- package/src/utils/useTablePage.js +0 -174
package/.env
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly-admin",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.56",
|
|
4
4
|
"description": "Befly Admin - 基于 Vue3 + OpenTiny Vue 的后台管理系统",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"preview": "vite preview"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@befly-addon/admin": "^1.0.
|
|
29
|
+
"@befly-addon/admin": "^1.0.56",
|
|
30
30
|
"@iconify-json/lucide": "^1.2.76",
|
|
31
31
|
"axios": "^1.13.2",
|
|
32
|
-
"befly-shared": "^1.1.
|
|
33
|
-
"befly-vite": "^1.0.
|
|
32
|
+
"befly-shared": "^1.1.2",
|
|
33
|
+
"befly-vite": "^1.0.14",
|
|
34
34
|
"pinia": "^3.0.4",
|
|
35
35
|
"tdesign-vue-next": "^1.17.5",
|
|
36
36
|
"vite": "^7.2.4",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"pnpm": ">=10.0.0",
|
|
43
43
|
"bun": ">=1.3.0"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "d9a4c57539f6eb692d7db6c7fd800e021891a07c"
|
|
46
46
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="detail-panel">
|
|
3
3
|
<div class="detail-content">
|
|
4
4
|
<div v-if="data">
|
|
5
|
-
<div v-for="field in
|
|
5
|
+
<div v-for="field in normalizedFields" :key="field.key" class="detail-item">
|
|
6
6
|
<div class="detail-label">{{ field.label }}</div>
|
|
7
7
|
<div class="detail-value">
|
|
8
8
|
<!-- 状态字段特殊处理 -->
|
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
</template>
|
|
32
32
|
|
|
33
33
|
<script setup>
|
|
34
|
+
import { computed } from 'vue';
|
|
34
35
|
import { Tag as TTag } from 'tdesign-vue-next';
|
|
35
36
|
|
|
36
|
-
defineProps({
|
|
37
|
+
const props = defineProps({
|
|
37
38
|
/**
|
|
38
39
|
* 当前行数据
|
|
39
40
|
*/
|
|
@@ -42,13 +43,22 @@ defineProps({
|
|
|
42
43
|
default: null
|
|
43
44
|
},
|
|
44
45
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
46
|
+
* 字段配置,支持两种格式:
|
|
47
|
+
* 1. fields 格式: [{ key: 'id', label: 'ID' }]
|
|
48
|
+
* 2. columns 格式: [{ colKey: 'id', title: 'ID' }]
|
|
49
|
+
* 自动过滤 row-select、operation 等非数据列
|
|
47
50
|
*/
|
|
48
51
|
fields: {
|
|
49
52
|
type: Array,
|
|
50
53
|
required: true
|
|
51
54
|
},
|
|
55
|
+
/**
|
|
56
|
+
* 需要过滤的列 key
|
|
57
|
+
*/
|
|
58
|
+
excludeKeys: {
|
|
59
|
+
type: Array,
|
|
60
|
+
default: () => ['row-select', 'operation', 'index']
|
|
61
|
+
},
|
|
52
62
|
/**
|
|
53
63
|
* 空数据时的提示文字
|
|
54
64
|
*/
|
|
@@ -58,6 +68,23 @@ defineProps({
|
|
|
58
68
|
}
|
|
59
69
|
});
|
|
60
70
|
|
|
71
|
+
/**
|
|
72
|
+
* 标准化字段配置,支持 columns 和 fields 两种格式
|
|
73
|
+
*/
|
|
74
|
+
const normalizedFields = computed(() => {
|
|
75
|
+
return props.fields
|
|
76
|
+
.filter((item) => {
|
|
77
|
+
const key = item.colKey || item.key;
|
|
78
|
+
return key && !props.excludeKeys.includes(key);
|
|
79
|
+
})
|
|
80
|
+
.map((item) => ({
|
|
81
|
+
key: item.colKey || item.key,
|
|
82
|
+
label: item.title || item.label,
|
|
83
|
+
default: item.default,
|
|
84
|
+
formatter: item.formatter
|
|
85
|
+
}));
|
|
86
|
+
});
|
|
87
|
+
|
|
61
88
|
/**
|
|
62
89
|
* 格式化字段值
|
|
63
90
|
* @param {any} value - 字段值
|
|
@@ -79,44 +106,57 @@ function formatValue(value, field) {
|
|
|
79
106
|
.detail-panel {
|
|
80
107
|
height: 100%;
|
|
81
108
|
overflow: auto;
|
|
109
|
+
background: var(--bg-color-container);
|
|
82
110
|
}
|
|
83
111
|
|
|
84
112
|
.detail-content {
|
|
85
|
-
padding:
|
|
113
|
+
padding: var(--spacing-md);
|
|
86
114
|
}
|
|
87
115
|
|
|
88
116
|
.detail-item {
|
|
89
|
-
margin-bottom:
|
|
117
|
+
margin-bottom: var(--spacing-sm);
|
|
118
|
+
padding: var(--spacing-sm) 0;
|
|
119
|
+
border-bottom: 1px solid var(--border-color-light);
|
|
120
|
+
|
|
121
|
+
&:first-child {
|
|
122
|
+
padding-top: 0;
|
|
123
|
+
}
|
|
90
124
|
|
|
91
125
|
&:last-child {
|
|
92
126
|
margin-bottom: 0;
|
|
127
|
+
padding-bottom: 0;
|
|
128
|
+
border-bottom: none;
|
|
93
129
|
}
|
|
94
130
|
}
|
|
95
131
|
|
|
96
132
|
.detail-label {
|
|
97
133
|
color: var(--text-secondary);
|
|
98
|
-
margin-bottom:
|
|
99
|
-
font-size:
|
|
134
|
+
margin-bottom: 6px;
|
|
135
|
+
font-size: var(--font-size-xs);
|
|
136
|
+
font-weight: var(--font-weight-medium);
|
|
100
137
|
}
|
|
101
138
|
|
|
102
139
|
.detail-value {
|
|
103
140
|
color: var(--text-primary);
|
|
104
|
-
font-size:
|
|
141
|
+
font-size: var(--font-size-sm);
|
|
142
|
+
font-weight: var(--font-weight-medium);
|
|
105
143
|
word-break: break-all;
|
|
144
|
+
line-height: 1.5;
|
|
106
145
|
}
|
|
107
146
|
|
|
108
147
|
.detail-empty {
|
|
109
148
|
text-align: center;
|
|
110
|
-
padding:
|
|
149
|
+
padding: var(--spacing-xl) 0;
|
|
111
150
|
color: var(--text-placeholder);
|
|
112
151
|
}
|
|
113
152
|
|
|
114
153
|
.empty-icon {
|
|
115
|
-
font-size:
|
|
116
|
-
margin-bottom:
|
|
154
|
+
font-size: 40px;
|
|
155
|
+
margin-bottom: var(--spacing-sm);
|
|
156
|
+
opacity: 0.5;
|
|
117
157
|
}
|
|
118
158
|
|
|
119
159
|
.empty-text {
|
|
120
|
-
font-size:
|
|
160
|
+
font-size: var(--font-size-sm);
|
|
121
161
|
}
|
|
122
162
|
</style>
|
package/src/config/index.js
CHANGED
package/src/layouts/default.vue
CHANGED
|
@@ -1,62 +1,76 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="layout-
|
|
3
|
-
<!--
|
|
4
|
-
<div class="layout-
|
|
5
|
-
|
|
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
|
+
<i-lucide:box style="width: 24px; height: 24px; color: var(--primary-color)" />
|
|
9
|
+
</div>
|
|
6
10
|
<h2>{{ $Config.appTitle }}</h2>
|
|
7
11
|
</div>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
|
|
13
|
+
<!-- 菜单区域 -->
|
|
14
|
+
<div class="sidebar-menu">
|
|
15
|
+
<t-menu v-model:value="$Data.currentMenuKey" v-model:expanded="$Data.expandedKeys" width="220px" @change="$Method.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
|
+
<i-lucide:home v-if="menu.path === '/addon/admin/'" style="margin-right: 8px" />
|
|
21
|
+
<i-lucide:file-text 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
|
+
<i-lucide:folder 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
|
+
<i-lucide:file-text 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="$Method.handleSettings">
|
|
44
|
+
<i-lucide:settings style="width: 18px; height: 18px" />
|
|
45
|
+
<span>系统设置</span>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="footer-user">
|
|
48
|
+
<t-upload :action="$Config.uploadUrl" :headers="{ Authorization: $Storage.local.get('token') }" :show-upload-list="false" accept="image/*" @success="$Method.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
|
+
<i-lucide:user v-else style="width: 16px; height: 16px; color: #fff" />
|
|
52
|
+
<div class="avatar-overlay">
|
|
53
|
+
<i-lucide:camera style="width: 14px; height: 14px; color: #fff" />
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</t-upload>
|
|
57
|
+
<div class="user-info">
|
|
11
58
|
<span class="user-name">{{ $Data.userInfo.nickname || '管理员' }}</span>
|
|
12
|
-
<
|
|
59
|
+
<span class="user-role">{{ $Data.userInfo.role || '超级管理员' }}</span>
|
|
13
60
|
</div>
|
|
14
|
-
<t-button
|
|
61
|
+
<t-button theme="default" variant="text" size="small" @click="$Method.handleLogout">
|
|
15
62
|
<template #icon>
|
|
16
|
-
<i-lucide:log-out style="
|
|
63
|
+
<i-lucide:log-out style="width: 16px; height: 16px" />
|
|
17
64
|
</template>
|
|
18
65
|
</t-button>
|
|
19
66
|
</div>
|
|
20
67
|
</div>
|
|
21
68
|
</div>
|
|
22
69
|
|
|
23
|
-
<!--
|
|
24
|
-
<div class="layout-
|
|
25
|
-
<t-menu v-model:value="$Data.currentMenuKey" v-model:expanded="$Data.expandedKeys" style="height: 100%" @change="$Method.onMenuClick">
|
|
26
|
-
<template v-for="menu in $Data.userMenus" :key="menu.id">
|
|
27
|
-
<!-- 无子菜单 -->
|
|
28
|
-
<t-menu-item v-if="!menu.children || menu.children.length === 0" :value="menu.path">
|
|
29
|
-
<template #icon>
|
|
30
|
-
<i-lucide:home v-if="menu.path === '/addon/admin/'" style="margin-right: 8px" />
|
|
31
|
-
<i-lucide:file-text v-else style="margin-right: 8px" />
|
|
32
|
-
</template>
|
|
33
|
-
{{ menu.name }}
|
|
34
|
-
</t-menu-item>
|
|
35
|
-
<!-- 有子菜单 -->
|
|
36
|
-
<t-submenu v-else :value="String(menu.id)" :title="menu.name">
|
|
37
|
-
<template #icon>
|
|
38
|
-
<i-lucide:folder style="margin-right: 8px" />
|
|
39
|
-
</template>
|
|
40
|
-
<t-menu-item v-for="child in menu.children" :key="child.id" :value="child.path">
|
|
41
|
-
<template #icon>
|
|
42
|
-
<i-lucide:file-text style="margin-right: 8px" />
|
|
43
|
-
</template>
|
|
44
|
-
{{ child.name }}
|
|
45
|
-
</t-menu-item>
|
|
46
|
-
</t-submenu>
|
|
47
|
-
</template>
|
|
48
|
-
</t-menu>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<!-- 内容区域 -->
|
|
52
|
-
<div class="layout-content">
|
|
70
|
+
<!-- 右侧内容区域 -->
|
|
71
|
+
<div class="layout-main">
|
|
53
72
|
<RouterView />
|
|
54
73
|
</div>
|
|
55
|
-
|
|
56
|
-
<!-- 底部分页栏 -->
|
|
57
|
-
<div class="layout-footer">
|
|
58
|
-
<span>© 2024 Befly. All rights reserved.</span>
|
|
59
|
-
</div>
|
|
60
74
|
</div>
|
|
61
75
|
</template>
|
|
62
76
|
|
|
@@ -79,7 +93,8 @@ const $Data = $ref({
|
|
|
79
93
|
currentMenuKey: '',
|
|
80
94
|
userInfo: {
|
|
81
95
|
nickname: '管理员',
|
|
82
|
-
role: '超级管理员'
|
|
96
|
+
role: '超级管理员',
|
|
97
|
+
avatar: '' // 用户头像
|
|
83
98
|
}
|
|
84
99
|
});
|
|
85
100
|
|
|
@@ -138,15 +153,30 @@ const $Method = {
|
|
|
138
153
|
|
|
139
154
|
// 处理退出登录
|
|
140
155
|
handleLogout() {
|
|
141
|
-
DialogPlugin.confirm({
|
|
156
|
+
const dialog = DialogPlugin.confirm({
|
|
142
157
|
body: '确定要退出登录吗?',
|
|
143
158
|
header: '确认',
|
|
144
159
|
onConfirm: () => {
|
|
160
|
+
dialog.destroy();
|
|
145
161
|
$Storage.local.remove('token');
|
|
146
162
|
router.push('/internal/login');
|
|
147
163
|
MessagePlugin.success('退出成功');
|
|
148
164
|
}
|
|
149
165
|
});
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// 处理系统设置
|
|
169
|
+
handleSettings() {
|
|
170
|
+
router.push('/addon/admin/settings');
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// 头像上传成功
|
|
174
|
+
onAvatarUploadSuccess(res) {
|
|
175
|
+
if (res.response?.code === 0 && res.response?.data?.url) {
|
|
176
|
+
$Data.userInfo.avatar = res.response.data.url;
|
|
177
|
+
MessagePlugin.success('头像上传成功');
|
|
178
|
+
// TODO: 可以调用接口保存用户头像
|
|
179
|
+
}
|
|
150
180
|
}
|
|
151
181
|
};
|
|
152
182
|
|
|
@@ -154,107 +184,227 @@ $Method.fetchUserMenus();
|
|
|
154
184
|
</script>
|
|
155
185
|
|
|
156
186
|
<style scoped lang="scss">
|
|
157
|
-
.layout-
|
|
158
|
-
|
|
159
|
-
top: 0;
|
|
160
|
-
left: 0;
|
|
187
|
+
.layout-wrapper {
|
|
188
|
+
display: flex;
|
|
161
189
|
height: 100vh;
|
|
162
190
|
width: 100vw;
|
|
163
191
|
background: var(--bg-color-page);
|
|
192
|
+
padding: var(--layout-gap);
|
|
193
|
+
gap: var(--layout-gap);
|
|
164
194
|
overflow: hidden;
|
|
165
195
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
right: 0;
|
|
171
|
-
height: var(--header-height);
|
|
196
|
+
// 左侧边栏
|
|
197
|
+
.layout-sidebar {
|
|
198
|
+
width: var(--sidebar-width);
|
|
199
|
+
flex-shrink: 0;
|
|
172
200
|
display: flex;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.logo {
|
|
201
|
+
flex-direction: column;
|
|
202
|
+
background: var(--bg-color-container);
|
|
203
|
+
border-radius: var(--border-radius-large);
|
|
204
|
+
box-shadow: var(--shadow-1);
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
|
|
207
|
+
// Logo 区域
|
|
208
|
+
.sidebar-logo {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
gap: var(--spacing-sm);
|
|
212
|
+
padding: var(--spacing-md) var(--spacing-md);
|
|
213
|
+
border-bottom: 1px solid var(--border-color-light);
|
|
214
|
+
|
|
215
|
+
.logo-icon {
|
|
216
|
+
width: 36px;
|
|
217
|
+
height: 36px;
|
|
218
|
+
min-width: 36px;
|
|
219
|
+
display: flex;
|
|
220
|
+
align-items: center;
|
|
221
|
+
justify-content: center;
|
|
222
|
+
background: var(--primary-color-light);
|
|
223
|
+
border-radius: var(--border-radius);
|
|
224
|
+
}
|
|
225
|
+
|
|
181
226
|
h2 {
|
|
182
227
|
margin: 0;
|
|
183
|
-
font-size:
|
|
184
|
-
font-weight:
|
|
228
|
+
font-size: var(--font-size-md);
|
|
229
|
+
font-weight: var(--font-weight-semibold);
|
|
185
230
|
color: var(--text-primary);
|
|
186
|
-
|
|
231
|
+
white-space: nowrap;
|
|
232
|
+
overflow: hidden;
|
|
187
233
|
}
|
|
188
234
|
}
|
|
189
235
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
236
|
+
// 菜单区域
|
|
237
|
+
.sidebar-menu {
|
|
238
|
+
flex: 1;
|
|
239
|
+
overflow-y: auto;
|
|
240
|
+
padding: var(--spacing-xs) 0;
|
|
241
|
+
|
|
242
|
+
:deep(.t-menu) {
|
|
243
|
+
border-right: none;
|
|
244
|
+
background: transparent;
|
|
245
|
+
|
|
246
|
+
// 子菜单项(非父级的菜单项)
|
|
247
|
+
.t-menu__item {
|
|
248
|
+
margin: 2px var(--spacing-sm);
|
|
249
|
+
border-radius: var(--border-radius);
|
|
250
|
+
transition: all var(--transition-fast);
|
|
251
|
+
position: relative;
|
|
252
|
+
|
|
253
|
+
&:hover {
|
|
254
|
+
background-color: var(--bg-color-hover);
|
|
255
|
+
}
|
|
194
256
|
|
|
195
|
-
|
|
257
|
+
&.t-is-active {
|
|
258
|
+
background-color: var(--primary-color-light);
|
|
259
|
+
color: var(--primary-color);
|
|
260
|
+
font-weight: var(--font-weight-medium);
|
|
261
|
+
|
|
262
|
+
&::before {
|
|
263
|
+
content: '';
|
|
264
|
+
position: absolute;
|
|
265
|
+
left: 0;
|
|
266
|
+
top: 50%;
|
|
267
|
+
transform: translateY(-50%);
|
|
268
|
+
width: var(--menu-active-indicator);
|
|
269
|
+
height: 60%;
|
|
270
|
+
background-color: var(--primary-color);
|
|
271
|
+
border-radius: 0 2px 2px 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 父级菜单样式(有子菜单的)
|
|
277
|
+
.t-submenu {
|
|
278
|
+
// 父级菜单的 header(不显示指示条)
|
|
279
|
+
> .t-menu__item,
|
|
280
|
+
> .t-submenu__header {
|
|
281
|
+
margin: 2px var(--spacing-sm);
|
|
282
|
+
border-radius: var(--border-radius);
|
|
283
|
+
transition: all var(--transition-fast);
|
|
284
|
+
position: relative;
|
|
285
|
+
|
|
286
|
+
&:hover {
|
|
287
|
+
background-color: var(--bg-color-hover);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 父级菜单不显示指示条和背景
|
|
291
|
+
&::before {
|
|
292
|
+
display: none !important;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
&.t-is-active {
|
|
296
|
+
background-color: transparent !important;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 底部操作区域
|
|
304
|
+
.sidebar-footer {
|
|
305
|
+
border-top: 1px solid var(--border-color-light);
|
|
306
|
+
padding: var(--spacing-sm);
|
|
307
|
+
|
|
308
|
+
.footer-item {
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
gap: var(--spacing-sm);
|
|
312
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
313
|
+
border-radius: var(--border-radius);
|
|
314
|
+
color: var(--text-secondary);
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
transition: all var(--transition-fast);
|
|
317
|
+
|
|
318
|
+
&:hover {
|
|
319
|
+
background-color: var(--bg-color-hover);
|
|
320
|
+
color: var(--text-primary);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
span {
|
|
324
|
+
font-size: var(--font-size-sm);
|
|
325
|
+
white-space: nowrap;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.footer-user {
|
|
196
330
|
display: flex;
|
|
197
331
|
align-items: center;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
332
|
+
gap: var(--spacing-sm);
|
|
333
|
+
padding: var(--spacing-sm);
|
|
334
|
+
margin-top: var(--spacing-xs);
|
|
335
|
+
background: var(--bg-color-secondarycontainer);
|
|
336
|
+
border-radius: var(--border-radius);
|
|
202
337
|
|
|
203
|
-
.user-
|
|
338
|
+
.user-avatar {
|
|
339
|
+
width: 32px;
|
|
340
|
+
height: 32px;
|
|
341
|
+
min-width: 32px;
|
|
342
|
+
display: flex;
|
|
343
|
+
align-items: center;
|
|
344
|
+
justify-content: center;
|
|
345
|
+
background: var(--primary-color);
|
|
346
|
+
border-radius: 50%;
|
|
347
|
+
flex-shrink: 0;
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
position: relative;
|
|
350
|
+
overflow: hidden;
|
|
351
|
+
|
|
352
|
+
img {
|
|
353
|
+
width: 100%;
|
|
354
|
+
height: 100%;
|
|
355
|
+
object-fit: cover;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.avatar-overlay {
|
|
359
|
+
position: absolute;
|
|
360
|
+
top: 0;
|
|
361
|
+
left: 0;
|
|
362
|
+
right: 0;
|
|
363
|
+
bottom: 0;
|
|
364
|
+
background: rgba(0, 0, 0, 0.5);
|
|
365
|
+
display: flex;
|
|
366
|
+
align-items: center;
|
|
367
|
+
justify-content: center;
|
|
368
|
+
opacity: 0;
|
|
369
|
+
transition: opacity var(--transition-fast);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
&:hover .avatar-overlay {
|
|
373
|
+
opacity: 1;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.user-info {
|
|
378
|
+
flex: 1;
|
|
379
|
+
min-width: 0;
|
|
204
380
|
display: flex;
|
|
205
381
|
flex-direction: column;
|
|
206
|
-
align-items: flex-start;
|
|
207
382
|
|
|
208
383
|
.user-name {
|
|
209
384
|
font-size: var(--font-size-sm);
|
|
210
|
-
font-weight:
|
|
385
|
+
font-weight: var(--font-weight-medium);
|
|
211
386
|
color: var(--text-primary);
|
|
387
|
+
line-height: 1.3;
|
|
388
|
+
overflow: hidden;
|
|
389
|
+
text-overflow: ellipsis;
|
|
390
|
+
white-space: nowrap;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.user-role {
|
|
394
|
+
font-size: var(--font-size-xs);
|
|
395
|
+
color: var(--text-placeholder);
|
|
396
|
+
line-height: 1.3;
|
|
212
397
|
}
|
|
213
|
-
}
|
|
214
|
-
.logout-btn {
|
|
215
|
-
color: var(--text-secondary);
|
|
216
|
-
margin-left: var(--spacing-md);
|
|
217
398
|
}
|
|
218
399
|
}
|
|
219
400
|
}
|
|
220
401
|
}
|
|
221
402
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
width: var(--sidebar-width);
|
|
228
|
-
background: var(--bg-color-container);
|
|
229
|
-
border-right: 1px solid var(--border-color);
|
|
230
|
-
z-index: 99;
|
|
231
|
-
overflow-y: auto;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.layout-content {
|
|
235
|
-
position: absolute;
|
|
236
|
-
top: var(--header-height);
|
|
237
|
-
left: var(--sidebar-width);
|
|
238
|
-
right: 0;
|
|
239
|
-
bottom: var(--footer-height);
|
|
240
|
-
background: var(--bg-color-page);
|
|
241
|
-
overflow-y: auto;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.layout-footer {
|
|
245
|
-
position: absolute;
|
|
246
|
-
bottom: 0;
|
|
247
|
-
left: 0;
|
|
248
|
-
right: 0;
|
|
249
|
-
height: var(--footer-height);
|
|
250
|
-
display: flex;
|
|
251
|
-
align-items: center;
|
|
252
|
-
justify-content: center;
|
|
253
|
-
background: var(--bg-color-container);
|
|
254
|
-
border-top: 1px solid var(--border-color);
|
|
255
|
-
color: var(--text-secondary);
|
|
256
|
-
font-size: var(--font-size-sm);
|
|
257
|
-
z-index: 98;
|
|
403
|
+
// 右侧主内容区域
|
|
404
|
+
.layout-main {
|
|
405
|
+
flex: 1;
|
|
406
|
+
min-width: 0;
|
|
407
|
+
overflow: hidden;
|
|
258
408
|
}
|
|
259
409
|
}
|
|
260
410
|
</style>
|