Browse Source

修改后台排课

master
王泽彦 10 months ago
parent
commit
2ea1f352f0
  1. 8
      admin/src/app/api/course_schedule.ts
  2. 4
      admin/src/app/api/person_course_schedule.ts
  3. 532
      admin/src/app/views/timetables/timetables.vue
  4. 4
      niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php
  5. 5
      niucloud/app/adminapi/controller/customer_resources/CustomerResources.php
  6. 1
      niucloud/app/adminapi/controller/order_table/OrderTable.php
  7. 21
      niucloud/app/adminapi/controller/person_course_schedule/PersonCourseSchedule.php
  8. 4
      niucloud/app/adminapi/route/customer_resources.php
  9. 3
      niucloud/app/adminapi/route/person_course_schedule.php
  10. 7
      niucloud/app/common.php
  11. 3
      niucloud/app/model/course_schedule/CourseSchedule.php
  12. 5
      niucloud/app/model/customer_resources/CustomerResources.php
  13. 59
      niucloud/app/model/person_course_schedule/PersonCourseSchedule.php
  14. 86
      niucloud/app/service/admin/course_schedule/CourseScheduleService.php
  15. 46
      niucloud/app/service/admin/customer_resources/CustomerResourcesService.php
  16. 108
      niucloud/app/service/admin/person_course_schedule/PersonCourseScheduleService.php
  17. 18
      niucloud/app/validate/person_course_schedule/PersonCourseSchedule.php

8
admin/src/app/api/course_schedule.ts

@ -65,4 +65,12 @@ export function getTimetables(params: Record<string, any>) {
return request.get(`course_schedule/timetables`, { params }) return request.get(`course_schedule/timetables`, { params })
} }
/**通过课程id获取人员列表 */
export function getCourseStudents(id: number) {
return request.get(`customer_resources/getResourceByCourse/${id}/students`)
}
/**通过名字或手机号获取人员列表 */
export function getResourceByNameOrPhone(params: Record<string, any>) {
return request.get(`customer_resources/personnel_all_byname`, { params })
}
// USER_CODE_END -- course_schedule // USER_CODE_END -- course_schedule

4
admin/src/app/api/person_course_schedule.ts

@ -58,5 +58,9 @@ export function deletePersonCourseSchedule(id: number) {
showSuccessMessage: true, showSuccessMessage: true,
}) })
} }
/**获取试课人员列表 */
export function getTryCoursePerson(id: number) {
return request.get(`person_course_schedule/get_try_course_person/${id}`)
}
// USER_CODE_END -- person_course_schedule // USER_CODE_END -- person_course_schedule

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

