保誠-保戶業務員媒合平台
Tomas
2022-08-28 9f9b7a95f358aafae44557336d7015ef98bd5634
PAMapp/pages/login/index.vue
@@ -9,10 +9,10 @@
            <el-row type="flex" class="pt-30">
              <el-button
                :class="{ 'active': connectDevice === 'MOBILE'}"
                @click="connectDevice = 'MOBILE'">手機號碼</el-button>
                @click="connectDevice = 'MOBILE'; regenerateImgOfVerification()">手機號碼</el-button>
              <el-button
                :class="{ 'active': connectDevice === 'EMAIL'}"
                @click="connectDevice = 'EMAIL'">Email</el-button>
                @click="connectDevice = 'EMAIL'; regenerateImgOfVerification()">Email</el-button>
            </el-row>
          </div>
@@ -58,13 +58,33 @@
            </div>
          </div>
          <div class="pam-paragraph" v-if="(!showPhoneOtpCodeField && !showEmailVerifyField)">
            <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"
                style="margin-left: 16px"
                @click="regenerateImgOfVerification">重新產生</div>
            </div>
            <div class="pam-consultant-login__verifyBlock mt-10">
              <div class="w-55">
                <input type="text"
                  v-model="verificationCode"
                  maxlength="4"
                  class="pam-consultant-login__input">
              </div>
              <div class="pam-consultant-login__verifyImg">
                <img :src="imgSrc" alt="驗證碼">
              </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="mdTxt">輸入 OTP 驗證碼</div>
                  <div class="otp-count-timer">
                    {{phoneCounter}}
                    {{counterTime(otpCounterSec)}}
                  </div>
              </el-row>
@@ -75,11 +95,11 @@
                    'is-invalid': !otpCode
                  }"
                  v-model="otpCode"
                  placeholder="請輸入驗證碼"
                  placeholder="請輸入 OTP 驗證碼"
                  >
              </el-row>
              <div class="error mt-5 mb-10">
                  <span v-show="otpCounterSec === 0">驗證碼已過期,請重發驗證碼</span>
                  <span v-show="otpCounterSec === 0">OTP 驗證碼已過期,請重發 OTP 驗證碼</span>
              </div>
              <el-row>
@@ -88,22 +108,23 @@
                  @click="resentOtp('MOBILE')"
                  icon="icon-arrow"
                >
                  重發驗證碼<span
                  重發 OTP 驗證碼<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"
                  :disabled="!phoneNumber || !phoneValid || verificationCode.length !== 4"
                  @click="applyOtpVerification('MOBILE')"
                  icon="icon-arrow"
                >
                  發送驗證碼
                  發送 OTP 驗證碼
                </el-button>
            </el-row>
@@ -113,9 +134,9 @@
          <template v-if="connectDevice === 'EMAIL'">
            <el-row v-show="showEmailVerifyField">
              <el-row type="flex" justify="space-between">
                  <div class="mdTxt">輸入驗證碼</div>
                  <div class="mdTxt">輸入 OTP 驗證碼</div>
                  <div class="otp-count-timer">
                    {{emailOtpCounter}}
                    {{counterTime(emailCounterSec)}}
                  </div>
              </el-row>
@@ -126,11 +147,11 @@
                    'is-invalid': !emailOtpCode
                  }"
                  v-model="emailOtpCode"
                  placeholder="請輸入驗證碼"
                  placeholder="請輸入 OTP 驗證碼"
                  >
              </el-row>
              <div class="error mt-5 mb-10">
                  <span v-show="emailCounterSec === 0">驗證碼已過期,請重發驗證碼</span>
                  <span v-show="emailCounterSec === 0">OTP 驗證碼已過期,請重發 OTP 驗證碼</span>
              </div>
              <el-button
@@ -138,20 +159,21 @@
                icon="icon-arrow"
                @click="resentOtp('EMAIL')"
              >
                重發驗證碼<span
                重發 OTP 驗證碼<span
                    v-if="emailResendCounter !== 0"
                    class="pam-field-title__hint pl-5"
                    class="pam-`field-title__hint pl-5"
                  >({{ emailResendCounter }})</span>
              </el-button>
            </el-row>
            <el-row v-show="!showEmailVerifyField">
            <el-row>
              <el-button
                  :disabled="!email || !emailValid"
                  v-if="onEmailVerifyResendStatus === 'APPLY_OTP'"
                  :disabled="!email || !emailValid || verificationCode.length !== 4"
                  @click="applyOtpVerification('EMAIL')"
                  icon="icon-arrow"
                >
                  發送驗證碼
                  發送 OTP 驗證碼
                </el-button>
            </el-row>
          </template>
@@ -171,7 +193,7 @@
        :isOpen.sync="registerDialogVisible"
        :dialogWidth="'90%'"
        class="pam-register-dialog"
        @closePopUp="isReadContract = false"
        @closePopUp="isReadContract = false;agreeContract = false"
      >
          <div class="subTitle text--center mb-20">歡迎新使用者</div>
          <el-row>
@@ -195,61 +217,41 @@
              class="mdTxt pam-register-dialog__contract"
              ref="contract"
              @scroll="detectContractReadStatus">
              <h3>蒐集個人資料告知事項</h3>
              <h3>保誠人壽保險股份有限公司蒐集個人資料告知事項</h3>
              <p class="mt-10">
              遵守個人資料保護法規定,在您提供個人資料予本處前,依法告知下列事項:
              保誠人壽保險股份有限公司(下稱「本公司」)依據個人資料保護法(以下稱個資法)第六條第二項、第八條第一項規定,向台端告知下列事項,請台端詳閱:
              <p>
              <p class="mt-10">
              一、獲取您下列個人資料類別:姓名、出生年月日、國民身分證統一編號、性別、職業、教育、
              連絡方式(包括但不限於電話號碼、E-MAIL、居住或工作地址)等,或其他得以直接
              或間接識別您個人之資料。
              一、個人資料蒐集之目的(參照法務部頒佈之「個資法之特定目的及個人資料之類別」):身分確認
              人身保險 (001)、行銷(040)、金融服務業依法令規定及金融監理需要,所為之蒐集處理及利用(059)、契約、類似契約或其他法律關係事務(069)、消費者、客戶管理與服務(090)、網路購物及其他電子商務服務(148)、調查、統計與研究分析(157)、其他經營合於營業登記項目或組織章程所定之業務(181)。
              <p>
              <p class="mt-10">
              二、本處將依個人資料保護法及相關法令之規定下,依本處隱私權保護政策,蒐集、
              處理及利用您的個人資料。
              二、蒐集之個人資料:真實姓名、手機號碼、電子郵件信箱及其他基於本平台媒合顧問及預約諮詢服務之需要所提供之個人資料。
              <p>
              <p class="mt-10">
              三、本處將於蒐集目的之存續期間合理利用您的個人資料。
              三、個人資料利用之期間、地區、對象、方式:
              (一)期間:因前述目的及執行業務所必須及依法令規定應為保存之期間。
              (二)地區:個人資料利用對象所在之地區。
              (三)對象:本公司、依法有調查權機關或金融監理機關及本平台合作保經代公司(範圍限於 台端選擇之顧問所隸屬之公司)。
              (四)方式:合於法令規定之利用方式。
              </p>
              <p class="mt-10">
              四、除蒐集之目的涉及國際業務或活動外,本處僅於中華民國領域內利用您的個人資料。
              四、依據個資法第三條規定, 台端就本公司保有 台端之個人資料得行使之權利及方式:
              (一)得向本公司行使之權利:
              向本公司查詢、請求閱覽或請求製給複製本。
              向本公司請求補充或更正。
              向本公司請求停止蒐集、處理或利用及請求刪除。
              (二)行使權利之方式: 台端得撥打本公司免費客戶服務專線: 0809-0809-68 行使上述權利。(專員接聽時間 : 週一 ~ 週五 08:00-20:00 及週六、日及例假日 09:00-17:30;語音服務:週一-週日24小時)。
              </p>
              <p class="mt-10">
              五、本處將於原蒐集之特定目的、本次以外之產業之推廣、宣導及輔導、以及其他公務機關請求行政協助之目的範圍內,合理利用您的個人資料。
              五、台端可自行決定是否提供個人資料予本公司, 台端於勾選同意後並於本平台提供個人資料時,視為已同意前述事項,惟若 台端不同意提供或未提供正確之資訊,將導致無法與使用本平台進行顧問媒合與預約諮詢等服務。
              </p>
              <p class="mt-10">
              六、您可依個人資料保護法第 3 條規定,就您的個人資料向本處行使之下列權利:
              (一) 查詢或請求閱覽。
              (二) 請求製給複製本。
              (三) 請求補充或更正。
              (四) 請求停止蒐集、處理及利用。
              (五) 請求刪除。
              您因行使上述權利而導致對您的權益產生減損時,本處不負相關賠償責任。另依
              個人資料保護法第 14 條規定,本處得酌收行政作業費用。
              </p>
              <p class="mt-10">
              七、若您未提供正確之個人資料,本處將無法為您提供特定目的之相關業務。
              </p>
              <p class="mt-10">
              八、本處因業務需要而委託其他機關處理您的個人資料時,本處將會善盡監督之責。
              </p>
              <p class="mt-10">
              九、您瞭解此一同意書符合個人資料保護法及相關法規之要求,且同意本處留存此同
              意書,供日後取出查驗。
              個人資料之同意提供
              一、本人已充分知悉貴處上述告知事項。
              二、本人同意貴處蒐集、處理、利用本人之個人資料,以及其他公務機關請求行政協
              助目的之提供。
              </p>
            </div>
          </el-row>
          <el-row class="pt-30">
