|
@@ -1,72 +1,105 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="login-container" id="particles-js">
|
|
<div class="login-container" id="particles-js">
|
|
|
<div class="bg-logo anim"></div>
|
|
<div class="bg-logo anim"></div>
|
|
|
- <el-form
|
|
|
|
|
- ref="loginForm"
|
|
|
|
|
- :model="loginForm"
|
|
|
|
|
- :rules="loginRules"
|
|
|
|
|
- class="login-form"
|
|
|
|
|
- autocomplete="on"
|
|
|
|
|
- label-position="left">
|
|
|
|
|
-
|
|
|
|
|
|
|
+ <div class="login-form">
|
|
|
<div class="title-container">
|
|
<div class="title-container">
|
|
|
<!-- <h3 class="title">{{appName}}</h3> -->
|
|
<!-- <h3 class="title">{{appName}}</h3> -->
|
|
|
<img class="csms-logo" src="../../icons/logo.png"/>
|
|
<img class="csms-logo" src="../../icons/logo.png"/>
|
|
|
- <p class="title">CSMS LOGIN</p>
|
|
|
|
|
|
|
+ <p class="title" v-if="showOTP">Please enter 6-digit OTP sent to your email</p>
|
|
|
|
|
+ <p class="title" v-else-if="passwordTitle">{{passwordTitle}}</p>
|
|
|
|
|
+ <p class="title" v-else>{{showSetPassword ? "Reset Password" : "CSMS LOGIN"}}</p>
|
|
|
|
|
+ <p class="sub-title" v-if="passwordSubTitle">{{passwordSubTitle}}</p>
|
|
|
</div>
|
|
</div>
|
|
|
-
|
|
|
|
|
- <el-form-item prop="email">
|
|
|
|
|
- <div class="form-login-input">
|
|
|
|
|
- <span class="svg-container">
|
|
|
|
|
- <svg-icon icon-class="email" />
|
|
|
|
|
- </span>
|
|
|
|
|
- <el-input
|
|
|
|
|
- ref="username"
|
|
|
|
|
- v-model="loginForm.email"
|
|
|
|
|
- name="email"
|
|
|
|
|
- type="text"
|
|
|
|
|
- tabindex="1"
|
|
|
|
|
- autocomplete="on"
|
|
|
|
|
- clearable
|
|
|
|
|
- maxlength="50"
|
|
|
|
|
- @focus="() => focusFiled.email = true"
|
|
|
|
|
- @blur="() => focusFiled.email = false"/>
|
|
|
|
|
- <label :class='isNameValid ? "focus" : ""'>Email</label>
|
|
|
|
|
- </div>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item prop="password">
|
|
|
|
|
- <div class="form-login-input">
|
|
|
|
|
- <span class="svg-container">
|
|
|
|
|
- <svg-icon icon-class="password" />
|
|
|
|
|
- </span>
|
|
|
|
|
- <el-input
|
|
|
|
|
- ref="password"
|
|
|
|
|
- v-model="loginForm.password"
|
|
|
|
|
- :type="passwordType"
|
|
|
|
|
- name="password"
|
|
|
|
|
- tabindex="2"
|
|
|
|
|
- autocomplete="off"
|
|
|
|
|
- clearable
|
|
|
|
|
- maxlength="20"
|
|
|
|
|
- @keyup.enter.native="handleLogin"
|
|
|
|
|
- @focus="() => focusFiled.password = true"
|
|
|
|
|
- @blur="() => focusFiled.password = false"
|
|
|
|
|
- />
|
|
|
|
|
- <span class="show-pwd" @click="showPwd">
|
|
|
|
|
- <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
|
|
|
|
|
- </span>
|
|
|
|
|
- <label :class='isPsdValid ? "focus" : ""'>Password</label>
|
|
|
|
|
|
|
+ <div v-if="showResetSuccess">
|
|
|
|
|
+ <div style="height: 200px;"></div>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ class="login-button"
|
|
|
|
|
+ @click="handleShowLogin">
|
|
|
|
|
+ GO TO LOGIN
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <OtpView
|
|
|
|
|
+ v-else-if="showOTP"
|
|
|
|
|
+ :loginInfo="loginForm"
|
|
|
|
|
+ :resetInfo="newPasswordInfo"
|
|
|
|
|
+ :isReset="showSetPassword"
|
|
|
|
|
+ @onLogin="handleLogin"
|
|
|
|
|
+ @onReset="handleResetSuccess"/>
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ v-else-if="!showSetPassword"
|
|
|
|
|
+ ref="loginForm"
|
|
|
|
|
+ :model="loginForm"
|
|
|
|
|
+ :rules="loginRules"
|
|
|
|
|
+ autocomplete="on"
|
|
|
|
|
+ label-position="left">
|
|
|
|
|
+ <el-form-item prop="email">
|
|
|
|
|
+ <div class="form-login-input">
|
|
|
|
|
+ <span class="svg-container">
|
|
|
|
|
+ <svg-icon icon-class="email" />
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ ref="username"
|
|
|
|
|
+ v-model="loginForm.email"
|
|
|
|
|
+ name="email"
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ tabindex="1"
|
|
|
|
|
+ autocomplete="on"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ maxlength="50"
|
|
|
|
|
+ @focus="() => focusFiled.email = true"
|
|
|
|
|
+ @blur="() => focusFiled.email = false"/>
|
|
|
|
|
+ <label :class='isNameValid ? "focus" : ""'>Email</label>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item prop="password">
|
|
|
|
|
+ <div class="form-login-input">
|
|
|
|
|
+ <span class="svg-container">
|
|
|
|
|
+ <svg-icon icon-class="password" />
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ ref="password"
|
|
|
|
|
+ v-model="loginForm.password"
|
|
|
|
|
+ :type="passwordType"
|
|
|
|
|
+ name="password"
|
|
|
|
|
+ tabindex="2"
|
|
|
|
|
+ autocomplete="off"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ maxlength="32"
|
|
|
|
|
+ @keyup.enter.native="onPreLogin"
|
|
|
|
|
+ @focus="() => focusFiled.password = true"
|
|
|
|
|
+ @blur="() => focusFiled.password = false"
|
|
|
|
|
+ />
|
|
|
|
|
+ <span class="show-pwd" @click="showPwd">
|
|
|
|
|
+ <svg-icon :icon-class="passwordType === 'password' ? 'eye' : 'eye-open'" />
|
|
|
|
|
+ </span>
|
|
|
|
|
+ <label :class='isPsdValid ? "focus" : ""'>Password</label>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ :loading="loading"
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ class="login-button"
|
|
|
|
|
+ @click.native.prevent="onPreLogin">
|
|
|
|
|
+ LOGIN
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="content"
|
|
|
|
|
+ class="forgot-button"
|
|
|
|
|
+ @click="handleForgotPassword">
|
|
|
|
|
+ FORGET PASSWORD
|
|
|
|
|
+ </el-button>
|
|
|
</div>
|
|
</div>
|
|
|
- </el-form-item>
|
|
|
|
|
-
|
|
|
|
|
- <el-button
|
|
|
|
|
- :loading="loading"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- class="login-button"
|
|
|
|
|
- @click.native.prevent="handleLogin">
|
|
|
|
|
- Login
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </el-form>
|
|
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <Password
|
|
|
|
|
+ v-else
|
|
|
|
|
+ :email="expireEmail"
|
|
|
|
|
+ @back="handleShowLogin"
|
|
|
|
|
+ @confirm="onResetPasswordConfirm"/>
|
|
|
|
|
+ </div>
|
|
|
<div class="copyinfo">©{{copyYear}} {{company}}</div>
|
|
<div class="copyinfo">©{{copyYear}} {{company}}</div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
@@ -78,6 +111,10 @@ import settings from '../../settings.js'
|
|
|
import {getEmail} from '../../utils/auth.js'
|
|
import {getEmail} from '../../utils/auth.js'
|
|
|
import particles from 'particles.js'
|
|
import particles from 'particles.js'
|
|
|
import animateJson from './animate.json'
|
|
import animateJson from './animate.json'
|
|
|
|
|
+import Password from './Password.vue'
|
|
|
|
|
+import OtpView from './otp.vue'
|
|
|
|
|
+import api from '@/http/api/user'
|
|
|
|
|
+import {parseTime} from '@/utils/index'
|
|
|
const { isNavigationFailure, NavigationFailureType } = VueRouter
|
|
const { isNavigationFailure, NavigationFailureType } = VueRouter
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
@@ -95,6 +132,7 @@ export default {
|
|
|
appName: settings.title,
|
|
appName: settings.title,
|
|
|
company: settings.company,
|
|
company: settings.company,
|
|
|
loginForm: {
|
|
loginForm: {
|
|
|
|
|
+ otp: '',
|
|
|
email: '',
|
|
email: '',
|
|
|
password: ''
|
|
password: ''
|
|
|
},
|
|
},
|
|
@@ -122,7 +160,14 @@ export default {
|
|
|
focusFiled: {
|
|
focusFiled: {
|
|
|
email: false,
|
|
email: false,
|
|
|
password: false
|
|
password: false
|
|
|
- }
|
|
|
|
|
|
|
+ },
|
|
|
|
|
+ showOTP: false,
|
|
|
|
|
+ expireEmail: "",
|
|
|
|
|
+ showSetPassword: false,
|
|
|
|
|
+ showResetSuccess: false,
|
|
|
|
|
+ passwordTitle: "",
|
|
|
|
|
+ passwordSubTitle: "",
|
|
|
|
|
+ newPasswordInfo: {}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
watch: {
|
|
watch: {
|
|
@@ -153,6 +198,7 @@ export default {
|
|
|
return (this.loginForm.password !== "" || this.focusFiled.password)
|
|
return (this.loginForm.password !== "" || this.focusFiled.password)
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ components: {Password, OtpView},
|
|
|
created() {
|
|
created() {
|
|
|
// window.addEventListener('storage', this.afterQRScan)
|
|
// window.addEventListener('storage', this.afterQRScan)
|
|
|
const user = getEmail()
|
|
const user = getEmail()
|
|
@@ -189,45 +235,111 @@ export default {
|
|
|
this.$refs.password.focus()
|
|
this.$refs.password.focus()
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
- handleLogin() {
|
|
|
|
|
|
|
+ handleShowLogin() {
|
|
|
|
|
+ this.showOTP = false;
|
|
|
|
|
+ this.showSetPassword = false;
|
|
|
|
|
+ this.showResetSuccess = false;
|
|
|
|
|
+ this.expireEmail = "";
|
|
|
|
|
+ this.passwordTitle = "";
|
|
|
|
|
+ this.passwordSubTitle = "";
|
|
|
|
|
+ this.newPasswordInfo = {};
|
|
|
|
|
+ },
|
|
|
|
|
+ handleResetSuccess() {
|
|
|
|
|
+ this.handleShowLogin();
|
|
|
|
|
+ this.showResetSuccess = true;
|
|
|
|
|
+ this.passwordTitle = "Successful. Your password has been changed.";
|
|
|
|
|
+ this.passwordSubTitle = parseTime(new Date(), "{d}/{m}/{y}");
|
|
|
|
|
+ },
|
|
|
|
|
+ handleForgotPassword() {
|
|
|
|
|
+ this.handleShowLogin();
|
|
|
|
|
+ this.showSetPassword = true;
|
|
|
|
|
+ },
|
|
|
|
|
+ handleResetPassword(isFirst) {
|
|
|
|
|
+ this.handleShowLogin();
|
|
|
|
|
+ this.passwordTitle = isFirst ? "Please set your password." : "Password Expired.";
|
|
|
|
|
+ this.passwordSubTitle = isFirst ? "" : "Please create new password";
|
|
|
|
|
+ this.expireEmail = this.loginForm.email;
|
|
|
|
|
+ this.showSetPassword = true;
|
|
|
|
|
+ },
|
|
|
|
|
+ onResetPasswordConfirm(info) {
|
|
|
|
|
+ //reset 忘记密码
|
|
|
|
|
+ //create 首次登录、密码过期
|
|
|
|
|
+ this.loginForm.password = "";
|
|
|
|
|
+ this.newPasswordInfo = info;
|
|
|
|
|
+ this.showOTP = true;
|
|
|
|
|
+ },
|
|
|
|
|
+ onPreLogin() {
|
|
|
this.$refs.loginForm.validate(valid => {
|
|
this.$refs.loginForm.validate(valid => {
|
|
|
if (valid) {
|
|
if (valid) {
|
|
|
this.loading = true
|
|
this.loading = true
|
|
|
- this.$store.dispatch('user/login', this.loginForm).then((response) => {
|
|
|
|
|
- let path = this.redirect || '/'
|
|
|
|
|
- if (response.data.roleName === "MCST") {
|
|
|
|
|
- path = '/site-management'
|
|
|
|
|
- } else if (response.data.resources.length > 0) {
|
|
|
|
|
- let validPath = false;
|
|
|
|
|
- for (let _route of response.data.resources) {
|
|
|
|
|
- if (path.indexOf(_route.resourcePath) >= 0) {
|
|
|
|
|
- validPath = true;
|
|
|
|
|
- break
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- if (!validPath) {
|
|
|
|
|
- path = response.data.resources[0].resourcePath;
|
|
|
|
|
|
|
+ api.preLogin(this.loginForm).then(res => {
|
|
|
|
|
+ if (res.code) {
|
|
|
|
|
+ switch(res.code) {
|
|
|
|
|
+ case 2016: //未启用二次登录验证
|
|
|
|
|
+ this.handleLogin("");
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2017: //首次登录流程
|
|
|
|
|
+ this.handleResetPassword(true);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2018: //密码过期流程
|
|
|
|
|
+ this.handleResetPassword(false);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 2019: //OTP 登录流程
|
|
|
|
|
+ this.showOTP = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ this.handleLogin("");
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- this.$router.push({
|
|
|
|
|
- path: path,
|
|
|
|
|
- query: this.otherQuery,
|
|
|
|
|
- }).catch((failure) => {
|
|
|
|
|
- if (!isNavigationFailure(failure, NavigationFailureType.redirected)) {
|
|
|
|
|
- throw failure
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ }).catch(err => {
|
|
|
|
|
+ this.$message({
|
|
|
|
|
+ type: 'error',
|
|
|
|
|
+ message: err
|
|
|
})
|
|
})
|
|
|
- this.loading = false
|
|
|
|
|
- }).catch((error) => {
|
|
|
|
|
- Message.error(error.message)
|
|
|
|
|
- this.loading = false
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ }).finally(() => {
|
|
|
|
|
+ this.loading = false;
|
|
|
|
|
+ })
|
|
|
} else {
|
|
} else {
|
|
|
console.log('error submit!!')
|
|
console.log('error submit!!')
|
|
|
- return false
|
|
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
|
|
+ handleLogin(otp) {
|
|
|
|
|
+ if (otp) {
|
|
|
|
|
+ this.loginForm.otp = otp;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$store.dispatch('user/login', this.loginForm).then((response) => {
|
|
|
|
|
+ let path = this.redirect || '/'
|
|
|
|
|
+ if (response.data.roleName === "MCST") {
|
|
|
|
|
+ path = '/site-management'
|
|
|
|
|
+ } else if (response.data.resources.length > 0) {
|
|
|
|
|
+ let validPath = false;
|
|
|
|
|
+ for (let _route of response.data.resources) {
|
|
|
|
|
+ if (path.indexOf(_route.resourcePath) >= 0) {
|
|
|
|
|
+ validPath = true;
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!validPath) {
|
|
|
|
|
+ path = response.data.resources[0].resourcePath;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.$router.push({
|
|
|
|
|
+ path: path,
|
|
|
|
|
+ query: this.otherQuery,
|
|
|
|
|
+ }).catch((failure) => {
|
|
|
|
|
+ if (!isNavigationFailure(failure, NavigationFailureType.redirected)) {
|
|
|
|
|
+ throw failure
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ }).catch((error) => {
|
|
|
|
|
+ Message.error(error.message)
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ this.handleShowLogin();
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
getOtherQuery(query) {
|
|
getOtherQuery(query) {
|
|
|
return Object.keys(query).reduce((acc, cur) => {
|
|
return Object.keys(query).reduce((acc, cur) => {
|
|
|
if (cur !== 'redirect') {
|
|
if (cur !== 'redirect') {
|
|
@@ -256,12 +368,13 @@ export default {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
z-index: 2;
|
|
z-index: 2;
|
|
|
margin: 0 auto;
|
|
margin: 0 auto;
|
|
|
- max-width: 420px;
|
|
|
|
|
|
|
+ max-width: 450px;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
background: #fff;
|
|
background: #fff;
|
|
|
position: relative;
|
|
position: relative;
|
|
|
border-radius: 3px;
|
|
border-radius: 3px;
|
|
|
padding: 30px 40px 50px;
|
|
padding: 30px 40px 50px;
|
|
|
|
|
+ transition: all .3s;
|
|
|
box-shadow: 1px 1px 5px 3px rgba(0,0,0,.2);
|
|
box-shadow: 1px 1px 5px 3px rgba(0,0,0,.2);
|
|
|
}
|
|
}
|
|
|
.title-container {
|
|
.title-container {
|
|
@@ -270,14 +383,23 @@ export default {
|
|
|
padding: 20px 0 0;
|
|
padding: 20px 0 0;
|
|
|
|
|
|
|
|
.title {
|
|
.title {
|
|
|
- font-size: 18px;
|
|
|
|
|
|
|
+ font-size: 16px;
|
|
|
color: #333;
|
|
color: #333;
|
|
|
margin: 20px 0;
|
|
margin: 20px 0;
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
font-weight: bold;
|
|
font-weight: bold;
|
|
|
}
|
|
}
|
|
|
|
|
+ .sub-title {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ margin-top: -10px;
|
|
|
|
|
+ padding-bottom: 5px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ }
|
|
|
.csms-logo {
|
|
.csms-logo {
|
|
|
max-width: 200px;
|
|
max-width: 200px;
|
|
|
|
|
+ height: 70px;
|
|
|
|
|
+ object-fit: contain;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -343,7 +465,14 @@ export default {
|
|
|
color: #fff;
|
|
color: #fff;
|
|
|
padding: 15px;
|
|
padding: 15px;
|
|
|
margin-top: 40px;
|
|
margin-top: 40px;
|
|
|
- margin-bottom: 30px;
|
|
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .forgot-button {
|
|
|
|
|
+ width:100%;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ margin: 0 0 20px;
|
|
|
font-weight: bold;
|
|
font-weight: bold;
|
|
|
}
|
|
}
|
|
|
|
|
|