chore: install prettier

This commit is contained in:
Viet An
2026-05-04 15:22:27 +07:00
parent 93d29ca7d8
commit bd58e2b847
267 changed files with 22950 additions and 13581 deletions

View File

@@ -5,7 +5,11 @@
@click="checkFilter() ? false : $emit('modalevent', { name: 'dosort', data: 'az' })"
>
<SvgIcon
v-bind="{ name: 'az.svg', type: checkFilter() ? 'grey' : 'primary', size: 22 }"
v-bind="{
name: 'az.svg',
type: checkFilter() ? 'grey' : 'primary',
size: 22,
}"
></SvgIcon>
</a>
<span
@@ -20,7 +24,11 @@
@click="checkFilter() ? false : $emit('modalevent', { name: 'dosort', data: 'za' })"
>
<SvgIcon
v-bind="{ name: 'az.svg', type: checkFilter() ? 'grey' : 'primary', size: 22 }"
v-bind="{
name: 'az.svg',
type: checkFilter() ? 'grey' : 'primary',
size: 22,
}"
></SvgIcon>
</a>
<span
@@ -30,7 +38,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="moveLeft()">
<a
class="mr-4"
@click="moveLeft()"
>
<SvgIcon v-bind="{ name: 'left5.png', type: 'primary', size: 22 }"></SvgIcon>
</a>
<span
@@ -40,7 +51,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="moveRight()">
<a
class="mr-4"
@click="moveRight()"
>
<SvgIcon v-bind="{ name: 'right5.png', type: 'primary', size: 22 }"></SvgIcon>
</a>
<span
@@ -50,7 +64,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="resizeWidth()">
<a
class="mr-4"
@click="resizeWidth()"
>
<SvgIcon v-bind="{ name: 'thick.svg', type: 'primary', size: 22 }"></SvgIcon>
</a>
<span
@@ -60,7 +77,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="resizeWidth(true)">
<a
class="mr-4"
@click="resizeWidth(true)"
>
<SvgIcon v-bind="{ name: 'thin.svg', type: 'primary', size: 23 }"></SvgIcon>
</a>
<span
@@ -70,7 +90,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="hideField()">
<a
class="mr-4"
@click="hideField()"
>
<SvgIcon v-bind="{ name: 'eye-off.svg', type: 'primary', size: 23 }"></SvgIcon>
</a>
<span
@@ -81,7 +104,10 @@
</span>
<!-- <template v-if="store.login ? store.login.is_admin : false"> -->
<span class="tooltip">
<a class="mr-4" @click="currentField.mandatory ? false : doRemove()">
<a
class="mr-4"
@click="currentField.mandatory ? false : doRemove()"
>
<SvgIcon v-bind="{ name: 'bin.svg', type: 'primary', size: 23 }"></SvgIcon>
</a>
<span
@@ -94,11 +120,7 @@
<a
class="mr-4"
:class="currentField.format === 'number' ? null : 'has-text-grey-light'"
@click="
currentField.format === 'number'
? $emit('modalevent', { name: 'copyfield', data: currentField })
: false
"
@click="currentField.format === 'number' ? $emit('modalevent', { name: 'copyfield', data: currentField }) : false"
>
<SvgIcon v-bind="{ name: 'copy.svg', type: 'primary', size: 22 }"></SvgIcon>
</a>
@@ -109,7 +131,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="fieldList()">
<a
class="mr-4"
@click="fieldList()"
>
<SvgIcon v-bind="{ name: 'menu4.png', type: 'primary', size: 22 }"></SvgIcon>
</a>
<span
@@ -119,7 +144,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="createField()">
<a
class="mr-4"
@click="createField()"
>
<SvgIcon v-bind="{ name: 'add.png', type: 'primary', size: 22 }"></SvgIcon>
</a>
<span
@@ -129,7 +157,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="tableOption()">
<a
class="mr-4"
@click="tableOption()"
>
<SvgIcon v-bind="{ name: 'more.svg', type: 'primary', size: 22 }"></SvgIcon>
</a>
<span
@@ -139,7 +170,10 @@
>
</span>
<span class="tooltip">
<a class="mr-4" @click="saveSetting()">
<a
class="mr-4"
@click="saveSetting()"
>
<SvgIcon v-bind="{ name: 'save.svg', type: 'primary', size: 22 }"></SvgIcon>
</a>
<span
@@ -156,7 +190,7 @@
? currentField.formula
? true
: x.code !== 'formula'
: !['filter', 'formula'].find((y) => y === x.code)
: !['filter', 'formula'].find((y) => y === x.code),
)"
:key="i"
:class="selectTab.code === v.code ? 'is-active' : 'has-text-primary'"
@@ -193,35 +227,44 @@
/>
</div>
<div class="control">
<button class="button" @click="editLabel()">
<button
class="button"
@click="editLabel()"
>
<SvgIcon v-bind="{ name: 'pen.svg', type: 'dark', size: 19 }"></SvgIcon>
</button>
</div>
</div>
<p class="help is-danger" v-if="errors.find((v) => v.name === 'label')">
<p
class="help is-danger"
v-if="errors.find((v) => v.name === 'label')"
>
{{ errors.find((v) => v.name === "label").msg }}
</p>
<div class="field mt-3">
<label class="label fs-14"
>Kiểu dữ liệu<span class="has-text-danger"> * </span></label
>
<label class="label fs-14">Kiểu dữ liệu<span class="has-text-danger"> * </span></label>
<div class="control fs-14">
<span class="mr-4" v-for="(v, i) in datatype">
<span class="icon-text" v-if="radioType === v">
<SvgIcon
v-bind="{ name: 'radio-checked.svg', type: 'gray', size: 22 }"
></SvgIcon>
<span
class="mr-4"
v-for="(v, i) in datatype"
>
<span
class="icon-text"
v-if="radioType === v"
>
<SvgIcon v-bind="{ name: 'radio-checked.svg', type: 'gray', size: 22 }"></SvgIcon>
</span>
{{ v.name }}
</span>
</div>
</div>
<div class="field is-horizontal" v-if="currentField.format === 'number'">
<div
class="field is-horizontal"
v-if="currentField.format === 'number'"
>
<div class="field-body">
<div class="field">
<label class="label fs-14"
>Đơn vị <span class="has-text-danger"> * </span>
</label>
<label class="label fs-14">Đơn vị <span class="has-text-danger"> * </span> </label>
<div class="control">
<SearchBox
v-bind="{
@@ -234,7 +277,10 @@
@option="selected('_account', $event)"
></SearchBox>
</div>
<p class="help has-text-danger" v-if="errors.find((v) => v.name === 'unit')">
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'unit')"
>
{{ errors.find((v) => v.name === "unit").msg }}
</p>
</div>
@@ -261,7 +307,10 @@
class="mr-4"
v-for="(v, i) in colorchoice.filter((v) => v.code !== 'condition')"
>
<a class="icon-text" @click="changeTemplate(v)">
<a
class="icon-text"
@click="changeTemplate(v)"
>
<SvgIcon
v-bind="{
name: `radio-${radioTemplate === v.code ? '' : 'un'}checked.svg`,
@@ -277,11 +326,15 @@
</div>
</div>
</div>
<p class="mt-3" v-if="radioTemplate === 'option'">
<button class="button is-primary is-small has-text-white" @click="showSidebar()">
<span class="fs-14">{{
`${currentField.template ? "Sửa" : "Tạo"} định dạng`
}}</span>
<p
class="mt-3"
v-if="radioTemplate === 'option'"
>
<button
class="button is-primary is-small has-text-white"
@click="showSidebar()"
>
<span class="fs-14">{{ `${currentField.template ? "Sửa" : "Tạo"} định dạng` }}</span>
</button>
</p>
</div>
@@ -308,14 +361,7 @@
import { useStore } from "@/stores/index";
import ScrollBox from "~/components/datatable/ScrollBox";
const store = useStore();
const {
$copy,
$stripHtml,
$clone,
$arrayMove,
$snackbar,
$copyToClipboard,
} = useNuxtApp();
const { $copy, $stripHtml, $clone, $arrayMove, $snackbar, $copyToClipboard } = useNuxtApp();
var props = defineProps({
pagename: String,
field: Object,
@@ -337,9 +383,7 @@ const getMenu = function () {
let field = currentField;
field.disable = "display,tooltip";
let arr = field.disable ? field.disable.split(",") : undefined;
let array = arr
? store.menuchoice.filter((v) => arr.findIndex((x) => x === v.code) < 0)
: store.menuchoice;
let array = arr ? store.menuchoice.filter((v) => arr.findIndex((x) => x === v.code) < 0) : store.menuchoice;
//if (store.login ? !(store.login.is_admin === false) : true) array = [array[0]];
return array;
};
@@ -350,10 +394,7 @@ var value1 = undefined;
var value2 = undefined;
var moneyunit = store.moneyunit;
var radioType = store.datatype.find((v) => v.code === currentField.format);
var selectUnit =
currentField.format === "number"
? moneyunit.find((v) => v.detail === currentField.unit)
: undefined;
var selectUnit = currentField.format === "number" ? moneyunit.find((v) => v.detail === currentField.unit) : undefined;
var bgcolor = undefined;
var radioBGcolor = colorchoice.find((v) => v.code === "none");
var color = undefined;
@@ -366,16 +407,12 @@ var radioMaxWidth = colorchoice.find((v) => v.code === "none");
var maxwidth = undefined;
var selectAlign = undefined;
var radioAlign = colorchoice.find((v) => v.code === "none");
var radioTemplate = ref(
colorchoice.find((v) => v.code === (currentField.template ? "option" : "none"))["code"]
);
var radioTemplate = ref(colorchoice.find((v) => v.code === (currentField.template ? "option" : "none"))["code"]);
var selectPlacement = store.placement.find((v) => v.code === "is-right");
var selectScheme = store.colorscheme.find((v) => v.code === "is-primary");
var radioTooltip = store.colorchoice.find((v) => v.code === "none");
var selectField = undefined;
var tags = currentField.tags
? currentField.tags.map((v) => fields.find((x) => x.name === v))
: [];
var tags = currentField.tags ? currentField.tags.map((v) => fields.find((x) => x.name === v)) : [];
var formula = currentField.formula ? currentField.formula : undefined;
var decimal = currentField.decimal;
let shortmenu = store.menuchoice.filter((x) =>
@@ -383,7 +420,7 @@ let shortmenu = store.menuchoice.filter((x) =>
? currentField.formula
? true
: x.code !== "formula"
: !["filter", "formula"].find((y) => y === x.code)
: !["filter", "formula"].find((y) => y === x.code),
);
var selectTab = shortmenu.find((v) => selectTab.code === v.code)
? selectTab
@@ -448,9 +485,7 @@ function tableOption() {
}
const getFields = function () {
fields = pagedata ? $copy(pagedata.fields) : [];
fields.map(
(v) => (v.caption = (v.label ? v.label.indexOf("<") >= 0 : false) ? v.name : v.label)
);
fields.map((v) => (v.caption = (v.label ? v.label.indexOf("<") >= 0 : false) ? v.name : v.label));
};
const doSelect = function (evt) {
emit("modalevent", { name: "selected", data: evt[props.field.name] });
@@ -510,15 +545,16 @@ const saveSetting = function () {
const showSidebar = function () {
let event = { name: "template", field: currentField };
let title = "Danh sách cột";
if (event.name === "bgcolor")
title = `Đổi màu nền: ${event.field.name} / ${$stripHtml(event.field.label, 30)}`;
else if (event.name === "color")
title = `Đổi màu chữ: ${event.field.name} / ${$stripHtml(event.field.label, 30)}`;
else if (event.name === "template")
title = `Định dạng nâng cao: ${$stripHtml(event.field.label, 30)}`;
if (event.name === "bgcolor") title = `Đổi màu nền: ${event.field.name} / ${$stripHtml(event.field.label, 30)}`;
else if (event.name === "color") title = `Đổi màu chữ: ${event.field.name} / ${$stripHtml(event.field.label, 30)}`;
else if (event.name === "template") title = `Định dạng nâng cao: ${$stripHtml(event.field.label, 30)}`;
showmodal.value = {
component: "datatable/FormatOption",
vbind: { event: event, currentField: currentField, pagename: props.pagename },
vbind: {
event: event,
currentField: currentField,
pagename: props.pagename,
},
width: "850px",
height: "700px",
title: title,

View File

@@ -1,180 +1,312 @@
<template>
<div v-if="docid">
<div class="field is-horizontal">
<div class="field-body">
<div class="field">
<label class="label">Đối tượng</label>
<p class="control fs-14">
<b-radio v-for="(v,i) in types" :key="i" v-model="type"
:native-value="v" @input="changeType(v)">
{{v.name}}
</b-radio>
</p>
</div>
<div class="field">
<label class="label">Kích cỡ</label>
<p class="control fs-14">
<b-radio v-for="(v,i) in sizes.filter(v=>type? (type.code==='tag'? v.code!=='is-small' : 1>0) : true)" :key="i" v-model="size"
:native-value="v" @input="changeType(v)">
{{v.name}}
</b-radio>
</p>
</div>
<div class="field">
<label class="label" v-if="['tag'].find(v=>v===type.code)">Hình khối</label>
<p class="control fs-14" v-if="['tag'].find(v=>v===type.code)">
<b-radio v-for="(v,i) in shapes" :key="i" v-model="shape"
:native-value="v" @input="changeType(v)">
{{v.name}}
</b-radio>
</p>
</div>
</div>
</div>
<div class="field is-horizontal" v-if="['tag'].find(v=>v===type.code)">
<div class="field-body">
<div class="field" v-if="type.code!=='tag'">
<label class="label">Outline</label>
<div class="field">
<label class="label">Đối tượng</label>
<p class="control fs-14">
<b-radio v-for="(v,i) in outlines" :key="i" v-model="outline"
:native-value="v" @input="changeType(v)">
{{v.name}}
<b-radio
v-for="(v, i) in types"
:key="i"
v-model="type"
:native-value="v"
@input="changeType(v)"
>
{{ v.name }}
</b-radio>
</p>
</div>
<div class="field">
<label class="label">Kích cỡ</label>
<p class="control fs-14">
<b-radio
v-for="(v, i) in sizes.filter((v) =>
type ? (type.code === 'tag' ? v.code !== 'is-small' : 1 > 0) : true,
)"
:key="i"
v-model="size"
:native-value="v"
@input="changeType(v)"
>
{{ v.name }}
</b-radio>
</p>
</div>
<div class="field">
<label
class="label"
v-if="['tag'].find((v) => v === type.code)"
>Hình khối</label
>
<p
class="control fs-14"
v-if="['tag'].find((v) => v === type.code)"
>
<b-radio
v-for="(v, i) in shapes"
:key="i"
v-model="shape"
:native-value="v"
@input="changeType(v)"
>
{{ v.name }}
</b-radio>
</p>
</div>
</div>
</div>
<div class="tags" v-if="type.code==='tag'">
<a :class="getClass(v)" v-for="(v,i) in colorscheme" :key="i"
@click="doSelect(v)" :ref="'tag' + i"> {{v.name}} </a>
</div>
<div class="pt-2" v-else-if="type.code==='span'">
<a class="mr-3" :class="getSpanClass(v)" v-for="(v,i) in colorscheme" :key="i"
@click="doSelectSpan(v)" :ref="'span' + i"> {{v.name}} </a>
<div
class="field is-horizontal"
v-if="['tag'].find((v) => v === type.code)"
>
<div class="field-body">
<div
class="field"
v-if="type.code !== 'tag'"
>
<label class="label">Outline</label>
<p class="control fs-14">
<b-radio
v-for="(v, i) in outlines"
:key="i"
v-model="outline"
:native-value="v"
@input="changeType(v)"
>
{{ v.name }}
</b-radio>
</p>
</div>
</div>
</div>
<div :class="`tabs is-boxed mt-5 mb-5 ${tab.code==='template'? '' : 'pb-2'}`">
<div
class="tags"
v-if="type.code === 'tag'"
>
<a
:class="getClass(v)"
v-for="(v, i) in colorscheme"
:key="i"
@click="doSelect(v)"
:ref="'tag' + i"
>
{{ v.name }}
</a>
</div>
<div
class="pt-2"
v-else-if="type.code === 'span'"
>
<a
class="mr-3"
:class="getSpanClass(v)"
v-for="(v, i) in colorscheme"
:key="i"
@click="doSelectSpan(v)"
:ref="'span' + i"
>
{{ v.name }}
</a>
</div>
<div :class="`tabs is-boxed mt-5 mb-5 ${tab.code === 'template' ? '' : 'pb-2'}`">
<ul>
<li :class="tab.code===v.code? 'is-active' : ''"
v-for="(v,i) in tabs" :key="i" @click="changeTab(v)"><a class="fs-15">{{v.name}}</a>
<li
:class="tab.code === v.code ? 'is-active' : ''"
v-for="(v, i) in tabs"
:key="i"
@click="changeTab(v)"
>
<a class="fs-15">{{ v.name }}</a>
</li>
</ul>
</div>
<template v-if="tab.code==='selected'">
<a v-for="(v,i) in tags" :key="i" @click="selected=v">
<div class="field is-grouped is-grouped-multiline mt-4">
<p class="control">
<a :class="v.class">
{{v.name}}
<template v-if="tab.code === 'selected'">
<a
v-for="(v, i) in tags"
:key="i"
@click="selected = v"
>
<div class="field is-grouped is-grouped-multiline mt-4">
<p class="control">
<a :class="v.class">
{{ v.name }}
</a>
</p>
<p class="control">
<input
class="input is-small"
type="text"
v-model="v.name"
/>
</p>
<p class="control">
<a @click="remove(i)">
<SvgIcon v-bind="{ name: 'close.svg', type: 'danger', size: 22 }"></SvgIcon>
</a>
</p>
<p
class="control has-text-right ml-5"
v-if="selected ? selected.id === v.id : false"
>
<SvgIcon v-bind="{ name: 'tick.svg', type: 'primary', size: 22 }"></SvgIcon>
</p>
</div>
</a>
</p>
<p class="control">
<input class="input is-small" type="text" v-model="v.name">
</p>
<p class="control">
<a @click="remove(i)">
<SvgIcon v-bind="{name: 'close.svg', type: 'danger', size: 22}"></SvgIcon>
</a>
</p>
<p class="control has-text-right ml-5" v-if="selected? selected.id===v.id : false">
<SvgIcon v-bind="{name: 'tick.svg', type: 'primary', size: 22}"></SvgIcon>
</p>
</div>
</a>
</template>
</template>
<template v-else-if="tab.code==='condition'">
<div class="mb-5" v-if="selected">
<b-radio v-for="(v,i) in conditions" :key="i" v-model="condition"
:native-value="v" @input="changeCondition(v)">
{{v.name}}
</b-radio>
</div>
<template v-if="condition? condition.code==='yes' : false">
<div class="field mt-3">
<label class="label fs-14">Chọn trường xây dựng biểu thức <span class="has-text-danger"> * </span> </label>
<div class="control">
<b-taginput
size="is-small"
v-model="tagsField"
:data="pageData? pageData.fields.filter(v=>v.format==='number') : []"
type="is-dark is-light"
autocomplete
:open-on-focus="true"
field="name"
icon="plus"
placeholder="Chọn trường"
>
<template slot-scope="props">
<span class="mr-3 has-text-danger has-text-weight-bold"> {{props.option.name}}</span>
<span :class="tagsField.find(v=>v.id===props.option.id)? 'has-text-dark' : ''"> {{$stripHtml(props.option.label, 50)}} </span>
</template>
<template slot="empty">
Không trường thỏa mãn
</template>
</b-taginput>
<template v-else-if="tab.code === 'condition'">
<div
class="mb-5"
v-if="selected"
>
<b-radio
v-for="(v, i) in conditions"
:key="i"
v-model="condition"
:native-value="v"
@input="changeCondition(v)"
>
{{ v.name }}
</b-radio>
</div>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='tagsField')"> {{errors.find(v=>v.name==='tagsField').message}} </p>
</div>
<div class="field mt-1" v-if="tagsField.length>0">
<p class="help is-primary"> Click đúp vào để thêm vào biểu thức.</p>
<template v-if="condition ? condition.code === 'yes' : false">
<div class="field mt-3">
<label class="label fs-14"
>Chọn trường xây dựng biểu thức
<span class="has-text-danger"> * </span>
</label>
<div class="control">
<b-taginput
size="is-small"
v-model="tagsField"
:data="pageData ? pageData.fields.filter((v) => v.format === 'number') : []"
type="is-dark is-light"
autocomplete
:open-on-focus="true"
field="name"
icon="plus"
placeholder="Chọn trường"
>
<template slot-scope="props">
<span class="mr-3 has-text-danger has-text-weight-bold"> {{ props.option.name }}</span>
<span :class="tagsField.find((v) => v.id === props.option.id) ? 'has-text-dark' : ''">
{{ $stripHtml(props.option.label, 50) }}
</span>
</template>
<template slot="empty"> Không có trường thỏa mãn </template>
</b-taginput>
</div>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'tagsField')"
>
{{ errors.find((v) => v.name === "tagsField").message }}
</p>
</div>
<div
class="field mt-1"
v-if="tagsField.length > 0"
>
<p class="help is-primary">Click đúp vào để thêm vào biểu thức.</p>
<div class="tagsField">
<a @dblclick="expression = expression? (expression + ' ' + v.name) : v.name"
class="tag is-rounded" v-for="(v,i) in tagsField" :key="i">
<span class="tooltip">
{{v.name}}
<span class="tooltiptext">{{ $stripHtml(v.label) }}</span>
</span>
<a
@dblclick="expression = expression ? expression + ' ' + v.name : v.name"
class="tag is-rounded"
v-for="(v, i) in tagsField"
:key="i"
>
<span class="tooltip">
{{ v.name }}
<span class="tooltiptext">{{ $stripHtml(v.label) }}</span>
</span>
</a>
</div>
</div>
<div class="field">
<label class="label fs-14">Biểu thức dạng Đúng / Sai <span class="has-text-danger"> * </span> </label>
<label class="label fs-14"
>Biểu thức có dạng Đúng / Sai
<span class="has-text-danger"> * </span>
</label>
<p class="control is-expanded">
<input class="input" type="text" v-model="expression" placeholder="Tạo biểu thức tại đây">
</p>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='expression')"> {{errors.find(v=>v.name==='expression').message}} </p>
<input
class="input"
type="text"
v-model="expression"
placeholder="Tạo biểu thức tại đây"
/>
</p>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'expression')"
>
{{ errors.find((v) => v.name === "expression").message }}
</p>
</div>
</template>
</template>
<template v-else-if="tab.code==='option' && selected">
<div class="field is-horizontal border-bottom pb-2 mt-1">
</template>
</template>
<template v-else-if="tab.code === 'option' && selected">
<div class="field is-horizontal border-bottom pb-2 mt-1">
<div class="field-body">
<div class="field">
<label class="label fs-14">Màu nền </label>
<p class="control fs-14">
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioBGcolor"
:native-value="v" @input="changeStyle()">
{{v.name}}
</b-radio>
<b-radio
v-for="(v, i) in colorchoice.filter((v) => v.code !== 'condition')"
:key="i"
v-model="radioBGcolor"
:native-value="v"
@input="changeStyle()"
>
{{ v.name }}
</b-radio>
</p>
</div>
<div class="field" v-if="radioBGcolor? radioBGcolor.code==='option' : false">
<div
class="field"
v-if="radioBGcolor ? radioBGcolor.code === 'option' : false"
>
<label class="label fs-14"> Mã màu <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" v-model="bgcolor" @change="changeStyle()">
<input
type="color"
v-model="bgcolor"
@change="changeStyle()"
/>
</p>
</div>
</div>
</div>
<div class="field is-horizontal border-bottom pb-2">
<div class="field-body">
<div class="field">
<label class="label fs-14">Màu chữ </label>
<p class="control fs-14">
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioColor"
:native-value="v" @input="changeStyle()">
{{v.name}}
<b-radio
v-for="(v, i) in colorchoice.filter((v) => v.code !== 'condition')"
:key="i"
v-model="radioColor"
:native-value="v"
@input="changeStyle()"
>
{{ v.name }}
</b-radio>
</p>
</div>
<div class="field" v-if="radioColor? radioColor.code==='option' : false">
<div
class="field"
v-if="radioColor ? radioColor.code === 'option' : false"
>
<label class="label fs-14"> Mã màu <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" v-model="color" @change="changeStyle()">
<input
type="color"
v-model="color"
@change="changeStyle()"
/>
</p>
</div>
</div>
@@ -184,119 +316,189 @@
<div class="field">
<label class="label fs-14">Cỡ chữ </label>
<p class="control fs-14">
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioSize"
:native-value="v" @input="changeStyle()">
{{v.name}}
</b-radio>
<b-radio
v-for="(v, i) in colorchoice.filter((v) => v.code !== 'condition')"
:key="i"
v-model="radioSize"
:native-value="v"
@input="changeStyle()"
>
{{ v.name }}
</b-radio>
</p>
</div>
<div class="field" v-if="radioSize? radioSize.code==='option' : false">
<div
class="field"
v-if="radioSize ? radioSize.code === 'option' : false"
>
<label class="label fs-14"> Cỡ chữ <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input class="input is-small" type="text" placeholder="Nhập số" v-model="textsize" @change="changeStyle()">
<input
class="input is-small"
type="text"
placeholder="Nhập số"
v-model="textsize"
@change="changeStyle()"
/>
</p>
</div>
</div>
</div>
</template>
<template v-else-if="tab.code==='template'">
<p class="mb-3">
<a @click="copyContent()" class="mr-6">
<span class="icon-text">
<SvgIcon class="mr-2" v-bind="{name: 'copy.svg', type: 'primary', siz: 18}"></SvgIcon>
</template>
<template v-else-if="tab.code === 'template'">
<p class="mb-3">
<a
@click="copyContent()"
class="mr-6"
>
<span class="icon-text">
<SvgIcon
class="mr-2"
v-bind="{ name: 'copy.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Copy</span>
</span>
</a>
<a @click="paste()" class="mr-6">
<a
@click="paste()"
class="mr-6"
>
<span class="icon-text">
<SvgIcon class="mr-2" v-bind="{name: 'pen1.svg', type: 'primary', siz: 18}"></SvgIcon>
<SvgIcon
class="mr-2"
v-bind="{ name: 'pen1.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Paste</span>
</span>
</a>
</p>
<div>
<textarea class="textarea fs-14" rows="8" v-model="text" @dblclick="doCheck"></textarea>
</div>
<p class="mt-5">
<div>
<textarea
class="textarea fs-14"
rows="8"
v-model="text"
@dblclick="doCheck"
></textarea>
</div>
<p class="mt-5">
<span class="icon-text fsb-18">
Replace
<SvgIcon v-bind="{name: 'right.svg', type: 'dark', size: 22}"></SvgIcon>
<SvgIcon v-bind="{ name: 'right.svg', type: 'dark', size: 22 }"></SvgIcon>
</span>
</p>
<div class="field is-grouped mt-4">
<div class="control">
<p class="fsb-14 mb-1">Đoạn text</p>
<input class="input" type="text" placeholder="" v-model="source">
<input
class="input"
type="text"
placeholder=""
v-model="source"
/>
</div>
<div class="control">
<p class="fsb-14 mb-1">Thay bằng</p>
<input class="input" type="text" placeholder="" v-model="target">
<input
class="input"
type="text"
placeholder=""
v-model="target"
/>
</div>
<div class="control pl-5">
<button class="button is-primary is-outlined mt-5" @click="replace()">Replace</button>
<button
class="button is-primary is-outlined mt-5"
@click="replace()"
>
Replace
</button>
</div>
</div>
<p class="mt-5">
<button class="button is-primary has-text-white" @click="changeTemplate()">Áp dụng</button>
<button
class="button is-primary has-text-white"
@click="changeTemplate()"
>
Áp dụng
</button>
</p>
</template>
</div>
</template>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useStore } from '@/stores/index'
const store = useStore()
const { $id, $copy, $empty, $stripHtml, $calc, $remove, $copyToClipboard } = useNuxtApp()
var props = defineProps({
pagename: String,
field: Object
})
var colorscheme = store.colorscheme
var colorchoice = store.colorchoice
var pageData = store[props.pagename]
var field = props.field
var type = undefined
var size = undefined
var types = [{code: 'span', name: 'span'}, {code: 'tag', name: 'tag'}]
var sizes = [{code: 'is-small', name: 'Nhỏ', value: 'is-size-6'}, {code: 'is-normal', name: 'Trung bình', value: 'is-size-5'},
{code: 'is-medium', name: 'Lớn', value: 'is-size-4'}]
var shapes = [{code: 'default', name: 'Mặc định'}, {code: 'is-rounded', name: 'Tròn góc'}]
var shape = undefined
var outlines = [{code: 'default', name: 'Mặc định'}, {code: 'is-outlined', name: 'Outline'}]
var outline = undefined
var conditions = [{code: 'no', name: 'Không áp dụng'}, {code: 'yes', name: 'Có áp dụng'}]
var condition = undefined
var tags = []
var selected = undefined
var tabs = [{code: 'selected', name: 'Bước 1: Tạo nội dung'}, {code: 'condition', name: 'Bước 2: Đặt điều kiện'}, {code: 'option', name: 'Bước 3: Chọn màu, cỡ chữ'},
{code: 'template', name: 'Bước 4: Mã lệnh & áp dụng'}]
var tab = ref(undefined)
var tagsField = []
var errors = []
var expression = ''
var text = ref(null)
var radioBGcolor = undefined
var radioColor = undefined
var radioSize = undefined
var bgcolor = undefined
var color = undefined
var textsize = undefined
var source = undefined
var target = $copy(field.name)
const initData = function() {
type = types.find(v=>v.code==='tag')
size = sizes.find(v=>v.code==='is-normal')
shape = shapes.find(v=>v.code==='is-rounded')
outline = shapes.find(v=>v.code==='default')
if($empty(field.template)) tab.value =tabs.find(v=>v.code==='selected')
else {
text.value =$copy(field.template)
tab.value =tabs.find(v=>v.code==='template')
}
condition =conditions.find(v=>v.code==='no')
import { ref } from "vue";
import { useStore } from "@/stores/index";
const store = useStore();
const { $id, $copy, $empty, $stripHtml, $calc, $remove, $copyToClipboard } = useNuxtApp();
var props = defineProps({
pagename: String,
field: Object,
});
var colorscheme = store.colorscheme;
var colorchoice = store.colorchoice;
var pageData = store[props.pagename];
var field = props.field;
var type = undefined;
var size = undefined;
var types = [
{ code: "span", name: "span" },
{ code: "tag", name: "tag" },
];
var sizes = [
{ code: "is-small", name: "Nhỏ", value: "is-size-6" },
{ code: "is-normal", name: "Trung bình", value: "is-size-5" },
{ code: "is-medium", name: "Lớn", value: "is-size-4" },
];
var shapes = [
{ code: "default", name: "Mặc định" },
{ code: "is-rounded", name: "Tròn góc" },
];
var shape = undefined;
var outlines = [
{ code: "default", name: "Mặc định" },
{ code: "is-outlined", name: "Outline" },
];
var outline = undefined;
var conditions = [
{ code: "no", name: "Không áp dụng" },
{ code: "yes", name: "Có áp dụng" },
];
var condition = undefined;
var tags = [];
var selected = undefined;
var tabs = [
{ code: "selected", name: "Bước 1: Tạo nội dung" },
{ code: "condition", name: "Bước 2: Đặt điều kiện" },
{ code: "option", name: "Bước 3: Chọn màu, cỡ chữ" },
{ code: "template", name: "Bước 4: Mã lệnh & áp dụng" },
];
var tab = ref(undefined);
var tagsField = [];
var errors = [];
var expression = "";
var text = ref(null);
var radioBGcolor = undefined;
var radioColor = undefined;
var radioSize = undefined;
var bgcolor = undefined;
var color = undefined;
var textsize = undefined;
var source = undefined;
var target = $copy(field.name);
const initData = function () {
type = types.find((v) => v.code === "tag");
size = sizes.find((v) => v.code === "is-normal");
shape = shapes.find((v) => v.code === "is-rounded");
outline = shapes.find((v) => v.code === "default");
if ($empty(field.template)) tab.value = tabs.find((v) => v.code === "selected");
else {
text.value = $copy(field.template);
tab.value = tabs.find((v) => v.code === "template");
}
/*watch: {
condition = conditions.find((v) => v.code === "no");
};
/*watch: {
expression: function(newVal) {
if($empty(newVal)) return
elsecheckExpression()
@@ -332,100 +534,98 @@ const { $id, $copy, $empty, $stripHtml, $calc, $remove, $copyToClipboard } = use
}
}
},*/
function changeTab(v) {
tab.value = v
function changeTab(v) {
tab.value = v;
}
const paste = async function () {
text.value = await navigator.clipboard.readText();
};
const replace = function () {
if ($empty(text.value)) return;
text.value = text.value.replaceAll(source, target);
};
const doCheck = function () {
let text = window.getSelection().toString();
if ($empty(text)) return;
source = text;
};
const changeStyle = function () {
selected.bgcolor = selected.color = selected.textsize = selected.style = undefined;
let style = "";
if (radioBGcolor.code === "option" ? !$empty(bgcolor) : false) {
selected.bgcolor = bgcolor;
style += "background-color: " + bgcolor + " !important; ";
}
const paste = async function() {
text.value = await navigator.clipboard.readText()
if (radioColor.code === "option" ? !$empty(color) : false) {
selected.color = color;
style += "color: " + color + " !important; ";
}
if (radioSize.code === "option" ? $isNumber(textsize) : false) {
selected.textsize = textsize;
style += "font-size: " + textsize + "px !important; ";
}
$empty(style) ? false : (selected.style = style);
};
const changeCondition = function (v) {
if (v.code === "no") selected.expression = undefined;
};
const copyContent = function () {
$copyToClipboard(text.value);
};
const changeTemplate = function () {
let copy = pageData;
let found = copy.fields.find((v) => v.name === field.name);
found.template = text.value;
store.commit(props.pagename, copy);
};
const checkExpression = function () {
errors = [];
let val = $copy(expression);
let exp = $copy(expression);
tagsField.forEach((v) => {
let myRegExp = new RegExp(v.name, "g");
val = val.replace(myRegExp, Math.random());
exp = exp.replace(myRegExp, "formatNumber(row['" + v.name + "'])");
});
try {
let value = $calc(val);
if (isNaN(value) || value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) {
errors.push({ name: "expression", message: "Biểu thức không hợp lệ" });
} else if (!(eval(value) === true || eval(value) === false)) {
errors.push({ name: "expression", message: "Biểu thức không hợp lệ" });
} else if (selected) {
selected.expression = exp;
selected.formula = expression;
selected.tags = $copy(tagsField);
}
const replace = function() {
if($empty(text.value)) return
text.value =text.value.replaceAll(source,target)
}
const doCheck = function() {
let text = window.getSelection().toString()
if($empty(text)) return
source = text
}
const changeStyle = function() {
selected.bgcolor =selected.color =selected.textsize =selected.style = undefined
let style = ''
if(radioBGcolor.code==='option'? !$empty(bgcolor) : false) {
selected.bgcolor =bgcolor
style += 'background-color: ' +bgcolor + ' !important; '
}
if(radioColor.code==='option'? !$empty(color) : false) {
selected.color =color
style += 'color: ' +color + ' !important; '
}
if(radioSize.code==='option'?$isNumber(textsize) : false) {
selected.textsize =textsize
style += 'font-size: ' +textsize + 'px !important; '
}
$empty(style)? false :selected.style = style
}
const changeCondition = function(v) {
if(v.code==='no')selected.expression = undefined
}
const copyContent = function() {
$copyToClipboard(text.value)
}
const changeTemplate = function() {
let copy = pageData
let found = copy.fields.find(v=>v.name===field.name)
found.template = text.value
store.commit(props.pagename, copy)
}
const checkExpression = function() {
errors = []
let val =$copy(expression)
let exp =$copy(expression)
tagsField.forEach(v => {
let myRegExp = new RegExp(v.name, 'g')
val = val.replace(myRegExp, Math.random())
exp = exp.replace(myRegExp, "formatNumber(row['" + v.name + "'])")
})
try {
let value =$calc(val)
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
} else if(!(eval(value)===true || eval(value)===false)) {
errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
} else if(selected) {
selected.expression = exp
selected.formula =expression
selected.tags =$copy(tagsField)
}
}
catch(err) {
errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
}
returnerrors.length>0? false : true
}
const changeType = function(v) {
}
const doSelect = function(v) {
tags.push({id:$id(), name: v.name, class:getClass(v)})
tab =tabs.find(v=>v.code==='selected')
selected =tags[tags.length-1]
}
const doSelectSpan = function(v) {
tags.push({id:$id(), name: v.name, class:getSpanClass(v)})
tab =tabs.find(v=>v.code==='selected')
selected =tags[tags.length-1]
}
const remove = function(i) {
$remove(tags, i)
}
const getClass = function(v) {
let value =type.code + ' ' + v.code + ' ' +size.code + (shape.code==='default'? '' : ' ' +shape.code)
value += (outline.code==='default'? '' : ' ' +outline.code)
return value
}
const getSpanClass = function(v) {
let value = 'has-text-' + v.name.toLowerCase() + ' ' +size.value
return value
}
initData()
var docid = $id()
</script>
} catch (err) {
errors.push({ name: "expression", message: "Biểu thức không hợp lệ" });
}
returnerrors.length > 0 ? false : true;
};
const changeType = function (v) {};
const doSelect = function (v) {
tags.push({ id: $id(), name: v.name, class: getClass(v) });
tab = tabs.find((v) => v.code === "selected");
selected = tags[tags.length - 1];
};
const doSelectSpan = function (v) {
tags.push({ id: $id(), name: v.name, class: getSpanClass(v) });
tab = tabs.find((v) => v.code === "selected");
selected = tags[tags.length - 1];
};
const remove = function (i) {
$remove(tags, i);
};
const getClass = function (v) {
let value = type.code + " " + v.code + " " + size.code + (shape.code === "default" ? "" : " " + shape.code);
value += outline.code === "default" ? "" : " " + outline.code;
return value;
};
const getSpanClass = function (v) {
let value = "has-text-" + v.name.toLowerCase() + " " + size.value;
return value;
};
initData();
var docid = $id();
</script>

View File

@@ -1,193 +1,314 @@
<template>
<div class="columns mx-0">
<div class="column is-2">
<Caption class="mb-2" v-bind="{title: 'Tên model (bảng)', type: 'has-text-warning'}"></Caption>
<div class="mb-2">
<input class="input" v-model="text" placeholder="Tìm model" @change="findModel()">
<Caption
class="mb-2"
v-bind="{ title: 'Tên model (bảng)', type: 'has-text-warning' }"
></Caption>
<div class="mb-2">
<input
class="input"
v-model="text"
placeholder="Tìm model"
@change="findModel()"
/>
</div>
<div style="max-height: 80vh; overflow: auto">
<div
:class="`py-1 border-bottom is-clickable ${current.model === v.model ? 'has-background-primary has-text-white' : ''}`"
v-for="v in displayData"
@click="changeMenu(v)"
>
{{ v.model }}
</div>
</div>
</div>
<div style="max-height: 80vh; overflow: auto;">
<div :class="`py-1 border-bottom is-clickable ${current.model===v.model? 'has-background-primary has-text-white' : ''}`"
v-for="v in displayData" @click="changeMenu(v)">
{{ v.model}}
</div>
</div>
</div>
<div class="column is-10 py-0 px-0">
<div class="tabs mb-3">
<ul>
<li :class="`${v.code===tab? 'is-active has-text-weight-bold fs-18' : 'fs-18'}`" v-for="v in tabs">
<a @click="changeTab(v)">{{v.name}}</a>
</li>
</ul>
</div>
<div v-if="tab==='datatype'">
<Caption class="mb-2" v-bind="{title: 'Kiểu dữ liệu (type)', type: 'has-text-warning'}"></Caption>
<div style="max-height:75vh; overflow-y: auto;">
<div class="py-1 border-bottom is-clickable" v-for="x in current.fields">
{{ x.name}}
<span class="ml-6 has-text-grey">{{ x.type }}</span>
<a class="ml-6 has-text-primary" v-if="x.model" @click="openModel(x)">{{ x.model }}</a>
<div class="column is-10 py-0 px-0">
<div class="tabs mb-3">
<ul>
<li
:class="`${v.code === tab ? 'is-active has-text-weight-bold fs-18' : 'fs-18'}`"
v-for="v in tabs"
>
<a @click="changeTab(v)">{{ v.name }}</a>
</li>
</ul>
</div>
<div v-if="tab === 'datatype'">
<Caption
class="mb-2"
v-bind="{ title: 'Kiểu dữ liệu (type)', type: 'has-text-warning' }"
></Caption>
<div style="max-height: 75vh; overflow-y: auto">
<div
class="py-1 border-bottom is-clickable"
v-for="x in current.fields"
>
{{ x.name }}
<span class="ml-6 has-text-grey">{{ x.type }}</span>
<a
class="ml-6 has-text-primary"
v-if="x.model"
@click="openModel(x)"
>{{ x.model }}</a
>
</div>
</div>
</div>
<template v-else-if="tab === 'table'">
<div class="columns mx-0 mb-0 pb-0">
<div class="column is-5">
<Caption
class="mb-1"
v-bind="{ title: 'Values', type: 'has-text-warning' }"
></Caption>
<input
class="input"
rows="1"
v-model="values"
placeholder="Tên trường không chứa dấu cách, vd: code,name"
/>
</div>
<div class="column is-4">
<Caption
class="mb-1"
v-bind="{ title: 'Filter', type: 'has-text-warning' }"
></Caption>
<input
class="input"
rows="1"
v-model="filter"
placeholder="{'code': 'xyz'}"
/>
</div>
<div class="column is-2">
<Caption
class="mb-1"
v-bind="{ title: 'Sort', type: 'has-text-warning' }"
></Caption>
<input
class="input"
rows="1"
v-model="sort"
placeholder="vd: -code,name"
/>
</div>
<div class="column is-1">
<Caption
class="mb-1"
v-bind="{ title: 'Load', type: 'has-text-warning' }"
></Caption>
<div>
<button
class="button is-primary has-text-white"
@click="loadData()"
>
Load
</button>
</div>
</div>
</div>
<Caption
class="mb-1"
v-bind="{ title: 'Query', type: 'has-text-warning' }"
></Caption>
<div class="mb-2">
{{ query }}
<a
class="has-text-primary ml-5"
@click="copy()"
>copy</a
>
<p>
{{ apiUrl }}
<a
class="has-text-primary ml-5"
@click="$copyToClipboard(apiUrl)"
>copy</a
>
<a
class="has-text-primary ml-5"
target="_blank"
:href="apiUrl"
>open</a
>
</p>
</div>
<div>
<DataTable
v-bind="{ pagename: pagename }"
v-if="pagedata"
/>
</div>
</template>
<div v-else>
<img
id="image"
:src="filePath"
alt=""
/>
<p class="pl-5">
<a
class="mr-5"
@click="downloadFile()"
>
<SvgIcon v-bind="{ name: 'download.svg', type: 'black', size: 24 }"></SvgIcon>
</a>
<a
target="_blank"
:href="filePath"
>
<SvgIcon v-bind="{ name: 'open.svg', type: 'black', size: 24 }"></SvgIcon>
</a>
</p>
</div>
</div>
</div>
</div>
<template v-else-if="tab==='table'">
<div class="columns mx-0 mb-0 pb-0">
<div class="column is-5">
<Caption class="mb-1" v-bind="{title: 'Values', type: 'has-text-warning'}"></Caption>
<input class="input" rows="1" v-model="values" placeholder="Tên trường không chứa dấu cách, vd: code,name">
</div>
<div class="column is-4">
<Caption class="mb-1" v-bind="{title: 'Filter', type: 'has-text-warning'}"></Caption>
<input class="input" rows="1" v-model="filter" placeholder="{'code': 'xyz'}">
</div>
<div class="column is-2">
<Caption class="mb-1" v-bind="{title: 'Sort', type: 'has-text-warning'}"></Caption>
<input class="input" rows="1" v-model="sort" placeholder="vd: -code,name">
</div>
<div class="column is-1">
<Caption class="mb-1" v-bind="{title: 'Load', type: 'has-text-warning'}"></Caption>
<div>
<button class="button is-primary has-text-white" @click="loadData()">Load</button>
</div>
</div>
</div>
<Caption class="mb-1" v-bind="{title: 'Query', type: 'has-text-warning'}"></Caption>
<div class="mb-2">
{{ query }}
<a class="has-text-primary ml-5" @click="copy()">copy</a>
<p>{{apiUrl}}
<a class="has-text-primary ml-5" @click="$copyToClipboard(apiUrl)">copy</a>
<a class="has-text-primary ml-5" target="_blank" :href="apiUrl">open</a>
</p>
</div>
<div>
<DataTable v-bind="{pagename: pagename}" v-if="pagedata" />
</div>
</template>
<div v-else>
<img id="image" :src="filePath" alt="">
<p class="pl-5">
<a class="mr-5" @click="downloadFile()">
<SvgIcon v-bind="{name: 'download.svg', type: 'black', size: 24}"></SvgIcon>
</a>
<a target="_blank" :href="filePath">
<SvgIcon v-bind="{name: 'open.svg', type: 'black', size: 24}"></SvgIcon>
</a>
</p>
</div>
</div>
</div>
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"></Modal>
<Modal
@close="showmodal = undefined"
v-bind="showmodal"
v-if="showmodal"
></Modal>
</template>
<script setup>
import { useStore } from '@/stores/index'
const { $getdata, $getapi, $createField, $clone, $getpage, $empty, $copyToClipboard, $find, $multiSort, $download, $getpath } = useNuxtApp()
const store = useStore()
var pagename = 'pagedata3'
var pagedata = ref()
pagedata.value = $getpage()
pagedata.value.perPage = 10
store.commit(pagename, pagedata)
let list = ['LogEntry', 'Permission', 'ContentType', 'Session', 'Group']
var data = (await $getdata('getmodel')).filter(v=>list.findIndex(x=>x===v.model)<0)
data = $multiSort(data, {model: 'asc'})
var current = ref({fields: []})
var tabs = [{code: 'datatype', name: 'Kiểu dữ liệu'}, {code: 'table', name: 'Dữ liệu'}, {code: 'datamodel', name: 'Data model'}]
var tab = ref('datatype')
var datatable = ref()
var query = ref()
var values, filter
var apiUrl = ref()
var showmodal = ref()
var text = null
var displayData = ref(data)
var filePath = `${$getpath()}static/files/datamodel.png`
var sort = "-id"
current.value = data[0]
import { useStore } from "@/stores/index";
const {
$getdata,
$getapi,
$createField,
$clone,
$getpage,
$empty,
$copyToClipboard,
$find,
$multiSort,
$download,
$getpath,
} = useNuxtApp();
const store = useStore();
var pagename = "pagedata3";
var pagedata = ref();
pagedata.value = $getpage();
pagedata.value.perPage = 10;
store.commit(pagename, pagedata);
let list = ["LogEntry", "Permission", "ContentType", "Session", "Group"];
var data = (await $getdata("getmodel")).filter((v) => list.findIndex((x) => x === v.model) < 0);
data = $multiSort(data, { model: "asc" });
var current = ref({ fields: [] });
var tabs = [
{ code: "datatype", name: "Kiểu dữ liệu" },
{ code: "table", name: "Dữ liệu" },
{ code: "datamodel", name: "Data model" },
];
var tab = ref("datatype");
var datatable = ref();
var query = ref();
var values, filter;
var apiUrl = ref();
var showmodal = ref();
var text = null;
var displayData = ref(data);
var filePath = `${$getpath()}static/files/datamodel.png`;
var sort = "-id";
current.value = data[0];
function changeMenu(v) {
values = undefined
filter = undefined
sort = undefined
current.value = v
if(tab.value==='table') loadData()
values = undefined;
filter = undefined;
sort = undefined;
current.value = v;
if (tab.value === "table") loadData();
}
async function changeTab(v) {
tab.value = v.code
if(v.code==='table') loadData()
tab.value = v.code;
if (v.code === "table") loadData();
}
async function loadData() {
let vfilter = filter? filter.trim() : undefined
if(vfilter) {
let vfilter = filter ? filter.trim() : undefined;
if (vfilter) {
try {
vfilter = JSON.parse(vfilter)
vfilter = JSON.parse(vfilter);
} catch (error) {
alert('Cấu trúc filter lỗi')
vfilter = undefined
alert("Cấu trúc filter có lỗi");
vfilter = undefined;
}
}
let params = {values: $empty(values)? undefined : values.trim(), filter: filter, sort: $empty(sort)? undefined : sort.trim()}
let modelName = current.value.model
let found = {name: modelName.toLowerCase().replace('_', ''), url: `data/${modelName}/`, url_detail: `data-detail/${modelName}/`, params: params}
query.value = $clone(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.')
datatable.value = rs[0].data.rows
showData()
let params = {
values: $empty(values) ? undefined : values.trim(),
filter: filter,
sort: $empty(sort) ? undefined : sort.trim(),
};
let modelName = current.value.model;
let found = {
name: modelName.toLowerCase().replace("_", ""),
url: `data/${modelName}/`,
url_detail: `data-detail/${modelName}/`,
params: params,
};
query.value = $clone(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.");
datatable.value = rs[0].data.rows;
showData();
// api query
const baseUrl = $getpath() + `${query.value.url}`
apiUrl.value = baseUrl
let vparams = !$empty(values)? {values: values} : null
if(!$empty(filter)) {
vparams = vparams? {values: values, filter:filter} : {filter:filter}
const baseUrl = $getpath() + `${query.value.url}`;
apiUrl.value = baseUrl;
let vparams = !$empty(values) ? { values: values } : null;
if (!$empty(filter)) {
vparams = vparams ? { values: values, filter: filter } : { filter: filter };
}
if(!$empty(sort)) {
if(vparams) {
vparams.sort = sort.trim()
if (!$empty(sort)) {
if (vparams) {
vparams.sort = sort.trim();
} else {
vparams = {sort: sort.trim()}
vparams = { sort: sort.trim() };
}
}
if(vparams) {
if (vparams) {
let url = new URL(baseUrl);
let searchParams = new URLSearchParams(vparams);
url.search = searchParams.toString();
apiUrl.value = baseUrl + url.search
}
apiUrl.value = baseUrl + url.search;
}
}
function showData() {
let arr = []
if(!$empty(values)) {
let arr1 = values.trim().split(',')
arr1.map(v=>{
let val = v.trim()
let field = $createField(val, val, 'string', true)
arr.push(field)
})
let arr = [];
if (!$empty(values)) {
let arr1 = values.trim().split(",");
arr1.map((v) => {
let val = v.trim();
let field = $createField(val, val, "string", true);
arr.push(field);
});
} else {
current.value.fields.map(v=>{
let field = $createField(v.name, v.name, 'string', true)
arr.push(field)
})
current.value.fields.map((v) => {
let field = $createField(v.name, v.name, "string", true);
arr.push(field);
});
}
let clone = $clone(pagedata.value)
clone.fields = arr
clone.data = datatable.value
pagedata.value = undefined
setTimeout(()=>pagedata.value = clone)
let clone = $clone(pagedata.value);
clone.fields = arr;
clone.data = datatable.value;
pagedata.value = undefined;
setTimeout(() => (pagedata.value = clone));
}
function copy() {
$copyToClipboard(JSON.stringify(query.value))
$copyToClipboard(JSON.stringify(query.value));
}
function openModel(x) {
showmodal.value = {component: 'datatable/ModelInfo', title: x.model, width: '70%', height: '600px',
vbind: {data: data, info: $find(data, {model: x.model})}}
showmodal.value = {
component: "datatable/ModelInfo",
title: x.model,
width: "70%",
height: "600px",
vbind: { data: data, info: $find(data, { model: x.model }) },
};
}
function downloadFile() {
$download(`${$getpath()}download/?name=datamodel.png&type=file`, 'datamodel.png')
$download(`${$getpath()}download/?name=datamodel.png&type=file`, "datamodel.png");
}
function findModel() {
if($empty(text)) return displayData.value = data
displayData.value = data.filter(v=>v.model.toLowerCase().indexOf(text.toLowerCase())>=0)
if ($empty(text)) return (displayData.value = data);
displayData.value = data.filter((v) => v.model.toLowerCase().indexOf(text.toLowerCase()) >= 0);
}
</script>
</script>

View File

@@ -1,57 +1,104 @@
<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) : '') + '...&#931;' + v.select.length + ']') :
(v.condition))}}</span>
</div>
<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>
<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>
<span class="help has-text-black-bis">
{{
v.sort
? v.sort
: v.select
? "[" + (v.select.length > 0 ? $stripHtml(v.select[0], 20) : "") + "...&#931;" + 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 }"
@@ -124,7 +171,7 @@ watch(
() => store[props.pagename],
(newVal, oldVal) => {
updateChange();
}
},
);
function updateChange() {
pagedata = store[props.pagename];
@@ -141,24 +188,21 @@ function updateChange() {
const updateShow = function (full_data) {
// allowed JS expressions - should return a boolean
const allowedFns = {
'$getEditRights()': $getEditRights,
"$getEditRights()": $getEditRights,
};
const arr = pagedata.fields.filter(({ show }) => {
if (typeof show === 'boolean') return show;
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;
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
)
data.filter((ele, index) => index >= (currentPage - 1) * perPage && index < currentPage * perPage),
);
displayData.map((v) => {
arr.map((x) => (v[`${x.name}color`] = getStyle(x, v)));
@@ -222,9 +266,7 @@ const getStyle = function (field, record) {
field.bgcolor.map((v) => {
if (v.type === "search") {
if (
record[field.name] && !stop
? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase()) >= 0
: false
record[field.name] && !stop ? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase()) >= 0 : false
) {
val += ` background-color:${v.color}; `;
stop = true;
@@ -245,9 +287,7 @@ const getStyle = function (field, record) {
field.color.map((v) => {
if (v.type === "search") {
if (
record[field.name] && !stop
? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase()) >= 0
: false
record[field.name] && !stop ? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase()) >= 0 : false
) {
val += ` color:${v.color}; `;
stop = true;
@@ -268,9 +308,7 @@ const getStyle = function (field, record) {
field.textsize.map((v) => {
if (v.type === "search") {
if (
record[field.name] && !stop
? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase()) >= 0
: false
record[field.name] && !stop ? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase()) >= 0 : false
) {
val += ` font-size:${v.size}px; `;
stop = true;
@@ -283,10 +321,7 @@ const getStyle = function (field, record) {
}
}
});
} else
val += ` font-size:${
tablesetting.find((v) => v.code === "table-font-size").detail
}px;`;
} 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; `;
@@ -295,56 +330,28 @@ const getStyle = function (field, record) {
const getSettingStyle = function (name, field) {
let value = "";
if (name === "container") {
value =
"min-height:" +
tablesetting.find((v) => v.code === "container-height").detail +
"rem; ";
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 + "; ";
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 +
"; ";
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 += "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; ";
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 +
"; ");
? (value += "color:" + tablesetting.find((v) => v.code === "header-filter-color").detail + "; ")
: (value += "color:" + tablesetting.find((v) => v.code === "header-font-color").detail + "; ");
}
return value;
};
@@ -369,12 +376,8 @@ const frontendFilter = function (newVal) {
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)}`;
text += `${k > 0 ? (filter[k - 1].operator === "and" ? " &&" : " ||") : ""} ${$formatNumber(x[name])}
${y.condition === "=" ? "==" : y.condition === "<>" ? "!==" : y.condition} ${$formatNumber(y.value)}`;
});
return $calc(text);
}
@@ -385,11 +388,7 @@ const frontendFilter = function (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
);
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));
}
@@ -503,9 +502,7 @@ const updateData = async function (newVal) {
}
tablesetting = $copy(pagedata.tablesetting || gridsetting);
if (tablesetting) {
perPage = pagedata.perPage
? pagedata.perPage
: Number(tablesetting.find((v) => v.code === "per-page").detail);
perPage = pagedata.perPage ? pagedata.perPage : Number(tablesetting.find((v) => v.code === "per-page").detail);
}
if (newVal.fields) {
fields = $copy(newVal.fields);

View File

@@ -1,20 +1,46 @@
<template>
<TimeOption
v-bind="{ pagename: vpagename, api: api, timeopt: timeopt, filter: optfilter, importdata: props.importdata, newDataAvailable: newDataAvailable, params: vparams }"
ref="timeopt" @option="timeOption" @excel="exportExcel" @add="insert" @manual-refresh="manualRefresh" @refresh-data="refreshData"
@import="openImportModal" class="mb-3" v-if="timeopt"></TimeOption>
<DataTable v-bind="{ pagename: vpagename }" @edit="edit" @insert="insert" @dataevent="dataEvent" v-if="pagedata" />
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal" />
v-bind="{
pagename: vpagename,
api: api,
timeopt: timeopt,
filter: optfilter,
importdata: props.importdata,
newDataAvailable: newDataAvailable,
params: vparams,
}"
ref="timeopt"
@option="timeOption"
@excel="exportExcel"
@add="insert"
@manual-refresh="manualRefresh"
@refresh-data="refreshData"
@import="openImportModal"
class="mb-3"
v-if="timeopt"
></TimeOption>
<DataTable
v-bind="{ pagename: vpagename }"
@edit="edit"
@insert="insert"
@dataevent="dataEvent"
v-if="pagedata"
/>
<Modal
@close="showmodal = undefined"
v-bind="showmodal"
v-if="showmodal"
/>
</template>
<script setup>
import TimeOption from '~/components/datatable/TimeOption'
import { useStore } from '~/stores/index'
import TimeOption from "~/components/datatable/TimeOption";
import { useStore } from "~/stores/index";
// [FIX] Thêm onActivated, onDeactivated để xử lý KeepAlive
import { ref, watch, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
import { ref, watch, onBeforeUnmount, onActivated, onDeactivated } from "vue";
const emit = defineEmits(['modalevent', 'dataevent', 'dataUpdated'])
const store = useStore()
const emit = defineEmits(["modalevent", "dataevent", "dataUpdated"]);
const store = useStore();
const props = defineProps({
pagename: String,
@@ -26,43 +52,46 @@ const props = defineProps({
modal: Object,
timeopt: Object,
realtime: Object,
importdata: Object
})
importdata: Object,
});
const { $copy, $find, $findapi, $getapi, $setpage, $clone, $stripHtml, $snackbar, $dayjs } = useNuxtApp()
const { $copy, $find, $findapi, $getapi, $setpage, $clone, $stripHtml, $snackbar, $dayjs } = useNuxtApp();
const showmodal = ref()
const pagedata = ref()
const newDataAvailable = ref(false)
const pendingNewData = ref(null)
const lastDataHash = ref(null)
const pollingInterval = ref(null)
const showmodal = ref();
const pagedata = ref();
const newDataAvailable = ref(false);
const pendingNewData = ref(null);
const lastDataHash = ref(null);
const pollingInterval = ref(null);
let vpagename = props.pagename
let vfilter = props.filter ? $copy(props.filter) : undefined
let vparams = props.params ? $copy(props.params) : undefined
let connection = undefined
let optfilter = props.filter || (props.params ? props.params.filter : undefined)
let vpagename = props.pagename;
let vfilter = props.filter ? $copy(props.filter) : undefined;
let vparams = props.params ? $copy(props.params) : undefined;
let connection = undefined;
let optfilter = props.filter || (props.params ? props.params.filter : undefined);
const realtimeConfig = ref({ time: 0, update: "true" })
const realtimeConfig = ref({ time: 0, update: "true" });
if (props.realtime) {
realtimeConfig.value = { time: props.realtime.time || 0, update: props.realtime.update }
realtimeConfig.value = {
time: props.realtime.time || 0,
update: props.realtime.update,
};
}
if (vparams?.filter) {
for (const [key, value] of Object.entries(vparams.filter)) {
if (value.toString().indexOf('$') >= 0) {
vparams.filter[key] = store[value.replace('$', '')].id
if (value.toString().indexOf("$") >= 0) {
vparams.filter[key] = store[value.replace("$", "")].id;
}
}
}
const generateDataHash = (data) => {
if (!data) return null
if (!data) return null;
try {
const replacer = (key, value) =>
value && typeof value === 'object' && !Array.isArray(value)
value && typeof value === "object" && !Array.isArray(value)
? Object.keys(value)
.sort()
.reduce((sorted, key) => {
@@ -73,341 +102,356 @@ const generateDataHash = (data) => {
const stringToHash = JSON.stringify(data, replacer);
return stringToHash.split('').reduce((a, b) => {
a = ((a << 5) - a) + b.charCodeAt(0)
return a & a
}, 0)
return stringToHash.split("").reduce((a, b) => {
a = (a << 5) - a + b.charCodeAt(0);
return a & a;
}, 0);
} catch (e) {
console.error('Error generating data hash:', e);
return null
console.error("Error generating data hash:", e);
return null;
}
}
};
// [FIX] Tách hàm dừng polling ra riêng để tái sử dụng
const stopAutoCheck = () => {
if (pollingInterval.value) {
clearInterval(pollingInterval.value)
pollingInterval.value = null
clearInterval(pollingInterval.value);
pollingInterval.value = null;
}
}
};
const startAutoCheck = () => {
// [FIX] Dừng interval cũ trước khi tạo mới, tránh tạo nhiều interval chồng nhau
stopAutoCheck()
stopAutoCheck();
if (realtimeConfig.value.time && realtimeConfig.value.time > 0) {
pollingInterval.value = setInterval(() => checkDataChanges(), realtimeConfig.value.time * 1000)
pollingInterval.value = setInterval(() => checkDataChanges(), realtimeConfig.value.time * 1000);
}
}
};
const checkDataChanges = async () => {
try {
const connlist = []
const conn1 = $findapi(props.api)
const connlist = [];
const conn1 = $findapi(props.api);
if (vfilter) {
const filter = $copy(conn1.params.filter) || {}
const filter = $copy(conn1.params.filter) || {};
for (const [key, value] of Object.entries(vfilter)) {
filter[key] = value
filter[key] = value;
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
if (value.toString().indexOf("$") >= 0) {
filter[key] = store[value.replace("$", "")].id;
}
}
conn1.params.filter = filter
conn1.params.filter = filter;
}
if (vparams) conn1.params = $copy(vparams)
if (vparams) conn1.params = $copy(vparams);
delete conn1.params.sort
delete conn1.params.values
delete conn1.params.sort;
delete conn1.params.values;
conn1.params.summary = 'aggregate'
conn1.params.summary = "aggregate";
conn1.params.distinct_values = JSON.stringify({
total_count: { type: 'Count', field: 'id' },
last_updated: { type: 'Max', field: 'update_time' },
last_created: { type: 'Max', field: 'create_time' }
})
total_count: { type: "Count", field: "id" },
last_updated: { type: "Max", field: "update_time" },
last_created: { type: "Max", field: "create_time" },
});
connlist.push(conn1)
connlist.push(conn1);
const rs = await $getapi(connlist)
const obj = $find(rs, { name: props.api })
const newMetadata = obj ? obj.data.rows : {}
const newHash = generateDataHash(newMetadata)
const rs = await $getapi(connlist);
const obj = $find(rs, { name: props.api });
const newMetadata = obj ? obj.data.rows : {};
const newHash = generateDataHash(newMetadata);
if (lastDataHash.value === null) {
lastDataHash.value = newHash
return
lastDataHash.value = newHash;
return;
}
if (newHash !== lastDataHash.value) {
lastDataHash.value = newHash
lastDataHash.value = newHash;
if (realtimeConfig.value.update === "true") {
await loadFullDataAsync()
emit('dataUpdated', { newData: store[vpagename].data, autoUpdate: true, hasChanges: true })
await loadFullDataAsync();
emit("dataUpdated", {
newData: store[vpagename].data,
autoUpdate: true,
hasChanges: true,
});
} else {
newDataAvailable.value = true
emit('dataUpdated', { newData: null, autoUpdate: false, hasChanges: true })
newDataAvailable.value = true;
emit("dataUpdated", {
newData: null,
autoUpdate: false,
hasChanges: true,
});
}
}
} catch (error) {
console.error('Error checking data:', error)
console.error("Error checking data:", error);
}
}
};
const loadFullDataAsync = async () => {
try {
const connlist = []
const conn1 = $findapi(props.api)
const connlist = [];
const conn1 = $findapi(props.api);
if (vfilter) {
const filter = $copy(conn1.params.filter) || {}
const filter = $copy(conn1.params.filter) || {};
for (const [key, value] of Object.entries(vfilter)) {
filter[key] = value
filter[key] = value;
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
if (value.toString().indexOf("$") >= 0) {
filter[key] = store[value.replace("$", "")].id;
}
}
conn1.params.filter = filter
conn1.params.filter = filter;
}
if (vparams) conn1.params = $copy(vparams)
if (vparams) conn1.params = $copy(vparams);
delete conn1.params.summary
delete conn1.params.distinct_values
delete conn1.params.summary;
delete conn1.params.distinct_values;
connlist.push(conn1)
connlist.push(conn1);
const rs = await $getapi(connlist)
const obj = $find(rs, { name: props.api })
const newData = obj ? $copy(obj.data.rows) : []
const rs = await $getapi(connlist);
const obj = $find(rs, { name: props.api });
const newData = obj ? $copy(obj.data.rows) : [];
updateDataDisplay(newData)
updateDataDisplay(newData);
} catch (error) {
console.error('Error loading full data:', error)
console.error("Error loading full data:", error);
}
}
};
const openImportModal = () => {
const copy = $copy(props.importdata)
showmodal.value = copy
}
const copy = $copy(props.importdata);
showmodal.value = copy;
};
const updateDataDisplay = (newData) => {
const copy = $clone(store[vpagename])
copy.data = newData
copy.update = { data: newData }
store.commit(vpagename, copy)
newDataAvailable.value = false
pendingNewData.value = null
}
const copy = $clone(store[vpagename]);
copy.data = newData;
copy.update = { data: newData };
store.commit(vpagename, copy);
newDataAvailable.value = false;
pendingNewData.value = null;
};
const manualRefresh = () => {
if (pendingNewData.value) {
updateDataDisplay(pendingNewData.value)
updateDataDisplay(pendingNewData.value);
}
}
};
const refreshData = async () => {
stopAutoCheck()
stopAutoCheck();
newDataAvailable.value = false;
pendingNewData.value = null;
await getApi();
lastDataHash.value = null;
await checkDataChanges();
newDataAvailable.value = false;
startAutoCheck();
}
};
watch(() => props.realtime, (newVal) => {
if (newVal) {
realtimeConfig.value.time = newVal.time || 0
realtimeConfig.value.update = newVal.update === true
startAutoCheck()
}
}, { deep: true })
watch(
() => props.realtime,
(newVal) => {
if (newVal) {
realtimeConfig.value.time = newVal.time || 0;
realtimeConfig.value.update = newVal.update === true;
startAutoCheck();
}
},
{ deep: true },
);
onDeactivated(() => {
stopAutoCheck()
})
stopAutoCheck();
});
onActivated(() => {
startAutoCheck()
})
startAutoCheck();
});
onBeforeUnmount(() => {
stopAutoCheck()
})
stopAutoCheck();
});
const timeOption = (v) => {
if (!v) return getApi()
if (!v) return getApi();
if (v.filter_or) {
if (vfilter) vfilter = undefined
if (vfilter) vfilter = undefined;
if (vparams) {
vparams.filter_or = v.filter_or
vparams.filter_or = v.filter_or;
} else {
const found = $copy($findapi(props.api))
found.params.filter_or = v.filter_or
const found = $copy($findapi(props.api));
found.params.filter_or = v.filter_or;
if (props.filter) {
const filter = $copy(props.filter)
const filter = $copy(props.filter);
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
if (value.toString().indexOf("$") >= 0) {
filter[key] = store[value.replace("$", "")].id;
}
}
found.params.filter = filter
found.params.filter = filter;
}
vparams = found.params
vparams = found.params;
}
return getApi()
return getApi();
}
let filter = vfilter ? vfilter : (props.params ? props.params.filter || {} : {})
let filter = vfilter ? vfilter : props.params ? props.params.filter || {} : {};
for (const [key, value] of Object.entries(v.filter)) {
filter[key] = value
filter[key] = value;
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
if (value.toString().indexOf("$") >= 0) {
filter[key] = store[value.replace("$", "")].id;
}
}
if (vfilter) {
vfilter = filter
vparams = undefined
vfilter = filter;
vparams = undefined;
} else if (vparams) {
vparams.filter = filter
vparams.filter_or = undefined
vparams.filter = filter;
vparams.filter_or = undefined;
}
if (!vfilter && !vparams) vfilter = filter
getApi()
}
if (!vfilter && !vparams) vfilter = filter;
getApi();
};
const edit = (v) => {
const copy = props.modal ? $copy(props.modal) : {}
const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api, row: v }
f.pagename = vpagename
f.row = v
copy.vbind = f
showmodal.value = copy
}
const copy = props.modal ? $copy(props.modal) : {};
const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api, row: v };
f.pagename = vpagename;
f.row = v;
copy.vbind = f;
showmodal.value = copy;
};
const insert = () => {
const copy = props.modal ? $copy(props.modal) : {}
const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api }
f.pagename = vpagename
copy.vbind = f
showmodal.value = copy
}
const copy = props.modal ? $copy(props.modal) : {};
const f = copy.vbind ? copy.vbind : { pagename: vpagename, api: props.api };
f.pagename = vpagename;
copy.vbind = f;
showmodal.value = copy;
};
const getApi = async () => {
const connlist = []
let row = props.setting?.id ? $copy(props.setting) : undefined
const connlist = [];
let row = props.setting?.id ? $copy(props.setting) : undefined;
if (!row) {
const found = $find(store.settings.filter(v => v), props.setting > 0 ? { id: props.setting } : { name: props.setting })
if (found) row = $copy(found)
const found = $find(
store.settings.filter((v) => v),
props.setting > 0 ? { id: props.setting } : { name: props.setting },
);
if (found) row = $copy(found);
}
if (!row) {
const conn = $findapi('usersetting')
conn.params.filter = props.setting > 0 ? { id: props.setting } : { name: props.setting }
connlist.push(conn)
const conn = $findapi("usersetting");
conn.params.filter = props.setting > 0 ? { id: props.setting } : { name: props.setting };
connlist.push(conn);
}
let data = props.data ? $copy(props.data) : undefined
let data = props.data ? $copy(props.data) : undefined;
if (!data) {
const conn1 = $findapi(props.api)
const conn1 = $findapi(props.api);
if (vfilter) {
const filter = conn1.params.filter || {}
const filter = conn1.params.filter || {};
for (const [key, value] of Object.entries(vfilter)) {
filter[key] = value
filter[key] = value;
}
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf('$') >= 0) {
filter[key] = store[value.replace('$', '')].id
if (value.toString().indexOf("$") >= 0) {
filter[key] = store[value.replace("$", "")].id;
}
}
conn1.params.filter = filter
conn1.params.filter = filter;
}
if (vparams) conn1.params = vparams
connection = conn1
connlist.push(conn1)
if (vparams) conn1.params = vparams;
connection = conn1;
connlist.push(conn1);
}
let obj = undefined
let obj = undefined;
if (connlist.length > 0) {
const rs = await $getapi(connlist)
const ele = $find(rs, { name: 'usersetting' })
const rs = await $getapi(connlist);
const ele = $find(rs, { name: "usersetting" });
if (ele) {
row = $find(ele.data.rows, { name: props.setting.name || props.setting })
const copy = $copy(store.settings)
copy.push(row)
store.commit('settings', copy)
row = $find(ele.data.rows, { name: props.setting.name || props.setting });
const copy = $copy(store.settings);
copy.push(row);
store.commit("settings", copy);
}
obj = $find(rs, { name: props.api })
if (obj) data = $copy(obj.data.rows)
obj = $find(rs, { name: props.api });
if (obj) data = $copy(obj.data.rows);
}
pagedata.value = $setpage(vpagename, row, obj)
const copy = $clone(pagedata.value)
copy.data = data
copy.update = { data: data }
store.commit(vpagename, copy)
}
pagedata.value = $setpage(vpagename, row, obj);
const copy = $clone(pagedata.value);
copy.data = data;
copy.update = { data: data };
store.commit(vpagename, copy);
};
const dataEvent = (v, field, data) => {
if (data?.modal) {
const copy = $copy(data.modal)
const f = copy.vbind ? copy.vbind : {}
if (!f.api) f.api = props.api
f.pagename = vpagename
if (!f.row) f.row = v
copy.vbind = f
copy.field = field
showmodal.value = copy
const copy = $copy(data.modal);
const f = copy.vbind ? copy.vbind : {};
if (!f.api) f.api = props.api;
f.pagename = vpagename;
if (!f.row) f.row = v;
copy.vbind = f;
copy.field = field;
showmodal.value = copy;
}
emit('modalevent', { name: 'dataevent', data: { row: v, field: field } })
emit('dataevent', v, field)
}
emit("modalevent", { name: "dataevent", data: { row: v, field: field } });
emit("dataevent", v, field);
};
const exportExcel = async () => {
if (!props.api) return
if (!props.api) return;
const found = $findapi('exportcsv')
found.params = connection.params
const found = $findapi("exportcsv");
found.params = connection.params;
const fields = pagedata.value.fields
.filter(v => (v.show && v.export !== 'no') || v.export === 'yes')
.map(x => ({ name: x.name, label: $stripHtml(x.label) }))
found.params.fields = JSON.stringify(fields)
found.url = connection.url.replace('data/', 'exportcsv/')
const rs = await $getapi([found])
.filter((v) => (v.show && v.export !== "no") || v.export === "yes")
.map((x) => ({ name: x.name, label: $stripHtml(x.label) }));
found.params.fields = JSON.stringify(fields);
found.url = connection.url.replace("data/", "exportcsv/");
const rs = await $getapi([found]);
if (rs === 'error') {
$snackbar('Đã xảy ra lỗi. Vui lòng thử lại.')
return
if (rs === "error") {
$snackbar("Đã xảy ra lỗi. Vui lòng thử lại.");
return;
}
const url = window.URL.createObjectURL(new Blob([rs[0].data]))
const link = document.createElement('a')
const fileName = `${$dayjs(new Date()).format('YYYYMMDDhhmmss')}-data.csv`
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
link.remove()
}
const url = window.URL.createObjectURL(new Blob([rs[0].data]));
const link = document.createElement("a");
const fileName = `${$dayjs(new Date()).format("YYYYMMDDhhmmss")}-data.csv`;
link.href = url;
link.setAttribute("download", fileName);
document.body.appendChild(link);
link.click();
link.remove();
};
if (!props.timeopt) await getApi()
startAutoCheck()
</script>
if (!props.timeopt) await getApi();
startAutoCheck();
</script>

