R51 移动端增强:入口页、底部导航与审批批量处理
这个提交包含在:
@@ -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>
|
||||
);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户