@coffic/cosy-ui 0.4.3 → 0.4.5

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.
@@ -1,32 +1,32 @@
1
1
  ---
2
2
  /**
3
3
  * @component DashboardLayout
4
- *
4
+ *
5
5
  * @description
6
6
  * DashboardLayout 组件适用于管理后台的布局,包含侧边栏导航和顶部导航栏。
7
7
  * 提供了完整的管理界面框架,包括响应式设计和暗色主题支持。
8
- *
8
+ *
9
9
  * @design
10
10
  * 设计理念:
11
11
  * 1. 清晰的信息层次 - 通过侧边栏和顶部导航提供明确的导航结构
12
12
  * 2. 响应式布局 - 在不同设备上提供良好的用户体验
13
13
  * 3. 可定制性 - 支持自定义导航项、系统名称和用户信息
14
14
  * 4. 状态保持 - 记住用户的侧边栏折叠状态
15
- *
15
+ *
16
16
  * @example
17
17
  * ```astro
18
18
  * ---
19
19
  * import DashboardLayout from '../layouts/DashboardLayout.astro';
20
- *
20
+ *
21
21
  * const navItems = [
22
22
  * { href: "/dashboard", icon: "home", text: "仪表盘" },
23
23
  * { href: "/dashboard/users", icon: "user", text: "用户管理" },
24
24
  * { href: "/dashboard/settings", icon: "settings", text: "系统设置" }
25
25
  * ];
26
26
  * ---
27
- *
28
- * <DashboardLayout
29
- * title="管理后台"
27
+ *
28
+ * <DashboardLayout
29
+ * title="管理后台"
30
30
  * navItems={navItems}
31
31
  * userName="管理员"
32
32
  * >
@@ -40,621 +40,632 @@ import BaseLayout from './BaseLayout.astro';
40
40
  import '../../app.css';
41
41
 
42
42
  export interface NavItem {
43
- href: string;
44
- icon: string;
45
- text: string;
46
- badge?: string | number;
47
- items?: NavItem[];
43
+ href: string;
44
+ icon: string;
45
+ text: string;
46
+ badge?: string | number;
47
+ items?: NavItem[];
48
48
  }
49
49
 
50
50
  export interface Props {
51
- /**
52
- * 页面标题
53
- */
54
- title: string;
55
-
56
- /**
57
- * 页面描述
58
- */
59
- description?: string;
60
-
61
- /**
62
- * 系统名称
63
- * @default "管理系统"
64
- */
65
- systemName?: string;
66
-
67
- /**
68
- * 导航项目
69
- */
70
- navItems: NavItem[];
71
-
72
- /**
73
- * 用户名
74
- */
75
- userName?: string;
76
-
77
- /**
78
- * 用户头像
79
- */
80
- userAvatar?: string;
81
-
82
- /**
83
- * 是否折叠侧边栏
84
- * @default false
85
- */
86
- sidebarCollapsed?: boolean;
87
-
88
- /**
89
- * 自定义头部内容
90
- */
91
- head?: astroHTML.JSX.Element;
92
-
93
- /**
94
- * 页面类名
95
- */
96
- class?: string;
97
-
98
- /**
99
- * 类名列表
100
- */
101
- 'class:list'?: any;
51
+ /**
52
+ * 页面标题
53
+ */
54
+ title: string;
55
+
56
+ /**
57
+ * 页面描述
58
+ */
59
+ description?: string;
60
+
61
+ /**
62
+ * 系统名称
63
+ * @default "管理系统"
64
+ */
65
+ systemName?: string;
66
+
67
+ /**
68
+ * 导航项目
69
+ */
70
+ navItems: NavItem[];
71
+
72
+ /**
73
+ * 用户名
74
+ */
75
+ userName?: string;
76
+
77
+ /**
78
+ * 用户头像
79
+ */
80
+ userAvatar?: string;
81
+
82
+ /**
83
+ * 是否折叠侧边栏
84
+ * @default false
85
+ */
86
+ sidebarCollapsed?: boolean;
87
+
88
+ /**
89
+ * 自定义头部内容
90
+ */
91
+ head?: astroHTML.JSX.Element;
92
+
93
+ /**
94
+ * 页面类名
95
+ */
96
+ class?: string;
97
+
98
+ /**
99
+ * 类名列表
100
+ */
101
+ 'class:list'?: any;
102
102
  }
103
103
 
