增加前端格式检查配置
This commit is contained in:
@@ -1,24 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { RouterLink, useRouter } from "vue-router";
|
||||
import { showToast } from "vant";
|
||||
import MobileBottomNav from "@/components/MobileBottomNav.vue";
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { RouterLink, useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
import MobileBottomNav from '@/components/MobileBottomNav.vue'
|
||||
|
||||
import { ensureSupportChat } from "@/features/chats/api/chats";
|
||||
import { ensureSupportChat } from '@/features/chats/api/chats'
|
||||
import {
|
||||
emptyListingPublishOptions,
|
||||
type ListingPublishOptions,
|
||||
} from "@/features/listings/api/listingOptions";
|
||||
import { fetchListingsPage, type Listing, type PublicListingQuery } from "@/features/listings/api/listings";
|
||||
} from '@/features/listings/api/listingOptions'
|
||||
import {
|
||||
fetchListingsPage,
|
||||
type Listing,
|
||||
type PublicListingQuery,
|
||||
} from '@/features/listings/api/listings'
|
||||
import {
|
||||
defaultHomeAnnouncements,
|
||||
defaultHomeBanners,
|
||||
fetchMobileHomeConfig,
|
||||
type HomeBannerSlide,
|
||||
} from "@/features/listings/api/homeConfig";
|
||||
import MobileHomeFilterSheet, {
|
||||
type FilterSection,
|
||||
} from "./MobileHomeFilterSheet.vue";
|
||||
} from '@/features/listings/api/homeConfig'
|
||||
import MobileHomeFilterSheet, { type FilterSection } from './MobileHomeFilterSheet.vue'
|
||||
import {
|
||||
getListingChips,
|
||||
getListingDisplayPrice,
|
||||
@@ -28,237 +30,256 @@ import {
|
||||
getServerRegion,
|
||||
hasAcceleratedSaleRatio,
|
||||
hasGiftResources,
|
||||
} from "@/utils/listingDisplay";
|
||||
import { useSessionStore } from "@/stores/session";
|
||||
} from '@/utils/listingDisplay'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
|
||||
const router = useRouter();
|
||||
const session = useSessionStore();
|
||||
const loading = ref(false);
|
||||
const loadingMore = ref(false);
|
||||
const loadFailed = ref(false);
|
||||
const listings = ref<Listing[]>([]);
|
||||
const totalListings = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const hasMoreListings = ref(true);
|
||||
const publishOptions = ref<ListingPublishOptions>(emptyListingPublishOptions);
|
||||
const sortOpen = ref(false);
|
||||
const activeSort = ref("comprehensive");
|
||||
const filterOpen = ref(false);
|
||||
const selectedFilters = ref<Record<string, string[]>>({});
|
||||
const rangeFilters = ref<Record<string, { min: string; max: string }>>({});
|
||||
const refreshing = ref(false);
|
||||
const searchValue = ref("");
|
||||
const announcements = ref<string[]>(defaultHomeAnnouncements);
|
||||
const bannerSlides = ref<HomeBannerSlide[]>(defaultHomeBanners);
|
||||
const supportLoading = ref(false);
|
||||
const mobilePageSize = 10;
|
||||
let listingRequestSeq = 0;
|
||||
const router = useRouter()
|
||||
const session = useSessionStore()
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const loadFailed = ref(false)
|
||||
const listings = ref<Listing[]>([])
|
||||
const totalListings = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const hasMoreListings = ref(true)
|
||||
const publishOptions = ref<ListingPublishOptions>(emptyListingPublishOptions)
|
||||
const sortOpen = ref(false)
|
||||
const activeSort = ref('comprehensive')
|
||||
const filterOpen = ref(false)
|
||||
const selectedFilters = ref<Record<string, string[]>>({})
|
||||
const rangeFilters = ref<Record<string, { min: string; max: string }>>({})
|
||||
const refreshing = ref(false)
|
||||
const searchValue = ref('')
|
||||
const announcements = ref<string[]>(defaultHomeAnnouncements)
|
||||
const bannerSlides = ref<HomeBannerSlide[]>(defaultHomeBanners)
|
||||
const supportLoading = ref(false)
|
||||
const mobilePageSize = 10
|
||||
let listingRequestSeq = 0
|
||||
|
||||
const sortOptions = [
|
||||
{ key: "comprehensive", label: "综合排序" },
|
||||
{ key: "published", label: "发布时间" },
|
||||
{ key: "awmDesc", label: "AWM数量" },
|
||||
{ key: "priceAsc", label: "价格最低" },
|
||||
{ key: "priceDesc", label: "价格最高" },
|
||||
];
|
||||
{ key: 'comprehensive', label: '综合排序' },
|
||||
{ key: 'published', label: '发布时间' },
|
||||
{ key: 'awmDesc', label: 'AWM数量' },
|
||||
{ key: 'priceAsc', label: '价格最低' },
|
||||
{ key: 'priceDesc', label: '价格最高' },
|
||||
]
|
||||
|
||||
const rangePresets: Record<string, Array<{ label: string; min: string; max: string }>> = {
|
||||
coin: [
|
||||
{ label: "50-100", min: "50", max: "100" },
|
||||
{ label: "100-200", min: "100", max: "200" },
|
||||
{ label: "200-300", min: "200", max: "300" },
|
||||
{ label: "300-500", min: "300", max: "500" },
|
||||
{ label: "500以上", min: "500", max: "" },
|
||||
{ label: '50-100', min: '50', max: '100' },
|
||||
{ label: '100-200', min: '100', max: '200' },
|
||||
{ label: '200-300', min: '200', max: '300' },
|
||||
{ label: '300-500', min: '300', max: '500' },
|
||||
{ label: '500以上', min: '500', max: '' },
|
||||
],
|
||||
resource_awmAmmo: [
|
||||
{ label: "0-20", min: "0", max: "20" },
|
||||
{ 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: "" },
|
||||
{ label: '0-20', min: '0', max: '20' },
|
||||
{ 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 activeSortLabel = computed(
|
||||
() =>
|
||||
sortOptions.find((option) => option.key === activeSort.value)?.label ||
|
||||
"综合排序"
|
||||
);
|
||||
() => sortOptions.find(option => option.key === activeSort.value)?.label || '综合排序'
|
||||
)
|
||||
|
||||
async function handleSupportClick() {
|
||||
if (!session.isLoggedIn) {
|
||||
router.push({ path: "/m/login", query: { redirect: router.currentRoute.value.fullPath } });
|
||||
return;
|
||||
router.push({ path: '/m/login', query: { redirect: router.currentRoute.value.fullPath } })
|
||||
return
|
||||
}
|
||||
if (supportLoading.value) return;
|
||||
supportLoading.value = true;
|
||||
if (supportLoading.value) return
|
||||
supportLoading.value = true
|
||||
try {
|
||||
const chat = await ensureSupportChat();
|
||||
router.push(`/m/chats/${chat.id}`);
|
||||
const chat = await ensureSupportChat()
|
||||
router.push(`/m/chats/${chat.id}`)
|
||||
} catch {
|
||||
showToast({ message: "联系客服失败,请稍后重试", icon: "cross" });
|
||||
showToast({ message: '联系客服失败,请稍后重试', icon: 'cross' })
|
||||
} finally {
|
||||
supportLoading.value = false;
|
||||
supportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const serverFilterOptions = computed(() =>
|
||||
uniqueOptions(
|
||||
publishOptions.value.server_options
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
);
|
||||
uniqueOptions(publishOptions.value.server_options.map(item => item.trim()).filter(Boolean))
|
||||
)
|
||||
|
||||
const loginMethodFilterOptions = computed(() =>
|
||||
uniqueOptions(
|
||||
publishOptions.value.login_method_options
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
)
|
||||
);
|
||||
uniqueOptions(publishOptions.value.login_method_options.map(item => item.trim()).filter(Boolean))
|
||||
)
|
||||
|
||||
const filterSections = computed<FilterSection[]>(() => [
|
||||
{ key: "price", title: "价格区间", type: "range", unit: "元", minPlaceholder: "最低价", maxPlaceholder: "最高价" },
|
||||
{ key: "coin", title: "哈夫币数量", type: "range", unit: "M", minPlaceholder: "最低", maxPlaceholder: "最高" },
|
||||
{ key: "server", title: "区服", type: "chips", options: serverFilterOptions.value },
|
||||
{ key: "login", title: "上号方式", type: "chips", options: loginMethodFilterOptions.value },
|
||||
{ key: "insurance", title: "保险", type: "chips", options: publishOptions.value.insurance_options },
|
||||
{ key: "stamina", title: "体力", type: "chips", options: publishOptions.value.level_options },
|
||||
{ key: "load", title: "负重", type: "chips", options: publishOptions.value.level_options },
|
||||
...publishOptions.value.quantity_items.map((item) => ({
|
||||
{
|
||||
key: 'price',
|
||||
title: '价格区间',
|
||||
type: 'range',
|
||||
unit: '元',
|
||||
minPlaceholder: '最低价',
|
||||
maxPlaceholder: '最高价',
|
||||
},
|
||||
{
|
||||
key: 'coin',
|
||||
title: '哈夫币数量',
|
||||
type: 'range',
|
||||
unit: 'M',
|
||||
minPlaceholder: '最低',
|
||||
maxPlaceholder: '最高',
|
||||
},
|
||||
{ key: 'server', title: '区服', type: 'chips', options: serverFilterOptions.value },
|
||||
{ key: 'login', title: '上号方式', type: 'chips', options: loginMethodFilterOptions.value },
|
||||
{
|
||||
key: 'insurance',
|
||||
title: '保险',
|
||||
type: 'chips',
|
||||
options: publishOptions.value.insurance_options,
|
||||
},
|
||||
{ key: 'stamina', title: '体力', type: 'chips', options: publishOptions.value.level_options },
|
||||
{ key: 'load', title: '负重', type: 'chips', options: publishOptions.value.level_options },
|
||||
...publishOptions.value.quantity_items.map(item => ({
|
||||
key: `resource_${item.key}`,
|
||||
title: item.label,
|
||||
type: "range" as const,
|
||||
type: 'range' as const,
|
||||
unit: parseQuantityUnit(item.price),
|
||||
minPlaceholder: "最低",
|
||||
maxPlaceholder: "最高",
|
||||
minPlaceholder: '最低',
|
||||
maxPlaceholder: '最高',
|
||||
})),
|
||||
...publishOptions.value.skin_groups.map((group) => ({
|
||||
...publishOptions.value.skin_groups.map(group => ({
|
||||
key: group.key,
|
||||
title: group.title,
|
||||
type: "chips" as const,
|
||||
type: 'chips' as const,
|
||||
options: group.options,
|
||||
})),
|
||||
{ key: "secretKd", title: "绝密KD", type: "range", minPlaceholder: "最低", maxPlaceholder: "最高" },
|
||||
{ key: "rank", title: "段位", type: "chips", options: publishOptions.value.rank_options },
|
||||
{ key: "deposit", title: "押金", type: "range", unit: "元", minPlaceholder: "最低", maxPlaceholder: "最高" },
|
||||
]);
|
||||
{
|
||||
key: 'secretKd',
|
||||
title: '绝密KD',
|
||||
type: 'range',
|
||||
minPlaceholder: '最低',
|
||||
maxPlaceholder: '最高',
|
||||
},
|
||||
{ key: 'rank', title: '段位', type: 'chips', options: publishOptions.value.rank_options },
|
||||
{
|
||||
key: 'deposit',
|
||||
title: '押金',
|
||||
type: 'range',
|
||||
unit: '元',
|
||||
minPlaceholder: '最低',
|
||||
maxPlaceholder: '最高',
|
||||
},
|
||||
])
|
||||
|
||||
const activeFilterCount = computed(() => {
|
||||
const chipCount = Object.values(selectedFilters.value).reduce(
|
||||
(sum, values) => sum + values.length,
|
||||
0
|
||||
);
|
||||
)
|
||||
const rangeCount = Object.values(rangeFilters.value).filter(
|
||||
(range) => range.min || range.max
|
||||
).length;
|
||||
return chipCount + rangeCount;
|
||||
});
|
||||
range => range.min || range.max
|
||||
).length
|
||||
return chipCount + rangeCount
|
||||
})
|
||||
|
||||
const displayListings = computed(() => {
|
||||
return listings.value;
|
||||
});
|
||||
return listings.value
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
loadListings();
|
||||
loadHomeConfig();
|
||||
window.addEventListener("scroll", handleWindowScroll, { passive: true });
|
||||
});
|
||||
loadListings()
|
||||
loadHomeConfig()
|
||||
window.addEventListener('scroll', handleWindowScroll, { passive: true })
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("scroll", handleWindowScroll);
|
||||
});
|
||||
window.removeEventListener('scroll', handleWindowScroll)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => listingQuerySignature(),
|
||||
() => {
|
||||
loadListings(true);
|
||||
loadListings(true)
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
async function loadListings(reset = true) {
|
||||
if ((loadingMore.value && !reset) || (!hasMoreListings.value && !reset)) return;
|
||||
const requestSeq = ++listingRequestSeq;
|
||||
if ((loadingMore.value && !reset) || (!hasMoreListings.value && !reset)) return
|
||||
const requestSeq = ++listingRequestSeq
|
||||
if (reset) {
|
||||
loading.value = true;
|
||||
currentPage.value = 1;
|
||||
hasMoreListings.value = true;
|
||||
loading.value = true
|
||||
currentPage.value = 1
|
||||
hasMoreListings.value = true
|
||||
}
|
||||
loadingMore.value = true;
|
||||
loadFailed.value = false;
|
||||
loadingMore.value = true
|
||||
loadFailed.value = false
|
||||
try {
|
||||
const page = await fetchListingsPage(buildListingQuery(currentPage.value));
|
||||
if (requestSeq !== listingRequestSeq) return;
|
||||
listings.value = reset ? page.items : [...listings.value, ...page.items];
|
||||
totalListings.value = page.total;
|
||||
hasMoreListings.value = listings.value.length < page.total;
|
||||
currentPage.value = page.page + 1;
|
||||
requestAnimationFrame(handleWindowScroll);
|
||||
const page = await fetchListingsPage(buildListingQuery(currentPage.value))
|
||||
if (requestSeq !== listingRequestSeq) return
|
||||
listings.value = reset ? page.items : [...listings.value, ...page.items]
|
||||
totalListings.value = page.total
|
||||
hasMoreListings.value = listings.value.length < page.total
|
||||
currentPage.value = page.page + 1
|
||||
requestAnimationFrame(handleWindowScroll)
|
||||
} catch {
|
||||
if (reset) {
|
||||
listings.value = [];
|
||||
totalListings.value = 0;
|
||||
hasMoreListings.value = false;
|
||||
loadFailed.value = true;
|
||||
listings.value = []
|
||||
totalListings.value = 0
|
||||
hasMoreListings.value = false
|
||||
loadFailed.value = true
|
||||
}
|
||||
} finally {
|
||||
if (requestSeq === listingRequestSeq) {
|
||||
loading.value = false;
|
||||
loadingMore.value = false;
|
||||
loading.value = false
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHomeConfig() {
|
||||
try {
|
||||
const config = await fetchMobileHomeConfig();
|
||||
announcements.value = config.announcements;
|
||||
bannerSlides.value = config.banners;
|
||||
publishOptions.value = config.publish_options;
|
||||
const config = await fetchMobileHomeConfig()
|
||||
announcements.value = config.announcements
|
||||
bannerSlides.value = config.banners
|
||||
publishOptions.value = config.publish_options
|
||||
} catch {
|
||||
announcements.value = defaultHomeAnnouncements;
|
||||
bannerSlides.value = defaultHomeBanners;
|
||||
publishOptions.value = emptyListingPublishOptions;
|
||||
announcements.value = defaultHomeAnnouncements
|
||||
bannerSlides.value = defaultHomeBanners
|
||||
publishOptions.value = emptyListingPublishOptions
|
||||
}
|
||||
}
|
||||
|
||||
async function onRefresh() {
|
||||
refreshing.value = true;
|
||||
refreshing.value = true
|
||||
try {
|
||||
const [, nextHomeConfig] = await Promise.all([
|
||||
loadListings(true),
|
||||
fetchMobileHomeConfig(),
|
||||
]);
|
||||
announcements.value = nextHomeConfig.announcements;
|
||||
bannerSlides.value = nextHomeConfig.banners;
|
||||
publishOptions.value = nextHomeConfig.publish_options;
|
||||
showToast({ message: "刷新成功", icon: "passed" });
|
||||
const [, nextHomeConfig] = await Promise.all([loadListings(true), fetchMobileHomeConfig()])
|
||||
announcements.value = nextHomeConfig.announcements
|
||||
bannerSlides.value = nextHomeConfig.banners
|
||||
publishOptions.value = nextHomeConfig.publish_options
|
||||
showToast({ message: '刷新成功', icon: 'passed' })
|
||||
} catch {
|
||||
// 静默处理
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openFilters() {
|
||||
sortOpen.value = false;
|
||||
filterOpen.value = true;
|
||||
sortOpen.value = false
|
||||
filterOpen.value = true
|
||||
}
|
||||
|
||||
function toggleSortPanel() {
|
||||
sortOpen.value = !sortOpen.value;
|
||||
sortOpen.value = !sortOpen.value
|
||||
}
|
||||
|
||||
function selectSort(sortKey: string) {
|
||||
activeSort.value = sortKey;
|
||||
sortOpen.value = false;
|
||||
activeSort.value = sortKey
|
||||
sortOpen.value = false
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
selectedFilters.value = {};
|
||||
rangeFilters.value = {};
|
||||
searchValue.value = "";
|
||||
selectedFilters.value = {}
|
||||
rangeFilters.value = {}
|
||||
searchValue.value = ''
|
||||
}
|
||||
|
||||
function buildListingQuery(page: number): PublicListingQuery {
|
||||
@@ -267,79 +288,78 @@ function buildListingQuery(page: number): PublicListingQuery {
|
||||
page_size: mobilePageSize,
|
||||
keyword: searchValue.value.trim(),
|
||||
sort: activeSort.value,
|
||||
};
|
||||
const skinGroups: string[] = [];
|
||||
const skinNames: string[] = [];
|
||||
}
|
||||
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 === "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;
|
||||
const value = values.filter(Boolean).join(',')
|
||||
if (!value) continue
|
||||
if (key === 'server') query.server = 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);
|
||||
skinGroups.push(key)
|
||||
skinNames.push(...values)
|
||||
}
|
||||
}
|
||||
if (skinGroups.length) query.skin_group = skinGroups.join(",");
|
||||
if (skinNames.length) query.skin_name = skinNames.join(",");
|
||||
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 === "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.startsWith("resource_")) {
|
||||
const resourceKey = key.replace("resource_", "");
|
||||
query[`resource_${resourceKey}_min`] = min;
|
||||
query[`resource_${resourceKey}_max`] = max;
|
||||
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 === '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.startsWith('resource_')) {
|
||||
const resourceKey = key.replace('resource_', '')
|
||||
query[`resource_${resourceKey}_min`] = min
|
||||
query[`resource_${resourceKey}_max`] = max
|
||||
}
|
||||
}
|
||||
return query;
|
||||
return query
|
||||
}
|
||||
|
||||
function listingQuerySignature() {
|
||||
return JSON.stringify(buildListingQuery(1));
|
||||
return JSON.stringify(buildListingQuery(1))
|
||||
}
|
||||
|
||||
function parseOptionalNumber(value: string) {
|
||||
if (value === "") return undefined;
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : undefined;
|
||||
if (value === '') return undefined
|
||||
const number = Number(value)
|
||||
return Number.isFinite(number) ? number : undefined
|
||||
}
|
||||
|
||||
function handleWindowScroll() {
|
||||
if (window.innerHeight + window.scrollY < document.documentElement.scrollHeight - 360) return;
|
||||
loadListings(false);
|
||||
if (window.innerHeight + window.scrollY < document.documentElement.scrollHeight - 360) return
|
||||
loadListings(false)
|
||||
}
|
||||
|
||||
function isSkinGroupKey(key: string) {
|
||||
return publishOptions.value.skin_groups.some((group) => group.key === key);
|
||||
return publishOptions.value.skin_groups.some(group => group.key === key)
|
||||
}
|
||||
|
||||
function parseQuantityUnit(price: string) {
|
||||
const unit = price.split("/")[1]?.trim();
|
||||
return unit || undefined;
|
||||
const unit = price.split('/')[1]?.trim()
|
||||
return unit || undefined
|
||||
}
|
||||
|
||||
function uniqueOptions(values: string[]) {
|
||||
return [...new Set(values.map((item) => item.trim()).filter(Boolean))];
|
||||
return [...new Set(values.map(item => item.trim()).filter(Boolean))]
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -360,8 +380,13 @@ function uniqueOptions(values: string[]) {
|
||||
placeholder="搜区服 / 段位"
|
||||
class="home-search"
|
||||
/>
|
||||
<button class="mobile-service" type="button" :disabled="supportLoading" @click="handleSupportClick">
|
||||
{{ supportLoading ? "接入中" : "客服" }}
|
||||
<button
|
||||
class="mobile-service"
|
||||
type="button"
|
||||
:disabled="supportLoading"
|
||||
@click="handleSupportClick"
|
||||
>
|
||||
{{ supportLoading ? '接入中' : '客服' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -388,10 +413,7 @@ function uniqueOptions(values: string[]) {
|
||||
<section class="mobile-content">
|
||||
<!-- Banner 轮播 -->
|
||||
<van-swipe class="banner-swipe" :autoplay="3600" lazy-render>
|
||||
<van-swipe-item
|
||||
v-for="slide in bannerSlides"
|
||||
:key="slide.title || slide.image_url"
|
||||
>
|
||||
<van-swipe-item v-for="slide in bannerSlides" :key="slide.title || slide.image_url">
|
||||
<div
|
||||
class="mobile-banner"
|
||||
:class="[`tone-${slide.tone}`, { 'has-image': slide.image_url }]"
|
||||
@@ -432,11 +454,7 @@ function uniqueOptions(values: string[]) {
|
||||
@click="selectSort(option.key)"
|
||||
>
|
||||
<span>{{ option.label }}</span>
|
||||
<van-icon
|
||||
v-if="activeSort === option.key"
|
||||
name="success"
|
||||
:size="18"
|
||||
/>
|
||||
<van-icon v-if="activeSort === option.key" name="success" :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="result-count">
|
||||
@@ -460,9 +478,7 @@ function uniqueOptions(values: string[]) {
|
||||
image="search"
|
||||
description="没有符合条件的账号"
|
||||
>
|
||||
<van-button size="small" type="primary" @click="clearFilters">
|
||||
重置条件
|
||||
</van-button>
|
||||
<van-button size="small" type="primary" @click="clearFilters"> 重置条件 </van-button>
|
||||
</van-empty>
|
||||
|
||||
<!-- 列表卡片:全宽上下布局 -->
|
||||
@@ -510,10 +526,7 @@ function uniqueOptions(values: string[]) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-chip-row">
|
||||
<span
|
||||
v-for="chip in getListingChips(item)"
|
||||
:key="`${item.id}-${chip.label}`"
|
||||
>
|
||||
<span v-for="chip in getListingChips(item)" :key="`${item.id}-${chip.label}`">
|
||||
{{ chip.label }}:{{ chip.value }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user