@sansenjian/qq-music-api 2.2.10 → 2.3.1

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/README.md +10 -11
  3. package/dist/app.js +0 -1
  4. package/dist/koaApp.js +5 -1
  5. package/dist/module/apis/downloadQQMusic.js +2 -1
  6. package/dist/module/apis/music/getMusicPlay.js +3 -2
  7. package/dist/package.json +20 -16
  8. package/dist/routers/context/getNewDisks.js +4 -0
  9. package/dist/routers/context/getRecommend.js +4 -0
  10. package/dist/routers/context/getTicketInfo.js +4 -0
  11. package/dist/util/cookieResolver.js +10 -4
  12. package/docs-dist/404.html +6 -6
  13. package/docs-dist/CHANGELOG-ARCHITECTURE.html +14 -14
  14. package/docs-dist/COOKIE_CONFIG_GUIDE.html +13 -13
  15. package/docs-dist/DEGRADE_EXAMPLES.html +109 -0
  16. package/docs-dist/FALLBACK_MODE_GUIDE.html +21 -21
  17. package/docs-dist/QUICK_START.html +62 -0
  18. package/docs-dist/README.html +17 -17
  19. package/docs-dist/TEST_USER_PLAYLISTS.html +14 -14
  20. package/docs-dist/USER_AVATAR_GUIDE.html +16 -16
  21. package/docs-dist/api/comments.html +12 -12
  22. package/docs-dist/api/index.html +10 -10
  23. package/docs-dist/api/music.html +15 -13
  24. package/docs-dist/api/other.html +13 -13
  25. package/docs-dist/api/playground.html +27 -0
  26. package/docs-dist/api/playlist.html +15 -15
  27. package/docs-dist/api/rank.html +12 -12
  28. package/docs-dist/api/search.html +13 -13
  29. package/docs-dist/api/singer.html +12 -12
  30. package/docs-dist/api/user.html +16 -16
  31. package/docs-dist/assets/{CHANGELOG-ARCHITECTURE.md.DV9Xr7ve.js → CHANGELOG-ARCHITECTURE.md.CYHmaBdY.js} +5 -5
  32. package/docs-dist/assets/CHANGELOG-ARCHITECTURE.md.CYHmaBdY.lean.js +1 -0
  33. package/docs-dist/assets/COOKIE_CONFIG_GUIDE.md.Nvywo7CW.js +13 -0
  34. package/docs-dist/assets/COOKIE_CONFIG_GUIDE.md.Nvywo7CW.lean.js +1 -0
  35. package/docs-dist/assets/DEGRADE_EXAMPLES.md.Cz2J-qwE.js +83 -0
  36. package/docs-dist/assets/DEGRADE_EXAMPLES.md.Cz2J-qwE.lean.js +1 -0
  37. package/docs-dist/assets/{FALLBACK_MODE_GUIDE.md.0wqXqYxw.js → FALLBACK_MODE_GUIDE.md.wKA9yqoI.js} +12 -12
  38. package/docs-dist/assets/FALLBACK_MODE_GUIDE.md.wKA9yqoI.lean.js +1 -0
  39. package/docs-dist/assets/QUICK_START.md.D5KfDgbs.js +36 -0
  40. package/docs-dist/assets/QUICK_START.md.D5KfDgbs.lean.js +1 -0
  41. package/docs-dist/assets/README.md.CHbArqA7.js +421 -0
  42. package/docs-dist/assets/README.md.CHbArqA7.lean.js +1 -0
  43. package/docs-dist/assets/TEST_USER_PLAYLISTS.md.BfBxYbaM.js +16 -0
  44. package/docs-dist/assets/TEST_USER_PLAYLISTS.md.BfBxYbaM.lean.js +1 -0
  45. package/docs-dist/assets/{USER_AVATAR_GUIDE.md.CGPI9GUj.js → USER_AVATAR_GUIDE.md.D5Rti1Gg.js} +7 -7
  46. package/docs-dist/assets/USER_AVATAR_GUIDE.md.D5Rti1Gg.lean.js +1 -0
  47. package/docs-dist/assets/{api_comments.md.CATvWhrg.js → api_comments.md.5nDhrWa8.js} +3 -3
  48. package/docs-dist/assets/api_comments.md.5nDhrWa8.lean.js +1 -0
  49. package/docs-dist/assets/api_index.md.DdG1WHkZ.js +1 -0
  50. package/docs-dist/assets/api_index.md.DdG1WHkZ.lean.js +1 -0
  51. package/docs-dist/assets/{api_music.md.D20_neZB.js → api_music.md.D66hq-_4.js} +6 -4
  52. package/docs-dist/assets/api_music.md.D66hq-_4.lean.js +1 -0
  53. package/docs-dist/assets/api_other.md.9KhspVEM.js +7 -0
  54. package/docs-dist/assets/api_other.md.9KhspVEM.lean.js +1 -0
  55. package/docs-dist/assets/api_playground.md.BZBvYMm2.js +11 -0
  56. package/docs-dist/assets/api_playground.md.BZBvYMm2.lean.js +11 -0
  57. package/docs-dist/assets/{api_playlist.md.CyLdLRR9.js → api_playlist.md.BQK32ZyE.js} +6 -6
  58. package/docs-dist/assets/api_playlist.md.BQK32ZyE.lean.js +1 -0
  59. package/docs-dist/assets/{api_rank.md.Z3xyYG_S.js → api_rank.md.z7YBwVgw.js} +3 -3
  60. package/docs-dist/assets/api_rank.md.z7YBwVgw.lean.js +1 -0
  61. package/docs-dist/assets/{api_search.md.D_lbFmYo.js → api_search.md.GfzIBRfc.js} +4 -4
  62. package/docs-dist/assets/api_search.md.GfzIBRfc.lean.js +1 -0
  63. package/docs-dist/assets/api_singer.md.BDR-_qDH.js +21 -0
  64. package/docs-dist/assets/api_singer.md.BDR-_qDH.lean.js +1 -0
  65. package/docs-dist/assets/{api_user.md.4WdmTXIB.js → api_user.md.Dx8BWGrb.js} +7 -7
  66. package/docs-dist/assets/api_user.md.Dx8BWGrb.lean.js +1 -0
  67. package/docs-dist/assets/app.CxuIZ1W7.js +1 -0
  68. package/docs-dist/assets/chunks/@localSearchIndexroot.ygoKgu27.js +1 -0
  69. package/docs-dist/assets/chunks/VPLocalSearchBox.BqgkAhNM.js +3 -0
  70. package/docs-dist/assets/chunks/framework.BUY3a635.js +4 -0
  71. package/docs-dist/assets/chunks/theme.C-Z3DN0r.js +2 -0
  72. package/docs-dist/assets/{guide_architecture.md.D_46khUI.js → guide_architecture.md.SHnKkzwb.js} +20 -20
  73. package/docs-dist/assets/guide_architecture.md.SHnKkzwb.lean.js +1 -0
  74. package/docs-dist/assets/guide_authentication.md.CZCKocgR.js +4 -0
  75. package/docs-dist/assets/guide_authentication.md.CZCKocgR.lean.js +1 -0
  76. package/docs-dist/assets/guide_index.md.CkJ-jjL0.js +1 -0
  77. package/docs-dist/assets/guide_index.md.CkJ-jjL0.lean.js +1 -0
  78. package/docs-dist/assets/guide_installation.md.D2TBzILh.js +7 -0
  79. package/docs-dist/assets/guide_installation.md.D2TBzILh.lean.js +1 -0
  80. package/docs-dist/assets/guide_quickstart.md.J7Sib8wg.js +13 -0
  81. package/docs-dist/assets/guide_quickstart.md.J7Sib8wg.lean.js +1 -0
  82. package/docs-dist/assets/index.md.ClwYf6Qc.js +1 -0
  83. package/docs-dist/assets/index.md.ClwYf6Qc.lean.js +1 -0
  84. package/docs-dist/assets/inter-italic-cyrillic-ext._dlW9xFb.woff2 +0 -0
  85. package/docs-dist/assets/inter-italic-cyrillic.D7dRslh9.woff2 +0 -0
  86. package/docs-dist/assets/inter-italic-greek-ext.Ct-Tf2bq.woff2 +0 -0
  87. package/docs-dist/assets/inter-italic-greek.DNcpQ8QC.woff2 +0 -0
  88. package/docs-dist/assets/inter-italic-latin-ext.DytegdRQ.woff2 +0 -0
  89. package/docs-dist/assets/inter-italic-latin.COaG5lWR.woff2 +0 -0
  90. package/docs-dist/assets/inter-italic-vietnamese.BI5UxJD-.woff2 +0 -0
  91. package/docs-dist/assets/inter-roman-cyrillic-ext.BeNbU08G.woff2 +0 -0
  92. package/docs-dist/assets/inter-roman-cyrillic.CD0kT8R4.woff2 +0 -0
  93. package/docs-dist/assets/inter-roman-greek-ext.CFAEQ5Ow.woff2 +0 -0
  94. package/docs-dist/assets/inter-roman-greek.Dsf7YjP7.woff2 +0 -0
  95. package/docs-dist/assets/inter-roman-latin-ext.Dl_ayf4-.woff2 +0 -0
  96. package/docs-dist/assets/inter-roman-latin.Cy4MYw_J.woff2 +0 -0
  97. package/docs-dist/assets/inter-roman-vietnamese.CpqCnS2H.woff2 +0 -0
  98. package/docs-dist/assets/reference_response-format.md.BrGoGoPV.js +12 -0
  99. package/docs-dist/assets/reference_response-format.md.BrGoGoPV.lean.js +1 -0
  100. package/docs-dist/assets/style.D_YoXH3a.css +1 -0
  101. package/docs-dist/guide/architecture.html +29 -29
  102. package/docs-dist/guide/authentication.html +12 -12
  103. package/docs-dist/guide/index.html +10 -10
  104. package/docs-dist/guide/installation.html +13 -13
  105. package/docs-dist/guide/quickstart.html +15 -15
  106. package/docs-dist/hashmap.json +1 -1
  107. package/docs-dist/index.html +10 -10
  108. package/docs-dist/reference/response-format.html +13 -13
  109. package/docs-dist/version.json +3 -3
  110. package/package.json +20 -16
  111. package/public/index.html +966 -0
  112. package/public/playground-utils.js +150 -0
  113. package/dist/jest.config.js +0 -52
  114. package/dist/scripts/run-tests-with-flags.js +0 -139
  115. package/dist/types/api.js +0 -55
  116. package/docs-dist/assets/CHANGELOG-ARCHITECTURE.md.DV9Xr7ve.lean.js +0 -1
  117. package/docs-dist/assets/COOKIE_CONFIG_GUIDE.md.B2-aTdcH.js +0 -13
  118. package/docs-dist/assets/COOKIE_CONFIG_GUIDE.md.B2-aTdcH.lean.js +0 -1
  119. package/docs-dist/assets/FALLBACK_MODE_GUIDE.md.0wqXqYxw.lean.js +0 -1
  120. package/docs-dist/assets/README.md.DFCMeLFa.js +0 -421
  121. package/docs-dist/assets/README.md.DFCMeLFa.lean.js +0 -1
  122. package/docs-dist/assets/TEST_USER_PLAYLISTS.md.Bj0AVpHw.js +0 -16
  123. package/docs-dist/assets/TEST_USER_PLAYLISTS.md.Bj0AVpHw.lean.js +0 -1
  124. package/docs-dist/assets/USER_AVATAR_GUIDE.md.CGPI9GUj.lean.js +0 -1
  125. package/docs-dist/assets/api_comments.md.CATvWhrg.lean.js +0 -1
  126. package/docs-dist/assets/api_index.md.Dqx3qXyO.js +0 -1
  127. package/docs-dist/assets/api_index.md.Dqx3qXyO.lean.js +0 -1
  128. package/docs-dist/assets/api_music.md.D20_neZB.lean.js +0 -1
  129. package/docs-dist/assets/api_other.md.CXyEsl8R.js +0 -7
  130. package/docs-dist/assets/api_other.md.CXyEsl8R.lean.js +0 -1
  131. package/docs-dist/assets/api_playlist.md.CyLdLRR9.lean.js +0 -1
  132. package/docs-dist/assets/api_rank.md.Z3xyYG_S.lean.js +0 -1
  133. package/docs-dist/assets/api_search.md.D_lbFmYo.lean.js +0 -1
  134. package/docs-dist/assets/api_singer.md.BbyYE88D.js +0 -21
  135. package/docs-dist/assets/api_singer.md.BbyYE88D.lean.js +0 -1
  136. package/docs-dist/assets/api_user.md.4WdmTXIB.lean.js +0 -1
  137. package/docs-dist/assets/app.2f7gcITE.js +0 -1
  138. package/docs-dist/assets/chunks/@localSearchIndexroot.D461xa5C.js +0 -1
  139. package/docs-dist/assets/chunks/VPLocalSearchBox.BiPSl83v.js +0 -9
  140. package/docs-dist/assets/chunks/framework.aJbMEiY9.js +0 -19
  141. package/docs-dist/assets/chunks/theme.BrMPT0hE.js +0 -2
  142. package/docs-dist/assets/guide_architecture.md.D_46khUI.lean.js +0 -1
  143. package/docs-dist/assets/guide_authentication.md.nCiAu07w.js +0 -4
  144. package/docs-dist/assets/guide_authentication.md.nCiAu07w.lean.js +0 -1
  145. package/docs-dist/assets/guide_index.md.gLozHqz5.js +0 -1
  146. package/docs-dist/assets/guide_index.md.gLozHqz5.lean.js +0 -1
  147. package/docs-dist/assets/guide_installation.md.BUDl8zk1.js +0 -7
  148. package/docs-dist/assets/guide_installation.md.BUDl8zk1.lean.js +0 -1
  149. package/docs-dist/assets/guide_quickstart.md.COQUzUN9.js +0 -13
  150. package/docs-dist/assets/guide_quickstart.md.COQUzUN9.lean.js +0 -1
  151. package/docs-dist/assets/index.md.DBZfQ2kF.js +0 -1
  152. package/docs-dist/assets/index.md.DBZfQ2kF.lean.js +0 -1
  153. package/docs-dist/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  154. package/docs-dist/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  155. package/docs-dist/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  156. package/docs-dist/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  157. package/docs-dist/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  158. package/docs-dist/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  159. package/docs-dist/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  160. package/docs-dist/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  161. package/docs-dist/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  162. package/docs-dist/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  163. package/docs-dist/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  164. package/docs-dist/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  165. package/docs-dist/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  166. package/docs-dist/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  167. package/docs-dist/assets/reference_response-format.md.yrdeqFUN.js +0 -12
  168. package/docs-dist/assets/reference_response-format.md.yrdeqFUN.lean.js +0 -1
  169. package/docs-dist/assets/style.DM4qKDd4.css +0 -1
