From ec84d19ec4a7a52319a11345eae21ffb991799b9 Mon Sep 17 00:00:00 2001 From: zeyan <258785420@qq.com> Date: Mon, 18 Aug 2025 23:05:22 +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 --- admin/src/app/views/contract/contract.vue | 23 +- .../api/controller/apiController/Contract.php | 5 + niucloud/app/api/route/route.php | 4 +- .../apiService/ContractSignFormService.php | 169 ++++++++++--- .../core/sys/CoreFieldMappingService.php | 4 + uniapp/api/apiRoute.js | 7 + uniapp/pages-student/contracts/sign.vue | 225 +++++++++++++++++- 7 files changed, 389 insertions(+), 48 deletions(-) diff --git a/admin/src/app/views/contract/contract.vue b/admin/src/app/views/contract/contract.vue index 1471b27d..c5c0b395 100644 --- a/admin/src/app/views/contract/contract.vue +++ b/admin/src/app/views/contract/contract.vue @@ -126,11 +126,6 @@ - - - - -
@@ -294,29 +289,31 @@ - - - + + + - - + + - + + - + + - + diff --git a/niucloud/app/api/controller/apiController/Contract.php b/niucloud/app/api/controller/apiController/Contract.php index 751ed165..5d650acd 100644 --- a/niucloud/app/api/controller/apiController/Contract.php +++ b/niucloud/app/api/controller/apiController/Contract.php @@ -425,4 +425,9 @@ class Contract extends BaseApiService return fail('合同签署失败:' . $e->getMessage()); } } + + public function confirmGenerateContract(Request $request) + { + $contract_sign_id = $request->param('contract_sign_id', 0); + } } \ No newline at end of file diff --git a/niucloud/app/api/route/route.php b/niucloud/app/api/route/route.php index 02d21cb0..720e137c 100644 --- a/niucloud/app/api/route/route.php +++ b/niucloud/app/api/route/route.php @@ -613,7 +613,9 @@ Route::group(function () { Route::post('student/attendance/leave', 'student.AttendanceController/leave'); // 学员取消 Route::post('student/attendance/cancel', 'student.AttendanceController/cancel'); - + // 员工端生成合同 + Route::post('contract/confirmGenerateContract', 'apiController.Contract/confirmGenerateContract'); + })->middleware(ApiChannel::class) ->middleware(ApiPersonnelCheckToken::class, true) ->middleware(ApiLog::class); diff --git a/niucloud/app/service/api/apiService/ContractSignFormService.php b/niucloud/app/service/api/apiService/ContractSignFormService.php index bd18441b..6797ab41 100644 --- a/niucloud/app/service/api/apiService/ContractSignFormService.php +++ b/niucloud/app/service/api/apiService/ContractSignFormService.php @@ -33,7 +33,7 @@ class ContractSignFormService extends BaseApiService /** * 根据合同签署记录ID获取签署表单配置 * 优先使用contract_sign_id获取该签署关系专属的字段配置 - * + * * @param array $params 请求参数 * - contract_sign_id: 合同签署记录ID (优先) * - contract_id: 合同模板ID (兼容旧版本) @@ -90,6 +90,8 @@ class ContractSignFormService extends BaseApiService 'contract_name' => $contract['contract_name'], 'contract_type' => $contract['contract_type'], 'contract_content' => $contract['contract_content'] ?? '', + 'status' => $sign_record['status'], + 'sign_file' => $sign_record['sign_file'], 'form_fields' => $form_fields, 'student_info' => [ 'id' => $student['id'], @@ -131,7 +133,7 @@ class ContractSignFormService extends BaseApiService * 获取学员合同签署表单配置 * 该方法为移动端提供合同签署表单的完整配置信息 * 包括合同基本信息、占位符字段配置、预填充数据等 - * + * * @param array $params 请求参数 * - contract_id: 合同模板ID (必填) * - student_id: 学员ID (必填) @@ -218,7 +220,7 @@ class ContractSignFormService extends BaseApiService /** * 获取合同模板基本信息 * 从数据库中获取合同模板的基础信息 - * + * * @param int $contract_id 合同模板ID * @return array|null 合同信息数组,失败返回null */ @@ -251,7 +253,7 @@ class ContractSignFormService extends BaseApiService /** * 获取学员基本信息 * 从数据库中获取学员的详细信息,用于表单预填充 - * + * * @param int $student_id 学员ID * @return array|null 学员信息数组,失败返回null */ @@ -285,7 +287,7 @@ class ContractSignFormService extends BaseApiService * 获取合同占位符配置 * 从合同模板中解析占位符配置信息 * 支持从 placeholder_config JSON字段获取配置 - * + * * @param int $contract_id 合同模板ID * @return array 占位符配置数组 */ @@ -333,7 +335,7 @@ class ContractSignFormService extends BaseApiService * 处理表单字段配置 * 根据占位符配置生成移动端可用的表单字段数据 * 对不同数据类型进行差异化处理,预填充相应的默认值 - * + * * @param array $placeholder_config 占位符配置数组 * @param array $student 学员信息 * @return array 处理后的表单字段数组 @@ -428,7 +430,7 @@ class ContractSignFormService extends BaseApiService * 获取数据库字段值 * 根据配置的表名和字段名从数据库获取对应的值 * 支持学员表、订单表、用户表、员工表等多种数据源 - * + * * @param array $config 字段配置 * @param array $student 学员信息(用于关联查询) * @return string 字段值 @@ -501,7 +503,7 @@ class ContractSignFormService extends BaseApiService * 获取系统函数值 * 调用预定义的系统函数获取动态值 * 支持日期时间、业务信息、系统信息等多种函数 - * + * * @param array $config 字段配置 * @return string 函数返回值 */ @@ -545,7 +547,7 @@ class ContractSignFormService extends BaseApiService * 格式化字段值 * 对特定类型的字段值进行格式化处理 * 例如日期格式化、金额格式化等 - * + * * @param string $field_name 字段名 * @param mixed $value 原始值 * @return string 格式化后的值 @@ -587,7 +589,7 @@ class ContractSignFormService extends BaseApiService /** * 保存合同签署记录 * 将签署数据保存到数据库中 - * + * * @param int $contract_id 合同ID * @param int $student_id 学员ID * @param array $form_data 表单数据 @@ -734,27 +736,140 @@ class ContractSignFormService extends BaseApiService switch ($table_name) { case 'school_student': - // 学员表:直接从学员信息获取 - $service = new CoreFieldMappingService($config['table_name'], ['id'=>$student['id']]); - $value = $service->getValue($field_name); + // 学员表:使用CoreFieldMappingService进行字段映射和转义 + $service = new CoreFieldMappingService($config['table_name'], ['id' => $student['id']]); + + // 配置枚举映射 + $service->setFieldEnums([ + 'gender' => [0 => '未知', 1 => '男', 2 => '女'], + 'status' => [0 => '无效', 1 => '有效', 2 => '过期', 3 => '结业'] + ]); + + // 配置关联字段映射 + $service->setFieldRelations([ + 'user_id' => [ + 'table' => 'school_customer_resources', + 'display_field' => 'name', + 'key_field' => 'id' + ], + 'campus_id' => [ + 'table' => 'school_campus', + 'display_field' => 'campus_name', + 'key_field' => 'id' + ], + 'class_id' => [ + 'table' => 'school_class', + 'display_field' => 'class_name', + 'key_field' => 'id' + ], + 'consultant_id' => [ + 'table' => 'school_personnel', + 'display_field' => 'name', + 'key_field' => 'id' + ], + 'coach_id' => [ + 'table' => 'school_personnel', + 'display_field' => 'name', + 'key_field' => 'id' + ] + ]); + + // 检查是否为关联字段,使用链式调用获取关联值 + if (in_array($field_name, ['user_id', 'campus_id', 'class_id', 'consultant_id', 'coach_id'])) { + $relation_config = $service->getRelationMapping()[$field_name]; + $value = $service->getValueWithChain($field_name) + ->withRelation( + $relation_config['table'], + $relation_config['display_field'], + $relation_config['key_field'] + ); + } else { + // 普通字段或枚举字段,自动转义枚举值 + $value = $service->getValue($field_name); + } break; case 'school_customer_resources': - // 用户表:通过学员的user_id关联查询 + // 客户资源表:通过学员的user_id关联查询 if (!empty($student['user_id'])) { - $service = new CoreFieldMappingService($config['table_name'], ['id'=>$student['user_id']]); + $service = new CoreFieldMappingService($config['table_name'], ['id' => $student['user_id']]); + + // 配置枚举映射 + $service->setFieldEnums([ + 'gender' => ['male' => '男性', 'female' => '女性', 'other' => '其他'], + 'initial_intent' => ['high' => '高', 'medium' => '中', 'low' => '低'], + 'rf_type' => ['market' => '市场人员新增', 'sale' => '销售人员新增', 'teacher' => '教练新增'], + 'blacklist' => [1 => '可追单', 0 => '黑名单'] + ]); + $value = $service->getValue($field_name); } break; case 'school_order_table': - // 订单表:查询该学员最新的订单信息 - $order = OrderTable::where('contract_sign_id',$config['contract_sign_id'])->find(); + // 订单表:使用CoreFieldMappingService进行字段映射和转义 + $service = new CoreFieldMappingService($config['table_name'], ['contract_sign_id' => $config['contract_sign_id']]); + + // 配置枚举映射 + $service->setFieldEnums([ + 'order_type' => [1 => '新订单', 2 => '续费订单', 3 => '内部员工订单', 4 => '转校', 5 => '客户内转课订单'], + 'order_status' => ['pending' => '待支付', 'paid' => '已支付', 'signed' => '待签约', 'completed' => '已完成', 'transfer' => '转学'], + 'payment_type' => ['cash' => '现金支付', 'scan_code' => '扫码支付', 'subscription' => '订阅支付', 'wxpay_online' => '微信在线代付'], + 'gift_type' => [1 => '减现', 2 => '赠课'] + ]); - $value = Db::table('school_order_table') - ->where('student_id', $student['id']) - ->order('created_at', 'desc') - ->value($field_name) ?? ''; + // 配置关联字段映射 + $service->setFieldRelations([ + 'course_id' => [ + 'table' => 'school_course', + 'display_field' => 'course_name', + 'key_field' => 'id' + ], + 'class_id' => [ + 'table' => 'school_class', + 'display_field' => 'class_name', + 'key_field' => 'id' + ], + 'staff_id' => [ + 'table' => 'school_personnel', + 'display_field' => 'name', + 'key_field' => 'id' + ], + 'resource_id' => [ + 'table' => 'school_customer_resources', + 'display_field' => 'name', + 'key_field' => 'id' + ], + 'campus_id' => [ + 'table' => 'school_campus', + 'display_field' => 'CONCAT(campus_name, campus_address)', + 'key_field' => 'id' + ], + 'gift_id' => [ + 'table' => 'shcool_resources_gift', + 'display_field' => 'gift_name', + 'key_field' => 'id' + ], + 'course_plan_id' => [ + 'table' => 'school_student_courses', + 'display_field' => 'CONCAT("正式课时数量:", total_hours, ",赠送课时", gift_hours, ",课程有效期为", start_date, "至", end_date)', + 'key_field' => 'id' + ] + ]); + + // 检查是否为关联字段,使用链式调用获取关联值 + if (in_array($field_name, ['course_id', 'class_id', 'staff_id', 'resource_id', 'campus_id', 'gift_id', 'course_plan_id'])) { + $relation_config = $service->getRelationMapping()[$field_name]; + $value = $service->getValueWithChain($field_name) + ->withRelation( + $relation_config['table'], + $relation_config['display_field'], + $relation_config['key_field'] + ); + } else { + // 普通字段或枚举字段,自动转义枚举值 + $value = $service->getValue($field_name); + } break; case 'school_personnel': @@ -830,7 +945,7 @@ class ContractSignFormService extends BaseApiService /** * 员工端合同签署提交 * 处理员工端提交的合同签署表单数据,员工可以填写甲乙双方所有字段 - * + * * @param array $params 提交参数 * - contract_id: 合同模板ID * - contract_sign_id: 合同签署记录ID @@ -916,7 +1031,7 @@ class ContractSignFormService extends BaseApiService /** * 学员端合同签署提交(新版本) * 处理学员端提交的合同签署表单数据,学员只能填写乙方字段 - * + * * @param array $params 提交参数 * - contract_id: 合同模板ID * - contract_sign_id: 合同签署记录ID @@ -1016,7 +1131,7 @@ class ContractSignFormService extends BaseApiService /** * 保存表单数据到 school_document_data_source_config 表的 default_value 字段 * 这是核心方法,确保数据正确写入到指定表中 - * + * * @param int $contract_id 合同模板ID * @param int $contract_sign_id 合同签署记录ID * @param array $form_data 表单数据 @@ -1129,7 +1244,7 @@ class ContractSignFormService extends BaseApiService /** * 过滤学员端表单数据,只保留乙方字段 - * + * * @param int $contract_id 合同模板ID * @param int $contract_sign_id 合同签署记录ID * @param array $form_data 原始表单数据 @@ -1149,7 +1264,7 @@ class ContractSignFormService extends BaseApiService foreach ($form_data as $placeholder => $value) { // 查找对应的字段配置 - $config = array_filter($configs, function($c) use ($placeholder) { + $config = array_filter($configs, function ($c) use ($placeholder) { return $c['placeholder'] === $placeholder; }); @@ -1202,7 +1317,7 @@ class ContractSignFormService extends BaseApiService /** * 更新合同签署记录状态 - * + * * @param int $contract_sign_id 合同签署记录ID * @param int|null $personnel_id 员工ID(员工签署时传入) * @param string $signature_image 签名图片 diff --git a/niucloud/app/service/core/sys/CoreFieldMappingService.php b/niucloud/app/service/core/sys/CoreFieldMappingService.php index be497f56..af3cc35e 100644 --- a/niucloud/app/service/core/sys/CoreFieldMappingService.php +++ b/niucloud/app/service/core/sys/CoreFieldMappingService.php @@ -299,6 +299,10 @@ class CoreFieldMappingService extends BaseCoreService // 尝试从缓存获取 $cached_value = Cache::get($cache_key); if ($cached_value !== false && $cached_value !== null) { + // 如果需要返回包装对象,将缓存值包装成FieldValue对象 + if ($return_wrapper) { + return new FieldValue($cached_value, $this, $field_name); + } return $cached_value; } } diff --git a/uniapp/api/apiRoute.js b/uniapp/api/apiRoute.js index 3699cf18..2af84deb 100644 --- a/uniapp/api/apiRoute.js +++ b/uniapp/api/apiRoute.js @@ -1755,6 +1755,13 @@ export default { }) }, + // 确认生成合同 + async confirmGenerateContract(data = {}) { + return await http.post('/contract/confirmGenerateContract', { + contract_sign_id: data.contract_sign_id, + }) + }, + // 下载合同文件 async downloadStudentContract(data = {}) { const params = { diff --git a/uniapp/pages-student/contracts/sign.vue b/uniapp/pages-student/contracts/sign.vue index 49bf1809..423f1ff2 100644 --- a/uniapp/pages-student/contracts/sign.vue +++ b/uniapp/pages-student/contracts/sign.vue @@ -149,15 +149,41 @@ - + + + + + + + + + + @@ -180,6 +206,7 @@ formData: {}, loading: true, submitting: false, + generating: false, // 生成合同状态 // 角色和权限控制 userInfo: null, userRole: 'student', // 默认为学生角色 @@ -441,8 +468,11 @@ if (response.code === 1) { const data = response.data this.contractInfo = { + contract_sign_id: data.contract_sign_id, contract_name: data.contract_name, - contract_type: data.contract_type + contract_type: data.contract_type, + status: data.status, + sign_file: data.sign_file } this.contractContent = data.contract_content || '' this.formFields = data.form_fields || [] @@ -694,6 +724,118 @@ } finally { this.submitting = false } + }, + + // 确认生成合同 + async confirmGenerateContract() { + this.generating = true + try { + console.log('确认生成合同:', { + contractSignId: this.contractSignId + }) + + const response = await apiRoute.confirmGenerateContract({ + contract_sign_id: this.contractSignId + }) + + if (response.code === 1) { + uni.showToast({ + title: '合同生成成功', + icon: 'success', + duration: 2000 + }) + + // 重新加载表单数据以更新合同状态 + setTimeout(() => { + this.loadSignForm() + }, 2000) + } else { + uni.showToast({ + title: response.msg || '合同生成失败', + icon: 'none' + }) + } + } catch (error) { + console.error('合同生成失败:', error) + uni.showToast({ + title: '合同生成失败', + icon: 'none' + }) + } finally { + this.generating = false + } + }, + // 下载合同 + downloadContract() { + if (!this.contractInfo || !this.contractInfo.sign_file) { + uni.showToast({ + title: '合同文件不存在', + icon: 'none' + }) + return + } + + // 下载合同文件 + uni.showLoading({ + title: '准备下载...', + mask: true + }) + + // 构建完整的文件URL + const fileUrl = this.contractInfo.sign_file.startsWith('http') + ? this.contractInfo.sign_file + : Api_url + this.contractInfo.sign_file + + // 下载文件 + uni.downloadFile({ + url: fileUrl, + success: (res) => { + uni.hideLoading() + if (res.statusCode === 200) { + // 保存到相册或打开文件 + uni.saveFile({ + tempFilePath: res.tempFilePath, + success: (saveRes) => { + uni.showToast({ + title: '下载成功', + icon: 'success' + }) + + // 打开文件 + uni.openDocument({ + filePath: saveRes.savedFilePath, + success: () => { + console.log('打开文档成功') + }, + fail: (err) => { + console.error('打开文档失败:', err) + } + }) + }, + fail: (err) => { + console.error('保存文件失败:', err) + uni.showToast({ + title: '保存失败', + icon: 'none' + }) + } + }) + } else { + uni.showToast({ + title: '下载失败', + icon: 'none' + }) + } + }, + fail: (err) => { + uni.hideLoading() + console.error('下载失败:', err) + uni.showToast({ + title: '下载失败', + icon: 'none' + }) + } + }) } } } @@ -1079,13 +1221,82 @@ bottom: 0; left: 0; right: 0; - background: #fff; + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(10rpx); padding: 20rpx 32rpx 40rpx; border-top: 1rpx solid #f0f0f0; + box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1); + z-index: 999; // 小程序端适配底部安全区域 // #ifdef MP-WEIXIN padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); // #endif + + // 为了确保完全不透明,添加伪元素背景 + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: #fff; + z-index: -1; + } + + // 按钮组布局 + .button_group { + display: flex; + justify-content: space-between; + align-items: center; + gap: 20rpx; + } + + // UniApp原生按钮样式 + .uni-button { + height: 88rpx; + line-height: 88rpx; + border-radius: 44rpx; + font-size: 32rpx; + font-weight: 500; + border: none; + outline: none; + + &.generate-btn { + flex: 1; + background: linear-gradient(135deg, #FF6B35 0%, #F7931E 100%); + color: #fff; + } + + &.modify-btn { + flex: 1; + background: linear-gradient(135deg, #29D3B4 0%, #1BA89A 100%); + color: #fff; + } + + &.download-btn { + width: 100%; + background: linear-gradient(135deg, #52C41A 0%, #389E0D 100%); + color: #fff; + } + + &.submit-btn { + width: 100%; + background: linear-gradient(135deg, #FF6B35 0%, #F7931E 100%); + color: #fff; + } + + // 按钮按下效果 + &:active { + opacity: 0.8; + transform: scale(0.98); + } + + // 加载状态 + &[loading] { + opacity: 0.7; + } + } } \ No newline at end of file