2026秋软件工程第二次结对作业之程序实现

2026秋软件工程结对作业:校园寻物系统的程序实现

一、项目与结对信息

项目 内容
课程 H202601软件工程与软件工程实践
作业要求 2026秋软件工程结对作业(第二次之程序实现)
结对成员 102401115林鑫 102401118卢志勇
GitHub 仓库 https://github.com/209691048/102401118-102401115
在线预览 https://209691048.github.io/102401118-102401115/

本项目基于第一次结对作业的原型,完成“校园失物招领”Web 系统的核心流程:发布寻物或招领信息、浏览和搜索、查看详情并联系发布者,以及由发布者更新和维护信息状态。详情页支持一键复制联系方式;寻物标记为“已找到”、招领标记为“已归还”后,卡片会淡化显示,发布者还可在 3 分钟内撤销失误操作。

二、具体分工

成员 主要工作
林鑫 实现独立编辑页、站内弹窗、成功提示自动消失、完成状态卡片淡化、3 分钟内撤销“已找到/已归还”、联系方式补齐与一键复制、搜索提示等改进;提供截图和复现步骤,回归浏览器测试,在个人分支提交功能及测试改进,跟进 GitHub Actions/PR,。
卢志勇 围绕试用过程梳理并验收需求;创建并维护主仓库,搭建 Web 项目初始结构;负责基础页面、数据及发布、浏览、搜索、详情等核心流程的建设,完善初始测试,参与功能集成和主分支维护,并整理项目说明与博客

我们通过 GitHub 分支和 Pull Request 协作。功能完成后先提交到各自分支,再由队友互相检查后合并到主仓库。

三、PSP 时间记录

PSP 阶段 工作内容 预估耗时(分钟) 实际耗时(分钟)
Planning / Estimate 明确任务范围、拆分功能并估时 45 50
Analysis 阅读题目、梳理用户流程与异常场景 100 120
Design Spec / Review 设计页面结构、数据字段和状态流转 120 150
Coding Standard 约定目录、命名和提交信息规范 30 25
Coding 页面、数据读写与交互功能实现 480 620
Code Review 检查改动、处理 PR 反馈和合并问题 110 180
Test 手动走查、Node 测试、浏览器测试与 CI 排错 240 360
Test Report 整理测试用例、结果与截图 90 120
Reporting 完善 README、流程图和博客 180 210
Postmortem 复盘问题并制定后续改进计划 45 55
合计 1440 1890

实际耗时比预估多 450 分钟,主要集中在编码、代码检查和测试。独立编辑页和站内弹窗需要同时调整页面切换、字段回填和取消逻辑;“已找到 / 已归还”撤销、卡片淡化与复制联系方式是在试用后补充的功能;CI 又先后出现测试文件未提交、VM 环境缺少定时器等问题,需要结合 Actions 日志反复定位。之后会在编码前先列出异常流程和测试场景,并为每项功能的联调、回归测试单独预留时间,减少后期集中返工。

四、需求分析与设计

4.1 解决的问题

校园失物信息常散落在聊天群中,消息容易被覆盖,后续查找和确认状态不方便。系统把寻物和招领信息集中展示,让用户可以搜索物品、按信息类型筛选、查看详情和联系方式。物品找回或归还后,发布者可以更新状态,减少重复询问。

4.2 功能范围

本次完成的核心功能:

  • 发布寻物或招领信息,并填写物品名称、地点、日期、描述和联系方式。
  • 浏览信息、按关键词搜索,并按“寻物 / 招领”筛选。
  • 查看物品详情和发布者联系方式。
  • 在“我的发布”中查看本人发布的信息。
  • 编辑、删除本人信息;寻物可以标记为“已找到”,招领可以标记为“已归还”。
  • 两种完成状态的卡片都会淡化显示,发布者可在标记后的 3 分钟内撤销,恢复为“待处理”;超过时限后撤销入口隐藏。
  • 详情页提供“联系发布者”和“复制联系方式”操作;复制失败时会显示可手动复制的联系方式。

4.3 主要数据结构

一条信息使用 JavaScript 对象保存,包含物品内容、发布者标识和当前状态。数据由浏览器 localStorage 保存。statusUpdatedAt 记录完成状态的更新时间,用于判断 3 分钟撤销期限。

{
  id, publisherId, title, type, location, date,
  description, contactName, contact, icon, status, statusUpdatedAt
}