View File

@@ -1,82 +1,111 @@
<template>
<div>
<p class="fsb-20 mb-5">Điều chỉnh tiêu đề</p>
<div v-for="(v, i) in arr" :key="i" :class="(i>0? 'mt-4' : null)">
<p class="fsb-14">Dòng thứ {{(i+1)}}<span class="has-text-danger"> *</span></p>
<div class="field has-addons mt-1">
<div class="control is-expanded">
<input class="input" type="text" v-model="v.label">
</div>
<div class="control">
<a class="button px-2 is-primary" @click="add()">
<span>
<SvgIcon v-bind="{name: 'add1.png', type: 'white', size: 17}"></SvgIcon></span>
</a>
</div>
<div class="control" @click="remove(i)" v-if="(i>0)">
<a class="button px-2 is-dark">
<span>
<SvgIcon v-bind="{name: 'bin.svg', type: 'white', size: 17}"></SvgIcon>
</span>
</a>
</div>
</div>
<p class="help has-text-danger" v-if="v.error"> {{v.error}} </p>
</div>
<div class="buttons mt-5">
<button class="button is-primary has-text-white" @click="update()">Cập nhật</button>
<button class="button is-dark" @click="$emit('close')">Hủy bỏ</button>
</div>
<div
v-for="(v, i) in arr"
:key="i"
:class="i > 0 ? 'mt-4' : null"
>
<p class="fsb-14">Dòng thứ {{ i + 1 }}<span class="has-text-danger"> *</span></p>
<div class="field has-addons mt-1">
<div class="control is-expanded">
<input
class="input"
type="text"
v-model="v.label"
/>
</div>
<div class="control">
<a
class="button px-2 is-primary"
@click="add()"
>
<span> <SvgIcon v-bind="{ name: 'add1.png', type: 'white', size: 17 }"></SvgIcon></span>
</a>
</div>
<div
class="control"
@click="remove(i)"
v-if="i > 0"
>
<a class="button px-2 is-dark">
<span>
<SvgIcon v-bind="{ name: 'bin.svg', type: 'white', size: 17 }"></SvgIcon>
</span>
</a>
</div>
</div>
<p
class="help has-text-danger"
v-if="v.error"
>
{{ v.error }}
</p>
</div>
<div class="buttons mt-5">
<button
class="button is-primary has-text-white"
@click="update()"
>
Cập nhật
</button>
<button
class="button is-dark"
@click="$emit('close')"
>
Hủy bỏ
</button>
</div>
</div>
</template>
<script>
export default {
props: ['label'],
props: ["label"],
data() {
return {
arr: []
}
arr: [],
};
},
created() {
let arr1 = this.label.replace('<div>', '').replace('</div>', '').split("</p>")
arr1.map(v=>{
if(!this.$empty(v)) {
let label = v + '</p>'
label = this.$stripHtml(label)
this.arr.push({label: label})
let arr1 = this.label.replace("<div>", "").replace("</div>", "").split("</p>");
arr1.map((v) => {
if (!this.$empty(v)) {
let label = v + "</p>";
label = this.$stripHtml(label);
this.arr.push({ label: label });
}
})
});
},
methods: {
add() {
this.arr.push({label: undefined})
this.arr.push({ label: undefined });
},
remove(i) {
this.$remove(this.arr, i)
this.$remove(this.arr, i);
},
checkError() {
let error = false
this.arr.map(v=>{
if(this.$empty(v.label)) {
v.error = 'Nội dung không được bỏ trống'
error = true
let error = false;
this.arr.map((v) => {
if (this.$empty(v.label)) {
v.error = "Nội dung không được bỏ trống";
error = true;
}
})
if(error) this.arr = this.$copy(this.arr)
return error
});
if (error) this.arr = this.$copy(this.arr);
return error;
},
update() {
if(this.checkError()) return
let label = ''
if(this.arr.length>1) {
this.arr.map((v,i)=>{
label += `<p${i<this.arr.length-1? ' style="border-bottom: 1px solid white;"' : ''}>${v.label.trim()}</p>`
})
label = `<div>${label}</div>`
} else label = this.arr[0].label.trim()
this.$emit('modalevent', {name: 'label', data: label})
this.$emit('close')
}
}
}
</script>
if (this.checkError()) return;
let label = "";
if (this.arr.length > 1) {
this.arr.map((v, i) => {
label += `<p${i < this.arr.length - 1 ? ' style="border-bottom: 1px solid white;"' : ""}>${v.label.trim()}</p>`;
});
label = `<div>${label}</div>`;
} else label = this.arr[0].label.trim();
this.$emit("modalevent", { name: "label", data: label });
this.$emit("close");
},
},
};
</script>

