From 03d20223c8e057ed8f09ea60860696489912a8eb Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 10 Oct 2026 16:48:16 +0800 Subject: [PATCH] =?UTF-8?q?refactor(listings):=20=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E4=B8=8EPC=E7=AB=AF=E7=AD=9B?= =?UTF-8?q?=E9=80=89=E9=80=BB=E8=BE=91=E5=B9=B6=E6=94=AF=E6=8C=81=E6=97=A5?= =?UTF-8?q?=E6=B6=88=E8=80=97=E4=B8=8E=E7=BB=9D=E5=AF=86KD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一预设体系:在 useFilterOptions.ts 集中导出 defaultRangePresets,两端共享档位定义,消除移动端私有硬编码档位 - 统一参数模型:在 useHomeFilters.ts 增加 mobileFiltersToHomeFilters,将移动端字典结构标准化转换为领域层 HomeFilters 模型 - 统一查询构建:在 useListingQuery.ts 抽象纯函数 buildListingQueryFromFilters,双端共用同一个后端发包构建器 - 功能对齐:移动端首页完整补充「日消耗」区间筛选;PC端高级抽屉对齐补充「绝密KD」筛选 - 验证通过:补充双端测试用例,全站门禁 ./scripts/check.sh(SQL/Go/MySQL/Vitest/Vite构建)全部绿灯 --- .../listings/components/HomeFilters.vue | 21 +++ .../__tests__/useFilterOptions.spec.ts | 30 ++++ .../__tests__/useHomeFilters.spec.ts | 112 ++++++++++++++- .../listings/composables/useFilterOptions.ts | 38 +++++ .../listings/composables/useHomeFilters.ts | 119 ++++++++++++++++ .../listings/composables/useListingQuery.ts | 95 ++++++++----- .../listings/views/MobileHomeView.vue | 134 +++--------------- 7 files changed, 403 insertions(+), 146 deletions(-) diff --git a/frontend/src/features/listings/components/HomeFilters.vue b/frontend/src/features/listings/components/HomeFilters.vue index e3ee21b..c935ccb 100644 --- a/frontend/src/features/listings/components/HomeFilters.vue +++ b/frontend/src/features/listings/components/HomeFilters.vue @@ -11,6 +11,7 @@ import { totalRangeOptions, fireLevelRangeOptions, dailyLossRangeOptions, + secretKdRangeOptions, isRangeSelected, } from '@/features/listings/composables/useFilterOptions' import type { ListingPublishOptions } from '@/features/listings/api/listingOptions' @@ -72,6 +73,7 @@ function toggleRange( | 'minTotal' | 'minFireLevel' | 'minDailyLoss' + | 'minSecretKd' | 'minAwmAmmo', maxKey: | 'maxCoin' @@ -81,6 +83,7 @@ function toggleRange( | 'maxTotal' | 'maxFireLevel' | 'maxDailyLoss' + | 'maxSecretKd' | 'maxAwmAmmo', option: RangeOption ) { @@ -102,6 +105,7 @@ function isRangeActive( | 'minTotal' | 'minFireLevel' | 'minDailyLoss' + | 'minSecretKd' | 'minAwmAmmo', maxKey: | 'maxCoin' @@ -111,6 +115,7 @@ function isRangeActive( | 'maxTotal' | 'maxFireLevel' | 'maxDailyLoss' + | 'maxSecretKd' | 'maxAwmAmmo', option: RangeOption ) { @@ -375,6 +380,22 @@ const redSkinGroup = computed(() => +
+ +
+ +
+
+
diff --git a/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts b/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts index 51dcc30..57cbeb2 100644 --- a/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts +++ b/frontend/src/features/listings/composables/__tests__/useFilterOptions.spec.ts @@ -2,10 +2,12 @@ import { describe, it, expect } from 'vitest' import { awmRangeOptions, depositRangeOptions, + defaultRangePresets, isRangeSelected, moneyRangeOptions, rangeLabel, ratioRangeOptions, + secretKdRangeOptions, } from '@/features/listings/composables/useFilterOptions' describe('useFilterOptions', () => { @@ -65,4 +67,32 @@ describe('useFilterOptions', () => { max: undefined, }) }) + + it('应该包含绝密KD预设选项', () => { + expect(secretKdRangeOptions.map(option => option.label)).toEqual([ + '全部', + '1-2', + '2-3', + '3-5', + '5以上', + ]) + }) + + it('defaultRangePresets 应该为移动端提供统一的日消耗与绝密KD档位', () => { + expect(defaultRangePresets.dailyLoss).toEqual([ + { label: '≤10', min: '', max: '10' }, + { label: '10-20', min: '10', max: '20' }, + { label: '20-30', min: '20', max: '30' }, + { label: '30-40', min: '30', max: '40' }, + { label: '40以上', min: '40', max: '' }, + ]) + expect(defaultRangePresets.secretKd).toEqual([ + { label: '1-2', min: '1', max: '2' }, + { label: '2-3', min: '2', max: '3' }, + { label: '3-5', min: '3', max: '5' }, + { label: '5以上', min: '5', max: '' }, + ]) + expect(defaultRangePresets.price.length).toBeGreaterThan(0) + expect(defaultRangePresets.coin.length).toBeGreaterThan(0) + }) }) diff --git a/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts b/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts index 107ccc6..8c24642 100644 --- a/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts +++ b/frontend/src/features/listings/composables/__tests__/useHomeFilters.spec.ts @@ -1,5 +1,9 @@ import { describe, it, expect } from 'vitest' -import { useHomeFilters } from '@/features/listings/composables/useHomeFilters' +import { + useHomeFilters, + mobileFiltersToHomeFilters, +} from '@/features/listings/composables/useHomeFilters' +import { buildListingQueryFromFilters } from '@/features/listings/composables/useListingQuery' import { ref } from 'vue' import { emptyListingPublishOptions } from '@/features/listings/api/listingOptions' @@ -90,4 +94,110 @@ describe('useHomeFilters', () => { expect(filters.minCoin).toBe(100) expect(filters.maxCoin).toBe(500) }) + + it('应该支持绝密KD与日消耗并正确统计高级筛选数量', () => { + const publishOptions = ref(emptyListingPublishOptions) + const listings = ref([]) + + const { filters, advancedActiveCount, setDailyLossRange, setSecretKdRange, resetFilters } = + useHomeFilters(publishOptions, listings) + + expect(advancedActiveCount.value).toBe(0) + + setDailyLossRange(10, 20) + expect(filters.minDailyLoss).toBe(10) + expect(filters.maxDailyLoss).toBe(20) + expect(advancedActiveCount.value).toBe(1) + + setSecretKdRange(2, 3) + expect(filters.minSecretKd).toBe(2) + expect(filters.maxSecretKd).toBe(3) + expect(advancedActiveCount.value).toBe(2) + + resetFilters() + expect(filters.minDailyLoss).toBeUndefined() + expect(filters.maxDailyLoss).toBeUndefined() + expect(filters.minSecretKd).toBeUndefined() + expect(filters.maxSecretKd).toBeUndefined() + expect(advancedActiveCount.value).toBe(0) + }) + + describe('mobileFiltersToHomeFilters & buildListingQueryFromFilters', () => { + it('应该正确转换移动端结构为统一 HomeFilters 并生成对齐的查询参数', () => { + const selectedFilters = { + server: ['微信区'], + region: ['北京', '上海'], + login: ['账密'], + rank: ['大师'], + insurance: ['3*3'], + melee: ['小刀'], + } + const rangeFilters = { + price: { min: '50', max: '100' }, + coin: { min: '100', max: '300' }, + dailyLoss: { min: '10', max: '20' }, + secretKd: { min: '2', max: '3' }, + resource_awmAmmo: { min: '50', max: '100' }, + resource_testItem: { min: '1', max: '5' }, + } + + const homeFilters = mobileFiltersToHomeFilters(selectedFilters, rangeFilters, '搜索关键字', [ + 'melee', + ]) + + expect(homeFilters.keyword).toBe('搜索关键字') + expect(homeFilters.server).toBe('微信区') + expect(homeFilters.region).toEqual(['北京', '上海']) + expect(homeFilters.loginMethod).toEqual(['账密']) + expect(homeFilters.rank).toEqual(['大师']) + expect(homeFilters.insurance).toEqual(['3*3']) + expect(homeFilters.skinGroups).toEqual(['melee']) + expect(homeFilters.skinNames).toEqual(['小刀']) + expect(homeFilters.minPrice).toBe(50) + expect(homeFilters.maxPrice).toBe(100) + expect(homeFilters.minCoin).toBe(100) + expect(homeFilters.maxCoin).toBe(300) + expect(homeFilters.minDailyLoss).toBe(10) + expect(homeFilters.maxDailyLoss).toBe(20) + expect(homeFilters.minSecretKd).toBe(2) + expect(homeFilters.maxSecretKd).toBe(3) + expect(homeFilters.minAwmAmmo).toBe(50) + expect(homeFilters.maxAwmAmmo).toBe(100) + expect(homeFilters.resources).toEqual({ testItem: { min: 1, max: 5 } }) + + const query = buildListingQueryFromFilters(homeFilters, { + page: 1, + pageSize: 10, + sort: 'recommended', + zone: 'all', + }) + + expect(query).toMatchObject({ + page: 1, + page_size: 10, + keyword: '搜索关键字', + sort: 'recommended', + zone: 'all', + server: '微信区', + region: '北京,上海', + login_method: '账密', + rank: '大师', + insurance: '3*3', + skin_group: 'melee', + skin_name: '小刀', + min_price: 50, + max_price: 100, + min_coin: 100, + max_coin: 300, + min_daily_loss: 10, + max_daily_loss: 20, + min_secret_kd: 2, + max_secret_kd: 3, + resource_awmAmmo_min: 50, + resource_awmAmmo_max: 100, + resource_testItem_min: 1, + resource_testItem_max: 5, + }) + }) + }) }) diff --git a/frontend/src/features/listings/composables/useFilterOptions.ts b/frontend/src/features/listings/composables/useFilterOptions.ts index 3a2a51c..b1d6e90 100644 --- a/frontend/src/features/listings/composables/useFilterOptions.ts +++ b/frontend/src/features/listings/composables/useFilterOptions.ts @@ -71,6 +71,44 @@ export const dailyLossRangeOptions = [ { label: '40以上', min: 40, max: undefined }, ] +export const secretKdRangeOptions = [ + { label: '全部', min: undefined, max: undefined }, + { label: '1-2', min: 1, max: 2 }, + { label: '2-3', min: 2, max: 3 }, + { label: '3-5', min: 3, max: 5 }, + { label: '5以上', min: 5, max: undefined }, +] + +export interface RangePresetOption { + label: string + min: string + max: string +} + +export function toRangePresets( + options: Array<{ label: string; min?: number; max?: number }> +): RangePresetOption[] { + return options + .filter(option => option.label !== '全部') + .map(option => ({ + label: option.label, + min: option.min === undefined ? '' : String(option.min), + max: option.max === undefined ? '' : String(option.max), + })) +} + +export const defaultRangePresets: Record = { + price: toRangePresets(moneyRangeOptions), + coin: toRangePresets(coinRangeOptions), + ratio: toRangePresets(ratioRangeOptions), + deposit: toRangePresets(depositRangeOptions), + total: toRangePresets(totalRangeOptions), + fireLevel: toRangePresets(fireLevelRangeOptions), + dailyLoss: toRangePresets(dailyLossRangeOptions), + secretKd: toRangePresets(secretKdRangeOptions), + resource_awmAmmo: toRangePresets(awmRangeOptions), +} + export function rangeLabel(min: number | undefined, max: number | undefined, fallback: string) { if (min !== undefined && max !== undefined) return `${min}-${max}` if (min !== undefined) return `${min}+` diff --git a/frontend/src/features/listings/composables/useHomeFilters.ts b/frontend/src/features/listings/composables/useHomeFilters.ts index 270e0f8..7d0061b 100644 --- a/frontend/src/features/listings/composables/useHomeFilters.ts +++ b/frontend/src/features/listings/composables/useHomeFilters.ts @@ -55,6 +55,13 @@ export interface HomeFilters { /** 日消耗(M/天)区间 */ minDailyLoss: number | undefined maxDailyLoss: number | undefined + /** 绝密KD区间 */ + minSecretKd?: number | undefined + maxSecretKd?: number | undefined + /** 皮肤分类列表 */ + skinGroups?: string[] | undefined + /** 动态资源区间 */ + resources?: Record | undefined /** AWM 子弹数量区间 */ minAwmAmmo: number | undefined maxAwmAmmo: number | undefined @@ -66,6 +73,107 @@ export interface SkinFilterGroup { options: string[] } +function parseOptionalNumber(value: string | undefined): number | undefined { + if (!value || value.trim() === '') return undefined + const num = Number(value.trim()) + return Number.isFinite(num) ? num : undefined +} + +/** 将移动端筛选抽屉的 (selectedFilters, rangeFilters) 映射为统一的 HomeFilters 模型 */ +export function mobileFiltersToHomeFilters( + selectedFilters: Record, + rangeFilters: Record, + keyword = '', + knownSkinGroupKeys: string[] = [] +): HomeFilters { + const isSkinGroup = (key: string) => + knownSkinGroupKeys.includes(key) || key === 'melee' || key === 'operatorRed' || key === 'weapon' + + const skinGroups: string[] = [] + const skinNames: string[] = [] + let server = '' + const region: string[] = [] + const loginMethod: string[] = [] + const rank: string[] = [] + const insurance: string[] = [] + const stamina: string[] = [] + const load: string[] = [] + + for (const [key, values] of Object.entries(selectedFilters)) { + const validValues = values.map(v => v.trim()).filter(Boolean) + if (!validValues.length) continue + + if (key === 'server') { + server = validValues.join(',') + } else if (key === 'region') { + region.push(...validValues) + } else if (key === 'login') { + loginMethod.push(...validValues) + } else if (key === 'rank') { + rank.push(...validValues) + } else if (key === 'insurance') { + insurance.push(...validValues) + } else if (key === 'stamina') { + stamina.push(...validValues) + } else if (key === 'load') { + load.push(...validValues) + } else if (isSkinGroup(key)) { + skinGroups.push(key) + skinNames.push(...validValues) + } + } + + const resources: Record = {} + let minAwmAmmo: number | undefined + let maxAwmAmmo: number | undefined + + for (const [key, range] of Object.entries(rangeFilters)) { + if (key.startsWith('resource_')) { + const resKey = key.replace('resource_', '') + const min = parseOptionalNumber(range.min) + const max = parseOptionalNumber(range.max) + if (resKey === 'awmAmmo') { + minAwmAmmo = min + maxAwmAmmo = max + } else if (min !== undefined || max !== undefined) { + resources[resKey] = { min, max } + } + } + } + + return { + keyword, + server, + region, + loginMethod, + rank, + insurance, + stamina, + load, + skinNames, + skinGroups: skinGroups.length ? skinGroups : undefined, + resources: Object.keys(resources).length ? resources : undefined, + minCoin: parseOptionalNumber(rangeFilters.coin?.min), + maxCoin: parseOptionalNumber(rangeFilters.coin?.max), + minRatio: parseOptionalNumber(rangeFilters.ratio?.min), + maxRatio: parseOptionalNumber(rangeFilters.ratio?.max), + minPrice: parseOptionalNumber(rangeFilters.price?.min), + maxPrice: parseOptionalNumber(rangeFilters.price?.max), + minDeposit: parseOptionalNumber(rangeFilters.deposit?.min), + maxDeposit: parseOptionalNumber(rangeFilters.deposit?.max), + minTotal: parseOptionalNumber(rangeFilters.total?.min), + maxTotal: parseOptionalNumber(rangeFilters.total?.max), + minFireLevel: parseOptionalNumber(rangeFilters.fireLevel?.min), + maxFireLevel: parseOptionalNumber(rangeFilters.fireLevel?.max), + minDailyLoss: parseOptionalNumber(rangeFilters.dailyLoss?.min), + maxDailyLoss: parseOptionalNumber(rangeFilters.dailyLoss?.max), + minSecretKd: parseOptionalNumber(rangeFilters.secretKd?.min), + maxSecretKd: parseOptionalNumber(rangeFilters.secretKd?.max), + minAwmAmmo, + maxAwmAmmo, + } +} + export function useHomeFilters( publishOptions: Ref, listings: Ref @@ -94,6 +202,8 @@ export function useHomeFilters( maxFireLevel: undefined, minDailyLoss: undefined, maxDailyLoss: undefined, + minSecretKd: undefined, + maxSecretKd: undefined, minAwmAmmo: undefined, maxAwmAmmo: undefined, }) @@ -159,6 +269,7 @@ export function useHomeFilters( if (filters.minTotal !== undefined || filters.maxTotal !== undefined) count += 1 if (filters.minFireLevel !== undefined || filters.maxFireLevel !== undefined) count += 1 if (filters.minDailyLoss !== undefined || filters.maxDailyLoss !== undefined) count += 1 + if (filters.minSecretKd !== undefined || filters.maxSecretKd !== undefined) count += 1 return count }) @@ -190,6 +301,8 @@ export function useHomeFilters( filters.maxFireLevel = undefined filters.minDailyLoss = undefined filters.maxDailyLoss = undefined + filters.minSecretKd = undefined + filters.maxSecretKd = undefined filters.minAwmAmmo = undefined filters.maxAwmAmmo = undefined } @@ -249,6 +362,11 @@ export function useHomeFilters( filters.maxDailyLoss = max } + function setSecretKdRange(min: number | undefined, max: number | undefined) { + filters.minSecretKd = min + filters.maxSecretKd = max + } + function setAwmAmmoRange(min: number | undefined, max: number | undefined) { filters.minAwmAmmo = min filters.maxAwmAmmo = max @@ -302,6 +420,7 @@ export function useHomeFilters( setTotalRange, setFireLevelRange, setDailyLossRange, + setSecretKdRange, setAwmAmmoRange, toggleRange, setFilterPopover, diff --git a/frontend/src/features/listings/composables/useListingQuery.ts b/frontend/src/features/listings/composables/useListingQuery.ts index 5424116..c530511 100644 --- a/frontend/src/features/listings/composables/useListingQuery.ts +++ b/frontend/src/features/listings/composables/useListingQuery.ts @@ -4,6 +4,65 @@ import type { HomeFilters } from './useHomeFilters' const homePageSize = 12 +function joinCSV(values: string[] | undefined) { + if (!values?.length) return undefined + return values.filter(Boolean).join(',') || undefined +} + +export function buildListingQueryFromFilters( + filters: HomeFilters, + options: { + page: number + pageSize: number + sort?: string + zone?: string + } +): PublicListingQuery { + const query: PublicListingQuery = { + page: options.page, + page_size: options.pageSize, + keyword: filters.keyword?.trim() || undefined, + sort: options.sort, + zone: options.zone, + server: filters.server || undefined, + region: joinCSV(filters.region), + login_method: joinCSV(filters.loginMethod), + rank: joinCSV(filters.rank), + insurance: joinCSV(filters.insurance), + stamina: joinCSV(filters.stamina), + load: joinCSV(filters.load), + skin_group: joinCSV(filters.skinGroups), + skin_name: joinCSV(filters.skinNames), + min_coin: filters.minCoin, + max_coin: filters.maxCoin, + min_ratio: filters.minRatio, + max_ratio: filters.maxRatio, + min_price: filters.minPrice, + max_price: filters.maxPrice, + min_deposit: filters.minDeposit, + max_deposit: filters.maxDeposit, + min_total: filters.minTotal, + max_total: filters.maxTotal, + min_fire_level: filters.minFireLevel, + max_fire_level: filters.maxFireLevel, + min_daily_loss: filters.minDailyLoss, + max_daily_loss: filters.maxDailyLoss, + min_secret_kd: filters.minSecretKd, + max_secret_kd: filters.maxSecretKd, + resource_awmAmmo_min: filters.minAwmAmmo, + resource_awmAmmo_max: filters.maxAwmAmmo, + } + + if (filters.resources) { + for (const [key, range] of Object.entries(filters.resources)) { + if (range.min !== undefined) query[`resource_${key}_min`] = range.min + if (range.max !== undefined) query[`resource_${key}_max`] = range.max + } + } + + return query +} + export function useListingQuery( filters: HomeFilters, sortBy: Ref, @@ -18,43 +77,13 @@ export function useListingQuery( const hasMoreListings = ref(true) let listingRequestSeq = 0 - function joinCSV(values: string[]) { - return values.filter(Boolean).join(',') || undefined - } - function buildListingQuery(page: number): PublicListingQuery { - return { + return buildListingQueryFromFilters(filters, { page, - page_size: homePageSize, - keyword: filters.keyword.trim(), + pageSize: homePageSize, sort: sortBy.value, zone: activeZone.value, - server: filters.server, - region: joinCSV(filters.region), - login_method: joinCSV(filters.loginMethod), - rank: joinCSV(filters.rank), - insurance: joinCSV(filters.insurance), - stamina: joinCSV(filters.stamina), - load: joinCSV(filters.load), - // 多选皮肤名;后端 skin_name CSV 为 AND 匹配(账号需包含所有选中皮肤) - skin_name: joinCSV(filters.skinNames), - min_coin: filters.minCoin, - max_coin: filters.maxCoin, - min_ratio: filters.minRatio, - max_ratio: filters.maxRatio, - min_price: filters.minPrice, - max_price: filters.maxPrice, - min_deposit: filters.minDeposit, - max_deposit: filters.maxDeposit, - min_total: filters.minTotal, - max_total: filters.maxTotal, - min_fire_level: filters.minFireLevel, - max_fire_level: filters.maxFireLevel, - min_daily_loss: filters.minDailyLoss, - max_daily_loss: filters.maxDailyLoss, - resource_awmAmmo_min: filters.minAwmAmmo, - resource_awmAmmo_max: filters.maxAwmAmmo, - } + }) } function listingQuerySignature() { diff --git a/frontend/src/features/listings/views/MobileHomeView.vue b/frontend/src/features/listings/views/MobileHomeView.vue index b23c8ce..f9c8430 100644 --- a/frontend/src/features/listings/views/MobileHomeView.vue +++ b/frontend/src/features/listings/views/MobileHomeView.vue @@ -40,7 +40,9 @@ import { storeHomeQuery, type HomeQueryValue, } from '@/features/listings/composables/useHomeFilterQuery' -import { depositRangeOptions } from '@/features/listings/composables/useFilterOptions' +import { defaultRangePresets } from '@/features/listings/composables/useFilterOptions' +import { mobileFiltersToHomeFilters } from '@/features/listings/composables/useHomeFilters' +import { buildListingQueryFromFilters } from '@/features/listings/composables/useListingQuery' import { getListingChips, getListingDisplayPrice, @@ -139,51 +141,8 @@ const zoneOptions = [ { key: 'highCoin', label: '高币', hint: '100M以上' }, ] -/** 押金档位直接复用首页高级筛选项,保证移动端与桌面端完全一致 */ -const depositPresets = depositRangeOptions - .filter(option => option.label !== '全部') - .map(option => ({ - label: option.label, - min: option.min === undefined ? '' : String(option.min), - max: option.max === undefined ? '' : String(option.max), - })) - -const rangePresets: Record> = { - price: [ - { label: '50-100', min: '50', max: '100' }, - { label: '100-200', min: '100', max: '200' }, - { label: '200以上', min: '200', max: '' }, - ], - coin: [ - { label: '0-100', min: '0', max: '100' }, - { label: '100-300', min: '100', max: '300' }, - { label: '300-500', min: '300', max: '500' }, - { label: '500以上', min: '500', max: '' }, - ], - ratio: [ - { label: '30-40', min: '30', max: '40' }, - { label: '40以上', min: '40', max: '' }, - ], - deposit: depositPresets, - total: [ - { label: '0-500', min: '0', max: '500' }, - { label: '500-1000', min: '500', max: '1000' }, - { label: '1000-2000', min: '1000', max: '2000' }, - { label: '2000-5000', min: '2000', max: '5000' }, - ], - fireLevel: [ - { label: '38-50', min: '38', max: '50' }, - { label: '50-60', min: '50', max: '60' }, - { label: '60-70', min: '60', max: '70' }, - { label: '70以上', min: '70', max: '' }, - ], - resource_awmAmmo: [ - { label: '20-50', min: '20', max: '50' }, - { label: '50-100', min: '50', max: '100' }, - { label: '100-200', min: '100', max: '200' }, - { label: '200以上', min: '200', max: '' }, - ], -} +/** 统一使用领域层导出的预设区间,保证与桌面端完全一致 */ +const rangePresets = defaultRangePresets const activeSortLabel = computed( () => sortOptions.find(option => option.key === activeSort.value)?.label || '综合排序' @@ -279,6 +238,14 @@ const filterSections = computed(() => [ minPlaceholder: '最低', maxPlaceholder: '最高', }, + { + key: 'dailyLoss', + title: '日消耗', + type: 'range', + unit: 'M/天', + minPlaceholder: '最低', + maxPlaceholder: '最高', + }, { key: 'fireLevel', title: '等级', @@ -602,65 +569,18 @@ function clearFilters() { } function buildListingQuery(page: number): PublicListingQuery { - const query: PublicListingQuery = { + const homeFilters = mobileFiltersToHomeFilters( + selectedFilters.value, + rangeFilters.value, + searchValue.value, + publishOptions.value.skin_groups.map(group => group.key) + ) + return buildListingQueryFromFilters(homeFilters, { page, - page_size: mobilePageSize, - keyword: searchValue.value.trim(), + pageSize: mobilePageSize, sort: activeSort.value, zone: activeZone.value, - } - const skinGroups: string[] = [] - const skinNames: string[] = [] - for (const [key, values] of Object.entries(selectedFilters.value)) { - const value = values.filter(Boolean).join(',') - if (!value) continue - if (key === 'server') query.server = value - else if (key === 'region') query.region = value - else if (key === 'login') query.login_method = value - else if (key === 'insurance') query.insurance = value - else if (key === 'stamina') query.stamina = value - else if (key === 'load') query.load = value - else if (key === 'rank') query.rank = value - else if (isSkinGroupKey(key)) { - skinGroups.push(key) - skinNames.push(...values) - } - } - if (skinGroups.length) query.skin_group = skinGroups.join(',') - if (skinNames.length) query.skin_name = skinNames.join(',') - - for (const [key, range] of Object.entries(rangeFilters.value)) { - if (!range.min && !range.max) continue - const min = parseOptionalNumber(range.min) - const max = parseOptionalNumber(range.max) - if (key === 'price') { - query.min_price = min - query.max_price = max - } else if (key === 'coin') { - query.min_coin = min - query.max_coin = max - } else if (key === 'ratio') { - query.min_ratio = min - query.max_ratio = max - } else if (key === 'secretKd') { - query.min_secret_kd = min - query.max_secret_kd = max - } else if (key === 'deposit') { - query.min_deposit = min - query.max_deposit = max - } else if (key === 'total') { - query.min_total = min - query.max_total = max - } else if (key === 'fireLevel') { - query.min_fire_level = min - query.max_fire_level = max - } else if (key.startsWith('resource_')) { - const resourceKey = key.replace('resource_', '') - query[`resource_${resourceKey}_min`] = min - query[`resource_${resourceKey}_max`] = max - } - } - return query + }) } function filterQuerySignature() { @@ -672,12 +592,6 @@ function filterQuerySignature() { }) } -function parseOptionalNumber(value: string) { - if (value === '') return undefined - const number = Number(value) - return Number.isFinite(number) ? number : undefined -} - function applyRouteQueryToMobileState() { const storedQuery = readStoredHomeQuery(mobileHomeFilterStorageKey) const query = hasHomeQueryValues(route.query, mobileHomeFilterQueryKeys) @@ -776,10 +690,6 @@ function scrollToTop() { homeCache.saveScrollTop(0) } -function isSkinGroupKey(key: string) { - return publishOptions.value.skin_groups.some(group => group.key === key) -} - function parseQuantityUnit(price: string) { const unit = price.split('/')[1]?.trim() return unit || undefined