feat(机柜): 添加机柜模式分类和商店列表优化
- 新增 MODE_MAP 常量定义机柜模式映射关系 - 重构商店列表为分类侧边栏+列表布局 - 添加一键全开功能 - 优化商店列表样式和响应式设计 - 修改 getShopListApi 接口参数为对象形式 - 添加 getModeListApi 接口获取模式列表
This commit is contained in:
parent
ec44a6b0fc
commit
b6f7824846
|
|
@ -39,6 +39,8 @@ onMounted(async () => {
|
|||
let corpid = urlParams.get('corpid') || undefined;
|
||||
const cid = urlParams.get('cid') || undefined;
|
||||
let isAdmin = urlParams.get('isAdmin') || undefined;
|
||||
// 模拟管理员
|
||||
// isAdmin = '1';
|
||||
|
||||
if (cid && Number(cid)) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -99,16 +99,24 @@ export function getBalanceByQyUserid(corpid: string, userid: string) {
|
|||
})
|
||||
}
|
||||
|
||||
export function getShopListApi(corpid: string, mode?: number) {
|
||||
const params: any = {
|
||||
corpid
|
||||
};
|
||||
if (typeof mode !== 'undefined') {
|
||||
params.mode = mode;
|
||||
}
|
||||
export interface GetShopListParams {
|
||||
corpid: string;
|
||||
mode?: number;
|
||||
eqMode?: number;
|
||||
}
|
||||
|
||||
export function getShopListApi(params: GetShopListParams) {
|
||||
return request<ApiResponseData<ShopEntity[]>>({
|
||||
url: "shop/list",
|
||||
method: "get",
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取模式列表 */
|
||||
export function getModeListApi() {
|
||||
return request<ApiResponseData<number[]>>({
|
||||
url: "shop/mode/list",
|
||||
method: "get"
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export const MODE_MAP: Record<number, string> = {
|
||||
0: '支付柜',
|
||||
1: '审批柜',
|
||||
2: '借还柜',
|
||||
3: '会员柜',
|
||||
4: '耗材柜',
|
||||
5: '暂存柜',
|
||||
}
|
||||
|
|
@ -1,20 +1,50 @@
|
|||
<template>
|
||||
<div v-if="showShopList" class="shop-list">
|
||||
<div v-if="showShopList" class="shop-list-container van-safe-area-bottom">
|
||||
<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 class="shop-content">
|
||||
<!-- 左侧分类侧边栏 -->
|
||||
<div class="shop-sidebar-container">
|
||||
<van-sidebar v-model="activeModeIndex" class="shop-sidebar" @change="onModeChange">
|
||||
<van-sidebar-item title="全部" />
|
||||
<van-sidebar-item
|
||||
v-for="mode in modeList"
|
||||
:key="mode"
|
||||
:title="MODE_MAP[mode] || `模式${mode}`"
|
||||
/>
|
||||
</van-sidebar>
|
||||
</div>
|
||||
|
||||
<!-- 右侧商店列表(单列) -->
|
||||
<div class="shop-list-wrapper">
|
||||
<div class="shop-list">
|
||||
<div class="shop-item"
|
||||
v-for="shop in shopList"
|
||||
:key="shop.shopId"
|
||||
@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 v-if="shop.mode !== undefined" class="shop-mode-tag">
|
||||
{{ getModeLabel(shop.mode) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="shopList.length === 0" class="empty-state">
|
||||
<van-empty description="暂无商店" />
|
||||
</div>
|
||||
</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">
|
||||
|
|
@ -25,6 +55,11 @@
|
|||
</div>
|
||||
|
||||
<div class="product-list">
|
||||
<div class="product-list-header">
|
||||
<van-button type="primary" icon="lock" :loading="isOpeningAll" :disabled="isOpeningAll" @click="handleOpenAllLockers">
|
||||
一键全开
|
||||
</van-button>
|
||||
</div>
|
||||
<van-cell v-for="locker in lockerList" :key="locker.lockerId" class="product-item">
|
||||
<template #icon>
|
||||
<div class="image-container">
|
||||
|
|
@ -191,16 +226,17 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { throttle } from 'lodash-es';
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import { getShopListApi } from '@/common/apis/shop';
|
||||
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import { getShopListApi, getModeListApi } from '@/common/apis/shop';
|
||||
import { ShopEntity } from '@/common/apis/shop/type';
|
||||
import { getCabinetDetailApi, openCabinet, changeGoodsCellsStock, clearGoodsCells, resetCellById } from '@/common/apis/cabinet';
|
||||
import type { CabinetDetailDTO } from '@/common/apis/cabinet/type';
|
||||
import { useWxStore, useWxStoreOutside } from '@/pinia/stores/wx';
|
||||
import { publicPath } from "@/common/utils/path";
|
||||
import { MODE_MAP } from '@/common/utils/maps/mode';
|
||||
import BindGoods from './components/BindGoods.vue';
|
||||
import VanPopup from 'vant/es/popup';
|
||||
import { showDialog, showToast } from 'vant';
|
||||
import { showConfirmDialog, showDialog, showToast } from 'vant';
|
||||
|
||||
const wxStore = useWxStore();
|
||||
const { userid: qyUserid, name: qyName } = storeToRefs(wxStore);
|
||||
|
|
@ -209,6 +245,7 @@ const activeCabinet = ref(0)
|
|||
const cabinetList = ref<CabinetItem[]>([])
|
||||
const lockerList = ref<LockerItem[]>([])
|
||||
const openingLockerId = ref<number | null>(null)
|
||||
const isOpeningAll = ref(false)
|
||||
const showBindGoodsPopup = ref(false)
|
||||
const currentLocker = ref<LockerItem | null>(null)
|
||||
const cabinetData = ref<CabinetDetailDTO[]>([]);
|
||||
|
|
@ -219,6 +256,15 @@ const shopList = ref<ShopEntity[]>([]);
|
|||
const shopId = ref<number>(0);
|
||||
const selectedShop = ref<ShopEntity | null>(null);
|
||||
const headerHeight = ref(150);
|
||||
// 分类选择相关状态
|
||||
const activeModeIndex = ref(0); // 默认选中第一个(全部)
|
||||
const modeList = ref<number[]>([]);
|
||||
const activeModeValue = computed(() => {
|
||||
// 索引 0 -> -1(全部)
|
||||
// 索引 1 -> 第一个mode值...
|
||||
if (activeModeIndex.value === 0) return -1;
|
||||
return modeList.value[activeModeIndex.value - 1] || 0;
|
||||
});
|
||||
let scrollListener: any[] = [];
|
||||
|
||||
interface CabinetItem {
|
||||
|
|
@ -302,6 +348,34 @@ function switchCellType(cellType: number | undefined) {
|
|||
}
|
||||
}
|
||||
|
||||
// 获取mode列表
|
||||
const loadModeList = async () => {
|
||||
try {
|
||||
const res = await getModeListApi();
|
||||
if (res?.code === 0 && res?.data?.length > 0) {
|
||||
modeList.value = res.data;
|
||||
} else {
|
||||
modeList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取模式列表失败:', error);
|
||||
modeList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 获取mode对应的标签
|
||||
const getModeLabel = (mode: number | undefined): string => {
|
||||
if (mode === undefined) return '';
|
||||
return MODE_MAP[mode] || '';
|
||||
};
|
||||
|
||||
// 处理分类切换
|
||||
const onModeChange = (index: number) => {
|
||||
// activeModeIndex 已通过v-model更新
|
||||
// 重新加载商店列表
|
||||
getShopList();
|
||||
};
|
||||
|
||||
const showBindGoods = (locker: LockerItem) => {
|
||||
currentLocker.value = locker;
|
||||
showBindGoodsPopup.value = true;
|
||||
|
|
@ -315,7 +389,6 @@ const handleBindSuccess = () => {
|
|||
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,
|
||||
|
|
@ -331,8 +404,49 @@ const handleOpenLocker = async (locker: LockerItem) => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleOpenAllLockers = async () => {
|
||||
if (isOpeningAll.value || lockerList.value.length === 0) return;
|
||||
|
||||
await showConfirmDialog({
|
||||
title: '确认开启',
|
||||
message: `确定要开启所有 ${lockerList.value.length} 个格口吗?`
|
||||
});
|
||||
|
||||
isOpeningAll.value = true;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < lockerList.value.length; i++) {
|
||||
const locker = lockerList.value[i];
|
||||
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(`打开格口 ${locker.cellNo} 失败:`, error);
|
||||
}
|
||||
|
||||
if (i < lockerList.value.length - 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('一键全开失败:', error);
|
||||
} finally {
|
||||
openingLockerId.value = null;
|
||||
isOpeningAll.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
await loadModeList();
|
||||
if (showShopList.value) {
|
||||
await getShopList();
|
||||
} else if (shopId.value) {
|
||||
|
|
@ -343,12 +457,17 @@ const init = async () => {
|
|||
// 获取商店列表
|
||||
const getShopList = async () => {
|
||||
try {
|
||||
const res = await getShopListApi(wxStore.corpid, -1);
|
||||
// 使用当前选中的mode作为参数,-1代表"全部"
|
||||
const modeParam = activeModeValue.value === -1 ? -1 : activeModeValue.value;
|
||||
const res = await getShopListApi({ corpid: wxStore.corpid, eqMode: modeParam });
|
||||
if (res?.code === 0 && res?.data?.length > 0) {
|
||||
shopList.value = res.data;
|
||||
} else {
|
||||
shopList.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取商店列表失败:', error);
|
||||
shopList.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -367,6 +486,8 @@ const handleShopSelect = (selectedShopId: number) => {
|
|||
const handleBackToShopList = () => {
|
||||
showShopList.value = true;
|
||||
shopId.value = 0;
|
||||
// 重置分类选择为"全部"
|
||||
activeModeIndex.value = 0;
|
||||
};
|
||||
|
||||
// 监听滚动事件调整头部高度
|
||||
|
|
@ -452,60 +573,15 @@ onBeforeUnmount(() => {
|
|||
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;
|
||||
}
|
||||
}
|
||||
/* 商店列表提示 */
|
||||
.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;
|
||||
}
|
||||
|
||||
.showShopListBtn {
|
||||
|
|
@ -611,6 +687,12 @@ onBeforeUnmount(() => {
|
|||
background: #ffffff;
|
||||
}
|
||||
|
||||
.product-list-header {
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
margin-bottom: 10px;
|
||||
padding: min(2.667vw, 20px) 0;
|
||||
|
|
@ -690,4 +772,179 @@ onBeforeUnmount(() => {
|
|||
color: #999;
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
/* 商店列表容器 */
|
||||
.shop-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - var(--van-tabbar-height));
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.shop-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 左侧分类侧边栏 */
|
||||
.shop-sidebar-container {
|
||||
width: 90px;
|
||||
background: #f7f8fa;
|
||||
border-right: 1px solid #ebedf0;
|
||||
overflow-y: auto;
|
||||
|
||||
.shop-sidebar {
|
||||
width: 100%;
|
||||
|
||||
:deep(.van-sidebar-item) {
|
||||
padding: 16px 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
|
||||
&.van-sidebar-item--select {
|
||||
background-color: #ffffff;
|
||||
color: var(--van-primary-color);
|
||||
font-weight: 500;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background-color: var(--van-primary-color);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 右侧商店列表区域 */
|
||||
.shop-list-wrapper {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.shop-list {
|
||||
.shop-item {
|
||||
margin-bottom: 12px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.shop-cover-img {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.shop-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
|
||||
.shop-icon {
|
||||
color: var(--van-primary-color);
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.shop-name {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.shop-mode-tag {
|
||||
margin-left: 8px;
|
||||
padding: 2px 8px;
|
||||
background: var(--van-primary-color);
|
||||
color: white;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
/* 移动端适配 - 针对小屏幕优化 */
|
||||
@media (max-width: 375px) {
|
||||
.shop-sidebar-container {
|
||||
width: 80px;
|
||||
|
||||
.shop-sidebar {
|
||||
:deep(.van-sidebar-item) {
|
||||
padding: 12px 6px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.shop-item {
|
||||
.shop-cover-img {
|
||||
height: 100px !important;
|
||||
}
|
||||
|
||||
.shop-info {
|
||||
padding: 12px !important;
|
||||
|
||||
.shop-name {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.shop-mode-tag {
|
||||
font-size: 11px !important;
|
||||
padding: 2px 6px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 横屏适配 */
|
||||
@media (orientation: landscape) and (max-height: 500px) {
|
||||
.shop-list-container {
|
||||
height: calc(100vh - var(--van-nav-bar-height));
|
||||
}
|
||||
|
||||
.shop-sidebar-container {
|
||||
width: 100px;
|
||||
|
||||
.shop-sidebar {
|
||||
:deep(.van-sidebar-item) {
|
||||
padding: 10px 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -69,7 +69,7 @@ const init = async () => {
|
|||
const loadShopList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getShopListApi(wxStore.corpid || 'wpZ1ZrEgAA2QTxIRcB4cMtY7hQbTcPAw', -1)
|
||||
const res = await getShopListApi({ corpid: wxStore.corpid || 'wpZ1ZrEgAA2QTxIRcB4cMtY7hQbTcPAw', mode: -1 })
|
||||
if (res?.code === 0 && res?.data?.length > 0) {
|
||||
shopList.value = res.data.filter(shop => shop.mode === 5)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ onMounted(async () => {
|
|||
if (showShopList.value) {
|
||||
// 等待 handleWxCallback 完成
|
||||
await wxStore.waitForHandleWxCallbackComplete();
|
||||
getShopListApi(wxStore.corpid).then((res) => {
|
||||
getShopListApi({ corpid: wxStore.corpid }).then((res) => {
|
||||
if (res?.code === 0 && res?.data?.length > 0) {
|
||||
shopList.value = res.data;
|
||||
if (shopIdParam && shopList.value.some(shop => shop.shopId.toString() == shopIdParam)) {
|
||||
|
|
@ -102,7 +102,7 @@ watch(() => route.path, async (newPath) => {
|
|||
if (showShopList.value) {
|
||||
// 等待 handleWxCallback 完成
|
||||
await wxStore.waitForHandleWxCallbackComplete();
|
||||
getShopListApi(wxStore.corpid).then((res) => {
|
||||
getShopListApi({ corpid: wxStore.corpid }).then((res) => {
|
||||
if (res?.code === 0 && res?.data?.length > 0) {
|
||||
shopList.value = res.data;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue