生产管理系统 - 页面上路由参数缓存
This commit is contained in:
@@ -1474,4 +1474,539 @@ SearchFormPagination 组件通过配置驱动的方式,极大地简化了复
|
||||
- **类型安全**:完整的 TypeScript 支持
|
||||
- **功能专注**:专注搜索、展示、分页核心功能,避免过度设计
|
||||
|
||||
该组件可以作为项目中所有数据展示页面的标准解决方案,显著提升开发效率和代码质量。
|
||||
该组件可以作为项目中所有数据展示页面的标准解决方案,显著提升开发效率和代码质量。
|
||||
|
||||
---
|
||||
|
||||
## path:src/components/common/searchFormPagination,name:URL参数同步功能集成规范
|
||||
|
||||
### 功能概述
|
||||
|
||||
URL参数同步功能是 SearchFormPagination 组件的高级特性,能够自动将用户的搜索条件、分页状态与浏览器URL参数保持同步,实现页面刷新后状态的恢复,提升用户体验。
|
||||
|
||||
### 设计原则
|
||||
|
||||
#### 1. 职责分离原则
|
||||
|
||||
**子组件职责**(SearchFormPagination):
|
||||
- ✅ UI 显示:渲染搜索表单、数据表格、分页组件
|
||||
- ✅ 状态管理:管理内部搜索条件和分页状态
|
||||
- ✅ URL 同步:自动同步状态到 URL 参数
|
||||
- ✅ 状态通知:通过回调通知父组件状态变化
|
||||
- ✅ 参数推导:自动从 searchFields 推导 URL 参数映射
|
||||
|
||||
**父组件职责**(业务页面):
|
||||
- ✅ 查询逻辑:处理所有查询相关的业务逻辑
|
||||
- ✅ 数据管理:管理数据、加载状态、错误处理
|
||||
- ✅ API 调用:调用后端接口获取数据
|
||||
|
||||
#### 2. 自动参数推导原则
|
||||
|
||||
URL 同步功能会自动从 `searchFields` 配置中提取参数映射,无需手动配置:
|
||||
|
||||
```tsx
|
||||
// searchFields 配置
|
||||
const searchFields = [
|
||||
{ key: 'search', label: '搜索', type: 'text' },
|
||||
{ key: 'status', label: '状态', type: 'select' },
|
||||
{ key: 'type', label: '类型', type: 'select' }
|
||||
];
|
||||
|
||||
// 自动推导出 URL 参数映射
|
||||
// {
|
||||
// page: 'page',
|
||||
// size: 'size',
|
||||
// search: 'search', // 来自 searchFields[0].key
|
||||
// status: 'status', // 来自 searchFields[1].key
|
||||
// type: 'type' // 来自 searchFields[2].key
|
||||
// }
|
||||
|
||||
// 生成的 URL:?page=1&size=10&search=张三&status=active&type=admin
|
||||
```
|
||||
|
||||
#### 3. 可选启用原则
|
||||
|
||||
URL 同步功能是完全可选的,不影响现有使用方式:
|
||||
|
||||
```tsx
|
||||
// 不启用 URL 同步(默认行为)
|
||||
<SearchFormPagination
|
||||
searchFields={searchFields}
|
||||
columns={columns}
|
||||
data={data}
|
||||
// ... 其他 props
|
||||
/>
|
||||
|
||||
// 启用 URL 同步 - 极简配置
|
||||
<SearchFormPagination
|
||||
searchFields={searchFields}
|
||||
columns={columns}
|
||||
data={data}
|
||||
urlSync={{ enabled: true }} // 只需这一行即可启用
|
||||
// ... 其他 props
|
||||
/>
|
||||
```
|
||||
|
||||
### 接口定义
|
||||
|
||||
#### UrlSyncConfig - URL 同步配置
|
||||
|
||||
```tsx
|
||||
interface UrlSyncConfig {
|
||||
// 是否启用 URL 参数同步
|
||||
enabled?: boolean;
|
||||
|
||||
// 是否在初始化时检测空 URL 并添加默认参数
|
||||
initWithDefaults?: boolean;
|
||||
|
||||
// 默认分页参数
|
||||
defaultPagination?: {
|
||||
page: number;
|
||||
size: number;
|
||||
};
|
||||
|
||||
// URL 更新防抖时间(毫秒),避免频繁更新
|
||||
updateDebounce?: number;
|
||||
|
||||
// 自定义 URL 参数名映射(可选,不配置则自动从 searchFields 提取)
|
||||
paramNames?: {
|
||||
page?: string;
|
||||
size?: string;
|
||||
[key: string]: string | undefined;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### 统一查询接口(可选)
|
||||
|
||||
```tsx
|
||||
// 新增的统一查询回调,替代传统的多个回调
|
||||
onQueryChange?: (query: {
|
||||
filters: Record<string, string; // 搜索条件(已处理)
|
||||
pagination: { page: number; size: number }; // 分页信息(已处理)
|
||||
isFromUrl?: boolean; // 是否来自URL初始化
|
||||
}) => void;
|
||||
```
|
||||
|
||||
### 使用方式
|
||||
|
||||
#### 1. 基础使用(推荐)
|
||||
|
||||
```tsx
|
||||
<SearchFormPagination
|
||||
searchFields={searchFields}
|
||||
columns={columns}
|
||||
data={state.users}
|
||||
loading={state.loading}
|
||||
error={state.error}
|
||||
pagination={state.pagination}
|
||||
onPageChange={handlePageChange}
|
||||
onSizeChange={handleSizeChange}
|
||||
onSearch={handleSearch}
|
||||
|
||||
// 启用 URL 同步 - 参数名自动从 searchFields 提取
|
||||
urlSync={{
|
||||
enabled: true,
|
||||
initWithDefaults: true,
|
||||
updateDebounce: 300
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
#### 2. 高级配置(自定义参数名)
|
||||
|
||||
```tsx
|
||||
<SearchFormPagination
|
||||
// ... 其他 props
|
||||
urlSync={{
|
||||
enabled: true,
|
||||
initWithDefaults: true,
|
||||
|
||||
// 自定义 URL 参数名(覆盖自动提取的参数名)
|
||||
paramNames: {
|
||||
page: 'pageNum', // 页码参数名
|
||||
size: 'pageSize', // 每页大小参数名
|
||||
search: 'keyword', // 搜索框参数名(覆盖自动提取)
|
||||
// status 和 type 会自动从 searchFields 提取,这里不配置
|
||||
},
|
||||
|
||||
// 默认分页参数
|
||||
defaultPagination: {
|
||||
page: 1,
|
||||
size: 10
|
||||
},
|
||||
|
||||
// URL 更新防抖时间
|
||||
updateDebounce: 500
|
||||
}}
|
||||
|
||||
// 统一查询回调(推荐使用)
|
||||
onQueryChange={handleQueryChange}
|
||||
|
||||
// 传统回调方式(向后兼容)
|
||||
onSearch={handleSearch}
|
||||
onPageChange={handlePageChange}
|
||||
onSizeChange={handleSizeChange}
|
||||
|
||||
// URL 状态变化监听(可选)
|
||||
onUrlStateChange={(urlState) => {
|
||||
console.log('URL 状态变化:', urlState);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
#### 3. 父组件统一查询处理
|
||||
|
||||
```tsx
|
||||
// 统一查询处理函数
|
||||
const handleQueryChange = useCallback((query: {
|
||||
filters: Record<string, string>;
|
||||
pagination: { page: number; size: number };
|
||||
isFromUrl?: boolean;
|
||||
}) => {
|
||||
console.log('收到查询变化:', query);
|
||||
|
||||
// 映射过滤器字段名(根据业务需求调整)
|
||||
const mappedFilters = {
|
||||
searchKeyword: query.filters.search || '',
|
||||
statusFilter: query.filters.status || 'all',
|
||||
typeFilter: query.filters.type || 'all'
|
||||
};
|
||||
|
||||
// 更新状态
|
||||
dispatch({ type: 'SET_FILTERS', payload: mappedFilters });
|
||||
dispatch({ type: 'SET_PAGINATION', payload: query.pagination });
|
||||
|
||||
// 执行查询
|
||||
loadUsers({
|
||||
resetPage: !query.isFromUrl, // URL 初始化时保持页码,否则重置
|
||||
page: query.pagination.page,
|
||||
filters: mappedFilters,
|
||||
sortBy: state.sortBy,
|
||||
sortOrder: state.sortOrder,
|
||||
size: query.pagination.size
|
||||
});
|
||||
}, [state.sortBy, state.sortOrder, loadUsers]);
|
||||
```
|
||||
|
||||
### 工作流程
|
||||
|
||||
#### 1. 页面初始化
|
||||
|
||||
```
|
||||
用户访问页面
|
||||
↓
|
||||
子组件检查 URL 参数
|
||||
├─ 无参数 → 使用默认状态,父组件执行默认查询
|
||||
└─ 有参数 → 解析参数 → 同步到内部状态 → 通知父组件 → 父组件执行查询
|
||||
```
|
||||
|
||||
#### 2. 用户操作
|
||||
|
||||
```
|
||||
用户搜索/分页操作
|
||||
↓
|
||||
子组件更新内部状态
|
||||
↓
|
||||
同步状态到 URL 参数(防抖处理)
|
||||
↓
|
||||
通知父组件状态变化
|
||||
↓
|
||||
父组件执行查询
|
||||
```
|
||||
|
||||
#### 3. 页面刷新
|
||||
|
||||
```
|
||||
页面刷新
|
||||
↓
|
||||
子组件从 URL 读取参数
|
||||
↓
|
||||
恢复内部状态
|
||||
↓
|
||||
通知父组件
|
||||
↓
|
||||
父组件执行查询 → 恢复用户之前的搜索状态
|
||||
```
|
||||
|
||||
### URL 参数格式
|
||||
|
||||
#### 默认格式
|
||||
|
||||
```
|
||||
# 基础搜索
|
||||
?page=1&size=10&search=张三&status=active&type=admin
|
||||
|
||||
# 分页状态
|
||||
?page=3&size=20
|
||||
|
||||
# 组合条件
|
||||
?page=2&size=15&search=admin&status=active
|
||||
```
|
||||
|
||||
#### 自定义参数名格式
|
||||
|
||||
```tsx
|
||||
paramNames: {
|
||||
page: 'p',
|
||||
size: 'limit',
|
||||
search: 'q',
|
||||
status: 's',
|
||||
type: 't'
|
||||
}
|
||||
|
||||
// 生成 URL:
|
||||
?pageNum=2&pageSize=15&keyword=admin&status=active&type=user
|
||||
```
|
||||
|
||||
### 技术实现要点
|
||||
|
||||
#### 1. 防抖处理
|
||||
|
||||
```tsx
|
||||
// URL 更新防抖,避免频繁修改浏览器历史记录
|
||||
const updateUrl = useCallback((filters, pagination) => {
|
||||
if (urlUpdateTimeoutRef.current) {
|
||||
clearTimeout(urlUpdateTimeoutRef.current);
|
||||
}
|
||||
|
||||
urlUpdateTimeoutRef.current = setTimeout(() => {
|
||||
// 更新 URL 参数
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
}, urlConfig.updateDebounce);
|
||||
}, []);
|
||||
```
|
||||
|
||||
#### 2. 参数映射
|
||||
|
||||
```tsx
|
||||
// 支持字段名映射,适应不同的 API 接口
|
||||
const paramValue = urlParams.get(
|
||||
urlConfig.paramNames[field.key] || field.key
|
||||
);
|
||||
```
|
||||
|
||||
#### 3. 状态同步时机
|
||||
|
||||
```tsx
|
||||
// 搜索条件变化时同步
|
||||
useEffect(() => {
|
||||
if (urlConfig.enabled) {
|
||||
updateUrl(filters, pagination);
|
||||
}
|
||||
}, [filters, urlConfig.enabled]);
|
||||
|
||||
// 分页变化时同步
|
||||
useEffect(() => {
|
||||
if (urlConfig.enabled && pagination) {
|
||||
updateUrl(filters, pagination);
|
||||
}
|
||||
}, [pagination?.page, pagination?.size, urlConfig.enabled]);
|
||||
```
|
||||
|
||||
### 最佳实践
|
||||
|
||||
#### 1. 参数命名规范
|
||||
|
||||
```tsx
|
||||
// ✅ 推荐:使用有意义的参数名
|
||||
paramNames: {
|
||||
search: 'keyword', // 搜索关键词
|
||||
status: 'userStatus', // 用户状态
|
||||
type: 'userType', // 用户类型
|
||||
page: 'pageNum', // 页码
|
||||
size: 'pageSize' // 每页大小
|
||||
}
|
||||
|
||||
// ❌ 避免:过于简化的参数名
|
||||
paramNames: {
|
||||
search: 's',
|
||||
status: 'st',
|
||||
type: 't'
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 防抖时间设置
|
||||
|
||||
```tsx
|
||||
// ✅ 推荐:根据用户操作频率调整
|
||||
urlSync: {
|
||||
updateDebounce: 300 // 文本搜索:300ms,下拉选择:立即
|
||||
}
|
||||
|
||||
// 快速响应场景
|
||||
urlSync: {
|
||||
updateDebounce: 100 // 需要即时反馈的场景
|
||||
}
|
||||
|
||||
// 性能优先场景
|
||||
urlSync: {
|
||||
updateDebounce: 500 // 减少频繁更新
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 默认值配置
|
||||
|
||||
```tsx
|
||||
// ✅ 推荐:设置合理的默认值
|
||||
urlSync: {
|
||||
defaultPagination: {
|
||||
page: 1,
|
||||
size: 10 // 根据业务需求设置合理的默认每页条数
|
||||
},
|
||||
initWithDefaults: true // 为新用户提供更好的体验
|
||||
}
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
#### 1. 浏览器兼容性
|
||||
|
||||
- 支持 `window.history.replaceState` 的现代浏览器
|
||||
- 服务端渲染(SSR)时需要检查 `typeof window !== 'undefined'`
|
||||
|
||||
#### 2. 参数长度限制
|
||||
|
||||
- URL 参数总长度建议控制在 2048 字符以内
|
||||
- 复杂搜索条件考虑使用 POST 请求而非 GET
|
||||
|
||||
#### 3. 安全性考虑
|
||||
|
||||
- 对 URL 参数进行验证和清理
|
||||
- 避免将敏感信息存储在 URL 中
|
||||
- 考虑 XSS 防护
|
||||
|
||||
#### 4. 性能影响
|
||||
|
||||
- URL 同步功能对性能影响很小
|
||||
- 防抖机制避免频繁的 DOM 操作
|
||||
- 合理设置防抖时间可进一步优化性能
|
||||
|
||||
### 向后兼容性
|
||||
|
||||
URL 同步功能完全向后兼容,不会影响现有代码:
|
||||
|
||||
```tsx
|
||||
// 现有代码无需修改,继续正常工作
|
||||
<SearchFormPagination
|
||||
searchFields={searchFields}
|
||||
columns={columns}
|
||||
onSearch={handleSearch}
|
||||
onPageChange={handlePageChange}
|
||||
// ... 其他 props
|
||||
/>
|
||||
|
||||
// 极简启用 - 只需添加一行
|
||||
<SearchFormPagination
|
||||
// ... 现有 props
|
||||
urlSync={{ enabled: true }} // 参数名自动从 searchFields 推导
|
||||
/>
|
||||
```
|
||||
|
||||
### 配置简化对比
|
||||
|
||||
#### 优化前(复杂配置)
|
||||
```tsx
|
||||
urlSync={{
|
||||
enabled: true,
|
||||
initWithDefaults: true,
|
||||
paramNames: {
|
||||
page: 'page',
|
||||
size: 'size',
|
||||
search: 'search',
|
||||
status: 'status',
|
||||
type: 'type'
|
||||
},
|
||||
defaultPagination: { page: 1, size: 10 },
|
||||
updateDebounce: 300
|
||||
}}
|
||||
```
|
||||
|
||||
#### 优化后(极简配置)
|
||||
```tsx
|
||||
urlSync={{
|
||||
enabled: true,
|
||||
initWithDefaults: true,
|
||||
updateDebounce: 300
|
||||
}}
|
||||
// page、size 以及所有 searchFields 的 key 都会自动推导
|
||||
```
|
||||
|
||||
### 故障排除
|
||||
|
||||
#### 1. URL 参数不更新
|
||||
|
||||
**可能原因**:
|
||||
- `urlSync.enabled` 设置为 `false`
|
||||
- 防抖时间设置过长
|
||||
- 浏览器不支持 `history.replaceState`
|
||||
|
||||
**解决方案**:
|
||||
```tsx
|
||||
urlSync: {
|
||||
enabled: true,
|
||||
updateDebounce: 100 // 减少防抖时间测试
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 页面刷新后状态丢失
|
||||
|
||||
**可能原因**:
|
||||
- `initWithDefaults` 设置为 `false`
|
||||
- 参数名映射不正确
|
||||
- 父组件未正确处理 `onQueryChange` 回调
|
||||
|
||||
**解决方案**:
|
||||
```tsx
|
||||
urlSync: {
|
||||
enabled: true,
|
||||
initWithDefaults: true // 确保启用默认值初始化
|
||||
}
|
||||
|
||||
// 检查参数名映射
|
||||
paramNames: {
|
||||
search: 'search', // 确保与搜索字段 key 一致
|
||||
status: 'status'
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 搜索条件与分页不同步
|
||||
|
||||
**可能原因**:
|
||||
- 父组件未传递正确的分页状态
|
||||
- 回调函数中丢失搜索条件
|
||||
|
||||
**解决方案**:
|
||||
```tsx
|
||||
const handlePageChange = useCallback((page) => {
|
||||
// 确保传递当前搜索条件
|
||||
loadUsers({
|
||||
filters: currentFilters, // 关键:保持搜索条件
|
||||
pagination: { page, size: currentSize }
|
||||
});
|
||||
}, [loadUsers, currentFilters, currentSize]);
|
||||
```
|
||||
|
||||
### 总结
|
||||
|
||||
URL 参数同步功能为 SearchFormPagination 组件提供了强大的状态持久化能力,通过极简配置即可实现:
|
||||
|
||||
- **自动同步**:无需手动管理 URL 参数
|
||||
- **自动推导**:参数名从 `searchFields` 自动提取,无需手动映射
|
||||
- **状态恢复**:页面刷新后自动恢复搜索状态
|
||||
- **用户体验**:提供更好的导航和分享体验
|
||||
- **极简配置**:只需 `urlSync={{ enabled: true }}` 即可启用
|
||||
- **向后兼容**:不影响现有代码,渐进式升级
|
||||
|
||||
#### 配置简化成果
|
||||
|
||||
- **优化前**:需要手动配置所有参数名映射,配置复杂
|
||||
- **优化后**:参数名自动推导,配置减少 70%+
|
||||
|
||||
#### 使用建议
|
||||
|
||||
- **基础场景**:直接使用 `urlSync={{ enabled: true }}`
|
||||
- **特殊需求**:仅在需要自定义参数名时配置 `paramNames`
|
||||
- **避免使用**:不推荐使用 `q`、`s` 等过于简化的参数名
|
||||
|
||||
该功能特别适用于数据展示、搜索、筛选等需要状态保持的场景,是提升用户体验的重要功能。
|
||||
Reference in New Issue
Block a user