View File

@@ -1,88 +1,127 @@
<template>
<div>
<template v-if="keys.length>0">
<div class="field is-horizontal" v-for="(v,i) in keys" :key="i">
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<input class="input fs-14" type="text" placeholder="" v-model="keys[i]">
</div>
</div>
<div class="field">
<div class="control">
<input class="input fs-14" type="text" placeholder="" v-model="values[i]">
<template v-if="keys.length > 0">
<div
class="field is-horizontal"
v-for="(v, i) in keys"
:key="i"
>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<input
class="input fs-14"
type="text"
placeholder=""
v-model="keys[i]"
/>
</div>
</div>
<div class="field">
<div class="control">
<input
class="input fs-14"
type="text"
placeholder=""
v-model="values[i]"
/>
</div>
</div>
<div class="field is-narrow">
<p class="control">
<a @click="addAttr()">
<SvgIcon v-bind="{ name: 'add1.png', type: 'gray', size: 18 }"></SvgIcon>
</a>
<a
class="ml-2"
@click="remove(i)"
>
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'gray', size: 18 }"></SvgIcon>
</a>
<a
class="ml-2"
@click="jsonData(v, i)"
>
<SvgIcon v-bind="{ name: 'apps.svg', type: 'gray', size: 18 }"></SvgIcon>
</a>
</p>
</div>
</div>
</div>
</div>
<div class="field is-narrow">
<p class="control">
<a @click="addAttr()">
<SvgIcon v-bind="{name: 'add1.png', type: 'gray', size: 18}"></SvgIcon>
</a>
<a class="ml-2" @click="remove(i)">
<SvgIcon v-bind="{name: 'bin1.svg', type: 'gray', size: 18}"></SvgIcon>
</a>
<a class="ml-2" @click="jsonData(v, i)">
<SvgIcon v-bind="{name: 'apps.svg', type: 'gray', size: 18}"></SvgIcon>
</a>
</p>
</div>
</div>
</div>
</template>
<div class="mb-6" v-else>
<button class="button is-primary has-text-white" @click="addAttr()">Thêm thuộc tính</button>
<div
class="mb-6"
v-else
>
<button
class="button is-primary has-text-white"
@click="addAttr()"
>
Thêm thuộc tính
</button>
</div>
<div class="buttons mt-5">
<a class="button is-primary has-text-white" @click="update()">Cập nhật</a>
<a
class="button is-primary has-text-white"
@click="update()"
>Cập nhật</a
>
</div>
<Modal @close="comp=undefined" @update="doUpdate"
v-bind="{component: comp, width: '40%', height: '300px', vbind: vbind}" v-if="comp"></Modal>
<Modal
@close="comp = undefined"
@update="doUpdate"
v-bind="{ component: comp, width: '40%', height: '300px', vbind: vbind }"
v-if="comp"
></Modal>
</div>
</template>
<script>
export default {
props: ['field', 'close'],
data() {
return {
keys: [],
values: [],
comp: undefined,
vbind: undefined,
current: undefined
}
</template>
<script>
export default {
props: ["field", "close"],
data() {
return {
keys: [],
values: [],
comp: undefined,
vbind: undefined,
current: undefined,
};
},
created() {
Object.keys(this.field).map((v) => {
this.keys.push(v);
this.values.push(this.field[v]);
});
},
methods: {
doUpdate(v) {
this.values[this.current.i] = v;
},
created() {
Object.keys(this.field).map(v=>{
this.keys.push(v)
this.values.push(this.field[v])
})
jsonData(v, i) {
this.current = { v: v, i: i };
this.vbind = {
field: this.$empty(this.values[i]) || typeof this.values[i] === "string" ? {} : this.values[i],
close: true,
};
this.comp = "datatable/FieldAttribute";
},
methods: {
doUpdate(v) {
this.values[this.current.i] = v
},
jsonData(v, i) {
this.current = {v: v, i: i}
this.vbind = {field: this.$empty(this.values[i]) || typeof this.values[i] === 'string'? {} : this.values[i], close: true}
this.comp = 'datatable/FieldAttribute'
},
addAttr() {
this.keys.push(undefined)
this.values.push(undefined)
},
remove(i) {
this.$remove(this.keys, i)
this.$remove(this.values, i)
},
update() {
let obj = {}
this.keys.map((v,i)=>{
if(!this.$empty(v)) obj[v] = v.indexOf('__in')>0? this.values[i].split(',') : this.values[i]
})
this.$emit('update', obj)
this.$emit('modalevent', {name: 'update', data: obj})
if(this.close) this.$emit('close')
}
}
}
</script>
addAttr() {
this.keys.push(undefined);
this.values.push(undefined);
},
remove(i) {
this.$remove(this.keys, i);
this.$remove(this.values, i);
},
update() {
let obj = {};
this.keys.map((v, i) => {
if (!this.$empty(v)) obj[v] = v.indexOf("__in") > 0 ? this.values[i].split(",") : this.values[i];
});
this.$emit("update", obj);
this.$emit("modalevent", { name: "update", data: obj });
if (this.close) this.$emit("close");
},
},
};
</script>

View File

@@ -1,52 +1,88 @@
<template>
<div>
<div class="field mt-3 mb-1" v-if="field.format==='number'">
<div
class="field mt-3 mb-1"
v-if="field.format === 'number'"
>
<label class="label fs-14">Chọn trường<span class="has-text-danger"> * </span> </label>
<div class="control">
<b-taginput
size="is-small"
size="is-small"
v-model="tagsField"
:data="pageData? pageData.fields.filter(v=>v.format==='number') : []"
:data="pageData ? pageData.fields.filter((v) => v.format === 'number') : []"
type="is-dark is-light"
autocomplete
:open-on-focus="true"
field="name"
icon="plus"
placeholder="Chọn trường"
>
>
<template slot-scope="props">
<span class="mr-3 has-text-danger has-text-weight-bold"> {{props.option.name}}</span>
<span :class="tagsField.find(v=>v.id===props.option.id)? 'has-text-dark' : ''"> {{$stripHtml(props.option.label, 60)}} </span>
</template>
<template slot="empty">
Không trường thỏa mãn
<span class="mr-3 has-text-danger has-text-weight-bold"> {{ props.option.name }}</span>
<span :class="tagsField.find((v) => v.id === props.option.id) ? 'has-text-dark' : ''">
{{ $stripHtml(props.option.label, 60) }}
</span>
</template>
<template slot="empty"> Không trường thỏa mãn </template>
</b-taginput>
</div>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='tagsField')"> {{errors.find(v=>v.name==='tagsField').message}} </p>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'tagsField')"
>
{{ errors.find((v) => v.name === "tagsField").message }}
</p>
</div>
<div class="mt-2" v-if="tagsField.length>0">
<a @dblclick="expression = expression? (expression + ' ' + v.name) : v.name"
class="tag is-rounded" v-for="(v,i) in tagsField" :key="i">
<span class="tooltip">
{{ v.name }}
<span class="tooltiptext" style="top: 60%; bottom: unset; min-width: max-content; left: 25px;">{{ $stripHtml(v.label) }}</span>
</span>
<div
class="mt-2"
v-if="tagsField.length > 0"
>
<a
@dblclick="expression = expression ? expression + ' ' + v.name : v.name"
class="tag is-rounded"
v-for="(v, i) in tagsField"
:key="i"
>
<span class="tooltip">
{{ v.name }}
<span
class="tooltiptext"
style="top: 60%; bottom: unset; min-width: max-content; left: 25px"
>{{ $stripHtml(v.label) }}</span
>
</span>
</a>
</div>
<div class="field is-horizontal mt-3">
<div class="field-body">
<div class="field" v-if="field.format==='number'">
<label class="label fs-14">Biểu thức dạng Đúng / Sai <span class="has-text-danger"> * </span> </label>
<div class="field is-horizontal mt-3">
<div class="field-body">
<div
class="field"
v-if="field.format === 'number'"
>
<label class="label fs-14"
>Biểu thức dạng Đúng / Sai
<span class="has-text-danger"> * </span>
</label>
<p class="control is-expanded">
<input class="input is-small" type="text" v-model="expression">
</p>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='expression')"> {{errors.find(v=>v.name==='expression').message}} </p>
<input
class="input is-small"
type="text"
v-model="expression"
/>
</p>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'expression')"
>
{{ errors.find((v) => v.name === "expression").message }}
</p>
</div>
<div class="field" v-else>
<label class="label"> Chuỗi tự <span class="has-text-danger"> * </span>
</label>
<div
class="field"
v-else
>
<label class="label"> Chuỗi tự <span class="has-text-danger"> * </span> </label>
<p class="control">
<input
class="input is-small"
@@ -63,27 +99,53 @@
{{ errors.find((v) => v.name === "searchText").msg }}
</p>
</div>
<div class="field is-narrow" v-if="filterType==='color'">
<label class="label fs-14"> màu <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" v-model="color" @change="changeStyle()">
</p>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='color')"> {{errors.find(v=>v.name==='color').message}} </p>
</div>
<div class="field is-narrow" v-else-if="filterType==='size'">
<label class="label fs-14"> Cỡ chữ <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input class="input is-small" type="text" placeholder="Nhập số" v-model="size" @change="changeStyle()">
</p>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='size')"> {{errors.find(v=>v.name==='size').message}} </p>
</div>
<div
class="field is-narrow"
v-if="filterType === 'color'"
>
<label class="label fs-14"> màu <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
v-model="color"
@change="changeStyle()"
/>
</p>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'color')"
>
{{ errors.find((v) => v.name === "color").message }}
</p>
</div>
<div
class="field is-narrow"
v-else-if="filterType === 'size'"
>
<label class="label fs-14"> Cỡ chữ <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
placeholder="Nhập số"
v-model="size"
@change="changeStyle()"
/>
</p>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'size')"
>
{{ errors.find((v) => v.name === "size").message }}
</p>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['filterObj', 'filterType', 'pagename', 'field'],
props: ["filterObj", "filterType", "pagename", "field"],
data() {
return {
tagsField: [],
@@ -92,79 +154,113 @@ export default {
color: undefined,
size: undefined,
errors: [],
searchText: undefined
}
searchText: undefined,
};
},
created() {
this.color = this.filterObj.color
this.size = this.filterObj.size
this.expression = this.filterObj.expression? this.filterObj.expression : this.field.name
if(this.filterObj.tags) {
this.filterObj.tags.map(v=>{
this.tagsField.push(this.pageData.fields.find(x=>x.name===v))
})
} else if(this.field.format==='number') this.tagsField.push(this.pageData.fields.find(v=>v.name===this.field.name))
this.color = this.filterObj.color;
this.size = this.filterObj.size;
this.expression = this.filterObj.expression ? this.filterObj.expression : this.field.name;
if (this.filterObj.tags) {
this.filterObj.tags.map((v) => {
this.tagsField.push(this.pageData.fields.find((x) => x.name === v));
});
} else if (this.field.format === "number")
this.tagsField.push(this.pageData.fields.find((v) => v.name === this.field.name));
},
watch: {
expression: function(newVal) {
if(this.$empty(newVal)) return
else this.changeStyle()
}
expression: function (newVal) {
if (this.$empty(newVal)) return;
else this.changeStyle();
},
},
computed: {
colorscheme: {
get: function() {return this.$store.state.colorscheme},
set: function(val) {this.$store.commit("updateColorScheme", {colorscheme: val})}
get: function () {
return this.$store.state.colorscheme;
},
set: function (val) {
this.$store.commit("updateColorScheme", { colorscheme: val });
},
},
pageData: {
get: function() {return this.$store.state[this.pagename]},
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
get: function () {
return this.$store.state[this.pagename];
},
set: function (val) {
this.$store.commit("updateStore", { name: this.pagename, data: val });
},
},
colorchoice: {
get: function() {return this.$store.state.colorchoice},
set: function(val) {this.$store.commit("updateColorChoice", {colorchoice: val})}
}
get: function () {
return this.$store.state.colorchoice;
},
set: function (val) {
this.$store.commit("updateColorChoice", { colorchoice: val });
},
},
},
methods: {
changeStyle() {
let check = this.field.format==='number'? this.checkExpression() : this.checkCondition()
if(!check) return
var row = this.field.format==='number'? {expression: this.expression, tags: this.tagsField.map(v=>v.name)}
: {keyword: this.searchText, type: 'search'}
this.filterType==='color'? row.color = this.color : row.size = this.size
this.$emit('databack', row)
let check = this.field.format === "number" ? this.checkExpression() : this.checkCondition();
if (!check) return;
var row =
this.field.format === "number"
? {
expression: this.expression,
tags: this.tagsField.map((v) => v.name),
}
: { keyword: this.searchText, type: "search" };
this.filterType === "color" ? (row.color = this.color) : (row.size = this.size);
this.$emit("databack", row);
},
checkCondition() {
this.errors = []
if(this.filterType==='color' && this.$empty(this.color)) this.errors.push({name: 'color', message: 'Chọn màu'})
if(this.filterType==='size' && this.$empty(this.size)) this.errors.push({name: 'size', message: 'Nhập cỡ chữ'})
if(this.$empty(this.searchText)) this.errors.push({name: 'searchText', message: 'Chưa nhập chuỗi kí tự'})
return this.errors.length>0? false : true
this.errors = [];
if (this.filterType === "color" && this.$empty(this.color))
this.errors.push({ name: "color", message: "Chọn màu" });
if (this.filterType === "size" && this.$empty(this.size))
this.errors.push({ name: "size", message: "Nhập cỡ chữ" });
if (this.$empty(this.searchText))
this.errors.push({
name: "searchText",
message: "Chưa nhập chuỗi kí tự",
});
return this.errors.length > 0 ? false : true;
},
checkExpression() {
this.errors = []
if(this.filterType==='color' && this.$empty(this.color)) this.errors.push({name: 'color', message: 'Chọn màu'})
if(this.filterType==='size' && this.$empty(this.size)) this.errors.push({name: 'size', message: 'Nhập cỡ chữ'})
let val = this.$copy(this.expression)
let exp = this.$copy(this.expression)
this.tagsField.forEach(v => {
let myRegExp = new RegExp(v.name, 'g')
val = val.replace(myRegExp, Math.random())
exp = exp.replace(myRegExp, "field.formatNumber(row['" + v.name + "'])")
})
try {
let value = this.$calc(val)
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
} else if(!(eval(value)===true || eval(value)===false)) {
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
this.errors = [];
if (this.filterType === "color" && this.$empty(this.color))
this.errors.push({ name: "color", message: "Chọn màu" });
if (this.filterType === "size" && this.$empty(this.size))
this.errors.push({ name: "size", message: "Nhập cỡ chữ" });
let val = this.$copy(this.expression);
let exp = this.$copy(this.expression);
this.tagsField.forEach((v) => {
let myRegExp = new RegExp(v.name, "g");
val = val.replace(myRegExp, Math.random());
exp = exp.replace(myRegExp, "field.formatNumber(row['" + v.name + "'])");
});
try {
let value = this.$calc(val);
if (isNaN(value) || value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) {
this.errors.push({
name: "expression",
message: "Biểu thức không hợp lệ",
});
} else if (!(eval(value) === true || eval(value) === false)) {
this.errors.push({
name: "expression",
message: "Biểu thức không hợp lệ",
});
}
}
catch(err) {
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
} catch (err) {
this.errors.push({
name: "expression",
message: "Biểu thức không hợp lệ",
});
}
return this.errors.length>0? false : true
}
}
}
</script>
return this.errors.length > 0 ? false : true;
},
},
};
</script>