4.4 核心功能流程图

下图展示了从首页进入浏览、发布和个人信息维护的主要路径,以及表单校验、本人归属检查、取消操作和状态更新等分支。

campus-lost-found-flow.drawio (1)

  • 联系流程:详情页 → 查看联系方式 → 联系发布者,或复制联系方式后粘贴使用。
  • 状态流程:我的发布 → 标记已找到 / 已归还 → 对应卡片淡化 → 3 分钟内可撤销并恢复待处理;超过 3 分钟后撤销入口消失。

五、代码实现思路

5.1 页面与数据

项目使用原生 HTML、CSS 和 JavaScript。index.html 包含首页、详情、发布、我的发布和编辑页面;js/app.js 负责页面交互和业务逻辑;js/data.js 负责数据读写。发布记录保存在当前浏览器的 localStorage 中。

5.2 发布者归属校验

每条新发布的信息会保存一个 publisherId。个人发布列表只筛选当前浏览器身份对应的记录;编辑、删除和状态更新前都会检查归属。这里的身份标识用于本地原型演示,不等于服务器端账号认证。

function isOwnItem(item) {
    const publisherId = getCurrentPublisherId();
    return Boolean(publisherId && item && item.publisherId === publisherId);
}

function requireOwnership(item) {
    if (!item) {
        alert("未找到这条信息。");
        return false;
    }
    if (!isOwnItem(item)) {
        alert("只能修改自己发布的信息;旧版无归属信息仅供浏览。");
        return false;
    }
    return true;
}

这段逻辑把“记录是否存在”和“记录是否属于当前用户”作为维护操作的前置条件,避免仅凭页面按钮控制权限。

5.3 编辑内容校验与保存

编辑页面保存时会重新读取数据并检查归属。必填字段会去除首尾空格后校验;保存时只更新允许修改的字段,保留记录 ID、发布者、类型和完成状态。

const values = {};
Object.entries(fields).forEach(function ([suffix, key]) {
    values[key] = document.getElementById("edit" + suffix).value.trim();
});

if (!values.title || !values.location || !values.date ||
    !values.description || !values.contact) {
    showEditError("物品名称、地点、日期、描述和联系方式不能为空。");
    return;
}

// 只更新可编辑字段,保留 ID、发布者、类型和状态。
Object.assign(item, values);
saveItems(items);

5.4 状态维护与限时撤销

寻物完成后显示“已找到”,招领完成后显示“已归还”。两种状态都会写入 statusUpdatedAt,更新后重新渲染首页和“我的发布”,并给出站内弹窗提示。卡片共用 .item-completed 样式,降低完成信息的视觉高亮。重复标记时会提示用户,不重复写入相同状态。

发布者在完成状态写入后的 3 分钟内可以撤销;撤销后状态恢复为“待处理”,时间戳清除,卡片也恢复正常显示。超过 3 分钟后,页面隐藏撤销入口,执行撤销时还会再次检查时间和记录归属。

const COMPLETION_UNDO_WINDOW_MS = 3 * 60 * 1000;

function canUndoCompletedStatus(item, now) {
    if (!item || (item.type !== "寻物" && item.type !== "招领") ||
        getItemStatus(item) !== getCompletedStatus(item)) return false;
    const markedAt = Number(item.statusUpdatedAt);
    const currentTime = typeof now === "number" ? now : Date.now();
    return Number.isFinite(markedAt) && markedAt <= currentTime &&
        currentTime - markedAt <= COMPLETION_UNDO_WINDOW_MS;
}

5.5 一键复制联系方式

详情页的复制按钮读取已去除首尾空格的联系方式。浏览器支持剪贴板 API 时优先使用该 API;本地直接打开 HTML 或权限受限时,尝试创建临时文本框并执行复制。成功和失败都会通过站内弹窗反馈,失败时同时展示联系方式供手动复制。

async function copyTextToClipboard(value) {
    const text = String(value || "").trim();
    if (!text) throw new Error("没有可复制的联系方式。");

    if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
        try {
            await navigator.clipboard.writeText(text);
            return;
        } catch (error) {
            // 本地打开 HTML 或剪贴板权限受限时,尝试兼容方式。
        }
    }

    const textarea = document.createElement("textarea");
    textarea.value = text;
    textarea.setAttribute("readonly", "");
    textarea.setAttribute("aria-hidden", "true");
    textarea.style.position = "fixed";
    textarea.style.opacity = "0";
    textarea.style.left = "-9999px";
    document.body.appendChild(textarea);
    textarea.focus();
    textarea.select();

    let copied = false;
    try {
        copied = typeof document.execCommand === "function" && document.execCommand("copy");
    } finally {
        textarea.remove();
    }
    if (!copied) throw new Error("浏览器未能访问剪贴板。");
}

