493 lines
12 KiB
Vue
493 lines
12 KiB
Vue
<template>
|
||
<view class="map-detail-page">
|
||
<!-- 顶部导航栏 -->
|
||
<NavHeader title="店铺位置" />
|
||
|
||
<!-- 地图区域 -->
|
||
<view class="map-container" v-if="mapInitialized">
|
||
<map
|
||
id="storeMap"
|
||
class="map"
|
||
:latitude="mapCenter.latitude"
|
||
:longitude="mapCenter.longitude"
|
||
:markers="markers"
|
||
:scale="16"
|
||
:show-location="false"
|
||
:enable-zoom="false"
|
||
:enable-scroll="false"
|
||
:enable-rotate="false"
|
||
:enable-poi="true"
|
||
></map>
|
||
</view>
|
||
<view class="map-container" v-else>
|
||
<view class="map-placeholder">
|
||
<text>加载地图中...</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 店铺信息卡片(悬浮在下方) -->
|
||
<view class="store-card" v-if="storeInfo.id">
|
||
<view class="store-card-content">
|
||
<!-- 左侧店铺图片 -->
|
||
<view class="store-image-wrapper">
|
||
<image
|
||
class="store-image"
|
||
:src="storeInfo.coverUrl || '/static/service/store-default.png'"
|
||
mode="aspectFill"
|
||
></image>
|
||
</view>
|
||
|
||
<!-- 右侧店铺信息 -->
|
||
<view class="store-info">
|
||
<view class="store-title-row">
|
||
<text class="store-name">{{ storeInfo.name }}</text>
|
||
<view class="distance-info">
|
||
<image
|
||
class="location-icon"
|
||
src="/static/service/location-icon.png"
|
||
mode="aspectFill"
|
||
></image>
|
||
<text class="distance-text">{{ storeInfo.distance || "0" }}km</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="store-detail">
|
||
<text class="detail-label">门店地址:</text>
|
||
<text class="detail-value">{{
|
||
storeInfo.address || "暂无地址"
|
||
}}</text>
|
||
</view>
|
||
|
||
<view class="store-detail">
|
||
<text class="detail-label">联系电话:</text>
|
||
<text class="detail-value">{{
|
||
storeInfo.phone || "暂无电话"
|
||
}}</text>
|
||
</view>
|
||
|
||
<view class="store-tags-row">
|
||
<view class="store-tags">
|
||
<view class="tag-item tag-pink">会员特惠</view>
|
||
<view class="tag-item tag-orange" v-if="storeInfo.typeName">{{
|
||
storeInfo.typeName
|
||
}}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- <view class="enter-store-btn" @click="handleEnterStore"> 进店 </view> -->
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载中提示 -->
|
||
<view class="loading-mask" v-if="loading">
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { getGuildStoreDetail } from "@/api/service";
|
||
import NavHeader from "@/components/NavHeader/NavHeader.vue";
|
||
|
||
export default {
|
||
components: {
|
||
NavHeader
|
||
},
|
||
data() {
|
||
return {
|
||
shopId: null,
|
||
storeInfo: {},
|
||
loading: false,
|
||
// 地图中心点(初始值,加载数据后会更新)
|
||
mapCenter: {
|
||
latitude: 39.908823,
|
||
longitude: 116.39747,
|
||
},
|
||
// 地图标记点
|
||
markers: [],
|
||
// 用户当前位置
|
||
userLocation: null,
|
||
// 地图是否已初始化(避免重复移动)
|
||
mapInitialized: false,
|
||
};
|
||
},
|
||
computed: {
|
||
// // 计算距离文本
|
||
// distanceText() {
|
||
// if (
|
||
// !this.userLocation ||
|
||
// !this.storeInfo.latitude ||
|
||
// !this.storeInfo.longitude
|
||
// ) {
|
||
// return "计算中...";
|
||
// }
|
||
// const distance = this.calculateDistance(
|
||
// this.userLocation.latitude,
|
||
// this.userLocation.longitude,
|
||
// this.storeInfo.latitude,
|
||
// this.storeInfo.longitude
|
||
// );
|
||
// return distance < 1
|
||
// ? `${(distance * 1000).toFixed(0)}m`
|
||
// : `${distance.toFixed(1)}km`;
|
||
// },
|
||
},
|
||
onLoad(options) {
|
||
this.shopId = options.id;
|
||
if (this.shopId) {
|
||
// 从存储中读取用户位置信息
|
||
this.loadUserLocation();
|
||
this.loadStoreDetail();
|
||
} else {
|
||
uni.showToast({
|
||
title: "店铺信息错误",
|
||
icon: "none",
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
}
|
||
},
|
||
methods: {
|
||
// 获取系统信息
|
||
|
||
// 从存储中加载用户位置信息
|
||
loadUserLocation() {
|
||
const savedLocation = uni.getStorageSync("userLocation");
|
||
if (savedLocation && savedLocation.latitude && savedLocation.longitude) {
|
||
this.userLocation = savedLocation;
|
||
}
|
||
},
|
||
|
||
// 验证经纬度是否有效
|
||
isValidCoordinate(lat, lng) {
|
||
const latitude = parseFloat(lat);
|
||
const longitude = parseFloat(lng);
|
||
// 纬度范围:-90 到 90,经度范围:-180 到 180
|
||
return (
|
||
!isNaN(latitude) &&
|
||
!isNaN(longitude) &&
|
||
latitude >= -90 &&
|
||
latitude <= 90 &&
|
||
longitude >= -180 &&
|
||
longitude <= 180
|
||
);
|
||
},
|
||
|
||
// 加载店铺详情
|
||
async loadStoreDetail() {
|
||
if (!this.shopId) return;
|
||
|
||
this.loading = true;
|
||
try {
|
||
const res = await getGuildStoreDetail(this.shopId);
|
||
if (res) {
|
||
this.storeInfo = res;
|
||
// 设置地图中心点和标记
|
||
if (res.latitude && res.longitude) {
|
||
// 验证经纬度是否有效
|
||
if (this.isValidCoordinate(res.latitude, res.longitude)) {
|
||
// 直接设置地图中心点,避免从默认位置移动
|
||
this.mapCenter = {
|
||
latitude: parseFloat(res.latitude),
|
||
longitude: parseFloat(res.longitude),
|
||
};
|
||
this.setMapMarkers();
|
||
// 标记地图已初始化,此时再显示地图,避免移动动画
|
||
this.$nextTick(() => {
|
||
this.mapInitialized = true;
|
||
});
|
||
} else {
|
||
console.warn("店铺经纬度无效:", res.latitude, res.longitude);
|
||
uni.showToast({
|
||
title: "店铺位置信息无效",
|
||
icon: "none",
|
||
});
|
||
// 即使经纬度无效,也显示地图(使用默认位置)
|
||
this.mapInitialized = true;
|
||
}
|
||
} else {
|
||
// 如果没有经纬度,也显示地图(使用默认位置)
|
||
this.mapInitialized = true;
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error("加载店铺详情失败:", error);
|
||
uni.showToast({
|
||
title: "加载店铺信息失败",
|
||
icon: "none",
|
||
});
|
||
// 即使加载失败,也显示地图
|
||
this.mapInitialized = true;
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
// 设置地图标记点
|
||
setMapMarkers() {
|
||
if (!this.storeInfo.latitude || !this.storeInfo.longitude) return;
|
||
|
||
const lat = parseFloat(this.storeInfo.latitude);
|
||
const lng = parseFloat(this.storeInfo.longitude);
|
||
|
||
// 再次验证经纬度有效性
|
||
if (!this.isValidCoordinate(lat, lng)) {
|
||
console.warn("标记点经纬度无效:", lat, lng);
|
||
return;
|
||
}
|
||
|
||
this.markers = [
|
||
{
|
||
id: 1,
|
||
latitude: lat,
|
||
longitude: lng,
|
||
title: this.storeInfo.name || "店铺位置",
|
||
width: 30,
|
||
height: 30,
|
||
callout: {
|
||
content: this.storeInfo.name || "店铺位置",
|
||
color: "#333333",
|
||
fontSize: 14,
|
||
borderRadius: 4,
|
||
bgColor: "#ffffff",
|
||
padding: 8,
|
||
display: "BYCLICK", // 点击时显示
|
||
},
|
||
},
|
||
];
|
||
},
|
||
|
||
// 计算两点之间的距离(单位:km)
|
||
calculateDistance(lat1, lng1, lat2, lng2) {
|
||
// 验证坐标有效性
|
||
if (
|
||
!this.isValidCoordinate(lat1, lng1) ||
|
||
!this.isValidCoordinate(lat2, lng2)
|
||
) {
|
||
return 0;
|
||
}
|
||
|
||
const R = 6371; // 地球半径(km)
|
||
const dLat = ((lat2 - lat1) * Math.PI) / 180;
|
||
const dLng = ((lng2 - lng1) * Math.PI) / 180;
|
||
const a =
|
||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||
Math.cos((lat1 * Math.PI) / 180) *
|
||
Math.cos((lat2 * Math.PI) / 180) *
|
||
Math.sin(dLng / 2) *
|
||
Math.sin(dLng / 2);
|
||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||
return R * c;
|
||
},
|
||
|
||
// // 进店按钮点击
|
||
// handleEnterStore() {
|
||
// uni.navigateTo({
|
||
// url: `/pages/detail/serviceDetail?id=${this.shopId}`,
|
||
// });
|
||
// },
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.map-detail-page {
|
||
height: 100vh;
|
||
background-color: #f5f5f5;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 地图容器 */
|
||
.map-container {
|
||
width: 100%;
|
||
flex: 1;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
.map {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.map-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background-color: #f5f5f5;
|
||
|
||
text {
|
||
font-family: PingFang-SC, PingFang-SC;
|
||
font-weight: 500;
|
||
font-size: 28rpx;
|
||
color: #999999;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 店铺信息卡片 */
|
||
.store-card {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
// height: 40vh;
|
||
background-color: #ffffff;
|
||
border-radius: 24rpx 24rpx 0 0;
|
||
|
||
.store-card-content {
|
||
display: flex;
|
||
padding: 30rpx;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.store-image-wrapper {
|
||
width: 186rpx;
|
||
height: 200rpx;
|
||
flex-shrink: 0;
|
||
border-radius: 12rpx;
|
||
overflow: hidden;
|
||
|
||
.store-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
.store-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
|
||
.store-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 16rpx;
|
||
|
||
.store-name {
|
||
flex: 1;
|
||
font-family: PingFang-SC, PingFang-SC;
|
||
font-weight: bold;
|
||
font-size: 30rpx;
|
||
color: #1a1819;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.distance-info {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
|
||
.location-icon {
|
||
width: 17rpx;
|
||
height: 20rpx;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.distance-text {
|
||
font-family: PingFang-SC, PingFang-SC;
|
||
font-weight: bold;
|
||
font-size: 22rpx;
|
||
color: #004294;
|
||
}
|
||
}
|
||
}
|
||
|
||
.store-detail {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 12rpx;
|
||
line-height: 1.5;
|
||
font-family: PingFang-SC, PingFang-SC;
|
||
font-weight: 500;
|
||
font-size: 22rpx;
|
||
color: #888888;
|
||
|
||
.detail-label {
|
||
margin-right: 8rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.detail-value {
|
||
flex: 1;
|
||
word-break: break-all;
|
||
}
|
||
}
|
||
|
||
.store-tags-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
|
||
.store-tags {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
flex: 1;
|
||
|
||
.tag-item {
|
||
padding: 7rpx 12rpx;
|
||
font-size: 20rpx;
|
||
border-radius: 4rpx;
|
||
|
||
&.tag-pink {
|
||
background-color: rgba(213, 28, 60, 0.1);
|
||
color: #d51c3c;
|
||
border: 1rpx solid #d51c3c;
|
||
}
|
||
|
||
&.tag-orange {
|
||
background-color: rgba(255, 107, 0, 0.1);
|
||
color: #ff6b00;
|
||
border: 1rpx solid #ff6b00;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.enter-store-btn {
|
||
align-self: flex-end;
|
||
padding: 12rpx 40rpx;
|
||
background: #004294;
|
||
border-radius: 25rpx;
|
||
font-family: PingFang-SC, PingFang-SC;
|
||
font-weight: bold;
|
||
font-size: 26rpx;
|
||
color: #ffffff;
|
||
margin-top: auto;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 加载中遮罩 */
|
||
.loading-mask {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.3);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
|
||
.loading-text {
|
||
font-family: PingFang-SC, PingFang-SC;
|
||
font-weight: 500;
|
||
font-size: 28rpx;
|
||
color: #ffffff;
|
||
}
|
||
}
|
||
</style>
|