@@ -23,6 +23,10 @@ import { toast } from "sonner"
2323import { apiRequest } from "@/utils/requestUtils"
2424import { Link , useNavigate } from "react-router-dom"
2525import { captchaChallenge } from "@/utils/common"
26+ import { Eye , EyeOff } from "lucide-react"
27+
28+ const USER_STORAGE_KEY = 'login_user'
29+ const MANAGER_STORAGE_KEY = 'login_manager'
2630
2731export default function LoginPage ( {
2832 className,
@@ -33,14 +37,35 @@ export default function LoginPage({
3337 const [ teamManagerEmail , setTeamManagerEmail ] = React . useState ( '' )
3438 const [ teamManagerPassword , setTeamManagerPassword ] = React . useState ( '' )
3539 const [ logging , setLogging ] = React . useState ( false )
40+ const [ showUserPassword , setShowUserPassword ] = React . useState ( false )
41+ const [ showManagerPassword , setShowManagerPassword ] = React . useState ( false )
3642 const navigate = useNavigate ( )
3743
44+ React . useEffect ( ( ) => {
45+ try {
46+ const savedUser = localStorage . getItem ( USER_STORAGE_KEY )
47+ if ( savedUser ) {
48+ const { email, password } = JSON . parse ( savedUser )
49+ if ( email ) setUserEmail ( email )
50+ if ( password ) setUserPassword ( password )
51+ }
52+ const savedManager = localStorage . getItem ( MANAGER_STORAGE_KEY )
53+ if ( savedManager ) {
54+ const { email, password } = JSON . parse ( savedManager )
55+ if ( email ) setTeamManagerEmail ( email )
56+ if ( password ) setTeamManagerPassword ( password )
57+ }
58+ } catch {
59+ // ignore
60+ }
61+ } , [ ] )
62+
3863 const handleUserLogin = async ( ) => {
3964 if ( userEmail . trim ( ) === '' || userPassword . trim ( ) === '' ) {
4065 toast . error ( '请输入账号和密码' )
4166 return
4267 }
43-
68+
4469 setLogging ( true )
4570
4671 const token = await captchaChallenge ( ) ;
@@ -51,6 +76,7 @@ export default function LoginPage({
5176 captcha_token : token ,
5277 } , [ ] , ( resp ) => {
5378 if ( resp . code === 0 ) {
79+ localStorage . setItem ( USER_STORAGE_KEY , JSON . stringify ( { email : userEmail . trim ( ) , password : userPassword . trim ( ) } ) )
5480 navigate ( '/console/' )
5581 } else {
5682 toast . error ( '登录失败,请重试' )
@@ -67,18 +93,19 @@ export default function LoginPage({
6793 toast . error ( '请输入账号和密码' )
6894 return
6995 }
70-
96+
7197 setLogging ( true )
7298
7399 const token = await captchaChallenge ( ) ;
74100 if ( token ) {
75-
101+
76102 await apiRequest ( 'v1TeamsUsersLoginCreate' , {
77103 email : teamManagerEmail . trim ( ) ,
78104 password : teamManagerPassword . trim ( ) ,
79105 captcha_token : token ,
80106 } , [ ] , ( resp ) => {
81107 if ( resp . code === 0 ) {
108+ localStorage . setItem ( MANAGER_STORAGE_KEY , JSON . stringify ( { email : teamManagerEmail . trim ( ) , password : teamManagerPassword . trim ( ) } ) )
82109 navigate ( '/manager/' )
83110 } else {
84111 toast . error ( '登录失败,请重试' )
@@ -124,19 +151,30 @@ export default function LoginPage({
124151 < Field >
125152 < div className = "flex flex-row items-center justify-between" >
126153 < FieldLabel htmlFor = "user-password" > 密码</ FieldLabel >
127- < Link to = "/findpassword" className = "text-sm text-muted-foreground hover:underline" >
154+ < Link to = "/findpassword" tabIndex = { - 1 } className = "text-sm text-muted-foreground hover:underline" >
128155 找回密码
129156 </ Link >
130157 </ div >
131- < Input
132- value = { userPassword }
133- placeholder = "************"
134- onChange = { ( e ) => setUserPassword ( e . target . value ) }
135- id = "user-password"
136- type = "password"
137- required
138- disabled = { logging }
139- />
158+ < div className = "relative" >
159+ < Input
160+ value = { userPassword }
161+ placeholder = "************"
162+ onChange = { ( e ) => setUserPassword ( e . target . value ) }
163+ id = "user-password"
164+ type = { showUserPassword ? "text" : "password" }
165+ required
166+ disabled = { logging }
167+ className = "pr-9"
168+ />
169+ < button
170+ type = "button"
171+ tabIndex = { - 1 }
172+ onClick = { ( ) => setShowUserPassword ( v => ! v ) }
173+ className = "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
174+ >
175+ { showUserPassword ? < EyeOff size = { 16 } /> : < Eye size = { 16 } /> }
176+ </ button >
177+ </ div >
140178 </ Field >
141179 < Field >
142180 < Button type = "submit" disabled = { logging } variant = "outline" >
@@ -173,15 +211,26 @@ export default function LoginPage({
173211 </ Field >
174212 < Field >
175213 < FieldLabel htmlFor = "password" > 密码</ FieldLabel >
176- < Input
177- id = "password"
178- placeholder = "************"
179- type = "password"
180- required
181- disabled = { logging }
182- value = { teamManagerPassword }
183- onChange = { ( e ) => setTeamManagerPassword ( e . target . value ) }
184- />
214+ < div className = "relative" >
215+ < Input
216+ id = "password"
217+ placeholder = "************"
218+ type = { showManagerPassword ? "text" : "password" }
219+ required
220+ disabled = { logging }
221+ value = { teamManagerPassword }
222+ onChange = { ( e ) => setTeamManagerPassword ( e . target . value ) }
223+ className = "pr-9"
224+ />
225+ < button
226+ type = "button"
227+ tabIndex = { - 1 }
228+ onClick = { ( ) => setShowManagerPassword ( v => ! v ) }
229+ className = "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
230+ >
231+ { showManagerPassword ? < EyeOff size = { 16 } /> : < Eye size = { 16 } /> }
232+ </ button >
233+ </ div >
185234 </ Field >
186235 < Field >
187236 < Button type = "submit" disabled = { logging } >
0 commit comments