|
|
@@ -18,13 +18,37 @@
|
|
|
<view class="price-row">
|
|
|
<view class="price-main">
|
|
|
<text class="price">{{ formatPriceWithSymbol(displayPrice) }}</text>
|
|
|
- <text v-if="hasMemberPrice && isMember" class="member-tag">会员价</text>
|
|
|
+ <text v-if="isSpecialDealRunning" class="special-tag">特价</text>
|
|
|
+ <text v-else-if="hasMemberPrice && isMember" class="member-tag">会员价</text>
|
|
|
</view>
|
|
|
- <view v-if="hasMemberPrice && isMember" class="price-original">
|
|
|
+ <view v-if="isSpecialDealRunning" class="price-original">
|
|
|
<text class="original-label">原价</text>
|
|
|
<text class="original-text">{{ formatPriceWithSymbol(originalPriceVal) }}</text>
|
|
|
</view>
|
|
|
- <view v-if="hasMemberPrice && !isMember" class="member-hint" @click="goUpgrade">加入会员享会员价 {{ formatPriceWithSymbol(memberPriceVal) }} ›</view>
|
|
|
+ <view v-else-if="hasMemberPrice && isMember" class="price-original">
|
|
|
+ <text class="original-label">原价</text>
|
|
|
+ <text class="original-text">{{ formatPriceWithSymbol(originalPriceVal) }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="hasMemberPrice && !isMember && !isSpecialDealRunning" class="member-hint" @click="goUpgrade">加入会员享会员价 {{ formatPriceWithSymbol(memberPriceVal) }} ›</view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 限时特价(扫码闪购)信息区 -->
|
|
|
+ <view v-if="showSpecialDealBlock" class="special-deal-block">
|
|
|
+ <view v-if="specialDealStatus === 'active'" class="special-deal-row">
|
|
|
+ <text class="special-deal-tag">限时特价</text>
|
|
|
+ <text class="special-deal-limit">限每人 {{ specialDeal.perUserLimit || 1 }} 件</text>
|
|
|
+ <text class="special-deal-remain">剩余 {{ specialDeal.remainingQuantity || 0 }} / {{ specialDeal.totalQuantity || 0 }}</text>
|
|
|
+ </view>
|
|
|
+ <view v-else-if="specialDealStatus === 'not_started'" class="special-deal-row">
|
|
|
+ <text class="special-deal-tag">限时特价</text>
|
|
|
+ <text class="special-deal-countdown">距开售还有 {{ countdownHours }} 小时</text>
|
|
|
+ </view>
|
|
|
+ <view v-else class="special-deal-row">
|
|
|
+ <text class="special-deal-status-text">活动已售罄 / 已结束</text>
|
|
|
+ </view>
|
|
|
+ <view v-if="specialDealStatus === 'active'" class="special-deal-progress">
|
|
|
+ <view class="special-deal-progress-bar" :style="{ width: (specialDeal.totalQuantity > 0 ? Math.min(100, Math.round(specialDeal.soldQuantity * 100 / specialDeal.totalQuantity)) : 0) + '%' }"></view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
<view v-if="product.giftLevelName" class="gift-tag">购买即送 {{ product.giftLevelName }} 会员</view>
|
|
|
<!-- 可选赠品选择区 -->
|
|
|
@@ -153,7 +177,8 @@
|
|
|
<script>
|
|
|
import { BrandColors } from '@/utils/brand-colors.js'
|
|
|
import config from '@/config.js'
|
|
|
-import { productDetail, productOrderCreate, productOrderPay, cartAdd, getShareQrCode, productSpecMap, getMyMembership } from '@/utils/api.js'
|
|
|
+import { productDetail, productOrderCreate, productOrderPay, cartAdd, getShareQrCode, productSpecMap, getMyMembership, getSpecialPriceInfo } from '@/utils/api.js'
|
|
|
+import { parseDate } from '@/utils/format.js'
|
|
|
import MpHtml from '@/components/mp-html/mp-html.vue'
|
|
|
import ContentSharePoster from '@/components/ContentSharePoster.vue'
|
|
|
import RelatedContent from '@/components/RelatedContent.vue'
|
|
|
@@ -187,7 +212,12 @@ export default {
|
|
|
// 会员身份(实时调 getMyMembership,FREE=非会员)
|
|
|
isMember: false,
|
|
|
memberLevel: 'FREE',
|
|
|
- bundleItems: []
|
|
|
+ bundleItems: [],
|
|
|
+ // 限时特价(扫码闪购):scene=id={productId}&sp={dealId}
|
|
|
+ specialDealId: null,
|
|
|
+ specialDeal: null,
|
|
|
+ specialDealStatus: 'invalid',
|
|
|
+ specialDealCountdownTimer: null
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
@@ -289,18 +319,42 @@ export default {
|
|
|
return true
|
|
|
},
|
|
|
displayPrice: function() {
|
|
|
+ // 特价活动(进行中/未开始)展示特价,覆盖会员价
|
|
|
+ if (this.specialDeal && (this.specialDealStatus === 'active' || this.specialDealStatus === 'not_started')) {
|
|
|
+ return this.specialDeal.specialPrice || 0
|
|
|
+ }
|
|
|
// 实际支付价(按会员身份)
|
|
|
return this.actualPrice
|
|
|
},
|
|
|
+ // 特价进行中/未开始(决定价格行的特价标签与划线原价展示)
|
|
|
+ isSpecialDealRunning: function() {
|
|
|
+ return this.specialDealStatus === 'active' || this.specialDealStatus === 'not_started'
|
|
|
+ },
|
|
|
+ // 特价信息区是否展示(进行中/未开始/已售罄)
|
|
|
+ showSpecialDealBlock: function() {
|
|
|
+ return this.specialDealStatus === 'active' || this.specialDealStatus === 'not_started' || this.specialDealStatus === 'sold_out'
|
|
|
+ },
|
|
|
+ // 距开售剩小时数(not_started 倒计时展示,向上取整)
|
|
|
+ countdownHours: function() {
|
|
|
+ if (!this.specialDeal || !this.specialDeal.startTime) return 0
|
|
|
+ var start = parseDate(this.specialDeal.startTime)
|
|
|
+ if (!start) return 0
|
|
|
+ var diff = start.getTime() - Date.now()
|
|
|
+ if (diff <= 0) return 0
|
|
|
+ return Math.ceil(diff / 3600000)
|
|
|
+ },
|
|
|
displayStock: function() {
|
|
|
if (this.selectedSku) return this.selectedSku.stock || 0
|
|
|
if (this.hasSku) return 0
|
|
|
return this.product.stock || 0
|
|
|
},
|
|
|
buyButtonDisabled: function() {
|
|
|
+ // 特价未开始时禁用购买按钮(到点自动刷新进入 active)
|
|
|
+ if (this.specialDealStatus === 'not_started') return true
|
|
|
return false
|
|
|
},
|
|
|
buyButtonText: function() {
|
|
|
+ if (this.specialDealStatus === 'not_started') return '距开售还有 ' + this.countdownHours + ' 小时'
|
|
|
return '立即购买'
|
|
|
},
|
|
|
// 展平 giftConfigs 内所有赠品项(后端按当前等级返回,通常单配置)
|
|
|
@@ -331,6 +385,11 @@ export default {
|
|
|
if (idMatch && idMatch[1]) pid = idMatch[1]
|
|
|
var refMatch = sceneDecoded.match(/ref=([^&]+)/)
|
|
|
if (refMatch && refMatch[1]) this.autoBindInviteCode(refMatch[1])
|
|
|
+ // 限时特价(扫码闪购):scene 含 sp=dealId 时记录活动 ID
|
|
|
+ var spMatch = sceneDecoded.match(/sp=(\d+)/)
|
|
|
+ if (spMatch && spMatch[1]) {
|
|
|
+ this.specialDealId = parseInt(spMatch[1], 10)
|
|
|
+ }
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
if (pid) {
|
|
|
@@ -338,10 +397,20 @@ export default {
|
|
|
}
|
|
|
// 实时查询会员身份,用于会员价展示和实付价计算
|
|
|
this.loadMembership()
|
|
|
+ // 特价入口:加载活动状态(倒计时/进行中/已售罄)
|
|
|
+ if (this.specialDealId) {
|
|
|
+ this.loadSpecialDealInfo()
|
|
|
+ }
|
|
|
},
|
|
|
onShow() {
|
|
|
this.fontScale = getFontScale()
|
|
|
},
|
|
|
+ onUnload() {
|
|
|
+ if (this.specialDealCountdownTimer) {
|
|
|
+ clearInterval(this.specialDealCountdownTimer)
|
|
|
+ this.specialDealCountdownTimer = null
|
|
|
+ }
|
|
|
+ },
|
|
|
methods: {
|
|
|
loadMembership: function() {
|
|
|
var that = this
|
|
|
@@ -364,6 +433,45 @@ export default {
|
|
|
that.memberLevel = 'FREE'
|
|
|
})
|
|
|
},
|
|
|
+ // 加载限时特价活动信息(扫码闪购入口;normal 状态回落原价)
|
|
|
+ loadSpecialDealInfo: function() {
|
|
|
+ var that = this
|
|
|
+ if (!this.specialDealId) return
|
|
|
+ getSpecialPriceInfo(this.specialDealId).then(function(res) {
|
|
|
+ if (res && res.code === 200 && res.data) {
|
|
|
+ that.specialDeal = res.data
|
|
|
+ that.specialDealStatus = that.computeSpecialDealStatus(res.data.runStatus)
|
|
|
+ } else {
|
|
|
+ that.specialDeal = null
|
|
|
+ that.specialDealStatus = 'invalid'
|
|
|
+ }
|
|
|
+ that.refreshSpecialDealTimer()
|
|
|
+ }).catch(function() {
|
|
|
+ that.specialDeal = null
|
|
|
+ that.specialDealStatus = 'invalid'
|
|
|
+ that.refreshSpecialDealTimer()
|
|
|
+ })
|
|
|
+ },
|
|
|
+ // 倒计时管理:仅 not_started 每秒刷新(到点自动变 active/ended);其余状态清除定时器
|
|
|
+ refreshSpecialDealTimer: function() {
|
|
|
+ if (this.specialDealStatus === 'not_started') {
|
|
|
+ if (this.specialDealCountdownTimer) return
|
|
|
+ this.specialDealCountdownTimer = setInterval(this.loadSpecialDealInfo, 1000)
|
|
|
+ } else {
|
|
|
+ if (this.specialDealCountdownTimer) {
|
|
|
+ clearInterval(this.specialDealCountdownTimer)
|
|
|
+ this.specialDealCountdownTimer = null
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // runStatus 归一化:ACTIVE/NOT_STARTED/SOLD_OUT 保留,其余(ENDED/DISABLED/DRAFT/INVALID)回落原价
|
|
|
+ computeSpecialDealStatus: function(runStatus) {
|
|
|
+ if (!runStatus) return 'invalid'
|
|
|
+ if (runStatus === 'ACTIVE') return 'active'
|
|
|
+ if (runStatus === 'NOT_STARTED') return 'not_started'
|
|
|
+ if (runStatus === 'SOLD_OUT') return 'sold_out'
|
|
|
+ return 'invalid'
|
|
|
+ },
|
|
|
getImageUrl: function(path) {
|
|
|
if (!path) return '/static/default-product.png'
|
|
|
if (path.startsWith('http') || path.startsWith('/static/')) return path
|
|
|
@@ -589,7 +697,13 @@ export default {
|
|
|
this.openLoginSheet()
|
|
|
return
|
|
|
}
|
|
|
- var price = this.actualPrice
|
|
|
+ // 限时特价(扫码闪购):仅活动进行中携带 dealId 走特价路径。
|
|
|
+ // 先清除再写入,保证普通购买不残留旧 dealId(特价仅扫码入口生效,后端亦校验商品/活动一致性)
|
|
|
+ uni.removeStorageSync('checkoutSpecialDealId')
|
|
|
+ if (this.specialDealId && this.specialDealStatus === 'active') {
|
|
|
+ uni.setStorageSync('checkoutSpecialDealId', this.specialDealId)
|
|
|
+ }
|
|
|
+ var price = this.displayPrice
|
|
|
var productId = this.product.id
|
|
|
// Linked SKU: 使用关联商品 ID
|
|
|
if (this.selectedSku && this.selectedSku.linkedProductId) {
|
|
|
@@ -800,6 +914,56 @@ export default {
|
|
|
border-radius: 8rpx;
|
|
|
align-self: flex-start;
|
|
|
}
|
|
|
+.special-tag {
|
|
|
+ margin-left: 8rpx;
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #fff;
|
|
|
+ background: $brand-primary;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ padding: 2rpx 10rpx;
|
|
|
+}
|
|
|
+.special-deal-block {
|
|
|
+ margin-top: 16rpx;
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ background: $uni-primary-pale;
|
|
|
+ border: 1rpx solid $brand-primary-tint-2;
|
|
|
+ border-radius: 16rpx;
|
|
|
+}
|
|
|
+.special-deal-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.special-deal-tag {
|
|
|
+ font-size: 22rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #fff;
|
|
|
+ background: $brand-primary;
|
|
|
+ border-radius: 8rpx;
|
|
|
+ padding: 2rpx 12rpx;
|
|
|
+ margin-right: 16rpx;
|
|
|
+}
|
|
|
+.special-deal-limit,
|
|
|
+.special-deal-countdown,
|
|
|
+.special-deal-remain,
|
|
|
+.special-deal-status-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: $brand-primary;
|
|
|
+}
|
|
|
+.special-deal-remain {
|
|
|
+ margin-left: 16rpx;
|
|
|
+}
|
|
|
+.special-deal-progress {
|
|
|
+ margin-top: 12rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ background: $brand-primary-tint-2;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.special-deal-progress-bar {
|
|
|
+ height: 100%;
|
|
|
+ background: linear-gradient(90deg, $brand-primary, #fb923c);
|
|
|
+ border-radius: 6rpx;
|
|
|
+}
|
|
|
.gift-tag {
|
|
|
display: inline-block;
|
|
|
margin-top: 12rpx;
|