重构 monorepo 并完善网页端订阅与首页体验
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var Api = function () { return window.AdminApi; };
|
||||
var Ui = function () { return window.AdminUi; };
|
||||
var I = function () { return window.AdminI18n; };
|
||||
|
||||
function statCard(label, value, opts) {
|
||||
opts = opts || {};
|
||||
var inner = '<div class="soon-admin-stat__label">' + I().esc(label) +
|
||||
'</div><div class="soon-admin-stat__value">' + I().esc(value) + '</div>';
|
||||
if (opts.hint) inner += '<div class="soon-admin-stat__hint">' + I().esc(opts.hint) + '</div>';
|
||||
if (opts.href) {
|
||||
return '<a class="soon-admin-stat soon-admin-stat--link" href="' + I().esc(opts.href) + '">' + inner + '</a>';
|
||||
}
|
||||
return '<div class="soon-admin-stat">' + inner + '</div>';
|
||||
}
|
||||
|
||||
function alertBar(d) {
|
||||
var parts = [];
|
||||
if (d.orders_pending > 0) parts.push('待支付订单 <strong>' + d.orders_pending + '</strong> 笔');
|
||||
if (d.refunds_pending > 0) parts.push('退款待审核 <strong>' + d.refunds_pending + '</strong> 笔');
|
||||
if (!parts.length) return '';
|
||||
var href = '#/orders';
|
||||
if (d.refunds_pending > 0) href = '#/orders?refund_status=pending';
|
||||
else if (d.orders_pending > 0) href = '#/orders?status=pending';
|
||||
return '<div class="soon-admin-alert-bar"><span>' + parts.join(' · ') + '</span>' +
|
||||
'<a href="' + href + '" class="soon-btn soon-btn--sm">去处理</a></div>';
|
||||
}
|
||||
|
||||
function quickLinks() {
|
||||
var links = [
|
||||
{ href: '#/users', label: '用户管理' },
|
||||
{ href: '#/orders', label: '订单管理' },
|
||||
{ href: '#/plans', label: '套餐配置' },
|
||||
{ href: '#/payment', label: '支付密钥' },
|
||||
{ href: '#/settings', label: '系统设置' },
|
||||
{ href: '#/audits', label: '审计日志' },
|
||||
];
|
||||
var level = (window.AdminState && AdminState.adminLevel) || 'full';
|
||||
var visible = window.AdminPermissions
|
||||
? AdminPermissions.filterQuickLinks(links, level)
|
||||
: links;
|
||||
var items = visible.map(function (l) {
|
||||
return '<a class="soon-admin-quick-link" href="' + l.href + '">' + I().esc(l.label) + '</a>';
|
||||
}).join('');
|
||||
return '<div class="soon-admin-card"><h3 class="soon-admin-card__title">快捷入口</h3>' +
|
||||
'<div class="soon-admin-quick-grid">' + items + '</div></div>';
|
||||
}
|
||||
|
||||
function recentOrdersTable(orders) {
|
||||
var rows = (orders || []).map(function (o) {
|
||||
return '<tr><td>' + I().esc(o.order_no || o.id) + '</td><td>' + I().esc(o.email || '') + '</td><td>' +
|
||||
I().formatCents(o.amount_cents) + '</td><td>' + I().badgeStatus(o.status) + '</td><td>' +
|
||||
I().formatDateTime(o.created_at) + '</td></tr>';
|
||||
}).join('');
|
||||
return Ui().dataTable(['订单号', '用户', '金额', '状态', '时间'], rows, 5);
|
||||
}
|
||||
|
||||
function render() {
|
||||
var panel = Ui().el('panel-dashboard');
|
||||
if (!panel) return;
|
||||
panel.innerHTML = '<div class="soon-admin-page">' +
|
||||
Ui().pageHeader('运营概览', '关键指标、待办提醒与最近订单',
|
||||
'<button type="button" class="soon-btn soon-btn--sm soon-btn--ghost" id="dashRefresh">刷新</button>') +
|
||||
Ui().skeletonStats(6) +
|
||||
'<div class="soon-admin-dash-grid">' +
|
||||
'<div class="soon-admin-card"><h3 class="soon-admin-card__title">最近订单</h3>' + Ui().skeletonTable() + '</div>' +
|
||||
'<div class="soon-skeleton soon-skeleton--h160"></div></div></div>';
|
||||
|
||||
function fetchStats(showLoading) {
|
||||
var btn = Ui().el('dashRefresh');
|
||||
if (showLoading && btn) Ui().setBtnLoading(btn, true, '刷新中…');
|
||||
Api().get('stats').then(function (j) {
|
||||
if (showLoading && btn) Ui().setBtnLoading(btn, false);
|
||||
if (!j) {
|
||||
panel.innerHTML = '<div class="soon-admin-page">' +
|
||||
Ui().pageHeader('运营概览', '关键指标、待办提醒与最近订单',
|
||||
'<button type="button" class="soon-btn soon-btn--sm soon-btn--ghost" id="dashRefresh">刷新</button>') +
|
||||
Ui().listError('概览数据加载失败', 'dashRetry') + '</div>';
|
||||
Ui().el('dashRetry').onclick = render;
|
||||
Ui().el('dashRefresh').onclick = function () { fetchStats(true); };
|
||||
return;
|
||||
}
|
||||
paintDashboard(panel, j.data);
|
||||
});
|
||||
}
|
||||
|
||||
Ui().el('dashRefresh').onclick = function () { fetchStats(true); };
|
||||
fetchStats(false);
|
||||
}
|
||||
|
||||
function paintDashboard(panel, d) {
|
||||
var stats =
|
||||
statCard('用户总数', d.users, { href: '#/users', hint: '点击进入用户管理' }) +
|
||||
statCard('订单总数', d.orders, { href: '#/orders', hint: '含全部状态' }) +
|
||||
statCard('今日订单', d.orders_today, { href: '#/orders', hint: '按创建日统计' }) +
|
||||
statCard('待支付', d.orders_pending || 0, { href: '#/orders?status=pending', hint: '需关注超时未付' }) +
|
||||
statCard('活跃订阅', d.active_subscriptions, { href: '#/users', hint: '有效会员数' }) +
|
||||
statCard('今日收入', I().formatCents(d.revenue_today_cents), { href: '#/orders', hint: '已支付订单' }) +
|
||||
statCard('云端文件', d.files_total, { hint: '未删除的设计文件' }) +
|
||||
statCard('退款待审', d.refunds_pending || 0, { href: '#/orders?refund_status=pending', hint: '需在订单页处理' });
|
||||
|
||||
panel.innerHTML = '<div class="soon-admin-page">' +
|
||||
Ui().pageHeader('运营概览', '关键指标、待办提醒与最近订单',
|
||||
'<button type="button" class="soon-btn soon-btn--sm soon-btn--ghost" id="dashRefresh">刷新</button>' +
|
||||
'<a href="#/orders" class="soon-btn soon-btn--sm">全部订单</a>') +
|
||||
alertBar(d) +
|
||||
'<div class="soon-admin-stat-grid soon-admin-stat-grid--dash">' + stats + '</div>' +
|
||||
'<div class="soon-admin-dash-grid">' +
|
||||
'<div class="soon-admin-card"><div class="soon-admin-card__head-row">' +
|
||||
'<h3 class="soon-admin-card__title">最近订单</h3>' +
|
||||
'<a href="#/orders" class="soon-admin-card__link">查看全部</a></div>' +
|
||||
recentOrdersTable(d.recent_orders) + '</div>' +
|
||||
quickLinks() + '</div></div>';
|
||||
|
||||
Ui().el('dashRefresh').onclick = render;
|
||||
}
|
||||
|
||||
window.AdminViews = window.AdminViews || {};
|
||||
window.AdminViews.dashboard = { render: render };
|
||||
})();
|
||||
Reference in New Issue
Block a user