campus-errand/miniapp/pages/food/shop-detail.vue
18631081161 1fd330b233
Some checks reported errors
continuous-integration/drone/push Build encountered an error
下单优化
2026-04-03 15:40:09 +08:00

581 lines
12 KiB
Vue

<template>
<view class="shop-detail-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="nav-back" @click="goBack">
<image class="back-icon" src="/static/ic_back.png" mode="aspectFit"></image>
</view>
<text class="navbar-title">门店详情</text>
<view class="nav-placeholder"></view>
</view>
</view>
<view :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
<!-- 门店 Banner -->
<swiper v-if="shop.banners && shop.banners.length > 0" class="shop-banner" indicator-dots autoplay circular
indicator-color="rgba(255,255,255,0.5)" indicator-active-color="#ffffff">
<swiper-item v-for="(banner, index) in shop.banners" :key="index">
<image class="banner-image" :src="banner.imageUrl" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 门店信息卡片 -->
<view class="shop-info-card">
<image class="shop-avatar" :src="shop.photo || '/static/ic_default.png'" mode="aspectFill"></image>
<view class="shop-info-text">
<text class="shop-name">{{ shop.name }}</text>
<view class="shop-location-row">
<image class="location-icon" src="/static/ic_address.png" mode="aspectFit"></image>
<text class="shop-location">{{ shop.location || '暂无位置信息' }}</text>
</view>
</view>
</view>
<!-- 注意事项 -->
<view v-if="shop.notice" class="notice-section">
<text class="notice-text">{{ shop.notice }}</text>
</view>
<!-- 菜品列表 -->
<view class="dish-list">
<view class="dish-item" v-for="dish in dishes" :key="dish.id">
<image class="dish-photo" :src="dish.photo" mode="aspectFill"></image>
<view class="dish-info">
<text class="dish-name">{{ dish.name }}</text>
<view class="dish-bottom">
<text class="dish-price">¥{{ dish.price.toFixed(1) }}</text>
<view class="dish-actions">
<image class="action-icon" src="/static/ic_reduce.png" mode="aspectFit" v-if="getQuantity(dish.id) > 0" @click="onMinus(dish)"></image>
<text class="dish-quantity"
v-if="getQuantity(dish.id) > 0">{{ getQuantity(dish.id) }}</text>
<image class="action-icon" src="/static/ic_add.png" mode="aspectFit" @click="onPlus(dish)"></image>
</view>
</view>
</view>
</view>
</view>
<!-- 底部购物车栏 -->
<view class="cart-bar">
<view class="cart-left" @click="cartTotalCount > 0 && (showCartPopup = true)">
<image class="cart-icon" src="/static/ic_courier.png" mode="aspectFit"></image>
<text class="cart-price">¥ {{ cartTotalCount > 0 ? cartTotalPrice : '0' }}</text>
</view>
<button class="cart-checkout-btn" :class="{ disabled: cartTotalCount === 0 }"
@click="goCheckout">去结算</button>
</view>
<!-- 购物车弹窗 -->
<view class="cart-popup-mask" v-if="showCartPopup" @click="showCartPopup = false">
<view class="cart-popup" @click.stop>
<view class="cart-popup-header">
<text class="cart-popup-title">购物车</text>
<text class="cart-popup-clear" @click="clearCart">清空</text>
</view>
<scroll-view scroll-y class="cart-popup-list">
<view v-for="shopGroup in cartStore.shopList" :key="shopGroup.shopInfo.id" class="cart-shop-group">
<text class="cart-shop-name">{{ shopGroup.shopInfo.name }}</text>
<view class="cart-popup-item" v-for="item in shopGroup.items" :key="item.dish.id">
<image class="cart-item-photo" :src="item.dish.photo" mode="aspectFill"></image>
<view class="cart-item-info">
<text class="cart-item-name">{{ item.dish.name }}</text>
<text class="cart-item-price">¥{{ item.dish.price.toFixed(1) }}</text>
</view>
<view class="dish-actions">
<image class="action-icon" src="/static/ic_reduce.png" mode="aspectFit" @click="onPopupMinus(shopGroup.shopInfo.id, item.dish)"></image>
<text class="dish-quantity">{{ item.quantity }}</text>
<image class="action-icon" src="/static/ic_add.png" mode="aspectFit" @click="onPopupPlus(shopGroup.shopInfo.id, item.dish)"></image>
</view>
</view>
</view>
</scroll-view>
<view class="cart-popup-footer">
<view class="cart-fee-row" v-if="cartStore.packingFee > 0">
<text class="cart-fee-label">打包费</text>
<text class="cart-fee-value">¥{{ cartStore.packingFee.toFixed(2) }}</text>
</view>
<view class="cart-fee-row">
<text class="cart-fee-label">总价</text>
<text class="cart-fee-total">¥{{ cartStore.totalPriceWithFee.toFixed(2) }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
getShopDetail
} from '../../utils/api'
import {
useCartStore
} from '../../stores/cart'
export default {
data() {
return {
shop: {},
dishes: [],
showCartPopup: false,
statusBarHeight: 0
}
},
computed: {
cartStore() {
return useCartStore()
},
cartTotalCount() {
return this.cartStore.totalCount
},
cartTotalPrice() {
return this.cartStore.totalPrice.toFixed(2)
}
},
onLoad(options) {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight || 0
if (options.id) this.loadShopDetail(options.id)
},
methods: {
goBack() {
uni.navigateBack()
},
async loadShopDetail(id) {
try {
const res = await getShopDetail(id)
this.shop = res || {}
this.dishes = res.dishes || []
this.cartStore.setShopInfo({
id: this.shop.id,
name: this.shop.name,
photo: this.shop.photo,
packingFeeType: this.shop.packingFeeType,
packingFeeAmount: this.shop.packingFeeAmount
})
} catch (e) {}
},
getQuantity(dishId) {
return this.cartStore.getQuantity(dishId)
},
onPlus(dish) {
this.cartStore.addItem(dish)
},
onMinus(dish) {
this.cartStore.removeItem(dish.id)
},
clearCart() {
this.cartStore.clearCart()
// 清空后重新设置当前门店信息,确保可以继续加购
this.cartStore.setShopInfo({
id: this.shop.id,
name: this.shop.name,
photo: this.shop.photo,
packingFeeType: this.shop.packingFeeType,
packingFeeAmount: this.shop.packingFeeAmount
})
this.showCartPopup = false
},
/** 弹窗中减少指定门店的菜品 */
onPopupMinus(shopId, dish) {
const prevShopId = this.cartStore.currentShopId
this.cartStore.currentShopId = shopId
this.cartStore.removeItem(dish.id)
this.cartStore.currentShopId = prevShopId
// 购物车清空后关闭弹窗
if (this.cartStore.totalCount === 0) this.showCartPopup = false
},
/** 弹窗中增加指定门店的菜品 */
onPopupPlus(shopId, dish) {
const prevShopId = this.cartStore.currentShopId
this.cartStore.currentShopId = shopId
this.cartStore.addItem(dish)
this.cartStore.currentShopId = prevShopId
},
goCheckout() {
if (this.cartTotalCount === 0) return
uni.navigateTo({
url: '/pages/food/food-order'
})
}
}
}
</script>
<style scoped>
.shop-detail-page {
padding-bottom: 140rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
/* 自定义导航栏 */
.custom-navbar {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 999;
background: #FFB700;
}
.navbar-content {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
width: 40rpx;
height: 40rpx;
}
.navbar-title {
font-size: 34rpx;
font-weight: bold;
color: #363636;
}
.nav-placeholder {
width: 60rpx;
}
/* Banner */
.shop-banner {
width: 100%;
height: 360rpx;
}
.banner-image {
width: 100%;
height: 360rpx;
}
/* 门店信息卡片 */
.shop-info-card {
display: flex;
align-items: center;
padding: 24rpx;
background: #fff;
}
.shop-avatar {
width: 100rpx;
height: 100rpx;
border-radius: 16rpx;
flex-shrink: 0;
}
.shop-info-text {
flex: 1;
padding-left: 20rpx;
}
.shop-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 12rpx;
display: block;
}
.shop-location-row {
display: flex;
align-items: flex-start;
}
.location-icon {
width: 26rpx;
height: 26rpx;
margin-right: 6rpx;
margin-top: 4rpx;
flex-shrink: 0;
}
.shop-location {
font-size: 26rpx;
color: #999;
line-height: 1.4;
}
/* 注意事项 */
.notice-section {
padding: 16rpx 24rpx;
background: #fff;
border-top: 1rpx solid #f0f0f0;
}
.notice-text {
font-size: 24rpx;
color: #999;
}
/* 菜品列表 */
.dish-list {
margin-top: 20rpx;
padding: 0 24rpx;
}
.dish-item {
display: flex;
align-items: center;
background: #fff;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.dish-item:first-child {
border-radius: 20rpx 20rpx 0 0;
}
.dish-item:last-child {
border-radius: 0 0 20rpx 20rpx;
border-bottom: none;
}
.dish-item:first-child:last-child {
border-radius: 20rpx;
}
.dish-photo {
width: 140rpx;
height: 140rpx;
border-radius: 12rpx;
flex-shrink: 0;
}
.dish-info {
flex: 1;
padding-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 140rpx;
}
.dish-name {
font-size: 30rpx;
color: #333;
font-weight: 500;
}
.dish-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
.dish-price {
font-size: 32rpx;
color: #FF6B00;
font-weight: bold;
}
.dish-actions {
display: flex;
align-items: center;
flex-shrink: 0;
}
.action-icon {
width: 48rpx;
height: 48rpx;
flex-shrink: 0;
}
.dish-quantity {
font-size: 28rpx;
color: #333;
min-width: 48rpx;
text-align: center;
}
/* 底部购物车栏 */
.cart-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 110rpx;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
padding-bottom: env(safe-area-inset-bottom);
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.cart-left {
display: flex;
align-items: center;
}
.cart-icon {
width: 80rpx;
height: 80rpx;
margin-right: 16rpx;
}
.cart-price {
font-size: 36rpx;
color: #FF6B00;
font-weight: bold;
}
.cart-checkout-btn {
width: 220rpx;
height: 72rpx;
line-height: 72rpx;
background: linear-gradient(135deg, #FFB700, #FF9500);
color: #fff;
font-size: 28rpx;
border-radius: 36rpx;
border: none;
text-align: center;
margin-right: 10rpx;
}
.cart-checkout-btn::after {
border: none;
}
.cart-checkout-btn.disabled {
background: #e0e0e0;
color: #999;
}
/* 购物车弹窗 */
.cart-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 100;
display: flex;
align-items: flex-end;
}
.cart-popup {
width: 100%;
max-height: 60vh;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
}
.cart-popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.cart-popup-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.cart-popup-clear {
font-size: 26rpx;
color: #999;
}
.cart-popup-list {
max-height: 50vh;
padding: 0 30rpx;
box-sizing: border-box;
width: 100%;
}
.cart-shop-group {
margin-bottom: 16rpx;
}
.cart-shop-name {
font-size: 28rpx;
font-weight: bold;
color: #333;
padding: 16rpx 0 8rpx;
display: block;
}
.cart-popup-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.cart-item-photo {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
flex-shrink: 0;
}
.cart-item-info {
flex: 1;
padding: 0 16rpx;
display: flex;
flex-direction: column;
min-width: 0;
}
.cart-item-name {
font-size: 26rpx;
color: #333;
}
.cart-item-price {
font-size: 26rpx;
color: #FF6B00;
}
.cart-popup-footer {
padding: 16rpx 30rpx;
padding-bottom: calc(16rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f0f0;
}
.cart-fee-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8rpx 0;
}
.cart-fee-label {
font-size: 26rpx;
color: #666;
}
.cart-fee-value {
font-size: 26rpx;
color: #333;
}
.cart-fee-total {
font-size: 32rpx;
color: #FF6B00;
font-weight: bold;
}
</style>