mirror of https://github.com/Fabio286/antares.git
perf: improved scroll speed of result tables
This commit is contained in:
parent
949f7add8f
commit
bbde2bd994
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="vscroll-holder">
|
<div :style="{'height': visibleHeight+'px'}" class="vscroll-holder">
|
||||||
<div
|
<div
|
||||||
class="vscroll-spacer"
|
class="vscroll-spacer"
|
||||||
:style="{
|
:style="{
|
||||||
|
@ -21,47 +21,43 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// credits: https://github.com/xrado 👼
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BaseVirtualScroll',
|
name: 'BaseVirtualScroll',
|
||||||
props: {
|
props: {
|
||||||
items: Array,
|
items: Array,
|
||||||
itemHeight: Number
|
itemHeight: Number,
|
||||||
|
visibleHeight: Number
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
topHeight: 0,
|
topHeight: 0,
|
||||||
bottomHeight: 0,
|
bottomHeight: 0,
|
||||||
visibleItems: []
|
visibleItems: [],
|
||||||
|
renderTimeout: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this._checkScrollPosition = this.checkScrollPosition.bind(this);
|
this._checkScrollPosition = this.updateWindow.bind(this);
|
||||||
this.checkScrollPosition();
|
this.updateWindow();
|
||||||
this.$el.addEventListener('scroll', this._checkScrollPosition);
|
this.$el.addEventListener('scroll', this._checkScrollPosition);
|
||||||
this.$el.addEventListener('wheel', this._checkScrollPosition);
|
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
this.$el.removeEventListener('scroll', this._checkScrollPosition);
|
this.$el.removeEventListener('scroll', this._checkScrollPosition);
|
||||||
this.$el.removeEventListener('wheel', this._checkScrollPosition);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkScrollPosition (e = {}) {
|
checkScrollPosition () {
|
||||||
const el = this.$el;
|
|
||||||
|
|
||||||
// prevent parent scroll
|
|
||||||
if ((el.scrollTop === 0 && e.deltaY < 0) || (Math.abs(el.scrollTop - (el.scrollHeight - el.clientHeight)) <= 1 && e.deltaY > 0))
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
this.updateWindow(e);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateWindow (e) {
|
updateWindow (e) {
|
||||||
const visibleItemsCount = Math.ceil(this.$el.clientHeight / this.itemHeight);
|
const visibleItemsCount = Math.ceil(this.$el.clientHeight / this.itemHeight);
|
||||||
const totalScrollHeight = this.items.length * this.itemHeight;
|
const totalScrollHeight = this.items.length * this.itemHeight;
|
||||||
|
const offset = 50;
|
||||||
|
|
||||||
const scrollTop = this.$el.scrollTop;
|
const scrollTop = this.$el.scrollTop;
|
||||||
const offset = 5;
|
|
||||||
|
clearTimeout(this.renderTimeout);
|
||||||
|
|
||||||
|
this.renderTimeout = setTimeout(() => {
|
||||||
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
|
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
|
||||||
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
||||||
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
||||||
|
@ -71,6 +67,7 @@ export default {
|
||||||
|
|
||||||
this.topHeight = firstCutIndex * this.itemHeight;
|
this.topHeight = firstCutIndex * this.itemHeight;
|
||||||
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
|
this.bottomHeight = totalScrollHeight - this.visibleItems.length * this.itemHeight - this.topHeight;
|
||||||
|
}, 200);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,9 +11,10 @@
|
||||||
v-if="results.rows"
|
v-if="results.rows"
|
||||||
ref="resultTable"
|
ref="resultTable"
|
||||||
:items="sortedResults"
|
:items="sortedResults"
|
||||||
:item-height="25"
|
:item-height="22"
|
||||||
class="vscroll"
|
class="vscroll"
|
||||||
:style="{'height': resultsSize+'px'}"
|
:style="{'height': resultsSize+'px'}"
|
||||||
|
:visible-height="resultsSize"
|
||||||
>
|
>
|
||||||
<template slot-scope="{ items }">
|
<template slot-scope="{ items }">
|
||||||
<div class="table table-hover">
|
<div class="table table-hover">
|
||||||
|
@ -40,26 +41,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tbody">
|
<div class="tbody">
|
||||||
<div
|
<WorkspaceQueryTableRow
|
||||||
v-for="row in items"
|
v-for="row in items"
|
||||||
:key="row._id"
|
:key="row._id"
|
||||||
|
:row="row"
|
||||||
|
:fields="fields"
|
||||||
class="tr"
|
class="tr"
|
||||||
:class="{'selected': selectedRows.includes(row._id)}"
|
:class="{'selected': selectedRows.includes(row._id)}"
|
||||||
@click="selectRow($event, row._id)"
|
@selectRow="selectRow($event, row._id)"
|
||||||
>
|
|
||||||
<WorkspaceQueryTableCell
|
|
||||||
v-for="(col, cKey) in row"
|
|
||||||
:key="cKey"
|
|
||||||
:content="col"
|
|
||||||
:field="cKey"
|
|
||||||
:precision="fieldPrecision(cKey)"
|
|
||||||
:type="fieldType(cKey)"
|
|
||||||
@updateField="updateField($event, row[primaryField.name])"
|
@updateField="updateField($event, row[primaryField.name])"
|
||||||
@contextmenu="contextMenu($event, {id: row._id, field: cKey})"
|
@contextmenu="contextMenu"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</BaseVirtualScroll>
|
</BaseVirtualScroll>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,7 +62,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||||
import WorkspaceQueryTableCell from '@/components/WorkspaceQueryTableCell';
|
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
|
||||||
import TableContext from '@/components/WorkspaceQueryTableContext';
|
import TableContext from '@/components/WorkspaceQueryTableContext';
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
|
|
||||||
|
@ -76,7 +70,7 @@ export default {
|
||||||
name: 'WorkspaceQueryTable',
|
name: 'WorkspaceQueryTable',
|
||||||
components: {
|
components: {
|
||||||
BaseVirtualScroll,
|
BaseVirtualScroll,
|
||||||
WorkspaceQueryTableCell,
|
WorkspaceQueryTableRow,
|
||||||
TableContext
|
TableContext
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div class="tr" @click="selectRow($event, row._id)">
|
||||||
<div
|
<div
|
||||||
v-if="field !== '_id'"
|
v-for="(col, cKey) in row"
|
||||||
ref="cell"
|
:key="cKey"
|
||||||
class="td p-0"
|
class="td p-0"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@contextmenu.prevent="$emit('contextmenu', $event)"
|
@contextmenu.prevent="$emit('contextmenu', $event, {id: row._id, field: cKey})"
|
||||||
|
@updateField="updateField($event, row[primaryField.name])"
|
||||||
>
|
>
|
||||||
|
<template v-if="cKey !== '_id'">
|
||||||
<span
|
<span
|
||||||
v-if="!isInlineEditor"
|
v-if="!isInlineEditor[cKey]"
|
||||||
class="cell-content px-2"
|
class="cell-content px-2"
|
||||||
:class="`${isNull(content)} type-${type}`"
|
:class="`${isNull(col)} type-${fieldType(cKey)}`"
|
||||||
@dblclick="editON"
|
@dblclick="editON($event, col, cKey)"
|
||||||
>{{ content | typeFormat(type, precision) | cutText }}</span>
|
>{{ col | typeFormat(fieldType(cKey), fieldPrecision(cKey)) | cutText }}</span>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input
|
<input
|
||||||
v-if="inputProps.mask"
|
v-if="inputProps.mask"
|
||||||
ref="editField"
|
ref="editField"
|
||||||
v-model="localContent"
|
v-model="editingContent"
|
||||||
v-mask="inputProps.mask"
|
v-mask="inputProps.mask"
|
||||||
:type="inputProps.type"
|
:type="inputProps.type"
|
||||||
autofocus
|
autofocus
|
||||||
|
@ -26,13 +29,15 @@
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
ref="editField"
|
ref="editField"
|
||||||
v-model="localContent"
|
v-model="editingContent"
|
||||||
:type="inputProps.type"
|
:type="inputProps.type"
|
||||||
autofocus
|
autofocus
|
||||||
class="editable-field px-2"
|
class="editable-field px-2"
|
||||||
@blur="editOFF"
|
@blur="editOFF"
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
v-if="isTextareaEditor"
|
v-if="isTextareaEditor"
|
||||||
:confirm-text="$t('word.update')"
|
:confirm-text="$t('word.update')"
|
||||||
|
@ -41,19 +46,19 @@
|
||||||
@hide="hideEditorModal"
|
@hide="hideEditorModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template :slot="'header'">
|
||||||
{{ $t('word.edit') }} "{{ field }}"
|
{{ $t('word.edit') }} "{{ editingField }}"
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<div :slot="'body'">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<div>
|
<div>
|
||||||
<textarea
|
<textarea
|
||||||
v-model="localContent"
|
v-model="editingContent"
|
||||||
class="form-input textarea-editor"
|
class="form-input textarea-editor"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="editor-field-info">
|
<div class="editor-field-info">
|
||||||
<div><b>{{ $t('word.size') }}</b>: {{ localContent.length }}</div>
|
<div><b>{{ $t('word.size') }}</b>: {{ editingContent.length }}</div>
|
||||||
<div><b>{{ $t('word.type') }}</b>: {{ type.toUpperCase() }}</div>
|
<div><b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,7 +70,7 @@
|
||||||
@hide="hideEditorModal"
|
@hide="hideEditorModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template :slot="'header'">
|
||||||
{{ $t('word.edit') }} "{{ field }}"
|
{{ $t('word.edit') }} "{{ editingField }}"
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<div :slot="'body'">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
|
@ -73,7 +78,7 @@
|
||||||
<div v-if="contentInfo.size">
|
<div v-if="contentInfo.size">
|
||||||
<img
|
<img
|
||||||
v-if="isImage"
|
v-if="isImage"
|
||||||
:src="`data:${contentInfo.mime};base64, ${bufferToBase64(localContent)}`"
|
:src="`data:${contentInfo.mime};base64, ${bufferToBase64(editingContent)}`"
|
||||||
class="img-responsive p-centered bg-checkered"
|
class="img-responsive p-centered bg-checkered"
|
||||||
>
|
>
|
||||||
<div v-else class="text-center">
|
<div v-else class="text-center">
|
||||||
|
@ -93,10 +98,10 @@
|
||||||
</transition>
|
</transition>
|
||||||
<div class="editor-field-info">
|
<div class="editor-field-info">
|
||||||
<div>
|
<div>
|
||||||
<b>{{ $t('word.size') }}</b>: {{ localContent.length | formatBytes }}<br>
|
<b>{{ $t('word.size') }}</b>: {{ editingContent.length | formatBytes }}<br>
|
||||||
<b>{{ $t('word.mimeType') }}</b>: {{ contentInfo.mime }}
|
<b>{{ $t('word.mimeType') }}</b>: {{ contentInfo.mime }}
|
||||||
</div>
|
</div>
|
||||||
<div><b>{{ $t('word.type') }}</b>: {{ type.toUpperCase() }}</div>
|
<div><b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<label>{{ $t('message.uploadFile') }}</label>
|
<label>{{ $t('message.uploadFile') }}</label>
|
||||||
|
@ -123,10 +128,13 @@ import { mask } from 'vue-the-mask';
|
||||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceQueryTableCell',
|
name: 'WorkspaceQueryTableRow',
|
||||||
components: {
|
components: {
|
||||||
ConfirmModal
|
ConfirmModal
|
||||||
},
|
},
|
||||||
|
directives: {
|
||||||
|
mask
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
formatBytes,
|
formatBytes,
|
||||||
cutText (val) {
|
cutText (val) {
|
||||||
|
@ -163,22 +171,20 @@ export default {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
directives: {
|
|
||||||
mask
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
type: String,
|
row: Object,
|
||||||
field: String,
|
fields: Array
|
||||||
precision: [Number, null],
|
|
||||||
content: [String, Number, Object, Date, Uint8Array]
|
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isInlineEditor: false,
|
isInlineEditor: {},
|
||||||
isTextareaEditor: false,
|
isTextareaEditor: false,
|
||||||
isBlobEditor: false,
|
isBlobEditor: false,
|
||||||
willBeDeleted: false,
|
willBeDeleted: false,
|
||||||
localContent: null,
|
originalContent: null,
|
||||||
|
editingContent: null,
|
||||||
|
editingType: null,
|
||||||
|
editingField: null,
|
||||||
contentInfo: {
|
contentInfo: {
|
||||||
ext: '',
|
ext: '',
|
||||||
mime: '',
|
mime: '',
|
||||||
|
@ -220,35 +226,61 @@ export default {
|
||||||
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
this.fields.forEach(field => {
|
||||||
|
this.isInlineEditor[field.name] = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
fieldType (cKey) {
|
||||||
|
let type = 'unknown';
|
||||||
|
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||||
|
if (field)
|
||||||
|
type = field.type;
|
||||||
|
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
fieldPrecision (cKey) {
|
||||||
|
let length = 0;
|
||||||
|
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||||
|
if (field)
|
||||||
|
length = field.precision;
|
||||||
|
|
||||||
|
return length;
|
||||||
|
},
|
||||||
isNull (value) {
|
isNull (value) {
|
||||||
return value === null ? ' is-null' : '';
|
return value === null ? ' is-null' : '';
|
||||||
},
|
},
|
||||||
bufferToBase64 (val) {
|
bufferToBase64 (val) {
|
||||||
return bufferToBase64(val);
|
return bufferToBase64(val);
|
||||||
},
|
},
|
||||||
editON () {
|
editON (event, content, field) {
|
||||||
if (LONG_TEXT.includes(this.type)) {
|
const type = this.fieldType(field);
|
||||||
|
this.originalContent = content;
|
||||||
|
this.editingType = type;
|
||||||
|
this.editingField = field;
|
||||||
|
|
||||||
|
if (LONG_TEXT.includes(type)) {
|
||||||
this.isTextareaEditor = true;
|
this.isTextareaEditor = true;
|
||||||
this.localContent = this.$options.filters.typeFormat(this.content, this.type);
|
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BLOB.includes(this.type)) {
|
if (BLOB.includes(type)) {
|
||||||
this.isBlobEditor = true;
|
this.isBlobEditor = true;
|
||||||
this.localContent = this.content ? this.content : '';
|
this.editingContent = this.originalContent || '';
|
||||||
this.fileToUpload = null;
|
this.fileToUpload = null;
|
||||||
this.willBeDeleted = false;
|
this.willBeDeleted = false;
|
||||||
|
|
||||||
if (this.content !== null) {
|
if (this.originalContent !== null) {
|
||||||
const buff = Buffer.from(this.localContent);
|
const buff = Buffer.from(this.editingContent);
|
||||||
if (buff.length) {
|
if (buff.length) {
|
||||||
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
|
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
|
||||||
const { ext, mime } = mimeFromHex(hex);
|
const { ext, mime } = mimeFromHex(hex);
|
||||||
this.contentInfo = {
|
this.contentInfo = {
|
||||||
ext,
|
ext,
|
||||||
mime,
|
mime,
|
||||||
size: this.localContent.length
|
size: this.editingContent.length
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,20 +288,24 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline editable fields
|
// Inline editable fields
|
||||||
this.localContent = this.$options.filters.typeFormat(this.content, this.type);
|
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type);
|
||||||
this.$nextTick(() => { // Focus on input
|
this.$nextTick(() => { // Focus on input
|
||||||
this.$refs.cell.blur();
|
event.target.blur();
|
||||||
|
|
||||||
this.$nextTick(() => this.$refs.editField.focus());
|
this.$nextTick(() => document.querySelector('.editable-field').focus());
|
||||||
});
|
});
|
||||||
this.isInlineEditor = true;
|
|
||||||
|
const obj = {
|
||||||
|
[field]: true
|
||||||
|
};
|
||||||
|
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
|
||||||
},
|
},
|
||||||
editOFF () {
|
editOFF () {
|
||||||
this.isInlineEditor = false;
|
this.isInlineEditor[this.editingField] = false;
|
||||||
let content;
|
let content;
|
||||||
if (!['blob', 'mediumblob', 'longblob'].includes(this.type)) {
|
if (!['blob', 'mediumblob', 'longblob'].includes(this.editingType)) {
|
||||||
if (this.localContent === this.$options.filters.typeFormat(this.content, this.type)) return;// If not changed
|
if (this.editingContent === this.$options.filters.typeFormat(this.originalContent, this.editingType)) return;// If not changed
|
||||||
content = this.localContent;
|
content = this.editingContent;
|
||||||
}
|
}
|
||||||
else { // Handle file upload
|
else { // Handle file upload
|
||||||
if (this.willBeDeleted) {
|
if (this.willBeDeleted) {
|
||||||
|
@ -283,10 +319,13 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('updateField', {
|
this.$emit('updateField', {
|
||||||
field: this.field,
|
field: this.editingField,
|
||||||
type: this.type,
|
type: this.editingType,
|
||||||
content
|
content
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.editingType = null;
|
||||||
|
this.editingField = null;
|
||||||
},
|
},
|
||||||
hideEditorModal () {
|
hideEditorModal () {
|
||||||
this.isTextareaEditor = false;
|
this.isTextareaEditor = false;
|
||||||
|
@ -295,7 +334,7 @@ export default {
|
||||||
downloadFile () {
|
downloadFile () {
|
||||||
const downloadLink = document.createElement('a');
|
const downloadLink = document.createElement('a');
|
||||||
|
|
||||||
downloadLink.href = `data:${this.contentInfo.mime};base64, ${bufferToBase64(this.localContent)}`;
|
downloadLink.href = `data:${this.contentInfo.mime};base64, ${bufferToBase64(this.editingContent)}`;
|
||||||
downloadLink.setAttribute('download', `${this.field}.${this.contentInfo.ext}`);
|
downloadLink.setAttribute('download', `${this.field}.${this.contentInfo.ext}`);
|
||||||
document.body.appendChild(downloadLink);
|
document.body.appendChild(downloadLink);
|
||||||
|
|
||||||
|
@ -310,13 +349,22 @@ export default {
|
||||||
this.willBeDeleted = false;
|
this.willBeDeleted = false;
|
||||||
},
|
},
|
||||||
prepareToDelete () {
|
prepareToDelete () {
|
||||||
this.localContent = '';
|
this.editingContent = '';
|
||||||
this.contentInfo = {
|
this.contentInfo = {
|
||||||
ext: '',
|
ext: '',
|
||||||
mime: '',
|
mime: '',
|
||||||
size: null
|
size: null
|
||||||
};
|
};
|
||||||
this.willBeDeleted = true;
|
this.willBeDeleted = true;
|
||||||
|
},
|
||||||
|
updateField (event, id) {
|
||||||
|
this.$emit('updateField', event, id);
|
||||||
|
},
|
||||||
|
contextMenu (event, cell) {
|
||||||
|
this.$emit('updateField', event, cell);
|
||||||
|
},
|
||||||
|
selectRow (event, row) {
|
||||||
|
this.$emit('selectRow', event, row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
Loading…
Reference in New Issue