Skip to content

Hooks 业务钩子

提供常用业务场景的 Vue 3 Composition API 钩子函数,简化开发流程。

🎮 在线演示

想要查看 Hooks 的使用效果?请访问 在线演示 查看功能演示

特性

🎯 业务导向

专注于常见业务场景的功能封装

📦 开箱即用

无需配置,导入即可使用

⚡ 状态管理

内置加载、进度等状态管理

🔧 灵活扩展

支持自定义配置和扩展

存储相关

useLocalStorage

操作 localStorage 的钩子函数。

js
import { useLocalStorage } from 'imtes-ui'

const { get, set, remove } = useLocalStorage('userSettings', {})

// 获取数据
const data = get()

// 设置数据  
set({ theme: 'dark', language: 'zh-CN' })

// 删除数据
remove()

useSessionStorage

操作 sessionStorage 的钩子函数。

js
import { useSessionStorage } from 'imtes-ui'

const { get, set, remove } = useSessionStorage('tempData', null)

// 获取数据
const data = get()

// 设置数据
set({ token: 'abc123' })

// 删除数据
remove()

工具函数

useThrottle

节流函数钩子。

js
import { useThrottle } from 'imtes-ui'

const throttledFn = useThrottle(() => {
  console.log('执行函数')
}, 300)

// 在300ms内多次调用,只会执行一次
throttledFn()

useDebounce

防抖函数钩子。

查看代码
vue
<template>
  <div>
    <el-input 
      v-model="searchText"
      placeholder="输入搜索关键词"
      @input="handleSearch"
    />
    <div v-if="isSearching" class="mt-2 text-gray-500">
      正在搜索...
    </div>
    <div class="mt-4">
      <div v-for="item in searchResults" :key="item.id" class="p-2 border-b">
        {{ item.name }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useDebounce } from 'imtes-ui'

const searchText = ref('')
const searchResults = ref([])
const isSearching = ref(false)

const { debouncedFunction: debouncedSearch } = useDebounce(
  async (query) => {
    if (!query) {
      searchResults.value = []
      return
    }
    
    isSearching.value = true
    try {
      // 模拟API调用
      const response = await fetch(`/api/search?q=${query}`)
      searchResults.value = await response.json()
    } finally {
      isSearching.value = false
    }
  },
  500 // 500ms防抖
)

const handleSearch = (value) => {
  debouncedSearch(value)
}
</script>

API:

js
const { debouncedFunction, cancel } = useDebounce(fn, delay)

参数:

  • fn: 要防抖的函数
  • delay: 防抖延迟时间(毫秒)

返回值:

  • debouncedFunction: 防抖后的函数
  • cancel: 取消防抖的函数

网络相关

useNetworkStatus

网络状态检测钩子。

js
import { useNetworkStatus } from 'imtes-ui'

const { isOnline } = useNetworkStatus()

// 监听网络状态
watch(isOnline, (online) => {
  if (online) {
    console.log('网络已连接')
  } else {
    console.log('网络已断开')
  }
})

业务组件 Hook

useTable

表格业务逻辑钩子。

查看代码
vue
<template>
  <div>
    <im-el-table
      :data="tableData"
      :loading="loading"
      :columns="columns"
      :current-page="currentPage"
      :page-size="pageSize"
      :total="total"
      @page-change="handlePageChange"
      @sort-change="handleSortChange"
    />
  </div>
</template>

<script setup>
import { useTable } from 'imtes-ui'

const {
  tableData,
  loading,
  currentPage,
  pageSize,
  total,
  fetchData,
  refresh,
  search,
  reset,
  handlePageChange,
  handleSortChange
} = useTable({
  api: '/api/users',
  pageSize: 10
})

// 搜索
const handleSearch = (params) => {
  search(params)
}

// 刷新
const handleRefresh = () => {
  refresh()
}
</script>

API:

js
const tableHook = useTable(options)

