增加推销模式
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<view class="points-page">
|
||||
<template>
|
||||
<scroll-view class="points-page" scroll-y>
|
||||
<view class="points-header">
|
||||
<view class="points-info">
|
||||
<text class="points-label">当前积分</text>
|
||||
@@ -9,7 +9,53 @@
|
||||
<button class="exchange-btn" @click="handleExchange">积分兑换</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view class="quick-actions">
|
||||
<view class="action-item" @click="goToSignin">
|
||||
<view class="action-icon signin-icon">📅</view>
|
||||
<text class="action-text">每日签到</text>
|
||||
<view class="action-badge" v-if="!signedToday">
|
||||
<text class="badge-text">+5</text>
|
||||
</view>
|
||||
<view class="signed-badge" v-else>
|
||||
<text class="signed-text">已签</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-item" @click="handleExchange">
|
||||
<view class="action-icon exchange-icon">🎁</view>
|
||||
<text class="action-text">积分兑换</text>
|
||||
</view>
|
||||
<view class="action-item" @click="goToMyReviews">
|
||||
<view class="action-icon review-icon">⭐</view>
|
||||
<text class="action-text">我的评价</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="signin-card" v-if="!signedToday">
|
||||
<view class="signin-info">
|
||||
<text class="signin-title">今日未签到</text>
|
||||
<text class="signin-desc">连续签到可获得额外奖励</text>
|
||||
</view>
|
||||
<button class="signin-btn" @click="goToSignin">去签到</button>
|
||||
</view>
|
||||
|
||||
<view class="signin-card signed" v-else>
|
||||
<view class="signin-info">
|
||||
<text class="signin-title">今日已签到</text>
|
||||
<text class="signin-desc">已连续签到 {{ continuousDays }} 天</text>
|
||||
</view>
|
||||
<text class="signed-icon">✓</text>
|
||||
</view>
|
||||
|
||||
<view class="expiring-card" v-if="expiringPoints > 0" @click="showExpiringDetails">
|
||||
<view class="expiring-icon">⚠️</view>
|
||||
<view class="expiring-info">
|
||||
<text class="expiring-title">{{ expiringPoints }} 积分即将过期</text>
|
||||
<text class="expiring-date">过期日期:{{ expiringDate }}</text>
|
||||
</view>
|
||||
<text class="expiring-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<view class="records-section">
|
||||
<text class="section-title">积分明细</text>
|
||||
|
||||
@@ -35,7 +81,31 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="expiring-popup" v-if="showExpiringPopup" @click="closeExpiringPopup">
|
||||
<view class="popup-content" @click.stop>
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">即将过期积分</text>
|
||||
<text class="popup-close" @click="closeExpiringPopup">×</text>
|
||||
</view>
|
||||
<view class="popup-list">
|
||||
<view class="popup-item" v-for="(detail, index) in expiringDetails" :key="index">
|
||||
<view class="popup-item-info">
|
||||
<text class="popup-item-points">+{{ detail.points }} 积分</text>
|
||||
<text class="popup-item-desc">{{ detail.description ?? '积分获取' }}</text>
|
||||
</view>
|
||||
<view class="popup-item-expire">
|
||||
<text class="popup-item-date">{{ formatDate(detail.expires_at) }}</text>
|
||||
<text class="popup-item-label">过期</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-tip">
|
||||
<text class="tip-text">积分有效期为获取后365天,请及时使用避免过期</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
@@ -51,9 +121,22 @@ type PointRecord = {
|
||||
created_at: string
|
||||
}
|
||||
|
||||
type ExpiringDetail = {
|
||||
points: number
|
||||
description: string | null
|
||||
expires_at: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
const totalPoints = ref<number>(0)
|
||||
const records = ref<PointRecord[]>([])
|
||||
const loading = ref<boolean>(true)
|
||||
const signedToday = ref<boolean>(false)
|
||||
const continuousDays = ref<number>(0)
|
||||
const expiringPoints = ref<number>(0)
|
||||
const expiringDate = ref<string>('')
|
||||
const expiringDetails = ref<ExpiringDetail[]>([])
|
||||
const showExpiringPopup = ref<boolean>(false)
|
||||
|
||||
const loadPoints = async (): Promise<void> => {
|
||||
try {
|
||||
@@ -67,16 +150,62 @@ const loadPoints = async (): Promise<void> => {
|
||||
const loadRecords = async (): Promise<void> => {
|
||||
try {
|
||||
const list = await supabaseService.getPointRecords()
|
||||
records.value = list as PointRecord[]
|
||||
records.value = list as PointRecord[]
|
||||
} catch (e) {
|
||||
console.error('获取积分记录失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const loadSigninStatus = async (): Promise<void> => {
|
||||
try {
|
||||
const status = await supabaseService.getTodaySigninStatus()
|
||||
signedToday.value = status.getBoolean('signed') ?? false
|
||||
continuousDays.value = status.getNumber('continuous_days') ?? 0
|
||||
} catch (e) {
|
||||
console.error('获取签到状态失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const loadExpiringPoints = async (): Promise<void> => {
|
||||
try {
|
||||
const result = await supabaseService.getExpiringPoints()
|
||||
expiringPoints.value = result.getNumber('expiring_points') ?? 0
|
||||
expiringDate.value = result.getString('expiring_date') ?? ''
|
||||
|
||||
const detailsRaw = result.get('details')
|
||||
if (detailsRaw != null && Array.isArray(detailsRaw)) {
|
||||
const details: ExpiringDetail[] = []
|
||||
const arr = detailsRaw as any[]
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const item = arr[i]
|
||||
let itemObj: UTSJSONObject
|
||||
if (item instanceof UTSJSONObject) {
|
||||
itemObj = item
|
||||
} else {
|
||||
itemObj = JSON.parse(JSON.stringify(item)) as UTSJSONObject
|
||||
}
|
||||
details.push({
|
||||
points: itemObj.getNumber('points') ?? 0,
|
||||
description: itemObj.getString('description'),
|
||||
expires_at: itemObj.getString('expires_at') ?? '',
|
||||
created_at: itemObj.getString('created_at') ?? ''
|
||||
})
|
||||
}
|
||||
expiringDetails.value = details
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取即将过期积分失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
const loadData = async (): Promise<void> => {
|
||||
loading.value = true
|
||||
await loadPoints()
|
||||
await loadRecords()
|
||||
await Promise.all([
|
||||
loadPoints(),
|
||||
loadRecords(),
|
||||
loadSigninStatus(),
|
||||
loadExpiringPoints()
|
||||
])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -84,15 +213,33 @@ onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
const handleExchange = () => {
|
||||
uni.showToast({
|
||||
title: '积分商城开发中',
|
||||
icon: 'none'
|
||||
const handleExchange = (): void => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/points/exchange'
|
||||
})
|
||||
}
|
||||
|
||||
const goToSignin = (): void => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/points/signin'
|
||||
})
|
||||
}
|
||||
|
||||
const goToMyReviews = (): void => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mall/consumer/my-reviews'
|
||||
})
|
||||
}
|
||||
|
||||
const showExpiringDetails = (): void => {
|
||||
showExpiringPopup.value = true
|
||||
}
|
||||
|
||||
const closeExpiringPopup = (): void => {
|
||||
showExpiringPopup.value = false
|
||||
}
|
||||
|
||||
const getTypeText = (type: string): string => {
|
||||
// 不支持 Record<string, string>,使用 if-else
|
||||
if (type == 'signin') {
|
||||
return '每日签到'
|
||||
} else if (type == 'shopping') {
|
||||
@@ -103,6 +250,8 @@ const getTypeText = (type: string): string => {
|
||||
return '系统调整'
|
||||
} else if (type == 'register') {
|
||||
return '注册赠送'
|
||||
} else if (type == 'expire') {
|
||||
return '积分过期'
|
||||
} else {
|
||||
return '积分变动'
|
||||
}
|
||||
@@ -118,15 +267,26 @@ const formatTime = (timeStr: string): string => {
|
||||
const mm = date.getMinutes().toString().padStart(2, '0')
|
||||
return `${y}-${m}-${d} ${hh}:${mm}`
|
||||
}
|
||||
|
||||
const formatDate = (dateStr: string): string => {
|
||||
if (dateStr == '') return ''
|
||||
const date = new Date(dateStr)
|
||||
const y = date.getFullYear()
|
||||
const m = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const d = date.getDate().toString().padStart(2, '0')
|
||||
return `${y}-${m}-${d}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.points-page {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.points-header {
|
||||
background-color: #ff5000;
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%);
|
||||
padding: 30px 20px;
|
||||
color: white;
|
||||
display: flex;
|
||||
@@ -161,11 +321,174 @@ const formatTime = (timeStr: string): string => {
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: white;
|
||||
padding: 16px 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.signin-icon {
|
||||
background-color: #fff5f0;
|
||||
}
|
||||
|
||||
.exchange-icon {
|
||||
background-color: #f0f5ff;
|
||||
}
|
||||
|
||||
.review-icon {
|
||||
background-color: #fff5f0;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 20px;
|
||||
background-color: #ff6b35;
|
||||
border-radius: 8px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.badge-text {
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.signed-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 20px;
|
||||
background-color: #52c41a;
|
||||
border-radius: 8px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.signed-text {
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.signin-card {
|
||||
background-color: white;
|
||||
margin: 0 12px 8px;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.signin-card.signed {
|
||||
background: linear-gradient(135deg, #f6ffed 0%, #e6fffb 100%);
|
||||
}
|
||||
|
||||
.signin-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.signin-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.signin-desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.signin-btn {
|
||||
background-color: #ff6b35;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
border-radius: 16px;
|
||||
padding: 0 20px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.signed-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: #52c41a;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.expiring-card {
|
||||
background: linear-gradient(135deg, #fff7e6 0%, #ffe7ba 100%);
|
||||
margin: 0 12px 8px;
|
||||
border-radius: 12px;
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.expiring-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.expiring-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.expiring-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #d48806;
|
||||
}
|
||||
|
||||
.expiring-date {
|
||||
font-size: 12px;
|
||||
color: #ad8b00;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.expiring-arrow {
|
||||
font-size: 20px;
|
||||
color: #d48806;
|
||||
}
|
||||
|
||||
.records-section {
|
||||
background-color: white;
|
||||
margin-top: 10px;
|
||||
padding: 0 16px;
|
||||
min-height: 500px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@@ -187,7 +510,10 @@ const formatTime = (timeStr: string): string => {
|
||||
.record-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.record-title {
|
||||
margin-bottom: 4px;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
}
|
||||
@@ -204,7 +530,7 @@ const formatTime = (timeStr: string): string => {
|
||||
}
|
||||
|
||||
.record-amount.positive {
|
||||
color: #ff5000;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.record-amount.negative {
|
||||
@@ -221,4 +547,108 @@ const formatTime = (timeStr: string): string => {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
.loading-state {
|
||||
padding: 40px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.expiring-popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
background-color: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
width: 100%;
|
||||
max-height: 60%;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
font-size: 24px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.popup-list {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.popup-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.popup-item-points {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.popup-item-desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.popup-item-expire {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.popup-item-date {
|
||||
font-size: 12px;
|
||||
color: #d48806;
|
||||
}
|
||||
|
||||
.popup-item-label {
|
||||
font-size: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.popup-tip {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user