Browse Source

排课页面完成

master
王泽彦 11 months ago
parent
commit
93e7e7769a
  1. 4
      admin/src/app/lang/zh-cn/course.course.json
  2. 20
      admin/src/app/views/course/components/course-edit.vue
  3. 11
      admin/src/app/views/course/course.vue
  4. 259
      admin/src/app/views/timetables/timetables.vue
  5. 1
      niucloud/app/adminapi/controller/course/Course.php
  6. 116
      niucloud/app/model/course/Course.php

4
admin/src/app/lang/zh-cn/course.course.json

@ -9,8 +9,8 @@
"durationPlaceholder": "请输入课程时长", "durationPlaceholder": "请输入课程时长",
"sessionCount": "课时数量", "sessionCount": "课时数量",
"sessionCountPlaceholder": "请输入课时数量", "sessionCountPlaceholder": "请输入课时数量",
"singleSessionCount": "单次逍客数量", "singleSessionCount": "单次消课数量",
"singleSessionCountPlaceholder": "请输入单次逍客数量", "singleSessionCountPlaceholder": "请输入单次消课数量",
"price": "课程价格", "price": "课程价格",
"pricePlaceholder": "请输入课程价格", "pricePlaceholder": "请输入课程价格",
"internalReminder": "内部提醒课时", "internalReminder": "内部提醒课时",

20
admin/src/app/views/course/components/course-edit.vue

