feat(智能柜): 实现物品存取流程及密码验证功能
添加格口分配、密码验证和状态重置的API接口 重构storage-cells-summary组件,实现完整的存入取出流程 添加密码生成、验证和格口操作的用户交互
This commit is contained in:
parent
ad13dce989
commit
8239ed657f
|
|
@ -1,5 +1,5 @@
|
||||||
import { http } from "@/http/http";
|
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';
|
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) {
|
export async function clearGoodsCells(cellId: number) {
|
||||||
return await http.put<void>(`/cabinet/clearGoodsCells/${cellId}`);
|
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);
|
||||||
|
}
|
||||||
|
|
@ -104,3 +104,27 @@ export interface AvailableStorageCellDTO {
|
||||||
/** 封面图URL */
|
/** 封面图URL */
|
||||||
coverImg: string
|
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;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<script setup lang="ts">
|
<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 type { AvailableStorageCellDTO } from '@/api/cabinet/types'
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useMessage } from 'wot-design-uni'
|
||||||
|
|
||||||
// 格口类型映射
|
// 格口类型映射
|
||||||
const CELL_TYPE_MAP = {
|
const CELL_TYPE_MAP = {
|
||||||
|
|
@ -32,6 +33,7 @@ const emit = defineEmits<{
|
||||||
(e: 'retrieve'): void
|
(e: 'retrieve'): void
|
||||||
(e: 'refresh'): void
|
(e: 'refresh'): void
|
||||||
(e: 'error', error: Error): void
|
(e: 'error', error: Error): void
|
||||||
|
(e: 'backToAddressSelect'): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 状态变量
|
// 状态变量
|
||||||
|
|
@ -40,6 +42,11 @@ const error = ref<Error | null>(null)
|
||||||
const cellsData = ref<AvailableStorageCellDTO[]>([])
|
const cellsData = ref<AvailableStorageCellDTO[]>([])
|
||||||
const selectedCellType = ref<number>(1)
|
const selectedCellType = ref<number>(1)
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const depositLoading = ref(false)
|
||||||
|
const retrieveLoading = ref(false)
|
||||||
|
const generatedPassword = ref('')
|
||||||
|
|
||||||
// 统计计算属性
|
// 统计计算属性
|
||||||
const availableCells = computed(() =>
|
const availableCells = computed(() =>
|
||||||
cellsData.value.filter(cell => !cell.hasPassword)
|
cellsData.value.filter(cell => !cell.hasPassword)
|
||||||
|
|
@ -89,11 +96,173 @@ async function refresh() {
|
||||||
|
|
||||||
// 事件处理函数
|
// 事件处理函数
|
||||||
function handleDeposit() {
|
function handleDeposit() {
|
||||||
|
// 保持事件发射以向后兼容
|
||||||
emit('deposit')
|
emit('deposit')
|
||||||
|
// 调用新的存入流程函数
|
||||||
|
handleDepositFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRetrieve() {
|
function handleRetrieve() {
|
||||||
|
// 保持事件发射以向后兼容
|
||||||
emit('retrieve')
|
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,10 +278,16 @@ onMounted(() => {
|
||||||
<!-- 标题区域 -->
|
<!-- 标题区域 -->
|
||||||
<view class="summary-header">
|
<view class="summary-header">
|
||||||
<text class="header-title">本区域空余暂存柜子</text>
|
<text class="header-title">本区域空余暂存柜子</text>
|
||||||
|
<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 v-if="!loading" size="small" type="primary" plain @click="refresh">
|
||||||
刷新
|
刷新
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
<!-- 加载状态 -->
|
||||||
<view v-if="loading" class="loading-state">
|
<view v-if="loading" class="loading-state">
|
||||||
|
|
@ -151,13 +326,16 @@ onMounted(() => {
|
||||||
<view v-if="props.showButtons && hasAvailableCells && !loading" class="action-buttons">
|
<view v-if="props.showButtons && hasAvailableCells && !loading" class="action-buttons">
|
||||||
<wd-row :gutter="16">
|
<wd-row :gutter="16">
|
||||||
<wd-col :span="12">
|
<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>
|
||||||
<wd-col :span="12">
|
<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-col>
|
||||||
</wd-row>
|
</wd-row>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- MessageBox 组件 -->
|
||||||
|
<wd-message-box />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -179,6 +357,28 @@ onMounted(() => {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
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 {
|
.cell-type-selection {
|
||||||
|
|
@ -219,5 +419,10 @@ onMounted(() => {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.custom-message-box {
|
||||||
|
/* 保留空白,自动换行 */
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -121,15 +121,23 @@ function handleCheckoutRenting() {
|
||||||
|
|
||||||
// storage-cells-summary 事件处理方法
|
// storage-cells-summary 事件处理方法
|
||||||
function handleDeposit() {
|
function handleDeposit() {
|
||||||
uni.navigateTo({
|
// 原有的跳转逻辑已删除
|
||||||
url: `/pages/storage/deposit?shopId=${shopId.value}`
|
// uni.navigateTo({
|
||||||
})
|
// url: `/pages/storage/deposit?shopId=${shopId.value}`
|
||||||
|
// })
|
||||||
|
|
||||||
|
// 可以保留简单的事件处理
|
||||||
|
console.log('存入操作触发')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRetrieve() {
|
function handleRetrieve() {
|
||||||
uni.navigateTo({
|
// 原有的跳转逻辑已删除
|
||||||
url: `/pages/storage/retrieve?shopId=${shopId.value}`
|
// uni.navigateTo({
|
||||||
})
|
// url: `/pages/storage/retrieve?shopId=${shopId.value}`
|
||||||
|
// })
|
||||||
|
|
||||||
|
// 可以保留简单的事件处理
|
||||||
|
console.log('取出操作触发')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRefresh() {
|
function handleRefresh() {
|
||||||
|
|
@ -279,6 +287,7 @@ onShow(async () => {
|
||||||
@retrieve="handleRetrieve"
|
@retrieve="handleRetrieve"
|
||||||
@refresh="handleRefresh"
|
@refresh="handleRefresh"
|
||||||
@error="handleError"
|
@error="handleError"
|
||||||
|
@back-to-address-select="backToShopList"
|
||||||
/>
|
/>
|
||||||
<!-- 商品列表 -->
|
<!-- 商品列表 -->
|
||||||
<ProductContainer
|
<ProductContainer
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue