Browse Source

修改 bug

master
王泽彦 5 months ago
parent
commit
b8a510485d
  1. 26
      admin/src/app/views/classroom/components/classroom-edit.vue
  2. 17
      admin/src/app/views/course_schedule/components/course-schedule-edit.vue
  3. 66
      niucloud/app/api/controller/apiController/Course.php
  4. 31
      niucloud/app/api/controller/apiController/CourseSchedule.php
  5. 2
      niucloud/app/api/controller/apiController/CourseScheduleController.php
  6. 3
      niucloud/app/api/route/route.php
  7. 2
      niucloud/app/model/course_schedule/CourseSchedule.php
  8. 56
      niucloud/app/service/admin/classroom/ClassroomService.php
  9. 173
      niucloud/app/service/api/apiService/CourseScheduleService.php
  10. 33
      niucloud/app/service/api/apiService/CourseService.php
  11. 67
      uniapp/api/apiRoute.js
  12. 387
      uniapp/components/schedule/ScheduleDetail.vue
  13. 175
      uniapp/components/student-info-card/student-info-card.vue
  14. 129
      uniapp/pages-coach/coach/schedule/adjust_course.vue
  15. 130
      uniapp/pages-coach/coach/schedule/sign_in.vue
  16. 139
      uniapp/pages-market/clue/class_arrangement_detail.vue
  17. 5
      uniapp/pages-market/clue/clue_info.vue

26
admin/src/app/views/classroom/components/classroom-edit.vue

@ -321,16 +321,18 @@ const setEducationalList = async (campusId?: number | string) => {
setEducationalList() setEducationalList()
// //
watch(() => formData.campus_id, (newCampusId) => { watch(() => formData.campus_id, (newCampusId, oldCampusId) => {
if (newCampusId) { if (newCampusId) {
// //
setCoachLists(newCampusId) setCoachLists(newCampusId)
setEducationalList(newCampusId) setEducationalList(newCampusId)
// //
formData.head_coach = '' if (oldCampusId !== undefined && oldCampusId !== '' && oldCampusId !== newCampusId) {
formData.assistant_coach = '' formData.head_coach = ''
formData.educational_id = '' formData.assistant_coach = ''
formData.educational_id = ''
}
} else { } else {
// //
setCoachLists() setCoachLists()
@ -342,10 +344,18 @@ const setFormData = async (row: any = null) => {
loading.value = true loading.value = true
if (row) { if (row) {
const data = await (await getClassroomInfo(row.id)).data const data = await (await getClassroomInfo(row.id)).data
if (data) if (data) {
Object.keys(formData).forEach((key: string) => { Object.keys(formData).forEach((key: string) => {
if (data[key] != undefined) formData[key] = data[key] if (data[key] != undefined) {
// head_coachassistant_coacheducational_idselectvalue
if (['head_coach', 'assistant_coach', 'educational_id'].includes(key)) {
formData[key] = String(data[key])
} else {
formData[key] = data[key]
}
}
}) })
}
} }
loading.value = false loading.value = false
} }

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

@ -147,7 +147,7 @@ import { getWithCampusList, getAllVenueList } from '@/app/api/venue'
import { getVenueInfo } from '@/app/api/venue' import { getVenueInfo } from '@/app/api/venue'
import { generateTimeSlots } from '@/utils/timeslots' import { generateTimeSlots } from '@/utils/timeslots'
import { getAllCourseList } from '@/app/api/course' import { getAllCourseList } from '@/app/api/course'
import { getWithCoachList } from '@/app/api/customer_resources' import { getCoachPersonnel } from '@/app/api/classroom'
let showDialog = ref(false) let showDialog = ref(false)
const loading = ref(false) const loading = ref(false)
@ -272,7 +272,7 @@ const loadCourseList = () => {
} }
// //
const loadCoachList = (campus_id?: string | number) => { const loadCoachList = (campus_id?: string | number) => {
getWithCoachList({ campus_id }) getCoachPersonnel(campus_id)
.then((res) => { .then((res) => {
coachList.value = res.data || [] coachList.value = res.data || []
}) })
@ -298,7 +298,7 @@ watch(
}) })
.catch(() => {}) .catch(() => {})
getWithCoachList({ campus_id: newValue }) getCoachPersonnel(newValue)
.then((res) => { .then((res) => {
coachList.value = res.data || [] coachList.value = res.data || []
}) })
@ -396,7 +396,7 @@ const setFormData = async (row: any = null) => {
}); });
}), }),
new Promise<void>((resolve) => { new Promise<void>((resolve) => {
getWithCoachList({ campus_id: detailData.campus_id }) getCoachPersonnel(detailData.campus_id)
.then((res) => { .then((res) => {
coachList.value = res.data || []; coachList.value = res.data || [];
resolve(); resolve();
@ -444,9 +444,14 @@ const setFormData = async (row: any = null) => {
if (key === 'campus_id' || key === 'venue_id' || key === 'time_slot') { if (key === 'campus_id' || key === 'venue_id' || key === 'time_slot') {
return; return;
} }
if (detailData[key] !== undefined) { if (detailData[key] !== undefined) {
formData[key] = detailData[key]; // IDselectvalue
if (['coach_id', 'course_id', 'venue_id'].includes(key)) {
formData[key] = Number(detailData[key]);
} else {
formData[key] = detailData[key];
}
} else if (key === 'auto_schedule' && detailData[key] === undefined) { } else if (key === 'auto_schedule' && detailData[key] === undefined) {
// 使1 // 使1
formData.auto_schedule = 1; formData.auto_schedule = 1;

66
niucloud/app/api/controller/apiController/Course.php

@ -144,17 +144,17 @@ class Course extends BaseApiService
{ {
try { try {
$params = $request->all(); $params = $request->all();
// 验证必要参数 // 验证必要参数
if (empty($params['student_course_id'])) { if (empty($params['student_course_id'])) {
return fail('学员课程ID不能为空'); return fail('学员课程ID不能为空');
} }
$res = (new CourseService())->updateStudentCoursePersonnel($params); $res = (new CourseService())->updateStudentCoursePersonnel($params);
if (!$res['code']) { if (!$res['code']) {
return fail($res['msg']); return fail($res['msg']);
} }
return success($res['data'], '更新成功'); return success($res['data'], '更新成功');
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('更新学员课程人员配置失败:' . $e->getMessage()); return fail('更新学员课程人员配置失败:' . $e->getMessage());
@ -174,12 +174,12 @@ class Course extends BaseApiService
["course_type", ""], // 课程类型筛选 ["course_type", ""], // 课程类型筛选
["status", 1] // 状态筛选,默认获取有效课程 ["status", 1] // 状态筛选,默认获取有效课程
]); ]);
$result = (new CourseService())->getCourseListForSchedule($data); $result = (new CourseService())->getCourseListForSchedule($data);
if (!$result['code']) { if (!$result['code']) {
return fail($result['msg']); return fail($result['msg']);
} }
return success('获取成功', $result['data']); return success('获取成功', $result['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('获取课程列表失败:' . $e->getMessage()); return fail('获取课程列表失败:' . $e->getMessage());
@ -197,16 +197,16 @@ class Course extends BaseApiService
$data = $this->request->params([ $data = $this->request->params([
["schedule_id", 0] // 课程安排ID ["schedule_id", 0] // 课程安排ID
]); ]);
if (empty($data['schedule_id'])) { if (empty($data['schedule_id'])) {
return fail('课程安排ID不能为空'); return fail('课程安排ID不能为空');
} }
$result = (new CourseService())->getScheduleDetail($data['schedule_id']); $result = (new CourseService())->getScheduleDetail($data['schedule_id']);
if (!$result['code']) { if (!$result['code']) {
return fail($result['msg']); return fail($result['msg']);
} }
return success('获取成功', $result['data']); return success('获取成功', $result['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('获取课程安排详情失败:' . $e->getMessage()); return fail('获取课程安排详情失败:' . $e->getMessage());
@ -226,16 +226,16 @@ class Course extends BaseApiService
["search_type", ""], // 搜索类型(name或phone) ["search_type", ""], // 搜索类型(name或phone)
["schedule_id", 0] // 课程安排ID(用于排除已添加的学员) ["schedule_id", 0] // 课程安排ID(用于排除已添加的学员)
]); ]);
if (empty($data['keyword'])) { if (empty($data['keyword'])) {
return success('搜索成功', []); return success('搜索成功', []);
} }
$result = (new CourseService())->searchAvailableStudents($data); $result = (new CourseService())->searchAvailableStudents($data);
if (!$result['code']) { if (!$result['code']) {
return fail($result['msg']); return fail($result['msg']);
} }
return success('搜索成功', $result['data']); return success('搜索成功', $result['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('搜索学员失败:' . $e->getMessage()); return fail('搜索学员失败:' . $e->getMessage());
@ -259,20 +259,20 @@ class Course extends BaseApiService
["course_type", 1], // 课程类型:1-加课,2-补课,3-等待位 ["course_type", 1], // 课程类型:1-加课,2-补课,3-等待位
["remarks", ""] // 备注 ["remarks", ""] // 备注
]); ]);
if (empty($data['schedule_id'])) { if (empty($data['schedule_id'])) {
return fail('课程安排ID不能为空'); return fail('课程安排ID不能为空');
} }
if (empty($data['student_id']) && empty($data['resources_id'])) { if (empty($data['student_id']) && empty($data['resources_id'])) {
return fail('学员ID或资源ID不能都为空'); return fail('学员ID或资源ID不能都为空');
} }
$result = (new CourseService())->addStudentToSchedule($data); $result = (new CourseService())->addStudentToSchedule($data);
if (!$result['code']) { if (!$result['code']) {
return fail($result['msg']); return fail($result['msg']);
} }
return success('添加成功', $result['data']); return success('添加成功', $result['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('添加学员失败:' . $e->getMessage()); return fail('添加学员失败:' . $e->getMessage());
@ -292,16 +292,16 @@ class Course extends BaseApiService
["reason", ""], // 移除原因 ["reason", ""], // 移除原因
["remark", ""] // 备注 ["remark", ""] // 备注
]); ]);
if (empty($data['person_schedule_id'])) { if (empty($data['person_schedule_id'])) {
return fail('人员课程安排关系ID不能为空'); return fail('人员课程安排关系ID不能为空');
} }
$result = (new CourseService())->removeStudentFromSchedule($data); $result = (new CourseService())->removeStudentFromSchedule($data);
if (!$result['code']) { if (!$result['code']) {
return fail($result['msg']); return fail($result['msg']);
} }
return success('移除成功', $result['data']); return success('移除成功', $result['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('移除学员失败:' . $e->getMessage()); return fail('移除学员失败:' . $e->getMessage());
@ -321,22 +321,22 @@ class Course extends BaseApiService
["status", 0], // 状态:0-待上课,1-已上课,2-请假 ["status", 0], // 状态:0-待上课,1-已上课,2-请假
["remark", ""] // 备注 ["remark", ""] // 备注
]); ]);
if (empty($data['person_schedule_id'])) { if (empty($data['person_schedule_id'])) {
return fail('人员课程安排关系ID不能为空'); return fail('人员课程安排关系ID不能为空');
} }
$result = (new CourseService())->updateStudentStatus($data); $result = (new CourseService())->updateStudentStatus($data);
if (!$result['code']) { if (!$result['code']) {
return fail($result['msg']); return fail($result['msg']);
} }
return success('更新成功', $result['data']); return success('更新成功', $result['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('更新学员状态失败:' . $e->getMessage()); return fail('更新学员状态失败:' . $e->getMessage());
} }
} }
/** /**
* 获取教练列表 * 获取教练列表
* @param Request $request * @param Request $request
@ -350,13 +350,13 @@ class Course extends BaseApiService
if (!$res['code']) { if (!$res['code']) {
return fail($res['msg']); return fail($res['msg']);
} }
return success($res['data']); return success($res['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('获取教练列表失败:' . $e->getMessage()); return fail('获取教练列表失败:' . $e->getMessage());
} }
} }
/** /**
* 获取教务人员列表 * 获取教务人员列表
* @param Request $request * @param Request $request
@ -370,13 +370,13 @@ class Course extends BaseApiService
if (!$res['code']) { if (!$res['code']) {
return fail($res['msg']); return fail($res['msg']);
} }
return success($res['data']); return success($res['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('获取教务人员列表失败:' . $e->getMessage()); return fail('获取教务人员列表失败:' . $e->getMessage());
} }
} }
/** /**
* 更新学员课程信息(主教练、助教、教务) * 更新学员课程信息(主教练、助教、教务)
* @param Request $request * @param Request $request
@ -392,22 +392,22 @@ class Course extends BaseApiService
["education_id", 0], ["education_id", 0],
["class_id", 0] // 可选,如果需要更新班级关联 ["class_id", 0] // 可选,如果需要更新班级关联
]); ]);
if (empty($data['student_course_id'])) { if (empty($data['student_course_id'])) {
return fail('学员课程ID不能为空'); return fail('学员课程ID不能为空');
} }
$res = (new CourseService())->updateCourseInfo($data); $res = (new CourseService())->updateCourseInfo($data);
if (!$res['code']) { if (!$res['code']) {
return fail($res['msg']); return fail($res['msg']);
} }
return success('更新成功', $res['data']); return success('更新成功', $res['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('更新失败:' . $e->getMessage()); return fail('更新失败:' . $e->getMessage());
} }
} }
/** /**
* 检查学员班级关联情况 * 检查学员班级关联情况
* @param Request $request * @param Request $request
@ -417,16 +417,16 @@ class Course extends BaseApiService
{ {
try { try {
$resource_id = $request->param('resource_id', ''); $resource_id = $request->param('resource_id', '');
if (empty($resource_id)) { if (empty($resource_id)) {
return fail('资源ID不能为空'); return fail('资源ID不能为空');
} }
$res = (new CourseService())->checkClassRelation($resource_id); $res = (new CourseService())->checkClassRelation($resource_id);
if (!$res['code']) { if (!$res['code']) {
return fail($res['msg']); return fail($res['msg']);
} }
return success($res['data']); return success($res['data']);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail('检查班级关联失败:' . $e->getMessage()); return fail('检查班级关联失败:' . $e->getMessage());

31
niucloud/app/api/controller/apiController/CourseSchedule.php

@ -384,4 +384,35 @@ class CourseSchedule extends BaseApiService
return fail('签到失败:' . $e->getMessage()); return fail('签到失败:' . $e->getMessage());
} }
} }
/**
* 删除学员所有未来课程安排
* @param Request $request
* @return \think\Response
*/
public function deleteStudentFutureSchedules(Request $request)
{
try {
$data = $this->request->params([
["student_id", 0],
["person_id", 0],
["resources_id", 0],
["reason", ""] // 删除原因
]);
// 验证必填参数
if (empty($data['student_id']) && empty($data['person_id']) && empty($data['resources_id'])) {
return fail('学员ID、人员ID或资源ID不能为空');
}
$result = (new CourseScheduleService())->deleteStudentFutureSchedules($data);
if (!$result['code']) {
return fail($result['msg']);
}
return success($result['msg'], $result['data'] ?? []);
} catch (\Exception $e) {
return fail('删除失败:' . $e->getMessage());
}
}
} }

2
niucloud/app/api/controller/apiController/CourseScheduleController.php

@ -240,4 +240,4 @@ class CourseScheduleController extends BaseApiController
$service = new CourseScheduleService(); $service = new CourseScheduleService();
return success('获取成功', $service->getFilterOptions($data)); return success('获取成功', $service->getFilterOptions($data));
} }
} }

