main
lion 5 months ago
parent 8b7567a500
commit f3cf04ad19

@ -45,6 +45,8 @@ function onTypeChange(element: FormSchemaEditorItem) {
if (element.type === SIGNUP_COMMITMENT_TYPE) { if (element.type === SIGNUP_COMMITMENT_TYPE) {
element.key = 'commitment_accepted' element.key = 'commitment_accepted'
element.options = [] element.options = []
element.requiredWhenField = ''
element.requiredWhenValuesLines = ''
if (!element.label.trim()) { if (!element.label.trim()) {
element.label = '本人已阅读并同意《参赛承诺书》的全部内容' element.label = '本人已阅读并同意《参赛承诺书》的全部内容'
} }
@ -134,6 +136,23 @@ function applyOptionsText(element: FormSchemaEditorItem, raw: string) {
@input="(v: string) => applyOptionsText(element, v)" @input="(v: string) => applyOptionsText(element, v)"
/> />
</el-col> </el-col>
<el-col v-if="purpose === 'signup' && element.type !== SIGNUP_COMMITMENT_TYPE" :span="24">
<div class="field-label">条件必填(可选)</div>
<p class="cond-hint">
当下方「依赖字段」取值为列表中任一值时,本字段视为必填(示例:企业名称依赖「参赛组别」为「创业组」)。
</p>
<el-input
v-model="element.requiredWhenField"
class="mb-2"
placeholder="依赖字段 key,如 entry_group"
/>
<el-input
v-model="element.requiredWhenValuesLines"
type="textarea"
:rows="3"
placeholder="依赖取值,每行一个,例如:&#10;创业组"
/>
</el-col>
<el-col :span="24" class="field-actions"> <el-col :span="24" class="field-actions">
<el-switch v-model="element.required" active-text="必填" /> <el-switch v-model="element.required" active-text="必填" />
<el-button size="small" @click="duplicateField(index)">复制</el-button> <el-button size="small" @click="duplicateField(index)">复制</el-button>
@ -169,6 +188,13 @@ function applyOptionsText(element: FormSchemaEditorItem, raw: string) {
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
.cond-hint {
margin: 0 0 8px;
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.5;
}
.schema-draggable-list { .schema-draggable-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

@ -10,6 +10,8 @@ export interface SignupFormSchemaField {
placeholder?: string placeholder?: string
help?: string help?: string
options?: { label: string; value: string }[] options?: { label: string; value: string }[]
/** 当依赖字段取值命中列表时,本字段视为必填(与企业名称依赖参赛组别一致) */
required_when?: { field: string; values: string[] }
} }
/** /**
@ -39,12 +41,24 @@ export const DEFAULT_SIGNUP_FORM_SCHEMA: SignupFormSchemaField[] = [
required: true, required: true,
placeholder: '11 位中国大陆手机号', placeholder: '11 位中国大陆手机号',
}, },
{
key: 'entry_group',
type: 'select',
label: '参赛组别',
required: true,
options: [
{ label: '创新组', value: '创新组' },
{ label: '创业组', value: '创业组' },
],
help: '选择「创业组」时须填写企业名称',
},
{ {
key: 'company_name', key: 'company_name',
type: 'text', type: 'text',
label: '企业名称', label: '企业名称',
required: false, required: false,
placeholder: '如已注册企业,请填写', required_when: { field: 'entry_group', values: ['创业组'] },
placeholder: '创业组为必填;创新组选填',
}, },
{ key: 'project_name', type: 'text', label: '项目名称', required: true }, { key: 'project_name', type: 'text', label: '项目名称', required: true },
{ {
@ -142,6 +156,19 @@ export function normalizeSignupSchema(raw: unknown): SignupFormSchemaField[] {
const helpRaw = o.help const helpRaw = o.help
const help = const help =
helpRaw != null && String(helpRaw).trim() !== '' ? String(helpRaw).trim() : undefined helpRaw != null && String(helpRaw).trim() !== '' ? String(helpRaw).trim() : undefined
let required_when: { field: string; values: string[] } | undefined
const rw = o.required_when
if (rw != null && typeof rw === 'object' && !Array.isArray(rw)) {
const rwo = rw as Record<string, unknown>
const wf = String(rwo.field ?? '').trim()
const valsRaw = rwo.values
const values = Array.isArray(valsRaw)
? valsRaw.map((x) => String(x).trim()).filter((s) => s !== '')
: []
if (wf && values.length) {
required_when = { field: wf, values }
}
}
let options: { label: string; value: string }[] | undefined let options: { label: string; value: string }[] | undefined
if (type === 'select' && Array.isArray(o.options)) { if (type === 'select' && Array.isArray(o.options)) {
options = parseSchemaOptionsArray(o.options) options = parseSchemaOptionsArray(o.options)
@ -161,7 +188,7 @@ export function normalizeSignupSchema(raw: unknown): SignupFormSchemaField[] {
} }
} }
} }
out.push({ key, type, label, required, placeholder, help, options }) out.push({ key, type, label, required, placeholder, help, options, required_when })
} }
return out.length ? out : DEFAULT_SIGNUP_FORM_SCHEMA.map((f) => ({ ...f })) return out.length ? out : DEFAULT_SIGNUP_FORM_SCHEMA.map((f) => ({ ...f }))
} }

@ -13,6 +13,10 @@ export interface FormSchemaEditorItem {
help?: string help?: string
/** select 等:{ label, value } */ /** select 等:{ label, value } */
options?: { label: string; value: string }[] options?: { label: string; value: string }[]
/** 报名表:条件必填依赖字段 key(入库为 required_when.field) */
requiredWhenField?: string
/** 报名表:依赖取值,每行一个(入库为 required_when.values) */
requiredWhenValuesLines?: string
} }
/** 入库时为 checkbox + key commitment_accepted,仅供报名表可视化 */ /** 入库时为 checkbox + key commitment_accepted,仅供报名表可视化 */
@ -55,6 +59,8 @@ export function createEmptySchemaItem(
placeholder: '', placeholder: '',
help: '', help: '',
options: [], options: [],
requiredWhenField: '',
requiredWhenValuesLines: '',
} }
} }
@ -85,6 +91,19 @@ export function schemaJsonToEditorItems(json: unknown, purpose: FormSchemaPurpos
purpose === 'signup' && rawKey === 'commitment_accepted' && rawType === 'checkbox' purpose === 'signup' && rawKey === 'commitment_accepted' && rawType === 'checkbox'
? SIGNUP_COMMITMENT_TYPE ? SIGNUP_COMMITMENT_TYPE
: rawType : rawType
let requiredWhenField = ''
let requiredWhenValuesLines = ''
if (purpose === 'signup') {
const rw = o.required_when
if (rw != null && typeof rw === 'object' && !Array.isArray(rw)) {
const rwo = rw as Record<string, unknown>
requiredWhenField = String(rwo.field ?? '').trim()
const vals = rwo.values
if (Array.isArray(vals)) {
requiredWhenValuesLines = vals.map((x) => String(x).trim()).filter(Boolean).join('\n')
}
}
}
return { return {
__uid: newUid(), __uid: newUid(),
key: rawKey, key: rawKey,
@ -94,12 +113,14 @@ export function schemaJsonToEditorItems(json: unknown, purpose: FormSchemaPurpos
placeholder: o.placeholder != null ? String(o.placeholder) : '', placeholder: o.placeholder != null ? String(o.placeholder) : '',
help: o.help != null ? String(o.help) : '', help: o.help != null ? String(o.help) : '',
options: type === 'select' ? normalizeOptions(o.options) : [], options: type === 'select' ? normalizeOptions(o.options) : [],
requiredWhenField,
requiredWhenValuesLines,
} }
}) })
} }
/** 写入接口的 schema_json(数组) */ /** 写入接口的 schema_json(数组) */
export function editorItemsToSchemaJson(items: FormSchemaEditorItem[]): unknown[] { export function editorItemsToSchemaJson(items: FormSchemaEditorItem[], purpose: FormSchemaPurpose): unknown[] {
return items.map((item) => { return items.map((item) => {
const isCommitment = item.type === SIGNUP_COMMITMENT_TYPE const isCommitment = item.type === SIGNUP_COMMITMENT_TYPE
const row: Record<string, unknown> = { const row: Record<string, unknown> = {
@ -113,11 +134,25 @@ export function editorItemsToSchemaJson(items: FormSchemaEditorItem[]): unknown[
if (item.type === 'select' && item.options?.length) { if (item.type === 'select' && item.options?.length) {
row.options = item.options.filter((o) => o.value !== '' || o.label !== '') row.options = item.options.filter((o) => o.value !== '' || o.label !== '')
} }
if (
purpose === 'signup'
&& !isCommitment
&& item.requiredWhenField?.trim()
&& item.requiredWhenValuesLines?.trim()
) {
const values = item.requiredWhenValuesLines
.split('\n')
.map((s) => s.trim())
.filter(Boolean)
if (values.length) {
row.required_when = { field: item.requiredWhenField.trim(), values }
}
}
return row return row
}) })
} }
export function validateEditorItems(items: FormSchemaEditorItem[]): string | null { export function validateEditorItems(items: FormSchemaEditorItem[], purpose: FormSchemaPurpose): string | null {
const keys = new Set<string>() const keys = new Set<string>()
for (const it of items) { for (const it of items) {
const k = it.key.trim() const k = it.key.trim()
@ -125,6 +160,14 @@ export function validateEditorItems(items: FormSchemaEditorItem[]): string | nul
if (keys.has(k)) return `字段 key「${k}」重复` if (keys.has(k)) return `字段 key「${k}」重复`
keys.add(k) keys.add(k)
if (!it.label.trim()) return `字段「${k}」缺少显示标签` if (!it.label.trim()) return `字段「${k}」缺少显示标签`
if (
purpose === 'signup'
&& it.type !== SIGNUP_COMMITMENT_TYPE
&& it.requiredWhenField?.trim()
&& !it.requiredWhenValuesLines?.trim()
) {
return `字段「${k}」填写了条件必填依赖,但未填写「当取值」列表`
}
} }
return null return null
} }

@ -271,6 +271,11 @@ function effectiveRequired(field: SignupFormSchemaField): boolean {
if (field.key === 'oversea_country') { if (field.key === 'oversea_country') {
return isOversea.value return isOversea.value
} }
const rw = field.required_when
if (rw?.field && rw.values?.length) {
const dep = String(formModel[rw.field] ?? '').trim()
if (rw.values.includes(dep)) return true
}
return field.required return field.required
} }
@ -842,6 +847,7 @@ function applyServerPayload(d: {
degree?: string degree?: string
contact_email?: string contact_email?: string
contact_mobile?: string contact_mobile?: string
entry_group?: string
company_name?: string company_name?: string
project_name?: string project_name?: string
track?: string track?: string
@ -861,6 +867,7 @@ function applyServerPayload(d: {
formModel.degree = d.degree || '' formModel.degree = d.degree || ''
formModel.contact_email = d.contact_email || '' formModel.contact_email = d.contact_email || ''
formModel.contact_mobile = d.contact_mobile || '' formModel.contact_mobile = d.contact_mobile || ''
formModel.entry_group = d.entry_group || ''
formModel.company_name = d.company_name || '' formModel.company_name = d.company_name || ''
formModel.project_name = d.project_name || '' formModel.project_name = d.project_name || ''
formModel.track = d.track || '' formModel.track = d.track || ''

@ -168,7 +168,11 @@ watch(editSchemaTab, (t) => {
}) })
function syncEditVisualToJson() { function syncEditVisualToJson() {
editSchemaJsonText.value = JSON.stringify(editorItemsToSchemaJson(editSchemaVisualItems.value), null, 2) editSchemaJsonText.value = JSON.stringify(
editorItemsToSchemaJson(editSchemaVisualItems.value, editSchemaPurpose.value),
null,
2,
)
} }
function syncEditJsonToVisual() { function syncEditJsonToVisual() {
@ -646,12 +650,12 @@ async function saveEditedSchema() {
editSchemaError.value = '' editSchemaError.value = ''
let parsed: unknown[] = [] let parsed: unknown[] = []
if (editSchemaTab.value === 'visual') { if (editSchemaTab.value === 'visual') {
const err = validateEditorItems(editSchemaVisualItems.value) const err = validateEditorItems(editSchemaVisualItems.value, editSchemaPurpose.value)
if (err) { if (err) {
editSchemaError.value = err editSchemaError.value = err
return return
} }
parsed = editorItemsToSchemaJson(editSchemaVisualItems.value) as unknown[] parsed = editorItemsToSchemaJson(editSchemaVisualItems.value, editSchemaPurpose.value) as unknown[]
} else { } else {
try { try {
const raw = JSON.parse(editSchemaJsonText.value) const raw = JSON.parse(editSchemaJsonText.value)

@ -40,6 +40,7 @@ interface DetailPayload {
degree: string degree: string
contact_email: string contact_email: string
contact_mobile: string contact_mobile: string
entry_group: string
company_name: string company_name: string
track_title: string track_title: string
track_code: string track_code: string
@ -375,6 +376,10 @@ async function submitReviewScore() {
<label class="form-label">联系电话</label> <label class="form-label">联系电话</label>
<input class="form-control" type="text" :value="detail.contact_mobile" disabled /> <input class="form-control" type="text" :value="detail.contact_mobile" disabled />
</div> </div>
<div class="col-md-4">
<label class="form-label">参赛组别</label>
<input class="form-control" type="text" :value="detail.entry_group || '—'" disabled />
</div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">企业名称</label> <label class="form-label">企业名称</label>
<input <input

Loading…
Cancel
Save