Browse Source

修改 bug

master
王泽彦 5 months ago
parent
commit
b8a510485d
  1. 18
      admin/src/app/views/classroom/components/classroom-edit.vue
  2. 13
      admin/src/app/views/course_schedule/components/course-schedule-edit.vue
  3. 31
      niucloud/app/api/controller/apiController/CourseSchedule.php
  4. 3
      niucloud/app/api/route/route.php
  5. 2
      niucloud/app/model/course_schedule/CourseSchedule.php
  6. 54
      niucloud/app/service/admin/classroom/ClassroomService.php
  7. 169
      niucloud/app/service/api/apiService/CourseScheduleService.php
  8. 31
      niucloud/app/service/api/apiService/CourseService.php
  9. 57
      uniapp/api/apiRoute.js
  10. 283
      uniapp/components/schedule/ScheduleDetail.vue
  11. 39
      uniapp/components/student-info-card/student-info-card.vue
  12. 9
      uniapp/pages-coach/coach/schedule/adjust_course.vue
  13. 124
      uniapp/pages-coach/coach/schedule/sign_in.vue
  14. 3
      uniapp/pages-market/clue/class_arrangement_detail.vue
  15. 3
      uniapp/pages-market/clue/clue_info.vue

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

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

13
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 { generateTimeSlots } from '@/utils/timeslots'
import { getAllCourseList } from '@/app/api/course'
import { getWithCoachList } from '@/app/api/customer_resources'
import { getCoachPersonnel } from '@/app/api/classroom'
let showDialog = ref(false)
const loading = ref(false)
@ -272,7 +272,7 @@ const loadCourseList = () => {
}
//
const loadCoachList = (campus_id?: string | number) => {
getWithCoachList({ campus_id })
getCoachPersonnel(campus_id)
.then((res) => {
coachList.value = res.data || []
})
@ -298,7 +298,7 @@ watch(
})
.catch(() => {})
getWithCoachList({ campus_id: newValue })
getCoachPersonnel(newValue)
.then((res) => {
coachList.value = res.data || []
})
@ -396,7 +396,7 @@ const setFormData = async (row: any = null) => {
});
}),
new Promise<void>((resolve) => {
getWithCoachList({ campus_id: detailData.campus_id })
getCoachPersonnel(detailData.campus_id)
.then((res) => {
coachList.value = res.data || [];
resolve();
@ -446,7 +446,12 @@ const setFormData = async (row: any = null) => {
}
if (detailData[key] !== undefined) {
// 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) {
// 使1
formData.auto_schedule = 1;

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

@ -384,4 +384,35 @@ class CourseSchedule extends BaseApiService
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());
}
}
}

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

@ -354,6 +354,8 @@ Route::group(function () {
Route::post('courseSchedule/batchSignIn', 'apiController.CourseSchedule/batchSignIn');
//员工端-单个学员签到
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::post('course/upgradeStudent', 'apiController.Course/upgradeStudent');
//获取班级列表(用于添加课程安排)
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()
{
return $this->hasOne(Personnel::class, 'id', 'coach_id');
return $this->hasOne(\app\model\school\SchoolPersonnel::class, 'id', 'coach_id');
}
public function course()

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

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

169
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,
'old_trial_count' => $currentTrialCount,
'new_trial_count' => $updateData['trial_class_count'],
'update_fields' => array_keys($updateData)
]);
]), 'info');
} catch (\Exception $e) {
// 抛出异常以便外层事务回滚
@ -2779,28 +2780,47 @@ class CourseScheduleService extends BaseApiService
* @param string $reason 备注
* @return array 处理结果
*/
private function processSingleStudentSignIn($scheduleId, $studentId, $resourceId, $status, $reason = '')
private function processSingleStudentSignIn($scheduleId, $personId, $resourceId, $status, $reason = '')
{
try {
// 查找学员课程安排记录
$whereCondition = [
'schedule_id' => $scheduleId,
'deleted_at' => 0
];
$enrollment = null;
if (!empty($studentId)) {
$whereCondition['student_id'] = $studentId;
} elseif (!empty($resourceId)) {
$whereCondition['resources_id'] = $resourceId;
} else {
return ['success' => false, 'error' => '学员ID或资源ID不能为空'];
}
// 优先使用student_id字段查询(前端传递的student_id)
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($whereCondition)
->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)) {
$enrollment = Db::name('person_course_schedule')
->where('resources_id', $resourceId)
->where('schedule_id', $scheduleId)
->where('deleted_at', 0)
->find();
}
if (empty($enrollment)) {
return ['success' => false, 'error' => '找不到学员记录'];
}
@ -2810,6 +2830,13 @@ class CourseScheduleService extends BaseApiService
return ['success' => false, 'error' => '找不到学员信息'];
}
// 检查体验课次数(仅对未付费学员且状态为签到时)
if ($status === 1 && $student->pay_status != 1) {
if ($student->trial_class_count <= 0) {
return ['success' => false, 'error' => '该学员体验课次数已用完,无法签到'];
}
}
// 准备更新数据
$updateData = [
'status' => $status,
@ -2840,4 +2867,116 @@ class CourseScheduleService extends BaseApiService
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');
}
}
}

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

