refactor(listings): 统一移动端与PC端筛选逻辑并支持日消耗与绝密KD
- 统一预设体系:在 useFilterOptions.ts 集中导出 defaultRangePresets,两端共享档位定义,消除移动端私有硬编码档位 - 统一参数模型:在 useHomeFilters.ts 增加 mobileFiltersToHomeFilters,将移动端字典结构标准化转换为领域层 HomeFilters 模型 - 统一查询构建:在 useListingQuery.ts 抽象纯函数 buildListingQueryFromFilters,双端共用同一个后端发包构建器 - 功能对齐:移动端首页完整补充「日消耗」区间筛选;PC端高级抽屉对齐补充「绝密KD」筛选 - 验证通过:补充双端测试用例,全站门禁 ./scripts/check.sh(SQL/Go/MySQL/Vitest/Vite构建)全部绿灯
This commit is contained in:
@@ -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(() =>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-field">
|
||||
<label>绝密KD</label>
|
||||
<div class="pill-row">
|
||||
<button
|
||||
v-for="item in secretKdRangeOptions.filter(o => o.label !== '全部')"
|
||||
:key="`kd-${item.label}`"
|
||||
type="button"
|
||||
class="pill-chip"
|
||||
:class="{ active: isRangeActive('minSecretKd', 'maxSecretKd', item) }"
|
||||
@click="toggleRange('minSecretKd', 'maxSecretKd', item)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-field">
|
||||
<label>段位</label>
|
||||
<div class="pill-row">
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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<string, RangePresetOption[]> = {
|
||||
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}+`
|
||||
|
||||
@@ -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<string, { min?: number; max?: number }> | 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<string, string[]>,
|
||||
rangeFilters: Record<string, { min: string; max: string }>,
|
||||
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<string, { min?: number; max?: number }> = {}
|
||||
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<ListingPublishOptions>,
|
||||
listings: Ref<PublicListingItem[]>
|
||||
@@ -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,
|
||||
|
||||
@@ -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<string>,
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<string, Array<{ label: string; min: string; max: string }>> = {
|
||||
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<FilterSection[]>(() => [
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user