优化筛选
This commit is contained in:
+275
-70
@@ -6,12 +6,18 @@ import {
|
||||
Copy,
|
||||
Filter,
|
||||
LoaderCircle,
|
||||
RefreshCw,
|
||||
Search,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
} from "@lucide/vue";
|
||||
import { fetchListingsPage, fetchMobileHomeConfig, type HomeBannerSlide, type Listing, type PublicListingQuery } from "./api";
|
||||
import {
|
||||
fetchListingsPage,
|
||||
fetchMobileHomeConfig,
|
||||
type HomeBannerSlide,
|
||||
type Listing,
|
||||
type ListingPublishOptions,
|
||||
type PublicListingQuery,
|
||||
} from "./api";
|
||||
import {
|
||||
formatCoin,
|
||||
formatListingCode,
|
||||
@@ -28,34 +34,61 @@ const loadError = ref("");
|
||||
const listings = ref<Listing[]>([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const zoneCounts = ref<Record<string, number>>({});
|
||||
const announcements = ref<string[]>([]);
|
||||
const banners = ref<HomeBannerSlide[]>([]);
|
||||
const publishOptions = ref<ListingPublishOptions>({
|
||||
server_options: [],
|
||||
login_method_options: [],
|
||||
rank_options: [],
|
||||
insurance_options: [],
|
||||
level_options: [],
|
||||
skin_groups: [],
|
||||
});
|
||||
const copiedCode = ref("");
|
||||
const activeSort = ref("recommended");
|
||||
const activeZone = ref("all");
|
||||
const filterOpen = ref(false);
|
||||
const filters = reactive({
|
||||
keyword: "",
|
||||
minCoin: "",
|
||||
maxCoin: "",
|
||||
minPrice: "",
|
||||
maxPrice: "",
|
||||
minKd: "",
|
||||
maxKd: "",
|
||||
stamina: [] as string[],
|
||||
load: [] as string[],
|
||||
insurance: [] as string[],
|
||||
skinNames: [] as string[],
|
||||
});
|
||||
|
||||
const sortOptions = [
|
||||
{ key: "recommended", label: "默认" },
|
||||
{ key: "priceAsc", label: "价格" },
|
||||
{ key: "coinDesc", label: "哈夫币" },
|
||||
{ key: "awmDesc", label: "AWM" },
|
||||
{ key: "recommended", label: "默认", sortable: false },
|
||||
{ key: "price", label: "价格", sortable: true },
|
||||
{ key: "ratio", label: "比例", sortable: true },
|
||||
{ key: "coin", label: "哈夫币", sortable: true },
|
||||
];
|
||||
|
||||
const zoneOptions = computed(() => [
|
||||
{ key: "all", label: "全部", count: zoneCount("all") },
|
||||
{ key: "sale", label: "特惠", count: zoneCount("sale") },
|
||||
{ key: "highCoin", label: "高币", count: zoneCount("highCoin") },
|
||||
{ key: "password", label: "账密", count: zoneCount("password") },
|
||||
{ key: "night", label: "夜间", count: zoneCount("night") },
|
||||
]);
|
||||
const fallbackLevels = ["3级", "4级", "5级", "6级", "7级"];
|
||||
const fallbackInsurance = ["2*1", "2*2", "2*3", "3*3"];
|
||||
const fallbackSkins = ["信条", "坠星者", "处刑者", "影锋", "暗星", "电锯", "蚀金玫瑰", "维什戴尔", "水墨云图"];
|
||||
|
||||
const levelOptions = computed(() => publishOptions.value.level_options.length ? publishOptions.value.level_options : fallbackLevels);
|
||||
const insuranceOptions = computed(() =>
|
||||
publishOptions.value.insurance_options.length ? publishOptions.value.insurance_options : fallbackInsurance
|
||||
);
|
||||
const skinSections = computed(() => {
|
||||
const groups = publishOptions.value.skin_groups.filter((group) =>
|
||||
["melee", "operatorGold", "operatorRed", "weapon"].includes(group.key)
|
||||
);
|
||||
if (groups.length) {
|
||||
return groups.map((group) => ({
|
||||
key: group.key,
|
||||
title: groupTitle(group.key, group.title),
|
||||
options: group.options,
|
||||
}));
|
||||
}
|
||||
return [{ key: "skin", title: "刀皮", options: fallbackSkins }];
|
||||
});
|
||||
|
||||
const topBanner = computed(() => banners.value[0] || {
|
||||
eyebrow: "三角洲行动账号专区",
|
||||
@@ -66,13 +99,40 @@ const topBanner = computed(() => banners.value[0] || {
|
||||
});
|
||||
|
||||
const hasMore = computed(() => listings.value.length < total.value);
|
||||
const displayListings = computed(() => {
|
||||
const items = [...listings.value];
|
||||
if (activeSort.value === "ratioAsc") {
|
||||
return items.sort((a, b) => ratioValue(a) - ratioValue(b));
|
||||
}
|
||||
if (activeSort.value === "ratioDesc") {
|
||||
return items.sort((a, b) => ratioValue(b) - ratioValue(a));
|
||||
}
|
||||
if (activeSort.value === "coinAsc") {
|
||||
return items.sort((a, b) => a.haf_coin_amount - b.haf_coin_amount);
|
||||
}
|
||||
return items;
|
||||
});
|
||||
const hasAdvancedFilters = computed(() => {
|
||||
return Boolean(
|
||||
filters.minCoin ||
|
||||
filters.maxCoin ||
|
||||
filters.minPrice ||
|
||||
filters.maxPrice ||
|
||||
filters.minKd ||
|
||||
filters.maxKd ||
|
||||
filters.stamina.length ||
|
||||
filters.load.length ||
|
||||
filters.insurance.length ||
|
||||
filters.skinNames.length
|
||||
);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadHome();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [activeSort.value, activeZone.value],
|
||||
() => activeSort.value,
|
||||
() => loadListings(true)
|
||||
);
|
||||
|
||||
@@ -82,6 +142,7 @@ async function loadHome() {
|
||||
const [home] = await Promise.all([fetchMobileHomeConfig(), loadListings(true)]);
|
||||
announcements.value = home.announcements;
|
||||
banners.value = home.banners;
|
||||
publishOptions.value = home.publish_options;
|
||||
} catch (error) {
|
||||
loadError.value = error instanceof Error ? error.message : "加载失败";
|
||||
} finally {
|
||||
@@ -99,7 +160,6 @@ async function loadListings(reset = false) {
|
||||
listings.value = reset ? result.items : [...listings.value, ...result.items];
|
||||
total.value = result.total;
|
||||
page.value = result.page + 1;
|
||||
zoneCounts.value = result.zone_counts;
|
||||
} catch (error) {
|
||||
loadError.value = error instanceof Error ? error.message : "加载失败";
|
||||
if (reset) listings.value = [];
|
||||
@@ -113,12 +173,17 @@ function buildQuery(nextPage: number): PublicListingQuery {
|
||||
page: nextPage,
|
||||
page_size: pageSize,
|
||||
keyword: filters.keyword.trim(),
|
||||
sort: activeSort.value,
|
||||
zone: activeZone.value,
|
||||
sort: backendSortKey(),
|
||||
min_coin: toNumber(filters.minCoin),
|
||||
max_coin: toNumber(filters.maxCoin),
|
||||
min_price: toNumber(filters.minPrice),
|
||||
max_price: toNumber(filters.maxPrice),
|
||||
min_secret_kd: toNumber(filters.minKd),
|
||||
max_secret_kd: toNumber(filters.maxKd),
|
||||
stamina: filters.stamina.join(","),
|
||||
load: filters.load.join(","),
|
||||
insurance: filters.insurance.join(","),
|
||||
skin_name: filters.skinNames.join(","),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -132,11 +197,71 @@ function resetFilters() {
|
||||
filters.maxCoin = "";
|
||||
filters.minPrice = "";
|
||||
filters.maxPrice = "";
|
||||
filters.minKd = "";
|
||||
filters.maxKd = "";
|
||||
filters.stamina = [];
|
||||
filters.load = [];
|
||||
filters.insurance = [];
|
||||
filters.skinNames = [];
|
||||
activeSort.value = "recommended";
|
||||
activeZone.value = "all";
|
||||
loadListings(true);
|
||||
}
|
||||
|
||||
function confirmFilters() {
|
||||
filterOpen.value = false;
|
||||
loadListings(true);
|
||||
}
|
||||
|
||||
function setQuickSort(key: string) {
|
||||
if (key === "recommended") {
|
||||
activeSort.value = "recommended";
|
||||
return;
|
||||
}
|
||||
if (key === "price") {
|
||||
activeSort.value = activeSort.value === "priceAsc" ? "priceDesc" : "priceAsc";
|
||||
return;
|
||||
}
|
||||
if (key === "ratio") {
|
||||
activeSort.value = activeSort.value === "ratioDesc" ? "ratioAsc" : "ratioDesc";
|
||||
return;
|
||||
}
|
||||
if (key === "coin") {
|
||||
activeSort.value = activeSort.value === "coinDesc" ? "coinAsc" : "coinDesc";
|
||||
}
|
||||
}
|
||||
|
||||
function isQuickSortActive(key: string) {
|
||||
if (key === "recommended") return activeSort.value === "recommended";
|
||||
return activeSort.value.startsWith(key);
|
||||
}
|
||||
|
||||
function sortMark(key: string) {
|
||||
if (key === "price") return activeSort.value === "priceAsc" ? "▲" : activeSort.value === "priceDesc" ? "▼" : "↕";
|
||||
if (key === "ratio") return activeSort.value === "ratioAsc" ? "▲" : activeSort.value === "ratioDesc" ? "▼" : "↕";
|
||||
if (key === "coin") return activeSort.value === "coinAsc" ? "▲" : activeSort.value === "coinDesc" ? "▼" : "↕";
|
||||
return "";
|
||||
}
|
||||
|
||||
function backendSortKey() {
|
||||
if (activeSort.value === "priceAsc" || activeSort.value === "priceDesc" || activeSort.value === "coinDesc") {
|
||||
return activeSort.value;
|
||||
}
|
||||
return "recommended";
|
||||
}
|
||||
|
||||
function toggleListValue(list: string[], value: string) {
|
||||
const index = list.indexOf(value);
|
||||
if (index >= 0) {
|
||||
list.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
list.push(value);
|
||||
}
|
||||
|
||||
function isSelected(list: string[], value: string) {
|
||||
return list.includes(value);
|
||||
}
|
||||
|
||||
async function copyListing(item: Listing) {
|
||||
const text = `${formatListingCode(item)} 纯币 ${formatCoin(item)} ¥${item.price} 比例 ${formatRatio(item)}`;
|
||||
await navigator.clipboard?.writeText(text);
|
||||
@@ -146,15 +271,32 @@ async function copyListing(item: Listing) {
|
||||
}, 1600);
|
||||
}
|
||||
|
||||
function zoneCount(key: string) {
|
||||
return Number(zoneCounts.value[key] || 0);
|
||||
}
|
||||
|
||||
function toNumber(value: string) {
|
||||
if (!value.trim()) return undefined;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function ratioValue(item: Listing) {
|
||||
const configuredRatio = readAssetNumber(item, "publish_ratio");
|
||||
if (configuredRatio > 0) return configuredRatio;
|
||||
if (!item.price) return 0;
|
||||
return Number(item.haf_coin_amount || 0) / 10000 / item.price;
|
||||
}
|
||||
|
||||
function groupTitle(key: string, title: string) {
|
||||
if (key === "melee") return "刀皮";
|
||||
if (key === "operatorGold") return "干员金皮";
|
||||
if (key === "operatorRed") return "干员红皮";
|
||||
if (key === "weapon") return "枪皮";
|
||||
return title;
|
||||
}
|
||||
|
||||
function insuranceLabel(value: string) {
|
||||
const [rows, cols] = value.split("*").map((item) => Number(item));
|
||||
if (rows > 0 && cols > 0) return `${rows * cols}格`;
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -193,64 +335,127 @@ function toNumber(value: string) {
|
||||
<span>{{ announcements[0] }}</span>
|
||||
</div>
|
||||
|
||||
<section class="filters">
|
||||
<div class="segmented" aria-label="专区筛选">
|
||||
<button
|
||||
v-for="zone in zoneOptions"
|
||||
:key="zone.key"
|
||||
:class="{ active: activeZone === zone.key }"
|
||||
type="button"
|
||||
@click="activeZone = zone.key"
|
||||
>
|
||||
{{ zone.label }} <span>{{ zone.count }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="sorts">
|
||||
<section class="quick-filter">
|
||||
<div class="sort-line">
|
||||
<button
|
||||
v-for="sort in sortOptions"
|
||||
:key="sort.key"
|
||||
:class="{ active: activeSort === sort.key }"
|
||||
:class="{ active: isQuickSortActive(sort.key) }"
|
||||
type="button"
|
||||
@click="activeSort = sort.key"
|
||||
@click="setQuickSort(sort.key)"
|
||||
>
|
||||
<ArrowDownUp v-if="sort.key !== 'recommended'" :size="13" />
|
||||
{{ sort.label }}
|
||||
<span v-if="sort.sortable" class="sort-mark">{{ sortMark(sort.key) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="rangebar">
|
||||
<label>
|
||||
<span>哈夫币</span>
|
||||
<input v-model="filters.minCoin" inputmode="numeric" placeholder="最低M" @keyup.enter="submitSearch" />
|
||||
<input v-model="filters.maxCoin" inputmode="numeric" placeholder="最高M" @keyup.enter="submitSearch" />
|
||||
</label>
|
||||
<label>
|
||||
<span>价格</span>
|
||||
<input v-model="filters.minPrice" inputmode="numeric" placeholder="最低¥" @keyup.enter="submitSearch" />
|
||||
<input v-model="filters.maxPrice" inputmode="numeric" placeholder="最高¥" @keyup.enter="submitSearch" />
|
||||
</label>
|
||||
<button class="icon-action" type="button" title="应用筛选" @click="submitSearch">
|
||||
<Filter :size="17" />
|
||||
</button>
|
||||
<button class="icon-action" type="button" title="重置" @click="resetFilters">
|
||||
<RefreshCw :size="17" />
|
||||
<button :class="{ active: filterOpen || hasAdvancedFilters }" type="button" @click="filterOpen = !filterOpen">
|
||||
筛选 <Filter :size="17" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="summary">
|
||||
<div>
|
||||
<strong>{{ total }}</strong>
|
||||
<span>当前可租账号</span>
|
||||
<section v-if="filterOpen" class="filter-panel">
|
||||
<div class="filter-head">
|
||||
<strong>筛选条件</strong>
|
||||
<button type="button" @click="resetFilters">重置</button>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ zoneCount("highCoin") }}</strong>
|
||||
<span>100M 以上</span>
|
||||
|
||||
<div class="filter-body">
|
||||
<label class="filter-field filter-field-wide">
|
||||
<span>账号编号</span>
|
||||
<input v-model="filters.keyword" placeholder="输入编号搜索" @keyup.enter="confirmFilters" />
|
||||
</label>
|
||||
|
||||
<div class="filter-group">
|
||||
<strong>体力等级</strong>
|
||||
<div class="filter-chips">
|
||||
<button
|
||||
v-for="option in levelOptions"
|
||||
:key="`stamina-${option}`"
|
||||
:class="{ active: isSelected(filters.stamina, option) }"
|
||||
type="button"
|
||||
@click="toggleListValue(filters.stamina, option)"
|
||||
>
|
||||
{{ option }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<strong>负重等级</strong>
|
||||
<div class="filter-chips">
|
||||
<button
|
||||
v-for="option in levelOptions"
|
||||
:key="`load-${option}`"
|
||||
:class="{ active: isSelected(filters.load, option) }"
|
||||
type="button"
|
||||
@click="toggleListValue(filters.load, option)"
|
||||
>
|
||||
{{ option }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<strong>保险格数</strong>
|
||||
<div class="filter-chips">
|
||||
<button
|
||||
v-for="option in insuranceOptions"
|
||||
:key="`insurance-${option}`"
|
||||
:class="{ active: isSelected(filters.insurance, option) }"
|
||||
type="button"
|
||||
@click="toggleListValue(filters.insurance, option)"
|
||||
>
|
||||
{{ insuranceLabel(option) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-for="section in skinSections" :key="section.key" class="filter-group filter-group-wide">
|
||||
<strong>{{ section.title }}</strong>
|
||||
<div class="filter-chips">
|
||||
<button
|
||||
v-for="option in section.options"
|
||||
:key="`${section.key}-${option}`"
|
||||
:class="{ active: isSelected(filters.skinNames, option) }"
|
||||
type="button"
|
||||
@click="toggleListValue(filters.skinNames, option)"
|
||||
>
|
||||
{{ option }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-range filter-group-wide">
|
||||
<strong>哈夫币范围(M)</strong>
|
||||
<div>
|
||||
<input v-model="filters.minCoin" inputmode="numeric" placeholder="最小" @keyup.enter="confirmFilters" />
|
||||
<span>-</span>
|
||||
<input v-model="filters.maxCoin" inputmode="numeric" placeholder="最大" @keyup.enter="confirmFilters" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-range filter-group-wide">
|
||||
<strong>价格范围</strong>
|
||||
<div>
|
||||
<input v-model="filters.minPrice" inputmode="numeric" placeholder="最低¥" @keyup.enter="confirmFilters" />
|
||||
<span>-</span>
|
||||
<input v-model="filters.maxPrice" inputmode="numeric" placeholder="最高¥" @keyup.enter="confirmFilters" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-range filter-group-wide">
|
||||
<strong>KD 范围</strong>
|
||||
<div>
|
||||
<input v-model="filters.minKd" inputmode="decimal" placeholder="最小" @keyup.enter="confirmFilters" />
|
||||
<span>-</span>
|
||||
<input v-model="filters.maxKd" inputmode="decimal" placeholder="最大" @keyup.enter="confirmFilters" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ zoneCount("sale") }}</strong>
|
||||
<span>特惠账号</span>
|
||||
|
||||
<div class="filter-actions">
|
||||
<button type="button" @click="filterOpen = false">取消</button>
|
||||
<button type="button" @click="confirmFilters">确定筛选</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -265,7 +470,7 @@ function toNumber(value: string) {
|
||||
</div>
|
||||
|
||||
<section v-else class="grid">
|
||||
<article v-for="item in listings" :key="item.id" class="card">
|
||||
<article v-for="item in displayListings" :key="item.id" class="card">
|
||||
<div class="card-head">
|
||||
<div>
|
||||
<h2>编号 {{ formatListingCode(item) }}</h2>
|
||||
|
||||
+26
@@ -45,10 +45,18 @@ export interface PublicListingQuery {
|
||||
keyword?: string;
|
||||
sort?: string;
|
||||
zone?: string;
|
||||
login_method?: string;
|
||||
rank?: string;
|
||||
insurance?: string;
|
||||
stamina?: string;
|
||||
load?: string;
|
||||
skin_name?: string;
|
||||
min_coin?: number;
|
||||
max_coin?: number;
|
||||
min_price?: number;
|
||||
max_price?: number;
|
||||
min_secret_kd?: number;
|
||||
max_secret_kd?: number;
|
||||
}
|
||||
|
||||
export interface HomeBannerSlide {
|
||||
@@ -66,6 +74,11 @@ export interface ListingPublishOptions {
|
||||
rank_options: string[];
|
||||
insurance_options: string[];
|
||||
level_options: string[];
|
||||
skin_groups: Array<{
|
||||
key: string;
|
||||
title: string;
|
||||
options: string[];
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface MobileHomeConfig {
|
||||
@@ -121,9 +134,22 @@ function normalizeOptions(value: unknown): ListingPublishOptions {
|
||||
rank_options: normalizeStringArray(options.rank_options),
|
||||
insurance_options: normalizeStringArray(options.insurance_options),
|
||||
level_options: normalizeStringArray(options.level_options),
|
||||
skin_groups: normalizeSkinGroups(options.skin_groups),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSkinGroups(value: unknown) {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value
|
||||
.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null)
|
||||
.map((item) => ({
|
||||
key: typeof item.key === "string" ? item.key : "",
|
||||
title: typeof item.title === "string" ? item.title : "",
|
||||
options: normalizeStringArray(item.options),
|
||||
}))
|
||||
.filter((item) => item.key && item.title);
|
||||
}
|
||||
|
||||
async function request<T>(path: string, query: Record<string, unknown> = {}) {
|
||||
const url = new URL(`${apiBase}${path}`, window.location.origin);
|
||||
Object.entries(query).forEach(([key, value]) => {
|
||||
|
||||
+162
-125
@@ -196,10 +196,8 @@ button {
|
||||
}
|
||||
|
||||
.notice span,
|
||||
.segmented,
|
||||
.sorts,
|
||||
.rangebar,
|
||||
.summary,
|
||||
.quick-filter,
|
||||
.filter-panel,
|
||||
.grid {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -209,114 +207,160 @@ button {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
.quick-filter {
|
||||
background: #fff;
|
||||
margin-bottom: 20px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.segmented,
|
||||
.sorts {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
.sort-line {
|
||||
min-height: 66px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.segmented button,
|
||||
.sorts button {
|
||||
min-height: 34px;
|
||||
.sort-line button {
|
||||
min-height: 66px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #777b85;
|
||||
color: #686d77;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 6px;
|
||||
padding: 0 18px;
|
||||
gap: 5px;
|
||||
padding: 0 8px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.sort-line button.active {
|
||||
color: #ff506d;
|
||||
}
|
||||
|
||||
.sort-mark {
|
||||
color: #c4c6cc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.filter-panel {
|
||||
background: #fff;
|
||||
margin: -8px 0 20px;
|
||||
border-top: 1px solid #f0f1f4;
|
||||
border-radius: 0 0 8px 8px;
|
||||
box-shadow: 0 8px 18px rgba(24, 26, 32, 0.05);
|
||||
}
|
||||
|
||||
.filter-head {
|
||||
min-height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 10px;
|
||||
border-bottom: 1px solid #f0f1f4;
|
||||
}
|
||||
|
||||
.filter-head button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #ff506d;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.segmented button.active,
|
||||
.sorts button.active {
|
||||
color: #ff506d;
|
||||
background: #fff0f3;
|
||||
}
|
||||
|
||||
.segmented span {
|
||||
color: #b4b7bd;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rangebar {
|
||||
.filter-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 36px 36px;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px 28px;
|
||||
padding: 16px 10px 20px;
|
||||
}
|
||||
|
||||
.rangebar label {
|
||||
.filter-field,
|
||||
.filter-group,
|
||||
.filter-range {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr 1fr;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.rangebar label span {
|
||||
color: #757985;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.rangebar input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
border: 1px solid #eceef2;
|
||||
border-radius: 6px;
|
||||
padding: 0 8px;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.icon-action {
|
||||
width: 36px;
|
||||
height: 34px;
|
||||
border: 1px solid #eceef2;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
color: #606571;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.summary div {
|
||||
min-width: 0;
|
||||
min-height: 58px;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
align-content: center;
|
||||
box-shadow: 0 1px 8px rgba(24, 26, 32, 0.04);
|
||||
}
|
||||
|
||||
.summary strong {
|
||||
font-size: 22px;
|
||||
.filter-field-wide,
|
||||
.filter-group-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.filter-field span,
|
||||
.filter-group strong,
|
||||
.filter-range strong {
|
||||
color: #33363d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-field input,
|
||||
.filter-range input {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 5px;
|
||||
outline: 0;
|
||||
padding: 0 12px;
|
||||
color: #33363d;
|
||||
}
|
||||
|
||||
.summary span {
|
||||
font-size: 12px;
|
||||
color: #8e929c;
|
||||
.filter-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-chips button {
|
||||
min-width: 58px;
|
||||
min-height: 36px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 5px;
|
||||
background: #fff;
|
||||
color: #555b66;
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.filter-chips button.active {
|
||||
border-color: #ff6f86;
|
||||
color: #ff506d;
|
||||
background: #fff2f5;
|
||||
}
|
||||
|
||||
.filter-range > div {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) max-content minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.filter-range span {
|
||||
color: #9aa0aa;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
padding: 0 10px 18px;
|
||||
}
|
||||
|
||||
.filter-actions button {
|
||||
height: 44px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.filter-actions button:first-child {
|
||||
background: #f2f2f3;
|
||||
color: #70757f;
|
||||
}
|
||||
|
||||
.filter-actions button:last-child {
|
||||
background: #ff6f86;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.grid {
|
||||
@@ -516,8 +560,8 @@ button {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.rangebar {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.filter-body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,49 +583,42 @@ button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summary {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.segmented,
|
||||
.sorts {
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
overflow-x: hidden;
|
||||
max-width: 100%;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.segmented::-webkit-scrollbar,
|
||||
.sorts::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.segmented button,
|
||||
.sorts button {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.hero-copy h1 {
|
||||
font-size: 22px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.sort-line {
|
||||
grid-template-columns: repeat(5, max-content);
|
||||
overflow-x: auto;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sort-line button {
|
||||
min-width: 68px;
|
||||
padding: 0 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-panel {
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.filter-body {
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-chips button {
|
||||
min-width: 58px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.rangebar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.rangebar label {
|
||||
grid-template-columns: 52px 1fr 1fr;
|
||||
}
|
||||
|
||||
.icon-action {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user