Эх сурвалжийг харах

Merge branch 'dev' of yue/DataMiddleware into main

YueYunyun 9 сар өмнө
parent
commit
59b31f9a01
53 өөрчлөгдсөн 4226 нэмэгдсэн , 1274 устгасан
  1. 0 0
      SourceCode/DataMiddleware/app/ai/__init__.py
  2. 48 0
      SourceCode/DataMiddleware/app/ai/anything_llm.py
  3. 63 0
      SourceCode/DataMiddleware/app/ai/fast_gpt.py
  4. 9 0
      SourceCode/DataMiddleware/app/config.yml
  5. 1 1
      SourceCode/DataMiddleware/app/data_collect/__init__.py
  6. 5 5
      SourceCode/DataMiddleware/app/data_collect/collect.py
  7. 12 10
      SourceCode/DataMiddleware/app/data_process/__init__.py
  8. 48 29
      SourceCode/DataMiddleware/app/data_process/pre_process.py
  9. 64 8
      SourceCode/DataMiddleware/app/data_process/process.py
  10. 30 46
      SourceCode/DataMiddleware/app/data_process/standard_data_process.py
  11. 5 3
      SourceCode/DataMiddleware/app/data_send/__init__.py
  12. 12 8
      SourceCode/DataMiddleware/app/data_send/send.py
  13. 9 5
      SourceCode/DataMiddleware/app/init.sql
  14. 1 1
      SourceCode/DataMiddleware/app/main.py
  15. 28 8
      SourceCode/DataMiddleware/app/models/project_data.py
  16. 2 2
      SourceCode/DataMiddleware/app/models/standard_data.py
  17. 3 3
      SourceCode/DataMiddleware/app/models/standard_update_log.py
  18. 176 128
      SourceCode/DataMiddleware/app/stores/mysql_store.py
  19. 2 2
      SourceCode/DataMiddleware/app/ui/__init__.py
  20. 150 0
      SourceCode/DataMiddleware/app/ui/project_services.py
  21. 182 0
      SourceCode/DataMiddleware/app/ui/project_views.py
  22. 0 141
      SourceCode/DataMiddleware/app/ui/source_data_services.py
  23. 0 106
      SourceCode/DataMiddleware/app/ui/source_data_views.py
  24. 143 0
      SourceCode/DataMiddleware/app/ui/static/project_item_list.js
  25. 212 0
      SourceCode/DataMiddleware/app/ui/static/project_list.js
  26. 0 160
      SourceCode/DataMiddleware/app/ui/static/source_data_list.js
  27. 0 125
      SourceCode/DataMiddleware/app/ui/static/source_item_list.js
  28. 223 150
      SourceCode/DataMiddleware/app/ui/static/styles.css
  29. 161 0
      SourceCode/DataMiddleware/app/ui/templates/project/project_item_list.html
  30. 249 0
      SourceCode/DataMiddleware/app/ui/templates/project/project_list.html
  31. 0 180
      SourceCode/DataMiddleware/app/ui/templates/source_data/source_data_list.html
  32. 0 149
      SourceCode/DataMiddleware/app/ui/templates/source_data/source_item_list.html
  33. 4 4
      SourceCode/DataMiddleware/requirements.txt
  34. 0 0
      SourceCode/DataMiddleware/tools/__init__.py
  35. 24 0
      SourceCode/DataMiddleware/tools/config.yml
  36. 30 0
      SourceCode/DataMiddleware/tools/init.sql
  37. 23 0
      SourceCode/DataMiddleware/tools/main.py
  38. 0 0
      SourceCode/DataMiddleware/tools/models/__init__.py
  39. 38 0
      SourceCode/DataMiddleware/tools/models/pdf_records.py
  40. 23 0
      SourceCode/DataMiddleware/tools/models/pdf_standard.py
  41. 417 0
      SourceCode/DataMiddleware/tools/pdf_json/v1/01.json
  42. 562 0
      SourceCode/DataMiddleware/tools/pdf_json/v1/03.json
  43. 4 0
      SourceCode/DataMiddleware/tools/pdf_split/__init__.py
  44. 26 0
      SourceCode/DataMiddleware/tools/pdf_split/model.py
  45. 73 0
      SourceCode/DataMiddleware/tools/pdf_split/mysql_store.py
  46. 507 0
      SourceCode/DataMiddleware/tools/pdf_split/processor.py
  47. 53 0
      SourceCode/DataMiddleware/tools/test/fast_gpt_client.py
  48. 116 0
      SourceCode/DataMiddleware/tools/utils/__init__.py
  49. 162 0
      SourceCode/DataMiddleware/tools/utils/ai_helper.py
  50. 79 0
      SourceCode/DataMiddleware/tools/utils/config_helper.py
  51. 17 0
      SourceCode/DataMiddleware/tools/utils/file_helper.py
  52. 113 0
      SourceCode/DataMiddleware/tools/utils/logger_helper.py
  53. 117 0
      SourceCode/DataMiddleware/tools/utils/mysql_helper.py

+ 0 - 0
SourceCode/DataMiddleware/app/ai/__init__.py


+ 48 - 0
SourceCode/DataMiddleware/app/ai/anything_llm.py

@@ -0,0 +1,48 @@
+import requests,utils
+
+class AnythingLLM:
+
+
+    def __init__(self, api_key: str=None, api_url: str=None):
+        self._api_key = api_key or utils.get_config_value('anythingLLm.api_key')
+        self._api_url = api_url or utils.get_config_value('anythingLLm.api_url')
+        self._logger = utils.get_logger()
+        self._headers ={}
+
+    def build_header(self):
+        if self._api_key is None:
+            self._logger.error("anythingLLm.api_key 没有配置")
+            raise Exception("anythingLLm.api_key 没有配置")
+        self._headers = {
+            "Authorization": f"Bearer {self._api_key}",
+            "Content-Type": "application/json",
+            "accept": "application/json"
+        }
+
+    def call_ai(self, msg: str, workspace:str=None) -> str|None:
+        try:
+            self.build_header()
+            if workspace is None:
+                workspace = utils.get_config_value('anythingLLm.workspace')
+            url = f"{self._api_url}/workspace/{workspace}/chat"
+            data = {
+                "message": msg,
+                "mode": "query"  # 可选chat/query模式
+            }
+            response = requests.post(url, headers=self._headers, json=data)
+            if response.status_code == 200:
+                result = response.json()
+                self._logger.info(f"Response: {result}")
+                # 提取有效回答(去除思考过程)
+                answer = result['textResponse'].split('</think>')[-1].strip()
+                self._logger.info(f"Answer: {answer}")
+                sources = result.get('sources', [])
+                for source in sources:
+                    self._logger.info(f"Source: {source}")
+                return answer
+            else:
+                return f"Error: {response.text}"
+        except Exception as e:
+            self._logger.error(f"Error: {str(e)}")
+            return None
+

+ 63 - 0
SourceCode/DataMiddleware/app/ai/fast_gpt.py

@@ -0,0 +1,63 @@
+from httpx import stream
+
+import utils,json,requests
+import re  # 添加正则表达式库
+
+class FastGPTAi:
+
+    def __init__(self, api_key: str=None, api_url: str=None):
+        self._api_key = api_key or utils.get_config_value('fastgpt.api_key')
+        self._api_url = api_url or utils.get_config_value('fastgpt.api_url')
+        self._logger = utils.get_logger()
+        self._headers ={}
+
+    def call_ai(self, msg: str) -> json:
+        try:
+            if self._api_key is None:
+                self._logger.error("fastgpt.api_key 没有配置")
+                raise Exception("fastgpt.api_key 没有配置")
+
+            self._headers = {
+                "Authorization": f"Bearer {self._api_key}",
+                "Content-Type": "application/json"
+            }
+            url = f"{self._api_url}/v1/chat/completions"
+            data = {
+                # "model":"",
+                "stream": False,
+                "detail": False,
+                "messages": [
+                    {
+                        "role": "user",
+                        "content": msg
+                    }
+                ],
+                "response_format": {
+                    "type": "json_object"
+                }
+            }
+
+            response = requests.post(url, headers=self._headers, json=data)
+            if response.status_code == 200:
+                result = response.json()
+                self._logger.info(f"Response: {result}")
+                content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
+                if content:
+                    # 使用正则表达式去除 content 前后的特定字符串
+                    content = re.sub(r'^```json\n', '', content.strip())
+                    content = re.sub(r'\n```$', '', content.strip())
+                    try:
+                        data = json.loads(content)
+                        self._logger.info(f"Response_JSON: {data}")
+                        return data
+                    except json.JSONDecodeError as e:
+                        self._logger.error(f"Failed to decode JSON: {e}")
+                        return None
+                return None
+            else:
+                error_msg = f"Error: {response.status_code} - {response.text}"
+                self._logger.error(error_msg)
+                return None
+        except Exception as e:
+            self._logger.error(f"Error: {str(e)}")
+            return None

+ 9 - 0
SourceCode/DataMiddleware/app/config.yml

@@ -19,3 +19,12 @@ ai:
   max_tokens: 1024
 file:
   source_path: './temp_files'
+anythingLLm:
+  api_key: KN9ZCNM-ET04KGB-QPAQ02C-X1P1VN9
+  api_url: http://192.168.0.104:8011/v1
+  workspace: datamiddleware
+  thread: fd218d56-8717-4519-a6e0-09c64b732091
+fastgpt:
+  api_key: fastgpt-wdJruTQNjqeH7oRdf54Ilb12pHAspEdW7Io3hcsuaifx5U1OOFzW8Qrc
+  api_url: http://192.168.0.104:8020/api
+  app_id: 67c6be7d686fc1d3f0cc1cce

+ 1 - 1
SourceCode/DataMiddleware/app/data_collect/__init__.py

@@ -1,4 +1,4 @@
 from data_collect import collect
 
-def collect_data(project_no: str) ->bool:
+def collect_data(project_no: int) ->bool:
    return collect.DataCollect().run(project_no)

+ 5 - 5
SourceCode/DataMiddleware/app/data_collect/collect.py

@@ -1,18 +1,18 @@
 import utils
 # from stores.mysql_store import MysqlStore
-# from models.source_data import SourceData
+# from models.project import SourceData
 
 class DataCollect:
 
     def __init__(self):
         self._logger= utils.get_logger()
 
-    def run(self, project_no: str):
+    def run(self, project_id: int):
         try:
-            self._logger.info(f"开始采集数据:{project_no}")
+            self._logger.info(f"开始采集数据:{project_id}")
 
-            self._logger.info(f"采集数据完成:{project_no}")
+            self._logger.info(f"采集数据完成:{project_id}")
             return True
         except Exception as e:
-            self._logger.error(f"采集数据失败:{project_no}",{e})
+            self._logger.error(f"采集数据失败:{project_id}", {e})
             return False

+ 12 - 10
SourceCode/DataMiddleware/app/data_process/__init__.py

@@ -1,13 +1,15 @@
 from data_process import pre_process,process, standard_data_process
+from models.project_data import ProjectModel
 
-def process_project(project_no: str, standard_version: str = "") -> bool:
+
+def process_project(project: ProjectModel) -> bool:
     """
     分析处理数据
-    :param project_no: 项目编号
+    :param project: 项目
     :return: None
     """
-    if _pre_process_data(project_no):
-        if _process_data(project_no):
+    if _pre_process_data(project):
+        if process_data(project):
             return True
         else:
             return False
@@ -15,21 +17,21 @@ def process_project(project_no: str, standard_version: str = "") -> bool:
         return False
 
 
-def _pre_process_data(project_no: str) -> bool:
+def _pre_process_data(project: ProjectModel) -> bool:
     """
     预处理数据
-    :param project_no: 项目编号
+    :param project: 项目
     :return: None
     """
-    return pre_process.PreProcess().run(project_no)
+    return pre_process.PreProcess().run(project)
 
-def _process_data(project_no: str) -> bool:
+def process_data(project: ProjectModel) -> bool:
     """
     处理数据
-    :param project_no: 项目编号
+    :param project: 项目编号
     :return: None
     """
-    return process.Process().run(project_no)
+    return process.Process().run(project)
 def process_standard_data(version: str) -> None:
     """
     处理标准数据

+ 48 - 29
SourceCode/DataMiddleware/app/data_process/pre_process.py

@@ -1,7 +1,7 @@
 import utils, pandas as pd
 from pathlib import Path
 
-from models.source_data import SourceData, SourceItemData
+from models.project_data import ProjectModel, ProjectItemModel
 from stores.mysql_store import MysqlStore
 
 class PreProcess:
@@ -14,29 +14,28 @@ class PreProcess:
         self.separate_ai_calls = False
         self._data={}
 
-    def run(self, project_no: str, separate_ai_calls=True) ->bool:
+    def run(self, project: ProjectModel, separate_ai_calls=True) ->bool:
         try:
-            project = self._store.query_source_data(project_no)
-            self._logger.info(f"开始预处理项目:{project.project_name}[{project_no}_{project.standard_version}] ")
+            self._logger.info(f"开始预处理项目:{project.project_name}[{project.project_no}_{project.standard_version}] ")
+            self._store.update_project_status(project.id,21)
             self.separate_ai_calls = separate_ai_calls
-            raw_data = self.read_excels(f"{utils.get_config_value("file.source_path","./temp_files")}/{project_no}/")
+            raw_data = self.read_excels(f"{utils.get_config_value("file.source_path","./temp_files")}/project/{project.project_no}/")
             if self.separate_ai_calls:
                 for filename, sheets in raw_data.items():
                     excel_data = self.normalize_data({filename: sheets})
-                    self.call_ai(project_no, excel_data)
+                    self.call_ai(project.id, excel_data)
             else:
                 excel_data = self.normalize_data(raw_data)
-                self.call_ai(project_no, excel_data)
-            data= self._data[project_no]
-            data.project_name = project.project_name
-            data.standard_version = project.standard_version
-            data.status = project.status
-            self._store.re_insert_source_data(data)
-            del self._data[project_no]
-            self._logger.info(f"结束预处理项目:{project.project_name}[{project_no}_{project.standard_version}] [设备条数:{len(data.items)}]")
+                self.call_ai(project.id, excel_data)
+            project.items = self._data[project.id].items
+            self._store.re_insert_project(project)
+            del self._data[project.id]
+            self._logger.info(f"结束预处理项目:{project.project_name}[{project.project_no}_{project.standard_version}] [设备条数:{len(project.items)}]")
+            self._store.update_project_status(project.id,31)
             return True
         except Exception as e:
-            self._logger.error(f"预处理项目失败:[{project_no}] 错误: {e}")
+            self._logger.error(f"预处理项目失败:[{project.project_no}] 错误: {e}")
+            self._store.update_project_status(project.id,11)
             return False
 
     def read_excels(self, folder_path):
@@ -117,6 +116,7 @@ class PreProcess:
         n: string; //物料名称
         m: string; //型号规格
         u:string; //单位
+        c: float; //数量
         }
         ```
         3. 返回结构体device的数组的json数组格式,压缩成一行
@@ -129,30 +129,49 @@ class PreProcess:
         print("AI_PROMPT: " + prompt)
         return prompt
 
-    def call_ai(self, project_no, excel_data):
-        source_data = SourceData(project_no)
+    def call_ai(self, project_id:int, excel_data):
+        project = ProjectModel(project_id=project_id)
         if self.separate_ai_calls:
             # 初始化self._data[project_no],避免在循环中重复初始化
             for data in excel_data:
                 prompt = self.prompt_template([data])
                 response = self._ai_helper.call_openai(self._ai_sys_prompt, prompt)
+                # response = {'data': [{'n': '阻燃型导线穿管敷设', 'm': 'WDZB1-BYJ-750V-2.5mm2', 'u': '米', 'c': 900.0}, {'n': '阻燃型导线穿管敷设', 'm': 'WDZB1-BYJ-750V-4mm2', 'u': '米', 'c': 800.0}, {'n': '耐火型导线穿管敷设', 'm': 'WDZB1N-BYJ-750V-2.5mm2', 'u': '米', 'c': 200.0}, {'n': '防火堵料', 'm': '', 'u': '公斤', 'c': 10.0}, {'n': '防火漆', 'm': '10kg/', '': '桶', 'c': 2.0}, {'n': '镀锌钢管', 'm': 'SC20', 'u': '米', 'c': 580.0}, {'n': '接地线', 'm': '热浸镀锌扁钢25x4', 'u': '米', 'c': 50.0}, {'n': '局部等电位端子箱', 'm': '', 'u': '个', 'c': 2.0}, {'n': '双联单控照明开关', 'm': '~250V 10A', 'u': '个', 'c': 4.0}, {'n': '密闭双联单控照明开关', 'm': '~250V 10A', 'u': '个', 'c': 4.0}, {'n': '配合空调室外机移位', 'm': '', 'u': '项', 'c': 1.0}, {'n': '应急照明灯', 'm': '220V,10W', 'u': '套', 'c': 4.0}, {'n': '门禁', 'm': '', 'u': '套', 'c': 4.0}, {'n': '配电线路改移', 'm': '开槽、移点位等', 'u': '项', 'c': 1.0}, {'n': '烘手器插座', 'm': '220V,10A,密闭型', 'u': '个', 'c': 2.0}], 'completion_tokens': 439, 'prompt_tokens': 766, 'total_tokens': 1205}
                 # 更新数据部分
-                source_data.items.extend(self.format_data(project_no, response["data"]))
-                source_data.completion_tokens += response["completion_tokens"]
-                source_data.prompt_tokens += response["prompt_tokens"]
-                source_data.total_tokens += response["total_tokens"]
+                project.items.extend(self.format_data(project_id, response["data"]))
+                project.completion_tokens += response["completion_tokens"]
+                project.prompt_tokens += response["prompt_tokens"]
+                project.total_tokens += response["total_tokens"]
         else:
             prompt = self.prompt_template(excel_data)
             response = self._ai_helper.call_openai(self._ai_sys_prompt, prompt)
-            source_data.completion_tokens = response["completion_tokens"]
-            source_data.prompt_tokens = response["prompt_tokens"]
-            source_data.total_tokens = response["total_tokens"]
-            source_data.items = self.format_data(project_no, response["data"])
-        self._data[project_no] = source_data  
+            project.completion_tokens = response["completion_tokens"]
+            project.prompt_tokens = response["prompt_tokens"]
+            project.total_tokens = response["total_tokens"]
+            project.items = self.format_data(project_id, response["data"])
+        self._data[project_id] = project
 
-    @staticmethod
-    def format_data(project_no,new_data) ->list[SourceItemData]:
+
+    def format_data(self,project_id,new_data) ->list[ProjectItemModel]:
         formatted_data = []
         for data in new_data:
-            formatted_data.append(SourceItemData(project_no,data["n"],data["m"],data["u"]))
+            try:
+                item = ProjectItemModel(
+                    project_id=project_id,
+                    device_name="",
+                    device_model="",
+                    device_unit="",
+                )
+                if data.get('n'):
+                    item.device_name = data['n']
+                if data.get('m'):
+                    item.device_model = data['m']
+                if data.get('u'):
+                    item.device_unit = data['u']
+                if data.get('c'):
+                    item.device_count = data['c']
+                formatted_data.append(item)
+            except Exception as e:
+                self._logger.error(f"格式化数据时出错: {data} {e}")
+
         return formatted_data

+ 64 - 8
SourceCode/DataMiddleware/app/data_process/process.py

@@ -1,21 +1,77 @@
-import utils
+import utils,json
 
 from stores.mysql_store import MysqlStore
+from models.project_data import ProjectModel, ProjectItemModel
+from ai.fast_gpt import FastGPTAi
+
 
 class Process:
     def __init__(self):
+        self._logger= utils.get_logger()
         self._store= MysqlStore()
-        self._ai_helper = utils.AiHelper()
-        self._ai_sys_prompt = "查询给定数据的标准编号信息,返回压缩的json字符传"
+        self._ai = FastGPTAi()
         self._data={}
 
-    def run(self, project_no: str) ->bool:
+    def run(self, project: ProjectModel) ->bool:
         try:
-            data = self._store.query_source_item_data_list_by_project(project_no)
-            if data is None:
-                return False
+            self._logger.info(f"开始处理数据:{project.project_no}")
+            self._store.update_project_status(project.id,22)
+            project_items = self._store.query_project_items_by_project(project.id)
+            text = self.prompt_template(project,project_items)
+            items = self.call_ai_process(text)
+            self._store.update_project_item_standard_no_batch(items)
+            self._store.update_project_status(project.id,12)
+            self._logger.info(f"处理数据完成:{project.project_no}")
             return True
         except Exception as e:
-            utils.get_logger().error(f"查询数据失败:{e}")
+            self._logger.error(f"处理数据失败:{e}")
+            self._store.update_project_status(project.id,12)
             return False
 
+    @staticmethod
+    def prompt_template(project, items) ->str:
+        text = f"""
+        请分析提供的json数据,要求:
+        1. 根据工作内容、类型、项目名称、规格型号、数量、单位查找计算出定额编号
+        2. 工程的工作内容及类型:{project.work_content}"""
+        text += """
+        3. 提供的数据结构化信息:```typescript
+        export interface item { 
+        i: // ID
+        n: string; //物料名称
+        m: string; //型号规格
+        u:string; //单位
+        c: float; //数量
+        }
+        ```
+        4. 需返回的结构体:```typescript
+        export interface item { 
+        i: int; // ID 与提供的ID保持一致
+        s: string; // 定额编号
+        }
+        ```
+        5. 返回结构体item的数组的json数组格式,压缩成一行。
+        6. 数据如下:
+        """
+        item_ai_json = ProjectItemModel.list_to_ai_json(items)
+        text += json.dumps(item_ai_json,ensure_ascii=False, separators=(',', ':'))
+        return text
+
+    def call_ai_process(self, message:str) ->list[ProjectItemModel]:
+        try:
+
+            self._logger.info(f"开始调用AI:\n {message}")
+            json_data = self._ai.call_ai(message)
+            self._logger.info(f"AI返回结果:{json_data}")
+            data=[]
+            for item in json_data:
+                data.append(ProjectItemModel(
+                    item_id=item["i"],
+                    standard_no=item["s"]
+                ))
+            return data
+        except Exception as e:
+            self._logger.error(f"AI调用失败:{e}")
+            return []
+
+

+ 30 - 46
SourceCode/DataMiddleware/app/data_process/standard_data_process.py

@@ -2,27 +2,22 @@ import utils
 from pathlib import Path
 
 from stores.mysql_store import MysqlStore
-import pdfplumber  # 添加对pdfplumber库的导入,用于读取PDF文件
-from pytesseract import image_to_string  # 添加对pytesseract库的导入,用于OCR识别
-from PIL import Image  # 添加对PIL库的导入,用于处理图片
-import io
+from models.standard_data import StandardModel
 import pandas as pd  # 添加对pandas库的导入,用于处理表格数据
-from pdf2image import convert_from_path  # 添加对pdf2image库的导入,用于提取PDF中的图像
 
 class StandardDataProcess:
     def __init__(self):
+        self._logger= utils.get_logger()
         self._store= MysqlStore()
         self._ai_helper = utils.AiHelper()
         self._ai_sys_prompt = "查询给定数据的标准编号信息,返回压缩的json字符传"
-        self._data={}
 
     def run(self,version:str):
         path= f"./temp_files/data/v{version}"
-        pdf_data=  self.read_pdf(path)
-        print(pdf_data)
+        data = self.read_excels(path)
+        self.insert_data(version,data)
 
-    @staticmethod
-    def read_pdf(folder_path:str):
+    def read_excels(self, folder_path:str):
         path = Path(folder_path)
         # 验证路径是否存在
         if not path.exists():
@@ -32,41 +27,30 @@ class StandardDataProcess:
         if not path.is_dir():
             raise NotADirectoryError(f"路径不是目录: {path.resolve()}")
 
-        pdf_files = list(path.glob('*.pdf'))
-
-        if not pdf_files:
-            print(f"警告:未找到任何PDF文件")
-        for file in pdf_files:
+        excel_files = list(path.glob('.csv'))
+        for file in excel_files:
             try:
-                # 读取PDF文件
-                text = ""
-                # 使用pdfplumber提取PDF中的图像
-                with pdfplumber.open(file) as pdf:
-                    for page in pdf.pages:
-                        # 提取页面中的图像
-                        for image in page.images:
-                            # 使用PIL打开图像
-                            img = Image.open(io.BytesIO(image["stream"].get_data()))
-                            # 使用pytesseract进行OCR识别
-                            ocr_text = image_to_string(img, lang='chi_sim')
-                            # 尝试将OCR识别的文本转换为表格
-                            try:
-                                table = pd.read_csv(io.StringIO(ocr_text), sep='\s+', engine='python')
-                                text += table.to_markdown() + "\n"
-                            except Exception as e:
-                                print(f"无法将OCR识别的文本转换为表格: {str(e)}")
-                return text
+                # 读取CSV文件
+                df = pd.read_csv(file)
+                data =  df.to_dict(orient='records')
+                return data
             except Exception as e:
-                print(f"Error reading {file}: {str(e)}")
-
-    def prompt_template(data:str):
-        prompt = f"""
-        从下面的表格里,找出电算代号对应的项目名称及单位要求:
-        1. 电算代号是每个表格的第一列数据中的只有数字的,电算代号、项目名称、单位是表格中一列一列的数据。
-        2. 输出压缩成一行的json格式,
-        3. 找出所有的电算代号,不能有遗漏。
-        4. 例子:7 |人工|工日 、1940005|工字钢 Q235-A|kg 输出:[{"e": "7","p": "人工","u": "工日"},{"e": "1940005","p": "工字钢 Q235-A","u": "kg"}]
-        表格数据如下:
-        {data}
-        """
-        return prompt
+                self._logger.error(f"读取数据失败 {file}: {str(e)}")
+
+    def insert_data(self,version:str,data):
+        for row in data:
+            standard_no = row['电算代号']
+            if not standard_no:
+                continue
+            name = row['项目名称']
+            unit = row['单位']
+            item = self._store.query_standard_data_by_no(row['电算代号'],version)
+            if item:
+                item.standard_version=version
+                item.standard_name=name
+                item.device_model=None
+                item.device_unit=unit
+                self._store.update_standard_data(item)
+            else:
+                item= StandardModel(device_name=name, device_unit=unit, standard_no=standard_no, standard_version=version)
+            self._store.insert_standard_data(item)

+ 5 - 3
SourceCode/DataMiddleware/app/data_send/__init__.py

@@ -1,11 +1,13 @@
 from data_send import  send
+from models.project_data import ProjectModel
 
-def send_project_data(project_no: str) -> bool:
+
+def send_project_data(project: ProjectModel) -> bool:
     """
     发送项目数据到平台
-    :param project_no
+    :param project
     """
-    return send.DataSend().send_by_project(project_no)
+    return send.DataSend().send_by_project(project)
 
 def send_item_data(item_id: int) -> bool:
     """

+ 12 - 8
SourceCode/DataMiddleware/app/data_send/send.py

@@ -1,6 +1,6 @@
 import  utils
 from stores.mysql_store import MysqlStore
-from models.source_data import SourceItemData
+from models.project_data import ProjectModel, ProjectItemModel
 
 
 class DataSend:
@@ -8,22 +8,26 @@ class DataSend:
         self._logger= utils.get_logger()
         self._store= MysqlStore()
 
-    def send_by_project(self, project_no: str) -> bool:
+    def send_by_project(self, project: ProjectModel) -> bool:
         try:
-            self._logger.info(f"开始发送数据,项目:{project_no}")
-            data_list = self._store.query_source_item_data_list_by_project(project_no, with_empty=False)
+
+            self._logger.info(f"开始发送数据,项目:{project.project_no}")
+            self._store.update_project_status(project.id,23)
+            data_list = self._store.query_project_items_by_project(project.id, with_empty=False)
             for data in data_list:
                 self.send_data(data)
-            self._logger.info(f"发送数据成功,项目:{project_no} 共发送{len(data_list)}条")
+            self._store.update_project_status(project.id,33)
+            self._logger.info(f"发送数据成功,项目:{project.project_no} 共发送{len(data_list)}条")
             return True
         except Exception as e:
