当前位置: 首页 > 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.gsyq.cn/news/1557755.html

相关文章:

  • 为什么AI审核了99%的内容,平台还是会“翻车”?一文看懂社交媒体内容审核技术架构
  • 2026年现阶段成都地区有机化工溶剂诚信工厂深度解析与选择指南 - 品牌鉴赏官2026
  • 终极Markdown Viewer浏览器插件指南:3分钟实现优雅文档预览
  • 湖北世达实用外国语学校招生老师电话 官方最新 - 武汉中职最新信息发布
  • OpenClaw机器人跨平台安装指南:Node.js驱动的舵机控制实战
  • MC68HC908GZ监控模式原理与实战:嵌入式调试的底层利器
  • BenchmarkSQL重大特性更新及claude code对源码的版本分析
  • 2026年电大中专招生简章(附官方报名入口与学费明细) - 武汉中职最新信息发布
  • 终极实战指南:5分钟部署高效大麦网自动化抢票脚本
  • 2026年中江西省刹车片采购指南:如何甄选优质生产源头厂家 - 品牌鉴赏官2026
  • GKCM RF:基于随机森林的核方法条件独立性测试
  • 2026年当下,如何甄选河北地区靠谱的防水隔热背衬板生产合作伙伴? - 品牌鉴赏官2026
  • QObject::sender () 完整详解
  • 2026年更新:深度剖析武汉可靠建设工程施工公司的选择逻辑与价值标杆 - 品牌鉴赏官2026
  • RocketMQ 5.0 实战指南:从部署到主流框架集成
  • MPC555/556 TouCAN控制器:消息缓冲区管理与特殊工作模式详解
  • 2026年电大中专(成人中专)一年制专业招生简章和招生联系方式 - 武汉中职最新信息发布
  • SciTech-Science-Tech.-电池: 铅酸蓄电池的 拆盖、清洗、加注电解液、激活
  • 武汉2026年6月Top5GEO优化公司:多维度对比优劣分析 - GEO优化
  • 【官方】武汉助产学校2026年招生简章 | 招生办咨询电话 - 武汉中职最新信息发布
  • DDrawCompat完全指南:3分钟让经典游戏在现代Windows系统上流畅运行的终极解决方案
  • 5步彻底解决BepInEx IL2CPP启动失败问题:从黑屏崩溃到稳定运行
  • 苏州Top5GEO优化公司2026年6月:解读搜索算法演进趋势 - GEO优化
  • 深度探索nunif iw3:如何将2D视频转换为沉浸式VR 3D体验的技术揭秘
  • 上海Top5GEO优化公司2026年6月:洞察未来搜索布局方向 - GEO优化
  • Ultimaker Cura:免费开源3D打印切片软件的完整指南,5分钟学会专业级打印设置
  • 2026年6月南京GEO优化公司Top5:手把手教你落地方法 - GEO优化
  • Freshman 大模型学习记录
  • 西安2026年6月Top5GEO优化公司:核心技术能力深度拆解 - GEO优化
  • 2026年6月,选择摘星AI江苏代理,开启企业AI搜索精准获客新时代 - 品牌鉴赏官2026