changes
This commit is contained in:
422
components/datatable/DataTable.vue
Normal file
422
components/datatable/DataTable.vue
Normal file
@@ -0,0 +1,422 @@
|
||||
<template>
|
||||
<div class="field is-grouped is-grouped-multiline pl-2" v-if="filters? filters.length>0 : false">
|
||||
<div class="control mr-5">
|
||||
<a class="button is-primary is-small has-text-white has-text-weight-bold" @click="updateData({filters: []})">
|
||||
<span class="fs-14">Xóa lọc</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="control pr-2 mr-5">
|
||||
<span class="icon-text">
|
||||
<SvgIcon v-bind="{name: 'sigma.svg', type: 'primary', size: 20}"></SvgIcon>
|
||||
<span class="fsb-18 has-text-primary">{{totalRows}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="control" v-for="(v,i) in filters" :key="i">
|
||||
<div class="tags has-addons is-marginless">
|
||||
<a class="tag is-primary has-text-white is-marginless" @click="showCondition(v)">{{v.label.indexOf('>')>=0? $stripHtml(v.label,30) : v.label}}</a>
|
||||
<a class="tag is-delete is-marginless has-text-black-bis" @click="removeFilter(i)"></a>
|
||||
</div>
|
||||
<span class="help has-text-black-bis">
|
||||
{{v.sort? v.sort : (v.select? ('[' + (v.select.length>0? $stripHtml(v.select[0],20) : '') + '...Σ' + v.select.length + ']') :
|
||||
(v.condition))}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container mb-0" ref="container" id="docid">
|
||||
<table class="table is-fullwidth is-bordered is-narrow is-hoverable" :style="tableStyle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(field,i) in displayFields" :key="i" :style="field.headerStyle">
|
||||
<div @click="showField(field)" :style="field.dropStyle">
|
||||
<a v-if="field.label.indexOf('<')<0">{{field.label}}</a>
|
||||
<a v-else>
|
||||
<component :is="dynamicComponent(field.label)" :row="v" @clickevent="clickEvent($event, v, field)" />
|
||||
</a>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(v,i) in displayData" :key="i">
|
||||
<td v-for="(field, j) in displayFields" :key="j" :id="`${field.name}`" :style="v[`${field.name}color`]"
|
||||
@dblclick="doubleClick(field, v)">
|
||||
<component :is="dynamicComponent(field.template)" :row="v" v-if="field.template" @clickevent="clickEvent($event, v, field)" />
|
||||
<span v-else>{{ v[field.name] }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<DatatablePagination v-bind="{data: data, perPage: perPage}" @changepage="changePage" v-if="showPaging"></DatatablePagination>
|
||||
</div>
|
||||
<Modal @close="close" @selected="doSelect" @confirm="confirmRemove" v-bind="showmodal" v-if="showmodal"></Modal>
|
||||
</template>
|
||||
<script setup>
|
||||
import { createApp } from "vue/dist/vue.esm-bundler.js"
|
||||
import { ref, defineComponent } from 'vue'
|
||||
import { useStore } from '~/stores/index'
|
||||
const emit = defineEmits(['edit', 'insert', 'dataevent'])
|
||||
const { $copy, $empty, $unique, $multiSort, $remove, $calc, $calculate, $find, $formatNumber, $stripHtml, $calculateData, $deleterow } = useNuxtApp()
|
||||
const store = useStore()
|
||||
var props = defineProps({
|
||||
pagename: String
|
||||
})
|
||||
function dynamicComponent(htmlString) {
|
||||
return defineComponent({
|
||||
template: htmlString,
|
||||
props: {
|
||||
row: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
var timer = undefined
|
||||
var showPaging = ref(false)
|
||||
var totalRows = ref(0)
|
||||
var currentPage = 1
|
||||
var displayFields = ref([])
|
||||
var displayData = []
|
||||
var pagedata = store[props.pagename]
|
||||
var tablesetting = $copy(pagedata.tablesetting || store.tablesetting)
|
||||
var perPage = Number($find(tablesetting, {code: 'per-page'}, 'detail')) || 20
|
||||
var filters = $copy(pagedata.filters || [])
|
||||
var currentField
|
||||
var filterData = []
|
||||
var currentsetting
|
||||
var scrollbar
|
||||
var fields
|
||||
var currentRow
|
||||
var data = $copy(pagedata.data)
|
||||
var showmodal = ref()
|
||||
watch(() => store[props.pagename], (newVal, oldVal) => {
|
||||
updateChange()
|
||||
})
|
||||
function updateChange() {
|
||||
pagedata = store[props.pagename]
|
||||
if(!pagedata.update) return
|
||||
if(pagedata.update.data) data = $copy(pagedata.update.data)
|
||||
if(pagedata.update.filters) {
|
||||
doFilter(pagedata.update.filters)
|
||||
updateShow()
|
||||
return //exit
|
||||
}
|
||||
if(filters.length>0) doFilter(filters)
|
||||
if(pagedata.update.fields || pagedata.update.data) updateShow()
|
||||
}
|
||||
const updateShow = function(full_data) {
|
||||
let arr = pagedata.fields.filter(v=>v.show)
|
||||
if(full_data===false) displayData = $copy(data)
|
||||
else displayData = $copy(data.filter((ele,index) => (index>=(currentPage-1)*perPage && index<currentPage*perPage)))
|
||||
displayData.map(v=>{
|
||||
arr.map(x=>v[`${x.name}color`] = getStyle(x, v))
|
||||
})
|
||||
arr.map(v=>{
|
||||
v.headerStyle = getSettingStyle('header', v)
|
||||
v.dropStyle = getSettingStyle('dropdown', v)
|
||||
})
|
||||
displayFields.value = arr
|
||||
showPagination()
|
||||
}
|
||||
function confirmRemove() {
|
||||
$deleterow(pagedata.api.name, currentRow.id, props.pagename, true)
|
||||
}
|
||||
const clickEvent = function(event, row, field) {
|
||||
let name = typeof event === "string"? event : event.name
|
||||
let data = typeof event === "string"? event : event.data
|
||||
if(name==='remove') {
|
||||
currentRow = row
|
||||
showmodal.value = {component: `dialog/Confirm`,vbind: {content: 'Bạn có muốn xóa bản ghi này không?', duration: 10},
|
||||
title: 'Xác nhận', width: '500px', height: '100px'}
|
||||
}
|
||||
emit(name, row, field, data)
|
||||
}
|
||||
const showField = async function(field) {
|
||||
if(pagedata.contextMenu===false || field.menu==='no') return
|
||||
currentField = field
|
||||
filterData = $unique(pagedata.data, [field.name])
|
||||
//let doc = this.$refs[`th${field.name}`]
|
||||
//let width = (doc? doc.length>0 : false)? doc[0].getBoundingClientRect().width : 100
|
||||
let width = 100
|
||||
if(pagedata.setting) currentsetting = $copy(pagedata.setting)
|
||||
showmodal.value = {vbind: {pagename: props.pagename, field: field, filters: filters, filterData: filterData, width: width},
|
||||
component: 'datatable/ContextMenu', title: field.name, width: '650px', height: '500px'} //$stripHtml(field.label)
|
||||
}
|
||||
const getStyle = function(field, record) {
|
||||
var stop = false
|
||||
let val = tablesetting.find(v=>v.code==='td-border')? tablesetting.find(v=>v.code==='td-border').detail
|
||||
: 'border: solid 1px rgb(44, 44, 44); '
|
||||
val = val.indexOf(';')>=0? val : val + ';'
|
||||
if(field.bgcolor? !Array.isArray(field.bgcolor) : false) {
|
||||
val += ` background-color:${field.bgcolor}; `
|
||||
} else if(field.bgcolor? Array.isArray(field.bgcolor) : false) {
|
||||
field.bgcolor.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` background-color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
} else {
|
||||
let res = $calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` background-color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
stop = false
|
||||
if(field.color? !Array.isArray(field.color) : false) {
|
||||
val += ` color:${field.color}; `
|
||||
} else if(field.color? Array.isArray(field.color) : false) {
|
||||
field.color.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
} else {
|
||||
let res = $calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
stop = false
|
||||
if(field.textsize? !Array.isArray(field.textsize) : false) {
|
||||
val += ` font-size:${field.textsize}px; `
|
||||
} else if(field.textsize? Array.isArray(field.textsize) : false) {
|
||||
field.textsize.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` font-size:${v.size}px; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
let res = $calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` font-size:${v.size}px; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
} else val += ` font-size:${tablesetting.find(v=>v.code==='table-font-size').detail}px;`
|
||||
if(field.textalign) val += ` text-align:${field.textalign}; `
|
||||
if(field.minwidth) val += ` min-width:${field.minwidth}px; `
|
||||
if(field.maxwidth) val += ` max-width:${field.maxwidth}px; `
|
||||
return val
|
||||
}
|
||||
const getSettingStyle = function(name, field) {
|
||||
let value = ''
|
||||
if(name==='container') {
|
||||
value = 'min-height:' + tablesetting.find(v=>v.code==='container-height').detail + 'rem; '
|
||||
} else if(name==='table') {
|
||||
value += 'background-color:' + tablesetting.find(v=>v.code==='table-background').detail + '; '
|
||||
value += 'font-size:' + tablesetting.find(v=>v.code==='table-font-size').detail + 'px;'
|
||||
value += 'color:' + tablesetting.find(v=>v.code==='table-font-color').detail + '; '
|
||||
} else if(name==='header') {
|
||||
value += 'background-color:' + tablesetting.find(v=>v.code==='header-background').detail + '; '
|
||||
if(field.minwidth) value += ' min-width: ' + field.minwidth + 'px; '
|
||||
if(field.maxwidth) value += ' max-width: ' + field.maxwidth + 'px; '
|
||||
} else if(name==='menu') {
|
||||
let arg = tablesetting.find(v=>v.code==='menu-width').detail
|
||||
arg = field? (field.menuwidth? field.menuwidth : arg) : arg
|
||||
value += 'width:' + arg + 'rem; '
|
||||
value += 'min-height:' + tablesetting.find(v=>v.code==='menu-min-height').detail + 'rem; '
|
||||
value += 'max-height:' + tablesetting.find(v=>v.code==='menu-max-height').detail + 'rem; '
|
||||
value += "overflow:auto; "
|
||||
} else if(name==='dropdown') {
|
||||
value += 'font-size:' + tablesetting.find(v=>v.code==='header-font-size').detail + 'px; '
|
||||
let found = filters.find(v=>v.name===field.name)
|
||||
found? value += 'color:' + tablesetting.find(v=>v.code==='header-filter-color').detail + '; '
|
||||
:value += 'color:' + tablesetting.find(v=>v.code==='header-font-color').detail + '; '
|
||||
}
|
||||
return value
|
||||
}
|
||||
function changePage(page) {
|
||||
currentPage = page
|
||||
updateShow()
|
||||
}
|
||||
const showPagination = function() {
|
||||
showPaging.value = pagedata.pagination===false? false : true
|
||||
totalRows.value = data.length
|
||||
if(showPaging.value && pagedata.api) {
|
||||
if(pagedata.api.full_data===false) totalRows.value = pagedata.api.total_rows
|
||||
showPaging.value = totalRows.value > perPage
|
||||
}
|
||||
}
|
||||
const close = function() {
|
||||
showmodal.value = undefined
|
||||
}
|
||||
const frontendFilter = function(newVal) {
|
||||
let checkValid = function(name, x, filter) {
|
||||
if($empty(x[name])) return false
|
||||
else {
|
||||
let text = ''
|
||||
filter.map((y,k)=>{
|
||||
text += `${k>0? (filter[k-1].operator==='and'? ' &&' : ' ||') : ''} ${$formatNumber(x[name])}
|
||||
${y.condition==='='? '==' : (y.condition==='<>'? '!==' : y.condition)} ${$formatNumber(y.value)}`
|
||||
})
|
||||
return $calc(text)
|
||||
}
|
||||
}
|
||||
newVal = $copy(newVal)
|
||||
var data = $copy(pagedata.data)
|
||||
newVal.filter(m=>m.select || m.filter).map(v => {
|
||||
if(v.select) {
|
||||
data = data.filter(x => v.select.findIndex(y => $empty(y)? $empty(x[v.name]) : (y===x[v.name])) >-1)
|
||||
} else if(v.filter) {
|
||||
data = data.filter(x => checkValid(v.name, x, v.filter))
|
||||
}
|
||||
})
|
||||
let sort = {}
|
||||
let format = {}
|
||||
let list = filters.filter(x=>x.sort)
|
||||
list.map(v=>{
|
||||
sort[v.name] = v.sort === "az" ? "asc" : "desc"
|
||||
format[v.name] = v.format;
|
||||
})
|
||||
return list.length>0? $multiSort(data, sort, format) : data
|
||||
}
|
||||
const backendFilter = function(newVal) {
|
||||
|
||||
}
|
||||
const doFilter = function(newVal, nonset) {
|
||||
if(currentPage>1 && nonset!==true) currentPage = 1
|
||||
if(pagedata.api.full_data) {
|
||||
data = frontendFilter(newVal)
|
||||
pagedata.dataFilter = $copy(data)
|
||||
store.commit(props.pagename, pagedata)
|
||||
emit('changedata', newVal)
|
||||
}
|
||||
else {
|
||||
if(timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => backendFilter(newVal), 200)
|
||||
}
|
||||
pagedata.filters = newVal
|
||||
store.commit(props.pagename, pagedata)
|
||||
emit('changefilter', newVal? newVal.length>0 : false)
|
||||
}
|
||||
const doSelect = function(value) {
|
||||
showmodal.value = undefined
|
||||
let field = currentField
|
||||
let found = filters.find(v=>v.name===field.name)
|
||||
if(found) {
|
||||
!found.select? found.select = [] : false
|
||||
let idx = found.select.findIndex(x=>x===value)
|
||||
idx>=0? $remove(found.select, idx) : found.select.push(value)
|
||||
if(found.select.length===0) {
|
||||
idx = filters.findIndex(v=>v.name===field.name)
|
||||
if(idx>=0) $remove(filters, idx)
|
||||
}
|
||||
} else {
|
||||
filters.push({name: field.name, label: field.label, select: [value], format: field.format})
|
||||
}
|
||||
doFilter(filters)
|
||||
updateShow()
|
||||
}
|
||||
const doubleScroll = function(element) {
|
||||
var _scrollbar= document.createElement('div');
|
||||
_scrollbar.appendChild(document.createElement('div'));
|
||||
_scrollbar.style.overflow= 'auto';
|
||||
_scrollbar.style.overflowY= 'hidden';
|
||||
_scrollbar.firstChild.style.width= element.scrollWidth+'px';
|
||||
_scrollbar.firstChild.style.height = '1px'
|
||||
_scrollbar.firstChild.appendChild(document.createTextNode('\xA0'));
|
||||
var running = false;
|
||||
_scrollbar.onscroll= function() {
|
||||
if(running) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
element.scrollLeft= _scrollbar.scrollLeft;
|
||||
};
|
||||
element.onscroll= function() {
|
||||
if(running) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
_scrollbar.scrollLeft= element.scrollLeft;
|
||||
}
|
||||
element.parentNode.insertBefore(scrollbar, element)
|
||||
_scrollbar.scrollLeft= element.scrollLeft
|
||||
scrollbar = _scrollbar
|
||||
}
|
||||
const removeFilter = function(i) {
|
||||
$remove(filters, i)
|
||||
doFilter(filters)
|
||||
updateShow()
|
||||
}
|
||||
const scrollbarVisible = function() {
|
||||
let element = this.$refs['container']
|
||||
if(!element) return
|
||||
let result = element.scrollWidth > element.clientWidth? true : false
|
||||
if(scrollbar) {
|
||||
element.parentNode.removeChild(scrollbar)
|
||||
scrollbar = undefined
|
||||
}
|
||||
if(result) doubleScroll(element)
|
||||
}
|
||||
const updateData = async function(newVal) {
|
||||
if(newVal.columns) { //change attribute
|
||||
fields = $copy(newVal.columns)
|
||||
let _fields = fields.filter(v=>v.show)
|
||||
data.map(v=>{
|
||||
_fields.map(x=>v[`${x.name}color`] = getStyle(x, v))
|
||||
})
|
||||
return updateShow()
|
||||
}
|
||||
if(newVal.tablesetting) {
|
||||
tablesetting = newVal.tablesetting
|
||||
perPage = $formatNumber(tablesetting.find(v=>v.code=="per-page").detail)
|
||||
currentPage = 1
|
||||
}
|
||||
tablesetting = $copy(pagedata.tablesetting || gridsetting)
|
||||
if(tablesetting) {
|
||||
perPage = pagedata.perPage? pagedata.perPage : Number(tablesetting.find(v=>v.code==='per-page').detail)
|
||||
}
|
||||
if(newVal.fields) {
|
||||
fields = $copy(newVal.fields)
|
||||
} else fields = $copy(pagedata.fields)
|
||||
if(newVal.data || newVal.fields) {
|
||||
let copy = $copy(newVal.data || data)
|
||||
this.data = $calculateData(copy, fields)
|
||||
let fields = fields.filter(v=>v.show)
|
||||
data.map(v=>{
|
||||
fields.map(x=>v[`${x.name}color`] = getStyle(x, v))
|
||||
})
|
||||
}
|
||||
if(newVal.filters) filters = $copy(newVal.filters)
|
||||
else if(pagedata.filters) filters = $copy(pagedata.filters)
|
||||
if(newVal.data || newVal.fields || newVal.filters) {
|
||||
let copy = $copy(filters)
|
||||
filters.map((v,i)=>{
|
||||
let idx = $findIndex(fields, {name: v.name})
|
||||
let index = $findIndex(copy, {name: v.name})
|
||||
if(idx<0 && index>=0) $delete(copy, index)
|
||||
else if(idx>=0 && index>=0) copy[index].label = fields[idx].label
|
||||
})
|
||||
filters = copy
|
||||
doFilter(filters)
|
||||
}
|
||||
if(newVal.data || newVal.fields || newVal.filters || newVal.tablesetting) updateShow()
|
||||
if(newVal.data || newVal.fields) setTimeout(()=> scrollbarVisible(), 100)
|
||||
if(newVal.highlight) setTimeout(()=>highlight(newVal.highlight), 50)
|
||||
}
|
||||
const doubleClick = function(field, v) {
|
||||
currentField = field
|
||||
doSelect(v[field.name])
|
||||
}
|
||||
var tableStyle = getSettingStyle('table')
|
||||
setTimeout(()=> updateShow(), 200)
|
||||
</script>
|
||||
<style scoped>
|
||||
:deep(.table tbody tr:hover td, .table tbody tr:hover th) {
|
||||
background-color: hsl(0, 0%, 78%);
|
||||
color: rgb(0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user