feat(租用柜子): 新增租用柜子功能模块

实现租用柜子的完整功能流程,包括:
1. 新增租用柜子容器组件和状态管理
2. 修改商品列表组件支持租用模式
3. 调整结算页面适配租用柜子订单
4. 新增租用购物车逻辑
This commit is contained in:
dzq 2025-06-27 15:41:40 +08:00
parent c43ab6a184
commit e83f9750ea
5 changed files with 749 additions and 111 deletions

View File

@ -14,6 +14,8 @@ import { bindQyUserApi } from "@/common/apis/ab98"
import { getShopListApi } from "@/common/apis/shop"
import { ShopEntity } from "@/common/apis/shop/type"
import ProductContainer from "./components/ProductContainer.vue"
import RentingCabinetContainer from "./components/RentingCabinetContainer.vue"
import { useRentingCabinetStoreOutside } from "@/pinia/stores/rentingCabinet"
const router = useRouter()
const route = useRoute()
@ -21,6 +23,7 @@ const route = useRoute()
const productStore = useProductStore();
const cartStore = useCartStore();
const wxStore = useWxStore();
const rentingCabinetStore = useRentingCabinetStoreOutside();
const { openid, corpidLogin, ab98User, qyUserId } = storeToRefs(wxStore);
@ -47,8 +50,13 @@ function handleShopSelect(selectedShopId: number) {
shopId.value = selectedShopId;
showShopList.value = false;
productStore.setSelectedShop(shopList.value.find(shop => shop.shopId === selectedShopId)!);
productStore.getGoods(selectedShopId);
if(productStore.selectedShop?.mode == 3) {
rentingCabinetStore.fetchRentingCabinetDetail(selectedShopId);
} else {
productStore.getGoods(selectedShopId);
}
cartStore.clearCart();
rentingCabinetStore.clearRentingCart();
}
@ -157,10 +165,15 @@ async function handleAb98Bind() {
<div class="shop-header" :style="{ height: `${headerHeight}px` }">
<van-image :src="`${publicPath}cover.png`" class="shop-cover" fit="cover" />
</div>
<ProductContainer
<template v-if="productStore.selectedShop?.mode === 3">
<RentingCabinetContainer :shop-id="productStore.selectedShop.shopId"
@backToShopList="showShopList = true"
@checkout="handleCheckout"
/>
@checkoutRenting="handleCheckout"
/>
</template>
<template v-else>
<ProductContainer @backToShopList="showShopList = true" @checkout="handleCheckout" />
</template>
</div>
<VanPopup v-model:show="showAb98BindPopup" position="center" :style="{ padding: '12px' }" round>
<div style="text-align: center; font-size: 16px; font-weight: bold; margin-bottom: 16px;">请绑定汇邦云账号</div>

View File

@ -0,0 +1,397 @@
<script setup lang="ts">
import { useRentingCabinetStore } from "@/pinia/stores/rentingCabinet"
import { storeToRefs } from "pinia"
import { computed, onMounted, ref } from "vue"
import { publicPath } from "@/common/utils/path";
import VanPopup from "vant/es/popup"
import Cart from "./cart.vue" // Cart Detail components
// RentingDetail RentingCart components
//
// import RentingDetail from "./RentingDetail.vue" //
// import RentingCart from "./RentingCart.vue" //
// Props
const props = defineProps<{
shopId: number; // ID
}>();
// Emit
const emit = defineEmits<{
(e: 'backToShopList'): void;
(e: 'checkoutRenting'): void; //
}>();
//
const rentingCabinetStore = useRentingCabinetStore();
const { rentingCabinets, rentingCartItems, rentingCartTotalQuantity } = storeToRefs(rentingCabinetStore);
(window as any).rentingCartItems = rentingCartItems;
// props
const activeCategory = ref(0); // cabinetName
//
const scrollContainer = ref<HTMLElement>();
//
const showCartPopup = ref(false); //
const searchQuery = ref('');
// cabinetName
function handleCategoryClick(index: number) {
activeCategory.value = index;
}
// URL
const PLACEHOLDER_IMAGE_URL = `${publicPath}` + 'img/product-image.svg'; // URL
//
function handleAddToCart(cabinetCell: any) { // CabinetCellEntity
// 1
const existingItem = rentingCartItems.value.find(item => item.cabinetCell.cellId === cabinetCell.cellId);
if (!existingItem) {
rentingCabinetStore.addToRentingCart(cabinetCell, 1);
}
//
}
//
function handleRemoveFromCart(cellId: number) {
rentingCabinetStore.removeFromRentingCart(cellId, 1); // 1
}
// 01
function getRentingCartItemCount(cellId: number) {
const item = rentingCartItems.value.find(item => item.cabinetCell.cellId === cellId);
return item ? item.quantity : 0;
}
//
const filteredRentingCells = computed(() => {
let cells: any[] = []; // CabinetCellEntity[]
//
if (rentingCabinets.value.length > 0) {
// Flattern all cells from all cabinets
cells = rentingCabinets.value.flatMap(cabinet => cabinet.cells);
}
// (isRented = 1) (usageStatus = 2)
const availableCells = cells.filter(cell => cell.isRented === 0 && cell.usageStatus === 1);
//
if (!searchQuery.value) {
return availableCells;
}
// (cellNo)
return availableCells.filter(cell =>
String(cell.cellNo).includes(searchQuery.value) ||
(cell.cabinetName && cell.cabinetName.toLowerCase().includes(searchQuery.value.toLowerCase())) // cell cabinetName
);
});
//
onMounted(() => {
rentingCabinetStore.fetchRentingCabinetDetail(props.shopId);
});
//
function handleCheckout() {
emit('checkoutRenting');
}
</script>
<template>
<div class="product-container">
<!-- 左侧分类导航 (如果需要根据柜机名称进行分类可以改造) -->
<div>
<van-button icon="revoke" type="default" class="showShopListBtn" @click="emit('backToShopList')">重选地址</van-button>
<!-- 简化分类如果不需要按cabinetName分类可以移除van-sidebar -->
<van-sidebar v-model="activeCategory" class="category-nav" @change="handleCategoryClick">
<van-sidebar-item v-for="(cabinet, index) in rentingCabinets" :key="cabinet.cabinetId" :title="cabinet.cabinetName" />
</van-sidebar>
<!-- 暂时保持一个简单的所有可租用格口的逻辑你可以根据实际需求调整分类 -->
<!-- <van-sidebar v-model="activeCategory" class="category-nav">
<van-sidebar-item title="可租用格口" />
</van-sidebar> -->
</div>
<!-- 右侧可租用格口列表 -->
<div ref="scrollContainer" class="product-list">
<van-search v-model="searchQuery" placeholder="搜索格口号或柜机名称" shape="round" class="search-box" />
<div class="category-section">
<van-cell v-for="cell in filteredRentingCells" :key="cell.cellId" class="product-item">
<template #icon>
<van-image :src="PLACEHOLDER_IMAGE_URL" width="80" height="80" class="product-image">
<!-- 已被占用或故障的格口显示 '不可租用' -->
<div v-if="cell.isRented === 1 || cell.usageStatus === 2 || cell.availableStatus === 2" class="sold-out-overlay">
<span class="sold-out-text">不可租用</span>
</div>
<template #error>
<div class="custom-error">
图片加载失败
</div>
</template>
</van-image>
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
格口号: {{ cell.cellNo }}
</div>
<div class="product-price">
<!-- 如果格口有价格属性可以用 cell.cellPrice -->
<!-- 例如租金¥{{ (cell.cellPrice || 0).toFixed(2) }}/ -->
¥{{ (cell.cellPrice || 0).toFixed(2) }}
</div>
<div class="action-row">
<!-- 这里的库存概念是格口是否可用数量只有1 -->
<span v-if="cell.isRented === 0 && cell.usageStatus === 1 && cell.availableStatus === 1" class="stock-count">
可租用
</span>
<div class="cart-actions">
<!-- 数量减按钮如果已在购物车则显示 -->
<van-button v-if="getRentingCartItemCount(cell.cellId) > 0" size="mini" icon="minus"
@click.stop="handleRemoveFromCart(cell.cellId)" />
<!-- 数量显示0或1 -->
<span v-if="getRentingCartItemCount(cell.cellId) > 0" class="cart-count">{{ getRentingCartItemCount(cell.cellId) }}</span>
<!-- 数量加按钮如果未在购物车且可租用则显示 -->
<van-button size="mini" type="primary" class="add-cart-btn" icon="plus"
:disabled="getRentingCartItemCount(cell.cellId) > 0 || cell.isRented === 1 || cell.usageStatus === 2 || cell.availableStatus === 2"
@click.stop="handleAddToCart(cell)"
:style="{ opacity: (getRentingCartItemCount(cell.cellId) > 0 || cell.isRented === 1 || cell.usageStatus === 2 || cell.availableStatus === 2) ? 0.6 : 1 }"
/>
</div>
</div>
</div>
</van-cell>
</div>
</div>
<!-- 底部购物车栏 -->
<div v-if="rentingCartItems.length" class="shopping-cart-bar">
<div class="cart-info">
<van-badge :content="rentingCartTotalQuantity" @click.stop="showCartPopup = true">
<van-icon name="shopping-cart-o" size="24" />
</van-badge>
<div class="total-price">
已选格口数{{ rentingCartTotalQuantity }}
</div>
</div>
<van-button type="primary" size="small" @click="handleCheckout" class="checkout-btn">
去结算
</van-button>
</div>
</div>
<VanPopup v-model:show="showCartPopup" position="bottom" :style="{ height: '80%' }" round>
<Cart class="detail-container" @cart-close="showCartPopup = false" />
</VanPopup>
<!-- 购物车弹窗如果需要 -->
<!-- <VanPopup v-model:show="showCartPopup" position="bottom" :style="{ height: '80%' }" round>
<RentingCart class="detail-container" @cart-close="showCartPopup = false" />
</VanPopup> -->
</template>
<style scoped lang="scss">
/* 导入或复制 ProductContainer.vue 的样式 */
/* 注意:这里可以直接复制 ProductContainer.vue 中的 <style scoped lang="scss"> 的内容 */
/* 或者将公共样式抽离成CSS文件导入 */
.product-container {
display: flex;
height: 100vh;
background: #f7f8fa;
flex: 1;
min-height: 0;
position: relative;
border-top: 1px solid #e0e0e0;
/* 顶部边框 */
border-top-left-radius: 8px;
/* 左上圆角 */
border-top-right-radius: 8px;
/* 右上圆角 */
overflow: hidden;
/* 确保圆角生效 */
}
.category-nav {
width: 100px;
flex-shrink: 0;
}
.showShopListBtn {
width: 100%;
padding: 0;
border: 0;
}
.product-list {
flex: 1;
overflow-y: auto;
padding: 10px 16px 60px;
background: #ffffff;
}
.category-section {
margin-bottom: 20px;
}
.category-title {
margin: 5px;
padding: 0;
font-size: 16px;
color: #333;
font-weight: bold;
top: 0;
z-index: 1;
}
.product-item {
margin-bottom: 10px;
padding: min(2.667vw, 20px) 0;
}
.product-image {
margin-right: 12px;
border-radius: 4px;
overflow: hidden;
}
.product-info {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 80px;
position: relative;
}
.product-name {
font-size: 14px;
color: #333;
line-height: 1.4;
text-align: left;
}
.product-price {
font-size: 16px;
color: #e95d5d;
font-weight: bold;
text-align: left;
}
.add-cart-btn {
align-self: flex-end;
margin-top: 0;
}
.van-sidebar-item--select:before {
background-color: #e95d5d;
}
.van-button--primary {
background-color: #e95d5d;
border: 0;
border-color: #e95d5d;
}
.sold-out-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 2; /* 确保在图片上层 */
}
.sold-out-text {
color: #999;
font-size: 14px;
transform: rotate(-15deg);
border: 1px solid #eee;
padding: 2px 8px;
border-radius: 4px;
}
.action-row {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: auto;
}
.stock-count {
font-size: 11px;
color: #bbbbbb;
margin-right: 8px;
display: flex;
align-items: flex-end;
height: 100%;
line-height: 1;
}
/* 禁用状态样式 */
.van-button--disabled {
background-color: #ccc !important;
border-color: #ccc !important;
}
/* 商品项置灰 */
.product-item:has(.sold-out-overlay) {
opacity: 0.6;
}
.detail-container {
height: 100%;
}
/* 修改购物车栏样式 */
.shopping-cart-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
z-index: 100;
}
.cart-info {
display: flex;
align-items: center;
gap: 12px;
}
.total-price {
font-size: 14px;
color: #333;
font-weight: bold;
}
.checkout-btn {
background-color: #e95d5d;
border: none;
border-radius: 16px;
padding: 0 24px;
}
.cart-actions {
margin-left: auto;
align-self: flex-end;
display: flex;
align-items: center;
gap: 4px;
.cart-count {
font-size: 12px;
min-width: 20px;
text-align: center;
}
}
</style>

