This commit is contained in:
Xuan Loi
2026-01-09 17:25:23 +07:00
commit ae1ea57130
315 changed files with 57694 additions and 0 deletions

View File

@@ -0,0 +1,431 @@
<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>
<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="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}}
</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 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 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à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à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'">
<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">
<span class="icon-text">
<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">
<span class="icon-text fsb-18">
Replace
<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">
</div>
<div class="control">
<p class="fsb-14 mb-1">Thay bằng</p>
<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>
</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>