Skip to content

模态框工具 (modal.ts)

Element Plus 消息与弹窗工具函数集,提供统一的用户交互反馈功能,包括消息提示、弹窗提示、通知、确认框、输入框和加载遮罩等。

📖 概述

模态框工具库基于 Element Plus 组件库,提供以下功能:

  • 消息提示:各种类型的消息提示
  • 弹窗提示:各种类型的弹窗提示
  • 通知提示:各种类型的通知提示
  • 交互弹窗:确认框和输入框
  • 加载遮罩:控制全局加载遮罩

🎯 设计特点

Result 格式返回:所有异步方法都返回 Result<T> 格式:[Error | null, T | null]

统一 API 风格:所有函数使用 show 前缀,便于智能提示和语义理解

灵活参数支持:支持字符串快捷调用和对象详细配置两种方式

TypeScript 友好:完整的类型定义和智能提示支持

📢 消息提示

showMsg

显示一般信息消息。

typescript
showMsg(content: string | MessageParams): void

示例:

typescript
// 简单消息
showMsg('操作已完成')

// 自定义配置
showMsg({
  message: '操作已完成',
  duration: 5000,
  showClose: true
})

showMsgError

显示错误消息。

typescript
showMsgError(content: string | MessageParams): void

示例:

typescript
// 简单错误消息
showMsgError('操作失败,请重试')

// 详细错误配置
showMsgError({
  message: '网络请求失败,请检查网络连接',
  duration: 8000,
  showClose: true
})

showMsgSuccess

显示成功消息。

typescript
showMsgSuccess(content: string | MessageParams): void

showMsgWarning

显示警告消息。

typescript
showMsgWarning(content: string | MessageParams): void

示例:

typescript
// API 响应处理
async function handleApiResponse() {
  const [err, data] = await to(apiCall())
  
  if (err) {
    showMsgError('请求失败: ' + err.message)
    return
  }
  
  if (data.code === 200) {
    showMsgSuccess('操作成功')
  } else {
    showMsgWarning(data.message || '操作完成但有警告')
  }
}

🔔 弹窗提示

showAlert

显示一般信息弹窗。

typescript
showAlert(
  content: string | AlertOptions, 
  title?: string, 
  options?: ElMessageBoxOptions
): Promise<Result<MessageBoxData>>

示例:

typescript
// 基础用法
const [err, result] = await showAlert('系统将在5分钟后进行维护')
if (err) {
  console.log('用户取消了操作')
  return
}
console.log('用户确认了操作')

// 带标题的信息弹窗
const [err] = await showAlert('系统将在5分钟后进行维护', '维护通知')
if (!err) {
  proceedWithMaintenance()
}

// 使用配置对象自定义弹窗
const [err] = await showAlert({
  message: '系统将在5分钟后进行维护',
  title: '维护通知',
  closeOnClickModal: false,
  confirmButtonText: '我知道了'
})

showAlertError

显示错误信息弹窗。

typescript
showAlertError(
  content: string | AlertOptions, 
  title?: string, 
  options?: ElMessageBoxOptions
): Promise<Result<MessageBoxData>>

示例:

typescript
// 系统错误处理
const [err] = await showAlertError('系统遇到错误,请联系管理员')
if (!err) {
  contactAdmin()
}

// 自定义错误弹窗
const [err] = await showAlertError({
  message: '文件上传失败,请检查文件格式和大小',
  title: '上传错误',
  confirmButtonText: '重新上传'
})

showAlertSuccess / showAlertWarning

显示成功/警告信息弹窗,用法与 showAlertError 类似。

📨 通知提示

showNotify

显示一般信息通知。

typescript
showNotify(
  content: string | NotificationParams, 
  title?: string, 
  options?: NotificationParams
): void

示例:

typescript
// 简单通知
showNotify('新消息已送达')

// 带标题的通知
showNotify('您有一条新消息', '消息通知')

// 详细配置通知
showNotify({
  title: '系统通知',
  message: '您的账户余额不足,请及时充值',
  duration: 5000,
  position: 'top-right',
  showClose: true
})

showNotifyError / showNotifySuccess / showNotifyWarning

显示不同类型的通知,用法与 showNotify 类似。

示例:

