feat(web): 重做设计系统与全部页面,前端观感对齐当下审美

用户反馈"UI 太丑、太简陋"。不是换配色,而是先立 token 再让页面去用:

- tailwind.config.js:纸白 paper / 墨绿 primary / 陶土橙 clay + 语义色
  sage/wheat/rust/sky,字号阶、圆角阶、投影、动效
- index.css:卡片、按钮五种变体、输入、pill/chip、导航、骨架屏
- 新增 icons.tsx(线性图标集)与 ui.tsx(Button/Sheet/Toast/空态/骨架/PageHeader)
- 新增 AppShell 取代 Layout + WorkspaceNav:桌面侧栏 + 移动顶部横条,工单未读角标
- 六个页面全部重写:登录、首页、任务、看板、工单、知识、成员、邀请落地
- 详情抽屉改用 Sheet,工单态补齐需求人/办理人/退回次数/动作按钮

e2e 因 DOM 升级同步调整(抽屉按 role=dialog 定位、下拉改用 aria-label),
三套用例(7 + 8 + 7 项)全部重跑通过;tsc 0 错,构建 45 modules / gzip 76.70 kB。
This commit is contained in:
dadao
2026-10-28 18:37:35 +08:00
parent f683436689
commit 5be55fdccb
21 changed files with 2520 additions and 968 deletions
+33 -10
View File
@@ -1,7 +1,8 @@
import { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useAuth } from "../App";
import { Layout } from "../components/Layout";
import { Check, Logo, Spinner } from "../components/icons";
import { Button, InlineAlert } from "../components/ui";
import { ApiError, api, session } from "../lib/client";
import type { Workspace } from "../lib/types";
@@ -11,6 +12,7 @@ export function JoinPage() {
const { user, loading, reload } = useAuth();
const navigate = useNavigate();
const [message, setMessage] = useState("正在加入工作区…");
const [done, setDone] = useState(false);
const [failed, setFailed] = useState(false);
const started = useRef(false);
@@ -27,8 +29,9 @@ export function JoinPage() {
try {
const ws = await api.post<Workspace>(`/workspaces/${wsId}/join`, { code });
await reload();
setDone(true);
setMessage(`已加入「${ws.name}」,正在跳转…`);
setTimeout(() => navigate(`/w/${wsId}`, { replace: true }), 600);
window.setTimeout(() => navigate(`/w/${wsId}`, { replace: true }), 700);
} catch (err) {
setFailed(true);
setMessage(err instanceof ApiError ? err.message : "加入失败");
@@ -37,17 +40,37 @@ export function JoinPage() {
}, [loading, user, wsId, code, navigate, reload]);
return (
<Layout>
<div className="mx-auto mt-10 max-w-md">
<div className="card text-center">
<p className={failed ? "text-sm text-red-600" : "text-sm text-ink-700"}>{message}</p>
<main className="grid min-h-full place-items-center bg-paper-100 px-6">
<div className="w-full max-w-sm animate-slide-up">
<div className="mb-6 flex justify-center">
<Logo size={40} />
</div>
<div className="card p-7 text-center">
<div className="mx-auto mb-4 grid h-12 w-12 place-items-center rounded-2xl bg-primary-50 text-primary-700">
{failed ? (
<span className="text-xl">!</span>
) : done ? (
<Check size={22} />
) : (
<Spinner size={20} />
)}
</div>
<p className="text-sm leading-relaxed text-ink-700">{message}</p>
<p className="mt-1 text-2xs text-ink-400">
{failed ? "链接可能已失效,或你已经加入过了。" : "邀请码一次有效"}
</p>
{failed && (
<button className="btn-ghost mt-4" onClick={() => navigate("/")}>
返回工作区列表
</button>
<>
<div className="mt-4">
<InlineAlert>可以把邀请码发给同事,让他重新生成一条链接。</InlineAlert>
</div>
<Button variant="ghost" className="mt-4 w-full" onClick={() => navigate("/")}>
返回工作区列表
</Button>
</>
)}
</div>
</div>
</Layout>
</main>
);
}