Files
memind/design-preview/ops-console-preview.html
John 2e14873f2d Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.
Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 15:04:43 -07:00

468 lines
21 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Console · 架构方案预览</title>
<style>
:root {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color-scheme: dark;
--bg: #0c1016;
--panel: #121820;
--panel-2: #171f2b;
--line: #243041;
--text: #edf2f8;
--muted: #8fa3bf;
--faint: #5a6d84;
--accent: #4d93ff;
--accent-dim: rgba(77, 147, 255, 0.14);
--ok: #42c88a;
--warn: #ddb945;
--danger: #ef7b7b;
--index-w: 300px;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; background: var(--bg); color: var(--text); }
/* ===== 新架构:Console Shell(非传统后台菜单) ===== */
.console { display: flex; flex-direction: column; height: 100%; }
.top {
height: 52px; padding: 0 20px;
display: flex; align-items: center; gap: 16px;
border-bottom: 1px solid var(--line);
background: var(--panel);
}
.logo { font-size: 14px; font-weight: 600; white-space: nowrap; }
.logo span { color: var(--muted); font-weight: 400; margin-left: 8px; }
.top-search {
flex: 1; max-width: 420px;
padding: 8px 12px; border-radius: 8px;
border: 1px solid var(--line); background: var(--bg);
color: var(--text); font-size: 13px;
}
.top-search::placeholder { color: var(--faint); }
.top-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; }
.link { font-size: 13px; color: var(--muted); background: none; border: none; cursor: pointer; }
.link:hover { color: var(--text); }
.gear {
width: 34px; height: 34px; border-radius: 8px;
border: 1px solid var(--line); background: var(--bg);
color: var(--muted); cursor: pointer;
}
.gear:hover { color: var(--text); border-color: #355070; }
.workspace { flex: 1; display: flex; min-height: 0; }
/* 左:用户索引(主导航对象,不是功能菜单) */
.index {
width: var(--index-w);
border-right: 1px solid var(--line);
background: var(--panel);
display: flex; flex-direction: column;
}
.index-head {
padding: 14px 14px 10px;
border-bottom: 1px solid var(--line);
}
.index-head input {
width: 100%; padding: 8px 10px; border-radius: 8px;
border: 1px solid var(--line); background: var(--bg);
color: var(--text); font-size: 13px;
}
.index-meta {
margin-top: 8px; font-size: 11px; color: var(--faint);
display: flex; justify-content: space-between;
}
.index-list { flex: 1; overflow: auto; }
.user-row {
display: block; width: 100%; text-align: left;
padding: 12px 14px; border: none; border-bottom: 1px solid rgba(36,48,65,.5);
background: none; color: inherit; cursor: pointer;
}
.user-row:hover { background: rgba(255,255,255,.03); }
.user-row.active { background: var(--accent-dim); border-left: 3px solid var(--accent); padding-left: 11px; }
.user-row .name { font-size: 13px; font-weight: 500; }
.user-row .sub { font-size: 11px; color: var(--muted); margin-top: 3px; }
.user-row .sub.warn { color: var(--warn); }
.index-foot {
padding: 10px 14px; border-top: 1px solid var(--line);
font-size: 11px; color: var(--faint);
}
/* 右:工作区(随选中用户切换模式) */
.stage { flex: 1; overflow: auto; padding: 28px 32px 40px; }
.mode-label {
font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
color: var(--faint); margin-bottom: 8px;
}
.mode-title { font-size: 22px; font-weight: 600; margin: 0 0 6px; letter-spacing: -.02em; }
.mode-desc { font-size: 13px; color: var(--muted); margin: 0 0 24px; }
/* 工作台模式 */
.pulse {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px;
margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px solid var(--line);
}
.pulse-item .n { font-size: 26px; font-weight: 600; }
.pulse-item .l { font-size: 12px; color: var(--muted); margin-top: 4px; }
.queue { max-width: 640px; }
.queue h3 { font-size: 13px; font-weight: 600; margin: 0 0 12px; color: var(--muted); }
.queue-item {
display: flex; align-items: center; justify-content: space-between;
padding: 12px 0; border-bottom: 1px solid var(--line);
font-size: 13px; cursor: pointer;
}
.queue-item:hover .q-action { color: var(--accent); }
.q-action { color: var(--faint); font-size: 12px; }
/* 用户模式 */
.ctx-bar {
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
margin-bottom: 20px;
}
.ctx-tabs { display: flex; gap: 4px; }
.ctx-tab {
padding: 7px 14px; border-radius: 999px; border: none;
background: none; color: var(--muted); font-size: 13px; cursor: pointer;
}
.ctx-tab.active { background: var(--accent-dim); color: var(--text); }
.ctx-actions { margin-left: auto; display: flex; gap: 8px; }
.btn {
padding: 7px 12px; border-radius: 7px; font-size: 12px;
border: 1px solid var(--line); background: var(--panel-2); color: var(--text); cursor: pointer;
}
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-danger { color: var(--danger); border-color: rgba(239,123,123,.35); }
.ctx-panel { display: none; max-width: 720px; }
.ctx-panel.active { display: block; }
.kv { display: grid; grid-template-columns: 120px 1fr; gap: 8px 16px; font-size: 13px; margin-bottom: 24px; }
.kv .k { color: var(--muted); }
.kv .v.mono { font-family: ui-monospace, Menlo, monospace; font-size: 12px; }
.inline-form { display: grid; gap: 12px; max-width: 360px; margin-top: 8px; }
.inline-form label { font-size: 12px; color: var(--muted); display: block; margin-bottom: 5px; }
.inline-form input, .inline-form select {
width: 100%; padding: 9px 10px; border-radius: 7px;
border: 1px solid var(--line); background: var(--panel); color: var(--text); font-size: 13px;
}
.timeline { font-size: 13px; }
.timeline-row {
display: grid; grid-template-columns: 72px 1fr auto;
gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line);
}
.timeline-row .t { color: var(--faint); font-size: 12px; }
.perm-list { max-width: 480px; }
.perm-row {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 13px;
}
.sw { width: 30px; height: 16px; border-radius: 999px; background: var(--accent); position: relative; }
.sw::after { content:''; position:absolute; top:2px; right:2px; width:12px; height:12px; border-radius:50%; background:#fff; }
.sw.off { background:#3a4555; }
.sw.off::after { right:auto; left:2px; }
/* 系统抽屉(全局配置,不占主导航) */
.overlay {
position: fixed; inset: 0; background: rgba(0,0,0,.55);
display: none; align-items: stretch; justify-content: flex-end; z-index: 50;
}
.overlay.open { display: flex; }
.drawer {
width: min(480px, 100vw); background: var(--panel);
border-left: 1px solid var(--line);
display: flex; flex-direction: column;
}
.drawer-head {
padding: 16px 18px; border-bottom: 1px solid var(--line);
display: flex; justify-content: space-between; align-items: center;
}
.drawer-head h2 { margin: 0; font-size: 15px; }
.drawer-tabs { display: flex; gap: 6px; padding: 12px 18px 0; border-bottom: 1px solid var(--line); }
.drawer-tab {
padding: 8px 12px 10px; border: none; background: none;
color: var(--muted); font-size: 12px; cursor: pointer;
border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.drawer-tab.active { color: var(--text); border-bottom-color: var(--accent); }
.drawer-body { padding: 18px; overflow: auto; flex: 1; }
.drawer-panel { display: none; }
.drawer-panel.active { display: block; }
.arch-note {
margin-top: 36px; padding: 16px 18px;
border: 1px solid #2a4060; border-radius: 10px;
background: rgba(18, 30, 48, .6);
max-width: 680px; font-size: 12px; color: var(--muted); line-height: 1.75;
}
.arch-note strong { color: #9ec5ff; }
.hidden { display: none !important; }
@media (max-width: 800px) {
.index { width: 100%; max-height: 40vh; border-right: none; border-bottom: 1px solid var(--line); }
.workspace { flex-direction: column; }
.pulse { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="console">
<!-- 顶栏:只有搜索 + 系统入口,没有功能菜单 -->
<header class="top">
<div class="logo">Console<span>运营</span></div>
<input class="top-search" id="globalSearch" placeholder="搜索用户、流水 ID… 回车定位" />
<div class="top-actions">
<button class="link" type="button">返回用户端</button>
<button class="gear" type="button" id="openSystem" title="系统配置"></button>
</div>
</header>
<div class="workspace">
<!-- 左栏 = 用户索引(不是菜单) -->
<aside class="index">
<div class="index-head">
<input id="filterUsers" placeholder="筛选用户…" />
<div class="index-meta"><span id="userCount">4 位用户</span><span>按最近活跃</span></div>
</div>
<div class="index-list" id="userList"></div>
<div class="index-foot">点选用户 → 右侧进入该用户工作区</div>
</aside>
<!-- 右栏 = 双模式工作区 -->
<main class="stage" id="stage">
<!-- 模式 A:未选用户 → 工作台 -->
<div id="modeDesk">
<div class="mode-label">模式 A · 工作台</div>
<h1 class="mode-title">今日平台</h1>
<p class="mode-desc">未选中用户时,只看全局态势与待处理事项</p>
<div class="pulse">
<div class="pulse-item"><div class="n">128</div><div class="l">7 日活跃</div></div>
<div class="pulse-item"><div class="n">¥386</div><div class="l">今日扣费</div></div>
<div class="pulse-item"><div class="n">7</div><div class="l">余额不足</div></div>
</div>
<div class="queue">
<h3>待处理</h3>
<div class="queue-item" data-pick="demo">
<span>@demo 余额 ¥0.30,账户已禁用</span>
<span class="q-action">去处理 →</span>
</div>
<div class="queue-item" data-pick="john">
<span>@john 等 6 人余额 &lt; ¥1</span>
<span class="q-action">批量查看 →</span>
</div>
</div>
<div class="arch-note">
<strong>新架构要点</strong><br>
① 取消「功能侧栏菜单」——运营动作围绕<strong>用户对象</strong>展开,左栏是用户索引不是模块导航<br>
② 右栏两种模式:<strong>工作台</strong>(无人选中)/ <strong>用户工作区</strong>(选中后)<br>
③ LLM、全局策略进<strong>系统抽屉</strong>(⚙),低频配置不占主界面<br>
④ 路由建议:<code>/ops</code> 独立 ShellURL 带 <code>?u=john</code> 可 deep link
</div>
</div>
<!-- 模式 B:选中用户 → 上下文工作区 -->
<div id="modeUser" class="hidden">
<div class="mode-label">模式 B · 用户工作区</div>
<h1 class="mode-title" id="ctxName">@john</h1>
<p class="mode-desc" id="ctxMeta">John · 正常 · ¥128.50</p>
<div class="ctx-bar">
<div class="ctx-tabs">
<button class="ctx-tab active" data-ctx="profile">资料</button>
<button class="ctx-tab" data-ctx="billing">计费</button>
<button class="ctx-tab" data-ctx="perm">权限</button>
<button class="ctx-tab" data-ctx="log">记录</button>
</div>
<div class="ctx-actions">
<button class="btn btn-primary" type="button">充值</button>
<button class="btn btn-danger" type="button">禁用</button>
</div>
</div>
<div class="ctx-panel active" data-ctx-panel="profile">
<div class="kv">
<div class="k">用户名</div><div class="v" id="f-username">john</div>
<div class="k">显示名</div><div class="v" id="f-display">John</div>
<div class="k">状态</div><div class="v" id="f-status">active</div>
<div class="k">工作目录</div><div class="v mono" id="f-workspace">/data/.../john</div>
<div class="k">Space</div><div class="v">2.1 / 5 MB</div>
</div>
<button class="btn" type="button">重置密码</button>
</div>
<div class="ctx-panel" data-ctx-panel="billing">
<div class="inline-form">
<div><label>充值金额(元)</label><input value="50" /></div>
<div><label>备注</label><input placeholder="写入流水" /></div>
<button class="btn btn-primary" type="button" style="width:fit-content">确认充值</button>
</div>
<h3 style="font-size:13px;color:var(--muted);margin:28px 0 8px">最近计费</h3>
<div class="timeline">
<div class="timeline-row"><span class="t">10:22</span><span>Token 扣费</span><span>−¥0.42</span></div>
<div class="timeline-row"><span class="t">昨天</span><span>运营充值</span><span>+¥50</span></div>
</div>
</div>
<div class="ctx-panel" data-ctx-panel="perm">
<p style="font-size:12px;color:var(--muted);margin:0 0 12px">相对角色默认的覆盖项</p>
<div class="perm-list">
<div class="perm-row"><span>mindspace.publish</span><span class="sw"></span></div>
<div class="perm-row"><span>chat.save_page</span><span class="sw"></span></div>
<div class="perm-row"><span>static-page-publish</span><span class="sw off"></span></div>
</div>
</div>
<div class="ctx-panel" data-ctx-panel="log">
<div class="timeline">
<div class="timeline-row"><span class="t">10:22</span><span>对话扣费 1680 tokens</span><span></span></div>
<div class="timeline-row"><span class="t">09:01</span><span>登录成功</span><span></span></div>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- 系统抽屉:LLM / 全局策略 / 平台参数 -->
<div class="overlay" id="systemOverlay">
<aside class="drawer">
<div class="drawer-head">
<h2>系统配置</h2>
<button class="link" type="button" id="closeSystem">关闭</button>
</div>
<div class="drawer-tabs">
<button class="drawer-tab active" data-sys="llm">LLM</button>
<button class="drawer-tab" data-sys="defaults">默认策略</button>
<button class="drawer-tab" data-sys="platform">平台</button>
</div>
<div class="drawer-body">
<div class="drawer-panel active" data-sys-panel="llm">
<div class="perm-row"><span>DeepSeek 生产</span><span style="font-size:11px;color:var(--ok)">使用中</span></div>
<div class="perm-row"><span>备用 OpenAI</span><button class="btn" style="padding:4px 8px">选用</button></div>
<button class="btn btn-primary" style="margin-top:14px" type="button">同步 goosed</button>
</div>
<div class="drawer-panel" data-sys-panel="defaults">
<p style="font-size:12px;color:var(--muted);margin:0 0 10px">角色 user 的默认 Capabilities / Skills / Policies</p>
<div class="perm-row"><span>mindspace.read</span><span class="sw"></span></div>
<div class="perm-row"><span>require_scan</span><span class="sw"></span></div>
</div>
<div class="drawer-panel" data-sys-panel="platform">
<div class="inline-form">
<div><label>新用户赠送(元)</label><input value="10" /></div>
<div><label>Space 默认 MB</label><input value="5" /></div>
<button class="btn btn-primary" type="button" style="width:fit-content">保存</button>
</div>
</div>
</div>
</aside>
</div>
<script>
const USERS = [
{ id: 'john', name: 'John', status: 'active', balance: '128.50', warn: false, workspace: '/data/mindspace/users/john' },
{ id: 'demo', name: '演示', status: 'disabled', balance: '0.30', warn: true, workspace: '/data/mindspace/users/demo' },
{ id: 'alice', name: 'Alice', status: 'active', balance: '0.80', warn: true, workspace: '/data/mindspace/users/alice' },
{ id: 'admin', name: '管理员', status: 'active', balance: '—', warn: false, workspace: '/data/mindspace/users/admin', role: 'admin' },
];
let selected = null;
function renderUsers(filter = '') {
const q = filter.toLowerCase();
const list = USERS.filter(u => u.id.includes(q) || u.name.toLowerCase().includes(q));
const el = document.getElementById('userList');
el.innerHTML = list.map(u => `
<button class="user-row${selected === u.id ? ' active' : ''}" data-id="${u.id}" type="button">
<div class="name">${u.name}</div>
<div class="sub${u.warn ? ' warn' : ''}">@${u.id} · ¥${u.balance}${u.warn ? ' · 余额低' : ''}</div>
</button>
`).join('');
document.getElementById('userCount').textContent = list.length + ' 位用户';
el.querySelectorAll('.user-row').forEach(btn => {
btn.addEventListener('click', () => selectUser(btn.dataset.id));
});
}
function selectUser(id) {
selected = id;
const u = USERS.find(x => x.id === id);
if (!u) return;
document.getElementById('modeDesk').classList.add('hidden');
document.getElementById('modeUser').classList.remove('hidden');
document.getElementById('ctxName').textContent = '@' + u.id;
document.getElementById('ctxMeta').textContent = `${u.name} · ${u.status === 'active' ? '正常' : '禁用'} · ¥${u.balance}`;
document.getElementById('f-username').textContent = u.id;
document.getElementById('f-display').textContent = u.name;
document.getElementById('f-status').textContent = u.status;
document.getElementById('f-workspace').textContent = u.workspace;
renderUsers(document.getElementById('filterUsers').value);
history.replaceState(null, '', '?u=' + id);
}
function clearUser() {
selected = null;
document.getElementById('modeDesk').classList.remove('hidden');
document.getElementById('modeUser').classList.add('hidden');
renderUsers(document.getElementById('filterUsers').value);
history.replaceState(null, '', location.pathname);
}
document.getElementById('filterUsers').addEventListener('input', e => renderUsers(e.target.value));
document.getElementById('globalSearch').addEventListener('keydown', e => {
if (e.key === 'Enter') {
const q = e.target.value.replace('@', '').trim().toLowerCase();
const hit = USERS.find(u => u.id.includes(q) || u.name.toLowerCase().includes(q));
if (hit) selectUser(hit.id);
}
});
document.querySelectorAll('.queue-item').forEach(el => {
el.addEventListener('click', () => selectUser(el.dataset.pick));
});
document.querySelectorAll('.ctx-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.ctx-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.ctx-panel').forEach(p => p.classList.remove('active'));
document.querySelector(`[data-ctx-panel="${tab.dataset.ctx}"]`).classList.add('active');
});
});
document.getElementById('openSystem').addEventListener('click', () => document.getElementById('systemOverlay').classList.add('open'));
document.getElementById('closeSystem').addEventListener('click', () => document.getElementById('systemOverlay').classList.remove('open'));
document.getElementById('systemOverlay').addEventListener('click', e => {
if (e.target.id === 'systemOverlay') document.getElementById('systemOverlay').classList.remove('open');
});
document.querySelectorAll('.drawer-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.drawer-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
document.querySelectorAll('.drawer-panel').forEach(p => p.classList.remove('active'));
document.querySelector(`[data-sys-panel="${tab.dataset.sys}"]`).classList.add('active');
});
});
// 双击顶栏标题返回工作台
document.querySelector('.logo').addEventListener('dblclick', clearUser);
renderUsers();
const params = new URLSearchParams(location.search);
if (params.get('u')) selectUser(params.get('u'));
</script>
</body>
</html>