Initial commit
This commit is contained in:
550
app/components/datatable/DataTable.vue
Normal file
550
app/components/datatable/DataTable.vue
Normal file
@@ -0,0 +1,550 @@
|
||||
<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`]"
|
||||
style="
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
"
|
||||
@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 {
|
||||
$calc,
|
||||
$calculate,
|
||||
$calculateData,
|
||||
$copy,
|
||||
$deleterow,
|
||||
$empty,
|
||||
$find,
|
||||
$getEditRights,
|
||||
$formatNumber,
|
||||
$multiSort,
|
||||
$remove,
|
||||
$stripHtml,
|
||||
$unique,
|
||||
} = 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) {
|
||||
// allowed JS expressions - should return a boolean
|
||||
const allowedFns = {
|
||||
'$getEditRights()': $getEditRights,
|
||||
};
|
||||
const arr = pagedata.fields.filter(({ show }) => {
|
||||
if (typeof show === 'boolean') return show;
|
||||
else {
|
||||
// show is a string
|
||||
if (show === 'true') return true;
|
||||
if (show === 'false') return false;
|
||||
return allowedFns[show]?.() || false;
|
||||
}
|
||||
});
|
||||
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