-            self._logger.error(f"发送数据失败,项目:{project_no},错误信息:{e}")
+            self._logger.error(f"发送数据失败,项目:{project.project_no},错误信息:{e}")
+            self._store.update_project_status(project.id,13)
             return False
 
     def send_by_item_id(self, item_id: int) -> bool:
         try:
             self._logger.info(f"开始发送数据,项目:{item_id}")
-            data = self._store.query_source_item_data_by_id(item_id)
+            data = self._store.query_project_item_by_id(item_id)
             if self.send_data(data):
                 self._logger.info(f"发送数据成功,项目:{item_id}")
                 return True
@@ -32,7 +36,7 @@ class DataSend:
             self._logger.error(f"发送数据失败,项目:{item_id},错误信息:{e}")
             return False
 
-    def send_data(self, data: SourceItemData) -> bool:
+    def send_data(self, data: ProjectItemModel) -> bool:
         try:
             self._logger.debug(f"开始远程请求发送数据,数据:{data}" )
         except Exception as e:

+ 9 - 5
SourceCode/DataMiddleware/app/init.sql

@@ -3,9 +3,13 @@ CREATE DATABASE IF NOT EXISTS iwb_data_middleware_dev CHARACTER SET = utf8mb4 CO
 USE iwb_data_middleware_dev;
 
 -- 创建 SourceData 表
