cleaning-refactor-plan.md 22 KB

cleaning.vue 组件拆分方案(待审核)

目标文件:ai-frontend/src/case/views/data/cleaning.vue(12029 行) 本文只做方案,不改代码。审核通过后按「六、分阶段实施计划」执行。


一、现状盘点

区块 行号 行数 占比
<template> 1–1502 1502 12.5%
<script setup lang="ts"> 1504–8209 6706 55.7%
<style lang="less"> 8211–12029 3819 31.8%
  • 顶层声明(ref / computed / function / type / interface)约 380+ 个
  • 样式块 .jeesite-case-data-cleaning.case-data-cleaning-page 一层就 2708 行

定性结论:这不是"某个组件写胖了",而是一个文件里塞了 6 个业务域 + 4 个弹窗 + 3 栏骨架 + 全局样式。因此拆分必须按业务域切,不能按段落切。

1.1 模板结构

行号 块 说明
3–16 页头 标题 / 返回 / 收起左侧 / 导入数据
18–78 三栏骨架 main-layout + panel-resizer
19–76 左栏 left-panel BasicTree 文件树 + 图例
80–453 中栏 middle-panel 模板匹配条 → 字段映射卡(98–228) → 识别规则(230–395) → 子模板(396–408) → 空态(413–452)
457–1162 右栏 right-panel 列操作工具条 → 数据预览表(487–576) → 清洗规则面板(577–1173)
1165–1268 Modal 新增 / 编辑列
1269–1324 Modal 拆分列
1325–1328 Modal 删除列(4 行,不单独拆)
1329–1502 Modal 模板库选择

清洗规则面板内部再分四栏:规则库(604–619) / 操作箭头(620–624) / 已选规则(625–644) / 规则参数配置(646–1158)。

1.2 脚本结构(按业务域)

行号 业务域 行数
1504–1675 import + 类型定义 172
1676–2110 通用 normalize / 规则 flag 解析 / 模板字段 meta 435
2010–2340 mock 数据 + 文件树构建·排序·查找 330
2342–2643 列头与目标字段、规则常量、分组列草稿 300
2644–3045 节点级状态缓存(nodeStateCache / analyzingNodeKeys) 400
3046–3176 预览数据 & 快照 130
3177–3737 规则序列化 / 反序列化(写回树) 560
3738–3990 tableRule 构建 / 模板字段 meta / 规则面板 computed 250
3991–4324 格式校验(日期·时间·数字·手机·卡号·身份证·借贷标志·通信类别) 334
4325–4560 目标格式校验 map + 匹配状态 236
4565–4723 表头标签构建 / 规则重映射 160
4724–4935 表头行应用 / 预览表格辅助 210
4936–5485 列操作(增·拆·删)+ 映射浮层交互 550
5486–5585 子模板命名校验 100
5586–6168 识别规则(方向配置 / 基本信息 / 正负开关) 583
6168–6285 自动字段匹配 117
6285–6890 模板库 Modal 逻辑 605
6890–7103 页面级 computed + 面板拖拽 215
7104–7596 列操作 Modal 逻辑 + 生成列重建 493
7597–7996 清洗规则交互 + 13 条规则的 apply 逻辑 400
7996–8209 导入 / 树加载 / 生命周期 214

1.3 样式结构

行号 选择器 行数
8214–10922 .jeesite-case-data-cleaning.case-data-cleaning-page(巨型嵌套块) 2708
10925–10996 .cleaning-config-modal / .cleaning-modal-title 71
10998–11073 .split-column-modal / .split-column-form 75
11075–11366 .add-column-modal / .add-col-dialog 291
11368–11467 .origin-field-popover 100
11469–12018 .select-template-modal 550
12020–12028 @keyframes 9

8214–10922 内部二级块的行数分布,直接决定每个组件自带多少样式:

子块 行号 行数 归属
page-header / 布局 / resizer 8229–8331 103 index.vue
tree-header / tree-wrap / legend 8332–8519 188 FileTreePanel
panel-header / right-panel-body 8520–8550 31 index.vue
template-match-bar 8551–8654 104 MiddlePanel
middle-empty-* 8655–8860 206 MiddleEmptyState
mapping-* 8861–9256 396 FieldMappingCard
recognition-* 9257–9842 586 RecognitionRulePanel
child-template-* 9843–9880 38 ChildTemplatePanel
cleaning-rule-panel 9881–10479 599 CleaningRulePanel
table-mock 10480–10810 331 DataPreviewTable
@media 10811–10921 111 index.vue

二、拆分目标与验收标准

