feat(cabinet): 添加格口备注功能
- 在类型定义中为 CabinetCellEntity、CellInfoDTO 和 AvailableStorageCellDTO 添加 remark 字段 - 新增 UpdateCellRemarkDTO 类型用于更新格口备注 - 添加 updateCellRemark API 方法 - 在格口列表页添加备注显示 - 实现格口点击弹窗功能,包含备注编辑和保存 - 添加相关样式和状态管理
This commit is contained in:
parent
7eb6521dfd
commit
3d99b20754
|
|
@ -6,7 +6,8 @@ import type {
|
||||||
StoreItemToCellCommand,
|
StoreItemToCellCommand,
|
||||||
OpenCellByPasswordCommand,
|
OpenCellByPasswordCommand,
|
||||||
ResetCellByPasswordCommand,
|
ResetCellByPasswordCommand,
|
||||||
CabinetCellEntity
|
CabinetCellEntity,
|
||||||
|
UpdateCellRemarkDTO
|
||||||
} from './type'
|
} from './type'
|
||||||
import { OpenCabinetApiData } from '../shop/type'
|
import { OpenCabinetApiData } from '../shop/type'
|
||||||
|
|
||||||
|
|
@ -54,32 +55,41 @@ export function openCabinet(cabinetId: number, pinNo: number, data: OpenCabinetA
|
||||||
|
|
||||||
export const configureGoodsCellsStock = (cellId: number, goodsId: number, stock: number) => {
|
export const configureGoodsCellsStock = (cellId: number, goodsId: number, stock: number) => {
|
||||||
return request<ApiResponseData<void>>({
|
return request<ApiResponseData<void>>({
|
||||||
url: `/cabinet/configureGoodsCellsStock/${cellId}/${goodsId}/${stock}`,
|
url: `cabinet/configureGoodsCellsStock/${cellId}/${goodsId}/${stock}`,
|
||||||
method: 'put'
|
method: 'put'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const changeGoodsCellsStock = (cellId: number, stock: number) => {
|
export const changeGoodsCellsStock = (cellId: number, stock: number) => {
|
||||||
return request<ApiResponseData<void>>({
|
return request<ApiResponseData<void>>({
|
||||||
url: `/cabinet/changeGoodsCellsStock/${cellId}/${stock}`,
|
url: `cabinet/changeGoodsCellsStock/${cellId}/${stock}`,
|
||||||
method: 'put'
|
method: 'put'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clearGoodsCells = (cellId: number) => {
|
export const clearGoodsCells = (cellId: number) => {
|
||||||
return request<ApiResponseData<void>>({
|
return request<ApiResponseData<void>>({
|
||||||
url: `/cabinet/clearGoodsCells/${cellId}`,
|
url: `cabinet/clearGoodsCells/${cellId}`,
|
||||||
method: 'put'
|
method: 'put'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const resetCellById = (cellId: number) => {
|
export const resetCellById = (cellId: number) => {
|
||||||
return request<ApiResponseData<void>>({
|
return request<ApiResponseData<void>>({
|
||||||
url: `/cabinet/reset/${cellId}`,
|
url: `cabinet/reset/${cellId}`,
|
||||||
method: 'put'
|
method: 'put'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 临时:更新格口备注 */
|
||||||
|
export const updateCellRemark = (data: UpdateCellRemarkDTO) => {
|
||||||
|
return request<ApiResponseData<void>>({
|
||||||
|
url: `cabinet/cell`,
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/** 获取可用暂存柜格口列表 */
|
/** 获取可用暂存柜格口列表 */
|
||||||
export function availableStorageCells(shopId: number) {
|
export function availableStorageCells(shopId: number) {
|
||||||
return request<ApiResponseData<AvailableStorageCellDTO[]>>({
|
return request<ApiResponseData<AvailableStorageCellDTO[]>>({
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ export interface CabinetCellEntity {
|
||||||
goodsId?: number
|
goodsId?: number
|
||||||
/** 密码 */
|
/** 密码 */
|
||||||
password?: string
|
password?: string
|
||||||
|
/** 备注 */
|
||||||
|
remark?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CellInfoDTO {
|
export interface CellInfoDTO {
|
||||||
|
|
@ -75,6 +77,8 @@ export interface CellInfoDTO {
|
||||||
usageStatus: number
|
usageStatus: number
|
||||||
/** 格口类型(1小格 2中格 3大格 4超大格) */
|
/** 格口类型(1小格 2中格 3大格 4超大格) */
|
||||||
cellType: number
|
cellType: number
|
||||||
|
/** 备注 */
|
||||||
|
remark?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProductInfoDTO {
|
export interface ProductInfoDTO {
|
||||||
|
|
@ -122,6 +126,8 @@ export interface AvailableStorageCellDTO {
|
||||||
price: number
|
price: number
|
||||||
/** 封面图URL */
|
/** 封面图URL */
|
||||||
coverImg: string
|
coverImg: string
|
||||||
|
/** 备注 */
|
||||||
|
remark?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 存入物品分配格口请求参数 */
|
/** 存入物品分配格口请求参数 */
|
||||||
|
|
@ -147,3 +153,35 @@ export interface ResetCellByPasswordCommand {
|
||||||
/** 格口密码 */
|
/** 格口密码 */
|
||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 更新格口备注请求参数(cellId 必需,其他字段可选) */
|
||||||
|
export interface UpdateCellRemarkDTO {
|
||||||
|
/** 格口唯一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
|
||||||
|
/** 密码 */
|
||||||
|
password?: string
|
||||||
|
/** 备注 */
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
一键全开
|
一键全开
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
<van-cell v-for="locker in lockerList" :key="locker.lockerId" class="product-item">
|
<van-cell v-for="locker in lockerList" :key="locker.lockerId" class="product-item" @click="handleLockerClick(locker)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<van-image width="80" height="80" v-if="locker.coverImg"
|
<van-image width="80" height="80" v-if="locker.coverImg"
|
||||||
|
|
@ -183,22 +183,8 @@
|
||||||
<!-- <div class="goods-name">{{ locker.usageStatus === 1 ? '空闲' : '占用' }}</div> -->
|
<!-- <div class="goods-name">{{ locker.usageStatus === 1 ? '空闲' : '占用' }}</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div class="remark-info">
|
||||||
<van-button v-if="selectedShop?.mode !== 5" size="small" type="primary" class="detail-btn"
|
备注: {{ locker.remark || '-' }}
|
||||||
@click="showBindGoods(locker)">
|
|
||||||
绑定商品
|
|
||||||
</van-button>
|
|
||||||
<van-button v-if="selectedShop?.mode === 5 && locker.password" size="small" type="primary"
|
|
||||||
class="detail-btn" @click="handleClearPassword(locker)">
|
|
||||||
清除密码
|
|
||||||
</van-button>
|
|
||||||
<div v-else-if="selectedShop?.mode === 5 && !locker.password" class="detail-btn-placeholder">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<van-button size="small" plain hairline :loading="openingLockerId === locker.lockerId"
|
|
||||||
@click="handleOpenLocker(locker)" style="margin-left: auto;">
|
|
||||||
立即开启
|
|
||||||
</van-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</van-cell>
|
</van-cell>
|
||||||
|
|
@ -211,6 +197,48 @@
|
||||||
@cancel="showBindGoodsPopup = false" @success="handleBindSuccess" @unbind="handleBindSuccess"
|
@cancel="showBindGoodsPopup = false" @success="handleBindSuccess" @unbind="handleBindSuccess"
|
||||||
@update:currentStock="handleStockUpdate" />
|
@update:currentStock="handleStockUpdate" />
|
||||||
</VanPopup>
|
</VanPopup>
|
||||||
|
|
||||||
|
<!-- 格口控制弹窗 -->
|
||||||
|
<VanPopup v-model:show="showLockerPopup" position="bottom" :style="{ height: '45%' }" round>
|
||||||
|
<div class="locker-popup-content" v-if="selectedLocker">
|
||||||
|
<div class="popup-header">
|
||||||
|
<span class="popup-title">格口 {{ selectedLocker.cellNo }}</span>
|
||||||
|
<van-icon name="cross" @click="showLockerPopup = false" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="locker-info">
|
||||||
|
<div class="info-row">
|
||||||
|
<span>状态: {{ selectedLocker.usageStatus === 1 ? '空闲' : '占用' }}</span>
|
||||||
|
<span v-if="selectedLocker.goodsName">商品: {{ selectedLocker.goodsName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row" v-if="selectedShop?.mode !== 5">
|
||||||
|
<span>价格: ¥{{ (selectedLocker.price || 0).toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-field v-model="editingRemark" label="备注" placeholder="请输入备注" rows="2" type="textarea"
|
||||||
|
class="remark-field" />
|
||||||
|
|
||||||
|
<div class="popup-actions">
|
||||||
|
<van-button type="primary" :loading="openingLockerId === selectedLocker.lockerId"
|
||||||
|
@click="handleOpenLocker(selectedLocker)" style="flex: 1;">
|
||||||
|
立即开启
|
||||||
|
</van-button>
|
||||||
|
<van-button v-if="selectedShop?.mode === 5 && selectedLocker.password" plain
|
||||||
|
@click="handleClearPassword(selectedLocker)" style="flex: 1;">
|
||||||
|
清除密码
|
||||||
|
</van-button>
|
||||||
|
<van-button v-else-if="selectedShop?.mode !== 5" plain
|
||||||
|
@click="handleBindFromLocker(selectedLocker)" style="flex: 1;">
|
||||||
|
绑定商品
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<van-button @click="handleSaveRemark" :loading="isSavingRemark" style="margin-top: 8px;">
|
||||||
|
保存备注
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</VanPopup>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -219,7 +247,7 @@ import { throttle } from 'lodash-es';
|
||||||
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue';
|
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||||
import { getShopListApi, getModeListApi } from '@/common/apis/shop';
|
import { getShopListApi, getModeListApi } from '@/common/apis/shop';
|
||||||
import { ShopEntity } from '@/common/apis/shop/type';
|
import { ShopEntity } from '@/common/apis/shop/type';
|
||||||
import { getCabinetDetailApi, openCabinet, changeGoodsCellsStock, clearGoodsCells, resetCellById } from '@/common/apis/cabinet';
|
import { getCabinetDetailApi, openCabinet, changeGoodsCellsStock, clearGoodsCells, resetCellById, updateCellRemark } from '@/common/apis/cabinet';
|
||||||
import type { CabinetDetailDTO } from '@/common/apis/cabinet/type';
|
import type { CabinetDetailDTO } from '@/common/apis/cabinet/type';
|
||||||
import { useWxStore, useWxStoreOutside } from '@/pinia/stores/wx';
|
import { useWxStore, useWxStoreOutside } from '@/pinia/stores/wx';
|
||||||
import { useRouterStore } from '@/pinia/stores/router';
|
import { useRouterStore } from '@/pinia/stores/router';
|
||||||
|
|
@ -249,6 +277,11 @@ const shopList = ref<ShopEntity[]>([]);
|
||||||
const shopId = ref<number>(0);
|
const shopId = ref<number>(0);
|
||||||
const selectedShop = ref<ShopEntity | null>(null);
|
const selectedShop = ref<ShopEntity | null>(null);
|
||||||
const headerHeight = ref(150);
|
const headerHeight = ref(150);
|
||||||
|
// 弹窗控制相关状态
|
||||||
|
const showLockerPopup = ref(false)
|
||||||
|
const selectedLocker = ref<LockerItem | null>(null)
|
||||||
|
const editingRemark = ref('')
|
||||||
|
const isSavingRemark = ref(false)
|
||||||
// 分类选择相关状态
|
// 分类选择相关状态
|
||||||
const activeModeIndex = ref(0); // 默认选中第一个(全部)
|
const activeModeIndex = ref(0); // 默认选中第一个(全部)
|
||||||
const modeList = ref<number[]>([]);
|
const modeList = ref<number[]>([]);
|
||||||
|
|
@ -279,6 +312,7 @@ interface LockerItem {
|
||||||
password?: string
|
password?: string
|
||||||
usageStatus?: number
|
usageStatus?: number
|
||||||
cellType?: number
|
cellType?: number
|
||||||
|
remark?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取机柜列表
|
// 获取机柜列表
|
||||||
|
|
@ -320,6 +354,7 @@ const updateLockerList = (cabinet: CabinetDetailDTO) => {
|
||||||
password: cell.password,
|
password: cell.password,
|
||||||
usageStatus: cell.usageStatus,
|
usageStatus: cell.usageStatus,
|
||||||
cellType: cell.cellType,
|
cellType: cell.cellType,
|
||||||
|
remark: cell.remark,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -509,6 +544,42 @@ const handleClearPassword = async (locker: LockerItem) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBindFromLocker = (locker: LockerItem) => {
|
||||||
|
currentLocker.value = locker;
|
||||||
|
showLockerPopup.value = false;
|
||||||
|
showBindGoodsPopup.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点击格口打开弹窗
|
||||||
|
const handleLockerClick = (locker: LockerItem) => {
|
||||||
|
selectedLocker.value = locker
|
||||||
|
editingRemark.value = locker.remark || ''
|
||||||
|
showLockerPopup.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存备注
|
||||||
|
const handleSaveRemark = async () => {
|
||||||
|
if (!selectedLocker.value) return
|
||||||
|
isSavingRemark.value = true
|
||||||
|
try {
|
||||||
|
await updateCellRemark({
|
||||||
|
cellId: selectedLocker.value.lockerId,
|
||||||
|
remark: editingRemark.value
|
||||||
|
})
|
||||||
|
showToast('保存成功')
|
||||||
|
// 更新本地数据
|
||||||
|
if (selectedLocker.value) {
|
||||||
|
selectedLocker.value.remark = editingRemark.value
|
||||||
|
}
|
||||||
|
loadCabinetDetail()
|
||||||
|
showLockerPopup.value = false
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存备注失败:', error)
|
||||||
|
} finally {
|
||||||
|
isSavingRemark.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
init();
|
init();
|
||||||
scrollListener.push(window.addEventListener('scroll', throttledScroll));
|
scrollListener.push(window.addEventListener('scroll', throttledScroll));
|
||||||
|
|
@ -656,6 +727,13 @@ onBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remark-info {
|
||||||
|
text-align: left;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
@ -944,4 +1022,53 @@ onBeforeUnmount(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 格口控制弹窗样式 */
|
||||||
|
.locker-popup-content {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #ebedf0;
|
||||||
|
|
||||||
|
.popup-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
color: #999;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.locker-info {
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-field {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Loading…
Reference in New Issue