记录了博客搭建过程中,常用到的“前端代码”!

效果如下所示:点击粉字会跳转到对应文章网页!

代码如下所示:

1
<a href="https://www.fomal.cc/posts/2013454d.html#1-Markdown%E8%AF%AD%E6%B3%95%E8%87%AA%E5%B8%A6%E6%A0%BC%E5%BC%8F" title="跳转到参考文章">Fomalaut</a>

效果见本博客画廊详细页面那轮播图,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
{% raw %}
<!-- 自定义元素容器 -->
<image-carousel data-images='[
"https://pic1.imgdb.cn/item/68552b1658cb8da5c85e8bd3.jpg",
"https://pic1.imgdb.cn/item/68552b1658cb8da5c85e8bd6.png",
"https://pic1.imgdb.cn/item/6856232e58cb8da5c85f34d0.jpg",
"https://pic1.imgdb.cn/item/6856232e58cb8da5c85f34cf.png",
"https://pic1.imgdb.cn/item/6856232d58cb8da5c85f34cb.png",
"https://pic1.imgdb.cn/item/68567bf058cb8da5c8608c86.jpg",
"https://pic1.imgdb.cn/item/68567bde58cb8da5c8608c84.jpg"
]'></image-carousel>
<script>
// 定义自定义元素
class ImageCarousel extends HTMLElement {
constructor() {
super();
// 创建Shadow DOM
this.attachShadow({ mode: 'open' });

// 初始化图片URL数组
this.imageUrls = [];
this.index = 0;
this.time = null;
}
connectedCallback() {
// 从data-images属性获取图片URL
const imagesData = this.getAttribute('data-images');
try {
this.imageUrls = imagesData ? JSON.parse(imagesData) : this.getDefaultImages();
} catch (e) {
console.error("Invalid images data format, using default images instead");
this.imageUrls = this.getDefaultImages();
}

this.render();
this.initElements();
this.createImages();
this.createThumbnails(); // 创建缩略图
this.timer();
this.addEventListeners();
}
getDefaultImages() {
return [
"https://pic1.imgdb.cn/item/68552b1658cb8da5c85e8bd3.jpg",
"https://pic1.imgdb.cn/item/68552b1658cb8da5c85e8bd6.png",
"https://pic1.imgdb.cn/item/6856232e58cb8da5c85f34d0.jpg",
"https://pic1.imgdb.cn/item/6856232e58cb8da5c85f34cf.png",
"https://pic1.imgdb.cn/item/6856232d58cb8da5c85f34cb.png",
"https://pic1.imgdb.cn/item/68567bf058cb8da5c8608c86.jpg",
"https://pic1.imgdb.cn/item/68567bde58cb8da5c8608c84.jpg"
];
}
render() {
this.shadowRoot.innerHTML = `
<style>
* {
padding: 0px;
margin: 0px;
list-style: none;
bottom: 0;
text-decoration: none;
}
.carousel-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.shell {
width: 870px;
height: 520px;
position: relative;
overflow: hidden;
border-radius: 5px;
border: 10px #fff solid;
}
.images {
width: ${this.imageUrls.length * 100}%;
height: 100%;
display: flex;
position: absolute;
left: 0;
transition: 0.5s;
}
.img {
width: ${100 / this.imageUrls.length}%;
background-size: cover;
background-position: center;
}
.min-images {
display: flex;
justify-content: center;
position: absolute;
bottom: 20px;
width: 100%;
z-index: 999;
}
.min {
width: 10px;
height: 10px;
margin: 0 3px;
cursor: pointer;
border-radius: 50%;
background-color: white;
border: 1px solid rgba(0, 0, 0, 0.5);
transition: all 0.3s;
}
.min.active {
background-color: transparent;
border-color: white;
}
.button {
width: 100%;
height: 100%;
position: absolute;
display: flex;
justify-content: space-between;
user-select: none;
}
.button-left,
.button-right {
font-size: 50px;
background-color: rgba(160, 193, 255, 0.7);
padding: 0 20px;
cursor: pointer;
line-height: 500px;
}

/* 缩略图样式 */
.thumbnails {
display: flex;
justify-content: center;
gap: 10px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 5px;
margin-top: 10px;
width: 870px;
}

.thumbnail {
width: 80px;
height: 50px;
background-size: cover;
background-position: center;
cursor: pointer;
border: 2px solid transparent;
border-radius: 3px;
transition: all 0.3s;
}

.thumbnail.active {
border-color: #4361ee;
transform: scale(1.05);
}
</style>
<div class="carousel-container">
<div class="shell">
<ul class="images"></ul>
<ul class="min-images"></ul>
<div class="button">
<div class="button-left">&lt;</div>
<div class="button-right">&gt;</div>
</div>
</div>
<div class="thumbnails"></div> <!-- 缩略图容器 -->
</div>
`;
}
initElements() {
this.imagesContainer = this.shadowRoot.querySelector(".images");
this.minImagesContainer = this.shadowRoot.querySelector(".min-images");
this.leftButton = this.shadowRoot.querySelector(".button-left");
this.rightButton = this.shadowRoot.querySelector(".button-right");
this.thumbnailsContainer = this.shadowRoot.querySelector(".thumbnails");
}
createImages() {
// 清空容器
this.imagesContainer.innerHTML = '';
this.minImagesContainer.innerHTML = '';

this.imageUrls.forEach((url, i) => {
const img = document.createElement("li");
img.classList.add("img");
img.style.backgroundImage = `url(${url})`;
this.imagesContainer.appendChild(img);
const minImg = document.createElement("li");
minImg.classList.add("min");
if (i === 0) {
minImg.classList.add("active");
}
this.minImagesContainer.appendChild(minImg);
});
}
// 创建缩略图
createThumbnails() {
this.thumbnailsContainer.innerHTML = '';

this.imageUrls.forEach((url, i) => {
const thumbnail = document.createElement("div");
thumbnail.classList.add("thumbnail");
thumbnail.style.backgroundImage = `url(${url})`;
if (i === 0) {
thumbnail.classList.add("active");
}

thumbnail.addEventListener("click", () => {
this.index = i;
this.updateIndicators();
this.updateThumbnails();
this.position();
clearInterval(this.time);
this.timer();
});

this.thumbnailsContainer.appendChild(thumbnail);
});
}
position() {
this.imagesContainer.style.left = (this.index * -100) + "%";
}
add() {
if (this.index >= this.imageUrls.length - 1) {
this.index = 0;
} else {
this.index++;
}
this.updateIndicators();
this.updateThumbnails();
}
desc() {
if (this.index < 1) {
this.index = this.imageUrls.length - 1;
} else {
this.index--;
}
this.updateIndicators();
this.updateThumbnails();
}
updateIndicators() {
const minImages = this.minImagesContainer.querySelectorAll(".min");
minImages.forEach((minImg, i) => {
if (i === this.index) {
minImg.classList.add("active");
} else {
minImg.classList.remove("active");
}
});
}
// 更新缩略图选中状态
updateThumbnails() {
const thumbnails = this.thumbnailsContainer.querySelectorAll(".thumbnail");
thumbnails.forEach((thumbnail, i) => {
if (i === this.index) {
thumbnail.classList.add("active");
} else {
thumbnail.classList.remove("active");
}
});
}
timer() {
this.time = setInterval(() => {
this.add();
this.position();
}, 3000);
}
addEventListeners() {
this.leftButton.addEventListener("click", () => {
this.desc();
this.position();
clearInterval(this.time);
this.timer();
});
this.rightButton.addEventListener("click", () => {
this.add();
this.position();
clearInterval(this.time);
this.timer();
});
this.minImagesContainer.addEventListener("click", (event) => {
if (event.target.classList.contains("min")) {
const minImages = Array.from(this.minImagesContainer.children);
const targetIndex = minImages.indexOf(event.target);
this.index = targetIndex;
this.updateIndicators();
this.updateThumbnails();
this.position();
clearInterval(this.time);
this.timer();
}
});
}
}
// 注册自定义元素
customElements.define('image-carousel', ImageCarousel);
</script>
{% endraw %}

效果如下所示:

代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
{% raw %}
<center><font font-family="ZhuZiAWan_light" size="4px">

<a class="gallery_link" href="/life/gallery/girl2/index.html" data-pjax-state=""><mark class="hl-label blue">&nbsp;<b>上一页</b>&nbsp;</mark></a> &nbsp; &nbsp;

<b>2</b> &nbsp;&nbsp;

<a class="gallery_link" href="/life/gallery/girl2/p3/index.html" data-pjax-state=""><mark class="hl-label blue">&nbsp;<b>下一页</b>&nbsp;</mark></a> &nbsp; &nbsp;

</font></center>
{% endraw %}

效果见本博客音乐页面,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
{% raw %}
<!-- 这是我们的挂载点 -->
<div id="music-player-container"></div>