typescript
// 文件操作反馈
class FileUploader {
  async uploadFile(file: File) {
    showNotify('开始上传文件...', '上传进度')
    
    const [err, result] = await to(this.doUpload(file))
    
    if (err) {
      showNotifyError('文件上传失败: ' + err.message, '上传失败')
      return
    }
    
    showNotifySuccess('文件上传成功', '上传完成', {
      duration: 3000,
      onClick: () => {
        // 点击通知查看文件
        this.viewUploadedFile(result.fileId)
      }
    })
  }
}

❓ 交互弹窗

showConfirm

显示确认对话框。

typescript
showConfirm(
  content: string | ConfirmOptions, 
  title?: string, 
  options?: ElMessageBoxOptions
): Promise<Result<MessageBoxData>>

示例:

typescript
// 基本确认
const [err] = await showConfirm('确定要删除这条记录吗?')
if (err) {
  console.log('用户取消了删除操作')
  return
}
// 用户确认了,执行删除
deleteRecord()

// 自定义标题和按钮文本
const [err, result] = await showConfirm('确定要删除这条记录吗?', '删除确认', {
  confirmButtonText: '是的,删除',
  cancelButtonText: '取消操作'
})
if (!err) {
  deleteRecord()
  console.log('删除操作结果:', result)
}

// 使用配置对象自定义确认框
const [err] = await showConfirm({
  message: '确定要执行此操作吗?',
  title: '操作确认',
  confirmButtonText: '确定执行',
  cancelButtonText: '放弃操作',
  type: 'warning',
  closeOnClickModal: false
})
if (!err) {
  executeOperation()
}

实际应用:

typescript
// 批量操作确认
async function batchDelete(selectedIds: number[]) {
  const [err] = await showConfirm(
    `确定要删除选中的 ${selectedIds.length} 条记录吗?此操作不可恢复。`,
    '批量删除确认',
    {
      confirmButtonText: '确定删除',
      cancelButtonText: '取消',
      type: 'warning'
    }
  )
  
  if (err) return
  
  showLoading('正在删除...')
  const [deleteErr] = await to(batchDeleteAPI(selectedIds))
  hideLoading()
  
  if (deleteErr) {
    showMsgError('删除失败: ' + deleteErr.message)
  } else {
    showMsgSuccess(`成功删除 ${selectedIds.length} 条记录`)
    refreshList()
  }
}

showPrompt

显示输入对话框。

typescript
showPrompt(
  content: string | PromptOptions, 
  title?: string, 
  options?: ElMessageBoxOptions
): Promise<Result<MessageBoxData>>

示例:

typescript
// 基本输入
const [err, result] = await showPrompt('请输入备注信息')
if (err) {
  console.log('用户取消了输入')
  return
}
// 用户输入的内容在 result.value 中
saveRemark(result.value)

// 自定义标题和验证
const [err, result] = await showPrompt('请输入备注信息', '添加备注', {
  inputPattern: /^.{1,50}$/,
  inputErrorMessage: '备注长度应在1-50个字符之间'
})
if (!err && result.value) {
  saveRemark(result.value)
}

// 使用配置对象自定义输入框
const [err, result] = await showPrompt({
  message: '请输入新的用户名',
  title: '修改用户名',
  inputPattern: /^[a-zA-Z0-9_]{3,20}$/,
  inputErrorMessage: '用户名只能包含字母、数字和下划线,长度3-20字符',
  confirmButtonText: '保存',
  cancelButtonText: '取消',
  inputPlaceholder: '请输入用户名...'
})
if (!err && result.value) {
  handleUsernameChange(result.value)
}

实际应用:

typescript
// 重命名功能
async function renameItem(itemId: number, currentName: string) {
  const [err, result] = await showPrompt({
    message: '请输入新名称',
    title: '重命名',
    inputValue: currentName,
    inputPattern: /^.{1,100}$/,
    inputErrorMessage: '名称不能为空且不超过100个字符',
    confirmButtonText: '确定',
    cancelButtonText: '取消'
  })
  
  if (err || !result.value) return
  
  if (result.value === currentName) {
    showMsgWarning('名称未发生变化')
    return
  }
  
  const [renameErr] = await to(renameItemAPI(itemId, result.value))
  if (renameErr) {
    showMsgError('重命名失败: ' + renameErr.message)
  } else {
    showMsgSuccess('重命名成功')
    refreshItemList()
  }
}

🔄 加载遮罩

showLoading

显示全局加载遮罩。

typescript
showLoading(content: string | CustomLoadingOptions): void

示例:

typescript
// 基本使用
showLoading('数据加载中...')

