476 lines
14 KiB
Vue
476 lines
14 KiB
Vue
<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>
|
|
</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
|
|
>
|
|
</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
|
|
size="is-small"
|
|
v-model="tags"
|
|
:data="fields.filter(v=>v.format==='number')"
|
|
type="is-dark is-light"
|
|
autocomplete
|
|
:open-on-focus="true"
|
|
field="caption"
|
|
icon="plus"
|
|
placeholder="Chọn trường"
|
|
>
|
|
<template slot-scope="props">
|
|
<span class="mr-3 has-text-danger">{{props.option.name}}</span>
|
|
<span :class="tags.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 === '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>
|
|
</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>
|
|
</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">Mô 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.",
|
|
});
|
|
}
|
|
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");
|
|
}
|
|
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>
|