开发说明.md 19 KB

易标投标工具箱 Client 开发说明

本文面向参与 client/ 独立客户端开发的成员,目标是快速理解架构边界、目录职责和统一开发风格。

技术栈

  • Electron:桌面窗口、本地文件系统、配置存储、后续打包入口。
  • Vite + React + TypeScript:Renderer 层 UI 与业务流程编排。
  • Radix UI:Dialog、Tooltip、Switch 等无样式基础组件。
  • CSS:当前使用全局 CSS,不引入 Tailwind,不复用旧 frontend/ 样式。

运行命令

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 保存大文本或业务流程状态。

目录职责

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/<feature-name>/ 下:

features/<feature-name>/
├── 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 暴露:

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 暴露 fspathipcRenderer 原始对象。
  • Renderer 不决定文件解析方式;文件解析方式由 Main 从 configStore 读取配置后选择。
  • 新增任何文件上传、导入、转换功能时,不要直接调用 doc2markdown/convert.mjs 或自行拼接 MinerU 请求;统一在 Main 侧调用 electron/services/fileService.cjsparseDocumentWithConfig(app, filePath, config, { assetScope })
  • parseDocumentWithConfig() 会统一处理本地解析、MinerU Agent、MinerU Accurate、MinerU 不支持格式自动切回本地解析、设置页“保留图片”和图片资产落盘。业务代码只负责选择文件、保存业务状态和传入稳定的 assetScope
  • assetScope 必须能对应到业务资源,例如技术方案使用 technical-plan,知识库文档使用 knowledge-${documentId}。后续新功能应使用 <feature>-<resourceId> 这类可定位前缀,便于删除时清理历史图片。

数据存储

  • 配置数据保存到 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/<assetScope>-<timestamp>-<random>/,正文中通过 yibiao-asset://imported-images/... 引用,不把图片 base64 长期写入业务 JSON。
  • 删除或重置某个业务资源时,必须调用 electron/utils/importedImages.cjsdeleteImportedImageBatches(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

使用方式:

<FloatingToolbar groups={groups} />
  • 不提供 App 级默认工具条;需要流程控制或保存入口的页面自行渲染 FloatingToolbar,例如技术方案步骤工具条、设置页保存工具条、标书查重处理工具条。
  • 页面主体不要重复放与工具条同语义的按钮,避免同一动作出现多个入口。
  • 工具条前置拖动手柄,用户只能通过手柄拖动工具条;不要让普通按钮区域承担拖动,避免点击和拖拽冲突。
  • 工具条应悬浮在内容上层,不要求页面底部 padding-bottom 或空白占位;被遮挡的长内容通过内部滚动查看。

布局与滚动

  • 应用外层保持 100vh 高度并隐藏全局溢出,禁止依赖 body 产生页面级滚动条。
  • content-shell 只负责占满主内容区和承载悬浮层,不作为长内容滚动容器。
  • 页面根容器需要设置 height: 100%min-height: 0,需要滚动时在页面内部容器上使用 overflow: auto
  • 技术方案这类工作台页面优先使用 command-bar + workspace 的网格结构,workspace 占据剩余高度,左右面板分别内部滚动。
  • 设置页使用页面根容器内部滚动,保存工具条继续悬浮覆盖在上层。

打包发布与自动更新

  • 客户端使用 electron-builder 打包,配置位于 client/package.jsonbuild 字段。
  • 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 打包验证可运行:

    cd client
    npm run dist:win
    
  • 本地 macOS 打包需在 macOS 环境运行:

    cd client
    npm run dist:mac
    
  • 发布新版本的基本流程:

    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 开头。

验证标准

每次改动至少运行:

npm run build

涉及依赖变更时额外运行:

npm audit

涉及 Electron Main 或 preload 时,需要手动运行:

npm run dev

检查窗口是否正常打开、控制台是否有错误、window.yibiao 是否存在。