diff --git a/reading-platform-frontend/src/components/course-edit/Step4IntroLesson.vue b/reading-platform-frontend/src/components/course-edit/Step4IntroLesson.vue index 1597d66..70d1626 100644 --- a/reading-platform-frontend/src/components/course-edit/Step4IntroLesson.vue +++ b/reading-platform-frontend/src/components/course-edit/Step4IntroLesson.vue @@ -12,7 +12,10 @@ style="margin-bottom: 16px" > - 导入课用于激发幼儿兴趣,引入课程主题。建议时长5-15分钟,重点在于吸引注意力、建立学习期待。 + 导入课用于激发幼儿兴趣,引入课程主题。建议时长 5-15 分钟,重点在于吸引注意力、建立学习期待。 + + 必填项:课程名称、教学目标、教学准备、核心资源(至少 1 个)、教学环节(至少 1 个,含环节名称、内容、目标);时长:5-15 分钟。 + @@ -27,6 +30,7 @@ (null); +const configPanelRef = ref | null>(null); // 获取导入课数据 const fetchLesson = async () => { @@ -154,35 +159,12 @@ const handleLessonChange = () => { emit('change'); }; -// 验证:若配置了导入课,则教学目标、教学准备、教学过程环节必填 -const validate = () => { +// 验证:若配置了导入课,则通过 formRules 校验 +const validate = async () => { if (!lessonData.value) { return { valid: true, errors: [] as string[], warnings: ['未配置导入课'] }; } - - const errors: string[] = []; - if (!lessonData.value.objectives?.trim()) { - errors.push('请输入教学目标'); - } - if (!lessonData.value.preparation?.trim()) { - errors.push('请输入教学准备'); - } - const duration = lessonData.value.duration; - if (duration != null && (duration < 5 || duration > 15)) { - errors.push('导入课时长需在 5-15 分钟之间'); - } - const steps = lessonData.value.steps || []; - if (steps.length < 1) { - errors.push('请至少添加一个教学环节'); - } else { - steps.forEach((step, i) => { - if (!step.name?.trim()) errors.push(`第${i + 1}个环节:请填写环节名称`); - if (!step.content?.trim()) errors.push(`第${i + 1}个环节:请填写环节内容`); - if (!step.objective?.trim()) errors.push(`第${i + 1}个环节:请填写教学目标`); - }); - } - - return { valid: errors.length === 0, errors }; + return configPanelRef.value?.validate() ?? { valid: true, errors: [] as string[] }; }; // 获取保存数据 @@ -232,5 +214,13 @@ defineExpose({ border-top: 1px solid #f0f0f0; text-align: right; } + + .form-hints { + margin-top: 8px; + padding-top: 8px; + border-top: 1px dashed rgba(24, 144, 255, 0.3); + font-size: 12px; + color: #595959; + } } diff --git a/reading-platform-frontend/src/components/course-edit/Step5CollectiveLesson.vue b/reading-platform-frontend/src/components/course-edit/Step5CollectiveLesson.vue index cf70cb4..0505752 100644 --- a/reading-platform-frontend/src/components/course-edit/Step5CollectiveLesson.vue +++ b/reading-platform-frontend/src/components/course-edit/Step5CollectiveLesson.vue @@ -12,7 +12,10 @@ style="margin-bottom: 16px" > - 集体课是课程包的核心教学活动,全班幼儿共同参与。建议时长20-30分钟,包含绘本动画、教学课件、电子绘本等核心资源。 + 集体课是课程包的核心教学活动,全班幼儿共同参与。建议时长 20-30 分钟,包含绘本动画、教学课件、电子绘本等核心资源。 + + 必填项:课程名称、教学目标、教学准备、核心资源(至少 1 个)、教学环节(至少 1 个)、教学延伸;时长:15-45 分钟。 + @@ -27,6 +30,7 @@ (null); +const configPanelRef = ref | null>(null); // 获取集体课数据 const fetchLesson = async () => { @@ -155,38 +160,12 @@ const handleLessonChange = () => { emit('change'); }; -// 验证:若配置了集体课,则教学目标、教学准备、核心资源、时长 15-45 分钟必填 -const validate = () => { +// 验证:若配置了集体课,则通过 formRules 校验 +const validate = async () => { if (!lessonData.value) { return { valid: true, errors: [] as string[], warnings: ['未配置集体课'] }; } - - const errors: string[] = []; - if (!lessonData.value.objectives?.trim()) { - errors.push('请输入教学目标'); - } - if (!lessonData.value.preparation?.trim()) { - errors.push('请输入教学准备'); - } - if (!lessonData.value.videoPath && !lessonData.value.pptPath && !lessonData.value.pdfPath) { - errors.push('请至少上传一个核心资源(动画/课件/电子绘本)'); - } - const duration = lessonData.value.duration; - if (duration != null && (duration < 15 || duration > 45)) { - errors.push('集体课时长需在 15-45 分钟之间'); - } - const steps = lessonData.value.steps || []; - if (steps.length < 1) { - errors.push('请至少添加一个教学环节'); - } else { - steps.forEach((step, i) => { - if (!step.name?.trim()) errors.push(`第${i + 1}个环节:请填写环节名称`); - if (!step.content?.trim()) errors.push(`第${i + 1}个环节:请填写环节内容`); - if (!step.objective?.trim()) errors.push(`第${i + 1}个环节:请填写教学目标`); - }); - } - - return { valid: errors.length === 0, errors }; + return configPanelRef.value?.validate() ?? { valid: true, errors: [] as string[] }; }; // 获取保存数据 @@ -236,5 +215,13 @@ defineExpose({ border-top: 1px solid #f0f0f0; text-align: right; } + + .form-hints { + margin-top: 8px; + padding-top: 8px; + border-top: 1px dashed rgba(24, 144, 255, 0.3); + font-size: 12px; + color: #595959; + } } diff --git a/reading-platform-frontend/src/components/course-edit/Step6DomainLessons.vue b/reading-platform-frontend/src/components/course-edit/Step6DomainLessons.vue index 9819f1a..b310834 100644 --- a/reading-platform-frontend/src/components/course-edit/Step6DomainLessons.vue +++ b/reading-platform-frontend/src/components/course-edit/Step6DomainLessons.vue @@ -12,7 +12,10 @@ style="margin-bottom: 16px" > - 五大领域课为可选配置,根据课程内容选择配置相关领域课程。每个领域课独立配置,包含教学目标、准备、环节等内容。 + 五大领域课为可选配置,根据课程内容选择配置相关领域课程。每个领域课独立配置,包含教学目标、准备、环节等内容。 + + 已启用领域必填:课程名称、教学目标、教学准备、核心资源(至少 1 个)、教学环节(至少 1 个)、教学延伸;时长:15-45 分钟。 + @@ -46,8 +49,9 @@ - + (); const loading = ref(false); +const configPanelRefs = reactive | null>>({}); + +const setConfigPanelRef = (type: string, el: any) => { + if (el) { + configPanelRefs[type] = el; + } else { + configPanelRefs[type] = null; + } +}; const domains = reactive([ { @@ -253,34 +266,22 @@ const handleLessonChange = () => { emit('change'); }; -// 验证:若启用某领域,则需填写教学目标,时长 15-45 分钟 -const validate = () => { +// 验证:若启用某领域,则通过 formRules 校验各领域 +const validate = async () => { const enabledDomains = domains.filter((d) => d.enabled); - const errors: string[] = []; + const allErrors: string[] = []; - enabledDomains.forEach((domain) => { - if (domain.lessonData) { - if (!domain.lessonData.objectives?.trim()) { - errors.push(`${domain.name}:请填写教学目标`); - } - const duration = domain.lessonData.duration; - if (duration != null && (duration < 15 || duration > 45)) { - errors.push(`${domain.name}:时长需在 15-45 分钟之间`); - } - const steps = domain.lessonData.steps || []; - if (steps.length < 1) { - errors.push(`${domain.name}:请至少添加一个教学环节`); - } else { - steps.forEach((step, i) => { - if (!step.name?.trim()) errors.push(`${domain.name}:第${i + 1}个环节请填写环节名称`); - if (!step.content?.trim()) errors.push(`${domain.name}:第${i + 1}个环节请填写环节内容`); - if (!step.objective?.trim()) errors.push(`${domain.name}:第${i + 1}个环节请填写教学目标`); - }); + for (const domain of enabledDomains) { + const panel = configPanelRefs[domain.type]; + if (panel?.validate) { + const result = await panel.validate(); + if (!result.valid && result.errors?.length) { + result.errors.forEach((e) => allErrors.push(`${domain.name}:${e}`)); } } - }); + } - return { valid: errors.length === 0, errors }; + return { valid: allErrors.length === 0, errors: allErrors }; }; // 获取保存数据(仅返回已启用且教学目标已填写的领域) @@ -386,5 +387,13 @@ defineExpose({ gap: 4px; } } + + .form-hints { + margin-top: 8px; + padding-top: 8px; + border-top: 1px dashed rgba(24, 144, 255, 0.3); + font-size: 12px; + color: #595959; + } } diff --git a/reading-platform-frontend/src/components/course/LessonConfigPanel.vue b/reading-platform-frontend/src/components/course/LessonConfigPanel.vue index 3d0f0fb..7bf2005 100644 --- a/reading-platform-frontend/src/components/course/LessonConfigPanel.vue +++ b/reading-platform-frontend/src/components/course/LessonConfigPanel.vue @@ -1,15 +1,23 @@ + - + - + - 核心资源 * + + 核心资源 * + - - - - - - - - - - - - - - - - - + 至少上传一个(动画/课件/电子绘本) + + + + + + + + + + + + + + + + + + + - + - + 教学环节 * + 至少添加一个环节,每个环节需填写名称、内容、目标 + + - + @@ -166,11 +192,13 @@ /> + @@ -317,6 +431,18 @@ defineExpose({ margin-left: 2px; } + .optional-tag { + font-size: 12px; + color: #8c8c8c; + margin-left: 4px; + } + + .field-hint { + font-size: 12px; + color: #8c8c8c; + margin-bottom: 12px; + } + :deep(.ant-form-item) { margin-bottom: 12px; diff --git a/reading-platform-frontend/src/components/course/LessonStepsEditor.vue b/reading-platform-frontend/src/components/course/LessonStepsEditor.vue index 391e15c..14c7f2d 100644 --- a/reading-platform-frontend/src/components/course/LessonStepsEditor.vue +++ b/reading-platform-frontend/src/components/course/LessonStepsEditor.vue @@ -278,11 +278,15 @@ defineExpose({ .step-name-field { flex: 1; display: flex; - flex-direction: column; - gap: 4px; + flex-direction: row; + align-items: center; + gap: 8px; + min-width: 0; .field-label { + flex-shrink: 0; margin-bottom: 0; + white-space: nowrap; } }