413 lines
9.1 KiB
Plaintext
413 lines
9.1 KiB
Plaintext
<template>
|
||
<scroll-view class="share-page" direction="vertical">
|
||
<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 != null && share.product_image.length > 0 ? 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]
|
||
let itemObj: UTSJSONObject | null = null
|
||
if (item instanceof UTSJSONObject) {
|
||
itemObj = item
|
||
} else {
|
||
itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||
}
|
||
|
||
parsed.push({
|
||
id: itemObj.getString('id') ?? '',
|
||
product_id: itemObj.getString('product_id') ?? '',
|
||
product_name: itemObj.getString('product_name') ?? '',
|
||
product_image: itemObj.getString('product_image'),
|
||
product_price: itemObj.getNumber('product_price') ?? 0,
|
||
share_code: itemObj.getString('share_code') ?? '',
|
||
required_count: itemObj.getNumber('required_count') ?? 4,
|
||
current_count: itemObj.getNumber('current_count') ?? 0,
|
||
status: itemObj.getNumber('status') ?? 0,
|
||
reward_amount: itemObj.getNumber('reward_amount'),
|
||
created_at: itemObj.getString('created_at') ?? ''
|
||
})
|
||
}
|
||
|
||
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>
|