Browse Source

排课页面完成

master
王泽彦 11 months ago
parent
commit
93e7e7769a
  1. 4
      admin/src/app/lang/zh-cn/course.course.json
  2. 22
      admin/src/app/views/course/components/course-edit.vue
  3. 13
      admin/src/app/views/course/course.vue
  4. 269
      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": "内部提醒课时",

22
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

13
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>()
// //

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

@ -1,125 +1,198 @@
<template> <template>
<div class="schedule-container"> <el-card class="box-card !border-none" shadow="never">
<!-- 周视图日历 --> <el-button @click="addDict">添加</el-button>
<FullCalendar <div class="schedule-container">
:options="calendarOptions" <!-- 周一到周日的布局 -->
ref="fullCalendar" <div v-for="(day, index) in days" :key="index" class="day-column">
class="calendar" <div class="day-header">{{ day.date }}</div>
/> <el-table
:data="day.timeSlots"
<!-- 课程详情弹窗 --> border
<el-dialog :span-method="(data) => objectSpanMethod(day.timeSlots, data)"
v-model="dialogVisible" style="width: 100%"
title="课程人员安排" @cell-click="handleCellClick"
width="30%" >
:before-close="handleClose" <!-- 时间列 -->
> <el-table-column
<div v-if="selectedEvent"> prop="timeRange"
<h3>{{ selectedEvent.title }}</h3> label="时间"
<p>时间{{ selectedEvent.time }}</p> width="80"
<h4>参与人员</h4> align="center"
<ul> >
<li v-for="person in selectedEvent.people" :key="person.id"> <template #default="{ row }">
{{ person.name }} - {{ person.role }} <div :style="{ backgroundColor: row.color }">
</li> {{ row.timeRange }}
</ul> </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 class="classroom-name">
剩余空位{{ row.course.hasnumber }}
</div>
</div>
</template>
</el-table-column>
</el-table>
</div> </div>
</el-dialog>
</div> <!-- 详情弹窗 -->
<el-dialog v-model="dialogVisible" title="课程详情">
<p><strong>教师:</strong> {{ selectedCourse?.teacher }}</p>
<p><strong>学员:</strong> {{ selectedCourse?.students.join(', ') }}</p>
</el-dialog>
</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'
//
const courses = ref([
{ {
id: 1, date: '周一',
title: '数学课', timeSlots: [
start: '2025-05-18 08:00:00', {
end: '2025-05-18 09:00:00', timeRange: '9:00-10:00',
people: [ color: '#FFA07A',
{ id: 1, name: '张老师', role: '主讲' }, course: {
{ id: 2, name: '李同学', role: '学生' }, teacher: '',
students: [],
classroom: '教室1',
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,
},
},
{
timeRange: '11:00-12:00',
color: '#712e13',
course: {
teacher: '张老师',
students: ['小明', '小红'],
classroom: '教室2',
hasnumber: 5,
},
},
// ...
],
classrooms: ['教室1', '教室2'], //
}, },
]) ]
//
const dialogVisible = ref(false) const dialogVisible = ref(false)
const selectedEvent = ref(null) const selectedCourse = ref(null)
// FullCalendar //
const calendarOptions = ref({ const objectSpanMethod = (timeSlots, { row, column, rowIndex }) => {
locale: zhLocale, if (column.property === 'timeRange') {
plugins: [ //
dayGridPlugin, const current = timeSlots[rowIndex]
timeGridPlugin,
interactionPlugin, // //
], if (!current || !current.timeRange) return { rowspan: 0, colspan: 0 }
initialView: 'timeGridWeek', //
headerToolbar: { let spanCount = 1
left: 'prev,next today',
center: 'title', //
right: 'timeGridWeek,timeGridDay', // while (timeSlots[rowIndex + spanCount]?.timeRange === current.timeRange) {
}, spanCount++
events: courses.value,
eventClick: (info) => {
selectedEvent.value = {
title: info.event.title,
time: `${info.event.start.toLocaleTimeString()} - ${info.event.end.toLocaleTimeString()}`,
people: info.event.extendedProps.people,
} }
dialogVisible.value = true
},
eventContent: (arg) => {
//
return { html: `<div class="fc-event-title">${arg.event.title}</div>` }
},
allDaySlot: false, //
slotDuration: '01:00', // 1
slotLabelFormat: {
hour: 'numeric',
minute: '2-digit',
omitZeroMinute: false,
meridiem: 'short',
},
})
// //
const handleClose = () => { if (
dialogVisible.value = false spanCount > 1 &&
selectedEvent.value = null rowIndex > 0 &&
timeSlots[rowIndex - 1]?.timeRange === current.timeRange
) {
return { rowspan: 0, colspan: 0 }
}
return { rowspan: spanCount, colspan: 1 }
}
return { rowspan: 1, colspan: 1 }
}
//
const handleCellClick = (row, column, cell, event) => {
console.log(row, column, cell, event)
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