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
+205
View File
@@ -0,0 +1,205 @@
import type { SVGProps } from "react";
type IconProps = SVGProps<SVGSVGElement> & { size?: number };
function base({ size = 16, ...rest }: IconProps) {
return {
width: size,
height: size,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: 1.7,
strokeLinecap: "round" as const,
strokeLinejoin: "round" as const,
"aria-hidden": true,
...rest,
};
}
export const Check = (p: IconProps) => (
<svg {...base(p)}>
<path d="M20 6 9 17l-5-5" />
</svg>
);
export const CheckCircle = (p: IconProps) => (
<svg {...base(p)}>
<circle cx="12" cy="12" r="9" />
<path d="m8.5 12 2.5 2.5 4.5-5" />
</svg>
);
export const Circle = (p: IconProps) => (
<svg {...base(p)}>
<circle cx="12" cy="12" r="8.5" />
</svg>
);
export const Search = (p: IconProps) => (
<svg {...base(p)}>
<circle cx="11" cy="11" r="6.5" />
<path d="m16 16 4 4" />
</svg>
);
export const Plus = (p: IconProps) => (
<svg {...base(p)}>
<path d="M12 5v14M5 12h14" />
</svg>
);
export const X = (p: IconProps) => (
<svg {...base(p)}>
<path d="M18 6 6 18M6 6l12 12" />
</svg>
);
export const ChevronDown = (p: IconProps) => (
<svg {...base(p)}>
<path d="m6 9 6 6 6-6" />
</svg>
);
export const ChevronRight = (p: IconProps) => (
<svg {...base(p)}>
<path d="m9 6 6 6-6 6" />
</svg>
);
export const ChevronLeft = (p: IconProps) => (
<svg {...base(p)}>
<path d="m15 6-6 6 6 6" />
</svg>
);
export const ArrowRight = (p: IconProps) => (
<svg {...base(p)}>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
);
export const Columns = (p: IconProps) => (
<svg {...base(p)}>
<rect x="3.5" y="4" width="17" height="16" rx="2.5" />
<path d="M9.5 4v16M15 4v16" />
</svg>
);
export const TicketIcon = (p: IconProps) => (
<svg {...base(p)}>
<path d="M3.5 9.5V7a2 2 0 0 1 2-2h13a2 2 0 0 1 2 2v2.5a2.5 2.5 0 0 0 0 5V17a2 2 0 0 1-2 2h-13a2 2 0 0 1-2-2v-2.5a2.5 2.5 0 0 0 0-5Z" />
<path d="M12 7v2M12 15v2" />
</svg>
);
export const Book = (p: IconProps) => (
<svg {...base(p)}>
<path d="M4 5.5A2.5 2.5 0 0 1 6.5 3H19v15.5H6.5A2.5 2.5 0 0 0 4 21Z" />
<path d="M4 18.5V5.5M8.5 7.5h6" />
</svg>
);
export const Users = (p: IconProps) => (
<svg {...base(p)}>
<circle cx="9" cy="8.5" r="3.2" />
<path d="M3.5 19.5a5.5 5.5 0 0 1 11 0" />
<path d="M16 6.2a3.2 3.2 0 0 1 0 6M17.5 15.2a5.5 5.5 0 0 1 3 4.3" />
</svg>
);
export const Inbox = (p: IconProps) => (
<svg {...base(p)}>
<path d="M3.5 13.5h4l1.2 2.2h6.6l1.2-2.2h4" />
<path d="M5.6 4.5h12.8l2.1 9v4.3a2 2 0 0 1-2 2H5.5a2 2 0 0 1-2-2V13.5Z" />
</svg>
);
export const Sparkle = (p: IconProps) => (
<svg {...base(p)}>
<path d="M12 3.5 13.6 9l5.4 1.6-5.4 1.6L12 17.5l-1.6-5.3L5 10.6 10.4 9Z" />
<path d="M18.5 16.5 19.2 19l2.3.7-2.3.8-.7 2.4" />
</svg>
);
export const Clock = (p: IconProps) => (
<svg {...base(p)}>
<circle cx="12" cy="12" r="8.5" />
<path d="M12 7.5V12l3 2" />
</svg>
);
export const Trash = (p: IconProps) => (
<svg {...base(p)}>
<path d="M4.5 7h15M9.5 7V5.5A1.5 1.5 0 0 1 11 4h2a1.5 1.5 0 0 1 1.5 1.5V7" />
<path d="M6.5 7l.8 11.2A2 2 0 0 0 9.3 20h5.4a2 2 0 0 0 2-1.8L17.5 7" />
</svg>
);
export const Lock = (p: IconProps) => (
<svg {...base(p)}>
<rect x="4.5" y="10.5" width="15" height="10" rx="2.2" />
<path d="M8 10.5V8a4 4 0 0 1 8 0v2.5" />
</svg>
);
export const Logout = (p: IconProps) => (
<svg {...base(p)}>
<path d="M14.5 4.5H7a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h7.5" />
<path d="M18 12H10M15.5 8.5 19 12l-3.5 3.5" />
</svg>
);
export const Flag = (p: IconProps) => (
<svg {...base(p)}>
<path d="M6 21V4.5M6 5h10.5l-1.7 3.6L16.5 12H6" />
</svg>
);
export const Link2 = (p: IconProps) => (
<svg {...base(p)}>
<path d="M9.5 14.5 14.5 9.5" />
<path d="M11 6.8 12.6 5.2a3.7 3.7 0 0 1 5.2 5.2l-1.6 1.6" />
<path d="M13 17.2 11.4 18.8a3.7 3.7 0 0 1-5.2-5.2l1.6-1.6" />
</svg>
);
export const More = (p: IconProps) => (
<svg {...base(p)}>
<circle cx="6" cy="12" r="1.2" fill="currentColor" stroke="none" />
<circle cx="12" cy="12" r="1.2" fill="currentColor" stroke="none" />
<circle cx="18" cy="12" r="1.2" fill="currentColor" stroke="none" />
</svg>
);
export const Spinner = ({ size = 16, className = "" }: { size?: number; className?: string }) => (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
className={`animate-spin ${className}`}
aria-hidden
>
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="2.6" opacity="0.2" fill="none" />
<path
d="M21 12a9 9 0 0 0-9-9"
stroke="currentColor"
strokeWidth="2.6"
strokeLinecap="round"
fill="none"
/>
</svg>
);
export const Logo = ({ size = 28 }: { size?: number }) => (
<svg width={size} height={size} viewBox="0 0 32 32" aria-hidden>
<rect width="32" height="32" rx="9" fill="#2F6054" />
<path
d="M9 11.5h14M9 16h9.5M9 20.5h6"
stroke="#EFF5F3"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="23" cy="20.5" r="3" fill="#C2703D" />
</svg>
);