import { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { useAuth } from "../App"; import { SideRail } from "../components/SideRail"; import { Button, Field, InlineAlert } from "../components/ui"; import { ApiError, api } from "../lib/client"; import type { User } from "../lib/types"; /** * 账号设置:昵称、邮箱、登录密码、退出登录。 * * 邮箱是**账号名**(能拿它 + 密码登录),不是"通知地址"的附属品, * 所以放在基本信息里、和手机号并列,而不是藏在通知偏好里。 */ /** 小节标题:一行标题 + 一句"这是干嘛的",比光秃秃的 h2 省解释。 */ function SectionLabel({ hint, children }: { hint?: string; children: React.ReactNode }) { return (

{children}

{hint ?

{hint}

: null}
); } export function AccountPage() { const { user, loading, reload, signOut } = useAuth(); const navigate = useNavigate(); const [nickname, setNickname] = useState(""); const [email, setEmail] = useState(""); const [profileBusy, setProfileBusy] = useState(false); const [profileMsg, setProfileMsg] = useState(null); const [profileError, setProfileError] = useState(null); const [oldPassword, setOldPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); const [confirm, setConfirm] = useState(""); const [pwdBusy, setPwdBusy] = useState(false); const [pwdMsg, setPwdMsg] = useState(null); const [pwdError, setPwdError] = useState(null); useEffect(() => { if (!loading && !user) navigate("/login", { replace: true }); }, [loading, user, navigate]); useEffect(() => { if (user?.nickname) setNickname(user.nickname); setEmail(user?.email ?? ""); }, [user?.nickname, user?.email]); const hasPassword = Boolean(user?.has_password); const nicknameChanged = nickname.trim() !== (user?.nickname ?? ""); const emailChanged = email.trim() !== (user?.email ?? ""); async function saveProfile(event: React.FormEvent) { event.preventDefault(); setProfileBusy(true); setProfileMsg(null); setProfileError(null); try { // 只发改过的字段:邮箱留空表示"解绑",不能因为没动它就被清掉 const patch: { nickname?: string; email?: string } = {}; if (nicknameChanged) patch.nickname = nickname.trim(); if (emailChanged) patch.email = email.trim(); await api.patch("/auth/me", patch); await reload(); setProfileMsg( emailChanged && !email.trim() ? "邮箱已解绑,之后不能用邮箱登录" : "资料已更新", ); } catch (err) { setProfileError(err instanceof ApiError ? err.message : "保存失败"); } finally { setProfileBusy(false); } } async function savePassword(event: React.FormEvent) { event.preventDefault(); setPwdMsg(null); setPwdError(null); if (newPassword !== confirm) { setPwdError("两次输入的新密码不一样"); return; } setPwdBusy(true); try { await api.post("/auth/password", { password: newPassword, ...(hasPassword ? { old_password: oldPassword } : {}), }); await reload(); setOldPassword(""); setNewPassword(""); setConfirm(""); setPwdMsg(hasPassword ? "密码已修改,下次可用新密码登录" : "密码已设置,之后可以用密码登录"); } catch (err) { setPwdError(err instanceof ApiError ? err.message : "保存失败"); } finally { setPwdBusy(false); } } return (

账号设置

这里改的是你自己:昵称、登录密码、退出登录。工作区里的成员与权限在各自的工作区里管。

基本信息
{(user?.nickname ?? "我").trim().slice(0, 1).toUpperCase()}
{user?.nickname ?? "—"}
{user?.created_at ? `加入于 ${new Date(user.created_at).toLocaleDateString("zh-CN")}` : "—"}
setNickname(e.target.value)} />

手机号就是登录账号,暂不支持自助换号;换号等于换账号,需要单独处理。

setEmail(e.target.value.trim())} />

邮箱是第二个账号名:绑上之后,登录页可以用「邮箱 + 密码」进入。留空保存就是解绑。

{profileMsg && {profileMsg}} {profileError && {profileError}}
登录密码

{hasPassword ? "已设置密码。登录页可以选「密码」进入;改密码要先输当前密码。" : "还没有设置密码。现在只能用手机号 + 验证码登录;设一个密码,换手机或短信不通时也能进。"}

{hasPassword ? ( setOldPassword(e.target.value)} /> ) : null} setNewPassword(e.target.value)} /> setConfirm(e.target.value)} /> {pwdMsg && {pwdMsg}} {pwdError && {pwdError}}
退出

退出只会清掉这台浏览器里的令牌;工作区里的数据不受影响。

{user?.platform_admin ? (
系统设置

发信邮箱(SMTP)在那儿改:填完可以当场发一封测试邮件,确认通道通不通。 改完即时生效,不用登服务器改配置、也不用重启。

打开系统设置 →
) : null}
); }