# 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年

