/** * 登录页(ADR-010)—— 两份原型都没有这一屏,是新造的。 * * 它在两个台面**之前**渲染,拿不到任一台面的皮肤(工作台补充样式通篇作用域 `.proto-root`, * 治理台压根不加载它),所以视觉全部由 `login.css` 自带 —— 类名一律 `jc-login-*`, * 不蹭原型的类,免得在另一台面上落空。 */ import { useState } from 'react'; import type { Session } from '@contracts'; import { loginRoute } from '@contracts'; import { Ico } from '@web-shared/Ico.tsx'; import { ApiCallError, call } from '@web-shared/typed-fetch.ts'; export function LoginPage({ onLogin }: { onLogin(session: Session): void }) { const [account, setAccount] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(); const [busy, setBusy] = useState(false); const ready = account.trim() !== '' && password !== ''; const submit = async () => { if (!ready || busy) return; setBusy(true); setError(undefined); try { onLogin(await call(loginRoute, { body: { account: account.trim(), password } })); } catch (e) { // 后端只回一句话(不区分账号不存在 / 已停用 / 口令错),这里照实呈现,不替它猜 setError(e instanceof ApiCallError ? e.message : '登录失败,请稍后再试'); setBusy(false); } }; return (
{ e.preventDefault(); void submit(); }} >

JCAI 任务操作系统

登录后进入你的工位

{error !== undefined && (

{error}

)}

初始口令由部署时的 JCAI_SEED_PASSWORD 决定

); }