Files
login-v2/pages/signin.vue
2026-03-25 16:03:35 +07:00

617 lines
19 KiB
Vue

<template>
<div class="login-page">
<div class="columns is-centered px-0 mx-0 my-0">
<div :class="`column wrapper-login is-4-desktop is-6-tablet`">
<div class="px-6 py-6 login-box">
<div class="login-header">
<Logo />
<h2 class="title mt-4 has-text-centered">{{ isVietnamese ? 'Đăng nhập' : 'Login' }}</h2>
</div>
<div class="login-body">
<div class="form-login">
<div class="field mt-5">
<label class="label">Email<b class="ml-1 has-text-danger">*</b></label>
<div class="control">
<input
class="input"
type="email"
placeholder="Nhập email"
v-model="email"
@blur="validateEmail(email)"
@keyup.enter="signin()"
/>
</div>
<p
class="help is-danger"
v-if="errors.find((v) => v.name === 'email')"
v-html="errors.find((v) => v.name === 'email').text"
></p>
</div>
<div class="field mt-5">
<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="Nhập mật khẩu"
v-model="password"
@blur="validatePassword(password)"
@keyup.enter="signin()"
/>
</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-5 pt-1 group-action">
<button :class="['button', 'is-primary', isLoading && 'is-loading']" @click="signin()">
Đăng nhập
</button>
<a class="is-primary" @click="accountRecovery()">Quên mật khẩu?</a>
</div>
</div>
<div class="social-login">
<div class="social-header my-4">
<span>Hoặc</span>
</div>
<div class="social-body">
<div class="social-item">
<GoogleLogin class="button" :onSuccess="onSuccess" :params="params">
<img class="icon" src="/icons/google.svg" alt="Google" />
<span>Đăng nhập với Google</span>
</GoogleLogin>
</div>
<div class="social-item">
<button class="button" @click="logInWithFacebook()">
<img class="icon" src="/icons/facebook.svg" alt="Facebook" />
<span> Đăng nhập với Facebook </span>
</button>
</div>
</div>
</div>
<p class="mt-5 has-text-centered">
Chưa tài khoản?
<nuxt-link class="ml-2" to="/signup">Tạo tài khoản</nuxt-link>
</p>
<div class="info-support mt-4">
<div class="support-header">
<h5 class="title">Cần hỗ trợ</h5>
</div>
<div class="support-body">
<p class="support-item">
<a :href="`tel:${company.phone.support}`" target="_blank">
<img src="/icons/phone-call.svg" alt="Support" width="24" height="24" />
<span>{{ company.phone.support }}</span>
</a>
</p>
<p class="support-item">
<a :href="`mailto:${company.email.support}`">
<img src="/icons/email.svg" alt="" width="24" height="24" />
<span>{{ company.email.support }}</span>
</a>
</p>
</div>
</div>
</div>
<div class="login-footer">
<img src="/icons/shield.svg" alt="" width="24" />
<span> {{ new Date().getFullYear() }} </span> &nbsp;
<span v-if="isVietnamese">
Bản quyền thuộc về
<a
:href="company.parent.website"
target="_blank"
rel="noopener noreferrer"
:title="company.parent.slogan"
>{{ company.parent.name }}</a
>
</span>
<span v-else>
Copyright by
<a
:href="company.parent.website"
target="_blank"
rel="noopener noreferrer"
:title="company.parent.sloganEn"
>{{ company.parent.name }}</a
>
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Bowser from 'bowser';
import GoogleLogin from 'vue-google-login';
export default {
components: {
GoogleLogin,
},
head() {
return { title: `${this.isVietnamese ? 'Đăng nhập' : 'Login'} - ${this.company.name}` };
},
data() {
return {
fullname: undefined,
username: undefined,
email: undefined,
password: undefined,
errors: [],
showpass: false,
registers: [],
params: { client_id: '389310749372-mnfsd09ukgl0ahbdrm94dsepremlfgs9.apps.googleusercontent.com' },
type: undefined,
isLoaded: false,
data: undefined,
account: undefined,
company: this.$companyInfo(),
isVietnamese: true,
isLoading: false,
};
},
watch: {
isLoaded: function (newVal) {
if (this.isLoaded) {
let self = this;
window.FB.login(
function (response) {
if (response.authResponse) {
let userId = response.authResponse.userID;
self.getUserInfo(userId);
}
},
{
scope: 'public_profile,email',
return_scopes: true,
auth_type: 'rerequest',
},
);
}
},
},
computed: {
login: {
get: function () {
return this.$store.state.login;
},
set: function (val) {
this.$store.commit('updateLogin', { login: val });
},
},
registermethod: {
get: function () {
return this.$store.state.registermethod;
},
set: function (val) {
this.$store.commit('updateRegisterMethod', { registermethod: val });
},
},
authmethod: {
get: function () {
return this.$store.state.authmethod;
},
set: function (val) {
this.$store.commit('updateAuthMethod', { authmethod: val });
},
},
authstatus: {
get: function () {
return this.$store.state.authstatus;
},
set: function (val) {
this.$store.commit('updateAuthStatus', { authstatus: val });
},
},
usertype: {
get: function () {
return this.$store.state.usertype;
},
set: function (val) {
this.$store.commit('updateUserType', { usertype: val });
},
},
viewport: {
get: function () {
return this.$store.state.viewport;
},
set: function (val) {
this.$store.commit('updateViewPort', { viewport: val });
},
},
},
methods: {
checkError() {
this.errors = [];
if (!this.$empty(this.email)) {
this.email = this.email.trim().toLowerCase();
}
this.validateEmail(this.email);
this.validatePassword(this.password);
return this.errors.length > 0 ? true : false;
},
validateEmail(email) {
// Xóa lỗi cũ
this.errors = this.errors.filter((err) => err.name !== 'email');
const res = this.$validateEmail(email);
if (!res.status) {
this.errors.push({
name: 'email',
text: res.message,
});
}
},
validatePassword(password) {
// Xóa lỗi cũ
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,
});
}
},
async signin() {
this.isLoading = true;
if (this.checkError()) {
this.isLoading = false;
return;
}
let conn = this.$findapi('login');
conn.params.filter = { email: this.email, password: this.password };
let result = await this.$getapi([conn]);
let data = result?.find((v) => v.name === 'login').data.rows;
this.account = data;
this.fillData(data);
},
onSuccess(googleUser) {
let info = googleUser.getBasicProfile();
let keys = Object.keys(info);
this.email = info[keys[5]];
this.fullname = info[keys[1]];
this.username = info[keys[0]];
this.type = 'google';
if (!this.$empty(this.email)) this.checkAccount();
},
async checkAccount() {
if (this.$empty(this.email)) return;
let found = this.$findapi('user');
found.params = { filter: { email: this.email } };
let result = await this.$getapi([found]);
let data = result.find((v) => v.name === 'user').data.rows;
if (data.length > 0) this.fillData(data[0]);
else {
let rs = await this.$insertapi('gethash', { text: this.$id() });
let hash = rs.rows[0];
let registerMethod = this.registermethod.find((v) => v.code === 'create-account').id;
if (this.type) registerMethod = this.registermethod.find((v) => v.code === this.type).id;
data = {
fullname: this.fullname,
username: this.username ?? this.email,
email: this.email,
password: hash,
type: this.usertype.find((v) => v.code === 'customer').id,
register_method: registerMethod,
auth_status: this.authstatus.find((v) => v.code === 'auth').id,
auth_method:
this.email.indexOf('@') >= 0
? this.authmethod.find((v) => v.code === 'email').id
: this.authmethod.find((v) => v.code === 'phone').id,
display_name: this.fullname,
};
let res = await this.$insertapi('user', data);
if (res !== 'error') {
this.login = res;
this.redirectUrl();
}
}
},
invalidLogin(data) {
if (!data) {
const text = 'Tài khoản hoặc mật khẩu không chính xác';
this.errors.push({ name: 'email', text: text });
this.errors.push({ name: 'password', text: text });
} else if (data.blocked) {
this.errors.push({ name: 'email', text: 'Tài khoản đang bị khóa. Đăng nhập không thành công' });
} else if (data.auth_status === 1) {
this.errors.push({
name: 'email',
text: `Tài khoản đang chờ xác thực. <a @click="$emit('resend')">Gửi lại mã</a>`,
});
}
return this.errors.length > 0 ? true : false;
},
async fillData(data) {
this.isLoading = true;
if (this.invalidLogin(data)) {
this.isLoaded = false;
this.isLoading = false;
return;
}
//check permision
if (this.module !== 'website') {
let userapps = await this.$getdata('userapps', { user: data.id, apps__code: this.module }, undefined, true);
if (!userapps) return this.$router.push('/welcome');
}
this.login = data; //store login
if (this.$store.state.link) {
let ele = this.$copy(data);
//get token & redirect link
const browser = Bowser.getParser(window.navigator.userAgent);
let obj = {
browser: browser.getBrowserName(),
browser_version: browser.getBrowserVersion(),
platform: browser.getPlatform().type,
os: browser.getOSName(),
user: data.id,
token: this.$id(),
};
ele.token = obj.token;
await this.$insertapi('authtoken', obj);
let link = this.$store.state.link;
if (data.type === 3 && link.indexOf('y99') >= 0) {
link = link.indexOf('dev') >= 0 ? '' : '';
}
let href = `${link}?username=${ele.username}&token=${ele.token}&fullname=${ele.fullname}&userid=${ele.id}`;
if (ele.avatar) href = `${href}&avatar=${ele.avatar}`;
window.location.href = href;
} else this.redirectUrl();
},
async sendNoti(obj) {
let found = this.$findapi('notiform');
found.params.filter = { code: 'login-alert' };
const result = await this.$getapi([found]);
let data = result[0].data.rows[0];
let content = data.detail;
content = content.replace('[1]', this.email);
content = content.replace('[2]', obj.browser);
content = content.replace('[3]', obj.browser_version);
content = content.replace('[4]', obj.platform);
content = content.replace('[5]', obj.os);
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 };
await this.$insertapi('notification', ele);
},
async logInWithFacebook() {
if (window.FB) {
let self = this;
window.FB.login(
function (response) {
if (response.authResponse) {
let userId = response.authResponse.userID;
self.getUserInfo(userId);
}
},
{
scope: 'public_profile,email',
return_scopes: true,
auth_type: 'rerequest',
},
);
} else {
await this.loadFacebookSDK(document, 'script', 'facebook-jssdk');
await this.initFacebook();
}
},
async initFacebook() {
let self = this;
window.fbAsyncInit = function () {
window.FB.init({
appId: '314849628253331',
cookie: true,
xfbml: true,
version: 'v8.0',
});
self.isLoaded = true;
};
},
async loadFacebookSDK(d, s, id) {
var js,
fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement(s);
js.id = id;
js.src = 'https://connect.facebook.net/en_US/sdk.js';
fjs.parentNode.insertBefore(js, fjs);
},
getUserInfo(userId) {
let self = this;
window.FB.api('/me?fields=id,name,email', function (response) {
self.email = response.email;
self.fullname = response.name;
self.type = 'facebook';
self.checkAccount();
});
},
accountRecovery() {
this.$router.push({ path: '/account/recovery' });
},
// async resendAuthcode() {
// let code = this.$id();
// let result = await this.$insertapi('userauth', { user: this.account.id, code: code });
// let query = this.$store.state.link ? { link: this.$store.state.link } : {};
// query.id = this.account.id;
// query.email = this.email;
// query.action = 'sendcode';
// this.$router.push({ path: '/account/auth', query: query });
// },
async redirectUrl() {
this.$router.push(this.$route.query.href || '/welcome');
},
},
};
</script>
<style lang="scss">
.login-page {
background-color: #f0fdf4;
min-height: 100vh;
align-content: center;
button.is-primary {
background-color: #53b147;
box-shadow: 0 4px 12px rgba(83, 177, 71, 0.25);
&:hover {
background: #45963a;
transform: translateY(-1px);
}
}
.wrapper-login {
border-radius: 20px;
overflow: hidden;
border: 1px solid #bbf7d0;
background-color: #fff;
padding: 0;
// .login-header {
// }
.login-body {
.form-login {
.group-action {
display: flex;
align-items: center;
justify-content: space-between;
}
}
.social-login {
.social-header {
text-align: center;
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
top: 50%;
width: 100%;
height: 1px;
background-color: #ddd;
}
span {
position: relative;
z-index: 1;
background-color: #fff;
padding: 0 10px;
}
}
.social-body {
display: flex;
align-items: center;
gap: 10px;
.social-item {
flex: 1;
button {
height: fit-content;
width: 100%;
font-weight: 600;
&:hover {
background-color: #bbf7d0;
border-color: #bbf7d0;
}
}
.icon {
width: 24px;
height: 24px;
}
}
}
}
.info-support {
background-color: #fff;
border: 1px solid #bbf7d0;
border-radius: 16px;
overflow: hidden;
.support-header {
background-color: #f0fdf4;
text-align: center;
padding: 14px 0;
.title {
font-weight: bold;
font-size: 16px;
}
}
.support-body {
display: flex;
padding: 16px 0;
align-items: center;
justify-content: space-around;
.support-item {
a {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
color: #374151;
&:hover {
color: #16a34a;
img {
filter: brightness(0) saturate(100%) invert(40%) sepia(80%) saturate(400%) hue-rotate(90deg);
}
}
}
}
}
}
}
.login-footer {
margin-top: 24px;
padding-top: 12px;
border-top: 1px solid #e5e7eb;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
color: #9ca3af;
span {
white-space: nowrap;
}
}
}
}
</style>