88c6ce8ccc
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
145 lines
5.6 KiB
JavaScript
145 lines
5.6 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var Api = function () { return window.AdminApi; };
|
|
var Ui = function () { return window.AdminUi; };
|
|
var I = function () { return window.AdminI18n; };
|
|
var State = function () { return window.AdminState; };
|
|
var Filter = function () { return window.AdminFilter; };
|
|
var Router = function () { return window.AdminRouter; };
|
|
|
|
var ACTION_OPTS = [
|
|
{ v: '', l: '全部操作' },
|
|
{ v: 'users.setStatus', l: '变更用户状态' },
|
|
{ v: 'users.create', l: '创建用户' },
|
|
{ v: 'users.update', l: '编辑用户' },
|
|
{ v: 'users.delete', l: '删除用户' },
|
|
{ v: 'users.setPassword', l: '重置密码' },
|
|
{ v: 'users.setSubscription', l: '设置会员' },
|
|
{ v: 'users.detail', l: '查看用户详情' },
|
|
{ v: 'orders.detail', l: '查看订单详情' },
|
|
{ v: 'orders.cancel', l: '取消订单' },
|
|
{ v: 'orders.refundApprove', l: '通过退款' },
|
|
{ v: 'orders.refundReject', l: '拒绝退款' },
|
|
{ v: 'plans.upsert', l: '编辑套餐' },
|
|
{ v: 'settings.set', l: '修改系统设置' },
|
|
{ v: 'settings.set_batch', l: '批量修改设置' },
|
|
{ v: 'payment.key.upload', l: '上传支付密钥' },
|
|
];
|
|
|
|
function buildQuery() {
|
|
var st = State().audits;
|
|
var q = 'audits?page=' + st.page + '&size=' + st.size;
|
|
if (st.q) q += '&q=' + encodeURIComponent(st.q);
|
|
if (st.from) q += '&from=' + encodeURIComponent(st.from);
|
|
if (st.to) q += '&to=' + encodeURIComponent(st.to);
|
|
if (st.action) q += '&action=' + encodeURIComponent(st.action);
|
|
return q;
|
|
}
|
|
|
|
function actionOptions(selected) {
|
|
return ACTION_OPTS.map(function (o) {
|
|
return '<option value="' + o.v + '"' + (selected === o.v ? ' selected' : '') + '>' + o.l + '</option>';
|
|
}).join('');
|
|
}
|
|
|
|
function formatContext(ctx) {
|
|
if (!ctx) return '';
|
|
var body;
|
|
try {
|
|
body = '<pre class="soon-admin-code">' + I().esc(JSON.stringify(JSON.parse(ctx), null, 2)) + '</pre>';
|
|
} catch (e) {
|
|
body = '<div class="soon-audit-item__context">' + I().esc(ctx) + '</div>';
|
|
}
|
|
return '<details class="soon-audit-item__details"><summary>查看上下文</summary>' + body + '</details>';
|
|
}
|
|
|
|
function renderFilterBar(st) {
|
|
return Filter().bar(
|
|
Filter().field('关键词', Filter().searchInput('auditQ', st.q, '操作、目标或管理员')) +
|
|
Filter().field('操作类型', Filter().select('auditAction', actionOptions(st.action))) +
|
|
Ui().filterDateField('auditFrom', '从', st.from) +
|
|
Ui().filterDateField('auditTo', '至', st.to),
|
|
'<button type="button" class="soon-btn soon-btn--sm" id="auditSearchBtn">查询</button>' +
|
|
'<button type="button" class="soon-btn soon-btn--sm soon-btn--ghost" id="auditResetBtn">重置</button>'
|
|
);
|
|
}
|
|
|
|
function bindFilters(st) {
|
|
var searchBtn = Ui().el('auditSearchBtn');
|
|
var resetBtn = Ui().el('auditResetBtn');
|
|
var qInput = Ui().el('auditQ');
|
|
if (searchBtn) {
|
|
searchBtn.onclick = function () {
|
|
st.q = qInput ? qInput.value.trim() : '';
|
|
st.action = Ui().el('auditAction').value;
|
|
st.from = Ui().el('auditFrom').value;
|
|
st.to = Ui().el('auditTo').value;
|
|
if (st.from && st.to && st.from > st.to) {
|
|
soonToast('开始日期不能晚于结束日期', 'warn');
|
|
return;
|
|
}
|
|
st.page = 1;
|
|
Router().syncQuery();
|
|
render();
|
|
};
|
|
}
|
|
if (qInput) {
|
|
qInput.onkeydown = function (e) {
|
|
if (e.key === 'Enter' && searchBtn) searchBtn.click();
|
|
};
|
|
}
|
|
if (resetBtn) {
|
|
resetBtn.onclick = function () {
|
|
State().audits = { page: 1, size: 50, q: '', from: '', to: '', action: '' };
|
|
Router().syncQuery();
|
|
render();
|
|
};
|
|
}
|
|
}
|
|
|
|
function renderTimeline(items) {
|
|
if (!items.length) {
|
|
return Ui().emptyBlock('暂无审计记录', '调整筛选条件或等待管理员操作产生日志', '');
|
|
}
|
|
return items.map(function (a) {
|
|
return '<div class="soon-audit-item"><div class="soon-audit-item__title">' +
|
|
I().esc(I().tAction(a.action)) + ' · ' + I().esc(a.target) + '</div>' +
|
|
'<div class="soon-audit-item__meta">' + I().esc(a.admin_email || '') + ' · ' +
|
|
I().formatDateTime(a.created_at) + '</div>' + formatContext(a.context) + '</div>';
|
|
}).join('');
|
|
}
|
|
|
|
function render() {
|
|
var panel = Ui().el('panel-audits');
|
|
if (!panel) return;
|
|
var st = State().audits;
|
|
var header = Ui().pageHeader('审计日志', '管理员操作记录,支持按操作类型与时间筛选');
|
|
panel.innerHTML = '<div class="soon-admin-page">' + header + renderFilterBar(st) + Ui().skeletonTable() + '</div>';
|
|
|
|
Api().get(buildQuery()).then(function (j) {
|
|
if (!j) {
|
|
panel.innerHTML = '<div class="soon-admin-page">' + header + renderFilterBar(st) +
|
|
Ui().listError('审计日志加载失败', 'auditsListRetry') + '</div>';
|
|
bindFilters(st);
|
|
Ui().el('auditsListRetry').onclick = render;
|
|
return;
|
|
}
|
|
var items = j.data.items || [];
|
|
panel.innerHTML = '<div class="soon-admin-page">' + header + renderFilterBar(st) +
|
|
'<div class="soon-admin-card"><div class="soon-audit-timeline">' + renderTimeline(items) + '</div></div>' +
|
|
Ui().pagerHtml(j.data.page, j.data.size, j.data.total) + '</div>';
|
|
bindFilters(st);
|
|
Ui().bindPager(panel, j.data.page, j.data.size, j.data.total, function (p, newSize) {
|
|
st.page = p;
|
|
if (newSize) st.size = newSize;
|
|
Router().syncQuery();
|
|
render();
|
|
});
|
|
});
|
|
}
|
|
|
|
window.AdminViews = window.AdminViews || {};
|
|
window.AdminViews.audits = { render: render };
|
|
})();
|