Files
system/components/datatable/NewField.vue
Xuan Loi ae1ea57130 changes
2026-01-09 17:25:23 +07:00

334 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 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"> 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>