Browse Source

修改 bug

master
王泽彦 8 months ago
parent
commit
c3d2487f4b
  1. 179
      admin/src/app/views/venue/components/venue-edit.vue
  2. 2
      niucloud/app/Request.php
  3. 165
      niucloud/app/api/controller/apiController/ScheduleOptions.php
  4. 14
      niucloud/app/api/route/route.php
  5. 20
      niucloud/app/model/school/CampusPersonRole.php
  6. 20
      niucloud/app/model/school/SchoolClass.php
  7. 20
      niucloud/app/model/school/SchoolPersonnel.php
  8. 20
      niucloud/app/model/school/SchoolVenue.php
  9. 13
      niucloud/app/service/api/apiService/CourseScheduleService.php
  10. 291
      niucloud/app/service/api/apiService/ScheduleOptionsService.php
  11. 16
      uniapp/api/apiRoute.js
  12. 41
      uniapp/components/schedule/ScheduleDetail.vue
  13. 83
      uniapp/mock/scheduleOptions.js
  14. 52
      uniapp/pages-coach/coach/schedule/add_schedule.vue
  15. 262
      uniapp/pages-coach/coach/schedule/schedule_table.vue

179
admin/src/app/views/venue/components/venue-edit.vue

@ -81,8 +81,10 @@
</el-radio-group>
</el-form-item>
<!-- 时间段类型为 range -->
<el-form-item
:label="t('fixedTimeRanges')"
:label="t('timeRange')"
prop="time_range_start"
class="input-width"
v-if="formData.time_range_type === 'range'"
>
@ -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 @@
</div>
</div>
</el-form-item>
<!-- 时间段类型为 fixed -->
<el-form-item
:label="t('fixedTimeRanges')"
prop="fixed_time_ranges"
class="input-width"
v-if="formData.time_range_type === 'fixed'"
>
@ -145,18 +150,33 @@
<el-button
v-if="index === formData.fixed_time_ranges.length - 1"
type="primary"
size="small"
@click="addTimeRange"
>新增</el-button
>
<el-button
v-if="index > 0"
v-if="index > 0 || formData.fixed_time_ranges.length > 1"
type="danger"
size="small"
@click="removeTimeRange(index)"
>删除</el-button
>
</div>
</div>
</el-form-item>
<!-- 时间段类型为 all 时的提示 -->
<el-form-item
:label="t('allTimeAvailable')"
v-if="formData.time_range_type === 'all'"
>
<el-alert
title="该场地全天可用 (08:30-22:30)"
type="info"
:closable="false"
show-icon
/>
</el-form-item>
</el-form>
<template #footer>
@ -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<FormInstance>()
//
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
}

2
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

165
niucloud/app/api/controller/apiController/ScheduleOptions.php

@ -0,0 +1,165 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\api\controller\apiController;
use app\Request;
use app\service\api\apiService\ScheduleOptionsService;
use core\base\BaseApiService;
/**
* 课程安排选项接口
* Class ScheduleOptions
* @package app\api\controller\apiController
*/
class ScheduleOptions extends BaseApiService
{
/**
* 获取所有排课相关的选项列表(统一接口)
* @param Request $request
* @return \think\Response
*/
public function getAllOptions(Request $request)
{
try {
// 从登录用户的角色信息中获取校区ID
$campus_id = $this->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());
}
}
}

14
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');
//获取班级列表(用于添加课程安排)

20
niucloud/app/model/school/CampusPersonRole.php

@ -0,0 +1,20 @@
<?php
namespace app\model\school;
use core\base\BaseModel;
/**
* 校区人员角色关联模型
*/
class CampusPersonRole extends BaseModel
{
protected $table = 'school_campus_person_role';
protected $pk = 'id';
/**
* 软删除
*/
protected $deleteTime = 'deleted_at';
protected $defaultSoftDelete = 0;
}

20
niucloud/app/model/school/SchoolClass.php

@ -0,0 +1,20 @@
<?php
namespace app\model\school;
use core\base\BaseModel;
/**
* 班级模型
*/
class SchoolClass extends BaseModel
{
protected $table = 'school_class';
protected $pk = 'id';
/**
* 软删除
*/
protected $deleteTime = 'deleted_at';
protected $defaultSoftDelete = 0;
}

20
niucloud/app/model/school/SchoolPersonnel.php

@ -0,0 +1,20 @@
<?php
namespace app\model\school;
use core\base\BaseModel;
/**
* 人员模型
*/
class SchoolPersonnel extends BaseModel
{
protected $table = 'school_personnel';
protected $pk = 'id';
/**
* 软删除
*/
protected $deleteTime = 'deleted_at';
protected $defaultSoftDelete = 0;
}

20
niucloud/app/model/school/SchoolVenue.php

@ -0,0 +1,20 @@
<?php
namespace app\model\school;
use core\base\BaseModel;
/**
* 场地模型
*/
class SchoolVenue extends BaseModel
{
protected $table = 'school_venue';
protected $pk = 'id';
/**
* 软删除
*/
protected $deleteTime = 'deleted_at';
protected $defaultSoftDelete = 0;
}

13
niucloud/app/service/api/apiService/CourseScheduleService.php

@ -25,6 +25,7 @@ class CourseScheduleService extends BaseApiService
public function __construct()
{
parent::__construct();
$this->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'];
}

291
niucloud/app/service/api/apiService/ScheduleOptionsService.php

