PC6 完善 PC 工作台与审批信息架构
这个提交包含在:
@@ -74,6 +74,11 @@ type GettingStartedChecklistItem = {
|
||||
|
||||
const LEAVE_PORTAL_PERMISSIONS = ["leave.view_own", "leave.view_all", "leave.create", "leave.submit"];
|
||||
const EXPENSE_PORTAL_PERMISSIONS = ["expense.view_own", "expense.view_all", "expense.create", "expense.proxy_manage"];
|
||||
const PROCUREMENT_PORTAL_PERMISSIONS = ["procurement.view_own", "procurement.view_all", "procurement.create", "procurement.submit"];
|
||||
const TRAVEL_PORTAL_PERMISSIONS = ["travel.view_own", "travel.view_all", "travel.create", "travel.submit"];
|
||||
const APPROVAL_PORTAL_PERMISSIONS = ["expense.approve", "leave.approve", "procurement.approve", "travel.approve"];
|
||||
const FINANCE_PORTAL_PERMISSIONS = ["payment.batch.manage", "risk.review.manage", "expense.finance.pay"];
|
||||
const SYSTEM_PORTAL_PERMISSIONS = ["system.users", "system.settings", "system.audit.view", "notification.channel.manage"];
|
||||
const TODO_SEVERITY_RANK = {
|
||||
high: 3,
|
||||
warning: 2,
|
||||
@@ -90,6 +95,28 @@ function severityTone(severity?: "normal" | "warning" | "high") {
|
||||
return "neutral" as const;
|
||||
}
|
||||
|
||||
function buildFallbackRoleCapabilities(permissions: string[], roleCodes: string[]) {
|
||||
const hasAdminRole = roleCodes.includes("admin") || hasAnyPermission(permissions, ["*"]);
|
||||
const hasBusinessApproverRole = roleCodes.some((roleCode) =>
|
||||
["manager", "ceo", "finance", "finance_mgr", "hr"].includes(roleCode),
|
||||
);
|
||||
const hasFinanceRole = roleCodes.some((roleCode) => ["finance", "finance_mgr"].includes(roleCode));
|
||||
|
||||
return {
|
||||
canSubmitApplications: hasAnyPermission(permissions, [
|
||||
...LEAVE_PORTAL_PERMISSIONS,
|
||||
...EXPENSE_PORTAL_PERMISSIONS,
|
||||
...PROCUREMENT_PORTAL_PERMISSIONS,
|
||||
...TRAVEL_PORTAL_PERMISSIONS,
|
||||
]),
|
||||
canApproveTasks: hasBusinessApproverRole && hasAnyPermission(permissions, APPROVAL_PORTAL_PERMISSIONS),
|
||||
canUseFinance: hasFinanceRole && hasAnyPermission(permissions, FINANCE_PORTAL_PERMISSIONS),
|
||||
canManageSystem: hasAdminRole || hasAnyPermission(permissions, SYSTEM_PORTAL_PERMISSIONS),
|
||||
canViewTeam: hasBusinessApproverRole || hasAnyPermission(permissions, ["report.dashboard.view", "system.users", "project.manage"]),
|
||||
isAdminOnly: hasAdminRole && !hasBusinessApproverRole,
|
||||
};
|
||||
}
|
||||
|
||||
function pickCards(cards: PortalHomePayload["cards"], keys: string[]) {
|
||||
const map = new Map(cards.map((card) => [card.key, card]));
|
||||
return keys.map((key) => map.get(key)).filter((card): card is PortalHomePayload["cards"][number] => Boolean(card));
|
||||
@@ -376,6 +403,38 @@ function SummaryPanel({ title, items }: { title: string; items: SummaryItem[] })
|
||||
);
|
||||
}
|
||||
|
||||
function FinanceSummaryPanel({ summary }: { summary: PortalHomePayload["financeSummary"] }) {
|
||||
if (!summary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items: SummaryItem[] = [
|
||||
{
|
||||
key: "finance-pending-batches",
|
||||
href: summary.href,
|
||||
label: "待付款批次",
|
||||
value: `${summary.pendingBatchCount} 个`,
|
||||
tone: summary.pendingBatchCount > 0 ? "warning" : "normal",
|
||||
},
|
||||
{
|
||||
key: "finance-failed-items",
|
||||
href: summary.href,
|
||||
label: "异常付款",
|
||||
value: `${summary.failedItemCount} 项`,
|
||||
tone: summary.failedItemCount > 0 ? "warning" : "normal",
|
||||
},
|
||||
{
|
||||
key: "finance-risk-flags",
|
||||
href: summary.href,
|
||||
label: "待复核风险",
|
||||
value: `${summary.riskCount} 项`,
|
||||
tone: summary.riskCount > 0 ? "warning" : "normal",
|
||||
},
|
||||
];
|
||||
|
||||
return <SummaryPanel title="财务待处理" items={items} />;
|
||||
}
|
||||
|
||||
function AnnouncementsList({ announcements }: { announcements: PortalHomePayload["announcements"] }) {
|
||||
const safeAnnouncements = Array.isArray(announcements) ? announcements : [];
|
||||
|
||||
@@ -817,7 +876,6 @@ export function PortalHomePage() {
|
||||
retry: (failureCount, error) => !isAttendanceFeatureDisabled(error) && failureCount < 2,
|
||||
});
|
||||
const canSetupWorkspace = hasAnyPermission(permissions, ["system.settings"]);
|
||||
const isManagerView = isManagerRole(permissions);
|
||||
|
||||
useEffect(() => {
|
||||
setShowWelcome(shouldShowWelcomeDialog());
|
||||
@@ -910,6 +968,20 @@ export function PortalHomePage() {
|
||||
const quickLinkItems = Array.isArray(payload.quickLinks) ? payload.quickLinks : [];
|
||||
const recentActivities = Array.isArray(payload.recentActivities) ? payload.recentActivities : [];
|
||||
const teamStatusBoard = Array.isArray(payload.teamStatusBoard) ? payload.teamStatusBoard : [];
|
||||
const roleCapabilities = payload.roleCapabilities ?? buildFallbackRoleCapabilities(permissions, me?.roleCodes ?? []);
|
||||
const isManagerView =
|
||||
roleCapabilities.canViewTeam ||
|
||||
roleCapabilities.canApproveTasks ||
|
||||
roleCapabilities.canUseFinance ||
|
||||
roleCapabilities.canManageSystem ||
|
||||
isManagerRole(permissions);
|
||||
const workspaceEyebrow = roleCapabilities.canUseFinance
|
||||
? "财务工作台"
|
||||
: roleCapabilities.canManageSystem
|
||||
? "管理工作台"
|
||||
: roleCapabilities.canApproveTasks
|
||||
? "审批工作台"
|
||||
: "个人工作台";
|
||||
const portalSchema = isManagerView ? managerPortalSchema : employeePortalSchema;
|
||||
const primaryCards = pickCards(cards, portalSchema.heroCards);
|
||||
const primaryCardKeySet = primaryCards.map((card) => card.key);
|
||||
@@ -986,8 +1058,87 @@ export function PortalHomePage() {
|
||||
: null,
|
||||
] as Array<SummaryItem | null>).filter(isSummaryItem);
|
||||
|
||||
const approvalSummaryItems = ([
|
||||
roleCapabilities.canApproveTasks
|
||||
? {
|
||||
key: "approval-pending",
|
||||
href: payload.pendingTasks.href,
|
||||
label: "审批待办",
|
||||
value: `${payload.pendingTasks.count} 条`,
|
||||
note: payload.pendingTasks.count > 0 ? "进入审批中心处理。" : "暂无待处理审批。",
|
||||
tone: payload.pendingTasks.count > 0 ? "warning" as const : "normal" as const,
|
||||
}
|
||||
: null,
|
||||
roleCapabilities.canApproveTasks
|
||||
? {
|
||||
key: "approval-completed",
|
||||
href: "/workflow/tasks?status=completed",
|
||||
label: "已处理",
|
||||
value: "查看",
|
||||
note: "复核历史处理记录。",
|
||||
}
|
||||
: null,
|
||||
] as Array<SummaryItem | null>).filter(isSummaryItem);
|
||||
|
||||
const systemSummaryItems = ([
|
||||
roleCapabilities.canManageSystem
|
||||
? {
|
||||
key: "system-users",
|
||||
href: "/people",
|
||||
label: "账号与人员",
|
||||
value: "管理",
|
||||
note: "维护账号、角色和组织信息。",
|
||||
}
|
||||
: null,
|
||||
roleCapabilities.canManageSystem
|
||||
? {
|
||||
key: "system-workspace",
|
||||
href: "/system/workspace",
|
||||
label: "工作区设置",
|
||||
value: "配置",
|
||||
note: "检查入口、通知和基础配置。",
|
||||
}
|
||||
: null,
|
||||
roleCapabilities.canManageSystem
|
||||
? {
|
||||
key: "system-audit",
|
||||
href: "/audit",
|
||||
label: "审计日志",
|
||||
value: "查看",
|
||||
note: "查询关键操作留痕。",
|
||||
}
|
||||
: null,
|
||||
] as Array<SummaryItem | null>).filter(isSummaryItem);
|
||||
|
||||
const quickActions: QuickActionConfig[] = [];
|
||||
if (hasAnyPermission(permissions, LEAVE_PORTAL_PERMISSIONS)) {
|
||||
if (roleCapabilities.canApproveTasks) {
|
||||
quickActions.push({
|
||||
key: "approval",
|
||||
icon: "fact_check",
|
||||
label: "处理审批",
|
||||
description: "进入审批中心查看待办",
|
||||
to: "/workflow/tasks",
|
||||
});
|
||||
}
|
||||
if (roleCapabilities.canUseFinance && payload.financeSummary) {
|
||||
quickActions.push({
|
||||
key: "finance",
|
||||
icon: "payments",
|
||||
label: "财务处理",
|
||||
description: "查看付款和风险待处理",
|
||||
to: payload.financeSummary.href,
|
||||
});
|
||||
}
|
||||
if (roleCapabilities.canManageSystem) {
|
||||
quickActions.push({
|
||||
key: "system",
|
||||
icon: "admin_panel_settings",
|
||||
label: "系统管理",
|
||||
description: "账号、配置和审计入口",
|
||||
to: "/people",
|
||||
});
|
||||
}
|
||||
if (!roleCapabilities.isAdminOnly && hasAnyPermission(permissions, LEAVE_PORTAL_PERMISSIONS)) {
|
||||
quickActions.push({
|
||||
key: "leave",
|
||||
icon: "event_available",
|
||||
@@ -996,7 +1147,7 @@ export function PortalHomePage() {
|
||||
to: "/leave-requests?action=create",
|
||||
});
|
||||
}
|
||||
if (hasAnyPermission(permissions, EXPENSE_PORTAL_PERMISSIONS)) {
|
||||
if (!roleCapabilities.isAdminOnly && hasAnyPermission(permissions, EXPENSE_PORTAL_PERMISSIONS)) {
|
||||
quickActions.push({
|
||||
key: "expense",
|
||||
icon: "request_quote",
|
||||
@@ -1036,10 +1187,16 @@ export function PortalHomePage() {
|
||||
</ErrorBoundary>
|
||||
|
||||
<WorkbenchHero
|
||||
eyebrow={isManagerView ? "管理工作台" : "个人工作台"}
|
||||
eyebrow={workspaceEyebrow}
|
||||
title={`你好,${payload.greeting.name}`}
|
||||
className="workbench-hero--compact"
|
||||
subtitle={isManagerView ? "审批、团队、周报。" : "待办、打卡、申请。"}
|
||||
subtitle={roleCapabilities.canUseFinance
|
||||
? "付款、风险、审批。"
|
||||
: roleCapabilities.canManageSystem
|
||||
? "配置、审计、通知。"
|
||||
: isManagerView
|
||||
? "审批、团队、周报。"
|
||||
: "待办、打卡、申请。"}
|
||||
actions={
|
||||
!payload.workspace.persisted && canSetupWorkspace ? (
|
||||
<Button asChild variant="secondary" type="button">
|
||||
@@ -1117,11 +1274,22 @@ export function PortalHomePage() {
|
||||
{isManagerView ? (
|
||||
<>
|
||||
<SecondaryWorkspacePanel
|
||||
title="今日管理重点 / 常用入口"
|
||||
title="角色重点 / 常用入口"
|
||||
description="按需展开。"
|
||||
>
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),360px]">
|
||||
<SummaryPanel title="今日管理重点" items={managerSummaryItems} />
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr),minmax(0,1fr)] 2xl:grid-cols-[minmax(0,1fr),minmax(0,1fr),360px]">
|
||||
{roleCapabilities.canApproveTasks ? (
|
||||
<SummaryPanel title="审批处理" items={approvalSummaryItems} />
|
||||
) : null}
|
||||
{roleCapabilities.canUseFinance ? (
|
||||
<FinanceSummaryPanel summary={payload.financeSummary ?? null} />
|
||||
) : null}
|
||||
{roleCapabilities.canManageSystem ? (
|
||||
<SummaryPanel title="系统管理" items={systemSummaryItems} />
|
||||
) : null}
|
||||
{roleCapabilities.canViewTeam ? (
|
||||
<SummaryPanel title="今日团队状态" items={managerSummaryItems} />
|
||||
) : null}
|
||||
<QuickLinksPanel items={quickLinkItems} />
|
||||
</div>
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户