@ -24,14 +24,19 @@
</el-form-item> </el-form-item>
<el-form-item :label="t('courseType')" prop="course_type"> <el-form-item :label="t('courseType')" prop="course_type">
<el-input <el-select
class="input-width"
v-model="formData.course_type" v-model="formData.course_type"
clearable
:placeholder="t('courseTypePlaceholder')" :placeholder="t('courseTypePlaceholder')"
class="input-width" >
<el-option
v-for="(item, index) in courseTypeList"
:key="index"
:label="item.name"
:value="item.value"
/> />
</el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('duration')" prop="duration"> <el-form-item :label="t('duration')" prop="duration">
<el-input <el-input
v-model="formData.duration" v-model="formData.duration"
@ -193,6 +198,13 @@ const formRules = computed(() => {
const emit = defineEmits(['complete']) const emit = defineEmits(['complete'])
const courseTypeList = ref([])
const getcourseTypeList = async () => {
courseTypeList.value = await (
await useDictionary('course_type')
).data.dictionary
}
getcourseTypeList()
/** /**
* 确认 * 确认
* @param formEl * @param formEl

11
admin/src/app/views/course/course.vue

@ -24,10 +24,17 @@
/> />
</el-form-item> </el-form-item>
<el-form-item :label="t('courseType')" prop="course_type"> <el-form-item :label="t('courseType')" prop="course_type">
<el-input <el-select
v-model="courseTable.searchParam.course_type" v-model="courseTable.searchParam.course_type"
:placeholder="t('courseTypePlaceholder')" :placeholder="t('courseTypePlaceholder')"
>
<el-option
v-for="item in courseTypeList"
:key="item.value"
:label="item.label"
:value="item.value"
/> />
</el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('duration')" prop="duration"> <el-form-item :label="t('duration')" prop="duration">
<el-input <el-input
@ -220,7 +227,7 @@ let courseTable = reactive({
remarks: '', remarks: '',
}, },
}) })
const courseTypeList = useDictionary('course_type')
const searchFormRef = ref<FormInstance>() const searchFormRef = ref<FormInstance>()
// //

259
admin/src/app/views/timetables/timetables.vue

@ -1,125 +1,198 @@
<template> <template>
<el-card class="box-card !border-none" shadow="never">
<el-button @click="addDict">添加</el-button>
<div class="schedule-container"> <div class="schedule-container">
<!-- 周视图日历 --> <!-- 周一到周日的布局 -->
<FullCalendar <div v-for="(day, index) in days" :key="index" class="day-column">
:options="calendarOptions" <div class="day-header">{{ day.date }}</div>
ref="fullCalendar" <el-table
class="calendar" :data="day.timeSlots"
/> border
:span-method="(data) => objectSpanMethod(day.timeSlots, data)"
<!-- 课程详情弹窗 --> style="width: 100%"
<el-dialog @cell-click="handleCellClick"
v-model="dialogVisible"
title="课程人员安排"
width="30%"
:before-close="handleClose"
> >
<div v-if="selectedEvent"> <!-- 时间列 -->
<h3>{{ selectedEvent.title }}</h3> <el-table-column
<p>时间{{ selectedEvent.time }}</p> prop="timeRange"
<h4>参与人员</h4> label="时间"
<ul> width="80"
<li v-for="person in selectedEvent.people" :key="person.id"> align="center"
{{ person.name }} - {{ person.role }} >
</li> <template #default="{ row }">
</ul> <div :style="{ backgroundColor: row.color }">
{{ row.timeRange }}
</div>
</template>
</el-table-column>
<!-- 教室列 -->
<el-table-column
v-for="(classroom, idx) in day.classrooms"
:key="idx"
:label="`教室${idx + 1}`"
:prop="`classroom${idx + 1}`"
align="center"
>
<template #default="{ row }">
<div v-if="row.course && row.course.classroom === classroom">
<div class="teacher-name">{{ row.course.teacher }}</div>
<div class="student-list">
<el-tag
v-for="student in row.course.students"
:key="student"
size="small"
effect="plain"
>
{{ student }}
</el-tag>
</div> </div>
<div class="classroom-name">
剩余空位{{ row.course.hasnumber }}
</div>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 详情弹窗 -->
<el-dialog v-model="dialogVisible" title="课程详情">
<p><strong>教师:</strong> {{ selectedCourse?.teacher }}</p>
<p><strong>学员:</strong> {{ selectedCourse?.students.join(', ') }}</p>
</el-dialog> </el-dialog>
</div> </div>
</el-card>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import FullCalendar from '@fullcalendar/vue3'
import dayGridPlugin from '@fullcalendar/daygrid' const days = [
import timeGridPlugin from '@fullcalendar/timegrid' //
import interactionPlugin from '@fullcalendar/interaction' // {
import zhLocale from '@fullcalendar/core/locales/zh-cn' date: '周一',
timeSlots: [
//
const courses = ref([
{ {
id: 1, timeRange: '9:00-10:00',
title: '数学课', color: '#FFA07A',
start: '2025-05-18 08:00:00', course: {
end: '2025-05-18 09:00:00', teacher: '',
people: [ students: [],
{ id: 1, name: '张老师', role: '主讲' }, classroom: '教室1',
{ id: 2, name: '李同学', role: '学生' }, hasnumber: 5,
},
},
// ...
], ],
classrooms: ['教室1'], //
}, },
{ {
id: 2, date: '周二',
title: '英语课', timeSlots: [
start: '2025-05-18 10:00:00', {
end: '2025-05-18 11:00:00', timeRange: '9:00-10:00',
people: [{ id: 3, name: '王老师', role: '主讲' }], color: '#FFA07A',
course: {
teacher: '张老师',
students: ['小明', '小红'],
classroom: '教室1',
hasnumber: 5,
}, },
])
//
const dialogVisible = ref(false)
const selectedEvent = ref(null)
// FullCalendar
const calendarOptions = ref({
locale: zhLocale,
plugins: [
dayGridPlugin,
timeGridPlugin,
interactionPlugin, //
],
initialView: 'timeGridWeek', //
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'timeGridWeek,timeGridDay', //
}, },
events: courses.value, {
eventClick: (info) => { timeRange: '11:00-12:00',
selectedEvent.value = { color: '#712e13',
title: info.event.title, course: {
time: `${info.event.start.toLocaleTimeString()} - ${info.event.end.toLocaleTimeString()}`, teacher: '张老师',
people: info.event.extendedProps.people, students: ['小明', '小红'],
} classroom: '教室2',
dialogVisible.value = true hasnumber: 5,
}, },
eventContent: (arg) => {
//
return { html: `<div class="fc-event-title">${arg.event.title}</div>` }
}, },
allDaySlot: false, // // ...
slotDuration: '01:00', // 1 ],
slotLabelFormat: { classrooms: ['教室1', '教室2'], //
hour: 'numeric',
minute: '2-digit',
omitZeroMinute: false,
meridiem: 'short',
}, },
}) ]
const dialogVisible = ref(false)
const selectedCourse = ref(null)
//
const objectSpanMethod = (timeSlots, { row, column, rowIndex }) => {
if (column.property === 'timeRange') {
//
const current = timeSlots[rowIndex]
//
if (!current || !current.timeRange) return { rowspan: 0, colspan: 0 }
let spanCount = 1
//
while (timeSlots[rowIndex + spanCount]?.timeRange === current.timeRange) {
spanCount++
}
//
if (
spanCount > 1 &&
rowIndex > 0 &&
timeSlots[rowIndex - 1]?.timeRange === current.timeRange
) {
return { rowspan: 0, colspan: 0 }
}
return { rowspan: spanCount, colspan: 1 }
}
return { rowspan: 1, colspan: 1 }
}
// //
const handleClose = () => { const handleCellClick = (row, column, cell, event) => {
dialogVisible.value = false console.log(row, column, cell, event)
selectedEvent.value = null if (column.property.startsWith('classroom')) {
selectedCourse.value = row.course
dialogVisible.value = true
}
} }
</script> </script>
<style> <style scoped>
.schedule-container { .schedule-container {
padding: 20px; display: flex;
gap: 10px;
}
.day-column {
flex: 1;
min-width: 200px;
}
.day-header {
text-align: center;
font-weight: bold;
padding: 8px;
background-color: #f0f0f0;
}
.teacher-name {
font-weight: bold;
margin-bottom: 5px;
} }
.calendar { .student-list {
margin-bottom: 20px; margin-top: 5px;
} }
.fc-event-title { .el-table__cell {
white-space: normal !important; display: flex;
font-size: 14px; align-items: center;
} }
.fc-timegrid-event { .classroom-name {
cursor: pointer; margin-bottom: 5px;
} }
</style> </style>

1
niucloud/app/adminapi/controller/course/Course.php

@ -105,5 +105,4 @@ class Course extends BaseAdminController
return success('DELETE_SUCCESS'); return success('DELETE_SUCCESS');
} }
} }

116
niucloud/app/model/course/Course.php

@ -9,7 +9,7 @@
// | Author: Niucloud Team // | Author: Niucloud Team
// +---------------------------------------------------------------------- // +----------------------------------------------------------------------
namespace app\model\campus; namespace app\model\course;
use core\base\BaseModel; use core\base\BaseModel;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
@ -17,11 +17,11 @@ use think\model\relation\HasMany;
use think\model\relation\HasOne; use think\model\relation\HasOne;
/** /**
* 校区模型 * 课程模型
* Class Campus * Class Course
* @package app\model\campus * @package app\model\course
*/ */
class Campus extends BaseModel class Course extends BaseModel
{ {
use SoftDelete; use SoftDelete;
@ -36,13 +36,13 @@ class Campus extends BaseModel
* 模型名称 * 模型名称
* @var string * @var string
*/ */
protected $name = 'campus'; protected $name = 'course';
/** /**
* 定义软删除标记字段. * 定义软删除标记字段.
* @var string * @var string
*/ */
protected $deleteTime = 'delete_time'; protected $deleteTime = 'deleted_at';
/** /**
* 定义软删除字段的默认值. * 定义软删除字段的默认值.
@ -51,38 +51,122 @@ class Campus extends BaseModel
protected $defaultSoftDelete = 0; protected $defaultSoftDelete = 0;
/** /**
* 搜索器:校区校区名称 * 搜索器:课程课程编号
* @param $value * @param $value
* @param $data * @param $data
*/ */
public function searchCampusNameAttr($query, $value, $data) public function searchIdAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("campus_name", "like", "%".$value."%"); $query->where("id", $value);
} }
} }
/** /**
* 搜索器:校区校区地址 * 搜索器:课程课程名称
* @param $value * @param $value
* @param $data * @param $data
*/ */
public function searchCampusAddressAttr($query, $value, $data) public function searchCourseNameAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("campus_address", $value); $query->where("course_name", $value);
} }
} }
/** /**
* 搜索器:校区校区状态 * 搜索器:课程课程类型
* @param $value * @param $value
* @param $data * @param $data
*/ */
public function searchCampusStatusAttr($query, $value, $data) public function searchCourseTypeAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("campus_status", $value); $query->where("course_type", $value);
}
}
/**
* 搜索器:课程课程时长
* @param $value
* @param $data
*/
public function searchDurationAttr($query, $value, $data)
{
if ($value) {
$query->where("duration", $value);
}
}
/**
* 搜索器:课程课时数量
* @param $value
* @param $data
*/
public function searchSessionCountAttr($query, $value, $data)
{
if ($value) {
$query->where("session_count", $value);
}
}
/**
* 搜索器:课程单次逍客数量
* @param $value
* @param $data
*/
public function searchSingleSessionCountAttr($query, $value, $data)
{
if ($value) {
$query->where("single_session_count", $value);
}
}
/**
* 搜索器:课程课程价格
* @param $value
* @param $data
*/
public function searchPriceAttr($query, $value, $data)
{
if ($value) {
$query->where("price", $value);
}
}
/**
* 搜索器:课程内部提醒课时
* @param $value
* @param $data
*/
public function searchInternalReminderAttr($query, $value, $data)
{
if ($value) {
$query->where("internal_reminder", $value);
}
}
/**
* 搜索器:课程客户提醒课时
* @param $value
* @param $data
*/
public function searchCustomerReminderAttr($query, $value, $data)
{
if ($value) {
$query->where("customer_reminder", $value);
}
}
/**
* 搜索器:课程课程备注
* @param $value
* @param $data
*/
public function searchRemarksAttr($query, $value, $data)
{
if ($value) {
$query->where("remarks", $value);
} }
} }

Loading…
Cancel
Save