mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 06:43:51 +02:00
refactor DataTable component
This commit is contained in:
@@ -26,307 +26,338 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { computed, onBeforeUnmount, ref, toRefs, watch, watchEffect } from 'vue';
|
import { computed, onBeforeUnmount, ref, toRaw, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
import { useAppearanceSettingsStore, useVrcxStore } from '../stores';
|
import { useAppearanceSettingsStore, useVrcxStore } from '../stores';
|
||||||
|
|
||||||
export default {
|
const props = defineProps({
|
||||||
name: 'DataTable',
|
data: {
|
||||||
props: {
|
type: Array,
|
||||||
data: {
|
default: () => []
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
tableProps: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
paginationProps: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
currentPage: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
pageSize: {
|
|
||||||
type: Number,
|
|
||||||
default: 20
|
|
||||||
},
|
|
||||||
pageSizeLinked: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
type: [Array, Object],
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
type: String,
|
|
||||||
default: 'table, pagination'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: [
|
tableProps: {
|
||||||
'update:currentPage',
|
type: Object,
|
||||||
'update:pageSize',
|
default: () => ({})
|
||||||
'update:tableProps',
|
},
|
||||||
'size-change',
|
paginationProps: {
|
||||||
'current-change',
|
type: Object,
|
||||||
'selection-change',
|
default: () => ({})
|
||||||
'row-click',
|
},
|
||||||
'filtered-data',
|
currentPage: {
|
||||||
'sort-change'
|
type: Number,
|
||||||
],
|
default: 1
|
||||||
setup(props, { emit }) {
|
},
|
||||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
pageSize: {
|
||||||
const vrcxStore = useVrcxStore();
|
type: Number,
|
||||||
const { data, currentPage, pageSize, tableProps, paginationProps, filters } = toRefs(props);
|
default: 20
|
||||||
|
},
|
||||||
|
pageSizeLinked: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
layout: {
|
||||||
|
type: String,
|
||||||
|
default: 'table, pagination'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const internalCurrentPage = ref(currentPage.value);
|
const emit = defineEmits([
|
||||||
const internalPageSize = ref(pageSize.value);
|
'update:currentPage',
|
||||||
const sortData = ref({
|
'update:pageSize',
|
||||||
prop: props.tableProps.defaultSort?.prop || 'created_at',
|
'size-change',
|
||||||
order: props.tableProps.defaultSort?.order || 'descending'
|
'current-change',
|
||||||
});
|
'selection-change',
|
||||||
|
'row-click',
|
||||||
|
'sort-change'
|
||||||
|
]);
|
||||||
|
|
||||||
const throttledData = ref(Array.isArray(data.value) ? data.value.slice() : []);
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
const throttledFilters = ref(filters.value);
|
const vrcxStore = useVrcxStore();
|
||||||
const throttledSortData = ref({ ...sortData.value });
|
|
||||||
|
|
||||||
let throttleTimerId = null;
|
const { data, currentPage, pageSize, tableProps, paginationProps, filters } = toRefs(props);
|
||||||
|
|
||||||
const syncThrottledInputs = () => {
|
const internalCurrentPage = ref(currentPage.value);
|
||||||
throttleTimerId = null;
|
const internalPageSize = ref(pageSize.value);
|
||||||
throttledData.value = Array.isArray(data.value) ? data.value.slice() : [];
|
const sortData = ref({
|
||||||
throttledFilters.value = Array.isArray(filters.value) ? filters.value.slice() : filters.value;
|
prop: props.tableProps.defaultSort?.prop || 'created_at',
|
||||||
throttledSortData.value = { ...sortData.value };
|
order: props.tableProps.defaultSort?.order || 'descending'
|
||||||
};
|
});
|
||||||
|
|
||||||
const scheduleThrottledSync = () => {
|
const asRawArray = (value) => (Array.isArray(value) ? toRaw(value) : []);
|
||||||
if (throttleTimerId !== null) {
|
const isEmptyFilterValue = (value) => (Array.isArray(value) ? value.length === 0 : !value);
|
||||||
return;
|
const hasActiveFilters = (activeFilters) => {
|
||||||
}
|
if (!Array.isArray(activeFilters) || activeFilters.length === 0) return false;
|
||||||
throttleTimerId = setTimeout(syncThrottledInputs, 500);
|
return activeFilters.some((filter) => !isEmptyFilterValue(filter?.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(data, scheduleThrottledSync);
|
const showPagination = computed(() => {
|
||||||
watch(() => (Array.isArray(data.value) ? data.value.length : 0), scheduleThrottledSync);
|
return props.layout.includes('pagination');
|
||||||
|
});
|
||||||
|
|
||||||
watch(filters, scheduleThrottledSync, { deep: true });
|
const effectivePageSize = computed(() => {
|
||||||
watch(sortData, scheduleThrottledSync, { deep: true });
|
return props.pageSizeLinked ? appearanceSettingsStore.tablePageSize : internalPageSize.value;
|
||||||
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
const looksSortedByCreatedAtDesc = (src) => {
|
||||||
if (throttleTimerId !== null) {
|
if (!Array.isArray(src) || src.length <= 2) return true;
|
||||||
clearTimeout(throttleTimerId);
|
const start = Math.max(1, src.length - effectivePageSize.value - 1);
|
||||||
throttleTimerId = null;
|
for (let i = start; i < src.length; i++) {
|
||||||
}
|
const a = src[i - 1]?.created_at;
|
||||||
});
|
const b = src[i]?.created_at;
|
||||||
|
if (typeof a === 'string' && typeof b === 'string' && a > b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const showPagination = computed(() => {
|
const throttledData = ref(asRawArray(data.value));
|
||||||
return props.layout.includes('pagination');
|
const throttledFilters = ref(filters.value);
|
||||||
});
|
const throttledSortData = ref({ ...sortData.value });
|
||||||
|
const throttledLooksSortedByCreatedAt = ref(true);
|
||||||
|
|
||||||
const mergedTableProps = computed(() => ({
|
let throttleTimerId = null;
|
||||||
stripe: true,
|
const syncThrottledInputs = () => {
|
||||||
...tableProps.value
|
throttleTimerId = null;
|
||||||
}));
|
throttledData.value = asRawArray(data.value);
|
||||||
|
throttledFilters.value = Array.isArray(filters.value) ? filters.value.slice() : filters.value;
|
||||||
|
throttledSortData.value = { ...sortData.value };
|
||||||
|
|
||||||
const mergedPaginationProps = computed(() => ({
|
const sort = throttledSortData.value;
|
||||||
layout: 'sizes, prev, pager, next, total',
|
const shouldCheckFastPath =
|
||||||
...paginationProps.value,
|
showPagination.value &&
|
||||||
pageSizes: paginationProps.value?.pageSizes ?? appearanceSettingsStore.tablePageSizes
|
!hasActiveFilters(throttledFilters.value) &&
|
||||||
}));
|
sort?.prop === 'created_at' &&
|
||||||
|
sort?.order === 'descending';
|
||||||
|
throttledLooksSortedByCreatedAt.value = shouldCheckFastPath
|
||||||
|
? looksSortedByCreatedAtDesc(throttledData.value)
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
const effectivePageSize = computed(() => {
|
const scheduleThrottledSync = () => {
|
||||||
return props.pageSizeLinked ? appearanceSettingsStore.tablePageSize : internalPageSize.value;
|
if (throttleTimerId !== null) return;
|
||||||
});
|
throttleTimerId = setTimeout(syncThrottledInputs, 500);
|
||||||
|
};
|
||||||
|
|
||||||
const applyFilter = function (row, filter) {
|
watch(data, scheduleThrottledSync);
|
||||||
if (Array.isArray(filter.prop)) {
|
watch(() => (Array.isArray(data.value) ? data.value.length : 0), scheduleThrottledSync);
|
||||||
return filter.prop.some((propItem) => applyFilter(row, { prop: propItem, value: filter.value }));
|
watch(filters, scheduleThrottledSync, { deep: true });
|
||||||
}
|
watch(sortData, scheduleThrottledSync, { deep: true });
|
||||||
|
watch(effectivePageSize, scheduleThrottledSync);
|
||||||
|
|
||||||
const cellValue = row[filter.prop];
|
onBeforeUnmount(() => {
|
||||||
if (cellValue === undefined || cellValue === null) return false;
|
if (throttleTimerId !== null) {
|
||||||
|
clearTimeout(throttleTimerId);
|
||||||
|
throttleTimerId = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (Array.isArray(filter.value)) {
|
const canUseFastCreatedAtDescPagination = computed(() => {
|
||||||
// assume filter dropdown multi select
|
if (!showPagination.value) return false;
|
||||||
return filter.value.some((val) => String(cellValue).toLowerCase() === String(val).toLowerCase());
|
if (!throttledLooksSortedByCreatedAt.value) return false;
|
||||||
} else {
|
|
||||||
return String(cellValue).toLowerCase().includes(String(filter.value).toLowerCase());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredData = computed(() => {
|
const activeFilters = throttledFilters.value;
|
||||||
let result = throttledData.value;
|
if (hasActiveFilters(activeFilters)) return false;
|
||||||
const activeFilters = throttledFilters.value;
|
|
||||||
const activeSort = throttledSortData.value;
|
|
||||||
|
|
||||||
if (activeFilters && Array.isArray(activeFilters) && activeFilters.length > 0) {
|
const sort = throttledSortData.value;
|
||||||
activeFilters.forEach((filter) => {
|
return sort?.prop === 'created_at' && sort?.order === 'descending';
|
||||||
if (!filter.value) {
|
});
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (filter.filterFn) {
|
|
||||||
result = result.filter((row) => filter.filterFn(row, filter));
|
|
||||||
} else if (!Array.isArray(filter.value) || filter.value.length > 0) {
|
|
||||||
result = result.filter((row) => applyFilter(row, filter));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeSort?.prop && activeSort?.order) {
|
const mergedTableProps = computed(() => ({
|
||||||
if (result === throttledData.value) {
|
stripe: true,
|
||||||
result = [...result];
|
...tableProps.value
|
||||||
}
|
}));
|
||||||
const { prop, order } = activeSort;
|
|
||||||
const sortKeyByRow = new Map();
|
|
||||||
|
|
||||||
const getSortKey = (row) => {
|
const mergedPaginationProps = computed(() => ({
|
||||||
if (sortKeyByRow.has(row)) {
|
layout: 'sizes, prev, pager, next, total',
|
||||||
return sortKeyByRow.get(row);
|
...paginationProps.value,
|
||||||
}
|
pageSizes: paginationProps.value?.pageSizes ?? appearanceSettingsStore.tablePageSizes
|
||||||
|
}));
|
||||||
|
|
||||||
const value = row[prop];
|
const applyFilter = function (row, filter) {
|
||||||
let key;
|
if (Array.isArray(filter.prop)) {
|
||||||
if (value == null) {
|
return filter.prop.some((propItem) => applyFilter(row, { prop: propItem, value: filter.value }));
|
||||||
key = null;
|
}
|
||||||
} else if (typeof value === 'number') {
|
|
||||||
key = value;
|
|
||||||
} else if (value instanceof Date) {
|
|
||||||
key = value.getTime();
|
|
||||||
} else {
|
|
||||||
key = String(value).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
sortKeyByRow.set(row, key);
|
const cellValue = row[filter.prop];
|
||||||
return key;
|
if (cellValue === undefined || cellValue === null) return false;
|
||||||
};
|
|
||||||
|
|
||||||
result.sort((a, b) => {
|
if (Array.isArray(filter.value)) {
|
||||||
const aVal = getSortKey(a);
|
return filter.value.some((val) => String(cellValue).toLowerCase() === String(val).toLowerCase());
|
||||||
const bVal = getSortKey(b);
|
} else {
|
||||||
let comparison = 0;
|
return String(cellValue).toLowerCase().includes(String(filter.value).toLowerCase());
|
||||||
|
|
||||||
if (aVal == null && bVal == null) return 0;
|
|
||||||
if (aVal == null) return 1;
|
|
||||||
if (bVal == null) return -1;
|
|
||||||
|
|
||||||
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
||||||
comparison = aVal - bVal;
|
|
||||||
} else {
|
|
||||||
const aStr = typeof aVal === 'string' ? aVal : String(aVal);
|
|
||||||
const bStr = typeof bVal === 'string' ? bVal : String(bVal);
|
|
||||||
if (aStr > bStr) comparison = 1;
|
|
||||||
else if (aStr < bStr) comparison = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return order === 'descending' ? -comparison : comparison;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(
|
|
||||||
() => {
|
|
||||||
emit('filtered-data', filteredData.value);
|
|
||||||
},
|
|
||||||
{ flush: 'post' }
|
|
||||||
);
|
|
||||||
|
|
||||||
const paginatedData = computed(() => {
|
|
||||||
if (!showPagination.value) {
|
|
||||||
return filteredData.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = (internalCurrentPage.value - 1) * effectivePageSize.value;
|
|
||||||
const end = start + effectivePageSize.value;
|
|
||||||
return filteredData.value.slice(start, end);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Frictionless user experience when bigger than maxTableSize
|
|
||||||
const totalItems = computed(() => {
|
|
||||||
const length = filteredData.value.length;
|
|
||||||
const max = vrcxStore.maxTableSize;
|
|
||||||
return length > max && length < max + 51 ? max : length;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSortChange = ({ prop, order }) => {
|
|
||||||
if (props.tableProps.defaultSort) {
|
|
||||||
const { tableProps } = props;
|
|
||||||
tableProps.defaultSort.prop = prop;
|
|
||||||
tableProps.defaultSort.order = order;
|
|
||||||
emit('update:tableProps', tableProps);
|
|
||||||
}
|
|
||||||
sortData.value = { prop, order };
|
|
||||||
emit('sort-change', sortData.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelectionChange = (selection) => {
|
|
||||||
emit('selection-change', selection);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRowClick = (row, column, event) => {
|
|
||||||
emit('row-click', row, column, event);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSizeChange = (size) => {
|
|
||||||
if (props.pageSizeLinked) {
|
|
||||||
appearanceSettingsStore.setTablePageSize(size);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
internalPageSize.value = size;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCurrentChange = (page) => {
|
|
||||||
internalCurrentPage.value = page;
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(currentPage, (newVal) => {
|
|
||||||
internalCurrentPage.value = newVal;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(pageSize, (newVal) => {
|
|
||||||
internalPageSize.value = newVal;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.tableProps.defaultSort,
|
|
||||||
(newSort) => {
|
|
||||||
if (newSort) {
|
|
||||||
sortData.value = {
|
|
||||||
prop: newSort.prop,
|
|
||||||
order: newSort.order
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalItems,
|
|
||||||
internalCurrentPage,
|
|
||||||
internalPageSize,
|
|
||||||
effectivePageSize,
|
|
||||||
showPagination,
|
|
||||||
mergedTableProps,
|
|
||||||
mergedPaginationProps,
|
|
||||||
filteredData,
|
|
||||||
paginatedData,
|
|
||||||
handleSortChange,
|
|
||||||
handleSelectionChange,
|
|
||||||
handleRowClick,
|
|
||||||
handleSizeChange,
|
|
||||||
handleCurrentChange
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toSortKey = (value) => {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (typeof value === 'number') return value;
|
||||||
|
if (value instanceof Date) return value.getTime();
|
||||||
|
return String(value).toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
const compareSortKeys = (aKey, bKey) => {
|
||||||
|
if (aKey == null && bKey == null) return 0;
|
||||||
|
if (aKey == null) return 1;
|
||||||
|
if (bKey == null) return -1;
|
||||||
|
|
||||||
|
if (typeof aKey === 'number' && typeof bKey === 'number') {
|
||||||
|
if (aKey > bKey) return 1;
|
||||||
|
if (aKey < bKey) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aStr = typeof aKey === 'string' ? aKey : String(aKey);
|
||||||
|
const bStr = typeof bKey === 'string' ? bKey : String(bKey);
|
||||||
|
if (aStr > bStr) return 1;
|
||||||
|
if (aStr < bStr) return -1;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createRowSortKeyGetter = (prop) => {
|
||||||
|
const sortKeyByRow = new Map();
|
||||||
|
return (row) => {
|
||||||
|
if (sortKeyByRow.has(row)) {
|
||||||
|
return sortKeyByRow.get(row);
|
||||||
|
}
|
||||||
|
const key = toSortKey(row?.[prop]);
|
||||||
|
sortKeyByRow.set(row, key);
|
||||||
|
return key;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortRows = (rows, { prop, order }) => {
|
||||||
|
const getKey = createRowSortKeyGetter(prop);
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const comparison = compareSortKeys(getKey(a), getKey(b));
|
||||||
|
return order === 'descending' ? -comparison : comparison;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredData = computed(() => {
|
||||||
|
let result = throttledData.value;
|
||||||
|
const activeFilters = throttledFilters.value;
|
||||||
|
const activeSort = throttledSortData.value;
|
||||||
|
|
||||||
|
if (activeFilters && Array.isArray(activeFilters) && activeFilters.length > 0) {
|
||||||
|
activeFilters.forEach((filter) => {
|
||||||
|
if (isEmptyFilterValue(filter?.value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (filter.filterFn) {
|
||||||
|
result = result.filter((row) => filter.filterFn(row, filter));
|
||||||
|
} else if (!Array.isArray(filter.value) || filter.value.length > 0) {
|
||||||
|
result = result.filter((row) => applyFilter(row, filter));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeSort?.prop && activeSort?.order) {
|
||||||
|
if (result === throttledData.value) {
|
||||||
|
result = [...result];
|
||||||
|
}
|
||||||
|
sortRows(result, activeSort);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
const paginatedData = computed(() => {
|
||||||
|
if (!showPagination.value) {
|
||||||
|
return filteredData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canUseFastCreatedAtDescPagination.value) {
|
||||||
|
const src = throttledData.value;
|
||||||
|
const page = [];
|
||||||
|
if (!Array.isArray(src) || src.length === 0) {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
const startOffset = (internalCurrentPage.value - 1) * effectivePageSize.value;
|
||||||
|
const endOffset = startOffset + effectivePageSize.value;
|
||||||
|
for (let offset = startOffset; offset < endOffset; offset++) {
|
||||||
|
const idx = src.length - 1 - offset;
|
||||||
|
if (idx < 0) break;
|
||||||
|
page.push(src[idx]);
|
||||||
|
}
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = (internalCurrentPage.value - 1) * effectivePageSize.value;
|
||||||
|
const end = start + effectivePageSize.value;
|
||||||
|
return filteredData.value.slice(start, end);
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalItems = computed(() => {
|
||||||
|
const length = canUseFastCreatedAtDescPagination.value
|
||||||
|
? Array.isArray(throttledData.value)
|
||||||
|
? throttledData.value.length
|
||||||
|
: 0
|
||||||
|
: filteredData.value.length;
|
||||||
|
const max = vrcxStore.maxTableSize;
|
||||||
|
return length > max && length < max + 51 ? max : length;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSortChange = ({ prop, order }) => {
|
||||||
|
sortData.value = { prop, order };
|
||||||
|
emit('sort-change', sortData.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectionChange = (selection) => {
|
||||||
|
emit('selection-change', selection);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRowClick = (row, column, event) => {
|
||||||
|
emit('row-click', row, column, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
if (props.pageSizeLinked) {
|
||||||
|
appearanceSettingsStore.setTablePageSize(size);
|
||||||
|
emit('update:pageSize', size);
|
||||||
|
emit('size-change', size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
internalPageSize.value = size;
|
||||||
|
emit('update:pageSize', size);
|
||||||
|
emit('size-change', size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCurrentChange = (page) => {
|
||||||
|
internalCurrentPage.value = page;
|
||||||
|
emit('update:currentPage', page);
|
||||||
|
emit('current-change', page);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(currentPage, (newVal) => {
|
||||||
|
internalCurrentPage.value = newVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(pageSize, (newVal) => {
|
||||||
|
internalPageSize.value = newVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.tableProps.defaultSort,
|
||||||
|
(newSort) => {
|
||||||
|
if (newSort) {
|
||||||
|
sortData.value = {
|
||||||
|
prop: newSort.prop,
|
||||||
|
order: newSort.order
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -322,7 +322,17 @@
|
|||||||
}, 80);
|
}, 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancelClose() {
|
||||||
|
clearTimer('close');
|
||||||
|
clearTimer('hide');
|
||||||
|
if (isPopoverOpen(tooltipEl.value)) {
|
||||||
|
isClosing.value = false;
|
||||||
|
isOpen.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
close(true);
|
||||||
clearAllTimers();
|
clearAllTimers();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
style="width: 150px" />
|
style="width: 150px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-loading="loading" v-bind="previousInstancesGroupDialogTable" style="margin-top: 10px">
|
<DataTable :loading="loading" v-bind="previousInstancesGroupDialogTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
style="width: 150px"
|
style="width: 150px"
|
||||||
clearable></el-input>
|
clearable></el-input>
|
||||||
</div>
|
</div>
|
||||||
<DataTable v-loading="loading" v-bind="dataTable" style="margin-top: 10px">
|
<DataTable :loading="loading" v-bind="dataTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="110">
|
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="110">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tooltip placement="left">
|
<el-tooltip placement="left">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||||
style="display: block; width: 150px"></el-input>
|
style="display: block; width: 150px"></el-input>
|
||||||
</div>
|
</div>
|
||||||
<DataTable v-loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
|
<DataTable :loading="loading" v-bind="previousInstancesWorldDialogTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
:placeholder="t('dialog.previous_instances.search_placeholder')"
|
||||||
style="display: block; width: 150px"></el-input>
|
style="display: block; width: 150px"></el-input>
|
||||||
</div>
|
</div>
|
||||||
<DataTable v-loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
|
<DataTable :loading="loading" v-bind="previousInstancesUserDialogTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
<el-table-column :label="t('table.previous_instances.date')" prop="created_at" sortable width="170">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
<span>{{ formatDateFilter(scope.row.created_at, 'long') }}</span>
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<pre style="white-space: pre-wrap; font-size: 12px" v-text="avatarImportDialog.errors"></pre>
|
<pre style="white-space: pre-wrap; font-size: 12px" v-text="avatarImportDialog.errors"></pre>
|
||||||
</template>
|
</template>
|
||||||
<DataTable v-loading="avatarImportDialog.loading" v-bind="avatarImportTable" style="margin-top: 10px">
|
<DataTable :loading="avatarImportDialog.loading" v-bind="avatarImportTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.import.image')" width="70" prop="thumbnailImageUrl">
|
<el-table-column :label="t('table.import.image')" width="70" prop="thumbnailImageUrl">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-popover placement="right" :width="500" trigger="hover">
|
<el-popover placement="right" :width="500" trigger="hover">
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
<h2 style="font-weight: bold; margin: 5px 0">{{ t('dialog.friend_import.errors') }}</h2>
|
<h2 style="font-weight: bold; margin: 5px 0">{{ t('dialog.friend_import.errors') }}</h2>
|
||||||
<pre style="white-space: pre-wrap; font-size: 12px" v-text="friendImportDialog.errors"></pre>
|
<pre style="white-space: pre-wrap; font-size: 12px" v-text="friendImportDialog.errors"></pre>
|
||||||
</template>
|
</template>
|
||||||
<DataTable v-loading="friendImportDialog.loading" v-bind="friendImportTable" style="margin-top: 10px">
|
<DataTable :loading="friendImportDialog.loading" v-bind="friendImportTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.import.image')" width="70" prop="currentAvatarThumbnailImageUrl">
|
<el-table-column :label="t('table.import.image')" width="70" prop="currentAvatarThumbnailImageUrl">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-popover placement="right" :width="500" trigger="hover">
|
<el-popover placement="right" :width="500" trigger="hover">
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<pre style="white-space: pre-wrap; font-size: 12px" v-text="worldImportDialog.errors"></pre>
|
<pre style="white-space: pre-wrap; font-size: 12px" v-text="worldImportDialog.errors"></pre>
|
||||||
</template>
|
</template>
|
||||||
<DataTable v-loading="worldImportDialog.loading" v-bind="worldImportTable" style="margin-top: 10px">
|
<DataTable :loading="worldImportDialog.loading" v-bind="worldImportTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.import.image')" width="70" prop="thumbnailImageUrl">
|
<el-table-column :label="t('table.import.image')" width="70" prop="thumbnailImageUrl">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-popover placement="right" :width="500" trigger="hover">
|
<el-popover placement="right" :width="500" trigger="hover">
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
@change="gameLogTableLookup"></el-input>
|
@change="gameLogTableLookup"></el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-loading="gameLogTable.loading" v-bind="gameLogTable">
|
<DataTable v-bind="gameLogTable">
|
||||||
<el-table-column :label="t('table.gameLog.date')" prop="created_at" :sortable="true" width="130">
|
<el-table-column :label="t('table.gameLog.date')" prop="created_at" :sortable="true" width="130">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<NativeTooltip placement="right">
|
<NativeTooltip placement="right">
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<pre style="white-space: pre-wrap; font-size: 12px" v-text="errors"></pre>
|
<pre style="white-space: pre-wrap; font-size: 12px" v-text="errors"></pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<DataTable v-loading="loading" v-bind="noteExportTable" style="margin-top: 10px">
|
<DataTable :loading="loading" v-bind="noteExportTable" style="margin-top: 10px">
|
||||||
<el-table-column :label="t('table.import.image')" width="70" prop="currentAvatarThumbnailImageUrl">
|
<el-table-column :label="t('table.import.image')" width="70" prop="currentAvatarThumbnailImageUrl">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-popover placement="right" :width="500" trigger="hover">
|
<el-popover placement="right" :width="500" trigger="hover">
|
||||||
|
|||||||
Reference in New Issue
Block a user