Changes Update User

This commit is contained in:
Thien Pham Van
2026-02-24 11:06:46 +07:00
parent 24dfaf52c4
commit 06f78f95d7
9 changed files with 675 additions and 536 deletions

View File

@@ -49,24 +49,24 @@
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal"></Modal> <Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal"></Modal>
</template> </template>
<script setup> <script setup>
import { useStore } from "@/stores/index"; import { useStore } from '@/stores/index';
var props = defineProps({ var props = defineProps({
data: Array, data: Array,
info: Object, info: Object,
}); });
const { $getdata, $getapi, $createField, $clone, $getpage, $empty, $copyToClipboard, $find } = useNuxtApp(); const { $getdata, $getapi, $createField, $clone, $getpage, $empty, $copyToClipboard, $find, $path } = useNuxtApp();
const store = useStore(); const store = useStore();
var pagename = "pagedata99"; var pagename = 'pagedata99';
var pagedata = ref(); var pagedata = ref();
pagedata.value = $getpage(); pagedata.value = $getpage();
store.commit(pagename, pagedata); store.commit(pagename, pagedata);
let list = ["LogEntry", "Permission", "ContentType", "Session", "Group"]; let list = ['LogEntry', 'Permission', 'ContentType', 'Session', 'Group'];
var current = ref({ fields: [] }); var current = ref({ fields: [] });
var tabs = [ var tabs = [
{ code: "datatype", name: "Kiểu dữ liệu" }, { code: 'datatype', name: 'Kiểu dữ liệu' },
{ code: "table", name: "Dữ liệu" }, { code: 'table', name: 'Dữ liệu' },
]; ];
var tab = ref("datatype"); var tab = ref('datatype');
var datatable = ref(); var datatable = ref();
var query = ref(); var query = ref();
var values, filter; var values, filter;
@@ -78,11 +78,11 @@ function changeMenu(v) {
values = undefined; values = undefined;
filter = undefined; filter = undefined;
current.value = v; current.value = v;
if (tab.value === "table") loadData(); if (tab.value === 'table') loadData();
} }
async function changeTab(v) { async function changeTab(v) {
tab.value = v.code; tab.value = v.code;
if (v.code === "table") loadData(); if (v.code === 'table') loadData();
} }
async function loadData() { async function loadData() {
let vfilter = filter ? filter.trim() : undefined; let vfilter = filter ? filter.trim() : undefined;
@@ -90,26 +90,26 @@ async function loadData() {
try { try {
vfilter = JSON.parse(vfilter); vfilter = JSON.parse(vfilter);
} catch (error) { } catch (error) {
alert("Cấu trúc filter có lỗi"); alert('Cấu trúc filter có lỗi');
vfilter = undefined; vfilter = undefined;
} }
} }
let params = { values: values ? values.trim() : undefined, filter: filter }; let params = { values: values ? values.trim() : undefined, filter: filter };
let modelName = current.value.model; let modelName = current.value.model;
let found = { let found = {
name: modelName.toLowerCase().replace("_", ""), name: modelName.toLowerCase().replace('_', ''),
url: `data/${modelName}/`, url: `data/${modelName}/`,
url_detail: `data-detail/${modelName}/`, url_detail: `data-detail/${modelName}/`,
params: params, params: params,
}; };
query.value = $clone(found); query.value = $clone(found);
let rs = await $getapi([found]); let rs = await $getapi([found]);
if (rs === "error") return alert("Đã xảy ra lỗi, hãy xem lại câu lệnh."); if (rs === 'error') return alert('Đã xảy ra lỗi, hãy xem lại câu lệnh.');
datatable.value = rs[0].data.rows; datatable.value = rs[0].data.rows;
showData(); showData();
// api query // api query
const baseUrl = "https://api.y99.vn/" + `${query.value.url}`; const baseUrl = $path + `${query.value.url}`;
apiUrl.value = baseUrl; apiUrl.value = baseUrl;
let vparams = !$empty(values) ? { values: values } : null; let vparams = !$empty(values) ? { values: values } : null;
if (!$empty(filter)) { if (!$empty(filter)) {
@@ -125,15 +125,15 @@ async function loadData() {
function showData() { function showData() {
let arr = []; let arr = [];
if (!$empty(values)) { if (!$empty(values)) {
let arr1 = values.trim().split(","); let arr1 = values.trim().split(',');
arr1.map((v) => { arr1.map((v) => {
let val = v.trim(); let val = v.trim();
let field = $createField(val, val, "string", true); let field = $createField(val, val, 'string', true);
arr.push(field); arr.push(field);
}); });
} else { } else {
current.value.fields.map((v) => { current.value.fields.map((v) => {
let field = $createField(v.name, v.name, "string", true); let field = $createField(v.name, v.name, 'string', true);
arr.push(field); arr.push(field);
}); });
} }
@@ -148,10 +148,10 @@ function copy() {
} }
function openModel(x) { function openModel(x) {
showmodal.value = { showmodal.value = {
component: "datatable/ModelInfo", component: 'datatable/ModelInfo',
title: x.model, title: x.model,
width: "70%", width: '70%',
height: "600px", height: '600px',
vbind: { data: data, info: $find(data, { model: x.model }) }, vbind: { data: data, info: $find(data, { model: x.model }) },
}; };
} }

View File

@@ -1,28 +1,28 @@
<template> <template>
<div class="mx-2"> <div class="mx-2 has-text-centered">
<a class="ml-5" @click="resetPassword"> <a @click="resetPassword">
<SvgIcon v-bind="{ name: 'padlock.png', type: 'gray', size: 20 }"></SvgIcon> <SvgIcon v-bind="{ name: 'padlock.png', type: 'gray', size: 20 }"></SvgIcon>
</a> </a>
</div> </div>
</template> </template>
<script setup> <script setup>
const { $store } = useNuxtApp(); const { $store, $isVietnamese } = useNuxtApp();
const emit = defineEmits(["clickevent"]); const emit = defineEmits(['clickevent']);
var props = defineProps({ var props = defineProps({
appid: Number, appid: Number,
row: Object, row: Object,
}); });
function resetPassword() { function resetPassword() {
emit("clickevent", { emit('clickevent', {
name: "dataevent", name: 'dataevent',
data: { data: {
modal: { modal: {
title: $store.lang === "en" ? "Reset password" : "Đặt lại mật khẩu", title: $isVietnamese() ? 'Đặt lại mật khẩu' : 'Reset password',
height: "300px", height: '300px',
width: "800px", width: '800px',
component: "user/ResetPassword", component: 'user/ResetPassword',
vbind: { row: props.row }, vbind: { row: props.row },
}, },
}, },

View File

@@ -1,91 +1,125 @@
<template> <template>
<div class="px-3"> <div class="px-3">
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
<label class="label">Username<b class="ml-1 has-text-danger">*</b></label> <label class="label"
<div class="control"> >{{ isVietnamese ? 'Tên tài khoản' : 'Username' }}<b class="ml-1 has-text-danger">*</b></label
<input class="input" type="text" placeholder="" v-model="username"> >
</div> <div class="control">
<p class="help is-danger" v-if="errors.find(v=>v.name==='username')">{{errors.find(v=>v.name==='username').text}}</p> <input class="input" type="text" placeholder="" v-model="username" />
<p class="help is-primary" v-else-if="info">{{info}}</p> </div>
</div> <p class="help is-danger" v-if="errors.find((v) => v.name === 'username')">
<div class="field" v-if="!dealer"> {{ errors.find((v) => v.name === 'username').text }}
<label class="label">Account type<b class="ml-1 has-text-danger">*</b></label> </p>
<div class="control"> <p class="help is-primary" v-else-if="info">{{ info }}</p>
<SearchBox v-bind="{api:'usertype', field:'name', column:['name'], first:true, position: 'top'}" </div>
@option="selected('_type', $event)"></SearchBox> <div class="field" v-if="!dealer">
</div> <label class="label"
<p class="help is-danger" v-if="errors.find(v=>v.name==='type')">{{errors.find(v=>v.name==='type').text}}</p> >{{ isVietnamese ? 'Loại tài khoản' : 'Account type' }}<b class="ml-1 has-text-danger">*</b></label
</div> >
</div> <div class="control">
</div> <SearchBox
<div class="field is-horizontal mt-4"> v-bind="{ api: 'usertype', field: 'name', column: ['name'], first: true, position: 'top' }"
<div class="field-body"> @option="selected('_type', $event)"
<div class="field"> ></SearchBox>
<label class="label">Full name<b class="ml-1 has-text-danger">*</b></label> </div>
<div class="control"> <p class="help is-danger" v-if="errors.find((v) => v.name === 'type')">
<input class="input" type="text" placeholder="" v-model="fullname"> {{ errors.find((v) => v.name === 'type').text }}
</div> </p>
<p class="help is-danger" v-if="errors.find(v=>v.name==='fullname')">{{errors.find(v=>v.name==='fullname').text}}</p> </div>
</div> </div>
<div class="field">
<label class="label">Email<b class="ml-1 has-text-danger">*</b></label>
<div class="control">
<input class="input" type="text" placeholder="" v-model="email">
</div>
<p class="help is-danger" v-if="errors.find(v=>v.name==='email')">{{errors.find(v=>v.name==='email').text}}</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-4">
<div class="field-body">
<div class="field">
<label class="label">Password<b class="ml-1 has-text-danger">*</b></label>
<div class="field has-addons">
<p class="control is-expanded">
<input class="input" :type="showpass? 'text' : 'password'" placeholder="At least 6 characters including letters and numbers." v-model="password">
</p>
<div class="control">
<a class="button" @click="showpass=!showpass">
<SvgIcon v-bind="{name: 'eye-off.svg', type: 'dark', size: 22}" v-if="showpass"></SvgIcon>
<SvgIcon v-bind="{name: 'view.svg', type: 'dark', size: 22}" v-else></SvgIcon>
</a>
</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">
<label class="label">Retype password<b class="ml-1 has-text-danger">*</b></label>
<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 class="field is-horizontal mt-4">
</div> <div class="field-body">
</div> <div class="field">
<div class="mt-5 pt-2"> <label class="label"
<button :class="`button is-primary has-text-white ${ loading? 'is-loading' : ''}`" @click="createAccount()" v-if="enable">Tạo tài khoản</button> >{{ isVietnamese ? 'Họ và tên' : 'Full name' }}<b class="ml-1 has-text-danger">*</b></label
</div> >
</div> <div class="control">
<input class="input" type="text" placeholder="" v-model="fullname" />
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'fullname')">
{{ errors.find((v) => v.name === 'fullname').text }}
</p>
</div>
<div class="field">
<label class="label">{{ isVietnamese ? 'Email' : 'Email' }}<b class="ml-1 has-text-danger">*</b></label>
<div class="control">
<input class="input" type="text" placeholder="" v-model="email" />
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'email')">
{{ errors.find((v) => v.name === 'email').text }}
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-4">
<div class="field-body">
<div class="field">
<label class="label">{{ isVietnamese ? 'Mật khẩu' : 'Password' }}<b class="ml-1 has-text-danger">*</b></label>
<div class="field has-addons">
<p class="control is-expanded">
<input
class="input"
:type="showpass ? 'text' : 'password'"
placeholder="At least 6 characters including letters and numbers."
v-model="password"
/>
</p>
<div class="control">
<a class="button" @click="showpass = !showpass">
<SvgIcon v-bind="{ name: 'eye-off.svg', type: 'dark', size: 22 }" v-if="showpass"></SvgIcon>
<SvgIcon v-bind="{ name: 'view.svg', type: 'dark', size: 22 }" v-else></SvgIcon>
</a>
</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">
<label class="label"
>{{ isVietnamese ? 'Nhập lại mật khẩu' : 'Retype password' }}<b class="ml-1 has-text-danger">*</b></label
>
<p class="control is-expanded">
<input class="input" :type="showpass ? 'text' : 'password'" placeholder="" v-model="retypePassword" />
</p>
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'retypePassword')">
{{ errors.find((v) => v.name === 'retypePassword').text }}
</p>
</div>
</div>
<div class="mt-5 pt-2">
<button
:class="`button is-primary has-text-white ${loading ? 'is-loading' : ''}`"
@click="createAccount()"
v-if="enable"
>
{{ isVietnamese ? 'Tạo tài khoản' : 'Create account' }}
</button>
</div>
</div>
</template> </template>
<script> <script>
import { useStore } from '~/stores/index' import { useStore } from '~/stores/index';
export default { export default {
setup() { setup() {
const store = useStore() const store = useStore();
return {store} return { store };
}, },
props: ['pagename', 'row', 'api', 'dealer'], props: ['pagename', 'row', 'api', 'dealer'],
data () { data() {
return { return {
isVietnamese: this.$isVietnamese(),
fullname: undefined, fullname: undefined,
username: undefined, username: undefined,
email: undefined, email: undefined,
password: undefined, password: undefined,
retypePassword: undefined, retypePassword: undefined,
errors: [], errors: [],
info: 'User name must not contain any spaces.', info: undefined,
showpass: true, showpass: true,
hash: undefined, hash: undefined,
status: undefined, status: undefined,
@@ -97,140 +131,175 @@ export default {
check: {}, check: {},
branchOpt: undefined, branchOpt: undefined,
enable: true, enable: true,
loading: false loading: false,
} };
}, },
created() { created() {
if(!this.row) return if (!this.row) return;
this.fullname = this.row.fullname this.fullname = this.row.fullname;
this.email = this.row.email this.email = this.row.email;
if(this.row.code) this.username = this.row.code.toLocaleLowerCase() if (this.row.code) this.username = this.row.code.toLocaleLowerCase();
}, },
mounted() { mounted() {
let pass = this.$id().toLocaleLowerCase() let pass = this.$id().toLocaleLowerCase();
this.password = pass this.password = pass;
this.retypePassword = pass this.retypePassword = pass;
window.addEventListener("keyup", (ev) => ev.key==='Enter' && this.$route.name==='signup'? this.createAccount() : false) window.addEventListener('keyup', (ev) =>
ev.key === 'Enter' && this.$route.name === 'signup' ? this.createAccount() : false,
);
}, },
computed: { computed: {
registermethod: { registermethod: {
get: function() {return this.store.registermethod}, get: function () {
set: function(val) {this.$store.commit("updateRegisterMethod", {registermethod: val})} return this.store.registermethod;
},
set: function (val) {
this.$store.commit('updateRegisterMethod', { registermethod: val });
},
}, },
authmethod: { authmethod: {
get: function() {return this.store.authmethod}, get: function () {
set: function(val) {this.$store.commit("updateAuthMethod", {authmethod: val})} return this.store.authmethod;
},
set: function (val) {
this.$store.commit('updateAuthMethod', { authmethod: val });
},
}, },
authstatus: { authstatus: {
get: function() {return this.store.authstatus}, get: function () {
set: function(val) {this.$store.commit("updateAuthStatus", {authstatus: val})} return this.store.authstatus;
},
set: function (val) {
this.$store.commit('updateAuthStatus', { authstatus: val });
},
}, },
usertype: { usertype: {
get: function() {return this.store.usertype}, get: function () {
set: function(val) {this.$store.commit("updateUserType", {usertype: val})} return this.store.usertype;
},
set: function (val) {
this.$store.commit('updateUserType', { usertype: val });
},
}, },
dialog: { dialog: {
get: function() {return this.store['dialog']}, get: function () {
set: function(val) {this.$store.commit('updateStore', {name: 'dialog', data: val})} return this.store['dialog'];
} },
set: function (val) {
this.$store.commit('updateStore', { name: 'dialog', data: val });
},
},
}, },
methods: { methods: {
checkError () { checkError() {
this.errors = [] this.errors = [];
if (!this.$empty(this.fullname)) { this.fullname = this.fullname.trim() } if (!this.$empty(this.fullname)) {
if (!this.$empty(this.username)) { this.username = this.username.trim().toLowerCase() } this.fullname = this.fullname.trim();
}
if (!this.$empty(this.username)) {
this.username = this.username.trim().toLowerCase();
}
if (this.$empty(this.fullname)) { if (this.$empty(this.fullname)) {
this.errors.push({ name: 'fullname', text: 'Họ và tên không được bỏ trống' }) this.errors.push({ name: 'fullname', text: 'Họ và tên không được bỏ trống' });
} else if (this.fullname.length < 5) { } 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' }) 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.username)) { if (this.$empty(this.username)) {
this.errors.push({ name: 'username', text: 'Tài khoản không được bỏ trống' }) this.errors.push({ name: 'username', text: 'Tài khoản không được bỏ trống' });
} else if (this.username!==this.username.replace(' ', '')) { } else if (this.username !== this.username.replace(' ', '')) {
this.errors.push({ name: 'username', text: 'Tài khoản không được chứa khoảng trắng' }) this.errors.push({ name: 'username', text: 'Tài khoản không được chứa khoảng trắng' });
} else if(this.username.length<5) { } else if (this.username.length < 5) {
this.errors.push({ name: 'fullname', text: 'Tài khoản quá ngắn. Yêu cầu từ 5 tự trở nên' }) this.errors.push({ name: 'fullname', text: 'Tài khoản quá ngắn. Yêu cầu từ 5 kí tự trở nên' });
} }
if (this.$empty(this.password)) { if (this.$empty(this.password)) {
this.errors.push({ name: 'password', text: 'Mật khẩu không được bỏ trống' }) this.errors.push({ name: 'password', text: 'Mật khẩu không được bỏ trống' });
} else if (this.password.length < 6) { } else if (this.password.length < 6) {
this.errors.push({ name: 'password', text: 'Mật khẩu gồm 6 tự trở nên bao gồm chữ số ' }) this.errors.push({ name: 'password', text: 'Mật khẩu gồm 6 kí tự trở nên bao gồm chữ và số ' });
} else if (!(/\d/.test(this.password) && /[a-zA-Z]/.test(this.password))) { } else if (!(/\d/.test(this.password) && /[a-zA-Z]/.test(this.password))) {
this.errors.push({ name: 'password', text: 'Mật khẩu gồm 6 tự trở nên bao gồm chữ số ' }) this.errors.push({ name: 'password', text: 'Mật khẩu gồm 6 kí tự trở nên bao gồm chữ và số ' });
} }
if (this.$empty(this.retypePassword)) { 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' }) this.errors.push({ name: 'retypePassword', text: 'Nhắc lại mật khẩu không được bỏ trống' });
} else if (this.password !== this.retypePassword) { } else if (this.password !== 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' }) 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.$empty(this.email)) { if (!this.$empty(this.email)) {
this.email = this.email.trim() this.email = this.email.trim();
if(this.$errEmail(this.email)) this.errors.push({ name: 'email', text: 'Email không hợp lệ.' }) if (this.$errEmail(this.email)) this.errors.push({ name: 'email', text: 'Email không hợp lệ.' });
} else { } else {
this.errors.push({ name: 'email', text: 'Email không được bỏ trống.' }) this.errors.push({ name: 'email', text: 'Email không được bỏ trống.' });
} }
if(this.$empty(this.option)) { if (this.$empty(this.option)) {
if(this.dealer) { if (this.dealer) {
this.option = {id: 3} this.option = { id: 3 };
} else this.errors.push({ name: 'type', text: 'Chưa chọn loại tài khoản.' }) } else this.errors.push({ name: 'type', text: 'Chưa chọn loại tài khoản.' });
} }
let opts = this.radio==='all'? 'all' : [] let opts = this.radio === 'all' ? 'all' : [];
if(opts.length===0) { if (opts.length === 0) {
for (const [key, value] of Object.entries(this.check)) { for (const [key, value] of Object.entries(this.check)) {
if(value) opts.push(key) if (value) opts.push(key);
} }
} }
return this.errors.length>0? true : false return this.errors.length > 0 ? true : false;
}, },
async createAccount() { async createAccount() {
this.loading = true this.loading = true;
if(this.checkError()) return this.loading = false if (this.checkError()) return (this.loading = false);
const response = await fetch(`${this.$getpath()}password/${this.password}/`) const response = await fetch(`${this.$getpath()}password/${this.password}/`);
if(!response.ok) { if (!response.ok) {
throw new Error(`Response status: ${response.status}`); throw new Error(`Response status: ${response.status}`);
} }
this.hash = await response.json(); this.hash = await response.json();
let data = await this.$getdata('user', {username: this.username}, undefined, true) let data = await this.$getdata('user', { username: this.username }, undefined, true);
if(data) { if (data) {
this.loading = false this.loading = false;
return this.errors.push({name: 'username', text: 'Tài khoản đã tồn tại trong hệ thống'}) return this.errors.push({ name: 'username', text: 'Tài khoản đã tồn tại trong hệ thống' });
} }
let found = this.$findapi('user') let found = this.$findapi('user');
data = { fullname: this.fullname, username: this.username, phone: this.$empty(this.phone)? undefined : this.phone, password: data = {
this.hash, type: this.option.id, register_method: 1, auth_status: 2, auth_method: 1, email: this.email} fullname: this.fullname,
this.user = await this.$insertrow('user', data, found.params.values, this.pagename) username: this.username,
if(this.user==='error') return this.loading = false phone: this.$empty(this.phone) ? undefined : this.phone,
if(this.row) { password: this.hash,
let copy = this.$copy(this.row) type: this.option.id,
copy.user = this.user.id register_method: 1,
await this.$updaterow(this.api, copy, found.params.values, this.pagename) auth_status: 2,
auth_method: 1,
email: this.email,
};
this.user = await this.$insertrow('user', data, found.params.values, this.pagename);
if (this.user === 'error') return (this.loading = false);
if (this.row) {
let copy = this.$copy(this.row);
copy.user = this.user.id;
await this.$updaterow(this.api, copy, found.params.values, this.pagename);
} else { } else {
//send email //send email
if(this.email) { if (this.email) {
let content = `<p>Xin chào ${this.fullname}, </p>` let content = `<p>Xin chào ${this.fullname}, </p>`;
content += '<p>Tài khoản đăng nhập của bạn đã được khởi tạo và sẵn sàng sử dụng:</p>' content += '<p>Tài khoản đăng nhập của bạn đã được khởi tạo và sẵn sàng sử dụng:</p>';
content += `<p>Username: ${this.username}</p>` content += `<p>Username: ${this.username}</p>`;
content += `<p>Password: ${this.password}</p>` content += `<p>Password: ${this.password}</p>`;
content += `<p>Đội ngũ Utopia.</p>` content += `<p>Đội ngũ Utopia.</p>`;
let info = {subject: 'Tài khoản đăng nhập Utopia', to: this.email, sender: 1, content: content} let info = { subject: 'Tài khoản đăng nhập Utopia', to: this.email, sender: 1, content: content };
let rs = await this.$insertapi('sendemail', info) let rs = await this.$insertapi('sendemail', info);
} }
} }
if(this.dealer) { if (this.dealer) {
let copy = this.$copy(this.row) let copy = this.$copy(this.row);
copy.user = this.user.id copy.user = this.user.id;
await this.$updaterow('dealer', copy, null, this.pagename) await this.$updaterow('dealer', copy, null, this.pagename);
} }
this.loading = false this.loading = false;
this.$dialog('Tạo tài khoản thành công.', 'Thành công', 'Success', 10) this.$dialog('Tạo tài khoản thành công.', 'Thành công', 'Success', 10);
this.$emit('close') this.$emit('close');
}, },
selected(attr, obj) { selected(attr, obj) {
this.option = obj this.option = obj;
}, },
doCheck(v) { doCheck(v) {
this.$set(this.check, v.code, this.check[v.code]? false : true) this.$set(this.check, v.code, this.check[v.code] ? false : true);
} },
} },
} };
</script> </script>

View File

@@ -1,52 +1,67 @@
<template> <template>
<div v-if="record"> <div v-if="record">
<div class="columns is-multiline mx-0"> <div class="columns is-multiline mx-0">
<div class="column is-4"> <div class="column is-4">
<div class="field"> <div class="field">
<label class="label">Username<b class="ml-1 has-text-danger">*</b></label> <label class="label">{{isVietnamese ? 'Tên tài khoản' : 'Username'}}<b class="ml-1 has-text-danger">*</b></label>
<div class="control"> <div class="control">
<input class="input" type="text" placeholder="" v-model="record.username" disabled> <input class="input" type="text" placeholder="" v-model="record.username" disabled />
</div> </div>
</div> </div>
</div> </div>
<div class="column is-4"> <div class="column is-4">
<div class="field"> <div class="field">
<label class="label">Full name<b class="ml-1 has-text-danger">*</b></label> <label class="label">{{isVietnamese ? 'Họ tên' : 'Full name'}}<b class="ml-1 has-text-danger">*</b></label>
<div class="control"> <div class="control">
<input class="input" type="text" placeholder="" v-model="record.fullname"> <input class="input" type="text" placeholder="" v-model="record.fullname" />
</div> </div>
<p class="help is-danger" v-if="errors.fullname">{{errors.fullname}}</p> <p class="help is-danger" v-if="errors.fullname">{{ errors.fullname }}</p>
</div> </div>
</div> </div>
<div class="column is-4"> <div class="column is-4">
<div class="field"> <div class="field">
<label class="label">Account type<b class="ml-1 has-text-danger">*</b></label> <label class="label">{{isVietnamese ? 'Loại tài khoản' : 'Account type'}}<b class="ml-1 has-text-danger">*</b></label>
<div class="control"> <div class="control">
<SearchBox v-bind="{api:'usertype', field:'name', column:['name'], first:true, optionid:record.type, disabled: true}" <SearchBox
@option="selected('_type', $event)"></SearchBox> v-bind="{
</div> api: 'usertype',
<p class="help is-danger" v-if="errors.type">{{errors.type}}</p> field: 'name',
</div> column: ['name'],
</div> first: true,
<div class="column is-4"> optionid: record.type,
<div class="field"> disabled: false,
<label class="label">Status<b class="ml-1 has-text-danger">*</b></label> }"
<div class="control"> @option="selected('_type', $event)"
<p class="mt-4"> ></SearchBox>
<span class="mr-4" v-for="(v,i) in option"> </div>
<a class="icon-text" @click="radioBlocked=v"> <p class="help is-danger" v-if="errors.type">{{ errors.type }}</p>
<SvgIcon v-bind="{name: `radio-${radioBlocked.code===v.code? '' : 'un'}checked.svg`, type: 'gray', size: 22}"></SvgIcon> </div>
<span>{{ v.name }}</span> </div>
</a> <div class="column is-4">
</span> <div class="field">
</p> <label class="label">{{ isVietnamese ? 'Trạng thái' : 'Status' }}<b class="ml-1 has-text-danger">*</b></label>
<div class="control">
<p class="mt-4">
<span class="mr-4" v-for="(v, i) in option">
<a class="icon-text" @click="radioBlocked = v">
<SvgIcon
v-bind="{
name: `radio-${radioBlocked?.code === v.code ? '' : 'un'}checked.svg`,
type: 'gray',
size: 22,
}"
></SvgIcon>
<span>{{ v.name }}</span>
</a>
</span>
</p>
</div>
</div>
</div> </div>
</div> </div>
</div>
</div>
<div class="mt-5"> <div class="mt-5">
<button class="button is-primary has-text-white" @click="update()">Update</button> <button class="button is-primary has-text-white" @click="update()">{{isVietnamese ? 'Cập nhật' : 'Update'}}</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
@@ -54,58 +69,80 @@ export default {
props: ['pagename', 'row'], props: ['pagename', 'row'],
data() { data() {
return { return {
isVietnamese: this.$isVietnamese(),
errors: {}, errors: {},
record: this.row? this.$copy(this.row) : undefined, record: this.row ? this.$copy(this.row) : undefined,
radioBlocked: undefined, radioBlocked: undefined,
branch: [], branch: [],
check: {}, check: {},
branchOpt: undefined, branchOpt: undefined,
option: [{code: 0, name: 'Active'}, {code: 1, name: 'Blocked'}] option: [
} { code: 0, name: 'Active' },
{ code: 1, name: 'Blocked' },
],
};
}, },
computed: {
option() {
return [
{ code: 0, name: this.isVietnamese ? 'Hoạt động' : 'Active' },
{ code: 1, name: this.isVietnamese ? 'Khóa' : 'Blocked' },
];
},
},
async created() { async created() {
this.record = await this.$getdata('user', undefined, {filter: {id: this.row.id}}, true) this.record = await this.$getdata('user', undefined, { filter: { id: this.row.id } }, true);
this.radioBlocked = this.record.blocked? this.option[1] : this.option[0]
this.radioBlocked = this.record.blocked ? this.option[1] : this.option[0];
console.log(this.radioBlocked);
}, },
methods: { methods: {
selected(attr, obj) { selected(attr, obj) {
this.record[attr] = obj this.record[attr] = obj;
}, },
checkError () { checkError() {
this.errors = [] this.errors = [];
if (!this.$empty(this.record.fullname)) { this.record.fullname = this.record.fullname.trim() } if (!this.$empty(this.record.fullname)) {
if (!this.$empty(this.record.username)) { this.record.username = this.record.username.trim().toLowerCase() } this.record.fullname = this.record.fullname.trim();
}
if (!this.$empty(this.record.username)) {
this.record.username = this.record.username.trim().toLowerCase();
}
if (this.$empty(this.record.fullname)) { if (this.$empty(this.record.fullname)) {
this.errors.fullname = 'Họ và tên không được bỏ trống' this.errors.fullname = 'Họ và tên không được bỏ trống';
} else if (this.record.fullname.length < 5) { } else if (this.record.fullname.length < 5) {
this.errors.fullname = 'Họ và tên quá ngắn. Yêu cầu từ 5 kí tự trở nên' this.errors.fullname = 'Họ và tên quá ngắn. Yêu cầu từ 5 kí tự trở nên';
} }
if (this.$empty(this.record.username)) { if (this.$empty(this.record.username)) {
this.errors.username = 'Tài khoản không được bỏ trống' this.errors.username = 'Tài khoản không được bỏ trống';
} else if (this.record.username!==this.record.username.replace(' ', '')) { } else if (this.record.username !== this.record.username.replace(' ', '')) {
this.errors.username = 'Tài khoản không được chứa khoảng trắng' this.errors.username = 'Tài khoản không được chứa khoảng trắng';
} else if(this.record.username.length<5) { } else if (this.record.username.length < 5) {
this.errors.fullname = 'Tài khoản quá ngắn. Yêu cầu từ 5 tự trở nên' this.errors.fullname = 'Tài khoản quá ngắn. Yêu cầu từ 5 kí tự trở nên';
} }
if(!(this.record._type || this.record.type)) this.errors.type = 'Chưa chọn loại tài khoản' if (!(this.record._type || this.record.type)) this.errors.type = 'Chưa chọn loại tài khoản';
return this.errors.length>0? true : false return this.errors.length > 0 ? true : false;
}, },
async update() { async update() {
if(this.checkError()) return if (this.checkError()) return;
if(this.record._type) this.record.type = this.record._type.id if (this.record._type) this.record.type = this.record._type.id;
this.record.blocked = this.radioBlocked.code this.record.blocked = this.radioBlocked.code;
let rs = await this.$updaterow('user', this.record, undefined, this.pagename) let rs = await this.$updaterow('user', this.record, undefined, this.pagename);
if(this.radioBlocked.code===1) this.setTokenExpiry() if (this.radioBlocked.code === 1) this.setTokenExpiry();
}, },
doCheck(v) { doCheck(v) {
this.$set(this.check, v.code, this.check[v.code]? false : true) this.$set(this.check, v.code, this.check[v.code] ? false : true);
}, },
async setTokenExpiry() { async setTokenExpiry() {
let rows = await this.$getdata('token', {user: this.record.id, expiry: 0}) let rows = await this.$getdata('token', { user: this.record.id, expiry: 0 });
if(rows.length===0) return if (rows.length === 0) return;
rows.map(v=>v.expiry = 1) rows.map((v) => (v.expiry = 1));
this.$insertapi('token', rows, undefined, true) this.$insertapi('token', rows, undefined, true);
} },
} },
} mounted() {
</script> console.log('User:', this.radioBlocked);
},
};
</script>

View File

@@ -3,13 +3,13 @@
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-body columns"> <div class="field-body columns">
<div class="field column"> <div class="field column">
<label class="label">Username</label> <label class="label">{{ isVietnamese ? 'Tên tài khoản' : 'Username' }}</label>
<div class="control"> <div class="control">
<p>{{ props.row.username }}</p> <p>{{ props.row.username }}</p>
</div> </div>
</div> </div>
<div class="field column"> <div class="field column">
<label class="label">{{ isVietnamese ? "Loại tài khoản" : "Account type" }}</label> <label class="label">{{ isVietnamese ? 'Loại tài khoản' : 'Account type' }}</label>
<div class="control"> <div class="control">
<p>{{ isVietnamese ? props.row.type__name : props.row.type__en }}</p> <p>{{ isVietnamese ? props.row.type__name : props.row.type__en }}</p>
</div> </div>
@@ -19,7 +19,7 @@
<div class="field is-horizontal mt-4"> <div class="field is-horizontal mt-4">
<div class="field-body columns"> <div class="field-body columns">
<div class="field column"> <div class="field column">
<label class="label">{{ isVietnamese ? "Họ tên" : "Full name" }}</label> <label class="label">{{ isVietnamese ? 'Họ tên' : 'Full name' }}</label>
<div class="control"> <div class="control">
<p>{{ props.row.fullname }}</p> <p>{{ props.row.fullname }}</p>
</div> </div>
@@ -34,15 +34,15 @@
</div> </div>
<div class="mt-5 pt-2"> <div class="mt-5 pt-2">
<button :class="`button is-primary has-text-white ${showModal.loading ? 'is-loading' : ''}`" @click="showConfirm"> <button :class="`button is-primary has-text-white ${showModal.loading ? 'is-loading' : ''}`" @click="showConfirm">
{{ isVietnamese ? "Đặt lại mật khẩu" : "Reset password" }} {{ isVietnamese ? 'Đặt lại mật khẩu' : 'Reset password' }}
</button> </button>
</div> </div>
<Modal v-bind="showModal" v-if="showModal.show" @close="showModal.show = false"> </Modal> <Modal v-bind="showModal" v-if="showModal.show" @close="showModal.show = false"> </Modal>
</div> </div>
</template> </template>
<script setup> <script setup>
const { $store, $id, $insertapi, $getdata, $getpath, $updateapi, $dialog, $snackbar } = useNuxtApp(); const { $store, $id, $insertapi, $getdata, $getpath, $updateapi, $dialog, $snackbar, $isVietnamese } = useNuxtApp();
const emit = defineEmits(["close"]); const emit = defineEmits(['close']);
var props = defineProps({ var props = defineProps({
row: Object, row: Object,
}); });
@@ -52,47 +52,51 @@ const showModal = ref({
loading: false, loading: false,
}); });
const isVietnamese = computed(() => $store.lang.toLowerCase() === "vi"); const isVietnamese = computed(() => $isVietnamese());
let pass = $id().toLocaleLowerCase(); let pass = $id().toLocaleLowerCase();
async function handlerResetPassword() { async function handlerResetPassword() {
showModal.value.loading = true; showModal.value.loading = true;
let user = await $getdata("user", undefined, { filter: { id: props.row.id } }, true); let user = await $getdata('user', undefined, { filter: { id: props.row.id } }, true);
const response = await fetch(`${$getpath()}password/${pass}/`); const response = await fetch(`${$getpath()}password/${pass}/`);
let hash = await response.json(); let hash = await response.json();
user.password = hash; user.password = hash;
let rs1 = await $updateapi("user", user, null, false); let rs1 = await $updateapi('user', user, null, false);
if (rs1 === "error") { if (rs1 === 'error') {
$dialog("Có lỗi xảy ra. Hãy thử lại một lần nữa", "Lỗi", "Error"); $dialog('Có lỗi xảy ra. Hãy thử lại một lần nữa', 'Lỗi', 'Error');
} else { } else {
showModal.value.show = false; showModal.value.show = false;
let content = `<p>Xin chào ${props.row.fullname}, </p>`; let content = `<p>Xin chào ${props.row.fullname}, </p>`;
content += "<p>Mật khẩu tài khoản của bạn trên hệ thống đã được đặt lại thành công.</p>"; content += '<p>Mật khẩu tài khoản của bạn trên hệ thống đã được đặt lại thành công.</p>';
content += "<p>Tài khoản đăng nhập của bạn:</p>"; content += '<p>Tài khoản đăng nhập của bạn:</p>';
content += `<p>Username: ${props.row.username}</p>`; content += `<p>Username: ${props.row.username}</p>`;
content += `<p>Password: ${pass}</p>`; content += `<p>Password: ${pass}</p>`;
content += `<p>Đội ngũ Utopia.</p>`; content += `<p>Đội ngũ Utopia.</p>`;
let info = { let info = {
subject: "Thông báo đặt lại mật khẩu tài khoản đăng nhập Utopia", subject: 'Thông báo đặt lại mật khẩu tài khoản đăng nhập Utopia',
to: props.row.email, to: props.row.email,
sender: 1, sender: 1,
content: content, content: content,
}; };
let rs = await $insertapi("sendemail", info, null, false); let rs = await $insertapi('sendemail', info, null, false);
showModal.value.loading = false; showModal.value.loading = false;
$snackbar("Mật khẩu đã được đặt lại thành công. Thông tin đã được gửi tới địa chỉ email đã đăng ký.", "Thành công", "Success"); $snackbar(
emit("close"); 'Mật khẩu đã được đặt lại thành công. Thông tin đã được gửi tới địa chỉ email đã đăng ký.',
'Thành công',
'Success',
);
emit('close');
} }
} }
function showConfirm() { function showConfirm() {
showModal.value = { showModal.value = {
show: true, show: true,
title: !isVietnamese ? "Confirm password reset" : "Xác nhận đặt lại mật khẩu", title: !isVietnamese ? 'Confirm password reset' : 'Xác nhận đặt lại mật khẩu',
height: "300px", height: '300px',
width: "800px", width: '800px',
component: "dialog/Confirm", component: 'dialog/Confirm',
vbind: { vbind: {
content: `Bạn có muốn đặt lại mật khẩu cho tài khoản ${props.row.username?.toUpperCase()} này không?`, content: `Bạn có muốn đặt lại mật khẩu cho tài khoản ${props.row.username?.toUpperCase()} này không?`,
duration: 10, duration: 10,

View File

@@ -21,10 +21,10 @@
</template> </template>
<script setup> <script setup>
const {$createMeta} = useNuxtApp() const {$createMeta} = useNuxtApp()
const text = 'Rất tiếc! Có vẻ như đã xảy ra sự cố. Vui lòng thử lại hoặc liên hệ support@y99.vn nếu vấn đề vẫn tiếp diễn. Xin cảm ơn!' const text = 'Rất tiếc! Có vẻ như đã xảy ra sự cố. Vui lòng thử lại hoặc liên hệ bộ phận hỗ trợ nếu vấn đề vẫn tiếp diễn. Xin cảm ơn!'
useHead( useHead(
$createMeta({title: 'Thông báo mới nhất | Y99', description: text, $createMeta({title: 'Thông báo mới nhất | Utopia', description: text,
image: '/logo.png', type: 'website', image: '/logo.png', type: 'website',
keywords: 'Y99'}) keywords: 'Utopia'})
) )
</script> </script>

View File

@@ -39,9 +39,9 @@ const changeTab = function(_tab, _subtab) {
let meta = { let meta = {
title: currentTab[store.lang], title: currentTab[store.lang],
image: undefined, image: undefined,
description: 'Y99 - Account', description: 'Utopia - Account',
type: 'article', type: 'article',
keywords: 'y99, account' keywords: 'Utopia, account'
} }
useHead($createMeta(meta)) useHead($createMeta(meta))
} }

View File

@@ -1,260 +1,282 @@
// nuxt 3 - plugins/my-plugin.ts // nuxt 3 - plugins/my-plugin.ts
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router';
import { useStore } from '~/stores/index' import { useStore } from '~/stores/index';
import dayjs from 'dayjs' import dayjs from 'dayjs';
import weekday from 'dayjs/plugin/weekday' import weekday from 'dayjs/plugin/weekday';
import weekOfYear from 'dayjs/plugin/weekOfYear' import weekOfYear from 'dayjs/plugin/weekOfYear';
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(weekday) dayjs.extend(weekday);
dayjs.extend(weekOfYear) dayjs.extend(weekOfYear);
dayjs.extend(relativeTime) dayjs.extend(relativeTime);
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
const route = useRoute() const route = useRoute();
const {$id, $empty} = useNuxtApp() const { $id, $empty } = useNuxtApp();
const store = useStore() const store = useStore();
const dialog = function(content, title, type, duration, width, height, vbind) { const dialog = function (content, title, type, duration, width, height, vbind) {
content = typeof content === 'string'? content : JSON.stringify(content) content = typeof content === 'string' ? content : JSON.stringify(content);
let vtitle = type==='Success'? `<span class="has-text-primary">${title}</span>` : title let vtitle = type === 'Success' ? `<span class="has-text-primary">${title}</span>` : title;
if(type==='Error') vtitle = `<span class="has-text-danger">${title}</span>` if (type === 'Error') vtitle = `<span class="has-text-danger">${title}</span>`;
let data = {id: $id() , component: `dialog/${type || 'Info'}`, let data = {
vbind: {content: content, duration: duration, vbind: vbind}, title: vtitle, width: width || '600px', height: height || '100px'} id: $id(),
store.commit('showmodal', data) component: `dialog/${type || 'Info'}`,
} vbind: { content: content, duration: duration, vbind: vbind },
title: vtitle,
width: width || '600px',
height: height || '100px',
};
store.commit('showmodal', data);
};
const snackbar = function(content, title, type, width, height) { const snackbar = function (content, title, type, width, height) {
content = typeof content == 'string'? content : JSON.stringify(content) content = typeof content == 'string' ? content : JSON.stringify(content);
let vtitle = type==='Success'? `<span class="has-text-primary">${title}</span>` : title let vtitle = type === 'Success' ? `<span class="has-text-primary">${title}</span>` : title;
if(type==='Error') vtitle = `<span class="has-text-danger">${title}</span>` if (type === 'Error') vtitle = `<span class="has-text-danger">${title}</span>`;
let data = {id: $id() , component: `snackbar/${type || 'Info'}`, let data = {
vbind: {content: content}, title: vtitle, width: width || '600px', height: height || '100px'} id: $id(),
store.commit('snackbar', data) component: `snackbar/${type || 'Info'}`,
} vbind: { content: content },
title: vtitle,
const getLink = function(val) { width: width || '600px',
if(val === undefined || val === null || val === '' || val==="") return '' height: height || '100px',
let json = val.indexOf('{')>=0? JSON.parse(val) : {path: val} };
return json store.commit('snackbar', data);
} };
const errPhone = function(phone) { const getLink = function (val) {
var text = undefined if (val === undefined || val === null || val === '' || val === '') return '';
let json = val.indexOf('{') >= 0 ? JSON.parse(val) : { path: val };
return json;
};
const errPhone = function (phone) {
var text = undefined;
if ($empty(phone)) { if ($empty(phone)) {
text = 'Số điện thoại di động không được bỏ trống.' text = 'Số điện thoại di động không được bỏ trống.';
} else if (isNaN(phone)) { } else if (isNaN(phone)) {
text = 'Số điện thoại di động không hợp lệ.' text = 'Số điện thoại di động không hợp lệ.';
} else if(phone.length<9 || phone.length>11) { } else if (phone.length < 9 || phone.length > 11) {
text = 'Số điện thoại di động phải từ 9-11 số.' text = 'Số điện thoại di động phải từ 9-11 số.';
} }
return text return text;
} };
const errEmail = function(email) { const errEmail = function (email) {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ const re =
var text = undefined /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var text = undefined;
if ($empty(email)) { if ($empty(email)) {
text = 'Email không được bỏ trống.' text = 'Email không được bỏ trống.';
} else if (!(re.test(String(email).toLowerCase()))) { } else if (!re.test(String(email).toLowerCase())) {
text = 'Email không hợp lệ.' text = 'Email không hợp lệ.';
} }
return text return text;
} };
const errPhoneEmail = function(contact) { const errPhoneEmail = function (contact) {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ const re =
var text = undefined /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var text = undefined;
if ($empty(contact)) { if ($empty(contact)) {
text = 'Số điện thoại di động hoặc Email không được bỏ trống.' text = 'Số điện thoại di động hoặc Email không được bỏ trống.';
} else if (!(re.test(String(contact).toLowerCase()) || !isNaN(contact))) { } else if (!(re.test(String(contact).toLowerCase()) || !isNaN(contact))) {
text = 'Số điện thoại di động hoặc Email không hợp lệ.' text = 'Số điện thoại di động hoặc Email không hợp lệ.';
} else if(!isNaN(contact) && (contact.length<9 || contact.length>11)) { } else if (!isNaN(contact) && (contact.length < 9 || contact.length > 11)) {
text = 'Số điện thoại di động không hợp lệ.' text = 'Số điện thoại di động không hợp lệ.';
} }
return text return text;
} };
const upload = function(file, type, user, convert) { const upload = function (file, type, user, convert) {
var fileFormat = [{type: 'image', format: ['.png', '.jpg', 'jpeg', '.bmp', '.gif', '.svg', '.webp']}, var fileFormat = [
{type: 'video', format: ['.wmv', '.avi', '.mp4', '.flv', '.mov', '.mpg', '.amv', '.rm']} { type: 'image', format: ['.png', '.jpg', 'jpeg', '.bmp', '.gif', '.svg', '.webp'] },
] { type: 'video', format: ['.wmv', '.avi', '.mp4', '.flv', '.mov', '.mpg', '.amv', '.rm'] },
var valid = undefined ];
if(type==='image' || type==='video') { var valid = undefined;
valid = false if (type === 'image' || type === 'video') {
let found = fileFormat.find(v=>v.type===type) valid = false;
found.format.map(x=>{ let found = fileFormat.find((v) => v.type === type);
if(file.name.toLowerCase().indexOf(x)>=0) valid = true found.format.map((x) => {
}) if (file.name.toLowerCase().indexOf(x) >= 0) valid = true;
});
} }
if(valid===false) return {name: file.name, error: true, text: 'Định dạng file không hợp lệ'} if (valid === false) return { name: file.name, error: true, text: 'Định dạng file không hợp lệ' };
if((type==='image' || type==='file') && file.size > 500*1024*1024) { if ((type === 'image' || type === 'file') && file.size > 500 * 1024 * 1024) {
return {name: file.name, error: true, text: 'Kích thước ' + (type==='image'? 'hình ảnh' : 'tài liệu') + ' phải dưới 500MB'} return {
} else if(type==='video' && file.size > 1073741274) { name: file.name,
return {name: file.name, error: true, text: 'Kích thước video phải dưới 1GB'} error: true,
text: 'Kích thước ' + (type === 'image' ? 'hình ảnh' : 'tài liệu') + ' phải dưới 500MB',
};
} else if (type === 'video' && file.size > 1073741274) {
return { name: file.name, error: true, text: 'Kích thước video phải dưới 1GB' };
} }
let data = new FormData() let data = new FormData();
let fileName = dayjs(new Date()).format("YYYYMMDDhhmmss") + '-' + nonAccent(file.name) let fileName = dayjs(new Date()).format('YYYYMMDDhhmmss') + '-' + nonAccent(file.name);
data.append('name', file.name) data.append('name', file.name);
data.append('filename', fileName) data.append('filename', fileName);
data.append('file', file) data.append('file', file);
data.append('type', type) data.append('type', type);
data.append('size', file.size) data.append('size', file.size);
data.append('user', user) data.append('user', user);
data.append('convert', convert) data.append('convert', convert);
return {form: data, type: type, size: file.size, file: file, name: file.name, filename: fileName} return { form: data, type: type, size: file.size, file: file, name: file.name, filename: fileName };
} };
const checkChange = function(obj1, obj2, list) { const checkChange = function (obj1, obj2, list) {
var change = false var change = false;
if(list) { if (list) {
list.map(v=>{if(obj1[v]!==obj2[v]) change = true}) list.map((v) => {
if (obj1[v] !== obj2[v]) change = true;
});
} else { } else {
for (var k in obj1 ) { for (var k in obj1) {
if(obj1[k]!==obj2[k]) change = true if (obj1[k] !== obj2[k]) change = true;
} }
} }
return change return change;
} };
const resetNull = function(obj) { const resetNull = function (obj) {
for (const [key, value] of Object.entries(obj)) { for (const [key, value] of Object.entries(obj)) {
if(typeof value==='string') { if (typeof value === 'string') {
let val = value.trim() let val = value.trim();
if(val==='' || val==="") val = null if (val === '' || val === '') val = null;
obj[key] = val obj[key] = val;
} }
} }
return obj return obj;
} };
const copyToClipboard = function(text) { const copyToClipboard = function (text) {
snackbar('Copied to clipboard') snackbar('Copied to clipboard');
if (window.clipboardData && window.clipboardData.setData) { if (window.clipboardData && window.clipboardData.setData) {
// IE specific code path to prevent textarea being shown while dialog is visible. // IE specific code path to prevent textarea being shown while dialog is visible.
return clipboardData.setData("Text", text); return clipboardData.setData('Text', text);
} else if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
} else if (document.queryCommandSupported && document.queryCommandSupported("copy")) { var textarea = document.createElement('textarea');
var textarea = document.createElement("textarea");
textarea.textContent = text; textarea.textContent = text;
textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge. textarea.style.position = 'fixed'; // Prevent scrolling to bottom of page in MS Edge.
document.body.appendChild(textarea); document.body.appendChild(textarea);
textarea.select(); textarea.select();
try { try {
return document.execCommand("copy"); // Security exception may be thrown by some browsers. return document.execCommand('copy'); // Security exception may be thrown by some browsers.
} catch (ex) { } catch (ex) {
console.warn("Copy to clipboard failed.", ex); console.warn('Copy to clipboard failed.', ex);
return false; return false;
} finally { } finally {
document.body.removeChild(textarea); document.body.removeChild(textarea);
} }
} }
} };
const nonAccent = function(str) { const nonAccent = function (str) {
if($empty(str)) return null if ($empty(str)) return null;
str = str.replaceAll('/', '-').replaceAll('%', '-').replaceAll('?', '-') str = str.replaceAll('/', '-').replaceAll('%', '-').replaceAll('?', '-');
str = str.toLowerCase(); str = str.toLowerCase();
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, "a"); str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, 'a');
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, "e"); str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, 'e');
str = str.replace(/ì|í|ị|ỉ|ĩ/g, "i"); str = str.replace(/ì|í|ị|ỉ|ĩ/g, 'i');
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, "o"); str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, 'o');
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, "u"); str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, 'u');
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, "y"); str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, 'y');
str = str.replace(/đ/g, "d"); str = str.replace(/đ/g, 'd');
// Some system encode vietnamese combining accent as individual utf-8 characters // Some system encode vietnamese combining accent as individual utf-8 characters
str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, ""); // Huyền sắc hỏi ngã nặng str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, ''); // Huyền sắc hỏi ngã nặng
str = str.replace(/\u02C6|\u0306|\u031B/g, ""); // Â, Ê, Ă, Ơ, Ư str = str.replace(/\u02C6|\u0306|\u031B/g, ''); // Â, Ê, Ă, Ơ, Ư
str = str.split(' ').filter(s => s).join('-') str = str
return str .split(' ')
} .filter((s) => s)
.join('-');
return str;
};
const linkID = function(link) { const linkID = function (link) {
link = link? link : route.params.slug link = link ? link : route.params.slug;
if($empty(link)) return if ($empty(link)) return;
let idx = link.lastIndexOf('-') let idx = link.lastIndexOf('-');
let id = (idx>-1 && idx<link.length-1)? link.substring(idx+1, link.length) : undefined let id = idx > -1 && idx < link.length - 1 ? link.substring(idx + 1, link.length) : undefined;
return id return id;
} };
const exportpdf = function(docid, name, size) { const exportpdf = function (docid, name, size) {
var element = document.getElementById(docid) var element = document.getElementById(docid);
let elms = element.querySelectorAll("[id='ignore']") let elms = element.querySelectorAll("[id='ignore']");
for(var i = 0; i < elms.length; i++) { for (var i = 0; i < elms.length; i++) {
elms[i].style.display='none'; elms[i].style.display = 'none';
} }
var opt = { var opt = {
margin: 0.2, margin: 0.2,
filename: `${name || 'file'}-${dayjs().format('YYYYMMDDHHmmss')}.pdf`, filename: `${name || 'file'}-${dayjs().format('YYYYMMDDHHmmss')}.pdf`,
image: { type: 'jpeg', quality: 1 }, image: { type: 'jpeg', quality: 1 },
html2canvas: { scale: 2, useCORS: true}, html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'in', format: size || 'a4', orientation: 'portrait' } jsPDF: { unit: 'in', format: size || 'a4', orientation: 'portrait' },
} };
html2pdf().set(opt).from(element).save() html2pdf().set(opt).from(element).save();
setTimeout(()=> { setTimeout(() => {
for(var i = 0; i < elms.length; i++) { for (var i = 0; i < elms.length; i++) {
elms[i].style.display='block'; elms[i].style.display = 'block';
} }
}, 1000) }, 1000);
return opt.filename return opt.filename;
} };
const exportImage = function(docid, name) { const exportImage = function (docid, name) {
var element = document.getElementById(docid) var element = document.getElementById(docid);
let elms = element.querySelectorAll("[id='ignore']") let elms = element.querySelectorAll("[id='ignore']");
for(var i = 0; i < elms.length; i++) { for (var i = 0; i < elms.length; i++) {
elms[i].style.display='none'; elms[i].style.display = 'none';
} }
let filename = `${name}-${dayjs().format('YYYYMMDDHHmmss')}.png` let filename = `${name}-${dayjs().format('YYYYMMDDHHmmss')}.png`;
html2canvas(element).then(canvas => { html2canvas(element).then((canvas) => {
const link = document.createElement("a"); const link = document.createElement('a');
link.href = canvas.toDataURL("image/png"); link.href = canvas.toDataURL('image/png');
link.download = filename; link.download = filename;
link.click(); link.click();
link.remove() link.remove();
}) });
setTimeout(()=> { setTimeout(() => {
for(var i = 0; i < elms.length; i++) { for (var i = 0; i < elms.length; i++) {
elms[i].style.display='block'; elms[i].style.display = 'block';
} }
}, 1000) }, 1000);
return filename return filename;
} };
const download = async function(url, fileName) { const download = async function (url, fileName) {
let name = dayjs(new Date()).format("YYYYMMDDhhmmss") + '-' + fileName let name = dayjs(new Date()).format('YYYYMMDDhhmmss') + '-' + fileName;
const response = await fetch(url, { method: "GET" }); const response = await fetch(url, { method: 'GET' });
const blob = await response.blob(); const blob = await response.blob();
const urlDownload = window.URL.createObjectURL(blob); const urlDownload = window.URL.createObjectURL(blob);
const link = document.createElement("a"); const link = document.createElement('a');
link.href = urlDownload; link.href = urlDownload;
link.setAttribute("download", name); link.setAttribute('download', name);
link.click(); link.click();
link.remove(); link.remove();
} };
const vnmoney = function(amount) { const vnmoney = function (amount) {
var readGroup = function (group) { var readGroup = function (group) {
let readDigit = [" Không", " Một", " Hai", " Ba", " Bốn", " Năm", " Sáu", " Bảy", " Tám", " Chín"]; let readDigit = [' Không', ' Một', ' Hai', ' Ba', ' Bốn', ' Năm', ' Sáu', ' Bảy', ' Tám', ' Chín'];
var temp = ""; var temp = '';
if (group == "000") return ""; if (group == '000') return '';
temp = readDigit[parseInt(group.substring(0, 1))] + " Trăm"; temp = readDigit[parseInt(group.substring(0, 1))] + ' Trăm';
if (group.substring(1, 2) == "0") if (group.substring(1, 2) == '0')
if (group.substring(2, 3) == "0") return temp; if (group.substring(2, 3) == '0') return temp;
else { else {
temp += " Lẻ" + readDigit[parseInt(group.substring(2, 3))]; temp += ' Lẻ' + readDigit[parseInt(group.substring(2, 3))];
return temp; return temp;
} }
else else temp += readDigit[parseInt(group.substring(1, 2))] + ' Mươi';
temp += readDigit[parseInt(group.substring(1, 2))] + " Mươi"; if (group.substring(2, 3) == '5') temp += ' Lăm';
if (group.substring(2, 3) == "5") temp += " Lăm"; else if (group.substring(2, 3) != '0') temp += readDigit[parseInt(group.substring(2, 3))];
else if (group.substring(2, 3) != "0") temp += readDigit[parseInt(group.substring(2, 3))];
return temp; return temp;
} };
var readMoney = function(num) { var readMoney = function (num) {
if ((num == null) || (num == "")) return ""; if (num == null || num == '') return '';
let temp = ""; let temp = '';
while (num.length < 18) { while (num.length < 18) {
num = "0" + num; num = '0' + num;
} }
let g1 = num.substring(0, 3); let g1 = num.substring(0, 3);
let g2 = num.substring(3, 6); let g2 = num.substring(3, 6);
@@ -262,110 +284,114 @@ export default defineNuxtPlugin(() => {
let g4 = num.substring(9, 12); let g4 = num.substring(9, 12);
let g5 = num.substring(12, 15); let g5 = num.substring(12, 15);
let g6 = num.substring(15, 18); let g6 = num.substring(15, 18);
if (g1 != "000") { if (g1 != '000') {
temp = readGroup(g1); temp = readGroup(g1);
temp += " Triệu"; temp += ' Triệu';
} }
if (g2 != "000") { if (g2 != '000') {
temp += readGroup(g2); temp += readGroup(g2);
temp += " Nghìn"; temp += ' Nghìn';
} }
if (g3 != "000") { if (g3 != '000') {
temp += readGroup(g3); temp += readGroup(g3);
temp += " Tỷ"; temp += ' Tỷ';
} else if ("" != temp) { } else if ('' != temp) {
temp += " Tỷ"; temp += ' Tỷ';
} }
if (g4 != "000") { if (g4 != '000') {
temp += readGroup(g4); temp += readGroup(g4);
temp += " Triệu"; temp += ' Triệu';
} }
if (g5 != "000") { if (g5 != '000') {
temp += readGroup(g5); temp += readGroup(g5);
temp += " Nghìn"; temp += ' Nghìn';
} }
temp = temp + readGroup(g6); temp = temp + readGroup(g6);
temp = temp.replaceAll("Một Mươi", "Mười"); temp = temp.replaceAll('Một Mươi', 'Mười');
temp = temp.trim(); temp = temp.trim();
temp = temp.replaceAll("Không Trăm", ""); temp = temp.replaceAll('Không Trăm', '');
temp = temp.trim(); temp = temp.trim();
temp = temp.replaceAll("Mười Không", "Mười"); temp = temp.replaceAll('Mười Không', 'Mười');
temp = temp.trim(); temp = temp.trim();
temp = temp.replaceAll("Mươi Không", "Mươi"); temp = temp.replaceAll('Mươi Không', 'Mươi');
temp = temp.trim(); temp = temp.trim();
if (temp.indexOf("Lẻ") == 0) temp = temp.substring(2); if (temp.indexOf('Lẻ') == 0) temp = temp.substring(2);
temp = temp.trim(); temp = temp.trim();
temp = temp.replaceAll("Mươi Một", "Mươi Mốt"); temp = temp.replaceAll('Mươi Một', 'Mươi Mốt');
temp = temp.trim(); temp = temp.trim();
let result = temp.substring(0, 1).toUpperCase() + temp.substring(1).toLowerCase(); let result = temp.substring(0, 1).toUpperCase() + temp.substring(1).toLowerCase();
return (result == "" ? "Không" : result) + " đồng chẵn" return (result == '' ? 'Không' : result) + ' đồng chẵn';
} };
return readMoney(amount.toString()) return readMoney(amount.toString());
} };
const lang = function(code) { const lang = function (code) {
let field = store.common.find((v) => v.code===code) let field = store.common.find((v) => v.code === code);
return field? field[store.lang] : '' return field ? field[store.lang] : '';
} };
const createMeta = function(metainfo) { const createMeta = function (metainfo) {
return { return {
title: metainfo.title, title: metainfo.title,
link: [ link: [
{ {
hid: 'canonical', hid: 'canonical',
rel: 'canonical', rel: 'canonical',
href: `https://y99.vn${route.path}` href: ``,
} },
], ],
meta: [ meta: [
{ {
hid: 'description', hid: 'description',
name: 'description', name: 'description',
content: metainfo.description || '' content: metainfo.description || '',
}, },
{ {
hid: 'og:title', hid: 'og:title',
property: 'og:title', property: 'og:title',
content: metainfo.title content: metainfo.title,
}, },
{ {
hid: 'og:description', hid: 'og:description',
property: 'og:description', property: 'og:description',
content: metainfo.description || '' content: metainfo.description || '',
}, },
{ {
hid: 'og:type', hid: 'og:type',
property: 'og:type', property: 'og:type',
content: metainfo.type || '' content: metainfo.type || '',
}, },
{ {
hid: 'og:image', hid: 'og:image',
property: 'og:image', property: 'og:image',
content: metainfo.image || '' content: metainfo.image || '',
}, },
{ {
hid: 'og:url', hid: 'og:url',
property: 'og:url', property: 'og:url',
content: `https://y99.vn${route.path}` content: ``,
}, },
{ {
property: 'og:locale', property: 'og:locale',
content: 'vi_VN' content: 'vi_VN',
}, },
{ {
hid: 'fb:app_id', hid: 'fb:app_id',
property: 'fb:app_id', property: 'fb:app_id',
content: metainfo.app_id || '549555567061256' content: metainfo.app_id || '',
}, },
{ {
hid: 'keywords', hid: 'keywords',
name: 'keywords', name: 'keywords',
content: metainfo.keywords || '' content: metainfo.keywords || '',
} },
] ],
} };
} };
const isVietnamese = () => {
return store.lang.toLowerCase() === 'vi';
};
return { return {
provide: { provide: {
@@ -387,7 +413,8 @@ export default defineNuxtPlugin(() => {
createMeta, createMeta,
download, download,
dayjs, dayjs,
lang lang,
} isVietnamese,
} },
}) };
});

View File

@@ -449,7 +449,9 @@ export default defineNuxtPlugin(() => {
deleterow, deleterow,
updatepage, updatepage,
store, store,
requestLogin requestLogin,
mode,
path
} }
} }
}) })