diff --git a/admin/src/app/views/course_schedule/components/course-arrangement-detail.vue b/admin/src/app/views/course_schedule/components/course-arrangement-detail.vue index a63cfe20..811d9cd5 100644 --- a/admin/src/app/views/course_schedule/components/course-arrangement-detail.vue +++ b/admin/src/app/views/course_schedule/components/course-arrangement-detail.vue @@ -43,6 +43,9 @@ :formal-has-empty="formalEmptySeats.length > 0" @confirm-action="handleConfirmAction" /> + + + @@ -61,6 +64,7 @@ import CourseInfoSection from './course-info-section.vue' import StudentSection from './student-section.vue' import StudentSearchModal from './student-search-modal.vue' import StudentActionMenu from './student-action-menu.vue' +import StudentDetailModal from './student-detail-modal.vue' import type { StudentInfo, @@ -104,6 +108,9 @@ const currentSlot = ref({ type: 'formal', index: 1 }) const currentStudent = ref(null) const presetStudent = ref(null) +// 学员详情弹窗引用 +const studentDetailModalRef = ref() + // 暴露给父组件的方法 const open = (scheduleId: number, resourceId?: number, studentId?: number) => { if (scheduleId) { @@ -630,40 +637,59 @@ const handleCancelLeave = async (student: StudentInfo) => { // 学员签到 const handleStudentCheckin = async (student: StudentInfo) => { try { - const statusOptions = [ - { label: '待上课', value: 0 }, - { label: '已签到', value: 1 }, - { label: '请假', value: 2 } - ] - - const currentStatus = statusOptions.find(opt => opt.value === student.status) + const currentStatus = student.status === 1 ? '已签到' : student.status === 2 ? '请假' : '待上课' - const { value: statusValue } = await ElMessageBox.prompt( - `当前状态:${currentStatus?.label || '未知'}\n请选择新的签到状态:`, - '更新签到状态', - { - confirmButtonText: '确定', - cancelButtonText: '取消', - inputPlaceholder: '请选择状态: 0-待上课, 1-已签到, 2-请假', - inputPattern: /^[0-2]$/, - inputErrorMessage: '请输入 0、1 或 2' - } - ) + // 如果已经签到,询问是否取消签到 + if (student.status === 1) { + const result = await ElMessageBox.confirm( + `学员 ${student.name} 当前状态:${currentStatus}\n\n确定要取消签到吗?`, + '取消签到确认', + { + confirmButtonText: '取消签到', + cancelButtonText: '保持现状', + type: 'warning' + } + ) + + if (result === 'confirm') { + const response = await updateStudentStatus({ + schedule_id: scheduleInfo.value.id, + person_id: student.person_id || student.id, + status: 0, // 改为待上课 + reason: '管理员取消签到' + }) - const newStatus = parseInt(statusValue) - - if (!isNaN(newStatus) && newStatus !== student.status && [0, 1, 2].includes(newStatus)) { - const response = await updateStudentStatus({ - schedule_id: scheduleInfo.value.id, - person_id: student.person_id || student.id, - status: newStatus, - reason: `签到状态更新为: ${statusOptions.find(opt => opt.value === newStatus)?.label}` - }) + if (response.code === 1) { + ElMessage.success('已取消签到状态') + } else { + ElMessage.error(response.msg || '取消签到失败') + } + } + } else { + // 其他状态,询问是否签到 + const result = await ElMessageBox.confirm( + `学员 ${student.name} 当前状态:${currentStatus}\n\n确定要标记为已签到吗?`, + '签到确认', + { + confirmButtonText: '确认签到', + cancelButtonText: '取消', + type: 'info' + } + ) + + if (result === 'confirm') { + const response = await updateStudentStatus({ + schedule_id: scheduleInfo.value.id, + person_id: student.person_id || student.id, + status: 1, // 标记为已签到 + reason: '管理员确认签到' + }) - if (response.code === 1) { - ElMessage.success('签到状态更新成功') - } else { - ElMessage.error(response.msg || '签到状态更新失败') + if (response.code === 1) { + ElMessage.success('签到成功') + } else { + ElMessage.error(response.msg || '签到失败') + } } } } catch (error) { @@ -677,10 +703,20 @@ const handleStudentCheckin = async (student: StudentInfo) => { // 查看学员详情 const handleViewStudentDetails = async (student: StudentInfo) => { try { - // 这里可以打开学员详情页面或弹窗 - ElMessage.info('查看学员详情功能开发中') - // TODO: 实现学员详情查看功能 - console.log('查看学员详情:', student) + // 从StudentInfo中获取student_id或person_id作为学员ID + const studentId = student.student_id || student.person_id || student.id + + if (!studentId) { + ElMessage.warning('无法获取学员ID') + return + } + + // 打开学员详情弹窗 + if (studentDetailModalRef.value) { + await studentDetailModalRef.value.open(studentId) + } else { + ElMessage.error('学员详情组件未正确加载') + } } catch (error) { console.error('查看学员详情失败:', error) ElMessage.error('查看学员详情失败') diff --git a/admin/src/app/views/course_schedule/components/student-action-menu.vue b/admin/src/app/views/course_schedule/components/student-action-menu.vue index 74428433..7e24ae30 100644 --- a/admin/src/app/views/course_schedule/components/student-action-menu.vue +++ b/admin/src/app/views/course_schedule/components/student-action-menu.vue @@ -136,8 +136,9 @@ 升级到正式位 - + - + insert($insertData, true); if ($result) { - // 更新订单表的合同模板ID(来自课程配置) - OrderTable::where('id', $orderData['id'])->update(['contract_id' => $course['contract_id']]); + // 更新订单表的合同模板ID和合同签署记录ID + OrderTable::where('id', $orderData['id'])->update([ + 'contract_id' => $course['contract_id'], // 合同模板ID + 'contract_sign_id' => $result // 合同签署记录ID + ]); // 创建签署记录成功后,为甲乙双方生成数据源配置记录 $this->createDocumentDataSourceConfig($course['contract_id'], $orderData); diff --git a/niucloud/core/upload/Tencent.php b/niucloud/core/upload/Tencent.php index f2cfc03a..20b86d44 100644 --- a/niucloud/core/upload/Tencent.php +++ b/niucloud/core/upload/Tencent.php @@ -42,10 +42,13 @@ class Tencent extends BaseUpload return new Client( array( 'region' => $region, -// 'schema' => 'https', //协议头部,默认为http +// 'schema' => 'https', // 强制使用HTTPS,避免签名问题 'credentials' => array( 'secretId' => $secret_id, - 'secretKey' => $secret_key) + 'secretKey' => $secret_key), + // 添加调试和时间同步选项 + 'timeout' => 60, // 增加超时时间 + 'verify' => false // 在开发环境禁用SSL验证(仅开发用) ) ); } @@ -61,24 +64,14 @@ class Tencent extends BaseUpload $this->validate(); $bucket = $this->config['bucket']; try { - // 临时关闭PHP警告,避免deprecated警告被当作异常 - $old_error_reporting = error_reporting(E_ERROR | E_PARSE); - $result = $this->client()->putObject(array( 'Bucket' => $bucket, //存储桶名称,由BucketName-Appid 组成,可以在COS控制台查看 https://console.tencentcloud.com/cos5/bucket 'Key' => $this->getFullPath($dir), 'Body' => fopen($this->getRealPath(), 'rb'), )); - - // 恢复错误报告级别 - error_reporting($old_error_reporting); - // 请求成功 return true; } catch ( Exception $e ) { - // 恢复错误报告级别 - error_reporting($old_error_reporting); - // 输出详细错误信息用于调试 error_log("Tencent COS Upload Error: " . $e->getMessage()); error_log("Tencent COS Config: " . json_encode($this->config)); diff --git a/uniapp/pages-market/clue/clue_info.vue b/uniapp/pages-market/clue/clue_info.vue index 844e6bf3..61c2a2e2 100644 --- a/uniapp/pages-market/clue/clue_info.vue +++ b/uniapp/pages-market/clue/clue_info.vue @@ -1555,7 +1555,7 @@ ${orderInfo.paid_at ? '支付时间:' + this.formatOrderTime(orderInfo.paid_at const isOrderPaid = order.status === 'paid' const buttons = isOrderPaid ? ['知道了', '合同签署'] : ['知道了'] - + console.log('订单数据',order) uni.showModal({ title: '订单详情', content: detailText, diff --git a/uniapp/pages-student/contracts/sign.vue b/uniapp/pages-student/contracts/sign.vue index cfc058fa..0db54738 100644 --- a/uniapp/pages-student/contracts/sign.vue +++ b/uniapp/pages-student/contracts/sign.vue @@ -406,7 +406,7 @@ // 上传图片到服务器 const uploadResult = await new Promise((resolve, reject) => { uni.uploadFile({ - url: Api_url + '/api/upload/image', + url: Api_url + '/file/image', filePath: tempFilePath, name: 'image', header: {