Browse Source

Merge branch 'wangzeyan'

master
王泽彦 10 months ago
parent
commit
0e8a71c554
  1. 3
      admin/src/app/api/course.ts
  2. 2
      admin/src/app/api/course_schedule.ts
  3. 4
      admin/src/app/api/customer_resources.ts
  4. 10
      admin/src/app/lang/zh-cn/course.course.json
  5. 42
      admin/src/app/lang/zh-cn/course_schedule.course_schedule.json
  6. 390
      admin/src/app/views/course_schedule/components/course-schedule-edit.vue
  7. 184
      admin/src/app/views/course_schedule/course_schedule.vue
  8. 288
      admin/src/app/views/timetables/components/schedule-add.vue
  9. 315
      admin/src/app/views/timetables/components/seat-selector.vue
  10. 23
      admin/src/app/views/timetables/timetables.vue
  11. 113
      admin/src/utils/timeslots.ts
  12. 93
      niucloud/app/adminapi/controller/course/Course.php
  13. 101
      niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php
  14. 195
      niucloud/app/adminapi/controller/customer_resources/CustomerResources.php
  15. 2
      niucloud/app/adminapi/route/classroom.php
  16. 2
      niucloud/app/adminapi/route/course.php
  17. 2
      niucloud/app/adminapi/route/course_schedule.php
  18. 3
      niucloud/app/adminapi/route/customer_resources.php
  19. 63
      niucloud/app/model/course_schedule/CourseSchedule.php
  20. 2
      niucloud/app/model/venue/Venue.php
  21. 10
      niucloud/app/service/admin/course/CourseService.php
  22. 172
      niucloud/app/service/admin/course_schedule/CourseScheduleService.php
  23. 139
      niucloud/app/service/admin/customer_resources/CustomerResourcesService.php
  24. 99
      niucloud/app/service/admin/venue/VenueService.php
  25. 6
      niucloud/app/validate/course_schedule/CourseSchedule.php

3
admin/src/app/api/course.ts

@ -56,4 +56,7 @@ export function deleteCourse(id: number) {
})
}
export function getAllCourseList(params: Record<string, any>) {
return request.get(`course/getAllCourseList`, { params })
}
// USER_CODE_END -- course

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

@ -62,7 +62,7 @@ export function deleteCourseSchedule(id: number) {
* @returns
*/
export function getTimetables(params: Record<string, any>) {
return request.get(`course_schedule/course_schedule/timetables`, { params })
return request.get(`course_schedule/timetables`, { params })
}
// USER_CODE_END -- course_schedule

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

@ -72,3 +72,7 @@ export function fpEdit(params: Record<string, any>) {
showSuccessMessage: true,
})
}
export function getWithCoachList(params: Record<string, any>) {
return request.get('customer_resources/coach_person', { params })
}

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

@ -23,5 +23,13 @@
"updateCourse": "编辑课程",
"courseDeleteTips": "确定要删除该数据吗?",
"startDate": "请选择开始时间",
"endDate": "请选择结束时间"
"endDate": "请选择结束时间",
"pending": "待开始",
"upcoming": "即将开始",
"ongoing": "进行中",
"completed": "已结束",
"autoSchedule": "自动排课",
"autoSchedulePlaceholder": "请选择是否自动排课",
"yes": "是",
"no": "否"
}

42
admin/src/app/lang/zh-cn/course_schedule.course_schedule.json

@ -1,29 +1,37 @@
{
"id": "课程安排编号",
"idPlaceholder": "请输入课程安排编号",
"campusId": "校区ID",
"campusIdPlaceholder": "请输入校区ID",
"venueId": "场地ID",
"venueIdPlaceholder": "请输入场地ID",
"campusId": "校区",
"campusIdPlaceholder": "请选择校区",
"venueId": "场地",
"venueIdPlaceholder": "请选择场地",
"courseDate": "上课日期",
"courseDatePlaceholder": "请输入上课日期",
"courseDatePlaceholder": "请选择上课日期",
"timeSlot": "上课时段",
"timeSlotPlaceholder": "请输入上课时段",
"courseId": "课程ID",
"courseIdPlaceholder": "请输入课程ID",
"coachId": "上课教练ID",
"coachIdPlaceholder": "请输入上课教练ID",
"participants": "参与人员列表",
"participantsPlaceholder": "请输入参与人员列表",
"studentIds": "上课学生列表",
"studentIdsPlaceholder": "请输入上课学生列表",
"timeSlotPlaceholder": "请选择上课时段",
"courseId": "课程",
"courseIdPlaceholder": "请选择课程",
"coachId": "上课教练",
"coachIdPlaceholder": "请选择上课教练",
"participants": "参与人员",
"participantsPlaceholder": "请选择参与人员",
"studentIds": "参与学生",
"studentIdsPlaceholder": "请选择参与学生",
"availableCapacity": "根据场地容量判断的可安排学员位置数量",
"availableCapacityPlaceholder": "请输入根据场地容量判断的可安排学员位置数量",
"status": "课程状态:",
"statusPlaceholder": "请输入课程状态:",
"status": "课程状态",
"statusPlaceholder": "请选择课程状态",
"addCourseSchedule": "添加课程安排",
"updateCourseSchedule": "编辑课程安排",
"courseScheduleDeleteTips": "确定要删除该数据吗?",
"startDate": "请选择开始时间",
"endDate": "请选择结束时间"
"endDate": "请选择结束时间",
"pending": "待开始",
"upcoming": "即将开始",
"ongoing": "进行中",
"completed": "已结束",
"autoSchedule": "自动排课",
"autoSchedulePlaceholder": "请选择是否自动排课",
"yes": "是",
"no": "否"
}

390
admin/src/app/views/course_schedule/components/course-schedule-edit.vue

