feat(management-navigation-single-company-ux): 收敛管理导航与单公司配置体验
这个提交包含在:
@@ -0,0 +1,360 @@
|
||||
import { hasAnyPermission } from "../../auth/permissions";
|
||||
|
||||
export type ManagementGroup = "人员与组织" | "流程与规则" | "系统与审计" | "功能管理";
|
||||
|
||||
type ManagementRouteMatch =
|
||||
| { type: "exact"; path: string }
|
||||
| { type: "prefix"; path: string }
|
||||
| { type: "pattern"; pattern: RegExp };
|
||||
|
||||
export type ManagementRouteDefinition = {
|
||||
key: string;
|
||||
path: string;
|
||||
title: string;
|
||||
parentKey: string | null;
|
||||
group: ManagementGroup | null;
|
||||
icon: string;
|
||||
description: string;
|
||||
anyOf: readonly string[];
|
||||
showOnLanding: boolean;
|
||||
match: ManagementRouteMatch;
|
||||
};
|
||||
|
||||
const MANAGEMENT_CHILD_ROUTES: ManagementRouteDefinition[] = [
|
||||
{
|
||||
key: "people",
|
||||
path: "/people",
|
||||
title: "人员",
|
||||
parentKey: "management",
|
||||
group: "人员与组织",
|
||||
icon: "badge",
|
||||
description: "员工档案、入转调离和人员状态。",
|
||||
anyOf: ["system.users"],
|
||||
showOnLanding: true,
|
||||
match: { type: "exact", path: "/people" },
|
||||
},
|
||||
{
|
||||
key: "people_stats",
|
||||
path: "/people/stats",
|
||||
title: "人员统计",
|
||||
parentKey: "people",
|
||||
group: null,
|
||||
icon: "query_stats",
|
||||
description: "",
|
||||
anyOf: ["system.users"],
|
||||
showOnLanding: false,
|
||||
match: { type: "exact", path: "/people/stats" },
|
||||
},
|
||||
{
|
||||
key: "employee_profile",
|
||||
path: "/people/:employeeId",
|
||||
title: "员工档案",
|
||||
parentKey: "people",
|
||||
group: null,
|
||||
icon: "badge",
|
||||
description: "",
|
||||
anyOf: ["system.users"],
|
||||
showOnLanding: false,
|
||||
match: { type: "pattern", pattern: /^\/people\/[^/]+$/ },
|
||||
},
|
||||
{
|
||||
key: "organization",
|
||||
path: "/organization",
|
||||
title: "组织",
|
||||
parentKey: "management",
|
||||
group: "人员与组织",
|
||||
icon: "account_tree",
|
||||
description: "组织结构、部门和汇报关系。",
|
||||
anyOf: ["system.users", "report.dashboard.view"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/organization" },
|
||||
},
|
||||
{
|
||||
key: "user_accounts",
|
||||
path: "/system/users",
|
||||
title: "账号",
|
||||
parentKey: "management",
|
||||
group: "人员与组织",
|
||||
icon: "manage_accounts",
|
||||
description: "账号、角色和登录权限。",
|
||||
anyOf: ["system.accounts.manage", "system.roles.manage"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/users" },
|
||||
},
|
||||
{
|
||||
key: "departments",
|
||||
path: "/system/departments",
|
||||
title: "部门",
|
||||
parentKey: "management",
|
||||
group: "人员与组织",
|
||||
icon: "account_tree",
|
||||
description: "部门基础数据维护。",
|
||||
anyOf: ["system.users"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/departments" },
|
||||
},
|
||||
{
|
||||
key: "approval_rules",
|
||||
path: "/system/approval-rules",
|
||||
title: "审批规则",
|
||||
parentKey: "management",
|
||||
group: "流程与规则",
|
||||
icon: "rule",
|
||||
description: "金额段、审批层级和处理规则。",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/approval-rules" },
|
||||
},
|
||||
{
|
||||
key: "workflow_definitions",
|
||||
path: "/system/workflow-definitions",
|
||||
title: "流程定义",
|
||||
parentKey: "management",
|
||||
group: "流程与规则",
|
||||
icon: "schema",
|
||||
description: "流程模板和节点治理。",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/workflow-definitions" },
|
||||
},
|
||||
{
|
||||
key: "expense_standards",
|
||||
path: "/system/expense-standards",
|
||||
title: "费用标准",
|
||||
parentKey: "management",
|
||||
group: "流程与规则",
|
||||
icon: "rule_settings",
|
||||
description: "费用上限、类别和管控口径。",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/expense-standards" },
|
||||
},
|
||||
{
|
||||
key: "reimbursement_templates",
|
||||
path: "/system/reimbursement-templates",
|
||||
title: "报销模板",
|
||||
parentKey: "management",
|
||||
group: "流程与规则",
|
||||
icon: "receipt_long",
|
||||
description: "常见报销场景模板。",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/reimbursement-templates" },
|
||||
},
|
||||
{
|
||||
key: "workspace",
|
||||
path: "/system/workspace",
|
||||
title: "工作区",
|
||||
parentKey: "management",
|
||||
group: "系统与审计",
|
||||
icon: "dashboard_customize",
|
||||
description: "菜单入口、模块显隐和小团队基线。",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/workspace" },
|
||||
},
|
||||
{
|
||||
key: "audit_logs",
|
||||
path: "/audit-logs",
|
||||
title: "审计日志",
|
||||
parentKey: "management",
|
||||
group: "系统与审计",
|
||||
icon: "manage_search",
|
||||
description: "关键操作留痕和排障。",
|
||||
anyOf: ["system.audit.view"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/audit-logs" },
|
||||
},
|
||||
{
|
||||
key: "company_information",
|
||||
path: "/system/master-data",
|
||||
title: "主数据",
|
||||
parentKey: "management",
|
||||
group: "功能管理",
|
||||
icon: "dataset",
|
||||
description: "公司信息、成本中心、供应商等基础数据。",
|
||||
anyOf: ["masterdata.view", "masterdata.manage"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/master-data" },
|
||||
},
|
||||
{
|
||||
key: "payment_accounts",
|
||||
path: "/system/payment-accounts",
|
||||
title: "企业银行账户",
|
||||
parentKey: "management",
|
||||
group: "功能管理",
|
||||
icon: "account_balance",
|
||||
description: "维护付款来源账户、脱敏查看与变更留痕。",
|
||||
anyOf: ["payment.account.read", "payment.account.manage"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/payment-accounts" },
|
||||
},
|
||||
{
|
||||
key: "notification_channels",
|
||||
path: "/system/notification-channels",
|
||||
title: "通知渠道",
|
||||
parentKey: "management",
|
||||
group: "功能管理",
|
||||
icon: "notifications_active",
|
||||
description: "企业微信、邮件等补充通知。",
|
||||
anyOf: ["notification.channel.manage"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/notification-channels" },
|
||||
},
|
||||
{
|
||||
key: "feature_flags",
|
||||
path: "/system/feature-flags",
|
||||
title: "功能管理",
|
||||
parentKey: "management",
|
||||
group: "功能管理",
|
||||
icon: "toggle_on",
|
||||
description: "功能开关、灰度和试运行边界。",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: true,
|
||||
match: { type: "prefix", path: "/system/feature-flags" },
|
||||
},
|
||||
{
|
||||
key: "system_announcements",
|
||||
path: "/system/announcements",
|
||||
title: "公告管理",
|
||||
parentKey: "management",
|
||||
group: null,
|
||||
icon: "campaign",
|
||||
description: "",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: false,
|
||||
match: { type: "prefix", path: "/system/announcements" },
|
||||
},
|
||||
{
|
||||
key: "cutover",
|
||||
path: "/system/cutover",
|
||||
title: "切换控制台",
|
||||
parentKey: "management",
|
||||
group: null,
|
||||
icon: "swap_calls",
|
||||
description: "",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: false,
|
||||
match: { type: "prefix", path: "/system/cutover" },
|
||||
},
|
||||
{
|
||||
key: "performance",
|
||||
path: "/performance",
|
||||
title: "绩效管理",
|
||||
parentKey: "management",
|
||||
group: null,
|
||||
icon: "monitoring",
|
||||
description: "",
|
||||
anyOf: ["system.settings"],
|
||||
showOnLanding: false,
|
||||
match: { type: "prefix", path: "/performance" },
|
||||
},
|
||||
];
|
||||
|
||||
function uniquePermissions(routes: readonly ManagementRouteDefinition[]) {
|
||||
return Array.from(new Set(routes.flatMap((route) => route.anyOf)));
|
||||
}
|
||||
|
||||
export const MANAGEMENT_ROOT_PERMISSIONS = uniquePermissions(
|
||||
MANAGEMENT_CHILD_ROUTES.filter((route) => route.showOnLanding),
|
||||
);
|
||||
|
||||
const MANAGEMENT_ROOT_ROUTE: ManagementRouteDefinition = {
|
||||
key: "management",
|
||||
path: "/system/settings",
|
||||
title: "管理",
|
||||
parentKey: null,
|
||||
group: null,
|
||||
icon: "settings",
|
||||
description: "人员、组织、流程、基础资料和系统审计。",
|
||||
anyOf: MANAGEMENT_ROOT_PERMISSIONS,
|
||||
showOnLanding: false,
|
||||
match: { type: "exact", path: "/system/settings" },
|
||||
};
|
||||
|
||||
export const MANAGEMENT_ROUTES: readonly ManagementRouteDefinition[] = [
|
||||
MANAGEMENT_ROOT_ROUTE,
|
||||
...MANAGEMENT_CHILD_ROUTES,
|
||||
];
|
||||
|
||||
export const MANAGEMENT_GROUPS: readonly ManagementGroup[] = [
|
||||
"人员与组织",
|
||||
"流程与规则",
|
||||
"系统与审计",
|
||||
"功能管理",
|
||||
];
|
||||
|
||||
const ROUTES_BY_KEY = new Map(MANAGEMENT_ROUTES.map((route) => [route.key, route]));
|
||||
|
||||
function matchesRoute(route: ManagementRouteDefinition, pathname: string) {
|
||||
if (route.match.type === "pattern") {
|
||||
return route.match.pattern.test(pathname);
|
||||
}
|
||||
if (route.match.type === "exact") {
|
||||
return pathname === route.match.path;
|
||||
}
|
||||
return pathname === route.match.path || pathname.startsWith(`${route.match.path}/`);
|
||||
}
|
||||
|
||||
export function canAccessManagementRoute(route: ManagementRouteDefinition, permissions: string[]) {
|
||||
return hasAnyPermission(permissions, [...route.anyOf]);
|
||||
}
|
||||
|
||||
export function resolveManagementRouteDefinition(pathname: string) {
|
||||
return MANAGEMENT_ROUTES.find((route) => matchesRoute(route, pathname)) ?? null;
|
||||
}
|
||||
|
||||
export function resolveManagementRoute(pathname: string, permissions: string[]) {
|
||||
const route = resolveManagementRouteDefinition(pathname);
|
||||
return route && canAccessManagementRoute(route, permissions) ? route : null;
|
||||
}
|
||||
|
||||
export function isKnownManagementPath(pathname: string) {
|
||||
return Boolean(resolveManagementRouteDefinition(pathname));
|
||||
}
|
||||
|
||||
export function isManagementPath(pathname: string, permissions: string[]) {
|
||||
return Boolean(resolveManagementRoute(pathname, permissions));
|
||||
}
|
||||
|
||||
export function getVisibleManagementItems(permissions: string[]) {
|
||||
return MANAGEMENT_ROUTES.filter(
|
||||
(route) => route.showOnLanding && canAccessManagementRoute(route, permissions),
|
||||
);
|
||||
}
|
||||
|
||||
export type ManagementBreadcrumb = {
|
||||
key: string;
|
||||
title: string;
|
||||
href: string | null;
|
||||
};
|
||||
|
||||
export function resolveManagementBreadcrumbs(pathname: string, permissions: string[]) {
|
||||
const current = resolveManagementRoute(pathname, permissions);
|
||||
if (!current) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const chain: ManagementRouteDefinition[] = [];
|
||||
const visited = new Set<string>();
|
||||
let cursor: ManagementRouteDefinition | undefined = current;
|
||||
while (cursor && !visited.has(cursor.key)) {
|
||||
chain.unshift(cursor);
|
||||
visited.add(cursor.key);
|
||||
cursor = cursor.parentKey ? ROUTES_BY_KEY.get(cursor.parentKey) : undefined;
|
||||
}
|
||||
|
||||
return chain.map((route, index) => ({
|
||||
key: route.key,
|
||||
title: route.title,
|
||||
href: index < chain.length - 1 ? route.path : null,
|
||||
}));
|
||||
}
|
||||
|
||||
export function getManagementRoutePermissions(key: string) {
|
||||
const route = ROUTES_BY_KEY.get(key);
|
||||
if (!route) {
|
||||
throw new Error(`Unknown management route key: ${key}`);
|
||||
}
|
||||
return [...route.anyOf];
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
isKnownManagementPath,
|
||||
resolveManagementRoute,
|
||||
} from "./managementRoutes";
|
||||
|
||||
export const MAIN_NAV_ORDER = ["/portal", "/workflow/tasks", "/reimbursements", "/purchase-requests", "/contracts-registry", "/projects", "/assets", "/payment-batches", "/ap-operations", "/invoice-pool", "/team", "/leave-requests"];
|
||||
export const ADMIN_NAV_ORDER = ["/system/settings"];
|
||||
export const BACKUP_NAV_ORDER = ["/weekly-reports", "/announcements", "/budgets", "/attendance", "/wecom-approval-archive", "/travel-requests", "/inventory-checks", "/dashboard"];
|
||||
@@ -38,19 +43,19 @@ const PAGE_TITLES: Array<{ path: string; title: string; group: string }> = [
|
||||
{ path: "/mobile/reimbursements", title: "移动报销", group: "移动工作台" },
|
||||
{ path: "/mobile", title: "移动首页", group: "移动工作台" },
|
||||
{ path: "/settings/profile", title: "个人设置", group: "账号设置" },
|
||||
{ path: "/system/workspace", title: "工作区设置", group: "管理" },
|
||||
{ path: "/people", title: "人员管理", group: "管理" },
|
||||
{ path: "/organization", title: "组织架构", group: "管理" },
|
||||
{ path: "/system/approval-rules", title: "审批规则", group: "管理" },
|
||||
{ path: "/system/workflow-definitions", title: "流程定义", group: "管理" },
|
||||
{ path: "/system/settings", title: "管理", group: "管理" },
|
||||
{ path: "/system/feature-flags", title: "功能管理", group: "管理" },
|
||||
{ path: "/system/cutover", title: "切换控制台", group: "更多" },
|
||||
{ path: "/dashboard", title: "数据看板", group: "更多" },
|
||||
{ path: "/audit-logs", title: "审计日志", group: "管理" },
|
||||
];
|
||||
|
||||
export function resolvePageMeta(pathname: string) {
|
||||
export function resolvePageMeta(pathname: string, permissions: string[] = []) {
|
||||
const managementRoute = resolveManagementRoute(pathname, permissions);
|
||||
if (managementRoute) {
|
||||
return { title: managementRoute.title, group: "管理" };
|
||||
}
|
||||
|
||||
if (isKnownManagementPath(pathname)) {
|
||||
return { title: "管理", group: "管理" };
|
||||
}
|
||||
|
||||
const matched = [...PAGE_TITLES].sort((left, right) => right.path.length - left.path.length)
|
||||
.find((item) => pathname === item.path || pathname.startsWith(item.path));
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户