Files
VRCX/src/components/DataTable.vue
Natsumi 3324d0d279 Upgrade to Vue3 and Element Plus (#1374)
* Update Vue devtools

* upgrade vue pinia element-plus vue-i18n, add vite

* fix: i18n

* global components

* change v-deep

* upgrade vue-lazyload

* data table

* update enlint and safe-dialog

* package.json and vite.config.js

* el-icon

* el-message

* vue 2 -> vue3 migration changes

* $pinia

* dialog

* el-popover slot

* lint

* chore

* slot

* scss

* remote state access

* misc

* jsconfig

* el-button size mini -> small

* :model-value

* ElMessageBox

* datatable

* remove v-lazyload

* template #dropdown

* mini -> small

* css

* byebye hideTooltips

* use sass-embedded

* Update SQLite, remove unneeded libraries

* Fix shift remove local avatar favorites

* Electron arm64

* arm64 support

* bye pug

* f-word vite hah

* misc

* remove safe dialog component

* Add self invite to launch dialog

* Fix errors

* Icons 1

* improve localfavorite loading performance

* improve favorites world item performance

* dialog visibility changes for Element Plus

* clear element plus error

* import performance

* revert App.vue hah

* hah

* Revert "Add self invite to launch dialog"

This reverts commit 4801cfad58.

* Toggle self invite/open in-game

* Self invite on launch dialog

* el-button icon

* el-icon

* fix user dialog tab switching logic

* fix PlayerList

* Formatting changes

* More icons

* Fix friend log table

* loading margin

* fix markdown

* fix world dialog tab switching issue

* Fixes and formatting

* fix: global i18n.t export

* fix favorites world tab not working

* Create instance, displayName

* Remove group members sort by userId

* Fix loading dialog tabs on swtich

* Star

* charts console.warn

* wip: fix charts

* wip: fix charts

* wip: charts composables

* fix favorite item tooltip warning

* Fixes and formatting

* Clean up image dialogs

* Remove unused method

* Fix platform/size border

* Fix platform/size border

* $vr

* fix friendExportDialogVisible binding

* ElMessageBox and Settings

* Login formatting

* Rename VR overlay query

* Fix image popover and userdialog badges

* Formatting

* Big buttons

* Fixes, update Cef

* Fix gameLog table nav buttons jumping around while using nav buttons

* Fix z-index

* vr overlay

* vite input add theme

* defineAsyncComponent

* ISO 639-1

* fix i18n

* clean t

* Formatting, fix calendar, rotate arrows

* Show user status when user is offline

* Fix VR overlay

* fix theme and clean up

* split InstanceActivity

* tweak

* Fix VR overlay formatting

* fix scss var

* AppDebug hahahaha

* Years

* remove reactive

* improve perf

* state hah…

* fix user rendering poblems when user object is not yet loaded

* improve perf

* Update avatar/world image uploader, licenses, remove previous images dialog (old images are now deleted)

* improve perf 1

* Suppress stray errors

* fix traveling location display issue

* Fix empty instance creator

* improve friend list refresh performance

* fix main charts

* fix chart

* Fix darkmode

* Fix avatar dialog tags

---------

Co-authored-by: pa <maplenagisa@gmail.com>
2025-09-12 10:45:24 +12:00

245 lines
7.9 KiB
Vue

<template>
<div class="data-table-wrapper">
<el-table
ref="tableRef"
v-loading="loading"
:data="paginatedData"
v-bind="mergedTableProps"
default-sort-prop="created_at"
default-sort-order="descending"
lazy
@sort-change="handleSortChange"
@selection-change="handleSelectionChange"
@row-click="handleRowClick">
<slot></slot>
</el-table>
<div v-if="showPagination" class="pagination-wrapper">
<el-pagination
:current-page="internalCurrentPage"
:page-size="internalPageSize"
:total="filteredData.length"
v-bind="mergedPaginationProps"
@size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
</div>
</template>
<script>
import { computed, ref, watch, toRefs } from 'vue';
export default {
name: 'DataTable',
props: {
data: {
type: Array,
default: () => []
},
tableProps: {
type: Object,
default: () => ({})
},
paginationProps: {
type: Object,
default: () => ({})
},
currentPage: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
filters: {
type: [Array, Object],
default: () => []
},
loading: {
type: Boolean,
default: false
},
layout: {
type: String,
default: 'table, pagination'
},
defaultSort: {
type: Object,
default: () => ({ prop: 'created_at', order: 'descending' })
}
},
emits: [
'update:currentPage',
'update:pageSize',
'size-change',
'current-change',
'selection-change',
'row-click',
'filtered-data'
],
setup(props, { emit }) {
const { data, currentPage, pageSize, tableProps, paginationProps, filters } = toRefs(props);
const internalCurrentPage = ref(currentPage.value);
const internalPageSize = ref(pageSize.value);
const sortData = ref({
prop: props.defaultSort?.prop || null,
order: props.defaultSort?.order || null
});
const showPagination = computed(() => {
return props.layout.includes('pagination');
});
const mergedTableProps = computed(() => ({
stripe: true,
...tableProps.value
}));
const mergedPaginationProps = computed(() => ({
layout: 'sizes, prev, pager, next, total',
pageSizes: [20, 50, 100, 200],
small: true,
...paginationProps.value
}));
const applyFilter = function (row, filter) {
if (Array.isArray(filter.prop)) {
return filter.prop.some((propItem) => applyFilter(row, { prop: propItem, value: filter.value }));
}
const cellValue = row[filter.prop];
if (cellValue === undefined || cellValue === null) return false;
if (Array.isArray(filter.value)) {
return filter.value.some((val) =>
String(cellValue).toLowerCase().includes(String(val).toLowerCase())
);
} else {
return String(cellValue).toLowerCase().includes(String(filter.value).toLowerCase());
}
};
const filteredData = computed(() => {
let result = [...data.value];
if (filters.value && Array.isArray(filters.value) && filters.value.length > 0) {
filters.value.forEach((filter) => {
if (filter.value && (!Array.isArray(filter.value) || filter.value.length > 0)) {
result = result.filter((row) => applyFilter(row, filter));
}
});
}
if (sortData.value.prop && sortData.value.order) {
const { prop, order } = sortData.value;
result.sort((a, b) => {
const aVal = a[prop];
const bVal = b[prop];
let comparison = 0;
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 if (aVal instanceof Date && bVal instanceof Date) {
comparison = aVal.getTime() - bVal.getTime();
} else {
const aStr = String(aVal).toLowerCase();
const bStr = String(bVal).toLowerCase();
if (aStr > bStr) comparison = 1;
else if (aStr < bStr) comparison = -1;
}
return order === 'descending' ? -comparison : comparison;
});
}
emit('filtered-data', result);
return result;
});
const paginatedData = computed(() => {
if (!showPagination.value) {
return filteredData.value;
}
const start = (internalCurrentPage.value - 1) * internalPageSize.value;
const end = start + internalPageSize.value;
return filteredData.value.slice(start, end);
});
const handleSortChange = ({ prop, order }) => {
sortData.value = { prop, order };
};
const handleSelectionChange = (selection) => {
emit('selection-change', selection);
};
const handleRowClick = (row, column, event) => {
emit('row-click', row, column, event);
};
const handleSizeChange = (size) => {
internalPageSize.value = size;
};
const handleCurrentChange = (page) => {
internalCurrentPage.value = page;
};
watch(currentPage, (newVal) => {
internalCurrentPage.value = newVal;
});
watch(pageSize, (newVal) => {
internalPageSize.value = newVal;
});
watch(
() => props.defaultSort,
(newSort) => {
if (newSort) {
sortData.value = {
prop: newSort.prop,
order: newSort.order
};
}
},
{ immediate: true }
);
return {
internalCurrentPage,
internalPageSize,
showPagination,
mergedTableProps,
mergedPaginationProps,
filteredData,
paginatedData,
handleSortChange,
handleSelectionChange,
handleRowClick,
handleSizeChange,
handleCurrentChange
};
}
};
</script>
<style scoped>
.data-table-wrapper {
width: 100%;
}
.pagination-wrapper {
margin-top: 16px;
display: flex;
justify-content: space-around;
}
</style>