104
104
  const {
105
- title,
106
- description,
107
- systemName = "管理系统",
108
- navItems,
109
- userName,
110
- userAvatar,
111
- sidebarCollapsed = false,
112
- head,
113
- class: className,
114
- 'class:list': classList,
115
- ...rest
105
+ title,
106
+ description,
107
+ systemName = '管理系统',
108
+ navItems,
109
+ userName,
110
+ userAvatar,
111
+ sidebarCollapsed = false,
112
+ head,
113
+ class: className,
114
+ 'class:list': classList,
115
+ ...rest
116
116
  } = Astro.props;
117
117
 
118
118
  const currentPath = Astro.url.pathname;
119
119
 
120
120
  // 图标映射
121
121
  const iconMap: Record<string, string> = {
122
- home: "🏠",
123
- user: "👤",
124
- users: "👥",
125
- settings: "⚙️",
126
- chart: "📊",
127
- document: "📄",
128
- calendar: "📅",
129
- notification: "🔔",
130
- message: "💬",
131
- search: "🔍",
132
- star: "",
133
- heart: "❤️",
134
- menu: ""
122
+ home: '🏠',
123
+ user: '👤',
124
+ users: '👥',
125
+ settings: '⚙️',
126
+ chart: '📊',
127
+ document: '📄',
128
+ calendar: '📅',
129
+ notification: '🔔',
130
+ message: '💬',
131
+ search: '🔍',
132
+ star: '',
133
+ heart: '❤️',
134
+ menu: '',
135
135
  };
136
136
  ---
137
137
 
