consumer-app/pages/detail/mapDetail.vue

637 lines
17 KiB
Vue
Raw Permalink Normal View History

2025-12-19 12:27:55 +00:00
<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"
2026-03-02 07:32:41 +00:00
:show-location="true"
:enable-zoom="true"
:enable-scroll="true"
2025-12-19 12:27:55 +00:00
: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"
2026-01-14 10:55:42 +00:00
:src="storeInfo.coverUrl"
2025-12-19 12:27:55 +00:00
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>
2026-03-02 07:32:41 +00:00
<text class="distance-text">{{ distanceText }}</text>
2025-12-19 12:27:55 +00:00
</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>
2026-03-02 07:32:41 +00:00
<view class="action-row">
<view class="action-btn action-nav" @click="handleNavigate"></view>
</view>
2025-12-19 12:27:55 +00:00
</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";
2026-03-02 07:32:41 +00:00
import { transformFromWGSToGCJ } from "@/utils/coordinate";
2025-12-19 12:27:55 +00:00
export default {
components: {
NavHeader
},
data() {
return {
shopId: null,
storeInfo: {},
loading: false,
// 地图中心点(初始值,加载数据后会更新)
mapCenter: {
latitude: 39.908823,
longitude: 116.39747,
},
// 地图标记点
markers: [],
// 用户当前位置
userLocation: null,
// 地图是否已初始化(避免重复移动)
mapInitialized: false,
2026-03-02 07:32:41 +00:00
// 店铺坐标GCJ-02用于地图显示
storeLocationGCJ: null,
2025-12-19 12:27:55 +00:00
};
},
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`;
// },
2026-03-02 07:32:41 +00:00
distanceText() {
if (
!this.userLocation ||
!this.storeInfo ||
!this.storeInfo.latitude ||
!this.storeInfo.longitude
) {
return "0km";
}
// 计算距离时使用原始 WGS-84 坐标(店铺坐标)和用户位置坐标
// 注意:如果用户位置也是 WGS-84直接计算如果是 GCJ-02需要转换
// 但距离计算对坐标系不敏感,误差很小,可以直接使用
const lat = parseFloat(this.storeInfo.latitude);
const lng = parseFloat(this.storeInfo.longitude);
if (!this.isValidCoordinate(lat, lng)) return "0km";
const distance = this.calculateDistance(
this.userLocation.latitude,
this.userLocation.longitude,
lat,
lng
);
return distance < 1 ? `${(distance * 1000).toFixed(0)}m` : `${distance.toFixed(1)}km`;
},
2025-12-19 12:27:55 +00:00
},
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)) {
2026-03-02 07:32:41 +00:00
// 后台返回的是 WGS-84 坐标系,需要转换为 GCJ-02腾讯地图使用
try {
const wgs84Lat = parseFloat(res.latitude);
const wgs84Lng = parseFloat(res.longitude);
const gcj02Coord = transformFromWGSToGCJ(wgs84Lat, wgs84Lng);
// 保存转换后的坐标
this.storeLocationGCJ = {
latitude: gcj02Coord.latitude,
longitude: gcj02Coord.longitude,
};
// 使用转换后的坐标设置地图中心点
this.mapCenter = {
latitude: gcj02Coord.latitude,
longitude: gcj02Coord.longitude,
};
this.setMapMarkers();
// 标记地图已初始化,此时再显示地图,避免移动动画
this.$nextTick(() => {
this.mapInitialized = true;
});
} catch (error) {
console.error("坐标转换失败:", error);
// 转换失败时使用原始坐标(降级处理)
const fallbackLat = parseFloat(res.latitude);
const fallbackLng = parseFloat(res.longitude);
this.storeLocationGCJ = {
latitude: fallbackLat,
longitude: fallbackLng,
};
this.mapCenter = {
latitude: fallbackLat,
longitude: fallbackLng,
};
this.setMapMarkers();
this.$nextTick(() => {
this.mapInitialized = true;
});
}
2025-12-19 12:27:55 +00:00
} 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() {
2026-03-02 07:32:41 +00:00
// 使用转换后的 GCJ-02 坐标
if (!this.storeLocationGCJ || !this.storeLocationGCJ.latitude || !this.storeLocationGCJ.longitude) {
return;
}
2025-12-19 12:27:55 +00:00
2026-03-02 07:32:41 +00:00
const lat = this.storeLocationGCJ.latitude;
const lng = this.storeLocationGCJ.longitude;
2025-12-19 12:27:55 +00:00
// 再次验证经纬度有效性
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", // 点击时显示
},
},
];
},
2026-03-02 07:32:41 +00:00
// 导航到店铺(打开系统地图)
handleNavigate() {
// 使用转换后的 GCJ-02 坐标进行导航(微信的 openLocation 需要 GCJ-02 坐标系)
if (!this.storeLocationGCJ || !this.storeLocationGCJ.latitude || !this.storeLocationGCJ.longitude) {
uni.showToast({ title: "暂无位置信息", icon: "none" });
return;
}
const lat = this.storeLocationGCJ.latitude;
const lng = this.storeLocationGCJ.longitude;
if (!this.isValidCoordinate(lat, lng)) {
uni.showToast({ title: "位置信息无效", icon: "none" });
return;
}
// 在微信小程序中,使用 wx.openLocation 打开地图选择器
// 用户可以选择使用哪个地图 App高德、百度、腾讯等来导航
// wx.openLocation 需要 GCJ-02 坐标系
// #ifdef MP-WEIXIN
wx.openLocation({
latitude: lat,
longitude: lng,
name: this.storeInfo.name || "店铺位置",
address: this.storeInfo.address || "",
scale: 18,
success: () => {
console.log("打开地图成功");
},
fail: (err) => {
console.error("打开地图失败:", err);
uni.showToast({
title: "打开地图失败,请检查位置权限",
icon: "none",
});
},
});
// #endif
// #ifndef MP-WEIXIN
// 非微信小程序环境,使用 uni.openLocation
uni.openLocation({
latitude: lat,
longitude: lng,
name: this.storeInfo.name || "店铺位置",
address: this.storeInfo.address || "",
success: () => {
console.log("打开地图成功");
},
fail: (err) => {
console.error("打开地图失败:", err);
uni.showToast({
title: "打开地图失败",
icon: "none",
});
},
});
// #endif
},
2025-12-19 12:27:55 +00:00
// 计算两点之间的距离单位km
2026-03-02 07:32:41 +00:00
// 注意:计算距离时使用原始 WGS-84 坐标,因为距离计算不受坐标系影响
2025-12-19 12:27:55 +00:00
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;
}
2026-03-02 07:32:41 +00:00
.action-row {
display: flex;
gap: 16rpx;
margin-top: auto;
justify-content: flex-end;
}
.action-btn {
padding: 12rpx 30rpx;
border-radius: 25rpx;
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 26rpx;
color: #ffffff;
}
.action-nav {
background: #004294;
}
.action-call {
background: #ff6b00;
}
2025-12-19 12:27:55 +00:00
}
}
/* 加载中遮罩 */
.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>