Browse Source

修改 bug

master
王泽彦 8 months ago
parent
commit
db75a64d5e
  1. 12
      admin/src/api/contract.ts
  2. 328
      admin/src/app/views/contract/contract.vue
  3. 36
      niucloud/app/adminapi/controller/document/DocumentTemplate.php
  4. 2
      niucloud/app/adminapi/route/document_template.php
  5. 1
      niucloud/app/api/controller/apiController/Course.php
  6. 2
      niucloud/app/api/controller/apiController/PersonCourseSchedule.php
  7. 2
      niucloud/app/api/route/student.php
  8. 2
      niucloud/app/model/document/DocumentDataSourceConfig.php
  9. 3
      niucloud/app/service/admin/course_schedule/CourseScheduleService.php
  10. 220
      niucloud/app/service/admin/document/DocumentTemplateService.php
  11. 102
      niucloud/app/service/api/apiService/CourseService.php
  12. 12
      niucloud/app/service/api/apiService/PersonCourseScheduleService.php
  13. 17
      niucloud/app/service/api/apiService/StudentService.php
  14. 203
      uniapp/App.vue
  15. 175
      uniapp/components/fitness-record-popup/fitness-record-popup.vue
  16. 13
      uniapp/components/student-edit-popup/student-edit-popup.vue
  17. 3
      uniapp/pages-coach/coach/student/student_list.vue
  18. 75
      uniapp/pages-common/contract/contract_sign.vue
  19. 15
      uniapp/pages-market/clue/class_arrangement_detail.vue
  20. 7
      uniapp/pages-market/clue/clue_info.vue
  21. 30
      uniapp/pages-student/contracts/index.vue
  22. 3
      uniapp/pages.json
  23. 223
      uniapp/pages/student/contracts/index.vue
  24. 3
      uniapp/pages/student/profile/index.vue

12
admin/src/api/contract.ts

