fix: 修复用户列表使用时间排序冲突

This commit is contained in:
shaw
2026-06-30 13:36:36 +08:00
parent dcd8689d78
commit bf4f006736
4 changed files with 210 additions and 19 deletions
+28 -14
View File
@@ -76,6 +76,7 @@
v-for="(column, index) in columns"
:key="column.key"
scope="col"
:aria-sort="column.sortable ? getColumnAriaSort(column.key) : undefined"
:class="[
'sticky-header-cell py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dark-400',
getAdaptivePaddingClass(),
@@ -93,24 +94,26 @@
>
<div class="flex items-center space-x-1">
<span>{{ column.label }}</span>
<span v-if="column.sortable" class="text-gray-400 dark:text-dark-500">
<span
v-if="column.sortable"
class="inline-flex h-5 w-4 flex-col items-center justify-center"
aria-hidden="true"
>
<svg
v-if="sortKey === column.key"
class="h-4 w-4"
:class="{ 'rotate-180 transform': sortOrder === 'desc' }"
class="h-2.5 w-2.5"
:class="getSortIndicatorClass(column.key, 'asc')"
fill="currentColor"
viewBox="0 0 20 20"
viewBox="0 0 10 10"
>
<path
fill-rule="evenodd"
d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z"
clip-rule="evenodd"
/>
<path d="M5 2L1.5 6.5h7L5 2z" />
</svg>
<svg v-else class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
<path
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
/>
<svg
class="-mt-0.5 h-2.5 w-2.5"
:class="getSortIndicatorClass(column.key, 'desc')"
fill="currentColor"
viewBox="0 0 10 10"
>
<path d="M5 8L1.5 3.5h7L5 8z" />
</svg>
</span>
</div>
@@ -464,6 +467,17 @@ const applySortState = (state: PersistedSortState | null) => {
sortOrder.value = state.order
}
const getSortIndicatorClass = (key: string, order: 'asc' | 'desc') => {
return sortKey.value === key && sortOrder.value === order
? 'text-primary-600 dark:text-primary-400'
: 'text-gray-300 transition-colors dark:text-dark-500'
}
const getColumnAriaSort = (key: string) => {
if (sortKey.value !== key) return 'none'
return sortOrder.value === 'asc' ? 'ascending' : 'descending'
}
const isNullishOrEmpty = (value: any) => value === null || value === undefined || value === ''
const toFiniteNumberOrNull = (value: any): number | null => {
@@ -0,0 +1,65 @@
import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import DataTable from '../DataTable.vue'
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key
})
}))
const stubDesktopMatchMedia = () => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: true,
media: query,
onchange: null,
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
addListener: vi.fn(),
removeListener: vi.fn(),
dispatchEvent: vi.fn()
}))
})
}
describe('DataTable', () => {
beforeEach(() => {
stubDesktopMatchMedia()
localStorage.clear()
})
it('renders paired sort arrows and highlights the active direction', async () => {
const wrapper = mount(DataTable, {
props: {
columns: [
{ key: 'name', label: 'Name', sortable: true },
{ key: 'created_at', label: 'Created', sortable: true }
],
data: [
{ id: 1, name: 'Beta', created_at: '2026-01-02T00:00:00Z' },
{ id: 2, name: 'Alpha', created_at: '2026-01-01T00:00:00Z' }
],
defaultSortKey: 'name',
defaultSortOrder: 'asc'
}
})
await wrapper.vm.$nextTick()
const nameHeader = wrapper.findAll('th')[0]
expect(nameHeader.attributes('aria-sort')).toBe('ascending')
expect(nameHeader.findAll('svg')).toHaveLength(2)
expect(nameHeader.findAll('svg')[0].classes()).toContain('text-primary-600')
expect(nameHeader.findAll('svg')[1].classes()).toContain('text-gray-300')
await nameHeader.trigger('click')
await wrapper.vm.$nextTick()
expect(nameHeader.attributes('aria-sort')).toBe('descending')
expect(nameHeader.findAll('svg')[0].classes()).toContain('text-gray-300')
expect(nameHeader.findAll('svg')[1].classes()).toContain('text-primary-600')
})
})