1518 lines
36 KiB
Markdown
1518 lines
36 KiB
Markdown
# Hooks 使用指南
|
||
|
||
本文档详细说明了项目中所有 Hooks 的使用方法,帮助开发者快速上手和正确使用。
|
||
|
||
## 目录
|
||
|
||
1. [useDict - 字典数据获取](#1-usedict---字典数据获取-hook)
|
||
2. [useParam - 参数数据获取](#2-useparam---参数数据获取-hook)
|
||
3. [useMessage - 消息提示](#3-usemessage---消息提示)
|
||
4. [useMessageBox - 消息确认框](#4-usemessagebox---消息确认框)
|
||
5. [useTable - 表格数据管理](#5-usetable---表格数据管理-hook)
|
||
|
||
---
|
||
|
||
## 1. useDict - 字典数据获取 Hook
|
||
|
||
**文件位置:** `src/hooks/dict.ts`
|
||
|
||
### 功能说明
|
||
|
||
用于获取字典数据,支持多个字典类型同时获取,具有自动缓存机制,避免重复请求。
|
||
|
||
### 基本用法
|
||
|
||
```typescript
|
||
import { useDict } from '/@/hooks/dict';
|
||
|
||
// 在 setup 中使用
|
||
const { dictType1, dictType2 } = useDict('dictType1', 'dictType2');
|
||
```
|
||
|
||
### 完整示例
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<!-- 使用字典数据 -->
|
||
<el-select v-model="form.status">
|
||
<el-option
|
||
v-for="item in userStatus"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
|
||
<el-select v-model="form.type">
|
||
<el-option
|
||
v-for="item in userType"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useDict } from '/@/hooks/dict';
|
||
|
||
// 获取多个字典类型
|
||
const { userStatus, userType, deptType } = useDict(
|
||
'user_status',
|
||
'user_type',
|
||
'dept_type'
|
||
);
|
||
|
||
const form = ref({
|
||
status: '',
|
||
type: ''
|
||
});
|
||
</script>
|
||
```
|
||
|
||
### 特点
|
||
|
||
- ✅ 支持传入多个字典类型参数
|
||
- ✅ 自动缓存机制,避免重复请求
|
||
- ✅ 返回响应式引用对象(使用 `toRefs`)
|
||
- ✅ 字典数据包含:`label`、`value`、`elTagType`、`elTagClass`
|
||
|
||
### 返回数据结构
|
||
|
||
```typescript
|
||
{
|
||
label: string, // 显示文本
|
||
value: string, // 值
|
||
elTagType: string, // 标签类型(用于 el-tag)
|
||
elTagClass: string // 标签样式类(用于 el-tag)
|
||
}
|
||
```
|
||
|
||
### 注意事项
|
||
|
||
- 字典类型参数为字符串类型
|
||
- 返回的对象是响应式的,可以直接在模板中使用
|
||
- 首次请求会从服务器获取,后续会使用缓存
|
||
|
||
---
|
||
|
||
## 2. useParam - 参数数据获取 Hook
|
||
|
||
**文件位置:** `src/hooks/param.ts`
|
||
|
||
### 功能说明
|
||
|
||
用于获取系统参数值,支持缓存机制,避免重复请求。
|
||
|
||
### 基本用法
|
||
|
||
```typescript
|
||
import { useParam } from '/@/hooks/param';
|
||
|
||
// 在 setup 中使用
|
||
const paramValue = useParam('paramType');
|
||
```
|
||
|
||
### 完整示例
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<div>最大上传大小:{{ uploadMaxSize }}</div>
|
||
<div>系统名称:{{ systemName }}</div>
|
||
<div>系统版本:{{ systemVersion }}</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useParam } from '/@/hooks/param';
|
||
|
||
// 获取单个参数
|
||
const uploadMaxSize = useParam('upload_max_size');
|
||
const systemName = useParam('system_name');
|
||
const systemVersion = useParam('system_version');
|
||
</script>
|
||
```
|
||
|
||
### 特点
|
||
|
||
- ✅ 单个参数类型获取
|
||
- ✅ 自动缓存机制
|
||
- ✅ 返回响应式引用(`ref`)
|
||
- ✅ 参数值类型为字符串
|
||
|
||
### 注意事项
|
||
|
||
- 每次调用只能获取一个参数类型
|
||
- 如需获取多个参数,需要多次调用
|
||
- 返回的是响应式引用,在模板中会自动解包
|
||
|
||
---
|
||
|
||
## 3. useMessage - 消息提示
|
||
|
||
**文件位置:** `src/hooks/message.ts`
|
||
|
||
### 功能说明
|
||
|
||
统一的消息提示功能,封装了 Element Plus 的 `ElMessage`,提供统一的配置和样式。
|
||
|
||
### 基本用法
|
||
|
||
```typescript
|
||
import { useMessage } from '/@/hooks/message';
|
||
|
||
// 在 setup 中使用
|
||
const message = useMessage();
|
||
```
|
||
|
||
### 方法列表
|
||
|
||
| 方法 | 说明 | 显示时间 |
|
||
|------|------|---------|
|
||
| `info(title: string)` | 普通提示 | 3秒 |
|
||
| `warning(title: string)` | 警告提示 | 3秒 |
|
||
| `success(title: string)` | 成功提示 | 3秒 |
|
||
| `error(title: string)` | 错误提示 | 2秒 |
|
||
|
||
### 完整示例
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<el-button @click="handleInfo">普通提示</el-button>
|
||
<el-button @click="handleWarning">警告提示</el-button>
|
||
<el-button @click="handleSuccess">成功提示</el-button>
|
||
<el-button @click="handleError">错误提示</el-button>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useMessage } from '/@/hooks/message';
|
||
|
||
const message = useMessage();
|
||
|
||
const handleInfo = () => {
|
||
message.info('这是一条普通提示信息');
|
||
};
|
||
|
||
const handleWarning = () => {
|
||
message.warning('这是一条警告提示信息');
|
||
};
|
||
|
||
const handleSuccess = () => {
|
||
message.success('操作成功!');
|
||
};
|
||
|
||
const handleError = () => {
|
||
message.error('操作失败,请重试!');
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 实际应用场景
|
||
|
||
```typescript
|
||
// 保存成功后提示
|
||
const handleSave = async () => {
|
||
try {
|
||
await saveData(formData);
|
||
message.success('保存成功!');
|
||
} catch (error) {
|
||
message.error('保存失败:' + error.message);
|
||
}
|
||
};
|
||
|
||
// 删除操作提示
|
||
const handleDelete = async () => {
|
||
try {
|
||
await deleteData(id);
|
||
message.success('删除成功!');
|
||
} catch (error) {
|
||
message.error('删除失败:' + error.message);
|
||
}
|
||
};
|
||
```
|
||
|
||
### 默认配置
|
||
|
||
- **显示时间:** 普通/警告/成功 3秒,错误 2秒
|
||
- **显示关闭按钮:** 是
|
||
- **距离顶部偏移:** 20px
|
||
|
||
### 注意事项
|
||
|
||
- 消息提示会自动消失
|
||
- 可以同时显示多条消息
|
||
- 错误提示显示时间较短,便于快速查看
|
||
|
||
---
|
||
|
||
## 4. useMessageBox - 消息确认框
|
||
|
||
**文件位置:** `src/hooks/message.ts`
|
||
|
||
### 功能说明
|
||
|
||
统一的消息确认框功能,封装了 Element Plus 的 `ElMessageBox`,支持提示框、确认框和输入框。
|
||
|
||
### 基本用法
|
||
|
||
```typescript
|
||
import { useMessageBox } from '/@/hooks/message';
|
||
|
||
// 在 setup 中使用
|
||
const messageBox = useMessageBox();
|
||
```
|
||
|
||
### 方法列表
|
||
|
||
| 方法 | 说明 | 返回值 |
|
||
|------|------|--------|
|
||
| `info(msg: string)` | 普通提示框 | `void` |
|
||
| `warning(msg: string)` | 警告提示框 | `void` |
|
||
| `success(msg: string)` | 成功提示框 | `void` |
|
||
| `error(msg: string)` | 错误提示框 | `void` |
|
||
| `confirm(msg: string)` | 确认对话框 | `Promise` |
|
||
| `prompt(msg: string)` | 输入对话框 | `Promise<{ value: string }>` |
|
||
|
||
### 完整示例
|
||
|
||
#### 4.1 提示框(Alert)
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { useMessageBox } from '/@/hooks/message';
|
||
|
||
const messageBox = useMessageBox();
|
||
|
||
// 普通提示框
|
||
const handleInfo = () => {
|
||
messageBox.info('这是一条信息提示');
|
||
};
|
||
|
||
// 警告提示框
|
||
const handleWarning = () => {
|
||
messageBox.warning('这是一条警告信息');
|
||
};
|
||
|
||
// 成功提示框
|
||
const handleSuccess = () => {
|
||
messageBox.success('操作成功!');
|
||
};
|
||
|
||
// 错误提示框
|
||
const handleError = () => {
|
||
messageBox.error('操作失败!');
|
||
};
|
||
</script>
|
||
```
|
||
|
||
#### 4.2 确认对话框(Confirm)
|
||
|
||
```vue
|
||
<template>
|
||
<el-button @click="handleDelete">删除</el-button>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useMessageBox } from '/@/hooks/message';
|
||
import { useMessage } from '/@/hooks/message';
|
||
|
||
const messageBox = useMessageBox();
|
||
const message = useMessage();
|
||
|
||
const handleDelete = async () => {
|
||
try {
|
||
// 显示确认对话框
|
||
await messageBox.confirm('确定要删除这条记录吗?删除后无法恢复!');
|
||
|
||
// 用户点击确认后执行
|
||
await deleteData(id);
|
||
message.success('删除成功!');
|
||
} catch {
|
||
// 用户点击取消,不执行任何操作
|
||
console.log('用户取消了删除操作');
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
#### 4.3 输入对话框(Prompt)
|
||
|
||
```vue
|
||
<template>
|
||
<el-button @click="handleRename">重命名</el-button>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useMessageBox } from '/@/hooks/message';
|
||
import { useMessage } from '/@/hooks/message';
|
||
|
||
const messageBox = useMessageBox();
|
||
const message = useMessage();
|
||
|
||
const handleRename = async () => {
|
||
try {
|
||
// 显示输入对话框
|
||
const { value } = await messageBox.prompt('请输入新名称');
|
||
|
||
// 用户输入了值并点击确认
|
||
if (value && value.trim()) {
|
||
await renameData(id, value);
|
||
message.success('重命名成功!');
|
||
} else {
|
||
message.warning('名称不能为空');
|
||
}
|
||
} catch {
|
||
// 用户点击取消
|
||
console.log('用户取消了重命名操作');
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 实际应用场景
|
||
|
||
```typescript
|
||
// 删除确认
|
||
const handleDelete = async (id: number) => {
|
||
try {
|
||
await messageBox.confirm('确定要删除这条记录吗?');
|
||
await deleteApi(id);
|
||
message.success('删除成功!');
|
||
getDataList();
|
||
} catch {
|
||
// 用户取消,不执行任何操作
|
||
}
|
||
};
|
||
|
||
// 批量删除确认
|
||
const handleBatchDelete = async () => {
|
||
if (selectedIds.length === 0) {
|
||
message.warning('请先选择要删除的记录');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await messageBox.confirm(`确定要删除选中的 ${selectedIds.length} 条记录吗?`);
|
||
await batchDeleteApi(selectedIds);
|
||
message.success('批量删除成功!');
|
||
getDataList();
|
||
} catch {
|
||
// 用户取消
|
||
}
|
||
};
|
||
|
||
// 重置密码确认
|
||
const handleResetPassword = async (userId: number) => {
|
||
try {
|
||
await messageBox.confirm('确定要重置该用户的密码吗?');
|
||
const result = await resetPasswordApi(userId);
|
||
messageBox.info(`重置后密码为:${result.data.password}`);
|
||
} catch {
|
||
// 用户取消
|
||
}
|
||
};
|
||
```
|
||
|
||
### 特点
|
||
|
||
- ✅ 支持国际化(自动使用 i18n)
|
||
- ✅ `confirm` 和 `prompt` 返回 Promise,便于链式调用
|
||
- ✅ 统一的按钮文本(确认/取消)
|
||
- ✅ 自动处理用户取消操作
|
||
|
||
### 注意事项
|
||
|
||
- `confirm` 和 `prompt` 返回 Promise,需要使用 `async/await` 或 `.then()/.catch()` 处理
|
||
- 用户点击取消会触发 Promise 的 `reject`,需要在 `catch` 中处理
|
||
- 提示框方法(`info`、`warning`、`success`、`error`)不返回 Promise
|
||
|
||
---
|
||
|
||
## 5. useTable - 表格数据管理 Hook
|
||
|
||
**文件位置:** `src/hooks/table.ts`
|
||
|
||
### 功能说明
|
||
|
||
提供完整的表格数据管理功能,包括数据加载、分页、排序、文件下载等,简化表格相关开发。
|
||
|
||
### 基本用法
|
||
|
||
```typescript
|
||
import { useTable } from '/@/hooks/table';
|
||
import { fetchList } from '/@/api/your-api';
|
||
|
||
const {
|
||
getDataList, // 获取数据列表方法
|
||
currentChangeHandle, // 页码改变处理方法
|
||
sizeChangeHandle, // 每页条数改变处理方法
|
||
sortChangeHandle, // 排序改变处理方法
|
||
downBlobFile, // 下载文件方法
|
||
tableStyle // 表格样式
|
||
} = useTable({
|
||
pageList: fetchList,
|
||
queryForm: {}
|
||
});
|
||
```
|
||
|
||
### 配置选项
|
||
|
||
| 属性 | 类型 | 默认值 | 说明 |
|
||
|------|------|--------|------|
|
||
| `createdIsNeed` | `boolean` | `true` | 是否在创建时自动加载数据 |
|
||
| `isPage` | `boolean` | `true` | 是否需要分页 |
|
||
| `queryForm` | `any` | `{}` | 查询条件表单对象 |
|
||
| `pageList` | `Function` | - | 数据列表查询接口(必填) |
|
||
| `pagination` | `Pagination` | 见下方 | 分页配置 |
|
||
| `props` | `object` | `{ item: 'records', totalCount: 'total' }` | 数据属性映射 |
|
||
| `validate` | `Function` | - | 验证函数 |
|
||
| `onLoaded` | `Function` | - | 数据加载完成回调 |
|
||
| `onCascaded` | `Function` | - | 级联数据回调 |
|
||
|
||
### 分页配置(Pagination)
|
||
|
||
```typescript
|
||
{
|
||
current: 1, // 当前页码
|
||
size: 10, // 每页显示条数
|
||
total: 0, // 总条数
|
||
pageSizes: [10, 20, 50, 100], // 每页条数选择器选项
|
||
layout: 'total, sizes, prev, pager, next, jumper' // 分页组件布局
|
||
}
|
||
```
|
||
|
||
### 完整示例
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<!-- 搜索表单 -->
|
||
<el-form :model="searchForm" inline>
|
||
<el-form-item label="姓名">
|
||
<el-input v-model="searchForm.name" placeholder="请输入姓名" />
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||
<el-button @click="handleReset">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 表格 -->
|
||
<el-table
|
||
:data="state.dataList"
|
||
:cell-style="tableStyle.cellStyle"
|
||
:header-cell-style="tableStyle.headerCellStyle"
|
||
@sort-change="sortChangeHandle"
|
||
v-loading="state.loading"
|
||
border
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" />
|
||
<el-table-column prop="name" label="姓名" sortable="custom" />
|
||
<el-table-column prop="status" label="状态" />
|
||
<el-table-column label="操作" width="200">
|
||
<template #default="scope">
|
||
<el-button type="primary" link size="small" @click="handleEdit(scope.row)">
|
||
编辑
|
||
</el-button>
|
||
<el-button type="danger" link size="small" @click="handleDelete(scope.row)">
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<pagination
|
||
:current="state.pagination.current"
|
||
:size="state.pagination.size"
|
||
:total="state.pagination.total"
|
||
@currentChange="currentChangeHandle"
|
||
@sizeChange="sizeChangeHandle"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, reactive } from 'vue';
|
||
import { useTable } from '/@/hooks/table';
|
||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||
import { fetchList, deleteData } from '/@/api/your-api';
|
||
|
||
// 消息提示
|
||
const message = useMessage();
|
||
const messageBox = useMessageBox();
|
||
|
||
// 搜索表单
|
||
const searchForm = reactive({
|
||
name: '',
|
||
status: ''
|
||
});
|
||
|
||
// 表格管理
|
||
const {
|
||
getDataList,
|
||
currentChangeHandle,
|
||
sizeChangeHandle,
|
||
sortChangeHandle,
|
||
tableStyle,
|
||
state
|
||
} = useTable({
|
||
// 查询条件
|
||
queryForm: searchForm,
|
||
|
||
// 数据列表查询接口
|
||
pageList: fetchList,
|
||
|
||
// 分页配置(可选)
|
||
pagination: {
|
||
current: 1,
|
||
size: 10,
|
||
total: 0,
|
||
pageSizes: [10, 20, 50, 100],
|
||
layout: 'total, sizes, prev, pager, next, jumper'
|
||
},
|
||
|
||
// 数据属性映射(可选,根据后端返回结构调整)
|
||
props: {
|
||
item: 'records', // 数据列表字段名
|
||
totalCount: 'total' // 总数字段名
|
||
},
|
||
|
||
// 验证函数(可选)
|
||
validate: async (state) => {
|
||
// 可以在这里进行表单验证
|
||
if (!searchForm.name && !searchForm.status) {
|
||
message.warning('请至少输入一个查询条件');
|
||
return false;
|
||
}
|
||
return true;
|
||
},
|
||
|
||
// 数据加载完成回调(可选)
|
||
onLoaded: async (state) => {
|
||
console.log('数据加载完成', state.dataList);
|
||
// 可以在这里处理加载完成后的逻辑
|
||
}
|
||
});
|
||
|
||
// 查询
|
||
const handleSearch = () => {
|
||
getDataList(); // 刷新并跳转到第一页
|
||
};
|
||
|
||
// 重置
|
||
const handleReset = () => {
|
||
searchForm.name = '';
|
||
searchForm.status = '';
|
||
getDataList();
|
||
};
|
||
|
||
// 编辑
|
||
const handleEdit = (row: any) => {
|
||
// 编辑逻辑
|
||
};
|
||
|
||
// 删除
|
||
const handleDelete = async (row: any) => {
|
||
try {
|
||
await messageBox.confirm('确定要删除这条记录吗?');
|
||
await deleteData(row.id);
|
||
message.success('删除成功!');
|
||
getDataList(false); // 刷新但不跳转第一页
|
||
} catch {
|
||
// 用户取消
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 返回的方法说明
|
||
|
||
#### getDataList(refresh?)
|
||
|
||
获取数据列表方法。
|
||
|
||
**参数:**
|
||
- `refresh` (可选): `boolean` - 是否刷新并跳转到第一页,默认为 `true`
|
||
|
||
**使用示例:**
|
||
```typescript
|
||
// 刷新并跳转到第一页(默认)
|
||
getDataList();
|
||
|
||
// 刷新但保持当前页
|
||
getDataList(false);
|
||
```
|
||
|
||
#### currentChangeHandle(val)
|
||
|
||
页码改变处理方法,用于分页组件的 `@currentChange` 事件。
|
||
|
||
**参数:**
|
||
- `val`: `number` - 新的页码
|
||
|
||
**使用示例:**
|
||
```vue
|
||
<pagination
|
||
@currentChange="currentChangeHandle"
|
||
/>
|
||
```
|
||
|
||
#### sizeChangeHandle(val)
|
||
|
||
每页条数改变处理方法,用于分页组件的 `@sizeChange` 事件。
|
||
|
||
**参数:**
|
||
- `val`: `number` - 新的每页条数
|
||
|
||
**使用示例:**
|
||
```vue
|
||
<pagination
|
||
@sizeChange="sizeChangeHandle"
|
||
/>
|
||
```
|
||
|
||
#### sortChangeHandle(column)
|
||
|
||
排序改变处理方法,用于表格的 `@sort-change` 事件。
|
||
|
||
**参数:**
|
||
- `column`: `object` - 排序列信息
|
||
- `prop`: 列属性名
|
||
- `order`: 排序方式(`ascending` | `descending` | `null`)
|
||
|
||
**使用示例:**
|
||
```vue
|
||
<el-table @sort-change="sortChangeHandle">
|
||
<el-table-column prop="name" sortable="custom" />
|
||
</el-table>
|
||
```
|
||
|
||
#### downBlobFile(url, query, fileName)
|
||
|
||
下载文件方法。
|
||
|
||
**参数:**
|
||
- `url`: `string` - 文件下载地址
|
||
- `query`: `object` - 请求参数
|
||
- `fileName`: `string` - 文件名
|
||
|
||
**使用示例:**
|
||
```typescript
|
||
const handleExport = () => {
|
||
downBlobFile(
|
||
'/api/export',
|
||
{ type: 'excel' },
|
||
'数据导出.xlsx'
|
||
);
|
||
};
|
||
```
|
||
|
||
#### tableStyle
|
||
|
||
表格样式配置对象。
|
||
|
||
**结构:**
|
||
```typescript
|
||
{
|
||
cellStyle: { textAlign: 'center' },
|
||
headerCellStyle: {
|
||
textAlign: 'center',
|
||
background: 'var(--el-table-row-hover-bg-color)',
|
||
color: 'var(--el-text-color-primary)'
|
||
}
|
||
}
|
||
```
|
||
|
||
**使用示例:**
|
||
```vue
|
||
<el-table
|
||
:cell-style="tableStyle.cellStyle"
|
||
:header-cell-style="tableStyle.headerCellStyle"
|
||
>
|
||
</el-table>
|
||
```
|
||
|
||
### State 对象说明
|
||
|
||
`useTable` 返回的 `state` 对象包含以下属性:
|
||
|
||
```typescript
|
||
{
|
||
dataList: [], // 表格数据列表
|
||
loading: false, // 加载状态
|
||
pagination: { // 分页信息
|
||
current: 1,
|
||
size: 10,
|
||
total: 0
|
||
},
|
||
dataListSelections: [], // 选中的数据
|
||
queryForm: {}, // 查询表单
|
||
// ... 其他配置属性
|
||
}
|
||
```
|
||
|
||
### 实际应用场景
|
||
|
||
#### 场景1:带验证的查询
|
||
|
||
```typescript
|
||
const { getDataList, state } = useTable({
|
||
queryForm: searchForm,
|
||
pageList: fetchList,
|
||
validate: async (state) => {
|
||
if (!searchForm.name && !searchForm.status) {
|
||
message.warning('请至少输入一个查询条件');
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
});
|
||
```
|
||
|
||
#### 场景2:数据加载后处理
|
||
|
||
```typescript
|
||
const { getDataList, state } = useTable({
|
||
queryForm: searchForm,
|
||
pageList: fetchList,
|
||
onLoaded: async (state) => {
|
||
// 数据加载完成后,处理字典转换
|
||
state.dataList = state.dataList.map(item => {
|
||
// 转换状态字典
|
||
const statusItem = statusDict.find(d => d.value === item.status);
|
||
item.statusName = statusItem?.label || '';
|
||
return item;
|
||
});
|
||
}
|
||
});
|
||
```
|
||
|
||
#### 场景3:无分页表格
|
||
|
||
```typescript
|
||
const { getDataList, state } = useTable({
|
||
queryForm: searchForm,
|
||
pageList: fetchList,
|
||
isPage: false, // 禁用分页
|
||
createdIsNeed: true
|
||
});
|
||
```
|
||
|
||
#### 场景4:自定义数据字段映射
|
||
|
||
```typescript
|
||
const { getDataList, state } = useTable({
|
||
queryForm: searchForm,
|
||
pageList: fetchList,
|
||
props: {
|
||
item: 'data.list', // 后端返回的数据列表路径
|
||
totalCount: 'data.total' // 后端返回的总数路径
|
||
}
|
||
});
|
||
```
|
||
|
||
### 弹窗编辑后刷新表格
|
||
|
||
在弹窗编辑/新增完成后,需要刷新表格数据。有两种方式:
|
||
|
||
#### 方式一:使用 `getDataList(false)`(推荐)
|
||
|
||
**父组件(列表页):**
|
||
|
||
```vue
|
||
<template>
|
||
<!-- 表格 -->
|
||
<el-table :data="state.dataList" v-loading="state.loading">
|
||
<!-- 表格列 -->
|
||
</el-table>
|
||
|
||
<!-- 弹窗组件,监听 refresh 事件 -->
|
||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { useTable } from '/@/hooks/table';
|
||
import { fetchList } from '/@/api/user';
|
||
|
||
const { getDataList, state, tableStyle } = useTable({
|
||
queryForm: {},
|
||
pageList: fetchList
|
||
});
|
||
|
||
const formDialogRef = ref();
|
||
</script>
|
||
```
|
||
|
||
**子组件(弹窗表单):**
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
const emit = defineEmits(['refresh']);
|
||
|
||
// 提交表单
|
||
const onSubmit = async () => {
|
||
const valid = await dataFormRef.value.validate().catch(() => {});
|
||
if (!valid) return false;
|
||
|
||
try {
|
||
loading.value = true;
|
||
form.id ? await putObj(form) : await addObj(form);
|
||
useMessage().success(form.id ? '修改成功' : '添加成功');
|
||
visible.value = false;
|
||
emit('refresh'); // 触发 refresh 事件
|
||
} catch (err: any) {
|
||
useMessage().error(err.msg);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
**说明:**
|
||
- `getDataList(false)` - 刷新数据但**不跳转到第一页**,保持当前页
|
||
- `getDataList()` 或 `getDataList(true)` - 刷新数据并**跳转到第一页**
|
||
|
||
#### 方式二:手动管理(不使用 useTable)
|
||
|
||
**父组件:**
|
||
|
||
```vue
|
||
<template>
|
||
<el-table :data="tableData" v-loading="tableLoading">
|
||
<!-- 表格列 -->
|
||
</el-table>
|
||
|
||
<form-dialog ref="formDialogRef" @refresh="handleRefresh" />
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
const tableData = ref([]);
|
||
const tableLoading = ref(false);
|
||
const page = reactive({
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
total: 0
|
||
});
|
||
|
||
// 刷新数据
|
||
const handleRefresh = () => {
|
||
getList(page);
|
||
};
|
||
|
||
const getList = (page: any) => {
|
||
tableLoading.value = true;
|
||
fetchList({
|
||
current: page.currentPage,
|
||
size: page.pageSize
|
||
}).then((response: any) => {
|
||
tableData.value = response.data.record.records;
|
||
page.total = response.data.record.total;
|
||
}).finally(() => {
|
||
tableLoading.value = false;
|
||
});
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 刷新时机的选择
|
||
|
||
| 场景 | 推荐方法 | 说明 |
|
||
|------|---------|------|
|
||
| **编辑后刷新** | `getDataList(false)` | 保持当前页,用户可以看到刚才编辑的数据 |
|
||
| **新增后刷新** | `getDataList()` | 跳转到第一页,因为新数据通常在第一页 |
|
||
| **删除后刷新** | `getDataList(false)` | 保持当前页,如果当前页没数据了会自动调整 |
|
||
| **查询后刷新** | `getDataList()` | 跳转到第一页,显示查询结果 |
|
||
|
||
### 完整示例
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<!-- 搜索表单 -->
|
||
<el-form :model="searchForm" inline>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="mb15">
|
||
<el-button type="primary" @click="handleAdd">新增</el-button>
|
||
</div>
|
||
|
||
<!-- 表格 -->
|
||
<el-table :data="state.dataList" v-loading="state.loading">
|
||
<el-table-column prop="name" label="姓名" />
|
||
<el-table-column label="操作">
|
||
<template #default="scope">
|
||
<el-button @click="handleEdit(scope.row)">编辑</el-button>
|
||
<el-button @click="handleDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<pagination
|
||
:current="state.pagination.current"
|
||
:size="state.pagination.size"
|
||
:total="state.pagination.total"
|
||
@currentChange="currentChangeHandle"
|
||
@sizeChange="sizeChangeHandle"
|
||
/>
|
||
|
||
<!-- 弹窗表单 -->
|
||
<form-dialog ref="formDialogRef" @refresh="handleFormRefresh" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive, ref } from 'vue';
|
||
import { useTable } from '/@/hooks/table';
|
||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||
import { fetchList, deleteData } from '/@/api/user';
|
||
|
||
const message = useMessage();
|
||
const messageBox = useMessageBox();
|
||
const formDialogRef = ref();
|
||
|
||
const searchForm = reactive({
|
||
name: ''
|
||
});
|
||
|
||
const {
|
||
getDataList,
|
||
currentChangeHandle,
|
||
sizeChangeHandle,
|
||
state
|
||
} = useTable({
|
||
queryForm: searchForm,
|
||
pageList: fetchList
|
||
});
|
||
|
||
// 查询
|
||
const handleSearch = () => {
|
||
getDataList(); // 查询后跳转到第一页
|
||
};
|
||
|
||
// 新增
|
||
const handleAdd = () => {
|
||
formDialogRef.value?.openDialog();
|
||
};
|
||
|
||
// 编辑
|
||
const handleEdit = (row: any) => {
|
||
formDialogRef.value?.openDialog(row.id);
|
||
};
|
||
|
||
// 删除
|
||
const handleDelete = async (row: any) => {
|
||
try {
|
||
await messageBox.confirm('确定要删除吗?');
|
||
await deleteData(row.id);
|
||
message.success('删除成功');
|
||
getDataList(false); // 删除后保持当前页
|
||
} catch {
|
||
// 用户取消
|
||
}
|
||
};
|
||
|
||
// 弹窗提交后的刷新处理
|
||
const handleFormRefresh = () => {
|
||
// 可以根据操作类型选择刷新方式
|
||
// 如果是新增,跳转到第一页
|
||
// 如果是编辑,保持当前页
|
||
getDataList(false); // 默认保持当前页
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 注意事项
|
||
|
||
- `pageList` 方法是必填的,需要传入数据查询接口函数
|
||
- `queryForm` 会在请求时自动合并到请求参数中
|
||
- `state.loading` 会自动管理,无需手动设置
|
||
- 排序功能会自动将驼峰命名转换为下划线命名(如 `userName` → `user_name`)
|
||
- 错误会自动通过 `ElMessage.error` 提示
|
||
- **弹窗提交成功后,记得 `emit('refresh')` 触发刷新事件**
|
||
- **根据业务场景选择合适的刷新方式**(是否跳转第一页)
|
||
|
||
---
|
||
|
||
## 6. useTable vs 正常使用 Table 对比
|
||
|
||
### 概述
|
||
|
||
`useTable` Hook 是对表格数据管理的封装,提供了统一的状态管理和方法,相比手动管理表格状态,具有明显的优势。
|
||
|
||
### 对比表格
|
||
|
||
| 对比项 | 使用 useTable Hook | 正常使用 Table(手动管理) |
|
||
|--------|-------------------|-------------------------|
|
||
| **代码量** | 少,配置化 | 多,需要手动编写大量代码 |
|
||
| **状态管理** | 自动管理(分页、loading、数据) | 需要手动管理所有状态 |
|
||
| **初始化** | 自动在 `onMounted` 时加载数据 | 需要手动在 `onMounted` 中调用 |
|
||
| **分页处理** | 自动处理页码和每页条数变化 | 需要手动实现 `currentChange` 和 `sizeChange` |
|
||
| **排序处理** | 自动处理排序,自动转换命名 | 需要手动实现排序逻辑 |
|
||
| **错误处理** | 自动错误提示 | 需要手动 try-catch 和错误提示 |
|
||
| **Loading 状态** | 自动管理 | 需要手动设置 `tableLoading.value = true/false` |
|
||
| **数据刷新** | 统一方法 `getDataList()` | 需要手动调用 `getList(page)` |
|
||
| **代码复用** | 高度复用,配置即可 | 每个页面都要重复编写 |
|
||
| **维护性** | 集中维护,修改一处即可 | 分散在各处,修改困难 |
|
||
| **统一性** | 所有表格行为一致 | 每个页面可能实现不同 |
|
||
|
||
### 代码对比示例
|
||
|
||
#### 使用 useTable Hook(推荐)
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { reactive } from 'vue';
|
||
import { useTable } from '/@/hooks/table';
|
||
import { fetchList } from '/@/api/user';
|
||
|
||
// 搜索表单
|
||
const searchForm = reactive({
|
||
name: '',
|
||
status: ''
|
||
});
|
||
|
||
// 一行配置,自动获得所有功能
|
||
const {
|
||
getDataList,
|
||
currentChangeHandle,
|
||
sizeChangeHandle,
|
||
sortChangeHandle,
|
||
tableStyle,
|
||
state
|
||
} = useTable({
|
||
queryForm: searchForm,
|
||
pageList: fetchList
|
||
});
|
||
|
||
// 查询 - 只需一行
|
||
const handleSearch = () => {
|
||
getDataList();
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<el-table
|
||
:data="state.dataList"
|
||
:cell-style="tableStyle.cellStyle"
|
||
:header-cell-style="tableStyle.headerCellStyle"
|
||
@sort-change="sortChangeHandle"
|
||
v-loading="state.loading"
|
||
>
|
||
<!-- 表格列 -->
|
||
</el-table>
|
||
|
||
<pagination
|
||
:current="state.pagination.current"
|
||
:size="state.pagination.size"
|
||
:total="state.pagination.total"
|
||
@currentChange="currentChangeHandle"
|
||
@sizeChange="sizeChangeHandle"
|
||
/>
|
||
</template>
|
||
```
|
||
|
||
**代码行数:约 30 行**
|
||
|
||
#### 正常使用 Table(手动管理)
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { ref, reactive, onMounted } from 'vue';
|
||
import { fetchList } from '/@/api/user';
|
||
import { ElMessage } from 'element-plus';
|
||
|
||
// 需要手动定义所有状态
|
||
const tableData = ref([]);
|
||
const tableLoading = ref(false);
|
||
const page = reactive({
|
||
currentPage: 1,
|
||
pageSize: 10,
|
||
total: 0
|
||
});
|
||
const params = ref({});
|
||
const search = reactive({
|
||
name: '',
|
||
status: ''
|
||
});
|
||
|
||
// 需要手动实现数据加载
|
||
const getList = (page: any) => {
|
||
tableLoading.value = true;
|
||
fetchList(Object.assign({
|
||
current: page.currentPage,
|
||
size: page.pageSize
|
||
}, params.value)).then((response: any) => {
|
||
tableData.value = response.data.record.records;
|
||
page.total = response.data.record.total;
|
||
tableLoading.value = false;
|
||
}).catch((error: any) => {
|
||
ElMessage.error(error.msg || '加载失败');
|
||
tableLoading.value = false;
|
||
});
|
||
};
|
||
|
||
// 需要手动实现分页处理
|
||
const currentChange = (val: number) => {
|
||
page.currentPage = val;
|
||
getList(page);
|
||
};
|
||
|
||
const handleSizeChange = (val: number) => {
|
||
page.pageSize = val;
|
||
page.currentPage = 1;
|
||
getList(page);
|
||
};
|
||
|
||
// 需要手动实现排序(如果需要)
|
||
const sortChangeHandle = (column: any) => {
|
||
// 手动实现排序逻辑...
|
||
getList(page);
|
||
};
|
||
|
||
// 需要手动在 onMounted 中调用
|
||
onMounted(() => {
|
||
getList(page);
|
||
});
|
||
|
||
// 查询
|
||
const handleSearch = () => {
|
||
params.value = { ...search };
|
||
page.currentPage = 1;
|
||
getList(page);
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<el-table
|
||
:data="tableData"
|
||
v-loading="tableLoading"
|
||
@sort-change="sortChangeHandle"
|
||
>
|
||
<!-- 表格列 -->
|
||
</el-table>
|
||
|
||
<pagination
|
||
:current="page.currentPage"
|
||
:size="page.pageSize"
|
||
:total="page.total"
|
||
@currentChange="currentChange"
|
||
@sizeChange="handleSizeChange"
|
||
/>
|
||
</template>
|
||
```
|
||
|
||
**代码行数:约 80+ 行**
|
||
|
||
### 主要优势总结
|
||
|
||
#### 1. **代码量减少 60%+**
|
||
- useTable:约 30 行代码
|
||
- 手动管理:约 80+ 行代码
|
||
- **减少约 50 行重复代码**
|
||
|
||
#### 2. **自动状态管理**
|
||
- ✅ 自动管理 `loading` 状态
|
||
- ✅ 自动管理分页状态(`current`、`size`、`total`)
|
||
- ✅ 自动管理数据列表
|
||
- ✅ 自动处理错误提示
|
||
|
||
#### 3. **自动初始化**
|
||
```typescript
|
||
// useTable 自动在 onMounted 时加载数据
|
||
onMounted(() => {
|
||
if (state.createdIsNeed) {
|
||
query(); // 自动调用
|
||
}
|
||
});
|
||
```
|
||
|
||
#### 4. **统一的方法接口**
|
||
- `getDataList()` - 统一的数据刷新方法
|
||
- `currentChangeHandle()` - 统一的分页处理
|
||
- `sizeChangeHandle()` - 统一的每页条数处理
|
||
- `sortChangeHandle()` - 统一的排序处理(自动转换命名)
|
||
|
||
#### 5. **统一的表格样式**
|
||
```typescript
|
||
// 所有使用 useTable 的表格样式一致
|
||
tableStyle: {
|
||
cellStyle: { textAlign: 'center' },
|
||
headerCellStyle: { textAlign: 'center', ... }
|
||
}
|
||
```
|
||
|
||
#### 6. **更好的维护性**
|
||
- 修改 Hook 代码,所有使用的地方自动生效
|
||
- 统一的错误处理逻辑
|
||
- 统一的 API 响应结构处理
|
||
|
||
#### 7. **扩展功能**
|
||
- ✅ 自动排序字段转换(驼峰 → 下划线)
|
||
- ✅ 支持验证函数
|
||
- ✅ 支持数据加载回调
|
||
- ✅ 支持文件下载方法
|
||
- ✅ 支持无分页模式
|
||
|
||
### 使用建议
|
||
|
||
#### ✅ 推荐使用 useTable 的场景
|
||
|
||
1. **标准 CRUD 页面** - 列表、分页、查询、排序
|
||
2. **数据展示页面** - 需要分页的数据表格
|
||
3. **需要统一风格的表格** - 保持项目一致性
|
||
4. **快速开发** - 减少重复代码,提高开发效率
|
||
|
||
#### ⚠️ 可以考虑手动管理的场景
|
||
|
||
1. **特殊业务逻辑** - 分页逻辑与标准差异很大
|
||
2. **性能优化需求** - 需要精细控制加载时机
|
||
3. **复杂的数据处理** - 需要大量自定义数据处理逻辑
|
||
4. **遗留代码** - 已有大量手动管理的代码,迁移成本高
|
||
|
||
### 迁移建议
|
||
|
||
如果现有代码使用手动管理,建议逐步迁移到 `useTable`:
|
||
|
||
1. **新页面**:直接使用 `useTable`
|
||
2. **旧页面**:在重构时逐步迁移
|
||
3. **复杂页面**:可以先迁移基础功能,保留特殊逻辑
|
||
|
||
### 总结
|
||
|
||
`useTable` Hook 通过封装常见的表格管理逻辑,显著减少了代码量,提高了开发效率和代码质量。对于大多数标准表格场景,**强烈推荐使用 `useTable`**。
|
||
|
||
---
|
||
|
||
## 综合使用示例
|
||
|
||
以下是一个完整的页面示例,展示如何综合使用所有 Hooks:
|
||
|
||
```vue
|
||
<template>
|
||
<div class="layout-padding">
|
||
<div class="layout-padding-auto layout-padding-view">
|
||
<!-- 搜索表单 -->
|
||
<el-form :model="searchForm" inline>
|
||
<el-form-item label="姓名">
|
||
<el-input v-model="searchForm.name" placeholder="请输入姓名" />
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-select v-model="searchForm.status" clearable>
|
||
<el-option
|
||
v-for="item in userStatus"
|
||
:key="item.value"
|
||
:label="item.label"
|
||
:value="item.value"
|
||
/>
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="handleSearch">查询</el-button>
|
||
<el-button @click="handleReset">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="mb15">
|
||
<el-button type="primary" icon="FolderAdd" @click="handleAdd">
|
||
新增
|
||
</el-button>
|
||
<el-button
|
||
type="warning"
|
||
plain
|
||
icon="Download"
|
||
@click="handleExport"
|
||
class="ml10">
|
||
导出
|
||
</el-button>
|
||
</div>
|
||
|
||
<!-- 表格 -->
|
||
<el-table
|
||
:data="state.dataList"
|
||
:cell-style="tableStyle.cellStyle"
|
||
:header-cell-style="tableStyle.headerCellStyle"
|
||
@sort-change="sortChangeHandle"
|
||
v-loading="state.loading"
|
||
border
|
||
>
|
||
<el-table-column type="index" label="序号" width="60" />
|
||
<el-table-column prop="name" label="姓名" sortable="custom" />
|
||
<el-table-column prop="status" label="状态">
|
||
<template #default="scope">
|
||
<el-tag v-for="item in userStatus.filter(s => s.value === scope.row.status)" :key="item.value">
|
||
{{ item.label }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="200">
|
||
<template #default="scope">
|
||
<el-button type="primary" link size="small" @click="handleEdit(scope.row)">
|
||
编辑
|
||
</el-button>
|
||
<el-button type="danger" link size="small" @click="handleDelete(scope.row)">
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<pagination
|
||
:current="state.pagination.current"
|
||
:size="state.pagination.size"
|
||
:total="state.pagination.total"
|
||
@currentChange="currentChangeHandle"
|
||
@sizeChange="sizeChangeHandle"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { reactive } from 'vue';
|
||
import { useDict } from '/@/hooks/dict';
|
||
import { useParam } from '/@/hooks/param';
|
||
import { useMessage, useMessageBox } from '/@/hooks/message';
|
||
import { useTable } from '/@/hooks/table';
|
||
import { fetchList, deleteData, exportData } from '/@/api/user';
|
||
|
||
// 1. 字典数据
|
||
const { userStatus } = useDict('user_status');
|
||
|
||
// 2. 参数数据
|
||
const systemName = useParam('system_name');
|
||
|
||
// 3. 消息提示
|
||
const message = useMessage();
|
||
const messageBox = useMessageBox();
|
||
|
||
// 4. 搜索表单
|
||
const searchForm = reactive({
|
||
name: '',
|
||
status: ''
|
||
});
|
||
|
||
// 5. 表格管理
|
||
const {
|
||
getDataList,
|
||
currentChangeHandle,
|
||
sizeChangeHandle,
|
||
sortChangeHandle,
|
||
downBlobFile,
|
||
tableStyle,
|
||
state
|
||
} = useTable({
|
||
queryForm: searchForm,
|
||
pageList: fetchList,
|
||
validate: async (state) => {
|
||
// 可以添加验证逻辑
|
||
return true;
|
||
},
|
||
onLoaded: async (state) => {
|
||
console.log('数据加载完成,共', state.dataList.length, '条');
|
||
}
|
||
});
|
||
|
||
// 查询
|
||
const handleSearch = () => {
|
||
getDataList();
|
||
};
|
||
|
||
// 重置
|
||
const handleReset = () => {
|
||
searchForm.name = '';
|
||
searchForm.status = '';
|
||
getDataList();
|
||
};
|
||
|
||
// 新增
|
||
const handleAdd = () => {
|
||
message.info('打开新增对话框');
|
||
// 新增逻辑
|
||
};
|
||
|
||
// 编辑
|
||
const handleEdit = (row: any) => {
|
||
message.info('打开编辑对话框');
|
||
// 编辑逻辑
|
||
};
|
||
|
||
// 删除
|
||
const handleDelete = async (row: any) => {
|
||
try {
|
||
await messageBox.confirm('确定要删除这条记录吗?删除后无法恢复!');
|
||
await deleteData(row.id);
|
||
message.success('删除成功!');
|
||
getDataList(false); // 刷新但不跳转第一页
|
||
} catch {
|
||
// 用户取消,不执行任何操作
|
||
}
|
||
};
|
||
|
||
// 导出
|
||
const handleExport = () => {
|
||
downBlobFile(
|
||
'/api/user/export',
|
||
searchForm,
|
||
`用户数据_${new Date().getTime()}.xlsx`
|
||
);
|
||
};
|
||
</script>
|
||
```
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 字典数据使用
|
||
|
||
- ✅ 在 `setup` 顶层调用 `useDict`,确保响应式
|
||
- ✅ 字典数据可以直接在模板中使用
|
||
- ✅ 使用 `el-tag` 时可以利用 `elTagType` 和 `elTagClass`
|
||
|
||
### 2. 消息提示使用
|
||
|
||
- ✅ 操作成功使用 `message.success()`
|
||
- ✅ 操作失败使用 `message.error()`
|
||
- ✅ 需要用户确认的操作使用 `messageBox.confirm()`
|
||
- ✅ 删除等危险操作必须使用确认框
|
||
|
||
### 3. 表格管理使用
|
||
|
||
- ✅ 查询条件变化时调用 `getDataList()` 刷新数据
|
||
- ✅ 删除、新增等操作后调用 `getDataList(false)` 保持当前页
|
||
- ✅ 使用 `validate` 进行查询条件验证
|
||
- ✅ 使用 `onLoaded` 处理数据加载后的逻辑
|
||
|
||
### 4. 错误处理
|
||
|
||
- ✅ 所有异步操作使用 `try-catch` 处理错误
|
||
- ✅ 使用 `message.error()` 提示错误信息
|
||
- ✅ 用户取消操作不需要提示错误
|
||
|
||
---
|
||
|
||
## 更新记录
|
||
|
||
- **2024-XX-XX**: 创建 Hooks 使用指南文档
|
||
- 文档版本:v1.0
|
||
|
||
---
|
||
|
||
**维护者:** 前端开发团队
|
||
**最后更新:** 2024年
|
||
|