kindergarten_java/reading-platform-frontend/src/components/course-edit/Step6DomainLessons.vue

391 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="step6-domain-lessons">
<div class="section-header">
<span class="title">五大领域课配置</span>
<a-tag color="purple">可选配置</a-tag>
</div>
<a-alert
message="领域课说明"
type="info"
show-icon
style="margin-bottom: 16px"
>
<template #description>
五大领域课为可选配置根据课程内容选择配置相关领域课程每个领域课独立配置包含教学目标准备环节等内容
</template>
</a-alert>
<a-spin :spinning="loading">
<div class="domain-tabs">
<div
v-for="domain in domains"
:key="domain.type"
class="domain-card"
:class="{ active: domain.enabled }"
>
<div class="domain-header" @click="toggleDomain(domain)">
<div class="domain-info">
<a-switch
v-model:checked="domain.enabled"
size="small"
@click.stop
@change="handleDomainToggle(domain)"
/>
<component :is="domain.icon" class="domain-icon" :style="{ color: domain.color }" />
<span class="domain-name">{{ domain.name }}</span>
</div>
<a-button
v-if="domain.enabled"
type="link"
size="small"
@click.stop="expandDomain(domain)"
>
{{ domain.expanded ? '收起' : '展开' }}
<DownOutlined :class="{ rotated: domain.expanded }" />
</a-button>
</div>
<div v-if="domain.enabled && domain.expanded" class="domain-content">
<LessonConfigPanel
v-model="domain.lessonData"
:lesson-type="domain.type"
:min-duration="15"
:max-duration="45"
:show-resources="true"
:show-extension="true"
:show-template="false"
@change="handleLessonChange"
/>
</div>
<div v-if="domain.enabled && !domain.expanded" class="domain-summary">
<span v-if="domain.lessonData?.objectives" class="summary-item">
<CheckCircleOutlined style="color: #52c41a" /> 已配置教学目标
</span>
<span v-else class="summary-item">
<ExclamationCircleOutlined style="color: #faad14" /> 未配置教学目标
</span>
</div>
</div>
</div>
</a-spin>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch, onMounted, markRaw } from 'vue';
import { message } from 'ant-design-vue';
import {
HeartOutlined,
SoundOutlined,
TeamOutlined,
ExperimentOutlined,
HighlightOutlined,
DownOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons-vue';
import LessonConfigPanel from '@/components/course/LessonConfigPanel.vue';
import type { LessonData } from '@/components/course/LessonConfigPanel.vue';
import { getLessonList, createLesson, updateLesson, deleteLesson } from '@/api/lesson';
interface DomainConfig {
type: string;
name: string;
color: string;
icon: any;
enabled: boolean;
expanded: boolean;
lessonData: LessonData | null;
}
interface Props {
courseId: number;
courseName: string;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const loading = ref(false);
const domains = reactive<DomainConfig[]>([
{
type: 'DOMAIN_HEALTH',
name: '健康领域',
color: '#f5222d',
icon: markRaw(HeartOutlined),
enabled: false,
expanded: false,
lessonData: null,
},
{
type: 'DOMAIN_LANGUAGE',
name: '语言领域',
color: '#fa8c16',
icon: markRaw(SoundOutlined),
enabled: false,
expanded: false,
lessonData: null,
},
{
type: 'DOMAIN_SOCIAL',
name: '社会领域',
color: '#722ed1',
icon: markRaw(TeamOutlined),
enabled: false,
expanded: false,
lessonData: null,
},
{
type: 'DOMAIN_SCIENCE',
name: '科学领域',
color: '#13c2c2',
icon: markRaw(ExperimentOutlined),
enabled: false,
expanded: false,
lessonData: null,
},
{
type: 'DOMAIN_ART',
name: '艺术领域',
color: '#eb2f96',
icon: markRaw(HighlightOutlined),
enabled: false,
expanded: false,
lessonData: null,
},
]);
// 获取领域课数据
const fetchLessons = async () => {
if (!props.courseId) return;
loading.value = true;
try {
const res = await getLessonList(props.courseId) as any;
const lessons = res.data || res || [];
// 匹配领域课
lessons.forEach((lesson: any) => {
const domain = domains.find(d => d.type === lesson.lessonType);
if (domain) {
domain.enabled = true;
domain.lessonData = {
id: lesson.id,
lessonType: lesson.lessonType,
name: lesson.name || domain.name,
description: lesson.description || '',
duration: lesson.duration || 25,
videoPath: lesson.videoPath || '',
videoName: lesson.videoName || '',
pptPath: lesson.pptPath || '',
pptName: lesson.pptName || '',
pdfPath: lesson.pdfPath || '',
pdfName: lesson.pdfName || '',
objectives: lesson.objectives || '',
preparation: lesson.preparation || '',
extension: lesson.extension || '',
reflection: lesson.reflection || '',
assessmentData: lesson.assessmentData || '',
useTemplate: lesson.useTemplate || false,
steps: lesson.steps || [],
isNew: false,
};
}
});
} catch (error) {
console.error('获取领域课失败', error);
} finally {
loading.value = false;
}
};
// 切换领域启用状态
const toggleDomain = (domain: DomainConfig) => {
// 切换逻辑由 switch 组件处理
};
// 处理领域启用/禁用
const handleDomainToggle = (domain: DomainConfig) => {
if (domain.enabled) {
// 启用时创建默认数据
domain.lessonData = {
lessonType: domain.type,
name: props.courseName ? `${props.courseName}-${domain.name}` : domain.name,
description: '',
duration: 25,
videoPath: '',
videoName: '',
pptPath: '',
pptName: '',
pdfPath: '',
pdfName: '',
objectives: '',
preparation: '',
extension: '',
reflection: '',
assessmentData: '',
useTemplate: false,
steps: [],
isNew: true,
};
domain.expanded = true;
} else {
// 禁用时清除数据
domain.lessonData = null;
domain.expanded = false;
}
emit('change');
};
// 展开/收起领域
const expandDomain = (domain: DomainConfig) => {
domain.expanded = !domain.expanded;
};
// 处理课程数据变化
const handleLessonChange = () => {
emit('change');
};
// 验证:若启用某领域,则需填写教学目标,时长 15-45 分钟
const validate = () => {
const enabledDomains = domains.filter((d) => d.enabled);
const errors: 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}个环节请填写教学目标`);
});
}
}
});
return { valid: errors.length === 0, errors };
};
// 获取保存数据(仅返回已启用且教学目标已填写的领域)
const getSaveData = () => {
return domains
.filter(d => d.enabled && d.lessonData && d.lessonData.objectives?.trim())
.map(d => d.lessonData);
};
watch(() => props.courseId, fetchLessons, { immediate: true });
onMounted(() => {
if (props.courseId) {
fetchLessons();
}
});
defineExpose({
validate,
getSaveData,
domains,
});
</script>
<style scoped lang="scss">
.step6-domain-lessons {
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.title {
font-size: 16px;
font-weight: 500;
}
}
.domain-tabs {
display: flex;
flex-direction: column;
gap: 12px;
}
.domain-card {
border: 1px solid #e8e8e8;
border-radius: 8px;
background: #fff;
transition: all 0.3s;
&:hover {
border-color: #d9d9d9;
}
&.active {
border-color: #1890ff;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
}
}
.domain-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
cursor: pointer;
.domain-info {
display: flex;
align-items: center;
gap: 12px;
.domain-icon {
font-size: 18px;
}
.domain-name {
font-weight: 500;
}
}
.rotated {
transform: rotate(180deg);
transition: transform 0.3s;
}
}
.domain-content {
padding: 0 16px 16px;
border-top: 1px solid #f0f0f0;
}
.domain-summary {
padding: 8px 16px 16px;
display: flex;
gap: 16px;
.summary-item {
font-size: 13px;
color: #666;
display: flex;
align-items: center;
gap: 4px;
}
}
}
</style>