Update Account

This commit is contained in:
ThienPhamVan
2026-03-30 14:09:41 +07:00
parent e7e8f0b38a
commit e1b9d9c15d
11 changed files with 1029 additions and 280 deletions

View File

@@ -0,0 +1,73 @@
:root {
/* ===== PRIMARY (GREEN) ===== */
--primary-50: #f0fdf4;
--primary-100: #dcfce7;
--primary-200: #bbf7d0;
--primary-300: #86efac;
--primary-400: #4ade80;
--primary-500: #22c55e;
--primary-600: #16a34a; /* MAIN */
--primary-700: #15803d;
--primary-800: #166534;
--primary-900: #14532d;
/* ===== SEMANTIC ===== */
--color-primary: var(--primary-600);
--color-primary-hover: var(--primary-700);
--color-primary-active: var(--primary-800);
/* ===== TEXT (NEUTRAL - QUAN TRỌNG) ===== */
--text-primary: #1f2937; /* gray-800 */
--text-secondary: #6b7280; /* gray-500 */
--text-light: #ffffff;
/* ===== BACKGROUND ===== */
--bg-primary: #ffffff;
--bg-secondary: #f9fafb; /* nhẹ hơn để không bị xanh quá */
--bg-accent: var(--primary-50);
/* ===== BORDER ===== */
--border-light: #e5e7eb;
--border-default: #d1d5db;
/* ===== STATE ===== */
--color-error: #ef4444;
--color-success: #22c55e;
/* ===== SHADOW ===== */
--shadow-card: 0 4px 20px rgba(0, 0, 0, 0.08);
/* ===== BUTTON ===== */
--btn-primary-bg: var(--primary-600);
--btn-primary-hover: var(--primary-700);
--btn-primary-active: var(--primary-800);
--btn-primary-text: #ffffff;
--btn-secondary-bg: var(--primary-100);
--btn-secondary-hover: var(--primary-200);
--btn-secondary-text: var(--primary-700);
--btn-disabled-bg: #e5e7eb;
--btn-disabled-text: #9ca3af;
/* ===== TEXT ===== */
--text-primary: #1f2937;
--text-secondary: #6b7280;
--text-muted: #9ca3af;
--text-on-primary: #ffffff; /* chữ trên nền xanh */
--text-link: var(--primary-600);
--text-link-hover: var(--primary-700);
/* ===== BORDER ===== */
--border-default: #d1d5db;
--border-focus: var(--primary-600);
/* ===== STATE ===== */
--state-success: var(--primary-500);
--state-error: #ef4444;
--state-warning: #f59e0b;
/* ===== LAYOUT ===== */
--layout-full-height: 100dvh;
}

View File

@@ -1,6 +1,7 @@
// Import Bulma's core // Import Bulma's core
@import "~bulma/sass/utilities/_all"; @import "~bulma/sass/utilities/_all";
@import "./variables";
// Set your colors // Set your colors
$primary: #107FFB; // #4285F4; // #0F9D58; // #009047; $primary: #107FFB; // #4285F4; // #0F9D58; // #009047;
$primary-invert: findColorInvert($primary); $primary-invert: findColorInvert($primary);

115
components/OtpInput.vue Normal file
View File

@@ -0,0 +1,115 @@
<template>
<div class="otp-wrapper">
<input
v-for="(item, index) in length"
:key="index"
ref="inputs"
type="text"
inputmode="numeric"
maxlength="1"
class="otp-input"
v-model="codes[index]"
@input="handleInput(index)"
@keydown.backspace="handleBackspace(index)"
@paste="handlePaste"
/>
</div>
</template>
<script>
export default {
props: {
length: {
type: Number,
default: 9,
},
value: {
type: String,
default: '',
},
},
data() {
return {
codes: Array(this.length).fill(''),
};
},
methods: {
handleInput(index) {
const value = this.codes[index];
// Chỉ cho nhập số
if (!/^[0-9a-zA-Z]?$/.test(value)) {
this.codes[index] = '';
return;
}
// Tự nhảy sang ô tiếp theo
if (value && index < this.length - 1) {
this.$refs.inputs[index + 1].focus();
}
this.emitValue();
},
handleBackspace(index) {
if (!this.codes[index] && index > 0) {
this.$refs.inputs[index - 1].focus();
}
},
handlePaste(event) {
event.preventDefault();
const paste = event.clipboardData.getData('text').trim();
if (!paste) return;
const values = paste
.replace(/[^0-9a-zA-Z]/g, '')
.slice(0, this.length)
.split('');
this.codes = Array(this.length).fill('');
values.forEach((char, i) => {
this.codes[i] = char;
});
this.$nextTick(() => {
const index = Math.min(values.length, this.length - 1);
this.$refs.inputs[index]?.focus();
});
this.emitValue();
},
emitValue() {
const code = this.codes.join('');
this.$emit('input', code);
},
},
};
</script>
<style lang="scss">
.otp-wrapper {
display: flex;
gap: 10px;
justify-content: center;
.otp-input {
width: 42px;
height: 48px;
text-align: center;
font-size: 18px;
border: 1px solid #e5e7eb;
border-radius: 8px;
outline: none;
transition: all 0.2s;
&:focus {
border-color: #16a34a;
box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.2);
}
}
}
</style>

View File