@ -1,12 +1,14 @@
<template> <template>
<el-card class="box-card !border-none" shadow="never"> <el-card class="box-card !border-none" shadow="never">
<div class="mb-4 flex items-center justify-between"> <div class="header-control-panel mb-4">
<div class="flex items-center" style="width: 50%;"> <div class="flex items-center flex-wrap">
<el-select <el-select
v-model="selectedCampus" v-model="selectedCampus"
placeholder="请选择校区" placeholder="请选择校区"
clearable clearable
class="mr-2" size="default"
class="mr-2 mb-2"
style="width: 160px;"
@change="handleCampusChange" @change="handleCampusChange"
> >
<el-option <el-option
@ -16,21 +18,24 @@
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
<el-button @click="prevWeek" icon="el-icon-arrow-left">上一周</el-button> <el-button size="default" @click="prevWeek" icon="el-icon-arrow-left" class="mb-2">上一周</el-button>
<div class="ml-2 mr-2"> <div class="mx-2 mb-2">
<el-date-picker <el-date-picker
v-model="weekDate" v-model="weekDate"
type="week" type="week"
format="YYYY 第 ww 周" format="YYYY 第 ww 周"
placeholder="选择周" placeholder="选择周"
size="default"
style="width: 180px;" style="width: 180px;"
@change="handleWeekChange" @change="handleWeekChange"
/> />
</div> </div>
<el-button @click="nextWeek" icon="el-icon-arrow-right">下一周</el-button> <el-button size="default" @click="nextWeek" icon="el-icon-arrow-right" class="mb-2">下一周</el-button>
<el-button type="primary" class="ml-2" @click="fetchData">查询</el-button> <el-button type="primary" size="default" class="ml-2 mb-2" @click="fetchData">查询</el-button>
</div>
<div>
<el-button size="default" type="primary" plain @click="addSchedule">添加课程</el-button>
</div> </div>
<el-button @click="addSchedule">添加课程</el-button>
</div> </div>
<div class="schedule-container"> <div class="schedule-container">
@ -41,7 +46,10 @@
:data="day.timeSlots" :data="day.timeSlots"
border border
:span-method="(data) => objectSpanMethod(day.timeSlots, data)" :span-method="(data) => objectSpanMethod(day.timeSlots, data)"
style="width: 100%" style="width: 100%; height: 100%;"
max-height="100%"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
:cell-style="{ padding: '4px' }"
@cell-click="handleCellClick" @cell-click="handleCellClick"
> >
<!-- 时间列 --> <!-- 时间列 -->
@ -60,23 +68,31 @@
:label="`${classroom.venue_name}`" :label="`${classroom.venue_name}`"
:prop="`classroom${classroom.id}`" :prop="`classroom${classroom.id}`"
align="center" align="center"
width="280"
> >
<template #default="{ row }"> <template #default="{ row }">
<div v-if="row.course && row.course.classroom.id == classroom.id"> <div v-if="row.course && row.course.classroom[0].id == classroom.id" class="course-cell" :style="{ backgroundColor: row.backgroundColor || '#f0f9eb', color: row.color ? '#fff' : '#000' }">
<div class="teacher-name"> <div class="teacher-name">
{{ getTeacherName(row.course.teacher) }} {{ row.course.teacher[0].name }}
</div> </div>
<div class="student-list"> <div class="student-list" :style="{ backgroundColor: row.backgroundColor || '#f0f9eb', color: row.color ? '#fff' : '#000' }">
<el-tag <div
v-for="student in row.course.students" v-for="student in row.course.students"
:key="student" :key="student.id"
size="small" class="custom-student-tag"
effect="plain"
> >
{{ getStudentName(student) }} <div class="tag-content">
</el-tag> <span class="tag-label">家长</span>
<span class="tag-value">{{ student.resources?.name || '未知' }}</span>
<template v-if="student.student && student.student?.name">
<span class="tag-divider">|</span>
<span class="tag-label">学员</span>
<span class="tag-value">{{ student.student?.name }}</span>
</template>
</div>
</div>
</div> </div>
<div class="classroom-name"> <div class="classroom-name" :style="{ backgroundColor: row.backgroundColor || '#f0f9eb', color: row.color ? '#fff' : '#000' }">
剩余空位{{ row.course.hasnumber }} 剩余空位{{ row.course.hasnumber }}
</div> </div>
</div> </div>
@ -86,16 +102,61 @@
</div> </div>
<!-- 详情弹窗 --> <!-- 详情弹窗 -->
<el-dialog v-model="dialogVisible" title="课程详情"> <el-dialog v-model="dialogVisible" title="课程安排" width="600px" @open="handleDialogOpen">
<p> <!-- 人员类型选择 -->
<strong>教师:</strong> {{ getTeacherName(selectedCourse?.teacher) }} <div class="my-3">
</p> <el-radio-group v-model="personType" @change="handlePersonTypeChange">
<p> <el-radio :label="'course'">课程人员</el-radio>
<strong>学员:</strong> <el-radio :label="'trial'">试课人员</el-radio>
{{ </el-radio-group>
selectedCourse?.students.map((id) => getStudentName(id)).join(', ') </div>
}}
</p> <!-- 试课人员搜索框 -->
<div v-if="personType === 'trial'" class="search-box mb-3">
<el-input
v-model="searchKeyword"
placeholder="请输入姓名或手机号"
class="mr-2"
style="width: 220px;"
>
<template #append>
<el-button @click="searchPerson">
<el-icon><Search /></el-icon>
</el-button>
</template>
</el-input>
</div>
<!-- 人员列表多选区域 -->
<div class="student-checkbox-container">
<el-checkbox-group v-model="selectedStudentIds">
<el-checkbox
v-for="student in students"
:key="student.id"
:label="student.id"
class="student-checkbox-item"
:checked="student.checked === true"
@change="(val) => handleStudentCheck(student, val)"
>
<div class="student-info">
<span class="student-name">{{ student.name }}</span>
<span v-if="student.student_name" class="student-school">
({{ student.student_name }})
</span>
</div>
</el-checkbox>
</el-checkbox-group>
<div v-if="students.length === 0" class="text-center py-3 text-gray-500">
暂无人员数据
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveStudents">保存</el-button>
</span>
</template>
</el-dialog> </el-dialog>
<!-- 添加课程弹窗组件 --> <!-- 添加课程弹窗组件 -->
@ -110,9 +171,12 @@
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { getTimetables } from '@/app/api/course_schedule' import { getTimetables, getCourseStudents, getResourceByNameOrPhone } from '@/app/api/course_schedule'
import { addPersonCourseSchedule,getTryCoursePerson } from '@/app/api/person_course_schedule'
import { getWithCampusList } from '@/app/api/venue' import { getWithCampusList } from '@/app/api/venue'
import ScheduleAdd from './components/schedule-add.vue' import ScheduleAdd from './components/schedule-add.vue'
import { ElMessage } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
// //
const campusList = ref([]) const campusList = ref([])
@ -148,6 +212,15 @@ const days = ref([])
const dialogVisible = ref(false) const dialogVisible = ref(false)
const selectedCourse = ref(null) const selectedCourse = ref(null)
//
const personType = ref('course') //
const searchKeyword = ref('') //
const students = ref([]) //
const selectedStudentIds = ref([]) // ID
const selectedStudents = ref([]) //
const courseStudents = ref([]) //
const trialStudents = ref([]) //
// //
const prevWeek = () => { const prevWeek = () => {
const date = new Date(weekDate.value) const date = new Date(weekDate.value)
@ -185,8 +258,10 @@ const fetchCampusList = async () => {
selectedCampus.value = campusList.value[0].id selectedCampus.value = campusList.value[0].id
} }
} }
return Promise.resolve()
} catch (error) { } catch (error) {
console.error('获取校区列表失败:', error) console.error('获取校区列表失败:', error)
return Promise.reject(error)
} }
} }
@ -212,18 +287,6 @@ const fetchData = async () => {
} }
} }
//
const getTeacherName = (teacherId) => {
// teacherId
return teacherId ? `教师${teacherId}` : ''
}
//
const getStudentName = (studentId) => {
// studentId
return studentId ? `学生${studentId}` : ''
}
// //
const addSchedule = () => { const addSchedule = () => {
addDialogVisible.value = true addDialogVisible.value = true
@ -263,44 +326,286 @@ const objectSpanMethod = (timeSlots, { row, column, rowIndex }) => {
// //
const handleCellClick = (row, column, cell, event) => { const handleCellClick = (row, column, cell, event) => {
if (column.property.startsWith('classroom') && row.course) { if (column.property.startsWith('classroom') && row.course) {
selectedCourse.value = row.course console.log(row.course.schedule)
selectedCourse.value = row.course.schedule.id
dialogVisible.value = true dialogVisible.value = true
} else {
ElMessage.warning('当天该时间段没有课程')
}
}
//
const handleDialogOpen = async () => {
if (!selectedCourse.value) {
ElMessage.warning('未选择有效课程')
return
}
//
personType.value = 'course'
searchKeyword.value = ''
courseStudents.value = []
trialStudents.value = []
students.value = []
selectedStudentIds.value = []
selectedStudents.value = []
//
await fetchCourseStudents()
}
//
const fetchCourseStudents = async () => {
try {
if (!selectedCourse.value) return
const response = await getCourseStudents(selectedCourse.value)
if (response.data) {
courseStudents.value = response.data || []
students.value = courseStudents.value
// - checked
const checkedStudents = students.value.filter(student => student.checked === true)
selectedStudentIds.value = checkedStudents.map(student => student.id)
//
selectedStudents.value = checkedStudents.map(student => ({
id: student.id,
student_id: student.student_id || null,
name: student.name,
student_name: student.student_name || null
}))
}
} catch (error) {
console.error('获取课程人员列表失败:', error)
ElMessage.error('获取课程人员列表失败')
courseStudents.value = []
students.value = []
selectedStudentIds.value = []
selectedStudents.value = []
}
}
//
const handleStudentCheck = (student, checked) => {
if (checked) {
//
if (!selectedStudents.value.some(item => item.id === student.id)) {
selectedStudents.value.push({
id: student.id,
student_id: student.student_id || null,
name: student.name,
student_name: student.student_name || null
})
}
} else {
//
const index = selectedStudents.value.findIndex(item => item.id === student.id)
if (index !== -1) {
selectedStudents.value.splice(index, 1)
}
}
}
//
const handlePersonTypeChange = async () => {
//
searchKeyword.value = ''
if (personType.value === 'course') {
//
students.value = courseStudents.value
} else {
// getTryCoursePerson
try {
if (selectedCourse.value) {
const response = await getTryCoursePerson(selectedCourse.value)
if (response.data) {
trialStudents.value = response.data || []
students.value = trialStudents.value
//
const checkedStudents = students.value.filter(student => student.checked === true)
if (checkedStudents.length > 0) {
checkedStudents.forEach(student => {
if (!selectedStudentIds.value.includes(student.id)) {
selectedStudentIds.value.push(student.id)
//
if (!selectedStudents.value.some(item => item.id === student.id)) {
selectedStudents.value.push({
id: student.id,
student_id: student.student_id || null,
name: student.name,
student_name: student.student_name || null
})
}
}
})
}
}
} else {
//
trialStudents.value = []
students.value = trialStudents.value
}
} catch (error) {
console.error('获取试课人员失败:', error)
ElMessage.error('获取试课人员失败')
trialStudents.value = []
students.value = trialStudents.value
}
}
}
//
const searchPerson = async () => {
if (!searchKeyword.value.trim()) {
ElMessage.warning('请输入搜索关键词')
return
}
try {
const response = await getResourceByNameOrPhone({
name: searchKeyword.value.trim()
})
if (response.data) {
// ID
const currentSelectedIds = [...selectedStudentIds.value]
trialStudents.value = response.data || []
students.value = trialStudents.value
// checkedtrue
//
const checkedStudents = students.value.filter(student => student.checked === true)
// 使
selectedStudentIds.value = currentSelectedIds
// checked
if (checkedStudents.length > 0) {
checkedStudents.forEach(student => {
if (!selectedStudentIds.value.includes(student.id)) {
selectedStudentIds.value.push(student.id)
//
if (!selectedStudents.value.some(item => item.id === student.id)) {
selectedStudents.value.push({
id: student.id,
student_id: student.student_id || null,
name: student.name,
student_name: student.student_name || null
})
}
}
})
}
}
} catch (error) {
console.error('搜索人员失败:', error)
ElMessage.error('搜索人员失败')
}
}
//
const saveStudents = async () => {
if (selectedStudentIds.value.length === 0) {
ElMessage.warning('请至少选择一名人员')
return
}
try {
//
await addPersonCourseSchedule({
schedule_id: selectedCourse.value,
resources_id: selectedStudentIds.value,
// student_id
student_ids: selectedStudents.value
.filter(student => student.student_id)
.map(student => student.student_id)
})
ElMessage.success('保存成功')
dialogVisible.value = false
//
fetchData()
} catch (error) {
console.error('保存人员失败:', error)
ElMessage.error('保存人员失败')
} }
} }
// //
onMounted(() => { onMounted(() => {
fetchCampusList() fetchCampusList().then(() => {
fetchData() fetchData()
})
}) })
</script> </script>
<style scoped> <style scoped>
.header-control-panel {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
}
.schedule-container { .schedule-container {
display: flex; display: flex;
gap: 10px; gap: 5px;
overflow-x: auto; overflow-x: auto;
background-color: #fff;
padding: 0;
border-radius: 4px;
} }
.day-column { .day-column {
flex: 1; flex: 1;
min-width: 200px; min-width: 180px;
display: flex;
flex-direction: column;
} }
.day-header { .day-header {
text-align: center; text-align: center;
font-weight: bold; font-weight: bold;
padding: 8px; padding: 8px 0;
background-color: #f0f0f0; background-color: #f5f7fa;
border-top: 1px solid #ebeef5;
border-left: 1px solid #ebeef5;
border-right: 1px solid #ebeef5;
color: #606266;
font-size: 14px;
} }
.teacher-name { .teacher-name {
font-weight: bold; font-weight: bold;
margin-bottom: 5px; margin-bottom: 3px;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.student-list { .student-list {
margin-top: 5px; margin: 3px 0;
max-height: 60px;
overflow-y: auto;
display: flex;
flex-wrap: wrap;
}
.el-table {
height: calc(100vh - 220px);
overflow-y: auto;
font-size: 13px;
border-collapse: collapse;
} }
.el-table__cell { .el-table__cell {
@ -309,6 +614,129 @@ onMounted(() => {
} }
.classroom-name { .classroom-name {
margin-bottom: 5px; margin-top: 3px;
font-size: 12px;
color: #606266;
}
.course-cell {
padding: 4px;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 3px;
}
.custom-student-tag {
background-color: #ecf5ff;
color: #409eff;
border: 1px solid #d9ecff;
border-radius: 4px;
padding: 0 8px;
margin: 2px;
font-size: 12px;
display: inline-block;
max-width: 260px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
box-sizing: border-box;
height: 24px;
line-height: 22px;
transition: all .2s;
}
.custom-student-tag:hover {
background-color: #d9ecff;
color: #3a8ee6;
cursor: default;
}
.tag-content {
display: flex;
flex-wrap: nowrap;
align-items: center;
max-width: 100%;
overflow: hidden;
}
.tag-label {
font-weight: bold;
font-size: 11px;
flex-shrink: 0;
}
.tag-value {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.tag-divider {
margin: 0 2px;
color: #99c2ff;
flex-shrink: 0;
}
:deep(.el-table__body-wrapper) {
overflow-y: auto;
height: 100%;
}
:deep(.el-table__header-wrapper) {
position: sticky;
top: 0;
z-index: 2;
}
:deep(.el-table--border) {
border-color: #ebeef5;
}
:deep(.el-table td), :deep(.el-table th) {
padding: 4px 0;
}
:deep(.el-table--enable-row-hover .el-table__body tr:hover > td) {
background-color: #f5f7fa;
}
.student-checkbox-container {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
margin-bottom: 15px;
}
.student-checkbox-item {
margin-right: 10px;
margin-bottom: 8px;
display: inline-block;
}
.student-info {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
}
.student-name {
font-weight: 500;
}
.student-school {
color: #666;
margin-left: 4px;
font-size: 0.9em;
}
.search-box {
display: flex;
align-items: center;
} }
</style> </style>

4
niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php

@ -72,7 +72,7 @@ class CourseSchedule extends BaseAdminController
["student_ids", ""], ["student_ids", ""],
["available_capacity", 0], ["available_capacity", 0],
["status", ""], ["status", ""],
['is_system_add', 1] ['auto_schedule', 1]
]); ]);
$this->validate($data, 'app\validate\course_schedule\CourseSchedule.add'); $this->validate($data, 'app\validate\course_schedule\CourseSchedule.add');
$id = (new CourseScheduleService())->add($data); $id = (new CourseScheduleService())->add($data);
@ -97,7 +97,7 @@ class CourseSchedule extends BaseAdminController
["student_ids", ""], ["student_ids", ""],
["available_capacity", 0], ["available_capacity", 0],
["status", ""], ["status", ""],
['is_system_add', 1] ['auto_schedule', 1]
]); ]);
$this->validate($data, 'app\validate\course_schedule\CourseSchedule.edit'); $this->validate($data, 'app\validate\course_schedule\CourseSchedule.edit');
(new CourseScheduleService())->edit($id, $data); (new CourseScheduleService())->edit($id, $data);

