mikuyyds commited on
Commit
c3a3aa0
·
verified ·
1 Parent(s): 6d63722

Create script.js

Browse files
Files changed (1) hide show
  1. script.js +610 -0
script.js ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 缓存图床 Logo
2
+ const imageCache = new Map();
3
+ function preloadImages(urls) {
4
+ urls.forEach(url => {
5
+ if (!imageCache.has(url)) {
6
+ const img = new Image();
7
+ img.src = url;
8
+ img.onload = () => imageCache.set(url, img);
9
+ img.onerror = () => console.warn(`Logo 加载失败: ${url}`);
10
+ }
11
+ });
12
+ }
13
+
14
+ // 调试信息显示
15
+ let debugTimer;
16
+ function showDebugInfo(message) {
17
+ const debugEl = document.getElementById('debugInfo');
18
+ debugEl.textContent = message;
19
+ clearTimeout(debugTimer);
20
+ debugTimer = setTimeout(() => debugEl.textContent = '', 5000);
21
+ }
22
+
23
+ // 实时时钟
24
+ function updateTime() {
25
+ const now = new Date();
26
+ document.getElementById('currentTime').textContent =
27
+ `${now.getHours().toString().padStart(2, '0')}:` +
28
+ `${now.getMinutes().toString().padStart(2, '0')}:` +
29
+ `${now.getSeconds().toString().padStart(2, '0')}`;
30
+ }
31
+ setInterval(updateTime, 1000);
32
+ updateTime();
33
+
34
+ // 获取天气
35
+ async function getWeather() {
36
+ const cacheKey = 'weather_data';
37
+ const cacheTime = 3600 * 1000;
38
+ const cached = localStorage.getItem(cacheKey);
39
+ if (cached) {
40
+ const { data, timestamp } = JSON.parse(cached);
41
+ if (Date.now() - timestamp < cacheTime) {
42
+ renderWeather(data);
43
+ return;
44
+ }
45
+ }
46
+ try {
47
+ const response = await retryFetch('https://api.kxzjoker.cn/api/Weather');
48
+ const data = await response.json();
49
+ if (data.code !== 200) throw new Error('天气获取失败');
50
+ localStorage.setItem(cacheKey, JSON.stringify({ data, timestamp: Date.now() }));
51
+ renderWeather(data);
52
+ } catch (error) {
53
+ renderWeather(null);
54
+ showDebugInfo('欢迎访问本服务');
55
+ }
56
+ }
57
+
58
+ function renderWeather(data) {
59
+ const widget = document.getElementById('weatherWidget');
60
+ widget.innerHTML = data ? `🌤️ ${data.data.tianqi.temperature}°C` : `🌤️ 未知天气`;
61
+ if (data) showDebugInfo(`👏欢迎来自${data.data.ipdata.info.split('-')[0]}的用户`);
62
+ }
63
+
64
+ // 随机文案
65
+ async function getRandomQuote() {
66
+ const cacheKey = 'random_quote';
67
+ const cacheTime = 3600 * 1000;
68
+ const cached = localStorage.getItem(cacheKey);
69
+ if (cached) {
70
+ const { text, timestamp } = JSON.parse(cached);
71
+ if (Date.now() - timestamp < cacheTime) {
72
+ document.getElementById('random-quote').textContent = text;
73
+ return;
74
+ }
75
+ }
76
+ try {
77
+ const response = await fetch('https://api.suyanw.cn/api/weimei.php?type=json');
78
+ const data = await response.json();
79
+ const text = data.text || '生活就像魔法,总有惊喜等着你!';
80
+ localStorage.setItem(cacheKey, JSON.stringify({ text, timestamp: Date.now() }));
81
+ document.getElementById('random-quote').textContent = text;
82
+ } catch (error) {
83
+ document.getElementById('random-quote').textContent = '生活就像魔法,总有惊喜等着你!';
84
+ }
85
+ }
86
+ setInterval(getRandomQuote, 30000);
87
+ getRandomQuote();
88
+
89
+ // 平台数据
90
+ const platforms = [
91
+ { name: '抖音', url: 'https://www.douyin.com', logo: 'https://img.zlee.de/file/logo图片/1747377427291_dy.png' },
92
+ { name: '快手', url: 'https://www.kuaishou.com', logo: 'https://img.zlee.de/file/logo图片/1747377484677_OIP-C.png' },
93
+ { name: '火山视频', url: 'https://www.huoshan.com', logo: 'https://img.zlee.de/file/logo图片/1747377480732_hssp.png' },
94
+ { name: '西瓜视频', url: 'https://www.ixigua.com', logo: 'https://img.zlee.de/file/logo图片/1747377479737_xgsp-black.png' },
95
+ { name: '皮皮虾', url: 'https://www.pipix.com', logo: 'https://img.zlee.de/file/logo图片/1747377485180_ppx.png' },
96
+ { name: '秒拍', url: 'https://www.miaopai.com', logo: 'https://img.zlee.de/file/logo图片/1747377885467_mp (1).png' },
97
+ { name: '头条视频', url: 'https://www.toutiao.com', logo: 'https://img.zlee.de/file/logo图片/1747377883945_ttsp.png' },
98
+ { name: '腾讯微视', url: 'https://weishi.qq.com', logo: 'https://img.zlee.de/file/logo图片/1747377891436_腾讯微视.png' },
99
+ { name: '美图秀秀', url: 'https://www.meitu.com', logo: 'https://img.zlee.de/file/logo图片/1747377886033_mtxx.png' },
100
+ { name: '美拍', url: 'https://www.meipai.com', logo: 'https://img.zlee.de/file/logo图片/1747377887635_mp.png' },
101
+ { name: '微博', url: 'https://weibo.com', logo: 'https://img.zlee.de/file/logo图片/1747377888691_wb.png' },
102
+ { name: '小红书', url: 'https://www.xiaohongshu.com', logo: 'https://img.zlee.de/file/logo图片/1747377889452_xhs.png' },
103
+ { name: '网易云', url: 'https://music.163.com', logo: 'https://img.zlee.de/file/logo图片/1747377894287_wyy.png' }
104
+ ];
105
+
106
+ // 渲染平台列表
107
+ function renderPlatforms() {
108
+ const list = document.querySelector('.platform-list');
109
+ list.innerHTML = platforms.map(p => `
110
+ <a href="${p.url}" target="_blank" rel="noopener noreferrer" class="platform-item" aria-label="${p.name}" role="listitem" title="访问${p.name}">
111
+ <img src="${p.logo}" alt="${p.name}图标" class="platform-icon" loading="lazy" onerror="this.src='assets/icons/fallback.png'">
112
+ ${p.name}
113
+ <span class="platform-tooltip">${p.name}</span>
114
+ <span id="platform-desc-${p.name.replace(/\s/g, '-')}" class="sr-only">${p.name}官方网站</span>
115
+ </a>
116
+ `).join('');
117
+ validatePlatforms();
118
+ // 添加键盘导航和 Logo 预览事件
119
+ document.querySelectorAll('.platform-item').forEach(item => {
120
+ const img = item.querySelector('.platform-icon');
121
+ img.addEventListener('click', e => {
122
+ e.preventDefault();
123
+ showFullImage(img.src);
124
+ });
125
+ });
126
+ }
127
+
128
+ // 校验平台链接和图标
129
+ function validatePlatforms() {
130
+ const items = document.querySelectorAll('.platform-item');
131
+ items.forEach((item, index) => {
132
+ const expected = platforms[index];
133
+ if (item.href !== expected.url || item.querySelector('img').src !== expected.logo) {
134
+ console.warn(`平台错误: ${expected.name} (URL: ${item.href}, Logo: ${item.querySelector('img').src})`);
135
+ showDebugInfo(`警告: ${expected.name} 配置可能错误`);
136
+ }
137
+ });
138
+ }
139
+
140
+ // URL提取
141
+ function extractURL(text) {
142
+ try {
143
+ const urlRegex = /(https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*))/g;
144
+ const matches = text.match(urlRegex);
145
+ return matches ? matches[0] : null;
146
+ } catch (e) {
147
+ console.error('URL提取错误:', e);
148
+ return null;
149
+ }
150
+ }
151
+
152
+ // 剪贴板自动识别
153
+ async function checkClipboard() {
154
+ try {
155
+ if (!navigator.clipboard) return;
156
+ const text = await navigator.clipboard.readText();
157
+ const url = extractURL(text);
158
+ if (url) {
159
+ const input = document.getElementById('urlInput');
160
+ if (!input.value) {
161
+ input.value = url;
162
+ showAlert('📋 已自动填充剪贴板链接!');
163
+ }
164
+ }
165
+ } catch (error) {
166
+ console.warn('剪贴板读取失败:', error);
167
+ }
168
+ }
169
+
170
+ // 解析内容
171
+ let isParsing = false;
172
+ async function parseContent() {
173
+ if (isParsing) return;
174
+ isParsing = true;
175
+ const parseBtn = document.querySelector('.parse-btn');
176
+ parseBtn.disabled = true;
177
+ parseBtn.classList.add('parsing');
178
+ parseBtn.setAttribute('aria-busy', 'true');
179
+ showDebugInfo('开始解析流程...');
180
+ try {
181
+ const input = document.getElementById('urlInput');
182
+ if (!input) throw new Error('找不到输入框元素');
183
+ const url = extractURL(input.value);
184
+ showDebugInfo(`提取到URL: ${url || '无'}`);
185
+ if (!url) {
186
+ showAlert('🚨 请输入有效的链接哦~ (´•̥ ̯ •̥`)');
187
+ return;
188
+ }
189
+ toggleLoading(true);
190
+ showDebugInfo(`正在解析中: ${url}`);
191
+ let data, response;
192
+ try {
193
+ const apiUrl1 = `https://api.kxzjoker.cn/api/jiexi_video?url=${encodeURIComponent(url)}`;
194
+ response = await retryFetch(apiUrl1);
195
+ data = await response.json();
196
+ } catch (error) {
197
+ showDebugInfo('主接口失败,尝试备用接口...');
198
+ const apiUrl2 = `https://api.kxzjoker.cn/api/jiexi_video_2?url=${encodeURIComponent(url)}`;
199
+ response = await retryFetch(apiUrl2);
200
+ data = await response.json();
201
+ }
202
+ showDebugInfo(`收到响应状态: ${response.status}`);
203
+ if (!response.ok) {
204
+ throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
205
+ }
206
+ if (!data || (data.success !== 200 && data.success !== true)) {
207
+ throw new Error('API返回数据格式异常');
208
+ }
209
+ renderContent(data.data);
210
+ saveHistory(url, data.data.title || '未命名');
211
+ input.value = '';
212
+ showDebugInfo('内容渲染完成');
213
+ } catch (error) {
214
+ console.error('解析流程错误:', error);
215
+ showDebugInfo(`错误: ${error.message}`);
216
+ const message = error.message.includes('API') ? '❌ 解析失败,服务器可能繁忙' :
217
+ error.message.includes('URL') ? '❌ 无效的链接格式' : '❌ 发生错误,请重试';
218
+ showAlert(message);
219
+ } finally {
220
+ toggleLoading(false);
221
+ parseBtn.disabled = false;
222
+ parseBtn.classList.remove('parsing');
223
+ parseBtn.setAttribute('aria-busy', 'false');
224
+ isParsing = false;
225
+ }
226
+ }
227
+
228
+ // 渲染内容
229
+ function renderContent(data) {
230
+ const contentBox = document.getElementById('contentBox');
231
+ if (!contentBox) throw new Error('找不到内容容器');
232
+ contentBox.innerHTML = '';
233
+ try {
234
+ if (data.images) {
235
+ renderImages(contentBox, data);
236
+ } else if (data.video_url) {
237
+ renderVideo(contentBox, data);
238
+ }
239
+ contentBox.style.opacity = 1;
240
+ } catch (e) {
241
+ console.error('渲染错误:', e);
242
+ showAlert('内容渲染失败,请检查数据格式');
243
+ }
244
+ }
245
+
246
+ // 渲染图片图集
247
+ function renderImages(contentBox, data) {
248
+ const galleryHTML = data.images.map((img, index) => `
249
+ <div class="gallery-item" onclick="showFullImage('${img}')" role="button" aria-label="查看图片 ${index + 1}">
250
+ <img src="${img}" alt="图集 ${index + 1}" loading="lazy" style="border-radius: 10px; width: 100%; aspect-ratio: 1/1; object-fit: cover; cursor: zoom-in;">
251
+ <div class="image-index">${index + 1}</div>
252
+ </div>
253
+ `).join('');
254
+ contentBox.innerHTML = `
255
+ <div class="media-card">
256
+ <h2 style="color: #9370DB; margin-bottom: 15px;">
257
+ 🖼️ ${data.title || '未命名图集'}
258
+ </h2>
259
+ <progress class="progress-bar" value="0" max="100" aria-label="下载进度"></progress>
260
+ <div class="download-info" aria-live="polite"></div>
261
+ <div style="margin-top: 15px; text-align: center;">
262
+ <a href="#" class="download-all-btn" aria-label="打包下载图片">
263
+ 📦 打包保存
264
+ </a>
265
+ </div>
266
+ <br>
267
+ <div style="display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));">
268
+ ${galleryHTML}
269
+ </div>
270
+ </div>
271
+ `;
272
+ const downloadBtn = contentBox.querySelector('.download-all-btn');
273
+ const progressBar = contentBox.querySelector('.progress-bar');
274
+ const downloadInfo = contentBox.querySelector('.download-info');
275
+ downloadBtn.addEventListener('click', async (e) => {
276
+ e.preventDefault();
277
+ try {
278
+ downloadBtn.innerHTML = `<i class="spinner-icon">⏳</i> 准备中...`;
279
+ downloadBtn.disabled = true;
280
+ const zip = new JSZip();
281
+ const imgFolder = zip.folder("images");
282
+ const maxConcurrent = 4;
283
+ let completed = 0;
284
+ let totalBytes = 0;
285
+ let startTime = Date.now();
286
+ const downloadPromises = [];
287
+ for (let i = 0; i < data.images.length; i++) {
288
+ downloadPromises.push(async () => {
289
+ try {
290
+ const imgUrl = data.images[i];
291
+ const response = await fetch(imgUrl);
292
+ if (!response.ok) throw new Error(`图片${i+1}下载失败`);
293
+ const blob = await response.blob();
294
+ totalBytes += blob.size;
295
+ imgFolder.file(`image_${i+1}.${getFileExtension(imgUrl)}`, blob);
296
+ } catch (error) {
297
+ console.warn(`图片${i+1}下载失败:`, error);
298
+ } finally {
299
+ completed++;
300
+ const progress = Math.round((completed / data.images.length) * 100);
301
+ progressBar.value = progress;
302
+ const elapsed = (Date.now() - startTime) / 1000;
303
+ const speed = elapsed > 0 ? (totalBytes / elapsed / 1024).toFixed(2) : 0;
304
+ downloadInfo.textContent = `进度: ${progress}% 速度: ${speed} KB/s`;
305
+ }
306
+ });
307
+ }
308
+ await Promise.all(downloadPromises.map((fn, idx) => idx < maxConcurrent ? fn() : downloadPromises[idx - maxConcurrent].then(fn)));
309
+ const zipBlob = await zip.generateAsync({
310
+ type: "blob",
311
+ compression: "DEFLATE",
312
+ compressionOptions: { level: 6 }
313
+ });
314
+ const cleanTitle = data.title ? data.title.replace(/[<>:"/\\|?*]/g, '') : '未命名图集';
315
+ saveAs(zipBlob, `${cleanTitle}.zip`);
316
+ showAlert('✅ 图片打包下载完成!');
317
+ } catch (error) {
318
+ console.error('打包下载失败:', error);
319
+ showAlert('❌ 打包下载失败,请重试');
320
+ } finally {
321
+ downloadBtn.innerHTML = `📦 打包下载`;
322
+ downloadBtn.disabled = false;
323
+ progressBar.value = 0;
324
+ downloadInfo.textContent = '';
325
+ }
326
+ });
327
+ }
328
+
329
+ // 渲染视频
330
+ function renderVideo(contentBox, data) {
331
+ const cover = data.cover_url || 'https://via.placeholder.com/640x360?text=视频封面';
332
+ contentBox.innerHTML = `
333
+ <div class="media-card">
334
+ <h2 style="color: #9370DB; margin-bottom: 15px;">
335
+ 🎥 ${data.video_title || '未命名视频'}
336
+ </h2>
337
+ <div class="video-container">
338
+ <img src="${cover}" alt="视频封面" class="video-cover" loading="lazy">
339
+ <video controls preload="metadata">
340
+ <source src="${data.video_url}" type="video/mp4">
341
+ 您的浏览器不支持视频播放
342
+ </video>
343
+ </div>
344
+ <progress class="progress-bar" value="0" max="100" aria-label="下载进度"></progress>
345
+ <div class="download-info" aria-live="polite"></div>
346
+ <div style="margin-top: 15px; text-align: center;">
347
+ <a href="#" class="download-btn" aria-label="下载视频">
348
+ ⬇ 保存视频
349
+ </a>
350
+ </div>
351
+ </div>
352
+ `;
353
+ const downloadBtn = contentBox.querySelector('.download-btn');
354
+ const progressBar = contentBox.querySelector('.progress-bar');
355
+ const downloadInfo = contentBox.querySelector('.download-info');
356
+ downloadBtn.addEventListener('click', async (e) => {
357
+ e.preventDefault();
358
+ try {
359
+ downloadBtn.innerHTML = `<i class="spinner-icon">⏳</i> 下载中...`;
360
+ downloadBtn.disabled = true;
361
+ const startTime = Date.now();
362
+ let downloadedBytes = 0;
363
+ const response = await fetch(data.download_url);
364
+ if (!response.ok) throw new Error(`HTTP 错误!状态码: ${response.status}`);
365
+ const contentLength = response.headers.get('content-length');
366
+ const totalBytes = contentLength ? parseInt(contentLength) : 0;
367
+ const reader = response.body.getReader();
368
+ const chunks = [];
369
+ while (true) {
370
+ const { done, value } = await reader.read();
371
+ if (done) break;
372
+ chunks.push(value);
373
+ downloadedBytes += value.length;
374
+ if (totalBytes) {
375
+ const progress = Math.round((downloadedBytes / totalBytes) * 100);
376
+ progressBar.value = progress;
377
+ const elapsed = (Date.now() - startTime) / 1000;
378
+ const speed = elapsed > 0 ? (downloadedBytes / elapsed / 1024).toFixed(2) : 0;
379
+ downloadInfo.textContent = `进度: ${progress}% 速度: ${speed} KB/s`;
380
+ }
381
+ }
382
+ const blob = new Blob(chunks);
383
+ const fileName = `${data.video_title || '未命名视频'}.mp4`;
384
+ saveAs(blob, fileName);
385
+ showAlert('✅ 视频下载完成!');
386
+ } catch (error) {
387
+ console.error('下载失败:', error);
388
+ showAlert('❌ 视频下载失败,请重试');
389
+ } finally {
390
+ downloadBtn.innerHTML = `⬇ 保存视频`;
391
+ downloadBtn.disabled = false;
392
+ progressBar.value = 0;
393
+ downloadInfo.textContent = '';
394
+ }
395
+ });
396
+ }
397
+
398
+ // 显示完整图片
399
+ function showFullImage(url) {
400
+ const overlay = document.createElement('div');
401
+ overlay.style.cssText = `
402
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
403
+ background: rgba(0,0,0,0.8); display: flex; justify-content: center;
404
+ align-items: center; z-index: 9999; cursor: zoom-out;
405
+ `;
406
+ const img = document.createElement('img');
407
+ img.src = url;
408
+ img.style.cssText = 'max-width: 90%; max-height: 90%; border-radius: 10px;';
409
+ overlay.onclick = () => overlay.remove();
410
+ overlay.appendChild(img);
411
+ document.body.appendChild(overlay);
412
+ }
413
+
414
+ // 工具函数
415
+ function toggleLoading(show) {
416
+ document.getElementById('loading').style.display = show ? 'block' : 'none';
417
+ }
418
+
419
+ function showAlert(message) {
420
+ const alert = document.createElement('div');
421
+ alert.style.cssText = `
422
+ position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%);
423
+ background: rgba(255, 255, 255, 0.95); padding: 15px 30px;
424
+ border-radius: 30px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
425
+ color: #6A5ACD; z-index: 10000;
426
+ `;
427
+ alert.innerHTML = message;
428
+ document.body.appendChild(alert);
429
+ setTimeout(() => alert.remove(), 3000);
430
+ }
431
+
432
+ function getFileExtension(url) {
433
+ try {
434
+ const ext = url.split('.').pop().split(/[#?]/)[0].toLowerCase();
435
+ const validExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp'];
436
+ return validExtensions.includes(ext) ? ext : 'jpg';
437
+ } catch (e) {
438
+ return 'jpg';
439
+ }
440
+ }
441
+
442
+ async function retryFetch(url, options = {}, retries = 3) {
443
+ const controller = new AbortController();
444
+ const timeout = setTimeout(() => controller.abort(), 10000);
445
+ try {
446
+ const response = await fetch(url, { ...options, signal: controller.signal });
447
+ clearTimeout(timeout);
448
+ return response;
449
+ } catch (error) {
450
+ clearTimeout(timeout);
451
+ if (retries > 1) {
452
+ showDebugInfo(`请求失败,重试中... (${retries - 1})`);
453
+ return retryFetch(url, options, retries - 1);
454
+ }
455
+ throw error;
456
+ }
457
+ }
458
+
459
+ // 历史记录
460
+ function saveHistory(url, title) {
461
+ const history = JSON.parse(localStorage.getItem('parse_history') || '[]');
462
+ if (!history.some(item => item.url === url)) {
463
+ history.unshift({ url, title });
464
+ if (history.length > 5) history.pop();
465
+ localStorage.setItem('parse_history', JSON.stringify(history));
466
+ }
467
+ renderHistory();
468
+ }
469
+
470
+ function renderHistory() {
471
+ const historyList = document.getElementById('history-list');
472
+ const history = JSON.parse(localStorage.getItem('parse_history') || '[]');
473
+ historyList.innerHTML = history.map(item => `
474
+ <div class="history-item" role="button" aria-label="重新解析 ${item.title}" onclick="reuseHistory('${item.url}')">
475
+ <span>${item.title.slice(0, 30)}${item.title.length > 30 ? '...' : ''}</span>
476
+ <button class="delete-history" aria-label="删除记录" onclick="deleteHistory('${item.url}', event)">×</button>
477
+ </div>
478
+ `).join('');
479
+ }
480
+
481
+ function deleteHistory(url, event) {
482
+ event.stopPropagation();
483
+ let history = JSON.parse(localStorage.getItem('parse_history') || '[]');
484
+ history = history.filter(item => item.url !== url);
485
+ localStorage.setItem('parse_history', JSON.stringify(history));
486
+ renderHistory();
487
+ }
488
+
489
+ function reuseHistory(url) {
490
+ document.getElementById('urlInput').value = url;
491
+ parseContent();
492
+ }
493
+
494
+ // 主题切换
495
+ function initTheme() {
496
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
497
+ const theme = localStorage.getItem('theme') || (prefersDark ? 'dark' : 'light');
498
+ document.documentElement.dataset.theme = theme;
499
+ updateThemeIcon(theme);
500
+ }
501
+
502
+ function toggleTheme() {
503
+ const current = document.documentElement.dataset.theme;
504
+ const newTheme = current === 'dark' ? 'light' : 'dark';
505
+ document.documentElement.dataset.theme = newTheme;
506
+ localStorage.setItem('theme', newTheme);
507
+ updateThemeIcon(newTheme);
508
+ }
509
+
510
+ function updateThemeIcon(theme) {
511
+ const icon = document.querySelector('#theme-toggle i');
512
+ icon.textContent = theme === 'dark' ? '☀️' : '🌙';
513
+ }
514
+
515
+ // 自定义主题色
516
+ function applyThemeColor(color) {
517
+ const colors = {
518
+ pink: { main: '#FFA1C9', accent: '#9370DB' },
519
+ blue: { main: '#87CEEB', accent: '#4682B4' },
520
+ green: { main: '#98FB98', accent: '#228B22' }
521
+ };
522
+ const selected = colors[color] || colors.pink;
523
+ document.documentElement.style.setProperty('--main-pink', selected.main);
524
+ document.documentElement.style.setProperty('--deep-purple', selected.accent);
525
+ localStorage.setItem('theme_color', color);
526
+ }
527
+
528
+ // 分享功能
529
+ function initShare() {
530
+ document.getElementById('copy-link').addEventListener('click', async () => {
531
+ try {
532
+ await navigator.clipboard.writeText(window.location.href);
533
+ showAlert('✅ 链接已复制到剪贴板!');
534
+ } catch (error) {
535
+ showAlert('❌ 复制失败,请手动复制!');
536
+ }
537
+ });
538
+ document.querySelectorAll('.share-btn:not(#copy-link)').forEach(btn => {
539
+ btn.addEventListener('click', () => showAlert('分享功能开发中,请复制链接分享!'));
540
+ });
541
+ }
542
+
543
+ // 用户反馈
544
+ function initFeedback() {
545
+ const btn = document.getElementById('feedback-btn');
546
+ const form = document.getElementById('feedback-form');
547
+ const closeBtn = document.getElementById('close-feedback');
548
+ btn.addEventListener('click', () => {
549
+ form.style.display = 'block';
550
+ });
551
+ closeBtn.addEventListener('click', () => {
552
+ form.style.display = 'none';
553
+ });
554
+ form.addEventListener('submit', (e) => {
555
+ e.preventDefault();
556
+ showAlert('✅ 反馈已提交,感谢您的支持!');
557
+ form.style.display = 'none';
558
+ form.reset();
559
+ });
560
+ }
561
+
562
+ // 访客统计
563
+ function initVisitorCount() {
564
+ let count = parseInt(localStorage.getItem('visitor_count') || '0');
565
+ count++;
566
+ localStorage.setItem('visitor_count', count);
567
+ document.getElementById('visitor-count').textContent = `访客数:${count}`;
568
+ }
569
+
570
+ // 返回顶部
571
+ function initBackToTop() {
572
+ const btn = document.querySelector('.back-to-top');
573
+ window.addEventListener('scroll', () => {
574
+ btn.style.display = window.scrollY > 300 ? 'block' : 'none';
575
+ });
576
+ btn.addEventListener('click', () => {
577
+ window.scrollTo({ top: 0, behavior: 'smooth' });
578
+ });
579
+ }
580
+
581
+ // 事件绑定
582
+ document.querySelector('.parse-btn').addEventListener('click', parseContent);
583
+ document.getElementById('urlInput').addEventListener('keypress', (e) => {
584
+ if (e.key === 'Enter' || (e.ctrlKey && e.key === 'Enter')) parseContent();
585
+ });
586
+ document.getElementById('urlInput').addEventListener('focus', checkClipboard);
587
+ document.querySelector('#theme-toggle').addEventListener('click', toggleTheme);
588
+
589
+ // 初始化
590
+ document.addEventListener('DOMContentLoaded', () => {
591
+ // 预加载图床 Logo
592
+ preloadImages(platforms.map(p => p.logo));
593
+ renderPlatforms();
594
+ initTheme();
595
+ renderHistory();
596
+ getWeather();
597
+ initShare();
598
+ initFeedback();
599
+ initVisitorCount();
600
+ initBackToTop();
601
+ applyThemeColor(localStorage.getItem('theme_color') || 'pink');
602
+ if (!window.saveAs) {
603
+ console.warn('FileSaver.js 未加载!下载功能将不可用');
604
+ showDebugInfo('警告:文件保存功能需要 FileSaver.js 支持');
605
+ }
606
+ if (!window.JSZip) {
607
+ console.warn('JSZip ��加载!打包下载功能不可用');
608
+ showDebugInfo('警告:打包下载需要 JSZip 库支持');
609
+ }
610
+ });