// 自定义配置
showLoading({
  text: '正在保存数据,请稍候...',
  background: 'rgba(0, 0, 0, 0.8)',
  spinner: 'el-icon-loading'
})

hideLoading

隐藏全局加载遮罩。

typescript
hideLoading(): void

示例:

typescript
// 在加载完成后隐藏遮罩
showLoading('正在提交数据')

const [err] = await to(submitData())
hideLoading()

if (err) {
  showMsgError('提交失败')
} else {
  showMsgSuccess('提交成功')
}

实际应用:

typescript
// 表单提交处理
class FormHandler {
  async submitForm(formData: any) {
    showLoading('正在提交表单...')
    
    try {
      const [err, response] = await to(this.apiSubmit(formData))
      
      if (err) {
        showMsgError('提交失败: ' + err.message)
        return false
      }
      
      if (response.code === 200) {
        showMsgSuccess('提交成功')
        return true
      } else {
        showMsgWarning(response.message || '提交完成但有警告')
        return false
      }
    } finally {
      hideLoading()
    }
  }
}

// 数据加载管理
class DataManager {
  async loadData() {
    showLoading({
      text: '正在加载数据...',
      background: 'rgba(0, 0, 0, 0.7)'
    })
    
    const [err, data] = await to(this.fetchData())
    hideLoading()
    
    if (err) {
      showAlertError('数据加载失败,请重试', '加载错误')
      return null
    }
    
    return data
  }
}

💡 实际应用场景

1. 用户操作反馈系统

typescript
class UserFeedbackManager {
  // 操作成功反馈
  static success(message: string, showNotification = false) {
    showMsgSuccess(message)
    
    if (showNotification) {
      showNotifySuccess(message, '操作成功', {
        duration: 3000
      })
    }
  }
  
  // 操作错误反馈
  static error(error: any, showDialog = false) {
    const message = error?.message || '操作失败'
    showMsgError(message)
    
    if (showDialog) {
      showAlertError(message, '错误提示')
    }
  }
  
  // 操作确认
  static async confirm(message: string, title = '确认操作'): Promise<boolean> {
    const [err] = await showConfirm(message, title)
    return !err
  }
  
  // 获取用户输入
  static async input(message: string, title = '请输入', validator?: RegExp): Promise<string | null> {
    const options: any = { message, title }
    
    if (validator) {
      options.inputPattern = validator
      options.inputErrorMessage = '输入格式不正确'
    }
    
    const [err, result] = await showPrompt(options)
    return err ? null : result.value
  }
}

// 使用示例
async function deleteUser(userId: number) {
  const confirmed = await UserFeedbackManager.confirm(
    '确定要删除这个用户吗?此操作不可恢复。'
  )
  
  if (!confirmed) return
  
  showLoading('正在删除用户...')
  const [err] = await to(deleteUserAPI(userId))
  hideLoading()
  
  if (err) {
    UserFeedbackManager.error(err, true)
  } else {
    UserFeedbackManager.success('用户删除成功', true)
  }
}

2. 表单验证与提交

typescript
class FormManager {
  constructor(private formRef: Ref<ElFormInstance>) {}
  
  async validateAndSubmit(submitHandler: () => Promise<any>) {
    // 表单验证
    const [validateErr, isValid] = await toValidate(this.formRef)
    if (validateErr || !isValid) {
      showMsgError(validateErr?.message || '表单验证失败')
      return false
    }
    
    // 确认提交
    const [confirmErr] = await showConfirm('确定要提交表单吗?')
    if (confirmErr) return false
    
    // 执行提交
    showLoading('正在提交表单...')
    
    try {
      const [submitErr, result] = await to(submitHandler())
      
      if (submitErr) {
        showAlertError('提交失败: ' + submitErr.message, '提交错误')
        return false
      }
      
      showMsgSuccess('表单提交成功')
      showNotifySuccess('数据已保存', '提交完成')
      return true
      
    } finally {
      hideLoading()
    }
  }
}

// 使用示例
const formManager = new FormManager(formRef)

async function handleSubmit() {
  const success = await formManager.validateAndSubmit(async () => {
    return submitFormAPI(formData.value)
  })
  
  if (success) {
    router.push('/success-page')
  }
}

3. 文件操作管理