参数:

  • options.api: API 接口地址
  • options.pageSize: 每页数量,默认 10
  • options.params: 默认查询参数

返回值:

  • tableData: 表格数据
  • loading: 加载状态
  • currentPage: 当前页码
  • pageSize: 每页数量
  • total: 总数量
  • fetchData: 获取数据函数
  • refresh: 刷新数据函数
  • search: 搜索函数
  • reset: 重置函数
  • handlePageChange: 页码变化处理
  • handleSortChange: 排序变化处理

useForm

表单业务逻辑钩子。

查看代码
vue
<template>
  <el-form ref="formRef" :model="formData" :rules="rules">
    <el-form-item label="用户名" prop="username">
      <el-input v-model="formData.username" />
    </el-form-item>
    <el-form-item label="邮箱" prop="email">
      <el-input v-model="formData.email" />
    </el-form-item>
    <el-form-item>
      <el-button type="primary" :loading="submitting" @click="handleSubmit">
        提交
      </el-button>
      <el-button @click="handleReset">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup>
import { useForm } from 'imtes-ui'

const { 
  formRef, 
  formData, 
  submitting, 
  validate, 
  resetForm, 
  submit 
} = useForm({
  initialData: {
    username: '',
    email: ''
  },
  submitApi: '/api/users'
})

const rules = {
  username: [{ required: true, message: '请输入用户名' }],
  email: [{ required: true, message: '请输入邮箱' }]
}

const handleSubmit = async () => {
  const isValid = await validate()
  if (isValid) {
    await submit()
  }
}

const handleReset = () => {
  resetForm()
}
</script>

API:

js
const formHook = useForm(options)

参数:

  • options.initialData: 初始数据
  • options.submitApi: 提交接口地址

返回值:

  • formRef: 表单引用
  • formData: 表单数据
  • submitting: 提交状态
  • validate: 验证函数
  • resetForm: 重置函数
  • submit: 提交函数

useDialog

弹窗业务逻辑钩子。

js
import { useDialog } from 'imtes-ui'

const {
  visible,
  open,
  close,
  confirm,
  cancel
} = useDialog({
  onConfirm: async () => {
    // 确认逻辑
    console.log('确认操作')
    return true // 返回 true 关闭弹窗
  }
})

// 打开弹窗
const handleOpen = () => {
  open()
}

文件操作

useDownload

文件下载钩子。

查看代码
vue
<template>
  <div>
    <ImButton 
      type="info" 
      :loading="downloading" 
      @click="handleDownload"
    >
      下载文件
    </ImButton>
    
    <div v-if="progress > 0" class="mt-3">
      <div class="text-sm text-gray-500 mb-1">
        下载进度: {{ progress }}%
      </div>
      <el-progress :percentage="progress" />
    </div>
  </div>
</template>

<script setup>
import { useDownload } from 'imtes-ui'

const { 
  downloading, 
  progress, 
  error, 
  download 
} = useDownload()

const handleDownload = async () => {
  try {
    await download('https://example.com/file.pdf', 'document.pdf')
  } catch (err) {
    console.error('下载失败:', err)
  }
}
</script>

useUpload

文件上传钩子。

js
import { useUpload } from 'imtes-ui'

const { 
  uploading, 
  progress, 
  result, 
  error, 
  upload 
} = useUpload()

const handleUpload = async (file) => {
  try {
    const response = await upload(file, '/api/upload')
    console.log('上传成功:', response)
  } catch (err) {
    console.error('上传失败:', err)
  }
}

useExport

数据导出钩子。

js
import { useExport } from 'imtes-ui'

const { 
  exporting, 
  progress, 
  exportData 
} = useExport()

const handleExport = async () => {
  const data = [
    { name: '张三', age: 25, city: '北京' },
    { name: '李四', age: 30, city: '上海' }
  ]
  
  await exportData(data, {
    filename: '用户数据.xlsx',
    headers: ['姓名', '年龄', '城市']
  })
}

