This commit is contained in:
Xuan Loi
2025-12-05 17:53:49 +07:00
commit 56f3509d4d
187 changed files with 30840 additions and 0 deletions

236
plugins/common.js Executable file
View File

@@ -0,0 +1,236 @@
import Vue from 'vue'
Vue.use( {
install(Vue){
Vue.prototype.$dialog = function(content, title, type, duration, width, height, vbind) {
if(typeof content == 'string') {
let vtitle = type==='Success'? `<span class="has-text-primary">${title}</span>` : title
if(type==='Error') vtitle = `<span class="has-text-danger">${title}</span>`
let data = {id: this.$id(), component: `dialog/${type || 'Info'}`,
vbind: {content: content, duration: duration, vbind: vbind}, title: vtitle, width: width || '500px', height: height || '100px'}
this.$store.commit('updateStore', {name: 'showmodal', data: data})
} else this.$store.commit('updateStore', {name: 'showmodal', data: content})
}
Vue.prototype.$snackbar = function(content, title, type, width, height) {
if(typeof content == 'string') {
let vtitle = type==='Success'? `<span class="has-text-primary">${title}</span>` : title
if(type==='Error') vtitle = `<span class="has-text-danger">${title}</span>`
let data = {id: this.$id(), component: `snackbar/${type || 'Info'}`,
vbind: {content: content}, title: vtitle, width: width || '400px', height: height || '100px'}
this.$store.commit('updateStore', {name: 'snackbar', data: data})
} else this.$store.commit('updateStore', {name: 'snackbar', data: content})
}
Vue.prototype.$pending = function() {
this.$dialog({width: '500px', icon: ' mdi mdi-wrench-clock',
content: '<p class="fs-16">Chức năng này đang được xây dựng, vui lòng trở lại sau</p>', type: 'is-dark', progress:true, duration: 5})
}
Vue.prototype.$getLink = function(val) {
if(val === undefined || val === null || val === '' || val==="") return ''
let json = val.indexOf('{')>=0? JSON.parse(val) : {path: val}
return json
}
Vue.prototype.$timeFormat = function(startDate, endDate) {
let milliseconds = startDate - endDate
let secs = Math.floor(Math.abs(milliseconds) / 1000);
let mins = Math.floor(secs / 60);
let hours = Math.floor(mins / 60);
let days = Math.floor(hours / 24);
const millisecs = Math.floor(Math.abs(milliseconds)) % 1000;
function pad2(n) { return (n < 10 ? '0' : '') + n }
let display = undefined
if(days>=1) {
display = pad2(startDate.getHours()) + ':' + pad2(startDate.getMinutes()) + ' ' + pad2(startDate.getDate()) + '/' + pad2(startDate.getMonth())
}
else if(hours>0) display = hours + 'h trước'
else if(mins>0) display = mins + "' trước"
else if(secs>0 || millisecs>0) display = 'Vừa xong'
return {
days: days,
hours: hours % 24,
minutes: mins % 60,
seconds: secs % 60,
milliSeconds: millisecs,
display: display
}
}
Vue.prototype.$errPhone = function(phone) {
var text = undefined
if (this.$empty(phone)) {
text = 'Số điện thoại di động không được bỏ trống.'
} else if (isNaN(phone)) {
text = 'Số điện thoại di động không hợp lệ.'
} else if(phone.length<9 || phone.length>11) {
text = 'Số điện thoại di động phải có từ 9-11 số.'
}
return text
},
Vue.prototype.$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,}))$/
var text = undefined
if (this.$empty(email)) {
text = 'Email không được bỏ trống.'
} else if (!(re.test(String(email).toLowerCase()))) {
text = 'Email không hợp lệ.'
}
return text
}
Vue.prototype.$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,}))$/
var text = undefined
if (this.$empty(contact)) {
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))) {
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)) {
text = 'Số điện thoại di động không hợp lệ.'
}
return text
}
Vue.prototype.$dummy = function(data, count) {
let list = this.$copy(data)
for (let index = 0; index < count; index++) {
if(data.length<index+1) list.push({dummy: true})
}
return list
}
Vue.prototype.$upload = function(file, type, user) {
var fileFormat = [{type: 'image', format: ['.png', '.jpg', 'jpeg', '.bmp', '.gif', '.svg']},
{type: 'video', format: ['.wmv', '.avi', '.mp4', '.flv', '.mov', '.mpg', '.amv', '.rm']}
]
var valid = undefined
if(type==='image' || type==='video') {
valid = false
let found = fileFormat.find(v=>v.type===type)
found.format.map(x=>{
if(file.name.toLowerCase().indexOf(x)>=0) valid = true
})
}
if(valid===false) return {error: true, text: 'Định dạng file không hợp lệ'}
if((type==='image' || type==='file') && file.size > 500*1024*1024) {
return {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 {error: true, text: 'Kích thước video phải dưới 1GB'}
}
let data = new FormData()
let fileName = this.$dayjs(new Date()).format("YYYYMMDDhhmmss") + '-' + file.name
data.append('name', fileName)
data.append('file', file)
data.append('type', type)
data.append('size', file.size)
data.append('user', user)
return {form: data, name: fileName, type: type, size: file.size, file: file}
}
Vue.prototype.$change = function(obj1, obj2, list) {
var change = false
if(list) {
list.map(v=>{if(obj1[v]!==obj2[v]) change = true})
} else {
for (var k in obj1 ) {
if(obj1[k]!==obj2[k]) change = true
}
}
return change
}
Vue.prototype.$resetNull = function(obj) {
for (const [key, value] of Object.entries(obj)) {
if(typeof value==='string') {
let val = value.trim()
if(val==='' || val==="") val = null
obj[key] = val
}
}
return obj
}
Vue.prototype.$responsiveMenu = function() {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
// Check if there are any navbar burgers
if ($navbarBurgers.length > 0) {
// Add a click event on each of them
$navbarBurgers.forEach( el => {
// Get the target from the "data-target" attribute
const target = el.dataset.target;
const $target = document.getElementById(target);
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active');
$target.classList.toggle('is-active');
})
}
}
Vue.prototype.$copyToClipboard = function(text) {
if (window.clipboardData && window.clipboardData.setData) {
// IE specific code path to prevent textarea being shown while dialog is visible.
return clipboardData.setData("Text", text);
} else if (document.queryCommandSupported && document.queryCommandSupported("copy")) {
var textarea = document.createElement("textarea");
textarea.textContent = text;
textarea.style.position = "fixed"; // Prevent scrolling to bottom of page in MS Edge.
document.body.appendChild(textarea);
textarea.select();
try {
return document.execCommand("copy"); // Security exception may be thrown by some browsers.
} catch (ex) {
console.warn("Copy to clipboard failed.", ex);
return false;
} finally {
document.body.removeChild(textarea);
}
}
}
Vue.prototype.$nonAccent = function(str) {
if(this.$empty(str)) return null
str = str.replaceAll('/', '-').replaceAll('%', '-').replaceAll('?', '-')
str = str.toLowerCase();
str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g, "a");
str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g, "e");
str = str.replace(/ì|í|ị|ỉ|ĩ/g, "i");
str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g, "o");
str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g, "u");
str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g, "y");
str = str.replace(/đ/g, "d");
// 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(/\u02C6|\u0306|\u031B/g, ""); // Â, Ê, Ă, Ơ, Ư
str = str.split(' ').filter(s => s).join('-')
return str
}
Vue.prototype.$linkID = function(link) {
link = link? link : this.$route.params.slug
if(this.$empty(link)) return
let idx = link.lastIndexOf('-')
let id = (idx>-1 && idx<link.length-1)? link.substring(idx+1, link.length) : undefined
return id
},
Vue.prototype.$height = function(id) {
let doc = document.getElementById(id)
return doc? doc.clientHeight : undefined
}
Vue.prototype.$color = function(i) {
var colors = ['#4285F4','#0F9D58', '#f8961e', '#008000', '#e01e37','#090c02', '#390099','#335c67','#C46210','#FFBF00','#288D85','#89B700','#EFDECD','#E52B50','#9F2B68','#F19CBB','#AB274F','#D3212D','#3B7A57','#FFBF00','#FF7E00','#9966CC','#3DDC84','#CD9575','#665D1E','#915C83','#841B2D','#FAEBD7']
return i>=colors.length? ('#' + Math.floor(Math.random()*16777215).toString(16)) : colors[i]
}
}
})

