From e98740192d6ace39104580cc97a693bb84c748da Mon Sep 17 00:00:00 2001 From: oneao Date: Mon, 11 May 2026 21:45:04 +0800 Subject: [PATCH] 20260511214503 --- .../documents/draft_state_refactor_plan.md | 274 +++++ .../feegrid_manager_refactor_plan.md | 230 +++++ .../documents/feegrid_module_refactor_plan.md | 400 ++++++++ code/jsp/.trae/documents/js_refactor_plan.md | 162 +++ .../.trae/documents/merge_js_files_plan.md | 155 +++ .../.trae/documents/search_local_data_plan.md | 225 +++++ code/jsp/editorder_s_cw.jsp | 504 ++-------- code/jsp/js/draft-page-manager.js | 439 -------- code/jsp/js/feegrid-manager.js | 937 ++++++++++++++++++ 9 files changed, 2451 insertions(+), 875 deletions(-) create mode 100644 code/jsp/.trae/documents/draft_state_refactor_plan.md create mode 100644 code/jsp/.trae/documents/feegrid_manager_refactor_plan.md create mode 100644 code/jsp/.trae/documents/feegrid_module_refactor_plan.md create mode 100644 code/jsp/.trae/documents/js_refactor_plan.md create mode 100644 code/jsp/.trae/documents/merge_js_files_plan.md create mode 100644 code/jsp/.trae/documents/search_local_data_plan.md delete mode 100644 code/jsp/js/draft-page-manager.js create mode 100644 code/jsp/js/feegrid-manager.js diff --git a/code/jsp/.trae/documents/draft_state_refactor_plan.md b/code/jsp/.trae/documents/draft_state_refactor_plan.md new file mode 100644 index 00000000..1fc0477b --- /dev/null +++ b/code/jsp/.trae/documents/draft_state_refactor_plan.md @@ -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 │ +└─────────────────────────────────────────────────────────────┘ +``` \ No newline at end of file diff --git a/code/jsp/.trae/documents/feegrid_manager_refactor_plan.md b/code/jsp/.trae/documents/feegrid_manager_refactor_plan.md new file mode 100644 index 00000000..1d8f7596 --- /dev/null +++ b/code/jsp/.trae/documents/feegrid_manager_refactor_plan.md @@ -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 是基础,必须首先完成。后续步骤可并行或按顺序进行。 \ No newline at end of file diff --git a/code/jsp/.trae/documents/feegrid_module_refactor_plan.md b/code/jsp/.trae/documents/feegrid_module_refactor_plan.md new file mode 100644 index 00000000..2fcbf5d6 --- /dev/null +++ b/code/jsp/.trae/documents/feegrid_module_refactor_plan.md @@ -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 + +``` + +*** + +## 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 等通用逻辑。 diff --git a/code/jsp/.trae/documents/js_refactor_plan.md b/code/jsp/.trae/documents/js_refactor_plan.md new file mode 100644 index 00000000..5a49faec --- /dev/null +++ b/code/jsp/.trae/documents/js_refactor_plan.md @@ -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 + +
+``` + +### 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 中定义分页器 +- **代码清理**:删除未使用的函数和变量 +- **更好的复用性**:通用名称可在其他页面直接使用 \ No newline at end of file diff --git a/code/jsp/.trae/documents/merge_js_files_plan.md b/code/jsp/.trae/documents/merge_js_files_plan.md new file mode 100644 index 00000000..abad4289 --- /dev/null +++ b/code/jsp/.trae/documents/merge_js_files_plan.md @@ -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 + + + + + + +``` + +--- + +## 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 + + +``` + +**之后(JSP 中)**: +```html + +``` + +### 5.3 业务代码不变 + +`createFeegridManager` 的 API 保持不变,业务代码无需修改。 + +--- + +## 6. 风险与注意事项 + +1. **保持 API 兼容**:合并后 `createFeegridManager` 的接口不变 +2. **删除源文件**:确认合并成功后删除 `draft-page-manager.js` +3. **测试验证**:合并后需要测试分页、选中、删除、撤销等功能 \ No newline at end of file diff --git a/code/jsp/.trae/documents/search_local_data_plan.md b/code/jsp/.trae/documents/search_local_data_plan.md new file mode 100644 index 00000000..d101f9ef --- /dev/null +++ b/code/jsp/.trae/documents/search_local_data_plan.md @@ -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. **多表关联**:不支持跨表字段的搜索条件 + diff --git a/code/jsp/editorder_s_cw.jsp b/code/jsp/editorder_s_cw.jsp index f855d086..6e17f018 100644 --- a/code/jsp/editorder_s_cw.jsp +++ b/code/jsp/editorder_s_cw.jsp @@ -17,7 +17,7 @@ - +