mirror of
https://github.com/QingdaoU/OnlineJudgeFE.git
synced 2024-12-29 16:01:51 +00:00
support update password and send password reset email
This commit is contained in:
parent
69c3178a58
commit
f6b833594e
@ -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"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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')
|
||||||
},
|
},
|
||||||
|
@ -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 {
|
||||||
|
23
oj/src/components/mixins/form.js
Normal file
23
oj/src/components/mixins/form.js
Normal 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
|
||||||
// }
|
|
||||||
]
|
]
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
|
@ -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}
|
||||||
|
131
oj/src/views/user/ApplyResetPassword.vue
Normal file
131
oj/src/views/user/ApplyResetPassword.vue
Normal 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>
|
@ -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>
|
0
oj/src/views/user/ResetPassword.vue
Normal file
0
oj/src/views/user/ResetPassword.vue
Normal file
Loading…
Reference in New Issue
Block a user