Compare commits
4 Commits
6e49347de2
...
4599e5c6ad
Author | SHA1 | Date |
---|---|---|
|
4599e5c6ad | |
|
e83f9750ea | |
|
c43ab6a184 | |
|
b9e0077b4d |
|
@ -1,5 +1,5 @@
|
|||
import { request } from '@/http/axios'
|
||||
import type { CabinetDetailResponse } from './type'
|
||||
import type { CabinetDetailResponse, RentingCabinetDetailDTO } from './type'
|
||||
import { OpenCabinetApiData } from '../shop/type'
|
||||
|
||||
/** 获取智能柜详情接口 */
|
||||
|
@ -13,6 +13,17 @@ export function getCabinetDetailApi(shopId: number) {
|
|||
})
|
||||
}
|
||||
|
||||
/** 获取租用中的智能柜详情接口 */
|
||||
export function getRentingCabinetDetailApi(shopId: number) {
|
||||
return request<ApiResponseData<RentingCabinetDetailDTO[]>>({
|
||||
url: 'cabinet/detail/renting',
|
||||
method: 'get',
|
||||
params: {
|
||||
shopId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function openCabinet(cabinetId: number, pinNo: number, data: OpenCabinetApiData) {
|
||||
return request<ApiResponseData<void>>({
|
||||
url: `cabinet/openCabinet/${cabinetId}/${pinNo}`,
|
||||
|
|
|
@ -5,6 +5,46 @@ export interface CabinetDetailDTO {
|
|||
cells: CellInfoDTO[]
|
||||
}
|
||||
|
||||
/** 租用中的智能柜详情DTO */
|
||||
export interface RentingCabinetDetailDTO {
|
||||
/** 柜机ID */
|
||||
cabinetId: number
|
||||
/** 柜机名称 */
|
||||
cabinetName: string
|
||||
/** 锁控编号 */
|
||||
lockControlNo: number
|
||||
/** 柜格列表 */
|
||||
cells: CabinetCellEntity[]
|
||||
}
|
||||
|
||||
/** 智能柜格口实体类 */
|
||||
export interface CabinetCellEntity {
|
||||
/** 格口唯一ID */
|
||||
cellId: number
|
||||
/** 关联柜机ID */
|
||||
cabinetId: number
|
||||
/** 主板ID */
|
||||
mainboardId?: number
|
||||
/** 格口号 */
|
||||
cellNo: number
|
||||
/** 针脚序号 */
|
||||
pinNo: number
|
||||
/** 库存数量 */
|
||||
stock: number
|
||||
/** 格口价格 */
|
||||
cellPrice?: number
|
||||
/** 是否已租用:0-未租用,1-已租用 */
|
||||
isRented: number
|
||||
/** 格口类型(1小格 2中格 3大格 4超大格) */
|
||||
cellType: number
|
||||
/** 使用状态:1空闲 2已占用 */
|
||||
usageStatus: number
|
||||
/** 可用状态:1正常 2故障 */
|
||||
availableStatus: number
|
||||
/** 商品ID */
|
||||
goodsId?: number
|
||||
}
|
||||
|
||||
export interface CellInfoDTO {
|
||||
cellId: number
|
||||
cellNo: number
|
||||
|
|
|
@ -69,11 +69,15 @@ export interface SubmitOrderRequestData {
|
|||
/** 是否内部订单 0否 1汇邦云用户 2企业微信用户 */
|
||||
isInternal: number;
|
||||
applyRemark: string;
|
||||
/** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */
|
||||
mode: number;
|
||||
/** 订单商品明细列表 */
|
||||
goodsList: Array<{
|
||||
goodsId: number
|
||||
goodsId?: number
|
||||
quantity: number
|
||||
cellId: number
|
||||
/** 运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式) */
|
||||
mode: number;
|
||||
}>
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ import { useWxStore } from "@/pinia/stores/wx"
|
|||
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()
|
||||
|
@ -20,13 +23,11 @@ const route = useRoute()
|
|||
const productStore = useProductStore();
|
||||
const cartStore = useCartStore();
|
||||
const wxStore = useWxStore();
|
||||
const { cartItems, totalPrice, totalQuantity } = storeToRefs(cartStore); // 新增购物车状态
|
||||
// 从 store 解构分类标签和商品数据
|
||||
const { labels, categories } = storeToRefs(productStore);
|
||||
const rentingCabinetStore = useRentingCabinetStoreOutside();
|
||||
|
||||
const { openid, corpidLogin, ab98User, qyUserId } = storeToRefs(wxStore);
|
||||
|
||||
// 当前选中的分类索引
|
||||
const activeCategory = ref(0)
|
||||
|
||||
// 滚动容器引用
|
||||
const scrollContainer = ref<HTMLElement>()
|
||||
// 顶部头图高度(随滚动变化)
|
||||
|
@ -40,19 +41,6 @@ const showShopList = ref(true);
|
|||
const shopList = ref<ShopEntity[]>([]);
|
||||
const shopId = ref<number>(0);
|
||||
|
||||
// 商品详情弹窗控制
|
||||
const showDetailPopup = ref(false)
|
||||
// 当前查看的商品ID
|
||||
const currentProductId = ref<number>()
|
||||
// 当前商品详情计算属性
|
||||
const currentProduct = computed(() =>
|
||||
categories.value.find(p => p.id === currentProductId.value)
|
||||
)
|
||||
// 购物车弹窗控制
|
||||
const showCartPopup = ref(false)
|
||||
|
||||
const searchQuery = ref('');
|
||||
|
||||
const name = ref('');
|
||||
const idNum = ref('');
|
||||
const showAb98BindPopup = ref(false);
|
||||
|
@ -62,79 +50,15 @@ 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();
|
||||
activeCategory.value = 0;
|
||||
}
|
||||
function handleBackToShopList() {
|
||||
showShopList.value = true;
|
||||
shopId.value = 0;
|
||||
cartStore.clearCart();
|
||||
}
|
||||
|
||||
function handleCategoryClick(index: number) {
|
||||
activeCategory.value = index
|
||||
}
|
||||
|
||||
// 节流的滚动位置计算(用于切换左侧导航激活状态)
|
||||
/* const throttledUpdate = throttle(() => {
|
||||
if (!scrollContainer.value || !categoryRefs.value.length) return
|
||||
|
||||
// 添加数组元素存在性检查
|
||||
const validRefs = categoryRefs.value.filter(Boolean)
|
||||
if (!validRefs.length) return
|
||||
|
||||
const containerTop = scrollContainer.value.getBoundingClientRect().top
|
||||
const offsets = validRefs.map((el) => {
|
||||
return el.getBoundingClientRect().top - containerTop
|
||||
})
|
||||
let activeIndex = 0
|
||||
for (let i = offsets.length - 1; i >= 0; i--) {
|
||||
if (offsets[i] < 100) {
|
||||
activeIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// activeCategory.value = activeIndex
|
||||
}, 100) */
|
||||
|
||||
// 节流的头部高度计算(实现顶部图片缩放效果)
|
||||
const throttledScroll = throttle(() => {
|
||||
if (!scrollContainer.value) return
|
||||
const scrollTop = scrollContainer.value.scrollTop
|
||||
headerHeight.value = Math.max(150 - scrollTop * 0.5, 60)
|
||||
}, 100)
|
||||
|
||||
// 打开商品详情弹窗
|
||||
function showProductDetail(productId: number) {
|
||||
currentProductId.value = productId
|
||||
showDetailPopup.value = true
|
||||
cartStore.clearCart();
|
||||
rentingCabinetStore.clearRentingCart();
|
||||
}
|
||||
|
||||
function handleAddToCart(product: Product) {
|
||||
cartStore.addToCart(product)
|
||||
}
|
||||
|
||||
function handleRemoveFromCart(product: Product) {
|
||||
cartStore.removeFromCart(product.cellId)
|
||||
}
|
||||
|
||||
function getCartItemCount(cellId: number) {
|
||||
const item = cartItems.value.find(item => item.product.cellId === cellId)
|
||||
return item ? item.quantity : 0
|
||||
}
|
||||
|
||||
function filterProductsByName(products: Product[], query: string) {
|
||||
if (!query) return products
|
||||
return products.filter(p =>
|
||||
p.name.toLowerCase().includes(query.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
const currentProducts = computed(() => {
|
||||
const filteredByCategory = categories.value.filter(c => c.label === labels.value[activeCategory.value].id)
|
||||
return filterProductsByName(filteredByCategory, searchQuery.value)
|
||||
})
|
||||
|
||||
// 组件挂载时添加滚动监听
|
||||
onMounted(() => {
|
||||
|
@ -153,7 +77,7 @@ onMounted(() => {
|
|||
} else {
|
||||
productStore.getGoods(shopId.value);
|
||||
}
|
||||
scrollListener.push(scrollContainer.value?.addEventListener("scroll", throttledScroll))
|
||||
// scrollListener.push(scrollContainer.value?.addEventListener("scroll", throttledScroll))
|
||||
// scrollListener.push(scrollContainer.value?.addEventListener("scroll", throttledUpdate))
|
||||
})
|
||||
|
||||
|
@ -241,83 +165,16 @@ async function handleAb98Bind() {
|
|||
<div class="shop-header" :style="{ height: `${headerHeight}px` }">
|
||||
<van-image :src="`${publicPath}cover.png`" class="shop-cover" fit="cover" />
|
||||
</div>
|
||||
<div class="product-container">
|
||||
<!-- 左侧分类导航 -->
|
||||
<div>
|
||||
<van-button icon="revoke" type="default" class="showShopListBtn" @click="showShopList = true">重选地址</van-button>
|
||||
<van-sidebar v-model="activeCategory" class="category-nav" @change="handleCategoryClick">
|
||||
<van-sidebar-item v-for="label in labels" :key="label.id" :title="label.name" />
|
||||
</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="product in currentProducts" :key="product.id" class="product-item">
|
||||
<template #icon>
|
||||
<van-image :src="product.image" width="80" height="80" @click.stop="showProductDetail(product.id)"
|
||||
class="product-image">
|
||||
<div v-if="product.stock === 0" class="sold-out-overlay">
|
||||
<span class="sold-out-text">已售罄</span>
|
||||
</div>
|
||||
<!-- 真正的图片错误处理 -->
|
||||
<template #error>
|
||||
<div class="custom-error">
|
||||
图片加载失败
|
||||
</div>
|
||||
<template v-if="productStore.selectedShop?.mode === 3">
|
||||
<RentingCabinetContainer :shop-id="productStore.selectedShop.shopId"
|
||||
@backToShopList="showShopList = true"
|
||||
@checkoutRenting="handleCheckout"
|
||||
/>
|
||||
</template>
|
||||
</van-image>
|
||||
<template v-else>
|
||||
<ProductContainer @backToShopList="showShopList = true" @checkout="handleCheckout" />
|
||||
</template>
|
||||
<div class="product-info">
|
||||
<div class="product-name van-ellipsis" @click.stop="showProductDetail(product.id)">
|
||||
{{ product.name }}
|
||||
</div>
|
||||
<div class="product-price" @click.stop="showProductDetail(product.id)">
|
||||
¥{{ product.price.toFixed(2) }}
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<span v-if="product.stock > 0" class="stock-count">
|
||||
还剩{{ product.stock }}份
|
||||
</span>
|
||||
<div class="cart-actions">
|
||||
<van-button v-if="getCartItemCount(product.cellId)" size="mini" icon="minus"
|
||||
@click.stop="handleRemoveFromCart(product)" />
|
||||
<span v-if="getCartItemCount(product.cellId)" class="cart-count">{{ getCartItemCount(product.cellId)
|
||||
}}</span>
|
||||
<van-button size="mini" type="primary" class="add-cart-btn" icon="plus"
|
||||
:disabled="product.stock === 0" @click.stop="handleAddToCart(product)" :style="{
|
||||
opacity: product.stock === 0 ? 0.6 : 1,
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部购物车栏 -->
|
||||
<div v-if="cartItems.length" class="shopping-cart-bar">
|
||||
<div class="cart-info">
|
||||
<van-badge :content="totalQuantity" @click.stop="showCartPopup = true">
|
||||
<van-icon name="shopping-cart-o" size="24" />
|
||||
</van-badge>
|
||||
<div class="total-price">
|
||||
合计:¥{{ totalPrice.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<van-button type="primary" size="small" @click="handleCheckout" class="checkout-btn">
|
||||
去结算
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VanPopup v-model:show="showDetailPopup" position="bottom" :style="{ height: '80%' }" round>
|
||||
<Detail class="detail-container" @detail-close="showDetailPopup = false" v-if="currentProduct"
|
||||
:product="currentProduct" />
|
||||
</VanPopup>
|
||||
<VanPopup v-model:show="showCartPopup" position="bottom" :style="{ height: '80%' }" round>
|
||||
<Cart class="detail-container" @cart-close="showCartPopup = false" />
|
||||
</VanPopup>
|
||||
<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>
|
||||
<van-form @submit="handleAb98Bind">
|
||||
|
@ -601,6 +458,7 @@ async function handleAb98Bind() {
|
|||
justify-content: start;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.shop-cover-img {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
<!-- ProductList.vue -->
|
||||
<script setup lang="ts">
|
||||
import { publicPath } from "@/common/utils/path"
|
||||
import { useCartStore } from "@/pinia/stores/cart"
|
||||
import { Product, useProductStore } from "@/pinia/stores/product"
|
||||
import { throttle } from "lodash-es"
|
||||
import { storeToRefs } from "pinia"
|
||||
import VanPopup from "vant/es/popup"
|
||||
import { computed, onMounted, onBeforeUnmount, ref, watch } from "vue"
|
||||
import Cart from "./cart.vue" // 假设 Cart 和 Detail 也在同级或components文件夹中
|
||||
import Detail from "./detail.vue" // 假设 Cart 和 Detail 也在同级或components文件夹中
|
||||
|
||||
// 定义Props
|
||||
|
||||
// 定义Emit事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'backToShopList'): void;
|
||||
(e: 'checkout'): void;
|
||||
}>();
|
||||
|
||||
// 状态管理
|
||||
const productStore = useProductStore();
|
||||
const { labels, categories } = storeToRefs(productStore);
|
||||
const cartStore = useCartStore();
|
||||
const { cartItems, totalPrice, totalQuantity } = storeToRefs(cartStore);
|
||||
|
||||
// 从props接收的数据
|
||||
const activeCategory = ref(0);
|
||||
|
||||
// 滚动容器引用
|
||||
const scrollContainer = ref<HTMLElement>();
|
||||
// 顶部头图高度(随滚动变化),这里保持在组件内部控制,或者也可以通过prop传入
|
||||
const headerHeight = ref(150);
|
||||
let scrollListener: any[] = [];
|
||||
|
||||
// 商品详情弹窗控制
|
||||
const showDetailPopup = ref(false);
|
||||
// 当前查看的商品ID
|
||||
const currentProductId = ref<number>();
|
||||
// 当前商品详情计算属性
|
||||
const currentProduct = computed(() =>
|
||||
categories.value.find(p => p.id === currentProductId.value)
|
||||
);
|
||||
// 购物车弹窗控制
|
||||
const showCartPopup = ref(false);
|
||||
|
||||
const searchQuery = ref('');
|
||||
|
||||
// 点击分类导航
|
||||
function handleCategoryClick(index: number) {
|
||||
activeCategory.value = index;
|
||||
}
|
||||
|
||||
// 节流的头部高度计算(实现顶部图片缩放效果)
|
||||
/* const throttledScroll = throttle(() => {
|
||||
if (!scrollContainer.value) return;
|
||||
const scrollTop = scrollContainer.value.scrollTop;
|
||||
headerHeight.value = Math.max(150 - scrollTop * 0.5, 60);
|
||||
}, 100); */
|
||||
|
||||
// 打开商品详情弹窗
|
||||
function showProductDetail(productId: number) {
|
||||
currentProductId.value = productId;
|
||||
showDetailPopup.value = true;
|
||||
}
|
||||
|
||||
function handleAddToCart(product: Product) {
|
||||
cartStore.addToCart(product);
|
||||
}
|
||||
|
||||
function handleRemoveFromCart(cellId: number) { // 接收 cellId
|
||||
cartStore.removeFromCart(cellId);
|
||||
}
|
||||
|
||||
function getCartItemCount(cellId: number) {
|
||||
const item = cartItems.value.find(item => item.product.cellId === cellId);
|
||||
return item ? item.quantity : 0;
|
||||
}
|
||||
|
||||
function filterProductsByName(products: Product[], query: string) {
|
||||
if (!query) return products;
|
||||
return products.filter(p =>
|
||||
p.name.toLowerCase().includes(query.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
const currentProducts = computed(() => {
|
||||
const filteredByCategory = categories.value.filter(c => c.label === labels.value[activeCategory.value]?.id);
|
||||
return filterProductsByName(filteredByCategory, searchQuery.value);
|
||||
});
|
||||
|
||||
// 组件挂载时添加滚动监听
|
||||
onMounted(() => {
|
||||
// scrollListener.push(scrollContainer.value?.addEventListener("scroll", throttledScroll));
|
||||
});
|
||||
|
||||
// 组件卸载前清理事件监听
|
||||
onBeforeUnmount(() => {
|
||||
scrollListener.forEach(listener => scrollContainer.value?.removeEventListener("scroll", listener));
|
||||
scrollListener = [];
|
||||
});
|
||||
|
||||
// 结算方法
|
||||
function handleCheckout() {
|
||||
// 这里的路由跳转可能需要父组件通过emit来处理,或者直接在子组件中导入router
|
||||
// 为了封装性,这里emit给父组件处理
|
||||
emit('checkout');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="product-container">
|
||||
<!-- 左侧分类导航 -->
|
||||
<div>
|
||||
<van-button icon="revoke" type="default" class="showShopListBtn" @click="emit('backToShopList')">重选地址</van-button>
|
||||
<van-sidebar v-model="activeCategory" class="category-nav" @change="handleCategoryClick">
|
||||
<van-sidebar-item v-for="label in labels" :key="label.id" :title="label.name" />
|
||||
</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="product in currentProducts" :key="product.id" class="product-item">
|
||||
<template #icon>
|
||||
<van-image :src="product.image" width="80" height="80" @click.stop="showProductDetail(product.id)"
|
||||
class="product-image">
|
||||
<div v-if="product.stock === 0" 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" @click.stop="showProductDetail(product.id)">
|
||||
{{ product.name }}
|
||||
</div>
|
||||
<div class="product-price" @click.stop="showProductDetail(product.id)">
|
||||
¥{{ product.price.toFixed(2) }}
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<span v-if="product.stock > 0" class="stock-count">
|
||||
还剩{{ product.stock }}份
|
||||
</span>
|
||||
<div class="cart-actions">
|
||||
<van-button v-if="getCartItemCount(product.cellId)" size="mini" icon="minus"
|
||||
@click.stop="handleRemoveFromCart(product.cellId)" />
|
||||
<span v-if="getCartItemCount(product.cellId)" class="cart-count">{{ getCartItemCount(product.cellId)
|
||||
}}</span>
|
||||
<van-button size="mini" type="primary" class="add-cart-btn" icon="plus" :disabled="product.stock === 0"
|
||||
@click.stop="handleAddToCart(product)" :style="{
|
||||
opacity: product.stock === 0 ? 0.6 : 1,
|
||||
}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部购物车栏 -->
|
||||
<div v-if="cartItems.length" class="shopping-cart-bar">
|
||||
<div class="cart-info">
|
||||
<van-badge :content="totalQuantity" @click.stop="showCartPopup = true">
|
||||
<van-icon name="shopping-cart-o" size="24" />
|
||||
</van-badge>
|
||||
<div class="total-price">
|
||||
合计:¥{{ totalPrice.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<van-button type="primary" size="small" @click="handleCheckout" class="checkout-btn">
|
||||
去结算
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
<VanPopup v-model:show="showDetailPopup" position="bottom" :style="{ height: '80%' }" round>
|
||||
<Detail class="detail-container" @detail-close="showDetailPopup = false" v-if="currentProduct"
|
||||
:product="currentProduct" />
|
||||
</VanPopup>
|
||||
<VanPopup v-model:show="showCartPopup" position="bottom" :style="{ height: '80%' }" round>
|
||||
<Cart class="detail-container" @cart-close="showCartPopup = false" />
|
||||
</VanPopup>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.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;
|
||||
/* background: linear-gradient(to right, #f7f8fa, transparent); */
|
||||
/* position: sticky; */
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
margin-bottom: 10px;
|
||||
padding: min(2.667vw, 20px) 0;
|
||||
/* border-radius: 4px; */
|
||||
/* box-shadow: 0 2px 4px rgba(0,0,0,0.05); */
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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;
|
||||
/* margin-left: 8px; */
|
||||
|
||||
.cart-count {
|
||||
font-size: 12px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -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,21 +147,39 @@ async function handleSubmit() {
|
|||
console.log('qyUserid', qyUserid.value)
|
||||
console.log("isInternal", isInternal)
|
||||
|
||||
// <<<<<< 修改:根据模式组织 goodsList
|
||||
let goodsListToSend;
|
||||
if (isRentingMode.value) {
|
||||
goodsListToSend = rentingCartItems.value.map(item => ({
|
||||
quantity: item.quantity,
|
||||
cellId: item.cabinetCell.cellId, // 租用模式下,cellId
|
||||
mode: productStore.selectedShop?.mode || 0, // 默认为 0
|
||||
// 注意:后端可能需要额外的字段来区分是商品订单还是租用订单,
|
||||
// 或者根据 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,
|
||||
mode: productStore.selectedShop?.mode || 0, // 默认为 0
|
||||
}));
|
||||
}
|
||||
|
||||
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,
|
||||
name: isInternal === 2 ? qyName.value : name.value,
|
||||
applyRemark: applyRemark.value,
|
||||
qyUserid: wxStore.userid,
|
||||
mode: productStore.selectedShop?.mode || 0, // 默认为 0
|
||||
isInternal: isInternal
|
||||
}
|
||||
|
||||
|
@ -148,29 +193,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 +216,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 +241,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 +292,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 +303,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 +324,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 +335,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