shop-wx/src/pages/rental/index.vue

366 lines
7.6 KiB
Vue

<template>
<view class="cabinet-container">
<view class="left-container">
<scroll-view
class="cabinet-sidebar"
scroll-y
>
<view
v-for="(cabinet, index) in cabinetList"
:key="cabinet.cabinetId"
class="cabinet-sidebar-item"
:class="{ active: activeCabinet === index }"
@tap="onCabinetChange(index)"
>
{{ cabinet.cabinetName }}
</view>
</scroll-view>
</view>
<scroll-view class="product-list" scroll-y>
<view
v-for="locker in lockerList"
:key="locker.lockerId"
class="product-item"
>
<view class="product-info">
<view class="image-container">
<image
class="product-image"
:src="locker.coverImg || defaultImage"
mode="aspectFill"
:style="{ filter: locker.stock === 0 ? 'grayscale(100%)' : 'none' }"
/>
</view>
<view class="goods-info">
<view class="info-row">
<view class="locker-number">格口 {{ locker.cellNo }}</view>
<view class="goods-price">¥{{ (locker.price || 0).toFixed(2) }}</view>
</view>
<view class="goods-name">{{ locker.goodsName || '暂无商品信息' }}</view>
<view class="button-group">
<button
class="custom-btn"
:loading="openingLockerId === locker.lockerId"
@tap="handleRefund(locker.orderId, locker.orderGoodsId)"
>
退还格口
</button>
<button
class="custom-btn primary"
:loading="openingLockerId === locker.lockerId"
@tap="handleOpenLocker(locker)"
>
开启格口
</button>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { storeToRefs } from 'pinia';
import { getUserRentedCabinetListApi, openCabinet } from '@/api/cabinet';
import type { RentingCabinetDetailDTO, RetingCellEntity } from '@/api/cabinet/types';
import { useWxStore } from '@/pinia/stores/wx';
import { useAb98UserStore } from '@/pinia/stores/ab98-user';
definePage({
style: {
navigationBarTitleText: '我的柜子',
},
enablePullDownRefresh: true,
})
const wxStore = useWxStore();
const { corpid, ab98User } = storeToRefs(wxStore);
// 默认图片
const defaultImage = '/static/img/product-image.svg'
// 响应式数据
const activeCabinet = ref(0)
const cabinetList = ref<CabinetItem[]>([])
const lockerList = ref<LockerItem[]>([])
const openingLockerId = ref<number | null>(null)
const cabinetData = ref<RentingCabinetDetailDTO[]>([])
// 类型定义
interface CabinetItem {
cabinetId: number
cabinetName: string
lockControlNo: number
}
interface LockerItem {
lockerId: number
cellNo: number
lockerNumber: number
stock: number
status: 0 | 1
statusClass: 'available' | 'occupied'
goodsName?: string
price?: number
coverImg?: string
orderId: number
orderGoodsId: number
}
/**
* 获取用户租用的机柜列表
*/
const loadUserRentedCabinetDetail = async () => {
if (!ab98User?.value?.ab98UserId) {
uni.showToast({
title: '用户信息不完整',
icon: 'none'
})
return
}
try {
const res = await getUserRentedCabinetListApi(corpid.value, ab98User.value.ab98UserId);
if (res.code !== 0) {
return
}
cabinetData.value = res.data || [];
// 转换机柜列表
cabinetList.value = cabinetData.value.map(cabinet => ({
cabinetId: cabinet.cabinetId,
cabinetName: cabinet.cabinetName,
lockControlNo: cabinet.lockControlNo
}))
// 根据当前选中柜机加载格口数据
if (cabinetData.value.length > 0) {
updateLockerList(cabinetData.value[activeCabinet.value])
}
} catch (error) {
console.error('获取用户租用的柜机详情失败:', error)
uni.showToast({
title: '获取柜机详情失败',
icon: 'none'
})
}
}
/**
* 更新格口列表数据
*/
const updateLockerList = (cabinet: RentingCabinetDetailDTO) => {
lockerList.value = cabinet.cells.map(cell => ({
lockerId: cell.cellId,
cellNo: cell.cellNo,
lockerNumber: cell.pinNo,
stock: cell.stock,
status: cell.isRented ? 1 : 0,
statusClass: cell.isRented ? 'occupied' : 'available',
goodsName: '',
price: cell.cellPrice,
coverImg: defaultImage,
orderId: cell.orderId,
orderGoodsId: cell.orderGoodsId,
}))
}
/**
* 监听侧边栏切换事件
*/
const onCabinetChange = (index: number) => {
activeCabinet.value = index
if (cabinetList.value.length > index && cabinetData.value[index]) {
updateLockerList(cabinetData.value[index])
}
}
/**
* 开启格口
*/
const handleOpenLocker = async (locker: LockerItem) => {
openingLockerId.value = locker.lockerId
try {
// 调用打开柜口接口
await openCabinet(cabinetList.value[activeCabinet.value].cabinetId, locker.lockerNumber, {
cellId: locker.lockerId,
userid: wxStore.userid,
isInternal: 2,
name: wxStore.name,
mobile: '',
operationType: 2
})
uni.showToast({
title: '格口开启成功',
icon: 'success'
})
} catch (error) {
console.error('打开柜口失败:', error)
uni.showToast({
title: '打开柜口失败',
icon: 'none'
})
} finally {
openingLockerId.value = null
}
}
/**
* 退还格口
*/
const handleRefund = (orderId: number, orderGoodsId: number) => {
uni.navigateTo({
url: `/pages/approval/submit?orderGoodsId=${orderGoodsId}&orderId=${orderId}`,
fail: (err) => {
console.error('页面跳转失败:', err)
uni.showToast({
title: '页面跳转失败',
icon: 'none'
})
}
})
}
/**
* 初始化
*/
const init = async () => {
await loadUserRentedCabinetDetail()
}
onMounted(() => {
init()
})
</script>
<style lang="scss" scoped>
.image-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.cabinet-container {
display: flex;
height: 100vh;
}
.left-container {
width: 180rpx;
background-color: #f5f5f5;
}
.cabinet-sidebar {
height: 100%;
overflow-y: auto;
}
.cabinet-sidebar-item {
padding: 30rpx 20rpx;
text-align: center;
font-size: 28rpx;
color: #666;
border-bottom: 1rpx solid #e5e5e5;
&.active {
background-color: #ffffff;
color: #e95d5d;
font-weight: bold;
}
}
.product-list {
flex: 1;
overflow-y: auto;
padding: 20rpx 32rpx 120rpx;
background-color: #ffffff;
}
.product-item {
margin-bottom: 20rpx;
padding: 20rpx 0;
}
.product-info {
display: flex;
gap: 20rpx;
}
.product-image {
width: 160rpx;
height: 160rpx;
border-radius: 8rpx;
}
.goods-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.locker-number {
font-size: 28rpx;
color: #666;
}
.goods-name {
font-size: 24rpx;
color: #666;
line-height: 1.4;
}
.goods-price {
font-size: 28rpx;
color: #e95d5d;
font-weight: bold;
}
.button-group {
display: flex;
gap: 16rpx;
margin-top: 8rpx;
}
.custom-btn {
flex: 1;
height: 48rpx;
line-height: 48rpx;
font-size: 24rpx;
border: 1rpx solid #e95d5d;
color: #e95d5d;
background-color: transparent;
border-radius: 8rpx;
&::after {
border: none;
}
&.primary {
background-color: #e95d5d;
color: #ffffff;
}
&:active {
opacity: 0.8;
}
}
</style>