Support reset password.

pull/40/head
ruibaby 2019-09-05 17:08:20 +08:00
parent 4da73491b9
commit 600f9d682e
6 changed files with 345 additions and 40 deletions

View File

@ -59,6 +59,22 @@ adminApi.refreshToken = refreshToken => {
}) })
} }
adminApi.sendResetCode = param => {
return service({
url: `${baseUrl}/password/code`,
data: param,
method: 'post'
})
}
adminApi.resetPassword = param => {
return service({
url: `${baseUrl}/password/reset`,
data: param,
method: 'put'
})
}
adminApi.updateAdminAssets = () => { adminApi.updateAdminAssets = () => {
return service({ return service({
url: `${baseUrl}/halo-admin`, url: `${baseUrl}/halo-admin`,

View File

@ -218,6 +218,12 @@ export const constantRouterMap = [
meta: { title: '安装向导' }, meta: { title: '安装向导' },
component: () => import('@/views/system/Installation') component: () => import('@/views/system/Installation')
}, },
{
path: '/password/reset',
name: 'ResetPassword',
meta: { title: '重置密码' },
component: () => import('@/views/user/ResetPassword')
},
{ {
path: '/404', path: '/404',
name: 'NotFound', name: 'NotFound',

View File

@ -1,14 +1,19 @@
import Vue from 'vue' import Vue from 'vue'
import router from './router' import router from './router'
import store from './store' import store from './store'
import { setDocumentTitle, domTitle } from '@/utils/domUtil' import {
setDocumentTitle,
domTitle
} from '@/utils/domUtil'
import NProgress from 'nprogress' // progress bar import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style import 'nprogress/nprogress.css' // progress bar style
NProgress.configure({ showSpinner: false }) // NProgress Configuration NProgress.configure({
showSpinner: false
}) // NProgress Configuration
const whiteList = ['Login', 'Install', 'NotFound'] // no redirect whitelist const whiteList = ['Login', 'Install', 'NotFound', 'ResetPassword'] // no redirect whitelist
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
NProgress.start() NProgress.start()
@ -16,7 +21,9 @@ router.beforeEach((to, from, next) => {
Vue.$log.debug('Token', store.getters.token) Vue.$log.debug('Token', store.getters.token)
if (store.getters.token) { if (store.getters.token) {
if (to.name === 'Login') { if (to.name === 'Login') {
next({ name: 'Dashboard' }) next({
name: 'Dashboard'
})
NProgress.done() NProgress.done()
return return
} }
@ -35,6 +42,11 @@ router.beforeEach((to, from, next) => {
return return
} }
next({ name: 'Login', query: { redirect: to.fullPath } }) next({
name: 'Login',
query: {
redirect: to.fullPath
}
})
NProgress.done() NProgress.done()
}) })

View File

@ -8,7 +8,7 @@ import router from '@/router'
import { isObject } from './util' import { isObject } from './util'
const service = axios.create({ const service = axios.create({
timeout: 5000, timeout: 8000,
withCredentials: true withCredentials: true
}) })

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="container"> <div class="container-wrapper">
<div class="loginLogo animated fadeInUp"> <div class="halo-logo animated fadeInUp">
<span>Halo</span> <span>Halo</span>
</div> </div>
<div class="loginBody animated"> <div class="animated">
<a-form <a-form
layout="vertical" layout="vertical"
@keyup.enter.native="handleLogin" @keyup.enter.native="handleLogin"
@ -39,17 +39,27 @@
/> />
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-row> <a-form-item
class="animated fadeInUp"
:style="{'animation-delay': '0.3s'}"
>
<a-button <a-button
type="primary" type="primary"
:block="true" :block="true"
@click="handleLogin" @click="handleLogin"
class="animated fadeInUp"
:style="{'animation-delay': '0.3s'}"
>登录</a-button> >登录</a-button>
</a-row> </a-form-item>
<a-row> <a-row>
<router-link :to="{ name:'ResetPassword' }">
<a
class="tip animated fadeInRight"
v-if="resetPasswordButton"
href="javascript:void(0);"
>
找回密码
</a>
</router-link>
<a <a
@click="handleApiModifyModalOpen" @click="handleApiModifyModalOpen"
class="tip animated fadeInUp" class="tip animated fadeInUp"
@ -92,12 +102,21 @@ export default {
password: null, password: null,
apiModifyVisible: false, apiModifyVisible: false,
defaultApiBefore: window.location.protocol + '//', defaultApiBefore: window.location.protocol + '//',
apiUrl: window.location.host apiUrl: window.location.host,
resetPasswordButton: false
} }
}, },
computed: { computed: {
...mapGetters({ defaultApiUrl: 'apiUrl' }) ...mapGetters({ defaultApiUrl: 'apiUrl' })
}, },
created() {
const _this = this
document.addEventListener('keydown', function(e) {
if (e.keyCode === 72 && e.altKey && e.shiftKey) {
_this.toggleHidden()
}
})
},
methods: { methods: {
...mapActions(['login', 'loadUser', 'loadOptions']), ...mapActions(['login', 'loadUser', 'loadOptions']),
...mapMutations({ ...mapMutations({
@ -144,49 +163,53 @@ export default {
handleApiUrlRestore() { handleApiUrlRestore() {
this.restoreApiUrl() this.restoreApiUrl()
this.apiUrl = this.defaultApiUrl this.apiUrl = this.defaultApiUrl
},
toggleHidden() {
this.resetPasswordButton = !this.resetPasswordButton
} }
} }
} }
</script> </script>
<style lang="less" scope> <style lang="less">
body { body {
height: 100%; height: 100%;
background-color: #f5f5f5; background-color: #f5f5f5;
} }
.container {
background: #f7f7f7; .container-wrapper {
background: #ffffff;
position: absolute; position: absolute;
border-radius: 5px;
top: 45%; top: 45%;
left: 50%; left: 50%;
margin: -160px 0 0 -160px; margin: -160px 0 0 -160px;
width: 320px; width: 320px;
padding: 16px 32px 32px 32px; padding: 18px 28px 28px 28px;
box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1); box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1);
.halo-logo {
margin-bottom: 20px;
text-align: center;
span {
vertical-align: text-bottom;
font-size: 38px;
display: inline-block;
font-weight: 600;
color: #1790fe;
background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-clip: text;
small {
margin-left: 5px;
font-size: 35%;
}
}
}
.tip { .tip {
cursor: pointer; cursor: pointer;
margin-top: .5rem; margin-left: 0.5rem;
float: right; float: right;
} }
} }
.loginLogo {
margin-bottom: 20px;
text-align: center;
}
.loginLogo span {
vertical-align: text-bottom;
font-size: 36px;
display: inline-block;
font-weight: 600;
color: #1790fe;
background-image: -webkit-gradient(
linear,
37.219838% 34.532506%,
36.425669% 93.178216%,
from(#36c8f5),
to(#1790fe),
color-stop(0.37, #1790fe)
);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
}
</style> </style>

View File

@ -0,0 +1,248 @@
<template>
<div class="container-wrapper">
<div class="halo-logo animated fadeInUp">
<span>Halo<small>重置密码</small></span>
</div>
<div class="animated">
<a-form layout="vertical">
<a-form-item
class="animated fadeInUp"
:style="{'animation-delay': '0.1s'}"
>
<a-input
placeholder="用户名"
v-model="resetParam.username"
>
<a-icon
slot="prefix"
type="user"
style="color: rgba(0,0,0,.25)"
/>
</a-input>
</a-form-item>
<a-form-item
class="animated fadeInUp"
:style="{'animation-delay': '0.2s'}"
>
<a-input
placeholder="邮箱"
v-model="resetParam.email"
>
<a-icon
slot="prefix"
type="mail"
style="color: rgba(0,0,0,.25)"
/>
</a-input>
</a-form-item>
<a-form-item
class="animated fadeInUp"
:style="{'animation-delay': '0.3s'}"
>
<a-input
v-model="resetParam.code"
type="password"
placeholder="验证码"
>
<a-icon
slot="prefix"
type="safety-certificate"
style="color: rgba(0,0,0,.25)"
/>
<a
href="javascript:void(0);"
slot="addonAfter"
@click="handleSendCode"
>
获取
</a>
</a-input>
</a-form-item>
<a-form-item
class="animated fadeInUp"
:style="{'animation-delay': '0.4s'}"
>
<a-input
v-model="resetParam.password"
type="password"
placeholder="新密码"
>
<a-icon
slot="prefix"
type="lock"
style="color: rgba(0,0,0,.25)"
/>
</a-input>
</a-form-item>
<a-form-item
class="animated fadeInUp"
:style="{'animation-delay': '0.5s'}"
>
<a-input
v-model="resetParam.confirmPassword"
type="password"
placeholder="确认密码"
>
<a-icon
slot="prefix"
type="lock"
style="color: rgba(0,0,0,.25)"
/>
</a-input>
</a-form-item>
<a-form-item
class="animated fadeInUp"
:style="{'animation-delay': '0.6s'}"
>
<a-button
type="primary"
:block="true"
@click="handleResetPassword"
>重置密码</a-button>
</a-form-item>
<a-row>
<router-link :to="{ name:'Login' }">
<a
class="tip animated fadeInUp"
:style="{'animation-delay': '0.7s'}"
>
返回登陆
</a>
</router-link>
</a-row>
</a-form>
</div>
</div>
</template>
<script>
import adminApi from '@/api/admin'
export default {
data() {
return {
resetParam: {
username: '',
email: '',
code: '',
password: '',
confirmPassword: ''
}
}
},
methods: {
handleSendCode() {
if (!this.resetParam.username) {
this.$notification['error']({
message: '提示',
description: '用户名不能为空!'
})
return
}
if (!this.resetParam.email) {
this.$notification['error']({
message: '提示',
description: '邮箱不能为空!'
})
return
}
adminApi.sendResetCode(this.resetParam).then(response => {
this.$message.info('邮件发送成功,五分钟内有效')
})
},
handleResetPassword() {
if (!this.resetParam.username) {
this.$notification['error']({
message: '提示',
description: '用户名不能为空!'
})
return
}
if (!this.resetParam.email) {
this.$notification['error']({
message: '提示',
description: '邮箱不能为空!'
})
return
}
if (!this.resetParam.code) {
this.$notification['error']({
message: '提示',
description: '验证码不能为空!'
})
return
}
if (!this.resetParam.password) {
this.$notification['error']({
message: '提示',
description: '新密码不能为空!'
})
return
}
if (!this.resetParam.confirmPassword) {
this.$notification['error']({
message: '提示',
description: '确认密码不能为空!'
})
return
}
if (this.resetParam.confirmPassword !== this.resetParam.password) {
this.$notification['error']({
message: '提示',
description: '确认密码和新密码不匹配!'
})
return
}
adminApi.resetPassword(this.resetParam).then(response => {
this.$message.info('密码重置成功!')
this.$router.push({ name: 'Login' })
})
}
}
}
</script>
<style lang="less">
body {
height: 100%;
background-color: #f5f5f5;
}
.container-wrapper {
background: #ffffff;
position: absolute;
border-radius: 5px;
top: 45%;
left: 50%;
margin: -160px 0 0 -160px;
width: 320px;
padding: 18px 28px 28px 28px;
box-shadow: -4px 7px 46px 2px rgba(0, 0, 0, 0.1);
.halo-logo {
margin-bottom: 20px;
text-align: center;
span {
vertical-align: text-bottom;
font-size: 38px;
display: inline-block;
font-weight: 600;
color: #1790fe;
background-image: linear-gradient(-20deg, #6e45e2 0%, #88d3ce 100%);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
background-clip: text;
small {
margin-left: 5px;
font-size: 35%;
}
}
}
.tip {
cursor: pointer;
margin-left: 0.5rem;
float: right;
}
}
</style>