@ -0,0 +1,291 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\service\api\apiService;
use app\model\course\Course;
use app\model\school\SchoolClass;
use app\model\school\SchoolPersonnel;
use app\model\school\SchoolVenue;
use core\base\BaseApiService;
/**
* 课程安排选项服务
* Class ScheduleOptionsService
* @package app\service\api\apiService
*/
class ScheduleOptionsService extends BaseApiService
{
/**
* 获取所有排课相关的选项列表(统一接口)
* @param array $data
* @return array
*/
public function getAllOptions(array $data)
{
try {
$campus_id = $data['campus_id'] ?? 0;
$include_all = $data['include_all'] ?? false;
// 并行获取所有选项
$courses = $this->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;
}
}

16
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)
},
// 获取场地可用时间段(新开发的通用接口)

41
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 || [])
];
// 使APIdata
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);

83
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
};

52
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()
]);
//
if (courseRes.code === 1) {
this.courseOptions = courseRes.data || [];
}
//
if (classRes.code === 1) {
this.classOptions = classRes.data || [];
}
//
if (coachRes.code === 1) {
this.coachOptions = coachRes.data || [];
}
// 使
const res = await api.getAllScheduleOptions();
//
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({

262
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)"
>
<view class="course-name">{{ course.courseName }}</view>
<view class="course-students">{{ course.students }}</view>
<view class="course-teacher">{{ course.teacher }}</view>
<view class="course-venue" v-if="course.campus_name || course.venue">{{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}</view>
<!-- 班级名称和课程名称 -->
<view class="course-name">{{ course.class_name || course.courseName }}</view>
<!-- 校区信息 -->
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
<!-- 上课时间 -->
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
{{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
</view>
<!-- 教练和场地 -->
<view class="course-basic-info">
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
<view class="course-venue">🏠 {{ course.venue }}</view>
</view>
<!-- 课程状态 -->
<view class="course-status">{{ course.status }}</view>
<!-- 学员信息 -->
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
<view class="students-title">学员列表</view>
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
</view>
</view>
<view class="course-students" v-else>
{{ course.students }}
</view>
</view>
</view>
</view>
@ -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)"
>
<view class="course-name">{{ course.courseName }}</view>
<view class="course-time">{{ course.time }}</view>
<view class="course-students">{{ course.students }}</view>
<view class="course-venue" v-if="course.campus_name || course.venue">{{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}</view>
<!-- 班级名称和课程名称 -->
<view class="course-name">{{ course.class_name || course.courseName }}</view>
<!-- 校区信息 -->
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
<!-- 上课时间 -->
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
{{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
</view>
<!-- 教练和场地 -->
<view class="course-basic-info">
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
<view class="course-venue">🏠 {{ course.venue }}</view>
</view>
<!-- 课程状态 -->
<view class="course-status">{{ course.status }}</view>
<!-- 学员信息 -->
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
<view class="students-title">学员列表</view>
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
</view>
</view>
<view class="course-students" v-else>
{{ course.students }}
</view>
</view>
</view>
</view>
@ -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)"
>
<view class="course-name">{{ course.courseName }}</view>
<view class="course-time">{{ course.time }}</view>
<view class="course-teacher">{{ course.teacher }}</view>
<view class="course-venue" v-if="course.campus_name || course.venue">{{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}</view>
<!-- 班级名称和课程名称 -->
<view class="course-name">{{ course.class_name || course.courseName }}</view>
<!-- 校区信息 -->
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
<!-- 上课时间 -->
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
{{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
</view>
<!-- 教练和场地 -->
<view class="course-basic-info">
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
<view class="course-venue">🏠 {{ course.venue }}</view>
</view>
<!-- 课程状态 -->
<view class="course-status">{{ course.status }}</view>
<!-- 学员信息 -->
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
<view class="students-title">学员列表</view>
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
</view>
</view>
<view class="course-students" v-else>
{{ course.students }}
</view>
</view>
</view>
</view>
@ -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)"
>
<view class="course-name">{{ course.courseName }}</view>
<view class="course-time">{{ course.time }}</view>
<view class="course-teacher">{{ course.teacher }}</view>
<view class="course-venue" v-if="course.campus_name || course.venue">{{ course.campus_name ? course.campus_name + ' ' : '' }}{{ course.venue }}</view>
<!-- 班级名称和课程名称 -->
<view class="course-name">{{ course.class_name || course.courseName }}</view>
<!-- 校区信息 -->
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
<!-- 上课时间 -->
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
{{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
</view>
<!-- 教练和场地 -->
<view class="course-basic-info">
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
<view class="course-venue">🏠 {{ course.venue }}</view>
</view>
<!-- 课程状态 -->
<view class="course-status">{{ course.status }}</view>
<!-- 学员信息 -->
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
<view class="students-title">学员列表</view>
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
</view>
</view>
<view class="course-students" v-else>
{{ course.students }}
</view>
</view>
</view>
</view>
@ -739,12 +855,31 @@ 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)
},
return matchedCourses
// - 使getCourseMinHeight
getCourseRowHeight(course) {
const spanSlots = this.getCourseSpanSlots(course)
return 'auto'
},
// -
getCourseMinHeight(course) {
const spanSlots = this.getCourseSpanSlots(course)
//
return spanSlots * 120 + 'rpx'
},
//
@ -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;

Loading…
Cancel
Save