From 58b5d9d7ded29e556c0c4db0a09b030258380cf7 Mon Sep 17 00:00:00 2001 From: wuzilong <704541015@qq.com> Date: Tue, 8 Sep 2026 21:47:37 +0800 Subject: [PATCH] feat(web-admin): add useColumnSort for CRM date headers. Co-authored-by: Cursor --- .../src/composables/useColumnSort.test.ts | 29 +++++++ web-admin/src/composables/useColumnSort.ts | 77 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 web-admin/src/composables/useColumnSort.test.ts create mode 100644 web-admin/src/composables/useColumnSort.ts diff --git a/web-admin/src/composables/useColumnSort.test.ts b/web-admin/src/composables/useColumnSort.test.ts new file mode 100644 index 000000000..63afd0662 --- /dev/null +++ b/web-admin/src/composables/useColumnSort.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest' +import { nextSortState } from './useColumnSort' + +describe('nextSortState', () => { + it('sets asc when empty', () => { + expect(nextSortState(null, null, 'createTime', 'asc')).toEqual({ + sortField: 'createTime', + sortOrder: 'asc' + }) + }) + it('clears when same field and same order', () => { + expect(nextSortState('createTime', 'asc', 'createTime', 'asc')).toEqual({ + sortField: null, + sortOrder: null + }) + }) + it('switches order on same field', () => { + expect(nextSortState('createTime', 'asc', 'createTime', 'desc')).toEqual({ + sortField: 'createTime', + sortOrder: 'desc' + }) + }) + it('replaces field', () => { + expect(nextSortState('createTime', 'asc', 'dealTime', 'desc')).toEqual({ + sortField: 'dealTime', + sortOrder: 'desc' + }) + }) +}) diff --git a/web-admin/src/composables/useColumnSort.ts b/web-admin/src/composables/useColumnSort.ts new file mode 100644 index 000000000..81f0ef568 --- /dev/null +++ b/web-admin/src/composables/useColumnSort.ts @@ -0,0 +1,77 @@ +import { computed, ref, watch, type Ref } from 'vue' + +export type SortOrder = 'asc' | 'desc' + +export function nextSortState( + currentField: string | null, + currentOrder: SortOrder | null, + prop: string, + order: SortOrder +): { sortField: string | null; sortOrder: SortOrder | null } { + if (currentField === prop && currentOrder === order) { + return { sortField: null, sortOrder: null } + } + return { sortField: prop, sortOrder: order } +} + +export function useColumnSort(options?: { onChange?: () => void }) { + const sortField = ref(null) + const sortOrder = ref(null) + + const hasActiveSort = computed(() => Boolean(sortField.value && sortOrder.value)) + + function setSort(prop: string, order: SortOrder) { + const next = nextSortState(sortField.value, sortOrder.value, prop, order) + sortField.value = next.sortField + sortOrder.value = next.sortOrder + options?.onChange?.() + } + + function clearSort() { + if (!sortField.value && !sortOrder.value) return + sortField.value = null + sortOrder.value = null + options?.onChange?.() + } + + function isSortActive(prop: string, order?: SortOrder) { + if (!sortField.value || sortField.value !== prop) return false + if (order == null) return true + return sortOrder.value === order + } + + function toQueryParams(): Record { + if (!sortField.value || !sortOrder.value) return {} + return { sortField: sortField.value, sortOrder: sortOrder.value } + } + + function onColumnHidden(prop: string) { + if (sortField.value === prop) clearSort() + } + + function watchVisibleColumns(visibleProps: Ref) { + watch( + visibleProps, + (next, prev) => { + if (!prev) return + const nextSet = new Set(next || []) + for (const p of prev) { + if (!nextSet.has(p)) onColumnHidden(p) + } + }, + { deep: true } + ) + } + + return { + sortField, + sortOrder, + hasActiveSort, + setSort, + clearSort, + isSortActive, + toQueryParams, + onColumnHidden, + watchVisibleColumns + } +}