重构 monorepo 并完善网页端订阅与首页体验

- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构
- 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐
- 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作
- 新增管理后台、支付与云端文件 API,更新 README 与项目规范

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
24kycj
2026-06-08 18:17:39 +08:00
parent 5814b7bc0e
commit 88c6ce8ccc
511 changed files with 189528 additions and 22804 deletions
@@ -0,0 +1,168 @@
(function () {
'use strict';
var I = function () { return window.AdminI18n; };
function itemDataset(it) {
if (!it.dataset) return '';
return Object.keys(it.dataset).map(function (k) {
return ' data-' + k + '="' + I().esc(it.dataset[k]) + '"';
}).join('');
}
function menuItemBtn(it) {
if (it.disabled) {
return '<button type="button" class="soon-admin-action-menu__item" disabled title="' +
I().esc(it.label) + '">' + I().esc(it.label) + '</button>';
}
var cls = 'soon-admin-action-menu__item' + (it.className ? ' ' + it.className : '');
return '<button type="button" class="' + cls + '" data-action="' + I().esc(it.action) + '"' +
itemDataset(it) + '>' + I().esc(it.label) + '</button>';
}
function inlineBtn(it) {
if (it.disabled) {
return '<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost soon-admin-row-actions__btn" disabled title="' +
I().esc(it.label) + '">' + I().esc(it.label) + '</button>';
}
return '<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost soon-admin-row-actions__btn" data-action="' +
I().esc(it.action) + '"' + itemDataset(it) + '>' + I().esc(it.label) + '</button>';
}
function positionFloatingPanel(menu) {
var toggle = menu.querySelector('.soon-admin-action-menu__toggle');
var panel = menu.querySelector('.soon-admin-action-menu__panel');
if (!toggle || !panel) return;
panel.classList.add('soon-admin-action-menu__panel--floating');
panel.style.display = 'block';
panel.style.visibility = 'hidden';
var r = toggle.getBoundingClientRect();
var panelW = panel.offsetWidth;
var panelH = panel.offsetHeight;
var gap = 4;
var left = r.right - panelW;
if (left < 8) left = 8;
if (left + panelW > window.innerWidth - 8) left = window.innerWidth - panelW - 8;
var top = r.bottom + gap;
if (top + panelH > window.innerHeight - 8) {
top = Math.max(8, r.top - panelH - gap);
}
panel.style.left = left + 'px';
panel.style.top = top + 'px';
panel.style.visibility = '';
}
function actionMenu(menuId, items) {
var btns = items.map(menuItemBtn).join('');
return '<div class="soon-admin-action-menu" data-menu="' + I().esc(menuId) + '">' +
'<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost soon-admin-action-menu__toggle">操作 ▾</button>' +
'<div class="soon-admin-action-menu__panel">' + btns + '</div></div>';
}
function rowActions(menuId, inlineItems, moreItems) {
var inline = (inlineItems || []).map(inlineBtn).join('');
var more = (moreItems || []).map(menuItemBtn).join('');
if (!more) {
return '<div class="soon-admin-row-actions" data-menu="' + I().esc(menuId) + '">' + inline + '</div>';
}
return '<div class="soon-admin-row-actions" data-menu="' + I().esc(menuId) + '">' + inline +
'<div class="soon-admin-action-menu soon-admin-action-menu--more">' +
'<button type="button" class="soon-btn soon-btn--xs soon-btn--ghost soon-admin-action-menu__toggle">更多 ▾</button>' +
'<div class="soon-admin-action-menu__panel">' + more + '</div></div></div>';
}
function closeAll() {
document.querySelectorAll('.soon-admin-action-menu.is-open').forEach(function (m) {
m.classList.remove('is-open');
resetPanelStyle(m.querySelector('.soon-admin-action-menu__panel'));
});
}
function bindActionButtons(container, handler, selector) {
container.querySelectorAll(selector).forEach(function (btn) {
if (btn.disabled) return;
btn.onclick = function (e) {
e.stopPropagation();
closeAll();
if (typeof handler === 'function') handler(btn.dataset.action, btn);
};
});
}
function resetPanelStyle(panel) {
if (!panel) return;
panel.style.display = '';
panel.style.left = '';
panel.style.top = '';
panel.style.visibility = '';
}
function bindHoverMore(container) {
container.querySelectorAll('.soon-admin-action-menu--more').forEach(function (menu) {
var hideTimer = null;
var panel = menu.querySelector('.soon-admin-action-menu__panel');
function openMenu() {
if (hideTimer) {
clearTimeout(hideTimer);
hideTimer = null;
}
closeAll();
positionFloatingPanel(menu);
menu.classList.add('is-open');
}
function scheduleClose() {
hideTimer = setTimeout(function () {
menu.classList.remove('is-open');
resetPanelStyle(panel);
}, 180);
}
function cancelClose() {
if (hideTimer) {
clearTimeout(hideTimer);
hideTimer = null;
}
}
menu.addEventListener('mouseenter', openMenu);
menu.addEventListener('mouseleave', scheduleClose);
if (panel) {
panel.addEventListener('mouseenter', cancelClose);
panel.addEventListener('mouseleave', scheduleClose);
}
});
}
function bind(container, handler) {
if (!container) return;
bindActionButtons(container, handler, '.soon-admin-row-actions__btn[data-action]');
container.querySelectorAll('.soon-admin-action-menu__toggle').forEach(function (btn) {
var menu = btn.closest('.soon-admin-action-menu');
if (menu && menu.classList.contains('soon-admin-action-menu--more')) return;
btn.onclick = function (e) {
e.stopPropagation();
var wasOpen = menu.classList.contains('is-open');
closeAll();
if (!wasOpen) {
positionFloatingPanel(menu);
menu.classList.add('is-open');
}
};
});
bindActionButtons(container, handler, '.soon-admin-action-menu__item[data-action]');
bindHoverMore(container);
}
if (!window._soonAdminMenuDocBound) {
window._soonAdminMenuDocBound = true;
document.addEventListener('click', closeAll);
window.addEventListener('scroll', closeAll, true);
window.addEventListener('resize', closeAll);
}
window.AdminMenu = {
actionMenu: actionMenu,
rowActions: rowActions,
bind: bind,
closeAll: closeAll,
positionFloatingPanel: positionFloatingPanel,
};
})();