From d2571854bc927424d2525759b6661618ff8120c9 Mon Sep 17 00:00:00 2001
From: zhangyibo95 <690096405@qq.com>
Date: Thu, 20 Aug 2026 14:37:08 +0800
Subject: [PATCH] =?UTF-8?q?update:=20=E5=AF=B9=E6=8E=A5=E9=87=91=E5=B8=81?=
=?UTF-8?q?=E7=9B=B8=E5=85=B3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/apis.js | 58 +++++++-
src/components/Container.vue | 3 +
.../coin/components/ProductHeroSwiper.vue | 29 ++--
.../coin/components/RewardProductCard.vue | 2 +-
src/pages/coin/data.js | 84 ------------
src/pages/coin/earning-records.vue | 98 +++++++++++--
src/pages/coin/exchange-records.vue | 98 +++++++++++--
src/pages/coin/index.vue | 92 ++++++++++++-
src/pages/coin/nearby-stores.vue | 129 +++++++++++++-----
src/pages/coin/product-detail.vue | 59 +++++++-
10 files changed, 494 insertions(+), 158 deletions(-)
diff --git a/src/apis.js b/src/apis.js
index 9f7f8bc..7e8f0ed 100644
--- a/src/apis.js
+++ b/src/apis.js
@@ -26,8 +26,9 @@ try {
}
const ADDONS_BASE_URL = BASE_URL.replace(/\/api\/shoot$/, "/api/shoot");
+const API_ROOT_URL = BASE_URL.replace(/\/api\/shoot$/, "");
// 统一处理业务接口请求,包含登录态、业务错误和特定接口空响应兼容。
-function request(method, url, data = {}, baseUrl = BASE_URL) {
+function request(method, url, data = {}, baseUrl = BASE_URL, successCodes = [0]) {
const token = uni.getStorageSync(
`${uni.getAccountInfoSync().miniProgram.envVersion}_token`
);
@@ -51,7 +52,7 @@ function request(method, url, data = {}, baseUrl = BASE_URL) {
}
if (res.data) {
const {code, data, message} = res.data;
- if (code === 0) resolve(data);
+ if (successCodes.includes(code)) resolve(data);
else if (message) {
const error = {code, data, message};
if (message.indexOf("登录身份已失效") !== -1) {
@@ -698,3 +699,56 @@ export const getMyTenRingRank = (seasonId) => {
if (seasonId !== undefined && seasonId !== null) data.seasonId = seasonId;
return request("GET", "/index/myTenRingRank", data);
};
+
+// 获取当前用户的金币统计,可按门店查询。
+export const getMyGoldAPI = (storeId) => {
+ const data = {};
+ if (storeId !== undefined && storeId !== null) data.storeId = storeId;
+ return request("GET", "/index/gold/my", data);
+};
+
+// 分页获取当前用户的金币流水,type:1=获得,2=兑换。
+export const getGoldLogAPI = ({page = 1, pageSize = 20, type, storeId} = {}) => {
+ const data = {page, pageSize};
+ if (type !== undefined && type !== null) data.type = type;
+ if (storeId !== undefined && storeId !== null) data.storeId = storeId;
+ return request("GET", "/index/gold/log", data);
+};
+
+// 前台礼品接口位于站点根路径,并使用 code=200 表示成功。
+export const getGiftListAPI = ({page = 1, pageSize = 20, sort = "coin_desc"} = {}) => {
+ return request(
+ "GET",
+ "/gin/api/v1/gift/list",
+ {page, page_size: pageSize, sort},
+ API_ROOT_URL,
+ [0, 200]
+ );
+};
+
+export const getGiftDetailAPI = (id) => {
+ return request(
+ "GET",
+ `/gin/api/v1/gift/${id}`,
+ {},
+ API_ROOT_URL,
+ [0, 200]
+ );
+};
+
+// 根据用户定位分页获取附近门店。
+export const getNearbyStoresAPI = ({
+ longitude,
+ latitude,
+ radius = 65535,
+ page = 1,
+ pageSize = 20,
+} = {}) => {
+ return request("GET", "/store/nearby", {
+ longitude,
+ latitude,
+ radius,
+ page,
+ pageSize,
+ });
+};
diff --git a/src/components/Container.vue b/src/components/Container.vue
index 280449e..cf3c3dc 100644
--- a/src/components/Container.vue
+++ b/src/components/Container.vue
@@ -9,6 +9,7 @@ import DeviceChargingDialog from "@/components/DeviceChargingDialog.vue";
import {laserAimAPI, getBattleAPI, matchGameAPI} from "@/apis";
import { capsuleHeight, debounce } from "@/util";
import { returnToBattle } from "@/utils/matchReturn";
+const emit = defineEmits(["scrolltolower"]);
const props = defineProps({
title: {
type: String,
@@ -159,6 +160,8 @@ const goCalibration = async () => {
:enhanced="true"
:bounces="false"
:show-scrollbar="false"
+ :lower-threshold="120"
+ @scrolltolower="emit('scrolltolower')"
:style="{
height: `calc(100vh - ${capsuleHeight + (($slots.header || !isHome) ? 50 : 0)}px - ${
$slots.bottom && showBottom ? (isIOS ? '75px' : '65px') : '0px'
diff --git a/src/pages/coin/components/ProductHeroSwiper.vue b/src/pages/coin/components/ProductHeroSwiper.vue
index ea1886e..160d30c 100644
--- a/src/pages/coin/components/ProductHeroSwiper.vue
+++ b/src/pages/coin/components/ProductHeroSwiper.vue
@@ -1,18 +1,21 @@
-
+
+
+ {{ loading ? "加载中..." : "没有更多了" }}
+
@@ -32,4 +106,12 @@ onLoad((options = {}) => {
width: 100%;
min-height: 100%;
}
+
+.records-page__status {
+ padding: 24rpx 0 32rpx;
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 24rpx;
+ line-height: 34rpx;
+ text-align: center;
+}
diff --git a/src/pages/coin/exchange-records.vue b/src/pages/coin/exchange-records.vue
index 2456318..30242aa 100644
--- a/src/pages/coin/exchange-records.vue
+++ b/src/pages/coin/exchange-records.vue
@@ -1,28 +1,102 @@
-
+
+
+ {{ loading ? "加载中..." : "没有更多了" }}
+
@@ -32,4 +106,12 @@ onLoad((options = {}) => {
width: 100%;
min-height: 100%;
}
+
+.records-page__status {
+ padding: 24rpx 0 32rpx;
+ color: rgba(255, 255, 255, 0.6);
+ font-size: 24rpx;
+ line-height: 34rpx;
+ text-align: center;
+}
diff --git a/src/pages/coin/index.vue b/src/pages/coin/index.vue
index 7094e52..5319b00 100644
--- a/src/pages/coin/index.vue
+++ b/src/pages/coin/index.vue
@@ -1,11 +1,86 @@
-
+
-
+
{
v-for="product in rewardProducts"
:key="product.id"
class="reward-grid__item"
- @click="toProductDetail"
+ @click="toProductDetail(product)"
>
diff --git a/src/pages/coin/nearby-stores.vue b/src/pages/coin/nearby-stores.vue
index fa63a28..e08f1e9 100644
--- a/src/pages/coin/nearby-stores.vue
+++ b/src/pages/coin/nearby-stores.vue
@@ -5,22 +5,106 @@ import Container from "@/components/Container.vue";
import CoinHeader from "./components/CoinHeader.vue";
import LocationPermissionState from "./components/LocationPermissionState.vue";
import StoreCard from "./components/StoreCard.vue";
-import { nearbyStore } from "./data";
+import { getNearbyStoresAPI } from "@/apis";
+const PAGE_SIZE = 20;
+const DEFAULT_STORE_IMAGE = "/static/coin/store-photo.png";
const showPermissionState = ref(false);
+const stores = ref([]);
+const location = ref(null);
+const page = ref(0);
+const total = ref(0);
+const loading = ref(false);
+const noMore = ref(false);
+const loaded = ref(false);
-onLoad((options = {}) => {
- // 预留未授权态,接口接入后替换为微信定位授权结果。
- showPermissionState.value = options.state === "empty";
+const getCurrentLocation = () =>
+ new Promise((resolve, reject) => {
+ uni.getLocation({
+ type: "gcj02",
+ success: resolve,
+ fail: reject,
+ });
+ });
+
+const mapStore = (item = {}) => ({
+ id: item.id,
+ name: item.name || "",
+ address: item.address || "",
+ phone: item.phone || "",
+ hours: item.businessHours || "",
+ image: item.coverImage || DEFAULT_STORE_IMAGE,
});
-const authorizeLocation = () => {
- showPermissionState.value = false;
+const loadStores = async ({ reset = false } = {}) => {
+ if (!location.value || loading.value || (!reset && noMore.value)) return;
+
+ const nextPage = reset ? 1 : page.value + 1;
+ loading.value = true;
+ if (reset) noMore.value = false;
+
+ try {
+ const result = await getNearbyStoresAPI({
+ ...location.value,
+ page: nextPage,
+ pageSize: PAGE_SIZE,
+ });
+ const list = Array.isArray(result?.list) ? result.list : [];
+ const mappedList = list.map(mapStore);
+ stores.value = reset ? mappedList : stores.value.concat(mappedList);
+ page.value = Number(result?.page) || nextPage;
+ const pageCount = Number(result?.pageCount);
+ const totalCount = Number(result?.totalCount ?? result?.total);
+ const responsePageSize = Number(result?.pageSize) || PAGE_SIZE;
+ total.value = Number.isFinite(totalCount) ? totalCount : 0;
+
+ if (Number.isFinite(pageCount) && pageCount >= 0) {
+ noMore.value = page.value >= pageCount;
+ } else if (Number.isFinite(totalCount) && totalCount >= 0) {
+ noMore.value = stores.value.length >= totalCount;
+ } else {
+ noMore.value = list.length < responsePageSize;
+ }
+ } catch (error) {
+ if (reset) {
+ stores.value = [];
+ page.value = 0;
+ }
+ console.error("加载附近门店失败", error);
+ } finally {
+ loading.value = false;
+ loaded.value = true;
+ }
};
+
+const locateAndLoadStores = async () => {
+ try {
+ const position = await getCurrentLocation();
+ location.value = {
+ longitude: position.longitude,
+ latitude: position.latitude,
+ };
+ showPermissionState.value = false;
+ await loadStores({ reset: true });
+ } catch (error) {
+ showPermissionState.value = true;
+ loaded.value = true;
+ console.error("获取定位失败", error);
+ }
+};
+
+const authorizeLocation = () => {
+ uni.openSetting({
+ success: locateAndLoadStores,
+ fail: locateAndLoadStores,
+ });
+};
+
+onLoad(locateAndLoadStores);
-
+
@@ -30,15 +114,11 @@ const authorizeLocation = () => {
@authorize="authorizeLocation"
/>
-
-
-
-
- 更多门店敬请期待~
+
+
+ 附近暂无门店~
+
+ 没有更多门店了~
@@ -52,23 +132,6 @@ const authorizeLocation = () => {
box-sizing: border-box;
}
-.store-placeholder {
- position: relative;
- width: 650rpx;
- height: 324rpx;
- margin: 44rpx auto 0;
- overflow: hidden;
- border-radius: 12rpx;
-}
-
-.store-placeholder__background {
- position: absolute;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
-}
-
.stores-page__more {
display: block;
width: 650rpx;
diff --git a/src/pages/coin/product-detail.vue b/src/pages/coin/product-detail.vue
index 1019288..930225b 100644
--- a/src/pages/coin/product-detail.vue
+++ b/src/pages/coin/product-detail.vue
@@ -1,10 +1,60 @@
@@ -14,14 +64,13 @@ import { productDetail } from "./data";
{{ productDetail.name }}
- 累计金币:
- {{ productDetail.cumulative }}
+ 金币:
+ {{ productDetail.coinPrice }}
(剩余{{ productDetail.stock }}个)