usePrint

打印功能钩子。

js
import { usePrint } from 'imtes-ui'

const { print } = usePrint()

const handlePrint = () => {
  // 打印指定元素
  print('#printContent')
  
  // 或打印整个页面
  print()
}

usePreview

文件预览钩子。

查看代码
vue
<template>
  <div>
    <ImButton @click="handlePreview">预览文件</ImButton>
    
    <ImDialog v-model="previewVisible" title="文件预览" width="80%">
      <div v-if="loading" class="text-center">
        <el-icon class="is-loading"><Loading /></el-icon>
        <div class="mt-2">正在加载...</div>
      </div>
      
      <div v-else-if="error" class="text-center text-red-500">
        {{ error }}
      </div>
      
      <div v-else class="preview-content">
        <img v-if="isImage" :src="previewUrl" class="max-w-full h-auto" />
        <iframe v-else-if="isPdf" :src="previewUrl" class="w-full h-96"></iframe>
        <div v-else class="text-center text-gray-500">
          不支持预览此文件类型
        </div>
      </div>
    </ImDialog>
  </div>
</template>

<script setup>
import { usePreview } from 'imtes-ui'

const {
  previewVisible,
  loading,
  error,
  previewUrl,
  isImage,
  isPdf,
  preview,
  close
} = usePreview()

const handlePreview = async () => {
  await preview('https://example.com/document.pdf')
}
</script>

API 测试

useTestApi

API 接口测试钩子。

查看代码
vue
<template>
  <div class="api-test">
    <div class="mb-4">
      <el-select v-model="method" placeholder="请求方法">
        <el-option label="GET" value="GET" />
        <el-option label="POST" value="POST" />
        <el-option label="PUT" value="PUT" />
        <el-option label="DELETE" value="DELETE" />
      </el-select>
      
      <el-input 
        v-model="url" 
        placeholder="请输入API地址"
        class="ml-2"
        style="width: 300px"
      />
      
      <ImButton 
        type="primary" 
        :loading="testing" 
        @click="handleTest"
        class="ml-2"
      >
        测试
      </ImButton>
    </div>
    
    <div class="mb-4">
      <h4>请求参数:</h4>
      <el-input 
        v-model="params" 
        type="textarea" 
        :rows="4"
        placeholder="JSON格式参数"
      />
    </div>
    
    <div v-if="result" class="result">
      <h4>响应结果:</h4>
      <pre class="bg-gray-100 p-4 rounded">{{ JSON.stringify(result, null, 2) }}</pre>
    </div>
    
    <div v-if="error" class="error text-red-500">
      <h4>错误信息:</h4>
      <pre class="bg-red-50 p-4 rounded">{{ error }}</pre>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useTestApi } from 'imtes-ui'

const {
  testing,
  result,
  error,
  testApi
} = useTestApi()

const method = ref('GET')
const url = ref('')
const params = ref('')

const handleTest = async () => {
  try {
    const requestParams = params.value ? JSON.parse(params.value) : {}
    await testApi({
      method: method.value,
      url: url.value,
      data: requestParams
    })
  } catch (err) {
    console.error('测试失败:', err)
  }
}
</script>

工具类 Hook

usePermission

权限检查钩子。

js
import { usePermission } from 'imtes-ui'

const userPermissions = ['user:read', 'user:write', 'admin:read']

const { hasPermission, hasAllPermissions } = usePermission(
  ['user:read', 'user:write'], 
  userPermissions
)

console.log(hasPermission.value) // true (有其中一个权限)
console.log(hasAllPermissions.value) // true (有所有权限)

useFormatter

数据格式化钩子。

js
import { useFormatter } from 'imtes-ui'

const {
  formatFileSize,
  formatMoney,
  formatDate,
  formatPhone,
  formatIdCard
} = useFormatter()

