R68 A1-A6:Portal 首页信息架构与对齐优化
这个提交包含在:
@@ -1,18 +1,21 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
export function WorkbenchHero({
|
||||
eyebrow,
|
||||
title,
|
||||
subtitle,
|
||||
actions,
|
||||
className,
|
||||
}: {
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<section className="workbench-hero">
|
||||
<section className={cn("workbench-hero", className)}>
|
||||
<div>
|
||||
{eyebrow ? <div className="workbench-hero__eyebrow">{eyebrow}</div> : null}
|
||||
<h1 className="workbench-hero__title">{title}</h1>
|
||||
|
||||
@@ -92,6 +92,18 @@
|
||||
@apply mt-3 max-w-3xl text-sm leading-6 md:text-base md:leading-7;
|
||||
}
|
||||
|
||||
.workbench-hero--compact {
|
||||
@apply gap-4 px-5 py-5 md:px-6 md:py-6;
|
||||
}
|
||||
|
||||
.workbench-hero--compact .workbench-hero__title {
|
||||
@apply text-2xl md:text-3xl;
|
||||
}
|
||||
|
||||
.workbench-hero--compact .workbench-hero__subtitle {
|
||||
@apply mt-2 max-w-2xl text-sm leading-6;
|
||||
}
|
||||
|
||||
.card-std {
|
||||
background: var(--color-bg-surface-raised);
|
||||
border: 1px solid rgba(226, 232, 240, 0.92);
|
||||
@@ -295,6 +307,22 @@
|
||||
@apply inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ring-1 ring-inset ring-slate-300/55;
|
||||
}
|
||||
|
||||
.portal-metric-strip {
|
||||
@apply grid gap-4 md:grid-cols-2 xl:grid-cols-4;
|
||||
}
|
||||
|
||||
.portal-home-grid {
|
||||
@apply grid gap-6 xl:grid-cols-[minmax(0,2fr),minmax(320px,0.95fr)] xl:items-stretch;
|
||||
}
|
||||
|
||||
.portal-home-grid--collaboration {
|
||||
@apply xl:grid-cols-[minmax(0,1.55fr),minmax(320px,0.9fr)];
|
||||
}
|
||||
|
||||
.portal-panel-fill {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
.portal-list-card {
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(226, 232, 240, 0.94);
|
||||
@@ -314,6 +342,15 @@
|
||||
border-color: rgba(245, 158, 11, 0.36);
|
||||
}
|
||||
|
||||
.portal-list-card--compact {
|
||||
border-radius: var(--radius-lg);
|
||||
@apply px-3.5 py-3.5;
|
||||
}
|
||||
|
||||
.portal-action-stack {
|
||||
@apply grid gap-3 sm:grid-cols-2 xl:grid-cols-1;
|
||||
}
|
||||
|
||||
.portal-action-tile {
|
||||
min-height: 100%;
|
||||
background:
|
||||
@@ -336,6 +373,10 @@
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.portal-action-tile--compact {
|
||||
@apply px-3.5 py-3.5;
|
||||
}
|
||||
|
||||
.portal-action-icon {
|
||||
background: linear-gradient(135deg, rgba(0, 57, 138, 0.12), rgba(19, 168, 184, 0.12));
|
||||
color: var(--color-accent-primary);
|
||||
@@ -353,6 +394,47 @@
|
||||
border-radius: var(--radius-panel);
|
||||
@apply px-4 py-4;
|
||||
}
|
||||
|
||||
.portal-team-list {
|
||||
border: 1px solid rgba(226, 232, 240, 0.94);
|
||||
background: rgba(255, 255, 255, 0.64);
|
||||
@apply overflow-hidden rounded-2xl divide-y divide-slate-200/80;
|
||||
}
|
||||
|
||||
.portal-team-row {
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
@apply flex min-h-[64px] items-center gap-3 px-4 py-3;
|
||||
}
|
||||
|
||||
.portal-panel-footer {
|
||||
color: var(--color-text-secondary);
|
||||
@apply mt-4 flex flex-wrap items-center justify-between gap-3 text-xs leading-5;
|
||||
}
|
||||
|
||||
.portal-insight-tabs__list {
|
||||
@apply h-10 rounded-full border-slate-200 bg-slate-50 p-1;
|
||||
}
|
||||
|
||||
.portal-insight-tabs__content {
|
||||
@apply mt-4;
|
||||
}
|
||||
|
||||
.portal-secondary-drawer {
|
||||
@apply p-5 md:p-6;
|
||||
}
|
||||
|
||||
.portal-secondary-summary {
|
||||
@apply flex cursor-pointer list-none items-center justify-between gap-4 text-left;
|
||||
}
|
||||
|
||||
.portal-secondary-summary__toggle {
|
||||
color: var(--color-text-tertiary);
|
||||
@apply shrink-0 text-xs font-semibold uppercase tracking-[0.18em];
|
||||
}
|
||||
|
||||
.portal-secondary-drawer__body {
|
||||
@apply mt-5 space-y-6;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes page-reveal {
|
||||
|
||||
+164
-74
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { type ReactNode, useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
@@ -27,6 +27,7 @@ import { PageScaffold } from "../components/patterns/PageScaffold";
|
||||
import { SectionPanel } from "../components/patterns/SectionPanel";
|
||||
import { WorkbenchHero } from "../components/patterns/WorkbenchHero";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../components/ui/tabs";
|
||||
import { employeePortalSchema, managerPortalSchema } from "../features/portal/schema";
|
||||
import {
|
||||
clearWelcomeDialogFlag,
|
||||
@@ -375,29 +376,23 @@ function SummaryPanel({ title, items }: { title: string; items: SummaryItem[] })
|
||||
);
|
||||
}
|
||||
|
||||
function AnnouncementsPanel({ announcements }: { announcements: PortalHomePayload["announcements"] }) {
|
||||
function AnnouncementsList({ announcements }: { announcements: PortalHomePayload["announcements"] }) {
|
||||
const safeAnnouncements = Array.isArray(announcements) ? announcements : [];
|
||||
|
||||
return (
|
||||
<SectionPanel
|
||||
title="公告"
|
||||
description="公告保留在第二阅读层,不与今日任务卡竞争。"
|
||||
actions={<Link to="/announcements" className="text-sm font-medium text-primary hover:underline">查看全部</Link>}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{safeAnnouncements.length ? safeAnnouncements.slice(0, 4).map((item) => (
|
||||
<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 ? (
|
||||
<a className="mt-3 inline-flex text-sm font-medium text-primary hover:underline" href={item.href} rel="noreferrer" target="_blank">
|
||||
查看详情
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
)) : <EmptyStateCard title="暂无公告" description="当前没有需要广播的公告。" />}
|
||||
</div>
|
||||
</SectionPanel>
|
||||
<div className="space-y-3">
|
||||
{safeAnnouncements.length ? safeAnnouncements.slice(0, 4).map((item) => (
|
||||
<div key={item.id} className="portal-list-card portal-list-card--compact">
|
||||
<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 ? (
|
||||
<a className="mt-3 inline-flex text-sm font-medium text-primary hover:underline" href={item.href} rel="noreferrer" target="_blank">
|
||||
查看详情
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
)) : <EmptyStateCard title="暂无公告" description="当前没有需要广播的公告。" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -463,11 +458,11 @@ function AdoptionSummaryPanel({ adoptionSummary }: { adoptionSummary: NonNullabl
|
||||
}
|
||||
|
||||
return (
|
||||
<details className="card-std p-6" open>
|
||||
<details className="card-std portal-secondary-drawer">
|
||||
<summary className="flex cursor-pointer list-none items-center justify-between gap-3 text-left">
|
||||
<div>
|
||||
<div className="text-base font-semibold text-zinc-900">试点观测摘要</div>
|
||||
<div className="mt-1 text-sm text-zinc-500">只给管理员默认展开,用于观察习惯是否已形成。</div>
|
||||
<div className="mt-1 text-sm text-zinc-500">管理员按需展开,用于观察习惯是否已形成。</div>
|
||||
</div>
|
||||
<span className="text-xs uppercase tracking-[0.18em] text-zinc-400">展开 / 收起</span>
|
||||
</summary>
|
||||
@@ -501,6 +496,31 @@ function AdoptionSummaryPanel({ adoptionSummary }: { adoptionSummary: NonNullabl
|
||||
);
|
||||
}
|
||||
|
||||
function SecondaryWorkspacePanel({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<details className="card-std portal-secondary-drawer">
|
||||
<summary className="portal-secondary-summary">
|
||||
<div>
|
||||
<div className="text-base font-semibold text-zinc-900">{title}</div>
|
||||
<div className="mt-1 text-sm leading-6 text-zinc-500">{description}</div>
|
||||
</div>
|
||||
<span className="portal-secondary-summary__toggle">展开 / 收起</span>
|
||||
</summary>
|
||||
<div className="portal-secondary-drawer__body">
|
||||
{children}
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
function PortalSectionFallback({
|
||||
title,
|
||||
description,
|
||||
@@ -586,10 +606,11 @@ function QuickActionTile({
|
||||
to,
|
||||
onClick,
|
||||
disabled = false,
|
||||
}: QuickActionConfig) {
|
||||
compact = false,
|
||||
}: QuickActionConfig & { compact?: boolean }) {
|
||||
const baseClassName = disabled
|
||||
? "portal-action-tile portal-action-tile--disabled"
|
||||
: "portal-action-tile";
|
||||
? `portal-action-tile portal-action-tile--disabled${compact ? " portal-action-tile--compact" : ""}`
|
||||
: `portal-action-tile${compact ? " portal-action-tile--compact" : ""}`;
|
||||
|
||||
const content = (
|
||||
<>
|
||||
@@ -601,8 +622,8 @@ function QuickActionTile({
|
||||
<span className="material-symbols-outlined text-[20px]">{icon}</span>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className={disabled ? "font-medium text-zinc-500" : "font-medium text-zinc-900"}>{label}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{description}</div>
|
||||
<div className={disabled ? "truncate font-medium text-zinc-500" : "truncate font-medium text-zinc-900"}>{label}</div>
|
||||
<div className="mt-1 text-xs leading-5 text-zinc-500">{description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -623,12 +644,18 @@ function QuickActionTile({
|
||||
|
||||
function QuickActionsPanel({
|
||||
items,
|
||||
layout = "grid",
|
||||
}: {
|
||||
items: QuickActionConfig[];
|
||||
layout?: "grid" | "rail";
|
||||
}) {
|
||||
return (
|
||||
<SectionPanel title="快捷操作栏" description="把每天高频动作直接前置到首页,不再绕路。">
|
||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<SectionPanel
|
||||
title="快捷操作栏"
|
||||
description="保留每天真正高频的动作,不把所有入口都放在首屏。"
|
||||
className={layout === "rail" ? "portal-panel-fill" : undefined}
|
||||
>
|
||||
<div className={layout === "rail" ? "portal-action-stack" : "grid gap-3 sm:grid-cols-2 xl:grid-cols-4"}>
|
||||
{items.length ? items.map((item) => (
|
||||
<QuickActionTile
|
||||
key={item.key}
|
||||
@@ -638,6 +665,7 @@ function QuickActionsPanel({
|
||||
to={item.to}
|
||||
onClick={item.onClick}
|
||||
disabled={item.disabled}
|
||||
compact={layout === "rail"}
|
||||
/>
|
||||
)) : <EmptyStateCard title="暂无快捷动作" description="当前账号没有可直接执行的首页动作。" />}
|
||||
</div>
|
||||
@@ -646,10 +674,17 @@ function QuickActionsPanel({
|
||||
}
|
||||
|
||||
function TodoAggregatePanel({ items }: { items: TodoAggregateItem[] }) {
|
||||
const visibleItems = items.slice(0, 5);
|
||||
const hiddenCount = Math.max(items.length - visibleItems.length, 0);
|
||||
|
||||
return (
|
||||
<SectionPanel title="今日待办聚合" description="合并审批、TODO 和高风险指标,只保留今天最该处理的事。">
|
||||
<SectionPanel
|
||||
title="今日待办聚合"
|
||||
description="合并审批、TODO 和高风险指标,只保留今天最该处理的事。"
|
||||
className="portal-panel-fill"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
{items.length ? items.map((item) => (
|
||||
{visibleItems.length ? visibleItems.map((item) => (
|
||||
<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"
|
||||
@@ -673,54 +708,99 @@ function TodoAggregatePanel({ items }: { items: TodoAggregateItem[] }) {
|
||||
</Link>
|
||||
)) : <EmptyStateCard title="暂无待办事项,休息一下吧" description="当前没有需要立刻处理的审批、TODO 或风险提醒。" />}
|
||||
</div>
|
||||
{hiddenCount > 0 ? (
|
||||
<div className="portal-panel-footer">
|
||||
<span>还有 {hiddenCount} 项已收起,避免首页变成待办清单。</span>
|
||||
<Link to="/todos" className="font-medium text-primary hover:underline">查看全部待办</Link>
|
||||
</div>
|
||||
) : null}
|
||||
</SectionPanel>
|
||||
);
|
||||
}
|
||||
|
||||
function TeamStatusBoardPanel({ items }: { items: PortalTeamStatusItem[] }) {
|
||||
const safeItems = Array.isArray(items) ? items : [];
|
||||
const visibleItems = safeItems.slice(0, 5);
|
||||
const hiddenCount = Math.max(safeItems.length - visibleItems.length, 0);
|
||||
|
||||
return (
|
||||
<SectionPanel title="团队全员状态" description="同时看人在不在岗、有没有请假,以及今天是否已打卡。">
|
||||
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
||||
{safeItems.length ? safeItems.map((item) => {
|
||||
<SectionPanel
|
||||
title="团队全员状态"
|
||||
description="小团队首页只展示关键状态,完整考勤与在岗信息进入团队页。"
|
||||
actions={<Link to="/team" className="text-sm font-medium text-primary hover:underline">查看团队页</Link>}
|
||||
className="portal-panel-fill"
|
||||
>
|
||||
<div className="portal-team-list">
|
||||
{visibleItems.length ? visibleItems.map((item) => {
|
||||
const badge = resolveTeamStatusBadge(item);
|
||||
return (
|
||||
<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">
|
||||
<div className="truncate font-medium text-zinc-900">{item.name}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{formatTeamStatusAttendance(item)}</div>
|
||||
</div>
|
||||
<span className={badge.className}>{badge.label}</span>
|
||||
<div key={item.employeeId} className="portal-team-row">
|
||||
<EmployeeAvatar name={item.name} src={item.avatarUrl} size="sm" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate font-medium text-zinc-900">{item.name}</div>
|
||||
<div className="mt-1 truncate text-xs text-zinc-500">{formatTeamStatusAttendance(item)}</div>
|
||||
</div>
|
||||
<span className={badge.className}>{badge.label}</span>
|
||||
</div>
|
||||
);
|
||||
}) : <EmptyStateCard title="暂无团队状态" description="当前没有可展示的团队成员状态。" />}
|
||||
</div>
|
||||
{hiddenCount > 0 ? (
|
||||
<div className="portal-panel-footer">
|
||||
<span>已收起 {hiddenCount} 位成员,避免首页纵向过长。</span>
|
||||
<Link to="/team" className="font-medium text-primary hover:underline">查看全部</Link>
|
||||
</div>
|
||||
) : null}
|
||||
</SectionPanel>
|
||||
);
|
||||
}
|
||||
|
||||
function RecentActivitiesPanel({ items }: { items: PortalRecentActivity[] }) {
|
||||
function RecentActivitiesList({ items }: { items: PortalRecentActivity[] }) {
|
||||
const safeItems = Array.isArray(items) ? items : [];
|
||||
|
||||
return (
|
||||
<SectionPanel title="近期动态流" description="把最近发生的报销、请假、考勤和资产动作放在同一条时间线上。">
|
||||
<div className="space-y-3">
|
||||
{safeItems.length ? safeItems.map((item) => (
|
||||
<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">
|
||||
<div className="text-sm font-medium leading-6 text-zinc-900">{item.summary}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{formatRelativeTime(item.createdAt)}</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{safeItems.length ? safeItems.slice(0, 5).map((item) => (
|
||||
<Link key={item.id} to={item.href} className="portal-list-card portal-list-card--compact">
|
||||
<div className="flex items-start gap-3">
|
||||
<EmployeeAvatar name={item.actorName} size="sm" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm font-medium leading-6 text-zinc-900">{item.summary}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">{formatRelativeTime(item.createdAt)}</div>
|
||||
</div>
|
||||
</Link>
|
||||
)) : <EmptyStateCard title="暂无近期动态" description="当前没有可展示的最近业务动作。" />}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)) : <EmptyStateCard title="暂无近期动态" description="当前没有可展示的最近业务动作。" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ActivityAnnouncementsPanel({
|
||||
activities,
|
||||
announcements,
|
||||
}: {
|
||||
activities: PortalRecentActivity[];
|
||||
announcements: PortalHomePayload["announcements"];
|
||||
}) {
|
||||
return (
|
||||
<SectionPanel
|
||||
title="近期动态流"
|
||||
description="动态优先,公告进入同一张右侧阅读卡,减少首页白卡堆叠。"
|
||||
actions={<Link to="/announcements" className="text-sm font-medium text-primary hover:underline">公告页</Link>}
|
||||
className="portal-panel-fill"
|
||||
>
|
||||
<Tabs defaultValue="activity" className="portal-insight-tabs">
|
||||
<TabsList className="portal-insight-tabs__list">
|
||||
<TabsTrigger value="activity">动态</TabsTrigger>
|
||||
<TabsTrigger value="announcements">公告</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="activity" className="portal-insight-tabs__content">
|
||||
<RecentActivitiesList items={activities} />
|
||||
</TabsContent>
|
||||
<TabsContent value="announcements" className="portal-insight-tabs__content">
|
||||
<AnnouncementsList announcements={announcements} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</SectionPanel>
|
||||
);
|
||||
}
|
||||
@@ -958,6 +1038,7 @@ export function PortalHomePage() {
|
||||
<WorkbenchHero
|
||||
eyebrow={isManagerView ? "管理工作台" : "个人工作台"}
|
||||
title={`你好,${payload.greeting.name}`}
|
||||
className="workbench-hero--compact"
|
||||
subtitle={isManagerView
|
||||
? "先处理审批、团队状态和周报缺口,再进入项目与组织协同。"
|
||||
: "先处理今天必须完成的事,再继续查看项目、周报和公告。"}
|
||||
@@ -970,7 +1051,7 @@ export function PortalHomePage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid gap-4 xl:grid-cols-4">
|
||||
<div className="portal-metric-strip">
|
||||
{primaryCards.map((card) => (
|
||||
<MetricCard
|
||||
key={card.key}
|
||||
@@ -1007,12 +1088,12 @@ export function PortalHomePage() {
|
||||
</ErrorBoundary>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),minmax(0,1fr)]">
|
||||
<QuickActionsPanel items={quickActions} />
|
||||
<div className="portal-home-grid">
|
||||
<TodoAggregatePanel items={todoAggregateItems} />
|
||||
<QuickActionsPanel items={quickActions} layout="rail" />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1.15fr),minmax(0,0.85fr)]">
|
||||
<div className="portal-home-grid portal-home-grid--collaboration">
|
||||
<ErrorBoundary
|
||||
fallback={(
|
||||
<PortalSectionFallback
|
||||
@@ -1031,40 +1112,49 @@ export function PortalHomePage() {
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<RecentActivitiesPanel items={recentActivities} />
|
||||
<ActivityAnnouncementsPanel activities={recentActivities} announcements={announcementItems} />
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
{isManagerView ? (
|
||||
<>
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1.05fr),minmax(0,0.95fr)]">
|
||||
<SummaryPanel title="今日管理重点" items={managerSummaryItems} />
|
||||
<QuickLinksPanel items={quickLinkItems} />
|
||||
</div>
|
||||
<SecondaryWorkspacePanel
|
||||
title="今日管理重点 / 常用入口"
|
||||
description="默认收起第二阅读层;需要巡检项目、组织协同或常用入口时再展开。"
|
||||
>
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),360px]">
|
||||
<SummaryPanel title="今日管理重点" items={managerSummaryItems} />
|
||||
<QuickLinksPanel items={quickLinkItems} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,0.95fr),minmax(0,1.05fr)]">
|
||||
<AnnouncementsPanel announcements={announcementItems} />
|
||||
<SecondaryCardsPanel
|
||||
title="项目跟进与组织协同"
|
||||
description="把项目、组织和风险类信息下移到第二阅读层。"
|
||||
cards={secondaryCards}
|
||||
/>
|
||||
</div>
|
||||
</SecondaryWorkspacePanel>
|
||||
|
||||
<AdoptionSummaryPanel adoptionSummary={payload.adoptionSummary} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1.1fr),minmax(0,0.9fr)]">
|
||||
<SecondaryWorkspacePanel
|
||||
title="我的申请与资产 / 继续工作"
|
||||
description="默认收起低频参考信息,首页先服务今天要处理的事。"
|
||||
>
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),minmax(0,1fr)]">
|
||||
<SummaryPanel title="我的申请与资产" items={employeeSummaryItems} />
|
||||
<AnnouncementsPanel announcements={announcementItems} />
|
||||
<RecentVisitsPanel />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),minmax(0,1fr)]">
|
||||
<RecentVisitsPanel />
|
||||
<QuickLinksPanel items={quickLinkItems} />
|
||||
<SecondaryCardsPanel
|
||||
title="项目跟进与个人协同"
|
||||
description="项目、资产和其他参考指标保留在第二阅读层。"
|
||||
cards={secondaryCards}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</SecondaryWorkspacePanel>
|
||||
)}
|
||||
</PageScaffold>
|
||||
);
|
||||
|
||||
在新工单中引用
屏蔽一个用户