551 lines
17 KiB
Vue
551 lines
17 KiB
Vue
<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>
|