|
@@ -0,0 +1,375 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="page" :style="{ '--font-scale': fontScale }">
|
|
|
|
|
+ <!-- 登录浮层(全局 login-sheet 组件,mixin 提供 openLoginSheet) -->
|
|
|
|
|
+ <login-sheet ref="loginSheet" @success="onSheetLoginSuccess" />
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="loading" class="state-box">
|
|
|
|
|
+ <text class="state-text">加载中...</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <block v-else>
|
|
|
|
|
+ <!-- 标题:正常态 / 失效态 -->
|
|
|
|
|
+ <view class="hero">
|
|
|
|
|
+ <text class="hero-emoji">🎁</text>
|
|
|
|
|
+ <text class="hero-text">{{ isInvalid ? '分享已失效' : '好友送你一张会员券' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 券卡片(token 无效无 detail,不渲染卡片) -->
|
|
|
|
|
+ <view v-if="detail" class="coupon-card" :class="isInvalid ? 'coupon-card-disabled' : ''">
|
|
|
|
|
+ <view class="card-left">
|
|
|
|
|
+ <text class="card-value">{{ formatValue(detail) }}</text>
|
|
|
|
|
+ <text class="card-type-label">{{ typeLabel(detail.couponType) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card-right">
|
|
|
|
|
+ <text class="card-name">{{ detail.couponName }}</text>
|
|
|
|
|
+ <text class="card-condition">{{ conditionText(detail.minSpend) }}</text>
|
|
|
|
|
+ <text class="card-scope">会员专享</text>
|
|
|
|
|
+ <text class="card-expiry">有效期至 {{ formatDate(detail.validUntil) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 赠送人信息(正常态展示) -->
|
|
|
|
|
+ <view v-if="!isInvalid && detail && detail.giverNickname" class="giver-row">
|
|
|
|
|
+ <image v-if="detail.giverAvatar" class="giver-avatar" :src="detail.giverAvatar" mode="aspectFill"></image>
|
|
|
|
|
+ <view v-else class="giver-avatar giver-avatar-default">{{ giverInitial }}</view>
|
|
|
|
|
+ <text class="giver-name">赠送人:{{ detail.giverNickname }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 正常态:确认领取 -->
|
|
|
|
|
+ <view v-if="!isInvalid" class="action-area">
|
|
|
|
|
+ <button class="claim-btn" :disabled="claiming" @click="onConfirmClaim">{{ claiming ? '领取中...' : '确认领取' }}</button>
|
|
|
|
|
+ <text class="claim-tip">领取后可用于购买会员</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 失效态:提示 + 知道了 -->
|
|
|
|
|
+ <view v-else class="action-area">
|
|
|
|
|
+ <text class="invalid-tip">⚠ {{ invalidText }}</text>
|
|
|
|
|
+ <button class="know-btn" @click="onKnow">知道了</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </block>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { getCouponShareDetail, claimCouponShare } from '../../utils/api.js'
|
|
|
|
|
+import { parseDate } from '../../utils/format.js'
|
|
|
|
|
+import fontScaleMixin from '@/mixins/font-scale.js'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ mixins: [fontScaleMixin],
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ shareToken: '',
|
|
|
|
|
+ loading: true,
|
|
|
|
|
+ detail: null,
|
|
|
|
|
+ loadError: '',
|
|
|
|
|
+ claimError: '',
|
|
|
|
|
+ claiming: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ isInvalid: function() {
|
|
|
|
|
+ if (this.loading) return false
|
|
|
|
|
+ if (this.loadError || this.claimError) return true
|
|
|
|
|
+ if (!this.detail) return true
|
|
|
|
|
+ if (this.detail.status !== 'PENDING') return true
|
|
|
|
|
+ if (this.detail.claimable !== true) return true
|
|
|
|
|
+ return false
|
|
|
|
|
+ },
|
|
|
|
|
+ invalidText: function() {
|
|
|
|
|
+ if (this.loadError) return this.loadError
|
|
|
|
|
+ if (this.claimError) return this.claimError
|
|
|
|
|
+ if (!this.detail) return '分享不存在或已失效'
|
|
|
|
|
+ var status = this.detail.status || ''
|
|
|
|
|
+ if (status === 'CLAIMED') return '该分享已被领取'
|
|
|
|
|
+ if (status === 'REVOKED') return '该分享已撤回'
|
|
|
|
|
+ if (status === 'EXPIRED') return '该分享已过期'
|
|
|
|
|
+ if (this.detail.claimable !== true) return '优惠券已失效'
|
|
|
|
|
+ return '分享不存在或已失效'
|
|
|
|
|
+ },
|
|
|
|
|
+ giverInitial: function() {
|
|
|
|
|
+ var name = this.detail && this.detail.giverNickname
|
|
|
|
|
+ if (!name) return '友'
|
|
|
|
|
+ return name.substring(0, 1)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onLoad(options) {
|
|
|
|
|
+ this.shareToken = (options && options.shareToken) || ''
|
|
|
|
|
+ this.loadDetail()
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ loadDetail() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (!this.shareToken) {
|
|
|
|
|
+ this.loadError = '分享不存在或已失效'
|
|
|
|
|
+ this.loading = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.loading = true
|
|
|
|
|
+ this.loadError = ''
|
|
|
|
|
+ this.claimError = ''
|
|
|
|
|
+ getCouponShareDetail(this.shareToken).then(function(res) {
|
|
|
|
|
+ self.detail = (res && res.data) || null
|
|
|
|
|
+ }).catch(function(e) {
|
|
|
|
|
+ self.loadError = (e && e.message) || '分享不存在或已失效'
|
|
|
|
|
+ }).then(function() {
|
|
|
|
|
+ self.loading = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ onConfirmClaim() {
|
|
|
|
|
+ if (this.claiming || this.isInvalid) return
|
|
|
|
|
+ // 未登录先弹登录浮层,登录成功后经 onLoginSuccess 自动继续领取
|
|
|
|
|
+ if (!uni.getStorageSync('token')) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ this.openLoginSheet({ onSuccess: function() { self.doClaim() } })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ this.doClaim()
|
|
|
|
|
+ },
|
|
|
|
|
+ // 登录浮层登录成功回调(login-sheet mixin onSheetLoginSuccess 调用)
|
|
|
|
|
+ onLoginSuccess() {
|
|
|
|
|
+ this.doClaim()
|
|
|
|
|
+ },
|
|
|
|
|
+ doClaim() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (this.claiming || !this.shareToken) return
|
|
|
|
|
+ this.claiming = true
|
|
|
|
|
+ claimCouponShare(this.shareToken).then(function(res) {
|
|
|
|
|
+ if (res && res.code === 200) {
|
|
|
|
|
+ uni.showToast({ title: '领取成功', icon: 'success' })
|
|
|
|
|
+ setTimeout(function() {
|
|
|
|
|
+ uni.redirectTo({ url: '/pages/profile-extra/coupons' })
|
|
|
|
|
+ }, 1200)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ self.claimError = (res && res.message) || '领取失败'
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function(e) {
|
|
|
|
|
+ self.claimError = (e && e.message) || '领取失败'
|
|
|
|
|
+ }).then(function() {
|
|
|
|
|
+ self.claiming = false
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ onKnow() {
|
|
|
|
|
+ var pages = getCurrentPages()
|
|
|
|
|
+ if (pages.length > 1) {
|
|
|
|
|
+ uni.navigateBack({ delta: 1 })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.switchTab({ url: '/pages/index-home/index' })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ formatValue(detail) {
|
|
|
|
|
+ var type = detail.couponType
|
|
|
|
|
+ var value = detail.couponValue
|
|
|
|
|
+ if (type === 'DISCOUNT' || type === 'PERCENT') {
|
|
|
|
|
+ return (value ? (value / 100).toFixed(1) : '9.0') + '折'
|
|
|
|
|
+ }
|
|
|
|
|
+ return '¥' + (value ? (value / 100).toFixed(2) : '0.00')
|
|
|
|
|
+ },
|
|
|
|
|
+ typeLabel(type) {
|
|
|
|
|
+ if (type === 'FIXED') return '满减券'
|
|
|
|
|
+ if (type === 'DISCOUNT' || type === 'PERCENT') return '折扣券'
|
|
|
|
|
+ if (type === 'CASH') return '无门槛券'
|
|
|
|
|
+ return '会员券'
|
|
|
|
|
+ },
|
|
|
|
|
+ conditionText(minSpend) {
|
|
|
|
|
+ if (!minSpend || minSpend <= 0) return '无门槛'
|
|
|
|
|
+ return '满' + (minSpend / 100).toFixed(2) + '元可用'
|
|
|
|
|
+ },
|
|
|
|
|
+ formatDate(date) {
|
|
|
|
|
+ if (!date) return '长期有效'
|
|
|
|
|
+ var d = parseDate(date)
|
|
|
|
|
+ if (!d) return String(date)
|
|
|
|
|
+ var y = d.getFullYear()
|
|
|
|
|
+ var m = String(d.getMonth() + 1).padStart(2, '0')
|
|
|
|
|
+ var day = String(d.getDate()).padStart(2, '0')
|
|
|
|
|
+ return y + '.' + m + '.' + day
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+.page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: $facade-fill-2;
|
|
|
|
|
+ padding: 40rpx $uni-space-page;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ===== 加载态 ===== */
|
|
|
|
|
+.state-box {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding-top: 240rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.state-text {
|
|
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-muted;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ===== 标题 ===== */
|
|
|
|
|
+.hero {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ margin: 24rpx 0 40rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.hero-emoji {
|
|
|
|
|
+ font-size: calc(40rpx * var(--font-scale, 1));
|
|
|
|
|
+ margin-right: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.hero-text {
|
|
|
|
|
+ font-size: calc(40rpx * var(--font-scale, 1));
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: $facade-text-strong;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ===== 券卡片 ===== */
|
|
|
|
|
+.coupon-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ background: $app-surface;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
|
|
|
|
+}
|
|
|
|
|
+.coupon-card-disabled {
|
|
|
|
|
+ opacity: 0.55;
|
|
|
|
|
+}
|
|
|
|
|
+.card-left {
|
|
|
|
|
+ width: 220rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
|
|
|
|
|
+ padding: $uni-space-lg 12rpx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.coupon-card-disabled .card-left {
|
|
|
|
|
+ background: #c0c0c0;
|
|
|
|
|
+}
|
|
|
|
|
+.card-value {
|
|
|
|
|
+ font-size: calc(44rpx * var(--font-scale, 1));
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: $app-surface;
|
|
|
|
|
+}
|
|
|
|
|
+.card-type-label {
|
|
|
|
|
+ font-size: calc(20rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.85);
|
|
|
|
|
+ margin-top: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.card-right {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 28rpx 24rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+.card-name {
|
|
|
|
|
+ font-size: calc(30rpx * var(--font-scale, 1));
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $facade-text-strong;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 10rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+}
|
|
|
|
|
+.card-condition {
|
|
|
|
|
+ font-size: calc(24rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $brand-primary;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.card-scope {
|
|
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $uni-info;
|
|
|
|
|
+ background: $tint-sky-ghost;
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ padding: 2rpx 12rpx;
|
|
|
|
|
+ border-radius: 8rpx;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+ align-self: flex-start;
|
|
|
|
|
+}
|
|
|
|
|
+.card-expiry {
|
|
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-ghost-light;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ===== 赠送人 ===== */
|
|
|
|
|
+.giver-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ margin: 8rpx 0 32rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.giver-avatar {
|
|
|
|
|
+ width: 64rpx;
|
|
|
|
|
+ height: 64rpx;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ margin-right: 16rpx;
|
|
|
|
|
+ background: $brand-primary-light-2;
|
|
|
|
|
+}
|
|
|
|
|
+.giver-avatar-default {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ color: $app-surface;
|
|
|
|
|
+ font-size: calc(30rpx * var(--font-scale, 1));
|
|
|
|
|
+}
|
|
|
|
|
+.giver-name {
|
|
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-strong;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ===== 操作区 ===== */
|
|
|
|
|
+.action-area {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-top: 32rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.claim-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 88rpx;
|
|
|
|
|
+ line-height: 88rpx;
|
|
|
|
|
+ border-radius: 44rpx;
|
|
|
|
|
+ background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
|
|
|
|
|
+ color: $app-surface;
|
|
|
|
|
+ font-size: calc(32rpx * var(--font-scale, 1));
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+}
|
|
|
|
|
+.claim-btn::after {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+}
|
|
|
|
|
+.claim-btn[disabled] {
|
|
|
|
|
+ opacity: 0.7;
|
|
|
|
|
+}
|
|
|
|
|
+.claim-tip {
|
|
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-muted;
|
|
|
|
|
+ margin-top: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.invalid-tip {
|
|
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $status-danger-2;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 32rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+.know-btn {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 88rpx;
|
|
|
|
|
+ line-height: 88rpx;
|
|
|
|
|
+ border-radius: 44rpx;
|
|
|
|
|
+ background: $facade-fill;
|
|
|
|
|
+ color: $facade-text-strong;
|
|
|
|
|
+ font-size: calc(32rpx * var(--font-scale, 1));
|
|
|
|
|
+ border: none;
|
|
|
|
|
+}
|
|
|
|
|
+.know-btn::after {
|
|
|
|
|
+ border: none;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|