diff --git a/doc/plans/lively-crafting-taco.md b/doc/plans/lively-crafting-taco.md
new file mode 100644
index 0000000..496f585
--- /dev/null
+++ b/doc/plans/lively-crafting-taco.md
@@ -0,0 +1,416 @@
+# 将 storage-cells-summary 组件的 message 弹窗改为 wd-popup 底部弹出
+
+## 当前状态分析
+
+### 组件位置
+`src/components/storage-cells-summary/index.vue`
+
+### 当前弹窗使用情况
+组件使用 `useMessage()` 调用 `wot-design-uni` 的 `MessageBox` 组件进行弹窗交互:
+
+1. **存入流程** (`handleDepositFlow`):
+ - `message.alert()`: 显示生成的密码(第135-140行)
+ - `message.prompt()`: 密码验证输入(第143-153行)
+
+2. **取出流程** (`handleRetrieveFlow`):
+ - `message.prompt()`: 输入取出密码(第204-210行)
+
+### 现有 wd-popup 使用模式
+从代码库中找到的示例:
+- `src/components/position-edit/index.vue`: 底部弹出 + 安全区配置
+- `src/pages/index/components/product-container.vue`: 简单底部弹出
+
+**关键配置**:
+```vue
+
+
+
+```
+
+## 需求总结
+
+1. 将 `message.alert` 和 `message.prompt` 调用改为自定义 `wd-popup` 组件
+2. 使用底部弹出位置 (`position="bottom"`)
+3. 启用底部安全区 (`safe-area-inset-bottom="true"`)
+4. 自定义弹窗内容展示
+5. 保持现有业务逻辑不变
+
+## 设计方案
+
+### 方案一:创建独立弹窗子组件
+**优点**: 代码结构清晰,易于维护
+**缺点**: 需要创建新文件,增加组件复杂度
+
+### 方案二:在当前组件内实现多个弹窗状态
+**优点**: 简单直接,无需新增文件
+**缺点**: 组件内部状态增多,模板复杂度增加
+
+**推荐方案二**,因为弹窗逻辑相对简单,且与当前组件紧密耦合。
+
+## 状态机设计
+
+根据用户要求采用状态机模式管理弹窗流程。设计以下状态:
+
+### 状态定义
+```typescript
+type PopupState =
+ | { type: 'idle' } // 空闲状态
+ | { type: 'password-show', password: string } // 显示密码
+ | { type: 'password-verify', correctPassword: string } // 密码验证输入
+ | { type: 'retrieve-input' } // 取出密码输入
+ | { type: 'processing', action: 'deposit' | 'retrieve' } // 处理中
+```
+
+### 状态转换
+1. **存入流程**:
+ - `idle` → `password-show` (显示生成的密码)
+ - `password-show` → `password-verify` (用户点击"已记住")
+ - `password-verify` → `processing` (验证通过,打开格口)
+ - `processing` → `idle` (完成)
+
+2. **取出流程**:
+ - `idle` → `retrieve-input` (输入取出密码)
+ - `retrieve-input` → `processing` (输入完成,打开格口)
+ - `processing` → `idle` (完成)
+
+### 状态管理实现
+使用 `reactive` 或 `ref` 管理当前状态,通过状态转换函数实现流程控制。
+
+## 详细实现计划
+
+### 1. 移除 useMessage 导入和声明
+- 删除第5行: `import { useMessage } from 'wot-design-uni'`
+- 删除第45行: `const message = useMessage()`
+- 注意:其他API导入保持不变
+- wd-password-input 组件通常已全局注册,无需额外导入
+
+### 2. 实现状态机
+```typescript
+// 状态定义
+type PopupState =
+ | { type: 'idle' }
+ | { type: 'password-show', password: string }
+ | { type: 'password-verify', correctPassword: string }
+ | { type: 'retrieve-input' }
+ | { type: 'processing', action: 'deposit' | 'retrieve' }
+
+// 当前状态
+const currentState = ref({ type: 'idle' })
+
+// 弹窗输入值
+const popupInputValue = ref('')
+const passwordLength = 4
+
+// 状态转换函数
+function transitionTo(state: PopupState) {
+ currentState.value = state
+ // 重置输入值当进入需要输入的弹窗
+ if (state.type === 'password-verify' || state.type === 'retrieve-input') {
+ popupInputValue.value = ''
+ }
+}
+
+// 根据状态计算弹窗显示属性
+const popupVisible = computed(() => currentState.value.type !== 'idle')
+
+const popupTitle = computed(() => {
+ const state = currentState.value
+ switch (state.type) {
+ case 'password-show': return '密码已生成'
+ case 'password-verify': return '密码验证'
+ case 'retrieve-input': return '输入密码'
+ default: return ''
+ }
+})
+
+const popupMessage = computed(() => {
+ const state = currentState.value
+ switch (state.type) {
+ case 'password-show': return `请牢记你的暂存密码为:${state.password}\n请确认已打开的柜子,放置物品后将柜子关闭。`
+ case 'password-verify': return '请输入刚才显示的密码进行验证'
+ case 'retrieve-input': return '请输入格口密码'
+ default: return ''
+ }
+})
+
+const popupConfirmText = computed(() => {
+ const state = currentState.value
+ switch (state.type) {
+ case 'password-show': return '已记住'
+ case 'password-verify': return '验证'
+ case 'retrieve-input': return '确认'
+ default: return '确认'
+ }
+})
+
+const showCancelButton = computed(() => {
+ const state = currentState.value
+ return state.type === 'password-verify' || state.type === 'retrieve-input'
+})
+```
+
+### 3. 重构业务流程
+```typescript
+// 存入流程(状态机驱动)
+async function handleDepositFlow() {
+ try {
+ depositLoading.value = true
+
+ // 1. 分配格口
+ const response = await storeItemApi({
+ shopId: props.shopId,
+ cellType: selectedCellType.value
+ })
+
+ const password = response.data?.password || ''
+ if (!password) {
+ throw new Error('格口分配失败,未获取到密码')
+ }
+
+ // 2. 显示密码(状态转换)
+ transitionTo({ type: 'password-show', password })
+
+ // 注意:原流程在这里等待用户点击"已记住"
+ // 现在改为在弹窗确认事件中处理
+
+ } catch (error) {
+ // 错误处理
+ console.error('存入流程失败:', error)
+ uni.showToast({
+ title: (error as any)?.message || '操作失败',
+ icon: 'error',
+ duration: 3000
+ })
+ transitionTo({ type: 'idle' })
+ } finally {
+ depositLoading.value = false
+ }
+}
+
+// 取出流程(状态机驱动)
+async function handleRetrieveFlow() {
+ // 直接进入密码输入状态
+ transitionTo({ type: 'retrieve-input' })
+}
+
+// 处理弹窗确认事件
+async function handlePopupConfirm() {
+ const state = currentState.value
+
+ switch (state.type) {
+ case 'password-show':
+ // 点击"已记住",进入密码验证
+ transitionTo({ type: 'password-verify', correctPassword: state.password })
+ break
+
+ case 'password-verify':
+ // 验证密码
+ if (popupInputValue.value !== state.correctPassword) {
+ uni.showToast({ title: '密码不正确', icon: 'error' })
+ return
+ }
+ // 打开格口
+ await performOpenByPassword(popupInputValue.value, 'deposit')
+ break
+
+ case 'retrieve-input':
+ // 打开格口
+ await performOpenByPassword(popupInputValue.value, 'retrieve')
+ break
+ }
+}
+
+// 处理弹窗取消/关闭事件
+function handlePopupCancel() {
+ transitionTo({ type: 'idle' })
+}
+
+// 统一的打开格口函数
+async function performOpenByPassword(password: string, action: 'deposit' | 'retrieve') {
+ transitionTo({ type: 'processing', action })
+
+ try {
+ await openByPassword({
+ shopId: props.shopId,
+ password: String(password)
+ })
+
+ uni.showToast({
+ title: '格口已打开',
+ icon: 'success'
+ })
+
+ // 刷新数据
+ await refresh()
+ } catch (error) {
+ console.error('打开格口失败:', error)
+ uni.showToast({
+ title: (error as any)?.message || '操作失败',
+ icon: 'error',
+ duration: 3000
+ })
+ } finally {
+ transitionTo({ type: 'idle' })
+ }
+}
+```
+
+### 4. 实现 wd-popup 模板
+```vue
+
+
+
+
+```
+
+### 5. 更新样式
+在现有 SCSS 中添加弹窗相关样式:
+```scss
+.popup-content {
+ display: flex;
+ flex-direction: column;
+ gap: 32rpx;
+
+ .popup-title {
+ font-size: 36rpx;
+ font-weight: 600;
+ color: #333;
+ text-align: center;
+ }
+
+ .popup-message {
+ font-size: 28rpx;
+ color: #666;
+ line-height: 1.5;
+ white-space: pre-line;
+ text-align: center;
+ }
+
+ .popup-password-input {
+ margin: 32rpx 0;
+ }
+
+ .processing-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24rpx;
+ padding: 48rpx 0;
+
+ .processing-text {
+ font-size: 28rpx;
+ color: #666;
+ }
+ }
+
+ .popup-actions {
+ display: flex;
+ gap: 16rpx;
+ margin-top: 32rpx;
+ }
+}
+```
+
+## 实施步骤
+
+1. **准备阶段**
+ - 备份当前文件
+ - 分析现有弹窗调用的所有参数和返回值
+
+2. **状态变量添加**
+ - 在 script setup 中添加弹窗状态变量
+ - 创建弹窗控制函数
+
+3. **模板修改**
+ - 添加 wd-popup 组件模板
+ - 设计弹窗内容布局
+ - 添加输入框和按钮
+
+4. **业务流程重构**
+ - 替换 `message.alert` 调用
+ - 替换 `message.prompt` 调用
+ - 更新异步流程处理
+
+5. **样式适配**
+ - 添加弹窗内容样式
+ - 调整响应式布局
+
+6. **测试验证**
+ - 测试存入流程弹窗
+ - 测试取出流程弹窗
+ - 验证底部安全区效果
+
+## 注意事项
+
+1. **向后兼容**: 保持现有的 `emit('deposit')` 等事件发射
+2. **错误处理**: 保留现有的错误处理逻辑
+3. **用户体验**: 保持相似的交互流程和提示信息
+4. **性能考虑**: 使用 `lazy-render`(默认启用)避免不必要的渲染
+
+## 风险与缓解
+
+1. **流程中断**: 仔细测试每个弹窗转换点,确保业务流程完整
+2. **样式不一致**: 参考现有弹窗样式,保持视觉统一
+3. **输入验证**: 确保密码验证逻辑正确移植
+
+## 预期结果
+
+- 所有弹窗改为底部弹出的 `wd-popup`
+- 启用底部安全区适配
+- 业务功能完全正常
+- 用户体验保持一致
\ No newline at end of file
diff --git a/doc/暂存柜功能文档.md b/doc/暂存柜功能文档.md
new file mode 100644
index 0000000..ced1e8c
--- /dev/null
+++ b/doc/暂存柜功能文档.md
@@ -0,0 +1,374 @@
+# 暂存柜功能文档
+
+## 概述
+
+`storage-cells-summary` 组件是智能柜管理系统中用于暂存柜(临时存储)功能的核心组件。该组件提供以下核心功能:
+
+1. **可用格口展示** - 显示当前店铺/区域的可用暂存柜格口,按格口类型(小格、中格、大格、超大格)分类统计
+2. **格口选择** - 允许用户选择特定类型的格口进行物品存入
+3. **物品存入流程** - 完整的存入物品流程:分配格口 → 生成密码 → 验证密码 → 打开格口
+4. **物品取出流程** - 通过密码验证取出已暂存的物品
+5. **状态管理** - 使用状态机模式管理弹窗流程,确保业务流程完整性
+
+## 功能特性
+
+### 1. 数据展示
+- 实时获取并显示可用格口数量
+- 按四种格口类型分类统计:小格(1)、中格(2)、大格(3)、超大格(4)
+- 显示每种类型的剩余可用数量
+- 支持刷新数据
+
+### 2. 格口选择
+- 可视化格口类型卡片展示
+- 显示每种类型的SVG图标
+- 智能禁用无可用格口的类型
+- 选中状态视觉反馈
+
+### 3. 存入流程
+```
+用户点击"物品暂存" → 分配选定类型格口 → 生成4位数字密码 →
+显示密码给用户 → 用户确认已记住 → 输入密码验证 →
+验证通过打开格口 → 刷新格口状态
+```
+
+### 4. 取出流程
+```
+用户点击"物品取出" → 输入4位密码 → 验证密码 →
+验证通过打开格口 → 刷新格口状态
+```
+
+### 5. 用户交互
+- 加载状态提示
+- 错误状态处理与重试
+- 空状态提示
+- 弹窗式密码输入与验证
+- 数字键盘集成
+
+## 组件结构
+
+### 文件位置
+```
+src/components/storage-cells-summary/
+├── index.vue # 主组件文件
+└── usePopupState.ts # 弹窗状态管理hook
+```
+
+### 组件Props
+| 属性名 | 类型 | 默认值 | 说明 |
+|--------|------|--------|------|
+| `shopId` | `number` | 必填 | 店铺ID,用于获取可用格口列表 |
+| `autoLoad` | `boolean` | `true` | 是否自动加载数据 |
+| `showButtons` | `boolean` | `true` | 是否显示操作按钮(存入/取出) |
+
+### 组件Emits
+| 事件名 | 参数 | 说明 |
+|--------|------|------|
+| `deposit` | 无 | 点击存入按钮时触发(向后兼容) |
+| `retrieve` | 无 | 点击取出按钮时触发(向后兼容) |
+| `refresh` | 无 | 数据刷新完成时触发 |
+| `error` | `Error` | 数据加载失败时触发 |
+| `backToAddressSelect` | 无 | 点击"重选地址"按钮时触发 |
+
+### 核心状态变量
+| 变量名 | 类型 | 说明 |
+|--------|------|------|
+| `loading` | `Ref` | 数据加载状态 |
+| `error` | `Ref` | 错误信息 |
+| `cellsData` | `Ref` | 格口数据列表 |
+| `selectedCellType` | `Ref` | 当前选中的格口类型 |
+| `depositLoading` | `Ref` | 存入操作加载状态 |
+| `retrieveLoading` | `Ref` | 取出操作加载状态 |
+| `generatedPassword` | `Ref` | 生成的密码(向后兼容) |
+
+### 计算属性
+| 属性名 | 说明 |
+|--------|------|
+| `availableCells` | 过滤出无密码的可用格口 |
+| `cellTypeStats` | 按类型统计可用格口数量 |
+| `hasAvailableCells` | 是否存在可用格口 |
+
+## 业务流程
+
+### 1. 数据初始化流程
+```mermaid
+graph TD
+ A[组件挂载] --> B{autoLoad为true?};
+ B -->|是| C[调用refresh函数];
+ B -->|否| D[等待手动刷新];
+ C --> E[调用availableStorageCells API];
+ E --> F[更新cellsData];
+ F --> G[触发refresh事件];
+```
+
+### 2. 物品存入流程
+```mermaid
+graph TD
+ A[用户点击"物品暂存"] --> B[触发deposit事件];
+ B --> C[调用handleDepositFlow];
+ C --> D[调用storeItemApi分配格口];
+ D --> E{是否获取到密码?};
+ E -->|是| F[显示密码弹窗];
+ E -->|否| G[显示错误提示];
+ F --> H[用户点击"已记住"];
+ H --> I[进入密码验证状态];
+ I --> J[用户输入密码];
+ J --> K[密码验证];
+ K --> L{密码正确?};
+ L -->|是| M[调用openByPassword打开格口];
+ L -->|否| N[显示密码错误提示];
+ M --> O[显示成功提示];
+ O --> P[刷新格口数据];
+```
+
+### 3. 物品取出流程
+```mermaid
+graph TD
+ A[用户点击"物品取出"] --> B[触发retrieve事件];
+ B --> C[显示密码输入弹窗];
+ C --> D[用户输入密码];
+ D --> E[调用openByPassword打开格口];
+ E --> F[显示成功提示];
+ F --> G[刷新格口数据];
+```
+
+## API接口
+
+### 依赖的API模块
+```typescript
+import {
+ availableStorageCells, // 获取可用格口列表
+ storeItemApi, // 存入物品分配格口
+ openByPassword, // 根据密码打开格口
+ resetByPassword // 重置格口状态(当前未使用)
+} from '@/api/cabinet/index'
+```
+
+### 1. availableStorageCells
+**功能**:获取指定店铺的可用暂存柜格口列表
+
+**请求参数**:
+```typescript
+{
+ shopId: number // 店铺ID
+}
+```
+
+**响应数据结构**:`AvailableStorageCellDTO[]`
+```typescript
+interface AvailableStorageCellDTO {
+ cellId: number; // 格口ID
+ cabinetId: number; // 柜机ID
+ cabinetName: string; // 柜子名称
+ mainboardId: number; // 主板ID
+ cellNo: number; // 格口号
+ pinNo: number; // 针脚序号
+ stock: number; // 库存数量
+ cellPrice: number; // 格口租用价格
+ isRented: number; // 是否已租用(0-未租用,1-已租用)
+ cellType: number; // 格口类型(1-小格,2-中格,3-大格,4-超大格)
+ usageStatus: number; // 使用状态(1-空闲,2-已占用)
+ availableStatus: number;// 可用状态(1-正常,2-故障)
+ hasPassword: boolean; // 是否有密码(true-有密码/已占用,false-无密码/可用)
+ goodsId: number; // 商品ID
+ goodsName: string; // 商品名称
+ price: number; // 商品价格
+ coverImg: string; // 封面图URL
+}
+```
+
+### 2. storeItemApi
+**功能**:存入物品并分配格口,生成访问密码
+
+**请求参数**:`StoreItemToCellCommand`
+```typescript
+interface StoreItemToCellCommand {
+ shopId: number; // 店铺ID
+ cellType: number; // 格口类型(1-小格,2-中格,3-大格,4-超大格)
+}
+```
+
+**响应数据结构**:`CabinetCellEntity`
+```typescript
+interface CabinetCellEntity {
+ cellId: number; // 格口唯一ID
+ cabinetId: number; // 关联柜机ID
+ mainboardId?: number; // 主板ID
+ cellNo: number; // 格口号
+ pinNo: number; // 针脚序号
+ stock: number; // 库存数量
+ cellPrice?: number; // 格口价格
+ isRented: number; // 是否已租用:0-未租用,1-已租用
+ cellType: number; // 格口类型(1小格 2中格 3大格 4超大格)
+ usageStatus: number; // 使用状态:1空闲 2已占用
+ availableStatus: number;// 可用状态:1正常 2故障
+ goodsId?: number; // 商品ID
+ password?: string; // 密码(暂存模式使用)
+}
+```
+
+### 3. openByPassword
+**功能**:根据密码打开对应的格口
+
+**请求参数**:`OpenCellByPasswordCommand`
+```typescript
+interface OpenCellByPasswordCommand {
+ shopId: number; // 店铺ID
+ password: string; // 格口密码
+}
+```
+
+**响应**:无数据,成功时HTTP状态码为200
+
+## 状态管理:usePopupState
+
+### 状态机设计
+弹窗流程采用状态机模式,确保业务流程的完整性和一致性:
+
+| 状态类型 | 说明 | 显示内容 |
+|----------|------|----------|
+| `idle` | 空闲状态,无弹窗显示 | 无弹窗 |
+| `password-show` | 显示生成的密码 | 密码显示弹窗 |
+| `password-verify` | 验证用户输入的密码 | 密码输入框+验证按钮 |
+| `retrieve-input` | 输入取出密码 | 密码输入框+确认按钮 |
+| `processing` | 显示处理中的加载提示 | 加载动画+处理文本 |
+
+### 状态转换图
+```mermaid
+graph LR
+ A[idle] -->|存入流程开始| B[password-show];
+ B -->|点击"已记住"| C[password-verify];
+ C -->|密码验证通过| D[processing];
+ C -->|密码验证失败| C;
+ A -->|取出流程开始| E[retrieve-input];
+ E -->|输入密码确认| D[processing];
+ D -->|操作完成| A;
+ B -->|取消| A;
+ C -->|取消| A;
+ E -->|取消| A;
+```
+
+### Hook接口
+```typescript
+interface UsePopupStateReturn {
+ currentState: Ref; // 当前弹窗状态
+ popupInputValue: Ref; // 弹窗输入框的值
+ passwordLength: number; // 密码长度(固定为4)
+ popupVisible: ComputedRef; // 弹窗显示状态
+ keyboardVisible: Ref; // 键盘显示状态
+ popupTitle: ComputedRef; // 弹窗标题
+ popupMessage: ComputedRef; // 弹窗消息内容
+ popupConfirmText: ComputedRef; // 确认按钮文本
+ showCancelButton: ComputedRef; // 是否显示取消按钮
+ transitionTo: (state: PopupState) => void; // 状态转换函数
+ // ... 其他处理函数
+}
+```
+
+## 使用方法
+
+### 1. 基本使用
+```vue
+
+
+
+```
+
+### 2. 仅展示模式
+```vue
+
+
+
+```
+
+### 3. 手动控制数据加载
+```vue
+
+
+
+
+
+```
+
+## 样式与UI组件
+
+### 使用的UI组件
+- `wd-popup` - 弹窗容器
+- `wd-password-input` - 密码输入框(格子模式)
+- `wd-keyboard` - 数字键盘
+- `wd-button` - 按钮
+- `wd-loading` - 加载动画
+- `wd-icon` - 图标
+- `wd-row` / `wd-col` - 栅格布局
+
+### 样式特点
+- 使用 `rpx` 单位适配不同屏幕
+- 响应式网格布局(2列)
+- 交互状态反馈(点击效果、选中状态)
+- 弹窗圆角设计(border-top-left-radius: 24rpx)
+- 安全区域适配(safe-area-inset-bottom)
+
+## 注意事项
+
+### 1. 密码安全
+- 密码为4位数字,由系统自动生成
+- 密码仅在存入流程中显示一次,用户需要牢记
+- 密码验证失败会提示错误,但不会泄露正确密码
+
+### 2. 格口状态同步
+- 每次成功操作(存入/取出)后会自动刷新格口数据
+- 可用格口数量实时更新
+- 已被占用的格口(hasPassword: true)不会显示在可用列表中
+
+### 3. 错误处理
+- 网络请求失败会显示错误提示
+- 用户可点击"重试"按钮重新加载
+- 操作失败会回到初始状态,不会卡在中间状态
+
+### 4. 向后兼容性
+- 保留了 `deposit` 和 `retrieve` 事件发射
+- 保留了 `generatedPassword` 状态变量
+- 新增的 `handleDepositFlow` 和 `handleRetrieveFlow` 函数与原有事件处理并行
+
+### 5. 性能考虑
+- 按需加载数据(autoLoad控制)
+- 计算属性缓存统计结果
+- 避免不必要的重复请求
+
+## 相关文件
+
+- `src/components/storage-cells-summary/index.vue` - 主组件实现
+- `src/components/storage-cells-summary/usePopupState.ts` - 弹窗状态管理
+- `src/api/cabinet/index.ts` - 柜机相关API
+- `src/api/cabinet/types.ts` - 数据类型定义
+- `src/static/svg/` - 格口类型SVG图标
+
+---
+
+**最后更新**:2025-12-22
+**组件版本**:基于当前代码实现
+**维护者**:项目开发团队
\ No newline at end of file
diff --git a/src/components/storage-cells-summary/index.vue b/src/components/storage-cells-summary/index.vue
index b142eec..2318cb3 100644
--- a/src/components/storage-cells-summary/index.vue
+++ b/src/components/storage-cells-summary/index.vue
@@ -2,7 +2,7 @@
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'
+import { usePopupState } from './usePopupState'
// 格口类型映射
const CELL_TYPE_MAP = {
@@ -42,11 +42,29 @@ const error = ref(null)
const cellsData = ref([])
const selectedCellType = ref(1)
-const message = useMessage()
const depositLoading = ref(false)
const retrieveLoading = ref(false)
const generatedPassword = ref('')
+// 弹窗状态管理
+const {
+ currentState,
+ popupInputValue,
+ passwordLength,
+ popupVisible,
+ keyboardVisible,
+ popupTitle,
+ popupMessage,
+ popupConfirmText,
+ showCancelButton,
+ transitionTo,
+ handlePasswordInputChange,
+ handlePopupCancel,
+ handleKeyboardInput,
+ handleKeyboardDelete,
+ handleKeyboardClose
+} = usePopupState()
+
// 统计计算属性
const availableCells = computed(() =>
cellsData.value.filter(cell => !cell.hasPassword)
@@ -116,164 +134,105 @@ function handleBackToAddressSelect() {
// 存入流程处理函数
async function handleDepositFlow() {
try {
- // 1. 调用 storeItemApi 分配格口
depositLoading.value = true
+
+ // 1. 分配格口
const response = await storeItemApi({
shopId: props.shopId,
cellType: selectedCellType.value
})
- // 保存生成的密码(根据用户确认,接口返回包含 password 字段)
- generatedPassword.value = response.data?.password || ''
-
- // 检查密码是否为空
- if (!generatedPassword.value) {
+ const password = response.data?.password || ''
+ if (!password) {
throw new Error('格口分配失败,未获取到密码')
}
- // 2. 显示密码弹窗让用户记住
- await message.alert({
- title: '密码已生成',
- msg: `请牢记你的暂存密码为:${generatedPassword.value}\n请确认已打开的柜子,放置物品后将柜子关闭。`,
- confirmButtonText: '已记住',
- closeOnClickModal: false
- })
+ // 保存生成的密码以向后兼容
+ generatedPassword.value = password
- // 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
- })
+ // 2. 显示密码(状态转换)
+ transitionTo({ type: 'password-show', password })
- // 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,
+ title: (error as any)?.message || '操作失败',
icon: 'error',
duration: 3000
})
+ transitionTo({ type: 'idle' })
} finally {
depositLoading.value = false
}
}
-// 取出流程处理函数
-async function handleRetrieveFlow() {
+// 弹窗相关事件处理
+
+async function handlePopupConfirm() {
+ const state = currentState.value
+
+ switch (state.type) {
+ case 'password-show':
+ // 点击"已记住",进入密码验证
+ transitionTo({ type: 'password-verify', correctPassword: state.password })
+ break
+
+ case 'password-verify':
+ // 验证密码
+ if (popupInputValue.value !== state.correctPassword) {
+ uni.showToast({ title: '密码不正确', icon: 'error' })
+ return
+ }
+ // 打开格口
+ await performOpenByPassword(popupInputValue.value, 'deposit')
+ break
+
+ case 'retrieve-input':
+ // 打开格口
+ await performOpenByPassword(popupInputValue.value, 'retrieve')
+ break
+ }
+}
+
+
+async function performOpenByPassword(password: string, action: 'deposit' | 'retrieve') {
+ transitionTo({ type: 'processing', action })
+
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. 成功提示
uni.showToast({
title: '格口已打开',
icon: 'success'
})
- // 注释掉清空格口部分,打开格口即显示打开成功结束流程
- /*
- // 成功提示并询问是否清空
- await message.alert({
- title: '格口已打开',
- msg: '柜子已开,请取物品!如不再使用柜子请点击 "清空" 。',
- confirmButtonText: '清空',
- closeOnClickModal: false
- })
-
- // 确认清空弹窗
- await message.confirm({
- title: '确认清空',
- msg: '清空后密码将不能再次使用,确认清空?',
- closeOnClickModal: false
- })
-
- // 重置格口状态
- await resetByPassword({
- shopId: props.shopId,
- password: String(password)
- })
-
- // 重置成功提示
- uni.showToast({
- title: '格口已清空',
- icon: 'success'
- })
- */
+ // 刷新数据
+ 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)
-
- // 显示具体的错误信息
+ console.error('打开格口失败:', error)
uni.showToast({
- title: errorMessage.length > 20 ? '操作失败' : errorMessage,
+ title: (error as any)?.message || '操作失败',
icon: 'error',
duration: 3000
})
} finally {
- retrieveLoading.value = false
- await refresh()
+ transitionTo({ type: 'idle' })
}
}
+// 取出流程处理函数
+async function handleRetrieveFlow() {
+ // 直接进入密码输入状态
+ transitionTo({ type: 'retrieve-input' })
+}
+
// 格口类型图标映射(需验证图标名称可用性)
const CELL_TYPE_ICON_MAP = {
1: 'box', // 小格
@@ -391,8 +350,77 @@ onMounted(() => {
-
-
+
+
+
+
+
+
@@ -562,5 +590,50 @@ onMounted(() => {
height: 80rpx;
border-radius: 4px;
}
+
+ // 弹窗样式
+ .popup-content {
+ display: flex;
+ flex-direction: column;
+ gap: 32rpx;
+
+ .popup-title {
+ font-size: 36rpx;
+ font-weight: 600;
+ color: #333;
+ text-align: center;
+ }
+
+ .popup-message {
+ font-size: 28rpx;
+ color: #666;
+ line-height: 1.5;
+ white-space: pre-line;
+ text-align: center;
+ }
+
+ .popup-password-input {
+ margin: 32rpx 0;
+ }
+
+ .processing-state {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24rpx;
+ padding: 48rpx 0;
+
+ .processing-text {
+ font-size: 28rpx;
+ color: #666;
+ }
+ }
+
+ .popup-actions {
+ display: flex;
+ gap: 16rpx;
+ margin-top: 32rpx;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/components/storage-cells-summary/usePopupState.ts b/src/components/storage-cells-summary/usePopupState.ts
new file mode 100644
index 0000000..04a7461
--- /dev/null
+++ b/src/components/storage-cells-summary/usePopupState.ts
@@ -0,0 +1,243 @@
+/**
+ * 弹窗状态机管理 hook
+ *
+ * 用于管理 storage-cells-summary 组件的弹窗流程,采用状态机模式控制不同弹窗的显示和转换。
+ * 支持以下弹窗状态:
+ * 1. 空闲状态 (idle) - 无弹窗显示
+ * 2. 密码显示状态 (password-show) - 显示生成的密码
+ * 3. 密码验证状态 (password-verify) - 验证用户输入的密码
+ * 4. 取出输入状态 (retrieve-input) - 输入取出密码
+ * 5. 处理中状态 (processing) - 显示处理中的加载提示
+ *
+ * 通过状态机模式确保业务流程的完整性,并提供统一的弹窗控制接口。
+ */
+
+import { ref, computed, watch } from 'vue'
+
+/**
+ * 弹窗状态联合类型
+ * 定义所有可能的弹窗状态及其携带的数据
+ */
+export type PopupState =
+ | { type: 'idle' } // 空闲状态,无弹窗显示
+ | { type: 'password-show', password: string } // 显示生成的密码
+ | { type: 'password-verify', correctPassword: string } // 密码验证输入
+ | { type: 'retrieve-input' } // 取出密码输入
+ | { type: 'processing', action: 'deposit' | 'retrieve' } // 处理中状态
+
+/**
+ * usePopupState hook 返回值接口
+ * 定义 hook 返回的所有状态变量和函数的类型
+ */
+export interface UsePopupStateReturn {
+ /** 当前弹窗状态,通过 ref 管理以便响应式更新 */
+ currentState: ReturnType>
+ /** 弹窗输入框的值,用于密码验证和取出流程 */
+ popupInputValue: ReturnType>
+ /** 密码长度,固定为 4 位 */
+ passwordLength: number
+ /** 弹窗显示状态,根据当前状态自动计算 */
+ popupVisible: ReturnType>
+ /** 键盘显示状态 */
+ keyboardVisible: ReturnType>
+ /** 弹窗标题,根据当前状态自动计算 */
+ popupTitle: ReturnType>
+ /** 弹窗消息内容,根据当前状态自动计算 */
+ popupMessage: ReturnType>
+ /** 确认按钮文本,根据当前状态自动计算 */
+ popupConfirmText: ReturnType>
+ /** 是否显示取消按钮,根据当前状态自动计算 */
+ showCancelButton: ReturnType>
+ /** 状态转换函数,用于切换弹窗状态 */
+ transitionTo: (state: PopupState) => void
+ /** 密码输入变化处理函数 */
+ handlePasswordInputChange: (value: string) => void
+ /** 弹窗取消/关闭处理函数 */
+ handlePopupCancel: () => void
+ /** 键盘输入处理函数 */
+ handleKeyboardInput: (key: string) => void
+ /** 键盘删除处理函数 */
+ handleKeyboardDelete: () => void
+ /** 键盘关闭处理函数 */
+ handleKeyboardClose: () => void
+}
+
+/**
+ * 弹窗状态机管理 hook
+ *
+ * 提供弹窗状态管理、状态转换、弹窗内容计算等功能
+ * 使用状态机模式确保业务流程的完整性和一致性
+ *
+ * @returns {UsePopupStateReturn} 弹窗状态管理对象,包含状态变量和控制函数
+ */
+export function usePopupState(): UsePopupStateReturn {
+ // 当前弹窗状态,初始化为空闲状态
+ const currentState = ref({ type: 'idle' })
+ // 弹窗输入框的值,用于密码输入验证
+ const popupInputValue = ref('')
+ // 键盘显示状态
+ const keyboardVisible = ref(false)
+ // 密码长度,固定为 4 位数字密码
+ const passwordLength = 4
+
+ /**
+ * 状态转换函数
+ *
+ * 切换当前弹窗状态,并在需要时重置输入值
+ *
+ * @param {PopupState} state - 要切换到的目标状态
+ */
+ function transitionTo(state: PopupState) {
+ currentState.value = state
+
+ // 控制键盘显示状态
+ if (state.type === 'password-verify' || state.type === 'retrieve-input') {
+ // 进入需要输入密码的状态时显示键盘
+ keyboardVisible.value = true
+ // 重置输入值当进入需要输入的弹窗(密码验证或取出输入)
+ popupInputValue.value = ''
+ } else {
+ // 其他状态隐藏键盘
+ keyboardVisible.value = false
+ }
+ }
+
+ /**
+ * 弹窗显示状态计算属性
+ *
+ * 只要当前状态不是空闲状态,就显示弹窗
+ */
+ const popupVisible = computed(() => currentState.value.type !== 'idle')
+
+ /**
+ * 弹窗标题计算属性
+ *
+ * 根据当前状态返回相应的标题文本
+ */
+ const popupTitle = computed(() => {
+ const state = currentState.value
+ switch (state.type) {
+ case 'password-show': return '密码已生成'
+ case 'password-verify': return '密码验证'
+ case 'retrieve-input': return '输入密码'
+ default: return ''
+ }
+ })
+
+ /**
+ * 弹窗消息内容计算属性
+ *
+ * 根据当前状态返回相应的消息文本
+ * 支持使用 \n 换行符进行换行
+ */
+ const popupMessage = computed(() => {
+ const state = currentState.value
+ switch (state.type) {
+ case 'password-show': return `请牢记你的暂存密码为:${state.password}\n请确认已打开的柜子,放置物品后将柜子关闭。`
+ case 'password-verify': return '请输入刚才显示的密码进行验证'
+ case 'retrieve-input': return '请输入格口密码'
+ default: return ''
+ }
+ })
+
+ /**
+ * 确认按钮文本计算属性
+ *
+ * 根据当前状态返回相应的确认按钮文本
+ */
+ const popupConfirmText = computed(() => {
+ const state = currentState.value
+ switch (state.type) {
+ case 'password-show': return '已记住'
+ case 'password-verify': return '验证'
+ case 'retrieve-input': return '确认'
+ default: return '确认'
+ }
+ })
+
+ /**
+ * 显示取消按钮计算属性
+ *
+ * 在需要用户输入的状态(密码验证、取出输入)显示取消按钮
+ * 允许用户取消当前操作流程
+ */
+ const showCancelButton = computed(() => {
+ const state = currentState.value
+ return state.type === 'password-verify' || state.type === 'retrieve-input'
+ })
+
+ /**
+ * 密码输入变化处理函数
+ *
+ * 处理密码输入框的变化事件,可以留空或添加额外的业务逻辑
+ * 例如:输入时进行实时验证、限制输入格式等
+ *
+ * @param {string} value - 当前输入的密码值
+ */
+ function handlePasswordInputChange(value: string) {
+ // 密码输入变化处理,可以留空或添加额外逻辑
+ // 例如:实时验证密码格式、限制输入长度等
+ }
+
+ /**
+ * 弹窗取消/关闭处理函数
+ *
+ * 用户点击取消按钮或关闭弹窗时调用
+ * 将状态切换回空闲状态,中断当前操作流程
+ */
+ function handlePopupCancel() {
+ transitionTo({ type: 'idle' })
+ }
+
+ /**
+ * 键盘输入处理函数
+ *
+ * 处理键盘按键输入,更新输入值,但不超过最大长度
+ *
+ * @param {string} key - 输入的按键值
+ */
+ function handleKeyboardInput(key: string) {
+ if (popupInputValue.value.length < passwordLength) {
+ popupInputValue.value += key
+ }
+ }
+
+ /**
+ * 键盘删除处理函数
+ *
+ * 删除最后一位输入
+ */
+ function handleKeyboardDelete() {
+ if (popupInputValue.value.length > 0) {
+ popupInputValue.value = popupInputValue.value.slice(0, -1)
+ }
+ }
+
+ /**
+ * 键盘关闭处理函数
+ *
+ * 关闭键盘,但保持弹窗显示(用户可能点击外部关闭键盘但不想取消操作)
+ */
+ function handleKeyboardClose() {
+ keyboardVisible.value = false
+ }
+
+ // 返回弹窗状态管理对象
+ return {
+ currentState,
+ popupInputValue,
+ passwordLength,
+ popupVisible,
+ keyboardVisible,
+ popupTitle,
+ popupMessage,
+ popupConfirmText,
+ showCancelButton,
+ transitionTo,
+ handlePasswordInputChange,
+ handlePopupCancel,
+ handleKeyboardInput,
+ handleKeyboardDelete,
+ handleKeyboardClose
+ }
+}
\ No newline at end of file