# 易标投标工具箱 Client 开发说明 本文面向参与 `client/` 独立客户端开发的成员,目标是快速理解架构边界、目录职责和统一开发风格。 ## 技术栈 - Electron:桌面窗口、本地文件系统、配置存储、后续打包入口。 - Vite + React + TypeScript:Renderer 层 UI 与业务流程编排。 - Radix UI:Dialog、Tooltip、Switch 等无样式基础组件。 - CSS:当前使用全局 CSS,不引入 Tailwind,不复用旧 `frontend/` 样式。 ## 运行命令 ```powershell cd client npm install npm run dev npm run build npm audit ``` ## 架构边界 - `client/` 是独立桌面客户端,不依赖旧 `frontend/` 和 `backend/` 源码。 - Renderer 不直接访问 Node API,不直接保存 API Key。 - Electron Main 负责本地能力:配置、文件、AI 请求、导出。 - Renderer 通过 `window.yibiao` 调用 preload 暴露的安全桥接 API。 - Prompt builder 只负责组装消息,不直接请求 AI。 - Feature service 只负责本功能业务编排,不直接写 IPC 细节。 - 配置、工作区、草稿等需要持久化的业务数据由 Electron Main 侧读写文件;Renderer 不用 `localStorage` 保存大文本或业务流程状态。 ## 目录职责 ```text client/ ├── electron/ # Electron Main / Preload / IPC / Node 服务 │ ├── ipc/ # IPC 通道注册,只做参数转发 │ ├── services/ # 本地配置、AI、文件、导出服务 │ └── utils/ # Main 侧工具函数 ├── src/ │ ├── app/ # 应用级路由、菜单、Provider │ ├── components/ # 应用布局组件,例如 AppShell、Sidebar │ ├── features/ # 业务功能模块 │ ├── shared/ # 跨功能共享能力 │ ├── main.tsx # Renderer 入口 │ └── styles.css # 全局样式 └── assets/ # 客户端图标和静态资源 ``` ## App 层 - `src/app/menuConfig.ts`:左侧菜单配置,新增一级功能优先改这里。 - `src/app/AppRouter.tsx`:根据 `SectionId` 渲染对应页面。 - `src/app/providers/AppProviders.tsx`:全局 Provider 入口,后续接状态管理或主题时从这里加。 新增主菜单页面时,按以下顺序修改: - 在 `src/shared/types/navigation.ts` 增加 `SectionId`。 - 在 `src/app/menuConfig.ts` 增加菜单项。 - 在 `src/app/AppRouter.tsx` 增加页面分支。 - 如需工具条动作,由页面根据自身流程和状态渲染 `FloatingToolbar`。 ## Feature 层 每个业务功能放在 `src/features//` 下: ```text features// ├── pages/ # 页面级组件 ├── components/ # 本功能专用组件 ├── hooks/ # 本功能专用 Hook ├── services/ # 本功能流程编排 └── types.ts # 本功能专用类型 ``` 当前功能模块: - `technical-plan`:技术方案,流程统一为上传招标文件、招标文件解析、目录生成、正文生成、扩写改写。 - `knowledge-base`:知识库。 - `duplicate-check`:标书查重。 - `rejection-check`:废标项检查。 - `settings`:独立设置页,由侧栏底部入口进入,管理文本模型、生图模型、文件解析和关于信息。 Feature 开发规则: - 页面只做 UI 和交互编排,不直接调用 IPC。 - AI 请求通过本 feature 的 `services/` 进入,再调用 `shared/ai`。 - Prompt 统一放 `shared/prompts/`,不要散落在组件里。 - 跨功能类型放 `shared/types/`,仅本功能使用的类型放本功能 `types.ts`。 - 技术方案 Step01 只负责上传招标文件并展示解析出的 Markdown,不做 AI 理解或目录生成。 - 技术方案步骤推进统一由页面级 `FloatingToolbar` 控制,未满足前置条件时禁用“下一步”,不要在步骤页面主体里重复放同语义下一步按钮。 - 页面主体必须占满当前窗口内容区,避免出现全局滚动条;长内容使用页面内部列表、阅读器、工作区或设置页容器自己的滚动条。 - 设置页保持独立页面形态,不改回弹窗;保存入口统一使用页面级 `FloatingToolbar`,不要在各配置卡片底部重复放保存按钮。 - 设置页模型测试按钮放在模型名称输入/选择行内,保存按钮文案统一为 `保存`。 ## Shared 层 `src/shared/` 放跨功能复用能力: - `shared/ai/aiClient.ts`:Renderer 侧 AI 请求门面。 - `shared/ai/stream.ts`:流式文本收集工具。 - `shared/prompts/`:Prompt builder。 - `shared/storage/`:仅保留轻量 Renderer 缓存工具;业务工作区数据优先走 Electron Main 侧存储。 - `shared/types/`:跨功能共享类型。 - `shared/ui/`:跨功能通用 UI,例如 `FloatingToolbar`。 - `shared/utils/`:错误、JSON、ID 等通用工具。 共享层规则: - 不引用任何 feature 模块,避免循环依赖。 - 类型优先 `interface`,联合类型和泛型工具类型用 `type`。 - 用户可见文案使用中文。 ## Electron 层 `electron/` 只使用 CommonJS,保持与 Electron 当前入口一致。 ### preload `electron/preload.cjs` 暴露: ```ts window.yibiao.config.load() // 返回 ClientConfig window.yibiao.config.save(config) // 保存 ClientConfig window.yibiao.config.listModels() window.yibiao.ai.chat(request) window.yibiao.ai.requestJson(request) window.yibiao.ai.testImageModel(config) window.yibiao.file.importDocument() window.yibiao.workspace.loadTechnicalPlan() window.yibiao.workspace.saveTechnicalPlan(state) window.yibiao.workspace.clearTechnicalPlan() window.yibiao.tasks.startBidAnalysis(payload) window.yibiao.tasks.startOutlineGeneration(payload) window.yibiao.tasks.startContentGeneration(payload) window.yibiao.tasks.getActiveTasks() window.yibiao.tasks.onTaskEvent(callback) window.yibiao.export.exportWord(payload) ``` ### IPC `electron/ipc/*.cjs` 只注册通道,不写业务逻辑。 命名规则: - 配置:`config:*` - AI:`ai:*` - 文件:`file:*` - 工作区:`workspace:*` - 后台任务:`tasks:*` - 导出:`export:*` ### Services `electron/services/*.cjs` 承载 Main 侧业务: - `configStore.cjs`:配置读写,路径位于 Electron `userData`,包含文本模型、生图模型和文件解析配置。 - `aiService.cjs`:文本模型请求、生图模型测试、正文配图生图等 AI 能力封装。 - `fileService.cjs`:文档导入与解析,按配置选择本地或 MinerU 解析方式;新的文件上传/转换入口必须复用这里的 `parseDocumentWithConfig()`。 - `workspaceStore.cjs`:工作区业务数据缓存,例如技术方案流程状态和招标文件内容。 - `taskService.cjs`:后台长任务管理,负责招标文件解析、目录生成、正文生成等跨页面任务的状态推送和结果落盘。 - `exportService.cjs`:Word 导出,包含 Markdown 到 Word、图片插入、Mermaid 转图片和导出进度上报。 - `updateService.cjs`:打包应用启动后的 GitHub Release 更新检查、下载和重启安装提示。 Main 层规则: - 文件读写显式使用 UTF-8。 - Windows 路径和中文路径要作为默认情况考虑。 - 不在 Renderer 暴露 `fs`、`path`、`ipcRenderer` 原始对象。 - Renderer 不决定文件解析方式;文件解析方式由 Main 从 `configStore` 读取配置后选择。 - 新增任何文件上传、导入、转换功能时,不要直接调用 `doc2markdown/convert.mjs` 或自行拼接 MinerU 请求;统一在 Main 侧调用 `electron/services/fileService.cjs` 的 `parseDocumentWithConfig(app, filePath, config, { assetScope })`。 - `parseDocumentWithConfig()` 会统一处理本地解析、MinerU Agent、MinerU Accurate、MinerU 不支持格式自动切回本地解析、设置页“保留图片”和图片资产落盘。业务代码只负责选择文件、保存业务状态和传入稳定的 `assetScope`。 - `assetScope` 必须能对应到业务资源,例如技术方案使用 `technical-plan`,知识库文档使用 `knowledge-${documentId}`。后续新功能应使用 `-` 这类可定位前缀,便于删除时清理历史图片。 ## 数据存储 - 配置数据保存到 Electron `userData/user_config.json`,由 `configStore.cjs` 管理。 - 工作区业务数据保存到 Electron `userData/workspace/`,由 `workspaceStore.cjs` 管理。 - 技术方案缓存保存到 `userData/workspace/technical_plan.json`,包含当前步骤、上传文件名、招标文件内容和后续流程中间结果。 - 正文生成图片保存到 `userData/workspace/generated-images/`,正文中通过 `yibiao-asset://generated-images/...` 引用,不把图片 base64 长期写入 `technical_plan.json`。 - 文件解析保留的原文图片保存到 `userData/workspace/imported-images/--/`,正文中通过 `yibiao-asset://imported-images/...` 引用,不把图片 base64 长期写入业务 JSON。 - 删除或重置某个业务资源时,必须调用 `electron/utils/importedImages.cjs` 的 `deleteImportedImageBatches(app, assetScopePrefix)` 清理对应历史图片目录,避免用户长期使用后磁盘被遗留图片占满。 - 解析失败时 `parseDocumentWithConfig()` 会清理本次新建图片批次;业务删除、清空、重新导入或删除父级目录时仍必须按 `assetScopePrefix` 主动清理旧批次。 - 设置页通用配置中的“开发者模式”开启后,AI 请求日志保存到 Electron `userData/logs/ai/`;每次 AI 请求对应一个 JSON 日志文件,包含完整请求参数、响应内容或错误信息。 - Renderer 通过 `window.yibiao.workspace` 读写工作区数据,不直接写文件,也不把招标文件 Markdown、正文草稿等大文本长期放进 `localStorage`。 - `localStorage` 只用于轻量 UI 偏好或临时状态,不作为业务数据的权威来源。 ## 后台长任务 - 招标文件解析、目录生成、正文生成等耗时任务必须放在 Electron Main 侧执行,不在页面组件里承载完整任务编排。 - Renderer 只负责启动任务、订阅 `window.yibiao.tasks.onTaskEvent()`、展示 `technical_plan.json` 中的任务状态和结果。 - 后台任务运行过程中必须持续写入 `workspaceStore`,确保切换页面后任务不中断,切回来能恢复当前日志、进度和结果。 - 页面卸载不能作为任务取消信号;如需取消任务,应单独设计取消接口。 ## 技术方案流程约定 - Step01 只负责上传招标文件并展示解析出的 Markdown,不做 AI 理解或目录生成。 - Step02/Step03/Step04 的长任务统一走 Electron Main 后台任务,不在 Renderer 中承载完整生成流程。 - Step04 正文生成页面采用“左侧目录树/任务树 + 右侧内容阅读区”的布局;目录树应能体现每个小节的生成状态。 - 正文只生成叶子小节内容,父级目录只作为结构分组。 - 正文内容以 `outlineData.outline[*].content` 为展示和导出的权威来源,不要另建一套正文存储源。 - Step04 正文生成先由 AI 输出章节编排决策,再生成正文;是否使用表格或配图由 AI 结合章节内容自行判断,代码不要按关键词硬编码配图规则。 - 正文配图只有在设置页生图模型状态为 `available` 时执行;生图失败不应影响已生成正文落盘。 - Mermaid 图以 Markdown `mermaid` 代码块保存;Renderer 负责前端本地渲染预览,Word 导出时由 Main 通过 mermaid.ink 转图片,并通过 `window.yibiao.export.onWordExportProgress()` 展示进度和失败提示。 - 目录重新生成、编辑、添加或删除后,必须清空正文生成缓存和旧 `content`,避免旧正文污染新目录和导出结果。 - Step04 的工具条使用 `导出 Word` 和 `继续扩写`,不要再显示“下一步”。 ## AI 与 Prompt AI 调用分三层: - 页面或 Hook 调 feature service。 - Feature service 调 `shared/prompts` 生成 messages,再调 `shared/ai/aiClient`。 - `aiClient` 通过 `window.yibiao.ai` 调 Electron Main。 Prompt 文件约定: - `analysisPrompts.ts`:招标文件分析。 - `outlinePrompts.ts`:目录生成、子目录生成、目录审核。 - `contentPrompts.ts`:章节正文生成。 - `expandPrompts.ts`:旧方案目录提取。 - `duplicatePrompts.ts`:标书查重。 - `rejectionPrompts.ts`:废标项检查。 - `jsonRepairPrompts.ts`:JSON 修复。 当前除 `jsonRepairPrompts.ts` 外,多数 Prompt builder 是占位实现,后续迁移时参考旧 `backend/app/utils/prompts/` 的职责,但不要直接依赖旧文件。 - AI 生成正文属于不可信模型输出,Markdown 渲染默认不要启用 `rehypeRaw`,避免 HTML 被渲染成真实 DOM。 - 只有明确需要展示解析文档中 HTML 的场景,才允许局部启用 raw HTML,并在代码评审中说明原因。 ## UI 风格 - 配色使用易标官网/工作台体系:`#2174FD`、`#4098FC`、`#5B54D3`、`#f8fafd`。 - 保持高端、清爽、克制,不使用花哨动效。 - 组件优先清晰的信息层级和可读性。 - 前端基础组件优先使用 Radix UI 组件实现,若未引用可自行安装,再用全局 CSS 覆盖视觉风格。 - 新页面不要默认加占空间的大标题横幅;除非业务确实需要说明上下文,否则直接进入核心内容或使用轻量导航/状态栏。 - 新增页面优先延续现有卡片、胶囊 Tab、轻量状态栏、`FloatingToolbar` 等模式,不要另起一套视觉语言。 - 新增或调整页面时,先查找并复用现有布局、按钮、卡片、表单、状态栏和工具条等视觉模式;页面专用样式只补充必要结构和间距,不要为局部页面重写一整套颜色、圆角、阴影和字号体系。 - 如果某个 UI 模式后续可能跨页面复用,优先沉淀到全局样式或 `shared/ui`,避免每个 feature 各写一套相似实现。 - 技术方案流程页优先沿用 `command-bar + progress-card + workspace` 结构;左侧列表/树、右侧阅读器的交互模式保持一致。 - 页面不要为了底部工具条额外预留空白;`FloatingToolbar` 是覆盖层,必要内容应由内部滚动区域承载。 - 通用 UI 放 `shared/ui/`,业务专用 UI 放 feature 内。 - 全局布局组件放 `components/`。 ## 全局消息提示 - 所有成功、失败、警告、普通消息提示统一使用 `shared/ui/ToastProvider` 提供的 Toast。 - Toast 位置固定在窗口右上角。 - 不使用 `alert`,不在页面里自建长期占位提示条。 - 页面内只保留与业务状态强相关的空状态、加载态和结果态。 ## 悬浮工具条 通用组件位于 `src/shared/ui/FloatingToolbar.tsx`。 使用方式: ```tsx ``` - 不提供 App 级默认工具条;需要流程控制或保存入口的页面自行渲染 `FloatingToolbar`,例如技术方案步骤工具条、设置页保存工具条、标书查重处理工具条。 - 页面主体不要重复放与工具条同语义的按钮,避免同一动作出现多个入口。 - 工具条前置拖动手柄,用户只能通过手柄拖动工具条;不要让普通按钮区域承担拖动,避免点击和拖拽冲突。 - 工具条应悬浮在内容上层,不要求页面底部 `padding-bottom` 或空白占位;被遮挡的长内容通过内部滚动查看。 ## 布局与滚动 - 应用外层保持 `100vh` 高度并隐藏全局溢出,禁止依赖 `body` 产生页面级滚动条。 - `content-shell` 只负责占满主内容区和承载悬浮层,不作为长内容滚动容器。 - 页面根容器需要设置 `height: 100%`、`min-height: 0`,需要滚动时在页面内部容器上使用 `overflow: auto`。 - 技术方案这类工作台页面优先使用 `command-bar + workspace` 的网格结构,`workspace` 占据剩余高度,左右面板分别内部滚动。 - 设置页使用页面根容器内部滚动,保存工具条继续悬浮覆盖在上层。 ## 打包发布与自动更新 - 客户端使用 `electron-builder` 打包,配置位于 `client/package.json` 的 `build` 字段。 - GitHub Actions 工作流位于 `.github/workflows/release.yml`,推送 `v*` tag 时触发,例如 `v0.1.1`;也支持手动执行 workflow 并输入已有 tag。 - Release notes 由 workflow 根据上一个 tag 到当前 tag 的 `git log` 显式生成,避免 GitHub 原生自动说明只显示 `Full Changelog`。 - Windows 在 `windows-latest` 构建,产物包含 NSIS 安装包和 zip;macOS 在 `macos-latest` 构建,产物包含 dmg 和 zip。 - Windows 图标来自 `assets/icon.ico`,必须允许 electron-builder 编辑 exe 资源;macOS 图标由 workflow 使用 `assets/icon_256.png` 临时生成 `assets/icon.icns` 后打包。 - 当前未接入代码签名,Windows 可能出现 SmartScreen 提示,macOS 可能被 Gatekeeper 拦截;正式分发前再补签名和 macOS notarization。 - 未签名 Windows 安装包首次通过资源管理器双击启动时,可能被 Defender/SmartScreen 扫描而表现为短暂无响应;命令行启动或等待扫描完成后通常会恢复。正式分发前应通过代码签名改善该体验。 - workflow 会从 tag 中同步客户端版本号,例如 tag `v0.1.1` 会临时执行 `npm version 0.1.1 --no-git-tag-version --allow-same-version` 后再打包。 - workflow 使用 `electron-builder --publish never` 只负责构建,再通过 `gh release upload` 显式上传安装包、zip/dmg、blockmap 和 `latest*.yml` 更新元数据。 - 自动更新由 `electron-updater` 负责,只在 `app.isPackaged` 的打包应用中启用;`npm run dev` 开发调试模式不会检查或下载更新。 - 打包应用启动后会自动检查一次更新,之后每 30 分钟轮询一次;自动检查失败保持静默,不打断用户。 - 若 GitHub Release 存在更高版本,应用会先在后台下载更新包;下载完成后通过右上角常驻 Toast 提示“安装并重启 / 稍后”。 - 用户点击“安装并重启”后才会安装更新;不会自动安装,也不会在应用退出时自动安装。 - 用户点击“稍后”或关闭自动更新 Toast 后,同一版本在本次运行期间不会再次自动提醒;设置页“关于”中的“检查更新”仍可手动触发检查和提示。 - 本地 Windows 打包验证可运行: ```powershell cd client npm run dist:win ``` - 本地 macOS 打包需在 macOS 环境运行: ```powershell cd client npm run dist:mac ``` - 发布新版本的基本流程: ```powershell git tag v0.1.1 git push origin v0.1.1 ``` - 如果某个 tag 的 Release 已经创建但缺少产物,可以在 GitHub Actions 页面手动运行 `Release Client`,输入该 tag 重新生成说明并上传产物。 ## 开发约定 - 优先小步提交,不做无关重构。 - 不新增全局状态库,除非实际复杂度需要。 - 不在组件内硬编码大段 Prompt。 - 不在 Renderer 存储 API Key。 - 不直接复用旧 `frontend/` 或 `backend/` 的源码路径。 - 不为新上传功能另写一套文档解析链路;复用 `parseDocumentWithConfig()`,并在删除对应业务资源时复用 `deleteImportedImageBatches()` 清理导入图片资产。 - 命名保持清晰:页面 `*Page.tsx`,服务 `*Service.ts` 或 `*Workflow.ts`,Hook 以 `use` 开头。 ## 验证标准 每次改动至少运行: ```powershell npm run build ``` 涉及依赖变更时额外运行: ```powershell npm audit ``` 涉及 Electron Main 或 preload 时,需要手动运行: ```powershell npm run dev ``` 检查窗口是否正常打开、控制台是否有错误、`window.yibiao` 是否存在。