Base Login
This commit is contained in:
39
components/datatable/ChartField.vue
Normal file
39
components/datatable/ChartField.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<p><label class="label fs-14">Chọn cột hiển thị</label></p>
|
||||
<div class="field is-grouped is-grouped-multiline mt-2">
|
||||
<div class="control" v-for="(v,i) in args" :key="i">
|
||||
<div class="tags has-addons">
|
||||
<a class="tag is-primary">{{$stripHtml(v.label)}}</a>
|
||||
<a class="tag is-delete" @click="remove(args, i)"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename'],
|
||||
data() {
|
||||
return {
|
||||
args: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.args = this.$copy(this.pagedata.fields.filter(v=>v.format==='number' && v.show))
|
||||
},
|
||||
computed: {
|
||||
pagedata: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
remove(args, i) {
|
||||
this.$delete(args, i)
|
||||
this.$emit('changefields', args)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
946
components/datatable/ContextMenu.vue
Normal file
946
components/datatable/ContextMenu.vue
Normal file
@@ -0,0 +1,946 @@
|
||||
<template>
|
||||
<div class="fs-14 has-text-weight-normal">
|
||||
<div>
|
||||
<b-tooltip label="Sắp xếp tăng dần" type="is-dark" position="is-right">
|
||||
<a @click="checkFilter()? false : $emit('modalevent', {name: 'dosort', data: 'az'})">
|
||||
<span class="icon is-medium fs-22 mr-2" :class="checkFilter()? 'has-text-grey-light' : ''">
|
||||
<i class="mdi mdi-sort-alphabetical-ascending" />
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Sắp xếp giảm dần" type="is-dark" position="is-right">
|
||||
<a @click="checkFilter()? false : $emit('modalevent', {name: 'dosort', data: 'za'})">
|
||||
<span class="icon is-medium fs-22 mr-2" :class="checkFilter()? 'has-text-grey-light' : '' " >
|
||||
<i class="mdi mdi-sort-alphabetical-descending" />
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Chuyển cột sang trái" type="is-dark" position="is-right">
|
||||
<a>
|
||||
<span class="icon is-medium fs-22 mr-2" @click="moveLeft()">
|
||||
<i class="mdi mdi-arrow-left" />
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Chuyển cột sang phải" type="is-dark" position="is-right">
|
||||
<a>
|
||||
<span class="icon is-medium fs-22 mr-2" @click="moveRight()">
|
||||
<i class="mdi mdi-arrow-right"/>
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Ẩn cột" type="is-dark" position="is-right">
|
||||
<a @click="hideField()">
|
||||
<span class="icon is-medium fs-22 mr-2">
|
||||
<i class="mdi mdi-eye-off-outline"/>
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Xóa cột" type="is-dark" position="is-right">
|
||||
<a>
|
||||
<span class="icon is-medium fs-22 mr-2" @click="currentField.mandatory? false : doRemove()">
|
||||
<i :class="`mdi mdi-delete-outline ${currentField.mandatory? 'has-text-grey-light' : ''}`"/>
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Sao chép cột" type="is-dark" position="is-right">
|
||||
<a>
|
||||
<span class="icon is-medium fs-20 mr-2" @click="$emit('modalevent', {name: 'copyfield', data:currentField})">
|
||||
<i class="mdi mdi-content-copy"/>
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Tăng độ rộng cột" type="is-dark" position="is-right">
|
||||
<a>
|
||||
<span class="icon is-medium fs-22 mr-2" @click="resizeWidth()">
|
||||
<i class="mdi mdi-arrow-expand-horizontal"/>
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Giảm độ rộng cột" type="is-dark" position="is-right">
|
||||
<a>
|
||||
<span class="icon is-medium fs-22 mr-2" @click="resizeWidth(true)">
|
||||
<i class="mdi mdi-arrow-collapse-horizontal"/>
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Danh sách cột" type="is-dark" position="is-right">
|
||||
<a>
|
||||
<span class="icon is-medium mr-2 fs-22" @click="$emit('modalevent', {name: 'showsidebar', data: {field: currentField, name: 'option'}})">
|
||||
<i class="mdi mdi-format-list-bulleted"/>
|
||||
</span>
|
||||
</a>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
<div class="field mt-2 mb-2" v-if="currentField.disable? currentField.disable.indexOf('search') <0 : true">
|
||||
<div :class="loading? 'control is-loading' : 'control'">
|
||||
<input class="input is-rounded fs-13" type="text" v-model="search" @keyup="startSearch" @keypress.enter="pressEnter"
|
||||
:placeholder="'Tìm kiếm: ' + (currentField.label.indexOf('<')>=0? currentField.name : currentField.label)"
|
||||
@focus="doFocus()" :ref="currentField.name">
|
||||
</div>
|
||||
</div>
|
||||
<p class="panel-tabs mb-2">
|
||||
<a v-for="(v,i) in getMenu().filter(x=>currentField.format==='number'? (currentField.formula? true : x.code!=='formula')
|
||||
: !['filter','formula'].find(y=>y===x.code))" :key="i"
|
||||
:class="selectTab.code===v.code? 'is-active' : ''" @click="selectTab=v"> {{v.name}}
|
||||
</a>
|
||||
</p>
|
||||
<template v-if="selectTab.code==='value'">
|
||||
<ScrollBox v-bind="{data: filterData, name: currentField.name, maxheight: '380px', perpage: 20, selects: checkSelected()}"
|
||||
@selected="doSelect" />
|
||||
</template>
|
||||
<template v-else-if="selectTab.code==='display'">
|
||||
<div class="field is-horizontal border-bottom pb-0 mb-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" :key="i" v-model="radioBGcolor"
|
||||
:native-value="v" @input="changeBGColor()">
|
||||
{{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="changeBGColor()">
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="radioBGcolor? radioBGcolor.code==='condition' : false">
|
||||
<label class="label fs-14"> Mã màu <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<a class="button is-small is-primary is-outlined is-rounded" @click="doAdvance('bgcolor')"> Nâng cao </a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal border-bottom pb-0 mb-1">
|
||||
<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" :key="i" v-model="radioColor"
|
||||
:native-value="v" @input="changeColor()">
|
||||
{{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="changeColor()">
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="radioColor? radioColor.code==='condition' : false">
|
||||
<label class="label fs-14"> Mã màu <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<a class="button is-small is-primary is-outlined is-rounded" @click="doAdvance('color')"> Nâng cao </a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal border-bottom pb-0 mb-1">
|
||||
<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" :key="i" v-model="radioSize"
|
||||
:native-value="v" @input="changeSize()">
|
||||
{{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="changeSize()">
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="radioSize? radioSize.code==='condition' : false">
|
||||
<label class="label fs-14"> Cỡ chữ <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<a class="button is-small is-primary is-outlined is-rounded" @click="doAdvance('textsize')"> Nâng cao </a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal pb-0 mb-1 border-bottom">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Vị trí text</label>
|
||||
<p class="control fs-14">
|
||||
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioAlign"
|
||||
:native-value="v" @input="changeAlign()">{{v.name}}
|
||||
</b-radio>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-narrow" v-if="radioAlign? radioAlign.code==='option' : false">
|
||||
<label class="label fs-14">Chọn vị trí <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<b-autocomplete
|
||||
size="is-small"
|
||||
icon-right="magnify"
|
||||
:value="selectAlign? selectAlign.name : ''"
|
||||
placeholder=""
|
||||
:keep-first=true
|
||||
:open-on-focus=true
|
||||
:data="textalign"
|
||||
field="name"
|
||||
@select="option => { selectAlign = option; changeAlign()}">
|
||||
</b-autocomplete>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal border-bottom pb-0 mb-1">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Độ rộng nhỏ nhất</label>
|
||||
<p class="control fs-14">
|
||||
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioWidth"
|
||||
:native-value="v" @input="changeWidth()">
|
||||
{{v.name}}
|
||||
</b-radio>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="radioWidth? radioWidth.code==='option' : false">
|
||||
<label class="label fs-14"> Kích thước</label>
|
||||
<p class="control fs-14">
|
||||
<input class="input is-small" type="text" placeholder="Nhập số" v-model="minwidth" @change="changeWidth()">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal border-bottom pb-0 mb-1">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Độ rộng lớn nhất</label>
|
||||
<p class="control fs-14">
|
||||
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioMaxWidth"
|
||||
:native-value="v" @input="changeMaxWidth()">
|
||||
{{v.name}}
|
||||
</b-radio>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="radioMaxWidth? radioMaxWidth.code==='option' : false">
|
||||
<label class="label fs-14"> Kích thước</label>
|
||||
<p class="control fs-14">
|
||||
<input class="input is-small" type="text" placeholder="Nhập số" v-model="maxwidth" @change="changeMaxWidth()">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="selectTab.code==='filter'">
|
||||
<p class="mt-5 has-text-grey" v-if="arr===undefined">Không thể áp dụng đồng thời chức năng lọc cùng với sắp xếp</p>
|
||||
<div :class="`field is-horizontal mt-4`" v-for="(v,i) in arr" :key="i">
|
||||
<div class="field-body">
|
||||
<div class="field" style="width: 10%;">
|
||||
<label class="label" v-if="i===0">Điều kiện<span class="has-text-danger"> * </span></label>
|
||||
<p class="control">
|
||||
<b-autocomplete
|
||||
icon-right="magnify"
|
||||
v-model="v.condition"
|
||||
placeholder=""
|
||||
:keep-first=true
|
||||
:open-on-focus=true
|
||||
:data="arr2"
|
||||
field="code"
|
||||
@select="option => doOption()">
|
||||
</b-autocomplete>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" style="width:30%">
|
||||
<label class="label" v-if="i===0">Giá trị<span class="has-text-danger"> *</span></label>
|
||||
<p class="control">
|
||||
<input class="input" type="text" placeholder="" v-model="v.value" @change="checkValid()">
|
||||
</p>
|
||||
<p class="is-help mt-2 has-text-danger" v-if="v.error">{{v.error}}</p>
|
||||
</div>
|
||||
<div class="field" style="width: 10%;" v-if="arr.length>=1 && i===arr.length-2">
|
||||
<label class="label" v-if="i===0">Kết hợp<span class="has-text-danger"> * </span></label>
|
||||
<p class="control">
|
||||
<b-autocomplete
|
||||
icon-right="magnify"
|
||||
v-model="v.operator"
|
||||
placeholder=""
|
||||
:keep-first=true
|
||||
:open-on-focus=true
|
||||
:data="arr1"
|
||||
field="code"
|
||||
@select="option => doOption()">
|
||||
</b-autocomplete>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<div class="control">
|
||||
<div :class="i===0? 'mt-5 pt-2' : ''">
|
||||
<span class="icon has-text-primary is-clickable" @click="addCondition()" v-if="arr.length<2">
|
||||
<i class="mdi mdi-plus-thick fs-22"></i>
|
||||
</span>
|
||||
<span class="icon has-text-danger is-clickable" @click="removeCondition(i)" v-if="arr.length>1">
|
||||
<i class="mdi mdi-minus-thick fs-22"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="selectTab.code==='detail'">
|
||||
<p class="fs-14 mt-3"> <strong> Tên trường: </strong> {{currentField.name}}
|
||||
<a @click="copyContent(currentField.name)">
|
||||
<b-tooltip label="Copy tên trường" type="is-dark">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-content-copy"/>
|
||||
</span>
|
||||
</b-tooltip>
|
||||
</a>
|
||||
</p>
|
||||
<div class="field mt-3">
|
||||
<label class="label fs-14">Mô tả<span class="has-text-danger"> *</span></label>
|
||||
<div class="control has-icons-right">
|
||||
<input
|
||||
class="input fs-14"
|
||||
type="text"
|
||||
@change="changeLabel($event.target.value)"
|
||||
v-model="label"
|
||||
/>
|
||||
<a class="button is-clickable icon is-right fs-14" @click="editLabel()">
|
||||
<i class="mdi mdi-pen has-text-primary fs-18"></i>
|
||||
</a>
|
||||
<p class="help is-danger" v-if="errors.find((v) => v.name === 'label')">
|
||||
{{ errors.find((v) => v.name === "label").msg }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field mt-3">
|
||||
<label class="label fs-14">Kiểu dữ liệu<span class="has-text-danger"> * </span></label>
|
||||
<div class="control fs-14 has-text-primary">
|
||||
<b-radio v-for="(v,i) in datatype" :key="i" v-model="radioType" :native-value="v" disabled>
|
||||
{{v.name}}
|
||||
</b-radio>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal" v-if="field.format==='number'">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Đơn vị <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; changeUnit()}">
|
||||
</b-autocomplete>
|
||||
</div>
|
||||
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='unit')"> {{errors.find(v=>v.name==='unit').msg}} </p>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<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" @input="changeDecimal($event.target.value)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-3">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14">Định dạng nâng cao</label>
|
||||
<p class="control fs-14">
|
||||
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioTemplate"
|
||||
:native-value="v" @input="changeTemplate(undefined)">
|
||||
{{v.name}}
|
||||
</b-radio>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3" v-if="radioTemplate? radioTemplate.code==='option' : false">
|
||||
<button class="button is-primary is-outlined is-rounded is-small" @click="$emit('modalevent', {name: 'showsidebar', data: {name: 'template', field: currentField}})">
|
||||
<span class="fs-14">{{`${currentField.template? 'Sửa' : 'Tạo'} định dạng`}}</span>
|
||||
</button>
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="selectTab.code==='tooltip'">
|
||||
<p class="mt-5 fs-15 has-text-dark" v-if="currentField.template">
|
||||
Không thể sử dụng đồng thời template và tooltip
|
||||
</p>
|
||||
<template v-else>
|
||||
<div class="field mt-3">
|
||||
<label class="label fs-14">Sử dụng tooltip</label>
|
||||
<p class="control fs-14">
|
||||
<b-radio v-for="(v,i) in colorchoice.filter(v=>v.code!=='condition')" :key="i" v-model="radioTooltip"
|
||||
:native-value="v" @input="changeTooltip()">
|
||||
{{v.name}}
|
||||
</b-radio>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" v-if="radioTooltip? radioTooltip.code==='option' : false">
|
||||
<label class="label fs-14"> Chọn trường <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<b-autocomplete
|
||||
size="is-small"
|
||||
icon-right="magnify"
|
||||
:value="selectField? selectField.label : ''"
|
||||
placeholder=""
|
||||
:keep-first=true
|
||||
:open-on-focus=true
|
||||
:data="pagedata.fields"
|
||||
field="label"
|
||||
@select="option => {selectField = option; changeTooltip()}">
|
||||
</b-autocomplete>
|
||||
</p>
|
||||
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='tooltip')"> {{errors.find(v=>v.name==='tooltip').msg}} </p>
|
||||
</div>
|
||||
<div class="field mt-3" v-if="radioTooltip? radioTooltip.code==='option' : false">
|
||||
<label class="label fs-14"> Vị trí hiển thị <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<b-autocomplete
|
||||
size="is-small"
|
||||
icon-right="magnify"
|
||||
:value="selectPlacement? selectPlacement.name : ''"
|
||||
placeholder=""
|
||||
:keep-first=true
|
||||
:open-on-focus=true
|
||||
:data="placement"
|
||||
field="name"
|
||||
@select="option => {selectPlacement = option; changeTooltip()}">
|
||||
</b-autocomplete>
|
||||
</p>
|
||||
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='placement')"> {{errors.find(v=>v.name==='placement').msg}} </p>
|
||||
</div>
|
||||
<div class="field mt-3" v-if="radioTooltip? radioTooltip.code==='option' : false">
|
||||
<label class="label fs-14"> Bảng màu <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<b-autocomplete
|
||||
size="is-small"
|
||||
icon-right="magnify"
|
||||
:value="selectScheme? selectScheme.name : ''"
|
||||
placeholder=""
|
||||
:keep-first=true
|
||||
:open-on-focus=true
|
||||
:data="colorscheme"
|
||||
field="name"
|
||||
@select="option => {selectScheme = option; changeTooltip()}">
|
||||
</b-autocomplete>
|
||||
</p>
|
||||
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='tooltip')"> {{errors.find(v=>v.name==='tooltip').msg}} </p>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="selectTab.code==='formula'">
|
||||
<div class="field mt-3 px-0 mx-0">
|
||||
<label class="label fs-14"> Trường để tạo công thức <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">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 + ' ' + y.name) : y.name" class="tag is-dark is-rounded is-clickable"
|
||||
v-for="y in tags"><b-tooltip type="is-primary" :label="$stripHtml(y.label)">{{y.name}}</b-tooltip></span>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<b-tooltip type="is-dark" :label="v.name" 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>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<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="4" type="text" placeholder="Tạo công thức tại đây" 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 class="mt-5"><button class="button is-primary is-rounded" @click="changeFormula()">Cập nhật</button></div>
|
||||
</template>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal" @label="changeLabel"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
ScrollBox: ()=> import('@/components/datatable/ScrollBox')
|
||||
},
|
||||
props: ['pagename', 'field', 'filters', 'filterData', 'width'],
|
||||
data() {
|
||||
return {
|
||||
search: undefined,
|
||||
loading: false,
|
||||
fields: [],
|
||||
current: 1,
|
||||
currentPage: 1,
|
||||
timer: undefined,
|
||||
selectTab: undefined,
|
||||
radioBGcolor: undefined,
|
||||
radioColor: undefined,
|
||||
radioSize: undefined,
|
||||
selectAlign: undefined,
|
||||
radioAlign: undefined,
|
||||
radioWidth: undefined,
|
||||
minwidth: undefined,
|
||||
color: undefined,
|
||||
bgcolor: undefined,
|
||||
textsize: undefined,
|
||||
showPage: 1,
|
||||
perPage: 30,
|
||||
value1: undefined,
|
||||
value2: undefined,
|
||||
errors: [],
|
||||
label: undefined,
|
||||
radioType: undefined,
|
||||
selectUnit: undefined,
|
||||
radioTemplate: undefined,
|
||||
currentField: this.$copy(this.field),
|
||||
selectPlacement: undefined,
|
||||
radioTooltip: undefined,
|
||||
selectScheme: undefined,
|
||||
selectField: undefined,
|
||||
tags: [],
|
||||
formula: undefined,
|
||||
radioMaxWidth: undefined,
|
||||
maxwidth: undefined,
|
||||
bgcolorFilter: [{id: this.$id()}],
|
||||
colorFilter: [{id: this.$id()}],
|
||||
sizeFilter: [{id: this.$id()}],
|
||||
tabs: [{code: 'expression', name: 'Biểu thức'}, {code: 'script', name: 'Mã lệnh'}],
|
||||
tab: {},
|
||||
decimal: undefined,
|
||||
arr: [{id:this.$id(), operator: 'and'}],
|
||||
arr1: [{code: 'and', name: '&&'}, {code: 'or', name: 'or'}],
|
||||
arr2: [{code: '>', name: 'gt'}, {code: '>=', name: 'gte'}, {code: '=', name: 'e'},
|
||||
{code: '<=', name: 'lte'}, {code: '<', name: 'lt'}, {code: '<>', name: 'oth'}],
|
||||
showmodal: undefined,
|
||||
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'}]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.label = this.$copy(this.field.label)
|
||||
this.getFields()
|
||||
this.selectTab = this.getMenu()[0]
|
||||
this.tab = this.tabs.find(v=>v.code==='expression')
|
||||
let found = this.filters.find(v=>v.name===this.field.name)
|
||||
if(found) this.arr = this.$copy(found.filter)
|
||||
this.getDisplay(this.field)
|
||||
},
|
||||
watch: {
|
||||
selectTab: function(newVal) {
|
||||
if(newVal? newVal.code==='value' : false) {
|
||||
if(this.$refs[this.field.name]) this.$refs[this.field.name].focus()
|
||||
}
|
||||
},
|
||||
field: function(newVal) {
|
||||
this.currentField = this.$copy(newVal)
|
||||
this.getDisplay(newVal)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pagedata: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
tablesetting: {
|
||||
get: function() {return this.$store.state.tablesetting},
|
||||
set: function(val) {this.$store.commit("updateTableSetting", {tablesetting: val})}
|
||||
},
|
||||
colorchoice: {
|
||||
get: function() {return this.$store.state.colorchoice},
|
||||
set: function(val) {this.$store.commit("updateColorChoice", {colorchoice: val})}
|
||||
},
|
||||
textalign: {
|
||||
get: function() {return this.$store.state.textalign},
|
||||
set: function(val) {this.$store.commit("updateTextAlign", {textalign: val})}
|
||||
},
|
||||
filterchoice: {
|
||||
get: function () {return this.$store.state.filterchoice},
|
||||
set: function (val) {this.$store.commit("updateFilterChoice", { filterchoice: val })}
|
||||
},
|
||||
datatype: {
|
||||
get: function() {return this.$store.state.datatype},
|
||||
set: function(val) {this.$store.commit("updateDataType", {datatype: val})}
|
||||
},
|
||||
moneyunit: {
|
||||
get: function() {return this.$store.state.moneyunit},
|
||||
set: function(val) {this.$store.commit("updateMoneyUnit", {moneyunit: val})}
|
||||
},
|
||||
menuaction: {
|
||||
get: function() {return this.$store.state.menuaction},
|
||||
set: function(val) {this.$store.commit("updateMenuAction", {menuaction: val})}
|
||||
},
|
||||
placement: {
|
||||
get: function() {return this.$store.state.placement},
|
||||
set: function(val) {this.$store.commit("updatePlacement", {placement: val})}
|
||||
},
|
||||
colorscheme: {
|
||||
get: function() {return this.$store.state.colorscheme},
|
||||
set: function(val) {this.$store.commit("updateColorScheme", {colorscheme: val})}
|
||||
},
|
||||
menuchoice: {
|
||||
get: function() {return this.$store.state.menuchoice},
|
||||
set: function(val) {this.$store.commit("updateMenuChoice", {menuchoice: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addOperator(v) {
|
||||
let text = v.code==='iif'? 'a>b? c : d' : v.code
|
||||
this.formula = `${this.formula || ''} ${text}`
|
||||
},
|
||||
doSelect(row) {
|
||||
this.$emit('modalevent', {name: 'doselect', data: row[this.field.name]})
|
||||
},
|
||||
editLabel() {
|
||||
this.showmodal = {component: 'datatable/EditLabel', width: '500px', height: '300px', vbind: {label: this.label}}
|
||||
},
|
||||
addCondition() {
|
||||
this.arr.push({})
|
||||
},
|
||||
removeCondition(i) {
|
||||
this.$delete(this.arr, i)
|
||||
this.setFilter(this.field)
|
||||
},
|
||||
getMenu() {
|
||||
let field = this.getfield()
|
||||
let arr = field.disable? field.disable.split(',') : undefined
|
||||
return arr? this.menuchoice.filter(v=>arr.findIndex(x=>x===v.code) <0) : this.menuchoice
|
||||
},
|
||||
getFields() {
|
||||
this.fields = this.pagedata? this.$copy(this.pagedata.fields) : []
|
||||
this.fields.map(v=>v.caption = (v.label? v.label.indexOf('<')>=0 : false)? v.name : v.label)
|
||||
},
|
||||
getDisplay(field) {
|
||||
this.current = 1
|
||||
this.value1 = undefined
|
||||
this.value2 = undefined
|
||||
this.radioType = this.datatype.find(v=>v.code===field.format)
|
||||
if(field.format==='number') this.selectUnit = this.moneyunit.find(v=>v.detail===field.unit)
|
||||
this.bgcolor = undefined
|
||||
this.radioBGcolor = this.colorchoice.find(v=>v.code==='none')
|
||||
this.color = undefined
|
||||
this.radioColor = this.colorchoice.find(v=>v.code==='none')
|
||||
this.textsize = undefined
|
||||
this.radioSize = this.colorchoice.find(v=>v.code==='none')
|
||||
this.minwidth = undefined
|
||||
this.radioWidth = this.colorchoice.find(v=>v.code==='none')
|
||||
this.radioMaxWidth = this.colorchoice.find(v=>v.code==='none')
|
||||
this.maxwidth = undefined
|
||||
this.selectAlign = undefined
|
||||
this.radioAlign = this.colorchoice.find(v=>v.code==='none')
|
||||
this.radioTemplate = this.colorchoice.find(v=>v.code=== (field.template? 'option' : 'none'))
|
||||
this.selectPlacement = this.placement.find(v=>v.code==='is-right')
|
||||
this.selectScheme = this.colorscheme.find(v=>v.code==='is-primary')
|
||||
this.radioTooltip = this.colorchoice.find(v=>v.code==='none')
|
||||
this.selectField = undefined
|
||||
this.tags = field.tags? field.tags.map(v=>this.fields.find(x=>x.name===v)) : []
|
||||
this.formula = field.formula? field.formula : undefined
|
||||
this.decimal = field.decimal
|
||||
let shortmenu = this.menuchoice.filter(x=>field.format==='number'? (field.formula? true : x.code!=='formula')
|
||||
: !['filter','formula'].find(y=>y===x.code))
|
||||
this.selectTab = shortmenu.find(v=>this.selectTab.code===v.code)? this.selectTab : this.menuchoice.find(v=>v.code==='value')
|
||||
this.search = undefined
|
||||
if(this.selectTab.code==='value') {
|
||||
let self = this
|
||||
setTimeout(function() {self.$refs[field.name]? self.$refs[field.name].focus() : false}, 50)
|
||||
}
|
||||
|
||||
this.bgcolorFilter = [{id: this.$id()}]
|
||||
if(field.bgcolor) {
|
||||
if(Array.isArray(field.bgcolor)) {
|
||||
this.radioBGcolor = this.colorchoice.find(v=>v.code==='condition')
|
||||
this.bgcolorFilter = this.$copy(field.bgcolor)
|
||||
} else {
|
||||
this.radioBGcolor = this.colorchoice.find(v=>v.code==='option')
|
||||
this.bgcolor = field.bgcolor
|
||||
}
|
||||
}
|
||||
|
||||
this.colorFilter = [{id: this.$id()}]
|
||||
if(field.color) {
|
||||
if(Array.isArray(field.color)) {
|
||||
this.radioColor = this.colorchoice.find(v=>v.code==='condition')
|
||||
this.colorFilter = this.$copy(field.color)
|
||||
} else {
|
||||
this.radioColor = this.colorchoice.find(v=>v.code==='option')
|
||||
this.color = field.color
|
||||
}
|
||||
}
|
||||
this.sizeFilter = [{id: this.$id()}]
|
||||
if(field.textsize) {
|
||||
if(Array.isArray(field.textsize)) {
|
||||
this.radioSize = this.colorchoice.find(v=>v.code==='condition')
|
||||
this.sizeFilter = field.textsize
|
||||
} else {
|
||||
this.radioSize = this.colorchoice.find(v=>v.code==='option')
|
||||
this.textsize = field.textsize
|
||||
}
|
||||
}
|
||||
|
||||
if(field.textalign) {
|
||||
this.radioAlign = this.colorchoice.find(v=>v.code==='option')
|
||||
this.selectAlign = this.textalign.find(v=>v.code===field.textalign)
|
||||
}
|
||||
|
||||
if(field.minwidth) {
|
||||
this.radioWidth = this.colorchoice.find(v=>v.code==='option')
|
||||
this.minwidth = field.minwidth
|
||||
}
|
||||
|
||||
if(field.maxwidth) {
|
||||
this.radioMaxWidth = this.colorchoice.find(v=>v.code==='option')
|
||||
this.maxwidth = field.maxwidth
|
||||
}
|
||||
|
||||
if(field.tooltip) {
|
||||
this.radioTooltip = this.colorchoice.find(v=>v.code==='option')
|
||||
this.selectPlacement = this.placement.find(v=>v.code===field.tooltip.placement)
|
||||
this.selectField = this.pagedata.fields.find(v=>v.name===field.tooltip.field)
|
||||
this.selectScheme = this.colorscheme.find(v=>v.code===field.tooltip.type)
|
||||
}
|
||||
},
|
||||
moveLeft() {
|
||||
let i = this.pagedata.fields.findIndex(v=>v.name===this.field.name)
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = i-1>=0? i - 1 : copy.length - 1
|
||||
copy = this.$arrayMove(copy, i, idx)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy}})
|
||||
this.$emit('changepos')
|
||||
},
|
||||
moveRight() {
|
||||
let i = this.pagedata.fields.findIndex(v=>v.name===this.field.name)
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.length-1>i? i + 1 : 0
|
||||
copy = this.$arrayMove(copy, i, idx)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy}})
|
||||
this.$emit('changepos')
|
||||
},
|
||||
doRemove() {
|
||||
let field = this.getfield()
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
this.$delete(copy, idx)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy}})
|
||||
this.$emit('close')
|
||||
},
|
||||
startSearch(event) {
|
||||
if (this.timer) clearTimeout(this.timer)
|
||||
let self = this
|
||||
this.timer = setTimeout(() => { self.$emit('modalevent', {name: 'dosearch', data: event.srcElement.value})}, 100)
|
||||
},
|
||||
pressEnter(event) {
|
||||
if(!this.$empty(event.srcElement.value) && this.filterData.length>0) this.$emit('modalevent', {name: 'doselect', data: this.filterData[0][this.field.name]})
|
||||
},
|
||||
getfield() {
|
||||
return this.currentField
|
||||
},
|
||||
changeColor() {
|
||||
let copy = this.getfield()
|
||||
copy.color = this.radioColor.code==='none'? undefined : this.color
|
||||
this.updateFields(copy)
|
||||
},
|
||||
changeBGColor() {
|
||||
let copy = this.getfield()
|
||||
copy.bgcolor = this.radioBGcolor.code==='none'? undefined : this.bgcolor
|
||||
this.updateFields(copy)
|
||||
},
|
||||
checkSelected() {
|
||||
let found = this.filters.find(v=>v.name===this.field.name)
|
||||
return found? found.select : undefined
|
||||
},
|
||||
doAdvance(name) {
|
||||
let field = this.getfield()
|
||||
let script = (field[name]? Array.isArray(field[name]) : false)? JSON.stringify(field[name]) : undefined
|
||||
this.$emit('modalevent', {name: 'showsidebar', data: {field: field, name: name, script: script,
|
||||
radio: name==='bgcolor'? this.radioBGcolor : (name==='color'? this.radioColor : this.radioSize) }})
|
||||
},
|
||||
changeSize() {
|
||||
let copy = this.getfield()
|
||||
if(this.radioSize.code==='option' && !this.$isNumber(this.textsize)) return
|
||||
copy.textsize = this.radioSize.code==='none'? undefined : this.textsize
|
||||
this.updateFields(copy)
|
||||
},
|
||||
changeAlign() {
|
||||
let copy = this.getfield()
|
||||
copy.textalign = this.radioAlign.code==='none'? undefined : (this.selectAlign? this.selectAlign.code : undefined)
|
||||
this.updateFields(copy)
|
||||
},
|
||||
changeWidth() {
|
||||
let copy = this.getfield()
|
||||
if(!this.$isNumber(this.minwidth)) return
|
||||
copy.minwidth = this.radioWidth.code==='none'? undefined : this.minwidth
|
||||
this.updateFields(copy)
|
||||
},
|
||||
changeMaxWidth() {
|
||||
let copy = this.getfield()
|
||||
if(!this.$isNumber(this.maxwidth)) return
|
||||
copy.maxwidth = this.radioMaxWidth.code==='none'? undefined : this.maxwidth
|
||||
this.updateFields(copy)
|
||||
},
|
||||
setFilter(field) {
|
||||
let arr = this.arr.map(v=>{return {
|
||||
condition: v.condition, value: v.value, operator: v.operator
|
||||
}})
|
||||
let text = ''
|
||||
arr.map((y,k)=>{
|
||||
text += `${k>0? (arr[k-1].operator==='and'? ' &' : ' ||') : ''} ${y.condition} ${this.$numtoString(y.value)}`
|
||||
})
|
||||
let filter = {name: field.name, label: field.label, filter: arr, condition: text}
|
||||
this.$emit('modalevent', {name: 'setfilter', data: filter})
|
||||
},
|
||||
copyContent(value) {
|
||||
this.$copyToClipboard(value)
|
||||
},
|
||||
changeLabel(value) {
|
||||
if(this.$empty(value)) return
|
||||
if(this.label!==value) this.label = value
|
||||
let copy = this.getfield()
|
||||
copy.label = value
|
||||
this.updateFields(copy)
|
||||
},
|
||||
changeUnit() {
|
||||
if(this.$empty(this.selectUnit)) return
|
||||
let copy = this.getfield()
|
||||
copy.unit = this.selectUnit.detail
|
||||
this.updateFields(copy)
|
||||
setTimeout(()=> this.menuaction = {pagename: this.pagename, name: 'reload-data', time: new Date()}, 1000)
|
||||
},
|
||||
changeTemplate(value) {
|
||||
if(this.radioTemplate.code==='none') value = undefined
|
||||
else if(this.$empty(value)) return
|
||||
let copy = this.getfield()
|
||||
copy.template = value
|
||||
this.updateFields(copy)
|
||||
},
|
||||
changeDecimal(evt) {
|
||||
if(!this.$isNumber(evt)) return
|
||||
let copy = this.getfield()
|
||||
copy.decimal = evt
|
||||
this.updateFields(copy)
|
||||
setTimeout(()=>this.menuaction = {pagename: this.pagename, name: 'reload-data', time: new Date()}, 1000)
|
||||
},
|
||||
checkFilter() {
|
||||
if(!this.pagedata) return
|
||||
let field = this.getfield()
|
||||
let found = this.pagedata.filters? this.pagedata.filters.find(v=>v.name===field.name) : undefined
|
||||
return found? (found.select || found.filter) : false
|
||||
},
|
||||
changeTooltip() {
|
||||
let copy = this.getfield()
|
||||
if(this.radioTooltip? this.radioTooltip.code==='none' : false) copy.tooltip = undefined
|
||||
else if(!(this.selectField && this.selectPlacement && this.selectScheme)) return
|
||||
else {copy.tooltip = {field: this.selectField.name, placement: this.selectPlacement.code, type: this.selectScheme.code}}
|
||||
this.updateFields(copy)
|
||||
},
|
||||
changeFormula() {
|
||||
let field = this.getfield()
|
||||
//check formula
|
||||
this.errors = []
|
||||
let val = this.$copy(this.formula)
|
||||
this.tags.forEach(v => {
|
||||
let myRegExp = new RegExp(v.name, 'g')
|
||||
val = val.replace(myRegExp, Math.random())
|
||||
})
|
||||
try {
|
||||
let value = this.$calc(val)
|
||||
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
|
||||
this.errors.push({name: 'formula', message: 'Công thức không hợp lệ'})
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
this.errors.push({name: 'formula', message: 'Công thức không hợp lệ'})
|
||||
}
|
||||
if(this.errors.length>0) return
|
||||
let copyField = this.$copy(field)
|
||||
copyField.formula = this.formula.trim()
|
||||
copyField.tags = this.tags.map(v=>v.name)
|
||||
copyField.level = Math.max(...this.tags.map(v=>v.level? v.level : 0)) + 1
|
||||
this.updateFields(copyField, 'fields')
|
||||
this.$emit('close')
|
||||
},
|
||||
updateFields(field, type) {
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
copy[idx] = this.$copy(field)
|
||||
if(type==='fields') this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy, data: this.pagedata.data}})
|
||||
else {
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'fields', data: copy})
|
||||
if(this.field.inputitem) this.$updateinput(this.pagename)
|
||||
setTimeout(()=>this.$store.commit("updateState", {name: this.pagename, key: "update", data: {columns: copy}}), 100)
|
||||
}
|
||||
},
|
||||
doFocus() {
|
||||
if(this.selectTab.code!=='value') this.selectTab = this.menuchoice.find(v=>v.code==='value')
|
||||
},
|
||||
hideField() {
|
||||
let field = this.getfield()
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let found = copy.find(v=>v.name===field.name)
|
||||
found.show = false
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'update', data: {fields: copy}})
|
||||
this.$emit('close')
|
||||
},
|
||||
checkValid() {
|
||||
let error
|
||||
this.arr.map((v,i) => {
|
||||
if(this.$empty(v.condition)) error = 'Chưa chọn điều kiện'
|
||||
if(this.arr.length>=2 && i===0 && this.$empty(v.operator)) error = 'Chưa chọn kết hợp'
|
||||
if(!this.$isNumber(v.value)) error = 'Giá trị phải là số'
|
||||
if(error) v.error = error
|
||||
else {
|
||||
v.error = undefined
|
||||
v.value = this.$numtoString(v.value)
|
||||
}
|
||||
})
|
||||
this.arr = this.$copy(this.arr)
|
||||
if(!error) this.setFilter(this.field)
|
||||
},
|
||||
doOption() {
|
||||
setTimeout(()=> this.checkValid(), 100)
|
||||
},
|
||||
resizeWidth(minus) {
|
||||
let val = this.maxwidth || this.minwidth || this.width
|
||||
val = minus? parseInt(val - 0.1* val) : parseInt(val + 0.1* val)
|
||||
if(val>1000) return this.$buefy.toast.open('Độ rộng cột lớn hơn giới hạn cho phép')
|
||||
else if(val<20) return this.$buefy.toast.open('Độ rộng cột nhỏ hơn giới hạn cho phép')
|
||||
this.radioMaxWidth = this.colorchoice.find(v=>v.code==='option')
|
||||
this.radioWidth = this.colorchoice.find(v=>v.code==='option')
|
||||
this.maxwidth = val
|
||||
this.currentField.maxwidth = val
|
||||
this.minwidth = val
|
||||
this.currentField.minwidth = val
|
||||
this.updateFields(this.currentField)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
240
components/datatable/CreateField.vue
Normal file
240
components/datatable/CreateField.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="panel-tabs">
|
||||
<a v-for="(v,i) in fieldType" :key="i"
|
||||
:class="selectType.code===v.code? 'is-active' : ''"
|
||||
@click="selectType = v"
|
||||
>
|
||||
{{v.name}}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<template v-if="selectType.code==='formula'">
|
||||
<div class="field mt-1 px-0 mx-0">
|
||||
<label class="label fs-14"> Chọn trường để tạo công thức <span class="has-text-danger"> * </span> </label>
|
||||
<div class="control">
|
||||
<b-taginput
|
||||
size="is-small"
|
||||
v-model="tags"
|
||||
:data="pageData? pageData.fields.filter(v=>v.format==='number') : []"
|
||||
type="is-primary is-light"
|
||||
autocomplete
|
||||
:open-on-focus="true"
|
||||
field="label"
|
||||
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="tags.find(v=>v.id===props.option.id)? 'has-text-primary' : ''"> {{props.option.label}} </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"> Click đúp vào để thêm vào công thức tính.</p>
|
||||
<div class="tags">
|
||||
<a @dblclick="formula = formula? (formula + ' ' + v.name) : v.name" class="tag is-rounded" v-for="(v,i) in tags" :key="i">{{v.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<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 is-small" rows="3" type="text" 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 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 mt-3 px-0 mx-0">
|
||||
<label class="label fs-14">Tên trường <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control">
|
||||
<input class="input is-small" 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="field mt-3">
|
||||
<label class="label fs-14"> Mô tả <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control is-expanded">
|
||||
<input class="input is-small" type="text" placeholder="" v-model="label">
|
||||
</p>
|
||||
<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-3" v-if="selectType.code==='empty'">
|
||||
<label class="label fs-14"
|
||||
>Kiểu dữ liệu
|
||||
<span class="has-text-danger"> * </span>
|
||||
</label>
|
||||
<div class="control fs-14">
|
||||
<b-radio v-for="(v,i) in datatype.filter(x=>x.code!=='date')" :key="i" v-model="radioType" :native-value="v">
|
||||
{{v.name}}
|
||||
</b-radio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field mt-4">
|
||||
<p class="control">
|
||||
<a class="button is-primary is-small is-outlined is-rounded"
|
||||
@click="selectType.code==='formula'? createField() : createEmptyField()"> Tạo trường</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename'],
|
||||
data() {
|
||||
return {
|
||||
selectUnit: undefined,
|
||||
data: [],
|
||||
current: 1,
|
||||
filterData: [],
|
||||
loading: false,
|
||||
fieldType: [{code: 'formula', name: 'Công thức'}, {code: 'empty', name: 'Trường rỗng'}],
|
||||
tags: [],
|
||||
formula: undefined,
|
||||
name: 'f' + this.$dayjs(new Date()).format("hhmmss"),
|
||||
label: undefined,
|
||||
errors: [],
|
||||
selectType: undefined,
|
||||
radioType: undefined,
|
||||
decimal: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.radioType = this.datatype.find(v=>v.code==='string')
|
||||
this.selectType = this.fieldType.find(v=>v.code==='formula')
|
||||
this.selectUnit = this.moneyunit.find(v=>v.code==='one')
|
||||
},
|
||||
computed: {
|
||||
moneyunit: {
|
||||
get: function() {return this.$store.state.moneyunit},
|
||||
set: function(val) {this.$store.commit("updateMoneyUnit", {moneyunit: val})}
|
||||
},
|
||||
pageData: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
tablesetting: {
|
||||
get: function() {return this.$store.state.tablesetting},
|
||||
set: function(val) {this.$store.commit("updateTableSetting", {tablesetting: val})}
|
||||
},
|
||||
datatype: {
|
||||
get: function() {return this.$store.state.datatype},
|
||||
set: function(val) {this.$store.commit("updateDataType", {datatype: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkValid() {
|
||||
this.errors = []
|
||||
if(this.tags.length===0) {
|
||||
this.errors.push({name: 'tags', message: 'Chưa chọn trường xây dựng công thức.'})
|
||||
}
|
||||
|
||||
if(!this.$empty(this.formula)? this.$empty(this.formula.trim()) : true) {
|
||||
this.errors.push({name: 'formula', message: 'Công thức không được bỏ trống.'})
|
||||
}
|
||||
|
||||
if(!this.$empty(this.label)? this.$empty(this.label.trim()) : true )
|
||||
this.errors.push({name: 'label', message: 'Mô tả không được bỏ trống.'})
|
||||
else if(this.pageData.fields.find(v=>v.label.toLowerCase()===this.label.toLowerCase())) {
|
||||
this.errors.push({name: 'label', message: 'Mô tả bị trùng. Hãy đặt mô tả khác.'})
|
||||
}
|
||||
|
||||
if(this.errors.length>0) return false
|
||||
//check formula
|
||||
let val = this.$copy(this.formula)
|
||||
this.tags.forEach(v => {
|
||||
let myRegExp = new RegExp(v.name, 'g')
|
||||
val = val.replace(myRegExp, Math.random())
|
||||
})
|
||||
|
||||
try {
|
||||
let value = this.$calc(val)
|
||||
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
|
||||
this.errors.push({name: 'formula', message: 'Công thức không hợp lệ'})
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
this.errors.push({name: 'formula', message: 'Công thức không hợp lệ'})
|
||||
}
|
||||
return this.errors.length>0? false : true
|
||||
},
|
||||
createField() {
|
||||
if(!this.checkValid()) return
|
||||
let field = this.$createField(this.name.trim(), this.label.trim(), 'number', true)
|
||||
field.formula = this.formula.trim()
|
||||
field.tags = this.tags.map(v=>v.name)
|
||||
field.level = Math.max(...this.tags.map(v=>v.level? v.level : 0)) + 1
|
||||
field.unit = this.selectUnit.detail
|
||||
field.decimal = this.decimal
|
||||
field.disable = 'search,value'
|
||||
let copy = this.$copy(this.pageData.fields)
|
||||
copy.push(field)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy}})
|
||||
this.$emit('newfield', field)
|
||||
this.tags = []
|
||||
this.formula = undefined
|
||||
this.label = undefined
|
||||
this.name = 'f' + this.$dayjs(new Date()).format("hhmmss")
|
||||
},
|
||||
createEmptyField() {
|
||||
this.errors = []
|
||||
if(!this.$empty(this.name)? this.$empty(this.name.trim()) : true )
|
||||
this.errors.push({name: 'name', message: 'Tên không được bỏ trống.'})
|
||||
else if(this.pageData.fields.find(v=>v.name.toLowerCase()===this.name.toLowerCase())) {
|
||||
this.errors.push({name: 'name', message: 'Tên trường bị trùng. Hãy đặt tên khác.'})
|
||||
}
|
||||
if(!this.$empty(this.label)? this.$empty(this.label.trim()) : true )
|
||||
this.errors.push({name: 'label', message: 'Mô tả không được bỏ trống.'})
|
||||
else if(this.pageData.fields.find(v=>v.label.toLowerCase()===this.label.toLowerCase())) {
|
||||
this.errors.push({name: 'label', message: 'Mô tả bị trùng. Hãy đặt mô tả khác.'})
|
||||
}
|
||||
if(this.errors.length>0) return
|
||||
let field = this.$createField(this.name.trim(), this.label.trim(), this.radioType.code, true)
|
||||
let copy = this.$copy(this.pageData.fields)
|
||||
copy.push(field)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "fields", data: copy})
|
||||
this.$emit('newfield', field)
|
||||
this.label = undefined
|
||||
this.name = 'f' + this.$dayjs(new Date()).format("hhmmss")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
444
components/datatable/CreateTemplate.vue
Normal file
444
components/datatable/CreateTemplate.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<div class="px-3">
|
||||
<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="tab=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)">
|
||||
<span class="icon has-text-danger fs-26">
|
||||
<i class="mdi mdi-close"/>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<p class="control has-text-right ml-6" v-if="selected? selected.id===v.id : false">
|
||||
<span class="icon fs-26">
|
||||
<i class="mdi mdi-check"/>
|
||||
</span>
|
||||
</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">
|
||||
<b-tooltip :label="$stripHtml(v.label)" type="is-dark">{{v.name}}</b-tooltip></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'">
|
||||
<p class="mb-3">
|
||||
<a @click="copyContent()" class="mr-6">
|
||||
<span class="icon fs-20">
|
||||
<i class="mdi mdi-content-copy"/>
|
||||
</span>
|
||||
Copy
|
||||
</a>
|
||||
<a @click="paste()" class="mr-6">
|
||||
<span class="icon fs-20">
|
||||
<i class="mdi mdi-content-copy"/>
|
||||
</span>
|
||||
Paste
|
||||
</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
|
||||
<span class="material-symbols-outlined">
|
||||
chevron_right
|
||||
</span>
|
||||
</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-rounded is-outlined mt-5" @click="replace()">Replace</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-6 has-text-centered">
|
||||
<button class="button is-primary is-medium is-rounded" @click="changeTemplate()">Áp dụng</button>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename', 'field'],
|
||||
data() {
|
||||
return {
|
||||
type: undefined,
|
||||
size: undefined,
|
||||
types: [{code: 'span', name: 'span'}, {code: 'tag', name: 'tag'}],
|
||||
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'}],
|
||||
shapes: [{code: 'default', name: 'Mặc định'}, {code: 'is-rounded', name: 'Tròn góc'}],
|
||||
shape: undefined,
|
||||
outlines: [{code: 'default', name: 'Mặc định'}, {code: 'is-outlined', name: 'Outline'}],
|
||||
outline: undefined,
|
||||
conditions: [{code: 'no', name: 'Không áp dụng'}, {code: 'yes', name: 'Có áp dụng'}],
|
||||
condition: undefined,
|
||||
tags: [],
|
||||
selected: undefined,
|
||||
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'}],
|
||||
tab: undefined,
|
||||
tagsField: [],
|
||||
errors: [],
|
||||
expression: '',
|
||||
text: undefined,
|
||||
radioBGcolor: undefined,
|
||||
radioColor: undefined,
|
||||
radioSize: undefined,
|
||||
bgcolor: undefined,
|
||||
color: undefined,
|
||||
textsize: undefined,
|
||||
source: undefined,
|
||||
target: this.$copy(this.field.name)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.type = this.types.find(v=>v.code==='tag')
|
||||
this.size = this.sizes.find(v=>v.code==='is-normal')
|
||||
this.shape = this.shapes.find(v=>v.code==='is-rounded')
|
||||
this.outline = this.shapes.find(v=>v.code==='default')
|
||||
if(this.$empty(this.field.template)) this.tab = this.tabs.find(v=>v.code==='selected')
|
||||
else {
|
||||
this.text = this.$copy(this.field.template)
|
||||
this.tab = this.tabs.find(v=>v.code==='template')
|
||||
}
|
||||
this.condition = this.conditions.find(v=>v.code==='no')
|
||||
},
|
||||
watch: {
|
||||
expression: function(newVal) {
|
||||
if(this.$empty(newVal)) return
|
||||
else this.checkExpression()
|
||||
},
|
||||
tab: function(newVal, oldVal) {
|
||||
if(oldVal===undefined) return
|
||||
if(newVal.code==='template') {
|
||||
let value = '<div>'
|
||||
this.tags.map((v,i)=>{
|
||||
value += '<span class="' + v.class + (this.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>'
|
||||
this.text = value
|
||||
} else if(newVal.code==='option') {
|
||||
if(!this.selected) return
|
||||
this.radioBGcolor = this.selected.bgcolor? this.colorchoice.find(v=>v.code==='option') : this.colorchoice.find(v=>v.code==='none')
|
||||
this.radioColor = this.selected.color? this.colorchoice.find(v=>v.code==='option') : this.colorchoice.find(v=>v.code==='none')
|
||||
this.radioSize = this.selected.textsize? this.colorchoice.find(v=>v.code==='option') : this.colorchoice.find(v=>v.code==='none')
|
||||
this.bgcolor = this.selected.bgcolor? this.selected.bgcolor : undefined
|
||||
this.color = this.selected.color? this.selected.color : undefined
|
||||
this.textsize = this.selected.textsize? this.selected.textsize : undefined
|
||||
} else if(newVal.code==='condition') {
|
||||
this.condition = this.conditions.find(v=>v.code==='no')
|
||||
this.tagsField = []
|
||||
this.expression = ''
|
||||
if(this.selected? this.selected.expression : false) {
|
||||
this.condition = this.conditions.find(v=>v.code==='yes')
|
||||
this.tagsField = this.$copy(this.selected.tags)
|
||||
this.expression = this.$copy(this.selected.formula)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colorscheme: {
|
||||
get: function() {return this.$store.state.colorscheme},
|
||||
set: function(val) {this.$store.commit("updateColorScheme", {colorscheme: val})}
|
||||
},
|
||||
pageData: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
colorchoice: {
|
||||
get: function() {return this.$store.state.colorchoice},
|
||||
set: function(val) {this.$store.commit("updateColorChoice", {colorchoice: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async paste() {
|
||||
this.text = await navigator.clipboard.readText()
|
||||
},
|
||||
replace() {
|
||||
if(this.$empty(this.text)) return
|
||||
this.text = this.text.replaceAll(this.source, this.target)
|
||||
},
|
||||
doCheck() {
|
||||
let text = window.getSelection().toString()
|
||||
if(this.$empty(text)) return
|
||||
this.source = text
|
||||
},
|
||||
changeStyle() {
|
||||
this.selected.bgcolor = this.selected.color = this.selected.textsize = this.selected.style = undefined
|
||||
let style = ''
|
||||
if(this.radioBGcolor.code==='option'? !this.$empty(this.bgcolor) : false) {
|
||||
this.selected.bgcolor = this.bgcolor
|
||||
style += 'background-color: ' + this.bgcolor + ' !important; '
|
||||
}
|
||||
if(this.radioColor.code==='option'? !this.$empty(this.color) : false) {
|
||||
this.selected.color = this.color
|
||||
style += 'color: ' + this.color + ' !important; '
|
||||
}
|
||||
if(this.radioSize.code==='option'? this.$isNumber(this.textsize) : false) {
|
||||
this.selected.textsize = this.textsize
|
||||
style += 'font-size: ' + this.textsize + 'px !important; '
|
||||
}
|
||||
this.$empty(style)? false : this.selected.style = style
|
||||
},
|
||||
changeCondition(v) {
|
||||
if(v.code==='no') this.selected.expression = undefined
|
||||
},
|
||||
copyContent() {
|
||||
this.$copyToClipboard(this.text)
|
||||
},
|
||||
changeTemplate() {
|
||||
let copy = this.$copy(this.pageData.fields)
|
||||
let found = copy.find(v=>v.name===this.field.name)
|
||||
found.template = this.text
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy}})
|
||||
},
|
||||
checkExpression() {
|
||||
this.errors = []
|
||||
let val = this.$copy(this.expression)
|
||||
let exp = this.$copy(this.expression)
|
||||
this.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 = this.$calc(val)
|
||||
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
|
||||
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
|
||||
} else if(!(eval(value)===true || eval(value)===false)) {
|
||||
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
|
||||
} else if(this.selected) {
|
||||
this.selected.expression = exp
|
||||
this.selected.formula = this.expression
|
||||
this.selected.tags = this.$copy(this.tagsField)
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
|
||||
}
|
||||
return this.errors.length>0? false : true
|
||||
},
|
||||
changeType(v) {
|
||||
},
|
||||
doSelect(v) {
|
||||
this.tags.push({id: this.$id(), name: v.name, class: this.getClass(v)})
|
||||
this.tab = this.tabs.find(v=>v.code==='selected')
|
||||
this.selected = this.tags[this.tags.length-1]
|
||||
},
|
||||
doSelectSpan(v) {
|
||||
this.tags.push({id: this.$id(), name: v.name, class: this.getSpanClass(v)})
|
||||
this.tab = this.tabs.find(v=>v.code==='selected')
|
||||
this.selected = this.tags[this.tags.length-1]
|
||||
},
|
||||
remove(i) {
|
||||
this.$delete(this.tags, i)
|
||||
},
|
||||
getClass(v) {
|
||||
let value = this.type.code + ' ' + v.code + ' ' + this.size.code + (this.shape.code==='default'? '' : ' ' + this.shape.code)
|
||||
value += (this.outline.code==='default'? '' : ' ' + this.outline.code)
|
||||
return value
|
||||
},
|
||||
getSpanClass(v) {
|
||||
let value = 'has-text-' + v.name.toLowerCase() + ' ' + this.size.value
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
612
components/datatable/DataTable.vue
Normal file
612
components/datatable/DataTable.vue
Normal file
@@ -0,0 +1,612 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="pb-1" v-if="pagedata.showFilter && filters.length>0">
|
||||
<div class="field is-grouped is-grouped-multiline pl-2" v-if="filters? filters.length>0 : false">
|
||||
<div class="control mr-5">
|
||||
<a class="button is-primary is-outlined is-rounded fs-10" @click="clearFilter()">
|
||||
<span class="fs-13">Xóa lọc</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="control pr-2 mr-5">
|
||||
<span class="icon has-text-primary fsb-18"> <i class="mdi mdi-sigma"> </i>
|
||||
{{totalRows}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="control" v-for="(v,i) in filters" :key="i">
|
||||
<div class="tags has-addons is-marginless">
|
||||
<a class="tag is-primary is-marginless" @click="showCondition(v)">{{v.label.indexOf('>')>=0? $stripHtml(v.label,30) : v.label}}</a>
|
||||
<a class="tag is-delete is-marginless has-text-black-bis" @click="removeFilter(i)"></a>
|
||||
</div>
|
||||
<span class="help has-text-black-bis">
|
||||
{{v.sort? v.sort : (v.select? ('[' + (v.select.length>0? $stripHtml(v.select[0],20) : '') + '...Σ' + v.select.length + ']') :
|
||||
(v.condition))}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container" ref="container">
|
||||
<table class="table is-bordered is-narrow is-hoverable" ref="table" :style="getSettingStyle('table')">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(field,i) in displayFields" :key="i" :ref="`th${field.name}`"
|
||||
:style="getSettingStyle('header', field)">
|
||||
<div class="hyperlink" @click="showField(field)" :style="getSettingStyle('dropdown', field)">
|
||||
<template v-if="field.label.indexOf('<')<0">{{field.label}}</template>
|
||||
<template v-else>
|
||||
<component v-bind="{row: field, tick: tickall}" :is="compiledComponent(field.label)" @clickevent="doAction($event, field)" />
|
||||
</template>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(v,i) in displayData" :key="i">
|
||||
<td v-for="(field, j) in fields.filter(v=>v.show)" :key="j" :ref="`${v.stock_code}${field.name}`" :id="`${v.stock_code}${field.name}`"
|
||||
:style="v[`${field.name}color`]" @dblclick="doubleClick(field, v)">
|
||||
<component v-bind="{row: v, tick: tick, pagename: pagename, field: field, highlight: highlight}" v-if="field.template"
|
||||
:is="compiledComponent(field.template)" @clickevent="doAction($event, v, field)"
|
||||
/>
|
||||
<template v-else-if="field.tooltip">
|
||||
<b-tooltip :label="v[field.tooltip.field]"
|
||||
:position="field.tooltip.placement"
|
||||
:type="field.tooltip.type">
|
||||
{{v[field.name]}}
|
||||
</b-tooltip>
|
||||
</template>
|
||||
<template v-else> {{v[field.name]}} </template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="mt-3 px-3" v-if="showPaging">
|
||||
<b-pagination
|
||||
:total="totalRows"
|
||||
:current.sync="currentPage"
|
||||
:order="'is-centered'"
|
||||
:rounded="true"
|
||||
:per-page="perPage"
|
||||
@change="changePage()"
|
||||
>
|
||||
</b-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"
|
||||
@dosearch="doSearch(currentField, $event)" @doselect="doSelect(currentField, $event)" @dosort="doSort(currentField, $event)"
|
||||
@setfilter="setFilter(currentField, $event)" @showsidebar="showSidebar($event)" @copyfield="copyField">
|
||||
</Modal>
|
||||
<Modal @close="showmodal1=undefined" v-bind="showmodal1" v-if="showmodal1" @updatefields="updateFields"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
export default {
|
||||
props: ['pagename'],
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
fields: [],
|
||||
search: '',
|
||||
filters: [],
|
||||
currentPage: 1,
|
||||
filterData: [],
|
||||
timer: undefined,
|
||||
perPage: 30,
|
||||
currentField: undefined,
|
||||
flagSearch: false,
|
||||
scrollbar: undefined,
|
||||
tablesetting: undefined,
|
||||
tick: {},
|
||||
tickall: false,
|
||||
displayData: [],
|
||||
displayFields: [],
|
||||
showmodal: undefined,
|
||||
showmodal1: undefined,
|
||||
totalRows: 0,
|
||||
showPaging: false,
|
||||
highlight: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(this.pagedata.data) this.data = this.$copy(this.pagedata.data)
|
||||
if(this.pagedata.fields) this.fields = this.$copy(this.pagedata.fields)
|
||||
if(this.pagedata.filters) this.filters = this.$copy(this.pagedata.filters)
|
||||
this.tablesetting = this.$copy(this.pagedata.tablesetting || this.gridsetting)
|
||||
this.perPage = this.pagedata.perPage? this.pagedata.perPage : this.$formatNumber(this.tablesetting.find(v=>v.code==='per-page').detail)
|
||||
if(this.data.length>0 && this.fields.length>0) this.updateShow()
|
||||
else this.showPagination()
|
||||
},
|
||||
watch: {
|
||||
'pagedata.update': function(newVal) {
|
||||
if(newVal) this.updateData(newVal)
|
||||
},
|
||||
menuaction: function(newVal) {
|
||||
if(this.$empty(newVal)) return
|
||||
if(newVal.name==='export-excel' && (newVal.pagename? newVal.pagename===this.pagename : true)) {
|
||||
this.$exportExcel(this.data, this.menuaction.file, this.fields.filter(v=>v.show))
|
||||
} else if(newVal.name==='opensidebar' && (newVal.pagename? newVal.pagename===this.pagename : true)) {
|
||||
this.showmodal = {component: 'datatable/TableOption', vbind: {pagename: this.pagename}, width: '850px', height: '600px', title: 'Danh sách cột'}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pagedata: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
gridsetting: {
|
||||
get: function() {return this.$store.state.tablesetting},
|
||||
set: function(val) {this.$store.commit("updateTableSetting", {tablesetting: val})}
|
||||
},
|
||||
menuaction: {
|
||||
get: function() {return this.$store.state.menuaction},
|
||||
set: function(val) {this.$store.commit("updateMenuAction", {menuaction: val})}
|
||||
},
|
||||
currentsetting: {
|
||||
get: function() {return this.$store.state.currentsetting},
|
||||
set: function(val) {this.$store.commit("updateCurrentSetting", {currentsetting: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showPagination() {
|
||||
this.showPaging = this.pagedata.pagination===false? false : true
|
||||
this.totalRows = this.data.length
|
||||
if(this.showPaging && this.pagedata.api) {
|
||||
if(this.pagedata.api.full_data===false) this.totalRows = this.pagedata.api.total_rows
|
||||
this.showPaging = this.totalRows > this.perPage
|
||||
}
|
||||
},
|
||||
clearFilter() {
|
||||
this.updateData({filters: []})
|
||||
},
|
||||
async updateData(newVal) {
|
||||
if(newVal.columns) { //change attribute
|
||||
this.fields = this.$copy(newVal.columns)
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'fields', data: this.fields})
|
||||
this.$emit('changefield', this.fields)
|
||||
let fields = this.fields.filter(v=>v.show)
|
||||
this.data.map(v=>{
|
||||
fields.map(x=>v[`${x.name}color`] = this.getStyle(x, v))
|
||||
})
|
||||
return this.updateShow()
|
||||
}
|
||||
if(newVal.tablesetting) {
|
||||
this.tablesetting = newVal.tablesetting
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'tablesetting', data: this.tablesetting})
|
||||
this.perPage = this.$formatNumber(this.tablesetting.find(v=>v.code=="per-page").detail)
|
||||
this.currentPage = 1
|
||||
}
|
||||
this.tablesetting = this.$copy(this.pagedata.tablesetting || this.gridsetting)
|
||||
if(this.tablesetting) {
|
||||
this.perPage = this.pagedata.perPage? this.pagedata.perPage : Number(this.tablesetting.find(v=>v.code==='per-page').detail)
|
||||
}
|
||||
if(newVal.fields) {
|
||||
this.fields = this.$copy(newVal.fields)
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'fields', data: this.$copy(this.fields)})
|
||||
this.$emit('changefield', this.fields)
|
||||
} else this.fields = this.$copy(this.pagedata.fields)
|
||||
if(newVal.data || newVal.fields) {
|
||||
let copy = this.$copy(newVal.data || this.data)
|
||||
this.data = this.$calculateData(copy, this.fields)
|
||||
let fields = this.fields.filter(v=>v.show)
|
||||
this.data.map(v=>{
|
||||
fields.map(x=>v[`${x.name}color`] = this.getStyle(x, v))
|
||||
})
|
||||
if(newVal.data) {
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'data', data: this.$copy(this.data)})
|
||||
this.$emit('changedata', this.data)
|
||||
}
|
||||
}
|
||||
if(newVal.filters) this.filters = this.$copy(newVal.filters)
|
||||
else if(this.pagedata.filters) this.filters = this.$copy(this.pagedata.filters)
|
||||
if(newVal.data || newVal.fields || newVal.filters) {
|
||||
let copy = this.$copy(this.filters)
|
||||
this.filters.map((v,i)=>{
|
||||
let idx = this.$findIndex(this.fields, {name: v.name})
|
||||
let index = this.$findIndex(copy, {name: v.name})
|
||||
if(idx<0 && index>=0) this.$delete(copy, index)
|
||||
else if(idx>=0 && index>=0) copy[index].label = this.fields[idx].label
|
||||
})
|
||||
this.filters = copy
|
||||
this.doFilter(this.filters)
|
||||
}
|
||||
if(newVal.data || newVal.fields || newVal.filters || newVal.tablesetting) this.updateShow()
|
||||
if(newVal.data || newVal.fields) setTimeout(()=> this.scrollbarVisible(), 100)
|
||||
if(newVal.highlight) {
|
||||
this.highlight = this.$copy(newVal.highlight)
|
||||
setTimeout(()=>this.highlight = undefined, 500)
|
||||
}
|
||||
},
|
||||
updateShow(full_data) {
|
||||
this.showPagination()
|
||||
this.displayFields = this.fields.filter(v=>v.show)
|
||||
if(full_data===false) this.displayData = this.data
|
||||
else this.displayData = this.data.filter((ele,index) => (index>=(this.currentPage-1)*this.perPage && index<this.currentPage*this.perPage))
|
||||
},
|
||||
async changePage() {
|
||||
if(this.pagedata.api? this.pagedata.api.full_data===false : false) await this.backendFilter(this.filters)
|
||||
else this.updateShow()
|
||||
this.$emit('changepage', this.currentPage)
|
||||
},
|
||||
doubleClick(field, v) {
|
||||
this.doSelect(field, v[field.name])
|
||||
},
|
||||
showField(field) {
|
||||
if(this.pagedata.contextMenu===false || field.menu==='no') return
|
||||
this.showContextMenu(field)
|
||||
let doc = this.$refs[`th${field.name}`]
|
||||
let width = (doc? doc.length>0 : false)? doc[0].getBoundingClientRect().width : 100
|
||||
if(this.pagedata.setting) this.currentsetting = this.$copy(this.pagedata.setting)
|
||||
this.showmodal = {vbind: {pagename: this.pagename, field: this.currentField, filters: this.filters, filterData: this.filterData, width: width},
|
||||
component: 'datatable/ContextMenu', title: this.$stripHtml(field.label), width: '600px', height: '500px'}
|
||||
},
|
||||
showCondition(v) {
|
||||
this.$emit('contextmenu', 'open')
|
||||
this.currentField = this.$find(this.pagedata.fields, {'name': v.name})
|
||||
this.showField(this.currentField)
|
||||
},
|
||||
compiledComponent(value) {
|
||||
return {
|
||||
template: `${value}`,
|
||||
props: ['row', 'tick', 'pagename', 'field', 'highlight'],
|
||||
methods: {
|
||||
formatNumber(val) {
|
||||
return this.$formatNumber(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
showContextMenu(field) {
|
||||
this.currentField = field
|
||||
this.filterData = this.$unique(this.data, [field.name])
|
||||
this.menuaction = {name: 'display', key: this.$id(), field: field}
|
||||
this.$emit('contextmenu', 'open')
|
||||
},
|
||||
showSidebar(event) {
|
||||
let title = 'Danh sách cột'
|
||||
if(event.name==="bgcolor") title = `Đổi màu nền: ${event.field.name} / ${this.$stripHtml(event.field.label, 30)}`
|
||||
else if(event.name==="color") title = `Đổi màu chữ: ${event.field.name} / ${this.$stripHtml(event.field.label, 30)}`
|
||||
else if(event.name==='template') title = `Định dạng nâng cao: ${this.$stripHtml(event.field.label, 30)}`
|
||||
this.showmodal1 = {component: 'datatable/FormatOption',
|
||||
vbind: {event: event, currentField: this.currentField, pagename: this.pagename}, width: '850px', height: '700px', title: title}
|
||||
},
|
||||
getStyle(field, record) {
|
||||
var stop = false
|
||||
let val = this.tablesetting.find(v=>v.code==='td-border')? this.tablesetting.find(v=>v.code==='td-border').detail : 'border: solid 1px rgb(44, 44, 44); '
|
||||
val = val.indexOf(';')>=0? val : val + ';'
|
||||
if(field.bgcolor? !Array.isArray(field.bgcolor) : false) {
|
||||
val += ` background-color:${field.bgcolor}; `
|
||||
} else if(field.bgcolor? Array.isArray(field.bgcolor) : false) {
|
||||
field.bgcolor.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` background-color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
} else {
|
||||
let res = this.$calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` background-color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
stop = false
|
||||
if(field.color? !Array.isArray(field.color) : false) {
|
||||
val += ` color:${field.color}; `
|
||||
} else if(field.color? Array.isArray(field.color) : false) {
|
||||
field.color.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
} else {
|
||||
let res = this.$calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
stop = false
|
||||
if(field.textsize? !Array.isArray(field.textsize) : false) {
|
||||
val += ` font-size:${field.textsize}px; `
|
||||
} else if(field.textsize? Array.isArray(field.textsize) : false) {
|
||||
field.textsize.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` font-size:${v.size}px; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
let res = this.$calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` font-size:${v.size}px; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
} else val += ` font-size:${this.tablesetting.find(v=>v.code==='table-font-size').detail}px;`
|
||||
if(field.textalign) val += ` text-align:${field.textalign}; `
|
||||
if(field.minwidth) val += ` min-width:${field.minwidth}px; `
|
||||
if(field.maxwidth) val += ` max-width:${field.maxwidth}px; `
|
||||
return val
|
||||
},
|
||||
getSettingStyle(name, field) {
|
||||
let value = ''
|
||||
if(name==='container') {
|
||||
value = 'min-height:' + this.tablesetting.find(v=>v.code==='container-height').detail + 'rem; '
|
||||
} else if(name==='table') {
|
||||
value += 'background-color:' + this.tablesetting.find(v=>v.code==='table-background').detail + '; '
|
||||
value += 'font-size:' + this.tablesetting.find(v=>v.code==='table-font-size').detail + 'px;'
|
||||
value += 'color:' + this.tablesetting.find(v=>v.code==='table-font-color').detail + '; '
|
||||
} else if(name==='header') {
|
||||
value += 'background-color:' + this.tablesetting.find(v=>v.code==='header-background').detail + '; '
|
||||
if(field.minwidth) value += ' min-width: ' + field.minwidth + 'px; '
|
||||
if(field.maxwidth) value += ' max-width: ' + field.maxwidth + 'px; '
|
||||
} else if(name==='menu') {
|
||||
let arg = this.tablesetting.find(v=>v.code==='menu-width').detail
|
||||
arg = field? (field.menuwidth? field.menuwidth : arg) : arg
|
||||
value += 'width:' + arg + 'rem; '
|
||||
value += 'min-height:' + this.tablesetting.find(v=>v.code==='menu-min-height').detail + 'rem; '
|
||||
value += 'max-height:' + this.tablesetting.find(v=>v.code==='menu-max-height').detail + 'rem; '
|
||||
value += "overflow:auto; "
|
||||
} else if(name==='dropdown') {
|
||||
value += 'font-size:' + this.tablesetting.find(v=>v.code==='header-font-size').detail + 'px; '
|
||||
let found = this.filters.find(v=>v.name===field.name)
|
||||
found? value += 'color:' + this.tablesetting.find(v=>v.code==='header-filter-color').detail + '; '
|
||||
:value += 'color:' + this.tablesetting.find(v=>v.code==='header-font-color').detail + '; '
|
||||
}
|
||||
return value
|
||||
},
|
||||
removeFilter(i) {
|
||||
Vue.delete(this.filters, i)
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
updateFields(field) {
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
copy[idx] = field
|
||||
this.updateData({columns: copy})
|
||||
this.currentField = this.$copy(field)
|
||||
if(this.showmodal) {
|
||||
this.showmodal.vbind.field = this.$copy(field)
|
||||
this.showmodal = this.$copy(this.showmodal)
|
||||
}
|
||||
},
|
||||
doAction(event, row, field) {
|
||||
let name = typeof event === "string"? event : event.name
|
||||
let data = typeof event === "string"? event : event.data
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'action', data: {event: name, row: row, field: field, data: data, time: new Date()}})
|
||||
if(name==='remove') this.$deleterow(this.pagedata.api.name, row.id, this.pagename, true)
|
||||
if(name==='batchdelete') this.batchDelete()
|
||||
this.$emit(name, row, field, data)
|
||||
if(name==='tickall') {
|
||||
this.tickall = data
|
||||
if(data===false) this.tick = {}
|
||||
else {
|
||||
this.data.map(v=>this.tick[v.id] = true)
|
||||
this.tick = this.$copy(this.tick)
|
||||
}
|
||||
}
|
||||
},
|
||||
batchDelete() {
|
||||
let arr = []
|
||||
Object.entries(this.tick).forEach(([key, value]) => {
|
||||
if(value) arr.push({id: Number(key)})
|
||||
})
|
||||
if(arr.length===0) this.$buefy.toast.open({message: 'Bạn chưa chọn bản ghi để xoá', type: 'is-warning'})
|
||||
else this.$deleterow(this.pagedata.api.name, arr, this.pagename, true)
|
||||
},
|
||||
doSort(field, type) {
|
||||
let filter = {name: field.name, label: field.label, sort: type, format: field.format}
|
||||
let idx = this.filters.findIndex(v=>v.name===field.name)
|
||||
if(idx>=0) Vue.set(this.filters, idx, filter)
|
||||
else this.filters.push(filter)
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
doSearch(field, search) {
|
||||
let copy = this.$copy(this.filters)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
if(idx>=0) Vue.delete(copy, idx)
|
||||
if(this.pagedata.origin_api.full_data) {
|
||||
let data = this.frontendFilter(copy)
|
||||
let rows = this.$empty(search)? data
|
||||
: data.filter(v=>v[field.name]? v[field.name].toString().toLowerCase().indexOf(search.toLowerCase())>=0 : false)
|
||||
this.filterData = this.$unique(rows, [field.name])
|
||||
if(this.showmodal) this.showmodal.vbind.filterData = this.filterData
|
||||
} else {
|
||||
copy.push({name: field.name, label: field.label, search: search.toLowerCase(), format: field.format})
|
||||
this.flagSearch = true
|
||||
this.backendFilter(copy)
|
||||
}
|
||||
},
|
||||
doSelect(field, value) {
|
||||
let found = this.filters.find(v=>v.name===field.name)
|
||||
if(found) {
|
||||
!found.select? found.select = [] : false
|
||||
let idx = found.select.findIndex(x=>x===value)
|
||||
idx>=0? Vue.delete(found.select, idx) : found.select.push(value)
|
||||
if(found.select.length===0) {
|
||||
idx = this.filters.findIndex(v=>v.name===field.name)
|
||||
if(idx>=0) Vue.delete(this.filters, idx)
|
||||
}
|
||||
} else {
|
||||
this.filters.push({name: field.name, label: field.label, select: [value], format: field.format})
|
||||
}
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
setFilter(field, filter) {
|
||||
let idx = this.filters.findIndex(v=>v.name===field.name)
|
||||
if(idx<0) this.filters.push(filter)
|
||||
else Vue.set(this.filters, idx, filter)
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
doFilter(newVal, nonset) {
|
||||
if(this.currentPage>1 && nonset!==true) this.currentPage = 1
|
||||
if(this.pagedata.origin_api.full_data) {
|
||||
this.data = this.frontendFilter(newVal)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "dataFilter", data: this.$copy(this.data)})
|
||||
this.$emit('changedata', newVal)
|
||||
}
|
||||
else {
|
||||
if(this.timer) clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => this.backendFilter(newVal), 200)
|
||||
}
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "filters", data: this.$copy(newVal)})
|
||||
this.$emit('changefilter', newVal? newVal.length>0 : false)
|
||||
},
|
||||
frontendFilter(newVal) {
|
||||
let self = this
|
||||
let checkValid = function(name, x, filter) {
|
||||
if(self.$empty(x[name])) return false
|
||||
else {
|
||||
let text = ''
|
||||
filter.map((y,k)=>{
|
||||
text += `${k>0? (filter[k-1].operator==='and'? ' &&' : ' ||') : ''} ${self.$formatNumber(x[name])}
|
||||
${y.condition==='='? '==' : (y.condition==='<>'? '!==' : y.condition)} ${self.$formatNumber(y.value)}`
|
||||
})
|
||||
return self.$calc(text)
|
||||
}
|
||||
}
|
||||
newVal = this.$copy(newVal)
|
||||
var data = this.$copy(this.pagedata.data)
|
||||
newVal.filter(m=>m.select || m.filter).map(v => {
|
||||
if(v.select) {
|
||||
data = data.filter(x => v.select.findIndex(y => this.$empty(y)? this.$empty(x[v.name]) : (y===x[v.name])) >-1)
|
||||
} else if(v.filter) {
|
||||
data = data.filter(x => checkValid(v.name, x, v.filter))
|
||||
}
|
||||
})
|
||||
let sort = {}
|
||||
let format = {}
|
||||
let list = this.filters.filter(x=>x.sort)
|
||||
list.map(v=>{
|
||||
sort[v.name] = v.sort === "az" ? "asc" : "desc"
|
||||
format[v.name] = v.format;
|
||||
})
|
||||
return list.length>0? this.$multiSort(data, sort, format) : data
|
||||
},
|
||||
// Sử dụng backend filter
|
||||
async backendFilter(newVal) {
|
||||
let arr = [{code: '>', name: 'gt'}, {code: '>=', name: 'gte'}, {code: '<', name: 'lt'}, {code: '<=', name: 'lte'}, {code: '=', name: 'e'},
|
||||
{code: '<>', name: 'e'}]
|
||||
let params = this.pagedata.origin_api.params? this.$copy(this.pagedata.origin_api.params) : {}
|
||||
params.page = this.currentPage
|
||||
var where = params.filter? params.filter : {}
|
||||
var exclude = {}
|
||||
var sort = params.sort? params.sort.split(',') : []
|
||||
var filter = newVal.filter(v=>!v.formula)
|
||||
if (this.pagedata.origin_api.url.indexOf("data/") >= 0) {
|
||||
filter.forEach(v => {
|
||||
if(v.search) where[v.name + "__icontains"] = v.search
|
||||
else if (v.select) where[v.name + "__in"] = v.select
|
||||
else if (v.filter) {
|
||||
v.filter.map(x=>{
|
||||
let obj = this.$find(arr, {code: x.condition})
|
||||
if(obj) {
|
||||
if(x.condition==='<>') exclude[v.name + (obj.name==='e'? '' : '__' + obj.name)] = this.$formatNumber(x.value)
|
||||
else where[v.name + (obj.name==='e'? '' : '__' + obj.name)] = this.$formatNumber(x.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (v.sort) sort.push(v.sort==="az" ? v.name : "-" + v.name)
|
||||
})
|
||||
params.filter = Object.keys(where).length>0? where : undefined
|
||||
params.exclude = Object.keys(exclude).length>0? exclude : undefined
|
||||
params.sort = sort.length === 0 ? undefined : sort.toString()
|
||||
}
|
||||
// Tải lại dữ liệu
|
||||
let found = this.$copy(this.pagedata.api)
|
||||
found.params = params
|
||||
await this.loadData(found)
|
||||
},
|
||||
async loadData(found) {
|
||||
let result = await this.$getapi([found])
|
||||
if(result==='error') return
|
||||
if(this.flagSearch) {
|
||||
this.flagSearch = false
|
||||
var rows = result[0].data.rows
|
||||
if(this.currentField) this.filterData = this.$unique(rows, [this.currentField.name])
|
||||
} else {
|
||||
let copy = this.$copy(result[0])
|
||||
copy.total_rows = copy.data.total_rows
|
||||
copy.full_data = copy.data.full_data
|
||||
delete copy.data
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "data", data: this.$copy(result[0].data.rows)})
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "api", data: this.$copy(copy)})
|
||||
this.data = this.$copy(result[0].data.rows)
|
||||
this.updateShow(copy.full_data)
|
||||
}
|
||||
},
|
||||
scrollbarVisible() {
|
||||
let element = this.$refs['container']
|
||||
if(!element) return
|
||||
let result = element.scrollWidth > element.clientWidth? true : false
|
||||
if(this.scrollbar) {
|
||||
element.parentNode.removeChild(this.scrollbar)
|
||||
this.scrollbar = undefined
|
||||
}
|
||||
if(result) this.doubleScroll(element)
|
||||
},
|
||||
doubleScroll(element) {
|
||||
var scrollbar= document.createElement('div');
|
||||
scrollbar.appendChild(document.createElement('div'));
|
||||
scrollbar.style.overflow= 'auto';
|
||||
scrollbar.style.overflowY= 'hidden';
|
||||
scrollbar.firstChild.style.width= element.scrollWidth+'px';
|
||||
scrollbar.firstChild.style.height = '1px'
|
||||
scrollbar.firstChild.appendChild(document.createTextNode('\xA0'));
|
||||
var running = false;
|
||||
scrollbar.onscroll= function() {
|
||||
if(running) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
element.scrollLeft= scrollbar.scrollLeft;
|
||||
};
|
||||
element.onscroll= function() {
|
||||
if(running) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
scrollbar.scrollLeft= element.scrollLeft;
|
||||
}
|
||||
element.parentNode.insertBefore(scrollbar, element)
|
||||
scrollbar.scrollLeft= element.scrollLeft
|
||||
this.scrollbar = scrollbar
|
||||
},
|
||||
copyField(field) {
|
||||
let newField = this.$copy(field)
|
||||
newField.name = `f${this.$id()}`
|
||||
newField.label = field.label + '-copy'
|
||||
if(field.format==='number') {
|
||||
newField.formula = field.name
|
||||
newField.tags = [field.name]
|
||||
newField.unit = field.unit==='0.01'? field.unit : '1'
|
||||
} else {
|
||||
newField.copy = field.name
|
||||
if(newField.mandatory==='yes') delete newField['mandatory']
|
||||
}
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
copy.splice(idx+1, 0, newField)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy, data: this.pagedata.data}})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.table tbody tr:hover td, .table tbody tr:hover th {
|
||||
background-color: hsl(0, 0%, 29%);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
38
components/datatable/Date.vue
Normal file
38
components/datatable/Date.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="has-text-right mb-2">
|
||||
<span class="tag is-rounded is-danger is-clickable" @click="remove()">Xóa ngày</span>
|
||||
</div>
|
||||
<b-field>
|
||||
<b-datepicker
|
||||
inline
|
||||
v-model="date"
|
||||
:unselectable-days-of-week="[0, 6]"
|
||||
@input="changeDate()">
|
||||
</b-datepicker>
|
||||
</b-field>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['trandate'],
|
||||
data() {
|
||||
return {
|
||||
date: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.date = new Date(this.trandate)
|
||||
},
|
||||
methods: {
|
||||
changeDate() {
|
||||
this.$emit('modalevent', {name: 'changedate', data: this.date})
|
||||
this.$emit('close')
|
||||
},
|
||||
remove() {
|
||||
this.date = null
|
||||
this.changeDate()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
84
components/datatable/EditLabel.vue
Normal file
84
components/datatable/EditLabel.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<p class="fsb-20 mb-5">Điều chỉnh tiêu đề</p>
|
||||
<div v-for="(v, i) in arr" :key="i" :class="(i>0? 'mt-4' : null)">
|
||||
<p class="fsb-14">Dòng thứ {{(i+1)}}<span class="has-text-danger"> *</span></p>
|
||||
<div class="field has-addons mt-1">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="text" v-model="v.label">
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-primary" @click="add()">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-plus"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="control" @click="remove(i)" v-if="(i>0)">
|
||||
<a class="button is-primary">
|
||||
<span class="icon">
|
||||
<i class="mdi mdi-minus"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help has-text-danger" v-if="v.error"> {{v.error}} </p>
|
||||
</div>
|
||||
<div class="buttons mt-5">
|
||||
<button class="button is-primary" @click="update()">Cập nhật</button>
|
||||
<button class="button is-dark" @click="$emit('close')">Hủy bỏ</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['label'],
|
||||
data() {
|
||||
return {
|
||||
arr: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let arr1 = this.label.replace('<div>', '').replace('</div>', '').split("</p>")
|
||||
arr1.map(v=>{
|
||||
if(!this.$empty(v)) {
|
||||
let label = v + '</p>'
|
||||
label = this.$stripHtml(label)
|
||||
this.arr.push({label: label})
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
this.arr.push({label: undefined})
|
||||
},
|
||||
remove(i) {
|
||||
this.$delete(this.arr, i)
|
||||
},
|
||||
checkError() {
|
||||
let error = false
|
||||
this.arr.map(v=>{
|
||||
if(this.$empty(v.label)) {
|
||||
v.error = 'Nội dung không được bỏ trống'
|
||||
error = true
|
||||
}
|
||||
})
|
||||
if(error) this.arr = this.$copy(this.arr)
|
||||
return error
|
||||
},
|
||||
update() {
|
||||
if(this.checkError()) return
|
||||
let label = ''
|
||||
if(this.arr.length>1) {
|
||||
this.arr.map((v,i)=>{
|
||||
label += `<p${i<this.arr.length-1? ' style="border-bottom: 1px solid white;"' : ''}>${v.label.trim()}</p>`
|
||||
})
|
||||
label = `<div>${label}</div>`
|
||||
} else label = this.arr[0].label.trim()
|
||||
this.$emit('modalevent', {name: 'label', data: label})
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
88
components/datatable/FieldAttribute.vue
Normal file
88
components/datatable/FieldAttribute.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="keys.length>0">
|
||||
<div class="field is-horizontal" v-for="(v,i) in keys" :key="i">
|
||||
<div class="field-body">
|
||||
<div class="field is-narrow">
|
||||
<div class="control">
|
||||
<input class="input fs-14" type="text" placeholder="" v-model="keys[i]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input fs-14" type="text" placeholder="" v-model="values[i]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-narrow">
|
||||
<p class="control">
|
||||
<a>
|
||||
<span class="icon fs-22" @click="addAttr()"><i class="mdi mdi-plus-thick" /></span>
|
||||
</a>
|
||||
<a class="ml-2">
|
||||
<span class="icon has-text-danger fs-22" @click="remove(i)"><i class="mdi mdi-minus-thick" /></span>
|
||||
</a>
|
||||
<a class="ml-2">
|
||||
<span class="icon fs-22" @click="jsonData(v, i)"><i class="mdi mdi-dots-grid" /></span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mb-6" v-else>
|
||||
<button class="button is-primary" @click="addAttr()">Thêm thuộc tính</button>
|
||||
</div>
|
||||
<div class="buttons mt-5">
|
||||
<a class="button is-primary fs-14" @click="update()">Cập nhật</a>
|
||||
</div>
|
||||
<Modal @close="comp=undefined" @update="doUpdate"
|
||||
v-bind="{component: comp, width: '40%', height: '300px', vbind: vbind}" v-if="comp"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['field', 'close'],
|
||||
data() {
|
||||
return {
|
||||
keys: [],
|
||||
values: [],
|
||||
comp: undefined,
|
||||
vbind: undefined,
|
||||
current: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
Object.keys(this.field).map(v=>{
|
||||
this.keys.push(v)
|
||||
this.values.push(this.field[v])
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
doUpdate(v) {
|
||||
this.values[this.current.i] = v
|
||||
},
|
||||
jsonData(v, i) {
|
||||
this.current = {v: v, i: i}
|
||||
this.vbind = {field: this.$empty(this.values[i]) || typeof this.values[i] === 'string'? {} : this.values[i], close: true}
|
||||
this.comp = 'datatable/FieldAttribute'
|
||||
},
|
||||
addAttr() {
|
||||
this.keys.push(undefined)
|
||||
this.values.push(undefined)
|
||||
},
|
||||
remove(i) {
|
||||
this.$delete(this.keys, i)
|
||||
this.$delete(this.values, i)
|
||||
},
|
||||
update() {
|
||||
let obj = {}
|
||||
this.keys.map((v,i)=>{
|
||||
if(!this.$empty(v)) obj[v] = this.values[i]
|
||||
})
|
||||
this.$emit('update', obj)
|
||||
this.$emit('modalevent', {name: 'update', data: obj})
|
||||
if(this.close) this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
180
components/datatable/FilterOption.vue
Normal file
180
components/datatable/FilterOption.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field mt-3 mb-1" v-if="field.format==='number'">
|
||||
<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="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, 60)}} </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="mt-2" v-if="tagsField.length>0">
|
||||
<a @dblclick="expression = expression? (expression + ' ' + v.name) : v.name"
|
||||
class="tag is-rounded" v-for="(v,i) in tagsField" :key="i">
|
||||
<b-tooltip :label="$stripHtml(v.label)" type="is-dark">{{v.name}}</b-tooltip></a>
|
||||
</div>
|
||||
<div class="tags mt-2 mb-0">
|
||||
<b-tooltip type="is-dark" :label="v.name" v-for="(v,i) in operator" :key="i">
|
||||
<span @dblclick="addOperator(v)" class="tag is-success is-rounded is-clickable mr-4">
|
||||
<span class="fs-16">{{v.code}}</span>
|
||||
</span>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-1">
|
||||
<div class="field-body">
|
||||
<div class="field" v-if="field.format==='number'">
|
||||
<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 is-small" type="text" v-model="expression">
|
||||
</p>
|
||||
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='expression')"> {{errors.find(v=>v.name==='expression').message}} </p>
|
||||
</div>
|
||||
|
||||
<div class="field" v-else>
|
||||
<label class="label"> Chuỗi kí tự <span class="has-text-danger"> * </span>
|
||||
</label>
|
||||
<p class="control">
|
||||
<input
|
||||
class="input is-small"
|
||||
type="text"
|
||||
placeholder=""
|
||||
v-model="searchText"
|
||||
@change="changeStyle()"
|
||||
/>
|
||||
</p>
|
||||
<p
|
||||
class="help has-text-danger"
|
||||
v-if="errors.find((v) => v.name === 'searchText')"
|
||||
>
|
||||
{{ errors.find((v) => v.name === "searchText").msg }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field is-narrow" v-if="filterType==='color'">
|
||||
<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>
|
||||
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='color')"> {{errors.find(v=>v.name==='color').message}} </p>
|
||||
</div>
|
||||
<div class="field is-narrow" v-else-if="filterType==='size'">
|
||||
<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="size" @change="changeStyle()">
|
||||
</p>
|
||||
<p class="help has-text-danger" v-if="errors.find(v=>v.name==='size')"> {{errors.find(v=>v.name==='size').message}} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['filterObj', 'filterType', 'pagename', 'field'],
|
||||
data() {
|
||||
return {
|
||||
tagsField: [],
|
||||
expression: undefined,
|
||||
form: undefined,
|
||||
color: undefined,
|
||||
size: undefined,
|
||||
errors: [],
|
||||
searchText: undefined,
|
||||
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'}]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.color = this.filterObj.color
|
||||
this.size = this.filterObj.size
|
||||
this.expression = this.filterObj.expression? this.filterObj.expression : this.field.name
|
||||
if(this.filterObj.tags) {
|
||||
this.filterObj.tags.map(v=>{
|
||||
this.tagsField.push(this.pageData.fields.find(x=>x.name===v))
|
||||
})
|
||||
} else if(this.field.format==='number') this.tagsField.push(this.pageData.fields.find(v=>v.name===this.field.name))
|
||||
},
|
||||
watch: {
|
||||
expression: function(newVal) {
|
||||
if(this.$empty(newVal)) return
|
||||
else this.changeStyle()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colorscheme: {
|
||||
get: function() {return this.$store.state.colorscheme},
|
||||
set: function(val) {this.$store.commit("updateColorScheme", {colorscheme: val})}
|
||||
},
|
||||
pageData: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
colorchoice: {
|
||||
get: function() {return this.$store.state.colorchoice},
|
||||
set: function(val) {this.$store.commit("updateColorChoice", {colorchoice: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addOperator(v) {
|
||||
this.expression = `${this.expression} ${v.code}`
|
||||
},
|
||||
changeStyle() {
|
||||
let check = this.field.format==='number'? this.checkExpression() : this.checkCondition()
|
||||
if(!check) return
|
||||
var row = this.field.format==='number'? {expression: this.expression, tags: this.tagsField.map(v=>v.name)}
|
||||
: {keyword: this.searchText, type: 'search'}
|
||||
this.filterType==='color'? row.color = this.color : row.size = this.size
|
||||
this.$emit('databack', row)
|
||||
},
|
||||
checkCondition() {
|
||||
this.errors = []
|
||||
if(this.filterType==='color' && this.$empty(this.color)) this.errors.push({name: 'color', message: 'Chọn màu'})
|
||||
if(this.filterType==='size' && this.$empty(this.size)) this.errors.push({name: 'size', message: 'Nhập cỡ chữ'})
|
||||
if(this.$empty(this.searchText)) this.errors.push({name: 'searchText', message: 'Chưa nhập chuỗi kí tự'})
|
||||
return this.errors.length>0? false : true
|
||||
},
|
||||
checkExpression() {
|
||||
this.errors = []
|
||||
if(this.filterType==='color' && this.$empty(this.color)) this.errors.push({name: 'color', message: 'Chọn màu'})
|
||||
if(this.filterType==='size' && this.$empty(this.size)) this.errors.push({name: 'size', message: 'Nhập cỡ chữ'})
|
||||
let val = this.$copy(this.expression)
|
||||
let exp = this.$copy(this.expression)
|
||||
this.tagsField.forEach(v => {
|
||||
let myRegExp = new RegExp(v.name, 'g')
|
||||
val = val.replace(myRegExp, Math.random())
|
||||
exp = exp.replace(myRegExp, "field.formatNumber(row['" + v.name + "'])")
|
||||
})
|
||||
try {
|
||||
let value = this.$calc(val)
|
||||
if(isNaN(value) || value===Number.POSITIVE_INFINITY || value===Number.NEGATIVE_INFINITY) {
|
||||
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
|
||||
} else if(!(eval(value)===true || eval(value)===false)) {
|
||||
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
this.errors.push({name: 'expression', message: 'Biểu thức không hợp lệ'})
|
||||
}
|
||||
return this.errors.length>0? false : true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
195
components/datatable/FormatOption.vue
Normal file
195
components/datatable/FormatOption.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="['bgcolor', 'color', 'textsize'].find(x=>x===sideBar)">
|
||||
<p class="has-text-right has-text-grey is-italic fs-13">Màu sắc sẽ hiển thị theo điều kiện Đúng / Sai, mã lệnh do hệ thống tự sinh</p>
|
||||
<div class="tabs is-boxed">
|
||||
<ul> <li v-for="(v,i) in tabs" :key="i" :class="tab.code===v.code? 'is-active' : ''" @click="tab=v">
|
||||
<a>{{v.name}}</a></li> </ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="tab.code==='expression' && ['bgcolor', 'color', 'textsize'].find(x=>x===sideBar)">
|
||||
<div style="max-height:530px; overflow-y:auto">
|
||||
<template v-if="radio? (radio.code==='condition' && sideBar==='bgcolor') : false">
|
||||
<div v-for="(v,i) in bgcolorFilter" :key="v.id" class="px-4 mb-3" style="border: 1px solid #DCDCDC; border-radius: 10px;">
|
||||
<FilterOption v-bind="{filterObj: v, filterType: 'color', pagename: pagename, field: openField}" :ref="v.id"
|
||||
@databack="doConditionFilter($event, 'bgcolor', v.id)" />
|
||||
<p class="fs-14 mt-1 has-text-centered" :class="currentField.format==='string'? 'mb-1' : 'mb-2'">
|
||||
<a class="has-text-primary mr-5" @click="addCondition(bgcolorFilter)" v-if="bgcolorFilter.length<=30"> Thêm </a>
|
||||
<a class="has-text-danger" @click="removeCondition(bgcolorFilter, i)" v-if="bgcolorFilter.length>1"> Bớt </a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="radio? (radio.code==='condition' && sideBar==='color') : false">
|
||||
<div v-for="(v,i) in colorFilter" :key="v.id" class="px-4 mb-3" style="border: 1px solid #DCDCDC; border-radius: 10px;">
|
||||
<FilterOption v-bind="{filterObj: v, filterType: 'color', pagename: pagename, field: openField}" :ref="v.id"
|
||||
@databack="doConditionFilter($event, 'color', v.id)"/>
|
||||
<p class="fs-14 mt-1 has-text-centered" :class="currentField.format==='string'? 'mb-1' : 'mb-2'">
|
||||
<a class="has-text-primary mr-5" @click="addCondition(colorFilter)" v-if="colorFilter.length<=30"> Thêm </a>
|
||||
<a class="has-text-danger" @click="removeCondition(colorFilter,i)" v-if="colorFilter.length>1"> Bớt </a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="radio? (radio.code==='condition' && sideBar==='textsize') : false">
|
||||
<div v-for="(v, i) in sizeFilter" :key="v.id" class="px-4 mb-3" style="border: 1px solid #DCDCDC; border-radius: 10px;">
|
||||
<FilterOption v-bind="{filterObj: v, filterType: 'size', pagename: pagename, field: openField}" :ref="v.id"
|
||||
@databack="doConditionFilter($event, 'textsize', v.id)"/>
|
||||
<p class="fs-14 mt-1 has-text-centered" :class="currentField.format==='string'? 'mb-1' : 'mb-2'">
|
||||
<a class="has-text-primary mr-5" @click="addCondition(sizeFilter)" v-if="sizeFilter.length<=30"> Thêm </a>
|
||||
<a class="has-text-danger" @click="removeCondition(sizeFilter, i)" v-if="sizeFilter.length>1"> Bớt </a>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!--<p class="mx-4 mt-4"><button class="button is-primary is-rounded" @click="update()">Cập nhật</button></p>-->
|
||||
</template>
|
||||
|
||||
<template v-else-if="tab.code==='script' && ['bgcolor', 'color', 'textsize'].find(x=>x===sideBar)">
|
||||
<p class="my-4 mx-4">
|
||||
<a @click="copyContent(script? script : '')" class="mr-6">
|
||||
<span class="icon fs-20">
|
||||
<i class="mdi mdi-content-copy"/>
|
||||
</span>
|
||||
Copy
|
||||
</a>
|
||||
<a @click="paste()" class="mr-6">
|
||||
<span class="icon fs-20">
|
||||
<i class="mdi mdi-content-copy"/>
|
||||
</span>
|
||||
Paste
|
||||
</a>
|
||||
</p>
|
||||
<div class="mx-4">
|
||||
<textarea class="textarea fs-14" rows="8" v-model="script" @change="checkScript()" @dblclick="doCheck"></textarea>
|
||||
</div>
|
||||
<p class="mt-5 mx-4">
|
||||
<span class="icon-text fsb-18">
|
||||
Replace
|
||||
<span class="material-symbols-outlined">
|
||||
chevron_right
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="field is-grouped mx-4 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-rounded is-outlined mt-5" @click="replace()">Replace</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-5 pt-2 mx-4">
|
||||
<span class="icon-text fsb-18">
|
||||
Thay đổi màu
|
||||
<span class="material-symbols-outlined">
|
||||
chevron_right
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="mx-4 mt-4"><button class="button is-primary is-rounded" @click="changeScript()">Cập nhật</button></p>
|
||||
</template>
|
||||
<TableOption v-bind="{pagename: pagename}" v-else-if="sideBar==='option'"> </TableOption>
|
||||
<CreateTemplate v-else-if="sideBar==='template'" v-bind="{pagename: pagename, field: openField}"> </CreateTemplate>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
FilterOption: () => import("@/components/datatable/FilterOption"),
|
||||
TableOption: () => import("@/components/datatable/TableOption"),
|
||||
CreateTemplate: () => import("@/components/datatable/CreateTemplate")
|
||||
},
|
||||
props: ['event', 'currentField', 'pagename'],
|
||||
data() {
|
||||
return {
|
||||
openField: {},
|
||||
bgcolorFilter: [],
|
||||
colorFilter: [],
|
||||
sizeFilter: [],
|
||||
sideBar: undefined,
|
||||
script: undefined,
|
||||
radio: undefined,
|
||||
tabs: [{code: 'expression', name: 'Biểu thức'}, {code: 'script', name: 'Mã lệnh'}],
|
||||
tab: {code: 'expression', name: 'Biểu thức'},
|
||||
source: undefined,
|
||||
target: this.$copy(this.currentField.name),
|
||||
openField: this.$copy(this.currentField)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.openField = this.event.field
|
||||
this.sideBar = this.event.name
|
||||
this.script = this.event.script
|
||||
this.radio = this.event.radio
|
||||
let field = this.event.field
|
||||
this.bgcolorFilter = [{id: this.$id()}]
|
||||
if(field.bgcolor) {
|
||||
if(Array.isArray(field.bgcolor)) this.bgcolorFilter = this.$copy(field.bgcolor)
|
||||
}
|
||||
this.colorFilter = [{id: this.$id()}]
|
||||
if(field.color) {
|
||||
if(Array.isArray(field.color)) this.colorFilter = this.$copy(field.color)
|
||||
}
|
||||
this.sizeFilter = [{id: this.$id()}]
|
||||
if(field.textsize) {
|
||||
if(Array.isArray(field.textsize)) this.sizeFilter = field.textsize
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
doCheck() {
|
||||
let text = window.getSelection().toString()
|
||||
if(this.$empty(text)) return
|
||||
this.source = text
|
||||
},
|
||||
replace() {
|
||||
if(this.$empty(this.script)) return
|
||||
this.script = this.script.replaceAll(this.source, this.target)
|
||||
},
|
||||
async paste() {
|
||||
this.script = await navigator.clipboard.readText()
|
||||
},
|
||||
addCondition(arr) {
|
||||
arr.push({id: this.$id()})
|
||||
},
|
||||
removeCondition(arr, i) {
|
||||
this.$delete(arr, i)
|
||||
},
|
||||
copyContent(value) {
|
||||
this.$copyToClipboard(value)
|
||||
},
|
||||
checkScript() {
|
||||
if(this.$empty(this.script)) return
|
||||
try {
|
||||
JSON.parse(this.script)
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
changeScript() {
|
||||
if(!this.checkScript()) return
|
||||
let copy = this.$copy(this.openField)
|
||||
copy[this.sideBar] = JSON.parse(this.script)
|
||||
this.$emit('modalevent', {name: 'updatefields', data: copy})
|
||||
this.$buefy.toast.open('Màu / cỡ chữ đã được cập nhật')
|
||||
this.$emit('close')
|
||||
},
|
||||
doConditionFilter(v, type, id) {
|
||||
v.id = id
|
||||
let copy = this.openField
|
||||
if(copy[type]? Array.isArray(copy[type]) : false) {
|
||||
let idx = copy[type].findIndex(x=>x.id===id)
|
||||
idx>=0? copy[type][idx] = v : copy[type].push(v)
|
||||
} else copy[type] = [v]
|
||||
this.$emit('modalevent', {name: 'updatefields', data: this.openField})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
56
components/datatable/InputDate.vue
Normal file
56
components/datatable/InputDate.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="hyperlink" @click="open()" v-html="note" v-if="note"></div>
|
||||
<div class="inputshow" style="min-height: 20px;" v-else>
|
||||
<span class="material-symbols-outlined fs-12 is-clickable inputhide" @click="open()">edit</span>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" @changedate="changeDate" v-if="showmodal" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename', 'row', 'field'],
|
||||
data() {
|
||||
return {
|
||||
showmodal: undefined,
|
||||
note: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(this.row[this.field.name]) this.note = this.row[this.field.name]
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
let title = this.$stripHtml(this.field.label)
|
||||
title = title.toLowerCase().indexOf('nhập')>=0? title : `Nhập ${title}`
|
||||
let date = this.note? `${this.note.substring(6,10)}-${this.note.substring(3,5)}-${this.note.substring(0,2)}` : new Date()
|
||||
this.showmodal = {component: 'datatable/Date', vbind: {trandate: date}, title: title, width: '460px', height: '300px'}
|
||||
},
|
||||
changeDate(date) {
|
||||
this.note = date? this.$dayjs(date).format('DD/MM/YYYY') : null
|
||||
let copy = this.$copy(this.$store.state[this.pagename])
|
||||
let idx = this.$findIndex(copy.data, {stock_code: this.row.stock_code})
|
||||
let copyRow = this.$copy(this.row)
|
||||
copyRow[this.field.name] = this.note
|
||||
copy.data[idx] = copyRow
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {data: copy.data}})
|
||||
let datainput = copy.setting.input || {}
|
||||
let f = datainput[this.row.stock_code] || {}
|
||||
this.note? f[this.field.name] = this.note : delete f[this.field.name]
|
||||
Object.keys(f).length>0? datainput[this.row.stock_code] = f : delete datainput[this.row.stock_code]
|
||||
copy.setting.input = Object.keys(datainput).length>0? datainput : null
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "setting", data: copy.setting})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.inputhide {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.inputshow:hover .inputhide {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
56
components/datatable/InputNote.vue
Normal file
56
components/datatable/InputNote.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="hyperlink" @click="open()" v-if="note">{{ note }}</div>
|
||||
<div class="inputshow" style="min-height: 20px;" v-else>
|
||||
<span class="material-symbols-outlined fs-12 is-clickable inputhide" @click="open()">edit</span>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" @changenote="changeNote" v-if="showmodal" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename', 'row', 'field'],
|
||||
data() {
|
||||
return {
|
||||
showmodal: undefined,
|
||||
note: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(this.row[this.field.name]) this.note = this.row[this.field.name]
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
let title = this.$stripHtml(this.field.label)
|
||||
title = title.toLowerCase().indexOf('nhập')>=0? title : `Nhập ${title}`
|
||||
this.showmodal = {component: 'datatable/Note', vbind: {note: this.note}, title: title, width: '500px', height: '200px'}
|
||||
},
|
||||
changeNote(data) {
|
||||
this.note = data.note
|
||||
let copy = this.$copy(this.$store.state[this.pagename])
|
||||
let idx = this.$findIndex(copy.data, {stock_code: this.row.stock_code})
|
||||
let copyRow = this.$copy(this.row)
|
||||
copyRow[this.field.name] = this.note
|
||||
copy.data[idx] = copyRow
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {data: copy.data}})
|
||||
this.showmodal = undefined
|
||||
let datainput = copy.setting.input || {}
|
||||
let f = datainput[this.row.stock_code] || {}
|
||||
this.note? f[this.field.name] = this.note : delete f[this.field.name]
|
||||
Object.keys(f).length>0? datainput[this.row.stock_code] = f : delete datainput[this.row.stock_code]
|
||||
copy.setting.input = Object.keys(datainput).length>0? datainput : null
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "setting", data: copy.setting})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.inputhide {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.inputshow:hover .inputhide {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
57
components/datatable/InputNumber.vue
Normal file
57
components/datatable/InputNumber.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="hyperlink" @click="open()" v-html="note" v-if="note"></div>
|
||||
<div class="inputshow" style="min-height: 20px;" v-else>
|
||||
<span class="material-symbols-outlined fs-12 is-clickable inputhide" @click="open()">edit</span>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" @changenote="changeNote" v-if="showmodal" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename', 'row', 'field'],
|
||||
data() {
|
||||
return {
|
||||
showmodal: undefined,
|
||||
note: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if(this.row[this.field.name]) this.note = this.row[this.field.name]
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
let title = this.$stripHtml(this.field.label)
|
||||
title = title.toLowerCase().indexOf('nhập')>=0? title : `Nhập ${title}`
|
||||
let value = this.note? this.$formatUnit(this.note, 1/this.field.unit, this.field.decimal, true) : undefined
|
||||
this.showmodal = {component: 'datatable/Number', vbind: {note: value}, title: title, width: '300px', height: '150px'}
|
||||
},
|
||||
changeNote(data) {
|
||||
this.note = data.note
|
||||
let copy = this.$copy(this.$store.state[this.pagename])
|
||||
let idx = this.$findIndex(copy.data, {stock_code: this.row.stock_code})
|
||||
let copyRow = this.$copy(this.row)
|
||||
copyRow[this.field.name] = this.$formatUnit(data.note, this.field.unit, this.field.decimal, true, this.field.decimal)
|
||||
copy.data[idx] = copyRow
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {data: copy.data}})
|
||||
this.showmodal = undefined
|
||||
let datainput = copy.setting.input || {}
|
||||
let f = datainput[this.row.stock_code] || {}
|
||||
data.note? f[this.field.name] = data.note : delete f[this.field.name]
|
||||
Object.keys(f).length>0? datainput[this.row.stock_code] = f : delete datainput[this.row.stock_code]
|
||||
copy.setting.input = Object.keys(datainput).length>0? datainput : null
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "setting", data: copy.setting})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.inputhide {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
.inputshow:hover .inputhide {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
32
components/datatable/Note.vue
Normal file
32
components/datatable/Note.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<textarea class="textarea" rows="6" maxlength="300" placeholder="Nhập tối đa 300 kí tự" v-model="text" id="refinput"></textarea>
|
||||
<p class="mt-5 pt-3">
|
||||
<button class="button is-primary" @click="changeNote()">Cập nhật</button>
|
||||
<button class="button is-danger ml-5" @click="remove()">Xóa</button>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['note'],
|
||||
data() {
|
||||
return {
|
||||
text: this.note? this.$copy(this.note) : ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.getElementById('refinput').focus()
|
||||
},
|
||||
methods: {
|
||||
changeNote() {
|
||||
let stext = this.$empty(this.text)? null : this.text.trimLeft().trimRight()
|
||||
this.$emit('modalevent', {name: 'changenote', data: {note: this.$empty(stext)? null : stext}})
|
||||
},
|
||||
remove() {
|
||||
this.text = null
|
||||
this.changeNote()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
54
components/datatable/Number.vue
Normal file
54
components/datatable/Number.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="has-text-left">
|
||||
<input class="input" type="text" placeholder="" @keyup="change" v-model="text" @keyup.enter="doClick()" id="refinput">
|
||||
<p class="help has-text-danger mt-1" v-if="error">{{ error }}</p>
|
||||
<div class="mt-5 pt-3">
|
||||
<button class="button is-primary" @click="doClick()">Cập nhật</button>
|
||||
<button class="button is-danger ml-5" @click="remove()">Xóa</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['note'],
|
||||
data() {
|
||||
return {
|
||||
text: this.note? this.$copy(this.note) : '',
|
||||
error: false,
|
||||
timer: undefined
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.getElementById('refinput').focus()
|
||||
},
|
||||
beforeDestroy() {
|
||||
if(this.timer) clearTimeout(this.timer)
|
||||
this.timer = undefined
|
||||
},
|
||||
methods: {
|
||||
changeNote() {
|
||||
this.$emit('modalevent', {name: 'changenumber', data: {note: this.$empty(this.text)? null : this.text}})
|
||||
},
|
||||
change(e) {
|
||||
if(this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = setTimeout(()=>this.checkNumber(e.target.value), 1000)
|
||||
} else this.timer = setTimeout(()=>this.checkNumber(e.target.value), 1000)
|
||||
},
|
||||
checkNumber(text) {
|
||||
this.error = undefined
|
||||
if(!this.$empty(text) && !this.$isNumber(text)) return this.error = 'Số không hợp lệ'
|
||||
this.text = this.$numtoString(text)
|
||||
},
|
||||
doClick() {
|
||||
this.checkNumber(this.text)
|
||||
if(this.error) return
|
||||
this.$emit('modalevent', {name: 'changenote', data: {note: this.$empty(this.text)? null : this.text}})
|
||||
},
|
||||
remove() {
|
||||
this.text = null
|
||||
this.doClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
103
components/datatable/ScrollBox.vue
Normal file
103
components/datatable/ScrollBox.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="px-2" :style="`max-height: ${maxheight}; overflow-y: auto;`" @scroll="handleScroll">
|
||||
<div class="field is-grouped py-1 border-bottom my-0" v-for="(v, i) in rows" :key="i">
|
||||
<p class="control is-expanded py-0 fs-14 hyperlink" @click="doClick(v,i)">
|
||||
{{v[name]? $stripHtml(v[name], 75) : 'n/a'}}
|
||||
<span class="icon has-text-primary" v-if="checked[i]"><i class="mdi mdi-check"></i></span>
|
||||
</p>
|
||||
<p class="control py-0" v-if="show">
|
||||
<span class="icon-text has-text-grey mr-2 fs-13" v-if="show.author">
|
||||
<span class="icon"><i class="mdi mdi-account-outline"></i></span>
|
||||
<span>{{ v[show.author] }}</span>
|
||||
</span>
|
||||
<span class="icon-text has-text-grey mr-2 fs-13" v-if="show.view">
|
||||
<span class="icon"><i class="mdi mdi-eye-outline"></i></span>
|
||||
<span>{{ v[show.view] }}</span>
|
||||
</span>
|
||||
<span class="fs-13 has-text-grey" v-if="show.time">{{$dayjs(v['create_time']).fromNow(true)}}</span>
|
||||
<b-tooltip label="Mở trong tab mới" type="is-dark" position="is-left">
|
||||
<span class="icon hyperlink ml-1" v-if="show.link" @click="doClick(v,i, 'newtab')"><i class="mdi mdi-open-in-new"></i></span>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Đổi tên" type="is-dark" position="is-left" v-if="show.rename">
|
||||
<span class="icon hyperlink ml-1" @click="$emit('rename', v, i)"><i class="mdi mdi-pencil-outline"></i></span>
|
||||
</b-tooltip>
|
||||
<b-tooltip label="Xóa" type="is-dark" position="is-left" v-if="show.delete">
|
||||
<span class="icon hyperlink has-text-danger ml-1" @click="$emit('remove', v, i)"><i class="mdi mdi-delete-outline"></i></span>
|
||||
</b-tooltip>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['data', 'name', 'maxheight', 'perpage', 'sort', 'selects', 'keyval', 'show'],
|
||||
data() {
|
||||
return {
|
||||
currentPage: 1,
|
||||
total: this.data.length,
|
||||
rows: this.data.slice(0, this.perpage),
|
||||
selected: [],
|
||||
checked: {},
|
||||
time: undefined,
|
||||
array: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getdata()
|
||||
},
|
||||
watch: {
|
||||
data: function(newVal) {
|
||||
this.getdata()
|
||||
},
|
||||
selects: function(newVal) {
|
||||
this.getSelect()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getdata() {
|
||||
this.currentPage = 1
|
||||
this.array = this.$copy(this.data)
|
||||
if(this.sort!==false) {
|
||||
let f = {}
|
||||
let showtime = this.show? this.show.time : false
|
||||
showtime? f['create_time'] = 'desc' : f[this.name] = 'asc'
|
||||
this.$multiSort(this.array, f)
|
||||
}
|
||||
this.rows = this.array.slice(0, this.perpage)
|
||||
this.getSelect()
|
||||
},
|
||||
getSelect() {
|
||||
if(!this.selects) return
|
||||
this.selected = []
|
||||
this.checked = {}
|
||||
this.selects.map(v=>{
|
||||
let idx = this.rows.findIndex(x=>x[this.keyval? this.keyval : this.name]===v)
|
||||
if(idx>=0) {
|
||||
this.selected.push(this.rows[idx])
|
||||
this.checked[idx] = true
|
||||
}
|
||||
})
|
||||
},
|
||||
doClick(v, i, type) {
|
||||
this.checked[i] = this.checked[i]? false : true
|
||||
this.checked = this.$copy(this.checked)
|
||||
let idx = this.selected.findIndex(x=>x.id===v.id)
|
||||
idx>=0? this.$delete(this.selected) : this.selected.push(v)
|
||||
this.$emit('selected', v, type)
|
||||
},
|
||||
handleScroll(e) {
|
||||
const bottom = e.target.scrollHeight - e.target.scrollTop -5 < e.target.clientHeight
|
||||
if (bottom) {
|
||||
if(this.total? this.total>this.rows.length : true) {
|
||||
this.currentPage +=1
|
||||
let arr = this.array.filter((ele,index) => (index>=(this.currentPage-1)*this.perpage && index<this.currentPage*this.perpage))
|
||||
this.rows = this.rows.concat(arr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
177
components/datatable/TableOption.vue
Normal file
177
components/datatable/TableOption.vue
Normal file
@@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div v-if="pagedata">
|
||||
<p class="has-text-right has-text-grey is-italic fs-13 mb-1">Chọn để ẩn hiện cột, kéo thả thay đổi vị trí</p>
|
||||
<b-table
|
||||
class="fs-13"
|
||||
:data="fields"
|
||||
draggable
|
||||
@dragstart="dragstart"
|
||||
@drop="drop"
|
||||
@dragover="dragover"
|
||||
@dragleave="dragleave"
|
||||
>
|
||||
<b-table-column field="index" label="Tt cột" numeric v-slot="props">
|
||||
{{ `C${props.index}` }}
|
||||
</b-table-column>
|
||||
<b-table-column field="label" label="Tên trường" v-slot="props">
|
||||
<span class="hyperlink" @click="openField(props.row)" v-if="1>0">{{ props.row.name }}</span>
|
||||
<span v-else>{{ props.row.name }}</span>
|
||||
<a @click="copyContent(props.row.name)">
|
||||
<span class="icon"><i class="mdi mdi-content-copy"/></span>
|
||||
</a>
|
||||
</b-table-column>
|
||||
<b-table-column>
|
||||
<template v-slot:header="{}">
|
||||
Tên cột
|
||||
<a class="ml-2" @click="edit=!edit">{{edit? 'Cập nhật' : 'Sửa tên'}}</a>
|
||||
</template>
|
||||
<template v-slot="props" v-if="edit">
|
||||
<input class="input is-small" type="text" placeholder="" v-model="props.row.label" @change="checkChange(props.row)">
|
||||
</template>
|
||||
<template v-slot="props" v-else>
|
||||
{{$stripHtml(props.row.label, 80)}}
|
||||
</template>
|
||||
</b-table-column>
|
||||
<b-table-column field="check" width="80px">
|
||||
<template v-slot:header="{}">
|
||||
<span :class="`material-symbols-outlined fs-18 ${checkAll? 'has-text-primary' : 'has-text-grey'} is-clickable`" @click="multiCheck()">
|
||||
{{ checkAll? 'visibility' : 'visibility_off' }}
|
||||
</span>
|
||||
|
||||
<span class="is-clickable" @click="deleteConfirm()">
|
||||
<span class="material-symbols-outlined fs-18 ml-3">delete</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-slot="props">
|
||||
<span class="is-clickable" v-if="!(props.row.required || props.row.mandatory)" @click="checkChange(props.row)">
|
||||
<span :class="`material-symbols-outlined fs-18 ${props.row.show? 'has-text-primary' : 'has-text-grey'}`">
|
||||
{{ props.row.show? 'visibility' : 'visibility_off' }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="is-clickable ml-3" v-if="!(props.row.required || props.row.mandatory)" @click="deleteRow(props.index)">
|
||||
<span class="material-symbols-outlined fs-18">delete</span>
|
||||
</span>
|
||||
</template>
|
||||
</b-table-column>
|
||||
</b-table>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal" @update="updateField"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename', 'detail'],
|
||||
data() {
|
||||
return {
|
||||
fields: [],
|
||||
draggingRow: undefined,
|
||||
draggingRowIndex: undefined,
|
||||
checkAll: true,
|
||||
change: false,
|
||||
edit: false,
|
||||
showmodal: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.pagedata) this.fields = this.$copy(this.pagedata.fields)
|
||||
},
|
||||
watch: {
|
||||
'pagedata.fields': function(newVal) {
|
||||
if(this.change) this.change = false
|
||||
else this.fields = this.$copy(this.pagedata.fields)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pagedata: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
settingclass: {
|
||||
get: function() {return this.$store.state.settingclass},
|
||||
set: function(val) {this.$store.commit("updateSettingClass", { settingclass: val })}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
multiCheck() {
|
||||
this.checkAll = !this.checkAll
|
||||
let newVal = this.checkAll
|
||||
this.fields.filter((v) => !v.mandatory).map((x) => (x.show = newVal));
|
||||
this.fields = this.$copy(this.fields);
|
||||
this.change = true
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: this.$copy(this.fields)}})
|
||||
},
|
||||
openField(v) {
|
||||
this.showmodal = {title: `${v.name} / ${this.$stripHtml(v.label,50)}`, component: "datatable/FieldAttribute", vbind: {field: v}, width: '40%', height: '600px'}
|
||||
},
|
||||
deleteConfirm() {
|
||||
this.$buefy.dialog.confirm({
|
||||
message: 'Bạn có chắc chắc muốn xóa tất cả các trường không?.',
|
||||
confirmText: 'Có',
|
||||
cancelText: 'Không',
|
||||
onConfirm: () => {this.deleteFields()}
|
||||
})
|
||||
},
|
||||
copyContent(value) {
|
||||
this.$copyToClipboard(value)
|
||||
},
|
||||
checkChange(row) {
|
||||
row.show = !row.show
|
||||
this.change = true
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: this.$copy(this.fields)}})
|
||||
},
|
||||
deleteRow(idx) {
|
||||
this.change = true
|
||||
this.$delete(this.fields, idx)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: this.$copy(this.fields)}})
|
||||
},
|
||||
deleteFields() {
|
||||
let filter = this.fields.filter(
|
||||
(v) => !v.mandatory && !v.required
|
||||
);
|
||||
if (filter.length === 0) return;
|
||||
filter.map((v) => {
|
||||
let idx = this.fields.findIndex((x) => x.name === v.name);
|
||||
if (idx >= 0) this.$delete(this.fields, idx);
|
||||
});
|
||||
this.change = true
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: this.$copy(this.fields)}})
|
||||
},
|
||||
dragstart(payload) {
|
||||
this.draggingRow = payload.row;
|
||||
this.draggingRowIndex = payload.index;
|
||||
payload.event.dataTransfer.effectAllowed = "copy";
|
||||
},
|
||||
dragover(payload) {
|
||||
payload.event.dataTransfer.dropEffect = "copy";
|
||||
payload.event.target.closest("tr").classList.add("is-selected");
|
||||
payload.event.preventDefault();
|
||||
},
|
||||
dragleave(payload) {
|
||||
payload.event.target.closest("tr").classList.remove("is-selected");
|
||||
payload.event.preventDefault();
|
||||
},
|
||||
drop(payload) {
|
||||
payload.event.target.closest("tr").classList.remove("is-selected")
|
||||
this.$arrayMove(this.fields, this.draggingRowIndex, payload.index)
|
||||
this.change = true
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: this.$copy(this.fields)}})
|
||||
},
|
||||
exportData() {
|
||||
this.$exportExcel(this.pagedata.dataFilter || this.pagedata.data, 'data-export', this.pagedata.fields.filter(v=>v.show))
|
||||
},
|
||||
updateField(field) {
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
copy[idx] = field
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy}})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
* >>> .table tbody tr td {
|
||||
height: 14px !important;
|
||||
padding-top: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
}
|
||||
</style>
|
||||
156
components/datatable/TableSetting.vue
Normal file
156
components/datatable/TableSetting.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="field is-horizontal mt-3">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14"> Cỡ chữ của bảng <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input class="input is-small" type="number" :value="tablesetting.find(v=>v.code==='table-font-size').detail"
|
||||
@change="changeSetting($event.target.value, 'table-font-size')">
|
||||
</p>
|
||||
<p class="help is-danger mt-1" v-if="errors['table-font-size']">{{errors['table-font-size']}}</p>
|
||||
</div>
|
||||
<div class="field" >
|
||||
<label class="label fs-14"> Cỡ chữ tiêu đề <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<p class="control fs-14">
|
||||
<input class="input is-small" type="number" :value="tablesetting.find(v=>v.code==='header-font-size').detail"
|
||||
@change="changeSetting($event.target.value, 'header-font-size')">
|
||||
</p>
|
||||
<p class="help is-danger mt-1" v-if="errors['header-font-size']">{{errors['header-font-size']}}</p>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14"> Số dòng trên 1 trang <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input class="input is-small" type="number" :value="tablesetting.find(v=>v.code==='per-page').detail"
|
||||
@change="changeSetting($event.target.value, 'per-page')">
|
||||
</p>
|
||||
<p class="help is-danger mt-1" v-if="errors['per-page']">{{errors['per-page']}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-5">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14"> Màu nền bảng <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input type="color" :value="tablesetting.find(v=>v.code==='table-background').detail"
|
||||
@change="changeSetting($event.target.value, 'table-background')">
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14"> Màu chữ <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input type="color" :value="tablesetting.find(v=>v.code==='table-font-color').detail"
|
||||
@change="changeSetting($event.target.value, 'table-font-color')">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-5">
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<label class="label fs-14"> Màu chữ tiêu đề <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input type="color" :value="tablesetting.find(v=>v.code==='header-font-color').detail"
|
||||
@change="changeSetting($event.target.value, 'header-font-color')">
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14"> Màu nền tiêu đề <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input type="color" :value="tablesetting.find(v=>v.code==='header-background').detail"
|
||||
@change="changeSetting($event.target.value, 'header-background')">
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label fs-14"> Màu chữ khi filter<span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input type="color" :value="tablesetting.find(v=>v.code==='header-filter-color').detail"
|
||||
@change="changeSetting($event.target.value, 'header-filter-color')">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-horizontal mt-5">
|
||||
<div class="field-body">
|
||||
<div class="field" >
|
||||
<label class="label fs-14"> Đường viền <span class="has-text-danger"> * </span> </label>
|
||||
<p class="control fs-14">
|
||||
<input class="input is-small" type="text"
|
||||
:value="tablesetting.find(v=>v.code==='td-border')? tablesetting.find(v=>v.code==='td-border').detail : undefined"
|
||||
@change="changeSetting($event.target.value, 'td-border')">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['pagename'],
|
||||
data() {
|
||||
return {
|
||||
errors: [],
|
||||
radioNote: 'no',
|
||||
tablesetting: undefined,
|
||||
errors: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.tablesetting = this.$copy(this.pagedata.tablesetting || this.setting)
|
||||
this.readSetting()
|
||||
},
|
||||
watch: {
|
||||
radioNote: function(newVal) {
|
||||
if(newVal==='no') this.changeSetting('@', 'note')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
setting: {
|
||||
get: function() {return this.$store.state.tablesetting},
|
||||
set: function(val) {this.$store.commit("updateTableSetting", {tablesetting: val})}
|
||||
},
|
||||
pagedata: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
readSetting() {
|
||||
let found = this.tablesetting.find(v=>v.code==='note')
|
||||
if(found? found.detail!=='@' : false) this.radioNote = 'yes'
|
||||
},
|
||||
checkError(value, code) {
|
||||
this.errors = {}
|
||||
let fvalue = this.$formatNumber(value)
|
||||
if(code==='table-font-size' || code==='header-font-size') {
|
||||
if(fvalue <8) this.errors[code] = 'Giá trị phải từ 8 trở lên'
|
||||
else if(fvalue >100) this.errors[code] = 'Giá trị lớn nhất là 100'
|
||||
}
|
||||
if(this.pagedata.setting? this.pagedata.setting.classify__code==='priceboard' : false) {
|
||||
if(code==='per-page') {
|
||||
if(fvalue>50) this.errors[code] = 'Giá trị lớn nhất là 50'
|
||||
else if(fvalue<1) this.errors[code] = 'Giá trị nhỏ nhất là 1'
|
||||
}
|
||||
}
|
||||
return Object.keys(this.errors).length>0? true : false
|
||||
},
|
||||
changeSetting(value, code) {
|
||||
if(this.checkError(value, code)) return
|
||||
if(code==='note' && this.$empty(value)) return
|
||||
let copy = this.$copy(this.tablesetting)
|
||||
let found = copy.find(v=>v.code===code)
|
||||
if(found) found.detail = value
|
||||
else {
|
||||
found = this.$copy(this.tablesetting.find(v=>v.code===code))
|
||||
found.detail = value
|
||||
copy.push(found)
|
||||
}
|
||||
this.tablesetting = copy
|
||||
if(this.pagename) this.$store.commit("updateState", {name: this.pagename, key: "update", data: {tablesetting: copy}})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
585
components/datatable/TableView.vue
Normal file
585
components/datatable/TableView.vue
Normal file
@@ -0,0 +1,585 @@
|
||||
<template>
|
||||
<div :style="`max-height: ${maxheight}; overflow-y: auto;`" @scroll="handleScroll">
|
||||
<div class="table-container" ref="container">
|
||||
<table class="table is-bordered is-narrow is-hoverable" ref="table" :style="getSettingStyle('table')">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(field,i) in displayFields" :key="i" :ref="`th${field.name}`"
|
||||
:style="getSettingStyle('header', field)">
|
||||
<div class="hyperlink" @click="showField(field)" :style="getSettingStyle('dropdown', field)">
|
||||
<template v-if="field.label.indexOf('<')<0">{{field.label}}</template>
|
||||
<template v-else>
|
||||
<component v-bind="{row: field, tick: tickall}" :is="compiledComponent(field.label)" @clickevent="doAction($event, field)" />
|
||||
</template>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(v,i) in displayData" :key="i">
|
||||
<td v-for="(field, j) in fields.filter(v=>v.show)" :key="j" :ref="`${v.stock_code}${field.name}`" :id="`${v.stock_code}${field.name}`"
|
||||
:style="v[`${field.name}color`]" @dblclick="doubleClick(field, v)">
|
||||
<component v-bind="{row: v, tick: tick, pagename: pagename, field: field, highlight: highlight}" v-if="field.template"
|
||||
:is="compiledComponent(field.template)" @clickevent="doAction($event, v, field)"
|
||||
/>
|
||||
<template v-else-if="field.tooltip">
|
||||
<b-tooltip :label="v[field.tooltip.field]"
|
||||
:position="field.tooltip.placement"
|
||||
:type="field.tooltip.type">
|
||||
{{v[field.name]}}
|
||||
</b-tooltip>
|
||||
</template>
|
||||
<template v-else> {{v[field.name]}} </template>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Modal @close="showmodal=undefined" v-bind="showmodal" v-if="showmodal"
|
||||
@dosearch="doSearch(currentField, $event)" @doselect="doSelect(currentField, $event)" @dosort="doSort(currentField, $event)"
|
||||
@setfilter="setFilter(currentField, $event)" @showsidebar="showSidebar($event)" @copyfield="copyField">
|
||||
</Modal>
|
||||
<Modal @close="showmodal1=undefined" v-bind="showmodal1" v-if="showmodal1" @updatefields="updateFields"></Modal>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Vue from 'vue'
|
||||
export default {
|
||||
props: ['pagename', 'vmaxheight'],
|
||||
data() {
|
||||
return {
|
||||
data: [],
|
||||
fields: [],
|
||||
search: '',
|
||||
filters: [],
|
||||
currentPage: 1,
|
||||
filterData: [],
|
||||
timer: undefined,
|
||||
perPage: 30,
|
||||
currentField: undefined,
|
||||
flagSearch: false,
|
||||
scrollbar: undefined,
|
||||
tablesetting: undefined,
|
||||
tick: {},
|
||||
tickall: false,
|
||||
displayData: [],
|
||||
displayFields: [],
|
||||
showmodal: undefined,
|
||||
showmodal1: undefined,
|
||||
totalRows: 0,
|
||||
showPaging: false,
|
||||
highlight: undefined,
|
||||
maxheight: this.vmaxheight || '500px',
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "contextMenu", data: false})
|
||||
if(this.pagedata.data) this.data = this.$copy(this.pagedata.data)
|
||||
if(this.pagedata.fields) this.fields = this.$copy(this.pagedata.fields)
|
||||
if(this.pagedata.filters) this.filters = this.$copy(this.pagedata.filters)
|
||||
this.tablesetting = this.$copy(this.pagedata.tablesetting || this.gridsetting)
|
||||
this.perPage = this.pagedata.perPage? this.pagedata.perPage : this.$formatNumber(this.tablesetting.find(v=>v.code==='per-page').detail)
|
||||
if(this.data.length>0 && this.fields.length>0) this.updateShow()
|
||||
else this.showPagination()
|
||||
},
|
||||
watch: {
|
||||
'pagedata.update': function(newVal) {
|
||||
if(newVal) this.updateData(newVal)
|
||||
},
|
||||
menuaction: function(newVal) {
|
||||
if(this.$empty(newVal)) return
|
||||
if(newVal.name==='export-excel' && (newVal.pagename? newVal.pagename===this.pagename : true)) {
|
||||
this.$exportExcel(this.data, this.menuaction.file, this.fields.filter(v=>v.show))
|
||||
} else if(newVal.name==='opensidebar' && (newVal.pagename? newVal.pagename===this.pagename : true)) {
|
||||
this.showmodal = {component: 'datatable/TableOption', vbind: {pagename: this.pagename}, width: '850px', height: '600px', title: 'Danh sách cột'}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pagedata: {
|
||||
get: function() {return this.$store.state[this.pagename]},
|
||||
set: function(val) {this.$store.commit('updateStore', {name: this.pagename, data: val})}
|
||||
},
|
||||
gridsetting: {
|
||||
get: function() {return this.$store.state.tablesetting},
|
||||
set: function(val) {this.$store.commit("updateTableSetting", {tablesetting: val})}
|
||||
},
|
||||
menuaction: {
|
||||
get: function() {return this.$store.state.menuaction},
|
||||
set: function(val) {this.$store.commit("updateMenuAction", {menuaction: val})}
|
||||
},
|
||||
currentsetting: {
|
||||
get: function() {return this.$store.state.currentsetting},
|
||||
set: function(val) {this.$store.commit("updateCurrentSetting", {currentsetting: val})}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showPagination() {
|
||||
this.showPaging = this.pagedata.pagination===false? false : true
|
||||
this.totalRows = this.data.length
|
||||
if(this.showPaging && this.pagedata.api) {
|
||||
if(this.pagedata.api.full_data===false) this.totalRows = this.pagedata.api.total_rows
|
||||
this.showPaging = this.totalRows > this.perPage
|
||||
}
|
||||
},
|
||||
async updateData(newVal) {
|
||||
if(newVal.columns) { //change attribute
|
||||
this.fields = this.$copy(newVal.columns)
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'fields', data: this.fields})
|
||||
this.$emit('changefield', this.fields)
|
||||
let fields = this.fields.filter(v=>v.show)
|
||||
this.data.map(v=>{
|
||||
fields.map(x=>v[`${x.name}color`] = this.getStyle(x, v))
|
||||
})
|
||||
return this.updateShow()
|
||||
}
|
||||
if(newVal.tablesetting) {
|
||||
this.tablesetting = newVal.tablesetting
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'tablesetting', data: this.tablesetting})
|
||||
this.perPage = this.$formatNumber(this.tablesetting.find(v=>v.code=="per-page").detail)
|
||||
this.currentPage = 1
|
||||
}
|
||||
this.tablesetting = this.$copy(this.pagedata.tablesetting || this.gridsetting)
|
||||
if(this.tablesetting) {
|
||||
this.perPage = this.pagedata.perPage? this.pagedata.perPage : Number(this.tablesetting.find(v=>v.code==='per-page').detail)
|
||||
}
|
||||
if(newVal.fields) {
|
||||
this.fields = this.$copy(newVal.fields)
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'fields', data: this.fields})
|
||||
this.$emit('changefield', this.fields)
|
||||
} else this.fields = this.$copy(this.pagedata.fields)
|
||||
if(newVal.data || newVal.fields) {
|
||||
let copy = this.$copy(newVal.data || this.data)
|
||||
this.data = this.$calculateData(copy, this.fields)
|
||||
let fields = this.fields.filter(v=>v.show)
|
||||
this.data.map(v=>{
|
||||
fields.map(x=>v[`${x.name}color`] = this.getStyle(x, v))
|
||||
})
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'data', data: this.data})
|
||||
this.$emit('changedata', this.data)
|
||||
}
|
||||
if(newVal.filters) this.filters = this.$copy(newVal.filters)
|
||||
else if(this.pagedata.filters) this.filters = this.$copy(this.pagedata.filters)
|
||||
if(newVal.data || newVal.fields || newVal.filters) {
|
||||
let copy = this.$copy(this.filters)
|
||||
this.filters.map((v,i)=>{
|
||||
let idx = this.$findIndex(this.fields, {name: v.name})
|
||||
let index = this.$findIndex(copy, {name: v.name})
|
||||
if(idx<0 && index>=0) this.$delete(copy, index)
|
||||
else if(idx>=0 && index>=0) copy[index].label = this.fields[idx].label
|
||||
})
|
||||
this.filters = copy
|
||||
this.doFilter(this.filters)
|
||||
}
|
||||
if(newVal.data || newVal.fields || newVal.filters || newVal.tablesetting) this.updateShow()
|
||||
if(newVal.data || newVal.fields) setTimeout(()=> this.scrollbarVisible(), 100)
|
||||
if(newVal.highlight) {
|
||||
this.highlight = this.$copy(newVal.highlight)
|
||||
setTimeout(()=>this.highlight = undefined, 500)
|
||||
}
|
||||
},
|
||||
updateShow(full_data) {
|
||||
this.displayFields = this.fields.filter(v=>v.show)
|
||||
if(full_data===false) this.displayData = this.data
|
||||
else {
|
||||
let data = this.data.filter((ele,index) => (index>=(this.currentPage-1)*this.perPage && index<this.currentPage*this.perPage))
|
||||
if(data.length>0) this.displayData = this.displayData.concat(data)
|
||||
}
|
||||
},
|
||||
async changePage() {
|
||||
if(this.pagedata.api? this.pagedata.api.full_data===false : false) await this.backendFilter(this.filters)
|
||||
else this.updateShow()
|
||||
this.$emit('changepage', this.currentPage)
|
||||
this.loading = false
|
||||
},
|
||||
doubleClick(field, v) {
|
||||
this.doSelect(field, v[field.name])
|
||||
},
|
||||
showField(field) {
|
||||
if(this.pagedata.contextMenu===false || field.menu==='no') return
|
||||
this.showContextMenu(field)
|
||||
let doc = this.$refs[`th${field.name}`]
|
||||
let width = (doc? doc.length>0 : false)? doc[0].getBoundingClientRect().width : 100
|
||||
if(this.pagedata.setting) this.currentsetting = this.$copy(this.pagedata.setting)
|
||||
this.showmodal = {vbind: {pagename: this.pagename, field: this.currentField, filters: this.filters, filterData: this.filterData, width: width},
|
||||
component: 'datatable/ContextMenu', title: this.$stripHtml(field.label), width: '600px', height: '500px'}
|
||||
},
|
||||
showCondition(v) {
|
||||
this.$emit('contextmenu', 'open')
|
||||
this.currentField = this.$find(this.pagedata.fields, {'name': v.name})
|
||||
this.showField(this.currentField)
|
||||
},
|
||||
compiledComponent(value) {
|
||||
return {
|
||||
template: `${value}`,
|
||||
props: ['row', 'tick', 'pagename', 'field', 'highlight'],
|
||||
methods: {
|
||||
formatNumber(val) {
|
||||
return this.$formatNumber(val)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
showContextMenu(field) {
|
||||
this.currentField = field
|
||||
this.filterData = this.$unique(this.data, [field.name])
|
||||
this.menuaction = {name: 'display', key: this.$id(), field: field}
|
||||
this.$emit('contextmenu', 'open')
|
||||
},
|
||||
showSidebar(event) {
|
||||
let title = 'Danh sách cột'
|
||||
if(event.name==="bgcolor") title = `Đổi màu nền: ${event.field.name} / ${this.$stripHtml(event.field.label, 30)}`
|
||||
else if(event.name==="color") title = `Đổi màu chữ: ${event.field.name} / ${this.$stripHtml(event.field.label, 30)}`
|
||||
else if(event.name==='template') title = `Định dạng nâng cao: ${this.$stripHtml(event.field.label, 30)}`
|
||||
this.showmodal1 = {component: 'datatable/FormatOption',
|
||||
vbind: {event: event, currentField: this.currentField, pagename: this.pagename}, width: '850px', height: '700px', title: title}
|
||||
},
|
||||
getStyle(field, record) {
|
||||
var stop = false
|
||||
let val = this.tablesetting.find(v=>v.code==='td-border')? this.tablesetting.find(v=>v.code==='td-border').detail : 'border: solid 1px rgb(44, 44, 44); '
|
||||
val = val.indexOf(';')>=0? val : val + ';'
|
||||
if(field.bgcolor? !Array.isArray(field.bgcolor) : false) {
|
||||
val += ` background-color:${field.bgcolor}; `
|
||||
} else if(field.bgcolor? Array.isArray(field.bgcolor) : false) {
|
||||
field.bgcolor.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` background-color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
} else {
|
||||
let res = this.$calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` background-color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
stop = false
|
||||
if(field.color? !Array.isArray(field.color) : false) {
|
||||
val += ` color:${field.color}; `
|
||||
} else if(field.color? Array.isArray(field.color) : false) {
|
||||
field.color.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
} else {
|
||||
let res = this.$calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` color:${v.color}; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
stop = false
|
||||
if(field.textsize? !Array.isArray(field.textsize) : false) {
|
||||
val += ` font-size:${field.textsize}px; `
|
||||
} else if(field.textsize? Array.isArray(field.textsize) : false) {
|
||||
field.textsize.map(v=>{
|
||||
if(v.type==='search') {
|
||||
if(record[field.name] && !stop? record[field.name].toLowerCase().indexOf(v.keyword.toLowerCase())>=0 : false) {
|
||||
val += ` font-size:${v.size}px; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
let res = this.$calculate(record, v.tags, v.expression)
|
||||
if(res.success && res.value && !stop) {
|
||||
val += ` font-size:${v.size}px; `
|
||||
stop = true
|
||||
}
|
||||
}
|
||||
})
|
||||
} else val += ` font-size:${this.tablesetting.find(v=>v.code==='table-font-size').detail}px;`
|
||||
if(field.textalign) val += ` text-align:${field.textalign}; `
|
||||
if(field.minwidth) val += ` min-width:${field.minwidth}px; `
|
||||
if(field.maxwidth) val += ` max-width:${field.maxwidth}px; `
|
||||
return val
|
||||
},
|
||||
getSettingStyle(name, field) {
|
||||
let value = ''
|
||||
if(name==='container') {
|
||||
value = 'min-height:' + this.tablesetting.find(v=>v.code==='container-height').detail + 'rem; '
|
||||
} else if(name==='table') {
|
||||
value += 'background-color:' + this.tablesetting.find(v=>v.code==='table-background').detail + '; '
|
||||
value += 'font-size:' + this.tablesetting.find(v=>v.code==='table-font-size').detail + 'px;'
|
||||
value += 'color:' + this.tablesetting.find(v=>v.code==='table-font-color').detail + '; '
|
||||
} else if(name==='header') {
|
||||
value += 'background-color:' + this.tablesetting.find(v=>v.code==='header-background').detail + '; '
|
||||
if(field.minwidth) value += ' min-width: ' + field.minwidth + 'px; '
|
||||
if(field.maxwidth) value += ' max-width: ' + field.maxwidth + 'px; '
|
||||
} else if(name==='menu') {
|
||||
let arg = this.tablesetting.find(v=>v.code==='menu-width').detail
|
||||
arg = field? (field.menuwidth? field.menuwidth : arg) : arg
|
||||
value += 'width:' + arg + 'rem; '
|
||||
value += 'min-height:' + this.tablesetting.find(v=>v.code==='menu-min-height').detail + 'rem; '
|
||||
value += 'max-height:' + this.tablesetting.find(v=>v.code==='menu-max-height').detail + 'rem; '
|
||||
value += "overflow:auto; "
|
||||
} else if(name==='dropdown') {
|
||||
value += 'font-size:' + this.tablesetting.find(v=>v.code==='header-font-size').detail + 'px; '
|
||||
let found = this.filters.find(v=>v.name===field.name)
|
||||
found? value += 'color:' + this.tablesetting.find(v=>v.code==='header-filter-color').detail + '; '
|
||||
:value += 'color:' + this.tablesetting.find(v=>v.code==='header-font-color').detail + '; '
|
||||
}
|
||||
return value
|
||||
},
|
||||
removeFilter(i) {
|
||||
Vue.delete(this.filters, i)
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
updateFields(field) {
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
copy[idx] = field
|
||||
this.updateData({columns: copy})
|
||||
this.currentField = this.$copy(field)
|
||||
if(this.showmodal) {
|
||||
this.showmodal.vbind.field = this.$copy(field)
|
||||
this.showmodal = this.$copy(this.showmodal)
|
||||
}
|
||||
},
|
||||
doAction(event, row, field) {
|
||||
let name = typeof event === "string"? event : event.name
|
||||
let data = typeof event === "string"? event : event.data
|
||||
this.$store.commit('updateState', {name: this.pagename, key: 'action', data: {event: name, row: row, field: field, data: data, time: new Date()}})
|
||||
if(name==='remove') this.$deleterow(this.pagedata.api.name, row.id, this.pagename, true)
|
||||
if(name==='batchdelete') this.batchDelete()
|
||||
this.$emit(name, row, field, data)
|
||||
if(name==='tickall') {
|
||||
this.tickall = data
|
||||
if(data===false) this.tick = {}
|
||||
else {
|
||||
this.data.map(v=>this.tick[v.id] = true)
|
||||
this.tick = this.$copy(this.tick)
|
||||
}
|
||||
}
|
||||
},
|
||||
batchDelete() {
|
||||
let arr = []
|
||||
Object.entries(this.tick).forEach(([key, value]) => {
|
||||
if(value) arr.push({id: Number(key)})
|
||||
})
|
||||
if(arr.length===0) this.$buefy.toast.open({message: 'Bạn chưa chọn bản ghi để xoá', type: 'is-warning'})
|
||||
else this.$deleterow(this.pagedata.api.name, arr, this.pagename, true)
|
||||
},
|
||||
doSort(field, type) {
|
||||
let filter = {name: field.name, label: field.label, sort: type, format: field.format}
|
||||
let idx = this.filters.findIndex(v=>v.name===field.name)
|
||||
if(idx>=0) Vue.set(this.filters, idx, filter)
|
||||
else this.filters.push(filter)
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
doSearch(field, search) {
|
||||
let copy = this.$copy(this.filters)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
if(idx>=0) Vue.delete(copy, idx)
|
||||
if(this.pagedata.origin_api.full_data) {
|
||||
let data = this.frontendFilter(copy)
|
||||
let rows = this.$empty(search)? data
|
||||
: data.filter(v=>v[field.name]? v[field.name].toString().toLowerCase().indexOf(search.toLowerCase())>=0 : false)
|
||||
this.filterData = this.$unique(rows, [field.name])
|
||||
if(this.showmodal) this.showmodal.vbind.filterData = this.filterData
|
||||
} else {
|
||||
copy.push({name: field.name, label: field.label, search: search.toLowerCase(), format: field.format})
|
||||
this.flagSearch = true
|
||||
this.backendFilter(copy)
|
||||
}
|
||||
},
|
||||
doSelect(field, value) {
|
||||
let found = this.filters.find(v=>v.name===field.name)
|
||||
if(found) {
|
||||
!found.select? found.select = [] : false
|
||||
let idx = found.select.findIndex(x=>x===value)
|
||||
idx>=0? Vue.delete(found.select, idx) : found.select.push(value)
|
||||
if(found.select.length===0) {
|
||||
idx = this.filters.findIndex(v=>v.name===field.name)
|
||||
if(idx>=0) Vue.delete(this.filters, idx)
|
||||
}
|
||||
} else {
|
||||
this.filters.push({name: field.name, label: field.label, select: [value], format: field.format})
|
||||
}
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
setFilter(field, filter) {
|
||||
let idx = this.filters.findIndex(v=>v.name===field.name)
|
||||
if(idx<0) this.filters.push(filter)
|
||||
else Vue.set(this.filters, idx, filter)
|
||||
this.doFilter(this.filters)
|
||||
this.updateShow()
|
||||
},
|
||||
doFilter(newVal, nonset) {
|
||||
if(this.currentPage>1 && nonset!==true) this.currentPage = 1
|
||||
if(this.pagedata.origin_api.full_data) {
|
||||
this.data = this.frontendFilter(newVal)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "dataFilter", data: this.$copy(this.data)})
|
||||
this.$emit('changedata', newVal)
|
||||
}
|
||||
else {
|
||||
if(this.timer) clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => this.backendFilter(newVal), 200)
|
||||
}
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "filters", data: this.$copy(newVal)})
|
||||
this.$emit('changefilter', newVal? newVal.length>0 : false)
|
||||
},
|
||||
frontendFilter(newVal) {
|
||||
let self = this
|
||||
let checkValid = function(name, x, filter) {
|
||||
if(self.$empty(x[name])) return false
|
||||
else {
|
||||
let text = ''
|
||||
filter.map((y,k)=>{
|
||||
text += `${k>0? (filter[k-1].operator==='and'? ' &&' : ' ||') : ''} ${self.$formatNumber(x[name])}
|
||||
${y.condition==='='? '==' : (y.condition==='<>'? '!==' : y.condition)} ${self.$formatNumber(y.value)}`
|
||||
})
|
||||
return self.$calc(text)
|
||||
}
|
||||
}
|
||||
newVal = this.$copy(newVal)
|
||||
var data = this.$copy(this.pagedata.data)
|
||||
newVal.filter(m=>m.select || m.filter).map(v => {
|
||||
if(v.select) {
|
||||
data = data.filter(x => v.select.findIndex(y => this.$empty(y)? this.$empty(x[v.name]) : (y===x[v.name])) >-1)
|
||||
} else if(v.filter) {
|
||||
data = data.filter(x => checkValid(v.name, x, v.filter))
|
||||
}
|
||||
})
|
||||
let sort = {}
|
||||
let format = {}
|
||||
let list = this.filters.filter(x=>x.sort)
|
||||
list.map(v=>{
|
||||
sort[v.name] = v.sort === "az" ? "asc" : "desc"
|
||||
format[v.name] = v.format;
|
||||
})
|
||||
return list.length>0? this.$multiSort(data, sort, format) : data
|
||||
},
|
||||
// Sử dụng backend filter
|
||||
async backendFilter(newVal) {
|
||||
let arr = [{code: '>', name: 'gt'}, {code: '>=', name: 'gte'}, {code: '<', name: 'lt'}, {code: '<=', name: 'lte'}, {code: '=', name: 'e'},
|
||||
{code: '<>', name: 'e'}]
|
||||
let params = this.pagedata.origin_api.params? this.$copy(this.pagedata.origin_api.params) : {}
|
||||
params.page = this.currentPage
|
||||
var where = params.filter? params.filter : {}
|
||||
var exclude = {}
|
||||
var sort = params.sort? params.sort.split(',') : []
|
||||
var filter = newVal.filter(v=>!v.formula)
|
||||
if (this.pagedata.origin_api.url.indexOf("data/") >= 0) {
|
||||
filter.forEach(v => {
|
||||
if(v.search) where[v.name + "__icontains"] = v.search
|
||||
else if (v.select) where[v.name + "__in"] = v.select
|
||||
else if (v.filter) {
|
||||
v.filter.map(x=>{
|
||||
let obj = this.$find(arr, {code: x.condition})
|
||||
if(obj) {
|
||||
if(x.condition==='<>') exclude[v.name + (obj.name==='e'? '' : '__' + obj.name)] = this.$formatNumber(x.value)
|
||||
else where[v.name + (obj.name==='e'? '' : '__' + obj.name)] = this.$formatNumber(x.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
else if (v.sort) sort.push(v.sort==="az" ? v.name : "-" + v.name)
|
||||
})
|
||||
params.filter = Object.keys(where).length>0? where : undefined
|
||||
params.exclude = Object.keys(exclude).length>0? exclude : undefined
|
||||
params.sort = sort.length === 0 ? undefined : sort.toString()
|
||||
}
|
||||
// Tải lại dữ liệu
|
||||
let found = this.$copy(this.pagedata.api)
|
||||
found.params = params
|
||||
await this.loadData(found)
|
||||
},
|
||||
async loadData(found) {
|
||||
let result = await this.$getapi([found])
|
||||
if(result==='error') return
|
||||
if(this.flagSearch) {
|
||||
this.flagSearch = false
|
||||
var rows = result[0].data.rows
|
||||
if(this.currentField) this.filterData = this.$unique(rows, [this.currentField.name])
|
||||
} else {
|
||||
let copy = this.$copy(result[0])
|
||||
copy.total_rows = copy.data.total_rows
|
||||
copy.full_data = copy.data.full_data
|
||||
delete copy.data
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "data", data: this.$copy(result[0].data.rows)})
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "api", data: this.$copy(copy)})
|
||||
this.data = this.$copy(result[0].data.rows)
|
||||
this.data = this.$formatArray(this.data, this.pagedata.fields)
|
||||
if(this.data.length>0) this.displayData = this.displayData.concat(this.data)
|
||||
}
|
||||
},
|
||||
scrollbarVisible() {
|
||||
let element = this.$refs['container']
|
||||
if(!element) return
|
||||
let result = element.scrollWidth > element.clientWidth? true : false
|
||||
if(this.scrollbar) {
|
||||
element.parentNode.removeChild(this.scrollbar)
|
||||
this.scrollbar = undefined
|
||||
}
|
||||
if(result) this.doubleScroll(element)
|
||||
},
|
||||
doubleScroll(element) {
|
||||
var scrollbar= document.createElement('div');
|
||||
scrollbar.appendChild(document.createElement('div'));
|
||||
scrollbar.style.overflow= 'auto';
|
||||
scrollbar.style.overflowY= 'hidden';
|
||||
scrollbar.firstChild.style.width= element.scrollWidth+'px';
|
||||
scrollbar.firstChild.style.height = '1px'
|
||||
scrollbar.firstChild.appendChild(document.createTextNode('\xA0'));
|
||||
var running = false;
|
||||
scrollbar.onscroll= function() {
|
||||
if(running) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
element.scrollLeft= scrollbar.scrollLeft;
|
||||
};
|
||||
element.onscroll= function() {
|
||||
if(running) {
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
running = true;
|
||||
scrollbar.scrollLeft= element.scrollLeft;
|
||||
}
|
||||
element.parentNode.insertBefore(scrollbar, element)
|
||||
scrollbar.scrollLeft= element.scrollLeft
|
||||
this.scrollbar = scrollbar
|
||||
},
|
||||
copyField(field) {
|
||||
let newField = this.$copy(field)
|
||||
newField.formula = field.name
|
||||
newField.tags = [field.name]
|
||||
newField.name = 'f' + this.$dayjs(new Date()).format("hhmmss")
|
||||
newField.label = field.label + '-copy'
|
||||
newField.unit = field.unit==='0.01'? field.unit : '1'
|
||||
let copy = this.$copy(this.pagedata.fields)
|
||||
let idx = copy.findIndex(v=>v.name===field.name)
|
||||
copy.splice(idx+1, 0, newField)
|
||||
this.$store.commit("updateState", {name: this.pagename, key: "update", data: {fields: copy}})
|
||||
},
|
||||
handleScroll(e) {
|
||||
if(this.loading) return
|
||||
const bottom = e.target.scrollHeight - e.target.scrollTop -5 < e.target.clientHeight
|
||||
if (bottom) {
|
||||
this.currentPage += 1
|
||||
this.loading = true
|
||||
console.log('bottom', this.currentPage)
|
||||
this.changePage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.table tbody tr:hover td, .table tbody tr:hover th {
|
||||
background-color: hsl(0, 0%, 29%);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user