R67 A1-A6:官网风格前端 UI 刷新

这个提交包含在:
2026-04-26 14:07:11 +08:00
父节点 a15bbcbadb
当前提交 a08db8590a
共修改 20 个文件,包含 390 行新增和 185 行删除
+21 -21
查看文件
@@ -32,7 +32,7 @@ function noticeClasses(tone: WecomNotice["tone"]) {
if (tone === "warning") {
return "border-amber-200 bg-amber-50 text-amber-700";
}
return "border-zinc-200 bg-zinc-50 text-zinc-700";
return "border-slate-200 bg-slate-50 text-slate-700";
}
export function LoginPage() {
@@ -104,11 +104,11 @@ export function LoginPage() {
function renderPasswordPanel(testId: string, primary: boolean) {
return (
<div className="space-y-4 rounded-[1.5rem] border border-zinc-200 bg-white p-6 shadow-sm" data-testid={testId}>
<div className="space-y-4 rounded-[1.5rem] border border-slate-200 bg-white p-6 shadow-sm" data-testid={testId}>
<div>
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400">{primary ? "推荐入口" : "回退入口"}</div>
<h2 className="mt-2 text-xl font-semibold tracking-tight text-zinc-900">使用账号密码登录</h2>
<p className="mt-2 text-sm leading-6 text-zinc-600">
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">{primary ? "推荐入口" : "回退入口"}</div>
<h2 className="mt-2 text-xl font-semibold tracking-tight text-slate-900">使用账号密码登录</h2>
<p className="mt-2 text-sm leading-6 text-slate-600">
{primary
? "适合桌面浏览器直接登录;若企业微信入口暂未开放,也可正常使用。"
: "企业微信免登失败、未绑定或超时时,可改用账号密码继续进入。"}
@@ -152,14 +152,14 @@ export function LoginPage() {
}
return (
<div className="card-std overflow-hidden border-zinc-200 p-0 shadow-md">
<div className="border-b border-zinc-200 bg-gradient-to-br from-zinc-50 via-white to-zinc-100 px-6 py-6 sm:px-8 sm:py-7">
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400">统一入口</div>
<h1 className="mt-2 text-3xl font-semibold tracking-tight text-zinc-900">登录海音达工作台</h1>
<p className="mt-3 max-w-2xl text-sm leading-6 text-zinc-600">
<div className="card-std overflow-hidden border-slate-200 p-0 shadow-md">
<div className="border-b border-slate-200 bg-[radial-gradient(circle_at_8%_0%,rgba(0,57,138,0.12),transparent_16rem),linear-gradient(135deg,#ffffff,#e6ecf4)] px-6 py-6 sm:px-8 sm:py-7">
<div className="text-xs font-bold uppercase tracking-[0.2em] text-primary">统一入口</div>
<h1 className="mt-2 text-3xl font-semibold tracking-tight text-slate-900">登录海音达工作台</h1>
<p className="mt-3 max-w-2xl text-sm leading-6 text-slate-600">
企业微信负责进入与提醒,真正的审批、项目、周报和团队留痕都在海音达工作台内完成。
</p>
<div className="mt-4 rounded-2xl border border-zinc-200 bg-white/90 px-4 py-3 text-sm text-zinc-600">
<div className="mt-4 rounded-2xl border border-slate-200 bg-white/90 px-4 py-3 text-sm text-slate-600">
{isWecomContext
? "当前在企业微信中打开,推荐直接使用企业微信进入工作台;失败时可随时回退到账号密码登录。"
: "当前为浏览器场景,推荐直接使用账号密码登录;企业微信入口会作为次级入口提供。"}
@@ -176,10 +176,10 @@ export function LoginPage() {
<div className="grid gap-4">
{primaryMode === "wecom" && wecomEntry?.loginUrl ? (
<div className="rounded-[1.5rem] border border-primary-100 bg-primary-50/70 p-6" data-testid="login-wecom-primary-panel">
<div className="rounded-[1.5rem] border border-primary/20 bg-primary/5 p-6" data-testid="login-wecom-primary-panel">
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-primary">推荐入口</div>
<h2 className="mt-2 text-xl font-semibold tracking-tight text-zinc-900">使用企业微信进入工作台</h2>
<p className="mt-2 text-sm leading-6 text-zinc-600">当前是企业微信场景,优先使用免登入口;若未绑定或回调失效,仍可切换为账号密码登录。</p>
<h2 className="mt-2 text-xl font-semibold tracking-tight text-slate-900">使用企业微信进入工作台</h2>
<p className="mt-2 text-sm leading-6 text-slate-600">当前是企业微信场景,优先使用免登入口;若未绑定或回调失效,仍可切换为账号密码登录。</p>
<Button className="mt-5 w-full" data-testid="wecom-entry-primary" type="button" onClick={handleWecomLogin}>
使用企业微信进入工作台
</Button>
@@ -192,10 +192,10 @@ export function LoginPage() {
)}
{((primaryMode !== "wecom" && wecomEntry?.loginUrl) || oidcProvider) ? (
<div className="rounded-[1.5rem] border border-zinc-200 bg-zinc-50/70 p-6">
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400">补充入口</div>
<h2 className="mt-2 text-lg font-semibold text-zinc-900">其他进入方式</h2>
<p className="mt-2 text-sm leading-6 text-zinc-600">入口失败时请优先选择可理解、可回退的方式继续进入。</p>
<div className="rounded-[1.5rem] border border-slate-200 bg-slate-50/70 p-6">
<div className="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">补充入口</div>
<h2 className="mt-2 text-lg font-semibold text-slate-900">其他进入方式</h2>
<p className="mt-2 text-sm leading-6 text-slate-600">入口失败时请优先选择可理解、可回退的方式继续进入。</p>
<div className="mt-4 grid gap-3">
{primaryMode !== "wecom" && wecomEntry?.loginUrl ? (
<Button className="w-full" data-testid="wecom-entry-secondary" type="button" variant="secondary" onClick={handleWecomLogin}>
@@ -220,11 +220,11 @@ export function LoginPage() {
) : null}
</div>
<div className="rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-3 text-sm text-zinc-600">
<div className="font-medium text-zinc-900">企业微信入口状态:{wecomStatus.label}</div>
<div className="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
<div className="font-medium text-slate-900">企业微信入口状态:{wecomStatus.label}</div>
<div className="mt-1 leading-6">{wecomStatus.description}</div>
{wecomStatus.reasonLabels.length ? (
<ul className="mt-2 list-disc space-y-1 pl-5 text-xs text-zinc-500">
<ul className="mt-2 list-disc space-y-1 pl-5 text-xs text-slate-500">
{wecomStatus.reasonLabels.map((label) => (
<li key={label}>{label}</li>
))}
+19 -21
查看文件
@@ -361,9 +361,7 @@ function SummaryPanel({ title, items }: { title: string; items: SummaryItem[] })
<Link
key={item.key}
to={item.href}
className={item.tone === "warning"
? "block rounded-2xl border border-amber-200 bg-amber-50 px-4 py-4 hover:bg-amber-100"
: "block rounded-2xl border border-zinc-200 bg-white px-4 py-4 hover:bg-zinc-50"}
className={item.tone === "warning" ? "portal-list-card portal-list-card--warning" : "portal-list-card"}
>
<div className="flex items-center justify-between gap-3">
<div className="font-medium text-zinc-900">{item.label}</div>
@@ -388,7 +386,7 @@ function AnnouncementsPanel({ announcements }: { announcements: PortalHomePayloa
>
<div className="space-y-3">
{safeAnnouncements.length ? safeAnnouncements.slice(0, 4).map((item) => (
<div key={item.id} className="rounded-2xl border border-zinc-200 bg-white px-4 py-4">
<div key={item.id} className="portal-list-card">
<div className="font-medium text-zinc-900">{item.title}</div>
<div className="mt-2 text-sm leading-6 text-zinc-600">{item.summary}</div>
{item.href ? (
@@ -410,7 +408,7 @@ function RecentVisitsPanel() {
<SectionPanel title="继续工作" description="保留最近访问路径,帮助快速回到上一次处理中断的位置。">
<div className="space-y-3">
{recentVisits.length ? recentVisits.map((item) => (
<Link key={item.path} to={item.path} className="block rounded-2xl border border-zinc-200 bg-white px-4 py-4 hover:bg-zinc-50">
<Link key={item.path} to={item.path} className="portal-list-card">
<div className="font-medium text-zinc-900">{item.title}</div>
<div className="mt-1 text-xs text-zinc-500">{item.path}</div>
</Link>
@@ -427,7 +425,7 @@ function QuickLinksPanel({ items }: { items: PortalHomePayload["quickLinks"] })
<SectionPanel title="常用入口" description="只保留高频入口,避免把所有模块堆成超市。">
<div className="grid gap-3 sm:grid-cols-2">
{safeItems.length ? safeItems.map((item) => (
<Link key={item.key} to={item.href} className="rounded-2xl border border-zinc-200 bg-white px-4 py-4 text-sm font-medium text-zinc-700 hover:bg-zinc-50">
<Link key={item.key} to={item.href} className="portal-list-card text-sm font-medium text-slate-700">
{item.title}
</Link>
)) : <EmptyStateCard title="暂无常用入口" description="工作区尚未配置推荐入口。" />}
@@ -474,24 +472,24 @@ function AdoptionSummaryPanel({ adoptionSummary }: { adoptionSummary: NonNullabl
<span className="text-xs uppercase tracking-[0.18em] text-zinc-400">展开 / 收起</span>
</summary>
<div className="mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
<div className="rounded-2xl border border-zinc-200 bg-white px-4 py-4 text-sm">
<div className="portal-status-card text-sm">
<div className="text-zinc-500">近 7 天登录覆盖率</div>
<div className="mt-2 text-2xl font-semibold tracking-tight text-zinc-900">{adoptionSummary.login7dRate}%</div>
</div>
<div className="rounded-2xl border border-zinc-200 bg-white px-4 py-4 text-sm">
<div className="portal-status-card text-sm">
<div className="text-zinc-500">周报提交率</div>
<div className="mt-2 text-2xl font-semibold tracking-tight text-zinc-900">{adoptionSummary.weeklySubmissionRate}%</div>
</div>
<div className="rounded-2xl border border-zinc-200 bg-white px-4 py-4 text-sm">
<div className="portal-status-card text-sm">
<div className="text-zinc-500">团队状态覆盖率</div>
<div className="mt-2 text-2xl font-semibold tracking-tight text-zinc-900">{adoptionSummary.presenceCoverageRate}%</div>
</div>
<div className="rounded-2xl border border-zinc-200 bg-white px-4 py-4 text-sm">
<div className="portal-status-card text-sm">
<div className="text-zinc-500">当前公告数</div>
<div className="mt-2 text-2xl font-semibold tracking-tight text-zinc-900">{adoptionSummary.announcementCount}</div>
</div>
</div>
<div className="mt-4 rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-4 text-sm text-zinc-600">
<div className="mt-4 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600">
<div className="font-medium text-zinc-900">本周观测提示</div>
<ul className="mt-2 list-disc space-y-1 pl-5">
{adoptionSummary.hints.map((hint) => (
@@ -549,7 +547,7 @@ function GettingStartedCard({
<Link
key={item.key}
to={item.href}
className="flex items-center gap-3 rounded-2xl border border-zinc-200 bg-white px-4 py-4 transition-colors hover:bg-zinc-50"
className="portal-list-card flex items-center gap-3"
>
<span className={item.completed ? "text-emerald-600" : "text-zinc-400"}>
<span className="material-symbols-outlined text-[20px]">
@@ -564,12 +562,12 @@ function GettingStartedCard({
</Link>
))}
<div className="rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-4">
<div className="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div className="flex items-center justify-between gap-3 text-sm">
<span className="font-medium text-zinc-900">完成进度</span>
<span className="text-zinc-500">{completedCount}/{items.length}</span>
</div>
<div className="mt-3 h-2 overflow-hidden rounded-full bg-zinc-200">
<div className="mt-3 h-2 overflow-hidden rounded-full bg-slate-200">
<div
className="h-full rounded-full bg-primary transition-all"
style={{ width: `${progressPercent}%` }}
@@ -590,15 +588,15 @@ function QuickActionTile({
disabled = false,
}: QuickActionConfig) {
const baseClassName = disabled
? "rounded-2xl border border-zinc-200 bg-zinc-50 px-4 py-4 text-left text-zinc-400"
: "rounded-2xl border border-zinc-200 bg-white px-4 py-4 text-left shadow-sm transition-colors hover:bg-zinc-50";
? "portal-action-tile portal-action-tile--disabled"
: "portal-action-tile";
const content = (
<>
<div className="flex items-center gap-3">
<div className={disabled
? "flex size-10 items-center justify-center rounded-2xl bg-zinc-100 text-zinc-400"
: "flex size-10 items-center justify-center rounded-2xl bg-primary/10 text-primary"}
? "portal-action-icon portal-action-icon--muted"
: "portal-action-icon"}
>
<span className="material-symbols-outlined text-[20px]">{icon}</span>
</div>
@@ -652,7 +650,7 @@ function TodoAggregatePanel({ items }: { items: TodoAggregateItem[] }) {
<SectionPanel title="今日待办聚合" description="合并审批、TODO 和高风险指标,只保留今天最该处理的事。">
<div className="space-y-3">
{items.length ? items.map((item) => (
<Link key={`${item.title}-${item.href}-${item.sourceLabel}`} to={item.href} className="block rounded-2xl border border-zinc-200 bg-white px-4 py-4 hover:bg-zinc-50">
<Link key={`${item.title}-${item.href}-${item.sourceLabel}`} to={item.href} className="portal-list-card">
<div className="flex items-start gap-3">
<div className={item.severity === "high"
? "mt-0.5 flex size-9 items-center justify-center rounded-2xl bg-red-50 text-red-600"
@@ -688,7 +686,7 @@ function TeamStatusBoardPanel({ items }: { items: PortalTeamStatusItem[] }) {
{safeItems.length ? safeItems.map((item) => {
const badge = resolveTeamStatusBadge(item);
return (
<div key={item.employeeId} className="rounded-2xl border border-zinc-200 bg-white px-4 py-4">
<div key={item.employeeId} className="portal-status-card">
<div className="flex items-center gap-3">
<EmployeeAvatar name={item.name} src={item.avatarUrl} size="sm" />
<div className="min-w-0 flex-1">
@@ -712,7 +710,7 @@ function RecentActivitiesPanel({ items }: { items: PortalRecentActivity[] }) {
<SectionPanel title="近期动态流" description="把最近发生的报销、请假、考勤和资产动作放在同一条时间线上。">
<div className="space-y-3">
{safeItems.length ? safeItems.map((item) => (
<Link key={item.id} to={item.href} className="block rounded-2xl border border-zinc-200 bg-white px-4 py-4 hover:bg-zinc-50">
<Link key={item.id} to={item.href} className="portal-list-card">
<div className="flex items-start gap-3">
<EmployeeAvatar name={item.actorName} size="sm" />
<div className="min-w-0 flex-1">