基线:鉴权会话+工作组+工作台三区修复+编排工作区+产品设计文档

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-27 20:43:35 +08:00
co-authored by Claude Fable 5
parent 61fe181678
commit ac85ecf2ea
119 changed files with 16634 additions and 1246 deletions
+2
View File
@@ -0,0 +1,2 @@
/** feature: auth —— 唯一出口(编码规范 §9.1)。 */
export { LoginPage } from './internal/LoginPage.tsx';
@@ -0,0 +1,85 @@
/**
* 登录页(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<string>();
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<Session>(loginRoute, { body: { account: account.trim(), password } }));
} catch (e) {
// 后端只回一句话(不区分账号不存在 / 已停用 / 口令错),这里照实呈现,不替它猜
setError(e instanceof ApiCallError ? e.message : '登录失败,请稍后再试');
setBusy(false);
}
};
return (
<div className="jc-login">
<form
className="jc-login-card"
onSubmit={(e) => {
e.preventDefault();
void submit();
}}
>
<span className="jc-login-mark">
<Ico name="bot" s={24} />
</span>
<h1 className="jc-login-title">JCAI 任务操作系统</h1>
<p className="jc-login-sub">登录后进入你的工位</p>
<label className="jc-login-field">
<span>账号</span>
<input
value={account}
autoFocus
autoComplete="username"
placeholder="如 fengbing"
onChange={(e) => setAccount(e.target.value)}
/>
</label>
<label className="jc-login-field">
<span>口令</span>
<input
type="password"
value={password}
autoComplete="current-password"
placeholder="口令"
onChange={(e) => setPassword(e.target.value)}
/>
</label>
{error !== undefined && (
<div className="jc-login-err" role="alert">
<Ico name="alert-circle" s={15} />
<p>{error}</p>
</div>
)}
<button className="jc-login-go" type="submit" disabled={busy || !ready}>
{busy ? '登录中…' : '登录'}
</button>
<p className="jc-login-hint">初始口令由部署时的 JCAI_SEED_PASSWORD 决定</p>
</form>
</div>
);
}