增加推销模式
This commit is contained in:
603
pages/mall/consumer/share/detail.uvue
Normal file
603
pages/mall/consumer/share/detail.uvue
Normal file
@@ -0,0 +1,603 @@
|
||||
<template>
|
||||
<scroll-view class="share-detail-page" scroll-y>
|
||||
<view class="product-section">
|
||||
<image class="product-image" :src="shareRecord.product_image || defaultImage" mode="aspectFill" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ shareRecord.product_name }}</text>
|
||||
<text class="product-price">¥{{ shareRecord.product_price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="progress-section">
|
||||
<view class="progress-header">
|
||||
<text class="progress-title">免单进度</text>
|
||||
<text class="progress-status" :class="getStatusClass(shareRecord.status)">{{ getStatusText(shareRecord.status) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="progress-content">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" :style="{ width: getProgressPercent() + '%' }"></view>
|
||||
</view>
|
||||
<view class="progress-numbers">
|
||||
<text class="current-count">{{ shareRecord.current_count }}</text>
|
||||
<text class="divider">/</text>
|
||||
<text class="required-count">{{ shareRecord.required_count }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="progress-tip" v-if="shareRecord.status === 0">
|
||||
<text class="tip-text">还需 {{ shareRecord.required_count - shareRecord.current_count }} 人购买即可免单</text>
|
||||
</view>
|
||||
|
||||
<view class="reward-info" v-if="shareRecord.status === 1">
|
||||
<text class="reward-label">已获得免单奖励</text>
|
||||
<text class="reward-amount">¥{{ shareRecord.reward_amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="share-code-section">
|
||||
<view class="code-header">
|
||||
<text class="code-title">分享码</text>
|
||||
<text class="copy-btn" @click="copyShareCode">复制</text>
|
||||
</view>
|
||||
<view class="code-content">
|
||||
<text class="code-value">{{ shareRecord.share_code }}</text>
|
||||
</view>
|
||||
<view class="code-tip">
|
||||
<text class="tip-text">将分享码告诉好友,好友下单时填写即可</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="buyers-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">购买记录</text>
|
||||
<text class="section-count">({{ buyers.length }}人)</text>
|
||||
</view>
|
||||
|
||||
<view v-if="buyersLoading" class="loading-state">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="buyers.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无购买记录</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="buyer-list">
|
||||
<view class="buyer-item" v-for="buyer in buyers" :key="buyer.id">
|
||||
<view class="buyer-avatar">
|
||||
<text class="avatar-text">{{ getBuyerInitial(buyer.buyer_name) }}</text>
|
||||
</view>
|
||||
<view class="buyer-info">
|
||||
<text class="buyer-name">{{ maskName(buyer.buyer_name) }}</text>
|
||||
<text class="buyer-time">{{ formatTime(buyer.created_at) }}</text>
|
||||
</view>
|
||||
<view class="buyer-count">
|
||||
<text class="count-text">购买 {{ buyer.quantity }} 件</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="time-section">
|
||||
<view class="time-item">
|
||||
<text class="time-label">创建时间</text>
|
||||
<text class="time-value">{{ formatTime(shareRecord.created_at) }}</text>
|
||||
</view>
|
||||
<view class="time-item" v-if="shareRecord.completed_at">
|
||||
<text class="time-label">完成时间</text>
|
||||
<text class="time-value">{{ formatTime(shareRecord.completed_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||
|
||||
type ShareRecordType = {
|
||||
id: string
|
||||
product_name: string
|
||||
product_image: string | null
|
||||
product_price: number
|
||||
share_code: string
|
||||
required_count: number
|
||||
current_count: number
|
||||
status: number
|
||||
reward_amount: number | null
|
||||
created_at: string
|
||||
completed_at: string | null
|
||||
}
|
||||
|
||||
type BuyerType = {
|
||||
id: string
|
||||
buyer_id: string
|
||||
buyer_name: string
|
||||
quantity: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
const shareId = ref<string>('')
|
||||
const shareRecord = ref<ShareRecordType>({
|
||||
id: '',
|
||||
product_name: '',
|
||||
product_image: null,
|
||||
product_price: 0,
|
||||
share_code: '',
|
||||
required_count: 4,
|
||||
current_count: 0,
|
||||
status: 0,
|
||||
reward_amount: null,
|
||||
created_at: '',
|
||||
completed_at: null
|
||||
})
|
||||
|
||||
const buyers = ref<BuyerType[]>([])
|
||||
const buyersLoading = ref<boolean>(false)
|
||||
const defaultImage: string = '/static/images/default-product.png'
|
||||
|
||||
const loadShareDetail = async (): Promise<void> => {
|
||||
if (shareId.value === '') return
|
||||
|
||||
try {
|
||||
const result = await supabaseService.getShareDetail(shareId.value)
|
||||
|
||||
const recordRaw = result.get('share_record')
|
||||
if (recordRaw != null) {
|
||||
const recordAny = recordRaw as any
|
||||
if (typeof recordAny._getValue === 'function') {
|
||||
shareRecord.value = {
|
||||
id: (recordAny._getValue('id') as string) ?? '',
|
||||
product_name: (recordAny._getValue('product_name') as string) ?? '',
|
||||
product_image: recordAny._getValue('product_image') as string | null,
|
||||
product_price: (recordAny._getValue('product_price') as number) ?? 0,
|
||||
share_code: (recordAny._getValue('share_code') as string) ?? '',
|
||||
required_count: (recordAny._getValue('required_count') as number) ?? 4,
|
||||
current_count: (recordAny._getValue('current_count') as number) ?? 0,
|
||||
status: (recordAny._getValue('status') as number) ?? 0,
|
||||
reward_amount: recordAny._getValue('reward_amount') as number | null,
|
||||
created_at: (recordAny._getValue('created_at') as string) ?? '',
|
||||
completed_at: recordAny._getValue('completed_at') as string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const purchasesRaw = result.get('secondary_purchases')
|
||||
if (purchasesRaw != null && Array.isArray(purchasesRaw)) {
|
||||
const parsed: BuyerType[] = []
|
||||
const arr = purchasesRaw as any[]
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const item = arr[i]
|
||||
const itemAny = item as any
|
||||
|
||||
if (typeof itemAny._getValue === 'function') {
|
||||
parsed.push({
|
||||
id: (itemAny._getValue('id') as string) ?? '',
|
||||
buyer_id: (itemAny._getValue('buyer_id') as string) ?? '',
|
||||
buyer_name: '用户' + (i + 1),
|
||||
quantity: (itemAny._getValue('quantity') as number) ?? 1,
|
||||
created_at: (itemAny._getValue('created_at') as string) ?? ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
buyers.value = parsed
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载分享详情失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
const getProgressPercent = (): number => {
|
||||
if (shareRecord.value.required_count <= 0) return 0
|
||||
return Math.min(100, Math.round((shareRecord.value.current_count / shareRecord.value.required_count) * 100))
|
||||
}
|
||||
|
||||
const getStatusText = (status: number): string => {
|
||||
if (status === 0) return '进行中'
|
||||
if (status === 1) return '已免单'
|
||||
if (status === 2) return '已失效'
|
||||
if (status === 3) return '已过期'
|
||||
return '未知'
|
||||
}
|
||||
|
||||
const getStatusClass = (status: number): string => {
|
||||
if (status === 0) return 'status-progress'
|
||||
if (status === 1) return 'status-completed'
|
||||
if (status === 2) return 'status-invalid'
|
||||
if (status === 3) return 'status-expired'
|
||||
return ''
|
||||
}
|
||||
|
||||
const copyShareCode = (): void => {
|
||||
uni.setClipboardData({
|
||||
data: shareRecord.value.share_code,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已复制分享码', icon: 'success' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getBuyerInitial = (name: string): string => {
|
||||
if (name.length > 0) {
|
||||
return name.charAt(0)
|
||||
}
|
||||
return '用'
|
||||
}
|
||||
|
||||
const maskName = (name: string): string => {
|
||||
if (name.length <= 2) {
|
||||
return name.charAt(0) + '*'
|
||||
}
|
||||
return name.charAt(0) + '***' + name.charAt(name.length - 1)
|
||||
}
|
||||
|
||||
const formatTime = (timeStr: string | null): string => {
|
||||
if (timeStr == null || timeStr === '') return ''
|
||||
const date = new Date(timeStr)
|
||||
const y = date.getFullYear()
|
||||
const m = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const d = date.getDate().toString().padStart(2, '0')
|
||||
const hh = date.getHours().toString().padStart(2, '0')
|
||||
const mm = date.getMinutes().toString().padStart(2, '0')
|
||||
return `${y}-${m}-${d} ${hh}:${mm}`
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const pages = getCurrentPages()
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1]
|
||||
const options = (currentPage as any).options
|
||||
if (options != null && options.id != null) {
|
||||
shareId.value = options.id as string
|
||||
loadShareDetail()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.share-detail-page {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.product-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
lines: 2;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.progress-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
font-size: 14px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.status-progress {
|
||||
background-color: #fff5f0;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-invalid {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
background-color: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.progress-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 12px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #ff6b35 0%, #ff8c42 100%);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.progress-numbers {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.current-count {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.divider {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.required-count {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-tip {
|
||||
margin-top: 12px;
|
||||
padding: 10px;
|
||||
background-color: #fff5f0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 13px;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.reward-info {
|
||||
margin-top: 12px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #f6ffed 0%, #e6fffb 100%);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.reward-label {
|
||||
font-size: 15px;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.reward-amount {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.share-code-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.code-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.code-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
font-size: 14px;
|
||||
color: #ff6b35;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid #ff6b35;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.code-content {
|
||||
background-color: #f9f9f9;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
letter-spacing: 8px;
|
||||
}
|
||||
|
||||
.code-tip {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.buyers-section {
|
||||
background-color: white;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-count {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
padding: 30px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 30px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.buyer-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.buyer-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
}
|
||||
|
||||
.buyer-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 20px;
|
||||
background-color: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.buyer-info {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.buyer-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.buyer-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.buyer-count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.count-text {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.time-section {
|
||||
background-color: white;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.time-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
409
pages/mall/consumer/share/index.uvue
Normal file
409
pages/mall/consumer/share/index.uvue
Normal file
@@ -0,0 +1,409 @@
|
||||
<template>
|
||||
<scroll-view class="share-page" scroll-y>
|
||||
<view class="share-summary">
|
||||
<view class="summary-item">
|
||||
<text class="summary-value">{{ totalShares }}</text>
|
||||
<text class="summary-label">分享次数</text>
|
||||
</view>
|
||||
<view class="summary-divider"></view>
|
||||
<view class="summary-item">
|
||||
<text class="summary-value">{{ completedShares }}</text>
|
||||
<text class="summary-label">免单成功</text>
|
||||
</view>
|
||||
<view class="summary-divider"></view>
|
||||
<view class="summary-item">
|
||||
<text class="summary-value">{{ totalReward }}</text>
|
||||
<text class="summary-label">累计奖励(元)</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="rules-section">
|
||||
<view class="rules-header" @click="toggleRules">
|
||||
<text class="rules-title">免单规则</text>
|
||||
<text class="rules-arrow">{{ showRules ? '▲' : '▼' }}</text>
|
||||
</view>
|
||||
<view class="rules-content" v-if="showRules">
|
||||
<text class="rules-text">1. 购买商品后可生成分享链接</text>
|
||||
<text class="rules-text">2. 分享给好友,好友通过链接购买</text>
|
||||
<text class="rules-text">3. 累计4人购买后,即可免单</text>
|
||||
<text class="rules-text">4. 免单金额存入余额,可联系商家提现</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="share-list-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">我的分享</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-state">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="shares.length === 0" class="empty-state">
|
||||
<text class="empty-text">暂无分享记录</text>
|
||||
<text class="empty-tip">购买商品后可以分享免单哦~</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="share-list">
|
||||
<view class="share-item" v-for="share in shares" :key="share.id" @click="goToShareDetail(share.id)">
|
||||
<image class="product-image" :src="share.product_image || defaultImage" mode="aspectFill" />
|
||||
<view class="share-info">
|
||||
<text class="product-name">{{ share.product_name }}</text>
|
||||
<view class="progress-section">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" :style="{ width: getProgressPercent(share.current_count, share.required_count) + '%' }"></view>
|
||||
</view>
|
||||
<text class="progress-text">{{ share.current_count }}/{{ share.required_count }}</text>
|
||||
</view>
|
||||
<view class="share-bottom">
|
||||
<text class="share-code">分享码: {{ share.share_code }}</text>
|
||||
<text class="share-status" :class="getStatusClass(share.status)">{{ getStatusText(share.status) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="share-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { supabaseService } from '@/utils/supabaseService.uts'
|
||||
|
||||
type ShareRecord = {
|
||||
id: string
|
||||
product_id: string
|
||||
product_name: string
|
||||
product_image: string | null
|
||||
product_price: number
|
||||
share_code: string
|
||||
required_count: number
|
||||
current_count: number
|
||||
status: number
|
||||
reward_amount: number | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
const shares = ref<ShareRecord[]>([])
|
||||
const loading = ref<boolean>(true)
|
||||
const showRules = ref<boolean>(false)
|
||||
const defaultImage: string = '/static/images/default-product.png'
|
||||
|
||||
const totalShares = computed((): number => shares.value.length)
|
||||
|
||||
const completedShares = computed((): number => {
|
||||
let count = 0
|
||||
for (let i = 0; i < shares.value.length; i++) {
|
||||
if (shares.value[i].status === 1) count++
|
||||
}
|
||||
return count
|
||||
})
|
||||
|
||||
const totalReward = computed((): number => {
|
||||
let total = 0
|
||||
for (let i = 0; i < shares.value.length; i++) {
|
||||
if (shares.value[i].reward_amount != null) {
|
||||
total += shares.value[i].reward_amount!
|
||||
}
|
||||
}
|
||||
return total
|
||||
})
|
||||
|
||||
const loadShares = async (): Promise<void> => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await supabaseService.getMyShareRecords()
|
||||
const parsed: ShareRecord[] = []
|
||||
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const item = result[i]
|
||||
const itemAny = item as any
|
||||
|
||||
if (typeof itemAny._getValue === 'function') {
|
||||
parsed.push({
|
||||
id: (itemAny._getValue('id') as string) ?? '',
|
||||
product_id: (itemAny._getValue('product_id') as string) ?? '',
|
||||
product_name: (itemAny._getValue('product_name') as string) ?? '',
|
||||
product_image: itemAny._getValue('product_image') as string | null,
|
||||
product_price: (itemAny._getValue('product_price') as number) ?? 0,
|
||||
share_code: (itemAny._getValue('share_code') as string) ?? '',
|
||||
required_count: (itemAny._getValue('required_count') as number) ?? 4,
|
||||
current_count: (itemAny._getValue('current_count') as number) ?? 0,
|
||||
status: (itemAny._getValue('status') as number) ?? 0,
|
||||
reward_amount: itemAny._getValue('reward_amount') as number | null,
|
||||
created_at: (itemAny._getValue('created_at') as string) ?? ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
shares.value = parsed
|
||||
} catch (e) {
|
||||
console.error('加载分享记录失败:', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const toggleRules = (): void => {
|
||||
showRules.value = !showRules.value
|
||||
}
|
||||
|
||||
const getProgressPercent = (current: number, required: number): number => {
|
||||
if (required <= 0) return 0
|
||||
return Math.min(100, Math.round((current / required) * 100))
|
||||
}
|
||||
|
||||
const getStatusText = (status: number): string => {
|
||||
if (status === 0) return '进行中'
|
||||
if (status === 1) return '已免单'
|
||||
if (status === 2) return '已失效'
|
||||
if (status === 3) return '已过期'
|
||||
return '未知'
|
||||
}
|
||||
|
||||
const getStatusClass = (status: number): string => {
|
||||
if (status === 0) return 'status-progress'
|
||||
if (status === 1) return 'status-completed'
|
||||
if (status === 2) return 'status-invalid'
|
||||
if (status === 3) return 'status-expired'
|
||||
return ''
|
||||
}
|
||||
|
||||
const goToShareDetail = (shareId: string): void => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/consumer/share/detail?id=${shareId}`
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadShares()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.share-page {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.share-summary {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: white;
|
||||
padding: 20px 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.summary-divider {
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.rules-section {
|
||||
background-color: white;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.rules-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.rules-title {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.rules-arrow {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.rules-content {
|
||||
padding: 0 16px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rules-text {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.share-list-section {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
padding: 40px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 60px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.share-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.share-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.share-info {
|
||||
flex: 1;
|
||||
margin-left: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
lines: 2;
|
||||
}
|
||||
|
||||
.progress-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background-color: #ff6b35;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 12px;
|
||||
color: #ff6b35;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.share-bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.share-code {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.share-status {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-progress {
|
||||
background-color: #fff5f0;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-invalid {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
background-color: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.share-arrow {
|
||||
font-size: 20px;
|
||||
color: #ccc;
|
||||
margin-left: 8px;
|
||||
align-self: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user