20260511214503
This commit is contained in:
1 parent
364d29dbaa
commit
e98740192d
9 files changed
+2451
-875
No files matched your search
@@ -0,0 +1,274 @@
|
||||
# 草稿态核心架构重构计划
|
||||
|
||||
## 一、问题分析
|
||||
|
||||
当前草稿态管理存在以下核心问题:
|
||||
|
||||
| 问题 | 影响 |
|
||||
|------|------|
|
||||
| 维护操作过程而非最终状态 | recover/search/pagination/merge 逻辑复杂 |
|
||||
| delete 直接覆盖状态 | recover 后编辑数据丢失 |
|
||||
| 本地数据参与搜索 | total/page 不准确,分页错乱 |
|
||||
| 搜索与展示混在一起 | 搜索结果不稳定 |
|
||||
| 缺少基础搜索条件 | 业务层重复拼接条件 |
|
||||
|
||||
---
|
||||
|
||||
## 二、核心设计调整
|
||||
|
||||
### 2.1 草稿态数据模型
|
||||
|
||||
**新模型结构:**
|
||||
|
||||
```js
|
||||
// 草稿状态映射 - 只维护最终状态
|
||||
draftStateMap = {
|
||||
[rowId]: {
|
||||
state: 'insert' | 'update' | 'delete',
|
||||
data: {}, // 本地修改后的数据
|
||||
backup: {} // delete 时的备份(用于 recover)
|
||||
}
|
||||
};
|
||||
|
||||
// 本地新增数据(尚未保存到数据库)
|
||||
insertRows = [];
|
||||
|
||||
// 选中状态(独立于草稿状态)
|
||||
selectedRows = [];
|
||||
```
|
||||
|
||||
**状态转换规则:**
|
||||
|
||||
```text
|
||||
insert → delete → recover → insert(恢复本地新增态)
|
||||
update → delete → recover → update(恢复编辑态)
|
||||
delete → recover → 恢复 backup,如果没有则恢复远程原始态
|
||||
```
|
||||
|
||||
### 2.2 搜索与展示分离
|
||||
|
||||
| 阶段 | 数据源 | 说明 |
|
||||
|------|--------|------|
|
||||
| 搜索 | 远程接口 | 完全依赖服务器,本地数据不参与 |
|
||||
| 展示 | 远程 + 本地覆盖 | 使用本地状态覆盖远程数据展示 |
|
||||
|
||||
### 2.3 baseSearchCondition 机制
|
||||
|
||||
```js
|
||||
// 基础条件(永久保留)
|
||||
baseSearchCondition: "mx_zdorderid='xxx'"
|
||||
|
||||
// 动态条件(可清空)
|
||||
searchCondition: "name like '%abc%'"
|
||||
|
||||
// 最终条件
|
||||
finalCondition = baseSearchCondition + " AND " + searchCondition
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、实施步骤
|
||||
|
||||
### 3.1 步骤一:重构 DraftPageManager
|
||||
|
||||
**修改文件:** `js/feegrid-manager.js`
|
||||
|
||||
**核心改动:**
|
||||
|
||||
1. **新增状态映射结构**:
|
||||
```js
|
||||
var draftStateMap = {}; // { rowId: { state, data, backup } }
|
||||
var insertRows = []; // 本地新增行
|
||||
```
|
||||
|
||||
2. **简化状态操作方法**:
|
||||
```js
|
||||
function setDraftState(rowId, state, data, backup) {
|
||||
draftStateMap[rowId] = { state, data, backup };
|
||||
}
|
||||
|
||||
function getDraftState(rowId) {
|
||||
return draftStateMap[rowId];
|
||||
}
|
||||
|
||||
function removeDraftState(rowId) {
|
||||
delete draftStateMap[rowId];
|
||||
}
|
||||
```
|
||||
|
||||
3. **实现 delete backup 机制**:
|
||||
```js
|
||||
function addDeleteRow(row) {
|
||||
var rowId = getRowKey(row);
|
||||
var existingState = getDraftState(rowId);
|
||||
|
||||
setDraftState(rowId, 'delete', row, existingState ? existingState.data : row);
|
||||
}
|
||||
|
||||
function recoverRow(rowId) {
|
||||
var state = getDraftState(rowId);
|
||||
if (state && state.state === 'delete') {
|
||||
setDraftState(rowId, state.backup._rowState || '', state.backup, null);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 步骤二:重构 loadPage 方法
|
||||
|
||||
**核心改动:**
|
||||
|
||||
1. **搜索阶段**:只调用远程接口,本地数据不参与
|
||||
2. **展示阶段**:使用本地状态覆盖远程数据
|
||||
|
||||
```js
|
||||
function loadPage(pageNumber) {
|
||||
_setLoading();
|
||||
|
||||
var searchCondition = buildFinalCondition();
|
||||
|
||||
$.ajax({
|
||||
url: options.searchUrl,
|
||||
data: {
|
||||
viewname: options.viewName,
|
||||
searchcondition: searchCondition,
|
||||
page: pageNumber,
|
||||
rows: options.pageSize
|
||||
},
|
||||
success: function(result) {
|
||||
// 应用本地状态覆盖
|
||||
var displayRows = applyDraftState(result.rows);
|
||||
|
||||
_renderPage(displayRows, result.total, pageNumber);
|
||||
},
|
||||
complete: _clearLoading
|
||||
});
|
||||
}
|
||||
|
||||
function applyDraftState(remoteRows) {
|
||||
var displayRows = [];
|
||||
|
||||
for (var i = 0; i < remoteRows.length; i++) {
|
||||
var row = remoteRows[i];
|
||||
var rowId = getRowKey(row);
|
||||
var draftState = getDraftState(rowId);
|
||||
|
||||
if (draftState) {
|
||||
switch (draftState.state) {
|
||||
case 'delete':
|
||||
// 删除状态不展示
|
||||
continue;
|
||||
case 'update':
|
||||
// 展示更新后的数据
|
||||
displayRows.push(draftState.data);
|
||||
break;
|
||||
default:
|
||||
displayRows.push(row);
|
||||
}
|
||||
} else {
|
||||
displayRows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
// 本地新增行单独处理(不参与分页)
|
||||
return displayRows;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 步骤三:添加 baseSearchCondition 支持
|
||||
|
||||
**配置新增:**
|
||||
```js
|
||||
var DEFAULT_CONFIG = {
|
||||
baseSearchCondition: null,
|
||||
searchCondition: null,
|
||||
// ... 其他配置
|
||||
};
|
||||
```
|
||||
|
||||
**条件构建:**
|
||||
```js
|
||||
function buildFinalCondition() {
|
||||
var parts = [];
|
||||
|
||||
if (options.baseSearchCondition) {
|
||||
parts.push(options.baseSearchCondition);
|
||||
}
|
||||
|
||||
if (options.searchCondition) {
|
||||
parts.push(options.searchCondition);
|
||||
}
|
||||
|
||||
return parts.join(' AND ');
|
||||
}
|
||||
|
||||
function setSearchCondition(condition) {
|
||||
options.searchCondition = condition;
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
options.searchCondition = null;
|
||||
}
|
||||
|
||||
function reload() {
|
||||
loadPage(1);
|
||||
}
|
||||
```
|
||||
|
||||
### 3.4 步骤四:更新 JSP 调用
|
||||
|
||||
**修改文件:** `editorder_s_cw.jsp`
|
||||
|
||||
**简化调用:**
|
||||
```js
|
||||
var gridManager = createDataGridManager({
|
||||
gridId: 'feegrid',
|
||||
keyField: 'subid',
|
||||
viewName: subviewtable,
|
||||
baseSearchCondition: "mx_zdorderid='" + strid + "'",
|
||||
onLoadSuccess: calcw_selfee,
|
||||
onAfterEdit: calcw_fee,
|
||||
onSelectionChange: calcw_selfee
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、文件修改清单
|
||||
|
||||
| 文件 | 修改类型 | 说明 |
|
||||
|------|----------|------|
|
||||
| `js/feegrid-manager.js` | 重构 | 核心数据模型与搜索逻辑 |
|
||||
| `editorder_s_cw.jsp` | 更新 | 使用新的 baseSearchCondition 配置 |
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与注意事项
|
||||
|
||||
1. **数据一致性**:确保 draftStateMap 与 insertRows 同步
|
||||
2. **性能考虑**:draftStateMap 使用对象查找,O(1) 复杂度
|
||||
3. **状态清理**:保存成功后及时清理 draftStateMap
|
||||
4. **边界情况**:处理 rowId 为 null/undefined 的情况
|
||||
|
||||
---
|
||||
|
||||
## 六、最终架构图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ createDataGridManager │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Configuration │
|
||||
│ ├── gridId, keyField, viewName │
|
||||
│ ├── baseSearchCondition (永久) │
|
||||
│ └── searchCondition (动态) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Draft State Management │
|
||||
│ ├── draftStateMap: { id → { state, data, backup } } │
|
||||
│ ├── insertRows: [] │
|
||||
│ └── selectedRows: [] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Search & Display │
|
||||
│ ├── loadPage() → 远程搜索 → applyDraftState() → 展示 │
|
||||
│ └── buildFinalCondition() = base + dynamic │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
@@ -0,0 +1,230 @@
|
||||
# FeeGrid Manager 重构计划
|
||||
|
||||
## 一、问题分析总结
|
||||
|
||||
### 核心问题清单
|
||||
|
||||
| 问题类型 | 严重程度 | 影响范围 | 根因 |
|
||||
|---------|---------|---------|------|
|
||||
| 状态来源不唯一 | **高** | 所有状态操作 | `draftStateMap`、`insertRows`、`selectedRows`、`excludedRows` 并行维护 |
|
||||
| Selection 基于对象引用 | **高** | 分页/刷新后选择状态 | `selectedRows` 存储完整行对象 |
|
||||
| applyDraftState 副作用 | **高** | 展示层数据 | 直接使用 `draftState.data` 引用 |
|
||||
| 请求无版本化 | **高** | 异步分页切换 | 旧请求会污染新状态 |
|
||||
| recover 后展示不同步 | **中** | 删除恢复场景 | 状态恢复但未触发刷新 |
|
||||
| insertRows 与 draftStateMap 不同步 | **中** | 插入删除场景 | `addDeleteRow` 未清理 `insertRows` |
|
||||
|
||||
### 架构层面的根本缺陷
|
||||
|
||||
1. **状态模型未真正单源化** - 多个状态源持有同一行引用,修改时容易不同步
|
||||
2. **状态修改具有副作用** - `applyDraftState` 直接返回修改过的引用,导致状态污染链
|
||||
3. **缺乏请求版本控制** - 无法区分旧请求和新请求,导致状态回滚
|
||||
4. **UI 状态与数据状态耦合** - selection 基于对象引用而非 id
|
||||
|
||||
---
|
||||
|
||||
## 二、重构目标
|
||||
|
||||
### 核心原则
|
||||
|
||||
1. **状态单源化** - 仅保留 `draftStateMap` 作为唯一状态源
|
||||
2. **状态 id 化** - 所有 UI 状态(selection、excluded)基于 rowId 而非对象引用
|
||||
3. **纯函数化** - `applyDraftState` 不产生副作用,返回新对象
|
||||
4. **请求版本化** - 仅处理最后一次请求,防止旧请求污染
|
||||
|
||||
### 预期改进
|
||||
|
||||
- 彻底消除请求竞态导致的状态回滚
|
||||
- 消除 reload/refresh/page switch 后的 selection 丢失
|
||||
- 消除 insert → delete → display 的不一致
|
||||
- 消除 recover 后展示层与数据层不一致
|
||||
|
||||
---
|
||||
|
||||
## 三、修改方案
|
||||
|
||||
### 3.1 状态结构重构
|
||||
|
||||
**原结构:**
|
||||
```javascript
|
||||
var draftStateMap = {}; // {rowId: {state, data, backup}}
|
||||
var insertRows = []; // 插入行数组
|
||||
var selectedRows = []; // 选中行数组
|
||||
var excludedRows = []; // 排除行数组
|
||||
var isAllChecked = false; // 全选状态
|
||||
```
|
||||
|
||||
**新结构:**
|
||||
```javascript
|
||||
var draftStateMap = {}; // {rowId: {state, data, backup, selected, excluded}}
|
||||
var isAllChecked = false; // 全选状态
|
||||
```
|
||||
|
||||
**状态对象结构:**
|
||||
```javascript
|
||||
{
|
||||
state: 'insert' | 'update' | 'delete' | null, // 数据状态
|
||||
data: {...}, // 当前数据
|
||||
backup: {...} | null, // 删除时的备份数据
|
||||
selected: boolean, // 是否选中
|
||||
excluded: boolean // 是否排除(全选时)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 核心函数修改
|
||||
|
||||
#### 3.2.1 applyDraftState - 纯函数化
|
||||
- 创建行的深拷贝
|
||||
- 不直接使用 draftState.data 引用
|
||||
- 返回全新的 displayRows
|
||||
|
||||
#### 3.2.2 loadPage - 请求版本化
|
||||
- 引入 `requestVersion` 计数器
|
||||
- 每次请求递增版本号
|
||||
- 响应时检查版本号,只处理最新请求
|
||||
|
||||
#### 3.2.3 recoverRow - 触发刷新
|
||||
- 恢复状态后调用 `refreshCurrentPage()`
|
||||
- 确保展示层同步更新
|
||||
|
||||
#### 3.2.4 所有 selection 操作 - id 化
|
||||
- `addSelectedRow(row)` → `selectRow(rowId)`
|
||||
- `removeSelectedRow(row)` → `deselectRow(rowId)`
|
||||
- `getSelectedRows()` → 返回当前页中被选中的行(通过 id 匹配)
|
||||
|
||||
---
|
||||
|
||||
## 四、修改步骤
|
||||
|
||||
### 步骤 1:重构 DraftStateManager 状态结构
|
||||
**文件:** `js/feegrid-manager.js`(第 27-302 行)
|
||||
|
||||
**修改内容:**
|
||||
1. 移除 `insertRows`、`selectedRows`、`excludedRows` 数组
|
||||
2. 修改 `draftStateMap` 结构,增加 `selected` 和 `excluded` 字段
|
||||
3. 修改相关函数:
|
||||
- `addInsertRow` - 不再维护 `insertRows`
|
||||
- `addSelectedRow` - 改为通过 rowId 设置 `selected` 字段
|
||||
- `removeSelectedRow` - 改为通过 rowId 清除 `selected` 字段
|
||||
- `addExcludedRow` - 改为通过 rowId 设置 `excluded` 字段
|
||||
- `removeExcludedRow` - 改为通过 rowId 清除 `excluded` 字段
|
||||
- `getInsertRows` - 从 `draftStateMap` 中筛选
|
||||
- `getSelectedRows` - 从 `draftStateMap` 中筛选
|
||||
- `getExcludedRows` - 从 `draftStateMap` 中筛选
|
||||
- `getValidSelectedRows` - 从 `draftStateMap` 中筛选
|
||||
|
||||
### 步骤 2:applyDraftState 纯函数化
|
||||
**文件:** `js/feegrid-manager.js`(第 161-185 行)
|
||||
|
||||
**修改内容:**
|
||||
- 使用深拷贝创建新对象
|
||||
- 不修改原始输入参数
|
||||
- 返回全新的 displayRows 数组
|
||||
|
||||
### 步骤 3:请求版本化
|
||||
**文件:** `js/feegrid-manager.js`(第 441-490 行)
|
||||
|
||||
**修改内容:**
|
||||
- 在 `createDataGridManager` 中添加 `requestVersion` 变量
|
||||
- 修改 `loadPage` 函数,递增版本号并在响应时检查
|
||||
|
||||
### 步骤 4:recoverRow 触发刷新
|
||||
**文件:** `js/feegrid-manager.js`(第 100-114 行)
|
||||
|
||||
**修改内容:**
|
||||
- 在 `recoverRow` 函数末尾调用 `refreshCurrentPage()`
|
||||
|
||||
### 步骤 5:修复 insertRows 与 draftStateMap 同步问题
|
||||
**文件:** `js/feegrid-manager.js`(第 89-98 行)
|
||||
|
||||
**修改内容:**
|
||||
- 在 `addDeleteRow` 中同步清理 insert 状态(已通过单源化解决)
|
||||
|
||||
### 步骤 6:修改 _syncGridChecks 逻辑
|
||||
**文件:** `js/feegrid-manager.js`(第 377-409 行)
|
||||
|
||||
**修改内容:**
|
||||
- 根据 `draftStateMap` 中的 `selected` 和 `excluded` 字段同步 UI
|
||||
- 不再依赖 `selectedRows` 和 `excludedRows` 数组
|
||||
|
||||
---
|
||||
|
||||
## 五、风险处理
|
||||
|
||||
### 5.1 兼容性风险
|
||||
|
||||
**风险:** 外部调用者可能依赖 `getSelectedRows()` 返回行对象数组
|
||||
|
||||
**处理:**
|
||||
- 保持 API 接口不变,内部实现改为从 `draftStateMap` 中筛选
|
||||
- 返回的行对象从当前 grid 数据中获取(保持引用一致性)
|
||||
|
||||
### 5.2 性能风险
|
||||
|
||||
**风险:** 深拷贝可能影响性能
|
||||
|
||||
**处理:**
|
||||
- 只在必要时(applyDraftState)进行深拷贝
|
||||
- 考虑使用结构化克隆或 JSON 序列化(视浏览器支持)
|
||||
|
||||
### 5.3 测试风险
|
||||
|
||||
**风险:** 重构可能引入新的 bug
|
||||
|
||||
**处理:**
|
||||
- 按步骤逐步修改,每步进行功能验证
|
||||
- 重点测试以下场景:
|
||||
- 快速分页切换
|
||||
- insert → update → delete → recover
|
||||
- 搜索后 recover
|
||||
- reload 后 selection 保持
|
||||
|
||||
---
|
||||
|
||||
## 六、测试用例
|
||||
|
||||
### 场景 1:快速分页切换
|
||||
1. 快速点击第1页→第2页→第3页
|
||||
2. 验证最终显示正确页面数据
|
||||
3. 验证分页控件显示正确页码
|
||||
|
||||
### 场景 2:状态流转
|
||||
1. update → delete → recover
|
||||
2. 验证 recover 后状态正确(应为原始状态或 update 状态)
|
||||
3. 验证页面显示恢复的行
|
||||
|
||||
### 场景 3:insert → delete
|
||||
1. insert 行 → delete 该行
|
||||
2. 验证 insertRows 不包含已删除行
|
||||
3. 验证页面不显示已删除行
|
||||
|
||||
### 场景 4:reload 后 selection 保持
|
||||
1. 选中某行 → reload
|
||||
2. 验证选中状态保持
|
||||
|
||||
### 场景 5:搜索后 recover
|
||||
1. update → delete → search → recover
|
||||
2. 验证 recover 后页面显示恢复的行
|
||||
|
||||
---
|
||||
|
||||
## 七、预期代码变更量
|
||||
|
||||
| 修改类型 | 预估行数 | 说明 |
|
||||
|---------|---------|------|
|
||||
| 删除 | ~50 | 移除 insertRows/selectedRows/excludedRows 相关代码 |
|
||||
| 修改 | ~100 | 修改状态管理函数 |
|
||||
| 新增 | ~50 | 请求版本化、深拷贝等 |
|
||||
| 总计 | ~200 | 核心逻辑重构 |
|
||||
|
||||
---
|
||||
|
||||
## 八、实施顺序
|
||||
|
||||
1. **步骤 1** - 状态结构重构(核心)
|
||||
2. **步骤 2** - applyDraftState 纯函数化
|
||||
3. **步骤 3** - 请求版本化
|
||||
4. **步骤 4** - recoverRow 触发刷新
|
||||
5. **步骤 5** - 已通过步骤 1 解决
|
||||
6. **步骤 6** - 修改 _syncGridChecks 逻辑
|
||||
|
||||
> 注:步骤 1 是基础,必须首先完成。后续步骤可并行或按顺序进行。
|
||||
@@ -0,0 +1,400 @@
|
||||
# Feegrid 模块深度重构计划
|
||||
|
||||
## 1. 现状分析
|
||||
|
||||
### 1.1 现有代码结构
|
||||
|
||||
| 文件 | 职责 | 问题 |
|
||||
| -------------------------- | ---------------------- | --------------- |
|
||||
| `editorder_s_cw.jsp` | 业务页面,包含大量 feegrid 操作代码 | 通用逻辑与业务代码混杂 |
|
||||
| `js/draft-page-manager.js` | 草稿态分页管理核心模块 | 已完成基础封装,但仍需上层整合 |
|
||||
|
||||
### 1.2 需要封装的通用逻辑
|
||||
|
||||
通过分析 `editorder_s_cw.jsp`,以下代码模式重复出现且属于通用行为:
|
||||
|
||||
**Loading/Loaded 状态控制(散落在多个函数中):**
|
||||
|
||||
```js
|
||||
$('#feegrid').datagrid('loading');
|
||||
$('#subgridPager').css({ 'pointer-events': 'none', 'opacity': '0.6' });
|
||||
$('#subgridPager').pagination('loading');
|
||||
// ... 数据加载完成后
|
||||
$('#feegrid').datagrid('loaded');
|
||||
$('#subgridPager').css({ 'pointer-events': '', 'opacity': '' });
|
||||
$('#subgridPager').pagination('loaded');
|
||||
```
|
||||
|
||||
**分页刷新逻辑:**
|
||||
|
||||
```js
|
||||
$('#feegrid').datagrid('loadData', pageData);
|
||||
$('#subgridPager').pagination('refresh', { total, pageNumber, pageSize });
|
||||
```
|
||||
|
||||
**选中状态同步:**
|
||||
|
||||
```js
|
||||
syncFeeGridChecksFromSubState(); // 分页切换后同步勾选状态
|
||||
```
|
||||
|
||||
**行状态样式处理:**
|
||||
|
||||
```js
|
||||
// rowStyler 根据 _rowState 设置背景色
|
||||
// onLoadSuccess 中处理删除行样式
|
||||
```
|
||||
|
||||
### 1.3 当前问题总结
|
||||
|
||||
1. **内聚性不足**:loading、pager、datagrid 状态控制分散在业务代码中
|
||||
2. **耦合度高**:业务页面直接操作 DOM 和 datagrid API
|
||||
3. **复用性差**:相同的状态管理逻辑在多个地方重复
|
||||
4. **初始化暴露**:业务代码需要显式创建 draftManager
|
||||
|
||||
***
|
||||
|
||||
## 2. 重构目标
|
||||
|
||||
### 2.1 架构设计
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 业务页面 (editorder_s_cw.jsp) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 配置项 + 业务回调(仅关心业务逻辑) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ 配置/回调
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FeegridManager (feegrid-manager.js) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 高阶封装层:datagrid + pager + state 的统一管理 │ │
|
||||
│ │ - 初始化整合 │ │
|
||||
│ │ - loading/loaded 状态自动管理 │ │
|
||||
│ │ - 分页切换与选中状态同步 │ │
|
||||
│ │ - 行状态样式自动处理 │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ 内部调用
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ DraftPageManager (draft-page-manager.js) │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 低阶封装层:草稿池 + 分页 + 选中状态核心逻辑 │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.2 模块划分
|
||||
|
||||
| 模块 | 职责 | 文件 |
|
||||
| -------------------- | -------------------------------- | ------------------------------ |
|
||||
| **FeegridManager** | 高阶整合层,管理 datagrid + pager + 状态同步 | `js/feegrid-manager.js` |
|
||||
| **DraftPageManager** | 低阶核心层,草稿态分页核心逻辑 | `js/draft-page-manager.js`(现有) |
|
||||
|
||||
***
|
||||
|
||||
## 3. 重构方案
|
||||
|
||||
### 3.1 创建 FeegridManager 模块
|
||||
|
||||
**文件路径**:`js/feegrid-manager.js`
|
||||
|
||||
**核心职责**:
|
||||
|
||||
* 统一管理 datagrid 和 pager 的初始化
|
||||
|
||||
* 自动处理 loading/loaded 状态
|
||||
|
||||
* 封装分页数据加载逻辑
|
||||
|
||||
* 自动同步选中状态
|
||||
|
||||
* 处理行状态样式
|
||||
|
||||
**API 设计**:
|
||||
|
||||
| 方法 | 说明 | 参数 |
|
||||
| ------------------------ | -------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| `create(config)` | 创建 FeegridManager 实例 | `config.gridId`, `config.pagerId`, `config.keyField`, `config.pageSize`, `config.callbacks` |
|
||||
| `loadPage(pageNumber)` | 加载指定页码数据 | `pageNumber`: 页码 |
|
||||
| `refreshCurrentPage()` | 刷新当前页 | 无 |
|
||||
| `refreshDisplay()` | 仅刷新当前页显示 | 无 |
|
||||
| `refreshAfterDelete()` | 删除后刷新 | 无 |
|
||||
| `refreshAfterUndo()` | 撤销删除后刷新 | 无 |
|
||||
| `addInsertRows(rows)` | 添加新行到草稿池 | `rows`: 行数据数组 |
|
||||
| `removeInsertRow(row)` | 从草稿池移除行 | `row`: 行数据 |
|
||||
| `addDeleteRow(row)` | 标记行为删除状态 | `row`: 行数据 |
|
||||
| `removeDeleteRow(row)` | 撤销删除标记 | `row`: 行数据 |
|
||||
| `addUpdateRow(row)` | 添加到更新队列 | `row`: 行数据 |
|
||||
| `getValidSelectedRows()` | 获取有效的选中行 | 无 |
|
||||
| `toggleSelectAll()` | 全选/反选切换 | 无 |
|
||||
| `clearSelection()` | 清除选中状态 | 无 |
|
||||
| `setAllChecked(value)` | 设置全选状态 | `value`: boolean |
|
||||
| `reset()` | 重置所有状态 | 无 |
|
||||
|
||||
**配置项结构**:
|
||||
|
||||
```js
|
||||
{
|
||||
gridId: 'feegrid', // datagrid DOM ID
|
||||
pagerId: 'subgridPager', // pagination DOM ID
|
||||
keyField: 'subid', // 主键字段名
|
||||
pageSize: 50, // 每页条数
|
||||
viewName: '', // 查询视图名
|
||||
callbacks: {
|
||||
// 数据加载前回调
|
||||
beforeLoad: function() {},
|
||||
// 数据加载成功回调
|
||||
onLoadSuccess: function(rows) {},
|
||||
// 行编辑完成回调
|
||||
onAfterEdit: function(rowIndex, row, changes) {},
|
||||
// 选中状态变化回调(用于金额计算等)
|
||||
onSelectionChange: function() {}
|
||||
},
|
||||
// 行样式配置
|
||||
rowStateStyles: {
|
||||
insert: 'background-color: #f6ffed;',
|
||||
update: 'background-color: #fffbe6;',
|
||||
delete: 'background-color: #fff1f0;'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 修改业务页面
|
||||
|
||||
**目标**:业务页面仅保留配置和业务回调,移除所有通用逻辑代码。
|
||||
|
||||
**重构后的业务页面结构**:
|
||||
|
||||
```html
|
||||
<script type="text/javascript">
|
||||
// 1. 定义配置
|
||||
var feegridConfig = {
|
||||
gridId: 'feegrid',
|
||||
pagerId: 'subgridPager',
|
||||
keyField: 'subid',
|
||||
pageSize: 50,
|
||||
viewName: 'v_cwzd_orderfee_s',
|
||||
callbacks: {
|
||||
onLoadSuccess: function(rows) {
|
||||
// 业务逻辑:计算金额
|
||||
calcw_selfee();
|
||||
},
|
||||
onAfterEdit: function(rowIndex, row, changes) {
|
||||
// 业务逻辑:重新计算费用
|
||||
calcw_fee();
|
||||
},
|
||||
onSelectionChange: function() {
|
||||
// 业务逻辑:更新选中金额显示
|
||||
calcw_selfee();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 初始化(业务层无需感知内部实现)
|
||||
var feeGrid = createFeegridManager(feegridConfig);
|
||||
|
||||
// 3. 业务操作中调用
|
||||
function saveData() {
|
||||
// 获取草稿数据用于保存
|
||||
var insertRows = feeGrid.getInsertRows();
|
||||
var updateRows = feeGrid.getUpdateRows();
|
||||
var deleteRows = feeGrid.getDeleteRows();
|
||||
// ... 保存逻辑
|
||||
}
|
||||
|
||||
function removeit() {
|
||||
// 删除选中行
|
||||
feeGrid.deleteSelectedRows();
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 4. 具体实施步骤
|
||||
|
||||
### 4.1 步骤一:创建 FeegridManager 模块
|
||||
|
||||
**文件**:`js/feegrid-manager.js`
|
||||
|
||||
封装内容:
|
||||
|
||||
1. datagrid 初始化配置
|
||||
2. pager 初始化配置
|
||||
3. loading/loaded 状态自动管理
|
||||
4. 分页数据加载封装
|
||||
5. 选中状态同步
|
||||
6. 行状态样式处理
|
||||
|
||||
### 4.2 步骤二:修改 DraftPageManager(可选优化)
|
||||
|
||||
* 保持现有 API 稳定
|
||||
|
||||
* 可能需要增加一些内部方法供 FeegridManager 调用
|
||||
|
||||
### 4.3 步骤三:重构业务页面 `editorder_s_cw.jsp`
|
||||
|
||||
**需要移除的代码模式**:
|
||||
|
||||
1. **Loading 状态控制**:
|
||||
|
||||
* `$('#feegrid').datagrid('loading')`
|
||||
|
||||
* `$('#feegrid').datagrid('loaded')`
|
||||
|
||||
* `$('#subgridPager').pagination('loading')`
|
||||
|
||||
* `$('#subgridPager').pagination('loaded')`
|
||||
|
||||
* `$('#subgridPager').css({...})`
|
||||
|
||||
2. **分页数据加载**:
|
||||
|
||||
* `loaddata_sub()` 函数整体移除
|
||||
|
||||
* `refreshCurrentPage()` 函数移除
|
||||
|
||||
* `refreshCurrentPageDisplay()` 函数移除
|
||||
|
||||
* `refreshCurrentPageAfterDelete()` 函数移除
|
||||
|
||||
* `refreshCurrentPageAfterUndo()` 函数移除
|
||||
|
||||
3. **选中状态同步**:
|
||||
|
||||
* `syncFeeGridChecksFromSubState()` 函数移除
|
||||
|
||||
* `pruneSelectedRowsForCurrentPage()` 函数移除
|
||||
|
||||
4. **初始化代码**:
|
||||
|
||||
* `var draftManager = window.DraftPageManager.createDraftManager(...)` 移除
|
||||
|
||||
* datagrid 初始化配置移到 FeegridManager
|
||||
|
||||
* pager 初始化配置移到 FeegridManager
|
||||
|
||||
### 4.4 步骤四:测试验证
|
||||
|
||||
1. 测试基本功能:加载、分页、选中、删除、撤销
|
||||
2. 测试状态同步:分页切换后选中状态保持
|
||||
3. 测试 loading 状态:加载过程中按钮禁用
|
||||
4. 测试行样式:新增/修改/删除行样式正确
|
||||
|
||||
***
|
||||
|
||||
## 5. 代码规范
|
||||
|
||||
### 5.1 命名规范
|
||||
|
||||
| 类型 | 规则 | 示例 |
|
||||
| ---- | -------- | -------------------------------------- |
|
||||
| 模块名 | 小写 + 连字符 | `feegrid-manager.js` |
|
||||
| 函数名 | 驼峰式 | `createFeegridManager`, `loadPageData` |
|
||||
| 变量名 | 驼峰式 | `gridId`, `rowStateStyles` |
|
||||
| 常量名 | 大写 + 下划线 | `DEFAULT_PAGE_SIZE` |
|
||||
| 内部方法 | 下划线开头 | `_handleLoading`, `_syncCheckStates` |
|
||||
|
||||
### 5.2 注释规范
|
||||
|
||||
* 模块开头:说明模块职责和使用方式
|
||||
|
||||
* 函数/方法:说明功能、参数、返回值
|
||||
|
||||
* 关键逻辑:简要说明设计意图
|
||||
|
||||
* 避免冗余注释:不注释显而易见的代码
|
||||
|
||||
### 5.3 文件结构
|
||||
|
||||
```
|
||||
js/
|
||||
├── feegrid-manager.js # 高阶整合模块(新增)
|
||||
├── draft-page-manager.js # 低阶核心模块(现有)
|
||||
└── draft-page-manager.js # 保持不变
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 6. 风险与应对
|
||||
|
||||
### 6.1 风险识别
|
||||
|
||||
| 风险 | 影响 | 应对措施 |
|
||||
| -------------- | -------------- | -------------------------- |
|
||||
| API 变更导致业务代码出错 | 功能异常 | 保持 DraftPageManager API 兼容 |
|
||||
| 状态同步逻辑复杂 | 选中状态错误 | 单元测试覆盖核心场景 |
|
||||
| loading 状态管理不当 | UI 卡顿或按钮可用状态错误 | 统一状态管理机制 |
|
||||
| 浏览器兼容性 | 部分功能不生效 | 使用 jQuery 标准 API |
|
||||
|
||||
### 6.2 测试要点
|
||||
|
||||
1. **分页加载**:远程数据 + 本地插入数据混合展示
|
||||
2. **选中状态**:跨页选中、全选后取消个别、分页切换后状态保持
|
||||
3. **删除/撤销**:标记删除、撤销删除、物理删除
|
||||
4. **loading 状态**:加载过程中按钮禁用、加载完成后恢复
|
||||
5. **行样式**:新增(绿色)、修改(黄色)、删除(红色)样式正确
|
||||
|
||||
***
|
||||
|
||||
## 7. 预期成果
|
||||
|
||||
### 7.1 业务页面变化
|
||||
|
||||
**重构前**:约 800+ 行通用逻辑代码(loaddata\_sub、refresh 系列函数等)
|
||||
|
||||
**重构后**:仅保留配置项和业务回调(约 50 行)
|
||||
|
||||
### 7.2 代码对比
|
||||
|
||||
**重构前业务代码**:
|
||||
|
||||
```js
|
||||
function loaddata_sub() {
|
||||
$('#feegrid').datagrid('loading');
|
||||
$('#subgridPager').css({ 'pointer-events': 'none', 'opacity': '0.6' });
|
||||
$('#subgridPager').pagination('loading');
|
||||
|
||||
var pagination = draftManager.getPagination();
|
||||
// ... 大量分页逻辑
|
||||
|
||||
$.ajax({
|
||||
// ...
|
||||
complete: function() {
|
||||
$('#feegrid').datagrid('loaded');
|
||||
$('#subgridPager').css({ 'pointer-events': '', 'opacity': '' });
|
||||
$('#subgridPager').pagination('loaded');
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**重构后业务代码**:
|
||||
|
||||
```js
|
||||
// 配置
|
||||
var feeGrid = createFeegridManager({
|
||||
gridId: 'feegrid',
|
||||
|
||||
viewName: 'v_cwzd_orderfee_s',
|
||||
onLoadSuccess: calcw_selfee
|
||||
});
|
||||
|
||||
// 使用
|
||||
feeGrid.loadPage(1); // 加载第一页
|
||||
feeGrid.refreshCurrentPage(); // 刷新当前页
|
||||
```
|
||||
|
||||
### 7.3 复用性提升
|
||||
|
||||
重构后,其他页面如需使用类似功能,只需:
|
||||
|
||||
1. 引入 `feegrid-manager.js`
|
||||
2. 配置 gridId、pagerId、viewName
|
||||
3. 设置业务回调
|
||||
|
||||
无需重复编写分页、选中、loading 等通用逻辑。
|
||||
@@ -0,0 +1,162 @@
|
||||
# Feegrid 模块重构计划
|
||||
|
||||
## 1. 修改内容
|
||||
|
||||
### 1.1 JS 文件修改
|
||||
|
||||
| 修改项 | 原内容 | 新内容 | 说明 |
|
||||
|--------|--------|--------|------|
|
||||
| 函数名 | `createFeegridManager` | `createDataGridManager` | 更通用 |
|
||||
| callbacks 配置 | `callbacks: { onLoadSuccess, onAfterEdit, onSelectionChange }` | 直接展开 `onLoadSuccess, onAfterEdit, onSelectionChange` | 简化配置 |
|
||||
| pagerId 配置 | `pagerId: 'subgridPager'` | 移除 | 自动创建,无需配置 |
|
||||
| 全部选中/反选 | JSP 中有 wrapper 函数 | JS 中自动管理按钮文字 | 进一步解耦 |
|
||||
|
||||
### 1.2 JSP 文件修改
|
||||
|
||||
| 修改项 | 说明 |
|
||||
|--------|------|
|
||||
| 删除 `subgridPager` div | 由 JS 自动创建 |
|
||||
| 简化 `toggleSelectAll` 函数 | 直接调用 JS 方法 |
|
||||
| 变量名可保持或改为更通用的名字 | 可选 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 重构后的 API
|
||||
|
||||
```js
|
||||
// 之前
|
||||
var feeGrid = createFeegridManager({
|
||||
gridId: 'feegrid',
|
||||
pagerId: 'subgridPager',
|
||||
keyField: 'subid',
|
||||
pageSize: 50,
|
||||
viewName: subviewtable,
|
||||
callbacks: {
|
||||
onLoadSuccess: function(rows) { calcw_selfee(); },
|
||||
onAfterEdit: function(rowIndex, row, changes) { calcw_fee(); },
|
||||
onSelectionChange: function() { calcw_selfee(); }
|
||||
}
|
||||
});
|
||||
|
||||
// 之后
|
||||
var gridManager = createDataGridManager({
|
||||
gridId: 'feegrid',
|
||||
keyField: 'subid',
|
||||
pageSize: 50,
|
||||
viewName: subviewtable,
|
||||
searchUrl: 'jsp/searchdatapagenew.jsp',
|
||||
orderField: 'father ASC, mx_xh ASC, b_id ASC',
|
||||
onLoadSuccess: function(rows) { calcw_selfee(); },
|
||||
onAfterEdit: function(rowIndex, row, changes) { calcw_fee(); },
|
||||
onSelectionChange: function() { calcw_selfee(); }
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. JSP 中需要修改的部分
|
||||
|
||||
### 3.1 删除的分页器 HTML(自动创建)
|
||||
```html
|
||||
<!-- 删除这行 -->
|
||||
<div id="subgridPager" class="easyui-pagination" style="margin: 6px 0px;background:#efefef;border:1px solid #ccc;"></div>
|
||||
```
|
||||
|
||||
### 3.2 简化的 `toggleSelectAll` 函数
|
||||
```js
|
||||
// 之前:复杂的判断逻辑
|
||||
function toggleSelectAll() {
|
||||
if (feeGridManager.getDraftManager().isAllChecked()) {
|
||||
feeGridManager.toggleSelectAll();
|
||||
$('#btn_select_all').val('全部选中');
|
||||
} else {
|
||||
feeGridManager.toggleSelectAll();
|
||||
$('#btn_select_all').val('全部反选');
|
||||
}
|
||||
}
|
||||
|
||||
// 之后:直接调用
|
||||
function toggleSelectAll() {
|
||||
gridManager.toggleSelectAll();
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 更新的初始化代码
|
||||
```js
|
||||
// 之前
|
||||
feeGridManager = createFeegridManager({
|
||||
gridId: 'feegrid',
|
||||
pagerId: 'subgridPager',
|
||||
keyField: 'subid',
|
||||
pageSize: 50,
|
||||
viewName: subviewtable,
|
||||
callbacks: {
|
||||
onLoadSuccess: function(rows) { calcw_selfee(); },
|
||||
onAfterEdit: function(rowIndex, row, changes) { calcw_fee(); },
|
||||
onSelectionChange: function() { calcw_selfee(); }
|
||||
}
|
||||
});
|
||||
|
||||
// 之后
|
||||
gridManager = createDataGridManager({
|
||||
gridId: 'feegrid',
|
||||
keyField: 'subid',
|
||||
pageSize: 50,
|
||||
viewName: subviewtable,
|
||||
orderField: 'father ASC, mx_xh ASC, b_id ASC',
|
||||
onLoadSuccess: function(rows) { calcw_selfee(); },
|
||||
onAfterEdit: function(rowIndex, row, changes) { calcw_fee(); },
|
||||
onSelectionChange: function() { calcw_selfee(); }
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 检查 JSP 中未使用的代码
|
||||
|
||||
| 函数/变量 | 状态 | 说明 |
|
||||
|-----------|------|------|
|
||||
| `subdata_zdfees` | 未使用 | 可以删除 |
|
||||
| `feegridjson` | 未使用 | 可以删除 |
|
||||
| `loaddata_subdata_zdfees` | 未使用 | 可以删除 |
|
||||
| `loaddata_detail` | 部分使用 | 在 `newSKData`/`newIVData` 的 onClose 中调用 |
|
||||
| `removeit_all` | 注释掉 | 已废弃 |
|
||||
| `getSelections` | 未使用 | 可以删除 |
|
||||
|
||||
---
|
||||
|
||||
## 5. searchCondition 功能
|
||||
|
||||
代码中已有 `searchCondition` 配置项和 `setSearchCondition` 方法,JSP 中通过 `setMasterId` 设置主表ID后,会自动生成 `mx_zdorderid='xxx'` 的查询条件。
|
||||
|
||||
如需自定义查询条件,可使用:
|
||||
```js
|
||||
gridManager.setSearchCondition("mx_dw_id='xxx'");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 实施步骤
|
||||
|
||||
1. 修改 `feegrid-manager.js`:
|
||||
- 重命名函数为 `createDataGridManager`
|
||||
- 移除 `callbacks` 包装,直接展开回调配置
|
||||
- 移除 `pagerId` 配置
|
||||
- 完善 `toggleSelectAll` 自动管理按钮文字
|
||||
- 更新默认值配置
|
||||
|
||||
2. 修改 `editorder_s_cw.jsp`:
|
||||
- 删除 `subgridPager` div
|
||||
- 简化 `toggleSelectAll` 函数
|
||||
- 更新初始化代码
|
||||
- 删除未使用的变量和函数
|
||||
- 变量名改为更通用的 `gridManager`
|
||||
|
||||
---
|
||||
|
||||
## 7. 预期结果
|
||||
|
||||
- **API 简化**:从 `createFeegridManager({ callbacks: {...} })` 变为 `createDataGridManager({ onLoadSuccess, onAfterEdit, onSelectionChange })`
|
||||
- **配置减少**:移除 `pagerId`,无需在 HTML 中定义分页器
|
||||
- **代码清理**:删除未使用的函数和变量
|
||||
- **更好的复用性**:通用名称可在其他页面直接使用
|
||||
@@ -0,0 +1,155 @@
|
||||
# Feegrid 模块合并重构计划
|
||||
|
||||
## 1. 现状分析
|
||||
|
||||
### 1.1 当前文件结构
|
||||
|
||||
| 文件 | 行数 | 职责 |
|
||||
|------|------|------|
|
||||
| `js/draft-page-manager.js` | 439行 | 草稿态分页核心模块(底层) |
|
||||
| `js/feegrid-manager.js` | 772行 | Feegrid 高阶管理模块(依赖上层) |
|
||||
|
||||
**问题**:两个文件分离,DraftPageManager 被 FeegridManager 依赖,但业务页面需要同时引入两个文件,增加了复杂性。
|
||||
|
||||
### 1.2 用户需求
|
||||
|
||||
- 将公共代码统一到一个 JS 文件中
|
||||
- 不拆分多个 JS 文件
|
||||
- 代码放在一起方便维护
|
||||
- 保持单一文件的简单结构
|
||||
|
||||
---
|
||||
|
||||
## 2. 重构目标
|
||||
|
||||
### 2.1 合并方案
|
||||
|
||||
将 `draft-page-manager.js` 和 `feegrid-manager.js` 合并为一个文件 `js/feegrid-manager.js`。
|
||||
|
||||
合并后文件结构:
|
||||
```
|
||||
js/feegrid-manager.js
|
||||
├── DraftPageManager (内部模块,对外不可见)
|
||||
│ ├── 状态管理
|
||||
│ ├── 草稿池操作
|
||||
│ ├── 选中状态操作
|
||||
│ ├── 分页操作
|
||||
│ └── 工具方法
|
||||
│
|
||||
└── createFeegridManager (导出函数)
|
||||
├── 初始化方法
|
||||
├── 数据加载方法
|
||||
├── 草稿池操作方法
|
||||
├── 选中状态方法
|
||||
└── 配置方法
|
||||
```
|
||||
|
||||
### 2.2 优势
|
||||
|
||||
1. **单一文件**:只需引入一个 JS 文件
|
||||
2. **内聚性**:DraftPageManager 作为内部实现细节,不暴露给业务代码
|
||||
3. **简化引用**:业务页面只需 `createFeegridManager`
|
||||
4. **命名空间干净**:不再污染 window.DraftPageManager
|
||||
|
||||
---
|
||||
|
||||
## 3. 重构步骤
|
||||
|
||||
### 3.1 步骤一:合并文件
|
||||
|
||||
将 `draft-page-manager.js` 的内容作为 `js/feegrid-manager.js` 的内部模块,移除 `window.DraftPageManager` 挂载。
|
||||
|
||||
### 3.2 步骤二:修改 DraftPageManager 引用
|
||||
|
||||
将 `feegrid-manager.js` 中所有 `window.DraftPageManager.createDraftManager` 调用改为内部引用。
|
||||
|
||||
### 3.3 步骤三:清理导出
|
||||
|
||||
移除 `window.createFeegridManager` 的 UMD 封装,改为直接 `window.createFeegridManager = createFeegridManager`。
|
||||
|
||||
### 3.4 步骤四:更新 JSP 引用
|
||||
|
||||
将 JSP 页面中的引用从两个文件改为单一文件:
|
||||
```html
|
||||
<!-- 之前 -->
|
||||
<script src="js/draft-page-manager.js"></script>
|
||||
<script src="js/feegrid-manager.js"></script>
|
||||
|
||||
<!-- 之后 -->
|
||||
<script src="js/feegrid-manager.js"></script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 文件结构设计
|
||||
|
||||
### 4.1 合并后的文件
|
||||
|
||||
文件路径:`js/feegrid-manager.js`
|
||||
|
||||
```js
|
||||
/**
|
||||
* Feegrid 公共管理模块
|
||||
*
|
||||
* 包含:
|
||||
* 1. DraftPageManager - 草稿态分页核心(内部模块)
|
||||
* 2. createFeegridManager - Feegrid 高阶管理器(导出)
|
||||
*/
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
// ==================== DraftPageManager(内部模块)====================
|
||||
|
||||
function createDraftManager(config) {
|
||||
// ... draft-page-manager.js 的全部逻辑
|
||||
}
|
||||
|
||||
// ==================== createFeegridManager(导出)====================
|
||||
|
||||
function createFeegridManager(config) {
|
||||
// ... feegrid-manager.js 的全部逻辑
|
||||
// 内部调用 createDraftManager 而非 window.DraftPageManager
|
||||
}
|
||||
|
||||
// 导出到 window
|
||||
window.createFeegridManager = createFeegridManager;
|
||||
|
||||
})(window);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 预期成果
|
||||
|
||||
### 5.1 文件变化
|
||||
|
||||
| 操作 | 文件 |
|
||||
|------|------|
|
||||
| 删除 | `js/draft-page-manager.js` |
|
||||
| 合并+替换 | `js/feegrid-manager.js` |
|
||||
| 更新引用 | `editorder_s_cw.jsp` |
|
||||
|
||||
### 5.2 引用简化
|
||||
|
||||
**之前(JSP 中)**:
|
||||
```html
|
||||
<script src="js/draft-page-manager.js"></script>
|
||||
<script src="js/feegrid-manager.js"></script>
|
||||
```
|
||||
|
||||
**之后(JSP 中)**:
|
||||
```html
|
||||
<script src="js/feegrid-manager.js"></script>
|
||||
```
|
||||
|
||||
### 5.3 业务代码不变
|
||||
|
||||
`createFeegridManager` 的 API 保持不变,业务代码无需修改。
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险与注意事项
|
||||
|
||||
1. **保持 API 兼容**:合并后 `createFeegridManager` 的接口不变
|
||||
2. **删除源文件**:确认合并成功后删除 `draft-page-manager.js`
|
||||
3. **测试验证**:合并后需要测试分页、选中、删除、撤销等功能
|
||||
@@ -0,0 +1,225 @@
|
||||
# 搜索场景下本地数据处理方案
|
||||
|
||||
## 场景分析
|
||||
|
||||
### 场景一:新增数据后的搜索
|
||||
|
||||
**问题**:用户新增了10条数据(仅在本地,未保存到数据库),执行 LIKE 搜索时,这些本地数据无法被搜索到。
|
||||
|
||||
**期望**:搜索结果应包含本地新增的行,且支持 LIKE 模糊匹配。
|
||||
|
||||
### 场景二:更新数据后的搜索
|
||||
|
||||
**问题**:用户更新了10条数据(本地已修改),执行搜索时,远程返回的是旧数据,本地修改的数据可能不符合搜索条件。
|
||||
|
||||
**期望**:搜索结果应使用本地更新后的数据进行匹配,而不是远程的旧数据。
|
||||
|
||||
***
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案核心思路
|
||||
|
||||
1. **本地数据也参与搜索过滤**:在搜索时,对本地的 `insertRows` 和 `updateRows` 也应用搜索条件过滤
|
||||
2. **合并展示**:将远程搜索结果与本地过滤后的结果合并展示
|
||||
3. **保持状态**:保留本地数据的 `_rowState` 标记
|
||||
|
||||
### 修改内容
|
||||
|
||||
#### 1. JS 文件修改 (`js/feegrid-manager.js`)
|
||||
|
||||
**新增方法:**
|
||||
|
||||
* `_filterLocalRows(condition)` - 根据搜索条件过滤本地数据
|
||||
|
||||
* 修改 `loadPage()` - 在加载远程数据后,合并本地过滤后的数据
|
||||
|
||||
**搜索条件解析**:
|
||||
|
||||
* 支持 `LIKE '%xxx%'` 模糊匹配
|
||||
|
||||
* 支持 `=`, `!=`, `<`, `>`, `<=`, `>=` 比较
|
||||
|
||||
* 支持 `AND`, `OR` 逻辑(简化版)
|
||||
|
||||
#### 2. 修改 `loadPage()` 方法
|
||||
|
||||
```js
|
||||
function loadPage(pageNumber) {
|
||||
_setLoading();
|
||||
|
||||
var pagination = state.draftManager.getPagination();
|
||||
var pageSize = pagination.pageSize;
|
||||
var remoteTotal = pagination.remoteTotal;
|
||||
var insertRows = state.draftManager.getInsertRows();
|
||||
var updateRows = state.draftManager.getUpdateRows();
|
||||
|
||||
// 获取搜索条件
|
||||
var searchCondition = options.searchCondition ||
|
||||
options.keyField.replace('subid', 'mx_') + "='" + state.masterId + "'";
|
||||
|
||||
// 过滤本地数据
|
||||
var filteredInserts = _filterLocalRows(insertRows, searchCondition);
|
||||
var filteredUpdates = _filterLocalRows(updateRows, searchCondition);
|
||||
|
||||
// ... 原有远程加载逻辑 ...
|
||||
|
||||
// 合并结果时,使用本地过滤后的数据
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 新增条件解析工具函数
|
||||
|
||||
```js
|
||||
function _filterLocalRows(rows, condition) {
|
||||
if (!condition || condition.trim() === '') {
|
||||
return rows;
|
||||
}
|
||||
|
||||
var filtered = [];
|
||||
var conditions = _parseCondition(condition);
|
||||
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
var row = rows[i];
|
||||
if (_matchesConditions(row, conditions)) {
|
||||
filtered.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function _parseCondition(condition) {
|
||||
var result = [];
|
||||
|
||||
var parts = condition.split(/\s+(AND|OR)\s+/i);
|
||||
for (var i = 0; i < parts.length; i += 2) {
|
||||
var expr = parts[i].trim();
|
||||
var operator = parts[i + 1] ? parts[i + 1].toUpperCase() : 'AND';
|
||||
|
||||
var match;
|
||||
|
||||
// LIKE 匹配
|
||||
if ((match = expr.match(/^\s*(\w+)\s+LIKE\s+['"](.*)['"]\s*$/i))) {
|
||||
result.push({
|
||||
field: match[1],
|
||||
operator: 'LIKE',
|
||||
value: match[2],
|
||||
logic: operator
|
||||
});
|
||||
}
|
||||
// 等于匹配
|
||||
else if ((match = expr.match(/^\s*(\w+)\s*=\s*['"](.*)['"]\s*$/))) {
|
||||
result.push({
|
||||
field: match[1],
|
||||
operator: '=',
|
||||
value: match[2],
|
||||
logic: operator
|
||||
});
|
||||
}
|
||||
// 不等于匹配
|
||||
else if ((match = expr.match(/^\s*(\w+)\s*(!=|<>)=\s*['"](.*)['"]\s*$/))) {
|
||||
result.push({
|
||||
field: match[1],
|
||||
operator: '!=',
|
||||
value: match[2],
|
||||
logic: operator
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function _matchesConditions(row, conditions) {
|
||||
if (conditions.length === 0) return true;
|
||||
|
||||
var result = true;
|
||||
|
||||
for (var i = 0; i < conditions.length; i++) {
|
||||
var cond = conditions[i];
|
||||
var fieldValue = row[cond.field];
|
||||
|
||||
if (fieldValue === undefined || fieldValue === null) {
|
||||
fieldValue = '';
|
||||
}
|
||||
|
||||
var fieldStr = String(fieldValue).toLowerCase();
|
||||
var valueStr = String(cond.value).toLowerCase();
|
||||
var matches = false;
|
||||
|
||||
switch (cond.operator) {
|
||||
case 'LIKE':
|
||||
var pattern = valueStr
|
||||
.replace(/%/g, '.*')
|
||||
.replace(/_/g, '.');
|
||||
matches = new RegExp('^' + pattern + '$').test(fieldStr);
|
||||
break;
|
||||
|
||||
case '=':
|
||||
matches = fieldStr === valueStr;
|
||||
break;
|
||||
|
||||
case '!=':
|
||||
matches = fieldStr !== valueStr;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cond.logic === 'AND') {
|
||||
result = result && matches;
|
||||
} else {
|
||||
result = result || matches;
|
||||
}
|
||||
|
||||
if (!result && cond.logic === 'AND') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 实施步骤
|
||||
|
||||
1. **修改** **`js/feegrid-manager.js`**:
|
||||
|
||||
* 新增 `_filterLocalRows()` 函数
|
||||
|
||||
* 新增 `_parseCondition()` 函数
|
||||
|
||||
* 新增 `_matchesConditions()` 函数
|
||||
|
||||
* 修改 `loadPage()` 方法,在加载时过滤本地数据
|
||||
|
||||
2. **修改** **`reload()`** **方法**:
|
||||
|
||||
* 确保 `reload()` 时也应用本地数据过滤
|
||||
|
||||
3. **测试验证**:
|
||||
|
||||
* 测试新增数据后的 LIKE 搜索
|
||||
|
||||
* 测试更新数据后的搜索匹配
|
||||
|
||||
***
|
||||
|
||||
## 预期效果
|
||||
|
||||
| 场景 | 操作 | 预期结果 |
|
||||
| --- | ------------------------- | -------------------------------- |
|
||||
| 场景一 | 新增10条数据 → 搜索 LIKE '%xxx%' | 本地新增且匹配的行会显示在搜索结果中 |
|
||||
| 场景二 | 更新10条数据 → 搜索更新后的字段 | 使用本地更新后的数据进行匹配 |
|
||||
| 场景三 | 删除数据后搜索 | 已标记删除的行仍然出现在搜索结果中,但是status显示删除即可 |
|
||||
|
||||
***
|
||||
|
||||
## 风险与注意事项
|
||||
|
||||
1. **条件解析复杂度**:只支持简单的 LIKE、=、!= 操作符,复杂条件可能解析失败
|
||||
2. **性能考虑**:本地数据量大时,过滤可能有性能问题(建议限制本地数据量)
|
||||
3. **日期格式**:日期字段的比较需要注意格式统一
|
||||
4. **多表关联**:不支持跨表字段的搜索条件
|
||||
|
||||
Reference in new issue
Block a user