5
niucloud/app/adminapi/controller/customer_resources/CustomerResources.php

@ -190,4 +190,9 @@ class CustomerResources extends BaseAdminController
]); ]);
return success((new CustomerResourcesService())->getCoachPerson($data['campus_id'])); return success((new CustomerResourcesService())->getCoachPerson($data['campus_id']));
} }
public function getResourceByCourse($schedule)
{
return success((new CustomerResourcesService())->getResourceByCourse($schedule));
}
} }

1
niucloud/app/adminapi/controller/order_table/OrderTable.php

@ -108,5 +108,4 @@ class OrderTable extends BaseAdminController
public function getPersonnelAll(){ public function getPersonnelAll(){
return success(( new OrderTableService())->getPersonnelAll()); return success(( new OrderTableService())->getPersonnelAll());
} }
} }

21
niucloud/app/adminapi/controller/person_course_schedule/PersonCourseSchedule.php

@ -52,12 +52,9 @@ class PersonCourseSchedule extends BaseAdminController
*/ */
public function add(){ public function add(){
$data = $this->request->params([ $data = $this->request->params([
["person_id",0], ["resources_id",0],
["person_type",""],
["schedule_id",0], ["schedule_id",0],
["course_date","2025-05-16 17:47:53"], ["schedule_id",0]
["time_slot",""],
]); ]);
$this->validate($data, 'app\validate\person_course_schedule\PersonCourseSchedule.add'); $this->validate($data, 'app\validate\person_course_schedule\PersonCourseSchedule.add');
$id = (new PersonCourseScheduleService())->add($data); $id = (new PersonCourseScheduleService())->add($data);
@ -71,11 +68,8 @@ class PersonCourseSchedule extends BaseAdminController
*/ */
public function edit(int $id){ public function edit(int $id){
$data = $this->request->params([ $data = $this->request->params([
["person_id",0], ["resources_id",0],
["person_type",""], ["schedule_id",0]
["schedule_id",0],
["course_date","2025-05-16 17:47:53"],
["time_slot",""],
]); ]);
$this->validate($data, 'app\validate\person_course_schedule\PersonCourseSchedule.edit'); $this->validate($data, 'app\validate\person_course_schedule\PersonCourseSchedule.edit');
@ -93,5 +87,10 @@ class PersonCourseSchedule extends BaseAdminController
return success('DELETE_SUCCESS'); return success('DELETE_SUCCESS');
} }
/**
* 获取试课人员
*/
public function getTryCoursePerson($schedule_id){
return success((new PersonCourseScheduleService())->getTryCoursePerson($schedule_id));
}
} }

