@robot-admin/naive-ui-components 0.6.8 → 0.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +277 -261
- package/dist/C_AudioPlayer-DKRIGhi7.css.map +1 -0
- package/dist/C_AudioPlayer-DlB6eNsa.css.map +1 -0
- package/dist/C_AudioPlayer.cjs +1 -0
- package/dist/C_AudioPlayer.css +283 -0
- package/dist/C_AudioPlayer.d.cts +2 -0
- package/dist/C_AudioPlayer.d.ts +2 -0
- package/dist/C_AudioPlayer.js +1 -0
- package/dist/C_AudioPlayer.js.map +1 -0
- package/dist/C_AudioPlayer2.js +2 -0
- package/dist/C_AudioPlayer2.js.map +1 -0
- package/dist/C_AvatarGroup-D02zci-i.css.map +1 -0
- package/dist/C_AvatarGroup-DatHaUlY.css.map +1 -0
- package/dist/C_AvatarGroup.cjs +1 -0
- package/dist/C_AvatarGroup.css +107 -0
- package/dist/C_AvatarGroup.d.cts +2 -0
- package/dist/C_AvatarGroup.d.ts +2 -0
- package/dist/C_AvatarGroup.js +1 -0
- package/dist/C_AvatarGroup.js.map +1 -0
- package/dist/C_AvatarGroup2.js +2 -0
- package/dist/C_AvatarGroup2.js.map +1 -0
- package/dist/C_Barcode.d.cts +1 -1
- package/dist/C_Barcode.d.ts +1 -1
- package/dist/{C_Breadcrumb-B5PGpP5m.css.map → C_Breadcrumb-BiH0d9AI.css.map} +1 -1
- package/dist/{C_Breadcrumb-Cay7qluL.css.map → C_Breadcrumb-Bqqrl8nX.css.map} +1 -1
- package/dist/C_Breadcrumb.css +8 -2
- package/dist/C_Breadcrumb.d.cts +1 -1
- package/dist/C_Breadcrumb.d.ts +1 -1
- package/dist/C_Breadcrumb.js.map +1 -1
- package/dist/C_Breadcrumb2.js.map +1 -1
- package/dist/C_Captcha.d.cts +1 -1
- package/dist/C_Captcha.d.ts +1 -1
- package/dist/C_Cascade.d.cts +1 -1
- package/dist/C_Cascade.d.ts +1 -1
- package/dist/C_Chat-CcjZdAwu.css.map +1 -0
- package/dist/C_Chat-Diq7tyWU.css.map +1 -0
- package/dist/C_Chat.cjs +1 -0
- package/dist/C_Chat.css +395 -0
- package/dist/C_Chat.d.cts +2 -0
- package/dist/C_Chat.d.ts +2 -0
- package/dist/C_Chat.js +1 -0
- package/dist/C_Chat.js.map +1 -0
- package/dist/C_Chat2.js +2 -0
- package/dist/C_Chat2.js.map +1 -0
- package/dist/C_City.d.cts +1 -1
- package/dist/C_City.d.ts +1 -1
- package/dist/C_Code.d.cts +1 -1
- package/dist/C_Code.d.ts +1 -1
- package/dist/C_ContextMenu-B3Cgz_wJ.css.map +1 -0
- package/dist/C_ContextMenu-u1LixF3_.css.map +1 -0
- package/dist/C_ContextMenu.cjs +1 -0
- package/dist/C_ContextMenu.css +115 -0
- package/dist/C_ContextMenu.d.cts +2 -0
- package/dist/C_ContextMenu.d.ts +2 -0
- package/dist/C_ContextMenu.js +1 -0
- package/dist/C_ContextMenu.js.map +1 -0
- package/dist/C_ContextMenu2.js +2 -0
- package/dist/C_ContextMenu2.js.map +1 -0
- package/dist/C_Date.d.cts +1 -1
- package/dist/C_Date.d.ts +1 -1
- package/dist/C_Editor.d.cts +1 -1
- package/dist/C_Editor.d.ts +1 -1
- package/dist/C_Guide.d.cts +1 -1
- package/dist/C_Guide.d.ts +1 -1
- package/dist/C_Icon.d.cts +1 -1
- package/dist/C_Icon.d.ts +1 -1
- package/dist/C_Icon.js.map +1 -1
- package/dist/C_Icon2.js +1 -1
- package/dist/C_Icon2.js.map +1 -1
- package/dist/C_Language.d.cts +1 -1
- package/dist/C_Language.d.ts +1 -1
- package/dist/C_Language.js.map +1 -1
- package/dist/C_Language2.js +1 -1
- package/dist/C_Language2.js.map +1 -1
- package/dist/C_Login-BkdnkBKX.css.map +1 -0
- package/dist/C_Login-VkMM6OJ2.css.map +1 -0
- package/dist/C_Login.cjs +1 -0
- package/dist/C_Login.css +515 -0
- package/dist/C_Login.d.cts +2 -0
- package/dist/C_Login.d.ts +2 -0
- package/dist/C_Login.js +1 -0
- package/dist/C_Login.js.map +1 -0
- package/dist/C_Login2.js +2 -0
- package/dist/C_Login2.js.map +1 -0
- package/dist/C_Markdown.d.cts +1 -1
- package/dist/C_Markdown.d.ts +1 -1
- package/dist/C_Menu.d.cts +1 -1
- package/dist/C_Menu.d.ts +1 -1
- package/dist/C_Progress.d.cts +1 -1
- package/dist/C_Progress.d.ts +1 -1
- package/dist/C_Steps.d.cts +1 -1
- package/dist/C_Steps.d.ts +1 -1
- package/dist/C_TagsView.d.cts +1 -1
- package/dist/C_TagsView.d.ts +1 -1
- package/dist/C_Theme.d.cts +1 -1
- package/dist/C_Theme.d.ts +1 -1
- package/dist/C_Timeline-BIvw3f41.css.map +1 -0
- package/dist/C_Timeline-VsDfCv4Q.css.map +1 -0
- package/dist/C_Timeline.cjs +1 -0
- package/dist/C_Timeline.css +271 -0
- package/dist/C_Timeline.d.cts +2 -0
- package/dist/C_Timeline.d.ts +2 -0
- package/dist/C_Timeline.js +1 -0
- package/dist/C_Timeline.js.map +1 -0
- package/dist/C_Timeline2.js +2 -0
- package/dist/C_Timeline2.js.map +1 -0
- package/dist/C_Transfer-BOB6OBqS.css.map +1 -0
- package/dist/C_Transfer-Dpq7ezWC.css.map +1 -0
- package/dist/C_Transfer.cjs +1 -0
- package/dist/C_Transfer.css +193 -0
- package/dist/C_Transfer.d.cts +2 -0
- package/dist/C_Transfer.d.ts +2 -0
- package/dist/C_Transfer.js +1 -0
- package/dist/C_Transfer.js.map +1 -0
- package/dist/C_Transfer2.js +2 -0
- package/dist/C_Transfer2.js.map +1 -0
- package/dist/C_VtableGantt.d.cts +1 -1
- package/dist/C_VtableGantt.d.ts +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants2.d.ts +2 -2
- package/dist/constants4.d.ts +3 -3
- package/dist/constants5.d.ts +2 -2
- package/dist/data.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -41
- package/dist/index.d.ts +48 -41
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.vue.d.ts +1 -1
- package/dist/index10.vue.d.ts +41 -23
- package/dist/index11.vue.d.ts +80 -63
- package/dist/index12.vue.d.ts +48 -20
- package/dist/index13.vue.d.ts +23 -63
- package/dist/index14.vue.d.ts +21 -58
- package/dist/index15.vue.d.ts +65 -48
- package/dist/index16.vue.d.ts +16 -21
- package/dist/index17.vue.d.ts +106 -77
- package/dist/index18.vue.d.ts +74 -11
- package/dist/index19.vue.d.ts +58 -653
- package/dist/index2.vue.d.ts +75 -30
- package/dist/index20.vue.d.ts +55 -0
- package/dist/index21.vue.d.ts +33 -0
- package/dist/index22.vue.d.ts +88 -0
- package/dist/index23.vue.d.ts +18 -0
- package/dist/index24.vue.d.ts +112 -0
- package/dist/index25.vue.d.ts +81 -0
- package/dist/index26.vue.d.ts +662 -0
- package/dist/index3.vue.d.ts +77 -19
- package/dist/index4.vue.d.ts +29 -36
- package/dist/index5.vue.d.ts +19 -20
- package/dist/index6.vue.d.ts +36 -26
- package/dist/index7.vue.d.ts +23 -40
- package/dist/index8.vue.d.ts +117 -45
- package/dist/index9.vue.d.ts +19 -25
- package/dist/resolver.cjs +1 -1
- package/dist/resolver.cjs.map +1 -1
- package/dist/resolver.d.cts +1 -1
- package/dist/resolver.d.ts +1 -1
- package/dist/resolver.js +1 -1
- package/dist/resolver.js.map +1 -1
- package/dist/style.css +1920 -21
- package/dist/useCollapsePanel.d.ts +1 -1
- package/dist/useCropperCore.d.ts +4 -4
- package/dist/useDraggableLayout.d.ts +8 -8
- package/dist/useDynamicFormState.d.ts +10 -10
- package/dist/useEdgeInteraction.d.ts +1 -1
- package/dist/useInfiniteScroll.d.ts +2 -2
- package/dist/useModalEdit.d.ts +2 -2
- package/dist/useQRCode.d.ts +4 -4
- package/dist/useSearchState.d.ts +2 -2
- package/dist/useSignatureHistory.d.ts +1 -1
- package/dist/useSplitResize.d.ts +5 -5
- package/dist/useTimeSelection.d.ts +2 -2
- package/dist/useTreeOperations.d.ts +6 -6
- package/dist/useWorkflowValidation.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/* unplugin-vue-components disabled *//**
|
|
2
|
+
* C_AudioPlayer — 音频播放器样式
|
|
3
|
+
*/
|
|
4
|
+
.c-audio-player[data-v-3cb14639] {
|
|
5
|
+
--ap-bg: var(--card-color);
|
|
6
|
+
--ap-border: var(--border-color);
|
|
7
|
+
--ap-accent: var(--primary-color);
|
|
8
|
+
--ap-text: var(--text-color-1);
|
|
9
|
+
--ap-text-sub: var(--text-color-3);
|
|
10
|
+
--ap-progress-bg: color-mix(in srgb, var(--primary-color) 15%, var(--border-color));
|
|
11
|
+
--ap-progress-fill: var(--primary-color);
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
border-radius: 12px;
|
|
15
|
+
border: 1px solid var(--ap-border);
|
|
16
|
+
background: var(--ap-bg);
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
|
19
|
+
/* ======================== Now Playing ======================== */
|
|
20
|
+
/* ======================== Progress ======================== */
|
|
21
|
+
/* ======================== Controls ======================== */
|
|
22
|
+
/* ======================== Volume ======================== */
|
|
23
|
+
/* ======================== Playlist ======================== */
|
|
24
|
+
/* ======================== Minimal Theme ======================== */
|
|
25
|
+
}
|
|
26
|
+
.c-audio-player__now-playing[data-v-3cb14639] {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 16px;
|
|
30
|
+
padding: 20px;
|
|
31
|
+
}
|
|
32
|
+
.c-audio-player__cover[data-v-3cb14639] {
|
|
33
|
+
flex-shrink: 0;
|
|
34
|
+
width: 72px;
|
|
35
|
+
height: 72px;
|
|
36
|
+
border-radius: 10px;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
background: color-mix(in srgb, var(--ap-accent) 10%, var(--ap-bg));
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
43
|
+
}
|
|
44
|
+
.c-audio-player__cover img[data-v-3cb14639] {
|
|
45
|
+
width: 100%;
|
|
46
|
+
height: 100%;
|
|
47
|
+
object-fit: cover;
|
|
48
|
+
}
|
|
49
|
+
.c-audio-player__cover-placeholder[data-v-3cb14639] {
|
|
50
|
+
font-size: 32px;
|
|
51
|
+
color: var(--ap-accent);
|
|
52
|
+
opacity: 0.5;
|
|
53
|
+
}
|
|
54
|
+
.c-audio-player__info[data-v-3cb14639] {
|
|
55
|
+
flex: 1;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
}
|
|
58
|
+
.c-audio-player__title[data-v-3cb14639] {
|
|
59
|
+
font-size: 15px;
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
color: var(--ap-text);
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
text-overflow: ellipsis;
|
|
64
|
+
white-space: nowrap;
|
|
65
|
+
}
|
|
66
|
+
.c-audio-player__artist[data-v-3cb14639] {
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
color: var(--ap-text-sub);
|
|
69
|
+
margin-top: 2px;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
text-overflow: ellipsis;
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
}
|
|
74
|
+
.c-audio-player__progress[data-v-3cb14639] {
|
|
75
|
+
padding: 0 20px;
|
|
76
|
+
}
|
|
77
|
+
.c-audio-player__progress-bar[data-v-3cb14639] {
|
|
78
|
+
position: relative;
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 6px;
|
|
81
|
+
border-radius: 3px;
|
|
82
|
+
background: var(--ap-progress-bg);
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
transition: height 0.15s;
|
|
85
|
+
}
|
|
86
|
+
.c-audio-player__progress-bar[data-v-3cb14639]:hover {
|
|
87
|
+
height: 8px;
|
|
88
|
+
}
|
|
89
|
+
.c-audio-player__progress-fill[data-v-3cb14639] {
|
|
90
|
+
height: 100%;
|
|
91
|
+
border-radius: 3px;
|
|
92
|
+
background: var(--ap-progress-fill);
|
|
93
|
+
position: relative;
|
|
94
|
+
transition: width 0.1s linear;
|
|
95
|
+
}
|
|
96
|
+
.c-audio-player__progress-fill[data-v-3cb14639]::after {
|
|
97
|
+
content: "";
|
|
98
|
+
position: absolute;
|
|
99
|
+
right: -5px;
|
|
100
|
+
top: 50%;
|
|
101
|
+
transform: translateY(-50%);
|
|
102
|
+
width: 12px;
|
|
103
|
+
height: 12px;
|
|
104
|
+
border-radius: 50%;
|
|
105
|
+
background: var(--ap-accent);
|
|
106
|
+
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
|
|
107
|
+
opacity: 0;
|
|
108
|
+
transition: opacity 0.15s;
|
|
109
|
+
}
|
|
110
|
+
.c-audio-player__progress-bar:hover .c-audio-player__progress-fill[data-v-3cb14639]::after {
|
|
111
|
+
opacity: 1;
|
|
112
|
+
}
|
|
113
|
+
.c-audio-player__time[data-v-3cb14639] {
|
|
114
|
+
display: flex;
|
|
115
|
+
justify-content: space-between;
|
|
116
|
+
font-size: 11px;
|
|
117
|
+
color: var(--ap-text-sub);
|
|
118
|
+
margin-top: 4px;
|
|
119
|
+
padding-bottom: 4px;
|
|
120
|
+
}
|
|
121
|
+
.c-audio-player__controls[data-v-3cb14639] {
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
justify-content: center;
|
|
125
|
+
gap: 8px;
|
|
126
|
+
padding: 12px 20px;
|
|
127
|
+
}
|
|
128
|
+
.c-audio-player__ctrl-btn[data-v-3cb14639] {
|
|
129
|
+
display: inline-flex;
|
|
130
|
+
align-items: center;
|
|
131
|
+
justify-content: center;
|
|
132
|
+
border: none;
|
|
133
|
+
background: transparent;
|
|
134
|
+
color: var(--ap-text);
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
border-radius: 50%;
|
|
137
|
+
transition: background 0.2s, color 0.2s, transform 0.15s;
|
|
138
|
+
}
|
|
139
|
+
.c-audio-player__ctrl-btn[data-v-3cb14639]:hover {
|
|
140
|
+
color: var(--ap-accent);
|
|
141
|
+
background: color-mix(in srgb, var(--ap-accent) 10%, transparent);
|
|
142
|
+
}
|
|
143
|
+
.c-audio-player__ctrl-btn[data-v-3cb14639]:active {
|
|
144
|
+
transform: scale(0.92);
|
|
145
|
+
}
|
|
146
|
+
.c-audio-player__ctrl-btn.is-mode[data-v-3cb14639] {
|
|
147
|
+
font-size: 18px;
|
|
148
|
+
width: 34px;
|
|
149
|
+
height: 34px;
|
|
150
|
+
}
|
|
151
|
+
.c-audio-player__ctrl-btn.is-skip[data-v-3cb14639] {
|
|
152
|
+
font-size: 22px;
|
|
153
|
+
width: 38px;
|
|
154
|
+
height: 38px;
|
|
155
|
+
}
|
|
156
|
+
.c-audio-player__ctrl-btn.is-play[data-v-3cb14639] {
|
|
157
|
+
font-size: 28px;
|
|
158
|
+
width: 48px;
|
|
159
|
+
height: 48px;
|
|
160
|
+
background: var(--ap-accent);
|
|
161
|
+
color: #fff;
|
|
162
|
+
}
|
|
163
|
+
.c-audio-player__ctrl-btn.is-play[data-v-3cb14639]:hover {
|
|
164
|
+
background: color-mix(in srgb, var(--ap-accent) 85%, #000);
|
|
165
|
+
color: #fff;
|
|
166
|
+
box-shadow: 0 2px 10px color-mix(in srgb, var(--ap-accent) 35%, transparent);
|
|
167
|
+
}
|
|
168
|
+
.c-audio-player__volume[data-v-3cb14639] {
|
|
169
|
+
display: flex;
|
|
170
|
+
align-items: center;
|
|
171
|
+
gap: 6px;
|
|
172
|
+
padding: 0 20px 12px;
|
|
173
|
+
justify-content: flex-end;
|
|
174
|
+
}
|
|
175
|
+
.c-audio-player__volume-icon[data-v-3cb14639] {
|
|
176
|
+
font-size: 18px;
|
|
177
|
+
color: var(--ap-text-sub);
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
}
|
|
180
|
+
.c-audio-player__volume-icon[data-v-3cb14639]:hover {
|
|
181
|
+
color: var(--ap-accent);
|
|
182
|
+
}
|
|
183
|
+
.c-audio-player__volume-slider[data-v-3cb14639] {
|
|
184
|
+
width: 100px;
|
|
185
|
+
}
|
|
186
|
+
.c-audio-player__playlist[data-v-3cb14639] {
|
|
187
|
+
border-top: 1px solid var(--ap-border);
|
|
188
|
+
max-height: 260px;
|
|
189
|
+
overflow-y: auto;
|
|
190
|
+
}
|
|
191
|
+
.c-audio-player__playlist-header[data-v-3cb14639] {
|
|
192
|
+
display: flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
justify-content: space-between;
|
|
195
|
+
padding: 10px 20px;
|
|
196
|
+
font-size: 12px;
|
|
197
|
+
font-weight: 600;
|
|
198
|
+
color: var(--ap-text-sub);
|
|
199
|
+
background: color-mix(in srgb, var(--ap-accent) 4%, var(--ap-bg));
|
|
200
|
+
border-bottom: 1px solid var(--ap-border);
|
|
201
|
+
position: sticky;
|
|
202
|
+
top: 0;
|
|
203
|
+
z-index: 1;
|
|
204
|
+
}
|
|
205
|
+
.c-audio-player__track[data-v-3cb14639] {
|
|
206
|
+
display: flex;
|
|
207
|
+
align-items: center;
|
|
208
|
+
gap: 12px;
|
|
209
|
+
padding: 10px 20px;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
transition: background 0.15s;
|
|
212
|
+
}
|
|
213
|
+
.c-audio-player__track[data-v-3cb14639]:hover {
|
|
214
|
+
background: color-mix(in srgb, var(--ap-accent) 6%, var(--ap-bg));
|
|
215
|
+
}
|
|
216
|
+
.c-audio-player__track.is-active[data-v-3cb14639] {
|
|
217
|
+
background: color-mix(in srgb, var(--ap-accent) 10%, var(--ap-bg));
|
|
218
|
+
}
|
|
219
|
+
.c-audio-player__track.is-active .c-audio-player__track-title[data-v-3cb14639] {
|
|
220
|
+
color: var(--ap-accent);
|
|
221
|
+
font-weight: 600;
|
|
222
|
+
}
|
|
223
|
+
.c-audio-player__track-index[data-v-3cb14639] {
|
|
224
|
+
font-size: 12px;
|
|
225
|
+
width: 20px;
|
|
226
|
+
text-align: center;
|
|
227
|
+
color: var(--ap-text-sub);
|
|
228
|
+
flex-shrink: 0;
|
|
229
|
+
}
|
|
230
|
+
.c-audio-player__track-info[data-v-3cb14639] {
|
|
231
|
+
flex: 1;
|
|
232
|
+
min-width: 0;
|
|
233
|
+
}
|
|
234
|
+
.c-audio-player__track-title[data-v-3cb14639] {
|
|
235
|
+
font-size: 13px;
|
|
236
|
+
color: var(--ap-text);
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
text-overflow: ellipsis;
|
|
239
|
+
white-space: nowrap;
|
|
240
|
+
}
|
|
241
|
+
.c-audio-player__track-artist[data-v-3cb14639] {
|
|
242
|
+
font-size: 11px;
|
|
243
|
+
color: var(--ap-text-sub);
|
|
244
|
+
margin-top: 1px;
|
|
245
|
+
}
|
|
246
|
+
.c-audio-player__track-duration[data-v-3cb14639] {
|
|
247
|
+
font-size: 11px;
|
|
248
|
+
color: var(--ap-text-sub);
|
|
249
|
+
flex-shrink: 0;
|
|
250
|
+
}
|
|
251
|
+
.c-audio-player.is-minimal[data-v-3cb14639] {
|
|
252
|
+
border-radius: 99px;
|
|
253
|
+
padding: 6px 16px;
|
|
254
|
+
flex-direction: row;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: 10px;
|
|
257
|
+
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05);
|
|
258
|
+
}
|
|
259
|
+
.c-audio-player.is-minimal .c-audio-player__now-playing[data-v-3cb14639] {
|
|
260
|
+
padding: 0;
|
|
261
|
+
gap: 10px;
|
|
262
|
+
}
|
|
263
|
+
.c-audio-player.is-minimal .c-audio-player__cover[data-v-3cb14639] {
|
|
264
|
+
width: 36px;
|
|
265
|
+
height: 36px;
|
|
266
|
+
border-radius: 50%;
|
|
267
|
+
}
|
|
268
|
+
.c-audio-player.is-minimal .c-audio-player__progress[data-v-3cb14639],
|
|
269
|
+
.c-audio-player.is-minimal .c-audio-player__playlist[data-v-3cb14639],
|
|
270
|
+
.c-audio-player.is-minimal .c-audio-player__volume[data-v-3cb14639] {
|
|
271
|
+
display: none;
|
|
272
|
+
}
|
|
273
|
+
.c-audio-player.is-minimal .c-audio-player__controls[data-v-3cb14639] {
|
|
274
|
+
padding: 4px 0;
|
|
275
|
+
gap: 2px;
|
|
276
|
+
}
|
|
277
|
+
.c-audio-player.is-minimal .c-audio-player__ctrl-btn.is-play[data-v-3cb14639] {
|
|
278
|
+
width: 34px;
|
|
279
|
+
height: 34px;
|
|
280
|
+
font-size: 20px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/*# sourceMappingURL=C_AudioPlayer-DKRIGhi7.css.map*/
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as MODE_ICON_MAP, i as DEFAULT_AUDIO_PLAYER_PROPS, n as AudioPlayerProps, o as formatTime, r as AudioTrack, t as _default } from "./index2.vue.js";
|
|
2
|
+
export { type AudioPlayerProps, type AudioTrack, _default as C_AudioPlayer, DEFAULT_AUDIO_PLAYER_PROPS, MODE_ICON_MAP, formatTime };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as MODE_ICON_MAP, i as DEFAULT_AUDIO_PLAYER_PROPS, n as AudioPlayerProps, o as formatTime, r as AudioTrack, t as _default } from "./index2.vue.js";
|
|
2
|
+
export { type AudioPlayerProps, type AudioTrack, _default as C_AudioPlayer, DEFAULT_AUDIO_PLAYER_PROPS, MODE_ICON_MAP, formatTime };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./C_Icon2.js";import{i as e,n as t,r as n,t as r}from"./C_AudioPlayer2.js";export{r as C_AudioPlayer,t as DEFAULT_AUDIO_PLAYER_PROPS,n as MODE_ICON_MAP,e as formatTime};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_AudioPlayer.js","names":["theme","showCover","showPlaylist","tracks"],"sources":["../src/components/C_AudioPlayer/types.ts","../src/components/C_AudioPlayer/index.vue","../src/components/C_AudioPlayer/index.vue","../src/components/C_AudioPlayer/index.vue"],"sourcesContent":["/**\n * @Description: Audio player component type definitions\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\n */\n\n/** Single audio track */\nexport interface AudioTrack {\n /** Unique identifier */\n id: string | number\n /** Track title */\n title: string\n /** Artist / author */\n artist?: string\n /** Audio source URL */\n src: string\n /** Cover image URL */\n cover?: string\n /** Duration in seconds (optional, for static display) */\n duration?: number\n /** Extra data */\n extra?: Record<string, unknown>\n}\n\n/** Component props */\nexport interface AudioPlayerProps {\n /** Playlist */\n tracks: AudioTrack[]\n /** Initial active index */\n initialIndex?: number\n /** Whether to show the playlist panel */\n showPlaylist?: boolean\n /** Whether to show the cover art */\n showCover?: boolean\n /** Whether to auto-play on mount */\n autoplay?: boolean\n /** Playback mode */\n mode?: 'list' | 'loop' | 'single' | 'shuffle'\n /** Theme appearance */\n theme?: 'default' | 'minimal'\n}\n\n/** Default props */\nexport const DEFAULT_AUDIO_PLAYER_PROPS: Partial<AudioPlayerProps> = {\n initialIndex: 0,\n showPlaylist: true,\n showCover: true,\n autoplay: false,\n mode: 'list',\n theme: 'default',\n}\n\n/** Playback mode icon map */\nexport const MODE_ICON_MAP: Record<string, { icon: string; label: string }> = {\n list: { icon: 'mdi:repeat', label: 'List Loop' },\n loop: { icon: 'mdi:repeat-once', label: 'Single Loop' },\n single: { icon: 'mdi:numeric-1-box', label: 'Play Once' },\n shuffle: { icon: 'mdi:shuffle-variant', label: 'Shuffle' },\n}\n\n/** Format seconds to mm:ss */\nexport function formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '00:00'\n const m = Math.floor(seconds / 60)\n const s = Math.floor(seconds % 60)\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\n","/* unplugin-vue-components disabled */<!--\n * @Description: 音频播放器组件\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <div\n class=\"c-audio-player\"\n :class=\"{ 'is-minimal': theme === 'minimal' }\"\n >\n <!-- ================ Now Playing ================ -->\n <div class=\"c-audio-player__now-playing\">\n <div\n v-if=\"showCover\"\n class=\"c-audio-player__cover\"\n >\n <img\n v-if=\"currentTrack?.cover\"\n :src=\"currentTrack.cover\"\n :alt=\"currentTrack.title\"\n />\n <C_Icon\n v-else\n name=\"mdi:music-note\"\n class=\"c-audio-player__cover-placeholder\"\n />\n </div>\n <div class=\"c-audio-player__info\">\n <div class=\"c-audio-player__title\">{{\n currentTrack?.title ?? '未选择曲目'\n }}</div>\n <div\n v-if=\"currentTrack?.artist\"\n class=\"c-audio-player__artist\"\n >\n {{ currentTrack.artist }}\n </div>\n </div>\n </div>\n\n <!-- ================ Progress ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__progress\"\n >\n <div\n class=\"c-audio-player__progress-bar\"\n @click=\"seekByClick\"\n >\n <div\n class=\"c-audio-player__progress-fill\"\n :style=\"{ width: `${progressPercent}%` }\"\n />\n </div>\n <div class=\"c-audio-player__time\">\n <span>{{ formatTime(currentTime) }}</span>\n <span>{{ formatTime(totalDuration) }}</span>\n </div>\n </div>\n\n <!-- ================ Controls ================ -->\n <div class=\"c-audio-player__controls\">\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n :title=\"currentModeLabel\"\n @click=\"cycleMode\"\n >\n <C_Icon :name=\"currentModeIcon\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"上一曲\"\n @click=\"prev\"\n >\n <C_Icon name=\"mdi:skip-previous\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-play\"\n :title=\"isPlaying ? '暂停' : '播放'\"\n @click=\"togglePlay\"\n >\n <C_Icon :name=\"isPlaying ? 'mdi:pause' : 'mdi:play'\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"下一曲\"\n @click=\"next\"\n >\n <C_Icon name=\"mdi:skip-next\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n title=\"播放列表\"\n @click=\"playlistVisible = !playlistVisible\"\n >\n <C_Icon name=\"mdi:playlist-music\" />\n </button>\n </div>\n\n <!-- ================ Volume ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__volume\"\n >\n <C_Icon\n :name=\"volumeIcon\"\n class=\"c-audio-player__volume-icon\"\n @click=\"toggleMute\"\n />\n <NSlider\n v-model:value=\"volume\"\n :min=\"0\"\n :max=\"100\"\n :step=\"1\"\n class=\"c-audio-player__volume-slider\"\n @update:value=\"handleVolumeChange\"\n />\n </div>\n\n <!-- ================ Playlist ================ -->\n <div\n v-if=\"showPlaylist && playlistVisible && theme !== 'minimal'\"\n class=\"c-audio-player__playlist\"\n >\n <div class=\"c-audio-player__playlist-header\">\n <span>播放列表</span>\n <span>{{ tracks.length }} 首</span>\n </div>\n <div\n v-for=\"(track, idx) in tracks\"\n :key=\"track.id\"\n class=\"c-audio-player__track\"\n :class=\"{ 'is-active': idx === activeIndex }\"\n @click=\"playTrack(idx)\"\n >\n <span class=\"c-audio-player__track-index\">\n <C_Icon\n v-if=\"idx === activeIndex && isPlaying\"\n name=\"mdi:volume-high\"\n />\n <template v-else>{{ idx + 1 }}</template>\n </span>\n <div class=\"c-audio-player__track-info\">\n <div class=\"c-audio-player__track-title\">{{ track.title }}</div>\n <div\n v-if=\"track.artist\"\n class=\"c-audio-player__track-artist\"\n >\n {{ track.artist }}\n </div>\n </div>\n <span\n v-if=\"track.duration\"\n class=\"c-audio-player__track-duration\"\n >\n {{ formatTime(track.duration) }}\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onBeforeUnmount, ref, watch } from 'vue'\n import C_Icon from '../C_Icon/index.vue'\n import {\n DEFAULT_AUDIO_PLAYER_PROPS,\n formatTime,\n MODE_ICON_MAP,\n type AudioPlayerProps,\n } from './types'\n\n const props = withDefaults(\n defineProps<{\n tracks: AudioPlayerProps['tracks']\n initialIndex?: number\n showPlaylist?: boolean\n showCover?: boolean\n autoplay?: boolean\n mode?: AudioPlayerProps['mode']\n theme?: AudioPlayerProps['theme']\n }>(),\n {\n initialIndex: DEFAULT_AUDIO_PLAYER_PROPS.initialIndex,\n showPlaylist: DEFAULT_AUDIO_PLAYER_PROPS.showPlaylist,\n showCover: true,\n autoplay: DEFAULT_AUDIO_PLAYER_PROPS.autoplay,\n mode: DEFAULT_AUDIO_PLAYER_PROPS.mode,\n theme: DEFAULT_AUDIO_PLAYER_PROPS.theme,\n }\n )\n\n const emit = defineEmits<{\n play: [index: number]\n pause: []\n ended: [index: number]\n modeChange: [mode: string]\n }>()\n\n // ==================== Audio Core ====================\n\n let audio: HTMLAudioElement | null = null\n\n const activeIndex = ref(props.initialIndex ?? 0)\n const isPlaying = ref(false)\n const currentTime = ref(0)\n const totalDuration = ref(0)\n const volume = ref(70)\n const prevVolume = ref(70)\n const playlistVisible = ref(props.showPlaylist)\n const playMode = ref(props.mode ?? 'list')\n\n const currentTrack = computed(() => props.tracks[activeIndex.value])\n\n const progressPercent = computed(() => {\n if (totalDuration.value === 0) return 0\n return (currentTime.value / totalDuration.value) * 100\n })\n\n const volumeIcon = computed(() => {\n if (volume.value === 0) return 'mdi:volume-off'\n if (volume.value < 40) return 'mdi:volume-low'\n if (volume.value < 70) return 'mdi:volume-medium'\n return 'mdi:volume-high'\n })\n\n const currentModeIcon = computed(\n () => MODE_ICON_MAP[playMode.value]?.icon ?? 'mdi:repeat'\n )\n const currentModeLabel = computed(\n () => MODE_ICON_MAP[playMode.value]?.label ?? '循环'\n )\n\n /** 初始化音频实例 */\n function initAudio() {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n }\n const track = currentTrack.value\n if (!track) return\n\n audio = new Audio(track.src)\n audio.volume = volume.value / 100\n audio.addEventListener('timeupdate', onTimeUpdate)\n audio.addEventListener('loadedmetadata', onMetaLoaded)\n audio.addEventListener('ended', onEnded)\n }\n\n /** 播放进度更新回调 */\n function onTimeUpdate() {\n if (audio) currentTime.value = audio.currentTime\n }\n\n /** 音频元数据加载完成回调 */\n function onMetaLoaded() {\n if (audio) totalDuration.value = audio.duration\n }\n\n /** 播放结束回调,根据模式决定下一步 */\n function onEnded() {\n emit('ended', activeIndex.value)\n switch (playMode.value) {\n case 'loop':\n audio?.play().catch(() => {})\n break\n case 'single':\n isPlaying.value = false\n break\n case 'shuffle':\n playTrack(Math.floor(Math.random() * props.tracks.length))\n break\n default: // list\n if (activeIndex.value < props.tracks.length - 1) next()\n else {\n activeIndex.value = 0\n initAudio()\n audio?.play().catch(() => {})\n }\n }\n }\n\n /** 切换播放/暂停 */\n function togglePlay() {\n if (!audio) initAudio()\n if (isPlaying.value) {\n audio?.pause()\n isPlaying.value = false\n emit('pause')\n } else {\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', activeIndex.value)\n }\n }\n\n /** 播放指定索引的曲目 */\n function playTrack(idx: number) {\n activeIndex.value = idx\n initAudio()\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', idx)\n }\n\n /** 上一曲 */\n function prev() {\n const idx =\n activeIndex.value <= 0 ? props.tracks.length - 1 : activeIndex.value - 1\n playTrack(idx)\n }\n\n /** 下一曲 */\n function next() {\n const idx =\n activeIndex.value >= props.tracks.length - 1 ? 0 : activeIndex.value + 1\n playTrack(idx)\n }\n\n /** 点击进度条跳转播放位置 */\n function seekByClick(e: MouseEvent) {\n if (!audio || !totalDuration.value) return\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()\n const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n audio.currentTime = ratio * totalDuration.value\n currentTime.value = audio.currentTime\n }\n\n /** 处理音量变更 */\n function handleVolumeChange(val: number) {\n volume.value = val\n if (audio) audio.volume = val / 100\n }\n\n /** 切换静音状态 */\n function toggleMute() {\n if (volume.value > 0) {\n prevVolume.value = volume.value\n volume.value = 0\n } else {\n volume.value = prevVolume.value || 70\n }\n if (audio) audio.volume = volume.value / 100\n }\n\n const MODES = ['list', 'loop', 'single', 'shuffle'] as const\n /** 循环切换播放模式 */\n function cycleMode() {\n const idx = MODES.indexOf(playMode.value as (typeof MODES)[number])\n playMode.value = MODES[(idx + 1) % MODES.length]\n emit('modeChange', playMode.value)\n }\n\n // ==================== Lifecycle ====================\n\n watch(\n () => props.tracks,\n () => {\n activeIndex.value = 0\n initAudio()\n }\n )\n\n watch(\n () => props.initialIndex,\n val => {\n if (val !== undefined && val !== activeIndex.value) {\n activeIndex.value = val\n initAudio()\n }\n }\n )\n\n onBeforeUnmount(() => {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n audio = null\n }\n })\n\n // 自动播放\n if (props.autoplay && props.tracks.length > 0) {\n playTrack(activeIndex.value)\n }\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n","/* unplugin-vue-components disabled */<!--\n * @Description: 音频播放器组件\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <div\n class=\"c-audio-player\"\n :class=\"{ 'is-minimal': theme === 'minimal' }\"\n >\n <!-- ================ Now Playing ================ -->\n <div class=\"c-audio-player__now-playing\">\n <div\n v-if=\"showCover\"\n class=\"c-audio-player__cover\"\n >\n <img\n v-if=\"currentTrack?.cover\"\n :src=\"currentTrack.cover\"\n :alt=\"currentTrack.title\"\n />\n <C_Icon\n v-else\n name=\"mdi:music-note\"\n class=\"c-audio-player__cover-placeholder\"\n />\n </div>\n <div class=\"c-audio-player__info\">\n <div class=\"c-audio-player__title\">{{\n currentTrack?.title ?? '未选择曲目'\n }}</div>\n <div\n v-if=\"currentTrack?.artist\"\n class=\"c-audio-player__artist\"\n >\n {{ currentTrack.artist }}\n </div>\n </div>\n </div>\n\n <!-- ================ Progress ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__progress\"\n >\n <div\n class=\"c-audio-player__progress-bar\"\n @click=\"seekByClick\"\n >\n <div\n class=\"c-audio-player__progress-fill\"\n :style=\"{ width: `${progressPercent}%` }\"\n />\n </div>\n <div class=\"c-audio-player__time\">\n <span>{{ formatTime(currentTime) }}</span>\n <span>{{ formatTime(totalDuration) }}</span>\n </div>\n </div>\n\n <!-- ================ Controls ================ -->\n <div class=\"c-audio-player__controls\">\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n :title=\"currentModeLabel\"\n @click=\"cycleMode\"\n >\n <C_Icon :name=\"currentModeIcon\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"上一曲\"\n @click=\"prev\"\n >\n <C_Icon name=\"mdi:skip-previous\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-play\"\n :title=\"isPlaying ? '暂停' : '播放'\"\n @click=\"togglePlay\"\n >\n <C_Icon :name=\"isPlaying ? 'mdi:pause' : 'mdi:play'\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"下一曲\"\n @click=\"next\"\n >\n <C_Icon name=\"mdi:skip-next\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n title=\"播放列表\"\n @click=\"playlistVisible = !playlistVisible\"\n >\n <C_Icon name=\"mdi:playlist-music\" />\n </button>\n </div>\n\n <!-- ================ Volume ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__volume\"\n >\n <C_Icon\n :name=\"volumeIcon\"\n class=\"c-audio-player__volume-icon\"\n @click=\"toggleMute\"\n />\n <NSlider\n v-model:value=\"volume\"\n :min=\"0\"\n :max=\"100\"\n :step=\"1\"\n class=\"c-audio-player__volume-slider\"\n @update:value=\"handleVolumeChange\"\n />\n </div>\n\n <!-- ================ Playlist ================ -->\n <div\n v-if=\"showPlaylist && playlistVisible && theme !== 'minimal'\"\n class=\"c-audio-player__playlist\"\n >\n <div class=\"c-audio-player__playlist-header\">\n <span>播放列表</span>\n <span>{{ tracks.length }} 首</span>\n </div>\n <div\n v-for=\"(track, idx) in tracks\"\n :key=\"track.id\"\n class=\"c-audio-player__track\"\n :class=\"{ 'is-active': idx === activeIndex }\"\n @click=\"playTrack(idx)\"\n >\n <span class=\"c-audio-player__track-index\">\n <C_Icon\n v-if=\"idx === activeIndex && isPlaying\"\n name=\"mdi:volume-high\"\n />\n <template v-else>{{ idx + 1 }}</template>\n </span>\n <div class=\"c-audio-player__track-info\">\n <div class=\"c-audio-player__track-title\">{{ track.title }}</div>\n <div\n v-if=\"track.artist\"\n class=\"c-audio-player__track-artist\"\n >\n {{ track.artist }}\n </div>\n </div>\n <span\n v-if=\"track.duration\"\n class=\"c-audio-player__track-duration\"\n >\n {{ formatTime(track.duration) }}\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onBeforeUnmount, ref, watch } from 'vue'\n import C_Icon from '../C_Icon/index.vue'\n import {\n DEFAULT_AUDIO_PLAYER_PROPS,\n formatTime,\n MODE_ICON_MAP,\n type AudioPlayerProps,\n } from './types'\n\n const props = withDefaults(\n defineProps<{\n tracks: AudioPlayerProps['tracks']\n initialIndex?: number\n showPlaylist?: boolean\n showCover?: boolean\n autoplay?: boolean\n mode?: AudioPlayerProps['mode']\n theme?: AudioPlayerProps['theme']\n }>(),\n {\n initialIndex: DEFAULT_AUDIO_PLAYER_PROPS.initialIndex,\n showPlaylist: DEFAULT_AUDIO_PLAYER_PROPS.showPlaylist,\n showCover: true,\n autoplay: DEFAULT_AUDIO_PLAYER_PROPS.autoplay,\n mode: DEFAULT_AUDIO_PLAYER_PROPS.mode,\n theme: DEFAULT_AUDIO_PLAYER_PROPS.theme,\n }\n )\n\n const emit = defineEmits<{\n play: [index: number]\n pause: []\n ended: [index: number]\n modeChange: [mode: string]\n }>()\n\n // ==================== Audio Core ====================\n\n let audio: HTMLAudioElement | null = null\n\n const activeIndex = ref(props.initialIndex ?? 0)\n const isPlaying = ref(false)\n const currentTime = ref(0)\n const totalDuration = ref(0)\n const volume = ref(70)\n const prevVolume = ref(70)\n const playlistVisible = ref(props.showPlaylist)\n const playMode = ref(props.mode ?? 'list')\n\n const currentTrack = computed(() => props.tracks[activeIndex.value])\n\n const progressPercent = computed(() => {\n if (totalDuration.value === 0) return 0\n return (currentTime.value / totalDuration.value) * 100\n })\n\n const volumeIcon = computed(() => {\n if (volume.value === 0) return 'mdi:volume-off'\n if (volume.value < 40) return 'mdi:volume-low'\n if (volume.value < 70) return 'mdi:volume-medium'\n return 'mdi:volume-high'\n })\n\n const currentModeIcon = computed(\n () => MODE_ICON_MAP[playMode.value]?.icon ?? 'mdi:repeat'\n )\n const currentModeLabel = computed(\n () => MODE_ICON_MAP[playMode.value]?.label ?? '循环'\n )\n\n /** 初始化音频实例 */\n function initAudio() {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n }\n const track = currentTrack.value\n if (!track) return\n\n audio = new Audio(track.src)\n audio.volume = volume.value / 100\n audio.addEventListener('timeupdate', onTimeUpdate)\n audio.addEventListener('loadedmetadata', onMetaLoaded)\n audio.addEventListener('ended', onEnded)\n }\n\n /** 播放进度更新回调 */\n function onTimeUpdate() {\n if (audio) currentTime.value = audio.currentTime\n }\n\n /** 音频元数据加载完成回调 */\n function onMetaLoaded() {\n if (audio) totalDuration.value = audio.duration\n }\n\n /** 播放结束回调,根据模式决定下一步 */\n function onEnded() {\n emit('ended', activeIndex.value)\n switch (playMode.value) {\n case 'loop':\n audio?.play().catch(() => {})\n break\n case 'single':\n isPlaying.value = false\n break\n case 'shuffle':\n playTrack(Math.floor(Math.random() * props.tracks.length))\n break\n default: // list\n if (activeIndex.value < props.tracks.length - 1) next()\n else {\n activeIndex.value = 0\n initAudio()\n audio?.play().catch(() => {})\n }\n }\n }\n\n /** 切换播放/暂停 */\n function togglePlay() {\n if (!audio) initAudio()\n if (isPlaying.value) {\n audio?.pause()\n isPlaying.value = false\n emit('pause')\n } else {\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', activeIndex.value)\n }\n }\n\n /** 播放指定索引的曲目 */\n function playTrack(idx: number) {\n activeIndex.value = idx\n initAudio()\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', idx)\n }\n\n /** 上一曲 */\n function prev() {\n const idx =\n activeIndex.value <= 0 ? props.tracks.length - 1 : activeIndex.value - 1\n playTrack(idx)\n }\n\n /** 下一曲 */\n function next() {\n const idx =\n activeIndex.value >= props.tracks.length - 1 ? 0 : activeIndex.value + 1\n playTrack(idx)\n }\n\n /** 点击进度条跳转播放位置 */\n function seekByClick(e: MouseEvent) {\n if (!audio || !totalDuration.value) return\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()\n const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n audio.currentTime = ratio * totalDuration.value\n currentTime.value = audio.currentTime\n }\n\n /** 处理音量变更 */\n function handleVolumeChange(val: number) {\n volume.value = val\n if (audio) audio.volume = val / 100\n }\n\n /** 切换静音状态 */\n function toggleMute() {\n if (volume.value > 0) {\n prevVolume.value = volume.value\n volume.value = 0\n } else {\n volume.value = prevVolume.value || 70\n }\n if (audio) audio.volume = volume.value / 100\n }\n\n const MODES = ['list', 'loop', 'single', 'shuffle'] as const\n /** 循环切换播放模式 */\n function cycleMode() {\n const idx = MODES.indexOf(playMode.value as (typeof MODES)[number])\n playMode.value = MODES[(idx + 1) % MODES.length]\n emit('modeChange', playMode.value)\n }\n\n // ==================== Lifecycle ====================\n\n watch(\n () => props.tracks,\n () => {\n activeIndex.value = 0\n initAudio()\n }\n )\n\n watch(\n () => props.initialIndex,\n val => {\n if (val !== undefined && val !== activeIndex.value) {\n activeIndex.value = val\n initAudio()\n }\n }\n )\n\n onBeforeUnmount(() => {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n audio = null\n }\n })\n\n // 自动播放\n if (props.autoplay && props.tracks.length > 0) {\n playTrack(activeIndex.value)\n }\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n","<!--\n * @Description: 音频播放器组件\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <div\n class=\"c-audio-player\"\n :class=\"{ 'is-minimal': theme === 'minimal' }\"\n >\n <!-- ================ Now Playing ================ -->\n <div class=\"c-audio-player__now-playing\">\n <div\n v-if=\"showCover\"\n class=\"c-audio-player__cover\"\n >\n <img\n v-if=\"currentTrack?.cover\"\n :src=\"currentTrack.cover\"\n :alt=\"currentTrack.title\"\n />\n <C_Icon\n v-else\n name=\"mdi:music-note\"\n class=\"c-audio-player__cover-placeholder\"\n />\n </div>\n <div class=\"c-audio-player__info\">\n <div class=\"c-audio-player__title\">{{\n currentTrack?.title ?? '未选择曲目'\n }}</div>\n <div\n v-if=\"currentTrack?.artist\"\n class=\"c-audio-player__artist\"\n >\n {{ currentTrack.artist }}\n </div>\n </div>\n </div>\n\n <!-- ================ Progress ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__progress\"\n >\n <div\n class=\"c-audio-player__progress-bar\"\n @click=\"seekByClick\"\n >\n <div\n class=\"c-audio-player__progress-fill\"\n :style=\"{ width: `${progressPercent}%` }\"\n />\n </div>\n <div class=\"c-audio-player__time\">\n <span>{{ formatTime(currentTime) }}</span>\n <span>{{ formatTime(totalDuration) }}</span>\n </div>\n </div>\n\n <!-- ================ Controls ================ -->\n <div class=\"c-audio-player__controls\">\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n :title=\"currentModeLabel\"\n @click=\"cycleMode\"\n >\n <C_Icon :name=\"currentModeIcon\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"上一曲\"\n @click=\"prev\"\n >\n <C_Icon name=\"mdi:skip-previous\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-play\"\n :title=\"isPlaying ? '暂停' : '播放'\"\n @click=\"togglePlay\"\n >\n <C_Icon :name=\"isPlaying ? 'mdi:pause' : 'mdi:play'\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"下一曲\"\n @click=\"next\"\n >\n <C_Icon name=\"mdi:skip-next\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n title=\"播放列表\"\n @click=\"playlistVisible = !playlistVisible\"\n >\n <C_Icon name=\"mdi:playlist-music\" />\n </button>\n </div>\n\n <!-- ================ Volume ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__volume\"\n >\n <C_Icon\n :name=\"volumeIcon\"\n class=\"c-audio-player__volume-icon\"\n @click=\"toggleMute\"\n />\n <NSlider\n v-model:value=\"volume\"\n :min=\"0\"\n :max=\"100\"\n :step=\"1\"\n class=\"c-audio-player__volume-slider\"\n @update:value=\"handleVolumeChange\"\n />\n </div>\n\n <!-- ================ Playlist ================ -->\n <div\n v-if=\"showPlaylist && playlistVisible && theme !== 'minimal'\"\n class=\"c-audio-player__playlist\"\n >\n <div class=\"c-audio-player__playlist-header\">\n <span>播放列表</span>\n <span>{{ tracks.length }} 首</span>\n </div>\n <div\n v-for=\"(track, idx) in tracks\"\n :key=\"track.id\"\n class=\"c-audio-player__track\"\n :class=\"{ 'is-active': idx === activeIndex }\"\n @click=\"playTrack(idx)\"\n >\n <span class=\"c-audio-player__track-index\">\n <C_Icon\n v-if=\"idx === activeIndex && isPlaying\"\n name=\"mdi:volume-high\"\n />\n <template v-else>{{ idx + 1 }}</template>\n </span>\n <div class=\"c-audio-player__track-info\">\n <div class=\"c-audio-player__track-title\">{{ track.title }}</div>\n <div\n v-if=\"track.artist\"\n class=\"c-audio-player__track-artist\"\n >\n {{ track.artist }}\n </div>\n </div>\n <span\n v-if=\"track.duration\"\n class=\"c-audio-player__track-duration\"\n >\n {{ formatTime(track.duration) }}\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onBeforeUnmount, ref, watch } from 'vue'\n import C_Icon from '../C_Icon/index.vue'\n import {\n DEFAULT_AUDIO_PLAYER_PROPS,\n formatTime,\n MODE_ICON_MAP,\n type AudioPlayerProps,\n } from './types'\n\n const props = withDefaults(\n defineProps<{\n tracks: AudioPlayerProps['tracks']\n initialIndex?: number\n showPlaylist?: boolean\n showCover?: boolean\n autoplay?: boolean\n mode?: AudioPlayerProps['mode']\n theme?: AudioPlayerProps['theme']\n }>(),\n {\n initialIndex: DEFAULT_AUDIO_PLAYER_PROPS.initialIndex,\n showPlaylist: DEFAULT_AUDIO_PLAYER_PROPS.showPlaylist,\n showCover: true,\n autoplay: DEFAULT_AUDIO_PLAYER_PROPS.autoplay,\n mode: DEFAULT_AUDIO_PLAYER_PROPS.mode,\n theme: DEFAULT_AUDIO_PLAYER_PROPS.theme,\n }\n )\n\n const emit = defineEmits<{\n play: [index: number]\n pause: []\n ended: [index: number]\n modeChange: [mode: string]\n }>()\n\n // ==================== Audio Core ====================\n\n let audio: HTMLAudioElement | null = null\n\n const activeIndex = ref(props.initialIndex ?? 0)\n const isPlaying = ref(false)\n const currentTime = ref(0)\n const totalDuration = ref(0)\n const volume = ref(70)\n const prevVolume = ref(70)\n const playlistVisible = ref(props.showPlaylist)\n const playMode = ref(props.mode ?? 'list')\n\n const currentTrack = computed(() => props.tracks[activeIndex.value])\n\n const progressPercent = computed(() => {\n if (totalDuration.value === 0) return 0\n return (currentTime.value / totalDuration.value) * 100\n })\n\n const volumeIcon = computed(() => {\n if (volume.value === 0) return 'mdi:volume-off'\n if (volume.value < 40) return 'mdi:volume-low'\n if (volume.value < 70) return 'mdi:volume-medium'\n return 'mdi:volume-high'\n })\n\n const currentModeIcon = computed(\n () => MODE_ICON_MAP[playMode.value]?.icon ?? 'mdi:repeat'\n )\n const currentModeLabel = computed(\n () => MODE_ICON_MAP[playMode.value]?.label ?? '循环'\n )\n\n /** 初始化音频实例 */\n function initAudio() {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n }\n const track = currentTrack.value\n if (!track) return\n\n audio = new Audio(track.src)\n audio.volume = volume.value / 100\n audio.addEventListener('timeupdate', onTimeUpdate)\n audio.addEventListener('loadedmetadata', onMetaLoaded)\n audio.addEventListener('ended', onEnded)\n }\n\n /** 播放进度更新回调 */\n function onTimeUpdate() {\n if (audio) currentTime.value = audio.currentTime\n }\n\n /** 音频元数据加载完成回调 */\n function onMetaLoaded() {\n if (audio) totalDuration.value = audio.duration\n }\n\n /** 播放结束回调,根据模式决定下一步 */\n function onEnded() {\n emit('ended', activeIndex.value)\n switch (playMode.value) {\n case 'loop':\n audio?.play().catch(() => {})\n break\n case 'single':\n isPlaying.value = false\n break\n case 'shuffle':\n playTrack(Math.floor(Math.random() * props.tracks.length))\n break\n default: // list\n if (activeIndex.value < props.tracks.length - 1) next()\n else {\n activeIndex.value = 0\n initAudio()\n audio?.play().catch(() => {})\n }\n }\n }\n\n /** 切换播放/暂停 */\n function togglePlay() {\n if (!audio) initAudio()\n if (isPlaying.value) {\n audio?.pause()\n isPlaying.value = false\n emit('pause')\n } else {\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', activeIndex.value)\n }\n }\n\n /** 播放指定索引的曲目 */\n function playTrack(idx: number) {\n activeIndex.value = idx\n initAudio()\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', idx)\n }\n\n /** 上一曲 */\n function prev() {\n const idx =\n activeIndex.value <= 0 ? props.tracks.length - 1 : activeIndex.value - 1\n playTrack(idx)\n }\n\n /** 下一曲 */\n function next() {\n const idx =\n activeIndex.value >= props.tracks.length - 1 ? 0 : activeIndex.value + 1\n playTrack(idx)\n }\n\n /** 点击进度条跳转播放位置 */\n function seekByClick(e: MouseEvent) {\n if (!audio || !totalDuration.value) return\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()\n const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n audio.currentTime = ratio * totalDuration.value\n currentTime.value = audio.currentTime\n }\n\n /** 处理音量变更 */\n function handleVolumeChange(val: number) {\n volume.value = val\n if (audio) audio.volume = val / 100\n }\n\n /** 切换静音状态 */\n function toggleMute() {\n if (volume.value > 0) {\n prevVolume.value = volume.value\n volume.value = 0\n } else {\n volume.value = prevVolume.value || 70\n }\n if (audio) audio.volume = volume.value / 100\n }\n\n const MODES = ['list', 'loop', 'single', 'shuffle'] as const\n /** 循环切换播放模式 */\n function cycleMode() {\n const idx = MODES.indexOf(playMode.value as (typeof MODES)[number])\n playMode.value = MODES[(idx + 1) % MODES.length]\n emit('modeChange', playMode.value)\n }\n\n // ==================== Lifecycle ====================\n\n watch(\n () => props.tracks,\n () => {\n activeIndex.value = 0\n initAudio()\n }\n )\n\n watch(\n () => props.initialIndex,\n val => {\n if (val !== undefined && val !== activeIndex.value) {\n activeIndex.value = val\n initAudio()\n }\n }\n )\n\n onBeforeUnmount(() => {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n audio = null\n }\n })\n\n // 自动播放\n if (props.autoplay && props.tracks.length > 0) {\n playTrack(activeIndex.value)\n }\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./C_Icon2.js";import{t}from"./export-helper.js";import{NSlider as n}from"naive-ui";import{Fragment as r,computed as i,createBlock as a,createCommentVNode as o,createElementBlock as s,createElementVNode as c,createTextVNode as l,createVNode as u,defineComponent as d,normalizeClass as f,normalizeStyle as p,onBeforeUnmount as m,openBlock as h,ref as g,renderList as _,toDisplayString as v,unref as y,watch as b}from"vue";const x={initialIndex:0,showPlaylist:!0,showCover:!0,autoplay:!1,mode:`list`,theme:`default`},S={list:{icon:`mdi:repeat`,label:`List Loop`},loop:{icon:`mdi:repeat-once`,label:`Single Loop`},single:{icon:`mdi:numeric-1-box`,label:`Play Once`},shuffle:{icon:`mdi:shuffle-variant`,label:`Shuffle`}};function C(e){if(!Number.isFinite(e)||e<0)return`00:00`;let t=Math.floor(e/60),n=Math.floor(e%60);return`${String(t).padStart(2,`0`)}:${String(n).padStart(2,`0`)}`}const w={class:`c-audio-player__now-playing`},T={key:0,class:`c-audio-player__cover`},E=[`src`,`alt`],D={class:`c-audio-player__info`},O={class:`c-audio-player__title`},k={key:0,class:`c-audio-player__artist`},A={key:0,class:`c-audio-player__progress`},ee={class:`c-audio-player__time`},te={class:`c-audio-player__controls`},ne=[`title`],re=[`title`],ie={key:1,class:`c-audio-player__volume`},ae={key:2,class:`c-audio-player__playlist`},oe={class:`c-audio-player__playlist-header`},se=[`onClick`],ce={class:`c-audio-player__track-index`},j={class:`c-audio-player__track-info`},M={class:`c-audio-player__track-title`},N={key:0,class:`c-audio-player__track-artist`},P={key:0,class:`c-audio-player__track-duration`};var F=t(d({__name:`index`,props:{tracks:{},initialIndex:{default:x.initialIndex},showPlaylist:{type:Boolean,default:x.showPlaylist},showCover:{type:Boolean,default:!0},autoplay:{type:Boolean,default:x.autoplay},mode:{default:x.mode},theme:{default:x.theme}},emits:[`play`,`pause`,`ended`,`modeChange`],setup(t,{emit:d}){let x=t,F=d,I=null,L=g(x.initialIndex??0),R=g(!1),z=g(0),B=g(0),V=g(70),H=g(70),U=g(x.showPlaylist),W=g(x.mode??`list`),G=i(()=>x.tracks[L.value]),le=i(()=>B.value===0?0:z.value/B.value*100),ue=i(()=>V.value===0?`mdi:volume-off`:V.value<40?`mdi:volume-low`:V.value<70?`mdi:volume-medium`:`mdi:volume-high`),de=i(()=>S[W.value]?.icon??`mdi:repeat`),fe=i(()=>S[W.value]?.label??`循环`);function K(){I&&(I.pause(),I.removeEventListener(`timeupdate`,q),I.removeEventListener(`loadedmetadata`,J),I.removeEventListener(`ended`,Y));let e=G.value;e&&(I=new Audio(e.src),I.volume=V.value/100,I.addEventListener(`timeupdate`,q),I.addEventListener(`loadedmetadata`,J),I.addEventListener(`ended`,Y))}function q(){I&&(z.value=I.currentTime)}function J(){I&&(B.value=I.duration)}function Y(){switch(F(`ended`,L.value),W.value){case`loop`:I?.play().catch(()=>{});break;case`single`:R.value=!1;break;case`shuffle`:X(Math.floor(Math.random()*x.tracks.length));break;default:L.value<x.tracks.length-1?Z():(L.value=0,K(),I?.play().catch(()=>{}))}}function pe(){I||K(),R.value?(I?.pause(),R.value=!1,F(`pause`)):(I?.play().catch(()=>{}),R.value=!0,F(`play`,L.value))}function X(e){L.value=e,K(),I?.play().catch(()=>{}),R.value=!0,F(`play`,e)}function me(){X(L.value<=0?x.tracks.length-1:L.value-1)}function Z(){X(L.value>=x.tracks.length-1?0:L.value+1)}function Q(e){if(!I||!B.value)return;let t=e.currentTarget.getBoundingClientRect(),n=Math.max(0,Math.min(1,(e.clientX-t.left)/t.width));I.currentTime=n*B.value,z.value=I.currentTime}function he(e){V.value=e,I&&(I.volume=e/100)}function ge(){V.value>0?(H.value=V.value,V.value=0):V.value=H.value||70,I&&(I.volume=V.value/100)}let $=[`list`,`loop`,`single`,`shuffle`];function _e(){W.value=$[($.indexOf(W.value)+1)%$.length],F(`modeChange`,W.value)}return b(()=>x.tracks,()=>{L.value=0,K()}),b(()=>x.initialIndex,e=>{e!==void 0&&e!==L.value&&(L.value=e,K())}),m(()=>{I&&=(I.pause(),I.removeEventListener(`timeupdate`,q),I.removeEventListener(`loadedmetadata`,J),I.removeEventListener(`ended`,Y),null)}),x.autoplay&&x.tracks.length>0&&X(L.value),(t,i)=>{let d=n;return h(),s(`div`,{class:f([`c-audio-player`,{"is-minimal":t.theme===`minimal`}])},[o(` ================ Now Playing ================ `),c(`div`,w,[t.showCover?(h(),s(`div`,T,[G.value?.cover?(h(),s(`img`,{key:0,src:G.value.cover,alt:G.value.title},null,8,E)):(h(),a(e,{key:1,name:`mdi:music-note`,class:`c-audio-player__cover-placeholder`}))])):o(`v-if`,!0),c(`div`,D,[c(`div`,O,v(G.value?.title??`未选择曲目`),1),G.value?.artist?(h(),s(`div`,k,v(G.value.artist),1)):o(`v-if`,!0)])]),o(` ================ Progress ================ `),t.theme===`minimal`?o(`v-if`,!0):(h(),s(`div`,A,[c(`div`,{class:`c-audio-player__progress-bar`,onClick:Q},[c(`div`,{class:`c-audio-player__progress-fill`,style:p({width:`${le.value}%`})},null,4)]),c(`div`,ee,[c(`span`,null,v(y(C)(z.value)),1),c(`span`,null,v(y(C)(B.value)),1)])])),o(` ================ Controls ================ `),c(`div`,te,[c(`button`,{class:`c-audio-player__ctrl-btn is-mode`,title:fe.value,onClick:_e},[u(e,{name:de.value},null,8,[`name`])],8,ne),c(`button`,{class:`c-audio-player__ctrl-btn is-skip`,title:`上一曲`,onClick:me},[u(e,{name:`mdi:skip-previous`})]),c(`button`,{class:`c-audio-player__ctrl-btn is-play`,title:R.value?`暂停`:`播放`,onClick:pe},[u(e,{name:R.value?`mdi:pause`:`mdi:play`},null,8,[`name`])],8,re),c(`button`,{class:`c-audio-player__ctrl-btn is-skip`,title:`下一曲`,onClick:Z},[u(e,{name:`mdi:skip-next`})]),c(`button`,{class:`c-audio-player__ctrl-btn is-mode`,title:`播放列表`,onClick:i[0]||=e=>U.value=!U.value},[u(e,{name:`mdi:playlist-music`})])]),o(` ================ Volume ================ `),t.theme===`minimal`?o(`v-if`,!0):(h(),s(`div`,ie,[u(e,{name:ue.value,class:`c-audio-player__volume-icon`,onClick:ge},null,8,[`name`]),u(d,{value:V.value,"onUpdate:value":[i[1]||=e=>V.value=e,he],min:0,max:100,step:1,class:`c-audio-player__volume-slider`},null,8,[`value`])])),o(` ================ Playlist ================ `),t.showPlaylist&&U.value&&t.theme!==`minimal`?(h(),s(`div`,ae,[c(`div`,oe,[i[2]||=c(`span`,null,`播放列表`,-1),c(`span`,null,v(t.tracks.length)+` 首`,1)]),(h(!0),s(r,null,_(t.tracks,(t,n)=>(h(),s(`div`,{key:t.id,class:f([`c-audio-player__track`,{"is-active":n===L.value}]),onClick:e=>X(n)},[c(`span`,ce,[n===L.value&&R.value?(h(),a(e,{key:0,name:`mdi:volume-high`})):(h(),s(r,{key:1},[l(v(n+1),1)],64))]),c(`div`,j,[c(`div`,M,v(t.title),1),t.artist?(h(),s(`div`,N,v(t.artist),1)):o(`v-if`,!0)]),t.duration?(h(),s(`span`,P,v(y(C)(t.duration)),1)):o(`v-if`,!0)],10,se))),128))])):o(`v-if`,!0)],2)}}}),[[`__scopeId`,`data-v-3cb14639`]]);export{C as i,x as n,S as r,F as t};
|
|
2
|
+
//# sourceMappingURL=C_AudioPlayer2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_AudioPlayer2.js","names":["theme","showCover","showPlaylist","tracks"],"sources":["../src/components/C_AudioPlayer/types.ts","../src/components/C_AudioPlayer/index.vue","../src/components/C_AudioPlayer/index.vue","../src/components/C_AudioPlayer/index.vue"],"sourcesContent":["/**\n * @Description: Audio player component type definitions\n * Copyright (c) 2026 by CHENY, All Rights Reserved.\n */\n\n/** Single audio track */\nexport interface AudioTrack {\n /** Unique identifier */\n id: string | number\n /** Track title */\n title: string\n /** Artist / author */\n artist?: string\n /** Audio source URL */\n src: string\n /** Cover image URL */\n cover?: string\n /** Duration in seconds (optional, for static display) */\n duration?: number\n /** Extra data */\n extra?: Record<string, unknown>\n}\n\n/** Component props */\nexport interface AudioPlayerProps {\n /** Playlist */\n tracks: AudioTrack[]\n /** Initial active index */\n initialIndex?: number\n /** Whether to show the playlist panel */\n showPlaylist?: boolean\n /** Whether to show the cover art */\n showCover?: boolean\n /** Whether to auto-play on mount */\n autoplay?: boolean\n /** Playback mode */\n mode?: 'list' | 'loop' | 'single' | 'shuffle'\n /** Theme appearance */\n theme?: 'default' | 'minimal'\n}\n\n/** Default props */\nexport const DEFAULT_AUDIO_PLAYER_PROPS: Partial<AudioPlayerProps> = {\n initialIndex: 0,\n showPlaylist: true,\n showCover: true,\n autoplay: false,\n mode: 'list',\n theme: 'default',\n}\n\n/** Playback mode icon map */\nexport const MODE_ICON_MAP: Record<string, { icon: string; label: string }> = {\n list: { icon: 'mdi:repeat', label: 'List Loop' },\n loop: { icon: 'mdi:repeat-once', label: 'Single Loop' },\n single: { icon: 'mdi:numeric-1-box', label: 'Play Once' },\n shuffle: { icon: 'mdi:shuffle-variant', label: 'Shuffle' },\n}\n\n/** Format seconds to mm:ss */\nexport function formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '00:00'\n const m = Math.floor(seconds / 60)\n const s = Math.floor(seconds % 60)\n return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n}\n","/* unplugin-vue-components disabled */<!--\n * @Description: 音频播放器组件\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <div\n class=\"c-audio-player\"\n :class=\"{ 'is-minimal': theme === 'minimal' }\"\n >\n <!-- ================ Now Playing ================ -->\n <div class=\"c-audio-player__now-playing\">\n <div\n v-if=\"showCover\"\n class=\"c-audio-player__cover\"\n >\n <img\n v-if=\"currentTrack?.cover\"\n :src=\"currentTrack.cover\"\n :alt=\"currentTrack.title\"\n />\n <C_Icon\n v-else\n name=\"mdi:music-note\"\n class=\"c-audio-player__cover-placeholder\"\n />\n </div>\n <div class=\"c-audio-player__info\">\n <div class=\"c-audio-player__title\">{{\n currentTrack?.title ?? '未选择曲目'\n }}</div>\n <div\n v-if=\"currentTrack?.artist\"\n class=\"c-audio-player__artist\"\n >\n {{ currentTrack.artist }}\n </div>\n </div>\n </div>\n\n <!-- ================ Progress ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__progress\"\n >\n <div\n class=\"c-audio-player__progress-bar\"\n @click=\"seekByClick\"\n >\n <div\n class=\"c-audio-player__progress-fill\"\n :style=\"{ width: `${progressPercent}%` }\"\n />\n </div>\n <div class=\"c-audio-player__time\">\n <span>{{ formatTime(currentTime) }}</span>\n <span>{{ formatTime(totalDuration) }}</span>\n </div>\n </div>\n\n <!-- ================ Controls ================ -->\n <div class=\"c-audio-player__controls\">\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n :title=\"currentModeLabel\"\n @click=\"cycleMode\"\n >\n <C_Icon :name=\"currentModeIcon\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"上一曲\"\n @click=\"prev\"\n >\n <C_Icon name=\"mdi:skip-previous\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-play\"\n :title=\"isPlaying ? '暂停' : '播放'\"\n @click=\"togglePlay\"\n >\n <C_Icon :name=\"isPlaying ? 'mdi:pause' : 'mdi:play'\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"下一曲\"\n @click=\"next\"\n >\n <C_Icon name=\"mdi:skip-next\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n title=\"播放列表\"\n @click=\"playlistVisible = !playlistVisible\"\n >\n <C_Icon name=\"mdi:playlist-music\" />\n </button>\n </div>\n\n <!-- ================ Volume ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__volume\"\n >\n <C_Icon\n :name=\"volumeIcon\"\n class=\"c-audio-player__volume-icon\"\n @click=\"toggleMute\"\n />\n <NSlider\n v-model:value=\"volume\"\n :min=\"0\"\n :max=\"100\"\n :step=\"1\"\n class=\"c-audio-player__volume-slider\"\n @update:value=\"handleVolumeChange\"\n />\n </div>\n\n <!-- ================ Playlist ================ -->\n <div\n v-if=\"showPlaylist && playlistVisible && theme !== 'minimal'\"\n class=\"c-audio-player__playlist\"\n >\n <div class=\"c-audio-player__playlist-header\">\n <span>播放列表</span>\n <span>{{ tracks.length }} 首</span>\n </div>\n <div\n v-for=\"(track, idx) in tracks\"\n :key=\"track.id\"\n class=\"c-audio-player__track\"\n :class=\"{ 'is-active': idx === activeIndex }\"\n @click=\"playTrack(idx)\"\n >\n <span class=\"c-audio-player__track-index\">\n <C_Icon\n v-if=\"idx === activeIndex && isPlaying\"\n name=\"mdi:volume-high\"\n />\n <template v-else>{{ idx + 1 }}</template>\n </span>\n <div class=\"c-audio-player__track-info\">\n <div class=\"c-audio-player__track-title\">{{ track.title }}</div>\n <div\n v-if=\"track.artist\"\n class=\"c-audio-player__track-artist\"\n >\n {{ track.artist }}\n </div>\n </div>\n <span\n v-if=\"track.duration\"\n class=\"c-audio-player__track-duration\"\n >\n {{ formatTime(track.duration) }}\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onBeforeUnmount, ref, watch } from 'vue'\n import C_Icon from '../C_Icon/index.vue'\n import {\n DEFAULT_AUDIO_PLAYER_PROPS,\n formatTime,\n MODE_ICON_MAP,\n type AudioPlayerProps,\n } from './types'\n\n const props = withDefaults(\n defineProps<{\n tracks: AudioPlayerProps['tracks']\n initialIndex?: number\n showPlaylist?: boolean\n showCover?: boolean\n autoplay?: boolean\n mode?: AudioPlayerProps['mode']\n theme?: AudioPlayerProps['theme']\n }>(),\n {\n initialIndex: DEFAULT_AUDIO_PLAYER_PROPS.initialIndex,\n showPlaylist: DEFAULT_AUDIO_PLAYER_PROPS.showPlaylist,\n showCover: true,\n autoplay: DEFAULT_AUDIO_PLAYER_PROPS.autoplay,\n mode: DEFAULT_AUDIO_PLAYER_PROPS.mode,\n theme: DEFAULT_AUDIO_PLAYER_PROPS.theme,\n }\n )\n\n const emit = defineEmits<{\n play: [index: number]\n pause: []\n ended: [index: number]\n modeChange: [mode: string]\n }>()\n\n // ==================== Audio Core ====================\n\n let audio: HTMLAudioElement | null = null\n\n const activeIndex = ref(props.initialIndex ?? 0)\n const isPlaying = ref(false)\n const currentTime = ref(0)\n const totalDuration = ref(0)\n const volume = ref(70)\n const prevVolume = ref(70)\n const playlistVisible = ref(props.showPlaylist)\n const playMode = ref(props.mode ?? 'list')\n\n const currentTrack = computed(() => props.tracks[activeIndex.value])\n\n const progressPercent = computed(() => {\n if (totalDuration.value === 0) return 0\n return (currentTime.value / totalDuration.value) * 100\n })\n\n const volumeIcon = computed(() => {\n if (volume.value === 0) return 'mdi:volume-off'\n if (volume.value < 40) return 'mdi:volume-low'\n if (volume.value < 70) return 'mdi:volume-medium'\n return 'mdi:volume-high'\n })\n\n const currentModeIcon = computed(\n () => MODE_ICON_MAP[playMode.value]?.icon ?? 'mdi:repeat'\n )\n const currentModeLabel = computed(\n () => MODE_ICON_MAP[playMode.value]?.label ?? '循环'\n )\n\n /** 初始化音频实例 */\n function initAudio() {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n }\n const track = currentTrack.value\n if (!track) return\n\n audio = new Audio(track.src)\n audio.volume = volume.value / 100\n audio.addEventListener('timeupdate', onTimeUpdate)\n audio.addEventListener('loadedmetadata', onMetaLoaded)\n audio.addEventListener('ended', onEnded)\n }\n\n /** 播放进度更新回调 */\n function onTimeUpdate() {\n if (audio) currentTime.value = audio.currentTime\n }\n\n /** 音频元数据加载完成回调 */\n function onMetaLoaded() {\n if (audio) totalDuration.value = audio.duration\n }\n\n /** 播放结束回调,根据模式决定下一步 */\n function onEnded() {\n emit('ended', activeIndex.value)\n switch (playMode.value) {\n case 'loop':\n audio?.play().catch(() => {})\n break\n case 'single':\n isPlaying.value = false\n break\n case 'shuffle':\n playTrack(Math.floor(Math.random() * props.tracks.length))\n break\n default: // list\n if (activeIndex.value < props.tracks.length - 1) next()\n else {\n activeIndex.value = 0\n initAudio()\n audio?.play().catch(() => {})\n }\n }\n }\n\n /** 切换播放/暂停 */\n function togglePlay() {\n if (!audio) initAudio()\n if (isPlaying.value) {\n audio?.pause()\n isPlaying.value = false\n emit('pause')\n } else {\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', activeIndex.value)\n }\n }\n\n /** 播放指定索引的曲目 */\n function playTrack(idx: number) {\n activeIndex.value = idx\n initAudio()\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', idx)\n }\n\n /** 上一曲 */\n function prev() {\n const idx =\n activeIndex.value <= 0 ? props.tracks.length - 1 : activeIndex.value - 1\n playTrack(idx)\n }\n\n /** 下一曲 */\n function next() {\n const idx =\n activeIndex.value >= props.tracks.length - 1 ? 0 : activeIndex.value + 1\n playTrack(idx)\n }\n\n /** 点击进度条跳转播放位置 */\n function seekByClick(e: MouseEvent) {\n if (!audio || !totalDuration.value) return\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()\n const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n audio.currentTime = ratio * totalDuration.value\n currentTime.value = audio.currentTime\n }\n\n /** 处理音量变更 */\n function handleVolumeChange(val: number) {\n volume.value = val\n if (audio) audio.volume = val / 100\n }\n\n /** 切换静音状态 */\n function toggleMute() {\n if (volume.value > 0) {\n prevVolume.value = volume.value\n volume.value = 0\n } else {\n volume.value = prevVolume.value || 70\n }\n if (audio) audio.volume = volume.value / 100\n }\n\n const MODES = ['list', 'loop', 'single', 'shuffle'] as const\n /** 循环切换播放模式 */\n function cycleMode() {\n const idx = MODES.indexOf(playMode.value as (typeof MODES)[number])\n playMode.value = MODES[(idx + 1) % MODES.length]\n emit('modeChange', playMode.value)\n }\n\n // ==================== Lifecycle ====================\n\n watch(\n () => props.tracks,\n () => {\n activeIndex.value = 0\n initAudio()\n }\n )\n\n watch(\n () => props.initialIndex,\n val => {\n if (val !== undefined && val !== activeIndex.value) {\n activeIndex.value = val\n initAudio()\n }\n }\n )\n\n onBeforeUnmount(() => {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n audio = null\n }\n })\n\n // 自动播放\n if (props.autoplay && props.tracks.length > 0) {\n playTrack(activeIndex.value)\n }\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n","/* unplugin-vue-components disabled */<!--\n * @Description: 音频播放器组件\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <div\n class=\"c-audio-player\"\n :class=\"{ 'is-minimal': theme === 'minimal' }\"\n >\n <!-- ================ Now Playing ================ -->\n <div class=\"c-audio-player__now-playing\">\n <div\n v-if=\"showCover\"\n class=\"c-audio-player__cover\"\n >\n <img\n v-if=\"currentTrack?.cover\"\n :src=\"currentTrack.cover\"\n :alt=\"currentTrack.title\"\n />\n <C_Icon\n v-else\n name=\"mdi:music-note\"\n class=\"c-audio-player__cover-placeholder\"\n />\n </div>\n <div class=\"c-audio-player__info\">\n <div class=\"c-audio-player__title\">{{\n currentTrack?.title ?? '未选择曲目'\n }}</div>\n <div\n v-if=\"currentTrack?.artist\"\n class=\"c-audio-player__artist\"\n >\n {{ currentTrack.artist }}\n </div>\n </div>\n </div>\n\n <!-- ================ Progress ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__progress\"\n >\n <div\n class=\"c-audio-player__progress-bar\"\n @click=\"seekByClick\"\n >\n <div\n class=\"c-audio-player__progress-fill\"\n :style=\"{ width: `${progressPercent}%` }\"\n />\n </div>\n <div class=\"c-audio-player__time\">\n <span>{{ formatTime(currentTime) }}</span>\n <span>{{ formatTime(totalDuration) }}</span>\n </div>\n </div>\n\n <!-- ================ Controls ================ -->\n <div class=\"c-audio-player__controls\">\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n :title=\"currentModeLabel\"\n @click=\"cycleMode\"\n >\n <C_Icon :name=\"currentModeIcon\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"上一曲\"\n @click=\"prev\"\n >\n <C_Icon name=\"mdi:skip-previous\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-play\"\n :title=\"isPlaying ? '暂停' : '播放'\"\n @click=\"togglePlay\"\n >\n <C_Icon :name=\"isPlaying ? 'mdi:pause' : 'mdi:play'\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"下一曲\"\n @click=\"next\"\n >\n <C_Icon name=\"mdi:skip-next\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n title=\"播放列表\"\n @click=\"playlistVisible = !playlistVisible\"\n >\n <C_Icon name=\"mdi:playlist-music\" />\n </button>\n </div>\n\n <!-- ================ Volume ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__volume\"\n >\n <C_Icon\n :name=\"volumeIcon\"\n class=\"c-audio-player__volume-icon\"\n @click=\"toggleMute\"\n />\n <NSlider\n v-model:value=\"volume\"\n :min=\"0\"\n :max=\"100\"\n :step=\"1\"\n class=\"c-audio-player__volume-slider\"\n @update:value=\"handleVolumeChange\"\n />\n </div>\n\n <!-- ================ Playlist ================ -->\n <div\n v-if=\"showPlaylist && playlistVisible && theme !== 'minimal'\"\n class=\"c-audio-player__playlist\"\n >\n <div class=\"c-audio-player__playlist-header\">\n <span>播放列表</span>\n <span>{{ tracks.length }} 首</span>\n </div>\n <div\n v-for=\"(track, idx) in tracks\"\n :key=\"track.id\"\n class=\"c-audio-player__track\"\n :class=\"{ 'is-active': idx === activeIndex }\"\n @click=\"playTrack(idx)\"\n >\n <span class=\"c-audio-player__track-index\">\n <C_Icon\n v-if=\"idx === activeIndex && isPlaying\"\n name=\"mdi:volume-high\"\n />\n <template v-else>{{ idx + 1 }}</template>\n </span>\n <div class=\"c-audio-player__track-info\">\n <div class=\"c-audio-player__track-title\">{{ track.title }}</div>\n <div\n v-if=\"track.artist\"\n class=\"c-audio-player__track-artist\"\n >\n {{ track.artist }}\n </div>\n </div>\n <span\n v-if=\"track.duration\"\n class=\"c-audio-player__track-duration\"\n >\n {{ formatTime(track.duration) }}\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onBeforeUnmount, ref, watch } from 'vue'\n import C_Icon from '../C_Icon/index.vue'\n import {\n DEFAULT_AUDIO_PLAYER_PROPS,\n formatTime,\n MODE_ICON_MAP,\n type AudioPlayerProps,\n } from './types'\n\n const props = withDefaults(\n defineProps<{\n tracks: AudioPlayerProps['tracks']\n initialIndex?: number\n showPlaylist?: boolean\n showCover?: boolean\n autoplay?: boolean\n mode?: AudioPlayerProps['mode']\n theme?: AudioPlayerProps['theme']\n }>(),\n {\n initialIndex: DEFAULT_AUDIO_PLAYER_PROPS.initialIndex,\n showPlaylist: DEFAULT_AUDIO_PLAYER_PROPS.showPlaylist,\n showCover: true,\n autoplay: DEFAULT_AUDIO_PLAYER_PROPS.autoplay,\n mode: DEFAULT_AUDIO_PLAYER_PROPS.mode,\n theme: DEFAULT_AUDIO_PLAYER_PROPS.theme,\n }\n )\n\n const emit = defineEmits<{\n play: [index: number]\n pause: []\n ended: [index: number]\n modeChange: [mode: string]\n }>()\n\n // ==================== Audio Core ====================\n\n let audio: HTMLAudioElement | null = null\n\n const activeIndex = ref(props.initialIndex ?? 0)\n const isPlaying = ref(false)\n const currentTime = ref(0)\n const totalDuration = ref(0)\n const volume = ref(70)\n const prevVolume = ref(70)\n const playlistVisible = ref(props.showPlaylist)\n const playMode = ref(props.mode ?? 'list')\n\n const currentTrack = computed(() => props.tracks[activeIndex.value])\n\n const progressPercent = computed(() => {\n if (totalDuration.value === 0) return 0\n return (currentTime.value / totalDuration.value) * 100\n })\n\n const volumeIcon = computed(() => {\n if (volume.value === 0) return 'mdi:volume-off'\n if (volume.value < 40) return 'mdi:volume-low'\n if (volume.value < 70) return 'mdi:volume-medium'\n return 'mdi:volume-high'\n })\n\n const currentModeIcon = computed(\n () => MODE_ICON_MAP[playMode.value]?.icon ?? 'mdi:repeat'\n )\n const currentModeLabel = computed(\n () => MODE_ICON_MAP[playMode.value]?.label ?? '循环'\n )\n\n /** 初始化音频实例 */\n function initAudio() {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n }\n const track = currentTrack.value\n if (!track) return\n\n audio = new Audio(track.src)\n audio.volume = volume.value / 100\n audio.addEventListener('timeupdate', onTimeUpdate)\n audio.addEventListener('loadedmetadata', onMetaLoaded)\n audio.addEventListener('ended', onEnded)\n }\n\n /** 播放进度更新回调 */\n function onTimeUpdate() {\n if (audio) currentTime.value = audio.currentTime\n }\n\n /** 音频元数据加载完成回调 */\n function onMetaLoaded() {\n if (audio) totalDuration.value = audio.duration\n }\n\n /** 播放结束回调,根据模式决定下一步 */\n function onEnded() {\n emit('ended', activeIndex.value)\n switch (playMode.value) {\n case 'loop':\n audio?.play().catch(() => {})\n break\n case 'single':\n isPlaying.value = false\n break\n case 'shuffle':\n playTrack(Math.floor(Math.random() * props.tracks.length))\n break\n default: // list\n if (activeIndex.value < props.tracks.length - 1) next()\n else {\n activeIndex.value = 0\n initAudio()\n audio?.play().catch(() => {})\n }\n }\n }\n\n /** 切换播放/暂停 */\n function togglePlay() {\n if (!audio) initAudio()\n if (isPlaying.value) {\n audio?.pause()\n isPlaying.value = false\n emit('pause')\n } else {\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', activeIndex.value)\n }\n }\n\n /** 播放指定索引的曲目 */\n function playTrack(idx: number) {\n activeIndex.value = idx\n initAudio()\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', idx)\n }\n\n /** 上一曲 */\n function prev() {\n const idx =\n activeIndex.value <= 0 ? props.tracks.length - 1 : activeIndex.value - 1\n playTrack(idx)\n }\n\n /** 下一曲 */\n function next() {\n const idx =\n activeIndex.value >= props.tracks.length - 1 ? 0 : activeIndex.value + 1\n playTrack(idx)\n }\n\n /** 点击进度条跳转播放位置 */\n function seekByClick(e: MouseEvent) {\n if (!audio || !totalDuration.value) return\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()\n const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n audio.currentTime = ratio * totalDuration.value\n currentTime.value = audio.currentTime\n }\n\n /** 处理音量变更 */\n function handleVolumeChange(val: number) {\n volume.value = val\n if (audio) audio.volume = val / 100\n }\n\n /** 切换静音状态 */\n function toggleMute() {\n if (volume.value > 0) {\n prevVolume.value = volume.value\n volume.value = 0\n } else {\n volume.value = prevVolume.value || 70\n }\n if (audio) audio.volume = volume.value / 100\n }\n\n const MODES = ['list', 'loop', 'single', 'shuffle'] as const\n /** 循环切换播放模式 */\n function cycleMode() {\n const idx = MODES.indexOf(playMode.value as (typeof MODES)[number])\n playMode.value = MODES[(idx + 1) % MODES.length]\n emit('modeChange', playMode.value)\n }\n\n // ==================== Lifecycle ====================\n\n watch(\n () => props.tracks,\n () => {\n activeIndex.value = 0\n initAudio()\n }\n )\n\n watch(\n () => props.initialIndex,\n val => {\n if (val !== undefined && val !== activeIndex.value) {\n activeIndex.value = val\n initAudio()\n }\n }\n )\n\n onBeforeUnmount(() => {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n audio = null\n }\n })\n\n // 自动播放\n if (props.autoplay && props.tracks.length > 0) {\n playTrack(activeIndex.value)\n }\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n","<!--\n * @Description: 音频播放器组件\n * Copyright (c) 2026 by CHENY, All Rights Reserved 😎.\n-->\n<template>\n <div\n class=\"c-audio-player\"\n :class=\"{ 'is-minimal': theme === 'minimal' }\"\n >\n <!-- ================ Now Playing ================ -->\n <div class=\"c-audio-player__now-playing\">\n <div\n v-if=\"showCover\"\n class=\"c-audio-player__cover\"\n >\n <img\n v-if=\"currentTrack?.cover\"\n :src=\"currentTrack.cover\"\n :alt=\"currentTrack.title\"\n />\n <C_Icon\n v-else\n name=\"mdi:music-note\"\n class=\"c-audio-player__cover-placeholder\"\n />\n </div>\n <div class=\"c-audio-player__info\">\n <div class=\"c-audio-player__title\">{{\n currentTrack?.title ?? '未选择曲目'\n }}</div>\n <div\n v-if=\"currentTrack?.artist\"\n class=\"c-audio-player__artist\"\n >\n {{ currentTrack.artist }}\n </div>\n </div>\n </div>\n\n <!-- ================ Progress ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__progress\"\n >\n <div\n class=\"c-audio-player__progress-bar\"\n @click=\"seekByClick\"\n >\n <div\n class=\"c-audio-player__progress-fill\"\n :style=\"{ width: `${progressPercent}%` }\"\n />\n </div>\n <div class=\"c-audio-player__time\">\n <span>{{ formatTime(currentTime) }}</span>\n <span>{{ formatTime(totalDuration) }}</span>\n </div>\n </div>\n\n <!-- ================ Controls ================ -->\n <div class=\"c-audio-player__controls\">\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n :title=\"currentModeLabel\"\n @click=\"cycleMode\"\n >\n <C_Icon :name=\"currentModeIcon\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"上一曲\"\n @click=\"prev\"\n >\n <C_Icon name=\"mdi:skip-previous\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-play\"\n :title=\"isPlaying ? '暂停' : '播放'\"\n @click=\"togglePlay\"\n >\n <C_Icon :name=\"isPlaying ? 'mdi:pause' : 'mdi:play'\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-skip\"\n title=\"下一曲\"\n @click=\"next\"\n >\n <C_Icon name=\"mdi:skip-next\" />\n </button>\n <button\n class=\"c-audio-player__ctrl-btn is-mode\"\n title=\"播放列表\"\n @click=\"playlistVisible = !playlistVisible\"\n >\n <C_Icon name=\"mdi:playlist-music\" />\n </button>\n </div>\n\n <!-- ================ Volume ================ -->\n <div\n v-if=\"theme !== 'minimal'\"\n class=\"c-audio-player__volume\"\n >\n <C_Icon\n :name=\"volumeIcon\"\n class=\"c-audio-player__volume-icon\"\n @click=\"toggleMute\"\n />\n <NSlider\n v-model:value=\"volume\"\n :min=\"0\"\n :max=\"100\"\n :step=\"1\"\n class=\"c-audio-player__volume-slider\"\n @update:value=\"handleVolumeChange\"\n />\n </div>\n\n <!-- ================ Playlist ================ -->\n <div\n v-if=\"showPlaylist && playlistVisible && theme !== 'minimal'\"\n class=\"c-audio-player__playlist\"\n >\n <div class=\"c-audio-player__playlist-header\">\n <span>播放列表</span>\n <span>{{ tracks.length }} 首</span>\n </div>\n <div\n v-for=\"(track, idx) in tracks\"\n :key=\"track.id\"\n class=\"c-audio-player__track\"\n :class=\"{ 'is-active': idx === activeIndex }\"\n @click=\"playTrack(idx)\"\n >\n <span class=\"c-audio-player__track-index\">\n <C_Icon\n v-if=\"idx === activeIndex && isPlaying\"\n name=\"mdi:volume-high\"\n />\n <template v-else>{{ idx + 1 }}</template>\n </span>\n <div class=\"c-audio-player__track-info\">\n <div class=\"c-audio-player__track-title\">{{ track.title }}</div>\n <div\n v-if=\"track.artist\"\n class=\"c-audio-player__track-artist\"\n >\n {{ track.artist }}\n </div>\n </div>\n <span\n v-if=\"track.duration\"\n class=\"c-audio-player__track-duration\"\n >\n {{ formatTime(track.duration) }}\n </span>\n </div>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\n import { computed, onBeforeUnmount, ref, watch } from 'vue'\n import C_Icon from '../C_Icon/index.vue'\n import {\n DEFAULT_AUDIO_PLAYER_PROPS,\n formatTime,\n MODE_ICON_MAP,\n type AudioPlayerProps,\n } from './types'\n\n const props = withDefaults(\n defineProps<{\n tracks: AudioPlayerProps['tracks']\n initialIndex?: number\n showPlaylist?: boolean\n showCover?: boolean\n autoplay?: boolean\n mode?: AudioPlayerProps['mode']\n theme?: AudioPlayerProps['theme']\n }>(),\n {\n initialIndex: DEFAULT_AUDIO_PLAYER_PROPS.initialIndex,\n showPlaylist: DEFAULT_AUDIO_PLAYER_PROPS.showPlaylist,\n showCover: true,\n autoplay: DEFAULT_AUDIO_PLAYER_PROPS.autoplay,\n mode: DEFAULT_AUDIO_PLAYER_PROPS.mode,\n theme: DEFAULT_AUDIO_PLAYER_PROPS.theme,\n }\n )\n\n const emit = defineEmits<{\n play: [index: number]\n pause: []\n ended: [index: number]\n modeChange: [mode: string]\n }>()\n\n // ==================== Audio Core ====================\n\n let audio: HTMLAudioElement | null = null\n\n const activeIndex = ref(props.initialIndex ?? 0)\n const isPlaying = ref(false)\n const currentTime = ref(0)\n const totalDuration = ref(0)\n const volume = ref(70)\n const prevVolume = ref(70)\n const playlistVisible = ref(props.showPlaylist)\n const playMode = ref(props.mode ?? 'list')\n\n const currentTrack = computed(() => props.tracks[activeIndex.value])\n\n const progressPercent = computed(() => {\n if (totalDuration.value === 0) return 0\n return (currentTime.value / totalDuration.value) * 100\n })\n\n const volumeIcon = computed(() => {\n if (volume.value === 0) return 'mdi:volume-off'\n if (volume.value < 40) return 'mdi:volume-low'\n if (volume.value < 70) return 'mdi:volume-medium'\n return 'mdi:volume-high'\n })\n\n const currentModeIcon = computed(\n () => MODE_ICON_MAP[playMode.value]?.icon ?? 'mdi:repeat'\n )\n const currentModeLabel = computed(\n () => MODE_ICON_MAP[playMode.value]?.label ?? '循环'\n )\n\n /** 初始化音频实例 */\n function initAudio() {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n }\n const track = currentTrack.value\n if (!track) return\n\n audio = new Audio(track.src)\n audio.volume = volume.value / 100\n audio.addEventListener('timeupdate', onTimeUpdate)\n audio.addEventListener('loadedmetadata', onMetaLoaded)\n audio.addEventListener('ended', onEnded)\n }\n\n /** 播放进度更新回调 */\n function onTimeUpdate() {\n if (audio) currentTime.value = audio.currentTime\n }\n\n /** 音频元数据加载完成回调 */\n function onMetaLoaded() {\n if (audio) totalDuration.value = audio.duration\n }\n\n /** 播放结束回调,根据模式决定下一步 */\n function onEnded() {\n emit('ended', activeIndex.value)\n switch (playMode.value) {\n case 'loop':\n audio?.play().catch(() => {})\n break\n case 'single':\n isPlaying.value = false\n break\n case 'shuffle':\n playTrack(Math.floor(Math.random() * props.tracks.length))\n break\n default: // list\n if (activeIndex.value < props.tracks.length - 1) next()\n else {\n activeIndex.value = 0\n initAudio()\n audio?.play().catch(() => {})\n }\n }\n }\n\n /** 切换播放/暂停 */\n function togglePlay() {\n if (!audio) initAudio()\n if (isPlaying.value) {\n audio?.pause()\n isPlaying.value = false\n emit('pause')\n } else {\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', activeIndex.value)\n }\n }\n\n /** 播放指定索引的曲目 */\n function playTrack(idx: number) {\n activeIndex.value = idx\n initAudio()\n audio?.play().catch(() => {})\n isPlaying.value = true\n emit('play', idx)\n }\n\n /** 上一曲 */\n function prev() {\n const idx =\n activeIndex.value <= 0 ? props.tracks.length - 1 : activeIndex.value - 1\n playTrack(idx)\n }\n\n /** 下一曲 */\n function next() {\n const idx =\n activeIndex.value >= props.tracks.length - 1 ? 0 : activeIndex.value + 1\n playTrack(idx)\n }\n\n /** 点击进度条跳转播放位置 */\n function seekByClick(e: MouseEvent) {\n if (!audio || !totalDuration.value) return\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()\n const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))\n audio.currentTime = ratio * totalDuration.value\n currentTime.value = audio.currentTime\n }\n\n /** 处理音量变更 */\n function handleVolumeChange(val: number) {\n volume.value = val\n if (audio) audio.volume = val / 100\n }\n\n /** 切换静音状态 */\n function toggleMute() {\n if (volume.value > 0) {\n prevVolume.value = volume.value\n volume.value = 0\n } else {\n volume.value = prevVolume.value || 70\n }\n if (audio) audio.volume = volume.value / 100\n }\n\n const MODES = ['list', 'loop', 'single', 'shuffle'] as const\n /** 循环切换播放模式 */\n function cycleMode() {\n const idx = MODES.indexOf(playMode.value as (typeof MODES)[number])\n playMode.value = MODES[(idx + 1) % MODES.length]\n emit('modeChange', playMode.value)\n }\n\n // ==================== Lifecycle ====================\n\n watch(\n () => props.tracks,\n () => {\n activeIndex.value = 0\n initAudio()\n }\n )\n\n watch(\n () => props.initialIndex,\n val => {\n if (val !== undefined && val !== activeIndex.value) {\n activeIndex.value = val\n initAudio()\n }\n }\n )\n\n onBeforeUnmount(() => {\n if (audio) {\n audio.pause()\n audio.removeEventListener('timeupdate', onTimeUpdate)\n audio.removeEventListener('loadedmetadata', onMetaLoaded)\n audio.removeEventListener('ended', onEnded)\n audio = null\n }\n })\n\n // 自动播放\n if (props.autoplay && props.tracks.length > 0) {\n playTrack(activeIndex.value)\n }\n</script>\n\n<style scoped lang=\"scss\">\n @use './index.scss';\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_AvatarGroup-D02zci-i.css","names":[],"sources":["../src/components/C_AvatarGroup/index.vue?vue&type=style&index=0&scoped=8be58d5b&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//**\n * C_AvatarGroup — 头像组组件样式\n */\n.c-avatar-group[data-v-8be58d5b] {\n display: inline-flex;\n align-items: center;\n /* ======================== 单个头像 ======================== */\n /* ---- 头像图片 ---- */\n /* ---- 首字母 fallback ---- */\n /* ---- 状态指示点 ---- */\n /* ======================== 溢出 +N ======================== */\n}\n.c-avatar-group.is-rtl[data-v-8be58d5b] {\n flex-direction: row-reverse;\n}\n.c-avatar-group__item[data-v-8be58d5b] {\n position: relative;\n flex-shrink: 0;\n border: 2px solid var(--card-color);\n background: var(--card-color);\n transition: transform 0.2s, z-index 0.2s, box-shadow 0.2s;\n cursor: default;\n z-index: 1;\n}\n.c-avatar-group__item.is-circle[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.c-avatar-group__item.is-square[data-v-8be58d5b] {\n border-radius: 6px;\n}\n.c-avatar-group__item.is-clickable[data-v-8be58d5b] {\n cursor: pointer;\n}\n.c-avatar-group__item.is-clickable[data-v-8be58d5b]:hover {\n transform: translateY(-3px) scale(1.08);\n z-index: 10;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n}\n.c-avatar-group__item[data-v-8be58d5b]:hover {\n z-index: 10;\n}\n.c-avatar-group__img[data-v-8be58d5b] {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n.is-circle .c-avatar-group__img[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.is-square .c-avatar-group__img[data-v-8be58d5b] {\n border-radius: 4px;\n}\n.c-avatar-group__initials[data-v-8be58d5b] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n color: #fff;\n font-weight: 600;\n user-select: none;\n}\n.is-circle .c-avatar-group__initials[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.is-square .c-avatar-group__initials[data-v-8be58d5b] {\n border-radius: 4px;\n}\n.c-avatar-group__status[data-v-8be58d5b] {\n position: absolute;\n bottom: 0;\n right: 0;\n border-radius: 50%;\n border: 2px solid var(--card-color);\n z-index: 2;\n}\n.c-avatar-group__overflow[data-v-8be58d5b] {\n position: relative;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--card-color);\n background: color-mix(in srgb, var(--primary-color) 12%, var(--card-color));\n color: var(--primary-color);\n font-weight: 700;\n z-index: 0;\n transition: transform 0.2s, box-shadow 0.2s;\n}\n.c-avatar-group__overflow.is-circle[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.c-avatar-group__overflow.is-square[data-v-8be58d5b] {\n border-radius: 6px;\n}\n.c-avatar-group__overflow.is-clickable[data-v-8be58d5b] {\n cursor: pointer;\n}\n.c-avatar-group__overflow.is-clickable[data-v-8be58d5b]:hover {\n transform: translateY(-3px) scale(1.08);\n z-index: 10;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);\n background: color-mix(in srgb, var(--primary-color) 20%, var(--card-color));\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"C_AvatarGroup-DatHaUlY.css","names":[],"sources":["../src/components/C_AvatarGroup/index.vue?vue&type=style&index=0&scoped=8be58d5b&lang.scss"],"sourcesContent":["/* unplugin-vue-components disabled *//**\n * C_AvatarGroup — 头像组组件样式\n */\n.c-avatar-group[data-v-8be58d5b] {\n display: inline-flex;\n align-items: center;\n /* ======================== 单个头像 ======================== */\n /* ---- 头像图片 ---- */\n /* ---- 首字母 fallback ---- */\n /* ---- 状态指示点 ---- */\n /* ======================== 溢出 +N ======================== */\n}\n.c-avatar-group.is-rtl[data-v-8be58d5b] {\n flex-direction: row-reverse;\n}\n.c-avatar-group__item[data-v-8be58d5b] {\n position: relative;\n flex-shrink: 0;\n border: 2px solid var(--card-color);\n background: var(--card-color);\n transition: transform 0.2s, z-index 0.2s, box-shadow 0.2s;\n cursor: default;\n z-index: 1;\n}\n.c-avatar-group__item.is-circle[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.c-avatar-group__item.is-square[data-v-8be58d5b] {\n border-radius: 6px;\n}\n.c-avatar-group__item.is-clickable[data-v-8be58d5b] {\n cursor: pointer;\n}\n.c-avatar-group__item.is-clickable[data-v-8be58d5b]:hover {\n transform: translateY(-3px) scale(1.08);\n z-index: 10;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n}\n.c-avatar-group__item[data-v-8be58d5b]:hover {\n z-index: 10;\n}\n.c-avatar-group__img[data-v-8be58d5b] {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n.is-circle .c-avatar-group__img[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.is-square .c-avatar-group__img[data-v-8be58d5b] {\n border-radius: 4px;\n}\n.c-avatar-group__initials[data-v-8be58d5b] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n color: #fff;\n font-weight: 600;\n user-select: none;\n}\n.is-circle .c-avatar-group__initials[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.is-square .c-avatar-group__initials[data-v-8be58d5b] {\n border-radius: 4px;\n}\n.c-avatar-group__status[data-v-8be58d5b] {\n position: absolute;\n bottom: 0;\n right: 0;\n border-radius: 50%;\n border: 2px solid var(--card-color);\n z-index: 2;\n}\n.c-avatar-group__overflow[data-v-8be58d5b] {\n position: relative;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--card-color);\n background: color-mix(in srgb, var(--primary-color) 12%, var(--card-color));\n color: var(--primary-color);\n font-weight: 700;\n z-index: 0;\n transition: transform 0.2s, box-shadow 0.2s;\n}\n.c-avatar-group__overflow.is-circle[data-v-8be58d5b] {\n border-radius: 50%;\n}\n.c-avatar-group__overflow.is-square[data-v-8be58d5b] {\n border-radius: 6px;\n}\n.c-avatar-group__overflow.is-clickable[data-v-8be58d5b] {\n cursor: pointer;\n}\n.c-avatar-group__overflow.is-clickable[data-v-8be58d5b]:hover {\n transform: translateY(-3px) scale(1.08);\n z-index: 10;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);\n background: color-mix(in srgb, var(--primary-color) 20%, var(--card-color));\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./C_AvatarGroup.js`);exports.C_AvatarGroup=e.t,exports.DEFAULT_AVATAR_GROUP_PROPS=e.n,exports.STATUS_COLOR_MAP=e.r,exports.getInitials=e.i,exports.nameToColor=e.a;
|