205 lines
11 KiB
Markdown
205 lines
11 KiB
Markdown
# LPT 前端应用(lpt-fe)
|
||
|
||
> Vue 3 + TypeScript + Vite 单页应用。
|
||
|
||
## Git 提交规范
|
||
|
||
- 提交信息必须简短且使用中文,不要使用英文长句。
|
||
- 格式:`类型: 简述`,例如 `feat: 新增学习会话页面`、`fix: 修复路由守卫401跳转`、`docs: 补充组件说明`。
|
||
|
||
## 技术栈
|
||
|
||
- 框架:Vue 3 + TypeScript + Vite 5
|
||
- UI 库:Element Plus 2.8
|
||
- HTTP:Axios(`withCredentials: true`,自动携带 Cookie `satoken`)
|
||
- 思维导图:mind-elixir 5.13
|
||
- Markdown:marked 18.0
|
||
- 测试:Vitest + Playwright
|
||
|
||
## 项目结构
|
||
|
||
```text
|
||
lpt-fe/src/
|
||
├── api/ → API 接口层
|
||
├── components/ → 页面组件
|
||
├── components/composables/ → 组合式函数
|
||
├── router/ → 路由配置
|
||
├── utils/ → 工具函数
|
||
├── assets/ → 全局样式与资源
|
||
└── __tests__/ → 单元测试
|
||
```
|
||
|
||
核心组件:`Login.vue`、`Welcome.vue`、`Study.vue`、`StartTask.vue`、`Review.vue`、`ReviewDetail.vue`、`ReviewRecall.vue`、`TaskForm.vue`、`MindMapViewer.vue`、`MarkdownRenderer.vue`。
|
||
|
||
## 路由表
|
||
|
||
| 路径 | 组件 | 说明 |
|
||
|------|------|------|
|
||
| `/login` | Login.vue | 登录页(无需认证) |
|
||
| `/welcome` | Welcome.vue | 首页 |
|
||
| `/study` | Study.vue | 学习任务列表 |
|
||
| `/start-task/:taskNum` | StartTask.vue | 学习会话 |
|
||
| `/add-task` | TaskForm.vue | 创建任务 |
|
||
| `/update-task/:taskId` | TaskForm.vue | 更新任务 |
|
||
| `/review` | Review.vue | 复习总览 |
|
||
| `/review/detail/:type/:id` | ReviewDetail.vue | 复习详情 |
|
||
| `/review/recall/:taskNum` | ReviewRecall.vue | 回忆复习 |
|
||
|
||
## 认证与请求
|
||
|
||
- Axios `withCredentials: true`,Cookie `satoken` 自动携带。
|
||
- 401 处理:HTTP 层和业务层双重检测,清理 `localStorage.isLoggedIn` 后跳转 `/login`。
|
||
- `validateResponse`:`code !== 200` 时 reject。
|
||
- Vite 代理:`/api` → `http://localhost:5157`,自动重写去掉 `/api` 前缀。
|
||
|
||
## Markdown 学习材料
|
||
|
||
- 编辑:textarea 输入,支持 `[文字](url)` 和裸 URL。
|
||
- 预览:`renderMarkdown(raw)` 同步函数,使用 `__LINK_N__` 占位符防止嵌套 HTML。
|
||
- 保存:`convertMaterialUrls()` 异步拉取标题,替换裸 URL 为 `[标题](url)`。
|
||
- `getUrlTitle(url)`:调用 `/utils/fetch-title`,带内存缓存和请求去重。
|
||
|
||
## 应用场景
|
||
|
||
- TaskForm 编辑页使用 `el-dialog` 弹窗管理应用场景。
|
||
- Study 详情页使用 `getUrlTitle` 展示应用场景链接标题。
|
||
|
||
## 样式与交互约定
|
||
|
||
- 链接使用绿色系 `var(--green-600)`,虚线下划线,hover 变实线。
|
||
- 区块分隔使用 `.detail-block`:`border-top` + `padding-top`,首个除外。
|
||
- 小字提示使用 12px `var(--text-secondary)`。
|
||
- `Study.vue` 任务清单每页 20 条。
|
||
- `StartTask.vue` 编辑任务时先 GET 详情,合并后再 PUT,避免覆盖其他字段。
|
||
- `MindMapViewer.vue` 支持只读、编辑、selectable、colorByCompare 模式;selectable 模式下点击节点 emit `node-select`。
|
||
- ReviewRecall 中 `standardExpanded` 展开后节点可点击,用于选择复习起点。
|
||
|
||
## 前端代码标准
|
||
|
||
### 交互防抖与加载原则
|
||
|
||
- 点击后需要跳转到其他页面时,应立刻执行跳转,不要在跳转前等待接口返回;目标页面在数据未加载完成时必须展示页面级 loading。
|
||
- 点击后不跳转页面时,触发接口的按钮必须自身展示 loading 或在请求期间禁用,防止网络慢时被多次触发。
|
||
- 登录、创建/更新、结束会话等必须确认后端成功的操作属于例外:按钮先进入 loading,成功后再跳转。
|
||
- 同一异步动作执行期间必须通过 loading 或 disabled 阻止重复触发,不能只依赖路由跳转后的页面卸载。
|
||
|
||
### 按钮规范
|
||
|
||
- 页面主操作 / 卡片 CTA:`size="large"`,如 Study 开始任务、TaskForm 保存、Welcome 快速入口。
|
||
- 区块工具栏 / 页内操作:默认尺寸,如 Review 刷新、ReviewRecall 面板操作、历史记录。
|
||
- 表格/列表行内操作:`size="small"`,主操作实底 `type="success"`,次操作 `text`;如 Review 卡片中「回忆复习」为主、「查看记录」为次。
|
||
- 弹窗 footer:默认尺寸;确认按钮 `type="success"`,取消按钮默认;危险操作统一 `type="danger"`。
|
||
- 正向/保存语义统一 `success`,次级编辑语义可用 `primary`,不要在同一功能上混用。
|
||
- 窄屏(`max-width: 768px`)操作区按钮纵向全宽,全局规则位于 `src/assets/main.css` 的 `.action-row / .edit-actions / .fragment-edit-actions / .task-actions`;`.task-actions` 中主操作在上、次操作在下(如 Review 的回忆复习/查看记录)。
|
||
|
||
### 移动端适配
|
||
|
||
- 断点约定:按钮/弹窗使用 768px,卡片与汇总布局使用 900px。
|
||
- 弹窗:窄屏宽度 `calc(100% - 24px)`,body 允许纵向滚动,footer 按钮等宽;全局规则位于 `main.css` 的 `@media (max-width: 768px)`,新增弹窗不需要再逐页适配。
|
||
- 多按钮弹窗(如 Welcome 回忆卡片)在窄屏纵向堆叠。
|
||
- 列表页优先使用卡片列表而非 `el-table`,桌面与移动端视觉统一,参考 `Review.vue` 的 `.task-list`。
|
||
- 文案不缩写:使用完整字段名,如 `学习报告数量`、`学习残片数量`,不使用 `报告数`、`残片数`。
|
||
|
||
### 代码整洁
|
||
|
||
- Markdown 渲染统一使用 `src/utils/markdown.ts` 的 `renderMarkdown`,禁止在组件内复制实现。
|
||
- 应用场景状态常量统一使用 `src/utils/taskApplication.ts` 的 `applicationStatusOptions`。
|
||
- 用户可见错误提示必须口语化、可理解,不要直接展示接口原始 message、JSON、参数名或“服务器返回”等技术信息;技术细节留在日志。
|
||
- 不保留无消费者代码:未使用的 import、prop、事件、API 封装、CSS class、组件文件与依赖应及时删除。
|
||
- 组件公开 props/events 只在有实际消费者时保留,例如 MindMapViewer 通过 `toOutline()` 导出编辑内容,不依赖无人监听的 change 事件。
|
||
- Vite 模板遗留文件(示例组件、icon、logo)不进入业务代码。
|
||
|
||
### 请求层与类型规范
|
||
|
||
- 所有接口封装必须放在 `src/api/`,组件内禁止直接拼 URL 调用 `request`(历史遗留的裸调用在改动到时迁移)。
|
||
- request 层统一返回 `Promise<ApiResponse<T>>`:新增接口必须显式标注泛型 `T`,组件消费 `res.data` 时应有明确类型,禁止把后端字段拼错暴露到运行期。
|
||
- 默认超时 30 秒;AI 聚合类接口(残片生成、思维导图生成/对比、报告草稿、结束会话)在 api 层显式覆写 `{ timeout: 300_000 }`,不要调大全局默认值。
|
||
- 组件解构响应时使用 `res?.data` 判空兜底;`ApiResponse` 从 `@/utils/request` 导入复用。
|
||
|
||
### 组合式函数复用
|
||
|
||
- “已等待 N 秒”类秒表计时统一使用 `useElapsedSeconds`,禁止组件内手写 `setInterval` 秒数递增。
|
||
- 页面新增功能域时先抽 composable(参考 `useSessionHistory` / `useSummaryReport` / `useSessionExpectation`),不要继续膨胀 StartTask 等大页面组件。
|
||
- 通用解析器、格式化工具放 `src/utils/`,不埋在组件内(如大纲文本转树应放 utils)。
|
||
|
||
### 测试规范(分层策略)
|
||
|
||
测试按「金字塔」分层组织,新增功能优先补中间层:
|
||
|
||
| 层 | 位置 | 职责 | 风格 |
|
||
|----|------|------|------|
|
||
| 逻辑单测 | `src/__tests__/{api,composables,utils,router}/` | 纯函数、composable、request 封装 | 直接调用,快、准 |
|
||
| **交互式集成测试** | `src/__tests__/integration/` | 页面关键用户路径 | 真实挂载 + 模拟点击 + DOM 断言 |
|
||
| E2E | `e2e/` | 真实浏览器回归 | Playwright + `page.route` 拦后端 |
|
||
|
||
### 交互式集成测试要求
|
||
|
||
- 必须真实挂载组件(不 `shallow`),通过 `trigger('click')`、`setValue()`、`find('…')` 模拟并断言用户可见行为;禁止 `wrapper.vm.xxx()` 直调内部方法作为主要测试手段(历史遗留的 vm 直调在改动到时迁移)。
|
||
- 页面关键路径必须有集成测试:登录、学习会话暂停/继续、任务创建/更新、回忆对比等新增关键流程同步补 `integration/*.spec.ts`。
|
||
- 只断言应用自身行为(API 调用参数、提示、路由、状态文案),不要重复验证 Element Plus 内部行为;jsdom 下 EP 的 callback 式表单校验不可靠(空表单也可能判有效),「校验拦截」类断言由 e2e 在真实浏览器覆盖。
|
||
- 已知兼容问题:`el-tag` 在 jsdom + VTU 全量挂载时 vnode mounted 钩子崩溃(EP 2.8),集成测试统一 `stubs: { ElTag: true }`;依赖 mind-elixir 的 `MindMapViewer` 用可编程 stub(提供 `toOutline`)。
|
||
- E2E 定位优先使用角色与可访问名称(`getByRole('button', { name })`、`getByPlaceholder`),仅断言 EP 内部 UI(校验错误、消息弹层)时才用类选择器;改文案不应导致大面积碎测。
|
||
|
||
### 逻辑单测要求
|
||
|
||
- 单元测试必须直接测试真实代码:直接调用 composable、挂载真实组件、使用真实路由;禁止把组件或 composable 的逻辑复制到测试里再自测。
|
||
- 测试中可以 mock 外部依赖(API、Element Plus 服务、Audio 等),但被测逻辑本身必须来自生产模块。
|
||
- `script setup` 内部状态需要测试访问时,通过 `defineExpose` 暴露,而不是在测试里重写一份相同逻辑。
|
||
|
||
### 覆盖率
|
||
|
||
- `npm run test:coverage` 输出 v8 覆盖率报告(`coverage/`),阈值配置在 `vite.config.ts`,作为棘轮只升不降:任何低于阈值的改动不允许提交。
|
||
- 提升覆盖率优先补集成测试与零覆盖模块(当前缺口:Review、ReviewDetail、fetchTitle、markdown 渲染分支),不为凑数写快照式断言。
|
||
|
||
### 提交前检查
|
||
|
||
- 运行 `npx vue-tsc --noEmit`、`npm run lint`(0 error 才可提交;`no-explicit-any` 允许存量警告,新代码避免 any)、`npm run test`、`npm run build`。
|
||
- 提交信息按 Git 提交规范使用中文短句;样式类改动用 `style:`,清理类用 `refactor:` / `chore:`。
|
||
|
||
## 开发配置
|
||
|
||
- 开发端口:5158
|
||
- 代理:`/api` → `http://localhost:5157`
|
||
- 环境变量:`.env.development` / `.env.production` / `.env.uat`
|
||
|
||
## 启动命令
|
||
|
||
```bash
|
||
npm install
|
||
npm run dev
|
||
npm run build
|
||
npm run test
|
||
npm run test:e2e
|
||
npx vue-tsc --noEmit
|
||
npm run lint
|
||
npm run test:coverage
|
||
```
|
||
|
||
编译检查也可以使用:
|
||
|
||
```bash
|
||
npx vite build
|
||
npx vue-tsc --noEmit
|
||
```
|
||
|
||
## Docker
|
||
|
||
- 多阶段构建:`node:20-alpine` 编译 → `nginx:alpine` 运行。
|
||
- 暴露端口:80。
|
||
- 构建参数:`BUILD_MODE`,默认 production。
|
||
|
||
## 关联项目
|
||
|
||
| 项目 | 路径 | 端口 | 说明 |
|
||
|------|------|------|------|
|
||
| lpt-be | `../lpt-be/` | 5157 | Spring Boot 后端,前端通过 `/api` 代理调用 |
|
||
| lpt-ai | `../lpt-ai/` | 5199 | AI 服务,前端不直接调用 |
|
||
|
||
## 调用关系
|
||
|
||
```text
|
||
浏览器 → lpt-fe (5158) ──/api──→ lpt-be (5157) ──HTTP──→ lpt-ai (5199)
|
||
│
|
||
└── MySQL (8109)
|
||
```
|