<script>
(function() {
// 创建隔离的作用域
const container = document.getElementById('music-player-container');

// 创建唯一的ID前缀,用于样式隔离
const uniqueId = 'music-player-' + Math.random().toString(36).substr(2, 9);

// 添加HTML结构和样式
container.innerHTML = `
<style id="${uniqueId}-styles">
/* 新增外部容器样式 - 粉蓝色主题 */
#music-player-container {
background: linear-gradient(135deg, #e6f0ff 0%, #f0f8ff 100%);
padding: 5px;
border-radius: 18px;
margin: 25px 0;
box-shadow: 0 6px 20px rgba(100, 149, 237, 0.25);
transition: all 0.3s ease;
border: 1px solid #c9ddff;
}

#music-player-container:hover {
background: linear-gradient(135deg, #d9e6ff 0%, #e6f2ff 100%);
box-shadow: 0 8px 25px rgba(100, 149, 237, 0.35);
}

#${uniqueId} {
display: block;
font-family: Arial, sans-serif;
--primary-color: #5b7cff; /* 调整为更亮的蓝色 */
--primary-hover: #4a6bf5;
--active-color: #ff6b8b; /* 改为粉色增加对比 */
--text-color: #333;
--light-gray: #f5f9ff; /* 更蓝的浅灰色 */
--medium-gray: #e0e8ff; /* 带蓝色调的灰色 */
--dark-gray: #6a7a9b; /* 蓝灰色 */
--white: #f8fbff; /* 浅粉蓝白色 */
}

#${uniqueId} #music-player {
background-color: var(--white);
border-radius: 15px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
padding: 30px;
width: 100%;
max-width: 1000px;
margin: 0 auto;
box-sizing: border-box;
border: 1px solid #d4e3ff;
background-image:
radial-gradient(circle at 10% 20%, rgba(255,255,255,0.8) 0%, transparent 20%),
radial-gradient(circle at 90% 80%, rgba(230,240,255,0.6) 0%, transparent 20%);
}

/* 其余原有样式保持不变... */
#${uniqueId} .player-header {
text-align: center;
margin-bottom: 20px;
color: var(--text-color);
}

#${uniqueId} .folder-input-container {
text-align: center;
margin-bottom: 20px;
}

#${uniqueId} .folder-input-label {
display: inline-block;
background-color: var(--primary-color);
color: var(--white);
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}

#${uniqueId} .folder-input-label:hover {
background-color: var(--primary-hover);
}

#${uniqueId} input[type="file"] {
display: none;
}

#${uniqueId} .player-controls {
display: flex;
justify-content: center;
gap: 20px;
margin: 20px 0;
}

#${uniqueId} .control-btn, #${uniqueId} .mode-btn {
background-color: var(--primary-color);
color: var(--white);
border: none;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.3s;
}

#${uniqueId} .control-btn:hover, #${uniqueId} .mode-btn:hover {
background-color: var(--primary-hover);
}

#${uniqueId} .mode-btn {
border-radius: 20px;
width: auto;
padding: 0 15px;
}

#${uniqueId} .mode-btn.active {
background-color: var(--active-color);
}

#${uniqueId} .progress-container {
height: 5px;
background-color: var(--medium-gray);
border-radius: 5px;
margin: 20px 0;
cursor: pointer;
}

#${uniqueId} .progress-bar {
height: 100%;
background-color: var(--primary-color);
border-radius: 5px;
width: 0%;
transition: width 0.1s linear;
}

#${uniqueId} .time-display {
display: flex;
justify-content: space-between;
font-size: 14px;
color: var(--dark-gray);
margin-bottom: 20px;
}

#${uniqueId} .volume-control {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
color: var(--text-color);
}

/* 自定义音量条样式 */
#${uniqueId} .custom-volume-container {
position: relative;
height: 5px;
background-color: #e0e0e0; /* 灰色背景 */
border-radius: 5px;
flex: 1;
cursor: pointer;
}

#${uniqueId} .custom-volume-bar {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: var(--primary-color); /* 蓝色填充 */
border-radius: 5px;
width: 30%; /* 默认音量30% */
transition: width 0.1s linear;
}

#${uniqueId} .custom-volume-handle {
position: absolute;
top: 50%;
left: 30%; /* 默认音量30% */
transform: translate(-50%, -50%);
width: 14px;
height: 14px;
background-color: white;
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
transition: left 0.1s linear;
z-index: 1;
}

#${uniqueId} input[type="range"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
z-index: 2;
}

#${uniqueId} .playlist {
max-height: 300px;
overflow-y: auto;
border-top: 1px solid var(--medium-gray);
padding-top: 15px;
}

#${uniqueId} .playlist-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid var(--medium-gray);
cursor: pointer;
transition: background-color 0.2s;
color: var(--text-color);
white-space: nowrap;
}

#${uniqueId} .playlist-item:hover {
background-color: var(--light-gray);
}

#${uniqueId} .playlist-item.active {
background-color: rgba(91, 124, 255, 0.1);
}

#${uniqueId} .playlist-item.active .name {
color: var(--primary-color) !important;
font-weight: bold;
}

#${uniqueId} .index {
width: 25px;
text-align: center;
color: var(--dark-gray);
margin-right: 10px;
flex-shrink: 0;
}

#${uniqueId} .name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

#${uniqueId} .empty-state {
text-align: center;
color: var(--dark-gray);
padding: 20px;
}

#${uniqueId} #loading-indicator {
display: none;
text-align: center;
padding: 20px;
margin: 20px 0;
background: rgba(240, 244, 255, 0.8);
border-radius: 10px;
border: 1px solid #d0d8e8;
}

#${uniqueId} .spinner {
width: 40px;
height: 40px;
margin: 0 auto 15px;
border: 4px solid rgba(91, 124, 255, 0.3);
border-radius: 50%;
border-top-color: var(--primary-color);
animation: spin 1s ease-in-out infinite;
}

@keyframes spin {
to { transform: rotate(360deg); }
}

#${uniqueId} .progress-text {
margin-top: 10px;
color: var(--primary-color);
font-size: 14px;
}

#${uniqueId} .upload-progress-container {
height: 10px;
background-color: var(--medium-gray);
border-radius: 5px;
margin-top: 15px;
overflow: hidden;
}

#${uniqueId} .upload-progress-bar {
height: 100%;
background-color: var(--primary-color);
width: 0%;
transition: width 0.3s ease;
}

@media (max-width: 500px) {
#music-player-container {
padding: 15px;
margin: 15px 0;
}

#${uniqueId} #music-player {
padding: 20px;
}

#${uniqueId} .player-controls {
gap: 10px;
}

#${uniqueId} .control-btn, #${uniqueId} .mode-btn {
width: 35px;
height: 35px;
font-size: 14px;
}

#${uniqueId} .mode-btn {
padding: 0 10px;
}
}
</style>
<div id="${uniqueId}">
<div id="music-player">
<div class="player-header">
<p id="status-text">请选择包含音乐的文件夹</p>
</div>

<div class="folder-input-container">
<label for="folder-input" class="folder-input-label">
📁 选择音乐文件夹
</label>
<input type="file" id="folder-input" webkitdirectory directory multiple>
</div>

<div id="loading-indicator">
<div class="spinner"></div>
<p>正在加载音乐文件,请稍候...</p>
<div class="upload-progress-container">
<div class="upload-progress-bar" id="upload-progress-bar"></div>
</div>
<div class="progress-text" id="progress-text">0/0 文件已处理</div>
</div>

<div class="player-controls">
<button class="control-btn" id="prev-btn" disabled title="上一首">⏮</button>
<button class="control-btn" id="play-btn" title="播放">▶</button>
<button class="control-btn" id="next-btn" disabled title="下一首">⏭</button>
<button class="mode-btn active" id="shuffle-btn" title="随机播放">🔀 随机</button>
<button class="mode-btn" id="sequential-btn" title="顺序播放">⏩ 顺序</button>
</div>

<div class="progress-container" id="progress-container">
<div class="progress-bar" id="progress-bar"></div>
</div>

<div class="time-display">
<span id="current-time">0:00</span>
<span id="duration">0:00</span>
</div>

<div class="volume-control">
<span>音量:</span>
<div class="custom-volume-container">
<div class="custom-volume-bar" id="custom-volume-bar"></div>
<div class="custom-volume-handle" id="custom-volume-handle"></div>
<input type="range" id="volume" min="0" max="1" step="0.01" value="0.3">
</div>
<span id="volume-percentage">30%</span>
</div>

<div class="playlist" id="playlist">
<div class="empty-state">暂无音乐,请选择文件夹</div>
</div>
</div>
</div>
`;

// 获取元素时加上唯一ID前缀
const getElement = (id) => document.querySelector(`#${uniqueId} #${id}`);

const audio = new Audio();
let currentTrackIndex = 0;
let tracks = [];
let isPlaying = false;
let isShuffleMode = true;
let originalOrder = [];
let currentFileURLs = new Set();

// 获取元素
const playBtn = getElement('play-btn');
const prevBtn = getElement('prev-btn');
const nextBtn = getElement('next-btn');
const shuffleBtn = getElement('shuffle-btn');
const sequentialBtn = getElement('sequential-btn');
const progressContainer = getElement('progress-container');
const progressBar = getElement('progress-bar');
const currentTimeEl = getElement('current-time');
const durationEl = getElement('duration');
const volumeControl = getElement('volume');
const playlistEl = getElement('playlist');
const folderInput = getElement('folder-input');
const statusText = getElement('status-text');
const loadingIndicator = getElement('loading-indicator');
const progressText = getElement('progress-text');
const uploadProgressBar = getElement('upload-progress-bar');
const volumePercentage = getElement('volume-percentage');

// 新增的自定义音量条元素
const customVolumeBar = getElement('custom-volume-bar');
const customVolumeHandle = getElement('custom-volume-handle');
const customVolumeContainer = customVolumeBar.parentElement;

// 加载音乐文件夹
folderInput.addEventListener('change', async function (e) {
cleanupFileURLs();

const files = Array.from(e.target.files).filter(file =>
file.type.startsWith('audio/')
);

if (files.length === 0) {
statusText.textContent = "文件夹中没有找到音乐文件";
return;
}

loadingIndicator.style.display = 'block';
statusText.textContent = `正在加载 ${files.length} 首音乐...`;
uploadProgressBar.style.width = '0%';
progressText.textContent = '准备中...';

try {
tracks = await processFilesWithProgress(files);
originalOrder = [...tracks];
renderPlaylist();
loadTrack(currentTrackIndex);
updateButtons();
statusText.textContent = `已加载 ${tracks.length} 首音乐,准备播放`;
} catch (error) {
console.error("加载音乐失败:", error);
statusText.textContent = "加载音乐时出错: " + error.message;
} finally {
loadingIndicator.style.display = 'none';
}
});

// 带进度反馈的文件处理函数
async function processFilesWithProgress(files) {
return new Promise((resolve, reject) => {
const results = [];
const total = files.length;
let processed = 0;
let failed = 0;

const processNextBatch = (index) => {
if (index >= total) {
if (processed === 0 && failed > 0) {
reject(new Error("无法处理任何音乐文件"));
} else {
resolve(results);
}
return;
}

const batchSize = 5;
const endIndex = Math.min(index + batchSize, total);

for (let i = index; i < endIndex; i++) {
const file = files[i];

if (currentFileURLs.has(file.name)) {
console.warn(`跳过重复文件: ${file.name}`);
processed++;
continue;
}

try {
const fileURL = URL.createObjectURL(file);
currentFileURLs.add(file.name);

results.push({
index: results.length + 1,
name: file.name.replace(/\.[^/.]+$/, ""),
path: fileURL,
file: file
});
processed++;
} catch (error) {
console.error(`处理文件 ${file.name} 失败:`, error);
failed++;
}
}

const percent = Math.round((processed / total) * 100);
uploadProgressBar.style.width = `${percent}%`;
progressText.textContent = `${processed}/${total} 文件已处理${failed > 0 ? ` (${failed} 个失败)` : ''}`;

setTimeout(() => processNextBatch(endIndex), 10);
};

processNextBatch(0);
});
}

// 清理所有已创建的文件URL
function cleanupFileURLs() {
if (audio.src && audio.src.startsWith('blob:')) {
URL.revokeObjectURL(audio.src);
}

currentFileURLs.forEach(url => {
try {
URL.revokeObjectURL(url);
} catch (error) {
console.error("释放URL失败:", error);
}
});

currentFileURLs.clear();
}

// 渲染播放列表
function renderPlaylist() {
if (tracks.length === 0) {
playlistEl.innerHTML = '<div class="empty-state">暂无音乐,请选择文件夹</div>';
return;
}

playlistEl.innerHTML = '';
tracks.forEach((track, index) => {
const item = document.createElement('div');
item.className = `playlist-item ${index === currentTrackIndex ? 'active' : ''}`;
item.innerHTML = `
<span class="index">${index + 1}</span>
<span class="name">${track.name}</span>
`;
item.addEventListener('click', () => {
currentTrackIndex = index;
loadTrack(currentTrackIndex);
playTrack();
});
playlistEl.appendChild(item);
});

// 自动滚动到当前选中的音乐
scrollToActiveTrack();
}

// 自动滚动到当前选中的音乐
function scrollToActiveTrack() {
const activeItem = playlistEl.querySelector('.playlist-item.active');
if (activeItem) {
activeItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}

// 加载曲目
function loadTrack(index) {
if (tracks.length === 0) return;

const track = tracks[index];

if (audio.src && audio.src.startsWith('blob:')) {
URL.revokeObjectURL(audio.src);
}

audio.src = track.path;
audio.volume = 0.3;
audio.load();

statusText.textContent = `正在播放: ${track.name} (${index + 1}/${tracks.length})`;
renderPlaylist();
updateVolumePercentage(0.3);
}

// 播放曲目
function playTrack() {
if (tracks.length === 0) return;

audio.play()
.then(() => {
isPlaying = true;
playBtn.textContent = '⏸';
playBtn.title = "暂停";
})
.catch(error => {
console.error("播放失败:", error);
statusText.textContent = `播放失败: ${error.message}`;
});
}

// 暂停曲目
function pauseTrack() {
audio.pause();
isPlaying = false;
playBtn.textContent = '▶';
playBtn.title = "播放";
}

// 下一首
function nextTrack() {
if (tracks.length === 0) return;

if (isShuffleMode) {
currentTrackIndex = Math.floor(Math.random() * tracks.length);
} else {
currentTrackIndex = (currentTrackIndex + 1) % tracks.length;
}

loadTrack(currentTrackIndex);
playTrack();
}

// 上一首
function prevTrack() {
if (tracks.length === 0) return;

if (isShuffleMode) {
currentTrackIndex = Math.floor(Math.random() * tracks.length);
} else {
currentTrackIndex = (currentTrackIndex - 1 + tracks.length) % tracks.length;
}

loadTrack(currentTrackIndex);
playTrack();
}

// 切换随机播放模式
function toggleShuffleMode() {
isShuffleMode = true;
shuffleBtn.classList.add('active');
sequentialBtn.classList.remove('active');
}

// 切换顺序播放模式
function toggleSequentialMode() {
isShuffleMode = false;
sequentialBtn.classList.add('active');
shuffleBtn.classList.remove('active');
}

// 更新按钮状态
function updateButtons() {
const hasTracks = tracks.length > 0;
prevBtn.disabled = !hasTracks || tracks.length <= 1;
nextBtn.disabled = !hasTracks || tracks.length <= 1;

if (hasTracks) {
playBtn.disabled = false;
}
}

// 播放/暂停按钮点击事件
playBtn.addEventListener('click', () => {
if (tracks.length === 0) {
folderInput.click();
return;
}

if (isPlaying) {
pauseTrack();
} else {
playTrack();
}
});

// 注册其他按钮事件
nextBtn.addEventListener('click', nextTrack);
prevBtn.addEventListener('click', prevTrack);
shuffleBtn.addEventListener('click', toggleShuffleMode);
sequentialBtn.addEventListener('click', toggleSequentialMode);

// 音频时间更新事件
audio.addEventListener('timeupdate', function() {
if (isNaN(audio.duration)) return;

const progressPercent = (audio.currentTime / audio.duration) * 100;
progressBar.style.width = `${progressPercent}%`;

currentTimeEl.textContent = formatTime(audio.currentTime);
durationEl.textContent = formatTime(audio.duration);
});

// 格式化时间显示
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
}

// 进度条点击事件
progressContainer.addEventListener('click', (e) => {
if (isNaN(audio.duration)) return;

const width = progressContainer.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;

audio.currentTime = (clickX / width) * duration;
});

// 音量控制
volumeControl.addEventListener('input', (e) => {
const volume = parseFloat(e.target.value);
audio.volume = volume;
updateVolumePercentage(volume);
updateCustomVolumeBar(volume);
});

// 自定义音量条点击事件
customVolumeContainer.addEventListener('click', (e) => {
const rect = customVolumeContainer.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const width = rect.width;
const volume = Math.max(0, Math.min(1, clickX / width));

volumeControl.value = volume;
audio.volume = volume;
updateVolumePercentage(volume);
updateCustomVolumeBar(volume);
});

// 更新自定义音量条显示
function updateCustomVolumeBar(volume) {
const percentage = volume * 100;
customVolumeBar.style.width = `${percentage}%`;
customVolumeHandle.style.left = `${percentage}%`;
}

// 更新音量百分比显示
function updateVolumePercentage(volume) {
const percentage = Math.round(volume * 100);
volumePercentage.textContent = `${percentage}%`;
}

// 音频播放结束事件
audio.addEventListener('ended', nextTrack);

// 初始化音量和显示
updateVolumePercentage(0.3);
updateCustomVolumeBar(0.3);
audio.volume = 0.3;

// 默认随机播放
toggleShuffleMode();

// 页面卸载时清理资源
window.addEventListener('beforeunload', cleanupFileURLs);
})();
</script>
{% endraw %}