typescript
class FileOperationManager {
  // 文件上传
  async uploadFile(file: File): Promise<boolean> {
    if (!this.validateFile(file)) {
      return false
    }
    
    showLoading(`正在上传文件: ${file.name}`)
    
    try {
      const [err, result] = await to(this.doUpload(file))
      
      if (err) {
        showNotifyError(`文件上传失败: ${err.message}`, '上传失败')
        return false
      }
      
      showNotifySuccess(`文件 ${file.name} 上传成功`, '上传完成', {
        duration: 5000,
        onClick: () => this.viewFile(result.fileId)
      })
      
      return true
      
    } finally {
      hideLoading()
    }
  }
  
  // 文件删除确认
  async deleteFile(fileName: string, fileId: string): Promise<boolean> {
    const [err] = await showConfirm(
      `确定要删除文件"${fileName}"吗?删除后无法恢复。`,
      '删除文件确认',
      {
        confirmButtonText: '删除',
        cancelButtonText: '取消',
        type: 'warning'
      }
    )
    
    if (err) return false
    
    showLoading('正在删除文件...')
    const [deleteErr] = await to(this.doDelete(fileId))
    hideLoading()
    
    if (deleteErr) {
      showMsgError('删除失败: ' + deleteErr.message)
      return false
    }
    
    showMsgSuccess('文件删除成功')
    return true
  }
  
  // 文件重命名
  async renameFile(fileId: string, currentName: string): Promise<string | null> {
    const [err, result] = await showPrompt({
      message: '请输入新的文件名',
      title: '重命名文件',
      inputValue: currentName,
      inputPattern: /^[^<>:"/\\|?*]+$/,
      inputErrorMessage: '文件名不能包含特殊字符',
      confirmButtonText: '重命名',
      cancelButtonText: '取消'
    })
    
    if (err || !result.value || result.value === currentName) {
      return null
    }
    
    showLoading('正在重命名文件...')
    const [renameErr] = await to(this.doRename(fileId, result.value))
    hideLoading()
    
    if (renameErr) {
      showMsgError('重命名失败: ' + renameErr.message)
      return null
    }
    
    showMsgSuccess('文件重命名成功')
    return result.value
  }
  
  private validateFile(file: File): boolean {
    const maxSize = 10 * 1024 * 1024 // 10MB
    const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']
    
    if (file.size > maxSize) {
      showMsgError('文件大小不能超过10MB')
      return false
    }
    
    if (!allowedTypes.includes(file.type)) {
      showMsgError('只支持JPG、PNG和PDF格式的文件')
      return false
    }
    
    return true
  }
  
  private async doUpload(file: File): Promise<any> {
    const formData = new FormData()
    formData.append('file', file)
    return uploadFileAPI(formData)
  }
  
  private async doDelete(fileId: string): Promise<void> {
    return deleteFileAPI(fileId)
  }
  
  private async doRename(fileId: string, newName: string): Promise<void> {
    return renameFileAPI(fileId, newName)
  }
  
  private viewFile(fileId: string): void {
    window.open(`/files/${fileId}`, '_blank')
  }
}

4. 数据操作工作流

typescript
class DataWorkflow {
  // 批量操作工作流
  async batchOperation(
    selectedItems: any[],
    operation: 'delete' | 'export' | 'archive',
    operationHandler: (items: any[]) => Promise<any>
  ) {
    if (selectedItems.length === 0) {
      showMsgWarning('请选择要操作的项目')
      return false
    }
    
    // 操作确认
    const operationNames = {
      delete: '删除',
      export: '导出',
      archive: '归档'
    }
    
    const operationName = operationNames[operation]
    const [confirmErr] = await showConfirm(
      `确定要${operationName}选中的 ${selectedItems.length} 个项目吗?`,
      `批量${operationName}确认`
    )
    
    if (confirmErr) return false
    
    // 执行操作
    showLoading(`正在${operationName},请稍候...`)
    
    try {
      const [err, result] = await to(operationHandler(selectedItems))
      
      if (err) {
        showAlertError(`批量${operationName}失败: ${err.message}`, `${operationName}错误`)
        return false
      }
      
      showMsgSuccess(`批量${operationName}成功`)
      showNotifySuccess(`已${operationName} ${selectedItems.length} 个项目`, `${operationName}完成`)
      
      return true
      
    } finally {
      hideLoading()
    }
  }
  
  // 数据导入工作流
  async importData(file: File): Promise<boolean> {
    // 文件验证
    if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.csv')) {
      showMsgError('只支持Excel和CSV格式的文件')
      return false
    }
    