View File

@@ -5,7 +5,12 @@
</p>
<div class="tabs is-boxed">
<ul>
<li v-for="(v, i) in tabs" :key="i" :class="tab.code === v.code ? 'is-active' : ''" @click="tab = v">
<li
v-for="(v, i) in tabs"
:key="i"
:class="tab.code === v.code ? 'is-active' : ''"
@click="tab = v"
>
<a>{{ v.name }}</a>
</li>
</ul>
@@ -14,17 +19,37 @@
<template v-if="tab.code === 'expression' && ['bgcolor', 'color', 'textsize'].find((x) => x === sideBar)">
<template v-if="radio ? radio.code === 'condition' && sideBar === 'bgcolor' : false">
<div v-for="(v, i) in bgcolorFilter" :key="v.id" class="px-4">
<div
v-for="(v, i) in bgcolorFilter"
:key="v.id"
class="px-4"
>
<FilterOption
v-bind="{ filterObj: v, filterType: 'color', pagename: pagename, field: openField }"
v-bind="{
filterObj: v,
filterType: 'color',
pagename: pagename,
field: openField,
}"
:ref="v.id"
@databack="doConditionFilter($event, 'bgcolor', v.id)"
/>
<p class="fs-14 mt-1" :class="currentField.format === 'string' ? 'mb-1' : 'mb-2'">
<a class="has-text-primary mr-5" @click="addCondition(bgcolorFilter)" v-if="bgcolorFilter.length <= 30">
<p
class="fs-14 mt-1"
:class="currentField.format === 'string' ? 'mb-1' : 'mb-2'"
>
<a
class="has-text-primary mr-5"
@click="addCondition(bgcolorFilter)"
v-if="bgcolorFilter.length <= 30"
>
Thêm
</a>
<a class="has-text-danger" @click="removeCondition(bgcolorFilter, i)" v-if="bgcolorFilter.length > 1">
<a
class="has-text-danger"
@click="removeCondition(bgcolorFilter, i)"
v-if="bgcolorFilter.length > 1"
>
Bớt
</a>
</p>
@@ -32,29 +57,77 @@
</template>
<template v-else-if="radio ? radio.code === 'condition' && sideBar === 'color' : false">
<div v-for="(v, i) in colorFilter" :key="v.id" class="px-4">
<div
v-for="(v, i) in colorFilter"
:key="v.id"
class="px-4"
>
<FilterOption
v-bind="{ filterObj: v, filterType: 'color', pagename: pagename, field: openField }"
v-bind="{
filterObj: v,
filterType: 'color',
pagename: pagename,
field: openField,
}"
:ref="v.id"
@databack="doConditionFilter($event, 'color', v.id)"
/>
<p class="fs-14 mt-1" :class="currentField.format === 'string' ? 'mb-1' : 'mb-2'">
<a class="has-text-primary mr-5" @click="addCondition(colorFilter)" v-if="colorFilter.length <= 30"> Thêm </a>
<a class="has-text-danger" @click="removeCondition(colorFilter, i)" v-if="colorFilter.length > 1"> Bớt </a>
<p
class="fs-14 mt-1"
:class="currentField.format === 'string' ? 'mb-1' : 'mb-2'"
>
<a
class="has-text-primary mr-5"
@click="addCondition(colorFilter)"
v-if="colorFilter.length <= 30"
>
Thêm
</a>
<a
class="has-text-danger"
@click="removeCondition(colorFilter, i)"
v-if="colorFilter.length > 1"
>
Bớt
</a>
</p>
</div>
</template>
<template v-else-if="radio ? radio.code === 'condition' && sideBar === 'textsize' : false">
<div v-for="(v, i) in sizeFilter" :key="v.id" class="px-4">
<div
v-for="(v, i) in sizeFilter"
:key="v.id"
class="px-4"
>
<FilterOption
v-bind="{ filterObj: v, filterType: 'size', pagename: pagename, field: openField }"
v-bind="{
filterObj: v,
filterType: 'size',
pagename: pagename,
field: openField,
}"
:ref="v.id"
@databack="doConditionFilter($event, 'textsize', v.id)"
/>
<p class="fs-14 mt-1" :class="currentField.format === 'string' ? 'mb-1' : 'mb-2'">
<a class="has-text-primary mr-5" @click="addCondition(sizeFilter)" v-if="sizeFilter.length <= 30"> Thêm </a>
<a class="has-text-danger" @click="removeCondition(sizeFilter, i)" v-if="sizeFilter.length > 1"> Bớt </a>
<p
class="fs-14 mt-1"
:class="currentField.format === 'string' ? 'mb-1' : 'mb-2'"
>
<a
class="has-text-primary mr-5"
@click="addCondition(sizeFilter)"
v-if="sizeFilter.length <= 30"
>
Thêm
</a>
<a
class="has-text-danger"
@click="removeCondition(sizeFilter, i)"
v-if="sizeFilter.length > 1"
>
Bớt
</a>
</p>
</div>
</template>
@@ -62,21 +135,39 @@
<template v-else-if="tab.code === 'script' && ['bgcolor', 'color', 'textsize'].find((x) => x === sideBar)">
<p class="my-4 mx-4">
<a @click="copyContent(script ? script : '')" class="mr-6">
<a
@click="copyContent(script ? script : '')"
class="mr-6"
>
<span class="icon-text">
<SvgIcon class="mr-2" v-bind="{ name: 'copy.svg', type: 'primary', siz: 18 }"></SvgIcon>
<SvgIcon
class="mr-2"
v-bind="{ name: 'copy.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Copy</span>
</span>
</a>
<a @click="paste()" class="mr-6">
<a
@click="paste()"
class="mr-6"
>
<span class="icon-text">
<SvgIcon class="mr-2" v-bind="{ name: 'pen1.svg', type: 'primary', siz: 18 }"></SvgIcon>
<SvgIcon
class="mr-2"
v-bind="{ name: 'pen1.svg', type: 'primary', siz: 18 }"
></SvgIcon>
<span class="fs-16">Paste</span>
</span>
</a>
</p>
<div class="mx-4">
<textarea class="textarea fs-14" rows="8" v-model="script" @change="checkScript()" @dblclick="doCheck"></textarea>
<textarea
class="textarea fs-14"
rows="8"
v-model="script"
@change="checkScript()"
@dblclick="doCheck"
></textarea>
</div>
<p class="mt-5 mx-4">
<span class="icon-text fsb-18">
@@ -87,14 +178,29 @@
<div class="field is-grouped mx-4 mt-4">
<div class="control">
<p class="fsb-14 mb-1">Đoạn text</p>
<input class="input" type="text" placeholder="" v-model="source" />
<input
class="input"
type="text"
placeholder=""
v-model="source"
/>
</div>
<div class="control">
<p class="fsb-14 mb-1">Thay bằng</p>
<input class="input" type="text" placeholder="" v-model="target" />
<input
class="input"
type="text"
placeholder=""
v-model="target"
/>
</div>
<div class="control pl-5">
<button class="button is-primary is-rounded is-outlined mt-5" @click="replace()">Replace</button>
<button
class="button is-primary is-rounded is-outlined mt-5"
@click="replace()"
>
Replace
</button>
</div>
</div>
<p class="mt-5 pt-2 mx-4">
@@ -103,10 +209,24 @@
<SvgIcon v-bind="{ name: 'right.svg', type: 'dark', size: 22 }"></SvgIcon>
</span>
</p>
<p class="mx-4 mt-4"><button class="button is-primary is-rounded" @click="changeScript()">Cập nhật</button></p>
<p class="mx-4 mt-4">
<button
class="button is-primary is-rounded"
@click="changeScript()"
>
Cập nhật
</button>
</p>
</template>
<TableOption v-bind="{ pagename: pagename }" v-else-if="sideBar === 'option'"> </TableOption>
<CreateTemplate v-else-if="sideBar === 'template'" v-bind="{ pagename: pagename, field: openField }">
<TableOption
v-bind="{ pagename: pagename }"
v-else-if="sideBar === 'option'"
>
</TableOption>
<CreateTemplate
v-else-if="sideBar === 'template'"
v-bind="{ pagename: pagename, field: openField }"
>
</CreateTemplate>
</template>
<script setup>