138
- <BaseLayout
139
- title={title}
140
- description={description}
141
- head={head}
142
- class="dashboard-layout"
143
- {...rest}
144
- >
145
- <div class:list={["dashboard-container", { collapsed: sidebarCollapsed }]}>
146
- <!-- 侧边栏 -->
147
- <aside class="sidebar">
148
- <div class="sidebar-header">
149
- <a href="/dashboard" class="logo">
150
- <span class="logo-icon">⚡</span>
151
- <span class="logo-text">{systemName}</span>
152
- </a>
153
- <button class="collapse-button" id="collapse-sidebar">
154
- <span class="collapse-icon">{iconMap.menu}</span>
155
- </button>
156
- </div>
157
-
158
- <nav class="sidebar-nav">
159
- <ul class="nav-list">
160
- {navItems.map((item: NavItem) => {
161
- const isActive = currentPath === item.href ||
162
- (item.items && item.items.some((subitem: NavItem) => currentPath === subitem.href));
163
-
164
- return (
165
- <li class:list={["nav-item", { active: isActive }]}>
166
- <a href={item.href} class="nav-link">
167
- <span class="nav-icon">{iconMap[item.icon] || "📁"}</span>
168
- <span class="nav-text">{item.text}</span>
169
- {item.badge && <span class="nav-badge">{item.badge}</span>}
170
- </a>
171
-
172
- {item.items && (
173
- <ul class="subnav-list">
174
- {item.items.map((subitem: NavItem) => {
175
- const isSubActive = currentPath === subitem.href;
176
- return (
177
- <li class:list={["subnav-item", { active: isSubActive }]}>
178
- <a href={subitem.href} class="subnav-link">
179
- <span class="subnav-icon">{iconMap[subitem.icon] || "📄"}</span>
180
- <span class="subnav-text">{subitem.text}</span>
181
- {subitem.badge && <span class="subnav-badge">{subitem.badge}</span>}
182
- </a>
183
- </li>
184
- );
185
- })}
186
- </ul>
187
- )}
188
- </li>
189
- );
190
- })}
191
- </ul>
192
- </nav>
193
- </aside>
194
-
195
- <!-- 主内容区 -->
196
- <div class="main-content">
197
- <!-- 顶部导航栏 -->
198
- <header class="top-navbar">
199
- <div class="navbar-left">
200
- <button class="menu-button" id="toggle-sidebar">
201
- <span class="menu-icon">{iconMap.menu}</span>
202
- </button>
203
-
204
- <div class="breadcrumb">
205
- <span class="breadcrumb-item">{title}</span>
206
- </div>
207
- </div>
208
-
209
- <div class="navbar-right">
210
- <div class="search-box">
211
- <input type="text" placeholder="搜索..." class="search-input" />
212
- <span class="search-icon">{iconMap.search}</span>
213
- </div>
214
-
215
- <button class="notification-button">
216
- <span class="notification-icon">{iconMap.notification}</span>
217
- <span class="notification-badge">3</span>
218
- </button>
219
-
220
- {userName && (
221
- <div class="user-dropdown">
222
- <button class="user-button">
223
- {userAvatar ? (
224
- <img src={userAvatar} alt={userName} class="user-avatar" />
225
- ) : (
226
- <span class="user-avatar-placeholder">{userName.charAt(0)}</span>
227
- )}
228
- <span class="user-name">{userName}</span>
229
- </button>
230
- </div>
231
- )}
232
- </div>
233
- </header>
234
-
235
- <!-- 页面内容 -->
236
- <main class="page-content">
237
- <div class="content-container">
238
- <slot />
239
- </div>
240
- </main>
241
- </div>
242
- </div>
138
+ <BaseLayout
139
+ title={title}
140
+ description={description || ''}
141
+ head={head}
142
+ keywords=""
143
+ author=""
144
+ robots=""
145
+ class="dashboard-layout"
146
+ {...rest}>
147
+ <div class:list={['dashboard-container', { collapsed: sidebarCollapsed }]}>
148
+ <!-- 侧边栏 -->
149
+ <aside class="sidebar">
150
+ <div class="sidebar-header">
151
+ <a href="/dashboard" class="logo">
152
+ <span class="logo-icon">⚡</span>
153
+ <span class="logo-text">{systemName}</span>
154
+ </a>
155
+ <button class="collapse-button" id="collapse-sidebar">
156
+ <span class="collapse-icon">{iconMap.menu}</span>
157
+ </button>
158
+ </div>
159
+
160
+ <nav class="sidebar-nav">
161
+ <ul class="nav-list">
162
+ {
163
+ navItems.map((item: NavItem) => {
164
+ const isActive =
165
+ currentPath === item.href ||
166
+ (item.items && item.items.some((subitem: NavItem) => currentPath === subitem.href));
167
+
168
+ return (
169
+ <li class:list={['nav-item', { active: isActive }]}>
170
+ <a href={item.href} class="nav-link">
171
+ <span class="nav-icon">{iconMap[item.icon] || '📁'}</span>
172
+ <span class="nav-text">{item.text}</span>
173
+ {item.badge && <span class="nav-badge">{item.badge}</span>}
174
+ </a>
175
+
176
+ {item.items && (
177
+ <ul class="subnav-list">
178
+ {item.items.map((subitem: NavItem) => {
179
+ const isSubActive = currentPath === subitem.href;
180
+ return (
181
+ <li class:list={['subnav-item', { active: isSubActive }]}>
182
+ <a href={subitem.href} class="subnav-link">
183
+ <span class="subnav-icon">{iconMap[subitem.icon] || '📄'}</span>
184
+ <span class="subnav-text">{subitem.text}</span>
185
+ {subitem.badge && <span class="subnav-badge">{subitem.badge}</span>}
186
+ </a>
187
+ </li>
188
+ );
189
+ })}
190
+ </ul>
191
+ )}
192
+ </li>
193
+ );
194
+ })
195
+ }
196
+ </ul>
197
+ </nav>
198
+ </aside>
199
+
200
+ <!-- 主内容区 -->
201
+ <div class="main-content">
202
+ <!-- 顶部导航栏 -->
203
+ <header class="top-navbar">
204
+ <div class="navbar-left">
205
+ <button class="menu-button" id="toggle-sidebar">
206
+ <span class="menu-icon">{iconMap.menu}</span>
207
+ </button>
208
+
209
+ <div class="breadcrumb">
210
+ <span class="breadcrumb-item">{title}</span>
211
+ </div>
212
+ </div>
213
+
214
+ <div class="navbar-right">
215
+ <div class="search-box">
216
+ <input type="text" placeholder="搜索..." class="search-input" />
217
+ <span class="search-icon">{iconMap.search}</span>
218
+ </div>
219
+
220
+ <button class="notification-button">
221
+ <span class="notification-icon">{iconMap.notification}</span>
222
+ <span class="notification-badge">3</span>
223
+ </button>
224
+
225
+ {
226
+ userName && (
227
+ <div class="user-dropdown">
228
+ <button class="user-button">
229
+ {userAvatar ? (
230
+ <img src={userAvatar} alt={userName} class="user-avatar" />
231
+ ) : (
232
+ <span class="user-avatar-placeholder">{userName.charAt(0)}</span>
233
+ )}
234
+ <span class="user-name">{userName}</span>
235
+ </button>
236
+ </div>
237
+ )
238
+ }
239
+ </div>
240
+ </header>
241
+
242
+ <!-- 页面内容 -->
243
+ <main class="page-content">
244
+ <div class="content-container">
245
+ <slot />
246
+ </div>
247
+ </main>
248
+ </div>
249
+ </div>
243
250
  </BaseLayout>
244
251
 
245
252
  <script>
