This commit is contained in:
guochunsi
2025-12-31 17:40:01 +08:00
parent 6d94e91b70
commit 74c06bb8a0
713 changed files with 115034 additions and 46 deletions

1517
docs/hooks使用指南.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,516 @@
# useTable 与 search-form 组件兼容使用说明
## 概述
`useTable` Hook **完全兼容**自定义 `<search-form>` 组件。只需要将搜索表单的数据对象作为 `queryForm` 传入即可。
## 兼容原理
1. **`search-form` 组件**:只是一个表单包装器,接收 `model` prop 绑定到内部的 `el-form`
2. **`useTable` Hook**:接收 `queryForm` 对象,会自动将其合并到 API 请求参数中
3. **两者配合**:将 `search-form``model` 对象作为 `useTable``queryForm` 传入即可
## 改造示例
### 当前代码(手动管理)
```vue
<template>
<!-- 搜索表单 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter(search)"
>
<!-- 表单项 -->
</search-form>
<!-- 表格 -->
<el-table :data="tableData" v-loading="tableLoading">
<!-- 表格列 -->
</el-table>
<!-- 分页 -->
<pagination
:current="page.currentPage"
:size="page.pageSize"
:total="page.total"
@currentChange="currentChange"
@sizeChange="handleSizeChange"
/>
</template>
<script setup lang="ts">
const search = reactive({
deptCode: '',
realName: '',
teacherNo: '',
// ... 其他字段
})
const tableData = ref([])
const tableLoading = ref(false)
const page = reactive({
currentPage: 1,
pageSize: 10,
total: 0
})
const params = ref<any>({})
// 手动实现数据加载
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
})
}
// 手动实现分页
const currentChange = (val: number) => {
page.currentPage = val
getList(page)
}
const handleSizeChange = (val: number) => {
page.pageSize = val
page.currentPage = 1
getList(page)
}
// 查询
const handleFilter = (param: any) => {
params.value = { ...param }
page.currentPage = 1
getList(page)
}
onMounted(() => {
getList(page)
})
</script>
```
### 改造后代码(使用 useTable
```vue
<template>
<!-- 搜索表单 - 保持不变 -->
<search-form
v-show="showSearch"
:model="search"
ref="searchFormRef"
@keyup-enter="handleFilter(search)"
>
<!-- 表单项 -->
</search-form>
<!-- 表格 - 使用 state.dataList state.loading -->
<el-table
:data="state.dataList"
v-loading="state.loading"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
>
<!-- 表格列 -->
</el-table>
<!-- 分页 - 使用 state.pagination -->
<pagination
:current="state.pagination.current"
:size="state.pagination.size"
:total="state.pagination.total"
@currentChange="currentChangeHandle"
@sizeChange="sizeChangeHandle"
/>
</template>
<script setup lang="ts">
import { BasicTableProps, useTable } from '/@/hooks/table'
import { fetchList } from '/@/api/professional/teacherbase'
// 搜索表单数据 - 保持不变
const search = reactive({
deptCode: '',
realName: '',
teacherNo: '',
// ... 其他字段
})
// 额外参数(如 flag 等)
const params = ref<any>({})
// 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
// 将 search 对象作为 queryForm
queryForm: search,
// 自定义 pageList 方法,合并额外参数
pageList: async (queryParams: any) => {
// 合并 search 和 params 中的额外参数
return await fetchList({
...queryParams,
...params.value
})
},
// 数据属性映射(根据后端返回结构调整)
props: {
item: 'record.records', // 数据列表路径
totalCount: 'record.total' // 总数字段路径
},
// 数据加载完成后的回调
onLoaded: async (state) => {
// 数据转换逻辑
state.dataList = convertDictData(state.dataList)
// 其他处理逻辑(如 auditAll
// ...
}
})
// 使用 useTable Hook
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle,
state
} = useTable(state)
// 查询方法 - 简化
const handleFilter = (param: any) => {
// 更新额外参数
params.value = { ...param }
// 调用 getDataList 刷新数据(会自动跳转到第一页)
getDataList()
}
// 重置查询
const resetQuery = () => {
searchFormRef.value?.formRef?.resetFields()
// 重置 search 对象
Object.keys(search).forEach(key => {
search[key] = ''
})
params.value = {}
getDataList()
}
// 其他需要刷新表格的地方
const handelQuickSeach = (val: any) => {
params.value.flag = val
getDataList() // 使用 getDataList 替代 getList(page)
}
const updateInoutFlag = (row: any, val: any) => {
const updateParams = {"teacherNo": row.teacherNo, "inoutFlag": val}
messageBox.confirm('确认操作?').then(() => {
updateInout(updateParams).then((res: any) => {
message.success("修改成功")
getDataList(false) // 刷新但保持当前页
})
})
}
// 数据转换函数(保持不变)
const convertDictData = (records: any[]) => {
// ... 转换逻辑
return records
}
</script>
```
## 关键改造点
### 1. 导入 useTable
```typescript
import { BasicTableProps, useTable } from '/@/hooks/table'
```
### 2. 配置 state
```typescript
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: search, // 将 search 对象作为 queryForm
pageList: fetchList, // 或自定义方法
props: {
item: 'record.records',
totalCount: 'record.total'
}
})
```
### 3. 处理额外参数
如果查询时需要额外的参数(如 `params.value.flag`),有两种方式:
#### 方式一:自定义 pageList 方法(推荐)
```typescript
const params = ref<any>({})
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: search,
pageList: async (queryParams: any) => {
// 合并额外参数
return await fetchList({
...queryParams,
...params.value // 合并额外参数
})
}
})
```
#### 方式二:在 onLoaded 中处理
```typescript
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: search,
pageList: fetchList,
onLoaded: async (state) => {
// 可以在这里处理数据转换等逻辑
}
})
```
### 4. 替换数据引用
| 原代码 | 改造后 |
|--------|--------|
| `tableData` | `state.dataList` |
| `tableLoading` | `state.loading` |
| `page.currentPage` | `state.pagination.current` |
| `page.pageSize` | `state.pagination.size` |
| `page.total` | `state.pagination.total` |
### 5. 替换方法调用
| 原代码 | 改造后 |
|--------|--------|
| `getList(page)` | `getDataList()``getDataList(false)` |
| `currentChange(val)` | `currentChangeHandle(val)` |
| `handleSizeChange(val)` | `sizeChangeHandle(val)` |
### 6. 处理数据转换
如果需要在数据加载后进行转换,使用 `onLoaded` 回调:
```typescript
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: search,
pageList: fetchList,
onLoaded: async (state) => {
// 字典数据转换
state.dataList = convertDictData(state.dataList)
// 处理 auditAll 等逻辑
// 注意:需要在 API 响应中获取,或通过其他方式处理
}
})
```
## 完整改造示例(针对当前页面)
```typescript
// 1. 导入 useTable
import { BasicTableProps, useTable } from '/@/hooks/table'
// 2. 搜索表单数据(保持不变)
const search = reactive({
deptCode: '',
secDeptCode: '',
tied: '',
realName: '',
teacherNo: '',
retireDate: '',
pfTitleId: '',
stationDutyLevelId: '',
politicsStatus: '',
teacherCate: '',
inoutFlag: ''
})
// 3. 额外参数(用于 flag 等)
const params = ref<any>({})
// 4. 配置 useTable
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: search, // 将 search 作为 queryForm
// 自定义 pageList合并额外参数
pageList: async (queryParams: any) => {
// 合并 search 和 params
const mergedParams = {
...queryParams,
...params.value,
tied: search.tied // 如果需要特殊处理
}
return await fetchList(mergedParams)
},
// 数据属性映射
props: {
item: 'record.records',
totalCount: 'record.total'
},
// 数据加载完成回调
onLoaded: async (state) => {
// 字典数据转换
state.dataList = convertDictData(state.dataList)
// 注意auditAll 需要从 API 响应中获取
// 如果 API 返回在 response.data.auditAll需要特殊处理
}
})
// 5. 使用 useTable
const {
getDataList,
currentChangeHandle,
sizeChangeHandle,
tableStyle,
state
} = useTable(state)
// 6. 查询方法(简化)
const handleFilter = (param: any) => {
params.value = { ...param }
getDataList() // 自动跳转到第一页
}
// 7. 重置查询
const resetQuery = () => {
searchFormRef.value?.formRef?.resetFields()
Object.keys(search).forEach(key => {
search[key] = ''
})
params.value = {}
getDataList()
}
// 8. 快速查询
const handelQuickSeach = (val: any) => {
params.value.flag = val
getDataList()
}
// 9. 其他需要刷新的地方
const updateInoutFlag = (row: any, val: any) => {
const updateParams = {"teacherNo": row.teacherNo, "inoutFlag": val}
messageBox.confirm('确认操作?').then(() => {
updateInout(updateParams).then((res: any) => {
message.success("修改成功")
getDataList(false) // 保持当前页
})
})
}
```
## 注意事项
### 1. API 响应结构
如果 API 返回结构是 `response.data.record.records`,需要配置 `props`
```typescript
props: {
item: 'record.records',
totalCount: 'record.total'
}
```
### 2. 额外参数处理
如果查询时需要额外的参数(不在 `search` 对象中),使用自定义 `pageList` 方法合并:
```typescript
pageList: async (queryParams: any) => {
return await fetchList({
...queryParams,
...params.value, // 额外参数
// 或其他特殊参数
})
}
```
### 3. 数据转换
如果需要在数据加载后进行转换,使用 `onLoaded` 回调:
```typescript
onLoaded: async (state) => {
state.dataList = convertDictData(state.dataList)
}
```
### 4. 特殊响应字段
如果 API 响应中有特殊字段(如 `auditAll`),需要在 `onLoaded` 中处理,或者自定义 `pageList` 方法:
```typescript
pageList: async (queryParams: any) => {
const response = await fetchList({
...queryParams,
...params.value
})
// 处理特殊字段
if (response.data.auditAll == '0') {
auditAll.value = false
} else {
auditAll.value = true
}
return response
}
```
### 5. 初始化数据加载
`useTable` 默认会在 `onMounted` 时自动加载数据。如果不需要自动加载,设置:
```typescript
const state: BasicTableProps = reactive<BasicTableProps>({
queryForm: search,
pageList: fetchList,
createdIsNeed: false // 禁用自动加载
})
// 手动调用
onMounted(() => {
init()
loadSearchDictData()
getDataList() // 手动加载
})
```
## 优势总结
使用 `useTable` 后:
1.**代码量减少**:不需要手动管理 `tableData``tableLoading``page`
2.**自动状态管理**loading、分页、数据自动管理
3.**统一方法**`getDataList()` 统一刷新数据
4.**兼容性好**:完全兼容自定义 `search-form` 组件
5.**灵活扩展**:支持自定义 `pageList``onLoaded` 等回调
## 总结
**`useTable` 完全兼容自定义 `<search-form>` 组件**,只需要:
1.`search` 对象作为 `queryForm` 传入
2. 使用 `state.dataList``state.loading``state.pagination` 替代手动管理的状态
3. 使用 `getDataList()` 替代 `getList(page)`
4. 使用 `currentChangeHandle``sizeChangeHandle` 替代手动分页方法
如果有额外参数或特殊处理,使用自定义 `pageList` 方法或 `onLoaded` 回调即可。

