feat: 教师端预约上课与学校端新建排课流程一致
- 后端:教师可访问课程套餐/课程包/lesson-types API - 后端:TeacherScheduleServiceImpl 支持 coursePackageId、lessonType - 前端:新增 TeacherCreateScheduleModal 四步流程(选择课程包→课程类型→班级→时间) - 前端:ScheduleView 集成新弹窗 - 前端:课程中心 PrepareModeView 预约上课使用相同四步流程,支持预设 Made-with: Cursor
This commit is contained in:
parent
3f6696d7bb
commit
824ce7ad80
@ -265,14 +265,22 @@ export const getPackageUsage = () =>
|
|||||||
|
|
||||||
// ==================== 套餐管理(两层结构) ====================
|
// ==================== 套餐管理(两层结构) ====================
|
||||||
|
|
||||||
// 课程包项(对应后端 CourseCollectionResponse.CoursePackageItem)
|
// 课程包项(对应后端 CourseCollectionResponse.CoursePackageItem / CoursePackageResponse)
|
||||||
export interface CoursePackageItem {
|
export interface CoursePackageItem {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
gradeLevels: string[];
|
gradeLevels: string[] | string;
|
||||||
courseCount: number;
|
courseCount: number;
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
|
/** getPackagesByCollection 返回的课程包包含课程列表 */
|
||||||
|
courses?: Array<{
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
gradeLevel?: string;
|
||||||
|
sortOrder?: number;
|
||||||
|
scheduleRefData?: string;
|
||||||
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 课程套餐(最上层,对应后端 CourseCollectionResponse)
|
// 课程套餐(最上层,对应后端 CourseCollectionResponse)
|
||||||
|
|||||||
@ -577,8 +577,11 @@ export interface TeacherSchedule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateTeacherScheduleDto {
|
export interface CreateTeacherScheduleDto {
|
||||||
|
name: string; // 计划名称,与学校端 SchedulePlanCreateRequest 一致
|
||||||
classId: number;
|
classId: number;
|
||||||
courseId: number;
|
courseId: number;
|
||||||
|
coursePackageId?: number; // 课程包 ID
|
||||||
|
lessonType?: string; // 课程类型 (INTRODUCTION/COLLECTIVE/LANGUAGE 等)
|
||||||
scheduledDate?: string;
|
scheduledDate?: string;
|
||||||
scheduledTime?: string;
|
scheduledTime?: string;
|
||||||
weekDay?: number;
|
weekDay?: number;
|
||||||
|
|||||||
@ -87,36 +87,8 @@
|
|||||||
:file-name="previewFileName"
|
:file-name="previewFileName"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 预约上课弹窗 -->
|
<!-- 预约上课弹窗(四步流程,与课表页一致) -->
|
||||||
<a-modal
|
<TeacherCreateScheduleModal ref="scheduleModalRef" @success="onScheduleSuccess" />
|
||||||
v-model:open="scheduleModalVisible"
|
|
||||||
title="预约上课"
|
|
||||||
@ok="confirmSchedule"
|
|
||||||
:confirmLoading="scheduleLoading"
|
|
||||||
okText="确认预约"
|
|
||||||
cancelText="取消"
|
|
||||||
>
|
|
||||||
<a-form layout="vertical">
|
|
||||||
<a-form-item label="授课班级">
|
|
||||||
<span>{{ selectedClassInfo?.name || '未选择' }}</span>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="计划上课时间" required>
|
|
||||||
<a-date-picker
|
|
||||||
v-model:value="scheduleDate"
|
|
||||||
show-time
|
|
||||||
format="YYYY-MM-DD HH:mm"
|
|
||||||
placeholder="选择计划上课时间"
|
|
||||||
style="width: 100%;"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-alert
|
|
||||||
message="预约成功后,可在【上课记录】中查看和管理预约"
|
|
||||||
type="info"
|
|
||||||
show-icon
|
|
||||||
style="margin-top: 8px;"
|
|
||||||
/>
|
|
||||||
</a-form>
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -137,6 +109,7 @@ import { translateGradeTags } from '@/utils/tagMaps';
|
|||||||
import FilePreviewModal from '@/components/FilePreviewModal.vue';
|
import FilePreviewModal from '@/components/FilePreviewModal.vue';
|
||||||
import PrepareNavigation from './components/PrepareNavigation.vue';
|
import PrepareNavigation from './components/PrepareNavigation.vue';
|
||||||
import PreparePreview from './components/PreparePreview.vue';
|
import PreparePreview from './components/PreparePreview.vue';
|
||||||
|
import TeacherCreateScheduleModal from '@/views/teacher/schedule/components/TeacherCreateScheduleModal.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -161,9 +134,7 @@ const classes = ref<any[]>([]);
|
|||||||
const selectedClassId = ref<number>();
|
const selectedClassId = ref<number>();
|
||||||
|
|
||||||
// 预约上课相关
|
// 预约上课相关
|
||||||
const scheduleModalVisible = ref(false);
|
const scheduleModalRef = ref<InstanceType<typeof TeacherCreateScheduleModal>>();
|
||||||
const scheduleLoading = ref(false);
|
|
||||||
const scheduleDate = ref<dayjs.Dayjs | undefined>(undefined);
|
|
||||||
|
|
||||||
// 解析标签(后端可能返回 JSON 字符串或数组)
|
// 解析标签(后端可能返回 JSON 字符串或数组)
|
||||||
const parseTags = (val: any): string[] => {
|
const parseTags = (val: any): string[] => {
|
||||||
@ -416,41 +387,26 @@ const showScheduleModal = () => {
|
|||||||
message.warning('请先选择班级');
|
message.warning('请先选择班级');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduleDate.value = undefined;
|
const firstLesson = lessons.value[0];
|
||||||
scheduleModalVisible.value = true;
|
if (!firstLesson) {
|
||||||
};
|
message.warning('课程数据异常,暂无课程');
|
||||||
|
|
||||||
const confirmSchedule = async () => {
|
|
||||||
if (!scheduleDate.value) {
|
|
||||||
message.warning('请选择计划上课时间');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const pkgId = parseInt(courseId.value, 10);
|
||||||
scheduleLoading.value = true;
|
if (isNaN(pkgId)) {
|
||||||
try {
|
message.warning('课程 ID 无效');
|
||||||
const teacherId = userStore.user?.id;
|
return;
|
||||||
if (!teacherId) {
|
|
||||||
message.error('未获取到教师信息,请重新登录');
|
|
||||||
scheduleLoading.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dt = scheduleDate.value!;
|
|
||||||
await teacherApi.createLesson({
|
|
||||||
courseId: courseId.value,
|
|
||||||
classId: selectedClassId.value!,
|
|
||||||
teacherId,
|
|
||||||
title: course.value.name || '授课',
|
|
||||||
lessonDate: dt.format('YYYY-MM-DD'),
|
|
||||||
startTime: dt.format('HH:mm'),
|
|
||||||
});
|
|
||||||
|
|
||||||
message.success('预约成功,可在"上课记录"中查看');
|
|
||||||
scheduleModalVisible.value = false;
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.message || '预约失败');
|
|
||||||
} finally {
|
|
||||||
scheduleLoading.value = false;
|
|
||||||
}
|
}
|
||||||
|
scheduleModalRef.value?.openWithPreset({
|
||||||
|
packageId: pkgId,
|
||||||
|
courseId: firstLesson.id,
|
||||||
|
lessonType: firstLesson.lessonType || 'INTRODUCTION',
|
||||||
|
classId: selectedClassId.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onScheduleSuccess = () => {
|
||||||
|
message.success('预约成功,可在"我的课表"中查看');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExit = () => {
|
const handleExit = () => {
|
||||||
|
|||||||
@ -129,66 +129,8 @@
|
|||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 预约上课弹窗 -->
|
<!-- 预约上课弹窗(四步流程,与学校端新建排课一致) -->
|
||||||
<a-modal
|
<TeacherCreateScheduleModal ref="createScheduleModalRef" @success="onCreateScheduleSuccess" />
|
||||||
v-model:open="createModalVisible"
|
|
||||||
title="预约上课"
|
|
||||||
:confirm-loading="createModalLoading"
|
|
||||||
@ok="handleCreateSubmit"
|
|
||||||
@cancel="createModalVisible = false"
|
|
||||||
width="600px"
|
|
||||||
>
|
|
||||||
<a-form
|
|
||||||
ref="createFormRef"
|
|
||||||
:model="createFormState"
|
|
||||||
:rules="createFormRules"
|
|
||||||
:label-col="{ span: 6 }"
|
|
||||||
:wrapper-col="{ span: 16 }"
|
|
||||||
>
|
|
||||||
<a-form-item label="班级" name="classId">
|
|
||||||
<a-select v-model:value="createFormState.classId" placeholder="选择班级">
|
|
||||||
<a-select-option v-for="cls in myClasses" :key="cls.id" :value="cls.id">
|
|
||||||
{{ cls.name }}
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="课程" name="courseId">
|
|
||||||
<a-select
|
|
||||||
v-model:value="createFormState.courseId"
|
|
||||||
placeholder="选择课程"
|
|
||||||
showSearch
|
|
||||||
:filterOption="filterCourseOption"
|
|
||||||
>
|
|
||||||
<a-select-option v-for="course in availableCourses" :key="course.id" :value="course.id">
|
|
||||||
{{ course.name }}
|
|
||||||
</a-select-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="预约日期" name="scheduledDate">
|
|
||||||
<a-date-picker v-model:value="createFormState.scheduledDate" style="width: 100%" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="时间段" name="scheduledTimeRange">
|
|
||||||
<a-time-range-picker
|
|
||||||
v-model:value="createFormState.scheduledTimeRange"
|
|
||||||
format="HH:mm"
|
|
||||||
style="width: 100%"
|
|
||||||
:placeholder="['开始时间', '结束时间']"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="重复方式" name="repeatType">
|
|
||||||
<a-radio-group v-model:value="createFormState.repeatType">
|
|
||||||
<a-radio value="NONE">单次</a-radio>
|
|
||||||
<a-radio value="WEEKLY">每周重复</a-radio>
|
|
||||||
</a-radio-group>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="createFormState.repeatType === 'WEEKLY'" label="重复截止" name="repeatEndDate">
|
|
||||||
<a-date-picker v-model:value="createFormState.repeatEndDate" style="width: 100%" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="备注" name="note">
|
|
||||||
<a-textarea v-model:value="createFormState.note" :rows="2" placeholder="备注信息" />
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-modal>
|
|
||||||
|
|
||||||
<!-- 排课详情弹窗 -->
|
<!-- 排课详情弹窗 -->
|
||||||
<a-modal
|
<a-modal
|
||||||
@ -254,11 +196,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import type { FormInstance } from 'ant-design-vue';
|
import dayjs from 'dayjs';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
|
||||||
import {
|
import {
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
CalendarOutlined,
|
CalendarOutlined,
|
||||||
@ -268,13 +209,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
getTeacherTimetable,
|
getTeacherTimetable,
|
||||||
getTodayTeacherSchedules,
|
getTodayTeacherSchedules,
|
||||||
createTeacherSchedule,
|
|
||||||
cancelTeacherSchedule,
|
cancelTeacherSchedule,
|
||||||
|
startLessonFromSchedule,
|
||||||
type TeacherSchedule,
|
type TeacherSchedule,
|
||||||
type CreateTeacherScheduleDto,
|
|
||||||
} from '@/api/teacher';
|
} from '@/api/teacher';
|
||||||
import { getTeacherClasses, getTeacherCourses, startLessonFromSchedule } from '@/api/teacher';
|
|
||||||
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||||
|
import TeacherCreateScheduleModal from './components/TeacherCreateScheduleModal.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -282,35 +222,14 @@ const router = useRouter();
|
|||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const todaySchedules = ref<TeacherSchedule[]>([]);
|
const todaySchedules = ref<TeacherSchedule[]>([]);
|
||||||
const timetable = ref<any[]>([]);
|
const timetable = ref<any[]>([]);
|
||||||
const myClasses = ref<any[]>([]);
|
|
||||||
const availableCourses = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 当前周
|
// 当前周
|
||||||
const currentWeekStart = ref(dayjs().startOf('week').add(1, 'day')); // 周一
|
const currentWeekStart = ref(dayjs().startOf('week').add(1, 'day')); // 周一
|
||||||
|
|
||||||
// 弹窗
|
// 弹窗
|
||||||
const createModalVisible = ref(false);
|
const createScheduleModalRef = ref<InstanceType<typeof TeacherCreateScheduleModal>>();
|
||||||
const createModalLoading = ref(false);
|
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
const selectedSchedule = ref<TeacherSchedule | null>(null);
|
const selectedSchedule = ref<TeacherSchedule | null>(null);
|
||||||
const createFormRef = ref<FormInstance>();
|
|
||||||
|
|
||||||
// 表单
|
|
||||||
const createFormState = reactive<CreateTeacherScheduleDto & { scheduledDate?: Dayjs; repeatEndDate?: Dayjs; scheduledTimeRange?: [Dayjs, Dayjs] }>({
|
|
||||||
classId: undefined as any,
|
|
||||||
courseId: undefined as any,
|
|
||||||
scheduledDate: undefined,
|
|
||||||
scheduledTimeRange: undefined,
|
|
||||||
repeatType: 'NONE',
|
|
||||||
repeatEndDate: undefined,
|
|
||||||
note: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createFormRules = {
|
|
||||||
classId: [{ required: true, message: '请选择班级' }],
|
|
||||||
courseId: [{ required: true, message: '请选择课程' }],
|
|
||||||
scheduledDate: [{ required: true, message: '请选择预约日期' }],
|
|
||||||
};
|
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const weekRangeText = computed(() => {
|
const weekRangeText = computed(() => {
|
||||||
@ -366,23 +285,6 @@ const loadTodaySchedules = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadBaseData = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const [classesRes, coursesRes] = await Promise.all([
|
|
||||||
getTeacherClasses(),
|
|
||||||
getTeacherCourses({ pageNum: 1, pageSize: 100 }),
|
|
||||||
]);
|
|
||||||
myClasses.value = classesRes || [];
|
|
||||||
availableCourses.value = coursesRes.items || [];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('加载基础数据失败', error);
|
|
||||||
message.error('加载基础数据失败');
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 周次导航
|
// 周次导航
|
||||||
const goToPrevWeek = () => {
|
const goToPrevWeek = () => {
|
||||||
currentWeekStart.value = currentWeekStart.value.subtract(7, 'day');
|
currentWeekStart.value = currentWeekStart.value.subtract(7, 'day');
|
||||||
@ -399,50 +301,14 @@ const goToCurrentWeek = () => {
|
|||||||
loadTimetable();
|
loadTimetable();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 预约上课
|
// 预约上课(打开四步弹窗)
|
||||||
const showCreateModal = () => {
|
const showCreateModal = () => {
|
||||||
Object.assign(createFormState, {
|
createScheduleModalRef.value?.open();
|
||||||
classId: undefined,
|
|
||||||
courseId: undefined,
|
|
||||||
scheduledDate: undefined,
|
|
||||||
scheduledTimeRange: undefined,
|
|
||||||
repeatType: 'NONE',
|
|
||||||
repeatEndDate: undefined,
|
|
||||||
note: undefined,
|
|
||||||
});
|
|
||||||
createModalVisible.value = true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateSubmit = async () => {
|
const onCreateScheduleSuccess = () => {
|
||||||
try {
|
loadTimetable();
|
||||||
await createFormRef.value?.validate();
|
loadTodaySchedules();
|
||||||
createModalLoading.value = true;
|
|
||||||
|
|
||||||
// 格式化时间范围
|
|
||||||
let scheduledTime: string | undefined = undefined;
|
|
||||||
if (createFormState.scheduledTimeRange && createFormState.scheduledTimeRange.length === 2) {
|
|
||||||
scheduledTime = `${createFormState.scheduledTimeRange[0].format('HH:mm')}-${createFormState.scheduledTimeRange[1].format('HH:mm')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await createTeacherSchedule({
|
|
||||||
classId: createFormState.classId,
|
|
||||||
courseId: createFormState.courseId,
|
|
||||||
scheduledDate: createFormState.scheduledDate?.format('YYYY-MM-DD'),
|
|
||||||
scheduledTime,
|
|
||||||
repeatType: createFormState.repeatType,
|
|
||||||
repeatEndDate: createFormState.repeatEndDate?.format('YYYY-MM-DD'),
|
|
||||||
note: createFormState.note,
|
|
||||||
});
|
|
||||||
|
|
||||||
message.success('预约成功');
|
|
||||||
createModalVisible.value = false;
|
|
||||||
loadTimetable();
|
|
||||||
loadTodaySchedules();
|
|
||||||
} catch (error) {
|
|
||||||
message.error('预约失败');
|
|
||||||
} finally {
|
|
||||||
createModalLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 详情
|
// 详情
|
||||||
@ -509,7 +375,6 @@ const getLessonStatusText = (status: string | undefined) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadBaseData();
|
|
||||||
loadTimetable();
|
loadTimetable();
|
||||||
loadTodaySchedules();
|
loadTodaySchedules();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,611 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<div v-if="selectedCollection && selectedCollection.packages && selectedCollection.packages.length > 0" class="packages-section">
|
||||||
|
<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>
|
||||||
|
<div class="package-grade">{{ Array.isArray(pkg.gradeLevels) ? pkg.gradeLevels.join(', ') : pkg.gradeLevels }}</div>
|
||||||
|
<div class="package-count">{{ pkg.courseCount }} 门课程</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="selectedCollection" class="packages-section">
|
||||||
|
<h4>选择课程包</h4>
|
||||||
|
<a-alert message="该套餐暂无课程包" type="warning" show-icon />
|
||||||
|
</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 v-if="!loadingLessonTypes && lessonTypes.length === 0" class="lesson-type-empty">
|
||||||
|
该课程包暂无课程类型,请先选择其他课程包
|
||||||
|
</div>
|
||||||
|
<div v-else class="lesson-type-grid">
|
||||||
|
<div
|
||||||
|
v-for="type in lessonTypes"
|
||||||
|
:key="type.lessonType"
|
||||||
|
:class="['lesson-type-card', { active: formData.lessonType === type.lessonType }]"
|
||||||
|
:style="getLessonTagStyle(type.lessonType)"
|
||||||
|
@click="selectLessonType(type.lessonType)"
|
||||||
|
>
|
||||||
|
<div class="type-icon">
|
||||||
|
<component :is="getLessonTypeIconComponent(type.lessonType)" />
|
||||||
|
</div>
|
||||||
|
<div class="type-name">{{ getLessonTypeName(type.lessonType) }}</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="class-select-grid">
|
||||||
|
<div
|
||||||
|
v-for="cls in myClasses"
|
||||||
|
:key="cls.id"
|
||||||
|
:class="['class-card', { active: formData.classId === cls.id }]"
|
||||||
|
@click="formData.classId = cls.id"
|
||||||
|
>
|
||||||
|
<div class="class-name">{{ cls.name }}</div>
|
||||||
|
<div class="class-detail">{{ cls.studentCount }} 名学生</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="myClasses.length === 0" class="empty-hint">暂无可用班级</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>{{ selectedClassName }}</strong> 创建排课</div>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<div>课程类型: {{ getSelectedLessonTypeName() }}</div>
|
||||||
|
<div>排课日期: {{ formData.scheduledDate?.format('YYYY-MM-DD') || '-' }}</div>
|
||||||
|
<div>时间段: {{ getSelectedTimeRange() || '-' }}</div>
|
||||||
|
</template>
|
||||||
|
</a-alert>
|
||||||
|
</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 } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import {
|
||||||
|
BookOutlined,
|
||||||
|
TeamOutlined,
|
||||||
|
HeartOutlined,
|
||||||
|
SoundOutlined,
|
||||||
|
UsergroupAddOutlined,
|
||||||
|
ExperimentOutlined,
|
||||||
|
BgColorsOutlined,
|
||||||
|
} from '@ant-design/icons-vue';
|
||||||
|
import {
|
||||||
|
getCourseCollections,
|
||||||
|
getCourseCollectionPackages,
|
||||||
|
getCoursePackageLessonTypes,
|
||||||
|
type CourseCollection,
|
||||||
|
type CoursePackageItem,
|
||||||
|
type LessonTypeInfo,
|
||||||
|
} from '@/api/school';
|
||||||
|
import { getTeacherClasses, createTeacherSchedule, type TeacherClass } from '@/api/teacher';
|
||||||
|
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||||
|
|
||||||
|
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 lessonTypes = ref<LessonTypeInfo[]>([]);
|
||||||
|
const myClasses = ref<TeacherClass[]>([]);
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
collectionId?: number;
|
||||||
|
packageId?: number;
|
||||||
|
courseId?: number;
|
||||||
|
lessonType?: string;
|
||||||
|
classId?: number;
|
||||||
|
scheduledDate?: Dayjs;
|
||||||
|
scheduledTimeRange?: [Dayjs, Dayjs];
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = reactive<FormData>({});
|
||||||
|
|
||||||
|
const selectedCollection = computed(() => {
|
||||||
|
if (!formData.collectionId) return null;
|
||||||
|
return collections.value.find(c => c.id === formData.collectionId) || null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedClassName = computed(() => {
|
||||||
|
if (!formData.classId) return '-';
|
||||||
|
const cls = myClasses.value.find(c => c.id === formData.classId);
|
||||||
|
return cls?.name || '-';
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 从课程中心打开时的预设(跳过步骤1、2) */
|
||||||
|
export interface SchedulePreset {
|
||||||
|
packageId: number;
|
||||||
|
courseId: number;
|
||||||
|
lessonType: string;
|
||||||
|
classId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
visible.value = true;
|
||||||
|
currentStep.value = 0;
|
||||||
|
resetForm();
|
||||||
|
loadCollections();
|
||||||
|
loadMyClasses();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 从课程中心打开,预填课程包、课程类型、班级,直接进入选择班级或设置时间 */
|
||||||
|
const openWithPreset = async (preset: SchedulePreset) => {
|
||||||
|
visible.value = true;
|
||||||
|
resetForm();
|
||||||
|
formData.packageId = preset.packageId;
|
||||||
|
formData.courseId = preset.courseId;
|
||||||
|
formData.lessonType = preset.lessonType;
|
||||||
|
formData.classId = preset.classId;
|
||||||
|
|
||||||
|
await loadMyClasses();
|
||||||
|
await loadLessonTypes(preset.packageId);
|
||||||
|
|
||||||
|
if (preset.classId) {
|
||||||
|
currentStep.value = 3; // 直接到设置时间
|
||||||
|
} else {
|
||||||
|
currentStep.value = 2; // 到选择班级
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.collectionId = undefined;
|
||||||
|
formData.packageId = undefined;
|
||||||
|
formData.courseId = undefined;
|
||||||
|
formData.lessonType = undefined;
|
||||||
|
formData.classId = undefined;
|
||||||
|
formData.scheduledDate = undefined;
|
||||||
|
formData.scheduledTimeRange = undefined;
|
||||||
|
lessonTypes.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadCollections = async () => {
|
||||||
|
try {
|
||||||
|
collections.value = await getCourseCollections();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载课程套餐失败:', error);
|
||||||
|
message.error('加载课程套餐失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadMyClasses = async () => {
|
||||||
|
try {
|
||||||
|
myClasses.value = await getTeacherClasses();
|
||||||
|
} catch (error) {
|
||||||
|
message.error('加载班级失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCollectionChange = async (collectionId: number) => {
|
||||||
|
formData.packageId = undefined;
|
||||||
|
formData.courseId = undefined;
|
||||||
|
formData.lessonType = undefined;
|
||||||
|
lessonTypes.value = [];
|
||||||
|
|
||||||
|
if (!collectionId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const packages = await getCourseCollectionPackages(collectionId);
|
||||||
|
const collection = collections.value.find(c => c.id === collectionId);
|
||||||
|
if (collection) {
|
||||||
|
collection.packages = packages as CoursePackageItem[];
|
||||||
|
}
|
||||||
|
if (!packages || packages.length === 0) {
|
||||||
|
message.warning('该套餐暂无课程包');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载课程包失败:', error);
|
||||||
|
message.error('加载课程包失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectPackage = async (packageId: number) => {
|
||||||
|
formData.packageId = packageId;
|
||||||
|
|
||||||
|
const collection = selectedCollection.value;
|
||||||
|
if (collection?.packages) {
|
||||||
|
const pkg = collection.packages.find((p: any) => p.id === packageId);
|
||||||
|
if (pkg?.courses && pkg.courses.length > 0) {
|
||||||
|
formData.courseId = pkg.courses[0].id;
|
||||||
|
} else {
|
||||||
|
formData.courseId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = (lessonType: string) => {
|
||||||
|
formData.lessonType = lessonType;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 lessonTypeIconMap: Record<string, any> = {
|
||||||
|
INTRODUCTION: BookOutlined,
|
||||||
|
INTRO: BookOutlined,
|
||||||
|
COLLECTIVE: TeamOutlined,
|
||||||
|
LANGUAGE: SoundOutlined,
|
||||||
|
DOMAIN_LANGUAGE: SoundOutlined,
|
||||||
|
SOCIETY: UsergroupAddOutlined,
|
||||||
|
SOCIAL: UsergroupAddOutlined,
|
||||||
|
DOMAIN_SOCIAL: UsergroupAddOutlined,
|
||||||
|
SCIENCE: ExperimentOutlined,
|
||||||
|
DOMAIN_SCIENCE: ExperimentOutlined,
|
||||||
|
ART: BgColorsOutlined,
|
||||||
|
DOMAIN_ART: BgColorsOutlined,
|
||||||
|
HEALTH: HeartOutlined,
|
||||||
|
DOMAIN_HEALTH: HeartOutlined,
|
||||||
|
MUSIC: SoundOutlined,
|
||||||
|
SPORT: HeartOutlined,
|
||||||
|
OUTDOOR: TeamOutlined,
|
||||||
|
};
|
||||||
|
const getLessonTypeIconComponent = (type: string) => lessonTypeIconMap[type] || BookOutlined;
|
||||||
|
|
||||||
|
const getSelectedLessonTypeName = (): string => {
|
||||||
|
if (!formData.lessonType) return '-';
|
||||||
|
return getLessonTypeName(formData.lessonType);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectedTimeRange = (): string => {
|
||||||
|
if (!formData.scheduledTimeRange) return '';
|
||||||
|
return `${formData.scheduledTimeRange[0].format('HH:mm')}-${formData.scheduledTimeRange[1].format('HH:mm')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
message.warning('课程包数据异常,请联系管理员');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (!formData.lessonType) {
|
||||||
|
message.warning('请选择课程类型');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (!formData.classId) {
|
||||||
|
message.warning('请选择班级');
|
||||||
|
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;
|
||||||
|
if (formData.scheduledTimeRange && formData.scheduledTimeRange.length === 2) {
|
||||||
|
scheduledTime = `${formData.scheduledTimeRange[0].format('HH:mm')}-${formData.scheduledTimeRange[1].format('HH:mm')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateStr = formData.scheduledDate!.format('YYYY-MM-DD');
|
||||||
|
const lessonTypeName = getSelectedLessonTypeName();
|
||||||
|
const name = `${lessonTypeName} - ${dateStr}`;
|
||||||
|
|
||||||
|
await createTeacherSchedule({
|
||||||
|
name,
|
||||||
|
classId: formData.classId!,
|
||||||
|
courseId: formData.courseId!,
|
||||||
|
coursePackageId: formData.packageId,
|
||||||
|
lessonType: formData.lessonType,
|
||||||
|
scheduledDate: dateStr,
|
||||||
|
scheduledTime,
|
||||||
|
repeatType: 'NONE',
|
||||||
|
note: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
message.success('预约成功');
|
||||||
|
visible.value = false;
|
||||||
|
emit('success');
|
||||||
|
} catch (error) {
|
||||||
|
message.error('预约失败');
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
visible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ open, openWithPreset });
|
||||||
|
</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: #722ed1;
|
||||||
|
background: #f9f0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: #722ed1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-type-empty {
|
||||||
|
padding: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-type-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lesson-type-card {
|
||||||
|
padding: 20px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 12px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: rgba(0, 0, 0, 0.2);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: #43e97b !important;
|
||||||
|
box-shadow: 0 0 0 3px rgba(67, 233, 123, 0.4) !important;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-icon { font-size: 32px; margin-bottom: 8px; opacity: 0.9; }
|
||||||
|
.type-name { font-weight: 500; margin-bottom: 4px; }
|
||||||
|
.type-count { font-size: 12px; opacity: 0.85; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-select-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-card {
|
||||||
|
padding: 16px;
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #E0E0E0;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover { border-color: #BDBDBD; }
|
||||||
|
&.active {
|
||||||
|
border-color: #722ed1;
|
||||||
|
background: #f9f0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.class-name { font-weight: 500; color: #2D3436; margin-bottom: 4px; }
|
||||||
|
.class-detail { font-size: 12px; color: #999; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
padding: 40px;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-info {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #F5F5F5;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
div { margin-bottom: 4px; }
|
||||||
|
div:last-child { margin-bottom: 0; }
|
||||||
|
strong { color: #722ed1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -37,7 +37,7 @@ public class SchoolPackageController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@Operation(summary = "查询租户课程套餐(两层结构-最上层)")
|
@Operation(summary = "查询租户课程套餐(两层结构-最上层)")
|
||||||
@RequireRole(UserRole.SCHOOL)
|
@RequireRole({UserRole.SCHOOL, UserRole.TEACHER})
|
||||||
public Result<List<CourseCollectionResponse>> findTenantCollections() {
|
public Result<List<CourseCollectionResponse>> findTenantCollections() {
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
return Result.success(collectionService.findTenantCollections(tenantId));
|
return Result.success(collectionService.findTenantCollections(tenantId));
|
||||||
@ -45,7 +45,7 @@ public class SchoolPackageController {
|
|||||||
|
|
||||||
@GetMapping("/{collectionId}/packages")
|
@GetMapping("/{collectionId}/packages")
|
||||||
@Operation(summary = "获取课程套餐下的课程包列表")
|
@Operation(summary = "获取课程套餐下的课程包列表")
|
||||||
@RequireRole(UserRole.SCHOOL)
|
@RequireRole({UserRole.SCHOOL, UserRole.TEACHER})
|
||||||
public Result<List<CoursePackageResponse>> getPackagesByCollection(@PathVariable Long collectionId) {
|
public Result<List<CoursePackageResponse>> getPackagesByCollection(@PathVariable Long collectionId) {
|
||||||
return Result.success(collectionService.getPackagesByCollection(collectionId));
|
return Result.success(collectionService.getPackagesByCollection(collectionId));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -139,6 +139,7 @@ public class SchoolScheduleController {
|
|||||||
|
|
||||||
@GetMapping("/course-packages/{id}/lesson-types")
|
@GetMapping("/course-packages/{id}/lesson-types")
|
||||||
@Operation(summary = "获取课程包的课程类型列表")
|
@Operation(summary = "获取课程包的课程类型列表")
|
||||||
|
@RequireRole({UserRole.SCHOOL, UserRole.TEACHER})
|
||||||
public Result<List<LessonTypeInfo>> getCoursePackageLessonTypes(@PathVariable Long id) {
|
public Result<List<LessonTypeInfo>> getCoursePackageLessonTypes(@PathVariable Long id) {
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
return Result.success(schoolScheduleService.getCoursePackageLessonTypes(tenantId, id));
|
return Result.success(schoolScheduleService.getCoursePackageLessonTypes(tenantId, id));
|
||||||
|
|||||||
@ -120,6 +120,8 @@ public class TeacherScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper,
|
|||||||
schedulePlan.setName(request.getName());
|
schedulePlan.setName(request.getName());
|
||||||
schedulePlan.setClassId(request.getClassId());
|
schedulePlan.setClassId(request.getClassId());
|
||||||
schedulePlan.setCourseId(request.getCourseId());
|
schedulePlan.setCourseId(request.getCourseId());
|
||||||
|
schedulePlan.setCoursePackageId(request.getCoursePackageId());
|
||||||
|
schedulePlan.setLessonType(request.getLessonType());
|
||||||
schedulePlan.setTeacherId(request.getTeacherId() != null ? request.getTeacherId() : teacherId);
|
schedulePlan.setTeacherId(request.getTeacherId() != null ? request.getTeacherId() : teacherId);
|
||||||
schedulePlan.setScheduledDate(request.getScheduledDate());
|
schedulePlan.setScheduledDate(request.getScheduledDate());
|
||||||
schedulePlan.setScheduledTime(request.getScheduledTime());
|
schedulePlan.setScheduledTime(request.getScheduledTime());
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user