2.1 目标

  1. 页面入口文件 ≤ 400 行,只负责:三栏骨架 + 拖拽调宽 + 生命周期编排 + 导入动作。
  2. 单文件上限 ≤ 900 行(模板库 Modal 是最大的一块,允许到 900)。
  3. 所有纯计算(格式校验、序列化、树构建、tableRule 构建)下沉到 utils/,不依赖 Vue 响应式。
  4. 所有领域状态 + 逻辑收敛到 composables/,每个域一个文件。
  5. 对外行为零变化:路由、keep-alive 名称、sessionStorage key、接口调用、交互流程全部不动。

2.2 验收标准(硬性)

  • pnpm type:check(vue-tsc --noEmit --skipLibCheck)零错误
  • pnpm build 通过
  • 页面功能回归清单全过(见「七」)
  • 目标文件行数:cleaning/index.vue ≤ 400 行;其余单文件 ≤ 900 行
  • 不新增任何第三方依赖
  • 不修改任何后端接口调用参数

2.3 明确不做的事

  • ❌ 不重写业务逻辑、不"顺手优化"算法
  • ❌ 不动 UI / 样式视觉效果(class 名和 DOM 层级保持等价)
  • ❌ 不引入 Pinia / 状态库(项目里有,但这里不需要)
  • ❌ 不做 props 爆炸式传递(见「四」)
  • ❌ 不动 cleanProgress.vue / upload.vue 等兄弟页面

三、目标目录结构

src/case/views/data/
├── cleaning/                          # 新目录(原 cleaning.vue 迁移为 index.vue)
│   ├── index.vue                      # 页面容器:三栏 + 拖拽 + 编排       ~350
│   ├── types.ts                       # 全部类型定义                        ~260
│   ├── constants.ts                   # 规则名/示例/选项/存储 key 等常量     ~300
│   ├── context.ts                     # provide/inject key + 上下文类型      ~80
│   │
│   ├── utils/                         # 纯函数层(零 Vue 依赖)
│   │   ├── treeUtils.ts               # 文件树构建/排序/查找                ~300
│   │   ├── formatValidate.ts          # 8 类格式校验(日期…身份证/借贷标志) ~340
│   │   ├── ruleSerialize.ts           # 规则的存储序列化/反序列化            ~700
│   │   ├── tableRuleBuild.ts          # tableRuleList 构建 + 字段 meta       ~500
│   │   ├── headerRow.ts               # 表头行解析与应用                    ~230
│   │   └── domScroll.ts               # 滚动定位(rAF 重试逻辑)             ~90
│   │
│   ├── composables/                   # 领域状态 + 逻辑层
│   │   ├── usePanelResize.ts          # 三栏拖拽调宽                        ~80
│   │   ├── useFileTree.ts             # 树数据 + 选中 + 节点切换            ~250
│   │   ├── useNodeStateCache.ts       # 节点状态快照缓存 + 分析中态          ~400
│   │   ├── usePreviewData.ts          # 预览数据集 + 列快照 + 网格宽度       ~400
│   │   ├── useTemplateLibrary.ts      # 模板库(列表/分类/详情/选中)        ~900
│   │   ├── useFieldMapping.ts         # 字段映射 + 自动匹配 + 浮层交互        ~520
│   │   ├── useRecognitionRules.ts     # 识别规则(方向/基本信息/正负)        ~580
│   │   ├── useCleaningRules.ts        # 规则库 + 已选规则 + 参数配置          ~750
│   │   ├── useColumnOps.ts            # 新增/拆分/删除列 + 生成列重建         ~800
│   │   └── useImportFlow.ts           # 导入数据流程                        ~130
│   │
│   ├── components/
│   │   ├── FileTreePanel.vue          # 左栏文件树                          ~400
│   │   ├── MiddlePanel.vue            # 中栏容器(匹配条+子块调度)             ~150
│   │   ├── FieldMappingCard.vue       # 字段映射卡                          ~600
│   │   ├── OriginFieldPopover.vue     # 原文列选择浮层(Popover 内容)       ~250
│   │   ├── RecognitionRulePanel.vue   # 识别规则设置                        ~750
│   │   ├── ChildTemplatePanel.vue     # 创建子模板                          ~150
│   │   ├── MiddleEmptyState.vue       # 中栏空态引导                        ~240
│   │   ├── RightPanel.vue             # 右栏容器(工具条+预览+规则)            ~120
│   │   ├── DataPreviewTable.vue       # 数据预览表(表头行/目标字段/数据行)     ~400
│   │   ├── CleaningRulePanel.vue      # 规则面板容器(tabs+三栏)              ~700
│   │   ├── CleaningRuleLibrary.vue    # 规则库列表                          ~40
│   │   ├── SelectedRuleList.vue       # 已选规则列表                        ~150
│   │   ├── RuleConfigForm.vue         # 规则参数配置(含示例区)              ~300
│   │   ├── configs/                   # 13 类规则的参数表单(按需拆分)       各 30–90
│   │   │   ├── IgnoreLineConfig.vue
│   │   │   ├── DateTimeConfig.vue
│   │   │   ├── ExtractDataConfig.vue
│   │   │   ├── ExtractSegmentConfig.vue
│   │   │   ├── TimestampConfig.vue
│   │   │   ├── RemoveSpecialConfig.vue
│   │   │   ├── AbsoluteValueConfig.vue
│   │   │   ├── DecimalConvertConfig.vue
│   │   │   ├── ReplaceConfig.vue
│   │   │   ├── GroupingColumnsConfig.vue
│   │   │   ├── MagnificationConfig.vue
│   │   │   ├── YearCompleteConfig.vue
│   │   │   └── UsePrevRowConfig.vue
│   │   ├── AddColumnModal.vue         # 新增/编辑列                        ~500
│   │   ├── SplitColumnModal.vue       # 拆分列                            ~300
│   │   └── TemplateSelectModal.vue    # 模板库选择                        ~800
│   │
│   └── styles/                        # 全局(非 scoped)样式
│       ├── variables.less             # prefix-cls / 断点 / 公共 mixin
│       ├── page.less                  # 三栏骨架 + 页头 + 空态
│       ├── panels.less                # 各面板细粒度样式
│       ├── modals.less                # 挂 body 的 Modal / Popover 样式
│       └── index.less                 # 汇总入口
│
└── cleaning.vue                       # ← 删除(路由改为指向 cleaning/index.vue)

