ARTICLE 2026—0814—01FOLIO 2026—0814—01ACRETIONDISKARCHIVUM PERSONAE

WMU 综测计算器技术栈总结与开发复盘:零依赖纯前端的完整形态

温医大综测计算器的技术栈全解:零依赖纯前端如何实现双方案计算、教务成绩粘贴导入、13 学院转专业规则数据化,以及 81 个零安装单测与 GitHub Actions CI 的工程化闭环。

WMU 综测计算器技术栈总结与开发复盘:零依赖纯前端的完整形态

给温州医科大学同学做了一个「学生素质综合测评计算器」(含独立的转专业考核计算器):按照《学生素质综合测评办法》把 C1 思想品德 / C2 课程成绩 / C3 发展素质的算法、加分细则、体测降档判定全部做成网页,另配套本部 13 个学院的转专业考核办法计算。部署在 GitHub Pages,上线后同学直接打开链接就能用。

这个项目最特别的地方不是功能多,而是整个仓库没有任何依赖:没有 package.json、没有框架、没有构建步骤,双击 index.html 就能跑,测试用 Node 内置模块就能全绿。这篇把技术栈和架构取舍完整讲一遍。

文中截图均为项目真实页面,数据为虚构演示数据。计算仅供学生自评参考,测评结果以班级测评小组、系及学院认定为准。

一句话总览

一个零依赖、零构建的纯前端静态网站 + Node 内置单测 + GitHub Actions CI + GitHub Pages 部署

技术 职责
页面载体 HTML5 + 原生 JavaScript index.html(综测)+ transfer.html(转专业)互链
样式 手写 CSS 单文件(22.9 KB) 卡片式表单布局、明暗两套观感
计算核心 纯函数模块 calc.js / transfer.js C1/C2/C3、加权平均、排名、体测判定,不碰 DOM
评分数据 data.js 方案注册表 本部 / 仁济两套方案、表 1~表 7 加分细则
持久化 localStorage + JSON 导入导出 主数据、档案卡、转专业输入三个 key
测试 Node 内置 assert + 自写微型 runner 3 个测试文件共 81 个用例,零安装
CI GitHub Actions(ubuntu + Node 20) push / PR 自动跑三个测试
部署 GitHub Pages 分支部署 手动推送即上线,无自动部署
可选验证 Playwright 渲染冒烟脚本 本机人工跑,不入 CI

仓库结构一眼看完:

index.html           综测页面骨架(个人测评 / 班级排名 / 数据管理 三个 tab)
transfer.html        转专业考核计算页面(与综测互链)
css/style.css        全部样式
js/data.js           评分表数据(表1~表7、两套方案、五级换算)
js/calc.js           综测纯计算逻辑(C1/C2/C3/总分/排名/教务文本解析)
js/storage.js        localStorage 持久化 + JSON 导入导出
js/app.js            综测交互层(渲染、快捷添加、学年管理、导入 UI)
js/transfer.js       转专业数据 + 纯计算(本部 13 学院 + 仁济)
js/transfer-app.js   转专业页面交互逻辑
test/*.test.js       计算层 / 存储层单测(Node 直接跑)
scripts/verify-transfer.js  Playwright 渲染冒烟验证(可选)

六个 JS 文件约 3200 行:data.js(728 行)和 transfer.js(547 行)几乎全是规则数据,app.js(1022 行)是交互层,真正的计算函数加起来不到 300 行——规则是数据,逻辑是函数,这是整个项目最核心的架构决定。

先看成品

综测页首屏(1440×900 视口)——顶部是方案切换与个人信息,往下依次是学年概览、C1 思想品德、C2 课程成绩、C3 发展素质四块:

综测计算器首屏

整页全貌——可以看到课程表支持百分制 / 五级制混排、课程性质标记、快捷添加按钮和底部的学年综合成绩概览:

综测计算器整页

转专业页首屏——左侧选学院与专业、录入成绩与排名,右侧实时输出综合成绩与分项明细:

转专业计算器首屏

转专业页整页——下方还有该学院考核办法原文卡片:

转专业计算器整页

架构:三条设计主线

1. 计算层与 DOM 严格分离,一份代码双环境复用

calc.js / transfer.js 是纯函数模块:不引用 document、不碰 localStorage,通过 window.ZCCalc 导出给浏览器,同时 module.exports 导出给 Node——测试直接 require("../js/calc.js") 就能跑,不需要浏览器、不需要 mock DOM:

// calc.js 尾部:双环境导出
if (typeof module !== "undefined" && module.exports) {
  module.exports = { ... };
} else if (typeof window !== "undefined") {
  window.ZCCalc = { ... };
}

这是「零依赖也能有像样测试」的关键:把计算从 UI 里抠出来,测试就只需要喂数据、断言结果。教务文本解析、五级制换算、加权平均、排名这些最容易算错的逻辑全部落在纯函数里,交互层只负责把 DOM 值喂进去、把结果显示出来。

2. 方案注册表:切换校区 = 换一套数据

温医大本部与仁济学院的评分表、基准分、体测文案都不一样(本部 C3 基准 65 分,仁济 70 分)。data.jsSCHEMES 注册表收拢两套方案,setScheme() 一次性把 FIVE_GRADE / WEIGHTS / BASE_C1 / BASE_C3 / CAP / C1_PASS / C2_FAIL_CREDITS 等常量挂到全局:

const SCHEMES = {
  benbu: { label: "温医大本部", ... },   // 本部:C3 基准 65
  renji: { label: "仁济学院", ... },     // 仁济:C3 基准 70
};
function setScheme(key) { /* 把该方案的全部常量挂到全局 */ }

