|
|
|
|
@ -1,597 +0,0 @@
|
|
|
|
|
import type {
|
|
|
|
|
AdminMenuNode,
|
|
|
|
|
CompetitionPayload,
|
|
|
|
|
CompetitionRow,
|
|
|
|
|
CompetitionTrackRow,
|
|
|
|
|
FormSchemaPayload,
|
|
|
|
|
FormSchemaRow,
|
|
|
|
|
Paginated,
|
|
|
|
|
ReviewerPayload,
|
|
|
|
|
ReviewerRow,
|
|
|
|
|
ReviewerScopePayload,
|
|
|
|
|
ReviewerScopeRow,
|
|
|
|
|
} from './types'
|
|
|
|
|
|
|
|
|
|
let nextCompetitionId = 100
|
|
|
|
|
let nextTrackId = 1000
|
|
|
|
|
|
|
|
|
|
/** 与后端 GET /me/menus 结构一致 */
|
|
|
|
|
export const MOCK_ADMIN_MENUS: AdminMenuNode[] = [
|
|
|
|
|
{ section: '赛事中心' },
|
|
|
|
|
{
|
|
|
|
|
name: 'admin-competitions-list',
|
|
|
|
|
path: 'competitions',
|
|
|
|
|
title: '赛事列表',
|
|
|
|
|
permissionCode: 'competition.read',
|
|
|
|
|
},
|
|
|
|
|
{ section: '评审管理' },
|
|
|
|
|
{
|
|
|
|
|
name: 'admin-reviewers-list',
|
|
|
|
|
path: 'review/reviewers',
|
|
|
|
|
title: '评审员管理',
|
|
|
|
|
permissionCode: 'reviewer.manage',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'admin-review-portal',
|
|
|
|
|
path: 'review/portal',
|
|
|
|
|
title: '评审端入口',
|
|
|
|
|
permissionCode: 'reviewer.manage',
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
let mockCompetitions: CompetitionRow[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
slug: '新消费大赛-演示',
|
|
|
|
|
name: '演示赛事 2026',
|
|
|
|
|
description: '模拟数据',
|
|
|
|
|
status: 'draft',
|
|
|
|
|
published: false,
|
|
|
|
|
signup_open_at: null,
|
|
|
|
|
signup_close_at: null,
|
|
|
|
|
branding_json: null,
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const mockTracksByCompetition = new Map<number, CompetitionTrackRow[]>()
|
|
|
|
|
mockTracksByCompetition.set(1, [
|
|
|
|
|
{
|
|
|
|
|
id: 10,
|
|
|
|
|
competition_id: 1,
|
|
|
|
|
track_code: 'xfc',
|
|
|
|
|
title: '特色消费',
|
|
|
|
|
description: '',
|
|
|
|
|
sort: 10,
|
|
|
|
|
is_enabled: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 11,
|
|
|
|
|
competition_id: 1,
|
|
|
|
|
track_code: 'jkxf',
|
|
|
|
|
title: '健康消费',
|
|
|
|
|
description: '',
|
|
|
|
|
sort: 20,
|
|
|
|
|
is_enabled: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 12,
|
|
|
|
|
competition_id: 1,
|
|
|
|
|
track_code: 'wl',
|
|
|
|
|
title: '商文旅融合',
|
|
|
|
|
description: '',
|
|
|
|
|
sort: 30,
|
|
|
|
|
is_enabled: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 13,
|
|
|
|
|
competition_id: 1,
|
|
|
|
|
track_code: 'ai',
|
|
|
|
|
title: '消费+人工智能',
|
|
|
|
|
description: '',
|
|
|
|
|
sort: 40,
|
|
|
|
|
is_enabled: true,
|
|
|
|
|
},
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
export function mockListCompetitions(): CompetitionRow[] {
|
|
|
|
|
return [...mockCompetitions].sort((a, b) => b.id - a.id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockFindCompetition(id: number): CompetitionRow | undefined {
|
|
|
|
|
return mockCompetitions.find((c) => c.id === id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockUpsertCompetition(payload: CompetitionPayload & { id?: number }): CompetitionRow {
|
|
|
|
|
if (payload.id) {
|
|
|
|
|
const existing = mockFindCompetition(payload.id)
|
|
|
|
|
if (!existing) {
|
|
|
|
|
throw new Error('赛事不存在')
|
|
|
|
|
}
|
|
|
|
|
const merged: CompetitionRow = {
|
|
|
|
|
...existing,
|
|
|
|
|
...payload,
|
|
|
|
|
id: payload.id,
|
|
|
|
|
branding_json: payload.branding_json ?? existing.branding_json ?? null,
|
|
|
|
|
}
|
|
|
|
|
mockCompetitions = mockCompetitions.map((c) => (c.id === payload.id ? merged : c))
|
|
|
|
|
return merged
|
|
|
|
|
}
|
|
|
|
|
const id = nextCompetitionId++
|
|
|
|
|
const created: CompetitionRow = {
|
|
|
|
|
id,
|
|
|
|
|
slug: payload.slug,
|
|
|
|
|
name: payload.name,
|
|
|
|
|
description: payload.description,
|
|
|
|
|
status: payload.status,
|
|
|
|
|
published: payload.published,
|
|
|
|
|
signup_open_at: payload.signup_open_at,
|
|
|
|
|
signup_close_at: payload.signup_close_at,
|
|
|
|
|
branding_json: payload.branding_json ?? null,
|
|
|
|
|
}
|
|
|
|
|
mockCompetitions = [created, ...mockCompetitions]
|
|
|
|
|
mockTracksByCompetition.set(id, [])
|
|
|
|
|
return created
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockListTracks(competitionId: number): CompetitionTrackRow[] {
|
|
|
|
|
return [...(mockTracksByCompetition.get(competitionId) ?? [])].sort((a, b) => a.sort - b.sort)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockUpsertTrack(
|
|
|
|
|
competitionId: number,
|
|
|
|
|
payload: Omit<CompetitionTrackRow, 'id' | 'competition_id'> & { id?: number },
|
|
|
|
|
): CompetitionTrackRow {
|
|
|
|
|
const list = mockTracksByCompetition.get(competitionId) ?? []
|
|
|
|
|
if (payload.id) {
|
|
|
|
|
const next = list.map((t) =>
|
|
|
|
|
t.id === payload.id ? { ...t, ...payload, id: payload.id, competition_id: competitionId } : t,
|
|
|
|
|
)
|
|
|
|
|
mockTracksByCompetition.set(competitionId, next)
|
|
|
|
|
return next.find((t) => t.id === payload.id)!
|
|
|
|
|
}
|
|
|
|
|
const id = nextTrackId++
|
|
|
|
|
const row: CompetitionTrackRow = {
|
|
|
|
|
id,
|
|
|
|
|
competition_id: competitionId,
|
|
|
|
|
track_code: payload.track_code,
|
|
|
|
|
title: payload.title,
|
|
|
|
|
description: payload.description ?? null,
|
|
|
|
|
sort: payload.sort,
|
|
|
|
|
is_enabled: payload.is_enabled,
|
|
|
|
|
}
|
|
|
|
|
mockTracksByCompetition.set(competitionId, [...list, row])
|
|
|
|
|
return row
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockDeleteTrack(competitionId: number, trackId: number): void {
|
|
|
|
|
const list = mockTracksByCompetition.get(competitionId) ?? []
|
|
|
|
|
mockTracksByCompetition.set(
|
|
|
|
|
competitionId,
|
|
|
|
|
list.filter((t) => t.id !== trackId),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let nextFormSchemaId = 20000
|
|
|
|
|
|
|
|
|
|
/** 内存中的表单定义(Mock 用) */
|
|
|
|
|
type MockFormSchemaStored = FormSchemaRow & { schema_json: unknown[] }
|
|
|
|
|
|
|
|
|
|
const mockFormSchemasByCompetition = new Map<number, MockFormSchemaStored[]>()
|
|
|
|
|
|
|
|
|
|
function mockExtractFieldLabels(schemaJson: unknown[]): string[] {
|
|
|
|
|
const labels: string[] = []
|
|
|
|
|
for (const item of schemaJson) {
|
|
|
|
|
if (item !== null && typeof item === 'object' && 'label' in item) {
|
|
|
|
|
const lb = String((item as { label?: string }).label || '')
|
|
|
|
|
if (lb) labels.push(lb)
|
|
|
|
|
}
|
|
|
|
|
if (labels.length >= 12) break
|
|
|
|
|
}
|
|
|
|
|
return labels
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mockToBrief(s: MockFormSchemaStored, c: CompetitionRow): FormSchemaRow {
|
|
|
|
|
return {
|
|
|
|
|
id: s.id,
|
|
|
|
|
competition_id: s.competition_id,
|
|
|
|
|
purpose: s.purpose,
|
|
|
|
|
name: s.name,
|
|
|
|
|
version: s.version,
|
|
|
|
|
is_published: s.is_published,
|
|
|
|
|
is_current_signup: (c.form_schema_id ?? null) === s.id,
|
|
|
|
|
is_current_review: (c.review_form_schema_id ?? null) === s.id,
|
|
|
|
|
field_labels: mockExtractFieldLabels(Array.isArray(s.schema_json) ? s.schema_json : []),
|
|
|
|
|
created_at: s.created_at,
|
|
|
|
|
updated_at: s.updated_at,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockListFormSchemas(competitionId: number, purpose?: 'signup' | 'review'): FormSchemaRow[] {
|
|
|
|
|
const c = mockFindCompetition(competitionId)
|
|
|
|
|
if (!c) return []
|
|
|
|
|
let list = [...(mockFormSchemasByCompetition.get(competitionId) ?? [])]
|
|
|
|
|
if (purpose) {
|
|
|
|
|
list = list.filter((s) => s.purpose === purpose)
|
|
|
|
|
}
|
|
|
|
|
return list
|
|
|
|
|
.sort((a, b) => b.version - a.version || b.id - a.id)
|
|
|
|
|
.map((s) => mockToBrief(s, c))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockGetFormSchema(competitionId: number, schemaId: number): FormSchemaRow {
|
|
|
|
|
const c = mockFindCompetition(competitionId)
|
|
|
|
|
if (!c) throw new Error('赛事不存在')
|
|
|
|
|
const list = mockFormSchemasByCompetition.get(competitionId) ?? []
|
|
|
|
|
const s = list.find((x) => x.id === schemaId)
|
|
|
|
|
if (!s) throw new Error('表单版本不存在')
|
|
|
|
|
return { ...mockToBrief(s, c), schema_json: s.schema_json }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockCreateFormSchema(competitionId: number, payload: FormSchemaPayload): FormSchemaRow {
|
|
|
|
|
const c = mockFindCompetition(competitionId)
|
|
|
|
|
if (!c) throw new Error('赛事不存在')
|
|
|
|
|
const list = mockFormSchemasByCompetition.get(competitionId) ?? []
|
|
|
|
|
const nextVersion =
|
|
|
|
|
Math.max(0, ...list.filter((x) => x.purpose === payload.purpose).map((x) => x.version)) + 1
|
|
|
|
|
const now = new Date().toISOString()
|
|
|
|
|
const id = nextFormSchemaId++
|
|
|
|
|
const row: MockFormSchemaStored = {
|
|
|
|
|
id,
|
|
|
|
|
competition_id: competitionId,
|
|
|
|
|
purpose: payload.purpose,
|
|
|
|
|
name: payload.name,
|
|
|
|
|
version: nextVersion,
|
|
|
|
|
is_published: payload.is_published ?? false,
|
|
|
|
|
is_current_signup: false,
|
|
|
|
|
is_current_review: false,
|
|
|
|
|
schema_json: Array.isArray(payload.schema_json) ? payload.schema_json : [],
|
|
|
|
|
created_at: now,
|
|
|
|
|
updated_at: now,
|
|
|
|
|
}
|
|
|
|
|
mockFormSchemasByCompetition.set(competitionId, [...list, row])
|
|
|
|
|
return { ...mockToBrief(row, c), schema_json: row.schema_json }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockUpdateFormSchema(
|
|
|
|
|
competitionId: number,
|
|
|
|
|
schemaId: number,
|
|
|
|
|
payload: Partial<{ name: string; schema_json: unknown[]; is_published: boolean }>,
|
|
|
|
|
): FormSchemaRow {
|
|
|
|
|
const c = mockFindCompetition(competitionId)
|
|
|
|
|
if (!c) throw new Error('赛事不存在')
|
|
|
|
|
const list = mockFormSchemasByCompetition.get(competitionId) ?? []
|
|
|
|
|
const idx = list.findIndex((x) => x.id === schemaId)
|
|
|
|
|
if (idx < 0) throw new Error('表单版本不存在')
|
|
|
|
|
const prev = list[idx]
|
|
|
|
|
const now = new Date().toISOString()
|
|
|
|
|
const next: MockFormSchemaStored = {
|
|
|
|
|
...prev,
|
|
|
|
|
name: payload.name !== undefined ? payload.name : prev.name,
|
|
|
|
|
schema_json:
|
|
|
|
|
payload.schema_json !== undefined
|
|
|
|
|
? payload.schema_json
|
|
|
|
|
: prev.schema_json,
|
|
|
|
|
is_published: payload.is_published !== undefined ? payload.is_published : prev.is_published,
|
|
|
|
|
updated_at: now,
|
|
|
|
|
}
|
|
|
|
|
const copy = [...list]
|
|
|
|
|
copy[idx] = next
|
|
|
|
|
mockFormSchemasByCompetition.set(competitionId, copy)
|
|
|
|
|
return { ...mockToBrief(next, c), schema_json: next.schema_json }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockDeleteFormSchema(competitionId: number, schemaId: number): void {
|
|
|
|
|
const c = mockFindCompetition(competitionId)
|
|
|
|
|
if (!c) throw new Error('赛事不存在')
|
|
|
|
|
if ((c.form_schema_id ?? null) === schemaId || (c.review_form_schema_id ?? null) === schemaId) {
|
|
|
|
|
throw new Error('该版本正被本场赛事使用,请先更换绑定后再删除。')
|
|
|
|
|
}
|
|
|
|
|
const list = mockFormSchemasByCompetition.get(competitionId) ?? []
|
|
|
|
|
mockFormSchemasByCompetition.set(
|
|
|
|
|
competitionId,
|
|
|
|
|
list.filter((x) => x.id !== schemaId),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let nextReviewerId = 500
|
|
|
|
|
let nextReviewerScopeId = 8000
|
|
|
|
|
|
|
|
|
|
/** Mock 下列表「密码」列展示的明文(与原型演示一致;仅内存,刷新即失) */
|
|
|
|
|
const mockReviewerPasswordPlain = new Map<number, string>()
|
|
|
|
|
|
|
|
|
|
let mockReviewers: ReviewerRow[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 401,
|
|
|
|
|
mobile: '13800138001',
|
|
|
|
|
username: 'demo_review',
|
|
|
|
|
name: '演示评委',
|
|
|
|
|
status: 'active',
|
|
|
|
|
password_set: true,
|
|
|
|
|
reviewer_scopes_count: 0,
|
|
|
|
|
last_login_at: null,
|
|
|
|
|
created_at: new Date().toISOString(),
|
|
|
|
|
updated_at: new Date().toISOString(),
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
mockReviewerPasswordPlain.set(401, 'Demo2026!')
|
|
|
|
|
|
|
|
|
|
const mockReviewerScopesRows = new Map<number, ReviewerScopeRow[]>()
|
|
|
|
|
|
|
|
|
|
function mockPwdDisplay(id: number, row: ReviewerRow): string {
|
|
|
|
|
const plain = mockReviewerPasswordPlain.get(id)
|
|
|
|
|
if (plain) return plain
|
|
|
|
|
if (row.password_set) return '········'
|
|
|
|
|
return '—'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mockRefreshReviewerCounts() {
|
|
|
|
|
const counts = new Map<number, number>()
|
|
|
|
|
for (const list of mockReviewerScopesRows.values()) {
|
|
|
|
|
for (const row of list) {
|
|
|
|
|
counts.set(row.reviewer_id, (counts.get(row.reviewer_id) ?? 0) + 1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mockReviewers = mockReviewers.map((r) => ({
|
|
|
|
|
...r,
|
|
|
|
|
reviewer_scopes_count: counts.get(r.id) ?? 0,
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockReviewerScopeTrackCounts(competitionId: number): Record<string, number> {
|
|
|
|
|
mockRefreshReviewerCounts()
|
|
|
|
|
const list = mockReviewerScopesRows.get(competitionId) ?? []
|
|
|
|
|
const out: Record<string, number> = {}
|
|
|
|
|
for (const row of list) {
|
|
|
|
|
const k = row.track_code
|
|
|
|
|
out[k] = (out[k] ?? 0) + 1
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockListReviewers(params: {
|
|
|
|
|
page?: number
|
|
|
|
|
per_page?: number
|
|
|
|
|
q?: string
|
|
|
|
|
status?: 'active' | 'disabled' | ''
|
|
|
|
|
}): Paginated<ReviewerRow> {
|
|
|
|
|
mockRefreshReviewerCounts()
|
|
|
|
|
let filtered = [...mockReviewers].sort((a, b) => b.id - a.id)
|
|
|
|
|
const q = params.q?.trim()
|
|
|
|
|
if (q) {
|
|
|
|
|
filtered = filtered.filter(
|
|
|
|
|
(r) =>
|
|
|
|
|
(r.mobile?.includes(q) ?? false) ||
|
|
|
|
|
r.name.includes(q) ||
|
|
|
|
|
(r.username?.includes(q) ?? false),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (params.status === 'active' || params.status === 'disabled') {
|
|
|
|
|
filtered = filtered.filter((r) => r.status === params.status)
|
|
|
|
|
}
|
|
|
|
|
const page = params.page ?? 1
|
|
|
|
|
const perPage = params.per_page ?? 15
|
|
|
|
|
const start = (page - 1) * perPage
|
|
|
|
|
const slice = filtered.slice(start, start + perPage)
|
|
|
|
|
return {
|
|
|
|
|
data: slice,
|
|
|
|
|
meta: {
|
|
|
|
|
current_page: page,
|
|
|
|
|
last_page: Math.max(1, Math.ceil(filtered.length / perPage)),
|
|
|
|
|
per_page: perPage,
|
|
|
|
|
total: filtered.length,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockCreateReviewer(payload: ReviewerPayload): ReviewerRow {
|
|
|
|
|
if (payload.username == null || String(payload.username).trim() === '') {
|
|
|
|
|
throw new Error('请填写账户')
|
|
|
|
|
}
|
|
|
|
|
const username = String(payload.username).trim()
|
|
|
|
|
if (mockReviewers.some((r) => r.username === username)) {
|
|
|
|
|
throw new Error('账户已存在')
|
|
|
|
|
}
|
|
|
|
|
const mobTrim = payload.mobile?.trim() ?? ''
|
|
|
|
|
if (mobTrim !== '' && mockReviewers.some((r) => r.mobile === mobTrim)) {
|
|
|
|
|
throw new Error('手机号已存在')
|
|
|
|
|
}
|
|
|
|
|
const pw = payload.password?.trim()
|
|
|
|
|
if (!pw) throw new Error('请填写密码')
|
|
|
|
|
const now = new Date().toISOString()
|
|
|
|
|
const row: ReviewerRow = {
|
|
|
|
|
id: nextReviewerId++,
|
|
|
|
|
mobile: mobTrim !== '' ? mobTrim : null,
|
|
|
|
|
username,
|
|
|
|
|
name: payload.name,
|
|
|
|
|
status: payload.status ?? 'active',
|
|
|
|
|
password_set: true,
|
|
|
|
|
reviewer_scopes_count: 0,
|
|
|
|
|
last_login_at: null,
|
|
|
|
|
created_at: now,
|
|
|
|
|
updated_at: now,
|
|
|
|
|
}
|
|
|
|
|
mockReviewerPasswordPlain.set(row.id, pw)
|
|
|
|
|
mockReviewers = [row, ...mockReviewers]
|
|
|
|
|
return row
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockUpdateReviewer(id: number, payload: Partial<ReviewerPayload>): ReviewerRow {
|
|
|
|
|
mockRefreshReviewerCounts()
|
|
|
|
|
const idx = mockReviewers.findIndex((r) => r.id === id)
|
|
|
|
|
if (idx < 0) throw new Error('评审员不存在')
|
|
|
|
|
const prev = mockReviewers[idx]
|
|
|
|
|
const uname = payload.username?.trim()
|
|
|
|
|
if (
|
|
|
|
|
uname !== undefined &&
|
|
|
|
|
uname !== '' &&
|
|
|
|
|
uname !== prev.username &&
|
|
|
|
|
mockReviewers.some((r) => r.username === uname && r.id !== id)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error('账户已存在')
|
|
|
|
|
}
|
|
|
|
|
const mob = payload.mobile?.trim()
|
|
|
|
|
if (
|
|
|
|
|
mob !== undefined &&
|
|
|
|
|
mob !== '' &&
|
|
|
|
|
mockReviewers.some((r) => r.mobile === mob && r.id !== id)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error('手机号已存在')
|
|
|
|
|
}
|
|
|
|
|
const pw = payload.password?.trim()
|
|
|
|
|
if (pw) {
|
|
|
|
|
mockReviewerPasswordPlain.set(id, pw)
|
|
|
|
|
}
|
|
|
|
|
const next: ReviewerRow = {
|
|
|
|
|
...prev,
|
|
|
|
|
...(uname !== undefined && uname !== '' ? { username: uname } : {}),
|
|
|
|
|
...(mob !== undefined ? { mobile: mob || null } : {}),
|
|
|
|
|
...(payload.name !== undefined ? { name: payload.name } : {}),
|
|
|
|
|
...(payload.status !== undefined ? { status: payload.status } : {}),
|
|
|
|
|
id,
|
|
|
|
|
password_set: prev.password_set || Boolean(pw),
|
|
|
|
|
updated_at: new Date().toISOString(),
|
|
|
|
|
}
|
|
|
|
|
mockReviewers = mockReviewers.map((r) => (r.id === id ? next : r))
|
|
|
|
|
mockRefreshReviewerCounts()
|
|
|
|
|
return mockReviewers.find((x) => x.id === id)!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockDeleteReviewer(id: number): void {
|
|
|
|
|
const exists = mockReviewers.some((r) => r.id === id)
|
|
|
|
|
if (!exists) throw new Error('评审员不存在')
|
|
|
|
|
mockReviewerPasswordPlain.delete(id)
|
|
|
|
|
mockReviewers = mockReviewers.filter((r) => r.id !== id)
|
|
|
|
|
for (const [cid, list] of mockReviewerScopesRows) {
|
|
|
|
|
mockReviewerScopesRows.set(
|
|
|
|
|
cid,
|
|
|
|
|
list.filter((s) => s.reviewer_id !== id),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
mockRefreshReviewerCounts()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockListReviewerScopes(params: {
|
|
|
|
|
competition_id: number
|
|
|
|
|
reviewer_id?: number
|
|
|
|
|
track_code?: string
|
|
|
|
|
page?: number
|
|
|
|
|
per_page?: number
|
|
|
|
|
}): Paginated<ReviewerScopeRow> {
|
|
|
|
|
const cid = params.competition_id
|
|
|
|
|
const list = [...(mockReviewerScopesRows.get(cid) ?? [])].sort((a, b) => b.id - a.id)
|
|
|
|
|
let filtered = list
|
|
|
|
|
if (params.reviewer_id) {
|
|
|
|
|
filtered = filtered.filter((s) => s.reviewer_id === params.reviewer_id)
|
|
|
|
|
}
|
|
|
|
|
if (params.track_code) {
|
|
|
|
|
filtered = filtered.filter((s) => s.track_code === params.track_code)
|
|
|
|
|
}
|
|
|
|
|
const page = params.page ?? 1
|
|
|
|
|
const perPage = params.per_page ?? 100
|
|
|
|
|
const start = (page - 1) * perPage
|
|
|
|
|
const slice = filtered.slice(start, start + perPage)
|
|
|
|
|
|
|
|
|
|
const c = mockFindCompetition(cid)
|
|
|
|
|
|
|
|
|
|
function enrich(row: ReviewerScopeRow): ReviewerScopeRow {
|
|
|
|
|
const reviewer = mockReviewers.find((x) => x.id === row.reviewer_id)
|
|
|
|
|
const tracks = mockTracksByCompetition.get(cid) ?? []
|
|
|
|
|
const track = tracks.find((t) => t.track_code === row.track_code)
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
|
|
|
|
track_title: track?.title ?? null,
|
|
|
|
|
reviewer: reviewer
|
|
|
|
|
? {
|
|
|
|
|
id: reviewer.id,
|
|
|
|
|
mobile: reviewer.mobile ?? null,
|
|
|
|
|
username: reviewer.username ?? null,
|
|
|
|
|
name: reviewer.name,
|
|
|
|
|
status: reviewer.status,
|
|
|
|
|
password_display: mockPwdDisplay(reviewer.id, reviewer),
|
|
|
|
|
}
|
|
|
|
|
: null,
|
|
|
|
|
competition: c
|
|
|
|
|
? {
|
|
|
|
|
id: c.id,
|
|
|
|
|
slug: c.slug,
|
|
|
|
|
name: c.name,
|
|
|
|
|
}
|
|
|
|
|
: null,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
data: slice.map(enrich),
|
|
|
|
|
meta: {
|
|
|
|
|
current_page: page,
|
|
|
|
|
last_page: Math.max(1, Math.ceil(filtered.length / perPage)),
|
|
|
|
|
per_page: perPage,
|
|
|
|
|
total: filtered.length,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockCreateReviewerScope(payload: ReviewerScopePayload): ReviewerScopeRow {
|
|
|
|
|
const reviewer = mockReviewers.find((r) => r.id === payload.reviewer_id)
|
|
|
|
|
if (!reviewer) throw new Error('评审员不存在')
|
|
|
|
|
|
|
|
|
|
const c = mockFindCompetition(payload.competition_id)
|
|
|
|
|
if (!c) throw new Error('赛事不存在')
|
|
|
|
|
|
|
|
|
|
const tracks = mockTracksByCompetition.get(payload.competition_id) ?? []
|
|
|
|
|
const track = tracks.find((t) => t.track_code === payload.track_code)
|
|
|
|
|
if (!track || !track.is_enabled) {
|
|
|
|
|
throw new Error('赛道编码无效或未启用')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const list = mockReviewerScopesRows.get(payload.competition_id) ?? []
|
|
|
|
|
if (
|
|
|
|
|
list.some(
|
|
|
|
|
(s) =>
|
|
|
|
|
s.reviewer_id === payload.reviewer_id &&
|
|
|
|
|
s.track_code === payload.track_code &&
|
|
|
|
|
s.competition_id === payload.competition_id,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error('该评审员在本场对该赛道已有范围配置')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const row: ReviewerScopeRow = {
|
|
|
|
|
id: nextReviewerScopeId++,
|
|
|
|
|
reviewer_id: payload.reviewer_id,
|
|
|
|
|
competition_id: payload.competition_id,
|
|
|
|
|
track_code: payload.track_code,
|
|
|
|
|
created_at: new Date().toISOString(),
|
|
|
|
|
track_title: track.title,
|
|
|
|
|
reviewer: {
|
|
|
|
|
id: reviewer.id,
|
|
|
|
|
mobile: reviewer.mobile ?? null,
|
|
|
|
|
username: reviewer.username ?? null,
|
|
|
|
|
name: reviewer.name,
|
|
|
|
|
status: reviewer.status,
|
|
|
|
|
password_display: mockPwdDisplay(reviewer.id, reviewer),
|
|
|
|
|
},
|
|
|
|
|
competition: {
|
|
|
|
|
id: c.id,
|
|
|
|
|
slug: c.slug,
|
|
|
|
|
name: c.name,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
mockReviewerScopesRows.set(payload.competition_id, [row, ...list])
|
|
|
|
|
mockRefreshReviewerCounts()
|
|
|
|
|
return row
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockDeleteReviewerScope(id: number): void {
|
|
|
|
|
let found = false
|
|
|
|
|
for (const [cid, list] of mockReviewerScopesRows) {
|
|
|
|
|
const next = list.filter((s) => s.id !== id)
|
|
|
|
|
if (next.length !== list.length) {
|
|
|
|
|
mockReviewerScopesRows.set(cid, next)
|
|
|
|
|
found = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!found) throw new Error('记录不存在')
|
|
|
|
|
mockRefreshReviewerCounts()
|
|
|
|
|
}
|