support update password and send password reset email

This commit is contained in:
zema1 2017-09-10 18:29:01 +08:00
parent 69c3178a58
commit f6b833594e
19 changed files with 497 additions and 104 deletions

View File

@ -18,7 +18,7 @@ module.exports = {
'generator-star-spacing': 0, 'generator-star-spacing': 0,
// allow debugger during development // allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
"space-before-function-paren": ["warn", { "space-before-function-paren": ["error", {
"anonymous": "never", "anonymous": "never",
"named": "never", "named": "never",
"asyncArrow": "always" "asyncArrow": "always"
@ -26,6 +26,7 @@ module.exports = {
"no-irregular-whitespace": ["error", { "no-irregular-whitespace": ["error", {
"skipComments": true, "skipComments": true,
"skipTemplates": true "skipTemplates": true
}] }],
"no-unused-vars": ["warn"]
} }
} }

View File

@ -35,9 +35,6 @@ export default {
getCaptcha() { getCaptcha() {
return ajax('captcha', 'get') return ajax('captcha', 'get')
}, },
getTwoFactorQrcode() {
return ajax('two_factor_auth', 'get')
},
// 获取自身信息 // 获取自身信息
getUserInfo(username = undefined) { getUserInfo(username = undefined) {
return ajax('profile', 'get', { return ajax('profile', 'get', {
@ -46,13 +43,25 @@ export default {
} }
}) })
}, },
// 保存用户资料设置 // 保存用户资料设置
updateProfile(profile) { updateProfile(profile) {
return ajax('profile', 'put', { return ajax('profile', 'put', {
data: profile data: profile
}) })
}, },
getTwoFactorQrcode() {
return ajax('two_factor_auth', 'get')
},
apply_reset_password(data) {
return ajax('apply_reset_password', 'post', {
data
})
},
changePassword(data) {
return ajax('change_password', 'post', {
data
})
},
getLanguages() { getLanguages() {
return ajax('languages', 'get') return ajax('languages', 'get')
}, },

View File

@ -56,7 +56,7 @@
</Dropdown> </Dropdown>
</template> </template>
</Menu> </Menu>
<Register :visible.sync="registerModalVisible" :mode.sync="modalMode"></Register> <LoginOrRegister :visible.sync="registerModalVisible" :mode.sync="modalMode"></LoginOrRegister>
</div> </div>
</template> </template>
@ -65,11 +65,11 @@
import api from '@/api' import api from '@/api'
import auth from '../utils/auth' import auth from '../utils/auth'
import Register from '@/views/user/LoginORRegister' import LoginOrRegister from '@/views/user/LoginOrRegister'
export default { export default {
components: { components: {
Register LoginOrRegister
}, },
data() { data() {
return { return {

View File

@ -0,0 +1,23 @@
import api from '@/api'
export default {
methods: {
validateForm(formName) {
return new Promise((resolve, reject) => {
this.$refs[formName].validate(valid => {
if (!valid) {
this.$error('please validate the error fields')
reject(valid)
} else {
resolve(valid)
}
})
})
},
getCaptchaSrc() {
api.getCaptcha().then(res => {
this.captchaSrc = res.data.data
})
}
}
}

View File

@ -1,5 +1,6 @@
import Emitter from './emitter' import Emitter from './emitter'
import ProblemMixin from './problem' import ProblemMixin from './problem'
import SettingMixin from './setting' import SettingMixin from './setting'
import FormMixin from './form'
export {Emitter, ProblemMixin, SettingMixin} export {Emitter, ProblemMixin, SettingMixin, FormMixin}

View File

@ -50,6 +50,9 @@ Vue.component(Panel.name, Panel)
// Vue.use(VueI18n) // Vue.use(VueI18n)
// 注册全局消息提示 // 注册全局消息提示
Vue.prototype.$Message.config({
duration: 1.8
})
Vue.prototype.$error = Vue.prototype.$Message.error Vue.prototype.$error = Vue.prototype.$Message.error
Vue.prototype.$info = Vue.prototype.$Message.info Vue.prototype.$info = Vue.prototype.$Message.info
Vue.prototype.$success = Vue.prototype.$Message.success Vue.prototype.$success = Vue.prototype.$Message.success

View File

@ -1,8 +1,10 @@
// all routes here. // all routes here.
import Test from '../views/test' import Test from '../views/test'
import { import {
ProblemList, ContestList, ContestDetails, ContestProblemList, ContestAnnouncement, ContestRank, Logout, ApplyResetPassword, ResetPassword,
Logout, ACMRank, Settings, ProfileSetting ProfileSetting, SecuritySetting, Settings,
ContestAnnouncement, ContestDetails, ContestList, ContestProblemList, ContestRank,
ProblemList, ACMRank
} from '../views' } from '../views'
export default [ export default [
@ -18,6 +20,16 @@ export default [
path: '/logout', path: '/logout',
component: Logout component: Logout
}, },
{
name: 'apply-reset-password',
path: '/apply-reset-password',
component: ApplyResetPassword
},
{
name: 'reset-password',
path: '/reset-password/:token',
component: ResetPassword
},
{ {
name: 'problem-list', name: 'problem-list',
path: '/problems', path: '/problems',
@ -93,6 +105,11 @@ export default [
name: 'profile-setting', name: 'profile-setting',
path: 'profile', path: 'profile',
component: ProfileSetting component: ProfileSetting
},
{
name: 'security-setting',
path: 'security',
component: SecuritySetting
} }
] ]
}, },
@ -100,8 +117,8 @@ export default [
path: '/test', path: '/test',
name: 'Test', name: 'Test',
component: Test component: Test
},
{
path: '*', redirect: '/problems'
} }
// {
// path: '*', redirect: '/problems'
// }
] ]

View File

@ -8,8 +8,8 @@
.section-title { .section-title {
font-size: 21px; font-size: 21px;
font-weight: 400; font-weight: 500;
padding: 20px 20px; padding: 20px 25px;
line-height: 30px; line-height: 30px;
} }

View File

@ -28,3 +28,11 @@ table {
color: #a94442; color: #a94442;
background-color: #f2dede; background-color: #f2dede;
} }
.ivu-modal-footer {
border-top-width: 0;
padding: 0 18px 20px 18px;
}
.ivu-modal-body {
padding: 18px;
}

View File

@ -148,7 +148,7 @@
this.applyToTable(res.data.data.results) this.applyToTable(res.data.data.results)
}) })
}, },
getContestAndProblems(contestID) { getContestAndProblems() {
// localStorage // localStorage
this.contest = utils.loadContest(this.contestID) this.contest = utils.loadContest(this.contestID)
let problems = storage.get(STORAGE_KEY.contestProblems + this.contestID) let problems = storage.get(STORAGE_KEY.contestProblems + this.contestID)

View File

@ -1,12 +1,15 @@
import ProblemList from './problem/ProblemList.vue' import ProblemList from './problem/ProblemList.vue'
import ACMRank from './rank/ACMRank.vue' import ACMRank from './rank/ACMRank.vue'
import Logout from './user/Logout.vue' import Logout from './user/Logout.vue'
import ApplyResetPassword from './user/ApplyResetPassword.vue'
import ResetPassword from './user/ResetPassword.vue'
export { export {
ProblemList, ACMRank, Logout Logout, ResetPassword, ApplyResetPassword,
ProblemList, ACMRank
} }
export {ContestRank, ContestProblemList, ContestList, ContestDetails, ContestAnnouncement} from './contest' export {ContestRank, ContestProblemList, ContestList, ContestDetails, ContestAnnouncement} from './contest'
export {Settings, ProfileSetting} from './setting' export {Settings, ProfileSetting, SecuritySetting} from './setting'
/* , Login, Logout, /* , Login, Logout,
* 在对应的route内加载 * 在对应的route内加载
* 见https://router.vuejs.org/en/advanced/lazy-loading.html * 见https://router.vuejs.org/en/advanced/lazy-loading.html

View File

@ -2,7 +2,7 @@
<Row type="flex" :gutter="18"> <Row type="flex" :gutter="18">
<Col :span=20> <Col :span=20>
<Panel shadow> <Panel shadow>
<div slot="title">Problems List</div> <div slot="title">Problem List</div>
<div slot="extra"> <div slot="extra">
<ul class="filter"> <ul class="filter">
<li> <li>

View File

@ -1,21 +1,25 @@
<template> <template>
<Card :padding="0" id="settings-card"> <Row type="flex" justify="space-around">
<div class="flex-container"> <Col :span="22">
<div class="menu"> <Card :padding="0" id="settings-card">
<Menu accordion @on-select="goRoute" activeName="/setting/profile" style="text-align: center;"> <div class="flex-container">
<div> <div class="menu">
<img class="avatar" src="../../assets/profile.jpg"/> <Menu accordion @on-select="goRoute" activeName="/setting/profile" style="text-align: center;">
</div> <div>
<Menu-item name="/setting/profile">Profile</Menu-item> <img class="avatar" src="../../assets/profile.jpg"/>
<Menu-item name="/setting/1">Security</Menu-item> </div>
<Menu-item name="/setting/2">Perference</Menu-item> <Menu-item name="/setting/profile">Profile</Menu-item>
</Menu> <Menu-item name="/setting/security">Security</Menu-item>
<Menu-item name="/setting/2">Perference</Menu-item>
</Menu>
</div>
<div class="panel">
<router-view></router-view>
</div>
</div> </div>
<div class="panel"> </Card>
<router-view></router-view> </Col>
</div> </Row>
</div>
</Card>
</template> </template>
<script> <script>
export default { export default {
@ -65,3 +69,10 @@
width: 0; width: 0;
} }
</style> </style>
<style>
.setting-main {
margin: 10px 40px;
padding-bottom: 40px;
}
</style>

View File

@ -1,48 +1,49 @@
<template> <template>
<div> <panel :padding="0" :bordered="false" dis-hover>
<panel :padding="0" :bordered="false" dis-hover> <div slot="title">Profile Setting</div>
<div slot="title">Profile Settings</div> <div slot="extra">
<div slot="extra"> <Button type="primary" @click="updateProfile" :loading="btnLoading">Save All</Button>
<Button type="primary" @click="updateProfile">Save All</Button> </div>
</div> <Form ref="formProfile" :model="formProfile">
<Form ref="formProfile" :model="formProfile"> <Row type="flex" :gutter="30" justify="space-around">
<Row type="flex" :gutter="30" justify="space-around"> <Col :span="10">
<Col :span="10"> <FormItem label="Real Name">
<FormItem label="Real Name"> <Input v-model="formProfile.real_name"/>
<Input v-model="formProfile.real_name"/> </FormItem>
</FormItem> <Form-item label="Phone">
<Form-item label="Phone"> <Input v-model="formProfile.phone_number"/>
<Input v-model="formProfile.phone_number"/> </Form-item>
</Form-item> <Form-item label="Mood">
<Form-item label="Mood"> <Input v-model="formProfile.mood"/>
<Input v-model="formProfile.mood"/> </Form-item>
</Form-item> </Col>
</Col>
<Col :span="10"> <Col :span="10">
<Form-item label="Major"> <Form-item label="Major">
<Input v-model="formProfile.major" /> <Input v-model="formProfile.major"/>
</Form-item> </Form-item>
<Form-item label="Blog"> <Form-item label="Blog">
<Input v-model="formProfile.blog"/> <Input v-model="formProfile.blog"/>
</Form-item> </Form-item>
<Form-item label="Language"> <Form-item label="Language">
<Input v-model="formProfile.language"/> <Input v-model="formProfile.language"/>
</Form-item> </Form-item>
</Col> </Col>
</Row> </Row>
</Form> </Form>
</panel> </panel>
</div>
</template> </template>
<script> <script>
import api from '@/api.js' import api from '@/api.js'
import auth from '@/utils/auth' import auth from '@/utils/auth'
import {SettingMixin} from '~/mixins'
export default { export default {
mixins: [SettingMixin],
data() { data() {
return { return {
btnLoading: false,
formProfile: { formProfile: {
real_name: '', real_name: '',
mood: '', mood: '',
@ -58,19 +59,23 @@
}, },
methods: { methods: {
getProfile() { getProfile() {
if (!auth.isAuthicated()) { let profile = this.loadProfile()
this.$error('please login first.') if (profile !== null && profile !== undefined) {
} else {
let profile = auth.getUser()
Object.keys(this.formProfile).forEach(element => { Object.keys(this.formProfile).forEach(element => {
this.formProfile[element] = profile[element] if (profile[element] !== undefined) {
this.formProfile[element] = profile[element]
}
}) })
} }
}, },
updateProfile() { updateProfile() {
this.btnLoading = true
api.updateProfile(this.formProfile).then(res => { api.updateProfile(this.formProfile).then(res => {
this.$success('Success') this.$success('Success')
this.btnLoading = false
auth.setUser(res.data.data) auth.setUser(res.data.data)
}, _ => {
this.btnLoading = false
}) })
} }
} }

View File

@ -0,0 +1,159 @@
<template>
<Card :padding="0" :bordered="false" dis-hover>
<div class="flex-container">
<div class="left">
<p class="section-title">Change Password</p>
<Form class="setting-main" ref="formPassword" :model="formPassword" :rules="rulePassword">
<FormItem label="Old password" prop="old_password">
<Input v-model="formPassword.old_password" type="password"/>
</FormItem>
<FormItem label="New password" prop="new_password">
<Input v-model="formPassword.new_password" type="password"/>
</FormItem>
<FormItem label="Confirm new password" prop="again_password">
<Input v-model="formPassword.again_password" type="password"/>
</FormItem>
<FormItem v-if="visible.passwordAlert">
<Alert type="success">Password successfully updated, you have to login again after 3 seconds..</Alert>
</FormItem>
<Button type="primary" @click="changePassword">Update password</Button>
</Form>
</div>
<div class="middle separator"></div>
<div class="right">
<p class="section-title">Change Email</p>
<Form class="setting-main" ref="formEmail" :model="formEmail">
<FormItem label="Current password">
<Input v-model="formEmail.password"/>
</FormItem>
<FormItem label="Old Email">
<Input v-model="formEmail.old_email" disabled/>
</FormItem>
<FormItem label="New Email">
<Input v-model="formEmail.new_email"/>
</FormItem>
<Button type="primary">Change Email</Button>
</Form>
</div>
</div>
<!--<img :src="qrcodeSrc" id="qr-img"/>-->
</Card>
</template>
<script>
import api from '@/api.js'
import {SettingMixin} from '~/mixins'
export default {
mixins: [SettingMixin],
data() {
const validatePass = (rule, value, callback) => {
if (this.formPassword.old_password !== '') {
if (this.formPassword.old_password === this.formPassword.new_password) {
callback(new Error('The new password doesn\'t change'))
} else {
//
this.$refs.formPassword.validateField('again_password')
}
}
callback()
}
const validatePassAgain = (rule, value, callback) => {
if (value !== this.formPassword.new_password) {
callback(new Error('password does not match'))
}
callback()
}
return {
qrcodeSrc: '',
loading: {
btnPassword: false,
btnEmail: false
},
visible: {
passwordAlert: false,
emailAlert: false
},
formPassword: {
old_password: '',
new_password: '',
again_password: ''
},
formEmail: {
password: '',
old_email: '',
new_email: ''
},
rulePassword: {
old_password: [
{required: true, trigger: 'blur', min: 6, max: 20}
],
new_password: [
{required: true, trigger: 'blur', min: 6, max: 20},
{validator: validatePass, trigger: 'blur'}
],
again_password: [
{required: true, validator: validatePassAgain, trigger: 'change'}
]
},
ruleEmail: {}
}
},
mounted() {
this.getProfile()
},
methods: {
getProfile() {
let profile = this.loadProfile()
if (profile !== null && profile !== undefined) {
this.formEmail.old_email = profile.user.email
}
},
changePassword() {
this.loading.btnPassword = true
let data = Object.assign({}, this.formPassword)
delete data.again_password
api.changePassword(data).then(res => {
this.loading.btnPassword = false
this.visible.passwordAlert = true
setTimeout(() => {
this.visible.passwordAlert = false
this.$router.push({name: 'logout'})
}, 3000)
}, _ => {
this.loading.btnPassword = false
})
},
changeEmail() {
this.btnEmailLoading = true
// todo
},
getAuthImg() {
api.getTwoFactorQrcode().then(res => {
this.qrcodeSrc = res.data.data
})
}
}
}
</script>
<style lang="less" scoped>
.flex-container {
justify-content: flex-start;
.left {
flex: 1 1;
padding-right: 10%;
}
.middle {
flex: none;
}
.right {
flex: 1 1;
padding-right: 10%;
}
}
</style>

View File

@ -1,4 +1,5 @@
import Settings from './Settings.vue' import Settings from './Settings.vue'
import ProfileSetting from './children/ProfileSetting.vue' import ProfileSetting from './children/ProfileSetting.vue'
import SecuritySetting from './children/SecuritySetting.vue'
export {Settings, ProfileSetting} export {Settings, ProfileSetting, SecuritySetting}

View File

@ -0,0 +1,131 @@
<template>
<Panel :padding="30" class="container">
<div slot="title" class="center">Lost Password</div>
<template v-if="!successApply">
<Form :rules="ruleResetPassword" :model=formResetPassword ref="formResetPassword">
<Form-item prop="email">
<Input v-model="formResetPassword.email" placeholder="Your Email Address" size="large">
<Icon type="ios-email-outline" slot="prepend"></Icon>
</Input>
</Form-item>
<Form-item prop="captcha" style="margin-bottom:10px">
<div id="captcha">
<div id="captchaCode">
<Input v-model="formResetPassword.captcha" placeholder="Captcha" size="large">
<Icon type="ios-lightbulb-outline" slot="prepend"></Icon>
</Input>
</div>
<div id="captchaImg">
<Tooltip content="Click to refresh" placement="top">
<img :src="captchaSrc" @click="getCaptchaSrc"/>
</Tooltip>
</div>
</div>
</Form-item>
</Form>
<Button type="primary"
@click="sendEmail"
class="btn" long
:loading="btnLoading">Send Password Reset Email
</Button>
</template>
<template v-else>
<Alert type="success" show-icon>
Success
<span slot="desc">Password reset mail has been sent to your email.</span>
</Alert>
</template>
</Panel>
</template>
<script>
import api from '@/api'
import {FormMixin} from '~/mixins'
export default {
mixins: [FormMixin],
data() {
const validateEmail = (rule, value, callback) => {
if (value !== '') {
api.checkUsernameOrEmail(undefined, value).then(res => {
if (res.data.data.email === false) {
callback(new Error('This email doesn\'t exist'))
} else {
callback()
}
}, _ => callback())
} else {
callback()
}
}
return {
captchaSrc: '',
successApply: false,
btnLoading: false,
formResetPassword: {
email: '',
captcha: ''
},
ruleResetPassword: {
email: [
{required: true, type: 'email', trigger: 'blur'},
{validator: validateEmail, trigger: 'blur'}
],
captcha: [
{required: true, trigger: 'blur', min: 1, max: 10}
]
}
}
},
mounted() {
this.getCaptchaSrc()
},
methods: {
sendEmail() {
this.validateForm('formResetPassword').then(() => {
this.btnLoading = true
api.apply_reset_password(this.formResetPassword).then(res => {
//
setTimeout(() => {
this.btnLoading = false
this.successApply = true
}, 2000)
}, _ => {
this.btnLoading = false
this.formResetPassword.captcha = ''
this.getCaptchaSrc()
})
}, _ => {
})
}
}
}
</script>
<style scoped lang="less">
.container {
width: 450px;
margin: auto;
.center {
text-align: center;
}
#captcha {
display: flex;
flex-wrap: nowrap;
justify-content: space-between;
width: 100%;
height: 36px;
#captchaCode {
flex: auto;
}
#captchaImg {
margin-left: 10px;
padding: 3px;
flex: initial;
}
}
.btn {
margin-top: 18px;
text-align: center;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal :value="visible" @on-cancel="handleUpdateProp('update:visible', false)" :width="350"> <Modal :value="visible" @on-cancel="handleUpdateProp('update:visible', false)" :width="400" className="modal">
<div slot="header"> <div slot="header">
<span class="title">Welcome to OJ</span> <span class="title">Welcome to OJ</span>
</div> </div>
@ -42,7 +42,7 @@
<Form-item prop="captcha" style="margin-bottom:10px"> <Form-item prop="captcha" style="margin-bottom:10px">
<div id="captcha"> <div id="captcha">
<div id="captchaCode"> <div id="captchaCode">
<Input v-model="formRegister.captcha" placeholder="Capacha" size="large"> <Input v-model="formRegister.captcha" placeholder="Captcha" size="large">
<Icon type="ios-lightbulb-outline" slot="prepend"></Icon> <Icon type="ios-lightbulb-outline" slot="prepend"></Icon>
</Input> </Input>
</div> </div>
@ -57,12 +57,30 @@
</template> </template>
<div slot="footer" class="footer"> <div slot="footer" class="footer">
<template v-if="mode === 'login'"> <template v-if="mode === 'login'">
<Button type="primary" @click="handleLogin()" class="btn" long>Login</Button> <Button
type="primary"
@click="handleLogin()"
class="btn" long
:loading="btnLoginLoading">
Login
</Button>
<a @click.stop="handleUpdateProp('update:mode', 'register')">No account? Register now!</a> <a @click.stop="handleUpdateProp('update:mode', 'register')">No account? Register now!</a>
<a @click.stop="goResetPassword" style="float: right">Forget Password</a>
</template> </template>
<template v-else> <template v-else>
<Button type="primary" @click="handleRegister()" class="btn" long>Register Now</Button> <Button
<a @click.stop="handleUpdateProp('update:mode', 'login')">Already registed? Login now!</a> type="primary"
@click="handleRegister()"
class="btn" long
:loading="btnRegisterLoading">
Register
</Button>
<Button
type="ghost"
@click="handleUpdateProp('update:mode', 'login')"
class="btn" long>
Already registed? Login now!
</Button>
</template> </template>
</div> </div>
</Modal> </Modal>
@ -71,8 +89,10 @@
<script> <script>
import api from '@/api' import api from '@/api'
import auth from '@/utils/auth' import auth from '@/utils/auth'
import {FormMixin} from '~/mixins'
export default { export default {
mixins: [FormMixin],
props: { props: {
visible: { visible: {
required: true, required: true,
@ -90,7 +110,7 @@
const validateUsername = (rule, value, callback) => { const validateUsername = (rule, value, callback) => {
if (value !== '') { if (value !== '') {
api.checkUsernameOrEmail(value, undefined).then(res => { api.checkUsernameOrEmail(value, undefined).then(res => {
if (res.data.data.username === false) { if (res.data.data.username === true) {
callback(new Error('username already exists.')) callback(new Error('username already exists.'))
} else { } else {
callback() callback()
@ -103,8 +123,8 @@
const validateEmail = (rule, value, callback) => { const validateEmail = (rule, value, callback) => {
if (value !== '') { if (value !== '') {
api.checkUsernameOrEmail(undefined, value).then(res => { api.checkUsernameOrEmail(undefined, value).then(res => {
if (res.data.data.email === false) { if (res.data.data.email === true) {
callback(new Error('email already exists')) callback(new Error('email already exist'))
} else { } else {
callback() callback()
} }
@ -129,6 +149,8 @@
return { return {
captchaSrc: '', captchaSrc: '',
btnRegisterLoading: false,
btnLoginLoading: false,
formRegister: { formRegister: {
username: '', username: '',
password: '', password: '',
@ -171,32 +193,30 @@
} }
}, },
methods: { methods: {
validateForm(formName) { handleUpdateProp(eventName, value) {
let isValid = false this.$emit(eventName, value)
this.$refs[formName].validate(valid => {
if (!valid) {
this.$error('please validate the error fields')
}
isValid = valid
})
return isValid
}, },
handleRegister() { handleRegister() {
if (this.validateForm('formRegister')) { if (this.validateForm('formRegister')) {
let formData = Object.assign({}, this.formRegister) let formData = Object.assign({}, this.formRegister)
delete formData['passwordAgain'] delete formData['passwordAgain']
this.btnRegisterLoading = true
api.register(formData).then(res => { api.register(formData).then(res => {
this.$success('Register successed, go to login') this.$success('Register successed, go to login')
this.handleUpdateProp('update:mode', 'login') this.handleUpdateProp('update:mode', 'login')
this.btnRegisterLoading = false
}, _ => { }, _ => {
this.getCaptchaSrc() this.getCaptchaSrc()
this.formRegister.captcha = '' this.formRegister.captcha = ''
this.btnRegisterLoading = false
}) })
} }
}, },
handleLogin() { handleLogin() {
if (this.validateForm('formLogin')) { if (this.validateForm('formLogin')) {
this.btnLoginLoading = true
api.login(this.formLogin.uname, this.formLogin.passwd).then(res => { api.login(this.formLogin.uname, this.formLogin.passwd).then(res => {
this.btnLoginLoading = false
api.getUserInfo().then(res => { api.getUserInfo().then(res => {
auth.setUser(res.data.data) auth.setUser(res.data.data)
this.$bus.$emit('login-success', res.data.data) this.$bus.$emit('login-success', res.data.data)
@ -204,16 +224,14 @@
this.handleUpdateProp('update:visible', false) this.handleUpdateProp('update:visible', false)
}) })
}, _ => { }, _ => {
this.btnLoginLoading = false
}) })
} }
}, },
handleUpdateProp(eventName, value) {
this.$emit(eventName, value) goResetPassword() {
}, this.handleUpdateProp('update:visible', false)
getCaptchaSrc() { this.$router.push({name: 'apply-reset-password'})
api.getCaptcha().then(res => {
this.captchaSrc = res.data.data
})
} }
}, },
watch: { watch: {
@ -255,15 +273,18 @@
} }
.title { .title {
font-size: 16px; font-size: 18px;
font-weight: 600; font-weight: 600;
} }
.footer { .footer {
text-align: center;
margin: 0; margin: 0;
text-align: left;
.btn { .btn {
margin: 0 0 10px 0; margin: 0 0 15px 0;
&:last-child {
margin: 0;
}
} }
} }
</style> </style>

View File