Files
TurboHR/frontend/src/pages/auth/ForgotPassword.tsx
T
selfrelease 3f39c56f4b feat: 部署到服务器+移动端适配+帮助系统RAG集成+网页图标
- 部署脚本 deploy.sh:一键部署到 154.8.162.18,端口3400,Nginx conf.d
- 应用名改为「企业用工专家」(登录/注册/侧边栏/员工端等6处)
- 移动端适配修复:Attendance/Dashboard/Settings/Contracts 6处grid响应式
- 帮助系统:HelpModal 组件 + TopNav 帮助图标 + RAG语义搜索集成
  - 后端 rag.service.ts 新增23条帮助种子数据 + searchHelp()
  - 前端搜索调用 /ai/rag/help-search,RAG不可用时回退关键词匹配
- 网页图标 favicon.svg(indigo主色+建筑人形图案)
- index.html 标题改为「企业用工专家」
2026-07-27 10:10:07 +08:00

158 lines
5.6 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.
import { useState } from 'react'
import { Link } from 'react-router-dom'
import { Building2, Eye, EyeOff } from 'lucide-react'
import api from '../../lib/api'
import { Input, Label } from '../../components/ui/Input'
import Button from '../../components/ui/Button'
export default function ForgotPassword() {
const [showPassword, setShowPassword] = useState(false)
const [error, setError] = useState('')
const [success, setSuccess] = useState(false)
const [loading, setLoading] = useState(false)
const [step, setStep] = useState<1 | 2>(1)
const [phone, setPhone] = useState('')
const [code, setCode] = useState('')
const [newPassword, setNewPassword] = useState('')
const [sentCode, setSentCode] = useState('')
const sendCode = async () => {
setError('')
if (!/^1[3-9]\d{9}$/.test(phone)) {
setError('手机号格式不正确')
return
}
setLoading(true)
try {
const res = await api.post('/auth/forgot-password/send-code', { phone }) as any
setSentCode(res.data?.code || '')
setStep(2)
} catch (err: any) {
setError(err.response?.data?.error?.message || '发送失败,请稍后重试')
} finally {
setLoading(false)
}
}
const resetPwd = async () => {
setError('')
if (code.length !== 6) {
setError('请输入6位验证码')
return
}
if (newPassword.length < 8) {
setError('密码至少8位')
return
}
setLoading(true)
try {
await api.post('/auth/forgot-password/verify', { phone, code, newPassword })
setSuccess(true)
} catch (err: any) {
setError(err.response?.data?.error?.message || '重置失败,请稍后重试')
} finally {
setLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-surface px-4">
<div className="w-full max-w-sm">
<div className="flex items-center justify-center gap-2 mb-8">
<Building2 className="w-8 h-8 text-primary" />
<span className="text-xl font-bold"></span>
</div>
<div className="card">
<h1 className="text-lg font-semibold mb-4"></h1>
{success ? (
<div className="text-center py-4">
<div className="text-green-600 mb-3"></div>
<Link to="/login" className="text-primary hover:underline text-sm"></Link>
</div>
) : (
<>
{error && (
<div className="mb-4 px-3 py-2 rounded-md bg-red-50 text-red-700 text-sm">{error}</div>
)}
{sentCode && step === 2 && (
<div className="mb-4 px-3 py-2 rounded-md bg-blue-50 text-blue-700 text-sm">
{sentCode}
</div>
)}
{step === 1 ? (
<div className="space-y-4">
<div>
<Label></Label>
<Input
type="tel"
placeholder="请输入注册手机号"
value={phone}
onChange={(e) => setPhone(e.target.value)}
maxLength={11}
/>
</div>
<Button className="w-full" disabled={loading} onClick={sendCode}>
{loading ? '发送中...' : '获取验证码'}
</Button>
</div>
) : (
<div className="space-y-4">
<div>
<Label></Label>
<Input type="tel" value={phone} disabled />
</div>
<div>
<Label></Label>
<Input
type="text"
placeholder="6位验证码"
value={code}
onChange={(e) => setCode(e.target.value)}
maxLength={6}
/>
</div>
<div>
<Label></Label>
<div className="relative">
<Input
type={showPassword ? 'text' : 'password'}
placeholder="至少8位"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400"
>
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
</button>
</div>
</div>
<Button className="w-full" disabled={loading} onClick={resetPwd}>
{loading ? '重置中...' : '重置密码'}
</Button>
<button
className="w-full text-xs text-gray-500 hover:text-gray-700"
onClick={() => { setStep(1); setCode(''); setSentCode('') }}
>
</button>
</div>
)}
<div className="mt-4 text-center text-sm">
<Link to="/login" className="text-primary hover:underline"></Link>
</div>
</>
)}
</div>
</div>
</div>
)
}