forked from github/dataease
Merge pull request #2488 from dataease/pr@dev@perf_visual_select
perf: 优化下拉框过滤组件数据量大卡顿 使用虚拟dom
This commit is contained in:
commit
5b8df7176b
166
frontend/src/components/ElVisualSelect/index.vue
Normal file
166
frontend/src/components/ElVisualSelect/index.vue
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<el-select
|
||||||
|
ref="visualSelect"
|
||||||
|
v-model="selectValue"
|
||||||
|
:class="classId"
|
||||||
|
popper-class="VisualSelects"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-on="$listeners"
|
||||||
|
@visible-change="popChange"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in options" :key="item.id" :label="item.text" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { uuid } from 'vue-uuid'
|
||||||
|
export default {
|
||||||
|
name: 'ElVisualSelect',
|
||||||
|
model: {
|
||||||
|
prop: 'value', // 绑定的值,通过父组件传递
|
||||||
|
event: 'update' // 自定义名
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
classId: {
|
||||||
|
type: String,
|
||||||
|
require: true,
|
||||||
|
default: uuid.v1()
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default: () => {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: [String, Number, Array],
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
newList: [],
|
||||||
|
selectValue: this.value,
|
||||||
|
options: [],
|
||||||
|
domList: null,
|
||||||
|
slectBoxDom: null,
|
||||||
|
scrollbar: null,
|
||||||
|
startIndex: 0,
|
||||||
|
endIndex: 0,
|
||||||
|
maxLength: 9, // 弹出框最大支持9个条目
|
||||||
|
itemHeight: 34, // select组件选项高度
|
||||||
|
maxHeightDom: null,
|
||||||
|
defaultFirst: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectValue(val) {
|
||||||
|
this.$emit('update', val)
|
||||||
|
if (!val) {
|
||||||
|
this.resetList()
|
||||||
|
this.maxHeightDom.style.height = this.newList.length * 34 + 'px'
|
||||||
|
this.domList.style.paddingTop = 0 + 'px'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list() {
|
||||||
|
this.resetList()
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.resetList()
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addScrollDiv(selectDom) {
|
||||||
|
this.maxHeightDom = document.createElement('div')
|
||||||
|
this.maxHeightDom.className = 'el-select-height'
|
||||||
|
selectDom.insertBefore(this.maxHeightDom, this.domList)
|
||||||
|
},
|
||||||
|
reCacularHeight() {
|
||||||
|
this.maxHeightDom.style.height = this.newList.length * this.itemHeight + 'px'
|
||||||
|
},
|
||||||
|
resetList(arrys) {
|
||||||
|
if (Array.isArray(arrys)) {
|
||||||
|
this.newList = arrys.slice()
|
||||||
|
this.domList.style.paddingTop = 0 + 'px'
|
||||||
|
this.scrollbar.scrollTop = 0
|
||||||
|
this.callback()
|
||||||
|
} else {
|
||||||
|
this.newList = this.list.slice()
|
||||||
|
}
|
||||||
|
this.options = this.newList.slice(0, this.maxLength)
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this.defaultFirst && this.list.length > 0) {
|
||||||
|
this.selectValue = this.list[0].value
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectDom = document.querySelector(
|
||||||
|
`.${this.classId} .el-select-dropdown .el-select-dropdown__wrap`
|
||||||
|
)
|
||||||
|
this.scrollbar = document.querySelector(`.${this.classId} .el-select-dropdown .el-scrollbar`)
|
||||||
|
this.slectBoxDom = document.querySelector(`.${this.classId} .el-select-dropdown__wrap`)
|
||||||
|
this.slectBoxDom.style.display = 'flex'
|
||||||
|
this.slectBoxDom.style.flexDirection = 'row'
|
||||||
|
this.domList = selectDom.querySelector(
|
||||||
|
`.${this.classId} .el-select-dropdown__wrap .el-select-dropdown__list`
|
||||||
|
)
|
||||||
|
this.addScrollDiv(this.slectBoxDom)
|
||||||
|
|
||||||
|
this.scrollFn()
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollFn() {
|
||||||
|
this.scrollbar.addEventListener('scroll', this.callback, false)
|
||||||
|
},
|
||||||
|
|
||||||
|
callback() {
|
||||||
|
const scrollTop = this.scrollbar.scrollTop
|
||||||
|
this.startIndex = parseInt(scrollTop / this.itemHeight)
|
||||||
|
this.endIndex = this.startIndex + this.maxLength
|
||||||
|
|
||||||
|
this.options = this.newList.slice(this.startIndex, this.endIndex)
|
||||||
|
this.domList.style.paddingTop = scrollTop - (scrollTop % this.itemHeight) + 'px'
|
||||||
|
},
|
||||||
|
popChange() {
|
||||||
|
this.domList.style.paddingTop = 0 + 'px'
|
||||||
|
|
||||||
|
this.resetList()
|
||||||
|
this.reCacularHeight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.VisualSelects {
|
||||||
|
.el-scrollbar {
|
||||||
|
position: relative;
|
||||||
|
height: 251px;
|
||||||
|
overflow: inherit;
|
||||||
|
overflow-x: hidden;
|
||||||
|
content-visibility: auto;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
background: #ffffff !important;
|
||||||
|
}
|
||||||
|
.el-select-height {
|
||||||
|
width: 1px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.el-select-dropdown__list {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.el-select-dropdown__wrap {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<component
|
||||||
<el-select
|
:is="mode"
|
||||||
v-if="element.options!== null && element.options.attrs!==null && show"
|
v-if="element.options!== null && element.options.attrs!==null && show "
|
||||||
ref="deSelect"
|
ref="deSelect"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
|
:class-id="'visual-' + element.id + '-' + inDraw + '-' + inScreen"
|
||||||
:collapse-tags="showNumber"
|
:collapse-tags="showNumber"
|
||||||
:clearable="!element.options.attrs.multiple"
|
:clearable="!element.options.attrs.multiple"
|
||||||
:multiple="element.options.attrs.multiple"
|
:multiple="element.options.attrs.multiple"
|
||||||
@ -12,12 +13,13 @@
|
|||||||
:size="size"
|
:size="size"
|
||||||
:filterable="true"
|
:filterable="true"
|
||||||
popper-class="coustom-de-select"
|
popper-class="coustom-de-select"
|
||||||
|
:list="datas"
|
||||||
@change="changeValue"
|
@change="changeValue"
|
||||||
@focus="setOptionWidth"
|
@focus="setOptionWidth"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in datas"
|
v-for="item in templateDatas || datas"
|
||||||
:key="item[element.options.attrs.key]"
|
:key="item[element.options.attrs.key]"
|
||||||
:style="{width:selectOptionWidth}"
|
:style="{width:selectOptionWidth}"
|
||||||
:label="item[element.options.attrs.label]"
|
:label="item[element.options.attrs.label]"
|
||||||
@ -25,11 +27,12 @@
|
|||||||
>
|
>
|
||||||
<span :title="item[element.options.attrs.label]" style="display:inline-block;width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;">{{ item[element.options.attrs.label] }}</span>
|
<span :title="item[element.options.attrs.label]" style="display:inline-block;width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;">{{ item[element.options.attrs.label] }}</span>
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</component>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import ElVisualSelect from '@/components/ElVisualSelect'
|
||||||
import { multFieldValues, linkMultFieldValues } from '@/api/dataset/dataset'
|
import { multFieldValues, linkMultFieldValues } from '@/api/dataset/dataset'
|
||||||
import bus from '@/utils/bus'
|
import bus from '@/utils/bus'
|
||||||
import { getLinkToken, getToken } from '@/utils/auth'
|
import { getLinkToken, getToken } from '@/utils/auth'
|
||||||
@ -37,6 +40,7 @@ import customInput from '@/components/widget/DeWidget/customInput'
|
|||||||
import { textSelectWidget } from '@/components/widget/DeWidget/serviceNameFn.js'
|
import { textSelectWidget } from '@/components/widget/DeWidget/serviceNameFn.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { ElVisualSelect },
|
||||||
mixins: [customInput],
|
mixins: [customInput],
|
||||||
props: {
|
props: {
|
||||||
element: {
|
element: {
|
||||||
@ -65,6 +69,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
mode() {
|
||||||
|
let result = 'el-select'
|
||||||
|
if (this.element.options && this.element.options.attrs && this.element.options.attrs.visual) {
|
||||||
|
result = 'el-visual-select'
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
templateDatas() {
|
||||||
|
return this.mode === 'el-visual-select' ? [] : null
|
||||||
|
},
|
||||||
operator() {
|
operator() {
|
||||||
return this.element.options.attrs.multiple ? 'in' : 'eq'
|
return this.element.options.attrs.multiple ? 'in' : 'eq'
|
||||||
},
|
},
|
||||||
|
@ -19,7 +19,8 @@ const dialogPanel = {
|
|||||||
value: 'id',
|
value: 'id',
|
||||||
fieldId: '',
|
fieldId: '',
|
||||||
dragItems: [],
|
dragItems: [],
|
||||||
sort: {}
|
sort: {},
|
||||||
|
visual: false
|
||||||
},
|
},
|
||||||
value: '',
|
value: '',
|
||||||
manualModify: false
|
manualModify: false
|
||||||
@ -52,6 +53,7 @@ class TextSelectServiceImpl extends WidgetService {
|
|||||||
super(options)
|
super(options)
|
||||||
this.filterDialog = true
|
this.filterDialog = true
|
||||||
this.showSwitch = true
|
this.showSwitch = true
|
||||||
|
this.showVisual = true
|
||||||
}
|
}
|
||||||
|
|
||||||
initLeftPanel() {
|
initLeftPanel() {
|
||||||
|
@ -9,8 +9,13 @@
|
|||||||
:inactive-text="$t('panel.single_choice')"
|
:inactive-text="$t('panel.single_choice')"
|
||||||
@change="multipleChange"
|
@change="multipleChange"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<span v-if="widget.showVisual" style="padding-left: 20px;">
|
||||||
|
<el-checkbox v-model="attrs.visual" @change="showVisualChange">虚拟化</el-checkbox>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
<el-col :span="16">
|
<el-col :span="16">
|
||||||
<div class="filter-options-right">
|
<div class="filter-options-right">
|
||||||
<span style="padding-right: 10px;">
|
<span style="padding-right: 10px;">
|
||||||
@ -64,7 +69,7 @@
|
|||||||
<el-checkbox v-model="attrs.enableParameters" @change="enableParametersChange"><span>
|
<el-checkbox v-model="attrs.enableParameters" @change="enableParametersChange"><span>
|
||||||
{{ $t('panel.binding_parameters') }} </span> </el-checkbox>
|
{{ $t('panel.binding_parameters') }} </span> </el-checkbox>
|
||||||
|
|
||||||
<el-popover placement="bottom-end" :disabled="!attrs.enableParameters" width="200">
|
<el-popover placement="bottom-end" :disabled="!attrs.enableParameters" width="200">
|
||||||
<div class="view-container-class">
|
<div class="view-container-class">
|
||||||
<el-checkbox-group v-model="attrs.parameters">
|
<el-checkbox-group v-model="attrs.parameters">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
@ -99,7 +104,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from "vuex";
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FilterControl',
|
name: 'FilterControl',
|
||||||
@ -163,6 +168,9 @@ export default {
|
|||||||
}
|
}
|
||||||
this.fillAttrs2Filter()
|
this.fillAttrs2Filter()
|
||||||
},
|
},
|
||||||
|
showVisualChange(value) {
|
||||||
|
this.fillAttrs2Filter()
|
||||||
|
},
|
||||||
|
|
||||||
fillAttrs2Filter() {}
|
fillAttrs2Filter() {}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user