@ -61,6 +61,18 @@ export const contractTemplateApi = {
savePlaceholderConfig: (contractId: number, data: any) => savePlaceholderConfig: (contractId: number, data: any) =>
request.post(`/document_template/config/save`, data), request.post(`/document_template/config/save`, data),
// 重新识别占位符
reidentifyPlaceholders: (contractId: number) =>
request.post(`/document_template/reidentify/${contractId}`),
// 更新模板Word文档
updateTemplateFile: (contractId: number, data: FormData) =>
request.post(`/document_template/update_file/${contractId}`, data, {
headers: {
'Content-Type': 'multipart/form-data'
}
}),
// 保存数据源配置到独立表 // 保存数据源配置到独立表
saveDataSourceConfig: (contractId: number, data: any) => saveDataSourceConfig: (contractId: number, data: any) =>
request.post(`/document_template/config/datasource/save`, { contract_id: contractId, configs: data }), request.post(`/document_template/config/datasource/save`, { contract_id: contractId, configs: data }),

328
admin/src/app/views/contract/contract.vue

@ -159,7 +159,65 @@
</div> </div>
<div v-else class="config-table-section"> <div v-else class="config-table-section">
<h4>检测到的占位符 (合同ID: {{ currentContractId }})</h4> <div class="config-header">
<h4>检测到的占位符 (合同ID: {{ currentContractId }})</h4>
<div class="header-buttons">
<!-- 如果没有Word文档显示上传按钮 -->
<button
v-if="!currentTemplateInfo?.contract_template"
@click="showReuploadSection = true"
class="btn-upload"
:disabled="reuploading">
{{ reuploading ? '上传中...' : '上传Word文档' }}
</button>
<!-- 如果有Word文档显示重新识别按钮 -->
<button
v-if="currentTemplateInfo?.contract_template"
@click="reidentifyPlaceholders"
class="btn-reidentify"
:disabled="reidentifying">
{{ reidentifying ? '识别中...' : '重新识别占位符' }}
</button>
<!-- 如果有Word文档也提供重新上传选项 -->
<button
v-if="currentTemplateInfo?.contract_template"
@click="showReuploadSection = !showReuploadSection"
class="btn-secondary"
:disabled="reuploading">
{{ showReuploadSection ? '取消上传' : '更换文档' }}
</button>
</div>
</div>
<!-- 重新上传Word文档区域 -->
<div v-if="showReuploadSection" class="reupload-section">
<h5>重新上传Word文档</h5>
<div class="reupload-form">
<div class="form-item">
<label>Word文档 <span style="color: red;">*</span></label>
<div class="file-upload-area">
<input
type="file"
accept=".docx,.doc"
@change="handleReuploadFileSelect"
class="file-input"
ref="reuploadFileInput"
/>
<div class="upload-tip">支持 .docx .doc 格式文件文件大小不超过 10MB</div>
<div v-if="reuploadFileName" class="file-info">
<span>📄 {{ reuploadFileName }}</span>
<button type="button" @click="clearReuploadFile" class="clear-file-btn">×</button>
</div>
</div>
</div>
<div class="reupload-actions">
<button @click="submitReupload" class="btn-primary" :disabled="reuploading || !reuploadFile">
{{ reuploading ? '上传中...' : '确定上传' }}
</button>
<button @click="showReuploadSection = false" class="btn-cancel">取消</button>
</div>
</div>
</div>
<div class="config-table-wrapper"> <div class="config-table-wrapper">
<table class="config-table"> <table class="config-table">
<thead> <thead>
@ -412,8 +470,17 @@ const currentContract = ref<ContractTemplate | null>(null)
const uploading = ref(false) const uploading = ref(false)
const configLoading = ref(false) const configLoading = ref(false)
const configList = ref<any[]>([]) const configList = ref<any[]>([])
const reidentifying = ref(false)
const fileInputKey = ref(0) const fileInputKey = ref(0)
const fileInput = ref<HTMLInputElement>() const fileInput = ref<HTMLInputElement>()
// Word
const showReuploadSection = ref(false)
const reuploadFile = ref<File | null>(null)
const reuploadFileName = ref('')
const reuploading = ref(false)
const reuploadFileInput = ref<HTMLInputElement>()
const currentTemplateInfo = ref<any>(null)
const staffList = ref<any[]>([]) const staffList = ref<any[]>([])
const filteredStaffList = ref<any[]>([]) const filteredStaffList = ref<any[]>([])
const staffLoading = ref(false) const staffLoading = ref(false)
@ -519,7 +586,10 @@ const updateStatus = async (row: ContractTemplate) => {
const configPlaceholder = async (row: ContractTemplate) => { const configPlaceholder = async (row: ContractTemplate) => {
currentContractId.value = row.id currentContractId.value = row.id
currentTemplateInfo.value = row //
showConfigDialog.value = true showConfigDialog.value = true
showReuploadSection.value = false //
clearReuploadFile() //
// //
await loadPlaceholderConfig(row.id) await loadPlaceholderConfig(row.id)
} }
@ -665,12 +735,66 @@ const handleUploadSuccess = () => {
getList() getList()
} }
//
const reidentifyPlaceholders = async () => {
if (!currentContractId.value) {
ElMessage.error('未找到合同ID')
return
}
reidentifying.value = true
try {
console.log('🔍 开始重新识别占位符, 合同ID:', currentContractId.value)
//
const { data } = await contractTemplateApi.reidentifyPlaceholders(currentContractId.value)
console.log('✅ 重新识别成功, 返回数据:', data)
//
const { placeholders, placeholder_count, new_placeholders, removed_placeholders } = data
let message = `成功识别到 ${placeholder_count} 个占位符`
if (new_placeholders && new_placeholders.length > 0) {
message += `,新增 ${new_placeholders.length}`
}
if (removed_placeholders && removed_placeholders.length > 0) {
message += `,移除 ${removed_placeholders.length}`
}
ElMessage.success(message)
//
await loadPlaceholderConfig(currentContractId.value)
} catch (error) {
console.error('❌ 重新识别失败:', error)
//
let errorMessage = error.message || '未知错误'
if (errorMessage.includes('模板未上传Word文档')) {
ElMessage.error('请先上传Word文档模板后再进行占位符识别')
} else if (errorMessage.includes('模板文件不存在')) {
ElMessage.error('模板文件不存在,请重新上传')
} else {
ElMessage.error(`重新识别失败: ${errorMessage}`)
}
} finally {
reidentifying.value = false
}
}
// //
const loadPlaceholderConfig = async (contractId: number) => { const loadPlaceholderConfig = async (contractId: number) => {
configLoading.value = true configLoading.value = true
try { try {
const { data } = await contractTemplateApi.getPlaceholderConfig(contractId) const { data } = await contractTemplateApi.getPlaceholderConfig(contractId)
console.log('API返回数据:', data) console.log('API返回数据:', data)
//
if (data && typeof data === 'object') {
currentTemplateInfo.value = data
}
// API // API
if (data && typeof data === 'object') { if (data && typeof data === 'object') {
@ -995,6 +1119,100 @@ const clearFile = () => {
// fileInputKey.value += 1 // // fileInputKey.value += 1 //
} }
//
const handleReuploadFileSelect = (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
console.log('📁 重新上传文件选择:', file)
if (!file) {
reuploadFile.value = null
reuploadFileName.value = ''
return
}
//
const fileName = file.name.toLowerCase()
const allowedExtensions = ['.docx', '.doc']
const isValidType = allowedExtensions.some(ext => fileName.endsWith(ext))
if (!isValidType) {
ElMessage.error('只支持上传 .docx 和 .doc 格式的文件!')
clearReuploadFile()
return
}
// (10MB)
if (file.size > 10 * 1024 * 1024) {
ElMessage.error('文件大小不能超过 10MB!')
clearReuploadFile()
return
}
//
reuploadFile.value = file
reuploadFileName.value = file.name
console.log('✅ 重新上传文件选择成功:', { name: file.name, size: file.size })
}
//
const clearReuploadFile = () => {
reuploadFile.value = null
reuploadFileName.value = ''
if (reuploadFileInput.value) {
reuploadFileInput.value.value = ''
}
}
//
const submitReupload = async () => {
if (!reuploadFile.value) {
ElMessage.error('请选择Word文档')
return
}
if (!currentContractId.value) {
ElMessage.error('未找到模板ID')
return
}
reuploading.value = true
try {
console.log('🚀 开始重新上传Word文档...', {
templateId: currentContractId.value,
fileName: reuploadFile.value.name,
fileSize: reuploadFile.value.size
})
// FormData
const formData = new FormData()
formData.append('file', reuploadFile.value)
// API
await contractTemplateApi.updateTemplateFile(currentContractId.value, formData)
console.log('✅ 重新上传成功')
ElMessage.success('Word文档更新成功')
//
showReuploadSection.value = false
clearReuploadFile()
//
await loadPlaceholderConfig(currentContractId.value)
//
getList()
} catch (error) {
console.error('❌ 重新上传失败:', error)
ElMessage.error(`更新文档失败: ${error.message || '未知错误'}`)
} finally {
reuploading.value = false
}
}
// //
const submitUpload = async () => { const submitUpload = async () => {
console.log('🚀 开始上传流程...') console.log('🚀 开始上传流程...')
@ -1187,6 +1405,114 @@ onMounted(() => {
margin-bottom: 15px; margin-bottom: 15px;
} }
.config-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.config-header h4 {
margin: 0;
color: #303133;
}
.header-buttons {
display: flex;
gap: 8px;
align-items: center;
}
.btn-reidentify {
padding: 6px 12px;
border: 1px solid #67c23a;
background: #67c23a;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
}
.btn-reidentify:hover:not(:disabled) {
background: #85ce61;
border-color: #85ce61;
}
.btn-reidentify:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-upload {
padding: 6px 12px;
border: 1px solid #409eff;
background: #409eff;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
}
.btn-upload:hover:not(:disabled) {
background: #66b1ff;
border-color: #66b1ff;
}
.btn-upload:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-secondary {
padding: 6px 12px;
border: 1px solid #909399;
background: #909399;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
}
.btn-secondary:hover:not(:disabled) {
background: #a6a9ad;
border-color: #a6a9ad;
}
.btn-secondary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.reupload-section {
margin: 20px 0;
padding: 15px;
background: #f9f9f9;
border: 1px solid #e4e7ed;
border-radius: 4px;
}
.reupload-section h5 {
margin: 0 0 15px 0;
color: #303133;
font-size: 14px;
font-weight: 500;
}
.reupload-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.reupload-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.config-table { .config-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;

36
niucloud/app/adminapi/controller/document/DocumentTemplate.php

@ -309,4 +309,40 @@ class DocumentTemplate extends BaseAdminController
return fail($e->getMessage()); return fail($e->getMessage());
} }
} }
/**
* 重新识别占位符
* @param int $id
* @return \think\Response
*/
public function reidentify(int $id)
{
try {
$result = (new DocumentTemplateService())->reidentifyPlaceholders($id);
return success('重新识别成功', $result);
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
/**
* 更新模板Word文档
* @param int $id
* @return \think\Response
*/
public function updateFile(int $id)
{
try {
// 获取上传的文件
$file = Request::file('file');
if (!$file) {
return fail('请选择要上传的Word文档');
}
$result = (new DocumentTemplateService())->updateTemplateFile($id, $file);
return success('文档更新成功', $result);
} catch (\Exception $e) {
return fail($e->getMessage());
}
}
} }

2
niucloud/app/adminapi/route/document_template.php

@ -34,6 +34,8 @@ Route::group('document_template', function () {
Route::post('config/save', 'document.DocumentTemplate/savePlaceholderConfig'); Route::post('config/save', 'document.DocumentTemplate/savePlaceholderConfig');
Route::post('config/datasource/save', 'document.DocumentTemplate/saveDataSourceConfig'); Route::post('config/datasource/save', 'document.DocumentTemplate/saveDataSourceConfig');
Route::get('datasources', 'document.DocumentTemplate/getDataSources'); Route::get('datasources', 'document.DocumentTemplate/getDataSources');
Route::post('reidentify/:id', 'document.DocumentTemplate/reidentify');
Route::post('update_file/:id', 'document.DocumentTemplate/updateFile');
// 文档生成 // 文档生成
Route::post('generate', 'document.DocumentTemplate/generateDocument'); Route::post('generate', 'document.DocumentTemplate/generateDocument');

1
niucloud/app/api/controller/apiController/Course.php

@ -104,6 +104,7 @@ class Course extends BaseApiService
public function addSchedule(Request $request){ public function addSchedule(Request $request){
$data = $this->request->params([ $data = $this->request->params([
["resources_id",''], ["resources_id",''],
["student_id",''],
["person_type",''], ["person_type",''],
["schedule_id",''], ["schedule_id",''],
["course_date",''], ["course_date",''],

2
niucloud/app/api/controller/apiController/PersonCourseSchedule.php

@ -160,6 +160,7 @@ class PersonCourseSchedule extends BaseApiService
public function getStudentCourseInfo(Request $request){ public function getStudentCourseInfo(Request $request){
$resource_id = $request->param('resource_id', '');//客户资源ID $resource_id = $request->param('resource_id', '');//客户资源ID
$member_id = $request->param('member_id', '');//会员ID $member_id = $request->param('member_id', '');//会员ID
$student_id = $request->param('student_id', '');//学生ID
if (empty($resource_id)) { if (empty($resource_id)) {
return fail('缺少参数resource_id'); return fail('缺少参数resource_id');
@ -168,6 +169,7 @@ class PersonCourseSchedule extends BaseApiService
$where = [ $where = [
'resource_id' => $resource_id, 'resource_id' => $resource_id,
'member_id' => $member_id, 'member_id' => $member_id,
'student_id' => $student_id,
]; ];
$res = (new PersonCourseScheduleService())->getStudentCourseInfo($where); $res = (new PersonCourseScheduleService())->getStudentCourseInfo($where);

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

@ -99,7 +99,7 @@ Route::group('contract', function () {
Route::get('download/:contract_id', 'app\api\controller\student\ContractController@downloadContract'); Route::get('download/:contract_id', 'app\api\controller\student\ContractController@downloadContract');
// 获取学员基本信息 // 获取学员基本信息
Route::get('student-info', 'app\api\controller\student\ContractController@getStudentInfo'); Route::get('student-info', 'app\api\controller\student\ContractController@getStudentInfo');
}); })->middleware(['ApiCheckToken']);
// 知识库(测试版本,无需token) // 知识库(测试版本,无需token)
Route::group('knowledge-test', function () { Route::group('knowledge-test', function () {

2
niucloud/app/model/document/DocumentDataSourceConfig.php

@ -11,7 +11,7 @@ use app\model\contract\Contract;
class DocumentDataSourceConfig extends BaseModel class DocumentDataSourceConfig extends BaseModel
{ {
protected $pk = 'id'; protected $pk = 'id';
protected $name = 'school_document_data_source_config'; protected $name = 'document_data_source_config';
/** /**
* 关联合同表 * 关联合同表

3
niucloud/app/service/admin/course_schedule/CourseScheduleService.php

@ -328,8 +328,10 @@ class CourseScheduleService extends BaseAdminService
public function addSchedule(array $data){ public function addSchedule(array $data){
$CourseSchedule = new CourseSchedule(); $CourseSchedule = new CourseSchedule();
$personCourseSchedule = new PersonCourseSchedule(); $personCourseSchedule = new PersonCourseSchedule();
dd($data);
if($personCourseSchedule->where([ if($personCourseSchedule->where([
'resources_id' => $data['resources_id'], 'resources_id' => $data['resources_id'],
'student_id' => $data['student_id'],
'schedule_id' => $data['schedule_id'] 'schedule_id' => $data['schedule_id']
])->find()){ ])->find()){
return fail("重复添加"); return fail("重复添加");
@ -337,6 +339,7 @@ class CourseScheduleService extends BaseAdminService
$personCourseSchedule->insert([ $personCourseSchedule->insert([
'resources_id' => $data['resources_id'], 'resources_id' => $data['resources_id'],
'student_id' => $data['student_id'],
'person_id' => 1, 'person_id' => 1,
'person_type' => $data['person_type'], 'person_type' => $data['person_type'],
'schedule_id' => $data['schedule_id'], 'schedule_id' => $data['schedule_id'],

220
niucloud/app/service/admin/document/DocumentTemplateService.php

@ -454,7 +454,6 @@ class DocumentTemplateService extends BaseAdminService
'table_name' => $settings['table_name'] ?? '', 'table_name' => $settings['table_name'] ?? '',
'field_name' => $settings['field_name'] ?? '', 'field_name' => $settings['field_name'] ?? '',
'system_function' => $settings['system_function'] ?? '', 'system_function' => $settings['system_function'] ?? '',
'user_input_value' => $settings['user_input_value'] ?? '',
'field_type' => $settings['field_type'] ?? 'text', 'field_type' => $settings['field_type'] ?? 'text',
'is_required' => $settings['is_required'] ?? 0, 'is_required' => $settings['is_required'] ?? 0,
'default_value' => $settings['default_value'] ?? '', 'default_value' => $settings['default_value'] ?? '',
@ -961,4 +960,223 @@ class DocumentTemplateService extends BaseAdminService
return $template->save(); return $template->save();
} }
/**
* 重新识别占位符
* @param int $id 模板ID
* @return array
* @throws \Exception
*/
public function reidentifyPlaceholders(int $id)
{
$template = $this->contractModel->find($id);
if (!$template) {
throw new \Exception('模板不存在');
}
// 检查模板文件路径是否存在
if (empty($template['contract_template'])) {
throw new \Exception('模板未上传Word文档,无法识别占位符');
}
// 检查模板文件是否存在
$templatePath = public_path() . '/upload/' . $template['contract_template'];
if (!file_exists($templatePath)) {
throw new \Exception('模板文件不存在:' . $template['contract_template']);
}
// 检查是否为文件而不是目录
if (is_dir($templatePath)) {
throw new \Exception('模板路径指向的是目录而不是文件:' . $template['contract_template']);
}
try {
// 重新解析Word文档内容和占位符
$parseResult = $this->parseWordTemplate($templatePath);
// 更新数据库中的占位符列表
$template->placeholders = json_encode($parseResult['placeholders']);
$template->contract_content = $parseResult['content'];
$template->updated_at = date('Y-m-d H:i:s');
$template->save();
// 获取现有的占位符配置
$existingConfig = [];
if ($template['placeholder_config']) {
$existingConfig = json_decode($template['placeholder_config'], true) ?: [];
}
// 为新的占位符创建默认配置,保留现有配置
$newConfig = [];
foreach ($parseResult['placeholders'] as $placeholder) {
if (isset($existingConfig[$placeholder])) {
// 保留现有配置
$newConfig[$placeholder] = $existingConfig[$placeholder];
} else {
// 为新占位符创建默认配置
$newConfig[$placeholder] = [
'data_type' => 'user_input',
'table_name' => '',
'field_name' => '',
'system_function' => '',
'user_input_value' => '',
'field_type' => 'text',
'is_required' => 0,
'default_value' => ''
];
}
}
// 更新占位符配置
if (!empty($newConfig)) {
$template->placeholder_config = json_encode($newConfig);
$template->save();
// 同步更新数据源配置表
$this->saveConfigToDataSourceTable($id, $newConfig);
}
return [
'placeholders' => $parseResult['placeholders'],
'placeholder_count' => count($parseResult['placeholders']),
'new_placeholders' => array_diff($parseResult['placeholders'], array_keys($existingConfig)),
'removed_placeholders' => array_diff(array_keys($existingConfig), $parseResult['placeholders'])
];
} catch (\Exception $e) {
Log::error('重新识别占位符失败:' . $e->getMessage());
throw new \Exception('重新识别占位符失败:' . $e->getMessage());
}
}
/**
* 更新模板Word文档
* @param int $id 模板ID
* @param $file 上传的文件
* @return array
* @throws \Exception
*/
public function updateTemplateFile(int $id, $file)
{
$template = $this->contractModel->find($id);
if (!$template) {
throw new \Exception('模板不存在');
}
// 验证文件类型
$allowedTypes = ['docx', 'doc'];
$extension = strtolower($file->getOriginalExtension());
if (!in_array($extension, $allowedTypes)) {
throw new \Exception('只支持 .docx 和 .doc 格式的Word文档');
}
// 获取文件信息
$fileSize = $file->getSize();
$realPath = $file->getRealPath();
// 验证文件大小 (ORM大10MB)
$maxSize = 10 * 1024 * 1024;
if ($fileSize > $maxSize) {
throw new \Exception('文件大小不能超过10MB');
}
// 生成文件hash防重复
$fileHash = md5_file($realPath);
try {
// 删除旧文件
if ($template['contract_template']) {
$oldFilePath = public_path() . '/upload/' . $template['contract_template'];
if (file_exists($oldFilePath) && is_file($oldFilePath)) {
unlink($oldFilePath);
}
}
// 生成保存路径
$uploadDir = 'contract_templates/' . date('Ymd');
$uploadPath = public_path() . '/upload/' . $uploadDir;
// 确保目录存在
if (!is_dir($uploadPath)) {
mkdir($uploadPath, 0777, true);
}
// 生成文件名
$fileName = md5(time() . $file->getOriginalName()) . '.' . $extension;
$fullPath = $uploadPath . '/' . $fileName;
$savePath = $uploadDir . '/' . $fileName;
// 移动文件到目标位置
if (!move_uploaded_file($realPath, $fullPath)) {
throw new \Exception('文件保存失败');
}
// 解析Word文档内容和占位符
$parseResult = $this->parseWordTemplate($fullPath);
// 更新数据库记录
$template->contract_template = $savePath;
$template->contract_content = $parseResult['content'];
$template->original_filename = $file->getOriginalName();
$template->file_size = $fileSize;
$template->file_hash = $fileHash;
$template->placeholders = json_encode($parseResult['placeholders']);
$template->updated_at = date('Y-m-d H:i:s');
$template->save();
// 获取现有的占位符配置
$existingConfig = [];
if ($template['placeholder_config']) {
$existingConfig = json_decode($template['placeholder_config'], true) ?: [];
}
// 为新的占位符创建默认配置,保留现有配置
$newConfig = [];
foreach ($parseResult['placeholders'] as $placeholder) {
if (isset($existingConfig[$placeholder])) {
// 保留现有配置
$newConfig[$placeholder] = $existingConfig[$placeholder];
} else {
// 为新占位符创建默认配置
$newConfig[$placeholder] = [
'data_type' => 'user_input',
'table_name' => '',
'field_name' => '',
'system_function' => '',
'user_input_value' => '',
'field_type' => 'text',
'is_required' => 0,
'default_value' => ''
];
}
}
// 更新占位符配置
if (!empty($newConfig)) {
$template->placeholder_config = json_encode($newConfig);
$template->save();
// 同步更新数据源配置表
$this->saveConfigToDataSourceTable($id, $newConfig);
}
return [
'id' => $template->id,
'template_name' => $template->contract_name,
'file_path' => $savePath,
'placeholders' => $parseResult['placeholders'],
'placeholder_count' => count($parseResult['placeholders']),
'new_placeholders' => array_diff($parseResult['placeholders'], array_keys($existingConfig)),
'removed_placeholders' => array_diff(array_keys($existingConfig), $parseResult['placeholders'])
];
} catch (\Exception $e) {
// 如果保存失败,删除已上传的文件
if (isset($fullPath) && file_exists($fullPath)) {
unlink($fullPath);
}
throw new \Exception('模板文档更新失败:' . $e->getMessage());
}
}
} }

102
niucloud/app/service/api/apiService/CourseService.php

@ -431,82 +431,55 @@ class CourseService extends BaseApiService
public function addSchedule(array $data){ public function addSchedule(array $data){
$CourseSchedule = new CourseSchedule(); $CourseSchedule = new CourseSchedule();
$personCourseSchedule = new PersonCourseSchedule(); $personCourseSchedule = new PersonCourseSchedule();
$student = Student::where('id', $data['student_id'])->find();
// 根据person_type确定正确的student_id和resources_id if (!$student) {
$student_id = 0; return fail("学员不存在");
$resources_id = 0;
if ($data['person_type'] == 'student') {
// 如果是学员类型,从传入的数据中获取student_id,然后查询对应的resources_id
$student_id = $data['resources_id']; // 前端传来的是student.id
// 通过student表查询对应的user_id(即customer_resources.id)
$Student = new Student();
$student = $Student->where('id', $student_id)->find();
if (!$student) {
return fail("学员不存在");
}
$resources_id = $student['user_id']; // student.user_id = customer_resources.id
} else if ($data['person_type'] == 'customer_resource') {
// 如果是客户资源类型,直接使用传入的resources_id
$resources_id = $data['resources_id'];
// 验证客户资源是否存在
$customerResource = Db::name('customer_resources')
->where('id', $resources_id)
->find();
if (!$customerResource) {
return fail("客户资源不存在");
}
// 通过customer_resources.id查找对应的学生记录
// school_student.user_id = school_customer_resources.id
$Student = new Student();
$student = $Student->where('user_id', $resources_id)->find();
if (!$student) {
return fail("该客户资源没有关联的学生记录");
}
$student_id = $student['id'];
} else {
return fail("无效的人员类型");
} }
// 检查重复添加 - 根据person_type使用不同的检查逻辑 $studentCourse = StudentCourses::where('student_id', $student->id)
$checkWhere = ['schedule_id' => $data['schedule_id']]; ->order('id', 'desc')
if ($data['person_type'] == 'student') { ->find();
$checkWhere['student_id'] = $student_id; if ($studentCourse){
} else { $person_type = 'student';
$checkWhere['resources_id'] = $resources_id; }else{
$person_type = 'customer_resource';
} }
if($personCourseSchedule->where($checkWhere)->find()){ // 检查重复添加 - 根据person_type使用不同的检查逻辑
return fail("重复添加"); $checkWhere = [
'schedule_id' => $data['schedule_id'],
'student_id'=>$student->id,
'resources_id'=>$data['resources_id'],
];
$course = $personCourseSchedule->where($checkWhere)->find();
if($course){
if ($course->schedule_type == 2 && $course->course_type == 1) {
$course->schedule_type == 1;
$course->save();
}else{
return fail("重复添加");
}
} }
// 调试:插入前记录变量值
error_log("Debug: Before insert - student_id = " . $student_id . ", resources_id = " . $resources_id);
$insertData = [ $insertData = [
'student_id' => $student_id, // 正确设置student_id 'student_id' => $student->id, // 正确设置student_id
'resources_id' => $resources_id, // 正确设置resources_id 'resources_id' => $data['resources_id'], // 正确设置resources_id
'person_id' => $this->member_id, 'person_id' => $this->member_id,
'person_type' => $data['person_type'], 'person_type' => $person_type,
'schedule_id' => $data['schedule_id'], 'schedule_id' => $data['schedule_id'],
'course_date' => $data['course_date'], 'course_date' => $data['course_date'],
'time_slot' => $data['time_slot'], 'time_slot' => $data['time_slot'],
'schedule_type' => $data['schedule_type'] ?? 1, // 1=正式位, 2=等待位 'schedule_type' => $data['schedule_type'] ?? 1, // 1=正式位, 2=等待位
'course_type' => $data['course_type'] ?? 1, // 1=正式课, 2=体验课, 3=等待位 'course_type' => empty($course) ? 2 : 1, // 1=正式学员, 2=体验课学员
'remark' => $data['remark'] ?? '' // 备注 'remark' => $data['remark'] ?? '' // 备注
]; ];
error_log("Debug: Insert data = " . json_encode($insertData));
$personCourseSchedule->insert($insertData); $personCourseSchedule->insert($insertData);
$CourseSchedule->where(['id' => $data['schedule_id']])->dec("available_capacity")->update(); $student_ids = $personCourseSchedule->where(['schedule_id' => $data['schedule_id']])->column('student_id');
$CourseSchedule->where(['id' => $data['schedule_id']])->update([
'student_ids'=>$student_ids,
'available_capacity'=>count($student_ids)
]);
return success("添加成功"); return success("添加成功");
} }
@ -543,7 +516,8 @@ class CourseService extends BaseApiService
// 查询记录 // 查询记录
$record = $personCourseSchedule->where([ $record = $personCourseSchedule->where([
'schedule_id' => $data['id'], 'schedule_id' => $data['id'],
'resources_id' => $data['resources_id'] 'resources_id' => $data['resources_id'],
])->find(); ])->find();
if (!$record) { if (!$record) {

12
niucloud/app/service/api/apiService/PersonCourseScheduleService.php

@ -366,9 +366,15 @@ class PersonCourseScheduleService extends BaseApiService
'data' => [] 'data' => []
]; ];
// 直接通过resource_id查询学员课程表 // 构建查询条件
$studentCourses = StudentCourses::where('resource_id', $where['resource_id']) $query = StudentCourses::where('resource_id', $where['resource_id']);
->with([
// 如果传入了student_id参数,添加student_id条件
if (!empty($where['student_id'])) {
$query = $query->where('student_id', $where['student_id']);
}
$studentCourses = $query->with([
'course' => function($query) { 'course' => function($query) {
$query->field('id,course_name'); $query->field('id,course_name');
}, },

17
niucloud/app/service/api/apiService/StudentService.php

@ -95,18 +95,8 @@ class StudentService extends BaseApiService
try { try {
// 获取当前登录人员的ID // 获取当前登录人员的ID
$currentUserId = $this->getUserId(); $currentUserId = $this->getUserId();
if (empty($currentUserId)) {
$res['code'] = 1;
$res['data'] = [];
$res['msg'] = '获取成功';
return $res;
}
// 查询符合条件的学生ID集合 if (empty($currentUserId)) {
$studentIds = $this->getStudentIds($currentUserId, $data);
if (empty($studentIds)) {
$res['code'] = 1; $res['code'] = 1;
$res['data'] = []; $res['data'] = [];
$res['msg'] = '获取成功'; $res['msg'] = '获取成功';
@ -116,9 +106,12 @@ class StudentService extends BaseApiService
// 构建学员基础查询条件 // 构建学员基础查询条件
$where = []; $where = [];
$where[] = ['s.deleted_at', '=', 0]; $where[] = ['s.deleted_at', '=', 0];
$where[] = ['s.id', 'in', $studentIds];
// 支持搜索参数 // 支持搜索参数
if (!empty($data['parent_resource_id'])){
$where[] = ['s.user_id', '=', $data['parent_resource_id']];
}
if (!empty($data['name'])) { if (!empty($data['name'])) {
$where[] = ['s.name', 'like', '%' . $data['name'] . '%']; $where[] = ['s.name', 'like', '%' . $data['name'] . '%'];
} }

203
uniapp/App.vue

@ -8,6 +8,9 @@
var wxtoken = new WxToken(); var wxtoken = new WxToken();
export default { export default {
async onLaunch() { async onLaunch() {
//
this.checkForUpdate();
// #ifdef MP-WEIXIN // #ifdef MP-WEIXIN
uni.login({ uni.login({
provider: 'weixin', provider: 'weixin',
@ -36,8 +39,206 @@
//token - //token -
// #endif // #endif
}, },
onShow: function() {}, onShow: function() {
//
this.checkForUpdate();
},
onHide: function() {}, onHide: function() {},
methods: {
/**
* 检查小程序更新
*/
checkForUpdate() {
// #ifdef MP-WEIXIN
try {
const updateManager = uni.getUpdateManager();
//
updateManager.onCheckForUpdate((res) => {
console.log('检查更新结果:', res);
if (res.hasUpdate) {
console.log('发现新版本,准备下载');
uni.showToast({
title: '发现新版本',
icon: 'none',
duration: 2000
});
}
});
//
updateManager.onUpdateReady(() => {
console.log('新版本下载完成,准备重启应用');
this.showUpdateDialog();
});
//
updateManager.onUpdateFailed(() => {
console.log('新版本下载失败');
uni.showToast({
title: '更新失败,请检查网络',
icon: 'none',
duration: 2000
});
});
//
updateManager.checkForUpdate();
} catch (error) {
console.error('更新管理器初始化失败:', error);
}
// #endif
// #ifdef H5
// H5
this.checkH5Update();
// #endif
},
/**
* 显示更新对话框
*/
showUpdateDialog() {
uni.showModal({
title: '更新提示',
content: '新版本已下载完成,是否立即重启应用以更新到最新版本?',
confirmText: '立即重启',
cancelText: '稍后重启',
success: (res) => {
if (res.confirm) {
console.log('用户选择立即重启');
this.applyUpdate();
} else {
console.log('用户选择稍后重启');
//
this.scheduleDelayedUpdate();
}
}
});
},
/**
* 应用更新并重启
*/
applyUpdate() {
// #ifdef MP-WEIXIN
try {
const updateManager = uni.getUpdateManager();
updateManager.applyUpdate();
} catch (error) {
console.error('应用更新失败:', error);
uni.showToast({
title: '重启失败,请手动重启',
icon: 'none',
duration: 2000
});
}
// #endif
},
/**
* 安排延迟更新
*/
scheduleDelayedUpdate() {
// 5
setTimeout(() => {
console.log('自动应用更新');
this.applyUpdate();
}, 5 * 60 * 1000);
uni.showToast({
title: '将在5分钟后自动重启',
icon: 'none',
duration: 3000
});
},
/**
* H5环境下的更新检查可选功能
*/
checkH5Update() {
// #ifdef H5
try {
//
const currentVersion = uni.getStorageSync('app_version') || '1.0.0';
const buildTime = uni.getStorageSync('build_time') || Date.now();
const now = Date.now();
//
if (now - buildTime > 60 * 60 * 1000) {
console.log('H5环境检查更新');
// API
//
this.checkVersionFromServer();
}
} catch (error) {
console.error('H5更新检查失败:', error);
}
// #endif
},
/**
* 从服务器检查版本信息H5环境
*/
async checkVersionFromServer() {
try {
// API
// const response = await uni.request({
// url: Api_url.domain + '/api/app/version',
// method: 'GET'
// });
//
// if (response.data.code === 1) {
// const serverVersion = response.data.data.version;
// const currentVersion = uni.getStorageSync('app_version') || '1.0.0';
//
// if (this.compareVersion(serverVersion, currentVersion) > 0) {
// this.showH5UpdateDialog();
// }
// }
console.log('从服务器检查H5版本更新');
} catch (error) {
console.error('服务器版本检查失败:', error);
}
},
/**
* 显示H5更新对话框
*/
showH5UpdateDialog() {
uni.showModal({
title: '发现新版本',
content: '检测到新版本,建议刷新页面以获得最佳体验',
confirmText: '立即刷新',
cancelText: '稍后刷新',
success: (res) => {
if (res.confirm) {
location.reload();
}
}
});
},
/**
* 版本号比较工具
*/
compareVersion(version1, version2) {
const v1 = version1.split('.').map(Number);
const v2 = version2.split('.').map(Number);
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
const a = v1[i] || 0;
const b = v2[i] || 0;
if (a > b) return 1;
if (a < b) return -1;
}
return 0;
}
}
} }
</script> </script>

175
uniapp/components/fitness-record-popup/fitness-record-popup.vue

@ -227,80 +227,147 @@ export default {
// PDF // PDF
selectPDFFiles() { selectPDFFiles() {
uni.chooseFile({ // API
let chooseFileMethod = null;
// #ifdef MP-WEIXIN
// 使 chooseMessageFile
if (typeof uni.chooseMessageFile === 'function') {
chooseFileMethod = uni.chooseMessageFile;
} else if (typeof wx !== 'undefined' && typeof wx.chooseMessageFile === 'function') {
chooseFileMethod = wx.chooseMessageFile;
}
// #endif
// #ifndef MP-WEIXIN
// 使 chooseFile
if (typeof uni.chooseFile === 'function') {
chooseFileMethod = uni.chooseFile;
}
// #endif
//
if (!chooseFileMethod) {
uni.showModal({
title: '不支持文件选择',
content: '当前平台不支持文件选择功能,请手动输入文件信息或联系技术支持',
showCancel: false
});
return;
}
//
chooseFileMethod({
count: 5, count: 5,
type: 'file', type: 'file',
extension: ['pdf'], extension: ['pdf'],
success: async (res) => { success: async (res) => {
console.log('选择的文件:', res.tempFiles) console.log('选择的文件:', res.tempFiles)
await this.handleSelectedFiles(res.tempFiles)
},
fail: (err) => {
console.error('选择文件失败:', err)
//
let errorMsg = '选择文件失败';
if (err.errMsg) {
if (err.errMsg.includes('cancel')) {
errorMsg = '用户取消了文件选择';
} else if (err.errMsg.includes('limit')) {
errorMsg = '文件数量或大小超出限制';
} else {
errorMsg = '选择文件失败: ' + err.errMsg;
}
}
// uni.showToast({
uni.showLoading({ title: errorMsg,
title: '上传中...', icon: 'none',
mask: true duration: 3000
}) })
}
let successCount = 0 })
let totalCount = res.tempFiles.length },
for (let file of res.tempFiles) { //
if (file.type === 'application/pdf') { async handleSelectedFiles(tempFiles) {
try { //
// PDF使 uni.showLoading({
const uploadResult = await this.uploadPdfFile(file) title: '上传中...',
if (uploadResult && uploadResult.code === 1) { mask: true
const pdfFile = { })
id: Date.now() + Math.random(),
name: file.name, let successCount = 0
size: file.size, let totalCount = tempFiles.length
url: uploadResult.data.url, // 使url
server_path: uploadResult.data.url, // 访 for (let file of tempFiles) {
upload_time: new Date().toLocaleString(), //
ext: uploadResult.data.ext || 'pdf', const isValidPDF = this.isValidPDFFile(file)
original_name: uploadResult.data.name || file.name
} if (isValidPDF) {
this.recordData.pdf_files.push(pdfFile) try {
successCount++ // PDF使
} else { const uploadResult = await this.uploadPdfFile(file)
console.error('文件上传失败:', uploadResult) if (uploadResult && uploadResult.code === 1) {
uni.showToast({ const pdfFile = {
title: uploadResult.msg || '文件上传失败', id: Date.now() + Math.random(),
icon: 'none' name: file.name,
}) size: file.size,
} url: uploadResult.data.url, // 使url
} catch (error) { server_path: uploadResult.data.url, // 访
console.error('上传PDF文件失败:', error) upload_time: new Date().toLocaleString(),
uni.showToast({ ext: uploadResult.data.ext || 'pdf',
title: '文件上传失败: ' + (error.msg || error.message || '网络异常'), original_name: uploadResult.data.name || file.name
icon: 'none'
})
} }
this.recordData.pdf_files.push(pdfFile)
successCount++
} else { } else {
console.error('文件上传失败:', uploadResult)
uni.showToast({ uni.showToast({
title: '请选择PDF格式文件', title: uploadResult.msg || '文件上传失败',
icon: 'none' icon: 'none'
}) })
} }
} } catch (error) {
console.error('上传PDF文件失败:', error)
uni.hideLoading()
//
if (successCount > 0) {
uni.showToast({ uni.showToast({
title: `成功上传 ${successCount}/${totalCount} 个文件`, title: '文件上传失败: ' + (error.msg || error.message || '网络异常'),
icon: 'success' icon: 'none'
}) })
} }
}, } else {
fail: (err) => {
console.error('选择文件失败:', err)
uni.showToast({ uni.showToast({
title: '选择文件失败', title: '请选择PDF格式文件',
icon: 'none' icon: 'none'
}) })
} }
}) }
uni.hideLoading()
//
if (successCount > 0) {
uni.showToast({
title: `成功上传 ${successCount}/${totalCount} 个文件`,
icon: 'success'
})
}
},
// PDF
isValidPDFFile(file) {
// MIME
if (file.type && file.type === 'application/pdf') {
return true
}
//
const fileName = file.name || ''
const extension = fileName.toLowerCase().split('.').pop()
if (extension === 'pdf') {
return true
}
return false
}, },
// PDF使 // PDF使

13
uniapp/components/student-edit-popup/student-edit-popup.vue

@ -220,10 +220,21 @@ export default {
}, },
methods: { methods: {
// - // -
openAdd() { openAdd(clientData = null) {
this.isEditing = false this.isEditing = false
this.resetStudentData() this.resetStudentData()
this.studentData.user_id = this.resourceId this.studentData.user_id = this.resourceId
//
if (clientData) {
if (clientData.contact_phone) {
this.studentData.contact_phone = clientData.contact_phone
}
if (clientData.emergency_contact) {
this.studentData.emergency_contact = clientData.emergency_contact
}
}
this.$refs.popup.open() this.$refs.popup.open()
}, },

3
uniapp/pages-coach/coach/student/student_list.vue

@ -306,11 +306,13 @@ import apiRoute from '@/api/apiRoute.js';
background-color: #18181c; background-color: #18181c;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh;
} }
.safe-area { .safe-area {
padding-top: var(--status-bar-height); padding-top: var(--status-bar-height);
padding-bottom: 120rpx; padding-bottom: 120rpx;
flex: 1;
} }
.search-bar { .search-bar {
@ -335,6 +337,7 @@ import apiRoute from '@/api/apiRoute.js';
.content { .content {
padding: 20rpx; padding: 20rpx;
background-color: #18181c;
} }
.empty-box { .empty-box {

75
uniapp/pages-common/contract/contract_sign.vue

@ -112,7 +112,7 @@
</template> </template>
<script> <script>
import apiRoute from '@/common/axios.js' import apiRoute from '@/api/apiRoute.js'
import { uploadFile } from '@/common/util.js'; import { uploadFile } from '@/common/util.js';
export default { export default {
@ -173,47 +173,73 @@ export default {
this.canvas = uni.createCanvasContext('signatureCanvas', this) this.canvas = uni.createCanvasContext('signatureCanvas', this)
this.ctx = this.canvas this.ctx = this.canvas
// Canvas
this.ctx.scale(this.pixelRatio, this.pixelRatio)
// //
this.ctx.lineWidth = this.currentWidth this.ctx.lineWidth = this.currentWidth
this.ctx.strokeStyle = this.currentColor this.ctx.strokeStyle = this.currentColor
this.ctx.lineCap = 'round' this.ctx.lineCap = 'round'
this.ctx.lineJoin = 'round' this.ctx.lineJoin = 'round'
// Canvas // Canvas
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) this.ctx.fillStyle = '#ffffff'
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
this.ctx.draw() this.ctx.draw()
console.log('Canvas初始化完成:', {
width: this.canvasWidth,
height: this.canvasHeight,
pixelRatio: this.pixelRatio
})
} }
}).exec() }).exec()
}, },
// //
touchStart(e) { touchStart(e) {
if (!this.ctx) return if (!this.ctx) {
console.log('Canvas未初始化')
return
}
//
e.preventDefault && e.preventDefault()
this.isDrawing = true this.isDrawing = true
const touch = e.touches[0] const touch = e.touches[0] || e.changedTouches[0]
this.lastPoint = {
x: touch.x, // UniApp Canvas使
y: touch.y const x = touch.x || touch.clientX || 0
} const y = touch.y || touch.clientY || 0
this.lastPoint = { x, y }
console.log('开始绘制:', { x, y, touch })
//
this.ctx.lineWidth = this.currentWidth
this.ctx.strokeStyle = this.currentColor
this.ctx.lineCap = 'round'
this.ctx.lineJoin = 'round'
this.ctx.beginPath() this.ctx.beginPath()
this.ctx.moveTo(touch.x, touch.y) this.ctx.moveTo(x, y)
}, },
// //
touchMove(e) { touchMove(e) {
if (!this.ctx || !this.isDrawing) return if (!this.ctx || !this.isDrawing) return
const touch = e.touches[0] //
const currentPoint = { e.preventDefault && e.preventDefault()
x: touch.x,
y: touch.y const touch = e.touches[0] || e.changedTouches[0]
}
// UniApp Canvas使
const x = touch.x || touch.clientX || 0
const y = touch.y || touch.clientY || 0
const currentPoint = { x, y }
// 线
this.ctx.lineTo(currentPoint.x, currentPoint.y) this.ctx.lineTo(currentPoint.x, currentPoint.y)
this.ctx.stroke() this.ctx.stroke()
this.ctx.draw(true) this.ctx.draw(true)
@ -224,6 +250,9 @@ export default {
// //
touchEnd(e) { touchEnd(e) {
if (this.isDrawing) {
console.log('绘制结束,已签名状态:', this.hasSigned)
}
this.isDrawing = false this.isDrawing = false
this.lastPoint = null this.lastPoint = null
}, },
@ -247,10 +276,18 @@ export default {
// //
clearSignature() { clearSignature() {
if (this.ctx) { if (this.ctx) {
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) // Canvas
this.ctx.fillStyle = '#ffffff'
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight)
this.ctx.draw() this.ctx.draw()
//
this.hasSigned = false this.hasSigned = false
this.signatureImageUrl = '' this.signatureImageUrl = ''
this.isDrawing = false
this.lastPoint = null
console.log('签名已清除')
} }
}, },

15
uniapp/pages-market/clue/class_arrangement_detail.vue

@ -204,9 +204,9 @@
minHeight="200" :isAutoHeight="true"></fui-textarea> minHeight="200" :isAutoHeight="true"></fui-textarea>
</view> </view>
<view class="leave-buttons"> <view class="leave-buttons">
<fui-button background="#434544" color="#fff" borderColor="#666" btnSize="medium" <fui-button background="#434544" color="#fff" borderColor="#666"
@tap="$refs.leaveReasonModal.close()">取消</fui-button> @tap="$refs.leaveReasonModal.close()">取消</fui-button>
<fui-button background="#29d3b4" color="#fff" btnSize="medium" <fui-button background="#29d3b4" color="#fff"
@tap="submitLeaveRequest">提交</fui-button> @tap="submitLeaveRequest">提交</fui-button>
</view> </view>
</view> </view>
@ -852,6 +852,7 @@
const params = { const params = {
resources_id: this.currentStudent.resources_id, resources_id: this.currentStudent.resources_id,
id: this.schedule_info.id, id: this.schedule_info.id,
student_id: this.currentStudent.student_id,
remark: this.leaveReason remark: this.leaveReason
}; };
@ -860,10 +861,10 @@
.then(res => { .then(res => {
uni.hideLoading(); uni.hideLoading();
if (res.code === 1) { if (res.code === 1) {
uni.showToast({ uni.showModal({
title: '请假申请提交成功', content: '请假申请提交成功',
icon: 'success' showCancel: false,
}); })
this.$refs.leaveReasonModal.close(); this.$refs.leaveReasonModal.close();
this.leaveReason = ''; // this.leaveReason = ''; //
@ -1358,6 +1359,7 @@
/* 请假弹窗样式 */ /* 请假弹窗样式 */
.leave-form { .leave-form {
padding: 20rpx; padding: 20rpx;
width: 100%;
.leave-label { .leave-label {
font-size: 28rpx; font-size: 28rpx;
@ -1373,7 +1375,6 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 20rpx; gap: 20rpx;
.fui-button { .fui-button {
flex: 1; flex: 1;
} }

7
uniapp/pages-market/clue/clue_info.vue

@ -995,7 +995,12 @@ export default {
// //
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.studentEditPopup) { if (this.$refs.studentEditPopup) {
this.$refs.studentEditPopup.openAdd() //
const clientData = {
contact_phone: this.clientInfo?.customerResource?.phone_number || '',
emergency_contact: this.clientInfo?.customerResource?.name || ''
}
this.$refs.studentEditPopup.openAdd(clientData)
} else { } else {
console.error('studentEditPopup 组件引用不存在') console.error('studentEditPopup 组件引用不存在')
uni.showToast({ uni.showToast({

30
uniapp/pages-student/contracts/index.vue

@ -105,34 +105,30 @@
</view> </view>
</view> </view>
<view class="contract_actions"> <view class="contract_actions" @click.stop>
<fui-button <view
v-if="contract.status === 3" v-if="contract.status === 3"
background="transparent" class="action_button secondary_button"
color="#29d3b4" @click="handleViewDetail(index, $event)"
size="small"
@click.stop="viewContractDetail(contract)"
> >
查看详情 查看详情
</fui-button> </view>
<fui-button <view
v-if="contract.status === 3 && contract.can_renew" v-if="contract.status === 3 && contract.can_renew"
background="#29d3b4" class="action_button primary_button"
size="small" @click="handleRenewContract(index, $event)"
@click.stop="renewContract(contract)"
> >
续约 续约
</fui-button> </view>
<fui-button <view
v-if="contract.status === 1" v-if="contract.status === 1"
background="#f39c12" class="action_button sign_button"
size="small" @click="handleSignContract(index, $event)"
@click.stop="signContract(contract)"
> >
签署合同 签署合同
</fui-button> </view>
</view> </view>
</view> </view>
</view> </view>

3
uniapp/pages.json

@ -390,7 +390,8 @@
"style": { "style": {
"navigationBarTitleText": "我的学员", "navigationBarTitleText": "我的学员",
"navigationBarBackgroundColor": "#29d3b4", "navigationBarBackgroundColor": "#29d3b4",
"navigationBarTextStyle": "white" "navigationBarTextStyle": "white",
"backgroundColor": "#18181c"
} }
}, },
{ {

223
uniapp/pages/student/contracts/index.vue

@ -48,10 +48,10 @@
<view v-else class="contracts_list"> <view v-else class="contracts_list">
<view <view
v-for="contract in filteredContracts" v-for="(contract, index) in filteredContracts"
:key="contract.id" :key="contract.id"
class="contract_item" class="contract_item"
@click="viewContractDetail(contract)" @click="handleViewContractDetail(contract)"
> >
<view class="contract_header"> <view class="contract_header">
<view class="contract_info"> <view class="contract_info">
@ -109,34 +109,30 @@
</view> </view>
</view> </view>
<view class="contract_actions"> <view class="contract_actions" @click.stop>
<fui-button <view
v-if="contract.status === 'active'" v-if="contract.status === 3"
background="transparent" class="action_button secondary_button"
color="#29d3b4" @click="handleViewDetail(index, $event)"
size="small"
@click.stop="viewContractDetail(contract)"
> >
查看详情 查看详情
</fui-button> </view>
<fui-button <view
v-if="contract.status === 'active' && contract.can_renew" v-if="contract.status === 3 && contract.can_renew"
background="#29d3b4" class="action_button primary_button"
size="small" @click="handleRenewContract(index, $event)"
@click.stop="renewContract(contract)"
> >
续约 续约
</fui-button> </view>
<fui-button <view
v-if="contract.status === 'pending'" v-if="contract.status === 1"
background="#f39c12" class="action_button sign_button"
size="small" @click="handleSignContract(index, $event)"
@click.stop="signContract(contract)"
> >
签署合同 签署合同
</fui-button> </view>
</view> </view>
</view> </view>
</view> </view>
@ -144,14 +140,13 @@
<!-- 加载更多 --> <!-- 加载更多 -->
<view class="load_more_section" v-if="!loading && hasMore"> <view class="load_more_section" v-if="!loading && hasMore">
<fui-button <view
background="transparent" class="load_more_button"
color="#666" @click="loadMoreContracts($event)"
@click="loadMoreContracts" :class="{ 'loading': loadingMore }"
:loading="loadingMore"
> >
{{ loadingMore ? '加载中...' : '加载更多' }} {{ loadingMore ? '加载中...' : '加载更多' }}
</fui-button> </view>
</view> </view>
<!-- 合同详情弹窗 --> <!-- 合同详情弹窗 -->
@ -218,21 +213,20 @@
</view> </view>
<view class="popup_actions"> <view class="popup_actions">
<fui-button <view
v-if="selectedContract && selectedContract.contract_file_url" v-if="selectedContract && selectedContract.contract_file_url"
background="#3498db" class="popup_button primary_popup_button"
@click="downloadContract" @click="downloadContract"
> >
下载合同 下载合同
</fui-button> </view>
<fui-button <view
background="#f8f9fa" class="popup_button secondary_popup_button"
color="#666"
@click="closeContractPopup" @click="closeContractPopup"
> >
关闭 关闭
</fui-button> </view>
</view> </view>
</view> </view>
</view> </view>
@ -412,7 +406,11 @@
} }
}, },
async loadMoreContracts() { async loadMoreContracts(e) {
//
if (e && typeof e.stopPropagation === 'function') {
e.stopPropagation()
}
if (this.loadingMore || !this.hasMore) return if (this.loadingMore || !this.hasMore) return
this.loadingMore = true this.loadingMore = true
@ -480,12 +478,12 @@
return `${year}-${month}-${day}` return `${year}-${month}-${day}`
}, },
viewContractDetail(contract) {
this.selectedContract = contract
this.showContractPopup = true
},
closeContractPopup() { closeContractPopup(e) {
//
if (e && typeof e.stopPropagation === 'function') {
e.stopPropagation()
}
this.showContractPopup = false this.showContractPopup = false
this.selectedContract = null this.selectedContract = null
}, },
@ -572,7 +570,11 @@
}) })
}, },
downloadContract() { downloadContract(e) {
//
if (e && typeof e.stopPropagation === 'function') {
e.stopPropagation()
}
if (!this.selectedContract || !this.selectedContract.contract_file_url) { if (!this.selectedContract || !this.selectedContract.contract_file_url) {
uni.showToast({ uni.showToast({
title: '合同文件不存在', title: '合同文件不存在',
@ -586,7 +588,56 @@
content: '合同下载功能开发中', content: '合同下载功能开发中',
showCancel: false showCancel: false
}) })
} },
//
handleViewDetail(index, event) {
//
if (event && typeof event.stopPropagation === 'function') {
event.stopPropagation()
}
const contract = this.filteredContracts[index]
if (contract) {
this.selectedContract = contract
this.showContractPopup = true
}
},
//
handleViewContractDetail(contract) {
this.selectedContract = contract
this.showContractPopup = true
},
//
handleRenewContract(index, event) {
//
if (event && typeof event.stopPropagation === 'function') {
event.stopPropagation()
}
const contract = this.filteredContracts[index]
if (contract) {
this.renewContract(contract)
}
},
//
handleSignContract(index, event) {
//
if (event && typeof event.stopPropagation === 'function') {
event.stopPropagation()
}
console.log('handleSignContract called with index:', index)
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)}`
})
}
},
} }
} }
</script> </script>
@ -874,6 +925,44 @@
display: flex; display: flex;
gap: 16rpx; gap: 16rpx;
justify-content: flex-end; justify-content: flex-end;
.action_button {
padding: 16rpx 32rpx;
border-radius: 8rpx;
font-size: 24rpx;
text-align: center;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
border: none;
&.secondary_button {
background: #f8f9fa;
color: #666;
&:active {
background: #e9ecef;
}
}
&.primary_button {
background: #29D3B4;
color: #fff;
&:active {
background: #26c6a0;
}
}
&.sign_button {
background: #ff6b35;
color: #fff;
&:active {
background: #e55a2b;
}
}
}
} }
} }
} }
@ -882,6 +971,27 @@
// //
.load_more_section { .load_more_section {
padding: 40rpx 20rpx 80rpx; padding: 40rpx 20rpx 80rpx;
.load_more_button {
padding: 24rpx 32rpx;
background: transparent;
color: #666;
font-size: 28rpx;
text-align: center;
border: 1rpx solid #e0e0e0;
border-radius: 8rpx;
cursor: pointer;
transition: all 0.3s ease;
&:active {
background: #f5f5f5;
}
&.loading {
color: #999;
cursor: not-allowed;
}
}
} }
// //
@ -979,8 +1089,33 @@
gap: 16rpx; gap: 16rpx;
border-top: 1px solid #f0f0f0; border-top: 1px solid #f0f0f0;
fui-button { .popup_button {
flex: 1; flex: 1;
padding: 16rpx 24rpx;
border-radius: 8rpx;
font-size: 28rpx;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
border: none;
&.primary_popup_button {
background: #3498db;
color: #fff;
&:active {
background: #2980b9;
}
}
&.secondary_popup_button {
background: #f8f9fa;
color: #666;
&:active {
background: #e9ecef;
}
}
} }
} }
} }

3
uniapp/pages/student/profile/index.vue

@ -60,7 +60,8 @@
<fui-picker <fui-picker
:options="genderOptions" :options="genderOptions"
:show="showGenderPicker" :show="showGenderPicker"
@change="changeGender" :linkage="true"
@change="changeGender"
@cancel="showGenderPicker = false" @cancel="showGenderPicker = false"
></fui-picker> ></fui-picker>
</view> </view>

Loading…
Cancel
Save