feat(upload): pending lifecycle for upload-on-select images

Mark front-end uploads (comment/ticket/report) as pending, return the
attachment id, discard own pending uploads on remove via
DELETE /upload/pending/{id}, claim referenced URLs on successful submit,
and purge unclaimed uploads older than 24h with an hourly cron.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-27 21:54:48 +08:00
1 parent a308f96837
commit ac3ed9c956
9 files changed
+247 -2

No files matched your search

+11 -1
View File
@@ -202,7 +202,17 @@
remove.className = 'remove';
remove.setAttribute('aria-label', '删除图片');
remove.innerHTML = '&times;';
remove.addEventListener('click', function () { item.remove(); });
var uploadId = result.data && result.data.id;
remove.addEventListener('click', function () {
item.remove();
// 删除服务端待认领附件;失败由定时任务兜底清理。
if (uploadId) {
window.fetch(API_ROOT + '/upload/pending/' + encodeURIComponent(uploadId), {
method: 'DELETE',
headers: { 'X-WP-Nonce': NONCE },
}).catch(function () {});
}
});
item.appendChild(img);
item.appendChild(remove);
preview.insertBefore(item, preview.querySelector('.add'));
+15 -1
View File
@@ -246,6 +246,16 @@
syncUploader(root);
}
// × 移除即删除服务端待认领附件(仅本人、未提交的);失败无需打扰用户,定时任务兜底清理。
function discardUpload(id) {
if (!id || !/^\d+$/.test(id)) return;
fetch(API_ROOT + '/upload/pending/' + id, {
method: 'DELETE',
headers: { 'X-WP-Nonce': NONCE },
credentials: 'same-origin',
}).catch(function () {});
}
function uploadOne(root, holder, file) {
var tile = document.createElement('div');
tile.className = 'aksrt-ticket-image is-uploading';
@@ -264,6 +274,8 @@
if (!url) return Promise.reject(result);
tile.classList.remove('is-uploading');
tile.setAttribute('data-aksrt-ticket-image', url);
var uploadId = result.data && result.data.id;
if (uploadId) tile.setAttribute('data-aksrt-ticket-image-id', String(uploadId));
tile.innerHTML = '<img src="' + escapeHtml(url) + '" alt="">'
+ '<button type="button" class="aksrt-ticket-image__remove" data-aksrt-ticket-image-remove aria-label="移除图片">'
+ icon('fa-times') + '</button>';
@@ -295,7 +307,9 @@
var remove = event.target.closest('[data-aksrt-ticket-image-remove]');
if (remove) {
var root = remove.closest('form');
remove.closest('.aksrt-ticket-image').remove();
var tile = remove.closest('.aksrt-ticket-image');
discardUpload(tile.getAttribute('data-aksrt-ticket-image-id'));
tile.remove();
syncUploader(root);
return;
}