perf: 移除秒杀相关功能

This commit is contained in:
richarjiang
2026-09-10 11:33:19 +08:00
parent 51dea488f6
commit 57edd8dcc0
32 changed files with 131 additions and 2880 deletions

View File

@@ -1,185 +0,0 @@
<template>
<view v-if="flashSales.length" class="flash-sale-section">
<!-- Section header -->
<view class="section-header">
<view class="header-left">
<text class="section-title">限时秒杀</text>
<text v-if="hasOngoing" class="live-note">进行中</text>
</view>
</view>
<!-- Horizontal scroll cards -->
<scroll-view
scroll-x
:show-scrollbar="false"
class="flash-scroll"
>
<view class="flash-card-list">
<view
v-for="sale in flashSales"
:key="sale.id"
class="flash-card"
:class="cardPhaseClass(sale.phase)"
@tap="goToDetail(sale.id)"
>
<!-- Top gradient band -->
<view class="card-top">
<!-- Phase badge -->
<view class="phase-badge" :class="badgeClass(sale.phase)">
<text class="phase-badge-text">{{ phaseLabel(sale.phase) }}</text>
</view>
<!-- Countdown / status text -->
<view class="countdown-row">
<text v-if="sale.phase === FlashSalePhase.UPCOMING" class="countdown-label">距开始</text>
<text v-else-if="sale.phase === FlashSalePhase.ONGOING" class="countdown-label">剩余</text>
<view v-if="sale.phase === FlashSalePhase.UPCOMING || sale.phase === FlashSalePhase.ONGOING" class="countdown-blocks">
<text class="cd-block">{{ getSaleCountdown(sale).h }}</text>
<text class="cd-sep">:</text>
<text class="cd-block">{{ getSaleCountdown(sale).m }}</text>
<text class="cd-sep">:</text>
<text class="cd-block">{{ getSaleCountdown(sale).s }}</text>
</view>
</view>
</view>
<!-- Card body -->
<view class="card-body">
<text class="card-title">{{ sale.title }}</text>
<text class="card-type-name">{{ sale.cardType.name }}</text>
<!-- Price area -->
<view class="price-area">
<view class="flash-price-row">
<text class="flash-currency">¥</text>
<text class="flash-price">{{ formatPrice(invite.price(sale.flashPrice)) }}</text>
</view>
<text v-if="invite.eligible" class="original-price">好友 95 </text>
<text class="original-price">¥{{ formatPrice(sale.originalPrice) }}</text>
</view>
<!-- Stock progress -->
<view class="stock-area">
<view class="stock-bar">
<view
class="stock-fill"
:class="{ 'stock-fill--hot': getStockRatio(sale.soldCount, sale.totalStock) > 0.6 }"
:style="{ width: stockPercent(sale) }"
/>
</view>
<text class="stock-text">
{{ sale.phase === FlashSalePhase.SOLD_OUT ? '已售罄' : `${sale.remainingStock}/${sale.totalStock}` }}
</text>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { useInviteStore } from "../stores/invite"
const invite = useInviteStore()
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { FlashSalePhase } from '@mp-pilates/shared'
import type { FlashSaleListItem } from '@mp-pilates/shared'
import { formatPrice, getFlashSalePhaseLabel, getCountdownParts, getStockRatio, getStockPercent } from '../utils/format'
import { get } from '../utils/request'
const flashSales = ref<FlashSaleListItem[]>([])
const tick = ref(0)
let timer: ReturnType<typeof setInterval> | null = null
const hasOngoing = computed(() =>
flashSales.value.some((s) => s.phase === FlashSalePhase.ONGOING),
)
async function fetchFlashSales() {
try {
const data = await get<FlashSaleListItem[]>('/flash-sales')
flashSales.value = [...data]
} catch {
flashSales.value = []
}
}
// Expose for parent page refresh
defineExpose({ fetchFlashSales })
function phaseLabel(phase: FlashSalePhase): string {
return getFlashSalePhaseLabel(phase)
}
function cardPhaseClass(phase: FlashSalePhase): string {
if (phase === FlashSalePhase.ONGOING) return 'card--ongoing'
if (phase === FlashSalePhase.UPCOMING) return 'card--upcoming'
if (phase === FlashSalePhase.SOLD_OUT) return 'card--soldout'
return 'card--ended'
}
function badgeClass(phase: FlashSalePhase): string {
if (phase === FlashSalePhase.ONGOING) return 'badge--ongoing'
if (phase === FlashSalePhase.UPCOMING) return 'badge--upcoming'
return 'badge--inactive'
}
function stockPercent(sale: FlashSaleListItem): string {
return getStockPercent(sale.soldCount, sale.totalStock)
}
function getSaleCountdown(sale: FlashSaleListItem) {
void tick.value
const target = sale.phase === FlashSalePhase.UPCOMING ? sale.startTime : sale.endTime
return getCountdownParts(target)
}
function goToDetail(id: string) {
uni.navigateTo({ url: `/pages/flash-sale/detail?id=${id}` })
}
onMounted(() => {
fetchFlashSales()
timer = setInterval(() => {
tick.value++
}, 1000)
})
onUnmounted(() => {
if (timer) {
clearInterval(timer)
timer = null
}
})
</script>
<style lang="scss" scoped>
.flash-sale-section { margin: 36rpx 32rpx 0; }
.section-header { margin-bottom: 20rpx; }
.header-left { display: flex; align-items: baseline; justify-content: space-between; gap: 16rpx; }
.section-title { font-size: 30rpx; font-weight: 500; color: #514943; }
.live-note { font-size: 22rpx; color: #9b7b66; }
.flash-scroll { width: 100%; white-space: nowrap; }
.flash-card-list { display: inline-flex; gap: 20rpx; }
.flash-card { width: 400rpx; border-radius: 28rpx; overflow: hidden; border: 1rpx solid #e7ded5; flex-shrink: 0; display: inline-flex; flex-direction: column; white-space: normal; background: #fff; }
.card-top { padding: 20rpx 24rpx; background: #f1e6de; display: flex; flex-direction: column; gap: 12rpx; }
.card--upcoming .card-top { background: #eaf0e8; }
.card--soldout .card-top, .card--ended .card-top { background: #eeeae5; }
.phase-badge-text { font-size: 22rpx; color: #7c6656; }
.countdown-row, .countdown-blocks { display: flex; align-items: baseline; gap: 8rpx; }
.countdown-label { font-size: 20rpx; color: #8b817b; }
.cd-block, .cd-sep { font-size: 24rpx; color: #7c6656; font-variant-numeric: tabular-nums; }
.card-body { padding: 24rpx; display: flex; flex-direction: column; gap: 10rpx; }
.card-title { font-size: 28rpx; font-weight: 500; color: #514943; line-height: 1.5; overflow-wrap: anywhere; }
.card-type-name { font-size: 22rpx; color: #8b817b; }
.price-area { display: flex; align-items: baseline; flex-wrap: wrap; gap: 12rpx; margin-top: 8rpx; }
.flash-price-row { display: flex; align-items: baseline; gap: 4rpx; }
.flash-currency { font-size: 22rpx; color: #8b6c5b; }
.flash-price { font-size: 38rpx; font-weight: 500; color: #8b6c5b; }
.original-price { font-size: 21rpx; color: #a59b93; text-decoration: line-through; }
.stock-area { margin-top: 12rpx; display: flex; flex-direction: column; gap: 10rpx; }
.stock-bar { height: 6rpx; background: #f3efea; border-radius: 6rpx; overflow: hidden; }
.stock-fill { height: 100%; border-radius: 6rpx; background: #bea28e; }
.stock-text { font-size: 20rpx; color: #8b817b; }
</style>