效果见本博客网址导航页面,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457

{% raw %}
<!-- 这是我们的挂载点 -->
<div id="nav-container"></div>
<script>
(function() {
// 创建 Shadow DOM 实现完全隔离
const container = document.getElementById('nav-container');
const shadowRoot = container.attachShadow({ mode: 'closed' });

// 创建唯一的ID前缀,用于样式隔离
const uniqueId = 'frontend-nav-' + Math.random().toString(36).substr(2, 9);

// 添加HTML结构和样式
shadowRoot.innerHTML = `
<style>
:host {
--primary-color: #6366f1;
--primary-hover: #4f46e5;
--text-color: #2d3748;
--text-secondary: #4a5568;
--bg-color: #ffffff;
--card-bg: #ffffff;
--border-radius: 8px;
--transition: all 0.2s ease;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.1);
--shadow-md: 0 3px 4px rgba(0,0,0,0.1);
--shadow-lg: 0 8px 20px rgba(0,0,0,0.1);
display: block;
margin: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
all: initial; /* 重置所有继承样式 */
}

#${uniqueId} {
all: initial; /* 确保不继承外部样式 */
}

#${uniqueId} .container {
padding: 1.2rem;
background: var(--bg-color);
border-radius: var(--border-radius);
box-shadow: var(--shadow-lg);
}

#${uniqueId} header {
text-align: center;
margin-bottom: 1.2rem;
}

#${uniqueId} h1 {
font-size: 1.8rem;
margin: 0 0 0.3rem;
color: var(--text-color);
font-weight: 700;
background: linear-gradient(90deg, #6366f1, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}

#${uniqueId} .subtitle {
font-size: 0.9rem;
color: var(--text-secondary);
margin-bottom: 1rem;
font-weight: 400;
}

#${uniqueId} .search-box {
max-width: 500px;
margin: 0 auto 1.2rem;
position: relative;
}

#${uniqueId} .search-box input {
width: 100%;
padding: 0.6rem 1.2rem;
border: 1px solid #e2e8f0;
border-radius: 40px;
font-size: 0.9rem;
outline: none;
box-shadow: var(--shadow-sm);
transition: var(--transition);
background-color: #f8fafc;
}

#${uniqueId} .search-box input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
}

#${uniqueId} .search-box::after {
content: "🔍";
position: absolute;
right: 1.2rem;
top: 50%;
transform: translateY(-50%);
opacity: 0.5;
}

#${uniqueId} .category {
margin-bottom: 1.5rem;
}

#${uniqueId} .category-header {
display: flex;
align-items: center;
margin: 0 0 0.8rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid #f1f5f9;
}

#${uniqueId} .category-icon {
font-size: 1.3rem;
margin-right: 0.6rem;
width: 1.5rem;
text-align: center;
}

#${uniqueId} .category-title {
font-size: 1.1rem;
margin: 0;
color: var(--text-color);
font-weight: 600;
flex-grow: 1;
}

#${uniqueId} .links-container {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 0.8rem;
}

#${uniqueId} .link-card {
background: var(--card-bg);
border-radius: var(--border-radius);
padding: 0.7rem;
box-shadow: var(--shadow-sm);
transition: var(--transition);
border: 1px solid #e2e8f0;
position: relative;
overflow: hidden;
}

#${uniqueId} .link-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 2px;
height: 100%;
background: linear-gradient(to bottom, var(--primary-color), #8b5cf6);
opacity: 0;
transition: var(--transition);
}

#${uniqueId} .link-card::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 2px;
height: 100%;
background: linear-gradient(to bottom, var(--primary-color), #8b5cf6);
opacity: 0;
transition: var(--transition);
}

#${uniqueId} .link-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: #cbd5e0;
background-color: #f8fafc;
}

#${uniqueId} .link-card:hover::before,
#${uniqueId} .link-card:hover::after {
opacity: 1;
}

#${uniqueId} .link-card a {
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}

#${uniqueId} .link-icon {
width: 32px;
height: 32px;
margin-bottom: 0.5rem;
border-radius: 6px;
object-fit: cover;
background: #f1f5f9;
padding: 0.2rem;
box-shadow: var(--shadow-sm);
}

#${uniqueId} .link-title {
font-size: 0.9rem;
margin: 0;
color: var(--text-color);
font-weight: 600;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}

#${uniqueId} .link-description {
font-size: 0.75rem;
color: var(--text-secondary);
line-height: 1.2;
margin-top: 0.3rem;
text-align: center;
max-height: 2.4em;
overflow: hidden;
}

#${uniqueId} footer {
text-align: center;
margin-top: 1.5rem;
padding: 0.8rem;
color: var(--text-secondary);
font-size: 0.8rem;
border-top: 1px solid #f1f5f9;
}

#${uniqueId} .no-results {
grid-column: 1 / -1;
text-align: center;
padding: 1rem;
color: var(--text-secondary);
font-size: 0.9rem;
}

@media (max-width: 1200px) {
#${uniqueId} .links-container {
grid-template-columns: repeat(5, 1fr);
}
}

@media (max-width: 992px) {
#${uniqueId} .links-container {
grid-template-columns: repeat(4, 1fr);
}
}

@media (max-width: 768px) {
#${uniqueId} .links-container {
grid-template-columns: repeat(3, 1fr);
}
}

@media (max-width: 576px) {
#${uniqueId} .links-container {
grid-template-columns: repeat(2, 1fr);
}

#${uniqueId} h1 {
font-size: 1.5rem;
}

#${uniqueId} .category-title {
font-size: 1rem;
}

#${uniqueId} .search-box input {
padding: 0.5rem 1rem;
font-size: 0.85rem;
}
}
</style>
<div id="${uniqueId}">
<div class="container">
<header>
<div class="search-box">
<input type="text" placeholder="搜索框架、工具或资源..." id="search-input">
</div>
</header>
<!-- 分类内容将通过JavaScript动态生成 -->
</div>
</div>
`;
// 获取导航数据
function getNavData() {
return {
categories: [
{
title: "综合平台",
icon: "🏆",
links: [
{
name: "bilibili",
url: "https://www.bilibili.com/?spm_id_from=333.337.0.0",
icon: "https://pic1.imgdb.cn/item/68233dc758cb8da5c8f09fc4.webp",
description: "来哔哩哔哩,学习也要干杯?"
},
{
name: "CSDN",
url: "https://www.csdn.net/",
icon: "https://g.csdnimg.cn/static/logo/favicon32.ico",
description: "开发者技术社区"
},
]
},
{
title: "娱乐网站",
icon: "🎮",
links: [
{
name: "PRTS",
url: "https://prts.wiki/w/%E9%A6%96%E9%A1%B5",
icon: "https://prts.wiki/ioslogo.png",
description: "明日方舟辅助网站"
},
{
name: "星尘幻梦",
url: "https://re.xcdream.com/",
icon: "https://dl.xcdream.com/2025/01/20250106043320606.png",
description: "Window文件夹、桌面、指针美化"
},
]
},
{
title: "朋友图",
icon: "👤",
links: [
{
name: "安知鱼`Blog",
url: "https://blog.anheyu.com/",
icon: "https://npm.elemecdn.com/anzhiyu-blog-static@1.0.4/img/avatar.jpg",
description: "生活明朗,万物可爱"
},
]
}
]
};
}
// 渲染导航内容
function renderNav() {
const navData = getNavData();
const navElement = shadowRoot.querySelector(`#${uniqueId} .container`);

let html = '';
navData.categories.forEach(category => {
html += `
<div class="category" data-category="${category.title.toLowerCase().replace(/\s+/g, '-')}">
<div class="category-header">
<span class="category-icon">${category.icon}</span>
<h2 class="category-title">${category.title}</h2>
</div>
<div class="links-container">
`;

category.links.forEach(link => {
html += `
<div class="link-card" data-name="${link.name.toLowerCase()}">
<a href="${link.url}" target="_blank" rel="noopener noreferrer">
${link.icon ? `<img src="${link.icon}" alt="${link.name}" class="link-icon">` : ''}
<h3 class="link-title">${link.name}</h3>
${link.description ? `<p class="link-description">${link.description}</p>` : ''}
</a>
</div>
`;
});

html += `
</div>
</div>
`;
});

html += `
<footer>
<p>© ${new Date().getFullYear()} 前端开发者导航 | 持续更新优质前端资源</p>
</footer>
`;

navElement.innerHTML += html;
}
// 设置搜索功能
function setupSearch() {
const searchInput = shadowRoot.querySelector(`#${uniqueId} #search-input`);
const linkCards = shadowRoot.querySelectorAll(`#${uniqueId} .link-card`);
const categories = shadowRoot.querySelectorAll(`#${uniqueId} .category`);

searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase().trim();

if (!searchTerm) {
// 恢复所有显示
linkCards.forEach(card => card.style.display = 'block');
categories.forEach(cat => {
cat.style.display = 'block';
cat.querySelector('.category-header').style.display = 'flex';
});
updateNoResultsMessage(true);
return;
}

let hasResults = false;

categories.forEach(category => {
const cards = category.querySelectorAll('.link-card');
let categoryHasResults = false;

cards.forEach(card => {
const name = card.dataset.name;
const description = card.querySelector('.link-description')?.textContent.toLowerCase() || '';

// 搜索名称或描述
if (name.includes(searchTerm) ||
description.includes(searchTerm)) {
card.style.display = 'block';
categoryHasResults = true;
} else {
card.style.display = 'none';
}
});

if (categoryHasResults) {
// 显示分类标题
category.querySelector('.category-header').style.display = 'flex';
category.style.display = 'block';
hasResults = true;
} else {
category.style.display = 'none';
}
});

updateNoResultsMessage(hasResults);
});
}
// 更新无结果消息
function updateNoResultsMessage(hasResults) {
let noResults = shadowRoot.querySelector(`#${uniqueId} .no-results`);

if (!hasResults) {
if (!noResults) {
noResults = document.createElement('div');
noResults.className = 'no-results';
noResults.textContent = '没有找到匹配的资源,请尝试其他关键词';
shadowRoot.querySelector(`#${uniqueId} .container`).appendChild(noResults);
}
} else if (noResults) {
noResults.remove();
}
}
// 初始化导航
renderNav();
setupSearch();
})();
</script>
{% endraw %}

