consumer-app/pages/detail/mapDetail.vue

661 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="map-detail-page">
<view class="header-fixed-wrapper" :style="{ height: headerHeight + 'px' }">
<NavHeader title="店铺位置" />
</view>
<view class="main-wrap" :style="{ paddingTop: headerHeight + 'px' }">
<!-- 地图区域 -->
<view class="map-container" v-if="mapInitialized">
<map
id="storeMap"
class="map"
:latitude="mapCenter.latitude"
:longitude="mapCenter.longitude"
:markers="markers"
:scale="16"
:show-location="true"
:enable-zoom="true"
:enable-scroll="true"
: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"
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">{{ distanceText }}</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="action-row">
<view class="action-btn action-nav" @click="handleNavigate"></view>
</view>
</view>
</view>
</view>
<!-- -->
<view class="loading-mask" v-if="loading">
<text class="loading-text">加载中...</text>
</view>
</view>
</view>
</template>
<script>
import { getGuildStoreDetail } from "@/api/service";
import NavHeader from "@/components/NavHeader/NavHeader.vue";
import { transformFromWGSToGCJ } from "@/utils/coordinate";
export default {
components: {
NavHeader
},
data() {
return {
statusBarHeight: 0,
shopId: null,
storeInfo: {},
loading: false,
// 地图中心点(初始值,加载数据后会更新)
mapCenter: {
latitude: 39.908823,
longitude: 116.39747,
},
// 地图标记点
markers: [],
// 用户当前位置
userLocation: null,
// 地图是否已初始化(避免重复移动)
mapInitialized: false,
// 店铺坐标GCJ-02用于地图显示
storeLocationGCJ: null,
};
},
computed: {
headerHeight() {
return this.statusBarHeight + 44;
},
// // 计算距离文本
// 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`;
// },
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`;
},
},
onLoad(options) {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
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)) {
// 后台返回的是 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;
});
}
} 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() {
// 使用转换后的 GCJ-02 坐标
if (!this.storeLocationGCJ || !this.storeLocationGCJ.latitude || !this.storeLocationGCJ.longitude) {
return;
}
const lat = this.storeLocationGCJ.latitude;
const lng = this.storeLocationGCJ.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", // 点击时显示
},
},
];
},
// 导航到店铺(打开系统地图)
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
},
// 计算两点之间的距离单位km
// 注意:计算距离时使用原始 WGS-84 坐标,因为距离计算不受坐标系影响
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;
}
.header-fixed-wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
background: #f5f5f5;
}
.main-wrap {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
/* 地图容器 */
.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;
}
.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;
}
}
}
/* 加载中遮罩 */
.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>