mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 14:53:50 +02:00
fix: datatable sorting issue
This commit is contained in:
@@ -4,8 +4,7 @@
|
|||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="paginatedData"
|
:data="paginatedData"
|
||||||
v-bind="mergedTableProps"
|
v-bind="mergedTableProps"
|
||||||
default-sort-prop="created_at"
|
:default-sort="resolvedDefaultSort"
|
||||||
default-sort-order="descending"
|
|
||||||
lazy
|
lazy
|
||||||
@sort-change="handleSortChange"
|
@sort-change="handleSortChange"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
@@ -77,6 +76,7 @@
|
|||||||
'current-change',
|
'current-change',
|
||||||
'selection-change',
|
'selection-change',
|
||||||
'row-click',
|
'row-click',
|
||||||
|
'filtered-data',
|
||||||
'sort-change'
|
'sort-change'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -87,17 +87,9 @@
|
|||||||
|
|
||||||
const internalCurrentPage = ref(currentPage.value);
|
const internalCurrentPage = ref(currentPage.value);
|
||||||
const internalPageSize = ref(pageSize.value);
|
const internalPageSize = ref(pageSize.value);
|
||||||
const sortData = ref({
|
|
||||||
prop: props.tableProps.defaultSort?.prop || 'created_at',
|
|
||||||
order: props.tableProps.defaultSort?.order || 'descending'
|
|
||||||
});
|
|
||||||
|
|
||||||
const asRawArray = (value) => (Array.isArray(value) ? toRaw(value) : []);
|
const asRawArray = (value) => (Array.isArray(value) ? toRaw(value) : []);
|
||||||
const isEmptyFilterValue = (value) => (Array.isArray(value) ? value.length === 0 : !value);
|
const isEmptyFilterValue = (value) => (Array.isArray(value) ? value.length === 0 : !value);
|
||||||
const hasActiveFilters = (activeFilters) => {
|
|
||||||
if (!Array.isArray(activeFilters) || activeFilters.length === 0) return false;
|
|
||||||
return activeFilters.some((filter) => !isEmptyFilterValue(filter?.value));
|
|
||||||
};
|
|
||||||
|
|
||||||
const showPagination = computed(() => {
|
const showPagination = computed(() => {
|
||||||
return props.layout.includes('pagination');
|
return props.layout.includes('pagination');
|
||||||
@@ -107,67 +99,14 @@
|
|||||||
return props.pageSizeLinked ? appearanceSettingsStore.tablePageSize : internalPageSize.value;
|
return props.pageSizeLinked ? appearanceSettingsStore.tablePageSize : internalPageSize.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const detectCreatedAtOrder = (src) => {
|
|
||||||
if (!Array.isArray(src) || src.length <= 2) return null;
|
|
||||||
|
|
||||||
const detectOrderInRange = (startIndexInclusive, endIndexInclusive) => {
|
|
||||||
let couldBeAsc = true;
|
|
||||||
let couldBeDesc = true;
|
|
||||||
|
|
||||||
const start = Math.max(1, startIndexInclusive);
|
|
||||||
const end = Math.min(endIndexInclusive, src.length - 1);
|
|
||||||
for (let i = start; i <= end; i++) {
|
|
||||||
const a = src[i - 1]?.created_at;
|
|
||||||
const b = src[i]?.created_at;
|
|
||||||
if (typeof a !== 'string' || typeof b !== 'string') continue;
|
|
||||||
if (a > b) {
|
|
||||||
couldBeAsc = false;
|
|
||||||
} else if (a < b) {
|
|
||||||
couldBeDesc = false;
|
|
||||||
}
|
|
||||||
if (!couldBeAsc && !couldBeDesc) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (couldBeAsc) return 'asc';
|
|
||||||
if (couldBeDesc) return 'desc';
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const windowSize = Math.min(Math.max(effectivePageSize.value + 1, 25), 200);
|
|
||||||
const headEnd = Math.min(src.length - 1, windowSize);
|
|
||||||
const tailStart = Math.max(1, src.length - windowSize);
|
|
||||||
|
|
||||||
const headOrder = detectOrderInRange(1, headEnd);
|
|
||||||
const tailOrder = detectOrderInRange(tailStart, src.length - 1);
|
|
||||||
|
|
||||||
if (headOrder && tailOrder && headOrder !== tailOrder) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return tailOrder || headOrder;
|
|
||||||
};
|
|
||||||
|
|
||||||
const throttledData = ref(asRawArray(data.value));
|
const throttledData = ref(asRawArray(data.value));
|
||||||
const throttledFilters = ref(filters.value);
|
const throttledFilters = ref(filters.value);
|
||||||
const throttledSortData = ref({ ...sortData.value });
|
|
||||||
const throttledCreatedAtOrder = ref(null);
|
|
||||||
|
|
||||||
let throttleTimerId = null;
|
let throttleTimerId = null;
|
||||||
const syncThrottledInputs = () => {
|
const syncThrottledInputs = () => {
|
||||||
throttleTimerId = null;
|
throttleTimerId = null;
|
||||||
throttledData.value = asRawArray(data.value);
|
throttledData.value = asRawArray(data.value);
|
||||||
throttledFilters.value = Array.isArray(filters.value) ? filters.value.slice() : filters.value;
|
throttledFilters.value = Array.isArray(filters.value) ? filters.value.slice() : filters.value;
|
||||||
throttledSortData.value = { ...sortData.value };
|
|
||||||
|
|
||||||
const sort = throttledSortData.value;
|
|
||||||
const shouldCheckFastPath =
|
|
||||||
showPagination.value &&
|
|
||||||
!hasActiveFilters(throttledFilters.value) &&
|
|
||||||
sort?.prop === 'created_at' &&
|
|
||||||
sort?.order === 'descending';
|
|
||||||
throttledCreatedAtOrder.value = shouldCheckFastPath ? detectCreatedAtOrder(throttledData.value) : null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheduleThrottledSync = () => {
|
const scheduleThrottledSync = () => {
|
||||||
@@ -178,7 +117,6 @@
|
|||||||
watch(data, scheduleThrottledSync);
|
watch(data, scheduleThrottledSync);
|
||||||
watch(() => (Array.isArray(data.value) ? data.value.length : 0), scheduleThrottledSync);
|
watch(() => (Array.isArray(data.value) ? data.value.length : 0), scheduleThrottledSync);
|
||||||
watch(filters, scheduleThrottledSync, { deep: true });
|
watch(filters, scheduleThrottledSync, { deep: true });
|
||||||
watch(sortData, scheduleThrottledSync, { deep: true });
|
|
||||||
watch(effectivePageSize, scheduleThrottledSync);
|
watch(effectivePageSize, scheduleThrottledSync);
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -188,32 +126,29 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const canUseFastCreatedAtDescPagination = computed(() => {
|
const resolvedDefaultSort = computed(() => {
|
||||||
if (!showPagination.value) return false;
|
if (props.tableProps?.defaultSort === null) {
|
||||||
if (!throttledCreatedAtOrder.value) return false;
|
return undefined;
|
||||||
|
|
||||||
const activeFilters = throttledFilters.value;
|
|
||||||
if (hasActiveFilters(activeFilters)) return false;
|
|
||||||
|
|
||||||
const sort = throttledSortData.value;
|
|
||||||
return sort?.prop === 'created_at' && sort?.order === 'descending';
|
|
||||||
});
|
|
||||||
const hasAnyNonNullSortValue = (rows, prop) => {
|
|
||||||
if (!Array.isArray(rows) || rows.length === 0) return false;
|
|
||||||
const sample = Math.min(rows.length, 50);
|
|
||||||
for (let i = 0; i < sample; i++) {
|
|
||||||
const value = rows[i]?.[prop];
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return (
|
||||||
};
|
props.tableProps?.defaultSort ?? {
|
||||||
|
prop: 'created_at',
|
||||||
|
order: 'descending'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const mergedTableProps = computed(() => ({
|
const mergedTableProps = computed(() => {
|
||||||
stripe: true,
|
const src = tableProps.value || {};
|
||||||
...tableProps.value
|
const rest = { ...src };
|
||||||
}));
|
if ('defaultSort' in rest) {
|
||||||
|
delete rest.defaultSort;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
stripe: true,
|
||||||
|
...rest
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const mergedPaginationProps = computed(() => ({
|
const mergedPaginationProps = computed(() => ({
|
||||||
layout: 'sizes, prev, pager, next, total',
|
layout: 'sizes, prev, pager, next, total',
|
||||||
@@ -236,55 +171,9 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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(() => {
|
const filteredData = computed(() => {
|
||||||
let result = throttledData.value;
|
let result = throttledData.value.slice();
|
||||||
const activeFilters = throttledFilters.value;
|
const activeFilters = throttledFilters.value;
|
||||||
const activeSort = throttledSortData.value;
|
|
||||||
|
|
||||||
if (activeFilters && Array.isArray(activeFilters) && activeFilters.length > 0) {
|
if (activeFilters && Array.isArray(activeFilters) && activeFilters.length > 0) {
|
||||||
activeFilters.forEach((filter) => {
|
activeFilters.forEach((filter) => {
|
||||||
@@ -299,65 +188,35 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeSort?.prop && activeSort?.order) {
|
|
||||||
if (!hasAnyNonNullSortValue(result, activeSort.prop)) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (result === throttledData.value) {
|
|
||||||
result = [...result];
|
|
||||||
}
|
|
||||||
sortRows(result, activeSort);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
filteredData,
|
||||||
|
(value) => {
|
||||||
|
emit('filtered-data', value);
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
const paginatedData = computed(() => {
|
const paginatedData = computed(() => {
|
||||||
if (!showPagination.value) {
|
if (!showPagination.value) {
|
||||||
return filteredData.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;
|
|
||||||
if (throttledCreatedAtOrder.value === 'desc') {
|
|
||||||
for (let idx = startOffset; idx < endOffset; idx++) {
|
|
||||||
if (idx >= src.length) break;
|
|
||||||
page.push(src[idx]);
|
|
||||||
}
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
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 start = (internalCurrentPage.value - 1) * effectivePageSize.value;
|
||||||
const end = start + effectivePageSize.value;
|
const end = start + effectivePageSize.value;
|
||||||
return filteredData.value.slice(start, end);
|
return filteredData.value.slice(start, end);
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalItems = computed(() => {
|
const totalItems = computed(() => {
|
||||||
const length = canUseFastCreatedAtDescPagination.value
|
const length = filteredData.value.length;
|
||||||
? Array.isArray(throttledData.value)
|
|
||||||
? throttledData.value.length
|
|
||||||
: 0
|
|
||||||
: filteredData.value.length;
|
|
||||||
const max = vrcxStore.maxTableSize;
|
const max = vrcxStore.maxTableSize;
|
||||||
return length > max && length < max + 51 ? max : length;
|
return length > max && length < max + 51 ? max : length;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSortChange = ({ prop, order }) => {
|
const handleSortChange = ({ prop, order }) => {
|
||||||
sortData.value = { prop, order };
|
emit('sort-change', { prop, order });
|
||||||
emit('sort-change', sortData.value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectionChange = (selection) => {
|
const handleSelectionChange = (selection) => {
|
||||||
@@ -393,19 +252,6 @@
|
|||||||
watch(pageSize, (newVal) => {
|
watch(pageSize, (newVal) => {
|
||||||
internalPageSize.value = 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>
|
||||||
|
|||||||
@@ -27,10 +27,7 @@ export const useFeedStore = defineStore('Feed', () => {
|
|||||||
tableProps: {
|
tableProps: {
|
||||||
stripe: true,
|
stripe: true,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
defaultSort: {
|
defaultSort: null,
|
||||||
prop: 'created_at',
|
|
||||||
order: 'descending'
|
|
||||||
},
|
|
||||||
rowKey: (row) =>
|
rowKey: (row) =>
|
||||||
`${row.type}:${row.rowId ?? row.uid}:${row.created_at ?? ''}`
|
`${row.type}:${row.rowId ?? row.uid}:${row.created_at ?? ''}`
|
||||||
},
|
},
|
||||||
@@ -191,7 +188,10 @@ export const useFeedStore = defineStore('Feed', () => {
|
|||||||
if (!feedSearch(feed)) {
|
if (!feedSearch(feed)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
feedTable.value.data.push({ ...feed, uid: crypto.randomUUID() });
|
feedTable.value.data.push({
|
||||||
|
...feed,
|
||||||
|
uid: crypto.randomUUID()
|
||||||
|
});
|
||||||
sweepFeed();
|
sweepFeed();
|
||||||
UiStore.notifyMenu('feed');
|
UiStore.notifyMenu('feed');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,10 +90,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
tableProps: {
|
tableProps: {
|
||||||
stripe: true,
|
stripe: true,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
defaultSort: {
|
defaultSort: null
|
||||||
prop: 'created_at',
|
|
||||||
order: 'descending'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
pageSizeLinked: true,
|
pageSizeLinked: true,
|
||||||
|
|||||||
@@ -66,10 +66,7 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
tableProps: {
|
tableProps: {
|
||||||
stripe: true,
|
stripe: true,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
defaultSort: {
|
defaultSort: null,
|
||||||
prop: 'created_at',
|
|
||||||
order: 'descending'
|
|
||||||
},
|
|
||||||
rowKey: (row) =>
|
rowKey: (row) =>
|
||||||
`${row.type}:${row.rowId ?? row.uid ?? row.displayName + row.location + row.time}:${row.created_at ?? ''}`
|
`${row.type}:${row.rowId ?? row.uid ?? row.displayName + row.location + row.time}:${row.created_at ?? ''}`
|
||||||
},
|
},
|
||||||
@@ -398,7 +395,10 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
if (!gameLogSearch(entry)) {
|
if (!gameLogSearch(entry)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
gameLogTable.value.data.push({ ...entry, uid: crypto.randomUUID() });
|
gameLogTable.value.data.push({
|
||||||
|
...entry,
|
||||||
|
uid: crypto.randomUUID()
|
||||||
|
});
|
||||||
sweepGameLog();
|
sweepGameLog();
|
||||||
uiStore.notifyMenu('game-log');
|
uiStore.notifyMenu('game-log');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,7 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
tableProps: {
|
tableProps: {
|
||||||
stripe: true,
|
stripe: true,
|
||||||
size: 'small',
|
size: 'small',
|
||||||
defaultSort: {
|
defaultSort: null
|
||||||
prop: 'created_at',
|
|
||||||
order: 'descending'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
pageSizeLinked: true,
|
pageSizeLinked: true,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
@change="feedTableLookup"></el-input>
|
@change="feedTableLookup"></el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-bind="feedTable">
|
<DataTable v-bind="feedTable" :data="feedDisplayData">
|
||||||
<el-table-column type="expand" width="20">
|
<el-table-column type="expand" width="20">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div style="position: relative; font-size: 14px">
|
<div style="position: relative; font-size: 14px">
|
||||||
@@ -211,6 +211,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Right } from '@element-plus/icons-vue';
|
import { Right } from '@element-plus/icons-vue';
|
||||||
|
import { computed } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -221,6 +222,8 @@
|
|||||||
const { feedTable } = storeToRefs(useFeedStore());
|
const { feedTable } = storeToRefs(useFeedStore());
|
||||||
const { feedTableLookup } = useFeedStore();
|
const { feedTableLookup } = useFeedStore();
|
||||||
|
|
||||||
|
const feedDisplayData = computed(() => feedTable.value.data.slice().reverse());
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
style="flex: 0.4; margin-left: 10px" />
|
style="flex: 0.4; margin-left: 10px" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-bind="friendLogTable">
|
<DataTable v-bind="friendLogTable" :data="friendLogDisplayData">
|
||||||
<el-table-column :label="t('table.friendLog.date')" prop="created_at" :sortable="true" width="200">
|
<el-table-column :label="t('table.friendLog.date')" prop="created_at" width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tooltip placement="right">
|
<el-tooltip placement="right">
|
||||||
<template #content>
|
<template #content>
|
||||||
@@ -90,10 +90,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Close, Delete, Right } from '@element-plus/icons-vue';
|
import { Close, Delete, Right } from '@element-plus/icons-vue';
|
||||||
|
import { computed, watch } from 'vue';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { watch } from 'vue';
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { useAppearanceSettingsStore, useFriendStore, useUiStore, useUserStore } from '../../stores';
|
import { useAppearanceSettingsStore, useFriendStore, useUiStore, useUserStore } from '../../stores';
|
||||||
import { formatDateFilter, removeFromArray } from '../../shared/utils';
|
import { formatDateFilter, removeFromArray } from '../../shared/utils';
|
||||||
@@ -106,6 +108,23 @@
|
|||||||
const { friendLogTable } = storeToRefs(useFriendStore());
|
const { friendLogTable } = storeToRefs(useFriendStore());
|
||||||
const { shiftHeld } = storeToRefs(useUiStore());
|
const { shiftHeld } = storeToRefs(useUiStore());
|
||||||
|
|
||||||
|
const friendLogDisplayData = computed(() => {
|
||||||
|
const data = friendLogTable.value.data;
|
||||||
|
return data.slice().sort((a, b) => {
|
||||||
|
const aTime = typeof a?.created_at === 'string' ? a.created_at : '';
|
||||||
|
const bTime = typeof b?.created_at === 'string' ? b.created_at : '';
|
||||||
|
const aTs = dayjs(aTime).valueOf();
|
||||||
|
const bTs = dayjs(bTime).valueOf();
|
||||||
|
if (Number.isFinite(aTs) && Number.isFinite(bTs) && aTs !== bTs) {
|
||||||
|
return bTs - aTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aId = typeof a?.rowId === 'number' ? a.rowId : 0;
|
||||||
|
const bId = typeof b?.rowId === 'number' ? b.rowId : 0;
|
||||||
|
return bId - aId;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => hideUnfriends.value,
|
() => hideUnfriends.value,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
@change="gameLogTableLookup"></el-input>
|
@change="gameLogTableLookup"></el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-bind="gameLogTable">
|
<DataTable v-bind="gameLogTable" :data="gameLogDisplayData">
|
||||||
<el-table-column :label="t('table.gameLog.date')" prop="created_at" width="130">
|
<el-table-column :label="t('table.gameLog.date')" prop="created_at" width="130">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<NativeTooltip placement="right">
|
<NativeTooltip placement="right">
|
||||||
@@ -184,9 +184,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Close, DataLine, Delete } from '@element-plus/icons-vue';
|
import { Close, DataLine, Delete } from '@element-plus/icons-vue';
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
|
import { computed } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import { useGameLogStore, useInstanceStore, useUiStore, useUserStore, useWorldStore } from '../../stores';
|
import { useGameLogStore, useInstanceStore, useUiStore, useUserStore, useWorldStore } from '../../stores';
|
||||||
import { formatDateFilter, openExternalLink, removeFromArray } from '../../shared/utils';
|
import { formatDateFilter, openExternalLink, removeFromArray } from '../../shared/utils';
|
||||||
import { database } from '../../service/database';
|
import { database } from '../../service/database';
|
||||||
@@ -200,6 +203,52 @@
|
|||||||
const { gameLogTable } = storeToRefs(useGameLogStore());
|
const { gameLogTable } = storeToRefs(useGameLogStore());
|
||||||
const { updateSharedFeed } = useSharedFeedStore();
|
const { updateSharedFeed } = useSharedFeedStore();
|
||||||
|
|
||||||
|
function getGameLogCreatedAt(row) {
|
||||||
|
if (typeof row?.created_at === 'string' && row.created_at.length > 0) {
|
||||||
|
return row.created_at;
|
||||||
|
}
|
||||||
|
if (typeof row?.createdAt === 'string' && row.createdAt.length > 0) {
|
||||||
|
return row.createdAt;
|
||||||
|
}
|
||||||
|
if (typeof row?.dt === 'string' && row.dt.length > 0) {
|
||||||
|
return row.dt;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGameLogCreatedAtTs(row) {
|
||||||
|
const createdAtRaw = row?.created_at ?? row?.createdAt ?? row?.dt;
|
||||||
|
if (typeof createdAtRaw === 'number') {
|
||||||
|
const ts = createdAtRaw > 1_000_000_000_000 ? createdAtRaw : createdAtRaw * 1000;
|
||||||
|
return Number.isFinite(ts) ? ts : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAt = getGameLogCreatedAt(row);
|
||||||
|
const ts = dayjs(createdAt).valueOf();
|
||||||
|
return Number.isFinite(ts) ? ts : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gameLogDisplayData = computed(() => {
|
||||||
|
const data = gameLogTable.value.data;
|
||||||
|
return data.slice().sort((a, b) => {
|
||||||
|
const aTs = getGameLogCreatedAtTs(a);
|
||||||
|
const bTs = getGameLogCreatedAtTs(b);
|
||||||
|
if (aTs !== bTs) {
|
||||||
|
return bTs - aTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aRowId = typeof a?.rowId === 'number' ? a.rowId : 0;
|
||||||
|
const bRowId = typeof b?.rowId === 'number' ? b.rowId : 0;
|
||||||
|
if (aRowId !== bRowId) {
|
||||||
|
return bRowId - aRowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aUid = typeof a?.uid === 'string' ? a.uid : '';
|
||||||
|
const bUid = typeof b?.uid === 'string' ? b.uid : '';
|
||||||
|
return aUid < bUid ? 1 : aUid > bUid ? -1 : 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const emit = defineEmits(['updateGameLogSessionTable']);
|
const emit = defineEmits(['updateGameLogSessionTable']);
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,11 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DataTable v-bind="notificationTable" ref="notificationTableRef" class="notification-table">
|
<DataTable
|
||||||
|
v-bind="notificationTable"
|
||||||
|
:data="notificationDisplayData"
|
||||||
|
ref="notificationTableRef"
|
||||||
|
class="notification-table">
|
||||||
<el-table-column :label="t('table.notification.date')" prop="created_at" width="130">
|
<el-table-column :label="t('table.notification.date')" prop="created_at" width="130">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tooltip placement="right">
|
<el-tooltip placement="right">
|
||||||
@@ -403,11 +407,12 @@
|
|||||||
Refresh
|
Refresh
|
||||||
} from '@element-plus/icons-vue';
|
} from '@element-plus/icons-vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import Noty from 'noty';
|
import Noty from 'noty';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useGalleryStore,
|
useGalleryStore,
|
||||||
@@ -451,6 +456,42 @@
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
function getNotificationCreatedAt(row) {
|
||||||
|
if (typeof row?.created_at === 'string' && row.created_at.length > 0) {
|
||||||
|
return row.created_at;
|
||||||
|
}
|
||||||
|
if (typeof row?.createdAt === 'string' && row.createdAt.length > 0) {
|
||||||
|
return row.createdAt;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNotificationCreatedAtTs(row) {
|
||||||
|
const createdAtRaw = row?.created_at ?? row?.createdAt;
|
||||||
|
if (typeof createdAtRaw === 'number') {
|
||||||
|
const ts = createdAtRaw > 1_000_000_000_000 ? createdAtRaw : createdAtRaw * 1000;
|
||||||
|
return Number.isFinite(ts) ? ts : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdAt = getNotificationCreatedAt(row);
|
||||||
|
const ts = dayjs(createdAt).valueOf();
|
||||||
|
return Number.isFinite(ts) ? ts : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationDisplayData = computed(() => {
|
||||||
|
return notificationTable.value.data.slice().sort((a, b) => {
|
||||||
|
const aTs = getNotificationCreatedAtTs(a);
|
||||||
|
const bTs = getNotificationCreatedAtTs(b);
|
||||||
|
if (aTs !== bTs) {
|
||||||
|
return bTs - aTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aId = typeof a?.id === 'string' ? a.id : '';
|
||||||
|
const bId = typeof b?.id === 'string' ? b.id : '';
|
||||||
|
return aId < bId ? 1 : aId > bId ? -1 : 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const sendInviteResponseDialog = ref({
|
const sendInviteResponseDialog = ref({
|
||||||
messageSlot: {},
|
messageSlot: {},
|
||||||
invite: {}
|
invite: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user