feat: 重设密码

pull/103/head
bigfengyu 5 years ago
parent 5bd0766eba
commit 93693cb8e8

@ -57,3 +57,13 @@ export const fetchLogList = (
) => ({ type: 'LOG_LIST_FETCH', cursor, limit })
export const fetchLogListSucceeded = (logs: any) => ({ type: 'LOG_LIST_FETCH_SUCCEEDED', logs })
export const fetchLogListFailed = (message: any) => ({ type: 'LOG_LIST_FETCH_FAILED', message })
// 发送重设密码邮件
export const findpwd = (user: any, onResolved: any) => ({ type: 'USER_FINDPWD', user, onResolved })
export const findpwdSucceeded = () => ({ type: 'USER_FINDPWD_SUCCEEDED' })
export const findpwdFailed = (message: any) => ({ type: 'USER_FINDPWD_FAILED', message })
// 用户通过邮件重设密码
export const resetpwd = (user: any, onResolved: any) => ({ type: 'USER_RESETPWD', user, onResolved })
export const resetpwdSucceeded = () => ({ type: 'USER_RESETPWD_SUCCEEDED' })
export const resetpwdFailed = (message: any) => ({ type: 'USER_RESETPWD_FAILED', message })

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["FindpwdForm.sass"],"names":[],"mappings":"AAEA;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;;AACA;EACE;;AACJ;EACE;;AACF;EACE;EACA","file":"FindpwdForm.css"}

@ -0,0 +1,27 @@
@import "../../assets/variables.sass"
// +
.wrapper
background-size: cover
width: 100%
height: 100%
min-height: 800px
.LoginForm
position: fixed
left: 50%
top: 30%
width: 25rem
margin-left: -12.5rem
margin-top: -135px
border: 1px solid #E6E6E6
border-radius: .5rem
.header
padding: 1.5rem 3rem
border-bottom: 1px solid $border
.title
font-size: 2rem
.body
padding: 1.5rem 3rem
.footer
padding: 1.5rem 3rem
border-top: 1px solid $border