切换方案时,评分表、快捷添加面板、公式速览、体测提示全部跟随变化,UI 代码零改动——新增一套方案 = 在注册表里加一份数据,而不是改逻辑。

3. 脚本加载顺序契约

index.htmldata.js → calc.js → storage.js → app.js 顺序加载,依赖全局变量链传递数据。这是零依赖方案的代价:顺序敏感、全局命名空间共享,所以交接文档里专门有一条「新增脚本别打乱顺序」。小项目里这是可接受的简单性,但它是后续接手的第一个坑位。

数据模型与持久化

三个 localStorage key 各管一件事:

key 内容
wmu-zongce-v1 主数据:{profile, scheme, years[], classMembers[]}
wmu-zongce-archives-v1 个人档案卡列表(命名快照,可载入 / 同步 / 导出 / 分享)
wmu-transfer-v1 转专业页的方案、学院、专业、输入值

主数据带 version 字段,load() 时做结构合并 + scheme 白名单校验——旧数据缺字段自动补默认值,非法方案回退本部,为将来迁移留了口子。跨设备数据同步没有后端,靠 JSON 导出 / 导入文件兜底;「个人档案卡」则让多位同学共用一台设备时能互不干扰地建档。

功能细节里的工程味道

  • 教务成绩粘贴导入:教务系统复制成绩表 → 粘贴 → parseJwText 解析。表头定位 + 制表符 / 空格分隔 + 作废行跳过,补考、重修、等级制自动识别并提示计分规则(补考按 60 分、重修按最高分计入)。
  • 豆包 OCR 批量导入:内置引导卡片——截图 → 豆包 OCR(一键复制的提示词)→ 把 OCR 输出的表格粘回本站。OCR 结果走同一条解析器:入口可以有多个,解析逻辑只有一份。
  • 五级制与百分制混排:每门课可独立切换,优 = 90 / 良 = 80 / 中 = 70 / 及格 = 60 / 不及格 = 50,切换自动换算清空;「仅统计必修 + 限选」开关与教务口径对齐,排除任选课。
  • 边界钳制:C1 加分封顶 100、减分下限钳到 0 不出现负分;空学年不计算误导性总分(显示「—」);不及格学分 ≥ 20 自动提示不合格。
  • 体测降档判定:总分 ≥ 80(良好)不降档、低于 80 降一等级评定,保健班 / 保健科证明豁免,红绿徽章实时提示——依据《奖学金实施办法》写成了纯函数 peVerdict
  • 快捷添加矩阵:表 1~表 7 全部做成快选,学科竞赛、文体竞赛用「级别 × 等级」矩阵编码(省级一等奖 2 分这类),已按 20-21、23-24 学年测评会议纪要维护,也支持自由录入。

转专业计算器:把规章文件变成数据