4
niucloud/app/adminapi/route/customer_resources.php

@ -41,10 +41,12 @@ Route::group('customer_resources', function () {
Route::post('fp_edit', 'customer_resources.CustomerResources/fp_edit'); Route::post('fp_edit', 'customer_resources.CustomerResources/fp_edit');
Route::post('personnel_all_byname', 'customer_resources.CustomerResources/personnelAllByname'); Route::get('personnel_all_byname', 'customer_resources.CustomerResources/personnelAllByname');
Route::get('coach_person', 'customer_resources.CustomerResources/getCoachPerson'); Route::get('coach_person', 'customer_resources.CustomerResources/getCoachPerson');
Route::get('getResourceByCourse/:schedule/students', 'customer_resources.CustomerResources/getResourceByCourse');
})->middleware([ })->middleware([
AdminCheckToken::class, AdminCheckToken::class,
AdminCheckRole::class, AdminCheckRole::class,

3
niucloud/app/adminapi/route/person_course_schedule.php

@ -28,7 +28,8 @@ Route::group('person_course_schedule', function () {
Route::put('person_course_schedule/:id', 'person_course_schedule.PersonCourseSchedule/edit'); Route::put('person_course_schedule/:id', 'person_course_schedule.PersonCourseSchedule/edit');
//删除人员与课程安排关系 //删除人员与课程安排关系
Route::delete('person_course_schedule/:id', 'person_course_schedule.PersonCourseSchedule/del'); Route::delete('person_course_schedule/:id', 'person_course_schedule.PersonCourseSchedule/del');
//获取试课人员
Route::get('get_try_course_person/:schedule_id', 'person_course_schedule.PersonCourseSchedule/getTryCoursePerson');
})->middleware([ })->middleware([
AdminCheckToken::class, AdminCheckToken::class,
AdminCheckRole::class, AdminCheckRole::class,

7
niucloud/app/common.php

@ -1145,3 +1145,10 @@ function decryptWechatPayNotify($ciphertext, $nonce, $associatedData, $key)
$associatedData $associatedData
); );
} }
/**
* 判断是否为手机号
*/
function isPhone($mobile)
{
return preg_match('/^1[3456789]\d{9}$/', $mobile);
}

