consumer-app/pages/detail/serviceDetail.vue

757 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="store-page">
<!-- 顶部导航栏 -->
<NavHeader title="店铺详情" />
<!-- 顶部店铺信息点击进入地图页 -->
<view class="store-header" @click="handleGoMap">
<view class="store-brand">
<image
class="brand-image"
:src="storeInfo.coverUrl"
mode="aspectFill"
></image>
</view>
<view class="store-info">
<text class="store-name">{{ storeInfo.name }}</text>
<view class="store-detail">
<text class="detail-text">门店地址: {{ storeInfo.address }}</text>
</view>
<view class="store-detail">
<text class="detail-text">联系电话: {{ storeInfo.phone }}</text>
</view>
<view class="store-tags-row">
<view class="store-tags">
<view class="tag-item tag-pink">会员特惠</view>
<view class="tag-item tag-orange">{{ categoryLabel }}</view>
</view>
<view class="distance-info">
<image
class="location-icon"
src="/static/service/location-icon.png"
mode="aspectFill"
></image>
<text class="distance-text"
>距您 {{ distance || "0" }}km</text
>
</view>
</view>
</view>
</view>
<!-- 中间菜单列表可滑动 -->
<scroll-view class="menu-list" scroll-y="true">
<view class="menu-item" v-for="(item, index) in menuList" :key="index">
<view
class="checkbox"
:class="{ checked: item.selected }"
@click="toggleMenuItem(index)"
>
<text v-if="item.selected" class="checkmark">✓</text>
</view>
<image
class="menu-image"
:src="item.coverUrl || '/static/service/menu-default.png'"
mode="aspectFill"
></image>
<view class="menu-info">
<view class="menu-title-row">
<text class="menu-title">{{ item.name }}</text>
<view class="discount-badge" v-if="item.discount">
{{ item.discount }}折
</view>
</view>
<!-- <text class="menu-desc">{{ item.description }}</text> -->
<view class="menu-price-row">
<view class="current-price">
<view class="price-label">现价¥</view>
<view class="price-text">{{ formatPrice(item.salePrice) }}</view>
</view>
<text class="original-price" v-if="item.originalPrice"
>原价¥{{ formatPrice(item.originalPrice) }}</text
>
<view class="quantity-control">
<view
class="quantity-btn minus"
:class="{
disabled: (item.quantity || 0) <= 0,
}"
@click="decreaseQuantity(index)"
>-</view
>
<text class="quantity-number">{{ item.quantity || 0 }}</text>
<view class="quantity-btn plus" @click="increaseQuantity(index)"
>+</view
>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部结算栏(固定) -->
<view class="checkout-footer">
<view class="total-info">
<text class="total-label">总金额¥</text>
<text class="total-amount">{{ totalAmount.toFixed(2) }}</text>
<view class="member-benefit">
<image
class="crown-icon"
src="/static/service/crown-icon.png"
mode="aspectFill"
></image>
<text class="benefit-text">{{ memberLevelName }}</text>
</view>
</view>
<button
class="checkout-btn"
:class="{ disabled: totalAmount <= 0 }"
:disabled="totalAmount <= 0"
@click="handleCheckout"
>
去结算
</button>
</view>
</view>
</template>
<script>
import {
getGuildStoreDetail,
getGuildCoupon,
getPaySign,
appBuy
} from "@/api/service";
import NavHeader from "@/components/NavHeader/NavHeader.vue";
export default {
components: {
NavHeader,
},
data() {
return {
storeInfo: {},
menuList: [],
categoryLabel: "",
shopId: null,
userInfo: {},
distance: null,
paying: false,
};
},
computed: {
// 计算总金额只计算选中且数量大于0的商品
totalAmount() {
return this.menuList.reduce((total, item) => {
if (item.selected && item.quantity > 0) {
// 价格是以分为单位,需要转换为元
const price = (item.salePrice || 0) / 100;
return total + price * item.quantity;
}
return total;
}, 0);
},
// 是否有会员优惠
hasMemberDiscount() {
return this.menuList.some((item) => item.selected && item.discount);
},
// 获取会员等级名称(安全访问)
memberLevelName() {
return (
(this.userInfo && this.userInfo.level && this.userInfo.level.name) ||
"普通会员"
);
},
},
onLoad(options) {
console.log(options, 111110);
// 从路由参数获取店铺ID和分类标签
this.shopId = options.id;
this.categoryLabel = options.categoryLabel;
// 将距离从米转换为千米保留2位小数
if (options.distance) {
this.distance = (parseFloat(options.distance) / 1000).toFixed(2);
}
this.userInfo = uni.getStorageSync('userInfo') || {};
if (this.shopId) {
this.loadStoreData();
} else {
uni.showToast({
title: "店铺信息错误",
icon: "none",
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
},
onShow() {
// 页面显示时,检查登录状态并更新用户信息
// 如果之前未登录,现在已登录,重新加载数据
const token = uni.getStorageSync("token");
const newUserInfo = uni.getStorageSync("userInfo") || {};
// 如果之前没有用户信息,现在有了(说明刚登录成功),重新加载数据
if (
token &&
(!this.userInfo || !this.userInfo.id) &&
newUserInfo &&
newUserInfo.id
) {
this.userInfo = newUserInfo;
// 如果有店铺ID重新加载店铺数据特别是菜单数据可能需要登录才能查看
if (this.shopId) {
this.loadStoreData();
}
} else if (token) {
// 如果已登录,更新用户信息(可能用户信息有更新)
this.userInfo = newUserInfo;
}
},
methods: {
// 顶部店铺信息点击:进入地图页
handleGoMap() {
if (!this.shopId) return;
uni.navigateTo({
url: `/pages/detail/mapDetail?id=${this.shopId}`,
});
},
// 加载店铺数据
async loadStoreData() {
try {
// 并行加载店铺详情和菜单
await Promise.all([
this.loadStoreDetail(this.shopId),
this.loadStoreMenu(this.shopId),
]);
} catch (error) {
console.error("加载店铺数据失败:", error);
uni.showToast({
title: "加载店铺信息失败",
icon: "none",
});
}
},
// 加载店铺详情
async loadStoreDetail(shopId) {
try {
const res = await getGuildStoreDetail(shopId);
if (res) {
this.storeInfo = res;
// 如果接口返回的距离是米,转换为千米
if (this.storeInfo.distance && typeof this.storeInfo.distance === 'number') {
this.storeInfo.distance = (this.storeInfo.distance / 1000).toFixed(2);
}
}
} catch (error) {
console.error("加载店铺详情失败:", error);
}
},
// 加载工会优惠券
async loadStoreMenu(shopId) {
try {
const res = await getGuildCoupon({ shopId });
console.log(res, 11119);
if (res && res.list) {
this.menuList = res.list;
}
} catch (error) {
uni.showToast({
title: error,
icon: "none",
});
}
},
// 切换菜单项选择状态
toggleMenuItem(index) {
const item = this.menuList[index];
item.selected = !item.selected;
if (!item.selected) {
item.quantity = 0;
} else if (!item.quantity || item.quantity <= 0) {
// 选中时,如果当前数量为 0 或未设置,则默认数量为 1
item.quantity = 1;
}
},
// 增加数量
increaseQuantity(index) {
const item = this.menuList[index];
// 如果未选中,先选中
if (!item.selected) {
item.selected = true;
}
// 增加数量
item.quantity = (item.quantity || 0) + 1;
},
// 减少数量
decreaseQuantity(index) {
const item = this.menuList[index];
const currentQuantity = item.quantity || 0;
// 确保数量不能小于0
if (currentQuantity > 0) {
item.quantity = currentQuantity - 1;
// 如果数量减到0自动取消选中状态
if (item.quantity === 0) {
item.selected = false;
}
}
},
// 格式化价格将分转换为元除以100保留两位小数
formatPrice(price) {
if (!price && price !== 0) {
return "0.00";
}
// 将分转换为元
const yuan = price / 100;
// 保留两位小数
return yuan.toFixed(2);
},
// 去结算
async handleCheckout() {
if (this.totalAmount <= 0) {
return;
}
// 获取选中的商品
const selectedItems = this.menuList.filter(
(item) => item.selected && item.quantity > 0
);
if (selectedItems.length === 0) {
uni.showToast({
title: "请选择商品",
icon: "none",
});
return;
}
//
// 把选中的数据拼接成这样的数据 couponId:num;couponId:num 例如 2324:1;2325:2
const couponStr = selectedItems
.map((item) => `${item.id}:${item.quantity}`)
.join(";");
// this.totalAmount 是元, 要转换成分
const trxamt = (this.totalAmount * 100).toFixed(0);
console.log("购买信息: " + couponStr);
console.log("金额:" + trxamt);
const res = await appBuy({
shopId: this.shopId,
couponData: couponStr,
payableAmount: trxamt,
});
console.log(res);
if (!res) {
uni.showToast({
title: "购买失败,请稍后重试",
icon: "none",
});
}
// 调用支付信息 res 为订单信息
this.handlePay(res);
},
// 支付调用
async handlePay(order) {
// 测试支付进行
// const reqsn =
// "mini" + Date.now() + "" + Math.floor(Math.random() * 10000);
const couponArray = order.couponPurchaseRespVOS || [];
const couponMap = new Map();
couponArray.forEach((item) => {
const key = item.couponId;
if (!key) return;
// 从 Map 中获取已有数组,没有则取空数组
const currentList = couponMap.get(key) || [];
currentList.push(item);
// 更新 Map 中的值
couponMap.set(key, currentList);
});
let bodyStr = ''
couponMap.forEach((couponList, couponId) => {
console.log(`优惠券ID${couponId}`);
console.log(`优惠券列表:`, couponList);
const name = couponList[0] && couponList[0].couponName ? couponList[0].couponName : "商品";
bodyStr = bodyStr + name + 'x' + couponList.length + ';';
});
const randomstr = Math.floor(Math.random() * 10000000) + "";
// 金额:优先用订单实付金额(分),无则用 "1" 测试
const trxamt =
order.payableAmount != null && order.payableAmount !== ""
? String(order.payableAmount)
: "1";
// 定义请求参数(与服务记录页「立即支付」保持一致)
let params = {
appid: "00390105", // 平台分配的appid
body: bodyStr, //
cusid: order.tlPayCusid, // 平台分配的商户号
notify_url:
"https://guangsh.manage.hschengtai.com/admin-api/member/lu-order/tlNotice", // 保持原值不变
orgid: "56479107392N35H",
paytype: "W06",
randomstr: randomstr,
orderNumber: order.orderNumber,
remark: "1:" + order.orderNumber + ":" + bodyStr, // 第一个1 为租户信息, 第二个是订单号, 第三个是body
reqsn: order.orderNumber,
sign: "",
signtype: "RSA",
trxamt: "1",
version: "12",
};
console.log(params)
if (order.orderNumber) {
uni.setStorageSync("lastOrderNumber", order.orderNumber);
}
try {
const sign = await getPaySign(params);
params["sign"] = sign;
uni.navigateToMiniProgram({
appId: "wxef277996acc166c3", // 目标小程序appid
extraData: params,
success(res) {
console.log("小程序跳转成功", res);
},
fail(err) {
console.error("小程序跳转失败", err);
uni.showToast({ title: "跳转失败,请稍后重试", icon: "none" });
},
});
} catch (e) {
console.error("获取支付签名失败:", e);
uni.showToast({ title: "支付准备失败,请稍后重试", icon: "none" });
}
},
},
};
</script>
<style lang="scss" scoped>
.store-page {
min-height: 100vh;
background-color: #e2e8f1;
display: flex;
flex-direction: column;
padding-bottom: 120rpx; // 为底部结算栏留出空间
}
/* 顶部店铺信息 */
.store-header {
background-color: #ffffff;
margin: 14rpx 20rpx;
padding: 40rpx 30rpx 30rpx;
display: flex;
align-items: flex-start;
border-radius: 20rpx;
margin-bottom: 20rpx;
.store-brand {
position: relative;
width: 140rpx;
height: 140rpx;
margin-right: 24rpx;
flex-shrink: 0;
.brand-image {
width: 100%;
height: 100%;
border-radius: 12rpx;
}
}
.store-info {
flex: 1;
display: flex;
flex-direction: column;
.store-name {
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 30rpx;
color: #1a1819;
}
.store-detail {
.detail-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 22rpx;
color: #888888;
line-height: 1.5;
}
}
.store-tags-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8rpx;
.store-tags {
display: flex;
gap: 12rpx;
.tag-item {
padding: 6rpx 16rpx;
font-size: 20rpx;
&.tag-pink {
background-color: rgba(213, 28, 60, 0.1);
color: #d51c3c;
}
&.tag-orange {
background-color: rgba(255, 107, 0, 0.1);
color: #ff6b00;
}
}
}
.distance-info {
display: flex;
align-items: center;
.location-icon {
width: 17rpx;
height: 20rpx;
margin-right: 8rpx;
}
.distance-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 22rpx;
color: #004294;
}
}
}
}
}
/* 中间菜单列表 */
.menu-list {
flex: 1;
overflow-y: auto;
width: 95%;
margin: 0 auto;
min-height: 0;
.menu-item {
background-color: #ffffff;
border-radius: 20rpx;
padding: 25rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
position: relative;
.checkbox {
width: 30rpx;
height: 30rpx;
border: 1rpx solid #cccccc;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 19rpx;
flex-shrink: 0;
&.checked {
background-color: #004294;
border-color: #004294;
.checkmark {
color: #ffffff;
font-size: 28rpx;
font-weight: bold;
}
}
}
.menu-image {
width: 160rpx;
height: 173rpx;
border-radius: 10rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.menu-info {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
.menu-title-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
.menu-title {
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 28rpx;
color: #1a1819;
margin-right: 12rpx;
}
.discount-badge {
border: 1rpx solid #d51c3c;
color: #d51c3c;
font-size: 24rpx;
padding: 6rpx 14rpx;
font-weight: bold;
}
}
.menu-desc {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 22rpx;
color: #888888;
line-height: 30rpx;
}
.menu-price-row {
display: flex;
align-items: baseline;
gap: 10rpx;
margin-top: 16rpx;
.current-price {
display: flex;
align-items: baseline;
font-weight: bold;
font-size: 22rpx;
color: #d51c3c;
.price-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 47rpx;
color: #d51c3c;
}
}
.original-price {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 18rpx;
color: #545454;
text-decoration: line-through;
background-color: rgba(115, 115, 115, 0.1);
padding: 7rpx 6rpx;
}
.quantity-control {
display: flex;
align-items: center;
gap: 15rpx;
margin-left: auto;
.quantity-btn {
width: 35rpx;
height: 35rpx;
border: 1rpx solid #888888;
line-height: 35rpx;
display: flex;
align-items: center;
justify-content: center;
color: #333333;
font-weight: 500;
background-color: #ffffff;
border-radius: 4rpx;
}
.quantity-number {
font-family: PingFang-SC, PingFang-SC;
min-width: 40rpx;
text-align: center;
font-weight: 500;
font-size: 30rpx;
color: #333333;
}
}
}
}
}
}
/* 底部结算栏 */
.checkout-footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 25rpx 20rpx 25rpx 48rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1rpx solid #e2e8f1;
z-index: 100;
.total-info {
display: flex;
align-items: baseline;
.total-label {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 30rpx;
color: #d51c3c;
}
.total-amount {
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 50rpx;
color: #d51c3c;
}
.member-benefit {
display: flex;
align-items: center;
gap: 8rpx;
margin-left: 12rpx;
background: rgba(255, 107, 0, 0.1);
padding: 4rpx 12rpx;
border-radius: 8rpx;
.crown-icon {
width: 23rpx;
height: 20rpx;
}
.benefit-text {
font-family: PingFang-SC, PingFang-SC;
font-weight: 500;
font-size: 20rpx;
color: #ff6b00;
}
}
}
.checkout-btn {
color: #ffffff;
font-family: PingFang-SC, PingFang-SC;
font-weight: bold;
font-size: 28rpx;
border: none;
background: #004294;
border-radius: 35rpx;
position: absolute;
right: 20rpx;
}
}
</style>