Files
medical-mall/pages/mall/consumer/share/index.uvue
2026-05-14 15:28:09 +08:00

413 lines
9.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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.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>