View File

@ -1,45 +1,95 @@
<script setup lang="ts">
import { useCartStore } from "@/pinia/stores/cart"
import { Product } from "@/pinia/stores/product"
import { storeToRefs } from "pinia"
import { showConfirmDialog } from "vant"
import { useRouter } from "vue-router"
//
import { useCartStore } from "@/pinia/stores/cart";
import { Product } from "@/pinia/stores/product";
import { storeToRefs } from "pinia";
import { showConfirmDialog } from "vant";
import { useRouter } from "vue-router";
import { useProductStore } from "@/pinia/stores/product"; // product store
import { useRentingCabinetStore } from "@/pinia/stores/rentingCabinet"; // rentingCabinet store
import type { CabinetCellEntity } from "@/common/apis/cabinet/type"; //
import { publicPath } from "@/common/utils/path";
//
const emit = defineEmits(["cartClose"])
const router = useRouter()
const emit = defineEmits(["cartClose"]);
const router = useRouter();
//
const cartStore = useCartStore()
const { cartItems, totalPrice, totalQuantity } = storeToRefs(cartStore)//
const cartStore = useCartStore();
const { cartItems, totalPrice, totalQuantity } = storeToRefs(cartStore);
// productStore selectedShop
const productStore = useProductStore();
const { selectedShop } = storeToRefs(productStore);
// rentingCabinetStore
const rentingCabinetStore = useRentingCabinetStore();
const { rentingCartItems, rentingCartTotalQuantity } = storeToRefs(rentingCabinetStore);
// (mode 3)
const isRentingMode = computed(() => selectedShop.value?.mode === 3);
//
const currentItems = computed(() => {
return isRentingMode.value ? rentingCartItems.value : cartItems.value;
});
//
const currentTotalQuantity = computed(() => {
return isRentingMode.value ? rentingCartTotalQuantity.value : totalQuantity.value;
});
//
function handleClose() {
emit("cartClose")
}
function handleAddToCart(product: Product) {
cartStore.addToCart(product)
emit("cartClose");
}
function handleRemoveFromCart(product: Product) {
cartStore.removeFromCart(product.cellId)
//
function handleAddItem(item: Product | { cabinetCell: CabinetCellEntity }) {
if (isRentingMode.value) {
if ('cabinetCell' in item) {
rentingCabinetStore.addToRentingCart(item.cabinetCell);
}
} else {
if ('id' in item) { // Product
cartStore.addToCart(item);
}
}
}
function handleClearCart() {
//
function handleRemoveItem(item: Product | { cabinetCell: CabinetCellEntity }) {
if (isRentingMode.value) {
if ('cabinetCell' in item) {
rentingCabinetStore.removeFromRentingCart(item.cabinetCell.cellId);
}
} else {
if ('id' in item) { // Product
cartStore.removeFromCart(item.cellId);
}
}
}
//
function handleClearCartOrRentingCart() {
showConfirmDialog({
title: "确认清空购物车吗?"
title: `确认清空${isRentingMode.value ? '租用' : ''}购物车吗?`
})
.then(() => {
cartStore.clearCart()
if (isRentingMode.value) {
rentingCabinetStore.clearRentingCart();
} else {
cartStore.clearCart();
}
})
.catch(() => {
// on cancel
})
});
}
//
function handleCheckout() {
handleClose()
router.push("/product/checkout")
handleClose();
router.push("/product/checkout");
}
</script>
@ -50,54 +100,91 @@ function handleCheckout() {
<div class="clear-header">
<van-button
size="small" type="primary" class="clear-btn" plain
@click="handleClearCart"
@click="handleClearCartOrRentingCart"
>
清空购物车
</van-button>
</div>
</div>
<div class="product-list">
<van-cell v-for="item in cartItems" :key="item.product.id" class="product-item">
<template #icon>
<van-image :src="item.product.image" width="80" height="80" class="product-image">
<!-- 真正的图片错误处理 -->
<template #error>
<div class="custom-error">
图片加载失败
<!-- 根据 isRentingMode 动态渲染列表 -->
<template v-if="isRentingMode">
<van-cell v-for="item in rentingCartItems" :key="item.cabinetCell.cellId" class="product-item">
<template #icon>
<!-- 租用柜子通常没有图片或者默认图片 -->
<van-image :src="`${publicPath}` + 'img/product-image.svg'" width="80" height="80" class="product-image">
<template #error>
<div class="custom-error">
图片加载失败
</div>
</template>
</van-image>
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
<!-- 租用柜子的名称可以根据 cellNo 或其他属性生成 -->
{{ `格口号 ${item.cabinetCell.cellNo}` }}
</div>
<div class="product-price">
¥{{ item.cabinetCell.cellPrice?.toFixed(2) }}
</div>
<div class="action-row">
<span class="stock-count">
<!-- 租用柜子可能没有库存概念或者显示可用数量 -->
<!-- 如果需要展示库存item.cabinetCell 需包含 stock 字段 -->
</span>
<div class="cart-actions">
<van-button size="mini" icon="minus" @click.stop="handleRemoveItem(item)" />
<span class="cart-count">{{ item.quantity }}</span>
</div>
</template>
</van-image>
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.product.name }}
</div>
<div class="product-price">
¥{{ item.product.price.toFixed(2) }}
</div>
<div class="action-row">
<span v-if="item.product.stock > 0" class="stock-count">
还剩{{ item.product.stock }}
</span>
<div class="cart-actions">
<van-button size="mini" icon="minus" @click.stop="handleRemoveFromCart(item.product)" />
<span class="cart-count">{{ item.quantity }}</span>
<van-button
size="mini" type="primary" class="add-cart-btn" icon="plus"
@click.stop="handleAddToCart(item.product)"
/>
</div>
</div>
</div>
</van-cell>
<!-- 底部购物车栏 -->
<div v-if="cartItems.length" class="shopping-cart-bar">
</van-cell>
</template>
<template v-else>
<van-cell v-for="item in cartItems" :key="item.product.id" class="product-item">
<template #icon>
<van-image :src="item.product.image" width="80" height="80" class="product-image">
<template #error>
<div class="custom-error">
图片加载失败
</div>
</template>
</van-image>
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.product.name }}
</div>
<div class="product-price">
¥{{ item.product.price.toFixed(2) }}
</div>
<div class="action-row">
<span v-if="item.product.stock > 0" class="stock-count">
还剩{{ item.product.stock }}
</span>
<div class="cart-actions">
<van-button size="mini" icon="minus" @click.stop="handleRemoveItem(item.product)" />
<span class="cart-count">{{ item.quantity }}</span>
<van-button
size="mini" type="primary" class="add-cart-btn" icon="plus"
@click.stop="handleAddItem(item.product)"
/>
</div>
</div>
</div>
</van-cell>
</template>
<!-- 底部购物车栏根据 currentItems 的长度判断是否显示 (通用逻辑) -->
<div v-if="currentItems.length" class="shopping-cart-bar">
<div class="cart-info">
<van-badge :content="totalQuantity" @click.stop="handleClose()">
<van-badge :content="currentTotalQuantity" @click.stop="handleClose()">
<van-icon name="shopping-cart-o" size="24" />
</van-badge>
<div class="total-price">
合计¥{{ totalPrice.toFixed(2) }}
<!-- 总价可能在租用模式下不适用或显示其他信息, 这里简化处理 -->
合计¥{{ isRentingMode ? rentingCabinetStore.rentingCartTotalPrice : totalPrice.toFixed(2) }}
</div>
</div>
<van-button type="primary" size="small" @click="handleCheckout" class="checkout-btn">
@ -109,6 +196,7 @@ function handleCheckout() {
</template>
<style scoped>
/* 保持所有样式不变因为DOM结构尽量保持一致 */
.product-list {
flex: 1;
overflow-y: auto;

View File

@ -8,6 +8,7 @@ import { submitOrderApi } from "@/common/apis/shop";
import type { SubmitOrderRequestData, WxJsApiPreCreateResponse } from "@/common/apis/shop/type";
import { useRouter } from 'vue-router';
import { useProductStore } from "@/pinia/stores/product";
import { useRentingCabinetStore } from "@/pinia/stores/rentingCabinet"; // <<<<<< rentingCabinet store
//
import { paymentMethodOptions, modeToPaymentMethodMap } from "@/common/utils/maps/payment";
@ -16,6 +17,10 @@ const router = useRouter();
const cartStore = useCartStore();
const { cartItems, totalPrice } = storeToRefs(cartStore);
// <<<<<< rentingCabinet store
const rentingCabinetStore = useRentingCabinetStore();
const { rentingCartItems, rentingCartTotalPrice } = storeToRefs(rentingCabinetStore);
const wxStore = useWxStore();
const { openid, balance, corpidLogin, userid: qyUserid, name: qyName } = storeToRefs(wxStore);
@ -24,6 +29,13 @@ const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore);
const productStore = useProductStore();
// <<<<<< (mode 3)
const isRentingMode = computed(() => productStore.selectedShop?.mode === 3);
const currentTotalPrice = computed(() => {
return isRentingMode.value ? rentingCartTotalPrice.value : totalPrice.value;
});
//
const supportedPayments = computed(() => {
const shopMode = productStore.selectedShop?.mode || 0;
@ -32,17 +44,23 @@ const supportedPayments = computed(() => {
// 1.
// 2. (0)
return paymentMethodOptions.filter(option => allowedValues.includes(option.value)
&& (option.value !== 0 || !corpidLogin.value));
&& (option.value !== 0 || !corpidLogin.value));
});
//
const selectedPayment = ref<string>(supportedPayments.value[0]?.value.toString() || '0');
const contact = ref("");
const applyRemark = ref("");
const submitting = ref(false);
// <<<<<< isApproval currentItems
const isApproval = computed(() => {
return cartItems.value.some(item => item.product.belongType == 1);
// (belongType == 1)
//
if (isRentingMode.value) {
return false; //
} else {
return cartItems.value.some(item => item.product.belongType == 1);
}
});
// value
@ -87,11 +105,20 @@ function callWxJsApi(paymentInfo: WxJsApiPreCreateResponse) {
}
async function handleSubmit() {
if (!cartItems.value.length) {
return showConfirmDialog({
title: "提示",
message: "请先选择商品后再结算"
})
if (isRentingMode.value) {
if (!rentingCartItems.value.length) {
return showConfirmDialog({
title: "提示",
message: `请先选择租用商品后再结算`
});
}
} else {
if (!cartItems.value.length) {
return showConfirmDialog({
title: "提示",
message: `请先选择商品后再结算`
});
}
}
if (!openid.value) {
@ -120,15 +147,31 @@ async function handleSubmit() {
console.log('qyUserid', qyUserid.value)
console.log("isInternal", isInternal)
// <<<<<< goodsList
let goodsListToSend;
if (isRentingMode.value) {
goodsListToSend = rentingCartItems.value.map(item => ({
goodsId: item.cabinetCell.cellId, // goodsId cellId
quantity: item.quantity,
cellId: item.cabinetCell.cellId, // cellId
//
// goodsId/cellId
// priceprice: item.cabinetCell.cellPrice,
// price goodsId
}));
} else {
goodsListToSend = cartItems.value.map(item => ({
goodsId: item.product.id,
quantity: item.quantity,
cellId: item.product.cellId,
}));
}
const requestData: SubmitOrderRequestData = {
openid: openid.value,
userid: wxStore.userid,
corpid: wxStore.corpid,
goodsList: cartItems.value.map(item => ({
goodsId: item.product.id,
quantity: item.quantity,
cellId: item.product.cellId,
})),
goodsList: goodsListToSend, // <<<<<< 使 goodsListToSend
// value
paymentType: paymentValueToType[selectedPayment.value] || 'wechat',
mobile: tel.value,
@ -148,29 +191,21 @@ async function handleSubmit() {
if (data.paymentInfo) {
await callWxJsApi(data.paymentInfo);
}
} else if (selectedPayment.value == '1') { //
} else if (selectedPayment.value == '1' || selectedPayment.value == '3') { // ()
wxStore.setBalance(data.newBalance || 0);
try {
await showConfirmDialog({
title: "支付成功",
message: `借呗支付成功,剩余余额:¥${data.newBalance?.toFixed(2)}`
message: `${selectedPayment.value === '1' ? '借呗' : '余额'}支付成功,剩余余额:¥${data.newBalance?.toFixed(2)}`
})
} catch (error) {}
} else if (selectedPayment.value == '2') { //
} catch (error) { }
} else if (selectedPayment.value == '2') { // ()
try {
await showConfirmDialog({
title: "提交领用申请成功",
message: `请等待管理员审批`
})
} catch (error) {}
} else if (selectedPayment.value == '3') { //
wxStore.setBalance(data.newBalance || 0);
try {
await showConfirmDialog({
title: "支付成功",
message: `余额支付成功,剩余余额:¥${data.newBalance?.toFixed(2)}`
})
} catch (error) {}
} catch (error) { }
} else { //
//
}
@ -179,7 +214,13 @@ async function handleSubmit() {
path: '/order-success',
query: { orderId: data.orderId }
});
cartStore.clearCart()
// <<<<<<
if (isRentingMode.value) {
rentingCabinetStore.clearRentingCart();
} else {
cartStore.clearCart();
}
} catch (error) {
if (error !== 'user_cancel') {
showConfirmDialog({
@ -198,21 +239,22 @@ async function handleSubmit() {
<van-nav-bar title="结算页面" left-text="返回" left-arrow fixed @click-left="() => $router.go(-1)" />
<div class="content-wrapper">
<!-- 原有商品列表等代码保持不动 -->
<!-- 根据 isRentingMode 动态渲染列表 -->
<van-cell-group class="product-list">
<template v-for="item in cartItems" :key="item.product.id">
<van-cell class="product-item">
<!-- <<<<<< 修改根据 isRentingMode 渲染不同商品列表 -->
<template v-if="isRentingMode">
<van-cell v-for="item in rentingCartItems" :key="item.cabinetCell.cellId" class="product-item">
<template #icon>
<van-image :src="item.product.image" width="60" height="60" class="product-image" />
<!-- 租用机柜可以放一个默认图片或特定图标 -->
<van-image src="/img/product-image.svg" width="60" height="60" class="product-image" />
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.product.name }}
{{ `格口号 ${item.cabinetCell.cellNo}` }}
</div>
<div class="price-row">
<span class="product-price">
¥{{ item.product.price.toFixed(2) }}
¥{{ item.cabinetCell.cellPrice?.toFixed(2) }}
</span>
<span class="quantity">
×{{ item.quantity }}
@ -220,11 +262,34 @@ async function handleSubmit() {
</div>
</div>
</van-cell>
<van-cell v-if="item.product.usageInstruction" class="usage-instruction">
<div class="instruction-content">
使用说明{{ item.product.usageInstruction }}
</div>
</van-cell>
</template>
<template v-else>
<template v-for="item in cartItems" :key="item.product.id">
<van-cell class="product-item">
<template #icon>
<van-image :src="item.product.image" width="60" height="60" class="product-image" />
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.product.name }}
</div>
<div class="price-row">
<span class="product-price">
¥{{ item.product.price.toFixed(2) }}
</span>
<span class="quantity">
×{{ item.quantity }}
</span>
</div>
</div>
</van-cell>
<van-cell v-if="item.product.usageInstruction" class="usage-instruction">
<div class="instruction-content">
使用说明{{ item.product.usageInstruction }}
</div>
</van-cell>
</template>
</template>
<van-cell>
<van-field v-model="tel" label="手机号" placeholder="请输入联系电话" required class="tel-input" />
@ -236,19 +301,17 @@ async function handleSubmit() {
</van-cell-group> -->
<!-- 支付方式 -->
<!-- <<<<<< 修改isApproval -->
<van-cell-group v-if="!isApproval" class="contact-form">
<van-cell
v-for="method in supportedPayments"
:key="method.value"
:class="['payment-option', { selected: selectedPayment === method.value.toString(), disabled: method.value === 1 && (balance < totalPrice) }]"
@click="selectedPayment = method.value.toString()"
>
<van-icon
<van-cell v-for="method in supportedPayments" :key="method.value"
:class="['payment-option', { selected: selectedPayment === method.value.toString(), disabled: method.value === 1 && (balance < currentTotalPrice) }]"
@click="selectedPayment = method.value.toString()">
<van-icon
:name="method.value === 0 ? 'wechat' : method.value === 1 ? 'balance-o' : method.value === 2 ? 'credit-card' : 'wallet'"
class="method-icon"
/>
class="method-icon" />
<span class="method-label">
{{ method.label }}
<!-- <<<<<< 修改balance < currentTotalPrice -->
<span v-if="method.value === 1" class="balance-amount">当前¥{{ balance.toFixed(2) }}</span>
</span>
<div class="empty"></div>
@ -259,7 +322,7 @@ async function handleSubmit() {
<!-- 提交订单栏 -->
<div class="submit-bar">
<div class="total-price">
合计¥{{ totalPrice.toFixed(2) }}
合计¥{{ currentTotalPrice.toFixed(2) }} <!-- <<<<<< 修改使用 currentTotalPrice -->
</div>
<van-button type="primary" size="large" :loading="submitting" loading-text="提交中..." @click="handleSubmit">
提交订单
@ -270,6 +333,7 @@ async function handleSubmit() {
</template>
<style lang="scss" scoped>
// cart.vue
.van-nav-bar {
position: fixed;
top: 0;

View File

@ -0,0 +1,76 @@
import { defineStore } from 'pinia'
import { computed } from 'vue'
import { ref } from 'vue'
import { getRentingCabinetDetailApi } from '@/common/apis/cabinet/index'
import type { CabinetCellEntity, RentingCabinetDetailDTO } from '@/common/apis/cabinet/type'
export const useRentingCabinetStore = defineStore('rentingCabinet', () => {
const rentingCabinets = ref<RentingCabinetDetailDTO[]>([])
// 租用购物车列表
const rentingCartItems = ref<Array<{ cabinetCell: CabinetCellEntity; quantity: number }>>([])
const fetchRentingCabinetDetail = async (shopId: number) => {
const res = await getRentingCabinetDetailApi(shopId);
console.log(res)
if (res.code === 0 && res.data) {
rentingCabinets.value = res.data
}
}
// 添加到租用购物车
const addToRentingCart = (cabinetCell: CabinetCellEntity, quantity: number = 1): boolean => {
if (quantity <= 0) return false
const existingItem = rentingCartItems.value.find(item => item.cabinetCell.cellId === cabinetCell.cellId)
if (existingItem) {
existingItem.quantity += quantity
} else {
rentingCartItems.value.push({ cabinetCell, quantity })
}
return true
}
// 从购物车移除
const removeFromRentingCart = (cellId: number, quantity: number = 1) => {
const index = rentingCartItems.value.findIndex(item => item.cabinetCell.cellId === cellId)
if (index !== -1) {
if (rentingCartItems.value[index].quantity <= quantity) {
rentingCartItems.value.splice(index, 1)
} else {
rentingCartItems.value[index].quantity -= quantity
}
}
}
// 计算租用总数
const rentingCartTotalQuantity = computed(() => {
return rentingCartItems.value.reduce((total, item) => total + item.quantity, 0)
})
// 计算租用总价
const rentingCartTotalPrice = computed(() => {
return rentingCartItems.value.reduce((sum, item) => {
return sum + (item.cabinetCell.cellPrice || 0) * item.quantity
}, 0)
})
// 清空租用购物车
const clearRentingCart = () => {
rentingCartItems.value = []
}
return {
rentingCabinets,
rentingCartItems,
addToRentingCart,
removeFromRentingCart,
rentingCartTotalQuantity,
rentingCartTotalPrice,
clearRentingCart,
fetchRentingCabinetDetail
}
})
export function useRentingCabinetStoreOutside() {
return useRentingCabinetStore()
}