217 lines
5.6 KiB
Vue
217 lines
5.6 KiB
Vue
<script setup>
|
|
import { ref } from "vue";
|
|
import { onLoad } from "@dcloudio/uni-app";
|
|
import Container from "@/components/Container.vue";
|
|
import CoinHeader from "./components/CoinHeader.vue";
|
|
import CoinBalancePanel from "./components/CoinBalancePanel.vue";
|
|
import CoinQuickMenu from "./components/CoinQuickMenu.vue";
|
|
import OfflineExchangeNotice from "./components/OfflineExchangeNotice.vue";
|
|
import RewardProductCard from "./components/RewardProductCard.vue";
|
|
import { getGiftListAPI, getMyGoldAPI } from "@/apis";
|
|
import { quickMenus } from "./data";
|
|
|
|
const PAGE_SIZE = 20;
|
|
const coinSummary = ref({
|
|
cumulative: 0,
|
|
available: 0,
|
|
});
|
|
const selectedStoreId = ref("");
|
|
const selectedStoreName = ref("");
|
|
const rewardProducts = ref([]);
|
|
const productPage = ref(0);
|
|
const productTotal = ref(0);
|
|
const productLoading = ref(false);
|
|
const productNoMore = ref(false);
|
|
|
|
const loadCoinSummary = async () => {
|
|
try {
|
|
const result = await getMyGoldAPI(selectedStoreId.value);
|
|
coinSummary.value = {
|
|
cumulative: Number(result?.totalGold) || 0,
|
|
available: Number(result?.usableGold) || 0,
|
|
};
|
|
} catch (error) {
|
|
console.error("加载金币统计失败", error);
|
|
}
|
|
};
|
|
|
|
const loadProducts = async ({ reset = false } = {}) => {
|
|
if (productLoading.value || (!reset && productNoMore.value)) return;
|
|
|
|
const nextPage = reset ? 1 : productPage.value + 1;
|
|
productLoading.value = true;
|
|
if (reset) productNoMore.value = false;
|
|
|
|
try {
|
|
const result = await getGiftListAPI({
|
|
page: nextPage,
|
|
pageSize: PAGE_SIZE,
|
|
storeId: selectedStoreId.value,
|
|
});
|
|
const list = Array.isArray(result?.list) ? result.list : [];
|
|
const mappedList = list.map((item) => ({
|
|
id: item.id,
|
|
name: item.name || "",
|
|
cost: Number(item.coin_price) || 0,
|
|
stock: Number(item.stock) || 0,
|
|
image: item.cover_image || "",
|
|
}));
|
|
|
|
rewardProducts.value = reset
|
|
? mappedList
|
|
: rewardProducts.value.concat(mappedList);
|
|
productPage.value = Number(result?.page) || nextPage;
|
|
const pageCount = Number(result?.pageCount ?? result?.page_count);
|
|
const totalCount = Number(result?.totalCount ?? result?.total);
|
|
const responsePageSize =
|
|
Number(result?.pageSize ?? result?.page_size) || PAGE_SIZE;
|
|
productTotal.value = Number.isFinite(totalCount) ? totalCount : 0;
|
|
|
|
if (Number.isFinite(pageCount) && pageCount >= 0) {
|
|
productNoMore.value = productPage.value >= pageCount;
|
|
} else if (Number.isFinite(totalCount) && totalCount >= 0) {
|
|
productNoMore.value = rewardProducts.value.length >= totalCount;
|
|
} else {
|
|
productNoMore.value = list.length < responsePageSize;
|
|
}
|
|
} catch (error) {
|
|
if (reset) {
|
|
rewardProducts.value = [];
|
|
productPage.value = 0;
|
|
}
|
|
console.error("加载礼品列表失败", error);
|
|
} finally {
|
|
productLoading.value = false;
|
|
}
|
|
};
|
|
|
|
const menuRoutes = {
|
|
rules: "/pages/coin/rules",
|
|
earningRecords: "/pages/coin/earning-records",
|
|
exchangeRecords: "/pages/coin/exchange-records",
|
|
};
|
|
|
|
const buildStorePageUrl = (path, extraQuery = "") => {
|
|
const query = [
|
|
`storeId=${encodeURIComponent(selectedStoreId.value)}`,
|
|
`storeName=${encodeURIComponent(selectedStoreName.value)}`,
|
|
extraQuery,
|
|
]
|
|
.filter(Boolean)
|
|
.join("&");
|
|
return `${path}?${query}`;
|
|
};
|
|
|
|
const decodeRouteText = (value = "") => {
|
|
const text = String(value);
|
|
try {
|
|
return decodeURIComponent(text.replace(/\+/g, " "));
|
|
} catch (error) {
|
|
console.error("门店名称解码失败", error);
|
|
return text;
|
|
}
|
|
};
|
|
|
|
const onMenuSelect = (menu) => {
|
|
if (menu.key === "nearbyStores") {
|
|
uni.redirectTo({
|
|
url: "/pages/coin/nearby-stores",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const url = menuRoutes[menu.key];
|
|
if (!url) {
|
|
uni.showToast({
|
|
title: "功能开发中",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
uni.navigateTo({
|
|
url: buildStorePageUrl(url),
|
|
});
|
|
};
|
|
|
|
const toProductDetail = (product) => {
|
|
uni.navigateTo({
|
|
url: buildStorePageUrl(
|
|
"/pages/coin/product-detail",
|
|
`id=${encodeURIComponent(product.id)}`
|
|
),
|
|
});
|
|
};
|
|
|
|
onLoad((options = {}) => {
|
|
const storeId = String(options.storeId || "");
|
|
if (!/^\d+$/.test(storeId) || Number(storeId) <= 0) {
|
|
uni.redirectTo({
|
|
url: "/pages/coin/nearby-stores",
|
|
});
|
|
return;
|
|
}
|
|
|
|
selectedStoreId.value = storeId;
|
|
selectedStoreName.value = decodeRouteText(options.storeName);
|
|
loadCoinSummary();
|
|
loadProducts({ reset: true });
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Container :bgType="6" :isHome="true" @scrolltolower="loadProducts">
|
|
<template #header>
|
|
<CoinHeader title="我的金币" :subtitle="selectedStoreName" />
|
|
</template>
|
|
<view class="coin-page">
|
|
<CoinBalancePanel
|
|
:cumulative="coinSummary.cumulative"
|
|
:available="coinSummary.available"
|
|
/>
|
|
<CoinQuickMenu :menus="quickMenus" @select="onMenuSelect" />
|
|
<OfflineExchangeNotice />
|
|
<view class="reward-section">
|
|
<view class="reward-grid">
|
|
<view
|
|
v-for="product in rewardProducts"
|
|
:key="product.id"
|
|
class="reward-grid__item"
|
|
@click="toProductDetail(product)"
|
|
>
|
|
<RewardProductCard :product="product" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</Container>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.coin-page {
|
|
width: 100%;
|
|
min-height: 100%;
|
|
padding-bottom: 40rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.reward-section {
|
|
padding: 20rpx 28rpx 40rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.reward-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.reward-grid__item {
|
|
width: 336rpx;
|
|
margin-right: 22rpx;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.reward-grid__item:nth-child(2n) {
|
|
margin-right: 0;
|
|
}
|
|
</style>
|