本部按接收学院切换 13 个学院的《选拔类转专业考核实施办法》(2024-12-30 印发),每份办法被拆解成结构化数据:专业成绩排名公式(含各专业不同系数,如第一临床 0.15/0.3/0.5、护理分档制)、面试满分与合格线(精神医学面试 < 18 分不予录取)、大学英语计分表(10 种规则,覆盖四六级、眼视光雅思 / 托福、口腔 / 药学特殊分档)、专长档次、高考成绩计分(口腔公式 10 − [(最低录取分 − 高考) × 0.25])、跨专业计分(眼视光非医 → 医 0 分)。

仁济学院则是 2025 年统一方案:成绩 65% / 面试 25% / 专长 5% / 英语 5%,并判定第一轮(前 15%)、第二轮(前 50%)申请资格。

transfer.js 的 547 行里大部分是 BENBU_COLLEGES 的数据声明,计算逻辑与数据完全分离——改学院办法 = 改数据,不动计算代码。选好学院和专业,页面自动匹配该学院的公式与计分规则,实时输出综合成绩、分项明细和资格判定。

测试与 CI:零依赖也能有 81 个用例

三个测试文件直接 node test/calc.test.js 运行,全部使用 Node 内置的 assert + 十几行自写的微型 runner(t(name, fn) 收集断言计数),不需要安装任何东西

node test/calc.test.js       # 33 用例:C1/C2/C3、五级换算、教务文本解析、体测判定
node test/transfer.test.js   # 25 用例:13 学院公式、英语计分、资格判定
node test/storage.test.js    # 23 用例:持久化结构合并、scheme 白名单、导入校验

计算层单测的关键技巧是上面说的「双环境复用」:测试里 require("../js/calc.js") 直接调纯函数,配合 setScheme("renji") 把全局常量切到仁济方案验证——每个测试文件都覆盖了两套方案的边界(比如 C3 基准 65 / 70)。

GitHub Actions 的 CI 只有 20 行:ubuntu + Node 20,push / PR 时依次跑三个测试。没有 lint、没有构建、没有部署步骤——因为项目根本不需要构建

复盘:零依赖的得与失

得到了什么

  • 双击即用index.html 本地打开就能跑,数据存本机浏览器;部署到 GitHub Pages 后链接即开箱。
  • 零供应链风险:没有 npm 依赖就没有 node_modules 泄漏、没有构建故障、没有依赖升级破坏;仓库 clone 下来就 2 MB。
  • 测试零门槛:任何有 Node 的机器直接跑测试,CI 配置也极简。
  • 零后端成本:数据全在 localStorage,个人使用场景完全够用。

付出了什么

  • 跨设备数据靠手动:没有账号体系,只能 JSON 导出 / 导入,这是最明显的短板。
  • UI 全靠手写 DOMapp.js 1022 行里大量模板字符串拼 HTML,交互复杂度再翻一倍就会吃力。
  • 全局命名空间共享:脚本加载顺序敏感,新增模块要遵守既有契约。
  • 规则维护靠人:加分细则、学院方案更新时要手动改 data.js / transfer.js 并同步更新测试用例(README 里有「想改什么改哪里」对照表)。

什么时候该上框架?我的判断是:交互复杂度再翻一倍、需要组件复用或多端复用时,会认真考虑 Vue / React + 构建工具链。但「表单 + 计算器 + 规则数据」这个规模,零依赖仍然是最优解——它把「部署」这个环节压缩到接近零,把「出 bug」的概率压到供应链之外。

结尾

技术栈本身没有秘密:HTML、CSS、原生 JS、localStorage、Node 内置测试、GitHub Actions、GitHub Pages。真正值得抄的是三条工程习惯——计算与 UI 分离、规则数据化、测试零依赖。这套组合让一个没有构建工具链的静态网页,也能做到有测试、有 CI、有清晰的数据模型,算是在「最小规模」和「工程化」之间找到了一个很舒服的平衡点。

项目托管在 GitHub Pages(wmu-zongce),计算规则依据学校公开的测评办法维护,仅供学生自评参考。

读完了FINIS

文章到这里结束本条目至此结束

评论COMMENTS IN THE MARGIN— ITEMS
仅文字 · 最多 300 字 · 显示 IP 属地仅文字 · 不能附加图片 · 发送后标注 IP 属地0 / 300