kindergarten/reading-platform-frontend/src/views/school/schedule/ScheduleView.vue
2026-03-03 13:59:02 +08:00

931 lines
30 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

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

<template>
<div>
<div class="flex justify-between items-center mb-5">
<h2 class="m-0">课程排期</h2>
<a-space>
<a-dropdown>
<a-button>
<template #icon><PlusOutlined /></template>
新建排课
<DownOutlined />
</a-button>
<template #overlay>
<a-menu @click="handleCreateMenuClick">
<a-menu-item key="single">
<PlusOutlined /> 单个新建
</a-menu-item>
<a-menu-item key="batch">
<AppstoreAddOutlined /> 批量新建
</a-menu-item>
<a-menu-item key="template">
<CopyOutlined /> 从模板创建
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-button @click="showTemplateModal">
<template #icon><CopyOutlined /></template>
排课模板
</a-button>
<a-dropdown>
<a-button>
<template #icon><CalendarOutlined /></template>
视图切换
<DownOutlined />
</a-button>
<template #overlay>
<a-menu @click="handleViewMenuClick">
<a-menu-item key="list">
<UnorderedListOutlined /> 列表视图
</a-menu-item>
<a-menu-item key="timetable">
<TableOutlined /> 课表视图
</a-menu-item>
<a-menu-item key="calendar">
<CalendarOutlined /> 日历视图
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</div>
<!-- 筛选区 -->
<div class="mb-5 p-4 bg-[#fafafa] rounded-lg">
<a-space wrap>
<a-select
v-model:value="filters.classId"
placeholder="选择班级"
allowClear
class="w-[150px]"
@change="loadSchedules"
>
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
{{ cls.name }}
</a-select-option>
</a-select>
<a-select
v-model:value="filters.teacherId"
placeholder="选择教师"
allowClear
class="w-[150px]"
@change="loadSchedules"
>
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
{{ teacher.name }}
</a-select-option>
</a-select>
<a-range-picker
:value="dateRange"
@change="handleDateChange"
/>
<a-select
v-model:value="filters.status"
placeholder="状态"
allowClear
class="w-[120px]"
@change="loadSchedules"
>
<a-select-option value="ACTIVE">有效</a-select-option>
<a-select-option value="CANCELLED">已取消</a-select-option>
</a-select>
</a-space>
</div>
<!-- 排课列表 -->
<a-table
:columns="columns"
:data-source="schedules"
:loading="loading"
:pagination="pagination"
rowKey="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'scheduledDate'">
{{ formatDate(record.scheduledDate) }}
<span v-if="record.scheduledTime" class="ml-2 text-[#666] text-xs">{{ record.scheduledTime }}</span>
</template>
<template v-if="column.key === 'repeatType'">
<a-tag v-if="record.repeatType === 'NONE'" color="default">单次</a-tag>
<a-tag v-else-if="record.repeatType === 'DAILY'" color="blue">每日</a-tag>
<a-tag v-else-if="record.repeatType === 'WEEKLY'" color="green">每周</a-tag>
</template>
<template v-if="column.key === 'source'">
<a-tag v-if="record.source === 'SCHOOL'" color="orange">学校排课</a-tag>
<a-tag v-else color="purple">教师预约</a-tag>
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 'ACTIVE'" color="success">有效</a-tag>
<a-tag v-else color="error">已取消</a-tag>
</template>
<template v-if="column.key === 'actions'">
<a-space>
<a-button type="link" size="small" @click="showEditModal(record)">编辑</a-button>
<a-popconfirm
v-if="record.status === 'ACTIVE'"
title="确定要取消此排课吗?"
@confirm="handleCancel(record.id)"
>
<a-button type="link" size="small" danger>取消</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<!-- 新建/编辑排课弹窗 -->
<a-modal
v-model:open="modalVisible"
:title="editingSchedule ? '编辑排课' : '新建排课'"
:confirm-loading="modalLoading"
@ok="handleSubmit"
@cancel="handleModalCancel"
width="600px"
>
<a-form
ref="formRef"
:model="formState"
:rules="formRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="班级" name="classId">
<a-select
v-model:value="formState.classId"
placeholder="选择班级"
:disabled="!!editingSchedule"
>
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
{{ cls.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="课程" name="courseId">
<a-select
v-model:value="formState.courseId"
placeholder="选择课程"
:disabled="!!editingSchedule"
>
<a-select-option v-for="course in courses" :key="course.id" :value="course.id">
{{ course.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="授课教师" name="teacherId">
<a-select
v-model:value="formState.teacherId"
placeholder="选择教师(可选)"
allowClear
>
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
{{ teacher.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="排课日期" name="scheduledDate">
<a-date-picker v-model:value="formState.scheduledDate" style="width: 100%" />
</a-form-item>
<a-form-item label="时间段" name="scheduledTimeRange">
<a-time-range-picker
v-model:value="formState.scheduledTimeRange"
format="HH:mm"
style="width: 100%"
:placeholder="['开始时间', '结束时间']"
/>
</a-form-item>
<a-form-item label="重复方式" name="repeatType">
<a-radio-group v-model:value="formState.repeatType">
<a-radio value="NONE">单次</a-radio>
<a-radio value="DAILY">每日重复</a-radio>
<a-radio value="WEEKLY">每周重复</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item v-if="formState.repeatType !== 'NONE'" label="重复截止" name="repeatEndDate">
<a-date-picker v-model:value="formState.repeatEndDate" style="width: 100%" />
</a-form-item>
<a-form-item label="备注" name="note">
<a-textarea v-model:value="formState.note" :rows="2" placeholder="备注信息" />
</a-form-item>
</a-form>
</a-modal>
<!-- 排课模板管理弹窗 -->
<a-modal
v-model:open="templateModalVisible"
title="排课模板管理"
:footer="null"
width="800px"
>
<div class="mb-4 flex justify-end">
<a-button type="primary" size="small" @click="showCreateTemplateModal">
<PlusOutlined /> 新建模板
</a-button>
</div>
<a-table
:columns="templateColumns"
:data-source="templates"
:loading="templateLoading"
size="small"
rowKey="id"
:pagination="{ pageSize: 5 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'weekDay'">
{{ weekDayNames[record.weekDay] || '-' }}
</template>
<template v-if="column.key === 'isDefault'">
<a-tag v-if="record.isDefault" color="blue">默认</a-tag>
<span v-else>-</span>
</template>
<template v-if="column.key === 'actions'">
<a-space>
<a-button type="link" size="small" @click="applyTemplate(record as any)">应用</a-button>
<a-button type="link" size="small" @click="showEditTemplateModal(record as any)">编辑</a-button>
<a-popconfirm title="确定删除?" @confirm="handleDeleteTemplate((record as any).id)">
<a-button type="link" size="small" danger>删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</a-modal>
<!-- 新建/编辑模板弹窗 -->
<a-modal
v-model:open="templateFormModalVisible"
:title="editingTemplate ? '编辑模板' : '新建模板'"
:confirm-loading="templateFormLoading"
@ok="handleTemplateSubmit"
>
<a-form
:model="templateForm"
:rules="templateFormRules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="模板名称" name="name">
<a-input v-model:value="templateForm.name" placeholder="如:周一早读" />
</a-form-item>
<a-form-item label="课程" name="courseId">
<a-select v-model:value="templateForm.courseId" placeholder="选择课程">
<a-select-option v-for="course in courses" :key="course.id" :value="course.id">
{{ course.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="班级" name="classId">
<a-select v-model:value="templateForm.classId" placeholder="选择班级" allowClear>
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
{{ cls.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="教师" name="teacherId">
<a-select v-model:value="templateForm.teacherId" placeholder="选择教师" allowClear>
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
{{ teacher.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="时间段" name="scheduledTime">
<a-input v-model:value="templateForm.scheduledTime" placeholder="如09:00-09:30" />
</a-form-item>
<a-form-item label="周几" name="weekDay">
<a-select v-model:value="templateForm.weekDay" placeholder="选择周几" allowClear>
<a-select-option :value="1">周一</a-select-option>
<a-select-option :value="2">周二</a-select-option>
<a-select-option :value="3">周三</a-select-option>
<a-select-option :value="4">周四</a-select-option>
<a-select-option :value="5">周五</a-select-option>
<a-select-option :value="6">周六</a-select-option>
<a-select-option :value="0">周日</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="时长(分钟)" name="duration">
<a-input-number v-model:value="templateForm.duration" :min="5" :max="120" />
</a-form-item>
<a-form-item label="设为默认" name="isDefault">
<a-switch v-model:checked="templateForm.isDefault" />
</a-form-item>
</a-form>
</a-modal>
<!-- 批量排课弹窗 -->
<a-modal
v-model:open="batchModalVisible"
title="批量新建排课"
:confirm-loading="batchLoading"
@ok="handleBatchSubmit"
width="900px"
>
<a-alert
message="批量添加排课信息,点击下方按钮添加更多行"
type="info"
show-icon
style="margin-bottom: 16px"
/>
<div class="mb-4">
<a-button type="dashed" @click="addBatchItem">
<PlusOutlined /> 添加排课
</a-button>
</div>
<a-table
:columns="batchColumns"
:data-source="batchItems"
size="small"
rowKey="key"
:pagination="false"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'classId'">
<a-select v-model:value="record.classId" placeholder="班级" style="width: 100%">
<a-select-option v-for="cls in classes" :key="cls.id" :value="cls.id">
{{ cls.name }}
</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'courseId'">
<a-select v-model:value="record.courseId" placeholder="课程" style="width: 100%">
<a-select-option v-for="course in courses" :key="course.id" :value="course.id">
{{ course.name }}
</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'teacherId'">
<a-select v-model:value="record.teacherId" placeholder="教师" style="width: 100%" allowClear>
<a-select-option v-for="teacher in teachers" :key="teacher.id" :value="teacher.id">
{{ teacher.name }}
</a-select-option>
</a-select>
</template>
<template v-if="column.key === 'scheduledDate'">
<a-date-picker v-model:value="record.scheduledDate" style="width: 100%" />
</template>
<template v-if="column.key === 'scheduledTime'">
<a-input v-model:value="record.scheduledTime" placeholder="09:00-09:30" style="width: 100%" />
</template>
<template v-if="column.key === 'actions'">
<a-button type="link" size="small" danger @click="removeBatchItem(index)">
<DeleteOutlined />
</a-button>
</template>
</template>
</a-table>
</a-modal>
<!-- 从模板创建弹窗 -->
<a-modal
v-model:open="templateSelectModalVisible"
title="从模板创建排课"
:confirm-loading="templateSelectLoading"
@ok="handleTemplateSelectSubmit"
>
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<a-form-item label="选择模板">
<a-select v-model:value="selectedTemplateId" placeholder="选择排课模板" style="width: 100%">
<a-select-option v-for="tpl in templates" :key="tpl.id" :value="tpl.id">
{{ tpl.name }} - {{ tpl.courseName }} ({{ tpl.scheduledTime }})
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="排课日期">
<a-date-picker v-model:value="templateApplyDate" style="width: 100%" />
</a-form-item>
<a-form-item v-if="selectedTemplate" label="模板详情">
<a-descriptions :column="1" size="small">
<a-descriptions-item label="课程">{{ selectedTemplate.courseName }}</a-descriptions-item>
<a-descriptions-item label="班级">{{ selectedTemplate.className || '未指定' }}</a-descriptions-item>
<a-descriptions-item label="教师">{{ selectedTemplate.teacherName || '未指定' }}</a-descriptions-item>
<a-descriptions-item label="时间">{{ selectedTemplate.scheduledTime }}</a-descriptions-item>
</a-descriptions>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue';
import { useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import type { TableProps, FormInstance } from 'ant-design-vue';
import dayjs, { Dayjs } from 'dayjs';
import { PlusOutlined, CalendarOutlined, DownOutlined, AppstoreAddOutlined, CopyOutlined, DeleteOutlined, UnorderedListOutlined, TableOutlined } from '@ant-design/icons-vue';
import {
getSchedules,
createSchedule,
updateSchedule,
cancelSchedule,
getClasses,
getTeachers,
getSchoolCourses,
getScheduleTemplates,
createScheduleTemplate,
updateScheduleTemplate,
deleteScheduleTemplate,
applyScheduleTemplate,
batchCreateSchedules,
type SchedulePlan,
type CreateScheduleDto,
type UpdateScheduleDto,
type ClassInfo,
type Teacher,
type ScheduleTemplate,
} from '@/api/school';
const router = useRouter();
// 数据
const loading = ref(false);
const schedules = ref<SchedulePlan[]>([]);
const classes = ref<ClassInfo[]>([]);
const teachers = ref<Teacher[]>([]);
const courses = ref<any[]>([]);
// 筛选
const filters = reactive({
classId: undefined as number | undefined,
teacherId: undefined as number | undefined,
status: undefined as string | undefined,
startDate: undefined as string | undefined,
endDate: undefined as string | undefined,
});
const dateRange = ref<[Dayjs, Dayjs] | undefined>(undefined);
// 分页
const pagination = reactive({
current: 1,
pageSize: 20,
total: 0,
showSizeChanger: true,
showTotal: (total: number) => `${total}`,
});
// 表格列
const columns = [
{ title: '班级', dataIndex: 'className', key: 'className' },
{ title: '课程', dataIndex: 'courseName', key: 'courseName' },
{ title: '授课教师', dataIndex: 'teacherName', key: 'teacherName' },
{ title: '排课时间', key: 'scheduledDate' },
{ title: '重复', dataIndex: 'repeatType', key: 'repeatType' },
{ title: '来源', dataIndex: 'source', key: 'source' },
{ title: '状态', dataIndex: 'status', key: 'status' },
{ title: '操作', key: 'actions', width: 150 },
];
// 弹窗
const modalVisible = ref(false);
const modalLoading = ref(false);
const editingSchedule = ref<SchedulePlan | null>(null);
const formRef = ref<FormInstance>();
// 表单
const formState = reactive<CreateScheduleDto & { repeatEndDate?: Dayjs; scheduledDate?: Dayjs; scheduledTimeRange?: [Dayjs, Dayjs] }>({
classId: undefined as any,
courseId: undefined as any,
teacherId: undefined,
scheduledDate: undefined,
scheduledTimeRange: undefined,
repeatType: 'NONE',
repeatEndDate: undefined,
note: undefined,
});
const formRules = {
classId: [{ required: true, message: '请选择班级' }],
courseId: [{ required: true, message: '请选择课程' }],
scheduledDate: [{ required: true, message: '请选择排课日期' }],
repeatType: [{ required: true, message: '请选择重复方式' }],
};
// 加载数据
const loadSchedules = async () => {
loading.value = true;
try {
const res = await getSchedules({
page: pagination.current,
pageSize: pagination.pageSize,
...filters,
});
schedules.value = res.items;
pagination.total = res.total;
} catch (error) {
message.error('加载排课列表失败');
} finally {
loading.value = false;
}
};
const loadBaseData = async () => {
try {
const [classesRes, teachersRes, coursesRes] = await Promise.all([
getClasses(),
getTeachers({ page: 1, pageSize: 100 }),
getSchoolCourses(),
]);
classes.value = classesRes;
teachers.value = teachersRes.items;
courses.value = coursesRes;
} catch (error) {
message.error('加载基础数据失败');
}
};
// 事件处理
const handleTableChange: TableProps['onChange'] = (pag) => {
pagination.current = pag.current || 1;
pagination.pageSize = pag.pageSize || 20;
loadSchedules();
};
const handleDateChange = (dates: any) => {
if (dates && dates.length === 2) {
dateRange.value = dates;
filters.startDate = dates[0].format('YYYY-MM-DD');
filters.endDate = dates[1].format('YYYY-MM-DD');
} else {
dateRange.value = undefined;
filters.startDate = undefined;
filters.endDate = undefined;
}
loadSchedules();
};
const showCreateModal = () => {
editingSchedule.value = null;
Object.assign(formState, {
classId: undefined,
courseId: undefined,
teacherId: undefined,
scheduledDate: undefined,
scheduledTimeRange: undefined,
repeatType: 'NONE',
repeatEndDate: undefined,
note: undefined,
});
modalVisible.value = true;
};
const showEditModal = (record: any) => {
editingSchedule.value = record;
// 解析时间字符串为时间范围
let timeRange: [Dayjs, Dayjs] | undefined = undefined;
if (record.scheduledTime) {
const parts = record.scheduledTime.split('-');
if (parts.length === 2) {
const baseDate = dayjs().format('YYYY-MM-DD');
timeRange = [
dayjs(`${baseDate} ${parts[0]}`, 'YYYY-MM-DD HH:mm'),
dayjs(`${baseDate} ${parts[1]}`, 'YYYY-MM-DD HH:mm'),
];
}
}
Object.assign(formState, {
classId: record.classId,
courseId: record.courseId,
teacherId: record.teacherId,
scheduledDate: record.scheduledDate ? dayjs(record.scheduledDate) : undefined,
scheduledTimeRange: timeRange,
repeatType: record.repeatType,
repeatEndDate: record.repeatEndDate ? dayjs(record.repeatEndDate) : undefined,
note: record.note,
});
modalVisible.value = true;
};
const handleModalCancel = () => {
modalVisible.value = false;
formRef.value?.resetFields();
};
const handleSubmit = async () => {
try {
await formRef.value?.validate();
modalLoading.value = true;
// 格式化时间范围
let scheduledTime: string | undefined = undefined;
if (formState.scheduledTimeRange && formState.scheduledTimeRange.length === 2) {
scheduledTime = `${formState.scheduledTimeRange[0].format('HH:mm')}-${formState.scheduledTimeRange[1].format('HH:mm')}`;
}
const data: CreateScheduleDto | UpdateScheduleDto = {
classId: formState.classId,
courseId: formState.courseId,
teacherId: formState.teacherId,
scheduledDate: formState.scheduledDate?.format('YYYY-MM-DD'),
scheduledTime,
repeatType: formState.repeatType,
repeatEndDate: formState.repeatEndDate?.format('YYYY-MM-DD'),
note: formState.note,
};
if (editingSchedule.value) {
await updateSchedule(editingSchedule.value.id, data);
message.success('更新成功');
} else {
await createSchedule(data as CreateScheduleDto);
message.success('创建成功');
}
modalVisible.value = false;
loadSchedules();
} catch (error) {
message.error(editingSchedule.value ? '更新失败' : '创建失败');
} finally {
modalLoading.value = false;
}
};
const handleCancel = async (id: number) => {
try {
await cancelSchedule(id);
message.success('取消成功');
loadSchedules();
} catch (error) {
message.error('取消失败');
}
};
const switchToTimetable = () => {
router.push('/school/schedule/timetable');
};
const switchToCalendar = () => {
router.push('/school/schedule/calendar');
};
// 视图切换菜单点击处理
const handleViewMenuClick = (e: any) => {
const key = e.key;
if (key === 'list') {
// 当前页面就是列表视图
} else if (key === 'timetable') {
switchToTimetable();
} else if (key === 'calendar') {
switchToCalendar();
}
};
const formatDate = (date: string | undefined) => {
if (!date) return '-';
return dayjs(date).format('YYYY-MM-DD');
};
// ==================== 模板管理 ====================
const templateModalVisible = ref(false);
const templateLoading = ref(false);
const templates = ref<ScheduleTemplate[]>([]);
const templateColumns = [
{ title: '模板名称', dataIndex: 'name', key: 'name' },
{ title: '课程', dataIndex: 'courseName', key: 'courseName' },
{ title: '班级', dataIndex: 'className', key: 'className' },
{ title: '教师', dataIndex: 'teacherName', key: 'teacherName' },
{ title: '时间', dataIndex: 'scheduledTime', key: 'scheduledTime' },
{ title: '周几', key: 'weekDay' },
{ title: '默认', key: 'isDefault' },
{ title: '操作', key: 'actions', width: 180 },
];
const weekDayNames: Record<number, string> = {
0: '周日',
1: '周一',
2: '周二',
3: '周三',
4: '周四',
5: '周五',
6: '周六',
};
const showTemplateModal = async () => {
templateModalVisible.value = true;
await loadTemplates();
};
const loadTemplates = async () => {
templateLoading.value = true;
try {
templates.value = await getScheduleTemplates();
} catch (error) {
message.error('加载模板失败');
} finally {
templateLoading.value = false;
}
};
// 模板表单
const templateFormModalVisible = ref(false);
const templateFormLoading = ref(false);
const editingTemplate = ref<ScheduleTemplate | null>(null);
const templateForm = reactive({
name: '',
courseId: undefined as number | undefined,
classId: undefined as number | undefined,
teacherId: undefined as number | undefined,
scheduledTime: '09:00-09:30',
weekDay: undefined as number | undefined,
duration: 30,
isDefault: false,
});
const templateFormRules = {
name: [{ required: true, message: '请输入模板名称' }],
courseId: [{ required: true, message: '请选择课程' }],
};
const showCreateTemplateModal = () => {
editingTemplate.value = null;
Object.assign(templateForm, {
name: '',
courseId: undefined,
classId: undefined,
teacherId: undefined,
scheduledTime: '09:00-09:30',
weekDay: undefined,
duration: 30,
isDefault: false,
});
templateFormModalVisible.value = true;
};
const showEditTemplateModal = (record: ScheduleTemplate) => {
editingTemplate.value = record;
Object.assign(templateForm, {
name: record.name,
courseId: record.courseId,
classId: record.classId,
teacherId: record.teacherId,
scheduledTime: record.scheduledTime || '09:00-09:30',
weekDay: record.weekDay,
duration: record.duration || 30,
isDefault: record.isDefault,
});
templateFormModalVisible.value = true;
};
const handleTemplateSubmit = async () => {
templateFormLoading.value = true;
try {
if (editingTemplate.value) {
await updateScheduleTemplate(editingTemplate.value.id, templateForm as any);
message.success('更新成功');
} else {
await createScheduleTemplate(templateForm as any);
message.success('创建成功');
}
templateFormModalVisible.value = false;
await loadTemplates();
} catch (error) {
message.error(editingTemplate.value ? '更新失败' : '创建失败');
} finally {
templateFormLoading.value = false;
}
};
const handleDeleteTemplate = async (id: number) => {
try {
await deleteScheduleTemplate(id);
message.success('删除成功');
await loadTemplates();
} catch (error) {
message.error('删除失败');
}
};
// 从模板应用
const templateSelectModalVisible = ref(false);
const templateSelectLoading = ref(false);
const selectedTemplateId = ref<number | undefined>();
const templateApplyDate = ref<Dayjs>(dayjs());
const selectedTemplate = computed(() => {
if (!selectedTemplateId.value) return null;
return templates.value.find(t => t.id === selectedTemplateId.value);
});
const applyTemplate = (record: ScheduleTemplate) => {
selectedTemplateId.value = record.id;
templateApplyDate.value = dayjs();
templateSelectModalVisible.value = true;
};
const handleTemplateSelectSubmit = async () => {
if (!selectedTemplateId.value || !templateApplyDate.value) {
message.warning('请选择模板和日期');
return;
}
templateSelectLoading.value = true;
try {
await applyScheduleTemplate(selectedTemplateId.value, {
scheduledDate: templateApplyDate.value.format('YYYY-MM-DD'),
});
message.success('应用模板成功');
templateSelectModalVisible.value = false;
templateModalVisible.value = false;
await loadSchedules();
} catch (error) {
message.error('应用模板失败');
} finally {
templateSelectLoading.value = false;
}
};
// ==================== 批量排课 ====================
const batchModalVisible = ref(false);
const batchLoading = ref(false);
const batchItems = ref<any[]>([]);
let batchKey = 0;
const batchColumns = [
{ title: '班级', key: 'classId', width: 120 },
{ title: '课程', key: 'courseId', width: 150 },
{ title: '教师', key: 'teacherId', width: 100 },
{ title: '日期', key: 'scheduledDate', width: 140 },
{ title: '时间', key: 'scheduledTime', width: 120 },
{ title: '', key: 'actions', width: 50 },
];
const showBatchModal = () => {
batchItems.value = [];
batchKey = 0;
addBatchItem();
batchModalVisible.value = true;
};
const addBatchItem = () => {
batchItems.value.push({
key: ++batchKey,
classId: undefined,
courseId: undefined,
teacherId: undefined,
scheduledDate: dayjs(),
scheduledTime: '09:00-09:30',
});
};
const removeBatchItem = (index: number) => {
batchItems.value.splice(index, 1);
};
const handleBatchSubmit = async () => {
// 验证数据
const validItems = batchItems.value.filter(item =>
item.classId && item.courseId && item.scheduledDate && item.scheduledTime
);
if (validItems.length === 0) {
message.warning('请至少填写一条完整的排课信息');
return;
}
batchLoading.value = true;
try {
const schedules = validItems.map(item => ({
classId: item.classId,
courseId: item.courseId,
teacherId: item.teacherId,
scheduledDate: item.scheduledDate.format('YYYY-MM-DD'),
scheduledTime: item.scheduledTime,
}));
const result = await batchCreateSchedules(schedules);
message.success(`成功创建 ${result.success} 条排课${result.failed > 0 ? `${result.failed} 条失败` : ''}`);
batchModalVisible.value = false;
await loadSchedules();
} catch (error) {
message.error('批量创建失败');
} finally {
batchLoading.value = false;
}
};
// 创建菜单点击处理
const handleCreateMenuClick = (e: any) => {
const key = e.key;
if (key === 'single') {
showCreateModal();
} else if (key === 'batch') {
showBatchModal();
} else if (key === 'template') {
selectedTemplateId.value = undefined;
templateApplyDate.value = dayjs();
templateSelectModalVisible.value = true;
loadTemplates();
}
};
onMounted(() => {
loadBaseData();
loadSchedules();
});
</script>
<style scoped>
/* 仅保留无法用 UnoCSS 实现的部分 */
</style>