Skip to content

feat(settings): /settings 用户偏好编辑页#278

Merged
longsizhuo merged 9 commits intomainfrom
feature/user-preferences
Apr 14, 2026
Merged

feat(settings): /settings 用户偏好编辑页#278
longsizhuo merged 9 commits intomainfrom
feature/user-preferences

Conversation

@longsizhuo
Copy link
Copy Markdown
Member

Summary

  • 新增 `/settings` 路由,Server Component 包壳 + Client Form 编辑
  • 未登录重定向 `/login?redirect=/settings`
  • 编辑 theme / language / aiDefaultProvider,保存后立即同步到 ThemeProvider(主题)
  • next.config.mjs 新增 `/api/user-center/*` rewrite 到后端
  • Newspaper 设计风格,骨架屏 + 错误 toast

Test plan

  • `pnpm lint && pnpm typecheck && pnpm test` 全过
  • 本地 3010 + 后端 8081 端到端:登录 admin → 访问 /settings → 编辑保存 → 刷新持久化

依赖

后端 PR: involutionhell-backend#feature/user-preferences

Copilot AI review requested due to automatic review settings April 14, 2026 18:47
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
involutionhell-github-io Ready Ready Preview, Comment Apr 14, 2026 9:13pm
website-preview Ready Ready Preview, Comment Apr 14, 2026 9:13pm

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 为应用新增 /settings 用户偏好编辑页,通过 Next.js rewrite 代理后端「用户中心」API,实现偏好(theme / language / aiDefaultProvider)的读取与保存,并在保存后同步主题到 ThemeProvider