309
docs/按钮样式规范.md Normal file
View File

@@ -0,0 +1,309 @@
# 按钮样式设计规范
本文档定义了项目中按钮组件的样式规范包括实心按钮和Plain按钮的使用规则确保整个应用的按钮样式统一、协调、美观。
## 一、按钮类型分类
### 1. 实心按钮Solid- 用于最重要的操作
**使用场景:**
- 新增、创建操作
- 保存、提交操作
- 确认、确定操作
- 删除等危险操作(需要突出警示)
**视觉特点:**
- 实心填充,高对比度
- 突出显示,吸引用户注意力
- 通常位于操作区域的主要位置
**代码示例:**
```vue
<!-- 主要操作新增 -->
<el-button type="primary" icon="Plus" @click="handleAdd"> </el-button>
<!-- 危险操作删除 -->
<el-button type="danger" icon="Delete" @click="handleDelete"> </el-button>
```
### 2. Plain按钮边框样式- 用于次要操作
**使用场景:**
- 查询、搜索操作
- 导出、导入操作
- 设置、配置操作
- 辅助功能操作
**视觉特点:**
- 边框+透明背景
- 不抢夺视觉焦点
- 保持页面协调统一
- 适合批量操作按钮
**代码示例:**
```vue
<!-- 查询操作 -->
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<!-- 导出操作 -->
<el-button type="primary" plain icon="Download" @click="handleExport">导出</el-button>
<!-- 导入操作 -->
<el-button type="primary" plain icon="Upload" @click="handleImport">导入</el-button>
```
### 3. 默认按钮 - 用于中性操作
**使用场景:**
- 设置、配置操作
- 状态切换操作
- 中性功能操作
**视觉特点:**
- 灰色系,低调不突出
- 适合不重要的操作
**代码示例:**
```vue
<!-- 设置操作 -->
<el-button icon="Setting" @click="handleSetting">状态锁定</el-button>
```
## 二、配色方案
> **设计原则:** 在保持项目默认样式的基础上,通过颜色区分不同操作类型,提升视觉层次和识别度。
### 主要操作按钮
- **类型:** `type="primary"` 实心
- **颜色:** 蓝色实心填充
- **用途:** 新增、保存、提交等主要操作
- **示例:** 新增按钮
### 查询操作按钮
- **类型:** `type="primary" plain`
- **颜色:** 蓝色边框 + 透明背景
- **用途:** 查询、搜索、筛选等操作
- **示例:** 一体化查询、搜索按钮
- **说明:** 与主要操作保持同一色系,体现关联性
### 导出操作按钮
- **类型:** `type="warning" plain`
- **颜色:** 橙色边框 + 透明背景
- **用途:** 数据导出、下载等操作
- **示例:** 导出WORD、自定义导出
- **说明:** 橙色表示数据输出,与导入形成对比
### 导入操作按钮
- **类型:** `type="primary" plain`
- **颜色:** 蓝色边框 + 透明背景
- **用途:** 数据导入、上传等操作
- **示例:** 导入信息
- **说明:** 保持项目默认样式,与查询操作保持一致
### 设置操作按钮
- **类型:** 默认样式无type属性
- **颜色:** 灰色系
- **用途:** 设置、配置、状态锁定等中性操作
- **示例:** 状态锁定按钮
### 危险操作按钮
- **类型:** `type="danger"` 实心 或 `type="danger" plain`
- **颜色:** 红色
- **用途:** 删除、清空等危险操作
- **示例:** 删除按钮
## 三、按钮图标规范
所有按钮应配合相应的图标使用,提升用户体验和视觉识别度。
### 常用图标映射
| 操作类型 | 图标名称 | 说明 |
|---------|---------|------|
| 新增/添加 | `FolderAdd` | 文件夹加号图标(项目默认) |
| 查询/搜索 | `Search` | 搜索图标 |
| 导出/下载 | `Download` | 下载图标 |
| 导入/上传 | `Upload` | 上传图标(项目默认) |
| 编辑/修改 | `Edit` | 编辑图标 |
| 删除 | `Delete` | 删除图标 |
| 查看/详情 | `View` | 查看图标 |
| 设置/配置 | `Setting` | 设置图标 |
| 锁定/解锁 | `Lock` | 锁定图标 |
| 刷新/重置 | `Refresh` | 刷新图标 |
| 用户相关 | `User` | 用户图标 |
### 图标使用示例
```vue
<el-button type="primary" icon="FolderAdd" @click="handleAdd"> </el-button>
<el-button type="primary" plain icon="Search" @click="handleSearch">查询</el-button>
<el-button type="warning" plain icon="Download" @click="handleExport">导出</el-button>
<el-button type="primary" plain icon="Upload" @click="handleImport">导入</el-button>
```
## 四、按钮尺寸规范
### 默认尺寸default
- 用于页面主要操作区域的按钮
- 高度32pxElement Plus默认
### 小尺寸small
- 用于表格操作列、对话框底部等空间受限的场景
- 高度24px
- 使用 `size="small"` 属性
```vue
<!-- 表格操作列 -->
<el-button type="primary" link size="small" @click="handleEdit">编辑</el-button>
```
### 链接按钮link
- 用于表格操作列,节省空间
- 使用 `link` 属性
```vue
<el-button type="primary" link size="small" @click="handleView">查看</el-button>
```
## 五、按钮布局规范
### 按钮间距
- 按钮之间使用 `class="ml10"` 保持10px的左边距
- 确保按钮组视觉统一
```vue
<el-button type="primary" icon="FolderAdd"> </el-button>
<el-button type="primary" plain icon="Search" class="ml10">查询</el-button>
<el-button type="warning" plain icon="Download" class="ml10">导出</el-button>
<el-button type="primary" plain icon="Upload" class="ml10">导入</el-button>
```
### 按钮分组
- 相关功能的按钮应放在一起
- 主要操作按钮放在最前面
- 次要操作按钮放在后面
## 六、完整示例
### 页面操作按钮组示例
```vue
<el-row>
<div class="mb15" style="width: 100%;">
<!-- 主要操作新增 -->
<el-button
type="primary"
icon="FolderAdd"
@click="handleAdd"
v-if="permissions.add">
</el-button>
<!-- 查询操作使用 primary plain 样式 -->
<el-button
type="primary"
plain
icon="Search"
class="ml10"
@click="handleSearch">查询
</el-button>
<!-- 导出操作使用 warning plain 样式橙色边框 -->
<el-button
type="warning"
plain
icon="Download"
class="ml10"
@click="handleExport">导出
</el-button>
<!-- 导入操作使用 primary plain 样式蓝色边框保持项目默认样式 -->
<el-button
type="primary"
plain
icon="Upload"
class="ml10"
@click="handleImport">导入
</el-button>
<!-- 设置操作使用默认样式 -->
<el-button
icon="Setting"
class="ml10"
@click="handleSetting">设置
</el-button>
</div>
</el-row>
```
### 表格操作列示例
```vue
<el-table-column label="操作" width="200" align="center" fixed="right">
<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>
```
## 七、最佳实践
1. **一致性原则**
- 相同功能的按钮在整个应用中应使用相同的样式
- 保持图标和颜色的统一性
2. **层次分明**
- 主要操作使用实心按钮,突出显示
- 次要操作使用Plain按钮保持协调
3. **语义化**
- 按钮的颜色和样式应与其功能语义相匹配
- 危险操作使用红色,数据操作使用绿色等
4. **用户体验**
- 重要操作按钮应放在显眼位置
- 按钮文字应简洁明了
- 配合图标提升识别度
5. **响应式考虑**
- 在移动端或小屏幕设备上,考虑使用更紧凑的布局
- 使用 `size="small"` 适应空间限制
## 八、注意事项
1. **避免过度使用实心按钮**
- 一个页面中实心按钮不应过多通常1-2个主要操作即可
- 过多的实心按钮会分散用户注意力
2. **Plain按钮的优势**
- Plain按钮视觉更柔和适合批量操作
- 不会抢夺主要操作的视觉焦点
3. **图标选择**
- 图标应与操作功能语义匹配
- 使用Element Plus内置图标保持一致性
4. **权限控制**
- 按钮应根据用户权限显示/隐藏
- 使用 `v-if` 控制按钮的显示
## 九、更新记录
- **2024-XX-XX**: 创建按钮样式规范文档
- 规范版本v1.0
---
**维护者:** 前端开发团队
**最后更新:** 2024年