六、附加特点

除了作业要求的核心流程,我们还完成了以下改进:

  1. 按信息类型筛选: 可以只看寻物或招领记录,减少无关信息。
  2. 独立编辑页面: 编辑操作在站内页面完成,不使用浏览器原生输入框;取消或返回不会保存修改。
  3. 操作弹窗与反馈: 状态更新、删除确认等操作通过页面内弹窗提示;编辑成功提示会在短暂显示后消失。
  4. 输入校验: 联系方式必填,拒绝纯空格,并限制字段长度。
  5. 限时撤销: 已找到和已归还状态都允许发布者在 3 分钟内撤销误操作。
  6. 完成卡片淡化: 两种完成状态在首页和“我的发布”中都会降低饱和度和透明度,方便区分待处理信息。
  7. 复制联系方式: 提供一键复制、剪贴板兼容方案和失败后的手动复制提示。

这些改进主要是为了让发布者更容易维护信息,也让用户更清楚操作是否成功。

七、目录说明与运行方法

7.1 目录结构

项目根目录/
├── index.html
├── README.md
├── css/
│   └── style.css
├── js/
│   ├── app.js
│   └── data.js
├── tests/
│   ├── test.html
│   ├── browser-test.js
│   ├── run-browser-test.cjs
│   └── ownership-test.cjs
└── .github/
    └── workflows/
        └── test.yml

7.2 运行主程序

  1. 下载或克隆主仓库。
  2. 保持目录结构不变。
  3. 使用 Google Chrome 打开项目根目录下的 index.html。

项目不需要安装前端框架。数据保存在当前浏览器中,不会自动同步到其他浏览器或设备。

7.3 运行浏览器测试

在项目根目录打开终端,执行:

python -m http.server 8000

然后在 Chrome 打开:

http://localhost:8000/tests/test.html

点击“开始测试”运行浏览器测试。测试页通过 iframe 加载主页面,因此建议通过本地 HTTP 服务运行测试页。

Node 逻辑测试可在项目根目录执行:

node tests/ownership-test.cjs

GitHub Actions 还会运行语法检查、Node 测试和 Chromium 浏览器测试。

八、单元测试与测试用例

8.1 测试工具与学习过程

我们使用 Node.js 的断言和 VM 模拟环境检查业务逻辑,并使用 Playwright 启动 Chromium 测试实际页面交互。测试过程中先阅读工具文档和已有示例,再为项目中的发布、归属校验、编辑、状态更新等功能编写用例。

本地运行命令:

node tests/ownership-test.cjs
node tests/run-browser-test.cjs

浏览器界面也可以通过前述 python -m http.server 8000 方式手动测试。

8.2 测试用例

当前浏览器自动化测试共 28 项,由 Playwright 启动 Chromium 后逐项操作实际页面。

编号 测试内容 预期结果
1 首页加载 显示 4 条预置记录
2 关键词搜索 搜索后仅显示匹配内容
3 招领筛选 只显示招领信息
4 搜索无结果 显示无匹配提示
5 有效发布 保存记录并显示成功结果
6 联系方式只输入空格 阻止发布
7 编辑并保存 打开独立编辑页并更新记录
8 取消编辑 不保存本次修改
9 标记完成状态 更新状态并显示站内弹窗
10 重复标记完成状态 提示已完成,不重复写入
11 删除确认 弹窗取消时保留,确认后删除
12 已归还卡片与撤销 卡片淡化,3 分钟内可撤销
13 已归还状态超过 3 分钟 隐藏撤销入口
14 大小写及多字段搜索 名称、描述、地点搜索及类型组合筛选正确
15 空列表和无匹配 分别显示对应提示
16 必填字段为空白 阻止提交
17 点击已经过期的撤销入口 再次校验时间,不修改状态
18 他人、无归属和过期记录 不提供有效撤销操作
19 标题包含 HTML 字符 按文本显示,不执行注入内容
20 联系发布者 显示联系方式站内弹窗,不调用浏览器原生弹窗
21 联系方式缺失 显示缺少联系方式的站内提示
22 已找到状态 3 分钟内撤销 状态恢复待处理,卡片恢复正常显示
23 已找到状态超过 3 分钟 隐藏撤销入口
24 旧版已找到记录 首次打开时补记时间并提供一次撤销机会
25 旧版示例数据缺少联系方式 自动补齐演示联系人和邮箱
26 一键复制成功 复制正确内容并显示成功提示
27 剪贴板权限受限 使用兼容复制方式完成操作
28 剪贴板和兼容方式都失败 显示手动复制内容

