All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6s
页面 (电商AI平台/) - account / team / settings / index / products / projects: 累积迭代 - restraint.css: 设计 token 补充 - login.html / register.html: 新增登录注册页 - _ARCHIVE.md: 归档说明 Next.js 工程骨架 - app/ + components/: 新一代 SPA 雏形 (page / layout / sidebar / topbar / GridBg / Icon) - package.json / package-lock.json / next.config.mjs / tsconfig.json / postcss.config.mjs / next-env.d.ts 历史归档 / 文档 - v1/: 原 V1 静态稿镜像 (含 mockup-A/B/C) - PRD.md / deployment-guide.md / _check.html - ui参考/ / screenshots/ 杂项 - .gitignore 调整 - 删除根 README.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
183 lines
8.1 KiB
JavaScript
183 lines
8.1 KiB
JavaScript
/* ============================================================
|
|
流·Studio · Shell renderer
|
|
渲染 sidebar / topbar / 网格背景装饰 / Toast / Modal helpers
|
|
每个页面调用 Shell.render({ active, crumbs, balance, topActions })
|
|
============================================================ */
|
|
|
|
const NAV = [
|
|
{
|
|
id: 'dashboard', label: '工作台', href: 'index.html',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M3 12 12 3l9 9"/><path d="M5 10v10h14V10"/></svg>'
|
|
},
|
|
{
|
|
id: 'products', label: '商品库', href: 'products.html', badge: '12',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="m3 7 9-4 9 4-9 4-9-4z"/><path d="m3 12 9 4 9-4M3 17l9 4 9-4"/></svg>'
|
|
},
|
|
{
|
|
id: 'projects', label: '视频项目', href: 'projects.html',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="18" height="16"/><path d="M7 4v16M16 4v16M3 9h18M3 15h18"/></svg>'
|
|
},
|
|
{
|
|
id: 'library', label: '资产库', href: 'library.html',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="4" width="6" height="16"/><rect x="11" y="4" width="4" height="16"/><rect x="17" y="6" width="4" height="14"/></svg>'
|
|
},
|
|
{
|
|
id: 'account', label: '账户', href: 'account.html',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18M16 14h2"/></svg>'
|
|
},
|
|
{
|
|
id: 'settings', label: '设置', href: '#',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8 2 2 0 0 1-2.8 2.8 1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5 2 2 0 0 1-4 0 1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3 2 2 0 0 1-2.8-2.8 1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1 2 2 0 0 1 0-4 1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8 2 2 0 0 1 2.8-2.8 1.7 1.7 0 0 0 1.8.3 1.7 1.7 0 0 0 1-1.5 2 2 0 0 1 4 0 1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3 2 2 0 0 1 2.8 2.8 1.7 1.7 0 0 0-.3 1.8 1.7 1.7 0 0 0 1.5 1 2 2 0 0 1 0 4 1.7 1.7 0 0 0-1.5 1.1Z"/></svg>'
|
|
}
|
|
];
|
|
|
|
const TEAM_NAV = {
|
|
label: '团队', icon:
|
|
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="9" cy="8" r="3"/><circle cx="17" cy="9" r="2.5"/><path d="M3 19c0-3 2.7-5 6-5s6 2 6 5M14 19c.5-2.4 2.4-4 5-4 .8 0 1.5.2 2 .5"/></svg>',
|
|
badge: 'V1.5'
|
|
};
|
|
|
|
window.Shell = {
|
|
render({ active = '', crumbs = [], balance = '¥327.40', topActions = '' } = {}) {
|
|
const navHtml = NAV.map(n => `
|
|
<a href="${n.href}" class="${active === n.id ? 'active' : ''}">
|
|
${n.icon}
|
|
<span>${n.label}</span>
|
|
${n.badge ? `<span class="pill-mini">${n.badge}</span>` : ''}
|
|
</a>
|
|
`).join('');
|
|
|
|
const sidebar = `
|
|
<aside class="sidebar">
|
|
<div class="brand">
|
|
<div class="flame"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2c1 3 4 5 4 9a4 4 0 0 1-4 4 4 4 0 0 1-4-4 5 5 0 0 1 1.5-3.5C10.5 6 11.5 4 12 2zm-1 13c0 2 1 3 1 5 1-1 3-2 3-5 0-1.5-1-2-2-3-1 1-2 2-2 3z"/></svg></div>
|
|
<div><div class="name">流·Studio</div></div>
|
|
</div>
|
|
<div class="search-box" onclick="document.getElementById('global-search').focus()">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
|
<input id="global-search" placeholder="搜索"/>
|
|
<span class="kbd">⌘K</span>
|
|
</div>
|
|
<div class="nav-section">主要</div>
|
|
<nav>${navHtml}</nav>
|
|
<div class="nav-section">协作</div>
|
|
<nav>
|
|
<a class="disabled" title="V1.5 上线,敬请期待">
|
|
${TEAM_NAV.icon}
|
|
<span>${TEAM_NAV.label}</span>
|
|
<span class="pill-mini">${TEAM_NAV.badge}</span>
|
|
</a>
|
|
</nav>
|
|
<div class="aside-foot">
|
|
<div class="user" onclick="Shell.toast('账户菜单', 'li@shop.com')">
|
|
<div class="av">李</div>
|
|
<div class="em">小李的店</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
`;
|
|
|
|
const crumbHtml = crumbs.length ? `
|
|
<div class="crumbs">
|
|
${crumbs.map((c, i) => {
|
|
const last = i === crumbs.length - 1;
|
|
const sep = i > 0 ? '<span class="sep">/</span>' : '';
|
|
if (last) return `${sep}<span class="here">${c.label}</span>`;
|
|
return `${sep}<a href="${c.href || '#'}">${c.label}</a>`;
|
|
}).join('')}
|
|
</div>
|
|
` : '';
|
|
|
|
const topbar = `
|
|
<header class="topbar">
|
|
${crumbHtml}
|
|
<div class="right">
|
|
<span class="balance-chip" onclick="location.href='account.html'">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 7v10M9 10h4a1.5 1.5 0 0 1 0 3h-3a1.5 1.5 0 0 0 0 3h4"/></svg>
|
|
余额 <strong>${balance}</strong>
|
|
</span>
|
|
<button class="icon-btn" onclick="Shell.toast('通知中心', '3 条未读')">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21a2 2 0 0 0 4 0"/></svg>
|
|
<span class="dot-noti"></span>
|
|
</button>
|
|
${topActions}
|
|
</div>
|
|
</header>
|
|
`;
|
|
|
|
const decorations = `
|
|
<div class="grid-bg"></div>
|
|
<pre class="scatter" style="top:96px;left:280px"> · · +
|
|
· +XX+
|
|
+XXXX·
|
|
+X· </pre>
|
|
<pre class="scatter" style="top:340px;right:96px">+ · ·
|
|
XX· ·
|
|
·XXXX·+
|
|
·++· </pre>
|
|
<pre class="scatter" style="bottom:160px;left:42%"> · +
|
|
+·XX·
|
|
·X+ ·
|
|
· </pre>
|
|
<pre class="scatter" style="top:580px;left:60px"> +X·
|
|
·XX·
|
|
+·X·+</pre>
|
|
<span class="sq-mark" style="top:238px;left:478px"></span>
|
|
<span class="sq-mark" style="top:478px;left:1198px"></span>
|
|
<span class="sq-mark" style="bottom:300px;left:238px"></span>
|
|
<span class="sq-mark" style="top:718px;right:240px"></span>
|
|
<span class="tag-corner" style="top:158px;left:34px">[ 200 OK ]</span>
|
|
<span class="tag-corner" style="top:158px;right:34px">[ /v2 ]</span>
|
|
<span class="tag-corner" style="bottom:36px;left:34px">[ .MP4 · 9:16 ]</span>
|
|
<span class="tag-corner" style="bottom:36px;right:34px">[ STUDIO ]</span>
|
|
`;
|
|
|
|
const toastHtml = `
|
|
<div class="toast" id="__toast">
|
|
<div class="ic-t"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="txt" id="__toast-txt">操作成功<span class="mono">[ 200 OK ]</span></div>
|
|
</div>
|
|
`;
|
|
|
|
const app = document.createElement('div');
|
|
app.className = 'app';
|
|
app.innerHTML = sidebar + `<main>${decorations}${topbar}<div class="content" id="page-content"></div></main>`;
|
|
|
|
const src = document.getElementById('page');
|
|
document.body.prepend(app);
|
|
if (src) {
|
|
document.getElementById('page-content').innerHTML = src.innerHTML;
|
|
src.remove();
|
|
}
|
|
document.body.insertAdjacentHTML('beforeend', toastHtml);
|
|
|
|
document.addEventListener('keydown', e => {
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
e.preventDefault();
|
|
document.getElementById('global-search')?.focus();
|
|
}
|
|
});
|
|
},
|
|
|
|
toast(text, mono) {
|
|
const t = document.getElementById('__toast');
|
|
const txt = document.getElementById('__toast-txt');
|
|
if (!t || !txt) return;
|
|
txt.innerHTML = text + (mono ? `<span class="mono">[ ${mono} ]</span>` : '');
|
|
t.classList.add('show');
|
|
clearTimeout(this._tt);
|
|
this._tt = setTimeout(() => t.classList.remove('show'), 2400);
|
|
},
|
|
|
|
openModal(id) { document.getElementById(id)?.classList.add('show'); },
|
|
closeModal(id) { document.getElementById(id)?.classList.remove('show'); },
|
|
openDrawer(id) {
|
|
document.getElementById(id)?.classList.add('show');
|
|
document.getElementById(id + '-bg')?.classList.add('show');
|
|
},
|
|
closeDrawer(id) {
|
|
document.getElementById(id)?.classList.remove('show');
|
|
document.getElementById(id + '-bg')?.classList.remove('show');
|
|
}
|
|
};
|