View File

@@ -1,5 +1,8 @@
<template>
<div class="mb-4" v-if="currentsetting ? currentsetting.user === login.id : false">
<div
class="mb-4"
v-if="currentsetting ? currentsetting.user === login.id : false"
>
<p class="fs-16 has-text-findata">
Đang mở: <b>{{ $stripHtml(currentsetting.name, 40) }}</b>
</p>
@@ -7,7 +10,11 @@
<div class="field">
<label class="label fs-14">Chọn chế độ lưu <span class="has-text-danger"> * </span></label>
<div class="control is-expanded fs-14">
<a class="mr-5" v-if="isOverwrite()" @click="changeType('overwrite')">
<a
class="mr-5"
v-if="isOverwrite()"
@click="changeType('overwrite')"
>
<span class="icon-text">
<SvgIcon
v-bind="{
@@ -22,7 +29,11 @@
<a @click="changeType('new')">
<span class="icon-text">
<SvgIcon
v-bind="{ name: radioSave === 'new' ? 'radio-checked.svg' : 'radio-unchecked.svg', type: 'gray', size: 22 }"
v-bind="{
name: radioSave === 'new' ? 'radio-checked.svg' : 'radio-unchecked.svg',
type: 'gray',
size: 22,
}"
></SvgIcon>
Tạo mới
</span>
@@ -33,16 +44,30 @@
<div class="field mt-4 px-0 mx-0">
<label class="label fs-14">Tên thiết lập <span class="has-text-danger"> * </span></label>
<div class="control">
<input class="input" type="text" placeholder="" v-model="name" ref="name" v-on:keyup.enter="saveSetting" />
<input
class="input"
type="text"
placeholder=""
v-model="name"
ref="name"
v-on:keyup.enter="saveSetting"
/>
</div>
<div class="help has-text-danger" v-if="errors.find((v) => v.name === 'name')">
<div
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'name')"
>
{{ errors.find((v) => v.name === "name").msg }}
</div>
</div>
<div class="field mt-4 px-0 mx-0">
<label class="label fs-14"> Mô tả </label>
<p class="control is-expanded">
<textarea class="textarea" rows="4" v-model="note"></textarea>
<textarea
class="textarea"
rows="4"
v-model="note"
></textarea>
</p>
</div>
<!--
@@ -60,11 +85,19 @@
</div>-->
</template>
<div class="field mt-5 px-0 mx-0">
<label class="label fs-14" v-if="status !== undefined" :class="status ? 'has-text-primary' : 'has-text-danger'">
<label
class="label fs-14"
v-if="status !== undefined"
:class="status ? 'has-text-primary' : 'has-text-danger'"
>
{{ status ? "Lưu thiết lập thành công." : "Lỗi. Lưu thiết lập thất bại." }}
</label>
<p class="control is-expanded">
<a class="button is-primary has-text-white" @click="saveSetting()">Lưu lại</a>
<a
class="button is-primary has-text-white"
@click="saveSetting()"
>Lưu lại</a
>
</p>
</div>
</template>
@@ -118,7 +151,10 @@ async function saveSetting() {
let result;
if (radioSave.value === "new") {
if ($empty(name)) {
return errors.push({ name: "name", msg: "Tên thiết lập không được bỏ trống" });
return errors.push({
name: "name",
msg: "Tên thiết lập không được bỏ trống",
});
}
result = await $insertapi("usersetting", data);
} else {

View File

@@ -1,52 +1,114 @@
<template>
<div class="tabs">
<ul>
<li :class="`${v.code === tab ? 'is-active has-text-weight-bold fs-18' : 'fs-18'}`" v-for="v in tabs">
<li
:class="`${v.code === tab ? 'is-active has-text-weight-bold fs-18' : 'fs-18'}`"
v-for="v in tabs"
>
<a @click="changeTab(v)">{{ v.name }}</a>
</li>
</ul>
</div>
<template v-if="tab === 'datatype'">
<Caption class="mb-3" v-bind="{ title: 'Kiểu dữ liệu (type)', type: 'has-text-warning' }"></Caption>
<div class="py-1 border-bottom is-clickable" v-for="x in current.fields">
<Caption
class="mb-3"
v-bind="{ title: 'Kiểu dữ liệu (type)', type: 'has-text-warning' }"
></Caption>
<div
class="py-1 border-bottom is-clickable"
v-for="x in current.fields"
>
{{ x.name }}
<span class="ml-6 has-text-grey">{{ x.type }}</span>
<a class="ml-6 has-text-primary" v-if="x.model" @click="openModel(x)">{{ x.model }}</a>
<a
class="ml-6 has-text-primary"
v-if="x.model"
@click="openModel(x)"
>{{ x.model }}</a
>
</div>
</template>
<template v-else>
<div class="columns mx-0 mb-0 pb-0">
<div class="column is-7">
<Caption class="mb-2" v-bind="{ title: 'Values', type: 'has-text-warning' }"></Caption>
<input class="input" rows="1" v-model="values" />
<Caption
class="mb-2"
v-bind="{ title: 'Values', type: 'has-text-warning' }"
></Caption>
<input
class="input"
rows="1"
v-model="values"
/>
</div>
<div class="column is-4s">
<Caption class="mb-2" v-bind="{ title: 'Filter', type: 'has-text-warning' }"></Caption>
<input class="input" rows="1" v-model="filter" />
<Caption
class="mb-2"
v-bind="{ title: 'Filter', type: 'has-text-warning' }"
></Caption>
<input
class="input"
rows="1"
v-model="filter"
/>
</div>
<div class="column is-1">
<Caption class="mb-2" v-bind="{ title: 'Load', type: 'has-text-warning' }"></Caption>
<Caption
class="mb-2"
v-bind="{ title: 'Load', type: 'has-text-warning' }"
></Caption>
<div>
<button class="button is-primary has-text-white" @click="loadData()">Load</button>
<button
class="button is-primary has-text-white"
@click="loadData()"
>
Load
</button>
</div>
</div>
</div>
<Caption class="mb-2" v-bind="{ title: 'Query', type: 'has-text-warning' }"></Caption>
<Caption
class="mb-2"
v-bind="{ title: 'Query', type: 'has-text-warning' }"
></Caption>
<div class="mb-4">
{{ query }}
<a class="has-text-primary ml-5" @click="copy()">copy</a>
<a
class="has-text-primary ml-5"
@click="copy()"
>copy</a
>
<p>
{{ apiUrl }}
<a class="has-text-primary ml-5" @click="$copyToClipboard(apiUrl)">copy</a>
<a class="has-text-primary ml-5" target="_blank" :href="apiUrl">open</a>
<a
class="has-text-primary ml-5"
@click="$copyToClipboard(apiUrl)"
>copy</a
>
<a
class="has-text-primary ml-5"
target="_blank"
:href="apiUrl"
>open</a
>
</p>
</div>
<div>
<Caption class="mb-2" v-bind="{ title: 'Data', type: 'has-text-warning' }"></Caption>
<DataTable v-bind="{ pagename: pagename }" v-if="pagedata" />
<Caption
class="mb-2"
v-bind="{ title: 'Data', type: 'has-text-warning' }"
></Caption>
<DataTable
v-bind="{ pagename: pagename }"
v-if="pagedata"
/>
</div>
</template>
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal"></Modal>
<Modal
@close="showmodal = undefined"
v-bind="showmodal"
v-if="showmodal"
></Modal>
</template>
<script setup>
import { useStore } from "@/stores/index";

View File

@@ -1,26 +1,45 @@
<template>
<div class="tabs is-boxed">
<ul>
<li :class="selectType.code===v.code? 'is-active fs-16' : 'fs-16'" v-for="v in fieldType">
<a @click="selectType = v"><span>{{ v.name }}</span></a>
<li
:class="selectType.code === v.code ? 'is-active fs-16' : 'fs-16'"
v-for="v in fieldType"
>
<a @click="selectType = v"
><span>{{ v.name }}</span></a
>
</li>
</ul>
</div>
<template v-if="selectType.code==='formula'">
<b-radio :class="i===1? 'ml-5' : null" v-model="choice" v-for="(v,i) in choices" :key="i"
:native-value="v.code">
<span :class="v.code===choice? 'fsb-16' : 'fs-16'">{{v.name}}</span>
</b-radio>
<div class="has-background-light mt-3 px-3 py-3">
<div class="tags are-medium mb-0" v-if="choice==='function'">
<span :class="`tag ${func===v.code? 'is-primary' : 'is-dark'} is-rounded is-clickable`"
v-for="(v,i) in funcs" :key="i" @click="changeFunc(v)" @dblclick="addFunc(v)">{{v.name}}</span>
<template v-if="selectType.code === 'formula'">
<b-radio
:class="i === 1 ? 'ml-5' : null"
v-model="choice"
v-for="(v, i) in choices"
:key="i"
:native-value="v.code"
>
<span :class="v.code === choice ? 'fsb-16' : 'fs-16'">{{ v.name }}</span>
</b-radio>
<div class="has-background-light mt-3 px-3 py-3">
<div
class="tags are-medium mb-0"
v-if="choice === 'function'"
>
<span
:class="`tag ${func === v.code ? 'is-primary' : 'is-dark'} is-rounded is-clickable`"
v-for="(v, i) in funcs"
:key="i"
@click="changeFunc(v)"
@dblclick="addFunc(v)"
>{{ v.name }}</span
>
</div>
<template v-else>
<div class="field px-0 mx-0">
<label class="label fs-14">Chọn trường<span class="has-text-danger"> *</span> </label>
<div class="control">
<!--<b-taginput
<template v-else>
<div class="field px-0 mx-0">
<label class="label fs-14">Chọn trường<span class="has-text-danger"> *</span> </label>
<div class="control">
<!--<b-taginput
size="is-small"
v-model="tags"
:data="fields.filter(v=>v.format==='number')"
@@ -39,296 +58,418 @@
Không trường thỏa mãn
</template>
</b-taginput>-->
</div>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='tags')"> {{errors.find(v=>v.name==='tags').message}} </p>
</div>
<div class="field mt-3" v-if="tags.length>0">
</div>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'tags')"
>
{{ errors.find((v) => v.name === "tags").message }}
</p>
</div>
<div
class="field mt-3"
v-if="tags.length > 0"
>
<p class="help is-primary mb-1">Click đúp vào để thêm vào công thức tính.</p>
<div class="tags mb-2">
<span @dblclick="formula = formula? (formula + ' ' + v.name) : v.name" class="tag is-dark is-rounded is-clickable"
v-for="v in tags">
{{v.name}}
</span>
<span
@dblclick="formula = formula ? formula + ' ' + v.name : v.name"
class="tag is-dark is-rounded is-clickable"
v-for="v in tags"
>
{{ v.name }}
</span>
</div>
<div class="tags">
<span v-for="(v,i) in operator" :key="i">
<span @dblclick="addOperator(v)" class="tag is-primary is-rounded is-clickable mr-4">
<span class="fs-16">{{v.code}}</span>
<span
v-for="(v, i) in operator"
:key="i"
>
<span
@dblclick="addOperator(v)"
class="tag is-primary is-rounded is-clickable mr-4"
>
<span class="fs-16">{{ v.code }}</span>
</span>
</span>
</span>
</div>
</div>
</template>
<div class="field mt-3 px-0 mx-0">
<label class="label fs-14">Công thức tính <span class="has-text-danger"> * </span> </label>
<p class="control">
<textarea class="textarea" rows="3" type="text" :placeholder="placeholder" v-model="formula"> </textarea>
</p>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='formula')"> {{errors.find(v=>v.name==='formula').message}} </p>
</div>
</div>
<div class="field is-horizontal mt-3 px-0 mx-0">
<div class="field-body">
<div class="field">
<label class="label fs-14">Hiển thị theo <span class="has-text-danger"> * </span> </label>
<div class="control">
<b-autocomplete
size="is-small"
icon-right="magnify"
:value="selectUnit? selectUnit.name : ''"
placeholder=""
:keep-first=true
:open-on-focus=true
:data="moneyunit"
field="name"
@select="option => selectUnit = option">
</b-autocomplete>
</div>
</div>
<div class="field">
<label class="label fs-14">Phần thập phân</label>
<div class="control">
<input class="input is-small" type="text" placeholder="" v-model="decimal">
</div>
</div>
</div>
</div>
</template>
<div class="field px-0 mx-0">
<label class="label">Tên trường <span class="has-text-danger"> * </span> </label>
<p class="control">
<input class="input" type="text" placeholder="Tên trường phải là duy nhất" v-model="name"
:readonly="selectType? selectType.code==='formula': false">
</p>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='name')"> {{errors.find(v=>v.name==='name').message}} </p>
<p class="help has-text-primary" v-else> Tên trường do hệ thống tự sinh.</p>
</div>
<div class="mt-5">
<label class="label"> tả<span class="has-text-danger"> *</span></label>
<div class="field has-addons">
<div class="control is-expanded" >
<input
class="input"
<div class="field mt-3 px-0 mx-0">
<label class="label fs-14">Công thức tính <span class="has-text-danger"> * </span> </label>
<p class="control">
<textarea
class="textarea"
rows="3"
type="text"
v-model="label"
/>
</div>
<div class="control">
<button class="button" @click="editLabel()">
<span><SvgIcon v-bind="{name: 'pen.svg', type: 'dark', size: 17}"></SvgIcon></span>
</button>
</div>
:placeholder="placeholder"
v-model="formula"
>
</textarea>
</p>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'formula')"
>
{{ errors.find((v) => v.name === "formula").message }}
</p>
</div>
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='label')"> {{errors.find(v=>v.name==='label').message}} </p>
</div>
<div class="field mt-5" v-if="selectType.code==='empty'">
<label class="label"
>Kiểu dữ liệu
<span class="has-text-danger"> * </span>
</label>
<div class="control fs-14">
<span class="mr-4" v-for="(v,i) in datatype">
<a class="icon-text" @click="changeType(v)">
<SvgIcon v-bind="{name: `radio-${radioType.code===v.code? '' : 'un'}checked.svg`, type: 'gray', size: 22}"></SvgIcon>
</a>
{{v.name}}
</span>
</div>
<div class="field is-horizontal mt-3 px-0 mx-0">
<div class="field-body">
<div class="field">
<label class="label fs-14">Hiển thị theo <span class="has-text-danger"> * </span> </label>
<div class="control">
<b-autocomplete
size="is-small"
icon-right="magnify"
:value="selectUnit ? selectUnit.name : ''"
placeholder=""
:keep-first="true"
:open-on-focus="true"
:data="moneyunit"
field="name"
@select="(option) => (selectUnit = option)"
>
</b-autocomplete>
</div>
</div>
<div class="field mt-5">
<p class="control">
<a class="button is-primary has-text-white"
@click="selectType.code==='formula'? createField() : createEmptyField()">Tạo cột</a>
</p>
<div class="field">
<label class="label fs-14">Phần thập phân</label>
<div class="control">
<input
class="input is-small"
type="text"
placeholder=""
v-model="decimal"
/>
</div>
<Modal v-bind="showmodal" v-if="showmodal" @label="changeLabel" @close="close"></Modal>
</div>
</div>
</div>
</template>
<div class="field px-0 mx-0">
<label class="label">Tên trường <span class="has-text-danger"> * </span> </label>
<p class="control">
<input
class="input"
type="text"
placeholder="Tên trường phải là duy nhất"
v-model="name"
:readonly="selectType ? selectType.code === 'formula' : false"
/>
</p>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'name')"
>
{{ errors.find((v) => v.name === "name").message }}
</p>
<p
class="help has-text-primary"
v-else
>
Tên trường do hệ thống tự sinh.
</p>
</div>
<div class="mt-5">
<label class="label"> tả<span class="has-text-danger"> *</span></label>
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input"
type="text"
v-model="label"
/>
</div>
<div class="control">
<button
class="button"
@click="editLabel()"
>
<span><SvgIcon v-bind="{ name: 'pen.svg', type: 'dark', size: 17 }"></SvgIcon></span>
</button>
</div>
</div>
<p
class="help has-text-danger"
v-if="errors.find((v) => v.name === 'label')"
>
{{ errors.find((v) => v.name === "label").message }}
</p>
</div>
<div
class="field mt-5"
v-if="selectType.code === 'empty'"
>
<label class="label"
>Kiểu dữ liệu
<span class="has-text-danger"> * </span>
</label>
<div class="control fs-14">
<span
class="mr-4"
v-for="(v, i) in datatype"
>
<a
class="icon-text"
@click="changeType(v)"
>
<SvgIcon
v-bind="{
name: `radio-${radioType.code === v.code ? '' : 'un'}checked.svg`,
type: 'gray',
size: 22,
}"
></SvgIcon>
</a>
{{ v.name }}
</span>
</div>
</div>
<div class="field mt-5">
<p class="control">
<a
class="button is-primary has-text-white"
@click="selectType.code === 'formula' ? createField() : createEmptyField()"
>Tạo cột</a
>
</p>
</div>
<Modal
v-bind="showmodal"
v-if="showmodal"
@label="changeLabel"
@close="close"
></Modal>
</template>
<script setup>
import { useStore } from '@/stores/index'
import ScrollBox from '~/components/datatable/ScrollBox'
const emit = defineEmits(['modalevent'])
const store = useStore()
const { $id, $copy, $clone, $empty, $stripHtml, $createField, $calc, $isNumber } = useNuxtApp()
var props = defineProps({
pagename: String,
field: Object,
filters: Object,
filterData: Object,
width: String
})
const moneyunit = store.moneyunit
const datatype = store.datatype
var showmodal = ref()
var pagedata = store[props.pagename]
var selectUnit = moneyunit.find(v=>v.code==='one')
var data = []
var current = 1
var filterData = []
var loading = false
var fieldType = [{code: 'formula', name: 'Tạo công thức'}, {code: 'empty', name: 'Tạo cột rỗng'}]
var errors = []
var tags = []
var formula = undefined
var name = `f${$id().toLocaleLowerCase()}`
var label = undefined
var errors = []
var selectType = fieldType.find(v=>v.code==='empty')
var radioType = ref(datatype.find(v=>v.code==='string'))
var fields = []
var options = undefined
var columns = $copy(pagedata.fields.filter(v=>v.format==='number'))
var decimal = undefined
var choices = [{code: 'column', name: 'Dùng cột dữ liệu'}, {code: 'function', name: 'Dùng hàm số'}]
var choice = 'column'
var funcs = [{code: 'sum', name: 'Sum'}, {code: 'max', name: 'Max'}, {code: 'min', name: 'Min'}, {code: 'avg', name: 'Avg'}]
var func = 'sum'
var placeholder = 'Minh hoạ công thức: f10001 + f10002'
var args = undefined
var operator = [{code: '+', name: 'Cộng'}, {code: '-', name: 'Trừ'}, {code: '*', name: 'Nhân'}, {code: '/', name: 'Chia'}, {code: '>', name: 'Lớn hơn'},
{code: '>=', name: 'Lớn hơn hoặc bằng'}, {code: '<', name: 'Nhỏ hơn'}, {code: '<=', name: 'Nhỏ hơn hoặc bằng'}, {code: '==', name: 'Bằng'},
{code: '&&', name: 'Và'}, {code: '||', name: 'Hoặc'}, {code: 'iif', name: 'Điều kiện rẽ nhánh'}]
function editLabel() {
if($empty(label)) return
showmodal.value = {component: 'datatable/EditLabel', width: '500px', height: '300px', vbind: {label: label}}
}
function close() {
showmodal.value = null
}
function changeLabel(evt) {
label = evt
showmodal.value = null
}
function changeType(v) {
radioType.value = v
}
function addFunc(v) {
formula = (formula? formula + ' ' : '') + v.name + '(C0: C2)'
}
function addOperator(v) {
let text = v.code==='iif'? 'a>b? c : d' : v.code
formula = `${formula || ''} ${text}`
}
function changeFunc(v) {
placeholder = `${v.name}(C0:C2) hoặc ${v.name}(C0,C1,C2). C là viết tắt của cột dữ liệu, số thứ tự của cột bắt đầu từ 0`
func = v.code
}
function getFields() {
fields = pagedata? $copy(pagedata.fields) : []
fields.map(v=>v.caption = (v.label? v.label.indexOf('<')>=0 : false)? v.name : v.label)
}
function checkFunc() {
let error = false
let val = formula.trim().replaceAll(' ', '')
if(val.toLowerCase().indexOf(func)<0) error = true
let start = val.toLowerCase().indexOf('(')
let end = val.toLowerCase().indexOf(')')
if( start<0 || end<0) error = true
let content = val.substring(start+1, end)
if($empty(content)) error = true
let content1 = content.replaceAll(':', ',')
let arr = content1.split(',')
arr.map(v=>{
let arr1 = v.toLowerCase().split('c')
if(arr1.length!==2) error = true
else if(!$isNumber(arr1[1])) error = true
})
return error? 'error' : content
}
function checkValid() {
errors = []
if(tags.length===0 && choice==='column') {
errors.push({name: 'tags', message: 'Chưa chọn trường xây dựng công thức.'})
import { useStore } from "@/stores/index";
import ScrollBox from "~/components/datatable/ScrollBox";
const emit = defineEmits(["modalevent"]);
const store = useStore();
const { $id, $copy, $clone, $empty, $stripHtml, $createField, $calc, $isNumber } = useNuxtApp();
var props = defineProps({
pagename: String,
field: Object,
filters: Object,
filterData: Object,
width: String,
});
const moneyunit = store.moneyunit;
const datatype = store.datatype;
var showmodal = ref();
var pagedata = store[props.pagename];
var selectUnit = moneyunit.find((v) => v.code === "one");
var data = [];
var current = 1;
var filterData = [];
var loading = false;
var fieldType = [
{ code: "formula", name: "Tạo công thức" },
{ code: "empty", name: "Tạo cột rỗng" },
];
var errors = [];
var tags = [];
var formula = undefined;
var name = `f${$id().toLocaleLowerCase()}`;
var label = undefined;
var errors = [];
var selectType = fieldType.find((v) => v.code === "empty");
var radioType = ref(datatype.find((v) => v.code === "string"));
var fields = [];
var options = undefined;
var columns = $copy(pagedata.fields.filter((v) => v.format === "number"));
var decimal = undefined;
var choices = [
{ code: "column", name: "Dùng cột dữ liệu" },
{ code: "function", name: "Dùng hàm số" },
];
var choice = "column";
var funcs = [
{ code: "sum", name: "Sum" },
{ code: "max", name: "Max" },
{ code: "min", name: "Min" },
{ code: "avg", name: "Avg" },
];
var func = "sum";
var placeholder = "Minh hoạ công thức: f10001 + f10002";
var args = undefined;
var operator = [
{ code: "+", name: "Cộng" },
{ code: "-", name: "Trừ" },
{ code: "*", name: "Nhân" },
{ code: "/", name: "Chia" },
{ code: ">", name: "Lớn hơn" },
{ code: ">=", name: "Lớn hơn hoặc bằng" },
{ code: "<", name: "Nhỏ hơn" },
{ code: "<=", name: "Nhỏ hơn hoặc bằng" },
{ code: "==", name: "Bằng" },
{ code: "&&", name: "Và" },
{ code: "||", name: "Hoặc" },
{ code: "iif", name: "Điều kiện rẽ nhánh" },
];
function editLabel() {
if ($empty(label)) return;
showmodal.value = {
component: "datatable/EditLabel",
width: "500px",
height: "300px",
vbind: { label: label },
};
}
function close() {
showmodal.value = null;
}
function changeLabel(evt) {
label = evt;
showmodal.value = null;
}
function changeType(v) {
radioType.value = v;
}
function addFunc(v) {
formula = (formula ? formula + " " : "") + v.name + "(C0: C2)";
}
function addOperator(v) {
let text = v.code === "iif" ? "a>b? c : d" : v.code;
formula = `${formula || ""} ${text}`;
}
function changeFunc(v) {
placeholder = `${v.name}(C0:C2) hoặc ${v.name}(C0,C1,C2). C là viết tắt của cột dữ liệu, số thứ tự của cột bắt đầu từ 0`;
func = v.code;
}
function getFields() {
fields = pagedata ? $copy(pagedata.fields) : [];
fields.map((v) => (v.caption = (v.label ? v.label.indexOf("<") >= 0 : false) ? v.name : v.label));
}
function checkFunc() {
let error = false;
let val = formula.trim().replaceAll(" ", "");
if (val.toLowerCase().indexOf(func) < 0) error = true;
let start = val.toLowerCase().indexOf("(");
let end = val.toLowerCase().indexOf(")");
if (start < 0 || end < 0) error = true;
let content = val.substring(start + 1, end);
if ($empty(content)) error = true;
let content1 = content.replaceAll(":", ",");
let arr = content1.split(",");
arr.map((v) => {
let arr1 = v.toLowerCase().split("c");
if (arr1.length !== 2) error = true;
else if (!$isNumber(arr1[1])) error = true;
});
return error ? "error" : content;
}
function checkValid() {
errors = [];
if (tags.length === 0 && choice === "column") {
errors.push({
name: "tags",
message: "Chưa chọn trường xây dựng công thức.",
});
}
if (!$empty(formula) ? $empty(formula.trim()) : true) {
errors.push({ name: "formula", message: "Công thức không được bỏ trống." });
}
if (!$empty(label) ? $empty(label.trim()) : true)
errors.push({ name: "label", message: "Mô tả không được bỏ trống." });
else if (pagedata.fields.find((v) => v.label.toLowerCase() === label.toLowerCase())) {
errors.push({
name: "label",
message: "Mô tả bị trùng. Hãy đặt mô tả khác.",
});
}
if (errors.length > 0) return false;
//check formula in case use column
if (choice === "column") {
let val = $copy(formula);
tags.forEach((v) => {
let myRegExp = new RegExp(v.name, "g");
val = val.replace(myRegExp, Math.random());
});
try {
let value = $calc(val);
if (isNaN(value) || value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) {
errors.push({ name: "formula", message: "Công thức không hợp lệ" });
}
if(!$empty(formula)? $empty(formula.trim()) : true) {
errors.push({name: 'formula', message: 'Công thức không được bỏ trống.'})
}
if(!$empty(label)? $empty(label.trim()) : true )
errors.push({name: 'label', message: 'Mô tả không được bỏ trống.'})
else if(pagedata.fields.find(v=>v.label.toLowerCase()===label.toLowerCase())) {
errors.push({name: 'label', message: 'Mô tả bị trùng. Hãy đặt mô tả khác.'})
}
if(errors.length>0) return false
//check formula in case use column
if(choice==='column') {
let val = $copy(formula)
tags.forEach(v => {
let myRegExp = new RegExp(v.name, 'g')
val = val.replace(myRegExp, Math.random())
})
try {
let value = $calc(val)
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
errors.push({name: 'formula', message: 'Công thức không hợp lệ'})
}
}
catch(err) {
console.log(err)
errors.push({name: 'formula', message: 'Công thức không hợp lệ'})
}
} else {
if(checkFunc()==='error') errors.push({name: 'formula', message: `Hàm ${func.toUpperCase()} không hợp lệ`})
}
return errors.length>0? false : true
}
function createField() {
if(!checkValid()) return
let field = $createField(name.trim(), label.trim(), 'number', true)
field.formula = formula.trim().replaceAll(' ', '')
if(choice==='function') {
field.func = func
field.vals = checkFunc()
} else field.tags = tags.map(v=>v.name)
field.level = Math.max(...pagedata.fields.map(v=>v.level? v.level : 0)) + 1
field.unit = selectUnit.detail
field.decimal = decimal
field.disable = 'search,value'
let copy = $copy(pagedata)
copy.fields.push(field)
store.commit(props.pagename, copy)
emit('newfield', field)
tags = []
formula = undefined
label = undefined
name = `f${$id()}`
emit('close')
} catch (err) {
console.log(err);
errors.push({ name: "formula", message: "Công thức không hợp lệ" });
}
function createEmptyField() {
errors = []
if(!$empty(name)? $empty(name.trim()) : true )
errors.push({name: 'name', message: 'Tên không được bỏ trống.'})
else if(pagedata.fields.find(v=>v.name.toLowerCase()===name.toLowerCase())) {
errors.push({name: 'name', message: 'Tên trường bị trùng. Hãy đặt tên khác.'})
}
if(!$empty(label)? $empty(label.trim()) : true )
errors.push({name: 'label', message: 'Mô tả không được bỏ trống.'})
else if(pagedata.fields.find(v=>v.label.toLowerCase()===label.toLowerCase())) {
errors.push({name: 'label', message: 'Mô tả bị trùng. Hãy đặt mô tả khác.'})
}
if(errors.length>0) return
let field = $createField(name.trim(), label.trim(), radioType.value.code, true)
if(selectType.code==='chart') field = createChartField()
let copy = $clone(pagedata)
copy.fields.push(field)
copy.update = {fields: copy.fields}
store.commit(props.pagename, copy)
//pagedata = copy
emit('newfield', field)
label = undefined
name = `f${$id()}`
emit('close')
}
function createChartField() {
let array = pagedata.fields.filter(v=>v.format==='number' && v.show)
if(args) array = $copy(args)
let text = ''
array.map((v,i)=>text += `'${v.name}${i<array.length-1? "'," : "'"}`)
let label = ''
array.map((v,i)=>label += `'${$stripHtml(v.label)}${i<array.length-1? "'," : "'"}`)
let field = $createField(name.trim(), label.trim(), radioType.value.code, true)
field.chart = 'yes'
field.template = `<TrendingChart class="is-clickable" v-bind="{row: row, fields: [${text}], labels: [${label}], width: '80', height: '26', 'header': ['stock_code', 'name']}"/>`
return field
}
//============
getFields()
</script>
} else {
if (checkFunc() === "error")
errors.push({
name: "formula",
message: `Hàm ${func.toUpperCase()} không hợp lệ`,
});
}
return errors.length > 0 ? false : true;
}
function createField() {
if (!checkValid()) return;
let field = $createField(name.trim(), label.trim(), "number", true);
field.formula = formula.trim().replaceAll(" ", "");
if (choice === "function") {
field.func = func;
field.vals = checkFunc();
} else field.tags = tags.map((v) => v.name);
field.level = Math.max(...pagedata.fields.map((v) => (v.level ? v.level : 0))) + 1;
field.unit = selectUnit.detail;
field.decimal = decimal;
field.disable = "search,value";
let copy = $copy(pagedata);
copy.fields.push(field);
store.commit(props.pagename, copy);
emit("newfield", field);
tags = [];
formula = undefined;
label = undefined;
name = `f${$id()}`;
emit("close");
}
function createEmptyField() {
errors = [];
if (!$empty(name) ? $empty(name.trim()) : true) errors.push({ name: "name", message: "Tên không được bỏ trống." });
else if (pagedata.fields.find((v) => v.name.toLowerCase() === name.toLowerCase())) {
errors.push({
name: "name",
message: "Tên trường bị trùng. Hãy đặt tên khác.",
});
}
if (!$empty(label) ? $empty(label.trim()) : true)
errors.push({ name: "label", message: "Mô tả không được bỏ trống." });
else if (pagedata.fields.find((v) => v.label.toLowerCase() === label.toLowerCase())) {
errors.push({
name: "label",
message: "Mô tả bị trùng. Hãy đặt mô tả khác.",
});
}
if (errors.length > 0) return;
let field = $createField(name.trim(), label.trim(), radioType.value.code, true);
if (selectType.code === "chart") field = createChartField();
let copy = $clone(pagedata);
copy.fields.push(field);
copy.update = { fields: copy.fields };
store.commit(props.pagename, copy);
//pagedata = copy
emit("newfield", field);
label = undefined;
name = `f${$id()}`;
emit("close");
}
function createChartField() {
let array = pagedata.fields.filter((v) => v.format === "number" && v.show);
if (args) array = $copy(args);
let text = "";
array.map((v, i) => (text += `'${v.name}${i < array.length - 1 ? "'," : "'"}`));
let label = "";
array.map((v, i) => (label += `'${$stripHtml(v.label)}${i < array.length - 1 ? "'," : "'"}`));
let field = $createField(name.trim(), label.trim(), radioType.value.code, true);
field.chart = "yes";
field.template = `<TrendingChart class="is-clickable" v-bind="{row: row, fields: [${text}], labels: [${label}], width: '80', height: '26', 'header': ['stock_code', 'name']}"/>`;
return field;
}
//============
getFields();
</script>

View File

@@ -1,64 +1,106 @@
<template>
<nav class="pagination mx-0" role="navigation" aria-label="pagination">
<ul class="pagination-list" v-if="pageInfo">
<li v-for="v in pageInfo">
<a v-if="currentPage===v" class="pagination-link is-current has-background-primary has-text-white" :aria-label="`Page ${v}`" aria-current="page">{{ v }}</a>
<a v-else href="#" class="pagination-link" :aria-label="`Goto page ${v}`" @click="changePage(v)">{{ v }}</a>
</li>
<a @click="previous()" class="pagination-previous ml-5">
<SvgIcon v-bind="{name: 'left1.svg', type: 'dark', size: 20, alt: 'Tìm kiếm'}"></SvgIcon>
</a>
<a @click="next()" class="pagination-next">
<SvgIcon v-bind="{name: 'right.svg', type: 'dark', size: 20, alt: 'Tìm kiếm'}"></SvgIcon>
</a>
</ul>
</nav>
<nav
class="pagination mx-0"
role="navigation"
aria-label="pagination"
>
<ul
class="pagination-list"
v-if="pageInfo"
>
<li v-for="v in pageInfo">
<a
v-if="currentPage === v"
class="pagination-link is-current has-background-primary has-text-white"
:aria-label="`Page ${v}`"
aria-current="page"
>{{ v }}</a
>
<a
v-else
href="#"
class="pagination-link"
:aria-label="`Goto page ${v}`"
@click="changePage(v)"
>{{ v }}</a
>
</li>
<a
@click="previous()"
class="pagination-previous ml-5"
>
<SvgIcon
v-bind="{
name: 'left1.svg',
type: 'dark',
size: 20,
alt: 'Tìm kiếm',
}"
></SvgIcon>
</a>
<a
@click="next()"
class="pagination-next"
>
<SvgIcon
v-bind="{
name: 'right.svg',
type: 'dark',
size: 20,
alt: 'Tìm kiếm',
}"
></SvgIcon>
</a>
</ul>
</nav>
</template>
<script setup>
const emit = defineEmits(['changepage'])
var props = defineProps({
data: Array,
perPage: Number
})
var currentPage = 1
var totalRows = props.data.length
var lastPage = parseInt(totalRows / props.perPage)
if(lastPage*props.perPage<totalRows) lastPage += 1
var pageInfo = ref()
function pages(current_page, last_page, onSides = 2) {
// pages
let pages = [];
// Loop through
for (let i = 1; i <= last_page; i++) {
// Define offset
let offset = (i == 1 || last_page) ? onSides + 1 : onSides;
// If added
if (i == 1 || (current_page - offset <= i && current_page + offset >= i) ||
i == current_page || i == last_page) {
pages.push(i);
} else if (i == current_page - (offset + 1) || i == current_page + (offset + 1)) {
pages.push('...');
}
}
return pages;
const emit = defineEmits(["changepage"]);
var props = defineProps({
data: Array,
perPage: Number,
});
var currentPage = 1;
var totalRows = props.data.length;
var lastPage = parseInt(totalRows / props.perPage);
if (lastPage * props.perPage < totalRows) lastPage += 1;
var pageInfo = ref();
function pages(current_page, last_page, onSides = 2) {
// pages
let pages = [];
// Loop through
for (let i = 1; i <= last_page; i++) {
// Define offset
let offset = i == 1 || last_page ? onSides + 1 : onSides;
// If added
if (i == 1 || (current_page - offset <= i && current_page + offset >= i) || i == current_page || i == last_page) {
pages.push(i);
} else if (i == current_page - (offset + 1) || i == current_page + (offset + 1)) {
pages.push("...");
}
function changePage(page) {
if(page==='...') return
currentPage = page
pageInfo.value = pages(page, lastPage, 2)
emit('changepage', page)
}
pageInfo.value = pages(1, lastPage, 2)
watch(() => props.data, (newVal, oldVal) => {
totalRows = props.data.length
lastPage = parseInt(totalRows / props.perPage)
if(lastPage*props.perPage<totalRows) lastPage += 1
pageInfo.value = pages(1, lastPage, 2)
})
function previous() {
if(currentPage>1) changePage(currentPage-1)
}
function next() {
if(currentPage<lastPage) changePage(currentPage+1)
}
</script>
}
return pages;
}
function changePage(page) {
if (page === "...") return;
currentPage = page;
pageInfo.value = pages(page, lastPage, 2);
emit("changepage", page);
}
pageInfo.value = pages(1, lastPage, 2);
watch(
() => props.data,
(newVal, oldVal) => {
totalRows = props.data.length;
lastPage = parseInt(totalRows / props.perPage);
if (lastPage * props.perPage < totalRows) lastPage += 1;
pageInfo.value = pages(1, lastPage, 2);
},
);
function previous() {
if (currentPage > 1) changePage(currentPage - 1);
}
function next() {
if (currentPage < lastPage) changePage(currentPage + 1);
}
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,79 @@
<template>
<div class="px-2" :style="`max-height: ${maxheight}; overflow-y: auto;`">
<div
v-for="(v, i) in rows" :key="i"
:class="[
'field is-grouped py-1 my-0',
i !== rows.length - 1 && 'border-bottom'
]"
<div
class="px-2"
:style="`max-height: ${maxheight}; overflow-y: auto;`"
>
<div
v-for="(v, i) in rows"
:key="i"
:class="['field is-grouped py-1 my-0', i !== rows.length - 1 && 'border-bottom']"
>
<p class="control is-expanded py-0 fs-14 hyperlink" @click="doClick(v,i)">
{{ $stripHtml(v[name] || v.fullname || v.code || 'n/a', 75) }}
<span class="icon has-text-primary" v-if="checked[i] && notick!==true">
<SvgIcon v-bind="{name: 'tick.svg', type: 'primary', size: 15}"></SvgIcon>
<p
class="control is-expanded py-0 fs-14 hyperlink"
@click="doClick(v, i)"
>
{{ $stripHtml(v[name] || v.fullname || v.code || "n/a", 75) }}
<span
class="icon has-text-primary"
v-if="checked[i] && notick !== true"
>
<SvgIcon v-bind="{ name: 'tick.svg', type: 'primary', size: 15 }"></SvgIcon>
</span>
</p>
<p class="control py-0" v-if="show">
<span class="icon-text has-text-grey mr-2 fs-13" v-if="show.author">
<SvgIcon v-bind="{name: 'user.svg', type: 'gray', size: 15}"></SvgIcon>
<p
class="control py-0"
v-if="show"
>
<span
class="icon-text has-text-grey mr-2 fs-13"
v-if="show.author"
>
<SvgIcon v-bind="{ name: 'user.svg', type: 'gray', size: 15 }"></SvgIcon>
<span>{{ v[show.author] }}</span>
</span>
<span class="icon-text has-text-grey mr-2 fs-13" v-if="show.view">
<SvgIcon v-bind="{name: 'view.svg', type: 'gray', size: 15}"></SvgIcon>
<span
class="icon-text has-text-grey mr-2 fs-13"
v-if="show.view"
>
<SvgIcon v-bind="{ name: 'view.svg', type: 'gray', size: 15 }"></SvgIcon>
<span>{{ v[show.view] }}</span>
</span>
<span class="fs-13 has-text-grey" v-if="show.time">{{$dayjs(v['create_time']).fromNow(true)}}</span>
<span class="tooltip">
<a class="icon ml-1" v-if="show.link" @click="doClick(v,i, 'newtab')">
<SvgIcon v-bind="{name: 'opennew.svg', type: 'gray', size: 15}"></SvgIcon>
<span
class="fs-13 has-text-grey"
v-if="show.time"
>{{ $dayjs(v["create_time"]).fromNow(true) }}</span
>
<span class="tooltip">
<a
class="icon ml-1"
v-if="show.link"
@click="doClick(v, i, 'newtab')"
>
<SvgIcon v-bind="{ name: 'opennew.svg', type: 'gray', size: 15 }"></SvgIcon>
</a>
<span class="tooltiptext">Mở trong tab mớ</span>
</span>
<span class="tooltip" v-if="show.rename">
<a class="icon ml-1" @click="$emit('rename', v, i)">
<SvgIcon v-bind="{name: 'pen1.svg', type: 'gray', size: 15}"></SvgIcon>
<span
class="tooltip"
v-if="show.rename"
>
<a
class="icon ml-1"
@click="$emit('rename', v, i)"
>
<SvgIcon v-bind="{ name: 'pen1.svg', type: 'gray', size: 15 }"></SvgIcon>
</a>
<span class="tooltiptext">Đổi tên</span>
</span>
<span class="tooltip" v-if="show.rename">
<a class="icon has-text-danger ml-1" @click="$emit('remove', v, i)">
<SvgIcon v-bind="{name: 'bin1.svg', type: 'gray', size: 15}"></SvgIcon>
<span
class="tooltip"
v-if="show.rename"
>
<a
class="icon has-text-danger ml-1"
@click="$emit('remove', v, i)"
>
<SvgIcon v-bind="{ name: 'bin1.svg', type: 'gray', size: 15 }"></SvgIcon>
</a>
<span class="tooltiptext">Xóa</span>
</span>
@@ -47,7 +83,7 @@
</template>
<script>
export default {
props: ['data', 'name', 'maxheight', 'perpage', 'sort', 'selects', 'keyval', 'show', 'notick'],
props: ["data", "name", "maxheight", "perpage", "sort", "selects", "keyval", "show", "notick"],
data() {
return {
currentPage: 1,
@@ -56,62 +92,64 @@ export default {
selected: [],
checked: {},
time: undefined,
array: []
}
array: [],
};
},
created() {
this.getdata()
this.getdata();
},
watch: {
data: function(newVal) {
this.getdata()
data: function (newVal) {
this.getdata();
},
selects: function (newVal) {
this.getSelect();
},
selects: function(newVal) {
this.getSelect()
}
},
methods: {
getdata() {
this.currentPage = 1
this.array = this.$copy(this.data)
if(this.sort!==false) {
let f = {}
let showtime = this.show? this.show.time : false
showtime? f['create_time'] = 'desc' : f[this.name] = 'asc'
this.$multiSort(this.array, f)
this.currentPage = 1;
this.array = this.$copy(this.data);
if (this.sort !== false) {
let f = {};
let showtime = this.show ? this.show.time : false;
showtime ? (f["create_time"] = "desc") : (f[this.name] = "asc");
this.$multiSort(this.array, f);
}
this.rows = this.array.slice(0, this.perpage)
this.getSelect()
this.rows = this.array.slice(0, this.perpage);
this.getSelect();
},
getSelect() {
if(!this.selects) return
this.selected = []
this.checked = {}
this.selects.map(v=>{
let idx = this.rows.findIndex(x=>x[this.keyval? this.keyval : this.name]===v)
if(idx>=0) {
this.selected.push(this.rows[idx])
this.checked[idx] = true
if (!this.selects) return;
this.selected = [];
this.checked = {};
this.selects.map((v) => {
let idx = this.rows.findIndex((x) => x[this.keyval ? this.keyval : this.name] === v);
if (idx >= 0) {
this.selected.push(this.rows[idx]);
this.checked[idx] = true;
}
})
});
},
doClick(v, i, type) {
this.checked[i] = this.checked[i]? false : true
this.checked = this.$copy(this.checked)
let idx = this.selected.findIndex(x=>x.id===v.id)
idx>=0? this.$remove(this.selected) : this.selected.push(v)
this.$emit('selected', v, type)
this.checked[i] = this.checked[i] ? false : true;
this.checked = this.$copy(this.checked);
let idx = this.selected.findIndex((x) => x.id === v.id);
idx >= 0 ? this.$remove(this.selected) : this.selected.push(v);
this.$emit("selected", v, type);
},
handleScroll(e) {
const bottom = e.target.scrollHeight - e.target.scrollTop -5 < e.target.clientHeight
if (bottom) {
if(this.total? this.total>this.rows.length : true) {
this.currentPage +=1
let arr = this.array.filter((ele,index) => (index>=(this.currentPage-1)*this.perpage && index<this.currentPage*this.perpage))
this.rows = this.rows.concat(arr)
const bottom = e.target.scrollHeight - e.target.scrollTop - 5 < e.target.clientHeight;
if (bottom) {
if (this.total ? this.total > this.rows.length : true) {
this.currentPage += 1;
let arr = this.array.filter(
(ele, index) => index >= (this.currentPage - 1) * this.perpage && index < this.currentPage * this.perpage,
);
this.rows = this.rows.concat(arr);
}
}
}
}
}
</script>
},
},
};
</script>

View File

@@ -9,17 +9,30 @@
</tr>
</thead>
<tbody>
<tr class="fs-14" v-for="(v, i) in fields">
<tr
class="fs-14"
v-for="(v, i) in fields"
>
<td>{{ i }}</td>
<td>
<a class="has-text-primary" @click="openField(v, i)">{{ v.name }}</a>
<a
class="has-text-primary"
@click="openField(v, i)"
>{{ v.name }}</a
>
</td>
<td>{{ $stripHtml(v.label, 50) }}</td>
<td>
<a class="mr-4" @click="moveDown(v, i)">
<a
class="mr-4"
@click="moveDown(v, i)"
>
<SvgIcon v-bind="{ name: 'down1.png', type: 'dark', size: 18 }"></SvgIcon>
</a>
<a class="mr-4" @click="moveUp(v, i)">
<a
class="mr-4"
@click="moveUp(v, i)"
>
<SvgIcon v-bind="{ name: 'up.png', type: 'dark', size: 18 }"></SvgIcon>
</a>
<a @click="askConfirm(v, i)">
@@ -29,7 +42,13 @@
</tr>
</tbody>
</table>
<Modal @close="showmodal = undefined" @update="update" @confirm="remove" v-bind="showmodal" v-if="showmodal"></Modal>
<Modal
@close="showmodal = undefined"
@update="update"
@confirm="remove"
v-bind="showmodal"
v-if="showmodal"
></Modal>
</template>
<script setup>
import { useStore } from "@/stores/index";

View File

@@ -1,112 +1,144 @@
<template>
<div class="field is-horizontal">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Cỡ chữ của bảng <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input class="input is-small" type="text" :value="tablesetting.find(v=>v.code==='table-font-size').detail"
@change="changeSetting($event.target.value, 'table-font-size')">
</p>
</div>
<div class="field" >
<label class="label fs-14"> Cỡ chữ tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<p class="control fs-14">
<input class="input is-small" type="text" :value="tablesetting.find(v=>v.code==='header-font-size').detail"
@change="changeSetting($event.target.value, 'header-font-size')">
</p>
</p>
</div>
<div class="field">
<label class="label fs-14"> Số dòng trên 1 trang <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input class="input is-small" type="text" :value="tablesetting.find(v=>v.code==='per-page').detail"
@change="changeSetting($event.target.value, 'per-page')">
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu nền bảng <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" :value="tablesetting.find(v=>v.code==='table-background').detail"
@change="changeSetting($event.target.value, 'table-background')">
</p>
</div>
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu chữ <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" :value="tablesetting.find(v=>v.code==='table-font-color').detail"
@change="changeSetting($event.target.value, 'table-font-color')">
</p>
<label class="label fs-14"> Cỡ chữ của bảng <span class="has-text-danger"> * </span></label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="tablesetting.find((v) => v.code === 'table-font-size').detail"
@change="changeSetting($event.target.value, 'table-font-size')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Cỡ chữ tiêu đề <span class="has-text-danger"> * </span></label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="tablesetting.find((v) => v.code === 'header-font-size').detail"
@change="changeSetting($event.target.value, 'header-font-size')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Số dòng trên 1 trang <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="tablesetting.find((v) => v.code === 'per-page').detail"
@change="changeSetting($event.target.value, 'per-page')"
/>
</p>
</div>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu chữ tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" :value="tablesetting.find(v=>v.code==='header-font-color').detail"
@change="changeSetting($event.target.value, 'header-font-color')">
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu nền tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" :value="tablesetting.find(v=>v.code==='header-background').detail"
@change="changeSetting($event.target.value, 'header-background')">
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu chữ khi filter<span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input type="color" :value="tablesetting.find(v=>v.code==='header-filter-color').detail"
@change="changeSetting($event.target.value, 'header-filter-color')">
</p>
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu nền bảng <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-background').detail"
@change="changeSetting($event.target.value, 'table-background')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu chữ <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'table-font-color').detail"
@change="changeSetting($event.target.value, 'table-font-color')"
/>
</p>
</div>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field" >
<label class="label fs-14"> Đường viền <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input class="input is-small" type="text"
:value="tablesetting.find(v=>v.code==='td-border')? tablesetting.find(v=>v.code==='td-border').detail : undefined"
@change="changeSetting($event.target.value, 'td-border')">
</p>
<div class="field-body">
<div class="field">
<label class="label fs-14"> Màu chữ tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-font-color').detail"
@change="changeSetting($event.target.value, 'header-font-color')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu nền tiêu đề <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-background').detail"
@change="changeSetting($event.target.value, 'header-background')"
/>
</p>
</div>
<div class="field">
<label class="label fs-14"> Màu chữ khi filter<span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
type="color"
:value="tablesetting.find((v) => v.code === 'header-filter-color').detail"
@change="changeSetting($event.target.value, 'header-filter-color')"
/>
</p>
</div>
</div>
</div>
<div class="field is-horizontal mt-5">
<div class="field-body">
<div class="field">
<label class="label fs-14"> Đường viền <span class="has-text-danger"> * </span> </label>
<p class="control fs-14">
<input
class="input is-small"
type="text"
:value="
tablesetting.find((v) => v.code === 'td-border')
? tablesetting.find((v) => v.code === 'td-border').detail
: undefined
"
@change="changeSetting($event.target.value, 'td-border')"
/>
</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useStore } from '@/stores/index'
const store = useStore()
var props = defineProps({
pagename: String
})
const { $copy, $clone, $empty } = useNuxtApp()
var pagedata = $clone(store[props.pagename])
var errors = []
var radioNote = 'no'
var tablesetting = pagedata.tablesetting
let found = tablesetting.find(v=>v.code==='note')
if(found? found.detail!=='@' : false) radioNote = 'yes'
function changeSetting(value, code) {
if(code==='note' && $empty(value)) return
let copy = $copy(tablesetting)
let found = copy.find(v=>v.code===code)
if(found) found.detail = value
else {
found = $copy(tablesetting.find(v=>v.code===code))
found.detail = value
copy.push(found)
}
tablesetting = copy
pagedata.tablesetting = tablesetting
store.commit(props.pagename, pagedata)
import { useStore } from "@/stores/index";
const store = useStore();
var props = defineProps({
pagename: String,
});
const { $copy, $clone, $empty } = useNuxtApp();
var pagedata = $clone(store[props.pagename]);
var errors = [];
var radioNote = "no";
var tablesetting = pagedata.tablesetting;
let found = tablesetting.find((v) => v.code === "note");
if (found ? found.detail !== "@" : false) radioNote = "yes";
function changeSetting(value, code) {
if (code === "note" && $empty(value)) return;
let copy = $copy(tablesetting);
let found = copy.find((v) => v.code === code);
if (found) found.detail = value;
else {
found = $copy(tablesetting.find((v) => v.code === code));
found.detail = value;
copy.push(found);
}
</script>
tablesetting = copy;
pagedata.tablesetting = tablesetting;
store.commit(props.pagename, pagedata);
}
</script>

View File

@@ -1,92 +1,157 @@
<template>
<div class="pb-1" style="border-bottom: 2px solid #3c5b63" v-if="array || !enableTime">
<div
class="pb-1"
style="border-bottom: 2px solid #3c5b63"
v-if="array || !enableTime"
>
<div class="columns mx-0 mb-0">
<div class="column is-8 px-0 pb-0" v-if="enableTime">
<div
class="column is-8 px-0 pb-0"
v-if="enableTime"
>
<div class="field is-grouped is-grouped-multiline mb-0">
<div class="control mb-0">
<Caption v-bind="{ title: lang === 'vi' ? 'Thời gian' : 'Time', type: 'has-text-warning' }" />
<Caption
v-bind="{
title: lang === 'vi' ? 'Thời gian' : 'Time',
type: 'has-text-warning',
}"
/>
</div>
<div class="control mb-0" v-for="v in array" :key="v.code">
<span class="icon-text fsb-16 has-text-warning px-1" v-if="v.code === current">
<div
class="control mb-0"
v-for="v in array"
:key="v.code"
>
<span
class="icon-text fsb-16 has-text-warning px-1"
v-if="v.code === current"
>
<SvgIcon v-bind="{ name: 'tick.png', size: 20 }"></SvgIcon>
<span>{{ v.name }}</span>
</span>
<span class="icon-text has-text-grey hyperlink px-1 fsb-16" @click="changeOption(v)" v-else>{{
v.name
}}</span>
<span
class="icon-text has-text-grey hyperlink px-1 fsb-16"
@click="changeOption(v)"
v-else
>{{ v.name }}</span
>
</div>
<span v-if="newDataAvailable" class="has-text-danger is-italic is-size-6 ml-2"> dữ liệu mới, vui lòng làm
mới.</span>
<span
v-if="newDataAvailable"
class="has-text-danger is-italic is-size-6 ml-2"
>Có dữ liệu mới, vui lòng làm mới.</span
>
</div>
</div>
<div class="column is-4 px-0">
<div class="field is-grouped is-grouped-multiline mb-0">
<div class="control mb-0">
<Caption v-bind="{
type: 'has-text-warning',
title: lang === 'vi' ? `Tìm ${viewport === 1 ? '' : 'kiếm'}` : 'Search',
}" />
<Caption
v-bind="{
type: 'has-text-warning',
title: lang === 'vi' ? `Tìm ${viewport === 1 ? '' : 'kiếm'}` : 'Search',
}"
/>
</div>
<div class="control mb-0">
<input class="input is-small" type="text" v-model="text"
:style="`${viewport === 1 ? 'width:150px;' : ''} border: 1px solid #BEBEBE;`" @keyup="startSearch"
id="input" :placeholder="lang === 'vi' ? 'Nhập từ khóa...' : 'Enter keyword...'" />
<input
class="input is-small"
type="text"
v-model="text"
:style="`${viewport === 1 ? 'width:150px;' : ''} border: 1px solid #BEBEBE;`"
@keyup="startSearch"
id="input"
:placeholder="lang === 'vi' ? 'Nhập từ khóa...' : 'Enter keyword...'"
/>
</div>
<div class="control mb-0">
<span class="tooltip" v-if="importdata && $getEditRights()">
<a class="mr-2" @click="openImport()">
<SvgIcon v-bind="{
name: 'upload.svg',
type: 'findata',
size: 22
}"></SvgIcon>
<span
class="tooltip"
v-if="importdata && $getEditRights()"
>
<a
class="mr-2"
@click="openImport()"
>
<SvgIcon
v-bind="{
name: 'upload.svg',
type: 'findata',
size: 22,
}"
></SvgIcon>
</a>
<span class="tooltiptext" style="min-width: max-content">
<span
class="tooltiptext"
style="min-width: max-content"
>
{{ lang === "vi" ? "Nhập dữ liệu" : "Import data" }}
</span>
</span>
<span class="tooltip" v-if="enableAdd && $getEditRights()">
<a class="mr-2" @click="$emit('add')">
<span
class="tooltip"
v-if="enableAdd && $getEditRights()"
>
<a
class="mr-2"
@click="$emit('add')"
>
<SvgIcon v-bind="{ name: 'add1.png', type: 'findata', size: 22 }"></SvgIcon>
</a>
<span class="tooltiptext" style="min-width: max-content">{{
lang === "vi" ? "Thêm mới" : "Add new"
}}</span>
<span
class="tooltiptext"
style="min-width: max-content"
>{{ lang === "vi" ? "Thêm mới" : "Add new" }}</span
>
</span>
<span class="tooltip">
<a class="mr-2" @click="$emit('excel')">
<a
class="mr-2"
@click="$emit('excel')"
>
<SvgIcon v-bind="{ name: 'excel.png', type: 'findata', size: 22 }"></SvgIcon>
</a>
<span class="tooltiptext" style="min-width: max-content">{{
lang === "vi" ? "Xuất excel" : "Export excel"
}}</span>
<span
class="tooltiptext"
style="min-width: max-content"
>{{ lang === "vi" ? "Xuất excel" : "Export excel" }}</span
>
</span>
<span class="tooltip">
<a @click="$emit('refresh-data')">
<SvgIcon v-bind="{ name: 'refresh.svg', type: 'findata', size: 22 }"></SvgIcon>
</a>
<span class="tooltiptext" style="min-width: max-content">{{
lang === "vi" ? "Làm mới" : "Refresh"
}}</span>
<span
class="tooltiptext"
style="min-width: max-content"
>{{ lang === "vi" ? "Làm mới" : "Refresh" }}</span
>
</span>
<a class="button is-primary is-loading is-small ml-3" v-if="loading"></a>
<a
class="button is-primary is-loading is-small ml-3"
v-if="loading"
></a>
</div>
</div>
</div>
</div>
<Modal @close="showmodal = undefined" v-bind="showmodal" v-if="showmodal"></Modal>
<Modal
@close="showmodal = undefined"
v-bind="showmodal"
v-if="showmodal"
></Modal>
</div>
</template>
<script>
import { useStore } from "@/stores/index"
import { useStore } from "@/stores/index";
export default {
setup() {
const store = useStore()
return { store }
const store = useStore();
return { store };
},
props: ["pagename", "api", "timeopt", "filter", "realtime", "newDataAvailable", "params", "importdata"],
@@ -117,19 +182,19 @@ export default {
pagedata: undefined,
loading: false,
pollingInterval: null,
searchableFields: [] // Lưu thông tin các field có thể tìm kiếm
}
searchableFields: [], // Lưu thông tin các field có thể tìm kiếm
};
},
watch: {
pagename(newVal) {
this.updateSearchableFields()
}
this.updateSearchableFields();
},
},
async created() {
// Cập nhật searchable fields ngay từ đầu
this.updateSearchableFields()
this.updateSearchableFields();
if (this.viewport < 5) {
this.options = [
@@ -139,85 +204,83 @@ export default {
{ code: 30, name: "1M" },
{ code: 90, name: "3M" },
{ code: 36000, name: "Tất cả" },
]
];
}
this.checkTimeopt()
if (!this.enableTime) return this.$emit("option")
this.checkTimeopt();
if (!this.enableTime) return this.$emit("option");
let found = this.$findapi(this.api)
found.commit = undefined
let found = this.$findapi(this.api);
found.commit = undefined;
let filter = this.$copy(this.filter)
let filter = this.$copy(this.filter);
if (filter) {
//dynamic parameter
for (const [key, value] of Object.entries(filter)) {
if (value.toString().indexOf("$") >= 0) {
filter[key] = this.store[value.replace("$", "")].id
filter[key] = this.store[value.replace("$", "")].id;
}
}
}
if (found.params.filter) {
if (!filter) filter = {}
if (!filter) filter = {};
for (const [key, value] of Object.entries(found.params.filter)) {
filter[key] = value
filter[key] = value;
}
}
this.options.map((v) => {
let f = filter ? this.$copy(filter) : {}
f["create_time__date__gte"] = this.$dayjs()
.subtract(v.code, "day")
.format("YYYY-MM-DD")
v.filter = f
})
let f = filter ? this.$copy(filter) : {};
f["create_time__date__gte"] = this.$dayjs().subtract(v.code, "day").format("YYYY-MM-DD");
v.filter = f;
});
this.$emit("option", this.$find(this.options, { code: this.current }))
this.$emit("option", this.$find(this.options, { code: this.current }));
let f = {}
let f = {};
this.options.map((v) => {
f[`${v.code}`] = {
type: "Count",
field: "create_time__date",
filter: v.filter
}
})
filter: v.filter,
};
});
let params = { summary: "aggregate", distinct_values: f }
found.params = params
let params = { summary: "aggregate", distinct_values: f };
found.params = params;
try {
let rs = await this.$getapi([found])
let rs = await this.$getapi([found]);
for (const [key, value] of Object.entries(rs[0].data.rows)) {
let found = this.$find(this.options, { code: Number(key) })
let found = this.$find(this.options, { code: Number(key) });
if (found) {
found.name = `${found.name} (${value})`
found.name = `${found.name} (${value})`;
}
}
} catch (error) {
console.error("Error fetching data:", error)
console.error("Error fetching data:", error);
}
this.array = this.$copy(this.options)
this.array = this.$copy(this.options);
},
mounted() {
if (this.realtime) {
const interval = typeof this.realtime === "number" ? this.realtime * 1000 : 5000
this.pollingInterval = setInterval(this.refresh, interval)
const interval = typeof this.realtime === "number" ? this.realtime * 1000 : 5000;
this.pollingInterval = setInterval(this.refresh, interval);
}
},
beforeUnmount() {
if (this.pollingInterval) {
clearInterval(this.pollingInterval)
clearInterval(this.pollingInterval);
}
},
computed: {
lang: function () {
return this.store.lang
return this.store.lang;
},
},
@@ -226,140 +289,141 @@ export default {
updateSearchableFields() {
try {
// Lấy API config
const found = this.$findapi(this.api)
const found = this.$findapi(this.api);
if (!found) {
console.warn('Không tìm thấy API config')
this.choices = []
this.searchableFields = []
return
console.warn("Không tìm thấy API config");
this.choices = [];
this.searchableFields = [];
return;
}
// Ưu tiên lấy values từ props.params, nếu không có thì lấy từ API config
let valuesString = ''
let valuesString = "";
if (this.params && this.params.values) {
// Lấy từ props.params (ưu tiên cao nhất)
valuesString = this.params.values
valuesString = this.params.values;
} else if (found.params && found.params.values) {
// Lấy từ API config mặc định
valuesString = found.params.values
valuesString = found.params.values;
} else {
console.warn('Không tìm thấy API values trong props hoặc config')
this.choices = []
this.searchableFields = []
return
console.warn("Không tìm thấy API values trong props hoặc config");
this.choices = [];
this.searchableFields = [];
return;
}
// Parse values string từ API
let fieldNames = valuesString.split(',').map(v => v.trim())
let fieldNames = valuesString.split(",").map((v) => v.trim());
// Lấy pagedata để lấy label
this.pagedata = this.store[this.pagename]
this.pagedata = this.store[this.pagename];
// Lấy tất cả các field để search (không lọc format)
const searchable = fieldNames.filter(fieldName => {
const searchable = fieldNames.filter((fieldName) => {
// Loại bỏ các field kỹ thuật
if (fieldName === 'id' ||
fieldName === 'create_time' ||
fieldName === 'update_time' ||
fieldName === 'created_at' ||
fieldName === 'updated_at') {
return false
if (
fieldName === "id" ||
fieldName === "create_time" ||
fieldName === "update_time" ||
fieldName === "created_at" ||
fieldName === "updated_at"
) {
return false;
}
return true
})
return true;
});
// Lấy tên và label các field
this.choices = searchable
this.searchableFields = searchable.map(fieldName => {
this.choices = searchable;
this.searchableFields = searchable.map((fieldName) => {
// Lấy field base name (trước dấu __)
const baseFieldName = fieldName.split('__')[0]
const fieldInfo = this.pagedata && this.pagedata.fields
? this.pagedata.fields.find(f => f.name === baseFieldName)
: null
const baseFieldName = fieldName.split("__")[0];
const fieldInfo =
this.pagedata && this.pagedata.fields ? this.pagedata.fields.find((f) => f.name === baseFieldName) : null;
return {
name: fieldName,
label: fieldInfo ? fieldInfo.label : fieldName
}
})
label: fieldInfo ? fieldInfo.label : fieldName,
};
});
} catch (error) {
console.error('Error updating searchable fields:', error)
this.choices = []
this.searchableFields = []
console.error("Error updating searchable fields:", error);
this.choices = [];
this.searchableFields = [];
}
},
refresh() {
let found = this.$find(this.options, { code: this.current })
this.changeOption(found)
let found = this.$find(this.options, { code: this.current });
this.changeOption(found);
},
changeOption(v) {
this.current = v.code
this.current = v.code;
if (this.search) {
this.text = undefined
this.search = undefined
this.text = undefined;
this.search = undefined;
}
this.$emit("option", this.$find(this.array, { code: this.current }))
this.$emit("option", this.$find(this.array, { code: this.current }));
},
doSearch() {
// Cập nhật choices trước khi search
this.updateSearchableFields()
this.updateSearchableFields();
this.pagedata = this.store[this.pagename]
this.pagedata = this.store[this.pagename];
if (!this.pagedata || !this.pagedata.fields) {
console.warn('Không có pagedata hoặc fields')
return
console.warn("Không có pagedata hoặc fields");
return;
}
let fields = this.pagedata.fields.filter(
(v) => this.choices.findIndex((x) => x === v.name) >= 0
)
let fields = this.pagedata.fields.filter((v) => this.choices.findIndex((x) => x === v.name) >= 0);
if (fields.length === 0) {
console.warn('Không tìm thấy field để tìm kiếm')
return
console.warn("Không tìm thấy field để tìm kiếm");
return;
}
let f = {}
let f = {};
fields.map((v) => {
f[`${v.name}__icontains`] = this.search
})
f[`${v.name}__icontains`] = this.search;
});
this.$emit("option", { filter_or: f })
this.$emit("option", { filter_or: f });
},
openImport() {
if (!this.importdata) return
if (!this.importdata) return;
// Emit event lên parent (DataView)
this.$emit('import', this.importdata)
this.$emit("import", this.importdata);
},
startSearch(val) {
this.search = this.$empty(val.target.value)
? ""
: val.target.value.trim()
this.search = this.$empty(val.target.value) ? "" : val.target.value.trim();
if (this.timer) clearTimeout(this.timer)
this.timer = setTimeout(() => this.doSearch(), 300)
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => this.doSearch(), 300);
},
checkTimeopt() {
if (this.timeopt > 0) {
let obj = this.$find(this.options, { code: this.$formatNumber(this.timeopt) })
if (obj) this.current = obj.code
let obj = this.$find(this.options, {
code: this.$formatNumber(this.timeopt),
});
if (obj) this.current = obj.code;
}
if (this.timeopt ? this.$empty(this.timeopt.disable) : true) return
if (this.timeopt.disable.indexOf("add") >= 0) this.enableAdd = false
if (this.timeopt.disable.indexOf("time") >= 0) this.enableTime = false
if (this.timeopt ? this.$empty(this.timeopt.disable) : true) return;
if (this.timeopt.disable.indexOf("add") >= 0) this.enableAdd = false;
if (this.timeopt.disable.indexOf("time") >= 0) this.enableTime = false;
if (this.timeopt.time) {
let obj = this.$find(this.options, { code: this.$formatNumber(this.timeopt.time) })
if (obj) this.current = obj.code
let obj = this.$find(this.options, {
code: this.$formatNumber(this.timeopt.time),
});
if (obj) this.current = obj.code;
}
},
},
}
</script>
};
</script>

