542 lines
14 KiB
Vue
542 lines
14 KiB
Vue
<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" @click="handleShare">
|
||
<u-icon name="share-square" color="#8dbafc" size="28"></u-icon>
|
||
<text>分享</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</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,
|
||
}
|
||
},
|
||
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 && options.Id && options.local) {
|
||
this.Id = options.Id;
|
||
this.local = options.local;
|
||
}
|
||
},
|
||
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 || !res.success) {
|
||
throw new Error('获取详情失败');
|
||
}
|
||
|
||
if (res.data.storeCover) {
|
||
res.data.storeCover = res.data.storeCover.startsWith('http') ? res.data.storeCover : IMAGE_BASE_URL + res.data.storeCover;
|
||
}
|
||
|
||
this.detailInfo = {...this.detailInfo, ...res.data};
|
||
} catch (err) {
|
||
console.error('获取详情失败:', err);
|
||
uni.showToast({
|
||
title: '获取详情失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
|
||
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.detailImages.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> |