Files
maestro/design/ui_kits/console_mobile/index.html
T
wangjia 5a1e185b1a docs(design): 解压 Maestro Design System 到 design/(重构参照材料)
phosphor console v1.1 设计系统(从现网 web/style.css 演化):tokens(colors/effects/
typography/fonts) + components(core/forms/surfaces .jsx) + ui_kits(console + console_mobile)
+ assets/icons(15 双色 SVG) + guidelines + CLAUDE/SKILL/readme。供 B1–B6 重构任务对照实现。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:29:07 +08:00

91 lines
4.8 KiB
HTML

<!-- @dsCard group="Console" viewport="390x760" name="移动版调度台" subtitle="单栏 + 底部 Tab(任务/审批/事件/项目)· 触控目标 ≥44px · 可点击" -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MAESTRO · 移动版调度台</title>
<link rel="stylesheet" href="../../styles.css">
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script src="../../_ds_bundle.js"></script>
<script src="../console/data.js"></script>
<style>
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body { background: var(--bg); color: var(--ink); font-family: var(--mono); font-size: 13px; line-height: 1.55; display: flex; justify-content: center; }
body::before {
content: ''; position: fixed; inset: 0; z-index: 999; pointer-events: none;
background:
repeating-linear-gradient(0deg, rgba(0,0,0,.16) 0 1px, transparent 1px 3px),
radial-gradient(ellipse at 50% 40%, transparent 55%, rgba(0,0,0,.5));
opacity: .5;
}
::selection { background: var(--green-dim); color: #fff; }
#root { width: 100%; max-width: 430px; height: 100vh; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel" src="MobileChrome.jsx"></script>
<script type="text/babel" src="MobileViews.jsx"></script>
<script type="text/babel">
const { Toast } = window.MaestroDesignSystem_a6a290;
const M = window.MAESTRO_MOCK;
function App() {
const [tab, setTab] = React.useState('tasks');
const [currentId, setCurrentId] = React.useState('p1');
const [approvals, setApprovals] = React.useState(M.approvals);
const [events, setEvents] = React.useState(M.events);
const [toasts, setToasts] = React.useState([]);
const project = M.projects.find((p) => p.id === currentId);
const isMain = currentId === 'p1';
const tasks = isMain ? M.tasks : [];
const agents = isMain ? M.agents : [];
const gates = isMain ? approvals : [];
const toast = (kind, text) => {
const id = Date.now();
setToasts((t) => [...t, { id, kind, text }]);
setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 2600);
};
const now = () => new Date().toTimeString().slice(0, 8);
const decide = (a, action, reason) => {
setApprovals((list) => list.filter((x) => x.id !== a.id));
if (action === 'accept') {
setEvents((ev) => [{ type: 'approval.granted', time: now(), detail: a.title + ' · ' + a.gate + '_review 通过' }, ...ev]);
toast('ok', '已通过 · ' + a.title);
} else {
setEvents((ev) => [{ type: 'approval.rejected', time: now(), detail: a.title + ' ↳ ' + reason }, ...ev]);
toast('warn', '已驳回 · 退回重做');
}
};
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', borderLeft: '1px solid var(--line-soft)', borderRight: '1px solid var(--line-soft)', background: 'var(--bg)' }}>
<window.MaestroMobHeader project={project} counts={{ gate: gates.length, run: agents.length }} />
<main style={{ flex: 1, overflowY: 'auto', paddingBottom: 16 }}>
{tab === 'tasks' ? (
tasks.length
? <window.MaestroMobTasks tasks={tasks} agents={agents} />
: <div style={{ margin: '24px 14px', padding: '38px 0', textAlign: 'center', color: 'var(--faint)', border: '1px dashed var(--line)', borderRadius: 6, fontSize: 12 }}>该项目暂无任务</div>
) : null}
{tab === 'gates' ? <window.MaestroMobGates approvals={gates} onDecide={decide} /> : null}
{tab === 'events' ? <window.MaestroMobEvents events={events} /> : null}
{tab === 'projects' ? <window.MaestroMobProjects projects={M.projects} currentId={currentId} project={project}
onSelect={(id) => { setCurrentId(id); toast('ok', '已切换 · ' + M.projects.find((p) => p.id === id).name); }} /> : null}
</main>
<window.MaestroMobTabBar tab={tab} setTab={setTab} gateCount={gates.length} />
<div style={{ position: 'fixed', bottom: 72, left: '50%', transform: 'translateX(-50%)', zIndex: 1200, display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'center', width: 'max-content', maxWidth: '90vw' }}>
{toasts.map((t) => <Toast key={t.id} kind={t.kind}>{t.text}</Toast>)}
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>