@ -459,6 +459,33 @@ class CourseService extends BaseApiService
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 = [
'student_id' => $student->id, // 正确设置student_id
'resources_id' => $data['resources_id'], // 正确设置resources_id
@ -723,7 +750,7 @@ class CourseService extends BaseApiService
// 获取课程安排基本信息
$schedule = $CourseSchedule
->where('id', $scheduleId)
->with(['course', 'venue', 'campus'])
->with(['course', 'venue', 'campus', 'coach'])
->find();
if (!$schedule) {
@ -905,6 +932,8 @@ class CourseService extends BaseApiService
'time_slot' => $schedule['time_slot'],
'venue_name' => $schedule['venue']['venue_name'] ?? '',
'campus_name' => $schedule['campus']['campus_name'] ?? '',
'coach_name' => $schedule['coach']['name'] ?? '',
'coach_id' => $schedule['coach_id'] ?: 0,
'available_capacity' => $schedule['available_capacity'] ?: 0,
'max_students' => $maxStudents,
'available_slots' => $availableSlots,

57
uniapp/api/apiRoute.js

@ -992,22 +992,14 @@ export default {
return response
} catch (error) {
console.error('获取课程安排列表错误:', error)
// 当发生错误时,返回模拟数据
return await this.getCourseScheduleListMock(data)
uni.showModal({
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 = {}) {
@ -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 = {}) {
return await http.get('/courseSchedule/filterOptions', data)
@ -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 = {}) {
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端)
async restoreStudentInArrangement(data = {}) {
try {

283
uniapp/components/schedule/ScheduleDetail.vue

@ -31,7 +31,7 @@
</view>
<view class="info-item">
<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 class="info-item">
<text class="item-label">班级</text>
@ -170,8 +170,12 @@
<!-- 操作按钮 -->
<view class="action-buttons">
<fui-button type="primary" @click="handleEditCourse">编辑课程</fui-button>
<fui-button type="success" @click="handleAddNewCourse">新增课程</fui-button>
<view class="action-button edit-btn" @click="handleEditCourse">
<text class="btn-text">编辑课程</text>
</view>
<view class="action-button add-btn" @click="handleAddNewCourse">
<text class="btn-text">新增课程</text>
</view>
</view>
</view>
@ -203,7 +207,7 @@
<view class="option-btn sign-in" @click="handleAttendanceAction('sign_in')">
<text>签到</text>
</view>
<view class="option-btn leave" @click="handleAttendanceAction('leave')">
<view v-if="!selectedStudent.isTrialStudent" class="option-btn leave" @click="handleAttendanceAction('leave')">
<text>请假</text>
</view>
<view class="option-btn cancel" @click="closeAttendanceModal">
@ -236,6 +240,33 @@
</view>
</view>
</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>
</template>
@ -273,6 +304,15 @@
};
return statusMap[this.scheduleInfo?.status] || '';
},
statusText() {
const statusTextMap = {
'pending': '待开始',
'upcoming': '即将开始',
'ongoing': '进行中',
'completed': '已结束'
};
return statusTextMap[this.scheduleInfo?.status] || '待开始';
},
studentList() {
const statusMap = {
0: 'status-absent',
@ -310,7 +350,10 @@
selectedStudentIndex: -1,
showUpgradeConfirm: false,
upgradeStudent: null,
upgradeStudentIndex: -1
upgradeStudentIndex: -1,
showDeleteSchedulesModal: false,
deleteErrorMessage: '',
studentToDelete: null
}
},
watch: {
@ -375,7 +418,7 @@
coach_avatar: data.schedule_info.coach_avatar || '',
//
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,
//
@ -427,7 +470,7 @@
coach_avatar: data.coach_avatar || '',
//
status: data.status,
status_text: data.status_text || '待定',
status_text: data.status_text || this.getStatusTextFromCode(data.status),
//
class_info: data.class_info || null,
//
@ -546,40 +589,39 @@
this.upgradeStudentIndex = -1;
},
// 使API - admin
//
async convertWaitingToFormal(student, index) {
try {
uni.showLoading({
title: '升级中...'
});
// 使APIadmin
console.log('升级学员信息:', student);
// - 使
const upgradeData = {
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',
from_type: 2, //
to_type: 1 //
from_schedule_type: 2, //
to_schedule_type: 1, //
resources_id: student.resources_id || student.resource_id || student.id
};
// ID
// student_id
if (student.person_type === 'student') {
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.upgradeStudentInArrangement(upgradeData);
//
const response = await api.upgradeStudentSchedule(upgradeData);
if (response.code === 1) {
//
await this.fetchScheduleDetail();
uni.showToast({
title: `学员 ${student.name} 升级成功!已移至正式位`,
title: `学员 ${student.name} 升级成功!已转为正式学员`,
icon: 'success',
duration: 3000
});
@ -591,7 +633,7 @@
});
}
} catch (error) {
console.error('升级等待位学员失败:', error);
console.error('升级学员失败:', error);
let errorMessage = '升级失败,请重试';
if (error.message) {
@ -619,6 +661,75 @@
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
async handleAttendanceAction(action) {
if (!this.selectedStudent) return;
@ -687,12 +798,18 @@
});
} else {
// API
//
if (response.msg && response.msg.includes('体验课次数已用完') && action === 'checkin') {
//
this.showDeleteSchedulesConfirm(response.msg);
} else {
uni.showToast({
title: response.msg || `${actionMap[action].text}失败`,
icon: 'none',
duration: 3000
});
}
}
} catch (error) {
console.error(`${action} API调用失败:`, error);
@ -737,6 +854,17 @@
return statusTextMap[status] || '未知状态';
},
//
getStatusTextFromCode(statusCode) {
const statusTextMap = {
'pending': '待开始',
'upcoming': '即将开始',
'ongoing': '进行中',
'completed': '已结束'
};
return statusTextMap[statusCode] || '待开始';
},
//
batchCheckIn() {
uni.navigateTo({
@ -1019,9 +1147,59 @@
.action-buttons {
display: flex;
justify-content: space-around;
justify-content: space-between;
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 {
display: grid;
grid-template-columns: 1fr 1fr;
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 20rpx;
}
@ -1153,6 +1331,12 @@
cursor: pointer;
transition: all 0.3s ease;
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 {
@ -1242,6 +1426,7 @@
font-weight: 600;
font-size: 24rpx;
margin-bottom: 12rpx;
flex-shrink: 0;
}
.waiting-avatar {
@ -1252,6 +1437,10 @@
.student-info {
font-size: 22rpx;
line-height: 1.4;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.student-name {
@ -1262,6 +1451,8 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
box-sizing: border-box;
}
.student-age,
@ -1425,4 +1616,46 @@
background: linear-gradient(45deg, #7c3aed, #9333ea);
transform: scale(0.98);
}
/* 删除确认弹窗样式 */
.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>

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

@ -2,7 +2,8 @@
<template>
<view class="student-info-card" >
<!-- 学生基本信息 -->
<view class="student-header" @click="handleStudentClick">
<view class="student-header">
<view class="student-content" @click="handleStudentClick">
<view class="student-avatar">
<text>{{ student.name ? student.name.charAt(0) : '学' }}</text>
</view>
@ -24,6 +25,11 @@
</view>
</view>
</view>
<!-- 编辑按钮 -->
<view class="edit-button" @click.stop="handleEditClick">
<text class="edit-icon"></text>
</view>
</view >
<!-- 学生详细信息 -->
<view class="student-info">
@ -81,6 +87,9 @@ export default {
handleStudentClick(){
this.$emit('action',{ action: 'edit', student: this.student})
},
handleEditClick(){
this.$emit('action',{ action: 'edit', student: this.student})
},
// xx
calculateAge(birthday) {
if (!birthday) return ''
@ -138,6 +147,12 @@ export default {
}
.student-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
.student-content {
flex: 1;
display: flex;
align-items: center;
@ -198,13 +213,27 @@ export default {
}
}
}
}
.action-toggle {
padding: 10rpx;
.edit-button {
width: 50rpx;
height: 50rpx;
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;
.toggle-icon {
.edit-icon {
font-size: 20rpx;
color: #29d3b4;
font-size: 24rpx;
}
&:active {
background-color: rgba(41, 211, 180, 0.4);
transform: scale(0.95);
}
}
}

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

@ -23,9 +23,12 @@
<text class="info-value">{{ scheduleInfo.time_slot }}</text>
</view>
<view class="info-row">
<text class="info-label">授课教练</text>
<text class="info-value">主教练{{ scheduleInfo.coach_name }}</text>
<text class="info-value">助教{{ scheduleInfo.coach_name }}</text>
<text class="info-label">主教练</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 class="info-row">
<text class="info-label">上课场地</text>

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

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

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

@ -6,7 +6,7 @@
<view class="course-detail">
<text class="course-item">日期{{ schedule_info.course_date }}</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.coach_name || '待安排' }}</text>
<text class="course-item">场地信息{{ schedule_info.venue_name }}</text>
@ -1270,7 +1270,6 @@
background: #fff;
border-radius: 24rpx 24rpx 0 0;
width: 100%;
max-height: 80vh;
overflow: hidden;
}

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

@ -30,8 +30,7 @@
@change="onStudentSwiperChange">
<swiper-item v-for="(student, index) in studentList" :key="student.id">
<view class="student-swiper-content">
<StudentInfoCard :student="student" :show-details="true"
@action="handleStudentAction" />
<StudentInfoCard :student="student" :show-details="true" @action="handleStudentAction" />
</view>
</swiper-item>
</swiper>

Loading…
Cancel
Save