贯通商品编号全流程

This commit is contained in:
yml
2026-06-08 20:00:07 +08:00
parent 38d7469965
commit 494b2c068a
22 changed files with 322 additions and 107 deletions
@@ -16,6 +16,7 @@ import {
assetRegions,
formatEstimatedRentalDuration,
formatHafCoinM,
formatListingCode,
formatRatio,
getCoinWan,
getDailyLoss,
@@ -248,6 +249,16 @@ async function submitOrder() {
}
}
async function copyListingCode() {
if (!listing.value) return
try {
await navigator.clipboard.writeText(formatListingCode(listing.value))
ElMessage.success('商品编号已复制')
} catch {
ElMessage.error('复制失败')
}
}
function handleAgreementScroll(type: 'virtual' | 'renter') {
updateAgreementReadState(type)
}
@@ -336,6 +347,9 @@ function listingPrice(item: Listing) {
<span v-if="getLoginMethod(listing)">{{ getLoginMethod(listing) }}</span>
<span v-if="listing.rank_level">{{ listing.rank_level }}</span>
</div>
<button class="detail-code-chip" type="button" @click="copyListingCode">
编号 {{ formatListingCode(listing) }}
</button>
<h1>{{ getListingTitle(listing) }}</h1>
<p>{{ getListingSubtitle(listing) }}</p>
</div>
@@ -606,6 +620,19 @@ function listingPrice(item: Listing) {
font-weight: 900;
}
.detail-code-chip {
width: fit-content;
margin-top: 10px;
padding: 5px 10px;
border: 1px solid rgba(147, 197, 253, 0.6);
border-radius: 999px;
background: rgba(239, 246, 255, 0.92);
color: #2563eb;
font-size: 12px;
font-weight: 900;
cursor: pointer;
}
.detail-hero-overlay h1 {
margin: 0;
color: #ffffff;
@@ -13,8 +13,6 @@ import {
type HomeBannerSlide,
} from '@/features/listings/api/homeConfig'
import {
assetRegions,
formatHafCoinM,
getCoinM,
getCoinWan,
getListingChips,
@@ -102,7 +100,6 @@ type RangePresets = Record<string, Array<{ label: string; min: string; max: stri
const selectedFilters = ref<SelectedFilters>({})
const rangeFilters = ref<RangeFilters>({})
const searchValue = ref('')
const sortOpen = ref(false)
const filterOpen = ref(false)
@@ -306,7 +303,6 @@ function toggleSortPanel() {
function clearFilters() {
selectedFilters.value = {}
rangeFilters.value = {}
searchValue.value = ''
}
/* ------------------------------------------------------------------ */
@@ -324,12 +320,7 @@ const activeFilterCount = computed(() => {
})
const filteredListings = computed(() => {
const keyword = searchValue.value.trim().toLowerCase()
const filtered = listings.value.filter(item => {
if (!matchesFilters(item)) return false
if (!keyword) return true
return searchText(item).includes(keyword)
})
const filtered = listings.value.filter(item => matchesFilters(item))
return sortListings(filtered)
})
@@ -371,24 +362,6 @@ function inRange(value: number, range: { min: string; max: string }) {
return true
}
function searchText(item: Listing) {
return [
item.title,
item.description,
getServerRegion(item),
getLoginMethod(item),
item.rank_level,
readAssetString(item, 'season_insurance'),
readAssetString(item, 'stamina_level'),
readAssetString(item, 'load_level'),
formatHafCoinM(getCoinWan(item)),
assetRegions(item).join(' '),
...publishOptions.value.skin_groups.map(group => getSkinGroup(item, group.key).join(' ')),
]
.join(' ')
.toLowerCase()
}
function sortListings(items: Listing[]) {
const sorted = [...items]
if (sortBy.value === 'comprehensive') return sorted
@@ -438,19 +411,8 @@ function parseQuantityUnit(price: string) {
</span>
</div>
<!-- 搜索 + 排序 + 筛选入口 -->
<!-- 排序 + 筛选入口搜索统一保留在首页顶部 -->
<div class="toolbar">
<div class="search-box">
<svg class="search-icon" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clip-rule="evenodd"
/>
</svg>
<input v-model="searchValue" placeholder="搜区服 / 段位 / 哈夫币数量" />
</div>
<button type="button" class="toolbar-btn sort-btn" @click="toggleSortPanel">
<span>{{ activeSortLabel }}</span>
<svg
@@ -748,7 +710,7 @@ function parseQuantityUnit(price: string) {
}
/* ================================================================== */
/* 工具栏:搜索 + 排序 + 筛选 */
/* 工具栏:排序 + 筛选 */
/* ================================================================== */
.toolbar {
display: flex;
@@ -757,47 +719,6 @@ function parseQuantityUnit(price: string) {
margin-bottom: 12px;
}
.search-box {
display: flex;
align-items: center;
gap: 8px;
flex: 0 1 360px;
height: 38px;
padding: 0 12px;
border-radius: 10px;
background: #f4f6f8;
border: 1px solid transparent;
transition:
border-color 0.15s,
box-shadow 0.15s;
}
.search-box:focus-within {
border-color: #1477ff;
box-shadow: 0 0 0 3px rgba(20, 119, 255, 0.1);
background: #fff;
}
.search-icon {
width: 16px;
height: 16px;
color: #a0aab6;
flex-shrink: 0;
}
.search-box input {
border: none;
outline: none;
background: transparent;
color: #17233d;
font-size: 13px;
width: 100%;
}
.search-box input::placeholder {
color: #a0aab6;
}
.toolbar-btn {
display: flex;
align-items: center;
@@ -15,6 +15,7 @@ import { formatMoney } from '@/shared/utils/money'
import {
assetRegions,
formatHafCoinM,
formatListingCode,
formatRatio,
getCoinWan,
getDailyLoss,
@@ -257,6 +258,16 @@ function readError(error: unknown, fallback: string) {
}
return fallback
}
async function copyListingCode() {
if (!listing.value) return
try {
await navigator.clipboard.writeText(formatListingCode(listing.value))
showToast({ message: '商品编号已复制', icon: 'passed' })
} catch {
showToast({ message: '复制失败', icon: 'cross' })
}
}
</script>
<template>
@@ -309,6 +320,9 @@ function readError(error: unknown, fallback: string) {
</van-tag>
<van-tag v-if="listing.rank_level" plain size="medium">{{ listing.rank_level }}</van-tag>
</div>
<button class="mobile-code-chip" type="button" @click="copyListingCode">
编号 {{ formatListingCode(listing) }}
</button>
<h2 class="detail-title">{{ getListingTitle(listing) }}</h2>
<p class="detail-desc">{{ getListingSubtitle(listing) }}</p>
</div>
@@ -601,6 +615,17 @@ function readError(error: unknown, fallback: string) {
margin-bottom: 8px;
}
.mobile-code-chip {
margin-bottom: 8px;
padding: 4px 9px;
border: 1px solid #dbeafe;
border-radius: 999px;
background: #eff6ff;
color: #2563eb;
font-size: 12px;
font-weight: 800;
}
.detail-title {
margin: 0 0 6px;
font-size: 20px;