效果见本博客我の名言页面,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
{% raw %}
<!-- 这是我们的挂载点 -->
<div id="quotes-container"></div>
<script>
(function() {
// 创建隔离的作用域
const container = document.getElementById('quotes-container');

// 创建唯一的ID前缀,用于样式隔离
const uniqueId = 'quotes-' + Math.random().toString(36).substr(2, 9);

// 添加HTML结构和样式
container.innerHTML = `
<style id="${uniqueId}-styles">
/* 新增外部容器样式 - 浅绿色主题 */
#quotes-container {
background: linear-gradient(135deg, #f0fff4 0%, #f8fffa 100%);
padding: 5px;
border-radius: 18px;
margin: 25px 0;
box-shadow: 0 6px 20px rgba(100, 237, 149, 0.25);
transition: all 0.3s ease;
border: 1px solid #c9ffdd;
}
#quotes-container:hover {
background: linear-gradient(135deg, #e6ffed 0%, #f0fff4 100%);
box-shadow: 0 8px 25px rgba(100, 237, 149, 0.35);
}

#${uniqueId} {
display: block;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--primary: #48bb78; /* 绿色主题 */
--secondary: #4299e1;
--neutral-100: #f0fff4;
--neutral-200: #e2e8f0;
--neutral-800: #2d3748;
--neutral-900: #1a202c;
--red-500: #f56565;
}

#${uniqueId} .container {
max-width: 56rem;
margin: 0 auto;
padding: 30px;
background-color: white;
border-radius: 15px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
border: 1px solid #e2e8f0;
}

/* 卡片样式 */
#${uniqueId} .card {
background: white;
border-radius: 0.75rem;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.03);
padding: 1.5rem;
margin-bottom: 2rem;
position: relative;
transition: all 0.3s ease;
border: 1px solid #edf2f7;
}

#${uniqueId} .card:hover {
transform: scale(1.01);
box-shadow: 0 15px 30px -5px rgba(0, 0, 0, 0.1), 0 10px 15px -6px rgba(0, 0, 0, 0.05);
}

#${uniqueId} .quote-icon {
font-size: 1.5rem;
color: rgba(72, 187, 120, 0.2);
margin-right: 1rem;
margin-top: 0.25rem;
}

#${uniqueId} .quote-text {
font-size: 1.25rem;
line-height: 1.75;
color: var(--neutral-800);
font-weight: 500;
margin-bottom: 1rem;
transition: all 0.5s ease;
}

#${uniqueId} .quote-author {
color: var(--neutral-800);
font-weight: 300;
font-style: italic;
transition: all 0.5s ease;
opacity: 0.8;
}

/* 按钮样式 */
#${uniqueId} .btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.5rem 1rem;
border-radius: 9999px;
transition: all 0.2s ease;
cursor: pointer;
}

#${uniqueId} .like-btn {
color: #a0aec0;
background: none;
border: none;
padding: 0;
}

#${uniqueId} .like-btn:hover {
color: var(--red-500);
}

#${uniqueId} .refresh-btn {
position: absolute;
bottom: 1rem;
right: 1rem;
padding: 0.5rem;
border-radius: 9999px;
background-color: rgba(72, 187, 120, 0.1);
color: var(--primary);
border: none;
}

#${uniqueId} .refresh-btn:hover {
background-color: var(--primary);
color: white;
}

#${uniqueId} .category-btn {
background: white;
color: var(--neutral-800);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
border: 1px solid var(--neutral-200);
padding: 0.5rem 1rem;
border-radius: 0.375rem;
transition: all 0.2s ease;
}

#${uniqueId} .category-btn:hover {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
background-color: var(--primary);
color: white;
transform: translateY(-2px);
}

#${uniqueId} .category-btn.active {
background-color: var(--primary);
color: white;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}

/* 列表样式 */
#${uniqueId} .quotes-list {
display: flex;
flex-direction: column;
gap: 1rem;
}

#${uniqueId} .quote-item {
background: white;
padding: 1rem;
border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
border: 1px solid #edf2f7;
}

#${uniqueId} .quote-item:hover {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transform: translateY(-0.25rem);
}

#${uniqueId} .quote-tag {
font-size: 0.75rem;
color: var(--primary);
background-color: rgba(72, 187, 120, 0.1);
padding: 0.25rem 0.5rem;
border-radius: 9999px;
margin-right: 0.25rem;
}

/* 分页样式 */
#${uniqueId} .pagination {
display: flex;
justify-content: center;
margin-top: 2rem;
gap: 0.5rem;
}

#${uniqueId} .page-btn {
padding: 0.5rem 1rem;
border: 1px solid var(--neutral-200);
background: white;
border-radius: 0.375rem;
cursor: pointer;
transition: all 0.2s ease;
}

#${uniqueId} .page-btn:hover {
background-color: var(--neutral-100);
}

#${uniqueId} .page-btn.active {
background-color: var(--primary);
color: white;
border-color: var(--primary);
}

#${uniqueId} .page-btn.disabled {
opacity: 0.5;
cursor: not-allowed;
}

/* 动画 */
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
}

#${uniqueId} .animate-pulse {
animation: pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

/* 工具类 */
#${uniqueId} .flex {
display: flex;
}

#${uniqueId} .items-start {
align-items: flex-start;
}

#${uniqueId} .justify-between {
justify-content: space-between;
}

#${uniqueId} .items-center {
align-items: center;
}

#${uniqueId} .flex-wrap {
flex-wrap: wrap;
}

#${uniqueId} .gap-2 {
gap: 0.5rem;
}

#${uniqueId} .mb-4 {
margin-bottom: 1rem;
}

#${uniqueId} .mb-8 {
margin-bottom: 2rem;
}

#${uniqueId} .mr-4 {
margin-right: 1rem;
}

#${uniqueId} .mt-1 {
margin-top: 0.25rem;
}

#${uniqueId} .text-lg {
font-size: 1.125rem;
}

#${uniqueId} .font-medium {
font-weight: 500;
}

#${uniqueId} .text-neutral-800 {
color: var(--neutral-800);
}

#${uniqueId} .text-neutral-600 {
color: var(--neutral-600);
}

#${uniqueId} .text-sm {
font-size: 0.875rem;
}

#${uniqueId} .italic {
font-style: italic;
}

#${uniqueId} .text-xl {
font-size: 1.5rem; /* 20px */
line-height: 1.75rem; /* 28px */
}
#${uniqueId} .quote-item {
background: white;
padding: 1.25rem;
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 2px solid #edf2f7;
margin-bottom: 1rem;
}

#${uniqueId} .quote-item:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
border-color: var(--primary);
}

#${uniqueId} .quote-item p {
font-size: 1.1rem;
line-height: 1.8;
color: var(--neutral-800);
margin-bottom: 0.75rem;
}

#${uniqueId} .quote-item .text-neutral-600 {
font-size: 0.95rem;
color: var(--neutral-800);
opacity: 0.9;
}

#${uniqueId} .quote-tag {
font-size: 0.8rem;
font-weight: 500;
color: var(--primary);
background-color: rgba(72, 187, 120, 0.15);
padding: 0.3rem 0.7rem;
border-radius: 1rem;
margin-right: 0.5rem;
transition: all 0.2s ease;
}

#${uniqueId} .quote-item:hover .quote-tag {
background-color: rgba(72, 187, 120, 0.25);
}

#${uniqueId} .font-bold {
font-weight: 700;
}
#${uniqueId} .leading-relaxed {
line-height: 1.625;
}
#${uniqueId} .card {
background: white;
border-radius: 0.75rem;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
padding: 1.75rem;
margin-bottom: 2.5rem;
position: relative;
transition: all 0.3s ease;
border: 2px solid #edf2f7;
}

#${uniqueId} .card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
border-color: var(--primary);
}

#${uniqueId} .quote-text {
font-size: 1.3rem;
line-height: 1.8;
color: var(--neutral-900);
font-weight: 500;
margin-bottom: 1.25rem;
transition: all 0.5s ease;
}

#${uniqueId} .quote-author {
font-size: 1.05rem;
color: var(--neutral-800);
font-weight: 400;
font-style: italic;
transition: all 0.5s ease;
}

#${uniqueId} .refresh-btn {
position: absolute;
bottom: 1.25rem;
right: 1.25rem;
padding: 0.6rem;
border-radius: 9999px;
background-color: rgba(72, 187, 120, 0.15);
color: var(--primary);
border: none;
transition: all 0.2s ease;
}

#${uniqueId} .refresh-btn:hover {
background-color: var(--primary);
color: white;
transform: rotate(90deg);
}
@media (max-width: 500px) {
#quotes-container {
padding: 15px;
margin: 15px 0;
}

#${uniqueId} .container {
padding: 20px;
}

#${uniqueId} .quote-text {
font-size: 1rem;
}

#${uniqueId} .card {
padding: 1rem;
}
}
</style>
<div id="${uniqueId}">
<div class="container">
<!-- 名言卡片 -->
<div id="quote-card" class="card">
<div class="flex items-start">
<i class="fa fa-quote-left quote-icon"></i>
<div>
<p id="quote-text" class="quote-text">
知不足而奋进,望远山而前行!
</p>
<div class="flex justify-between items-center">
<p id="quote-author" class="quote-author">
—— 孙伟
</p>
<button id="like-btn" class="like-btn">
<i class="fa fa-heart-o"></i>
</button>
</div>
</div>
</div>
<!-- 随机名言按钮 -->
<button id="refresh-btn" class="refresh-btn">
<i class="fa fa-refresh"></i>
</button>
</div>
<!-- 分类标签 -->
<div class="mb-8">
<p class="text-xl font-bold text-neutral-800 mb-4">名言分类</p>
<div class="flex flex-wrap gap-2" id="category-container">
<button class="category-btn active">
全部
</button>
</div>
</div>
<!-- 名言列表 -->
<div>
<div id="quotes-list" class="quotes-list">
<!-- 名言项将通过JavaScript动态生成 -->
</div>
<!-- 分页导航 -->
<div id="pagination" class="pagination">
<!-- 分页按钮将通过JavaScript动态生成 -->
</div>
</div>
</div>
</div>
`;
// 获取元素时加上唯一ID前缀
const getElement = (id) => document.querySelector(`#${uniqueId} #${id}`);

// 名言数据
const quotes = [
{
text: "你全力做到的最好,可能还不如别人的随便搞搞。",
author: "网友",
categories: ["毒鸡汤"]
},
{
text: "懒是一个很好的托辞,说得好像勤快了,就真能干出什么大事儿一样。",
author: "网友",
categories: ["毒鸡汤"]
},
{
text: "总有这样的人,该干活的时候职业不起来,玩的时候又放不开。",
author: "北大学长",
categories: ["学习方法"]
},
{
text: "在发现我没有道德后,对方放弃了道德绑架。",
author: "b站",
categories: ["格局"]
},
{
text: "等生活中真有了生老病死这样的大事,才知道自己以前的忧伤都是狗屁。",
author: "网友",
categories: ["格局"]
},
{
text: "失败并不可怕,可怕的是你还相信这句话。",
author: "网友",
categories: ["毒鸡汤"]
},
{
text: "命,是弱者的借口;运,是强者的谦卑。",
author: "网络",
categories: ["励志"]
},
{
text: "锦上添花永远比不上雪中送炭。",
author: "沙雕视频-云深不知处,然有六出焉",
categories: ["格局"]
},
{
text: "还万一成功,那九千九百九十九的失败概率你是提都不提。",
author: "沙雕视频-云深不知处,然有六出焉",
categories: ["毒鸡汤"]
},
{
text: "你以为有钱人很快乐吗?他们的快乐你根本想象不到。",
author: "网友",
categories: ["毒鸡汤"]
},
{
text: "没有必要的东西就敷衍而过,必要的东西就尽力而做!",
author: "番剧-冰菓",
categories: ["学习方法"]
},
{
text: "世界上的事情,最忌讳的就是个十全十美,你看那天上的月亮,一旦圆满了,马上就要亏厌;树上的果子,一旦熟透了,马上就要坠落。凡事总要稍留欠缺,才能持恒。",
author: "莫言《晚熟的人》",
categories: ["格局"]
},
{
text: "我们花了两年学会说话,却要花上六十年来学会闭嘴。大多数时候,我们说得越多,彼此的距离却越远,矛盾也越多。在沟通中,大多数人总是急于表达自己,一吐为快,却一点也不懂对方。两年学说话,一生学闭嘴。懂与不懂,不多说。心乱心静,慢慢说。若真没话,就别说。",
author: "莫言《晚熟的人》",
categories: ["格局"]
},
{
text: "除了生死,都是擦伤!",
author: "孙伟",
categories: ["格局"]
},
];
// 获取元素
const quoteText = getElement('quote-text');
const quoteAuthor = getElement('quote-author');
const refreshBtn = getElement('refresh-btn');
const likeBtn = getElement('like-btn');
const categoryContainer = getElement('category-container');
const quotesList = getElement('quotes-list');
const pagination = getElement('pagination');
// 状态
let currentQuoteIndex = 0;
let isLiked = false;
let currentCategory = '全部';
let currentPage = 1;
const itemsPerPage = 6;
// 所有可用标签
const allCategories = Array.from(new Set(quotes.flatMap(quote => quote.categories)));
// 初始化分类按钮
function initCategoryButtons() {
// 清空现有按钮(保留"全部"按钮)
categoryContainer.innerHTML = '';
// 添加"全部"按钮
const allButton = document.createElement('button');
allButton.className = 'category-btn';
allButton.textContent = '全部';
allButton.addEventListener('click', () => {
currentPage = 1;
setActiveCategory('全部');
renderQuotesList(quotes);
renderPagination(quotes);
});
categoryContainer.appendChild(allButton);
// 添加其他分类按钮
allCategories.forEach(category => {
const button = document.createElement('button');
button.className = 'category-btn';
button.textContent = category;
button.addEventListener('click', () => {
currentPage = 1;
setActiveCategory(category);
const filteredQuotes = filterQuotesByCategory(category);
renderQuotesList(filteredQuotes);
renderPagination(filteredQuotes);
});
categoryContainer.appendChild(button);
});
// 默认激活"全部"按钮
setActiveCategory('全部');
}
function setActiveCategory(category) {
currentCategory = category;
const buttons = categoryContainer.querySelectorAll('.category-btn');
buttons.forEach(btn => {
if (btn.textContent === category) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
}
function filterQuotesByCategory(category) {
if (category === '全部') {
return quotes;
} else {
return quotes.filter(quote => quote.categories.includes(category));
}
}
function setRandomQuote() {
// 生成随机索引
let randomIndex;
do {
randomIndex = Math.floor(Math.random() * quotes.length);
} while (randomIndex === currentQuoteIndex);
currentQuoteIndex = randomIndex;
const quote = quotes[randomIndex];
// 添加淡入淡出效果
quoteText.style.opacity = '0';
quoteAuthor.style.opacity = '0';
setTimeout(() => {
// 更新名言内容
quoteText.textContent = quote.text;
quoteAuthor.textContent = `—— ${quote.author}`;
// 淡入效果
quoteText.style.opacity = '1';
quoteAuthor.style.opacity = '1';
}, 300);
}
function toggleLike() {
isLiked = !isLiked;
if (isLiked) {
likeBtn.innerHTML = '<i class="fa fa-heart" style="color: var(--red-500);"></i>';
// 添加心跳动画
likeBtn.querySelector('i').classList.add('animate-pulse');
setTimeout(() => {
likeBtn.querySelector('i').classList.remove('animate-pulse');
}, 1000);
} else {
likeBtn.innerHTML = '<i class="fa fa-heart-o"></i>';
}
}
function renderQuotesList(quotesToRender) {
quotesList.innerHTML = '';
// 计算当前页显示的名言范围
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, quotesToRender.length);
const currentQuotes = quotesToRender.slice(startIndex, endIndex);
if (currentQuotes.length === 0) {
const emptyMessage = document.createElement('div');
emptyMessage.className = 'quote-item';
emptyMessage.textContent = '没有找到相关名言';
quotesList.appendChild(emptyMessage);
return;
}
currentQuotes.forEach(quote => {
const quoteItem = document.createElement('div');
quoteItem.className = 'quote-item';
// 创建标签元素
const tagsHtml = quote.categories.map(category =>
`<span class="quote-tag">${category}</span>`
).join('');
quoteItem.innerHTML = `
<p class="text-neutral-800 mb-2">${quote.text}</p>
<div class="flex justify-between items-center">
<p class="text-neutral-600 text-sm italic">—— ${quote.author}</p>
<div class="flex">
${tagsHtml}
</div>
</div>
`;
quotesList.appendChild(quoteItem);
});
}
function renderPagination(quotesToRender) {
pagination.innerHTML = '';
const totalPages = Math.ceil(quotesToRender.length / itemsPerPage);
if (totalPages <= 1) {
return;
}
// 上一页按钮
const prevButton = document.createElement('button');
prevButton.className = `page-btn ${currentPage === 1 ? 'disabled' : ''}`;
prevButton.innerHTML = '<i class="fa fa-chevron-left"></i>';
prevButton.addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
renderQuotesList(quotesToRender);
renderPagination(quotesToRender);
}
});
pagination.appendChild(prevButton);
// 页码按钮
for (let i = 1; i <= totalPages; i++) {
const pageButton = document.createElement('button');
pageButton.className = `page-btn ${currentPage === i ? 'active' : ''}`;
pageButton.textContent = i;
pageButton.addEventListener('click', () => {
currentPage = i;
renderQuotesList(quotesToRender);
renderPagination(quotesToRender);
});
pagination.appendChild(pageButton);
}
// 下一页按钮
const nextButton = document.createElement('button');
nextButton.className = `page-btn ${currentPage === totalPages ? 'disabled' : ''}`;
nextButton.innerHTML = '<i class="fa fa-chevron-right"></i>';
nextButton.addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
renderQuotesList(quotesToRender);
renderPagination(quotesToRender);
}
});
pagination.appendChild(nextButton);
}
// 初始化应用
function init() {
// 添加Font Awesome
const faLink = document.createElement('link');
faLink.rel = 'stylesheet';
faLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css';
document.head.appendChild(faLink);
// 设置随机名言
setRandomQuote();
// 绑定事件
refreshBtn.addEventListener('click', setRandomQuote);
likeBtn.addEventListener('click', toggleLike);
// 初始化分类按钮
initCategoryButtons();
// 初始渲染名言列表和分页
renderQuotesList(quotes);
renderPagination(quotes);
}
// 启动应用
init();
})();
</script>
{% endraw %}

