R110/A2,A3 补齐手机和PC路由分流

这个提交包含在:
2026-05-08 02:25:14 +08:00
父节点 3354608e20
当前提交 ee59ecfc45
共修改 9 个文件,包含 298 行新增和 17 行删除
+6 -3
查看文件
@@ -6,11 +6,14 @@ import { Sidebar } from "../components/navigation/Sidebar";
import { Topbar } from "../components/navigation/Topbar";
import { resolvePageMeta } from "../features/navigation/schema";
import { writeRecentVisit } from "../lib/recentVisits";
import { shouldPreferMobileWorkplace } from "../lib/runtimeContext";
import { resolveMobileEquivalentPath, shouldPreferMobileWorkplace } from "../lib/runtimeContext";
export function AppShellLayout() {
const location = useLocation();
const isMobileRoute = location.pathname.startsWith("/mobile");
const mobileEquivalentPath = !isMobileRoute && shouldPreferMobileWorkplace()
? resolveMobileEquivalentPath(location.pathname, location.search)
: null;
useEffect(() => {
const pathname = location.pathname;
@@ -20,8 +23,8 @@ export function AppShellLayout() {
writeRecentVisit(pathname, resolvePageMeta(pathname).title);
}, [location.pathname]);
if (location.pathname === "/portal" && shouldPreferMobileWorkplace()) {
return <Navigate to="/mobile" replace />;
if (mobileEquivalentPath && mobileEquivalentPath !== `${location.pathname}${location.search}`) {
return <Navigate to={mobileEquivalentPath} replace />;
}
return (
+85
查看文件
@@ -36,3 +36,88 @@ export function shouldPreferMobileWorkplace() {
export function resolveDefaultWorkspacePath() {
return shouldPreferMobileWorkplace() ? "/mobile" : "/portal";
}
function encodeSegment(value: string) {
return encodeURIComponent(value);
}
export function resolveMobileEquivalentPath(pathname: string, search = "") {
const normalizedPath = pathname.replace(/\/+$/, "") || "/";
if (normalizedPath === "/" || normalizedPath === "/portal" || normalizedPath === "/dashboard") {
return "/mobile";
}
if (normalizedPath === "/notifications") {
return "/mobile/notifications";
}
if (normalizedPath === "/settings/profile") {
return "/mobile/profile";
}
if (normalizedPath === "/reimbursements" || normalizedPath === "/reimbursements/dashboard") {
return "/mobile/reimbursements";
}
const reimbursementPrintMatch = normalizedPath.match(/^\/reimbursements\/([^/]+)\/print$/);
if (reimbursementPrintMatch?.[1]) {
return `/mobile/reimbursements/${encodeSegment(reimbursementPrintMatch[1])}`;
}
const reimbursementDetailMatch = normalizedPath.match(/^\/reimbursements\/([^/]+)$/);
if (reimbursementDetailMatch?.[1]) {
return `/mobile/reimbursements/${encodeSegment(reimbursementDetailMatch[1])}`;
}
if (normalizedPath === "/leave-requests") {
return "/mobile/leave";
}
if (normalizedPath === "/invoice-pool") {
return "/mobile/capture";
}
const workflowTaskMatch = normalizedPath.match(/^\/workflow\/tasks\/([^/]+)$/);
if (workflowTaskMatch?.[1]) {
return `/mobile/approvals?${new URLSearchParams({ taskId: workflowTaskMatch[1] }).toString()}`;
}
if (normalizedPath === "/workflow/tasks") {
return "/mobile/approvals";
}
if (normalizedPath === "/assets" || normalizedPath === "/inventory-checks") {
return "/mobile/assets/scan";
}
if (
normalizedPath.startsWith("/system") ||
normalizedPath.startsWith("/people") ||
normalizedPath.startsWith("/organization") ||
normalizedPath.startsWith("/payroll") ||
normalizedPath.startsWith("/performance")
) {
return "/mobile/me";
}
if (
normalizedPath.startsWith("/projects") ||
normalizedPath.startsWith("/contracts-registry") ||
normalizedPath.startsWith("/purchase-requests") ||
normalizedPath.startsWith("/travel-requests") ||
normalizedPath.startsWith("/payment-batches") ||
normalizedPath.startsWith("/ap-operations") ||
normalizedPath.startsWith("/weekly-reports") ||
normalizedPath.startsWith("/todos") ||
normalizedPath.startsWith("/team") ||
normalizedPath.startsWith("/attendance") ||
normalizedPath.startsWith("/budgets") ||
normalizedPath.startsWith("/announcements") ||
normalizedPath.startsWith("/delegations")
) {
return "/mobile";
}
return search ? `${pathname}${search}` : pathname;
}
+4 -4
查看文件
@@ -382,11 +382,11 @@ export function MobileAssetScanPage() {
const actions = (
<div className="flex flex-wrap gap-2">
{isInventoryMode ? (
<Button type="button" variant="secondary" onClick={() => navigate("/inventory-checks")}>
<Button type="button" variant="secondary" onClick={() => navigate("/mobile/assets/scan")}>
返回盘点页
</Button>
) : (
<Button type="button" variant="secondary" onClick={() => navigate("/assets")}>
<Button type="button" variant="secondary" onClick={() => navigate("/mobile")}>
返回资产台账
</Button>
)}
@@ -509,8 +509,8 @@ export function MobileAssetScanPage() {
<h3 className="text-lg font-semibold text-zinc-900">{selectedAsset.assetNo}</h3>
<p className="text-sm text-zinc-500">{selectedAsset.name}</p>
</div>
<Button type="button" variant="secondary" onClick={() => navigate("/assets")}>
打开资产台账
<Button type="button" variant="secondary" onClick={() => navigate("/mobile/assets/scan")}>
继续扫码
</Button>
</div>
+7 -7
查看文件
@@ -101,7 +101,7 @@ export function MobileMePage() {
<SectionPanel title="账号与绑定">
<div className="grid gap-2">
<MobileActionLink to="/settings/profile" label="个人设置" description="企业微信、账号与通知" icon="manage_accounts" />
<MobileActionLink to="/mobile/profile" label="个人设置" description="企业微信、账号与通知" icon="manage_accounts" />
{me?.id ? (
<MobileActionLink to={`/people/${me.id}`} label="员工档案" description="办公地点和紧急联系人" icon="badge" />
) : null}
@@ -110,21 +110,21 @@ export function MobileMePage() {
<SectionPanel title="更多入口">
<div className="grid grid-cols-2 gap-2" data-testid="mobile-more-links">
<MobileActionLink to="/notifications" label="通知中心" icon="notifications" />
<MobileActionLink to="/mobile/notifications" label="通知中心" icon="notifications" />
<MobileActionLink to="/mobile/leave" label="请假" icon="event_available" />
<MobileActionLink to="/mobile/reimbursements" label="报销" icon="request_quote" />
{canAccessApprovals ? <MobileActionLink to="/mobile/approvals" label="审批" icon="fact_check" /> : null}
{canAccessAssets ? <MobileActionLink to="/assets" label="资产" icon="inventory_2" /> : null}
{canAccessProjects ? <MobileActionLink to="/projects" label="项目" icon="workspaces" /> : null}
{canAccessAssets ? <MobileActionLink to="/mobile/assets/scan" label="资产扫码" icon="inventory_2" /> : null}
{canAccessProjects ? <MobileActionLink to="/mobile" label="项目动态" icon="workspaces" /> : null}
</div>
</SectionPanel>
{canAccessAdmin ? (
<SectionPanel title="管理入口">
<div className="grid grid-cols-2 gap-2">
<MobileActionLink to="/people" label="人员管理" icon="group" />
<MobileActionLink to="/organization" label="组织架构" icon="account_tree" />
<MobileActionLink to="/system/settings" label="系统设置" icon="settings" />
<MobileActionLink to="/mobile/me" label="人员管理" icon="group" />
<MobileActionLink to="/mobile/me" label="组织架构" icon="account_tree" />
<MobileActionLink to="/mobile/me" label="系统设置" icon="settings" />
</div>
</SectionPanel>
) : null}
+2 -2
查看文件
@@ -356,7 +356,7 @@ export function MobilePortalPage() {
<div className="mb-3 flex items-center justify-between gap-3">
<div className="text-base font-semibold text-zinc-900">通知</div>
<Button asChild size="sm" type="button" variant="secondary">
<Link to="/notifications">全部</Link>
<Link to="/mobile/notifications">全部</Link>
</Button>
</div>
@@ -374,7 +374,7 @@ export function MobilePortalPage() {
{notifications.map((item) => (
<Link
key={item.id}
to="/notifications"
to="/mobile/notifications"
className="block rounded-lg border border-zinc-200 px-3 py-2 text-sm"
>
<div className="flex items-center justify-between gap-3">
+4
查看文件
@@ -303,6 +303,10 @@ export function AppRouter() {
<Route path="mobile/me" element={withRouteFallback(<MobileMePage />)} />
<Route path="mobile/profile" element={<ProfilePage />} />
<Route path="mobile/notifications" element={withRouteFallback(<NotificationsPage />)} />
<Route
path="mobile/capture"
element={