完善测试建表并统一前端格式
This commit is contained in:
@@ -49,10 +49,7 @@ import {
|
||||
hasAcceleratedSaleRatio,
|
||||
hasGiftResources,
|
||||
} from '@/shared/utils/listingDisplay'
|
||||
import {
|
||||
buildMobileHomeListSignature,
|
||||
useMobileHomeCacheStore,
|
||||
} from '@/stores/mobileHomeCache'
|
||||
import { buildMobileHomeListSignature, useMobileHomeCacheStore } from '@/stores/mobileHomeCache'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -85,7 +82,6 @@ const mobilePageSize = 10
|
||||
let listingRequestSeq = 0
|
||||
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
|
||||
const sortOptions = [
|
||||
{ key: 'recommended', label: '综合推荐' },
|
||||
{ key: 'coinDesc', label: '哈夫币' },
|
||||
@@ -734,254 +730,259 @@ syncMobileHomeQuery()
|
||||
<template>
|
||||
<main class="mobile-shell">
|
||||
<div ref="scrollEl" class="mobile-scroll" @scroll.passive="handleListScroll">
|
||||
<!-- ========== Hero 区域:顶部搜索与公告 ========== -->
|
||||
<section class="mobile-hero">
|
||||
<div class="mobile-topbar">
|
||||
<div class="mobile-brand">
|
||||
<span class="mobile-logo">锤</span>
|
||||
<div>
|
||||
<strong>大锤商行</strong>
|
||||
<small>哈夫币租号</small>
|
||||
<!-- ========== Hero 区域:顶部搜索与公告 ========== -->
|
||||
<section class="mobile-hero">
|
||||
<div class="mobile-topbar">
|
||||
<div class="mobile-brand">
|
||||
<span class="mobile-logo">锤</span>
|
||||
<div>
|
||||
<strong>大锤商行</strong>
|
||||
<small>哈夫币租号</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="mobile-service"
|
||||
type="button"
|
||||
:disabled="supportLoading"
|
||||
@click="handleSupportClick"
|
||||
>
|
||||
{{ supportLoading ? '接入中' : '客服' }}
|
||||
</button>
|
||||
</div>
|
||||
<RouterLink class="add-home-strip" to="/m/add-home-guide" aria-label="查看添加到手机桌面教程">
|
||||
<span class="add-home-mark">
|
||||
<van-icon name="home-o" :size="18" />
|
||||
</span>
|
||||
<span class="add-home-copy">
|
||||
<strong>添加到手机桌面</strong>
|
||||
<small>下次像 App 一样快速打开</small>
|
||||
</span>
|
||||
<span class="add-home-cta">去添加</span>
|
||||
</RouterLink>
|
||||
<van-search
|
||||
v-model="searchValue"
|
||||
shape="round"
|
||||
placeholder="搜编号 / 区服 / 段位"
|
||||
class="home-search"
|
||||
/>
|
||||
|
||||
<!-- 防骗提示卡片(不用 van-notice-bar) -->
|
||||
<RouterLink class="fraud-tip" to="/m/announcements">
|
||||
<van-icon name="warning-o" :size="16" color="#b8860b" />
|
||||
<span class="fraud-dot"></span>
|
||||
<van-swipe
|
||||
class="announcement-swipe"
|
||||
vertical
|
||||
:autoplay="3200"
|
||||
:show-indicators="false"
|
||||
touchable
|
||||
>
|
||||
<van-swipe-item v-for="item in announcements" :key="item">
|
||||
<span>{{ item }}</span>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<van-icon class="fraud-more" name="arrow" :size="14" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
|
||||
<!-- ========== Content 区域 ========== -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<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">
|
||||
<div
|
||||
class="mobile-banner"
|
||||
:class="[`tone-${slide.tone}`, { 'has-image': slide.image_url }]"
|
||||
>
|
||||
<img
|
||||
v-if="slide.image_url"
|
||||
class="banner-image"
|
||||
:src="slide.image_url"
|
||||
:alt="slide.title || slide.eyebrow || '首页轮播图'"
|
||||
/>
|
||||
<div class="banner-copy">
|
||||
<p v-if="slide.eyebrow">{{ slide.eyebrow }}</p>
|
||||
<h1 v-if="slide.title">{{ slide.title }}</h1>
|
||||
<span v-if="slide.pill" class="banner-pill">{{ slide.pill }}</span>
|
||||
</div>
|
||||
<div v-if="slide.badge" class="banner-badge">{{ slide.badge }}</div>
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
|
||||
<div class="zone-strip" aria-label="账号专区">
|
||||
<button
|
||||
v-for="zone in visibleZoneOptions"
|
||||
:key="zone.key"
|
||||
class="mobile-service"
|
||||
type="button"
|
||||
class="zone-pill"
|
||||
:class="{ active: activeZone === zone.key }"
|
||||
@click="selectZone(zone.key)"
|
||||
:disabled="supportLoading"
|
||||
@click="handleSupportClick"
|
||||
>
|
||||
<strong>{{ zone.label }}</strong>
|
||||
<span>{{ zone.count }}</span>
|
||||
<small>{{ zone.hint }}</small>
|
||||
{{ supportLoading ? '接入中' : '客服' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<button type="button" class="sort-entry" @click="toggleSortPanel">
|
||||
<span>{{ activeSortLabel }}</span>
|
||||
<van-icon :name="sortOpen ? 'arrow-up' : 'arrow-down'" :size="14" />
|
||||
</button>
|
||||
<button type="button" class="filter-entry" @click="openFilters">
|
||||
<van-icon name="filter-o" :size="16" />
|
||||
<span>筛选</span>
|
||||
<em v-if="activeFilterCount">{{ activeFilterCount }}</em>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="sortOpen" class="sort-panel">
|
||||
<button
|
||||
v-for="option in sortOptions"
|
||||
:key="option.key"
|
||||
type="button"
|
||||
:class="{ active: activeSort === option.key }"
|
||||
@click="selectSort(option.key)"
|
||||
>
|
||||
<span>{{ option.label }}</span>
|
||||
<van-icon v-if="activeSort === option.key" name="success" :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="result-count">
|
||||
<strong>{{ totalListings }}</strong>
|
||||
<span>个可租账号</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载/错误状态 -->
|
||||
<van-loading v-if="loading" class="state-loading" size="24px" vertical>
|
||||
正在加载优质账号...
|
||||
</van-loading>
|
||||
<van-notice-bar
|
||||
v-else-if="loadFailed"
|
||||
left-icon="info-o"
|
||||
color="#6b7a90"
|
||||
background="transparent"
|
||||
text="接口暂不可用,请稍后刷新。"
|
||||
<RouterLink
|
||||
class="add-home-strip"
|
||||
to="/m/add-home-guide"
|
||||
aria-label="查看添加到手机桌面教程"
|
||||
>
|
||||
<span class="add-home-mark">
|
||||
<van-icon name="home-o" :size="18" />
|
||||
</span>
|
||||
<span class="add-home-copy">
|
||||
<strong>添加到手机桌面</strong>
|
||||
<small>下次像 App 一样快速打开</small>
|
||||
</span>
|
||||
<span class="add-home-cta">去添加</span>
|
||||
</RouterLink>
|
||||
<van-search
|
||||
v-model="searchValue"
|
||||
shape="round"
|
||||
placeholder="搜编号 / 区服 / 段位"
|
||||
class="home-search"
|
||||
/>
|
||||
<van-empty
|
||||
v-else-if="displayListings.length === 0"
|
||||
class="mobile-empty"
|
||||
image="search"
|
||||
description="没有符合条件的账号"
|
||||
>
|
||||
<van-button size="small" type="primary" @click="clearFilters"> 重置条件 </van-button>
|
||||
</van-empty>
|
||||
|
||||
<!-- 列表卡片:全宽上下布局 -->
|
||||
<div class="mobile-list">
|
||||
<RouterLink
|
||||
v-for="item in displayListings"
|
||||
:key="item.id"
|
||||
class="mobile-card"
|
||||
:to="`/m/listings/${item.id}`"
|
||||
<!-- 防骗提示卡片(不用 van-notice-bar) -->
|
||||
<RouterLink class="fraud-tip" to="/m/announcements">
|
||||
<van-icon name="warning-o" :size="16" color="#b8860b" />
|
||||
<span class="fraud-dot"></span>
|
||||
<van-swipe
|
||||
class="announcement-swipe"
|
||||
vertical
|
||||
:autoplay="3200"
|
||||
:show-indicators="false"
|
||||
touchable
|
||||
>
|
||||
<div class="card-cover">
|
||||
<img
|
||||
v-if="item.cover_url"
|
||||
:src="item.cover_url"
|
||||
:alt="getListingTitle(item)"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<span v-else>图</span>
|
||||
<div
|
||||
v-if="hasGiftResources(item) || hasAcceleratedSaleRatio(item)"
|
||||
class="card-cover-labels"
|
||||
>
|
||||
<em v-if="hasGiftResources(item)">有赠送</em>
|
||||
<em v-if="hasAcceleratedSaleRatio(item)">特惠</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-main">
|
||||
<div class="card-title-row">
|
||||
<h2>{{ getListingTitle(item) }}</h2>
|
||||
</div>
|
||||
<div class="card-meta-row">
|
||||
<div class="mobile-listing-no">{{ formatListingCode(item) }}</div>
|
||||
<div
|
||||
v-if="getDailyLoss(item) || formatEstimatedRentalDuration(item) !== '--'"
|
||||
class="rental-meta-badge"
|
||||
>
|
||||
<span v-if="getDailyLoss(item)">消耗 {{ getDailyLoss(item) }}/天</span>
|
||||
<span
|
||||
v-if="getDailyLoss(item) && formatEstimatedRentalDuration(item) !== '--'"
|
||||
class="meta-separator"
|
||||
>
|
||||
·
|
||||
</span>
|
||||
<span v-if="formatEstimatedRentalDuration(item) !== '--'">
|
||||
租期 {{ formatEstimatedRentalDuration(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-action-row">
|
||||
<p v-if="getMobileRatioText(item)" class="card-subtitle">
|
||||
{{ getMobileRatioText(item) }}
|
||||
</p>
|
||||
<div class="card-badges-row">
|
||||
<span
|
||||
class="server-badge"
|
||||
:class="`tone-${getAccessBadgeMeta(getServerRegion(item)).tone}`"
|
||||
>
|
||||
<van-icon :name="getAccessBadgeMeta(getServerRegion(item)).icon" :size="13" />
|
||||
{{ getServerRegion(item) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="getLoginMethod(item)"
|
||||
class="server-badge"
|
||||
:class="`tone-${getAccessBadgeMeta(getLoginMethod(item)).tone}`"
|
||||
>
|
||||
<van-icon :name="getAccessBadgeMeta(getLoginMethod(item)).icon" :size="13" />
|
||||
{{ getLoginMethod(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="price-col">
|
||||
<strong>¥{{ formatMoney(getListingDisplayPrice(item)) }}</strong>
|
||||
<span class="rent-sub">押金 ¥{{ formatCent(item.deposit_amount_cent) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-chip-row">
|
||||
<span
|
||||
v-for="chip in getListingChips(item)"
|
||||
:key="`${item.id}-${chip.label}`"
|
||||
:class="`tone-${chipTone(chip.label)}`"
|
||||
>
|
||||
{{ chip.label }}:{{ chip.value }}
|
||||
</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div v-if="!loading && displayListings.length" class="mobile-load-state">
|
||||
<span v-if="loadingMore">正在加载更多账号...</span>
|
||||
<span v-else-if="!hasMoreListings">已经到底了</span>
|
||||
</div>
|
||||
<van-swipe-item v-for="item in announcements" :key="item">
|
||||
<span>{{ item }}</span>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<van-icon class="fraud-more" name="arrow" :size="14" />
|
||||
</RouterLink>
|
||||
</section>
|
||||
</van-pull-refresh>
|
||||
|
||||
<footer class="home-footer">
|
||||
<a
|
||||
href="https://beian.miit.gov.cn/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="home-footer-icp"
|
||||
>湘ICP备2025146668号</a>
|
||||
<span class="home-footer-divider">|</span>
|
||||
<span class="home-footer-copyright">版权所有 ©2026 大锤网络游戏有限公司</span>
|
||||
</footer>
|
||||
<!-- ========== Content 区域 ========== -->
|
||||
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
|
||||
<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">
|
||||
<div
|
||||
class="mobile-banner"
|
||||
:class="[`tone-${slide.tone}`, { 'has-image': slide.image_url }]"
|
||||
>
|
||||
<img
|
||||
v-if="slide.image_url"
|
||||
class="banner-image"
|
||||
:src="slide.image_url"
|
||||
:alt="slide.title || slide.eyebrow || '首页轮播图'"
|
||||
/>
|
||||
<div class="banner-copy">
|
||||
<p v-if="slide.eyebrow">{{ slide.eyebrow }}</p>
|
||||
<h1 v-if="slide.title">{{ slide.title }}</h1>
|
||||
<span v-if="slide.pill" class="banner-pill">{{ slide.pill }}</span>
|
||||
</div>
|
||||
<div v-if="slide.badge" class="banner-badge">{{ slide.badge }}</div>
|
||||
</div>
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
|
||||
<div class="zone-strip" aria-label="账号专区">
|
||||
<button
|
||||
v-for="zone in visibleZoneOptions"
|
||||
:key="zone.key"
|
||||
type="button"
|
||||
class="zone-pill"
|
||||
:class="{ active: activeZone === zone.key }"
|
||||
@click="selectZone(zone.key)"
|
||||
>
|
||||
<strong>{{ zone.label }}</strong>
|
||||
<span>{{ zone.count }}</span>
|
||||
<small>{{ zone.hint }}</small>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<button type="button" class="sort-entry" @click="toggleSortPanel">
|
||||
<span>{{ activeSortLabel }}</span>
|
||||
<van-icon :name="sortOpen ? 'arrow-up' : 'arrow-down'" :size="14" />
|
||||
</button>
|
||||
<button type="button" class="filter-entry" @click="openFilters">
|
||||
<van-icon name="filter-o" :size="16" />
|
||||
<span>筛选</span>
|
||||
<em v-if="activeFilterCount">{{ activeFilterCount }}</em>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="sortOpen" class="sort-panel">
|
||||
<button
|
||||
v-for="option in sortOptions"
|
||||
:key="option.key"
|
||||
type="button"
|
||||
:class="{ active: activeSort === option.key }"
|
||||
@click="selectSort(option.key)"
|
||||
>
|
||||
<span>{{ option.label }}</span>
|
||||
<van-icon v-if="activeSort === option.key" name="success" :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="result-count">
|
||||
<strong>{{ totalListings }}</strong>
|
||||
<span>个可租账号</span>
|
||||
</div>
|
||||
|
||||
<!-- 加载/错误状态 -->
|
||||
<van-loading v-if="loading" class="state-loading" size="24px" vertical>
|
||||
正在加载优质账号...
|
||||
</van-loading>
|
||||
<van-notice-bar
|
||||
v-else-if="loadFailed"
|
||||
left-icon="info-o"
|
||||
color="#6b7a90"
|
||||
background="transparent"
|
||||
text="接口暂不可用,请稍后刷新。"
|
||||
/>
|
||||
<van-empty
|
||||
v-else-if="displayListings.length === 0"
|
||||
class="mobile-empty"
|
||||
image="search"
|
||||
description="没有符合条件的账号"
|
||||
>
|
||||
<van-button size="small" type="primary" @click="clearFilters"> 重置条件 </van-button>
|
||||
</van-empty>
|
||||
|
||||
<!-- 列表卡片:全宽上下布局 -->
|
||||
<div class="mobile-list">
|
||||
<RouterLink
|
||||
v-for="item in displayListings"
|
||||
:key="item.id"
|
||||
class="mobile-card"
|
||||
:to="`/m/listings/${item.id}`"
|
||||
>
|
||||
<div class="card-cover">
|
||||
<img
|
||||
v-if="item.cover_url"
|
||||
:src="item.cover_url"
|
||||
:alt="getListingTitle(item)"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<span v-else>图</span>
|
||||
<div
|
||||
v-if="hasGiftResources(item) || hasAcceleratedSaleRatio(item)"
|
||||
class="card-cover-labels"
|
||||
>
|
||||
<em v-if="hasGiftResources(item)">有赠送</em>
|
||||
<em v-if="hasAcceleratedSaleRatio(item)">特惠</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-main">
|
||||
<div class="card-title-row">
|
||||
<h2>{{ getListingTitle(item) }}</h2>
|
||||
</div>
|
||||
<div class="card-meta-row">
|
||||
<div class="mobile-listing-no">{{ formatListingCode(item) }}</div>
|
||||
<div
|
||||
v-if="getDailyLoss(item) || formatEstimatedRentalDuration(item) !== '--'"
|
||||
class="rental-meta-badge"
|
||||
>
|
||||
<span v-if="getDailyLoss(item)">消耗 {{ getDailyLoss(item) }}/天</span>
|
||||
<span
|
||||
v-if="getDailyLoss(item) && formatEstimatedRentalDuration(item) !== '--'"
|
||||
class="meta-separator"
|
||||
>
|
||||
·
|
||||
</span>
|
||||
<span v-if="formatEstimatedRentalDuration(item) !== '--'">
|
||||
租期 {{ formatEstimatedRentalDuration(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-action-row">
|
||||
<p v-if="getMobileRatioText(item)" class="card-subtitle">
|
||||
{{ getMobileRatioText(item) }}
|
||||
</p>
|
||||
<div class="card-badges-row">
|
||||
<span
|
||||
class="server-badge"
|
||||
:class="`tone-${getAccessBadgeMeta(getServerRegion(item)).tone}`"
|
||||
>
|
||||
<van-icon :name="getAccessBadgeMeta(getServerRegion(item)).icon" :size="13" />
|
||||
{{ getServerRegion(item) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="getLoginMethod(item)"
|
||||
class="server-badge"
|
||||
:class="`tone-${getAccessBadgeMeta(getLoginMethod(item)).tone}`"
|
||||
>
|
||||
<van-icon :name="getAccessBadgeMeta(getLoginMethod(item)).icon" :size="13" />
|
||||
{{ getLoginMethod(item) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="price-col">
|
||||
<strong>¥{{ formatMoney(getListingDisplayPrice(item)) }}</strong>
|
||||
<span class="rent-sub">押金 ¥{{ formatCent(item.deposit_amount_cent) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-chip-row">
|
||||
<span
|
||||
v-for="chip in getListingChips(item)"
|
||||
:key="`${item.id}-${chip.label}`"
|
||||
:class="`tone-${chipTone(chip.label)}`"
|
||||
>
|
||||
{{ chip.label }}:{{ chip.value }}
|
||||
</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div v-if="!loading && displayListings.length" class="mobile-load-state">
|
||||
<span v-if="loadingMore">正在加载更多账号...</span>
|
||||
<span v-else-if="!hasMoreListings">已经到底了</span>
|
||||
</div>
|
||||
</section>
|
||||
</van-pull-refresh>
|
||||
|
||||
<footer class="home-footer">
|
||||
<a
|
||||
href="https://beian.miit.gov.cn/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="home-footer-icp"
|
||||
>湘ICP备2025146668号</a
|
||||
>
|
||||
<span class="home-footer-divider">|</span>
|
||||
<span class="home-footer-copyright">版权所有 ©2026 大锤网络游戏有限公司</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<MobileHomeFilterSheet
|
||||
|
||||
Reference in New Issue
Block a user