-CREATE TABLE IF NOT EXISTS source_data (
-    project_no VARCHAR(255) PRIMARY KEY,
+CREATE TABLE IF NOT EXISTS project (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    project_no VARCHAR(255) NOT NULL,
+    INDEX idx_project_no (project_no),
     project_name VARCHAR(255),
+    work_catalog VARCHAR(255),
+    work_content VARCHAR(255),
     standard_version VARCHAR(50),
     status TINYINT DEFAULT 0,
     completion_tokens INT DEFAULT 0,
@@ -18,9 +22,9 @@ CREATE TABLE IF NOT EXISTS source_data (
 ) CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
 
 -- 创建 SourceItemData 表
-CREATE TABLE IF NOT EXISTS source_item_data (
+CREATE TABLE IF NOT EXISTS project_item (
     id INT AUTO_INCREMENT PRIMARY KEY,
-    project_no VARCHAR(255) NOT NULL,
+    project_id int NOT NULL,
     device_name VARCHAR(255) NOT NULL,
     device_model VARCHAR(255) ,
     device_unit VARCHAR(50) ,
@@ -47,7 +51,7 @@ CREATE TABLE IF NOT EXISTS standard_data (
 -- 创建 StandardUpdateLog 表
 CREATE TABLE IF NOT EXISTS standard_update_log (
     id INT AUTO_INCREMENT PRIMARY KEY,
-    project_no VARCHAR(255) ,
+    project_id int,
     device_name VARCHAR(255) ,
     device_model VARCHAR(255),
     device_unit VARCHAR(50) ,

+ 1 - 1
SourceCode/DataMiddleware/app/main.py

@@ -19,7 +19,7 @@ def debug_test():
     logger.info("调试程序开始")
     # project_no = "20250224"
     # data_process.pre_process_data(project_no, "测试","v1")
-    data_process.process_standard_data("1")
+    # data_process.process_standard_data("1")
     logger.info("调试程序结束")
 
 

+ 28 - 8
SourceCode/DataMiddleware/app/models/source_data.py → SourceCode/DataMiddleware/app/models/project_data.py

@@ -1,20 +1,27 @@
 from datetime import datetime
 
 
-class SourceData:
+class ProjectModel:
 
     def __init__(self,
-                 project_no: str,
+                 project_no: str= None,
                  project_name: str = None,
+                 work_catalog: str = None,
+                 work_content: str = None,
                  standard_version: str = None,
                  status: int = 0,
                  completion_tokens: int = 0,
                  prompt_tokens: int = 0,
                  total_tokens: int = 0,
                  update_time: datetime = None,
-                 create_time: datetime = None):
+                 create_time: datetime = None,
+                 project_id: int = None,
+                 ):
+        self.id = project_id
         self.project_no = project_no
         self.project_name = project_name
+        self.work_catalog = work_catalog
+        self.work_content = work_content
         self.standard_version = standard_version
         self.status = status
         self.completion_tokens = completion_tokens
@@ -30,13 +37,13 @@ class SourceData:
     def set_items(self, items: list):
         self.items = items
 
-    def add_item(self, item: 'SourceItemData'):
+    def add_item(self, item: 'ProjectItemModel'):
         self.items.append(item)
 
 
-class SourceItemData:
+class ProjectItemModel:
     def __init__(self,
-                 project_no: str=None,
+                 project_id: int=None,
                  device_name: str=None,
                  device_model: str=None,
                  device_unit: str = None,
@@ -47,7 +54,7 @@ class SourceItemData:
                  remark: str = None,
                  update_time: datetime = None):
         self.id = item_id
-        self.project_no = project_no
+        self.project_id = project_id
         self.device_name = device_name
         self.device_model = device_model
         self.device_unit = device_unit
@@ -58,4 +65,17 @@ class SourceItemData:
         self.update_time = update_time
 
     def __str__(self):
-        return f"SourceItemData(id={self.id}, project_no={self.project_no}, device_name={self.device_name}, device_model={self.device_model}, device_unit={self.device_unit}, device_count={self.device_count}, standard_no={self.standard_no}, standard_version={self.standard_version})"
+        return f"SourceItemData(id={self.id}, project_no={self.project_id}, device_name={self.device_name}, device_model={self.device_model}, device_unit={self.device_unit}, device_count={self.device_count}, standard_no={self.standard_no}, standard_version={self.standard_version})"
+
+    def to_ai_json(self):
+        return {
+            "i": self.id,
+            "n": self.device_name,
+            "m": self.device_model,
+            "u": self.device_unit,
+            "c": self.device_count,
+        }
+
+    @staticmethod
+    def list_to_ai_json(items):
+        return [item.to_ai_json() for item in items]

+ 2 - 2
SourceCode/DataMiddleware/app/models/standard_data.py

@@ -1,11 +1,11 @@
 from datetime import datetime
 
 
-class StandardData:
+class StandardModel:
 
     def __init__(self,
                  device_name: str,
-                 device_model: str,
+                 device_model: str=None,
                  device_unit: str=None,
                  standard_no: str=None,
                  standard_version: str=None,

+ 3 - 3
SourceCode/DataMiddleware/app/models/standard_update_log.py

@@ -1,10 +1,10 @@
 from datetime import datetime
 
 
-class StandardUpdateLog:
+class StandardUpdateLogModel:
 
     def __init__(self,
-                 project_no: str,
+                 project_id: int,
                  device_name: str = None,
                  device_model: str = None,
                  device_unit: str = None,
@@ -12,7 +12,7 @@ class StandardUpdateLog:
                  new_standard_no: str = None,
                  old_standard_no: str = None,
                  create_time: datetime = None):
-        self.project_no = project_no
+        self.project_id = project_id
         self.device_name = device_name
         self.device_model = device_model
         self.device_unit = device_unit

+ 176 - 128
SourceCode/DataMiddleware/app/stores/mysql_store.py

@@ -2,9 +2,9 @@ from datetime import datetime
 
 from utils.mysql_helper import MySQLHelper
 
-from models.source_data import SourceData, SourceItemData
-from models.standard_data import StandardData
-from models.standard_update_log import StandardUpdateLog
+from models.project_data import ProjectModel, ProjectItemModel
+from models.standard_data import StandardModel
+from models.standard_update_log import StandardUpdateLogModel
 
 
 class MysqlStore:
@@ -12,8 +12,8 @@ class MysqlStore:
     def __init__(self):
         self._db_helper = MySQLHelper()
 
-    def query_source_data_all(self):
-        query = "SELECT project_no,project_name,standard_version,status,create_time FROM source_data WHERE is_del=0"
+    def query_project_all(self):
+        query = "SELECT project_no,project_name,work_catalog,work_content,standard_version,status,create_time FROM project WHERE is_del=0"
         with self._db_helper:
             data = []
             result = self._db_helper.execute_query(query)
@@ -21,19 +21,21 @@ class MysqlStore:
                 return data
             for item in result:
                 data.append(
-                    SourceData(
+                    ProjectModel(
+                        project_id=item["id"],
                         project_no=item["project_no"],
                         project_name=item["project_name"],
+                        work_catalog=item["work_catalog"],
+                        work_content=item["work_content"],
                         standard_version=item["standard_version"],
                         status=item["status"],
                         create_time=item["create_time"],
                     ))
             return data
-
-    def query_source_data_all_paginated(self, page: int, per_page: int, keyword: str = None, status: int = None) -> (list[SourceData], None):
+    def query_project_all_paginated(self, page: int, per_page: int, keyword: str = None, status: int = None) -> (list[ProjectModel], None):
         offset = (page - 1) * per_page
-        query_count = "SELECT COUNT(*) as count FROM source_data WHERE is_del=%s"
-        query = "SELECT project_no,project_name,standard_version,status,create_time FROM source_data WHERE is_del=0 "
+        query_count = "SELECT COUNT(*) as count FROM project WHERE is_del=%s"
+        query = "SELECT id,project_no,project_name,work_catalog,work_content,standard_version,status,create_time FROM project WHERE is_del=0 "
         params_count = [0]
         params = []
         q_1=" AND (project_no LIKE %s OR project_name LIKE %s)"
@@ -62,50 +64,54 @@ class MysqlStore:
                 return [], count
             for item in result:
                 data.append(
-                    SourceData(
+                    ProjectModel(
+                        project_id=item["id"],
                         project_no=item["project_no"],
                         project_name=item["project_name"],
+                        work_catalog=item["work_catalog"],
+                        work_content=item["work_content"],
                         standard_version=item["standard_version"],
                         status=item["status"],
                         create_time=item["create_time"],
                     ))
         return data, count
-
-    def query_source_data(self, project_no: str ,with_items=False) -> SourceData|None:
-        query = "SELECT project_no,project_name,standard_version,status,create_time FROM source_data WHERE is_del=0 AND project_no = %s"
-        params = (project_no, )
-        query_items = "SELECT id,project_no,device_name,device_model,standard_version,standard_no FROM source_item_data WHERE project_no = %s"
+    def query_project(self, project_id: int, with_items=False) -> ProjectModel | None:
+        query = "SELECT id,project_no,project_name,work_catalog,work_content,standard_version,status,create_time FROM project WHERE is_del=0 AND id = %s"
+        params = (project_id,)
+        query_items = "SELECT id,project_id,device_name,device_model,standard_version,standard_no FROM project_item WHERE project_id = %s"
         with self._db_helper:
             result = self._db_helper.fetch_one(query, params)
             if not result:
                 return None
-            data = SourceData(result["project_no"],
-                              result["project_name"],
-                              result["standard_version"],
-                              result["status"],
-                              create_time=result["create_time"])
+            data = ProjectModel(result["project_no"],
+                                result["project_name"],
+                                result["work_catalog"],
+                                result["work_content"],
+                                result["standard_version"],
+                                result["status"],
+                                project_id=result["id"],
+                                create_time=result["create_time"])
             if not with_items:
                 return data
             items_result = self._db_helper.execute_query(query_items, params)
             if items_result:
                 for item in items_result:
                     data.add_item(
-                        SourceItemData(
+                        ProjectItemModel(
                             item_id=item["id"],
-                            project_no=item["project_no"],
+                            project_id=item["project_no"],
                             device_name=item["device_name"],
                             device_model=item["device_model"],
                             standard_version=item["standard_version"],
                             standard_no=item["standard_no"],
                         ))
             return data
-
-    def query_source_item_data_list_by_project_paginated(self, project_no: str, page: int, per_page: int, keyword: str = None) -> (list[SourceItemData], int):
+    def query_project_items_by_project_paginated(self, project_id: int, page: int, per_page: int, keyword: str = None) -> (list[ProjectItemModel], int):
         offset = (page - 1) * per_page
-        query_count = "SELECT COUNT(*) as count FROM source_item_data WHERE project_no = %s"
-        query = "SELECT id,project_no,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM source_item_data WHERE project_no = %s"
-        params_count = (project_no,)
-        params = (project_no,)
+        query_count = "SELECT COUNT(*) as count FROM project_item WHERE project_id = %s"
+        query = "SELECT id,project_id,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM project_item WHERE project_id = %s"
+        params_count = (project_id,)
+        params = (project_id,)
 
         if keyword:
             query_count += " AND (device_name LIKE %s OR device_model LIKE %s)"
@@ -125,9 +131,9 @@ class MysqlStore:
                 return data, count
             for item in result:
                 data.append(
-                    SourceItemData(
+                    ProjectItemModel(
                         item_id=item["id"],
-                        project_no=item["project_no"],
+                        project_id=item["project_id"],
                         device_name=item["device_name"],
                         device_model=item["device_model"],
                         device_unit=item["device_unit"],
@@ -136,11 +142,10 @@ class MysqlStore:
                         standard_no=item["standard_no"],
                     ))
         return data, count
-
-    def query_source_item_data_list_by_project(self, project_no: str, with_empty=True) -> list[SourceItemData]:
-        query = "SELECT id,project_no,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM source_item_data WHERE project_no = %s"
+    def query_project_items_by_project(self, project_id: int, with_empty=True) -> list[ProjectItemModel]:
+        query = "SELECT id,project_id,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM project_item WHERE project_id = %s"
         query += "" if with_empty else " AND standard_no !='' AND standard_no IS NOT NULL"
-        params = (project_no,)
+        params = (project_id,)
         with self._db_helper:
             result = self._db_helper.execute_query(query, params)
             data = []
@@ -148,9 +153,9 @@ class MysqlStore:
                 return data
             for item in result:
                 data.append(
-                    SourceItemData(
+                    ProjectItemModel(
                         item_id=item["id"],
-                        project_no=item["project_no"],
+                        project_id=item["project_id"],
                         device_name=item["device_name"],
                         device_model=item["device_model"],
                         device_unit=item["device_unit"],
@@ -159,83 +164,87 @@ class MysqlStore:
                         standard_no=item["standard_no"],
                     ))
             return data
-
-    def insert_source_data(self, source_data: SourceData) -> bool:
-        query = "INSERT INTO source_data (project_no,project_name,standard_version,completion_tokens,prompt_tokens,total_tokens,create_time) VALUES (%s,%s,%s,%s,%s,%s,%s)"
+    def insert_project(self, project: ProjectModel) -> bool:
+        query = "INSERT INTO project (project_no,project_name,work_catalog,work_content,standard_version,completion_tokens,prompt_tokens,total_tokens,create_time) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)"
         params = (
-            source_data.project_no,
-            source_data.project_name,
-            source_data.standard_version,
-            source_data.completion_tokens,
-            source_data.prompt_tokens,
-            source_data.total_tokens,
+            project.project_no,
+            project.project_name,
+            project.work_catalog,
+            project.work_content,
+            project.standard_version,
+            project.completion_tokens,
+            project.prompt_tokens,
+            project.total_tokens,
             datetime.now(),
         )
 
         with self._db_helper:
             self._db_helper.execute_non_query(query, params)
-        self.insert_source_data_item_list(source_data)
+        self.insert_project_item_list(project)
         return True
-
-    def insert_source_data_item_list(self,source_data):
-        if len(source_data.items) <= 0:
+    def insert_project_item_list(self, project):
+        if len(project.items) <= 0:
             return
-        query_items = "INSERT INTO source_item_data (project_no,device_name,device_model,device_unit,device_count,standard_version,standard_no,update_time) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
+        query_items = "INSERT INTO project_item (project_id,device_name,device_model,device_unit,device_count,standard_version,standard_no,update_time) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
         params_items = []
-        for item in source_data.items:
+        for item in project.items:
             params_items.append((
-                source_data.project_no,
+                project.id,
                 item.device_name,
                 item.device_model,
                 item.device_unit,
                 item.device_count,
-                source_data.standard_version,
+                project.standard_version,
                 item.standard_no,
                 datetime.now(),
             ))
         with self._db_helper:
             self._db_helper.execute_many(query_items, params_items)
-
-    def update_source_data(self, source_data: SourceData):
-        query = "UPDATE source_data SET project_name = %s, standard_version = %s ,status = %s,update_time = %s WHERE project_no = %s"
+    def update_project(self, project: ProjectModel):
+        query = "UPDATE project SET project_name = %s, work_catalog = %s, work_content = %s, standard_version = %s ,status = %s,update_time = %s WHERE id = %s"
         params = (
-            source_data.project_name,
-            source_data.standard_version,
-            source_data.status,
+            project.project_name,
+            project.work_catalog,
+            project.work_content,
+            project.standard_version,
+            project.status,
             datetime.now(),
-            source_data.project_no,
+            project.id,
         )
         with self._db_helper:
             self._db_helper.execute_non_query(query, params)
-
-    def re_insert_source_data(self, source_data: SourceData) -> bool:
-        if not source_data.project_no:
+    def update_project_status(self,project_id:int, status:int):
+        query = "UPDATE project SET status = %s,update_time = %s WHERE id = %s"
+        params = (status, datetime.now(), project_id,)
+        with self._db_helper:
+            self._db_helper.execute_non_query(query, params)
+    def re_insert_project(self, project: ProjectModel) -> bool:
+        if not project.id:
             return False
-        self.update_source_data(source_data)
-        query = "DELETE FROM source_item_data WHERE project_no = %s"
-        params = (source_data.project_no,)
+        self.update_project(project)
+        query = "DELETE FROM project_item WHERE project_id = %s"
+        params = (project.id,)
         with self._db_helper:
             self._db_helper.execute_non_query(query, params)
-        self.insert_source_data_item_list(source_data)
-
-    def delete_source_data(self, project_no: str) -> bool:
-        query_1 = "UPDATE source_data SET is_del=1,delete_time=%s WHERE project_no = %s"
-        # query_2 = "DELETE FROM source_item_data WHERE project_no = %s"
-        params = (datetime.now(),project_no, )
+        self.insert_project_item_list(project)
+    def delete_project(self, project_id: int) -> bool:
+        query_1 = "UPDATE project SET is_del=1,delete_time=%s WHERE id = %s"
+        # query_2 = "DELETE FROM project_item WHERE project_no = %s"
+        params = (datetime.now(), project_id,)
         with self._db_helper:
             self._db_helper.execute_non_query(query_1, params)
             # self._db_helper.execute_non_query(query_2, params)
             return True
-    def query_source_item_data_by_id(self, item_id:int) -> SourceItemData | None:
-        query = "SELECT id,project_no,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM source_item_data WHERE id = %s"
+    def query_project_item_by_id(self, item_id:int) -> ProjectItemModel | None:
+        query = "SELECT id,project_id,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM project_item WHERE id = %s"
         params = (item_id,)
         with self._db_helper:
             result = self._db_helper.fetch_one(query, params)
             if not result:
                 return None
-            data = SourceItemData(
+            data = ProjectItemModel(
                 item_id=result["id"],
-                project_no=result["project_no"],
+                project_id=result["project_id"],
                 device_name=result["device_name"],
                 device_model=result["device_model"],
                 device_unit=result["device_unit"],
@@ -245,16 +254,16 @@ class MysqlStore:
                 update_time=datetime.now()
             )
             return data
-    def query_source_item_data(self, project_no: str,device_name:str, device_model:str) -> SourceItemData| None:
-        query = "SELECT id,project_no,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM source_item_data WHERE project_no = %s AND device_name = %s AND device_model = %s"
-        params = (project_no,device_name, device_model )
+    def query_project_item(self, project_id: int, device_name:str, device_model:str) -> ProjectItemModel | None:
+        query = "SELECT id,project_id,device_name,device_model,device_unit,device_count,standard_version,standard_no FROM project_item WHERE project_id = %s AND device_name = %s AND device_model = %s"
+        params = (project_id,device_name, device_model )
         with self._db_helper:
             result = self._db_helper.fetch_one(query, params)
             if not result:
                 return None
-            data = SourceItemData(
+            data = ProjectItemModel(
                 item_id=result["id"],
-                project_no=result["project_no"],
+                project_id=result["project_id"],
                 device_name=result["device_name"],
                 device_model=result["device_model"],
                 device_unit=result["device_unit"],
@@ -263,58 +272,70 @@ class MysqlStore:
                 standard_no=result["standard_no"],
             )
             return data
-    def insert_source_item_data(self, source_item_data: SourceItemData) -> bool:
-        query = "INSERT INTO source_item_data (project_no,device_name,device_model,device_unit,device_count,standard_version,standard_no,update_time) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
+    def insert_project_item(self, project_item: ProjectItemModel) -> bool:
+        query = "INSERT INTO project_item (project_id,device_name,device_model,device_unit,device_count,standard_version,standard_no,update_time) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
         params = (
-            source_item_data.project_no,
-            source_item_data.device_name,
-            source_item_data.device_model,
-            source_item_data.device_unit,
-            source_item_data.device_count,
-            source_item_data.standard_version,
-            source_item_data.standard_no,
+            project_item.project_id,
+            project_item.device_name,
+            project_item.device_model,
+            project_item.device_unit,
+            project_item.device_count,
+            project_item.standard_version,
+            project_item.standard_no,
             datetime.now(),
         )
         with self._db_helper:
             self._db_helper.execute_non_query(query, params)
             return True
-    def update_source_item_data(self, source_item_data: SourceItemData) -> bool:
-        query = "SElECT standard_no FROM source_item_data WHERE id = %s"
-        params = (source_item_data.id,)
-        u_query = "UPDATE source_item_data SET device_unit= %s,device_count= %s,standard_no = %s,update_time = %s WHERE id = %s"
-        u_params = (source_item_data.device_unit, source_item_data.device_count, source_item_data.standard_no, datetime.now(), source_item_data.id)
+    def update_project_data(self, project_item: ProjectItemModel) -> bool:
+        query = "SElECT standard_no FROM project_item WHERE id = %s"
+        params = (project_item.id,)
+        u_query = "UPDATE project_item SET device_name= %s,device_model= %s,device_unit= %s,device_count= %s,standard_no = %s,update_time = %s WHERE id = %s"
+        u_params = (project_item.device_name, project_item.device_model,project_item.device_unit, project_item.device_count, project_item.standard_no, datetime.now(), project_item.id)
         with self._db_helper:
             result = self._db_helper.fetch_one(query, params)
             if result:
-
                 self._db_helper.execute_non_query(u_query, u_params)
                 self.insert_or_update_standard_data(
-                    StandardData(device_name=source_item_data.device_name,
-                                 device_model=source_item_data.device_model,
-                                 device_unit=source_item_data.device_unit,
-                                 standard_version=source_item_data.standard_version,
-                                 standard_no=source_item_data.standard_no),
-                    source_item_data.project_no)
+                    StandardModel(device_name=project_item.device_name,
+                                  device_model=project_item.device_model,
+                                  device_unit=project_item.device_unit,
+                                  standard_version=project_item.standard_version,
+                                  standard_no=project_item.standard_no),
+                    project_item.project_id)
             return True
-    def delete_source_item_data_by_id(self, item_id: int):
-        query = "DELETE FROM source_item_data WHERE id = %s"
+    def delete_project_item_by_id(self, item_id: int):
+        query = "DELETE FROM project_item WHERE id = %s"
         params = (item_id, )
         with self._db_helper:
             self._db_helper.execute_non_query(query, params)
             return True
-    def delete_source_item_data(self,
-                                  project_no: str,
-                                  device_name: str,
-                                  device_model: str) -> bool:
-        query = "DELETE FROM source_item_data WHERE project_no = %s AND device_name = %s AND device_model = %s"
-        params = (project_no, device_name, device_model)
-        with self._db_helper:
-            self._db_helper.execute_non_query(query, params)
-            return True
 
+    def update_project_item_standard_no_batch(self,items:list[ProjectItemModel]):
+        data =datetime.now()
+        for item in items:
+            query = "UPDATE project_item SET standard_no = %s,update_time = %s WHERE id = %s"
+            params = (item.standard_no, data, item.id)
+            with self._db_helper:
+                self._db_helper.execute_non_query(query, params)
 
+    def query_standard_data_by_no(self, standard_no: str,version:str):
+        query = "SELECT device_model,device_name,device_unit,standard_version,standard_no FROM standard_data WHERE standard_no = %s AND standard_version = %s"
+        params = (standard_no,version,)
+        with self._db_helper:
+            result = self._db_helper.fetch_one(query, params)
+            if not result:
+                return None
+            data = StandardModel(
+                device_model=result["device_model"],
+                device_name=result["device_name"],
+                device_unit=result["device_unit"],
+                standard_version=result["standard_version"],
+                standard_no=result["standard_no"],
+            )
+            return data
     def query_standard_data(self, device_name: str,
-                            device_model: str) -> list[StandardData]|None:
+                            device_model: str) -> list[StandardModel] | None:
         query = "SELECT device_model,device_name,device_unit,standard_version,standard_no FROM standard_data WHERE device_name = %s AND device_model = %s "
         params = (device_name, device_model)
         with self._db_helper:
@@ -323,7 +344,7 @@ class MysqlStore:
                 return None
             data = []
             for result in results:
-                data.append(StandardData(
+                data.append(StandardModel(
                 device_model=result["device_model"],
                 device_name=result["device_name"],
                 device_unit=result["device_unit"],
@@ -332,9 +353,36 @@ class MysqlStore:
             ))
             return data
 
+    def insert_standard_data(self, standard_data: StandardModel) -> bool:
+        query = "INSERT INTO standard_data (device_name,device_model,device_unit,standard_version,standard_no,create_time) VALUES (%s,%s,%s,%s,%s,%s)"
+        params = (
+            standard_data.device_name,
+            standard_data.device_model,
+            standard_data.device_unit,
+            standard_data.standard_version,
+            standard_data.standard_no,
+            datetime.now(),
+        )
+        with self._db_helper:
+            self._db_helper.execute_non_query(query, params)
+            return True
+
+    def update_standard_data(self, standard_data: StandardModel) -> bool:
+        query = "UPDATE standard_data SET device_name = %s, device_model = %s, device_unit= %s WHERE standard_no = %s AND standard_version = %s"
+        params = (
+            standard_data.device_name,
+            standard_data.device_model,
+            standard_data.device_unit,
+            standard_data.standard_no,
+            standard_data.standard_version,
+        )
+        with self._db_helper:
+            self._db_helper.execute_non_query(query, params)
+            return True
+
     def insert_or_update_standard_data(self,
-                                       standard_data: StandardData,
-                                       project_no: str = None) -> bool:
+                                       standard_data: StandardModel,
+                                       project_id: int = None) -> bool:
         query = "SELECT device_model,device_name,device_unit,standard_version,standard_no FROM standard_data WHERE device_name = %s AND device_model = %s AND standard_version = %s"
         params = (standard_data.device_name, standard_data.device_model,standard_data.standard_version)
         i_query = "INSERT INTO standard_data (device_name,device_model,device_unit,standard_version,standard_no,create_time) VALUES (%s,%s,%s,%s,%s,%s)"
@@ -359,10 +407,10 @@ class MysqlStore:
             data = self._db_helper.fetch_one(query, params)
             if data:
                 self._db_helper.execute_non_query(u_query, u_params)
-                if project_no:
+                if project_id:
                     self.insert_standard_update_log(
-                        StandardUpdateLog(
-                            project_no=project_no,
+                        StandardUpdateLogModel(
+                            project_id=project_id,
                             device_name=standard_data.device_name,
                             device_model=standard_data.device_model,
                             device_unit= standard_data.device_unit,
@@ -373,21 +421,21 @@ class MysqlStore:
                 self._db_helper.execute_non_query(i_query, i_params)
             return True
 
-    def query_standard_update_log(self, project_no: str) -> list[StandardUpdateLog]|None:
-        query = "SELECT project_no,device_name,device_model,device_unit,standard_version,new_standard_no,old_standard_no,create_time FROM standard_update_log WHERE project_no = %s"
-        params = (project_no, )
+    def query_standard_update_log(self, project_id: int) -> list[StandardUpdateLogModel] | None:
+        query = "SELECT project_id,device_name,device_model,device_unit,standard_version,new_standard_no,old_standard_no,create_time FROM standard_update_log WHERE project_id = %s"
+        params = (project_id, )
         with self._db_helper:
             results = self._db_helper.execute_query(query, params)
             if not results:
                 return None
-            data = [StandardUpdateLog(**result) for result in results]
+            data = [StandardUpdateLogModel(**result) for result in results]
             return data
 
     def insert_standard_update_log(
-            self, standard_update_log: StandardUpdateLog) -> bool:
-        query = "INSERT INTO standard_update_log (project_no,device_name,device_model,device_unit,standard_version,new_standard_no,old_standard_no,create_time) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
+            self, standard_update_log: StandardUpdateLogModel) -> bool:
+        query = "INSERT INTO standard_update_log (project_id,device_name,device_model,device_unit,standard_version,new_standard_no,old_standard_no,create_time) VALUES (%s,%s,%s,%s,%s,%s,%s,%s)"
         params = (
-            standard_update_log.project_no,
+            standard_update_log.project_id,
             standard_update_log.device_name,
             standard_update_log.device_model,
             standard_update_log.device_unit,

+ 2 - 2
SourceCode/DataMiddleware/app/ui/__init__.py

@@ -7,7 +7,7 @@ def create_app():
     # web_app = Flask(__name__, template_folder=template_path)
     web_app = Flask(__name__)
     # 注册蓝图或其他初始化操作
-    from .source_data_views import source_data as source_data_blueprint
-    web_app.register_blueprint(source_data_blueprint)
+    from .project_views import project as project_blueprint
+    web_app.register_blueprint(project_blueprint)
 
     return web_app

+ 150 - 0
SourceCode/DataMiddleware/app/ui/project_services.py

@@ -0,0 +1,150 @@
+import threading,utils
+
+from models.project_data import ProjectModel, ProjectItemModel
+from stores.mysql_store import MysqlStore
+import data_collect,data_process,data_send
+
+
+class SourceDataService:
+
+    def __init__(self):
+        self._store = MysqlStore()
+        self._logger = utils.get_logger()
+
+    def get_all_project_paginated(self, page: int, per_page: int, keyword: str = None, status: int = None) ->(list[ProjectModel], int):
+        project_list, total_count = self._store.query_project_all_paginated(page, per_page, keyword, status)
+        return project_list, total_count
+
+    def get_all_project(self) ->list[ProjectModel]:
+        project_list = self._store.query_project_all()
+        return project_list
+
+    def get_project_by_id(self, project_id:int) ->ProjectModel:
+        project = self._store.query_project(project_id)
+        return project
+
+    def add_project(self, project:ProjectModel):
+        self._store.insert_project(project)
+
+
+    def update_project(self, project:ProjectModel):
+        self._store.update_project(project)
+
+    def start_task(self, project_id: int) -> (bool, str):
+        data = self._store.query_project(project_id)
+        if data:
+            if data.status == 21 or  data.status == 31:
+                return False, '正在采集数据中'
+            if data.status == 22 or data.status == 32:
+                return False, '正在分析处理中'
+            if data.status == 32 or data.status == 33:
+                return False, '正在上传数据中'
+            thread = threading.Thread(target = self._process_and_send_project, args=(data,))
+            thread.start()
+            return True, ''
+        else:
+            return False, '项目不存在'
+
+    def _process_and_send_project(self, project: ProjectModel) :
+        # 启动分析处理
+        if data_process.process_project(project):
+            # 更新远程数据
+            data_send.send_project_data(project)
+            self._store.update_project_status(project.id,5)
+
+    def process_project(self, project_id:int):
+        data = self._store.query_project(project_id)
+        if data:
+            if data.status == 21 or data.status == 31:
+                return False, '正在采集数据中'
+            if data.status == 22 or data.status == 32:
+                return False, '正在分析处理中'
+            if data.status == 32 or data.status == 33:
+                return False, '正在上传数据中'
+            thread = threading.Thread(target=self._process_project, args=(data,))
+            thread.start()
+            return True, ''
+        else:
+            return False, '项目不存在'
+
+    def _process_project(self, project: ProjectModel):
+        if data_process.process_data(project):
+            return True, ''
+        else:
+            self._logger.error(f"分析处理失败:{project.project_no}")
+        return False, '分析处理失败'
+    def start_send_data(self, project_id: int) -> (bool, str):
+        data = self._store.query_project(project_id)
+        if data:
+            if data.status == 21 or data.status == 31:
+                return False, '正在采集数据中'
+            if data.status == 22 or data.status == 32:
+                return False, '正在分析处理中'
+            if data.status == 32 or data.status == 33:
+                return False, '正在上传数据中'
+            thread = threading.Thread(target=self._send_project_data, args=(data,))
+            thread.start()
+
+
+    def _send_project_data(self, project: ProjectModel):
+        if data_send.send_project_data(project):
+            self._store.update_project_status(project.id,5)
+            return True, ''
+        else:
+            return False, '上传数据失败'
+
+    def delete_project(self, project_id:int):
+        project = self._store.query_project(project_id)
+        if project:
+            self._store.delete_project(project_id)
+            return True,''
+        else:
+            return False, '项目不存在'
+
+    def get_project_item_list_by_project_paginated(self, project_id: int, page: int, per_page: int, keyword: str = None) -> (list[ProjectItemModel], int):
+        return self._store.query_project_items_by_project_paginated(project_id, page, per_page, keyword)
+    def get_project_item_list_by_project_id(self, project_id:int) ->list[ProjectItemModel]:
+        return  self._store.query_project_items_by_project(project_id)
+
+    def get_project_item_by_id(self, item_id: int):
+        return self._store.query_project_item_by_id(item_id)
+
+    def add_project_item(self, item:ProjectItemModel):
+        project_item = self._store.query_project_item(item.project_id, item.device_name, item.device_model)
+        if project_item:
+            return False
+        else:
+            project = self._store.query_project(item.project_id)
+            project_item = ProjectItemModel(
+                project_id=item.project_id,
+                device_name=item.device_name,
+                device_model=item.device_model,
+                device_unit=item.device_unit,
+                standard_version=project.standard_version,
+                standard_no=item.standard_no,
+            )
+            self._store.insert_project_item(project_item)
+            return True
+    def update_project_item(self, item:ProjectItemModel):
+        project_item = self._store.query_project_item_by_id(item.id)
+        if project_item:
+            project_item.device_name = item.device_name
+            project_item.device_model = item.device_model
+            project_item.device_unit = item.device_unit
+            project_item.device_count = item.device_count
+            project_item.standard_no = item.standard_no
+            self._store.update_project_data(project_item)
+            #TODO 更新标准数据库
+
+            #TODO 更新远程数据
+
+            return True
+        else:
+            return False
+    def delete_project_item(self, item_id:int):
+        project_item = self._store.query_project_item_by_id(item_id)
+        if project_item:
+            self._store.delete_project_item_by_id(project_item.id)
+            return True
+        else:
+            return False

+ 182 - 0
SourceCode/DataMiddleware/app/ui/project_views.py

@@ -0,0 +1,182 @@
+from flask import render_template, url_for, redirect, request, jsonify
+from flask import Blueprint
+import os,utils
+
+from .project_services import SourceDataService
+from models.project_data import ProjectModel,ProjectItemModel
+
+SourceDataService = SourceDataService()  # 确保 SourceDataService 是一个实例
+
+# 创建一个名为 'project' 的蓝图
+project = Blueprint('project', __name__, template_folder='templates/project')
+
+@project.route('/')
+def index():
+    return "Hello, World!"
+
+@project.route('/project_list')
+def project_list():
+    keyword = request.args.get('k', '', type=str)
+    page = request.args.get('p', 1, type=int)
+    per_page = request.args.get('pp', 15, type=int)
+    status = request.args.get('s',-1, type=int)
+    data_list, total_count = SourceDataService.get_all_project_paginated(page, per_page, keyword, None if status == -1 else status)  # 传递 status 参数
+    return render_template('project_list.html', project_list=data_list, keyword=keyword, page=page, per_page=per_page, total_count=total_count, status=status)  # 传递 status 参数到模板
+
+@project.route('/project_item_list/<project_id>')
+def project_item_list(project_id:int):
+    keyword = request.args.get('k', '', type=str)
+    page = request.args.get('p', 1, type=int)
+    per_page = request.args.get('pp', 15, type=int)
+    project_data= SourceDataService.get_project_by_id(project_id)
+    project_items, total_count = SourceDataService.get_project_item_list_by_project_paginated(project_id, page, per_page, keyword)
+    return render_template('project_item_list.html', project=project_data, project_items=project_items, keyword=keyword, page=page, per_page=per_page, total_count=total_count)
+
+@project.route('/save_project', methods=['POST'])
+def save_project():
+    project_id = request.form.get('id')
+    project_no = request.form.get('project_no')
+    if not project_no:
+        return jsonify({'success': False, 'error': 'ProjectNo不能为空'}), 400
+    project_name = request.form.get('project_name')
+    standard_version = request.form.get('standard_version')
+    work_catalog = request.form.get('work_catalog')
+    work_content = request.form.get('work_content')
+    delete_old_data = request.form.get('delete_old_data') == 'true'
+    # 处理多文件上传
+    project_files = request.files.getlist('project_files')
+    project_data = None
+    if project_id:
+        project_data = SourceDataService.get_project_by_id(int(project_id))
+    if len(project_files)<=0 and project_id and delete_old_data:
+        return jsonify({'success': False, 'error': '请上传项目数据文件'}), 400
+    elif project_files:
+        try:
+            base_path = os.path.abspath(utils.get_config_value("file.source_path","./temp_files"))
+            if not os.path.exists(base_path):
+                os.makedirs(base_path)
+            # 如果选择删除旧数据且目录存在,则删除目录下的所有文件
+            if delete_old_data and project_data :
+                del_path = os.path.join(base_path, f"project/{project_data.project_no}")
+                if os.path.exists(del_path):
+                    for filename in os.listdir(del_path):
+                        file_path = os.path.join(del_path, filename)
+                        try:
+                            if os.path.isfile(file_path):
+                                os.remove(file_path)
+                        except Exception as e:
+                            return jsonify({'success': False, 'error': f'删除旧文件失败:{str(e)}'}), 500
+            path = os.path.join(base_path, f"project/{project_no}")
+            if not os.path.exists(path):
+                os.makedirs(path)
+            for project_file in project_files:
+                filename = project_file.filename
+                if filename and '.' in filename:
+                    file_ext = filename.rsplit('.', 1)[1].lower()
+                    if file_ext not in {'xlsx', 'xls', 'csv'}:
+                        return jsonify({'success': False, 'error': f'不支持的文件格式:{filename},请上传xlsx,xls,csv文件'}), 400
+                    file_path = os.path.join(path, filename)
+                    project_file.save(file_path)
+                    
+        except PermissionError:
+            return jsonify({'success': False, 'error': '文件保存失败:权限不足,请确保应用程序具有写入权限'}), 403
+        except Exception as e:
+            return jsonify({'success': False, 'error': f'文件保存失败:{str(e)}'}), 500
+    elif not project_id:
+        return jsonify({'success': False, 'error': '请上传项目数据文件'}), 400
+
+    if project_data:
+        project_data.project_no = project_no
+        project_data.project_name = project_name
+        project_data.standard_version = standard_version
+        project_data.work_catalog = work_catalog
+        project_data.work_content = work_content
+        SourceDataService.update_project(project_data)
+    else:
+        project_data = ProjectModel(project_no=project_no,
+                                    project_name=project_name,
+                                    standard_version=standard_version,
+                                    work_catalog=work_catalog,
+                                    work_content=work_content,
+                                    status=0)
+        SourceDataService.add_project(project_data)
+    return jsonify({'success': True})
+
+@project.route('/update_project', methods=['POST'])
+def update_project():
+    req = request.get_json()
+    project_id = req.get('id')
+    project_no = req.get('project_no')
+    project_name= req.get('project_name')
+    standard_version= req.get('standard_version')
+    work_catalog = req.get('work_catalog')
+    work_content = req.get('work_content')
+    project_data = SourceDataService.get_project_by_id(project_id)
+    if not project_data:
+        return jsonify({'success': False, 'error': '项目不存在'}), 404
+    data= ProjectModel(project_id=project_id,
+                       project_no=project_no,
+                       project_name=project_name,
+                       standard_version=standard_version,
+                       work_catalog=work_catalog,
+                       work_content=work_content,
+                       status=project_data.status)
+    SourceDataService.update_project(data)
+    return jsonify({'success': True})
+
+@project.route('/start_project_task/<project_id>', methods=['POST'])
+def start_project_task(project_id:int):
+    result, err_msg = SourceDataService.start_task(project_id)
+    return jsonify({'success': result, 'error': err_msg})
+
+@project.route('/process_project/<project_id>', methods=['POST'])
+def process_project(project_id:int):
+    result, err_msg = SourceDataService.process_project(project_id)
+    return jsonify({'success': result, 'error': err_msg})
+@project.route('/send_project/<project_id>', methods=['POST'])
+def send_project(project_id:int):
+    result, err_msg = SourceDataService.start_send_data(project_id)
+    return jsonify({'success': result, 'error': err_msg})
+@project.route('/delete_project/<project_id>', methods=['POST'])
+def delete_project(project_id:int):
+    result, err_msg =  SourceDataService.delete_project(project_id)
+    return jsonify({'success': result, 'error': err_msg})
+
+@project.route('/add_project_item', methods=['POST'])
+def add_project_item():
+    data = request.get_json()
+    project_id = data.get('project_id')
+    device_name = data.get('device_name')
+    device_model = data.get('device_model')
+    device_unit = data.get('device_unit')
+    device_count = data.get('device_count')
+    standard_no = data.get('standard_no')
+    new_item = ProjectItemModel(project_id=project_id, device_name=device_name, device_model=device_model, device_unit=device_unit, standard_no=standard_no)
+    item_id = SourceDataService.add_project_item(new_item)
+    return jsonify({'success': True, 'id': item_id})
+
+@project.route('/update_data_item', methods=['POST'])
+def update_data_item():
+    data = request.get_json()
+    item_id = data.get('id')
+    if not item_id:
+        return jsonify({'success': False, 'error': 'ID 不能为空'}), 400
+    device_name = data.get('device_name')
+    device_model = data.get('device_model')
+    device_unit = data.get('device_unit')
+    device_count = data.get('device_count')
+    standard_no = data.get('standard_no')
+    # if not standard_no:
+    #     return jsonify({'success': False, 'error': 'Standard No is missing'}), 400
+    item =ProjectItemModel(item_id=item_id,project_id=project_id, device_name=device_name, device_model=device_model, device_unit=device_unit, standard_no=standard_no)
+    SourceDataService.update_project_item(item)
+    return jsonify({'success': True})
+
+@project.route('/delete_project_item/<item_id>', methods=['POST'])
+def delete_project_item_route(item_id):
+    project_item = SourceDataService.get_project_item_by_id(item_id)
+    SourceDataService.delete_project_item(item_id)
+    return redirect(url_for('project.project_item_list', project_no=project_item.project_id))
+
+
+

+ 0 - 141
SourceCode/DataMiddleware/app/ui/source_data_services.py

@@ -1,141 +0,0 @@
-import threading
-
-from models.source_data import SourceData, SourceItemData
-from stores.mysql_store import MysqlStore
-import data_collect,data_process,data_send
-
-
-class SourceDataService:
-
-    def __init__(self):
-        self._store = MysqlStore()
-
-    def get_all_source_data_paginated(self, page: int, per_page: int, keyword: str = None, status: int = None) ->(list[SourceData], int):
-        source_data_list, total_count = self._store.query_source_data_all_paginated(page, per_page, keyword, status)
-        return source_data_list, total_count
-
-    def get_all_source_data(self) ->list[SourceData]:
-        source_data_list = self._store.query_source_data_all()
-        return source_data_list
-
-    def get_source_data_by_project_no(self,project_no:str) ->SourceData:
-        source_data = self._store.query_source_data(project_no)
-        return source_data
-
-    def add_source_data(self,source_data:SourceData):
-        self._store.insert_source_data(source_data)
-    def update_source_data(self,source_data:SourceData):
-        self._store.update_source_data(source_data)
-
-    def start_collect_data(self, project_no:str)->(bool,str):
-        data = self._store.query_source_data(project_no)
-        if data:
-            if data.status == 21:
-                return False, '项目正采集数据中'
-            if data.status == 31:
-                return False, '项目已采集数据'
-            if data.status == 22:
-                return False, '项目正在分析处理中'
-            if data.status == 32:
-                return False, '项目分析处理完成'
-            data.status = 21
-            self._store.update_source_data(data)
-            #TODO 启动采集数据
-            thread = threading.Thread(target=self._collect_project, args=(data,))
-            thread.start()
-            return True, ''
-        else:
-            return False, '项目不存在'
-
-    def _collect_project(self,project:SourceData):
-        if  data_collect.collect_data(project.project_no):
-            project.status = 31
-            self._store.update_source_data(project)
-        else:
-            project.status = 11
-            self._store.update_source_data(project)
-    def start_process_data(self, project_no: str) -> (bool, str):
-        data = self._store.query_source_data(project_no)
-        if data:
-            if data.status == 0:
-                return False, '项目还未采集数据'
-            if data.status == 21:
-                return False, '项目正在采集数据中'
-            if data.status == 22:
-                return False, '项目正在分析处理中'
-            if data.status == 32:
-                return False, '项目分析处理完成'
-            data.status = 22
-            self._store.update_source_data(data)
-            thread = threading.Thread(target = self._process_and_send_project, args=(data,))
-            thread.start()
-            return True, ''
-        else:
-            return False, '项目不存在'
-
-    def _process_and_send_project(self, project: SourceData) :
-        # 启动分析处理
-        if data_process.process_project(project.project_no):
-            # 更新远程数据
-            data_send.send_project_data(project.project_no)
-            project.status = 32
-            self._store.update_source_data(project)
-            return True, ''
-        else:
-            project.status = 12
-            self._store.update_source_data(project)
-
-    def delete_source_data(self,project_no:str):
-        source_data = self._store.query_source_data(project_no)
-        if source_data:
-            self._store.delete_source_data(project_no)
-            return True
-        else:
-            return False
-
-    def get_source_item_data_list_by_project_paginated(self, project_no: str, page: int, per_page: int, keyword: str = None) -> (list[SourceItemData], int):
-        return self._store.query_source_item_data_list_by_project_paginated(project_no, page, per_page, keyword)
-    def get_source_item_list_by_project_no(self, project_no:str) ->list[SourceItemData]:
-        return  self._store.query_source_item_data_list_by_project(project_no)
-
-    def get_source_item_by_id(self, item_id: int):
-        return self._store.query_source_item_data_by_id(item_id)
-
-    def get_source_item(self, project_no:str, device_name:str, device_model:str):
-        return self._store.query_source_item_data(project_no, device_name, device_model)
-
-    def add_source_item(self,item:SourceItemData):
-        source_item = self._store.query_source_item_data(item.project_no, item.device_name, item.device_model)
-        if source_item:
-            return False
-        else:
-            project = self._store.query_source_data(item.project_no)
-            source_item = SourceItemData(
-                project_no=item.project_no,
-                device_name=item.device_name,
-                device_model=item.device_model,
-                device_unit=item.device_unit,
-                standard_version=project.standard_version,
-                standard_no=item.standard_no,
-            )
-            self._store.insert_source_item_data(source_item)
-            return True
-    def update_source_item(self, item:SourceItemData):
-        source_item = self._store.query_source_item_data_by_id(item.id)
-        if source_item:
-            source_item.standard_no = item.standard_no
-            self._store.update_source_item_data(source_item)
-            #TODO 更新标准数据库
-
-            #TODO 更新远程数据
-
-            return True
-        else:
-            return False
-    def delete_source_item(self,item_id:int):
-        source_item = self._store.query_source_item_data_by_id(item_id)
-        if source_item:
-            self._store.delete_source_item_data_by_id(source_item.id)
-            return True
-        else:
-            return False

+ 0 - 106
SourceCode/DataMiddleware/app/ui/source_data_views.py

@@ -1,106 +0,0 @@
-from flask import render_template, url_for, redirect, request, jsonify
-from flask import Blueprint
-import os
-
-from .source_data_services import SourceDataService
-from models.source_data import SourceData,SourceItemData
-
-SourceDataService = SourceDataService()  # 确保 SourceDataService 是一个实例
-
-# 创建一个名为 'source_data' 的蓝图
-source_data = Blueprint('source_data', __name__, template_folder='templates/source_data')
-
-@source_data.route('/')
-def index():
-    return "Hello, World!"
-
-@source_data.route('/source_data_list')
-def source_data_list():
-    keyword = request.args.get('k', '', type=str)
-    page = request.args.get('p', 1, type=int)
-    per_page = request.args.get('pp', 15, type=int)
-    status = request.args.get('s',-1, type=int)
-    data_list, total_count = SourceDataService.get_all_source_data_paginated(page, per_page, keyword,None if status==-1 else status)  # 传递 status 参数
-    return render_template('source_data_list.html', source_data_list=data_list, keyword=keyword, page=page, per_page=per_page, total_count=total_count, status=status)  # 传递 status 参数到模板
-
-@source_data.route('/source_item_list/<project_no>')
-def source_item_list(project_no):
-    keyword = request.args.get('k', '', type=str)
-    page = request.args.get('p', 1, type=int)
-    per_page = request.args.get('pp', 15, type=int)
-    source_data = SourceDataService.get_source_data_by_project_no(project_no)
-    source_items, total_count = SourceDataService.get_source_item_data_list_by_project_paginated(project_no, page, per_page, keyword)
-    return render_template('source_item_list.html', source_data=source_data, source_items=source_items, keyword=keyword, page=page, per_page=per_page, total_count=total_count)
-
-@source_data.route('/add_source_data', methods=['POST'])
-def add_source_data():
-    data = request.get_json()
-    project_no = data.get('project_no')
-    if not project_no:
-        return jsonify({'success': False, 'error': 'Project No 不能为空'}), 400
-    project_name = data.get('project_name')
-    standard_version = data.get('standard_version')
-    source_data= SourceData(project_no=project_no, project_name=project_name, standard_version=standard_version)
-    SourceDataService.add_source_data(source_data)
-    return jsonify({'success': True})
-
-@source_data.route('/update_source_data', methods=['POST'])
-def update_source_data():
-    data = request.get_json()
-    project_no = data.get('project_no')
-    if not project_no:
-        return jsonify({'success': False, 'error': 'Project No 不能为空'}), 400
-    project_name= data.get('project_name')
-    standard_version= data.get('standard_version')
-    source_data= SourceData(project_no=project_no, project_name=project_name, standard_version=standard_version)
-    SourceDataService.update_source_data(source_data)
-    return jsonify({'success': True})
-
-@source_data.route('/collect_source_data/<project_no>', methods=['POST'])
-def collect_source_data(project_no):
-    result, err_msg = SourceDataService.start_collect_data(project_no)
-    return jsonify({'success': result, 'error': err_msg})
-
-@source_data.route('/process_source_data/<project_no>', methods=['POST'])
-def process_source_data(project_no):
-    result, err_msg = SourceDataService.start_process_data(project_no)
-    return jsonify({'success': result, 'error': err_msg})
-
-@source_data.route('/delete_source_data/<project_no>', methods=['POST'])
-def delete_source_data(project_no):
-    SourceDataService.delete_source_data(project_no)  # 使用实例方法
-    return redirect(url_for('source_data.source_data_list'))
-
-@source_data.route('/add_source_item', methods=['POST'])
-def add_source_item():
-    data = request.get_json()
-    project_no = data.get('project_no')
-    device_name = data.get('device_name')
-    device_model = data.get('device_model')
-    device_unit = data.get('device_unit')
-    standard_no = data.get('standard_no')
-    new_item = SourceItemData(project_no=project_no, device_name=device_name, device_model=device_model, device_unit=device_unit, standard_no=standard_no)
-    item_id = SourceDataService.add_source_item(new_item)
-    return jsonify({'success': True, 'id': item_id})
-
-@source_data.route('/update_data_item', methods=['POST'])
-def update_data_item():
-    data = request.get_json()
-    item_id = data.get('id')
-    if not item_id:
-        return jsonify({'success': False, 'error': 'ID 不能为空'}), 400
-    standard_no = data.get('standard_no')
-    if not standard_no:
-        return jsonify({'success': False, 'error': 'Standard No is missing'}), 400
-    item = SourceItemData(item_id=int(item_id), standard_no=standard_no)
-    SourceDataService.update_source_item(item)
-    return jsonify({'success': True})
-
-@source_data.route('/delete_source_item/<item_id>', methods=['POST'])
-def delete_source_item_route(item_id):
-    source_item = SourceDataService.get_source_item_by_id(item_id)
-    SourceDataService.delete_source_item(item_id)
-    return redirect(url_for('source_data.source_item_list', project_no=source_item.project_no))
-
-
-

+ 143 - 0
SourceCode/DataMiddleware/app/ui/static/project_item_list.js

@@ -0,0 +1,143 @@
+function addNewItem(project_id) {
+	const table = document.querySelector('table.table tbody')
+	const rows = table.querySelectorAll('tr.edit-mode')
+	rows.forEach((row) => row.classList.remove('edit-mode'))
+
+	const newRow = document.createElement('tr')
+	newRow.classList.add('edit-mode')
+	newRow.innerHTML = `
+        <td class="editable name"><span class="edit"><input type="text" class="form-control" placeholder="名称"> </span></td>
+        <td class="editable model"><span class="edit"><input type="text" class="form-control" placeholder="规格型号"> </span></td>
+        <td class="editable count"><span class="edit"><input type="text" class="form-control" placeholder="数量"> </span></td>
+        <td class="editable unit"><span class="edit"><input type="text" class="form-control" placeholder="单位"> </span></td>
+        <td class="editable standard_no"><span class="edit"><input type="text" class="form-control" placeholder="标准编号"></span>
+        </td>
+        <td class="editable tool">
+            <span class="show"></span>
+            <span class="edit">
+                <button class="btn btn-success" onclick="saveItemCreate(this.parentNode.parentNode.parentNode,'${project_id}')">确定</button>
+                <button class="btn btn-warning" onclick="cancelNewChanges(this.parentNode.parentNode.parentNode)">取消</button>
+            </span>
+        </td>
+    `
+	table.insertBefore(newRow, table.firstChild)
+}
+
+function saveItemCreate(row, project_id) {
+	const deviceName = row.querySelector('td.name input').value
+	const deviceModel = row.querySelector('td.model input').value
+	const deviceUnit = row.querySelector('td.unit input').value
+	const deviceCount = row.querySelector('td.count input').value
+	const standardNo = row.querySelector('td.standard_no input').value
+	if (deviceName === '') {
+		alert('名称不能为空')
+		return
+	}
+	if (deviceModel === '') {
+		alert('规格型号不能为空')
+		return
+	}
+	if (deviceCount === '') {
+		alert('数量不能为空')
+		return
+	}
+	if (deviceUnit === '') {
+		alert('单位不能为空')
+		return
+	}
+	// if (standardNo === '') {
+	//     alert('标准编号不能为空');
+	//     return;
+	// }
+
+	fetch(`/add_project_item`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+		},
+		body: JSON.stringify({ project_id: project_id, device_name: deviceName, device_model: deviceModel, device_unit: deviceUnit, device_count: deviceCount, standard_no: standardNo }),
+	})
+		.then((response) => response.json())
+		.then((data) => {
+			if (data.success) {
+				alert('添加成功')
+				window.location.href = `/project_item_list/${project_id}`
+			} else {
+				alert('添加失败:' + data.error)
+			}
+		})
+		.catch((error) => {
+			console.error('添加失败:', error)
+			alert('添加失败')
+		})
+}
+
+function saveItemEdit(row, itemId) {
+	const deviceName = row.querySelector('td.name input').value
+	const deviceModel = row.querySelector('td.model input').value
+	const standardNo = row.querySelector('.standard_no input').value
+	const deviceUnit = row.querySelector('.unit input').value
+	const deviceCount = row.querySelector('.count input').value
+	// if (standardNo === '') {
+	//     alert('标准编号不能为空');
+	//     return;
+	// }
+	if (deviceCount === '') {
+		alert('数量不能为空')
+		return
+	}
+	if (deviceUnit === '') {
+		alert('单位不能为空')
+		return
+	}
+	fetch(`/update_data_item`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+		},
+		body: JSON.stringify({ id: itemId, device_name: deviceName, device_model: deviceModel, standard_no: standardNo, device_count: deviceCount, device_unit: deviceUnit }),
+	})
+		.then((response) => response.json())
+		.then((data) => {
+			if (data.success) {
+				alert('保存成功')
+				row.classList.remove('edit-mode')
+				row.querySelector('.standard_no .show').textContent = standardNo
+				// window.location.reload();
+			} else {
+				alert('保存失败:' + data.error)
+			}
+		})
+		.catch((error) => {
+			console.error('保存失败:', error)
+			alert('保存失败')
+		})
+}
+
+function confirmItemDelete(itemId) {
+	if (confirm('确定要删除该设备吗?')) {
+		fetch(`/delete_project_item/${itemId}`, {
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+		})
+			.then((response) => response.json())
+			.then((data) => {
+				if (data.success) {
+					alert('删除成功')
+					// 删除对应的行
+					const row = document.querySelector(`tr[data-id="${itemId}"]`)
+					if (row) {
+						row.remove()
+					}
+				} else {
+					alert('删除失败:' + data.error)
+				}
+			})
+			.catch((error) => {
+				console.error('删除失败:', error)
+				alert('删除失败')
+			})
+	}
+}

+ 212 - 0
SourceCode/DataMiddleware/app/ui/static/project_list.js

@@ -0,0 +1,212 @@
+function addProject() {
+	const editBox = document.querySelector('div.edit-box')
+	const inputs = editBox.querySelectorAll('.form-control')
+	inputs.forEach((input) => {
+		input.value = ''
+	})
+	// 移除已存在的delete_old_data复选框
+	const existingCheckbox = document.getElementById('delete_old_data')
+	if (existingCheckbox) {
+		existingCheckbox.parentNode.remove()
+	}
+	const file = document.getElementById('new_project_file').parentNode
+	file.innerHTML = `<label for="new_project_file">项目数据:</label><input type="file" id="new_project_file" class="form-control" accept=".xlsx,.xls,.csv" multiple>`
+
+	editBox.classList.add('show')
+}
+
+function updateProject(row, id) {
+	document.getElementById('project_id').value = id
+	document.getElementById('new_project_no').value = row.querySelector('.project_no .form-control').value
+	document.getElementById('new_project_name').value = row.querySelector('.project_name .form-control').value
+	const file = document.getElementById('new_project_file').parentNode
+	file.innerHTML = `<label for="new_project_file">项目数据:</label><input type="file" id="new_project_file" class="form-control" accept=".xlsx,.xls,.csv" multiple>`
+	// 移除已存在的delete_old_data复选框
+	const existingCheckbox = document.getElementById('delete_old_data')
+	if (existingCheckbox) {
+		existingCheckbox.parentNode.remove()
+	}
+	const checkboxDiv = document.createElement('div')
+	checkboxDiv.innerHTML = `<label for="delete_old_data">删除原数据:</label><input type="checkbox" id="delete_old_data" class="form-control" style="width: auto;flex: 0;">`
+	file.parentNode.insertBefore(checkboxDiv, file.nextSibling)
+	document.getElementById('new_work_catalog').value = row.querySelector('.work_catalog .form-control').value
+	document.getElementById('new_work_content').value = row.querySelector('.work_content .form-control').value
+	document.getElementById('new_standard_version').value = row.querySelector('.standard_version .form-control').value
+	const editBox = document.querySelector('div.edit-box')
+	editBox.classList.add('show')
+}
+function saveProject() {
+	const id = document.getElementById('project_id').value
+	const no = document.getElementById('new_project_no').value
+	const name = document.getElementById('new_project_name').value
+	const files = document.getElementById('new_project_file').files
+	const catalog = document.getElementById('new_work_catalog').value
+	const content = document.getElementById('new_work_content').value
+	const version = document.getElementById('new_standard_version').value
+
+	if (no === '') {
+		alert('项目编号不能为空')
+		return
+	}
+	if (name === '') {
+		alert('项目名称不能为空')
+		return
+	}
+	if (files.length === 0 && !id) {
+		alert('请选择项目数据文件')
+		return
+	}
+	if (catalog === '') {
+		alert('工作目录不能为空')
+		return
+	}
+	if (content === '') {
+		alert('工作内容不能为空')
+		return
+	}
+
+	const formData = new FormData()
+	formData.append('id', id)
+	formData.append('project_no', no)
+	formData.append('project_name', name)
+	for (let i = 0; i < files.length; i++) {
+		formData.append('project_files', files[i])
+	}
+	formData.append('work_catalog', catalog)
+	formData.append('work_content', content)
+	formData.append('standard_version', version)
+	if (document.getElementById('delete_old_data')){
+		formData.append('delete_old_data', document.getElementById('delete_old_data').checked)
+	}
+
+	fetch('/save_project', {
+		method: 'POST',
+		body: formData,
+	})
+		.then((response) => response.json())
+		.then((data) => {
+			if (data.success) {
+				alert('保存成功')
+				window.location.reload()
+			} else {
+				alert('保存失败:' + data.error)
+			}
+		})
+		.catch((error) => {
+			console.error('保存失败:', error)
+			alert('保存失败')
+		})
+}
+function cancelAdd() {
+	const editBox = document.querySelector('div.edit-box')
+	editBox.classList.remove('show')
+}
+
+function saveProjectUpdate(row, id) {
+	const no = row.querySelector('.project_no .form-control').value
+	const name = row.querySelector('.project_name .form-control').value
+	const work_catalog = row.querySelector('.work_catalog .form-control').value
+	const work_content = row.querySelector('.work_content .form-control').value
+	const version = row.querySelector('.standard_version .form-control').value
+	if (no === '') {
+		alert('名称不能为空')
+		return
+	}
+	if (name === '') {
+		alert('名称不能为空')
+		return
+	}
+	if (version === '') {
+		alert('版本不能为空')
+		return
+	}
+	fetch('/update_project', {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+		},
+		body: JSON.stringify({
+			id: id,
+			project_no: no,
+			project_name: name,
+			work_catalog: work_catalog,
+			work_content: work_content,
+			standard_version: version,
+		}),
+	})
+		.then((response) => response.json())
+		.then((data) => {
+			if (data.success) {
+				alert('更新成功')
+				window.location.reload()
+			} else {
+				alert('更新失败:' + data.error)
+			}
+		})
+		.catch((error) => {
+			console.error('更新失败:', error)
+			alert('更新失败')
+		})
+}
+
+function confirmStartTask(id){
+	_confirm('确定要开始任务吗?',`/start_project_task/${id}`)
+}
+
+function confirmReStartTask(id){
+	_confirm('确定要重新采集数据吗?',`/start_project_task/${id}`)
+}
+
+function confirmReProcessData(id){
+	_confirm('确定要重新处理数据吗?',`/process_project/${id}`)
+}
+
+function confirmSendData(id) {
+	_confirm('确定要开始上传数据吗?',`/send_project/${id}`)
+}
+
+function _confirm(text,url){
+	if (confirm(text)) {
+		fetch(url, {
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+		})
+			.then((response) => response.json())
+			.then((data) => {
+				if (data.success) {
+					alert('操作成功')
+					window.location.reload()
+				} else {
+					alert('操作失败:' + data.error)
+				}
+			})
+			.catch((error) => {})
+	}
+}
+
+
+function confirmProjectDelete(id) {
+	if (confirm('确定要删除该项目吗?')) {
+		fetch(`/delete_project/${id}`, {
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+		})
+			.then((response) => response.json())
+			.then((data) => {
+				if (data.success) {
+					alert('删除成功')
+					window.location.reload()
+				} else {
+					alert('删除失败:' + data.error)
+				}
+			})
+			.catch((error) => {
+				console.error('删除失败:', error)
+				alert('删除失败')
+			})
+	}
+}

+ 0 - 160
SourceCode/DataMiddleware/app/ui/static/source_data_list.js

@@ -1,160 +0,0 @@
-function addNewData(){
-    const table = document.querySelector('table.table tbody');
-    const rows = table.querySelectorAll('tr.edit-mode');
-    rows.forEach(row => row.classList.remove('edit-mode'));
-    const newRow = document.createElement('tr');
-    newRow.classList.add('edit-mode');
-    newRow.innerHTML = `
-        <td class="editable project_no"><span class="edit"><input type="text" class="form-control" placeholder="项目编号"></span></td>
-        <td class="editable project_name"><span class="edit"><input type="text" class="form-control" placeholder="项目名称"></span></td>
-        <td class="editable standard_version"><span class="edit"><select class="form-control"><option value="1" {% if source_data.standard_version == 1 %}selected{% endif %}>旧版</option><option value="2" {% if source_data.standard_version == 2 %}selected{% endif %}>新版</option></select></td>
-        <td class="editable "><span class="edit">新建</span></td>
-        <td class="editable tool">
-            <span class="show">
-                
-            </span>
-            <span class="edit">
-                <button class="btn btn-success" onclick="saveDataCreate(this.parentNode.parentNode.parentNode)">确定</button>
-                <button class="btn btn-warning" onclick="cancelNewChanges(this.parentNode.parentNode.parentNode)">取消</button>
-            </span>
-        </td>
-    `;
-    table.insertBefore(newRow, table.firstChild);
-
-}
-function saveDataCreate(row){
-    const project_no = row.querySelector('td.project_no .form-control').value;
-    if (project_no === '') {
-        alert('项目编号')
-    }
-    const name = row.querySelector('td.project_name .form-control').value;
-    if (name === '') {
-        alert('名称不能为空');
-        return;
-    }
-    version = row.querySelector('td.standard_version .form-control').value;
-    if (version === '') {
-        alert('版本号不能为空');
-        return;
-    }
-    fetch(`/add_source_data`, {
-        method: 'POST',
-        headers: {
-            'Content-Type': 'application/json'
-        },
-        body: JSON.stringify({project_no: project_no, project_name: name,standard_version:version })
-    }).then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('添加成功');
-                window.location.href = '/source_data_list';
-            }else{
-                alert('添加失败:'+data.error);
-            }
-        });
-}
-
-function saveDataEdit(row,project_no){
-    const name = row.querySelector('.project_name .form-control').value;
-    const version = row.querySelector('.standard_version .form-control').value;
-    if (name === '') {
-        alert('名称不能为空');
-        return;
-    }
-    if (version === '') {
-        alert('版本号不能为空');
-        return;
-    }
-    fetch(`/update_source_data`, {
-        method: 'POST',
-        headers: {
-            'Content-Type': 'application/json'
-        },
-        body: JSON.stringify({project_no: project_no, project_name: name,standard_version:version })
-    }).then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('保存成功');
-                window.location.reload();
-            } else {
-                alert('保存失败:'+data.error);
-            }
-        })
-        .catch(error => {
-            console.error('保存失败:', error);
-            alert('保存失败');
-        });
-
-}
-
-function confirmCollectData(project_no) {
-    if (confirm('确定要开始采集数据吗?')){
-        fetch(`/collect_source_data/${project_no}`, {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        })
-        .then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('操作成功');
-                window.location.reload();
-            } else {
-                alert('采集失败:'+data.error);
-            }
-        })
-        .catch(error=> {})
-    }
-}
-
-function confirmProcessData(project_no) {
-    if (confirm('确定要开始分析处理数据吗?')){
-        fetch(`/process_source_data/${project_no}`, {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        })
-        .then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('操作成功');
-                window.location.reload();
-            } else {
-                alert('分析处理失败:'+data.error);
-            }
-        })
-        .catch(error=> {})
-    }
-}
-
-
-function confirmDataDelete(project_no) {
-    if (confirm('确定要删除该项目吗?')) {
-        fetch(`/delete_source_data/${project_no}`, {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        })
-        .then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('删除成功');
-                // 删除对应的行
-                const row = document.querySelector(`tr[data-id="${itemId}"]`);
-                if (row) {
-                    row.remove();
-                }
-            } else {
-                alert('删除失败:'+data.error);
-            }
-        })
-        .catch(error => {
-            console.error('删除失败:', error);
-            alert('删除失败');
-        });
-    }
-}
-