3
niucloud/app/model/course_schedule/CourseSchedule.php

@ -53,6 +53,9 @@ class CourseSchedule extends BaseModel
*/ */
protected $defaultSoftDelete = 0; protected $defaultSoftDelete = 0;
protected $json = ['participants', 'student_ids'];
protected $jsonAssoc = true;
/** /**
* 搜索器:场地校区 * 搜索器:场地校区
* @param $value * @param $value

5
niucloud/app/model/customer_resources/CustomerResources.php

@ -12,6 +12,7 @@
namespace app\model\customer_resources; namespace app\model\customer_resources;
use app\model\dict\Dict; use app\model\dict\Dict;
use app\model\order_table\OrderTable;
use app\model\resource_sharing\ResourceSharing; use app\model\resource_sharing\ResourceSharing;
use app\model\six_speed\SixSpeed; use app\model\six_speed\SixSpeed;
use core\base\BaseModel; use core\base\BaseModel;
@ -89,6 +90,10 @@ class CustomerResources extends BaseModel
'status' => '客户状态', 'status' => '客户状态',
]; ];
public function orderTable()
{
return $this->hasMany(OrderTable::class, 'resource_id', 'id');
}
/** /**
* 搜索器:客户资源姓名 * 搜索器:客户资源姓名
* @param $value * @param $value

59
niucloud/app/model/person_course_schedule/PersonCourseSchedule.php

@ -11,6 +11,9 @@
namespace app\model\person_course_schedule; namespace app\model\person_course_schedule;
use app\model\customer_resources\CustomerResources;
use app\model\personnel\Personnel;
use app\model\student\Student;
use core\base\BaseModel; use core\base\BaseModel;
use think\model\concern\SoftDelete; use think\model\concern\SoftDelete;
use think\model\relation\HasMany; use think\model\relation\HasMany;
@ -23,9 +26,6 @@ use think\model\relation\HasOne;
*/ */
class PersonCourseSchedule extends BaseModel class PersonCourseSchedule extends BaseModel
{ {
use SoftDelete;
/** /**
* 数据表主键 * 数据表主键
* @var string * @var string
@ -38,18 +38,6 @@ class PersonCourseSchedule extends BaseModel
*/ */
protected $name = 'person_course_schedule'; protected $name = 'person_course_schedule';
/**
* 定义软删除标记字段.
* @var string
*/
protected $deleteTime = 'deleted_at';
/**
* 定义软删除字段的默认值.
* @var int
*/
protected $defaultSoftDelete = 0;
/** /**
* 搜索器:人员与课程安排关系关系编号 * 搜索器:人员与课程安排关系关系编号
* @param $value * @param $value
@ -57,11 +45,11 @@ class PersonCourseSchedule extends BaseModel
*/ */
public function searchIdAttr($query, $value, $data) public function searchIdAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("id", $value); $query->where("id", $value);
} }
} }
/** /**
* 搜索器:人员与课程安排关系人员或资源ID * 搜索器:人员与课程安排关系人员或资源ID
* @param $value * @param $value
@ -69,11 +57,11 @@ class PersonCourseSchedule extends BaseModel
*/ */
public function searchPersonIdAttr($query, $value, $data) public function searchPersonIdAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("person_id", $value); $query->where("person_id", $value);
} }
} }
/** /**
* 搜索器:人员与课程安排关系人员类型: student-正式学员, customer_resource-客户资源 * 搜索器:人员与课程安排关系人员类型: student-正式学员, customer_resource-客户资源
* @param $value * @param $value
@ -81,11 +69,11 @@ class PersonCourseSchedule extends BaseModel
*/ */
public function searchPersonTypeAttr($query, $value, $data) public function searchPersonTypeAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("person_type", $value); $query->where("person_type", $value);
} }
} }
/** /**
* 搜索器:人员与课程安排关系课程安排ID * 搜索器:人员与课程安排关系课程安排ID
* @param $value * @param $value
@ -93,11 +81,11 @@ class PersonCourseSchedule extends BaseModel
*/ */
public function searchScheduleIdAttr($query, $value, $data) public function searchScheduleIdAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("schedule_id", $value); $query->where("schedule_id", $value);
} }
} }
/** /**
* 搜索器:人员与课程安排关系上课日期 * 搜索器:人员与课程安排关系上课日期
* @param $value * @param $value
@ -105,11 +93,11 @@ class PersonCourseSchedule extends BaseModel
*/ */
public function searchCourseDateAttr($query, $value, $data) public function searchCourseDateAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("course_date", $value); $query->where("course_date", $value);
} }
} }
/** /**
* 搜索器:人员与课程安排关系上课时段 * 搜索器:人员与课程安排关系上课时段
* @param $value * @param $value
@ -117,14 +105,25 @@ class PersonCourseSchedule extends BaseModel
*/ */
public function searchTimeSlotAttr($query, $value, $data) public function searchTimeSlotAttr($query, $value, $data)
{ {
if ($value) { if ($value) {
$query->where("time_slot", $value); $query->where("time_slot", $value);
} }
} }
public function person()
{
return $this->hasOne(Personnel::class, 'id', 'person_id');
}
public function student()
{
return $this->hasOne(Student::class, 'id', 'student_id');
}
public function resources()
{
return $this->hasOne(CustomerResources::class, 'id', 'resources_id');
}
} }