View File

@@ -5,6 +5,6 @@
<script setup>
const props = defineProps({
text: { type: String, required: true },
color: { type: String, default: "#000" }
})
color: { type: String, default: "#000" },
});
</script>

View File

@@ -1,10 +1,10 @@
<template>
<span :style="color? `color:${color}` : ''">{{ $dayjs(date).format('DD/MM/YYYY') }}</span>
<span :style="color ? `color:${color}` : ''">{{ $dayjs(date).format("DD/MM/YYYY") }}</span>
</template>
<script setup>
const { $dayjs } = useNuxtApp()
const { $dayjs } = useNuxtApp();
const props = defineProps({
date: String,
color: String
})
</script>
color: String,
});
</script>

View File

@@ -1,10 +1,10 @@
<template>
<span :style="color? `color:${color}` : ''">{{ value === 0 || value === null ? '-' : $numtoString(value) }}</span>
<span :style="color ? `color:${color}` : ''">{{ value === 0 || value === null ? "-" : $numtoString(value) }}</span>
</template>
<script setup>
const { $numtoString } = useNuxtApp()
const { $numtoString } = useNuxtApp();
const props = defineProps({
value: Number,
color: String
})
</script>
color: String,
});
</script>

View File

@@ -1,10 +1,10 @@
<template>
<span :style="color? `color:${color}` : ''">{{ $numtoString(value) }}</span>
<span :style="color ? `color:${color}` : ''">{{ $numtoString(value) }}</span>
</template>
<script setup>
const { $numtoString } = useNuxtApp()
const { $numtoString } = useNuxtApp();
const props = defineProps({
value: Number,
color: String
})
</script>
color: String,
});
</script>