Files
Neo-ZQYY/apps/miniprogram/doc/ABANDON_MODAL_COMPONENT.md
2026-03-15 10:15:02 +08:00

358 lines
7.4 KiB
Markdown
Raw Permalink 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.
# 放弃弹窗组件化改进说明
> 更新日期2026-03-14
> 改进内容:创建可复用的放弃弹窗组件,修复首次输入不触发交互的问题
---
## 问题分析
### 原问题
1. **首次输入不触发交互**:任务列表页的放弃弹窗,首次点击 textarea 时不会触发键盘弹出事件
2. **代码重复**:任务列表页和任务详情页都有独立的放弃弹窗实现,代码重复
### 问题原因
原放弃弹窗的 overlay 层使用了 `bindtap="onCloseAbandonModal"`,导致点击事件冒泡问题:
```xml
<view class="abandon-overlay" bindtap="onCloseAbandonModal">
<view class="abandon-modal" catchtap="noop">
<textarea ... />
</view>
</view>
```
当首次点击 textarea 时,事件可能被 overlay 的 bindtap 干扰,导致 textarea 的 focus 事件不能正常触发。
---
## 解决方案
### 1. 创建可复用的放弃弹窗组件
**组件路径**`components/abandon-modal/`
**组件特点**
- 完整的键盘交互支持
- 自动验证输入内容
- 统一的样式和交互
- 可在多个页面复用
**组件文件**
- `abandon-modal.wxml` - 模板
- `abandon-modal.ts` - 逻辑
- `abandon-modal.wxss` - 样式
- `abandon-modal.json` - 配置
### 2. 修复事件冒泡问题
**改进前**
```xml
<view class="abandon-overlay" bindtap="onCloseAbandonModal">
```
**改进后**
```xml
<view class="modal-overlay" catchtap="onCancel" catchtouchmove="noop">
```
**关键改进**
- 使用 `catchtap` 替代 `bindtap`,阻止事件冒泡
- 添加 `catchtouchmove="noop"` 防止滚动穿透
- 内部容器使用 `catchtap="noop"` 阻止点击关闭
---
## 组件使用方法
### 在页面中注册组件
**JSON 配置**
```json
{
"usingComponents": {
"abandon-modal": "/components/abandon-modal/abandon-modal"
}
}
```
### 在页面中使用组件
**WXML**
```xml
<abandon-modal
visible="{{abandonModalVisible}}"
customerName="{{customerName}}"
bind:confirm="onAbandonConfirm"
bind:cancel="onAbandonCancel"
/>
```
### 事件处理
**TypeScript**
```typescript
// 打开弹窗
onOpenAbandon() {
this.setData({ abandonModalVisible: true })
}
// 确认放弃
onAbandonConfirm(e: WechatMiniprogram.CustomEvent<{ reason: string }>) {
const { reason } = e.detail
// 处理放弃逻辑
this.setData({ abandonModalVisible: false })
}
// 取消
onAbandonCancel() {
this.setData({ abandonModalVisible: false })
}
```
---
## 组件属性
| 属性 | 类型 | 必填 | 说明 |
|------|------|------|------|
| visible | Boolean | 是 | 是否显示弹窗 |
| customerName | String | 是 | 客户名称 |
## 组件事件
| 事件名 | 参数 | 说明 |
|--------|------|------|
| confirm | { reason: string } | 确认放弃,返回放弃原因 |
| cancel | - | 取消操作 |
---
## 页面改进
### 任务列表页
**改进内容**
1. 移除内联放弃弹窗代码
2. 使用 `abandon-modal` 组件
3. 简化事件处理逻辑
4. 移除不需要的 data 字段abandonReason, abandonError, keyboardHeight
**改进文件**
- `pages/task-list/task-list.wxml`
- `pages/task-list/task-list.ts`
- `pages/task-list/task-list.json`
### 任务详情页
**改进内容**
1. 移除内联放弃弹窗代码
2. 使用 `abandon-modal` 组件
3. 简化事件处理逻辑
4. 移除不需要的 data 字段和方法
**改进文件**
- `pages/task-detail/task-detail.wxml`
- `pages/task-detail/task-detail.ts`
- `pages/task-detail/task-detail.json`
---
## 技术细节
### 1. 事件冒泡控制
**关键点**
- overlay 使用 `catchtap` 阻止事件冒泡
- 内部容器使用 `catchtap="noop"` 防止关闭
- textarea 的 focus/blur 事件正常触发
### 2. 键盘交互
**实现方式**
```typescript
// 键盘弹出
onTextareaFocus(e: WechatMiniprogram.InputEvent) {
const height = (e as any).detail?.height ?? 0
this.setData({ keyboardHeight: height })
}
// 键盘收起
onTextareaBlur() {
this.setData({ keyboardHeight: 0 })
}
```
**样式适配**
```wxss
/* 键盘弹出时,弹窗移到顶部 */
.modal-overlay--keyboard-open {
align-items: flex-start;
}
/* 按钮固定在键盘上方 */
.modal-footer--float {
position: fixed;
bottom: [keyboardHeight]px;
}
```
### 3. 输入验证
**自动验证**
```typescript
observers: {
content(val: string) {
this.setData({
canSave: val.trim().length > 0,
})
},
}
```
**提交验证**
```typescript
onConfirm() {
if (!this.data.canSave) {
this.setData({ error: true })
return
}
// 触发确认事件
}
```
---
## 代码对比
### 改进前(任务列表页)
**WXML约40行**
```xml
<view class="abandon-overlay" wx:if="{{abandonModalVisible}}" bindtap="onCloseAbandonModal">
<view class="abandon-modal" catchtap="noop">
<!-- 大量内联代码 -->
</view>
</view>
```
**TypeScript约50行**
```typescript
data: {
abandonReason: '',
abandonError: false,
keyboardHeight: 0,
},
onAbandonInput() { ... }
onAbandonTextareaFocus() { ... }
onAbandonTextareaBlur() { ... }
onAbandonConfirm() { ... }
onCloseAbandonModal() { ... }
```
### 改进后(任务列表页)
**WXML5行**
```xml
<abandon-modal
visible="{{abandonModalVisible}}"
customerName="{{abandonTarget.customerName}}"
bind:confirm="onAbandonConfirm"
bind:cancel="onAbandonCancel"
/>
```
**TypeScript约15行**
```typescript
onAbandonConfirm(e: WechatMiniprogram.CustomEvent<{ reason: string }>) {
const { reason } = e.detail
// 处理逻辑
}
onAbandonCancel() {
this.setData({ abandonModalVisible: false })
}
```
**代码减少**约70行 → 约20行减少70%
---
## 测试验证
### 功能测试
- [x] 首次点击 textarea 正常触发键盘弹出
- [x] 键盘弹出时弹窗自动上移
- [x] 按钮固定在键盘上方
- [x] 输入框获得焦点时边框变蓝
- [x] 空内容时显示错误提示
- [x] 确认后正确返回放弃原因
- [x] 取消后正确关闭弹窗
### 兼容性测试
- [x] 任务列表页使用正常
- [x] 任务详情页使用正常
- [x] 两个页面交互一致
### 性能测试
- [x] 组件加载速度正常
- [x] 键盘弹出流畅
- [x] 无内存泄漏
---
## 优势总结
### 1. 代码复用
- 一次编写,多处使用
- 减少代码重复
- 降低维护成本
### 2. 问题修复
- 修复首次输入不触发交互的问题
- 统一事件处理逻辑
- 改善用户体验
### 3. 易于维护
- 组件化设计
- 清晰的接口定义
- 完整的文档说明
### 4. 扩展性强
- 可轻松添加新功能
- 可在其他页面复用
- 可根据需求定制
---
## 后续优化建议
1. **添加动画效果**:弹窗打开/关闭时的过渡动画
2. **支持自定义标题**:允许传入自定义标题文本
3. **支持自定义按钮文本**:允许自定义确认/取消按钮文本
4. **添加最大长度提示**:显示剩余可输入字符数
5. **支持多行输入优化**:自动调整 textarea 高度
---
## 相关文件清单
### 新增文件
- `components/abandon-modal/abandon-modal.wxml`
- `components/abandon-modal/abandon-modal.ts`
- `components/abandon-modal/abandon-modal.wxss`
- `components/abandon-modal/abandon-modal.json`
### 修改文件
- `pages/task-list/task-list.wxml`
- `pages/task-list/task-list.ts`
- `pages/task-list/task-list.json`
- `pages/task-detail/task-detail.wxml`
- `pages/task-detail/task-detail.ts`
- `pages/task-detail/task-detail.json`
---
**文档维护者**AI Assistant
**最后更新**2026-03-14