yindongqi 891ee783a5 feat: 新增优惠券功能与优化用户体验
- 新增商品券类型支持,包括商品券的领取、使用和展示
- 优化优惠券领取流程,支持兑换码兑换和自动跳转登录
- 新增优惠活动功能,支持活动商品折扣
- 重构优惠券页面布局,区分领取和使用页面
- 新增分销商信息编辑功能
- 优化小程序更新机制,改进更新提示流程
- 修复优惠券使用条件判断逻辑
- 调整UI样式,统一主题色为绿色
- 新增电话客服功能,支持动态电话号码
- 优化订单详情页,展示商品券信息
2025-07-14 10:00:22 +08:00

2338 lines
58 KiB
Vue
Raw Permalink 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>
<layout>
<uv-navbar :fixed="false" :title="title" left-arrow @leftClick="$onClickLeft" />
<view class="container" v-if="!loading">
<!-- <view class="banner-container">
<image :src="shopAd" mode="aspectFill" class="shop-banner"></image>
</view> -->
<!-- <view class="notice-bar" v-if="store.notice">
<uv-notice-bar :text="store.notice"></uv-notice-bar>
</view> -->
<view class="main">
<view class="nav">
<view class="header flex-row justify-between">
<view class="store-info flex-row">
<view class="mr-1">
<image :src="store.image" class="store-logo"></image>
</view>
<view class="left" v-if="orderType == 'takein'">
<view class="store-name flex-row align-center" @click="selectShop()">
<text style="font-weight: 800">{{ store.name }}</text>
<view class="iconfont iconarrow-right"></view>
</view>
<view class="store-location flex-row align-center">
<img src="../../static/images/menu/map.png"
style="width: 36rpx; height: 36rpx; margin-right: 22rpx" alt="" />
<text>距离您 {{ kmUnit(store.dis) }}</text>
</view>
</view>
<view class="left overflow-hidden" v-else>
<view class="store-name flex-row align-center" @click="selectShop()">
<view style="font-weight: 800">{{ store.name }}</view>
<view class="iconfont iconarrow-right"></view>
</view>
<view class="store-location flex-row align-center">
<text class="small" v-if="store.distance > 0 && orderType == 'takeout'">(配送距离:
{{
store.distance }}km)</text>
<text class="small" v-else-if="orderType == 'takeout'">(本店不支持外卖)</text>
</view>
</view>
</view>
<view class="order-type flex-row">
<view class="dinein" :class="{ active: orderType == 'takein' }" @tap="takein">
<text>自取</text>
</view>
<view class="takeout" :class="{ active: orderType == 'takeout' }" @tap="takout">
<text>外卖</text>
</view>
</view>
</view>
</view>
<!-- 广告部分 -->
<view class="banner-container flex justify-center align-center" style="height: 250rpx; margin: 1ch;">
<!-- <view class="" style=" width: 95%; height: 85%; border-radius: 26px; background-color: lightgray;">
</view> -->
<!-- {{ ads.list[0].image }} -->
<image :src="ads.list[0].image" mode="aspectFill" class="shop-banner"
style="background-color:lightgray; border-radius: 2ch;"></image>
</view>
<view class="content" :style="{
height: 'calc(100vh - ' + (headerHeight + (store.notice ? 0 : 60) + footerHeight) + 'rpx)',
paddingBottom: '160rpx'
}">
<scroll-view class="menu-sidebar" :scroll-into-view="menuScrollIntoView" scroll-with-animation
scroll-y>
<view class="sidebar-wrapper">
<view class="menu-category" :id="`menu-${item.id}`"
:class="{ 'current': item.id === currentCateId }" v-for="(item, index) in goods"
:key="index" @tap="handleMenuTap(item.id)">
<text>{{ item.name }}</text>
<view class="category-badge" v-if="menuCartNum(item.id) > 0">
{{ menuCartNum(item.id) }}
</view>
</view>
</view>
</scroll-view>
<!-- goods list begin -->
<scroll-view class="goods-container" scroll-with-animation scroll-y :scroll-top="cateScrollTop"
@scroll="handleGoodsScroll" id="goodsContainer">
<view class="goods-wrapper">
<view class="goods-list">
<!-- category begin -->
<view class="category-section" v-for="(item, index) in goods" :key="index"
:id="`cate-${item.id}`">
<view class="category-title">
<text class="category-title-text">{{ item.name }}</text>
<image mode="aspectFill" :src="item.icon" class="category-icon"></image>
</view>
<view class="category-items">
<!-- 商品 begin -->
<view class="good-card" v-for="(good, key) in item.goodsList" :key="key"
:class="{ 'sold-out': good.stock <= 0, 'large-image': good.bigImage === 1 }">
<template v-if="good.bigImage === 1">
<view class="promotion-large-tags"
v-if="getGoodPromotions(good.id).length > 0">
<text class="promotion-large-tag"
v-for="promo in getGoodPromotions(good.id)" :key="promo.id">{{
promo.name }}</text>
</view>
<image mode="aspectFill" :src="good.image" class="good-image-large"
@tap="showGoodDetailModal(item, good)"></image>
<view class="good-details-large flex-row justify-between">
<view class="good-info" style="flex: 2; padding-right: 20rpx;">
<text class="good-name">{{ good.storeName }}</text>
<text class="good-description">{{ good.storeInfo
}}</text>
</view>
<view class="price-action" style="flex: 1;">
<text class="good-price big-good-price">¥{{ good.price
}}</text>
<!-- <view class="action-buttons" v-if="good.stock > 0">
<button type="primary" class="spec-button"
hover-class="none" size="mini"
@tap="showGoodDetailModal(item, good)">
<view class="iconfont iconadd-select"></view>
</button>
<view class="item-badge" v-if="goodCartNum(good.id)">
{{ goodCartNum(good.id) }}
</view>
</view> -->
<view class="action-buttons" v-if="good.stock > 0">
<button type="default" plain
class="spec-button new-spec-button round-button"
v-if="goodCartNum(good.id)" hover-class="none"
size="mini" @tap="handleGoodReduce(good)">
<view class="iconfont iconsami-select"></view>
</button>
<view class="simple-badge" v-if="goodCartNum(good.id)">
{{ goodCartNum(good.id) }}
</view>
<button type="primary"
class="spec-button new-spec-button round-button"
hover-class="none" size="mini"
@tap="showGoodDetailModal(item, good)">
<view class="iconfont iconadd-select"></view>
</button>
</view>
<view class="sold-out-label" v-if="good.stock == 0">已售罄
</view>
</view>
</view>
</template>
<template v-else>
<view class="promotion-tags"
v-if="getGoodPromotions(good.id).length > 0">
<text class="promotion-tag"
v-for="promo in getGoodPromotions(good.id)" :key="promo.id">{{
promo.name }}</text>
</view>
<image mode="aspectFill" :src="good.image" class="good-image"
@tap="showGoodDetailModal(item, good)"></image>
<view class="good-details">
<text class="good-name">{{ good.storeName }}</text>
<text class="good-description">{{ good.storeInfo }}</text>
<view class="price-action flex-row justify-between align-center">
<text class="good-price">¥{{ good.price }}</text>
<!-- <view class="action-buttons" v-if="good.stock > 0">
<button type="primary" class="spec-button"
hover-class="none" size="mini"
@tap="showGoodDetailModal(item, good)">
<view class="iconfont iconadd-select"></view>
</button>
<view class="item-badge" v-if="goodCartNum(good.id)">
{{ goodCartNum(good.id) }}
</view>
</view> -->
<view class="action-buttons" v-if="good.stock > 0">
<button type="default" plain
class="spec-button new-spec-button round-button"
v-if="goodCartNum(good.id)" hover-class="none"
size="mini" @tap="handleGoodReduce(good)">
<view class="iconfont iconsami-select"></view>
</button>
<view class="simple-badge" v-if="goodCartNum(good.id)">
{{ goodCartNum(good.id) }}
</view>
<button type="primary"
class="spec-button new-spec-button round-button"
hover-class="none" size="mini"
@tap="showGoodDetailModal(item, good)">
<view class="iconfont iconadd-select"></view>
</button>
</view>
<view class="sold-out-label" v-if="good.stock == 0">已售罄
</view>
</view>
</view>
</template>
<!-- <image mode="aspectFill" :src="good.image" class="good-image"
@tap="showGoodDetailModal(item, good)"></image>
<view class="good-details">
<text class="good-name">{{ good.storeName }}</text>
<text class="good-description">{{ good.storeInfo }}</text>
<view class="price-action flex-row justify-between align-center">
<text class="good-price">¥{{ good.price }}</text>
<view class="action-buttons" v-if="good.stock > 0">
<button type="primary" class="spec-button"
hover-class="none" size="mini"
@tap="showGoodDetailModal(item, good)">
<view class="iconfont iconadd-select"></view>
</button>
<view class="item-badge" v-if="goodCartNum(good.id)">
{{ goodCartNum(good.id) }}
</view>
</view>
<view class="sold-out-label" v-if="good.stock == 0">已售罄</view>
</view>
</view> -->
</view>
<!-- 商品 end -->
</view>
</view>
<!-- category end -->
<!-- 提示 begin -->
<view class="title_title flex-row justify-between">
<image class="title_image" referrerpolicy="no-referrer"
src="https://file.aiyushantp.com/file/ea023e706e727e5c6dd4437382df9e36ea43d742c2d364e8c4b79997abfa6131.png" />
<text class="title_a">致敏物质提示:</text>
</view>
<text class="title_content">
本菜单中含有含麸质的谷物及其制品,甲壳类动物及甲壳类动物制品,蛋类及蛋类制品,鱼类及鱼类制品,花生、大豆及其制品,奶类及奶类制品,木本坚果及坚果制品。汤内含有当归,当归不适宜人群有脾胃虚弱、腹泻、孕妇、经期女性、热盛出血及当归过敏者。
</text>
<!-- 提示 end -->
</view>
</view>
</scroll-view>
<!-- goods list end -->
</view>
<!-- content end -->
<!-- 购物车栏 begin -->
<view class="cart-bar" v-if="cart.length > 0 && isCartShow">
<view class="cart-icon-container">
<image src="/static/images/menu/cart.png" class="cart-icon" @tap="openCartPopup">
</image>
<view class="cart-badge">{{ getCartGoodsNumber }}</view>
</view>
<view style="display: flex; flex-direction: column;">
<view class="cart-total" @tap="openCartShow">¥{{ getCartGoodsPrice }}</view>
<view v-if="store.freeDeliveryPrice === 1 && orderType == 'takeout'"
style="font-size: smaller; color: gray; margin-left: 20rpx;">
免配送费 <text style="text-decoration: line-through;">¥{{ store.deliveryPrice }}</text>
</view>
<view v-if="store.freeDeliveryPrice != 1 && orderType == 'takeout'"
style="font-size: smaller; color: gray; margin-left: 20rpx;">
预估配送费 ¥{{ store.deliveryPrice }}
</view>
</view>
<view class="promotion-selection" v-if="applicablePromotions.length > 0">
<view class="promotion-list">
<!-- 模板部分:无需传递参数 -->
<radio-group @change="onPromotionChange" style="transform: scale(0.7);">
<label v-for="promo in applicablePromotions" :key="promo.id" class="">
<view class="promotion-content">
<radio :value="promo.id" style="transform: scale(0.7);"
:checked="promo.id === selectedPromotion.id" />
<text class="promotion-name">{{ promo.name }}</text>
</view>
</label>
</radio-group>
</view>
</view>
<button type="primary" class="checkout-button" @tap="toPay" :disabled="disabledPay"
:class="{ disabled: disabledPay }">
{{ disabledPay ? `差${spread}元起送` : '去结算' }}
</button>
</view>
<!-- 购物车栏 end -->
</view>
<!-- 商品详情模态框 begin -->
<modal :show="goodDetailModalVisible" class="good-detail-modal" color="#5A5B5C" width="90%" custom
padding="0rpx" radius="12rpx" @cancel="closeGoodDetailModal">
<template #default>
<view class="modal-header">
<view class="close-button">
<image src="/static/images/menu/close.png" @tap="closeGoodDetailModal"></image>
</view>
</view>
<scroll-view class="modal-body" scroll-y>
<view v-if="good.image" class="modal-image">
<image mode="aspectFill" style="width: 100%; height: 100%; border-radius: 22rpx;"
:src="good.image"></image>
</view>
<view class="modal-content">
<view class="good-basic-info">
<view class="good-title">{{ good.storeName }}</view>
<view class="good-subtitle flex justify-between">
<rich-text :nodes="good.description"></rich-text>
</view>
<!-- <text class="points-info">可获积分:{{ good.giveIntegral }}</text> -->
</view>
<view class="good-properties">
<view class="property-item" v-for="(item, index) in good.productAttr" :key="index">
<view class="property-name">
<text class="name">{{ item.attrName }}</text>
</view>
<view class="property-values">
<view class="property-value" v-for="(value, key) in item.attrValueArr"
:key="key" :class="{ selected: value == newValue[index] }"
@tap="changePropertyDefault(index, key, false)">
{{ value }}
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="modal-footer">
<view class="price-info">
<view class="final-price">¥{{ good.price }}</view>
<view class="selected-props">
{{ good.valueStr }}
</view>
</view>
<view class="quantity-control">
<!-- <text class="stock-info">库存:{{ good.stock }} </text> -->
<button type="default" plain class="quantity-button" size="mini" hover-class="none"
@tap="handlePropertyReduce">
<view class="iconfont iconsami-select"></view>
</button>
<view class="quantity-number">{{ good.number }}</view>
<button type="primary" class="quantity-button" size="min" hover-class="none"
@tap="handlePropertyAdd">
<view class="iconfont iconadd-select"></view>
</button>
</view>
</view>
<view class="add-cart-button" @tap="handleAddToCartInModal">
<view>加入购物车</view>
</view>
</template>
</modal>
<!-- 商品详情模态框 end -->
<!-- 购物车详情popup -->
<uv-popup ref="popup" mode="bottom" class="cart-popup">
<template #default>
<view class="cart-popup-container">
<view class="popup-header">
<text @tap="handleCartClear">清空</text>
</view>
<scroll-view class="cart-items" scroll-y>
<view class="items-wrapper">
<!-- 添加打包费行 -->
<view class="cart-item packing-fee" @tap="toPackingFeeDetail">
<view class="item-info">
<view class="item-name">打包盒费</view>
</view>
<view class="item-price">
<text>¥{{ getPackingFee }}</text>
</view>
</view>
<view class="cart-item" v-for="(item, index) in cart" :key="index">
<view class="item-info">
<view class="item-name">{{ item.name }}</view>
<view class="item-properties">{{ item.valueStr }}</view>
</view>
<view class="item-price">
<text>{{ item.price }}</text>
</view>
<view class="item-controls">
<button type="default" plain size="mini" hover-class="none"
@tap="handleCartItemReduce(index)">
<view class="iconfont iconsami-select"></view>
</button>
<view class="item-quantity">{{ item.number }}</view>
<button type="primary" size="mini" hover-class="none"
@tap="handleCartItemAdd(index)">
<view class="iconfont iconadd-select"></view>
</button>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
</uv-popup>
<!-- 优惠活动选择弹窗 -->
<modal :show="showPromotionModal" class="promotion-select-modal" color="#5A5B5C" width="80%" custom
padding="20rpx" radius="12rpx">
<template #default>
<view class="modal-header">
<text class="modal-title">选择优惠活动</text>
</view>
<scroll-view class="modal-body" scroll-y>
<view class="promotion-item" v-for="promo in applicablePromotions" :key="promo.id">
<text class="promo-name">{{ promo.name }}</text>
<!-- 根据需要显示更多优惠信息 -->
</view>
</scroll-view>
</template>
</modal>
</view>
<!--轻提示-->
<view class="loading" v-if="loading">
<uv-loading-icon color="#DA5650" size=40 mode="circle"></uv-loading-icon>
<button type="primary" style="z-index: 3001;position: absolute;top: 650rpx;" @click="init"
class="cu-btn bg-red">刷新</button>
</view>
<uv-toast ref="uToast"></uv-toast>
<!-- 新增弹窗遮罩层 -->
<view class="popup-mask" v-if="showPopup">
<view class="popup-content" @tap.stop>
<image class="close-icon" src="/static/images/close.png" mode="widthFix" @tap="closePopup" />
<image class="popup-image"
src="https://file.aiyushantp.com/file/cb5c3b28270cf94771b21e517deb1e50aea6789d1c31310b99a289d94d5368c6.png"
mode="widthFix" />
</view>
</view>
<!-- 浮窗优惠券按钮 -->
<view class="coupon-float-btn" @tap="showCouponFlo" v-if="!loading">
<image src="/static/images/coupon-icon.png" class="coupon-float-icon" />
<text class="coupon-float-text">优惠券</text>
</view>
<!-- 优惠券弹窗 -->
<coupon-float :show="showCouponFloat" :coupons="allCoupons" @close="closeCouponFloat"/>
</layout>
</template>
<script setup>
import {
ref,
computed,
nextTick
} from "vue";
import {
useMainStore
} from "@/store/store";
import {
storeToRefs
} from "pinia";
import {
onLoad,
onShow,
onPullDownRefresh,
onHide
} from "@dcloudio/uni-app";
import {
formatDateTime,
kmUnit
} from "@/utils/util";
import {
shopNearby,
menuGoods
} from "@/api/goods";
import {
menuAds
} from "@/api/market";
import { getPromotionactivityInfo } from "@/api/activity"; // 确保路径正确
import CouponFloat from './coupon-float.vue'
import { couponMine } from '@/api/coupon'
const promotionActivities = ref([]); //所有优惠活动
const selectedPromotion = ref(null); // 用于存储用户选择的优惠活动
const showPromotionModal = ref(false); // 控制优惠选择弹窗的显示
const applicablePromotions = ref([]); // 存储当前购物车适用的优惠活动
// 用户选择优惠活动后的处理
const onPromotionChange = (e) => {
// 1. 获取选中项的ID
const selectedId = e.detail.value;
// 2. 通过ID查找完整对象
selectedPromotion.value = applicablePromotions.value.find(
p => p.id == parseInt(selectedId)
);
// console.log("ssssssssssssssss", selectedPromotion.value);
};
const main = useMainStore();
const {
orderType,
address,
store,
location,
isLogin
} = storeToRefs(main);
const title = ref("点餐");
const text = ref("滚动通知");
const goods = ref([]);
const ads = ref([]);
const loading = ref(true);
const currentCateId = ref(0);
const cateScrollTop = ref(0);
const menuScrollIntoView = ref("");
const cart = ref([]);
const goodDetailModalVisible = ref(false);
const good = ref({});
const category = ref({});
const cartPopupVisible = ref(false);
const sizeCalcState = ref(false);
const newValue = ref([]);
const shopAd = ref(
"https://lanhu-oss-2537-2.lanhuapp.com/FigmaDDSSlicePNGca115cd446d280796935fea74b5cd20f.png"
);
const showPopup = ref(false)
const distributorId = ref(null); // 配送员id
const isCartShow = ref(true);
const popup = ref();
const headerHeight = ref(300); // 头部区域高度rpx
const footerHeight = ref(100); // 底部区域高度rpx
const newkmUnit = computed(() => (param) => {
console.log("param:", param);
return "10km";
});
const showCouponFloat = ref(false)
const allCoupons = ref([])
const goodCartNum = computed(() => {
//计算单个饮品添加到购物车的数量
return (id) =>
cart.value.reduce((acc, cur) => {
if (cur.id === id) {
return (acc += cur.number);
}
return acc;
}, 0);
});
const menuCartNum = computed(() => {
return (id) =>
cart.value.reduce((acc, cur) => {
if (cur.cate_id === id) {
return (acc += cur.number);
}
return acc;
}, 0);
});
const getCartGoodsNumber = computed(() => {
//计算购物车总数
return cart.value.reduce((acc, cur) => acc + cur.number, 0);
});
// const getCartGoodsPrice = computed(() => {
// //计算购物车总价
// let price = cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);
// return parseFloat(price).toFixed(2);
// });
const getCartGoodsPrice = computed(() => {
let total = cart.value.reduce((acc, cur) => {
let itemPrice = cur.price * cur.number;
// 如果有优惠活动,将商品打折后,加入总价
if (selectedPromotion.value) {
// console.log("动态更新总价格11111111111",selectedPromotion.value)
itemPrice = selectedPromotion.value.discountRate * itemPrice;
}
const boxPrice = (cur.boxFee || 0) * cur.number;
return acc + itemPrice + boxPrice; // 注意:你原先漏掉了 boxPrice
}, 0);
// 返回数值类型(而非字符串)
return parseFloat(total.toFixed(2)); // 关键修改parseFloat 转换回数值
});
const disabledPay = computed(() => {
const minPrice = parseFloat(store.value.minPrice);
return (
orderType.value === "takeout" &&
getCartGoodsPrice.value < minPrice
);
});
const spread = computed(() => {
if (orderType.value !== "takeout") return;
const minPrice = parseFloat(store.value.minPrice);
return parseFloat((minPrice - getCartGoodsPrice.value).toFixed(2));
});
// 监听自定义事件
uni.$on("refreshMenu", () => {
// 在这里执行onLoad逻辑
console.log("refreshMenu1:", store.value.id);
init();
});
onPullDownRefresh(() => {
init();
});
onLoad(async (options) => {
// 处理扫描二维码进入的情况
if (options && options.q) {
try {
// 解码URL
const decodedUrl = decodeURIComponent(options.q);
// 使用正则表达式提取参数
const distributorIdMatch = decodedUrl.match(/distributorId=([^&]*)/);
const disId = distributorIdMatch ? distributorIdMatch[1] : null;
if (disId) {
// 处理分销员ID逻辑
distributorId.value = disId;
}
} catch (e) {
console.error('解析二维码URL失败:', e);
}
}
if (options && options.distributorId) {
console.log("distributorId111111111111111分销员页面分享:", options.distributorId);
distributorId.value = options.distributorId;
}
init();
refreshCart();
nextTick(() => {
if (goods.value.length > 0) {
currentCateId.value = goods.value[0].id;
}
});
});
onHide(() => {
// 重新进入要重新计算页面高度,否则有问题
sizeCalcState.value = false;
});
onShow(() => {
//init();
refreshCart();
shopAd.value = uni.getStorageSync("shopAd");
// 初始化页面高度计算
nextTick(() => {
calcLayoutHeights();
});
// 获取所有优惠券
loadAllCoupons();
});
const openCartShow = () => {
// isCartShow.value = false;
};
const in_array = (search, array) => {
for (var i in array) {
if (array[i] == search) {
return true;
}
}
return false;
};
const selectShop = () => {
uni.navigateTo({
url: "/pages/components/pages/shop/shop",
});
};
const uToast = ref();
const init = async () => {
//页面初始化
loading.value = true;
//return
let error = {},
result = location.value;
console.log("result:", result);
if (!location.value.hasOwnProperty("latitude")) {
console.log("result1:", location.value);
uni.getLocation({
type: "wgs84",
success: function (res) {
console.log("location1:", res);
result = {
latitude: res.latitude,
longitude: res.longitude,
};
getShopList(result);
},
fail: function (res) {
uni.showToast({
title: "获取位置失败,请检查是否开启相关权限",
duration: 2000,
icon: "error",
});
// 默认地为你为北京地址
result = {
latitude: 39.91999,
longitude: 116.45627,
};
getShopList(result);
},
complete: function (res) { },
});
return;
}
getShopList(result);
};
const getShopList = async (res) => {
console.log("location9:", res);
if (res) {
main.SET_LOCATION(res);
let shop_id = 0;
if (store.value.id) {
shop_id = store.value.id;
}
let shop = await shopNearby({
lat: res.latitude,
lng: res.longitude,
shop_id: shop_id,
kw: "",
});
if (shop) {
//广告图
getAds(shop.id);
shop.notice =
shop.status == 1 ?
shop.notice :
"店铺营业时间为:" +
formatDateTime(shop.startTime, "hh:mm") +
" - " +
formatDateTime(shop.endTime, "hh:mm") +
",不在营业时间内无法下单";
// 设置店铺信息
main.SET_STORE(shop);
let mygoods = await menuGoods({
shopId: shop.id,
});
if (mygoods) {
goods.value = mygoods;
refreshCart();
// 设置初始分类
if (mygoods.length > 0) {
currentCateId.value = mygoods[0].id;
}
// 数据加载完成后,重新计算布局
nextTick(() => {
calcLayoutHeights();
});
loading.value = false;
}
console.log("goods:", mygoods);
console.log("goods:", goods.value);
// loading.value = false;
// uni.stopPullDownRefresh();
// 加载数据后显示弹窗
showPopup.value = true
// 获取活动信息
loadPromotionActivities();
}
}
uni.stopPullDownRefresh();
};
const refreshCart = () => {
if (goods.value && goods.value.length > 0) {
let newGoods = goods.value;
cart.value = [];
let newCart = uni.getStorageSync("cart") || [];
let tmpCart = [];
if (newCart) {
for (let i in newCart) {
for (let ii in newGoods) {
for (let iii in newGoods[ii].goodsList) {
if (newCart[i].id == newGoods[ii].goodsList[iii].id) {
tmpCart.push(newCart[i]);
}
}
}
}
cart.value = tmpCart;
cartPopupVisible.value = false;
// 初始化,获取购物车数据,开始计算优惠
isPromotionActivity();
}
}
};
const loadPromotionActivities = async () => {
try {
const data = { pageNo: 1, pageSize: 100, shopId: store.value.id ? store.value.id : 0 }
const res = await getPromotionactivityInfo(data);
// console.log("res:", res);
if (res) {
promotionActivities.value = res.list; // 假设接口返回的数据在 data 字段
console.log("promotionActivities活动信息000000000:", promotionActivities.value);
} else {
console.error('获取优惠活动失败:', res.msg);
}
} catch (error) {
console.error('获取优惠活动异常:', error);
}
};
const getAds = async (shop_id) => {
let data = await menuAds({
shop_id: shop_id ? shop_id : 0
});
if (data) {
ads.value = data;
}
}
const takout = (force = false) => {
if (orderType.value == "takeout" && force == false) return;
main.SET_ORDER_TYPE("takeout");
orderType.value = "takeout";
if (!isLogin.value) {
uni.navigateTo({
url: "/pages/components/pages/login/login",
});
return;
}
};
const takein = (force = false) => {
if (orderType.value == "takein" && force == false) return;
main.SET_ORDER_TYPE("takein");
orderType.value = "takein";
if (!isLogin.value) {
uni.navigateTo({
url: "/pages/components/pages/login/login",
});
return;
}
};
const handleMenuTap = (id) => {
//点击菜单项事件
if (!sizeCalcState.value) {
calcSize();
}
currentCateId.value = id;
// nextTick(() => cateScrollTop.value = goods.value.find(item => item.id == id).top)
menuScrollIntoView.value = `menu-${id}`;
// 查找对应的分类区域并滚动到该位置
const category = goods.value.find((item) => item.id === id);
if (category && category.top !== undefined) {
// 使用nextTick确保DOM更新后再滚动
nextTick(() => {
cateScrollTop.value = category.top;
// 强制触发滚动以确保位置正确
const goodsContainer = uni
.createSelectorQuery()
.select("#goodsContainer");
if (goodsContainer) {
goodsContainer
.boundingClientRect((data) => {
if (data) {
console.log(
"Scrolling to category:",
id,
"at position:",
category.top
);
}
})
.exec();
}
});
}
};
/**
* 处理商品列表滚动事件的函数。
* 当商品列表滚动时,该函数会根据滚动位置更新当前选中的分类 ID。
*
* @param {Object} param - 包含滚动事件详细信息的对象。
* @param {Object} param.detail - 滚动事件的详细信息,包含滚动位置等信息。
* @param {number} param.detail.scrollTop - 当前滚动条的垂直位置。
*/
const handleGoodsScroll = ({
detail
}) => {
// 商品列表滚动事件
// 检查尺寸计算状态,如果尚未计算过商品分类的尺寸,则调用 calcSize 函数进行计算
if (!sizeCalcState.value) {
calcSize();
}
// 打印滚动事件的详细信息,方便调试
// console.log("scrollTop:", detail);
// 从滚动事件的详细信息中提取滚动条的垂直位置
const {
scrollTop
} = detail;
// 过滤出所有顶部位置小于等于当前滚动位置的商品分类
// 并将结果数组反转,以便获取最接近当前滚动位置的分类
// let tabs = goods.value.filter((item) => item.top <= scrollTop).reverse();
// 获取 class 为 category-title-text 的元素的第一个的高度
// const query = uni.createSelectorQuery();
// let firstElementHeight = 20;
// query.select('.category-title-text').boundingClientRect((data) => {
// if (data) {
// firstElementHeight = data.height;
// // console.log('class 为 category-title-text 的第一个元素的高度为:', firstElementHeight);
// }
// }).exec();
let tabs = goods.value.filter((item) => {
// console.log('item.top 的值为:', item.top);
return item.top <= scrollTop + 2;
}).reverse();
// console.log("tabs:", tabs);
// 如果存在符合条件的分类,则将当前选中的分类 ID 更新为第一个符合条件的分类的 ID
if (tabs.length > 0) {
currentCateId.value = tabs[0].id;
}
};
const calcSize = () => {
let h = 0;
// 更精确计算每个分类的位置
goods.value.forEach((item, index) => {
let view = uni.createSelectorQuery().select(`#cate-${item.id}`);
// 获取 class 为 category-title-text 的元素的第一个的高度
const query = uni.createSelectorQuery();
let firstElementHeight = 20;
query.select('.category-title-text').boundingClientRect((data) => {
if (data) {
firstElementHeight = data.height;
// console.log('class 为 category-title-text 的第一个元素的高度为:', firstElementHeight);
}
}).exec();
view
.fields({
size: true,
rect: true,
},
(data) => {
if (data) {
// 对于第一个分类顶部位置为0
if (index === 0) {
item.top = 0;
} else {
item.top = h + firstElementHeight * index;
}
h += data.height;
item.bottom = h;
// console.log(
// `Category ${item.id} calculated: top=${item.top}, height=${data.height}, bottom=${item.bottom}`
// );
}
}
)
.exec();
});
sizeCalcState.value = true;
};
// 增强页面布局高度计算的准确性
const calcLayoutHeights = () => {
// 获取系统信息
const systemInfo = uni.getSystemInfoSync();
const rpxRatio = 750 / systemInfo.windowWidth;
// 获取头部区域高度
const headerQuery = uni.createSelectorQuery().select(".nav");
headerQuery
.boundingClientRect((data) => {
if (data) {
// 将px转换为rpx
headerHeight.value = data.height * rpxRatio;
console.log("Header height:", headerHeight.value);
}
})
.exec();
// 获取底部购物车栏高度
const footerQuery = uni.createSelectorQuery().select(".cart-bar");
footerQuery
.boundingClientRect((data) => {
if (data) {
// 将px转换为rpx
footerHeight.value = data ? data.height * rpxRatio : 100;
console.log("Footer height:", footerHeight.value);
} else {
// 如果没有购物车栏,设置一个默认值
footerHeight.value = 100;
}
// 重新计算各个分类的位置
nextTick(() => {
sizeCalcState.value = false;
calcSize();
});
})
.exec();
};
// const calcSize = () => {
// let h = 10
// let view = uni.createSelectorQuery().select('#ads')
// if (view) {
// view.fields({
// size: true
// }, data => {
// if (data) {
// h += Math.floor(data.height)
// }
// }).exec()
// }
// goods.value.forEach(item => {
// let view = uni.createSelectorQuery().select(`#cate-${item.id}`)
// view.fields({
// size: true
// }, data => {
// console.log('h3:',h)
// item.top = h
// h += data.height
// item.bottom = h
// }).exec()
// })
// sizeCalcState.value = true
// }
const handleAddToCart = (cate, newGood, num) => {
//添加到购物车
const index = cart.value.findIndex((item) => {
if (newGood) {
return item.id === newGood.id && item.valueStr === good.value.valueStr;
} else {
return item.id === newGood.id;
}
});
// 计算餐饮盒费用假设每个商品需要1个餐饮盒每个0.5元)
const boxFee = newGood.boxFee || 0; // 如果没有设置boxFee则默认0.5元
if (index > -1) {
cart.value[index].number += num;
} else {
cart.value.push({
id: newGood.id,
cate_id: cate.id,
name: newGood.storeName,
price: newGood.price,
number: num,
image: newGood.image,
valueStr: good.value.valueStr,
boxFee: boxFee, // 添加餐饮盒费用
aloneSell: newGood.aloneSell,
});
}
uni.setStorageSync("cart", JSON.parse(JSON.stringify(cart.value)));
//将商品添加到购物车后,开始计算优惠
isPromotionActivity()
};
const handleReduceFromCart = (item, good) => {
const index = cart.value.findIndex((item) => item.id === good.id);
cart.value[index].number -= 1;
if (cart.value[index].number <= 0) {
cart.value.splice(index, 1);
}
uni.setStorageSync("cart", JSON.parse(JSON.stringify(cart.value)));
};
// 处理商品减购操作
const handleGoodReduce = (currentGood) => {
// 从购物车中找出该商品的所有项
const cartItems = cart.value.filter(item => item.id === currentGood.id);
// 检查是否存在不同规格
let hasDifferentSpecs = false;
if (cartItems.length > 1) {
const firstItemValueStr = cartItems[0].valueStr;
hasDifferentSpecs = cartItems.some(item => item.valueStr !== firstItemValueStr);
}
if (hasDifferentSpecs) {
// 存在不同规格提示并打开购物车popup
uToast.value.show({ message: '不同规格的商品需在购物车中减购' });
setTimeout(() => {
popup.value.open(); // 打开底部购物车popup
}, 100);
return;
}
// 单规格直接减购(从购物车中找到对应商品并减少数量)
const cartIndex = cart.value.findIndex(item => item.id === currentGood.id);
if (cartIndex > -1 && cart.value[cartIndex].number > 0) {
cart.value[cartIndex].number--;
// 如果数量减到0则移除商品
if (cart.value[cartIndex].number === 0) {
cart.value.splice(cartIndex, 1);
}
}
// 重新计算优惠
isPromotionActivity();
};
const getGoodPromotions = (productId) => {
if (!promotionActivities.value || promotionActivities.value.length === 0) {
return [];
}
return promotionActivities.value.filter(promo => {
if (promo.productId) {
const productIds = promo.productId.split(',');
return productIds.includes(String(productId));
}
return false;
});
};
const showGoodDetailModal = (item, newGood) => {
isCartShow.value = true;
good.value = JSON.parse(
JSON.stringify({
...newGood,
number: 1,
})
);
category.value = JSON.parse(JSON.stringify(item));
goodDetailModalVisible.value = true;
console.log("goodDetailModalVisible:", goodDetailModalVisible.value);
changePropertyDefault(0, 0, true);
};
const closeGoodDetailModal = () => {
//关闭饮品详情模态框
goodDetailModalVisible.value = false;
category.value = {};
good.value = {};
};
const changePropertyDefault = (index, key, isDefault) => {
//改变默认属性值
let valueStr = "";
// console.log("good:", good.value);
if (isDefault) {
newValue.value = [];
for (let i = 0; i < good.value.productAttr.length; i++) {
newValue.value[i] = good.value.productAttr[i].attrValueArr[0];
}
valueStr = newValue.value.join(",");
} else {
newValue.value[index] = good.value.productAttr[index].attrValueArr[key];
// //valueStr = newValue.value.join(',')
// }
valueStr = newValue.value.join(",");
// 转换为数组并排序
const sortedArray = valueStr.split(",").sort((a, b) => {
return a.charCodeAt(0) - b.charCodeAt(0); // 比较Unicode码点
});
// 重新组合为字符串
valueStr = sortedArray.join(",");
// let productValue = good.value.productValue[valueStr]
// if(!productValue) {
// let skukey = JSON.parse(JSON.stringify(newValue.value))
// //console.log('skukey:',skukey)
// valueStr = skukey.sort((a, b) => a.charCodeAt(0) - b.charCodeAt(0)).join(',')
// productValue = good.value.productValue[valueStr]
}
// console.log('valueStr--------:', valueStr)
let productValue = good.value.productValue[valueStr];
// console.log('productValue--------------:', productValue)
good.value.number = 1;
good.value.price = parseFloat(productValue.price).toFixed(2);
good.value.stock = productValue.stock;
good.value.image = productValue.image ? productValue.image : good.value.image;
good.value.valueStr = valueStr;
};
const handlePropertyAdd = () => {
good.value.number += 1;
};
const handlePropertyReduce = () => {
if (good.value.number === 1) return;
good.value.number -= 1;
};
const handleAddToCartInModal = () => {
if (good.value.stock <= 0) {
uToast.value.show({
message: "商品库存不足",
type: "error"
});
return;
}
// // 暂存当前要添加的商品信息
// const currentGoodToAdd = JSON.parse(JSON.stringify(good.value));
// const currentCategory = JSON.parse(JSON.stringify(category.value));
// const currentNum = good.value.number;
// // 模拟将商品加入购物车后的状态,用于计算优惠
// let tempCart = JSON.parse(JSON.stringify(cart.value));
// const existingCartItemIndex = tempCart.findIndex(item => item.id === currentGoodToAdd.id && item.valueStr === currentGoodToAdd.valueStr);
// if (existingCartItemIndex > -1) {
// tempCart[existingCartItemIndex].number += currentNum;
// } else {
// tempCart.push({
// id: currentGoodToAdd.id,
// cate_id: currentCategory.id,
// name: currentGoodToAdd.storeName,
// price: currentGoodToAdd.price,
// number: currentNum,
// image: currentGoodToAdd.image,
// valueStr: currentGoodToAdd.valueStr,
// boxFee: currentGoodToAdd.boxFee || 0,
// aloneSell: currentGoodToAdd.aloneSell,
// });
// }
// let cartTotalAmount = tempCart.reduce((acc, cur) => acc + cur.number * cur.price, 0);
// console.log('good:',good.value,'category:',category.value)
handleAddToCart(category.value, good.value, good.value.number);
closeGoodDetailModal();
};
// 判断是否满足优惠活动、
const isPromotionActivity = () => {
// console.log("===============初始化时判断是否满足优惠活动", cart.value);
// applicablePromotions.value = [];
const applicablePromotionsCopy = [];
if (promotionActivities.value && promotionActivities.value.length > 0) {
promotionActivities.value.forEach(promo => {
let activityAmount = 0;
let activityCount = 0;
const productIds = promo.productId ? promo.productId.split(',') : [];
// 遍历购物车计算活动商品总额和数量
cart.value.forEach(item => {
if (productIds.includes(String(item.id))) {
activityAmount += item.price * item.number;
activityCount += item.number;
}
});
if (activityAmount >= promo.minAmount && activityCount >= promo.minQuantity) {
applicablePromotionsCopy.push(promo);
}
});
// console.log(applicablePromotionsCopy.length,"applicablePromotionssssssssssssssss:", applicablePromotionsCopy);
if (selectedPromotion.value == null) {
selectedPromotion.value = applicablePromotionsCopy[0];
}
if (applicablePromotionsCopy.length == 0) {
selectedPromotion.value = null;
}
if (applicablePromotionsCopy.length == 1) {
selectedPromotion.value = applicablePromotionsCopy[0];
}
applicablePromotions.value = applicablePromotionsCopy;
// console.log("选择的优惠活动:", selectedPromotion.value);
// console.log("真实的优惠活动", applicablePromotions.value);
}
// console.log('applicablePromotions:', applicablePromotions.value);
// if (applicablePromotions.value.length > 1) {
// // 多个活动满足条件,弹窗让用户选择
// showPromotionModal.value = true;
// // 此处不直接添加购物车,等待用户选择活动
// } else if (applicablePromotions.value.length === 1) {
// // 只有一个活动满足条件,自动应用
// selectedPromotion.value = applicablePromotions.value[0];
// // applyPromotionAndAddToCart(currentCategory, currentGoodToAdd, currentNum);
// closeGoodDetailModal();
// } else {
// // 没有活动满足条件,直接添加
// selectedPromotion.value = null; // 清空已选优惠
// // handleAddToCart(currentCategory, currentGoodToAdd, currentNum);
// closeGoodDetailModal();
// }
};
const openCartPopup = () => {
//打开/关闭购物车列表popup
popup.value.open();
};
const handleCartClear = () => {
//清空购物车
uni.showModal({
title: "提示",
content: "确定清空购物车么",
success: ({
confirm
}) => {
if (confirm) {
popup.value.close();
cart.value = [];
uni.setStorageSync("cart", JSON.parse(JSON.stringify(cart.value)));
}
},
});
};
const handleCartItemAdd = (index) => {
cart.value[index].number += 1;
uni.setStorageSync("cart", JSON.parse(JSON.stringify(cart.value)));
// 重新计算优惠
isPromotionActivity();
};
const handleCartItemReduce = (index) => {
if (cart.value[index].number === 1) {
cart.value.splice(index, 1);
} else {
cart.value[index].number -= 1;
}
if (!cart.value.length) {
cartPopupVisible.value = false;
}
uni.setStorageSync("cart", JSON.parse(JSON.stringify(cart.value)));
// 重新计算优惠
isPromotionActivity();
};
const toPay = () => {
if (!isLogin.value) {
uni.navigateTo({
url: "/pages/components/pages/login/login",
});
return;
} else {
if (store.value.status == 0) {
uToast.value.show({
message: "不在店铺营业时间内",
type: "error"
});
return;
}
// 判断当前是否在配送范围内
if (
orderType.value == "takeout" &&
store.value.distance < store.value.far
) {
uToast.value.show({
message: "选中的地址不在配送范围",
type: "error"
});
return;
}
uni.showLoading({
title: "加载中",
});
uni.setStorageSync("cart", JSON.parse(JSON.stringify(cart.value)));
uni.navigateTo({
url: `/pages/components/pages/pay/pay?distributorId=${distributorId.value}&promotionId=${selectedPromotion.value?.id}`,
});
}
uni.hideLoading();
};
const getPackingFee = computed(() => {
// 计算打包费根据每个商品的boxFee计算
return cart.value.reduce((acc, cur) => acc + cur.number * (cur.boxFee || 0), 0);
});
const toPackingFeeDetail = () => {
// uni.navigateTo({
// url: "/pages/components/pages/packing-fee/packing-fee"
// });
};
const isSpecialPackage = (name) => {
const specialNames = [
"原鲜本味鸽子汤套餐",
"原味老鸡汤套餐",
"黑毛猪浓香筒骨汤套餐",
"杜仲劲爽半筋牛肉汤套餐"
];
return specialNames.includes(name);
};
const closePopup = () => {
showPopup.value = false
}
// 获取所有优惠券
const loadAllCoupons = async () => {
const data = await couponMine({ page: 1, pagesize: 100 });
if (data) allCoupons.value = data;
console.log(data,"------------")
console.log(allCoupons.value,"------------")
}
const showCouponFlo = () => {
console.log("我点击了优惠券按钮")
showCouponFloat.value = true;
}
const closeCouponFloat = () => {
showCouponFloat.value = false;
}
</script>
<style lang="scss" scoped>
@import "./common.css";
/* #ifdef H5 */
page {
height: auto;
min-height: 100%;
}
/* #endif */
.container {
overflow: hidden;
position: relative;
background-color: #ffffff;
}
.loading {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
image {
width: 260rpx;
height: 260rpx;
position: relative;
margin-top: -200rpx;
/* #ifdef h5 */
margin-top: 0;
/* #endif */
}
}
.flex-row {
display: flex;
flex-direction: column;
}
.justify-between {
justify-content: space-between;
}
.align-center {
align-items: center;
}
.shop-banner {
width: 100%;
height: 250rpx;
object-fit: cover;
}
.notice-bar {
height: 60rpx;
background-color: #ffffff;
}
.main {
position: relative;
}
.nav {
background-color: #ffffff;
padding: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
.header {
padding: 10rpx 0;
}
.store-info {
flex: 1;
}
.store-logo {
width: 80rpx;
height: 80rpx;
border-radius: 8rpx;
}
.store-name {
font-size: 32rpx;
font-weight: 800;
color: #000;
}
.store-location {
font-size: 24rpx;
color: #999;
margin-top: 4rpx;
}
.order-type {
background-color: #f7f3e9;
border-radius: 50rpx;
overflow: hidden;
border: 1px solid #fff;
height: 60rpx;
}
.dinein,
.takeout {
padding: 0 20rpx;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
}
.dinein.active {
background-color: #52ac41;
color: #fff;
}
.takeout.active {
background-color: #52ac41;
color: #fff;
}
.content {
width: 100%;
display: flex;
flex-direction: row;
padding-bottom: 160rpx;
}
.menu-sidebar {
width: 170rpx;
height: 100%;
background-color: #f9faf7;
}
.sidebar-wrapper {
display: flex;
flex-direction: column;
}
.menu-category {
width: 100%;
height: 148rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
box-shadow: inset 0px -1px 0px 0px rgba(249, 246, 239, 1);
color: #717171;
font-size: 24rpx;
text-align: center;
}
.menu-category.current {
background-image: linear-gradient(270deg,
rgba(255, 255, 255, 1) 0,
rgba(243, 203, 90, 1) 100%);
color: #000;
font-weight: 600;
font-size: 24rpx;
}
.menu-category.current:before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 10rpx;
height: 100%;
background-color: #52ac41;
}
.category-badge {
position: absolute;
top: 10rpx;
right: 10rpx;
background-color: #eab729;
color: #fff;
border-radius: 50%;
width: 32rpx;
height: 32rpx;
font-size: 22rpx;
display: flex;
align-items: center;
justify-content: center;
}
.goods-container {
flex: 1;
height: 100%;
}
.goods-wrapper {
padding: 20rpx;
}
.category-section {
margin-bottom: 40rpx;
}
.category-title {
font-size: 32rpx;
font-weight: 600;
margin-bottom: 20rpx;
display: flex;
align-items: center;
}
.category-icon {
width: 32rpx;
height: 32rpx;
margin-left: 10rpx;
}
.good-card {
display: flex;
margin-bottom: 30rpx;
border-radius: 12rpx;
padding: 20rpx;
background-color: #fff;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.good-image {
width: 200rpx;
height: 200rpx;
border-radius: 8rpx;
object-fit: cover;
}
.good-details {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
}
.good-name {
font-size: 28rpx;
font-weight: 500;
color: #000;
margin-bottom: 8rpx;
}
.good-description {
font-size: 22rpx;
color: #999;
}
.price-action {
display: flex;
flex: 1;
flex-direction: column;
align-items: flex-end;
}
.good-price {
font-size: 32rpx;
font-weight: 500;
color: #000;
margin-bottom: 10rpx;
}
.big-good-price {
margin-right: 5px;
}
.action-buttons {
display: flex;
align-items: center;
}
.spec-button {
height: 50rpx;
line-height: 50rpx;
padding: 0 20rpx;
font-size: 24rpx;
background-color: #52ac41;
border-radius: 25rpx;
}
.item-badge {
background-color: #eab729;
color: #fff;
border-radius: 50%;
width: 36rpx;
height: 36rpx;
font-size: 22rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 10rpx;
}
.sold-out-label {
color: #999;
font-size: 24rpx;
}
.good-card.sold-out {
opacity: 0.7;
}
.cart-bar {
position: fixed;
bottom: 30rpx;
left: 0;
width: calc(100% - 80rpx);
height: 100rpx;
background-color: #f7f3e9;
display: flex;
align-items: center;
padding: 0 20rpx;
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
border-radius: 50rpx;
margin: 0 40rpx;
z-index: 10;
border: 2px solid #fff;
}
.cart-icon-container {
position: relative;
}
.cart-icon {
width: 84rpx;
height: 84rpx;
}
.cart-badge {
position: absolute;
top: 0;
right: -10rpx;
background-color: #eab729;
color: #000;
border-radius: 50%;
min-width: 32rpx;
height: 32rpx;
font-size: 22rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 6rpx;
}
.cart-total {
font-size: 40rpx;
font-weight: 500;
margin-left: 20rpx;
}
.checkout-button {
height: 100rpx;
line-height: 100rpx;
background-color: #52ac41;
color: #fff;
font-size: 32rpx;
border-radius: 50rpx;
margin-left: auto;
width: 192rpx;
}
.checkout-button.disabled {
background-color: #cccccc !important;
/* 灰色背景 */
color: #999999 !important;
/* 可选:文字颜色变灰 */
}
/* 商品详情模态框 */
.good-detail-modal {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.modal-header {
padding: 20rpx;
display: flex;
justify-content: flex-end;
}
.close-button image {
width: 40rpx;
height: 40rpx;
}
.modal-body {
flex: 1;
overflow-y: auto;
}
.modal-image {
width: 100%;
height: 400rpx;
padding: 10rpx;
}
.modal-image image {
width: 100%;
height: 100%;
object-fit: cover;
}
.modal-content {
padding: 20rpx;
}
.good-basic-info {
margin-bottom: 30rpx;
}
.good-title {
font-size: 32rpx;
font-weight: 600;
margin-bottom: 10rpx;
}
.good-subtitle {
font-size: 24rpx;
color: #999;
word-wrap: break-word;
word-break: break-all;
white-space: normal;
}
.points-info {
color: #f00;
}
.good-properties {
margin-bottom: 30rpx;
}
.property-item {
margin-bottom: 20rpx;
}
.property-name {
font-size: 28rpx;
font-weight: 500;
margin-bottom: 15rpx;
}
.property-values {
display: flex;
flex-wrap: wrap;
}
.property-value {
padding: 10rpx 20rpx;
background-color: #f5f5f5;
border-radius: 6rpx;
margin-right: 15rpx;
margin-bottom: 15rpx;
font-size: 24rpx;
}
.property-value.selected {
background-color: #52ac41;
color: #fff;
}
.modal-footer {
padding: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px solid #eee;
}
.price-info {
display: flex;
flex-direction: column;
}
.final-price {
font-size: 32rpx;
font-weight: 600;
color: #000;
}
.selected-props {
font-size: 22rpx;
color: #999;
margin-top: 5rpx;
}
.quantity-control {
display: flex;
align-items: center;
}
.stock-info {
font-size: 24rpx;
color: #999;
margin-right: 15rpx;
}
.quantity-button {
width: 60rpx;
height: 60rpx;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.quantity-number {
width: 60rpx;
text-align: center;
font-size: 28rpx;
}
.add-cart-button {
height: 100rpx;
line-height: 100rpx;
background-color: #52ac41;
color: #fff;
font-size: 32rpx;
text-align: center;
}
/* 购物车popup */
.cart-popup-container {
max-height: 70vh;
display: flex;
flex-direction: column;
}
.popup-header {
height: 80rpx;
line-height: 80rpx;
padding: 0 30rpx;
background-color: #f7f7f7;
color: #52ac41;
font-size: 26rpx;
text-align: right;
}
// 添加打包费样式
.packing-fee {
border-bottom: 1px dashed #eee;
padding: 20rpx 0;
.item-info {
flex: 1;
}
.item-price {
font-size: 28rpx;
color: #999;
padding: 0 20rpx;
}
}
.cart-items {
max-height: calc(70vh - 80rpx);
}
.items-wrapper {
padding: 20rpx 30rpx;
}
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1px solid #eee;
}
.item-info {
flex: 1;
}
.item-name {
font-size: 28rpx;
color: #000;
margin-bottom: 5rpx;
}
.item-properties {
font-size: 22rpx;
color: #999;
}
.item-price {
font-size: 28rpx;
font-weight: 500;
padding: 0 20rpx;
}
.item-controls {
display: flex;
align-items: center;
}
.item-quantity {
width: 60rpx;
text-align: center;
font-size: 28rpx;
}
button[size="mini"] {
min-width: 50rpx !important;
width: 50rpx !important;
height: 50rpx !important;
padding: 0 !important;
display: flex;
align-items: center;
justify-content: center;
}
button[type="primary"] {
background-color: #52ac41 !important;
}
.image_3 {
width: 48rpx;
height: 6rpx;
margin: 194rpx 0 0 319rpx;
}
.good-image-large {
width: 100%;
height: 300rpx;
border-radius: 8rpx;
object-fit: cover;
}
.good-details-large {
width: 100%;
margin-top: 20rpx;
display: flex;
align-items: center;
}
.large-image {
flex-direction: column;
padding: 20rpx;
}
.good-info {
display: flex;
flex-direction: column;
}
.title_title {
width: 183rpx;
height: 34rpx;
}
.title_image {
width: 32rpx;
height: 32rpx;
margin-top: 1rpx;
}
.title_a {
width: 151rpx;
height: 34rpx;
overflow-wrap: break-word;
color: rgba(153, 153, 153, 1);
font-size: 24rpx;
font-family: PingFang SC-Regular;
font-weight: normal;
text-align: left;
white-space: nowrap;
}
.title_content {
width: 540rpx;
height: 155rpx;
overflow-wrap: break-word;
color: rgba(153, 153, 153, 1);
font-size: 22rpx;
font-family: PingFang SC-Regular;
font-weight: normal;
text-align: left;
padding-bottom: 150px;
}
/* 优惠活动选择弹窗样式 */
.promotion-select-modal .modal-header {
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 20rpx;
border-bottom: 1px solid #eee;
}
.promotion-select-modal .modal-title {
font-size: 30rpx;
font-weight: 500;
}
.promotion-select-modal .modal-body {
max-height: 60vh;
padding-top: 20rpx;
}
.promotion-item {
// padding: 20rpx;
// border-bottom: 1px solid #f5f5f5;
// display: flex;
// justify-content: space-between;
// align-items: center;
}
.promotion-item:last-child {
border-bottom: none;
}
.promo-name {
font-size: 28rpx;
}
.new-spec-button {
border-radius: 15rpx; // 调整按钮圆角
margin-right: 10rpx; // 调整按钮之间的间距
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1); // 添加阴影效果
}
.new-item-badge {
border-radius: 18rpx; // 调整徽章圆角
margin-left: 15rpx; // 调整徽章与按钮之间的间距
}
.round-button {
border-radius: 50%;
/* 设置按钮为圆形 */
width: 40rpx;
/* 设置按钮宽度 */
height: 40rpx;
/* 设置按钮高度 */
padding: 0;
/* 去除内边距 */
display: flex;
justify-content: center;
align-items: center;
}
.simple-badge {
padding: 0px 10px 0px 5px;
}
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.popup-content {
position: relative;
width: 80%;
}
.close-icon {
position: absolute;
top: -20px;
right: -20px;
width: 40px;
height: 40px;
z-index: 1000;
}
.popup-image {
width: 100%;
border-radius: 10px;
}
.promotion-selection {
// padding: 20rpx;
// background: #fff;
}
.promotion-list {
// margin-top: 10rpx;
}
.promotion-item {
display: flex;
padding: 20rpx 0;
border-bottom: 1px solid #eee;
}
.promotion-content {
// display: flex;
// align-items: center;
// gap: 20rpx;
}
.promotion-name {
font-size: 24rpx;
color: #333;
}
.promotion-tags {
width: 200rpx;
position: absolute;
display: flex;
flex-wrap: wrap;
gap: 8rpx;
/* 标签之间的间距 */
margin-top: 8rpx;
/* 标签与上方内容的间距 */
}
.promotion-tag {
display: inline-flex;
/* 使内容居中 */
align-items: center;
padding: 4rpx 12rpx;
/* 上下左右内边距 */
background-color: #ffe0b2;
/* 柔和的背景色,例如浅橙色 */
color: #e65100;
/* 标签文字颜色,与背景色搭配 */
border: 1rpx solid #ffb74d;
/* 边框 */
border-radius: 8rpx;
/* 圆角 */
font-size: 18rpx;
/* 字体大小 */
font-weight: bold;
/* 字体加粗 */
white-space: nowrap;
/* 防止标签文字换行 */
overflow: hidden;
text-overflow: ellipsis;
/* 文字溢出时显示省略号 */
max-width: 100%;
/* 确保标签不会超出父容器 */
}
.promotion-large-tags {
position: absolute;
display: flex;
flex-wrap: wrap;
gap: 8rpx;
/* 标签之间的间距 */
margin-top: 8rpx;
/* 标签与上方内容的间距 */
}
.promotion-large-tag {
display: inline-flex;
/* 使内容居中 */
align-items: center;
padding: 4rpx 12rpx;
/* 上下左右内边距 */
background-color: #ffe0b2;
/* 柔和的背景色,例如浅橙色 */
color: #e65100;
/* 标签文字颜色,与背景色搭配 */
border: 1rpx solid #ffb74d;
/* 边框 */
border-radius: 8rpx;
/* 圆角 */
font-size: 22rpx;
/* 字体大小 */
font-weight: bold;
/* 字体加粗 */
white-space: nowrap;
/* 防止标签文字换行 */
overflow: hidden;
text-overflow: ellipsis;
/* 文字溢出时显示省略号 */
max-width: 100%;
/* 确保标签不会超出父容器 */
}
/* 您还可以添加一些悬停或点击效果,如果需要的话 */
.promotion-tag:active {
opacity: 0.8;
}
.coupon-float-btn {
width: 120rpx;
height: 120rpx;
position: fixed;
right: 40rpx;
top: 550rpx;
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(135deg, #fffbe6 0%, #ffe0b2 100%);
border-radius: 50%;
box-shadow: 0 4rpx 16rpx rgba(249, 174, 61, 0.18);
padding: 18rpx 18rpx 10rpx 18rpx;
cursor: pointer;
transition: box-shadow 0.2s;
&:active {
box-shadow: 0 2rpx 8rpx rgba(249, 174, 61, 0.12);
}
}
.coupon-float-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 6rpx;
}
.coupon-float-text {
font-size: 22rpx;
color: #f9ae3d;
font-weight: bold;
letter-spacing: 2rpx;
}
</style>