feat(frontend): upgrade report dialog experience

Replace manual report links with automatic target association, improve validation and evidence uploads, and remove retired report UI code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-30 23:56:59 +08:00
1 parent 59ddf677b6
commit 80aecab586
10 files changed
+1121 -462

No files matched your search

+266 -346
View File
@@ -1,393 +1,313 @@
/**
* AKSRT Report — 前台统一举报弹窗
*
* 监听 [data-aksrt-report] 点击,弹出对齐参考主题的举报弹窗(像素级同构):
* edit-header 红色警示头 + 举报原因徽章(active 状态走 .active.badg 焦点色)
* + 违规链接 + 详细描述 + 图片举证(后台开启 user_report_img_s 时)
* + 举报说明(后台 user_report_desc,支持 HTML)+ but-average modal-buts 底栏。
* 提交走 REST /aksrt/v1/report/submit(user/comment/post 三类目标统一入口),
* 举证图片走 /aksrt/v1/report/upload-image(最多 3 张,URL 拼入举报内容)。
*
* Frontend reporting for articles, comments and users.
* @package AKSRT
*/
(function () {
'use strict';
var API_ROOT = (function () {
var d = window.aksrtData || {};
var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1';
return String(raw).replace(/\/$/, '');
})();
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
var TARGET_TITLES = {
user: '举报用户',
post: '举报文章',
comment: '举报评论',
};
var config = window.aksrtData || {};
var API_ROOT = String(config.apiUrl || '/index.php?rest_route=/aksrt/v1').replace(/\/$/, '');
var NONCE = config.restNonce || '';
var MAX_IMAGES = 3;
function getReportCategories() {
// 预置原因:后台“用户&互动 → 禁封/举报 → 预置原因”(ban_preset_reason,
// 由 UserRuntime::ban_settings 解析,aksrtData.reportCategories 注入)。
var list = window.aksrtData && window.aksrtData.reportCategories;
if (Array.isArray(list)) {
var items = list
.map(function (cat) {
return {
slug: String((cat && (cat.slug || cat.value)) || ''),
name: String((cat && (cat.name || cat.label || cat.t)) || ''),
};
})
.filter(function (cat) { return cat.name !== ''; });
if (items.length) return items;
}
return [
{ slug: 'spam', name: '垃圾信息' },
{ slug: 'abuse', name: '人身攻击' },
{ slug: 'copyright', name: '版权投诉' },
];
}
function reportDescHtml() {
// 举报说明:后台富文本(服务端已 wp_kses_post),直接注入。
var d = window.aksrtData && window.aksrtData.report;
return (d && typeof d.desc === 'string') ? d.desc : '';
}
function imgUploadEnabled() {
var d = window.aksrtData && window.aksrtData.report;
return !!(d && d.imgEnabled);
}
var sequence = 0;
var TARGETS = {
user: { title: '举报用户', label: '用户', icon: 'fa-user' },
post: { title: '举报文章', label: '文章', icon: 'fa-file-alt' },
comment: { title: '举报评论', label: '评论', icon: 'fa-comment' },
};
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
function buildReportModalHtml(data) {
var categories = getReportCategories();
var title = TARGET_TITLES[data.target_type] || '举报不良信息';
function getReportCategories() {
return (Array.isArray(config.reportCategories) ? config.reportCategories : [])
.filter(function (cat) { return cat && cat.name; });
}
var html = '';
// 弹窗头:参考主题 edit-header 同构(关闭钮 + 红色警示徽标 + 标题)。
html += '<div class="mb10 edit-header touch">';
html += '<button type="button" class="close" data-aksrt-modal-close aria-label="关闭"><svg class="ic-close" aria-hidden="true"><use xlink:href="#icon-close"></use></svg></button>';
html += '<b class="flex ac c-red"><span class="toggle-radius mr10 c-red"><i class="fa fas fa-exclamation-triangle" aria-hidden="true"></i></span>' + escapeHtml(title) + '</b>';
html += '</div>';
html += '<form class="aksrt-report-form dependency-box">';
html += '<div class="scroll-y mini-scrollbar max-vh7">';
html += '<div class="mt10 mb20">';
// 被举报对象(头像 + 名称),由服务端渲染进 data-aksrt-report。
if (data.target_avatar || data.target_name) {
html += '<div class="flex ac mb6">';
if (data.target_avatar) {
html += '<img class="avatar-mini mr6" src="' + escapeHtml(data.target_avatar) + '" alt="">';
}
html += '<b>' + escapeHtml(data.target_name || '') + '</b>';
html += '</div>';
}
html += '</div>';
// 举报原因(徽章单选,active 走参考主题焦点色;含“其它原因”展开自定义文本)。
html += '<div class="form-radio mb20 form-but-radio">';
html += '<div class="em09 muted-2-color mb6">举报原因</div>';
categories.forEach(function (cat) {
html += '<span class="mr6 mb6 badg p2-10 pointer" data-aksrt-report-reason="' + escapeHtml(cat.name) + '" data-reason-slug="' + escapeHtml(cat.slug) + '">' + escapeHtml(cat.name) + '</span>';
function buildReportModalHtml(data, categories) {
var target = TARGETS[data.target_type];
var report = config.report || {};
var id = 'aksrt-report-' + (++sequence);
var html = '<form class="aksrt-report-form" novalidate>';
html += '<div class="aksrt-report-body mini-scrollbar">';
html += '<div class="aksrt-report-target">';
html += '<span class="aksrt-report-target__icon" aria-hidden="true"><i class="fas ' + target.icon + '"></i></span>';
html += '<div><span class="aksrt-report-hint">举报对象 · ' + target.label + '</span>';
html += '<strong class="aksrt-report-target__name">' + escapeHtml(data.target_name || target.label + ' #' + data.target_id) + '</strong>';
html += '<p class="aksrt-report-hint">已自动关联该' + target.label + ',无需填写链接</p></div></div>';
html += '<fieldset class="aksrt-report-reasons" aria-describedby="' + id + '-reason-error">';
html += '<legend>举报原因<span class="aksrt-report-hint">必选一项</span></legend>';
html += '<div class="aksrt-report-reasons__options">';
categories.forEach(function (cat, index) {
html += '<label class="aksrt-report-reason"><input type="radio" name="report_reason" required value="' + index + '">';
html += '<span>' + escapeHtml(cat.name) + '</span></label>';
});
html += '<span class="mr6 mb6 badg p2-10 pointer" data-aksrt-report-reason="other">其它原因</span>';
html += '<div class="aksrt-report-other mb20" style="display: none;">';
html += '<textarea class="form-control" name="reason_other" rows="2" placeholder="请填写举报原因"></textarea>';
html += '</div>';
html += '</div>';
// 违规链接。
html += '<div class="mb20">';
html += '<div class="em09 muted-2-color mb6">违规链接</div>';
html += '<input type="text" class="form-control" name="report_url" placeholder="请输入违规链接">';
html += '</div>';
// 详细描述。
html += '<div class="mb20">';
html += '<div class="em09 muted-2-color mb6">详细描述</div>';
html += '<textarea class="form-control" name="report_desc" rows="2" placeholder="请详细描述违规事项"></textarea>';
html += '</div>';
// 图片举证(后台 user_report_img_s 开启时渲染,最多 3 张)。
if (imgUploadEnabled()) {
html += '<div class="form-upload mb10">';
html += '<div class="em09 muted-2-color mb6">添加图片举证</div>';
html += '<div class="preview">';
html += '<div class="add" data-aksrt-report-add role="button" aria-label="添加图片举证"></div>';
html += '</div>';
html += '<input class="hide" type="file" accept="image/gif,image/jpeg,image/jpg,image/png" data-aksrt-report-file>';
html += '</div>';
html += '<label class="aksrt-report-reason"><input type="radio" name="report_reason" required value="other"><span>其他原因</span></label>';
html += '</div><div class="aksrt-report-other" hidden>';
html += '<label class="aksrt-report-label" for="' + id + '-other">请说明举报原因</label>';
html += '<textarea id="' + id + '-other" name="reason_other" rows="2" maxlength="200" placeholder="请简要说明你认为存在的问题" aria-describedby="' + id + '-reason-error"></textarea>';
html += '</div><p class="aksrt-report-error" id="' + id + '-reason-error" data-aksrt-report-reason-error role="alert" hidden></p></fieldset>';
html += '<div class="aksrt-report-field"><div class="aksrt-report-label-row">';
html += '<label class="aksrt-report-label" for="' + id + '-desc">补充说明<span class="aksrt-report-hint">选填</span></label>';
html += '<span class="aksrt-report-count" data-aksrt-report-count aria-hidden="true">0 / 2000</span></div>';
html += '<textarea id="' + id + '-desc" name="report_desc" rows="3" maxlength="2000" placeholder="例如:问题出现在什么位置,具体有哪些违规内容"></textarea></div>';
if (report.imgEnabled) {
html += '<section class="aksrt-report-field" aria-label="图片举证">';
html += '<div class="aksrt-report-label-row"><span class="aksrt-report-label">图片举证<span class="aksrt-report-hint">选填</span></span>';
html += '<span class="aksrt-report-count" data-aksrt-report-image-count aria-live="polite">0 / ' + MAX_IMAGES + '</span></div>';
html += '<div class="aksrt-report-images"><div class="aksrt-report-images__list" data-aksrt-report-images></div>';
html += '<button type="button" class="aksrt-report-add" data-aksrt-report-add><i class="fas fa-plus" aria-hidden="true"></i><span>添加图片</span></button></div>';
html += '<p class="aksrt-report-hint">支持 JPG、PNG、GIF,最多 ' + MAX_IMAGES + ' 张</p>';
html += '<input type="file" accept="image/gif,image/jpeg,image/png" multiple data-aksrt-report-file hidden></section>';
}
// 举报说明(后台 user_report_desc,支持 HTML)。
var desc = reportDescHtml();
if (desc) {
html += '<div class="em09 padding-h10 muted-box mb10">' + desc + '</div>';
}
html += '</div>';
// 底栏:参考主题 but-average modal-buts 双按钮。
html += '<div class="but-average modal-buts">';
html += '<button type="button" class="but" data-aksrt-modal-close>取消</button>';
html += '<button type="button" class="but c-blue" data-aksrt-report-submit><i class="fa fas fa-check" aria-hidden="true"></i>确认提交</button>';
html += '</div>';
html += '</form>';
// The settings reader sanitizes this administrator-authored notice with wp_kses_post.
if (report.desc) html += '<div class="aksrt-report-notice">' + report.desc + '</div>';
html += '</div><footer class="aksrt-report-footer">';
html += '<p class="aksrt-report-error" data-aksrt-report-error role="alert" hidden></p>';
html += '<p class="aksrt-report-status" data-aksrt-report-status role="status"></p>';
html += '<div class="aksrt-report-actions"><button type="button" class="aksrt-report-cancel" data-aksrt-modal-close>取消</button>';
html += '<button type="submit" class="aksrt-report-submit" data-aksrt-report-submit>提交举报</button></div>';
html += '</footer></form>';
return html;
}
function selectedReason(shell) {
var active = shell.querySelector('[data-aksrt-report-reason].active');
if (!active) return { label: '', slug: '' };
return {
label: active.getAttribute('data-aksrt-report-reason') || '',
slug: active.getAttribute('data-reason-slug') || '',
};
function showError(element, message) {
element.textContent = message;
element.hidden = !message;
}
function collectImages(shell) {
return Array.prototype.map.call(
shell.querySelectorAll('.preview .preview-item img'),
function (img) { return img.getAttribute('src') || ''; }
).filter(function (url) { return url !== ''; });
}
function bindReportModal(shell, data, categories) {
var form = shell.querySelector('.aksrt-report-form');
var submit = form.querySelector('[data-aksrt-report-submit]');
var reasonError = form.querySelector('[data-aksrt-report-reason-error]');
var error = form.querySelector('[data-aksrt-report-error]');
var status = form.querySelector('[data-aksrt-report-status]');
var otherBox = form.querySelector('.aksrt-report-other');
var otherInput = form.querySelector('[name="reason_other"]');
var descInput = form.querySelector('[name="report_desc"]');
var fileInput = form.querySelector('[data-aksrt-report-file]');
var images = [];
var submitting = false;
var closed = false;
function uploadImage(shell, file) {
var preview = shell.querySelector('.form-upload .preview');
if (!preview) return Promise.resolve();
var existing = shell.querySelectorAll('.preview .preview-item').length;
if (existing + collectImages(shell).length >= MAX_IMAGES) {
notyf('最多上传 ' + MAX_IMAGES + ' 张图片', 'danger');
return Promise.resolve();
function isActive() {
return !closed && window.AksrtModalRuntime.getActive() === shell;
}
var body = new FormData();
body.append('image', file);
function syncState() {
var uploading = images.some(function (image) { return image.status === 'uploading'; });
submit.disabled = submitting || uploading;
submit.textContent = submitting ? '正在提交…' : uploading ? '图片上传中…' : '提交举报';
form.setAttribute('aria-busy', String(submitting || uploading));
status.textContent = submitting ? '正在提交举报,请稍候' : uploading ? '图片正在上传,完成后即可提交' : '';
var add = form.querySelector('[data-aksrt-report-add]');
if (add) {
add.disabled = submitting || images.length >= MAX_IMAGES;
form.querySelector('[data-aksrt-report-image-count]').textContent = images.length + ' / ' + MAX_IMAGES;
}
}
return window.fetch(API_ROOT + '/report/upload-image', {
method: 'POST',
headers: { 'X-WP-Nonce': NONCE },
body: body,
})
.then(function (res) { return res.json(); })
.then(function (result) {
var url = result && result.success && result.data && result.data.url;
if (!url) {
notyf((result && result.message) || '图片上传失败', 'danger');
function deletePending(image) {
if (!image.attachmentId) return;
window.fetch(API_ROOT + '/upload/pending/' + encodeURIComponent(image.attachmentId), {
method: 'DELETE', headers: { 'X-WP-Nonce': NONCE },
}).catch(function () {});
}
function renderImage(image) {
var html = image.url ? '<img src="' + escapeHtml(image.url) + '" alt="举证图片">' : '';
html += '<div class="aksrt-report-image__state">';
if (image.status === 'uploading') html += '<span role="status">上传中…</span>';
if (image.status === 'error') {
html += '<span>上传失败</span><button type="button" data-aksrt-report-retry>重试</button>';
}
html += '</div><button type="button" class="aksrt-report-image__remove" data-aksrt-report-remove aria-label="删除举证图片"' + (submitting ? ' disabled' : '') + '>&times;</button>';
image.node.innerHTML = html;
image.node.setAttribute('data-upload-state', image.status);
}
function uploadImage(image) {
image.status = 'uploading';
renderImage(image);
syncState();
var body = new FormData();
body.append('image', image.file);
window.fetch(API_ROOT + '/report/upload-image', {
method: 'POST', headers: { 'X-WP-Nonce': NONCE }, body: body,
}).then(function (response) { return response.json(); }).then(function (result) {
if (!result || !result.success || !result.data || !result.data.url) {
throw new Error((result && result.message) || '图片上传失败,请重试');
}
image.attachmentId = result.data.id;
if (closed || images.indexOf(image) === -1) {
deletePending(image);
return;
}
var item = document.createElement('div');
item.className = 'preview-item';
var img = document.createElement('img');
img.src = url;
img.alt = '';
var remove = document.createElement('button');
remove.type = 'button';
remove.className = 'remove';
remove.setAttribute('aria-label', '删除图片');
remove.innerHTML = '&times;';
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'));
})
.catch(function () {
notyf('图片上传失败,请重试', 'danger');
image.url = result.data.url;
image.status = 'ready';
renderImage(image);
}).catch(function (failure) {
if (closed || images.indexOf(image) === -1) return;
image.status = 'error';
renderImage(image);
showError(error, failure.message || '图片上传失败,请重试');
}).then(function () {
if (!closed) syncState();
});
}
function submitReport(data, shell) {
var form = shell.querySelector('.aksrt-report-form');
if (!form) return;
var submitBtn = form.querySelector('[data-aksrt-report-submit]');
var descInput = form.querySelector('[name="report_desc"]');
var otherInput = form.querySelector('[name="reason_other"]');
var urlInput = form.querySelector('[name="report_url"]');
var selected = selectedReason(shell);
var other = (otherInput && otherInput.value || '').trim();
if (!selected.label && !other) {
notyf('请选择或输入举报原因', 'danger');
return;
}
// “其它原因”以自定义文本为举报原因;预置徽章取徽章文案(参考主题同语义)。
var reason = selected.label === 'other' || !selected.label ? other : selected.label;
var slug = selected.label && selected.label !== 'other' ? selected.slug : '';
if (!reason) {
notyf('请填写举报原因', 'danger');
return;
}
var link = (urlInput && urlInput.value || '').trim();
if (link) {
reason += (reason ? '\n' : '') + '违规链接:' + link;
}
var images = collectImages(shell);
if (images.length) {
reason += '\n图片举证:';
images.forEach(function (url) { reason += '\n' + url; });
}
submitBtn.disabled = true;
notyf('正在提交举报…', 'load', 3000, 'aksrt-report-submit');
window.fetch(API_ROOT + '/report/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': NONCE,
},
body: JSON.stringify({
target_type: data.target_type,
target_id: data.target_id,
category: slug,
reason: reason,
}),
})
.then(function (res) { return res.json(); })
.then(function (result) {
if (result && result.success) {
window.AksrtModalRuntime.close('submit');
notyf(result.message || '举报已提交,我们会尽快处理', 'success');
} else {
submitBtn.disabled = false;
notyf((result && result.message) || '举报提交失败', 'danger');
form.addEventListener('change', function (event) {
if (event.target.name === 'report_reason') {
var other = event.target.value === 'other';
otherBox.hidden = !other;
otherInput.disabled = !other;
otherInput.required = other;
otherInput.removeAttribute('aria-invalid');
showError(reasonError, '');
if (other) otherInput.focus();
}
if (event.target !== fileInput || submitting) return;
var files = Array.prototype.slice.call(fileInput.files || []);
showError(error, '');
files.forEach(function (file) {
if (images.length >= MAX_IMAGES) {
showError(error, '最多上传 ' + MAX_IMAGES + ' 张图片,请先删除不需要的图片');
return;
}
})
.catch(function () {
submitBtn.disabled = false;
notyf('网络错误,请重试', 'danger');
})
.then(function () {
var toast = document.getElementById('aksrt-report-submit');
if (toast) toast.remove();
});
}
function bindAutoHeight(textarea) {
// 对齐参考主题 autoHeight 行为:随内容自动增高,最小保持初始行数高度。
// 隐藏中的 textarea(如“其它原因”)跳过首次计算,待可见后按实际高度增长。
if (!textarea || textarea.dataset.aksrtAutoHeight === '1') return;
textarea.dataset.aksrtAutoHeight = '1';
var minHeight = 0;
var grow = function () {
if (!minHeight) minHeight = textarea.offsetHeight || 0;
textarea.style.height = 'auto';
textarea.style.height = Math.max(textarea.scrollHeight, minHeight) + 'px';
};
textarea.addEventListener('input', grow);
if (textarea.offsetHeight > 0) grow();
}
function bindReportModal(shell) {
shell.querySelectorAll('textarea.form-control').forEach(bindAutoHeight);
shell.addEventListener('click', function (event) {
// 原因徽章:单选 active(参考主题 .active.badg 焦点色),其它原因展开文本框。
// 参考主题行为:重复点击已选徽章保持选中(先全清再激活),不做反选。
var chip = event.target.closest('[data-aksrt-report-reason]');
if (chip) {
event.preventDefault();
shell.querySelectorAll('[data-aksrt-report-reason].active').forEach(function (el) {
el.classList.remove('active');
});
chip.classList.add('active');
var otherBox = shell.querySelector('.aksrt-report-other');
if (otherBox) otherBox.style.display = chip.getAttribute('data-aksrt-report-reason') === 'other' ? '' : 'none';
return;
}
// 图片举证:点击加号或预览补位触发文件选择。
var addBtn = event.target.closest('[data-aksrt-report-add]');
if (addBtn) {
event.preventDefault();
var fileInput = shell.querySelector('[data-aksrt-report-file]');
if (fileInput) fileInput.click();
return;
}
var submitBtn = event.target.closest('[data-aksrt-report-submit]');
if (submitBtn) {
event.preventDefault();
submitReport(shell.getAttribute('data-aksrt-report-target') ? JSON.parse(shell.getAttribute('data-aksrt-report-target')) : {}, shell);
}
});
shell.addEventListener('change', function (event) {
var fileInput = event.target.closest('[data-aksrt-report-file]');
if (!fileInput || !fileInput.files || !fileInput.files.length) return;
Array.prototype.forEach.call(fileInput.files, function (file) {
uploadImage(shell, file);
if (!/^image\/(jpeg|png|gif)$/.test(file.type)) {
showError(error, '请选择 JPG、PNG 或 GIF 图片');
return;
}
var image = { file: file, status: 'uploading', url: '', attachmentId: 0, node: document.createElement('div') };
image.node.className = 'aksrt-report-image';
images.push(image);
form.querySelector('[data-aksrt-report-images]').appendChild(image.node);
uploadImage(image);
});
fileInput.value = '';
});
form.addEventListener('input', function (event) {
if (event.target === descInput) form.querySelector('[data-aksrt-report-count]').textContent = descInput.value.length + ' / 2000';
if (event.target === otherInput) {
otherInput.removeAttribute('aria-invalid');
showError(reasonError, '');
}
});
form.addEventListener('click', function (event) {
var add = event.target.closest('[data-aksrt-report-add]');
if (add && !add.disabled && !submitting) fileInput.click();
var imageNode = event.target.closest('.aksrt-report-image');
if (!imageNode || submitting) return;
var image = images.find(function (entry) { return entry.node === imageNode; });
if (!image) return;
if (event.target.closest('[data-aksrt-report-remove]')) {
images.splice(images.indexOf(image), 1);
image.node.remove();
deletePending(image);
showError(error, '');
syncState();
} else if (event.target.closest('[data-aksrt-report-retry]') && image.status === 'error') {
showError(error, '');
uploadImage(image);
}
});
form.addEventListener('submit', function (event) {
event.preventDefault();
if (submitting || closed) return;
showError(error, '');
var selected = form.querySelector('[name="report_reason"]:checked');
if (!selected) {
showError(reasonError, '请选择一项举报原因');
form.querySelector('[name="report_reason"]').focus();
return;
}
var category = selected.value === 'other' ? null : categories[Number(selected.value)];
var reason = category ? String(category.name) : otherInput.value.trim();
if (!reason) {
showError(reasonError, '请填写举报原因');
otherInput.setAttribute('aria-invalid', 'true');
otherInput.focus();
return;
}
if (images.some(function (image) { return image.status !== 'ready'; })) {
showError(error, '请等待图片上传完成,或重试、删除上传失败的图片');
return;
}
var description = descInput.value.trim();
if (description) reason += '\n详细描述:\n' + description;
if (images.length) reason += '\n图片举证:\n' + images.map(function (image) { return image.url; }).join('\n');
submitting = true;
syncState();
form.querySelectorAll('input, textarea, [data-aksrt-report-remove], [data-aksrt-report-retry]').forEach(function (element) { element.disabled = true; });
window.fetch(API_ROOT + '/report/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE },
body: JSON.stringify({ target_type: data.target_type, target_id: data.target_id, category: category ? category.slug : '', reason: reason }),
}).then(function (response) { return response.json(); }).then(function (result) {
if (!result || !result.success) throw new Error((result && result.message) || '举报提交失败,请重试');
// Submitted evidence belongs to the report even if its dialog has already closed.
images = [];
form.setAttribute('aria-busy', 'false');
submit.textContent = '举报已提交';
status.textContent = result.message || '举报已提交,我们会尽快处理';
form.querySelector('[data-aksrt-modal-close]').textContent = '关闭';
if (isActive()) window.AksrtModalRuntime.close('submit');
notyf(result.message || '举报已提交,我们会尽快处理', 'success');
}).catch(function (failure) {
submitting = false;
if (closed) {
images.forEach(deletePending);
return;
}
form.querySelectorAll('input, textarea, [data-aksrt-report-remove], [data-aksrt-report-retry]').forEach(function (element) { element.disabled = false; });
otherInput.disabled = otherBox.hidden;
syncState();
showError(error, failure.message || '网络错误,请重试');
});
});
function onClose(event) {
if (!event.detail || event.detail.shell !== shell) return;
closed = true;
document.removeEventListener('aksrt:modal:close', onClose);
// While submitting, the response decides whether pending evidence is still needed.
if (!submitting) images.forEach(deletePending);
}
document.addEventListener('aksrt:modal:close', onClose);
otherInput.disabled = true;
syncState();
}
function openReportModal(data) {
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== 'function') {
function openReportModal(data, opener) {
var runtime = window.AksrtModalRuntime;
if (!runtime || typeof runtime.open !== 'function') {
notyf('举报组件加载失败,请刷新页面后重试', 'danger');
return;
}
var shell = window.AksrtModalRuntime.open({
html: buildReportModalHtml(data),
className: 'aksrt-report-modal',
kind: 'report',
mobileBottom: true,
var categories = getReportCategories();
var shell = runtime.open({
html: buildReportModalHtml(data, categories), title: TARGETS[data.target_type].title,
className: 'aksrt-report-modal', kind: 'report', mobileBottom: true, focusReturn: opener,
});
shell.setAttribute('data-aksrt-report-target', JSON.stringify({
target_type: data.target_type,
target_id: data.target_id,
}));
bindReportModal(shell);
bindReportModal(shell, data, categories);
}
function initReportButtons() {
if (document.documentElement.dataset.aksrtReportInitialized === '1') return;
document.documentElement.dataset.aksrtReportInitialized = '1';
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-aksrt-report]');
if (!btn) return;
e.preventDefault();
try {
var data = JSON.parse(btn.getAttribute('data-aksrt-report'));
if (data && data.target_type && data.target_id) {
openReportModal(data);
}
} catch {
// invalid data attribute
}
document.addEventListener('click', function (event) {
var button = event.target.closest('[data-aksrt-report]');
if (!button) return;
event.preventDefault();
var data;
try { data = JSON.parse(button.getAttribute('data-aksrt-report')); } catch (error) { return; }
if (data && TARGETS[data.target_type] && Number(data.target_id) > 0) openReportModal(data, button);
});
}