# 超大文件拆分技术方案:TaskIndexPanel / ChatInput / session store 日期:2026-09-30 范围:`desktop/` 前端(桌面 / Web / 安卓共用 UI) 目标文件: - `desktop/src/components/task/TaskIndexPanel.vue`(2288 行) - `desktop/src/components/chat/ChatInput.vue`(2097 行) - `desktop/src/stores/session.ts`(1998 行) ## 1. 需求背景 desktop 端三个核心文件行数均已接近或超过 2000 行,成为维护瓶颈: - **改动冲突高发**:任务面板、输入框、会话 store 是前端最高频改动区域,单文件过大导致 PR 冲突率高。 - **逻辑定位困难**:如 `TaskIndexPanel.vue` 单文件内同时承载标准/聚焦两种列表模式、分组折叠、分组拖拽排序、分组重命名、会话右键菜单、归档/删除流转等至少 6 个独立关注点。 - **无法针对性测试**:desktop 当前无单元测试基建,`session.test.ts`(718 行、39 个用例)长期无法执行,store 行为失去回归保护。 ## 2. 需求描述 将三个超大文件拆分为多个职责单一的小文件,同时严格保证: 1. **不影响现有逻辑**:不改任何表达式、执行顺序、副作用时机。 2. **不丢失任何逻辑分支**:拆分后逐块 diff 核对,每一个 `v-if`、每一个 `watch` 触发条件、每一个 early-return 都可追溯到原文件。 3. **不打乱执行顺序和流程**:生命周期钩子、watch 注册顺序、事件监听挂载/卸载时机与原文件保持一致。 ## 3. 明确不做(不做清单) 以下事项本轮**一律不做**,写入不做清单以避免范围蔓延: | 不做项 | 说明 | |---|---| | 不改任何对外 API / 接口 | store 的 `useSessionStore` 导出签名、两个组件的 `props` / `emits` / `expose` 签名一律保持原样,所有调用方零改动 | | 不重构其他未点名文件 | 不碰 `ChatPanel.vue`、`useChat.ts`、`useStreamWS.ts`、`AgentSelector.vue` 等其他任何文件,即使发现可优化点 | | 不升级依赖 | 除引入 vitest 测试基建(见 §5.1)外,不新增、不升级任何依赖,不改 `vite.config` / `tsconfig` 等构建配置 | | 不动后端 / admin / 安卓 | 后端(backend-ts)、管理后台(admin)、安卓原生壳(android/)零改动;拆分对安卓 Capacitor 远程加载透明 | | 不做逻辑修复 | 拆分过程中发现的疑似 bug 只记录、不顺手修,另开任务处理 | | 不改 CHANGELOG | 纯内部重构、无用户/运维可见行为变化,按 AGENTS.md 约定不记 CHANGELOG | ## 4. 技术选型 ### 4.1 拆分手法:子组件为主 + composable 为辅 针对两个 Vue 大组件: - **模板中相对独立的 UI 区块拆成子组件**(.vue):真正同时减少 template 与 script 行数。 - **纯状态/逻辑抽成 composable**(useXxx.ts):与项目既有 `desktop/src/composables/` 约定一致(参考 `useChatScroll`、`useTaskPanelPrefs` 等)。 - 弃用「只抽 composable」方案:template 行数不变,文件仍然偏长,不达标。 - 弃用「只抽子组件」方案:props/emits 透传链过长,最容易引入逻辑偏差。 ### 4.2 store 拆分:单入口 + 领域模块文件 `session.ts` 为 setup 语法的 `defineStore('session', ...)`,对外暴露唯一 `useSessionStore`。采用: - 对外仍只有一个 `useSessionStore`,所有调用方 `import { useSessionStore } from '../stores/session'` 零改动。 - store 内部按领域拆到 `desktop/src/stores/session/*.ts` 模块文件(每个模块是一个接收上下文的工厂函数,返回该领域的 state 与 action),主文件只做组装与 `return`。 - 弃用「拆成多个独立 store」方案:需要改动全部调用方,违反「不影响现有逻辑」。 ### 4.3 验证基建:vitest 单测 + 存量 Playwright E2E - desktop 引入 `vitest` + `@vue/test-utils`(仅 devDependencies,测试基建,属获批的唯一依赖变动),让存量 `session.test.ts` 可执行,作为 store 拆分的自动化基线。 - 复用根目录既有 Playwright E2E(`tests/desktop.spec.ts`),不为本次拆分另建 E2E 框架。 ### 4.4 目录与命名:就近同目录 + 语义化命名 - 子组件与被拆组件同目录:`desktop/src/components/task/`、`desktop/src/components/chat/`。 - composable 统一进 `desktop/src/composables/`。 - store 领域模块进 `desktop/src/stores/session/`。 - 命名语义化,见 §6 各文件拆分表。 ## 5. 实现步骤(总流程) 按 **TaskIndexPanel → ChatInput → session store** 的顺序逐个拆分、逐个验证、逐个交付。每个文件走完完整的「基线 → 拆分 → 验证」闭环后才动下一个,保证回滚粒度最小、问题定位最快。 ### 5.1 第 0 步:建立验证基线(先于一切拆分) 1. desktop 引入 vitest 基建:`vitest`、`@vue/test-utils`、`jsdom`(devDependencies),新增 `vitest.config.ts`(jsdom 环境 + 路径别名与 vite 对齐)。 2. 跑通存量 `desktop/src/stores/session.test.ts`(39 个用例),确认全绿并固定为 store 拆分基线;若有存量失败用例,原样记录并在拆分后要求结果完全一致(不允许借拆分之机修改用例使其变绿)。 3. 跑通存量 E2E(`tests/desktop.spec.ts`),记录通过基线。 4. 对三个目标文件各做一次完整留档(拆分前快照),供逐块 diff 核对。 ### 5.2 第 1 步:拆分 TaskIndexPanel.vue 现状结构:template 1–470 行(标准模式分组列表、聚焦模式平铺列表、历史折叠区、已归档区、右键菜单 Teleport),script 472–1431 行,style 1432–2288 行。 **拆分子组件**(均放 `desktop/src/components/task/`,props/emits 透传保持原语义): | 新文件 | 承接内容 | |---|---| | `TaskSessionGroupList.vue` | 标准模式分组列表:分组头(图标/重命名输入/折叠箭头/操作按钮)、组内会话项、分组拖拽排序、分页加载更多 | | `TaskFocusList.vue` | 聚焦模式全量平铺列表(含优先级排序渲染、历史折叠区) | | `TaskArchivedSection.vue` | 底部已归档区(两种模式共用) | | `TaskContextMenu.vue` | 会话右键菜单 + 分组头右键菜单(Teleport 到 body) | **抽出 composable**(放 `desktop/src/composables/`): | 新文件 | 承接内容 | |---|---| | `useTaskPanelGroups.ts` | 分组折叠状态、分组别名/重命名(`renamingGroupKey`、`startGroupRename`、`confirmGroupRename` 等) | | `useTaskPanelDragSort.ts` | 分组拖拽排序(`dragIndex`、`onGroupDragStart/Over/Leave/Drop/End`) | | `useTaskPanelListMode.ts` | 标准/聚焦模式切换、`loadFocus`、`loadArchive`、可见数量分页 | 样式拆分:`TaskIndexPanel.vue` 的 `