feat(rental): 新增用户租用柜机列表页面及接口
添加获取用户租用柜机列表的API接口 实现租用柜机列表页面,包含地址选择、柜机切换和格口展示功能 支持用户查看已租用柜机格口状态并操作开柜
This commit is contained in:
parent
244408c523
commit
9b3b201ead
|
@ -13,7 +13,7 @@ export function getCabinetDetailApi(shopId: number) {
|
|||
})
|
||||
}
|
||||
|
||||
/** 获取租用中的智能柜详情接口 */
|
||||
/** 获取出租中的智能柜详情接口 */
|
||||
export function getRentingCabinetDetailApi(shopId: number) {
|
||||
return request<ApiResponseData<RentingCabinetDetailDTO[]>>({
|
||||
url: 'cabinet/detail/renting',
|
||||
|
@ -24,6 +24,18 @@ export function getRentingCabinetDetailApi(shopId: number) {
|
|||
})
|
||||
}
|
||||
|
||||
/** 获取自己租用中的智能柜详情接口 */
|
||||
export function getUserRentedCabinetListApi(corpid:string, ab98UserId: number) {
|
||||
return request<ApiResponseData<RentingCabinetDetailDTO[]>>({
|
||||
url: 'cabinet/detail/user',
|
||||
method: 'get',
|
||||
params: {
|
||||
corpid,
|
||||
ab98UserId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function openCabinet(cabinetId: number, pinNo: number, data: OpenCabinetApiData) {
|
||||
return request<ApiResponseData<void>>({
|
||||
url: `cabinet/openCabinet/${cabinetId}/${pinNo}`,
|
||||
|
|
|
@ -0,0 +1,462 @@
|
|||
<template>
|
||||
<div v-if="showShopList" class="shop-list">
|
||||
<div class="shop-prompt">
|
||||
<van-cell title="请选择机柜地址:" center />
|
||||
</div>
|
||||
<van-row :gutter="[10, 10]" class="shop-row" justify="start">
|
||||
<van-col v-for="shop in shopList" :key="shop.shopId" span="12" class="shop-col">
|
||||
<div class="shop-item" @click="handleShopSelect(shop.shopId)">
|
||||
<van-image :src="shop.coverImg || `${publicPath}product-image.png`" class="shop-cover-img" fit="cover" />
|
||||
<div class="shop-info">
|
||||
<van-icon name="shop-o" size="20" class="shop-icon" />
|
||||
<div class="shop-name van-ellipsis">{{ shop.shopName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-col>
|
||||
<van-col v-if="shopList.length % 2 === 0" span="12" class="shop-col"></van-col>
|
||||
</van-row>
|
||||
</div>
|
||||
<div v-else class="cabinet-container van-safe-area-bottom">
|
||||
<div class="left-container">
|
||||
<van-button icon="revoke" type="default" class="showShopListBtn" @click="handleBackToShopList">重选地址</van-button>
|
||||
<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>
|
||||
|
||||
<div class="product-list">
|
||||
<van-cell v-for="locker in lockerList" :key="locker.lockerId" class="product-item">
|
||||
<template #icon>
|
||||
<div class="image-container">
|
||||
<van-image width="80" height="80"
|
||||
:src="locker.coverImg ? locker.coverImg : `${publicPath}` + 'img/product-image.svg'"
|
||||
fit="cover" radius="4" class="product-image"
|
||||
:style="{ filter: locker.stock === 0 ? 'grayscale(100%)' : 'none' }">
|
||||
</van-image>
|
||||
</div>
|
||||
</template>
|
||||
<div class="product-info">
|
||||
<div class="goods-info">
|
||||
<div v-if="locker.goodsName">
|
||||
<div class="info-row">
|
||||
<div class="locker-number">格口 {{ locker.cellNo }}</div>
|
||||
<div class="goods-price">¥{{ (locker.price || 0).toFixed(2) }}</div>
|
||||
</div>
|
||||
<div class="goods-name">{{ locker.goodsName }}</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="info-row">
|
||||
<div class="locker-number">格口 {{ locker.cellNo }}</div>
|
||||
<div class="goods-price">¥0.00</div>
|
||||
</div>
|
||||
<div class="goods-name">空闲</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<van-button size="small" plain hairline :loading="openingLockerId === locker.lockerId"
|
||||
@click="handleOpenLocker(locker)">
|
||||
立即开启
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-cell>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { throttle } from 'lodash-es';
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { getShopListApi } from '@/common/apis/shop';
|
||||
import { ShopEntity } from '@/common/apis/shop/type';
|
||||
import { getUserRentedCabinetListApi, openCabinet } from '@/common/apis/cabinet';
|
||||
import type { RentingCabinetDetailDTO, CabinetCellEntity } from '@/common/apis/cabinet/type';
|
||||
import { useWxStore } from '@/pinia/stores/wx';
|
||||
import { publicPath } from "@/common/utils/path";
|
||||
import { showDialog, showToast } from 'vant';
|
||||
|
||||
const wxStore = useWxStore();
|
||||
const { userid: qyUserid, name: qyName, corpid } = storeToRefs(wxStore);
|
||||
|
||||
const activeCabinet = ref(0)
|
||||
const cabinetList = ref<CabinetItem[]>([])
|
||||
const lockerList = ref<LockerItem[]>([])
|
||||
const openingLockerId = ref<number | null>(null)
|
||||
const showBindGoodsPopup = ref(false)
|
||||
const currentLocker = ref<LockerItem | null>(null)
|
||||
const cabinetData = ref<RentingCabinetDetailDTO[]>([]);
|
||||
// 机柜地址选择相关状态
|
||||
const showShopList = ref(true);
|
||||
const shopList = ref<ShopEntity[]>([]);
|
||||
const shopId = ref<number>(0);
|
||||
const selectedShop = ref<ShopEntity | null>(null);
|
||||
let scrollListener: any[] = [];
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 获取用户租用的机柜列表
|
||||
const loadUserRentedCabinetDetail = async (selectedShopId?: number) => {
|
||||
const targetShopId = selectedShopId || shopId.value;
|
||||
if (!targetShopId || !corpid.value || !qyUserid.value) return;
|
||||
try {
|
||||
const { data } = await getUserRentedCabinetListApi(corpid.value, Number(qyUserid.value));
|
||||
cabinetData.value = 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新格口列表数据
|
||||
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: `${publicPath}img/product-image.svg`
|
||||
}))
|
||||
}
|
||||
|
||||
// 监听侧边栏切换事件
|
||||
const onCabinetChange = (index: number) => {
|
||||
activeCabinet.value = index
|
||||
if (cabinetList.value.length > 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: qyUserid.value,
|
||||
isInternal: 2,
|
||||
name: qyName.value,
|
||||
mobile: '',
|
||||
operationType: 2
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('打开柜口失败:', error)
|
||||
} finally {
|
||||
openingLockerId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
if (showShopList.value) {
|
||||
await getShopList();
|
||||
} else if (shopId.value) {
|
||||
await loadUserRentedCabinetDetail();
|
||||
}
|
||||
};
|
||||
|
||||
// 获取商店列表
|
||||
const getShopList = async () => {
|
||||
try {
|
||||
const res = await getShopListApi(wxStore.corpid, -1);
|
||||
if (res?.code === 0 && res?.data?.length > 0) {
|
||||
shopList.value = res.data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取商店列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 选择商店
|
||||
const handleShopSelect = (selectedShopId: number) => {
|
||||
const shop = shopList.value.find(s => s.shopId === selectedShopId);
|
||||
if (shop) {
|
||||
selectedShop.value = shop;
|
||||
shopId.value = selectedShopId;
|
||||
}
|
||||
showShopList.value = false;
|
||||
loadUserRentedCabinetDetail(selectedShopId);
|
||||
};
|
||||
|
||||
// 返回商店列表
|
||||
const handleBackToShopList = () => {
|
||||
showShopList.value = true;
|
||||
shopId.value = 0;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
scrollListener.forEach(listener => window.removeEventListener('scroll', listener));
|
||||
scrollListener = [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stock-overlay {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cabinet-container {
|
||||
display: flex;
|
||||
height: calc(100vh - var(--van-tabbar-height));
|
||||
}
|
||||
|
||||
.left-container {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
/* 机柜选择相关样式 */
|
||||
.shop {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.shop-header {
|
||||
overflow: hidden;
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.shop-cover {
|
||||
width: 100%;
|
||||
transform: translateY(calc((150px - var(--header-height)) / 2));
|
||||
scale: calc(1 + (150 - var(--header-height)) / 150);
|
||||
}
|
||||
|
||||
.shop-list {
|
||||
.shop-prompt {
|
||||
margin: 8px;
|
||||
height: 44px !important;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.shop-row {
|
||||
margin: 8px 0;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.shop-col {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.shop-item {
|
||||
height: auto !important;
|
||||
padding: 0;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.2s;
|
||||
|
||||
.shop-cover-img {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.shop-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.shop-name {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin: 2px 0 4px 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.showShopListBtn {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.cabinet-sidebar {
|
||||
width: 90px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.locker-number {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
|
||||
.goods-name {
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.2;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 14px;
|
||||
color: #e95d5d;
|
||||
font-weight: bold;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 0;
|
||||
|
||||
.detail-btn {
|
||||
flex: 1;
|
||||
background-color: #e95d5d;
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.van-button--default) {
|
||||
color: #e95d5d;
|
||||
border-color: #e95d5d;
|
||||
}
|
||||
}
|
||||
|
||||
.product-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px 16px 60px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
margin-bottom: 10px;
|
||||
padding: min(2.667vw, 20px) 0;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.button-group .van-button {
|
||||
font-size: 12px;
|
||||
height: 24px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
:deep(.van-button) {
|
||||
--van-button-mini-height: 24px;
|
||||
--van-button-mini-padding: 0 8px;
|
||||
}
|
||||
|
||||
.locker-number {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 16px;
|
||||
color: #e95d5d;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue