499 lines
15 KiB
Vue
Executable File
499 lines
15 KiB
Vue
Executable File
<template>
|
|
<div class="columns is-centered px-0 mx-0">
|
|
<div :class="`column wrapper-login is-${viewport >= 4 ? 4 : 6}`">
|
|
<div class="has-background-white px-6 py-6 login-box">
|
|
<div class="login-header">
|
|
<Logo />
|
|
<h2 class="title mt-4">{{ isVietnamese ? 'Đăng nhập' : 'Login' }}</h2>
|
|
<p class="subtitle is-6 has-text-centered" v-if="getEnv">
|
|
{{ getEnv }}
|
|
</p>
|
|
</div>
|
|
<div class="login-body mt-3">
|
|
<div class="field mt-5">
|
|
<label class="label"
|
|
>{{ isVietnamese ? 'Tên đăng nhập' : 'User name' }}<b class="ml-1 has-text-danger">*</b></label
|
|
>
|
|
<div class="control">
|
|
<input
|
|
class="input"
|
|
type="text"
|
|
:placeholder="isVietnamese ? 'Nhập tên đăng nhập' : 'Enter username'"
|
|
v-model="username"
|
|
@keyup.enter="signin()"
|
|
/>
|
|
</div>
|
|
<p
|
|
class="help is-danger"
|
|
v-if="errors.find((v) => v.name === 'username')"
|
|
v-html="errors.find((v) => v.name === 'username').text"
|
|
></p>
|
|
</div>
|
|
<div class="field mt-5">
|
|
<label class="label"
|
|
>{{ isVietnamese ? 'Mật khẩu' : 'Password' }}<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="isVietnamese ? 'Nhập mật khẩu' : 'Enter password'"
|
|
v-model="password"
|
|
@keyup.enter="signin()"
|
|
/>
|
|
</p>
|
|
<div class="control">
|
|
<a
|
|
class="button view-pass-btn"
|
|
@click="showpass = !showpass"
|
|
:title="
|
|
showpass
|
|
? isVietnamese
|
|
? 'Đăng nhập'
|
|
: 'Login'
|
|
: isVietnamese
|
|
? 'Hiện mật khẩu'
|
|
: 'Show Password'
|
|
"
|
|
>
|
|
<span class="icon">
|
|
<SvgIcon v-bind="{ name: showpass ? 'eye-off.svg' : 'view.svg', type: 'gray' }" />
|
|
</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 is-grouped mt-5 is-align-items-center is-justify-content-space-between">
|
|
<div class="control">
|
|
<button
|
|
:class="['button', 'btn-primary', isLoaded && 'is-loading']"
|
|
@click="signin()"
|
|
@keyup.enter="signin()"
|
|
>
|
|
{{ isVietnamese ? 'Đăng nhập' : 'Login' }}
|
|
</button>
|
|
</div>
|
|
<div class="control reset-wrapper">
|
|
<a class="reset-link" @click="accountRecovery()"
|
|
>{{ isVietnamese ? 'Đặt lại mật khẩu' : 'Forgot Password' }}?</a
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<footer class="login-footer">
|
|
<span v-if="isVietnamese">
|
|
©{{ new Date().getFullYear() }} Bản quyển thuộc về
|
|
<a :href="company.parent.website" target="_blank" :title="company.parent.slogan">
|
|
{{ company.parent.name }}
|
|
</a>
|
|
</span>
|
|
<span v-else>
|
|
©{{ new Date().getFullYear() }} Copyright by
|
|
<a :href="company.parent.website" target="_blank" :title="company.parent.name">
|
|
{{ company.parent.name }}
|
|
</a>
|
|
</span>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import Bowser from 'bowser';
|
|
import { COMPANY } from '../constants/company';
|
|
export default {
|
|
head() {
|
|
return { title: this.isVietnamese ? `Đăng nhập - ${this.company.name}` : `Login - ${this.company.name}` };
|
|
},
|
|
data() {
|
|
return {
|
|
fullname: undefined,
|
|
username: undefined,
|
|
password: undefined,
|
|
errors: [],
|
|
showpass: false,
|
|
registers: [],
|
|
type: undefined,
|
|
isLoaded: false,
|
|
data: undefined,
|
|
account: undefined,
|
|
company: COMPANY,
|
|
};
|
|
},
|
|
// mounted() {
|
|
// window.addEventListener('keyup', (ev) =>
|
|
// ev.key === 'Enter' && this.$route.name === 'signup' ? this.signin() : false,
|
|
// );
|
|
// if (!this.module) return this.$router.push('/welcome');
|
|
// },
|
|
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 });
|
|
},
|
|
},
|
|
module: {
|
|
get: function () {
|
|
return this.$store.state['module'];
|
|
},
|
|
set: function (val) {
|
|
this.$store.commit('updateStore', { name: 'module', data: val });
|
|
},
|
|
},
|
|
isVietnamese() {
|
|
// return navigator.language.toLowerCase().startsWith('vi');
|
|
return true;
|
|
},
|
|
|
|
getEnv() {
|
|
const link = (this.$route.query.link || '').replace(/^https?:\/\//, '').toLowerCase();
|
|
const module = (this.$route.query.module || '').replace(/^https?:\/\//, '').toLowerCase();
|
|
const isDev = link.includes('dev');
|
|
|
|
const ENV_MAP = [
|
|
{
|
|
key: 'system',
|
|
module: 'system',
|
|
vi: 'Cổng quản trị hệ thống',
|
|
en: 'Admin Portal',
|
|
},
|
|
{
|
|
key: 'biz',
|
|
module: 'application',
|
|
vi: 'Cổng thông tin Chủ đầu tư',
|
|
en: 'Business Portal',
|
|
},
|
|
{
|
|
key: 'dealer',
|
|
module: 'dealer',
|
|
vi: 'Cổng thông tin Đại lý',
|
|
en: 'Dealer Portal',
|
|
},
|
|
];
|
|
|
|
const found = ENV_MAP.find((item) => link.includes(item.key) && module === item.module);
|
|
if (found) {
|
|
const title = this.isVietnamese ? found.vi : found.en;
|
|
return isDev
|
|
? `${title} - ${this.isVietnamese ? 'Môi trường phát triển (DEV)' : 'Development Environment (DEV)'}`
|
|
: title;
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
},
|
|
methods: {
|
|
checkError() {
|
|
this.errors = [];
|
|
if (!this.$empty(this.username)) {
|
|
this.username = this.username.trim().toLowerCase();
|
|
}
|
|
if (this.$empty(this.username)) {
|
|
this.errors.push({
|
|
name: 'username',
|
|
text: this.isVietnamese ? 'Vui lòng nhập tên đăng nhập.' : 'Please enter your username.',
|
|
});
|
|
}
|
|
if (this.$empty(this.password)) {
|
|
this.errors.push({
|
|
name: 'password',
|
|
text: this.isVietnamese ? 'Vui lòng nhập mật khẩu.' : 'Please enter your password.',
|
|
});
|
|
} else if (this.password.length < 6) {
|
|
this.errors.push({
|
|
name: 'password',
|
|
text: this.isVietnamese
|
|
? 'Mật khẩu gồm 6 kí tự trở nên bao gồm chữ và số .'
|
|
: 'Password must be at least 6 characters long and include letters and numbers.',
|
|
});
|
|
} else if (!(/\d/.test(this.password) && /[a-zA-Z]/.test(this.password))) {
|
|
this.errors.push({
|
|
name: 'password',
|
|
text: this.isVietnamese
|
|
? 'Mật khẩu gồm 6 kí tự trở nên bao gồm chữ và số .'
|
|
: 'Password must be at least 6 characters long and include letters and numbers.',
|
|
});
|
|
}
|
|
return this.errors.length > 0 ? true : false;
|
|
},
|
|
async signin() {
|
|
this.isLoaded = true;
|
|
if (this.checkError()) {
|
|
this.isLoaded = false;
|
|
return;
|
|
}
|
|
let conn = this.$findapi('login');
|
|
conn.params.filter = { username: this.username, password: this.password };
|
|
let result = await this.$getapi([conn]);
|
|
if (result === 'error') {
|
|
this.isLoaded = false;
|
|
return this.$dialog(
|
|
`${
|
|
this.isVietnamese
|
|
? `Đã xảy ra lỗi. Vui lòng thử lại hoặc liên hệ số hotline: ${this.company.phone.hotline} để được hỗ trợ.`
|
|
: `An error occurred. Please try again or contact the hotline: ${this.company.phone.hotline} for support.`
|
|
}`,
|
|
`${this.isVietnamese ? 'Lỗi kết nối' : 'Connection error'}`,
|
|
'Error',
|
|
20,
|
|
);
|
|
}
|
|
let data = result.find((v) => v.name === 'login').data.rows;
|
|
this.account = data;
|
|
this.fillData(data);
|
|
},
|
|
invalidLogin(data) {
|
|
if (!data) {
|
|
const text = this.isVietnamese ? 'Tên đăng nhập hoặc mật khẩu không đúng.' : 'Incorrect username or password.';
|
|
this.errors.push({ name: 'username', text: text });
|
|
this.errors.push({ name: 'password', text: text });
|
|
} else if (data.blocked) {
|
|
this.errors.push({
|
|
name: 'username',
|
|
text: this.isVietnamese
|
|
? 'Tài khoản đang bị khóa. Đăng nhập không thành công'
|
|
: 'Your account has been blocked. Login failed.',
|
|
});
|
|
} else if (data.auth_status === 1) {
|
|
this.errors.push({
|
|
name: 'username',
|
|
text: this.isVietnamese ? `Tài khoản đang chờ xác thực.` : 'Your account is pending verification.',
|
|
});
|
|
}
|
|
return this.errors.length > 0 ? true : false;
|
|
},
|
|
async fillData(data) {
|
|
if (this.invalidLogin(data)) {
|
|
this.isLoaded = 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.username);
|
|
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);
|
|
},
|
|
accountRecovery() {
|
|
this.$router.push({ path: '/account/recovery' });
|
|
},
|
|
async redirectUrl() {
|
|
this.$router.push(this.$route.query.href || '/welcome');
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
<style>
|
|
.wrapper-login {
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.wrapper-login .login-box {
|
|
margin-top: 20%;
|
|
width: 100%;
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.wrapper-login .login-box .login-header .title {
|
|
text-align: center;
|
|
color: #c66a1c;
|
|
font-size: 28px;
|
|
font-weight: 600;
|
|
line-height: 36px;
|
|
letter-spacing: -0.2px;
|
|
}
|
|
.subtitle {
|
|
font-size: 15px;
|
|
color: #3f6e73;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.wrapper-login .login-box .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;
|
|
}
|
|
|
|
.wrapper-login .login-box .login-footer span {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.wrapper-login .btn-primary {
|
|
width: 100%;
|
|
height: 46px;
|
|
|
|
background-color: #16a34a; /* primary from logo */
|
|
color: #ffffff;
|
|
|
|
border: none;
|
|
border-radius: 12px;
|
|
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.wrapper-login .btn-primary:hover {
|
|
background-color: #15803d;
|
|
}
|
|
|
|
.wrapper-login .btn-primary:active {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
.wrapper-login .btn-primary:disabled {
|
|
background-color: #9ca3af;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.wrapper-login .reset-wrapper {
|
|
text-align: right;
|
|
}
|
|
|
|
.wrapper-login .reset-link {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: #16a34a;
|
|
text-decoration: none;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.wrapper-login .reset-link:hover {
|
|
color: #1f3a3d;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* INPUT BASE */
|
|
.wrapper-login input {
|
|
width: 100%;
|
|
height: 44px;
|
|
padding: 10px 14px;
|
|
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 10px;
|
|
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
outline: none;
|
|
}
|
|
|
|
/* PLACEHOLDER */
|
|
.wrapper-login input::placeholder {
|
|
color: #9ca3af;
|
|
}
|
|
|
|
/* FOCUS STATE */
|
|
.wrapper-login input:focus {
|
|
border-color: #1f3a3d;
|
|
box-shadow: 0 0 0 3px rgba(31, 58, 61, 0.12);
|
|
}
|
|
|
|
/* HOVER nhẹ */
|
|
.wrapper-login input:hover {
|
|
border-color: #cbd5e1;
|
|
}
|
|
.wrapper-login .view-pass-btn {
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
</style>
|