Skip to content

Commit af7d526

Browse files
author
Taois
committed
add: 优化下载功能
1 parent d88ef78 commit af7d526

File tree

1 file changed

+87
-123
lines changed

1 file changed

+87
-123
lines changed

controllers/web.js

Lines changed: 87 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {readFileSync, existsSync, readdirSync, statSync} from 'fs';
1+
import {readFileSync, existsSync, readdirSync, statSync, unlinkSync} from 'fs';
22
import {createReadStream} from 'fs';
33
import {execSync} from 'child_process';
44
import path from 'path';
@@ -53,7 +53,7 @@ const findLatestPackage = (projectDir, packageName) => {
5353
.map(file => {
5454
const filePath = path.join(parentDir, file);
5555
const stats = statSync(filePath);
56-
return {file, filePath, mtime: stats.mtime};
56+
return {file, filePath, mtime: stats.mtime, size: stats.size};
5757
})
5858
.sort((a, b) => b.mtime - a.mtime);
5959

@@ -170,6 +170,16 @@ export default (fastify, options, done) => {
170170
}
171171

172172
const projectName = path.basename(projectRootDir);
173+
const templatePath = path.join(projectRootDir, 'public', 'download.html');
174+
175+
if (!existsSync(templatePath)) {
176+
return reply.code(500).send({
177+
success: false,
178+
message: '下载页面模板不存在',
179+
});
180+
}
181+
182+
let html = readFileSync(templatePath, 'utf-8');
173183

174184
const files = [
175185
{name: `${projectName}.7z`, desc: '7z 压缩包(标准版)'},
@@ -178,134 +188,40 @@ export default (fastify, options, done) => {
178188
{name: `${projectName}-green.zip`, desc: 'ZIP 压缩包(绿色版,不含[密]文件)'}
179189
];
180190

181-
const html = `
182-
<!DOCTYPE html>
183-
<html lang="zh-CN">
184-
<head>
185-
<meta charset="UTF-8">
186-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
187-
<title>下载 ${projectName}</title>
188-
<style>
189-
body {
190-
font-family: Arial, sans-serif;
191-
max-width: 800px;
192-
margin: 50px auto;
193-
padding: 20px;
194-
background-color: #f5f5f5;
195-
}
196-
h1 {
197-
text-align: center;
198-
color: #333;
199-
}
200-
.download-list {
201-
background-color: white;
202-
border-radius: 8px;
203-
padding: 20px;
204-
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
205-
}
206-
.download-item {
207-
margin: 10px 0;
208-
padding: 15px;
209-
background-color: #f8f9fa;
210-
border-radius: 4px;
211-
display: flex;
212-
justify-content: space-between;
213-
align-items: center;
214-
}
215-
.download-info {
216-
flex: 1;
217-
}
218-
.download-actions {
219-
display: flex;
220-
gap: 10px;
221-
align-items: center;
222-
}
223-
.download-item a {
224-
text-decoration: none;
225-
font-weight: bold;
226-
padding: 8px 16px;
227-
background-color: #007bff;
228-
color: white;
229-
border-radius: 4px;
230-
transition: background-color 0.3s;
231-
border: none;
232-
cursor: pointer;
233-
}
234-
.download-item a:hover {
235-
background-color: #0056b3;
236-
}
237-
.copy-btn {
238-
text-decoration: none;
239-
font-weight: bold;
240-
padding: 8px 16px;
241-
background-color: #6c757d;
242-
color: white;
243-
border-radius: 4px;
244-
transition: background-color 0.3s;
245-
border: none;
246-
cursor: pointer;
247-
}
248-
.copy-btn:hover {
249-
background-color: #5a6268;
250-
}
251-
.file-type {
252-
color: #666;
253-
font-size: 14px;
254-
}
255-
.toast {
256-
position: fixed;
257-
top: 20px;
258-
right: 20px;
259-
background-color: #28a745;
260-
color: white;
261-
padding: 10px 20px;
262-
border-radius: 4px;
263-
display: none;
264-
z-index: 1000;
265-
}
266-
</style>
267-
</head>
268-
<body>
269-
<h1>${projectName} 下载中心</h1>
270-
<div class="toast" id="toast">链接已复制到剪贴板</div>
271-
<div class="download-list">
272-
${files.map(file => {
191+
const formatFileSize = (bytes) => {
192+
if (!bytes || bytes === 0) return '未打包';
193+
const mb = bytes / (1024 * 1024);
194+
return mb.toFixed(2) + ' MB';
195+
};
196+
197+
const downloadItems = files.map(file => {
198+
const latestPackage = findLatestPackage(projectRootDir, file.name);
199+
const fileSize = latestPackage ? formatFileSize(latestPackage.size) : '未打包';
200+
const sizeClass = latestPackage ? '' : ' not-packed';
273201
const token = generateDownloadToken(file.name);
274202
const downloadUrl = `/admin/download/${file.name}?auth=${token}`;
275-
return `
276-
<div class="download-item">
277-
<div class="download-info">
278-
<strong>${file.name}</strong>
279-
<div class="file-type">${file.desc}</div>
280-
</div>
281-
<div class="download-actions">
282-
<a href="${downloadUrl}">下载</a>
283-
<button class="copy-btn" onclick="copyLink('${downloadUrl}')">复制链接</button>
284-
</div>
285-
</div>`;
286-
}).join('')}
287-
</div>
288-
<script>
289-
function copyLink(url) {
290-
const fullUrl = window.location.origin + url;
291-
navigator.clipboard.writeText(fullUrl).then(() => {
292-
const toast = document.getElementById('toast');
293-
toast.style.display = 'block';
294-
setTimeout(() => {
295-
toast.style.display = 'none';
296-
}, 2000);
297-
});
298-
}
299-
</script>
300-
</body>
301-
</html>`;
203+
return '<div class="download-item">' +
204+
'<div class="download-info">' +
205+
'<strong>' + file.name + '</strong>' +
206+
'<div class="file-type">' + file.desc + '</div>' +
207+
'</div>' +
208+
'<div class="download-size' + sizeClass + '">' + fileSize + '</div>' +
209+
'<div class="download-actions">' +
210+
'<a href="' + downloadUrl + '">下载</a>' +
211+
'<button class="copy-btn" onclick="copyLink(\'' + downloadUrl + '\')">复制链接</button>' +
212+
'</div>' +
213+
'</div>';
214+
}).join('');
215+
216+
html = html.replace(/\{\{projectName\}\}/g, projectName);
217+
html = html.replace(/\{\{downloadItems\}\}/g, downloadItems);
302218