@@ -0,0 +1,966 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
+ <title>QQ 音乐 API</title>
8
+ <style>
9
+ :root {
10
+ --bg: #f5f7fb;
11
+ --panel: #ffffff;
12
+ --panel-soft: #f9fbff;
13
+ --text: #172033;
14
+ --muted: #647084;
15
+ --line: #dfe5ef;
16
+ --brand: #12b7a7;
17
+ --brand-dark: #0a7f75;
18
+ --accent: #f0b429;
19
+ --danger: #d94b4b;
20
+ --shadow: 0 18px 45px rgba(28, 38, 61, 0.11);
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ margin: 0;
29
+ min-height: 100vh;
30
+ color: var(--text);
31
+ background: var(--bg);
32
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
33
+ letter-spacing: 0;
34
+ }
35
+
36
+ a {
37
+ color: inherit;
38
+ text-decoration: none;
39
+ }
40
+
41
+ .shell {
42
+ width: min(1180px, calc(100% - 32px));
43
+ margin: 0 auto;
44
+ padding: 26px 0 48px;
45
+ }
46
+
47
+ .topbar {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ gap: 20px;
52
+ padding: 12px 0 22px;
53
+ }
54
+
55
+ .brand {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 12px;
59
+ min-width: 0;
60
+ }
61
+
62
+ .logo {
63
+ display: grid;
64
+ width: 42px;
65
+ height: 42px;
66
+ flex: 0 0 auto;
67
+ place-items: center;
68
+ border-radius: 10px;
69
+ color: white;
70
+ background: var(--brand);
71
+ font-weight: 800;
72
+ }
73
+
74
+ .brand h1 {
75
+ margin: 0;
76
+ font-size: 21px;
77
+ line-height: 1.2;
78
+ }
79
+
80
+ .brand p {
81
+ margin: 3px 0 0;
82
+ color: var(--muted);
83
+ font-size: 13px;
84
+ }
85
+
86
+ .nav {
87
+ display: flex;
88
+ gap: 10px;
89
+ flex-wrap: wrap;
90
+ justify-content: flex-end;
91
+ }
92
+
93
+ .nav a,
94
+ .action,
95
+ button {
96
+ display: inline-flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ min-height: 38px;
100
+ border: 1px solid var(--line);
101
+ border-radius: 7px;
102
+ padding: 0 13px;
103
+ color: var(--text);
104
+ background: var(--panel);
105
+ font-weight: 650;
106
+ font-size: 14px;
107
+ cursor: pointer;
108
+ }
109
+
110
+ .nav a.primary,
111
+ .action.primary,
112
+ button.primary {
113
+ border-color: var(--brand);
114
+ color: white;
115
+ background: var(--brand);
116
+ }
117
+
118
+ .nav a:hover,
119
+ .action:hover,
120
+ button:hover {
121
+ border-color: rgba(18, 183, 167, 0.55);
122
+ background: #eefbf8;
123
+ }
124
+
125
+ .nav a.primary:hover,
126
+ .action.primary:hover,
127
+ button.primary:hover {
128
+ background: var(--brand-dark);
129
+ }
130
+
131
+ button:disabled {
132
+ cursor: not-allowed;
133
+ opacity: 0.62;
134
+ }
135
+
136
+ .ghost-button {
137
+ color: var(--muted);
138
+ background: var(--panel-soft);
139
+ }
140
+
141
+ .hero {
142
+ display: grid;
143
+ grid-template-columns: minmax(0, 1.15fr) minmax(320px, 0.85fr);
144
+ gap: 22px;
145
+ align-items: stretch;
146
+ }
147
+
148
+ .intro {
149
+ min-height: 330px;
150
+ padding: 34px;
151
+ border: 1px solid var(--line);
152
+ border-radius: 8px;
153
+ background:
154
+ linear-gradient(90deg, rgba(18, 183, 167, 0.12), rgba(240, 180, 41, 0.08)),
155
+ var(--panel);
156
+ box-shadow: var(--shadow);
157
+ }
158
+
159
+ .eyebrow {
160
+ display: inline-flex;
161
+ align-items: center;
162
+ gap: 8px;
163
+ margin-bottom: 18px;
164
+ color: var(--brand-dark);
165
+ font-size: 13px;
166
+ font-weight: 800;
167
+ }
168
+
169
+ .dot {
170
+ width: 8px;
171
+ height: 8px;
172
+ border-radius: 50%;
173
+ background: #26c281;
174
+ box-shadow: 0 0 0 5px rgba(38, 194, 129, 0.16);
175
+ }
176
+
177
+ .intro h2 {
178
+ max-width: 720px;
179
+ margin: 0;
180
+ font-size: clamp(32px, 5vw, 58px);
181
+ line-height: 1;
182
+ letter-spacing: 0;
183
+ }
184
+
185
+ .intro p {
186
+ max-width: 640px;
187
+ margin: 18px 0 0;
188
+ color: var(--muted);
189
+ font-size: 16px;
190
+ line-height: 1.8;
191
+ }
192
+
193
+ .hero-actions {
194
+ display: flex;
195
+ gap: 12px;
196
+ flex-wrap: wrap;
197
+ margin-top: 26px;
198
+ }
199
+
200
+ .status-card {
201
+ display: grid;
202
+ gap: 14px;
203
+ padding: 22px;
204
+ border: 1px solid var(--line);
205
+ border-radius: 8px;
206
+ background: var(--panel);
207
+ box-shadow: var(--shadow);
208
+ }
209
+
210
+ .status-row {
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: space-between;
214
+ gap: 12px;
215
+ padding-bottom: 12px;
216
+ border-bottom: 1px solid var(--line);
217
+ }
218
+
219
+ .status-row:last-child {
220
+ padding-bottom: 0;
221
+ border-bottom: 0;
222
+ }
223
+
224
+ .status-label {
225
+ color: var(--muted);
226
+ font-size: 13px;
227
+ }
228
+
229
+ .status-value {
230
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
231
+ font-size: 13px;
232
+ font-weight: 700;
233
+ text-align: right;
234
+ }
235
+
236
+ .base-control {
237
+ display: grid;
238
+ gap: 8px;
239
+ }
240
+
241
+ .base-control input {
242
+ min-height: 38px;
243
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
244
+ font-size: 13px;
245
+ }
246
+
247
+ .tester {
248
+ margin-top: 22px;
249
+ padding: 22px;
250
+ border: 1px solid var(--line);
251
+ border-radius: 8px;
252
+ background: var(--panel);
253
+ box-shadow: var(--shadow);
254
+ }
255
+
256
+ .section-head {
257
+ display: flex;
258
+ align-items: end;
259
+ justify-content: space-between;
260
+ gap: 16px;
261
+ margin-bottom: 16px;
262
+ }
263
+
264
+ .section-head h2 {
265
+ margin: 0;
266
+ font-size: 22px;
267
+ line-height: 1.2;
268
+ }
269
+
270
+ .section-head p {
271
+ max-width: 620px;
272
+ margin: 0;
273
+ color: var(--muted);
274
+ font-size: 14px;
275
+ line-height: 1.7;
276
+ }
277
+
278
+ .request-builder {
279
+ display: grid;
280
+ grid-template-columns: 150px 120px minmax(0, 1fr) 116px;
281
+ gap: 10px;
282
+ align-items: stretch;
283
+ }
284
+
285
+ select,
286
+ input {
287
+ width: 100%;
288
+ min-height: 42px;
289
+ border: 1px solid var(--line);
290
+ border-radius: 7px;
291
+ padding: 0 12px;
292
+ color: var(--text);
293
+ background: var(--panel-soft);
294
+ font: inherit;
295
+ }
296
+
297
+ select:focus,
298
+ input:focus,
299
+ button:focus-visible,
300
+ a:focus-visible {
301
+ outline: 3px solid rgba(18, 183, 167, 0.22);
302
+ outline-offset: 2px;
303
+ border-color: var(--brand);
304
+ }
305
+
306
+ .request-actions,
307
+ .result-toolbar,
308
+ .history {
309
+ display: flex;
310
+ flex-wrap: wrap;
311
+ gap: 8px;
312
+ margin-top: 10px;
313
+ }
314
+
315
+ .request-actions {
316
+ justify-content: flex-end;
317
+ }
318
+
319
+ .result-toolbar {
320
+ align-items: center;
321
+ justify-content: space-between;
322
+ margin-top: 14px;
323
+ }
324
+
325
+ .result-meta {
326
+ display: flex;
327
+ flex-wrap: wrap;
328
+ gap: 8px;
329
+ color: var(--muted);
330
+ font-size: 13px;
331
+ }
332
+
333
+ .badge {
334
+ display: inline-flex;
335
+ align-items: center;
336
+ min-height: 28px;
337
+ border: 1px solid var(--line);
338
+ border-radius: 999px;
339
+ padding: 0 10px;
340
+ background: var(--panel-soft);
341
+ font-weight: 700;
342
+ }
343
+
344
+ .badge.success {
345
+ border-color: rgba(18, 183, 167, 0.34);
346
+ color: var(--brand-dark);
347
+ background: #eefbf8;
348
+ }
349
+
350
+ .badge.error {
351
+ border-color: rgba(217, 75, 75, 0.34);
352
+ color: var(--danger);
353
+ background: #fff1f1;
354
+ }
355
+
356
+ .history {
357
+ min-height: 34px;
358
+ align-items: center;
359
+ }
360
+
361
+ .history:empty {
362
+ display: none;
363
+ }
364
+
365
+ .history button {
366
+ max-width: 100%;
367
+ min-height: 32px;
368
+ color: var(--muted);
369
+ background: var(--panel-soft);
370
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
371
+ font-size: 12px;
372
+ font-weight: 650;
373
+ }
374
+
375
+ .result {
376
+ display: grid;
377
+ gap: 8px;
378
+ margin-top: 8px;
379
+ }
380
+
381
+ .request-url {
382
+ overflow-wrap: anywhere;
383
+ padding: 11px 12px;
384
+ border: 1px dashed var(--line);
385
+ border-radius: 7px;
386
+ color: var(--muted);
387
+ background: var(--panel-soft);
388
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
389
+ font-size: 13px;
390
+ }
391
+
392
+ pre {
393
+ min-height: 130px;
394
+ max-height: 360px;
395
+ overflow: auto;
396
+ margin: 0;
397
+ padding: 14px;
398
+ border-radius: 7px;
399
+ color: #d7e2f3;
400
+ background: #172033;
401
+ font-size: 13px;
402
+ line-height: 1.6;
403
+ white-space: pre-wrap;
404
+ }
405
+
406
+ .grid {
407
+ display: grid;
408
+ grid-template-columns: repeat(3, minmax(0, 1fr));
409
+ gap: 14px;
410
+ margin-top: 22px;
411
+ }
412
+
413
+ .group {
414
+ display: grid;
415
+ align-content: start;
416
+ gap: 10px;
417
+ padding: 18px;
418
+ border: 1px solid var(--line);
419
+ border-radius: 8px;
420
+ background: var(--panel);
421
+ }
422
+
423
+ .group h3 {
424
+ margin: 0 0 4px;
425
+ font-size: 17px;
426
+ }
427
+
428
+ .endpoint {
429
+ display: flex;
430
+ align-items: center;
431
+ justify-content: space-between;
432
+ gap: 10px;
433
+ min-height: 42px;
434
+ padding: 9px 10px;
435
+ border: 1px solid var(--line);
436
+ border-radius: 7px;
437
+ background: var(--panel-soft);
438
+ }
439
+
440
+ .endpoint:hover,
441
+ .endpoint.active {
442
+ border-color: rgba(18, 183, 167, 0.55);
443
+ background: #eefbf8;
444
+ }
445
+
446
+ .endpoint span {
447
+ min-width: 0;
448
+ overflow: hidden;
449
+ text-overflow: ellipsis;
450
+ white-space: nowrap;
451
+ font-weight: 650;
452
+ font-size: 14px;
453
+ }
454
+
455
+ .endpoint code {
456
+ flex: 0 0 auto;
457
+ color: var(--muted);
458
+ font-size: 12px;
459
+ }
460
+
461
+ .notice {
462
+ display: grid;
463
+ grid-template-columns: 1fr auto;
464
+ gap: 16px;
465
+ align-items: center;
466
+ margin-top: 22px;
467
+ padding: 18px;
468
+ border: 1px solid #ecd996;
469
+ border-radius: 8px;
470
+ background: #fff8df;
471
+ }
472
+
473
+ .notice strong {
474
+ display: block;
475
+ margin-bottom: 4px;
476
+ }
477
+
478
+ .notice p {
479
+ margin: 0;
480
+ color: #6f5b17;
481
+ line-height: 1.7;
482
+ font-size: 14px;
483
+ }
484
+
485
+ .footer {
486
+ display: flex;
487
+ justify-content: space-between;
488
+ gap: 16px;
489
+ margin-top: 26px;
490
+ color: var(--muted);
491
+ font-size: 13px;
492
+ }
493
+
494
+ @media (max-width: 900px) {
495
+ .topbar,
496
+ .hero,
497
+ .notice,
498
+ .footer {
499
+ grid-template-columns: 1fr;
500
+ }
501
+
502
+ .topbar,
503
+ .notice,
504
+ .footer {
505
+ display: grid;
506
+ }
507
+
508
+ .nav {
509
+ justify-content: flex-start;
510
+ }
511
+
512
+ .grid {
513
+ grid-template-columns: repeat(2, minmax(0, 1fr));
514
+ }
515
+ }
516
+
517
+ @media (max-width: 640px) {
518
+ .shell {
519
+ width: min(100% - 22px, 1180px);
520
+ padding-top: 12px;
521
+ }
522
+
523
+ .intro,
524
+ .status-card,
525
+ .tester,
526
+ .group,
527
+ .notice {
528
+ padding: 16px;
529
+ }
530
+
531
+ .request-builder,
532
+ .grid {
533
+ grid-template-columns: 1fr;
534
+ }
535
+
536
+ .request-actions,
537
+ .result-toolbar {
538
+ justify-content: stretch;
539
+ }
540
+
541
+ .request-actions button,
542
+ .result-toolbar button {
543
+ flex: 1 1 120px;
544
+ }
545
+
546
+ .intro {
547
+ min-height: auto;
548
+ }
549
+
550
+ .endpoint {
551
+ align-items: flex-start;
552
+ flex-direction: column;
553
+ }
554
+ }
555
+ </style>
556
+ </head>
557
+ <body>
558
+ <main class="shell">
559
+ <header class="topbar">
560
+ <a class="brand" href="/">
561
+ <span class="logo">QQ</span>
562
+ <span>
563
+ <h1>QQ 音乐 API</h1>
564
+ <p>Koa 2 + TypeScript 服务</p>
565
+ </span>
566
+ </a>
567
+ <nav class="nav" aria-label="快捷导航">
568
+ <a href="/getHotkey" target="_blank" rel="noreferrer">热词</a>
569
+ <a href="/user/getCookie" target="_blank" rel="noreferrer">Cookie</a>
570
+ <a class="primary" href="https://sansenjian.github.io/qq-music-api/" target="_blank" rel="noreferrer">在线文档</a>
571
+ </nav>
572
+ </header>
573
+
574
+ <section class="hero">
575
+ <div class="intro">
576
+ <div class="eyebrow"><span class="dot"></span> 服务已启动</div>
577
+ <h2>用于调试和集成的 QQ 音乐 API 服务</h2>
578
+ <p>当前实例监听在本机 3200 端口,提供搜索、歌单、歌词、播放链接、用户能力和扫码登录等接口。下面可以直接发起一次本地请求,快速确认服务与上游接口状态。</p>
579
+ <div class="hero-actions">
580
+ <a class="action primary" href="/getMusicPlay?songmid=003rJSwm3TechU" target="_blank" rel="noreferrer">测试播放链接</a>
581
+ <a class="action" href="/getLyric?songmid=003rJSwm3TechU&isFormat=1" target="_blank" rel="noreferrer">测试歌词</a>
582
+ </div>
583
+ </div>
584
+
585
+ <aside class="status-card" aria-label="服务信息">
586
+ <div class="status-row">
587
+ <span class="status-label">Base URL</span>
588
+ <span class="status-value" id="baseUrlText">http://localhost:3200</span>
589
+ </div>
590
+ <div class="base-control">
591
+ <span class="status-label">接口地址</span>
592
+ <input id="baseUrlInput" value="http://localhost:3200" spellcheck="false" aria-label="接口服务地址">
593
+ </div>
594
+ <div class="status-row">
595
+ <span class="status-label">运行模式</span>
596
+ <span class="status-value">HTTP API</span>
597
+ </div>
598
+ <div class="status-row">
599
+ <span class="status-label">公开接口</span>
600
+ <span class="status-value">GET / POST</span>
601
+ </div>
602
+ <div class="status-row">
603
+ <span class="status-label">登录态</span>
604
+ <span class="status-value">Cookie 可选</span>
605
+ </div>
606
+ <div class="status-row">
607
+ <span class="status-label">文档服务</span>
608
+ <span class="status-value">npm run docs:dev</span>
609
+ </div>
610
+ </aside>
611
+ </section>
612
+
613
+ <section class="tester">
614
+ <div class="section-head">
615
+ <div>
616
+ <h2>快速请求</h2>
617
+ <p>选择一个常用接口,或直接编辑路径与参数。请求会从当前浏览器发往本地服务。</p>
618
+ </div>
619
+ </div>
620
+
621
+ <div class="request-builder">
622
+ <select id="preset" aria-label="选择接口">
623
+ <option value="/getHotkey">搜索热词</option>
624
+ <option value="/getSearchByKey?key=周杰伦&limit=10&page=1">关键词搜索</option>
625
+ <option value="/getMusicPlay?songmid=003rJSwm3TechU">播放链接</option>
626
+ <option value="/getLyric?songmid=003rJSwm3TechU&isFormat=1">歌词</option>
627
+ <option value="/getTopLists">榜单列表</option>
628
+ <option value="/user/getCookie">Cookie 信息</option>
629
+ </select>
630
+ <select id="methodInput" aria-label="请求方法">
631
+ <option value="GET">GET</option>
632
+ <option value="POST">POST</option>
633
+ </select>
634
+ <input id="pathInput" value="/getHotkey" spellcheck="false" aria-label="请求路径">
635
+ <button class="primary" id="sendButton" type="button">发送</button>
636
+ </div>
637
+ <div class="request-actions" aria-label="请求操作">
638
+ <button class="ghost-button" id="copyUrlButton" type="button">复制 URL</button>
639
+ <button class="ghost-button" id="openUrlButton" type="button">新窗口打开</button>
640
+ <button class="ghost-button" id="clearButton" type="button">清空结果</button>
641
+ <button class="ghost-button" id="abortButton" type="button" disabled>取消请求</button>
642
+ </div>
643
+
644
+ <div class="result-toolbar" aria-live="polite">
645
+ <div class="result-meta">
646
+ <span class="badge" id="statusBadge">待发送</span>
647
+ <span class="badge" id="timeBadge">耗时 --</span>
648
+ <span class="badge" id="sizeBadge">大小 --</span>
649
+ </div>
650
+ <button class="ghost-button" id="copyResultButton" type="button">复制结果</button>
651
+ </div>
652
+
653
+ <div class="result" aria-label="请求结果">
654
+ <div class="request-url" id="requestUrl">http://localhost:3200/getHotkey</div>
655
+ <pre id="responseBox">响应会显示在这里</pre>
656
+ </div>
657
+ <div class="history" id="historyList" aria-label="最近请求"></div>
658
+ </section>
659
+
660
+ <section class="grid" aria-label="接口分组">
661
+ <div class="group">
662
+ <h3>搜索</h3>
663
+ <a class="endpoint" href="/getHotkey" data-method="GET" target="_blank" rel="noreferrer"><span>搜索热词</span><code>GET</code></a>
664
+ <a class="endpoint" href="/getSearchByKey?key=周杰伦" data-method="GET" target="_blank" rel="noreferrer"><span>关键词搜索</span><code>GET</code></a>
665
+ <a class="endpoint" href="/getSmartbox?key=周杰伦" data-method="GET" target="_blank" rel="noreferrer"><span>智能提示</span><code>GET</code></a>
666
+ </div>
667
+
668
+ <div class="group">
669
+ <h3>音乐</h3>
670
+ <a class="endpoint" href="/getSongInfo?songmid=003DQQ5Z1JYVgJ" data-method="GET" target="_blank" rel="noreferrer"><span>歌曲信息</span><code>GET</code></a>
671
+ <a class="endpoint" href="/getMusicPlay?songmid=003rJSwm3TechU" data-method="GET" target="_blank" rel="noreferrer"><span>播放链接</span><code>GET</code></a>
672
+ <a class="endpoint" href="/getLyric?songmid=003rJSwm3TechU&isFormat=1" data-method="GET" target="_blank" rel="noreferrer"><span>歌词</span><code>GET</code></a>
673
+ </div>
674
+
675
+ <div class="group">
676
+ <h3>歌单</h3>
677
+ <a class="endpoint" href="/getSongListCategories" data-method="GET" target="_blank" rel="noreferrer"><span>歌单分类</span><code>GET</code></a>
678
+ <a class="endpoint" href="/getSongLists?categoryId=10000000" data-method="GET" target="_blank" rel="noreferrer"><span>歌单列表</span><code>GET</code></a>
679
+ <a class="endpoint" href="/getSongListDetail?disstid=7707261125" data-method="GET" target="_blank" rel="noreferrer"><span>歌单详情</span><code>GET</code></a>
680
+ </div>
681
+
682
+ <div class="group">
683
+ <h3>歌手</h3>
684
+ <a class="endpoint" href="/getSingerList" data-method="GET" target="_blank" rel="noreferrer"><span>歌手列表</span><code>GET</code></a>
685
+ <a class="endpoint" href="/getSingerAlbum?singermid=0025NhlN2yWrP4" data-method="GET" target="_blank" rel="noreferrer"><span>歌手专辑</span><code>GET</code></a>
686
+ <a class="endpoint" href="/getSingerHotsong?singermid=0025NhlN2yWrP4" data-method="GET" target="_blank" rel="noreferrer"><span>热门歌曲</span><code>GET</code></a>
687
+ </div>
688
+
689
+ <div class="group">
690
+ <h3>榜单与推荐</h3>
691
+ <a class="endpoint" href="/getTopLists" data-method="GET" target="_blank" rel="noreferrer"><span>榜单列表</span><code>GET</code></a>
692
+ <a class="endpoint" href="/getRanks" data-method="GET" target="_blank" rel="noreferrer"><span>榜单详情</span><code>GET</code></a>
693
+ <a class="endpoint" href="/getRecommend" data-method="GET" target="_blank" rel="noreferrer"><span>首页推荐</span><code>GET</code></a>
694
+ </div>
695
+
696
+ <div class="group">
697
+ <h3>用户</h3>
698
+ <a class="endpoint" href="/getQQLoginQr" data-method="GET" target="_blank" rel="noreferrer"><span>登录二维码</span><code>GET</code></a>
699
+ <a class="endpoint" href="/user/getCookie" data-method="GET" target="_blank" rel="noreferrer"><span>Cookie 信息</span><code>GET</code></a>
700
+ <a class="endpoint" href="/user/getUserAvatar?uin=123456789" data-method="GET" target="_blank" rel="noreferrer"><span>用户头像</span><code>GET</code></a>
701
+ </div>
702
+ </section>
703
+
704
+ <section class="notice">
705
+ <div>
706
+ <strong>需要会员或登录态的接口</strong>
707
+ <p>可以通过 query 参数 `cookie=...` 或请求头 `X-Custom-Cookie` 传入登录态。播放链接接口会把 `qqmusic_key` 转发为上游需要的 `authst`。</p>
708
+ </div>
709
+ <a class="action" href="https://sansenjian.github.io/qq-music-api/api/playground" target="_blank" rel="noreferrer">打开调试台</a>
710
+ </section>
711
+
712
+ <footer class="footer">
713
+ <span>QQ Music API · 本地服务首页</span>
714
+ <span>默认端口 3200</span>
715
+ </footer>
716
+ </main>
717
+
718
+ <script type="module">
719
+ import {
720
+ buildFetchOptions,
721
+ buildRequestUrl,
722
+ fetchPlaygroundRequest,
723
+ formatBytes,
724
+ getErrorMessage,
725
+ getInitialBaseUrl,
726
+ isAbortError,
727
+ normalizeBaseUrl,
728
+ normalizePath
729
+ } from './playground-utils.js';
730
+
731
+ const preset = document.querySelector('#preset');
732
+ const methodInput = document.querySelector('#methodInput');
733
+ const pathInput = document.querySelector('#pathInput');
734
+ const requestUrl = document.querySelector('#requestUrl');
735
+ const responseBox = document.querySelector('#responseBox');
736
+ const baseUrlText = document.querySelector('#baseUrlText');
737
+ const baseUrlInput = document.querySelector('#baseUrlInput');
738
+ const sendButton = document.querySelector('#sendButton');
739
+ const copyUrlButton = document.querySelector('#copyUrlButton');
740
+ const openUrlButton = document.querySelector('#openUrlButton');
741
+ const clearButton = document.querySelector('#clearButton');
742
+ const abortButton = document.querySelector('#abortButton');
743
+ const copyResultButton = document.querySelector('#copyResultButton');
744
+ const statusBadge = document.querySelector('#statusBadge');
745
+ const timeBadge = document.querySelector('#timeBadge');
746
+ const sizeBadge = document.querySelector('#sizeBadge');
747
+ const historyList = document.querySelector('#historyList');
748
+ const endpoints = Array.from(document.querySelectorAll('.endpoint'));
749
+ const apiLinks = Array.from(document.querySelectorAll('a[href^="/"]'));
750
+ const initialResponseText = '响应会显示在这里';
751
+ let activeController = null;
752
+ let baseUrl = getInitialBaseUrl();
753
+ let lastResult = '';
754
+ let requestHistory = [];
755
+
756
+ baseUrlInput.value = baseUrl;
757
+
758
+ function updateApiLinks() {
759
+ apiLinks.forEach(link => {
760
+ const path = link.dataset.path || link.getAttribute('href');
761
+ link.dataset.path = path;
762
+ link.href = `${baseUrl}${normalizePath(path)}`;
763
+ });
764
+ }
765
+
766
+ function setBaseUrl(value) {
767
+ baseUrl = normalizeBaseUrl(value);
768
+ baseUrlInput.value = baseUrl;
769
+ baseUrlText.textContent = baseUrl;
770
+ updateApiLinks();
771
+ updateUrl();
772
+ }
773
+
774
+ function getRequestUrl() {
775
+ return buildRequestUrl({
776
+ baseUrl,
777
+ path: pathInput.value
778
+ });
779
+ }
780
+
781
+ function updateUrl(options = {}) {
782
+ const path = normalizePath(pathInput.value);
783
+
784
+ if (options.commit) {
785
+ pathInput.value = path;
786
+ }
787
+
788
+ requestUrl.textContent = `${baseUrl}${path}`;
789
+ highlightEndpoint(path);
790
+ return getRequestUrl();
791
+ }
792
+
793
+ function setStatus(text, type) {
794
+ statusBadge.textContent = text;
795
+ statusBadge.className = `badge${type ? ` ${type}` : ''}`;
796
+ }
797
+
798
+ function resetMetrics() {
799
+ setStatus('待发送');
800
+ timeBadge.textContent = '耗时 --';
801
+ sizeBadge.textContent = '大小 --';
802
+ }
803
+
804
+ function setLoading(isLoading) {
805
+ sendButton.disabled = isLoading;
806
+ abortButton.disabled = !isLoading;
807
+ methodInput.disabled = isLoading;
808
+ preset.disabled = isLoading;
809
+ sendButton.textContent = isLoading ? '请求中' : '发送';
810
+ }
811
+
812
+ async function writeClipboard(text, successLabel) {
813
+ if (!text) {
814
+ return;
815
+ }
816
+
817
+ try {
818
+ if (navigator.clipboard) {
819
+ await navigator.clipboard.writeText(text);
820
+ } else {
821
+ const textarea = document.createElement('textarea');
822
+ textarea.value = text;
823
+ textarea.setAttribute('readonly', '');
824
+ textarea.style.position = 'fixed';
825
+ textarea.style.opacity = '0';
826
+ document.body.appendChild(textarea);
827
+ textarea.select();
828
+ document.execCommand('copy');
829
+ textarea.remove();
830
+ }
831
+
832
+ setStatus(successLabel, 'success');
833
+ } catch (error) {
834
+ setStatus('复制失败', 'error');
835
+ }
836
+ }
837
+
838
+ function renderHistory() {
839
+ historyList.replaceChildren();
840
+
841
+ requestHistory.forEach(item => {
842
+ const button = document.createElement('button');
843
+ button.type = 'button';
844
+ button.title = `${item.method} ${item.path}`;
845
+ button.textContent = `${item.method} ${item.path}`;
846
+ button.addEventListener('click', () => {
847
+ methodInput.value = item.method;
848
+ pathInput.value = item.path;
849
+ updateUrl();
850
+ pathInput.focus();
851
+ });
852
+ historyList.appendChild(button);
853
+ });
854
+ }
855
+
856
+ function pushHistory(method, path) {
857
+ requestHistory = [
858
+ { method, path },
859
+ ...requestHistory.filter(item => item.method !== method || item.path !== path),
860
+ ].slice(0, 5);
861
+ renderHistory();
862
+ }
863
+
864
+ function highlightEndpoint(path) {
865
+ const pathOnly = path.split('#')[0];
866
+ endpoints.forEach(endpoint => {
867
+ const endpointPath = endpoint.dataset.path || endpoint.getAttribute('href');
868
+ endpoint.classList.toggle('active', normalizePath(endpointPath) === pathOnly);
869
+ });
870
+ }
871
+
872
+ async function sendRequest() {
873
+ const url = updateUrl({ commit: true });
874
+ const method = methodInput.value;
875
+ const path = normalizePath(pathInput.value);
876
+
877
+ if (activeController) {
878
+ activeController.abort();
879
+ }
880
+
881
+ activeController = new AbortController();
882
+ setLoading(true);
883
+ setStatus('请求中');
884
+ timeBadge.textContent = '耗时 --';
885
+ sizeBadge.textContent = '大小 --';
886
+ responseBox.textContent = '请求中...';
887
+
888
+ try {
889
+ const result = await fetchPlaygroundRequest({
890
+ url,
891
+ options: buildFetchOptions({
892
+ method,
893
+ signal: activeController.signal
894
+ })
895
+ });
896
+
897
+ lastResult = result.formattedText;
898
+ responseBox.textContent = result.formattedText;
899
+ setStatus(result.statusText, result.ok ? 'success' : 'error');
900
+ timeBadge.textContent = `耗时 ${result.elapsedMs}ms`;
901
+ sizeBadge.textContent = `大小 ${formatBytes(result.sizeBytes)}`;
902
+ pushHistory(method, path);
903
+ } catch (error) {
904
+ const isAbort = isAbortError(error);
905
+ lastResult = '';
906
+ responseBox.textContent = isAbort ? '请求已取消' : getErrorMessage(error);
907
+ setStatus(isAbort ? '已取消' : '请求失败', isAbort ? '' : 'error');
908
+ } finally {
909
+ activeController = null;
910
+ setLoading(false);
911
+ }
912
+ }
913
+
914
+ preset.addEventListener('change', () => {
915
+ pathInput.value = preset.value;
916
+ updateUrl();
917
+ });
918
+
919
+ baseUrlInput.addEventListener('change', () => setBaseUrl(baseUrlInput.value));
920
+ baseUrlInput.addEventListener('keydown', event => {
921
+ if (event.key === 'Enter') {
922
+ event.preventDefault();
923
+ setBaseUrl(baseUrlInput.value);
924
+ }
925
+ });
926
+
927
+ pathInput.addEventListener('input', updateUrl);
928
+ pathInput.addEventListener('keydown', event => {
929
+ if (event.key === 'Enter') {
930
+ event.preventDefault();
931
+ sendRequest();
932
+ }
933
+ });
934
+
935
+ endpoints.forEach(endpoint => {
936
+ endpoint.addEventListener('click', event => {
937
+ event.preventDefault();
938
+ methodInput.value = endpoint.dataset.method || 'GET';
939
+ pathInput.value = endpoint.dataset.path || endpoint.getAttribute('href') || '/';
940
+ updateUrl({ commit: true });
941
+ document.querySelector('.tester').scrollIntoView({ behavior: 'smooth', block: 'start' });
942
+ pathInput.focus();
943
+ });
944
+ });
945
+
946
+ sendButton.addEventListener('click', sendRequest);
947
+ copyUrlButton.addEventListener('click', () => writeClipboard(getRequestUrl(), 'URL 已复制'));
948
+ copyResultButton.addEventListener('click', () => writeClipboard(lastResult || responseBox.textContent, '结果已复制'));
949
+ openUrlButton.addEventListener('click', () => window.open(getRequestUrl(), '_blank', 'noopener,noreferrer'));
950
+ clearButton.addEventListener('click', () => {
951
+ lastResult = '';
952
+ responseBox.textContent = initialResponseText;
953
+ resetMetrics();
954
+ });
955
+ abortButton.addEventListener('click', () => {
956
+ if (activeController) {
957
+ activeController.abort();
958
+ }
959
+ });
960
+
961
+ resetMetrics();
962
+ setBaseUrl(baseUrl);
963
+ updateUrl({ commit: true });
964
+ </script>
965
+ </body>
966
+ </html>