City Hunt 是由致诚团委组织,面向全校同学开展的活动。活动中,四名选手组成一组,按组委会发布的任务清单要求前往各个打卡点打卡积分,活动结束时将所有组按积分排序决出优胜者。
- 请严格按照打卡点的样图和文字要求拍照上传,审核结果将会实时返回。请确认审核通过后再前往下一打卡点。
- 每个打卡点描述开头的数字为 首个打卡的队伍得分/第二名得分/第三名得分/其余队伍得分。
- “至少三人”表示照片中应出现至少三名小组成员;如未指明“按图中姿势”,则姿势可任意选取。
这是一个示例区域,所有点位均在南科大校园内。
6/5/4/3 在图中所示位置按图中所示姿势拍照。至少三人。

8/7/6/5 找到图1中所示标识并合影。至少三人。若照片中同时包含图2中所示标识额外+2分。
 
页面应能按下述的详细要求从服务器实时获取信息,并在连接异常时提示用户。
/login
用户登录。
后台登记选手,人工存储每组每组合照。
/checkpoints
选手端 UI 如附件所示(下划线表示链接/按钮),选手可以浏览所有打卡点,并选择打卡点上传本组打卡照片。照片只能上传一张,不超过 10MB。
打卡点分为四个状态:未打卡,待审核,未通过,已完成。每个状态下应展示的信息如下:
- 未打卡:点位信息,当前已通过人数,我的打卡图片(点击上传)
- 待审核:点位信息,当前已通过人数,我的打卡图片(点击查看/重新上传),我的打卡时间,我的预计得分(包含所有已通过/待审核的队伍的排名)
- 未通过:点位信息,当前已通过人数,我的打卡图片(点击查看/重新上传),我的打卡时间,未通过原因
- 已完成:待审核:点位信息,当前已通过人数,我的打卡图片(点击查看/重新上传),我的打卡时间,我的得分。
- 我的得分显示方式为 3 ,无附加分 / 3(+2),有附加分,附加分说明见管理端
选手应能在不手动刷新页面的前提下看到实时推送的通知,通知出现在页面顶部,确认后消失。任何一个打卡点状态变更;打卡点分数变更(因为管理员手动修改/其他组的状态变更导致排名变化);有公告变更的时候选手都应接到通知。打卡点状态也为实时更新。通知应有声音提示。
页面中应有浮动的回到顶部按钮,如果有新通知,按钮上展示红点。
可以继续查看本队打卡信息,但不能继续上传图片。排行榜数据不自动展示,待后台审核后另外发布。
管理员可以登记每队队长的学号,让其可用 CAS 登录。 一期使用 token 登录
管理员可以添加/导入区域、打卡点的信息(编号,名称,描述,分值,图片)。
/submissions
管理端控制选手的提交。
审核端页面如附件所示(下划线表示链接/按钮),管理员可以:
- 编辑赛事公告(每次保存都会推送通知给所有选手)
- 选择自己负责的打卡区域,此时只会显示该区域的所有未审核打卡提交
- 查看每一个未审核的提交,提交应包含:组名,小组合照,打卡图片,打卡时间,操作
- 为通过的提交输入附加分,接受任意正负整数(空置为0),加值到队伍的最终得分上。
- 为拒绝的提交输入拒绝原因。
- 手动修改某一提交记录的状态(修改为通过/拒绝/修改附加分),并通知所有受影响的选手
- 在无需手动刷新的情况下自动将新的提交追加至页面最后,并有声音提示。
无需存储历史提交记录,只需记录最后一次提交。
管理员应能导出(csv即可)含有组id、组名、各打卡点积分及总积分的表格,以备后续处理。
- 计算“我的预期得分时”,名次 = (当前点)已通过人数 + 待审核人数 + 1
- 计算实际得分时,名次 = 当前点打卡时间早于(严格小于)我的打卡时间的已通过人数 + 1
- 打卡时间以服务器收到请求时间为准
- 管理员修改某提交状态/新审核通过一个提交时,可能导致该打卡点其他组的名次变更
1. GET /checkpoints with userid
Return checkpoints info in json
{[{
"name": "SUSTech",
"desc": "All points are in SUSTech",
"points": [{
"id": "1-1"
"name": "Lecture Hall 1",
"scores": [6, 5, 4, 3],
"desc": "Take a photo as given",
"images": ["1-1-1.jpg", "1-1-2.jpg"],
"passed": 0, // Users that has submitted an accepted answer
"state": "pending", // or "accepted", "denied", null
"uploaded_time": "", // An ISO string describing time or null
"photo": "U12210101-P1-1.jpg", // or null
"score": "0(+2)", // or null
"fail_reason": "" // or a string containing reason
// Read these 6 above from submissions
},]
},]}
2. POST /submit/:checkpointid in file with userid
Create/Update submission, update all related ones and return result
3. POST /submissions/query in json
{
"checkpoints": ["1-1"],
"users": ["12210101"],
"states": ["pending"]
}
Return matching submissions in json
{[{
"id": "U12210101-P1-1"
"checkpoint": "1-1",
"user": "12210101",
"photo": "U12210101-P1-1.jpg",
"state": "pending",
"uploaded_time": "", // ISO format
"score": 0,
"bonus": 0
},]}
4. POST /submissions/modify in json
{
// Like the json above
}
Change submission, update all related ones and return result
Unchanged fields may not be in the request.
User related (suggestions welcomed):
5. POST /login in json
{
"userId": "12210101",
"password": "" // hashed
}
Return (set cookie) uid and token
6. GET /logout
Reset uid and token
Static files
/static
public folder, including checkpoint desc. images
/uploads
Auth required, for user uploaded photos
const submissionSchema = new Schema({
checkpoint: String, // Checkpoint id
user: String, // User id
photo: String, // Photo name (generate from the 2 ids like above)
uploaded: {type: Date, default: Date.now() }, // time
state: {type: String, enum:["pending", "accepted", "denied"], required: true },
score: Number,
bonus: Number
})
const userSchema = new Schema({
userId: String,
password: String
})
暂定使用 json 提前配好:
"checkpoints": [ {
"name": "SUSTech",
"desc": "All points are in SUSTech.",
"points": [ {
"id": "1-1",
"name": "Lecture Hall 1",
"scores": [6, 5, 4, 3],
"desc": "Take a photo as given",
"images": ["1-1-1.jpg", "1-1-2.jpg"]
}, {
"id": "1-2",
"name": "Lecture Hall 2",
"scores": [6, 5, 4, 3],
"desc": "Take a photo as given",
"images": ["2-1.jpg"]
}
]
}, {
"name": "Shenzhanbei Railway Station",
"desc": "All points are in or around the station.",
"points": []
}
]
- 身份认证的实现方式
v1.3:
修改了部分 api 格式,增加了对 id 的要求;增加了一些注释
v1.2:
新增的前后端分离的挂载路径的描述
v1.1:
将管理端的两个页面整合为一个页面