重构 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,106 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var STANDARD_LINKS = [
|
||||
{ id: 'home', href: 'index.web.html', label: '设计首页', show: 'always' },
|
||||
{ id: 'member', href: 'member.web.html', label: '订阅', show: 'authed', elId: 'auth_member' },
|
||||
{ id: 'admin', href: 'admin/index.html', label: '管理', show: 'admin', elId: 'auth_admin' },
|
||||
];
|
||||
|
||||
function esc(s) {
|
||||
if (s == null) return '';
|
||||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function authBlock() {
|
||||
return '<div class="soon-portal-topbar__guest" id="auth_guest">' +
|
||||
'<a href="login.web.html" id="auth_login" class="soon-btn soon-btn--sm">登录</a>' +
|
||||
'<a href="register.web.html" id="auth_register" class="soon-btn soon-btn--sm soon-btn--ghost">注册</a>' +
|
||||
'</div>' +
|
||||
'<div class="soon-portal-topbar__account" id="auth_account" style="display:none">' +
|
||||
'<span id="auth_avatar" class="soon-portal-avatar" style="display:none"></span>' +
|
||||
'<span id="auth_identity" class="soon-portal-identity" style="display:none"></span>' +
|
||||
'</div>' +
|
||||
'<nav class="soon-portal-topbar__session" id="auth_session" style="display:none" aria-label="账户">' +
|
||||
'<a href="member.web.html" id="auth_member" class="soon-portal-topbar__link" style="display:none">订阅</a>' +
|
||||
'<a href="admin/index.html" id="auth_admin" class="soon-portal-topbar__link" style="display:none">管理</a>' +
|
||||
'<a href="#" id="auth_logout" class="soon-portal-topbar__link" style="display:none">退出</a>' +
|
||||
'</nav>';
|
||||
}
|
||||
|
||||
function renderLinks(links, active) {
|
||||
return links.map(function (lnk) {
|
||||
if (lnk.id === 'member' || lnk.id === 'admin') return '';
|
||||
var cls = 'soon-portal-topbar__link' + (active === lnk.id ? ' is-active' : '');
|
||||
return '<a href="' + esc(lnk.href) + '" class="' + cls + '">' + esc(lnk.label) + '</a>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function toolsBlock(lang, linksHtml) {
|
||||
return '<div class="soon-portal-topbar__tools">' +
|
||||
lang +
|
||||
(linksHtml ? '<nav class="soon-portal-topbar__links" aria-label="导航">' + linksHtml + '</nav>' : '') +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} opts
|
||||
* @param {string} [opts.variant] standard | auth | design
|
||||
* @param {string} [opts.active] home | member
|
||||
* @param {string} [opts.extraClass]
|
||||
*/
|
||||
function html(opts) {
|
||||
opts = opts || {};
|
||||
var base = opts.assetBase || '../';
|
||||
var variant = opts.variant || 'standard';
|
||||
var extraCls = opts.extraClass ? ' ' + opts.extraClass : '';
|
||||
|
||||
if (variant === 'auth') {
|
||||
return '<header class="soon-portal-topbar soon-auth-topbar' + extraCls + '">' +
|
||||
'<div class="soon-portal-topbar__brand"><a href="index.web.html">' +
|
||||
'<img src="' + esc(base) + 'assets/images/logo.png" alt=""><span>SoonDesign</span></a></div>' +
|
||||
'<div class="soon-portal-topbar__actions">' +
|
||||
toolsBlock('', '<a href="index.web.html" class="soon-portal-topbar__link">返回首页</a>') +
|
||||
'</div></header>';
|
||||
}
|
||||
|
||||
if (variant === 'design') {
|
||||
return '<header class="soon-portal-topbar soon-design-portal-bar' + extraCls + '">' +
|
||||
'<div class="soon-portal-topbar__brand"><a href="index.web.html">' +
|
||||
'<img src="' + esc(base) + 'assets/images/logo.png" alt=""><span>SoonDesign</span></a></div>' +
|
||||
'<div class="soon-portal-topbar__actions">' +
|
||||
toolsBlock('', '<a href="index.web.html" class="soon-portal-topbar__link">返回首页</a>') +
|
||||
authBlock() + '</div></header>';
|
||||
}
|
||||
|
||||
var active = opts.active || '';
|
||||
var showLang = opts.showLanguage !== false;
|
||||
var links = opts.links || STANDARD_LINKS;
|
||||
var lang = showLang
|
||||
? '<select id="language_select" class="soon-portal-topbar__lang" aria-label="语言">' +
|
||||
'<option value="zh">中文-简体</option><option value="ozh">中文-繁體</option><option value="en">English</option></select>'
|
||||
: '';
|
||||
|
||||
return '<header class="soon-portal-topbar' + extraCls + '">' +
|
||||
'<div class="soon-portal-topbar__brand"><a href="' + esc(opts.brandHref || 'index.web.html') + '">' +
|
||||
'<img src="' + esc(base) + 'assets/images/logo.png" alt=""><span>SoonDesign</span></a></div>' +
|
||||
'<div class="soon-portal-topbar__actions">' +
|
||||
toolsBlock(lang, renderLinks(links, active)) +
|
||||
authBlock() + '</div></header>';
|
||||
}
|
||||
|
||||
function mount(selector, opts) {
|
||||
var el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
||||
if (!el) return;
|
||||
el.innerHTML = html(opts);
|
||||
if (opts && opts.initLang !== false) {
|
||||
var sel = document.getElementById('language_select');
|
||||
if (sel) {
|
||||
sel.value = localStorage.getItem('lang') || 'zh';
|
||||
sel.onchange = function () { localStorage.setItem('lang', sel.value); };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.SoonPortalTopbar = { html: html, mount: mount };
|
||||
})();
|
||||
Reference in New Issue
Block a user