contractModel = new Contract(); $this->logModel = new DocumentGenerateLog(); $this->dataSourceModel = new DocumentDataSourceConfig(); } /** * 获取模板列表 * @param array $where * @return array */ public function getPage(array $where = []) { $field = 'id,contract_name,contract_template,contract_status,contract_type,remarks,original_filename,file_size,placeholders,created_at,updated_at'; $order = 'id desc'; $search_model = $this->contractModel->withSearch(["contract_status", "contract_type", "created_at"], $where)->field($field)->order($order); $list = $this->pageQuery($search_model); // 处理数据格式 if (!empty($list['data'])) { foreach ($list['data'] as &$item) { $item['placeholders'] = $item['placeholders'] ? json_decode($item['placeholders'], true) : []; $item['file_size_formatted'] = $this->formatFileSize($item['file_size']); } } return $list; } /** * 获取模板详情 * @param int $id * @return array */ public function getInfo(int $id) { $field = 'id,contract_name,contract_template,contract_content,contract_status,contract_type,remarks,placeholder_config,original_filename,file_size,file_hash,placeholders,created_at,updated_at'; $info = $this->contractModel->field($field)->where([['id', "=", $id]])->findOrEmpty()->toArray(); if (!empty($info)) { $info['placeholder_config'] = $info['placeholder_config'] ? json_decode($info['placeholder_config'], true) : []; $info['placeholders'] = $info['placeholders'] ? json_decode($info['placeholders'], true) : []; $info['file_size_formatted'] = $this->formatFileSize($info['file_size']); // 获取数据源配置信息,从placeholder_config字段获取 $dataSourceConfigs = []; if (!empty($info['placeholder_config'])) { // 转换placeholder_config格式为前端需要的data_source_configs格式 foreach ($info['placeholder_config'] as $placeholder => $config) { $dataSourceConfigs[] = [ 'id' => 0, 'placeholder' => $placeholder, 'data_type' => $config['data_type'] ?? 'user_input', 'table_name' => $config['table_name'] ?? '', 'field_name' => $config['field_name'] ?? '', 'system_function' => $config['system_function'] ?? '', 'user_input_value' => $config['user_input_value'] ?? '', 'field_type' => $config['field_type'] ?? 'text', 'is_required' => $config['is_required'] ?? 0, 'default_value' => $config['default_value'] ?? '' ]; } } $info['data_source_configs'] = $dataSourceConfigs; // 如果没有数据源配置,但有占位符,则创建默认配置 if (empty($dataSourceConfigs) && !empty($info['placeholders'])) { $defaultConfigs = []; foreach ($info['placeholders'] as $placeholder) { $defaultConfigs[] = [ 'id' => 0, 'placeholder' => $placeholder, 'data_type' => 'user_input', 'table_name' => '', 'field_name' => '', 'system_function' => '', 'user_input_value' => '', 'field_type' => 'text', 'is_required' => 0, 'default_value' => '' ]; } $info['data_source_configs'] = $defaultConfigs; } } return $info; } /** * 保存数据源配置 * @param int $contractId 合同ID * @param array $configs 配置数据 * @return bool * @throws \Exception */ public function saveDataSourceConfig(int $contractId, array $configs): bool { // 验证合同是否存在 $contract = $this->contractModel->find($contractId); if (!$contract) { throw new \Exception('合同不存在'); } // 开启事务 \think\facade\Db::startTrans(); try { // 删除现有配置 $this->dataSourceModel->where('contract_id', $contractId)->delete(); // 批量插入新配置 if (!empty($configs)) { $insertData = []; foreach ($configs as $config) { // 验证必需字段 if (empty($config['placeholder'])) { throw new \Exception('占位符不能为空'); } $insertData[] = [ 'contract_id' => $contractId, 'placeholder' => $config['placeholder'], 'table_name' => $config['table_name'] ?? '', 'field_name' => $config['field_name'] ?? '', 'field_type' => $config['field_type'] ?? 'string', 'is_required' => $config['is_required'] ?? 0, 'default_value' => $config['default_value'] ?? '', 'created_at' => date('Y-m-d H:i:s') ]; } $result = $this->dataSourceModel->insertAll($insertData); if (!$result) { throw new \Exception('保存配置失败'); } } // 提交事务 \think\facade\Db::commit(); return true; } catch (\Exception $e) { // 回滚事务 \think\facade\Db::rollback(); throw $e; } } /** * 上传Word模板文件 * @param $file * @param array $data * @return array * @throws \Exception */ public function uploadTemplate($file, array $data = []) { // 验证文件类型 $allowedTypes = ['docx', 'doc']; $extension = strtolower($file->getOriginalExtension()); if (!in_array($extension, $allowedTypes)) { throw new \Exception('只支持 .docx 和 .doc 格式的Word文档'); } // 获取文件信息 $fileSize = $file->getSize(); $realPath = $file->getRealPath(); // 验证文件大小 (最大10MB) $maxSize = 10 * 1024 * 1024; if ($fileSize > $maxSize) { throw new \Exception('文件大小不能超过10MB'); } // 生成文件hash防重复 $fileHash = md5_file($realPath); try { // 生成保存路径 $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); // 准备保存到数据库的数据 $saveData = [ 'contract_name' => !empty($data['contract_name']) ? $data['contract_name'] : pathinfo($file->getOriginalName(), PATHINFO_FILENAME), 'contract_template' => $savePath, 'contract_content' => $parseResult['content'], 'contract_status' => 'draft', 'contract_type' => !empty($data['contract_type']) ? $data['contract_type'] : 'general', 'original_filename' => $file->getOriginalName(), 'file_size' => $fileSize, 'file_hash' => $fileHash, 'placeholders' => json_encode($parseResult['placeholders']), 'remarks' => !empty($data['remarks']) ? $data['remarks'] : '', 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s') ]; // 保存到数据库 $template = $this->contractModel->create($saveData); return [ 'id' => $template->id, 'template_name' => $saveData['contract_name'], 'file_path' => $savePath, 'placeholders' => $parseResult['placeholders'], 'placeholder_count' => count($parseResult['placeholders']) ]; } catch (\Exception $e) { // 如果保存失败,删除已上传的文件 if (isset($fullPath) && file_exists($fullPath)) { unlink($fullPath); } throw new \Exception('模板上传失败:' . $e->getMessage()); } } /** * 解析Word模板占位符 * @param array $data * @return array * @throws \Exception */ public function parsePlaceholder(array $data) { if (!empty($data['template_id'])) { // 从数据库获取模板信息 $template = $this->contractModel->find($data['template_id']); if (!$template) { throw new \Exception('模板不存在'); } $templatePath = public_path() . '/upload/' . $template['contract_template']; } else if (!empty($data['template_path'])) { $templatePath = $data['template_path']; } else { throw new \Exception('请提供模板文件路径或模板ID'); } if (!file_exists($templatePath)) { throw new \Exception('模板文件不存在'); } return $this->parseWordTemplate($templatePath); } /** * 解析Word模板文件内容 * @param string $filePath * @return array * @throws \Exception */ private function parseWordTemplate(string $filePath) { try { // 读取Word文档 $templateProcessor = new TemplateProcessor($filePath); // 获取文档内容(简化版本,实际可能需要更复杂的解析) $content = $this->extractWordContent($filePath); // 提取占位符 - 匹配 {{...}} 格式 $placeholders = $this->extractPlaceholders($content); return [ 'content' => $content, 'placeholders' => $placeholders ]; } catch (\Exception $e) { Log::error('Word模板解析失败:' . $e->getMessage()); throw new \Exception('Word模板解析失败:' . $e->getMessage()); } } /** * 提取Word文档内容 * @param string $filePath * @return string */ private function extractWordContent(string $filePath) { try { $phpWord = IOFactory::load($filePath); $content = ''; // 遍历所有章节 foreach ($phpWord->getSections() as $section) { foreach ($section->getElements() as $element) { if (method_exists($element, 'getText')) { $content .= $element->getText() . "\n"; } } } return $content; } catch (\Exception $e) { Log::error('提取Word内容失败:' . $e->getMessage()); return ''; } } /** * 从内容中提取占位符 * @param string $content * @return array */ private function extractPlaceholders(string $content) { $placeholders = []; // 匹配 {{变量名}} 格式的占位符 if (preg_match_all('/\{\{([^}]+)\}\}/', $content, $matches)) { foreach ($matches[1] as $placeholder) { $placeholder = trim($placeholder); if (!in_array($placeholder, $placeholders)) { $placeholders[] = $placeholder; } } } return $placeholders; } /** * 保存占位符配置 * @param array $data * @return bool * @throws \Exception */ public function savePlaceholderConfig(int $templateId, array $configs) { $template = $this->contractModel->find($templateId); if (!$template) { throw new \Exception('模板不存在'); } // 转换配置数据格式以支持三种数据类型:database, system, user_input $configData = []; foreach ($configs as $config) { $placeholder = $config['placeholder']; $dataType = $config['data_type'] ?? 'user_input'; $configData[$placeholder] = [ 'data_type' => $dataType, 'table_name' => $config['table_name'] ?? '', 'field_name' => $config['field_name'] ?? '', 'system_function' => $config['system_function'] ?? '', 'user_input_value' => $config['user_input_value'] ?? '', 'field_type' => $config['field_type'] ?? 'text', 'is_required' => $config['is_required'] ?? 0, 'default_value' => $config['default_value'] ?? '' ]; } // 开启事务 \think\facade\Db::startTrans(); try { // 1. 保存配置到合同表的placeholder_config字段(保持兼容性) $template->placeholder_config = json_encode($configData); $template->updated_at = date('Y-m-d H:i:s'); $template->save(); // 2. 同时保存到独立的数据源配置表(用户期望的表) $this->saveConfigToDataSourceTable($templateId, $configData); \think\facade\Db::commit(); return true; } catch (\Exception $e) { \think\facade\Db::rollback(); throw $e; } } /** * 保存配置到数据源配置表 * @param int $contractId 合同ID * @param array $config 配置数据 * @return void * @throws \Exception */ private function saveConfigToDataSourceTable(int $contractId, array $config): void { // 删除现有配置 $this->dataSourceModel->where('contract_id', $contractId)->delete(); // 转换配置格式并保存 if (!empty($config)) { $insertData = []; foreach ($config as $placeholder => $settings) { $insertData[] = [ 'contract_id' => $contractId, 'placeholder' => $placeholder, 'data_type' => $settings['data_type'] ?? 'user_input', 'table_name' => $settings['table_name'] ?? '', 'field_name' => $settings['field_name'] ?? '', 'system_function' => $settings['system_function'] ?? '', 'field_type' => $settings['field_type'] ?? 'text', 'is_required' => $settings['is_required'] ?? 0, 'default_value' => $settings['default_value'] ?? '', 'created_at' => date('Y-m-d H:i:s') ]; } if (!empty($insertData)) { $this->dataSourceModel->insertAll($insertData); } } } /** * 获取可用数据源列表 * @return array */ public function getDataSources() { return [ 'tables' => $this->getAvailableTables(), 'system_functions' => $this->getSystemFunctions() ]; } /** * 获取可用数据表配置 * @return array */ public function getAvailableTables() { return [ 'school_student' => [ 'label' => '学员表', 'fields' => [ 'id' => '学员ID', 'name' => '学员姓名', 'gender' => '性别', 'age' => '年龄', 'birthday' => '生日', 'emergency_contact' => '紧急联系人', 'contact_phone' => '联系人电话', 'status' => '学员状态', 'trial_class_count' => '体验课次数', 'created_at' => '创建时间', 'updated_at' => '修改时间' ] ], 'school_customer_resources' => [ 'label' => '客户资源表', 'fields' => [ 'id' => '编号', 'name' => '姓名', 'phone_number' => '联系电话', 'gender' => '性别', 'age' => '年龄', 'source_channel' => '来源渠道', 'source' => '来源', 'consultant' => '顾问', 'demand' => '需求', 'purchasing_power' => '购买力', 'initial_intent' => '客户初步意向度', 'trial_class_count' => '体验课次数', 'created_at' => '创建时间' ] ], 'school_order_table' => [ 'label' => '订单表', 'fields' => [ 'id' => '订单编号', 'payment_id' => '支付编号', 'order_type' => '订单类型', 'order_status' => '订单状态', 'payment_type' => '付款类型', 'order_amount' => '订单金额', 'discount_amount' => '优惠金额', 'payment_time' => '支付时间', 'created_at' => '创建时间', 'remark' => '订单备注' ] ], 'school_course' => [ 'label' => '课程表', 'fields' => [ 'id' => '课程编号', 'course_name' => '课程名称', 'course_type' => '课程类型', 'duration' => '课程时长', 'session_count' => '课时数量', 'single_session_count' => '单次消课数量', 'gift_session_count' => '赠送课时数量', 'price' => '课程价格', 'internal_reminder' => '内部提醒课时', 'customer_reminder' => '客户提醒课时', 'status' => '课程状态', 'created_at' => '创建时间' ] ], 'school_personnel' => [ 'label' => '人员表', 'fields' => [ 'id' => 'ID', 'name' => '姓名', 'gender' => '性别', 'phone' => '电话', 'email' => '邮箱', 'wx' => '微信号', 'address' => '家庭住址', 'education' => '学历', 'employee_number' => '员工编号', 'account_type' => '账号类型', 'status' => '状态', 'join_time' => '入职时间', 'create_time' => '创建时间' ] ] ]; } /** * 获取系统函数配置 * @return array */ public function getSystemFunctions() { return [ 'current_date' => '当前日期', 'current_time' => '当前时间', 'current_datetime' => '当前日期时间', 'current_year' => '当前年份', 'current_month' => '当前月份', 'current_day' => '当前日', 'random_number' => '随机编号', 'contract_generate_time' => '合同生成时间', 'system_name' => '系统名称', 'current_user' => '当前用户', 'current_campus' => '当前校区', // 签名占位符 'employee_signature' => '员工签名位置', 'student_signature' => '学员签名位置' ]; } /** * 生成Word文档 * @param array $data * @return array * @throws \Exception */ public function generateDocument(array $data) { $template = $this->contractModel->find($data['template_id']); if (!$template) { throw new \Exception('模板不存在'); } if (empty($template['placeholder_config'])) { throw new \Exception('模板尚未配置占位符'); } // 创建生成记录 $logData = [ 'site_id' => $this->site_id, 'template_id' => $data['template_id'], 'user_id' => $this->uid, 'user_type' => 'admin', 'fill_data' => json_encode($data['fill_data']), 'status' => 'pending', 'created_at' => time(), 'updated_at' => time() ]; $log = $this->logModel->create($logData); try { // 更新状态为处理中 $log->status = 'processing'; $log->process_start_time = date('Y-m-d H:i:s'); $log->save(); // 准备填充数据 $placeholderConfig = json_decode($template['placeholder_config'], true); $fillValues = $this->prepareFillData($placeholderConfig, $data['fill_data']); // 生成文档 $templatePath = public_path() . '/upload/' . $template['contract_template']; $outputFileName = $data['output_filename'] ?: ($template['contract_name'] . '_' . date('YmdHis') . '.docx'); $outputPath = 'generated_documents/' . date('Y/m/') . $outputFileName; $fullOutputPath = public_path() . '/upload/' . $outputPath; // 确保目录存在 $outputDir = dirname($fullOutputPath); if (!is_dir($outputDir)) { mkdir($outputDir, 0755, true); } // 使用 PhpWord 模板处理器 $templateProcessor = new TemplateProcessor($templatePath); foreach ($fillValues as $placeholder => $value) { $templateProcessor->setValue($placeholder, $value); } $templateProcessor->saveAs($fullOutputPath); // 更新生成记录 $log->status = 'completed'; $log->generated_file_path = $outputPath; $log->generated_file_name = $outputFileName; $log->process_end_time = date('Y-m-d H:i:s'); $log->save(); return [ 'log_id' => $log->id, 'file_path' => $outputPath, 'file_name' => $outputFileName, 'download_url' => url('/upload/' . $outputPath) ]; } catch (\Exception $e) { // 更新记录为失败状态 $log->status = 'failed'; $log->error_msg = $e->getMessage(); $log->process_end_time = date('Y-m-d H:i:s'); $log->save(); throw new \Exception('文档生成失败:' . $e->getMessage()); } } /** * 准备填充数据 * @param array $placeholderConfig * @param array $userFillData * @return array */ private function prepareFillData(array $placeholderConfig, array $userFillData) { $fillValues = []; foreach ($placeholderConfig as $placeholder => $config) { $value = ''; if ($config['data_source'] === 'manual') { // 手动填写的数据 $value = $userFillData[$placeholder] ?? $config['default_value'] ?? ''; } else if ($config['data_source'] === 'database') { // 从数据库获取数据 $value = $this->getDataFromDatabase($config, $userFillData); } // 应用处理函数 if (!empty($config['process_function'])) { $value = $this->applyProcessFunction($value, $config['process_function']); } $fillValues[$placeholder] = $value; } return $fillValues; } /** * 从数据库获取数据 * @param array $config * @param array $userFillData * @return string */ private function getDataFromDatabase(array $config, array $userFillData) { try { $tableName = $config['table_name']; $fieldName = $config['field_name']; // 简单的数据库查询(实际应用中需要更完善的查询逻辑) $model = Db::connect(); $result = $model->table($tableName)->field($fieldName)->find(); return $result[$fieldName] ?? $config['default_value'] ?? ''; } catch (\Exception $e) { Log::error('数据库查询失败:' . $e->getMessage()); return $config['default_value'] ?? ''; } } /** * 应用处理函数 * @param mixed $value * @param string $functionName * @return string */ private function applyProcessFunction($value, string $functionName) { switch ($functionName) { case 'formatDate': return $value ? date('Y年m月d日', strtotime($value)) : ''; case 'formatDateTime': return $value ? date('Y年m月d日 H:i', strtotime($value)) : ''; case 'formatNumber': return is_numeric($value) ? number_format($value, 2) : $value; case 'toUpper': return strtoupper($value); case 'toLower': return strtolower($value); default: return $value; } } /** * 下载生成的文档 * @param int $logId * @return Response * @throws \Exception */ public function downloadDocument(int $logId) { $log = $this->logModel->find($logId); if (!$log) { throw new \Exception('记录不存在'); } if ($log['status'] !== 'completed') { throw new \Exception('文档尚未生成完成'); } $filePath = public_path() . '/upload/' . $log['generated_file_path']; if (!file_exists($filePath)) { throw new \Exception('文件不存在'); } // 更新下载统计 $log->download_count = $log->download_count + 1; $log->last_download_time = date('Y-m-d H:i:s'); $log->save(); // 返回文件下载响应 return download($filePath, $log['generated_file_name']); } /** * 获取生成记录 * @param array $where * @return array */ public function getGenerateLog(array $where = []) { $field = 'id,template_id,user_id,user_type,generated_file_name,status,download_count,created_at,process_start_time,process_end_time'; $order = 'id desc'; $searchModel = $this->logModel ->alias('log') ->join('school_contract template', 'log.template_id = template.id') ->field($field . ',template.contract_name') ->where('log.site_id', $this->site_id) ->order($order); // 添加搜索条件 if (!empty($where['template_id'])) { $searchModel->where('log.template_id', $where['template_id']); } if (!empty($where['status'])) { $searchModel->where('log.status', $where['status']); } return $this->pageQuery($searchModel); } /** * 格式化文件大小 * @param int $size * @return string */ private function formatFileSize(int $size) { $units = ['B', 'KB', 'MB', 'GB']; $index = 0; while ($size >= 1024 && $index < count($units) - 1) { $size /= 1024; $index++; } return round($size, 2) . ' ' . $units[$index]; } /** * 预览模板 * @param int $id * @return array * @throws \Exception */ public function previewTemplate(int $id) { $template = $this->contractModel->find($id); if (!$template) { throw new \Exception('模板不存在'); } return [ 'content' => $template['contract_content'], 'placeholders' => json_decode($template['placeholders'], true) ?: [] ]; } /** * 删除模板 * @param int $id * @return bool * @throws \Exception */ public function delete(int $id) { $template = $this->contractModel->find($id); if (!$template) { throw new \Exception('模板不存在'); } // 删除关联的生成记录和文件 $logs = $this->logModel->where('template_id', $id)->select(); foreach ($logs as $log) { if ($log['generated_file_path']) { $filePath = public_path() . '/upload/' . $log['generated_file_path']; if (file_exists($filePath)) { unlink($filePath); } } } $this->logModel->where('template_id', $id)->delete(); // 删除模板文件 if ($template['contract_template']) { $templatePath = public_path() . '/upload/' . $template['contract_template']; if (file_exists($templatePath)) { unlink($templatePath); } } // 删除数据库记录 return $template->delete(); } /** * 复制模板 * @param int $id * @return array * @throws \Exception */ public function copy(int $id) { $template = $this->contractModel->find($id); if (!$template) { throw new \Exception('模板不存在'); } $newData = $template->toArray(); unset($newData['id']); $newData['contract_name'] = $newData['contract_name'] . '_副本'; $newData['created_at'] = date('Y-m-d H:i:s'); $newData['updated_at'] = date('Y-m-d H:i:s'); $newTemplate = $this->contractModel->create($newData); return ['id' => $newTemplate->id]; } /** * 批量删除生成记录 * @param array $ids * @return bool */ public function batchDeleteLog(array $ids) { $logs = $this->logModel->whereIn('id', $ids)->select(); foreach ($logs as $log) { if ($log['generated_file_path']) { $filePath = public_path() . '/upload/' . $log['generated_file_path']; if (file_exists($filePath)) { unlink($filePath); } } } return $this->logModel->whereIn('id', $ids)->delete(); } /** * 更新模板状态 * @param int $id * @param string $status * @return bool * @throws \Exception */ public function updateStatus(int $id, string $status) { $template = $this->contractModel->find($id); if (!$template) { throw new \Exception('模板不存在'); } $template->contract_status = $status; $template->updated_at = date('Y-m-d H:i:s'); 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()); } } }