86
niucloud/app/service/admin/course_schedule/CourseScheduleService.php

@ -13,6 +13,8 @@ namespace app\service\admin\course_schedule;
use app\model\course_schedule\CourseSchedule; use app\model\course_schedule\CourseSchedule;
use app\model\person_course_schedule\PersonCourseSchedule; use app\model\person_course_schedule\PersonCourseSchedule;
use app\model\personnel\Personnel;
use app\model\venue\Venue;
use app\service\admin\venue\VenueService; use app\service\admin\venue\VenueService;
use core\base\BaseAdminService; use core\base\BaseAdminService;
@ -37,7 +39,7 @@ class CourseScheduleService extends BaseAdminService
*/ */
public function getPage(array $where = []) public function getPage(array $where = [])
{ {
$field = 'id,campus_id,venue_id,course_date,time_slot,course_id,coach_id,participants,student_ids,available_capacity,status,created_by,created_at,updated_at,deleted_at,is_system_add'; $field = 'id,campus_id,venue_id,course_date,time_slot,course_id,coach_id,participants,student_ids,available_capacity,status,created_by,created_at,updated_at,deleted_at,auto_schedule';
$order = 'id desc'; $order = 'id desc';
$search_model = $this->model->withSearch(["campus_id", "venue_id", "course_date"], $where) $search_model = $this->model->withSearch(["campus_id", "venue_id", "course_date"], $where)
@ -55,7 +57,7 @@ class CourseScheduleService extends BaseAdminService
*/ */
public function getInfo(int $id) public function getInfo(int $id)
{ {
$field = 'id,campus_id,venue_id,course_date,time_slot,course_id,coach_id,participants,student_ids,available_capacity,status,created_by,created_at,updated_at,deleted_at,is_system_add'; $field = 'id,campus_id,venue_id,course_date,time_slot,course_id,coach_id,participants,student_ids,available_capacity,status,created_by,created_at,updated_at,deleted_at,auto_schedule';
$info = $this->model->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray(); $info = $this->model->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray();
return $info; return $info;
@ -75,9 +77,8 @@ class CourseScheduleService extends BaseAdminService
'time_slot' => $data['time_slot'], 'time_slot' => $data['time_slot'],
'course_id' => $data['course_id'], 'course_id' => $data['course_id'],
'coach_id' => $data['coach_id'], 'coach_id' => $data['coach_id'],
'participants' => $data['participants'] ? $data['participants'] : [], 'auto_schedule' => $data['auto_schedule'],
'student_ids' => $data['student_ids'] ? $data['student_ids'] : [], 'available_capacity' => (new Venue())->where('id', $data['venue_id'])->value('capacity')
'is_system_add' => $data['is_system_add']
]; ];
$status = $this->model->where([ $status = $this->model->where([
['course_date', '=', $data['course_date']], ['course_date', '=', $data['course_date']],
@ -108,9 +109,7 @@ class CourseScheduleService extends BaseAdminService
'time_slot' => $data['time_slot'], 'time_slot' => $data['time_slot'],
'course_id' => $data['course_id'], 'course_id' => $data['course_id'],
'coach_id' => $data['coach_id'], 'coach_id' => $data['coach_id'],
'participants' => $data['participants'] ? $data['participants'] : [], 'auto_schedule' => $data['is_system_add']
'student_ids' => $data['student_ids'] ? $data['student_ids'] : [],
'is_system_add' => $data['is_system_add']
]; ];
$status = $this->model->where([ $status = $this->model->where([
['course_date', '=', $data['course_date']], ['course_date', '=', $data['course_date']],
@ -172,36 +171,33 @@ class CourseScheduleService extends BaseAdminService
$person_schedules = []; $person_schedules = [];
if (!empty($schedule_ids)) { if (!empty($schedule_ids)) {
$person_schedules = (new PersonCourseSchedule())->where([ $person_schedules = (new PersonCourseSchedule())->with(['resources', 'person', 'student'])->where([
['schedule_id', 'in', $schedule_ids] ['schedule_id', 'in', $schedule_ids]
])->select()->toArray(); ])
->select()
->toArray();
} }
// 教师
$coach_ids = array_column($schedules, 'coach_id');
$coaches = (new Personnel())->where('id', 'in', $coach_ids)->select()->toArray();
// 组织数据结构 // 组织数据结构
$days = []; $days = [];
$weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']; $weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
// 获取所有时间段和教室 // 获取所有时间段和教室
$time_slots = []; $time_slots = [];
$classrooms = []; $classrooms = (new VenueService())->getVenueAll($campus_id);
// 如果没有数据,设置默认的时间段和教室 // 如果没有数据,设置默认的时间段和教室
if (empty($schedules)) { if (empty($schedules)) {
//获取校区下的所有教室 $time_slots = (new VenueService())->getVenueTime($classrooms);
$venues = (new VenueService())->getVenueAll($campus_id);
$time_slots = (new VenueService())->getVenueTime($venues);
$classrooms = $venues;
} else { } else {
foreach ($schedules as $schedule) { foreach ($schedules as $schedule) {
if (!in_array($schedule['time_slot'], $time_slots)) { if (!in_array($schedule['time_slot'], $time_slots)) {
$time_slots[] = $schedule['time_slot']; $time_slots[] = $schedule['time_slot'];
} }
// 假设venue_id代表教室
if (!in_array($schedule['venue_id'], $classrooms)) {
$classrooms[] = $schedule['venue_id'];
}
} }
} }
@ -214,22 +210,13 @@ class CourseScheduleService extends BaseAdminService
return $schedule['course_date'] == $current_date; return $schedule['course_date'] == $current_date;
}); });
$day_classrooms = empty($day_schedules) ? $classrooms : [];
if (!empty($day_schedules)) {
foreach ($day_schedules as $schedule) {
if (!in_array($schedule['venue_id'], $day_classrooms)) {
$day_classrooms[] = $schedule['venue_id'];
}
}
}
// 构建每个时间段的数据 // 构建每个时间段的数据
$day_time_slots = []; $day_time_slots = [];
foreach ($time_slots as $time_slot) { foreach ($time_slots as $time_slot) {
$slot_data = [ $slot_data = [
'timeRange' => $time_slot, 'timeRange' => $time_slot,
'color' => '#' . dechex(rand(0x000000, 0xFFFFFF)), // 随机颜色 'color' => '#FFFFFF', // 默认白色背景
'textColor' => '#000000', // 默认黑色文字
]; ];
// 查找该时间段的课程 // 查找该时间段的课程
@ -239,19 +226,38 @@ class CourseScheduleService extends BaseAdminService
if (!empty($slot_schedule)) { if (!empty($slot_schedule)) {
$schedule = reset($slot_schedule); $schedule = reset($slot_schedule);
// 查找该课程的学员 // 查找该课程的学员
$students = array_filter($person_schedules, function ($person) use ($schedule) { $students = array_filter($person_schedules, function ($person) use ($schedule) {
return $person['schedule_id'] == $schedule['id']; return $person['schedule_id'] == $schedule['id'];
}); });
$student_names = array_column($students, 'person_id'); $teacher = array_filter($coaches, function ($coach) use ($schedule) {
return $coach['id'] == $schedule['coach_id'];
});
$venues = array_filter($classrooms, function ($venue) use ($schedule) {
return $venue['id'] == $schedule['venue_id'];
});
$available_capacity = $schedule['available_capacity'] - count($students);
if ($available_capacity > 0) {
$slot_data['backgroundColor'] = '#079307'; // 绿色背景
$slot_data['color'] = '#FFFFFF'; // 白色文字
} elseif ($available_capacity == 0) {
$slot_data['backgroundColor'] = '#FF0000'; // 红色背景
$slot_data['color'] = '#FFFFFF'; // 白色文字
} else {
$slot_data['backgroundColor'] = '#FFFFFF'; // 白色背景
$slot_data['color'] = '#000000'; // 黑色文字
}
$slot_data['course'] = [ $slot_data['course'] = [
'teacher' => $schedule['coach_id'] ?? '', 'teacher' => is_array($teacher) ? array_values($teacher) : [],
'students' => $student_names, 'students' => is_array($students) ? array_values($students) : [],
'classroom' => $schedule['venue_id'], 'classroom' => is_array($venues) ? array_values($venues) : [],
'hasnumber' => $schedule['available_capacity'] ?? 0, 'hasnumber' => $available_capacity,
'schedule' => $schedule,
]; ];
} }
@ -261,7 +267,7 @@ class CourseScheduleService extends BaseAdminService
$days[] = [ $days[] = [
'date' => $weekdays[$day_index % 7] . ' (' . $current_date . ')', 'date' => $weekdays[$day_index % 7] . ' (' . $current_date . ')',
'timeSlots' => $day_time_slots, 'timeSlots' => $day_time_slots,
'classrooms' => $day_classrooms, 'classrooms' => $classrooms,
]; ];
$current_date = date('Y-m-d', strtotime($current_date . ' +1 day')); $current_date = date('Y-m-d', strtotime($current_date . ' +1 day'));
@ -280,6 +286,6 @@ class CourseScheduleService extends BaseAdminService
*/ */
public function getCampusVenue($id) public function getCampusVenue($id)
{ {
return $this->model->where('availability_status', 1)->where('campus_id', $id)->select(); return (new Venue())->where('availability_status', 1)->where('campus_id', $id)->select();
} }
} }

46
niucloud/app/service/admin/customer_resources/CustomerResourcesService.php

@ -12,8 +12,10 @@
namespace app\service\admin\customer_resources; namespace app\service\admin\customer_resources;
use app\model\campus_person_role\CampusPersonRole; use app\model\campus_person_role\CampusPersonRole;
use app\model\course_schedule\CourseSchedule;
use app\model\customer_resource_changes\CustomerResourceChanges; use app\model\customer_resource_changes\CustomerResourceChanges;
use app\model\customer_resources\CustomerResources; use app\model\customer_resources\CustomerResources;
use app\model\order_table\OrderTable;
use app\model\personnel\Personnel; use app\model\personnel\Personnel;
use app\model\campus\Campus; use app\model\campus\Campus;
@ -328,7 +330,17 @@ class CustomerResourcesService extends BaseAdminService
public function personnelAllByname($name) public function personnelAllByname($name)
{ {
return $this->model->where('name', 'like', '%' . $name . '%')->select()->toArray(); $query = $this->model->whereNotExists(function ($query) {
$query->name('order_table') // 订单表名
->whereRaw('school_order_table.resource_id = school_customer_resources.id');
});
//如果$name是手机号,则按手机号查询
if (isPhone($name)) {
$query = $query->where('phone_number', $name);
} else {
$query = $query->where('name', 'like', '%' . $name . '%');
}
return $query->select()->toArray();
} }
/** /**
@ -364,4 +376,36 @@ class CustomerResourcesService extends BaseAdminService
} }
/**
* 获取课程下的资源和学员
* @param $course_id
* @return mixed
*/
public function getResourceByCourse($schedule_id)
{
$schedule = CourseSchedule::find($schedule_id);
$course_id = $schedule->course_id;
$all = (new OrderTable())
->alias('a')
->join(['school_customer_resources' => 'b'], 'a.resource_id = b.id')
->join(['school_student' => 'c'], 'a.resource_id = c.user_id')
->field('b.name, b.phone_number, b.id, c.name as student_name, c.age, c.id as student_id, a.id as order_id')
->where([
['a.course_id', '=', $course_id],
['a.order_status', '=', 'paid']
])
->group('a.id')
->select()
->toArray();
// 假设 $selectedCode 是一个数组,包含已经加入课程的人员 ID
$selectedCode = $schedule->participants;
// 遍历 $all,为每个元素添加 checked 属性
foreach ($all as &$item) {
$item['checked'] = in_array($item['id'], $selectedCode ? $selectedCode : []);
}
return $all;
}
} }

108
niucloud/app/service/admin/person_course_schedule/PersonCourseScheduleService.php

@ -11,8 +11,12 @@
namespace app\service\admin\person_course_schedule; namespace app\service\admin\person_course_schedule;
use app\model\course_schedule\CourseSchedule;
use app\model\customer_resources\CustomerResources;
use app\model\person_course_schedule\PersonCourseSchedule; use app\model\person_course_schedule\PersonCourseSchedule;
use app\model\student\Student;
use app\model\venue\Venue;
use core\base\BaseAdminService; use core\base\BaseAdminService;
@ -39,7 +43,7 @@ class PersonCourseScheduleService extends BaseAdminService
$field = 'id,person_id,person_type,schedule_id,course_date,time_slot,created_at,updated_at,deleted_at'; $field = 'id,person_id,person_type,schedule_id,course_date,time_slot,created_at,updated_at,deleted_at';
$order = 'id desc'; $order = 'id desc';
$search_model = $this->model->withSearch(["id","person_id","person_type","schedule_id","course_date","time_slot"], $where)->field($field)->order($order); $search_model = $this->model->withSearch(["id", "person_id", "person_type", "schedule_id", "course_date", "time_slot"], $where)->field($field)->order($order);
$list = $this->pageQuery($search_model); $list = $this->pageQuery($search_model);
return $list; return $list;
} }
@ -64,9 +68,82 @@ class PersonCourseScheduleService extends BaseAdminService
*/ */
public function add(array $data) public function add(array $data)
{ {
$res = $this->model->create($data); // 获取所有要新增的学生
return $res->id; $students = (new Student())->whereIn('user_id', $data['resources_id'])->column('user_id', 'id');
// 新增的 participant 列表
$newParticipants = $data['resources_id'];
// 获取当前课程安排
$schedule = (new CourseSchedule())->find($data['schedule_id']);
if (!$schedule) {
return $this->error('课程安排不存在');
}
$capacity = (new Venue())->where('id', $schedule->venue_id)->value('capacity');
// 原来的 participant 列表
$oldParticipants = $schedule->participants ?? [];
// 计算差集:哪些是被删除的(需要从预约记录中删除)
$deletedParticipants = array_diff($oldParticipants, $newParticipants);
// 计算可用容量
$addedCount = count($newParticipants) - count($oldParticipants);
$deletedCount = count($deletedParticipants);
$available_capacity = $capacity + $deletedCount - $addedCount;
if ($available_capacity < 0) {
return $this->error('当前课程安排已满');
}
try {
$this->model->startTrans();
// 1. 更新课程安排信息
(new CourseSchedule())->where('id', $data['schedule_id'])->update([
'participants' => $newParticipants,
'student_ids' => array_keys($students),
'available_capacity' => $available_capacity
]);
// 2. 删除旧的预约记录(针对被移除的人)
if (!empty($deletedParticipants)) {
$this->model->whereIn('resources_id', $deletedParticipants)
->where('course_date', $schedule->course_date)
->where('time_slot', $schedule->time_slot)
->delete();
}
// 3. 删除原有的,再批量插入新的预约记录(避免重复)
foreach ($newParticipants as $participant) {
$student_id = array_flip($students)[$participant] ?? 0;
// 先删除已存在的记录(防止重复)
$this->model->where([
['resources_id', '=', $participant],
['course_date', '=', $schedule->course_date],
['time_slot', '=', $schedule->time_slot]
])->delete();
// 插入新的记录
$this->model->create([
'resources_id' => $participant,
'person_id' => $this->uid,
'schedule_id' => $data['schedule_id'],
'student_id' => $student_id,
'person_type' => $student_id ? 'student' : 'customer_resource',
'course_date' => $schedule->course_date,
'time_slot' => $schedule->time_slot
]);
}
$this->model->commit();
} catch (\Exception $e) {
$this->model->rollback();
return $this->error('操作失败: ' . $e->getMessage());
}
return true;
} }
/** /**
@ -94,6 +171,27 @@ class PersonCourseScheduleService extends BaseAdminService
return $res; return $res;
} }
public function getTryCoursePerson($schedule_id)
{
$list = $this->model->where('person_type', 'customer_resource')
->where('schedule_id', $schedule_id)
->select();
$resources = (new CustomerResources())->whereIn('id', $list->column('resources_id'))->select()->toArray();
$data = [];
foreach ($resources as $key => $value) {
// 构建符合需求的对象
$data[] = [
"name" => $value['name'],
"phone_number" => $value['phone_number'],
"id" => $value['id'],
"student_name" => null,
"age" => $value['age'] ?? null,
"student_id" => null,
"order_id" => null,
"checked" => true
];
}
return $data;
}
} }

18
niucloud/app/validate/person_course_schedule/PersonCourseSchedule.php

@ -20,24 +20,18 @@ class PersonCourseSchedule extends BaseValidate
{ {
protected $rule = [ protected $rule = [
'person_id' => 'require', 'resources_id' => 'require',
'person_type' => 'require', 'schedule_id' => 'require'
'schedule_id' => 'require',
'course_date' => 'require',
'time_slot' => 'require',
]; ];
protected $message = [ protected $message = [
'person_id.require' => ['common_validate.require', ['person_id']], 'resources_id.require' => ['common_validate.require', ['person_id']],
'person_type.require' => ['common_validate.require', ['person_type']], 'schedule_id.require' => ['common_validate.require', ['schedule_id']]
'schedule_id.require' => ['common_validate.require', ['schedule_id']],
'course_date.require' => ['common_validate.require', ['course_date']],
'time_slot.require' => ['common_validate.require', ['time_slot']],
]; ];
protected $scene = [ protected $scene = [
"add" => ['person_id', 'person_type', 'schedule_id', 'course_date', 'time_slot'], "add" => ['resources_id', 'schedule_id'],
"edit" => ['person_id', 'person_type', 'schedule_id', 'course_date', 'time_slot'] "edit" => ['resources_id', 'schedule_id']
]; ];
} }

Loading…
Cancel
Save