feat(课程包): 三课表单formRules校验与UI优化
- LessonConfigPanel: 添加a-form+formRules表单校验 - 导入课/集体课/领域课: 核心资源均必填校验 - 修复教学目标/教学准备/教学延伸等重复校验提示 - 核心资源校验错误提示可见展示 - 教学环节: 环节名称改为水平布局(标签左对齐) Made-with: Cursor
This commit is contained in:
parent
17dc815030
commit
337dcc43d8
@ -12,7 +12,10 @@
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #description>
|
||||
导入课用于激发幼儿兴趣,引入课程主题。建议时长5-15分钟,重点在于吸引注意力、建立学习期待。
|
||||
<div>导入课用于激发幼儿兴趣,引入课程主题。建议时长 5-15 分钟,重点在于吸引注意力、建立学习期待。</div>
|
||||
<div class="form-hints">
|
||||
<strong>必填项:</strong>课程名称、教学目标、教学准备、核心资源(至少 1 个)、教学环节(至少 1 个,含环节名称、内容、目标);<strong>时长:</strong>5-15 分钟。
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
@ -27,6 +30,7 @@
|
||||
|
||||
<LessonConfigPanel
|
||||
v-else
|
||||
ref="configPanelRef"
|
||||
v-model="lessonData"
|
||||
lesson-type="INTRO"
|
||||
:min-duration="5"
|
||||
@ -69,6 +73,7 @@ const emit = defineEmits<{
|
||||
|
||||
const loading = ref(false);
|
||||
const lessonData = ref<LessonData | null>(null);
|
||||
const configPanelRef = ref<InstanceType<typeof LessonConfigPanel> | 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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -12,7 +12,10 @@
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #description>
|
||||
集体课是课程包的核心教学活动,全班幼儿共同参与。建议时长20-30分钟,包含绘本动画、教学课件、电子绘本等核心资源。
|
||||
<div>集体课是课程包的核心教学活动,全班幼儿共同参与。建议时长 20-30 分钟,包含绘本动画、教学课件、电子绘本等核心资源。</div>
|
||||
<div class="form-hints">
|
||||
<strong>必填项:</strong>课程名称、教学目标、教学准备、核心资源(至少 1 个)、教学环节(至少 1 个)、教学延伸;<strong>时长:</strong>15-45 分钟。
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
@ -27,6 +30,7 @@
|
||||
|
||||
<LessonConfigPanel
|
||||
v-else
|
||||
ref="configPanelRef"
|
||||
v-model="lessonData"
|
||||
lesson-type="COLLECTIVE"
|
||||
:min-duration="15"
|
||||
@ -70,6 +74,7 @@ const emit = defineEmits<{
|
||||
|
||||
const loading = ref(false);
|
||||
const lessonData = ref<LessonData | null>(null);
|
||||
const configPanelRef = ref<InstanceType<typeof LessonConfigPanel> | 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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -12,7 +12,10 @@
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #description>
|
||||
五大领域课为可选配置,根据课程内容选择配置相关领域课程。每个领域课独立配置,包含教学目标、准备、环节等内容。
|
||||
<div>五大领域课为可选配置,根据课程内容选择配置相关领域课程。每个领域课独立配置,包含教学目标、准备、环节等内容。</div>
|
||||
<div class="form-hints">
|
||||
<strong>已启用领域必填:</strong>课程名称、教学目标、教学准备、核心资源(至少 1 个)、教学环节(至少 1 个)、教学延伸;<strong>时长:</strong>15-45 分钟。
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
@ -46,8 +49,9 @@
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div v-if="domain.enabled && domain.expanded" class="domain-content">
|
||||
<div v-if="domain.enabled" v-show="domain.expanded" class="domain-content">
|
||||
<LessonConfigPanel
|
||||
:ref="(el: any) => setConfigPanelRef(domain.type, el)"
|
||||
v-model="domain.lessonData"
|
||||
:lesson-type="domain.type"
|
||||
:min-duration="15"
|
||||
@ -112,6 +116,15 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const configPanelRefs = reactive<Record<string, InstanceType<typeof LessonConfigPanel> | null>>({});
|
||||
|
||||
const setConfigPanelRef = (type: string, el: any) => {
|
||||
if (el) {
|
||||
configPanelRefs[type] = el;
|
||||
} else {
|
||||
configPanelRefs[type] = null;
|
||||
}
|
||||
};
|
||||
|
||||
const domains = reactive<DomainConfig[]>([
|
||||
{
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,15 +1,23 @@
|
||||
<template>
|
||||
<div class="lesson-config-panel">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="lessonData"
|
||||
:rules="formRules"
|
||||
:label-col="{ span: 24 }"
|
||||
:wrapper-col="{ span: 24 }"
|
||||
layout="vertical"
|
||||
>
|
||||
<!-- 基本信息 -->
|
||||
<a-card size="small" title="基本信息" class="section-card">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="课程名称">
|
||||
<a-form-item label="课程名称" name="name">
|
||||
<a-input v-model:value="lessonData.name" placeholder="请输入课程名称" @change="handleChange" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="课程时长">
|
||||
<a-form-item label="课程时长" name="duration" :extra="`建议 ${minDuration}-${maxDuration} 分钟`">
|
||||
<a-input-number
|
||||
v-model:value="lessonData.duration"
|
||||
:min="minDuration"
|
||||
@ -36,50 +44,61 @@
|
||||
<!-- 核心资源(导入课/集体课/领域课显示) -->
|
||||
<a-card v-if="showResources" size="small" class="section-card">
|
||||
<template #title>
|
||||
<span>核心资源 <span class="required-mark">*</span></span>
|
||||
<span>
|
||||
核心资源 <span class="required-mark">*</span>
|
||||
</span>
|
||||
</template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="绘本动画">
|
||||
<FileUploader
|
||||
v-model:file-path="lessonData.videoPath"
|
||||
v-model:file-name="lessonData.videoName"
|
||||
file-type="video"
|
||||
:max-size="200"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="教学课件">
|
||||
<FileUploader
|
||||
v-model:file-path="lessonData.pptPath"
|
||||
v-model:file-name="lessonData.pptName"
|
||||
file-type="ppt"
|
||||
:max-size="100"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="电子绘本">
|
||||
<FileUploader
|
||||
v-model:file-path="lessonData.pdfPath"
|
||||
v-model:file-name="lessonData.pdfName"
|
||||
file-type="pdf"
|
||||
:max-size="100"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="field-hint">至少上传一个(动画/课件/电子绘本)</div>
|
||||
<a-form-item
|
||||
name="resourceCheck"
|
||||
label=" "
|
||||
:colon="false"
|
||||
:label-col="{ span: 0 }"
|
||||
:wrapper-col="{ span: 24 }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="绘本动画" :name="undefined">
|
||||
<FileUploader
|
||||
v-model:file-path="lessonData.videoPath"
|
||||
v-model:file-name="lessonData.videoName"
|
||||
file-type="video"
|
||||
:max-size="200"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="教学课件" :name="undefined">
|
||||
<FileUploader
|
||||
v-model:file-path="lessonData.pptPath"
|
||||
v-model:file-name="lessonData.pptName"
|
||||
file-type="ppt"
|
||||
:max-size="100"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="电子绘本" :name="undefined">
|
||||
<FileUploader
|
||||
v-model:file-path="lessonData.pdfPath"
|
||||
v-model:file-name="lessonData.pdfName"
|
||||
file-type="pdf"
|
||||
:max-size="100"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-card>
|
||||
|
||||
<!-- 教学目标 & 教学准备 -->
|
||||
<a-card size="small" class="section-card">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="教学目标" required>
|
||||
<a-form-item label="教学目标" name="objectives">
|
||||
<a-textarea
|
||||
v-model:value="lessonData.objectives"
|
||||
:rows="4"
|
||||
@ -94,7 +113,7 @@
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="教学准备" required>
|
||||
<a-form-item label="教学准备" name="preparation">
|
||||
<a-textarea
|
||||
v-model:value="lessonData.preparation"
|
||||
:rows="4"
|
||||
@ -116,24 +135,31 @@
|
||||
<template #title>
|
||||
<span>教学环节 <span class="required-mark">*</span></span>
|
||||
</template>
|
||||
<div class="field-hint">至少添加一个环节,每个环节需填写名称、内容、目标</div>
|
||||
<a-form-item name="steps">
|
||||
<LessonStepsEditor
|
||||
v-model="lessonData.steps"
|
||||
:show-template="showTemplate"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-card>
|
||||
|
||||
<!-- 教学延伸 & 教学反思(仅集体课/领域课显示) -->
|
||||
<a-card v-if="showExtension" size="small" class="section-card">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="教学延伸">
|
||||
<a-form-item
|
||||
label="教学延伸"
|
||||
:name="extensionRequired ? 'extension' : undefined"
|
||||
:extra="extensionRequired ? '必填(集体课要求)' : '可选'"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="lessonData.extension"
|
||||
:rows="3"
|
||||
:maxlength="1500"
|
||||
show-count
|
||||
placeholder="请输入教学延伸活动建议(可选)"
|
||||
:placeholder="extensionRequired ? '请输入教学延伸活动建议(必填)' : '请输入教学延伸活动建议(可选)'"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
@ -166,11 +192,13 @@
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-card>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, watch, computed } from 'vue';
|
||||
import { ref, reactive, watch, computed } from 'vue';
|
||||
import type { FormInstance } from 'ant-design-vue';
|
||||
import FileUploader from './FileUploader.vue';
|
||||
import LessonStepsEditor from './LessonStepsEditor.vue';
|
||||
import type { StepData } from './LessonStepsEditor.vue';
|
||||
@ -196,6 +224,8 @@ export interface LessonData {
|
||||
useTemplate: boolean;
|
||||
steps: StepData[];
|
||||
isNew?: boolean;
|
||||
/** 仅用于表单校验,不持久化 */
|
||||
resourceCheck?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
@ -242,6 +272,7 @@ const defaultLessonData: LessonData = {
|
||||
assessmentData: '',
|
||||
useTemplate: false,
|
||||
steps: [],
|
||||
resourceCheck: '',
|
||||
};
|
||||
|
||||
const lessonData = reactive<LessonData>({
|
||||
@ -249,37 +280,120 @@ const lessonData = reactive<LessonData>({
|
||||
...props.modelValue,
|
||||
});
|
||||
|
||||
// 教学延伸是否必填:集体课、领域课必填(结构同集体课)
|
||||
const extensionRequired = computed(
|
||||
() => props.lessonType === 'COLLECTIVE' || props.lessonType?.startsWith('DOMAIN_')
|
||||
);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
// formRules 根据 lessonType 动态生成
|
||||
const formRules = computed(() => {
|
||||
const minD = props.minDuration ?? 5;
|
||||
const maxD = props.maxDuration ?? 60;
|
||||
const rules: Record<string, any[]> = {
|
||||
name: [{ required: true, whitespace: true, message: '请输入课程名称' }],
|
||||
objectives: [{ required: true, whitespace: true, message: '请输入教学目标' }],
|
||||
preparation: [{ required: true, whitespace: true, message: '请输入教学准备' }],
|
||||
duration: [
|
||||
{ required: true, message: '请输入课程时长' },
|
||||
{
|
||||
type: 'number' as const,
|
||||
min: minD,
|
||||
max: maxD,
|
||||
message: `课程时长需在 ${minD}-${maxD} 分钟之间`,
|
||||
},
|
||||
],
|
||||
steps: [
|
||||
{
|
||||
validator: (_: unknown, value: StepData[] | undefined) => {
|
||||
const steps = value || [];
|
||||
if (steps.length < 1) {
|
||||
return Promise.reject(new Error('请至少添加一个教学环节'));
|
||||
}
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const s = steps[i];
|
||||
if (!s.name?.trim()) {
|
||||
return Promise.reject(new Error(`第${i + 1}个环节:请填写环节名称`));
|
||||
}
|
||||
if (!s.content?.trim()) {
|
||||
return Promise.reject(new Error(`第${i + 1}个环节:请填写环节内容`));
|
||||
}
|
||||
if (!s.objective?.trim()) {
|
||||
return Promise.reject(new Error(`第${i + 1}个环节:请填写教学目标`));
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
if (props.showResources) {
|
||||
rules.resourceCheck = [
|
||||
{
|
||||
validator: (_: unknown, _val: string) => {
|
||||
const has = lessonData.videoPath || lessonData.pptPath || lessonData.pdfPath;
|
||||
if (!has) {
|
||||
return Promise.reject(new Error('请至少上传一个核心资源(动画/课件/电子绘本)'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
if (extensionRequired.value) {
|
||||
rules.extension = [
|
||||
{ required: true, whitespace: true, message: '请输入教学延伸活动建议' },
|
||||
];
|
||||
}
|
||||
return rules;
|
||||
});
|
||||
|
||||
// 同步 resourceCheck 用于校验触发
|
||||
watch(
|
||||
() => [lessonData.videoPath, lessonData.pptPath, lessonData.pdfPath],
|
||||
() => {
|
||||
lessonData.resourceCheck = lessonData.videoPath || lessonData.pptPath || lessonData.pdfPath || '';
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听外部值变化
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
Object.assign(lessonData, defaultLessonData, newVal);
|
||||
lessonData.resourceCheck = lessonData.videoPath || lessonData.pptPath || lessonData.pdfPath || '';
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
// 处理变化
|
||||
// 处理变化(排除校验用字段,不传给父组件)
|
||||
const handleChange = () => {
|
||||
emit('update:modelValue', { ...lessonData });
|
||||
const { resourceCheck, ...toEmit } = lessonData;
|
||||
emit('update:modelValue', toEmit as LessonData);
|
||||
emit('change');
|
||||
};
|
||||
|
||||
// 表单校验(供 Step4/5/6 调用)
|
||||
const validate = async () => {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
return { valid: true, errors: [] as string[] };
|
||||
} catch (err: any) {
|
||||
const errorFields = err?.errorFields || [];
|
||||
const errors = errorFields
|
||||
.map((f: any) => f.errors?.[0])
|
||||
.filter(Boolean) as string[];
|
||||
return { valid: false, errors: errors.length ? errors : ['请完成表单必填项'] };
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露方法
|
||||
defineExpose({
|
||||
lessonData,
|
||||
validate: () => {
|
||||
const errors: string[] = [];
|
||||
if (!lessonData.name) {
|
||||
errors.push('请输入课程名称');
|
||||
}
|
||||
if (!lessonData.objectives) {
|
||||
errors.push('请输入教学目标');
|
||||
}
|
||||
if (!lessonData.preparation) {
|
||||
errors.push('请输入教学准备');
|
||||
}
|
||||
return { valid: errors.length === 0, errors };
|
||||
},
|
||||
formRef,
|
||||
validate,
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user