2026秋软件工程第二次结对作业:校园寻物站程序实现
2026 秋软件工程第二次结对作业:校园寻物站程序实现
本文记录校园寻物站核心模块的程序实现、测试过程和结对协作过程。
一、作业信息与项目地址
- 结对同学:唐捷(102401416)、许书凯(102401414)
- 唐捷博客:唐捷
- 许书凯博客:https://www.cnblogs.com/l12546333
- 本次作业博客:本文链接
- GitHub 仓库:https://github.com/trony33/102401416-102401414
- 上一次原型链接:墨刀校园寻物站原型
二、结对分工
唐捷(102401416)
- 复核第一次作业的需求边界和核心流程,确定本次只实现浏览、搜索、发布、详情、联系方式和状态维护。
- 负责数据结构、关键词搜索、筛选、状态转换、localStorage 持久化和单元测试设计。
- 负责 README、PSP、测试说明和最终提交材料整理。
许书凯(102401414)
- 根据第一次作业的移动端原型复核页面结构和交互入口。
- 负责首页、发布页、详情页、我的发布页面的视觉实现和响应式适配。
- 参与 Chrome 走查,重点检查表单缺失、搜索无结果、发布成功和状态变化后的页面反馈。
共同完成
共同讨论数据字段、状态命名和异常流程;共同检查代码和使用说明;共同确认不加入登录、实名认证、即时聊天、地图定位和复杂后台,避免超出作业范围。
三、需求分析与解题思路
校园失物信息通常分散在班级群、宿舍群和朋友圈,旧消息会被覆盖,物品归还后原消息又不会自动更新。第一次作业将问题收敛为一个轻量的“校园寻物站”:用户可以把寻物和招领信息集中发布,浏览者可以按关键词寻找线索,发布者可以在结果完成后更新状态。
本次实现遵循三个基本流程:
浏览或搜索 -> 查看详情 -> 查看联系方式
填写发布表单 -> 校验必填项 -> 发布成功 -> 进入详情或我的发布
我的发布 -> 标记已找回/已归还 -> 首页、详情、我的发布同步显示完成状态
本项目采用原生 HTML、CSS 和 JavaScript,不使用服务器和第三方运行时依赖。这样做是因为作业要求其他同学下载文件后可以直接用 Chrome 打开 HTML。数据模型是一条记录对象:
id, title, kind, category, place, date, description,
contact, owner, status
其中 kind 为 lost 或 found,status 为 open 或 done。界面显示时根据两者组合为“寻找中 / 已找回 / 待认领 / 已归还”。所有记录保存到浏览器 localStorage,因此刷新页面后仍能看到刚刚发布的信息。
四、流程图与数据流图
4.1 核心流程图
4.2 数据流图
五、代码实现思路
5.1 页面状态
项目使用 URL hash 在单页内切换页面:#home 是首页,#publish 是发布页,#mine 是我的发布,#detail/记录 id 是详情页。因此直接打开 index.html 也能完成页面跳转,不依赖服务器路由。
5.2 搜索与筛选
搜索函数会将关键词去掉首尾空格并转为小写,然后同时匹配物品名称、类别、地点和物品特征;类型筛选再限制 lost 或 found。搜索没有结果时,页面显示明确的空状态和“去发布信息”出口。
5.3 状态维护
只有 owner === '林小满' 的记录出现在“我的发布”中。寻物记录完成后显示“已找回”,招领记录完成后显示“已归还”。状态写回 localStorage,重新进入首页、详情页和我的发布时均使用同一份数据。
5.4 重要代码片段
function filterRecords(records, query = '', kind = 'all') {
const needle = normalize(query);
return records.filter(record =>
(kind === 'all' || record.kind === kind) &&
(!needle || [record.title, record.category, record.place, record.description]
.some(value => normalize(value).includes(needle)))
);
}
这段代码把搜索范围限定在用户真正需要查找的字段中,并通过同一个 filterRecords 同时支持首页关键词搜索和寻物/招领筛选。
function validateRecord(input) {
const required = [
['title', '请填写物品名称'],
['place', '请填写地点'],
['date', '请选择日期'],
['description', '请描述物品特征'],
['contact', '请填写联系方式']
];
return Object.fromEntries(required.map(([key, message]) => [
key, input[key]?.trim() ? '' : message
]));
}
表单提交前统一调用校验函数。校验失败不会清空表单,用户可以直接补充缺失字段;校验成功后才会创建记录并写入 localStorage。
六、附加特点设计
6.1 联系方式二次显示与一键复制
联系方式默认不直接铺开,用户点击“查看联系方式”后才显示,并附带核对物品特征、选择公共区域交接的风险提示。显示后可以点击“复制”,减少手动输入错误。
6.2 关键词多字段搜索
除了物品名称,地点和物品特征也参与匹配。例如搜索“图书馆”可以找到发生在图书馆的记录,搜索“划痕”可以找到描述中包含这个特征的雨伞。
6.3 状态闭环
状态更新不是只改变一个页面:状态写入 localStorage 后,首页卡片、详情页状态徽章和“我的发布”按钮都会同步反映“已找回”或“已归还”,减少重复联系。
6.4 响应式与可访问性
桌面端使用卡片网格,移动端切换为单列并保留底部导航;表单控件均有 label,错误信息使用文字呈现,键盘焦点有明显轮廓,支持减少动画模式。
七、目录说明与使用说明
campus-lost-found/
├── index.html
├── styles.css
├── app.js
├── logic.mjs
├── tests/app.test.mjs
├── DESIGN.md
├── package.json
├── README.md
└── BLOG_POST.md
使用 Chrome 打开 index.html 即可运行。首页可以筛选和搜索,点击卡片进入详情;发布页选择寻物或招领,填写带星号字段并提交;“我的发布”中可维护本人信息状态。测试需要 Node.js 18 或更高版本,在项目目录执行 npm test。
八、单元测试
8.1 工具选择与学习方式
项目采用 Node.js 内置 node:test 和 node:assert/strict,不依赖第三方测试框架。选择内置工具是为了让测试人员 clone 后无需额外安装 Mocha 等依赖即可执行。学习过程参考 Node.js 测试文档和 Mocha 测试组织方式,先把数据处理逻辑从 DOM 渲染中分离,再针对纯函数编写测试。
8.2 测试用例
目前共有 14 个测试用例:
- 关键词去空格并转小写。
- 缺省关键词转换为空字符串。
- 空表单返回全部必填错误。
- 完整表单不返回错误。
- 按物品名称搜索。
- 按地点搜索。
- 按物品特征搜索。
- 按寻物/招领类型筛选。
- 空关键词返回全部记录。
- 按日期倒序排列且不修改原数组。
- 寻物和招领记录都能转换为完成状态。
- 完成状态正确显示“已找回”和“已归还”。
- 新记录自动补充当前用户和 open 状态。
- 序列化可以恢复记录,损坏数据不会让程序崩溃。
执行结果:14 passed, 0 failed。
测试数据同时包含寻物、招领、空查询、完整输入、缺失输入、标题/地点/特征命中和损坏存储数据,覆盖了白盒分支以及助教可能进行的异常输入检查。未来如果接入真实后端,还需要增加接口失败、并发更新和权限校验测试。
九、PSP 表格
以下为本次开发阶段两人合计记录,实际提交前应根据双方各自的真实投入再次核对:
| PSP2.1 阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|
| Planning / Estimate | 30 | 30 |
| Analysis(需求与原型复核) | 45 | 45 |
| Design Spec / Design Review | 50 | 60 |
| Coding Standard | 15 | 15 |
| Design(数据模型与页面状态) | 45 | 45 |
| Coding(页面与交互) | 180 | 180 |
| Code Review | 35 | 35 |
| Test(单元测试与 Chrome 走查) | 80 | 90 |
| Reporting / Test Report | 60 | 60 |
| Size Measurement | 10 | 10 |
| Postmortem & Process Improvement | 30 | 30 |
| 合计 | 580 | 600 |
实际耗时比预估多 20 分钟,主要花在本地直接打开 HTML 的兼容检查、联系方式显示状态修正和移动端无横向溢出走查上。下一次会在编码前先确定“纯函数测试模块”和“file:// 运行约束”,减少后期返工。
十、GitHub 提交记录
仓库地址:https://github.com/trony33/102401416-102401414
本项目按功能拆分提交:基础页面与设计契约、核心数据与页面交互、单元测试与 README、博客材料。每个功能完成后先运行测试,再提交代码,便于助教根据 commit 记录抽查实现细节。许书凯应在自己的 fork 中提交进展并向主仓库发起 Pull Request,以符合题目要求的协作流程。
十一、遇到的问题与解决方法
问题 1:本地 HTML 直接打开时模块加载存在兼容风险
最初把页面入口写成 ES module,测试和浏览器策略可能对 file:// 模式有差异。解决方法是让 index.html 使用普通脚本 app.js,只把纯函数放入 logic.mjs 供 Node 测试导入,保证网页双击运行和测试模块解耦。
问题 2:同一条记录在多个页面状态不一致
如果首页、详情和我的发布分别维护数据,状态更新后容易出现一处更新、一处不变。解决方法是统一使用记录数组和 localStorage,所有页面重新渲染时从同一份记录读取状态。
问题 3:只测试成功路径会漏掉实际使用中的错误
解决方法是在测试和 Chrome 走查中加入空表单、无搜索结果、联系方式未显示、损坏存储数据、移动端窄屏等情况,并为每种情况保留清晰的提示或出口。
十二、队友评价
许书凯值得学习的地方是能够把原型中的页面层级和用户路径转化为具体的页面入口,并主动检查发布成功、返回和空状态等交互。需要改进的地方是开发初期可以更早把数据字段和状态转换规则写成共享文档,减少页面完成后再对齐字段的时间。
唐捷值得学习的地方是能够控制需求边界,优先完成作业明确要求的主流程,并补充测试和异常场景。需要改进的地方是编码前可以更早安排浏览器走查,避免在后期才发现本地打开方式和交互状态方面的问题。
十三、总结
本次实现没有继续扩展地图、聊天和实名认证,而是把发布、浏览、搜索、详情、联系和状态维护做成一个闭环。通过这次结对编程,我们进一步认识到,需求分析中列出的功能只有在代码中形成完整流程,并经过正常、异常和移动端走查,才算真正完成。项目后续若继续发展,可以在保留当前核心流程的基础上接入后端和真实用户身份,但不应在没有验证核心需求之前盲目增加模块。

浙公网安备 33010602011771号