11
plugins/components.js Executable file
View File

@@ -0,0 +1,11 @@
import Vue from 'vue'
import SnackBar from '@/components/snackbar/SnackBar'
import CountDown from '@/components/dialog/CountDown'
import Modal from '@/components/Modal'
import SearchBox from '@/components/SearchBox'
const components = { SnackBar, Modal, CountDown, SearchBox}
Object.entries(components).forEach(([name, component]) => {
Vue.component(name, component)
})

330
plugins/connection.js Normal file
View File

@@ -0,0 +1,330 @@
import Vue from 'vue'
const mode = 'dev'
var paths = [
{name: 'operation', url: 'https://oprtapi.bigdatatech.vn/' },
{name: 'local', url: 'http://127.0.0.1:8000/' },
{name: 'dev', url: 'https://utopiaapi.dev247.net/' },
{name: 'prod', url: 'https://utopiaapi.dev247.net/' }
]
const path = paths.find(v=>v.name===mode).url
const apis = [
{name: 'upload' , url: 'upload/', params: {}},
{name: 'image' , url: 'data/Image/', url_detail: 'data-detail/Image/', params: {}},
{name: 'file' , url: 'data/File/', url_detail: 'data-detail/File/', params: {}},
{name: 'user', url: 'data/User/', url_detail: 'data-detail/User/', params: {values: 'id,phone_verified,email_verified,create_time__date,username,password,avatar,fullname,display_name,type,blocked,block_reason,blocked_by,last_login,auth_method__code,auth_method__name,auth_method,auth_status,auth_status__code,auth_status__name,register_method,phone,create_time,update_time'}},
{name: 'blockreason', commit: 'updateBlockReason', url: 'data/Block_Reason/', url_detail: 'data-detail/Block_Reason/', params: {page: -1}},
{name: 'authstatus', commit: 'updateAuthStatus', url: 'data/Auth_Status/', url_detail: 'data-detail/Auth_Status/', params: {page: -1}},
{name: 'authmethod', commit: 'updateAuthMethod', url: 'data/Auth_Method/', url_detail: 'data-detail/Auth_Method/', params: {page: -1}},
{name: 'usertype', commit: 'updateUserType', url: 'data/User_Type/', url_detail: 'data-detail/User_Type/', params: {}},
{name: 'registermethod', commit: 'updateRegisterMethod', url: 'data/Register_Method/', url_detail: 'data-detail/Register_Method/', params: {page: -1}},
{name: 'langchoice', commit: 'updateLangChoice', url: 'data/Lang_Choice/', url_detail: 'data-detail/Lang_Choice/', params: {}},
{name: 'userauth', url: 'data/User_Auth/', url_detail: 'data-detail/User_Auth/', params: {}},
{name: 'accountrecovery', url: 'data/Account_Recovery/', url_detail: 'data-detail/Account_Recovery/', params: {}},
{name: 'login', url: 'login/', params: {values: 'id,username,password,avatar,fullname,display_name,type,type__code,type__name,blocked,block_reason,block_reason__code,block_reason__name,blocked_by,last_login,auth_method,auth_method__code,auth_method__name,auth_status,auth_status__code,auth_status__name,register_method,register_method__code,register_method__name,create_time,update_time'}},
{name: 'authtoken', url: 'auth-token/', params: {}},
{name: 'emailsetup', url: 'data/Email_Setup/', url_detail: 'data-detail/Email_Setup/', params: {sort: '-id'}},
{name: 'emailsent', url: 'data/Email_Sent/', url_detail: 'data-detail/Email_Sent/', params: {values: 'id,receiver,content,content__sender__email,content__subject,content__content,status__code,status,status__name,create_time', sort: '-id'}},
{name: 'sendemail', url: 'send-email/', path: 'operation', params: {}},
{name: 'token', url: 'data/Token/', url_detail: 'data-detail/Token', params: {filter: {expiry: 0}}},
{name: 'common', commit: 'updateCommon', url: 'data/Common/', url_detail: 'data-detail/Common/', params: {sort: 'index'}},
{name: 'sex', url: 'data/Sex/', url_detail: 'data-detail/Sex/', params: {}},
{name: 'downloadfile', url: 'download-file/', params: {}},
{name: 'download', url: 'download/', params: {}},
{name: 'gethash', url: 'get-hash/', params: {}},
{name: 'userapps', url: 'data/User_Apps/', url_detail: 'data-detail/User_Apps/', params: {}}
]
Vue.use( {
install(Vue){
Vue.prototype.$path = function(name) {
return name? paths.find(v=>v.name===name).url : path
}
Vue.prototype.$findapi = function(name) {
const result = Array.isArray(name)? apis.filter(v=>name.findIndex(x=>v.name===x)>=0) : apis.find(v=>v.name===name)
return this.$copy(result)
}
Vue.prototype.$readyapi = function(list) {
var array = []
list.forEach(element => {
let found = apis.find(v=>v.name===element)
if(found) {
let ele = JSON.parse(JSON.stringify(found))
ele.ready = this.$store.state[element]? true : false
array.push(ele)
}
})
return array
}
// get data
Vue.prototype.$getapi = async function(list) {
try {
let arr = list.map(v => {
let found = apis.find(v=>v.name===v.name)
let url = (v.path? paths.find(x=>x.name===v.path).url : path) + (v.url? v.url : found.url)
let params = v.params? v.params : (found.params===undefined? {} : found.params)
params.login = this.$store.state.login? this.$store.state.login.id : undefined
return {url: url, params: params}
})
let data = await Promise.all(arr.map(v=>this.$axios.get(v.url, {params: v.params})))
data.map((v,i) => {
list[i].data = v.data
if(list[i].commit) {
let payload = {}
payload[list[i].name] = v.data.rows? v.data.rows : v.data
this.$store.commit(list[i].commit, payload)
}
})
return list
} catch(err) {
console.log(err)
return 'error'
}
}
// insert data
Vue.prototype.$insertapi = async function(name, data, values) {
try {
let found = this.$findapi(name)
let curpath = found.path? paths.find(x=>x.name===found.path).url : path
var rs
if(!Array.isArray(data))
rs = await this.$axios.post(`${curpath}${found.url}`, data, {params: {values: values}})
else {
let params = {action: 'import', values: values}
rs = await this.$axios.post(`${curpath}import-data/${found.url.substring(5, found.url.length-1)}/`, data, {params: params})
}
// update store
if(found.commit) {
if(this.$store.state[found.name]) {
let copy = JSON.parse(JSON.stringify(this.$store.state[found.name]))
let rows = Array.isArray(rs.data)? rs.data : [rs.data]
rows.map(v=>{
if(v.id && !v.error) {
let idx = copy.findIndex(x=>x.id===v.id)
if(idx>=0) copy[idx] = v
else copy.push(v)
}
})
this.$store.commit('updateStore', {name: found.name, data: copy})
}
}
return rs.data
} catch(err) {
console.log(err)
return 'error'
}
}
// update api
Vue.prototype.$updateapi = async function(name, data, values) {
try {
let found = this.$findapi(name)
let rs = await this.$axios.put(`${path}${found.url_detail}${data.id}/`, data, {params: {values: values? values : found.params.values}})
if(found.commit) {
let index = this.$store.state[found.name]? this.$store.state[found.name].findIndex(v=>v.id===rs.data.id) : -1
if(index>=0) {
var copy = JSON.parse(JSON.stringify(this.$store.state[found.name]))
if(Array.isArray(rs.data)===false) Vue.set(copy, index, rs.data)
else {
rs.data.forEach(v => {
let index = copy.findIndex(v=>v.id===v.id)
if(index>=0) Vue.set(copy, index, v)
})
}
this.$store.commit('updateStore', {name: found.name, data: copy})
}
}
return rs.data
} catch(err) {
console.log(err)
return 'error'
}
}
// delete data
Vue.prototype.$deleteapi = async function(name, id) {
try {
let found = this.$findapi(name)
var rs
if(!Array.isArray(id))
rs = await this.$axios.delete(`${path}${found.url_detail}${id}`)
else {
let params = {action: 'delete'}
rs = await this.$axios.post(`${path}import-data/${found.url.substring(5,found.url.length-1)}/`, id, {params: params})
}
if(found.commit) {
let copy = JSON.parse(JSON.stringify(this.$store.state[found.name]))
if(!Array.isArray(id)) {
let index = copy.findIndex(v=>v.id===id)
if(index>=0) this.$delete(copy, index)
} else {
rs.data.forEach(element => {
let index = copy.findIndex(v=>v.id===element.id)
if(index>=0) this.$delete(copy, index)
})
}
this.$store.commit('updateStore', {name: found.name, data: copy})
}
return rs.data
} catch(err) {
console.log(err)
return 'error'
}
}
// insert row
Vue.prototype.$insertrow = async function(name, data, values, pagename) {
let result = await this.$insertapi(name, data, values)
if(result==='error') return
let arr = Array.isArray(result)? result : [result]
let copy = this.$copy(this.$store.state[pagename].data)
arr.map(x=>{
let index = copy.findIndex(v=>v.id===x.id)
index>=0? copy[index] = x : copy.unshift(x)
})
this.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
let pagedata = this.$store.state[pagename]
if(pagedata.filters? pagedata.filters.length>0 : false) {
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
}
}
// update row
Vue.prototype.$updaterow = async function(name, data, values, pagename) {
let result = await this.$updateapi(name, data, values)
if(result==='error') return
let arr = Array.isArray(result)? result : [result]
let copy = this.$copy(this.$store.state[pagename].data)
arr.map(x=>{
let index = copy.findIndex(v=>v.id===x.id)
index>=0? copy[index] = x : copy.unshift(x)
})
this.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
let pagedata = this.$store.state[pagename]
if(pagedata.filters? pagedata.filters.length>0 : false) {
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
}
}
// delete row
Vue.prototype.$deleterow = async function(name, id, pagename, ask) {
let self = this
var remove = async function() {
let result = await self.$deleteapi(name, id)
if(result==='error') return
let arr = Array.isArray(id)? id : [id]
let copy = self.$copy(self.$store.state[pagename].data)
arr.map(x=>{
let index = copy.findIndex(v=>v.id===x)
index>=0? self.$delete(copy,index) : false
})
self.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
let pagedata = this.$store.state[pagename]
if(pagedata.filters? pagedata.filters.length>0 : false) {
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
}
}
// ask confirm
if(ask) {
this.$buefy.dialog.confirm({
message: 'Bạn muốn xóa bản ghi: ' + id,
onConfirm: () => remove()
})
} else remove()
}
// update page
Vue.prototype.$updatepage = function(pagename, row, action) {
let pagedata = this.$store.state[pagename]
let copy = this.$copy(pagedata.data)
let idx = copy.findIndex(v=>v.id===row.id)
if(action==='delete') this.$delete(copy, idx)
else if(action==='insert') copy.unshift(row)
else copy[idx] = row
this.$store.commit('updateState', {name: pagename, key: 'data', data: copy})
if(pagedata.filters? pagedata.filters.length>0 : false) {
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: this.$copy(pagedata.filters)})
}
}
Vue.prototype.$getdata = async function(name, filter, params, first) {
let found = this.$findapi(name)
if(params) found.params = params
else if(filter) found.params.filter = filter
let rs = await this.$getapi([found])
return first? (rs[0].data.rows.length>0? rs[0].data.rows[0] : undefined) : rs[0].data.rows
}
Vue.prototype.$getpage = function(showFilter) {
return {data: [], fields: [], filters: [], update: undefined, action: undefined, filterby: undefined, api: {full_data: true}, origin_api: {full_data: true},
tablesetting: undefined, setting: undefined, tabfield: true, setpage: {}, showFilter: this.$empty(showFilter)? true : showFilter}
}
Vue.prototype.$setpage = function(pagename, row, api) {
if(!this.$store.state[pagename])return
let json = row.detail
let fields = this.$updateSeriesFields(json.fields)
this.$store.commit('updateState', {name: pagename, key: 'fields', data: fields})
this.$store.commit('updateState', {name: pagename, key: 'setting', data: this.$copy(row)})
if(json.filters) this.$store.commit('updateState', {name: pagename, key: 'filters', data: this.$copy(json.filters)})
if(json.tablesetting) this.$store.commit('updateState', {name: pagename, key: 'tablesetting', data: json.tablesetting})
if(api) {
let copy = this.$copy(api)
delete copy.data
copy.full_data = api.data.full_data
copy.total_rows = api.data.total_rows
this.$store.commit('updateState', {name: pagename, key: 'api', data: copy})
this.$store.commit('updateState', {name: pagename, key: 'origin_api', data: copy})
}
}
Vue.prototype.$findpage = function(arr) {
var copy = this.$copy(this.$store.state.pagetrack)
var doFind = function() {
let found = undefined
for (let i=1; i<=30; i++) {
let name = `pagedata${i}`
if(!copy[name]) {
found = name
copy[name] = true
break;
}
}
if(!found) console.log('pagename not found')
return found
}
let result
if(arr) {
result = []
arr.map(v=>{ result.push({name: v, value: doFind()})})
} else {
result = doFind(copy)
}
this.$store.commit('updateStore', {name: 'pagetrack', data: copy})
return result
}
Vue.prototype.$clearpage = function(pagename) {
if(!pagename) return
if(pagename==='reset') return this.$store.commit('updateStore', {name: 'pagetrack', data: {}})
let copy = this.$copy(this.$store.state.pagetrack)
let arr = Array.isArray(pagename)? pagename : [pagename]
arr.map(v=>{
copy[v] = false
this.$store.commit('updateStore', {name: v, data: undefined})
})
this.$store.commit('updateStore', {name: 'pagetrack', data: copy})
}
Vue.prototype.$updatepath = function(val) {
path = `https://${val}/`
}
}
})

