Browse Source

修改 bug

master
王泽彦 7 months ago
parent
commit
5255d8c12c
  1. 3
      admin/src/app/views/course_schedule/components/course-arrangement-detail.vue
  2. 4
      admin/src/app/views/course_schedule/components/student-search-modal.vue
  3. 28
      admin/src/app/views/course_schedule/components/student-section.vue
  4. 83
      niucloud/app/api/controller/apiController/Contract.php
  5. 5
      niucloud/app/api/route/route.php
  6. 48
      niucloud/app/api/route/student.php
  7. 601
      niucloud/app/service/api/apiService/ContractSignFormService.php
  8. 19
      niucloud/app/service/api/apiService/CourseScheduleService.php
  9. 21
      niucloud/app/service/api/apiService/OrderTableService.php
  10. 58
      uniapp/api/apiRoute.js
  11. 66
      uniapp/pages-student/contracts/sign.vue

3
admin/src/app/views/course_schedule/components/course-arrangement-detail.vue

@ -141,7 +141,8 @@ const loadScheduleDetail = async () => {
})
if (response.code === 1 && response.data) {
const data = response.data
// data
const data = response.data.data || response.data
//
scheduleInfo.value = {

4
admin/src/app/views/course_schedule/components/student-search-modal.vue

@ -291,7 +291,9 @@ const performSearch = async () => {
const response: ApiResponse<StudentInfo[]> = await searchStudents(searchParams)
if (response.code === 1) {
searchResults.value = response.data || []
//
const actualData = response.data?.data || response.data || []
searchResults.value = actualData
hasSearched.value = true
console.log('搜索结果:', searchResults.value)
} else {

28
admin/src/app/views/course_schedule/components/student-section.vue

@ -13,9 +13,19 @@
class="student-count-badge"
/>
</h4>
<div class="section-actions">
<div class="section-info">
剩余容量: {{ formalEmptySeats.length }}
</div>
<el-button
type="primary"
size="small"
@click="$emit('addStudent', { type: 'formal', index: 1 })"
:icon="Plus"
>
新增学员
</el-button>
</div>
</div>
</template>
@ -53,9 +63,19 @@
class="student-count-badge"
/>
</h4>
<div class="section-actions">
<div class="section-info">
剩余等待位: {{ waitingEmptySeats.length }}
</div>
<el-button
type="primary"
size="small"
@click="$emit('addStudent', { type: 'waiting', index: 1 })"
:icon="Plus"
>
新增学员
</el-button>
</div>
</div>
</template>
@ -83,7 +103,7 @@
</template>
<script lang="ts" setup>
import { UserFilled, Clock } from '@element-plus/icons-vue'
import { UserFilled, Clock, Plus } from '@element-plus/icons-vue'
import StudentCard from './student-card.vue'
import EmptySeatCard from './empty-seat-card.vue'
@ -157,12 +177,18 @@ defineEmits<Emits>()
}
}
.section-actions {
display: flex;
align-items: center;
gap: 16px;
.section-info {
font-size: 14px;
color: #909399;
font-weight: 500;
}
}
}
.students-grid {
display: grid;

83
niucloud/app/api/controller/apiController/Contract.php

@ -13,6 +13,7 @@ namespace app\api\controller\apiController;
use app\Request;
use app\service\api\apiService\ContractService;
use app\service\api\apiService\ContractSignFormService;
use core\base\BaseApiService;
/**
@ -215,4 +216,86 @@ class Contract extends BaseApiService
return fail('下载合同失败:' . $e->getMessage());
}
}
/**
* 获取学生合同签署表单配置
* @param Request $request
* @return mixed
*/
public function getStudentContractSignForm(Request $request)
{
$contract_id = $request->param('contract_id', 0);
$student_id = $request->param('student_id', 0);
if (empty($contract_id)) {
return fail('合同ID不能为空');
}
if (empty($student_id)) {
return fail('学生ID不能为空');
}
$params = [
'contract_id' => $contract_id,
'student_id' => $student_id
];
try {
$service = new ContractSignFormService();
$res = $service->getStudentContractSignForm($params);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data']);
} catch (\Exception $e) {
return fail('获取签署表单失败:' . $e->getMessage());
}
}
/**
* 提交学生合同签署
* @param Request $request
* @return mixed
*/
public function submitStudentContractSign(Request $request)
{
$contract_id = $request->param('contract_id', 0);
$student_id = $request->param('student_id', 0);
$form_data = $request->param('form_data', []);
$signature_data = $request->param('signature_data', '');
if (empty($contract_id)) {
return fail('合同ID不能为空');
}
if (empty($student_id)) {
return fail('学生ID不能为空');
}
if (empty($form_data)) {
return fail('表单数据不能为空');
}
$params = [
'contract_id' => $contract_id,
'student_id' => $student_id,
'form_data' => $form_data,
'signature_data' => $signature_data
];
try {
$service = new ContractSignFormService();
$res = $service->submitStudentContractSign($params);
if (!$res['code']) {
return fail($res['msg']);
}
return success($res['data'], '合同签署成功');
} catch (\Exception $e) {
return fail('合同签署失败:' . $e->getMessage());
}
}
}

5
niucloud/app/api/route/route.php

@ -461,6 +461,11 @@ 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('contract/getStudentContractSignForm', 'apiController.Contract/getStudentContractSignForm');
Route::post('contract/submitStudentContractSign', 'apiController.Contract/submitStudentContractSign');
// 前端期望的路由路径(映射到相同的控制器方法)
Route::get('student/contract/sign-form', 'apiController.Contract/getStudentContractSignForm');
//服务管理

48
niucloud/app/api/route/student.php

@ -31,7 +31,6 @@ Route::group('student', function () {
//学员端合同管理
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');
@ -99,27 +98,6 @@ Route::group('payment', function () {
Route::post('callback', 'student.PaymentController@paymentCallback');
})->middleware(['ApiCheckToken']);
// 知识库(测试版本,无需token)
Route::group('knowledge-test', function () {
// 获取知识文章列表
Route::get('list/:student_id', 'app\api\controller\student\KnowledgeController@getKnowledgeList');
// 获取知识分类列表
Route::get('categories', 'app\api\controller\student\KnowledgeController@getKnowledgeCategories');
// 获取推荐文章
Route::get('recommend/:student_id', 'app\api\controller\student\KnowledgeController@getRecommendArticles');
// 获取文章详情
Route::get('detail/:id', 'app\api\controller\student\KnowledgeController@getKnowledgeDetail');
// 标记文章已读
Route::post('mark-read', 'app\api\controller\student\KnowledgeController@markArticleRead');
// 收藏/取消收藏文章
Route::post('toggle-favorite', 'app\api\controller\student\KnowledgeController@toggleArticleFavorite');
// 获取知识库统计
Route::get('stats/:student_id', 'app\api\controller\student\KnowledgeController@getKnowledgeStats');
// 搜索知识文章
Route::get('search/:student_id', 'app\api\controller\student\KnowledgeController@searchKnowledgeArticles');
});
// 知识库
Route::group('knowledge', function () {
// 获取知识文章列表
@ -140,26 +118,6 @@ Route::group('knowledge', function () {
Route::get('search/:student_id', 'app\api\controller\student\KnowledgeController@searchKnowledgeArticles');
})->middleware(['ApiCheckToken']);
// 消息管理(测试版本,无需token)
Route::group('message-test', function () {
// 获取消息列表(按对话分组)
Route::get('list/:student_id', 'app\api\controller\student\MessageController@getMessageList');
// 获取消息详情
Route::get('detail/:message_id', 'app\api\controller\student\MessageController@getMessageDetail');
// 获取对话中的所有消息
Route::get('conversation', 'app\api\controller\student\MessageController@getConversationMessages');
// 学员回复消息
Route::post('reply', 'app\api\controller\student\MessageController@replyMessage');
// 标记消息已读
Route::post('mark-read', 'app\api\controller\student\MessageController@markMessageRead');
// 批量标记已读
Route::post('mark-batch-read', 'app\api\controller\student\MessageController@markBatchRead');
// 获取消息统计
Route::get('stats/:student_id', 'app\api\controller\student\MessageController@getMessageStats');
// 搜索消息
Route::get('search/:student_id', 'app\api\controller\student\MessageController@searchMessages');
});
// 消息管理
Route::group('message', function () {
// 获取消息列表(按对话分组)
@ -187,3 +145,9 @@ Route::post('wechat/bind', 'login.WechatLogin/bind');
Route::get('wechat/auth_url', 'login.WechatLogin/getAuthUrl');
Route::get('wechat/callback', 'login.WechatLogin/callback');
});
// 学员端公开接口(无需token验证)
Route::group('student', function () {
// 获取合同签署表单(无需验证)
Route::get('contract/sign-form', 'student.StudentContract/getSignForm');
});

601
niucloud/app/service/api/apiService/ContractSignFormService.php

@ -0,0 +1,601 @@
<?php
// +----------------------------------------------------------------------
// | Niucloud-admin 企业快速开发的多应用管理平台
// +----------------------------------------------------------------------
// | 官方网址:https://www.niucloud.com
// +----------------------------------------------------------------------
// | niucloud团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: Niucloud Team
// +----------------------------------------------------------------------
namespace app\service\api\apiService;
use core\base\BaseApiService;
use think\facade\Db;
use think\facade\Log;
/**
* 合同签署表单服务类
* 负责处理学员端合同签署相关的业务逻辑
* Class ContractSignFormService
* @package app\service\api\apiService
*/
class ContractSignFormService extends BaseApiService
{
public function __construct()
{
parent::__construct();
}
/**
* 获取学员合同签署表单配置
* 该方法为移动端提供合同签署表单的完整配置信息
* 包括合同基本信息、占位符字段配置、预填充数据等
*
* @param array $params 请求参数
* - contract_id: 合同模板ID (必填)
* - student_id: 学员ID (必填)
* @return array 返回格式化的表单配置数据
* @throws \Exception 当参数验证失败或数据获取异常时抛出异常
*/
public function getStudentContractSignForm(array $params)
{
try {
// 1. 验证必要参数
if (empty($params['contract_id']) || empty($params['student_id'])) {
throw new \Exception('缺少必要参数:contract_id 和 student_id');
}
$contract_id = (int)$params['contract_id'];
$student_id = (int)$params['student_id'];
Log::info('开始获取学员合同签署表单', [
'contract_id' => $contract_id,
'student_id' => $student_id
]);
// 2. 获取合同模板基本信息
$contract = $this->getContractInfo($contract_id);
if (!$contract) {
throw new \Exception('合同模板不存在或已删除');
}
// 3. 获取学员基本信息
$student = $this->getStudentInfo($student_id);
if (!$student) {
throw new \Exception('学员信息不存在');
}
// 4. 获取占位符配置信息
$placeholder_config = $this->getPlaceholderConfig($contract_id);
// 5. 处理占位符配置,生成表单字段
$form_fields = $this->processFormFields($placeholder_config, $student);
// 6. 组装返回数据
$result = [
'contract_id' => $contract_id,
'contract_name' => $contract['contract_name'],
'contract_type' => $contract['contract_type'],
'contract_content' => $contract['contract_content'] ?? '',
'form_fields' => $form_fields,
'student_info' => [
'id' => $student['id'],
'name' => $student['name'],
'phone' => $student['contact_phone'] ?? '',
'user_id' => $student['user_id']
]
];
Log::info('学员合同签署表单获取成功', [
'contract_id' => $contract_id,
'student_id' => $student_id,
'form_fields_count' => count($form_fields)
]);
return [
'code' => 1,
'msg' => '获取成功',
'data' => $result
];
} catch (\Exception $e) {
Log::error('获取学员合同签署表单失败', [
'contract_id' => $params['contract_id'] ?? 0,
'student_id' => $params['student_id'] ?? 0,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'code' => 0,
'msg' => $e->getMessage(),
'data' => []
];
}
}
/**
* 获取合同模板基本信息
* 从数据库中获取合同模板的基础信息
*
* @param int $contract_id 合同模板ID
* @return array|null 合同信息数组,失败返回null
*/
private function getContractInfo($contract_id)
{
try {
$contract = Db::table('school_contract')
->where([
['id', '=', $contract_id],
['contract_status', '=', 'active'], // 只获取启用状态的合同
['deleted_at', '=', 0]
])
->field([
'id', 'contract_name', 'contract_type', 'contract_content',
'placeholder_config', 'created_at', 'updated_at'
])
->find();
return $contract ? $contract : null;
} catch (\Exception $e) {
Log::error('获取合同信息失败', [
'contract_id' => $contract_id,
'error' => $e->getMessage()
]);
return null;
}
}
/**
* 获取学员基本信息
* 从数据库中获取学员的详细信息,用于表单预填充
*
* @param int $student_id 学员ID
* @return array|null 学员信息数组,失败返回null
*/
private function getStudentInfo($student_id)
{
try {
$student = Db::table('school_student')
->where([
['id', '=', $student_id],
['status', '=', 1] // 只获取有效学员
])
->field([
'id', 'name', 'gender', 'age', 'birthday',
'emergency_contact', 'contact_phone', 'member_label',
'user_id', 'campus_id', 'created_at'
])
->find();
return $student ? $student : null;
} catch (\Exception $e) {
Log::error('获取学员信息失败', [
'student_id' => $student_id,
'error' => $e->getMessage()
]);
return null;
}
}
/**
* 获取合同占位符配置
* 从合同模板中解析占位符配置信息
* 支持从 placeholder_config JSON字段获取配置
*
* @param int $contract_id 合同模板ID
* @return array 占位符配置数组
*/
private function getPlaceholderConfig($contract_id)
{
try {
// 从合同表获取占位符配置
$placeholder_config_json = Db::table('school_contract')
->where('id', $contract_id)
->value('placeholder_config');
if (empty($placeholder_config_json)) {
Log::warning('合同占位符配置为空', ['contract_id' => $contract_id]);
return [];
}
$config = json_decode($placeholder_config_json, true);
if (!is_array($config)) {
Log::warning('合同占位符配置格式错误', [
'contract_id' => $contract_id,
'config' => $placeholder_config_json
]);
return [];
}
Log::info('获取占位符配置成功', [
'contract_id' => $contract_id,
'config_count' => count($config),
'raw_config_length' => strlen($placeholder_config_json),
'config_keys' => array_keys($config)
]);
return $config;
} catch (\Exception $e) {
Log::error('获取占位符配置失败', [
'contract_id' => $contract_id,
'error' => $e->getMessage()
]);
return [];
}
}
/**
* 处理表单字段配置
* 根据占位符配置生成移动端可用的表单字段数据
* 对不同数据类型进行差异化处理,预填充相应的默认值
*
* @param array $placeholder_config 占位符配置数组
* @param array $student 学员信息
* @return array 处理后的表单字段数组
*/
private function processFormFields($placeholder_config, $student)
{
$form_fields = [];
Log::info('开始处理表单字段配置', [
'config_type' => gettype($placeholder_config),
'config_count' => is_array($placeholder_config) ? count($placeholder_config) : 0,
'config_keys' => is_array($placeholder_config) ? array_keys($placeholder_config) : []
]);
// 检查配置数据格式
if (!is_array($placeholder_config) || empty($placeholder_config)) {
Log::warning('占位符配置为空或格式错误');
return [];
}
// 处理JSON对象格式的配置:{"字段名": {配置}}
foreach ($placeholder_config as $placeholder_name => $config) {
// 确保$config是数组
if (!is_array($config)) {
Log::warning('跳过无效配置项', ['placeholder' => $placeholder_name, 'config' => $config]);
continue;
}
// 基础字段信息 - 使用键名作为占位符名称
$field = [
'name' => $placeholder_name,
'placeholder' => $placeholder_name,
'data_type' => $config['data_type'] ?? 'user_input',
'field_type' => $config['field_type'] ?? 'text',
'is_required' => (int)($config['is_required'] ?? 0),
'default_value' => '',
'validation_rule' => $config['validation_rule'] ?? '',
'sign_party' => $config['sign_party'] ?? '',
];
// 根据数据类型处理默认值
switch ($config['data_type'] ?? 'user_input') {
case 'database':
// 数据库类型:从相关表获取数据
$field['default_value'] = $this->getDatabaseFieldValue($config, $student);
break;
case 'system':
// 系统函数类型:调用系统函数获取值
$field['default_value'] = $this->getSystemFunctionValue($config);
break;
case 'user_input':
// 用户输入类型:使用配置的默认值
$field['default_value'] = $config['default_value'] ?? '';
break;
case 'signature':
// 电子签名类型:无默认值,需要用户手写签名
$field['signature_type'] = $config['signature_type'] ?? 'handwrite';
$field['default_value'] = '';
break;
case 'sign_img':
// 签名图片类型:无默认值,需要用户上传或选择
$field['sign_image_source'] = $config['sign_image_source'] ?? 'upload';
$field['default_value'] = '';
break;
default:
$field['default_value'] = $config['default_value'] ?? '';
break;
}
$form_fields[] = $field;
}
Log::info('表单字段处理完成', [
'total_fields' => count($form_fields),
'database_fields' => count(array_filter($form_fields, fn($f) => $f['data_type'] === 'database')),
'system_fields' => count(array_filter($form_fields, fn($f) => $f['data_type'] === 'system')),
'user_input_fields' => count(array_filter($form_fields, fn($f) => $f['data_type'] === 'user_input')),
'signature_fields' => count(array_filter($form_fields, fn($f) => $f['data_type'] === 'signature')),
'sign_img_fields' => count(array_filter($form_fields, fn($f) => $f['data_type'] === 'sign_img')),
'field_names' => array_column($form_fields, 'name')
]);
return $form_fields;
}
/**
* 获取数据库字段值
* 根据配置的表名和字段名从数据库获取对应的值
* 支持学员表、订单表、用户表、员工表等多种数据源
*
* @param array $config 字段配置
* @param array $student 学员信息(用于关联查询)
* @return string 字段值
*/
private function getDatabaseFieldValue($config, $student)
{
try {
$table_name = $config['table_name'] ?? '';
$field_name = $config['field_name'] ?? '';
if (empty($table_name) || empty($field_name)) {
return '';
}
$value = '';
switch ($table_name) {
case 'school_student':
// 学员表:直接从学员信息获取
$value = $student[$field_name] ?? '';
break;
case 'school_customer_resources':
// 用户表:通过学员的user_id关联查询
if (!empty($student['user_id'])) {
$value = Db::table('school_customer_resources')
->where('id', $student['user_id'])
->value($field_name) ?? '';
}
break;
case 'school_order_table':
// 订单表:查询该学员最新的订单信息
$value = Db::table('school_order_table')
->where('student_id', $student['id'])
->order('created_at', 'desc')
->value($field_name) ?? '';
break;
case 'school_personnel':
// 员工表:这里可能需要根据业务逻辑确定关联的员工
// 暂时返回空值,具体业务逻辑需要根据实际需求调整
$value = '';
break;
default:
Log::warning('不支持的数据库表', [
'table_name' => $table_name,
'field_name' => $field_name
]);
break;
}
// 对特殊字段进行格式化处理
$value = $this->formatFieldValue($field_name, $value);
return (string)$value;
} catch (\Exception $e) {
Log::error('获取数据库字段值失败', [
'config' => $config,
'student_id' => $student['id'],
'error' => $e->getMessage()
]);
return '';
}
}
/**
* 获取系统函数值
* 调用预定义的系统函数获取动态值
* 支持日期时间、业务信息、系统信息等多种函数
*
* @param array $config 字段配置
* @return string 函数返回值
*/
private function getSystemFunctionValue($config)
{
try {
$system_function = $config['system_function'] ?? '';
if (empty($system_function)) {
return '';
}
// 检查函数是否存在
if (!function_exists($system_function)) {
Log::warning('系统函数不存在', [
'function_name' => $system_function
]);
return '';
}
// 调用系统函数
$value = call_user_func($system_function);
Log::info('系统函数调用成功', [
'function_name' => $system_function,
'result' => $value
]);
return (string)$value;
} catch (\Exception $e) {
Log::error('获取系统函数值失败', [
'config' => $config,
'error' => $e->getMessage()
]);
return '';
}
}
/**
* 格式化字段值
* 对特定类型的字段值进行格式化处理
* 例如日期格式化、金额格式化等
*
* @param string $field_name 字段名
* @param mixed $value 原始值
* @return string 格式化后的值
*/
private function formatFieldValue($field_name, $value)
{
if (empty($value)) {
return '';
}
// 根据字段名进行特殊处理
switch (true) {
case str_contains($field_name, 'date') || str_contains($field_name, 'time'):
// 日期时间字段格式化
if (is_numeric($value)) {
return date('Y-m-d', $value);
} elseif (strtotime($value)) {
return date('Y-m-d', strtotime($value));
}
break;
case str_contains($field_name, 'amount') || str_contains($field_name, 'price'):
// 金额字段格式化
return number_format((float)$value, 2);
case str_contains($field_name, 'phone'):
// 手机号格式化(可以添加脱敏处理)
return (string)$value;
default:
// 默认返回字符串
return (string)$value;
}
return (string)$value;
}
/**
* 提交学员合同签署数据
* 处理移动端提交的合同签署表单数据
* 验证数据完整性并保存到数据库
*
* @param array $params 提交参数
* - contract_id: 合同模板ID
* - student_id: 学员ID
* - form_data: 表单数据数组
* @return array 提交结果
*/
public function submitStudentContractSign(array $params)
{
try {
// 验证必要参数
if (empty($params['contract_id']) || empty($params['student_id']) || empty($params['form_data'])) {
throw new \Exception('缺少必要参数');
}
$contract_id = (int)$params['contract_id'];
$student_id = (int)$params['student_id'];
$form_data = $params['form_data'];
Log::info('开始处理学员合同签署提交', [
'contract_id' => $contract_id,
'student_id' => $student_id,
'form_data_keys' => array_keys($form_data)
]);
// 验证合同和学员是否存在
$contract = $this->getContractInfo($contract_id);
if (!$contract) {
throw new \Exception('合同模板不存在');
}
$student = $this->getStudentInfo($student_id);
if (!$student) {
throw new \Exception('学员信息不存在');
}
// 保存签署记录
$sign_id = $this->saveContractSignRecord($contract_id, $student_id, $form_data);
if ($sign_id) {
Log::info('学员合同签署成功', [
'contract_id' => $contract_id,
'student_id' => $student_id,
'sign_id' => $sign_id
]);
return [
'code' => 1,
'msg' => '合同签署成功',
'data' => ['sign_id' => $sign_id]
];
} else {
throw new \Exception('保存签署记录失败');
}
} catch (\Exception $e) {
Log::error('学员合同签署失败', [
'params' => $params,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [
'code' => 0,
'msg' => $e->getMessage(),
'data' => []
];
}
}
/**
* 保存合同签署记录
* 将签署数据保存到数据库中
*
* @param int $contract_id 合同ID
* @param int $student_id 学员ID
* @param array $form_data 表单数据
* @return int|false 签署记录ID,失败返回false
*/
private function saveContractSignRecord($contract_id, $student_id, $form_data)
{
try {
$now = date('Y-m-d H:i:s');
// 准备签署记录数据
$sign_data = [
'contract_id' => $contract_id,
'student_id' => $student_id,
'form_data' => json_encode($form_data, JSON_UNESCAPED_UNICODE),
'sign_status' => 1, // 已签署
'sign_time' => $now,
'created_at' => $now,
'updated_at' => $now,
'deleted_at' => 0
];
// 插入签署记录
$sign_id = Db::table('school_contract_sign')->insertGetId($sign_data);
return $sign_id;
} catch (\Exception $e) {
Log::error('保存合同签署记录失败', [
'contract_id' => $contract_id,
'student_id' => $student_id,
'error' => $e->getMessage()
]);
return false;
}
}
}

19
niucloud/app/service/api/apiService/CourseScheduleService.php

@ -1805,6 +1805,12 @@ class CourseScheduleService extends BaseApiService
$courseProgress['percentage'] = round(($courseProgress['used'] / $courseProgress['total']) * 100, 1);
}
// 转换数据库枚举值为前端期望的值
$personType = $student['person_type'];
if ($personType === 'customer_resource') {
$personType = 'resource';
}
return [
'id' => $student['id'],
'person_id' => $student['person_id'],
@ -1813,7 +1819,7 @@ class CourseScheduleService extends BaseApiService
'name' => $student['resource_name'] ?: $student['student_name'],
'phone' => $student['phone_number'] ?: $student['student_phone'],
'age' => $student['age'] ?? '',
'person_type' => $student['person_type'],
'person_type' => $personType,
'schedule_type' => $student['schedule_type'] ?? 2, // 默认为等待位
'status' => $student['status'],
'courseStatus' => $this->getStudentStatusText($student['status']),
@ -1881,14 +1887,20 @@ class CourseScheduleService extends BaseApiService
// 处理结果数据
$searchResults = [];
foreach ($results as $result) {
// 判断学员类型:如果有正式的学员课程记录,则为正式学员;否则为客户资源
$person_type = !empty($result['student_course_id']) ? 'student' : 'resource';
$searchResults[] = [
'id' => $result['resource_id'],
'name' => $result['name'],
'phone_number' => $result['phone_number'],
'phone' => $result['phone_number'], // 添加phone字段别名
'age' => $result['age'],
'member_id' => $result['member_id'],
'student_id' => $result['student_id'] ?? 0,
'resource_id' => $result['resource_id'],
'resources_id' => $result['resource_id'], // 添加resources_id字段别名
'person_type' => $person_type, // 添加person_type字段
'is_formal_student' => !empty($result['student_course_id']),
'course_info' => [
'course_status' => $result['course_status'] ?? '',
@ -2016,6 +2028,11 @@ class CourseScheduleService extends BaseApiService
];
}
// 转换前端传递的person_type为数据库枚举值
if ($personType === 'resource') {
$personType = 'customer_resource';
}
// 开启事务
Db::startTrans();

21
niucloud/app/service/api/apiService/OrderTableService.php

@ -294,7 +294,12 @@ class OrderTableService extends BaseApiService
'updated_at' => $now
];
$result = Db::table('school_student_courses')->insert($insertData);
$result = Db::table('school_student_courses')->insert($insertData, true);
// 更新订单表的课程计划ID
if ($result) {
OrderTable::where('id', $orderData['id'])->update(['course_plan_id' => $result]);
}
\think\facade\Log::info('学员课程新增成功', [
'student_id' => $student_id,
@ -303,7 +308,8 @@ class OrderTableService extends BaseApiService
'new_start_date' => $new_start_date,
'new_end_date' => $new_end_date,
'new_hours' => $course['session_count'],
'new_gift_hours' => $course['gift_session_count']
'new_gift_hours' => $course['gift_session_count'],
'course_plan_id' => $result
]);
} else {
// 创建新的课程记录
@ -324,8 +330,12 @@ class OrderTableService extends BaseApiService
];
$result = Db::table('school_student_courses')->insert($insertData, true);
// 更新订单表
OrderTable::find($orderData['id'])->update(['course_plan_id' => $result]);
// 更新订单表的课程计划ID
if ($result) {
OrderTable::where('id', $orderData['id'])->update(['course_plan_id' => $result]);
}
\think\facade\Log::info('学员课程创建成功', [
'student_id' => $student_id,
'course_id' => $course_id,
@ -395,7 +405,8 @@ class OrderTableService extends BaseApiService
$result = Db::table('school_contract_sign')->insert($insertData, true);
if ($result) {
OrderTable::find($orderData['id'])->update(['contract_id' => $result]);
// 更新订单表的合同模板ID(来自课程配置)
OrderTable::where('id', $orderData['id'])->update(['contract_id' => $course['contract_id']]);
// 创建签署记录成功后,为甲乙双方生成数据源配置记录
$this->createDocumentDataSourceConfig($course['contract_id'], $orderData);

58
uniapp/api/apiRoute.js

@ -1293,47 +1293,6 @@ export default {
return await http.post('/course/updateStudentStatus', data)
},
//↓↓↓↓↓↓↓↓↓↓↓↓-----统一课程安排管理接口(与admin端保持一致)-----↓↓↓↓↓↓↓↓↓↓↓↓
// 获取课程安排详情(统一接口)
async getScheduleDetail(data = {}) {
try {
const response = await http.get('/course/scheduleDetail', data)
return response
} catch (error) {
console.error('获取课程安排详情失败:', error)
// 降级到 Mock 数据
return await this.getScheduleDetailMock(data)
}
},
// 搜索学员(统一接口)
async searchStudents(data = {}) {
try {
const response = await http.get('/course/searchStudents', data)
return response
} catch (error) {
console.error('搜索学员失败:', error)
// 降级到 Mock 数据
return await this.searchStudentsMock(data)
}
},
// 添加学员到课程安排(统一接口)
async addSchedule(data = {}) {
try {
const response = await http.post('/course/addStudentToSchedule', data)
return response
} catch (error) {
console.error('添加学员失败:', error)
// 返回模拟成功响应
return {
code: 1,
msg: '添加学员成功(模拟)',
data: { id: Date.now() }
}
}
},
// 移除学员(统一接口)
async removeStudent(data = {}) {
@ -1366,23 +1325,6 @@ export default {
}
}
},
// 更新学员状态(统一接口)
async updateStudentStatus(data = {}) {
try {
const response = await http.post('/course/updateStudentStatus', data)
return response
} catch (error) {
console.error('更新学员状态失败:', error)
// 返回模拟成功响应
return {
code: 1,
msg: '更新状态成功(模拟)',
data: {}
}
}
},
// 恢复学员(统一接口)
async restoreStudent(data = {}) {
try {

66
uniapp/pages-student/contracts/sign.vue

@ -27,7 +27,7 @@
</view>
</view>
<view class="content_body" :class="{ expanded: contentExpanded }">
<text class="content_text">{{ renderContractContent }}</text>
<view v-html="renderContractContentWithFields" class="content_text"></view>
</view>
</view>
@ -192,6 +192,70 @@
content = content.replace(regex, value)
})
return content
},
//
renderContractContentWithFields() {
if (!this.contractContent) return ''
let content = this.contractContent
//
this.formFields.forEach(field => {
const placeholder = field.placeholder || field.name
const regex = new RegExp(`\\{\\{${placeholder}\\}\\}`, 'g')
//
let replacementContent = ''
switch (field.data_type) {
case 'user_input':
//
const inputValue = this.formData[placeholder] || field.default_value || ''
if (inputValue) {
replacementContent = `<span style="border-bottom: 1px solid #333; padding: 2px 8px; min-width: 80px; display: inline-block;">${inputValue}</span>`
} else {
replacementContent = `<span style="border-bottom: 1px solid #ccc; padding: 2px 8px; min-width: 80px; display: inline-block; color: #999;">待填写</span>`
}
break
case 'signature':
//
const signatureValue = this.formData[placeholder]
if (signatureValue) {
replacementContent = `<span style="display: inline-block; border: 1px solid #27ae60; padding: 4px 8px; background: rgba(39, 174, 96, 0.1); color: #27ae60; border-radius: 4px;">✓ 已签名</span>`
} else {
replacementContent = `<span style="display: inline-block; border: 1px dashed #29D3B4; padding: 4px 8px; background: rgba(41, 211, 180, 0.05); color: #29D3B4; border-radius: 4px;">✒️ 待签名</span>`
}
break
case 'sign_img':
//
const signImgValue = this.formData[placeholder]
if (signImgValue) {
replacementContent = `<span style="display: inline-block; border: 1px solid #67c23a; padding: 4px 8px; background: rgba(103, 194, 58, 0.1); color: #67c23a; border-radius: 4px;">✓ 已上传</span>`
} else {
replacementContent = `<span style="display: inline-block; border: 1px dashed #409eff; padding: 4px 8px; background: rgba(64, 158, 255, 0.05); color: #409eff; border-radius: 4px;">📷 待上传</span>`
}
break
case 'database':
case 'system':
default:
//
const defaultValue = this.formData[placeholder] || field.default_value || ''
if (defaultValue) {
replacementContent = `<span style="font-weight: 500;">${defaultValue}</span>`
} else {
replacementContent = `<span style="color: #999; font-style: italic;">系统自动获取</span>`
}
break
}
content = content.replace(regex, replacementContent)
})
return content
}
},

Loading…
Cancel
Save