From 5ca8eb5ba618bb37e1e4c075ad76e734069e4d5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8F=9C=E9=B8=9F=E7=94=9F=E4=BA=A7?= Date: Wed, 18 Mar 2026 12:49:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=9D=A2=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=AA=E4=BA=BA=E4=BF=A1=E6=81=AF=E5=92=8C?= =?UTF-8?q?=E5=AF=86=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/auth.py | 28 +++- config/VERSION | 2 +- frontend/src/App.jsx | 2 + frontend/src/pages/Settings.jsx | 268 ++++++++++++++++++++++++++++++++ 4 files changed, 297 insertions(+), 3 deletions(-) create mode 100644 frontend/src/pages/Settings.jsx diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index fa4fdd1..f77cf67 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -1,9 +1,9 @@ # 认证路由 - 使用字段编码 -from fastapi import APIRouter, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, status, Body from fastapi.security import OAuth2PasswordRequestForm from sqlalchemy.orm import Session from app.core.database import get_db -from app.core.auth import verify_password, create_access_token, get_password_hash +from app.core.auth import verify_password, create_access_token, get_password_hash, get_current_user from app.models.models import User from app.schemas.schemas import Token, UserCreate, UserResponse @@ -94,3 +94,27 @@ def get_current_user_info( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="请使用正确的依赖注入" ) + + +@router.post("/change-password") +def change_password( + old_password: str = Body(...), + new_password: str = Body(...), + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """修改当前用户密码""" + from app.core.auth import verify_password, get_password_hash + + # 验证旧密码 + if not verify_password(old_password, current_user.password): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="当前密码错误" + ) + + # 更新密码 + current_user.password = get_password_hash(new_password) + db.commit() + + return {"message": "密码修改成功"} diff --git a/config/VERSION b/config/VERSION index 0b9bd91..2da7100 100644 --- a/config/VERSION +++ b/config/VERSION @@ -2,7 +2,7 @@ # Version Configuration for Zodiac Collection Management System # 当前版本号 (语义化版本:主版本。次版本.修订版) -VERSION=1.0.7 +VERSION=1.0.8 # 版本代号 (可选) VERSION_CODENAME="新生" diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c0fc23a..375b78d 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from 'react' import Home from './pages/Home' +import Settings from './pages/Settings' import List from './pages/List' import Add from './pages/Add' import Stats from './pages/Stats' @@ -31,6 +32,7 @@ export default function App() { if (basePath === '/list') return if (basePath === '/add') return if (basePath === '/login') return + if (basePath === '/settings') return if (basePath === '/admin') return if (basePath.startsWith('/edit')) return if (basePath.startsWith('/detail')) return diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx new file mode 100644 index 0000000..f9f0bd9 --- /dev/null +++ b/frontend/src/pages/Settings.jsx @@ -0,0 +1,268 @@ +import React, { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' + +export default function Settings() { + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [error, setError] = useState('') + const [success, setSuccess] = useState('') + + const [form, setForm] = useState({ + username: '', + email: '', + phone: '', + bio: '' + }) + + const [passwordForm, setPasswordForm] = useState({ + oldPassword: '', + newPassword: '', + confirmPassword: '' + }) + + const navigate = useNavigate() + const token = localStorage.getItem('token') + + useEffect(() => { + fetchUserInfo() + }, []) + + const fetchUserInfo = async () => { + try { + const res = await fetch('/api/users/me', { + headers: { 'Authorization': 'Bearer ' + token } + }) + if (res.ok) { + const data = await res.json() + setForm({ + username: data.username || '', + email: data.email || '', + phone: data.phone || '', + bio: data.bio || '' + }) + } + } catch (e) { + setError('获取用户信息失败') + } finally { + setLoading(false) + } + } + + const handleSubmit = async (e) => { + e.preventDefault() + setError('') + setSuccess('') + setSaving(true) + + try { + const res = await fetch('/api/users/me', { + method: 'PUT', + headers: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(form) + }) + + if (res.ok) { + setSuccess('保存成功!') + // 更新本地存储的用户信息 + const userStr = localStorage.getItem('user') + if (userStr) { + const user = JSON.parse(userStr) + user.username = form.username + localStorage.setItem('user', JSON.stringify(user)) + } + } else { + const data = await res.json() + setError(data.error?.message || '保存失败') + } + } catch (e) { + setError('保存失败,请重试') + } finally { + setSaving(false) + } + } + + const handlePasswordChange = async (e) => { + e.preventDefault() + setError('') + setSuccess('') + + if (passwordForm.newPassword !== passwordForm.confirmPassword) { + setError('两次输入的密码不一致') + return + } + + if (passwordForm.newPassword.length < 6) { + setError('密码长度至少6位') + return + } + + setSaving(true) + + try { + const res = await fetch('/api/auth/change-password', { + method: 'POST', + headers: { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + old_password: passwordForm.oldPassword, + new_password: passwordForm.newPassword + }) + }) + + if (res.ok) { + setSuccess('密码修改成功!') + setPasswordForm({ oldPassword: '', newPassword: '', confirmPassword: '' }) + } else { + const data = await res.json() + setError(data.error?.message || '密码修改失败') + } + } catch (e) { + setError('密码修改失败,请重试') + } finally { + setSaving(false) + } + } + + if (loading) { + return ( +
+
加载中...
+
+ ) + } + + return ( +
+ {/* 顶部导航 */} +
+
navigate('/')} style={{ cursor: 'pointer', fontSize: '20px' }}>←
+
个人设置
+
+ + {error &&
{error}
} + {success &&
{success}
} + + {/* 基本信息 */} +
+
基本信息
+ +
+
+ + setForm({ ...form, username: e.target.value })} + style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }} + /> +
+ +
+ + setForm({ ...form, email: e.target.value })} + style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }} + /> +
+ +
+ + setForm({ ...form, phone: e.target.value })} + style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }} + /> +
+ +
+ +