358
plugins/datatable.js Executable file
View File

@@ -0,0 +1,358 @@
import Vue from 'vue'
Vue.use( {
install(Vue) {
//==========Find & filter=================
Vue.prototype.$find = function(arr, obj, attr) {
const keys = Object.keys(obj)
let found = arr.find(v=>{
let valid = true
keys.map(key=>{
let val = obj[key]
if(valid===false) return false
else if(Array.isArray(val)) {
if(val.findIndex(x=>x===v[key]) <0) valid = false
} else if(!(v[key]===val)) valid = false
})
return valid
})
return found? (attr? found[attr] : found) : undefined
}
Vue.prototype.$findIndex = function(arr, obj) {
const keys = Object.keys(obj)
return arr.findIndex(v=>{
let valid = true
keys.map(key=>{
let val = obj[key]
if(valid===false) return false
else if(Array.isArray(val)) {
if(val.findIndex(x=>x===v[key]) <0) valid = false
} else if(!(v[key]===val)) valid = false
})
return valid
})
}
Vue.prototype.$filter = function(arr, obj, attr) {
const keys = Object.keys(obj)
let rows = arr.filter(v=>{
let valid = true
keys.map(key=>{
let val = obj[key]
if(valid===false) return false
else if(Array.isArray(val)) {
if(val.findIndex(x=>x===v[key]) <0) valid = false
} else if(!(v[key]===val)) valid = false
})
return valid
})
return attr? rows.map(v=>v[attr]) : rows
}
//=========Empty & copy============
Vue.prototype.$id = function() {
return Math.random().toString(36).substr(2, 9)
}
Vue.prototype.$empty = function(val) {
if(val === undefined || val === null || val === '' || val==="") return true
return false
},
Vue.prototype.$copy = function(val) {
if(val === undefined || val === null || val === '' || val==="") return val
return JSON.parse(JSON.stringify(val))
}
Vue.prototype.$clone = function(obj) {
if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
return obj;
if (obj instanceof Date)
var temp = new obj.constructor(); //or new Date(obj);
else
var temp = obj.constructor();
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = this.$clone(obj[key]);
delete obj['isActiveClone'];
}
}
return temp
}
Vue.prototype.$delete = function(arr, idx) {
arr.splice(idx, 1)
}
Vue.prototype.$stripHtml = function(html, length) {
if(!html) return null
else if(typeof html!=='string') return html
if(html? html.indexOf('<')<0 : false) {
return length? (html.length > length? html.substring(0, length) + '...' : html ) : html
}
var tmp = document.createElement("DIV")
tmp.innerHTML = html
var val = tmp.textContent || tmp.innerText || ""
return length? (val.length > length? val.substring(0, length) + '...' : val ) : val
}
//==========Convert=================
Vue.prototype.$isNumber = function(val) {
if(val === undefined || val === null || val === '' || val==="") return false
val = val.toString().replace(/,/g, "")
return !isNaN(val)
}
Vue.prototype.$numtoString = function(val, type, decimal, mindecimal) {
if(val === undefined || val === "" || val==='' || val === null) return
val = val.toString().replace(/,/g, "")
if(isNaN(val)) return
let f = decimal? {maximumFractionDigits: decimal || 0} : {}
if(mindecimal) f['minimumFractionDigits'] = mindecimal
return decimal? Number(val).toLocaleString(type || 'en-EN', f) : Number(val).toLocaleString(type || 'en-EN')
}
Vue.prototype.$formatNumber = function(val) {
if(val === undefined || val === "" || val==='' || val === null) return
val = val.toString().replace(/,/g, "")
if (val.indexOf('%') >0) {
val = val.replace(/%/g, "")
return isNaN(val)? undefined : Number(val)/100
}
return isNaN(val)? undefined : Number(val)
}
Vue.prototype.$formatUnit = function(val, unit, decimal, string, mindecimal) {
val = this.$formatNumber(val)
if(val===undefined) return
let percentage = (unit===0.01 || unit==="0.01")? '%' : ''
val = unit? val/Number(unit) : val
let f = {maximumFractionDigits: decimal || 0}
if(mindecimal) f['minimumFractionDigits'] = mindecimal
return string? (val.toLocaleString('en-EN', f) + percentage) : val
}
//==========Calculate=================
Vue.prototype.$calc = function(fn) {
return new Function('return ' + fn)()
}
Vue.prototype.$calculate = function(row, tags, formula, decimal, unit) {
let val = this.$copy(formula)
let valid = 0
tags.forEach(v => {
let myRegExp = new RegExp(v, 'g')
let res = this.$formatNumber(row[v])
if(res) valid = 1
val = val.replace(myRegExp, `(${res || 0})`)
})
if(valid===0) return {success: false, value : undefined} //all values is null
//calculate
try {
let value = this.$calc(val)
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
var result = {success: false, value : value}
} else {
value = (value===true || value===false)? value : this.$formatUnit(value, unit, decimal, true, decimal)
var result = {success: true, value: value}
}
}
catch(err) {
var result = {success: false, value : undefined}
}
return result
}
Vue.prototype.$calculateFunc = function(row, cols, func, decimal, unit) {
let value
let arr1 = cols.map(v=>this.$formatNumber(row[v]))
let arr = arr1.filter(v=>v)
if(arr.length===0) return {success: false, value : undefined}
if(func==='max') value = Math.max(...arr)
else if(func==='min') value = Math.min(...arr)
else if(func==='sum') value = arr.reduce((a, b) => a + b, 0)
else if(func==='avg') {
let total = arr.reduce((a, b) => a + b, 0)
value = total / cols.length
}
if(!value) return {success: false, value: undefined}
value = this.$formatUnit(value, unit, decimal, true, decimal)
return {success: true, value: value}
}
Vue.prototype.$calculateData = function(data, fields) {
let arr = this.$copy(fields.filter(h=>h.formula))
if(arr.length===0) return data
let arr1 = arr.filter(v=>v.func)
arr1.map(v=>{
if(v.vals.indexOf(':')>=0) {
let arr2 = v.vals.toLowerCase().replaceAll('c', '').split(':')
let cols = []
for (let i = parseInt(arr2[0]); i <= parseInt(arr2[1]); i++) {
let field = fields.length>i? fields[i] : undefined
if(field? (field.format==='number' && field.name!==v.name) : false) cols.push(field.name)
}
v.cols = cols
} else {
let arr2 = v.vals.toLowerCase().replaceAll('c', '').split(',')
let cols = []
arr2.map(v=>{
let i = parseInt(v)
let field = fields.length>i? fields[i] : undefined
if(field? (field.format==='number' && field.name!==v.name) : false) cols.push(field.name)
})
v.cols = cols
}
})
arr = this.$multiSort(arr, {level: 'asc'})
let copy = data
copy.map(v=>{
arr.map(x=>{
if(x.func) {
let res = this.$calculateFunc(v, x.cols, x.func, x.decimal, x.unit)
if(res? res.success : false) v[x.name] = res.value
} else {
let res = this.$calculate(v, x.tags, x.formula, x.decimal, x.unit)
if(res? res.success : false) v[x.name] = res.value
}
})
})
return copy
}
Vue.prototype.$summary = function(arr, fields, type) {
let obj = {}
if(type==='total') {
fields.map(x=> obj[x] = arr.map(v=>v[x]? v[x] : 0).reduce((a, b) => a + b, 0))
} else if(type==='min') {
fields.map(x=>obj[x] = Math.min(...arr.map(v=>v[x])))
}
else if(type==='max') {
fields.map(x=>obj[x] = Math.max(...arr.map(v=>v[x])))
}
else if(type==='count') {
fields.map(x=>obj[x] = arr.map(v=>!this.$empty(v[x])).length)
}
return obj
}
//====================Array====================
Vue.prototype.$formatArray = function(data, fields) {
let args = fields.filter(v=>v.format==='number')
data.map(v=>{
args.map(x=>{
v[x.name] = this.$empty(v[x.name])? undefined : this.$formatUnit(v[x.name], x.unit, x.decimal, true, x.decimal)
})
})
return data
}
Vue.prototype.$unique = function(arr, keyProps) {
const kvArray = arr.map(entry => {
const key = keyProps.map(k => entry[k]).join('|');
return [key, entry];
});
const map = new Map(kvArray);
return Array.from(map.values());
}
Vue.prototype.$arrayMove = function(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr; // for testing
}
Vue.prototype.$multiSort = function(array, sortObject = {}, format = {}) {
const sortKeys = Object.keys(sortObject)
// Return array if no sort object is supplied.
if (!sortKeys.length) return array
// Change the values of the sortObject keys to -1, 0, or 1.
for (let key in sortObject)
sortObject[key] = sortObject[key] === 'desc' || sortObject[key] === -1 ? -1 : (sortObject[key] === 'skip' || sortObject[key] === 0 ? 0 : 1)
const keySort = (a, b, direction) => {
direction = direction !== null ? direction : 1
if (a === b) return 0
// If b > a, multiply by -1 to get the reverse direction.
return a > b ? direction : -1 * direction;
}
return array.sort((a, b) => {
let sorted = 0, index = 0
// Loop until sorted (-1 or 1) or until the sort keys have been processed.
while (sorted === 0 && index < sortKeys.length) {
const key = sortKeys[index]
if (key) {
const direction = sortObject[key]
let val1 = format[key]==='number'? (this.$empty(a[key])? 0 : this.$formatNumber(a[key])) : a[key]
let val2 = format[key]==='number'? (this.$empty(b[key])? 0 : this.$formatNumber(b[key])) : b[key]
sorted = keySort(val1, val2, direction)
index++
}
}
return sorted
})
}
//======================Fields====================
Vue.prototype.$createField = function(name,label,format,show, minwidth) {
let field = {name: name, label: label, format: format, show: show, minwidth: minwidth}
if(format==='number') {
field.unit = '1'
field.textalign = 'right'
}
return field
}
Vue.prototype.$updateFields = function(pagename, field, action) {
let pagedata = this.$store.state[pagename]
let copy = this.$copy(pagedata.fields)
let idx = this.$findIndex(copy, {name: field.name})
if(action==='delete') {
this.$delete(copy, idx)
if(pagedata.filters? pagedata.filters.length>0 : false) {
let index = this.$findIndex(pagedata.filters, {name: field.name})
if(index>=0) {
let copyFilter = this.$copy(this.pagedata.filters)
this.$delete(copyFilter, index)
this.$store.commit('updateState', {name: pagename, key: 'filterby', data: copyFilter})
}
}
} else {
idx>=0? copy[idx] = field : copy.push(field)
}
this.$store.commit("updateState", {name: pagename, key: "fields", data: copy})
},
Vue.prototype.$updateSeriesFields = function(fields) {
fields.filter(v=>v.series).map(field=>{
let obj = field.api==='finitem'? this.$findPeriod(field.series, 'period') : this.$findPeriod(field.series)
field.period = field.api==='finitem'? obj.code : obj
let idx = field.label.indexOf('[')
field.label = (idx>0? field.label.substring(0, idx) : field.label) + '[' + (field.api==='finitem'? obj.show : obj) + ']'
})
return fields
}
Vue.prototype.$updateSeriesFilters = function(filters, fields) {
if(fields.filter(v=>v.series).length===0) return filters
filters.map(v=>{
let found = fields.find(x=>x.name===v.name)
if(found? found.series : false) {
v.label = found.label
}
})
return filters
}
}
})