|
|
|
|
@ -0,0 +1,112 @@
|
|
|
|
|
/**
|
|
|
|
|
* 报名表 schema 字段注册表(运行时由管理端 API 同步,与 Application::$fillable 对齐)。
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export const SIGNUP_COMMITMENT_FIELD_KEY = 'commitment_accepted' as const
|
|
|
|
|
export const SIGNUP_COMMITMENT_TYPE = 'commitment_promise' as const
|
|
|
|
|
|
|
|
|
|
export const SIGNUP_FILE_FIELD_KEYS = ['plan', 'supporting'] as const
|
|
|
|
|
|
|
|
|
|
/** 接口 meta.signup_field_registry / signup_field_registry 结构 */
|
|
|
|
|
export interface SignupFieldRegistryManifest {
|
|
|
|
|
scalar_field_keys: string[]
|
|
|
|
|
file_field_keys: string[]
|
|
|
|
|
commitment_field_key: string
|
|
|
|
|
scalar_type_hints: Record<string, string[]>
|
|
|
|
|
generic_scalar_types: string[]
|
|
|
|
|
all_supported_form_keys: string[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 未拉取接口前的兜底(与当前默认 fillable 一致) */
|
|
|
|
|
const FALLBACK_MANIFEST: SignupFieldRegistryManifest = {
|
|
|
|
|
scalar_field_keys: [
|
|
|
|
|
'player_name',
|
|
|
|
|
'school',
|
|
|
|
|
'degree',
|
|
|
|
|
'contact_email',
|
|
|
|
|
'contact_mobile',
|
|
|
|
|
'entry_group',
|
|
|
|
|
'company_name',
|
|
|
|
|
'project_name',
|
|
|
|
|
'track',
|
|
|
|
|
'location_country',
|
|
|
|
|
'location_province',
|
|
|
|
|
'location_city',
|
|
|
|
|
'oversea_country',
|
|
|
|
|
'intro',
|
|
|
|
|
'recommend',
|
|
|
|
|
],
|
|
|
|
|
file_field_keys: [...SIGNUP_FILE_FIELD_KEYS],
|
|
|
|
|
commitment_field_key: SIGNUP_COMMITMENT_FIELD_KEY,
|
|
|
|
|
scalar_type_hints: {
|
|
|
|
|
player_name: ['text'],
|
|
|
|
|
school: ['text'],
|
|
|
|
|
degree: ['select'],
|
|
|
|
|
contact_email: ['email', 'text'],
|
|
|
|
|
contact_mobile: ['tel', 'text'],
|
|
|
|
|
entry_group: ['select'],
|
|
|
|
|
company_name: ['text'],
|
|
|
|
|
project_name: ['text'],
|
|
|
|
|
track: ['select'],
|
|
|
|
|
location_country: ['select'],
|
|
|
|
|
location_province: ['select', 'text'],
|
|
|
|
|
location_city: ['select', 'text'],
|
|
|
|
|
oversea_country: ['text'],
|
|
|
|
|
intro: ['textarea', 'text'],
|
|
|
|
|
recommend: ['text', 'textarea'],
|
|
|
|
|
},
|
|
|
|
|
generic_scalar_types: ['text', 'textarea', 'email', 'tel', 'select', 'number', 'date'],
|
|
|
|
|
all_supported_form_keys: [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FALLBACK_MANIFEST.all_supported_form_keys = [
|
|
|
|
|
...FALLBACK_MANIFEST.scalar_field_keys,
|
|
|
|
|
...FALLBACK_MANIFEST.file_field_keys,
|
|
|
|
|
FALLBACK_MANIFEST.commitment_field_key,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
let activeManifest: SignupFieldRegistryManifest = FALLBACK_MANIFEST
|
|
|
|
|
|
|
|
|
|
export function setSignupFieldRegistry(manifest: SignupFieldRegistryManifest | null | undefined): void {
|
|
|
|
|
if (manifest && Array.isArray(manifest.scalar_field_keys)) {
|
|
|
|
|
activeManifest = manifest
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getSignupFieldRegistry(): SignupFieldRegistryManifest {
|
|
|
|
|
return activeManifest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function allowedTypesForKey(key: string): string[] {
|
|
|
|
|
const m = activeManifest
|
|
|
|
|
return m.scalar_type_hints[key] ?? m.generic_scalar_types
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function validateSignupSchemaFieldKey(key: string, type: string): string | null {
|
|
|
|
|
const k = key.trim()
|
|
|
|
|
const t = type.trim()
|
|
|
|
|
if (!k) return '存在未填写「字段 key」的项(须与 applications 表列名一致)'
|
|
|
|
|
|
|
|
|
|
const m = activeManifest
|
|
|
|
|
|
|
|
|
|
if (k === m.commitment_field_key) {
|
|
|
|
|
return t === 'checkbox' || t === SIGNUP_COMMITMENT_TYPE
|
|
|
|
|
? null
|
|
|
|
|
: '参赛承诺书字段须为 checkbox 类型'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m.file_field_keys.includes(k)) {
|
|
|
|
|
return t === 'file' ? null : `附件字段「${k}」须为 file 类型`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m.scalar_field_keys.includes(k)) {
|
|
|
|
|
return `字段 key「${k}」不是 applications 表可填列,请使用:${m.all_supported_form_keys.join('、')}(新增列请先做数据库迁移并加入 Application.$fillable)`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const allowed = allowedTypesForKey(k)
|
|
|
|
|
if (!t || !allowed.includes(t)) {
|
|
|
|
|
return `字段「${k}」类型须为:${allowed.join(' / ')}`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
}
|