R51 移动端增强:入口页、底部导航与审批批量处理

这个提交包含在:
2026-04-05 22:20:08 +08:00
父节点 47e10e6acc
当前提交 ba29ede573
共修改 12 个文件,包含 1168 行新增和 163 行删除
+98
查看文件
@@ -0,0 +1,98 @@
import { Link } from "react-router-dom";
import { useMutation, useQuery } from "@tanstack/react-query";
import { toast } from "sonner";
import { updateNotificationPreferences } from "../api/auth";
import { fetchEmployeeProfile } from "../api/employees";
import { mapApiErrorMessage } from "../api/errorMessages";
import { isApiError } from "../api/types";
import { useAuth } from "../auth/AuthContext";
import { EmployeeAvatar } from "../components/common/EmployeeAvatar";
import { PageHeader } from "../components/common/PageHeader";
import { PageScaffold } from "../components/patterns/PageScaffold";
import { SectionPanel } from "../components/patterns/SectionPanel";
import { Button } from "../components/ui/button";
function resolveErrorMessage(error: unknown) {
if (isApiError(error)) {
return `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
}
if (error instanceof Error) {
return error.message;
}
return "请求失败,请稍后重试。";
}
export function MobileMePage() {
const { me, refreshMe } = useAuth();
const profileQuery = useQuery({
enabled: Boolean(me?.id),
queryKey: ["mobile", "me", "profile", me?.id],
queryFn: () => fetchEmployeeProfile(me!.id),
});
const notificationPreferenceMutation = useMutation({
mutationFn: (notificationPushEnabled: boolean) => updateNotificationPreferences({ notificationPushEnabled }),
onSuccess: async () => {
await refreshMe();
toast.success("外部推送偏好已更新");
},
onError: (error) => {
toast.error(resolveErrorMessage(error));
},
});
const profile = profileQuery.data?.employee;
const departmentName = profile?.department?.name ?? (me?.departmentId ? "已关联组织" : "待管理员维护");
return (
<PageScaffold className="space-y-4">
<PageHeader
title="我的"
subtitle="查看个人信息、进入完整档案并管理外部推送开关。"
/>
<SectionPanel title="个人信息" description="头像与部门信息从完整员工档案读取。">
<div className="flex items-center gap-4">
<EmployeeAvatar name={me?.name ?? "员工"} src={profile?.avatarUrl} size="xl" />
<div className="min-w-0 space-y-1">
<div className="text-lg font-semibold text-zinc-900">{me?.name ?? "-"}</div>
<div className="text-sm text-zinc-500">{departmentName}</div>
{me?.employeeNo ? <div className="text-xs text-zinc-400">员工号:{me.employeeNo}</div> : null}
</div>
</div>
{profileQuery.isLoading ? <p className="mt-4 text-sm text-zinc-500">加载档案信息中...</p> : null}
{profileQuery.isError ? (
<p className="mt-4 rounded-xl border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
{resolveErrorMessage(profileQuery.error)}
</p>
) : null}
</SectionPanel>
<SectionPanel title="完整员工档案" description="办公地点、紧急联系人、绩效和薪资等完整信息仍在员工档案页维护或查看。">
{me?.id ? (
<Button asChild type="button" className="w-full">
<Link to={`/people/${me.id}`}>进入完整员工档案</Link>
</Button>
) : null}
</SectionPanel>
<SectionPanel title="外部通知推送" description="关闭后仅停止外部渠道推送,站内通知仍保留。">
<label className="flex items-center gap-3 text-sm text-zinc-800">
<input
type="checkbox"
className="h-4 w-4 rounded border-zinc-300"
checked={Boolean(me?.notificationPushEnabled ?? true)}
disabled={!me || notificationPreferenceMutation.isPending}
onChange={(event) => notificationPreferenceMutation.mutate(event.target.checked)}
/>
接收外部渠道推送
</label>
<p className="mt-3 text-sm leading-6 text-zinc-500">
适用于审批待办、报销付款和资产流转提醒。关闭后不会影响站内通知或系统登录。
</p>
</SectionPanel>
</PageScaffold>
);
}