行数核算:index.vue 350 + context 80,其余按域分摊,最大单文件 900(useTemplateLibrary.ts / TemplateSelectModal.vue),不再出现 4 位数文件。


四、模块间通信方案(关键决策,需你拍板)

方案 A:props + emits(教科书做法)

页面持有全部状态,逐层下发。

  • ✅ 显式、可测试、易复用
  • ❌ 现有约 150 个 ref/computed 被跨面板共享(currentColumn、selectedRowIndex、headerRowIndex、rightTableHeaders、tablePreviewRows、fieldMapping、selectedRules …),全改 props 会产出 60+ 个 props / 40+ 个 emit,接口面比原文件还难维护,且改造过程中极易漏传导致行为漂移

方案 B:provide/inject + 领域 composable(推荐)

// context.ts
export const CleaningContextKey: InjectionKey<CleaningContext> = Symbol('CleaningContext');

// index.vue
const ctx = {
  tree:      useFileTree(),
  preview:   usePreviewData(),
  template:  useTemplateLibrary(),
  mapping:   useFieldMapping(),
  recognize: useRecognitionRules(),
  rules:     useCleaningRules(),
  columns:   useColumnOps(),
  import:    useImportFlow(),
};
provide(CleaningContextKey, ctx);

// FieldMappingCard.vue
const { mapping, preview, template } = useCleaningContext();
  • ✅ 迁移成本最低:子组件内部把原来的变量名 const { a, b } = useXxx() 解构出来,模板几乎可以整块剪切粘贴,改名风险最小
  • ✅ 每个 composable 独立成文件、可单独 review、可单测纯逻辑
  • ✅ 不污染全局(provide 只在页面子树内生效)
  • ⚠️ 依赖是隐式的,组件不能脱离页面单独复用 —— 本项目这些面板本来也不会被别处复用,代价可接受
  • ⚠️ 后续若要复用,可再把热门组件渐进式改成 props

建议采用方案 B,并在 context.ts 里用 TypeScript 约束每个域暴露哪些字段,避免"隐式却无类型"。

补充:usePanelResize / useNodeStateCache / useImportFlow 属于页面自身职责,不进 context,直接在 index.vue 使用。

样式方案

  • 保留全局 less 文件,不用 <style scoped>。原因:
    1. 现有 3819 行样式全部挂在 .jeesite-case-data-cleaning 前缀下,改成 scoped 需要重写全部选择器,风险大、收益小
    2. ant-design-vue 的 Modal / Popover / Select 默认 teleport 到 body,scoped 样式根本打不进去。wrapClassName="cleaning-config-modal add-column-modal"、.origin-field-popover、.select-template-modal 这三块必须是全局样式
  • 拆分为 styles/ 下 4 个 less 文件按域组织,各组件不写 <style>,统一由 styles/index.less 引入
  • 不改变任何现有 class 名,保证视觉零回归