303219
reply.type('text/html').send(html);
304220
} catch (error) {
305-
console.error('下载页面加载失败:', error.message);
221+
console.error('获取下载页面失败:', error.message);
306222
return reply.code(500).send({
307223
success: false,
308-
message: '加载下载页面失败',
224+
message: '获取下载页面失败',
309225
error: error.message,
310226
});
311227
}
@@ -387,5 +303,53 @@ export default (fastify, options, done) => {
387303
}
388304
});
389305

306+
fastify.post('/admin/download/clear', {
307+
preHandler: validateBasicAuth
308+
}, async (request, reply) => {
309+
try {
310+
if (IS_VERCEL) {
311+
return reply.code(403).send({
312+
success: false,
313+
message: 'Vercel 环境不支持文件操作',
314+
});
315+
}
316+
317+
const parentDir = path.dirname(projectRootDir);
318+
const projectName = path.basename(projectRootDir);
319+
const files = readdirSync(parentDir);
320+
const pattern = new RegExp(`^${projectName.replace(/\./g, '\\.')}-\\d{8}(-green)?\\.(7z|zip)$`);
321+
322+
let deletedCount = 0;
323+
const deletedFiles = [];
324+
325+
for (const file of files) {
326+
if (pattern.test(file)) {
327+
const filePath = path.join(parentDir, file);
328+
try {
329+
unlinkSync(filePath);
330+
deletedFiles.push(file);
331+
deletedCount++;
332+
} catch (error) {
333+
console.error(`删除文件失败: ${file}`, error.message);
334+
}
335+
}
336+
}
337+
338+
return reply.send({
339+
success: true,
340+
count: deletedCount,
341+
deletedFiles,
342+
message: `已清除 ${deletedCount} 个历史文件`
343+
});
344+
} catch (error) {
345+
console.error('清除历史文件失败:', error.message);
346+
return reply.code(500).send({
347+
success: false,
348+
message: '清除历史文件失败',
349+
error: error.message,
350+
});
351+
}
352+
});
353+
390354
done();
391355
};

0 commit comments

Comments
 (0)