<template>
|
<div class="pam-login-page">
|
<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-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>
|
|
<div class="pam-inputs mb-10">
|
<div class="pt-10" v-show="connectDevice === 'MOBILE'">
|
<input
|
class="pam-input"
|
:class="{
|
'is-invalid': !phoneValid
|
}"
|
v-model="phoneNumber"
|
placeholder="請輸入手機號碼">
|
<div class="error mt-5 mb-5">
|
<span v-show="!phoneValid">手機號碼格式有誤</span>
|
</div>
|
</div>
|
|
<div class="pt-10" v-show="connectDevice === 'EMAIL'">
|
<input
|
class="pam-input"
|
:class="{
|
'is-invalid': !emailValid
|
}"
|
v-model="email"
|
placeholder="請輸入 Email 地址"
|
>
|
<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">
|
{{otpCounter}}
|
</div>
|
</el-row>
|
|
<el-row class="mb-30">
|
<input
|
class="pam-input mt-10"
|
:class="{
|
'is-invalid': !otpCode
|
}"
|
v-model="otpCode"
|
placeholder="請輸入驗證碼"
|
>
|
</el-row>
|
|
<el-row>
|
<el-button
|
:disabled="!phoneNumber || otpResendCounter !== 0 || !phoneValid"
|
@click="resentOtp('MOBILE')"
|
icon="icon-arrow"
|
>
|
重發驗證碼<span class="pam-field-title__hint pl-5">({{ 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>
|
|
</template>
|
|
<!-- email 驗證碼 -->
|
<template v-if="connectDevice === 'EMAIL'">
|
<el-row v-show="showEmailVerifyField">
|
<el-button
|
:disabled="!email || emailResendCounter !== 0 || !emailValid"
|
icon="icon-arrow"
|
@click="resentOtp('EMAIL')"
|
>
|
重發驗證碼<span class="pam-field-title__hint pl-5">({{ emailResendCounter }})</span>
|
</el-button>
|
<div class="mt-10 smTxt_bold text--primary">! 請稍等,新的Email待啟用</div>
|
</el-row>
|
|
<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 mt-30">
|
<el-button
|
type="primary"
|
v-if="connectDevice === 'MOBILE'
|
&& onPhoneVerifyStep === 'INPUT_OTP'"
|
:disabled="isSubmitBtnDisabled"
|
@click="phoneLogin">
|
送出
|
</el-button>
|
</el-row>
|
|
<el-dialog
|
title="歡迎新使用者"
|
:custom-class="'pam-register-dialog'"
|
:visible.sync="registerDialogVisible"
|
:fullscreen="true"
|
:close-on-click-modal="false"
|
:show-close="false"
|
center>
|
<span>
|
<el-row>
|
<input
|
class="pam-input"
|
:class="{
|
'is-invalid': !name
|
}"
|
v-model="name"
|
placeholder="請輸入姓名"
|
>
|
</el-row>
|
<el-row class="pt-30">
|
<div class="mdTxt">
|
請審閱條款,並核勾確認已閱讀且同意相關條款內容
|
</div>
|
</el-row>
|
<el-row class="pt-10">
|
<div
|
class="mdTxt pam-register-dialog__contract"
|
ref="contract"
|
@scroll="detectContractReadStatus">
|
<h3>蒐集個人資料告知事項</h3>
|
<p class="mt-10">
|
遵守個人資料保護法規定,在您提供個人資料予本處前,依法告
|
知下列事項:
|
<p>
|
|
<p class="mt-10">
|
一、獲取您下列個人資料類別:姓名、出生年月日、國民身分證統一編號、性別、職業、教育、
|
連絡方式(包括但不限於電話號碼、E-MAIL、居住或工作地址)等,或其他得以直接
|
或間接識別您個人之資料。
|
<p>
|
|
<p class="mt-10">
|
二、本處將依個人資料保護法及相關法令之規定下,依本處隱私權保護政策,蒐集、
|
處理及利用您的個人資料。
|
<p>
|
|
<p class="mt-10">
|
三、本處將於蒐集目的之存續期間合理利用您的個人資料。
|
</p>
|
|
<p class="mt-10">
|
四、除蒐集之目的涉及國際業務或活動外,本處僅於中華民國領域內利用您的個人資
|
料。
|
</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">
|
<div class="pam-agree-radio">
|
<label for="agreeContract" class="pam-radio"
|
:class="{disabled: !isReadContract}">
|
<input
|
type="radio"
|
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 || !agreeContract || !isReadContract"
|
@click="applyAccount"
|
>建立新帳號
|
</el-button>
|
</span>
|
</el-dialog>
|
|
<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 pam-paragraph">
|
<div class="text--center">
|
<el-button
|
type="primary"
|
@click="emailOtpConfirmVisable = false"
|
>我知道了</el-button>
|
</div>
|
</div>
|
</PopUpFrame>
|
|
<PopUpFrame class="pam-popUpFrame"
|
:isOpen.sync="registerSuccessConfirmVisable">
|
<div class="pam-popUp-title text--center">
|
歡迎您登入成功,如您預約諮詢,顧問會以您留下的{{ connectDevice === 'MOBILE' ? '手機號碼' : 'Email'}}與您聯繫
|
</div>
|
<div class="pam-popUp-confirm-bolck pam-paragraph">
|
<div class="text--center">
|
<el-button
|
type="primary"
|
@click="confirmApplySuccess"
|
>我知道了</el-button>
|
</div>
|
</div>
|
</PopUpFrame>
|
|
<PopUpFrame class="pam-popUpFrame"
|
:isOpen.sync="phoneSuccessConfirmVisable">
|
<div class="pam-popUp-title text--center mb-50"
|
>歡迎您登入成功</div>
|
<div class="pam-popUp-confirm-bolck pam-paragraph">
|
<div class="text--center">
|
<el-button
|
type="primary"
|
@click="confirmApplySuccess"
|
>我知道了</el-button>
|
</div>
|
</div>
|
</PopUpFrame>
|
|
</div>
|
</template>
|
|
<script lang="ts">
|
import { namespace } from 'nuxt-property-decorator';
|
import { Vue, Component, Ref } from 'vue-property-decorator';
|
import { LoginRequest, loginVerify, OtpInfo, register, RegisterInfo, sendOtp } from '~/assets/ts/api/consultant';
|
import { Role } from '~/assets/ts/models/enum/Role';
|
|
const roleStorage = namespace('localStorage');
|
|
@Component
|
export default class Login extends Vue {
|
@roleStorage.Mutation storageIdToken!: (token:string) => void;
|
@roleStorage.Mutation storageRole!: (role:string) => void;
|
@Ref('contract') readonly contract!: any;
|
|
connectDevice: 'MOBILE' | 'EMAIL' = 'MOBILE';
|
|
phoneNumber = '';
|
otpCode = '';
|
onPhoneVerifyStep: 'APPLY_OTP' | 'INPUT_OTP' | 'SUBMIT_OTP' = 'APPLY_OTP';
|
otpCounter = '15:00';
|
otpResendCounter = 30;
|
otpInterval: any;
|
phoneOtpInfo!: OtpInfo;
|
|
get isSubmitBtnDisabled(): boolean {
|
return !this.otpCode || !this.phoneNumber || !this.phoneValid;
|
}
|
|
email = '';
|
onEmailVerifyResendStatus: 'APPLY_OTP' | 'CAN_RESEND' = 'APPLY_OTP';
|
emailResendCounter = 30;
|
emailResendInterval: any;
|
emailOtpInfo!: OtpInfo;
|
|
name = '';
|
agreeContract = false;
|
isReadContract = false;
|
|
phoneSuccessConfirmVisable = false;
|
emailOtpConfirmVisable = false;
|
|
registerDialogVisible = false;
|
registerSuccessConfirmVisable = false;
|
|
applyAccount_onAction = false;
|
|
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 showPhoneOtpCodeField(): boolean {
|
return this.connectDevice === 'MOBILE' && this.onPhoneVerifyStep === 'INPUT_OTP';
|
};
|
|
get showEmailVerifyField(): boolean {
|
return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus !== 'APPLY_OTP';
|
};
|
|
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,
|
}
|
sendOtp(loginInfo).then(otpInfo => {
|
if (otpInfo.success) {
|
this.startOtpCount(type, otpInfo);
|
}
|
});
|
};
|
|
resentOtp(type: string) {
|
if (type === 'MOBILE') {
|
clearInterval(this.otpInterval);
|
this.otpResendCounter = 30;
|
this.otpCounter = '15:00';
|
this.startPhoneCounter();
|
} else {
|
this.emailResendCounter = 30;
|
this.startEmailCounter();
|
this.emailOtpConfirmVisable = true;
|
}
|
}
|
|
private startOtpCount(type: string, otpInfo) {
|
type === 'MOBILE' ? this.phoneOtpInfo = otpInfo : this.emailOtpInfo = otpInfo;
|
if (type === 'MOBILE') {
|
this.onPhoneVerifyStep = 'INPUT_OTP';
|
this.startPhoneCounter();
|
} else {
|
this.onEmailVerifyResendStatus = 'CAN_RESEND';
|
this.startEmailCounter();
|
this.emailOtpConfirmVisable = true;
|
}
|
}
|
|
private startEmailCounter() {
|
this.emailResendInterval = setInterval(() => {
|
this.emailResendCounter -= 1;
|
if (this.emailResendCounter === 0) {
|
clearInterval(this.emailResendInterval)
|
}
|
}, 1000)
|
}
|
|
private startPhoneCounter() {
|
const minCount = this.otpCounter.split(':');
|
let secCount = (+minCount[0] * 60) + (+minCount[1]);
|
let min = 0;
|
let sec = 0;
|
this.otpInterval = setInterval(() => {
|
secCount -= 1;
|
min = Math.floor(secCount/60);
|
sec = Math.floor(secCount%60);
|
this.otpCounter = `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`;
|
|
if (this.otpResendCounter !== 0) {
|
this.otpResendCounter -= 1;
|
}
|
|
if (secCount === 0) {
|
clearInterval(this.otpInterval)
|
}
|
}, 1000)
|
}
|
|
private setRegisterInfo(): RegisterInfo {
|
return this.connectDevice === 'MOBILE'
|
? {
|
phone: this.phoneNumber,
|
indexKey: this.phoneOtpInfo.indexKey,
|
otpCode: this.otpCode,
|
name: this.name,
|
contactType: 'SMS'
|
}
|
: {
|
email: this.email,
|
indexKey: this.emailOtpInfo.indexKey,
|
otpCode: this.otpCode,
|
name: this.name,
|
contactType: 'EMAIL'
|
}
|
}
|
|
applyAccount(): void {
|
if (this.applyAccount_onAction) {
|
return ;
|
}
|
|
this.applyAccount_onAction = true;
|
const registerInfo = this.setRegisterInfo();
|
this.storagePhoneOrEmail(registerInfo);
|
register(registerInfo).then(res => {
|
this.storageIdToken(res.data.id_token);
|
this.storageRole(Role.USER);
|
this.registerSuccessConfirmVisable = true;
|
}).catch(() => {
|
this.applyAccount_onAction = false;
|
});
|
};
|
|
storagePhoneOrEmail(registerInfo:RegisterInfo):void{
|
localStorage.setItem('userInfo',JSON.stringify(registerInfo));
|
}
|
|
confirmApplySuccess(): void {
|
this.phoneSuccessConfirmVisable = false;
|
this.registerSuccessConfirmVisable = false;
|
this.$router.go(-1);
|
}
|
|
phoneLogin() {
|
const login = {
|
account: this.phoneNumber,
|
indexKey: this.phoneOtpInfo.indexKey,
|
otpCode: this.otpCode
|
}
|
|
loginVerify(login).then(res => {
|
this.storageIdToken(res.data.id_token);
|
this.storageRole(Role.USER);
|
this.phoneSuccessConfirmVisable = true;
|
}).catch(error => {
|
if (error.response.status === 401) {
|
this.registerDialogVisible = true;
|
setTimeout(() => {
|
const isScrollBarNeedless = this.contract.scrollHeight <= this.contract.clientHeight;
|
if (isScrollBarNeedless) {
|
this.isReadContract = true;
|
}
|
}, 1000);
|
}
|
});
|
}
|
|
destroyed() {
|
clearInterval(this.otpInterval);
|
clearInterval(this.emailResendInterval);
|
}
|
|
}
|
</script>
|
|
<style lang="scss">
|
.pam-login-page {
|
font-size: 20px !important;
|
display: flex;
|
flex-direction: column;
|
.pam-login-page__action-bar {
|
display: flex;
|
flex: 1;
|
align-items: flex-end;
|
}
|
}
|
|
.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;
|
}
|
}
|
|
.pam-register-dialog__contract {
|
$DEVICE_EXTRA_HEIGHT: 42px;
|
$ALIGN_PADDING: 60px;
|
$TOP_CONTENT_HEIGHT: 186px;
|
$BOTTOM_CONTENT_HEIGHT: 131px;
|
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;
|
max-height: 335px;
|
}
|
|
.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;
|
}
|
}
|
|
.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;
|
}
|
}
|
.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 {
|
line-height: 24px;
|
}
|
.disabled {
|
color: #A7A8AA;
|
}
|
</style>
|