88c6ce8ccc
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
169 lines
6.1 KiB
JavaScript
169 lines
6.1 KiB
JavaScript
(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,
|
|
};
|
|
})();
|