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>
94 lines
4.5 KiB
React
94 lines
4.5 KiB
React
// 移动版 · 头部 + 底部 Tab 栏
|
|
const mIcon = { fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', viewBox: '0 0 24 24' };
|
|
function MIconTask({ s = 22 }) {
|
|
return <svg {...mIcon} width={s} height={s}><rect x="4" y="4" width="16" height="16" rx="2.5" /><polyline points="8 12 11 15 16 9" /></svg>;
|
|
}
|
|
function MIconGate({ s = 22 }) {
|
|
return <svg {...mIcon} width={s} height={s}><path d="M12 3 L22 21 L2 21 Z" /><line x1="12" y1="10" x2="12" y2="14" /><line x1="12" y1="17.5" x2="12" y2="17.51" /></svg>;
|
|
}
|
|
function MIconEvent({ s = 22 }) {
|
|
return <svg {...mIcon} width={s} height={s}><polyline points="2 12 7 12 10 5 14 19 17 12 22 12" /></svg>;
|
|
}
|
|
function MIconGit({ s = 22 }) {
|
|
return <svg {...mIcon} width={s} height={s}><line x1="6" y1="3" x2="6" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>;
|
|
}
|
|
|
|
function MobileMark({ scale = 1.6 }) {
|
|
const ref = React.useRef(null);
|
|
React.useEffect(() => {
|
|
const MAP = ['.....LLLLL.....','...LLLLLLLLL...','..GGGGGGGGGGG..','..GGEEGGGEEGG..','..GGEEGGGEEGG..','..GGGGGGGGGGG..','...GGGGGGGGG...','...G..G.G..G...','...G..G.G..G...','..G...G.G...G..','..D...D.D...D..','.D...D...D...D.'];
|
|
const INK = { L: '#79ec94', G: '#5fdd7d', D: '#2e6b3d', E: '#070908' };
|
|
const ctx = ref.current.getContext('2d');
|
|
MAP.forEach((row, y) => [...row].forEach((ch, x) => {
|
|
if (INK[ch]) { ctx.fillStyle = INK[ch]; ctx.fillRect(x, y, 1, 1); }
|
|
}));
|
|
}, []);
|
|
return <canvas ref={ref} width="15" height="12"
|
|
style={{ width: 15 * scale, height: 12 * scale, imageRendering: 'pixelated', filter: 'drop-shadow(0 0 5px rgba(95,221,125,.4))' }} />;
|
|
}
|
|
|
|
function MobileHeader({ project, counts }) {
|
|
const { CountBadge } = window.MaestroDesignSystem_a6a290;
|
|
return (
|
|
<header style={{
|
|
display: 'flex', alignItems: 'center', gap: 10, padding: '12px 14px',
|
|
borderBottom: '1px solid var(--line)', background: 'var(--bg-deep)',
|
|
position: 'sticky', top: 0, zIndex: 20,
|
|
}}>
|
|
<MobileMark />
|
|
<div style={{ minWidth: 0 }}>
|
|
<div style={{ fontSize: 15, fontWeight: 700, letterSpacing: '.04em', whiteSpace: 'nowrap' }}>{project.name}</div>
|
|
<div style={{ fontSize: 10, color: 'var(--faint)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{project.branch} · {project.autonomy}</div>
|
|
</div>
|
|
<div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
|
|
<CountBadge kind="gate" count={counts.gate} />
|
|
<CountBadge kind="run" count={counts.run} />
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|
|
|
|
function MobileTabBar({ tab, setTab, gateCount }) {
|
|
const tabs = [
|
|
{ id: 'tasks', label: '任务', icon: MIconTask },
|
|
{ id: 'gates', label: '审批', icon: MIconGate, badge: gateCount },
|
|
{ id: 'events', label: '事件', icon: MIconEvent },
|
|
{ id: 'projects', label: '项目', icon: MIconGit },
|
|
];
|
|
return (
|
|
<nav style={{
|
|
display: 'grid', gridTemplateColumns: 'repeat(4,1fr)',
|
|
borderTop: '1px solid var(--line)', background: 'var(--bg-deep)',
|
|
position: 'sticky', bottom: 0, zIndex: 20,
|
|
}}>
|
|
{tabs.map((t) => {
|
|
const active = tab === t.id;
|
|
const Icon = t.icon;
|
|
return (
|
|
<button key={t.id} onClick={() => setTab(t.id)} style={{
|
|
fontFamily: 'var(--mono)', background: 'transparent', border: 'none', cursor: 'pointer',
|
|
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3,
|
|
padding: '9px 0 10px', minHeight: 56, position: 'relative',
|
|
color: active ? 'var(--green)' : 'var(--faint)',
|
|
textShadow: active ? '0 0 10px rgba(95,221,125,.45)' : 'none',
|
|
}}>
|
|
<span style={{ position: 'relative', filter: active ? 'drop-shadow(0 0 6px rgba(95,221,125,.5))' : 'none' }}>
|
|
<Icon />
|
|
{t.badge ? (
|
|
<span style={{
|
|
position: 'absolute', top: -4, right: -8, minWidth: 15, height: 15,
|
|
fontSize: 9.5, fontWeight: 700, lineHeight: '15px', textAlign: 'center',
|
|
background: 'var(--violet)', color: 'var(--bg-deep)', borderRadius: 8, padding: '0 3px',
|
|
}}>{t.badge}</span>
|
|
) : null}
|
|
</span>
|
|
<span style={{ fontSize: 10, letterSpacing: '.12em' }}>{t.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</nav>
|
|
);
|
|
}
|
|
|
|
Object.assign(window, { MaestroMobHeader: MobileHeader, MaestroMobTabBar: MobileTabBar });
|