5a1e185b1a
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>
91 lines
4.8 KiB
HTML
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>
|