feat(智能柜): 实现物品存取流程及密码验证功能

添加格口分配、密码验证和状态重置的API接口
重构storage-cells-summary组件,实现完整的存入取出流程
添加密码生成、验证和格口操作的用户交互
This commit is contained in:
dzq 2025-12-19 11:14:02 +08:00
parent ad13dce989
commit 8239ed657f
4 changed files with 266 additions and 13 deletions

View File

@ -1,5 +1,5 @@
import { http } from "@/http/http";
import type { AvailableStorageCellDTO, CabinetDetailDTO, RentingCabinetDetailDTO } from './types';
import type { AvailableStorageCellDTO, CabinetCellEntity, CabinetDetailDTO, RentingCabinetDetailDTO, StoreItemToCellCommand, OpenCellByPasswordCommand, ResetCellByPasswordCommand } from './types';
import type { OpenCabinetApiData } from '@/api/shop/types';
/** 获取智能柜详情接口 */
@ -37,3 +37,18 @@ export async function changeGoodsCellsStock(cellId: number, stock: number) {
export async function clearGoodsCells(cellId: number) {
return await http.put<void>(`/cabinet/clearGoodsCells/${cellId}`);
}
/** 存入物品分配格口 */
export async function storeItemApi(command: StoreItemToCellCommand) {
return await http.post<CabinetCellEntity>('cabinet/storeItem', command);
}
/** 根据密码开启格口接口 */
export async function openByPassword(command: OpenCellByPasswordCommand) {
return await http.post<void>("cabinet/openByPassword", command);
}
/** 重置格口状态接口 */
export async function resetByPassword(command: ResetCellByPasswordCommand) {
return await http.post<void>("cabinet/resetByPassword", command);
}

View File

@ -104,3 +104,27 @@ export interface AvailableStorageCellDTO {
/** 封面图URL */
coverImg: string
}
/** 存入物品分配格口请求参数 */
export interface StoreItemToCellCommand {
/** 店铺ID */
shopId: number
/** 格口类型1小格 2中格 3大格 4超大格 */
cellType: number
}
/** 根据密码开启格口请求参数 */
export interface OpenCellByPasswordCommand {
/** 店铺ID */
shopId: number;
/** 格口密码 */
password: string;
}
/** 重置格口状态请求参数 */
export interface ResetCellByPasswordCommand {
/** 店铺ID */
shopId: number;
/** 格口密码 */
password: string;
}

View File

@ -1,7 +1,8 @@
<script setup lang="ts">
import { availableStorageCells } from '@/api/cabinet/index'
import { availableStorageCells, storeItemApi, openByPassword, resetByPassword } from '@/api/cabinet/index'
import type { AvailableStorageCellDTO } from '@/api/cabinet/types'
import { ref, computed, onMounted } from 'vue'
import { useMessage } from 'wot-design-uni'
//
const CELL_TYPE_MAP = {
@ -32,6 +33,7 @@ const emit = defineEmits<{
(e: 'retrieve'): void
(e: 'refresh'): void
(e: 'error', error: Error): void
(e: 'backToAddressSelect'): void
}>()
//
@ -40,6 +42,11 @@ const error = ref<Error | null>(null)
const cellsData = ref<AvailableStorageCellDTO[]>([])
const selectedCellType = ref<number>(1)
const message = useMessage()
const depositLoading = ref(false)
const retrieveLoading = ref(false)
const generatedPassword = ref('')
//
const availableCells = computed(() =>
cellsData.value.filter(cell => !cell.hasPassword)
@ -89,11 +96,173 @@ async function refresh() {
//
function handleDeposit() {
//
emit('deposit')
//
handleDepositFlow()
}
function handleRetrieve() {
//
emit('retrieve')
//
handleRetrieveFlow()
}
function handleBackToAddressSelect() {
emit('backToAddressSelect')
}
//
async function handleDepositFlow() {
try {
// 1. storeItemApi
depositLoading.value = true
const response = await storeItemApi({
shopId: props.shopId,
cellType: selectedCellType.value
})
// password
generatedPassword.value = response.data?.password || ''
//
if (!generatedPassword.value) {
throw new Error('格口分配失败,未获取到密码')
}
// 2.
await message.alert({
title: '密码已生成',
msg: `请牢记你的暂存密码为:${generatedPassword.value}\n请确认已打开的柜子放置物品后将柜子关闭。`,
confirmButtonText: '已记住',
closeOnClickModal: false
})
// 3.
const { value: inputPassword } = await message.prompt({
title: '密码验证',
msg: '请输入刚才显示的密码进行验证',
inputPlaceholder: '请输入密码',
closeOnClickModal: false,
inputValidate: ((value: string) => {
if (!value) return '请输入密码'
if (value !== generatedPassword.value) return '密码不正确'
return true
}) as any
})
// 4.
await openByPassword({
shopId: props.shopId,
password: String(inputPassword)
})
// 5.
uni.showToast({
title: '格口已打开',
icon: 'success'
})
// 6.
await refresh()
} catch (error) {
//
// message.prompt/confirm/alert 'cancel' cancel
if (error === 'cancel' || (error as any)?.message?.includes?.('cancel')) {
return
}
// API
const errorMessage = (error as any)?.message || '操作失败'
console.error('存入流程失败:', error)
//
uni.showToast({
title: errorMessage.length > 20 ? '操作失败' : errorMessage,
icon: 'error',
duration: 3000
})
} finally {
depositLoading.value = false
}
}
//
async function handleRetrieveFlow() {
try {
// 1.
//
/* await message.confirm({
title: '物品取出',
msg: '确认要取出物品吗?',
closeOnClickModal: false
}) */
// 2.
const { value: password } = await message.prompt({
title: '输入密码',
msg: '请输入格口密码',
inputPlaceholder: '请输入密码',
closeOnClickModal: false,
inputType: ('password') as any
})
// 3.
retrieveLoading.value = true
await openByPassword({
shopId: props.shopId,
password: String(password)
})
// 4.
await message.alert({
title: '格口已打开',
msg: '柜子已开,请取物品!如不再使用柜子请点击 “清空” 。',
confirmButtonText: '清空',
closeOnClickModal: false
})
// 5.
await message.confirm({
title: '确认清空',
msg: '清空后密码将不能再次使用,确认清空?',
closeOnClickModal: false
})
// 6.
await resetByPassword({
shopId: props.shopId,
password: String(password)
})
// 7.
uni.showToast({
title: '格口已清空',
icon: 'success'
})
} catch (error) {
//
// message.prompt/confirm/alert 'cancel' cancel
if (error === 'cancel' || (error as any)?.message?.includes?.('cancel')) {
return
}
// API
const errorMessage = (error as any)?.message || '操作失败'
console.error('取出流程失败:', error)
//
uni.showToast({
title: errorMessage.length > 20 ? '操作失败' : errorMessage,
icon: 'error',
duration: 3000
})
} finally {
retrieveLoading.value = false
await refresh()
}
}
//
@ -109,9 +278,15 @@ onMounted(() => {
<!-- 标题区域 -->
<view class="summary-header">
<text class="header-title">本区域空余暂存柜子</text>
<wd-button v-if="!loading" size="small" type="primary" plain @click="refresh">
刷新
</wd-button>
<view class="header-actions">
<view class="back-btn" @click="handleBackToAddressSelect">
<wd-icon name="arrow-left" size="12px"></wd-icon>
<text style="margin-left: 4px">重选地址</text>
</view>
<wd-button v-if="!loading" size="small" type="primary" plain @click="refresh">
刷新
</wd-button>
</view>
</view>
<!-- 加载状态 -->
@ -151,13 +326,16 @@ onMounted(() => {
<view v-if="props.showButtons && hasAvailableCells && !loading" class="action-buttons">
<wd-row :gutter="16">
<wd-col :span="12">
<wd-button type="primary" block @click="handleDeposit">物品暂存</wd-button>
<wd-button type="primary" block @click="handleDeposit" :loading="depositLoading" :disabled="depositLoading || retrieveLoading">物品暂存</wd-button>
</wd-col>
<wd-col :span="12">
<wd-button type="success" block @click="handleRetrieve">物品取出</wd-button>
<wd-button type="success" block @click="handleRetrieve" :loading="retrieveLoading" :disabled="depositLoading || retrieveLoading">物品取出</wd-button>
</wd-col>
</wd-row>
</view>
<!-- MessageBox 组件 -->
<wd-message-box />
</view>
</template>
@ -179,6 +357,28 @@ onMounted(() => {
font-weight: 600;
color: #333;
}
.header-actions {
display: flex;
align-items: center;
gap: 16rpx;
.back-btn {
display: flex;
align-items: center;
padding: 8rpx 12rpx;
background: #fff;
border: 1px solid #e0e0e0;
border-radius: 16rpx;
font-size: 24rpx;
color: #666;
white-space: nowrap;
&:active {
background: #f5f5f5;
}
}
}
}
.cell-type-selection {
@ -219,5 +419,10 @@ onMounted(() => {
margin-bottom: 24rpx;
}
}
.custom-message-box {
/* 保留空白,自动换行 */
white-space: pre-wrap;
}
}
</style>

View File

@ -121,15 +121,23 @@ function handleCheckoutRenting() {
// storage-cells-summary
function handleDeposit() {
uni.navigateTo({
url: `/pages/storage/deposit?shopId=${shopId.value}`
})
//
// uni.navigateTo({
// url: `/pages/storage/deposit?shopId=${shopId.value}`
// })
//
console.log('存入操作触发')
}
function handleRetrieve() {
uni.navigateTo({
url: `/pages/storage/retrieve?shopId=${shopId.value}`
})
//
// uni.navigateTo({
// url: `/pages/storage/retrieve?shopId=${shopId.value}`
// })
//
console.log('取出操作触发')
}
function handleRefresh() {
@ -279,6 +287,7 @@ onShow(async () => {
@retrieve="handleRetrieve"
@refresh="handleRefresh"
@error="handleError"
@back-to-address-select="backToShopList"
/>
<!-- 商品列表 -->
<ProductContainer