@@ -334,22 +336,30 @@
<script lang="ts">
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component, Ref } from 'vue-property-decorator';
import { Vue, Component, Ref, Watch } from 'vue-property-decorator';
import { OtpErrorCode } from '~/shared/models/enum/otpErrorCode';
import { Role } from '~/shared/models/enum/role';
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
  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';
@@ -360,7 +370,7 @@
  otpCounterSec = 300;
  otpResendCounter = 30;
  otpInterval: any;
  phoneOtpInfo!: OtpInfo;
  phoneOtpIndexKey!: string;
  email = '';
  onEmailVerifyResendStatus: 'APPLY_OTP' | 'CAN_RESEND' = 'APPLY_OTP';
@@ -368,7 +378,10 @@
  emailResendCounter = 30;
  emailOtpCode = '';
  emailResendInterval: any;
  emailOtpInfo!: OtpInfo;
  emailOtpIndexKey!: string;
  verificationCode = '';
  imgSrc = '';
  autoRedirectCounter = 3;
  autoRedirectInterval: any;
@@ -387,25 +400,60 @@
  previousPath = '';
  /////////////////////////////////////////////////////
  mounted() {
    const phoneOtpTime = localStorage.getItem('phoneOtpTime');
    const emailOtpTime = localStorage.getItem('emailOtpTime');
    const parsePhoneOtpTime = phoneOtpTime ? JSON.parse(phoneOtpTime) : '';
    const parseEmailOtpTime = emailOtpTime ? JSON.parse(emailOtpTime) : '';
    if (parsePhoneOtpTime && parsePhoneOtpTime.contactType === 'SMS') {
      this.phoneDiffTime(parsePhoneOtpTime);
    }
    if (parseEmailOtpTime && parseEmailOtpTime.contactType === 'EMAIL') {
      this.emailDiffTime(parseEmailOtpTime);
  @Watch('onEmailVerifyResendStatus')
  onEmailVerifyResendStatusChange() {
    if (this.onEmailVerifyResendStatus === 'APPLY_OTP') {
      this.regenerateImgOfVerification();
    }
  }
  @Watch('onPhoneVerifyStep')
  onPhoneVerifyStepChange() {
    if (this.onPhoneVerifyStep === 'APPLY_OTP') {
        this.regenerateImgOfVerification();
    }
  }
  /////////////////////////////////////////////////////
  beforeRouteEnter (to, from, next) {
      next(vm => {
        console.log(from.path, 'beforeRouteEnter');
        vm.previousPath = from.path;
      })
  }
  mounted() {
    this.parsePhoneOtpTimeFromStorage();
    this.parseEmailOtpTimeFromStorage();
    this.regenerateImgOfVerification();
  }
  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() {
@@ -417,87 +465,36 @@
  //////////////////////////////////////////////////////////
  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;
    }
  };
  get isSubmitBtnDisabled(): boolean {
    return this.connectDevice === 'MOBILE'
      ? (!this.otpCode || !this.phoneNumber || !this.phoneValid || !this.otpCounterSec)
      : (!this.emailOtpCode || !this.email || !this.emailValid || !this.emailCounterSec)
  }
  get phoneCounter() {
    let min = Math.floor(this.otpCounterSec / 60);
    let sec = Math.floor(this.otpCounterSec % 60);
    return `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`;
  }
  get emailOtpCounter() {
    let min = Math.floor(this.emailCounterSec / 60);
    let sec = Math.floor(this.emailCounterSec % 60);
    return `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`;
  }
  get showPhoneOtpCodeField(): boolean {
    return this.connectDevice === 'MOBILE' && this.onPhoneVerifyStep === 'INPUT_OTP';
  };
  get showEmailVerifyField(): boolean {
    return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus === 'CAN_RESEND';
  };
  get phoneValid() {
    const rule = /^09[0-9]{8}$/;
    return this.phoneNumber ? rule.test(this.phoneNumber) : true;
  }
  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;
  }
  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);
  regenerateImgOfVerification(): void {
    loginService.getImgOfVerification().then( imgOfBase64 => {
        this.imgSrc = imgOfBase64;
        this.verificationCode = '';
      }
    });
    );
  };
  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 = '';
    }
  //////////////////// 登入
  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 ;
