用华为云码道构建校园导航应用「新生通」:从需求分析到工程落地

用华为云码道构建校园导航应用「新生通」:从需求分析到工程落地

新生通是一套面向高校的校园导航 Web 应用:用原生 ES Module 实现矢量校园地图、场所详情、课表联动与换校配置,零第三方依赖、零构建步骤 ,132 项自动化测试全部通过,代码开源于 AtomGit(https://atomgit.com/2403_87838053/xinshengtong)。

在"校园面积 1200×800 米、28 个点位、9 份 JSON 数据"的限定条件下,它把"找教室、取快递、找座位、看施工"四类高频查询从平均 4 步操作压缩到 1 次点击。

本文回答以下 10 个问题

  1. 校园导航类应用应该怎样组织信息?
  2. 零依赖、无构建的前端方案是否可行,代价是什么?
  3. 课表如何与地图联动,并给出出发时间?
  4. 换一所学校需要改什么代码?
  5. 132 项自动化测试具体覆盖哪些内容?
  6. 华为云码道(CodeArts 代码智能体)在开发中承担了什么角色?
  7. 这套方案与通用地图、教务系统是什么关系?
  8. 如何在本机运行、二次开发与替换数据?
  9. 前端如何做到可验证的安全与无障碍?
  10. 当前方案有哪些明确的局限?

一、项目背景与问题定义

每年九月,校园里都会集中出现一类高频问题:新生不知道教学楼在哪、不知道快递到哪个门取、不知道下一节课的教室怎么走。这些问题的共同点是------答案本来就有,只是分散在不同系统里。

纸质地图没有检索能力;教务系统有课表,但不提供空间信息;后勤的施工通知发布在公众号里,与地图没有关联;宿舍分配表是 Excel,无法和路线规划结合。学生要在四个地方来回切换,才能拼出"我现在该往哪走"。

因此,这个项目的目标不是"再做一个地图",而是把与地点相关的信息收拢到一个以地点为索引的界面里,让一次查询替代四次翻找。

项目名称「新生通」,定位是校园导航与服务台。开发过程使用华为云码道(CodeArts 代码智能体)完成需求拆解、模块生成与代码评审,最终交付一个纯前端、零第三方依赖、可换校复用的 Web 应用。

动手前先梳理了校园里已经在用的几类工具:纸质地图、群文件里的平面图、通用地图软件的校园标注、学校公众号的迎新推文。它们的共同缺口不是"没有信息",而是信息没有结构化------位置是图片、通知是文本、课表是表格,三者之间没有可以建立索引的关联。

这也决定了技术路线:先建立结构化的地点数据,再让其他信息挂到地点上。

开源仓库:https://atomgit.com/2403_87838053/xinshengtong


二、需求分析:谁在什么场景下需要它

2.1 用户与场景

开发前先明确三类用户和六个高频场景,避免功能堆砌。

用户 典型场景 需要的信息 期望的操作
新生 报到当天找报到点、宿舍、缴费窗口 位置 + 流程顺序 + 步行时间 打开就能看到"下一步去哪"
在校生 课间换教室、找空教室自习、取快递 教室号、空余状态、营业时间 三步之内拿到答案
访客与家长 找校门、食堂、卫生间 位置 + 步行路线 不需要注册,直接查

六个高频场景按发生频率排序:找教室、取快递、找食堂、找自习座位、看施工封闭、办手续(补卡、报修、开证明)。

2.2 需求优先级

优先级 需求 判断依据
P0 地图定位与搜索 没有它,其余功能无从落地
P0 场所详情(按类型) 决定"点开之后有没有用"
P1 课表与教室联动 每天都会发生,频次最高
P1 公告在图上可见 直接影响"能不能走通"
P2 服务电话、失物招领、问题上报 低频但刚需
P2 迎新流程 集中在一周内,但那一周极其密集
P3 数据看板、换校配置 面向维护者与推广,不面向日常使用

2.3 明确的"不做"

边界和能力同样重要。以下内容在第一版就确定不做:

  • 不做社交与内容社区:与导航目标无关,且带来审核成本。
  • 不做实时定位与轨迹:室内定位精度不足,误差会直接损害可信度。
  • 不做账号体系:没有后端时,账号只会增加使用门槛;需要身份的功能(课表)用本地配置解决。
  • 不接入第三方地图 SDK:校园内部道路与建筑命名与通用地图不一致,且会引入外部请求与授权问题。

这四条"不做",让项目在两周内收敛到一个可交付的范围,而不是变成一个半成品平台。

2.4 使用节奏:一天、一周、一学期

功能的排布还取决于使用节奏。把时间尺度拉开看:

  • 一天之内:早上确认第一节课在哪,中午看食堂排队,傍晚取快递,晚上找自习座位。四次打开,每次目标明确,每次停留不超过一分钟。
  • 一周之内:以课表为主,周末关注公告(场馆是否开放、道路是否施工)。
  • 一学期之内:迎新、考试周、假期前后的时间节点集中出现。

结论是首页必须是地图而不是列表------一天里的多数操作都是"从当前位置去某处"。课表排在第二位,因为它回答"什么时候出发"。数据看板放到后面,使用频率低,但对管理者有价值。

这也是信息架构的依据:按使用频率排序,而不是按功能重要性排序。


三、产品设计:以地点为中心组织信息

3.1 信息架构

整体采用"一图 + 多页"的结构:地图是索引,其余页面是地图在不同维度上的展开。

  • 地图:空间维度,回答"在哪、怎么走"
  • 课表:时间维度,回答"什么时候、去哪个地点"
  • 公告:状态维度,回答"哪里现在不能走"
  • 数据:整体维度,回答"现在校园里什么情况"
  • 服务 / 失物 / 上报 / 迎新:事务维度,回答"到了之后办什么"

九个页面共享同一份数据源,因此从任意页面跳转到地图时,点位、状态、路线都是一致的------这是"一个应用"和"四个工具"的区别。

3.2 页面职责与关键取舍

地图页只保留三样常驻元素:搜索、分类筛选、地图本体。详情面板按需从右侧滑出,关闭后地图恢复完整视野。

这里有一个明确的设计原则:常驻控件不浮在内容之上。初期版本曾把搜索框与筛选条做成地图上的浮层,无论怎样调整位置,都会遮挡最上一排建筑的名称。最终改为"标题栏 → 工具栏 → 地图"的三段式布局,牺牲约 60 像素的高度,换取内容零遮挡。

课表页做了教务系统没有做的一步:把课程与地点连接起来。顶部卡片给出"下一节 + 建议出发时间",下方是一周网格,点击任意课程直接跳转到对应建筑。这样"看课表"和"找教室"从两次操作合并成一次。

数据页承担节奏变化的作用。前几页以列表和卡片为主,这一页全部是图表:课时分布、食堂排队、设施状态、单车余量。图表用 DOM 与 CSS 实现,不引入图表库,保证离线可用。

空状态与加载态同样是设计的一部分。

九个页面各自定义了空状态与下一步建议:没有公告时提示"一切正常",没有课程时引导去图书馆自习,地图没有点位时直接指出缺哪一份数据。加载阶段使用骨架屏占位,避免布局跳动。全部数据源失败时进入独立的错误页,给出原因与重试入口。

这些状态在演示数据完整时看不到,但决定了数据不全时的体验。为此项目提供了 ?empty=1 的入口,让空状态可以被直接检查,而不是等到上线之后才发现某一块是空白。

3.3 三个关键设计决策

决策一:详情面板按类型差异化。

多数校园地图对所有点位展示同一张卡片。但用户点开"食堂"和点开"卫生间"想获取的信息完全不同。因此详情面板按类型渲染:教室看课表与容量,食堂看排队与菜单,图书馆看空位与预约入口,设施看可用状态,单车点看余量。同一个交互位置,承载不同类型的信息结构。

决策二:地图标签分层显示。

28 个点位如果全部带名称,缩小视图时文字会互相覆盖。最终规则是:建筑名常驻,点位名仅在放大(缩放比例大于 1.35)或悬停、选中时出现。信息密度跟随缩放级别变化。

决策三:把"现在挤不挤"直接画在图上。

食堂点位的周围绘制一层颜色气泡,半径与排队热度成正比,颜色按绿、黄、红分档。用户不需要点开详情,就能判断该去哪个食堂。这是"把数据前移到索引层"的一次尝试------索引本身携带决策信息。

图 1:校园地图主界面。左侧功能栏、中间矢量地图、右侧场所详情,常驻控件不压内容。

3.4 交互细节:手势与键盘并存

地图需要同时支持三种输入方式:

  • 鼠标:拖动平移,滚轮以指针为锚点缩放,双击放大
  • 触屏:单指拖动,双指缩放;底层统一用指针事件处理,不为设备写两套逻辑
  • 键盘:方向键平移,加减号缩放,Tab 在点位之间移动,回车选中

所有变换通过 requestAnimationFrame 合并,避免高频事件触发重复计算。点击与拖动的区分采用位移阈值(6 像素):小于阈值视为点击,超过则视为拖动,避免"想拖动却触发了选中"。

这套规则的价值在于一致性:无论在手机还是桌面,平移、缩放、选中的判定逻辑都来自同一份代码。


四、技术选型:三个约束下的取舍

4.1 约束条件

项目启动时确定了三个硬约束:

  1. 可被他人接手:克隆后一条命令即可运行,不依赖环境配置。
  2. 可离线演示:校园内部分区域网络不稳定,页面不能在断网时失效。
  3. 代码即产物:评审看到的源码就是运行的代码,不存在"构建后才正确"的黑箱。

4.2 方案对比

维度 方案 A:React + Vite 方案 B:原生 ES Module(最终选择)
启动成本 需要安装依赖、执行构建 一条命令启动静态服务
离线可用 依赖构建产物与依赖体积 源码直接运行,无外部请求
可维护性 生态成熟,组件化清晰 需要自行约束分层与工具函数
可评审性 需理解构建配置 打开文件即所见
换校成本 与业务代码耦合较深 数据与视图完全分离

同样地,后端方案也在"自建服务"与"本地数据 + 本地存储"之间做了比较。最终结论是:在没有真实教务、后勤接口的前提下,自建后端只会增加演示负担,因此第一版采用 9 份 JSON 作为数据源、localStorage 作为写入层,并把接口对接待为后续演进。

4.3 选型结论

选择方案 B。代价是明确的:没有 JSX、没有类型检查、组件复用需要自己写工具函数。对应的补偿措施是:

  • 纯逻辑全部下沉到 src/core/,可用 node:test 直接验证;
  • 视图层只做 DOM 组装,通过统一工具函数约束写法;
  • 用测试守卫替代部分类型检查的职责。

4.4 性能预算

首屏设定三条预算,作为实现阶段的约束:

  1. 资源全部同源:不加载第三方脚本、字体与图片,首屏只有 HTML、4 个 CSS 与若干 ES Module
  2. 地图只渲染一次:之后所有平移缩放仅修改 transform,不重建节点
  3. 动效节点有上限:切页动画的临时节点控制在 260 个以内,动画结束立即回收

这些预算直接影响了实现方式。例如地图没有采用"每次缩放重算点位坐标"的做法,而是整体变换;动效没有采用粒子方案,而是有限网格。


五、架构设计

5.1 分层与依赖方向

复制代码
index.html                 外壳
└─ src/main.js             装配:加载数据 → 建立状态 → 启动路由 → 挂载工牌
   ├─ src/app/             装配层:repo(数据)、store(状态)、router(路由)、config(换校)
   ├─ src/core/            纯函数层:dom、validate、storage、fuzzy、schedule、pendulum
   ├─ src/map/viewport.js  地图数学:平移缩放状态机
   ├─ src/ui/              可复用视图件:kit、icons、tilt、badge、shard
   └─ src/features/        九个页面模块

依赖方向单向:features → ui → core,app 只被 main 调用。这条约束不只写在文档里,还写成了测试:解析所有相对导入,判断每一层允许依赖哪些层,越界即失败;同时检查循环依赖与无人引用的模块。

5.2 数据模型

点位数据分为两层:

  • 公共字段 :id、name、aliases、kind、x、y、tags、summary、hours、steps
  • 类型字段 :收进 detail,由 detail.type 决定解析方式(教室、食堂、图书馆、设施、单车、普通场所)

这样设计的好处是校验可以按类型分派,未知类型只丢弃 detail 而不影响点位本身;新增一种点位类型时,只需增加一个解析分支和一段渲染逻辑,数据结构保持稳定。

5.3 数据流与校验

复制代码
data/*.json → repo.js(校验 + 归一化)→ main.js(装配为上下文)→ features(渲染)

校验层负责四件事:类型检查(坐标必须是数字)、枚举收敛(未知 kind 归为 other)、缺省补全(缺少坐标时回退到所属建筑中心)、非法丢弃(不合法记录不进入渲染层)。加载失败时按数据源降级,只有全部失败才进入错误页。

5.4 决策记录

关键决策以 ADR 形式记录,每条都写明代价:

决策 收益 代价
零依赖、无构建 可离线、可评审、无供应链风险 无 JSX 与类型检查,需自建工具与测试
数据驱动 + 校验层 换校不改代码,脏数据不白屏 校验代码占比上升
地图只改一个 transform 平移缩放不触发重排 文字随缩放变细,需标签分层补偿
换校数据包存本地 无需部署即可换校 数据仅在当前浏览器

5.5 状态管理:三层就够

没有引入状态管理库,按生命周期把状态分成三层:

  • 会话级:数据(一次加载、只读)、当前路由、当前时间
  • 设备级:报到清单、失物发布、问题上报(localStorage,带版本与结构校验)
  • 组件级:地图视口、详情面板选中项、搜索关键词(组件内部闭包)

判据是"是否需要跨页面共享,且是否需要跨会话保留"。九个页面的规模下,引入全局状态库会带来比收益更多的样板代码,因此没有再增加抽象。

代价是页面之间不能直接读写彼此的状态,必须通过路由参数传递------但这恰好强制了"页面之间只通过 URL 通信",深链能力因此自然成立。


六、核心实现

6.1 地图:单变换与锚点缩放

地图性能的关键在于避免重排。实现方式是把整张图放进固定尺寸容器,平移缩放只修改一个属性:

js 复制代码
stage.style.transform = `translate3d(${x}px, ${y}px, 0) scale(${scale})`;

坐标换算由纯函数完成,核心是保证缩放时指针下的内容点保持不动:

js 复制代码
zoomAt(factor, screenX, screenY) {
  const next = clamp(scale * factor, minScale, maxScale);
  const ratio = next / scale;
  offsetX = screenX - (screenX - offsetX) * ratio;
  offsetY = screenY - (screenY - offsetY) * ratio;
  scale = next;
  clampOffsets();
}

这条不变量被写成测试:

js 复制代码
const before = vp.toContent(300, 200);
vp.zoomAt(1.6, 300, 200);
const after = vp.toContent(300, 200);
assert.ok(Math.abs(before.x - after.x) < 0.001);

同时对所有方法做边界收敛:内容小于视口时保持居中,大于视口时不允许拖出可视区。

6.2 搜索:可解释的三段式打分

搜索需要覆盖三类输入:全名、别名、口语简称。打分规则设计为可解释的三段:

  • 匹配强度:完全相同 1.0、前缀 0.9、包含 0.62 至 0.82、子序列 0.34 至 0.6
  • 字段权重:名称 1.0、别名 0.96、标签 0.62
  • 次序规则:同分时名称较短者优先

由此「二教」命中别名、「菜鸟」命中「菜鸟驿站」的包含匹配、「大活」命中「大学生活动中心」。规则可以被解释,也就可以被测试:口语别名、简称、标签命中、无关查询、数量上限、名称优先,共六条。

6.3 课表时间:跨周判定

"下一节"的计算需要处理跨天与跨周:

js 复制代码
const dayOffset = (course.weekday - weekday + 7) % 7;
let wait = dayOffset * 1440 + start - minutes;
if (wait <= 0) wait += 7 * 1440;

当天已经开始但尚未结束的课程算"正在上",不算"下一节";当天全部结束后顺延到下周。这段逻辑有九条测试覆盖,包括跨周与跨天的边界。

图 2:课表页。"下一节"提醒卡给出出发时间,下方为一周网格,今天列高亮。

6.4 换校:三段校验

换校数据来自外部文件,必须按不可信输入处理,链路分三段:

  1. 文件层 :不超过 2MB,JSON.parse 成功,顶层为对象
  2. 归一化层:只处理出现的键,逐条校验字段
  3. 语义层 :campus.pois 非空,school 与 campus 至少存在一个

通过后写入本地存储;读取时再次归一化,因为存储内容同样可能被手工修改。失败时给出具体原因,便于定位。

6.5 动效与工牌

切页动效采用"碎片波 + 卡片浮入":内容区铺一层同色系半透明方块,从点击位置向外淡出,新视图同时上浮落位。所有动画只修改 transform 与 opacity,节点数量按视口自适应(150 至 260),动画结束后节点全部移除;系统开启"减弱动效"时整体跳过。

工牌使用阻尼摆物理,方程与参数为 θ'' = -k·sinθ - c·θ'(k=16,c=2.6),支持拖拽与按角速度续摆。物理部分实现为纯函数模块,可在 Node 环境中测试,不依赖浏览器。

图 3:头像工牌翻到背面,四个功能磁贴,悬停时立体浮起。

6.6 路线助手:一句话规划多站点

新生常见的一句话是"下午去二教上课,顺路取快递"。路线助手把这句话拆成多站点路线:

  1. 分段:按标点以及"顺路、然后、再去"等词切分
  2. 实体识别:优先匹配"别名被包含"的关系,其次整体模糊匹配
  3. 去重与排序:按出现顺序保留首次出现的地点
  4. 估算:相邻点位直线距离除以步行速度(75 米/分钟)

输出是"校门 → 第二教学楼 → 快递驿站",以及每段的距离与时间。

它不是模型调用,而是三段规则,因此可离线、可测试------七条用例覆盖口语、别名、去重、无匹配、显式起点与时间换算。接口按"实体识别可替换"设计,后续接入模型能力时只需替换这一处。

6.7 数据看板:图表怎么画

看板上有四个指标卡、一组柱状图、两组横条、一组状态统计,全部由 DOM 与 CSS 构成:

  • 柱状图:网格容器 + 高度百分比,今天那一根替换为强调色
  • 横条:宽度百分比 + 三档配色(低、中、高)
  • 状态统计:圆点着色 + 计数

没有引入图表库,原因有三:图表数量少、交互需求简单(不需要缩放与提示框)、离线可用是硬约束。用 CSS 实现之后,这一页没有任何额外请求。

图 4:数据看板。课时分布、食堂排队、设施状态、单车余量,全部由 DOM 与 CSS 绘制。


七、工程质量保障

7.1 测试体系

项目共 132 项测试,按类型分布:

类型 数量 代表用例
纯函数正确性 40+ 搜索打分、课表下一节、视口缩放、阻尼摆
数据与状态 25+ JSON 归一化、本地存储篡改、换校数据包
边界与异常 20+ 空数组、超长文本、非法日期、存储不可用
架构与质量守卫 15 依赖方向、循环依赖、死模块、行长度
安全与对比度 15 危险 API、外部域名、CSP、路径穿越、WCAG
渲染冒烟 17 九个页面渲染、空数据、键盘可达

编写顺序是:先纯函数、再数据、最后渲染。纯函数最稳定,是地基;渲染测试最易受结构变化影响,放在最后维护。

7.2 四类守卫

除功能测试外,另有四类自动检查:

守卫 内容
架构 依赖方向、循环依赖、死模块、单文件不超过 600 行
质量 无 console.log 与 TODO、单行不超过 140 字符、无 var、文件有头注释
安全 危险 DOM API、外部域名、CSP、响应头、路径穿越
对比度 正文不低于 4.5、标题不低于 7、按钮白字不低于 4.5

其中对比度检查使用 WCAG 公式计算实际比值。开发过程中它发现次要文字的对比度为 4.04,低于 4.5,随后将颜色由 #6e7789 调整为 #5f6779。这类问题肉眼难以判断,但可以被计算。

7.3 可访问性

  • 地图点位支持 Tab 聚焦、回车与空格选中,焦点环可见
  • 侧边栏使用 aria-current 标记当前页,而非仅依赖样式类
  • 折叠面板使用原生 details,保留浏览器默认的键盘行为
  • 提供跳转链接,键盘用户可直接进入主内容
  • 尊重 prefers-reduced-motion,关闭非必要动画

文字层面也做了约束:正文 14 像素、行高 1.6,长段落在小屏上不显拥挤;标题与正文保持两级以上字号差,层级清晰。这些数值统一定义在样式变量中,改动一处即可全站生效。

7.4 安全

  • 全站不使用 innerHTML、eval、new Function,由静态扫描测试保证
  • 严格 CSP:脚本仅允许同源、禁止内联事件、禁止 object 与 base 标签
  • 链接白名单:仅放行 http(s)、tel、mailto
  • 本地存储按不可信输入处理,带版本号与结构校验
  • 静态服务器仅监听本机,路径解析后校验是否位于项目根目录

7.5 交付前的检查清单

每个功能合入前,走一遍固定清单:

  • 测试全绿,新增功能有对应用例
  • 无调试残留(日志、断点、被注释的旧代码)
  • 空数据下九个页面均可渲染
  • 窄屏 375 像素与桌面 1440 像素各走一遍主流程
  • 键盘可完成主要操作
  • 文档与实现一致(功能列表、测试数量、目录结构)

清单的作用是把"记得检查"变成"必然检查"。项目里几次布局问题都是在窄屏这一步暴露的。

7.6 错误处理:分级降级

按影响范围把错误分成三级:

  • 单条数据错误:丢弃该条记录,记录警告,页面继续渲染
  • 单个数据源失败:该模块降级为空状态,其余模块正常
  • 全部数据源失败:进入错误页,提供原因与重试按钮

本地存储的写入失败(隐私模式、配额不足)不抛异常,只返回失败标记,界面以提示告知,不阻断主流程。

分级的意义是:错误的影响范围应与处理范围一致。一条坏数据不应该让整页不可用,一个模块失败也不应该让整个应用退出。


八、软件价值

8.1 对学生:把四次查询压缩成一次

在没有这个应用之前,找到一间教室通常需要:打开教务系统查课表、打开地图软件找楼、在群里问教室位置、到楼下看楼层指引。四步之后才到达目的地。

应用把这条链路压缩为一次操作:在地图上直接看到课程对应的建筑,点击即可获取步行路线。按每周 12 节课、每节课前节省约 2 分钟估算,一个学生一学期可节省数十次查找动作。

场景 原来的做法 现在的做法 大致节省
找教室 查课表、找楼、问人、看楼层指引 点课程直接定位 约 2 分钟
取快递 翻聊天记录或问室友 搜索"菜鸟"直达 约 1 分钟
看施工 翻公众号历史消息 公告页或地图高亮 约 3 分钟
找空教室 逐层走 详情页看空闲状态 约 5 分钟

这些数字是估算,但方向明确:把"找信息"的时间成本从分钟级压到秒级。

8.2 对学校:信息发布从"多处维护"变为"一处生效"

公告、设施状态、服务电话集中在同一份数据源中。后勤更新一条施工公告,地图上对应点位立即出现高亮提示,不需要同时修改公众号、群通知和纸质告示。

迎新期间尤其明显:报到流程、宿舍分布、缴费入口集中在一个页面,减少重复咨询。

8.3 对开发者:可长期维护的结构

  • 零第三方依赖,不存在依赖升级与供应链风险
  • 数据与视图分离,更换学校时无需修改业务代码
  • 132 项测试与四类守卫,修改后有回归保障
  • 分层明确,新增一个页面只需注册路由与模块

8.4 可量化指标

指标 数值
校园点位 28 个,覆盖 12 类设施
建筑与道路 18 栋、7 条主路
数据文件 9 份 JSON,全部可替换
自动化测试 132 项,全通过
第三方运行时依赖 0
构建步骤 0
换校所需代码改动 0 行
CSS 总体积 65.7 KB(5 个文件,未压缩)
JS 总体积 147.3 KB(未压缩,含注释)
测试耗时 约 0.6 秒(132 项,单进程)

8.5 与现有方案的关系

它不是替代,而是补齐:

  • 教务系统负责权威数据(课程、成绩),本应用负责把这些数据落到空间上
  • 通用地图负责城市尺度导航,本应用负责校园尺度的建筑与设施命名
  • 学校公众号负责发布通知,本应用负责让通知出现在它影响的位置上

因此它的定位是"空间索引层":把已有信息按地点重新组织,而不是再造一套数据。这也解释了为什么它必须轻------轻到可以嵌入任何现有流程。


九、开发过程:技术决策路径

这一节记录三个真实的技术决策过程,重点在"如何定位"与"如何验证"。

9.1 从需求到模块拆解

第一步不是写代码,而是拆模块。用码道把功能拆成数据层、地图层、视图层与应用层,并标注每个模块的输入、输出与依赖关系。

这一步的产出直接决定了目录结构。尤其是"纯逻辑与视图分离"这一条,使后续所有纯函数都能脱离浏览器测试,也为换校配置的复用打下基础。

9.2 案例一:搜索从"能搜到"到"搜得准"

初版实现采用"完全相同 + 包含"两段匹配,覆盖了正式名称,但口语简称无法命中。定位方式是列出失败用例:二教、三号食堂、菜鸟驿站、大活。

改进分三步:增加别名数据、引入子序列匹配、明确字段权重与同分次序。每一步都用失败用例回归。最终规则可以被解释,因此也能被六条测试固定下来。

这个案例说明:搜索的问题往往不在算法,而在"用户会怎么称呼一个地方"。数据层的别名设计,比匹配算法更影响结果。

9.3 案例二:交互冲突的定位

工牌翻面后,背面的功能磁贴点击无响应。排查顺序是:先确认层级与可见性,再确认命中测试,最后在指针事件中打点,发现 pointerup 未到达磁贴。

原因是卡片在 pointerdown 时调用了 setPointerCapture。指针捕获会把后续事件重定向到捕获元素,点击事件因此不再冒泡到目标元素。这是指针事件的规范行为,只是在"卡片内包含可点击元素"这一结构下不适用。

解决方案是移除指针捕获,改为在 window 上跟踪指针,并在松手时区分两种意图:位移小于阈值的视为点击,交给磁贴处理;超过阈值的视为拖动,执行甩动。判定逻辑集中在一处,行为可预测。

9.4 案例三:视觉层的合成问题

服务页出现一块半透明残影。排查采用二分法:先排除单个元素,再逐步关闭视觉特效,最终定位到大面积 backdrop-filter 与嵌套 filter 的组合。

处理方式是整体转向扁平化视觉:移除毛玻璃,头像光晕改用遮罩实现。这次调整同时简化了视觉层级,页面更干净,渲染也更稳定。

9.5 与码道协作的三条方法

方法一:先拆解,再生成。 第一轮只做模块拆分,不产出代码。模块边界确定后,每次只生成一个文件,减少上下文串扰。

方法二:约束前置。 把禁止项写在需求最前面(禁用 DOM 注入 API、必须提供空状态、必须键盘可达),命中率明显高于写在末尾。

方法三:让评审独立于生成。 同一段代码,让它以"安全评审"的视角逐条检查注入面、边界条件、异常路径与无障碍问题,比让它重新生成整段代码更容易发现问题。

方法四:把验收标准写成断言。 需求里写"缩放时指针下的内容不能漂移",落地就是一条断言;写"键盘可达",落地就是检查 tabindex 与 aria-label。把主观描述翻译成可执行检查,是这套协作方式里最有效的一步。

完整的 34 条提示词按文件组织,附产出与验收标准,已整理进仓库文档,便于复现。

〔待补:码道对话窗口截图 ------ 见下方"配图待补"说明〕

9.6 一个完整的开发循环

以课表联动为例,记录一轮完整过程:

  1. 需求:点课程能到教室,并提示出发时间
  2. 设计:课程需要绑定点位标识;出发时间 = 上课时间 − 步行时间 − 缓冲
  3. 实现:先写纯函数(当前课程、下一节、等待描述),再写视图
  4. 验证:九条时间用例,覆盖当天、跨天、跨周
  5. 回归:新页面加入渲染冒烟测试
  6. 文档:在架构文档补充数据流,在提示词清单补充对应条目

这个循环在项目里重复了九次(九个页面),每次都留下测试与文档,因此最后的清理阶段没有出现"没人知道这块代码做什么"的模块。


十、局限与后续规划

当前局限

  1. 数据为演示数据,楼宇、点位、分机号均为示例,正式使用需按真实校区重绘坐标
  2. 无后端,失物招领与问题上报仅保存在本机;接入真实系统需补充鉴权、限流与内容审核
  3. 照片仅本地预览,不上传、不持久化
  4. 换校数据保存在当前浏览器,更换设备需重新导入
  5. 尚未建立自动化视觉回归,样式修改依赖人工确认

后续规划

  • 接入教务与后勤接口,将 JSON 数据源替换为实时接口,视图层不变
  • 增加室内楼层图与教室级寻路
  • 补充无障碍路线,标注台阶与施工路段
  • 建立截图基线,将视觉回归纳入自动检查

维护与交接

数据与代码分离之后,接手者只需要理解两件事:数据包的 9 个键分别管什么,以及四个目录各自的职责。

仓库内提供完整文档:架构说明(分层、数据流、取舍表)、四份决策记录(每个决策都写了代价)、安全说明(威胁模型与已知限制)、提示词清单(34 条,按文件组织)。新成员可以按顺序阅读,而不必从代码反推设计意图。

这也是"零依赖、无构建"的延伸价值:接手成本不随依赖数量增长。


十一、常见问题(FAQ)

Q1:新生通是什么?

新生通是一套面向高校的校园导航 Web 应用,用一张矢量校园图承载地点信息,并把课表、公告、服务电话、失物招领挂在对应地点上。技术上为原生 ES Module + CSS,零第三方依赖、零构建步骤。

Q2:它具体解决什么问题?

解决"答案本来就有、但分散在四个系统里"的问题。限定条件是校园场景:学生需要频繁在建筑之间移动,而教务系统不提供空间信息、通用地图不提供校园内部命名、公众号通知与位置无关。

Q3:为什么选择零依赖、无构建?

三个约束决定:克隆后一条命令即可运行(可被他人接手)、断网可用(校园部分区域网络不稳)、源码即产物(评审看到的代码就是运行的代码)。代价是没有 JSX 与类型检查,用纯函数下沉 + 测试守卫来补偿。

Q4:课表联动是怎么实现的?

课程数据里绑定点位标识与步行分钟数。出发时间 = 上课时间 − 步行时间 − 2 分钟缓冲;当天已开始未结束的课判定为"正在上",当天结束后顺延到下周。这段逻辑由 9 条测试覆盖,包括跨天与跨周。

Q5:换一所学校需要改多少代码?

0 行。数据集中在 9 个 JSON 中,界面上可通过"导出配置 → 修改 → 导入"完成切换;导入链路分文件层、归一化层、语义层三段校验,非法数据不会进入渲染层。

Q6:132 项测试具体测什么?

分六类:纯函数正确性 40 余项(搜索打分、课表时间、视口缩放、阻尼摆)、数据与状态 25 项、边界与异常 20 项、架构与质量守卫 15 项、安全与对比度 15 项、渲染冒烟 17 项(9 个页面在 DOM 垫片中真实渲染)。

Q7:华为云码道在开发中做了什么?

承担三件事:第一轮把需求拆成模块并标注依赖关系;按文件生成实现(每次只生成一个文件,减少上下文串扰);以"安全评审"视角逐条检查注入面、边界条件与无障碍问题。产品决策与验收仍由人完成。

Q8:它和通用地图、教务系统是什么关系?

不是替代关系。教务系统负责权威数据(课程、成绩),通用地图负责城市尺度导航,本应用负责校园尺度的建筑与设施命名,定位是"空间索引层"------把已有信息按地点重新组织。

Q9:如何在本机运行?

安装 Node 20 以上,在项目根目录执行 node server/start.mjs,浏览器访问 http://127.0.0.1:5173。不要直接双击 index.html,浏览器会因安全策略拒绝读取本地 JSON。

Q10:前端如何保证安全与可访问性?

安全:全站禁用 innerHTML 与 eval(由静态扫描测试强制)、严格 CSP、链接白名单、本地存储按不可信输入处理、静态服务器防目录穿越。可访问性:点位可 Tab 聚焦、对比度按 WCAG 校验(正文 ≥4.5)、尊重减弱动效偏好。

Q11:数据从哪里来,正式落地怎么办?

当前为演示数据,楼宇、点位、分机号均为示例。落地时替换 9 份 JSON,或把数据层换成学校接口;视图层不随数据源变化,接口对接只需改 repo.js。

Q12:有哪些已知局限?

五条:数据为演示数据需按真实校区重绘坐标;无后端,失物与上报仅存本机;照片仅本地预览不上传;换校数据仅在当前浏览器;尚未建立自动化视觉回归。

十二、结语

「新生通」没有使用框架,也没有追求功能数量。它做的是把分散在四个系统中的地点相关信息,收拢到一张以地点为索引的图上,并让其他学校可以直接替换数据使用。

在工程上,它验证了一件事:约束越明确,产出越稳定。零依赖、无构建、数据驱动、测试守卫,这些约束在开始时看起来是限制,实际上让项目在两周内保持了可控的复杂度。

如果它能让一位新生少问三次"二教在哪",这个项目的价值就成立了。

相关推荐
贾伟康1 小时前
HarmonyOS API 26 Navigation 自适应模式:mode、分栏切换与窗口断点治理
harmonyos·arkts·arkui·navigation·自适应布局
贾伟康2 小时前
【转盘决定吧|07】HarmonyOS ArkTS 6.0.2.130 趣味题库:场景分类、随机选择与详情页解耦
harmonyos·arkts·路由·数据建模·arkui
贾伟康2 小时前
【电路实验模拟|03】HarmonyOS ArkTS 交通灯实战:用定时状态切换红黄绿灯
harmonyos·arkts·canvas·arkui·交通灯
贾伟康19 小时前
HarmonyOS API 26 嵌套 Navigation:getParent、父子栈边界与返回分发
harmonyos·arkts·navigation·嵌套路由·navpathstack
行者-全栈开发20 小时前
【鸿蒙心迹】多线程并发实战——TaskPool 与 Worker 选型与踩坑(HarmonyOS 7.x)
华为·harmonyos·多线程并发·worker·taskpool·线程通信·harmonyos 并发
OH_TPC1 天前
@ohos/aki 源码解读(一):一行绑定背后,宏、模板与索引分发
harmonyos
贾伟康1 天前
【解压时刻|13】HarmonyOS ArkTS 启动链路审计:从 EntryAbility、AppStore 到 Navigation 首屏
harmonyos·arkts·appstorage·应用启动·entryability
贾伟康1 天前
【古诗词闯关|12】HarmonyOS ArkTS 单页组件拆分实战:降低题库、闯关与结果弹层耦合
harmonyos·arkts·状态管理·arkui·组件化
威哥爱编程1 天前
模拟面试智能体开发实战 07:答完一题就点评一题,结构化输出给分建议,让每道题都变成复盘
python·agent·harmonyos