console.log(formatFileSize(1024)) // "1 KB"
console.log(formatMoney(1234.56)) // "¥1,234.56"
console.log(formatDate(new Date())) // "2024-01-15 10:30:45"
console.log(formatPhone('13800138000')) // "138****8000"
console.log(formatIdCard('123456789012345678')) // "123456********5678"

useClipboard

剪贴板操作钩子。

js
import { useClipboard } from 'imtes-ui'

const { copy, read } = useClipboard()

// 复制文本
const handleCopy = async () => {
  const success = await copy('要复制的文本')
  if (success) {
    console.log('复制成功')
  }
}

// 读取剪贴板
const handleRead = async () => {
  const text = await read()
  console.log('剪贴板内容:', text)
}

useFullscreen

全屏操作钩子。

js
import { useFullscreen } from 'imtes-ui'

const { isFullscreen, enter, exit, toggle } = useFullscreen('#app')

// 进入全屏
const handleEnter = () => {
  enter()
}

// 退出全屏
const handleExit = () => {
  exit()
}

// 切换全屏
const handleToggle = () => {
  toggle()
}

useConfirm

确认对话框钩子。

js
import { useConfirm } from 'imtes-ui'

const { confirm, alert, prompt } = useConfirm()

// 确认对话框
const handleDelete = async () => {
  const result = await confirm('确定要删除这条记录吗?', '删除确认')
  if (result) {
    // 执行删除操作
    console.log('已确认删除')
  }
}

// 警告对话框
const handleAlert = async () => {
  await alert('操作成功!', '提示')
}

// 输入对话框
const handlePrompt = async () => {
  const result = await prompt('请输入新的名称', '重命名')
  if (result) {
    console.log('新名称:', result)
  }
}

useTitle

页面标题管理钩子。

js
import { useTitle } from 'imtes-ui'

const { title, setTitle, resetTitle } = useTitle('默认标题')

// 设置标题
setTitle('新的页面标题')

// 重置标题
resetTitle()

使用示例

完整的业务场景

结合多个 Hook 实现完整的用户管理页面:

查看完整示例
vue
<template>
  <div class="user-management">
    <!-- 搜索表单 -->
    <div class="search-form mb-4">
      <el-form inline>
        <el-form-item label="用户名">
          <el-input 
            v-model="searchForm.username" 
            placeholder="请输入用户名"
            @input="handleSearch"
          />
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="searchForm.status" placeholder="请选择状态">
            <el-option label="启用" value="1" />
            <el-option label="禁用" value="0" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <ImButton type="primary" @click="handleRefresh">刷新</ImButton>
          <ImButton @click="handleReset">重置</ImButton>
        </el-form-item>
      </el-form>
    </div>

    <!-- 操作按钮 -->
    <div class="actions mb-4">
      <ImButton type="primary" @click="handleAdd">新增用户</ImButton>
      <ImButton 
        type="success" 
        :loading="exporting" 
        @click="handleExport"
      >
        导出数据
      </ImButton>
    </div>

    <!-- 用户表格 -->
    <im-el-table
      :data="tableData"
      :loading="loading"
      :columns="columns"
      :current-page="currentPage"
      :page-size="pageSize"
      :total="total"
      @page-change="handlePageChange"
      @sort-change="handleSortChange"
    >
      <template #actions="{ row }">
        <ImButton size="small" @click="handleEdit(row)">编辑</ImButton>
        <ImButton 
          size="small" 
          type="danger" 
          @click="handleDelete(row)"
        >
          删除
        </ImButton>
      </template>
    </im-el-table>

    <!-- 用户表单弹窗 -->
    <ImDialog v-model="formVisible" :title="isEdit ? '编辑用户' : '新增用户'">
      <el-form ref="formRef" :model="formData" :rules="rules" label-width="80px">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="formData.username" />
        </el-form-item>
        <el-form-item label="邮箱" prop="email">
          <el-input v-model="formData.email" />
        </el-form-item>
        <el-form-item label="手机号" prop="phone">
          <el-input v-model="formData.phone" />
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="formData.status">
            <el-radio :label="1">启用</el-radio>
            <el-radio :label="0">禁用</el-radio>
          </el-radio-group>
        </el-form-item>
      </el-form>
      
      <template #footer>
        <ImButton @click="formVisible = false">取消</ImButton>
        <ImButton 
          type="primary" 
          :loading="submitting" 
          @click="handleSubmit"
        >
          确定
        </ImButton>
      </template>
    </ImDialog>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { 
  useTable, 
  useForm, 
  useDialog, 
  useConfirm, 
  useExport,
  useDebounce 
} from 'imtes-ui'