    // 预览确认
    showLoading('正在解析文件...')
    const [parseErr, previewData] = await to(this.parseFile(file))
    hideLoading()
    
    if (parseErr) {
      showAlertError('文件解析失败: ' + parseErr.message, '解析错误')
      return false
    }
    
    // 显示预览并确认
    const [confirmErr] = await showConfirm(
      `检测到 ${previewData.length} 条数据,确定要导入吗?`,
      '导入数据确认'
    )
    
    if (confirmErr) return false
    
    // 执行导入
    showLoading('正在导入数据...')
    const [importErr, result] = await to(this.doImport(previewData))
    hideLoading()
    
    if (importErr) {
      showAlertError('数据导入失败: ' + importErr.message, '导入错误')
      return false
    }
    
    showNotifySuccess(
      `成功导入 ${result.successCount} 条数据,失败 ${result.failCount} 条`,
      '导入完成',
      {
        duration: 8000,
        onClick: () => this.showImportReport(result)
      }
    )
    
    return true
  }
  
  private async parseFile(file: File): Promise<any[]> {
    // 模拟文件解析
    return new Promise((resolve) => {
      setTimeout(() => resolve([]), 1000)
    })
  }
  
  private async doImport(data: any[]): Promise<any> {
    // 模拟数据导入
    return new Promise((resolve) => {
      setTimeout(() => resolve({
        successCount: data.length - 2,
        failCount: 2
      }), 2000)
    })
  }
  
  private showImportReport(result: any): void {
    // 显示导入报告
    console.log('导入报告:', result)
  }
}

🎨 与 Element Plus 的集成

表单验证结合

typescript
// 表单验证与模态框结合使用
const handleFormSubmit = async () => {
  // 使用 toValidate 进行表单验证
  const [validateErr, isValid] = await toValidate(formRef.value)
  
  if (validateErr || !isValid) {
    // 显示验证错误
    showAlertError(validateErr?.message || '表单填写有误,请检查', '验证失败')
    return
  }
  
  // 确认提交
  const [confirmErr] = await showConfirm('确定要提交表单吗?')
  if (confirmErr) return
  
  // 执行提交
  showLoading('正在提交...')
  const [submitErr] = await to(submitForm(formData.value))
  hideLoading()
  
  if (submitErr) {
    showAlertError('提交失败: ' + submitErr.message)
  } else {
    showMsgSuccess('提交成功')
  }
}

表格操作结合

typescript
// 表格操作与模态框结合
const handleTableRowAction = async (row: any, action: string) => {
  switch (action) {
    case 'edit':
      // 可以结合 showPrompt 进行快速编辑
      const [err, result] = await showPrompt(
        '请输入新名称',
        '编辑名称',
        { inputValue: row.name }
      )
      if (!err && result.value !== row.name) {
        await updateRowName(row.id, result.value)
      }
      break
      
    case 'delete':
      const [confirmErr] = await showConfirm(
        `确定要删除"${row.name}"吗?`,
        '删除确认'
      )
      if (!confirmErr) {
        await deleteRow(row.id)
      }
      break
  }
}

⚡ 性能优化建议

1. 避免频繁弹窗

typescript
// ❌ 不推荐:连续多个弹窗
async function badExample() {
  await showAlert('第一个提示')
  await showAlert('第二个提示')  
  await showAlert('第三个提示')
}

// ✅ 推荐:合并提示内容
async function goodExample() {
  await showAlert(`
    操作完成,请注意以下事项:
    1. 数据已保存
    2. 邮件通知已发送
    3. 相关人员已收到提醒
  `, '操作完成')
}

2. 合理使用加载状态

typescript
// ✅ 推荐:统一加载状态管理
class LoadingManager {
  private loadingCount = 0
  
  show(text: string = '加载中...') {
    if (this.loadingCount === 0) {
      showLoading(text)
    }
    this.loadingCount++
  }
  
  hide() {
    this.loadingCount--
    if (this.loadingCount <= 0) {
      this.loadingCount = 0
      hideLoading()
    }
  }
}

const loadingManager = new LoadingManager()

⚠️ 注意事项

  1. 异步处理:所有弹窗函数都是异步的,记得使用 await
  2. 错误处理:使用 Result 格式可以优雅地处理用户取消操作
  3. 用户体验:避免过度使用弹窗,影响用户体验
  4. 移动端适配:在移动设备上测试弹窗显示效果
  5. 国际化:考虑多语言环境下的文本显示