@@ -0,0 +1,182 @@
import { COMPANY } from '~/constants/company';
export default function sendVerificationEmail(
customer,
emailCustomer,
code,
urlVerify,
companyName = COMPANY.name,
office = COMPANY.address,
website = COMPANY.website,
emailCompany = COMPANY.email.support,
hotline = COMPANY.phone.hotline,
) {
return `
<div style="font-family: Arial, Helvetica, sans-serif; line-height: 1.5; background-color: #f0fdf4; font-size: 16px; color: #000; padding: 26px 0;">
<div
style="
max-width: 600px;
margin: 0 auto;
padding: 40px 20px;
border: 1px solid #53b147;
border-radius: 16px;
background-color: #fff;
"
>
<div style="text-align: center">
<img src="https://bigdatatech.cloud/logo.png" alt="Logo" width="150" style="display: block; margin: 0 auto" />
</div>
<div style="margin-top: 1rem">
<h2 style="font-size: 18px; font-weight: 700; text-align: center">Xác thực tài khoản người dùng</h2>
<p style="font-weight: 700">Kính gửi quý khách hàng: ${customer}</p>
<p>${companyName} xin chân thành cảm ơn đến Quý khách đã tin tưởng và sử dụng dịch vụ của chúng tôi.</p>
<p>Hệ thống đã tiếp nhận yêu cầu đăng ký tài khoản từ Quý khách.</p>
<table style="width: 100%; margin-top: 1.5rem; border: 1px solid #53b147;">
<tr style="background-color: #53b147; color: #fff">
<th colspan="2" style="text-align: center; color: #fff; padding: 12px 10px">THÔNG TIN TÀI KHOẢN</th>
</tr>
<tr>
<td style="width: 200px; padding: 12px 10px">Email đăng nhập</td>
<td style="font-weight: 700; padding: 12px 10px">:${emailCustomer}</td>
</tr>
<tr>
<td style="width: 200px; padding: 12px 10px">Mã xác thực</td>
<td style="padding: 12px 10px">:${code}</td>
</tr>
<tr>
<td style="width: 200px; padding: 12px 10px">Link xác nhận đăng ký</td>
<td style="padding: 12px 10px">
:<a
href="${urlVerify}"
target="_blank"
title="Xác nhận đăng ký"
style="
background-color: #1769b2;
border: none;
color: white;
padding: 7px 16px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
border-radius: 5px;
"
>Xác nhận đăng ký</a
>
</td>
</tr>
</table>
<p>
<span style="font-weight: 700; text-decoration: underline; font-style: italic;">Chú ý:</span>
<br />
Các thông tin
<i>${companyName}</i>
đã cung cấp rất quan trọng, Quý khách vui lòng bảo mật nghiêm ngặt các thông tin này.
</p>
<p>
<i> Trân trọng cảm ơn Quý khách! </i>
</p>
</div>
<div style="margin-top: 3rem">
<p style="font-weight: 700; font-size: 20px; text-transform: uppercase">${companyName}</p>
<p><strong>Văn phòng:</strong>${office}</p>
<p>Website: <a href="${website}" target="_blank" rel="noopener noreferrer">${website}</a></p>
<p>Email: <a href="mailto${emailCompany}" target="_blank" rel="noopener noreferrer">${emailCompany}</a></p>
<p>Hotline: ${hotline}</p>
</div>
</div>
</div>
`;
}
export const sendResetPasswordEmail = (
customer,
emailCustomer,
code,
urlVerify,
companyName = COMPANY.name,
office = COMPANY.address,
website = COMPANY.website,
emailCompany = COMPANY.email.support,
hotline = COMPANY.phone.hotline,
) => {
return `
<div style="font-family: Arial, Helvetica, sans-serif; line-height: 1.5; background-color: #f0fdf4; font-size: 16px; color: #000; padding: 26px 0;">
<div
style="
max-width: 600px;
margin: 0 auto;
padding: 40px 20px;
border: 1px solid #53b147;
border-radius: 16px;
background-color: #fff;
"
>
<div style="text-align: center">
<img src="https://bigdatatech.cloud/logo.png" alt="Logo" width="150" style="display: block; margin: 0 auto" />
</div>
<div style="margin-top: 1rem">
<h2 style="font-size: 18px; font-weight: 700; text-align: center">Xác thực đặt lại mật khẩu</h2>
<p style="font-weight: 700">Kính gửi quý khách hàng: ${customer}</p>
<p>${companyName} xin chân thành cảm ơn đến Quý khách đã tin tưởng và sử dụng dịch vụ của chúng tôi.</p>
<p>Hệ thống đã tiếp nhận yêu cầu đặt lại mật khẩu từ Quý khách.</p>
<table style="width: 100%; margin-top: 1.5rem; border: 1px solid #53b147;">
<tr style="background-color: #53b147; color: #fff">
<th colspan="2" style="text-align: center; color: #fff; padding: 12px 10px">THÔNG TIN TÀI KHOẢN</th>
</tr>
<tr>
<td style="width: 200px; padding: 12px 10px">Email đăng nhập</td>
<td style="font-weight: 700; padding: 12px 10px">:${emailCustomer}</td>
</tr>
<tr>
<td style="width: 200px; padding: 12px 10px">Mã xác thực</td>
<td style="padding: 12px 10px">:${code}</td>
</tr>
<tr>
<td style="width: 200px; padding: 12px 10px">Link xác nhận đặt lại mật khẩu</td>
<td style="padding: 12px 10px">
:<a
href="${urlVerify}"
target="_blank"
title="Xác nhận đặt lại mật khẩu"
style="
background-color: #1769b2;
border: none;
color: white;
padding: 7px 16px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
border-radius: 5px;
"
>Xác nhận đặt lại mật khẩu</a
>
</td>
</tr>
</table>
<p>
<span style="font-weight: 700; text-decoration: underline; font-style: italic;">Chú ý:</span>
<br />
Các thông tin
<i>${companyName}</i>
đã cung cấp rất quan trọng, Quý khách vui lòng bảo mật nghiêm ngặt các thông tin này.
</p>
<p>Vui lòng bỏ qua email này nếu Quý khách không yêu cầu đặt lại mật khẩu.</p>
<p>
<i> Trân trọng cảm ơn Quý khách! </i>
</p>
</div>
<div style="margin-top: 3rem">
<p style="font-weight: 700; font-size: 20px; text-transform: uppercase">${companyName}</p>
<p><strong>Văn phòng:</strong>${office}</p>
<p>Website: <a href="${website}" target="_blank" rel="noopener noreferrer">${website}</a></p>
<p>Email: <a href="mailto${emailCompany}" target="_blank" rel="noopener noreferrer">${emailCompany}</a></p>
<p>Hotline: ${hotline}</p>
</div>
</div>
</div>
`;
};

View File

