| | |
| | | <template> |
| | | <div class="pam-login-page"> |
| | | <div class="text--middle">登入</div> |
| | | <div class='mb-30'> |
| | | <div class="mdTxt">登入 | 註冊方式</div> |
| | | <div class="pam-field-title__hint mt-5 mb-10" |
| | | >顧問將會以此{{connectDevice === 'MOBILE' ? '手機號碼' : 'Email'}}為主要預約諮詢聯繫方式</div> |
| | | |
| | | <div class="pam-paragraph"> |
| | | <div class="mdTxt"> |
| | | 綁定方式 |
| | | </div> |
| | | <div class="pam-tags"> |
| | | <el-row type="flex" class="pt-10"> |
| | | <el-button |
| | | :class="{ 'active': connectDevice === 'MOBILE'}" |
| | | @click="connectDevice = 'MOBILE'">手機號碼</el-button> |
| | | <el-button |
| | | :class="{ 'active': connectDevice === 'EMAIL'}" |
| | | @click="connectDevice = 'EMAIL'">Email</el-button> |
| | | </el-row> |
| | | |
| | | |
| | | </div> |
| | | <el-row type="flex" class="pt-10" v-show="connectDevice === 'MOBILE'"> |
| | | <input |
| | | class="pam-input" |
| | | :class="{ |
| | | 'is-invalid': !phoneNumber |
| | | }" |
| | | v-model="phoneNumber" |
| | | placeholder="請輸入手機號碼" |
| | | > |
| | | </el-row> |
| | | |
| | | <el-row class="pt-10" v-show="connectDevice === 'EMAIL'"> |
| | | <input |
| | | class="pam-input" |
| | | :class="{ |
| | | 'is-invalid': !phoneNumber |
| | | }" |
| | | v-model="email" |
| | | placeholder="請輸入 Email 地址" |
| | | > |
| | | </el-row> |
| | | |
| | | <div class="pt-30" v-show="showPhoneOtpCodeField"> |
| | | <el-row type="flex" justify="space-between"> |
| | | <div class="mdTxt">輸入驗證碼</div> |
| | | <div class="otp-count-timer"> |
| | | 13:50 |
| | | </div> |
| | | <div class="pam-tags"> |
| | | <el-row type="flex" class="pt-30"> |
| | | <el-button |
| | | :class="{ 'active': connectDevice === 'MOBILE'}" |
| | | @click="connectDevice = 'MOBILE'">手機號碼</el-button> |
| | | <el-button |
| | | :class="{ 'active': connectDevice === 'EMAIL'}" |
| | | @click="connectDevice = 'EMAIL'">Email</el-button> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <el-row class="pt-10"> |
| | | <div class="pam-inputs mb-10"> |
| | | <div class="pam-input-position pt-10" v-show="connectDevice === 'MOBILE'"> |
| | | <input |
| | | class="pam-input" |
| | | :class="{ |
| | | 'is-invalid': !phoneValid |
| | | }" |
| | | v-model="phoneNumber" |
| | | placeholder="請輸入手機號碼" |
| | | :disabled="showPhoneOtpCodeField" |
| | | > |
| | | <i |
| | | class="icon-close" |
| | | v-if="onPhoneVerifyStep !== 'APPLY_OTP'" |
| | | @click="deleteOtpInfo('MOBILE')" |
| | | ></i> |
| | | <div class="error mt-5 mb-5"> |
| | | <span v-show="!phoneValid">手機號碼格式有誤</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="pam-input-position pt-10" v-show="connectDevice === 'EMAIL'"> |
| | | <input |
| | | class="pam-input" |
| | | :class="{ |
| | | 'is-invalid': !otpCode |
| | | 'is-invalid': !emailValid |
| | | }" |
| | | v-model="otpCode" |
| | | placeholder="請輸入驗證碼" |
| | | v-model="email" |
| | | placeholder="請輸入 Email 地址" |
| | | :disabled="showEmailVerifyField" |
| | | > |
| | | <i |
| | | class="icon-close" |
| | | v-if="showEmailVerifyField" |
| | | @click="deleteOtpInfo('EMAIL')" |
| | | ></i> |
| | | <div class="error mt-5 mb-5"> |
| | | <span v-show="!emailValid">Email格式有誤</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- mobile 驗證碼 --> |
| | | <template v-if="connectDevice === 'MOBILE'"> |
| | | <div v-show="showPhoneOtpCodeField"> |
| | | <el-row type="flex" justify="space-between"> |
| | | <div class="mdTxt">輸入驗證碼</div> |
| | | <div class="otp-count-timer"> |
| | | {{counterTime(otpCounterSec)}} |
| | | </div> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <input |
| | | class="pam-input mt-10" |
| | | :class="{ |
| | | 'is-invalid': !otpCode |
| | | }" |
| | | v-model="otpCode" |
| | | placeholder="請輸入驗證碼" |
| | | > |
| | | </el-row> |
| | | <div class="error mt-5 mb-10"> |
| | | <span v-show="otpCounterSec === 0">驗證碼已過期,請重發驗證碼</span> |
| | | </div> |
| | | |
| | | <el-row> |
| | | <el-button |
| | | :disabled="!phoneNumber || otpResendCounter !== 0 || !phoneValid" |
| | | @click="resentOtp('MOBILE')" |
| | | icon="icon-arrow" |
| | | > |
| | | 重發驗證碼<span |
| | | class="pam-field-title__hint pl-5" |
| | | v-if="otpResendCounter !== 0" |
| | | >({{ otpResendCounter }})</span> |
| | | </el-button> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <el-row> |
| | | <el-button |
| | | v-if="onPhoneVerifyStep === 'APPLY_OTP'" |
| | | :disabled="!phoneNumber || !phoneValid" |
| | | @click="applyOtpVerification('MOBILE')" |
| | | icon="icon-arrow" |
| | | > |
| | | 發送驗證碼 |
| | | </el-button> |
| | | </el-row> |
| | | |
| | | <el-row class="pt-10"> |
| | | <button |
| | | class="pam-otp-resend-btn" |
| | | :class="{'disabled': true}"> |
| | | <i class="icon-arrow"></i> |
| | | 重發驗證碼({{ otpResendCounter }}) |
| | | </button> |
| | | </el-row> |
| | | </div> |
| | | </template> |
| | | |
| | | <div v-show="showEmailVerifyField"> |
| | | <el-row class="pt-10"> |
| | | <button |
| | | class="pam-otp-resend-btn" |
| | | :class="{'disabled': onEmailVerifyResendStatus === 'CANNOT_RESEND'}"> |
| | | <i class="icon-arrow"></i> |
| | | 重發驗證碼({{ emailResendCounter }}) |
| | | </button> |
| | | <!-- email 驗證碼 --> |
| | | <template v-if="connectDevice === 'EMAIL'"> |
| | | <el-row v-show="showEmailVerifyField"> |
| | | <el-row type="flex" justify="space-between"> |
| | | <div class="mdTxt">輸入驗證碼</div> |
| | | <div class="otp-count-timer"> |
| | | {{counterTime(emailCounterSec)}} |
| | | </div> |
| | | </el-row> |
| | | |
| | | <el-row> |
| | | <input |
| | | class="pam-input mt-10" |
| | | :class="{ |
| | | 'is-invalid': !emailOtpCode |
| | | }" |
| | | v-model="emailOtpCode" |
| | | placeholder="請輸入驗證碼" |
| | | > |
| | | </el-row> |
| | | <div class="error mt-5 mb-10"> |
| | | <span v-show="emailCounterSec === 0">驗證碼已過期,請重發驗證碼</span> |
| | | </div> |
| | | |
| | | <el-button |
| | | :disabled="!email || emailResendCounter !== 0 || !emailValid" |
| | | icon="icon-arrow" |
| | | @click="resentOtp('EMAIL')" |
| | | > |
| | | 重發驗證碼<span |
| | | v-if="emailResendCounter !== 0" |
| | | class="pam-field-title__hint pl-5" |
| | | >({{ emailResendCounter }})</span> |
| | | </el-button> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <el-row v-show="!showEmailVerifyField"> |
| | | <el-button |
| | | :disabled="!email || !emailValid" |
| | | @click="applyOtpVerification('EMAIL')" |
| | | icon="icon-arrow" |
| | | > |
| | | 發送驗證碼 |
| | | </el-button> |
| | | </el-row> |
| | | </template> |
| | | </div> |
| | | |
| | | <el-row type="flex" justify="center" class="pam-login-page__action-bar"> |
| | | <div v-if="connectDevice === 'MOBILE'"> |
| | | <el-button |
| | | type="primary" |
| | | v-if="onPhoneVerifyStep === 'APPLY_OTP'" |
| | | :disabled="!phoneNumber" |
| | | @click="applyOtpVerification"> |
| | | 發送驗證碼 |
| | | </el-button> |
| | | |
| | | <el-button |
| | | type="primary" |
| | | v-if="connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP'" |
| | | :disabled="!otpCode" |
| | | @click="registerDialogVisable = true"> |
| | | 送出 |
| | | </el-button> |
| | | </div> |
| | | |
| | | <el-row type="flex" justify="center" class="pam-login-page__action-bar mt-30"> |
| | | <el-button |
| | | type="primary" |
| | | v-if="(connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP') || (connectDevice === 'EMAIL' && onEmailVerifyResendStatus === 'CAN_RESEND')" |
| | | :disabled="isSubmitBtnDisabled" |
| | | @click="login"> |
| | | 送出 |
| | | </el-button> |
| | | </el-row> |
| | | |
| | | <el-dialog |
| | | title="歡迎新使用者" |
| | | :custom-class="'pam-register-dialog'" |
| | | :visible.sync="registerDialogVisable" |
| | | :fullscreen="true" |
| | | :close-on-click-modal="false" |
| | | :show-close="false" |
| | | center> |
| | | <span> |
| | | <PopUpFrame |
| | | :isOpen.sync="registerDialogVisible" |
| | | :dialogWidth="'90%'" |
| | | class="pam-register-dialog" |
| | | @closePopUp="isReadContract = false;agreeContract = false" |
| | | > |
| | | <div class="subTitle text--center mb-20">歡迎新使用者</div> |
| | | <el-row> |
| | | <input |
| | | class="pam-input" |
| | |
| | | </el-row> |
| | | <el-row class="pt-10"> |
| | | <div |
| | | v-if="registerDialogVisible" |
| | | class="mdTxt pam-register-dialog__contract" |
| | | @scroll="detectContructReadStatus"> |
| | | ref="contract" |
| | | @scroll="detectContractReadStatus"> |
| | | <h3>蒐集個人資料告知事項</h3> |
| | | <p class="mt-10"> |
| | | 遵守個人資料保護法規定,在您提供個人資料予本處前,依法告 |
| | | 知下列事項: |
| | | 遵守個人資料保護法規定,在您提供個人資料予本處前,依法告知下列事項: |
| | | <p> |
| | | |
| | | <p class="mt-10"> |
| | |
| | | </p> |
| | | |
| | | <p class="mt-10"> |
| | | 四、除蒐集之目的涉及國際業務或活動外,本處僅於中華民國領域內利用您的個人資 |
| | | 料。 |
| | | 四、除蒐集之目的涉及國際業務或活動外,本處僅於中華民國領域內利用您的個人資料。 |
| | | </p> |
| | | |
| | | <p class="mt-10"> |
| | | 五、本處將於原蒐集之特定目的、本次以外之產業之推廣、宣導及輔導、以及其他公 |
| | | 務機關請求行政協助之目的範圍內,合理利用您的個人資料。 |
| | | 五、本處將於原蒐集之特定目的、本次以外之產業之推廣、宣導及輔導、以及其他公務機關請求行政協助之目的範圍內,合理利用您的個人資料。 |
| | | </p> |
| | | |
| | | <p class="mt-10"> |
| | |
| | | </el-row> |
| | | <el-row class="pt-30"> |
| | | <div class="pam-agree-radio"> |
| | | <label for="agreeControct" class="pam-radio"> |
| | | <label for="agreeContract" class="pam-radio" |
| | | :class="{disabled: !isReadContract}"> |
| | | <input |
| | | type="radio" |
| | | id="agreeControct" |
| | | @click="agreeControct = !agreeControct" |
| | | value="agreeControct"> |
| | | <i :class="agreeControct ?'icon-checkbox-1': 'icon-checkbox'"></i>我同意並繼續 |
| | | id="agreeContract" |
| | | @click="agreeContract = !agreeContract" |
| | | :disabled="!isReadContract" |
| | | value="agreeContract"> |
| | | <i :class="agreeContract ?'icon-checkbox-1': 'icon-checkbox'"></i>我同意並繼續 |
| | | </label> |
| | | </div> |
| | | </el-row> |
| | | </span> |
| | | <span slot="footer" class="dialog-footer"> |
| | | <el-button |
| | | type="primary" |
| | | :disabled="!name || !agreeControct || !isReadContract" |
| | | @click="applyAccount" |
| | | >建立新帳號 |
| | | </el-button> |
| | | </span> |
| | | </el-dialog> |
| | | <div class="text--center mt-10"> |
| | | <el-button |
| | | type="primary" |
| | | :disabled="!name || !agreeContract || !isReadContract" |
| | | @click="applyAccount" |
| | | >建立新帳號 |
| | | </el-button> |
| | | </div> |
| | | </PopUpFrame> |
| | | |
| | | <PopUpFrame class="pam-popUpFrame" |
| | | :isOpen.sync="emailOtpConfirmVisable" |
| | | > |
| | | <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="emailOtpConfirmVisable = false" |
| | | >我知道了</el-button> |
| | | </div> |
| | | </div> |
| | | </PopUpFrame> |
| | | |
| | | <PopUpFrame class="pam-popUpFrame" |
| | | :isOpen.sync="registerSuccessConfirmVisable" |
| | | @closePopUp="confirmApplySuccess" |
| | | > |
| | | <div class="pam-popUp-title text--center"> |
| | | 歡迎您登入成功,如您預約諮詢,顧問會以您留下的{{ connectDevice === 'MOBILE' ? '手機號碼' : 'Email'}}與您聯繫 |
| | | </div> |
| | | <div class="pam-popUp-txt text--center mb-10 mt-5" |
| | | >即將轉跳頁面...{{autoRedirectCounter}}秒</div> |
| | | <div class="pam-popUp-confirm-bolck mt-30"> |
| | | <div class="text--center"> |
| | | <el-button |
| | | type="primary" |
| | | @click="registerSuccessConfirmVisable = false" |
| | | >我知道了</el-button> |
| | | </div> |
| | | </div> |
| | | </PopUpFrame> |
| | | |
| | | <el-button class="mt-30" @click="fakeLogin">客戶登入</el-button> |
| | | <PopUpFrame class="pam-popUpFrame" |
| | | :isOpen.sync="phoneSuccessConfirmVisable" |
| | | @closePopUp="confirmApplySuccess" |
| | | > |
| | | <div class="pam-popUp-title text--center" |
| | | >歡迎您登入成功</div> |
| | | <div class="pam-popUp-txt text--center mb-30 mt-5 xsTxt" |
| | | >即將轉跳頁面...{{autoRedirectCounter}}秒</div> |
| | | <div class="pam-popUp-confirm-bolck mt-30"> |
| | | <div class="text--center"> |
| | | <el-button |
| | | type="primary" |
| | | @click="phoneSuccessConfirmVisable = false" |
| | | >我知道了</el-button> |
| | | </div> |
| | | </div> |
| | | </PopUpFrame> |
| | | |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { Vue, Component } from 'vue-property-decorator'; |
| | | import { login } from '~/assets/ts/api/consultant'; |
| | | import { Role } from '../../components/NavBar.vue'; |
| | | import { namespace } from 'nuxt-property-decorator'; |
| | | import { Vue, Component, Ref } from 'vue-property-decorator'; |
| | | import { OtpErrorCode } from '~/shared/models/enum/otpErrorCode'; |
| | | import { Role } from '~/shared/models/enum/Role'; |
| | | import { LoginRequest } from '~/shared/models/loginRequest.model'; |
| | | import { LoginVerify } from '~/shared/models/loginVerify.model'; |
| | | import { OtpInfo } from '~/shared/models/otpInfo.model'; |
| | | import { RegisterInfo } from '~/shared/models/registerInfo'; |
| | | import loginService from '~/shared/services/login.service'; |
| | | import messageBoxService from '~/shared/services/message-box.service'; |
| | | import otpService, { OtpStorageName } from '~/shared/services/otp.service'; |
| | | |
| | | const roleStorage = namespace('localStorage'); |
| | | |
| | | @Component |
| | | export default class Login extends Vue { |
| | | @roleStorage.Mutation |
| | | storageIdToken!: (token:string) => void; |
| | | |
| | | @roleStorage.Mutation |
| | | storageRole!: (role:string) => void; |
| | | |
| | | @roleStorage.Mutation |
| | | storageUserInfo!: (userInfo: RegisterInfo) => void; |
| | | |
| | | @Ref('contract') readonly contract!: any; |
| | | |
| | | connectDevice: 'MOBILE' | 'EMAIL' = 'MOBILE'; |
| | | |
| | | phoneNumber = ''; |
| | | otpCode = ''; |
| | | onPhoneVerifyStep: 'APPLY_OTP' | 'INPUT_OTP' | 'SUBMIT_OTP' = 'APPLY_OTP'; |
| | | otpCounterSec = 300; |
| | | otpResendCounter = 30; |
| | | otpInterval: any; |
| | | phoneOtpIndexKey!: string; |
| | | |
| | | email = ''; |
| | | onEmailVerifyResendStatus: 'CAN_RESEND' | 'CANNOT_RESEND' = 'CANNOT_RESEND'; |
| | | onEmailVerifyResendStatus: 'APPLY_OTP' | 'CAN_RESEND' = 'APPLY_OTP'; |
| | | emailCounterSec = 300; |
| | | emailResendCounter = 30; |
| | | emailOtpCode = ''; |
| | | emailResendInterval: any; |
| | | emailOtpIndexKey!: string; |
| | | |
| | | registerDialogVisable = false; |
| | | autoRedirectCounter = 3; |
| | | autoRedirectInterval: any; |
| | | |
| | | name = ''; |
| | | agreeControct = false; |
| | | agreeContract = false; |
| | | isReadContract = false; |
| | | |
| | | detectContructReadStatus(event: any): void { |
| | | this.isReadContract = event.target.scrollTop === (event.target.scrollHeight - event.target.clientHeight); |
| | | phoneSuccessConfirmVisable = false; |
| | | emailOtpConfirmVisable = false; |
| | | |
| | | registerDialogVisible = false; |
| | | registerSuccessConfirmVisable = false; |
| | | |
| | | applyAccount_onAction = false; |
| | | |
| | | previousPath = ''; |
| | | |
| | | ///////////////////////////////////////////////////// |
| | | |
| | | beforeRouteEnter (to, from, next) { |
| | | next(vm => { |
| | | vm.previousPath = from.path; |
| | | }) |
| | | } |
| | | |
| | | mounted() { |
| | | this.parsePhoneOtpTimeFromStorage(); |
| | | this.parseEmailOtpTimeFromStorage(); |
| | | } |
| | | |
| | | private parsePhoneOtpTimeFromStorage() { |
| | | const parsePhoneOtpTime = otpService.parseOtpTime(OtpStorageName.PHONE); |
| | | const diffSecs = otpService.diffOtpTime(OtpStorageName.PHONE, this.otpCounterSec); |
| | | |
| | | if (parsePhoneOtpTime && diffSecs) { |
| | | this.otpResendCounter = diffSecs < 30 ? 30 - diffSecs : 0; |
| | | this.otpCounterSec -= diffSecs; |
| | | this.phoneNumber = parsePhoneOtpTime.phone ? parsePhoneOtpTime.phone : ''; |
| | | this.onPhoneVerifyStep = 'INPUT_OTP'; |
| | | this.phoneOtpIndexKey = parsePhoneOtpTime.indexKey; |
| | | this.startOtpCount('MOBILE'); |
| | | } |
| | | } |
| | | |
| | | private parseEmailOtpTimeFromStorage() { |
| | | const parseEmailOtpTime = otpService.parseOtpTime(OtpStorageName.EMAIL); |
| | | const diffSecs = otpService.diffOtpTime(OtpStorageName.EMAIL, this.emailCounterSec); |
| | | |
| | | if (parseEmailOtpTime && diffSecs) { |
| | | this.emailResendCounter = diffSecs < 30 ? 30 - diffSecs : 0; |
| | | this.emailCounterSec -= diffSecs; |
| | | this.email = parseEmailOtpTime.email ? parseEmailOtpTime.email : ''; |
| | | this.onEmailVerifyResendStatus = 'CAN_RESEND'; |
| | | this.emailOtpIndexKey = parseEmailOtpTime.indexKey; |
| | | this.startOtpCount('EMAIL'); |
| | | } |
| | | } |
| | | |
| | | destroyed() { |
| | | this.removeOtpTime(); |
| | | clearInterval(this.otpInterval); |
| | | clearInterval(this.emailResendInterval); |
| | | clearInterval(this.autoRedirectInterval); |
| | | } |
| | | |
| | | ////////////////////////////////////////////////////////// |
| | | |
| | | //////////////////// 登入 |
| | | login() { |
| | | const login: LoginVerify = this.setLoginInfo(); |
| | | this.removeOtpTime(); |
| | | loginService.loginVerify(login).then(res => { |
| | | this.storageIdToken(res.id_token); |
| | | this.storageRole(Role.USER); |
| | | this.phoneSuccessConfirmVisable = true; |
| | | this.autoRedirect(); |
| | | this.storagePhoneOrEmail(this.setRegisterInfo()); |
| | | }).catch(error => { |
| | | this.checkHttpErrorStatus(error); |
| | | }); |
| | | } |
| | | |
| | | confirmApplySuccess(): void { |
| | | this.phoneSuccessConfirmVisable = false; |
| | | this.registerSuccessConfirmVisable = false; |
| | | this.redirect(); |
| | | } |
| | | |
| | | //////////////////// 註冊 |
| | | applyAccount(): void { |
| | | if (this.applyAccount_onAction) { |
| | | return ; |
| | | } |
| | | |
| | | this.applyAccount_onAction = true; |
| | | const registerInfo = this.setRegisterInfo(); |
| | | |
| | | loginService.register(registerInfo).then(res => { |
| | | this.storageIdToken(res.id_token); |
| | | this.storageRole(Role.USER); |
| | | this.storagePhoneOrEmail(registerInfo); |
| | | this.autoRedirect(); |
| | | this.registerSuccessConfirmVisable = true; |
| | | }).catch(() => { |
| | | this.applyAccount_onAction = false; |
| | | }); |
| | | }; |
| | | |
| | | private autoRedirect() { |
| | | this.autoRedirectInterval = setInterval(() => { |
| | | this.autoRedirectCounter -= 1; |
| | | |
| | | if (this.autoRedirectCounter === 0) { |
| | | clearInterval(this.autoRedirectInterval); |
| | | this.redirect(); |
| | | } |
| | | }, 1000) |
| | | } |
| | | |
| | | private redirect() { |
| | | const backToPrevious = ['questionnaire', 'myConsultantList']; |
| | | const find = backToPrevious.findIndex(item => this.previousPath.includes(item)); |
| | | find > -1 ? this.$router.go(-1) : this.$router.push('/'); |
| | | } |
| | | |
| | | detectContractReadStatus(event: any): void { |
| | | const scrollTop = Math.round(event.target.scrollTop); |
| | | const height = event.target.scrollHeight - event.target.clientHeight; |
| | | if (Math.floor(scrollTop/10) === (Math.floor(height/10))) { |
| | | this.isReadContract = true; |
| | | } |
| | | }; |
| | | |
| | | //////////////////// 發送/重發/刪除驗證碼 |
| | | |
| | | applyOtpVerification(type: string): void { |
| | | const isMobile = this.connectDevice === 'MOBILE'; |
| | | const loginInfo: LoginRequest = { |
| | | loginType: isMobile ? 'SMS' : 'EMAIL', |
| | | account: isMobile ? this.phoneNumber : this.email, |
| | | } |
| | | loginService.sendOtp(loginInfo).then(otpInfo => { |
| | | if (otpInfo.success) { |
| | | this.storageOtpTime(type, otpInfo); |
| | | this.startOtpSetting(type); |
| | | this.startOtpCount(type); |
| | | } else { |
| | | const errorMsg = OtpErrorCode[otpInfo.failCode] ? OtpErrorCode[otpInfo.failCode]:'OTP系統錯誤'; |
| | | messageBoxService.showErrorMessage(errorMsg); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | private storageOtpTime(type: string, otpInfo: OtpInfo) { |
| | | type === 'MOBILE' ? this.phoneOtpIndexKey = otpInfo.indexKey : this.emailOtpIndexKey = otpInfo.indexKey; |
| | | const info = {...this.setRegisterInfo(), time: new Date()} |
| | | const storageName = type === 'MOBILE' ? OtpStorageName.PHONE : OtpStorageName.EMAIL; |
| | | otpService.setOtpTimeToStorage(storageName, info); |
| | | } |
| | | |
| | | private startOtpSetting(type: string) { |
| | | if (type === 'MOBILE') { |
| | | this.onPhoneVerifyStep = 'INPUT_OTP'; |
| | | } else { |
| | | this.onEmailVerifyResendStatus = 'CAN_RESEND'; |
| | | this.emailOtpConfirmVisable = true; |
| | | } |
| | | } |
| | | |
| | | private startOtpCount(type: string) { |
| | | type === 'MOBILE' ? this.startPhoneCounter() : this.startEmailCounter();; |
| | | } |
| | | |
| | | private startEmailCounter() { |
| | | this.emailResendInterval = setInterval(() => { |
| | | this.emailCounterSec -= 1; |
| | | if (this.emailResendCounter !== 0) { |
| | | this.emailResendCounter -= 1; |
| | | } |
| | | if (this.emailCounterSec === 0) { |
| | | clearInterval(this.emailResendInterval); |
| | | } |
| | | }, 1000) |
| | | } |
| | | |
| | | private startPhoneCounter() { |
| | | this.otpInterval = setInterval(() => { |
| | | this.otpCounterSec -= 1; |
| | | if (this.otpResendCounter !== 0) { |
| | | this.otpResendCounter -= 1; |
| | | } |
| | | if (this.otpCounterSec === 0) { |
| | | clearInterval(this.otpInterval) |
| | | } |
| | | }, 1000) |
| | | } |
| | | |
| | | resentOtp(type: string) { |
| | | this.resetOtpSetting(type); |
| | | this.applyOtpVerification(type); |
| | | } |
| | | |
| | | deleteOtpInfo(type: string) { |
| | | this.resetOtpSetting(type); |
| | | if (type === 'MOBILE') { |
| | | this.onPhoneVerifyStep = 'APPLY_OTP'; |
| | | this.phoneNumber = ''; |
| | | this.otpCode = ''; |
| | | } else { |
| | | this.onEmailVerifyResendStatus = 'APPLY_OTP'; |
| | | this.email = ''; |
| | | this.emailOtpCode = ''; |
| | | } |
| | | this.removeOtpTime(); |
| | | } |
| | | |
| | | private resetOtpSetting(type: string) { |
| | | if (type === 'MOBILE') { |
| | | clearInterval(this.otpInterval); |
| | | this.otpResendCounter = 30; |
| | | this.otpCounterSec = 300; |
| | | } else { |
| | | clearInterval(this.emailResendInterval); |
| | | this.emailResendCounter = 30; |
| | | this.emailCounterSec = 300; |
| | | } |
| | | } |
| | | |
| | | 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}`; |
| | | } |
| | | |
| | | ////////////////////////////////////////////////////////////////// |
| | | |
| | | private checkHttpErrorStatus(error:any):void{ |
| | | switch (error.response.status) { |
| | | case 401: |
| | | const errorMsg = OtpErrorCode[error.response?.data?.detail] ? OtpErrorCode[error.response?.data?.detail]:'OTP系統錯誤'; |
| | | messageBoxService.showErrorMessage(errorMsg); |
| | | break; |
| | | case 403: |
| | | this.registerDialogVisible = true; |
| | | setTimeout(() => { |
| | | const isScrollBarNeedless = this.contract.scrollHeight <= this.contract.clientHeight; |
| | | if (isScrollBarNeedless) { |
| | | this.isReadContract = true; |
| | | } |
| | | }, 1000); |
| | | break; |
| | | default: |
| | | const defaultErrorMsg = OtpErrorCode[error.response?.data?.title] |
| | | messageBoxService.showErrorMessage('',defaultErrorMsg); |
| | | break; |
| | | } |
| | | } |
| | | |
| | | private storagePhoneOrEmail(registerInfo:RegisterInfo):void{ |
| | | const info = {...registerInfo, time: new Date()} |
| | | // storageUserInfo!: (userInfo: RegisterInfo) => void; |
| | | this.storageUserInfo(info); |
| | | // localStorage.setItem('userInfo',JSON.stringify(info)); |
| | | } |
| | | |
| | | private removeOtpTime() { |
| | | otpService.removeOtpTimeToStorage(OtpStorageName.PHONE); |
| | | otpService.removeOtpTimeToStorage(OtpStorageName.EMAIL); |
| | | } |
| | | |
| | | |
| | | private setLoginInfo() { |
| | | const isMobile = this.connectDevice === 'MOBILE' |
| | | return { |
| | | account: isMobile ? this.phoneNumber : this.email, |
| | | indexKey: isMobile ? this.phoneOtpIndexKey : this.emailOtpIndexKey, |
| | | otpCode: isMobile ? this.otpCode : this.emailOtpCode |
| | | } |
| | | } |
| | | |
| | | private setRegisterInfo(): RegisterInfo { |
| | | return this.connectDevice === 'MOBILE' |
| | | ? { |
| | | phone: this.phoneNumber, |
| | | indexKey: this.phoneOtpIndexKey, |
| | | otpCode: this.otpCode, |
| | | name: this.name, |
| | | contactType: 'SMS' |
| | | } |
| | | : { |
| | | email: this.email, |
| | | indexKey: this.emailOtpIndexKey, |
| | | otpCode: this.emailOtpCode, |
| | | name: this.name, |
| | | contactType: 'EMAIL' |
| | | } |
| | | } |
| | | |
| | | get isSubmitBtnDisabled(): boolean { |
| | | return this.connectDevice === 'MOBILE' |
| | | ? (!this.otpCode || !this.phoneNumber || !this.phoneValid || !this.otpCounterSec) |
| | | : (!this.emailOtpCode || !this.email || !this.emailValid || !this.emailCounterSec) |
| | | } |
| | | |
| | | get showPhoneOtpCodeField(): boolean { |
| | | return this.connectDevice === 'MOBILE' && this.onPhoneVerifyStep === 'INPUT_OTP'; |
| | | }; |
| | | |
| | | get showEmailVerifyField(): boolean { |
| | | return this.connectDevice === 'EMAIL'; |
| | | return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus === 'CAN_RESEND'; |
| | | }; |
| | | |
| | | applyOtpVerification(): void { |
| | | this.onPhoneVerifyStep = 'INPUT_OTP'; |
| | | }; |
| | | get phoneValid() { |
| | | const rule = /^09[0-9]{8}$/; |
| | | return this.phoneNumber ? rule.test(this.phoneNumber) : true; |
| | | } |
| | | |
| | | applyAccount(): void { |
| | | console.log('apply new account!') |
| | | }; |
| | | |
| | | // TODO: 僅OTP認證開發前 暫時使用 |
| | | fakeLogin() { |
| | | const user = { |
| | | username: 'user', |
| | | password: 'user', |
| | | } |
| | | login(user).then((res) => { |
| | | localStorage.setItem('id_token', res.data.id_token); |
| | | localStorage.setItem('roleOfState',Role.USER); |
| | | this.$router.go(-1); |
| | | }) |
| | | }; |
| | | |
| | | get emailValid() { |
| | | const rule = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; |
| | | return this.email ? rule.test(this.email) : true; |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .pam-login-page { |
| | | font-size: 20px !important; |
| | | display: flex; |
| | | flex-direction: column; |
| | | .pam-login-page__action-bar { |
| | | font-size: 20px !important; |
| | | display: flex; |
| | | flex: 1; |
| | | align-items: flex-end; |
| | | flex-direction: column; |
| | | .pam-login-page__action-bar { |
| | | display: flex; |
| | | flex: 1; |
| | | align-items: flex-end; |
| | | @include desktop { |
| | | margin-bottom: 30px; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pam-input { |
| | | height: 26px; |
| | | width: calc(100% - 36px); |
| | | border-radius: 10px !important; |
| | | padding: 12px 18px !important; |
| | | border-width: 1px; |
| | | outline: none; |
| | | @extend .text--middle; |
| | | &::placeholder { |
| | | color: $PRUDENTIAL_GREY; |
| | | .pam-input { |
| | | height: 26px; |
| | | width: calc(100% - 36px); |
| | | border-radius: 10px !important; |
| | | padding: 12px 18px !important; |
| | | border:1px solid #CCCCCC; |
| | | outline: 0; |
| | | @extend .text--middle; |
| | | &::placeholder { |
| | | color: $PRUDENTIAL_GREY; |
| | | } |
| | | &.is-invalid { |
| | | border: 1px solid $PRIMARY_RED !important; |
| | | border-radius: 20px; |
| | | } |
| | | } |
| | | &.is-invalid { |
| | | border: 1px solid $PRIMARY_RED !important; |
| | | border-radius: 20px; |
| | | } |
| | | } |
| | | |
| | | .pam-otp-resend-btn { |
| | | background: transparent; |
| | | border: none; |
| | | color: $PRIMARY_RED; |
| | | font-weight: bold; |
| | | i { |
| | | margin-right: 10px; |
| | | } |
| | | &.disabled { |
| | | color: $LIGHT_GREY; |
| | | } |
| | | } |
| | | |
| | | .pam-register-dialog__contract { |
| | | $DEVICE_EXTRA_HEIGHT: 42px; |
| | | $DEVICE_EXTRA_HEIGHT: 80px; |
| | | $ALIGN_PADDING: 60px; |
| | | $TOP_CONTENT_HEIGHT: 186px; |
| | | $BOTTOM_CONTENT_HEIGHT: 131px; |
| | | $TOP_CONTENT_HEIGHT: 211px; |
| | | $BOTTOM_CONTENT_HEIGHT: 141px; |
| | | // text-align:start; |
| | | max-height: calc(100vh - $DEVICE_EXTRA_HEIGHT - $ALIGN_PADDING - $TOP_CONTENT_HEIGHT - $BOTTOM_CONTENT_HEIGHT); |
| | | overflow-y: scroll; |
| | | border-radius: 6px; |
| | | border: 1px solid #707070; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .pam-radio { |
| | | color: $PRIMARY_RED; |
| | | align-items: center; |
| | | display: flex; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | input { |
| | | display: none; |
| | | } |
| | | i { |
| | | font-size: 27px; |
| | | padding-right: 5px; |
| | | @include desktop { |
| | | height: 335px; |
| | | } |
| | | } |
| | | |
| | | .pam-register-dialog { |
| | | padding: 30px 20px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | border-radius: 0; |
| | | &.el-dialog { |
| | | border-radius: 0; |
| | | } |
| | | .el-dialog__header { |
| | | padding: 0; |
| | | margin-bottom: 30px; |
| | | .el-dialog__title { |
| | | @extend .subTitle; |
| | | .pam-radio { |
| | | color: $PRIMARY_RED; |
| | | align-items: center; |
| | | display: flex; |
| | | font-size: 20px; |
| | | font-weight: bold; |
| | | input { |
| | | display: none; |
| | | } |
| | | i { |
| | | font-size: 27px; |
| | | padding-right: 5px; |
| | | } |
| | | } |
| | | .el-dialog__body { |
| | | flex: 1; |
| | | padding: 0; |
| | | margin-bottom: 30px; |
| | | } |
| | | .el-dialog__footer { |
| | | padding: 0 !important; |
| | | } |
| | | } |
| | | |
| | | .pam-field-title__hint { |
| | | @extend .smTxt_bold; |
| | | color: #68737A; |
| | | } |
| | | |
| | | .error { |
| | | @extend .smTxt_bold; |
| | | @extend .text--primary; |
| | | height: 16px; |
| | | } |
| | | |
| | | .pam-popUp-title { |
| | | font-size: 20px; |
| | | line-height: 27px; |
| | | } |
| | | |
| | | .pam-popUp-txt { |
| | | font-size: 18px; |
| | | color: $MID_GREY; |
| | | } |
| | | |
| | | .disabled { |
| | | color: #A7A8AA; |
| | | } |
| | | |
| | | .pam-input-position { |
| | | position: relative; |
| | | .icon-close { |
| | | cursor: pointer; |
| | | position: absolute; |
| | | right: 15px; |
| | | top: 28px; |
| | | font-size: 16px; |
| | | } |
| | | } |
| | | </style> |