当前位置: 首页 > news >正文

山东大学软件学院创新实训——CodeGaurd(七)

6.19日

从 V1 骨架到 V14 验收,前端跟随业务迭代逐步演进,而非一开始就设计"完美架构"。

1. API 层的演进:从单一调用到复杂业务流程

V1-V2 时期,API 层只是简单的 CRUD 调用:

export async function fetchProjects() { const { data } = await apiClient.get<Project[]>("/projects"); return data; }

V8 之后,API 层开始承载复杂业务流程:

- 团队准入审批 :申请 → 审批 → 拒绝 → 撤销 → 批量操作
- GitHub 同步 :配置 → 预览 → 应用 → 历史
- 评论状态流转 :确认 → 忽略 → 发布

review.ts从 50 行增长到 540 行,体现了业务复杂度的累积

技术要点 :

- API 函数命名遵循业务语义( previewTeamGitHubSync → applyTeamGitHubSync )
- 参数类型从简单对象演进为嵌套结构
- 响应类型从单一实体演进为聚合结果(如 TeamGitHubSyncApplyResult )

2. 状态管理的演进:从 auth 到团队治理

V1 只有简单的登录状态:

export const useAuthStore = defineStore("auth", { state: () => ({ user: null }), });

V8 之后,状态开始承载复杂业务:

- 团队准入申请 : requestFilters 、 selectedRequestIdsByTeam
- GitHub 同步 : syncConfigForm 、 teamSyncPreview 、 teamSyncHistory
- 定时刷新 : syncHistoryTimer 的生命周期管理
TeamsView.vue的 script 部分超过 1000 行,其中状态定义占了约 100 行。

技术要点 :

- 使用 reactive 管理表单状态,而非分散的 ref
- 定时器生命周期与组件生命周期绑定( onMounted / onBeforeUnmount )
- 状态持久化到 localStorage(如仓库筛选)

3. 路由守卫的演进:从简单跳转到会话恢复

V1-V4 时期,路由只是简单的页面映射。

V8.1 引入 GitHub 真实身份后,路由守卫开始承担认证职责:

router.beforeEach(async (to) => { const authStore = useAuthStore(pinia); await authStore.restoreSession(); // 页面刷新后恢复登录状态 if (to.meta.requiresAuth && !authStore.isAuthenticated) { return { name: "login", query: { next: to.fullPath } }; } });

技术要点 :

- restoreSession的幂等设计:已初始化则跳过
- next参数支持登录后跳转回原目标页面
- 401响应拦截器自动跳转登录页

4. 组件设计的演进:从 StatCard 到嵌套弹窗

V1-V2 只有简单的 StatCard:

<template> <section class="panel metric-card"> <div class="label">{{ label }}</div> <div class="value">{{ value }}</div> </section> </template>

V8 之后,组件开始承载复杂交互:

- 团队详情弹窗 :5 个 Tab + 关键词搜索 + 二级弹窗
- GitHub 同步弹窗 :预览 → 应用 → 历史 + 定时刷新
- 审查任务详情页 :LLM 诊断 + 分块进度 + 问题分组 + 草稿评论
技术要点

- 弹窗嵌套使用 append-to-body 避免层级问题
- Tab 切换时按需加载数据(规则/技能/规范详情)
- 折叠面板实现渐进式信息展示

5. 测试的演进:从零到关键路径覆盖

V9-V10 引入测试:

- analytics.spec.ts:趋势图表算法测试
- draftCommentState.spec.ts:状态机测试
- router.spec.ts:路由守卫测试
技术要点 :

- 优先测试"纯函数"(如 buildTrendPolyline )
- 状态机逻辑单独抽取为 draftCommentState.ts ,便于测试
- 路由守卫测试覆盖认证跳转和会话恢复

6. 类型定义的演进:从简单实体到复杂聚合

V1-V2 时期,类型定义只有基础实体:

// V1-V2:基础实体 interface Project { id: number; name: string; repository_bindings: RepositoryBinding[]; }

V8 之后,类型开始承载复杂聚合结构:

// V8+:复杂聚合 interface ReviewTaskDetail extends ReviewTaskSummary { changed_files: ChangedFile[]; findings: Finding[]; draft_comments: DraftComment[]; review_chunks: ReviewTaskChunk[]; command_runs: ReviewCommandRun[]; review_summary: ReviewSummary | null; llm_diagnostics: ReviewLLMDiagnostics | null; } interface TeamGitHubSyncResult { source_member_count: number; added_count: number; removed_count: number; pending_invite_count: number; add_candidates: TeamGitHubSyncAddCandidate[]; remove_candidates: TeamGitHubSyncRemoveCandidate[]; pending_invites: string[]; skipped_owner_members: string[]; }

index.ts从 50 行增长到 676 行,包含 40+ 个类型定义。

7. 翻译函数的演进:从硬编码到统一映射

V1-V2 时期,状态翻译散落在各组件中。

V9 之后,统一抽取到display.ts :

// 统一的翻译函数 export function translateReviewTaskStatus(status: string) { const labels: Record<string, string> = { PENDING: "待处理", PROCESSING: "处理中", ANALYZED: "已分析", GOVERNED: "已生成建议", PUBLISHED: "已发布", FAILED: "执行失败", }; return labels[status] ?? status; } export function translateDraftCommentStatus(status: string) { const labels: Record<string, string> = { DRAFT: "未确认", PENDING_CONFIRMATION: "未确认", CONFIRMED: "确认待发布", IGNORED: "已忽略", PUBLISHED: "已发布", }; return labels[status] ?? status; }

演进要点 :

- 250 行的翻译函数,覆盖 20+ 种状态/类型
- 统一的 fallback 处理: labels[status] ?? status
- 支持可选参数: translateTeamRole(role: string | null | undefined)

8. 表单状态管理的演进:从分散 ref 到 reactive

V1-V2 时期,表单状态使用分散的 ref :

// V1-V2:分散的 ref const projectName = ref(""); const owner = ref(""); const repo = ref("");

V8 之后,使用 reactive 管理表单状态:

// V8+:reactive 表单 const manualForm = reactive({ project_name: "", owner: "", repo: "", default_branch: "main", }); const scopeForm = reactive({ uses_custom_configuration: false, rule_ids: [] as number[], skill_ids: [] as number[], norm_mapping_ids: [] as number[], });

演进要点 :

- reactive 适合表单场景,修改时无需 .value
- 重置函数统一管理: resetManualForm() 、 resetOAuthForm()
- 类型注解: [] as number[] 明确数组类型

9. 加载状态的演进:从单一 loading 到多状态

V1-V2 时期,只有单一 loading 状态:

const loading = ref(false);

V8 之后,需要区分多种操作状态:

// ProjectsView.vue 的加载状态 const loading = ref(false); const saving = ref(false); const scopeSaving = ref(false); const oauthLoading = ref(false); const repoLoading = ref(false); const branchLoading = ref(false); const oauthBindingSaving = ref(false); const verifyingBindingId = ref<number | null>(null); const diagnosingBindingId = ref<number | null>(null); const syncingBindingId = ref<number | null>(null); const recreatingBindingId = ref<number | null>(null); const teamSavingBindingId = ref<number | null>(null); const teamRequestBindingId = ref<number | null>(null);

演进要点 :

- 按操作类型区分 loading 状态
- 按实体 ID 区分 loading 状态(如 verifyingBindingId )
- 按钮绑定对应 loading: :loading="verifyingBindingId === binding.id"

10. 测试策略的演进:从零到关键路径覆盖

V9-V10 引入测试,优先测试三类内容:

纯函数测试 :

// analytics.spec.ts describe("analytics polyline", () => { it("returns empty string for empty trend", () => { expect(buildTrendPolyline([], (point) => point.created_tasks)).toBe(""); }); it("builds chart points and keeps them within viewport", () => { const trend = [ mockPoint({ date: "2026-03-20", created_tasks: 1 }), mockPoint({ date: "2026-03-21", created_tasks: 3 }), mockPoint({ date: "2026-03-22", created_tasks: 2 }), ]; const points = buildTrendPolyline(trend, (point) => point.created_tasks, { width: 200, height: 100, padding: 10, }); expect(points).toBe("10,63 100,10 190,37"); }); });

状态机测试 :

// draftCommentState.spec.ts describe("draftCommentState", () => { it("allows confirm and ignore only for unconfirmed statuses", () => { expect(canConfirmDraftComment("DRAFT")).toBe(true); expect(canConfirmDraftComment("PENDING_CONFIRMATION")).toBe(true); expect(canConfirmDraftComment("CONFIRMED")).toBe(false); }); it("allows publish only for confirmed status", () => { expect(canPublishDraftComment("CONFIRMED")).toBe(true); expect(canPublishDraftComment("DRAFT")).toBe(false); }); });

路由测试 :

// router.spec.ts describe("router", () => { it("registers the core MVP pages", () => { const routeNames = router.getRoutes().map((route) => route.name); expect(routeNames).toEqual( expect.arrayContaining([ "dashboard", "projects", "review-tasks", "review-task-detail", "comments", ]) ); }); });

测试策略 :

- 纯函数优先测试(无依赖、易断言)
- 状态机逻辑单独抽取便于测试
- 路由配置测试覆盖核心页面

http://www.jsqmd.com/news/1045850/

相关文章:

  • MC9S12XE GPIO深度解析:从寄存器配置到中断实战
  • 微信聊天记录永久保存终极指南:如何让珍贵对话永不丢失
  • 2026年当下,如何精准选择无闪屏的湖南漫反射光源服务商? - 品牌鉴赏官2026
  • 2026珠海本地人必选防水补漏检测维修公司靠谱服务商TOP5推荐:房屋渗漏水检测维修/卫生间/厨房/天花板/阳台/外墙渗漏水检测补漏维修-暗管漏水检测专业仪器精准定位漏水点 - 即刻修防水
  • LPC2470低功耗模式深度解析与硬件设计实战指南
  • 商业门店山野风造景设计方案:多业态店铺仿真绿植软装落地指南 - 三棵树园艺
  • ARP协议实战:从原理到eNSP模拟攻防演练
  • 为什么AI审核了99%的内容,平台还是会“翻车”?一文看懂社交媒体内容审核技术架构
  • 2026玉溪漏水检测维修精选优质服务商TOP5推荐!卫生间漏水/厨房漏水/屋顶天花板漏水/阳台漏水/地下室漏水防水补漏检测维修-正规防水补漏公司优选口碑榜测评推荐 - 即刻修防水
  • 2026年保定市PMP培训机构哪家好?官方授权R.E.P.报考指南 - 众智商学院课程中心
  • 2026襄阳2026正规漏水检测维修公司精选口碑榜TOP5权威推荐-精准定位检测漏水点-专业防水补漏堵漏维修、卫生间/厨房/屋顶/天沟/地下室/阳台防水漏水检测维修 - 安佳防水
  • 3分钟生成专业视频:Pixelle-Video AI全自动短视频引擎完全指南
  • 商业空间顶部造景设计:室内吊顶仿真绿植软装落地方式与场景适配 - 三棵树园艺
  • 5步掌握FitGirl游戏启动器:高效管理压缩游戏的终极工具
  • 湖北世达实用外国语学校招生老师电话 官方最新 - 武汉中职最新信息发布
  • 如何在OBS直播中添加实时语音识别字幕:免费开源插件终极指南
  • 终极Markdown Viewer浏览器插件指南:3分钟实现优雅文档预览
  • 2026年现阶段成都地区有机化工溶剂诚信工厂深度解析与选择指南 - 品牌鉴赏官2026
  • 2026年西安评价高的玻璃门生产厂家哪家强 - 品牌鉴赏官2026
  • 江门报名 CPPM 注册采购经理哪家靠谱?机构选择避坑指南 - 众智商学院课程中心
  • 终极.NET逆向工具dnSpyEx:无源码调试与程序集编辑完全实战指南
  • 2026焦作漏水检测维修精选优质服务商TOP5推荐!卫生间漏水/厨房漏水/屋顶天花板漏水/阳台漏水/地下室漏水防水补漏检测维修-正规防水补漏公司优选口碑榜测评推荐 - 即刻修防水
  • MC68HC908GZ监控模式原理与实战:嵌入式调试的底层利器
  • OpenClaw机器人跨平台安装指南:Node.js驱动的舵机控制实战
  • 如何快速掌握跨设备控制:终极多平台键鼠共享方案
  • 武汉南华光电职业技术学校 2026 年报名入口以及招生办联系方式 - 武汉中职最新信息发布
  • 2026衢州2026正规漏水检测维修公司精选口碑榜TOP5权威推荐-精准定位检测漏水点-专业防水补漏堵漏维修、卫生间/厨房/屋顶/天沟/地下室/阳台防水漏水检测维修 - 安佳防水
  • OmenSuperHub:如何为你的惠普暗影精灵笔记本解锁隐藏性能,提升游戏体验?
  • 2026年台州市PMP培训机构哪家好?官方授权R.E.P.报考指南 - 众智商学院课程中心
  • 从入门到实战:用Altium Designer PDN仿真搞定电源完整性设计