@@ -1,31 +1,37 @@
<template> <template>
<div class="columns is-centered mt-6 mx-3"> <div class="auth-page" :style="!isShow ? { alignContent: 'center' } : {}">
<div class="column is-5 mx-0"> <div class="columns is-centered" v-if="isShow">
<div class="column is-5 mx-0 auth-wrapper mt-6">
<div class="auth-header mb-5">
<Logo class="mb-5"></Logo> <Logo class="mb-5"></Logo>
<h2 class="title">Xác thực tài khoản</h2>
</div>
<div class="auth-body">
<template v-if="!userAuth"> <template v-if="!userAuth">
<article class="message is-primary"> <article class="">
<div class="message-body has-background-white py-3"> <div class="py-3">
<strong> xác thực tài khoản </strong> chuỗi gồm <strong> 9 tự, </strong> chúng tôi đã gửi cho bạn <p class="has-text-centered mb-4">
qua email <b>{{ $route.query.email ? ' ' + $route.query.email : '' }}</b Chúng tôi đã gửi xác thực gồm 9 tự đến email
>. Hãy nhập dãy số đó vào ô dưới đây. Hoặc click vào đường link trong email. <strong>{{ emailUser ? ' ' + emailUser : '' }}</strong
</div> >.
</article> <br />
<div class="field mt-4"> Vui lòng nhập bên dưới hoặc kiểm tra email để xác thực.
<div class="control"> </p>
<input class="input is-primary" type="text" placeholder="Nhập mã xác thực" v-model="code" id="inputcode" /> <OtpInput v-model="code" :length="lengthCode" />
</div> <p class="help is-danger mt5 fs13 has-text-centered" v-if="errors.find((v) => v.name === 'code')">
<p class="help is-danger mt5 fs13" v-if="errors.find((v) => v.name === 'code')">
{{ errors.find((v) => v.name === 'code').text }} {{ errors.find((v) => v.name === 'code').text }}
</p> </p>
</div> </div>
</article>
<div class="field mt-5"> <div class="field mt-5">
<p class="control"> <p class="control has-text-centered">
<a class="button is-primary" @click="checkCode()">Xác thực tài khoản</a> <a class="button is-primary" @click="checkCode()">Xác thực tài khoản</a>
</p> </p>
</div> </div>
<p class="mt-5 pt-4 has-text-danger" v-if="$route.query.id"> <p class="pt-4 has-text-danger has-text-centered" v-if="idUser">
<span>Không nhận được xác thực?</span> <span>Bạn chưa nhận được xác thực?</span>
<button :class="`button is-dark is-light ${loading ? 'is-loading' : ''} ml-4`" @click="sendCode()"> <br />
<button :class="`button is-dark is-light ${loading ? 'is-loading' : ''} mt-4`" @click="sendCode()">
Gửi lại Gửi lại
</button> </button>
</p> </p>
@@ -34,6 +40,7 @@
<article class="message" :class="success ? 'is-primary' : 'is-danger'" v-if="success !== undefined"> <article class="message" :class="success ? 'is-primary' : 'is-danger'" v-if="success !== undefined">
<div class="message-body has-background-white py-3"> <div class="message-body has-background-white py-3">
{{ message }} {{ message }}
Xác thực tài khoản thành công.
</div> </div>
</article> </article>
<div class="field mt-5" v-if="action"> <div class="field mt-5" v-if="action">
@@ -46,8 +53,16 @@
</template> </template>
</div> </div>
</div> </div>
</div>
<div class="columns is-centered" v-else>
<Logo isLogoMain></Logo>
</div>
</div>
</template> </template>
<script> <script>
import Logo from '~/components/Logo.vue';
import sendVerificationEmail from '~/components/template/email';
export default { export default {
data() { data() {
return { return {
@@ -57,11 +72,38 @@ export default {
message: undefined, message: undefined,
success: undefined, success: undefined,
action: undefined, action: undefined,
code: undefined,
loading: false, loading: false,
isVietnamese: true,
lengthCode: 9,
isShow: false,
idUser: undefined,
emailUser: undefined,
fullname: undefined,
}; };
}, },
created() { watch: {
code(val) {
if (val.length === this.lengthCode) {
this.checkCode();
}
},
},
head() {
return {
title: `${this.isVietnamese ? 'Xác thực tài khoản' : 'Account Verification'} - ${this.$companyInfo().name}`,
};
},
async created() {
this.idUser = this.$route.query.id || undefined;
this.emailUser = this.$route.query.email || undefined;
if (!this.idUser && !this.emailUser) {
return this.goToLogin();
} else {
const resUser = await this.$getdata('user', { id: this.idUser, email: this.emailUser });
if (!resUser[0] || resUser[0]?.auth_status === 2) return this.goToLogin();
this.fullname = resUser[0].fullname;
}
this.isShow = true;
if (this.$route.query.code) { if (this.$route.query.code) {
this.code = this.$route.query.code; this.code = this.$route.query.code;
this.checkCode(); this.checkCode();
@@ -88,29 +130,38 @@ export default {
this.message = undefined; this.message = undefined;
this.action = undefined; this.action = undefined;
this.errors = []; this.errors = [];
if (this.$empty(this.code)) this.errors.push({ name: 'code', text: 'Mã xác thực không được bỏ trống' }); this.validateCode(this.code);
else if (this.code.length !== 9) this.errors.push({ name: 'code', text: 'Mã xác thực phải là 9 kí tự' }); let resUserAuth = await this.$getdata('userauth', { code: this.code || 0, expiry: 0 });
if (this.errors.length > 0) return; let data = resUserAuth?.[0] || null;
let found = { name: 'userauth', url: 'data/User_Auth', params: { filter: { code: this.code } } };
let result = await this.$getapi([found]); if (data && this.errors.length === 0) {
let data = result[0].data.rows; this.userAuth = data;
if (data.length > 0) { if (!this.userAuth.expiry) {
this.userAuth = data[0]; let result = await this.$getdata('user', { id: this.userAuth.user });
if (this.userAuth.expiry) { let user = result[0];
this.message = 'Bạn đã xác thực tài khoản thành công.';
this.success = true;
this.action = { name: 'signin', to: { path: '/signin' }, text: 'Đi tới trang đăng nhập' };
} else {
let found = { name: 'user', url: 'data/User', params: { filter: { id: this.userAuth.user } } };
result = await this.$getapi([found]);
let user = result[0].data.rows[0];
user.auth_status = 2; user.auth_status = 2;
user.update_time = new Date(); user.update_time = new Date();
result = await this.$updateapi('user', user); result = await this.$updateapi('user', user);
this.processAuth(result); return this.processAuth(result);
} }
} else this.errors.push({ name: 'code', text: 'mã xác thực không hợp lệ' }); return this.goToLogin();
} else this.errors.push({ name: 'code', text: 'Mã xác thực không hợp lệ' });
}, },
validateCode(code) {
const value = String(code || '').trim();
if (!value) {
this.errors.push({ name: 'code', text: 'Mã xác thực không được bỏ trống' });
return;
}
if (value.length !== 9) {
this.errors.push({ name: 'code', text: 'Mã xác thực phải là 9 kí tự' });
return;
}
},
async processAuth(newVal) { async processAuth(newVal) {
if (newVal !== 'error') { if (newVal !== 'error') {
this.message = 'Xác thực tài khoản thành công.'; this.message = 'Xác thực tài khoản thành công.';
@@ -118,7 +169,7 @@ export default {
this.action = { name: 'signin', to: { path: '/signin' }, text: 'Đi tới trang đăng nhập' }; this.action = { name: 'signin', to: { path: '/signin' }, text: 'Đi tới trang đăng nhập' };
this.userAuth.expiry = true; this.userAuth.expiry = true;
this.userAuth.update_time = new Date(); this.userAuth.update_time = new Date();
let result = await this.$updateapi('userauth', this.userAuth); await this.$updateapi('userauth', this.userAuth);
} else if (newVal === false) { } else if (newVal === false) {
this.message = 'Có lỗi xẩy ra. Xác thực tài khoản thành công.'; this.message = 'Có lỗi xẩy ra. Xác thực tài khoản thành công.';
this.success = false; this.success = false;
@@ -128,23 +179,41 @@ export default {
let code = this.$id(); let code = this.$id();
let data = { user: this.$route.query.id, code: code }; let data = { user: this.$route.query.id, code: code };
let result = await this.$insertapi('userauth', data); let result = await this.$insertapi('userauth', data);
let query = this.$store.state.link ? { code: code, link: this.$store.state.link } : { code: code }; let query = this.$store.state.link
? { code: code, link: this.$store.state.link, id: this.idUser, email: this.emailUser }
: { code: code, id: this.idUser, email: this.emailUser };
let routeData = this.$router.resolve({ path: '/account/auth', query: query }); let routeData = this.$router.resolve({ path: '/account/auth', query: query });
let path = window.location.origin + routeData.href; let path = window.location.origin + routeData.href;
let conn = this.$findapi('notiform');
conn.params.filter = { code: 'account-auth' }; const contentEmail = sendVerificationEmail(this.fullname, this.emailUser, code, path);
result = await this.$getapi([conn]); data = { subject: 'Xác thực tài khoản BigDataTechCloud', content: contentEmail, to: this.emailUser };
let msg = result[0].data.rows[0].detail;
msg = msg.replace(' [1]', '');
msg = msg.replace('[2]', code);
msg = msg.replace('[3]', path);
data = { subject: 'Xác thực tài khoản BigDataTechCloud', content: msg, to: this.$route.query.email, sender: 2 };
this.loading = true; this.loading = true;
result = await this.$insertapi('sendemailnow', data); result = await this.$insertapi('sendemail', data);
let text = `Hãy mở email <b>${this.$route.query.email}</b> để nhận mã xác thực`; let text = `Hãy mở email <b>${this.emailUser}</b> để nhận mã xác thực`;
this.$dialog(text, 'Mã xác thực', undefined, 10); this.$dialog(text, 'Mã xác thực', undefined, 10);
this.loading = false; this.loading = false;
}, },
goToLogin() {
return this.$router.push('/signin');
},
}, },
}; };
</script> </script>
<style lang="scss">
.auth-page {
min-height: 100vh;
background-color: var(--bg-accent);
.auth-wrapper {
border-radius: 20px;
overflow: hidden;
border: 1px solid var(--primary-200);
background-color: #fff;
.title {
font-size: 2rem;
text-align: center;
}
}
}
</style>

View File

@@ -1,96 +1,184 @@
<template> <template>
<div class="columns is-centered mt-6 mx-0"> <div>
<div class="column is-5"> <div class="reset-password-page">
<div class="mb-5"> <div class="columns is-centered">
<Logo></Logo> <div class="column is-5 reset-password-wrapper mt-6">
<div class="reset-password-header mb-5">
<Logo class="mb-5" />
<h2 class="title">Đặt lại mật khẩu</h2>
</div> </div>
<section class="hero"> <div class="reset-password-body">
<div class="hero-body px-3 pt-3"> <!-- ============================> STEP EMAIL <============================ -->
<template v-if="!action"> <div class="reset-step-email" v-if="!showStepVerify && !showStepPassword">
<article class="message is-primary"> <p class="has-text-centered mb-4">
<div class="message-body has-background-white py-1"> Vui lòng nhập email xác thực để lấy lại mật khẩu. <br />
<strong> Để lấy lại mật khẩu </strong> vui lòng nhập email kiểm tra. Nếu thông tin hợp lệ chúng Sau khi xác nhận thành ng, chúng tôi sẽ gửi cho bạn một email để đặt lại mật khẩu.
tôi sẽ gửi cho bạn một email chứa đường link để thay đổi mật khẩu.
</div>
</article>
<div class="field is-horizontal mt-2">
<div class="field-body">
<div class="field">
<label class="label has-text-dark">Nhập email</label>
<div class="control">
<input
class="input is-primary"
type="text"
placeholder="Email đã dùng để mở tài khoản"
v-model="email"
ref="inputcode"
@change="checkInfo()"
/>
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'email')">
{{ errors.find((v) => v.name === 'email').text }}
</p> </p>
</div>
<div class="field is-narrow">
<label class="label has-text-dark"> kiểm tra : {{ refcode }} </label>
<div class="control">
<input
class="input is-primary"
type="text"
:placeholder="'Nhập ' + refcode + ' vào đây'"
v-model="code"
/>
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'code')">
{{ errors.find((v) => v.name === 'code').text }}
</p>
</div>
</div>
</div>
<div class="field mt-4" v-if="isPhone"> <div class="form-login">
<label class="label has-text-dark"> Vui lòng cung cấp email để nhận link </label> <div class="field mt-5">
<label class="label">Email<b class="ml-1 has-text-danger">*</b></label>
<div class="control"> <div class="control">
<input <input
class="input is-primary" class="input"
type="text" type="email"
placeholder="Nhập email" placeholder="Nhập email"
v-model="email" v-model="email"
@change="checkEmail()" @blur="validateEmail(email)"
@keyup.enter="getPassword()"
/> />
</div> </div>
<p class="help is-danger mt5 fs13" v-if="errors.find((v) => v.name === 'email')"> <p
{{ errors.find((v) => v.name === 'email').text }} class="help is-danger"
</p> v-if="errors.find((v) => v.name === 'email')"
v-html="errors.find((v) => v.name === 'email').text"
></p>
</div> </div>
<div class="field mt-5"> <div class="field mt-5">
<label for="ref-code" class="label"> xác thực<b class="ml-1 has-text-danger">*</b></label>
<div class="field-body">
<div class="field has-addons">
<p class="control"> <p class="control">
<a class="button is-primary" :class="loading ? 'is-loading' : ''" @click="getPassword()"> <input
Lấy lại mật khẩu</a class="input has-text-centered"
> type="text"
v-model="generatedCode"
disabled
name="generated-code"
/>
</p>
<p class="control is-expanded">
<input
class="input"
id="ref-code"
type="text"
:placeholder="`Nhập ${generatedCode} vào đây`"
v-model="refCode"
@keyup.enter="getPassword()"
@blur="validateRefCode()"
autocomplete="off"
/>
</p>
</div>
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'refCode')">
{{ errors.find((v) => v.name === 'refCode').text }}
</p>
</div>
<div class="field mt-5 pt-1 group-action">
<button :class="['button', 'is-primary', isLoading && 'is-loading']" @click="getPassword()">
Lấy lại mật khẩu
</button>
</div>
</div>
</div>
<!-- ============================> STEP VERIFY <============================ -->
<div class="reset-step-verify" v-if="showStepVerify && !showStepPassword">
<article class="">
<div class="py-3">
<p class="has-text-centered mb-4">
Chúng tôi đã gửi xác thực gồm 9 tự đến email
<strong>{{ email ? ' ' + email : '' }}</strong
>.
<br />
Vui lòng nhập bên dưới hoặc kiểm tra email để xác thực.
</p>
<OtpInput v-model="inputCodeVerify" :length="lengthCodeVerify" />
<p
class="help is-danger mt5 fs13 has-text-centered"
v-if="errors.find((v) => v.name === 'codeVerify')"
>
{{ errors.find((v) => v.name === 'codeVerify').text }}
</p> </p>
</div> </div>
</template>
<template v-else>
<article class="message" :class="success ? 'is-primary' : 'is-danger'" v-if="success !== undefined">
<div class="message-body fs18 has-background-white py-2" v-html="message"></div>
</article> </article>
<div class="field mt-5"> <div class="field mt-5">
<p class="control"> <p class="control has-text-centered">
<nuxt-link class="button is-primary" :to="action.to"> <a class="button is-primary" @click="verifyCode()">Xác nhận</a>
{{ action.text }}
</nuxt-link>
</p> </p>
</div> </div>
</template> <p class="pt-4 has-text-danger has-text-centered" v-if="user">
<span>Bạn chưa nhận được xác thực?</span>
<br />
<button :class="`button is-dark is-light ${isLoading ? 'is-loading' : ''} mt-4`" @click="sendCode()">
Gửi lại
</button>
</p>
</div>
<!-- ============================> STEP PASSWORD <============================ -->
<div class="reset-step-password" v-if="showStepPassword && !showStepVerify">
<div class="field mt-4">
<label class="label">Mật khẩu<b class="ml-1 has-text-danger">*</b></label>
<div class="field-body">
<div class="field has-addons">
<p class="control is-expanded">
<input
class="input"
:type="showpass ? 'text' : 'password'"
placeholder="Mật khẩu phải có ít nhất 8 ký tự, bao gồm chữ hoa, chữ thường, số và ký tự đặc biệt."
v-model="inputPassword"
@blur="validatePassword(inputPassword)"
/>
</p>
<div class="control">
<a class="button" @click="showpass = !showpass">
<span class="icon">
<i
:class="
showpass
? 'mdi mdi-eye-outline has-text-dark fs22'
: 'mdi mdi-eye-off-outline has-text-dark fs22'
"
/>
</span>
</a>
</div>
</div>
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'password')">
{{ errors.find((v) => v.name === 'password').text }}
</p>
</div>
<div class="field mt-4">
<label class="label">Nhập lại mật khẩu<b class="ml-1 has-text-danger">*</b></label>
<div class="field-body">
<div class="field">
<p class="control is-expanded">
<input
class="input"
:type="showpass ? 'text' : 'password'"
placeholder=""
v-model="retypePassword"
/>
</p>
</div>
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'retypePassword')">
{{ errors.find((v) => v.name === 'retypePassword').text }}
</p>
</div>
<div class="field mt-5">
<p class="control has-text-centered">
<a class="button is-primary" @click="changePassword()">Đổi mật khẩu</a>
</p>
</div>
</div>
<!-- Error system -->
<p class="help is-danger mt5 fs13 has-text-centered" v-if="errors.find((v) => v.name === 'system')">
{{ errors.find((v) => v.name === 'system').text }}
</p>
</div>
</div>
</div> </div>
</section>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { sendResetPasswordEmail } from '~/components/template/email';
export default { export default {
data() { data() {
return { return {
@@ -100,95 +188,315 @@ export default {
message: undefined, message: undefined,
success: undefined, success: undefined,
action: undefined, action: undefined,
code: undefined, refCode: undefined,
refcode: undefined, generatedCode: undefined,
username: undefined,
isPhone: false,
email: undefined, email: undefined,
authcode: undefined, authcode: undefined,
loading: false, isLoading: false,
isVietnamese: true,
idUser: undefined,
// ========================> step-verify <========================
showStepVerify: false,
codeVerify: undefined,
lengthCodeVerify: 9,
inputCodeVerify: undefined,
// ========================> step-password <========================
showStepPassword: false,
showpass: false,
inputPassword: undefined,
retypePassword: undefined,
}; };
}, },
watch: {
refCode(val) {
if (val === this.generatedCode) {
this.getPassword();
}
},
// step-verify
inputCodeVerify(val) {
if (val?.length === this.lengthCodeVerify) {
this.checkCode();
}
},
},
computed: {
authstatus: {
get: function () {
return this.$store.state.authstatus;
},
set: function (val) {
this.$store.commit('updateAuthStatus', { authstatus: val });
},
},
},
head() {
return {
title: `${this.isVietnamese ? 'Đặt lại mật khẩu' : 'Reset password'} - ${this.$companyInfo().name}`,
};
},
async created() {
const { email, id, code } = this.$route.query;
this.email = this.$validateEmail(email)?.status ? email.trim() : undefined;
this.idUser = id?.trim() || undefined;
this.inputCodeVerify = code?.trim() || undefined;
if (this.email && this.idUser) {
const resUser = await this.getInfoUser(this.email, this.idUser);
this.user = resUser;
}
const isValidQuery = this.idUser && this.email && this.inputCodeVerify;
if (isValidQuery) {
this.showStepVerify = true;
this.verifyCode();
}
},
mounted() { mounted() {
this.refcode = this.$id().substring(0, 4); this.generatedCode = this.$id().substring(0, 4);
if (this.$refs.inputcode) this.$refs.inputcode.focus();
window.addEventListener('keyup', (ev) => (ev.key === 'Enter' && !this.success ? this.getPassword() : false)); window.addEventListener('keyup', (ev) => (ev.key === 'Enter' && !this.success ? this.getPassword() : false));
}, },
methods: { methods: {
checkInfo() { validateEmail(email) {
this.errors = []; // Xóa lỗi cũ
if (!this.$empty(this.username)) this.username = this.username.trim().toLowerCase(); this.errors = this.errors.filter((err) => err.name !== 'email');
let result = this.$errEmail(this.username);
if (result) this.errors.push({ name: 'username', text: 'Email không hợp lệ' }); const res = this.$validateEmail(email);
else this.isPhone = this.username.indexOf('@') >= 0 ? false : true;
if (!res.status) {
this.errors.push({
name: 'email',
text: res.message,
});
}
}, },
checkEmail() {
this.errors = []; validatePassword(password) {
let result = this.$errEmail(this.email); // Xóa lỗi cũ
if (result) this.errors.push({ name: 'email', text: 'Email không hợp lệ' }); this.errors = this.errors.filter((err) => err.name !== 'password');
const res = this.$validatePassword(password);
if (!res.status) {
this.errors.push({
name: 'password',
text: res.message,
});
}
}, },
validateRefCode() {
// Xóa lỗi cũ
this.errors = this.errors.filter((err) => err.name !== 'refCode');
if (this.$empty(this.refCode)) {
this.errors.push({ name: 'refCode', text: 'Chưa nhập mã xác thực' });
return;
}
if (this.refCode !== this.generatedCode) this.errors.push({ name: 'refCode', text: 'Mã xác thực không đúng' });
},
async getInfoUser(email, id) {
let query = {
email: email,
};
if (id) query.id = id;
const resUser = await this.$getdata('user', query);
return resUser?.[0] ?? null;
},
async createAccountRecovery(user, code) {
let payload = {
user: user,
code: code,
};
await this.$insertapi('accountrecovery', payload);
},
// ============================> STEP EMAIL <============================
async getPassword() { async getPassword() {
this.isLoading = true;
this.success = undefined; this.success = undefined;
this.message = undefined; this.message = undefined;
this.action = undefined; this.action = undefined;
this.errors = []; this.errors = [];
let result = this.$errEmail(this.username);
if (result) this.errors.push({ name: 'username', text: 'Email không hợp lệ' });
if (this.$empty(this.code)) this.errors.push({ name: 'code', text: 'Chưa nhập mã kiểm tra' });
else if (this.refcode !== this.code) this.errors.push({ name: 'code', text: 'Mã kiểm tra không đúng' });
if (this.errors.length > 0) return;
let found = {
name: 'user',
url: 'data/User/',
params: {
filter: {
email: this.username,
},
},
};
result = await this.$getapi([found]);
console.log('===>', result);
let data = result[0].data.rows; this.validateEmail(this.email);
if (data.length > 0) { this.validateRefCode();
this.user = data[0];
if (this.errors.length > 0) {
this.isLoading = false;
return;
}
const resUser = await this.getInfoUser(this.email);
if (!resUser) {
this.isLoading = false;
this.errors.push({ name: 'email', text: 'Tài khoản không tồn tại' });
return;
}
this.user = resUser;
this.authcode = this.$id(); this.authcode = this.$id();
let ele = { user: this.user.id, code: this.authcode }; this.idUser = resUser.id;
result = await this.$insertapi('accountrecovery', ele); this.createAccountRecovery(resUser.id, this.authcode);
this.sendEmailGetPassword(resUser);
},
async sendEmailGetPassword(data) {
this.isLoading = true;
console.log('data', data);
console.log('=====>', result); let query = { id: data.id, code: this.authcode, email: data.email };
if (this.$store.state.link) query.link = this.$store.state.link;
let routeData = this.$router.resolve({ path: '/account/recovery', query: query });
let path = window.location.origin + routeData.href;
const contentEmail = sendResetPasswordEmail(data.fullname, data.email, this.authcode, path);
this.sendEmail(result); const dataSendEmail = {
} else { subject: `[${this.$companyInfo().name}] Mã xác thực đặt lại mật khẩu của bạn`,
this.errors.push({ name: 'username', text: 'Tài khoản không tồn tại' }); content: contentEmail,
to: this.email,
};
const resSendMail = await this.$insertapi('sendemail', dataSendEmail);
console.log('resSendMail', resSendMail);
this.isLoading = false;
let text = `Hãy mở email <b>${this.email}</b> để nhận mã xác thực`;
this.$dialog(text, 'Mã xác thực', undefined, 5);
setTimeout(() => {
this.showStepVerify = true;
}, 6000);
},
// ============================> STEP VERIFY <============================
async verifyCode() {
this.success = undefined;
this.message = undefined;
this.action = undefined;
this.errors = [];
this.validateEmail(this.email);
if (this.$empty(this.inputCodeVerify)) this.errors.push({ name: 'codeVerify', text: 'Chưa nhập mã xác thực' });
if (this.errors.length > 0) {
this.isLoading = false;
return;
}
this.checkCode();
},
async checkCode() {
this.errors = [];
if (!this.idUser || !this.email || !this.inputCodeVerify) {
return;
}
try {
const resAccountRecovery = await this.$getdata('accountrecovery', {
user: this.idUser,
code: this.inputCodeVerify,
});
const tempData = resAccountRecovery?.[0] ?? null;
if (!tempData) {
this.errors.push({ name: 'codeVerify', text: 'Mã xác thực không đúng' });
return;
}
if (tempData.expiry) {
this.errors.push({ name: 'codeVerify', text: 'Mã xác thực đã hết hạn' });
return;
}
this.showStepVerify = false;
this.showStepPassword = true;
resAccountRecovery[0].expiry = true;
await this.$updateapi('accountrecovery', resAccountRecovery[0]);
} catch (error) {
this.errors.push({ name: 'system', text: 'Có lỗi xảy ra, vui lòng thử lại sau' });
} }
}, },
async sendEmail(data) {
let query = { id: this.user.id, code: this.authcode };
if (this.$store.state.link) query.link = this.$store.state.link;
let routeData = this.$router.resolve({ path: '/get-password', query: query });
let path = window.location.origin + routeData.href;
let conn = this.$findapi('notiform');
console.log('conn =====>', conn);
conn.params.filter = { code: 'get-password' }; sendCode() {
let result = await this.$getapi([conn]); if (!this.idUser || !this.email) {
let msg = result[0].data.rows[0].detail; return;
msg = msg.replace('[1]', this.user.fullname); }
msg = msg.replace('[3]', path); this.authcode = this.$id();
data = { this.createAccountRecovery(this.idUser, this.authcode);
subject: 'Phục hồi tài khoản BigDataTech.vn', this.sendEmailGetPassword(this.user);
content: msg, },
to: this.email ? this.email : this.username,
sender: 2, // ============================> STEP PASSWORD <============================
};
this.loading = true; async changePassword() {
result = await this.$insertapi('sendemailnow', data); this.success = undefined;
this.message = `<b>Thành công</b>. Hãy mở email <b>${this.username}</b> để lấy lại mật khẩu.`; this.message = undefined;
this.success = true; this.action = undefined;
this.action = { name: 'signin', to: { path: '/signin' }, text: 'Đi tới trang đăng nhập' }; this.errors = [];
this.loading = false;
this.validatePassword(this.inputPassword);
if (this.$empty(this.retypePassword)) {
this.errors.push({ name: 'retypePassword', text: 'Nhắc lại mật khẩu không được bỏ trống.' });
} else if (this.inputPassword !== this.retypePassword) {
this.errors.push({ name: 'retypePassword', text: 'Nhắc lại mật khẩu phải giống với mật khẩu đã nhập.' });
}
if (this.errors.length > 0) {
this.isLoading = false;
return;
}
if (this.idUser && this.email) {
const resUser = await this.$getdata('user', {
id: this.idUser,
email: this.email,
});
let hasPass = await this.$insertapi('gethash', { text: this.inputPassword });
resUser[0].password = hasPass.rows[0];
const resUpdateUser = await this.$updateapi('user', resUser[0]);
if (resUpdateUser.errors) {
this.errors.push({ name: 'retypePassword', text: 'Đổi mật khẩu thất bại' });
} else {
let text = `Mật khẩu của tài khoản <b>${this.email}</b> đã được cập nhật thành công. Bạn có thể đăng nhập lại bằng mật khẩu mới.`;
this.$dialog(text, 'Mã xác thực', undefined, 5);
setTimeout(() => {
this.$router.push('/signin');
}, 6000);
}
} else {
this.errors.push({ name: 'retypePassword', text: 'Đổi mật khẩu thất bại' });
}
}, },
}, },
}; };
</script> </script>
<style lang="scss">
.reset-password-page {
min-height: var(--layout-full-height);
background-color: var(--bg-accent);
.reset-password-wrapper {
border-radius: 20px;
overflow: hidden;
border: 1px solid var(--primary-200);
background-color: #fff;
.title {
font-size: 2rem;
text-align: center;
}
}
}
</style>

View File

@@ -299,6 +299,7 @@ export default {
this.account = data; this.account = data;
this.fillData(data); this.fillData(data);
}, },
onSuccess(googleUser) { onSuccess(googleUser) {
let info = googleUser.getBasicProfile(); let info = googleUser.getBasicProfile();
@@ -309,6 +310,7 @@ export default {
this.type = 'google'; this.type = 'google';
if (!this.$empty(this.email)) this.checkAccount(); if (!this.$empty(this.email)) this.checkAccount();
}, },
async checkAccount() { async checkAccount() {
if (this.$empty(this.email)) return; if (this.$empty(this.email)) return;
let found = this.$findapi('user'); let found = this.$findapi('user');
@@ -355,6 +357,7 @@ export default {
name: 'email', name: 'email',
text: `Tài khoản đang chờ xác thực. <a @click="$emit('resend')">Gửi lại mã</a>`, text: `Tài khoản đang chờ xác thực. <a @click="$emit('resend')">Gửi lại mã</a>`,
}); });
this.$router.push({ path: '/account/auth', query: { id: data.id, email: data.email } });
} }
return this.errors.length > 0 ? true : false; return this.errors.length > 0 ? true : false;
}, },
@@ -366,10 +369,11 @@ export default {
return; return;
} }
//check permision //check permision
if (this.module !== 'website') { // if (this.module !== 'website') {
let userapps = await this.$getdata('userapps', { user: data.id, apps__code: this.module }, undefined, true); // let userapps = await this.$getdata('userapps', { user: data.id, apps__code: this.module }, undefined, true);
if (!userapps) return this.$router.push('/welcome'); // if (!userapps) return this.$router.push('/welcome');
} // }
this.login = data; //store login this.login = data; //store login
if (this.$store.state.link) { if (this.$store.state.link) {
let ele = this.$copy(data); let ele = this.$copy(data);
@@ -394,21 +398,21 @@ export default {
window.location.href = href; window.location.href = href;
} else this.redirectUrl(); } else this.redirectUrl();
}, },
async sendNoti(obj) { // async sendNoti(obj) {
let found = this.$findapi('notiform'); // let found = this.$findapi('notiform');
found.params.filter = { code: 'login-alert' }; // found.params.filter = { code: 'login-alert' };
const result = await this.$getapi([found]); // const result = await this.$getapi([found]);
let data = result[0].data.rows[0]; // let data = result[0].data.rows[0];
let content = data.detail; // let content = data.detail;
content = content.replace('[1]', this.email); // content = content.replace('[1]', this.email);
content = content.replace('[2]', obj.browser); // content = content.replace('[2]', obj.browser);
content = content.replace('[3]', obj.browser_version); // content = content.replace('[3]', obj.browser_version);
content = content.replace('[4]', obj.platform); // content = content.replace('[4]', obj.platform);
content = content.replace('[5]', obj.os); // content = content.replace('[5]', obj.os);
content = content.replace('[6]', this.$dayjs().format('DD/MM/YYYY HH:mm')); // content = content.replace('[6]', this.$dayjs().format('DD/MM/YYYY HH:mm'));
let ele = { title: data.name, content: content, user: [obj.user.toString()], type: 1 }; // let ele = { title: data.name, content: content, user: [obj.user.toString()], type: 1 };
await this.$insertapi('notification', ele); // await this.$insertapi('notification', ele);
}, // },
async logInWithFacebook() { async logInWithFacebook() {
if (window.FB) { if (window.FB) {
let self = this; let self = this;

View File

@@ -26,15 +26,6 @@
{{ errors.find((v) => v.name === 'fullname').text }} {{ errors.find((v) => v.name === 'fullname').text }}
</p> </p>
</div> </div>
<div class="field mt-4">
<label class="label">Điện thoại</label>
<div class="control">
<input class="input" type="text" placeholder="" v-model="phone" @blur="validatePhone(phone)" />
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'phone')">
{{ errors.find((v) => v.name === 'phone').text }}
</p>
</div>
<div class="field mt-4"> <div class="field mt-4">
<label class="label">Email<b class="ml-1 has-text-danger">*</b></label> <label class="label">Email<b class="ml-1 has-text-danger">*</b></label>
<div class="control"> <div class="control">
@@ -107,7 +98,9 @@
</p> </p>
</div> </div>
<div class="field mt-5 pt-1 group-action"> <div class="field mt-5 pt-1 group-action">
<button class="button is-primary" @click="createAccount()">Tạo tài khoản</button> <button :class="['button', 'is-primary', isLoadingCreate && 'is-loading']" @click="createAccount()">
Tạo tài khoản
</button>
<!-- <a class="ml-2" @click="$router.push('/signin')">Đăng nhập</a> --> <!-- <a class="ml-2" @click="$router.push('/signin')">Đăng nhập</a> -->
</div> </div>
</div> </div>
@@ -191,6 +184,7 @@
<script> <script>
import GoogleLogin from 'vue-google-login'; import GoogleLogin from 'vue-google-login';
import sendVerificationEmail from '~/components/template/email';
export default { export default {
components: { GoogleLogin }, components: { GoogleLogin },
head() { head() {
@@ -221,6 +215,7 @@ export default {
validTo: this.$dayjs().add(30, 'day').format('DD/MM/YYYY'), validTo: this.$dayjs().add(30, 'day').format('DD/MM/YYYY'),
company: this.$companyInfo(), company: this.$companyInfo(),
isVietnamese: true, isVietnamese: true,
isLoadingCreate: false,
}; };
}, },
async mounted() { async mounted() {
@@ -310,18 +305,7 @@ export default {
if (!this.$empty(this.email)) { if (!this.$empty(this.email)) {
this.email = this.email.trim().toLowerCase(); this.email = this.email.trim().toLowerCase();
} }
// if (this.$empty(this.fullname)) {
// this.errors.push({ name: 'fullname', text: 'Họ và tên không được bỏ trống.' });
// } else if (this.fullname.length < 5) {
// this.errors.push({ name: 'fullname', text: 'Họ và tên quá ngắn. Yêu cầu từ 5 kí tự trở nên.' });
// }
// if (!this.$empty(this.phone)) {
// if (!this.$regexPhone(this.phone)) {
// this.errors.push({ name: 'phone', text: 'Số điện thoại không hợp lệ.' });
// }
// }
this.validateFullName(this.fullname); this.validateFullName(this.fullname);
this.validatePhone(this.phone);
this.validateEmail(this.email); this.validateEmail(this.email);
this.validatePassword(this.password); this.validatePassword(this.password);
@@ -377,26 +361,17 @@ export default {
} }
}, },
validatePhone(phone) {
// Xóa lỗi cũ
this.errors = this.errors.filter((err) => err.name !== 'phone');
const res = this.$validatePhone(phone, true);
if (!res.status) {
this.errors.push({
name: 'phone',
text: res.message,
});
}
},
async createAccount() { async createAccount() {
if (this.checkError()) return; this.isLoadingCreate = true;
if (this.checkError()) {
this.isLoadingCreate = false;
return;
}
const userRes = await this.$getdata('user', { email: this.email }); const userRes = await this.$getdata('user', { email: this.email });
const customerRes = await this.$getdata('customer', { email: this.email }); const customerRes = await this.$getdata('customer', { email: this.email });
if (userRes.length > 0 || customerRes.length > 0) { if (userRes.length > 0 || customerRes.length > 0) {
this.isLoadingCreate = false;
return this.errors.push({ name: 'email', text: 'Tài khoản đã tồn tại trong hệ thống.' }); return this.errors.push({ name: 'email', text: 'Tài khoản đã tồn tại trong hệ thống.' });
} }
@@ -407,6 +382,7 @@ export default {
let result = await this.$getapi([ele]); let result = await this.$getapi([ele]);
let data = result.find((v) => v.name === 'user').data.rows; let data = result.find((v) => v.name === 'user').data.rows;
if (data.length > 0) { if (data.length > 0) {
this.isLoadingCreate = false;
return this.errors.push({ name: 'email', text: 'Tài khoản đã tồn tại trong hệ thống.' }); return this.errors.push({ name: 'email', text: 'Tài khoản đã tồn tại trong hệ thống.' });
} }
@@ -436,6 +412,7 @@ export default {
this.user = await this.$insertapi('user', data); this.user = await this.$insertapi('user', data);
const customer = await this.$insertapi('customer', { ...data, type: null }); const customer = await this.$insertapi('customer', { ...data, type: null });
this.isLoadingCreate = false;
if (this.user !== 'error' && customer != 'error') { if (this.user !== 'error' && customer != 'error') {
let text = 'Bạn đã đăng ký tài khoản thành công. Hệ thống tự động chuyển tới trang đăng nhập.'; let text = 'Bạn đã đăng ký tài khoản thành công. Hệ thống tự động chuyển tới trang đăng nhập.';
if (this.status.code === 'wait') if (this.status.code === 'wait')
@@ -446,24 +423,29 @@ export default {
this.code = this.$id(); this.code = this.$id();
let data = { user: this.user.id, code: this.code }; let data = { user: this.user.id, code: this.code };
result = await this.$insertapi('userauth', data); result = await this.$insertapi('userauth', data);
setTimeout(() => {
this.processAuth(); this.processAuth();
}, 6000);
} else this.$router.push({ path: '/signin' }); } else this.$router.push({ path: '/signin' });
}, },
async processAuth() { async processAuth() {
let query = this.$store.state.link ? { code: this.code, link: this.$store.state.link } : { code: this.code }; let query = this.$store.state.link
? { code: this.code, link: this.$store.state.link, id: this.user.id, email: this.email }
: { code: this.code, id: this.user.id, email: this.email };
let routeData = this.$router.resolve({ path: '/account/auth', query: query }); let routeData = this.$router.resolve({ path: '/account/auth', query: query });
let path = window.location.origin + routeData.href; let path = window.location.origin + routeData.href;
let conn = this.$findapi('userauth');
conn.params.filter = { user: this.user.id };
let result = await this.$getapi([conn]);
let msg = result[0].data.rows[0].code; const contentEmail = sendVerificationEmail(
msg = msg.replace('[1]', this.fullname); this.fullname,
msg = msg.replace('[2]', this.code); this.user.email,
msg = msg.replace('[3]', path); this.code,
path
);
let data = { subject: 'Xác thực tài khoản BigDataTechCloud', content: contentEmail, to: this.user.email };
const resSendMail = await this.$insertapi('sendemail', data);
console.log('resSendMail', resSendMail);
let data = { subject: 'Xác thực tài khoản BigDataTechCloud', content: msg, to: this.user.email };
result = await this.$insertapi('sendemail', data);
this.$router.push({ path: '/account/auth', query: { id: this.user.id, email: this.email } }); this.$router.push({ path: '/account/auth', query: { id: this.user.id, email: this.email } });
}, },
onSuccess(googleUser) { onSuccess(googleUser) {
@@ -532,7 +514,7 @@ export default {
<style lang="scss"> <style lang="scss">
.login-page { .login-page {
background-color: #f0fdf4; background-color: var(--bg-accent);
min-height: 100vh; min-height: 100vh;
align-content: center; align-content: center;

View File

@@ -389,5 +389,20 @@ Vue.use({
message: '', message: '',
}; };
}; };
Vue.prototype.$maskEmailAdvanced = (email) => {
if (!email) return '';
const [name, domain] = email.split('@');
if (!name || !domain) return email;
const [domainName, ext] = domain.split('.');
const maskedName = name.slice(0, 2) + '*'.repeat(Math.max(name.length - 2, 1));
const maskedDomain = domainName[0] + '*'.repeat(Math.max(domainName.length - 1, 1));
return `${maskedName}@${maskedDomain}.${ext}`;
};
}, },
}); });

View File

@@ -18,7 +18,7 @@ const apis = [
params: { params: {
sort: '-id', sort: '-id',
values: values:
'id,auth_method__code,blocked,auth_status__code,username,register_method__code,fullname,type,type__code,type__name,create_time,create_time__date,auth_method,auth_status,register_method,create_time,update_time', 'id,auth_method__code,blocked,auth_status__code,username,email,password,register_method__code,fullname,type,type__code,type__name,create_time,create_time__date,auth_method,auth_status,register_method,create_time,update_time',
}, },
}, },