// 表格相关
const {
  tableData,
  loading,
  currentPage,
  pageSize,
  total,
  fetchData,
  refresh,
  search,
  reset,
  handlePageChange,
  handleSortChange
} = useTable({
  api: '/api/users',
  pageSize: 10
})

// 表单相关
const {
  formRef,
  formData,
  submitting,
  validate,
  resetForm,
  submit
} = useForm({
  initialData: {
    username: '',
    email: '',
    phone: '',
    status: 1
  }
})

// 弹窗相关
const { visible: formVisible, open: openForm, close: closeForm } = useDialog()

// 确认对话框
const { confirm } = useConfirm()

// 导出相关
const { exporting, exportData } = useExport()

// 搜索表单
const searchForm = reactive({
  username: '',
  status: ''
})

// 防抖搜索
const { debouncedFunction: debouncedSearch } = useDebounce((params) => {
  search(params)
}, 300)

const isEdit = ref(false)

// 表格列配置
const columns = [
  { field: 'id', title: 'ID', width: 80 },
  { field: 'username', title: '用户名' },
  { field: 'email', title: '邮箱' },
  { field: 'phone', title: '手机号' },
  { field: 'status', title: '状态', slots: { default: 'status' } },
  { field: 'actions', title: '操作', slots: { default: 'actions' } }
]

// 表单验证规则
const rules = {
  username: [{ required: true, message: '请输入用户名' }],
  email: [
    { required: true, message: '请输入邮箱' },
    { type: 'email', message: '请输入正确的邮箱格式' }
  ],
  phone: [
    { required: true, message: '请输入手机号' },
    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
  ]
}

// 搜索
const handleSearch = () => {
  debouncedSearch(searchForm)
}

// 刷新
const handleRefresh = () => {
  refresh()
}

// 重置
const handleReset = () => {
  Object.assign(searchForm, { username: '', status: '' })
  reset()
}

// 新增
const handleAdd = () => {
  isEdit.value = false
  resetForm()
  openForm()
}

// 编辑
const handleEdit = (row) => {
  isEdit.value = true
  Object.assign(formData, row)
  openForm()
}

// 删除
const handleDelete = async (row) => {
  const result = await confirm(`确定要删除用户"${row.username}"吗?`, '删除确认')
  if (result) {
    // 调用删除API
    console.log('删除用户:', row.id)
    refresh()
  }
}

// 提交表单
const handleSubmit = async () => {
  const isValid = await validate()
  if (isValid) {
    await submit()
    closeForm()
    refresh()
  }
}

// 导出数据
const handleExport = async () => {
  await exportData(tableData.value, {
    filename: '用户数据.xlsx',
    headers: ['ID', '用户名', '邮箱', '手机号', '状态']
  })
}
</script>

这个示例展示了如何组合使用多个 Hook 来构建一个完整的用户管理页面,包括:

  • 使用 useTable 管理表格状态和分页
  • 使用 useForm 管理表单状态和验证
  • 使用 useDialog 管理弹窗状态
  • 使用 useConfirm 处理确认操作
  • 使用 useExport 实现数据导出
  • 使用 useDebounce 实现搜索防抖

通过这些 Hook 的组合,可以大大简化业务逻辑的实现,提高开发效率。

基于 MIT 许可发布