From d3fc1502b8c71aaf72c6fe415f164bd3a8cfd56e Mon Sep 17 00:00:00 2001 From: zeyan <258785420@qq.com> Date: Sun, 17 Aug 2025 16:35:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/course-schedule-edit.vue | 4 +- .../api/apiService/OrderTableService.php | 75 ++++ uniapp/components/schedule/ScheduleDetail.vue | 337 +++++++++++------- .../coach/schedule/schedule_table.vue | 40 ++- 4 files changed, 328 insertions(+), 128 deletions(-) diff --git a/admin/src/app/views/course_schedule/components/course-schedule-edit.vue b/admin/src/app/views/course_schedule/components/course-schedule-edit.vue index 95340eed..6ed96814 100644 --- a/admin/src/app/views/course_schedule/components/course-schedule-edit.vue +++ b/admin/src/app/views/course_schedule/components/course-schedule-edit.vue @@ -113,8 +113,8 @@ - {{ t('yes') }} - {{ t('no') }} + {{ t('yes') }} + {{ t('no') }} diff --git a/niucloud/app/service/api/apiService/OrderTableService.php b/niucloud/app/service/api/apiService/OrderTableService.php index e4cbe08b..dd6243b2 100644 --- a/niucloud/app/service/api/apiService/OrderTableService.php +++ b/niucloud/app/service/api/apiService/OrderTableService.php @@ -387,6 +387,9 @@ class OrderTableService extends BaseApiService $result = Db::table('school_contract_sign')->insert($insertData); if ($result) { + // 创建签署记录成功后,为甲乙双方生成数据源配置记录 + $this->createDocumentDataSourceConfig($course['contract_id'], $orderData); + \think\facade\Log::info('合同签署记录创建成功', [ 'student_id' => $student_id, 'contract_id' => $course['contract_id'], @@ -405,6 +408,78 @@ class OrderTableService extends BaseApiService } } + /** + * 为甲乙双方创建文档数据源配置记录 + * @param int $contract_id 合同ID + * @param array $orderData 订单数据 + * @return bool + */ + private function createDocumentDataSourceConfig($contract_id, $orderData) + { + try { + // 获取合同模板的占位符配置 + $contract = Db::table('school_contract')->where('id', $contract_id)->find(); + if (!$contract || empty($contract['placeholder_config'])) { + \think\facade\Log::warning('合同模板无占位符配置', ['contract_id' => $contract_id]); + return false; + } + + $placeholder_config = json_decode($contract['placeholder_config'], true); + if (!is_array($placeholder_config)) { + \think\facade\Log::warning('占位符配置格式错误', ['contract_id' => $contract_id]); + return false; + } + + $insert_data = []; + $now = date('Y-m-d H:i:s'); + + // 为每个占位符配置创建数据源记录 + foreach ($placeholder_config as $config) { + $data_source_record = [ + 'contract_id' => $contract_id, + 'placeholder' => $config['placeholder'] ?? '', + 'data_type' => $config['data_type'] ?? 'user_input', + 'system_function' => $config['system_function'] ?? '', + 'table_name' => $config['table_name'] ?? '', + 'field_name' => $config['field_name'] ?? '', + 'field_type' => $config['field_type'] ?? 'text', + 'is_required' => $config['is_required'] ?? 0, + 'default_value' => $config['default_value'] ?? '', + 'sign_party' => $config['sign_party'] ?? '', + 'validation_rule' => $config['validation_rule'] ?? '', + 'created_at' => $now, + 'updated_at' => $now + ]; + + $insert_data[] = $data_source_record; + } + + if (!empty($insert_data)) { + $result = Db::table('school_document_data_source_config')->insertAll($insert_data); + + if ($result) { + \think\facade\Log::info('文档数据源配置创建成功', [ + 'contract_id' => $contract_id, + 'order_id' => $orderData['id'], + 'records_count' => count($insert_data) + ]); + } + + return $result ? true : false; + } + + return true; + } catch (\Exception $e) { + \think\facade\Log::error('创建文档数据源配置异常', [ + 'contract_id' => $contract_id, + 'order_data' => $orderData, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString() + ]); + return false; + } + } + /** * 支付成功后创建支付记录 * @param array $orderData 订单数据 diff --git a/uniapp/components/schedule/ScheduleDetail.vue b/uniapp/components/schedule/ScheduleDetail.vue index c825ebf0..ce4718cb 100644 --- a/uniapp/components/schedule/ScheduleDetail.vue +++ b/uniapp/components/schedule/ScheduleDetail.vue @@ -329,7 +329,7 @@ } }, - // 获取课程安排详情 + // 获取课程安排详情(使用统一API - 对接admin端) async fetchScheduleDetail() { if (!this.scheduleId) { this.error = true; @@ -342,8 +342,8 @@ this.scheduleInfo = null; try { - // 调用真实API获取课程安排详情和学员信息 - const res = await api.getCourseScheduleInfo({ + // 调用新的统一API获取课程安排详情,与admin端保持一致 + const res = await api.getCourseArrangementDetail({ schedule_id: this.scheduleId }); @@ -351,57 +351,114 @@ // 处理课程安排基本信息 const data = res.data; - // 使用新的API数据结构,data直接包含所有信息 - this.scheduleInfo = { - // 基本课程信息 - id: data.id, - course_name: data.course_name || '未命名课程', - course_date: data.course_date, - time_slot: data.time_slot, - venue_name: data.venue_name || '未分配', - campus_name: data.campus_name || '', - // 教练信息 - coach_name: data.coach_name || '未分配', - coach_avatar: data.coach_avatar || '', - // 课程状态 - status: data.status, - status_text: data.status_text || '待定', - // 班级信息 - class_info: data.class_info || null, - // 时间信息 - time_info: data.time_info || null, - // 课程时长 - course_duration: data.time_info?.duration || data.course_duration || 60, - // 学员数据 - students: (data.students || []).map(student => ({ - ...student, - status_text: this.getStatusText(student.status || 0), - // 确保包含课程进度数据 - course_progress: student.course_progress || { - total: student.totalHours || 0, - used: student.usedHours || 0, - remaining: student.remainingHours || 0, - percentage: student.totalHours > 0 ? Math.round((student.usedHours / student.totalHours) * 100) : 0 - }, - // 确保包含续费和体验课标识 - needsRenewal: student.needsRenewal || false, - isTrialStudent: student.isTrialStudent || false, - // 确保包含课程状态和类型 - courseStatus: student.courseStatus || (student.person_type === 'student' ? '正式课' : '体验课'), - courseType: student.schedule_type === 2 ? 'fixed' : 'temporary', - // 确保包含年龄信息 - age: student.age || 0, - // 确保包含体验课时信息 - trialClassCount: student.trialClassCount || 0, - // 确保包含剩余课时和到期时间 - remainingHours: student.remainingHours || 0, - expiryDate: student.expiryDate || '' - })), - // 其他信息 - available_capacity: data.available_capacity || 0, - enrolled_count: data.enrolled_count || 0, - remaining_capacity: data.remaining_capacity || 0 - }; + // 使用新的统一API数据结构,与admin端保持一致 + if (data.schedule_info) { + // 处理新的统一数据结构(包含schedule_info、formal_students、waiting_students) + const allStudents = [...(data.formal_students || []), ...(data.waiting_students || [])]; + + this.scheduleInfo = { + // 基本课程信息从schedule_info获取 + id: data.schedule_info.id, + course_name: data.schedule_info.course_name || '未命名课程', + course_date: data.schedule_info.course_date, + time_slot: data.schedule_info.time_slot, + venue_name: data.schedule_info.venue_name || '未分配', + campus_name: data.schedule_info.campus_name || '', + // 教练信息 + coach_name: data.schedule_info.coach_name || '未分配', + coach_avatar: data.schedule_info.coach_avatar || '', + // 课程状态 + status: data.schedule_info.status, + status_text: data.schedule_info.status_text || '待定', + // 班级信息 + class_info: data.schedule_info.class_info || null, + // 时间信息 + time_info: data.schedule_info.time_info || null, + // 课程时长 + course_duration: data.schedule_info.time_info?.duration || data.schedule_info.course_duration || 60, + // 合并正式学员和等待位学员数据 + students: allStudents.map(student => ({ + ...student, + status_text: this.getStatusText(student.status || 0), + // 确保包含课程进度数据 + course_progress: student.course_progress || { + total: student.totalHours || 0, + used: student.usedHours || 0, + remaining: student.remainingHours || 0, + percentage: student.totalHours > 0 ? Math.round((student.usedHours / student.totalHours) * 100) : 0 + }, + // 确保包含续费和体验课标识 + needsRenewal: student.needsRenewal || false, + isTrialStudent: student.isTrialStudent || student.person_type !== 'student', + // 确保包含课程状态和类型 + courseStatus: student.courseStatus || (student.person_type === 'student' ? '正式课' : '体验课'), + courseType: student.schedule_type === 2 ? 'waiting' : 'formal', + // 确保包含年龄信息 + age: student.age || 0, + // 确保包含体验课时信息 + trialClassCount: student.trialClassCount || 0, + // 确保包含剩余课时和到期时间 + remainingHours: student.remainingHours || student.course_progress?.remaining || 0, + expiryDate: student.expiryDate || '' + })), + // 其他信息 + available_capacity: data.available_capacity || 0, + enrolled_count: allStudents.length, + remaining_capacity: data.remaining_capacity || 0 + }; + } else { + // 兼容旧的数据结构 + this.scheduleInfo = { + // 基本课程信息 + id: data.id, + course_name: data.course_name || '未命名课程', + course_date: data.course_date, + time_slot: data.time_slot, + venue_name: data.venue_name || '未分配', + campus_name: data.campus_name || '', + // 教练信息 + coach_name: data.coach_name || '未分配', + coach_avatar: data.coach_avatar || '', + // 课程状态 + status: data.status, + status_text: data.status_text || '待定', + // 班级信息 + class_info: data.class_info || null, + // 时间信息 + time_info: data.time_info || null, + // 课程时长 + course_duration: data.time_info?.duration || data.course_duration || 60, + // 学员数据 + students: (data.students || []).map(student => ({ + ...student, + status_text: this.getStatusText(student.status || 0), + // 确保包含课程进度数据 + course_progress: student.course_progress || { + total: student.totalHours || 0, + used: student.usedHours || 0, + remaining: student.remainingHours || 0, + percentage: student.totalHours > 0 ? Math.round((student.usedHours / student.totalHours) * 100) : 0 + }, + // 确保包含续费和体验课标识 + needsRenewal: student.needsRenewal || false, + isTrialStudent: student.isTrialStudent || false, + // 确保包含课程状态和类型 + courseStatus: student.courseStatus || (student.person_type === 'student' ? '正式课' : '体验课'), + courseType: student.schedule_type === 2 ? 'waiting' : 'formal', + // 确保包含年龄信息 + age: student.age || 0, + // 确保包含体验课时信息 + trialClassCount: student.trialClassCount || 0, + // 确保包含剩余课时和到期时间 + remainingHours: student.remainingHours || 0, + expiryDate: student.expiryDate || '' + })), + // 其他信息 + available_capacity: data.available_capacity || 0, + enrolled_count: data.enrolled_count || 0, + remaining_capacity: data.remaining_capacity || 0 + }; + } console.log('课程安排详情加载成功:', this.scheduleInfo); } else { @@ -483,51 +540,66 @@ this.upgradeStudentIndex = -1; }, - // 将等待位学员转为正式课学员 + // 将等待位学员转为正式课学员(使用统一API - 对接admin端) async convertWaitingToFormal(student, index) { try { uni.showLoading({ title: '升级中...' }); - // 构建升级参数,参考class_arrangement_detail页面的逻辑 + // 使用新的统一升级API,与admin端保持一致 const upgradeData = { - resources_id: student.resources_id || student.resource_id, schedule_id: this.scheduleId, - student_id: student.student_id || student.id, - from_schedule_type: 2, // 从等待位 - to_schedule_type: 1, // 升级到正式位 - position: this.formalStudents.length + 1, // 新的正式位位置 - course_type: student.course_type === 3 ? 1 : student.course_type // 等待位课程类型改为正式课 + person_id: student.person_id || student.id, + person_type: student.person_type || 'customer_resource', + from_type: 2, // 从等待位 + to_type: 1 // 到正式位 }; - // 调用升级接口 - const response = await api.upgradeStudentSchedule(upgradeData); + // 根据学员类型添加对应的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); - if (response.code === 1) { - // 转换成功,更新学员类型 - student.course_type = 1; // 改为正式学员 - student.courseStatus = '正式课'; - student.courseType = 'temporary'; // 临时课 + // 调用新的统一升级接口 + const response = await api.upgradeStudentInArrangement(upgradeData); + if (response.code === 1) { // 重新获取课程详情以更新学员列表 await this.fetchScheduleDetail(); uni.showToast({ - title: '升级成功', - icon: 'success' + title: `学员 ${student.name} 升级成功!已移至正式位`, + icon: 'success', + duration: 3000 }); } else { uni.showToast({ title: response.msg || '升级失败', - icon: 'none' + icon: 'none', + duration: 3000 }); } } catch (error) { console.error('升级等待位学员失败:', error); + + let errorMessage = '升级失败,请重试'; + if (error.message) { + if (error.message.includes('timeout')) { + errorMessage = '升级超时,请重试'; + } else if (error.message.includes('Network')) { + errorMessage = '网络异常,请检查网络连接'; + } + } + uni.showToast({ - title: '升级失败,请重试', - icon: 'none' + title: errorMessage, + icon: 'none', + duration: 3000 }); } finally { uni.hideLoading(); @@ -541,13 +613,13 @@ this.selectedStudentIndex = -1; }, - // 处理点名操作 + // 处理点名操作(使用统一API - 对接admin端) async handleAttendanceAction(action) { if (!this.selectedStudent) return; const actionMap = { - 'sign_in': { status: 1, text: '已签到', apiMethod: 'studentCheckin' }, - 'leave': { status: 2, text: '请假', apiMethod: 'studentLeave' } + 'sign_in': { status: 1, text: '已签到' }, + 'leave': { status: 2, text: '请假' } }; if (actionMap[action]) { @@ -556,39 +628,31 @@ title: '处理中...' }); - // 准备API调用参数 - const apiData = { + // 使用新的统一状态更新API,与admin端保持一致 + const updateData = { schedule_id: this.scheduleId, person_id: this.selectedStudent.person_id || this.selectedStudent.id, + person_type: this.selectedStudent.person_type || 'student', + status: actionMap[action].status, + reason: `移动端${actionMap[action].text}操作` }; - - // 根据学员类型传递不同的ID - if (this.selectedStudent.student_id && this.selectedStudent.student_id > 0) { - // 正式学员 - apiData.student_id = this.selectedStudent.student_id; - } else if (this.selectedStudent.resources_id && this.selectedStudent.resources_id > 0) { - // 客户资源 - apiData.resources_id = this.selectedStudent.resources_id; + + // 根据学员类型添加对应的ID + if (this.selectedStudent.person_type === 'student') { + updateData.student_id = this.selectedStudent.student_id || this.selectedStudent.id; } else { - // 如果都没有,尝试使用id字段 - if (this.selectedStudent.person_type === 'student') { - apiData.student_id = this.selectedStudent.id; - } else { - apiData.resources_id = this.selectedStudent.id; - } + updateData.resources_id = this.selectedStudent.resources_id || this.selectedStudent.resource_id || this.selectedStudent.id; } - - // 添加调试信息 - console.log('签到API调用参数:', apiData); - console.log('selectedStudent:', this.selectedStudent); - // 如果是请假,可以添加请假备注(暂时为空,后续可扩展) + // 如果是请假,可以添加请假备注 if (action === 'leave') { - apiData.remark = ''; + updateData.reason = '移动端请假操作'; } + + console.log('更新学员状态数据:', updateData); - // 调用对应的API - const response = await api[actionMap[action].apiMethod](apiData); + // 调用新的统一状态更新接口 + const response = await api.updateStudentStatusInArrangement(updateData); if (response.code === 1) { // API调用成功,更新前端状态 @@ -611,21 +675,34 @@ }); uni.showToast({ - title: `${this.selectedStudent.name} ${actionMap[action].text}`, - icon: 'success' + title: `${this.selectedStudent.name} ${actionMap[action].text}成功`, + icon: 'success', + duration: 3000 }); } else { // API调用失败 uni.showToast({ title: response.msg || `${actionMap[action].text}失败`, - icon: 'none' + icon: 'none', + duration: 3000 }); } } catch (error) { console.error(`${action} API调用失败:`, error); + + let errorMessage = `${actionMap[action].text}失败,请重试`; + if (error.message) { + if (error.message.includes('timeout')) { + errorMessage = `${actionMap[action].text}超时,请重试`; + } else if (error.message.includes('Network')) { + errorMessage = '网络异常,请检查网络连接'; + } + } + uni.showToast({ - title: `${actionMap[action].text}失败,请重试`, - icon: 'none' + title: errorMessage, + icon: 'none', + duration: 3000 }); } finally { uni.hideLoading(); @@ -654,25 +731,37 @@ return statusTextMap[status] || '未知状态'; }, - // 处理安排学员按钮点击 + // 处理安排学员按钮点击(支持统一学员管理功能) handleArrangeStudent() { - // 跳转到课程安排详情页面进行学员管理 - const url = `/pages-market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`; - console.log('跳转到学员管理页面:', url); - uni.navigateTo({ - url: url, - success: () => { - // 关闭当前弹窗 - this.closePopup(); - }, - fail: (error) => { - console.error('跳转到学员管理页面失败:', error); - uni.showToast({ - title: '跳转失败,请重试', - icon: 'none' - }); - } - }); + try { + // 跳转到课程安排详情页面进行学员管理 + const url = `/pages-market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`; + console.log('跳转到学员管理页面:', url); + + uni.navigateTo({ + url: url, + success: () => { + console.log('成功跳转到学员管理页面'); + // 关闭当前弹窗 + this.closePopup(); + }, + fail: (error) => { + console.error('跳转到学员管理页面失败:', error); + uni.showToast({ + title: '跳转失败,请重试', + icon: 'none', + duration: 3000 + }); + } + }); + } catch (error) { + console.error('处理安排学员按钮失败:', error); + uni.showToast({ + title: '操作失败,请重试', + icon: 'none', + duration: 3000 + }); + } }, } } diff --git a/uniapp/pages-coach/coach/schedule/schedule_table.vue b/uniapp/pages-coach/coach/schedule/schedule_table.vue index 04de22d6..f4662dbd 100644 --- a/uniapp/pages-coach/coach/schedule/schedule_table.vue +++ b/uniapp/pages-coach/coach/schedule/schedule_table.vue @@ -1441,6 +1441,14 @@ export default { // 查看课程安排详情(使用统一API) async viewScheduleDetail(scheduleId) { try { + if (!scheduleId) { + uni.showToast({ + title: '课程ID不能为空', + icon: 'none' + }); + return; + } + // 显示课程详情弹窗 this.selectedScheduleId = scheduleId; this.showScheduleDetail = true; @@ -1451,12 +1459,40 @@ export default { // 注意:具体的API调用逻辑在ScheduleDetail组件的fetchScheduleDetail方法中实现 // ScheduleDetail组件会调用api.getCourseArrangementDetail()方法,与admin端保持一致 // 这确保了移动端和管理端使用相同的数据结构和业务逻辑 + + // 预先验证API是否可用(可选的预检查) + try { + const testResponse = await api.getCourseArrangementDetail({ + schedule_id: scheduleId + }); + if (testResponse.code !== 1) { + console.warn('API预检查警告:', testResponse.msg); + } + } catch (preCheckError) { + console.warn('API预检查失败,将在ScheduleDetail组件中处理:', preCheckError); + } + } catch (error) { console.error('获取课程安排详情失败:', error); + + let errorMessage = '获取课程详情失败'; + if (error.message) { + if (error.message.includes('timeout')) { + errorMessage = '请求超时,请重试'; + } else if (error.message.includes('Network')) { + errorMessage = '网络连接失败'; + } + } + uni.showToast({ - title: '获取课程详情失败', - icon: 'none' + title: errorMessage, + icon: 'none', + duration: 3000 }); + + // 即使出错也要显示弹窗,让ScheduleDetail组件处理错误 + this.selectedScheduleId = scheduleId; + this.showScheduleDetail = true; } },