feat(join): B1 名单加入申请 —— 学生提交 + 管理端审批(V7 存 AES 密文)
按契约「服务端转 HMAC、不落明文」设计会写不进名册:collect_roster.id_card_no 是 NOT NULL 的密文列,而申请表只有 hash 与 mask(三条替代路都被排除)。 V7 给 collect_join_request 补 id_card_cipher(与名册同一把密钥/算法),审批时原样搬进名册, 明文全程只在提交请求的内存里存在过。V7 之前的老申请明确提示重新提交,不静默建一条 永远过不了校验的名册行。 另修 F-20:自述班级不能借用姓名规范化(姓名要「张 三」→「张三」,班级名里的空格有意义)。 验证:scripts/e2e-m2-join.py 50/50。
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 名单加入申请接口(《03-接口设计》§8.7.3 ~ §8.7.5,FR-509)。
|
||||
*
|
||||
* ## 这条通道的边界(界面文案要照着说)
|
||||
*
|
||||
* 它**只解决"进名单",不绕过门禁** —— 申请期间学生仍然不能填报。
|
||||
* 所以审批通过之后,界面要告诉管理员"学生返回重新核验就能进",
|
||||
* 而不是"他已经可以填了"。
|
||||
*
|
||||
* ## 两个刻意的设计,前端要照做
|
||||
*
|
||||
* 1. **没有批量通过/一键全通过接口**(契约 §8.7.5 的取舍):身份认定环节的
|
||||
* 误操作代价极高 —— 放错一个人进名单,等于让一个不是这批的人拿到认定资格。
|
||||
* 所以这里只有逐条通过、逐条驳回(驳回还必须填原因)。
|
||||
* 2. **`notifySent` 恒为 false**:通知渠道(M5-12)还没做,
|
||||
* 服务端如实返回 false,界面必须显示"请口头/群里告知学生",不能假装通知过了。
|
||||
*/
|
||||
import http from '@/utils/http'
|
||||
|
||||
export interface JoinDupHint {
|
||||
type: string
|
||||
taskName: string
|
||||
realName: string
|
||||
}
|
||||
|
||||
export interface JoinAdminItem {
|
||||
requestId: number
|
||||
taskId: number
|
||||
taskName: string | null
|
||||
realName: string
|
||||
idCardMask: string
|
||||
phoneMask: string | null
|
||||
orgHint: string | null
|
||||
reason: string | null
|
||||
/** `0` 待审 / `1` 已通过 / `2` 已驳回 */
|
||||
status: number
|
||||
statusText: string
|
||||
createTime: string | null
|
||||
waitHours: number | null
|
||||
/** 该证件号在**其他任务**的名册里出现过(帮判断是不是本校学生) */
|
||||
duplicateHint: JoinDupHint | null
|
||||
rosterId: number | null
|
||||
}
|
||||
|
||||
export interface JoinAdminList {
|
||||
list: JoinAdminItem[]
|
||||
total: number
|
||||
counts: { pending: number, approved: number, rejected: number }
|
||||
alert: { level: number, text: string } | null
|
||||
}
|
||||
|
||||
export interface JoinListQuery {
|
||||
taskId?: number
|
||||
/** 默认 0(待审) */
|
||||
status?: number
|
||||
keyword?: string
|
||||
pageNum?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export function getJoinRequests(params: JoinListQuery = {}) {
|
||||
return http.get<JoinAdminList>('/roster/join-requests', params)
|
||||
}
|
||||
|
||||
export interface JoinApproveReq {
|
||||
/** 指定班级;不传则用学生的自述班级匹配组织名(匹配不上会要求手选) */
|
||||
orgId: number
|
||||
/** 修正姓名(管理员核对后可改) */
|
||||
realName?: string
|
||||
/** 修正证件号 */
|
||||
idCardNo?: string
|
||||
}
|
||||
|
||||
export interface JoinApproveResult {
|
||||
requestId: number
|
||||
status: number
|
||||
rosterId: number
|
||||
rosterCreated: boolean
|
||||
/** ⚠️ 恒为 false:通知没发出去,需要人工告知 */
|
||||
notifySent: boolean
|
||||
/** true = 已绑定,学生返回重新核验就能直接进 */
|
||||
canFillNow: boolean
|
||||
}
|
||||
|
||||
export function approveJoinRequest(id: number, data: JoinApproveReq) {
|
||||
return http.post<JoinApproveResult>(`/roster/join-requests/${id}/approve`, data)
|
||||
}
|
||||
|
||||
export interface JoinRejectReq {
|
||||
/** **必填** —— 会原样给学生看 */
|
||||
reason: string
|
||||
reasonCode?: string
|
||||
}
|
||||
|
||||
export function rejectJoinRequest(id: number, data: JoinRejectReq) {
|
||||
return http.post<{ requestId: number, status: number, notifySent: boolean }>(
|
||||
`/roster/join-requests/${id}/reject`,
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
/** 申请状态。 */
|
||||
export const JOIN_STATUS: Record<number, { text: string, color: string }> = {
|
||||
0: { text: '待审核', color: 'orange' },
|
||||
1: { text: '已通过', color: 'green' },
|
||||
2: { text: '已驳回', color: 'red' }
|
||||
}
|
||||
|
||||
/** 驳回快捷原因码(契约 §8.7.5)。 */
|
||||
export const REJECT_REASON_CODES = [
|
||||
{ code: 'NOT_THIS_SCHOOL', label: '不是本校学生' },
|
||||
{ code: 'INFO_MISMATCH', label: '信息与报名资料不符' },
|
||||
{ code: 'NOT_IN_SCOPE', label: '不在本次认定范围' },
|
||||
{ code: 'OTHER', label: '其他原因(请在说明里写清)' }
|
||||
]
|
||||
|
||||
/** 关键错误码 → 一句"接下来怎么办"。 */
|
||||
export const JOIN_ERROR_HINT: Record<number, string> = {
|
||||
40040: '这条申请已经被别人处理过了(刷新列表即可看到最新状态)。',
|
||||
40041: '名册里已经有这个证件号了 —— 可能刚被老师手工加进去,去「名单管理」核对一下。',
|
||||
40042: '已通过的申请不能驳回。要撤销请到「名单管理」里删掉那条名册项。',
|
||||
40037: '该学生申请次数已达上限(每任务 2 次)。',
|
||||
40038: '该学生已有一条待审申请。',
|
||||
40039: '该证件号已在名册中 —— 学生只是姓名打错了,让他回去重新核验即可。'
|
||||
}
|
||||
Reference in New Issue
Block a user