Changes:

  • 新增 /settings 路由:Server Component 页面壳 + Client Component 表单交互
  • 新增 /api/user-center/* → 后端的 rewrite,用于偏好设置等接口同源访问
  • 在设置页引入骨架屏与 toast 提示,提供加载/保存反馈

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
next.config.mjs 增加 /api/user-center/:path* rewrite 到后端,避免浏览器跨域
app/settings/page.tsx 新增设置页 Server Component 容器(Header/Footer + 表单挂载)
app/settings/SettingsForm.tsx 新增偏好表单:拉取/保存偏好、toast、骨架屏、保存后同步 ThemeProvider

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2 to +33
// 未登录时重定向到 /login?redirect=/settings
import { cookies } from "next/headers";
import { Header } from "@/app/components/Header";
import { Footer } from "@/app/components/Footer";
import { SettingsForm } from "./SettingsForm";

async function getServerUser() {
const cookieStore = await cookies();
const token = cookieStore.get("satoken")?.value;
if (!token || !process.env.BACKEND_URL) return null;
try {
const res = await fetch(`${process.env.BACKEND_URL}/auth/me`, {
headers: { satoken: token },
cache: "no-store",
});
if (!res.ok) return null;
const body = await res.json();
return body?.data ?? null;
} catch {
return null;
}
}

export default async function SettingsPage() {
const user = await getServerUser();

// satoken 存在于 localStorage 而非 cookie,服务端无法读取
// 因此此处 user 可能为 null;实际登录态由客户端 SettingsForm 内部处理
// 仅当能从服务端确认已登出时才重定向,避免误跳转
// (大多数情况下 user 为 null 是正常的,由客户端 useAuth 判断)
void user;

@@ -0,0 +1,53 @@
// 用户偏好设置页(Server Component)
// 未登录时重定向到 /login?redirect=/settings
Comment on lines +61 to +65
useEffect(() => {
if (status !== "authenticated") return;
const token = getToken();
if (!token) return;

})
.then((body) => {
if (body?.success && body?.data) {
setPrefs({ ...DEFAULT_PREFS, ...body.data });
Comment on lines +84 to +87
function showToast(type: "success" | "error", msg: string) {
setToast({ type, msg });
setTimeout(() => setToast(null), 3000);
}

async function handleSave() {
const token = getToken();
if (!token) return;
longsizhuo added a commit that referenced this pull request Apr 14, 2026
Copilot CR #278:
- page.tsx 删除 getServerUser 死代码:token 存 localStorage 服务端读不到,注释误导
- SettingsForm: token 缺失时 setLoading(false) 结束骨架屏 + 提示 + 跳登录页
- SettingsForm: 加载到偏好后立刻 setTheme,让已保存 theme 与当前主题一致
- SettingsForm: 用 useRef 存 toast timer,新 toast/卸载时 clearTimeout,避免 setState on unmounted
- SettingsForm: handleSave 的 token 缺失分支也给 toast + 跳登录,与加载逻辑一致
Sa-Token 后端配置 sa-token.token-name=satoken,从 HTTP header 中读取的是裸
satoken 字段;之前写成 x-satoken 是 Next.js /api/analytics 内部 resolveUserId
的约定,两者不通用。调后端 /api/user-center/* 必须走 Sa-Token 本尊的 header 名。
Copilot CR #278:
- page.tsx 删除 getServerUser 死代码:token 存 localStorage 服务端读不到,注释误导
- SettingsForm: token 缺失时 setLoading(false) 结束骨架屏 + 提示 + 跳登录页
- SettingsForm: 加载到偏好后立刻 setTheme,让已保存 theme 与当前主题一致
- SettingsForm: 用 useRef 存 toast timer,新 toast/卸载时 clearTimeout,避免 setState on unmounted
- SettingsForm: handleSave 的 token 缺失分支也给 toast + 跳登录,与加载逻辑一致
接前 commit:page.tsx 里 getServerUser 从 cookie 读 satoken,但本项目 token 存
localStorage,服务端拿不到,user 变量被 void 掉也没用。直接删死代码,页面纯
Server Component 壳,登录态由 SettingsForm 的 useAuth 处理。
之前只做了 /settings 页面本身,没任何入口,用户只能手打 URL 才能访问。
在登录用户右上角 Avatar 下拉菜单里加一条「设置」,指向 /settings。
位置:账号信息下方、GitHub 切换/登出之上,符合常见产品 pattern。
之前用 bg-popover / text-foreground / bg-muted 等语义色 token,
在当前主题下 popover 色与 background 几乎同色(白-近白 / 黑-近黑),
面板看起来像透明的悬浮,文字勉强可辨。

改为显式 neutral 色阶:
- 容器:bg-white dark:bg-neutral-900
- 边框:border-neutral-200 dark:border-neutral-700
- 账号信息区:bg-neutral-50 dark:bg-neutral-800 做背景分层
- 文字:text-neutral-900 dark:text-neutral-100
- hover:bg-neutral-100 dark:bg-neutral-800
- 登出按钮加 border-t 与上面其它选项分隔

阴影从 shadow-lg 升到 shadow-xl 让浮层从下方内容里跳出来。
SignInButton 之前 fallback 到 http://localhost:8080,在后端跑 8081 的本地
环境就直接打不通。参考 /analytics、/auth、/api/user-center 的 rewrite pattern,
改用同源 /oauth/render/github,next.config.mjs 补一条 /oauth/:path* → BACKEND_URL。
前端不再关心后端端口,换端口时只改 .env 里 BACKEND_URL 即可。

仍然存在的限制(不在本 PR 范围):GitHub OAuth app 注册的 callback URL 是
localhost:3000/api/auth/callback/github,前端换端口跑(如本机 3010)时需要
到 GitHub OAuth app 里补一条 callback URL,否则授权回来依旧 404。
@longsizhuo longsizhuo force-pushed the feature/user-preferences branch from 83c6c52 to 766ccbb Compare April 14, 2026 21:02
@longsizhuo longsizhuo merged commit ed1817a into main Apr 14, 2026
6 of 8 checks passed
@longsizhuo longsizhuo deleted the feature/user-preferences branch April 14, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants