问题描述

应用中需要频繁使用确认对话框、选择器对话框等,如何封装通用的 Dialog 组件避免重复代码?如何实现优雅的回调处理?

关键字: CustomDialog、对话框封装、组件复用、回调处理、UI 组件

解决方案

完整代码

/**
 * 确认对话框
 */
@CustomDialog
export struct ConfirmDialog {
  controller: CustomDialogController;
  title: string = '提示';
  message: string = '';
  confirmText: string = '确定';
  cancelText: string = '取消';
  onConfirm?: () => void;
  onCancel?: () => void;
  
  build() {
    Column({ space: 16 }) {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
      
      // 消息内容
      Text(this.message)
        .fontSize(14)
        .fontColor('#666')
        .width('100%')
        .margin({ top: 8, bottom: 16 })
      
      // 按钮组
      Row({ space: 12 }) {
        Button(this.cancelText)
          .fontSize(16)
          .backgroundColor('#f5f5f5')
          .fontColor('#333')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
            if (this.onCancel) {
              this.onCancel();
            }
          })
        
        Button(this.confirmText)
          .fontSize(16)
          .backgroundColor('#ff6b6b')
          .fontColor('#fff')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
            if (this.onConfirm) {
              this.onConfirm();
            }
          })
      }
      .width('100%')
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}
​
/**
 * 单选对话框
 */
@CustomDialog
export struct SelectDialog {
  controller: CustomDialogController;
  title: string = '请选择';
  options: string[] = [];
  selectedIndex: number = 0;
  onSelect?: (index: number, value: string) => void;
  
  @State currentIndex: number = 0;
  
  aboutToAppear() {
    this.currentIndex = this.selectedIndex;
  }
  
  build() {
    Column({ space: 12 }) {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ bottom: 12 })
      
      // 选项列表
      List({ space: 0 }) {
        ForEach(this.options, (option: string, index: number) => {
          ListItem() {
            Row() {
              Text(option)
                .fontSize(16)
                .fontColor(this.currentIndex === index ? '#ff6b6b' : '#333')
                .layoutWeight(1)
              
              if (this.currentIndex === index) {
                Text('✓')
                  .fontSize(18)
                  .fontColor('#ff6b6b')
              }
            }
            .width('100%')
            .padding(12)
            .backgroundColor(this.currentIndex === index ? '#fff5f5' : Color.White)
            .borderRadius(8)
            .onClick(() => {
              this.currentIndex = index;
            })
          }
        })
      }
      .height(Math.min(this.options.length * 48, 300))
      
      // 确定按钮
      Button('确定')
        .width('100%')
        .backgroundColor('#ff6b6b')
        .fontColor('#fff')
        .margin({ top: 12 })
        .onClick(() => {
          this.controller.close();
          if (this.onSelect) {
            this.onSelect(this.currentIndex, this.options[this.currentIndex]);
          }
        })
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .width('80%')
  }
}
​
/**
 * 输入对话框
 */
@CustomDialog
export struct InputDialog {
  controller: CustomDialogController;
  title: string = '输入';
  placeholder: string = '请输入';
  defaultValue: string = '';
  inputType: InputType = InputType.Normal;
  maxLength: number = 50;
  onConfirm?: (value: string) => void;
  
  @State inputValue: string = '';
  
  aboutToAppear() {
    this.inputValue = this.defaultValue;
  }
  
  build() {
    Column({ space: 16 }) {
      // 标题
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width('100%')
      
      // 输入框
      TextInput({ text: this.inputValue, placeholder: this.placeholder })
        .type(this.inputType)
        .maxLength(this.maxLength)
        .onChange((value: string) => {
          this.inputValue = value;
        })
        .width('100%')
        .padding(12)
        .borderRadius(8)
        .backgroundColor('#f5f5f5')
      
      // 按钮组
      Row({ space: 12 }) {
        Button('取消')
          .fontSize(16)
          .backgroundColor('#f5f5f5')
          .fontColor('#333')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
          })
        
        Button('确定')
          .fontSize(16)
          .backgroundColor('#ff6b6b')
          .fontColor('#fff')
          .layoutWeight(1)
          .onClick(() => {
            this.controller.close();
            if (this.onConfirm) {
              this.onConfirm(this.inputValue);
            }
          })
      }
      .width('100%')
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}
​
/**
 * 加载对话框
 */
@CustomDialog
export struct LoadingDialog {
  controller: CustomDialogController;
  message: string = '加载中...';
  
