Skip to content

Commit 47eb872

Browse files
committed
feat: 添加文件上传进度条功能,优化用户上传体验;更新相关样式和结构
1 parent 9a59678 commit 47eb872

4 files changed

Lines changed: 150 additions & 3 deletions

File tree

public/assets/admin/upload-preview.js

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ export function initUploadPreview() {
169169
}
170170

171171
if (uploadForm && fileInput) {
172+
const uploadProgress = $('#uploadProgress');
173+
const progressBar = $('#progressBar');
174+
const progressPercent = $('#progressPercent');
175+
const progressBytes = $('#progressBytes');
176+
const progressSpeed = $('#progressSpeed');
177+
172178
uploadForm.addEventListener('submit', (event) => {
173179
if (uploadForm.dataset.submitting === 'true') {
174180
event.preventDefault();
@@ -180,8 +186,83 @@ export function initUploadPreview() {
180186
showUploadMessage('请先选择或拖入至少一张图片。', 'error');
181187
return;
182188
}
189+
190+
event.preventDefault();
183191
uploadForm.dataset.submitting = 'true';
184-
setSubmitLoading(event.submitter || uploadForm.querySelector('button[type="submit"]'), '上传中...');
192+
const submitBtn = event.submitter || uploadForm.querySelector('button[type="submit"]');
193+
setSubmitLoading(submitBtn, '上传中...');
194+
clearUploadMessage();
195+
196+
if (uploadProgress) {
197+
uploadProgress.hidden = false;
198+
uploadProgress.setAttribute('aria-valuenow', '0');
199+
progressBar.value = 0;
200+
progressPercent.textContent = '0%';
201+
progressBytes.textContent = '0 B / 0 B';
202+
progressSpeed.textContent = '0 B/s';
203+
}
204+
205+
const formData = new FormData(uploadForm);
206+
const xhr = new XMLHttpRequest();
207+
let lastLoaded = 0;
208+
let lastTime = Date.now();
209+
210+
xhr.upload.onprogress = (e) => {
211+
if (!uploadProgress) return;
212+
const now = Date.now();
213+
const timeDiff = (now - lastTime) / 1000;
214+
215+
if (timeDiff > 0.2 || e.loaded === e.total) {
216+
const loadedDiff = e.loaded - lastLoaded;
217+
const speed = timeDiff > 0 ? loadedDiff / timeDiff : 0;
218+
219+
if (e.lengthComputable) {
220+
const percent = Math.round((e.loaded / e.total) * 100);
221+
progressBar.value = percent;
222+
uploadProgress.setAttribute('aria-valuenow', String(percent));
223+
progressPercent.textContent = `${percent}%`;
224+
progressBytes.textContent = `${formatSize(e.loaded)} / ${formatSize(e.total)}`;
225+
} else {
226+
progressBar.removeAttribute('value');
227+
progressPercent.textContent = '上传中...';
228+
progressBytes.textContent = `${formatSize(e.loaded)}`;
229+
}
230+
231+
progressSpeed.textContent = `${formatSize(speed)}/s`;
232+
233+
lastLoaded = e.loaded;
234+
lastTime = now;
235+
}
236+
};
237+
238+
xhr.onload = () => {
239+
if (xhr.status >= 200 && xhr.status < 400) {
240+
let redirect = uploadForm.action.split('?')[0];
241+
try {
242+
redirect = JSON.parse(xhr.responseText).redirect || redirect;
243+
} catch {}
244+
window.location.href = redirect;
245+
} else {
246+
handleUploadError(new Error(`HTTP ${xhr.status}`));
247+
}
248+
};
249+
250+
xhr.onerror = () => {
251+
handleUploadError(new Error('网络错误,上传失败'));
252+
};
253+
254+
function handleUploadError(err) {
255+
uploadForm.dataset.submitting = 'false';
256+
submitBtn.disabled = false;
257+
submitBtn.textContent = submitBtn.dataset.originalText || '上传图片';
258+
if (uploadProgress) uploadProgress.hidden = true;
259+
showUploadMessage(`上传失败: ${err.message}`, 'error');
260+
}
261+
262+
xhr.open(uploadForm.method, uploadForm.action);
263+
xhr.setRequestHeader('Accept', 'application/json');
264+
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
265+
xhr.send(formData);
185266
});
186267
}
187268

public/assets/style.css

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,53 @@ input[type="file"] {
864864
border-color: #bde2dd;
865865
}
866866

867+
.upload-progress {
868+
display: grid;
869+
gap: 8px;
870+
padding: 12px;
871+
border: 1px solid var(--line);
872+
border-radius: var(--radius);
873+
background: var(--surface-soft);
874+
}
875+
876+
.upload-progress[hidden] {
877+
display: none;
878+
}
879+
880+
.progress-bar {
881+
width: 100%;
882+
height: 8px;
883+
overflow: hidden;
884+
border: 0;
885+
border-radius: 999px;
886+
background: #e3e9f2;
887+
accent-color: var(--primary);
888+
}
889+
890+
.progress-bar::-webkit-progress-bar {
891+
border-radius: 999px;
892+
background: #e3e9f2;
893+
}
894+
895+
.progress-bar::-webkit-progress-value {
896+
border-radius: 999px;
897+
background: var(--primary);
898+
transition: width 0.2s ease;
899+
}
900+
901+
.progress-bar::-moz-progress-bar {
902+
border-radius: 999px;
903+
background: var(--primary);
904+
}
905+
906+
.progress-stats {
907+
display: flex;
908+
justify-content: space-between;
909+
font-size: 0.85rem;
910+
color: var(--muted);
911+
font-weight: 750;
912+
}
913+
867914
.selected-files {
868915
border: 1px solid var(--line);
869916
border-radius: var(--radius);

src/routes/adminRoutes.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,17 @@ function imageSectionRedirect(req) {
229229
}
230230
}
231231

232+
function wantsJson(req) {
233+
return req.get('x-requested-with') === 'XMLHttpRequest' || req.accepts(['html', 'json']) === 'json';
234+
}
235+
236+
function sendUploadResult(req, res, status = 200) {
237+
if (wantsJson(req)) {
238+
return res.status(status).json({ redirect: config.adminPath });
239+
}
240+
return res.redirect(config.adminPath);
241+
}
242+
232243
function apiExamples(adminPath) {
233244
const examples = [
234245
'/image/api/random',
@@ -377,10 +388,10 @@ export function createAdminRouter(store) {
377388
type: results.failed.length ? 'error' : 'success',
378389
text: messages.join('。') || '没有收到上传文件'
379390
};
380-
res.redirect(config.adminPath);
391+
return sendUploadResult(req, res);
381392
} catch (error) {
382393
req.session.flash = { type: 'error', text: error.message };
383-
res.redirect(config.adminPath);
394+
return sendUploadResult(req, res, 400);
384395
}
385396
});
386397

views/admin-dashboard.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ <h2>上传图片</h2>
105105
<span>可分批选择或拖拽追加,支持 jpg、jpeg、png、webp、gif、avif;最多 {{maxUploadFiles}} 张,单张 {{maxFileSizeMb}}MB。</span>
106106
</label>
107107
<div id="uploadMessage" class="upload-message" hidden></div>
108+
<div id="uploadProgress" class="upload-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" hidden>
109+
<progress id="progressBar" class="progress-bar" value="0" max="100"></progress>
110+
<div class="progress-stats">
111+
<span id="progressPercent">0%</span>
112+
<span id="progressBytes">0 B / 0 B</span>
113+
<span id="progressSpeed">0 B/s</span>
114+
</div>
115+
</div>
108116
<div id="fileList" class="selected-files" hidden></div>
109117
<button id="uploadSubmit" type="submit">上传图片</button>
110118
</form>

0 commit comments

Comments
 (0)