Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
dad4c14f97
@ -431,12 +431,15 @@ export const getStudentClassHistory = (studentId: number) =>
|
||||
|
||||
// ==================== 排课管理 ====================
|
||||
|
||||
// 课程类型枚举
|
||||
export type LessonType = 'INTRODUCTION' | 'COLLECTIVE' | 'LANGUAGE' | 'SOCIETY' | 'SCIENCE' | 'ART' | 'HEALTH';
|
||||
// 课程类型枚举(与后端 LessonTypeEnum 对齐)
|
||||
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 {
|
||||
lessonType: LessonType;
|
||||
lessonType: string;
|
||||
lessonTypeName: string;
|
||||
count: number;
|
||||
}
|
||||
@ -446,6 +449,8 @@ export interface DayScheduleItem {
|
||||
id: number;
|
||||
className: string;
|
||||
coursePackageName: string;
|
||||
courseName?: string; // 兼容 coursePackageName 的别名
|
||||
lessonType?: string;
|
||||
lessonTypeName: string;
|
||||
teacherName: string;
|
||||
scheduledTime: string;
|
||||
|
||||
@ -123,6 +123,65 @@ export const DOMAIN_TAG_COLORS: Record<string, { bg: string; text: string }> = {
|
||||
数学: { 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,
|
||||
getGradeTagStyle,
|
||||
getDomainTagStyle,
|
||||
getLessonTypeName,
|
||||
getLessonTagStyle,
|
||||
} from '@/utils/tagMaps';
|
||||
import { parseGradeLevels } from '@/api/collections';
|
||||
import * as schoolApi from '@/api/school';
|
||||
@ -262,30 +264,6 @@ const DOMAIN_TO_CODE: Record<string, string> = {
|
||||
艺术: '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 = () => {
|
||||
loadCourses();
|
||||
};
|
||||
|
||||
@ -83,7 +83,9 @@
|
||||
<div class="schedule-time">{{ item.scheduledTime }}</div>
|
||||
<div class="schedule-info">
|
||||
<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 v-if="day.schedules.length === 0" class="no-schedule">无排课</div>
|
||||
@ -109,7 +111,10 @@
|
||||
<div class="item-time">{{ item.scheduledTime || '待定' }}</div>
|
||||
<div class="item-info">
|
||||
<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>
|
||||
<div class="item-status">
|
||||
@ -137,6 +142,7 @@ import {
|
||||
type CalendarViewResponse,
|
||||
type DayScheduleItem,
|
||||
} from '@/api/school';
|
||||
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||
|
||||
const viewType = ref<'month' | 'week'>('month');
|
||||
const selectedClassId = ref<number | undefined>();
|
||||
@ -539,6 +545,10 @@ onMounted(() => {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.schedule-lesson-type {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
@change="loadSchedules"
|
||||
>
|
||||
<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-space>
|
||||
</div>
|
||||
@ -56,6 +56,12 @@
|
||||
{{ formatDate(record.scheduledDate) }}
|
||||
<span v-if="record.scheduledTime" class="time-slot">{{ record.scheduledTime }}</span>
|
||||
</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'">
|
||||
<a-tag v-if="record.status === 'ACTIVE' || record.status === 'scheduled'" color="success">有效</a-tag>
|
||||
<a-tag v-else color="error">已取消</a-tag>
|
||||
@ -113,6 +119,12 @@
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</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-select
|
||||
v-model:value="formState.teacherId"
|
||||
@ -157,6 +169,7 @@ import {
|
||||
type ClassInfo,
|
||||
type Teacher,
|
||||
} from '@/api/school';
|
||||
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||
|
||||
// 数据
|
||||
const loading = ref(false);
|
||||
@ -188,6 +201,7 @@ const pagination = reactive({
|
||||
const columns = [
|
||||
{ title: '班级', dataIndex: 'className', key: 'className' },
|
||||
{ title: '课程', dataIndex: 'courseName', key: 'courseName' },
|
||||
{ title: '课程类型', key: 'lessonType', width: 120 },
|
||||
{ title: '授课教师', dataIndex: 'teacherName', key: 'teacherName' },
|
||||
{ title: '排课时间', key: 'scheduledDate' },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status' },
|
||||
|
||||
@ -74,17 +74,17 @@
|
||||
:class="{
|
||||
'school-schedule': schedule.source === 'SCHOOL',
|
||||
'teacher-schedule': schedule.source === 'TEACHER',
|
||||
'cancelled': schedule.status === 'CANCELLED',
|
||||
'cancelled': schedule.status === 'cancelled' || schedule.status === 'CANCELLED',
|
||||
}"
|
||||
@click="showScheduleDetail(schedule)"
|
||||
>
|
||||
<div class="schedule-time">{{ schedule.scheduledTime || '待定' }}</div>
|
||||
<div class="schedule-course">{{ schedule.courseName }}</div>
|
||||
<div class="schedule-class">{{ schedule.className }}</div>
|
||||
<div v-if="schedule.teacherName" class="schedule-teacher">
|
||||
{{ schedule.teacherName }}
|
||||
</div>
|
||||
<a-tag v-if="schedule.status === 'CANCELLED'" color="error" size="small">已取消</a-tag>
|
||||
<div class="schedule-course">{{ schedule.courseName || '课程' }}</div>
|
||||
<div class="schedule-class">{{ schedule.className || '班级' }}</div>
|
||||
<div v-if="schedule.teacherName" class="schedule-teacher">{{ schedule.teacherName }}</div>
|
||||
<a-tag v-if="schedule.lessonType" size="small" class="schedule-lesson-type"
|
||||
:style="getLessonTagStyle(schedule.lessonType)">{{ getLessonTypeName(schedule.lessonType) }}</a-tag>
|
||||
<a-tag v-if="schedule.status === 'cancelled' || schedule.status === 'CANCELLED'" color="error" size="small">已取消</a-tag>
|
||||
</div>
|
||||
<div v-if="!day.schedules.length" class="empty-day">
|
||||
暂无排课
|
||||
@ -104,13 +104,19 @@
|
||||
>
|
||||
<template v-if="selectedSchedule">
|
||||
<a-descriptions :column="1" bordered>
|
||||
<a-descriptions-item label="班级">{{ selectedSchedule.className }}</a-descriptions-item>
|
||||
<a-descriptions-item label="课程">{{ selectedSchedule.courseName }}</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="课程类型">
|
||||
<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="排课日期">{{ formatDate(selectedSchedule.scheduledDate) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="时间段">{{ selectedSchedule.scheduledTime || '待定' }}</a-descriptions-item>
|
||||
<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-descriptions-item>
|
||||
</a-descriptions>
|
||||
@ -136,6 +142,7 @@ import {
|
||||
type ClassInfo,
|
||||
type Teacher,
|
||||
} from '@/api/school';
|
||||
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||
|
||||
// 数据
|
||||
const loading = ref(false);
|
||||
@ -391,6 +398,10 @@ onMounted(() => {
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.schedule-lesson-type {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.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 />
|
||||
</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">
|
||||
<CalendarOutlined class="ref-icon" />
|
||||
<span class="ref-title">排课计划参考</span>
|
||||
</div>
|
||||
<a-table
|
||||
:columns="scheduleRefColumns"
|
||||
:data-source="scheduleRefData"
|
||||
:data-source="scheduleRefDisplay"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
bordered
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<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>
|
||||
</a-table>
|
||||
@ -89,15 +95,19 @@
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<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
|
||||
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">{{ 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>
|
||||
</div>
|
||||
@ -241,6 +251,7 @@ import {
|
||||
type ClassInfo,
|
||||
type Teacher,
|
||||
} from '@/api/school';
|
||||
import { getLessonTypeName, getLessonTagStyle } from '@/utils/tagMaps';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'success'): void;
|
||||
@ -268,9 +279,13 @@ const classTeacherMap = ref<Record<number, number>>({});
|
||||
// 排课计划参考数据
|
||||
const scheduleRefData = ref<any[]>([]);
|
||||
|
||||
// 排课计划参考表格列(与管理端课程包详情一致)
|
||||
const scheduleRefColumns = [
|
||||
{ title: '星期', dataIndex: 'dayOfWeek', key: 'dayOfWeek', width: 80 },
|
||||
{ title: '活动安排', dataIndex: 'activity', key: 'activity' },
|
||||
{ title: '时间', dataIndex: 'dayOfWeek', key: 'dayOfWeek', width: 80 },
|
||||
{ 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> = {
|
||||
@ -283,12 +298,55 @@ const weekDayNames: Record<number, string> = {
|
||||
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 {
|
||||
collectionId?: number;
|
||||
packageId?: number;
|
||||
courseId?: number; // 内部使用,自动设置为课程包的第一门课程
|
||||
lessonType?: LessonType;
|
||||
lessonType?: string; // 课程类型代码,与后端 LessonTypeEnum 对齐
|
||||
classIds: number[];
|
||||
scheduledDate?: Dayjs;
|
||||
scheduledTimeRange?: [Dayjs, Dayjs];
|
||||
@ -307,19 +365,13 @@ const filteredClasses = computed(() => {
|
||||
// 计算属性:选中的课程套餐
|
||||
const selectedCollection = computed(() => {
|
||||
if (!formData.collectionId) return null;
|
||||
const collection = collections.value.find(c => c.id === formData.collectionId) || null;
|
||||
console.log('🎯 selectedCollection:', collection);
|
||||
console.log('📦 selectedCollection.packages:', collection?.packages);
|
||||
return collection;
|
||||
return collections.value.find(c => c.id === formData.collectionId) || null;
|
||||
});
|
||||
|
||||
// 计算属性:选中的课程包
|
||||
const selectedPackage = computed(() => {
|
||||
if (!formData.packageId || !selectedCollection.value?.packages) return null;
|
||||
const pkg = selectedCollection.value.packages.find(p => p.id === formData.packageId) || null;
|
||||
console.log('📦 selectedPackage:', pkg);
|
||||
console.log('📚 selectedPackage.courses:', pkg?.courses);
|
||||
return pkg;
|
||||
return selectedCollection.value.packages.find(p => p.id === formData.packageId) || null;
|
||||
});
|
||||
|
||||
// 打开弹窗
|
||||
@ -351,19 +403,6 @@ const resetForm = () => {
|
||||
const loadCollections = async () => {
|
||||
try {
|
||||
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) {
|
||||
console.error('❌ 加载课程套餐失败:', error);
|
||||
message.error('加载课程套餐失败');
|
||||
@ -401,30 +440,16 @@ const handleCollectionChange = async (collectionId: number) => {
|
||||
try {
|
||||
// 获取课程包列表(API: GET /v1/school/packages/{collectionId}/packages)
|
||||
const packages = await getCourseCollectionPackages(collectionId);
|
||||
console.log('📦 API返回的课程包列表:', packages);
|
||||
console.log('📦 课程包数量:', packages?.length);
|
||||
|
||||
if (!packages || packages.length === 0) {
|
||||
message.warning('该套餐暂无课程包');
|
||||
return;
|
||||
}
|
||||
|
||||
// 打印每个课程包的详细信息
|
||||
packages.forEach((pkg, idx) => {
|
||||
console.log(` 📦 课程包[${idx}]:`, {
|
||||
id: pkg.id,
|
||||
name: pkg.name,
|
||||
courseCount: pkg.courseCount,
|
||||
hasCourses: !!pkg.courses,
|
||||
coursesCount: pkg.courses?.length || 0
|
||||
});
|
||||
});
|
||||
|
||||
// 更新当前套餐的课程包列表
|
||||
const collection = collections.value.find(c => c.id === collectionId);
|
||||
if (collection) {
|
||||
collection.packages = packages;
|
||||
console.log('✅ 已更新套餐的课程包列表');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 加载课程包失败:', error);
|
||||
@ -434,32 +459,24 @@ const handleCollectionChange = async (collectionId: number) => {
|
||||
|
||||
// 选择课程包
|
||||
const selectPackage = async (packageId: number) => {
|
||||
console.log('🎯 点击选择课程包,packageId:', packageId);
|
||||
console.log('📦 当前 packages 数组:', selectedCollection.value?.packages);
|
||||
|
||||
formData.packageId = packageId;
|
||||
|
||||
// 调试:查看找到的课程包
|
||||
const foundPkg = selectedCollection.value?.packages?.find((p: any) => p.id === packageId);
|
||||
console.log('🔍 找到的课程包:', foundPkg);
|
||||
|
||||
// 自动选择第一门课程(用于后端API)
|
||||
if (selectedCollection.value?.packages) {
|
||||
const selectedPkg = selectedCollection.value.packages.find((p: any) => p.id === packageId);
|
||||
if (selectedPkg?.courses && selectedPkg.courses.length > 0) {
|
||||
// 自动设置为第一门课程
|
||||
formData.courseId = selectedPkg.courses[0].id;
|
||||
console.log('✅ 自动选择第一门课程:', formData.courseId);
|
||||
|
||||
// 加载排课计划参考(从第一门课程中获取)
|
||||
const firstCourse = selectedPkg.courses[0];
|
||||
if (firstCourse.scheduleRefData) {
|
||||
// 加载排课计划参考(从课程包中任一课程的 scheduleRefData 获取,后端统一来自 course_package)
|
||||
const courseWithRef = selectedPkg.courses.find((c: any) => c.scheduleRefData);
|
||||
const rawRef = courseWithRef?.scheduleRefData ?? (selectedPkg as any).scheduleRefData;
|
||||
if (rawRef) {
|
||||
try {
|
||||
const parsedData = JSON.parse(firstCourse.scheduleRefData);
|
||||
scheduleRefData.value = Array.isArray(parsedData) ? parsedData : [];
|
||||
console.log('✅ 排课计划参考数据:', scheduleRefData.value);
|
||||
const parsed = typeof rawRef === 'string' ? JSON.parse(rawRef) : rawRef;
|
||||
scheduleRefData.value = Array.isArray(parsed) ? parsed : [];
|
||||
} catch (e) {
|
||||
console.error('解析排课数据失败:', e);
|
||||
console.error('解析排课计划参考失败:', e);
|
||||
scheduleRefData.value = [];
|
||||
}
|
||||
} else {
|
||||
@ -488,8 +505,8 @@ const loadLessonTypes = async (packageId: number) => {
|
||||
};
|
||||
|
||||
// 选择课程类型
|
||||
const selectLessonType = (type: LessonType) => {
|
||||
formData.lessonType = type;
|
||||
const selectLessonType = (lessonType: string) => {
|
||||
formData.lessonType = lessonType;
|
||||
};
|
||||
|
||||
// 切换班级选择
|
||||
@ -520,25 +537,24 @@ const filterTeacher = (input: string, option: any) => {
|
||||
return teacher?.name?.toLowerCase().includes(input.toLowerCase()) || false;
|
||||
};
|
||||
|
||||
// 获取课程类型图标
|
||||
const getLessonTypeIcon = (type: LessonType): string => {
|
||||
const icons: Record<LessonType, string> = {
|
||||
INTRODUCTION: '📖',
|
||||
// 获取课程类型图标(与课程列表 tag 类型对齐)
|
||||
const getLessonTypeIcon = (type: string): string => {
|
||||
const icons: Record<string, string> = {
|
||||
INTRODUCTION: '📖', INTRO: '📖',
|
||||
COLLECTIVE: '👥',
|
||||
LANGUAGE: '💬',
|
||||
SOCIETY: '🤝',
|
||||
SCIENCE: '🔬',
|
||||
ART: '🎨',
|
||||
HEALTH: '❤️',
|
||||
LANGUAGE: '💬', DOMAIN_LANGUAGE: '💬',
|
||||
SOCIETY: '🤝', SOCIAL: '🤝', DOMAIN_SOCIAL: '🤝',
|
||||
SCIENCE: '🔬', DOMAIN_SCIENCE: '🔬',
|
||||
ART: '🎨', DOMAIN_ART: '🎨',
|
||||
HEALTH: '❤️', DOMAIN_HEALTH: '❤️',
|
||||
};
|
||||
return icons[type] || '📚';
|
||||
};
|
||||
|
||||
// 获取选中的课程类型名称
|
||||
// 获取选中的课程类型名称(与课程列表 tag 一致)
|
||||
const getSelectedLessonTypeName = (): string => {
|
||||
if (!formData.lessonType) return '-';
|
||||
const type = lessonTypes.value.find(t => t.lessonType === formData.lessonType);
|
||||
return type?.lessonTypeName || '-';
|
||||
return getLessonTypeName(formData.lessonType);
|
||||
};
|
||||
|
||||
// 获取选择的时间范围
|
||||
@ -670,261 +686,4 @@ const handleCancel = () => {
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.steps-navigator {
|
||||
margin-bottom: 32px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
min-height: 400px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.step-panel {
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
color: #2D3436;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
margin-top: 20px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.collection-option {
|
||||
.collection-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
.collection-info {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.packages-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.packages-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.package-card {
|
||||
padding: 12px;
|
||||
background: white;
|
||||
border: 2px solid #E0E0E0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #BDBDBD;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #FF8C42;
|
||||
background: #FFF0E6;
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-weight: 500;
|
||||
color: #2D3436;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.package-grade {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.package-count {
|
||||
font-size: 11px;
|
||||
color: #FF8C42;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule-ref-card {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #FFF8F0;
|
||||
border-radius: 8px;
|
||||
|
||||
.ref-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.ref-icon {
|
||||
color: #FF8C42;
|
||||
}
|
||||
|
||||
.ref-title {
|
||||
font-weight: 600;
|
||||
color: #2D3436;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lesson-type-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.lesson-type-card {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border: 2px solid #E0E0E0;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #FF8C42;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(255, 140, 66, 0.2);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #FF8C42;
|
||||
background: #FFF0E6;
|
||||
}
|
||||
|
||||
.type-icon {
|
||||
font-size: 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
font-weight: 500;
|
||||
color: #2D3436;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.type-count {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.grade-selector {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.class-teacher-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 12px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.class-teacher-card {
|
||||
background: white;
|
||||
border: 2px solid #E0E0E0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.selected {
|
||||
border-color: #FF8C42;
|
||||
}
|
||||
|
||||
.class-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
|
||||
.class-info {
|
||||
flex: 1;
|
||||
|
||||
.class-name {
|
||||
font-weight: 500;
|
||||
color: #2D3436;
|
||||
}
|
||||
|
||||
.class-detail {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.teacher-selector {
|
||||
padding: 12px;
|
||||
border-top: 1px solid #E0E0E0;
|
||||
background: #FAFAFA;
|
||||
}
|
||||
}
|
||||
|
||||
.selection-summary {
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: #FFF8F0;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
color: #FF8C42;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.confirm-info {
|
||||
margin-top: 24px;
|
||||
padding: 16px;
|
||||
background: #F5F5F5;
|
||||
border-radius: 8px;
|
||||
|
||||
div {
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #FF8C42;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.class-teacher-list {
|
||||
margin-top: 20px;
|
||||
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.teacher-status {
|
||||
color: #999;
|
||||
|
||||
&.assigned {
|
||||
color: #52c41a;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss" src="./CreateScheduleModal.scss"></style>
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ref, nextTick } from 'vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import ScheduleList from './ScheduleList.vue';
|
||||
import TimetableView from './TimetableView.vue';
|
||||
@ -45,6 +45,20 @@ const showCreateModal = () => {
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
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 = () => {
|
||||
|
||||
@ -13,7 +13,6 @@ import com.reading.platform.dto.response.CalendarViewResponse;
|
||||
import com.reading.platform.dto.response.ConflictCheckResult;
|
||||
import com.reading.platform.dto.response.LessonTypeInfo;
|
||||
import com.reading.platform.dto.response.SchedulePlanResponse;
|
||||
import com.reading.platform.dto.response.TimetableResponse;
|
||||
import com.reading.platform.entity.SchedulePlan;
|
||||
import com.reading.platform.service.SchoolScheduleService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@ -25,6 +24,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@ -55,7 +55,7 @@ public class SchoolScheduleController {
|
||||
tenantId, pageNum, pageSize, startDate, endDate, classId, teacherId, status);
|
||||
|
||||
List<SchedulePlanResponse> records = page.getRecords().stream()
|
||||
.map(this::toResponse)
|
||||
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Result.success(PageResult.of(records, page.getTotal(), page.getCurrent(), page.getSize()));
|
||||
@ -63,14 +63,13 @@ public class SchoolScheduleController {
|
||||
|
||||
@GetMapping("/timetable")
|
||||
@Operation(summary = "获取课程表")
|
||||
public Result<TimetableResponse> getTimetable(
|
||||
public Result<Map<String, Object>> getTimetable(
|
||||
@RequestParam(required = false) Long classId,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
|
||||
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
|
||||
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
// Service 返回 Map,暂时保留,后续可优化为 TimetableResponse
|
||||
return Result.success((TimetableResponse) schoolScheduleService.getTimetable(tenantId, classId, startDate, endDate));
|
||||
return Result.success(schoolScheduleService.getTimetable(tenantId, classId, startDate, endDate));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@ -78,7 +77,7 @@ public class SchoolScheduleController {
|
||||
public Result<SchedulePlanResponse> getSchedule(@PathVariable Long id) {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
SchedulePlan schedule = schoolScheduleService.getScheduleById(id, tenantId);
|
||||
return Result.success(toResponse(schedule));
|
||||
return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@ -87,7 +86,7 @@ public class SchoolScheduleController {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
List<SchedulePlan> plans = schoolScheduleService.createSchedule(tenantId, request);
|
||||
List<SchedulePlanResponse> result = plans.stream()
|
||||
.map(this::toResponse)
|
||||
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
}
|
||||
@ -100,7 +99,7 @@ public class SchoolScheduleController {
|
||||
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
SchedulePlan schedule = schoolScheduleService.updateSchedule(id, tenantId, request);
|
||||
return Result.success(toResponse(schedule));
|
||||
return Result.success(schoolScheduleService.toSchedulePlanResponse(schedule));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@ -119,7 +118,7 @@ public class SchoolScheduleController {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
List<SchedulePlan> plans = schoolScheduleService.batchCreateSchedules(tenantId, requests);
|
||||
List<SchedulePlanResponse> result = plans.stream()
|
||||
.map(this::toResponse)
|
||||
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
}
|
||||
@ -152,7 +151,7 @@ public class SchoolScheduleController {
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
List<SchedulePlan> plans = schoolScheduleService.createSchedulesByClasses(tenantId, request);
|
||||
List<SchedulePlanResponse> result = plans.stream()
|
||||
.map(this::toResponse)
|
||||
.map(schoolScheduleService::toSchedulePlanResponse)
|
||||
.collect(Collectors.toList());
|
||||
return Result.success(result);
|
||||
}
|
||||
@ -170,30 +169,4 @@ public class SchoolScheduleController {
|
||||
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 = "课程包名称")
|
||||
private String coursePackageName;
|
||||
|
||||
@Schema(description = "课程类型代码 (如 DOMAIN_HEALTH)")
|
||||
private String lessonType;
|
||||
|
||||
@Schema(description = "课程类型名称")
|
||||
private String lessonTypeName;
|
||||
|
||||
|
||||
@ -11,13 +11,22 @@ import lombok.Getter;
|
||||
public enum LessonTypeEnum {
|
||||
|
||||
INTRODUCTION("INTRODUCTION", "导入课"),
|
||||
INTRO("INTRO", "导入课"),
|
||||
COLLECTIVE("COLLECTIVE", "集体课"),
|
||||
LANGUAGE("LANGUAGE", "语言课"),
|
||||
ART("ART", "艺术课"),
|
||||
MUSIC("MUSIC", "音乐课"),
|
||||
SPORT("SPORT", "体育课"),
|
||||
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 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.ConflictCheckResult;
|
||||
import com.reading.platform.dto.response.LessonTypeInfo;
|
||||
import com.reading.platform.dto.response.SchedulePlanResponse;
|
||||
import com.reading.platform.entity.SchedulePlan;
|
||||
|
||||
import java.time.LocalDate;
|
||||
@ -136,4 +137,12 @@ public interface SchoolScheduleService extends IService<SchedulePlan> {
|
||||
CalendarViewResponse getCalendarViewData(Long tenantId, LocalDate startDate, LocalDate endDate,
|
||||
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.ConflictCheckResult;
|
||||
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.CoursePackageCourse;
|
||||
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.CoursePackageCourseMapper;
|
||||
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.SchoolScheduleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@ -44,8 +50,10 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
||||
|
||||
private final SchedulePlanMapper schedulePlanMapper;
|
||||
private final ScheduleConflictService scheduleConflictService;
|
||||
private final CoursePackageCourseMapper coursePackageCoursePackageMapper;
|
||||
private final CourseLessonService courseLessonService;
|
||||
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.setClassId(request.getClassId());
|
||||
plan.setCourseId(request.getCourseId());
|
||||
plan.setCoursePackageId(request.getCoursePackageId());
|
||||
plan.setLessonType(request.getLessonType());
|
||||
plan.setTeacherId(request.getTeacherId());
|
||||
plan.setScheduledDate(date);
|
||||
plan.setScheduledTime(request.getScheduledTime());
|
||||
@ -94,7 +104,7 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
||||
plan.setRepeatEndDate(request.getRepeatEndDate());
|
||||
plan.setSource(StringUtils.hasText(request.getSource()) ? request.getSource() : "SCHOOL");
|
||||
plan.setNote(request.getNote());
|
||||
plan.setStatus("scheduled");
|
||||
plan.setStatus("ACTIVE");
|
||||
plan.setReminderSent(0);
|
||||
|
||||
schedulePlanMapper.insert(plan);
|
||||
@ -212,7 +222,11 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
||||
wrapper.eq(SchedulePlan::getTeacherId, teacherId);
|
||||
}
|
||||
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);
|
||||
@ -248,13 +262,24 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
||||
|
||||
List<SchedulePlan> plans = schedulePlanMapper.selectList(wrapper);
|
||||
|
||||
// 按日期分组
|
||||
Map<LocalDate, List<SchedulePlan>> groupedByDate = plans.stream()
|
||||
.collect(Collectors.groupingBy(SchedulePlan::getScheduledDate, TreeMap::new, Collectors.toList()));
|
||||
// 转换为带展示字段的 Response
|
||||
List<SchedulePlanResponse> responses = plans.stream()
|
||||
.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()
|
||||
.collect(Collectors.groupingBy(SchedulePlan::getWeekDay, TreeMap::new, Collectors.toList()));
|
||||
Map<Integer, List<SchedulePlanResponse>> groupedByWeekDay = responses.stream()
|
||||
.filter(r -> r.getWeekDay() != null)
|
||||
.collect(Collectors.groupingBy(SchedulePlanResponse::getWeekDay, TreeMap::new, Collectors.toList()));
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("startDate", startDate);
|
||||
@ -293,79 +318,84 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
||||
public List<LessonTypeInfo> getCoursePackageLessonTypes(Long tenantId, Long coursePackageId) {
|
||||
log.info("获取课程包的课程类型列表: tenantId={}, coursePackageId={}", tenantId, coursePackageId);
|
||||
|
||||
// 1. 根据 coursePackageId 查询 course_package_course 获取 courseId 列表
|
||||
List<CoursePackageCourse> packageCourses = coursePackageCoursePackageMapper.selectList(
|
||||
new LambdaQueryWrapper<CoursePackageCourse>()
|
||||
.eq(CoursePackageCourse::getPackageId, coursePackageId)
|
||||
.orderByAsc(CoursePackageCourse::getSortOrder)
|
||||
);
|
||||
// 1. 优先从 course_lesson 表获取课程环节类型(course_package_course 表已废弃)
|
||||
List<CourseLesson> lessons = courseLessonService.findByCourseId(coursePackageId);
|
||||
|
||||
if (packageCourses.isEmpty()) {
|
||||
log.warn("课程包下没有课程: coursePackageId={}", coursePackageId);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<Long> courseIds = packageCourses.stream()
|
||||
.map(CoursePackageCourse::getCourseId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 2. 根据 courseId 列表查询 course 表
|
||||
List<CoursePackage> courses = courseMapper.selectList(
|
||||
new LambdaQueryWrapper<CoursePackage>()
|
||||
.in(CoursePackage::getId, courseIds)
|
||||
.eq(CoursePackage::getStatus, "PUBLISHED")
|
||||
);
|
||||
|
||||
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;
|
||||
if (!lessons.isEmpty()) {
|
||||
Map<String, LessonTypeInfo> lessonTypeMap = new LinkedHashMap<>();
|
||||
for (CourseLesson lesson : lessons) {
|
||||
String code = lesson.getLessonType();
|
||||
if (!StringUtils.hasText(code)) continue;
|
||||
String displayName = getLessonTypeDisplayName(code);
|
||||
lessonTypeMap.compute(code, (k, v) -> {
|
||||
if (v == null) {
|
||||
return LessonTypeInfo.builder()
|
||||
.lessonType(code)
|
||||
.lessonTypeName(displayName)
|
||||
.count(1L)
|
||||
.build();
|
||||
}
|
||||
v.setCount(v.getCount() + 1);
|
||||
return v;
|
||||
});
|
||||
}
|
||||
List<LessonTypeInfo> result = new ArrayList<>(lessonTypeMap.values());
|
||||
log.info("课程包课程类型统计完成(来自course_lesson): coursePackageId={}, types={}", coursePackageId, result.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
// 解析 JSON 数组
|
||||
JSONArray jsonArray = JSON.parseArray(scheduleRefData);
|
||||
if (jsonArray != null && !jsonArray.isEmpty()) {
|
||||
JSONObject firstItem = jsonArray.getJSONObject(0);
|
||||
if (firstItem != null && firstItem.containsKey("lessonType")) {
|
||||
String lessonType = firstItem.getString("lessonType");
|
||||
if (StringUtils.hasText(lessonType)) {
|
||||
// 转换为英文代码
|
||||
String lessonTypeCode = convertLessonTypeNameToCode(lessonType);
|
||||
// 2. 无 course_lesson 时,从 course_package.schedule_ref_data 解析
|
||||
CoursePackage pkg = courseMapper.selectById(coursePackageId);
|
||||
if (pkg == null) {
|
||||
log.warn("课程包不存在: coursePackageId={}", coursePackageId);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
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) {
|
||||
return LessonTypeInfo.builder()
|
||||
.lessonType(lessonTypeCode)
|
||||
.lessonTypeName(lessonType)
|
||||
.lessonType(code)
|
||||
.lessonTypeName(lessonTypeName)
|
||||
.count(1L)
|
||||
.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());
|
||||
log.info("课程包课程类型统计完成: coursePackageId={}, types={}", coursePackageId, result.size());
|
||||
log.info("课程包课程类型统计完成(来自schedule_ref_data): coursePackageId={}, types={}", coursePackageId, result.size());
|
||||
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())
|
||||
.className(getClassNameForCalendar(plan))
|
||||
.coursePackageName(getCoursePackageName(plan))
|
||||
.lessonType(plan.getLessonType())
|
||||
.lessonTypeName(getLessonTypeName(plan))
|
||||
.teacherName(getTeacherName(plan))
|
||||
.scheduledTime(plan.getScheduledTime())
|
||||
@ -499,23 +530,77 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
||||
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) {
|
||||
// TODO: 查询班级表获取班级名称
|
||||
return "班级" + plan.getClassId();
|
||||
if (plan.getClassId() == null) return "";
|
||||
Clazz clazz = clazzMapper.selectById(plan.getClassId());
|
||||
return clazz != null ? clazz.getName() : "班级" + plan.getClassId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取课程包名称(用于日历显示)
|
||||
*/
|
||||
private String getCoursePackageName(SchedulePlan plan) {
|
||||
// TODO: 查询课程包表获取课程包名称
|
||||
if (plan.getCoursePackageId() != null) {
|
||||
return "课程包" + plan.getCoursePackageId();
|
||||
}
|
||||
return "";
|
||||
Long id = plan.getCoursePackageId() != null ? plan.getCoursePackageId() : plan.getCourseId();
|
||||
if (id == null) return "";
|
||||
CoursePackage pkg = courseMapper.selectById(id);
|
||||
return pkg != null ? pkg.getName() : "课程包" + id;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -536,8 +621,9 @@ public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, S
|
||||
* 获取教师名称(用于日历显示)
|
||||
*/
|
||||
private String getTeacherName(SchedulePlan plan) {
|
||||
// TODO: 查询教师表获取教师名称
|
||||
return "教师" + plan.getTeacherId();
|
||||
if (plan.getTeacherId() == null) return "";
|
||||
Teacher teacher = teacherMapper.selectById(plan.getTeacherId());
|
||||
return teacher != null ? teacher.getName() : "教师" + plan.getTeacherId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user