2026-03-17 16:59:06 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<a-modal
|
|
|
|
|
|
v-model:open="visible"
|
|
|
|
|
|
title="新建排课"
|
|
|
|
|
|
:confirm-loading="loading"
|
|
|
|
|
|
@ok="handleSubmit"
|
|
|
|
|
|
@cancel="handleCancel"
|
|
|
|
|
|
width="700px"
|
|
|
|
|
|
destroy-on-close
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-steps :current="currentStep" size="small" class="steps-navigator">
|
|
|
|
|
|
<a-step title="选择课程包" />
|
|
|
|
|
|
<a-step title="选择课程类型" />
|
|
|
|
|
|
<a-step title="选择班级" />
|
|
|
|
|
|
<a-step title="设置时间" />
|
|
|
|
|
|
</a-steps>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="step-content">
|
|
|
|
|
|
<!-- 步骤1: 选择课程套餐和课程包 -->
|
|
|
|
|
|
<div v-show="currentStep === 0" class="step-panel">
|
|
|
|
|
|
<h3>选择课程套餐</h3>
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="formData.collectionId"
|
|
|
|
|
|
placeholder="请选择课程套餐"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
show-search
|
|
|
|
|
|
:filter-option="filterCollection"
|
|
|
|
|
|
@change="handleCollectionChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-select-option v-for="collection in collections" :key="collection.id" :value="collection.id">
|
|
|
|
|
|
<div class="collection-option">
|
|
|
|
|
|
<div class="collection-name">{{ collection.name }}</div>
|
|
|
|
|
|
<div class="collection-info">{{ collection.packageCount }} 个课程包 · {{ collection.gradeLevels?.join(', ') }}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 选择课程包 -->
|
2026-03-18 14:46:32 +08:00
|
|
|
|
<div v-if="selectedCollection && selectedCollection.packages && selectedCollection.packages.length > 0" class="packages-section">
|
2026-03-17 16:59:06 +08:00
|
|
|
|
<h4>选择课程包</h4>
|
|
|
|
|
|
<div class="packages-grid">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="pkg in selectedCollection.packages"
|
|
|
|
|
|
:key="pkg.id"
|
|
|
|
|
|
:class="['package-card', { active: formData.packageId === pkg.id }]"
|
|
|
|
|
|
@click="selectPackage(pkg.id)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="package-name">{{ pkg.name }}</div>
|
2026-03-18 14:46:32 +08:00
|
|
|
|
<div class="package-grade">{{ Array.isArray(pkg.gradeLevels) ? pkg.gradeLevels.join(', ') : pkg.gradeLevels }}</div>
|
2026-03-17 16:59:06 +08:00
|
|
|
|
<div class="package-count">{{ pkg.courseCount }} 门课程</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-18 14:46:32 +08:00
|
|
|
|
<!-- 调试信息:如果没有课程包,显示提示 -->
|
|
|
|
|
|
<div v-else-if="selectedCollection" class="packages-section">
|
|
|
|
|
|
<h4>选择课程包</h4>
|
|
|
|
|
|
<a-alert message="该套餐暂无课程包" type="warning" show-icon />
|
2026-03-17 16:59:06 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 排课计划参考 -->
|
|
|
|
|
|
<div v-if="scheduleRefData.length > 0" class="schedule-ref-card">
|
|
|
|
|
|
<div class="ref-header">
|
|
|
|
|
|
<CalendarOutlined class="ref-icon" />
|
|
|
|
|
|
<span class="ref-title">排课计划参考</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<a-table
|
|
|
|
|
|
:columns="scheduleRefColumns"
|
|
|
|
|
|
:data-source="scheduleRefData"
|
|
|
|
|
|
:pagination="false"
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
bordered
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #bodyCell="{ column, record }">
|
|
|
|
|
|
<template v-if="column.key === 'dayOfWeek'">
|
|
|
|
|
|
{{ weekDayNames[record.dayOfWeek] || '-' }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 步骤2: 选择课程类型 -->
|
|
|
|
|
|
<div v-show="currentStep === 1" class="step-panel">
|
|
|
|
|
|
<h3>选择课程类型</h3>
|
|
|
|
|
|
<a-alert
|
|
|
|
|
|
message="请选择一个课程类型进行排课"
|
|
|
|
|
|
type="info"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
style="margin-bottom: 16px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<a-spin :spinning="loadingLessonTypes">
|
|
|
|
|
|
<div class="lesson-type-grid">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="type in lessonTypes"
|
|
|
|
|
|
:key="type.lessonType"
|
|
|
|
|
|
:class="['lesson-type-card', { active: formData.lessonType === type.lessonType }]"
|
|
|
|
|
|
@click="selectLessonType(type.lessonType)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="type-icon">{{ getLessonTypeIcon(type.lessonType) }}</div>
|
|
|
|
|
|
<div class="type-name">{{ type.lessonTypeName }}</div>
|
|
|
|
|
|
<div class="type-count">{{ type.count }} 节课</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 步骤3: 选择班级并分配教师 -->
|
|
|
|
|
|
<div v-show="currentStep === 2" class="step-panel">
|
|
|
|
|
|
<h3>选择班级并分配教师</h3>
|
|
|
|
|
|
<a-alert
|
|
|
|
|
|
message="选择班级后,为每个班级指定授课教师"
|
|
|
|
|
|
type="info"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
style="margin-bottom: 16px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div class="grade-selector">
|
|
|
|
|
|
<a-radio-group v-model:value="selectedGrade" button-style="solid">
|
|
|
|
|
|
<a-radio-button value="">全部</a-radio-button>
|
|
|
|
|
|
<a-radio-button value="小班">小班</a-radio-button>
|
|
|
|
|
|
<a-radio-button value="中班">中班</a-radio-button>
|
|
|
|
|
|
<a-radio-button value="大班">大班</a-radio-button>
|
|
|
|
|
|
</a-radio-group>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="class-teacher-grid">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="cls in filteredClasses"
|
|
|
|
|
|
:key="cls.id"
|
|
|
|
|
|
:class="['class-teacher-card', { selected: isClassSelected(cls.id) }]"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="class-header" @click="toggleClass(cls.id)">
|
|
|
|
|
|
<a-checkbox :checked="isClassSelected(cls.id)" @click.stop />
|
|
|
|
|
|
<div class="class-info">
|
|
|
|
|
|
<div class="class-name">{{ cls.name }}</div>
|
|
|
|
|
|
<div class="class-detail">{{ cls.studentCount }} 名学生</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-if="isClassSelected(cls.id)" class="teacher-selector">
|
|
|
|
|
|
<a-select
|
|
|
|
|
|
v-model:value="classTeacherMap[cls.id]"
|
|
|
|
|
|
placeholder="选择教师"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
show-search
|
|
|
|
|
|
:filter-option="filterTeacher"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
|
|
|
|
|
|
{{ teacher.name }}
|
|
|
|
|
|
</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="selection-summary">
|
|
|
|
|
|
已选择 <strong>{{ formData.classIds.length }}</strong> 个班级
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 步骤4: 设置时间 -->
|
|
|
|
|
|
<div v-show="currentStep === 3" class="step-panel">
|
|
|
|
|
|
<h3>设置时间</h3>
|
|
|
|
|
|
<a-form layout="vertical">
|
|
|
|
|
|
<a-form-item label="排课日期" required>
|
|
|
|
|
|
<a-date-picker
|
|
|
|
|
|
v-model:value="formData.scheduledDate"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
placeholder="选择日期"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
|
|
|
|
|
|
<a-form-item label="时间段" required>
|
|
|
|
|
|
<a-time-range-picker
|
|
|
|
|
|
v-model:value="formData.scheduledTimeRange"
|
|
|
|
|
|
format="HH:mm"
|
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
|
:placeholder="['开始时间', '结束时间']"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-form-item>
|
|
|
|
|
|
</a-form>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 确认信息 -->
|
|
|
|
|
|
<div class="confirm-info">
|
|
|
|
|
|
<a-alert type="info" show-icon>
|
|
|
|
|
|
<template #message>
|
|
|
|
|
|
<div>将为 <strong>{{ formData.classIds.length }}</strong> 个班级创建排课</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #description>
|
|
|
|
|
|
<div>课程类型: {{ getSelectedLessonTypeName() }}</div>
|
|
|
|
|
|
<div>排课日期: {{ formData.scheduledDate?.format('YYYY-MM-DD') || '-' }}</div>
|
|
|
|
|
|
<div>时间段: {{ getSelectedTimeRange() || '-' }}</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-alert>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 班级教师分配列表 -->
|
|
|
|
|
|
<div v-if="formData.classIds.length > 0" class="class-teacher-list">
|
|
|
|
|
|
<h4>班级教师分配</h4>
|
|
|
|
|
|
<a-list :data-source="getSelectedClassesWithTeachers()" size="small">
|
|
|
|
|
|
<template #renderItem="{ item }">
|
|
|
|
|
|
<a-list-item>
|
|
|
|
|
|
<a-list-item-meta>
|
|
|
|
|
|
<template #title>{{ item.className }}</template>
|
|
|
|
|
|
</a-list-item-meta>
|
|
|
|
|
|
<template #actions>
|
|
|
|
|
|
<span :class="['teacher-status', { assigned: item.teacherId }]">
|
|
|
|
|
|
{{ item.teacherName || '未分配教师' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-list-item>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-list>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
|
<a-button v-if="currentStep > 0" @click="prevStep">上一步</a-button>
|
|
|
|
|
|
<a-button v-if="currentStep < 3" type="primary" @click="nextStep">下一步</a-button>
|
|
|
|
|
|
<a-button v-else type="primary" :loading="loading" @click="handleSubmit">创建排课</a-button>
|
|
|
|
|
|
<a-button @click="handleCancel">取消</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-modal>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ref, reactive, computed, watch } from 'vue';
|
|
|
|
|
|
import { message } from 'ant-design-vue';
|
|
|
|
|
|
import dayjs, { Dayjs } from 'dayjs';
|
|
|
|
|
|
import { CalendarOutlined } from '@ant-design/icons-vue';
|
|
|
|
|
|
import {
|
|
|
|
|
|
getCourseCollections,
|
|
|
|
|
|
getCourseCollectionPackages,
|
|
|
|
|
|
getCoursePackageLessonTypes,
|
|
|
|
|
|
createSchedulesByClasses,
|
|
|
|
|
|
getClasses,
|
|
|
|
|
|
getTeachers,
|
|
|
|
|
|
type CourseCollection,
|
|
|
|
|
|
type CoursePackage,
|
|
|
|
|
|
type LessonTypeInfo,
|
|
|
|
|
|
type LessonType,
|
|
|
|
|
|
type ClassInfo,
|
|
|
|
|
|
type Teacher,
|
|
|
|
|
|
} from '@/api/school';
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
(e: 'success'): void;
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
const visible = ref(false);
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const loadingLessonTypes = ref(false);
|
|
|
|
|
|
const currentStep = ref(0);
|
|
|
|
|
|
|
|
|
|
|
|
// 课程套餐列表
|
|
|
|
|
|
const collections = ref<CourseCollection[]>([]);
|
|
|
|
|
|
const selectedGrade = ref('');
|
|
|
|
|
|
|
|
|
|
|
|
// 课程类型列表
|
|
|
|
|
|
const lessonTypes = ref<LessonTypeInfo[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 班级列表
|
|
|
|
|
|
const classes = ref<ClassInfo[]>([]);
|
|
|
|
|
|
const teachers = ref<Teacher[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 班级教师映射
|
|
|
|
|
|
const classTeacherMap = ref<Record<number, number>>({});
|
|
|
|
|
|
|
|
|
|
|
|
// 排课计划参考数据
|
|
|
|
|
|
const scheduleRefData = ref<any[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
const scheduleRefColumns = [
|
|
|
|
|
|
{ title: '星期', dataIndex: 'dayOfWeek', key: 'dayOfWeek', width: 80 },
|
|
|
|
|
|
{ title: '活动安排', dataIndex: 'activity', key: 'activity' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const weekDayNames: Record<number, string> = {
|
|
|
|
|
|
1: '周一',
|
|
|
|
|
|
2: '周二',
|
|
|
|
|
|
3: '周三',
|
|
|
|
|
|
4: '周四',
|
|
|
|
|
|
5: '周五',
|
|
|
|
|
|
6: '周六',
|
|
|
|
|
|
0: '周日',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
interface FormData {
|
|
|
|
|
|
collectionId?: number;
|
|
|
|
|
|
packageId?: number;
|
2026-03-18 14:46:32 +08:00
|
|
|
|
courseId?: number; // 内部使用,自动设置为课程包的第一门课程
|
2026-03-17 16:59:06 +08:00
|
|
|
|
lessonType?: LessonType;
|
|
|
|
|
|
classIds: number[];
|
|
|
|
|
|
scheduledDate?: Dayjs;
|
|
|
|
|
|
scheduledTimeRange?: [Dayjs, Dayjs];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const formData = reactive<FormData>({
|
|
|
|
|
|
classIds: [],
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性:过滤后的班级列表
|
|
|
|
|
|
const filteredClasses = computed(() => {
|
|
|
|
|
|
if (!selectedGrade.value) return classes.value;
|
|
|
|
|
|
return classes.value.filter(cls => cls.grade === selectedGrade.value);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性:选中的课程套餐
|
|
|
|
|
|
const selectedCollection = computed(() => {
|
|
|
|
|
|
if (!formData.collectionId) return null;
|
2026-03-18 14:46:32 +08:00
|
|
|
|
const collection = collections.value.find(c => c.id === formData.collectionId) || null;
|
|
|
|
|
|
console.log('🎯 selectedCollection:', collection);
|
|
|
|
|
|
console.log('📦 selectedCollection.packages:', collection?.packages);
|
|
|
|
|
|
return collection;
|
2026-03-17 16:59:06 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 计算属性:选中的课程包
|
|
|
|
|
|
const selectedPackage = computed(() => {
|
|
|
|
|
|
if (!formData.packageId || !selectedCollection.value?.packages) return null;
|
2026-03-18 14:46:32 +08:00
|
|
|
|
const pkg = selectedCollection.value.packages.find(p => p.id === formData.packageId) || null;
|
|
|
|
|
|
console.log('📦 selectedPackage:', pkg);
|
|
|
|
|
|
console.log('📚 selectedPackage.courses:', pkg?.courses);
|
|
|
|
|
|
return pkg;
|
2026-03-17 16:59:06 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 打开弹窗
|
|
|
|
|
|
const open = () => {
|
|
|
|
|
|
visible.value = true;
|
|
|
|
|
|
currentStep.value = 0;
|
|
|
|
|
|
resetForm();
|
|
|
|
|
|
loadCollections();
|
|
|
|
|
|
loadClasses();
|
|
|
|
|
|
loadTeachers();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 重置表单
|
|
|
|
|
|
const resetForm = () => {
|
|
|
|
|
|
formData.collectionId = undefined;
|
|
|
|
|
|
formData.packageId = undefined;
|
|
|
|
|
|
formData.courseId = undefined;
|
|
|
|
|
|
formData.lessonType = undefined;
|
|
|
|
|
|
formData.classIds = [];
|
|
|
|
|
|
formData.scheduledDate = undefined;
|
|
|
|
|
|
formData.scheduledTimeRange = undefined;
|
|
|
|
|
|
selectedGrade.value = '';
|
|
|
|
|
|
scheduleRefData.value = [];
|
|
|
|
|
|
lessonTypes.value = [];
|
|
|
|
|
|
classTeacherMap.value = {};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载课程套餐列表
|
|
|
|
|
|
const loadCollections = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
collections.value = await getCourseCollections();
|
2026-03-18 14:46:32 +08:00
|
|
|
|
console.log('📚 课程套餐列表 (API返回):', collections.value);
|
|
|
|
|
|
console.log('📚 套餐数量:', collections.value?.length);
|
|
|
|
|
|
|
|
|
|
|
|
// 检查初始数据中的 packages 字段
|
|
|
|
|
|
collections.value.forEach((coll, idx) => {
|
|
|
|
|
|
console.log(` 📚 套餐[${idx}]:`, {
|
|
|
|
|
|
id: coll.id,
|
|
|
|
|
|
name: coll.name,
|
|
|
|
|
|
hasPackages: !!coll.packages,
|
|
|
|
|
|
packagesCount: coll.packages?.length || 0,
|
|
|
|
|
|
packages: coll.packages
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2026-03-17 16:59:06 +08:00
|
|
|
|
} catch (error) {
|
2026-03-18 14:46:32 +08:00
|
|
|
|
console.error('❌ 加载课程套餐失败:', error);
|
2026-03-17 16:59:06 +08:00
|
|
|
|
message.error('加载课程套餐失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载班级列表
|
|
|
|
|
|
const loadClasses = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
classes.value = await getClasses();
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('加载班级失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载教师列表
|
|
|
|
|
|
const loadTeachers = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await getTeachers({ pageNum: 1, pageSize: 100 });
|
|
|
|
|
|
teachers.value = data.list;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('加载教师失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 课程套餐变化 - 加载课程包列表
|
|
|
|
|
|
const handleCollectionChange = async (collectionId: number) => {
|
|
|
|
|
|
// 重置后续选择
|
|
|
|
|
|
formData.packageId = undefined;
|
|
|
|
|
|
formData.courseId = undefined;
|
|
|
|
|
|
scheduleRefData.value = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (!collectionId) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2026-03-18 14:46:32 +08:00
|
|
|
|
// 获取课程包列表(API: GET /v1/school/packages/{collectionId}/packages)
|
2026-03-17 16:59:06 +08:00
|
|
|
|
const packages = await getCourseCollectionPackages(collectionId);
|
2026-03-18 14:46:32 +08:00
|
|
|
|
console.log('📦 API返回的课程包列表:', packages);
|
|
|
|
|
|
console.log('📦 课程包数量:', packages?.length);
|
|
|
|
|
|
|
|
|
|
|
|
if (!packages || packages.length === 0) {
|
|
|
|
|
|
message.warning('该套餐暂无课程包');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 打印每个课程包的详细信息
|
|
|
|
|
|
packages.forEach((pkg, idx) => {
|
|
|
|
|
|
console.log(` 📦 课程包[${idx}]:`, {
|
|
|
|
|
|
id: pkg.id,
|
|
|
|
|
|
name: pkg.name,
|
|
|
|
|
|
courseCount: pkg.courseCount,
|
|
|
|
|
|
hasCourses: !!pkg.courses,
|
|
|
|
|
|
coursesCount: pkg.courses?.length || 0
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-17 16:59:06 +08:00
|
|
|
|
// 更新当前套餐的课程包列表
|
|
|
|
|
|
const collection = collections.value.find(c => c.id === collectionId);
|
|
|
|
|
|
if (collection) {
|
|
|
|
|
|
collection.packages = packages;
|
2026-03-18 14:46:32 +08:00
|
|
|
|
console.log('✅ 已更新套餐的课程包列表');
|
2026-03-17 16:59:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
2026-03-18 14:46:32 +08:00
|
|
|
|
console.error('❌ 加载课程包失败:', error);
|
2026-03-17 16:59:06 +08:00
|
|
|
|
message.error('加载课程包失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择课程包
|
|
|
|
|
|
const selectPackage = async (packageId: number) => {
|
2026-03-18 14:46:32 +08:00
|
|
|
|
console.log('🎯 点击选择课程包,packageId:', packageId);
|
|
|
|
|
|
console.log('📦 当前 packages 数组:', selectedCollection.value?.packages);
|
|
|
|
|
|
|
2026-03-17 16:59:06 +08:00
|
|
|
|
formData.packageId = packageId;
|
2026-03-17 21:13:06 +08:00
|
|
|
|
|
2026-03-18 14:46:32 +08:00
|
|
|
|
// 调试:查看找到的课程包
|
|
|
|
|
|
const foundPkg = selectedCollection.value?.packages?.find((p: any) => p.id === packageId);
|
|
|
|
|
|
console.log('🔍 找到的课程包:', foundPkg);
|
|
|
|
|
|
|
|
|
|
|
|
// 自动选择第一门课程(用于后端API)
|
2026-03-17 21:13:06 +08:00
|
|
|
|
if (selectedCollection.value?.packages) {
|
|
|
|
|
|
const selectedPkg = selectedCollection.value.packages.find((p: any) => p.id === packageId);
|
|
|
|
|
|
if (selectedPkg?.courses && selectedPkg.courses.length > 0) {
|
2026-03-18 14:46:32 +08:00
|
|
|
|
// 自动设置为第一门课程
|
|
|
|
|
|
formData.courseId = selectedPkg.courses[0].id;
|
|
|
|
|
|
console.log('✅ 自动选择第一门课程:', formData.courseId);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载排课计划参考(从第一门课程中获取)
|
2026-03-17 21:13:06 +08:00
|
|
|
|
const firstCourse = selectedPkg.courses[0];
|
|
|
|
|
|
if (firstCourse.scheduleRefData) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const parsedData = JSON.parse(firstCourse.scheduleRefData);
|
|
|
|
|
|
scheduleRefData.value = Array.isArray(parsedData) ? parsedData : [];
|
|
|
|
|
|
console.log('✅ 排课计划参考数据:', scheduleRefData.value);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('解析排课数据失败:', e);
|
|
|
|
|
|
scheduleRefData.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
scheduleRefData.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-03-18 14:46:32 +08:00
|
|
|
|
formData.courseId = undefined;
|
2026-03-17 21:13:06 +08:00
|
|
|
|
scheduleRefData.value = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-17 16:59:06 +08:00
|
|
|
|
|
|
|
|
|
|
// 加载课程类型列表
|
|
|
|
|
|
await loadLessonTypes(packageId);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 加载课程类型列表
|
|
|
|
|
|
const loadLessonTypes = async (packageId: number) => {
|
|
|
|
|
|
loadingLessonTypes.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
lessonTypes.value = await getCoursePackageLessonTypes(packageId);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('加载课程类型失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loadingLessonTypes.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择课程类型
|
|
|
|
|
|
const selectLessonType = (type: LessonType) => {
|
|
|
|
|
|
formData.lessonType = type;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 切换班级选择
|
|
|
|
|
|
const toggleClass = (classId: number) => {
|
|
|
|
|
|
const index = formData.classIds.indexOf(classId);
|
|
|
|
|
|
if (index > -1) {
|
|
|
|
|
|
formData.classIds.splice(index, 1);
|
|
|
|
|
|
delete classTeacherMap.value[classId];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
formData.classIds.push(classId);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 检查班级是否被选中
|
|
|
|
|
|
const isClassSelected = (classId: number): boolean => {
|
|
|
|
|
|
return formData.classIds.includes(classId);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤课程套餐
|
|
|
|
|
|
const filterCollection = (input: string, option: any) => {
|
|
|
|
|
|
const collection = collections.value.find(c => c.id === option.value);
|
|
|
|
|
|
return collection?.name?.toLowerCase().includes(input.toLowerCase()) || false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤教师
|
|
|
|
|
|
const filterTeacher = (input: string, option: any) => {
|
|
|
|
|
|
const teacher = teachers.value.find(t => t.id === option.value);
|
|
|
|
|
|
return teacher?.name?.toLowerCase().includes(input.toLowerCase()) || false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取课程类型图标
|
|
|
|
|
|
const getLessonTypeIcon = (type: LessonType): string => {
|
|
|
|
|
|
const icons: Record<LessonType, string> = {
|
|
|
|
|
|
INTRODUCTION: '📖',
|
|
|
|
|
|
COLLECTIVE: '👥',
|
|
|
|
|
|
LANGUAGE: '💬',
|
|
|
|
|
|
SOCIETY: '🤝',
|
|
|
|
|
|
SCIENCE: '🔬',
|
|
|
|
|
|
ART: '🎨',
|
|
|
|
|
|
HEALTH: '❤️',
|
|
|
|
|
|
};
|
|
|
|
|
|
return icons[type] || '📚';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取选中的课程类型名称
|
|
|
|
|
|
const getSelectedLessonTypeName = (): string => {
|
|
|
|
|
|
if (!formData.lessonType) return '-';
|
|
|
|
|
|
const type = lessonTypes.value.find(t => t.lessonType === formData.lessonType);
|
|
|
|
|
|
return type?.lessonTypeName || '-';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取选择的时间范围
|
|
|
|
|
|
const getSelectedTimeRange = (): string => {
|
|
|
|
|
|
if (!formData.scheduledTimeRange) return '';
|
|
|
|
|
|
return `${formData.scheduledTimeRange[0].format('HH:mm')}-${formData.scheduledTimeRange[1].format('HH:mm')}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取选中的班级及教师列表
|
|
|
|
|
|
const getSelectedClassesWithTeachers = () => {
|
|
|
|
|
|
return formData.classIds.map(classId => {
|
|
|
|
|
|
const cls = classes.value.find(c => c.id === classId);
|
|
|
|
|
|
const teacherId = classTeacherMap.value[classId];
|
|
|
|
|
|
const teacher = teachers.value.find(t => t.id === teacherId);
|
|
|
|
|
|
return {
|
|
|
|
|
|
classId,
|
|
|
|
|
|
className: cls?.name || '',
|
|
|
|
|
|
teacherId,
|
|
|
|
|
|
teacherName: teacher?.name || '',
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 验证当前步骤
|
|
|
|
|
|
const validateStep = (): boolean => {
|
|
|
|
|
|
switch (currentStep.value) {
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
if (!formData.collectionId) {
|
|
|
|
|
|
message.warning('请选择课程套餐');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!formData.packageId) {
|
|
|
|
|
|
message.warning('请选择课程包');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!formData.courseId) {
|
2026-03-18 14:46:32 +08:00
|
|
|
|
message.warning('课程包数据异常,请联系管理员');
|
2026-03-17 16:59:06 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
if (!formData.lessonType) {
|
|
|
|
|
|
message.warning('请选择课程类型');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
if (formData.classIds.length === 0) {
|
|
|
|
|
|
message.warning('请至少选择一个班级');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 检查每个班级是否都分配了教师
|
|
|
|
|
|
for (const classId of formData.classIds) {
|
|
|
|
|
|
if (!classTeacherMap.value[classId]) {
|
|
|
|
|
|
const cls = classes.value.find(c => c.id === classId);
|
|
|
|
|
|
message.warning(`请为 ${cls?.name} 分配教师`);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
case 3:
|
|
|
|
|
|
if (!formData.scheduledDate) {
|
|
|
|
|
|
message.warning('请选择排课日期');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!formData.scheduledTimeRange) {
|
|
|
|
|
|
message.warning('请选择时间段');
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 下一步
|
|
|
|
|
|
const nextStep = () => {
|
|
|
|
|
|
if (validateStep()) {
|
|
|
|
|
|
currentStep.value++;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 上一步
|
|
|
|
|
|
const prevStep = () => {
|
|
|
|
|
|
currentStep.value--;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 提交表单
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
|
if (!validateStep()) return;
|
|
|
|
|
|
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 格式化时间
|
|
|
|
|
|
let scheduledTime: string | undefined = undefined;
|
|
|
|
|
|
if (formData.scheduledTimeRange && formData.scheduledTimeRange.length === 2) {
|
|
|
|
|
|
scheduledTime = `${formData.scheduledTimeRange[0].format('HH:mm')}-${formData.scheduledTimeRange[1].format('HH:mm')}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个班级分别创建排课
|
|
|
|
|
|
const promises = formData.classIds.map(classId => {
|
|
|
|
|
|
return createSchedulesByClasses({
|
|
|
|
|
|
coursePackageId: formData.packageId!,
|
2026-03-18 14:46:32 +08:00
|
|
|
|
courseId: formData.courseId!, // 自动设置为课程包的第一门课程
|
2026-03-17 16:59:06 +08:00
|
|
|
|
lessonType: formData.lessonType!,
|
|
|
|
|
|
classIds: [classId],
|
|
|
|
|
|
teacherId: classTeacherMap.value[classId],
|
|
|
|
|
|
scheduledDate: formData.scheduledDate!.format('YYYY-MM-DD'),
|
|
|
|
|
|
scheduledTime,
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await Promise.all(promises);
|
|
|
|
|
|
|
|
|
|
|
|
message.success(`成功创建 ${formData.classIds.length} 条排课`);
|
|
|
|
|
|
visible.value = false;
|
|
|
|
|
|
emit('success');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
message.error('创建失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 取消
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
|
visible.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
defineExpose({ open });
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.steps-navigator {
|
|
|
|
|
|
margin-bottom: 32px;
|
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-content {
|
|
|
|
|
|
min-height: 400px;
|
|
|
|
|
|
padding: 0 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-panel {
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
color: #2D3436;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.collection-option {
|
|
|
|
|
|
.collection-name {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
.collection-info {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.packages-section {
|
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.packages-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.package-card {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border: 2px solid #E0E0E0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #BDBDBD;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
border-color: #FF8C42;
|
|
|
|
|
|
background: #FFF0E6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.package-name {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #2D3436;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.package-grade {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
margin-bottom: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.package-count {
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #FF8C42;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.schedule-ref-card {
|
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
background: #FFF8F0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
.ref-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.ref-icon {
|
|
|
|
|
|
color: #FF8C42;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.ref-title {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #2D3436;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lesson-type-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.lesson-type-card {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border: 2px solid #E0E0E0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.3s;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #FF8C42;
|
|
|
|
|
|
transform: translateY(-2px);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(255, 140, 66, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
border-color: #FF8C42;
|
|
|
|
|
|
background: #FFF0E6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-icon {
|
|
|
|
|
|
font-size: 32px;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-name {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #2D3436;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-count {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.grade-selector {
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.class-teacher-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
max-height: 400px;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
padding: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.class-teacher-card {
|
|
|
|
|
|
background: white;
|
|
|
|
|
|
border: 2px solid #E0E0E0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
transition: all 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
&.selected {
|
|
|
|
|
|
border-color: #FF8C42;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.class-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
.class-info {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
|
|
|
|
|
|
.class-name {
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #2D3436;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.class-detail {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
margin-top: 2px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.teacher-selector {
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
border-top: 1px solid #E0E0E0;
|
|
|
|
|
|
background: #FAFAFA;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selection-summary {
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
background: #FFF8F0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
color: #FF8C42;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.confirm-info {
|
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
background: #F5F5F5;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
|
|
|
|
|
|
div {
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
strong {
|
|
|
|
|
|
color: #FF8C42;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.class-teacher-list {
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
h4 {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.teacher-status {
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
|
|
|
|
|
|
&.assigned {
|
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-footer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|