@ -15,93 +15,107 @@
v-loading="loading"
>
<el-form-item :label="t('campusId')" prop="campus_id">
<el-input
<el-select
v-model="formData.campus_id"
clearable
:placeholder="t('campusIdPlaceholder')"
class="input-width"
/>
>
<el-option
v-for="item in campusList"
:key="item.id"
:label="item.campus_name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('venueId')" prop="venue_id">
<el-input
<el-select
v-model="formData.venue_id"
clearable
:placeholder="t('venueIdPlaceholder')"
class="input-width"
/>
>
<el-option
v-for="item in venueList"
:key="item.id"
:label="item.venue_name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('courseDate')" prop="course_date">
<el-input
<el-date-picker
v-model="formData.course_date"
type="date"
clearable
:placeholder="t('courseDatePlaceholder')"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
class="input-width"
style="width: 100%"
:disabled-date="disabledDate"
/>
</el-form-item>
<el-form-item :label="t('timeSlot')" prop="time_slot">
<el-input
<el-select
v-model="formData.time_slot"
clearable
:placeholder="t('timeSlotPlaceholder')"
class="input-width"
/>
style="width: 100%"
allow-create
filterable
>
<el-option
v-for="(item, index) in timeSlotOptions"
:key="index"
:label="item"
:value="item"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('courseId')" prop="course_id">
<el-input
<el-select
v-model="formData.course_id"
clearable
:placeholder="t('courseIdPlaceholder')"
class="input-width"
/>
>
<el-option
v-for="item in courseList"
:key="item.id"
:label="item.course_name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('coachId')" prop="coach_id">
<el-input
<el-select
v-model="formData.coach_id"
clearable
:placeholder="t('coachIdPlaceholder')"
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('participants')" prop="participants">
<el-input
v-model="formData.participants"
clearable
:placeholder="t('participantsPlaceholder')"
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('studentIds')" prop="student_ids">
<el-input
v-model="formData.student_ids"
clearable
:placeholder="t('studentIdsPlaceholder')"
class="input-width"
/>
</el-form-item>
<el-form-item :label="t('availableCapacity')" prop="available_capacity">
<el-input
v-model="formData.available_capacity"
clearable
:placeholder="t('availableCapacityPlaceholder')"
class="input-width"
/>
>
<el-option
v-for="item in coachList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('status')" prop="status">
<el-input
v-model="formData.status"
clearable
:placeholder="t('statusPlaceholder')"
class="input-width"
/>
<el-form-item :label="t('autoSchedule')" prop="auto_schedule">
<el-radio-group v-model="formData.auto_schedule">
<el-radio :label="1">{{ t('yes') }}</el-radio>
<el-radio :label="0">{{ t('no') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
@ -120,7 +134,7 @@
</template>
<script lang="ts" setup>
import { ref, reactive, computed, watch } from 'vue'
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useDictionary } from '@/app/api/dict'
import { t } from '@/lang'
import type { FormInstance } from 'element-plus'
@ -129,6 +143,11 @@ import {
editCourseSchedule,
getCourseScheduleInfo,
} from '@/app/api/course_schedule'
import { getWithCampusList, getAllVenueList } from '@/app/api/venue'
import { getVenueInfo } from '@/app/api/venue'
import { generateTimeSlots } from '@/utils/timeslots'
import { getAllCourseList } from '@/app/api/course'
import { getWithCoachList } from '@/app/api/customer_resources'
let showDialog = ref(false)
const loading = ref(false)
@ -148,6 +167,7 @@ const initialFormData = {
student_ids: '',
available_capacity: '',
status: '',
auto_schedule: 1, //
}
const formData: Record<string, any> = reactive({ ...initialFormData })
@ -174,26 +194,9 @@ const formRules = computed(() => {
coach_id: [
{ required: true, message: t('coachIdPlaceholder'), trigger: 'blur' },
],
participants: [
{
required: true,
message: t('participantsPlaceholder'),
trigger: 'blur',
},
],
student_ids: [
{ required: true, message: t('studentIdsPlaceholder'), trigger: 'blur' },
],
available_capacity: [
{
required: true,
message: t('availableCapacityPlaceholder'),
trigger: 'blur',
},
],
status: [
{ required: true, message: t('statusPlaceholder'), trigger: 'blur' },
],
auto_schedule: [
{ required: true, message: t('autoSchedulePlaceholder'), trigger: 'change' },
]
}
})
@ -228,17 +231,234 @@ const confirm = async (formEl: FormInstance | undefined) => {
//
//
const campusList = ref<any[]>([])
//
const venueList = ref<any[]>([])
//
const timeSlotOptions = ref<string[]>([])
//
const courseList = ref<any[]>([])
//
const coachList = ref<any[]>([])
//
const isInitializing = ref(false);
//
const loadCampusList = () => {
getWithCampusList({})
.then((res) => {
campusList.value = res.data || []
})
.catch(() => {})
}
//
const loadVenueList = (campus_id?: string | number) => {
getAllVenueList({ campus_id })
.then((res) => {
venueList.value = res.data || []
})
.catch(() => {})
}
//
const loadCourseList = () => {
getAllCourseList({})
.then((res) => {
courseList.value = res.data || []
})
.catch(() => {})
}
//
const loadCoachList = (campus_id?: string | number) => {
getWithCoachList({ campus_id })
.then((res) => {
coachList.value = res.data || []
})
.catch(() => {})
}
//
watch(
() => formData.campus_id,
(newValue, oldValue) => {
//
if (isInitializing.value) return;
//
if (newValue !== oldValue) {
formData.venue_id = ''
formData.coach_id = ''
timeSlotOptions.value = []
if (newValue) {
getAllVenueList({ campus_id: newValue })
.then((res) => {
venueList.value = res.data || []
})
.catch(() => {})
getWithCoachList({ campus_id: newValue })
.then((res) => {
coachList.value = res.data || []
})
.catch(() => {})
} else {
venueList.value = []
coachList.value = []
}
}
}
)
//
watch(
() => formData.venue_id,
async (newValue, oldValue) => {
//
if (isInitializing.value) return;
//
if (newValue !== oldValue) {
formData.time_slot = ''
timeSlotOptions.value = []
if (newValue) {
try {
const res = await getVenueInfo(newValue)
if (res.data) {
timeSlotOptions.value = generateTimeSlots(res.data)
}
} catch (error) {
console.error('获取场地详情失败:', error)
}
}
}
}
)
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
loading.value = true
if (row) {
const data = await (await getCourseScheduleInfo(row.id)).data
if (data)
Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key]
})
isInitializing.value = true
try {
// 1. ()
let detailData = null;
if (row) {
const response = await getCourseScheduleInfo(row.id);
detailData = response.data;
}
// 2.
await new Promise<void>((resolve) => {
getWithCampusList({})
.then((res) => {
campusList.value = res.data || [];
resolve();
})
.catch(() => {
resolve();
});
});
// 3.
await new Promise<void>((resolve) => {
getAllCourseList({})
.then((res) => {
courseList.value = res.data || [];
resolve();
})
.catch(() => {
resolve();
});
});
// 4.
if (detailData) {
// 便
const timeSlotValue = detailData.time_slot;
// 4.1
if (detailData.campus_id !== undefined) {
formData.campus_id = detailData.campus_id;
// 4.2
await Promise.all([
new Promise<void>((resolve) => {
getAllVenueList({ campus_id: detailData.campus_id })
.then((res) => {
venueList.value = res.data || [];
resolve();
})
.catch(() => {
resolve();
});
}),
new Promise<void>((resolve) => {
getWithCoachList({ campus_id: detailData.campus_id })
.then((res) => {
coachList.value = res.data || [];
resolve();
})
.catch(() => {
resolve();
});
})
]);
}
// 4.3 ID
if (detailData.venue_id) {
// ID
formData.venue_id = detailData.venue_id;
try {
const res = await getVenueInfo(detailData.venue_id);
if (res.data) {
//
timeSlotOptions.value = generateTimeSlots(res.data);
//
const isValidTimeSlot = timeSlotOptions.value.includes(timeSlotValue);
//
if (isValidTimeSlot) {
formData.time_slot = timeSlotValue;
} else if (timeSlotOptions.value.length > 0) {
formData.time_slot = timeSlotOptions.value[0];
console.warn('原时间段不可用,已选择第一个可用时间段');
} else {
formData.time_slot = '';
console.warn('没有可用的时间段选项');
}
}
} catch (error) {
console.error('获取场地详情失败:', error);
}
}
// 4.4
Object.keys(formData).forEach((key) => {
//
if (key === 'campus_id' || key === 'venue_id' || key === 'time_slot') {
return;
}
if (detailData[key] !== undefined) {
formData[key] = detailData[key];
} else if (key === 'auto_schedule' && detailData[key] === undefined) {
// 使1
formData.auto_schedule = 1;
}
});
}
} catch (error) {
console.error('加载数据失败:', error);
} finally {
loading.value = false;
isInitializing.value = false;
}
loading.value = false
}
//
@ -282,13 +502,35 @@ const numberVerify = (rule: any, value: any, callback: any) => {
}
}
//
const disabledDate = (time: Date) => {
const today = new Date()
today.setHours(0, 0, 0, 0)
const currentDate = new Date(time)
currentDate.setHours(0, 0, 0, 0)
//
return today.getTime() > currentDate.getTime()
}
defineExpose({
showDialog,
setFormData,
})
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.input-width {
width: 100%;
}
:deep(.el-date-editor.el-input),
:deep(.el-date-editor.el-input__wrapper),
:deep(.el-select),
:deep(.el-select__wrapper) {
width: 100%;
}
</style>
<style lang="scss">
.diy-dialog-wrap .el-form-item__label {
height: auto !important;

184
admin/src/app/views/course_schedule/course_schedule.vue

@ -18,68 +18,49 @@
ref="searchFormRef"
>
<el-form-item :label="t('campusId')" prop="campus_id">
<el-input
<el-select
v-model="courseScheduleTable.searchParam.campus_id"
:placeholder="t('campusIdPlaceholder')"
/>
clearable
>
<el-option
v-for="item in campusList"
:key="item.id"
:label="item.campus_name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('venueId')" prop="venue_id">
<el-input
<el-select
v-model="courseScheduleTable.searchParam.venue_id"
:placeholder="t('venueIdPlaceholder')"
/>
clearable
>
<el-option
v-for="item in venueFilterList"
:key="item.id"
:label="item.venue_name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('courseDate')" prop="course_date">
<el-input
<el-date-picker
v-model="courseScheduleTable.searchParam.course_date"
type="date"
:placeholder="t('courseDatePlaceholder')"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
clearable
/>
</el-form-item>
<el-form-item :label="t('timeSlot')" prop="time_slot">
<el-input
v-model="courseScheduleTable.searchParam.time_slot"
:placeholder="t('timeSlotPlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('courseId')" prop="course_id">
<el-input
v-model="courseScheduleTable.searchParam.course_id"
:placeholder="t('courseIdPlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('coachId')" prop="coach_id">
<!-- <el-form-item :label="t('coachId')" prop="coach_id">
<el-input
v-model="courseScheduleTable.searchParam.coach_id"
:placeholder="t('coachIdPlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('participants')" prop="participants">
<el-input
v-model="courseScheduleTable.searchParam.participants"
:placeholder="t('participantsPlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('studentIds')" prop="student_ids">
<el-input
v-model="courseScheduleTable.searchParam.student_ids"
:placeholder="t('studentIdsPlaceholder')"
/>
</el-form-item>
<el-form-item
:label="t('availableCapacity')"
prop="available_capacity"
>
<el-input
v-model="courseScheduleTable.searchParam.available_capacity"
:placeholder="t('availableCapacityPlaceholder')"
/>
</el-form-item>
<el-form-item :label="t('status')" prop="status">
<el-input
v-model="courseScheduleTable.searchParam.status"
:placeholder="t('statusPlaceholder')"
/>
</el-form-item>
</el-form-item> -->
<el-form-item>
<el-button type="primary" @click="loadCourseScheduleList()">{{
@ -108,14 +89,22 @@
:label="t('campusId')"
min-width="120"
:show-overflow-tooltip="true"
/>
>
<template #default="{ row }">
{{ getCampusName(row.campus_id) }}
</template>
</el-table-column>
<el-table-column
prop="venue_id"
:label="t('venueId')"
min-width="120"
:show-overflow-tooltip="true"
/>
>
<template #default="{ row }">
{{ row.venue ? row.venue.venue_name : '' }}
</template>
</el-table-column>
<el-table-column
prop="course_date"
@ -136,15 +125,25 @@
:label="t('courseId')"
min-width="120"
:show-overflow-tooltip="true"
/>
>
<template #default="{ row }">
{{ row.course ? row.course.course_name : '' }}
</template>
</el-table-column>
<el-table-column
prop="coach_id"
:label="t('coachId')"
min-width="120"
:show-overflow-tooltip="true"
/>
>
<template #default="{ row }">
{{ row.coach ? row.coach.name : '' }}
</template>
</el-table-column>
<!--
<el-table-column
prop="participants"
:label="t('participants')"
@ -164,14 +163,21 @@
:label="t('availableCapacity')"
min-width="120"
:show-overflow-tooltip="true"
/>
/> -->
<el-table-column
prop="status"
:label="t('status')"
min-width="120"
:show-overflow-tooltip="true"
/>
>
<template #default="{ row }">
<span v-if="row.status === 'pending'">{{ t('pending') }}</span>
<span v-if="row.status === 'upcoming'">{{ t('upcoming') }}</span>
<span v-if="row.status === 'ongoing'">{{ t('ongoing') }}</span>
<span v-if="row.status === 'completed'">{{ t('completed') }}</span>
</template>
</el-table-column>
<el-table-column
:label="t('operation')"
@ -206,13 +212,15 @@
</template>
<script lang="ts" setup>
import { reactive, ref, watch } from 'vue'
import { reactive, ref, watch, onMounted } from 'vue'
import { t } from '@/lang'
import { useDictionary } from '@/app/api/dict'
import {
getCourseScheduleList,
deleteCourseSchedule,
} from '@/app/api/course_schedule'
import { getAllClassroomList } from '@/app/api/classroom'
import { getWithCampusList, getAllVenueList } from '@/app/api/venue'
import { img } from '@/utils/common'
import { ElMessageBox, FormInstance } from 'element-plus'
import Edit from '@/app/views/course_schedule/components/course-schedule-edit.vue'
@ -229,14 +237,7 @@ let courseScheduleTable = reactive({
searchParam: {
campus_id: '',
venue_id: '',
course_date: '',
time_slot: '',
course_id: '',
coach_id: '',
participants: '',
student_ids: '',
available_capacity: '',
status: '',
course_date: ''
},
})
@ -245,7 +246,58 @@ const searchFormRef = ref<FormInstance>()
//
const selectData = ref<any[]>([])
//
//
const campusList = ref<any[]>([])
// -
const venueList = ref<any[]>([])
// -
const venueFilterList = ref<any[]>([])
// ID
const getCampusName = (id: string | number) => {
const campus = campusList.value.find(item => item.id === id)
return campus ? campus.campus_name : id
}
//
const loadCampusList = () => {
getWithCampusList({})
.then((res) => {
campusList.value = res.data || []
})
.catch(() => {})
}
// -
const loadVenueList = () => {
getAllVenueList({})
.then((res) => {
venueList.value = res.data || []
})
.catch(() => {})
}
// -
const loadVenuesByCampus = (campus_id?: string | number) => {
getAllVenueList({ campus_id })
.then((res) => {
venueFilterList.value = res.data || []
})
.catch(() => {})
}
//
watch(
() => courseScheduleTable.searchParam.campus_id,
(newValue) => {
courseScheduleTable.searchParam.venue_id = ''
if (newValue) {
loadVenuesByCampus(newValue)
} else {
venueFilterList.value = []
}
}
)
/**
* 获取课程安排列表
@ -268,7 +320,13 @@ const loadCourseScheduleList = (page: number = 1) => {
courseScheduleTable.loading = false
})
}
loadCourseScheduleList()
//
onMounted(() => {
loadCampusList()
loadVenueList()
loadCourseScheduleList()
})
const editCourseScheduleDialog: Record<string, any> | null = ref(null)

288
admin/src/app/views/timetables/components/schedule-add.vue

@ -70,12 +70,15 @@
</el-radio-group>
</el-form-item>
<el-form-item label="位置选择" v-if="form.venue_id && form.time_slot && form.course_type === 'class'">
<el-form-item label="位置选择" v-if="form.venue_id">
<SeatSelector
:venueId="form.venue_id"
:capacity="venueCapacity"
:venueId="form.venue_id"
:capacity="maxSeatSelections"
v-model:selectedSeats="form.selectedSeats"
:selected-seat-ids="selectedStudentSeats"
:max-selections="maxSeatSelections"
@change="handleSeatSelectionChange"
@update:selected-seat-ids="updateSelectedStudentSeats"
/>
</el-form-item>
@ -98,6 +101,28 @@
</div>
</el-form-item>
<el-form-item label="班级学员" prop="student_ids" v-if="form.course_type === 'class'">
<el-select v-model="form.class_id" placeholder="请选择班级">
<el-option v-for="item in classList" :key="item.id" :label="item.class_name" :value="item.id" />
</el-select>
<div class="class-student-list">
<div class="class-student-grid">
<div
v-for="item in filteredClassStudentList"
:key="item.id"
class="class-student-item"
>
<el-checkbox
:model-value="item.checked"
@update:model-value="(val) => handleClassStudentSelectionChange(item.id, val)"
>
{{ item.name }}
</el-checkbox>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="课程名称" prop="course_name">
<el-input v-model="form.course_name" placeholder="请输入课程名称" />
</el-form-item>
@ -125,7 +150,6 @@ import { ref, defineProps, defineEmits, watch } from 'vue'
import {getAllVenueList} from '@/app/api/venue'
import { getAllClassroomList, getClassroompeople, getWithPersonnelList, getClassroompeopleCount } from '@/app/api/classroom'
import { addCourseSchedule } from '@/app/api/course_schedule'
import { getTimetables } from '@/app/api/course_schedule'
import { ElMessage } from 'element-plus'
import SeatSelector from './seat-selector.vue'
@ -151,11 +175,15 @@ const studentList = ref([])
const coachList = ref([])
const timeSlotOptions = ref([])
const availableCapacity = ref(0)
const venueCapacity = ref(0)
const studentSearchKeyword = ref('')
const filteredStudentList = ref([])
const occupiedSeats = ref([])
const participants = ref([]) // getClassroompeopleCount
const selectedStudentSeats = ref([]) //
const maxSeatSelections = ref(30) //
const classStudentList = ref([]) //
const classStudentSearchKeyword = ref('') //
const filteredClassStudentList = ref([]) //
// visible
watch(() => props.visible, (newVal) => {
@ -170,6 +198,10 @@ watch(dialogVisible, (newVal) => {
loadCoachList()
if (form.value.campus_id) {
handleCampusChange()
//
if (form.value.course_type === 'class') {
handleCourseTypeChange()
}
}
}
})
@ -228,8 +260,12 @@ const handleCampusChange = async () => {
form.value.time_slot = ''
timeSlotOptions.value = []
availableCapacity.value = 0
venueCapacity.value = 0
occupiedSeats.value = []
//
if (form.value.course_type === 'class') {
handleCourseTypeChange()
}
}
//
@ -303,12 +339,12 @@ const generateTimeSlots = (venueInfo) => {
const startTotalMinutes = startHour * 60 + startMinute
const endTotalMinutes = endHour * 60 + endMinute
// 10
for (let minutes = startTotalMinutes; minutes < endTotalMinutes - 9; minutes += 10) {
// 60
for (let minutes = startTotalMinutes; minutes < endTotalMinutes; minutes += 60) {
const startHour = Math.floor(minutes / 60)
const startMinute = minutes % 60
const endHour = Math.floor((minutes + 10) / 60)
const endMinute = (minutes + 10) % 60
const endHour = Math.floor((minutes + 60) / 60)
const endMinute = (minutes + 60) % 60
const startTime = `${startHour.toString().padStart(2, '0')}:${startMinute.toString().padStart(2, '0')}`
const endTime = `${endHour.toString().padStart(2, '0')}:${endMinute.toString().padStart(2, '0')}`
@ -331,117 +367,121 @@ const handleVenueChange = () => {
const selectedVenue = venueList.value.find(item => item.id === form.value.venue_id)
if (selectedVenue) {
timeSlotOptions.value = generateTimeSlots(selectedVenue)
venueCapacity.value = selectedVenue.capacity || 0
//
maxSeatSelections.value = selectedVenue.capacity || 30
console.log('场地容量:', selectedVenue.capacity, '最大选择数:', maxSeatSelections.value)
} else {
timeSlotOptions.value = []
venueCapacity.value = 0
maxSeatSelections.value = 30
console.log('未找到场地,使用默认容量:', maxSeatSelections.value)
}
availableCapacity.value = 0
occupiedSeats.value = []
//
form.value.selectedSeats = []
selectedStudentSeats.value = []
}
//
const calculateAvailableCapacity = async () => {
if (!form.value.venue_id || !form.value.time_slot) {
availableCapacity.value = 0
occupiedSeats.value = []
return
}
try {
//
const venueInfo = venueList.value.find(item => item.id === form.value.venue_id)
if (venueInfo) {
//
availableCapacity.value = venueInfo.capacity || 0
//
if (form.value.course_date) {
const params = {
venue_id: form.value.venue_id,
course_date: form.value.course_date,
time_slot: form.value.time_slot
}
const response = await getTimetables(params)
if (response.data && response.data.length > 0) {
//
const matchingDay = response.data.find(day =>
day.date.includes(form.value.course_date)
)
if (matchingDay) {
const matchingTimeSlot = matchingDay.timeSlots.find(slot =>
slot.timeRange === form.value.time_slot
)
if (matchingTimeSlot && matchingTimeSlot.course) {
//
availableCapacity.value = matchingTimeSlot.course.hasnumber
// API
if (matchingTimeSlot.course.occupiedSeats) {
occupiedSeats.value = matchingTimeSlot.course.occupiedSeats
} else {
//
const totalSeats = venueCapacity.value;
const occupiedCount = totalSeats - availableCapacity.value;
occupiedSeats.value = generateRandomOccupiedSeats(occupiedCount);
}
}
}
}
}
}
} catch (error) {
console.error('计算可用容量失败:', error)
}
}
//
const generateRandomOccupiedSeats = (count) => {
const occupied = [];
const rows = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
const cols = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
//
const handleSeatSelectionChange = (selectedSeats) => {
console.log('选中的座位:', selectedSeats);
for (let i = 0; i < count; i++) {
const row = rows[Math.floor(Math.random() * rows.length)];
const col = cols[Math.floor(Math.random() * cols.length)];
const seat = `${row}-${col}`;
if (!occupied.includes(seat)) {
occupied.push(seat);
}
//
if (form.value.course_type === 'student' || form.value.course_type === 'trial') {
const students = studentList.value.filter(student => form.value.student_ids.includes(student.id));
//
selectedStudentSeats.value = selectedSeats.map((seatId, index) => {
const student = students[index];
return {
id: seatId,
name: student ? student.name : ''
};
});
}
return occupied;
};
//
const handleSeatSelectionChange = (selectedSeats) => {
console.log('选中的座位:', selectedSeats);
//
const updateSelectedStudentSeats = (seatObjects) => {
selectedStudentSeats.value = seatObjects;
console.log('选中的学生座位:', selectedStudentSeats.value);
};
//
const handleClassStudentSelectionChange = (studentId, checked) => {
//
const studentIndex = classStudentList.value.findIndex(s => s.id === studentId);
if (studentIndex !== -1) {
classStudentList.value[studentIndex].checked = checked;
//
const filteredIndex = filteredClassStudentList.value.findIndex(s => s.id === studentId);
if (filteredIndex !== -1) {
filteredClassStudentList.value[filteredIndex].checked = checked;
}
// ID
if (checked) {
if (!form.value.student_ids.includes(studentId)) {
form.value.student_ids.push(studentId);
}
} else {
const idIndex = form.value.student_ids.indexOf(studentId);
if (idIndex !== -1) {
form.value.student_ids.splice(idIndex, 1);
}
}
console.log('选中的班级学员IDs:', form.value.student_ids);
}
};
//
const handleCourseTypeChange = () => {
const handleCourseTypeChange = async () => {
//
form.value.class_ids = []
form.value.student_ids = []
form.value.course_name = ''
selectedStudentSeats.value = [] //
classStudentList.value = [] //
filteredClassStudentList.value = [] //
//
if (form.value.course_type === 'student' || form.value.course_type === 'trial') {
loadStudentList()
}
}
//
const handleClassChange = () => {
//
if (form.value.class_ids.length > 0) {
const selectedClass = classList.value.find(item => item.id === form.value.class_ids[0])
if (selectedClass) {
form.value.course_name = selectedClass.class_name
} else if (form.value.course_type === 'class' && form.value.campus_id) {
//
try {
const response = await getClassroompeople(form.value.campus_id)
if (response.data && Array.isArray(response.data)) {
// API
classStudentList.value = response.data.map(student => ({
id: student.id,
name: student.name || '未命名学员',
checked: false //
}))
filteredClassStudentList.value = [...classStudentList.value]
console.log('获取到的班级学员:', classStudentList.value)
} else {
//
classStudentList.value = Array.from({ length: 20 }, (_, i) => ({
id: i + 1,
name: `班级学员${i + 1}`,
checked: false
}))
filteredClassStudentList.value = [...classStudentList.value]
}
} catch (error) {
console.error('获取班级学员失败:', error)
//
classStudentList.value = Array.from({ length: 20 }, (_, i) => ({
id: i + 1,
name: `班级学员${i + 1}`,
checked: false
}))
filteredClassStudentList.value = [...classStudentList.value]
}
}
}
@ -500,6 +540,18 @@ const searchStudents = () => {
)
}
//
const searchClassStudents = () => {
if (!classStudentSearchKeyword.value) {
filteredClassStudentList.value = [...classStudentList.value]
return
}
filteredClassStudentList.value = classStudentList.value.filter(student =>
student.name.includes(classStudentSearchKeyword.value)
)
}
//
const cancel = () => {
dialogVisible.value = false
@ -511,10 +563,6 @@ const handleTimeSlotChange = async () => {
participants.value = []
return
}
//
await calculateAvailableCapacity()
//
try {
const params = {
@ -560,15 +608,12 @@ const submit = () => {
participants_info: participants.value //
}
//
// 使student_ids
params.student_ids = form.value.student_ids
// class_ids
if (form.value.course_type === 'class') {
params.class_ids = form.value.class_ids
//
const classStudents = []
//
params.student_ids = classStudents
} else {
params.student_ids = form.value.student_ids
}
const response = await addCourseSchedule(params)
@ -599,6 +644,12 @@ const resetForm = () => {
coach_id: '',
selectedSeats: []
}
selectedStudentSeats.value = [] //
maxSeatSelections.value = 30 //
classStudentList.value = [] //
filteredClassStudentList.value = [] //
classStudentSearchKeyword.value = '' //
studentSearchKeyword.value = '' //
if (formRef.value) {
formRef.value.resetFields()
}
@ -614,4 +665,25 @@ const resetForm = () => {
padding: 10px;
margin-top: 8px;
}
.class-student-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #EBEEF5;
border-radius: 4px;
padding: 10px;
margin-top: 8px;
}
.class-student-grid {
display: flex;
flex-wrap: wrap;
}
.class-student-item {
width: 25%; /* 一行4个 */
margin-bottom: 8px;
padding-right: 10px;
box-sizing: border-box;
}
</style>

315
admin/src/app/views/timetables/components/seat-selector.vue

@ -1,20 +1,30 @@
<template>
<div class="seat-selector">
<div class="seats-container">
<div v-for="row in rows" :key="row" class="seat-row">
<div class="row-label">{{ row }}</div>
<div v-if="rows.length === 0" class="no-seats-message">
没有可用座位请确认场地容量设置
</div>
<div v-else v-for="row in rows" :key="row" class="seat-row">
<div
v-for="col in cols"
:key="`${row}-${col}`"
class="seat"
:class="{
'seat-available': isSeatAvailable(row, col),
'seat-occupied': !isSeatAvailable(row, col),
'seat-selected': isSelected(row, col)
}"
@click="toggleSeat(row, col)"
class="seat-container"
>
{{ col }}
<div
class="seat"
:class="{
'seat-available': isSeatAvailable(row, col),
'seat-occupied': !isSeatAvailable(row, col) && isSeatValid(row, col),
'seat-selected': isSelected(row, col),
'seat-hidden': !isSeatValid(row, col)
}"
@click="toggleSeat(row, col)"
>
{{ col }}
</div>
<div v-if="isSelected(row, col)" class="seat-name">
{{ getSeatObject(row, col)?.name || '' }}
</div>
</div>
</div>
</div>
@ -33,7 +43,7 @@
</div>
</div>
<div class="seat-info">
<span>已选: {{ selectedSeats.length }}</span>
<span>已选: {{ selectedSeats.length }}/{{ props.maxSelections === Infinity ? '不限' : props.maxSelections }}</span>
<span>剩余空位: {{ availableSeats.length }}</span>
</div>
</div>
@ -42,6 +52,7 @@
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { getClassroompeople } from '@/app/api/classroom';
import { getAllVenueList } from '@/app/api/venue';
const props = defineProps({
venueId: {
@ -55,53 +66,47 @@ const props = defineProps({
occupiedSeats: {
type: Array,
default: () => []
},
selectedSeatIds: {
type: Array,
default: () => []
},
maxSelections: {
type: Number,
default: Infinity
}
});
const emit = defineEmits(['update:selectedSeats', 'change']);
const emit = defineEmits(['update:selectedSeats', 'change', 'update:selectedSeatIds']);
//
const rows = ref(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']);
const cols = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const rows = ref([]);
const cols = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); // 10
const selectedSeats = ref([]);
const occupiedSeatsData = ref([]);
const selectedSeatObjects = ref([]);
const totalSeats = ref(0);
//
const adjustLayout = () => {
if (!props.capacity) return;
//
const totalCapacity = props.capacity;
let colCount = Math.ceil(Math.sqrt(totalCapacity));
if (colCount > 12) colCount = 12; //
const rowCount = Math.ceil(totalCapacity / colCount);
// A-Z
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
).slice(0, 26); // 26A-Z
// 1-N
cols.value = Array.from({ length: colCount }, (_, i) => i + 1);
// - 使onMountedwatch
const adjustLayout = (capacityValue) => {
console.log('已废弃的adjustLayout调用,使用值:', capacityValue);
};
//
const loadOccupiedSeats = async () => {
//
const loadVenueInfoAndOccupiedSeats = async () => {
if (!props.venueId) return;
try {
// 使
if (props.occupiedSeats && props.occupiedSeats.length) {
occupiedSeatsData.value = props.occupiedSeats;
return;
}
// API
const response = await getClassroompeople(props.venueId);
if (response.data && Array.isArray(response.data)) {
// API
occupiedSeatsData.value = response.data;
} else {
// API
const response = await getClassroompeople(props.venueId);
if (response.data && Array.isArray(response.data)) {
// API
occupiedSeatsData.value = response.data;
}
}
} catch (error) {
console.error('获取已占用位置失败:', error);
@ -128,22 +133,54 @@ const generateRandomOccupiedSeats = () => {
return occupied;
};
//
const availableSeats = computed(() => {
const allSeats = [];
//
const allValidSeats = computed(() => {
const seats = [];
if (rows.value.length === 0) return seats;
rows.value.forEach(row => {
cols.value.forEach(col => {
const seatId = `${row}-${col}`;
if (!occupiedSeatsData.value.includes(seatId)) {
allSeats.push(seatId);
if (isSeatValid(row, col)) {
const seatId = `${row}-${col}`;
seats.push(seatId);
}
});
});
return allSeats;
return seats;
});
//
const availableSeats = computed(() => {
const seats = [];
allValidSeats.value.forEach(seatId => {
if (!occupiedSeatsData.value.includes(seatId)) {
seats.push(seatId);
}
});
console.log('有效座位总数:', allValidSeats.value.length);
console.log('已占用座位数:', occupiedSeatsData.value.length);
console.log('可用座位数:', seats.length);
console.log('已选座位数:', selectedSeats.value.length);
return seats;
});
//
//
const isSeatValid = (row, col) => {
const seatIndex = (row.charCodeAt(0) - 65) * 10 + (col - 1);
return seatIndex < totalSeats.value;
};
//
const isSeatAvailable = (row, col) => {
//
if (!isSeatValid(row, col)) {
return false;
}
const seatId = `${row}-${col}`;
return !occupiedSeatsData.value.includes(seatId);
};
@ -154,40 +191,169 @@ const isSelected = (row, col) => {
return selectedSeats.value.includes(seatId);
};
//
const getSeatObject = (row, col) => {
const seatId = `${row}-${col}`;
return selectedSeatObjects.value.find(obj => obj.id === seatId);
};
//
const toggleSeat = (row, col) => {
console.log('点击座位:', row, col);
//
if (!isSeatValid(row, col)) {
console.log('座位无效,不能选择');
return;
}
const seatId = `${row}-${col}`;
if (!isSeatAvailable(row, col)) return; //
//
if (!isSeatAvailable(row, col)) {
console.log('座位已占用,不能选择');
return;
}
const index = selectedSeats.value.indexOf(seatId);
if (index === -1) {
//
if (selectedSeats.value.length >= props.maxSelections) {
console.log('已达到最大选择数量:', props.maxSelections);
return;
}
console.log('添加座位:', seatId);
selectedSeats.value.push(seatId);
//
if (!selectedSeatObjects.value.some(obj => obj.id === seatId)) {
selectedSeatObjects.value.push({ id: seatId, name: '' });
}
} else {
console.log('移除座位:', seatId);
selectedSeats.value.splice(index, 1);
//
const objIndex = selectedSeatObjects.value.findIndex(obj => obj.id === seatId);
if (objIndex !== -1) {
selectedSeatObjects.value.splice(objIndex, 1);
}
}
console.log('选中座位:', selectedSeats.value);
emit('update:selectedSeats', selectedSeats.value);
emit('update:selectedSeatIds', selectedSeatObjects.value);
emit('change', selectedSeats.value);
};
//
const venueList = ref([]);
//
const loadVenueList = async () => {
try {
const response = await getAllVenueList({});
if (response.data && Array.isArray(response.data)) {
venueList.value = response.data;
}
} catch (error) {
console.error('获取场地列表失败:', error);
}
};
//
const getCurrentVenueInfo = () => {
const venue = venueList.value.find(venue => venue.id === props.venueId) || null;
console.log('获取场地信息:', venue, '场地ID:', props.venueId, '场地列表:', venueList.value);
return venue;
};
//
watch(() => props.venueId, () => {
watch(() => props.venueId, async () => {
console.log('监听venueId变化:', props.venueId);
selectedSeats.value = [];
loadOccupiedSeats();
selectedSeatObjects.value = [];
//
if (props.capacity) {
console.log('使用props容量:', props.capacity);
totalSeats.value = props.capacity;
const rowCount = Math.ceil(props.capacity / 10);
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
).slice(0, 26);
} else {
//
totalSeats.value = 30;
const rowCount = 3;
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
);
}
console.log('更新布局 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
//
loadVenueInfoAndOccupiedSeats();
});
watch(() => props.capacity, () => {
adjustLayout();
//
watch(() => props.capacity, (newCapacity) => {
console.log('监听capacity变化:', newCapacity);
//
if (newCapacity || newCapacity === 0) {
totalSeats.value = newCapacity;
const rowCount = Math.ceil(newCapacity / 10);
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
).slice(0, 26);
console.log('根据新容量更新布局 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
}
});
watch(() => props.occupiedSeats, () => {
occupiedSeatsData.value = props.occupiedSeats;
});
// selectedSeatIds
watch(() => props.selectedSeatIds, (newValue) => {
if (newValue && Array.isArray(newValue)) {
selectedSeatObjects.value = [...newValue];
selectedSeats.value = newValue.map(item => item.id);
}
}, { immediate: true });
//
onMounted(() => {
adjustLayout();
loadOccupiedSeats();
onMounted(async () => {
console.log('组件挂载 - venueId:', props.venueId, 'capacity:', props.capacity);
//
if (!props.capacity && props.capacity !== 0) {
totalSeats.value = 30;
//
const rowCount = Math.ceil(totalSeats.value / 10);
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
);
console.log('使用默认设置 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
} else {
console.log('使用props容量:', props.capacity);
totalSeats.value = props.capacity;
//
const rowCount = Math.ceil(props.capacity / 10);
//
rows.value = Array.from({ length: rowCount }, (_, i) =>
String.fromCharCode(65 + i)
).slice(0, 26);
console.log('根据容量设置 - 总座位数:', totalSeats.value, '行数:', rows.value.length);
}
//
loadVenueInfoAndOccupiedSeats();
});
</script>
@ -225,7 +391,7 @@ onMounted(() => {
.seat-row {
display: flex;
align-items: center;
align-items: flex-start;
gap: 10px;
}
@ -233,6 +399,15 @@ onMounted(() => {
width: 20px;
text-align: center;
font-weight: bold;
margin-top: 10px;
}
.seat-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
margin-bottom: 5px;
}
.seat {
@ -247,6 +422,16 @@ onMounted(() => {
transition: all 0.2s;
}
.seat-name {
font-size: 10px;
max-width: 40px;
text-align: center;
word-break: break-all;
color: #333;
height: 24px;
overflow: hidden;
}
.seat-available {
background-color: #67c23a;
color: white;
@ -263,6 +448,11 @@ onMounted(() => {
color: white;
}
.seat-hidden {
visibility: hidden;
pointer-events: none;
}
.seat-legend {
display: flex;
gap: 20px;
@ -286,4 +476,11 @@ onMounted(() => {
gap: 20px;
font-size: 14px;
}
.no-seats-message {
font-size: 14px;
color: #666;
padding: 20px;
text-align: center;
}
</style>

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

@ -1,7 +1,7 @@
<template>
<el-card class="box-card !border-none" shadow="never">
<div class="mb-4 flex items-center justify-between">
<div class="flex items-center">
<div class="flex items-center" style="width: 50%;">
<el-select
v-model="selectedCampus"
placeholder="请选择校区"
@ -17,14 +17,16 @@
/>
</el-select>
<el-button @click="prevWeek" icon="el-icon-arrow-left">上一周</el-button>
<el-date-picker
<div class="ml-2 mr-2">
<el-date-picker
v-model="weekDate"
type="week"
format="YYYY 第 ww 周"
placeholder="选择周"
class="week-picker"
style="width: 180px;"
@change="handleWeekChange"
/>
</div>
<el-button @click="nextWeek" icon="el-icon-arrow-right">下一周</el-button>
<el-button type="primary" class="ml-2" @click="fetchData">查询</el-button>
</div>
@ -49,23 +51,18 @@
width="80"
align="center"
>
<template #default="{ row }">
<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="`教室${classroom}`"
:prop="`classroom${classroom}`"
:label="`${classroom.venue_name}`"
:prop="`classroom${classroom.id}`"
align="center"
>
<template #default="{ row }">
<div v-if="row.course && row.course.classroom == classroom">
<div v-if="row.course && row.course.classroom.id == classroom.id">
<div class="teacher-name">
{{ getTeacherName(row.course.teacher) }}
</div>
@ -314,8 +311,4 @@ onMounted(() => {
.classroom-name {
margin-bottom: 5px;
}
.week-picker {
width: 180px;
}
</style>

113
admin/src/utils/timeslots.ts

@ -0,0 +1,113 @@
/**
*
* @param venueInfo
* @returns ["08:00-09:00", "09:00-10:00"]
*/
export const generateTimeSlots = (venueInfo: any) => {
if (!venueInfo) return []
const timeSlots: string[] = []
// 如果场地已有预设时间段,优先使用
if (venueInfo.time_slots && Array.isArray(venueInfo.time_slots)) {
return venueInfo.time_slots.map((slot: string) => slot.trim());
}
// 根据time_range_type生成不同的时间段
switch (venueInfo.time_range_type) {
case 'fixed':
// 从fixed_time_ranges中获取固定时间段
try {
// 确保fixed_time_ranges是数组
const fixedRanges = Array.isArray(venueInfo.fixed_time_ranges)
? venueInfo.fixed_time_ranges
: (typeof venueInfo.fixed_time_ranges === 'string'
? JSON.parse(venueInfo.fixed_time_ranges || '[]')
: []);
fixedRanges.forEach((range: any) => {
// 确保start_time和end_time存在
if (range && range.start_time && range.end_time) {
timeSlots.push(`${range.start_time}-${range.end_time}`)
}
})
} catch (error) {
console.error('解析固定时间段失败:', error)
}
break
case 'all':
// 全天可用,但中午12:30-14:00不可用
// 早上8点到中午12:30,步长60分钟
for (let hour = 8; hour <= 12; hour++) {
for (let minute = 0; minute < 60; minute += 60) {
if (hour === 12 && minute === 30) continue // 跳过12:30
const startHour = hour
const startMinute = minute
const endHour = minute === 30 ? hour + 1 : hour
const endMinute = minute === 30 ? 0 : 30
const startTime = `${startHour.toString().padStart(2, '0')}:${startMinute.toString().padStart(2, '0')}`
const endTime = `${endHour.toString().padStart(2, '0')}:${endMinute.toString().padStart(2, '0')}`
timeSlots.push(`${startTime}-${endTime}`)
}
}
// 下午14:00到晚上22:00,步长60分钟
for (let hour = 14; hour < 22; hour++) {
for (let minute = 0; minute < 60; minute += 60) {
const startHour = hour
const startMinute = minute
const endHour = minute === 30 ? hour + 1 : hour
const endMinute = minute === 30 ? 0 : 30
const startTime = `${startHour.toString().padStart(2, '0')}:${startMinute.toString().padStart(2, '0')}`
const endTime = `${endHour.toString().padStart(2, '0')}:${endMinute.toString().padStart(2, '0')}`
timeSlots.push(`${startTime}-${endTime}`)
}
}
break
case 'range':
// 使用指定的时间范围,步长60分钟
if (venueInfo.time_range_start && venueInfo.time_range_end) {
const startTimeParts = venueInfo.time_range_start.split(':')
const endTimeParts = venueInfo.time_range_end.split(':')
if (startTimeParts.length === 2 && endTimeParts.length === 2) {
const startHour = parseInt(startTimeParts[0])
const startMinute = parseInt(startTimeParts[1])
const endHour = parseInt(endTimeParts[0])
const endMinute = parseInt(endTimeParts[1])
// 计算总分钟数
const startTotalMinutes = startHour * 60 + startMinute
const endTotalMinutes = endHour * 60 + endMinute
// 以60分钟为步长生成时间段
for (let minutes = startTotalMinutes; minutes < endTotalMinutes; minutes += 60) {
const startHour = Math.floor(minutes / 60)
const startMinute = minutes % 60
const endHour = Math.floor((minutes + 60) / 60)
const endMinute = (minutes + 60) % 60
const startTime = `${startHour.toString().padStart(2, '0')}:${startMinute.toString().padStart(2, '0')}`
const endTime = `${endHour.toString().padStart(2, '0')}:${endMinute.toString().padStart(2, '0')}`
timeSlots.push(`${startTime}-${endTime}`)
}
}
}
break
}
// 添加现有时间段,如果有的话
if (venueInfo.time_slot && !timeSlots.includes(venueInfo.time_slot)) {
timeSlots.push(venueInfo.time_slot);
}
return timeSlots
}

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

@ -22,21 +22,22 @@ use app\service\admin\course\CourseService;
*/
class Course extends BaseAdminController
{
/**
* 获取课程列表
* @return \think\Response
*/
public function lists(){
/**
* 获取课程列表
* @return \think\Response
*/
public function lists()
{
$data = $this->request->params([
["course_name",""],
["course_type",""],
["duration",""],
["session_count",""],
["single_session_count",""],
["price",""],
["internal_reminder",""],
["customer_reminder",""],
["remarks",""]
["course_name", ""],
["course_type", ""],
["duration", ""],
["session_count", ""],
["single_session_count", ""],
["price", ""],
["internal_reminder", ""],
["customer_reminder", ""],
["remarks", ""]
]);
return success((new CourseService())->getPage($data));
}
@ -46,7 +47,8 @@ class Course extends BaseAdminController
* @param int $id
* @return \think\Response
*/
public function info(int $id){
public function info(int $id)
{
return success((new CourseService())->getInfo($id));
}
@ -54,17 +56,18 @@ class Course extends BaseAdminController
* 添加课程
* @return \think\Response
*/
public function add(){
public function add()
{
$data = $this->request->params([
["course_name",""],
["course_type",""],
["duration",0],
["session_count",0],
["single_session_count",0],
["price",0.00],
["internal_reminder",0],
["customer_reminder",0],
["remarks",""],
["course_name", ""],
["course_type", ""],
["duration", 0],
["session_count", 0],
["single_session_count", 0],
["price", 0.00],
["internal_reminder", 0],
["customer_reminder", 0],
["remarks", ""],
]);
$this->validate($data, 'app\validate\course\Course.add');
@ -77,17 +80,18 @@ class Course extends BaseAdminController
* @param $id 课程id
* @return \think\Response
*/
public function edit(int $id){
public function edit(int $id)
{
$data = $this->request->params([
["course_name",""],
["course_type",""],
["duration",0],
["session_count",0],
["single_session_count",0],
["price",0.00],
["internal_reminder",0],
["customer_reminder",0],
["remarks",""],
["course_name", ""],
["course_type", ""],
["duration", 0],
["session_count", 0],
["single_session_count", 0],
["price", 0.00],
["internal_reminder", 0],
["customer_reminder", 0],
["remarks", ""],
]);
$this->validate($data, 'app\validate\course\Course.edit');
@ -100,9 +104,26 @@ class Course extends BaseAdminController
* @param $id 课程id
* @return \think\Response
*/
public function del(int $id){
public function del(int $id)
{
(new CourseService())->del($id);
return success('DELETE_SUCCESS');
}
public function getAllCourseList()
{
$data = $this->request->params([
["course_name", ""],
["course_type", ""],
["duration", ""],
["session_count", ""],
["single_session_count", ""],
["price", ""],
["internal_reminder", ""],
["customer_reminder", ""],
["remarks", ""]
]);
return success((new CourseService())->getAllCourseList($data));
}
}

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

@ -22,22 +22,23 @@ use app\service\admin\course_schedule\CourseScheduleService;
*/
class CourseSchedule extends BaseAdminController
{
/**
* 获取课程安排列表
* @return \think\Response
*/
public function lists(){
/**
* 获取课程安排列表
* @return \think\Response
*/
public function lists()
{
$data = $this->request->params([
["campus_id",""],
["venue_id",""],
["course_date",""],
["time_slot",""],
["course_id",""],
["coach_id",""],
["participants",""],
["student_ids",""],
["available_capacity",""],
["status",""]
["campus_id", ""],
["venue_id", ""],
["course_date", ""],
["time_slot", ""],
["course_id", ""],
["coach_id", ""],
["participants", ""],
["student_ids", ""],
["available_capacity", ""],
["status", ""]
]);
return success((new CourseScheduleService())->getPage($data));
}
@ -47,7 +48,8 @@ class CourseSchedule extends BaseAdminController
* @param mixed $id
* @return \think\Response
*/
public function info($id){
public function info($id)
{
// 确保 $id 是整数类型
$id = intval($id);
return success((new CourseScheduleService())->getInfo($id));
@ -57,19 +59,20 @@ class CourseSchedule extends BaseAdminController
* 添加课程安排
* @return \think\Response
*/
public function add(){
public function add()
{
$data = $this->request->params([
["campus_id",0],
["venue_id",0],
["course_date","2025-05-16 17:28:06"],
["time_slot",""],
["course_id",0],
["coach_id",0],
["participants",""],
["student_ids",""],
["available_capacity",0],
["status",""],
["campus_id", 0],
["venue_id", 0],
["course_date", "2025-05-16 17:28:06"],
["time_slot", ""],
["course_id", 0],
["coach_id", 0],
["participants", ""],
["student_ids", ""],
["available_capacity", 0],
["status", ""],
['is_system_add', 1]
]);
$this->validate($data, 'app\validate\course_schedule\CourseSchedule.add');
$id = (new CourseScheduleService())->add($data);
@ -81,19 +84,20 @@ class CourseSchedule extends BaseAdminController
* @param $id 课程安排id
* @return \think\Response
*/
public function edit(int $id){
public function edit(int $id)
{
$data = $this->request->params([
["campus_id",0],
["venue_id",0],
["course_date","2025-05-16 17:28:06"],
["time_slot",""],
["course_id",0],
["coach_id",0],
["participants",""],
["student_ids",""],
["available_capacity",0],
["status",""],
["campus_id", 0],
["venue_id", 0],
["course_date", "2025-05-16 17:28:06"],
["time_slot", ""],
["course_id", 0],
["coach_id", 0],
["participants", ""],
["student_ids", ""],
["available_capacity", 0],
["status", ""],
['is_system_add', 1]
]);
$this->validate($data, 'app\validate\course_schedule\CourseSchedule.edit');
(new CourseScheduleService())->edit($id, $data);
@ -105,24 +109,27 @@ class CourseSchedule extends BaseAdminController
* @param $id 课程安排id
* @return \think\Response
*/
public function del(int $id){
public function del(int $id)
{
(new CourseScheduleService())->del($id);
return success('DELETE_SUCCESS');
}
/**
* 获取课程表数据
* @return \think\Response
*/
public function timetables(){
public function timetables()
{
$data = $this->request->params([
["start_date", ""],
["end_date", ""],
["campus_id", ""],
["venue_id", ""]
["start_date", ""],
["end_date", ""],
["campus_id", ""],
["venue_id", ""]
]);
return success((new CourseScheduleService())->getTimetables($data));
}
public function getCampusVenue()
{
$data = $this->request->params([

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

@ -22,20 +22,21 @@ use app\service\admin\customer_resources\CustomerResourcesService;
*/
class CustomerResources extends BaseAdminController
{
/**
* 获取客户资源列表
* @return \think\Response
*/
public function lists(){
/**
* 获取客户资源列表
* @return \think\Response
*/
public function lists()
{
$data = $this->request->params([
["name",""],
["age",""],
["gender",""],
["phone_number",""],
['member_label', 0],
["type","khzy"],
["created_at",[]],
["updated_at",[]],
["name", ""],
["age", ""],
["gender", ""],
["phone_number", ""],
['member_label', 0],
["type", "khzy"],
["created_at", []],
["updated_at", []],
]);
return success((new CustomerResourcesService())->getPage($data));
}
@ -45,7 +46,8 @@ class CustomerResources extends BaseAdminController
* @param int $id
* @return \think\Response
*/
public function info(int $id){
public function info(int $id)
{
return success((new CustomerResourcesService())->getInfo($id));
}
@ -53,38 +55,39 @@ class CustomerResources extends BaseAdminController
* 添加客户资源
* @return \think\Response
*/
public function add(){
public function add()
{
$data = $this->request->params([
["source",""],
["source_channel",""],
["name",""],
["age",0],
["gender",""],
["phone_number",""],
["demand",""],
["purchasing_power",""],
["cognitive_idea",""],
["optional_class_time",""],
["distance",""],
["decision_maker",""],
["initial_intent",null],
["campus",""],
["status",null],
['member_label', []],
["create_year_month",date("Y-m")],
["create_date",date("Y-m-d")],
["purchase_power",""],
["concept_awareness",""],
["preferred_class_time",""],
["distance_tow",""],
["communication",""],
["promised_visit_time",""],
["actual_visit_time",""],
["call_intent","low"],
["first_visit_status",""],
["second_visit_status",""],
["is_closed",""]
["source", ""],
["source_channel", ""],
["name", ""],
["age", 0],
["gender", ""],
["phone_number", ""],
["demand", ""],
["purchasing_power", ""],
["cognitive_idea", ""],
["optional_class_time", ""],
["distance", ""],
["decision_maker", ""],
["initial_intent", null],
["campus", ""],
["status", null],
['member_label', []],
["create_year_month", date("Y-m")],
["create_date", date("Y-m-d")],
["purchase_power", ""],
["concept_awareness", ""],
["preferred_class_time", ""],
["distance_tow", ""],
["communication", ""],
["promised_visit_time", ""],
["actual_visit_time", ""],
["call_intent", "low"],
["first_visit_status", ""],
["second_visit_status", ""],
["is_closed", ""]
]);
$this->validate($data, 'app\validate\customer_resources\CustomerResources.add');
@ -96,38 +99,39 @@ class CustomerResources extends BaseAdminController
* @param $id 客户资源id
* @return \think\Response
*/
public function edit(int $id){
public function edit(int $id)
{
$data = $this->request->params([
["source",""],
["source_channel",""],
["name",""],
["age",0],
["gender",""],
["phone_number",""],
["demand",""],
["purchasing_power",""],
["cognitive_idea",""],
["optional_class_time",""],
["distance",""],
["decision_maker",""],
["initial_intent",null],
["campus",""],
["status",null],
['member_label', []],
["create_year_month",date("Y-m")],
["create_date",date("Y-m-d")],
["purchase_power",""],
["concept_awareness",""],
["preferred_class_time",""],
["distance_tow",""],
["communication",""],
["promised_visit_time",""],
["actual_visit_time",""],
["call_intent","low"],
["first_visit_status",""],
["second_visit_status",""],
["is_closed",""]
["source", ""],
["source_channel", ""],
["name", ""],
["age", 0],
["gender", ""],
["phone_number", ""],
["demand", ""],
["purchasing_power", ""],
["cognitive_idea", ""],
["optional_class_time", ""],
["distance", ""],
["decision_maker", ""],
["initial_intent", null],
["campus", ""],
["status", null],
['member_label', []],
["create_year_month", date("Y-m")],
["create_date", date("Y-m-d")],
["purchase_power", ""],
["concept_awareness", ""],
["preferred_class_time", ""],
["distance_tow", ""],
["communication", ""],
["promised_visit_time", ""],
["actual_visit_time", ""],
["call_intent", "low"],
["first_visit_status", ""],
["second_visit_status", ""],
["is_closed", ""]
]);
$this->validate($data, 'app\validate\customer_resources\CustomerResources.edit');
@ -139,32 +143,51 @@ class CustomerResources extends BaseAdminController
* @param $id 客户资源id
* @return \think\Response
*/
public function del(int $id){
public function del(int $id)
{
(new CustomerResourcesService())->del($id);
return success('DELETE_SUCCESS');
}
public function getPersonnelAll(){
public function getPersonnelAll()
{
$data = $this->request->params([
["role_id",""],
["role_id", ""],
]);
return success(( new CustomerResourcesService())->getPersonnelAll($data));
return success((new CustomerResourcesService())->getPersonnelAll($data));
}
public function getCampusAll(){
return success(( new CustomerResourcesService())->getCampusAll());
public function getCampusAll()
{
return success((new CustomerResourcesService())->getCampusAll());
}
public function fp_edit(){
public function fp_edit()
{
$data = $this->request->params([
["shared_id",""],
["shared_by",""],
["shared_id", ""],
["shared_by", ""],
]);
return success(( new CustomerResourcesService())->fp_edit($data));
return success((new CustomerResourcesService())->fp_edit($data));
}
public function personnelAllByname()
{
$data = $this->request->params([
["name", ""]
]);
return success((new CustomerResourcesService())->personnelAllByname($data['name']));
}
public function getCoachPerson()
{
$data = $this->request->params([
["campus_id", ""]
]);
return success((new CustomerResourcesService())->getCoachPerson($data['campus_id']));
}
}

2
niucloud/app/adminapi/route/classroom.php

@ -37,7 +37,7 @@ Route::group('classroom', function () {
Route::get('getClassroompeople/:class_id','classroom.Classroom/getClassroompeople');
Route::get('getClassroompeopleCount/:class_id','classroom.Classroom/getClassroompeopleCount');
Route::get('getClassroompeopleCount/:venue_id','classroom.Classroom/getClassroompeopleCount');
})->middleware([
AdminCheckToken::class,

2
niucloud/app/adminapi/route/course.php

@ -28,6 +28,8 @@ Route::group('course', function () {
Route::put('course/:id', 'course.Course/edit');
//删除课程
Route::delete('course/:id', 'course.Course/del');
//获取课程列表
Route::get('getAllCourseList', 'course.Course/getAllCourseList');
})->middleware([
AdminCheckToken::class,

2
niucloud/app/adminapi/route/course_schedule.php

@ -31,6 +31,8 @@ Route::group('course_schedule', function () {
Route::delete('course_schedule/:id', 'course_schedule.CourseSchedule/del');
//获取校区下的场地
Route::get('campus_venue', 'course_schedule.CourseSchedule/getCampusVenue');
Route::get('timetables', 'course_schedule.CourseSchedule/timetables');
})->middleware([
AdminCheckToken::class,

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

@ -41,6 +41,9 @@ Route::group('customer_resources', function () {
Route::post('fp_edit', 'customer_resources.CustomerResources/fp_edit');
Route::post('personnel_all_byname', 'customer_resources.CustomerResources/personnelAllByname');
Route::get('coach_person', 'customer_resources.CustomerResources/getCoachPerson');
})->middleware([
AdminCheckToken::class,

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

@ -11,6 +11,9 @@
namespace app\model\course_schedule;
use app\model\course\Course;
use app\model\personnel\Personnel;
use app\model\venue\Venue;
use core\base\BaseModel;
use think\model\concern\SoftDelete;
use think\model\relation\HasMany;
@ -24,7 +27,7 @@ use think\model\relation\HasOne;
class CourseSchedule extends BaseModel
{
use SoftDelete;
use SoftDelete;
/**
* 数据表主键
@ -39,17 +42,61 @@ class CourseSchedule extends BaseModel
protected $name = 'course_schedule';
/**
* 定义软删除标记字段.
* @var string
*/
* 定义软删除标记字段.
* @var string
*/
protected $deleteTime = 'deleted_at';
/**
* 定义软删除字段的默认值.
* @var int
*/
* 定义软删除字段的默认值.
* @var int
*/
protected $defaultSoftDelete = 0;
/**
* 搜索器:场地校区
* @param $value
* @param $data
*/
public function searchCampusIdAttr($query, $value, $data)
{
if ($value) {
$query->where("campus_id", $value);
}
}
/**
* 搜索器:场地ID
* @param $value
* @param $data
*/
public function searchVenueIdAttr($query, $value, $data)
{
if ($value) {
$query->where("venue_id", $value);
}
}
public function searchCourseDateAttr($query, $value, $data)
{
if ($value) {
$query->where("course_date", $value);
}
}
public function venue()
{
return $this->hasOne(Venue::class, 'id', 'venue_id');
}
public function coach()
{
return $this->hasOne(Personnel::class, 'id', 'coach_id');
}
public function course()
{
return $this->hasOne(Course::class, 'id', 'course_id');
}
}

2
niucloud/app/model/venue/Venue.php

@ -142,7 +142,7 @@ class Venue extends BaseModel
public function campus(){
return $this->hasOne(Campus::class, 'id', 'campus_id')->joinType('left')->withField('campus_name,id')->bind(['campus_id_name'=>'campus_name']);
return $this->hasOne(Campus::class, 'id', 'campus_id');
}
}

10
niucloud/app/service/admin/course/CourseService.php

@ -94,6 +94,14 @@ class CourseService extends BaseAdminService
return $res;
}
/**
* 获取课程列表
*/
public function getAllCourseList($where)
{
$field = 'id,course_name';
$where = array_filter($where);
return $this->model->where($where)->field($field)->select()->toArray();
}
}

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

@ -13,6 +13,7 @@ namespace app\service\admin\course_schedule;
use app\model\course_schedule\CourseSchedule;
use app\model\person_course_schedule\PersonCourseSchedule;
use app\service\admin\venue\VenueService;
use core\base\BaseAdminService;
@ -36,10 +37,13 @@ class CourseScheduleService extends BaseAdminService
*/
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';
$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';
$order = 'id desc';
$search_model = $this->model->withSearch(["id","campus_id","venue_id","course_date","time_slot","course_id","coach_id","participants","student_ids","available_capacity","status"], $where)->field($field)->order($order);
$search_model = $this->model->withSearch(["campus_id", "venue_id", "course_date"], $where)
->with(['venue', 'coach', 'course'])
->field($field)
->order($order);
$list = $this->pageQuery($search_model);
return $list;
}
@ -51,7 +55,7 @@ class CourseScheduleService extends BaseAdminService
*/
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';
$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';
$info = $this->model->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray();
return $info;
@ -64,9 +68,29 @@ class CourseScheduleService extends BaseAdminService
*/
public function add(array $data)
{
$res = $this->model->create($data);
return $res->id;
$create = [
'campus_id' => $data['campus_id'],
'venue_id' => $data['venue_id'],
'course_date' => $data['course_date'],
'time_slot' => $data['time_slot'],
'course_id' => $data['course_id'],
'coach_id' => $data['coach_id'],
'participants' => $data['participants'] ? $data['participants'] : [],
'student_ids' => $data['student_ids'] ? $data['student_ids'] : [],
'is_system_add' => $data['is_system_add']
];
$status = $this->model->where([
['course_date', '=', $data['course_date']],
['time_slot', '=', $data['time_slot']],
['campus_id', '=', $data['campus_id']],
['venue_id', '=', $data['venue_id']]
])->find();
if ($status) {
throw new \Exception('该时间段已有课程安排');
}
$res = $this->model->create($create);
return $res->id;
}
/**
@ -77,8 +101,29 @@ class CourseScheduleService extends BaseAdminService
*/
public function edit(int $id, array $data)
{
$create = [
'campus_id' => $data['campus_id'],
'venue_id' => $data['venue_id'],
'course_date' => $data['course_date'],
'time_slot' => $data['time_slot'],
'course_id' => $data['course_id'],
'coach_id' => $data['coach_id'],
'participants' => $data['participants'] ? $data['participants'] : [],
'student_ids' => $data['student_ids'] ? $data['student_ids'] : [],
'is_system_add' => $data['is_system_add']
];
$status = $this->model->where([
['course_date', '=', $data['course_date']],
['time_slot', '=', $data['time_slot']],
['campus_id', '=', $data['campus_id']],
['venue_id', '=', $data['venue_id']],
['id', '<>', $id]
])->find();
$this->model->where([['id', '=', $id]])->update($data);
if ($status) {
throw new \Exception('该时间段已有课程安排');
}
$this->model->where([['id', '=', $id]])->update($create);
return true;
}
@ -104,70 +149,73 @@ class CourseScheduleService extends BaseAdminService
// 获取日期范围,默认为本周
$start_date = $where['start_date'] ?? date('Y-m-d', strtotime('monday this week'));
$end_date = $where['end_date'] ?? date('Y-m-d', strtotime('sunday this week'));
// 校区ID
$campus_id = isset($where['campus_id']) && !empty($where['campus_id']) ? intval($where['campus_id']) : 0;
// 查询条件
$query_condition = [
['course_date', '>=', $start_date],
['course_date', '<=', $end_date]
];
// 如果指定了校区,添加校区筛选条件
if ($campus_id > 0) {
$query_condition[] = ['campus_id', '=', $campus_id];
}
// 查询指定日期范围内的课程安排
$schedules = $this->model->where($query_condition)->select()->toArray();
// 获取所有相关的人员课程安排关系
$schedule_ids = array_column($schedules, 'id');
$person_schedules = [];
if (!empty($schedule_ids)) {
$person_schedules = (new PersonCourseSchedule())->where([
['schedule_id', 'in', $schedule_ids]
])->select()->toArray();
}
// 组织数据结构
$days = [];
$weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
// 获取所有时间段和教室
$time_slots = [];
$classrooms = [];
// 如果没有数据,设置默认的时间段和教室
if (empty($schedules)) {
$time_slots = ['9:00-10:00', '10:00-11:00', '11:00-12:00', '14:00-15:00', '15:00-16:00', '16:00-17:00'];
$classrooms = [1, 2]; // 默认教室ID
//获取校区下的所有教室
$venues = (new VenueService())->getVenueAll($campus_id);
$time_slots = (new VenueService())->getVenueTime($venues);
$classrooms = $venues;
} else {
foreach ($schedules as $schedule) {
if (!in_array($schedule['time_slot'], $time_slots)) {
$time_slots[] = $schedule['time_slot'];
}
// 假设venue_id代表教室
if (!in_array($schedule['venue_id'], $classrooms)) {
$classrooms[] = $schedule['venue_id'];
}
}
}
// 按日期组织数据
$current_date = $start_date;
$day_index = (int)date('N', strtotime($current_date)) - 1; // 获取周几(1-7),转为索引(0-6)
while (strtotime($current_date) <= strtotime($end_date)) {
$day_schedules = array_filter($schedules, function($schedule) use ($current_date) {
$day_schedules = array_filter($schedules, function ($schedule) use ($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)) {
@ -175,7 +223,7 @@ class CourseScheduleService extends BaseAdminService
}
}
}
// 构建每个时间段的数据
$day_time_slots = [];
foreach ($time_slots as $time_slot) {
@ -183,22 +231,22 @@ class CourseScheduleService extends BaseAdminService
'timeRange' => $time_slot,
'color' => '#' . dechex(rand(0x000000, 0xFFFFFF)), // 随机颜色
];
// 查找该时间段的课程
$slot_schedule = array_filter($day_schedules, function($schedule) use ($time_slot) {
$slot_schedule = array_filter($day_schedules, function ($schedule) use ($time_slot) {
return $schedule['time_slot'] == $time_slot;
});
if (!empty($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'];
});
$student_names = array_column($students, 'person_id');
$slot_data['course'] = [
'teacher' => $schedule['coach_id'] ?? '',
'students' => $student_names,
@ -206,74 +254,32 @@ class CourseScheduleService extends BaseAdminService
'hasnumber' => $schedule['available_capacity'] ?? 0,
];
}
$day_time_slots[] = $slot_data;
}
$days[] = [
'date' => $weekdays[$day_index % 7] . ' (' . $current_date . ')',
'timeSlots' => $day_time_slots,
'classrooms' => $day_classrooms,
];
$current_date = date('Y-m-d', strtotime($current_date . ' +1 day'));
$day_index = ($day_index + 1) % 7;
}
return $days;
}
/**
* 自动创建课程安排记录
* @param string $start_date 开始日期
* @param string $end_date 结束日期
* @param int $campus_id 校区ID
* @return array 创建的课程安排记录
* @param $id
* @return CourseSchedule[]|array|\think\Collection
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
private function autoCreateSchedules(string $start_date, string $end_date, int $campus_id): array
{
$schedules = [];
$default_time_slots = ['9:00-10:00', '10:00-11:00', '11:00-12:00', '14:00-15:00', '15:00-16:00', '16:00-17:00'];
$default_venues = [1, 2]; // 默认教室ID
$current_date = $start_date;
// 遍历日期范围
while (strtotime($current_date) <= strtotime($end_date)) {
foreach ($default_venues as $venue_id) {
foreach ($default_time_slots as $time_slot) {
// 创建课程安排记录
$schedule_data = [
'campus_id' => $campus_id,
'venue_id' => $venue_id,
'course_date' => $current_date,
'time_slot' => $time_slot,
'course_id' => 0, // 默认课程ID
'coach_id' => 0, // 默认教练ID
'participants' => json_encode([]), // 空参与者列表
'student_ids' => json_encode([]), // 空学生列表
'available_capacity' => 10, // 默认容量
'status' => 'pending', // 默认状态
'created_by' => 'system', // 系统创建
];
// 插入数据库
$res = $this->model->create($schedule_data);
// 添加到返回结果
$schedule_data['id'] = $res->id;
$schedules[] = $schedule_data;
}
}
$current_date = date('Y-m-d', strtotime($current_date . ' +1 day'));
}
return $schedules;
}
public function getCampusVenue($id)
{
return $this->model->where('availability_status', 1)->where('campus_id',$id)->select();
return $this->model->where('availability_status', 1)->where('campus_id', $id)->select();
}
}

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

@ -11,6 +11,7 @@
namespace app\service\admin\customer_resources;
use app\model\campus_person_role\CampusPersonRole;
use app\model\customer_resource_changes\CustomerResourceChanges;
use app\model\customer_resources\CustomerResources;
use app\model\personnel\Personnel;
@ -47,44 +48,44 @@ class CustomerResourcesService extends BaseAdminService
$field = 'a.*,b.id as shared_id';
$order = 'a.id desc';
$where = [];
if($data['phone_number']){
$where[] = ['a.phone_number','=',$data['phone_number']];
if ($data['phone_number']) {
$where[] = ['a.phone_number', '=', $data['phone_number']];
}
if($data['name']){
$where[] = ['a.name','=',$data['name']];
if ($data['name']) {
$where[] = ['a.name', '=', $data['name']];
}
if($data['age']){
$where[] = ['a.age','=',$data['age']];
if ($data['age']) {
$where[] = ['a.age', '=', $data['age']];
}
if($data['gender']){
$where[] = ['a.gender','=',$data['gender']];
if ($data['gender']) {
$where[] = ['a.gender', '=', $data['gender']];
}
if($data['type'] == 'yjfp'){
$where[] = ['b.shared_by','=',0];
if ($data['type'] == 'yjfp') {
$where[] = ['b.shared_by', '=', 0];
}
if($data['member_label']){
$where[] = ['a.member_label','like',"%".$data['member_label']."%"];
if ($data['member_label']) {
$where[] = ['a.member_label', 'like', "%" . $data['member_label'] . "%"];
}
$search_model = $this->model
->alias("a")
->join(['school_resource_sharing' => 'b'],'a.id = b.resource_id','left')
->join(['school_resource_sharing' => 'b'], 'a.id = b.resource_id', 'left')
->where($where)
->with(['personnel'])->field($field)->order($order);
if (isset($data['created_at'][0]) && isset($data['created_at'][1])) {
$search_model->whereBetweenTime('created_at', $data['created_at'][0]."00:00:00", $data['created_at'][1]."23:59:59");
$search_model->whereBetweenTime('created_at', $data['created_at'][0] . "00:00:00", $data['created_at'][1] . "23:59:59");
}
if (isset($data['updated_at'][0]) && isset($data['updated_at'][1])) {
$search_model->whereBetweenTime('updated_at', $data['updated_at'][0]."00:00:00", $data['updated_at'][1]."23:59:59");
$search_model->whereBetweenTime('updated_at', $data['updated_at'][0] . "00:00:00", $data['updated_at'][1] . "23:59:59");
}
@ -106,15 +107,16 @@ class CustomerResourcesService extends BaseAdminService
$sixSpeed = new SixSpeed();
$data = $sixSpeed->where(['resource_id' => $id])->field("*,distance as distance_tow")->findOrEmpty()->toArray();
$info = $info+$data;
$info = $info + $data;
return $this->makeUp($info);
}
public function makeUp($data){
public function makeUp($data)
{
//会员标签
if(!empty($data['member_label'])){
if (!empty($data['member_label'])) {
$data['member_label_array'] = (new MemberLabelService())->getMemberLabelListByLabelIds($data['member_label']);
}
return $data;
@ -131,7 +133,7 @@ class CustomerResourcesService extends BaseAdminService
$personnel = new Personnel();
$data['consultant'] = $personnel->where(['sys_user_id' => $this->uid])->value("id");
if(!$data['consultant']){
if (!$data['consultant']) {
return fail("操作失败");
}
@ -142,15 +144,15 @@ class CustomerResourcesService extends BaseAdminService
$res = $this->model->create($data);
$role_id = $personnel->alias("a")->join(['school_campus_person_role' => 'b'],'a.id = b.person_id','left')
->where(['a.id' => $data['consultant']])->value('b.role_id');
$role_id = $personnel->alias("a")->join(['school_campus_person_role' => 'b'], 'a.id = b.person_id', 'left')
->where(['a.id' => $data['consultant']])->value('b.role_id');
$resourceSharing->insert([
'resource_id' => $res->id,
'user_id' => $data['consultant'],
'role_id' => $role_id
]);
if($data['purchase_power']){
if ($data['purchase_power']) {
$six_id = $sixSpeed->where(['resource_id' => $res->id])->value("id");
$data['staff_id'] = $data['consultant'];
@ -170,9 +172,9 @@ class CustomerResourcesService extends BaseAdminService
'staff_id' => $data['staff_id'],
'resource_id' => $res->id
];
if($six_id){
if ($six_id) {
$sixSpeed->where(['resource_id' => $res->id])->update($field);
}else{
} else {
$sixSpeed->insert($field);
}
}
@ -218,14 +220,14 @@ class CustomerResourcesService extends BaseAdminService
'create_date' => $data['create_date']
]);
$resources_save = getModifiedFields($res,$data);
$resources_save = getModifiedFields($res, $data);
$customerResourceChanges = new CustomerResourceChanges();
if($resources_save['is_save']){
if ($resources_save['is_save']) {
$customerResourceChanges->insert([
'customer_resource_id' => $id,
'operator_id' => $res['consultant'],
'campus_id' => $data['campus'],
'campus_id' => $data['campus'],
'modified_fields' => $resources_save['modified_fields'],
'old_values' => $resources_save['old_values'],
'new_values' => $resources_save['new_values']
@ -234,13 +236,12 @@ class CustomerResourcesService extends BaseAdminService
$sixSpeed = new SixSpeed();
if($data['purchase_power']){
if ($data['purchase_power']) {
$sixSpeedModificationLog = new SixSpeedModificationLog();
$six_id = $sixSpeed->where(['resource_id' => $id])->value("id");
$data['staff_id'] = $res['consultant'];
$field = [
'purchase_power' => $data['purchase_power'],
'concept_awareness' => $data['concept_awareness'],
@ -256,16 +257,16 @@ class CustomerResourcesService extends BaseAdminService
'staff_id' => $data['staff_id'],
'resource_id' => $id
];
if($six_id){
if ($six_id) {
$six_log = $sixSpeed->where(['resource_id' => $id])->findOrEmpty()->toArray();
$six_save = getModifiedFields($six_log,$field);
$six_save = getModifiedFields($six_log, $field);
if($six_save['is_save']){
if ($six_save['is_save']) {
$sixSpeedModificationLog->insert([
'customer_resource_id' => $id,
'operator_id' => $res['consultant'],
'campus_id' => $data['campus'],
'campus_id' => $data['campus'],
'modified_field' => $six_save['modified_fields'],
'old_value' => $six_save['old_values'],
'new_value' => $six_save['new_values']
@ -273,7 +274,7 @@ class CustomerResourcesService extends BaseAdminService
}
$sixSpeed->where(['resource_id' => $id])->update($field);
}else{
} else {
$sixSpeed->insert($field);
}
}
@ -294,26 +295,29 @@ class CustomerResourcesService extends BaseAdminService
}
public function getPersonnelAll($data){
$personnelModel = new Personnel();
$where = [];
if($data['role_id']){
$where[] = ['b.role_id','=',$data['role_id']];
}
return $personnelModel
->alias("a")
->join(['school_campus_person_role' => 'b'],'a.id = b.person_id','left')
->field("a.*")
->where($where)->select()->toArray();
public function getPersonnelAll($data)
{
$personnelModel = new Personnel();
$where = [];
if ($data['role_id']) {
$where[] = ['b.role_id', '=', $data['role_id']];
}
return $personnelModel
->alias("a")
->join(['school_campus_person_role' => 'b'], 'a.id = b.person_id', 'left')
->field("a.*")
->where($where)->select()->toArray();
}
public function getCampusAll(){
$campusModel = new Campus();
return $campusModel->select()->toArray();
public function getCampusAll()
{
$campusModel = new Campus();
return $campusModel->select()->toArray();
}
public function fp_edit($data){
public function fp_edit($data)
{
$resourceSharing = new ResourceSharing();
$resourceSharing->where(['id' => $data['shared_id']])->update([
'shared_by' => $data['shared_by'],
@ -322,5 +326,42 @@ class CustomerResourcesService extends BaseAdminService
return "分配成功";
}
public function personnelAllByname($name)
{
return $this->model->where('name', 'like', '%' . $name . '%')->select()->toArray();
}
/**
* 获取销售人员
*/
public function getSalesPerson($campus_id)
{
}
/**
* 获取市场人员
*/
public function getMarketPerson($campus_id)
{
}
/**
* 获取教练人员
*/
public function getCoachPerson($campus_id)
{
$coachModel = new CampusPersonRole();
return $coachModel
->alias("a")
->join(['school_personnel' => 'b'], 'a.person_id = b.id', 'left')
->field("a.*,b.name,b.phone")
->where([
['a.campus_id', '=', $campus_id],
['a.role_id', '=', 5]
])->select()->toArray();
}
}

99
niucloud/app/service/admin/venue/VenueService.php

@ -137,34 +137,83 @@ class VenueService extends BaseAdminService
/**
* 获取场地那天的可预约时间
*/
public function getVenueTime($venue_id, $date)
public function getVenueTime($date)
{
$venue_info = $this->model->where('id', '=', $venue_id)->find();
$venue_info['time_range'] = [];
//可用时间范围
if ($venue_info['time_range_type'] === 'range') {
$time_range_start = $venue_info['time_range_start'];
$time_range_end = $venue_info['time_range_end'];
for ($i = $time_range_start; $i <= $time_range_end; $i++) {
$venue_info['time_range'][] = $i;
$timeSlots = [];
foreach ($date as $item) {
// 获取 time_range_type,默认为空
$timeRangeType = $item['time_range_type'] ?? '';
switch ($timeRangeType) {
case 'fixed':
// 固定时间段
if (!empty($item['fixed_time_ranges'])) {
try {
$fixedRanges = json_decode($item['fixed_time_ranges'], true);
foreach ($fixedRanges as $range) {
if (isset($range['start_time'], $range['end_time'])) {
$timeSlots[] = "{$range['start_time']}-{$range['end_time']}";
}
}
} catch (\Exception $e) {
// 记录错误日志
\think\facade\Log::error('解析固定时间段失败: ' . $e->getMessage());
}
}
break;
case 'all':
// 全天可用,但中午12:30-14:00不可用
// 上午:8:00 - 12:30(每小时)
for ($hour = 8; $hour <= 12; $hour++) {
$startTime = sprintf('%02d:%02d', $hour, 0);
$endTime = sprintf('%02d:%02d', $hour + 1, 0);
if ("12:30" === "$startTime") continue;
$timeSlots[] = "$startTime-$endTime";
}
// 下午:14:00 - 21:00(每小时)
for ($hour = 14; $hour < 22; $hour++) {
$startTime = sprintf('%02d:%02d', $hour, 0);
$endTime = sprintf('%02d:%02d', $hour + 1, 0);
$timeSlots[] = "$startTime-$endTime";
}
break;
case 'range':
// 自定义时间范围
$start = $item['time_range_start'] ?? '';
$end = $item['time_range_end'] ?? '';
if ($start && $end) {
$startParts = explode(':', $start);
$endParts = explode(':', $end);
if (count($startParts) === 2 && count($endParts) === 2) {
$startTotalMinutes = intval($startParts[0]) * 60 + intval($startParts[1]);
$endTotalMinutes = intval($endParts[0]) * 60 + intval($endParts[1]);
if ($startTotalMinutes < $endTotalMinutes) {
for ($minutes = $startTotalMinutes; $minutes < $endTotalMinutes; $minutes += 60) {
$startHour = intdiv($minutes, 60);
$startMinute = $minutes % 60;
$endHour = intdiv($minutes + 60, 60);
$endMinute = ($minutes + 60) % 60;
$startTime = sprintf('%02d:%02d', $startHour, $startMinute);
$endTime = sprintf('%02d:%02d', $endHour, $endMinute);
$timeSlots[] = "$startTime-$endTime";
}
}
}
}
break;
default:
// 不支持的类型返回空数组
break;
}
}
// 固定使用时间
if ($venue_info['time_range_type'] === 'fixed') {
$venue_info['time_range'] = json_decode($venue_info['fixed_time_ranges'], true);
for ($i = 0; $i < count($venue_info['time_range']); $i++) {
$venue_info['time_range'][$i]['start'] = strtotime($date . ' ' . $venue_info['time_range'][$i]['start_time']);
$venue_info['time_range'][$i]['end'] = strtotime($date . ' ' . $venue_info['time_range'][$i]['end_time']);
}
}
// 全天可用从早上8点开始到晚上22点结束
if ($venue_info['time_range_type'] === 'all') {
for ($i = 8; $i <= 22; $i++) {
$venue_info['time_range'][] = $i;
}
}
return $venue_info;
// $timeSlots去重
return array_unique($timeSlots);
}
}

6
niucloud/app/validate/course_schedule/CourseSchedule.php

@ -25,11 +25,7 @@ class CourseSchedule extends BaseValidate
'course_date' => 'require',
'time_slot' => 'require',
'course_id' => 'require',
'coach_id' => 'require',
'participants' => 'require',
'student_ids' => 'require',
'available_capacity' => 'require',
'status' => 'require',
'coach_id' => 'require'
];
protected $message = [

Loading…
Cancel
Save