Prechádzať zdrojové kódy

update 调整页面

YueYunyun 3 mesiacov pred
rodič
commit
6d1d36acc3

+ 14 - 5
SourceCode/DataMiddleware/app/ai/fast_gpt.py

@@ -144,16 +144,25 @@ class FastGPTAi:
             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())
+                if "```json" in content:
+                    # 使用正则表达式提取代码块内容
+                    code_block_match = re.search(r'```(json)?(.*?)```', content, re.DOTALL)
+                    if code_block_match:
+                        content = code_block_match.group(2).strip()
+                    content = 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
+                    self._logger.error(f"JSON解析失败,尝试清理特殊字符: {e}")
+                    # 尝试清理特殊字符后重新解析
+                    content = re.sub(r'[“”‘’\x00-\x1f]', '', content)
+                    try:
+                        return json.loads(content)
+                    except json.JSONDecodeError:
+                        self._logger.error("最终JSON解析失败,返回原始内容")
+                        return {'raw_content': content}
             return None
         else:
             error_msg = f"Error: {response.status_code} - {response.text}"

+ 9 - 1
SourceCode/DataMiddleware/app/data_process/__init__.py

@@ -1,5 +1,5 @@
 from data_process import pre_process,process
-from models.project_data import SubProjectModel
+from models.project_data import SubProjectModel,SubProjectItemModel
 
 
 def process_project(project: SubProjectModel) -> bool:
@@ -32,3 +32,11 @@ def process_data(project: SubProjectModel) -> bool:
     :return: None
     """
     return process.Process().run(project)
+
+def process_item(project_item: SubProjectItemModel) -> bool:
+    """
+    处理数据
+    :param project_item: 项目明细
+    :return: None
+    """
+    return process.Process().process_item(project_item)

+ 93 - 50
SourceCode/DataMiddleware/app/data_process/pre_process.py

@@ -1,4 +1,4 @@
-import utils, pandas as pd,os
+import utils, pandas as pd, os, json
 from pathlib import Path
 
 from models.project_data import SubProjectModel, SubProjectItemModel
@@ -18,10 +18,12 @@ class PreProcess:
 
     def run(self, project: SubProjectModel, separate_ai_calls=True) ->bool:
         try:
-            self._logger.info(f"开始预处理项目:{project.sub_project_name}[{project.project_no}_{project.standard_version}] ")
+            if not project.file_paths:
+                return False
+            self._logger.info(f"开始预处理项目:{project.sub_project_name}[{project.project_id}_{project.standard_version}] ")
             self._store.update_sub_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/{project.project_no}/")
+            raw_data = self.read_excels(project.file_paths)
             if self.separate_ai_calls:
                 for filename, sheets in raw_data.items():
                     excel_data = self.normalize_data({filename: sheets})
@@ -32,15 +34,45 @@ class PreProcess:
             project.items = self._data[project.id].items
             self._store.re_insert_sub_project(project)
             del self._data[project.id]
-            self._logger.info(f"结束预处理项目:{project.sub_project_name}[{project.project_no}_{project.standard_version}] [设备条数:{len(project.items)}]")
+            self._logger.info(f"结束预处理项目:{project.sub_project_name}[{project.project_id}_{project.standard_version}] [设备条数:{len(project.items)}]")
             self._store.update_sub_project_status(project.id, 31)
             return True
         except Exception as e:
-            self._logger.error(f"预处理项目失败:[{project.project_no}] 错误: {e}")
+            self._logger.error(f"预处理项目失败:[{project.project_id}] 错误: {e}")
             self._store.update_sub_project_status(project.id, 11)
             return False
 
-    def read_excels(self, folder_path):
+    def read_excels(self, paths):
+        file_paths = paths.split(',')
+        raw_data = {}
+        for file in file_paths:
+            try:
+                file = Path(file)  # 将 file 转换为 Path 对象
+                if not file.exists():
+                    self._logger.error(f"文件不存在:{file}")
+                    continue
+                # 使用确定的表头行读取整个 Excel 文件
+                # 读取所有sheet
+                sheets = pd.read_excel(file, sheet_name=None)
+                sheet_data = {}
+                for sheet_name, df in sheets.items():
+                    # 读取前几行以确定表头
+                    header_row = self.determine_header_row(df, file.name)
+                    if header_row >= 0:
+                        # 使用确定的表头行重新读取Sheet
+                        df = df.iloc[header_row:]  # 直接使用确定的表头行
+                        df.columns = df.iloc[0]  # 设置表头
+                        df = df[1:]  # 去掉表头行
+                        df.reset_index(drop=True, inplace=True)  # 重置索引
+                    sheet_data[sheet_name] = df
+                raw_data[file.name] = sheet_data
+                self._logger.debug(f"读取 {file.name} 的 {len(sheet_data)} 个sheet")
+            except Exception as e:
+                self._logger.error(f"读取 {file.name} 失败: {e}")
+        return raw_data
+
+
+    def read_excels_1(self, folder_path):
         path = Path(folder_path)
         # 验证路径是否存在
         if not path.exists():
@@ -63,7 +95,7 @@ class PreProcess:
                 sheet_data = {}
                 for sheet_name, df in sheets.items():
                     # 读取前几行以确定表头
-                    header_row = self.determine_header_row(df,file.name)
+                    header_row = self.determine_header_row(df, file.name)
                     if header_row >= 0:
                         # 使用确定的表头行重新读取Sheet
                         df = df.iloc[header_row:]  # 直接使用确定的表头行
@@ -140,7 +172,6 @@ class PreProcess:
                 prompt = self.prompt_template([data])
                 response = self._ai.call_ai(prompt,api_key)
                 project.items.extend(self.format_data(project_id, response))
-                # 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}
                 # 更新数据部分
                 # project.items.extend(self.format_data(project_id, response["data"]))
                 # project.completion_tokens += response["completion_tokens"]
@@ -156,8 +187,6 @@ class PreProcess:
             # project.total_tokens = response["total_tokens"]
             # project.items = self.format_data(project_id, response["data"])
         self._data[project_id] = project
-
-
     def format_data(self,project_id,new_data) ->list[SubProjectItemModel]:
         formatted_data = []
         for data in new_data:
@@ -182,54 +211,68 @@ class PreProcess:
 
         return formatted_data
 
+    def run_1(self, sub_project: SubProjectModel) ->bool:
+        self._logger.info(f"开始预处理项目:{sub_project.sub_project_name}[{sub_project.project_id}_{sub_project.standard_version}] ")
+        base_path=  utils.get_config_value("file.source_path", "./temp_files")
+        folder_path= os.path.join(base_path, f'{sub_project.get_path()}')
+        path = Path(folder_path)
+        # 验证路径是否存在
+        if not path.exists():
+            raise FileNotFoundError(f"目录不存在: {path.resolve()}")
 
-    def run_1(self, project: SubProjectModel) ->bool:
-        self._logger.info(f"开始预处理项目:{project.sub_project_name}[{project.project_no}_{project.standard_version}] ")
-        self._store.update_sub_project_status(project.id, 21)
-        file_path =  f"{utils.get_config_value("file.source_path", "./temp_files")}/project/{project.project_no}/"
-        file = os.listdir(file_path)[0]
-        if not file:
-            self._logger.error(f"项目:{project.project_no} 没有找到文件")
-            return False
-        if not self.check_file_type(file):
-            self._logger.error(f"项目:{project.project_no} 文件格式不正确")
-            return False
+        # 验证是否是目录
+        if not path.is_dir():
+            raise NotADirectoryError(f"路径不是目录: {path.resolve()}")
+
+        excel_files = list(path.glob('*'))
+
+        if not excel_files:
+            self._logger.warning(f"警告:未找到任何文件")
+            raise FileNotFoundError(f"未找到任何文件")
         try:
-            prompt="""从上传的表格数据中提取信息,要求:
-            1. 识别字段类型:数值/文本/日期
-            2. 提取信息结构体:```typescript
-                type item { 
-                    n: string; //物料名称
-                    m: string; //型号规格
-                    u:string; //单位
-                    c: float; //数量,数量多列的话要求和
-                }
-            ```
-            3. 返回压缩成一行的item数组的json字符串
-            """
-            api_key= utils.get_config_value("fastgpt.api_key_pre_process")
-            data = self._ai.call_ai_with_file(file_path+file, prompt,api_key)
-            # data = utils.AiHelper().call_openai_with_file(file_path+file,"", user_prompt=prompt,api_model="qwen-max")
-            if isinstance(data, str):
-                import json
-                data= json.loads(data)
-            res_data = self.format_data(project.id, data)
-            if len(res_data)<=0:
-                self._logger.error(f"项目:{project.project_no} 文件处理失败: {data}")
-                self._store.update_sub_project_status(project.id, 11)
-                return False
-            project.items = res_data
-            self._store.re_insert_sub_project(project)
+            self._store.update_sub_project_status(sub_project.id, 21)
+            items =[]
+            for file in excel_files:
+                file_path = str(file.resolve())
+                data = self.call_ai_with_files(sub_project.id, file_path)
+                if data:
+                    items.extend(data)
+            sub_project.items = items
+            self._store.re_insert_sub_project(sub_project)
             self._logger.info(
-                f"结束预处理项目:{project.sub_project_name}[{project.project_no}_{project.standard_version}] [设备条数:{len(project.items)}]")
-            self._store.update_sub_project_status(project.id, 31)
+                f"结束预处理项目:{sub_project.sub_project_name}[{sub_project.project_id}_{sub_project.standard_version}] [设备条数:{len(sub_project.items)}]")
+            self._store.update_sub_project_status(sub_project.id, 31)
             return True
         except Exception as e:
-            self._logger.error(f"项目:{project.project_no} 文件处理失败: {e}")
-            self._store.update_sub_project_status(project.id, 11)
+            self._logger.error(f"项目:{sub_project.project_id} 文件处理失败: {e}")
+            self._store.update_sub_project_status(sub_project.id, 11)
             return False
 
 
+    def call_ai_with_files(self, sub_id:int,file_paths:str) ->json:
+        try:
+            prompt = """从上传的表格数据中提取信息,要求:
+                       1. 识别字段类型:数值/文本/日期
+                       2. 提取信息结构体:```typescript
+                           type item { 
+                               n: string; //物料名称
+                               m: string; //型号规格
+                               u:string; //单位
+                               c: float; //数量,数量多列的话要求和
+                           }
+                       ```
+                       3. 返回压缩成一行的item数组的json字符串
+                       """
+            api_key = utils.get_config_value("fastgpt.api_key_pre_process")
+            data = self._ai.call_ai_with_file(file_paths, prompt, api_key)
+            if isinstance(data, str):
+                data = json.loads(data)
+            res_data = self.format_data(sub_id, data)
+            return res_data
+        except Exception as e:
+            self._logger.error(f"文件处理失败: {e}")
+            return ""
+
     @staticmethod
     def check_file_type(file_name:str) ->bool:
         file_type = file_name.split('.')[-1]

+ 23 - 6
SourceCode/DataMiddleware/app/data_process/process.py

@@ -14,14 +14,18 @@ class Process:
 
     def run(self, sub_project: SubProjectModel) ->bool:
         try:
-            self._logger.info(f"开始处理数据:{sub_project.sub_project_name}[{sub_project.id}]")
+            self._logger.debug(f"开始处理数据:{sub_project.sub_project_name}[{sub_project.id}]")
             self._store.update_sub_project_status(sub_project.id, 22)
             project_items = self._store.query_sub_project_items_by_project(sub_project.id)
             for project_item in project_items:
-                data = self.call_ai(project_item)
-                project_item.standard_no = data.standard_no
-                self._store.update_sub_project_item(project_item)
-            self._store.update_sub_project_status(sub_project.id, 12)
+                try:
+                    data = self.call_ai(project_item)
+                    self._store.update_sub_project_item_standard_no(project_item.id, data.standard_no)
+                except Exception as e:
+                    self._logger.error(f"处理数据失败:{e}")
+                    self._store.update_sub_project_item_process_status(project_item.id, 2)
+                    continue
+            self._store.update_sub_project_status(sub_project.id, 32)
             self._logger.info(f"处理数据完成:{sub_project.sub_project_name}[{sub_project.id}]")
             return True
         except Exception as e:
@@ -29,6 +33,19 @@ class Process:
             self._store.update_sub_project_status(sub_project.id, 12)
             return False
 
+    def process_item(self, sub_project_item: SubProjectItemModel) ->bool:
+        try:
+            self._logger.debug(f"开始处理明细:{sub_project_item.device_name}{sub_project_item.device_model}[{sub_project_item.id}]")
+            item = self._store.query_sub_project_item_by_id(sub_project_item.id)
+            data = self.call_ai(item)
+            self._store.update_sub_project_item_standard_no(sub_project_item.id, data.standard_no)
+            return True
+        except Exception as e:
+            self._logger.error(f"处理数据明细:{e}")
+            self._store.update_sub_project_item_process_status(sub_project_item.id, 2)
+            return False
+
+
     def call_ai(self, item: SubProjectItemModel) ->SubProjectItemModel:
         try:
             self._logger.info(f"开始处理数据:{item.id}")
@@ -55,7 +72,7 @@ class Process:
             5. 返回结构体item的json字符串,压缩成一行。
             6. 数据如下:
                 """