效果见本博客关于我页面,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
{% raw %}
<!-- 独立个人介绍div - 使用Shadow DOM实现完全隔离 -->
<div id="personal-profile-container"></div>
<script>
// 使用IIFE封装所有代码,避免污染全局作用域
(function() {
// 创建Shadow DOM容器
const container = document.getElementById('personal-profile-container');
const shadowRoot = container.attachShadow({ mode: 'open' });

// 添加样式和内容到Shadow DOM
shadowRoot.innerHTML = `
<style>
/* 导入字体图标 - 只在Shadow DOM内生效 */
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css');

/* 个人介绍组件的样式 - 只作用于Shadow DOM内部 */
:host {
--primary-color: #4a6bdf;
--secondary-color: #3a56c0;
--text-color: #333;
--light-bg: #f9f9f9;
--border-color: #e1e4e8;
--icon-bg: #f0f4ff;

display: block;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: white;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
overflow: hidden;
margin: 20px 0;
padding: 25px;
width: 1000px;
max-width: 100%;
box-sizing: border-box;
}

.profile-header {
display: flex;
flex-wrap: wrap;
gap: 30px;
margin-bottom: 25px;
}

.avatar-container {
width: 200px;
flex-shrink: 0;
}

.avatar {
width: 200px;
height: 270px;
border-radius: 10px;
object-fit: cover;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
border: 3px solid white;
}

.avatar:hover {
transform: scale(1.03);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
}

.info-container {
flex: 1;
min-width: 300px;
}

h2 {
color: var(--primary-color);
margin-top: 0;
margin-bottom: 15px;
font-size: 28px;
}

.bio {
color: #555;
line-height: 1.6;
margin-bottom: 20px;
}

.basic-info {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 15px;
margin-bottom: 15px;
}

.info-item {
display: flex;
align-items: center;
font-size: 15px;
}

.info-item i {
display: inline-flex;
align-items: center;
justify-content: center;
margin-right: 12px;
color: white;
background-color: var(--primary-color);
width: 32px;
height: 32px;
border-radius: 50%;
text-align: center;
font-size: 16px;
transition: all 0.2s ease;
}

.info-item:hover i {
background-color: var(--secondary-color);
transform: scale(1.1);
}

.section-title {
color: var(--primary-color);
border-bottom: 2px solid var(--border-color);
padding-bottom: 8px;
margin: 25px 0 18px 0;
font-size: 20px;
display: flex;
align-items: center;
}

.section-title i {
margin-right: 10px;
font-size: 22px;
}

.skills {
display: flex;
flex-wrap: wrap;
gap: 10px;
}

.skill-tag {
background-color: var(--light-bg);
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
transition: all 0.2s ease;
border: 1px solid var(--border-color);
cursor: pointer;
text-decoration: none;
color: inherit;
display: inline-block;
}

.skill-tag:hover {
background-color: var(--primary-color);
color: white;
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-color: var(--primary-color);
}

.links {
display: flex;
flex-wrap: wrap;
gap: 12px;
}

.links a {
display: inline-flex;
align-items: center;
color: var(--primary-color);
text-decoration: none;
padding: 10px 15px;
border-radius: 8px;
transition: all 0.2s ease;
background-color: var(--icon-bg);
border: 1px solid var(--border-color);
}

.links a:hover {
background-color: var(--primary-color);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.links a i {
margin-right: 8px;
font-size: 18px;
}

.achievements {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 15px;
margin-top: 15px;
}

.achievement-item {
background-color: var(--light-bg);
padding: 15px;
border-radius: 8px;
border-left: 4px solid var(--primary-color);
transition: all 0.2s ease;
}

.achievement-item:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}

.achievement-title {
font-weight: bold;
margin-bottom: 5px;
color: var(--primary-color);
}

.achievement-date {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}

.achievement-desc {
font-size: 14px;
line-height: 1.5;
color: #555;
}

@media (max-width: 850px) {
:host {
width: 95%;
padding: 20px;
}

.avatar {
width: 150px;
height: 150px;
}

.avatar-container {
width: 150px;
}

.basic-info {
grid-template-columns: 1fr;
}

.achievements {
grid-template-columns: 1fr;
}
}
</style>

<div id="personal-profile">
<!-- 内容将由JavaScript动态生成 -->
</div>
`;

// 获取Shadow DOM内的元素
const profileContainer = shadowRoot.getElementById('personal-profile');

// 个人资料数据
const profileData = {
name: '孙伟',
birth: '2003-02-20',
qq: '1810915429',
wechat: 'wxid_y3xw0pdsosod22',
email: '1810915429@qq.com',
phone: '19195499101',
skills: [
{ name: 'Ps', url: 'https://blog.sunwei.online/tags/PS/' },
{ name: 'Butterfly', url: 'https://blog.sunwei.online/tags/Butterfly/' },
{ name: 'Markdown', url: 'https://blog.sunwei.online/tags/Markdown/' }
],
bio: '一名普普通通的本科研究生,热爱编程,立志成为一名优秀の程序员。',
achievements: [
{
title: "校级奖学金",
date: "2021年-2025年",
description: "于大学期间获得“三等奖学金”2份,“二等奖学金”1份。"
},
{
title: "全国大学生数学建模竞赛",
date: "2023年9月",
description: "在23年的全国大学生数学建模竞赛中,为解决a题:“定日镜场的优化设计”,作为编程手,获得“国一”。"
},
{
title: "软考中级",
date: "2024年6月",
description: "于软考中级中获得“-软件设计师”证书。"
},
{
title: "英语四级",
date: "2022年12月",
description: "获得全国大学生英语四级证书。"
}
]
};

// 生成HTML内容
profileContainer.innerHTML = `
<div class="profile-header">
<div class="avatar-container">
<img src="https://pic1.imgdb.cn/item/6856a67258cb8da5c860a617.jpg"
alt="头像" class="avatar">
</div>
<div class="info-container">
<h2>${profileData.name}</h2>
<p class="bio">${profileData.bio}</p>

<div class="basic-info">
<div class="info-item">
<i class="fas fa-phone-alt"></i>
<span>${profileData.phone}</span>
</div>
<div class="info-item">
<i class="fas fa-envelope"></i>
<span>${profileData.email}</span>
</div>
<div class="info-item">
<i class="fab fa-qq"></i>
<span>${profileData.qq}</span>
</div>
<div class="info-item">
<i class="fab fa-weixin"></i>
<span>${profileData.wechat}</span>
</div>
<div class="info-item">
<i class="fas fa-birthday-cake"></i>
<span>${profileData.birth}</span>
</div>
<div class="info-item">
<i class="fas fa-map-marker-alt"></i>
<span>中国 · 宜春</span>
</div>
</div>
</div>
</div>

<h3 class="section-title"><i class="fas fa-link"></i>我的网站</h3>
<div class="links">
<a href="https://www.sunwei.online/" target="_blank">
<i class="fas fa-globe"></i>主页
</a>
<a href="https://blog.sunwei.online/" target="_blank">
<i class="fas fa-globe"></i>博客
</a>
<a href="https://status.sunwei.online/" target="_blank">
<i class="fas fa-globe"></i>状态
</a>
<a href="https://qexo.sunwei.online/" target="_blank">
<i class="fas fa-globe"></i>后台
</a>
</div>

<h3 class="section-title"><i class="fas fa-code"></i>我的技能</h3>
<div class="skills">
${profileData.skills.map(skill => `
<a href="${skill.url}" target="_blank" class="skill-tag">${skill.name}</a>
`).join('')}
</div>

<h3 class="section-title"><i class="fas fa-trophy"></i>我的成就</h3>
<div class="achievements">
${profileData.achievements.map(achievement => `
<div class="achievement-item">
<div class="achievement-title">${achievement.title}</div>
<div class="achievement-date">${achievement.date}</div>
<div class="achievement-desc">${achievement.description}</div>
</div>
`).join('')}
</div>
`;

// 添加交互效果 - 只在Shadow DOM内生效
const avatar = profileContainer.querySelector('.avatar');
avatar.addEventListener('click', function() {
this.style.transform = 'scale(0.95)';
setTimeout(() => {
this.style.transform = 'scale(1)';
}, 200);
});

// 为链接添加点击动画
const links = profileContainer.querySelectorAll('.links a');
links.forEach(link => {
link.addEventListener('click', function(e) {
this.style.transform = 'scale(0.98)';
setTimeout(() => {
this.style.transform = '';
}, 200);
});
});

// 为技能标签添加点击效果
const skillTags = profileContainer.querySelectorAll('.skill-tag');
skillTags.forEach(tag => {
tag.addEventListener('click', function() {
this.style.transform = 'scale(0.95)';
setTimeout(() => {
this.style.transform = '';
}, 200);
});
});

// 为成就项添加点击效果
const achievementItems = profileContainer.querySelectorAll('.achievement-item');
achievementItems.forEach(item => {
item.addEventListener('click', function() {
this.style.transform = 'scale(0.98)';
setTimeout(() => {
this.style.transform = '';
}, 200);
});
});
})();
</script>
{% endraw %}

