v1.2.1 阶段性稳定版本
This commit is contained in:
parent
4965bf68d3
commit
d92559a3e3
|
|
@ -1,14 +1,5 @@
|
|||
# 甲辰藏品管理系统 - 版本配置
|
||||
# Version Configuration for Zodiac Collection Management System
|
||||
|
||||
# 当前版本号 (语义化版本:主版本。次版本.修订版)
|
||||
VERSION=1.1.21
|
||||
|
||||
# 版本代号 (可选)
|
||||
VERSION_CODENAME="新生"
|
||||
|
||||
# 发布日期
|
||||
RELEASE_DATE=2026-03-18
|
||||
|
||||
# 版本说明
|
||||
VERSION_NOTES="性能优化 - Nginx反向代理、图片OSS直连、自动压缩、上传限制20MB"
|
||||
VERSION=1.2.1
|
||||
VERSION_CODENAME="阶段性稳定版本"
|
||||
RELEASE_DATE=2026-03-20
|
||||
VERSION_NOTES="密码强度验证 - 至少8位需包含大写+小写+数字"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: jiachenlong-db-test
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: zodiac
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
backend:
|
||||
image: python:3.11-slim
|
||||
container_name: jiachenlong-backend-test
|
||||
restart: unless-stopped
|
||||
working_dir: /app
|
||||
command: >
|
||||
bash -c "pip install fastapi uvicorn sqlalchemy psycopg2-binary pydantic python-jose bcrypt python-multipart pillow dashscope alibabacloud-dysmsapi20170525 -q && uvicorn app.main:app --host 0.0.0.0 --port 3000"
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- /root/jiachenlong/backend:/app
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/zodiac
|
||||
- SECRET_KEY=test-secret-key-for-sms
|
||||
- ACCESS_TOKEN_EXPIRE_MINUTES=60
|
||||
- OSS_ACCESS_KEY_ID=LTAI5t6HUnpFBLEK9194kPVG
|
||||
- OSS_ACCESS_KEY_SECRET=LEr4Q8yRxb8D5b24cKfCwlt4MMoke1
|
||||
- OSS_BUCKET=jiachenlong-oss
|
||||
- OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
|
||||
- SMS_ACCESS_KEY_ID=LTAI5tQAx5niD7JQVqGE5acE
|
||||
- SMS_ACCESS_KEY_SECRET=QsQFAEKBkaNynIoKyvdIi3BUyWVZu1
|
||||
- SMS_SIGN_NAME=苏州算力
|
||||
- SMS_TEMPLATE_CODE=SMS_501590956
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<title>甲辰收藏 v1.1.19</title>
|
||||
<title>甲辰收藏 v1.2.0</title>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/x-icon" href="/static/icons/favicon.ico" />
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export default function Admin() {
|
|||
</div>
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<div style={{ color: '#94a3b8', fontSize: '12px' }}>藏品数</div>
|
||||
<div style={{ color: '#60a5fa', fontSize: '20px', fontWeight: 'bold' }}>{user.collection_count || 0}</div>
|
||||
<div style={{ color: '#60a5fa', fontSize: '20px', fontWeight: 'bold', cursor: 'pointer' }} onClick={() => window.location.hash = '#/list?userId=' + user.id} title='点击查看'>{user.collection_count || 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: '12px', paddingTop: '12px', borderTop: '1px solid rgba(255,255,255,0.1)' }}>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ export default function List() {
|
|||
const [loading, setLoading] = useState(true)
|
||||
const [filter, setFilter] = useState('')
|
||||
const [filterType, setFilterType] = useState('')
|
||||
const urlParams = new URLSearchParams(window.location.hash.split('?')[1] || '')
|
||||
const urlUserId = urlParams.get('userId') || ''
|
||||
const [userIdFilter, setUserIdFilter] = useState(urlUserId)
|
||||
const [sortField, setSortField] = useState('createdAt')
|
||||
const [sortOrder, setSortOrder] = useState('desc')
|
||||
const [viewMode, setViewMode] = useState('list')
|
||||
|
|
@ -80,6 +83,11 @@ export default function List() {
|
|||
}
|
||||
|
||||
// 应用筛选条件
|
||||
// 用户ID筛选(从URL获取)
|
||||
if (userIdFilter) {
|
||||
list = list.filter(item => item.userId === userIdFilter || item.userId === userIdFilter.replace(/-/g, ''))
|
||||
}
|
||||
|
||||
if (filterType && filter) {
|
||||
const fieldMap = {
|
||||
status: 'status',
|
||||
|
|
|
|||
|
|
@ -12,21 +12,83 @@ export default function Login() {
|
|||
|
||||
// 注册相关状态
|
||||
const [showRegister, setShowRegister] = useState(false)
|
||||
const [registerData, setRegisterData] = useState({ username: '', password: '', confirmPassword: '', email: '' })
|
||||
const [registerData, setRegisterData] = useState({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
verifyCode: ''
|
||||
})
|
||||
const [registerLoading, setRegisterLoading] = useState(false)
|
||||
const [registerError, setRegisterError] = useState('')
|
||||
|
||||
// 生成验证码
|
||||
// 短信获取相关
|
||||
const [sendingCode, setSendingCode] = useState(false)
|
||||
const [codeCountdown, setCodeCountdown] = useState(0)
|
||||
const [codeSent, setCodeSent] = useState(false)
|
||||
|
||||
// 生成获取
|
||||
useEffect(() => {
|
||||
generateCaptcha()
|
||||
}, [])
|
||||
|
||||
// 获取倒计时
|
||||
useEffect(() => {
|
||||
if (codeCountdown > 0) {
|
||||
const timer = setTimeout(() => setCodeCountdown(codeCountdown - 1), 1000)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [codeCountdown])
|
||||
|
||||
const generateCaptcha = () => {
|
||||
const num1 = Math.floor(Math.random() * 10)
|
||||
const num2 = Math.floor(Math.random() * 10)
|
||||
setCaptcha({ num1, num2, answer: '' })
|
||||
}
|
||||
|
||||
// 发送短信获取
|
||||
const handleSendCode = async () => {
|
||||
if (!registerData.phone) {
|
||||
setRegisterError('请先输入手机号')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(registerData.phone)) {
|
||||
setRegisterError('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
setSendingCode(true)
|
||||
setRegisterError('')
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/send-verification-code', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ phone: registerData.phone })
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (data.success) {
|
||||
setCodeSent(true)
|
||||
setCodeCountdown(60)
|
||||
setRegisterError('')
|
||||
} else {
|
||||
setRegisterError(data.message || '发送失败')
|
||||
}
|
||||
} catch (err) {
|
||||
setRegisterError('发送失败,请稍后重试')
|
||||
} finally {
|
||||
setSendingCode(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理注册
|
||||
const handleRegister = async () => {
|
||||
// 验证输入
|
||||
|
|
@ -35,13 +97,13 @@ export default function Login() {
|
|||
return
|
||||
}
|
||||
|
||||
if (registerData.username.length < 3) {
|
||||
setRegisterError('用户名至少 3 个字符')
|
||||
if (registerData.username.length < 2) {
|
||||
setRegisterError('用户名至少 2 个字符')
|
||||
return
|
||||
}
|
||||
|
||||
if (registerData.password.length < 6) {
|
||||
setRegisterError('密码至少 6 个字符')
|
||||
if (registerData.password.length < 8 || !/[A-Z]/.test(registerData.password) || !/[a-z]/.test(registerData.password) || !/[0-9]/.test(registerData.password)) {
|
||||
setRegisterError('密码至少8位,需包含大写、小写、数字')
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -50,13 +112,33 @@ export default function Login() {
|
|||
return
|
||||
}
|
||||
|
||||
// 验证手机号
|
||||
if (!registerData.phone) {
|
||||
setRegisterError('手机号为必填项')
|
||||
return
|
||||
}
|
||||
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(registerData.phone)) {
|
||||
setRegisterError('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证短信获取
|
||||
if (!registerData.verifyCode) {
|
||||
setRegisterError('请输入短信获取')
|
||||
return
|
||||
}
|
||||
|
||||
setRegisterLoading(true)
|
||||
setRegisterError('')
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
username: registerData.username,
|
||||
password: registerData.password
|
||||
password: registerData.password,
|
||||
phone: registerData.phone,
|
||||
verifyCode: registerData.verifyCode
|
||||
}
|
||||
if (registerData.email) {
|
||||
payload.email = registerData.email
|
||||
|
|
@ -73,12 +155,14 @@ export default function Login() {
|
|||
const data = await res.json()
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.error?.message || '注册失败')
|
||||
throw new Error(data.detail || data.error?.message || '注册失败')
|
||||
}
|
||||
|
||||
alert('注册成功!请登录')
|
||||
setShowRegister(false)
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '' })
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '' })
|
||||
setCodeSent(false)
|
||||
setCodeCountdown(0)
|
||||
generateCaptcha()
|
||||
} catch (err) {
|
||||
console.error('注册错误:', err)
|
||||
|
|
@ -95,17 +179,13 @@ export default function Login() {
|
|||
return
|
||||
}
|
||||
|
||||
if (username.length < 3) {
|
||||
if (username.length < 2) {
|
||||
setError(ErrorCodes.E00021.message)
|
||||
return
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
setError(ErrorCodes.E00022.message)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
// 验证获取
|
||||
const expectedAnswer = captcha.num1 + captcha.num2
|
||||
if (!captcha.answer || Number(captcha.answer) !== expectedAnswer) {
|
||||
setError(ErrorCodes.E00012.message)
|
||||
|
|
@ -241,11 +321,11 @@ export default function Login() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* 验证码 */}
|
||||
{/* 获取 */}
|
||||
<div style={{ marginBottom: '24px' }}>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
<div onClick={generateCaptcha} style={{
|
||||
flex: 1,
|
||||
flex: 1, minWidth: "90px", width: "auto",
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
|
|
@ -355,7 +435,9 @@ export default function Login() {
|
|||
background: 'rgba(30, 41, 59, 0.95)',
|
||||
borderRadius: '16px',
|
||||
padding: '24px',
|
||||
border: '1px solid rgba(255,255,255,0.1)'
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto'
|
||||
}}>
|
||||
<h2 style={{ color: '#fbbf24', fontSize: '20px', fontWeight: '600', marginBottom: '20px', textAlign: 'center' }}>
|
||||
注册新账号 🎉
|
||||
|
|
@ -381,7 +463,7 @@ export default function Login() {
|
|||
type="text"
|
||||
value={registerData.username}
|
||||
onChange={(e) => setRegisterData(prev => ({ ...prev, username: e.target.value }))}
|
||||
placeholder="用户名(至少 3 个字符)"
|
||||
placeholder="用户名(至少 2 个字符)"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '14px',
|
||||
|
|
@ -395,6 +477,68 @@ export default function Login() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/* 手机号 */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<input
|
||||
type="tel"
|
||||
value={registerData.phone}
|
||||
onChange={(e) => setRegisterData(prev => ({ ...prev, phone: e.target.value }))}
|
||||
placeholder="手机号(11位)"
|
||||
maxLength={11}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
color: '#fff',
|
||||
fontSize: '16px',
|
||||
outline: 'none'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 短信获取 */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
<input
|
||||
type="text"
|
||||
value={registerData.verifyCode}
|
||||
onChange={(e) => setRegisterData(prev => ({ ...prev, verifyCode: e.target.value }))}
|
||||
placeholder="短信验证码"
|
||||
maxLength={6}
|
||||
style={{
|
||||
flex: 1, minWidth: "90px", width: "auto",
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
background: 'rgba(255,255,255,0.05)',
|
||||
color: '#fff',
|
||||
fontSize: '16px',
|
||||
outline: 'none'
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSendCode}
|
||||
disabled={sendingCode || codeCountdown > 0}
|
||||
style={{
|
||||
minWidth: "90px", width: "auto",
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
background: codeCountdown > 0 ? 'rgba(148, 163, 184, 0.3)' : 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor: codeCountdown > 0 ? 'not-allowed' : 'pointer',
|
||||
whiteSpace: 'nowrap'
|
||||
}}
|
||||
>
|
||||
{codeCountdown > 0 ? `${codeCountdown}秒` : sendingCode ? '发送中...' : '获取验证码'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 邮箱(可选) */}
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<input
|
||||
|
|
@ -421,7 +565,7 @@ export default function Login() {
|
|||
type="password"
|
||||
value={registerData.password}
|
||||
onChange={(e) => setRegisterData(prev => ({ ...prev, password: e.target.value }))}
|
||||
placeholder="密码(至少 6 个字符)"
|
||||
placeholder="密码(至少8位,需大写+小写+数字)"
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '14px',
|
||||
|
|
@ -460,11 +604,13 @@ export default function Login() {
|
|||
<button
|
||||
onClick={() => {
|
||||
setShowRegister(false)
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '' })
|
||||
setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '' })
|
||||
setRegisterError('')
|
||||
setCodeSent(false)
|
||||
setCodeCountdown(0)
|
||||
}}
|
||||
style={{
|
||||
flex: 1,
|
||||
flex: 1, minWidth: "90px", width: "auto",
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
|
|
@ -481,7 +627,7 @@ export default function Login() {
|
|||
onClick={handleRegister}
|
||||
disabled={registerLoading}
|
||||
style={{
|
||||
flex: 1,
|
||||
flex: 1, minWidth: "90px", width: "auto",
|
||||
padding: '14px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
|
|
|
|||
|
|
@ -16,6 +16,13 @@ export default function Settings() {
|
|||
bio: ''
|
||||
})
|
||||
|
||||
// 手机验证码相关状态
|
||||
const [phoneChanged, setPhoneChanged] = useState(false)
|
||||
const [newPhone, setNewPhone] = useState('')
|
||||
const [verifyCode, setVerifyCode] = useState('')
|
||||
const [sendingCode, setSendingCode] = useState(false)
|
||||
const [codeCountdown, setCodeCountdown] = useState(0)
|
||||
|
||||
const [passwordForm, setPasswordForm] = useState({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
|
|
@ -29,6 +36,14 @@ export default function Settings() {
|
|||
fetchUserInfo()
|
||||
}, [])
|
||||
|
||||
// 验证码倒计时
|
||||
useEffect(() => {
|
||||
if (codeCountdown > 0) {
|
||||
const timer = setTimeout(() => setCodeCountdown(codeCountdown - 1), 1000)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [codeCountdown])
|
||||
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/users/me', {
|
||||
|
|
@ -45,12 +60,10 @@ export default function Settings() {
|
|||
address: data.address || '',
|
||||
bio: data.bio || ''
|
||||
})
|
||||
} else {
|
||||
console.error('获取用户信息失败:', res.status)
|
||||
setNewPhone(data.phone || '')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取用户信息异常:', e)
|
||||
// 尝试从本地存储获取
|
||||
const userStr = localStorage.getItem('user')
|
||||
if (userStr) {
|
||||
try {
|
||||
|
|
@ -63,6 +76,7 @@ export default function Settings() {
|
|||
address: user.address || '',
|
||||
bio: user.bio || ''
|
||||
})
|
||||
setNewPhone(user.phone || '')
|
||||
} catch (e2) {
|
||||
setError('获取用户信息失败')
|
||||
}
|
||||
|
|
@ -72,26 +86,92 @@ export default function Settings() {
|
|||
}
|
||||
}
|
||||
|
||||
// 发送手机验证码
|
||||
const handleSendCode = async () => {
|
||||
if (!newPhone) {
|
||||
setError('请输入手机号')
|
||||
return
|
||||
}
|
||||
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
if (!phoneRegex.test(newPhone)) {
|
||||
setError('请输入正确的手机号')
|
||||
return
|
||||
}
|
||||
|
||||
setSendingCode(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/auth/send-verification-code', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ phone: newPhone })
|
||||
})
|
||||
|
||||
const data = await res.json()
|
||||
|
||||
if (data.success) {
|
||||
setPhoneChanged(true)
|
||||
setCodeCountdown(60)
|
||||
setSuccess('验证码已发送到 ' + newPhone.substring(0,3) + '****' + newPhone.substring(7))
|
||||
} else {
|
||||
setError(data.message || '发送失败')
|
||||
}
|
||||
} catch (err) {
|
||||
setError('发送失败,请稍后重试')
|
||||
} finally {
|
||||
setSendingCode(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
setSuccess('')
|
||||
|
||||
// 如果更换了手机号,需要验证码
|
||||
if (newPhone !== form.phone) {
|
||||
if (!verifyCode) {
|
||||
setError('请输入手机验证码')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setSaving(true)
|
||||
|
||||
try {
|
||||
const updateData = {
|
||||
username: form.username,
|
||||
email: form.email || null,
|
||||
phone: newPhone,
|
||||
address: form.address,
|
||||
bio: form.bio
|
||||
}
|
||||
|
||||
// 如果更换了手机号,添加验证码
|
||||
if (newPhone !== form.phone) {
|
||||
updateData.verifyCode = verifyCode
|
||||
}
|
||||
|
||||
const res = await fetch('/api/users/me', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(form)
|
||||
body: JSON.stringify(updateData)
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setSuccess('保存成功!')
|
||||
// 更新本地存储的用户信息
|
||||
setForm({...form, phone: newPhone})
|
||||
setVerifyCode('')
|
||||
setPhoneChanged(false)
|
||||
// 更新本地存储
|
||||
const userStr = localStorage.getItem('user')
|
||||
if (userStr) {
|
||||
const user = JSON.parse(userStr)
|
||||
|
|
@ -102,7 +182,7 @@ export default function Settings() {
|
|||
}
|
||||
} else {
|
||||
const data = await res.json()
|
||||
setError(data.error?.message || '保存失败')
|
||||
setError(data.error?.message || data.detail || '保存失败')
|
||||
}
|
||||
} catch (e) {
|
||||
setError('保存失败,请重试')
|
||||
|
|
@ -126,16 +206,14 @@ export default function Settings() {
|
|||
return
|
||||
}
|
||||
|
||||
if (passwordForm.newPassword.length < 6) {
|
||||
setError('密码长度至少6位')
|
||||
if (passwordForm.newPassword.length < 8) {
|
||||
setError('密码至少8位,需包含大写+小写+数字')
|
||||
return
|
||||
}
|
||||
|
||||
setSaving(true)
|
||||
console.log('开始修改密码...')
|
||||
|
||||
try {
|
||||
console.log('发送请求到 /api/auth/change-password')
|
||||
const res = await fetch('/api/auth/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
|
@ -148,9 +226,7 @@ export default function Settings() {
|
|||
})
|
||||
})
|
||||
|
||||
console.log('响应状态:', res.status)
|
||||
const data = await res.json()
|
||||
console.log('响应数据:', data)
|
||||
|
||||
if (res.ok) {
|
||||
setSuccess('密码修改成功!')
|
||||
|
|
@ -200,11 +276,12 @@ export default function Settings() {
|
|||
</div>
|
||||
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>邮箱</label>
|
||||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>邮箱(选填)</label>
|
||||
<input
|
||||
type="email"
|
||||
value={form.email}
|
||||
onChange={(e) => setForm({ ...form, email: e.target.value })}
|
||||
placeholder="选填"
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -213,27 +290,69 @@ export default function Settings() {
|
|||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>手机号</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={form.phone}
|
||||
onChange={(e) => setForm({ ...form, phone: e.target.value })}
|
||||
value={newPhone}
|
||||
onChange={(e) => {
|
||||
setNewPhone(e.target.value)
|
||||
setPhoneChanged(true)
|
||||
setVerifyCode('')
|
||||
}}
|
||||
placeholder="11位手机号"
|
||||
maxLength={11}
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 手机验证码 - 仅在手机号变更时显示 */}
|
||||
{phoneChanged && (
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>地址</label>
|
||||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>手机验证码</label>
|
||||
<div style={{ display: 'flex', gap: '12px' }}>
|
||||
<input
|
||||
type="text"
|
||||
value={verifyCode}
|
||||
onChange={(e) => setVerifyCode(e.target.value)}
|
||||
placeholder="6位验证码"
|
||||
maxLength={6}
|
||||
style={{ flex: 1, padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSendCode}
|
||||
disabled={sendingCode || codeCountdown > 0}
|
||||
style={{
|
||||
width: '120px',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: 'none',
|
||||
background: codeCountdown > 0 ? '#4b5563' : '#22c55e',
|
||||
color: '#fff',
|
||||
fontSize: '14px',
|
||||
cursor: codeCountdown > 0 ? 'not-allowed' : 'pointer'
|
||||
}}
|
||||
>
|
||||
{codeCountdown > 0 ? codeCountdown + '秒' : sendingCode ? '发送中...' : '获取验证码'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>地址(选填)</label>
|
||||
<input
|
||||
type="text"
|
||||
value={form.address}
|
||||
onChange={(e) => setForm({ ...form, address: e.target.value })}
|
||||
placeholder="选填"
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>简介</label>
|
||||
<label style={{ display: 'block', color: '#94a3b8', fontSize: '14px', marginBottom: '8px' }}>简介(选填)</label>
|
||||
<textarea
|
||||
value={form.bio}
|
||||
onChange={(e) => setForm({ ...form, bio: e.target.value })}
|
||||
placeholder="选填"
|
||||
rows={3}
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff', resize: 'none' }}
|
||||
/>
|
||||
|
|
@ -260,8 +379,8 @@ export default function Settings() {
|
|||
type="password"
|
||||
value={passwordForm.oldPassword}
|
||||
onChange={(e) => setPasswordForm({ ...passwordForm, oldPassword: e.target.value })}
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
placeholder="请输入当前密码"
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -271,8 +390,8 @@ export default function Settings() {
|
|||
type="password"
|
||||
value={passwordForm.newPassword}
|
||||
onChange={(e) => setPasswordForm({ ...passwordForm, newPassword: e.target.value })}
|
||||
placeholder="至少8位,需包含大写+小写+数字"
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
placeholder="至少6位"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -282,8 +401,8 @@ export default function Settings() {
|
|||
type="password"
|
||||
value={passwordForm.confirmPassword}
|
||||
onChange={(e) => setPasswordForm({ ...passwordForm, confirmPassword: e.target.value })}
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
placeholder="再次输入新密码"
|
||||
style={{ width: '100%', padding: '12px', borderRadius: '8px', border: '1px solid #334155', background: '#0f172a', color: '#fff' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue