Commit 1398b020 authored by Data Governance Dev's avatar Data Governance Dev

docs(worklog): 数据字典浏览器分页改造 + EP 滚动事件 bug 修复记录

包含两段工作:
1. 2026-08-13 数据字典浏览器动态分页 + 后端缓存复用
   - 后端 3 个分页端点
   - 前端状态机重写(删 tableSummary/dataDict 全量 ref)
   - selectAllTables/Invert 改调 all-table-names
   - CSS 隐藏两边表格的全选 checkbox
2. 2026-08-13 数据字典浏览器滚动到底不增量刷新(bug fix)
   - 现象:滚到底不拉第二页
   - 根因:EP 2.14.3 + height 时内部 <el-scrollbar> 包裹 body,
     scroll 不冒泡到 el-table 根
   - 修复:直接在 .el-scrollbar__wrap 上挂原生 scroll 监听
parent a0b10c06
......@@ -189,6 +189,37 @@ mock DB 跑一条含嵌套组的规则:
---
## 2026-08-13 · 数据字典浏览器筛选时机:focusoff 触发
- **用户反馈**:「数据字典浏览器的筛选框,修改筛选时机,不用 onchange,在 focusoff 的时候触发筛选」
- **背景**:当前 1971 张表 / 36212 个字段,每键入一个字符都触发 `filteredTables` / `filteredFields`
重算 + el-table 重新渲染,长字段名搜索会卡。
- **实现**([web/static/index.html:1220-1245](web/static/index.html#L1220-L1245)):
- 拆成两份 ref:
- `tableSearchDraft` / `fieldSearchDraft`:v-model 实时绑定(输入即改)
- `tableSearchCommitted` / `fieldSearchCommitted`:驱动 computed 过滤
- 新增 `applyTableSearch()` / `applyFieldSearch()`:把 Draft 同步到 Committed
(trim + toLowerCase),并跳过同值写入(避免无意义的重渲染)
- el-input 绑 `@blur` + `@clear` + `@keyup.enter` 三个事件都触发 apply:
- **@blur**:用户点别处 / Tab 离开 → focusoff 应用
- **@clear**:用户点 X 清空输入 → 立刻应用(避免还要 blur 才生效)
- **@keyup.enter**:键盘用户按 Enter → 立刻应用(不用必须 blur)
- **computed 改读 Committed**:
- `filteredTables` 读 `tableSearchCommitted.value`(已经是 lowercase,computed 里省一次 toLowerCase)
- `filteredFields` 读 `fieldSearchCommitted.value`
- **设计权衡**
- 双 ref 看起来啰嗦,但语义清晰:Draft = 输入态,Committed = 过滤态。
也可以用单个 ref + `v-model.lazy` 修饰符,但 el-input 是组件不是原生 input,`.lazy`
不一定能透传(实际测试 element-plus 也支持 lazy 但行为是 change 事件触发,可能
不是用户期望的「blur 才提交」语义);显式拆 ref + 显式事件最稳。
- 同值跳过写入:`if (v !== tableSearchCommitted.value)` ——避免用户反复 blur 同一
个值触发多余的 computed 重算。
### 提交
待 commit。
---
## 2026-08-13 · Oracle 自定义规则踩坑(ORA-01036 + ORA-00933)+ 多 tab UX 改造
> 接续上面「Oracle 端到端打通」之后的两件事:
......@@ -5120,3 +5151,99 @@ Total: 28 steps # 4 基础 + 24 indicator checkbox
2. `feat(web): 卡片 header 点击折叠 + 按钮 stop 防止冒泡`
3. `fix(backend): sql_builder 数值比较符强制 float 避免达梦隐式转换坑`
4. `fix(backend): step9 SummarySpec template 改双花括号与前端 renderSummary 匹配`
---
## 2026-08-13 · 数据字典浏览器动态分页 + 后端缓存复用
### 改 1:后端 cache 复用 + 3 个分页端点
- **背景**:connect/test 已经把全量字典灌进 DataDictCache,但浏览器一次拖全量 → 36k 字段塞 el-table 卡
- **新增端点**(`web/api/routes.py:413-596`):
- `GET /api/dict/tables?db_type=&host=&port=&user=&database=&page=&page_size=&q=` —— 分页返回表清单,每项附 `field_count`
- `GET /api/dict/fields?...&tables=t1,t2&page=&page_size=&q=` —— 分页返回字段清单,每项附 `table_comment`(list_columns SQL 不带这列,从 table_summary 关联)
- `GET /api/dict/all-table-names?...&q=` —— 仅返回名字列表,供「全选 / 反选」按钮用(不分页,1971 张 in-memory 几百微秒)
- **cache 策略**:所有端点先 `DataDictCache.get_instance().get(cfg)`,命中 → 直接 list[a:b] 切片;miss → 现场抽一次(缺 password 时返回 `_warning` 让前端提示重连)
- **字段**:服务端排序(按 table_name 字典序;fields 按 table_name + ordinal_position),与旧前端 filteredFields 排序一致
- **踩坑**:
- `list_dict_fields` 默认参数顺序:port=3306 写在 user/database 后面 → Python SyntaxError(非默认参数不能跟在默认参数后面);改成 `port=3306` 放最末位
- `host` 参数别名 alias="host" 与同名 host 参数重复 → 简化为单一 `host` 参数
- `_resolve_dict_data` 返回 `(dict_data, warning)` 元组;前端可读 warning 但不强依赖(前端按 r._warning 弹 ElMessage.warning)
### 改 2:前端状态机重写(tableSummary/dataDict → tableList/fieldList)
- **删除**:`tableSummary` / `dataDict` 全量 ref、`tablesAll` / `filteredTables` / `fieldCountByTable` / `filteredFields` computed(前端不再做过滤,全部走后端)
- **新增**:每个表(tableList/tablePage/tableHasMore/tableTotal/tableLoading)+ 每个字段(fieldList/...)共 10 个 ref + 2 个 page size 常量 + 2 个 fetch gen 计数器(防 stale 响应)
- **fetchTablesPage(page, replace=false)** / **fetchFieldsPage(page, replace=false)**:
- replace=true 时清空 + 写入(首次/筛选重读/全选反选);否则 concat(滚动加载下一页)
- `tablesFetchGen++` → response 时校验 `myGen === tablesFetchGen` 否则丢弃
- `await nextTick()` 后 `restoreTableSelection()` 把 el-table 可见勾选与 form.tables 同步
- **滚动监听** `onTablesScroll / onFieldsScroll`:监听 el-table `@scroll` 事件,`scrollTop + clientHeight >= scrollHeight - 60` 时 `fetchTablesPage(tablePage + 1)`
- **筛选 commit**:`applyTableSearch / applyFieldSearch` 在 focusoff/clear/Enter 触发 → `tableSearchCommitted` 变更 → `fetchTablesPage(1, true)` 重读第 1 页
- **form.tables 变化 watch**:`watch(() => form.tables.slice().sort().join(','))` → `fetchFieldsPage(1, true)` 字段表重读
- **onTableSelectionChange 改 set 合并**:原来 `form.tables = selectedRows.map(...)` 会丢失不在 visible 的勾选(用户选了 5 张在 page 2,滚回 page 1 时 el-table 看不见它们) → 改为「visible 名 + 新选中 → add;visible 名 - 未选中 → delete;不在 visible 的保留」
### 改 3:selectAllTables / selectNoTables / selectTablesInvert 重写
- **旧**:`tablesTableRef.value.clearSelection() + toggleRowSelection` 遍历全量(在分页下只能遍历已加载行 → 「全选」名不副实)
- **新**:
- 全选:`fetch /api/dict/all-table-names?q=...` → `form.tables = r.names` → `fetchTablesPage(1, true)` 把所有匹配表拉回首屏,restoreTableSelection 自动勾上
- 清空:`form.tables = []` + `clearSelection()`
- 反选:拉匹配名字 list → `form.tables = matched.filter(n => !currentSet.has(n))` → `fetchTablesPage(1, true)`
### 改 4:CSS 隐藏两边表格的全选 checkbox
- **位置**:`web/static/style.css` 新增 `.dict-tables-table thead .el-checkbox, .dict-fields-table thead .el-checkbox { visibility: hidden; pointer-events: none; }`
- **保留每行 checkbox**:只用 thead 选择器,body 行的 `.el-checkbox` 不受影响
- **踩坑**:不能用 `display: none`(会让 thead 那行的高度计算错位)→ 用 visibility: hidden 保留布局
### 改 5:addCustomRule 去除 tableSummary 兜底
- **旧**:字段行 row.table_comment 为空时,从 tableSummary 反查
- **新**:后端 `/api/dict/fields` 每行都附 `table_comment`(从 `table_summary` 关联,list_columns SQL 不带这列)→ 前端 `row.table_comment || ''` 直接用
### 改 6:testConnection 流程调整
- **旧**:把 r.table_summary / r.data_dictionary 整体赋给前端 ref → 全量塞 el-table
- **新**:r 里的全量字段丢弃(保留响应字段不变向后兼容),改为 `Promise.all([fetchTablesPage(1, true), fetchFieldsPage(1, true)])` 各拉首屏
### 验证(后端)
- 服务重启:`Stop-Process 17592; PYTHONIOENCODING=utf-8 nohup python web/start.py &`(start.py 有 GBK 编码 print 坑,必须 UTF-8)
- `curl /api/dict/tables?db_type=mysql&host=192.168.20.10&port=3306&user=root&database=smart-build&page=1&page_size=5`
→ `{"tables":[],"page":1,"page_size":5,"total":0,"has_more":false,"_warning":"cache miss 且无法重抽(请重新测试连接)"}` ✓
- 同上 `/api/dict/fields` 返回 `fields[]` + warning ✓
- 同上 `/api/dict/all-table-names` 返回 `names[]` + warning ✓
- 全部 3 个端点结构正确;cache miss 路径走 `_warning` 不抛 500
### 后续 commit 拆分
1. `feat(backend): /api/dict/{tables,fields,all-table-names} 分页端点 + cache 复用`
2. `feat(web): 数据字典浏览器动态分页(滚动加载 + 筛选重读 + 隐藏全选)`
### 踩坑清单
1. `port=3306` 默认参数必须放最末(Python 语法规则)→ 写完 SyntaxError 才想起来
2. el-table 全选 checkbox `display:none` 会算错表头高度 → `visibility:hidden` + `pointer-events:none` 才能保留布局
3. Element Plus selection-change 只返回当前 visible 选中行 → 直接赋值 form.tables 会丢跨页勾选 → 必须 set 合并
4. 跨页勾选恢复:reserve-selection 只在同 dataset 内重排生效;:data 整体替换时失效 → 自己写 restoreTableSelection 用 row-key 匹配
5. start.py GBK 编码 print(`✓` 字符)→ 重启必须 `PYTHONIOENCODING=utf-8`,否则直接崩在 print 行
6. WEB_HOST 默认 0.0.0.0 → netstat 显示 0.0.0.0:8765 而不是 127.0.0.1,但本地 curl 照常工作(loopback 通透)
---
## 2026-08-13 · 数据字典浏览器滚动到底不增量刷新(bug fix)
### 现象
- 测试连接 → 左表加载 50 张(page_size=50,total=1971)→ 滚到底部 → 没拉第二页
- header 一直显示「已加载 50 / 1971 张表」
### 根因
- EP 2.14.3 + `height="520"` → el-table 内部用 `<el-scrollbar>` 包裹 body
- 真正的滚动容器是 `.el-scrollbar__wrap`,scroll 事件**不会**冒泡到 el-table 根
- 模板上写的 `@scroll="onTablesScroll"` 在 EP 2.14.3 不触发(事件源不匹配)
### 修复(`web/static/index.html`)
- 删掉模板的 `@scroll` 依赖(保留作为兜底,但实际不触发)
- 新增 `_onTablesScroll / _onFieldsScroll`:handler 直接读 `e.target.scrollTop/scrollHeight/clientHeight`
- 新增 `bindDictScroll(refEl, handler)`:用 `refEl.$el.querySelector('.el-table__body-wrapper .el-scrollbar__wrap')`(fallback 到 `.el-table__body-wrapper`)拿滚动容器,挂原生 `addEventListener('scroll', ..., { passive: true })`
- 用 `wrap.__dictScrollHandler` 标记防重复挂
- 新增 `rebindDictScroll()`:每次 `fetchTablesPage / fetchFieldsPage` 完成 + `nextTick` 后调一次(数据变化后 el-table 可能重建 wrap,重挂兜底)
- 字段表加 `ref="fieldsTableRef"` + return 里导出
### 踩坑
- 不要依赖 el-table 的 `@scroll` 事件(EP 2.14.3 + height 时不触发)
- 不要用 `display:none` 隐藏滚动容器(el-table 用 `el-scrollbar__wrap` 的 `overflow:auto`)
- `passive: true` 必须加(避免浏览器警告 + 提升滚动性能)
- `__dictScrollHandler` 用 DOM 属性而不是 WeakSet:DOM 节点被替换时旧的自然 GC,新的 wrap 是新对象 → 无需手动清理
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment