Files
web-fusion/src/components/pk-mini/mine/PointsList.vue
2026-03-30 09:53:08 +08:00

460 lines
10 KiB
Vue
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>
<!-- 积分列表 -->
<div class="points-container">
<div class="points-header">
<img class="points-icon" src="@/assets/pk-mini/Points.png" alt="" />
<div class="points-text">
我的积分: <span class="points-num">{{ currentUser.points || 0 }}</span>
</div>
<button class="exchange-btn" @click="openExchangeDialog">积分兑换</button>
</div>
<div class="points-list" v-if="pointsList.length > 0">
<div class="points-item" v-for="(item, index) in pointsList" :key="index">
<div class="item-content" :class="item.status == 1 ? 'positive' : 'negative'">
<div class="event">{{ item.info }}</div>
<div class="number">{{ item.number }}</div>
<div class="time">{{ formatTime(item.time * 1000) }}</div>
</div>
</div>
</div>
<div class="empty-tip" v-else>您还没有积分记录</div>
<!-- 积分兑换弹窗 -->
<div class="exchange-dialog" v-if="exchangeDialogVisible">
<div class="dialog-overlay" @click="closeExchangeDialog"></div>
<div class="dialog-content">
<div class="dialog-header">
<div class="dialog-title">积分兑换</div>
<div class="close-btn" @click="closeExchangeDialog">×</div>
</div>
<div class="dialog-body">
<div class="user-info">
我的积分: <span class="points-num">{{ currentUser.points || 0 }}</span>
</div>
<div class="item-list" v-if="itemList.length > 0">
<div class="item-card" v-for="item in itemList" :key="item.id">
<div class="item-info">
<div class="item-header">
<div class="item-name">{{ item.itemName }}</div>
<div class="item-price">
<span class="price-tag">积分</span>
<span class="price-num">{{ item.itemPrice }}</span>
</div>
</div>
<div class="item-desc">{{ item.itemDesc }}</div>
<button class="exchange-btn" @click="exchangeItem(item)"
:disabled="(currentUser.points || 0) < item.itemPrice">
{{ (currentUser.points || 0) < item.itemPrice ? '积分不足' : '立即兑换' }} </button>
</div>
</div>
</div>
<div class="empty-tip" v-else-if="!loading">暂无商品</div>
<div class="loading-tip" v-else>加载中...</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { getIntegralDetail, getPkItemList, buyPkItem } from '@/api/pk-mini'
import { getMainUserData } from '@/utils/pk-mini/storage'
import { TimestamptolocalTime } from '@/utils/pk-mini/timeConversion'
import { ElMessage } from 'element-plus'
import { getCurrent } from '@/api/account'
function getUserId(user) {
return user?.id || user?.userId || user?.uid || null
}
const currentUser = ref({})
const pointsList = ref([])
const page = ref(0)
const formatTime = TimestamptolocalTime
// 弹窗状态
const exchangeDialogVisible = ref(false)
const itemList = ref([])
const loading = ref(false)
// 打开积分兑换弹窗
const openExchangeDialog = async () => {
exchangeDialogVisible.value = true
await loadItemList()
}
// 关闭积分兑换弹窗
const closeExchangeDialog = () => {
exchangeDialogVisible.value = false
}
// 加载商品列表
const loadItemList = async () => {
try {
loading.value = true
const res = await getPkItemList({})
if (res && res.length > 0) {
itemList.value = res
}
} catch (e) {
console.error('加载商品列表失败', e)
ElMessage.error('加载商品列表失败')
} finally {
loading.value = false
}
}
// 兑换商品
const exchangeItem = async (item) => {
const userPoints = currentUser.value.points || 0
if (userPoints < item.itemPrice) {
ElMessage.warning('积分不足')
return
}
try {
// 调用兑换接口
const res = await buyPkItem({ itemId: item.id })
if (res) {
ElMessage.success(`兑换 ${item.itemName} 成功`)
// 重新获取用户信息,更新积分
const userRes = await getCurrent()
if (userRes) {
currentUser.value = userRes
}
closeExchangeDialog()
} else {
ElMessage.error('兑换失败,请稍后重试')
}
} catch (e) {
console.error('兑换商品失败', e)
ElMessage.error('兑换失败,请稍后重试')
}
}
async function loadPointsList() {
const userId = getUserId(currentUser.value)
if (!userId) return
try {
const res = await getIntegralDetail({
page: page.value,
size: 30,
userId: userId
})
if (res && res.length > 0) {
pointsList.value.push(...res)
}
} catch (e) {
console.error('加载积分记录失败', e)
}
}
onMounted(async () => {
try {
// 获取最新的用户信息,包括积分
const res = await getCurrent()
if (res) {
currentUser.value = res
} else {
// 如果获取失败,使用缓存中的数据
currentUser.value = getMainUserData() || {}
}
} catch (e) {
console.error('获取用户信息失败', e)
// 出错时使用缓存中的数据
currentUser.value = getMainUserData() || {}
}
const userId = getUserId(currentUser.value)
if (userId) {
loadPointsList()
}
})
</script>
<style scoped lang="less">
.points-container {
width: 100%;
height: 100%;
}
.points-header {
width: 100%;
height: 70px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
background: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
.exchange-btn {
padding: 8px 16px;
background: #ff6b35;
color: #fff;
border: none;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: background 0.3s ease;
&:hover {
background: #ff5216;
}
}
}
.points-icon {
width: 52px;
height: 52px;
margin-right: 18px;
}
.points-text {
font-size: 24px;
color: #666;
}
.points-num {
font-size: 24px;
font-weight: bold;
color: #333;
}
.points-list {
width: 100%;
height: calc(100% - 70px);
overflow: auto;
padding: 10px;
}
.points-item {
margin-bottom: 10px;
display: flex;
justify-content: center;
}
.item-content {
width: 90%;
height: 60px;
border-radius: 10px;
display: flex;
justify-content: space-around;
align-items: center;
}
.item-content.positive {
background: #dffefc;
}
.item-content.negative {
background: #fbece9;
}
.event {
color: #03aba8;
font-size: 16px;
}
.number {
color: #333;
font-size: 18px;
font-weight: bold;
}
.time {
color: #999;
font-size: 16px;
}
.empty-tip {
height: calc(100% - 70px);
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
color: #03aba8;
}
/* 积分兑换弹窗样式 */
.exchange-dialog {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
.dialog-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.dialog-content {
position: relative;
width: 90%;
max-width: 800px;
max-height: 80vh;
background: #fff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
.dialog-header {
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
border-bottom: 1px solid #eee;
.dialog-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.close-btn {
font-size: 24px;
color: #999;
cursor: pointer;
transition: color 0.3s ease;
&:hover {
color: #333;
}
}
}
.dialog-body {
padding: 20px;
max-height: calc(80vh - 60px);
overflow: auto;
.user-info {
font-size: 16px;
color: #666;
margin-bottom: 20px;
.points-num {
font-weight: bold;
color: #ff6b35;
margin-left: 5px;
}
}
.item-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
}
.item-card {
background: #f9f9f9;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
&:hover {
transform: translateY(-3px);
}
.item-info {
padding: 15px;
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.item-name {
font-size: 16px;
font-weight: bold;
color: #333;
flex: 1;
margin-right: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.item-price {
display: flex;
align-items: center;
.price-tag {
font-size: 12px;
color: #ff6b35;
background: rgba(255, 107, 53, 0.1);
padding: 3px 8px;
border-radius: 4px;
margin-right: 8px;
}
.price-num {
font-size: 18px;
font-weight: bold;
color: #ff6b35;
}
}
}
.item-desc {
font-size: 14px;
color: #666;
margin-bottom: 15px;
line-height: 1.4;
min-height: 40px;
}
.exchange-btn {
width: 100%;
height: 32px;
background: #ff6b35;
color: #fff;
border: none;
border-radius: 16px;
font-size: 12px;
font-weight: bold;
cursor: pointer;
transition: background 0.3s ease;
&:hover:not(:disabled) {
background: #ff5216;
}
&:disabled {
background: #ccc;
cursor: not-allowed;
}
}
}
}
.empty-tip,
.loading-tip {
height: 200px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #999;
}
}
}
}
</style>