3
niucloud/app/api/route/route.php

@ -354,6 +354,8 @@ Route::group(function () {
Route::post('courseSchedule/batchSignIn', 'apiController.CourseSchedule/batchSignIn'); Route::post('courseSchedule/batchSignIn', 'apiController.CourseSchedule/batchSignIn');
//员工端-单个学员签到 //员工端-单个学员签到
Route::post('courseSchedule/updateStudentStatus', 'apiController.CourseSchedule/updateStudentStatus'); Route::post('courseSchedule/updateStudentStatus', 'apiController.CourseSchedule/updateStudentStatus');
//员工端-删除学员所有未来课程安排
Route::post('courseSchedule/deleteStudentFutureSchedules', 'apiController.CourseSchedule/deleteStudentFutureSchedules');
// 课程安排统一选项接口(新增-支持校区过滤) // 课程安排统一选项接口(新增-支持校区过滤)
@ -371,6 +373,7 @@ Route::group(function () {
// 添加课程安排页面专用接口(保留兼容性) // 添加课程安排页面专用接口(保留兼容性)
//获取课程列表(用于添加课程安排) //获取课程列表(用于添加课程安排)
Route::get('course/list', 'apiController.Course/getCourseList'); Route::get('course/list', 'apiController.Course/getCourseList');
Route::post('course/upgradeStudent', 'apiController.Course/upgradeStudent');
//获取班级列表(用于添加课程安排) //获取班级列表(用于添加课程安排)
Route::get('class/list', 'apiController.ClassApi/getClassList'); Route::get('class/list', 'apiController.ClassApi/getClassList');
//获取教练列表(用于添加课程安排) //获取教练列表(用于添加课程安排)

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

@ -96,7 +96,7 @@ class CourseSchedule extends BaseModel
public function coach() public function coach()
{ {
return $this->hasOne(Personnel::class, 'id', 'coach_id'); return $this->hasOne(\app\model\school\SchoolPersonnel::class, 'id', 'coach_id');
} }
public function course() public function course()

56
niucloud/app/service/admin/classroom/ClassroomService.php

@ -150,22 +150,58 @@ class ClassroomService extends BaseAdminService
{ {
$campus_id = $params['campus_id'] ?? 0; $campus_id = $params['campus_id'] ?? 0;
// 如果没有传校区ID,返回所有教练人员 // 返回所有教练角色的人员(不限制校区,因为课程安排可能有跨校区教练)
if (empty($campus_id)) { $personnel_list = \think\facade\Db::table('school_personnel')
// 这里可以直接查询所有教练角色的人员,但为了保持一致性,我们使用公共方法 ->alias('p')
$personnelModel = new Personnel(); ->leftJoin('school_campus_person_role cpr', 'p.id = cpr.person_id')
return $personnelModel->field('id, name, phone')->select()->toArray(); ->leftJoin('school_sys_role sr', 'cpr.role_id = sr.role_id')
} ->where([
['sr.role_key', 'in', ['teacher_manager', 'teacher']],
['p.status', '=', 2] // 只返回状态正常的教练
])
->where(function ($query) {
$query->where('cpr.deleted_at', 0)->whereOr('cpr.deleted_at', null);
})
->field('p.id, p.name, p.phone, cpr.role_id, cpr.dept_id, cpr.campus_id')
->group('p.id') // 去重,防止一个教练有多个角色记录
->select()
->toArray();
// 根据校区ID获取教练人员(角色ID: 1=教练主管, 5=普通教练) // 如果指定了校区,优先返回该校区的教练,但也包含其他校区的教练
$role_ids = [1, 5]; if (!empty($campus_id)) {
$personnel_list = get_personnel_by_campus_role($campus_id, $role_ids); // 将指定校区的教练排在前面
$campus_coaches = [];
$other_coaches = [];
foreach ($personnel_list as $person) {
if ($person['campus_id'] == $campus_id) {
$campus_coaches[] = [
'id' => $person['id'],
'name' => $person['name'],
'phone' => $person['phone'],
'role_id' => $person['role_id'],
'dept_id' => $person['dept_id']
];
} else {
$other_coaches[] = [
'id' => $person['id'],
'name' => $person['name'],
'phone' => $person['phone'],
'role_id' => $person['role_id'],
'dept_id' => $person['dept_id']
];
}
}
// 合并结果,校区教练在前
return array_merge($campus_coaches, $other_coaches);
}
// 格式化返回数据 // 格式化返回数据
$result = []; $result = [];
foreach ($personnel_list as $person) { foreach ($personnel_list as $person) {
$result[] = [ $result[] = [
'id' => $person['person_id'], 'id' => $person['id'],
'name' => $person['name'], 'name' => $person['name'],
'phone' => $person['phone'], 'phone' => $person['phone'],
'role_id' => $person['role_id'], 'role_id' => $person['role_id'],

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

@ -2757,12 +2757,13 @@ class CourseScheduleService extends BaseApiService
} }
// 记录日志 // 记录日志
trace('Trial class checkin processed', [ trace('Trial class checkin processed', 'info');
trace('Student details: ' . json_encode([
'student_id' => $student->id, 'student_id' => $student->id,
'old_trial_count' => $currentTrialCount, 'old_trial_count' => $currentTrialCount,
'new_trial_count' => $updateData['trial_class_count'], 'new_trial_count' => $updateData['trial_class_count'],
'update_fields' => array_keys($updateData) 'update_fields' => array_keys($updateData)
]); ]), 'info');
} catch (\Exception $e) { } catch (\Exception $e) {
// 抛出异常以便外层事务回滚 // 抛出异常以便外层事务回滚
@ -2779,28 +2780,47 @@ class CourseScheduleService extends BaseApiService
* @param string $reason 备注 * @param string $reason 备注
* @return array 处理结果 * @return array 处理结果
*/ */
private function processSingleStudentSignIn($scheduleId, $studentId, $resourceId, $status, $reason = '') private function processSingleStudentSignIn($scheduleId, $personId, $resourceId, $status, $reason = '')
{ {
try { try {
// 查找学员课程安排记录 // 查找学员课程安排记录
$whereCondition = [ $enrollment = null;
'schedule_id' => $scheduleId,
'deleted_at' => 0
];
if (!empty($studentId)) { // 优先使用student_id字段查询(前端传递的student_id)
$whereCondition['student_id'] = $studentId; if (!empty($personId)) {
// 首先尝试按student_id字段查询
$enrollment = Db::name('person_course_schedule')
->where('student_id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
// 如果按student_id没找到,再尝试按主键id查询
if (!$enrollment) {
$enrollment = Db::name('person_course_schedule')
->where('id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
// 如果还没找到,最后尝试按person_id字段查询
if (!$enrollment) {
$enrollment = Db::name('person_course_schedule')
->where('person_id', $personId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
} elseif (!empty($resourceId)) { } elseif (!empty($resourceId)) {
$whereCondition['resources_id'] = $resourceId; $enrollment = Db::name('person_course_schedule')
} else { ->where('resources_id', $resourceId)
return ['success' => false, 'error' => '学员ID或资源ID不能为空']; ->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
} }
$enrollment = Db::name('person_course_schedule') if (empty($enrollment)) {
->where($whereCondition)
->find();
if (!$enrollment) {
return ['success' => false, 'error' => '找不到学员记录']; return ['success' => false, 'error' => '找不到学员记录'];
} }
@ -2810,6 +2830,13 @@ class CourseScheduleService extends BaseApiService
return ['success' => false, 'error' => '找不到学员信息']; return ['success' => false, 'error' => '找不到学员信息'];
} }
// 检查体验课次数(仅对未付费学员且状态为签到时)
if ($status === 1 && $student->pay_status != 1) {
if ($student->trial_class_count <= 0) {
return ['success' => false, 'error' => '该学员体验课次数已用完,无法签到'];
}
}
// 准备更新数据 // 准备更新数据
$updateData = [ $updateData = [
'status' => $status, 'status' => $status,
@ -2840,4 +2867,116 @@ class CourseScheduleService extends BaseApiService
return ['success' => false, 'error' => $e->getMessage()]; return ['success' => false, 'error' => $e->getMessage()];
} }
} }
/**
* 删除学员所有未来课程安排
* @param array $data 删除参数
* @return array 删除结果
*/
public function deleteStudentFutureSchedules(array $data)
{
try {
$studentId = $data['student_id'] ?? 0;
$personId = $data['person_id'] ?? 0;
$resourcesId = $data['resources_id'] ?? 0;
if (empty($studentId) && empty($personId) && empty($resourcesId)) {
return [
'code' => 0,
'msg' => '学员ID、人员ID或资源ID不能为空'
];
}
// 开启事务
Db::startTrans();
// 构建查询条件
$where = [
['course_date', '>=', date('Y-m-d')], // 只删除今天及未来的课程
['deleted_at', '=', 0]
];
// 根据传入的参数确定查询条件
if (!empty($studentId)) {
$where[] = ['student_id', '=', $studentId];
}
if (!empty($personId)) {
$where[] = ['person_id', '=', $personId];
}
if (!empty($resourcesId)) {
$where[] = ['resources_id', '=', $resourcesId];
}
// 查询要删除的课程安排数量
$count = Db::name('person_course_schedule')
->where($where)
->count();
if ($count == 0) {
Db::rollback();
return [
'code' => 1,
'msg' => '没有找到需要删除的未来课程安排',
'data' => ['deleted_count' => 0]
];
}
// 执行物理删除
$result = Db::name('person_course_schedule')
->where($where)
->delete();
if ($result === false) {
Db::rollback();
return [
'code' => 0,
'msg' => '删除课程安排失败'
];
}
// 记录删除日志
$this->recordDeleteScheduleLog([
'student_id' => $studentId,
'person_id' => $personId,
'resources_id' => $resourcesId,
'deleted_count' => $result,
'operator_id' => $this->user_id ?? 0,
'delete_time' => date('Y-m-d H:i:s'),
'reason' => $data['reason'] ?? '体验课次数用完,删除未来课程安排'
]);
// 提交事务
Db::commit();
return [
'code' => 1,
'msg' => "成功删除{$result}个未来课程安排",
'data' => [
'deleted_count' => $result
]
];
} catch (\Exception $e) {
Db::rollback();
return [
'code' => 0,
'msg' => '删除失败:' . $e->getMessage()
];
}
}
/**
* 记录删除课程安排日志
* @param array $logData 日志数据
*/
private function recordDeleteScheduleLog(array $logData)
{
try {
// 这里可以记录到日志表或系统日志
trace('Delete student future schedules: ' . json_encode($logData), 'info');
} catch (\Exception $e) {
// 日志记录失败不影响主流程
trace('Record delete schedule log failed: ' . $e->getMessage(), 'error');
}
}
} }

33
niucloud/app/service/api/apiService/CourseService.php

@ -419,7 +419,7 @@ class CourseService extends BaseApiService
case '上午(8:00-12:00)': case '上午(8:00-12:00)':
return ['08:00', '12:00']; // 上午时间范围 return ['08:00', '12:00']; // 上午时间范围
case '下午(12:00-18:00)': case '下午(12:00-18:00)':
return ['12:00', '18:00']; // 下午时间范围 return ['12:00', '18:00']; // 下午时间范围
case '晚上(18:00-22:00)': case '晚上(18:00-22:00)':
return ['18:00', '22:00']; // 晚上时间范围 return ['18:00', '22:00']; // 晚上时间范围
default: default:
@ -459,6 +459,33 @@ class CourseService extends BaseApiService
return fail("重复添加"); return fail("重复添加");
} }
// 验证场地容量限制
$schedule = $CourseSchedule->where('id', $data['schedule_id'])->find();
if (!$schedule) {
return fail("课程安排不存在");
}
// 获取场地信息
$venue = Db::name('venue')->where('id', $schedule['venue_id'])->find();
if ($venue && $venue['capacity'] > 0) {
// 如果添加的是正式学员(非等待位),检查容量限制
if (($data['course_type'] ?? 1) != 3) { // course_type=3是等待位
// 查询当前时间段正式学员人数(排除请假、软删除和等待位)
$currentFormalCount = $personCourseSchedule
->where('schedule_id', $data['schedule_id'])
->where('course_type', '<>', 3) // 排除等待位
->where('status', '<>', 2) // 排除请假(status=2)
->where(function ($query) {
$query->where('deleted_at', 0)->whereOr('deleted_at', null);
})
->count();
if ($currentFormalCount >= $venue['capacity']) {
return fail("该时间段场地已满({$venue['capacity']}人),当前正式学员{$currentFormalCount}人,无法添加更多学员");
}
}
}
$insertData = [ $insertData = [
'student_id' => $student->id, // 正确设置student_id 'student_id' => $student->id, // 正确设置student_id
'resources_id' => $data['resources_id'], // 正确设置resources_id 'resources_id' => $data['resources_id'], // 正确设置resources_id
@ -723,7 +750,7 @@ class CourseService extends BaseApiService
// 获取课程安排基本信息 // 获取课程安排基本信息
$schedule = $CourseSchedule $schedule = $CourseSchedule
->where('id', $scheduleId) ->where('id', $scheduleId)
->with(['course', 'venue', 'campus']) ->with(['course', 'venue', 'campus', 'coach'])
->find(); ->find();
if (!$schedule) { if (!$schedule) {
@ -905,6 +932,8 @@ class CourseService extends BaseApiService
'time_slot' => $schedule['time_slot'], 'time_slot' => $schedule['time_slot'],
'venue_name' => $schedule['venue']['venue_name'] ?? '', 'venue_name' => $schedule['venue']['venue_name'] ?? '',
'campus_name' => $schedule['campus']['campus_name'] ?? '', 'campus_name' => $schedule['campus']['campus_name'] ?? '',
'coach_name' => $schedule['coach']['name'] ?? '',
'coach_id' => $schedule['coach_id'] ?: 0,
'available_capacity' => $schedule['available_capacity'] ?: 0, 'available_capacity' => $schedule['available_capacity'] ?: 0,
'max_students' => $maxStudents, 'max_students' => $maxStudents,
'available_slots' => $availableSlots, 'available_slots' => $availableSlots,

67
uniapp/api/apiRoute.js

@ -992,22 +992,14 @@ export default {
return response return response
} catch (error) { } catch (error) {
console.error('获取课程安排列表错误:', error) console.error('获取课程安排列表错误:', error)
// 当发生错误时,返回模拟数据 uni.showModal({
return await this.getCourseScheduleListMock(data) title: '获取课程安排列表错误',
content: JSON.stringify(error),
showCancel: false,
})
} }
}, },
// 获取课程安排详情
async getCourseScheduleDetail(data = {}) {
try {
const response = await http.get('/course-schedule/detail/' + data.schedule_id)
return response
} catch (error) {
console.error('获取课程安排详情错误:', error)
// 当发生错误时,返回模拟数据
return await this.getCourseScheduleInfoMock(data)
}
},
// 申请课程请假 // 申请课程请假
async requestCourseLeave(data = {}) { async requestCourseLeave(data = {}) {
@ -1208,14 +1200,6 @@ export default {
} }
} }
}, },
// 学员加入课程安排
async joinCourseSchedule(data = {}) {
return await http.post('/courseSchedule/joinSchedule', data)
},
// 学员退出课程安排
async leaveCourseSchedule(data = {}) {
return await http.post('/courseSchedule/leaveSchedule', data)
},
// 获取筛选选项 // 获取筛选选项
async getCourseScheduleFilterOptions(data = {}) { async getCourseScheduleFilterOptions(data = {}) {
return await http.get('/courseSchedule/filterOptions', data) return await http.get('/courseSchedule/filterOptions', data)
@ -1227,14 +1211,14 @@ export default {
}, },
//↓↓↓↓↓↓↓↓↓↓↓↓-----添加课程安排页面专用接口-----↓↓↓↓↓↓↓↓↓↓↓↓ //↓↓↓↓↓↓↓↓↓↓↓↓-----添加课程安排页面专用接口-----↓↓↓↓↓↓↓↓↓↓↓↓
// 获取所有排课选项(统一接口-支持校区过滤) // 获取所有排课选项(统一接口-支持校区过滤)
async getAllScheduleOptions(data = {}) { async getAllScheduleOptions(data = {}) {
const token = uni.getStorageSync('token') const token = uni.getStorageSync('token')
const apiPath = token ? '/schedule/options/all' : '/test/schedule/options/all' const apiPath = token ? '/schedule/options/all' : '/test/schedule/options/all'
return await http.get(apiPath, data) return await http.get(apiPath, data)
}, },
// 获取课程列表(用于添加课程安排) // 获取课程列表(用于添加课程安排)
async getCourseListForSchedule(data = {}) { async getCourseListForSchedule(data = {}) {
// 检查是否有token,如果没有则使用测试接口 // 检查是否有token,如果没有则使用测试接口
@ -1310,21 +1294,6 @@ export default {
} }
}, },
// 升级学员(统一接口)
async upgradeStudent(data = {}) {
try {
const response = await http.post('/course/upgradeStudent', data)
return response
} catch (error) {
console.error('升级学员失败:', error)
// 返回模拟成功响应
return {
code: 1,
msg: '升级学员成功(模拟)',
data: {}
}
}
},
// 恢复学员(统一接口) // 恢复学员(统一接口)
async restoreStudent(data = {}) { async restoreStudent(data = {}) {
try { try {
@ -1342,7 +1311,7 @@ export default {
}, },
//↓↓↓↓↓↓↓↓↓↓↓↓-----统一课程安排详情管理接口(与admin端保持一致)-----↓↓↓↓↓↓↓↓↓↓↓↓ //↓↓↓↓↓↓↓↓↓↓↓↓-----统一课程安排详情管理接口(与admin端保持一致)-----↓↓↓↓↓↓↓↓↓↓↓↓
// 获取课程安排详情(新统一接口 - 对接admin端) // 获取课程安排详情(新统一接口 - 对接admin端)
async getCourseArrangementDetail(data = {}) { async getCourseArrangementDetail(data = {}) {
try { try {
@ -1431,6 +1400,22 @@ export default {
} }
}, },
// 删除学员所有未来课程安排
async deleteStudentFutureSchedules(data = {}) {
try {
const response = await http.post('/courseSchedule/deleteStudentFutureSchedules', data)
return response
} catch (error) {
console.error('删除学员未来课程安排失败:', error)
// 返回模拟成功响应
return {
code: 1,
msg: '删除成功(模拟)',
data: { deleted_count: 0 }
}
}
},
// 恢复学员(新统一接口 - 对接admin端) // 恢复学员(新统一接口 - 对接admin端)
async restoreStudentInArrangement(data = {}) { async restoreStudentInArrangement(data = {}) {
try { try {
@ -1744,7 +1729,7 @@ export default {
signature_image: data.signature_image, signature_image: data.signature_image,
}) })
}, },
// 员工端提交合同签署 // 员工端提交合同签署
async signStaffContract(data = {}) { async signStaffContract(data = {}) {
return await http.post('/contract/signStaffContract', { return await http.post('/contract/signStaffContract', {
@ -2516,4 +2501,4 @@ export default {
// 复用消息列表的Mock数据,根据关键词进行筛选 // 复用消息列表的Mock数据,根据关键词进行筛选
return await this.getStudentMessageListMock(data) return await this.getStudentMessageListMock(data)
}, },
} }

387
uniapp/components/schedule/ScheduleDetail.vue

@ -31,7 +31,7 @@
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="item-label">课程状态</text> <text class="item-label">课程状态</text>
<text :class="['item-value',statusClass]">{{ scheduleInfo.status_text }}</text> <text :class="['item-value',statusClass]">{{ scheduleInfo.status_text || statusText }}</text>
</view> </view>
<view class="info-item"> <view class="info-item">
<text class="item-label">班级</text> <text class="item-label">班级</text>
@ -65,31 +65,31 @@
</view> </view>
</view> </view>
<view class="cards-grid" v-if="formalStudents && formalStudents.length > 0"> <view class="cards-grid" v-if="formalStudents && formalStudents.length > 0">
<view class="student-card filled" v-for="(student, index) in formalStudents" :key="index" <view class="student-card filled" v-for="(student, index) in formalStudents" :key="index"
@click="handleStudentClick(student, index)"> @click="handleStudentClick(student, index)">
<!-- 续费提醒徽章 --> <!-- 续费提醒徽章 -->
<view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view> <view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view>
<!-- 体验课学员标识 --> <!-- 体验课学员标识 -->
<view v-if="student.isTrialStudent" class="trial-badge">体验课</view> <view v-if="student.isTrialStudent" class="trial-badge">体验课</view>
<view class="avatar">{{ student.name.charAt(0) }}</view> <view class="avatar">{{ student.name.charAt(0) }}</view>
<view class="student-info"> <view class="student-info">
<view class="student-name">{{ student.name }}</view> <view class="student-name">{{ student.name }}</view>
<view class="student-age">年龄{{ student.age }}</view> <view class="student-age">年龄{{ student.age }}</view>
<view class="course-status">课程状态{{ student.courseStatus }}</view> <view class="course-status">课程状态{{ student.courseStatus }}</view>
<view class="course-arrangement">课程安排{{ student.courseType === 'fixed' ? '固定课' : '临时课' }}</view> <view class="course-arrangement">课程安排{{ student.courseType === 'fixed' ? '固定课' : '临时课' }}</view>
<!-- 体验课学员显示 --> <!-- 体验课学员显示 -->
<view v-if="student.isTrialStudent" class="trial-info"> <view v-if="student.isTrialStudent" class="trial-info">
<view class="trial-hours">体验课时{{ student.trialClassCount }}</view> <view class="trial-hours">体验课时{{ student.trialClassCount }}</view>
</view> </view>
<!-- 付费学员显示 --> <!-- 付费学员显示 -->
<view v-else class="paid-student-info"> <view v-else class="paid-student-info">
<view class="remaining-hours">剩余课时{{ student.remainingHours }}</view> <view class="remaining-hours">剩余课时{{ student.remainingHours }}</view>
<view class="expiry-date">到期时间{{ student.expiryDate || '未设置' }}</view> <view class="expiry-date">到期时间{{ student.expiryDate || '未设置' }}</view>
<!-- 课时进度条 --> <!-- 课时进度条 -->
<view class="progress-container"> <view class="progress-container">
<view class="progress-label"> <view class="progress-label">
@ -97,8 +97,8 @@
<text class="progress-percentage">{{ student.course_progress.percentage }}%</text> <text class="progress-percentage">{{ student.course_progress.percentage }}%</text>
</view> </view>
<view class="progress-bar"> <view class="progress-bar">
<view <view
class="progress-fill" class="progress-fill"
:style="{ width: student.course_progress.percentage + '%' }" :style="{ width: student.course_progress.percentage + '%' }"
></view> ></view>
</view> </view>
@ -121,34 +121,34 @@
</view> </view>
</view> </view>
<view class="cards-grid"> <view class="cards-grid">
<view class="student-card filled waiting-filled" v-for="(student, index) in waitingStudents" :key="index" <view class="student-card filled waiting-filled" v-for="(student, index) in waitingStudents" :key="index"
@click="handleStudentClick(student, index)"> @click="handleStudentClick(student, index)">
<!-- 转换提示图标 --> <!-- 转换提示图标 -->
<view class="convert-icon"></view> <view class="convert-icon"></view>
<!-- 续费提醒徽章 --> <!-- 续费提醒徽章 -->
<view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view> <view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view>
<!-- 体验课学员标识 --> <!-- 体验课学员标识 -->
<view v-if="student.isTrialStudent" class="trial-badge">体验课</view> <view v-if="student.isTrialStudent" class="trial-badge">体验课</view>
<view class="avatar waiting-avatar">{{ student.name.charAt(0) }}</view> <view class="avatar waiting-avatar">{{ student.name.charAt(0) }}</view>
<view class="student-info"> <view class="student-info">
<view class="student-name">{{ student.name }}</view> <view class="student-name">{{ student.name }}</view>
<view class="student-age">年龄{{ student.age }}</view> <view class="student-age">年龄{{ student.age }}</view>
<view class="course-status">课程状态{{ student.courseStatus }}</view> <view class="course-status">课程状态{{ student.courseStatus }}</view>
<view class="course-arrangement">课程安排等待位</view> <view class="course-arrangement">课程安排等待位</view>
<!-- 体验课学员显示 --> <!-- 体验课学员显示 -->
<view v-if="student.isTrialStudent" class="trial-info"> <view v-if="student.isTrialStudent" class="trial-info">
<view class="trial-hours">体验课时{{ student.trialClassCount }}</view> <view class="trial-hours">体验课时{{ student.trialClassCount }}</view>
</view> </view>
<!-- 付费学员显示 --> <!-- 付费学员显示 -->
<view v-else class="paid-student-info"> <view v-else class="paid-student-info">
<view class="remaining-hours">剩余课时{{ student.remainingHours }}</view> <view class="remaining-hours">剩余课时{{ student.remainingHours }}</view>
<view class="expiry-date">到期时间{{ student.expiryDate || '未设置' }}</view> <view class="expiry-date">到期时间{{ student.expiryDate || '未设置' }}</view>
<!-- 课时进度条 --> <!-- 课时进度条 -->
<view class="progress-container"> <view class="progress-container">
<view class="progress-label"> <view class="progress-label">
@ -156,8 +156,8 @@
<text class="progress-percentage">{{ student.course_progress.percentage }}%</text> <text class="progress-percentage">{{ student.course_progress.percentage }}%</text>
</view> </view>
<view class="progress-bar"> <view class="progress-bar">
<view <view
class="progress-fill" class="progress-fill"
:style="{ width: student.course_progress.percentage + '%' }" :style="{ width: student.course_progress.percentage + '%' }"
></view> ></view>
</view> </view>
@ -170,8 +170,12 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<view class="action-buttons"> <view class="action-buttons">
<fui-button type="primary" @click="handleEditCourse">编辑课程</fui-button> <view class="action-button edit-btn" @click="handleEditCourse">
<fui-button type="success" @click="handleAddNewCourse">新增课程</fui-button> <text class="btn-text">编辑课程</text>
</view>
<view class="action-button add-btn" @click="handleAddNewCourse">
<text class="btn-text">新增课程</text>
</view>
</view> </view>
</view> </view>
@ -187,7 +191,7 @@
<text>重试</text> <text>重试</text>
</view> </view>
</view> </view>
<!-- 学员点名底部弹窗 --> <!-- 学员点名底部弹窗 -->
<fui-modal :show="showAttendanceModal" title="学员点名" @cancel="closeAttendanceModal" :buttons="[]"> <fui-modal :show="showAttendanceModal" title="学员点名" @cancel="closeAttendanceModal" :buttons="[]">
<view class="attendance-modal" v-if="selectedStudent"> <view class="attendance-modal" v-if="selectedStudent">
@ -198,12 +202,12 @@
<view class="student-name-large">{{ selectedStudent.name }}</view> <view class="student-name-large">{{ selectedStudent.name }}</view>
<view class="current-status">当前状态{{ selectedStudent.status_text }}</view> <view class="current-status">当前状态{{ selectedStudent.status_text }}</view>
</view> </view>
<view class="attendance-options"> <view class="attendance-options">
<view class="option-btn sign-in" @click="handleAttendanceAction('sign_in')"> <view class="option-btn sign-in" @click="handleAttendanceAction('sign_in')">
<text>签到</text> <text>签到</text>
</view> </view>
<view class="option-btn leave" @click="handleAttendanceAction('leave')"> <view v-if="!selectedStudent.isTrialStudent" class="option-btn leave" @click="handleAttendanceAction('leave')">
<text>请假</text> <text>请假</text>
</view> </view>
<view class="option-btn cancel" @click="closeAttendanceModal"> <view class="option-btn cancel" @click="closeAttendanceModal">
@ -212,7 +216,7 @@
</view> </view>
</view> </view>
</fui-modal> </fui-modal>
<!-- 升级确认弹窗 --> <!-- 升级确认弹窗 -->
<fui-modal :show="showUpgradeConfirm" title="升级确认" @cancel="cancelUpgrade" :buttons="[]" :zIndex="10000"> <fui-modal :show="showUpgradeConfirm" title="升级确认" @cancel="cancelUpgrade" :buttons="[]" :zIndex="10000">
<view class="upgrade-confirm-modal" v-if="upgradeStudent"> <view class="upgrade-confirm-modal" v-if="upgradeStudent">
@ -225,7 +229,7 @@
升级后将占用一个正式位 升级后将占用一个正式位
</view> </view>
</view> </view>
<view class="confirm-buttons"> <view class="confirm-buttons">
<view class="confirm-btn cancel-btn" @click="cancelUpgrade"> <view class="confirm-btn cancel-btn" @click="cancelUpgrade">
<text>取消</text> <text>取消</text>
@ -236,6 +240,33 @@
</view> </view>
</view> </view>
</fui-modal> </fui-modal>
<!-- 删除课程安排确认弹窗 -->
<fui-modal :show="showDeleteSchedulesModal" title="删除确认" @cancel="closeDeleteSchedulesModal" :buttons="[]" :zIndex="10001">
<view class="delete-confirm-modal" v-if="studentToDelete">
<view class="confirm-content">
<view class="error-icon"></view>
<view class="error-message">
{{ deleteErrorMessage }}
</view>
<view class="confirm-text">
是否删除学员 <text class="student-name-highlight">{{ studentToDelete.name }}</text> 的所有未来课程安排
</view>
<view class="confirm-tip">
删除后该学员的所有未来课程安排将被清除但历史记录会保留
</view>
</view>
<view class="confirm-buttons">
<view class="confirm-btn cancel-btn" @click="closeDeleteSchedulesModal">
<text>取消</text>
</view>
<view class="confirm-btn delete-btn" @click="confirmDeleteSchedules">
<text>确认删除</text>
</view>
</view>
</view>
</fui-modal>
</fui-modal> </fui-modal>
</template> </template>
@ -273,6 +304,15 @@
}; };
return statusMap[this.scheduleInfo?.status] || ''; return statusMap[this.scheduleInfo?.status] || '';
}, },
statusText() {
const statusTextMap = {
'pending': '待开始',
'upcoming': '即将开始',
'ongoing': '进行中',
'completed': '已结束'
};
return statusTextMap[this.scheduleInfo?.status] || '待开始';
},
studentList() { studentList() {
const statusMap = { const statusMap = {
0: 'status-absent', 0: 'status-absent',
@ -289,12 +329,12 @@
// //
progressPercentage() { progressPercentage() {
if (!this.scheduleInfo || !this.scheduleInfo.course_info) return 0; if (!this.scheduleInfo || !this.scheduleInfo.course_info) return 0;
const totalHours = this.scheduleInfo.course_info.total_hours || 0; const totalHours = this.scheduleInfo.course_info.total_hours || 0;
const usedHours = this.scheduleInfo.course_info.use_total_hours || 0; const usedHours = this.scheduleInfo.course_info.use_total_hours || 0;
if (totalHours <= 0) return 0; if (totalHours <= 0) return 0;
const percentage = Math.round((usedHours / totalHours) * 100); const percentage = Math.round((usedHours / totalHours) * 100);
return Math.min(percentage, 100); // 100% return Math.min(percentage, 100); // 100%
} }
@ -310,7 +350,10 @@
selectedStudentIndex: -1, selectedStudentIndex: -1,
showUpgradeConfirm: false, showUpgradeConfirm: false,
upgradeStudent: null, upgradeStudent: null,
upgradeStudentIndex: -1 upgradeStudentIndex: -1,
showDeleteSchedulesModal: false,
deleteErrorMessage: '',
studentToDelete: null
} }
}, },
watch: { watch: {
@ -334,7 +377,7 @@
this.closePopup(); this.closePopup();
} }
}, },
// 使API - admin // 使API - admin
async fetchScheduleDetail() { async fetchScheduleDetail() {
if (!this.scheduleId) { if (!this.scheduleId) {
@ -352,16 +395,16 @@
const res = await api.getCourseArrangementDetail({ const res = await api.getCourseArrangementDetail({
schedule_id: this.scheduleId schedule_id: this.scheduleId
}); });
if (res.code === 1) { if (res.code === 1) {
// //
const data = res.data; const data = res.data;
// 使APIadmin // 使APIadmin
if (data.schedule_info) { if (data.schedule_info) {
// schedule_infoformal_studentswaiting_students // schedule_infoformal_studentswaiting_students
const allStudents = [...(data.formal_students || []), ...(data.waiting_students || [])]; const allStudents = [...(data.formal_students || []), ...(data.waiting_students || [])];
this.scheduleInfo = { this.scheduleInfo = {
// schedule_info // schedule_info
id: data.schedule_info.id, id: data.schedule_info.id,
@ -375,7 +418,7 @@
coach_avatar: data.schedule_info.coach_avatar || '', coach_avatar: data.schedule_info.coach_avatar || '',
// //
status: data.schedule_info.status, status: data.schedule_info.status,
status_text: data.schedule_info.status_text || '待定', status_text: data.schedule_info.status_text || this.getStatusTextFromCode(data.schedule_info.status),
// //
class_info: data.schedule_info.class_info || null, class_info: data.schedule_info.class_info || null,
// //
@ -427,7 +470,7 @@
coach_avatar: data.coach_avatar || '', coach_avatar: data.coach_avatar || '',
// //
status: data.status, status: data.status,
status_text: data.status_text || '待定', status_text: data.status_text || this.getStatusTextFromCode(data.status),
// //
class_info: data.class_info || null, class_info: data.class_info || null,
// //
@ -465,7 +508,7 @@
remaining_capacity: data.remaining_capacity || 0 remaining_capacity: data.remaining_capacity || 0
}; };
} }
console.log('课程安排详情加载成功:', this.scheduleInfo); console.log('课程安排详情加载成功:', this.scheduleInfo);
} else { } else {
uni.showToast({ uni.showToast({
@ -530,7 +573,7 @@
this.upgradeStudentIndex = index; this.upgradeStudentIndex = index;
this.showUpgradeConfirm = true; this.showUpgradeConfirm = true;
}, },
// //
confirmUpgrade() { confirmUpgrade() {
this.showUpgradeConfirm = false; this.showUpgradeConfirm = false;
@ -538,7 +581,7 @@
this.convertWaitingToFormal(this.upgradeStudent, this.upgradeStudentIndex); this.convertWaitingToFormal(this.upgradeStudent, this.upgradeStudentIndex);
} }
}, },
// //
cancelUpgrade() { cancelUpgrade() {
this.showUpgradeConfirm = false; this.showUpgradeConfirm = false;
@ -546,40 +589,39 @@
this.upgradeStudentIndex = -1; this.upgradeStudentIndex = -1;
}, },
// 使API - admin //
async convertWaitingToFormal(student, index) { async convertWaitingToFormal(student, index) {
try { try {
uni.showLoading({ uni.showLoading({
title: '升级中...' title: '升级中...'
}); });
// 使APIadmin console.log('升级学员信息:', student);
// - 使
const upgradeData = { const upgradeData = {
schedule_id: this.scheduleId, schedule_id: this.scheduleId,
person_id: student.person_id || student.id, person_id: student.person_id || null, // person_id
person_type: student.person_type || 'customer_resource', person_type: student.person_type || 'customer_resource',
from_type: 2, // from_schedule_type: 2, //
to_type: 1 // to_schedule_type: 1, //
resources_id: student.resources_id || student.resource_id || student.id
}; };
// ID // student_id
if (student.person_type === 'student') { if (student.person_type === 'student') {
upgradeData.student_id = student.student_id || student.id; upgradeData.student_id = student.student_id || student.id;
} else {
upgradeData.resources_id = student.resources_id || student.resource_id || student.id;
} }
console.log('升级学员数据:', upgradeData); //
const response = await api.upgradeStudentSchedule(upgradeData);
//
const response = await api.upgradeStudentInArrangement(upgradeData);
if (response.code === 1) { if (response.code === 1) {
// //
await this.fetchScheduleDetail(); await this.fetchScheduleDetail();
uni.showToast({ uni.showToast({
title: `学员 ${student.name} 升级成功!已移至正式位`, title: `学员 ${student.name} 升级成功!已转为正式学员`,
icon: 'success', icon: 'success',
duration: 3000 duration: 3000
}); });
@ -591,8 +633,8 @@
}); });
} }
} catch (error) { } catch (error) {
console.error('升级等待位学员失败:', error); console.error('升级学员失败:', error);
let errorMessage = '升级失败,请重试'; let errorMessage = '升级失败,请重试';
if (error.message) { if (error.message) {
if (error.message.includes('timeout')) { if (error.message.includes('timeout')) {
@ -601,7 +643,7 @@
errorMessage = '网络异常,请检查网络连接'; errorMessage = '网络异常,请检查网络连接';
} }
} }
uni.showToast({ uni.showToast({
title: errorMessage, title: errorMessage,
icon: 'none', icon: 'none',
@ -619,15 +661,84 @@
this.selectedStudentIndex = -1; this.selectedStudentIndex = -1;
}, },
//
showDeleteSchedulesConfirm(errorMessage) {
this.deleteErrorMessage = errorMessage;
this.studentToDelete = this.selectedStudent;
this.showDeleteSchedulesModal = true;
},
//
closeDeleteSchedulesModal() {
this.showDeleteSchedulesModal = false;
this.deleteErrorMessage = '';
this.studentToDelete = null;
},
//
async confirmDeleteSchedules() {
if (!this.studentToDelete) return;
try {
uni.showLoading({
title: '删除中...'
});
// API
const deleteData = {
student_id: this.studentToDelete.id,
reason: '体验课次数用完,清理未来课程安排'
};
// resources_id
if (this.studentToDelete.resources_id) {
deleteData.resources_id = this.studentToDelete.resources_id;
}
const response = await api.deleteStudentFutureSchedules(deleteData);
if (response.code === 1) {
//
const deletedCount = response.data?.deleted_count || 0;
uni.showToast({
title: `成功删除${deletedCount}个未来课程安排`,
icon: 'success',
duration: 3000
});
//
this.fetchScheduleDetail();
} else {
uni.showToast({
title: response.msg || '删除失败',
icon: 'none',
duration: 3000
});
}
} catch (error) {
console.error('删除课程安排失败:', error);
uni.showToast({
title: '删除失败,请重试',
icon: 'none',
duration: 3000
});
} finally {
uni.hideLoading();
this.closeDeleteSchedulesModal();
this.closeAttendanceModal();
}
},
// 使API - admin // 使API - admin
async handleAttendanceAction(action) { async handleAttendanceAction(action) {
if (!this.selectedStudent) return; if (!this.selectedStudent) return;
const actionMap = { const actionMap = {
'sign_in': { status: 1, text: '已签到' }, 'sign_in': { status: 1, text: '已签到' },
'leave': { status: 2, text: '请假' } 'leave': { status: 2, text: '请假' }
}; };
if (actionMap[action]) { if (actionMap[action]) {
try { try {
uni.showLoading({ uni.showLoading({
@ -654,23 +765,23 @@
if (action === 'leave') { if (action === 'leave') {
updateData.reason = '移动端请假操作'; updateData.reason = '移动端请假操作';
} }
console.log('更新学员状态数据:', updateData); console.log('更新学员状态数据:', updateData);
// //
const response = await api.updateStudentStatusInArrangement(updateData); const response = await api.updateStudentStatusInArrangement(updateData);
if (response.code === 1) { if (response.code === 1) {
// API // API
this.selectedStudent.status = actionMap[action].status; this.selectedStudent.status = actionMap[action].status;
this.selectedStudent.status_text = actionMap[action].text; this.selectedStudent.status_text = actionMap[action].text;
this.selectedStudent.statusClass = this.getStudentStatusClass(actionMap[action].status); this.selectedStudent.statusClass = this.getStudentStatusClass(actionMap[action].status);
// scheduleInfo // scheduleInfo
if (this.scheduleInfo && this.scheduleInfo.students && this.selectedStudentIndex >= 0) { if (this.scheduleInfo && this.scheduleInfo.students && this.selectedStudentIndex >= 0) {
this.$set(this.scheduleInfo.students, this.selectedStudentIndex, this.selectedStudent); this.$set(this.scheduleInfo.students, this.selectedStudentIndex, this.selectedStudent);
} }
// //
this.$emit('student-attendance', { this.$emit('student-attendance', {
scheduleId: this.scheduleId, scheduleId: this.scheduleId,
@ -679,7 +790,7 @@
status: actionMap[action].status, status: actionMap[action].status,
studentName: this.selectedStudent.name studentName: this.selectedStudent.name
}); });
uni.showToast({ uni.showToast({
title: `${this.selectedStudent.name} ${actionMap[action].text}成功`, title: `${this.selectedStudent.name} ${actionMap[action].text}成功`,
icon: 'success', icon: 'success',
@ -687,15 +798,21 @@
}); });
} else { } else {
// API // API
uni.showToast({ //
title: response.msg || `${actionMap[action].text}失败`, if (response.msg && response.msg.includes('体验课次数已用完') && action === 'checkin') {
icon: 'none', //
duration: 3000 this.showDeleteSchedulesConfirm(response.msg);
}); } else {
uni.showToast({
title: response.msg || `${actionMap[action].text}失败`,
icon: 'none',
duration: 3000
});
}
} }
} catch (error) { } catch (error) {
console.error(`${action} API调用失败:`, error); console.error(`${action} API调用失败:`, error);
let errorMessage = `${actionMap[action].text}失败,请重试`; let errorMessage = `${actionMap[action].text}失败,请重试`;
if (error.message) { if (error.message) {
if (error.message.includes('timeout')) { if (error.message.includes('timeout')) {
@ -704,7 +821,7 @@
errorMessage = '网络异常,请检查网络连接'; errorMessage = '网络异常,请检查网络连接';
} }
} }
uni.showToast({ uni.showToast({
title: errorMessage, title: errorMessage,
icon: 'none', icon: 'none',
@ -714,7 +831,7 @@
uni.hideLoading(); uni.hideLoading();
} }
} }
this.closeAttendanceModal(); this.closeAttendanceModal();
}, },
@ -736,7 +853,18 @@
}; };
return statusTextMap[status] || '未知状态'; return statusTextMap[status] || '未知状态';
}, },
//
getStatusTextFromCode(statusCode) {
const statusTextMap = {
'pending': '待开始',
'upcoming': '即将开始',
'ongoing': '进行中',
'completed': '已结束'
};
return statusTextMap[statusCode] || '待开始';
},
// //
batchCheckIn() { batchCheckIn() {
uni.navigateTo({ uni.navigateTo({
@ -762,7 +890,7 @@
// //
const url = `/pages-market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`; const url = `/pages-market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`;
console.log('跳转到学员管理页面:', url); console.log('跳转到学员管理页面:', url);
uni.navigateTo({ uni.navigateTo({
url: url, url: url,
success: () => { success: () => {
@ -1019,9 +1147,59 @@
.action-buttons { .action-buttons {
display: flex; display: flex;
justify-content: space-around; justify-content: space-between;
margin-top: 30rpx; margin-top: 30rpx;
gap: 30rpx; gap: 20rpx;
width: 100%;
}
.action-button {
flex: 1;
height: 80rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
&:active {
transform: scale(0.98);
}
.btn-text {
font-size: 28rpx;
font-weight: 600;
color: #fff;
}
}
.edit-btn {
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
border: 1px solid #007bff;
&:hover {
background: linear-gradient(135deg, #0056b3 0%, #004085 100%);
}
&:active {
background: linear-gradient(135deg, #004085 0%, #002752 100%);
}
}
.add-btn {
background: linear-gradient(135deg, #29d3b4 0%, #22a68b 100%);
border: 1px solid #29d3b4;
&:hover {
background: linear-gradient(135deg, #22a68b 0%, #1a8b6f 100%);
}
&:active {
background: linear-gradient(135deg, #1a8b6f 0%, #136045 100%);
}
} }
@ -1138,8 +1316,8 @@
/* 学员卡片网格样式 */ /* 学员卡片网格样式 */
.cards-grid { .cards-grid {
display: grid; display: flex;
grid-template-columns: 1fr 1fr; flex-wrap: wrap;
gap: 16rpx; gap: 16rpx;
margin-top: 20rpx; margin-top: 20rpx;
} }
@ -1153,6 +1331,12 @@
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
border: 2px solid transparent; border: 2px solid transparent;
width: calc(50% - 8rpx);
flex: 0 0 calc(50% - 8rpx);
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
} }
.student-card.filled { .student-card.filled {
@ -1242,6 +1426,7 @@
font-weight: 600; font-weight: 600;
font-size: 24rpx; font-size: 24rpx;
margin-bottom: 12rpx; margin-bottom: 12rpx;
flex-shrink: 0;
} }
.waiting-avatar { .waiting-avatar {
@ -1252,6 +1437,10 @@
.student-info { .student-info {
font-size: 22rpx; font-size: 22rpx;
line-height: 1.4; line-height: 1.4;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
} }
.student-name { .student-name {
@ -1262,6 +1451,8 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
width: 100%;
box-sizing: border-box;
} }
.student-age, .student-age,
@ -1329,7 +1520,7 @@
border-radius: 4rpx; border-radius: 4rpx;
transition: width 0.3s ease; transition: width 0.3s ease;
} }
/* 升级确认弹窗样式 */ /* 升级确认弹窗样式 */
.upgrade-confirm-modal { .upgrade-confirm-modal {
padding: 40rpx 30rpx; padding: 40rpx 30rpx;
@ -1425,4 +1616,46 @@
background: linear-gradient(45deg, #7c3aed, #9333ea); background: linear-gradient(45deg, #7c3aed, #9333ea);
transform: scale(0.98); transform: scale(0.98);
} }
</style>
/* 删除确认弹窗样式 */
.delete-confirm-modal {
padding: 40rpx 30rpx;
text-align: center;
}
.error-icon {
font-size: 60rpx;
margin-bottom: 20rpx;
color: #ef4444;
animation: shake 2s ease-in-out infinite;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5rpx); }
75% { transform: translateX(5rpx); }
}
.error-message {
font-size: 28rpx;
color: #ef4444;
font-weight: 600;
margin-bottom: 20rpx;
padding: 20rpx;
background: rgba(239, 68, 68, 0.1);
border-radius: 16rpx;
border: 2rpx solid rgba(239, 68, 68, 0.2);
}
.delete-btn {
background: linear-gradient(45deg, #ef4444, #f87171);
color: #fff;
border: none;
transition: all 0.3s ease;
&:active {
transform: scale(0.95);
box-shadow: 0 2rpx 8rpx rgba(239, 68, 68, 0.4);
}
}
</style>

175
uniapp/components/student-info-card/student-info-card.vue

@ -2,27 +2,33 @@
<template> <template>
<view class="student-info-card" > <view class="student-info-card" >
<!-- 学生基本信息 --> <!-- 学生基本信息 -->
<view class="student-header" @click="handleStudentClick"> <view class="student-header">
<view class="student-avatar"> <view class="student-content" @click="handleStudentClick">
<text>{{ student.name ? student.name.charAt(0) : '学' }}</text> <view class="student-avatar">
</view> <text>{{ student.name ? student.name.charAt(0) : '学' }}</text>
<view class="student-details">
<view class="student-name">{{ student.name || '未知学生' }}</view>
<view class="student-meta">
<text class="student-age">{{ calculateAge(student.birthday) }}</text>
<text class="student-gender">{{ formatGender(student.gender) }}</text>
</view> </view>
<!-- 学员标签 --> <view class="student-details">
<view class="student-tags" v-if="student.student_tags && student.student_tags.length > 0"> <view class="student-name">{{ student.name || '未知学生' }}</view>
<view <view class="student-meta">
class="student-tag" <text class="student-age">{{ calculateAge(student.birthday) }}</text>
v-for="tag in student.student_tags" <text class="student-gender">{{ formatGender(student.gender) }}</text>
:key="tag" </view>
> <!-- 学员标签 -->
{{ tag }} <view class="student-tags" v-if="student.student_tags && student.student_tags.length > 0">
<view
class="student-tag"
v-for="tag in student.student_tags"
:key="tag"
>
{{ tag }}
</view>
</view> </view>
</view> </view>
</view> </view>
<!-- 编辑按钮 -->
<view class="edit-button" @click.stop="handleEditClick">
<text class="edit-icon"></text>
</view>
</view > </view >
<!-- 学生详细信息 --> <!-- 学生详细信息 -->
@ -81,6 +87,9 @@ export default {
handleStudentClick(){ handleStudentClick(){
this.$emit('action',{ action: 'edit', student: this.student}) this.$emit('action',{ action: 'edit', student: this.student})
}, },
handleEditClick(){
this.$emit('action',{ action: 'edit', student: this.student})
},
// xx // xx
calculateAge(birthday) { calculateAge(birthday) {
if (!birthday) return '' if (!birthday) return ''
@ -139,72 +148,92 @@ export default {
.student-header { .student-header {
display: flex; display: flex;
align-items: center; align-items: flex-start;
justify-content: space-between;
.student-avatar {
width: 60rpx; .student-content {
height: 60rpx; flex: 1;
border-radius: 50%;
background-color: #29d3b4;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
margin-right: 20rpx; .student-avatar {
width: 60rpx;
text { height: 60rpx;
color: white; border-radius: 50%;
font-size: 24rpx; background-color: #29d3b4;
font-weight: bold;
}
}
.student-details {
flex: 1;
.student-name {
color: white;
font-size: 28rpx;
font-weight: bold;
margin-bottom: 8rpx;
}
.student-meta {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 20rpx; justify-content: center;
margin-bottom: 8rpx; margin-right: 20rpx;
.student-age, text {
.student-gender { color: white;
color: #999; font-size: 24rpx;
font-size: 22rpx; font-weight: bold;
} }
} }
.student-tags { .student-details {
display: flex; flex: 1;
flex-wrap: wrap;
gap: 8rpx; .student-name {
margin-top: 8rpx; color: white;
font-size: 28rpx;
.student-tag { font-weight: bold;
color: #29d3b4; margin-bottom: 8rpx;
font-size: 18rpx; }
background-color: rgba(41, 211, 180, 0.2);
border: 1rpx solid rgba(41, 211, 180, 0.5); .student-meta {
padding: 4rpx 10rpx; display: flex;
border-radius: 10rpx; align-items: center;
display: inline-block; gap: 20rpx;
margin-bottom: 8rpx;
.student-age,
.student-gender {
color: #999;
font-size: 22rpx;
}
}
.student-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-top: 8rpx;
.student-tag {
color: #29d3b4;
font-size: 18rpx;
background-color: rgba(41, 211, 180, 0.2);
border: 1rpx solid rgba(41, 211, 180, 0.5);
padding: 4rpx 10rpx;
border-radius: 10rpx;
display: inline-block;
}
} }
} }
} }
.action-toggle { .edit-button {
padding: 10rpx; width: 50rpx;
height: 50rpx;
.toggle-icon { border-radius: 50%;
background-color: rgba(41, 211, 180, 0.2);
border: 1rpx solid rgba(41, 211, 180, 0.5);
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
.edit-icon {
font-size: 20rpx;
color: #29d3b4; color: #29d3b4;
font-size: 24rpx; }
&:active {
background-color: rgba(41, 211, 180, 0.4);
transform: scale(0.95);
} }
} }
} }

129
uniapp/pages-coach/coach/schedule/adjust_course.vue

@ -5,7 +5,7 @@
<fui-loading></fui-loading> <fui-loading></fui-loading>
<text class="loading-text">加载中...</text> <text class="loading-text">加载中...</text>
</view> </view>
<fui-form v-else> <fui-form v-else>
<!-- 课程信息 --> <!-- 课程信息 -->
<view class="section-title">当前课程信息</view> <view class="section-title">当前课程信息</view>
@ -23,9 +23,12 @@
<text class="info-value">{{ scheduleInfo.time_slot }}</text> <text class="info-value">{{ scheduleInfo.time_slot }}</text>
</view> </view>
<view class="info-row"> <view class="info-row">
<text class="info-label">授课教练</text> <text class="info-label">主教练</text>
<text class="info-value">主教练{{ scheduleInfo.coach_name }}</text> <text class="info-value">{{ scheduleInfo.coach_name }}</text>
<text class="info-value">助教{{ scheduleInfo.coach_name }}</text> </view>
<view class="info-row">
<text class="info-label">助教</text>
<text class="info-value">{{ scheduleInfo.assistants.join(',') }}</text>
</view> </view>
<view class="info-row"> <view class="info-row">
<text class="info-label">上课场地</text> <text class="info-label">上课场地</text>
@ -36,12 +39,12 @@
<text class="info-value">{{ scheduleInfo.class_info ? scheduleInfo.class_info.class_name : '未指定班级' }}</text> <text class="info-value">{{ scheduleInfo.class_info ? scheduleInfo.class_info.class_name : '未指定班级' }}</text>
</view> </view>
</view> </view>
<view class="section-title">调整后信息</view> <view class="section-title">调整后信息</view>
<!-- 班级选择 --> <!-- 班级选择 -->
<fui-form-item label="所属班级"> <fui-form-item label="所属班级">
<picker <picker
:value="classPickerIndex" :value="classPickerIndex"
:range="classOptions" :range="classOptions"
:range-key="'class_name'" :range-key="'class_name'"
@ -53,10 +56,10 @@
</view> </view>
</picker> </picker>
</fui-form-item> </fui-form-item>
<!-- 教练选择 --> <!-- 教练选择 -->
<fui-form-item label="授课教练"> <fui-form-item label="授课教练">
<picker <picker
:value="coachPickerIndex" :value="coachPickerIndex"
:range="coachOptions" :range="coachOptions"
:range-key="'name'" :range-key="'name'"
@ -68,10 +71,10 @@
</view> </view>
</picker> </picker>
</fui-form-item> </fui-form-item>
<!-- 场地选择 --> <!-- 场地选择 -->
<fui-form-item label="上课场地"> <fui-form-item label="上课场地">
<picker <picker
:value="venuePickerIndex" :value="venuePickerIndex"
:range="venueOptions" :range="venueOptions"
:range-key="'venue_name'" :range-key="'venue_name'"
@ -83,10 +86,10 @@
</view> </view>
</picker> </picker>
</fui-form-item> </fui-form-item>
<!-- 日期选择 --> <!-- 日期选择 -->
<fui-form-item label="上课日期"> <fui-form-item label="上课日期">
<picker <picker
mode="date" mode="date"
:value="formData.course_date || scheduleInfo.course_date || getCurrentDate()" :value="formData.course_date || scheduleInfo.course_date || getCurrentDate()"
:start="getMinDate()" :start="getMinDate()"
@ -99,10 +102,10 @@
</view> </view>
</picker> </picker>
</fui-form-item> </fui-form-item>
<!-- 时间选择 --> <!-- 时间选择 -->
<fui-form-item label="上课时间"> <fui-form-item label="上课时间">
<picker <picker
:value="timePickerIndex" :value="timePickerIndex"
:range="timeSlotOptions" :range="timeSlotOptions"
:range-key="'text'" :range-key="'text'"
@ -114,7 +117,7 @@
</view> </view>
</picker> </picker>
</fui-form-item> </fui-form-item>
<!-- 容量设置 --> <!-- 容量设置 -->
<fui-form-item label="课程容量"> <fui-form-item label="课程容量">
<fui-input <fui-input
@ -124,8 +127,8 @@
@input="formData.available_capacity = $event" @input="formData.available_capacity = $event"
></fui-input> ></fui-input>
</fui-form-item> </fui-form-item>
<!-- 提交按钮 --> <!-- 提交按钮 -->
<view class="btn-container"> <view class="btn-container">
<fui-button type="primary" @click="submitForm" :loading="submitting">确认调整</fui-button> <fui-button type="primary" @click="submitForm" :loading="submitting">确认调整</fui-button>
@ -144,13 +147,13 @@ export default {
// //
loading: true, loading: true,
submitting: false, submitting: false,
// ID // ID
scheduleId: null, scheduleId: null,
// //
scheduleInfo: {}, scheduleInfo: {},
// //
formData: { formData: {
schedule_id: '', schedule_id: '',
@ -161,20 +164,20 @@ export default {
time_slot: '', time_slot: '',
available_capacity: '' available_capacity: ''
}, },
// showDatePicker // showDatePicker
// //
classOptions: [], classOptions: [],
coachOptions: [], coachOptions: [],
venueOptions: [], venueOptions: [],
timeSlotOptions: [], timeSlotOptions: [],
// //
selectedClass: null, selectedClass: null,
selectedCoach: null, selectedCoach: null,
selectedVenue: null, selectedVenue: null,
// picker // picker
classPickerIndex: 0, classPickerIndex: 0,
coachPickerIndex: 0, coachPickerIndex: 0,
@ -182,7 +185,7 @@ export default {
timePickerIndex: 0 timePickerIndex: 0
}; };
}, },
onLoad(options) { onLoad(options) {
if (options.id) { if (options.id) {
this.scheduleId = options.id; this.scheduleId = options.id;
@ -199,21 +202,21 @@ export default {
}, 1500); }, 1500);
} }
}, },
methods: { methods: {
// //
goBack() { goBack() {
uni.navigateBack(); uni.navigateBack();
}, },
// //
async loadScheduleInfo() { async loadScheduleInfo() {
try { try {
const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId }); const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId });
if (res.code === 1) { if (res.code === 1) {
this.scheduleInfo = res.data; this.scheduleInfo = res.data;
// //
this.formData.class_id = this.scheduleInfo.class_id; this.formData.class_id = this.scheduleInfo.class_id;
this.formData.coach_id = this.scheduleInfo.coach_id; this.formData.coach_id = this.scheduleInfo.coach_id;
@ -235,25 +238,25 @@ export default {
}); });
} }
}, },
// //
async loadFilterOptions() { async loadFilterOptions() {
try { try {
const res = await api.getCourseScheduleFilterOptions(); const res = await api.getCourseScheduleFilterOptions();
if (res.code === 1) { if (res.code === 1) {
// //
this.classOptions = res.data.classes || []; this.classOptions = res.data.classes || [];
// //
this.coachOptions = res.data.coaches || []; this.coachOptions = res.data.coaches || [];
// //
this.venueOptions = res.data.venues || []; this.venueOptions = res.data.venues || [];
// //
this.generateTimeSlotOptions(); this.generateTimeSlotOptions();
// //
this.findSelectedOptions(); this.findSelectedOptions();
} else { } else {
@ -272,7 +275,7 @@ export default {
this.loading = false; this.loading = false;
} }
}, },
// //
findSelectedOptions() { findSelectedOptions() {
// //
@ -281,34 +284,34 @@ export default {
this.classPickerIndex = this.classOptions.findIndex(classItem => classItem.id === this.scheduleInfo.class_id); this.classPickerIndex = this.classOptions.findIndex(classItem => classItem.id === this.scheduleInfo.class_id);
if (this.classPickerIndex === -1) this.classPickerIndex = 0; if (this.classPickerIndex === -1) this.classPickerIndex = 0;
} }
// //
if (this.scheduleInfo.coach_id) { if (this.scheduleInfo.coach_id) {
this.selectedCoach = this.coachOptions.find(coach => coach.id === this.scheduleInfo.coach_id); this.selectedCoach = this.coachOptions.find(coach => coach.id === this.scheduleInfo.coach_id);
this.coachPickerIndex = this.coachOptions.findIndex(coach => coach.id === this.scheduleInfo.coach_id); this.coachPickerIndex = this.coachOptions.findIndex(coach => coach.id === this.scheduleInfo.coach_id);
if (this.coachPickerIndex === -1) this.coachPickerIndex = 0; if (this.coachPickerIndex === -1) this.coachPickerIndex = 0;
} }
// //
if (this.scheduleInfo.venue_id) { if (this.scheduleInfo.venue_id) {
this.selectedVenue = this.venueOptions.find(venue => venue.id === this.scheduleInfo.venue_id); this.selectedVenue = this.venueOptions.find(venue => venue.id === this.scheduleInfo.venue_id);
this.venuePickerIndex = this.venueOptions.findIndex(venue => venue.id === this.scheduleInfo.venue_id); this.venuePickerIndex = this.venueOptions.findIndex(venue => venue.id === this.scheduleInfo.venue_id);
if (this.venuePickerIndex === -1) this.venuePickerIndex = 0; if (this.venuePickerIndex === -1) this.venuePickerIndex = 0;
} }
// //
if (this.scheduleInfo.time_slot && this.timeSlotOptions.length > 0) { if (this.scheduleInfo.time_slot && this.timeSlotOptions.length > 0) {
this.timePickerIndex = this.timeSlotOptions.findIndex(time => time.value === this.scheduleInfo.time_slot); this.timePickerIndex = this.timeSlotOptions.findIndex(time => time.value === this.scheduleInfo.time_slot);
if (this.timePickerIndex === -1) this.timePickerIndex = 0; if (this.timePickerIndex === -1) this.timePickerIndex = 0;
} }
}, },
// //
generateTimeSlotOptions() { generateTimeSlotOptions() {
// 使 // 使
this.generateDefaultTimeOptions(); this.generateDefaultTimeOptions();
}, },
// //
onClassSelect(e) { onClassSelect(e) {
const index = e.detail.value; const index = e.detail.value;
@ -318,7 +321,7 @@ export default {
this.formData.class_id = this.selectedClass.id; this.formData.class_id = this.selectedClass.id;
} }
}, },
onCoachSelect(e) { onCoachSelect(e) {
const index = e.detail.value; const index = e.detail.value;
this.coachPickerIndex = index; this.coachPickerIndex = index;
@ -327,28 +330,28 @@ export default {
this.formData.coach_id = this.selectedCoach.id; this.formData.coach_id = this.selectedCoach.id;
} }
}, },
onVenueSelect(e) { onVenueSelect(e) {
const index = e.detail.value; const index = e.detail.value;
this.venuePickerIndex = index; this.venuePickerIndex = index;
if (index >= 0 && index < this.venueOptions.length) { if (index >= 0 && index < this.venueOptions.length) {
this.selectedVenue = this.venueOptions[index]; this.selectedVenue = this.venueOptions[index];
this.formData.venue_id = this.selectedVenue.id; this.formData.venue_id = this.selectedVenue.id;
// //
if (this.selectedVenue.capacity) { if (this.selectedVenue.capacity) {
this.formData.available_capacity = this.selectedVenue.capacity; this.formData.available_capacity = this.selectedVenue.capacity;
} }
// //
this.loadVenueTimeOptions(this.selectedVenue.id); this.loadVenueTimeOptions(this.selectedVenue.id);
} }
}, },
onDateSelect(e) { onDateSelect(e) {
this.formData.course_date = e.detail.value; this.formData.course_date = e.detail.value;
}, },
onTimeSelect(e) { onTimeSelect(e) {
const index = e.detail.value; const index = e.detail.value;
this.timePickerIndex = index; this.timePickerIndex = index;
@ -356,7 +359,7 @@ export default {
this.formData.time_slot = this.timeSlotOptions[index].value; this.formData.time_slot = this.timeSlotOptions[index].value;
} }
}, },
// //
validateForm() { validateForm() {
// //
@ -366,7 +369,7 @@ export default {
this.formData.course_date !== this.scheduleInfo.course_date || this.formData.course_date !== this.scheduleInfo.course_date ||
this.formData.time_slot !== this.scheduleInfo.time_slot || this.formData.time_slot !== this.scheduleInfo.time_slot ||
this.formData.available_capacity !== this.scheduleInfo.available_capacity; this.formData.available_capacity !== this.scheduleInfo.available_capacity;
if (!hasChanges) { if (!hasChanges) {
uni.showToast({ uni.showToast({
title: '未进行任何修改', title: '未进行任何修改',
@ -374,11 +377,11 @@ export default {
}); });
return false; return false;
} }
return true; return true;
}, },
// //
async loadVenueTimeOptions(venueId) { async loadVenueTimeOptions(venueId) {
if (!venueId) { if (!venueId) {
@ -386,10 +389,10 @@ export default {
this.generateDefaultTimeOptions(); this.generateDefaultTimeOptions();
return; return;
} }
try { try {
const res = await api.getVenueTimeOptions({ venue_id: venueId }); const res = await api.getVenueTimeOptions({ venue_id: venueId });
if (res.code === 1) { if (res.code === 1) {
this.timeSlotOptions = res.data.time_options || []; this.timeSlotOptions = res.data.time_options || [];
// picker // picker
@ -411,20 +414,20 @@ export default {
// 8:30 // 8:30
generateDefaultTimeOptions() { generateDefaultTimeOptions() {
const timeSlots = []; const timeSlots = [];
for (let hour = 8; hour < 22; hour++) { for (let hour = 8; hour < 22; hour++) {
const minute = (hour === 8) ? '30' : '00'; // 8:30 const minute = (hour === 8) ? '30' : '00'; // 8:30
const startHour = hour.toString().padStart(2, '0'); const startHour = hour.toString().padStart(2, '0');
const endHour = (hour + 1).toString().padStart(2, '0'); const endHour = (hour + 1).toString().padStart(2, '0');
const startTime = `${startHour}:${minute}`; const startTime = `${startHour}:${minute}`;
const endTime = `${endHour}:${minute}`; const endTime = `${endHour}:${minute}`;
timeSlots.push({ timeSlots.push({
value: `${startTime}-${endTime}`, value: `${startTime}-${endTime}`,
text: `${startTime}-${endTime}` text: `${startTime}-${endTime}`
}); });
} }
this.timeSlotOptions = timeSlots; this.timeSlotOptions = timeSlots;
}, },
@ -469,18 +472,18 @@ export default {
if (!this.validateForm()) { if (!this.validateForm()) {
return; return;
} }
this.submitting = true; this.submitting = true;
try { try {
const res = await api.updateCourseSchedule(this.formData); const res = await api.updateCourseSchedule(this.formData);
if (res.code === 1) { if (res.code === 1) {
uni.showToast({ uni.showToast({
title: '调整成功', title: '调整成功',
icon: 'success' icon: 'success'
}); });
// //
setTimeout(() => { setTimeout(() => {
uni.navigateBack(); uni.navigateBack();
@ -625,4 +628,4 @@ export default {
font-size: 28rpx; font-size: 28rpx;
color: #fff; color: #fff;
} }
</style> </style>

130
uniapp/pages-coach/coach/schedule/sign_in.vue

@ -25,10 +25,10 @@
<view class="student-section"> <view class="student-section">
<view class="section-header"> <view class="section-header">
<view class="section-title">学员点名</view> <view class="section-title">学员点名</view>
<view class="action-buttons"> <!-- <view class="action-buttons">-->
<fui-button type="primary" size="small" @click="checkAllStudents">全部签到</fui-button> <!-- <view class="view-btn primary" @click="checkAllStudents">全部签到</view>-->
<fui-button type="danger" size="small" @click="uncheckAllStudents">全部取消</fui-button> <!-- <view class="view-btn danger" @click="uncheckAllStudents">全部取消</view>-->
</view> <!-- </view>-->
</view> </view>
<view class="empty-list" v-if="studentList.length === 0"> <view class="empty-list" v-if="studentList.length === 0">
@ -50,19 +50,9 @@
</view> </view>
<view class="status-container"> <view class="status-container">
<view class="status-select"> <view class="current-status" @click.stop="toggleStudentStatus(index)"
<view class="status-option" :class="{ active: student.status === 1 }" :class="'status-' + student.status">
@click.stop="setStudentStatus(index, 1)"> {{ student.status_text }}
已到
</view>
<view class="status-option" :class="{ active: student.status === 2 }"
@click.stop="setStudentStatus(index, 2)">
请假
</view>
<view class="status-option" :class="{ active: student.status === 0 }"
@click.stop="setStudentStatus(index, 0)">
未到
</view>
</view> </view>
</view> </view>
</view> </view>
@ -103,6 +93,7 @@
<script> <script>
import api from '@/api/apiRoute.js'; import api from '@/api/apiRoute.js';
import util from '@/common/util.js';
export default { export default {
data() { data() {
@ -121,6 +112,7 @@
// //
classPhoto: '', classPhoto: '',
classPhotoRemoteUrl: '', //
// //
submitting: false submitting: false
@ -276,7 +268,35 @@
sizeType: ['compressed'], sizeType: ['compressed'],
sourceType: ['album', 'camera'], sourceType: ['album', 'camera'],
success: (res) => { success: (res) => {
this.classPhoto = res.tempFilePaths[0]; const tempFilePath = res.tempFilePaths[0];
this.classPhoto = tempFilePath;
//
uni.showLoading({
title: '上传中...'
});
// 使uploadFile
util.uploadFile(
tempFilePath,
(fileData) => {
// URL
this.classPhotoRemoteUrl = fileData.url;
uni.hideLoading();
uni.showToast({
title: '上传成功',
icon: 'success'
});
console.log('图片上传成功:', fileData);
},
(error) => {
//
this.classPhoto = '';
this.classPhotoRemoteUrl = '';
uni.hideLoading();
console.error('图片上传失败:', error);
}
);
}, },
fail: (error) => { fail: (error) => {
console.error('选择图片失败:', error); console.error('选择图片失败:', error);
@ -304,6 +324,7 @@
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
this.classPhoto = ''; this.classPhoto = '';
this.classPhotoRemoteUrl = '';
} }
} }
}); });
@ -311,6 +332,15 @@
// //
async submitSignIn() { async submitSignIn() {
//
if (this.classPhoto && !this.classPhotoRemoteUrl) {
uni.showToast({
title: '图片上传中,请稍候',
icon: 'none'
});
return;
}
// //
const studentData = this.studentList.map(student => ({ const studentData = this.studentList.map(student => ({
student_id: student.student_id, student_id: student.student_id,
@ -322,7 +352,7 @@
schedule_id: this.scheduleId, schedule_id: this.scheduleId,
students: studentData, students: studentData,
remark: this.signInRemark, remark: this.signInRemark,
class_photo: this.classPhoto class_photo: this.classPhotoRemoteUrl || '' // 使
}; };
this.submitting = true; this.submitting = true;
@ -442,6 +472,32 @@
gap: 16rpx; gap: 16rpx;
} }
.view-btn {
padding: 12rpx 24rpx;
font-size: 24rpx;
border-radius: 6rpx;
text-align: center;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
&.primary {
background-color: #29d3b4;
&:active {
background-color: #23b89d;
}
}
&.danger {
background-color: #ff3b30;
&:active {
background-color: #d63126;
}
}
}
.student-list { .student-list {
max-height: 600rpx; max-height: 600rpx;
overflow-y: auto; overflow-y: auto;
@ -518,20 +574,32 @@
margin-left: 16rpx; margin-left: 16rpx;
} }
.status-select { .current-status {
display: flex; padding: 12rpx 20rpx;
gap: 10rpx;
}
.status-option {
padding: 8rpx 16rpx;
font-size: 24rpx; font-size: 24rpx;
border-radius: 30rpx; border-radius: 30rpx;
background-color: #3a3a3a; text-align: center;
color: #fff; min-width: 80rpx;
cursor: pointer;
transition: all 0.3s ease;
&.active { &.status-0 {
background-color: #29d3b4; background-color: #3a3a3a;
color: #fff;
}
&.status-1 {
background-color: #34c759;
color: #fff;
}
&.status-2 {
background-color: #ff9500;
color: #fff;
}
&:active {
transform: scale(0.95);
} }
} }
@ -644,4 +712,4 @@
margin-top: 40rpx; margin-top: 40rpx;
padding-bottom: 40rpx; padding-bottom: 40rpx;
} }
</style> </style>

139
uniapp/pages-market/clue/class_arrangement_detail.vue

@ -6,7 +6,7 @@
<view class="course-detail"> <view class="course-detail">
<text class="course-item">日期{{ schedule_info.course_date }}</text> <text class="course-item">日期{{ schedule_info.course_date }}</text>
<!-- <text class="course-item">课程名称{{ schedule_info.course_name }}</text>--> <!-- <text class="course-item">课程名称{{ schedule_info.course_name }}</text>-->
<text class="course-item">班级名称{{ schedule_info.class_name }}</text> <text class="course-item">班级名称{{ schedule_info.class_name || '未设置' }}</text>
<text class="course-item">课程时间{{ schedule_info.time_slot }}</text> <text class="course-item">课程时间{{ schedule_info.time_slot }}</text>
<text class="course-item">主教练{{ schedule_info.coach_name || '待安排' }}</text> <text class="course-item">主教练{{ schedule_info.coach_name || '待安排' }}</text>
<text class="course-item">场地信息{{ schedule_info.venue_name }}</text> <text class="course-item">场地信息{{ schedule_info.venue_name }}</text>
@ -18,9 +18,9 @@
<text class="section-title">正式学员</text> <text class="section-title">正式学员</text>
<view class="cards-grid"> <view class="cards-grid">
<!-- Student Card with Data --> <!-- Student Card with Data -->
<view <view
v-for="(stu, idx) in formalStudents" v-for="(stu, idx) in formalStudents"
:key="idx" :key="idx"
class="student-card filled" class="student-card filled"
@tap="viewStudent(stu)" @tap="viewStudent(stu)"
> >
@ -38,10 +38,10 @@
</view> </view>
<!-- Empty Slots --> <!-- Empty Slots -->
<view <view
v-for="n in formalEmptySeats" v-for="n in formalEmptySeats"
:key="n" :key="n"
class="student-card empty" class="student-card empty"
:data-type="'formal'" :data-type="'formal'"
:data-index="n" :data-index="n"
@tap="openStudentModal" @tap="openStudentModal"
@ -62,9 +62,9 @@
<text class="section-title waiting-title">等待位</text> <text class="section-title waiting-title">等待位</text>
<view class="cards-grid"> <view class="cards-grid">
<!-- Waiting Students --> <!-- Waiting Students -->
<view <view
v-for="(stu, idx) in waitingStudents" v-for="(stu, idx) in waitingStudents"
:key="idx" :key="idx"
class="student-card waiting filled" class="student-card waiting filled"
@tap="viewStudent(stu)" @tap="viewStudent(stu)"
> >
@ -79,12 +79,12 @@
<view class="expiry-date" v-if="stu.expiryDate">到期时间{{ stu.expiryDate || '未设置' }}</view> <view class="expiry-date" v-if="stu.expiryDate">到期时间{{ stu.expiryDate || '未设置' }}</view>
</view> </view>
</view> </view>
<!-- Waiting Empty Slots --> <!-- Waiting Empty Slots -->
<view <view
v-for="n in waitingEmptySeats" v-for="n in waitingEmptySeats"
:key="n" :key="n"
class="student-card waiting" class="student-card waiting"
:data-type="'waiting'" :data-type="'waiting'"
:data-index="n" :data-index="n"
@tap="openStudentModal" @tap="openStudentModal"
@ -106,7 +106,7 @@
<view class="modal-header"> <view class="modal-header">
<text>添加学员</text> <text>添加学员</text>
</view> </view>
<view class="modal-body"> <view class="modal-body">
<!-- 预设学生信息显示 - 优先显示 --> <!-- 预设学生信息显示 - 优先显示 -->
<view v-if="presetStudent && presetStudent.name && presetStudent.phone" class="form-section"> <view v-if="presetStudent && presetStudent.name && presetStudent.phone" class="form-section">
@ -123,18 +123,18 @@
<!-- Customer Selection - 只在没有预设学生时显示 --> <!-- Customer Selection - 只在没有预设学生时显示 -->
<view v-if="!presetStudent || !presetStudent.name || !presetStudent.phone" class="form-section"> <view v-if="!presetStudent || !presetStudent.name || !presetStudent.phone" class="form-section">
<text class="form-label">客户选择</text> <text class="form-label">客户选择</text>
<input <input
v-model="searchQuery" v-model="searchQuery"
placeholder="请输入手机号或姓名" placeholder="请输入手机号或姓名"
class="search-input" class="search-input"
@input="searchStudents" @input="searchStudents"
/> />
<!-- Search Results --> <!-- Search Results -->
<view v-if="searchResults.length > 0" class="search-results"> <view v-if="searchResults.length > 0" class="search-results">
<view <view
v-for="student in searchResults" v-for="student in searchResults"
:key="student.id" :key="student.id"
:class="['student-item', { selected: selectedStudent && selectedStudent.id === student.id }]" :class="['student-item', { selected: selectedStudent && selectedStudent.id === student.id }]"
@tap="selectStudent(student)" @tap="selectStudent(student)"
@ -159,16 +159,16 @@
<text class="form-label">课程安排</text> <text class="form-label">课程安排</text>
<view class="radio-group"> <view class="radio-group">
<label class="radio-item"> <label class="radio-item">
<radio <radio
value="1" value="1"
:checked="courseArrangement === '1'" :checked="courseArrangement === '1'"
@tap="courseArrangement = '1'" @tap="courseArrangement = '1'"
/> />
<text class="radio-text">临时课</text> <text class="radio-text">临时课</text>
</label> </label>
<label class="radio-item" :class="{ disabled: !canSelectFixedCourse }"> <label class="radio-item" :class="{ disabled: !canSelectFixedCourse }">
<radio <radio
value="2" value="2"
:checked="courseArrangement === '2'" :checked="courseArrangement === '2'"
:disabled="!canSelectFixedCourse" :disabled="!canSelectFixedCourse"
@tap="selectFixedCourse" @tap="selectFixedCourse"
@ -184,7 +184,7 @@
<!-- Remarks --> <!-- Remarks -->
<view class="form-section"> <view class="form-section">
<text class="form-label">备注</text> <text class="form-label">备注</text>
<textarea <textarea
v-model="remarks" v-model="remarks"
placeholder="请输入备注信息" placeholder="请输入备注信息"
class="remarks-textarea" class="remarks-textarea"
@ -209,9 +209,9 @@
<view class="leave-form"> <view class="leave-form">
<view class="leave-label">请假原因</view> <view class="leave-label">请假原因</view>
<view class="leave-input"> <view class="leave-input">
<textarea <textarea
v-model="leaveReason" v-model="leaveReason"
placeholder="请输入请假原因" placeholder="请输入请假原因"
maxlength="200" maxlength="200"
class="leave-textarea" class="leave-textarea"
></textarea> ></textarea>
@ -228,7 +228,7 @@
<script> <script>
import apiRoute from '@/api/apiRoute.js'; import apiRoute from '@/api/apiRoute.js';
export default { export default {
name: 'ClassArrangementDetail', name: 'ClassArrangementDetail',
components: { components: {
@ -248,7 +248,7 @@
currentStudent: null, // currentStudent: null, //
formalEmptySeats: [], // formalEmptySeats: [], //
waitingEmptySeats: [1], // 1 waitingEmptySeats: [1], // 1
// //
showModal: false, showModal: false,
searchQuery: '', searchQuery: '',
@ -266,14 +266,14 @@
this.course_id = query.id || query.schedule_id || ''; this.course_id = query.id || query.schedule_id || '';
this.resource_id = query.resource_id || ''; this.resource_id = query.resource_id || '';
this.student_id = query.student_id || ''; this.student_id = query.student_id || '';
console.log('初始化参数 - course_id:', this.course_id, 'resource_id:', this.resource_id, 'student_id:', this.student_id); console.log('初始化参数 - course_id:', this.course_id, 'resource_id:', this.resource_id, 'student_id:', this.student_id);
// resource_idstudent_id // resource_idstudent_id
if (this.resource_id && this.student_id) { if (this.resource_id && this.student_id) {
this.loadPresetStudent(); this.loadPresetStudent();
} }
// IDAPI // IDAPI
if (this.course_id) { if (this.course_id) {
this.getScheduleDetail(); this.getScheduleDetail();
@ -305,15 +305,15 @@
async loadPresetStudent() { async loadPresetStudent() {
try { try {
// 使API // 使API
const res = await apiRoute.getPresetStudentInfo({ const res = await apiRoute.getPresetStudentInfo({
resource_id: this.resource_id, resource_id: this.resource_id,
student_id: this.student_id student_id: this.student_id
}); });
console.log('预设学员信息完整响应:', res); console.log('预设学员信息完整响应:', res);
if (res.code === 1 && res.data) { if (res.code === 1 && res.data) {
const studentData = res.data; const studentData = res.data;
this.presetStudent = { this.presetStudent = {
id: studentData.student_id || this.student_id, id: studentData.student_id || this.student_id,
name: studentData.name || `学员${this.student_id}`, name: studentData.name || `学员${this.student_id}`,
@ -326,7 +326,7 @@
course_info: studentData.course_info || [], course_info: studentData.course_info || [],
student_course_id: studentData.student_course_id || null student_course_id: studentData.student_course_id || null
}; };
this.selectedStudent = this.presetStudent; this.selectedStudent = this.presetStudent;
console.log('加载预设学生信息成功:', this.presetStudent); console.log('加载预设学生信息成功:', this.presetStudent);
console.log('是否可选择固定课:', this.presetStudent.is_formal_student); console.log('是否可选择固定课:', this.presetStudent.is_formal_student);
@ -341,7 +341,7 @@
this.createFallbackPresetStudent(); this.createFallbackPresetStudent();
} }
}, },
// //
createFallbackPresetStudent() { createFallbackPresetStudent() {
if (this.resource_id && this.student_id) { if (this.resource_id && this.student_id) {
@ -412,7 +412,7 @@
// //
const isPhoneNumber = /^\d+$/.test(this.searchQuery.trim()); const isPhoneNumber = /^\d+$/.test(this.searchQuery.trim());
try { try {
uni.showLoading({ uni.showLoading({
title: '搜索中...' title: '搜索中...'
@ -427,7 +427,7 @@
} }
const res = await apiRoute.searchStudents(params); const res = await apiRoute.searchStudents(params);
uni.hideLoading(); uni.hideLoading();
if (res.code === 1 && Array.isArray(res.data)) { if (res.code === 1 && Array.isArray(res.data)) {
@ -485,7 +485,7 @@
}); });
return; return;
} }
console.log(this.selectedStudent); console.log(this.selectedStudent);
// 2. // 2.
if (!this.selectedStudent.name || !this.selectedStudent.phone) { if (!this.selectedStudent.name || !this.selectedStudent.phone) {
@ -557,7 +557,7 @@
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
console.error('添加学员失败:', error); console.error('添加学员失败:', error);
// //
let errorMsg = '添加失败'; let errorMsg = '添加失败';
if (error && error.data && error.data.msg) { if (error && error.data && error.data.msg) {
@ -567,7 +567,7 @@
} else if (typeof error === 'string') { } else if (typeof error === 'string') {
errorMsg = error; errorMsg = error;
} }
uni.showToast({ uni.showToast({
title: errorMsg, title: errorMsg,
icon: 'none' icon: 'none'
@ -604,7 +604,7 @@
itemList: itemList, itemList: itemList,
success: (res) => { success: (res) => {
const selectedOption = itemList[res.tapIndex]; const selectedOption = itemList[res.tapIndex];
if (selectedOption === '取消课程') { if (selectedOption === '取消课程') {
// //
uni.showModal({ uni.showModal({
@ -665,7 +665,7 @@
} }
}); });
}, },
// //
confirmUpgradeStudent(stu) { confirmUpgradeStudent(stu) {
// //
@ -728,7 +728,7 @@
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
console.error('升级学员失败:', error); console.error('升级学员失败:', error);
// //
let errorMsg = '升级失败'; let errorMsg = '升级失败';
if (error && error.data && error.data.msg) { if (error && error.data && error.data.msg) {
@ -738,14 +738,14 @@
} else if (typeof error === 'string') { } else if (typeof error === 'string') {
errorMsg = error; errorMsg = error;
} }
uni.showToast({ uni.showToast({
title: errorMsg, title: errorMsg,
icon: 'none' icon: 'none'
}); });
} }
}, },
getStatusText(status) { getStatusText(status) {
const statusMap = { const statusMap = {
0: '待上课', 0: '待上课',
@ -930,7 +930,7 @@
} catch (error) { } catch (error) {
uni.hideLoading(); uni.hideLoading();
console.error('添加学员失败:', error); console.error('添加学员失败:', error);
// //
let errorMsg = '添加失败'; let errorMsg = '添加失败';
if (error && error.data && error.data.msg) { if (error && error.data && error.data.msg) {
@ -940,7 +940,7 @@
} else if (typeof error === 'string') { } else if (typeof error === 'string') {
errorMsg = error; errorMsg = error;
} }
uni.showToast({ uni.showToast({
title: errorMsg, title: errorMsg,
icon: 'none' icon: 'none'
@ -952,7 +952,7 @@
this.$refs.leaveReasonModal.close(); this.$refs.leaveReasonModal.close();
this.leaveReason = ''; this.leaveReason = '';
}, },
// //
submitLeaveRequest() { submitLeaveRequest() {
if (!this.leaveReason.trim()) { if (!this.leaveReason.trim()) {
@ -1032,7 +1032,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.back-icon { .back-icon {
color: #fff; color: #fff;
font-size: 48rpx; font-size: 48rpx;
@ -1075,13 +1075,13 @@
.section { .section {
margin: 30rpx; margin: 30rpx;
.section-title { .section-title {
color: #ffd86b; color: #ffd86b;
font-size: 28rpx; font-size: 28rpx;
margin-bottom: 30rpx; margin-bottom: 30rpx;
display: block; display: block;
&.waiting-title { &.waiting-title {
color: #8a7fff !important; color: #8a7fff !important;
} }
@ -1240,12 +1240,12 @@
border-radius: 8rpx; border-radius: 8rpx;
font-size: 20rpx; font-size: 20rpx;
font-weight: bold; font-weight: bold;
&.signed { &.signed {
background-color: #29d3b4; background-color: #29d3b4;
color: #fff; color: #fff;
} }
&.unsigned { &.unsigned {
background-color: #ffd86b; background-color: #ffd86b;
color: #232323; color: #232323;
@ -1270,7 +1270,6 @@
background: #fff; background: #fff;
border-radius: 24rpx 24rpx 0 0; border-radius: 24rpx 24rpx 0 0;
width: 100%; width: 100%;
max-height: 80vh;
overflow: hidden; overflow: hidden;
} }
@ -1373,7 +1372,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16rpx; gap: 16rpx;
.formal-badge { .formal-badge {
background: #29d3b4; background: #29d3b4;
color: #fff; color: #fff;
@ -1439,19 +1438,19 @@
.radio-item { .radio-item {
display: flex; display: flex;
align-items: center; align-items: center;
.radio-text { .radio-text {
margin-left: 16rpx; margin-left: 16rpx;
font-size: 28rpx; font-size: 28rpx;
color: #333; color: #333;
} }
&.disabled { &.disabled {
opacity: 0.5; opacity: 0.5;
.radio-text { .radio-text {
color: #999; color: #999;
.disabled-tip { .disabled-tip {
font-size: 24rpx; font-size: 24rpx;
color: #999; color: #999;
@ -1510,7 +1509,7 @@
padding: 40rpx 40rpx 20rpx 40rpx; padding: 40rpx 40rpx 20rpx 40rpx;
text-align: center; text-align: center;
border-bottom: 1rpx solid #eee; border-bottom: 1rpx solid #eee;
.leave-modal-title { .leave-modal-title {
font-size: 32rpx; font-size: 32rpx;
font-weight: bold; font-weight: bold;
@ -1529,7 +1528,7 @@
.leave-input { .leave-input {
margin-bottom: 40rpx; margin-bottom: 40rpx;
.leave-textarea { .leave-textarea {
width: 100%; width: 100%;
min-height: 200rpx; min-height: 200rpx;
@ -1546,19 +1545,19 @@
.leave-buttons { .leave-buttons {
display: flex; display: flex;
gap: 20rpx; gap: 20rpx;
.leave-btn { .leave-btn {
flex: 1; flex: 1;
padding: 24rpx; padding: 24rpx;
text-align: center; text-align: center;
border-radius: 8rpx; border-radius: 8rpx;
font-size: 28rpx; font-size: 28rpx;
&.cancel-btn { &.cancel-btn {
background: #f5f5f5; background: #f5f5f5;
color: #666; color: #666;
} }
&.confirm-btn { &.confirm-btn {
background: #29d3b4; background: #29d3b4;
color: #fff; color: #fff;
@ -1566,4 +1565,4 @@
} }
} }
} }
</style> </style>

5
uniapp/pages-market/clue/clue_info.vue

@ -30,8 +30,7 @@
@change="onStudentSwiperChange"> @change="onStudentSwiperChange">
<swiper-item v-for="(student, index) in studentList" :key="student.id"> <swiper-item v-for="(student, index) in studentList" :key="student.id">
<view class="student-swiper-content"> <view class="student-swiper-content">
<StudentInfoCard :student="student" :show-details="true" <StudentInfoCard :student="student" :show-details="true" @action="handleStudentAction" />
@action="handleStudentAction" />
</view> </view>
</swiper-item> </swiper-item>
</swiper> </swiper>
@ -1826,4 +1825,4 @@ ${orderInfo.paid_at ? '支付时间:' + this.formatOrderTime(orderInfo.paid_at
} }
@import './clue_info.less'; @import './clue_info.less';
</style> </style>

Loading…
Cancel
Save