周边服务修改

This commit is contained in:
qiuyuan 2025-07-28 16:40:33 +08:00
parent 7a3a2d50da
commit e459fd740a
2 changed files with 718 additions and 275 deletions

View File

@ -1,19 +1,123 @@
<template> <template>
<view class="kfc-container"> <view class="container">
<!-- 顶部商品图片 --> <!-- 商品图片带轻微圆角和阴影 -->
<view class="container-img"> <view class="cover-container">
<image <image
class="kfc-bucket-img" class="store-cover"
:src="detailInfo.storeCover" :src="detailInfo.storeCover"
></image> mode="aspectFill"
</view> ></image>
<view class="cover-overlay"></view>
</view>
<!-- 店铺信息模块 --> <!-- 店铺信息卡片 -->
<view class="store-info"> <view class="store-card">
<text class="store-name">{{ detailInfo.storeName }}</text> <view class="store-header">
<view class="business-hours">营业时间<text>{{ detailInfo.openAt}}</text></view> <text class="store-name">{{ detailInfo.storeName }}</text>
<view class="address">地址<text>{{ detailInfo.storeAddress }}</text></view> </view>
<view class="distance">距您{{ local }}</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>
</view> </view>
</template> </template>
@ -22,90 +126,416 @@
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config'; import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
import { formatTime, formatRelativeTime } from '@/utils/timeFormat'; import { formatTime, formatRelativeTime } from '@/utils/timeFormat';
export default { export default {
data() { data() {
return { return {
formatTime, formatTime,
IMAGE_BASE_URL, IMAGE_BASE_URL,
Id:null, Id: null,
detailInfo: {}, defaultCover: '/static/images/default-store-cover.png',
local:null, 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) { onLoad(options) {
if (options && options.Id && options.local) { if (options && options.Id && options.local) {
this.Id = options.Id; this.Id = options.Id;
this.local = options.local; this.local = options.local;
} }
}, },
mounted(){ mounted() {
this.getAroundDetail(); this.getAroundDetail();
}, },
methods: { methods: {
async getAroundDetail(){ formatDistance(distance) {
try { if (!distance) return '未知距离';
const res = await get(`/api/v1/apps/surrounding/${this.Id}`); if (distance < 1000) {
if (!res || !res.success) { return `${distance}`;
throw new Error('获取详情失败'); } else {
} return `${(distance / 1000).toFixed(1)}公里`;
res.data.storeCover = IMAGE_BASE_URL + res.data.storeCover; }
this.detailInfo = {...res.data}; },
} catch (err) {
console.error('获取详情失败:', err); 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// 便 $primary-color: #8dbafc;
$primary-color: #999; $secondary-color: #00A699;
$title-size: 36rpx; $dark-color: #2D2D2D;
$text-size: 26rpx; $gray-color: #767676;
$spacing: 30rpx; $light-gray: #F7F7F7;
$border-radius: 10rpx; $border-color: #EBEBEB;
$success-color: #52c41a;
$warning-color: #faad14;
$danger-color: #ff4d4f;
.kfc-container { $border-radius: 24rpx;
padding: 10px; $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;
.container-img{ .cover-container {
max-width: 750rpx; width: 100%;
height: 500rpx; height: 500rpx;
// position: relative;
.kfc-bucket-img { overflow: hidden;
width: 100%; border-bottom-left-radius: $border-radius;
height: 100%; border-bottom-right-radius: $border-radius;
border-radius: $border-radius;
}
}
//
.store-info {
margin-top: $spacing;
.store-name { .store-cover {
font-size: $title-size; width: 100%;
font-weight: bold; height: 100%;
//margin-bottom: $spacing / 2; transition: transform 0.5s ease;
display: block;
} }
.business-hours, .store-cover:active {
.address, transform: scale(1.02);
.distance { }
font-size: $text-size;
color: $primary-color; .cover-overlay {
margin-bottom: 5px; position: absolute;
display: block; bottom: 0;
text{ left: 0;
color: #333; right: 0;
// font-weight: 900; 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);
} }
} }
} }

View File

@ -1,104 +1,87 @@
<template> <template>
<view class="container"> <view class="container">
<view class="nearby-page"> <view class="nearby-page">
<!-- 附近美食模块 -->
<!-- 附近美食模块 --> <view class="section shadow" v-for="(item,index) in aroundList" :key="index">
<view class="section shadow" v-for="(item,index) in aroundList" :key="index"> <view class="section-header" @click="goPage('aroundList')">
<view class="section-header" @click="goPage('aroundList')"> <view class="section-title">{{item.label}}</view>
<view class="section-title">{{item.label}}</view> <u-icon name="arrow-right" size="20" color="#999"></u-icon>
<u-icon name="arrow-right" size="20" color="#999"></u-icon> </view>
</view> <!-- 优化使用网格布局实现更稳定的四列 -->
<view class="goods-list"> <view class="goods-grid">
<view class="goods-item" v-for="i in item.serviceList" @click="goDetail(i.id,i.distance)"> <view class="goods-item" v-for="i in item.serviceList" :key="i.id"
<image class="goods-img" :src="`${IMAGE_BASE_URL}`+i.storeCover"></image> @click="goDetail(i.id,i.distance)">
<text class="goods-name">{{ i.storeName }}</text> <image class="goods-img" :src="`${IMAGE_BASE_URL}`+i.storeCover" mode="aspectFill"></image>
<view class="goods-distance"> <view class="goods-info">
<u-icon name="map" size="18" color="#c6d8fa"></u-icon> <text class="goods-name">{{ i.storeName }}</text>
{{ i.distance + '米'}} <view class="goods-distance">
</view> <u-icon name="map" size="18" color="#c6d8fa"></u-icon>
</view> <text>{{ formatDistance(i.distance) }} </text>
</view> </view>
</view> </view>
</view>
</view> </view>
<Footer></Footer> </view>
</view> </view>
</template> <Footer></Footer>
</view>
<script> </template>
import Footer from '@/components/footer_common.vue';
import { <script>
get, import Footer from '@/components/footer_common.vue';
post import { get, post } from '@/utils/request';
} from '@/utils/request'; import { IMAGE_BASE_URL, BASE_URL } from '@/utils/config';
import { import { formatTime, formatRelativeTime } from '@/utils/timeFormat';
IMAGE_BASE_URL,
BASE_URL export default {
} from '@/utils/config'; components: { Footer },
import { data() {
formatTime,
formatRelativeTime
} from '@/utils/timeFormat';
export default {
components: {
Footer
},
data() {
return { return {
IMAGE_BASE_URL, IMAGE_BASE_URL,
aroundList:[], aroundList:[]
} }
}, },
onLoad() { onLoad() {},
mounted() {
}, this.surroundingList();
mounted() { },
this.surroundingList(); methods: {
}, goDetail(id, local) {
methods: { uni.navigateTo({
// url: `/pages/aroundDetail/index?Id=${id}&local=${local}`,
goDetail(id,local) { });
uni.navigateTo({ },
url: `/pages/aroundDetail/index?Id=${id}&local=${local}`, formatDistance(distance) {
}); if (!distance) return '未知距离';
}, if (distance < 1000) {
async surroundingList() { return `${distance}`;
const position = await this.getCurrentPosition(); } else {
let params = { return `${(distance / 1000).toFixed(1)}公里`;
current: 1, }
pageSize: 10, },
longitude: position.longitude, async surroundingList() {
latitude: position.latitude, const position = await this.getCurrentPosition();
let params = {
// statusArray:"2,3,4", current: 1,
}; pageSize: 10,
try { longitude: position.longitude,
const res = await get('/api/v1/apps/surrounding-type', params); latitude: position.latitude
if (!res || !res.success) { };
throw new Error('获取周边服务失败'); try {
} const res = await get('/api/v1/apps/surrounding-type', params);
console.log("---res", res) if (!res || !res.success) {
this.aroundList = [...res.data]; throw new Error('获取周边服务失败');
// res.data.forEach(item => { }
// let imgUrl = item.cover; this.aroundList = [...res.data];
// if (!imgUrl.startsWith('http') && !imgUrl.startsWith('https')) { } catch (err) {
// item.cover = IMAGE_BASE_URL + imgUrl; console.error('获取周边服务失败:', err);
// } }
// }) },
// this.activityList = [...res.data];
} catch (err) {
console.error('获取周边服务失败:', err);
}
},
getCurrentPosition() { getCurrentPosition() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.getLocation({ uni.getLocation({
type: 'wgs84', type: 'wgs84',
success: (res) => { success: (res) => {
console.log('当前位置:', res);
resolve({ resolve({
longitude: res.longitude, longitude: res.longitude,
latitude: res.latitude latitude: res.latitude
@ -114,106 +97,136 @@
} }
}); });
}); });
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; min-height: 100vh;
background: #f8faff; background: #f8faff;
padding-bottom: 400rpx; padding-bottom: 120rpx;
}
.nearby-page {
padding: 20rpx; .nearby-page {
width: 100%; padding: 20rpx 24rpx;
box-sizing: border-box; width: 100%;
} box-sizing: border-box;
flex: 1;
// section }
.section {
margin: 30rpx 0; // .section {
padding: 20rpx; margin: 32rpx 0;
border-radius: 16rpx; // padding: 24rpx;
background-color: #fff; border-radius: 20rpx;
transition: transform 0.3s ease; // background-color: #fff;
transition: all 0.3s ease;
// box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
&:hover {
transform: translateY(-5rpx); &:active {
} transform: scale(0.98);
} }
}
//
.shadow { .section-header {
box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.08); // display: flex;
} align-items: center;
justify-content: space-between;
.section-header { margin-bottom: 24rpx;
display: flex; padding: 0 10rpx;
align-items: center; }
justify-content: space-between;
margin-bottom: 20rpx; .section-title {
padding: 0 10rpx; font-size: 36rpx;
} font-weight: 600;
color: #333;
.section-title { border-left: 8rpx solid #8dbafc;
font-size: 34rpx; padding-left: 20rpx;
font-weight: 600; line-height: 1.4;
color: #333; }
/* 修正后的边框样式 */
border-left: 10rpx solid rgba(141, 186, 252, 0.55); /* 优化使用grid布局实现更稳定的四列分布 */
padding-left: 16rpx; .goods-grid {
} display: grid;
grid-template-columns: repeat(4, 1fr); /* 强制四列布局 */
.goods-list { gap: 10rpx; /* 统一间距 */
display: flex; }
flex-wrap: wrap;
// justify-content:; .goods-item {
} border-radius: 16rpx;
overflow: hidden;
.goods-item { background: #fff;
width: 23%; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
text-align: center; transition: transform 0.2s ease, box-shadow 0.2s ease;
margin-bottom: 20rpx;
} &:active {
transform: scale(0.96);
.goods-img { box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
width: 150rpx; }
height: 150rpx; }
border-radius: 12rpx;
// margin-bottom: 10rpx; .goods-img {
object-fit: cover; // width: 100%;
} height: 160rpx; /* 优化图片高度比例 */
border-radius: 12rpx 12rpx 0 0;
.goods-name { display: block;
font-size: 24rpx; object-fit: cover; /* 确保图片不变形 */
display: block; }
margin-bottom: 6rpx;
color: #333; .goods-info {
white-space: nowrap; padding: 16rpx;
overflow: hidden; }
text-overflow: ellipsis;
text-align: left; .goods-name {
} font-size: 24rpx;
color: #333;
// 使 font-weight: 500;
.goods-distance { display: -webkit-box;
font-size: 22rpx; -webkit-line-clamp: 2;
color: #999; -webkit-box-orient: vertical;
display: flex; overflow: hidden;
justify-content: flex-end; text-overflow: ellipsis;
align-items: center; line-height: 1.5;
text-align: right; margin-bottom: 8rpx;
}
//
.u-icon { .goods-distance {
margin-right: 6rpx; font-size: 22rpx;
font-size: 18rpx; color: #666;
} display: flex;
} align-items: center;
}
.u-icon {
margin-right: 4rpx;
}
}
/* 响应式优化:小屏幕自动调整为两列 */
@media (max-width: 750rpx) { /* 适配常见手机屏幕 */
.goods-grid {
grid-template-columns: repeat(2, 1fr); /* 小屏幕两列 */
}
.goods-img {
height: 200rpx; /* 增加小屏幕图片高度 */
}
.goods-name {
font-size: 26rpx; /* 增大字体 */
}
}
/* 针对超小屏幕的额外适配 */
@media (max-width: 375rpx) {
.goods-grid {
grid-template-columns: 1fr; /* 极小屏幕单列 */
}
.goods-img {
height: 240rpx;
}
}
</style> </style>