完成consumer端同步

This commit is contained in:
2026-05-14 15:28:09 +08:00
parent 612fb3d360
commit 0ffbc53902
197 changed files with 92657 additions and 7564 deletions

View File

@@ -0,0 +1,412 @@
<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>