| | |
| | | <template> |
| | | <div> |
| | | <div class="pam-consultant-login"> |
| | | <div class="pam-consultant-login__header mt-30">顧問登入</div> |
| | | <div class="pam-consultant-login__title mt-30">帳號</div> |
| | | <div> |
| | | <div class="pam-consultant-login"> |
| | | <div class="pam-consultant-login__header pam-paragraph">顧問登入</div> |
| | | <div class="pam-paragraph"> |
| | | <div class="pam-consultant-login__title">帳號</div> |
| | | <div class="position-r mt-10"> |
| | | <input type="text" |
| | | :model="loginDto.account" |
| | | v-model="consultantDto.username" |
| | | :disabled="onOTPVerifyStep === 'CAN_RESEND'" |
| | | class="pam-consultant-login__input" |
| | | placeholder="輸入eService帳號"> |
| | | <div class="pam-consultant-login__inputIcon text--primary cursor--pointer" @click="cookieAccount"> |
| | | <i :class="[isRemember ? 'icon-checkbox-1' : 'icon-checkbox','pr-5']"></i> |
| | | 記住 |
| | | <div v-if="onOTPVerifyStep === 'CAN_RESEND'" class="pam-consultant-login__inputIcon cursor--pointer fix-chrome-click--issue" |
| | | @click="deleteOtpInfo()"> |
| | | <i class="icon-close"></i> |
| | | </div> |
| | | <div v-if="onOTPVerifyStep !== 'CAN_RESEND'" class="pam-consultant-login__inputIcon text--primary cursor--pointer fix-chrome-click--issue" |
| | | @click="isRememberChange"> |
| | | <i :class="[isRememberUserName ? 'icon-checkbox-1' : 'icon-checkbox','pr-5']"></i> |
| | | 記住 |
| | | </div> |
| | | </div> |
| | | <div class="pam-consultant-login__title mt-30"> |
| | | <div>密碼</div> |
| | | <div class="text--primary fs-16 cursor--pointer" @click="forgetPassword">忘記密碼?</div> |
| | | </div> |
| | | </div> |
| | | <div class="pam-paragraph"> |
| | | <div class="password-Txt"> |
| | | <div class="pam-consultant-login__title ">密碼</div> |
| | | <div class="password-reset" @click="resetPassword">忘記密碼</div> |
| | | </div> |
| | | <div class="position-r mt-10"> |
| | | <input :type="[isShowPassword ? 'text' : 'password']" |
| | | :model="loginDto.password" |
| | | <input :type="checkPasswordType ? 'text' : 'password'" |
| | | v-model="consultantDto.password" |
| | | :disabled="onOTPVerifyStep === 'CAN_RESEND'" |
| | | class="pam-consultant-login__input" |
| | | placeholder="輸入eService密碼"> |
| | | <div class="pam-consultant-login__inputIcon cursor--pointer" @click="isShowPassword = !isShowPassword"> |
| | | <i :class="[isShowPassword ? 'icon-eye-1 fs-25' : 'icon-eye' , 'text--primary']"></i> |
| | | <div v-if="onOTPVerifyStep !== 'CAN_RESEND'" class="pam-consultant-login__inputIcon cursor--pointer fix-chrome-click--issue" |
| | | @click="isShowPassword = !isShowPassword"> |
| | | <i :class="[isShowPassword ? 'icon-eye':'icon-eye-1 fs-25', 'text--primary']"></i> |
| | | </div> |
| | | </div> |
| | | <div class="pam-consultant-login__title mt-30"> |
| | | <div>驗證碼</div> |
| | | <div class="text--dark-blue fs-16 cursor--pointer" @click="regenerateCode">重新產生</div> |
| | | </div> |
| | | </div> |
| | | <div class="pam-paragraph" v-if="onOTPVerifyStep === 'APPLY_OTP'"> |
| | | <div class="pam-consultant-login__title"> |
| | | <div>驗證碼 <span class="text--dark-blue fs-16">(區分大小寫)</span></div> |
| | | <div class="text--primary fs-16 cursor--pointer fix-chrome-click--issue" |
| | | @click="regenerateImgOfVerification">重新產生</div> |
| | | </div> |
| | | <div class="pam-consultant-login__verifyBlock mt-10"> |
| | | <div class="w-55"> |
| | | <input type="text" |
| | | :modal="loginDto.verificationCode" |
| | | class="pam-consultant-login__input" > |
| | | v-model="verificationCode" |
| | | maxlength="4" |
| | | class="pam-consultant-login__input"> |
| | | </div> |
| | | <div class="pam-consultant-login__verifyImg"></div> |
| | | </div> |
| | | <div class="pam-consultant-login__confirmBlock mt-30"> |
| | | <button class="pam-consultant-login__confirm cursor--pointer" @click="login">送出</button> |
| | | <div class="pam-consultant-login__verifyImg"> |
| | | <img :src="imgSrc" alt="驗證碼"> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- --> |
| | | <div v-show="onOTPVerifyStep === 'CAN_RESEND'"> |
| | | <el-row type="flex" justify="space-between"> |
| | | <div class="mdTxt">輸入 OTP 驗證碼</div> |
| | | <div class="otp-count-timer"> |
| | | {{counterTime(otpCounterSec)}} |
| | | </div> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <input |
| | | class="pam-consultant-login__input mt-10" |
| | | :class="{ |
| | | 'is-invalid': !otpCode |
| | | }" |
| | | v-model="otpCode" |
| | | placeholder="請輸入 OTP 驗證碼" |
| | | > |
| | | </el-row> |
| | | <div class="error mt-5 mb-10"> |
| | | <span v-show="otpCounterSec === 0">OTP 驗證碼已過期,請重發 OTP 驗證碼</span> |
| | | </div> |
| | | |
| | | <el-row> |
| | | <el-button |
| | | :disabled="!consultantDto.password || otpResendCounter !== 0 || !consultantDto.username" |
| | | @click="resetOtpSetting()" |
| | | icon="icon-arrow" |
| | | > |
| | | 重發 OTP 驗證碼<span |
| | | class="pam-field-title__hint pl-5" |
| | | v-if="otpResendCounter !== 0" |
| | | >({{ otpResendCounter }})</span> |
| | | </el-button> |
| | | |
| | | </el-row> |
| | | </div> |
| | | <div class="pam-paragraph"> |
| | | <el-button v-if="onOTPVerifyStep === 'APPLY_OTP'" icon="icon-arrow" |
| | | :disabled="!consultantDto.username || !consultantDto.password || verificationCode.length !== 4" |
| | | @click="applyOtpVerification"> |
| | | 發送 OTP 驗證碼</el-button> |
| | | </div> |
| | | <div class="pam-consultant-login__confirmBlock pam-paragraph" v-if="onOTPVerifyStep === 'CAN_RESEND'"> |
| | | <button class="pam-consultant-login__confirm cursor--pointer fix-chrome-click--issue" |
| | | :disabled="!consultantDto.username || !consultantDto.password || !otpCode || !otpCounterSec" |
| | | @click="sendInfo">送出</button> |
| | | </div> |
| | | </div> |
| | | |
| | | <PopUpFrame class="pam-popUpFrame" |
| | | :isOpen.sync="otpConfirmVisible" |
| | | > |
| | | <div class="pam-popUp-title text--center">已將驗證訊息發送至</div> |
| | | <div class="pam-popUp-title text--center">{{'email'}}</div> |
| | | <div class="pam-popUp-title text--center">請查看電子郵件並完成驗證流程</div> |
| | | <div class="pam-popUp-confirm-bolck mt-30"> |
| | | <div class="text--center"> |
| | | <el-button |
| | | type="primary" |
| | | @click="otpConfirmVisible = false" |
| | | >我知道了</el-button> |
| | | </div> |
| | | </div> |
| | | </PopUpFrame> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Vue, Component } from 'vue-property-decorator'; |
| | | import { Vue, Component , namespace, Watch } from 'nuxt-property-decorator'; |
| | | import { AxiosError } from 'axios'; |
| | | import { Role } from '~/shared/models/enum/Role'; |
| | | import messageBoxService from '~/shared/services/message-box.service'; |
| | | import loginService from '~/shared/services/login.service' |
| | | import { AgentInfo } from '~/shared/models/agent-info.model'; |
| | | |
| | | @Component({ |
| | | layout:'home' |
| | | }) |
| | | export default class ConsultantLogin extends Vue { |
| | | isRemember=false; |
| | | isShowPassword=false; |
| | | loginDto={ |
| | | account:'', |
| | | password:'', |
| | | verificationCode:'', |
| | | } |
| | | const loginStore = namespace('login.store'); |
| | | const roleStorage = namespace('localStorage'); |
| | | |
| | | @Component({ |
| | | layout: 'home' |
| | | }) |
| | | export default class ConsultantLogin extends Vue { |
| | | @roleStorage.Mutation |
| | | storageIdToken!: (token: string) => void; |
| | | |
| | | login():void{ |
| | | console.log('login'); |
| | | } |
| | | @roleStorage.Mutation |
| | | storageRole!: (role: string) => void; |
| | | |
| | | cookieAccount():void{ |
| | | this.isRemember = !this.isRemember; |
| | | if(this.isRemember){ |
| | | console.log('sotre account'); |
| | | @roleStorage.Mutation |
| | | storageConsultantId!:(id:string) => void; |
| | | |
| | | @loginStore.Action |
| | | getLoginConsultantDetail!: (agentNo: string) => Promise<AgentInfo>; |
| | | |
| | | consultantDto = { |
| | | password: '', |
| | | username: '', |
| | | }; |
| | | imgSrc = ''; |
| | | isRememberUserName = false; |
| | | isShowPassword = false; |
| | | verificationCode=''; |
| | | |
| | | otpConfirmVisible = false; |
| | | otpCode = ''; |
| | | onOTPVerifyStep: 'APPLY_OTP' | 'CAN_RESEND' = 'APPLY_OTP'; |
| | | otpCounterSec = 50; |
| | | otpResendCounter = 30; |
| | | otpInterval: NodeJS.Timeout | null = null; |
| | | otpIndexKey!: string; |
| | | |
| | | @Watch('onOTPVerifyStep') |
| | | onOTPVerifyStepChange() { |
| | | if (this.onOTPVerifyStep === 'APPLY_OTP') { |
| | | this.regenerateImgOfVerification(); |
| | | } |
| | | } |
| | | |
| | | //////////////////////////////////////////////////////////////////// |
| | | |
| | | mounted() { |
| | | this.getInitUserName(); |
| | | this.regenerateImgOfVerification(); |
| | | }; |
| | | |
| | | destroyed() { |
| | | clearInterval(this.otpInterval ?? undefined); |
| | | } |
| | | |
| | | private getInitUserName(): void { |
| | | const username = localStorage.getItem('consultantUserName') |
| | | if (username) { |
| | | this.consultantDto.username = username; |
| | | this.isRememberUserName = true; |
| | | } |
| | | } |
| | | |
| | | //////////////////////////////////////////////////////////////////// |
| | | |
| | | |
| | | public regenerateImgOfVerification(): void { |
| | | loginService.getImgOfVerification().then( imgOfBase64 =>{ |
| | | this.imgSrc = imgOfBase64; |
| | | this.verificationCode = ''; |
| | | }); |
| | | }; |
| | | |
| | | public isRememberChange():void{ |
| | | this.isRememberUserName = !this.isRememberUserName; |
| | | this.storeUserName(); |
| | | } |
| | | |
| | | public applyOtpVerification(type: string): void { |
| | | // TODO: sendOTP |
| | | const otpInfo = { |
| | | indexKey: "string", /** 用於帶入otp認證時 */ |
| | | success: true, /** Otp是否有成功發送 */ |
| | | failCode: "string", |
| | | failReason: "string", |
| | | } |
| | | if (otpInfo.success) { |
| | | // this.storageOtpTime(type, otpInfo); |
| | | this.startOtpSetting(type); |
| | | this.startOtpCount(); |
| | | } else { |
| | | // TODO: otp error |
| | | // const errorMsg = OtpErrorCode[otpInfo.failCode] ? OtpErrorCode[otpInfo.failCode]:'OTP系統錯誤'; |
| | | // messageBoxService.showErrorMessage(errorMsg); |
| | | } |
| | | } |
| | | |
| | | public sendInfo():void{ |
| | | this.isAlreadyDone |
| | | ? this.verify() |
| | | : messageBoxService.showErrorMessage('請確認帳號、密碼以及驗證碼是否填寫完畢'); |
| | | } |
| | | |
| | | get isAlreadyDone():boolean{ |
| | | return !!(this.verificationCode && this.consultantDto.username && this.consultantDto.password); |
| | | } |
| | | |
| | | resetPassword() { |
| | | window.open(process.env.CONSULTANT_FORGET_PASSWORD_URL); |
| | | } |
| | | |
| | | resetOtpSetting() { |
| | | clearInterval(this.otpInterval ?? undefined); |
| | | this.otpResendCounter = 30; |
| | | this.otpCounterSec = 50; |
| | | this.onOTPVerifyStep = 'APPLY_OTP'; |
| | | this.otpCode = ''; |
| | | } |
| | | |
| | | forgetPassword():void{ |
| | | console.log('forget password'); |
| | | counterTime(counterSec) { |
| | | let min = Math.floor(counterSec / 60); |
| | | let sec = Math.floor(counterSec % 60); |
| | | return `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`; |
| | | } |
| | | |
| | | deleteOtpInfo() { |
| | | this.resetOtpSetting(); |
| | | this.onOTPVerifyStep = 'APPLY_OTP'; |
| | | this.consultantDto.password = ''; |
| | | this.consultantDto.username = ''; |
| | | this.otpCode = ''; |
| | | } |
| | | |
| | | get checkPasswordType(): boolean { |
| | | return this.onOTPVerifyStep === 'CAN_RESEND' ? false : this.isShowPassword; |
| | | } |
| | | //////////////////////////////////////////////////////////////////// |
| | | |
| | | private verify():void{ |
| | | loginService.getVerificationStatus(this.verificationCode).then( verifySuccess => { |
| | | if(verifySuccess.data){ |
| | | this.loginWithConsultant(); |
| | | }else{ |
| | | this.clearValue(); |
| | | this.regenerateImgOfVerification(); |
| | | messageBoxService.showErrorMessage('驗證碼輸入錯誤'); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private loginWithConsultant(): void { |
| | | loginService.logInToConsultant(this.consultantDto, this.verificationCode).then(res => { |
| | | this.getLoginConsultantDetail(this.consultantDto.username); |
| | | this.storageIdToken(res.data.id_token); |
| | | this.storageRole(Role.ADMIN); |
| | | this.storageConsultantId(this.consultantDto.username) |
| | | this.storeUserName(); |
| | | this.$router.push('/myAppointmentList/appointmentList'); |
| | | }).catch((error:AxiosError)=>{ |
| | | this.checkHttpErrorStatus(error); |
| | | }); |
| | | } |
| | | private checkHttpErrorStatus(error:any):void{ |
| | | this.clearValue(); |
| | | this.regenerateImgOfVerification(); |
| | | switch (error.response.status) { |
| | | case 401: |
| | | const errorMsg = error.response.data.detail; |
| | | messageBoxService.showErrorMessage(errorMsg); |
| | | break; |
| | | |
| | | default: |
| | | const defaultErrorMsg = error.response.data.title |
| | | messageBoxService.showErrorMessage('',defaultErrorMsg); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private storeUserName(): void { |
| | | localStorage.setItem('consultantUserName', this.isRememberUserName ? this.consultantDto.username : ''); |
| | | }; |
| | | |
| | | private clearValue():void{ |
| | | if (!this.isRememberUserName) this.consultantDto.username=''; |
| | | this.consultantDto.password = ''; |
| | | this.verificationCode = ''; |
| | | } |
| | | |
| | | private startOtpSetting(type: string) { |
| | | this.onOTPVerifyStep = 'CAN_RESEND'; |
| | | this.otpConfirmVisible = true; |
| | | } |
| | | |
| | | regenerateCode():void{ |
| | | console.log('call api regenerate verificationCode') |
| | | private startOtpCount() { |
| | | this.otpInterval = setInterval(() => { |
| | | this.otpCounterSec -= 1; |
| | | if (this.otpResendCounter !== 0) { |
| | | this.otpResendCounter -= 1; |
| | | if (this.otpResendCounter === 0) { |
| | | // this.regenerateImgOfVerification(); |
| | | } |
| | | } |
| | | if (this.otpCounterSec === 0 && this.otpInterval) { |
| | | clearInterval(this.otpInterval); |
| | | } |
| | | }, 1000) |
| | | } |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .mt-20{ |
| | | .mt-20 { |
| | | margin-top: 20px; |
| | | } |
| | | .mt-25{ |
| | | |
| | | .mt-25 { |
| | | margin-top: 25px; |
| | | } |
| | | .w-55{ |
| | | |
| | | .w-55 { |
| | | width: 55% !important; |
| | | } |
| | | .position-r{ |
| | | |
| | | .position-r { |
| | | position: relative; |
| | | } |
| | | .pam-consultant-login{ |
| | | .password-Txt { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-end; |
| | | } |
| | | .password-reset { |
| | | font-size: 16px ; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .pam-popUp-title { |
| | | font-size: 20px; |
| | | line-height: 27px; |
| | | } |
| | | |
| | | .pam-consultant-login { |
| | | margin: auto; |
| | | width: 336px; |
| | | margin: 40px auto 30px auto; |
| | | font-size: 20px; |
| | | color: $PRIMARY_BLACK; |
| | | &__header{ |
| | | |
| | | &__header { |
| | | text-align: center; |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | letter-spacing: 1.2; |
| | | color: $PRIMARY_BLACK; |
| | | } |
| | | &__title{ |
| | | display:flex; |
| | | |
| | | &__title { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 0px 10px; |
| | | } |
| | | &__input{ |
| | | |
| | | &__input { |
| | | width: 100%; |
| | | outline: 0; |
| | | border: 1px solid #CCCCCC; |
| | |
| | | -webkit-box-sizing: border-box; |
| | | -moz-box-sizing: border-box; |
| | | |
| | | &Icon{ |
| | | &Icon { |
| | | position: absolute; |
| | | display: flex; |
| | | align-items: center; |
| | |
| | | right: 15px; |
| | | } |
| | | } |
| | | &__verifyBlock{ |
| | | |
| | | &__forgot-password { |
| | | color: $PRIMARY_RED; |
| | | text-decoration: none; |
| | | font-size: 16px; |
| | | } |
| | | |
| | | &__verifyBlock { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | } |
| | | &__verifyImg{ |
| | | width:126px; |
| | | border:1px black solid; |
| | | |
| | | &__verifyImg { |
| | | width: 126px; |
| | | height: 50px; |
| | | border:1px #cccccc solid; |
| | | img { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | } |
| | | &__confirmBlock{ |
| | | |
| | | &__confirmBlock { |
| | | display: flex; |
| | | justify-content: center; |
| | | } |
| | | &__confirm{ |
| | | |
| | | &__confirm { |
| | | color: $PRIMARY_WHITE; |
| | | width: 80px; |
| | | height: 50px; |
| | | border-radius: 30px; |
| | | border: 1px solid $LIGHT_GREY; |
| | | background-color:$PRIMARY_RED; |
| | | background-color: $PRIMARY_RED; |
| | | font-weight: 700; |
| | | |
| | | &:disabled { |
| | | color: $PRIMARY_WHITE; |
| | | background-color: $MID_GREY; |
| | | border-color: $MID_GREY; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pam-field-title__hint { |
| | | @extend .smTxt_bold; |
| | | color: #68737A; |
| | | } |
| | | |
| | | .error { |
| | | @extend .smTxt_bold; |
| | | @extend .text--primary; |
| | | height: 16px; |
| | | } |
| | | </style> |