效果见本博客阅读分类中收藏的文章,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

{% raw %}
<div id="short-story-container">
<script>
(function () {
const container = document.getElementById('short-story-container');
const shadowRoot = container.attachShadow({ mode: 'open' });
// 样式调整:魔法书风格
const style = document.createElement('style');
style.textContent = `
:host {
display: block;
font-family: 'Times New Roman', Georgia, serif;
max-width: 900px;
margin: 20px auto;
padding: 0;
position: relative;
background: #f5e7c1 url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23f5e7c1"/><path d="M0,50 Q25,40 50,50 T100,50" stroke="%23e8d9b5" fill="none" stroke-width="1"/></svg>');
border: 15px solid transparent;
border-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="94" height="94" x="3" y="3" fill="none" stroke="%23b38e6d" stroke-width="6" rx="5" ry="5"/><rect width="88" height="88" x="6" y="6" fill="none" stroke="%23d4b78f" stroke-width="4" rx="3" ry="3"/></svg>') 20;
box-shadow: 0 0 25px rgba(0,0,0,0.3),
inset 0 0 50px rgba(179, 142, 109, 0.5),
0 0 10px 5px rgba(179, 142, 109, 0.3);
/* 移除rotateX变换,保持宽度一致 */
}
/* 魔法书装饰元素 */
:host::before {
content: "";
position: absolute;
top: -10px;
left: 50%;
transform: translateX(-50%);
width: 60%;
height: 15px;
background: #b38e6d;
border-radius: 0 0 10px 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
:host::after {
content: "";
position: absolute;
bottom: -10px;
left: 50%;
transform: translateX(-50%);
width: 60%;
height: 15px;
background: #b38e6d;
border-radius: 10px 10px 0 0;
box-shadow: 0 -2px 5px rgba(0,0,0,0.3);
}
/* 标题 + 作者 容器 */
.title-wrap {
border-bottom: 2px solid #b38e6d;
padding: 15px 30px 10px;
margin: 0;
background: linear-gradient(to right, transparent, rgba(179, 142, 109, 0.2), transparent);
position: relative;
}
.title-wrap::before,
.title-wrap::after {
content: "✧";
position: absolute;
top: 50%;
transform: translateY(-50%);
color: #b38e6d;
font-size: 24px;
}
.title-wrap::before {
left: 5px;
}
.title-wrap::after {
right: 5px;
}
.story-title {
text-align: center;
color: #5a3921;
font-size: 32px;
margin: 0 0 5px;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0,0,0,0.1);
letter-spacing: 1px;
font-family: 'SimSun', '宋体', serif;
}
.author {
text-align: center;
font-style: italic;
color: #7f6b5a;
margin: 0;
font-size: 18px;
letter-spacing: 1px;
}
/* 正文容器 */
.story-content {
line-height: 1.8;
color: #4a3a2a;
text-indent: 2em;
background: rgba(245, 231, 193, 0.7);
padding: 25px 40px;
margin: 0;
border-left: 1px solid rgba(179, 142, 109, 0.5);
border-right: 1px solid rgba(179, 142, 109, 0.5);
border-bottom: 1px solid rgba(179, 142, 109, 0.5);
position: relative;
}
.story-content::before {
content: "";
position: absolute;
top: 0;
left: 20px;
right: 20px;
height: 1px;
background: linear-gradient(to right, transparent, #b38e6d, transparent);
}
.story-content p {
margin-bottom: 1em;
font-size: 18px;
}
/* 读后感部分 */
.reflection {
background: rgba(232, 220, 200, 0.7);
padding: 25px 40px;
border-left: 4px solid #b38e6d;
margin: 0;
position: relative;
border-bottom: 1px solid rgba(179, 142, 109, 0.5);
}
.reflection::before {
content: "";
position: absolute;
top: 0;
left: 20px;
right: 20px;
height: 1px;
background: linear-gradient(to right, transparent, #b38e6d, transparent);
}
.reflection-title {
color: #5a3921;
margin-top: 0;
font-family: 'SimSun', '宋体', serif;
text-align: left;
font-size: 24px;
border-bottom: 1px dashed #b38e6d;
padding-bottom: 10px;
}
/* 交互效果 */
.story-title:hover {
color: #8b4513;
cursor: pointer;
text-shadow: 1px 1px 3px rgba(139, 69, 19, 0.3);
}
/* 修改后的魔法书翻页效果 - 只旋转Y轴 */
@keyframes pageTurn {
0% { transform: rotateY(0deg); }
50% { transform: rotateY(10deg); }
100% { transform: rotateY(0deg); }
}
:host:hover {
animation: pageTurn 2s ease-in-out;
}
`;
shadowRoot.appendChild(style);
// HTML 结构
const content = document.createElement('div');
content.innerHTML = `
<div class="title-wrap">
<p class="story-title">努力,终会与成功相遇</p>
<p class="author">作者:余兢兢</p>
</div>
<div class="story-content">
<p>今天的成功是因为昨天的积累,明天的成功则依赖于今天的努力,只要不轻言放弃,坚持如初,追梦到底,成功终会与我们相遇。</p>
<p>三年前,我还是一名刚入学的高中生,天真幼稚,不谙世事,每天除了追剧看小说,就是睡觉打游戏。那时年少,对梦想并没有什么清晰的概念,只知道一味地玩耍疯闹。即使每天听着高三党们书声琅琅,看着他们快步如飞,争分夺秒温书做题,也并没有太多感触,只是天真地以为高考离我还远,考大学也没有那么难。</p>
</div>
<div class="reflection">
<h3 class="reflection-title">我的读后感</h3>
<p>还没看!</p>
<p>还没看!</p>
<p>还没看!</p>
<p>还没看!</p>
</div>
`;
shadowRoot.appendChild(content);
// 交互逻辑
const title = shadowRoot.querySelector('.story-title');
title.addEventListener('click', function() {
this.style.color = this.style.color === 'rgb(139, 69, 19)' ? '#5a3921' : '#8b4513';
});
})();
</script>
</div>
{% endraw %}