650 lines
20 KiB
Vue
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 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>
|