shop-web/src/pages/product/components/RentingCabinetContainer.vue

398 lines
12 KiB
Vue
Raw Normal View History

<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>