168 lines
4.5 KiB
Vue
168 lines
4.5 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 LocationPermissionState from "./components/LocationPermissionState.vue";
|
|
import StoreCard from "./components/StoreCard.vue";
|
|
import { getNearbyStoresAPI } from "@/apis";
|
|
|
|
const PAGE_SIZE = 20;
|
|
const DEFAULT_STORE_IMAGE =
|
|
"https://static.shelingxingqiu.com/shootmini/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);
|
|
|
|
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 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,
|
|
});
|
|
};
|
|
|
|
// 选择门店后替换当前页面,返回时直接回到进入金币模块前的页面。
|
|
const selectStore = (store) => {
|
|
const storeId = String(store?.id || "");
|
|
if (!/^\d+$/.test(storeId) || Number(storeId) <= 0) {
|
|
uni.showToast({
|
|
title: "门店信息无效",
|
|
icon: "none",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const storeName = encodeURIComponent(store?.name || "");
|
|
uni.redirectTo({
|
|
url: `/pages/coin/index?storeId=${encodeURIComponent(storeId)}&storeName=${storeName}`,
|
|
});
|
|
};
|
|
|
|
onLoad(locateAndLoadStores);
|
|
</script>
|
|
|
|
<template>
|
|
<Container :bgType="6" :isHome="true" @scrolltolower="loadStores">
|
|
<template #header>
|
|
<CoinHeader title="附近门店" />
|
|
</template>
|
|
<view class="stores-page">
|
|
<LocationPermissionState
|
|
v-if="showPermissionState"
|
|
@authorize="authorizeLocation"
|
|
/>
|
|
<template v-else>
|
|
<StoreCard
|
|
v-for="store in stores"
|
|
:key="store.id"
|
|
:store="store"
|
|
@select="selectStore"
|
|
/>
|
|
<text v-if="loaded && !stores.length" class="stores-page__more">
|
|
附近暂无门店~
|
|
</text>
|
|
<text v-else-if="noMore" class="stores-page__more">没有更多门店了~</text>
|
|
</template>
|
|
</view>
|
|
</Container>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.stores-page {
|
|
width: 100%;
|
|
min-height: 100%;
|
|
padding-bottom: 54rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.stores-page__more {
|
|
display: block;
|
|
width: 650rpx;
|
|
margin: 26rpx auto 0;
|
|
color: rgba(255, 255, 255, 0.72);
|
|
font-size: 26rpx;
|
|
line-height: 36rpx;
|
|
text-align: left;
|
|
}
|
|
</style>
|