+ 0 - 125
SourceCode/DataMiddleware/app/ui/static/source_item_list.js

@@ -1,125 +0,0 @@
-function addNewItem(project_no) {
-    const table = document.querySelector('table.table tbody');
-    const rows = table.querySelectorAll('tr.edit-mode');
-    rows.forEach(row => row.classList.remove('edit-mode'));
-
-    const newRow = document.createElement('tr');
-    newRow.classList.add('edit-mode');
-    newRow.innerHTML = `
-        <td class="editable name"><span class="edit"><input type="text" class="form-control" placeholder="名称"> </span></td>
-        <td class="editable model"><span class="edit"><input type="text" class="form-control" placeholder="规格型号"> </span></td>
-        <td class="editable unit"><span class="edit"><input type="text" class="form-control" placeholder="单位"> </span></td>
-        <td class="editable standard_no"><span class="edit"><input type="text" class="form-control" placeholder="标准编号"></span>
-        </td>
-        <td class="editable tool">
-            <span class="show"></span>
-            <span class="edit">
-                <button class="btn btn-success" onclick="saveItemCreate(this.parentNode.parentNode.parentNode,'${project_no}')">确定</button>
-                <button class="btn btn-warning" onclick="cancelNewChanges(this.parentNode.parentNode.parentNode)">取消</button>
-            </span>
-        </td>
-    `;
-    table.insertBefore(newRow, table.firstChild);
-}
-
-function saveItemCreate(row,project_no) {
-    const deviceName = row.querySelector('td.name input').value;
-    const deviceModel = row.querySelector('td.model input').value;
-    const deviceUnit = row.querySelector('td.unit input').value;
-    const standardNo = row.querySelector('td.standard_no input').value;
-    if (deviceName === '') {
-        alert('名称不能为空');
-        return;
-    }
-    if (deviceModel === '') {
-        alert('规格型号不能为空');
-        return;
-    }
-    if (deviceUnit === '') {
-        alert('单位不能为空');
-        return;
-    }
-    if (standardNo === '') {
-        alert('标准编号不能为空');
-        return;
-    }
-
-    fetch(`/add_source_item`, {
-        method: 'POST',
-        headers: {
-            'Content-Type': 'application/json'
-        },
-        body: JSON.stringify({project_no:project_no,device_name: deviceName, device_model: deviceModel, device_unit: deviceUnit, standard_no: standardNo })
-    }).then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('添加成功');
-                window.location.href = `/source_item_list/${project_no}`;
-            } else {
-                alert('添加失败:'+data.error);
-            }
-        })
-        .catch(error => {
-            console.error('添加失败:', error);
-            alert('添加失败');
-        });
-}
-
-function saveItemEdit(row, itemId) {
-    const standardNo = row.querySelector('.standard_no input').value;
-    if (standardNo === '') {
-        alert('标准编号不能为空');
-        return;
-    }
-    fetch(`/update_data_item`, {
-        method: 'POST',
-        headers: {
-            'Content-Type': 'application/json'
-        },
-        body: JSON.stringify({id: itemId, standard_no: standardNo })
-    }).then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('保存成功');
-                row.classList.remove('edit-mode');
-                row.querySelector('.standard_no .show').textContent = standardNo;
-                // window.location.reload();
-            } else {
-                alert('保存失败:'+data.error);
-            }
-        })
-        .catch(error => {
-            console.error('保存失败:', error);
-            alert('保存失败');
-        });
-}
-
-
-function confirmItemDelete(itemId) {
-    if (confirm('确定要删除该设备吗?')) {
-        fetch(`/delete_source_item/${itemId}`, {
-            method: 'POST',
-            headers: {
-                'Content-Type': 'application/json'
-            }
-        })
-        .then(response => response.json())
-        .then(data => {
-            if (data.success) {
-                alert('删除成功');
-                // 删除对应的行
-                const row = document.querySelector(`tr[data-id="${itemId}"]`);
-                if (row) {
-                    row.remove();
-                }
-            } else {
-                alert('删除失败:'+data.error);
-            }
-        })
-        .catch(error => {
-            console.error('删除失败:', error);
-            alert('删除失败');
-        });
-    }
-}
-

+ 223 - 150
SourceCode/DataMiddleware/app/ui/static/styles.css

@@ -1,183 +1,256 @@
 /* styles.css */
-h1,h2,h3,h4,h5,h6{
-    margin: 0;
+* {
+	margin: 0;
+	padding: 0;
+	box-sizing: border-box;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+	margin: 0;
 }
 .btn {
-    margin: 0 5px;
-    padding: 4px 15px;
-    background-color: #f2f2f2;
-    border: 1px solid #ccc;
-    cursor: pointer;
-    border-radius: 4px;
-    outline: none;
-}
-.btn-large{
-    padding: 6px 18px;
-}
-.btn:hover{
-    background-image: linear-gradient(to bottom, rgba(0,0,0,0.05), rgba(0,0,0,0.05));
-}
-.btn.btn-info{
-    background-color: #2196F3;
-    color: white;
-    border: 1px solid #2196F3;
-}
-.btn.btn-warning{
-    background-color: #ff9800;
-    color: white;
-    border: 1px solid #ff9800;
-}
-.btn.btn-danger{
-    background-color: #f44336;
-    color: white;
-    border: 1px solid #f44336;
-}
-.btn.btn-success{
-    background-color: #4CAF50;
-    color: white;
-    border: 1px solid #4CAF50;
-}
-.label{
-    margin: 0 5px;
-    padding: 3px 10px;
-    font-size: 12px;
-    color: #666;
-    background-color: #f2f2f2;
-    border: 1px solid #ccc;
-    border-radius: 15px;
-}
-.label.label-info{
-    background-color: #2196F3;
-    color: white;
-    border: 1px solid #2196F3;
-}
-.label.label-warning{
-    background-color: #ff9800;
-    color: white;
-    border: 1px solid #ff9800;
-}
-.label.label-danger{
-    background-color: #f44336;
-    color: white;
-    border: 1px solid #f44336;
-}
-.label.label-success{
-    background-color: #4CAF50;
-    color: white;
-    border: 1px solid #4CAF50;
-}
-.form-control{
-    width:  240px;
-    padding: 6px 10px;
-    outline: none;
-    border: 1px solid #2196F3;
-    border-radius: 3px;
-    margin: 0 5px;
-}
-.box{
-    margin: 20px 20px;
-}
-.box_header{
-    margin-bottom: 10px;
-    display: flex;
-    justify-content: space-between;
-    background-color: #f9f9f9;
-    padding: 10px;
-    border: 1px solid #ddd;
-    border-radius: 4px;
-}
-.box_header dl{
-    display: flex;
-    align-items: center;
-    margin: 0 10px;
-}
-.box_header dl dt{
-    font-weight: bolder;
-    color: #333;
-}
-.box_header dl dd{
-    margin-left: 10px;
-    color: #555;
+	margin: 0 5px;
+	padding: 4px 15px;
+	background-color: #f2f2f2;
+	border: 1px solid #ccc;
+	cursor: pointer;
+	border-radius: 4px;
+	outline: none;
+}
+.btn-large {
+	padding: 6px 18px;
+}
+.btn:hover {
+	background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.05));
+}
+.btn.btn-info {
+	background-color: #2196f3;
+	color: white;
+	border: 1px solid #2196f3;
+}
+.btn.btn-warning {
+	background-color: #ff9800;
+	color: white;
+	border: 1px solid #ff9800;
+}
+.btn.btn-danger {
+	background-color: #f44336;
+	color: white;
+	border: 1px solid #f44336;
+}
+.btn.btn-success {
+	background-color: #4caf50;
+	color: white;
+	border: 1px solid #4caf50;
+}
+.label {
+	margin: 0 5px;
+	padding: 3px 10px;
+	font-size: 12px;
+	color: #666;
+	background-color: #f2f2f2;
+	border: 1px solid #ccc;
+	border-radius: 15px;
+}
+.label.label-info {
+	background-color: #2196f3;
+	color: white;
+	border: 1px solid #2196f3;
+}
+.label.label-warning {
+	background-color: #ff9800;
+	color: white;
+	border: 1px solid #ff9800;
+}
+.label.label-danger {
+	background-color: #f44336;
+	color: white;
+	border: 1px solid #f44336;
+}
+.label.label-success {
+	background-color: #4caf50;
+	color: white;
+	border: 1px solid #4caf50;
+}
+.form-control {
+	width: 240px;
+	padding: 6px 10px;
+	outline: none;
+	border: 1px solid #2196f3;
+	border-radius: 3px;
+	margin: 0 5px;
+}
+.box {
+	margin: 20px 20px;
+	position: relative;
+}
+.box_header {
+	margin-bottom: 10px;
+	display: flex;
+	justify-content: space-between;
+	background-color: #f9f9f9;
+	padding: 10px;
+	border: 1px solid #ddd;
+	border-radius: 4px;
+}
+.box_header .box_title {
+	font-size: 18px;
+	font-weight: bolder;
+	color: #333;
+	margin-left: 20px;
+}
+.box_header .separator {
+	margin: 0 20px;
 }
 
-.box_body{
-    margin-top: 10px;
+.box_header dl {
+	display: flex;
+	align-items: center;
+	margin: 0 10px;
+}
+.box_header dl dt {
+	font-weight: bolder;
+	color: #333;
+}
+.box_header dl dd {
+	margin-left: 10px;
+	color: #555;
+}
+.search-box .form-select {
+	width: 100px;
+}
+.box_body {
+	margin-top: 10px;
 }
 table {
-    width: 100%;
-    border-collapse: collapse;
+	width: 100%;
+	border-collapse: collapse;
 }
-th, td {
-    border: 1px solid #ddd;
-    padding: 8px;
-    text-align: center;
+th,
+td {
+	border: 1px solid #ddd;
+	padding: 8px;
+	text-align: center;
 }
 th {
-    background-color: #f2f2f2;
+	background-color: #f2f2f2;
 }
 tr:hover {
-    background-color: #f5f5f5;
+	background-color: #f5f5f5;
 }
-   
-td.editable .edit{
-    display: none;
+
+td.editable .edit {
+	display: none;
 }
-.edit-mode .editable .show{
-    display: none;
+.edit-mode .editable .show {
+	display: none;
 }
-.edit-mode .editable .edit{
-    width: 100%;
-    display: flex;
-    justify-content: center;
+.edit-mode .editable .edit {
+	width: 100%;
+	display: flex;
+	justify-content: center;
 }
 .edit-mode .editable .form-control {
-    width: 80%;
-    max-width: 300px;
-    padding: 5px 10px;
-    border: 1px solid #4CAF50;
+	width: 80%;
+	max-width: 300px;
+	padding: 5px 10px;
+	border: 1px solid #4caf50;
 }
 
 .pagination {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 20px 0px 0;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding: 20px 0px 0;
 }
-.pagination .pagination-links{
-    display: flex;
+.pagination .pagination-links {
+	display: flex;
 }
 
 .pagination .page {
-    height: 40px;
-    min-width: 20px;
-    padding: 0 10px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    text-decoration: none;
-    color: black;
-    border: 1px solid #ddd;
-    margin: 0 4px;
-    border-radius: 4px;
-    background-color: #eee;
-}
-.pagination .pagination-links .page.page-link{
-    background-color: white;
-    cursor: pointer;
+	height: 40px;
+	min-width: 40px;
+	padding: 0 10px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	text-decoration: none;
+	color: black;
+	border: 1px solid #ddd;
+	margin: 0 4px;
+	border-radius: 4px;
+	background-color: #eee;
+}
+.pagination .pagination-links .page.page-link {
+	background-color: white;
+	cursor: pointer;
 }
 .pagination .pagination-links .page.page-link:hover {
-    background-color: #ddd;
+	background-color: #ddd;
 }
 
 .pagination .pagination-links .page.page-link.active {
-    background-color: #2196F3;
-    color: white;
-    border: 1px solid #2196F3;
+	background-color: #2196f3;
+	color: white;
+	border: 1px solid #2196f3;
 }
 
 .pagination .pagination-info {
-    font-size: 14px;
-    text-align: center;
-    color: #3c3c3c;
+	font-size: 14px;
+	text-align: center;
+	color: #3c3c3c;
+}
 
-}
+.edit-box {
+	position: absolute;
+	background: #f5f5f5;
+	z-index: 1000;
+	width: 100%;
+	border: 1px solid #ddd;
+	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+	display: none;
+}
+.edit-box.show {
+	display: block;
+	margin-top: -15px;
+}
+
+.edit-box .form-group {
+	display: flex;
+	flex-direction: column;
+	gap: 15px;
+	padding: 15px;
+	width: 60%;
+	margin: 0 auto;
+}
+
+.edit-box .form-group > div {
+	display: flex;
+	align-items: center;
+	gap: 10px;
+}
+
+.edit-box .form-group label {
+	min-width: 80px;
+}
+
+.edit-box .form-group .form-control {
+	flex: 1;
+}
+
+.edit-box .form-group textarea.form-control {
+	height: 60px;
+	resize: vertical;
+}
+
+.edit-box .form-group textarea.form-control.large {
+	height: 80px;
+}
+
+.edit-box .form-group .button-group {
+	justify-content: center;
+}

+ 161 - 0
SourceCode/DataMiddleware/app/ui/templates/project/project_item_list.html

@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="UTF-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+		<title>设备列表</title>
+		<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
+		<style>
+			.box_header .project_info {
+				display: flex;
+				justify-content: left;
+				align-items: center;
+				width: 60%;
+			}
+		</style>
+		<script src="{{ url_for('static', filename='utils.js') }}"></script>
+		<script src="{{ url_for('static', filename='project_item_list.js') }}"></script>
+		<script>
+			function searchData() {
+				var keyword = document.getElementById('search_input').value
+				window.location.href = `{{ url_for('project.project_item_list', project_id=project.id) }}?k=` + keyword
+			}
+			function reSearchData() {
+				window.location.href = `{{ url_for('project.project_item_list', project_id=project.id) }}`
+			}
+		</script>
+	</head>
+	<body>
+		<div class="box">
+			<div class="box_header">
+				<div class="project_info">
+					<button class="btn btn-info btn-large" onclick="goTo(`{{ url_for('project.project_list') }}`)">返回</button>
+					<h3 class="box_title">设备列表</h3>
+					<span class="separator">|</span>
+					<dl>
+						<dt>项目编号:</dt>
+						<dd>{{ project.project_no }}</dd>
+					</dl>
+					<dl>
+						<dt>项目名称:</dt>
+						<dd>{{ project.project_name }}</dd>
+					</dl>
+					<dl>
+						<dt>标准版本:</dt>
+						<dd>
+							{% if project.standard_version == '1' %}
+							<span class="label label-warning">旧版</span>
+							{% elif project.standard_version == '2' %}
+							<span class="label label-info">新版</span>
+							{% endif %}
+						</dd>
+					</dl>
+				</div>
+			</div>
+			<div class="box_header">
+				<div class="btn_box">
+					<button class="btn btn-success btn-large" onclick="addNewItem('{{ project.id}}')">添加</button>
+				</div>
+				<div class="search_box">
+					<input type="text" id="search_input" class="form-control" placeholder="请输入查询关键字" value="{{ keyword }}" />
+					<button type="button" class="btn btn-info btn-large" onclick="searchData()">查询</button>
+					<button type="button" class="btn btn-warning btn-large" onclick="reSearchData()">重置</button>
+				</div>
+			</div>
+
+			<div class="box_body">
+				<table class="table">
+					<thead>
+						<tr>
+							<th width="25%">名称</th>
+							<th>规格型号</th>
+							<th width="200px">数量</th>
+							<th width="8%">单位</th>
+							<th width="15%">定额编号</th>
+							<th width="180px">操作</th>
+						</tr>
+					</thead>
+					<tbody>
+						{% if project_items %} {% for item in project_items %}
+						<tr>
+							<td class="editable name">
+								<span class="show">{{ item.device_name if item.device_name else '-' }}</span>
+								<span class="edit">
+									<input type="text" class="form-control" title="设备名称" value="{{ item.device_name if item.device_name else '' }}" />
+								</span>
+							</td>
+							<td class="editable model">
+								<span class="show">{{ item.device_model if item.device_model else '-' }}</span>
+								<span class="edit">
+									<input type="text" class="form-control" title="设备规格型号" value="{{ item.device_model if item.device_model else '' }}" />
+								</span>
+							</td>
+							<td class="editable count">
+								<span class="show">{{ item.device_count if item.device_count else '-' }}</span>
+								<span class="edit">
+									<input type="text" class="form-control" title="设备数量" value="{{ item.device_count if item.device_count else '' }}" />
+								</span>
+							</td>
+							<td class="editable unit">
+								<span class="show">{{ item.device_unit if item.device_unit else '-' }}</span>
+								<span class="edit">
+									<input type="text" class="form-control" title="设备单位" value="{{ item.device_unit if item.device_unit else '' }}" />
+								</span>
+							</td>
+							<td class="editable standard_no">
+								<span class="show">{{ item.standard_no if item.standard_no else '-' }}</span>
+								<span class="edit">
+									<input type="text" class="form-control" title="设备定额编号" value="{{ item.standard_no if item.standard_no else '' }}" />
+								</span>
+							</td>
+							<td class="editable tool">
+								<span class="show">
+									<button class="btn btn-info" onclick="toggleEditMode(this.parentNode.parentNode.parentNode)">编辑</button>
+									<button class="btn btn-danger" onclick="confirmItemDelete('{{ item.id }}')">删除</button>
+								</span>
+								<span class="edit">
+									<button class="btn btn-success" onclick="saveItemEdit(this.parentNode.parentNode.parentNode, '{{ item.id }}')">保存</button>
+									<button class="btn btn-warning" onclick="cancelChanges(this.parentNode.parentNode.parentNode)">取消</button>
+								</span>
+							</td>
+						</tr>
+						{% endfor %} {% else %}
+						<tr>
+							<td colspan="15">没有找到设备数据</td>
+						</tr>
+						{% endif %}
+					</tbody>
+				</table>
+				<div class="pagination">
+					<div class="pagination-info">
+						{% set total_pages = (total_count|int + per_page|int - 1)//per_page %}
+						<span class="page"> 总共 {{ total_count }} 条数据,每页 {{ per_page }} 条,当前第 {{ page }} 页 / 共 {{ total_pages }} 页 </span>
+					</div>
+					<div class="pagination-links">
+						{% if page > 1 %}
+						<a class="page page-link" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=1, pp=per_page) }}">首页</a>
+						{% endif %} {% if page > 1 %}
+						<a class="page page-link" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=page-1, pp=per_page) }}">上一页</a>
+						{% endif %} {% set start_page = [1, page - 2]|max %} {% set end_page = [total_pages, page + 2]|min %} {% if start_page > 1 %}
+						<a class="page page-link" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=1, pp=per_page) }}">1</a>
+						{% if start_page > 2 %}
+						<span class="page">...</span>
+						{% endif %} {% endif %} {% for p in range(start_page, end_page + 1) %} {% if p == page %}
+						<a class="page page-link active" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=p, pp=per_page) }}">{{ p }}</a>
+						{% else %}
+						<a class="page page-link" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=p, pp=per_page) }}">{{ p }}</a>
+						{% endif %} {% endfor %} {% if end_page < total_pages %} {% if end_page < total_pages - 1 %}
+						<span class="page">...</span>
+						{% endif %}
+						<a class="page page-link" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=total_pages, pp=per_page) }}">{{ total_pages }}</a>
+						{% endif %} {% if page < total_pages %}
+						<a class="page page-link" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=page+1, pp=per_page) }}">下一页</a>
+						{% endif %} {% if page < total_pages %}
+						<a class="page page-link" href="{{ url_for('project.project_item_list', project_id=project.id, k=keyword, p=total_pages, pp=per_page) }}">末页</a>
+						{% endif %}
+					</div>
+				</div>
+			</div>
+		</div>
+	</body>
+</html>

