import { useEffect, useState, type ReactNode } from "react"; import { AnimatePresence, motion } from "motion/react"; import { BriefcaseBusiness, ChevronRight, CircleUserRound, HelpCircle, LogOut, Mail, MapPinned, Moon, Eye, EyeOff, Phone, Settings, Shield, Sun, User, X, } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { useTheme } from "@/components/ThemeProvider"; import { useIsMobileViewport } from "@/hooks/useIsMobileViewport"; import { useIsWecomBrowser } from "@/hooks/useIsWecomBrowser"; import { clearAuth, getCurrentUser, getProfileOverview, updateCurrentUserProfile, updateCurrentUserPassword, type ProfileOverview, type UpdateCurrentUserProfilePayload, type UpdateCurrentUserPasswordPayload, type UserProfile, } from "@/lib/auth"; type MenuItem = { key: "personal" | "security" | "help"; icon: typeof User; label: string; color: string; bg: string; }; type EditableProfileForm = UpdateCurrentUserProfilePayload; const MENU_ITEMS: MenuItem[] = [ { key: "personal", icon: User, label: "个人资料", color: "text-blue-500 dark:text-blue-400", bg: "bg-blue-50 dark:bg-blue-500/10" }, { key: "security", icon: Shield, label: "账号安全", color: "text-emerald-500 dark:text-emerald-400", bg: "bg-emerald-50 dark:bg-emerald-500/10" }, { key: "help", icon: HelpCircle, label: "帮助中心", color: "text-violet-500 dark:text-violet-400", bg: "bg-violet-50 dark:bg-violet-500/10" }, ]; const EMPTY_FORM: EditableProfileForm = { displayName: "", email: "", phone: "", }; const EMPTY_PASSWORD_FORM: SecurityForm = { oldPassword: "", newPassword: "", confirmPassword: "", }; type SecurityForm = UpdateCurrentUserPasswordPayload & { confirmPassword: string; }; function displayText(value?: string | null) { return value && value.trim() ? value : "无"; } function numericValue(value?: number | null) { return typeof value === "number" && Number.isFinite(value) ? value : 0; } function PageModal({ title, subtitle, onClose, children, footer, }: { title: string; subtitle?: string; onClose: () => void; children: ReactNode; footer?: ReactNode; }) { return ( <> {title} {subtitle ? {subtitle} : null} {children} {footer ? {footer} : null} > ); } export default function Profile() { const { theme, setTheme } = useTheme(); const navigate = useNavigate(); const isMobileViewport = useIsMobileViewport(); const isWecomBrowser = useIsWecomBrowser(); const disableMobileMotion = isMobileViewport || isWecomBrowser; const [overview, setOverview] = useState(null); const [currentUser, setCurrentUser] = useState(null); const [profileDrawerOpen, setProfileDrawerOpen] = useState(false); const [securityDrawerOpen, setSecurityDrawerOpen] = useState(false); const [form, setForm] = useState(EMPTY_FORM); const [detailLoading, setDetailLoading] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(""); const [securityForm, setSecurityForm] = useState(EMPTY_PASSWORD_FORM); const [securitySaving, setSecuritySaving] = useState(false); const [securityError, setSecurityError] = useState(""); const [securitySuccess, setSecuritySuccess] = useState(""); const [showOldPassword, setShowOldPassword] = useState(false); const [showNewPassword, setShowNewPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); useEffect(() => { let ignore = false; async function loadProfileData() { try { const me = await getCurrentUser(); const overviewData = await getProfileOverview().catch(() => null); if (ignore) { return; } setCurrentUser(me); setOverview(overviewData); } catch { if (ignore) { return; } setOverview(null); setCurrentUser(null); } } void loadProfileData(); return () => { ignore = true; }; }, []); const handleLogout = () => { clearAuth(); navigate("/login", { replace: true }); }; const handleNavigateToMonthlyOpportunity = () => { navigate("/opportunities"); }; const handleNavigateToMonthlyExpansion = () => { navigate("/expansion"); }; const handleOpenProfile = async () => { setDetailLoading(true); setError(""); try { const me = currentUser ?? await getCurrentUser(); const latestOverview = overview ?? await getProfileOverview().catch(() => null); setCurrentUser(me); setOverview(latestOverview); setForm({ userId: me.userId, username: me.username, displayName: me.displayName || "", email: me.email || "", phone: me.phone || "", pwdResetRequired: me.pwdResetRequired, isPlatformAdmin: me.isPlatformAdmin, orgId: undefined, }); setProfileDrawerOpen(true); } catch (loadError) { setError(loadError instanceof Error ? loadError.message : "获取个人资料失败"); setProfileDrawerOpen(true); } finally { setDetailLoading(false); } }; const handleCloseProfile = () => { if (saving) { return; } setProfileDrawerOpen(false); setError(""); }; const handleSave = async () => { if (saving) { return; } setSaving(true); setError(""); try { await updateCurrentUserProfile({ ...form, email: form.email || undefined, phone: form.phone || undefined, }); const me = await getCurrentUser(); const overviewData = await getProfileOverview().catch(() => overview); setCurrentUser(me); setOverview(overviewData); setForm({ userId: me.userId, username: me.username, displayName: me.displayName || "", email: me.email || "", phone: me.phone || "", pwdResetRequired: me.pwdResetRequired, isPlatformAdmin: me.isPlatformAdmin, orgId: undefined, }); sessionStorage.setItem("userProfile", JSON.stringify(me)); setProfileDrawerOpen(false); } catch (saveError) { setError(saveError instanceof Error ? saveError.message : "保存失败"); } finally { setSaving(false); } }; const handleOpenSecurity = () => { setSecurityForm(EMPTY_PASSWORD_FORM); setSecurityError(""); setSecuritySuccess(""); setShowOldPassword(false); setShowNewPassword(false); setShowConfirmPassword(false); setSecurityDrawerOpen(true); }; const handleCloseSecurity = () => { if (securitySaving) { return; } setSecurityDrawerOpen(false); setSecurityError(""); setSecuritySuccess(""); }; const handleOpenHelp = () => { window.alert("请联系系统管理员!"); }; const handleUpdatePassword = async () => { if (securitySaving) { return; } if (!securityForm.oldPassword || !securityForm.newPassword || !securityForm.confirmPassword) { setSecurityError("请完整填写旧密码、新密码和确认密码"); setSecuritySuccess(""); return; } if (securityForm.newPassword !== securityForm.confirmPassword) { setSecurityError("两次输入的新密码不一致"); setSecuritySuccess(""); return; } if (securityForm.newPassword.length < 6) { setSecurityError("新密码长度不能少于6位"); setSecuritySuccess(""); return; } setSecuritySaving(true); setSecurityError(""); setSecuritySuccess(""); try { await updateCurrentUserPassword({ oldPassword: securityForm.oldPassword, newPassword: securityForm.newPassword, }); clearAuth(); navigate("/login", { replace: true }); } catch (saveError) { setSecurityError(saveError instanceof Error ? saveError.message : "密码修改失败"); } finally { setSecuritySaving(false); } }; const roleNames = displayText(overview?.jobTitle); const orgNames = displayText(overview?.deptName); const displayName = displayText(currentUser?.displayName || overview?.realName); const avatarText = displayName === "无" ? "无" : displayName.slice(0, 1); const currentEmail = displayText(currentUser?.email); const currentPhone = displayText(currentUser?.phone); return ( 我的 查看个人信息、账号安全设置和本月工作概览。 void handleOpenProfile()} className="absolute right-4 top-4 rounded-full bg-slate-50 p-2 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-600 dark:bg-slate-800 dark:text-slate-500 dark:hover:bg-slate-700 dark:hover:text-slate-300 sm:right-6 sm:top-6" > {avatarText} {displayName} 部门:{orgNames} 岗位:{roleNames} setTheme(theme === "dark" ? "light" : "dark")} className="flex w-full items-center justify-between p-4 transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/50 md:hidden" > {theme === "dark" ? : } {theme === "dark" ? "切换亮色模式" : "切换暗色模式"} {MENU_ITEMS.map((item) => ( void handleOpenProfile() : item.key === "security" ? handleOpenSecurity : item.key === "help" ? handleOpenHelp : undefined } className="flex w-full items-center justify-between p-4 text-left transition-colors hover:bg-slate-50 dark:hover:bg-slate-800/50" > {item.label} ))} 退出登录 {profileDrawerOpen ? ( 取消 void handleSave()} disabled={saving || detailLoading} className="crm-btn crm-btn-primary disabled:cursor-not-allowed disabled:opacity-60" > {saving ? "保存中..." : "保存资料"} )} > {error && !currentUser ? ( {error} ) : ( {(form.displayName || currentUser?.displayName || "无").slice(0, 1)} {displayText(currentUser?.displayName)} 部门:{orgNames} 岗位:{roleNames} 账号:{displayText(currentUser?.username)} 显示名称 setForm((current) => ({ ...current, displayName: event.target.value }))} className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50" /> 用户名 {displayText(currentUser?.username)} 手机号 setForm((current) => ({ ...current, phone: event.target.value }))} className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50" /> 邮箱 setForm((current) => ({ ...current, email: event.target.value }))} className="crm-input-box crm-input-text w-full border border-slate-200 bg-white outline-none focus:border-violet-500 focus:ring-1 focus:ring-violet-500 dark:border-slate-800 dark:bg-slate-900/50" /> 岗位信息 岗位:{roleNames} 状态:{displayText(overview?.accountStatus)} 组织信息 部门:{orgNames} 入职天数:{numericValue(overview?.onboardingDays)} 当前手机号:{currentPhone} 当前邮箱:{currentEmail} {detailLoading ? ( 正在加载个人资料... ) : null} {error ? ( {error} ) : null} )} ) : null} {securityDrawerOpen ? ( 关闭 void handleUpdatePassword()} disabled={securitySaving} className="crm-btn crm-btn-success disabled:cursor-not-allowed disabled:opacity-60" > {securitySaving ? "提交中..." : "更新密码"} )} > 为了账号安全,请先输入当前密码,再设置新密码。 当前密码 setSecurityForm((current) => ({ ...current, oldPassword: event.target.value }))} className="crm-input-box crm-input-text w-full border border-slate-200 bg-white pr-12 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 dark:border-slate-800 dark:bg-slate-900/50" /> setShowOldPassword((current) => !current)} className="absolute inset-y-0 right-0 flex w-12 items-center justify-center text-slate-400 transition-colors hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300" aria-label={showOldPassword ? "隐藏当前密码" : "显示当前密码"} > {showOldPassword ? : } 新密码 setSecurityForm((current) => ({ ...current, newPassword: event.target.value }))} className="crm-input-box crm-input-text w-full border border-slate-200 bg-white pr-12 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 dark:border-slate-800 dark:bg-slate-900/50" /> setShowNewPassword((current) => !current)} className="absolute inset-y-0 right-0 flex w-12 items-center justify-center text-slate-400 transition-colors hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300" aria-label={showNewPassword ? "隐藏新密码" : "显示新密码"} > {showNewPassword ? : } 确认新密码 setSecurityForm((current) => ({ ...current, confirmPassword: event.target.value }))} className="crm-input-box crm-input-text w-full border border-slate-200 bg-white pr-12 outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 dark:border-slate-800 dark:bg-slate-900/50" /> setShowConfirmPassword((current) => !current)} className="absolute inset-y-0 right-0 flex w-12 items-center justify-center text-slate-400 transition-colors hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300" aria-label={showConfirmPassword ? "隐藏确认密码" : "显示确认密码"} > {showConfirmPassword ? : } {securityError ? ( {securityError} ) : null} {securitySuccess ? ( {securitySuccess} ) : null} ) : null} ); }
{subtitle}
查看个人信息、账号安全设置和本月工作概览。
部门:{orgNames}
岗位:{roleNames}
状态:{displayText(overview?.accountStatus)}
入职天数:{numericValue(overview?.onboardingDays)}
为了账号安全,请先输入当前密码,再设置新密码。