feat(mobile): 全面重构移动端首页与详情页布局并统一3*3顶级保险高光视觉
This commit is contained in:
@@ -53,6 +53,12 @@ import {
|
||||
assetRegions,
|
||||
hasAcceleratedSaleRatio,
|
||||
hasGiftResources,
|
||||
isTopTierInsurance,
|
||||
getCoinWan,
|
||||
formatHafCoinM,
|
||||
readAssetString,
|
||||
getListingSkinGroups,
|
||||
getListingResources,
|
||||
} from '@/shared/utils/listingDisplay'
|
||||
import { buildMobileHomeListSignature, useMobileHomeCacheStore } from '@/stores/mobileHomeCache'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
@@ -775,16 +781,41 @@ function getMobileRatioText(item: Listing) {
|
||||
return `1元=${formatted}w哈夫币`
|
||||
}
|
||||
|
||||
function chipTone(label: string) {
|
||||
if (label.includes('哈夫币')) return 'coin'
|
||||
if (label.includes('保险')) return 'insurance'
|
||||
if (label.includes('体力')) return 'stamina'
|
||||
if (label.includes('负重')) return 'load'
|
||||
if (label.includes('段位')) return 'rank'
|
||||
if (label.includes('AWM')) return 'weapon'
|
||||
if (label.includes('总资产')) return 'asset'
|
||||
if (label.includes('方便')) return 'time'
|
||||
return 'default'
|
||||
function getItemHafCoin(item: Listing) {
|
||||
return formatHafCoinM(getCoinWan(item))
|
||||
}
|
||||
|
||||
function getItemInsurance(item: Listing) {
|
||||
return readAssetString(item, 'season_insurance') || '--'
|
||||
}
|
||||
|
||||
function getItemRatioText(item: Listing) {
|
||||
const val = getRatioValue(item)
|
||||
if (val <= 0) return null
|
||||
return `1元=${val.toFixed(0)}w`
|
||||
}
|
||||
|
||||
function getItemSecondaryChips(item: Listing) {
|
||||
return getListingChips(item).filter(
|
||||
c => c.label !== '哈夫币' && c.label !== '保险格数' && c.label !== 'AWM'
|
||||
)
|
||||
}
|
||||
|
||||
function getItemSkinPreview(item: Listing) {
|
||||
const groups = getListingSkinGroups(item)
|
||||
const list: Array<{ name: string; type: string }> = []
|
||||
for (const group of groups) {
|
||||
for (const opt of group.options) {
|
||||
list.push({ name: opt, type: group.key })
|
||||
if (list.length >= 3) return list
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
function getItemResourcePreview(item: Listing) {
|
||||
const resources = getListingResources(item)
|
||||
return resources.slice(0, 2)
|
||||
}
|
||||
|
||||
syncMobileHomeQuery()
|
||||
@@ -979,7 +1010,7 @@ syncMobileHomeQuery()
|
||||
<van-button size="small" type="primary" @click="clearFilters"> 重置条件 </van-button>
|
||||
</van-empty>
|
||||
|
||||
<!-- 列表卡片:全宽上下布局 -->
|
||||
<!-- 列表卡片:现代结构化卡片(对齐PC首页体验) -->
|
||||
<div class="mobile-list">
|
||||
<RouterLink
|
||||
v-for="item in displayListings"
|
||||
@@ -987,81 +1018,141 @@ syncMobileHomeQuery()
|
||||
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"
|
||||
<!-- 顶部精简元信息栏 -->
|
||||
<div class="mobile-card-header">
|
||||
<div class="header-tags-left">
|
||||
<span class="code-badge">{{ formatListingCode(item) }}</span>
|
||||
<span
|
||||
v-if="getServerRegion(item)"
|
||||
class="server-badge"
|
||||
:class="`tone-${getAccessBadgeMeta(getServerRegion(item)).tone}`"
|
||||
>
|
||||
<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>
|
||||
<van-icon :name="getAccessBadgeMeta(getServerRegion(item)).icon" :size="11" />
|
||||
<span>{{ getServerRegion(item) }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="getLoginMethod(item)"
|
||||
class="server-badge"
|
||||
:class="`tone-${getAccessBadgeMeta(getLoginMethod(item)).tone}`"
|
||||
>
|
||||
<van-icon :name="getAccessBadgeMeta(getLoginMethod(item)).icon" :size="11" />
|
||||
<span>{{ getLoginMethod(item) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="header-tags-right">
|
||||
<span v-if="formatEstimatedRentalDuration(item) !== '--'" class="aux-pill rental">
|
||||
可租 {{ formatEstimatedRentalDuration(item) }}
|
||||
</span>
|
||||
<span v-if="getDailyLoss(item)" class="aux-pill loss">
|
||||
耗 {{ getDailyLoss(item) }}/天
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中部:封面 + 核心资产与高光展台 -->
|
||||
<div class="mobile-card-body">
|
||||
<!-- 左侧封面 -->
|
||||
<div class="mobile-card-media">
|
||||
<img
|
||||
v-if="item.cover_url || item.screenshot_urls?.[0]"
|
||||
:src="item.cover_url || item.screenshot_urls?.[0]"
|
||||
:alt="formatListingCode(item)"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<div v-else class="media-empty">HFB</div>
|
||||
|
||||
<div
|
||||
v-if="hasGiftResources(item) || hasAcceleratedSaleRatio(item)"
|
||||
class="media-badges"
|
||||
>
|
||||
<span v-if="hasAcceleratedSaleRatio(item)" class="badge sale">特惠</span>
|
||||
<span v-if="hasGiftResources(item)" class="badge gift">有赠送</span>
|
||||
</div>
|
||||
|
||||
<div v-if="getRatioValue(item) > 0" class="media-ratio">
|
||||
比例 1:{{ getRatioValue(item).toFixed(1) }}
|
||||
</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}`"
|
||||
|
||||
<!-- 右侧信息流 -->
|
||||
<div class="mobile-card-info">
|
||||
<!-- 核心资产三大微卡 (对齐PC,3*3橙底高光) -->
|
||||
<div class="card-core-assets">
|
||||
<div class="core-asset-card coin-card">
|
||||
<span class="c-label">哈夫币</span>
|
||||
<strong class="c-val orange">{{ getItemHafCoin(item) }}</strong>
|
||||
</div>
|
||||
<div
|
||||
class="core-asset-card slot-card"
|
||||
:class="{ 'top-insurance': isTopTierInsurance(getItemInsurance(item)) }"
|
||||
>
|
||||
<van-icon :name="getAccessBadgeMeta(getServerRegion(item)).icon" :size="13" />
|
||||
{{ getServerRegion(item) }}
|
||||
<span class="c-label">保险</span>
|
||||
<strong
|
||||
class="c-val"
|
||||
:class="isTopTierInsurance(getItemInsurance(item)) ? 'orange' : 'slate'"
|
||||
>{{ getItemInsurance(item) }}</strong>
|
||||
</div>
|
||||
<div v-if="getItemRatioText(item)" class="core-asset-card ratio-card">
|
||||
<span class="c-label">币比</span>
|
||||
<strong class="c-val amber">{{ getItemRatioText(item) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 高光皮肤与仓库物资微胶囊 -->
|
||||
<div
|
||||
v-if="getItemSkinPreview(item).length || getItemResourcePreview(item).length"
|
||||
class="card-highlights"
|
||||
>
|
||||
<span
|
||||
v-for="skin in getItemSkinPreview(item)"
|
||||
:key="skin.name"
|
||||
class="mobile-skin-pill"
|
||||
:class="`skin-type-${skin.type}`"
|
||||
>
|
||||
<i class="quality-dot" />
|
||||
<span>{{ skin.name }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="getLoginMethod(item)"
|
||||
class="server-badge"
|
||||
:class="`tone-${getAccessBadgeMeta(getLoginMethod(item)).tone}`"
|
||||
v-for="res in getItemResourcePreview(item)"
|
||||
:key="res.key"
|
||||
class="mobile-res-pill"
|
||||
>
|
||||
<van-icon :name="getAccessBadgeMeta(getLoginMethod(item)).icon" :size="13" />
|
||||
{{ getLoginMethod(item) }}
|
||||
<span class="r-label">{{ res.label }}</span>
|
||||
<strong class="r-qty">{{ res.quantity }}</strong>
|
||||
<em v-if="res.mode === '赠送'" class="r-gift">送</em>
|
||||
</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 class="card-price-action">
|
||||
<div class="price-wrap">
|
||||
<div class="price-main">
|
||||
<small>¥</small>
|
||||
<strong>{{ formatMoney(getListingDisplayPrice(item)) }}</strong>
|
||||
</div>
|
||||
<span class="deposit-text">
|
||||
押金 {{ item.deposit_amount_cent > 0 ? `¥${formatCent(item.deposit_amount_cent)}` : '免押' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="rent-cta-btn">
|
||||
<span>去租用</span>
|
||||
<van-icon name="arrow" :size="10" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-chip-row">
|
||||
|
||||
<!-- 底部:过滤后的次要规格流 (无多余重复) -->
|
||||
<div v-if="getItemSecondaryChips(item).length" class="card-specs-row">
|
||||
<span
|
||||
v-for="chip in getListingChips(item)"
|
||||
:key="`${item.id}-${chip.label}`"
|
||||
:class="`tone-${chipTone(chip.label)}`"
|
||||
v-for="chip in getItemSecondaryChips(item).slice(0, 5)"
|
||||
:key="chip.label"
|
||||
class="spec-chip"
|
||||
>
|
||||
{{ chip.label }}:{{ chip.value }}
|
||||
<span class="k">{{ chip.label }}</span>
|
||||
<strong class="v">{{ chip.value }}</strong>
|
||||
</span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
|
||||
Reference in New Issue
Block a user