feat: 学校端课程排期功能完善
- 排课计划参考:对齐管理端课程包详情,支持时间/课程类型/课程名称/区域活动/备注五列 - 支持两种 schedule_ref_data 格式(周排课表、课程类型说明) - 新建排课弹窗样式提取为 CreateScheduleModal.scss 修复 SASS 编译错误 - 切换视图(列表/课表/日历)时自动刷新数据 - 排课列表、课表、日历视图增加课程类型 tag 展示 - 后端:timetable/lesson-types 接口修复,LessonTypeEnum 补充类型 Made-with: Cursor
This commit is contained in:
parent
7743ae7a01
commit
829a70e448
@ -431,12 +431,15 @@ export const getStudentClassHistory = (studentId: number) =>
|
|||||||
|
|
||||||
// ==================== 排课管理 ====================
|
// ==================== 排课管理 ====================
|
||||||
|
|
||||||
// 课程类型枚举
|
// 课程类型枚举(与后端 LessonTypeEnum 对齐)
|
||||||
export type LessonType = 'INTRODUCTION' | 'COLLECTIVE' | 'LANGUAGE' | 'SOCIETY' | 'SCIENCE' | 'ART' | 'HEALTH';
|
export type LessonType =
|
||||||
|
| 'INTRODUCTION' | 'INTRO' | 'COLLECTIVE'
|
||||||
|
| 'LANGUAGE' | 'HEALTH' | 'SCIENCE' | 'SOCIAL' | 'SOCIETY' | 'ART'
|
||||||
|
| 'DOMAIN_HEALTH' | 'DOMAIN_LANGUAGE' | 'DOMAIN_SOCIAL' | 'DOMAIN_SCIENCE' | 'DOMAIN_ART';
|
||||||
|
|
||||||
// 课程类型信息
|
// 课程类型信息
|
||||||
export interface LessonTypeInfo {
|
export interface LessonTypeInfo {
|
||||||
lessonType: LessonType;
|
lessonType: string;
|
||||||
lessonTypeName: string;
|
lessonTypeName: string;
|
||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
@ -446,6 +449,8 @@ export interface DayScheduleItem {
|
|||||||
id: number;
|
id: number;
|
||||||
className: string;
|
className: string;
|
||||||
coursePackageName: string;
|
coursePackageName: string;
|
||||||
|
courseName?: string; // 兼容 coursePackageName 的别名
|
||||||
|
lessonType?: string;
|
||||||
lessonTypeName: string;
|
lessonTypeName: string;
|
||||||
teacherName: string;
|
teacherName: string;
|
||||||
scheduledTime: string;
|
scheduledTime: string;
|
||||||
|
|||||||
@ -123,6 +123,65 @@ export const DOMAIN_TAG_COLORS: Record<string, { bg: string; text: string }> = {
|
|||||||
数学: { bg: "#FFF8E1", text: "#F9A825" },
|
数学: { bg: "#FFF8E1", text: "#F9A825" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ==================== 课程环节类型映射(与课程列表 tag 一致) ====================
|
||||||
|
|
||||||
|
export const LESSON_TYPE_NAMES: Record<string, string> = {
|
||||||
|
INTRODUCTION: "导入课",
|
||||||
|
INTRO: "导入课",
|
||||||
|
COLLECTIVE: "集体课",
|
||||||
|
LANGUAGE: "语言",
|
||||||
|
HEALTH: "健康",
|
||||||
|
SCIENCE: "科学",
|
||||||
|
SOCIAL: "社会",
|
||||||
|
ART: "艺术",
|
||||||
|
DOMAIN_HEALTH: "健康",
|
||||||
|
DOMAIN_LANGUAGE: "语言",
|
||||||
|
DOMAIN_SOCIAL: "社会",
|
||||||
|
DOMAIN_SCIENCE: "科学",
|
||||||
|
DOMAIN_ART: "艺术",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LESSON_TYPE_COLORS: Record<string, { bg: string; text: string }> = {
|
||||||
|
INTRODUCTION: { bg: "#E8F5E9", text: "#2E7D32" },
|
||||||
|
INTRO: { bg: "#E8F5E9", text: "#2E7D32" },
|
||||||
|
COLLECTIVE: { bg: "#E3F2FD", text: "#1565C0" },
|
||||||
|
LANGUAGE: { bg: "#F3E5F5", text: "#7B1FA2" },
|
||||||
|
HEALTH: { bg: "#FFEBEE", text: "#C62828" },
|
||||||
|
SCIENCE: { bg: "#E8F5E9", text: "#388E3C" },
|
||||||
|
SOCIAL: { bg: "#E0F7FA", text: "#00838F" },
|
||||||
|
ART: { bg: "#FFF3E0", text: "#E65100" },
|
||||||
|
DOMAIN_HEALTH: { bg: "#FFEBEE", text: "#C62828" },
|
||||||
|
DOMAIN_LANGUAGE: { bg: "#F3E5F5", text: "#7B1FA2" },
|
||||||
|
DOMAIN_SOCIAL: { bg: "#E0F7FA", text: "#00838F" },
|
||||||
|
DOMAIN_SCIENCE: { bg: "#E8F5E9", text: "#388E3C" },
|
||||||
|
DOMAIN_ART: { bg: "#FFF3E0", text: "#E65100" },
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程环节类型的中文名称
|
||||||
|
*/
|
||||||
|
export function getLessonTypeName(type: string): string {
|
||||||
|
return LESSON_TYPE_NAMES[type] || type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取课程环节类型标签样式(与课程卡片 tag 一致)
|
||||||
|
*/
|
||||||
|
export function getLessonTagStyle(type: string): {
|
||||||
|
background: string;
|
||||||
|
color: string;
|
||||||
|
border: string;
|
||||||
|
} {
|
||||||
|
const colors = LESSON_TYPE_COLORS[type] || { bg: "#F5F5F5", text: "#666" };
|
||||||
|
return {
|
||||||
|
background: colors.bg,
|
||||||
|
color: colors.text,
|
||||||
|
border: "none",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 年级/领域 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换年级标签为中文
|
* 转换年级标签为中文
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -228,6 +228,8 @@ import {
|
|||||||
translateDomainTags,
|
translateDomainTags,
|
||||||
getGradeTagStyle,
|
getGradeTagStyle,
|
||||||
getDomainTagStyle,
|
getDomainTagStyle,
|
||||||
|
getLessonTypeName,
|
||||||
|
getLessonTagStyle,
|
||||||
} from '@/utils/tagMaps';
|
} from '@/utils/tagMaps';
|
||||||
import { parseGradeLevels } from '@/api/collections';
|
import { parseGradeLevels } from '@/api/collections';
|
||||||
import * as schoolApi from '@/api/school';
|
import * as schoolApi from '@/api/school';
|
||||||
@ -262,30 +264,6 @@ const DOMAIN_TO_CODE: Record<string, string> = {
|
|||||||
艺术: 'ART',
|
艺术: 'ART',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 课程环节类型映射
|
|
||||||
const LESSON_TYPE_NAMES: Record<string, string> = {
|
|
||||||
INTRODUCTION: '导入课', INTRO: '导入课', COLLECTIVE: '集体课',
|
|
||||||
LANGUAGE: '语言', HEALTH: '健康', SCIENCE: '科学', SOCIAL: '社会', ART: '艺术',
|
|
||||||
DOMAIN_HEALTH: '健康', DOMAIN_LANGUAGE: '语言', DOMAIN_SOCIAL: '社会',
|
|
||||||
DOMAIN_SCIENCE: '科学', DOMAIN_ART: '艺术',
|
|
||||||
};
|
|
||||||
const getLessonTypeName = (type: string) => LESSON_TYPE_NAMES[type] || type;
|
|
||||||
|
|
||||||
const getLessonTagStyle = (type: string) => {
|
|
||||||
const colors: Record<string, { background: string; color: string }> = {
|
|
||||||
INTRODUCTION: { background: '#E8F5E9', color: '#2E7D32' }, INTRO: { background: '#E8F5E9', color: '#2E7D32' },
|
|
||||||
COLLECTIVE: { background: '#E3F2FD', color: '#1565C0' },
|
|
||||||
LANGUAGE: { background: '#F3E5F5', color: '#7B1FA2' }, HEALTH: { background: '#FFEBEE', color: '#C62828' },
|
|
||||||
SCIENCE: { background: '#E8F5E9', color: '#388E3C' }, SOCIAL: { background: '#E0F7FA', color: '#00838F' },
|
|
||||||
ART: { background: '#FFF3E0', color: '#E65100' },
|
|
||||||
DOMAIN_HEALTH: { background: '#FFEBEE', color: '#C62828' }, DOMAIN_LANGUAGE: { background: '#F3E5F5', color: '#7B1FA2' },
|
|
||||||
DOMAIN_SOCIAL: { background: '#E0F7FA', color: '#00838F' }, DOMAIN_SCIENCE: { background: '#E8F5E9', color: '#388E3C' },
|
|
||||||
DOMAIN_ART: { background: '#FFF3E0', color: '#E65100' },
|
|
||||||
};
|
|
||||||
const c = colors[type] || { background: '#F5F5F5', color: '#666' };
|
|
||||||
return { background: c.background, color: c.color, border: 'none' };
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFilterChange = () => {
|
const handleFilterChange = () => {
|
||||||
loadCourses();
|
loadCourses();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -83,7 +83,9 @@
|
|||||||
<div class="schedule-time">{{ item.scheduledTime }}</div>
|
<div class="schedule-time">{{ item.scheduledTime }}</div>
|
||||||
<div class="schedule-info">
|
<div class="schedule-info">
|
||||||
<div class="schedule-class">{{ item.className }}</div>
|
<div class="schedule-class">{{ item.className }}</div>
|
||||||
<div class="schedule-lesson">{{ item.courseName }}</div>
|
<div class="schedule-lesson">{{ item.coursePackageName || item.courseName }}</div>
|
||||||
|
<a-tag v-if="item.lessonType" size="small" class="schedule-lesson-type"
|
||||||
|
:style="getLessonTagStyle(item.lessonType)">{{ getLessonTypeName(item.lessonType) }}</a-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="day.schedules.length === 0" class="no-schedule">无排课</div>
|
<div v-if="day.schedules.length === 0" class="no-schedule">无排课</div>
|
||||||
@ -109,7 +111,10 @@
|
|||||||
<div class="item-time">{{ item.scheduledTime || '待定' }}</div>
|
<div class="item-time">{{ item.scheduledTime || '待定' }}</div>
|
||||||
<div class="item-info">
|
<div class="item-info">
|
||||||
<div class="item-class">{{ item.className }}</div>
|
<div class="item-class">{{ item.className }}</div>
|
||||||
<div class="item-lesson">{{ item.courseName }}</div>
|
<div class="item-lesson">{{ item.coursePackageName || item.courseName }}</div>
|
||||||
|
<a-tag v-if="item.lessonType" size="small" :style="getLessonTagStyle(item.lessonType)">
|
||||||
|
{{ getLessonTypeName(item.lessonType) }}
|
||||||
|
</a-tag>
|
||||||
<div class="item-teacher">{{ item.teacherName || '未分配' }}</div>
|
<div class="item-teacher">{{ item.teacherName || '未分配' }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-status">
|
<div class="item-status">
|
||||||
@ -137,6 +142,7 @@ import {
|
|||||||
type CalendarViewResponse,
|
type CalendarViewResponse,
|
||||||
type DayScheduleItem,
|
type DayScheduleItem,
|
||||||
} from '@/api/school';
|
} from '@/api/school';
|
||||||
|
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||||
|
|
||||||
const viewType = ref<'month' | 'week'>('month');
|
const viewType = ref<'month' | 'week'>('month');
|
||||||
const selectedClassId = ref<number | undefined>();
|
const selectedClassId = ref<number | undefined>();
|
||||||
@ -539,6 +545,10 @@ onMounted(() => {
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schedule-lesson-type {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
@change="loadSchedules"
|
@change="loadSchedules"
|
||||||
>
|
>
|
||||||
<a-select-option value="ACTIVE">有效</a-select-option>
|
<a-select-option value="ACTIVE">有效</a-select-option>
|
||||||
<a-select-option value="CANCELLED">已取消</a-select-option>
|
<a-select-option value="cancelled">已取消</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-space>
|
</a-space>
|
||||||
</div>
|
</div>
|
||||||
@ -56,6 +56,12 @@
|
|||||||
{{ formatDate(record.scheduledDate) }}
|
{{ formatDate(record.scheduledDate) }}
|
||||||
<span v-if="record.scheduledTime" class="time-slot">{{ record.scheduledTime }}</span>
|
<span v-if="record.scheduledTime" class="time-slot">{{ record.scheduledTime }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="column.key === 'lessonType'">
|
||||||
|
<a-tag v-if="record.lessonType" size="small" :style="getLessonTagStyle(record.lessonType)">
|
||||||
|
{{ getLessonTypeName(record.lessonType) }}
|
||||||
|
</a-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
<template v-if="column.key === 'status'">
|
<template v-if="column.key === 'status'">
|
||||||
<a-tag v-if="record.status === 'ACTIVE' || record.status === 'scheduled'" color="success">有效</a-tag>
|
<a-tag v-if="record.status === 'ACTIVE' || record.status === 'scheduled'" color="success">有效</a-tag>
|
||||||
<a-tag v-else color="error">已取消</a-tag>
|
<a-tag v-else color="error">已取消</a-tag>
|
||||||
@ -113,6 +119,12 @@
|
|||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="课程类型">
|
||||||
|
<a-tag v-if="editingSchedule?.lessonType" :style="getLessonTagStyle(editingSchedule.lessonType)">
|
||||||
|
{{ getLessonTypeName(editingSchedule.lessonType) }}
|
||||||
|
</a-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label="授课教师" name="teacherId">
|
<a-form-item label="授课教师" name="teacherId">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:value="formState.teacherId"
|
v-model:value="formState.teacherId"
|
||||||
@ -157,6 +169,7 @@ import {
|
|||||||
type ClassInfo,
|
type ClassInfo,
|
||||||
type Teacher,
|
type Teacher,
|
||||||
} from '@/api/school';
|
} from '@/api/school';
|
||||||
|
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@ -188,6 +201,7 @@ const pagination = reactive({
|
|||||||
const columns = [
|
const columns = [
|
||||||
{ title: '班级', dataIndex: 'className', key: 'className' },
|
{ title: '班级', dataIndex: 'className', key: 'className' },
|
||||||
{ title: '课程', dataIndex: 'courseName', key: 'courseName' },
|
{ title: '课程', dataIndex: 'courseName', key: 'courseName' },
|
||||||
|
{ title: '课程类型', key: 'lessonType', width: 120 },
|
||||||
{ title: '授课教师', dataIndex: 'teacherName', key: 'teacherName' },
|
{ title: '授课教师', dataIndex: 'teacherName', key: 'teacherName' },
|
||||||
{ title: '排课时间', key: 'scheduledDate' },
|
{ title: '排课时间', key: 'scheduledDate' },
|
||||||
{ title: '状态', dataIndex: 'status', key: 'status' },
|
{ title: '状态', dataIndex: 'status', key: 'status' },
|
||||||
|
|||||||
@ -74,17 +74,17 @@
|
|||||||
:class="{
|
:class="{
|
||||||
'school-schedule': schedule.source === 'SCHOOL',
|
'school-schedule': schedule.source === 'SCHOOL',
|
||||||
'teacher-schedule': schedule.source === 'TEACHER',
|
'teacher-schedule': schedule.source === 'TEACHER',
|
||||||
'cancelled': schedule.status === 'CANCELLED',
|
'cancelled': schedule.status === 'cancelled' || schedule.status === 'CANCELLED',
|
||||||
}"
|
}"
|
||||||
@click="showScheduleDetail(schedule)"
|
@click="showScheduleDetail(schedule)"
|
||||||
>
|
>
|
||||||
<div class="schedule-time">{{ schedule.scheduledTime || '待定' }}</div>
|
<div class="schedule-time">{{ schedule.scheduledTime || '待定' }}</div>
|
||||||
<div class="schedule-course">{{ schedule.courseName }}</div>
|
<div class="schedule-course">{{ schedule.courseName || '课程' }}</div>
|
||||||
<div class="schedule-class">{{ schedule.className }}</div>
|
<div class="schedule-class">{{ schedule.className || '班级' }}</div>
|
||||||
<div v-if="schedule.teacherName" class="schedule-teacher">
|
<div v-if="schedule.teacherName" class="schedule-teacher">{{ schedule.teacherName }}</div>
|
||||||
{{ schedule.teacherName }}
|
<a-tag v-if="schedule.lessonType" size="small" class="schedule-lesson-type"
|
||||||
</div>
|
:style="getLessonTagStyle(schedule.lessonType)">{{ getLessonTypeName(schedule.lessonType) }}</a-tag>
|
||||||
<a-tag v-if="schedule.status === 'CANCELLED'" color="error" size="small">已取消</a-tag>
|
<a-tag v-if="schedule.status === 'cancelled' || schedule.status === 'CANCELLED'" color="error" size="small">已取消</a-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!day.schedules.length" class="empty-day">
|
<div v-if="!day.schedules.length" class="empty-day">
|
||||||
暂无排课
|
暂无排课
|
||||||
@ -104,13 +104,19 @@
|
|||||||
>
|
>
|
||||||
<template v-if="selectedSchedule">
|
<template v-if="selectedSchedule">
|
||||||
<a-descriptions :column="1" bordered>
|
<a-descriptions :column="1" bordered>
|
||||||
<a-descriptions-item label="班级">{{ selectedSchedule.className }}</a-descriptions-item>
|
<a-descriptions-item label="班级">{{ selectedSchedule.className || '-' }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="课程">{{ selectedSchedule.courseName }}</a-descriptions-item>
|
<a-descriptions-item label="课程">{{ selectedSchedule.courseName || '-' }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="课程类型">
|
||||||
|
<a-tag v-if="selectedSchedule.lessonType" size="small" :style="getLessonTagStyle(selectedSchedule.lessonType)">
|
||||||
|
{{ getLessonTypeName(selectedSchedule.lessonType) }}
|
||||||
|
</a-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</a-descriptions-item>
|
||||||
<a-descriptions-item label="授课教师">{{ selectedSchedule.teacherName || '未分配' }}</a-descriptions-item>
|
<a-descriptions-item label="授课教师">{{ selectedSchedule.teacherName || '未分配' }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="排课日期">{{ formatDate(selectedSchedule.scheduledDate) }}</a-descriptions-item>
|
<a-descriptions-item label="排课日期">{{ formatDate(selectedSchedule.scheduledDate) }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="时间段">{{ selectedSchedule.scheduledTime || '待定' }}</a-descriptions-item>
|
<a-descriptions-item label="时间段">{{ selectedSchedule.scheduledTime || '待定' }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="状态">
|
<a-descriptions-item label="状态">
|
||||||
<a-tag v-if="selectedSchedule.status === 'ACTIVE'" color="success">有效</a-tag>
|
<a-tag v-if="selectedSchedule.status === 'ACTIVE' || selectedSchedule.status === 'scheduled'" color="success">有效</a-tag>
|
||||||
<a-tag v-else color="error">已取消</a-tag>
|
<a-tag v-else color="error">已取消</a-tag>
|
||||||
</a-descriptions-item>
|
</a-descriptions-item>
|
||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
@ -136,6 +142,7 @@ import {
|
|||||||
type ClassInfo,
|
type ClassInfo,
|
||||||
type Teacher,
|
type Teacher,
|
||||||
} from '@/api/school';
|
} from '@/api/school';
|
||||||
|
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||||
|
|
||||||
// 数据
|
// 数据
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@ -391,6 +398,10 @@ onMounted(() => {
|
|||||||
color: #999;
|
color: #999;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.schedule-lesson-type {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-day {
|
.empty-day {
|
||||||
|
|||||||
@ -0,0 +1,266 @@
|
|||||||
|
.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-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;
|
||||||
|
/* 背景色、文字色由 getLessonTagStyle 内联样式注入,与课程卡片 tag 一致 */
|
||||||
|
|
||||||
|
&: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;
|
||||||
|
/* color 继承自父级内联样式 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-count {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.85;
|
||||||
|
/* color 继承自父级内联样式 */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
@ -57,22 +57,28 @@
|
|||||||
<a-alert message="该套餐暂无课程包" type="warning" show-icon />
|
<a-alert message="该套餐暂无课程包" type="warning" show-icon />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 排课计划参考 -->
|
<!-- 排课计划参考(与管理端课程包详情一致) -->
|
||||||
<div v-if="scheduleRefData.length > 0" class="schedule-ref-card">
|
<div v-if="scheduleRefDisplay.length > 0" class="schedule-ref-card">
|
||||||
<div class="ref-header">
|
<div class="ref-header">
|
||||||
<CalendarOutlined class="ref-icon" />
|
<CalendarOutlined class="ref-icon" />
|
||||||
<span class="ref-title">排课计划参考</span>
|
<span class="ref-title">排课计划参考</span>
|
||||||
</div>
|
</div>
|
||||||
<a-table
|
<a-table
|
||||||
:columns="scheduleRefColumns"
|
:columns="scheduleRefColumns"
|
||||||
:data-source="scheduleRefData"
|
:data-source="scheduleRefDisplay"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
size="small"
|
size="small"
|
||||||
bordered
|
bordered
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'dayOfWeek'">
|
<template v-if="column.key === 'dayOfWeek'">
|
||||||
{{ weekDayNames[record.dayOfWeek] || '-' }}
|
{{ formatDayOfWeek(record.dayOfWeek) }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="column.key === 'lessonType'">
|
||||||
|
<a-tag v-if="record.lessonType" size="small" :style="getLessonTagStyle(record.lessonType)">
|
||||||
|
{{ getLessonTypeName(record.lessonType) }}
|
||||||
|
</a-tag>
|
||||||
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
@ -89,15 +95,19 @@
|
|||||||
style="margin-bottom: 16px"
|
style="margin-bottom: 16px"
|
||||||
/>
|
/>
|
||||||
<a-spin :spinning="loadingLessonTypes">
|
<a-spin :spinning="loadingLessonTypes">
|
||||||
<div class="lesson-type-grid">
|
<div v-if="!loadingLessonTypes && lessonTypes.length === 0" class="lesson-type-empty">
|
||||||
|
该课程包暂无课程类型,请先选择其他课程包
|
||||||
|
</div>
|
||||||
|
<div v-else class="lesson-type-grid">
|
||||||
<div
|
<div
|
||||||
v-for="type in lessonTypes"
|
v-for="type in lessonTypes"
|
||||||
:key="type.lessonType"
|
:key="type.lessonType"
|
||||||
:class="['lesson-type-card', { active: formData.lessonType === type.lessonType }]"
|
:class="['lesson-type-card', { active: formData.lessonType === type.lessonType }]"
|
||||||
|
:style="getLessonTagStyle(type.lessonType)"
|
||||||
@click="selectLessonType(type.lessonType)"
|
@click="selectLessonType(type.lessonType)"
|
||||||
>
|
>
|
||||||
<div class="type-icon">{{ getLessonTypeIcon(type.lessonType) }}</div>
|
<div class="type-icon">{{ getLessonTypeIcon(type.lessonType) }}</div>
|
||||||
<div class="type-name">{{ type.lessonTypeName }}</div>
|
<div class="type-name">{{ getLessonTypeName(type.lessonType) }}</div>
|
||||||
<div class="type-count">{{ type.count }} 节课</div>
|
<div class="type-count">{{ type.count }} 节课</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -241,6 +251,7 @@ import {
|
|||||||
type ClassInfo,
|
type ClassInfo,
|
||||||
type Teacher,
|
type Teacher,
|
||||||
} from '@/api/school';
|
} from '@/api/school';
|
||||||
|
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'success'): void;
|
(e: 'success'): void;
|
||||||
@ -268,9 +279,13 @@ const classTeacherMap = ref<Record<number, number>>({});
|
|||||||
// 排课计划参考数据
|
// 排课计划参考数据
|
||||||
const scheduleRefData = ref<any[]>([]);
|
const scheduleRefData = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 排课计划参考表格列(与管理端课程包详情一致)
|
||||||
const scheduleRefColumns = [
|
const scheduleRefColumns = [
|
||||||
{ title: '星期', dataIndex: 'dayOfWeek', key: 'dayOfWeek', width: 80 },
|
{ title: '时间', dataIndex: 'dayOfWeek', key: 'dayOfWeek', width: 80 },
|
||||||
{ title: '活动安排', dataIndex: 'activity', key: 'activity' },
|
{ title: '课程类型', dataIndex: 'lessonType', key: 'lessonType', width: 100 },
|
||||||
|
{ title: '课程名称', dataIndex: 'lessonName', key: 'lessonName' },
|
||||||
|
{ title: '区域活动', dataIndex: 'activity', key: 'activity' },
|
||||||
|
{ title: '备注', dataIndex: 'note', key: 'note' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const weekDayNames: Record<number, string> = {
|
const weekDayNames: Record<number, string> = {
|
||||||
@ -283,12 +298,55 @@ const weekDayNames: Record<number, string> = {
|
|||||||
0: '周日',
|
0: '周日',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 格式化星期显示(支持数字 0-6/1-7 或字符串 "周一")
|
||||||
|
const formatDayOfWeek = (val: number | string | undefined): string => {
|
||||||
|
if (val === undefined || val === null) return '-';
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
if (/^[一二三四五六日]/.test(val) || val.startsWith('周')) return val;
|
||||||
|
const n = parseInt(val, 10);
|
||||||
|
if (!isNaN(n)) return weekDayNames[n] ?? weekDayNames[n as 0] ?? '-';
|
||||||
|
}
|
||||||
|
if (typeof val === 'number') return weekDayNames[val as 0] ?? '-';
|
||||||
|
return '-';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将原始 scheduleRefData 规范化为表格展示格式(支持两种数据格式)
|
||||||
|
const normalizeScheduleRefData = (raw: any[]): any[] => {
|
||||||
|
if (!Array.isArray(raw) || raw.length === 0) return [];
|
||||||
|
const first = raw[0];
|
||||||
|
// 格式1:周排课表(dayOfWeek, lessonType, lessonName, activity, note)- 管理端 Step3ScheduleRef 格式
|
||||||
|
const isWeeklyFormat = 'dayOfWeek' in first || 'lessonName' in first || 'activity' in first;
|
||||||
|
const isLessonMetaFormat = 'title' in first && ('suggestedOrder' in first || 'description' in first);
|
||||||
|
if (isWeeklyFormat && !isLessonMetaFormat) {
|
||||||
|
return raw.map((r, i) => ({
|
||||||
|
key: r.key ?? `row_${i}`,
|
||||||
|
dayOfWeek: r.dayOfWeek,
|
||||||
|
lessonType: r.lessonType,
|
||||||
|
lessonName: r.lessonName ?? r.title ?? '-',
|
||||||
|
activity: r.activity ?? r.description ?? '-',
|
||||||
|
note: r.note ?? r.tips ?? r.frequency ?? '-',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
// 格式2:课程类型说明(lessonType, title, description, suggestedOrder, keyPoints, tips)
|
||||||
|
return raw.map((r, i) => ({
|
||||||
|
key: r.key ?? `row_${i}`,
|
||||||
|
dayOfWeek: r.dayOfWeek ?? '-',
|
||||||
|
lessonType: r.lessonType,
|
||||||
|
lessonName: r.title ?? r.lessonName ?? '-',
|
||||||
|
activity: r.description ?? r.activity ?? (Array.isArray(r.keyPoints) ? r.keyPoints.join(';') : '-'),
|
||||||
|
note: r.tips ?? r.frequency ?? r.note ?? '-',
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算属性:规范化后的排课计划参考展示数据
|
||||||
|
const scheduleRefDisplay = computed(() => normalizeScheduleRefData(scheduleRefData.value));
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
interface FormData {
|
interface FormData {
|
||||||
collectionId?: number;
|
collectionId?: number;
|
||||||
packageId?: number;
|
packageId?: number;
|
||||||
courseId?: number; // 内部使用,自动设置为课程包的第一门课程
|
courseId?: number; // 内部使用,自动设置为课程包的第一门课程
|
||||||
lessonType?: LessonType;
|
lessonType?: string; // 课程类型代码,与后端 LessonTypeEnum 对齐
|
||||||
classIds: number[];
|
classIds: number[];
|
||||||
scheduledDate?: Dayjs;
|
scheduledDate?: Dayjs;
|
||||||
scheduledTimeRange?: [Dayjs, Dayjs];
|
scheduledTimeRange?: [Dayjs, Dayjs];
|
||||||
@ -307,19 +365,13 @@ const filteredClasses = computed(() => {
|
|||||||
// 计算属性:选中的课程套餐
|
// 计算属性:选中的课程套餐
|
||||||
const selectedCollection = computed(() => {
|
const selectedCollection = computed(() => {
|
||||||
if (!formData.collectionId) return null;
|
if (!formData.collectionId) return null;
|
||||||
const collection = collections.value.find(c => c.id === formData.collectionId) || null;
|
return collections.value.find(c => c.id === formData.collectionId) || null;
|
||||||
console.log('🎯 selectedCollection:', collection);
|
|
||||||
console.log('📦 selectedCollection.packages:', collection?.packages);
|
|
||||||
return collection;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算属性:选中的课程包
|
// 计算属性:选中的课程包
|
||||||
const selectedPackage = computed(() => {
|
const selectedPackage = computed(() => {
|
||||||
if (!formData.packageId || !selectedCollection.value?.packages) return null;
|
if (!formData.packageId || !selectedCollection.value?.packages) return null;
|
||||||
const pkg = selectedCollection.value.packages.find(p => p.id === formData.packageId) || null;
|
return selectedCollection.value.packages.find(p => p.id === formData.packageId) || null;
|
||||||
console.log('📦 selectedPackage:', pkg);
|
|
||||||
console.log('📚 selectedPackage.courses:', pkg?.courses);
|
|
||||||
return pkg;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 打开弹窗
|
// 打开弹窗
|
||||||
@ -351,19 +403,6 @@ const resetForm = () => {
|
|||||||
const loadCollections = async () => {
|
const loadCollections = async () => {
|
||||||
try {
|
try {
|
||||||
collections.value = await getCourseCollections();
|
collections.value = await getCourseCollections();
|
||||||
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 加载课程套餐失败:', error);
|
console.error('❌ 加载课程套餐失败:', error);
|
||||||
message.error('加载课程套餐失败');
|
message.error('加载课程套餐失败');
|
||||||
@ -401,30 +440,16 @@ const handleCollectionChange = async (collectionId: number) => {
|
|||||||
try {
|
try {
|
||||||
// 获取课程包列表(API: GET /v1/school/packages/{collectionId}/packages)
|
// 获取课程包列表(API: GET /v1/school/packages/{collectionId}/packages)
|
||||||
const packages = await getCourseCollectionPackages(collectionId);
|
const packages = await getCourseCollectionPackages(collectionId);
|
||||||
console.log('📦 API返回的课程包列表:', packages);
|
|
||||||
console.log('📦 课程包数量:', packages?.length);
|
|
||||||
|
|
||||||
if (!packages || packages.length === 0) {
|
if (!packages || packages.length === 0) {
|
||||||
message.warning('该套餐暂无课程包');
|
message.warning('该套餐暂无课程包');
|
||||||
return;
|
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新当前套餐的课程包列表
|
// 更新当前套餐的课程包列表
|
||||||
const collection = collections.value.find(c => c.id === collectionId);
|
const collection = collections.value.find(c => c.id === collectionId);
|
||||||
if (collection) {
|
if (collection) {
|
||||||
collection.packages = packages;
|
collection.packages = packages;
|
||||||
console.log('✅ 已更新套餐的课程包列表');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ 加载课程包失败:', error);
|
console.error('❌ 加载课程包失败:', error);
|
||||||
@ -434,32 +459,24 @@ const handleCollectionChange = async (collectionId: number) => {
|
|||||||
|
|
||||||
// 选择课程包
|
// 选择课程包
|
||||||
const selectPackage = async (packageId: number) => {
|
const selectPackage = async (packageId: number) => {
|
||||||
console.log('🎯 点击选择课程包,packageId:', packageId);
|
|
||||||
console.log('📦 当前 packages 数组:', selectedCollection.value?.packages);
|
|
||||||
|
|
||||||
formData.packageId = packageId;
|
formData.packageId = packageId;
|
||||||
|
|
||||||
// 调试:查看找到的课程包
|
|
||||||
const foundPkg = selectedCollection.value?.packages?.find((p: any) => p.id === packageId);
|
|
||||||
console.log('🔍 找到的课程包:', foundPkg);
|
|
||||||
|
|
||||||
// 自动选择第一门课程(用于后端API)
|
// 自动选择第一门课程(用于后端API)
|
||||||
if (selectedCollection.value?.packages) {
|
if (selectedCollection.value?.packages) {
|
||||||
const selectedPkg = selectedCollection.value.packages.find((p: any) => p.id === packageId);
|
const selectedPkg = selectedCollection.value.packages.find((p: any) => p.id === packageId);
|
||||||
if (selectedPkg?.courses && selectedPkg.courses.length > 0) {
|
if (selectedPkg?.courses && selectedPkg.courses.length > 0) {
|
||||||
// 自动设置为第一门课程
|
// 自动设置为第一门课程
|
||||||
formData.courseId = selectedPkg.courses[0].id;
|
formData.courseId = selectedPkg.courses[0].id;
|
||||||
console.log('✅ 自动选择第一门课程:', formData.courseId);
|
|
||||||
|
|
||||||
// 加载排课计划参考(从第一门课程中获取)
|
// 加载排课计划参考(从课程包中任一课程的 scheduleRefData 获取,后端统一来自 course_package)
|
||||||
const firstCourse = selectedPkg.courses[0];
|
const courseWithRef = selectedPkg.courses.find((c: any) => c.scheduleRefData);
|
||||||
if (firstCourse.scheduleRefData) {
|
const rawRef = courseWithRef?.scheduleRefData ?? (selectedPkg as any).scheduleRefData;
|
||||||
|
if (rawRef) {
|
||||||
try {
|
try {
|
||||||
const parsedData = JSON.parse(firstCourse.scheduleRefData);
|
const parsed = typeof rawRef === 'string' ? JSON.parse(rawRef) : rawRef;
|
||||||
scheduleRefData.value = Array.isArray(parsedData) ? parsedData : [];
|
scheduleRefData.value = Array.isArray(parsed) ? parsed : [];
|
||||||
console.log('✅ 排课计划参考数据:', scheduleRefData.value);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析排课数据失败:', e);
|
console.error('解析排课计划参考失败:', e);
|
||||||
scheduleRefData.value = [];
|
scheduleRefData.value = [];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -488,8 +505,8 @@ const loadLessonTypes = async (packageId: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 选择课程类型
|
// 选择课程类型
|
||||||
const selectLessonType = (type: LessonType) => {
|
const selectLessonType = (lessonType: string) => {
|
||||||
formData.lessonType = type;
|
formData.lessonType = lessonType;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 切换班级选择
|
// 切换班级选择
|
||||||
@ -520,25 +537,24 @@ const filterTeacher = (input: string, option: any) => {
|
|||||||
return teacher?.name?.toLowerCase().includes(input.toLowerCase()) || false;
|
return teacher?.name?.toLowerCase().includes(input.toLowerCase()) || false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取课程类型图标
|
// 获取课程类型图标(与课程列表 tag 类型对齐)
|
||||||
const getLessonTypeIcon = (type: LessonType): string => {
|
const getLessonTypeIcon = (type: string): string => {
|
||||||
const icons: Record<LessonType, string> = {
|
const icons: Record<string, string> = {
|
||||||
INTRODUCTION: '📖',
|
INTRODUCTION: '📖', INTRO: '📖',
|
||||||
COLLECTIVE: '👥',
|
COLLECTIVE: '👥',
|
||||||
LANGUAGE: '💬',
|
LANGUAGE: '💬', DOMAIN_LANGUAGE: '💬',
|
||||||
SOCIETY: '🤝',
|
SOCIETY: '🤝', SOCIAL: '🤝', DOMAIN_SOCIAL: '🤝',
|
||||||
SCIENCE: '🔬',
|
SCIENCE: '🔬', DOMAIN_SCIENCE: '🔬',
|
||||||
ART: '🎨',
|
ART: '🎨', DOMAIN_ART: '🎨',
|
||||||
HEALTH: '❤️',
|
HEALTH: '❤️', DOMAIN_HEALTH: '❤️',
|
||||||
};
|
};
|
||||||
return icons[type] || '📚';
|
return icons[type] || '📚';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取选中的课程类型名称
|
// 获取选中的课程类型名称(与课程列表 tag 一致)
|
||||||
const getSelectedLessonTypeName = (): string => {
|
const getSelectedLessonTypeName = (): string => {
|
||||||
if (!formData.lessonType) return '-';
|
if (!formData.lessonType) return '-';
|
||||||
const type = lessonTypes.value.find(t => t.lessonType === formData.lessonType);
|
return getLessonTypeName(formData.lessonType);
|
||||||
return type?.lessonTypeName || '-';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取选择的时间范围
|
// 获取选择的时间范围
|
||||||
@ -670,261 +686,4 @@ const handleCancel = () => {
|
|||||||
defineExpose({ open });
|
defineExpose({ open });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss" src="./CreateScheduleModal.scss"></style>
|
||||||
.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>
|
|
||||||
|
|||||||
@ -26,7 +26,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref, nextTick } from 'vue';
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
import ScheduleList from './ScheduleList.vue';
|
import ScheduleList from './ScheduleList.vue';
|
||||||
import TimetableView from './TimetableView.vue';
|
import TimetableView from './TimetableView.vue';
|
||||||
@ -45,6 +45,20 @@ const showCreateModal = () => {
|
|||||||
|
|
||||||
const handleTabChange = (key: string) => {
|
const handleTabChange = (key: string) => {
|
||||||
activeTab.value = key;
|
activeTab.value = key;
|
||||||
|
// 切换视图时刷新对应视图数据
|
||||||
|
nextTick(() => {
|
||||||
|
switch (key) {
|
||||||
|
case 'list':
|
||||||
|
scheduleListRef.value?.refresh();
|
||||||
|
break;
|
||||||
|
case 'timetable':
|
||||||
|
timetableRef.value?.refresh();
|
||||||
|
break;
|
||||||
|
case 'calendar':
|
||||||
|
calendarRef.value?.refresh();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateSuccess = () => {
|
const handleCreateSuccess = () => {
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import com.reading.platform.dto.response.CalendarViewResponse;
|
|||||||
import com.reading.platform.dto.response.ConflictCheckResult;
|
import com.reading.platform.dto.response.ConflictCheckResult;
|
||||||
import com.reading.platform.dto.response.LessonTypeInfo;
|
import com.reading.platform.dto.response.LessonTypeInfo;
|
||||||
import com.reading.platform.dto.response.SchedulePlanResponse;
|
import com.reading.platform.dto.response.SchedulePlanResponse;
|
||||||
import com.reading.platform.dto.response.TimetableResponse;
|
|
||||||
import com.reading.platform.entity.SchedulePlan;
|
import com.reading.platform.entity.SchedulePlan;
|
||||||
import com.reading.platform.service.SchoolScheduleService;
|
import com.reading.platform.service.SchoolScheduleService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@ -25,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,7 +55,7 @@ public class SchoolScheduleController {
|
|||||||
tenantId, pageNum, pageSize, startDate, endDate, classId, teacherId, status);
|
tenantId, pageNum, pageSize, startDate, endDate, classId, teacherId, status);
|
||||||
|
|
||||||
List<SchedulePlanResponse> records = page.getRecords().stream()
|
List<SchedulePlanResponse> records = page.getRecords().stream()
|
||||||
.map(this::toResponse)
|
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
return Result.success(PageResult.of(records, page.getTotal(), page.getCurrent(), page.getSize()));
|
return Result.success(PageResult.of(records, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||||
@ -63,14 +63,13 @@ public class SchoolScheduleController {
|
|||||||
|
|
||||||
@GetMapping("/timetable")
|
@GetMapping("/timetable")
|
||||||
@Operation(summary = "获取课程表")
|
@Operation(summary = "获取课程表")
|
||||||
public Result<TimetableResponse> getTimetable(
|
public Result<Map<String, Object>> getTimetable(
|
||||||
@RequestParam(required = false) Long classId,
|
@RequestParam(required = false) Long classId,
|
||||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
|
||||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
|
||||||
|
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
// Service 返回 Map,暂时保留,后续可优化为 TimetableResponse
|
return Result.success(schoolScheduleService.getTimetable(tenantId, classId, startDate, endDate));
|
||||||
return Result.success((TimetableResponse) schoolScheduleService.getTimetable(tenantId, classId, startDate, endDate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@ -78,7 +77,7 @@ public class SchoolScheduleController {
|
|||||||
public Result<SchedulePlanResponse> getSchedule(@PathVariable Long id) {
|
public Result<SchedulePlanResponse> getSchedule(@PathVariable Long id) {
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
SchedulePlan schedule = schoolScheduleService.getScheduleById(id, tenantId);
|
SchedulePlan schedule = schoolScheduleService.getScheduleById(id, tenantId);
|
||||||
return Result.success(toResponse(schedule));
|
return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@ -87,7 +86,7 @@ public class SchoolScheduleController {
|
|||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
List<SchedulePlan> plans = schoolScheduleService.createSchedule(tenantId, request);
|
List<SchedulePlan> plans = schoolScheduleService.createSchedule(tenantId, request);
|
||||||
List<SchedulePlanResponse> result = plans.stream()
|
List<SchedulePlanResponse> result = plans.stream()
|
||||||
.map(this::toResponse)
|
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
@ -100,7 +99,7 @@ public class SchoolScheduleController {
|
|||||||
|
|
||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
SchedulePlan schedule = schoolScheduleService.updateSchedule(id, tenantId, request);
|
SchedulePlan schedule = schoolScheduleService.updateSchedule(id, tenantId, request);
|
||||||
return Result.success(toResponse(schedule));
|
return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@ -119,7 +118,7 @@ public class SchoolScheduleController {
|
|||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
List<SchedulePlan> plans = schoolScheduleService.batchCreateSchedules(tenantId, requests);
|
List<SchedulePlan> plans = schoolScheduleService.batchCreateSchedules(tenantId, requests);
|
||||||
List<SchedulePlanResponse> result = plans.stream()
|
List<SchedulePlanResponse> result = plans.stream()
|
||||||
.map(this::toResponse)
|
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
@ -152,7 +151,7 @@ public class SchoolScheduleController {
|
|||||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||||
List<SchedulePlan> plans = schoolScheduleService.createSchedulesByClasses(tenantId, request);
|
List<SchedulePlan> plans = schoolScheduleService.createSchedulesByClasses(tenantId, request);
|
||||||
List<SchedulePlanResponse> result = plans.stream()
|
List<SchedulePlanResponse> result = plans.stream()
|
||||||
.map(this::toResponse)
|
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
@ -170,30 +169,4 @@ public class SchoolScheduleController {
|
|||||||
return Result.success(response);
|
return Result.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换为 Response 格式返回
|
|
||||||
*/
|
|
||||||
private SchedulePlanResponse toResponse(SchedulePlan plan) {
|
|
||||||
return SchedulePlanResponse.builder()
|
|
||||||
.id(plan.getId())
|
|
||||||
.tenantId(plan.getTenantId())
|
|
||||||
.name(plan.getName())
|
|
||||||
.classId(plan.getClassId())
|
|
||||||
.courseId(plan.getCourseId())
|
|
||||||
.coursePackageId(plan.getCoursePackageId())
|
|
||||||
.lessonType(plan.getLessonType())
|
|
||||||
.teacherId(plan.getTeacherId())
|
|
||||||
.scheduledDate(plan.getScheduledDate())
|
|
||||||
.scheduledTime(plan.getScheduledTime())
|
|
||||||
.weekDay(plan.getWeekDay())
|
|
||||||
.repeatType(plan.getRepeatType())
|
|
||||||
.repeatEndDate(plan.getRepeatEndDate())
|
|
||||||
.source(plan.getSource())
|
|
||||||
.note(plan.getNote())
|
|
||||||
.status(plan.getStatus())
|
|
||||||
.createdAt(plan.getCreatedAt())
|
|
||||||
.updatedAt(plan.getUpdatedAt())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,9 @@ public class CalendarViewResponse {
|
|||||||
@Schema(description = "课程包名称")
|
@Schema(description = "课程包名称")
|
||||||
private String coursePackageName;
|
private String coursePackageName;
|
||||||
|
|
||||||
|
@Schema(description = "课程类型代码 (如 DOMAIN_HEALTH)")
|
||||||
|
private String lessonType;
|
||||||
|
|
||||||
@Schema(description = "课程类型名称")
|
@Schema(description = "课程类型名称")
|
||||||
private String lessonTypeName;
|
private String lessonTypeName;
|
||||||
|
|
||||||
|
|||||||
@ -11,13 +11,22 @@ import lombok.Getter;
|
|||||||
public enum LessonTypeEnum {
|
public enum LessonTypeEnum {
|
||||||
|
|
||||||
INTRODUCTION("INTRODUCTION", "导入课"),
|
INTRODUCTION("INTRODUCTION", "导入课"),
|
||||||
|
INTRO("INTRO", "导入课"),
|
||||||
COLLECTIVE("COLLECTIVE", "集体课"),
|
COLLECTIVE("COLLECTIVE", "集体课"),
|
||||||
LANGUAGE("LANGUAGE", "语言课"),
|
LANGUAGE("LANGUAGE", "语言课"),
|
||||||
ART("ART", "艺术课"),
|
ART("ART", "艺术课"),
|
||||||
MUSIC("MUSIC", "音乐课"),
|
MUSIC("MUSIC", "音乐课"),
|
||||||
SPORT("SPORT", "体育课"),
|
SPORT("SPORT", "体育课"),
|
||||||
SCIENCE("SCIENCE", "科学课"),
|
SCIENCE("SCIENCE", "科学课"),
|
||||||
OUTDOOR("OUTDOOR", "户外课");
|
OUTDOOR("OUTDOOR", "户外课"),
|
||||||
|
SOCIAL("SOCIAL", "社会课"),
|
||||||
|
SOCIETY("SOCIETY", "社会课"),
|
||||||
|
HEALTH("HEALTH", "健康课"),
|
||||||
|
DOMAIN_HEALTH("DOMAIN_HEALTH", "健康课"),
|
||||||
|
DOMAIN_LANGUAGE("DOMAIN_LANGUAGE", "语言课"),
|
||||||
|
DOMAIN_SOCIAL("DOMAIN_SOCIAL", "社会课"),
|
||||||
|
DOMAIN_SCIENCE("DOMAIN_SCIENCE", "科学课"),
|
||||||
|
DOMAIN_ART("DOMAIN_ART", "艺术课");
|
||||||
|
|
||||||
private final String code;
|
private final String code;
|
||||||
private final String description;
|
private final String description;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import com.reading.platform.dto.request.ScheduleCreateByClassesRequest;
|
|||||||
import com.reading.platform.dto.response.CalendarViewResponse;
|
import com.reading.platform.dto.response.CalendarViewResponse;
|
||||||
import com.reading.platform.dto.response.ConflictCheckResult;
|
import com.reading.platform.dto.response.ConflictCheckResult;
|
||||||
import com.reading.platform.dto.response.LessonTypeInfo;
|
import com.reading.platform.dto.response.LessonTypeInfo;
|
||||||
|
import com.reading.platform.dto.response.SchedulePlanResponse;
|
||||||
import com.reading.platform.entity.SchedulePlan;
|
import com.reading.platform.entity.SchedulePlan;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
@ -136,4 +137,12 @@ public interface SchoolScheduleService extends IService<SchedulePlan> {
|
|||||||
CalendarViewResponse getCalendarViewData(Long tenantId, LocalDate startDate, LocalDate endDate,
|
CalendarViewResponse getCalendarViewData(Long tenantId, LocalDate startDate, LocalDate endDate,
|
||||||
Long classId, Long teacherId);
|
Long classId, Long teacherId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为 Response(含班级名、课程名、教师名等展示字段)
|
||||||
|
*
|
||||||
|
* @param plan 排课实体
|
||||||
|
* @return 排课响应
|
||||||
|
*/
|
||||||
|
SchedulePlanResponse toSchedulePlanResponse(SchedulePlan plan);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,12 +14,18 @@ import com.reading.platform.dto.request.ScheduleCreateByClassesRequest;
|
|||||||
import com.reading.platform.dto.response.CalendarViewResponse;
|
import com.reading.platform.dto.response.CalendarViewResponse;
|
||||||
import com.reading.platform.dto.response.ConflictCheckResult;
|
import com.reading.platform.dto.response.ConflictCheckResult;
|
||||||
import com.reading.platform.dto.response.LessonTypeInfo;
|
import com.reading.platform.dto.response.LessonTypeInfo;
|
||||||
|
import com.reading.platform.dto.response.SchedulePlanResponse;
|
||||||
|
import com.reading.platform.entity.Clazz;
|
||||||
|
import com.reading.platform.entity.CourseLesson;
|
||||||
import com.reading.platform.entity.CoursePackage;
|
import com.reading.platform.entity.CoursePackage;
|
||||||
import com.reading.platform.entity.CoursePackageCourse;
|
|
||||||
import com.reading.platform.entity.SchedulePlan;
|
import com.reading.platform.entity.SchedulePlan;
|
||||||
|
import com.reading.platform.entity.Teacher;
|
||||||
|
import com.reading.platform.enums.LessonTypeEnum;
|
||||||
|
import com.reading.platform.mapper.ClazzMapper;
|
||||||
import com.reading.platform.mapper.CoursePackageMapper;
|
import com.reading.platform.mapper.CoursePackageMapper;
|
||||||
import com.reading.platform.mapper.CoursePackageCourseMapper;
|
|
||||||
import com.reading.platform.mapper.SchedulePlanMapper;
|
import com.reading.platform.mapper.SchedulePlanMapper;
|
||||||
|
import com.reading.platform.mapper.TeacherMapper;
|
||||||
|
import com.reading.platform.service.CourseLessonService;
|
||||||
import com.reading.platform.service.ScheduleConflictService;
|
import com.reading.platform.service.ScheduleConflictService;
|
||||||
import com.reading.platform.service.SchoolScheduleService;
|
import com.reading.platform.service.SchoolScheduleService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@ -44,8 +50,10 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
|
|
||||||
private final SchedulePlanMapper schedulePlanMapper;
|
private final SchedulePlanMapper schedulePlanMapper;
|
||||||
private final ScheduleConflictService scheduleConflictService;
|
private final ScheduleConflictService scheduleConflictService;
|
||||||
private final CoursePackageCourseMapper coursePackageCoursePackageMapper;
|
private final CourseLessonService courseLessonService;
|
||||||
private final CoursePackageMapper courseMapper;
|
private final CoursePackageMapper courseMapper;
|
||||||
|
private final ClazzMapper clazzMapper;
|
||||||
|
private final TeacherMapper teacherMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 最大重复周数限制
|
* 最大重复周数限制
|
||||||
@ -86,6 +94,8 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
plan.setName(request.getName());
|
plan.setName(request.getName());
|
||||||
plan.setClassId(request.getClassId());
|
plan.setClassId(request.getClassId());
|
||||||
plan.setCourseId(request.getCourseId());
|
plan.setCourseId(request.getCourseId());
|
||||||
|
plan.setCoursePackageId(request.getCoursePackageId());
|
||||||
|
plan.setLessonType(request.getLessonType());
|
||||||
plan.setTeacherId(request.getTeacherId());
|
plan.setTeacherId(request.getTeacherId());
|
||||||
plan.setScheduledDate(date);
|
plan.setScheduledDate(date);
|
||||||
plan.setScheduledTime(request.getScheduledTime());
|
plan.setScheduledTime(request.getScheduledTime());
|
||||||
@ -94,7 +104,7 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
plan.setRepeatEndDate(request.getRepeatEndDate());
|
plan.setRepeatEndDate(request.getRepeatEndDate());
|
||||||
plan.setSource(StringUtils.hasText(request.getSource()) ? request.getSource() : "SCHOOL");
|
plan.setSource(StringUtils.hasText(request.getSource()) ? request.getSource() : "SCHOOL");
|
||||||
plan.setNote(request.getNote());
|
plan.setNote(request.getNote());
|
||||||
plan.setStatus("scheduled");
|
plan.setStatus("ACTIVE");
|
||||||
plan.setReminderSent(0);
|
plan.setReminderSent(0);
|
||||||
|
|
||||||
schedulePlanMapper.insert(plan);
|
schedulePlanMapper.insert(plan);
|
||||||
@ -212,7 +222,11 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
wrapper.eq(SchedulePlan::getTeacherId, teacherId);
|
wrapper.eq(SchedulePlan::getTeacherId, teacherId);
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(status)) {
|
if (StringUtils.hasText(status)) {
|
||||||
wrapper.eq(SchedulePlan::getStatus, status);
|
if ("ACTIVE".equalsIgnoreCase(status)) {
|
||||||
|
wrapper.in(SchedulePlan::getStatus, "ACTIVE", "scheduled");
|
||||||
|
} else {
|
||||||
|
wrapper.eq(SchedulePlan::getStatus, status);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapper.orderByAsc(SchedulePlan::getScheduledDate, SchedulePlan::getScheduledTime);
|
wrapper.orderByAsc(SchedulePlan::getScheduledDate, SchedulePlan::getScheduledTime);
|
||||||
@ -248,13 +262,24 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
|
|
||||||
List<SchedulePlan> plans = schedulePlanMapper.selectList(wrapper);
|
List<SchedulePlan> plans = schedulePlanMapper.selectList(wrapper);
|
||||||
|
|
||||||
// 按日期分组
|
// 转换为带展示字段的 Response
|
||||||
Map<LocalDate, List<SchedulePlan>> groupedByDate = plans.stream()
|
List<SchedulePlanResponse> responses = plans.stream()
|
||||||
.collect(Collectors.groupingBy(SchedulePlan::getScheduledDate, TreeMap::new, Collectors.toList()));
|
.map(this::toSchedulePlanResponse)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 按日期分组(使用 dateKey 字符串,便于前端按 YYYY-MM-DD 查找)
|
||||||
|
Map<String, List<SchedulePlanResponse>> groupedByDate = new LinkedHashMap<>();
|
||||||
|
for (SchedulePlanResponse r : responses) {
|
||||||
|
if (r.getScheduledDate() != null) {
|
||||||
|
String dateKey = r.getScheduledDate().toString();
|
||||||
|
groupedByDate.computeIfAbsent(dateKey, k -> new ArrayList<>()).add(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 按星期几分组
|
// 按星期几分组
|
||||||
Map<Integer, List<SchedulePlan>> groupedByWeekDay = plans.stream()
|
Map<Integer, List<SchedulePlanResponse>> groupedByWeekDay = responses.stream()
|
||||||
.collect(Collectors.groupingBy(SchedulePlan::getWeekDay, TreeMap::new, Collectors.toList()));
|
.filter(r -> r.getWeekDay() != null)
|
||||||
|
.collect(Collectors.groupingBy(SchedulePlanResponse::getWeekDay, TreeMap::new, Collectors.toList()));
|
||||||
|
|
||||||
Map<String, Object> result = new LinkedHashMap<>();
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
result.put("startDate", startDate);
|
result.put("startDate", startDate);
|
||||||
@ -293,79 +318,84 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
public List<LessonTypeInfo> getCoursePackageLessonTypes(Long tenantId, Long coursePackageId) {
|
public List<LessonTypeInfo> getCoursePackageLessonTypes(Long tenantId, Long coursePackageId) {
|
||||||
log.info("获取课程包的课程类型列表: tenantId={}, coursePackageId={}", tenantId, coursePackageId);
|
log.info("获取课程包的课程类型列表: tenantId={}, coursePackageId={}", tenantId, coursePackageId);
|
||||||
|
|
||||||
// 1. 根据 coursePackageId 查询 course_package_course 获取 courseId 列表
|
// 1. 优先从 course_lesson 表获取课程环节类型(course_package_course 表已废弃)
|
||||||
List<CoursePackageCourse> packageCourses = coursePackageCoursePackageMapper.selectList(
|
List<CourseLesson> lessons = courseLessonService.findByCourseId(coursePackageId);
|
||||||
new LambdaQueryWrapper<CoursePackageCourse>()
|
|
||||||
.eq(CoursePackageCourse::getPackageId, coursePackageId)
|
|
||||||
.orderByAsc(CoursePackageCourse::getSortOrder)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (packageCourses.isEmpty()) {
|
if (!lessons.isEmpty()) {
|
||||||
log.warn("课程包下没有课程: coursePackageId={}", coursePackageId);
|
Map<String, LessonTypeInfo> lessonTypeMap = new LinkedHashMap<>();
|
||||||
return new ArrayList<>();
|
for (CourseLesson lesson : lessons) {
|
||||||
}
|
String code = lesson.getLessonType();
|
||||||
|
if (!StringUtils.hasText(code)) continue;
|
||||||
List<Long> courseIds = packageCourses.stream()
|
String displayName = getLessonTypeDisplayName(code);
|
||||||
.map(CoursePackageCourse::getCourseId)
|
lessonTypeMap.compute(code, (k, v) -> {
|
||||||
.collect(Collectors.toList());
|
if (v == null) {
|
||||||
|
return LessonTypeInfo.builder()
|
||||||
// 2. 根据 courseId 列表查询 course 表
|
.lessonType(code)
|
||||||
List<CoursePackage> courses = courseMapper.selectList(
|
.lessonTypeName(displayName)
|
||||||
new LambdaQueryWrapper<CoursePackage>()
|
.count(1L)
|
||||||
.in(CoursePackage::getId, courseIds)
|
.build();
|
||||||
.eq(CoursePackage::getStatus, "PUBLISHED")
|
}
|
||||||
);
|
v.setCount(v.getCount() + 1);
|
||||||
|
return v;
|
||||||
if (courses.isEmpty()) {
|
});
|
||||||
log.warn("没有找到已发布的课程: courseIds={}", courseIds);
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 从 schedule_ref_data 中提取 lessonType 并统计
|
|
||||||
Map<String, LessonTypeInfo> lessonTypeMap = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
for (CoursePackage course : courses) {
|
|
||||||
String scheduleRefData = course.getScheduleRefData();
|
|
||||||
if (!StringUtils.hasText(scheduleRefData)) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
List<LessonTypeInfo> result = new ArrayList<>(lessonTypeMap.values());
|
||||||
|
log.info("课程包课程类型统计完成(来自course_lesson): coursePackageId={}, types={}", coursePackageId, result.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
// 2. 无 course_lesson 时,从 course_package.schedule_ref_data 解析
|
||||||
// 解析 JSON 数组
|
CoursePackage pkg = courseMapper.selectById(coursePackageId);
|
||||||
JSONArray jsonArray = JSON.parseArray(scheduleRefData);
|
if (pkg == null) {
|
||||||
if (jsonArray != null && !jsonArray.isEmpty()) {
|
log.warn("课程包不存在: coursePackageId={}", coursePackageId);
|
||||||
JSONObject firstItem = jsonArray.getJSONObject(0);
|
return new ArrayList<>();
|
||||||
if (firstItem != null && firstItem.containsKey("lessonType")) {
|
}
|
||||||
String lessonType = firstItem.getString("lessonType");
|
|
||||||
if (StringUtils.hasText(lessonType)) {
|
|
||||||
// 转换为英文代码
|
|
||||||
String lessonTypeCode = convertLessonTypeNameToCode(lessonType);
|
|
||||||
|
|
||||||
lessonTypeMap.compute(lessonTypeCode, (k, v) -> {
|
String scheduleRefData = pkg.getScheduleRefData();
|
||||||
|
if (!StringUtils.hasText(scheduleRefData)) {
|
||||||
|
log.warn("课程包无排课参考数据: coursePackageId={}", coursePackageId);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, LessonTypeInfo> lessonTypeMap = new LinkedHashMap<>();
|
||||||
|
try {
|
||||||
|
JSONArray jsonArray = JSON.parseArray(scheduleRefData);
|
||||||
|
if (jsonArray != null && !jsonArray.isEmpty()) {
|
||||||
|
for (int i = 0; i < jsonArray.size(); i++) {
|
||||||
|
JSONObject item = jsonArray.getJSONObject(i);
|
||||||
|
if (item != null && item.containsKey("lessonType")) {
|
||||||
|
String lessonTypeName = item.getString("lessonType");
|
||||||
|
if (StringUtils.hasText(lessonTypeName)) {
|
||||||
|
String code = convertLessonTypeNameToCode(lessonTypeName);
|
||||||
|
lessonTypeMap.compute(code, (k, v) -> {
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
return LessonTypeInfo.builder()
|
return LessonTypeInfo.builder()
|
||||||
.lessonType(lessonTypeCode)
|
.lessonType(code)
|
||||||
.lessonTypeName(lessonType)
|
.lessonTypeName(lessonTypeName)
|
||||||
.count(1L)
|
.count(1L)
|
||||||
.build();
|
.build();
|
||||||
} else {
|
|
||||||
v.setCount(v.getCount() + 1);
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
v.setCount(v.getCount() + 1);
|
||||||
|
return v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("解析课程排课参考数据失败: courseId={}, error={}", course.getId(), e.getMessage());
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析课程排课参考数据失败: coursePackageId={}, error={}", coursePackageId, e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
List<LessonTypeInfo> result = new ArrayList<>(lessonTypeMap.values());
|
List<LessonTypeInfo> result = new ArrayList<>(lessonTypeMap.values());
|
||||||
log.info("课程包课程类型统计完成: coursePackageId={}, types={}", coursePackageId, result.size());
|
log.info("课程包课程类型统计完成(来自schedule_ref_data): coursePackageId={}, types={}", coursePackageId, result.size());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getLessonTypeDisplayName(String code) {
|
||||||
|
LessonTypeEnum e = LessonTypeEnum.fromCode(code);
|
||||||
|
return e != null ? e.getDescription() : code;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将中文课程类型名称转换为英文代码
|
* 将中文课程类型名称转换为英文代码
|
||||||
*/
|
*/
|
||||||
@ -475,6 +505,7 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
.id(plan.getId())
|
.id(plan.getId())
|
||||||
.className(getClassNameForCalendar(plan))
|
.className(getClassNameForCalendar(plan))
|
||||||
.coursePackageName(getCoursePackageName(plan))
|
.coursePackageName(getCoursePackageName(plan))
|
||||||
|
.lessonType(plan.getLessonType())
|
||||||
.lessonTypeName(getLessonTypeName(plan))
|
.lessonTypeName(getLessonTypeName(plan))
|
||||||
.teacherName(getTeacherName(plan))
|
.teacherName(getTeacherName(plan))
|
||||||
.scheduledTime(plan.getScheduledTime())
|
.scheduledTime(plan.getScheduledTime())
|
||||||
@ -499,23 +530,77 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
return request.getLessonType();
|
return request.getLessonType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SchedulePlanResponse toSchedulePlanResponse(SchedulePlan plan) {
|
||||||
|
String className = null;
|
||||||
|
if (plan.getClassId() != null) {
|
||||||
|
Clazz clazz = clazzMapper.selectById(plan.getClassId());
|
||||||
|
className = clazz != null ? clazz.getName() : null;
|
||||||
|
}
|
||||||
|
String courseName = null;
|
||||||
|
String coursePackageName = null;
|
||||||
|
if (plan.getCourseId() != null) {
|
||||||
|
CoursePackage cp = courseMapper.selectById(plan.getCourseId());
|
||||||
|
if (cp != null) {
|
||||||
|
courseName = cp.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (plan.getCoursePackageId() != null && !plan.getCoursePackageId().equals(plan.getCourseId())) {
|
||||||
|
CoursePackage pkg = courseMapper.selectById(plan.getCoursePackageId());
|
||||||
|
coursePackageName = pkg != null ? pkg.getName() : null;
|
||||||
|
} else if (plan.getCourseId() != null && courseName != null) {
|
||||||
|
coursePackageName = courseName;
|
||||||
|
}
|
||||||
|
String teacherName = null;
|
||||||
|
if (plan.getTeacherId() != null) {
|
||||||
|
Teacher teacher = teacherMapper.selectById(plan.getTeacherId());
|
||||||
|
teacherName = teacher != null ? teacher.getName() : null;
|
||||||
|
}
|
||||||
|
String lessonTypeName = plan.getLessonType() != null ? getLessonTypeDisplayName(plan.getLessonType()) : "";
|
||||||
|
return SchedulePlanResponse.builder()
|
||||||
|
.id(plan.getId())
|
||||||
|
.tenantId(plan.getTenantId())
|
||||||
|
.name(plan.getName())
|
||||||
|
.classId(plan.getClassId())
|
||||||
|
.className(className)
|
||||||
|
.courseId(plan.getCourseId())
|
||||||
|
.courseName(courseName)
|
||||||
|
.coursePackageId(plan.getCoursePackageId())
|
||||||
|
.coursePackageName(coursePackageName != null ? coursePackageName : courseName)
|
||||||
|
.lessonType(plan.getLessonType())
|
||||||
|
.lessonTypeName(lessonTypeName)
|
||||||
|
.teacherId(plan.getTeacherId())
|
||||||
|
.teacherName(teacherName)
|
||||||
|
.scheduledDate(plan.getScheduledDate())
|
||||||
|
.scheduledTime(plan.getScheduledTime())
|
||||||
|
.weekDay(plan.getWeekDay())
|
||||||
|
.repeatType(plan.getRepeatType())
|
||||||
|
.repeatEndDate(plan.getRepeatEndDate())
|
||||||
|
.source(plan.getSource())
|
||||||
|
.note(plan.getNote())
|
||||||
|
.status(plan.getStatus())
|
||||||
|
.createdAt(plan.getCreatedAt())
|
||||||
|
.updatedAt(plan.getUpdatedAt())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取班级名称(用于日历显示)
|
* 获取班级名称(用于日历显示)
|
||||||
*/
|
*/
|
||||||
private String getClassNameForCalendar(SchedulePlan plan) {
|
private String getClassNameForCalendar(SchedulePlan plan) {
|
||||||
// TODO: 查询班级表获取班级名称
|
if (plan.getClassId() == null) return "";
|
||||||
return "班级" + plan.getClassId();
|
Clazz clazz = clazzMapper.selectById(plan.getClassId());
|
||||||
|
return clazz != null ? clazz.getName() : "班级" + plan.getClassId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取课程包名称(用于日历显示)
|
* 获取课程包名称(用于日历显示)
|
||||||
*/
|
*/
|
||||||
private String getCoursePackageName(SchedulePlan plan) {
|
private String getCoursePackageName(SchedulePlan plan) {
|
||||||
// TODO: 查询课程包表获取课程包名称
|
Long id = plan.getCoursePackageId() != null ? plan.getCoursePackageId() : plan.getCourseId();
|
||||||
if (plan.getCoursePackageId() != null) {
|
if (id == null) return "";
|
||||||
return "课程包" + plan.getCoursePackageId();
|
CoursePackage pkg = courseMapper.selectById(id);
|
||||||
}
|
return pkg != null ? pkg.getName() : "课程包" + id;
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -536,8 +621,9 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
|||||||
* 获取教师名称(用于日历显示)
|
* 获取教师名称(用于日历显示)
|
||||||
*/
|
*/
|
||||||
private String getTeacherName(SchedulePlan plan) {
|
private String getTeacherName(SchedulePlan plan) {
|
||||||
// TODO: 查询教师表获取教师名称
|
if (plan.getTeacherId() == null) return "";
|
||||||
return "教师" + plan.getTeacherId();
|
Teacher teacher = teacherMapper.selectById(plan.getTeacherId());
|
||||||
|
return teacher != null ? teacher.getName() : "教师" + plan.getTeacherId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user