2025-08-12 16:49:19 +08:00

684 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<!-- 商品图片带轻微圆角和阴影 -->
<view class="cover-container">
<image
class="store-cover"
:src="detailInfo.storeCover"
mode="aspectFill"
></image>
<view class="cover-overlay"></view>
</view>
<!-- 店铺信息卡片 -->
<view class="store-card">
<view class="store-header">
<text class="store-name">{{ detailInfo.storeName }}</text>
</view>
<!-- 价格和距离合并一行 -->
<view class="price-distance-row">
<view class="price-tag" v-if="detailInfo.price !== undefined">
{{ detailInfo.price > 0 ? '¥' + detailInfo.price : '免费' }}
</view>
<view class="distance-badge">
<u-icon name="map" color="#fff" size="24"></u-icon>
<text>{{ formatDistance(local) }}</text>
</view>
</view>
<!-- 标签展示 -->
<view class="tags-container" v-if="detailInfo.labels && detailInfo.labels.length">
<view class="tag" v-for="(tag, index) in detailInfo.labels" :key="index">
<u-icon name="tags" size="20" color="#8dbafc" style="margin-right: 6rpx;"></u-icon>
{{ tag }}
</view>
</view>
<view class="divider dotted"></view>
<view class="info-grid">
<view class="info-item">
<u-icon name="map" color="#8dbafc" size="36"></u-icon>
<view class="info-content">
<text class="info-label">详细地址</text>
<text class="info-value">{{ detailInfo.storeAddress }}</text>
</view>
</view>
<view class="info-item" v-if="detailInfo.openAt">
<u-icon name="clock" color="#8dbafc" size="32"></u-icon>
<view class="info-content">
<text class="info-label">开放时间</text>
<text class="info-value">{{ detailInfo.openAt }}</text>
</view>
</view>
<view class="info-item" v-if="detailInfo.concatPhone">
<u-icon name="phone" color="#8dbafc" size="32"></u-icon>
<view class="info-content">
<text class="info-label">联系电话</text>
<text class="info-value">{{ detailInfo.concatPhone }}</text>
</view>
</view>
<view class="info-item" v-if="detailInfo.content">
<u-icon name="info-circle" color="#8dbafc" size="36"></u-icon>
<view class="info-content">
<text class="info-label">详情介绍</text>
<text class="info-value">{{ detailInfo.content }}</text>
</view>
</view>
<view class="info-item" v-if="detailInfo.remark">
<u-icon name="chat-fill" color="#8dbafc" size="36"></u-icon>
<view class="info-content">
<text class="info-label">备注信息</text>
<text class="info-value">{{ detailInfo.remark }}</text>
</view>
</view>
<view class="info-item" v-if="detailInfo.link">
<u-icon name="link" color="#8dbafc" size="36"></u-icon>
<view class="info-content">
<text class="info-label">相关链接</text>
<text class="info-value">{{ detailInfo.link }}</text>
</view>
</view>
</view>
<view class="divider dotted"></view>
<!-- 详情图片部分 -->
<view class="detail-section" v-if="detailInfo.imgs && detailInfo.imgs.length">
<text class="section-title">
<u-icon name="photo-fill" color="#8dbafc" size="32" style="margin-right: 12rpx;"></u-icon>
环境展示
</text>
<view class="image-grid">
<image
class="detail-image"
v-for="(img, index) in detailInfo.imgs"
:key="index"
:src="IMAGE_BASE_URL + img"
mode="aspectFill"
@click="previewImage(index)"
lazy-load
></image>
</view>
</view>
<!-- 地图导航按钮 -->
<view class="action-buttons">
<button class="nav-button" @click="openMap">
<u-icon name="map-fill" color="#fff" size="28"></u-icon>
<text>导航前往</text>
</button>
<button class="share-button" open-type="share" @click="handleShareClick">
<u-icon name="share-square" color="#8dbafc" size="28"></u-icon>
<text>分享</text>
</button>
</view>
</view>
<!-- 授权弹窗 -->
<u-modal
v-model="showLocationModal"
title="位置授权提示"
:content="locationModalContent"
show-cancel-button
@confirm="handleAuthConfirm"
@cancel="handleAuthCancel"
></u-modal>
</view>
</template>
<script>
import { get, post } from '@/utils/request';
import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
import { formatTime, formatRelativeTime } from '@/utils/timeFormat';
export default {
data() {
return {
formatTime,
IMAGE_BASE_URL,
Id: null,
defaultCover: '/static/images/default-store-cover.png',
detailInfo: {
storeName: '',
storeCover: '',
storeAddress: '',
openAt: '',
content: '',
labels: [],
remark: '',
detailImages: [],
id: '',
createdAt: '',
updatedAt: '',
deletedAt: 0,
createdId: '',
deletedId: '',
typeId: '',
longitude: '',
latitude: '',
price: 0,
sequence: 0,
link: '',
status: 1
},
local: null,
showLocationModal: false,
locationModalContent: '我们需要获取您的位置信息,以便提供更精准的服务',
userLocation: null,
isRequestingLocation: false
}
},
computed: {
statusText() {
const statusMap = {
1: '营业中',
2: '已关闭',
3: '即将开业',
0: '未知状态'
};
return statusMap[this.detailInfo.status] || `状态: ${this.detailInfo.status}`;
},
statusClass() {
const statusMap = {
1: 'status-open',
2: 'status-closed',
3: 'status-soon'
};
return statusMap[this.detailInfo.status] || 'status-default';
},
statusIcon() {
const iconMap = {
1: 'home-fill',
2: 'close-circle-fill',
3: 'clock-fill'
};
return iconMap[this.detailInfo.status] || 'question-circle-fill';
}
},
onLoad(options) {
if (options?.Id) {
this.Id = options.Id;
this.local = options.local || null;
this.getAroundDetail();
}
// 启用分享功能
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
});
},
mounted() {
this.getAroundDetail();
},
methods: {
formatDistance(distance) {
if (!distance) return '未知距离';
if (distance < 1000) {
return `${distance}`;
} else {
return `${(distance / 1000).toFixed(1)}公里`;
}
},
async getAroundDetail() {
try {
const res = await get(`/api/v1/apps/surrounding/${this.Id}`);
if (res?.success) {
this.detailInfo = {
...this.detailInfo,
...res.data,
storeCover: res.data.storeCover?.startsWith('http')
? res.data.storeCover
: IMAGE_BASE_URL + res.data.storeCover
};
}
} catch (error) {
console.error('获取详情失败:', error);
uni.showToast({ title: '获取详情失败', icon: 'none' });
}
},
// 处理分享按钮点击
async handleShareClick() {
this.isRequestingLocation = true;
try {
await this.checkLocationPermission();
} catch (error) {
console.error('位置授权异常:', error);
} finally {
this.isRequestingLocation = false;
}
},
// 检查位置权限
async checkLocationPermission() {
const status = await this.getPermissionStatus();
if (status === 'granted') {
await this.getUserLocation();
return;
}
if (status === 'none') {
// 首次请求授权
this.requestLocationPermission();
} else {
// 已拒绝过,显示提示
this.showLocationModal = true;
}
},
// 获取权限状态
getPermissionStatus() {
return new Promise((resolve) => {
uni.getSetting({
success: (res) => {
if (res.authSetting['scope.userLocation'] === undefined) {
resolve('none');
} else if (res.authSetting['scope.userLocation']) {
resolve('granted');
} else {
resolve('denied');
}
},
fail: () => resolve('none')
});
});
},
// 请求位置授权
requestLocationPermission() {
uni.authorize({
scope: 'scope.userLocation',
success: () => this.getUserLocation(),
fail: (err) => {
console.error('授权失败:', err);
this.showLocationModal = true;
}
});
},
// 获取用户位置
getUserLocation() {
return new Promise((resolve, reject) => {
uni.getLocation({
type: 'wgs84',
success: (res) => {
this.userLocation = {
latitude: res.latitude,
longitude: res.longitude
};
resolve(res);
},
fail: (err) => {
console.error('获取位置失败:', err);
reject(err);
}
});
});
},
// 处理授权确认
handleAuthConfirm() {
uni.openSetting({
success: (res) => {
if (res.authSetting['scope.userLocation']) {
this.getUserLocation().catch(() => {
uni.showToast({ title: '获取位置失败', icon: 'none' });
});
}
this.showLocationModal = false;
}
});
},
// 处理授权取消
handleAuthCancel() {
this.showLocationModal = false;
uni.showToast({ title: '已取消位置授权', icon: 'none' });
},
// 微信分享好友
onShareAppMessage() {
return {
title: this.detailInfo.storeName || '发现一个好地方',
path: `/pages/surrounding/detail?Id=${this.Id}`,
imageUrl: this.detailInfo.storeCover,
success: () => {
this.logShareEvent();
},
fail: (err) => {
console.error('分享失败:', err);
}
};
},
// 朋友圈分享
onShareTimeline() {
return {
title: this.detailInfo.storeName || '发现一个好地方',
query: `Id=${this.Id}`,
imageUrl: this.detailInfo.storeCover
};
},
openMap() {
const { latitude, longitude, storeName, storeAddress } = this.detailInfo;
if (latitude && longitude) {
uni.openLocation({
latitude: Number(latitude),
longitude: Number(longitude),
name: storeName,
address: storeAddress,
success: () => {
console.log('打开地图成功');
},
fail: (err) => {
console.error('打开地图失败:', err);
uni.showToast({
title: '打开地图失败',
icon: 'none'
});
}
});
} else {
uni.showToast({
title: '暂无位置信息',
icon: 'none'
});
}
},
previewImage(index) {
// if (!this.detailInfo.detailImages || !this.detailInfo.detailImages.length) return;
uni.previewImage({
current: index,
urls: this.detailInfo.imgs.map(img => img.startsWith('http') ? img : IMAGE_BASE_URL + img)
});
},
handleShare() {
uni.showActionSheet({
itemList: ['分享到微信', '分享到朋友圈', '复制链接'],
success: (res) => {
uni.showToast({
title: `已选择: ${res.tapIndex === 0 ? '微信' : res.tapIndex === 1 ? '朋友圈' : '复制链接'}`,
icon: 'none'
});
}
});
}
}
}
</script>
<style lang="scss" scoped>
$primary-color: #8dbafc;
$secondary-color: #00A699;
$dark-color: #2D2D2D;
$gray-color: #767676;
$light-gray: #F7F7F7;
$border-color: #EBEBEB;
$success-color: #52c41a;
$warning-color: #faad14;
$danger-color: #ff4d4f;
$border-radius: 24rpx;
$spacing: 32rpx;
$card-padding: 40rpx;
$shadow-light: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
$shadow-medium: 0 10rpx 30rpx rgba(0, 0, 0, 0.08);
.container {
background-color: $light-gray;
min-height: 100vh;
padding-bottom: 80rpx;
.cover-container {
width: 100%;
height: 500rpx;
position: relative;
overflow: hidden;
border-bottom-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
.store-cover {
width: 100%;
height: 100%;
transition: transform 0.5s ease;
}
.store-cover:active {
transform: scale(1.02);
}
.cover-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 160rpx;
background: linear-gradient(transparent, rgba(0,0,0,0.6));
}
}
.store-card {
background: #FFFFFF;
border-radius: $border-radius;
margin: -60rpx $spacing 0;
padding: $card-padding;
position: relative;
z-index: 2;
box-shadow: $shadow-medium;
transition: transform 0.3s, box-shadow 0.3s;
&:active {
transform: translateY(2rpx);
box-shadow: $shadow-light;
}
.store-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20rpx;
.store-name {
font-size: 44rpx;
font-weight: 700;
color: $dark-color;
line-height: 1.3;
word-break: break-word;
flex: 1;
}
}
.price-distance-row {
display: flex;
align-items: center;
margin-bottom: 30rpx;
.price-tag {
background: #f5f8ff;
color: $primary-color;
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-size: 28rpx;
font-weight: 600;
margin-right: 12rpx;
white-space: nowrap;
}
.distance-badge {
background: $primary-color;
border-radius: 40rpx;
padding: 8rpx 20rpx;
display: flex;
align-items: center;
color: white;
font-size: 26rpx;
white-space: nowrap;
.u-icon {
margin-right: 8rpx;
}
}
}
.tags-container {
display: flex;
flex-wrap: wrap;
margin-bottom: 24rpx;
gap: 12rpx;
.tag {
background-color: #f0f7ff;
color: $primary-color;
padding: 8rpx 20rpx;
border-radius: 30rpx;
font-size: 26rpx;
border: 1rpx solid rgba($primary-color, 0.3);
display: inline-flex;
align-items: center;
}
}
.divider {
height: 2rpx;
margin: $spacing 0;
&.dotted {
background: repeating-linear-gradient(to right, $border-color, $border-color 4rpx, transparent 4rpx, transparent 8rpx);
}
}
.info-grid {
display: flex;
flex-direction: column;
.info-item {
display: flex;
padding: 24rpx 0;
align-items: flex-start;
border-bottom: 2rpx dashed $border-color;
&:last-child {
border-bottom: none;
}
.u-icon {
margin-right: 20rpx;
margin-top: 4rpx;
flex-shrink: 0;
}
.info-content {
flex: 1;
.info-label {
display: block;
font-size: 26rpx;
color: $gray-color;
margin-bottom: 8rpx;
padding-left: 10rpx;
}
.info-value {
display: block;
font-size: 28rpx;
color: $dark-color;
line-height: 1.6;
word-break: break-all;
padding-left: 10rpx;
}
}
}
}
.action-buttons {
margin-top: 40rpx;
display: flex;
gap: 20rpx;
button {
flex: 1;
height: 80rpx;
line-height: 80rpx;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
border: none;
transition: all 0.3s;
font-weight: 500;
.u-icon {
margin-right: 10rpx;
}
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
.nav-button {
background: $primary-color;
color: white;
box-shadow: 0 6rpx 16rpx rgba(141, 186, 252, 0.4);
}
.share-button {
background: #f0f7ff;
color: $primary-color;
border: 2rpx solid $primary-color;
}
}
}
.detail-section {
margin-top: $spacing;
.section-title {
display: flex;
align-items: center;
font-size: 32rpx;
font-weight: 600;
color: $dark-color;
margin-bottom: 24rpx;
}
.image-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
margin-bottom: $spacing;
}
.detail-image {
width: 100%;
height: 280rpx;
border-radius: 16rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
transition: transform 0.3s, box-shadow 0.3s;
overflow: hidden;
}
.detail-image:active {
transform: scale(0.98);
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
}
}
}
</style>