SFH5/pagesb/pointsMall/index.vue
2026-03-16 11:10:28 +08:00

523 lines
12 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>
<view
class="container"
:class="[`${themeInfo.theme}-theme`, `${themeInfo.language}`]"
>
<navBar />
<view class="content">
<view class="header-section">
<text class="page-title">
{{ $t('pointsMall.title') }}
</text>
</view>
<!-- 积分卡片 -->
<view class="points-card">
<view class="points-row" @click="openExchangeRecord">
<view class="points-left">
<text class="points-label">
{{ $t('pointsMall.myPoints') }}
</text>
<text class="points-num">{{ userInfo.points }}</text>
</view>
<!-- 右侧箭头 -->
<view class="points-arrow"><uv-icon name="arrow-right" color="#FFFFFF" size="28"></uv-icon></view>
</view>
</view>
<!-- 商品列表 -->
<view class="goods-container">
<view
class="goods-item"
v-for="(item, index) in goodsList"
:key="item.id ?? index"
>
<image :src="item.image" mode="aspectFill" class="goods-image" />
<view class="goods-info">
<text class="goods-title">{{ item.name }}</text>
<text class="goods-stock">
{{ $t('pointsMall.stock') }}{{ item.stock }}
</text>
<text class="goods-desc">{{ item.desc }}</text>
<view class="goods-footer">
<text class="goods-price">
{{ getNeedPoints(item) }} {{ $t('pointsMall.pointsUnit') }}
</text>
<view
class="exchange-btn"
:class="{ disabled: (item.stock ?? 0) <= 0 }"
@click="(item.stock ?? 0) > 0 && handleExchange(item)"
>
{{ $t('pointsMall.exchange') }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 兑换确认弹窗 -->
<myModal
v-model="showModal"
:title="$t('pointsMall.exchangeConfirmTitle')"
:confirmText="$t('common.confirm')"
:cancelText="$t('common.cancel')"
:noClose="true"
@confirm="submitExchange"
@close="closeModal"
>
<view class="confirm-box">
<text class="confirm-text">
{{
$t('pointsMall.exchangeConfirmTip').replace('{points}', currentNeedPoints)
}}
</text>
<view class="confirm-goods" v-if="currentGoods">
<image :src="currentGoods.image" mode="aspectFill" class="confirm-image" />
<view class="confirm-info">
<text class="confirm-title">{{ currentGoods.name }}</text>
<text class="confirm-points">
{{ currentNeedPoints }} {{ $t('pointsMall.pointsUnit') }}
</text>
</view>
</view>
</view>
</myModal>
<!-- 领取成功弹窗 -->
<myModal
v-model="showSuccessModal"
:title="$t('pointsMall.successTitle')"
:confirmText="$t('common.confirm')"
:noClose="true"
@confirm="closeSuccessModal"
@close="closeSuccessModal"
>
<view class="success-box">
<text class="success-text">
{{ $t('pointsMall.successTip') }}
</text>
</view>
</myModal>
<myModal
v-model="showRecordModal"
:title="$t('pointsMall.exchangeRecordTitle')"
:confirmShow="false"
>
<view class="record-box">
<!-- 加载中 -->
<!-- <view v-if="recordLoading" class="record-loading">
{{ $t('common.loading') }}
</view> -->
<!-- 空状态 -->
<view v-if="recordList.length === 0" class="record-empty" style="text-align: center;">
{{ $t('pointsMall.noExchangeRecord') }}
</view>
<!-- 列表 -->
<view v-else class="record-list">
<view class="record-item" v-for="(r, idx) in recordList" :key="r.id ?? idx">
<view class="record-top">
<text class="record-name">{{ r.giftName || r.name || '-' }}</text>
<!-- <text class="record-status">
已兑换
</text> -->
</view>
<view class="record-bottom">
<text class="record-time">{{ r.exchangeTime?.substring(0, 10) }}</text>
</view>
</view>
<!-- 加载更多 -->
<view class="record-more" v-if="recordHasMore" @click="loadMoreRecord">
{{ recordMoreLoading ? $t('common.loading') : $t('common.loadMore') }}
</view>
</view>
</view>
</myModal>
</view>
</template>
<script setup>
import { ref, reactive, computed } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import navBar from "@/components/navBar.vue";
import { useMainStore } from "@/store/index.js";
import myModal from "@/components/myModal.vue";
import { useLoginApi } from "@/Apis/login.js";
import { baseImageUrl } from "@/config/index.js";
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const getApi = useLoginApi();
const { themeInfo } = useMainStore();
// 用户积分
const userInfo = reactive({
points: 0
});
// 商品列表
const goodsList = ref([]);
const recordList = ref([]);
const showRecordModal = ref(false);
// 弹窗控制:确认弹窗
const showModal = ref(false);
const currentGoods = ref(null);
// 弹窗控制:成功弹窗
const showSuccessModal = ref(false);
// 打开弹窗
const openExchangeRecord = () => {
showRecordModal.value = true
// 每次打开都刷新第一页
GetCustomerExchangeGift()
}
// 统一计算商品所需积分:优先 required其次 points兜底 0
const getNeedPoints = (item) => {
return item?.required ?? item?.points ?? 0;
};
// 当前选中商品所需积分(用于弹窗显示)
const currentNeedPoints = computed(() => {
return getNeedPoints(currentGoods.value);
});
const GetCustomerExchangeGift = () => {
uni.showLoading({
mask: true
});
getApi.GetCustomerExchangeGift().then((res) => {
uni.hideLoading();
if (res.code == 200) {
recordList.value = res.data || [];
}
});
}
const getDataList = () => {
uni.showLoading({
mask: true
});
getApi.GetGiftList().then((res) => {
uni.hideLoading();
if (res.code == 200) {
goodsList.value = (res.data || []).map((item) => {
item.image = baseImageUrl + item.imageUrl;
return item;
});
}
});
};
// 获取用户积分
const GetCustomerPoint = () => {
userInfo.points = 0;
getApi.GetCustomerPoint().then((res) => {
if (res.code === 200) {
userInfo.points = res.data?.availablePoints || 0;
}
});
};
// 点击兑换:打开确认弹窗
const handleExchange = (item) => {
if ((item.stock ?? 0) <= 0) {
uni.showToast({ title: t('pointsMall.toastOutOfStock'), icon: "none" });
return;
}
const need = getNeedPoints(item);
if (userInfo.points < need) {
uni.showToast({ title: t('pointsMall.toastNotEnoughPoints'), icon: "none" });
return;
}
currentGoods.value = item;
showModal.value = true;
};
// 提交兑换:确认后调用接口
const submitExchange = () => {
if (!currentGoods.value) return;
const need = currentNeedPoints.value;
if (userInfo.points < need) {
uni.showToast({ title: t('pointsMall.toastNotEnoughPoints'), icon: "none" });
closeModal();
return;
}
uni.showLoading({ title: t('pointsMall.toastExchanging') });
getApi.CustomerExchangeGift({ giftId: currentGoods.value.id }).then((res) => {
uni.hideLoading();
// 这里你原来判断的是 res.data我保留逻辑如果你实际是 res.code===200 更合理可改
if (res.code == 200) {
// 关闭确认弹窗
closeModal();
// 刷新数据
getDataList();
GetCustomerPoint();
// 打开成功弹窗
showSuccessModal.value = true;
} else {
uni.showToast({ title: t('pointsMall.toastExchangeFailed'), icon: "none" });
}
}).catch(() => {
uni.hideLoading();
uni.showToast({ title: t('pointsMall.toastExchangeFailed'), icon: "none" });
});
};
// 关闭确认弹窗
const closeModal = () => {
showModal.value = false;
currentGoods.value = null;
};
// 关闭成功弹窗
const closeSuccessModal = () => {
showSuccessModal.value = false;
};
onShow(() => {
getDataList();
GetCustomerPoint();
});
onLoad(() => {});
</script>
<style lang="scss" scoped>
@import "@/static/style/theme.scss";
.container {
width: 100%;
min-height: 100vh;
padding-bottom: 40rpx;
background: linear-gradient(
to bottom,
var(--right-linear),
var(--left-linear2)
);
.content {
padding: 0 30rpx;
}
}
.header-section {
padding: 20rpx 0;
.page-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
}
.record-box{
padding: 20rpx;
width: 100%;
.record-item{
display: flex;
justify-content: space-around;
align-items: center;
font-size: 24rpx;
}
}
.points-card {
background: linear-gradient(135deg, #FF5722 0%, #FF9800 100%);
border-radius: 20rpx;
padding: 40rpx;
margin-bottom: 30rpx;
color: #fff;
box-shadow: 0 4rpx 12rpx rgba(255, 154, 158, 0.3);
.points-row{
display:flex;
align-items:center;
justify-content:space-between;
}
.points-left{
display:flex;
flex-direction:column;
}
.points-arrow{
font-size: 40rpx;
font-weight: 700;
opacity: 0.9;
padding-left: 20rpx;
}
.points-label {
font-size: 28rpx;
opacity: 0.9;
display: block;
margin-bottom: 10rpx;
}
.points-num {
font-size: 60rpx;
font-weight: bold;
}
}
.goods-container {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.goods-item {
display: flex;
background: #fff;
border-radius: 16rpx;
padding: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
.goods-image {
width: 200rpx;
height: 200rpx;
border-radius: 12rpx;
background-color: #eee;
margin-right: 20rpx;
flex-shrink: 0;
}
.goods-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.goods-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 8rpx;
}
.goods-stock {
font-size: 24rpx;
color: #666;
margin-bottom: 12rpx;
}
.goods-desc {
font-size: 24rpx;
color: #999;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
margin-bottom: 20rpx;
}
.goods-footer {
display: flex;
justify-content: space-between;
align-items: center;
.goods-price {
font-size: 32rpx;
color: #ff5a5f;
font-weight: bold;
}
.exchange-btn {
padding: 10rpx 30rpx;
background-color: #007aff;
color: #fff;
font-size: 26rpx;
border-radius: 30rpx;
&:active {
opacity: 0.8;
}
&.disabled {
background-color: #c8c9cc;
opacity: 0.9;
}
}
}
}
}
/* 兑换确认弹窗样式 */
.confirm-box {
padding: 20rpx 0;
.confirm-text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
display: block;
margin-bottom: 20rpx;
}
.confirm-goods {
display: flex;
align-items: center;
background: #f7f7f7;
border-radius: 14rpx;
padding: 16rpx;
.confirm-image {
width: 120rpx;
height: 120rpx;
border-radius: 10rpx;
margin-right: 16rpx;
flex-shrink: 0;
background: #eee;
}
.confirm-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
.confirm-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.confirm-points {
font-size: 26rpx;
color: #ff5a5f;
font-weight: 600;
}
}
}
}
/* 领取成功弹窗样式 */
.success-box {
padding: 30rpx 0;
.success-text {
font-size: 28rpx;
color: #333;
line-height: 1.7;
}
}
</style>