collabdocchat 2.1.2 → 2.1.3

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,800 +1,800 @@
1
- /**
2
- * 主题管理系统
3
- * 支持多种主题切换和自定义主题
4
- */
5
-
6
- export class ThemeManager {
7
- constructor() {
8
- this.currentTheme = this.loadTheme();
9
- this.themes = {
10
- default: {
11
- name: '默认深色',
12
- colors: {
13
- primary: '#6366f1',
14
- primaryDark: '#4f46e5',
15
- secondary: '#8b5cf6',
16
- success: '#10b981',
17
- danger: '#ef4444',
18
- warning: '#f59e0b',
19
- bgDark: '#0f172a',
20
- bgCard: '#1e293b',
21
- bgHover: '#334155',
22
- textPrimary: '#f1f5f9',
23
- textSecondary: '#94a3b8',
24
- border: '#334155'
25
- }
26
- },
27
- light: {
28
- name: '明亮模式',
29
- colors: {
30
- primary: '#6366f1',
31
- primaryDark: '#4f46e5',
32
- secondary: '#8b5cf6',
33
- success: '#10b981',
34
- danger: '#ef4444',
35
- warning: '#f59e0b',
36
- bgDark: '#ffffff',
37
- bgCard: '#f8fafc',
38
- bgHover: '#f1f5f9',
39
- textPrimary: '#0f172a',
40
- textSecondary: '#64748b',
41
- border: '#e2e8f0'
42
- }
43
- },
44
- ocean: {
45
- name: '海洋�?,
46
- colors: {
47
- primary: '#0ea5e9',
48
- primaryDark: '#0284c7',
49
- secondary: '#06b6d4',
50
- success: '#10b981',
51
- danger: '#ef4444',
52
- warning: '#f59e0b',
53
- bgDark: '#0c4a6e',
54
- bgCard: '#075985',
55
- bgHover: '#0369a1',
56
- textPrimary: '#f0f9ff',
57
- textSecondary: '#bae6fd',
58
- border: '#0369a1'
59
- }
60
- },
61
- forest: {
62
- name: '森林�?,
63
- colors: {
64
- primary: '#22c55e',
65
- primaryDark: '#16a34a',
66
- secondary: '#84cc16',
67
- success: '#10b981',
68
- danger: '#ef4444',
69
- warning: '#f59e0b',
70
- bgDark: '#14532d',
71
- bgCard: '#166534',
72
- bgHover: '#15803d',
73
- textPrimary: '#f0fdf4',
74
- textSecondary: '#bbf7d0',
75
- border: '#15803d'
76
- }
77
- },
78
- sunset: {
79
- name: '日落�?,
80
- colors: {
81
- primary: '#f97316',
82
- primaryDark: '#ea580c',
83
- secondary: '#fb923c',
84
- success: '#10b981',
85
- danger: '#ef4444',
86
- warning: '#f59e0b',
87
- bgDark: '#7c2d12',
88
- bgCard: '#9a3412',
89
- bgHover: '#c2410c',
90
- textPrimary: '#fff7ed',
91
- textSecondary: '#fed7aa',
92
- border: '#c2410c'
93
- }
94
- },
95
- purple: {
96
- name: '紫罗�?,
97
- colors: {
98
- primary: '#a855f7',
99
- primaryDark: '#9333ea',
100
- secondary: '#c084fc',
101
- success: '#10b981',
102
- danger: '#ef4444',
103
- warning: '#f59e0b',
104
- bgDark: '#581c87',
105
- bgCard: '#6b21a8',
106
- bgHover: '#7e22ce',
107
- textPrimary: '#faf5ff',
108
- textSecondary: '#e9d5ff',
109
- border: '#7e22ce'
110
- }
111
- },
112
- midnight: {
113
- name: '午夜�?,
114
- colors: {
115
- primary: '#3b82f6',
116
- primaryDark: '#2563eb',
117
- secondary: '#60a5fa',
118
- success: '#10b981',
119
- danger: '#ef4444',
120
- warning: '#f59e0b',
121
- bgDark: '#000000',
122
- bgCard: '#1a1a1a',
123
- bgHover: '#2d2d2d',
124
- textPrimary: '#ffffff',
125
- textSecondary: '#a3a3a3',
126
- border: '#404040'
127
- }
128
- },
129
- rose: {
130
- name: '玫瑰�?,
131
- colors: {
132
- primary: '#f43f5e',
133
- primaryDark: '#e11d48',
134
- secondary: '#fb7185',
135
- success: '#10b981',
136
- danger: '#ef4444',
137
- warning: '#f59e0b',
138
- bgDark: '#881337',
139
- bgCard: '#9f1239',
140
- bgHover: '#be123c',
141
- textPrimary: '#fff1f2',
142
- textSecondary: '#fecdd3',
143
- border: '#be123c'
144
- }
145
- }
146
- };
147
- }
148
-
149
- /**
150
- * 加载保存的主�? */
151
- loadTheme() {
152
- return localStorage.getItem('theme') || 'default';
153
- }
154
-
155
- /**
156
- * 保存主题设置
157
- */
158
- saveTheme(themeName) {
159
- localStorage.setItem('theme', themeName);
160
- }
161
-
162
- /**
163
- * 应用主题
164
- */
165
- applyTheme(themeName) {
166
- if (!this.themes[themeName]) {
167
- console.error('主题不存�?', themeName);
168
- return;
169
- }
170
-
171
- const theme = this.themes[themeName];
172
- const root = document.documentElement;
173
-
174
- // 应用CSS变量
175
- Object.entries(theme.colors).forEach(([key, value]) => {
176
- const cssVar = key.replace(/([A-Z])/g, '-$1').toLowerCase();
177
- root.style.setProperty(`--${cssVar}`, value);
178
- });
179
-
180
- // 保存当前主题
181
- this.currentTheme = themeName;
182
- this.saveTheme(themeName);
183
-
184
- // 触发主题变更事件
185
- window.dispatchEvent(new CustomEvent('themeChanged', {
186
- detail: { theme: themeName }
187
- }));
188
- }
189
-
190
- /**
191
- * 获取当前主题
192
- */
193
- getCurrentTheme() {
194
- return this.currentTheme;
195
- }
196
-
197
- /**
198
- * 获取所有主�? */
199
- getAllThemes() {
200
- return Object.entries(this.themes).map(([key, theme]) => ({
201
- id: key,
202
- name: theme.name,
203
- colors: theme.colors
204
- }));
205
- }
206
-
207
- /**
208
- * 创建自定义主�? */
209
- createCustomTheme(name, colors) {
210
- const themeId = `custom_${Date.now()}`;
211
- this.themes[themeId] = {
212
- name: name,
213
- colors: colors
214
- };
215
-
216
- // 保存到localStorage
217
- this.saveCustomThemes();
218
-
219
- return themeId;
220
- }
221
-
222
- /**
223
- * 保存自定义主�? */
224
- saveCustomThemes() {
225
- const customThemes = {};
226
- Object.entries(this.themes).forEach(([key, theme]) => {
227
- if (key.startsWith('custom_')) {
228
- customThemes[key] = theme;
229
- }
230
- });
231
- localStorage.setItem('customThemes', JSON.stringify(customThemes));
232
- }
233
-
234
- /**
235
- * 加载自定义主�? */
236
- loadCustomThemes() {
237
- const saved = localStorage.getItem('customThemes');
238
- if (saved) {
239
- try {
240
- const customThemes = JSON.parse(saved);
241
- Object.assign(this.themes, customThemes);
242
- } catch (error) {
243
- console.error('加载自定义主题失�?', error);
244
- }
245
- }
246
- }
247
-
248
- /**
249
- * 删除自定义主�? */
250
- deleteCustomTheme(themeId) {
251
- if (!themeId.startsWith('custom_')) {
252
- console.error('只能删除自定义主�?);
253
- return false;
254
- }
255
-
256
- delete this.themes[themeId];
257
- this.saveCustomThemes();
258
-
259
- // 如果删除的是当前主题,切换到默认主题
260
- if (this.currentTheme === themeId) {
261
- this.applyTheme('default');
262
- }
263
-
264
- return true;
265
- }
266
-
267
- /**
268
- * 渲染主题选择器(美化版)
269
- */
270
- renderThemeSelector(container) {
271
- const themes = this.getAllThemes();
272
-
273
- container.innerHTML = `
274
- <div class="theme-selector-modern">
275
- <div class="theme-header-modern">
276
- <div class="theme-header-content">
277
- <h2 class="theme-title-modern">🎨 主题设置</h2>
278
- <p class="theme-subtitle-modern">选择您喜欢的主题风格,让界面更加个性化</p>
279
- </div>
280
- <button class="btn-create-theme-modern" id="createCustomThemeBtn">
281
- <span class="btn-icon">�?/span>
282
- <span>创建自定义主�?/span>
283
- </button>
284
- </div>
285
-
286
- <div class="theme-grid-modern">
287
- ${themes.map(theme => `
288
- <div class="theme-card-modern ${this.currentTheme === theme.id ? 'active' : ''}"
289
- data-theme="${theme.id}">
290
- <div class="theme-preview-modern" style="background: linear-gradient(135deg, ${theme.colors.bgDark} 0%, ${theme.colors.bgCard} 100%);">
291
- <div class="theme-preview-header" style="background: ${theme.colors.bgCard};">
292
- <div class="preview-dot" style="background: ${theme.colors.danger};"></div>
293
- <div class="preview-dot" style="background: ${theme.colors.warning};"></div>
294
- <div class="preview-dot" style="background: ${theme.colors.success};"></div>
295
- </div>
296
- <div class="theme-preview-content">
297
- <div class="preview-sidebar" style="background: ${theme.colors.bgCard};"></div>
298
- <div class="preview-main">
299
- <div class="preview-card" style="background: ${theme.colors.bgCard};">
300
- <div class="preview-line" style="background: ${theme.colors.primary};"></div>
301
- <div class="preview-line" style="background: ${theme.colors.textSecondary}; width: 80%;"></div>
302
- <div class="preview-line" style="background: ${theme.colors.textSecondary}; width: 60%;"></div>
303
- </div>
304
- <div class="preview-button" style="background: linear-gradient(135deg, ${theme.colors.primary} 0%, ${theme.colors.secondary} 100%);"></div>
305
- </div>
306
- </div>
307
- <div class="theme-colors-modern">
308
- <span class="color-dot" style="background: ${theme.colors.primary};" title="主色�?></span>
309
- <span class="color-dot" style="background: ${theme.colors.secondary};" title="次要�?></span>
310
- <span class="color-dot" style="background: ${theme.colors.success};" title="成功�?></span>
311
- <span class="color-dot" style="background: ${theme.colors.danger};" title="危险�?></span>
312
- </div>
313
- </div>
314
- <div class="theme-info-modern">
315
- <div class="theme-name-modern">${theme.name}</div>
316
- ${this.currentTheme === theme.id ?
317
- '<div class="theme-badge-modern">�?当前使用</div>' :
318
- '<div class="theme-action-modern">点击应用</div>'}
319
- </div>
320
- ${theme.id.startsWith('custom_') ?
321
- `<button class="btn-delete-theme-modern" data-theme="${theme.id}" title="删除主题">
322
- <span>🗑�?/span>
323
- </button>` :
324
- ''}
325
- </div>
326
- `).join('')}
327
- </div>
328
- </div>
329
- `;
330
-
331
- // 添加样式
332
- this.addThemeSelectorStyles();
333
-
334
- // 添加事件监听
335
- container.querySelectorAll('.theme-card-modern').forEach(card => {
336
- card.addEventListener('click', (e) => {
337
- if (!e.target.closest('.btn-delete-theme-modern')) {
338
- const themeId = card.dataset.theme;
339
- this.applyTheme(themeId);
340
-
341
- // 更新选中状�? container.querySelectorAll('.theme-card-modern').forEach(c => c.classList.remove('active'));
342
- card.classList.add('active');
343
-
344
- // 更新徽章文字
345
- container.querySelectorAll('.theme-badge-modern, .theme-action-modern').forEach(badge => {
346
- badge.className = 'theme-action-modern';
347
- badge.textContent = '点击应用';
348
- });
349
-
350
- const badge = card.querySelector('.theme-badge-modern, .theme-action-modern');
351
- if (badge) {
352
- badge.className = 'theme-badge-modern';
353
- badge.textContent = '�?当前使用';
354
- }
355
- }
356
- });
357
- });
358
-
359
- // 删除自定义主�? container.querySelectorAll('.btn-delete-theme-modern').forEach(btn => {
360
- btn.addEventListener('click', (e) => {
361
- e.stopPropagation();
362
- const themeId = btn.dataset.theme;
363
- if (confirm('确定要删除这个主题吗�?)) {
364
- this.deleteCustomTheme(themeId);
365
- this.renderThemeSelector(container);
366
- }
367
- });
368
- });
369
-
370
- // 创建自定义主�? const createBtn = container.querySelector('#createCustomThemeBtn');
371
- if (createBtn) {
372
- createBtn.addEventListener('click', () => {
373
- this.showCustomThemeDialog(container);
374
- });
375
- }
376
- }
377
-
378
- /**
379
- * 显示自定义主题对话框
380
- */
381
- showCustomThemeDialog(container) {
382
- const dialog = document.createElement('div');
383
- dialog.className = 'modal';
384
- dialog.innerHTML = `
385
- <div class="modal-content" style="max-width: 600px;">
386
- <h3>创建自定义主�?/h3>
387
- <form id="customThemeForm">
388
- <div class="form-group">
389
- <label>主题名称</label>
390
- <input type="text" name="name" placeholder="我的主题" required>
391
- </div>
392
- <div class="form-group">
393
- <label>主色�?/label>
394
- <input type="color" name="primary" value="#6366f1">
395
- </div>
396
- <div class="form-group">
397
- <label>次要�?/label>
398
- <input type="color" name="secondary" value="#8b5cf6">
399
- </div>
400
- <div class="form-group">
401
- <label>成功�?/label>
402
- <input type="color" name="success" value="#10b981">
403
- </div>
404
- <div class="form-group">
405
- <label>危险�?/label>
406
- <input type="color" name="danger" value="#ef4444">
407
- </div>
408
- <div class="form-group">
409
- <label>背景�?/label>
410
- <input type="color" name="bgDark" value="#0f172a">
411
- </div>
412
- <div class="form-group">
413
- <label>卡片背景</label>
414
- <input type="color" name="bgCard" value="#1e293b">
415
- </div>
416
- <div class="form-group">
417
- <label>文字颜色</label>
418
- <input type="color" name="textPrimary" value="#f1f5f9">
419
- </div>
420
- <div style="display: flex; gap: 10px; margin-top: 20px;">
421
- <button type="submit" class="btn-primary">创建</button>
422
- <button type="button" class="btn-secondary" id="cancelCustomTheme">取消</button>
423
- </div>
424
- </form>
425
- </div>
426
- `;
427
-
428
- document.body.appendChild(dialog);
429
-
430
- // 取消按钮
431
- dialog.querySelector('#cancelCustomTheme').addEventListener('click', () => {
432
- dialog.remove();
433
- });
434
-
435
- // 提交表单
436
- dialog.querySelector('#customThemeForm').addEventListener('submit', (e) => {
437
- e.preventDefault();
438
- const formData = new FormData(e.target);
439
-
440
- const colors = {
441
- primary: formData.get('primary'),
442
- primaryDark: this.darkenColor(formData.get('primary'), 10),
443
- secondary: formData.get('secondary'),
444
- success: formData.get('success'),
445
- danger: formData.get('danger'),
446
- warning: '#f59e0b',
447
- bgDark: formData.get('bgDark'),
448
- bgCard: formData.get('bgCard'),
449
- bgHover: this.lightenColor(formData.get('bgCard'), 10),
450
- textPrimary: formData.get('textPrimary'),
451
- textSecondary: this.adjustOpacity(formData.get('textPrimary'), 0.7),
452
- border: this.lightenColor(formData.get('bgCard'), 20)
453
- };
454
-
455
- const themeId = this.createCustomTheme(formData.get('name'), colors);
456
- this.applyTheme(themeId);
457
-
458
- dialog.remove();
459
- this.renderThemeSelector(container);
460
- });
461
- }
462
-
463
- /**
464
- * 加深颜色
465
- */
466
- darkenColor(color, percent) {
467
- const num = parseInt(color.replace('#', ''), 16);
468
- const amt = Math.round(2.55 * percent);
469
- const R = (num >> 16) - amt;
470
- const G = (num >> 8 & 0x00FF) - amt;
471
- const B = (num & 0x0000FF) - amt;
472
- return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
473
- (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
474
- (B < 255 ? B < 1 ? 0 : B : 255))
475
- .toString(16).slice(1);
476
- }
477
-
478
- /**
479
- * 提亮颜色
480
- */
481
- lightenColor(color, percent) {
482
- const num = parseInt(color.replace('#', ''), 16);
483
- const amt = Math.round(2.55 * percent);
484
- const R = (num >> 16) + amt;
485
- const G = (num >> 8 & 0x00FF) + amt;
486
- const B = (num & 0x0000FF) + amt;
487
- return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
488
- (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
489
- (B < 255 ? B < 1 ? 0 : B : 255))
490
- .toString(16).slice(1);
491
- }
492
-
493
- /**
494
- * 调整透明�? */
495
- adjustOpacity(color, opacity) {
496
- const num = parseInt(color.replace('#', ''), 16);
497
- const R = num >> 16;
498
- const G = num >> 8 & 0x00FF;
499
- const B = num & 0x0000FF;
500
- return `rgba(${R}, ${G}, ${B}, ${opacity})`;
501
- }
502
-
503
- /**
504
- * 添加主题选择器样�? */
505
- addThemeSelectorStyles() {
506
- if (document.getElementById('theme-selector-modern-styles')) return;
507
-
508
- const style = document.createElement('style');
509
- style.id = 'theme-selector-modern-styles';
510
- style.textContent = `
511
- .theme-selector-modern {
512
- padding: 0;
513
- animation: fadeInUp 0.5s ease;
514
- }
515
-
516
- .theme-header-modern {
517
- display: flex;
518
- justify-content: space-between;
519
- align-items: center;
520
- margin-bottom: 40px;
521
- padding: 30px;
522
- background: linear-gradient(135deg, var(--bg-card) 0%, rgba(99,102,241,0.05) 100%);
523
- border-radius: 20px;
524
- border: 1px solid var(--border);
525
- }
526
-
527
- .theme-header-content {
528
- flex: 1;
529
- }
530
-
531
- .theme-title-modern {
532
- margin: 0 0 8px;
533
- font-size: 32px;
534
- font-weight: 800;
535
- color: var(--text-primary);
536
- display: flex;
537
- align-items: center;
538
- gap: 12px;
539
- }
540
-
541
- .theme-subtitle-modern {
542
- margin: 0;
543
- font-size: 15px;
544
- color: var(--text-secondary);
545
- font-weight: 500;
546
- }
547
-
548
- .btn-create-theme-modern {
549
- padding: 14px 28px;
550
- background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
551
- color: white;
552
- border: none;
553
- border-radius: 12px;
554
- font-size: 15px;
555
- font-weight: 600;
556
- cursor: pointer;
557
- transition: all 0.3s ease;
558
- display: flex;
559
- align-items: center;
560
- gap: 8px;
561
- box-shadow: 0 4px 12px rgba(99,102,241,0.3);
562
- }
563
-
564
- .btn-create-theme-modern:hover {
565
- transform: translateY(-3px);
566
- box-shadow: 0 8px 24px rgba(99,102,241,0.4);
567
- }
568
-
569
- .btn-icon {
570
- font-size: 18px;
571
- }
572
-
573
- .theme-grid-modern {
574
- display: grid;
575
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
576
- gap: 24px;
577
- padding: 0 30px 30px;
578
- }
579
-
580
- .theme-card-modern {
581
- background: var(--bg-card);
582
- border: 2px solid var(--border);
583
- border-radius: 16px;
584
- overflow: hidden;
585
- cursor: pointer;
586
- transition: all 0.3s ease;
587
- position: relative;
588
- }
589
-
590
- .theme-card-modern:hover {
591
- transform: translateY(-8px);
592
- box-shadow: 0 12px 32px rgba(0,0,0,0.3);
593
- border-color: var(--primary);
594
- }
595
-
596
- .theme-card-modern.active {
597
- border-color: var(--primary);
598
- box-shadow: 0 0 0 3px rgba(99,102,241,0.2);
599
- }
600
-
601
- .theme-card-modern.active::before {
602
- content: '';
603
- position: absolute;
604
- top: 0;
605
- left: 0;
606
- right: 0;
607
- height: 4px;
608
- background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%);
609
- z-index: 1;
610
- }
611
-
612
- .theme-preview-modern {
613
- height: 200px;
614
- padding: 12px;
615
- display: flex;
616
- flex-direction: column;
617
- gap: 8px;
618
- position: relative;
619
- }
620
-
621
- .theme-preview-header {
622
- height: 24px;
623
- border-radius: 8px 8px 0 0;
624
- display: flex;
625
- align-items: center;
626
- gap: 6px;
627
- padding: 0 10px;
628
- }
629
-
630
- .preview-dot {
631
- width: 10px;
632
- height: 10px;
633
- border-radius: 50%;
634
- }
635
-
636
- .theme-preview-content {
637
- flex: 1;
638
- display: flex;
639
- gap: 8px;
640
- }
641
-
642
- .preview-sidebar {
643
- width: 50px;
644
- border-radius: 8px;
645
- }
646
-
647
- .preview-main {
648
- flex: 1;
649
- display: flex;
650
- flex-direction: column;
651
- gap: 8px;
652
- }
653
-
654
- .preview-card {
655
- flex: 1;
656
- border-radius: 8px;
657
- padding: 12px;
658
- display: flex;
659
- flex-direction: column;
660
- gap: 6px;
661
- }
662
-
663
- .preview-line {
664
- height: 6px;
665
- border-radius: 3px;
666
- width: 100%;
667
- }
668
-
669
- .preview-button {
670
- height: 24px;
671
- border-radius: 6px;
672
- }
673
-
674
- .theme-colors-modern {
675
- display: flex;
676
- gap: 8px;
677
- justify-content: center;
678
- padding: 8px 0;
679
- }
680
-
681
- .color-dot {
682
- width: 24px;
683
- height: 24px;
684
- border-radius: 50%;
685
- border: 2px solid rgba(255,255,255,0.2);
686
- transition: all 0.3s ease;
687
- cursor: pointer;
688
- }
689
-
690
- .color-dot:hover {
691
- transform: scale(1.2);
692
- border-color: rgba(255,255,255,0.5);
693
- }
694
-
695
- .theme-info-modern {
696
- padding: 16px 20px;
697
- background: var(--bg-dark);
698
- display: flex;
699
- justify-content: space-between;
700
- align-items: center;
701
- }
702
-
703
- .theme-name-modern {
704
- font-size: 16px;
705
- font-weight: 700;
706
- color: var(--text-primary);
707
- }
708
-
709
- .theme-badge-modern {
710
- padding: 4px 12px;
711
- background: linear-gradient(135deg, var(--success) 0%, #059669 100%);
712
- color: white;
713
- border-radius: 12px;
714
- font-size: 12px;
715
- font-weight: 600;
716
- }
717
-
718
- .theme-action-modern {
719
- padding: 4px 12px;
720
- background: var(--bg-hover);
721
- color: var(--text-secondary);
722
- border-radius: 12px;
723
- font-size: 12px;
724
- font-weight: 600;
725
- }
726
-
727
- .btn-delete-theme-modern {
728
- position: absolute;
729
- top: 16px;
730
- right: 16px;
731
- width: 36px;
732
- height: 36px;
733
- background: rgba(239,68,68,0.9);
734
- border: none;
735
- border-radius: 50%;
736
- color: white;
737
- font-size: 16px;
738
- cursor: pointer;
739
- transition: all 0.3s ease;
740
- display: flex;
741
- align-items: center;
742
- justify-content: center;
743
- z-index: 2;
744
- opacity: 0;
745
- }
746
-
747
- .theme-card-modern:hover .btn-delete-theme-modern {
748
- opacity: 1;
749
- }
750
-
751
- .btn-delete-theme-modern:hover {
752
- background: var(--danger);
753
- transform: scale(1.1) rotate(10deg);
754
- }
755
-
756
- @keyframes fadeInUp {
757
- from {
758
- opacity: 0;
759
- transform: translateY(20px);
760
- }
761
- to {
762
- opacity: 1;
763
- transform: translateY(0);
764
- }
765
- }
766
-
767
- @media (max-width: 768px) {
768
- .theme-header-modern {
769
- flex-direction: column;
770
- gap: 20px;
771
- align-items: flex-start;
772
- }
773
-
774
- .btn-create-theme-modern {
775
- width: 100%;
776
- justify-content: center;
777
- }
778
-
779
- .theme-grid-modern {
780
- grid-template-columns: 1fr;
781
- padding: 0 20px 20px;
782
- }
783
- }
784
- `;
785
- document.head.appendChild(style);
786
- }
787
-
788
- /**
789
- * 初始化主�? */
790
- init() {
791
- this.loadCustomThemes();
792
- this.applyTheme(this.currentTheme);
793
- }
794
- }
795
-
796
- // 导出单例
797
- export const themeManager = new ThemeManager();
798
-
799
-
1
+ /**
2
+ * 主题管理系统
3
+ * 支持多种主题切换和自定义主题
4
+ */
5
+
6
+ export class ThemeManager {
7
+ constructor() {
8
+ this.currentTheme = this.loadTheme();
9
+ this.themes = {
10
+ default: {
11
+ name: '默认深色',
12
+ colors: {
13
+ primary: '#6366f1',
14
+ primaryDark: '#4f46e5',
15
+ secondary: '#8b5cf6',
16
+ success: '#10b981',
17
+ danger: '#ef4444',
18
+ warning: '#f59e0b',
19
+ bgDark: '#0f172a',
20
+ bgCard: '#1e293b',
21
+ bgHover: '#334155',
22
+ textPrimary: '#f1f5f9',
23
+ textSecondary: '#94a3b8',
24
+ border: '#334155'
25
+ }
26
+ },
27
+ light: {
28
+ name: '明亮模式',
29
+ colors: {
30
+ primary: '#6366f1',
31
+ primaryDark: '#4f46e5',
32
+ secondary: '#8b5cf6',
33
+ success: '#10b981',
34
+ danger: '#ef4444',
35
+ warning: '#f59e0b',
36
+ bgDark: '#ffffff',
37
+ bgCard: '#f8fafc',
38
+ bgHover: '#f1f5f9',
39
+ textPrimary: '#0f172a',
40
+ textSecondary: '#64748b',
41
+ border: '#e2e8f0'
42
+ }
43
+ },
44
+ ocean: {
45
+ name: '海洋�?,
46
+ colors: {
47
+ primary: '#0ea5e9',
48
+ primaryDark: '#0284c7',
49
+ secondary: '#06b6d4',
50
+ success: '#10b981',
51
+ danger: '#ef4444',
52
+ warning: '#f59e0b',
53
+ bgDark: '#0c4a6e',
54
+ bgCard: '#075985',
55
+ bgHover: '#0369a1',
56
+ textPrimary: '#f0f9ff',
57
+ textSecondary: '#bae6fd',
58
+ border: '#0369a1'
59
+ }
60
+ },
61
+ forest: {
62
+ name: '森林�?,
63
+ colors: {
64
+ primary: '#22c55e',
65
+ primaryDark: '#16a34a',
66
+ secondary: '#84cc16',
67
+ success: '#10b981',
68
+ danger: '#ef4444',
69
+ warning: '#f59e0b',
70
+ bgDark: '#14532d',
71
+ bgCard: '#166534',
72
+ bgHover: '#15803d',
73
+ textPrimary: '#f0fdf4',
74
+ textSecondary: '#bbf7d0',
75
+ border: '#15803d'
76
+ }
77
+ },
78
+ sunset: {
79
+ name: '日落�?,
80
+ colors: {
81
+ primary: '#f97316',
82
+ primaryDark: '#ea580c',
83
+ secondary: '#fb923c',
84
+ success: '#10b981',
85
+ danger: '#ef4444',
86
+ warning: '#f59e0b',
87
+ bgDark: '#7c2d12',
88
+ bgCard: '#9a3412',
89
+ bgHover: '#c2410c',
90
+ textPrimary: '#fff7ed',
91
+ textSecondary: '#fed7aa',
92
+ border: '#c2410c'
93
+ }
94
+ },
95
+ purple: {
96
+ name: '紫罗�?,
97
+ colors: {
98
+ primary: '#a855f7',
99
+ primaryDark: '#9333ea',
100
+ secondary: '#c084fc',
101
+ success: '#10b981',
102
+ danger: '#ef4444',
103
+ warning: '#f59e0b',
104
+ bgDark: '#581c87',
105
+ bgCard: '#6b21a8',
106
+ bgHover: '#7e22ce',
107
+ textPrimary: '#faf5ff',
108
+ textSecondary: '#e9d5ff',
109
+ border: '#7e22ce'
110
+ }
111
+ },
112
+ midnight: {
113
+ name: '午夜�?,
114
+ colors: {
115
+ primary: '#3b82f6',
116
+ primaryDark: '#2563eb',
117
+ secondary: '#60a5fa',
118
+ success: '#10b981',
119
+ danger: '#ef4444',
120
+ warning: '#f59e0b',
121
+ bgDark: '#000000',
122
+ bgCard: '#1a1a1a',
123
+ bgHover: '#2d2d2d',
124
+ textPrimary: '#ffffff',
125
+ textSecondary: '#a3a3a3',
126
+ border: '#404040'
127
+ }
128
+ },
129
+ rose: {
130
+ name: '玫瑰�?,
131
+ colors: {
132
+ primary: '#f43f5e',
133
+ primaryDark: '#e11d48',
134
+ secondary: '#fb7185',
135
+ success: '#10b981',
136
+ danger: '#ef4444',
137
+ warning: '#f59e0b',
138
+ bgDark: '#881337',
139
+ bgCard: '#9f1239',
140
+ bgHover: '#be123c',
141
+ textPrimary: '#fff1f2',
142
+ textSecondary: '#fecdd3',
143
+ border: '#be123c'
144
+ }
145
+ }
146
+ };
147
+ }
148
+
149
+ /**
150
+ * 加载保存的主�? */
151
+ loadTheme() {
152
+ return localStorage.getItem('theme') || 'default';
153
+ }
154
+
155
+ /**
156
+ * 保存主题设置
157
+ */
158
+ saveTheme(themeName) {
159
+ localStorage.setItem('theme', themeName);
160
+ }
161
+
162
+ /**
163
+ * 应用主题
164
+ */
165
+ applyTheme(themeName) {
166
+ if (!this.themes[themeName]) {
167
+ console.error('主题不存�?', themeName);
168
+ return;
169
+ }
170
+
171
+ const theme = this.themes[themeName];
172
+ const root = document.documentElement;
173
+
174
+ // 应用CSS变量
175
+ Object.entries(theme.colors).forEach(([key, value]) => {
176
+ const cssVar = key.replace(/([A-Z])/g, '-$1').toLowerCase();
177
+ root.style.setProperty(`--${cssVar}`, value);
178
+ });
179
+
180
+ // 保存当前主题
181
+ this.currentTheme = themeName;
182
+ this.saveTheme(themeName);
183
+
184
+ // 触发主题变更事件
185
+ window.dispatchEvent(new CustomEvent('themeChanged', {
186
+ detail: { theme: themeName }
187
+ }));
188
+ }
189
+
190
+ /**
191
+ * 获取当前主题
192
+ */
193
+ getCurrentTheme() {
194
+ return this.currentTheme;
195
+ }
196
+
197
+ /**
198
+ * 获取所有主�? */
199
+ getAllThemes() {
200
+ return Object.entries(this.themes).map(([key, theme]) => ({
201
+ id: key,
202
+ name: theme.name,
203
+ colors: theme.colors
204
+ }));
205
+ }
206
+
207
+ /**
208
+ * 创建自定义主�? */
209
+ createCustomTheme(name, colors) {
210
+ const themeId = `custom_${Date.now()}`;
211
+ this.themes[themeId] = {
212
+ name: name,
213
+ colors: colors
214
+ };
215
+
216
+ // 保存到localStorage
217
+ this.saveCustomThemes();
218
+
219
+ return themeId;
220
+ }
221
+
222
+ /**
223
+ * 保存自定义主�? */
224
+ saveCustomThemes() {
225
+ const customThemes = {};
226
+ Object.entries(this.themes).forEach(([key, theme]) => {
227
+ if (key.startsWith('custom_')) {
228
+ customThemes[key] = theme;
229
+ }
230
+ });
231
+ localStorage.setItem('customThemes', JSON.stringify(customThemes));
232
+ }
233
+
234
+ /**
235
+ * 加载自定义主�? */
236
+ loadCustomThemes() {
237
+ const saved = localStorage.getItem('customThemes');
238
+ if (saved) {
239
+ try {
240
+ const customThemes = JSON.parse(saved);
241
+ Object.assign(this.themes, customThemes);
242
+ } catch (error) {
243
+ console.error('加载自定义主题失�?', error);
244
+ }
245
+ }
246
+ }
247
+
248
+ /**
249
+ * 删除自定义主�? */
250
+ deleteCustomTheme(themeId) {
251
+ if (!themeId.startsWith('custom_')) {
252
+ console.error('只能删除自定义主�?);
253
+ return false;
254
+ }
255
+
256
+ delete this.themes[themeId];
257
+ this.saveCustomThemes();
258
+
259
+ // 如果删除的是当前主题,切换到默认主题
260
+ if (this.currentTheme === themeId) {
261
+ this.applyTheme('default');
262
+ }
263
+
264
+ return true;
265
+ }
266
+
267
+ /**
268
+ * 渲染主题选择器(美化版)
269
+ */
270
+ renderThemeSelector(container) {
271
+ const themes = this.getAllThemes();
272
+
273
+ container.innerHTML = `
274
+ <div class="theme-selector-modern">
275
+ <div class="theme-header-modern">
276
+ <div class="theme-header-content">
277
+ <h2 class="theme-title-modern">🎨 主题设置</h2>
278
+ <p class="theme-subtitle-modern">选择您喜欢的主题风格,让界面更加个性化</p>
279
+ </div>
280
+ <button class="btn-create-theme-modern" id="createCustomThemeBtn">
281
+ <span class="btn-icon">�?/span>
282
+ <span>创建自定义主�?/span>
283
+ </button>
284
+ </div>
285
+
286
+ <div class="theme-grid-modern">
287
+ ${themes.map(theme => `
288
+ <div class="theme-card-modern ${this.currentTheme === theme.id ? 'active' : ''}"
289
+ data-theme="${theme.id}">
290
+ <div class="theme-preview-modern" style="background: linear-gradient(135deg, ${theme.colors.bgDark} 0%, ${theme.colors.bgCard} 100%);">
291
+ <div class="theme-preview-header" style="background: ${theme.colors.bgCard};">
292
+ <div class="preview-dot" style="background: ${theme.colors.danger};"></div>
293
+ <div class="preview-dot" style="background: ${theme.colors.warning};"></div>
294
+ <div class="preview-dot" style="background: ${theme.colors.success};"></div>
295
+ </div>
296
+ <div class="theme-preview-content">
297
+ <div class="preview-sidebar" style="background: ${theme.colors.bgCard};"></div>
298
+ <div class="preview-main">
299
+ <div class="preview-card" style="background: ${theme.colors.bgCard};">
300
+ <div class="preview-line" style="background: ${theme.colors.primary};"></div>
301
+ <div class="preview-line" style="background: ${theme.colors.textSecondary}; width: 80%;"></div>
302
+ <div class="preview-line" style="background: ${theme.colors.textSecondary}; width: 60%;"></div>
303
+ </div>
304
+ <div class="preview-button" style="background: linear-gradient(135deg, ${theme.colors.primary} 0%, ${theme.colors.secondary} 100%);"></div>
305
+ </div>
306
+ </div>
307
+ <div class="theme-colors-modern">
308
+ <span class="color-dot" style="background: ${theme.colors.primary};" title="主色�?></span>
309
+ <span class="color-dot" style="background: ${theme.colors.secondary};" title="次要�?></span>
310
+ <span class="color-dot" style="background: ${theme.colors.success};" title="成功�?></span>
311
+ <span class="color-dot" style="background: ${theme.colors.danger};" title="危险�?></span>
312
+ </div>
313
+ </div>
314
+ <div class="theme-info-modern">
315
+ <div class="theme-name-modern">${theme.name}</div>
316
+ ${this.currentTheme === theme.id ?
317
+ '<div class="theme-badge-modern">�?当前使用</div>' :
318
+ '<div class="theme-action-modern">点击应用</div>'}
319
+ </div>
320
+ ${theme.id.startsWith('custom_') ?
321
+ `<button class="btn-delete-theme-modern" data-theme="${theme.id}" title="删除主题">
322
+ <span>🗑�?/span>
323
+ </button>` :
324
+ ''}
325
+ </div>
326
+ `).join('')}
327
+ </div>
328
+ </div>
329
+ `;
330
+
331
+ // 添加样式
332
+ this.addThemeSelectorStyles();
333
+
334
+ // 添加事件监听
335
+ container.querySelectorAll('.theme-card-modern').forEach(card => {
336
+ card.addEventListener('click', (e) => {
337
+ if (!e.target.closest('.btn-delete-theme-modern')) {
338
+ const themeId = card.dataset.theme;
339
+ this.applyTheme(themeId);
340
+
341
+ // 更新选中状�? container.querySelectorAll('.theme-card-modern').forEach(c => c.classList.remove('active'));
342
+ card.classList.add('active');
343
+
344
+ // 更新徽章文字
345
+ container.querySelectorAll('.theme-badge-modern, .theme-action-modern').forEach(badge => {
346
+ badge.className = 'theme-action-modern';
347
+ badge.textContent = '点击应用';
348
+ });
349
+
350
+ const badge = card.querySelector('.theme-badge-modern, .theme-action-modern');
351
+ if (badge) {
352
+ badge.className = 'theme-badge-modern';
353
+ badge.textContent = '�?当前使用';
354
+ }
355
+ }
356
+ });
357
+ });
358
+
359
+ // 删除自定义主�? container.querySelectorAll('.btn-delete-theme-modern').forEach(btn => {
360
+ btn.addEventListener('click', (e) => {
361
+ e.stopPropagation();
362
+ const themeId = btn.dataset.theme;
363
+ if (confirm('确定要删除这个主题吗�?)) {
364
+ this.deleteCustomTheme(themeId);
365
+ this.renderThemeSelector(container);
366
+ }
367
+ });
368
+ });
369
+
370
+ // 创建自定义主�? const createBtn = container.querySelector('#createCustomThemeBtn');
371
+ if (createBtn) {
372
+ createBtn.addEventListener('click', () => {
373
+ this.showCustomThemeDialog(container);
374
+ });
375
+ }
376
+ }
377
+
378
+ /**
379
+ * 显示自定义主题对话框
380
+ */
381
+ showCustomThemeDialog(container) {
382
+ const dialog = document.createElement('div');
383
+ dialog.className = 'modal';
384
+ dialog.innerHTML = `
385
+ <div class="modal-content" style="max-width: 600px;">
386
+ <h3>创建自定义主�?/h3>
387
+ <form id="customThemeForm">
388
+ <div class="form-group">
389
+ <label>主题名称</label>
390
+ <input type="text" name="name" placeholder="我的主题" required>
391
+ </div>
392
+ <div class="form-group">
393
+ <label>主色�?/label>
394
+ <input type="color" name="primary" value="#6366f1">
395
+ </div>
396
+ <div class="form-group">
397
+ <label>次要�?/label>
398
+ <input type="color" name="secondary" value="#8b5cf6">
399
+ </div>
400
+ <div class="form-group">
401
+ <label>成功�?/label>
402
+ <input type="color" name="success" value="#10b981">
403
+ </div>
404
+ <div class="form-group">
405
+ <label>危险�?/label>
406
+ <input type="color" name="danger" value="#ef4444">
407
+ </div>
408
+ <div class="form-group">
409
+ <label>背景�?/label>
410
+ <input type="color" name="bgDark" value="#0f172a">
411
+ </div>
412
+ <div class="form-group">
413
+ <label>卡片背景</label>
414
+ <input type="color" name="bgCard" value="#1e293b">
415
+ </div>
416
+ <div class="form-group">
417
+ <label>文字颜色</label>
418
+ <input type="color" name="textPrimary" value="#f1f5f9">
419
+ </div>
420
+ <div style="display: flex; gap: 10px; margin-top: 20px;">
421
+ <button type="submit" class="btn-primary">创建</button>
422
+ <button type="button" class="btn-secondary" id="cancelCustomTheme">取消</button>
423
+ </div>
424
+ </form>
425
+ </div>
426
+ `;
427
+
428
+ document.body.appendChild(dialog);
429
+
430
+ // 取消按钮
431
+ dialog.querySelector('#cancelCustomTheme').addEventListener('click', () => {
432
+ dialog.remove();
433
+ });
434
+
435
+ // 提交表单
436
+ dialog.querySelector('#customThemeForm').addEventListener('submit', (e) => {
437
+ e.preventDefault();
438
+ const formData = new FormData(e.target);
439
+
440
+ const colors = {
441
+ primary: formData.get('primary'),
442
+ primaryDark: this.darkenColor(formData.get('primary'), 10),
443
+ secondary: formData.get('secondary'),
444
+ success: formData.get('success'),
445
+ danger: formData.get('danger'),
446
+ warning: '#f59e0b',
447
+ bgDark: formData.get('bgDark'),
448
+ bgCard: formData.get('bgCard'),
449
+ bgHover: this.lightenColor(formData.get('bgCard'), 10),
450
+ textPrimary: formData.get('textPrimary'),
451
+ textSecondary: this.adjustOpacity(formData.get('textPrimary'), 0.7),
452
+ border: this.lightenColor(formData.get('bgCard'), 20)
453
+ };
454
+
455
+ const themeId = this.createCustomTheme(formData.get('name'), colors);
456
+ this.applyTheme(themeId);
457
+
458
+ dialog.remove();
459
+ this.renderThemeSelector(container);
460
+ });
461
+ }
462
+
463
+ /**
464
+ * 加深颜色
465
+ */
466
+ darkenColor(color, percent) {
467
+ const num = parseInt(color.replace('#', ''), 16);
468
+ const amt = Math.round(2.55 * percent);
469
+ const R = (num >> 16) - amt;
470
+ const G = (num >> 8 & 0x00FF) - amt;
471
+ const B = (num & 0x0000FF) - amt;
472
+ return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
473
+ (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
474
+ (B < 255 ? B < 1 ? 0 : B : 255))
475
+ .toString(16).slice(1);
476
+ }
477
+
478
+ /**
479
+ * 提亮颜色
480
+ */
481
+ lightenColor(color, percent) {
482
+ const num = parseInt(color.replace('#', ''), 16);
483
+ const amt = Math.round(2.55 * percent);
484
+ const R = (num >> 16) + amt;
485
+ const G = (num >> 8 & 0x00FF) + amt;
486
+ const B = (num & 0x0000FF) + amt;
487
+ return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
488
+ (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
489
+ (B < 255 ? B < 1 ? 0 : B : 255))
490
+ .toString(16).slice(1);
491
+ }
492
+
493
+ /**
494
+ * 调整透明�? */
495
+ adjustOpacity(color, opacity) {
496
+ const num = parseInt(color.replace('#', ''), 16);
497
+ const R = num >> 16;
498
+ const G = num >> 8 & 0x00FF;
499
+ const B = num & 0x0000FF;
500
+ return `rgba(${R}, ${G}, ${B}, ${opacity})`;
501
+ }
502
+
503
+ /**
504
+ * 添加主题选择器样�? */
505
+ addThemeSelectorStyles() {
506
+ if (document.getElementById('theme-selector-modern-styles')) return;
507
+
508
+ const style = document.createElement('style');
509
+ style.id = 'theme-selector-modern-styles';
510
+ style.textContent = `
511
+ .theme-selector-modern {
512
+ padding: 0;
513
+ animation: fadeInUp 0.5s ease;
514
+ }
515
+
516
+ .theme-header-modern {
517
+ display: flex;
518
+ justify-content: space-between;
519
+ align-items: center;
520
+ margin-bottom: 40px;
521
+ padding: 30px;
522
+ background: linear-gradient(135deg, var(--bg-card) 0%, rgba(99,102,241,0.05) 100%);
523
+ border-radius: 20px;
524
+ border: 1px solid var(--border);
525
+ }
526
+
527
+ .theme-header-content {
528
+ flex: 1;
529
+ }
530
+
531
+ .theme-title-modern {
532
+ margin: 0 0 8px;
533
+ font-size: 32px;
534
+ font-weight: 800;
535
+ color: var(--text-primary);
536
+ display: flex;
537
+ align-items: center;
538
+ gap: 12px;
539
+ }
540
+
541
+ .theme-subtitle-modern {
542
+ margin: 0;
543
+ font-size: 15px;
544
+ color: var(--text-secondary);
545
+ font-weight: 500;
546
+ }
547
+
548
+ .btn-create-theme-modern {
549
+ padding: 14px 28px;
550
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
551
+ color: white;
552
+ border: none;
553
+ border-radius: 12px;
554
+ font-size: 15px;
555
+ font-weight: 600;
556
+ cursor: pointer;
557
+ transition: all 0.3s ease;
558
+ display: flex;
559
+ align-items: center;
560
+ gap: 8px;
561
+ box-shadow: 0 4px 12px rgba(99,102,241,0.3);
562
+ }
563
+
564
+ .btn-create-theme-modern:hover {
565
+ transform: translateY(-3px);
566
+ box-shadow: 0 8px 24px rgba(99,102,241,0.4);
567
+ }
568
+
569
+ .btn-icon {
570
+ font-size: 18px;
571
+ }
572
+
573
+ .theme-grid-modern {
574
+ display: grid;
575
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
576
+ gap: 24px;
577
+ padding: 0 30px 30px;
578
+ }
579
+
580
+ .theme-card-modern {
581
+ background: var(--bg-card);
582
+ border: 2px solid var(--border);
583
+ border-radius: 16px;
584
+ overflow: hidden;
585
+ cursor: pointer;
586
+ transition: all 0.3s ease;
587
+ position: relative;
588
+ }
589
+
590
+ .theme-card-modern:hover {
591
+ transform: translateY(-8px);
592
+ box-shadow: 0 12px 32px rgba(0,0,0,0.3);
593
+ border-color: var(--primary);
594
+ }
595
+
596
+ .theme-card-modern.active {
597
+ border-color: var(--primary);
598
+ box-shadow: 0 0 0 3px rgba(99,102,241,0.2);
599
+ }
600
+
601
+ .theme-card-modern.active::before {
602
+ content: '';
603
+ position: absolute;
604
+ top: 0;
605
+ left: 0;
606
+ right: 0;
607
+ height: 4px;
608
+ background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%);
609
+ z-index: 1;
610
+ }
611
+
612
+ .theme-preview-modern {
613
+ height: 200px;
614
+ padding: 12px;
615
+ display: flex;
616
+ flex-direction: column;
617
+ gap: 8px;
618
+ position: relative;
619
+ }
620
+
621
+ .theme-preview-header {
622
+ height: 24px;
623
+ border-radius: 8px 8px 0 0;
624
+ display: flex;
625
+ align-items: center;
626
+ gap: 6px;
627
+ padding: 0 10px;
628
+ }
629
+
630
+ .preview-dot {
631
+ width: 10px;
632
+ height: 10px;
633
+ border-radius: 50%;
634
+ }
635
+
636
+ .theme-preview-content {
637
+ flex: 1;
638
+ display: flex;
639
+ gap: 8px;
640
+ }
641
+
642
+ .preview-sidebar {
643
+ width: 50px;
644
+ border-radius: 8px;
645
+ }
646
+
647
+ .preview-main {
648
+ flex: 1;
649
+ display: flex;
650
+ flex-direction: column;
651
+ gap: 8px;
652
+ }
653
+
654
+ .preview-card {
655
+ flex: 1;
656
+ border-radius: 8px;
657
+ padding: 12px;
658
+ display: flex;
659
+ flex-direction: column;
660
+ gap: 6px;
661
+ }
662
+
663
+ .preview-line {
664
+ height: 6px;
665
+ border-radius: 3px;
666
+ width: 100%;
667
+ }
668
+
669
+ .preview-button {
670
+ height: 24px;
671
+ border-radius: 6px;
672
+ }
673
+
674
+ .theme-colors-modern {
675
+ display: flex;
676
+ gap: 8px;
677
+ justify-content: center;
678
+ padding: 8px 0;
679
+ }
680
+
681
+ .color-dot {
682
+ width: 24px;
683
+ height: 24px;
684
+ border-radius: 50%;
685
+ border: 2px solid rgba(255,255,255,0.2);
686
+ transition: all 0.3s ease;
687
+ cursor: pointer;
688
+ }
689
+
690
+ .color-dot:hover {
691
+ transform: scale(1.2);
692
+ border-color: rgba(255,255,255,0.5);
693
+ }
694
+
695
+ .theme-info-modern {
696
+ padding: 16px 20px;
697
+ background: var(--bg-dark);
698
+ display: flex;
699
+ justify-content: space-between;
700
+ align-items: center;
701
+ }
702
+
703
+ .theme-name-modern {
704
+ font-size: 16px;
705
+ font-weight: 700;
706
+ color: var(--text-primary);
707
+ }
708
+
709
+ .theme-badge-modern {
710
+ padding: 4px 12px;
711
+ background: linear-gradient(135deg, var(--success) 0%, #059669 100%);
712
+ color: white;
713
+ border-radius: 12px;
714
+ font-size: 12px;
715
+ font-weight: 600;
716
+ }
717
+
718
+ .theme-action-modern {
719
+ padding: 4px 12px;
720
+ background: var(--bg-hover);
721
+ color: var(--text-secondary);
722
+ border-radius: 12px;
723
+ font-size: 12px;
724
+ font-weight: 600;
725
+ }
726
+
727
+ .btn-delete-theme-modern {
728
+ position: absolute;
729
+ top: 16px;
730
+ right: 16px;
731
+ width: 36px;
732
+ height: 36px;
733
+ background: rgba(239,68,68,0.9);
734
+ border: none;
735
+ border-radius: 50%;
736
+ color: white;
737
+ font-size: 16px;
738
+ cursor: pointer;
739
+ transition: all 0.3s ease;
740
+ display: flex;
741
+ align-items: center;
742
+ justify-content: center;
743
+ z-index: 2;
744
+ opacity: 0;
745
+ }
746
+
747
+ .theme-card-modern:hover .btn-delete-theme-modern {
748
+ opacity: 1;
749
+ }
750
+
751
+ .btn-delete-theme-modern:hover {
752
+ background: var(--danger);
753
+ transform: scale(1.1) rotate(10deg);
754
+ }
755
+
756
+ @keyframes fadeInUp {
757
+ from {
758
+ opacity: 0;
759
+ transform: translateY(20px);
760
+ }
761
+ to {
762
+ opacity: 1;
763
+ transform: translateY(0);
764
+ }
765
+ }
766
+
767
+ @media (max-width: 768px) {
768
+ .theme-header-modern {
769
+ flex-direction: column;
770
+ gap: 20px;
771
+ align-items: flex-start;
772
+ }
773
+
774
+ .btn-create-theme-modern {
775
+ width: 100%;
776
+ justify-content: center;
777
+ }
778
+
779
+ .theme-grid-modern {
780
+ grid-template-columns: 1fr;
781
+ padding: 0 20px 20px;
782
+ }
783
+ }
784
+ `;
785
+ document.head.appendChild(style);
786
+ }
787
+
788
+ /**
789
+ * 初始化主�? */
790
+ init() {
791
+ this.loadCustomThemes();
792
+ this.applyTheme(this.currentTheme);
793
+ }
794
+ }
795
+
796
+ // 导出单例
797
+ export const themeManager = new ThemeManager();
798
+
799
+
800
800