feat(租用柜子): 新增租用柜子功能模块
实现租用柜子的完整功能流程,包括: 1. 新增租用柜子容器组件和状态管理 2. 修改商品列表组件支持租用模式 3. 调整结算页面适配租用柜子订单 4. 新增租用购物车逻辑
This commit is contained in:
parent
c43ab6a184
commit
e83f9750ea
|
@ -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)!);
|
||||
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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// 获取购物车中某个格口的数量(应为0或1)
|
||||
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>
|
|
@ -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,17 +100,51 @@ 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">
|
||||
<!-- 根据 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>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
图片加载失败
|
||||
|
@ -80,24 +164,27 @@ function handleCheckout() {
|
|||
还剩{{ item.product.stock }}份
|
||||
</span>
|
||||
<div class="cart-actions">
|
||||
<van-button size="mini" icon="minus" @click.stop="handleRemoveFromCart(item.product)" />
|
||||
<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="handleAddToCart(item.product)"
|
||||
@click.stop="handleAddItem(item.product)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
<!-- 底部购物车栏 -->
|
||||
<div v-if="cartItems.length" class="shopping-cart-bar">
|
||||
</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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -37,12 +49,18 @@ const supportedPayments = computed(() => {
|
|||
|
||||
// 初始化选中的支付方式为第一个支持的选项
|
||||
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(() => {
|
||||
// 对于普通商品,判断是否需要审批 (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 (isRentingMode.value) {
|
||||
if (!rentingCartItems.value.length) {
|
||||
return showConfirmDialog({
|
||||
title: "提示",
|
||||
message: `请先选择租用商品后再结算`
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!cartItems.value.length) {
|
||||
return showConfirmDialog({
|
||||
title: "提示",
|
||||
message: "请先选择商品后再结算"
|
||||
})
|
||||
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 的性质来判断。
|
||||
// 如果后端接收的是 price,可能还需要加上:price: 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,8 +239,31 @@ 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">
|
||||
<!-- <<<<<< 修改:根据 isRentingMode 渲染不同商品列表 -->
|
||||
<template v-if="isRentingMode">
|
||||
<van-cell v-for="item in rentingCartItems" :key="item.cabinetCell.cellId" class="product-item">
|
||||
<template #icon>
|
||||
<!-- 租用机柜可以放一个默认图片或特定图标 -->
|
||||
<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.cabinetCell.cellNo}` }}
|
||||
</div>
|
||||
<div class="price-row">
|
||||
<span class="product-price">
|
||||
¥{{ item.cabinetCell.cellPrice?.toFixed(2) }}
|
||||
</span>
|
||||
<span class="quantity">
|
||||
×{{ item.quantity }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</template>
|
||||
<template v-else>
|
||||
<template v-for="item in cartItems" :key="item.product.id">
|
||||
<van-cell class="product-item">
|
||||
<template #icon>
|
||||
|
@ -226,6 +290,7 @@ async function handleSubmit() {
|
|||
</div>
|
||||
</van-cell>
|
||||
</template>
|
||||
</template>
|
||||
<van-cell>
|
||||
<van-field v-model="tel" label="手机号" placeholder="请输入联系电话" required class="tel-input" />
|
||||
</van-cell>
|
||||
|
@ -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-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;
|
||||
|
|
|
@ -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()
|
||||
}
|
Loading…
Reference in New Issue