246
- // 侧边栏折叠/展开功能
247
- document.addEventListener('DOMContentLoaded', () => {
248
- const toggleSidebarBtn = document.getElementById('toggle-sidebar');
249
- const collapseSidebarBtn = document.getElementById('collapse-sidebar');
250
- const dashboardContainer = document.querySelector('.dashboard-container');
251
-
252
- if (toggleSidebarBtn && dashboardContainer) {
253
- toggleSidebarBtn.addEventListener('click', () => {
254
- dashboardContainer.classList.toggle('collapsed');
255
- // 保存状态到本地存储
256
- localStorage.setItem('sidebarCollapsed',
257
- dashboardContainer.classList.contains('collapsed') ? 'true' : 'false');
258
- });
259
- }
260
-
261
- if (collapseSidebarBtn && dashboardContainer) {
262
- collapseSidebarBtn.addEventListener('click', () => {
263
- dashboardContainer.classList.toggle('collapsed');
264
- // 保存状态到本地存储
265
- localStorage.setItem('sidebarCollapsed',
266
- dashboardContainer.classList.contains('collapsed') ? 'true' : 'false');
267
- });
268
- }
269
-
270
- // 从本地存储恢复状态
271
- const savedState = localStorage.getItem('sidebarCollapsed');
272
- if (savedState === 'true' && dashboardContainer) {
273
- dashboardContainer.classList.add('collapsed');
274
- }
275
- });
253
+ // 侧边栏折叠/展开功能
254
+ document.addEventListener('DOMContentLoaded', () => {
255
+ const toggleSidebarBtn = document.getElementById('toggle-sidebar');
256
+ const collapseSidebarBtn = document.getElementById('collapse-sidebar');
257
+ const dashboardContainer = document.querySelector('.dashboard-container');
258
+
259
+ if (toggleSidebarBtn && dashboardContainer) {
260
+ toggleSidebarBtn.addEventListener('click', () => {
261
+ dashboardContainer.classList.toggle('collapsed');
262
+ // 保存状态到本地存储
263
+ localStorage.setItem(
264
+ 'sidebarCollapsed',
265
+ dashboardContainer.classList.contains('collapsed') ? 'true' : 'false'
266
+ );
267
+ });
268
+ }
269
+
270
+ if (collapseSidebarBtn && dashboardContainer) {
271
+ collapseSidebarBtn.addEventListener('click', () => {
272
+ dashboardContainer.classList.toggle('collapsed');
273
+ // 保存状态到本地存储
274
+ localStorage.setItem(
275
+ 'sidebarCollapsed',
276
+ dashboardContainer.classList.contains('collapsed') ? 'true' : 'false'
277
+ );
278
+ });
279
+ }
280
+
281
+ // 从本地存储恢复状态
282
+ const savedState = localStorage.getItem('sidebarCollapsed');
283
+ if (savedState === 'true' && dashboardContainer) {
284
+ dashboardContainer.classList.add('collapsed');
285
+ }
286
+ });
276
287
  </script>
277
288
 
278
289
  <style>
279
- /* 基础布局 */
280
- .dashboard-layout {
281
- min-height: 100vh;
282
- background-color: #f5f7fa;
283
- }
284
-
285
- .dashboard-container {
286
- display: flex;
287
- min-height: 100vh;
288
- }
289
-
290
- /* 侧边栏 */
291
- .sidebar {
292
- width: 260px;
293
- background-color: #1e293b;
294
- color: #e2e8f0;
295
- transition: width 0.3s ease;
296
- display: flex;
297
- flex-direction: column;
298
- position: fixed;
299
- top: 0;
300
- left: 0;
301
- bottom: 0;
302
- z-index: 50;
303
- overflow-y: auto;
304
- }
305
-
306
- .sidebar-header {
307
- display: flex;
308
- align-items: center;
309
- justify-content: space-between;
310
- padding: 1rem;
311
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
312
- }
313
-
314
- .logo {
315
- display: flex;
316
- align-items: center;
317
- gap: 0.75rem;
318
- color: #fff;
319
- text-decoration: none;
320
- }
321
-
322
- .logo-icon {
323
- font-size: 1.5rem;
324
- }
325
-
326
- .logo-text {
327
- font-size: 1.25rem;
328
- font-weight: 600;
329
- white-space: nowrap;
330
- }
331
-
332
- .collapse-button {
333
- background: none;
334
- border: none;
335
- color: #e2e8f0;
336
- cursor: pointer;
337
- padding: 0.5rem;
338
- border-radius: 0.375rem;
339
- }
340
-
341
- .collapse-button:hover {
342
- background-color: rgba(255, 255, 255, 0.1);
343
- }
344
-
345
- .sidebar-nav {
346
- flex: 1;
347
- padding: 1rem 0;
348
- }
349
-
350
- .nav-list {
351
- list-style: none;
352
- padding: 0;
353
- margin: 0;
354
- }
355
-
356
- .nav-item {
357
- margin-bottom: 0.25rem;
358
- }
359
-
360
- .nav-link {
361
- display: flex;
362
- align-items: center;
363
- padding: 0.75rem 1rem;
364
- color: #e2e8f0;
365
- text-decoration: none;
366
- border-radius: 0.375rem;
367
- margin: 0 0.5rem;
368
- gap: 0.75rem;
369
- }
370
-
371
- .nav-link:hover {
372
- background-color: rgba(255, 255, 255, 0.1);
373
- }
374
-
375
- .nav-item.active .nav-link {
376
- background-color: rgba(59, 130, 246, 0.5);
377
- color: #fff;
378
- }
379
-
380
- .nav-icon {
381
- font-size: 1.25rem;
382
- width: 1.5rem;
383
- text-align: center;
384
- }
385
-
386
- .nav-text {
387
- flex: 1;
388
- white-space: nowrap;
389
- }
390
-
391
- .nav-badge {
392
- background-color: #ef4444;
393
- color: #fff;
394
- font-size: 0.75rem;
395
- padding: 0.125rem 0.375rem;
396
- border-radius: 9999px;
397
- }
398
-
399
- .subnav-list {
400
- list-style: none;
401
- padding: 0;
402
- margin: 0.25rem 0 0.5rem 2.5rem;
403
- }
404
-
405
- .subnav-link {
406
- display: flex;
407
- align-items: center;
408
- padding: 0.5rem 1rem;
409
- color: #cbd5e1;
410
- text-decoration: none;
411
- border-radius: 0.375rem;
412
- gap: 0.5rem;
413
- font-size: 0.875rem;
414
- }
415
-
416
- .subnav-link:hover {
417
- background-color: rgba(255, 255, 255, 0.05);
418
- }
419
-
420
- .subnav-item.active .subnav-link {
421
- color: #3b82f6;
422
- font-weight: 500;
423
- }
424
-
425
- .subnav-icon {
426
- font-size: 1rem;
427
- width: 1.25rem;
428
- text-align: center;
429
- }
430
-
431
- /* 主内容区 */
432
- .main-content {
433
- flex: 1;
434
- margin-left: 260px;
435
- transition: margin-left 0.3s ease;
436
- display: flex;
437
- flex-direction: column;
438
- min-height: 100vh;
439
- }
440
-
441
- /* 顶部导航栏 */
442
- .top-navbar {
443
- display: flex;
444
- justify-content: space-between;
445
- align-items: center;
446
- padding: 0.75rem 1.5rem;
447
- background-color: #fff;
448
- border-bottom: 1px solid #e2e8f0;
449
- height: 64px;
450
- position: sticky;
451
- top: 0;
452
- z-index: 40;
453
- }
454
-
455
- .navbar-left {
456
- display: flex;
457
- align-items: center;
458
- gap: 1rem;
459
- }
460
-
461
- .menu-button {
462
- background: none;
463
- border: none;
464
- color: #64748b;
465
- cursor: pointer;
466
- padding: 0.5rem;
467
- border-radius: 0.375rem;
468
- display: flex;
469
- align-items: center;
470
- justify-content: center;
471
- }
472
-
473
- .menu-button:hover {
474
- background-color: #f1f5f9;
475
- }
476
-
477
- .breadcrumb {
478
- color: #64748b;
479
- font-size: 0.875rem;
480
- }
481
-
482
- .navbar-right {
483
- display: flex;
484
- align-items: center;
485
- gap: 1rem;
486
- }
487
-
488
- .search-box {
489
- position: relative;
490
- }
491
-
492
- .search-input {
493
- padding: 0.5rem 1rem 0.5rem 2.5rem;
494
- border: 1px solid #e2e8f0;
495
- border-radius: 0.375rem;
496
- background-color: #f8fafc;
497
- width: 240px;
498
- font-size: 0.875rem;
499
- }
500
-
501
- .search-icon {
502
- position: absolute;
503
- left: 0.75rem;
504
- top: 50%;
505
- transform: translateY(-50%);
506
- color: #94a3b8;
507
- }
508
-
509
- .notification-button {
510
- background: none;
511
- border: none;
512
- color: #64748b;
513
- cursor: pointer;
514
- padding: 0.5rem;
515
- border-radius: 0.375rem;
516
- position: relative;
517
- }
518
-
519
- .notification-button:hover {
520
- background-color: #f1f5f9;
521
- }
522
-
523
- .notification-badge {
524
- position: absolute;
525
- top: 0;
526
- right: 0;
527
- background-color: #ef4444;
528
- color: #fff;
529
- font-size: 0.75rem;
530
- width: 1rem;
531
- height: 1rem;
532
- border-radius: 9999px;
533
- display: flex;
534
- align-items: center;
535
- justify-content: center;
536
- }
537
-
538
- .user-dropdown {
539
- position: relative;
540
- }
541
-
542
- .user-button {
543
- display: flex;
544
- align-items: center;
545
- gap: 0.5rem;
546
- background: none;
547
- border: none;
548
- cursor: pointer;
549
- padding: 0.5rem;
550
- border-radius: 0.375rem;
551
- }
552
-
553
- .user-button:hover {
554
- background-color: #f1f5f9;
555
- }
556
-
557
- .user-avatar {
558
- width: 2rem;
559
- height: 2rem;
560
- border-radius: 9999px;
561
- object-fit: cover;
562
- }
563
-
564
- .user-avatar-placeholder {
565
- width: 2rem;
566
- height: 2rem;
567
- border-radius: 9999px;
568
- background-color: #3b82f6;
569
- color: #fff;
570
- display: flex;
571
- align-items: center;
572
- justify-content: center;
573
- font-weight: 600;
574
- }
575
-
576
- .user-name {
577
- color: #334155;
578
- font-weight: 500;
579
- }
580
-
581
- /* 页面内容 */
582
- .page-content {
583
- flex: 1;
584
- padding: 1.5rem;
585
- }
586
-
587
- .content-container {
588
- background-color: #fff;
589
- border-radius: 0.5rem;
590
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
591
- padding: 1.5rem;
592
- }
593
-
594
- /* 折叠状态 */
595
- .dashboard-container.collapsed .sidebar {
596
- width: 80px;
597
- }
598
-
599
- .dashboard-container.collapsed .logo-text,
600
- .dashboard-container.collapsed .nav-text,
601
- .dashboard-container.collapsed .nav-badge,
602
- .dashboard-container.collapsed .subnav-list {
603
- display: none;
604
- }
605
-
606
- .dashboard-container.collapsed .nav-link {
607
- justify-content: center;
608
- padding: 0.75rem;
609
- }
610
-
611
- .dashboard-container.collapsed .nav-icon {
612
- margin: 0;
613
- }
614
-
615
- .dashboard-container.collapsed .main-content {
616
- margin-left: 80px;
617
- }
618
-
619
- /* 响应式调整 */
620
- @media (max-width: 1024px) {
621
- .sidebar {
622
- transform: translateX(-100%);
623
- width: 260px;
624
- }
625
-
626
- .main-content {
627
- margin-left: 0;
628
- }
629
-
630
- .dashboard-container.collapsed .sidebar {
631
- transform: translateX(0);
632
- }
633
-
634
- .dashboard-container:not(.collapsed) .sidebar {
635
- transform: translateX(-100%);
636
- }
637
- }
638
-
639
- @media (max-width: 640px) {
640
- .search-box {
641
- display: none;
642
- }
643
-
644
- .user-name {
645
- display: none;
646
- }
647
-
648
- .top-navbar {
649
- padding: 0.75rem 1rem;
650
- }
651
-
652
- .page-content {
653
- padding: 1rem;
654
- }
655
-
656
- .content-container {
657
- padding: 1rem;
658
- }
659
- }
660
- </style>
290
+ /* 基础布局 */
291
+ .dashboard-layout {
292
+ min-height: 100vh;
293
+ background-color: #f5f7fa;
294
+ }
295
+
296
+ .dashboard-container {
297
+ display: flex;
298
+ min-height: 100vh;
299
+ }
300
+
301
+ /* 侧边栏 */
302
+ .sidebar {
303
+ width: 260px;
304
+ background-color: #1e293b;
305
+ color: #e2e8f0;
306
+ transition: width 0.3s ease;
307
+ display: flex;
308
+ flex-direction: column;
309
+ position: fixed;
310
+ top: 0;
311
+ left: 0;
312
+ bottom: 0;
313
+ z-index: 50;
314
+ overflow-y: auto;
315
+ }
316
+
317
+ .sidebar-header {
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: space-between;
321
+ padding: 1rem;
322
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
323
+ }
324
+
325
+ .logo {
326
+ display: flex;
327
+ align-items: center;
328
+ gap: 0.75rem;
329
+ color: #fff;
330
+ text-decoration: none;
331
+ }
332
+
333
+ .logo-icon {
334
+ font-size: 1.5rem;
335
+ }
336
+
337
+ .logo-text {
338
+ font-size: 1.25rem;
339
+ font-weight: 600;
340
+ white-space: nowrap;
341
+ }
342
+
343
+ .collapse-button {
344
+ background: none;
345
+ border: none;
346
+ color: #e2e8f0;
347
+ cursor: pointer;
348
+ padding: 0.5rem;
349
+ border-radius: 0.375rem;
350
+ }
351
+
352
+ .collapse-button:hover {
353
+ background-color: rgba(255, 255, 255, 0.1);
354
+ }
355
+
356
+ .sidebar-nav {
357
+ flex: 1;
358
+ padding: 1rem 0;
359
+ }
360
+
361
+ .nav-list {
362
+ list-style: none;
363
+ padding: 0;
364
+ margin: 0;
365
+ }
366
+
367
+ .nav-item {
368
+ margin-bottom: 0.25rem;
369
+ }
370
+
371
+ .nav-link {
372
+ display: flex;
373
+ align-items: center;
374
+ padding: 0.75rem 1rem;
375
+ color: #e2e8f0;
376
+ text-decoration: none;
377
+ border-radius: 0.375rem;
378
+ margin: 0 0.5rem;
379
+ gap: 0.75rem;
380
+ }
381
+
382
+ .nav-link:hover {
383
+ background-color: rgba(255, 255, 255, 0.1);
384
+ }
385
+
386
+ .nav-item.active .nav-link {
387
+ background-color: rgba(59, 130, 246, 0.5);
388
+ color: #fff;
389
+ }
390
+
391
+ .nav-icon {
392
+ font-size: 1.25rem;
393
+ width: 1.5rem;
394
+ text-align: center;
395
+ }
396
+
397
+ .nav-text {
398
+ flex: 1;
399
+ white-space: nowrap;
400
+ }
401
+
402
+ .nav-badge {
403
+ background-color: #ef4444;
404
+ color: #fff;
405
+ font-size: 0.75rem;
406
+ padding: 0.125rem 0.375rem;
407
+ border-radius: 9999px;
408
+ }
409
+
410
+ .subnav-list {
411
+ list-style: none;
412
+ padding: 0;
413
+ margin: 0.25rem 0 0.5rem 2.5rem;
414
+ }
415
+
416
+ .subnav-link {
417
+ display: flex;
418
+ align-items: center;
419
+ padding: 0.5rem 1rem;
420
+ color: #cbd5e1;
421
+ text-decoration: none;
422
+ border-radius: 0.375rem;
423
+ gap: 0.5rem;
424
+ font-size: 0.875rem;
425
+ }
426
+
427
+ .subnav-link:hover {
428
+ background-color: rgba(255, 255, 255, 0.05);
429
+ }
430
+
431
+ .subnav-item.active .subnav-link {
432
+ color: #3b82f6;
433
+ font-weight: 500;
434
+ }
435
+
436
+ .subnav-icon {
437
+ font-size: 1rem;
438
+ width: 1.25rem;
439
+ text-align: center;
440
+ }
441
+
442
+ /* 主内容区 */
443
+ .main-content {
444
+ flex: 1;
445
+ margin-left: 260px;
446
+ transition: margin-left 0.3s ease;
447
+ display: flex;
448
+ flex-direction: column;
449
+ min-height: 100vh;
450
+ }
451
+
452
+ /* 顶部导航栏 */
453
+ .top-navbar {
454
+ display: flex;
455
+ justify-content: space-between;
456
+ align-items: center;
457
+ padding: 0.75rem 1.5rem;
458
+ background-color: #fff;
459
+ border-bottom: 1px solid #e2e8f0;
460
+ height: 64px;
461
+ position: sticky;
462
+ top: 0;
463
+ z-index: 40;
464
+ }
465
+
466
+ .navbar-left {
467
+ display: flex;
468
+ align-items: center;
469
+ gap: 1rem;
470
+ }
471
+
472
+ .menu-button {
473
+ background: none;
474
+ border: none;
475
+ color: #64748b;
476
+ cursor: pointer;
477
+ padding: 0.5rem;
478
+ border-radius: 0.375rem;
479
+ display: flex;
480
+ align-items: center;
481
+ justify-content: center;
482
+ }
483
+
484
+ .menu-button:hover {
485
+ background-color: #f1f5f9;
486
+ }
487
+
488
+ .breadcrumb {
489
+ color: #64748b;
490
+ font-size: 0.875rem;
491
+ }
492
+
493
+ .navbar-right {
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 1rem;
497
+ }
498
+
499
+ .search-box {
500
+ position: relative;
501
+ }
502
+
503
+ .search-input {
504
+ padding: 0.5rem 1rem 0.5rem 2.5rem;
505
+ border: 1px solid #e2e8f0;
506
+ border-radius: 0.375rem;
507
+ background-color: #f8fafc;
508
+ width: 240px;
509
+ font-size: 0.875rem;
510
+ }
511
+
512
+ .search-icon {
513
+ position: absolute;
514
+ left: 0.75rem;
515
+ top: 50%;
516
+ transform: translateY(-50%);
517
+ color: #94a3b8;
518
+ }
519
+
520
+ .notification-button {
521
+ background: none;
522
+ border: none;
523
+ color: #64748b;
524
+ cursor: pointer;
525
+ padding: 0.5rem;
526
+ border-radius: 0.375rem;
527
+ position: relative;
528
+ }
529
+
530
+ .notification-button:hover {
531
+ background-color: #f1f5f9;
532
+ }
533
+
534
+ .notification-badge {
535
+ position: absolute;
536
+ top: 0;
537
+ right: 0;
538
+ background-color: #ef4444;
539
+ color: #fff;
540
+ font-size: 0.75rem;
541
+ width: 1rem;
542
+ height: 1rem;
543
+ border-radius: 9999px;
544
+ display: flex;
545
+ align-items: center;
546
+ justify-content: center;
547
+ }
548
+
549
+ .user-dropdown {
550
+ position: relative;
551
+ }
552
+
553
+ .user-button {
554
+ display: flex;
555
+ align-items: center;
556
+ gap: 0.5rem;
557
+ background: none;
558
+ border: none;
559
+ cursor: pointer;
560
+ padding: 0.5rem;
561
+ border-radius: 0.375rem;
562
+ }
563
+
564
+ .user-button:hover {
565
+ background-color: #f1f5f9;
566
+ }
567
+
568
+ .user-avatar {
569
+ width: 2rem;
570
+ height: 2rem;
571
+ border-radius: 9999px;
572
+ object-fit: cover;
573
+ }
574
+
575
+ .user-avatar-placeholder {
576
+ width: 2rem;
577
+ height: 2rem;
578
+ border-radius: 9999px;
579
+ background-color: #3b82f6;
580
+ color: #fff;
581
+ display: flex;
582
+ align-items: center;
583
+ justify-content: center;
584
+ font-weight: 600;
585
+ }
586
+
587
+ .user-name {
588
+ color: #334155;
589
+ font-weight: 500;
590
+ }
591
+
592
+ /* 页面内容 */
593
+ .page-content {
594
+ flex: 1;
595
+ padding: 1.5rem;
596
+ }
597
+
598
+ .content-container {
599
+ background-color: #fff;
600
+ border-radius: 0.5rem;
601
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
602
+ padding: 1.5rem;
603
+ }
604
+
605
+ /* 折叠状态 */
606
+ .dashboard-container.collapsed .sidebar {
607
+ width: 80px;
608
+ }
609
+
610
+ .dashboard-container.collapsed .logo-text,
611
+ .dashboard-container.collapsed .nav-text,
612
+ .dashboard-container.collapsed .nav-badge,
613
+ .dashboard-container.collapsed .subnav-list {
614
+ display: none;
615
+ }
616
+
617
+ .dashboard-container.collapsed .nav-link {
618
+ justify-content: center;
619
+ padding: 0.75rem;
620
+ }
621
+
622
+ .dashboard-container.collapsed .nav-icon {
623
+ margin: 0;
624
+ }
625
+
626
+ .dashboard-container.collapsed .main-content {
627
+ margin-left: 80px;
628
+ }
629
+
630
+ /* 响应式调整 */
631
+ @media (max-width: 1024px) {
632
+ .sidebar {
633
+ transform: translateX(-100%);
634
+ width: 260px;
635
+ }
636
+
637
+ .main-content {
638
+ margin-left: 0;
639
+ }
640
+
641
+ .dashboard-container.collapsed .sidebar {
642
+ transform: translateX(0);
643
+ }
644
+
645
+ .dashboard-container:not(.collapsed) .sidebar {
646
+ transform: translateX(-100%);
647
+ }
648
+ }
649
+
650
+ @media (max-width: 640px) {
651
+ .search-box {
652
+ display: none;
653
+ }
654
+
655
+ .user-name {
656
+ display: none;
657
+ }
658
+
659
+ .top-navbar {
660
+ padding: 0.75rem 1rem;
661
+ }
662
+
663
+ .page-content {
664
+ padding: 1rem;
665
+ }
666
+
667
+ .content-container {
668
+ padding: 1rem;
669
+ }
670
+ }
671
+ </style>