shop-wx/doc/plans/lively-crafting-taco.md

416 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 将 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
<wd-popup
v-model="showPopup"
position="bottom"
:safe-area-inset-bottom="true"
custom-style="border-top-left-radius: 24rpx; border-top-right-radius: 24rpx; padding: 0; background: #fff; max-height: 90vh;"
@close="handleClose">
<!-- 自定义内容 -->
</wd-popup>
```
## 需求总结
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<PopupState>({ 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
<wd-popup
:model-value="popupVisible"
position="bottom"
:safe-area-inset-bottom="true"
custom-style="border-top-left-radius: 24rpx; border-top-right-radius: 24rpx; padding: 32rpx; background: #fff; max-height: 80vh;"
@update:model-value="handlePopupCancel">
<view class="popup-content">
<!-- 标题 -->
<text class="popup-title">{{ popupTitle }}</text>
<!-- 消息内容支持换行 -->
<text class="popup-message">{{ popupMessage }}</text>
<!-- 密码输入框格子模式 -->
<wd-password-input
v-if="currentState.type === 'password-verify' || currentState.type === 'retrieve-input'"
v-model="popupInputValue"
:length="passwordLength"
:gutter="8"
:mask="true"
:focus="true"
class="popup-password-input"
@change="handlePasswordInputChange" />
<!-- 处理中状态提示 -->
<view v-if="currentState.type === 'processing'" class="processing-state">
<wd-loading type="ring" />
<text class="processing-text">
{{ currentState.action === 'deposit' ? '正在存入...' : '正在取出...' }}
</text>
</view>
<view class="popup-actions">
<!-- 取消按钮 -->
<wd-button
v-if="showCancelButton"
type="default"
block
@click="handlePopupCancel">
取消
</wd-button>
<!-- 确认按钮 -->
<wd-button
type="primary"
block
@click="handlePopupConfirm"
:disabled="(currentState.type === 'password-verify' || currentState.type === 'retrieve-input') && popupInputValue.length !== passwordLength">
{{ popupConfirmText }}
</wd-button>
</view>
</view>
</wd-popup>
```
### 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`
- 启用底部安全区适配
- 业务功能完全正常
- 用户体验保持一致