shop-web/src/pages/cabinet/index.vue

260 lines
7.3 KiB
Vue
Raw Normal View History

<template>
<div class="cabinet-container van-safe-area-bottom">
<van-sidebar v-model="activeCabinet" class="cabinet-sidebar" @change="onCabinetChange">
<van-sidebar-item v-for="cabinet in cabinetList" :key="cabinet.cabinetId" :title="cabinet.cabinetName" />
</van-sidebar>
<div class="locker-grid">
<van-grid :border="false" :column-num="1">
<van-grid-item v-for="locker in lockerList" :key="locker.lockerId" class="locker-item">
<template #icon>
<div class="locker-status" :class="[locker.statusClass]">
<van-image v-if="locker.coverImg" width="100%" height="120" :src="locker.coverImg"
fit="cover" radius="4" class="locker-image" />
<div v-else class="status-overlay">
<div class="status-text">
{{ locker.statusClass === 'available' ? '空闲' : '占用' }}
</div>
</div>
</div>
</template>
<template #text>
<div class="locker-info">
<div class="locker-number">格口 {{ locker.lockerNumber }}</div>
<div v-if="locker.goodsName" class="goods-info">
<div class="goods-name van-ellipsis">{{ locker.goodsName }}</div>
<div class="goods-price">¥{{ locker.price?.toFixed(2) }}</div>
</div>
<div class="button-group">
<van-button size="small" type="primary" class="detail-btn"
@click="showLockerDetail(locker)">
详情
</van-button>
<van-button size="small" plain hairline :loading="openingLockerId === locker.lockerId"
@click="handleOpenLocker(locker)">
立即开启
</van-button>
</div>
</div>
</template>
</van-grid-item>
</van-grid>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { getCabinetDetailApi, openCabinet } from '@/common/apis/cabinet'
import type { CabinetDetailDTO } from '@/common/apis/cabinet/type'
import { useWxStoreOutside } from '@/pinia/stores/wx'
const wxStore = useWxStoreOutside()
const activeCabinet = ref(0)
const cabinetList = ref<CabinetItem[]>([])
const lockerList = ref<LockerItem[]>([])
const openingLockerId = ref<number | null>(null)
const cabinetData = ref<CabinetDetailDTO[]>([]);
interface CabinetItem {
cabinetId: number
cabinetName: string
lockControlNo: number
}
interface LockerItem {
lockerId: number
lockerNumber: number
status: 0 | 1
statusClass: 'available' | 'occupied'
goodsName?: string
price?: number
coverImg?: string
}
// 获取柜机列表
const loadCabinetDetail = async () => {
try {
const { data } = await getCabinetDetailApi();
cabinetData.value = data;
cabinetList.value = data.map(cabinet => ({
cabinetId: cabinet.cabinetId,
cabinetName: cabinet.cabinetName,
lockControlNo: cabinet.lockControlNo
}))
// 根据当前选中柜机加载格口数据
if (data.length > 0) {
updateLockerList(data[activeCabinet.value])
}
} catch (error) {
console.error('获取柜机详情失败:', error)
}
}
// 更新格口列表数据
const updateLockerList = (cabinet: CabinetDetailDTO) => {
lockerList.value = cabinet.cells.map(cell => ({
lockerId: cell.cellId,
lockerNumber: cell.pinNo,
status: cell.product ? 1 : 0,
statusClass: cell.product ? 'occupied' : 'available',
goodsName: cell.product?.goodsName,
price: cell.product?.price,
coverImg: cell.product?.coverImg
}))
}
// 监听侧边栏切换事件
const onCabinetChange = (index: number) => {
activeCabinet.value = index
if (cabinetList.value.length > index) {
updateLockerList(cabinetData.value[index]);
}
}
const showLockerDetail = (locker: LockerItem) => {
// 实现详情弹窗逻辑
}
const handleOpenLocker = async (locker: LockerItem) => {
openingLockerId.value = locker.lockerId
try {
// 调用打开柜口接口
await openCabinet(cabinetList.value[activeCabinet.value].lockControlNo, locker.lockerNumber)
} catch (error) {
console.error('打开柜口失败:', error)
} finally {
openingLockerId.value = null
}
}
loadCabinetDetail()
</script>
<style lang="scss" scoped>
.cabinet-container {
display: flex;
height: calc(100vh - var(--van-tabbar-height));
}
.cabinet-sidebar {
width: 120px;
height: 100%;
overflow-y: auto;
}
.locker-grid {
flex: 1;
width: 100%;
padding: 12px;
overflow-y: auto;
:deep(.van-grid) {
width: 100%;
max-width: 100%;
padding: 0;
.van-grid-item {
padding: 8px;
.van-grid-item__content {
padding: 0;
margin: 0;
}
}
}
}
.locker-item {
width: 100%;
margin: 8px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
overflow: hidden;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.locker-status {
position: relative;
border-radius: 4px;
overflow: hidden;
.locker-image {
display: block;
}
.status-overlay {
position: absolute;
width: 100%;
height: 50px;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
.status-text {
color: white;
font-size: 16px;
font-weight: 500;
}
}
}
.locker-info {
padding: 8px;
.locker-number {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.goods-info {
margin: 8px 0;
padding: 8px;
background: #f7f8fa;
border-radius: 4px;
.goods-name {
font-size: 12px;
color: #333;
line-height: 1.4;
}
.goods-price {
font-size: 14px;
color: #e95d5d;
font-weight: bold;
margin-top: 4px;
}
}
.button-group {
display: flex;
gap: 8px;
margin-top: 12px;
.detail-btn {
flex: 1;
background-color: #e95d5d;
border: none;
}
:deep(.van-button--default) {
color: #e95d5d;
border-color: #e95d5d;
}
}
}
}
</style>