Files
jiang13-bbs/frontend/app/settings/ProfileForm.tsx
freefire e2e5ba077c feat: 添加用户个人资料编辑功能,优化页面布局与导航
1. 后端新增用户资料更新接口,支持修改昵称、头像、邮箱和签名
2. 前端新增资料编辑表单页面,适配响应式布局
3. 优化帖子详情页布局与移动端适配逻辑
4. 重构首页侧边栏导航,使用卡片式板块展示
5. 移除全局板块缓存,调整Header导航移除板块菜单
6. 补充用户签名展示功能,完善用户信息返回字段
2026-09-14 16:10:38 +08:00

146 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { apiUpdateProfile, type User } from "@/lib/api";
interface ProfileFormProps {
user: User;
}
/**
* 个人资料编辑表单:昵称、头像 URL、邮箱、签名。
* 提交成功后刷新 layout 使 Header 同步最新昵称/头像,并在页面内显示成功提示。
*/
export default function ProfileForm({ user }: ProfileFormProps) {
const router = useRouter();
const [nickname, setNickname] = useState(user.nickname || "");
const [avatar, setAvatar] = useState(user.avatar || "");
const [email, setEmail] = useState(user.email || "");
const [signature, setSignature] = useState(user.signature || "");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setSuccess("");
const trimmed = nickname.trim();
if (!trimmed) {
setError("昵称不能为空");
return;
}
if (trimmed.length > 64) {
setError("昵称不能超过 64 个字符");
return;
}
if (signature.length > 255) {
setError("签名不能超过 255 个字符");
return;
}
setSubmitting(true);
try {
const res = await apiUpdateProfile({
nickname: trimmed,
avatar: avatar.trim(),
email: email.trim(),
signature,
});
if (res.user) {
setSuccess("资料已保存");
// 刷新 layout 让 Header 同步最新昵称/头像
router.refresh();
} else {
setError(res.error || "保存失败");
}
} catch {
setError("网络错误,请重试");
} finally {
setSubmitting(false);
}
};
return (
<>
<p className="font-bold text-[15px] mb-3.5" style={{ color: "var(--ink)" }}>
个人资料
</p>
<form onSubmit={handleSubmit} className="panel p-5 sm:p-6 space-y-5">
{success && <div className="alert-ok" role="status">{success}</div>}
{error && <div className="alert-error" role="alert">{error}</div>}
<div>
<label className="field-label" htmlFor="pf-nickname">昵称</label>
<input
id="pf-nickname"
type="text"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
required
maxLength={64}
className="field"
placeholder="请输入昵称"
/>
</div>
<div>
<label className="field-label" htmlFor="pf-avatar">头像链接</label>
<input
id="pf-avatar"
type="url"
value={avatar}
onChange={(e) => setAvatar(e.target.value)}
maxLength={512}
className="field"
placeholder="https://example.com/avatar.png"
/>
<p className="meta mt-2">填写图片 URL,留空则使用默认字母头像</p>
</div>
<div>
<label className="field-label" htmlFor="pf-email">邮箱</label>
<input
id="pf-email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
maxLength={128}
className="field"
placeholder="your@email.com"
/>
</div>
<div>
<label className="field-label" htmlFor="pf-signature">
个性签名
<span className="font-normal ml-1.5" style={{ color: "var(--ink-3)" }}>
({signature.length}/255)
</span>
</label>
<textarea
id="pf-signature"
value={signature}
onChange={(e) => setSignature(e.target.value)}
maxLength={255}
rows={3}
className="field"
placeholder="介绍一下自己吧"
/>
</div>
<button
type="submit"
disabled={submitting}
className="btn btn-primary w-full justify-center mt-1"
>
{submitting ? "保存中..." : "保存资料"}
</button>
</form>
</>
);
}