-            msg += item.to_ai_json()
+            msg += json.dumps(item.to_ai_json())
             api_key = utils.get_config_value("fastgpt.api_key_process")
             self._logger.info(f"开始调用AI:\n {msg}")
             json_data = self._ai.call_ai(msg, api_key)

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

@@ -1,5 +1,5 @@
 from data_send import  send
-from models.project_data import SubProjectModel
+from models.project_data import SubProjectModel,SubProjectItemModel
 
 
 def send_project_data(project: SubProjectModel) -> bool:
@@ -9,9 +9,15 @@ def send_project_data(project: SubProjectModel) -> bool:
     """
     return send.DataSend().send_by_project(project)
 
-def send_item_data(item_id: int) -> bool:
+def send_item_data_by_id(item_id: int) -> bool:
     """
     发送项目数据到平台
     :param item_id
     """
-    return send.DataSend().send_by_item_id(item_id)
+    return send.DataSend().send_by_item_id(item_id)
+def send_item_data(item: SubProjectItemModel) -> bool:
+    """
+    发送项目数据到平台
+    :param item
+    """
+    return send.DataSend().send_by_item(item)

+ 18 - 13
SourceCode/DataMiddleware/app/data_send/send.py

@@ -8,32 +8,37 @@ class DataSend:
         self._logger= utils.get_logger()
         self._store= MysqlStore()
 
-    def send_by_project(self, project: SubProjectModel) -> bool:
+    def send_by_project(self, sub_project: SubProjectModel) -> bool:
         try:
 
-            self._logger.info(f"开始发送数据,项目:{project.project_no}")
-            self._store.update_sub_project_status(project.id, 23)
-            data_list = self._store.query_sub_project_items_by_project(project.id, with_empty=False)
+            self._logger.info(f"开始发送数据,项目:{sub_project.sub_project_name}[{sub_project.project_id}_{sub_project.id}]")
+            self._store.update_sub_project_status(sub_project.id, 23)
+            data_list = self._store.query_sub_project_items_by_project(sub_project.id, with_empty=False)
             for data in data_list:
                 self.send_data(data)
-            self._store.update_sub_project_status(project.id, 33)
-            self._logger.info(f"发送数据成功,项目:{project.project_no} 共发送{len(data_list)}条")
+            self._store.update_sub_project_status(sub_project.id, 33)
+            self._logger.info(f"发送数据成功,项目:{sub_project.sub_project_name}[{sub_project.project_id}_{sub_project.id}] 共发送{len(data_list)}条")
             return True
         except Exception as e:
-            self._logger.error(f"发送数据失败,项目:{project.project_no},错误信息:{e}")
-            self._store.update_sub_project_status(project.id, 13)
+            self._logger.error(f"发送数据失败,项目:{sub_project.sub_project_name}[{sub_project.project_id}_{sub_project.id}],错误信息:{e}")
+            self._store.update_sub_project_status(sub_project.id, 13)
             return False
 
     def send_by_item_id(self, item_id: int) -> bool:
+        data = self._store.query_sub_project_item_by_id(item_id)
+        if data:
+            return self.send_by_item(data)
+        return False
+
+    def send_by_item(self, item: SubProjectItemModel) -> bool:
         try:
-            self._logger.info(f"开始发送数据,项目:{item_id}")
-            data = self._store.query_sub_project_item_by_id(item_id)
-            if self.send_data(data):
-                self._logger.info(f"发送数据成功,项目:{item_id}")
+            self._logger.info(f"开始发送数据,项目:{item.id}")
+            if self.send_data(item):
+                self._logger.info(f"发送数据成功,项目:{item.id}")
                 return True
             return False
         except Exception as e:
-            self._logger.error(f"发送数据失败,项目:{item_id},错误信息:{e}")
+            self._logger.error(f"发送数据失败,项目:{item.id},错误信息:{e}")
             return False
 
     def send_data(self, data: SubProjectItemModel) -> bool:

+ 1 - 0
SourceCode/DataMiddleware/app/init.sql

@@ -25,6 +25,7 @@ CREATE TABLE IF NOT EXISTS sub_project (
     work_catalog VARCHAR(255),
     work_content VARCHAR(255),
     standard_version VARCHAR(50),
+    file_paths TEXT,
     status TINYINT DEFAULT 0,
     is_del TINYINT DEFAULT 0,
     delete_by VARCHAR(255),

+ 4 - 0
SourceCode/DataMiddleware/app/models/project_data.py

@@ -30,6 +30,7 @@ class SubProjectModel:
                  work_catalog: str = None,
                  work_content: str = None,
                  standard_version: str = None,
+                 file_paths: str = None,
                  status: int = 0,
                  completion_tokens: int = 0,
                  prompt_tokens: int = 0,
@@ -48,6 +49,7 @@ class SubProjectModel:
         self.work_catalog = work_catalog
         self.work_content = work_content
         self.standard_version = standard_version
+        self.file_paths = file_paths
         self.status = status
         self.completion_tokens = completion_tokens
         self.prompt_tokens = prompt_tokens
@@ -63,6 +65,8 @@ class SubProjectModel:
     def __str__(self):
         return f"SubProjectModel(id={self.id}, project_no={self.project_id}, project_name={self.sub_project_name}, work_catalog={self.work_catalog}, work_content={self.work_content}, standard_version={self.standard_version}, status={self.status}, completion_tokens={self.completion_tokens}, prompt_tokens={self.prompt_tokens}, total_tokens={self.total_tokens}, update_time={self.update_time}, create_time={self.create_time})"
 
+    def get_path(self):
+        return f"project/p{self.project_id}/s{self.id}"
     def set_items(self, items: list):
         self.items = items
 

+ 18 - 4
SourceCode/DataMiddleware/app/stores/mysql_store.py

@@ -79,7 +79,7 @@ class MysqlStore:
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
     def query_sub_project_all(self):
-        sql = "SELECT id,project_id,sub_project_name,work_catalog,work_content,standard_version,status,create_time FROM sub_project WHERE is_del=0"
+        sql = "SELECT id,project_id,sub_project_name,work_catalog,work_content,standard_version,status,file_paths,create_time FROM sub_project WHERE is_del=0"
         with self._db_helper:
             data = []
             result = self._db_helper.execute_query(sql)
@@ -95,13 +95,14 @@ class MysqlStore:
                         work_content=item["work_content"],
                         standard_version=item["standard_version"],
                         status=item["status"],
+                        file_paths=item["file_paths"],
                         create_time=item["create_time"],
                     ))
             return data
     def query_sub_project_all_paginated(self, project_id: int, page: int, per_page: int, keyword: str = None, status: int = None) -> (list[SubProjectModel], None):
         offset = (page - 1) * per_page
         sql_count = "SELECT COUNT(*) as count FROM sub_project WHERE is_del=0 AND project_id=%s"
-        sql = "SELECT id,project_id,sub_project_name,work_catalog,work_content,standard_version,status,create_time FROM sub_project WHERE is_del=0 AND project_id=%s"
+        sql = "SELECT id,project_id,sub_project_name,work_catalog,work_content,standard_version,status,file_paths,create_time FROM sub_project WHERE is_del=0 AND project_id=%s"
         params_count = (project_id,)
         params =  (project_id,)
         q_1=" AND (sub_project_name LIKE %s)"
@@ -138,11 +139,12 @@ class MysqlStore:
                         work_content=item["work_content"],
                         standard_version=item["standard_version"],
                         status=item["status"],
+                        file_paths=item["file_paths"],
                         create_time=item["create_time"],
                     ))
         return data, count
     def query_sub_project(self, sub_project_id: int, with_items=False) -> SubProjectModel | None:
-        sql = "SELECT id,project_id,sub_project_name,work_catalog,work_content,standard_version,status,create_time FROM sub_project WHERE is_del=0 AND id = %s"
+        sql = "SELECT id,project_id,sub_project_name,work_catalog,work_content,standard_version,status,file_paths,create_time FROM sub_project WHERE is_del=0 AND id = %s"
         params = (sub_project_id,)
         sql_items = "SELECT id,project_id,sub_project_id,device_name,device_model,standard_version,standard_no,process_status,process_time,send_status,send_time FROM sub_project_item WHERE sub_project_id = %s"
         with self._db_helper:
@@ -156,6 +158,7 @@ class MysqlStore:
                         work_content=result["work_content"],
                         standard_version=result["standard_version"],
                         status=result["status"],
+                        file_paths=result["file_paths"],
                         sub_id=result["id"],
                         create_time=result["create_time"])
             if not with_items:
@@ -253,13 +256,14 @@ class MysqlStore:
                     ))
             return data
     def insert_sub_project(self, sub_project: SubProjectModel) -> int:
-        sql = "INSERT INTO sub_project (project_id,sub_project_name,work_catalog,work_content,standard_version,create_time,create_by) VALUES (%s,%s,%s,%s,%s,%s,%s)"
+        sql = "INSERT INTO sub_project (project_id,sub_project_name,work_catalog,work_content,standard_version,file_paths,create_time,create_by) VALUES (%s,%s,%s,%s,%s,%s,%s)"
         params = (
             sub_project.project_id,
             sub_project.sub_project_name,
             sub_project.work_catalog,
             sub_project.work_content,
             sub_project.standard_version,
+            sub_project.file_paths,
             datetime.now(),
             sub_project.create_by,
         )
@@ -299,6 +303,11 @@ class MysqlStore:
         )
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
+    def update_sub_project_file_path(self, sub_project_id: int, file_paths: str):
+        sql = "UPDATE sub_project SET file_paths = %s WHERE id = %s"
+        params = (file_paths, sub_project_id,)
+        with self._db_helper:
+            self._db_helper.execute_non_query(sql, params)
     def update_sub_project_status(self, sub_project_id:int, status:int):
         sql = "UPDATE sub_project SET status = %s,update_time = %s WHERE id = %s"
         params = (status, datetime.now(), sub_project_id,)
@@ -381,6 +390,11 @@ class MysqlStore:
         with self._db_helper:
             self._db_helper.execute_non_query(sql, params)
             return True
+    def update_sub_project_item_standard_no(self, item_id:int, standard_no:str):
+        sql = "UPDATE sub_project_item SET standard_no=%s,process_status = 1,process_time = %s,update_time=%s WHERE id = %s"
+        params = (standard_no, datetime.now(), datetime.now(), item_id,)
+        with self._db_helper:
+            self._db_helper.execute_non_query(sql, params)
     def update_sub_project_item_process_status(self, item_id:int, status:int):
         sql = "UPDATE sub_project_item SET process_status = %s,process_time = %s WHERE id = %s"
         params = (status, datetime.now(), item_id,)

+ 4 - 0
SourceCode/DataMiddleware/app/ui/__init__.py

@@ -1,6 +1,9 @@
 import os,utils
 from flask import Flask
 
+def basename_filter(path):
+    return os.path.basename(path)
+
 def create_app():
     # current_dir = os.path.dirname(os.path.abspath(__file__))
     # template_path = os.path.join(current_dir, 'templates')
@@ -9,5 +12,6 @@ def create_app():
     # 注册蓝图或其他初始化操作
     from .project_views import project as project_blueprint
     web_app.register_blueprint(project_blueprint)
+    web_app.jinja_env.filters['basename'] = basename_filter
 
     return web_app

+ 97 - 2
SourceCode/DataMiddleware/app/ui/project_services.py

@@ -1,4 +1,4 @@
-import threading,utils
+import threading,utils,os
 
 from models.project_data import ProjectModel,SubProjectModel, SubProjectItemModel
 from stores.mysql_store import MysqlStore
@@ -53,12 +53,76 @@ class ProjectService:
         project = self._store.query_sub_project(sub_project_id)
         return project
 
+    def save_sub_project(self, sub_project:SubProjectModel, project_files: list, delete_old: bool):
+        if sub_project.id:
+            sub_data = self._store.query_sub_project(sub_project.id)
+            if not sub_data:
+                return False, '子项目不存在'
+            sub_data.sub_project_name = sub_project.sub_project_name
+            sub_data.standard_version = sub_project.standard_version
+            sub_data.work_catalog = sub_project.work_catalog
+            sub_data.work_content = sub_project.work_content
+            sub_data.status = 0
+            self.update_sub_project(sub_data)
+        else:
+            sub_data = SubProjectModel(
+                project_id=sub_project.project_id,
+                sub_project_name=sub_project.sub_project_name,
+                standard_version=sub_project.standard_version,
+                work_catalog=sub_project.work_catalog,
+                work_content=sub_project.work_content,
+                file_paths="",
+                status=0
+            )
+            new_id =  self.add_sub_project(sub_data)
+            sub_data.id = new_id
+        try:
+            # 处理文件上传逻辑
+            if project_files:
+                file_paths = self._process_file_upload(sub_data, project_files, delete_old)
+                self.update_sub_project_file_paths(sub_data.id, file_paths)
+            elif delete_old:
+                return False,"请上传项目数据文件"
+            return True, ''
+        except ValueError as ve:
+            return False,str(ve)
+        except Exception as e:
+            return False,f'服务器错误: {str(e)}'
+
+    def _process_file_upload(self,sub_project: SubProjectModel, files: list,  delete_old: bool) -> str:
+        """处理文件上传流程"""
+        base_path = utils.get_config_value('file.source_path', './temp_files')
+        sub_project_dir = os.path.join(base_path, f'{sub_project.get_path()}')
+        os.makedirs(sub_project_dir, exist_ok=True)
+        self._logger.info(f"保存处理文件,项目ID:{sub_project.project_id},子项目ID:{sub_project.id}")
+        if delete_old:
+            if os.path.exists(sub_project_dir):
+                for filename in os.listdir(sub_project_dir):
+                    file_path = os.path.join(sub_project_dir, filename)
+                    if os.path.isfile(file_path):
+                        os.remove(file_path)
+        file_paths =[] if delete_old or not sub_project.file_paths else sub_project.file_paths.split(',')
+        for file in files:
+            if not file.filename:
+                continue
+            allowed_ext = {'xlsx', 'xls', 'csv'}
+            ext = file.filename.rsplit('.', 1)[-1].lower()
+            if ext not in allowed_ext:
+                continue
+            file_path = os.path.join(sub_project_dir, file.filename)
+            file_path = file_path.replace('\\', '/')
+            file.save(file_path)
+            file_paths.append(file_path)
+        return ','.join(file_paths)
+
     def add_sub_project(self, sub_project:SubProjectModel):
        return self._store.insert_sub_project(sub_project)
 
     def update_sub_project(self, sub_project:SubProjectModel):
         self._store.update_sub_project(sub_project)
 
+    def update_sub_project_file_paths(self, sub_project_id: int, paths: str):
+        self._store.update_sub_project_file_path(sub_project_id, paths)
     def start_sub_project_task(self, sub_project_id: int) -> (bool, str):
         data = self._store.query_sub_project(sub_project_id)
         if data:
@@ -156,7 +220,7 @@ class ProjectService:
             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.device_count = float(item.device_count)
             project_item.standard_no = item.standard_no
             self._store.update_sub_project_item(project_item)
             return True
@@ -169,3 +233,34 @@ class ProjectService:
             return True
         else:
             return False
+
+    def start_process_item(self, item_id:int):
+        project_item = self._store.query_sub_project_item_by_id(item_id)
+        if not project_item:
+            return False, '项目不存在'
+        thread = threading.Thread(target=self._process_sub_project_item, args=(project_item,))
+        thread.start()
+        return True,""
+
+    def _process_sub_project_item(self, sub_project_item:SubProjectItemModel):
+        if data_process.process_item(sub_project_item):
+            return True, ''
+        else:
+            self._logger.error(f"分析处理失败:{sub_project_item.device_name}")
+            return False, '分析处理失败'
+
+    def start_send_item(self, item_id:int):
+        project_item = self._store.query_sub_project_item_by_id(item_id)
+        if not project_item:
+            return False, '项目不存在'
+        thread = threading.Thread(target=self._send_sub_project_item, args=(project_item,))
+        thread.start()
+        return True,""
+
+    def _send_sub_project_item(self, sub_project_item:SubProjectItemModel):
+        if data_send.send_item_data(sub_project_item):
+            return True, ''
+        else:
+            self._logger.error(f"上传失败:{sub_project_item.device_name}")
+            return False, '上传数据失败'
+

+ 159 - 146
SourceCode/DataMiddleware/app/ui/project_views.py

@@ -1,8 +1,8 @@
-from multiprocessing.util import sub_debug
+from flask import Blueprint, render_template, request, redirect, url_for, jsonify, send_from_directory
+import os, utils
 
-from flask import render_template, url_for, redirect, request, jsonify
-from flask import Blueprint
-import os,utils
+def basename_filter(path):
+    return os.path.basename(path)
 
 from .project_services import ProjectService
 from models.project_data import ProjectModel, SubProjectModel,SubProjectItemModel
@@ -22,27 +22,27 @@ def main_project_list():
     per_page = request.args.get('pp', 15, type=int)
     keyword = request.args.get('k', '')
     projects, total_count = ProjectService.get_all_project_paginated(page, per_page, keyword)
-    return render_template('project_list.html', project_list=projects, page=page, per_page=per_page, total_count=total_count)
+    return render_template('project_list.html', project_list=projects, keyword=keyword, page=page, per_page=per_page, total_count=total_count)
 
 @project.route('/save_project', methods=['POST'])
 def save_project():
-    data = request.get_json()
-    project_id = data.get('id')
-    project_name = data.get('project_name')
-    project_desc = data.get('project_desc')
-    if not project_name:
-        return jsonify({'success': False, 'error': '项目编号和名称不能为空'}), 400
     try:
+        data = request.get_json()
+        project_id = data.get('project_id')
+        project_name = data.get('project_name')
+        project_desc = data.get('project_desc')
+        if not project_name:
+            return jsonify({'success': False, 'error': '项目编号和名称不能为空'}), 400
         if project_id:
             project_data = ProjectService.get_project_by_id(int(project_id))
             if not project_data:
                 return jsonify({'success': False, 'error': '项目编号不存在'}), 400
             project_data.project_name = project_name
             project_data.description = project_desc
-            result,msg = ProjectService.update_project(project_data)
+            result, msg = ProjectService.update_project(project_data)
         else:
             project_data = ProjectModel(project_name=project_name, description=project_desc)
-            result, msg =  ProjectService.add_project(project_data)
+            result, msg = ProjectService.add_project(project_data)
         if not result:
             return jsonify({'success': False, 'error': msg}), 400
         return jsonify({'success': True})
@@ -51,11 +51,35 @@ def save_project():
 
 @project.route('/delete_project/<int:project_id>', methods=['POST'])
 def delete_project(project_id:int):
-    success, message = ProjectService.delete_project(project_id)
-    if success:
-        return jsonify({'success': True})
-    return jsonify({'success': False, 'error': message}), 400
+    try:
+       success, message = ProjectService.delete_project(project_id)
+       if success:
+           return jsonify({'success': True}), 200
+       return jsonify({'success': False, 'error': message}), 400
+    except Exception as e:
+       return jsonify({'success': False, 'error': f'删除项目失败{str(e)}'}), 400
 
+
+@project.route('/download')
+def download_file():
+    filename = request.args.get('f', type=str)
+    if not filename:
+        return jsonify({'success': False, 'error': '文件名不能为空'}), 400
+    try:
+        # 安全处理文件名
+        pure_filename = os.path.basename(filename)
+        safe_filename = os.path.basename(pure_filename)
+        path = filename.replace(safe_filename, '')
+        upload_folder = os.path.abspath(os.path.join(os.getcwd(),path))
+        if not os.path.exists(upload_folder):
+            return jsonify({'success': False, 'error': '项目目录不存在'}), 404
+        full_path = os.path.join(upload_folder, safe_filename)
+        if not os.path.exists(full_path):
+            return jsonify({'success': False, 'error': '文件不存在'}), 404
+    except Exception as e:
+        utils.get_logger().error(f'路径构建失败:{str(e)}')
+        return jsonify({'success': False, 'error': f'非法文件路径{str(e)}'}), 400
+    return send_from_directory(upload_folder.replace('\\', '/'), safe_filename, as_attachment=True)
 @project.route('/sub_project_list/<int:project_id>')
 def sub_project_list(project_id:int):
     project_data = ProjectService.get_project_by_id(project_id)
@@ -65,158 +89,147 @@ def sub_project_list(project_id:int):
     status = request.args.get('s',-1, type=int)
     data_list, total_count = ProjectService.get_all_sub_project_paginated(project_id, page, per_page, keyword, None if status == -1 else status)  # 传递 status 参数
     return render_template('sub_project_list.html', project=project_data ,sub_project_list=data_list, keyword=keyword, page=page, per_page=per_page, total_count=total_count, status=status)  # 传递 status 参数到模板
-
 @project.route('/project_item_list/<sub_project_id>')
 def project_item_list(sub_project_id:int):
+    process_status = request.args.get('p_s', -1, type=int)
+    send_status = request.args.get('s_s', -1, type=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)
     sub_project_data= ProjectService.get_sub_project_by_id(sub_project_id)
     sub_items, total_count = ProjectService.get_sub_project_item_list_by_sub_project_paginated(sub_project_id, page, per_page, keyword)
-    return render_template('sub_project_item_list.html', sub_project=sub_project_data,items=sub_items, keyword=keyword, page=page, per_page=per_page, total_count=total_count)
-
+    return render_template('sub_project_item_list.html', sub_project=sub_project_data,items=sub_items, process_status=process_status, send_status=send_status, keyword=keyword, page=page, per_page=per_page, total_count=total_count)
 @project.route('/save_sub_project', methods=['POST'])
-def save_sub_project():
-    sub_id = request.form.get('id')
-    project_id = request.form.get('project_id')
-    sub_project_name = request.form.get('sub_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 sub_id:
-        sub_id= int(sub_id)
-        project_data = ProjectService.get_sub_project_by_id(sub_id)
-    if project_data:
-        project_data.sub_project_name = sub_project_name
-        project_data.standard_version = standard_version
-        project_data.work_catalog = work_catalog
-        project_data.work_content = work_content
-        ProjectService.update_sub_project(project_data)
-    else:
+def save_sub_project() -> tuple[jsonify, int]:
+    """处理子项目保存请求,包含文件上传和数据处理"""
+    try:
+        # 获取表单数据
+        sub_id = request.form.get('id', type=int)
+        project_id = request.form.get('project_id', type=int)
+        sub_project_name = request.form.get('sub_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 = request.form.get('delete_old_data', 'false') == 'true'
+        project_files = request.files.getlist('project_files')
+        # 获取或创建子项目对象
         project_data = SubProjectModel(
-                                       project_id=project_id,
-                                       sub_project_name=sub_project_name,
-                                       standard_version=standard_version,
-                                       work_catalog=work_catalog,
-                                       work_content=work_content,
-                                       status=0)
-        new_id= ProjectService.add_sub_project(project_data)
-        project_data.id = new_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:
-                del_path = os.path.join(base_path, f"project/sub_data_{project_data.id}")
-                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/sub_data_{project_data.id}")
-            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
-
-
-    return jsonify({'success': True})
+            sub_id=sub_id,
+            project_id=project_id,
+            sub_project_name=sub_project_name,
+            standard_version=standard_version,
+            work_catalog=work_catalog,
+            work_content=work_content,
+        )
+        res, msg = ProjectService.save_sub_project(project_data, project_files, delete_old)
+        if res:
+            return jsonify({'success': True}), 200
+        return jsonify({'success': False, 'error': msg}), 400
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)}), 400
 
 @project.route('/update_sub_project', methods=['POST'])
 def update_sub_project():
-    req = request.get_json()
-    sub_project_id = req.get('id')
-    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 = ProjectService.get_sub_project_by_id(sub_project_id)
-    if not project_data:
-        return jsonify({'success': False, 'error': '项目不存在'}), 404
-    data= SubProjectModel(sub_id=sub_project_id,
-                          sub_project_name=project_name,
-                          standard_version=standard_version,
-                          work_catalog=work_catalog,
-                          work_content=work_content,
-                          status=project_data.status)
-    ProjectService.update_sub_project(data)
-    return jsonify({'success': True})
-
+    try:
+        req = request.get_json()
+        sub_project_id = req.get('id')
+        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 = ProjectService.get_sub_project_by_id(sub_project_id)
+        if not project_data:
+            return jsonify({'success': False, 'error': '项目不存在'}), 404
+        data = SubProjectModel(sub_id=sub_project_id,
+                               sub_project_name=project_name,
+                               standard_version=standard_version,
+                               work_catalog=work_catalog,
+                               work_content=work_content,
+                               status=project_data.status)
+        ProjectService.update_sub_project(data)
+        return jsonify({'success': True})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)}), 400
 @project.route('/start_sub_project_task/<project_id>', methods=['POST'])
 def start_sub_project_task(project_id:int):
-    result, err_msg = ProjectService.start_sub_project_task(project_id)
-    return jsonify({'success': result, 'error': err_msg})
+   try:
+       result, err_msg = ProjectService.start_sub_project_task(project_id)
+       return jsonify({'success': result, 'error': err_msg})
+   except Exception as e:
+       return jsonify({'success': False, 'error': str(e)})
 
 @project.route('/start_process_sub_project/<project_id>', methods=['POST'])
 def start_process_sub_project(project_id:int):
-    result, err_msg = ProjectService.process_sub_project(project_id)
-    return jsonify({'success': result, 'error': err_msg})
+    try:
+        result, err_msg = ProjectService.process_sub_project(project_id)
+        return jsonify({'success': result, 'error': err_msg})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)})
 @project.route('/start_send_sub_project/<project_id>', methods=['POST'])
 def start_send_sub_project(project_id:int):
-    result, err_msg = ProjectService.start_send_sub_project(project_id)
-    return jsonify({'success': result, 'error': err_msg})
+    try:
+        result, err_msg = ProjectService.start_send_sub_project(project_id)
+        return jsonify({'success': result, 'error': err_msg})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)})
 @project.route('/delete_sub_project/<project_id>', methods=['POST'])
 def delete_sub_project(project_id:int):
-    result, err_msg =  ProjectService.delete_sub_project(project_id)
-    return jsonify({'success': result, 'error': err_msg})
-
-@project.route('/add_sub_project_item', methods=['POST'])
-def add_sub_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 = SubProjectItemModel(sub_project_id=project_id, device_name=device_name, device_model=device_model, device_unit=device_unit, standard_no=standard_no)
-    item_id = ProjectService.add_sub_project_item(new_item)
-    return jsonify({'success': True, 'id': item_id})
-
-@project.route('/update_sub_project_item', methods=['POST'])
-def update_sub_project_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')
-    item =SubProjectItemModel(item_id=item_id, device_name=device_name, device_model=device_model, device_unit=device_unit,device_count=device_count, standard_no=standard_no)
-    ProjectService.update_sub_project_item(item)
-    return jsonify({'success': True})
+    try:
+        result, err_msg =  ProjectService.delete_sub_project(project_id)
+        return jsonify({'success': result, 'error': err_msg})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)})
+
+@project.route('/save_sub_project_item', methods=['POST'])
+def save_sub_project_item():
+    try:
+        data = request.get_json()
+        item_id = data.get('id')
+        sub_id = data.get('sub_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')
+        if  item_id:
+            item_data = ProjectService.get_sub_project_item_by_id(item_id)
+            # 更新项目
+            item_data.device_name = device_name
+            item_data.device_model = device_model
+            item_data.device_unit = device_unit
+            item_data.device_count = float(device_count)
+            item_data.standard_no = standard_no
+            ProjectService.update_sub_project_item(item_data)
+        else:
+            sub_project = ProjectService.get_sub_project_by_id(sub_id)
+            if not sub_project:
+                return jsonify({'success': False, 'error': '项目工程不存在'}), 404
+            new_item = SubProjectItemModel(project_id=sub_project.project_id,sub_project_id=sub_id, device_name=device_name,device_count=float(device_count), device_model=device_model, device_unit=device_unit, standard_no=standard_no)
+            ProjectService.add_sub_project_item(new_item)
+        return jsonify({'success': True})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)}), 400
 
 @project.route('/delete_sub_project_item/<item_id>', methods=['POST'])
 def delete_sub_project_item(item_id):
-    project_item = ProjectService.get_sub_project_item_by_id(item_id)
-    ProjectService.delete_sub_project_item(item_id)
-    return redirect(url_for('project.project_item_list', project_no=project_item.sub_project_id))
-
+    try:
+        ProjectService.delete_sub_project_item(item_id)
+        return jsonify({'success': True})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)}), 400
 
+@project.route('/start_process_item/<item_id>', methods=['POST'])
+def start_process_item(item_id:int):
+    try:
+        result, err_msg = ProjectService.start_process_item(item_id)
+        return jsonify({'success': result, 'error': err_msg})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)})
 
+@project.route('/start_send_item/<item_id>', methods=['POST'])
+def start_send_item(item_id:int):
+    try:
+        result, err_msg = ProjectService.start_send_item(item_id)
+        return jsonify({'success': result, 'error': err_msg})
+    except Exception as e:
+        return jsonify({'success': False, 'error': str(e)})

+ 27 - 50
SourceCode/DataMiddleware/app/ui/static/project_list.js

@@ -1,24 +1,27 @@
 function addProject() {
-	const editBox = document.querySelector('div.edit-box')
-	const inputs = editBox.querySelectorAll('.form-control')
+	const modal = document.querySelector('#modal-project')
+	const inputs = modal.querySelectorAll('.form-control')
 	inputs.forEach((input) => {
 		input.value = ''
 	})
-	editBox.classList.add('show')
+	show_add_moadl(modal)
 }
-
-function hideAddForm() {
-	const editBox = document.querySelector('div.edit-box')
-	editBox.classList.remove('show')
+function editProject(projectId) {
+	const row = document.querySelector(`tr[data-id='${projectId}']`)
+	const modal = document.querySelector('#modal-project')
+	modal.querySelector('#project_id').value = projectId
+	modal.querySelector('#project_name').value = row.querySelector('.project-name .form-control').value
+	modal.querySelector('#project_desc').value = row.querySelector('.project-desc .form-control').value
+	show_edit_moadl(modal)
 }
 
-async function saveProject() {
+function saveProject() {
 	const projectId = document.getElementById('project_id').value
 	const projectName = document.getElementById('project_name').value
 	const projectDesc = document.getElementById('project_desc').value
 
 	try {
-		await fetch('/save_project', {
+		fetch('/save_project', {
 			method: 'POST',
 			headers: {
 				'Content-Type': 'application/json',
@@ -46,53 +49,27 @@ async function saveProject() {
 	}
 }
 
-async function confirmDelete(projectId) {
+function confirmDelete(projectId) {
 	if (confirm('确定要删除该项目吗?')) {
 		try {
-			const response = await fetch(`/delete_project/${projectId}`, {
+			fetch(`/delete_project/${projectId}`, {
 				method: 'POST',
 			})
+				.then((response) => response.json())
+				.then((data) => {
+					if (data.success) {
+						alert('删除成功')
+						window.location.reload()
+					} else {
+						alert('删除失败:' + data.error)
+					}
+				})
+				.catch((error) => {
+					console.error('删除失败:', error)
+					alert('删除失败')
+				})
 		} catch (e) {
 			alert('网络请求异常')
 		}
 	}
 }
-
-function editProject(projectId) {
-	const row = document.querySelector(`tr[data-id='${projectId}']`)
-	row.querySelectorAll('.editable').forEach((cell) => {
-		cell.classList.add('edit-mode')
-	})
-}
-
-async function saveProjectUpdate(row, projectId) {
-	const data = {
-		project_code: row.querySelector('[name="project_code"]').value,
-		project_name: row.querySelector('[name="project_name"]').value,
-	}
-
-	try {
-		const response = await fetch(`/update_project/${projectId}`, {
-			method: 'POST',
-			headers: {
-				'Content-Type': 'application/json',
-			},
-			body: JSON.stringify(data),
-		})
-			.then((response) => response.json())
-			.then((data) => {
-				if (data.success) {
-					alert('删除成功')
-					window.location.reload()
-				} else {
-					alert('删除失败:' + data.error)
-				}
-			})
-			.catch((error) => {
-				console.error('删除失败:', error)
-				alert('删除失败')
-			})
-	} catch (e) {
-		alert('网络请求异常')
-	}
-}

+ 87 - 39
SourceCode/DataMiddleware/app/ui/static/styles.css

@@ -76,13 +76,21 @@ h6 {
 	color: white;
 	border: 1px solid #4caf50;
 }
+.hide,
+.hidden {
+	display: none;
+}
 .form-control {
-	width: 240px;
-	padding: 6px 10px;
+	padding: 8px 12px;
 	outline: none;
-	border: 1px solid #2196f3;
-	border-radius: 3px;
-	margin: 0 5px;
+	border: 1px solid #ddd;
+	border-radius: 6px;
+	transition: border-color 0.3s ease;
+}
+
+.form-control:focus {
+	border-color: #2196f3;
+	box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
 }
 .box {
 	margin: 20px 20px;
@@ -148,10 +156,6 @@ th {
 tr:hover {
 	background-color: #f5f5f5;
 }
-
-td.editable .edit {
-	display: none;
-}
 .edit-mode .editable .show {
 	display: none;
 }
@@ -166,7 +170,6 @@ td.editable .edit {
 	padding: 5px 10px;
 	border: 1px solid #4caf50;
 }
-
 .pagination {
 	display: flex;
 	justify-content: space-between;
@@ -176,7 +179,6 @@ td.editable .edit {
 .pagination .pagination-links {
 	display: flex;
 }
-
 .pagination .page {
 	height: 40px;
 	min-width: 40px;
@@ -198,65 +200,111 @@ td.editable .edit {
 .pagination .pagination-links .page.page-link:hover {
 	background-color: #ddd;
 }
-
 .pagination .pagination-links .page.page-link.active {
 	background-color: #2196f3;
 	color: white;
 	border: 1px solid #2196f3;
 }
-
 .pagination .pagination-info {
 	font-size: 14px;
 	text-align: center;
 	color: #3c3c3c;
 }
-
-.edit-box {
-	position: absolute;
-	background: #f5f5f5;
-	z-index: 1000;
+.modal {
+}
+.modal {
+	position: fixed;
+	opacity: 0;
+	transition: all 0.3s ease;
+	top: 0;
+	left: 0;
 	width: 100%;
-	border: 1px solid #ddd;
-	box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+	height: 100%;
 	display: none;
+	align-items: center;
+	justify-content: center;
+	background: rgba(0, 0, 0, 0.2);
+}
+
+.modal.active {
+	opacity: 1;
+	display: flex;
+}
+.modal .modal-dialog {
+	background: white;
+	padding: 0;
+	border-radius: 8px;
+	box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
+	z-index: 1000;
+	width: 600px;
+	overflow: hidden;
 }
-.edit-box.show {
-	display: block;
-	margin-top: -15px;
+.modal .close-btn {
+	font-size: 20px;
+	color: #666;
+	cursor: pointer;
+}
+.modal .close-btn:hover {
+	color: #333;
+	transform: scale(1.1);
 }
 
-.edit-box .form-group {
+.modal-content {
 	display: flex;
 	flex-direction: column;
-	gap: 15px;
-	padding: 15px;
-	width: 60%;
-	margin: 0 auto;
 }
 
-.edit-box .form-group > div {
+.modal-header {
+	padding: 16px 24px;
+	border-bottom: 1px solid #eee;
 	display: flex;
+	justify-content: space-between;
 	align-items: center;
-	gap: 10px;
 }
 
-.edit-box .form-group label {
-	min-width: 80px;
+.modal-header .modal-title {
+	font-weight: 600;
+	font-size: 18px;
+	margin: 0;
 }
 
-.edit-box .form-group .form-control {
-	flex: 1;
+.modal-body {
+	padding: 20px 24px;
+}
+
+.modal-footer {
+	padding: 16px 24px;
+	border-top: 1px solid #eee;
+	display: flex;
+	gap: 12px;
+	justify-content: center;
 }
 
-.edit-box .form-group textarea.form-control {
+.form-group {
+	display: flex;
+	flex-direction: column;
+	gap: 20px;
+	padding: 15px 0;
+	width: 100%;
+}
+.form-group > div {
+	display: flex;
+	align-items: flex-start;
+	gap: 16px;
+}
+.form-group label {
+	min-width: 80px;
+}
+.form-group .form-control {
+	flex: 1;
+}
+.form-group textarea.form-control {
 	height: 60px;
 	resize: vertical;
 }
-
-.edit-box .form-group textarea.form-control.large {
+.form-group textarea.form-control.large {
 	height: 80px;
 }
-
-.edit-box .form-group .button-group {
+.form-group .button-group {
 	justify-content: center;
 }

+ 39 - 76
SourceCode/DataMiddleware/app/ui/static/sub_project_item_list.js

@@ -1,34 +1,31 @@
-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 addSubProjectItem(){
+	const modal = document.querySelector('#modal-sub_project_item')
+	const inputs = modal.querySelectorAll('.form-control')
+	inputs.forEach((input) => {
+		input.value = ''
+	})
+	show_add_moadl(modal)
 }
+function editSubProjectItem(id) {
+	const row = document.querySelector(`tr[data-id='${id}']`)
+	const modal = document.querySelector('#modal-sub_project_item')
+	modal.querySelector('#item_id').value = id
+	modal.querySelector('#device_name').value = row.querySelector('.device_name .form-control').value
+	modal.querySelector('#device_model').value = row.querySelector('.device_model .form-control').value
+	modal.querySelector('#device_count').value = row.querySelector('.device_count .form-control').value
+	modal.querySelector('#device_unit').value = row.querySelector('.device_unit .form-control').value
+	modal.querySelector('#standard_no').value = row.querySelector('.standard_no .form-control').value
 
-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
+	show_edit_moadl(modal)
+}
+function saveSubProjectItem(sub_id) {
+	const modal = document.querySelector('#modal-sub_project_item')
+	const id = modal.querySelector('#item_id').value
+	const deviceName = modal.querySelector('#device_name').value
+	const deviceModel = modal.querySelector('#device_model').value
+	const deviceUnit = modal.querySelector('#device_unit').value
+	const deviceCount = modal.querySelector('#device_count').value
+	const standardNo = modal.querySelector('#standard_no').value
 	if (deviceName === '') {
 		alert('名称不能为空')
 		return
@@ -50,71 +47,28 @@ function saveItemCreate(row, project_id) {
 	//     return;
 	// }
 
-	fetch(`/add_sub_project_item`, {
+	fetch(`/save_sub_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_sub_project_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 }),
+		body: JSON.stringify({ id: id,sub_id: sub_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('保存成功')
-				row.classList.remove('edit-mode')
-				row.querySelector('.standard_no .show').textContent = standardNo
-				// window.location.reload();
+				window.location.href = `/project_item_list/${sub_id}`
 			} else {
 				alert('保存失败:' + data.error)
 			}
 		})
 		.catch((error) => {
 			console.error('保存失败:', error)
-			alert('保存失败')
 		})
 }
 
-function confirmItemDelete(itemId) {
+function deleteSubProjectItem(itemId) {
 	if (confirm('确定要删除该设备吗?')) {
 		fetch(`/delete_sub_project_item/${itemId}`, {
 			method: 'POST',
@@ -141,3 +95,12 @@ function confirmItemDelete(itemId) {
 			})
 	}
 }
+
+function startProcessItem(itemId){
+	_confirm('确定要开始处理吗?', `/start_process_item/${itemId}`)
+}
+function startSendItem(itemId){
+	_confirm('确定要开始发送吗?',`/start_send_item/${itemId}`)
+}
+
+

+ 104 - 117
SourceCode/DataMiddleware/app/ui/static/sub_project_list.js

@@ -1,49 +1,39 @@
+const files = new Set()
 function addSubProject() {
-	const editBox = document.querySelector('div.edit-box')
-	const inputs = editBox.querySelectorAll('.form-control')
+	const modal = document.querySelector('#modal-sub_project')
+	const inputs = modal.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('sub_project_file').parentNode
-	file.innerHTML = `<label for="sub_project_file">工程数据:</label><input type="file" id="sub_project_file" class="form-control" accept=".xlsx,.xls,.csv" multiple>`
-
-	editBox.classList.add('show')
+	modal.querySelector('#delete_old_data').checked = false
+	modal.querySelector('#delete_old_data').parentNode.style.display = 'none'
+	modal.querySelector('#sub_project_file').value = ''
+	modal.querySelector('.file-list').innerHTML = ''
+	files.clear()
+	show_add_moadl(modal)
 }
-
 function updateSubProject(row, id) {
-	document.getElementById('sub_id').value = id
-	document.getElementById('sub_project_name').value = row.querySelector('.sub_project_name .form-control').value
-	document.getElementById('work_catalog').value = row.querySelector('.work_catalog .form-control').value
-	document.getElementById('work_content').value = row.querySelector('.work_content .form-control').value
-	const file = document.getElementById('sub_project_file').parentNode
-	file.innerHTML = `<label for="sub_project_file">工程数据:</label><input type="file" id="sub_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_standard_version').value = row.querySelector('.standard_version .form-control').value
-	const editBox = document.querySelector('div.edit-box')
-	editBox.classList.add('show')
+	const modal = document.querySelector('#modal-sub_project')
+	modal.querySelector('#sub_id').value = id
+	modal.querySelector('#sub_project_name').value = row.querySelector('.sub_project_name .form-control').value
+	modal.querySelector('#work_catalog').value = row.querySelector('.work_catalog .form-control').value
+	// modal.querySelector('#work_content').value = row.querySelector('.work_content .form-control').value
+	modal.querySelector('#delete_old_data').checked = false
+	modal.querySelector('#delete_old_data').parentNode.style.display = 'block'
+	modal.querySelector('#sub_project_file').value = ''
+	modal.querySelector('.file-list').innerHTML = ''
+	files.clear()
+	show_edit_moadl(modal)
 }
 function saveSubProject() {
 	const id = document.getElementById('sub_id').value
 	const project_id = document.getElementById('project_id').value
 	const name = document.getElementById('sub_project_name').value
-	const files = document.getElementById('sub_project_file').files
 	const catalog = document.getElementById('work_catalog').value
+	// const content = document.getElementById('work_content').value
 	// const version = document.getElementById('new_standard_version').value
+	const content = ''
 	const version = '2'
-	const content = document.getElementById('work_content').value
-
 
 	if (name === '') {
 		alert('工程名称不能为空')
@@ -57,18 +47,18 @@ function saveSubProject() {
 	// 	alert('工作目录不能为空')
 	// 	return
 	// }
-	if (content === '') {
-		alert('工作内容不能为空')
-		return
-	}
+	// if (content === '') {
+	// 	alert('工作内容不能为空')
+	// 	return
+	// }
 
 	const formData = new FormData()
 	formData.append('id', id)
 	formData.append('project_id', project_id)
 	formData.append('sub_project_name', name)
-	for (let i = 0; i < files.length; i++) {
-		formData.append('project_files', files[i])
-	}
+	files.forEach((file) => {
+		formData.append('project_files', file)
+	})
 	formData.append('work_catalog', catalog)
 	formData.append('work_content', content)
 	formData.append('standard_version', version)
@@ -94,96 +84,24 @@ function saveSubProject() {
 			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_sub_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) {
+function startSubProjectTask(id) {
 	_confirm('确定要开始任务吗?', `/start_sub_project_task/${id}`)
 }
 
-function confirmReStartTask(id) {
-	_confirm('确定要重新采集数据吗?', `/start_sub_project_task/${id}`)
+function reStartSubProjectTask(id) {
+	_confirm('确定要重新采集Excel的数据吗?', `/start_sub_project_task/${id}`)
 }
 
-function confirmReProcessData(id) {
+function reStartProcessSubProjectTask(id) {
 	_confirm('确定要重新处理数据吗?', `/start_process_sub_project/${id}`)
 }
 
-function confirmSendData(id) {
+function reStartSendSubProjectTask(id) {
 	_confirm('确定要开始上传数据吗?', `/start_send_sub_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) {
+function deleteSubProject(id) {
 	if (confirm('确定要删除该工程吗?')) {
 		fetch(`/delete_sub_project/${id}`, {
 			method: 'POST',
@@ -206,3 +124,72 @@ function confirmProjectDelete(id) {
 			})
 	}
 }
+
+function handleDragOver(e) {
+	e.preventDefault()
+	e.stopPropagation()
+	const modal = document.querySelector('#modal-sub_project')
+	modal.querySelector('.drag-drop-box').classList.add('dragover')
+}
+
+function handleDragLeave(e) {
+	e.preventDefault()
+	e.stopPropagation()
+	const modal = document.querySelector('#modal-sub_project')
+	modal.querySelector('.drag-drop-box').classList.remove('dragover')
+}
+
+function handleDrop(e) {
+	e.preventDefault()
+	e.stopPropagation()
+	const modal = document.querySelector('#modal-sub_project')
+	modal.querySelector('.drag-drop-box').classList.remove('dragover')
+	const files = e.dataTransfer.files
+	handleFiles(files)
+}
+
+function handleFileSelect(e) {
+	const files = e.target.files
+	handleFiles(files)
+}
+
+function handleFiles(newFiles) {
+	const fileList = document.getElementById('fileList')
+	const existingFiles = new Set()
+	fileList.querySelectorAll('.file-item span:first-child').forEach((item) => {
+		existingFiles.add(item.textContent)
+	})
+
+	Array.from(newFiles).forEach((file) => {
+		if (existingFiles.has(file.name)) {
+			alert('文件' + file.name + '已存在,请勿重复添加!')
+			return
+		}
+		existingFiles.add(file.name)
+		fileList.appendChild(createFileItem(file))
+		files.add(file)
+	})
+	document.getElementById('sub_project_file').value = ''
+}
+function createFileItem(file) {
+	const item = document.createElement('div')
+	item.className = 'file-item'
+	item.innerHTML = `
+			<span>${file.name}</span>
+			<span class="del" onclick="removeFileItem(this)">×</span>
+	`
+	return item
+}
+
+function removeFileItem(element) {
+	const fileItem = element.closest('.file-item')
+	fileItem.remove()
+	// 清除文件输入框的值
+	document.getElementById('sub_project_file').value = ''
+}
+
+document.addEventListener('DOMContentLoaded', function () {
+	document.getElementById('dragDropArea').addEventListener('click', function () {
+		document.getElementById('sub_project_file').click()
+	})
+})

+ 63 - 12
SourceCode/DataMiddleware/app/ui/static/utils.js

@@ -1,21 +1,72 @@
 function goTo(url) {
-    window.location.href = url;
+	window.location.href = url
+}
+function show_add_moadl(modal) {
+	modal = modal || '.modal'
+	if (typeof modal == 'string') {
+		modal = document.querySelector(`${modal}`)
+	}
+	modal.querySelector('.modal-header .modal-title span').innerHTML = '添加'
+	show_modal(modal)
+}
+function show_edit_moadl(modal) {
+	modal = modal || '.modal'
+	if (typeof modal == 'string') {
+		modal = document.querySelector(`${modal}`)
+	}
+	modal.querySelector('.modal-header .modal-title span').innerHTML = '修改'
+	show_modal(modal)
+}
+function show_modal(modal) {
+	modal = modal || '.modal'
+	if (typeof modal == 'string') {
+		modal = document.querySelector(`${modal}`)
+	}
+	modal.classList.add('active')
+}
+function hide_modal(modal) {
+	modal = modal || '.modal'
+	if (typeof modal == 'string') {
+		modal = document.querySelector(`${modal}`)
+	}
+	modal.classList.remove('active')
 }
 
 function toggleEditMode(row) {
-    const table = document.querySelector('table.table tbody');
-    const rows = table.querySelectorAll('tr.edit-mode');
-    rows.forEach(row => row.classList.remove('edit-mode'));
-    let inputs = row.querySelectorAll('td .editable input');
-    for (let i = 0; i < inputs.length; i++) {
-        inputs[i].value = "";
-    }
-    row.classList.toggle('edit-mode');
+	const table = document.querySelector('table.table tbody')
+	const rows = table.querySelectorAll('tr.edit-mode')
+	rows.forEach((row) => row.classList.remove('edit-mode'))
+	let inputs = row.querySelectorAll('td .editable input')
+	for (let i = 0; i < inputs.length; i++) {
+		inputs[i].value = ''
+	}
+	row.classList.toggle('edit-mode')
 }
 function cancelChanges(row) {
-    row.classList.remove('edit-mode');
+	row.classList.remove('edit-mode')
 }
 
 function cancelNewChanges(row) {
-    row.remove();
-}
+	row.remove()
+}
+
+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) => {})
+	}
+}

+ 113 - 43
SourceCode/DataMiddleware/app/ui/templates/project/project_list.html

@@ -7,6 +7,18 @@
 		<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() {
+				const keyword = document.getElementById('search_input').value
+				window.location.href = `{{ url_for('project.main_project_list') }}?k=${keyword}`
+			}
+			function reSearchData() {
+				window.location.href = `{{ url_for('project.main_project_list') }}`
+			}
+			function jumpToPage(page) {
+				window.location.href = `{{ url_for('project.main_project_list') }}?k={{keyword}}&pp={{per_page}}&p=${page}`
+			}
+		</script>
 	</head>
 	<body>
 		<div class="box">
@@ -14,54 +26,112 @@
 				<h3 class="box_title">主项目列表</h3>
 			</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">
-                <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="btn_box">
+					<button type="button" class="btn btn-success btn-large" onclick="addProject()">新建项目</button>
+				</div>
+				<div class="search_box">
+					<input type="text" title="" 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="project_name">项目名称:</label>
-						<input type="text" id="project_name" class="form-control" placeholder="项目名称" required />
+			<div class="box-body">
+				<table class="table">
+					<thead>
+						<tr>
+							<th width="120px">项目ID</th>
+							<th width="350px">项目名称</th>
+							<th>项目简介</th>
+							<th width="300px">操作</th>
+						</tr>
+					</thead>
+					<tbody>
+					{% if  project_list %}
+						{% for project in project_list %}
+						<tr data-id="{{project.id}}" data-p="{{project}}">
+							<td class="project-name">
+								{{ project.id }}
+							</td>
+							<td class="project-name">
+								{{ project.project_name }}
+								<input type="hidden" class="form-control" value="{{project.project_name}}"/>
+							</td>
+							<td class="project-desc">
+								{{ project.description }}
+								<input type="hidden" class="form-control" value="{{project.description}}"/>
+							</td>
+							<td>
+								<button class="btn btn-info" onclick="goTo(`{{ url_for('project.sub_project_list', project_id=project.id) }}`)">子项目管理</button>
+								<button class="btn btn-warning" onclick="editProject('{{ project.id }}')">编辑</button>
+								<button class="btn btn-danger" onclick="confirmDelete('{{ project.id }}')">删除</button>
+							</td>
+						</tr>
+						{% endfor %}
+					{% else %}
+						<tr>
+							<td colspan="34">暂无数据</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>
-						<label for="project_desc">项目简介:</label>
-						<textarea id="project_desc" class="form-control large" placeholder="项目简介"></textarea>
+					<div class="pagination-links">
+						{% if page > 1 %}
+						<span class="page page-link" onclick="jumpToPage(1)">首页</span>
+						{% endif %} {% if page > 1 %}
+						<span class="page page-link" onclick="jumpToPage(`{{page - 1}}`)">上一页</span>
+						{% endif %} {% set start_page = [1, page - 2]|max %} {% set end_page = [total_pages, page + 2]|min %} {% if start_page > 1 %}
+						<span class="page page-link" onclick="jumpToPage(1)">1</span>
+						{% if start_page > 2 %}
+						<span class="page">...</span>
+						{% endif %} {% endif %} {% for p in range(start_page, end_page + 1) %} {% if p == page %}
+						<span class="page page-link active" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
+						{% else %}
+						<span class="page page-link" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
+						{% endif %} {% endfor %} {% if end_page < total_pages %} {% if end_page < total_pages - 1 %}
+						<span class="page">...</span>
+						{% endif %}
+						<span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">{{ total_pages }}</span>
+						{% endif %} {% if page < total_pages %}
+						<span class="page page-link" onclick="jumpToPage(`{{ page+1 }}`)">下一页</span>
+						{% endif %} {% if page < total_pages %}
+						<span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">末页</span>
+						{% endif %}
 					</div>
-					<button class="btn btn-success" onclick="saveProject()">保存</button>
-					<button class="btn btn-warning" onclick="hideAddForm()">取消</button>
 				</div>
 			</div>
-
-			<table class="table">
-				<thead>
-					<tr>
-						<th width="350px">项目名称</th>
-						<th>项目简介</th>
-						<th width="300px">操作</th>
-					</tr>
-				</thead>
-				<tbody>
-					{% for project in project_list %}
-					<tr>
-						<td>{{ project.project_name }}</td>
-						<td>{{ project.description }}</td>
-						<td>
-							<button class="btn btn-info" onclick="goTo(`{{ url_for('project.sub_project_list', project_id=project.id) }}`)">子项目管理</button>
-							<button class="btn btn-warning" onclick="editProject('{{ project.id }}')">编辑</button>
-							<button class="btn btn-danger" onclick="confirmDelete('{{ project.id }}')">删除</button>
-						</td>
-					</tr>
-					{% endfor %}
-				</tbody>
-			</table>
+		</div>
+		<div class="modal" id="modal-project">
+			<div class="modal-dialog">
+			<div class="modal-content">
+				<div class="modal-header">
+					<h3 class="modal-title"><span></span>项目</h3>
+					<span class="close-btn" onclick="hide_modal('#modal-project')">&times;</span>
+				</div>
+				<div class="modal-body">
+					<div class="form-group">
+						<input type="hidden" id="project_id" class="form-control" />
+						<div>
+							<label for="project_name">项目名称:</label>
+							<input type="text" id="project_name" class="form-control" placeholder="项目名称" required />
+						</div>
+						<div>
+							<label for="project_desc">项目简介:</label>
+							<textarea id="project_desc" class="form-control large" placeholder="项目简介"></textarea>
+						</div>
+					</div>
+				</div>
+				<div class="modal-footer">
+					<button class="btn btn-success btn-large" onclick="saveProject()">保存</button>
+					<button class="btn btn-warning btn-large" onclick="hide_modal()">取消</button>
+				</div>
+			</div>
+			</div>
 		</div>
 	</body>
 </html>

+ 134 - 56
SourceCode/DataMiddleware/app/ui/templates/project/sub_project_item_list.html

@@ -12,11 +12,16 @@
 		<script src="{{ url_for('static', filename='sub_project_item_list.js') }}"></script>
 		<script>
 			function searchData() {
-				var keyword = document.getElementById('search_input').value
-				window.location.href = `{{ url_for('project.project_item_list', sub_project_id=project.id) }}?k=` + keyword
+				const process_status = document.getElementById('process_status_select').value
+				const send_status = document.getElementById('send_status_select').value
+				const keyword = document.getElementById('search_input').value
+				window.location.href = `{{ url_for('project.project_item_list', sub_project_id=sub_project.id) }}?k=${keyword}&p_s=${process_status}&s_s=${send_status}`
 			}
 			function reSearchData() {
-				window.location.href = `{{ url_for('project.project_item_list', sub_project_id=project.id) }}`
+				window.location.href = `{{ url_for('project.project_item_list', sub_project_id=sub_project.id) }}`
+			}
+			function jumpToPage(page) {
+				window.location.href = `{{ url_for('project.project_item_list', sub_project_id=sub_project.id) }}?p_s={{process_status}}&s_s={{send_status}}&k={{keyword}}&pp={{per_page}}&p=${page}`
 			}
 		</script>
 	</head>
@@ -34,9 +39,9 @@
 					<!--<dl>
 						<dt>标准版本:</dt>
 						<dd>
-							{% if project.standard_version == '1' %}
+							{% if sub_project.standard_version == '1' %}
 							<span class="label label-warning">旧版</span>
-							{% elif project.standard_version == '2' %}
+							{% elif sub_project.standard_version == '2' %}
 							<span class="label label-info">新版</span>
 							{% endif %}
 						</dd>
@@ -45,9 +50,21 @@
 			</div>
 			<div class="box_header">
 				<div class="btn_box">
-					<button class="btn btn-success btn-large" onclick="addNewItem('{{ sub_project.id}}')">新建工程明细</button>
+					<button class="btn btn-success btn-large" onclick="addSubProjectItem()">新建工程明细</button>
 				</div>
 				<div class="search_box">
+					<select id="process_status_select" class="form-control form-select" title="处理状态选择">
+						<option value="-1" {% if process_status == -1 %}selected{% endif %}>请选择处理状态</option>
+						<option value="1" {% if process_status == 1 %}selected{% endif %}>已处理</option>
+						<option value="2" {% if process_status == 2 %}selected{% endif %}>处理失败</option>
+						<option value="0" {% if process_status == 0 %}selected{% endif %}>未处理</option>
+					</select>
+					<select id="send_status_select" class="form-control form-select" title="推送状态选择">
+						<option value="-1" {% if send_status == -1 %}selected{% endif %}>请选择推送状态</option>
+						<option value="1" {% if send_status == 1 %}selected{% endif %}>已推送</option>
+						<option value="2" {% if send_status == 2 %}selected{% endif %}>推送失败</option>
+						<option value="0" {% if send_status == 0 %}selected{% endif %}>未推送</option>
+					</select>
 					<input type="text" id="search_input" title="" 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>
@@ -58,61 +75,83 @@
 				<table class="table">
 					<thead>
 						<tr>
-							<th width="25%">名称</th>
+							<th width="300px">名称</th>
 							<th>规格型号</th>
-							<th width="200px">数量</th>
-							<th width="8%">单位</th>
-							<th width="15%">定额编号</th>
-							<th width="180px">操作</th>
+							<th width="150px">数量</th>
+							<th width="120px">单位</th>
+							<th width="120">定额编号</th>
+							<th width="120px">处理状态</th>
+							<th width="120px">发送状态</th>
+							<th width="360px">操作</th>
 						</tr>
 					</thead>
 					<tbody>
 						{% if items %} {% for item in 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>
+						<tr data-id="{{item.id}}">
+							<td class="device_name">
+								{{ item.device_name if item.device_name else '-' }}
+								<input type="hidden" class="form-control" title="设备名称" value="{{ item.device_name if item.device_name else '' }}" />
+							</td>
+							<td class="device_model">
+								{{ item.device_model if item.device_model else '-' }}
+								<input type="hidden" class="form-control" title="设备规格型号" value="{{ item.device_model if item.device_model else '' }}" />
+							</td>
+							<td class="device_count">
+								{{ item.device_count if item.device_count else '-' }}
+								<input type="hidden" class="form-control" title="设备数量" value="{{ item.device_count if item.device_count else '' }}" />
 							</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 class="device_unit">
+								{{ item.device_unit if item.device_unit else '-' }}
+								<input type="hidden" class="form-control" title="设备单位" value="{{ item.device_unit if item.device_unit else '' }}" />
 							</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 class="standard_no">
+								{{ item.standard_no if item.standard_no else '-' }}
+								<input type="hidden" class="form-control" title="设备定额编号" value="{{ item.standard_no if item.standard_no else '' }}" />
 							</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 class="process_status">
+								{% if item.process_status == 1 %}
+								<span class="label label-success">已处理</span>
+								{% elif item.process_status == 2 %}
+								<span class="label label-danger">处理失败</span>
+								{% else %}
+								<span class="label label-default">未处理</span>
+								{% endif %}
 							</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 class="send_status">
+								{% if item.send_status == 1 %}
+								<span class="label label-success">已推送</span>
+								{% elif item.send_status == 2 %}
+								<span class="label label-danger">推送失败</span>
+								{% else %}
+								<span class="label label-default">未推送</span>
+								{% endif %}
 							</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 class="tool">
+								<button class="btn btn-success" onclick="startProcessItem('{{ item.id }}')">
+									{% if item.process_status == 1 %}
+										重新处理
+									{% elif item.process_status == 2 %}
+										重新处理
+									{% else %}
+										开始处理
+									{% endif %}
+								</button>
+								<button class="btn btn-success" onclick="startSendItem('{{ item.id }}')">
+									{% if item.send_status == 1 %}
+										重新发送
+									{% elif item.send_status == 2 %}
+										重新发送
+									{% else %}
+										开始发送
+									{% endif %}
+								</button>
+								<button class="btn btn-info" onclick="editSubProjectItem('{{ item.id }}')">编辑</button>
+								<button class="btn btn-danger" onclick="deleteSubProjectItem('{{ item.id }}')">删除</button>
 							</td>
 						</tr>
 						{% endfor %} {% else %}
 						<tr>
-							<td colspan="15">没有找到设备数据</td>
+							<td colspan="15">没有明细数据</td>
 						</tr>
 						{% endif %}
 					</tbody>
@@ -124,29 +163,68 @@
 					</div>
 					<div class="pagination-links">
 						{% if page > 1 %}
-						<a class="page page-link" href="{{ url_for('project.project_item_list', sub_project_id=sub_project.id, k=keyword, p=1, pp=per_page) }}">首页</a>
+						<span class="page page-link" onclick="jumpToPage(1)">首页</span>
 						{% endif %} {% if page > 1 %}
-						<a class="page page-link" href="{{ url_for('project.project_item_list', sub_project_id=sub_project.id, k=keyword, p=page-1, pp=per_page) }}">上一页</a>
+						<span class="page page-link" onclick="jumpToPage(`{{page - 1}}`)">上一页</span>
 						{% 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', sub_project_id=sub_project.id, k=keyword, p=1, pp=per_page) }}">1</a>
+						<span class="page page-link" onclick="jumpToPage(1)">1</span>
 						{% 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', sub_project_id=sub_project.id, k=keyword, p=p, pp=per_page) }}">{{ p }}</a>
+						<span class="page page-link active" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
 						{% else %}
-						<a class="page page-link" href="{{ url_for('project.project_item_list', sub_project_id=sub_project.id, k=keyword, p=p, pp=per_page) }}">{{ p }}</a>
+						<span class="page page-link" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
 						{% 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', sub_project_id=sub_project.id, k=keyword, p=total_pages, pp=per_page) }}">{{ total_pages }}</a>
+						<span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">{{ total_pages }}</span>
 						{% endif %} {% if page < total_pages %}
-						<a class="page page-link" href="{{ url_for('project.project_item_list', sub_project_id=sub_project.id, k=keyword, p=page+1, pp=per_page) }}">下一页</a>
+						<span class="page page-link" onclick="jumpToPage(`{{ page+1 }}`)">下一页</span>
 						{% endif %} {% if page < total_pages %}
-						<a class="page page-link" href="{{ url_for('project.project_item_list', sub_project_id=sub_project.id, k=keyword, p=total_pages, pp=per_page) }}">末页</a>
+						<span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">末页</span>
 						{% endif %}
 					</div>
 				</div>
 			</div>
 		</div>
-	</body>
+	<div class="modal" id="modal-sub_project_item">
+	<div class="modal-dialog">
+		<div class="modal-content">
+			<div class="modal-header">
+				<h3 class="modal-title"><span></span>工程明细</h3>
+				<span class="close-btn" onclick="hide_modal('#modal-sub_project_item')">&times;</span>
+			</div>
+			<div class="modal-body">
+				<div class="form-group">
+					<input type="hidden" id="item_id">
+					<div>
+						<label>设备名称:</label>
+						<input type="text" title="" id="device_name" class="form-control" placeholder="请输入设备名称">
+					</div>
+					<div>
+						<label>规格型号:</label>
+						<input type="text" title="" id="device_model" class="form-control" placeholder="请输入规格型号">
+					</div>
+					<div>
+						<label>数量:</label>
+						<input type="text" title="" id="device_count" class="form-control" placeholder="请输入数量">
+					</div>
+					<div>
+						<label>单位:</label>
+						<input type="text" title="" id="device_unit" class="form-control" placeholder="请输入单位">
+					</div>
+					<div>
+						<label>定额编号:</label>
+						<input type="text" title="" id="standard_no" class="form-control" placeholder="请输入定额编号">
+					</div>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button class="btn btn-success" onclick="saveSubProjectItem('{{sub_project.id}}')">保存</button>
+				<button class="btn btn-warning" onclick="hide_modal('#modal-sub_project_item')">取消</button>
+			</div>
+		</div>
+	</div>
+</div>
+</body>
 </html>

+ 202 - 128
SourceCode/DataMiddleware/app/ui/templates/project/sub_project_list.html

@@ -7,6 +7,68 @@
     <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='sub_project_list.js') }}"></script>
+    <style>
+        .file-upload-box{
+            width: 100%;
+            display: flex;
+            flex-direction: column;
+        }
+        .drag-drop-box {
+            border: 2px dashed #ccc;
+            border-radius: 4px;
+            padding: 20px;
+            text-align: center;
+            cursor: pointer;
+            transition: border-color 0.3s;
+            color: #666;
+        }
+        .drag-drop-box.dragover {
+            border-color: #2196F3;
+            background-color: rgba(33, 150, 243, 0.1);
+        }
+         .file-list:empty{
+             display: none;
+         }
+        .file-list {
+            display: flex;
+            flex-wrap: wrap;
+            margin-top: 10px;
+        }
+        .file-item {
+            display: flex;
+            align-items: center;
+            background-color: #f5f5f5;
+            padding:5px 8px;
+            border-radius: 5px;
+            margin: 5px 8px;
+
+        }
+        .file-item span{
+            color: #2196f3;
+        }
+        .file-item span.del {
+            margin-left: 5px;
+            cursor: pointer;
+            color: #eeeeee;
+            background: #2196f3;
+            border-radius: 50%;
+            width: 15px;
+            height: 15px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+        td .file{
+            width: auto;
+            margin: 0 5px;
+            color: #2196f3;
+            text-decoration: none;
+        }
+        td .file:hover{
+            text-underline: #2196f3;
+            text-decoration: underline;
+        }
+    </style>
     <script>
         function searchData(){
             const keyword = document.getElementById('search_input').value;
@@ -16,6 +78,12 @@
         function reSearchData(){
             window.location.href = `{{ url_for('project.sub_project_list',project_id=project.id) }}`;
         }
+        function jumpToPage(page) {
+            window.location.href = `{{ url_for('project.sub_project_list', project_id=project.id) }}?s={{status}}&k={{keyword}}&pp={{per_page}}&p=${page}`
+        }
+        function detail(id){
+            window.location.href = `{{  url_for('project.project_item_list',sub_project_id='') }}${id}`
+        }
     </script>
 </head>
 <body>
@@ -55,89 +123,52 @@
                 <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="sub_id" class="form-control">
-                <input type="hidden" id="project_id" value="{{project.id}}">
-                <div>
-                    <label for="sub_project_name">工程名称:</label>
-                    <input type="text" id="sub_project_name" class="form-control" placeholder="工程名称">
-                </div>
-                <div>
-                    <label for="sub_project_file">工程数据:</label>
-                    <input type="file" id="sub_project_file" class="form-control" accept=".xlsx,.xls,.csv" multiple>
-                </div>
-                <div></div>
-                <div>
-                    <label for="work_catalog">工程类别:</label>
-                    <input type="text" id="work_catalog" class="form-control" placeholder="工作目录"></input>
-                </div>
-                <div>
-                    <label for="work_content">工程内容:</label>
-                    <textarea id="work_content" class="form-control large" placeholder="工作内容"></textarea>
-                </div>
-<!--                <div>-->
-<!--                    <label for="standard_version">标准版本:</label>-->
-<!--                    <select id="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="saveSubProject()">保存</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="330px">工程名称</th>
-                        <th width="15%">工程分类</th>
-                        <th width="">工作内容</th>
+                        <th width="25%">工程分类</th>
+                        <th width="">文件</th>
 <!--                        <th width="120px">标准版本</th>-->
                         <th width="120px">状态</th>
-                        <th width="280px">操作</th>
+                        <th width="340px">操作</th>
                     </tr>
                 </thead>
                 <tbody>
                 {% if sub_project_list %}
                     {% for item in sub_project_list %}
-                        <tr>
-                            <td class="editable sub_project_name">
-                                <span class="show">{{ item.sub_project_name if item.sub_project_name else '-' }}</span>
-                                <span class="edit">
-                                    <input type="text" class="form-control" title="工程名称" name="sub_project_name" value="{{ item.sub_project_name if item.sub_project_name else '' }}">
-                                </span>
+                        <tr data-id="{{item.id}}">
+                            <td class="sub_project_name">
+                                {{ item.sub_project_name if item.sub_project_name else '-' }}
+                                <input type="hidden" class="form-control" title="工程名称" name="sub_project_name" value="{{ item.sub_project_name if item.sub_project_name else '' }}">
                             </td>
-                            <td class="editable work_catalog">
-                                <span class="show">{{ item.work_catalog if item.work_catalog else '-' }}</span>
-                                <span class="edit">
-                                    <input type="text" class="form-control" title="工作目录" name="work_catalog" value="{{ item.work_catalog if item.work_catalog else '' }}">
-                                </span>
+                            <td class="work_catalog">
+                                {{ item.work_catalog if item.work_catalog else '-' }}
+                                <input type="hidden" class="form-control" title="工作目录" name="work_catalog" value="{{ item.work_catalog if item.work_catalog else '' }}">
                             </td>
-                            <td class="editable work_content">
-                                <span class="show">{{ item.work_content if item.work_content else '-' }}</span>
-                                <span class="edit">
-                                    <input type="text" class="form-control" title="工作内容" value="{{ item.work_content if item.work_content else '' }}">
-                                </span>
+                            <td class="">
+                                {% if item.file_paths %}
+                                {% for file in item.file_paths.split(",") %}
+                                <a class="file" href="{{ url_for('project.download_file') }}?f={{file}}&p={{item.project_id}}&sp={{item.id}}" download>{{ file|basename }}</a>
+                                 {% endfor %}
+                                 {% endif %}
                             </td>
-<!--                            <td class="editable standard_version">-->
-<!--                                <span class="show">-->
-<!--                                    {% if item.standard_version == '1' %}-->
-<!--                                    <span class="label label-warning">旧版</span>-->
-<!--                                    {% elif item.standard_version == '2' %}-->
-<!--                                    <span class="label label-info">新版</span>-->
-<!--                                    {% endif %}-->
-<!--                                </span>-->
-<!--                                <span class="edit">-->
-<!--                                    <select class="form-control" title="标准版本">-->
-<!--                                        <option value="1" {% if item.standard_version == '1' %}selected{% endif %}>旧版</option>-->
-<!--                                        <option value="2" {% if item.standard_version == '2' %}selected{% endif %}>新版</option>-->
-<!--                                    </select>-->
-<!--                                </span>-->
-<!--                            </td>-->
+<!--                            <td class="work_content">
+                                {{ item.work_content if item.work_content else '-' }}
+                                <input type="hidden" class="form-control" title="工作内容" value="{{ item.work_content if item.work_content else '' }}">
+                            </td>-->
+<!--                            <td class="standard_version">
+                                {% if item.standard_version == '1' %}
+                                    <span class="label label-warning">旧版</span>
+                                    {% elif item.standard_version == '2' %}
+                                    <span class="label label-info">新版</span>
+                                    {% endif %}
+                                <select class="form-control hide" title="标准版本">
+                                    <option value="1" {% if item.standard_version == '1' %}selected{% endif %}>旧版</option>
+                                    <option value="2" {% if item.standard_version == '2' %}selected{% endif %}>新版</option>
+                                </select>
+                            </td>-->
                             <td>
                                 <span class="status">
                                     {% if item.status == 0 %}
@@ -165,82 +196,125 @@
                                     {% endif %}
                                 </span>
                             </td>
-                            <td class="editable">
-                                <span class="show">
-                                    {% if item.status == 0 %}
-                                    <button class="btn btn-success" type="button" onclick="confirmStartTask('{{ item.id }}')">开始</button>
-                                    <button class="btn btn-info" onclick="updateSubProject(this.parentNode.parentNode.parentNode,'{{ item.id }}')">编辑</button>
-                                    {% elif item.status == 11 %}
-                                    <button class="btn btn-success" type="button" onclick="confirmReStartTask('{{ item.id }}')">重新采集</button>
-                                    <button class="btn btn-info" onclick="updateSubProject(this.parentNode.parentNode.parentNode,'{{ item.id }}')">编辑</button>
-                                    {% elif item.status == 12 %}
-                                    <button class="btn btn-success" type="button" onclick="confirmReProcessData('{{ item.id }}')">重新处理</button>
-                                    {% elif item.status == 13 %}
-                                    <button class="btn btn-success" type="button" onclick="confirmSendData('{{ item.id }}')">重新发送</button>
-                                    {% elif item.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=item.id) }}`)">详情</button>
-                                    {% endif %}
-                                    {% if item.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,'{{ item.id }}')">确定</button>
-                                    <button class="btn btn-warning" onclick="cancelChanges(this.parentNode.parentNode.parentNode)">取消</button>
-                                </span>
+                            <td>
+                            {% if item.status == 0 %}
+                                <button class="btn btn-success" type="button" onclick="startSubProjectTask('{{ item.id }}')">开始任务</button>
+                                {% elif item.status == 11 %}
+                                <button class="btn btn-success" type="button" onclick="reStartSubProjectTask('{{ item.id }}')">重新采集</button>
+                                {% elif item.status == 12 %}
+                                <button class="btn btn-success" type="button" onclick="reStartProcessSubProjectTask('{{ item.id }}')">重新处理</button>
+                                {% elif item.status == 13 %}
+                                <button class="btn btn-success" type="button" onclick="reStartSendSubProjectTask('{{ item.id }}')">重新发送</button>
+                                {% endif %}
+                                <button class="btn btn-info" type="button" onclick="detail('{{ item.id }}')">详情</button>
+                                {% if item.status != 21  and item.status != 22 %}
+                                <button class="btn btn-info" onclick="updateSubProject(this.parentNode.parentNode.parentNode,'{{ item.id }}')">编辑</button>
+                                <button class="btn btn-danger" onclick="deleteSubProject('{{ item.id }}')">删除</button>
+                                {% endif %}
                             </td>
                         </tr>
                     {% endfor %}
                 {% else %}
                     <tr>
-                        <td colspan="15">没有找到工程数据</td>
+                        <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.sub_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.sub_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.sub_project_list', k=keyword, s=status, p=1, pp=per_page) }}">1</a>
+                <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 %}
+                    <span class="page page-link" onclick="jumpToPage(1)">首页</span>
+                    {% endif %} {% if page > 1 %}
+                    <span class="page page-link" onclick="jumpToPage(`{{page - 1}}`)">上一页</span>
+                    {% endif %} {% set start_page = [1, page - 2]|max %} {% set end_page = [total_pages, page + 2]|min %} {% if start_page > 1 %}
+                    <span class="page page-link" onclick="jumpToPage(1)">1</span>
                     {% 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.sub_project_list',project_id=project.id, k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
+                    <span class="page">...</span>
+                    {% endif %} {% endif %} {% for p in range(start_page, end_page + 1) %} {% if p == page %}
+                    <span class="page page-link active" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
                     {% else %}
-                    <a class="page page-link" href="{{ url_for('project.sub_project_list',project_id=project.id, k=keyword, p=p, pp=per_page) }}" >{{ p }}</a>
+                    <span class="page page-link" onclick="jumpToPage(`{{ p }}`)">{{ p }}</span>
+                    {% endif %} {% endfor %} {% if end_page < total_pages %} {% if end_page < total_pages - 1 %}
+                    <span class="page">...</span>
                     {% endif %}
-                {% endfor %}
-                {% if end_page < total_pages %}
-                    {% if end_page < total_pages - 1 %}
-                        <span class="page">...</span>
+                    <span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">{{ total_pages }}</span>
+                    {% endif %} {% if page < total_pages %}
+                    <span class="page page-link" onclick="jumpToPage(`{{ page+1 }}`)">下一页</span>
+                    {% endif %} {% if page < total_pages %}
+                    <span class="page page-link" onclick="jumpToPage(`{{ total_pages }}`)">末页</span>
                     {% endif %}
-                    <a class="page page-link" href="{{ url_for('project.sub_project_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.sub_project_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.sub_project_list',project_id=project.id, k=keyword, p=total_pages, pp=per_page) }}">末页</a>
-                {% endif %}
+                </div>
             </div>
         </div>
+    </div>
+    <div class="modal" id="modal-sub_project" 
+    ondragover="handleDragOver(event)"
+    ondragleave="handleDragLeave(event)"
+    ondrop="handleDrop(event)">
+        <div class="modal-dialog">
+			<div class="modal-content">
+				<div class="modal-header">
+					<h3 class="modal-title"><span></span>工程</h3>
+					<span class="close-btn" onclick="hide_modal('#modal-sub_project')">&times;</span>
+				</div>
+				<div class="modal-body">
+					<div class="form-group">
+                    <input type="hidden" id="sub_id" class="form-control">
+                    <input type="hidden" id="project_id" value="{{project.id}}">
+                    <div>
+                        <label for="sub_project_name">工程名称:</label>
+                        <input type="text" id="sub_project_name" class="form-control" placeholder="工程名称">
+                    </div>
+                    <div class="file-upload-area">
+                        <label>工程数据:</label>
+                        <div class="file-upload-box">
+                            <div class="drag-drop-box" id="dragDropArea"
+                             >
+                            <div class="drag-text">
+                                <span>点击选择文件或拖拽文件至此</span>
+                                <input type="file" id="sub_project_file" class="form-control" accept=".xlsx,.xls,.csv" multiple
+                                    style="display:none"
+                                    onchange="handleFileSelect(event)">
+                            </div>
+                        </div>
+
+                        <div class="file-list" id="fileList"></div>
+                        </div>
+                    </div>
+                    <div>
+                        <label for="delete_old_data">删除原数据:</label>
+                        <input type="checkbox" id="delete_old_data" class="form-control" style="width: auto;flex: 0;">
+                    </div>
+                    <div>
+                        <label for="work_catalog">工程类别:</label>
+                        <input type="text" id="work_catalog" class="form-control" placeholder="工作目录"></input>
+                    </div>
+                  <!--  <div>
+                        <label for="work_content">工程内容:</label>
+                        <textarea id="work_content" class="form-control large" placeholder="工作内容"></textarea>
+                    </div>-->
+                    <!--   <div>
+                       <label for="standard_version">标准版本:</label>
+                       <select id="standard_version" class="form-control">
+                           <option value="1">旧版</option>
+                           <option value="2">新版</option>
+                       </select>
+                   </div>-->
+
+            </div>
+            </div>
+				<div class="modal-footer">
+					<button class="btn btn-success btn-large" onclick="saveSubProject()">保存</button>
+					<button class="btn btn-warning btn-large" onclick="hide_modal()">取消</button>
+				</div>
+			</div>
         </div>
     </div>
 </body>

+ 3 - 0
SourceCode/DataMiddleware/requirements.txt

@@ -12,3 +12,6 @@ cryptography==41.0.4
 openpyxl== 3.1.5
 tabulate==0.9.0
 
+
+pyxnat~=1.6.3
+httplib2~=0.22.0