Files
lpt-fe/AGENTS.md
T

12 KiB
Raw Blame History

LPT 前端应用(lpt-fe

Vue 3 + TypeScript + Vite 单页应用。

Git 提交规范

  • 提交信息必须简短且使用中文,不要使用英文长句。
  • 格式:类型: 简述,例如 feat: 新增学习会话页面fix: 修复路由守卫401跳转docs: 补充组件说明

技术栈

  • 框架:Vue 3 + TypeScript + Vite 5
  • UI 库:Element Plus 2.8
  • HTTPAxioswithCredentials: true,自动携带 Cookie satoken
  • 思维导图:mind-elixir 5.13
  • Markdownmarked 18.0
  • 测试:Vitest + Playwright

项目结构

lpt-fe/src/
├── api/              → API 接口层
├── components/       → 页面组件
├── components/composables/ → 组合式函数
├── router/           → 路由配置
├── utils/            → 工具函数
├── assets/           → 全局样式与资源
└── __tests__/        → 单元测试

核心组件:Login.vueWelcome.vueStudy.vueStartTask.vueReview.vueReviewDetail.vueReviewRecall.vueTaskForm.vueMindMapViewer.vueMarkdownRenderer.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: trueCookie satoken 自动携带。
  • 401 处理:HTTP 层和业务层双重检测,清理 localStorage.isLoggedIn 后跳转 /login
  • validateResponsecode !== 200 时 reject。
  • Vite 代理:/apihttp://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-blockborder-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 阻止重复触发,不能只依赖路由跳转后的页面卸载。

按钮规范

  • 主操作 / 卡片 CTAsize="large"(实测 40px 高、15px 字),如 Study 开始任务、Welcome 快速入口。
  • 区块工具栏 / 页内操作:默认尺寸(32px 高、14px 字),如 Review 刷新、ReviewRecall 面板操作。
  • 表格/列表行内操作:size="small"(28px 高、13px 字;不得再调小)。
  • 弹窗 footer:默认尺寸;确认按钮 type="success",取消按钮默认;危险操作统一 type="danger"
  • 正向/保存语义统一 success,次级编辑语义可用 primary,不要在同一功能上混用。
  • 窄屏(max-width: 768px)操作区按钮纵向全宽,全局规则位于 src/assets/main.css.action-row / .edit-actions / .fragment-edit-actions / .task-actions

颜色与主题

  • 禁止在组件内写颜色字面量:新增颜色先加到 src/assets/base.css:root,组件只用 var(--x)。语义色已提供 --accent-warning / --accent-warning-soft / --accent-warning-strong / --accent-success-soft / --accent-success-softer / --accent-success-strong / --accent-success-text
  • Element Plus 主题色在 src/assets/main.css:root 覆盖(--el-color-success / --el-color-primary 指向 --green-700),全站按钮/标签/开关自动生效,不要在组件里逐个覆盖 EP 组件色。
  • src/main.ts 中自定义样式必须写在 import 'element-plus/dist/index.css' 之后,否则主题覆盖会被 EP 覆盖。
  • 实底主色统一用满足 WCAG AA(白字对比度 ≥4.5:1)的 --green-700--green-600 仅用于文字/链接。
  • 颜色字面量不得出现在 JS/模板表达式里(如 :color="'#xxx'" 引 CSS 变量会失效),需要动态色时保留字面量或改用 CSS 类。

卡片列表交互

  • 卡片列表的卡片整体可点击进入详情:外层加 role="link" tabindex="0"aria-label,并绑定 @click@keydown.enter/@keydown.space.prevent
  • 卡片内的附加按钮必须 @click.stop,避免被卡片点击吞掉(如 Review 的「回忆复习」)。
  • 卡片操作使用右对齐操作簇(justify-content: flex-end),禁止用 space-between 把两个按钮拉到卡片两端
  • 加载失败必须区分于空数据:保留一个 loadFailed 状态,失败时给出可读提示 + 重试按钮,重试前先重置该状态;不要只靠 finally 关 loading 导致空白页。

移动端适配

  • 断点约定:按钮/弹窗使用 768px,卡片与汇总布局使用 900px。
  • 弹窗:窄屏宽度 calc(100% - 24px)body 允许纵向滚动,footer 按钮等宽;全局规则位于 main.css@media (max-width: 768px),新增弹窗不需要再逐页适配。
  • 多按钮弹窗(如 Welcome 回忆卡片)在窄屏纵向堆叠。
  • 列表页优先使用卡片列表而非 el-table,桌面与移动端视觉统一,参考 Review.vue.task-list
  • 文案不缩写:使用完整字段名,如 学习报告数量学习残片数量,不使用 报告数残片数

代码整洁

  • Markdown 渲染统一使用 src/utils/markdown.tsrenderMarkdown,禁止在组件内复制实现。
  • 应用场景状态常量统一使用 src/utils/taskApplication.tsapplicationStatusOptions
  • 用户可见错误提示必须口语化、可理解,不要直接展示接口原始 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 --noEmitnpm run lint0 error 才可提交;no-explicit-any 允许存量警告,新代码避免 any)、npm run testnpm run build
  • 提交信息按 Git 提交规范使用中文短句;样式类改动用 style:,清理类用 refactor: / chore:

开发配置

  • 开发端口:5158
  • 代理:/apihttp://localhost:5157
  • 环境变量:.env.development / .env.production / .env.uat

启动命令

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

编译检查也可以使用:

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 服务,前端不直接调用

调用关系

浏览器 → lpt-fe (5158) ──/api──→ lpt-be (5157) ──HTTP──→ lpt-ai (5199)
                                      │
                                      └── MySQL (8109)