@@ -517,49 +514,6 @@
    });
  };
  confirmApplySuccess(): void {
    this.phoneSuccessConfirmVisable = false;
    this.registerSuccessConfirmVisable = false;
    this.redirect();
  }
  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);
    });
  }
  //////////////////////////////////////////////////////////////////
  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:
          messageBoxService.showErrorMessage('',error);
          break;
      }
  }
  private autoRedirect() {
    this.autoRedirectInterval = setInterval(() => {
      this.autoRedirectCounter -= 1;
@@ -574,72 +528,42 @@
  private redirect() {
    const backToPrevious = ['questionnaire', 'myConsultantList'];
    const find = backToPrevious.findIndex(item => this.previousPath.includes(item));
    console.log(this.previousPath, find, 'redirect');
    find > -1 ? this.$router.go(-1) : this.$router.push('/');
  }
  private phoneDiffTime(parseOtpTime: any) {
    const diffSecs = this.calcDiffSecs(parseOtpTime.time);
    if (diffSecs < this.otpCounterSec) {
      this.otpResendCounter = diffSecs < 30 ? 30 - diffSecs : 0;
        this.otpCounterSec -= diffSecs;
        this.phoneNumber = parseOtpTime.phone;
        this.onPhoneVerifyStep = 'INPUT_OTP';
        this.phoneOtpInfo = this.setOtpInfo(parseOtpTime);
        this.startOtpCount('MOBILE');
    } else {
      localStorage.removeItem('phoneOtpTime');
  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;
    }
  }
  };
  private emailDiffTime(parseOtpTime: any) {
    const diffSecs = this.calcDiffSecs(parseOtpTime.time);
  //////////////////// 發送/重發/刪除驗證碼
    if (diffSecs < this.emailCounterSec) {
      this.emailResendCounter =  diffSecs < 30 ? 30 - diffSecs : 0;
      this.emailCounterSec -= diffSecs;
      this.email = parseOtpTime.email;
      this.onEmailVerifyResendStatus = 'CAN_RESEND';
      this.emailOtpInfo = this.setOtpInfo(parseOtpTime);
      this.startOtpCount('EMAIL');
    } else {
      localStorage.removeItem('emailOtpTime');
  applyOtpVerification(type: string): void {
    const isMobile = this.connectDevice === 'MOBILE';
    const loginInfo: LoginRequest = {
      loginType: isMobile ? 'SMS' : 'EMAIL',
      account: isMobile ? this.phoneNumber : this.email,
    }
  }
  private calcDiffSecs(parseOtpTime) {
    const currentTime = new Date().getTime();
    const storageTime = new Date(parseOtpTime).getTime();
    return Math.floor((currentTime - storageTime) / 1000);
  }
  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;
    }
  }
  private setOtpInfo(parseOtpTime) {
    return {
      indexKey: parseOtpTime.indexKey,
      success: true,
      failCode: '',
      failReason: '',
    }
  }
    loginService.sendOtp(loginInfo, this.verificationCode).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.phoneOtpInfo = otpInfo : this.emailOtpInfo = otpInfo;
    type === 'MOBILE' ? this.phoneOtpIndexKey = otpInfo.indexKey : this.emailOtpIndexKey = otpInfo.indexKey;
    const info = {...this.setRegisterInfo(), time: new Date()}
    type === 'MOBILE' ? localStorage.setItem('phoneOtpTime',JSON.stringify(info))
                      : localStorage.setItem('emailOtpTime',JSON.stringify(info));
    const storageName = type === 'MOBILE' ? OtpStorageName.PHONE : OtpStorageName.EMAIL;
    otpService.setOtpTimeToStorage(storageName, info);
  }
  private startOtpSetting(type: string) {
@@ -660,6 +584,9 @@
      this.emailCounterSec -= 1;
      if (this.emailResendCounter !== 0) {
        this.emailResendCounter -= 1;
        if (this.emailResendCounter === 0) {
          // this.regenerateImgOfVerification();
        }
      }
      if (this.emailCounterSec === 0) {
        clearInterval(this.emailResendInterval);
@@ -672,48 +599,140 @@
      this.otpCounterSec -= 1;
      if (this.otpResendCounter !== 0) {
        this.otpResendCounter -= 1;
        if (this.otpResendCounter === 0) {
          // this.regenerateImgOfVerification();
        }
      }
      if (this.otpCounterSec === 0) {
        clearInterval(this.otpInterval)
        clearInterval(this.otpInterval);
      }
    }, 1000)
  }
  resentOtp(type: string) {
    this.resetOtpSetting(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;
      this.onPhoneVerifyStep = 'APPLY_OTP';
    } else {
      clearInterval(this.emailResendInterval);
      this.emailResendCounter = 30;
      this.emailCounterSec = 300;
      this.onEmailVerifyResendStatus = 'APPLY_OTP';
    }
  }
  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.phoneOtpInfo.indexKey,
          indexKey: this.phoneOtpIndexKey,
          otpCode: this.otpCode,
          name: this.name,
          contactType: 'SMS'
        }
      : {
          email: this.email,
          indexKey: this.emailOtpInfo.indexKey,
          otpCode: this.otpCode,
          indexKey: this.emailOtpIndexKey,
          otpCode: this.emailOtpCode,
          name: this.name,
          contactType: 'EMAIL'
        }
  }
  private storagePhoneOrEmail(registerInfo:RegisterInfo):void{
    const info = {...registerInfo, time: new Date()}
    localStorage.setItem('userInfo',JSON.stringify(info));
  get isSubmitBtnDisabled(): boolean {
    return this.connectDevice === 'MOBILE'
      ? (!this.otpCode || !this.phoneNumber || !this.phoneValid || !this.otpCounterSec)
      : (!this.emailOtpCode || !this.email || !this.emailValid || !this.emailCounterSec)
  }
  private removeOtpTime() {
    localStorage.removeItem('emailOtpTime');
    localStorage.removeItem('phoneOtpTime');
  get showPhoneOtpCodeField(): boolean {
    return this.connectDevice === 'MOBILE' && this.onPhoneVerifyStep === 'INPUT_OTP';
  };
  get showEmailVerifyField(): boolean {
    return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus === 'CAN_RESEND';
  };
  get phoneValid() {
    const rule = /^09[0-9]{8}$/;
    return this.phoneNumber ? rule.test(this.phoneNumber) : true;
  }
  private setLoginInfo() {
    const isMobile = this.connectDevice === 'MOBILE'
    return {
      account: isMobile ? this.phoneNumber : this.email,
      indexKey: isMobile ? this.phoneOtpInfo.indexKey : this.emailOtpInfo.indexKey,
      otpCode: isMobile ? this.otpCode : this.emailOtpCode
    }
  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>
@@ -816,4 +835,82 @@
      font-size: 16px;
    }
  }
    .pam-consultant-login {
    margin: auto;
    width: 336px;
    font-size: 20px;
    color: $PRIMARY_BLACK;
    &__header {
      text-align: center;
      font-size: 24px;
      font-weight: bold;
      letter-spacing: 1.2;
      color: $PRIMARY_BLACK;
    }
    &__title {
      display: flex;
      // justify-content: space-between;
      align-items: center;
    }
    &__input {
      width: 100%;
      outline: 0;
      border: 1px solid #CCCCCC;
      border-radius: 10px;
      font-size: 20px;
      height: 50px;
      padding: 10px 90px 10px 15px;
      overflow: auto;
      box-sizing: border-box;
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      &Icon {
        position: absolute;
        display: flex;
        align-items: center;
        top: 15px;
        right: 15px;
      }
    }
    &__forgot-password {
      color: $PRIMARY_RED;
      text-decoration: none;
      font-size: 16px;
    }
    &__verifyBlock {
      display: flex;
      // justify-content: space-between;
    }
    &__verifyImg {
      margin-left: 8px;
      width: 126px;
      height: 50px;
      border:1px #cccccc solid;
      img {
        width: 100%;
        height: 100%;
      }
    }
    &__confirmBlock {
      display: flex;
      justify-content: center;
    }
    &__confirm {
      color: $PRIMARY_WHITE;
      width: 80px;
      height: 50px;
      border-radius: 30px;
      border: 1px solid $LIGHT_GREY;
      background-color: $PRIMARY_RED;
    }
  }
</style>