Files
web/app/components/datatable/CreateTemplate.vue
2026-05-21 16:54:07 +07:00

650 lines
20 KiB
Vue

<template>
<div v-if="docid">
<div class="field is-horizontal">
<div class="field-body">
<div class="field">
<label class="label">Đối tượng</label>
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in types"
:key="i"
class="radio"
>
<input
v-model="type"
@input="changeType(v)"
type="radio"
name="type"
/>
{{ v.name }}
</label>
</div>
</div>
<div class="field">
<label class="label">Kích cỡ</label>
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in sizes.filter((v) =>
type ? (type.code === 'tag' ? v.code !== 'is-small' : 1 > 0) : true,
)"
:key="i"
class="radio"
>
<input
v-model="size"
@input="changeType(v)"
type="radio"
name="size"
/>
{{ v.name }}
</label>
</div>
</div>
<div class="field">
<label
class="label"
v-if="['tag'].find((v) => v === type.code)"
>Hình khối</label
>
<div class="control fs-14 is-flex is-gap-2">
<label
v-for="(v, i) in shapes"
:key="i"
class="radio"
>
<input
v-model="shape"
@input="changeType(v)"
type="radio"
name="shape"
/>
{{ v.name }}
</label>
</div>
</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>
<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="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="">{{ 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 }}
</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 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 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>
</div>
</div>
<div class="field">
<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>
</div>
</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>
</p>
</div>
<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()"
/>
</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>
</p>
</div>
<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()"
/>
</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">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>
</p>
</div>
<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()"
/>
</p>
</div>
</div>
</div>
</template>
<template v-else-if="tab.code === 'template'">
<div class="buttons has-addons mb-3">
<button
@click="copyContent()"
class="button is-primary is-light fs-14"
>
<span class="icon">
<Icon
name="material-symbols:content-copy-outline-rounded"
:size="18"
/>
</span>
<span>Copy</span>
</button>
<button
@click="paste()"
class="button is-primary is-light fs-14"
>
<span class="icon">
<Icon
name="material-symbols:content-paste-rounded"
:size="18"
/>
</span>
<span>Paste</span>
</button>
</div>
<div>
<textarea
class="textarea fs-13"
style="font-family: monospace"
rows="4"
v-model="text"
@dblclick="doCheck"
></textarea>
</div>
<p class="mt-5">
<span class="icon-text">
<span class="fs-17 font-semibold">Replace</span>
<span class="icon">
<Icon
name="material-symbols:arrow-forward-ios-rounded"
:size="18"
/>
</span>
</span>
</p>
<div class="field is-grouped mt-4">
<div class="control">
<label class="label">Đoạn text</label>
<input
class="input"
type="text"
v-model="source"
/>
</div>
<div class="control">
<label class="label">Thay bằng</label>
<input
class="input"
type="text"
v-model="target"
/>
</div>
<div class="control">
<label
class="label"
style="opacity: 0"
>Hidden</label
>
<button
class="button is-primary is-light"
@click="replace()"
>
Replace
</button>
</div>
</div>
<p class="mt-5">
<button
class="button is-primary has-text-white"
@click="changeTemplate()"
>
Áp dụng
</button>
</p>
</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");
};
/*watch: {
expression: function(newVal) {
if($empty(newVal)) return
elsecheckExpression()
},
tab: function(newVal, oldVal) {
if(oldVal===undefined) return
if(newVal.code==='template') {
let value = '<div>'
tags.map((v,i)=>{
value += '<span class="' + v.class + (tags.length>i+1? ' mr-2' : '') + '" '
if(v.style) value += 'style="' + v.style + '" '
value += (v.expression? ' v-if="' + v.expression + '"' : '') + '>' + v.name + '</span>'
})
value += '</div>'
text = value
} else if(newVal.code==='option') {
if(!selected) return
radioBGcolor =selected.bgcolor?colorchoice.find(v=>v.code==='option') :colorchoice.find(v=>v.code==='none')
radioColor =selected.color?colorchoice.find(v=>v.code==='option') :colorchoice.find(v=>v.code==='none')
radioSize =selected.textsize?colorchoice.find(v=>v.code==='option') :colorchoice.find(v=>v.code==='none')
bgcolor =selected.bgcolor?selected.bgcolor : undefined
color =selected.color?selected.color : undefined
textsize =selected.textsize?selected.textsize : undefined
} else if(newVal.code==='condition') {
condition = conditions.find(v=>v.code==='no')
tagsField = []
expression = ''
if(selected?selected.expression : false) {
condition =conditions.find(v=>v.code==='yes')
tagsField =$copy(selected.tags)
expression =$copy(selected.formula)
}
}
}
},*/
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; ";
}
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>