第1组 · 嵇宇锋 · 第1次评审详情 - sebestp/coursewise GitHub Wiki
- 报告期次:第1次 | 报告日期:2026-10-08 | 项目组号:第1组 | 项目名称:在线购物系统
- 已完成工作:完成前端页面与文档:商品详情页、意向提交页、口令码展示页、卖家后台共 5 个页面按接口契约开发并置于 static/(启动实测 HTTP 200、静态资源加载正常);编写《前端页面说明》(页面清单、买卖双方操作流程、空状态、接口对应表),附 8 张运行截图
- 成果物:前端页面说明
负责人:嵇宇锋 | 状态:前端页面已接入 Java 后端(静态资源就位),页面说明与运行截图已产出 成果物:
server/src/main/resources/static/下 5 个前端文件 + 本说明 + 8 张运行截图。 前端页面按 接口设计 约定的接口契约编写,调用统一使用相对路径,置于 Spring Boot 的static/目录下与后端同源部署,因此无需跨域配置即可联调。
| 页面名 | 文件路径 | 用途 | 调用了哪些接口 |
|---|---|---|---|
| 买家首页 |
server/src/main/resources/static/index.html + buyer.js + style.css
|
展示当前在售 / 交易中商品;提交购买意向;凭口令码查询位次、改联系信息、撤销意向 |
GET /api/product、POST /api/intents、GET /api/intents/lookup/{code}、PUT /api/intents/lookup/{code}、POST /api/intents/lookup/{code}/cancel
|
| 卖家后台 |
server/src/main/resources/static/admin.html + admin.js + style.css
|
卖家登录;发布商品;查看排队;进入交易;标记结果;下架;历史记录;修改密码 |
POST /api/seller/login、GET /api/seller/overview、POST /api/seller/products、POST /api/seller/products/{id}/deal、POST /api/seller/products/{id}/deal/result、POST /api/seller/products/{id}/freeze、POST /api/seller/products/{id}/unfreeze、POST /api/seller/products/{id}/offshelf、POST /api/seller/intents/{id}/dispose、GET /api/seller/history、GET /api/seller/history/{id}、POST /api/seller/password
|
| 样式表 | server/src/main/resources/static/style.css |
两个页面共用的全部样式(卡片、按钮、表格、弹窗、徽标、Toast) | — |
页面访问地址(后端启动后):
| 地址 | 页面 |
|---|---|
http://localhost:8080/ |
买家首页(Spring Boot 默认把 index.html 作为欢迎页) |
http://localhost:8080/admin.html |
卖家后台 |
说明:买家首页右上角「卖家入口」链接已由
/admin修正为/admin.html(唯一一处在搬运时修改的代码);buyer.js、admin.js中所有请求本来就是相对路径(/api/xxx),未发现写死localhost:3000的情况,故未修改。
买家免注册,全程以「口令码」为唯一凭证。
- 页面元素:首页商品卡(
#productCard/#productArea)——图片(或占位图)、商品名、状态徽标(在售中/商品交易中)、价格、描述、发布时间、「我要购买」按钮。 - 行为:页面加载即调用
loadProduct()→GET /api/product,并每 10 秒轮询一次,保证商品被冻结 / 下架后状态及时刷新。 - 接口:
GET /api/product(buyer.js→loadProduct())。

- 页面元素:点击「我要购买」弹出弹窗
#buyMask,含「姓名」「联系电话」两个必填项(#buyName/#buyPhone)、错误提示区#buyError、「确认提交,加入排队」按钮(#buySubmit)。 - 校验:姓名或电话为空时不发请求,直接提示「姓名与联系电话均为必填」。
- 接口:
POST /api/intents(buyer.js→submitIntent()),入参{name, phone};若商品已被冻结,后端返回 400「商品交易中,暂停接收新意向」,弹窗内显示该错误并刷新商品状态。

- 页面元素:提交成功后关闭意向弹窗,弹出
#codeMask:口令码大字展示(#codeText)、「复制口令码」按钮(#copyCode)、风险提示文案、「我已保存好口令码」按钮(#codeDone)。 - 行为:口令码只在
POST /api/intents的响应中返回一次,之后任何接口都不再返回;点「我已保存好口令码」会自动把口令码填入查询框并立即查询。 - 接口:响应体
{code}来自POST /api/intents。

- 页面元素:「我的排队」卡片中的口令码输入框(
#codeInput,自动转大写)+「查询」按钮(#lookupBtn),结果区#lookupResult:- 位次条:
你当前排第 N 位;已进入交易时显示🎉 已进入交易!…; - 键值行:商品、提交时间;
- 可编辑的「姓名」「联系电话」输入框 + 「保存修改」(
#saveInfo)+「撤销我的意向」(#cancelIntent)。
- 位次条:
- 接口:
- 查询:
GET /api/intents/lookup/{code}(lookup()); - 改信息:
PUT /api/intents/lookup/{code}(保存后位次不变,renderLookup()内#saveInfo回调); - 撤销:
POST /api/intents/lookup/{code}/cancel(二次确认后调用,成功后口令码立即失效)。
- 查询:
- 失败提示:口令码无效 / 已失效 / 已撤销时,结果区显示后端错误原文,并附提示「口令码可能在交易结束后失效,或你已撤销意向」。

卖家为系统指定的唯一账号(默认 admin / seller123,首次启动自动创建,无注册)。
- 页面元素:
#loginCard中「用户名」(#loginUser)、「密码」(#loginPass)、错误提示(#loginError)、「登录」按钮(#loginBtn,密码框回车同样可登录)。 - 接口:
POST /api/seller/login,成功返回{token, username};token 存localStorage.seller_token,之后所有卖家接口通过请求头X-Token携带;登录会刷新 token(旧 token 立即失效)。token 失效(401)时自动退回登录页。

- 页面元素:无在售商品时,「当前商品」页展示发布表单:「商品名称」「商品描述(选填)」「价格(元)」「商品图片(选填,JPG/PNG ≤5MB)」+「发布商品」按钮。
- 前端校验:名称必填;价格必须为大于 0 的数字;图片仅 JPG/PNG 且 ≤5MB;图片以
FileReader读成data:image/...;base64,...随请求提交。 - 接口:
POST /api/seller/products(publishProduct())。发布后商品状态直接为「在售」,同一时刻仅允许一件在售。
- 页面元素:「当前商品」卡片(状态徽标、商品图片/占位图、名称、价格、描述、发布/冻结时间)+「意向购买人」表格(位次 / 姓名 / 联系电话 / 意向提交时间 / 状态);交易中的买家单独置顶一行并标「交易中」。
- 行为:
enterAdmin()与 5 秒轮询调用GET /api/seller/overview;轮询数据无变化时不重渲染,且发布表单正在填写时不会被刷新清空。 - 接口:
GET /api/seller/overview(loadOverview())。

- 页面元素:商品操作按钮随状态变化——在售时「与队首进入交易」(队列为空时禁用)「手动冻结」「手动下架」;手动冻结后为「手动解冻」「手动下架」;交易中显示交易对象信息与「标记交易成功」「标记交易失败」。
- 接口(均带二次确认):
- 进入交易:
POST /api/seller/products/{id}/deal; - 手动冻结:
POST /api/seller/products/{id}/freeze;手动解冻:POST /api/seller/products/{id}/unfreeze; - 手动下架:
POST /api/seller/products/{id}/offshelf。
- 进入交易:
- 页面元素:交易中的商品卡片下方两个按钮——「标记交易成功(下架进历史)」「标记交易失败(自动递补)」,均有不可回退的确认提示。
- 接口:
POST /api/seller/products/{id}/deal/result,入参{result: 'success' | 'fail'}。 - 失败后处置:若存在「交易失败的买家」待处理卡片,可对指定意向点「重新排队」或「作废」→
POST /api/seller/intents/{id}/dispose({action: 'requeue' | 'void'}),重新排队沿用原口令码。
- 页面元素:顶部 Tab「当前商品 / 历史记录」;历史表(名称 / 价格 / 发布时间 / 交易时间 / 意向数 /「查看意向」),底部分页(「上一页」「下一页」,每页 10 条);点击行弹出意向明细弹窗
#histMask,列姓名、电话、提交时间与终态徽标(成功 / 已撤销 / 失败)。 - 接口:
GET /api/seller/history?page=n(loadHistory())、GET /api/seller/history/{id}(showHistoryDetail())。

- 页面元素:右上角「修改密码」链接(
#changePwdLink)→ 弹窗「原密码」「新密码(不少于 8 位)」「确认新密码」+「确认修改」。 - 前端校验:三项均必填;新密码不足 8 位、两次输入不一致时在弹窗内提示。
- 接口:
POST /api/seller/password(changePassword())。

| 空状态 | 触发条件 | 前端表现(位置 / 文案) |
|---|---|---|
| 无商品在售 |
GET /api/product 返回 product: null(买家端) |
买家首页商品卡内居中显示「暂无商品在售」+ 灰色副文案「稍后再来看看吧」,不显示购买按钮(buyer.js → renderProduct(null))。卖家侧同理:无在售商品时「意向购买人」卡片显示「暂无在售商品」,同时「当前商品」卡片自动切换为发布表单。 |
| 商品没有图片 | 商品 imageUrl 为空 |
不使用 <img>,改用内联 SVG 占位图(灰色圆角方框 + 山峦图标),买家端 90×90、后台 70×70,卡片布局与有图时完全一致(buyer.js / admin.js 中 pic 三元表达式)。 |
| 商品描述为空 | 商品 description 为空串 |
通过三元表达式整段跳过描述节点,不渲染空的 .desc 元素、也不留空白占位,价格与发布时间之间自然衔接(buyer.js 商品卡、admin.js 当前商品与历史详情)。 |
补充:卖家后台另有两处空状态——「意向购买人」无记录时显示「暂无买家提交意向」;历史记录为空时「历史商品」卡片显示「暂无历史记录」;历史详情中该商品无意向时显示「该商品无任何意向记录」。
| 接口路径 | 方法 | 前端调用位置(static/ 下文件) |
|---|---|---|
/api/product |
GET |
buyer.js → loadProduct()(页面加载 + 每 10 秒轮询) |
/api/intents |
POST |
buyer.js → submitIntent()(意向弹窗「确认提交,加入排队」) |
/api/intents/lookup/{code} |
GET |
buyer.js → lookup()(「查询」按钮、口令码输入框回车、口令码弹窗「我已保存好口令码」) |
/api/intents/lookup/{code} |
PUT |
buyer.js → renderLookup() 内 #saveInfo 点击回调 |
/api/intents/lookup/{code}/cancel |
POST |
buyer.js → renderLookup() 内 #cancelIntent 点击回调 |
/api/seller/login |
POST |
admin.js → login()(「登录」按钮、密码框回车) |
/api/seller/password |
POST |
admin.js → changePassword()(修改密码弹窗「确认修改」) |
/api/seller/overview |
GET |
admin.js → loadOverview()(进入后台 + 每 5 秒轮询) |
/api/seller/products |
POST |
admin.js → publishProduct()(发布表单「发布商品」) |
/api/seller/products/{id}/deal |
POST |
admin.js → renderOverview() 内 #enterDeal
|
/api/seller/products/{id}/deal/result |
POST |
admin.js → renderOverview() 内 #markSuccess / #markFail
|
/api/seller/products/{id}/freeze |
POST |
admin.js → renderOverview() 内 #freezeBtn
|
/api/seller/products/{id}/unfreeze |
POST |
admin.js → renderOverview() 内 #unfreezeBtn
|
/api/seller/products/{id}/offshelf |
POST |
admin.js → renderOverview() 内 #offshelfBtn
|
/api/seller/intents/{id}/dispose |
POST |
admin.js → renderOverview() 内 [data-requeue] / [data-void] 按钮 |
/api/seller/history |
GET |
admin.js → loadHistory()(「历史记录」Tab、分页按钮) |
/api/seller/history/{id} |
GET |
admin.js → showHistoryDetail()(历史表行「查看意向」) |
/uploads/{file} |
GET | 由商品数据中的 imageUrl 直接作为 <img src> 使用(买家商品卡、后台商品卡) |
统一封装:
buyer.js/admin.js各自有一个api(path, opts)帮助函数,负责拼Content-Type: application/json、JSON 序列化请求体、解析响应与统一抛错;卖家侧额外附加X-Token请求头。
| 编号 | 截图 | 对应内容 |
|---|---|---|
| 01 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/01-买家首页.png |
买家首页,商品「iPhone 14 128G 星光色 ¥3599.00」在售中,含状态徽标、「我要购买」按钮与「我的排队」查询区 |
| 02 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/02-提交购买意向弹窗.png |
点击「我要购买」后的意向弹窗,已填写姓名与联系电话 |
| 03 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/03-口令码展示页.png |
提交成功后的口令码弹窗,含 8 位口令码、「复制口令码」与「仅显示这一次」的保存提示 |
| 04 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/04-凭口令码查询排队位次.png |
用口令码查询得到的位次页:显示「你当前排第 3 位」、商品与提交时间,以及改信息 / 撤销意向入口 |
| 05 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/05-卖家后台登录页.png |
/admin.html 未登录时的卖家登录页(用户名 + 密码 + 登录按钮) |
| 06 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/06-后台当前商品页.png |
登录后「当前商品」页:商品信息 + 操作按钮 +「意向购买人」排队列表(3 位买家按提交时间正序) |
| 07 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/07-后台历史记录页.png |
「历史记录」Tab:已下架商品 AirPods Pro 2(¥899.00,意向数 2)与分页控件 |
| 08 | https://github.com/xych5q/online-shop/raw/main/docs/design/screenshots/08-修改密码弹窗.png |
右上角「修改密码」弹窗:原密码、新密码(不少于 8 位)、确认新密码 |
- 前端文件本身已在 Java 后端上验证:本地打包运行
server后,http://localhost:8080/与http://localhost:8080/admin.html均正常返回页面(HTTP 200)。 - 买家端操作链路(浏览、提交意向、凭码查询/修改/撤销)的正确性由 16 组 JUnit 用例覆盖,另有人工全链路验证记录,见 第1次测试记录。
- 由于前端调用的是相对路径,同一套页面在 8080(Java 后端)下无需任何改动即可直接工作,已由组长启动实测确认。
(评审后填写)