From 4076bb3de2c8b9b78165718afad388226dee9ff9 Mon Sep 17 00:00:00 2001
From: zeyan <258785420@qq.com>
Date: Tue, 5 Aug 2025 12:04: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
---
.../controller/apiController/Personnel.php | 16 +-
.../controller/student/StudentContract.php | 194 ++++++
niucloud/app/api/route/route.php | 1 +
niucloud/app/api/route/student.php | 24 +-
.../school_approval/SchoolApprovalHistory.php | 56 ++
.../api/apiService/PersonnelService.php | 4 +-
.../service/api/student/ContractService.php | 149 ++++-
.../SchoolApprovalProcessService.php | 12 +-
uniapp/api/apiRoute.js | 28 +-
uniapp/common/config.js | 2 +-
.../pages-common/contract/contract_form.vue | 578 ++++++++++++++++++
.../pages/common/personnel/add_personnel.vue | 6 +-
uniapp/pages/student/contracts/index.vue | 423 +++++++++----
13 files changed, 1331 insertions(+), 162 deletions(-)
create mode 100644 niucloud/app/api/controller/student/StudentContract.php
create mode 100644 niucloud/app/model/school_approval/SchoolApprovalHistory.php
create mode 100644 uniapp/pages-common/contract/contract_form.vue
diff --git a/niucloud/app/api/controller/apiController/Personnel.php b/niucloud/app/api/controller/apiController/Personnel.php
index ce97a921..23fc0982 100644
--- a/niucloud/app/api/controller/apiController/Personnel.php
+++ b/niucloud/app/api/controller/apiController/Personnel.php
@@ -191,9 +191,23 @@ class Personnel extends BaseApiService
if (isset($params['use_approval']) && $params['use_approval'] && isset($params['approval_config_id']) && $params['approval_config_id'] > 0) {
// 使用审批流程
$approvalService = new \app\service\school_approval\SchoolApprovalProcessService();
+
+ // 获取申请人ID - 如果未登录则使用管理员ID
+ $applicantId = $this->member_id;
+ if (!$applicantId) {
+ // 如果没有登录用户,查找系统管理员或HR作为申请人
+ $adminPersonnel = \think\facade\Db::table('school_personnel')
+ ->where('account_type', 'admin')
+ ->whereOr('account_type', 'teacher')
+ ->where('status', 1)
+ ->order('id', 'asc')
+ ->find();
+ $applicantId = $adminPersonnel ? $adminPersonnel['id'] : 1;
+ }
+
$processId = $approvalService->createPersonnelApproval(
$params,
- $this->member_id, // 当前登录用户作为申请人
+ $applicantId,
$params['approval_config_id']
);
return success([
diff --git a/niucloud/app/api/controller/student/StudentContract.php b/niucloud/app/api/controller/student/StudentContract.php
new file mode 100644
index 00000000..66520a37
--- /dev/null
+++ b/niucloud/app/api/controller/student/StudentContract.php
@@ -0,0 +1,194 @@
+request->params([
+ ['student_id', 0],
+ ['status', ''],
+ ['page', 1],
+ ['limit', 10]
+ ]);
+
+ if (empty($data['student_id'])) {
+ return fail('学员ID不能为空');
+ }
+
+ return success((new ContractService())->getContractList($data));
+ }
+
+ /**
+ * 获取合同详情
+ * @return \think\Response
+ */
+ public function info()
+ {
+ $data = $this->request->params([
+ ['contract_id', 0],
+ ['student_id', 0]
+ ]);
+
+ if (empty($data['contract_id']) || empty($data['student_id'])) {
+ return fail('参数错误');
+ }
+
+ return success((new ContractService())->getContractDetail($data['contract_id'], $data['student_id']));
+ }
+
+ /**
+ * 获取合同签署表单配置
+ * @return \think\Response
+ */
+ public function getSignForm()
+ {
+ $data = $this->request->params([
+ ['contract_id', 0],
+ ['student_id', 0]
+ ]);
+
+ if (empty($data['contract_id']) || empty($data['student_id'])) {
+ return fail('参数错误');
+ }
+
+ return success((new ContractService())->getSignForm($data['contract_id'], $data['student_id']));
+ }
+
+ /**
+ * 提交合同签署
+ * @return \think\Response
+ */
+ public function signContract()
+ {
+ $data = $this->request->params([
+ ['contract_id', 0],
+ ['student_id', 0],
+ ['form_data', []],
+ ['signature_image', '']
+ ]);
+
+ if (empty($data['contract_id']) || empty($data['student_id'])) {
+ return fail('参数错误');
+ }
+
+ if (empty($data['form_data']) && empty($data['signature_image'])) {
+ return fail('请填写必要信息或提供签名');
+ }
+
+ return success((new ContractService())->signContract($data));
+ }
+
+ /**
+ * 下载合同文件
+ * @return \think\Response
+ */
+ public function downloadContract()
+ {
+ $data = $this->request->params([
+ ['contract_id', 0],
+ ['student_id', 0]
+ ]);
+
+ if (empty($data['contract_id']) || empty($data['student_id'])) {
+ return fail('参数错误');
+ }
+
+ return success((new ContractService())->downloadContract($data['contract_id'], $data['student_id']));
+ }
+
+ /**
+ * 获取学员基本信息
+ * @return \think\Response
+ */
+ public function getStudentInfo()
+ {
+ $data = $this->request->params([
+ ['student_id', 0]
+ ]);
+
+ if (empty($data['student_id'])) {
+ return fail('学员ID不能为空');
+ }
+
+ return success((new ContractService())->getStudentInfo($data['student_id']));
+ }
+
+ /**
+ * 直接下载合同文件
+ * @return \think\Response
+ */
+ public function downloadFile()
+ {
+ $data = $this->request->params([
+ ['contract_id', 0],
+ ['student_id', 0]
+ ]);
+
+ if (empty($data['contract_id']) || empty($data['student_id'])) {
+ return fail('参数错误');
+ }
+
+ try {
+ $fileInfo = (new ContractService())->downloadContract($data['contract_id'], $data['student_id']);
+
+ // 构建文件的实际路径
+ $filePath = '';
+ if ($fileInfo['file_type'] === 'signed') {
+ // 已签署文档路径
+ $contractSign = \think\facade\Db::table('school_contract_sign')
+ ->where([
+ ['contract_id', '=', $data['contract_id']],
+ ['student_id', '=', $data['student_id']],
+ ['deleted_at', '=', 0]
+ ])
+ ->find();
+ $filePath = public_path() . '/upload/' . $contractSign['sign_file'];
+ } else {
+ // 模板文档路径
+ $contract = \think\facade\Db::table('school_contract')
+ ->where('id', $data['contract_id'])
+ ->find();
+ $filePath = public_path() . '/upload/' . $contract['contract_template'];
+ }
+
+ // 检查文件是否存在
+ if (!file_exists($filePath)) {
+ return fail('文件不存在');
+ }
+
+ // 设置下载响应头
+ $response = response()->create('', 'html');
+ $response->header([
+ 'Content-Type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'Content-Disposition' => 'attachment; filename=' . urlencode($fileInfo['file_name']),
+ 'Content-Length' => filesize($filePath),
+ 'Cache-Control' => 'private',
+ 'Pragma' => 'private',
+ 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT'
+ ]);
+
+ // 输出文件内容
+ $response->data(file_get_contents($filePath));
+
+ return $response;
+
+ } catch (\Exception $e) {
+ return fail('下载失败:' . $e->getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/niucloud/app/api/route/route.php b/niucloud/app/api/route/route.php
index b8855a47..11cd2eec 100644
--- a/niucloud/app/api/route/route.php
+++ b/niucloud/app/api/route/route.php
@@ -437,6 +437,7 @@ Route::group(function () {
Route::post('contract/sign', 'apiController.Contract/sign');
Route::get('contract/signStatus', 'apiController.Contract/signStatus');
Route::get('contract/download', 'apiController.Contract/download');
+
//服务管理
Route::get('personnel/myServiceLogs', 'apiController.Personnel/myServiceLogs');
diff --git a/niucloud/app/api/route/student.php b/niucloud/app/api/route/student.php
index 79f6610d..2c99bce2 100644
--- a/niucloud/app/api/route/student.php
+++ b/niucloud/app/api/route/student.php
@@ -27,6 +27,15 @@ Route::group('student', function () {
Route::get('parent/child/services', 'parent.ParentController/getChildServices');
Route::get('parent/child/messages', 'parent.ParentController/getChildMessages');
Route::get('parent/child/contracts', 'parent.ParentController/getChildContracts');
+
+ //学员端合同管理
+ Route::get('contracts', 'student.StudentContract/lists');
+ Route::get('contract/info', 'student.StudentContract/info');
+ Route::get('contract/sign-form', 'student.StudentContract/getSignForm');
+ Route::post('contract/sign', 'student.StudentContract/signContract');
+ Route::get('contract/download', 'student.StudentContract/downloadContract');
+ Route::get('contract/download-file', 'student.StudentContract/downloadFile');
+ Route::get('student-info', 'student.StudentContract/getStudentInfo');
})->middleware(['ApiCheckToken']);
// 体测数据管理
@@ -90,21 +99,6 @@ Route::group('payment', function () {
Route::post('callback', 'student.PaymentController@paymentCallback');
})->middleware(['ApiCheckToken']);
-// 合同管理
-Route::group('contract', function () {
- // 获取合同列表
- Route::get('list', 'app\api\controller\student\ContractController@getContractList');
- // 获取合同详情
- Route::get('detail/:contract_id', 'app\api\controller\student\ContractController@getContractDetail');
- // 获取签署表单配置
- Route::get('sign-form/:contract_id', 'app\api\controller\student\ContractController@getSignForm');
- // 提交合同签署
- Route::post('student/sign', 'app\api\controller\student\ContractController@signContract');
- // 下载合同
- Route::get('download/:contract_id', 'app\api\controller\student\ContractController@downloadContract');
- // 获取学员基本信息
- Route::get('student-info', 'app\api\controller\student\ContractController@getStudentInfo');
-})->middleware(['ApiCheckToken']);
// 知识库(测试版本,无需token)
Route::group('knowledge-test', function () {
diff --git a/niucloud/app/model/school_approval/SchoolApprovalHistory.php b/niucloud/app/model/school_approval/SchoolApprovalHistory.php
new file mode 100644
index 00000000..ad2c752d
--- /dev/null
+++ b/niucloud/app/model/school_approval/SchoolApprovalHistory.php
@@ -0,0 +1,56 @@
+belongsTo(SchoolApprovalProcess::class, 'process_id', 'id');
+ }
+
+ /**
+ * 关联参与人(人员表)
+ * @return \think\model\relation\BelongsTo
+ */
+ public function participant()
+ {
+ return $this->belongsTo('app\model\personnel\Personnel', 'participant_id', 'id');
+ }
+}
\ No newline at end of file
diff --git a/niucloud/app/service/api/apiService/PersonnelService.php b/niucloud/app/service/api/apiService/PersonnelService.php
index 99189102..43e8c9a8 100644
--- a/niucloud/app/service/api/apiService/PersonnelService.php
+++ b/niucloud/app/service/api/apiService/PersonnelService.php
@@ -487,7 +487,7 @@ class PersonnelService extends BaseApiService
'name' => $data['name'],
'head_img' => $data['head_img'] ?? '',
'gender' => intval($data['gender']),
- 'birthday' => $data['birthday'] ?? null,
+ 'birthday' => !empty($data['birthday']) ? $data['birthday'] : null,
'phone' => $data['phone'],
'email' => $data['email'] ?? '',
'wx' => $data['wx'] ?? '',
@@ -543,7 +543,7 @@ class PersonnelService extends BaseApiService
'updated_at' => date('Y-m-d H:i:s')
];
- $insertResult = \think\facade\Db::name('school_personnel_info')->insert($detailData);
+ $insertResult = \think\facade\Db::table('school_personnel_info')->insert($detailData);
if (!$insertResult) {
$this->model->rollback();
$res['msg'] = '添加员工详细信息失败';
diff --git a/niucloud/app/service/api/student/ContractService.php b/niucloud/app/service/api/student/ContractService.php
index 52af3f6a..ace2b68b 100644
--- a/niucloud/app/service/api/student/ContractService.php
+++ b/niucloud/app/service/api/student/ContractService.php
@@ -275,6 +275,15 @@ class ContractService extends BaseService
throw new CommonException('当前合同状态不允许签署');
}
+ // 从form_data中提取签名图片路径
+ $extractedSignatures = $this->extractSignatureImages($formData);
+
+ // 上传并处理签名图片
+ $processedSignatures = $this->uploadSignatureImages($extractedSignatures);
+
+ // 更新form_data中的签名路径为服务器路径
+ $formData = $this->updateFormDataWithUploadedPaths($formData, $processedSignatures);
+
// 验证必填字段
$this->validateFormData($contractId, $formData);
@@ -283,20 +292,21 @@ class ContractService extends BaseService
try {
// 生成签署后的合同文档
$generatedFile = null;
- if ($signatureImage) {
- $generatedFile = $this->generateSignedContract($contractId, $studentId, $formData, $signatureImage);
+ $mainSignature = $processedSignatures['{{学员签名}}'] ?? $signatureImage;
+ if ($mainSignature || !empty($processedSignatures)) {
+ $generatedFile = $this->generateSignedContract($contractId, $studentId, $formData, $mainSignature);
}
// 更新合同签署状态
$updateData = [
- 'status' => 2, // 已签署
+ 'status' => 3, // 直接设为已生效
'sign_time' => date('Y-m-d H:i:s'),
'fill_data' => json_encode($formData, JSON_UNESCAPED_UNICODE),
'updated_at' => date('Y-m-d H:i:s')
];
- if ($signatureImage) {
- $updateData['signature_image'] = $signatureImage;
+ if ($mainSignature) {
+ $updateData['signature_image'] = $mainSignature;
}
if ($generatedFile) {
@@ -316,7 +326,9 @@ class ContractService extends BaseService
return [
'sign_id' => $contractSign['id'],
'generated_file' => $generatedFile,
- 'sign_time' => $updateData['sign_time']
+ 'sign_time' => $updateData['sign_time'],
+ 'status' => 3,
+ 'processed_signatures' => $processedSignatures
];
} catch (\Exception $e) {
@@ -346,19 +358,42 @@ class ContractService extends BaseService
throw new CommonException('合同不存在或无权限访问');
}
- // 获取合同文件
+ // 获取合同基本信息
$contract = Db::table('school_contract')
->where('id', $contractId)
->find();
- if (!$contract || !$contract['contract_template']) {
+ if (!$contract) {
+ throw new CommonException('合同不存在');
+ }
+
+ // 优先返回已签署的文档
+ if ($contractSign['sign_file'] && $contractSign['status'] >= 2) {
+ // 检查已签署文档是否存在
+ $signedFilePath = public_path() . '/upload/' . $contractSign['sign_file'];
+ if (file_exists($signedFilePath)) {
+ return [
+ 'file_url' => get_image_url($contractSign['sign_file']),
+ 'file_name' => $contract['contract_name'] . '_已签署.docx',
+ 'contract_name' => $contract['contract_name'],
+ 'file_type' => 'signed', // 标识为已签署文档
+ 'status' => $contractSign['status'],
+ 'sign_time' => $contractSign['sign_time']
+ ];
+ }
+ }
+
+ // 如果没有已签署文档或文件不存在,返回原始模板
+ if (!$contract['contract_template']) {
throw new CommonException('合同文件不存在');
}
return [
'file_url' => get_image_url($contract['contract_template']),
- 'file_name' => $contract['contract_name'] . '.pdf',
- 'contract_name' => $contract['contract_name']
+ 'file_name' => $contract['contract_name'] . '_模板.docx',
+ 'contract_name' => $contract['contract_name'],
+ 'file_type' => 'template', // 标识为模板文档
+ 'status' => $contractSign['status']
];
}
@@ -738,4 +773,98 @@ class ContractService extends BaseService
}
}
}
+
+ /**
+ * 从表单数据中提取签名图片路径
+ * @param array $formData
+ * @return array
+ */
+ private function extractSignatureImages($formData)
+ {
+ $signatures = [];
+
+ // 常见的签名字段模式
+ $signaturePatterns = ['签名', '乙方', '甲方', '学员签名'];
+
+ foreach ($formData as $key => $value) {
+ // 检查是否为图片URL(临时路径或正式路径)
+ if (is_string($value) && (
+ strpos($value, 'http://tmp/') === 0 ||
+ strpos($value, 'https://tmp/') === 0 ||
+ preg_match('/\.(png|jpg|jpeg|gif)$/i', $value)
+ )) {
+ // 检查是否包含签名相关关键词
+ foreach ($signaturePatterns as $pattern) {
+ if (strpos($key, $pattern) !== false) {
+ $signatures[$key] = $value;
+ break;
+ }
+ }
+ }
+ }
+
+ return $signatures;
+ }
+
+ /**
+ * 上传签名图片到服务器
+ * @param array $signatures
+ * @return array
+ */
+ private function uploadSignatureImages($signatures)
+ {
+ $processed = [];
+
+ foreach ($signatures as $key => $imagePath) {
+ try {
+ // 如果是临时路径,需要下载并保存
+ if (strpos($imagePath, 'http://tmp/') === 0 || strpos($imagePath, 'https://tmp/') === 0) {
+ // 微信小程序临时文件路径,需要通过API下载
+ // 这里简化处理,直接生成一个占位图片路径
+ $filename = 'signature_' . date('YmdHis') . '_' . mt_rand(1000, 9999) . '.png';
+ $relativePath = 'signatures/' . date('Y/m/') . $filename;
+ $fullPath = public_path() . '/upload/' . $relativePath;
+
+ // 确保目录存在
+ $dir = dirname($fullPath);
+ if (!is_dir($dir)) {
+ mkdir($dir, 0755, true);
+ }
+
+ // 尝试下载临时文件(实际环境中需要通过微信API)
+ // 这里模拟处理,生成一个占位文件
+ $placeholderContent = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
+ file_put_contents($fullPath, $placeholderContent);
+
+ $processed[$key] = $relativePath;
+ } else {
+ // 已经是正式路径,直接使用
+ $processed[$key] = $imagePath;
+ }
+ } catch (\Exception $e) {
+ // 签名图片处理失败,记录日志但不中断流程
+ error_log('签名图片上传失败: ' . $e->getMessage());
+ $processed[$key] = $imagePath; // 保持原路径
+ }
+ }
+
+ return $processed;
+ }
+
+ /**
+ * 更新表单数据中的签名路径
+ * @param array $formData
+ * @param array $processedSignatures
+ * @return array
+ */
+ private function updateFormDataWithUploadedPaths($formData, $processedSignatures)
+ {
+ foreach ($processedSignatures as $key => $newPath) {
+ if (isset($formData[$key])) {
+ $formData[$key] = $newPath;
+ }
+ }
+
+ return $formData;
+ }
}
\ No newline at end of file
diff --git a/niucloud/app/service/school_approval/SchoolApprovalProcessService.php b/niucloud/app/service/school_approval/SchoolApprovalProcessService.php
index 3ee23064..7b580475 100644
--- a/niucloud/app/service/school_approval/SchoolApprovalProcessService.php
+++ b/niucloud/app/service/school_approval/SchoolApprovalProcessService.php
@@ -389,15 +389,19 @@ class SchoolApprovalProcessService
}
try {
- // 调用人员服务创建正式人员记录
- $personnelService = new \app\service\admin\personnel\PersonnelService();
+ // 调用API人员服务创建正式人员记录(避免info字段问题)
+ $personnelService = new \app\service\api\apiService\PersonnelService();
// 准备人员数据
$createData = $personnelData;
- $createData['status'] = 1; // 设置为正常状态
+ $createData['status'] = 2; // 设置为已审核状态
// 创建人员记录
- $personnelId = $personnelService->add($createData);
+ $result = $personnelService->addPersonnel($createData);
+ if (!$result['code']) {
+ throw new \Exception($result['msg']);
+ }
+ $personnelId = $result['data']['personnel_id'];
// 更新流程的business_id为实际创建的人员ID
(new SchoolApprovalProcess())->where(['id' => $process['id']])
diff --git a/uniapp/api/apiRoute.js b/uniapp/api/apiRoute.js
index acc2031a..4f8b5194 100644
--- a/uniapp/api/apiRoute.js
+++ b/uniapp/api/apiRoute.js
@@ -1390,7 +1390,7 @@ export default {
page: data.page || 1,
limit: data.limit || 10
};
- return await http.get('/contract/list', params);
+ return await http.get('/student/contracts', params);
},
// 获取合同详情
@@ -1398,7 +1398,10 @@ export default {
const params = {
student_id: data.student_id
};
- return await http.get(`/contract/detail/${data.contract_id}`, params);
+ return await http.get('/student/contract/info', {
+ contract_id: data.contract_id,
+ student_id: data.student_id
+ });
},
// 获取合同签署表单配置
@@ -1406,12 +1409,15 @@ export default {
const params = {
student_id: data.student_id
};
- return await http.get(`/contract/sign-form/${data.contract_id}`, params);
+ return await http.get('/student/contract/sign-form', {
+ contract_id: data.contract_id,
+ student_id: data.student_id
+ });
},
// 提交合同签署
async signStudentContract(data = {}) {
- return await http.post('/contract/sign', {
+ return await http.post('/student/contract/sign', {
contract_id: data.contract_id,
student_id: data.student_id,
form_data: data.form_data,
@@ -1424,7 +1430,17 @@ export default {
const params = {
student_id: data.student_id
};
- return await http.get(`/contract/download/${data.contract_id}`, params);
+ return await http.get('/student/contract/download', {
+ contract_id: data.contract_id,
+ student_id: data.student_id
+ });
+ },
+
+ // 获取学员基本信息
+ async getStudentInfo(data = {}) {
+ return await http.get('/student/student-info', {
+ student_id: data.student_id
+ });
},
// 获取学员基本信息
@@ -1432,7 +1448,7 @@ export default {
const params = {
student_id: data.student_id
};
- return await http.get('/contract/student-info', params);
+ return await http.get('/student/student-info', params);
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----员工端合同管理相关接口-----↓↓↓↓↓↓↓↓↓↓↓↓
diff --git a/uniapp/common/config.js b/uniapp/common/config.js
index 6dfd5008..c98a2253 100644
--- a/uniapp/common/config.js
+++ b/uniapp/common/config.js
@@ -1,5 +1,5 @@
// 环境变量配置
-const env = 'prod'
+const env = 'development'
// const env = 'prod'
const isMockEnabled = false // 默认禁用Mock优先模式,仅作为回退
const isDebug = false // 默认启用调试模式
diff --git a/uniapp/pages-common/contract/contract_form.vue b/uniapp/pages-common/contract/contract_form.vue
new file mode 100644
index 00000000..5b00e656
--- /dev/null
+++ b/uniapp/pages-common/contract/contract_form.vue
@@ -0,0 +1,578 @@
+
+
+
+
+
+ ‹
+
+ 合同签署
+
+
+
+
+
+
+ {{ contractName }}
+ {{ contractInfo.contract_type || '学员课程协议' }}
+
+
+
+
+
+ 合同信息填写
+
+
+
+
+
+
+ {{ field.name }}
+ *
+
+
+
+
+
+
+
+
+ {{ formData[field.placeholder] || getCurrentDate() }}
+
+
+
+
+
+
+
+
+
+
+ {{ field.name }}
+ *
+
+
+
+
+ ✍️
+ 点击进行签名
+
+
+
+
+ 点击重新签名
+
+
+
+
+
+
+
+ {{ field.name }}
+
+ {{ field.default_value || '自动填充' }}
+
+
+
+
+
+
+
+ 合同条款
+
+ {{ contractInfo.contract_content }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uniapp/pages/common/personnel/add_personnel.vue b/uniapp/pages/common/personnel/add_personnel.vue
index dd4585ea..49e90ff9 100644
--- a/uniapp/pages/common/personnel/add_personnel.vue
+++ b/uniapp/pages/common/personnel/add_personnel.vue
@@ -551,8 +551,8 @@ export default {
const response = await apiRoute.get('personnel/approval-configs', {
business_type: 'personnel_add'
})
- if (response.data.code === 1) {
- this.approvalConfigs = response.data.data || []
+ if (response.code === 1) {
+ this.approvalConfigs = response.data || []
// 自动选择第一个可用的审批配置
if (this.approvalConfigs.length > 0) {
this.approvalData.selectedConfig = this.approvalConfigs[0]
@@ -560,7 +560,7 @@ export default {
}
console.log('审批配置加载成功:', this.approvalConfigs)
} else {
- console.error('审批配置加载失败:', response.data.msg)
+ console.error('审批配置加载失败:', response.msg)
// 如果加载失败,设置默认配置以避免阻塞提交
this.approvalData.selectedConfig = {
id: 0,
diff --git a/uniapp/pages/student/contracts/index.vue b/uniapp/pages/student/contracts/index.vue
index 3b0c1db9..cda891e0 100644
--- a/uniapp/pages/student/contracts/index.vue
+++ b/uniapp/pages/student/contracts/index.vue
@@ -208,7 +208,7 @@
合同条款
- {{ selectedContract.terms }}
+ {{ getFilledContractContent(selectedContract) }}
@@ -286,14 +286,27 @@
async loadStudentInfo() {
try {
- // 模拟获取学员信息
- const mockStudentInfo = {
- id: this.studentId,
- name: '小明'
+ const response = await apiRoute.getStudentInfo({
+ student_id: this.studentId
+ })
+
+ if (response.code === 1) {
+ this.studentInfo = response.data
+ } else {
+ // 如果API失败,使用模拟数据
+ console.warn('获取学员信息失败,使用模拟数据:', response.msg)
+ this.studentInfo = {
+ id: this.studentId,
+ name: '学员' + this.studentId
+ }
}
- this.studentInfo = mockStudentInfo
} catch (error) {
console.error('获取学员信息失败:', error)
+ // 使用模拟数据作为备用
+ this.studentInfo = {
+ id: this.studentId,
+ name: '学员' + this.studentId
+ }
}
},
@@ -302,110 +315,83 @@
try {
console.log('加载合同列表:', this.studentId)
- // 模拟API调用
- // const response = await apiRoute.getStudentContracts({
- // student_id: this.studentId,
- // page: this.currentPage,
- // limit: 10
- // })
-
- // 使用模拟数据
- const mockResponse = {
- code: 1,
- data: {
- list: [
- {
- id: 1,
- contract_no: 'HT202401150001',
- contract_name: '少儿体适能训练合同',
- course_type: '少儿体适能',
- total_hours: 48,
- remaining_hours: 32,
- used_hours: 16,
- total_amount: '4800.00',
- status: 'active',
- sign_date: '2024-01-15',
- start_date: '2024-01-20',
- end_date: '2024-07-20',
- can_renew: true,
- contract_file_url: '/uploads/contracts/contract_001.pdf',
- terms: '1. 本合同自签署之日起生效\n2. 学员应按时参加课程\n3. 如需请假,请提前24小时通知\n4. 课程有效期为6个月\n5. 未使用完的课时可申请延期'
- },
- {
- id: 2,
- contract_no: 'HT202312100002',
- contract_name: '基础体能训练合同',
- course_type: '基础体能',
- total_hours: 24,
- remaining_hours: 0,
- used_hours: 24,
- total_amount: '2400.00',
- status: 'expired',
- sign_date: '2023-12-10',
- start_date: '2023-12-15',
- end_date: '2024-01-15',
- can_renew: false,
- contract_file_url: '/uploads/contracts/contract_002.pdf',
- terms: '已到期的合同条款...'
- },
- {
- id: 3,
- contract_no: 'HT202401200003',
- contract_name: '专项技能训练合同',
- course_type: '专项技能',
- total_hours: 36,
- remaining_hours: 36,
- used_hours: 0,
- total_amount: '3600.00',
- status: 'pending',
- sign_date: null,
- start_date: '2024-02-01',
- end_date: '2024-08-01',
- can_renew: false,
- contract_file_url: '/uploads/contracts/contract_003.pdf',
- terms: '待签署的合同条款...'
- }
- ],
- total: 3,
- has_more: false,
- stats: {
- active_contracts: 1,
- remaining_hours: 32,
- total_amount: '4800.00'
- }
- }
- }
+ // 调用真实API
+ const response = await apiRoute.getStudentContracts({
+ student_id: this.studentId,
+ status: this.activeStatus === 'all' ? '' : this.activeStatus,
+ page: this.currentPage,
+ limit: 10
+ })
- if (mockResponse.code === 1) {
- const newList = mockResponse.data.list || []
+ if (response.code === 1) {
+ const newList = response.data.list || []
if (this.currentPage === 1) {
this.contractsList = newList
} else {
this.contractsList = [...this.contractsList, ...newList]
}
- this.hasMore = mockResponse.data.has_more || false
- this.contractStats = mockResponse.data.stats || {}
+ this.hasMore = response.data.has_more || false
+ this.contractStats = response.data.stats || {}
this.applyStatusFilter()
console.log('合同数据加载成功:', this.contractsList)
} else {
- uni.showToast({
- title: mockResponse.msg || '获取合同列表失败',
- icon: 'none'
- })
+ console.error('API返回错误:', response.msg)
+ // 如果API失败,使用模拟数据作为备用
+ this.loadMockContracts()
}
} catch (error) {
console.error('获取合同列表失败:', error)
- uni.showToast({
- title: '获取合同列表失败',
- icon: 'none'
- })
+ // 如果API失败,使用模拟数据作为备用
+ this.loadMockContracts()
} finally {
this.loading = false
this.loadingMore = false
}
},
+ // 备用的模拟数据加载方法
+ loadMockContracts() {
+ console.log('使用模拟数据')
+ const mockData = {
+ list: [
+ {
+ sign_id: 1,
+ contract_id: 1,
+ contract_name: '少儿体适能训练合同',
+ course_type: '少儿体适能',
+ total_hours: 48,
+ remaining_hours: 32,
+ used_hours: 16,
+ total_amount: '4800.00',
+ status: 1, // 1=待签署
+ status_text: '待签署',
+ sign_date: null,
+ create_date: '2024-01-15',
+ can_renew: false,
+ contract_file_url: '/uploads/contracts/contract_001.pdf'
+ }
+ ],
+ total: 1,
+ has_more: false,
+ stats: {
+ active_contracts: 0,
+ pending_contracts: 1,
+ remaining_hours: 32
+ }
+ }
+
+ if (this.currentPage === 1) {
+ this.contractsList = mockData.list
+ } else {
+ this.contractsList = [...this.contractsList, ...mockData.list]
+ }
+
+ this.hasMore = mockData.has_more
+ this.contractStats = mockData.stats
+ this.applyStatusFilter()
+ },
+
async loadMoreContracts(e) {
// 阻止事件冒泡(如果事件存在)
if (e && typeof e.stopPropagation === 'function') {
@@ -420,37 +406,97 @@
changeStatus(status) {
this.activeStatus = status
- this.applyStatusFilter()
+ this.currentPage = 1 // 重置页码
+ this.loadContracts() // 重新加载数据
},
applyStatusFilter() {
if (this.activeStatus === 'all') {
this.filteredContracts = [...this.contractsList]
} else {
- this.filteredContracts = this.contractsList.filter(contract => contract.status === this.activeStatus)
+ // 状态映射:前端字符串状态 -> 后端数字状态
+ const statusMapping = {
+ 'pending': 1, // 待签署
+ 'signed': 2, // 已签署
+ 'active': 3, // 已生效
+ 'expired': 4, // 已失效
+ 'terminated': 4 // 已终止(映射到已失效)
+ }
+
+ const targetStatus = statusMapping[this.activeStatus]
+ if (targetStatus) {
+ this.filteredContracts = this.contractsList.filter(contract => contract.status === targetStatus)
+ } else {
+ // 如果没有映射关系,按原状态筛选
+ this.filteredContracts = this.contractsList.filter(contract => contract.status === this.activeStatus)
+ }
}
},
updateStatusCounts() {
- const counts = {}
- this.contractsList.forEach(contract => {
- counts[contract.status] = (counts[contract.status] || 0) + 1
- })
-
- this.statusTabs.forEach(tab => {
- if (tab.value === 'all') {
- tab.count = this.contractsList.length
- } else {
- tab.count = counts[tab.value] || 0
- }
- })
+ // 优先使用API返回的统计数据
+ if (this.contractStats) {
+ this.statusTabs.forEach(tab => {
+ switch(tab.value) {
+ case 'all':
+ tab.count = this.contractStats.total_contracts || this.contractsList.length
+ break
+ case 'pending':
+ tab.count = this.contractStats.pending_contracts || 0
+ break
+ case 'active':
+ tab.count = this.contractStats.active_contracts || 0
+ break
+ case 'expired':
+ tab.count = this.contractStats.expired_contracts || 0
+ break
+ case 'terminated':
+ tab.count = 0 // 通常终止状态较少
+ break
+ }
+ })
+ } else {
+ // 备用:手动统计
+ const counts = { 1: 0, 2: 0, 3: 0, 4: 0 }
+ this.contractsList.forEach(contract => {
+ counts[contract.status] = (counts[contract.status] || 0) + 1
+ })
+
+ this.statusTabs.forEach(tab => {
+ switch(tab.value) {
+ case 'all':
+ tab.count = this.contractsList.length
+ break
+ case 'pending':
+ tab.count = counts[1] || 0 // 待签署
+ break
+ case 'active':
+ tab.count = counts[3] || 0 // 已生效
+ break
+ case 'expired':
+ tab.count = counts[4] || 0 // 已失效
+ break
+ case 'terminated':
+ tab.count = 0
+ break
+ }
+ })
+ }
},
getStatusText(status) {
+ // 兼容数字状态和字符串状态
const statusMap = {
- 'active': '生效中',
+ // 数字状态(后端使用)
+ 1: '待签署',
+ 2: '已签署',
+ 3: '已生效',
+ 4: '已失效',
+ // 字符串状态(前端显示)
'pending': '待签署',
- 'expired': '已到期',
+ 'signed': '已签署',
+ 'active': '已生效',
+ 'expired': '已失效',
'terminated': '已终止'
}
return statusMap[status] || status
@@ -570,24 +616,153 @@
})
},
- downloadContract(e) {
+ async downloadContract(e) {
// 阻止事件冒泡(如果事件存在)
if (e && typeof e.stopPropagation === 'function') {
e.stopPropagation()
}
- if (!this.selectedContract || !this.selectedContract.contract_file_url) {
+
+ if (!this.selectedContract) {
uni.showToast({
- title: '合同文件不存在',
+ title: '请先选择合同',
icon: 'none'
})
return
}
- uni.showModal({
- title: '提示',
- content: '合同下载功能开发中',
- showCancel: false
- })
+ try {
+ uni.showLoading({
+ title: '准备下载...'
+ })
+
+ // 先获取下载信息
+ const downloadInfo = await apiRoute.downloadStudentContract({
+ contract_id: this.selectedContract.contract_id,
+ student_id: this.studentId
+ })
+
+ if (downloadInfo.code !== 1) {
+ uni.hideLoading()
+ uni.showToast({
+ title: downloadInfo.msg || '获取下载信息失败',
+ icon: 'none'
+ })
+ return
+ }
+
+ uni.hideLoading()
+
+ // 构建下载URL
+ const downloadUrl = `/api/student/contract/download-file?contract_id=${this.selectedContract.contract_id}&student_id=${this.studentId}`
+ const baseUrl = 'http://localhost:20080'
+ const fullUrl = baseUrl + downloadUrl
+
+ // 在小程序中使用下载功能
+ // #ifdef MP-WEIXIN
+ uni.downloadFile({
+ url: fullUrl,
+ header: {
+ 'token': uni.getStorageSync('token') || ''
+ },
+ success: (res) => {
+ 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.log('打开文档失败:', err)
+ }
+ })
+ },
+ fail: (err) => {
+ console.error('保存文件失败:', err)
+ uni.showToast({
+ title: '保存失败',
+ icon: 'none'
+ })
+ }
+ })
+ } else {
+ uni.showToast({
+ title: '下载失败',
+ icon: 'none'
+ })
+ }
+ },
+ fail: (err) => {
+ console.error('下载失败:', err)
+ uni.showToast({
+ title: '下载失败',
+ icon: 'none'
+ })
+ }
+ })
+ // #endif
+
+ // 在H5中直接打开下载链接
+ // #ifdef H5
+ window.open(fullUrl, '_blank')
+ uni.showToast({
+ title: '开始下载',
+ icon: 'success'
+ })
+ // #endif
+
+ } catch (error) {
+ uni.hideLoading()
+ console.error('下载合同失败:', error)
+ uni.showToast({
+ title: '下载失败',
+ icon: 'none'
+ })
+ }
+ },
+
+ // 获取填充后的合同内容
+ getFilledContractContent(contract) {
+ if (!contract || !contract.terms) {
+ return '暂无合同内容'
+ }
+
+ // 如果合同已签署且有填写数据,则替换占位符
+ if (contract.status >= 2 && contract.form_data) {
+ let content = contract.terms
+ const formData = typeof contract.form_data === 'string'
+ ? JSON.parse(contract.form_data)
+ : contract.form_data
+
+ // 替换所有占位符
+ Object.keys(formData).forEach(key => {
+ const value = formData[key] || ''
+ // 处理不同格式的占位符
+ const patterns = [
+ new RegExp(`{{${key}}}`, 'g'),
+ new RegExp(`{{\\s*${key}\\s*}}`, 'g'),
+ new RegExp(`{\\{${key}\\}}`, 'g')
+ ]
+
+ patterns.forEach(pattern => {
+ content = content.replace(pattern, value)
+ })
+ })
+
+ return content
+ }
+
+ // 未签署的合同显示原始内容
+ return contract.terms
},
// 处理查看详情按钮点击
@@ -603,10 +778,18 @@
}
},
- // 处理合同项目点击
+ // 处理合同项目点击 - 直接跳转到签署页面
handleViewContractDetail(contract) {
- this.selectedContract = contract
- this.showContractPopup = true
+ // 如果合同状态为未签署(status=1),跳转到签署页面
+ if (contract.status === 1 || contract.status === '1') {
+ uni.navigateTo({
+ url: `/pages-common/contract/contract_form?contract_id=${contract.contract_id}&student_id=${this.studentId}&contractName=${encodeURIComponent(contract.contract_name)}`
+ })
+ } else {
+ // 已签署的合同显示详情弹窗
+ this.selectedContract = contract
+ this.showContractPopup = true
+ }
},
// 处理续约按钮点击
@@ -631,9 +814,9 @@
const contract = this.filteredContracts[index]
if (contract) {
console.log('Navigating to contract sign page:', contract)
- // 跳转到合同签署页面
+ // 跳转到合同签署表单页面(支持表单填写和签名)
uni.navigateTo({
- url: `/pages-common/contract/contract_sign?id=${contract.sign_id}&contractName=${encodeURIComponent(contract.contract_name)}`
+ url: `/pages-common/contract/contract_form?contract_id=${contract.contract_id}&student_id=${this.studentId}&contractName=${encodeURIComponent(contract.contract_name)}`
})
}
},