+ 249 - 0
SourceCode/DataMiddleware/app/ui/templates/project/project_list.html

@@ -0,0 +1,249 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>项目列表</title>
+    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
+    <script src="{{ url_for('static', filename='utils.js') }}"></script>
+    <script src="{{ url_for('static', filename='project_list.js') }}"></script>
+    <script>
+        function searchData(){
+            var keyword = document.getElementById('search_input').value;
+            var status = document.getElementById('status_select').value;
+            window.location.href = `{{ url_for('project.project_list') }}?k=${keyword}&s=${status}`;
+        }
+        function reSearchData(){
+            window.location.href = `{{ url_for('project.project_list') }}`;
+        }
+    </script>
+</head>
+<body>
+    <div class="box">
+        <div class="box_header">
+            <div><h3 class="box_title">项目列表</h3></div>
+        </div>
+        <div class="box_header">
+            <div class="btn_box">
+                <button type="button" class="btn btn-success btn-large" onclick="addProject()">添加</button>
+            </div>
+            <div class="search_box">
+                <select id="status_select" class="form-control form-select" title="状态选择">
+                    <option value="-1" {% if status == -1 %}selected{% endif %}>所有状态</option>
+                    <option value="0" {% if status == 0 %}selected{% endif %}>新建</option>
+                    <option value="5" {% if status == 5 %}selected{% endif %}>完成</option>
+                     <option value="11" {% if status == 11 %}selected{% endif %}>采集失败</option>
+                    <option value="12" {% if status == 12 %}selected{% endif %}>分析失败</option>
+                    <option value="13" {% if status == 13 %}selected{% endif %}>上传失败</option>
+                     <option value="21" {% if status == 21 %}selected{% endif %}>采集中...</option>
+                    <option value="22" {% if status == 22 %}selected{% endif %}>分析中...</option>
+                    <option value="23" {% if status == 23 %}selected{% endif %}>上传中...</option>
+                    <option value="31" {% if status == 31 %}selected{% endif %}>采集完成</option>
+                    <option value="32" {% if status == 32 %}selected{% endif %}>分析完成</option>
+                    <option value="33" {% if status == 32 %}selected{% endif %}>上传完成</option>
+                </select>
+                <input type="text" id="search_input" class="form-control" placeholder="请输入查询关键字" value="{{ keyword }}">
+                <button type="button" class="btn btn-info btn-large" onclick="searchData()">查询</button>
+                <button type="button" class="btn btn-warning btn-large" onclick="reSearchData()">重置</button>
+            </div>
+        </div>
+        <div class="edit-box">
+            <div class="form-group">
+                <input type="hidden" id="project_id" class="form-control">
+                <div>
+                    <label for="new_project_no">项目编号:</label>
+                    <input type="text" id="new_project_no" class="form-control" placeholder="项目编号">
+                </div>
+                <div>
+                    <label for="new_project_name">项目名称:</label>
+                    <input type="text" id="new_project_name" class="form-control" placeholder="项目名称">
+                </div>
+                <div>
+                    <label for="new_project_file">项目数据:</label>
+                    <input type="file" id="new_project_file" class="form-control" accept=".xlsx,.xls,.csv" multiple>
+                </div>
+                <div></div>
+                <div>
+                    <label for="new_work_catalog">工作目录:</label>
+                    <textarea id="new_work_catalog" class="form-control" placeholder="工作目录"></textarea>
+                </div>
+                <div>
+                    <label for="new_work_content">工作内容:</label>
+                    <textarea id="new_work_content" class="form-control large" placeholder="工作内容"></textarea>
+                </div>
+                <div>
+                    <label for="new_standard_version">标准版本:</label>
+                    <select id="new_standard_version" class="form-control">
+                        <option value="1">旧版</option>
+                        <option value="2">新版</option>
+                    </select>
+                </div>
+                <div class="button-group">
+                    <button type="button" class="btn btn-success btn-large" onclick="saveProject()">保存</button>
+                    <button type="button" class="btn btn-warning btn-large" onclick="cancelAdd()">关闭</button>
+                </div>
+            </div>
+        </div>
+        <div class="box-body">
+            <table class="table">
+                <thead>
+                    <tr>
+                        <th width="200px">项目编号</th>
+                        <th width="280px">项目名称</th>
+                        <th width="15%">工作目录</th>
+                        <th width="">工作内容</th>
+                        <th width="120px">标准版本</th>
+                        <th width="120px">状态</th>
+                        <th width="260px">操作</th>
+                    </tr>
+                </thead>
+                <tbody>
+                {% if project_list %}
+                    {% for project in project_list %}
+                        <tr>
+                            <td class="editable project_no">
+                                <span class="show">{{ project.project_no if project.project_no else '-' }}</span>
+                                <span class="edit">
+                                    <input type="text" class="form-control" title="项目编号" name="project_no" value="{{ project.project_no if project.project_no else '' }}">
+                                </span>
+                            </td>
+                            <td class="editable project_name">
+                                <span class="show">{{ project.project_name if project.project_name else '-' }}</span>
+                                <span class="edit">
+                                    <input type="text" class="form-control" title="项目名称" name="project_name" value="{{ project.project_name if project.project_name else '' }}">
+                                </span>
+                            </td>
+                            <td class="editable work_catalog">
+                                <span class="show">{{ project.work_catalog if project.work_catalog else '-' }}</span>
+                                <span class="edit">
+                                    <input type="text" class="form-control" title="工作目录" name="work_catalog" value="{{ project.work_catalog if project.work_catalog else '' }}">
+                                </span>
+                            </td>
+                            <td class="editable work_content">
+                                <span class="show">{{ project.work_content if project.work_content else '-' }}</span>
+                                <span class="edit">
+                                    <input type="text" class="form-control" title="工作内容" value="{{ project.work_content if project.work_content else '' }}">
+                                </span>
+                            </td>
+                            <td class="editable standard_version">
+                                <span class="show">
+                                    {% if project.standard_version == '1' %}
+                                    <span class="label label-warning">旧版</span>
+                                    {% elif project.standard_version == '2' %}
+                                    <span class="label label-info">新版</span>
+                                    {% endif %}
+                                </span>
+                                <span class="edit">
+                                    <select class="form-control" title="标准版本">
+                                        <option value="1" {% if project.standard_version == '1' %}selected{% endif %}>旧版</option>
+                                        <option value="2" {% if project.standard_version == '2' %}selected{% endif %}>新版</option>
+                                    </select>
+                                </span>
+                            </td>
+                            <td>
+                                <span class="status">
+                                    {% if project.status == 0 %}
+                                    <span class="label label-default">新建</span>
+                                    {% elif project.status == 21 %}
+                                    <span class="label label-warning">采集中...</span>
+                                    {% elif project.status == 22 %}
+                                    <span class="label label-warning">分析中...</span>
+                                    {% elif project.status == 23 %}
+                                    <span class="label label-warning">上传中...</span>
+                                    {% elif project.status == 31 %}
+                                    <span class="label label-info">采集完成</span>
+                                    {% elif project.status == 32 %}
+                                    <span class="label label-info">分析完成</span>
+                                    {% elif project.status == 33 %}
+                                    <span class="label label-info">上传完成</span>
+                                    {% elif project.status == 11 %}
+                                    <span class="label label-danger">采集失败</span>
+                                    {% elif project.status == 12 %}
+                                    <span class="label label-danger">分析失败</span>
+                                    {% elif project.status == 13 %}
+                                    <span class="label label-danger">上传失败</span>
+                                    {% elif project.status == 5 %}
+                                    <span class="label label-success">完成</span>
+                                    {% endif %}
+                                </span>
+                            </td>
+                            <td class="editable">
+                                <span class="show">
+                                    {% if project.status == 0 %}
+                                    <button class="btn btn-success" type="button" onclick="confirmStartTask('{{ project.id }}')">开始</button>
+                                    <button class="btn btn-info" onclick="updateProject(this.parentNode.parentNode.parentNode,'{{ project.id }}')">编辑</button>
+                                    {% elif project.status == 11 %}
+                                    <button class="btn btn-success" type="button" onclick="confirmReStartTask('{{ project.id }}')">重新采集</button>
+                                    <button class="btn btn-info" onclick="updateProject(this.parentNode.parentNode.parentNode,'{{ project.id }}')">编辑</button>
+                                    {% elif project.status == 12 %}
+                                    <button class="btn btn-success" type="button" onclick="confirmReProcessData('{{ project.id }}')">重新处理</button>
+                                    {% elif project.status == 13 %}
+                                    <button class="btn btn-success" type="button" onclick="confirmSendData('{{ project.id }}')">重新发送</button>
+                                    {% elif project.status == 5 or project.status == 32 or project.status == 13 or project.status == 23 or project.status == 33 %}
+                                    <button class="btn btn-info" type="button" onclick="goTo(`{{ url_for('project.project_item_list', project_id=project.id) }}`)">详情</button>
+                                    {% endif %}
+                                    {% if project.status != 21  and project.status != 22 %}
+                                    <button class="btn btn-danger" onclick="confirmProjectDelete('{{ project.id }}')">删除</button>
+                                    {% endif %}
+                                </span>
+                                <span class="edit">
+                                    <button class="btn btn-success" onclick="saveProjectUpdate(this.parentNode.parentNode.parentNode,'{{ project.id }}')">确定</button>
+                                    <button class="btn btn-warning" onclick="cancelChanges(this.parentNode.parentNode.parentNode)">取消</button>
+                                </span>
+                            </td>
+                        </tr>
+                    {% endfor %}
+                {% else %}
+                    <tr>
+                        <td colspan="15">没有找到项目数据</td>
+                    </tr>
+                {% endif %}
+                </tbody>
+            </table>
+             <div class="pagination">
+            <div class="pagination-info">
+                {% set total_pages = (total_count|int + per_page|int - 1)//per_page %}
+               <span class="page">
+                    总共 {{ total_count }} 条数据,每页 {{ per_page }} 条,当前第 {{ page }} 页 / 共 {{ total_pages }} 页
+               </span>
+            </div>
+            <div class="pagination-links">
+                {% if page > 1 %}
+                    <a class="page page-link" href="{{ url_for('project.project_list', k=keyword, s=status, p=1, pp=per_page) }}">首页</a>
+                {% endif %}
+                {% if page > 1 %}
+                    <a class="page page-link" href="{{ url_for('project.project_list', k=keyword, p=page-1, pp=per_page) }}">上一页</a>
+                {% endif %}
+                {% set start_page = [1, page - 2]|max %}
+                {% set end_page = [total_pages, page + 2]|min %}
+                {% if start_page > 1 %}
+                    <a class="page page-link" href="{{ url_for('project.project_list', k=keyword, s=status, p=1, pp=per_page) }}">1</a>
+                    {% if start_page > 2 %}
+                        <span class="page">...</span>
+                    {% endif %}
+                {% endif %}
+                {% for p in range(start_page, end_page + 1) %}
+                    {% if p == page %}
+                    <a class="page page-link active" href="{{ url_for('project.project_list', k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
+                    {% else %}
+                    <a class="page page-link" href="{{ url_for('project.project_list', k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
+                    {% endif %}
+                {% endfor %}
+                {% if end_page < total_pages %}
+                    {% if end_page < total_pages - 1 %}
+                        <span class="page">...</span>
+                    {% endif %}
+                    <a class="page page-link" href="{{ url_for('project.project_list', k=keyword, p=total_pages, pp=per_page) }}">{{ total_pages }}</a>
+                {% endif %}
+                {% if page < total_pages %}
+                    <a class="page page-link" href="{{ url_for('project.project_list', k=keyword, p=page+1, pp=per_page) }}">下一页</a>
+                {% endif %}
+                {% if page < total_pages %}
+                    <a class="page page-link" href="{{ url_for('project.project_list', k=keyword, p=total_pages, pp=per_page) }}">末页</a>
+                {% endif %}
+            </div>
+        </div>
+        </div>
+    </div>
+</body>
+</html>

+ 0 - 180
SourceCode/DataMiddleware/app/ui/templates/source_data/source_data_list.html

@@ -1,180 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>项目列表</title>
-    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
-    <script src="{{ url_for('static', filename='utils.js') }}"></script>
-    <script src="{{ url_for('static', filename='source_data_list.js') }}"></script>
-    <script>
-        function searchData(){
-            var keyword = document.getElementById('search_input').value;
-            var status = document.getElementById('status_select').value;
-            window.location.href = '{{ url_for('source_data.source_data_list') }}?k=' + keyword + '&s=' + status;
-        }
-        function reSearchData(){
-            window.location.href = '{{ url_for('source_data.source_data_list') }}';
-        }
-    </script>
-</head>
-<body>
-    <div class="box">
-        <div class="box_header">
-            <div><h3>项目列表</h3></div>
-        </div>
-        <div class="box_header">
-            <div class="btn_box">
-                <button type="button" class="btn btn-success btn-large" onclick="addNewData()">添加</button>
-            </div>
-            <div class="search_box">
-                <select id="status_select" class="form-control" style="width: 100px;">
-                    <option value="-1" {% if status == -1 %}selected{% endif %}>所有状态</option>
-                    <option value="0" {% if status == 0 %}selected{% endif %}>新建</option>
-                    <option value="32" {% if status == 32 %}selected{% endif %}>分析完成</option>
-                    <option value="31" {% if status == 31 %}selected{% endif %}>采集完成</option>
-                    <option value="21" {% if status == 21 %}selected{% endif %}>采集中...</option>
-                    <option value="22" {% if status == 22 %}selected{% endif %}>分析中...</option>
-                    <option value="11" {% if status == 11 %}selected{% endif %}>采集失败</option>
-                    <option value="12" {% if status == 12 %}selected{% endif %}>分析失败</option>
-                </select>
-                <input type="text" id="search_input" class="form-control" placeholder="请输入查询关键字" value="{{ keyword }}">
-                <button type="button" class="btn btn-info btn-large" onclick="searchData()">查询</button>
-                <button type="button" class="btn btn-warning btn-large" onclick="reSearchData()">重置</button>
-            </div>
-        </div>
-        <div class="box-body">
-            <table class="table">
-                <thead>
-                    <tr>
-                        <th width="20%">项目编号</th>
-                        <th width="">项目名称</th>
-                        <th width="20%">标准版本</th>
-                        <th width="150px">状态</th>
-                        <th width="260px">操作</th>
-                    </tr>
-                </thead>
-                <tbody>
-                {% if source_data_list %}
-                    {% for source_data in source_data_list %}
-                        <tr>
-                            <td class="project_no">{{ source_data.project_no }}</td>
-                            <td class="editable project_name">
-                                <span class="show">{{ source_data.project_name if source_data.project_name else '-' }}</span>
-                                <span class="edit">
-                                    <input type="text" class="form-control" value="{{ source_data.project_name if source_data.project_name else '' }}">
-                                </span>
-                            </td>
-                            <td class="editable standard_version">
-                                <span class="show">
-                                    {% if source_data.standard_version == '1' %}
-                                    <span class="label label-warning">旧版</span>
-                                    {% elif source_data.standard_version == '2' %}
-                                    <span class="label label-info">新版</span>
-                                    {% endif %}
-                                </span>
-                                <span class="edit">
-                                    <select class="form-control" >
-                                        <option value="1" {% if source_data.standard_version == '1' %}selected{% endif %}>旧版</option>
-                                        <option value="2" {% if source_data.standard_version == '2' %}selected{% endif %}>新版</option>
-                                    </select>
-                                </span>
-                            </td>
-                            <td>
-                                <span class="status">
-                                    {% if source_data.status == 0 %}
-                                    <span class="label label-default">新建</span>
-                                    {% elif source_data.status == 21 %}
-                                    <span class="label label-warning">采集中...</span>
-                                    {% elif source_data.status == 31 %}
-                                    <span class="label label-info">采集完成</span>
-                                    {% elif source_data.status == 22 %}
-                                    <span class="label label-warning">分析中...</span>
-                                    {% elif source_data.status == 32 %}
-                                    <span class="label label-success">分析完成</span>
-                                    {% elif source_data.status == 11 %}
-                                    <span class="label label-danger">采集失败</span>
-                                    {% elif source_data.status == 12 %}
-                                    <span class="label label-danger">分析失败</span>
-                                    {% endif %}
-                                </span>
-                            </td>
-                            <td class="editable">
-                                <span class="show">
-                                    {% if source_data.status == 0 %}
-                                    <button class="btn btn-success" type="button" onclick="confirmCollectData('{{ source_data.project_no }}')">采集数据</button>
-                                    <button class="btn btn-info" onclick="toggleEditMode(this.parentNode.parentNode.parentNode)">编辑</button>
-                                    {% elif source_data.status == 31 %}
-                                    <button class="btn btn-success" type="button" onclick="confirmProcessData('{{ source_data.project_no }}')">分析数据</button>
-                                    {% elif source_data.status == 32 %}
-                                    <button class="btn btn-info" type="button" onclick="goTo('{{ url_for('source_data.source_item_list', project_no=source_data.project_no) }}')">详情</button>
-                                    {% elif source_data.status == 11 %}
-                                    <button class="btn btn-warning" type="button" onclick="confirmCollectData('{{ source_data.project_no }}')">重新采集</button>
-                                    {% elif source_data.status == 12 %}
-                                    <button class="btn btn-warning" type="button" onclick="confirmProcessData('{{ source_data.project_no }}')">重新分析</button>
-                                    {% endif %}
-                                    {% if source_data.status != 21  and source_data.status != 22 %}
-                                    <button class="btn btn-danger" onclick="confirmDataDelete('{{ source_data.project_no }}')">删除</button>
-                                    {% endif %}
-                                </span>
-                                <span class="edit">
-                                    <button class="btn btn-success" onclick="saveDataEdit(this.parentNode.parentNode.parentNode,'{{ source_data.project_no }}')">确定</button>
-                                    <button class="btn btn-warning" onclick="cancelChanges(this.parentNode.parentNode.parentNode)">取消</button>
-                                </span>
-                            </td>
-                        </tr>
-                    {% endfor %}
-                {% else %}
-                    <tr>
-                        <td colspan="15">没有找到项目数据</td>
-                    </tr>
-                {% endif %}
-                </tbody>
-            </table>
-             <div class="pagination">
-            <div class="pagination-info">
-                {% set total_pages = (total_count|int + per_page|int - 1)//per_page %}
-               <span class="page">
-                    总共 {{ total_count }} 条数据,每页 {{ per_page }} 条,当前第 {{ page }} 页 / 共 {{ total_pages }} 页
-               </span>
-            </div>
-            <div class="pagination-links">
-                {% if page > 1 %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_data_list', k=keyword, s=status, p=1, pp=per_page) }}">首页</a>
-                {% endif %}
-                {% if page > 1 %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_data_list', k=keyword, p=page-1, pp=per_page) }}">上一页</a>
-                {% endif %}
-                {% set start_page = [1, page - 2]|max %}
-                {% set end_page = [total_pages, page + 2]|min %}
-                {% if start_page > 1 %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_data_list', k=keyword, s=status, p=1, pp=per_page) }}">1</a>
-                    {% if start_page > 2 %}
-                        <span class="page">...</span>
-                    {% endif %}
-                {% endif %}
-                {% for p in range(start_page, end_page + 1) %}
-                    {% if p == page %}
-                    <a class="page page-link active" href="{{ url_for('source_data.source_data_list', k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
-                    {% else %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_data_list', k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
-                    {% endif %}
-                {% endfor %}
-                {% if end_page < total_pages %}
-                    {% if end_page < total_pages - 1 %}
-                        <span class="page">...</span>
-                    {% endif %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_data_list', k=keyword, p=total_pages, pp=per_page) }}">{{ total_pages }}</a>
-                {% endif %}
-                {% if page < total_pages %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_data_list', k=keyword, p=page+1, pp=per_page) }}">下一页</a>
-                {% endif %}
-                {% if page < total_pages %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_data_list', k=keyword, p=total_pages, pp=per_page) }}">末页</a>
-                {% endif %}
-            </div>
-        </div>
-        </div>
-    </div>
-</body>
-</html>

+ 0 - 149
SourceCode/DataMiddleware/app/ui/templates/source_data/source_item_list.html

@@ -1,149 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>设备列表</title>
-    <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
-    <style>
-        .box_header .project_info{
-            display: flex;
-            justify-content: left;
-            width: 60%;
-        }
-
-    </style>
-    <script src="{{ url_for('static', filename='utils.js') }}"></script>
-    <script src="{{ url_for('static', filename='source_item_list.js') }}"></script>
-    <script>
-         function searchData(){
-            var keyword = document.getElementById('search_input').value;
-            window.location.href = '{{ url_for('source_data.source_item_list', project_no=source_data.project_no) }}?k=' + keyword;
-        }
-        function reSearchData(){
-            window.location.href = '{{ url_for('source_data.source_item_list', project_no=source_data.project_no) }}';
-        }
-    </script>
-</head>
-<body>
-<div class="box">
-    <div class="box_header">
-        <div class="project_info">
-            <button class="btn btn-info btn-large" onclick="goTo('{{ url_for('source_data.source_data_list') }}')">返回</button>
-            <h3 style="margin-left:20px">设备列表</h3>
-            <span style="margin:0 20px">|</span>
-            <dl>
-                <dt>项目编号:</dt>
-                <dd>{{ source_data.project_no }}</dd>
-            </dl>
-            <dl>
-                <dt>项目名称:</dt>
-                <dd>{{ source_data.project_name }}</dd>
-            </dl>
-            <dl>
-                <dt>标准版本:</dt>
-                <dd>{{ source_data.standard_version }}</dd>
-            </dl>
-        </div>
-    </div>
-    <div class="box_header">
-        <div class="btn_box">
-            <button class="btn btn-success btn-large" onclick="addNewItem('{{ source_data.project_no}}')">添加</button>
-        </div>
-        <div class="search_box">
-            <input type="text" id="search_input" class="form-control" placeholder="请输入查询关键字" value="{{ keyword }}">
-            <button type="button" class="btn btn-info btn-large" onclick="searchData()">查询</button>
-            <button type="button" class="btn btn-warning btn-large" onclick="reSearchData()">重置</button>
-        </div>
-    </div>
-
-    <div class="box_body">
-        <table class="table">
-            <thead>
-                <tr>
-                    <th width="25%">名称</th>
-                    <th>规格型号</th>
-                    <th width="8%">单位</th>
-                    <th width="18%">标准编号</th>
-                    <th width="180px">操作</th>
-                </tr>
-            </thead>
-            <tbody>
-                {% if source_items %}
-                    {% for item in source_items %}
-                        <tr>
-                            <td class="name">{{ item.device_name }}</td>
-                            <td class="model">{{ item.device_model }}</td>
-                            <td class="unit">{{ item.device_unit if item.device_unit else '-' }}</td>
-                            <td class="editable standard_no">
-                                <span class="show">{{ item.standard_no if item.standard_no else '-' }}</span>
-                                <span class="edit">
-                                    <input type="text" class="form-control" value="{{ item.standard_no if item.standard_no else '' }}">
-                                </span>
-                            </td>
-                            <td class="editable tool">
-                                <span class="show">
-                                    <button class="btn btn-info" onclick="toggleEditMode(this.parentNode.parentNode.parentNode)">编辑</button>
-                                    <button class="btn btn-danger" onclick="confirmItemDelete('{{ item.id }}')">删除</button>
-                                </span>
-                                <span class="edit">
-                                    <button class="btn btn-success" onclick="saveItemEdit(this.parentNode.parentNode.parentNode, '{{ item.id }}')">保存</button>
-                                    <button class="btn btn-warning" onclick="cancelChanges(this.parentNode.parentNode.parentNode)">取消</button>
-                                </span>
-                            </td>
-                        </tr>
-                    {% endfor %}
-                {% else %}
-                    <tr>
-                        <td colspan="15">没有找到设备数据</td>
-                    </tr>
-                {% endif %}
-            </tbody>
-        </table>
-        <div class="pagination">
-            <div class="pagination-info">
-                {% set total_pages = (total_count|int + per_page|int - 1)//per_page %}
-               <span class="page">
-                    总共 {{ total_count }} 条数据,每页 {{ per_page }} 条,当前第 {{ page }} 页 / 共 {{ total_pages }} 页
-               </span>
-            </div>
-            <div class="pagination-links">
-                {% if page > 1 %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=1, pp=per_page) }}">首页</a>
-                {% endif %}
-                {% if page > 1 %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=page-1, pp=per_page) }}">上一页</a>
-                {% endif %}
-                {% set start_page = [1, page - 2]|max %}
-                {% set end_page = [total_pages, page + 2]|min %}
-                {% if start_page > 1 %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=1, pp=per_page) }}">1</a>
-                    {% if start_page > 2 %}
-                        <span class="page">...</span>
-                    {% endif %}
-                {% endif %}
-                {% for p in range(start_page, end_page + 1) %}
-                    {% if p == page %}
-                    <a class="page page-link active" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
-                    {% else %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
-                    {% endif %}
-                {% endfor %}
-                {% if end_page < total_pages %}
-                    {% if end_page < total_pages - 1 %}
-                        <span class="page">...</span>
-                    {% endif %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=total_pages, pp=per_page) }}">{{ total_pages }}</a>
-                {% endif %}
-                {% if page < total_pages %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=page+1, pp=per_page) }}">下一页</a>
-                {% endif %}
-                {% if page < total_pages %}
-                    <a class="page page-link" href="{{ url_for('source_data.source_item_list', project_no=source_data.project_no, k=keyword, p=total_pages, pp=per_page) }}">末页</a>
-                {% endif %}
-            </div>
-        </div>
-    </div>
-</div>
-</body>
-</html>

+ 4 - 4
SourceCode/DataMiddleware/requirements.txt

@@ -3,8 +3,8 @@ PyYAML==6.0.2
 Requests==2.32.3
 openai==1.58.1
 pandas~=2.2.3
-pdfplumber~=0.11.5
-pytesseract~=0.3.13
-pillow~=11.1.0
 Flask~=3.1.0
-pdf2image~=1.17.0
+PyPDF2~=3.0.1
+pillow~=11.1.0
+pathlib~=1.0.1
+PyMuPDF~=1.25.3

+ 0 - 0
SourceCode/DataMiddleware/tools/__init__.py


+ 24 - 0
SourceCode/DataMiddleware/tools/config.yml

@@ -0,0 +1,24 @@
+mysql:
+  host: 192.168.0.81
+  port: 3307
+  db: iwb_data_tielu_pdf_dev
+  user: root
+  password: Iwb-2024
+  charset: utf8mb4
+logger:
+  file_path: './logs/'
+  level: 'debug'
+app:
+  port: 5223
+
+ai:
+  #  url: http://192.168.0.109:7580/api/chat
+  #  model: qwen2.5:7b
+  key: sk-febca8fea4a247f096cedeea9f185520
+  url: https://dashscope.aliyuncs.com/compatible-mode/v1
+  model: qwen2.5-vl-72b-instruct
+  max_tokens: 10240
+
+fastgpt:
+  url: http://192.168.0.104:8020/api/v1/chat/completions
+  key: fastgpt-pzXtKVjkBU8NW8MUqZ7WnEfqK3m8qP6wmDdfcBgOaK2PZDekoHM1

+ 30 - 0
SourceCode/DataMiddleware/tools/init.sql

@@ -0,0 +1,30 @@
+-- 创建数据库
+CREATE DATABASE IF NOT EXISTS iwb_data_tielu_pdf_dev CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+USE iwb_data_tielu_pdf_dev;
+-- 创建标准表
+CREATE TABLE IF NOT EXISTS pdf_standard (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    standard_code VARCHAR(100) COMMENT '标准编号',
+    standard_name VARCHAR(255) COMMENT '标准名称',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_standard_code (standard_code),
+    INDEX idx_standard_name (standard_name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PDF标准表';
+
+-- 创建PDF记录表
+CREATE TABLE IF NOT EXISTS pdf_records (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    standard_name VARCHAR(255) COMMENT '标准名称',
+    chapter_name VARCHAR(255) COMMENT '章节名称',
+    section_name VARCHAR(255) COMMENT '节名称',
+    subsection_name VARCHAR(255) COMMENT '小节名称',
+    pdf_path VARCHAR(500) COMMENT 'PDF文件路径',
+    image_path TEXT COMMENT '生成的图片路径',
+    markdown_text TEXT COMMENT 'AI分析生成的Markdown文本',
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_standard_code (standard_code),
+    INDEX idx_standard_name (standard_name),
+    INDEX idx_chapter_name (chapter_name)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PDF文件处理记录表';

+ 23 - 0
SourceCode/DataMiddleware/tools/main.py

@@ -0,0 +1,23 @@
+from pdf_split.processor import PDFProcessor
+import tools.utils as utils
+from tools.pdf_split.mysql_store import MysqlStore
+
+@app.route('/standards')
+def show_standards():
+    store = MysqlStore()
+    standards = store.get_all_standards()
+    records = store.get_all_pdf_records()
+    return render_template('standards.html', standards=standards, records=records)
+def main():
+    log = utils.get_logger()
+    #"""PDF拆分工具的主入口函数"""
+    #log.info("PDF拆分工具已启动")
+    #PDFProcessor.split("01")
+    """图片解析成文本入口函数"""
+    log.info("图片解析成文本工具已启动")
+    PDFProcessor.process_image_to_txt("01")
+
+
+
+if __name__ == '__main__':
+    main()

+ 0 - 0
SourceCode/DataMiddleware/tools/models/__init__.py


+ 38 - 0
SourceCode/DataMiddleware/tools/models/pdf_records.py

@@ -0,0 +1,38 @@
+from datetime import datetime
+
+class Record:
+    def __init__(self,
+                 standard_name: str,
+                 chapter_name: str,
+                 section_name: str,
+                 subsection_name: str,
+                 pdf_path: str,
+                 image_path: str,
+                 markdown_text: str,
+                 id: int = None,
+                 created_at: datetime = None,
+                 updated_at: datetime = None):
+        self.id = id  # 自增ID
+        self.standard_name = standard_name  # 标准名称
+        self.chapter_name = chapter_name  # 章节名称
+        self.section_name = section_name  # 节名称
+        self.subsection_name = subsection_name  # 小节名称
+        self.pdf_path = pdf_path  # PDF路径
+        self.image_path = image_path  # 图片路径
+        self.markdown_text = markdown_text  # Markdown文本
+        self.created_at = created_at or datetime.now()  # 创建时间
+        self.updated_at = updated_at or datetime.now()  # 更新时间
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'standard_name': self.standard_name,
+            'chapter_name': self.chapter_name,
+            'section_name': self.section_name,
+            'subsection_name': self.subsection_name,
+            'pdf_path': self.pdf_path,
+            'image_path': self.image_path,
+            'markdown_text': self.markdown_text,
+            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
+            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S')
+        }

+ 23 - 0
SourceCode/DataMiddleware/tools/models/pdf_standard.py

@@ -0,0 +1,23 @@
+from datetime import datetime
+
+class Standard:
+    def __init__(self,
+                 standard_code: str,
+                 standard_name: str,
+                 id: int = None,
+                 created_at: datetime = None,
+                 updated_at: datetime = None):
+        self.id = id  # 自增ID
+        self.standard_code = standard_code  # 标准编号
+        self.standard_name = standard_name  # 标准名称
+        self.created_at = created_at or datetime.now()  # 创建时间
+        self.updated_at = updated_at or datetime.now()  # 更新时间
+
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'standard_code': self.standard_code,
+            'standard_name': self.standard_name,
+            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
+            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S')
+        }

+ 417 - 0
SourceCode/DataMiddleware/tools/pdf_json/v1/01.json

@@ -0,0 +1,417 @@
+[
+	{
+		"output_dir": "01沟槽及管道/",
+		"page_configs": [
+			{
+				"start_page": 3,
+				"end_page": 8,
+				"output_name": "01沟槽及管道@01光(电)缆沟@01光(电)缆沟"
+			},
+			{
+				"start_page": 9,
+				"end_page": 13,
+				"output_name": "01沟槽及管道@02铺设光(电)缆管道@01铺设光(电)缆管道"
+			}
+		]
+	},
+	{
+		"output_dir": "02光(电)缆线路/",
+		"page_configs": [
+			{
+				"start_page": 14,
+				"end_page": 23,
+				"output_name": "02光(电)缆线路@01敷设长途光缆@01敷设埋式光缆"
+			},
+			{
+				"start_page": 24,
+				"end_page": 26,
+				"output_name": "02光(电)缆线路@01敷设长途光缆@02敷设管道光缆"
+			},
+			{
+				"start_page": 27,
+				"end_page": 32,
+				"output_name": "02光(电)缆线路@01敷设长途光缆@03敷设槽道光缆"
+			},
+			{
+				"start_page": 33,
+				"end_page": 33,
+				"output_name": "02光(电)缆线路@02安装光纤监测设备@01安装光纤监测设备"
+			},
+			{
+				"start_page": 34,
+				"end_page": 42,
+				"output_name": "02光(电)缆线路@03敷设长途电缆@01敷设埋式长途电缆"
+			},
+			{
+				"start_page": 43,
+				"end_page": 45,
+				"output_name": "02光(电)缆线路@03敷设长途电缆@02敷设管道长途电缆"
+			},
+			{
+				"start_page": 46,
+				"end_page": 50,
+				"output_name": "02光(电)缆线路@03敷设长途电缆@02敷设长途电缆"
+			},
+			{
+				"start_page": 51,
+				"end_page": 64,
+				"output_name": "02光(电)缆线路@04敷设地区通信光(电)缆@01敷设地区通信光(电)缆"
+			}
+		]
+	},
+	{
+		"output_dir": "03配线架(柜)及走线架(槽)安装/",
+		"page_configs": [
+			{
+				"start_page": 65,
+				"end_page": 69,
+				"output_name": "03配线架(柜)及走线架(槽)安装@01配线架(柜)及走线架(槽)安装@01配线架(柜)及走线架(槽)安装"
+			}
+		]
+	},
+	{
+		"output_dir": "04传输及接入网设备/",
+		"page_configs": [
+			{
+				"start_page": 70,
+				"end_page": 73,
+				"output_name": "04传输及接入网设备@01安装传输及接入网设备@01安装传输及接入网设备"
+			},
+			{
+				"start_page": 74,
+				"end_page": 77,
+				"output_name": "04传输及接入网设备@01安装传输及接入网设备@02安装传输及接入网设备"
+			},
+			{
+				"start_page": 78,
+				"end_page": 79,
+				"output_name": "04传输及接入网设备@01安装传输及接入网设备@03安装传输及接入网设备"
+			},
+			{
+				"start_page": 80,
+				"end_page": 82,
+				"output_name": "04传输及接入网设备@01安装传输及接入网设备@04安装传输及接入网设备"
+			},
+			{
+				"start_page": 83,
+				"end_page": 84,
+				"output_name": "04传输及接入网设备@02安装传输及接入网网管设备@01安装传输及接入网网管设备"
+			}
+		]
+	},
+	{
+		"output_dir": "05数据通信网设备/",
+		"page_configs": [
+			{
+				"start_page": 85,
+				"end_page": 86,
+				"output_name": "05数据通信网设备@01安装数据网设备@01安装数据网设备"
+			},
+			{
+				"start_page": 87,
+				"end_page": 88,
+				"output_name": "05数据通信网设备@02安装数据网网管设备@01安装数据网网管设备"
+			}
+		]
+	},
+	{
+		"output_dir": "06电话交换设备/",
+		"page_configs": [
+			{
+				"start_page": 89,
+				"end_page": 91,
+				"output_name": "06电话交换设备@01电话交换设备@01电话交换设备"
+			}
+		]
+	},
+	{
+		"output_dir": "07有线调度通信系统设备/",
+		"page_configs": [
+			{
+				"start_page": 92,
+				"end_page": 94,
+				"output_name": "07有线调度通信系统设备@01安装与调试调度交换机@01安装与调试调度交换机"
+			},
+			{
+				"start_page": 95,
+				"end_page": 99,
+				"output_name": "07有线调度通信系统设备@02安装与调试叫班系统@01安装与调试叫班系统"
+			}
+		]
+	},
+	{
+		"output_dir": "08数字移动通信系统(GSM-R)/",
+		"page_configs": [
+			{
+				"start_page": 100,
+				"end_page": 106,
+				"output_name": "08数字移动通信系统(GSM-R)@01架设漏泄同轴电缆@01架设漏泄同轴电缆"
+			},
+			{
+				"start_page": 107,
+				"end_page": 110,
+				"output_name": "08数字移动通信系统(GSM-R)@02架设通信铁塔@01架设通信铁塔"
+			},
+			{
+				"start_page": 111,
+				"end_page": 115,
+				"output_name": "08数字移动通信系统(GSM-R)@03安装无线网及附属设备@01安装基站及无线中继设备"
+			},
+			{
+				"start_page": 116,
+				"end_page": 117,
+				"output_name": "08数字移动通信系统(GSM-R)@03安装无线网及附属设备@02安装无线终端设备"
+			},
+			{
+				"start_page": 118,
+				"end_page": 119,
+				"output_name": "08数字移动通信系统(GSM-R)@03安装无线网及附属设备@03安装漏缆监测设备"
+			},
+			{
+				"start_page": 120,
+				"end_page": 122,
+				"output_name": "08数字移动通信系统(GSM-R)@04安装核心网设备@01安装核心网设备"
+			},
+			{
+				"start_page": 123,
+				"end_page": 124,
+				"output_name": "08数字移动通信系统(GSM-R)@05移动通信系统调试@01移动通信系统调试"
+			}
+		]
+	},
+	{
+		"output_dir": "09会议电视系统设备/",
+		"page_configs": [
+			{
+				"start_page": 125,
+				"end_page": 128,
+				"output_name": "09会议电视系统设备@01会议电视系统设备@01会议电视系统设备"
+			}
+		]
+	},
+	{
+		"output_dir": "10综合视频监控系统设备/",
+		"page_configs": [
+			{
+				"start_page": 129,
+				"end_page": 139,
+				"output_name": "10综合视频监控系统设备@01综合视频监控系统设备@01综合视频监控系统设备"
+			},
+			{
+				"start_page": 140,
+				"end_page": 143,
+				"output_name": "10综合视频监控系统设备@02安装视频采集点及汇集点设备@01安装视频采集点及汇集点设备"
+			}
+		]
+	},
+	{
+		"output_dir": "11应急通信系统设备/",
+		"page_configs": [
+			{
+				"start_page": 144,
+				"end_page": 146,
+				"output_name": "11应急通信系统设备@01安装应急通信系统设备@01安装应急通信系统设备"
+			},
+			{
+				"start_page": 147,
+				"end_page": 149,
+				"output_name": "11应急通信系统设备@02安装隧道应急电话设备@01安装隧道应急电话设备"
+			}
+		]
+	},
+	{
+		"output_dir": "12时钟及时间同步系统设备/",
+		"page_configs": [
+			{
+				"start_page": 150,
+				"end_page": 152,
+				"output_name": "12时钟及时间同步系统设备@01时钟及时间同步系统设备@01时钟及时间同步系统设备"
+			}
+		]
+	},
+	{
+		"output_dir": "13通信电源设备及防雷接地装置/",
+		"page_configs": [
+			{
+				"start_page": 153,
+				"end_page": 157,
+				"output_name": "13通信电源设备及防雷接地装置@01安装通信电源设备@01安装通信电源设备"
+			},
+			{
+				"start_page": 158,
+				"end_page": 160,
+				"output_name": "13通信电源设备及防雷接地装置@02安装防雷及地线装置@01安装防雷及地线装置"
+			}
+		]
+	},
+	{
+		"output_dir": "14电源及设备房屋环境监控设备/",
+		"page_configs": [
+			{
+				"start_page": 161,
+				"end_page": 162,
+				"output_name": "14电源及设备房屋环境监控设备@01电源及设备房屋环境监控设备@01电源及设备房屋环境监控设备"
+			}
+		]
+	},
+	{
+		"output_dir": "15综合布线/",
+		"page_configs": [
+			{
+				"start_page": 163,
+				"end_page": 168,
+				"output_name": "15综合布线@01综合布线@01综合布线"
+			}
+		]
+	},
+	{
+		"output_dir": "16附录_工程量组成/",
+		"page_configs": [
+			{
+				"start_page": 170,
+				"end_page": 170,
+				"output_name": "16附录_工程量组成@01沟槽及管道@01挖填光(电)缆沟"
+			},
+			{
+				"start_page": 171,
+				"end_page": 172,
+				"output_name": "16附录_工程量组成@01沟槽及管道@02铺设光(电)缆管道"
+			},
+			{
+				"start_page": 173,
+				"end_page": 178,
+				"output_name": "16附录_工程量组成@01沟槽及管道@01挖填光(电)缆沟"
+			},
+			{
+				"start_page": 179,
+				"end_page": 179,
+				"output_name": "16附录_工程量组成@02光(电)缆敷设@02安装光纤监测设备"
+			},
+			{
+				"start_page": 180,
+				"end_page": 182,
+				"output_name": "16附录_工程量组成@02光(电)缆敷设@03敷设长途电缆"
+			},
+			{
+				"start_page": 183,
+				"end_page": 186,
+				"output_name": "16附录_工程量组成@02光(电)缆敷设@04敷设地区通信光(电)缆"
+			},
+			{
+				"start_page": 187,
+				"end_page": 188,
+				"output_name": "16附录_工程量组成@03配线架(柜)及走线架(槽)安装@01配线架(柜)及走线架(槽)安装"
+			},
+			{
+				"start_page": 189,
+				"end_page": 193,
+				"output_name": "16附录_工程量组成@04传输及接入网设备@01安装传输及接入网设备"
+			},
+			{
+				"start_page": 194,
+				"end_page": 194,
+				"output_name": "16附录_工程量组成@04传输及接入网设备@02安装传输及接入网网管设备"
+			},
+				{
+				"start_page": 195,
+				"end_page": 195,
+				"output_name": "16附录_工程量组成@05数据通信网设备@01安装数据网设备"
+			},
+			{
+				"start_page": 196,
+				"end_page": 196,
+				"output_name": "16附录_工程量组成@05数据通信网设备@02安装数据网网管设备"
+			},
+			{
+				"start_page": 197,
+				"end_page": 197,
+				"output_name": "16附录_工程量组成@06电话交换设备@01电话交换设备"
+			},
+			{
+				"start_page": 198,
+				"end_page": 198,
+				"output_name": "16附录_工程量组成@07有线调度通信系统设备@01安装与调试调度交换机"
+			},
+			{
+				"start_page": 199,
+				"end_page": 200,
+				"output_name": "16附录_工程量组成@07有线调度通信系统设备@02安装与调试叫班系统"
+			},
+				{
+				"start_page": 201,
+				"end_page": 201,
+				"output_name": "16附录_工程量组成@08数字移动通信系统(GSM-R)@01架设漏泄同轴电缆"
+			},
+			{
+				"start_page": 202,
+				"end_page": 202,
+				"output_name": "16附录_工程量组成@08数字移动通信系统(GSM-R)@02架设通信铁塔"
+			},
+			{
+				"start_page": 203,
+				"end_page": 205,
+				"output_name": "16附录_工程量组成@08数字移动通信系统(GSM-R)@03安装无线网及附属设备"
+			},
+			{
+				"start_page": 206,
+				"end_page": 208,
+				"output_name": "16附录_工程量组成@08数字移动通信系统(GSM-R)@04安装核心网设备"
+			},
+			{
+				"start_page": 209,
+				"end_page": 209,
+				"output_name": "16附录_工程量组成@08数字移动通信系统(GSM-R)@05移动通信系统调试"
+			},
+			{
+				"start_page": 210,
+				"end_page": 210,
+				"output_name": "16附录_工程量组成@09会议电视系统设备@01会议电视系统设备"
+			},
+			{
+				"start_page": 211,
+				"end_page": 215,
+				"output_name": "16附录_工程量组成@10综合视频监控系统设备@01视频采集点及汇集点设备"
+			},
+			{
+				"start_page": 216,
+				"end_page": 216,
+				"output_name": "16附录_工程量组成@10综合视频监控系统设备@02视频采节点"
+			},
+				{
+				"start_page": 217,
+				"end_page": 217,
+				"output_name": "16附录_工程量组成@11应急通信系统设备@01安装应急通信系统设备"
+			},
+			{
+				"start_page": 218,
+				"end_page": 218,
+				"output_name": "16附录_工程量组成@11应急通信系统设备@02安装隧道应急电话设备"
+			},
+			{
+				"start_page": 219,
+				"end_page": 219,
+				"output_name": "16附录_工程量组成@12时钟及时间同步系统设备@01时钟及时间同步系统设备"
+			},
+			{
+				"start_page": 220,
+				"end_page": 221,
+				"output_name": "16附录_工程量组成@13通信电源设备及防雷接地装置@01安装通信电源设备"
+			},
+			{
+				"start_page": 222,
+				"end_page": 222,
+				"output_name": "16附录_工程量组成@13通信电源设备及防雷接地装置@02安装防雷及地线装置"
+			},
+			{
+				"start_page": 223,
+				"end_page": 223,
+				"output_name": "16附录_工程量组成@14电源及设备房屋环境监控设备@01电源及设备房屋环境监控设备"
+			},
+			{
+				"start_page": 223,
+				"end_page": 226,
+				"output_name": "16附录_工程量组成@15综合布线@01综合布线"
+			}
+		]
+	}
+]

+ 562 - 0
SourceCode/DataMiddleware/tools/pdf_json/v1/03.json

@@ -0,0 +1,562 @@
+[
+  {
+    "output_dir": "01洞身开挖、出砟/",
+    "page_configs": [
+      {
+        "start_page": 3,
+        "end_page": 6,
+        "output_name": "01洞身开挖、出砟@01洞身开挖@01隧道断面有效面积≤40m2(轨道运输)"
+      },
+      {
+        "start_page": 7,
+        "end_page": 9,
+        "output_name": "01洞身开挖、出砟@01洞身开挖@02隧道断面有效面积≤40m2(汽车运输)"
+      },
+      {
+        "start_page": 10,
+        "end_page": 13,
+        "output_name": "01洞身开挖、出砟@01洞身开挖@03隧道断面有效面积≤60m2(轨道运输)"
+      },
+      {
+        "start_page": 14,
+        "end_page": 16,
+        "output_name": "01洞身开挖、出砟@01洞身开挖@04隧道断面有效面积≤60m2(汽车运输)"
+      },
+      {
+        "start_page": 17,
+        "end_page": 19,
+        "output_name": "01洞身开挖、出砟@01洞身开挖@05隧道断面有效面积≤85m"
+      },
+      {
+        "start_page": 20,
+        "end_page": 22,
+        "output_name": "01洞身开挖、出砟@01洞身开挖@06隧道断面有效面积>85m2"
+      },
+      {
+        "start_page": 23,
+        "end_page": 24,
+        "output_name": "01洞身开挖、出砟@02出砟运输@01正洞轨道出砟"
+      },
+      {
+        "start_page": 25,
+        "end_page": 27,
+        "output_name": "01洞身开挖、出砟@02出砟运输@02正洞汽车出砟"
+      },
+      {
+        "start_page": 28,
+        "end_page": 31,
+        "output_name": "01洞身开挖、出砟@03正洞通过辅助坑道出砟运输@01通过有轨斜井出砟"
+      },
+      {
+        "start_page": 32,
+        "end_page": 32,
+        "output_name": "01洞身开挖、出砟@03正洞通过辅助坑道出砟运输@02通过无轨斜井出砟"
+      },
+      {
+        "start_page": 33,
+        "end_page": 34,
+        "output_name": "01洞身开挖、出砟@03正洞通过辅助坑道出砟运输@03通过平行导坑出砟"
+      },
+      {
+        "start_page": 35,
+        "end_page": 38,
+        "output_name": "01洞身开挖、出砟@03正洞通过辅助坑道出砟运输@04通过竖井出砟"
+      },
+      {
+        "start_page": 39,
+        "end_page": 39,
+        "output_name": "01洞身开挖、出砟@04洞外运砟@01出砟洞外汽车倒运、增运"
+      },
+      {
+        "start_page": 40,
+        "end_page": 40,
+        "output_name": "01洞身开挖、出砟@04洞外运砟@02有轨洞外增运"
+      },
+      {
+        "start_page": 41,
+        "end_page": 42,
+        "output_name": "01洞身开挖、出砟@05开挖台架和仰拱栈桥@01开挖台架"
+      },
+      {
+        "start_page": 43,
+        "end_page": 44,
+        "output_name": "01洞身开挖、出砟@05开挖台架和仰拱栈桥@02简易仰拱栈桥"
+      }
+    ]
+  },
+  {
+    "output_dir": "02支护/",
+    "page_configs": [
+      {
+        "start_page": 47,
+        "end_page": 48,
+        "output_name": "02支护@01喷射混凝土@01喷射普通混凝土"
+      },
+      {
+        "start_page": 49,
+        "end_page": 50,
+        "output_name": "02支护@01喷射混凝土@02喷射纤维混凝土"
+      },
+      {
+        "start_page": 51,
+        "end_page": 53,
+        "output_name": "02支护@02锚杆@01锚杆"
+      },
+      {
+        "start_page": 54,
+        "end_page": 55,
+        "output_name": "02支护@03钢筋网、格栅钢架、型钢钢架@01钢筋网、格栅钢架、型钢钢架"
+      },
+      {
+        "start_page": 56,
+        "end_page": 58,
+        "output_name": "02支护@04超前支护@01钻孔"
+      },
+      {
+        "start_page": 59,
+        "end_page": 60,
+        "output_name": "02支护@04超前支护@02注浆"
+      },
+      {
+        "start_page": 61,
+        "end_page": 62,
+        "output_name": "02支护@05拆除临时支护@01拆除临时支护"
+      },
+      {
+        "start_page": 63,
+        "end_page": 63,
+        "output_name": "02支护@06综合接地焊接@01综合接地焊接"
+      },
+      {
+        "start_page": 64,
+        "end_page": 66,
+        "output_name": "02支护@07支护台架@01支护台架"
+      }
+    ]
+  },
+  {
+    "output_dir": "03衬砌/",
+    "page_configs": [
+      {
+        "start_page": 69,
+        "end_page": 70,
+        "output_name": "03衬砌@01衬砌模板、台架@01衬砌钢台模"
+      },
+      {
+        "start_page": 71,
+        "end_page": 72,
+        "output_name": "03衬砌@01衬砌模板、台架@02衬砌组合模板"
+      },
+      {
+        "start_page": 73,
+        "end_page": 74,
+        "output_name": "03衬砌@01衬砌模板、台架@03沟槽模板"
+      },
+      {
+        "start_page": 75,
+        "end_page": 75,
+        "output_name": "03衬砌@01衬砌模板、台架@04防水板台架"
+      },
+      {
+        "start_page": 76,
+        "end_page": 77,
+        "output_name": "03衬砌@02模筑混凝土@01混凝土集中拌制"
+      },
+      {
+        "start_page": 78,
+        "end_page": 79,
+        "output_name": "03衬砌@02模筑混凝土@02混凝土浇筑"
+      },
+      {
+        "start_page": 80,
+        "end_page": 81,
+        "output_name": "03衬砌@03钢筋@01钢筋"
+      },
+      {
+        "start_page": 82,
+        "end_page": 84,
+        "output_name": "03衬砌@04钢筋混凝土盖板@01钢筋混凝土盖板"
+      },
+      {
+        "start_page": 85,
+        "end_page": 88,
+        "output_name": "03衬砌@05防水和排水@01防水和排水"
+      },
+      {
+        "start_page": 89,
+        "end_page": 90,
+        "output_name": "03衬砌@06中心水沟@01中心水沟开挖"
+      },
+      {
+        "start_page": 91,
+        "end_page": 91,
+        "output_name": "03衬砌@06中心水沟@02钢筋混凝土预制管铺设"
+      },
+      {
+        "start_page": 92,
+        "end_page": 94,
+        "output_name": "03衬砌@06中心水沟@03深埋中心水沟检查井"
+      },
+      {
+        "start_page": 95,
+        "end_page": 96,
+        "output_name": "03衬砌@07拱顶压浆@01拱顶压浆"
+      }
+    ]
+  },
+  {
+    "output_dir": "04通风及管线路/",
+    "page_configs": [
+      {
+        "start_page": 99,
+        "end_page": 102,
+        "output_name": "04通风及管线路@01通风@01通风"
+      },
+      {
+        "start_page": 103,
+        "end_page": 106,
+        "output_name": "04通风及管线路@02高压风水管、照明、电力线路@01高压风水管、照明、电力线路"
+      }
+    ]
+  },
+  {
+    "output_dir": "05运输/",
+    "page_configs": [
+      {
+        "start_page": 109,
+        "end_page": 110,
+        "output_name": "05运输@01混凝土运输@01洞外混凝土增运"
+      },
+      {
+        "start_page": 111,
+        "end_page": 112,
+        "output_name": "05运输@01混凝土运输@02正洞混凝土运输"
+      },
+      {
+        "start_page": 113,
+        "end_page": 116,
+        "output_name": "05运输@01混凝土运输@03通过辅助坑道运输混凝土"
+      },
+      {
+        "start_page": 117,
+        "end_page": 118,
+        "output_name": "05运输@02材料运输@01正洞运输材料"
+      },
+      {
+        "start_page": 119,
+        "end_page": 122,
+        "output_name": "05运输@02材料运输@02通过辅助坑道材料运输"
+      }
+    ]
+  },
+  {
+    "output_dir": "06洞门及明洞/",
+    "page_configs": [
+      {
+        "start_page": 125,
+        "end_page": 127,
+        "output_name": "06洞门及明洞@01洞门及明洞混凝土@01洞门及明洞混凝土"
+      },
+      {
+        "start_page": 128,
+        "end_page": 131,
+        "output_name": "06洞门及明洞@02洞门及明洞砌筑@01洞门及明洞砌筑"
+      },
+      {
+        "start_page": 132,
+        "end_page": 138,
+        "output_name": "06洞门及明洞@03洞门附属@01洞门附属"
+      },
+      {
+        "start_page": 139,
+        "end_page": 142,
+        "output_name": "06洞门及明洞@04明洞附属@01明洞附属"
+      }
+    ]
+  },
+  {
+    "output_dir": "07辅助坑道/",
+    "page_configs": [
+      {
+        "start_page": 145,
+        "end_page": 148,
+        "output_name": "07辅助坑道@01辅助坑道开挖@01斜井(轨道矿车提升运输)"
+      },
+      {
+        "start_page": 149,
+        "end_page": 151,
+        "output_name": "07辅助坑道@01辅助坑道开挖@02斜井(汽车运输)"
+      },
+      {
+        "start_page": 152,
+        "end_page": 155,
+        "output_name": "07辅助坑道@01辅助坑道开挖@03平行导坑(轨道运输)"
+      },
+      {
+        "start_page": 156,
+        "end_page": 158,
+        "output_name": "07辅助坑道@01辅助坑道开挖@04平行导坑(汽车运输)"
+      },
+      {
+        "start_page": 159,
+        "end_page": 164,
+        "output_name": "07辅助坑道@01辅助坑道开挖@05竖井"
+      },
+      {
+        "start_page": 165,
+        "end_page": 166,
+        "output_name": "07辅助坑道@02出砟运输@01斜井(轨道矿车提升运输)"
+      },
+      {
+        "start_page": 167,
+        "end_page": 167,
+        "output_name": "07辅助坑道@02出砟运输@02斜井(汽车运输)"
+      },
+      {
+        "start_page": 168,
+        "end_page": 169,
+        "output_name": "07辅助坑道@02出砟运输@03平行导坑(轨道运输)"
+      },
+      {
+        "start_page": 170,
+        "end_page": 170,
+        "output_name": "07辅助坑道@02出砟运输@04平行导坑(汽车运输)"
+      },
+      {
+        "start_page": 171,
+        "end_page": 174,
+        "output_name": "07辅助坑道@02出砟运输@05竖井(提升运输)"
+      },
+      {
+        "start_page": 175,
+        "end_page": 176,
+        "output_name": "07辅助坑道@03衬砌@01混凝土集中拌制"
+      },
+      {
+        "start_page": 177,
+        "end_page": 177,
+        "output_name": "07辅助坑道@03衬砌@02混凝土浇筑"
+      },
+      {
+        "start_page": 178,
+        "end_page": 179,
+        "output_name": "07辅助坑道@04通风@01斜井"
+      },
+      {
+        "start_page": 180,
+        "end_page": 181,
+        "output_name": "07辅助坑道@04通风@02平行导坑"
+      },
+      {
+        "start_page": 182,
+        "end_page": 183,
+        "output_name": "07辅助坑道@04通风@03竖井"
+      },
+      {
+        "start_page": 184,
+        "end_page": 185,
+        "output_name": "07辅助坑道@05管线路@01斜井"
+      },
+      {
+        "start_page": 186,
+        "end_page": 187,
+        "output_name": "07辅助坑道@05管线路@02平行导坑"
+      },
+      {
+        "start_page": 188,
+        "end_page": 189,
+        "output_name": "07辅助坑道@05管线路@03竖井"
+      },
+      {
+        "start_page": 190,
+        "end_page": 191,
+        "output_name": "07辅助坑道@06混凝土运输@01斜井"
+      },
+      {
+        "start_page": 192,
+        "end_page": 193,
+        "output_name": "07辅助坑道@06混凝土运输@02平行导坑"
+      },
+		{
+        "start_page": 194,
+        "end_page": 195,
+        "output_name": "07辅助坑道@06混凝土运输@03竖井"
+      },
+      {
+        "start_page": 196,
+        "end_page": 197,
+        "output_name": "07辅助坑道@07材料运输@01斜井"
+      },
+      {
+        "start_page": 198,
+        "end_page": 199,
+        "output_name": "07辅助坑道@07材料运输@02平行导坑"
+      },
+      {
+        "start_page": 200,
+        "end_page": 201,
+        "output_name": "07辅助坑道@07材料运输@03竖井"
+      },
+      {
+        "start_page": 202,
+        "end_page": 203,
+        "output_name": "07辅助坑道@08辅助坑道模板、台架@01组合钢模架"
+      },
+      {
+        "start_page": 204,
+        "end_page": 206,
+        "output_name": "07辅助坑道@08辅助坑道模板、台架@02开挖台架"
+      }
+    ]
+  },
+  {
+    "output_dir": "08超前地质预报及监控量测/",
+    "page_configs": [
+      {
+        "start_page": 209,
+        "end_page": 210,
+        "output_name": "08超前地质预报及监控量测@01超前地质预报探测@01风钻加深炮孔超前水平探测"
+      },
+      {
+        "start_page": 211,
+        "end_page": 213,
+        "output_name": "08超前地质预报及监控量测@01超前地质预报探测@02钻机冲击钻超前水平探测"
+      },
+      {
+        "start_page": 214,
+        "end_page": 216,
+        "output_name": "08超前地质预报及监控量测@01超前地质预报探测@03钻机钻孔取芯超前水平探测"
+      },
+      {
+        "start_page": 217,
+        "end_page": 218,
+        "output_name": "08超前地质预报及监控量测@01超前地质预报探测@04地震波反射法物理探测"
+      },
+      {
+        "start_page": 219,
+        "end_page": 220,
+        "output_name": "08超前地质预报及监控量测@02施工监控量测@01施工监控量测"
+      }
+    ]
+  },
+  {
+    "output_dir": "09改扩建工程/",
+    "page_configs": [
+      {
+        "start_page": 223,
+        "end_page": 224,
+        "output_name": "09改扩建工程@01围岩开挖@01围岩开挖"
+      },
+      {
+        "start_page": 225,
+        "end_page": 227,
+        "output_name": "09改扩建工程@02圬工凿除@01圬工凿除"
+      },
+      {
+        "start_page": 228,
+        "end_page": 229,
+        "output_name": "09改扩建工程@03洞身衬砌@01洞身衬砌"
+      },
+      {
+        "start_page": 230,
+        "end_page": 230,
+        "output_name": "09改扩建工程@04出砟@01出砟"
+      },
+      {
+        "start_page": 231,
+        "end_page": 232,
+        "output_name": "09改扩建工程@05支护@01喷射混凝土"
+      },
+      {
+        "start_page": 233,
+        "end_page": 235,
+        "output_name": "09改扩建工程@05支护@02锚杆"
+      },
+      {
+        "start_page": 236,
+        "end_page": 238,
+        "output_name": "09改扩建工程@05支护@03钢筋网、格栅钢架、型钢钢架"
+      },
+      {
+        "start_page": 239,
+        "end_page": 239,
+        "output_name": "09改扩建工程@06防水和排水@01防水板"
+      },
+      {
+        "start_page": 240,
+        "end_page": 241,
+        "output_name": "09改扩建工程@06防水和排水@02衬砌背后压浆"
+      },
+      {
+        "start_page": 242,
+        "end_page": 242,
+        "output_name": "09改扩建工程@06防水和排水@03盲沟、止水带、透水软管"
+      },
+      {
+        "start_page": 243,
+        "end_page": 246,
+        "output_name": "09改扩建工程@06防水和排水@04漏水处理"
+      },
+      {
+        "start_page": 247,
+        "end_page": 249,
+        "output_name": "09改扩建工程@07其他@01线路加固"
+      },
+      {
+        "start_page": 250,
+        "end_page": 250,
+        "output_name": "09改扩建工程@07其他@02管线路铺拆"
+      },
+      {
+        "start_page": 251,
+        "end_page": 252,
+        "output_name": "09改扩建工程@07其他@03管线路使用费、照明用电"
+      }
+    ]
+  },
+  {
+    "output_dir": "10隧道机械化施工/",
+    "page_configs": [
+      {
+        "start_page": 255,
+        "end_page": 256,
+        "output_name": "10隧道机械化施工@01凿岩台车机械化开挖@01凿岩台车机械化开挖"
+      },
+      {
+        "start_page": 257,
+        "end_page": 259,
+        "output_name": "10隧道机械化施工@02辅助坑道机械化出砟@01正洞自斜井底皮带机出砟(配合钻爆法施工)"
+      },
+      {
+        "start_page": 260,
+        "end_page": 261,
+        "output_name": "10隧道机械化施工@02辅助坑道机械化出砟@02平导出砟(挖装机装砟)"
+      },
+      {
+        "start_page": 262,
+        "end_page": 263,
+        "output_name": "10隧道机械化施工@03衬砌机械化施工@01衬砌台车及模架"
+      },
+      {
+        "start_page": 264,
+        "end_page": 265,
+        "output_name": "10隧道机械化施工@03衬砌机械化施工@02栈桥"
+      },
+      {
+        "start_page": 266,
+        "end_page": 267,
+        "output_name": "10隧道机械化施工@03衬砌机械化施工@03防水板机械自动铺设"
+      },
+      {
+        "start_page": 268,
+        "end_page": 269,
+        "output_name": "10隧道机械化施工@04支护机械化施工@01湿喷机械手喷射混凝土"
+      },
+      {
+        "start_page": 270,
+        "end_page": 272,
+        "output_name": "10隧道机械化施工@04支护机械化施工@02凿岩台车锚杆作业"
+      }
+    ]
+  }
+]

+ 4 - 0
SourceCode/DataMiddleware/tools/pdf_split/__init__.py

@@ -0,0 +1,4 @@
+from tools.pdf_split.processor import PDFProcessor
+from tools.pdf_split.model import SplitModel
+
+__all__ = ['SplitModel', 'PDFProcessor']

+ 26 - 0
SourceCode/DataMiddleware/tools/pdf_split/model.py

@@ -0,0 +1,26 @@
+from dataclasses import dataclass
+from typing import List
+
+@dataclass
+class PageConfig:
+    """单个PDF拆分页面配置
+    
+    Attributes:
+        start_page (int): 起始页码
+        end_page (int): 结束页码
+        output_name (str): 输出文件名称
+    """
+    start_page: int
+    end_page: int
+    output_name: str
+
+@dataclass
+class SplitModel:
+    """PDF拆分配置结构体
+    
+    Attributes:
+        output_dir (str): 输出相对目录路径,将与外部指定的基础目录拼接形成最终输出路径
+        page_configs (List[PageConfig]): 页面拆分配置列表
+    """
+    output_dir: str
+    page_configs: List[PageConfig]

+ 73 - 0
SourceCode/DataMiddleware/tools/pdf_split/mysql_store.py

@@ -0,0 +1,73 @@
+from tools.utils.mysql_helper import MySQLHelper
+from datetime import datetime
+from typing import Optional, Dict
+
+class MysqlStore:
+    def __init__(self):
+        self._db_helper = MySQLHelper()
+
+    # ------------------ 标准表基础操作 ------------------
+    # ------------------ 标准表基础操作 ------------------
+    def create_standard(self, code: str, name: str) -> int:
+        """创建标准记录,返回插入ID"""
+        sql = """INSERT INTO pdf_standard 
+                   (standard_code, standard_name, created_at, updated_at)
+                   VALUES (%s, %s, NOW(), NOW())"""  # 改用NOW()获取完整时间戳
+        with self._db_helper as db:
+            return db.execute(sql, (code, name))
+
+    def get_standard(self, standard_id: int) -> Optional[Dict]:
+        """根据ID获取标准"""
+        sql = "SELECT * FROM pdf_standard WHERE id = %s"
+        with self._db_helper as db:
+            return db.fetch_one(sql, (standard_id,))
+    def get_all_standards(self):
+        sql = "SELECT * FROM pdf_standard ORDER BY created_at DESC"
+        with self._db_helper as db:
+            return db.execute_query(sql)
+
+    def get_all_pdf_records(self):
+        sql = "SELECT chapter_name, pdf_path, created_at FROM pdf_records ORDER BY created_at DESC"
+        with self._db_helper as db:
+            return db.execute_query(sql)
+    # ------------------ PDF记录表核心操作 ------------------
+    def add_pdf_record(
+            self,
+            standard_name: str,
+            pdf_path: str,
+            image_path: str,
+            markdown_text: str,
+            chapter: str = "",
+            section: str = "",
+            subsection: str = ""
+    ) -> int:
+        """添加PDF处理记录,返回插入ID"""
+        sql = """INSERT INTO pdf_records 
+                   (standard_name, chapter_name, section_name, subsection_name,
+                    pdf_path, image_path, markdown_text, created_at, updated_at)
+                   VALUES (%s, %s, %s, %s, %s, %s, %s, NOW(), NOW())"""  # 增加时间字段
+        params = (standard_name, chapter, section, subsection,
+                  pdf_path, image_path, markdown_text)
+        with self._db_helper as db:
+            return db.execute(sql, params)
+
+    def update_markdown(self, record_id: int, new_text: str) -> bool:
+        """更新Markdown内容"""
+        sql = "UPDATE pdf_records SET markdown_text = %s, updated_at = NOW() WHERE id = %s"
+        with self._db_helper as db:
+            return db.execute(sql, (new_text, record_id))
+
+    def get_standard_by_name(self, name: str) -> dict:
+        """根据标准名称获取标准"""
+        sql = "SELECT * FROM pdf_standard WHERE standard_name = %s"
+        with self._db_helper as db:
+            return db.fetch_one(sql, (name,))
+
+    def update_pdf_record(self, markdown_text: str, image_paths: str, by_image_path: str):
+        """更新已有的PDF记录"""
+        with self._db_helper as db:
+            sql = """UPDATE pdf_records 
+                     SET markdown_text = %s, 
+                         image_path = %s 
+                     WHERE image_path LIKE %s"""
+            db.execute(sql, (markdown_text, image_paths, by_image_path))

+ 507 - 0
SourceCode/DataMiddleware/tools/pdf_split/processor.py

@@ -0,0 +1,507 @@
+import json,os,time
+import re
+from typing import List
+from PyPDF2 import PdfReader, PdfWriter
+from tools.pdf_split.model import SplitModel,PageConfig
+from PIL import Image
+import io
+import pymupdf
+import tools.utils as utils
+from tools.utils.file_helper import encode_image
+from tools.utils.ai_helper import AiHelper
+from tools.pdf_split.mysql_store import MysqlStore
+
+class PDFProcessor:
+    """PDF处理器类,负责执行PDF文件的拆分操作"""
+
+    def __init__(self):
+        pass
+    
+    s="""
+    按照我提供的目录整理信息,页数要准确,要求:
+    1. 每章的标题精确到每节的一、二..的页数。  例如 第一章 第一节 一、xxxx 二、xxxx 。
+    2. 返回的结构体:```typescript
+    type PageConfig = {
+      start_page: number; // 起始页码
+      end_page: number; // 结束页码
+      output_name: string; // 输出文件名称
+    };
+    type SplitModel = {
+      output_dir: string; // 输出目录
+      page_configs: PageConfig[]; // 页面配置数组
+    };
+  ```
+    3. 输出文件名 格式 章@节@小节.pdf 例如 第一章 第一节 一、xxxx 的格式为 01xxxx@01xxxx@01xxxx.pdf (xxxx为具体标题内容)
+    4. 输出目录路径为 章节/ 例如 第一章 就是 01xxxx/
+    5. 目录一定要完整,不能有遗漏,不能有多余的目录
+    6. 帮我整理1,2,3,4,5,6,7,8,9,10章的目录信息,并返回SplitModel的json数组,一个章节一个SplitModel
+    7. end_page不能与下一个start_page相同
+    """
+
+    _generated_pdfs = []  # 类变量,用于存储生成的PDF文件路径
+
+    @staticmethod
+    def split(filename:str)-> None:
+        """将PDF文件按指定配置拆分成多个PDF文件"""
+        version = "v1"
+        base_json_dir = "./tools/pdf_json/"
+        base_input_dir = "./temp_files/pdf/source/"
+        base_output_dir = "./temp_files/pdf/output/"
+        json_file = f"{base_json_dir}/{version}/{filename}.json"
+        input_file = f"{base_input_dir}/{version}/{filename}.pdf"
+        output_dir = f"{base_output_dir}/{version}/{filename}/"
+        # 清空生成的PDF文件列表
+        PDFProcessor._generated_pdfs = []
+
+        try:
+            # 读取并解析JSON文件
+            with open(json_file, 'r', encoding='utf-8') as f:
+                json_data = json.load(f)
+
+            # 将JSON数据转换为SplitModel对象列表
+            split_models = []
+            for item in json_data:
+                page_configs = [PageConfig(**page) for page in item['page_configs']]
+                split_model = SplitModel(output_dir=item['output_dir'], page_configs=page_configs)
+                split_models.append(split_model)
+
+            # 调用batch_split_pdf进行处理
+            PDFProcessor.batch_split_pdf(input_file, output_dir, split_models)
+            
+            # 所有PDF文件拆分完成后,执行图片转换
+            PDFProcessor.convert_pdf_images(PDFProcessor._generated_pdfs)
+            
+            
+            print("PDF文件拆分成功!")
+        except FileNotFoundError:
+            print(f"错误: 找不到JSON文件 {json_file}")
+            return
+        except json.JSONDecodeError as e:
+            print(f"错误: JSON文件格式无效 {str(e)}")
+            return
+        except Exception as e:
+            print(f"处理过程中发生错误: {str(e)}")
+            return
+
+    @staticmethod
+    def batch_split_pdf(input_file: str, base_output_dir: str, split_models: List[SplitModel]) -> None:
+        """批量处理多个PDF拆分任务
+
+        Args:
+            input_file: 输入PDF文件路径
+            base_output_dir: 基础输出目录路径
+            split_models: SplitModel配置对象数组
+        """
+        try:
+            for split_model in split_models:
+                try:
+                    PDFProcessor.split_pdf(input_file, base_output_dir, split_model)
+                except Exception as e:
+                    print(f"处理拆分任务时发生错误: {str(e)}")
+                    continue
+        except Exception as e:
+            print(f"批量处理PDF文件时发生错误: {str(e)}")
+            return
+
+    @staticmethod
+    def split_pdf(input_file: str, base_output_dir: str, split_model: SplitModel) -> None:
+        """将PDF文件按指定配置拆分成多个PDF文件,并为每个拆分的PDF文件执行图片转换
+
+        Args:
+            input_file: 输入PDF文件路径
+            base_output_dir: 基础输出目录路径
+            split_model: SplitModel配置对象
+        """
+        try:
+            # 确保输出目录存在
+            output_dir = os.path.join(f"{base_output_dir}pdf/", split_model.output_dir)
+            os.makedirs(output_dir, exist_ok=True)
+            
+            # 读取PDF文件
+            reader = PdfReader(input_file)
+            total_pages = len(reader.pages)
+
+            # 处理每个页面配置
+            for page_config in split_model.page_configs:
+                try:
+                    # 验证页码范围
+                    if page_config.start_page < 1 or page_config.end_page > total_pages or page_config.start_page > page_config.end_page:
+                        print(f"警告: 页码范围 {page_config.start_page}-{page_config.end_page} 无效,已跳过")
+                        continue
+                    
+                    # 创建新的PDF文件
+                    writer = PdfWriter()
+                    
+                    # 添加指定范围的页面
+                    for page_num in range(page_config.start_page - 1, page_config.end_page):
+                        writer.add_page(reader.pages[page_num])
+                    
+                    # 生成输出文件名
+                    output_name = page_config.output_name
+                    if not page_config.output_name.endswith(".pdf"):
+                        output_name = f"{output_name}.pdf"
+                    output_file = os.path.join(output_dir, output_name)
+                    
+                    # 保存拆分后的PDF文件
+                    with open(output_file, 'wb') as output:
+                        writer.write(output)
+                        
+                    print(f"成功创建文件: {output_file}")
+                    PDFProcessor._generated_pdfs.append(output_file)
+                    
+                except Exception as e:
+                    print(f"处理页面配置时发生错误: {str(e)}")
+                    continue
+        except Exception as e:
+            print(f"处理PDF文件时发生错误: {str(e)}")
+            return
+    @staticmethod
+    def convert_pdf_images(generated_pdfs: List[str]) -> None:
+        """处理PDF文件的图片转换
+
+        Args:
+            generated_pdfs: 生成的PDF文件路径列表
+        """
+        print("开始处理图片转换...")
+        for pdf_file in generated_pdfs:
+            try:
+                result = PDFProcessor.extract_and_merge_images(pdf_file)
+                if not result:
+                    print(f"图片转换失败: {pdf_file}")
+            except Exception as e:
+                print(f"图片转换过程中发生错误: {str(e)}")
+                continue
+    @staticmethod
+    def extract_and_merge_images(input_file: str, output_file: str = None) -> str:
+        try:
+            pdf_document = pymupdf.open(input_file)
+            # 根据输入路径生成图片目录
+            output_name = os.path.splitext(os.path.basename(input_file))[0]
+            parts = input_file.rsplit('/pdf/', 1)
+            output_file = '/pdf/'.join(parts[:-1]) + '/img/' + parts[-1]
+            output_dir = os.path.splitext(output_file)[0]
+
+            #output_dir = output_file + f'/{output_name}/'
+            os.makedirs(output_dir, exist_ok=True)
+
+            # 遍历每一页提取图片
+            for page_num in range(pdf_document.page_count):
+                page = pdf_document[page_num]
+                # 获取页面上的所有图片,包括内嵌图片
+                # pix = page.get_pixmap(matrix=pymupdf.Matrix(2, 2))  # 实际使用的缩放参数
+                # img_data = pix.tobytes("png")
+
+                # 初始化压缩参数
+                scale = 1.0
+                img_data = None
+                max_size = 200 * 1024
+                # 循环调整缩放直到符合大小要求
+                while scale >= 0.5:  # 最小缩放比例50%
+                    # 生成临时图片数据
+                    temp_pix = page.get_pixmap(matrix=pymupdf.Matrix(1.5 * scale, 1.5 * scale))
+                    img_data = temp_pix.tobytes("png")
+
+                    if len(img_data) <= max_size:  # 100KB限制
+                        break
+                    scale *= 0.9  # 每次缩小10%
+                # 生成序列文件名
+                img_path = os.path.join(output_dir, f"{page_num + 1:02d}.png")
+                # 保存单页图片
+                with open(img_path, 'wb') as f:
+                    f.write(img_data)
+                print(f"成功保存图片({len(img_data) // 1024}KB): {img_path}")
+            return output_dir
+
+        except Exception as e:
+            print(f"处理图片时发生错误: {str(e)}")
+            return ''
+
+    # @staticmethod
+    # def extract_and_merge_images(input_file: str, output_file: str = None) -> str:
+    #     try:
+    #         pdf_document = pymupdf.open(input_file)
+    #         images = []
+    #         total_height = 0
+    #         max_width = 0
+    #
+    #         # 遍历每一页提取图片
+    #         for page_num in range(pdf_document.page_count):
+    #             page = pdf_document[page_num]
+    #
+    #             # 获取页面上的所有图片,包括内嵌图片
+    #             pix = page.get_pixmap(matrix=pymupdf.Matrix(2, 2))  # 使用2倍缩放以获得更好的质量
+    #             img_data = pix.tobytes("png")
+    #
+    #             # 将图片字节转换为PIL Image对象
+    #             image = Image.open(io.BytesIO(img_data))
+    #             if image.mode != 'RGB':
+    #                 image = image.convert('RGB')
+    #
+    #             images.append(image)
+    #             total_height += image.height
+    #             max_width = max(max_width, image.width)
+    #
+    #         # 如果没有找到图片
+    #         if not images:
+    #             print("未在PDF中找到任何图片")
+    #             return ''
+    #
+    #         # 创建新的图片用于拼接
+    #         merged_image = Image.new('RGB', (max_width, total_height))
+    #         y_offset = 0
+    #
+    #         # 将所有图片垂直拼接
+    #         for img in images:
+    #             x_offset = (max_width - img.width) // 2
+    #             merged_image.paste(img, (x_offset, y_offset))
+    #             y_offset += img.height
+    #
+    #         # 设置输出路径
+    #         if output_file is None:
+    #             parts = input_file.rsplit('/pdf/', 1)
+    #             output_file = '/pdf/'.join(parts[:-1]) + '/img/' + parts[-1]
+    #             output_file = os.path.splitext(output_file)[0] + "_merged.png"
+    #             os.makedirs(os.path.dirname(output_file), exist_ok=True)
+    #
+    #         # 根据图片数量计算目标大小
+    #         target_size_per_image = 200 * 1024  # 每张图片100KB
+    #         max_size = target_size_per_image * len(images)
+    #         scale = 1.0
+    #         quality = 95
+    #
+    #         while True:
+    #             temp_buffer = io.BytesIO()
+    #             if scale < 1.0:
+    #                 new_size = (int(merged_image.width * scale), int(merged_image.height * scale))
+    #                 resized_image = merged_image.resize(new_size, Image.Resampling.LANCZOS)
+    #                 resized_image.save(temp_buffer, 'PNG', optimize=True, quality=quality)
+    #             else:
+    #                 merged_image.save(temp_buffer, 'PNG', optimize=True, quality=quality)
+    #
+    #             size = temp_buffer.tell()
+    #
+    #             if size <= max_size:
+    #                 with open(output_file, 'wb') as f:
+    #                     f.write(temp_buffer.getvalue())
+    #                     print(f"成功保存图片:[{(size // 1024)} KB] {output_file}")
+    #                 break
+    #
+    #             if scale > 0.5:
+    #                 scale *= 0.9
+    #             else:
+    #                 # 如果达到最小缩放比例,直接保存当前结果
+    #                 with open(output_file, 'wb') as f:
+    #                     f.write(temp_buffer.getvalue())
+    #                     print(f"成功保存图片:[{(size // 1024)} KB] {output_file}")
+    #                 break
+    #
+    #         return output_file
+    #
+    #     except Exception as e:
+    #         print(f"处理图片时发生错误: {str(e)}")
+    #         return ''
+    @staticmethod
+    def process_image_to_txt(filename: str):
+        """将目录下的多张图片合并生成一个Markdown文件"""
+        version = "v1"
+        base_output_dir = "./temp_files/pdf/output/"
+        output_dir = f"{base_output_dir}/{version}/{filename}/"
+        image_dir = f"{output_dir}/img/"
+        txt_dir = f"{output_dir}/txt/"
+
+        db_store = MysqlStore()
+        ai_helper = AiHelper()
+
+        # 创建标准记录(如果不存在)
+        if not db_store.get_standard_by_name(filename):
+            db_store.create_standard(code=filename, name=filename)
+
+        try:
+            # 遍历图片目录中的每个子目录(新增目录处理逻辑)
+            for dir_path, dir_names, file_names in os.walk(image_dir):
+                # 跳过根目录
+                if dir_path == image_dir:
+                    continue
+
+                # 解析目录结构(新增章节解析)
+                dir_rel_path = os.path.relpath(dir_path, image_dir)
+
+                chapter_parts = dir_rel_path.split('@')
+                if len(chapter_parts) < 3:
+                    continue  # 跳过不符合命名规范的目录
+
+                # 生成对应的txt目录
+                #txt_subdir = os.path.join(txt_dir, dir_rel_path)
+                #os.makedirs(txt_subdir, exist_ok=True)
+
+                # 收集当前目录的所有图片(新增图片收集逻辑)
+                image_files = sorted(
+                    [f for f in file_names if f.lower().endswith(('.png', '.jpg', '.jpeg'))],
+                    key=lambda x: int(x.split('.')[0])
+                )
+
+                if not image_files:
+                    continue
+
+                # 创建合并的markdown文件(修改文件生成逻辑)
+                md_filename = f"{dir_rel_path.replace('@', '_')}.md"
+                md_path = os.path.join(txt_dir, md_filename)
+                os.makedirs(os.path.dirname(md_path), exist_ok=True)
+                md_content = f"# {filename}\n## {'/'.join(chapter_parts)}\n\n"
+
+                # 处理目录下所有图片(新增合并循环)
+                all_images = []
+                for img_file in image_files:
+                    
+                    img_path = os.path.join(dir_path, img_file)
+                    img_name = os.path.basename(img_path)
+                    try:
+                        # 调用AI分析图片
+                        page_content = ai_helper.analyze_image_with_ai(img_path)
+                        # 添加5秒延时以控制API请求频率
+                        time.sleep(5)
+                        # 生成图片相对路径
+                        #rel_path = os.path.relpath(img_path, txt_subdir)
+                        utils.get_logger().info(f"处理图片 {img_path} 成功")
+                        md_content += f"########### {img_path} ####################\n"
+                        md_content += f"--start{img_name}--\n\n"
+                        md_content += f"\n\n{page_content}\n\n"
+                        md_content += f"--end{img_name}--\n\n"
+                        all_images.append(img_path)
+                    except Exception as e:
+                        print(f"处理图片 {img_file} 失败: {str(e)}")
+                        continue
+
+                # 保存合并的文档(修改保存逻辑)
+                with open(md_path, 'w', encoding='utf-8') as f:
+                    f.write(md_content)
+
+                # 插入数据库记录(新增批量记录)
+                db_store.add_pdf_record(
+                    standard_name=filename,
+                    pdf_path=os.path.abspath(all_images[0].replace("/img/", "/pdf/").rsplit('.', 1)[0] + '.pdf'),
+                    image_path='\n'.join([os.path.abspath(p) for p in all_images]),
+                    markdown_text=md_content,
+                    chapter=chapter_parts[0],
+                    section=chapter_parts[1],
+                    subsection=chapter_parts[2]
+                )
+
+                print(f"成功生成合并文档: {md_path}")
+
+        except Exception as e:
+            print(f"处理过程中发生错误: {str(e)}")
+
+    @staticmethod
+    def regenerate_markdown(img_path: str):
+        """重新生成指定图片或目录的Markdown内容"""
+        processor = PDFProcessor()
+        db_store = MysqlStore()
+        ai_helper = AiHelper()
+
+        if os.path.isdir(img_path):
+            # 处理整个目录
+            dir_path = img_path
+            # 通过图片路径反向推导标准名称和目录结构
+            parts = dir_path.split('/img/')
+            if len(parts) < 2:
+                print("无效的目录路径")
+                return
+
+            # 获取原Markdown文件路径
+            txt_root = dir_path.replace('/img/', '/txt/')
+            md_files = [f for f in os.listdir(txt_root) if f.endswith('.md')]
+            if not md_files:
+                print("找不到对应的Markdown文件")
+                return
+
+            md_path = os.path.join(txt_root, md_files[0])
+            # 重新生成整个目录内容
+            processor._process_directory(dir_path, md_path, db_store, ai_helper)
+
+        elif os.path.isfile(img_path):
+            # 处理单个图片
+            img_file = os.path.basename(img_path)
+            dir_path = os.path.dirname(img_path)
+            # 查找对应的Markdown文件
+            txt_dir = dir_path.replace('/img/', '/txt/')
+            md_files = [f for f in os.listdir(txt_dir) if f.endswith('.md')]
+            if not md_files:
+                print("找不到对应的Markdown文件")
+                return
+
+            md_path = os.path.join(txt_dir, md_files[0])
+            # 更新单个图片内容
+            processor._update_single_image(img_path, md_path, db_store, ai_helper)
+
+    def _process_directory(self, dir_path: str, md_path: str, db_store, ai_helper):
+        """处理整个目录重新生成"""
+        # 收集目录下所有图片
+        image_files = sorted(
+            [f for f in os.listdir(dir_path) if f.lower().endswith(('.png', '.jpg', '.jpeg'))],
+            key=lambda x: int(x.split('.')[0])
+        )
+
+        # 重新生成Markdown内容
+        new_content = f"# {os.path.basename(os.path.dirname(md_path))}\n"
+        all_images = []
+
+        for img_file in image_files:
+            img_path = os.path.join(dir_path, img_file)
+            page_content = ai_helper.analyze_image_with_ai(img_path)
+            img_name = os.path.splitext(img_file)[0]
+
+            new_content += f"########### {img_path} ####################\n"
+            new_content += f"--start{img_name}--\n\n"
+            new_content += f"\n\n{page_content}\n\n"
+            new_content += f"--end{img_name}--\n\n"
+            all_images.append(img_path)
+
+        # 写入更新后的内容
+        with open(md_path, 'w', encoding='utf-8') as f:
+            f.write(new_content)
+
+        # 更新数据库记录
+        db_store.update_pdf_record(
+            markdown_text=new_content,
+            image_paths=','.join(all_images),
+            by_image_path=all_images[0]
+        )
+
+    def _update_single_image(self, img_path: str, md_path: str, db_store, ai_helper):
+        """更新单个图片内容"""
+        img_name = os.path.splitext(os.path.basename(img_path))[0]
+
+        # 读取原有内容
+        with open(md_path, 'r', encoding='utf-8') as f:
+            content = f.read()
+
+        # 生成新内容
+        start_tag = f"--start{img_name}--"
+        end_tag = f"--end{img_name}--"
+        pattern = re.compile(f'{re.escape(start_tag)}(.*?){re.escape(end_tag)}', re.DOTALL)
+
+        # 调用AI重新分析
+        new_content = ai_helper.analyze_image_with_ai(img_path)
+        updated_section = f"{start_tag}\n\n{new_content}\n\n{end_tag}"
+
+        # 替换内容
+        new_md_content = re.sub(pattern, updated_section, content)
+
+        # 写入更新后的内容
+        with open(md_path, 'w', encoding='utf-8') as f:
+            f.write(new_md_content)
+
+        # 更新数据库记录
+        # db_store.update_pdf_record(
+        #     markdown_text=new_md_content,
+        #     image_paths=img_path,
+        #     by_image_path=img_path
+        # )
+
+
+
+
+
+
+

+ 53 - 0
SourceCode/DataMiddleware/tools/test/fast_gpt_client.py

@@ -0,0 +1,53 @@
+import yaml
+import requests
+from pathlib import Path
+from tools.utils import get_config
+
+
+class FastGPTClient:
+    def __init__(self):
+        self.config = get_config()
+        self.headers = {
+            "Content-Type": "application/json",
+            "Authorization": f"Bearer {self.config.get('fastgpt.key')}"
+        }
+
+
+
+    def chat(self, prompt: str, temperature: float = 0.7) -> str:
+        """与FastGPT进行对话"""
+        payload = {
+            "chatId": "my_chatId111",
+            "stream": False,
+            "detail": False,
+            "messages": [
+                {
+                    "role": "user",
+                    "content": prompt
+                }
+            ]
+        }
+
+        try:
+            response = requests.post(
+                self.config.get('fastgpt.url'),
+                headers=self.headers,
+                json=payload,
+                timeout=500
+            )
+            response.raise_for_status()
+
+            data = response.json()
+            return data['choices'][0]['message']['content']
+        except Exception as e:
+            print(f"API调用失败: {str(e)}")
+            raise e
+
+
+if __name__ == "__main__":
+    try:
+        client = FastGPTClient()
+        response = client.chat("总结一下知识库总定额编号相关的内容")
+        print("FastGPT回复:", response)
+    except Exception as e:
+        print(f"发生异常: {str(e)}")

+ 116 - 0
SourceCode/DataMiddleware/tools/utils/__init__.py

@@ -0,0 +1,116 @@
+"""
+utils/__init__.py
+
+该模块初始化文件,导入了多个辅助工具类,并定义了一系列便捷函数,用于日志记录、配置管理、文件操作、字符串处理和邮件发送等功能。
+"""
+import json
+
+from tools.utils.ai_helper import AiHelper
+from tools.utils.config_helper import ConfigHelper
+from tools.utils.logger_helper import LoggerHelper
+#import tools.utils.logger_helper as logger_helper
+
+
+
+def get_logger():
+    """
+    获取日志记录器实例。
+
+    该函数通过调用LoggerHelper类的静态方法get_logger()来获取一个日志记录器实例。
+    主要用于需要记录日志的位置,通过该函数获取日志记录器实例,然后进行日志记录。
+    这样做可以保持日志记录的一致性和集中管理。
+
+    :return: Logger实例,用于记录日志。
+    """
+    #return logger_helper.LoggerHelper.get_logger()
+    return LoggerHelper.get_logger()
+
+
+def clean_log_file(day: int):
+    """
+    清理指定天数之前的日志文件。
+
+    :param day: 整数,表示清理多少天前的日志文件。
+    """
+    #logger_helper.LoggerHelper.clean_log_file(day)
+    LoggerHelper.clean_log_file(day)
+
+
+def get_config():
+    """
+    获取配置管理器实例。
+
+    该函数返回一个ConfigHelper实例,用于读取和管理应用程序的配置信息。
+
+    :return: ConfigHelper实例,用于配置管理。
+    """
+    return ConfigHelper()
+
+
+def reload_config():
+    """
+    重新加载配置文件。
+
+    该函数会重新加载配置文件中的内容,适用于配置文件发生更改后需要重新加载的情况。
+    """
+    get_config().load_config()
+
+
+def get_config_value(key: str, default: str = None):
+    """
+    获取配置项的值。
+
+    :param key: 字符串,配置项的键。
+    :param default: 字符串,默认值(可选)。
+    :return: 配置项的值,如果不存在则返回默认值。
+    """
+    return get_config().get(key, default)
+
+
+def get_config_int(key: str, default: int = None):
+    """
+    获取配置项的整数值。
+
+    :param key: 字符串,配置项的键。
+    :param default: 整数,默认值(可选)。
+    :return: 配置项的整数值,如果不存在则返回默认值。
+    """
+    return get_config().get_int(key, default)
+
+
+def get_config_bool(key: str):
+    """
+    获取配置项的布尔值。
+
+    :param key: 字符串,配置项的键。
+    :return: 配置项的布尔值。
+    """
+    return get_config().get_bool(key)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+def call_openai(system_prompt: str, user_prompt: str) -> json:
+    """
+    调用OpenAI API进行对话。
+
+    :param system_prompt: 字符串,系统提示信息。
+    :param user_prompt: 字符串,用户输入的提示信息。
+    :return: JSON对象,API返回的结果。
+    """
+    return AiHelper().call_openai(system_prompt, user_prompt)
+
+
+

+ 162 - 0
SourceCode/DataMiddleware/tools/utils/ai_helper.py

@@ -0,0 +1,162 @@
+import json
+import re
+
+import tools.utils as utils
+from tools.utils.file_helper import encode_image
+from openai import OpenAI
+
+
+class AiHelper:
+
+    _ai_api_key = None
+    _ai_api_url = None
+    _ai_max_tokens = 150
+
+    def __init__(self, api_url: str=None, api_key: str=None, api_model: str=None):
+        self._ai_api_url = api_url if api_url else utils.get_config_value("ai.url")
+        self._ai_api_key = api_key if api_key else utils.get_config_value("ai.key")
+        self._api_model = api_model if api_model else utils.get_config_value("ai.model")
+        max_tokens = utils.get_config_value("ai.max_tokens")
+        if max_tokens:
+            self._ai_max_tokens = int(max_tokens)
+
+    def call_openai(self, system_prompt: str, user_prompt: str,api_url: str=None,api_key: str=None,api_model: str=None) -> json:
+        if api_url:
+            self._ai_api_url = api_url
+        if api_key:
+            self._ai_api_key = api_key
+        if api_model:
+            self._api_model = api_model
+        if self._ai_api_key is None:
+            raise Exception("AI API key 没有配置")
+        if self._ai_api_url is None:
+            raise Exception("AI API url 没有配置")
+        if self._api_model is None:
+            raise Exception("AI API model 没有配置")
+
+        utils.get_logger().info(f"调用AI API ==> Url:{self._ai_api_url},Model:{self._api_model}")
+
+        client = OpenAI(api_key=self._ai_api_key, base_url=self._ai_api_url)
+        completion = client.chat.completions.create(
+            model=self._api_model,
+            messages=[
+                {
+                    "role": "system",
+                    "content": system_prompt,
+                },
+                {
+                    "role": "user",
+                    "content": user_prompt,
+                },
+            ],
+            stream=False,
+            temperature=0.7,
+            response_format={"type": "json_object"},
+            # max_tokens=self._ai_max_tokens,
+        )
+        try:
+            response = completion.model_dump_json()
+            result = {}
+            response_json = json.loads(response)
+            res_str = self._extract_message_content(response_json)
+            result_data = self._parse_response(res_str, True)
+            if result_data:
+                result["data"] = result_data
+                usage = response_json["usage"]
+                result["completion_tokens"] = usage.get("completion_tokens", 0)
+                result["prompt_tokens"] = usage.get("prompt_tokens", 0)
+                result["total_tokens"] = usage.get("total_tokens", 0)
+                utils.get_logger().info(f"AI Process JSON: {result}")
+            else:
+                utils.get_logger().info(f"AI Response: {response}")
+            return result
+        except Exception as e:
+            raise Exception(f"解析 AI 响应错误: {e}")
+
+    @staticmethod
+    def _extract_message_content(response_json: dict) -> str:
+        utils.get_logger().info(f"AI Response JSON: {response_json}")
+        if "choices" in response_json and len(response_json["choices"]) > 0:
+            choice = response_json["choices"][0]
+            message_content = choice.get("message", {}).get("content", "")
+        elif "message" in response_json:
+            message_content = response_json["message"].get("content", "")
+        else:
+            raise Exception("AI 响应中未找到有效的 choices 或 message 数据")
+
+        # 移除多余的 ```json 和 ```
+        if message_content.startswith("```json") and message_content.endswith(
+                "```"):
+            message_content = message_content[6:-3]
+
+        # 去除开头的 'n' 字符
+        if message_content.startswith("n"):
+            message_content = message_content[1:]
+        # 移除无效的转义字符和时间戳前缀
+        message_content = re.sub(r"\\[0-9]{2}", "",
+                                 message_content)  # 移除 \32 等无效转义字符
+        message_content = re.sub(r"\d{4}-\d{2}-\dT\d{2}:\d{2}:\d{2}\.\d+Z", "",
+                                 message_content)  # 移除时间戳
+        message_content = message_content.strip()  # 去除首尾空白字符
+
+        # 替换所有的反斜杠
+        message_content = message_content.replace("\\", "")
+
+        return message_content
+
+    def _parse_response(self, response: str, first=True) -> json:
+        # utils.get_logger().info(f"AI Response JSON STR: {response}")
+        try:
+            data = json.loads(response)
+            return data
+
+        except json.JSONDecodeError as e:
+            if first:
+                utils.get_logger().error(f"JSON 解析错误,去除部分特殊字符重新解析一次: {e}")
+                # 替换中文引号为空
+                message_content = re.sub(r"[“”]", "", response)  # 替换双引号
+                message_content = re.sub(r"[‘’]", "", message_content)  # 替换单引号
+                return self._parse_response(message_content, False)
+            else:
+                raise Exception(f"解析 AI 响应错误: {response} {e}")
+
+    def analyze_image_with_ai(self,image_path, api_url: str=None,api_key: str=None,api_model: str=None):
+        """调用OpenAI的API分析图片内容"""
+        if api_url:
+            self._ai_api_url = api_url
+        if api_key:
+            self._ai_api_key = api_key
+        if api_model:
+            self._api_model = api_model
+        if self._ai_api_key is None:
+            raise Exception("AI API key 没有配置")
+        if self._ai_api_url is None:
+            raise Exception("AI API url 没有配置")
+        if self._api_model is None:
+            raise Exception("AI API model 没有配置")
+        try:
+            client = OpenAI(api_key=self._ai_api_key, base_url=self._ai_api_url)
+            base64_str = encode_image(image_path)
+            response = client.chat.completions.create(
+                model=self._api_model,
+                messages=[
+                    {
+                        "role": "user",
+                        "content": [
+                            {"type": "text",
+                             "text": "请总结图片中的表格,供RAG系统embedding使用。要求以文本的信息列出,定额编号对应的详细信息,其中表格的列名中显示了定额编号,行名中显示了电算代号。定额编号所示的列代表了这一类定额,通过项目的不同条件来区分,比如长度、地质条件等;而电算代号所示的行则代表了具体的材料、人工等的消耗量,表示在特定定额编号所示的条件下,具体的资源(人力或材料)消耗量。"},
+                            {
+                                "type": "image_url",
+                                "image_url": {
+                                    "url": base64_str
+                                }
+                            }
+                        ]
+                    }
+                ],
+                timeout=600
+            )
+            return response.choices[0].message.content
+        except Exception as e:
+            print(f"调用AI接口时出错: {e}")
+        return ''

+ 79 - 0
SourceCode/DataMiddleware/tools/utils/config_helper.py

@@ -0,0 +1,79 @@
+import os
+
+import yaml
+
+
+class ConfigHelper:
+    _instance = None
+
+    # 默认配置文件路径
+    default_config_path = os.path.join(os.path.dirname(__file__), "..", "config.yml")
+
+    # 类变量存储加载的配置
+    _config = None
+    _path = None
+
+    def __new__(cls, *args, **kwargs):
+        if not cls._instance:
+            cls._instance = super(ConfigHelper, cls).__new__(cls)
+        return cls._instance
+
+    def load_config(self, path=None):
+        if self._config is None:
+            if not path:
+                # print(f"使用默认配置文件:{self.default_config_path}")
+                self._path = self.default_config_path
+            else:
+                self._path = path
+            if not os.path.exists(self._path):
+                raise FileNotFoundError(f"没有找到文件或目录:'{self._path}'")
+        with open(self._path, "r", encoding="utf-8") as file:
+            self._config = yaml.safe_load(file)
+        # 合并环境变量配置
+        self._merge_env_vars()
+        # print(f"加载的配置文件内容:{self._config}")
+        return self._config
+
+    def _merge_env_vars(self, env_prefix="APP_"):  # 环境变量前缀为 APP_
+        for key, value in os.environ.items():
+            if key.startswith(env_prefix):
+                config_key = key[len(env_prefix) :].lower()
+                self._set_nested_key(self._config, config_key.split("__"), value)
+
+    def _set_nested_key(self, config, keys, value):
+        if len(keys) > 1:
+            if keys[0] not in config or not isinstance(config[keys[0]], dict):
+                config[keys[0]] = {}
+            self._set_nested_key(config[keys[0]], keys[1:], value)
+        else:
+            config[keys[0]] = value
+
+    def get(self, key: str, default: str = None):
+        if self._config is None:
+            self.load_config(self._path)
+        keys = key.split(".")
+        config = self._config
+        for k in keys:
+            if isinstance(config, dict) and k in config:
+                config = config[k]
+            else:
+                return default
+        return config
+
+    def get_bool(self, key: str) -> bool:
+        val = str(self.get(key, "0"))
+        return True if val.lower() == "true" or val == "1" else False
+
+    def get_int(self, key: str, default: int = 0) -> int:
+        val = self.get(key)
+        if not val:
+            return default
+        try:
+            return int(val)
+        except ValueError:
+            return default
+
+    def get_all(self):
+        if self._config is None:
+            self.load_config(self._path)
+        return self._config

+ 17 - 0
SourceCode/DataMiddleware/tools/utils/file_helper.py

@@ -0,0 +1,17 @@
+import os
+import mimetypes
+import base64
+
+
+
+
+def encode_image(path: str):
+    # 根据文件扩展名获取 MIME 类型
+    mime_type, _ = mimetypes.guess_type(path)
+    if mime_type is None:
+        mime_type = 'image/jpeg'  # 默认使用 jpeg 类型
+    # 将图片编码为 base64 字符串
+    with open(path, "rb") as image_file:
+        encoded_string = base64.b64encode(image_file.read())
+        base64Str = encoded_string.decode("utf-8")
+        return f"data:{mime_type};base64,{base64Str}"

+ 113 - 0
SourceCode/DataMiddleware/tools/utils/logger_helper.py

@@ -0,0 +1,113 @@
+import logging
+import os
+from datetime import datetime
+from logging.handlers import TimedRotatingFileHandler
+
+from tools.utils.config_helper import ConfigHelper
+
+
+class LoggerHelper:
+    """
+    日志辅助类,用于创建和提供日志记录器实例
+    该类实现了单例模式,确保在整个应用程序中只有一个日志记录器实例被创建和使用
+    """
+
+    _instance = None
+    config = ConfigHelper()
+    _log_file_name = f"{config.get("logger.file_name", "log")}.log"
+    _log_file_path = config.get("logger.file_path", "./logs")
+    _log_level_string = config.get("logger.level", "INFO")
+
+    def __new__(cls, *args, **kwargs):
+        """
+        实现单例模式,确保日志记录器仅被创建一次
+        如果尚未创建实例,则创建并初始化日志记录器
+        """
+        if not cls._instance:
+            cls._instance = super(LoggerHelper, cls).__new__(cls, *args, **kwargs)
+            try:
+                cls._instance._initialize_logger()
+            except Exception as e:
+                raise Exception(f"配置logger出错: {e}")
+        return cls._instance
+
+    @property
+    def logger(self):
+        return self._logger
+
+    def _initialize_logger(self):
+        """
+        初始化日志记录器,包括设置日志级别、创建处理器和格式化器,并将它们组合起来
+        """
+        log_level = self._get_log_level()
+        self._logger = logging.getLogger("app_logger")
+        self._logger.setLevel(log_level)
+
+        if not os.path.exists(self._log_file_path):
+            os.makedirs(self._log_file_path)
+
+        # 创建按日期分割的文件处理器
+        file_handler = TimedRotatingFileHandler(
+            os.path.join(self._log_file_path, self._log_file_name),
+            when="midnight",
+            interval=1,
+            backupCount=7,
+            encoding="utf-8",
+        )
+        file_handler.setLevel(log_level)
+
+        # 创建控制台处理器
+        console_handler = logging.StreamHandler()
+        console_handler.setLevel(logging.DEBUG)
+
+        # 创建格式化器
+        formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
+
+        # 将格式化器添加到处理器
+        file_handler.setFormatter(formatter)
+        console_handler.setFormatter(formatter)
+
+        # 将处理器添加到日志记录器
+        self._logger.addHandler(file_handler)
+        self._logger.addHandler(console_handler)
+
+    def _get_log_level(self):
+        try:
+            # 尝试将字符串转换为 logging 模块中的日志级别常量
+            log_level = getattr(logging, self._log_level_string.upper())
+            if not isinstance(log_level, int):
+                raise ValueError
+            return log_level
+        except (AttributeError, ValueError):
+            raise ValueError(
+                f"配置logger出错: Unknown level: '{self._log_level_string}'"
+            )
+
+    @classmethod
+    def get_logger(cls):
+        """
+        提供初始化后的日志记录器实例
+        :return: 初始化后的日志记录器实例
+        """
+        if not cls._instance:
+            cls._instance = cls()
+        return cls._instance._logger
+
+    @classmethod
+    def clean_log_file(cls, day: int):
+        if not os.path.exists(cls._log_file_path):
+            return
+        for filename in os.listdir(cls._log_file_path):
+            if filename != cls._log_file_name and filename.startswith(
+                cls._log_file_name
+            ):
+                try:
+                    file_path = os.path.join(cls._log_file_path, filename)
+                    file_time = datetime.strptime(
+                        filename.replace(f"{cls._log_file_name}.", ""), "%Y-%m-%d"
+                    )
+                    if (datetime.now() - file_time).days > day:
+                        os.remove(file_path)
+                        cls.get_logger().info(f"  删除日志文件: {file_path}")
+                except Exception as e:
+                    cls.get_logger().error(f"删除日志文件出错: {filename} {e}")

+ 117 - 0
SourceCode/DataMiddleware/tools/utils/mysql_helper.py

@@ -0,0 +1,117 @@
+import pymysql
+import tools.utils as utils
+from pymysql.cursors import DictCursor
+
+
+class MySQLHelper:
+
+    def __init__(self):
+        try:
+            self.host = utils.get_config_value("mysql.host")
+            self.user = utils.get_config_value("mysql.user")
+            self.password = utils.get_config_value("mysql.password")
+            self.db = utils.get_config_value("mysql.db")
+            self.port = int(utils.get_config_value("mysql.port"))
+            self.charset = utils.get_config_value("mysql.charset")
+            self.connection = None
+        except Exception as e:
+            utils.get_logger().error(f"加载数据库配置文件失败: {e}")
+
+    def connect(self):
+        try:
+            self.connection = pymysql.connect(
+                host=self.host,
+                user=self.user,
+                password=self.password,
+                db=self.db,
+                port=self.port,
+                charset=self.charset,
+                cursorclass=DictCursor,
+            )
+            # utils.get_logger().info(f"成功连接到数据库:{self.db}。")
+        except pymysql.MySQLError as e:
+            utils.get_logger().error(
+                f"数据库连接失败: {self.host}:{self.port} {self.db}"
+            )
+            self.connection = None  # 确保连接失败时设置为 None
+            raise Exception(f"连接数据库失败: {e}")
+
+    def disconnect(self):
+        if self.connection and self.connection.open:
+            self.connection.close()
+            # utils.get_logger().info("数据库连接已关闭。")
+
+    def execute_query(self, query, params=None):
+        try:
+            with self.connection.cursor() as cursor:
+                cursor.execute(query, params)
+                result = cursor.fetchall()
+                return result
+        except pymysql.MySQLError as e:
+            utils.get_logger().error(f"执行查询时出错:{e}")
+            return None
+
+    def execute_non_query(self, query, params=None):
+        if isinstance(params, list) and all(isinstance(p, tuple) for p in params):
+            self.execute_many(query, params)
+        elif isinstance(params, tuple):
+            self.execute(query, params)
+        else:
+            self.execute(query, (params,))
+
+    def execute(self, query, params=None):
+        try:
+            with self.connection.cursor() as cursor:
+                cursor.execute(query, params)
+                self.connection.commit()
+        except pymysql.MySQLError as e:
+            utils.get_logger().error(f"执行非查询时出错:{e}")
+            self.connection.rollback()
+
+    def execute_many(self, query, params: list):
+        if isinstance(params, list) and all(isinstance(p, tuple) for p in params):
+            try:
+                with self.connection.cursor() as cursor:
+                    cursor.executemany(query, params)
+                    self.connection.commit()
+            except pymysql.MySQLError as e:
+                utils.get_logger().error(f"执行非查询时出错:{e}")
+                self.connection.rollback()
+        else:
+            raise ValueError("参数必须是元组列表")
+
+    def fetch_one(self, query, params=None):
+        try:
+            with self.connection.cursor() as cursor:
+                cursor.execute(query, params)
+                result = cursor.fetchone()
+                return result
+        except pymysql.MySQLError as e:
+            utils.get_logger().error(f"获取一条记录时出错:{e}")
+            return None
+
+    def __enter__(self):
+        """
+        当进入上下文时自动调用此方法。
+        它负责建立连接,并将当前实例返回,以便在上下文中使用。
+
+        :return: 返回实例本身,以便在上下文中使用。
+        """
+
+        self.connect()  # 建立连接
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        """
+        当退出上下文时自动调用此方法。
+        无论上下文中的代码是否完成或因异常退出,此方法都会被调用,以确保断开连接。
+
+        :param exc_type: 异常类型, 如果没有异常则为None。
+        :param exc_value: 异常值, 如果没有异常则为None。
+        :param traceback: 异常的traceback对象, 如果没有异常则为None。
+        """
+        if exc_type:
+            utils.get_logger().error(
+                f"数据库发生异常,断开连接。异常类型:{exc_type}, 异常值:{exc_value} traceback: {traceback}"
+            )
+        self.disconnect()  # 断开连接