diff --git a/admin/src/app/views/venue/components/venue-edit.vue b/admin/src/app/views/venue/components/venue-edit.vue
index 35c47b61..9ee0105f 100644
--- a/admin/src/app/views/venue/components/venue-edit.vue
+++ b/admin/src/app/views/venue/components/venue-edit.vue
@@ -81,8 +81,10 @@
+
@@ -92,8 +94,8 @@
class="flex-1"
v-model="formData.time_range_start"
clearable
- :start="getStartTime(0)"
- :end="getEndTime(0)"
+ start="08:30"
+ end="22:30"
step="00:10"
value-format="HH:mm"
placeholder="开始时间"
@@ -112,8 +114,11 @@
+
+
@@ -145,18 +150,33 @@
新增
删除
+
+
+
+
+
@@ -199,6 +219,8 @@ const initialFormData = {
capacity: '',
availability_status: '1',
time_range_type: '',
+ time_range_start: '',
+ time_range_end: '',
fixed_time_ranges: [
{
start_time: '',
@@ -212,7 +234,7 @@ const formRef = ref()
// 表单验证规则
const formRules = computed(() => {
- return {
+ const rules: any = {
campus_id: [
{ required: true, message: t('campusIdPlaceholder'), trigger: 'blur' },
],
@@ -246,6 +268,69 @@ const formRules = computed(() => {
},
],
}
+
+ // 根据 time_range_type 动态添加验证规则
+ if (formData.time_range_type === 'range') {
+ rules.time_range_start = [
+ { required: true, message: '请选择开始时间', trigger: 'blur' },
+ ]
+ rules.time_range_end = [
+ { required: true, message: '请选择结束时间', trigger: 'blur' },
+ {
+ validator: (rule: any, value: string, callback: any) => {
+ if (value && formData.time_range_start && value <= formData.time_range_start) {
+ callback(new Error('结束时间必须大于开始时间'))
+ } else {
+ callback()
+ }
+ },
+ trigger: 'blur'
+ }
+ ]
+ } else if (formData.time_range_type === 'fixed') {
+ rules.fixed_time_ranges = [
+ {
+ validator: (rule: any, value: any[], callback: any) => {
+ if (!Array.isArray(value) || value.length === 0) {
+ callback(new Error('请至少添加一个时间段'))
+ return
+ }
+
+ for (let i = 0; i < value.length; i++) {
+ const range = value[i]
+ if (!range.start_time || !range.end_time) {
+ callback(new Error(`第${i + 1}个时间段不完整`))
+ return
+ }
+ if (range.start_time >= range.end_time) {
+ callback(new Error(`第${i + 1}个时间段的结束时间必须大于开始时间`))
+ return
+ }
+ }
+
+ // 检查时间段是否有重叠
+ for (let i = 0; i < value.length; i++) {
+ for (let j = i + 1; j < value.length; j++) {
+ const range1 = value[i]
+ const range2 = value[j]
+ if (
+ (range1.start_time < range2.end_time && range1.end_time > range2.start_time) ||
+ (range2.start_time < range1.end_time && range2.end_time > range1.start_time)
+ ) {
+ callback(new Error(`第${i + 1}个和第${j + 1}个时间段存在重叠`))
+ return
+ }
+ }
+ }
+
+ callback()
+ },
+ trigger: 'blur'
+ }
+ ]
+ }
+
+ return rules
})
const emit = defineEmits(['complete'])
@@ -301,6 +386,34 @@ const removeTimeRange = (index: number) => {
formData.fixed_time_ranges.splice(index, 1)
}
+// 监听时间段类型变化,重置相关字段
+watch(
+ () => formData.time_range_type,
+ (newValue, oldValue) => {
+ if (newValue !== oldValue && oldValue !== '') {
+ // 清空所有时间相关字段
+ formData.time_range_start = ''
+ formData.time_range_end = ''
+ formData.fixed_time_ranges = [
+ {
+ start_time: '',
+ end_time: '',
+ },
+ ]
+
+ // 清除表单验证错误
+ if (formRef.value) {
+ formRef.value.clearValidate([
+ 'time_range_start',
+ 'time_range_end',
+ 'fixed_time_ranges'
+ ])
+ }
+ }
+ }
+)
+
+// 监听固定时间段变化,确保时间逻辑正确
watch(
() => formData.fixed_time_ranges,
(newValue) => {
@@ -329,7 +442,31 @@ const confirm = async (formEl: FormInstance | undefined) => {
if (valid) {
loading.value = true
- let data = formData
+ // 根据不同的 time_range_type 处理数据
+ let data = { ...formData }
+
+ if (data.time_range_type === 'all') {
+ // 全天可用时,清空时间相关字段
+ data.time_range_start = null
+ data.time_range_end = null
+ data.fixed_time_ranges = null
+ } else if (data.time_range_type === 'range') {
+ // 单时间段时,清空固定时间段字段
+ data.fixed_time_ranges = null
+ } else if (data.time_range_type === 'fixed') {
+ // 固定时间段时,清空单时间段字段,并处理 JSON 格式
+ data.time_range_start = null
+ data.time_range_end = null
+
+ // 过滤掉不完整的时间段
+ data.fixed_time_ranges = data.fixed_time_ranges
+ .filter((range: any) => range.start_time && range.end_time)
+
+ // 如果是字符串格式,需要转换为 JSON 字符串
+ if (Array.isArray(data.fixed_time_ranges)) {
+ data.fixed_time_ranges = JSON.stringify(data.fixed_time_ranges)
+ }
+ }
save(data)
.then((res) => {
@@ -379,14 +516,38 @@ const setCampusIdList = async () => {
setCampusIdList()
const setFormData = async (row: any = null) => {
Object.assign(formData, initialFormData)
- formData.fixed_time_ranges = initialFormData.fixed_time_ranges
+ formData.fixed_time_ranges = [...initialFormData.fixed_time_ranges]
loading.value = true
if (row) {
const data = await (await getVenueInfo(row.id)).data
- if (data)
+ if (data) {
Object.keys(formData).forEach((key: string) => {
- if (data[key] != undefined) formData[key] = data[key]
+ if (data[key] !== undefined) {
+ if (key === 'fixed_time_ranges' && data[key]) {
+ // 处理 JSON 字段解析
+ try {
+ if (typeof data[key] === 'string') {
+ formData[key] = JSON.parse(data[key])
+ } else if (Array.isArray(data[key])) {
+ formData[key] = data[key]
+ } else {
+ formData[key] = [...initialFormData.fixed_time_ranges]
+ }
+
+ // 确保至少有一个时间段用于编辑
+ if (formData[key].length === 0) {
+ formData[key] = [...initialFormData.fixed_time_ranges]
+ }
+ } catch (error) {
+ console.error('解析 fixed_time_ranges 失败:', error)
+ formData[key] = [...initialFormData.fixed_time_ranges]
+ }
+ } else {
+ formData[key] = data[key]
+ }
+ }
})
+ }
}
loading.value = false
}
diff --git a/niucloud/app/Request.php b/niucloud/app/Request.php
index 0ebb981d..75a39869 100644
--- a/niucloud/app/Request.php
+++ b/niucloud/app/Request.php
@@ -3,7 +3,7 @@
namespace app;
use app\dict\common\ChannelDict;
-use app\model\campus_person_role\CampusPersonRole;
+use app\model\school\CampusPersonRole;
/**
* Class Request
diff --git a/niucloud/app/api/controller/apiController/ScheduleOptions.php b/niucloud/app/api/controller/apiController/ScheduleOptions.php
new file mode 100644
index 00000000..6ea68f70
--- /dev/null
+++ b/niucloud/app/api/controller/apiController/ScheduleOptions.php
@@ -0,0 +1,165 @@
+campus_id ?: 0;
+
+ $data = $this->request->params([
+ ["campus_id", $campus_id], // 校区ID,优先使用传入参数,否则使用登录用户的校区
+ ["include_all", false] // 是否包含全部数据(忽略校区过滤)
+ ]);
+
+ $result = (new ScheduleOptionsService())->getAllOptions($data);
+ if (!$result['code']) {
+ return fail($result['msg']);
+ }
+
+ return success('获取成功', $result['data']);
+ } catch (\Exception $e) {
+ return fail('获取选项列表失败:' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 获取课程列表
+ * @param Request $request
+ * @return \think\Response
+ */
+ public function getCourseList(Request $request)
+ {
+ try {
+ $campus_id = $this->campus_id ?: 0;
+
+ $data = $this->request->params([
+ ["campus_id", $campus_id],
+ ["keyword", ""],
+ ["course_type", ""],
+ ["status", 1],
+ ["include_all", false]
+ ]);
+
+ $result = (new ScheduleOptionsService())->getCourseList($data);
+ if (!$result['code']) {
+ return fail($result['msg']);
+ }
+
+ return success('获取成功', $result['data']);
+ } catch (\Exception $e) {
+ return fail('获取课程列表失败:' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 获取班级列表
+ * @param Request $request
+ * @return \think\Response
+ */
+ public function getClassList(Request $request)
+ {
+ try {
+ $campus_id = $this->campus_id ?: 0;
+
+ $data = $this->request->params([
+ ["campus_id", $campus_id],
+ ["keyword", ""],
+ ["class_type", ""],
+ ["status", 1],
+ ["include_all", false]
+ ]);
+
+ $result = (new ScheduleOptionsService())->getClassList($data);
+ if (!$result['code']) {
+ return fail($result['msg']);
+ }
+
+ return success('获取成功', $result['data']);
+ } catch (\Exception $e) {
+ return fail('获取班级列表失败:' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 获取教练列表
+ * @param Request $request
+ * @return \think\Response
+ */
+ public function getCoachList(Request $request)
+ {
+ try {
+ $campus_id = $this->campus_id ?: 0;
+
+ $data = $this->request->params([
+ ["campus_id", $campus_id],
+ ["keyword", ""],
+ ["status", 1],
+ ["include_all", false]
+ ]);
+
+ $result = (new ScheduleOptionsService())->getCoachList($data);
+ if (!$result['code']) {
+ return fail($result['msg']);
+ }
+
+ return success('获取成功', $result['data']);
+ } catch (\Exception $e) {
+ return fail('获取教练列表失败:' . $e->getMessage());
+ }
+ }
+
+ /**
+ * 获取场地列表
+ * @param Request $request
+ * @return \think\Response
+ */
+ public function getVenueList(Request $request)
+ {
+ try {
+ $campus_id = $this->campus_id ?: 0;
+
+ $data = $this->request->params([
+ ["campus_id", $campus_id],
+ ["keyword", ""],
+ ["availability_status", 1],
+ ["include_all", false]
+ ]);
+
+ $result = (new ScheduleOptionsService())->getVenueList($data);
+ if (!$result['code']) {
+ return fail($result['msg']);
+ }
+
+ return success('获取成功', $result['data']);
+ } catch (\Exception $e) {
+ return fail('获取场地列表失败:' . $e->getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/niucloud/app/api/route/route.php b/niucloud/app/api/route/route.php
index bb18b576..93c63624 100644
--- a/niucloud/app/api/route/route.php
+++ b/niucloud/app/api/route/route.php
@@ -345,7 +345,19 @@ Route::group(function () {
//员工端-获取场地时间选项
Route::get('courseSchedule/venueTimeOptions', 'apiController.CourseSchedule/getVenueTimeOptions');
- // 添加课程安排页面专用接口
+ // 课程安排统一选项接口(新增-支持校区过滤)
+ //获取所有排课选项(统一接口-支持校区过滤)
+ Route::get('schedule/options/all', 'apiController.ScheduleOptions/getAllOptions');
+ //获取课程列表(支持校区过滤)
+ Route::get('schedule/options/courses', 'apiController.ScheduleOptions/getCourseList');
+ //获取班级列表(支持校区过滤)
+ Route::get('schedule/options/classes', 'apiController.ScheduleOptions/getClassList');
+ //获取教练列表(支持校区过滤)
+ Route::get('schedule/options/coaches', 'apiController.ScheduleOptions/getCoachList');
+ //获取场地列表(支持校区过滤)
+ Route::get('schedule/options/venues', 'apiController.ScheduleOptions/getVenueList');
+
+ // 添加课程安排页面专用接口(保留兼容性)
//获取课程列表(用于添加课程安排)
Route::get('course/list', 'apiController.Course/getCourseList');
//获取班级列表(用于添加课程安排)
diff --git a/niucloud/app/model/school/CampusPersonRole.php b/niucloud/app/model/school/CampusPersonRole.php
new file mode 100644
index 00000000..1fc572a8
--- /dev/null
+++ b/niucloud/app/model/school/CampusPersonRole.php
@@ -0,0 +1,20 @@
+prefix = config('database.connections.mysql.prefix');
}
/**
@@ -51,6 +52,7 @@ class CourseScheduleService extends BaseApiService
->leftJoin($this->prefix . 'campus cap', 'cs.campus_id = cap.id')
->leftJoin($this->prefix . 'personnel coach', 'cs.coach_id = coach.id')
->leftJoin($this->prefix . 'personnel edu', 'cs.education_id = edu.id')
+ ->leftJoin($this->prefix . 'class cla', 'cs.class_id = cla.id')
->where($where)
->where('cs.deleted_at', 0);
@@ -70,7 +72,8 @@ class CourseScheduleService extends BaseApiService
'cap.campus_name',
'coach.name as coach_name',
'coach.head_img as coach_avatar',
- 'edu.name as education_name'
+ 'edu.name as education_name',
+ 'cla.class_name',
])
->order('cs.course_date DESC, cs.time_slot ASC')
->limit($offset, $limit)
@@ -703,7 +706,10 @@ class CourseScheduleService extends BaseApiService
'deleted_at' => 0
];
- // 班级信息暂不存储在课程安排表中,根据实际需求可以通过关联表处理
+ // 如果传入了班级ID,则添加到插入数据中
+ if (!empty($data['class_id'])) {
+ $insertData['class_id'] = $data['class_id'];
+ }
// 如果有备注,则添加
if (!empty($data['remarks'])) {
@@ -1015,6 +1021,9 @@ class CourseScheduleService extends BaseApiService
if (isset($data['course_id'])) {
$updateData['course_id'] = $data['course_id'];
}
+ if (isset($data['class_id'])) {
+ $updateData['class_id'] = $data['class_id'];
+ }
if (isset($data['education_id'])) {
$updateData['education_id'] = $data['education_id'];
}
diff --git a/niucloud/app/service/api/apiService/ScheduleOptionsService.php b/niucloud/app/service/api/apiService/ScheduleOptionsService.php
new file mode 100644
index 00000000..2d3fa1dd
--- /dev/null
+++ b/niucloud/app/service/api/apiService/ScheduleOptionsService.php
@@ -0,0 +1,291 @@
+getCourseListData($campus_id, $include_all);
+ $classes = $this->getClassListData($campus_id, $include_all);
+ $coaches = $this->getCoachListData($campus_id, $include_all);
+ $venues = $this->getVenueListData($campus_id, $include_all);
+
+ return [
+ 'code' => 1,
+ 'data' => [
+ 'courses' => $courses,
+ 'classes' => $classes,
+ 'coaches' => $coaches,
+ 'venues' => $venues,
+ 'campus_info' => [
+ 'campus_id' => $campus_id,
+ 'include_all' => $include_all
+ ]
+ ]
+ ];
+ } catch (\Exception $e) {
+ return [
+ 'code' => 0,
+ 'msg' => '获取选项列表失败:' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * 获取课程列表
+ * @param array $data
+ * @return array
+ */
+ public function getCourseList(array $data)
+ {
+ try {
+ $campus_id = $data['campus_id'] ?? 0;
+ $include_all = $data['include_all'] ?? false;
+
+ $courses = $this->getCourseListData($campus_id, $include_all, $data);
+
+ return [
+ 'code' => 1,
+ 'data' => $courses
+ ];
+ } catch (\Exception $e) {
+ return [
+ 'code' => 0,
+ 'msg' => '获取课程列表失败:' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * 获取班级列表
+ * @param array $data
+ * @return array
+ */
+ public function getClassList(array $data)
+ {
+ try {
+ $campus_id = $data['campus_id'] ?? 0;
+ $include_all = $data['include_all'] ?? false;
+
+ $classes = $this->getClassListData($campus_id, $include_all, $data);
+
+ return [
+ 'code' => 1,
+ 'data' => $classes
+ ];
+ } catch (\Exception $e) {
+ return [
+ 'code' => 0,
+ 'msg' => '获取班级列表失败:' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * 获取教练列表
+ * @param array $data
+ * @return array
+ */
+ public function getCoachList(array $data)
+ {
+ try {
+ $campus_id = $data['campus_id'] ?? 0;
+ $include_all = $data['include_all'] ?? false;
+
+ $coaches = $this->getCoachListData($campus_id, $include_all, $data);
+
+ return [
+ 'code' => 1,
+ 'data' => $coaches
+ ];
+ } catch (\Exception $e) {
+ return [
+ 'code' => 0,
+ 'msg' => '获取教练列表失败:' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * 获取场地列表
+ * @param array $data
+ * @return array
+ */
+ public function getVenueList(array $data)
+ {
+ try {
+ $campus_id = $data['campus_id'] ?? 0;
+ $include_all = $data['include_all'] ?? false;
+
+ $venues = $this->getVenueListData($campus_id, $include_all, $data);
+
+ return [
+ 'code' => 1,
+ 'data' => $venues
+ ];
+ } catch (\Exception $e) {
+ return [
+ 'code' => 0,
+ 'msg' => '获取场地列表失败:' . $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * 获取课程数据
+ * @param int $campus_id
+ * @param bool $include_all
+ * @param array $filters
+ * @return array
+ */
+ private function getCourseListData(int $campus_id, bool $include_all, array $filters = [])
+ {
+ $query = Course::where('deleted_at', 0)
+ ->where('status', $filters['status'] ?? 1)
+ ->field('id,course_name,course_type,duration,session_count,price,status');
+
+ // 如果有关键词搜索
+ if (!empty($filters['keyword'])) {
+ $query->where('course_name', 'like', '%' . $filters['keyword'] . '%');
+ }
+
+ // 如果有课程类型筛选
+ if (!empty($filters['course_type'])) {
+ $query->where('course_type', $filters['course_type']);
+ }
+
+ // TODO: 课程表暂时没有campus_id字段,后续需要添加
+ // 课程通常是全局的,不按校区过滤,但可以通过其他关联表来实现校区过滤
+
+ $courses = $query->order('id desc')->select()->toArray();
+
+ return $courses;
+ }
+
+ /**
+ * 获取班级数据
+ * @param int $campus_id
+ * @param bool $include_all
+ * @param array $filters
+ * @return array
+ */
+ private function getClassListData(int $campus_id, bool $include_all, array $filters = [])
+ {
+ $query = SchoolClass::where('deleted_at', 0)
+ ->where('status', $filters['status'] ?? 1)
+ ->field('id,campus_id,campus_name,class_name,head_coach,age_group,class_type,assistant_coach,status,sort_order');
+
+ // 校区过滤:如果当前登录人有校区且不是获取全部数据,则只获取该校区的数据
+ if (!$include_all && $campus_id > 0) {
+ $query->where('campus_id', $campus_id);
+ }
+
+ // 如果有关键词搜索
+ if (!empty($filters['keyword'])) {
+ $query->where('class_name', 'like', '%' . $filters['keyword'] . '%');
+ }
+
+ // 如果有班级类型筛选
+ if (!empty($filters['class_type'])) {
+ $query->where('class_type', $filters['class_type']);
+ }
+
+ $classes = $query->order('sort_order asc, id desc')->select()->toArray();
+
+ return $classes;
+ }
+
+ /**
+ * 获取教练数据
+ * @param int $campus_id
+ * @param bool $include_all
+ * @param array $filters
+ * @return array
+ */
+ private function getCoachListData(int $campus_id, bool $include_all, array $filters = [])
+ {
+ // 通过角色关联表获取教练
+ $query = SchoolPersonnel::alias('p')
+ ->where('p.deleted_at', 0)
+ ->where('p.status', $filters['status'] ?? 1)
+ ->field('p.id,p.name,p.head_img,p.gender,p.phone,p.email,p.account_type,p.status');
+
+ // 校区过滤:通过角色关联表过滤
+ if (!$include_all && $campus_id > 0) {
+ $query->join('school_campus_person_role r', 'p.id = r.person_id')
+ ->where('r.campus_id', $campus_id)
+ ->where('r.deleted_at', 0);
+ }
+
+ // 获取教练类型的人员(teacher类型的人员可以作为教练)
+ $query->where('p.account_type', 'like', '%teacher%');
+
+ // 如果有关键词搜索
+ if (!empty($filters['keyword'])) {
+ $query->where('p.name', 'like', '%' . $filters['keyword'] . '%');
+ }
+
+ $coaches = $query->order('p.id desc')->select()->toArray();
+
+ return $coaches;
+ }
+
+ /**
+ * 获取场地数据
+ * @param int $campus_id
+ * @param bool $include_all
+ * @param array $filters
+ * @return array
+ */
+ private function getVenueListData(int $campus_id, bool $include_all, array $filters = [])
+ {
+ $query = SchoolVenue::where('deleted_at', 0)
+ ->where('availability_status', $filters['availability_status'] ?? 1)
+ ->field('id,campus_id,venue_name,capacity,availability_status,time_range_type,time_range_start,time_range_end');
+
+ // 校区过滤:如果当前登录人有校区且不是获取全部数据,则只获取该校区的数据
+ if (!$include_all && $campus_id > 0) {
+ $query->where('campus_id', $campus_id);
+ }
+
+ // 如果有关键词搜索
+ if (!empty($filters['keyword'])) {
+ $query->where('venue_name', 'like', '%' . $filters['keyword'] . '%');
+ }
+
+ $venues = $query->order('id desc')->select()->toArray();
+
+ return $venues;
+ }
+}
\ No newline at end of file
diff --git a/uniapp/api/apiRoute.js b/uniapp/api/apiRoute.js
index ab77fae2..3382354c 100644
--- a/uniapp/api/apiRoute.js
+++ b/uniapp/api/apiRoute.js
@@ -1209,29 +1209,37 @@ export default {
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----添加课程安排页面专用接口-----↓↓↓↓↓↓↓↓↓↓↓↓
+
+ // 获取所有排课选项(统一接口-支持校区过滤)
+ async getAllScheduleOptions(data = {}) {
+ const token = uni.getStorageSync('token')
+ const apiPath = token ? '/schedule/options/all' : '/test/schedule/options/all'
+ return await http.get(apiPath, data)
+ },
+
// 获取课程列表(用于添加课程安排)
async getCourseListForSchedule(data = {}) {
// 检查是否有token,如果没有则使用测试接口
const token = uni.getStorageSync('token')
- const apiPath = token ? '/course/list' : '/test/course/list'
+ const apiPath = token ? '/schedule/options/courses' : '/test/course/list'
return await http.get(apiPath, data)
},
// 获取班级列表(用于添加课程安排)
async getClassListForSchedule(data = {}) {
const token = uni.getStorageSync('token')
- const apiPath = token ? '/class/list' : '/test/class/list'
+ const apiPath = token ? '/schedule/options/classes' : '/test/class/list'
return await http.get(apiPath, data)
},
// 获取教练列表(用于添加课程安排)
async getCoachListForSchedule(data = {}) {
const token = uni.getStorageSync('token')
- const apiPath = token ? '/coach/list' : '/test/coach/list'
+ const apiPath = token ? '/schedule/options/coaches' : '/test/coach/list'
return await http.get(apiPath, data)
},
// 获取场地列表(用于添加课程安排 - 新开发的通用接口)
async getVenueListForSchedule(data = {}) {
const token = uni.getStorageSync('token')
- const apiPath = token ? '/venue/list' : '/test/venue/list'
+ const apiPath = token ? '/schedule/options/venues' : '/test/venue/list'
return await http.get(apiPath, data)
},
// 获取场地可用时间段(新开发的通用接口)
diff --git a/uniapp/components/schedule/ScheduleDetail.vue b/uniapp/components/schedule/ScheduleDetail.vue
index 5fa535be..c825ebf0 100644
--- a/uniapp/components/schedule/ScheduleDetail.vue
+++ b/uniapp/components/schedule/ScheduleDetail.vue
@@ -343,7 +343,7 @@
try {
// 调用真实API获取课程安排详情和学员信息
- const res = await api.courseScheduleDetail({
+ const res = await api.getCourseScheduleInfo({
schedule_id: this.scheduleId
});
@@ -351,18 +351,29 @@
// 处理课程安排基本信息
const data = res.data;
- // 合并正式学员和等待位学员数据
- const allStudents = [
- ...(data.formal_students || []),
- ...(data.waiting_students || [])
- ];
-
+ // 使用新的API数据结构,data直接包含所有信息
this.scheduleInfo = {
- ...data.schedule_info,
- // 确保包含教练姓名
- coach_name: data.schedule_info.coach_name || '未分配',
- // 合并所有学员数据并添加状态文本和必要字段
- students: allStudents.map(student => ({
+ // 基本课程信息
+ id: data.id,
+ course_name: data.course_name || '未命名课程',
+ course_date: data.course_date,
+ time_slot: data.time_slot,
+ venue_name: data.venue_name || '未分配',
+ campus_name: data.campus_name || '',
+ // 教练信息
+ coach_name: data.coach_name || '未分配',
+ coach_avatar: data.coach_avatar || '',
+ // 课程状态
+ status: data.status,
+ status_text: data.status_text || '待定',
+ // 班级信息
+ class_info: data.class_info || null,
+ // 时间信息
+ time_info: data.time_info || null,
+ // 课程时长
+ course_duration: data.time_info?.duration || data.course_duration || 60,
+ // 学员数据
+ students: (data.students || []).map(student => ({
...student,
status_text: this.getStatusText(student.status || 0),
// 确保包含课程进度数据
@@ -385,7 +396,11 @@
// 确保包含剩余课时和到期时间
remainingHours: student.remainingHours || 0,
expiryDate: student.expiryDate || ''
- }))
+ })),
+ // 其他信息
+ available_capacity: data.available_capacity || 0,
+ enrolled_count: data.enrolled_count || 0,
+ remaining_capacity: data.remaining_capacity || 0
};
console.log('课程安排详情加载成功:', this.scheduleInfo);
diff --git a/uniapp/mock/scheduleOptions.js b/uniapp/mock/scheduleOptions.js
new file mode 100644
index 00000000..4cb78984
--- /dev/null
+++ b/uniapp/mock/scheduleOptions.js
@@ -0,0 +1,83 @@
+/**
+ * 排课选项Mock数据
+ * 提供与API响应结构一致的测试数据
+ */
+
+// 课程Mock数据
+const mockCourses = [
+ { id: 1, course_name: "基础篮球课", course_type: "1", duration: 1, session_count: 1, price: "100.00", status: 1 },
+ { id: 2, course_name: "中级篮球课", course_type: "2", duration: 1, session_count: 1, price: "150.00", status: 1 },
+ { id: 3, course_name: "高级篮球课", course_type: "3", duration: 1, session_count: 1, price: "200.00", status: 1 },
+ { id: 4, course_name: "私教课程", course_type: "3", duration: 1, session_count: 1, price: "300.00", status: 1 }
+];
+
+// 班级Mock数据
+const mockClasses = [
+ { id: 1, campus_id: 1, campus_name: "测试校区", class_name: "幼儿班", head_coach: "张教练", age_group: "3-5", class_type: "1", assistant_coach: "李助教", status: 1, sort_order: 1 },
+ { id: 2, campus_id: 1, campus_name: "测试校区", class_name: "少儿班", head_coach: "王教练", age_group: "6-8", class_type: "2", assistant_coach: "赵助教", status: 1, sort_order: 2 },
+ { id: 3, campus_id: 1, campus_name: "测试校区", class_name: "青少年班", head_coach: "刘教练", age_group: "9-12", class_type: "3", assistant_coach: "钱助教", status: 1, sort_order: 3 }
+];
+
+// 教练Mock数据
+const mockCoaches = [
+ { id: 1, name: "张教练", head_img: "", gender: 1, phone: "13800001001", email: "zhang@test.com", account_type: "teacher", status: 1 },
+ { id: 2, name: "王教练", head_img: "", gender: 0, phone: "13800001002", email: "wang@test.com", account_type: "teacher", status: 1 },
+ { id: 3, name: "刘教练", head_img: "", gender: 1, phone: "13800001003", email: "liu@test.com", account_type: "teacher", status: 1 },
+ { id: 4, name: "李助教", head_img: "", gender: 1, phone: "13800001004", email: "li@test.com", account_type: "teacher", status: 1 }
+];
+
+// 场地Mock数据
+const mockVenues = [
+ { id: 1, campus_id: 1, venue_name: "一号篮球场", capacity: 20, availability_status: 1, time_range_type: "all", time_range_start: null, time_range_end: null },
+ { id: 2, campus_id: 1, venue_name: "二号篮球场", capacity: 15, availability_status: 1, time_range_type: "range", time_range_start: "08:00", time_range_end: "18:00" },
+ { id: 3, campus_id: 1, venue_name: "多功能厅", capacity: 30, availability_status: 1, time_range_type: "fixed", time_range_start: null, time_range_end: null }
+];
+
+// 统一选项Mock响应
+export const mockAllScheduleOptions = {
+ code: 1,
+ msg: "获取成功",
+ data: {
+ courses: mockCourses,
+ classes: mockClasses,
+ coaches: mockCoaches,
+ venues: mockVenues,
+ campus_info: {
+ campus_id: 1,
+ include_all: false
+ }
+ }
+};
+
+// 单独选项Mock响应
+export const mockCourseList = {
+ code: 1,
+ msg: "获取成功",
+ data: mockCourses
+};
+
+export const mockClassList = {
+ code: 1,
+ msg: "获取成功",
+ data: mockClasses
+};
+
+export const mockCoachList = {
+ code: 1,
+ msg: "获取成功",
+ data: mockCoaches
+};
+
+export const mockVenueList = {
+ code: 1,
+ msg: "获取成功",
+ data: mockVenues
+};
+
+export default {
+ mockAllScheduleOptions,
+ mockCourseList,
+ mockClassList,
+ mockCoachList,
+ mockVenueList
+};
\ No newline at end of file
diff --git a/uniapp/pages-coach/coach/schedule/add_schedule.vue b/uniapp/pages-coach/coach/schedule/add_schedule.vue
index 352118dc..cf4286cb 100644
--- a/uniapp/pages-coach/coach/schedule/add_schedule.vue
+++ b/uniapp/pages-coach/coach/schedule/add_schedule.vue
@@ -220,32 +220,27 @@ export default {
});
try {
- // 并行加载所有选项数据
- const [courseRes, classRes, coachRes, venueRes] = await Promise.all([
- api.getCourseListForSchedule(),
- api.getClassListForSchedule(),
- api.getCoachListForSchedule(),
- api.getVenueListForSchedule()
- ]);
+ // 使用统一接口获取所有选项数据(自动根据登录用户校区过滤)
+ const res = await api.getAllScheduleOptions();
- // 设置课程选项
- if (courseRes.code === 1) {
- this.courseOptions = courseRes.data || [];
- }
-
- // 设置班级选项
- if (classRes.code === 1) {
- this.classOptions = classRes.data || [];
- }
-
- // 设置教练选项
- if (coachRes.code === 1) {
- this.coachOptions = coachRes.data || [];
- }
-
- // 设置场地选项
- if (venueRes.code === 1) {
- this.venueOptions = venueRes.data || [];
+ if (res.code === 1) {
+ const data = res.data || {};
+
+ // 设置各种选项
+ this.courseOptions = data.courses || [];
+ this.classOptions = data.classes || [];
+ this.coachOptions = data.coaches || [];
+ this.venueOptions = data.venues || [];
+
+ console.log('加载的数据:', {
+ courses: this.courseOptions.length,
+ classes: this.classOptions.length,
+ coaches: this.coachOptions.length,
+ venues: this.venueOptions.length,
+ campus_info: data.campus_info
+ });
+ } else {
+ throw new Error(res.msg || '获取选项数据失败');
}
// 如果有预填充时间段,设置选中的时间段
@@ -253,13 +248,6 @@ export default {
this.formData.time_slot = this.prefillTimeSlot;
}
- console.log('加载的数据:', {
- courses: this.courseOptions.length,
- classes: this.classOptions.length,
- coaches: this.coachOptions.length,
- venues: this.venueOptions.length
- });
-
// 检查是否有必要的数据
if (this.courseOptions.length === 0) {
uni.showToast({
diff --git a/uniapp/pages-coach/coach/schedule/schedule_table.vue b/uniapp/pages-coach/coach/schedule/schedule_table.vue
index 25d0e9a9..07d232fd 100644
--- a/uniapp/pages-coach/coach/schedule/schedule_table.vue
+++ b/uniapp/pages-coach/coach/schedule/schedule_table.vue
@@ -185,13 +185,42 @@
course.type === 'private' ? 'course-private' : '',
course.type === 'activity' ? 'course-activity' : ''
]"
+ :style="{
+ minHeight: getCourseMinHeight(course),
+ zIndex: 5
+ }"
@click.stop="viewScheduleDetail(course.id)"
>
- {{ course.courseName }}
- {{ course.students }}
- {{ course.teacher }}
- {{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}
+
+ {{ course.class_name || course.courseName }}
+
+
+ 📍 {{ course.campus_name }}
+
+
+
+ ⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
+
+
+
+
+ 👨🏫 {{ course.teacher }}
+ 🏠 {{ course.venue }}
+
+
+
{{ course.status }}
+
+
+
+ 学员列表:
+
+ {{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
+
+
+
+ {{ course.students }}
+
@@ -221,13 +250,42 @@
course.type === 'private' ? 'course-private' : '',
course.type === 'activity' ? 'course-activity' : ''
]"
+ :style="{
+ minHeight: getCourseMinHeight(course),
+ zIndex: 5
+ }"
@click.stop="viewScheduleDetail(course.id)"
>
- {{ course.courseName }}
- {{ course.time }}
- {{ course.students }}
- {{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}
+
+ {{ course.class_name || course.courseName }}
+
+
+ 📍 {{ course.campus_name }}
+
+
+
+ ⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
+
+
+
+
+ 👨🏫 {{ course.teacher }}
+ 🏠 {{ course.venue }}
+
+
+
{{ course.status }}
+
+
+
+ 学员列表:
+
+ {{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
+
+
+
+ {{ course.students }}
+
@@ -257,13 +315,42 @@
course.type === 'private' ? 'course-private' : '',
course.type === 'activity' ? 'course-activity' : ''
]"
+ :style="{
+ minHeight: getCourseMinHeight(course),
+ zIndex: 5
+ }"
@click.stop="viewScheduleDetail(course.id)"
>
- {{ course.courseName }}
- {{ course.time }}
- {{ course.teacher }}
- {{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}
+
+ {{ course.class_name || course.courseName }}
+
+
+ 📍 {{ course.campus_name }}
+
+
+
+ ⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
+
+
+
+
+ 👨🏫 {{ course.teacher }}
+ 🏠 {{ course.venue }}
+
+
+
{{ course.status }}
+
+
+
+ 学员列表:
+
+ {{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
+
+
+
+ {{ course.students }}
+
@@ -293,13 +380,42 @@
course.type === 'private' ? 'course-private' : '',
course.type === 'activity' ? 'course-activity' : ''
]"
+ :style="{
+ minHeight: getCourseMinHeight(course),
+ zIndex: 5
+ }"
@click.stop="viewScheduleDetail(course.id)"
>
- {{ course.courseName }}
- {{ course.time }}
- {{ course.teacher }}
- {{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}
+
+ {{ course.class_name || course.courseName }}
+
+
+ 📍 {{ course.campus_name }}
+
+
+
+ ⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
+
+
+
+
+ 👨🏫 {{ course.teacher }}
+ 🏠 {{ course.venue }}
+
+
+
{{ course.status }}
+
+
+
+ 学员列表:
+
+ {{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
+
+
+
+ {{ course.students }}
+
@@ -739,13 +855,32 @@ export default {
// 获取指定时间和日期的课程
getCoursesByTimeAndDate(time, date) {
- const matchedCourses = this.courses.filter(course =>
- course.time === time && course.date === date
- )
-
-
+ const matchedCourses = this.courses.filter(course => {
+ if (course.date !== date) return false
+ // 只在课程开始时间显示课程,不在后续时间段重复显示
+ return course.time === time
+ })
return matchedCourses
},
+
+ // 获取课程跨越的时间段数量
+ getCourseSpanSlots(course) {
+ if (!course.duration) return 1
+ return Math.ceil(course.duration / 30)
+ },
+
+ // 获取课程显示的行高度 - 已废弃,使用getCourseMinHeight
+ getCourseRowHeight(course) {
+ const spanSlots = this.getCourseSpanSlots(course)
+ return 'auto'
+ },
+
+ // 获取课程的最小高度 - 用于单元格合并视觉效果
+ getCourseMinHeight(course) {
+ const spanSlots = this.getCourseSpanSlots(course)
+ // 设置最小高度保持单元格合并效果,但允许内容超出
+ return spanSlots * 120 + 'rpx'
+ },
// 获取指定教练和日期的课程
getCoursesByTeacherAndDate(teacherId, date) {
@@ -1025,7 +1160,8 @@ export default {
venue_id: item.venue_id, // 保存场地ID
campus_name: item.campus_name || '', // 添加校区名称
class_id: item.class_id, // 保存班级ID
- duration: item.time_info?.duration ? Math.floor(item.time_info.duration / 60) : 60,
+ class_name: item.class_name || '', // 添加班级名称
+ duration: item.time_info?.duration || 60,
time_slot: item.time_slot,
raw: item, // 保存原始数据
}))
@@ -1570,7 +1706,9 @@ export default {
border-bottom: 2px solid #29d3b4;
position: sticky;
top: 0;
- z-index: 2;
+ z-index: 10;
+ /* 确保完全覆盖下方内容 */
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
}
.date-header-cell {
@@ -1583,6 +1721,8 @@ export default {
border-right: 1px solid #555;
background: #434544;
flex-shrink: 0;
+ position: relative;
+ z-index: 11;
.date-week {
font-size: 26rpx;
@@ -1689,7 +1829,13 @@ export default {
word-wrap: break-word; /* 允许长文本换行 */
overflow-wrap: break-word;
min-height: auto; /* 允许根据内容调整高度 */
+ height: auto !important; /* 覆盖内联样式,允许内容自适应高度 */
flex-shrink: 0; /* 防止被压缩 */
+ position: relative;
+ overflow: visible; /* 允许内容溢出显示 */
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
&.course-normal {
background: rgba(41, 211, 180, 0.2);
@@ -1748,6 +1894,78 @@ export default {
font-size: 18rpx;
}
+// 课程信息合并显示
+.course-info {
+ font-size: 18rpx;
+ color: #ccc;
+ line-height: 1.2;
+ margin-top: 4rpx;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+}
+
+// 时间信息显示
+.course-time-info {
+ font-size: 16rpx;
+ color: #29d3b4;
+ line-height: 1.2;
+ margin-top: 2rpx;
+ font-weight: 500;
+}
+
+// 校区信息显示
+.course-campus {
+ font-size: 16rpx;
+ color: #ff9500;
+ line-height: 1.2;
+ margin-top: 2rpx;
+ margin-bottom: 2rpx;
+}
+
+// 基础信息容器
+.course-basic-info {
+ margin-top: 4rpx;
+ margin-bottom: 4rpx;
+}
+
+.course-basic-info .course-teacher,
+.course-basic-info .course-venue {
+ font-size: 16rpx;
+ line-height: 1.2;
+ margin-bottom: 2rpx;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+}
+
+.course-basic-info .course-teacher {
+ color: #ccc;
+}
+
+.course-basic-info .course-venue {
+ color: #ffa500;
+}
+
+// 学员信息显示
+.course-students {
+ margin-top: 4rpx;
+ font-size: 16rpx;
+ color: #999;
+}
+
+.students-title {
+ color: #29d3b4;
+ font-weight: 500;
+ margin-bottom: 2rpx;
+}
+
+.student-item {
+ color: #ccc;
+ line-height: 1.2;
+ margin-bottom: 2rpx;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+}
+
// 添加按钮
.add-btn {
position: fixed;