  build() {
    Column({ space: 16 }) {
      LoadingProgress()
        .width(50)
        .height(50)
        .color('#ff6b6b')
      
      Text(this.message)
        .fontSize(14)
        .fontColor('#666')
    }
    .padding(30)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

使用示例

@Entry
@Component
struct DemoPage {
  private confirmDialogController: CustomDialogController | null = null;
  private selectDialogController: CustomDialogController | null = null;
  private inputDialogController: CustomDialogController | null = null;
  private loadingDialogController: CustomDialogController | null = null;
  
  // 显示确认对话框
  showConfirmDialog() {
    this.confirmDialogController = new CustomDialogController({
      builder: ConfirmDialog({
        title: '删除确认',
        message: '确定要删除这条记录吗?此操作不可恢复。',
        confirmText: '删除',
        cancelText: '取消',
        onConfirm: () => {
          console.log('用户点击了删除');
          this.deleteRecord();
        },
        onCancel: () => {
          console.log('用户取消了删除');
        }
      }),
      autoCancel: true,
      alignment: DialogAlignment.Center
    });
    this.confirmDialogController.open();
  }
  
  // 显示选择对话框
  showSelectDialog() {
    this.selectDialogController = new CustomDialogController({
      builder: SelectDialog({
        title: '选择关系',
        options: ['朋友', '同事', '亲戚', '同学', '其他'],
        selectedIndex: 0,
        onSelect: (index: number, value: string) => {
          console.log(`选择了: ${value}`);
        }
      }),
      autoCancel: true,
      alignment: DialogAlignment.Center
    });
    this.selectDialogController.open();
  }
  
  // 显示输入对话框
  showInputDialog() {
    this.inputDialogController = new CustomDialogController({
      builder: InputDialog({
        title: '添加备注',
        placeholder: '请输入备注内容',
        defaultValue: '',
        maxLength: 100,
        onConfirm: (value: string) => {
          console.log(`输入内容: ${value}`);
        }
      }),
      autoCancel: true,
      alignment: DialogAlignment.Center
    });
    this.inputDialogController.open();
  }
  
  // 显示加载对话框
  async showLoadingDialog() {
    this.loadingDialogController = new CustomDialogController({
      builder: LoadingDialog({
        message: '正在保存...'
      }),
      autoCancel: false,
      alignment: DialogAlignment.Center
    });
    this.loadingDialogController.open();
    
    // 模拟异步操作
    await this.saveData();
    
    // 关闭加载对话框
    this.loadingDialogController.close();
  }
  
  async deleteRecord() {
    // 删除逻辑
  }
  
  async saveData() {
    // 保存逻辑
  }
  
  build() {
    Column({ space: 16 }) {
      Button('确认对话框').onClick(() => this.showConfirmDialog())
      Button('选择对话框').onClick(() => this.showSelectDialog())
      Button('输入对话框').onClick(() => this.showInputDialog())
      Button('加载对话框').onClick(() => this.showLoadingDialog())
    }
    .padding(20)
  }
}

原理解析

1. @CustomDialog 装饰器

@CustomDialog
export struct ConfirmDialog {
  controller: CustomDialogController;
}
  • 标记为自定义对话框组件
  • 必须包含 controller 属性
  • 通过 controller 控制显示/隐藏

2. 回调函数传递

onConfirm?: () => void;
  • 使用可选属性定义回调
  • 调用前检查是否存在
  • 支持传递参数

3. @State 状态管理

@State currentIndex: number = 0;
  • 对话框内部状态
  • 响应用户交互
  • 触发 UI 更新

最佳实践

  1. 统一风格: 所有对话框使用相同的样式和动画
  2. 回调处理: 使用可选回调,调用前检查
  3. 自动关闭: 设置 autoCancel: true 支持点击外部关闭
  4. 内存管理: 对话框关闭后 controller 置 null
  5. 异步操作: 加载对话框配合 async/await 使用

避坑指南

  1. 忘记 close: 必须手动调用 controller.close()
  2. 重复打开: 打开前检查 controller 是否已存在
  3. 内存泄漏: 组件销毁时关闭所有对话框
  4. 回调丢失: 箭头函数保持 this 指向
  5. 样式覆盖: 使用 width/height 限制对话框大小

效果展示

  • 确认对话框:标题 + 消息 + 双按钮
  • 选择对话框:标题 + 列表 + 确定按钮
  • 输入对话框:标题 + 输入框 + 双按钮
  • 加载对话框:加载动画 + 提示文字

相关资源

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