@ -0,0 +1,132 @@
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import config from '../../config'
import { Button, createStyles, makeStyles, List, ListItem, InputLabel, Input, FormControl, InputAdornment, IconButton, Paper } from '@material-ui/core'
import { green } from '@material-ui/core/colors'
import EmailIcon from '@material-ui/icons/Email'
import CodeIcon from '@material-ui/icons/Code'
import Refresh from '@material-ui/icons/Refresh'
import { findpwd } from 'actions/account'
import { showMessage, MSG_TYPE } from 'actions/common'
import { push } from 'connected-react-router'
const { serve } = config
const useStyles = makeStyles(() => createStyles({
root: {
width: '100%',
height: '100%',
position: 'absolute',
overflow: 'hidden',
backgroundSize: 'cover',
},
container: {
width: 350,
margin: 'auto',
marginTop: 150,
opacity: 0.85,
},
ctl: {
display: 'flex',
justifyContent: 'space-between',
},
captchaWrapper: {
cursor: 'pointer',
},
captcha: {
width: 108,
height: 36,
},
buttonProgress: {
color: green[500],
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
buttonWrapper: {
position: 'relative',
},
}))
export default function FindpwdForm() {
const [email, setEmail] = useState('')
const [captchaId, setCaptchaId] = useState(Date.now())
const [captcha, setCaptcha] = useState('')
const classes = useStyles()
const dispatch = useDispatch()
const handleSubmit = (e?: any) => {
e && e.preventDefault()
if (!email || !captcha) {
dispatch(showMessage(`请输入Email、验证码`, MSG_TYPE.WARNING))
} else {
dispatch(
findpwd({ email, captcha }, () => {
dispatch(showMessage(`发送成功,请登录您的邮箱按提示重置密码`, MSG_TYPE.SUCCESS))
})
)
}
}
return (
<div className={classes.root}>
<Paper className={classes.container}>
<List>
<ListItem>
<h2></h2>
</ListItem>
<ListItem>
<FormControl fullWidth={true}>
<InputLabel htmlFor="email"></InputLabel>
<Input
tabIndex={0}
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="Email"
autoFocus={true}
required={true}
endAdornment={
<InputAdornment position="end" tabIndex={100}>
<IconButton>
<EmailIcon />
</IconButton>
</InputAdornment>}
/>
</FormControl>
</ListItem>
<ListItem>
<FormControl fullWidth={true}>
<InputLabel htmlFor="captcha"></InputLabel>
<Input
tabIndex={2}
name="captcha"
value={captcha}
autoComplete="off"
onKeyDown={e => e.keyCode === 13 && handleSubmit()}
onChange={e => setCaptcha(e.target.value)}
endAdornment={
<InputAdornment position="end">
<IconButton>
<CodeIcon />
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</ListItem>
<ListItem className={classes.ctl}>
<div className={classes.captchaWrapper} onClick={() => setCaptchaId(Date.now())}>
<img src={`${serve}/captcha?t=${captchaId}`} className={classes.captcha} alt="captcha" />
<Refresh />
</div>
<div className={classes.buttonWrapper}>
<Button variant="outlined" color="default" style={{ marginRight: 8 }} onClick={() => dispatch(push('/account/login'))}></Button>
<Button variant="contained" color="primary" tabIndex={3} onClick={handleSubmit}></Button>
</div>
</ListItem>
</List>
</Paper>
</div>
)
}

@ -15,6 +15,7 @@ import URI from 'urijs'
import { showMessage, MSG_TYPE } from 'actions/common'
import { push } from 'connected-react-router'
import { getRouter } from 'selectors/router'
import { Link } from '../../family'
const { serve } = config
@ -36,6 +37,10 @@ const useStyles = makeStyles(() => createStyles({
display: 'flex',
justifyContent: 'space-between',
},
ctlend: {
display: 'flex',
justifyContent: 'flex-end',
},
captchaWrapper: {
cursor: 'pointer',
},
@ -161,9 +166,12 @@ export default function LoginForm() {
</div>
<div className={classes.buttonWrapper}>
<Button variant="outlined" color="default" style={{ marginRight: 8 }} onClick={() => dispatch(push('/account/register'))}></Button>
<Button variant="contained" color="primary" tabIndex={3} onClick={handleSubmit}></Button>
<Button variant="contained" color="primary" tabIndex={3} onClick={handleSubmit}></Button>
</div>
</ListItem>
<ListItem className={classes.ctlend}>
<Link to="#" onClick={() => dispatch(push('/account/findpwd'))} className="operation "></Link>
</ListItem>
</List>
</Paper>
</div>

@ -0,0 +1,152 @@
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import config from '../../config'
import { Button, createStyles, makeStyles, List, ListItem, InputLabel, Input, FormControl, InputAdornment, IconButton, Paper } from '@material-ui/core'
import { green } from '@material-ui/core/colors'
import Visibility from '@material-ui/icons/Visibility'
import Refresh from '@material-ui/icons/Refresh'
import VisibilityOff from '@material-ui/icons/VisibilityOff'
import { resetpwd } from 'actions/account'
import { showMessage, MSG_TYPE } from 'actions/common'
import CodeIcon from '@material-ui/icons/Code'
import { getRouter } from 'selectors/router'
import { push } from 'connected-react-router'
import URI from 'urijs'
const { serve } = config
const useStyles = makeStyles(() => createStyles({
root: {
width: '100%',
height: '100%',
position: 'absolute',
overflow: 'hidden',
backgroundSize: 'cover',
},
container: {
width: 350,
margin: 'auto',
marginTop: 150,
opacity: 0.85,
},
ctl: {
display: 'flex',
justifyContent: 'space-between',
},
captchaWrapper: {
cursor: 'pointer',
},
captcha: {
width: 108,
height: 36,
},
buttonProgress: {
color: green[500],
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
buttonWrapper: {
position: 'relative',
},
}))
export default function ResetpwdForm() {
const [captchaId, setCaptchaId] = useState(Date.now())
const [captcha, setCaptcha] = useState('')
const [showPassword, setShowPassword] = useState(false)
const [password, setPassword] = useState('')
const classes = useStyles()
const dispatch = useDispatch()
const router = useSelector(getRouter)
const { pathname, hash, search } = router.location
const uri = URI(pathname + hash + search)
const email = uri.search(true).email
const code = uri.search(true).code
const token = uri.search(true).token
const handleSubmit = (e?: any) => {
e && e.preventDefault()
if (!password) {
dispatch(showMessage(`请输入密码`, MSG_TYPE.WARNING))
} else if (password.length < 6) {
dispatch(showMessage(`密码长度过短,请输入六位以上密码`, MSG_TYPE.WARNING))
} else {
dispatch(
resetpwd({ email, code, token, password, captcha }, () => {
dispatch(showMessage(`密码重置成功,请重新登录`, MSG_TYPE.SUCCESS))
dispatch(push('/'))
})
)
}
}
return (
<div className={classes.root}>
<Paper className={classes.container}>
<List>
<ListItem>
<h2></h2>
</ListItem>
<ListItem>
{email}
</ListItem>
<ListItem>
<FormControl fullWidth={true}>
<InputLabel htmlFor="password"></InputLabel>
<Input
tabIndex={1}
name="password"
type={showPassword ? 'text' : 'password'}
value={password}
autoComplete="current-password"
onChange={e => setPassword(e.target.value)}
endAdornment={
<InputAdornment position="end" tabIndex={101}>
<IconButton
aria-label="Toggle password visibility"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>}
/>
</FormControl>
</ListItem>
<ListItem>
<FormControl fullWidth={true}>
<InputLabel htmlFor="captcha"></InputLabel>
<Input
tabIndex={2}
name="captcha"
value={captcha}
autoComplete="off"
onKeyDown={e => e.keyCode === 13 && handleSubmit()}
onChange={e => setCaptcha(e.target.value)}
endAdornment={
<InputAdornment position="end">
<IconButton>
<CodeIcon />
</IconButton>
</InputAdornment>
}
/>
</FormControl>
</ListItem>
<ListItem className={classes.ctl}>
<div className={classes.captchaWrapper} onClick={() => setCaptchaId(Date.now())}>
<img src={`${serve}/captcha?t=${captchaId}`} className={classes.captcha} alt="captcha" />
<Refresh />
</div>
<div className={classes.buttonWrapper}>
<Button variant="outlined" color="default" style={{ marginRight: 8 }} onClick={() => dispatch(push('/account/login'))}></Button>
<Button variant="contained" color="primary" tabIndex={3} onClick={handleSubmit}></Button>
</div>
</ListItem>
</List>
</Paper>
</div>
)
}

@ -1,4 +1,3 @@
import './assets/index.css'
import 'animate.css'
@ -41,7 +40,7 @@ function* authenticate() {
} else {
const { pathname, search, hash } = window.location
// const uri = URI(pathname + search + hash)
if (pathname.indexOf('/account/login') > -1) {
if (pathname.indexOf('/account/login') > -1 || pathname.indexOf('/account/resetpwd') > -1 ) {
yield Promise.resolve()
return
}

@ -29,6 +29,8 @@ const relatives = {
},
auth(state: any = {}, action: any) {
switch (action.type) {
case AccountAction.findpwdSucceeded().type:
case AccountAction.findpwdFailed('').type:
case AccountAction.loginSucceeded({}).type:
case AccountAction.fetchLoginInfoSucceeded({}).type:
return action.user && action.user.id ? action.user : {}
@ -43,6 +45,8 @@ const relatives = {
},
user(state: any = {}, action: any) {
switch (action.type) {
case AccountAction.findpwdSucceeded().type:
case AccountAction.findpwdFailed('').type:
case AccountAction.loginSucceeded({}).type:
case AccountAction.fetchLoginInfoSucceeded({}).type:
return action.user && action.user.id ? action.user : {}
@ -180,6 +184,32 @@ const relatives = {
yield put(AccountAction.fetchUserListFailed(e.message))
}
},
*[AccountAction.findpwd({}, () => {/** empty */ }).type](action: any) {
try {
const result = yield call(AccountService.findpwd, action.user)
if (result.errMsg) {
throw new Error(result.errMsg)
}
yield put(AccountAction.findpwdSucceeded())
if (action.onResolved) { action.onResolved() }
} catch (e) {
yield put(showMessage(e.message, MSG_TYPE.WARNING))
yield put(AccountAction.findpwdFailed(e.message))
}
},
*[AccountAction.resetpwd({}, () => {/** empty */ }).type](action: any) {
try {
const result = yield call(AccountService.resetpwd, action.user)
if (result.errMsg) {
throw new Error(result.errMsg)
}
yield put(AccountAction.resetpwdSucceeded())
if (action.onResolved) { action.onResolved() }
} catch (e) {
yield put(showMessage(e.message, MSG_TYPE.WARNING))
yield put(AccountAction.resetpwdFailed(e.message))
}
},
},
listeners: {
'/account': [AccountAction.fetchUserList],

@ -118,4 +118,57 @@ export default {
}).then(res => res.json())
// .then(json => json.data)
},
// 发送重置密码激活邮件
findpwd({
email,
captcha,
}: {
email: string;
captcha: string;
}) {
return fetch(`${serve}/account/findpwd`, {
...CREDENTIALS,
method: 'POST',
body: JSON.stringify({
email,
captcha,
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(res => res.json())
.then(json => json.data)
},
// 通过邮件链接重置密码
resetpwd({
email,
code,
token,
password,
captcha,
}: {
email: string;
code: string;
token: string;
password: string;
captcha: string;
}) {
return fetch(`${serve}/account/findpwd/reset`, {
...CREDENTIALS,
method: `POST`,
body: JSON.stringify({
code,
email,
captcha,
token,
password,
}),
headers: {
'Content-Type': 'application/json',
},
})
.then(res => res.json())
.then(json => json.data)
},
}

@ -8,6 +8,8 @@ import Footer from './components/common/Footer'
import Home from './components/home/Home'
import LoginForm from './components/account/LoginForm'
import RegisterForm from './components/account/RegisterForm'
import FindpwdForm from './components/account/FindpwdForm'
import ResetpwdForm from './components/account/ResetpwdForm'
import Message from 'components/common/Message'
import { useSelector } from 'react-redux'
import { RootState } from 'actions/types'
@ -56,6 +58,8 @@ const Routes = () => {
<Message messageInfo={message} />
<Switch>
<Route path="/account/register" component={RegisterForm} />
<Route path="/account/findpwd" component={FindpwdForm} />
<Route path="/account/resetpwd" component={ResetpwdForm} />
<Route component={LoginForm} />
</Switch>
</article>
@ -117,6 +121,8 @@ const Routes = () => {
<Route path="/account/users" component={UserList} />
<Route path="/account/login" component={LoginForm} />
<Route path="/account/register" component={RegisterForm} />
<Route path="/account/findpwd" component={FindpwdForm} />
<Route path="/account/resetpwd" component={ResetpwdForm} />
<Route component={NoMatch} />
</Switch>
)}

@ -8,7 +8,7 @@ import { RootState } from 'actions/types'
const interfaceSelector = (state: RootState) => {
const router = getRouter(state)
const itfId = +(router.location as any).params.itf
const itfId = +((router.location as any).query || (router.location as any).params).itf
if (itfId > 0) {
for (const mod of state.repository.data.modules) {
for (const itf of mod.interfaces) {

Loading…
Cancel
Save