优化筛选, 增加记住筛选功能

This commit is contained in:
yml2213
2026-06-13 15:46:47 +08:00
parent 9b41b9dc84
commit 55326b7d06
4 changed files with 317 additions and 15 deletions
@@ -0,0 +1,54 @@
import { describe, expect, it } from 'vitest'
import type { LocationQuery } from 'vue-router'
import {
decodeHomeQueryJson,
encodeHomeQueryJson,
isSameQuery,
mergeHomeQuery,
readQueryNumber,
readQueryString,
} from '@/features/listings/composables/useHomeFilterQuery'
describe('useHomeFilterQuery', () => {
it('应该从路由 query 中读取字符串和数字', () => {
const query: LocationQuery = {
keyword: ['测试账号'],
min_coin: '100',
max_coin: 'abc',
}
expect(readQueryString(query, 'keyword')).toBe('测试账号')
expect(readQueryNumber(query, 'min_coin')).toBe(100)
expect(readQueryNumber(query, 'max_coin')).toBeUndefined()
})
it('应该合并新筛选并移除旧的托管参数', () => {
const query = mergeHomeQuery(
{ keyword: '旧关键词', page: '2', region: '上海' },
['keyword', 'region', 'sort'],
{ keyword: '新关键词', sort: undefined }
)
expect(query).toEqual({
page: '2',
keyword: '新关键词',
})
})
it('应该判断 query 是否语义一致', () => {
expect(isSameQuery({ keyword: '测试', empty: '' }, { empty: undefined, keyword: '测试' })).toBe(
true
)
})
it('应该安全编码和恢复移动端动态筛选', () => {
const filters = { server: ['微信区'], operatorRed: ['红皮肤'] }
const encoded = encodeHomeQueryJson(filters)
expect(encoded).toBe(JSON.stringify(filters))
expect(decodeHomeQueryJson({ filters: encoded }, 'filters', {})).toEqual(filters)
expect(decodeHomeQueryJson({ filters: '{' }, 'filters', { server: [] })).toEqual({
server: [],
})
})
})
@@ -0,0 +1,105 @@
import type { LocationQuery, LocationQueryRaw } from 'vue-router'
export const desktopHomeFilterQueryKeys = [
'keyword',
'sort',
'zone',
'server',
'region',
'login_method',
'rank',
'insurance',
'stamina',
'load',
'skin_group',
'skin_name',
'min_coin',
'max_coin',
'min_price',
'max_price',
'min_deposit',
'max_deposit',
'min_total',
'max_total',
'min_fire_level',
'max_fire_level',
] as const
export const mobileHomeFilterQueryKeys = ['keyword', 'sort', 'zone', 'filters', 'ranges'] as const
export type HomeQueryValue = string | number | undefined
export function readQueryString(query: LocationQuery, key: string) {
const value = query[key]
if (Array.isArray(value)) return String(value[0] || '')
return typeof value === 'string' ? value : ''
}
export function readQueryNumber(query: LocationQuery, key: string) {
const value = readQueryString(query, key)
if (value === '') return undefined
const number = Number(value)
return Number.isFinite(number) ? number : undefined
}
export function mergeHomeQuery(
currentQuery: LocationQuery,
managedKeys: readonly string[],
nextValues: Record<string, HomeQueryValue>
): LocationQueryRaw {
const managedKeySet = new Set(managedKeys)
const nextQuery: LocationQueryRaw = {}
for (const [key, value] of Object.entries(currentQuery)) {
if (!managedKeySet.has(key)) nextQuery[key] = value
}
for (const [key, value] of Object.entries(nextValues)) {
if (value === undefined || value === '') continue
nextQuery[key] = String(value)
}
return nextQuery
}
export function isSameQuery(left: LocationQuery, right: LocationQueryRaw) {
return stableQuerySignature(left) === stableQuerySignature(right)
}
export function encodeHomeQueryJson(value: unknown) {
if (!hasMeaningfulValue(value)) return undefined
return JSON.stringify(value)
}
export function decodeHomeQueryJson<T>(query: LocationQuery, key: string, fallback: T): T {
const raw = readQueryString(query, key)
if (!raw) return fallback
try {
const parsed = JSON.parse(raw)
return parsed && typeof parsed === 'object' ? (parsed as T) : fallback
} catch {
return fallback
}
}
function stableQuerySignature(query: LocationQuery | LocationQueryRaw) {
const entries: Array<[string, string | string[]]> = Object.entries(query)
.filter(([, value]) => value !== undefined && value !== '')
.map(([key, value]) => [
key,
Array.isArray(value) ? value.map(item => String(item ?? '')) : String(value ?? ''),
])
return JSON.stringify(entries.sort(([leftKey], [rightKey]) => leftKey.localeCompare(rightKey)))
}
function hasMeaningfulValue(value: unknown): boolean {
if (!value || typeof value !== 'object') return false
if (Array.isArray(value)) return value.length > 0
return Object.values(value).some(item => {
if (Array.isArray(item)) return item.length > 0
if (!item || typeof item !== 'object') return item !== '' && item !== undefined
return Object.values(item).some(nested => nested !== '' && nested !== undefined)
})
}