五、关键风险与坑位(必须提前处理)

# 风险 说明与对策
1 keep-alive 依赖组件名 路由用 name: 'CaseCleaning',SFC 用 name="ViewsDataCleaning"(vite-plugin-vue-setup-extend)。迁移后 index.vue 必须保留 name="ViewsDataCleaning",否则缓存失效
2 Modal/Popover 样式失效 见「四」样式方案,相关样式一律留全局 less
3 document 级 DOM 查询 8158: document.querySelector('[data-...]')、5239/5258: querySelectorAll('[data-origin-target-key]')。拆组件后只要 data-* 属性还在即可工作;需逐条核对,不能丢属性
4 document.body 副作用 7062/7100/8206 拖拽时加 is-resizing-layout class 与 cursor。usePanelResize 拆出后必须保证 onBeforeUnmount 也清理
5 tableMockRef 跨组件 table-mock 拆为 DataPreviewTable 后,ref 需通过 defineExpose 暴露或用 data-* 选择器;mockGridTemplate 由 composable 提供
6 sessionStorage key 不能变 case_clean_file_infos / case_clean_import_file_infos(2008–2009)。与 importProgressSession.ts 有联动,key 放 constants.ts 并保持字面值不变
7 prefixCls 获取 只 useDesign('case-data-cleaning') 一次并把 prefixCls 放进 context;子组件不要各自 useDesign 出不同 scope,导致 class 前缀不一致
8 规则 key 白名单 disabledCleaningRuleKeySet(2532)含 groupingColumns/yearComplete/removeSpecialCharacters/usePreviousRowData 4 个被禁用的规则,但规则表单和示例仍在。禁止"顺手清理"
9 循环依赖 utils/* 之间禁止互相 import;composables/* 之间允许单向依赖(如 useFieldMapping → usePreviewData),但不允许回环。拆分时按依赖方向排
10 @ 别名与相对路径 现有文件用 ./importProgressSession、../../types/enum 等相对路径。迁到 cleaning/ 子目录后层级 +1,所有相对路径必须同步改为 ../types/enum 等,这是最容易漏的地方 → 用 @/ 绝对路径统一
11 13 条规则的 apply 逻辑分散 applyIgnoreLineRule → applyYearCompleteRule(7893–7996)与模板里的 config 段一一对应。拆 configs/*.vue 时按规则 key 一一映射,不合并

六、分阶段实施计划

原则:每一阶段结束都能 pnpm build 通过、页面功能完整、可独立回滚。不做"一次性大重构"。

Phase 0:目录与路由迁移(无逻辑改动)

  • cleaning.vue → cleaning/index.vue(内容原样搬运,仅改相对路径为 @/)
  • 更新 src/core/router/routes/index.ts:108 的 import 路径
  • 抽出 styles/ 骨架,把 8211–12029 整体搬入 styles/index.less,index.vue 里 @import './styles/index.less'
  • ✅ 产物:一行代码没改,构建通过,页面行为完全一致 → 这一步先验证"搬家"安全

Phase 1:类型 / 常量 / 纯函数下沉

  • types.ts(1543–1675 + 1710–1717 + 2376–2415 + 2644–2694 等)
  • constants.ts(1720–1727 / 2320–2327 / 2374 / 2416–2537 / 2605–2618 / 3991–4002 / 4244–4285 / 5044–5054 / 6304–6309 / 2008–2010)
  • utils/treeUtils.ts、utils/formatValidate.ts、utils/ruleSerialize.ts、utils/tableRuleBuild.ts、utils/headerRow.ts
  • ✅ 预期:script 减少 ~2500 行,零 UI 影响

Phase 2:独立 Modal 组件化(风险最低,优先做)

  • TemplateSelectModal.vue(template 1329–1502 / script 6285–6890 / style 11469–12018)
  • AddColumnModal.vue(template 1165–1268 / script 7104–7472 / style 10925–11366)
  • SplitColumnModal.vue(template 1269–1324 / script 4983–5068 + 7124–7148 + 7473–7556 / style 10998–11073)
  • 删除列 Modal 保留在 index(仅 4 行)
  • ✅ 预期:template 减 ~334、script 减 ~1100、style 减 ~900

Phase 3:右栏清洗规则面板(最大一块)

  • CleaningRulePanel.vue + CleaningRuleLibrary.vue + SelectedRuleList.vue + RuleConfigForm.vue
  • configs/*.vue 13 个规则表单(可先只做外壳、规则表单逐步迁,保证每步可编译)
  • composables/useCleaningRules.ts
  • ✅ 预期:template 减 ~597、script 减 ~400、style 减 ~600

Phase 4:中栏三个面板 + 空态

  • FieldMappingCard.vue + OriginFieldPopover.vue + composables/useFieldMapping.ts
  • RecognitionRulePanel.vue + composables/useRecognitionRules.ts
  • ChildTemplatePanel.vue、MiddleEmptyState.vue、composables/useTemplateLibrary.ts
  • ✅ 预期:template 减 ~350、script 减 ~1500、style 减 ~1400

Phase 5:左栏树 + 右栏预览表 + 节点缓存

  • FileTreePanel.vue + composables/useFileTree.ts
  • DataPreviewTable.vue + composables/usePreviewData.ts
  • composables/useNodeStateCache.ts、composables/useColumnOps.ts、composables/useImportFlow.ts
  • ✅ 预期:template 减 ~148、script 减 ~1200、style 减 ~450

Phase 6:收口与整理

  • index.vue 清理为骨架 + provide(context)
  • 删除死代码 / 未使用的 import(用 lint:eslint 扫)
  • styles/ 4 个 less 文件按域重新分段注释
  • 全量回归 + pnpm lint:all
  • ✅ 预期:index.vue ≤ 400 行

拆分后行数推演

阶段 index.vue 目标文件最大
现在 12029 12029
Phase 0 后 ~12029(仅搬家) 12029
Phase 1 后 ~9500 9500
Phase 2 后 ~7200 7200
Phase 3 后 ~5600 5600
Phase 4 后 ~2400 1500
Phase 5 后 ~700 900
Phase 6 后 ≤400 ≤900

七、回归验证清单

每阶段结束都要走的冒烟测试(建议 Phase 2 起每步都跑):

左栏

  1. 文件树加载、展开/收起、树节点选中切换
  2. 升序/降序排序
  3. 选中节点后滚动定位到可视区
  4. 从 URL query 带 focusKey 进入时正确选中

中栏

  1. 无模板时展示空态;点"选择模板"打开模板库
  2. 模板库:分类切换、来源筛选(系统内置/新创建子模板)、关键字搜索、列表点击预览、确认选择
  3. 选完模板后:字段映射卡渲染、目标字段匹配状态(✓/✕/!)、自动匹配、清空映射
  4. 原文列浮层:悬停预览列、点击选中、替换、置空、被占用字段置灰
  5. 识别规则:面板展开/收起、tab 切换、基本信息行解析、借贷标志/通信类别方向配置的增删改、正负标识开关、保存/清空
  6. 子模板名称输入 + 失焦校验(重名提示、重复提示)

右栏

  1. 表头行输入与应用(含错误行号提示)
  2. 预览表:单元格点击、列选中、忽略行置灰、表头行高亮、目标字段行状态
  3. 新增列:字段选择、内容 drag 排序、上移/下移、重复名校验、编辑已有列
  4. 拆分列:来源列、分隔符/固定长度、拆分算法、列类型
  5. 删除列
  6. 清洗规则:规则库 → 已选规则(> / < / 双击)、全部删除、启用/禁用切换
  7. 13 条规则各自的参数表单与"应用"(其中 groupingColumns/yearComplete/removeSpecialCharacters/usePreviousRowData 在 UI 中被禁用,需确认禁用状态保持一致)

布局与流程

  1. 三栏拖拽调宽、收起左栏、边界值限制
  2. 页面离开再进入不残留 is-resizing-layout class / cursor
  3. 导入数据:校验(子模板重名/重复/表头行未配)→ 跳转 cleanProgress
  4. sessionStorage 两个 key 的写入与回填
  5. keep-alive 缓存生效(切走再切回状态保持)

八、待你确认的问题

  1. 通信方案:采用推荐的 方案 B(provide/inject + 领域 composable),还是坚持方案 A(props/emits)?
  2. 目录命名:cleaning.vue → cleaning/index.vue 需要改一行路由 import,是否接受?(若不想动路由,可保留 cleaning.vue 作为壳,子件放 cleaning/ 目录)
  3. 执行粒度:按 Phase 0→6 顺序每阶段停下来给你 review,还是一口气做完 Phase 0–2 再给你看?
  4. 样式形态:保持全局 less(推荐,视觉零回归),还是要一并改成 <style scoped>(工作量翻倍,且 Modal/Popover 必须留全局)?
  5. 拆 configs/*.vue:13 个规则表单拆成独立文件(推荐),还是先在 RuleConfigForm.vue 里用 v-if 分支保留(改动更小、单文件约 800 行)?