# 2026秋软件工程结对作业(第二次之程序实现)——失物有迹
| 项目 | 内容 |
|---|---|
| 课程 | H202601 软件工程与软件工程实践 |
| 作业要求 | 2026秋软件工程结对作业(第二次之程序实现) |
| 学生 | 102401216 吴欣键 |
| 组队情况 | 独立完成 |
| 原型链接 | https://www.cnblogs.com/PIG1/p/23001643 |
| GitHub 仓库 | baomihua1234/102401216 |
一、项目简介
“失物有迹”是一个可离线运行的校园失物招领 Web 应用。我基于第一次作业原型,实现了“发布信息 → 浏览或搜索 → 查看详情并联系 → 发布者更新状态”的完整闭环。
项目只使用原生 HTML、CSS、JavaScript 和浏览器 LocalStorage;下载全部文件后,用 Chrome 直接打开 index.html 即可运行。
二、个人工作说明
本项目以一人组形式完成。我负责需求回顾、页面结构与样式、业务逻辑、交互实现、核心逻辑测试、README、GitHub 提交和博客整理。双人 Fork、Pull Request、队友评价等要求不适用于本次一人组,未作虚构填写。
| 工作项 | 具体完成内容 |
|---|---|
| 需求与设计 | 回顾原型,确定发布、搜索、详情、联系与状态更新的主流程及数据字段。 |
| 前端实现 | 独立编写 HTML、CSS 与 JavaScript,完成响应式界面、表单和弹窗交互。 |
| 核心逻辑 | 将校验、筛选、状态更新抽取为 core.js 纯函数。 |
| 测试与交付 | 编写 12 个单元测试用例,整理 README、GitHub 提交记录与博客材料。 |
三、PSP
“预估”可直接使用;“实际”请根据自己的真实记录填写,不能把预估时间当作实际时间提交。
| PSP2.1 | 阶段 | 预估(分钟) | 实际(分钟) |
|---|---|---|---|
| Planning | 计划与任务拆分 | 20 | 25 |
| Estimate | 估计任务时间 | 15 | 15 |
| Analysis | 需求回顾、技术学习 | 45 | 70 |
| Design Spec | 页面、数据字段与流程设计 | 50 | 60 |
| Design Review | 核对原型与功能边界 | 30 | 35 |
| Coding Standard | 制定命名与文件组织规则 | 15 | 20 |
| Design | 具体交互和数据结构设计 | 60 | 80 |
| Coding | HTML、CSS、JavaScript 编码 | 210 | 270 |
| Code Review | 检查表单、搜索与状态逻辑 | 40 | 45 |
| Test | 单元测试、Chrome 走查与修复 | 100 | 120 |
| Test Report | 截图与测试说明整理 | 45 | 45 |
| Size Measurement | 整理目录和提交记录 | 20 | 20 |
| Postmortem | 总结、README 与博客 | 70 | 35 |
| 合计 | 720(12 小时) | 840(14 小时) |
实际耗时比预估多 2 小时,主要是低估了表单异常分支、组合筛选、状态更新走查及 GitHub 上传整理的时间。下次会在计划阶段为浏览器兼容检查和提交材料预留更独立的时间。
四、解题思路与设计实现
4.1 数据与模块划分
每条信息包含 id、type、title、category、location、date、contact、description、status。type 区分寻物与招领,status 表示待处理或已解决。
index.html:页面结构,包括失物广场、发布表单和我的发布。css/style.css:颜色、卡片、表单、弹窗与窄屏适配。js/core.js:输入校验、筛选、状态更新等纯函数。js/app.js:DOM 渲染、表单提交、详情弹窗和 LocalStorage 持久化。
4.2 核心流程与数据流
填写发布表单 → 完整性校验 → 保存到 LocalStorage → 广场/我的发布展示
↓
浏览或关键词搜索 → 查看详情 → 复制联系方式 → 双方线下核验
↓
发布者在“我的发布”标记为已解决 → 页面同步显示已解决
数据流由表单输入开始:FormData 被转换为普通对象后先调用 validateListing;校验成功的数据写入 LocalStorage。页面每次渲染都从完整数据源读取,再将关键词、类型、类别和状态交给 filterListings 组合筛选。发布者确认物品已处理后,transitionStatus 只允许“待处理 → 已解决”的单向变更,并将结果重新保存和渲染。
4.3 关键实现
输入校验逐项检查类型、名称、类别、地点、日期、联系方式和描述;筛选函数同时匹配关键词、类型、类别和状态;状态更新只允许“待处理 → 已解决”,避免重复修改。
function transitionStatus(item, nextStatus) {
if (!item || item.status !== 'active') {
return { ok: false, message: '该信息已是已解决状态' };
}
if (nextStatus !== 'resolved') {
return { ok: false, message: '不支持的状态变更' };
}
return { ok: true, item: { ...item, status: 'resolved' } };
}
该逻辑使“联系之后如何结束信息”有清晰答案:发布者在“我的发布”操作,状态立即更新,其他用户也能看到已解决标识。
五、附加特点与成果展示
5.1 多条件组合筛选
设计意义: 除关键词搜索外,系统支持按信息类型、物品类别和处理状态组合筛选。校园失物信息较多时,用户能够更快缩小范围,减少无关信息干扰。
实现思路: 每次筛选均从完整信息列表开始,先将关键词统一转换为小写,再同时判断关键词、类型、类别和状态,避免先后筛选导致隐藏状态累积。
function filterListings(listings, filters) {
const query = normalized(filters.keyword);
return listings.filter(item => {
const haystack = [item.title, item.location, item.description, item.category]
.map(normalized).join(' ');
return (!query || haystack.includes(query)) &&
(!filters.type || item.type === filters.type) &&
(!filters.category || item.category === filters.category) &&
(!filters.status || item.status === filters.status);
});
}
【图 1:首页与失物广场截图】
【图 2:输入“校园卡”后仅显示一条匹配信息的截图】
5.2 一键复制联系方式与隐私提示
设计意义: 联系方式只在详情弹窗中展示,减少首页直接暴露个人信息;用户确认物品特征后可一键复制联系方式,避免手动抄写出错,也能加快线下核验。
实现思路: 详情页仅对待处理信息渲染“复制联系方式”按钮;点击后调用浏览器 Clipboard API。若浏览器不允许剪贴板写入,则提示用户手动复制,保证功能可理解、可降级。
async function copyContact(text) {
try {
await navigator.clipboard.writeText(text);
showToast('联系方式已复制');
} catch (_) {
showToast('请手动复制:' + text);
}
}
5.3 表单异常反馈与状态更新闭环
发布时缺少必填项会显示具体提示;页面提醒避免公开身份证号等敏感信息。发布成功后,信息出现在“我的发布”中;发布者可将它从待处理更新为已解决,更新后按钮不可重复操作。
设计意义: 表单即时反馈能够减少无效信息;状态更新让已找回或已归还的信息不再持续干扰其他用户,形成完整闭环。
【图 3:未填写物品名称时显示“请填写物品名称”的截图】
【图 4:黑色保温杯处于待处理、可标记为已解决的截图】
【图 5:黑色保温杯更新为已解决的截图】
六、目录与运行说明
102401216/
├─ index.html # 网页入口
├─ css/style.css # 样式与响应式布局
├─ js/core.js # 校验、筛选、状态更新等核心逻辑
├─ js/app.js # 页面交互与本地数据存储
├─ tests/core.test.html # 可在浏览器运行的 12 个自动化测试
├─ README.md # 运行说明
└─ 作业博客稿.md # 博客 Markdown 初稿
打开 GitHub 仓库,点击绿色 Code,选择 Download ZIP;解压后保留目录结构,用 Google Chrome 打开根目录 index.html。不要只下载单个 HTML 文件,否则 CSS 和 JavaScript 无法加载。
七、单元测试
7.1 测试工具与学习方式
本项目没有额外引入测试框架,而是在 tests/core.test.html 中使用原生 JavaScript 编写断言式测试,并调用 js/core.js 的纯函数。选择这种方式的原因是项目无需 Node.js 或安装依赖,测试人员下载项目后,用 Chrome 直接打开测试页即可复现。
我的简易测试流程如下:
- 将不依赖 DOM 的业务规则提取到
core.js。 - 在测试页引入
core.js,为每个规则准备输入数据与预期结果。 - 执行函数并判断布尔结果,将“通过/失败”逐条显示在页面上。
- 用 Chrome 打开
tests/core.test.html,核对汇总结果是否为12/12 通过。
7.2 测试代码与被测函数
下列测试直接调用 transitionStatus,验证待处理信息能更新为已解决,同时验证已解决信息不能重复更新:
['09 待处理信息可更新为已解决', () =>
C.transitionStatus({ id: '1', status: 'active' }, 'resolved')
.item.status === 'resolved'
],
['10 已解决信息不能重复更新', () =>
!C.transitionStatus({ id: '1', status: 'resolved' }, 'resolved').ok
]
同一测试页还覆盖了 validateListing 的必填项、非法类型和标题边界,以及 filterListings 的大小写无关搜索、地点搜索和多条件组合筛选。
7.3 测试数据构造思路
12 个用例覆盖正常路径、异常路径、边界条件和组合条件。正常路径验证合法信息可发布、待处理信息可更新;异常路径验证缺失名称、缺失联系方式、非法类型和非法状态更新;边界条件使用 31 个字符的标题检验长度限制;组合条件验证类型与状态同时满足才返回结果。对于测试人员可能的“刁难”,还考虑了空关键词、英文大小写不同、已解决信息重复更新等情况。
单元测试主要覆盖业务规则;DOM 点击、浏览器存储和剪贴板权限另通过 Chrome 人工走查验证。
【图 6:core.test.html 显示 12/12 通过的截图】
八、GitHub 签入记录
仓库地址:https://github.com/baomihua1234/102401216
提交按可验证的功能拆分:
docs: 添加项目入口与使用说明feat: 添加响应式页面样式feat: 实现发布搜索详情和状态更新test: 增加核心业务逻辑自动化测试
【图 7:GitHub 提交历史截图】
九、问题与解决办法
9.1 状态闭环不完整
- 问题: 原型中有“已找到/已归还”的概念,但未明确谁修改、在哪里修改。
- 尝试: 先考虑在详情页直接修改,随后发现任何访问者都可能误操作;最终增加“我的发布”入口,仅在发布者自己的列表中提供状态按钮。
- 结果: 已解决。状态更新后会保存并同步渲染,按钮不可重复操作。
- 收获: 业务状态不仅要有显示,还要明确操作主体、入口与允许的状态迁移。
9.2 页面逻辑不便测试
- 问题: 若将校验和筛选全部写在 DOM 事件中,浏览器界面测试难以覆盖异常分支。
- 尝试: 将校验、筛选和状态更新拆为
core.js中的纯函数,再在独立测试页引入这些函数。 - 结果: 已解决。测试页可直接复用核心逻辑,12 个用例均可在 Chrome 中运行。
- 收获: 将业务规则与页面渲染分离,既提高可测试性,也降低后续修改风险。
9.3 筛选条件容易相互干扰
- 问题: 若在已筛选结果上继续筛选,清空某个条件后可能无法恢复完整结果。
- 尝试: 每次筛选都从完整数据源开始,同时应用关键词、类型、类别和状态条件。
- 结果: 已解决。用户调整任意条件后都能得到一致结果。
- 收获: 多条件筛选应保持无状态计算,避免把中间结果当作新的数据源。
9.4 联系方式与隐私风险
- 问题: 联系方式若在卡片列表中直接展示,可能造成不必要的公开暴露。
- 尝试: 不实现即时聊天或复杂认证;改为仅在详情弹窗展示联系方式,同时加入敏感证件信息提示。
- 结果: 已解决。用户需先查看详情再复制联系方式,信息展示范围更小。
- 收获: 即使是课程项目,也应在便利性与个人信息保护之间做明确取舍。
十、总结与改进
本次作业把第一次的原型转化为可以运行、可以测试、可以复现的网页。后续可增加信息编辑、图片上传和数据导入导出;但应优先保证当前发布、搜索、详情、联系与状态更新主流程稳定可用。

浙公网安备 33010602011771号