8.3 测试数据与测试设计

测试数据包含本人记录、其他用户记录和没有发布者标识的旧版记录。这样可以检查正常路径,也能检查边界与异常路径。对于输入校验,测试空字符串、纯空格和超长联系方式;对于维护操作,测试合法 ID、他人 ID、旧数据 ID 和不存在的 ID。

目前测试覆盖了主要功能路径,但还可以增加不同浏览器存储限制、键盘操作、窄屏布局和跨浏览器数据差异等测试。由于数据保存在本地浏览器,本版本无法测试多设备同步。

九、代码提交与协作过程

我们使用 Fork、分支和 Pull Request 进行协作。每次完成一组功能后提交代码,通过 PR 让队友检查,再合并到主仓库。

PQ#4 PQ#5 因为发生了冲突导致了关闭与撤销 PQ#1是进行PQ的初次尝试

  • Commit 记录截图:
    1f5507273065fec61b11dd5b2332acf2

PQ#2的commit没有合并,导致会有点混乱

  • 自动化测试记录:
    image

十、遇到的问题与解决方法

10.1 编辑按钮未打开编辑页

  • 问题描述: 点击编辑后没有进入独立编辑界面。
  • 做过的尝试: 检查编辑按钮事件、页面元素 ID 和测试页加载情况;通过浏览器测试复现。
  • 解决情况: 补全页面切换和字段回填逻辑,并增加“编辑页是否打开、保存是否生效、取消是否不保存”的浏览器测试。
  • 收获: 不能只确认按钮存在,还要验证点击后的完整用户流程。

10.2 GitHub Actions 找不到测试文件

  • 问题描述: 工作流提示 MODULE_NOT_FOUND,无法找到 tests/ownership-test.cjs。
  • 做过的尝试: 对照仓库中的目录结构、工作流命令和文件名,确认实际文件是否已提交到触发工作流的分支。
  • 解决情况: 把测试文件提交到分支。
  • 收获: 本地存在文件不代表 GitHub 运行的提交也包含该文件,应检查 Actions 对应的 commit 和分支。

10.3 Node 测试模拟环境缺少定时器

  • 问题描述: 编辑保存后的成功提示调用 setTimeout,而 VM 测试环境没有提供该函数。
  • 做过的尝试: 查看 Actions 错误堆栈,确认错误发生在测试模拟环境。
  • 解决情况: 在 VM 上下文中补充定时器函数,使测试环境具备页面逻辑所需的运行能力。
  • 收获: 测试环境需要模拟被测代码真实依赖的浏览器 API。

十一、队友评价

对卢志勇的评价

值得学习的地方: 卢志勇先搭建了仓库、页面和数据的基础结构,把发布、浏览、搜索和详情等核心路径做成了可运行的版本,后续功能可以在此基础上逐步迭代。遇到主分支集成和测试问题时,也能够配合检查并维护主仓库。

需要改进的地方: 后续可以更早同步数据字段、页面入口和每次提交覆盖的功能,尤其是在新增状态字段或调整页面结构前先说明影响范围。这样双方编写测试和处理 PR 冲突时会更顺畅。

十二、总结与后续计划

这次结对作业让我经历了需求拆分、页面实现、测试设计、PR 协作和问题排查。实现过程中,我们补充了完成状态的 3 分钟撤销、已找到与已归还卡片淡化、联系方式复制和旧示例联系方式迁移,并为这些交互增加了浏览器测试。页面看起来完成并不代表交互已经可靠,仍要通过手动走查和自动化用例验证。

当前项目使用浏览器 localStorage 保存数据,适合本地演示。后续若要让全校用户共同使用,需要增加后端、数据库和真正的用户认证,并在服务器端校验数据归属。

posted @ 2026-10-10 18:17  Yishang  阅读(8)  评论(0)    收藏  举报