Ver Fonte

修改枚举值,修改bug

chenkun há 2 semanas atrás
pai
commit
c04dc77660

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 101
logs/lq-admin-app.log.1


Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 1074
logs/lq-admin-app.log.5


+ 4 - 0
src/app/sample/schemas/sample_schemas.py

@@ -19,6 +19,10 @@ class BatchDeleteRequest(BaseModel):
     ids: list[Union[int, str]]
     table_type: Optional[str] = None
 
+class BatchClearRequest(BaseModel):
+    ids: list[Union[int, str]]
+    table_type: Optional[str] = None
+
 class ConvertRequest(BaseModel):
     id: Union[int, str]
     table_type: Optional[str] = None

+ 2 - 8
src/app/services/image_service.py

@@ -324,14 +324,8 @@ class ImageService:
             if not image_ids:
                 return False, "未指定要加入任务的图片 ID"
             
-            # 0. 获取或生成项目 UUID
-            project_id = None
-            cursor.execute("SELECT project_id FROM t_task_management WHERE project_name = %s LIMIT 1", (project_name,))
-            existing_project = cursor.fetchone()
-            if existing_project:
-                project_id = existing_project['project_id']
-            else:
-                project_id = str(uuid.uuid4())
+            # 0. 每次点击都生成一个新的项目 UUID (不再检查重名复用)
+            project_id = str(uuid.uuid4())
             
             # 处理标签
             tag_str = json.dumps(tags, ensure_ascii=False) if tags else None

+ 567 - 66
src/app/services/sample_service.py

@@ -21,6 +21,154 @@ TABLE_MAP = {
     'regulation': 't_samp_office_regulations'
 }
 
+# 文档类型映射 (前端显示 -> 数据库存储)
+DOCUMENT_TYPE_MAP = {
+    "国家标准": "GB",
+    "行业标准": "HY",
+    "部门规章": "BM",
+    "地方标准": "DB",
+    "企业标准": "QY",
+    "管理制度": "GL",
+    "技术规范": "GF",
+    "团体标准": "TT",
+    "国际标准": "GJ",
+    "国家法律": "FL",
+    "地方法规": "LR",
+    "其他": "QT"
+}
+
+# 文档类型反向映射 (数据库存储 -> 前端显示)
+# 包含旧版本简写以保证存量数据兼容性
+DOCUMENT_TYPE_REVERSE_MAP = {v: k for k, v in DOCUMENT_TYPE_MAP.items()}
+DOCUMENT_TYPE_REVERSE_MAP.update({
+    "HB": "行业标准",
+    "QB": "企业标准",
+    "GLZD": "管理制度",
+    "JSGF": "技术规范",
+    "TB": "团体标准",
+    "IB": "国际标准",
+    "OTHER": "其他"
+})
+
+# 专业领域映射 (前端显示 -> 数据库存储)
+PROFESSIONAL_FIELD_MAP = {
+    "法律法规": "FL",
+    "通用标准": "TY",
+    "勘察钻探": "KC",
+    "地基基础": "DJ",
+    "路基路面": "LJ",
+    "桥梁工程": "QL",
+    "隧道工程": "SD",
+    "交通工程": "JT",
+    "建筑工程": "JZ",
+    "市政工程": "SZ",
+    "机电安装": "JD",
+    "路桥工程": "LB",
+    "装饰装修": "ZS",
+    "港口航道": "GK",
+    "铁路工程": "TL",
+    "房建工程": "FJ",
+    "水利电力": "SL",
+    "信息化": "XX",
+    "试验检测": "SY",
+    "安全环保": "AQ",
+    "其他": "QT"
+}
+
+# 专业领域反向映射 (数据库存储 -> 前端显示)
+PROFESSIONAL_FIELD_REVERSE_MAP = {v: k for k, v in PROFESSIONAL_FIELD_MAP.items()}
+
+# 方案类别映射 (前端显示 -> 数据库存储)
+PLAN_CATEGORY_MAP = {
+    "超危大方案": "CH",
+    "超危大方案较大Ⅱ级": "CH2",
+    "超危大方案特大Ⅳ级": "CH4",
+    "超危大方案一般Ⅰ级": "CH1",
+    "超危大方案重大Ⅲ级": "CH3",
+    "危大方案": "WD",
+    "一般方案": "YB"
+}
+
+# 方案类别反向映射 (数据库存储 -> 前端显示)
+PLAN_CATEGORY_REVERSE_MAP = {v: k for k, v in PLAN_CATEGORY_MAP.items()}
+
+# 一级分类映射 (前端显示 -> 数据库存储)
+FIRST_LEVEL_MAP = {
+    "施工方案": "SC"
+}
+
+# 一级分类反向映射 (数据库存储 -> 前端显示)
+FIRST_LEVEL_REVERSE_MAP = {v: k for k, v in FIRST_LEVEL_MAP.items()}
+
+# 二级分类映射 (前端显示 -> 数据库存储)
+SECOND_LEVEL_MAP = {
+    "临建工程": "LZ",
+    "路基工程": "LJ",
+    "桥梁工程": "QL",
+    "隧道工程": "SD",
+    "其他": "QT"
+}
+
+# 二级分类反向映射 (数据库存储 -> 前端显示)
+SECOND_LEVEL_REVERSE_MAP = {v: k for k, v in SECOND_LEVEL_MAP.items()}
+
+# 三级分类映射 (前端显示 -> 数据库存储)
+THIRD_LEVEL_MAP = {
+    "TBM施工": "TM",
+    "拌和站安、拆施工": "BH",
+    "不良地质隧道施工": "BL",
+    "常规桥梁": "CG",
+    "挡土墙工程类": "DT",
+    "辅助坑道施工": "FB",
+    "复杂洞口工程施工": "FD",
+    "钢筋加工场安、拆": "GG",
+    "钢栈桥施工": "GZ",
+    "拱桥": "GH",
+    "涵洞工程类": "HD",
+    "滑坡体处理类": "HP",
+    "路堤": "LT",
+    "路堑": "LQ",
+    "其他": "QT",
+    "深基坑": "JK",
+    "隧道总体施工": "ZT",
+    "特殊结构隧道": "TS",
+    "斜拉桥": "XL",
+    "悬索桥": "XS"
+}
+
+# 三级分类反向映射 (数据库存储 -> 前端显示)
+THIRD_LEVEL_REVERSE_MAP = {v: k for k, v in THIRD_LEVEL_MAP.items()}
+
+# 四级分类映射 (前端显示 -> 数据库存储)
+FOURTH_LEVEL_MAP = {
+    "挡土墙": "DT",
+    "顶管": "DG",
+    "断层破碎带及软弱围岩": "DL",
+    "钢筋砼箱涵": "GX",
+    "高填路堤": "GT",
+    "抗滑桩": "KH",
+    "软岩大变形隧道": "RY",
+    "上部结构": "SB",
+    "深基坑开挖与支护": "JK",
+    "深挖路堑": "LC",
+    "隧道TBM": "TM",
+    "隧道进洞": "JD",
+    "隧道竖井": "SJ",
+    "隧道斜井": "XJ",
+    "特种设备": "TZ",
+    "瓦斯隧道": "WS",
+    "下部结构": "XB",
+    "小净距隧道": "NJ",
+    "岩爆隧道": "YB",
+    "岩溶隧道": "YR",
+    "涌水突泥隧道": "YN",
+    "桩基础": "ZJ",
+    "其他": "QT"
+}
+
+# 四级分类反向映射 (数据库存储 -> 前端显示)
+FOURTH_LEVEL_REVERSE_MAP = {v: k for k, v in FOURTH_LEVEL_MAP.items()}
+
 
 def get_table_name(source_type: str) -> Optional[str]:
     """根据source_type获取表名"""
@@ -60,6 +208,42 @@ class SampleService:
         
         # 2. 映射字段以适配前端通用显示
         source_type = item.get('source_type')
+        
+        # 处理文档类型显示 (简写 -> 中文)
+        doc_type_code = item.get('document_type')
+        if doc_type_code in DOCUMENT_TYPE_REVERSE_MAP:
+            item['document_type'] = DOCUMENT_TYPE_REVERSE_MAP[doc_type_code]
+            
+        # 处理专业领域显示 (简写 -> 中文)
+        prof_field_code = item.get('professional_field')
+        if prof_field_code in PROFESSIONAL_FIELD_REVERSE_MAP:
+            item['professional_field'] = PROFESSIONAL_FIELD_REVERSE_MAP[prof_field_code]
+
+        # 处理一级分类显示 (简写 -> 中文)
+        level_1_code = item.get('level_1_classification')
+        if level_1_code in FIRST_LEVEL_REVERSE_MAP:
+            item['level_1_classification'] = FIRST_LEVEL_REVERSE_MAP[level_1_code]
+
+        # 处理二级分类显示 (简写 -> 中文)
+        level_2_code = item.get('level_2_classification')
+        if level_2_code in SECOND_LEVEL_REVERSE_MAP:
+            item['level_2_classification'] = SECOND_LEVEL_REVERSE_MAP[level_2_code]
+
+        # 处理三级分类显示 (简写 -> 中文)
+        level_3_code = item.get('level_3_classification')
+        if level_3_code in THIRD_LEVEL_REVERSE_MAP:
+            item['level_3_classification'] = THIRD_LEVEL_REVERSE_MAP[level_3_code]
+
+        # 处理四级分类显示 (简写 -> 中文)
+        level_4_code = item.get('level_4_classification')
+        if level_4_code in FOURTH_LEVEL_REVERSE_MAP:
+            item['level_4_classification'] = FOURTH_LEVEL_REVERSE_MAP[level_4_code]
+
+        # 处理方案类别显示 (简写 -> 中文)
+        plan_category_code = item.get('plan_category')
+        if plan_category_code in PLAN_CATEGORY_REVERSE_MAP:
+            item['plan_category'] = PLAN_CATEGORY_REVERSE_MAP[plan_category_code]
+
         if source_type == 'standard':
             # 标准规范特有映射
             if 'standard_number' in item:
@@ -234,9 +418,54 @@ class SampleService:
                         """
                         cursor.execute(plan_sql, (doc_id,))
                         business_metadata = cursor.fetchone() or {}
+                    elif source_type == 'regulation':
+                        reg_sql = """
+                            SELECT id as basic_info_id, file_name, issuing_department as issuing_authority, 
+                                   document_type, publish_date as release_date, note
+                            FROM t_samp_office_regulations 
+                            WHERE id = %s
+                        """
+                        cursor.execute(reg_sql, (doc_id,))
+                        business_metadata = cursor.fetchone() or {}
                     
-                    # 统一使用主表的 file_url,确保数据来源一致
-                    business_metadata['file_url'] = doc.get('file_url', '')
+                    # 强制转换 document_type 为简写 (处理存量数据或异常情况)
+                    if 'document_type' in business_metadata and business_metadata['document_type']:
+                        doc_type = business_metadata['document_type']
+                        business_metadata['document_type'] = DOCUMENT_TYPE_MAP.get(doc_type, doc_type)
+
+                    # 强制转换 professional_field 为简写
+                    if 'professional_field' in business_metadata and business_metadata['professional_field']:
+                        prof_field = business_metadata['professional_field']
+                        business_metadata['professional_field'] = PROFESSIONAL_FIELD_MAP.get(prof_field, prof_field)
+
+                    # 强制转换 plan_category 为简写
+                    if 'plan_category' in business_metadata and business_metadata['plan_category']:
+                        plan_cat = business_metadata['plan_category']
+                        business_metadata['plan_category'] = PLAN_CATEGORY_MAP.get(plan_cat, plan_cat)
+
+                    # 强制转换 level_1_classification 为简写
+                    if 'level_1_classification' in business_metadata and business_metadata['level_1_classification']:
+                        l1_class = business_metadata['level_1_classification']
+                        business_metadata['level_1_classification'] = FIRST_LEVEL_MAP.get(l1_class, l1_class)
+
+                    # 强制转换 level_2_classification 为简写
+                    if 'level_2_classification' in business_metadata and business_metadata['level_2_classification']:
+                        l2_class = business_metadata['level_2_classification']
+                        business_metadata['level_2_classification'] = SECOND_LEVEL_MAP.get(l2_class, l2_class)
+
+                    # 强制转换 level_3_classification 为简写
+                    if 'level_3_classification' in business_metadata and business_metadata['level_3_classification']:
+                        l3_class = business_metadata['level_3_classification']
+                        business_metadata['level_3_classification'] = THIRD_LEVEL_MAP.get(l3_class, l3_class)
+
+                    # 强制转换 level_4_classification 为简写
+                    if 'level_4_classification' in business_metadata and business_metadata['level_4_classification']:
+                        l4_class = business_metadata['level_4_classification']
+                        business_metadata['level_4_classification'] = FOURTH_LEVEL_MAP.get(l4_class, l4_class)
+
+                    # 统一使用主表的 file_url,确保数据来源一致,入库时使用完整 URL
+                    raw_file_url = doc.get('file_url', '')
+                    business_metadata['file_url'] = self.minio_manager.get_full_url(raw_file_url) if raw_file_url else ''
 
                     # 准备元数据
                     current_date = int(datetime.now().strftime('%Y%m%d'))
@@ -318,15 +547,9 @@ class SampleService:
             if not doc_ids:
                 return False, "未指定要加入任务的文档 ID"
             
-            # 0. 尝试从现有任务中获取该项目名称对应的 UUID
-            # 如果不存在,则生成一个新的 UUID
-            project_id = None
-            cursor.execute("SELECT project_id FROM t_task_management WHERE project_name = %s LIMIT 1", (project_name,))
-            existing_project = cursor.fetchone()
-            if existing_project:
-                project_id = existing_project['project_id']
-            else:
-                project_id = str(uuid.uuid4())
+            # 0. 每次点击都生成一个新的项目 UUID (不再检查重名复用)
+            import uuid
+            project_id = str(uuid.uuid4())
             
             # 处理标签:转换为 JSON 字符串存储
             import json
@@ -395,7 +618,7 @@ class SampleService:
                         metadata=json.dumps(metadata_dict, ensure_ascii=False) if metadata_dict else None
                     )
                 except Exception as e:
-                    logger.error(f"添加文档 {doc_id} 到任务中心失败: {e}")
+                    logger.exception(f"添加文档 {doc_id} 到任务中心失败: {e}")
             
             conn.commit()
 
@@ -422,8 +645,85 @@ class SampleService:
             if conn:
                 conn.close()
 
+    async def clear_knowledge_base_data(self, doc_ids: List[str], username: str) -> Tuple[int, str]:
+        """批量清空文档在知识库(Milvus)中的数据片段"""
+        conn = get_db_connection()
+        if not conn:
+            return 0, "数据库连接失败"
+        
+        cursor = conn.cursor()
+        success_count = 0
+        error_details = []
+        
+        try:
+            # 1. 获取文档的知识库信息
+            placeholders = ', '.join(['%s'] * len(doc_ids))
+            sql = f"""
+                SELECT id, title, kb_id, whether_to_enter 
+                FROM t_samp_document_main 
+                WHERE id IN ({placeholders})
+            """
+            cursor.execute(sql, doc_ids)
+            docs = cursor.fetchall()
+            
+            for doc in docs:
+                doc_id = doc['id']
+                title = doc.get('title', '未命名')
+                kb_id = doc.get('kb_id')
+                
+                # 只有入库了的才需要清空
+                if doc.get('whether_to_enter') != 1:
+                    continue
+                
+                if not kb_id:
+                    error_details.append(f"· {title}: 未关联知识库,无法清空")
+                    continue
+                
+                # 获取集合名称
+                cursor.execute("SELECT collection_name_parent, collection_name_children FROM t_samp_knowledge_base WHERE id = %s", (kb_id,))
+                kb_info = cursor.fetchone()
+                
+                if not kb_info:
+                    error_details.append(f"· {title}: 找不到关联的知识库配置")
+                    continue
+                
+                # 2. 从 Milvus 删除
+                try:
+                    collections = [kb_info.get('collection_name_parent'), kb_info.get('collection_name_children')]
+                    for coll_name in collections:
+                        if coll_name and self.milvus_service.client.has_collection(coll_name):
+                            # 使用 document_id 进行过滤删除
+                            self.milvus_service.client.delete(
+                                collection_name=coll_name,
+                                filter=f'document_id == "{doc_id}"'
+                            )
+                    
+                    # 3. 更新数据库状态
+                    update_sql = "UPDATE t_samp_document_main SET whether_to_enter = 0, updated_by = %s, updated_time = NOW() WHERE id = %s"
+                    cursor.execute(update_sql, (username, doc_id))
+                    success_count += 1
+                except Exception as milvus_err:
+                    logger.error(f"清空文档 {title} Milvus 数据失败: {milvus_err}")
+                    error_details.append(f"· {title}: 清空向量库数据失败")
+            
+            conn.commit()
+            
+            msg = f"成功清空 {success_count} 份文档的知识库片段"
+            if error_details:
+                msg += "\n部分操作受限:\n" + "\n".join(error_details)
+            
+            return success_count, msg
+            
+        except Exception as e:
+            logger.exception("批量清空知识库数据失败")
+            conn.rollback()
+            return 0, f"操作失败: {str(e)}"
+        finally:
+            cursor.close()
+            conn.close()
+
     async def batch_delete_documents(self, doc_ids: List[str]) -> Tuple[int, str]:
-        """批量删除文档"""
+        """批量删除文档 (仅限未入库或已清空的文档)"""
         conn = get_db_connection()
         if not conn:
             return 0, "数据库连接失败"
@@ -434,8 +734,17 @@ class SampleService:
             if not doc_ids:
                 return 0, "未指定要删除的文档 ID"
             
+            # 1. 检查文档状态:已入库的文档不允许直接删除
             placeholders = ', '.join(['%s'] * len(doc_ids))
+            check_sql = f"SELECT id, title FROM t_samp_document_main WHERE id IN ({placeholders}) AND whether_to_enter = 1"
+            cursor.execute(check_sql, doc_ids)
+            entered_docs = cursor.fetchall()
+            
+            if entered_docs:
+                titles = [d['title'] for d in entered_docs]
+                return 0, f"删除失败:以下文档已入库,请先执行‘清空数据’操作后再删除:\n{', '.join(titles[:5])}{'...' if len(titles)>5 else ''}"
             
+            # 2. 执行物理删除
             # 尝试同步删除子表中的数据
             try:
                 for sub_table in TABLE_MAP.values():
@@ -454,8 +763,11 @@ class SampleService:
             
             # 同步删除任务管理中心的数据
             try:
+                from app.services.task_service import task_service
                 for doc_id in doc_ids:
-                    await task_service.delete_task(doc_id)
+                    # 注意:task_service 的删除方法可能叫 delete_task_by_business_id 或类似,这里假设存在
+                    # 如果没有,我们需要查出主键 ID 再删
+                    cursor.execute("DELETE FROM t_task_management WHERE business_id = %s", (doc_id,))
             except Exception as task_err:
                 logger.error(f"同步删除任务中心数据失败: {task_err}")
 
@@ -530,41 +842,45 @@ class SampleService:
             params = []
             
             # 基础查询
-            if table_type and table_type in TABLE_MAP:
-                # 如果指定了类型,使用 LEFT JOIN 关联查询,以便搜索子表字段
-                sub_table = TABLE_MAP[table_type]
-                from_sql = f"""
-                    t_samp_document_main m 
-                    LEFT JOIN {sub_table} s ON m.id = s.id
-                    LEFT JOIN t_sys_user u1 ON m.created_by = u1.id
-                    LEFT JOIN t_sys_user u2 ON m.updated_by = u2.id
-                    LEFT JOIN t_samp_knowledge_base kb ON m.kb_id = kb.id
-                """
-                fields_sql = "m.*, s.*, u1.username as creator_name, u2.username as updater_name, kb.name as kb_name, m.id as id"
+            if table_type:
                 where_clauses.append("m.source_type = %s")
                 params.append(table_type)
-                order_sql = "m.created_time DESC"
-                title_field = "m.title"
-
-                # 施工方案特有的过滤字段
-                if table_type == 'construction_plan':
-                    if plan_category:
-                        where_clauses.append("s.plan_category = %s")
-                        params.append(plan_category)
-                    if level_2_classification:
-                        where_clauses.append("s.level_2_classification = %s")
-                        params.append(level_2_classification)
-                    if level_3_classification:
-                        where_clauses.append("s.level_3_classification = %s")
-                        params.append(level_3_classification)
-                    if level_4_classification:
-                        where_clauses.append("s.level_4_classification = %s")
-                        params.append(level_4_classification)
+                
+                if table_type in TABLE_MAP:
+                    # 如果指定了类型且在 TABLE_MAP 中,使用 LEFT JOIN 关联查询,以便搜索子表字段
+                    sub_table = TABLE_MAP[table_type]
+                    from_sql = f"""
+                        t_samp_document_main m 
+                        LEFT JOIN {sub_table} s ON m.id = s.id
+                        LEFT JOIN t_sys_user u1 ON m.created_by = u1.id
+                        LEFT JOIN t_sys_user u2 ON m.updated_by = u2.id
+                        LEFT JOIN t_samp_knowledge_base kb ON m.kb_id = kb.id
+                    """
+                    fields_sql = "m.*, s.*, u1.username as creator_name, u2.username as updater_name, kb.name as kb_name, m.id as id"
+                    
+                    # 施工方案特有的过滤字段
+                    if table_type == 'construction_plan':
+                        if plan_category:
+                            where_clauses.append("s.plan_category = %s")
+                            params.append(plan_category)
+                        if level_2_classification:
+                            where_clauses.append("s.level_2_classification = %s")
+                            params.append(SECOND_LEVEL_MAP.get(level_2_classification, level_2_classification))
+                        if level_3_classification:
+                            where_clauses.append("s.level_3_classification = %s")
+                            params.append(THIRD_LEVEL_MAP.get(level_3_classification, level_3_classification))
+                        if level_4_classification:
+                            where_clauses.append("s.level_4_classification = %s")
+                            params.append(FOURTH_LEVEL_MAP.get(level_4_classification, level_4_classification))
+                else:
+                    from_sql = "t_samp_document_main m LEFT JOIN t_sys_user u1 ON m.created_by = u1.id LEFT JOIN t_sys_user u2 ON m.updated_by = u2.id LEFT JOIN t_samp_knowledge_base kb ON m.kb_id = kb.id"
+                    fields_sql = "m.*, u1.username as creator_name, u2.username as updater_name, kb.name as kb_name"
             else:
                 from_sql = "t_samp_document_main m LEFT JOIN t_sys_user u1 ON m.created_by = u1.id LEFT JOIN t_sys_user u2 ON m.updated_by = u2.id LEFT JOIN t_samp_knowledge_base kb ON m.kb_id = kb.id"
                 fields_sql = "m.*, u1.username as creator_name, u2.username as updater_name, kb.name as kb_name"
-                order_sql = "m.created_time DESC"
-                title_field = "m.title"
+            
+            order_sql = "m.created_time DESC"
+            title_field = "m.title"
             
             # 分离 whether_to_enter 与 conversion_status 的过滤逻辑
             if whether_to_enter is not None:
@@ -693,6 +1009,42 @@ class SampleService:
             # 处理 URL 存储(转为相对路径)
             file_url = self.minio_manager.get_relative_path(doc_data.get('file_url'))
             
+            # 处理文档类型 (中文 -> 简写)
+            doc_type_cn = doc_data.get('document_type')
+            doc_type_code = DOCUMENT_TYPE_MAP.get(doc_type_cn, doc_type_cn)  # 找不到则保持原样
+
+            # 处理专业领域 (中文 -> 简写)
+            prof_field_cn = doc_data.get('professional_field')
+            prof_field_code = PROFESSIONAL_FIELD_MAP.get(prof_field_cn, prof_field_cn)
+
+            # 处理方案类别 (中文 -> 简写)
+            plan_category_cn = doc_data.get('plan_category')
+            plan_category_code = PLAN_CATEGORY_MAP.get(plan_category_cn, plan_category_cn)
+
+            # 处理一级分类 (中文 -> 简写)
+            level_1_cn = doc_data.get('level_1_classification')
+            level_1_code = FIRST_LEVEL_MAP.get(level_1_cn, level_1_cn)
+
+            # 处理二级分类 (中文 -> 简写)
+            level_2_cn = doc_data.get('level_2_classification')
+            level_2_code = SECOND_LEVEL_MAP.get(level_2_cn, level_2_cn)
+
+            # 处理三级分类 (中文 -> 简写)
+            level_3_cn = doc_data.get('level_3_classification')
+            level_3_code = THIRD_LEVEL_MAP.get(level_3_cn, level_3_cn)
+
+            # 处理四级分类 (中文 -> 简写)
+            level_4_cn = doc_data.get('level_4_classification')
+            level_4_code = FOURTH_LEVEL_MAP.get(level_4_cn, level_4_cn)
+
+            # 处理四级分类 (中文 -> 简写)
+            level_4_cn = doc_data.get('level_4_classification')
+            level_4_code = FOURTH_LEVEL_MAP.get(level_4_cn, level_4_cn)
+
+            # 处理四级分类 (中文 -> 简写)
+            level_4_cn = doc_data.get('level_4_classification')
+            level_4_code = FOURTH_LEVEL_MAP.get(level_4_cn, level_4_cn)
+
             # 1. 插入主表 (作为资产中心)
             cursor.execute(
                 """
@@ -725,7 +1077,7 @@ class SampleService:
                         doc_id, doc_data.get('title'), doc_data.get('english_name'), doc_data.get('standard_no'), 
                         doc_data.get('issuing_authority'), release_date, self._to_date(doc_data.get('implementation_date')),
                         doc_data.get('drafting_unit'), doc_data.get('approving_department'), doc_data.get('participating_units'),
-                        doc_data.get('document_type'), doc_data.get('professional_field'), doc_data.get('engineering_phase'),
+                        doc_type_code, prof_field_code, doc_data.get('engineering_phase'),
                         doc_data.get('validity', '现行'), doc_data.get('reference_basis'), doc_data.get('source_url'), doc_data.get('note'),
                         user_id, user_id
                     )
@@ -743,8 +1095,8 @@ class SampleService:
                     (
                         doc_id, doc_data.get('title'), doc_data.get('project_name'), doc_data.get('project_section'), 
                         doc_data.get('issuing_authority'), release_date, doc_data.get('plan_summary'), 
-                        doc_data.get('compilation_basis'), doc_data.get('plan_category'), doc_data.get('level_1_classification', '施工方案'), 
-                        doc_data.get('level_2_classification'), doc_data.get('level_3_classification'), doc_data.get('level_4_classification'), 
+                        doc_data.get('compilation_basis'), plan_category_code, level_1_code or 'SC', 
+                        level_2_code, level_3_code, level_4_code, 
                         doc_data.get('note'), user_id, user_id
                     )
                 )
@@ -758,7 +1110,7 @@ class SampleService:
                     ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
                     """,
                     (
-                        doc_id, doc_data.get('title'), doc_data.get('issuing_authority'), doc_data.get('document_type'), 
+                        doc_id, doc_data.get('title'), doc_data.get('issuing_authority'), doc_type_code, 
                         release_date, self._to_date(doc_data.get('effective_start_date')), self._to_date(doc_data.get('effective_end_date')),
                         doc_data.get('note'), user_id, user_id
                     )
@@ -799,6 +1151,34 @@ class SampleService:
             # 处理 URL 存储(转为相对路径)
             file_url = self.minio_manager.get_relative_path(doc_data.get('file_url'))
             
+            # 处理文档类型 (中文 -> 简写)
+            doc_type_cn = doc_data.get('document_type')
+            doc_type_code = DOCUMENT_TYPE_MAP.get(doc_type_cn, doc_type_cn)  # 找不到则保持原样
+
+            # 处理专业领域 (中文 -> 简写)
+            prof_field_cn = doc_data.get('professional_field')
+            prof_field_code = PROFESSIONAL_FIELD_MAP.get(prof_field_cn, prof_field_cn)
+
+            # 处理方案类别 (中文 -> 简写)
+            plan_category_cn = doc_data.get('plan_category')
+            plan_category_code = PLAN_CATEGORY_MAP.get(plan_category_cn, plan_category_cn)
+
+            # 处理一级分类 (中文 -> 简写)
+            level_1_cn = doc_data.get('level_1_classification')
+            level_1_code = FIRST_LEVEL_MAP.get(level_1_cn, level_1_cn)
+
+            # 处理二级分类 (中文 -> 简写)
+            level_2_cn = doc_data.get('level_2_classification')
+            level_2_code = SECOND_LEVEL_MAP.get(level_2_cn, level_2_cn)
+
+            # 处理三级分类 (中文 -> 简写)
+            level_3_cn = doc_data.get('level_3_classification')
+            level_3_code = THIRD_LEVEL_MAP.get(level_3_cn, level_3_cn)
+
+            # 处理四级分类 (中文 -> 简写)
+            level_4_cn = doc_data.get('level_4_classification')
+            level_4_code = FOURTH_LEVEL_MAP.get(level_4_cn, level_4_cn)
+
             # 1. 更新主表
             cursor.execute(
                 """
@@ -828,7 +1208,7 @@ class SampleService:
                     (
                         doc_data.get('title'), doc_data.get('english_name'), doc_data.get('standard_no'), doc_data.get('issuing_authority'), 
                         release_date, self._to_date(doc_data.get('implementation_date')), doc_data.get('drafting_unit'), doc_data.get('approving_department'),
-                        doc_data.get('participating_units'), doc_data.get('document_type'), doc_data.get('professional_field'), doc_data.get('engineering_phase'),
+                        doc_data.get('participating_units'), doc_type_code, prof_field_code, doc_data.get('engineering_phase'),
                         doc_data.get('validity'), doc_data.get('reference_basis'), doc_data.get('source_url'), doc_data.get('note'),
                         updater_id, doc_id
                     )
@@ -845,8 +1225,8 @@ class SampleService:
                     """,
                     (
                         doc_data.get('title'), doc_data.get('project_name'), doc_data.get('project_section'), doc_data.get('issuing_authority'), 
-                        release_date, doc_data.get('plan_summary'), doc_data.get('compilation_basis'), doc_data.get('plan_category'), 
-                        doc_data.get('level_1_classification'), doc_data.get('level_2_classification'), doc_data.get('level_3_classification'), doc_data.get('level_4_classification'), 
+                        release_date, doc_data.get('plan_summary'), doc_data.get('compilation_basis'), plan_category_code, 
+                        level_1_code, level_2_code, level_3_code, level_4_code, 
                         doc_data.get('note'), updater_id, doc_id
                     )
                 )
@@ -860,7 +1240,7 @@ class SampleService:
                     WHERE id = %s
                     """,
                     (
-                        doc_data.get('title'), doc_data.get('issuing_authority'), doc_data.get('document_type'), release_date, 
+                        doc_data.get('title'), doc_data.get('issuing_authority'), doc_type_code, release_date, 
                         self._to_date(doc_data.get('effective_start_date')), self._to_date(doc_data.get('effective_end_date')), doc_data.get('note'), 
                         updater_id, doc_id
                     )
@@ -1001,6 +1381,34 @@ class SampleService:
 
                 db_field = field_map.get(filter_key)
                 if db_field:
+                    # 处理文档类型查询 (前端传中文 -> 数据库查简写)
+                    if filter_key == 'document_type':
+                        filter_value = DOCUMENT_TYPE_MAP.get(filter_value, filter_value)
+                    
+                    # 处理专业领域查询 (前端传中文 -> 数据库查简写)
+                    if filter_key == 'professional_field':
+                        filter_value = PROFESSIONAL_FIELD_MAP.get(filter_value, filter_value)
+                    
+                    # 处理方案类别查询 (前端传中文 -> 数据库查简写)
+                    if filter_key == 'plan_category':
+                        filter_value = PLAN_CATEGORY_MAP.get(filter_value, filter_value)
+
+                    # 处理一级分类查询 (前端传中文 -> 数据库查简写)
+                    if filter_key == 'level_1_classification':
+                        filter_value = FIRST_LEVEL_MAP.get(filter_value, filter_value)
+
+                    # 处理二级分类查询 (前端传中文 -> 数据库查简写)
+                    if filter_key == 'level_2_classification':
+                        filter_value = SECOND_LEVEL_MAP.get(filter_value, filter_value)
+
+                    # 处理三级分类查询 (前端传中文 -> 数据库查简写)
+                    if filter_key == 'level_3_classification':
+                        filter_value = THIRD_LEVEL_MAP.get(filter_value, filter_value)
+
+                    # 处理四级分类查询 (前端传中文 -> 数据库查简写)
+                    if filter_key == 'level_4_classification':
+                        filter_value = FOURTH_LEVEL_MAP.get(filter_value, filter_value)
+
                     # 如果是 title, standard_no, issuing_authority,支持模糊查询
                     if filter_key in ['title', 'standard_no', 'issuing_authority']:
                         where_clauses.append(f"{db_field} LIKE %s")
@@ -1031,6 +1439,41 @@ class SampleService:
             
             # 处理 URL 转换
             for item in items:
+                # 处理文档类型显示 (简写 -> 中文)
+                doc_type_code = item.get('document_type')
+                if doc_type_code in DOCUMENT_TYPE_REVERSE_MAP:
+                    item['document_type'] = DOCUMENT_TYPE_REVERSE_MAP[doc_type_code]
+                
+                # 处理专业领域显示 (简写 -> 中文)
+                prof_field_code = item.get('professional_field')
+                if prof_field_code in PROFESSIONAL_FIELD_REVERSE_MAP:
+                    item['professional_field'] = PROFESSIONAL_FIELD_REVERSE_MAP[prof_field_code]
+
+                # 处理方案类别显示 (简写 -> 中文)
+                plan_category_code = item.get('plan_category')
+                if plan_category_code in PLAN_CATEGORY_REVERSE_MAP:
+                    item['plan_category'] = PLAN_CATEGORY_REVERSE_MAP[plan_category_code]
+
+                # 处理一级分类显示 (简写 -> 中文)
+                level_1_code = item.get('level_1_classification')
+                if level_1_code in FIRST_LEVEL_REVERSE_MAP:
+                    item['level_1_classification'] = FIRST_LEVEL_REVERSE_MAP[level_1_code]
+
+                # 处理二级分类显示 (简写 -> 中文)
+                level_2_code = item.get('level_2_classification')
+                if level_2_code in SECOND_LEVEL_REVERSE_MAP:
+                    item['level_2_classification'] = SECOND_LEVEL_REVERSE_MAP[level_2_code]
+
+                # 处理三级分类显示 (简写 -> 中文)
+                level_3_code = item.get('level_3_classification')
+                if level_3_code in THIRD_LEVEL_REVERSE_MAP:
+                    item['level_3_classification'] = THIRD_LEVEL_REVERSE_MAP[level_3_code]
+
+                # 处理四级分类显示 (简写 -> 中文)
+                level_4_code = item.get('level_4_classification')
+                if level_4_code in FOURTH_LEVEL_REVERSE_MAP:
+                    item['level_4_classification'] = FOURTH_LEVEL_REVERSE_MAP[level_4_code]
+
                 for key in ['file_url', 'md_url', 'json_url']:
                     if item.get(key):
                         item[key] = self.minio_manager.get_full_url(item[key])
@@ -1157,6 +1600,38 @@ class SampleService:
             file_url = data.get('file_url')
             file_extension = file_url.split('.')[-1] if file_url and '.' in file_url else None
             
+            # 处理文档类型 (中文 -> 简写)
+            doc_type_cn = data.get('document_type')
+            doc_type_code = DOCUMENT_TYPE_MAP.get(doc_type_cn, doc_type_cn)
+            
+            # 处理专业领域 (中文 -> 简写)
+            prof_field_cn = data.get('professional_field')
+            prof_field_code = PROFESSIONAL_FIELD_MAP.get(prof_field_cn, prof_field_cn)
+
+            # 处理方案类别 (中文 -> 简写)
+            plan_category_cn = data.get('plan_category')
+            plan_category_code = PLAN_CATEGORY_MAP.get(plan_category_cn, plan_category_cn)
+
+            # 处理一级分类 (中文 -> 简写)
+            level_1_cn = data.get('level_1_classification')
+            level_1_code = FIRST_LEVEL_MAP.get(level_1_cn, level_1_cn)
+
+            # 处理二级分类 (中文 -> 简写)
+            level_2_cn = data.get('level_2_classification')
+            level_2_code = SECOND_LEVEL_MAP.get(level_2_cn, level_2_cn)
+
+            # 处理三级分类 (中文 -> 简写)
+            level_3_cn = data.get('level_3_classification')
+            level_3_code = THIRD_LEVEL_MAP.get(level_3_cn, level_3_cn)
+
+            # 处理四级分类 (中文 -> 简写)
+            level_4_cn = data.get('level_4_classification')
+            level_4_code = FOURTH_LEVEL_MAP.get(level_4_cn, level_4_cn)
+
+            # 处理四级分类 (中文 -> 简写)
+            level_4_cn = data.get('level_4_classification')
+            level_4_code = FOURTH_LEVEL_MAP.get(level_4_cn, level_4_cn)
+
             # 1. 插入主表 (解耦触发器,手动同步)
             cursor.execute(
                 """
@@ -1189,7 +1664,7 @@ class SampleService:
                     data.get('issuing_authority'), self._to_date(data.get('release_date')), self._to_date(data.get('implementation_date')),
                     data.get('drafting_unit'), data.get('approving_department'),
                     data.get('participating_units'),
-                    data.get('document_type'), data.get('professional_field'), data.get('engineering_phase'),
+                    doc_type_code, prof_field_code, data.get('engineering_phase'),
                     data.get('validity', '现行'), data.get('reference_basis'), data.get('source_url'), data.get('note'),
                     user_id, user_id
                 )
@@ -1205,8 +1680,8 @@ class SampleService:
                 params = (
                     doc_id, data.get('title'), data.get('project_name'), data.get('project_section'), 
                     data.get('issuing_authority'), self._to_date(data.get('release_date')), data.get('plan_summary'), 
-                    data.get('compilation_basis'), data.get('plan_category'), data.get('level_1_classification'), 
-                    data.get('level_2_classification'), data.get('level_3_classification'), data.get('level_4_classification'), 
+                    data.get('compilation_basis'), plan_category_code, level_1_code or 'SC', 
+                    level_2_code, level_3_code, level_4_code, 
                     data.get('note'), user_id, user_id
                 )
             elif type == 'regulation':
@@ -1218,7 +1693,7 @@ class SampleService:
                     ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
                 """
                 params = (
-                    doc_id, data.get('title'), data.get('issuing_authority'), data.get('document_type'), 
+                    doc_id, data.get('title'), data.get('issuing_authority'), doc_type_code, 
                     self._to_date(data.get('release_date')), self._to_date(data.get('effective_start_date')), self._to_date(data.get('effective_end_date')),
                     data.get('note'), user_id, user_id
                 )
@@ -1273,6 +1748,34 @@ class SampleService:
             file_url = self.minio_manager.get_relative_path(data.get('file_url'))
             file_extension = file_url.split('.')[-1] if file_url and '.' in file_url else None
 
+            # 处理文档类型 (中文 -> 简写)
+            doc_type_cn = data.get('document_type')
+            doc_type_code = DOCUMENT_TYPE_MAP.get(doc_type_cn, doc_type_cn)
+
+            # 处理专业领域 (中文 -> 简写)
+            prof_field_cn = data.get('professional_field')
+            prof_field_code = PROFESSIONAL_FIELD_MAP.get(prof_field_cn, prof_field_cn)
+
+            # 处理方案类别 (中文 -> 简写)
+            plan_category_cn = data.get('plan_category')
+            plan_category_code = PLAN_CATEGORY_MAP.get(plan_category_cn, plan_category_cn)
+
+            # 处理一级分类 (中文 -> 简写)
+            level_1_cn = data.get('level_1_classification')
+            level_1_code = FIRST_LEVEL_MAP.get(level_1_cn, level_1_cn)
+
+            # 处理二级分类 (中文 -> 简写)
+            level_2_cn = data.get('level_2_classification')
+            level_2_code = SECOND_LEVEL_MAP.get(level_2_cn, level_2_cn)
+
+            # 处理三级分类 (中文 -> 简写)
+            level_3_cn = data.get('level_3_classification')
+            level_3_code = THIRD_LEVEL_MAP.get(level_3_cn, level_3_cn)
+
+            # 处理四级分类 (中文 -> 简写)
+            level_4_cn = data.get('level_4_classification')
+            level_4_code = FOURTH_LEVEL_MAP.get(level_4_cn, level_4_cn)
+
             # 1. 更新主表 (解耦触发器)
             cursor.execute(
                 """
@@ -1299,7 +1802,7 @@ class SampleService:
                 """
                 params = (
                     data.get('title'), data.get('standard_no'), data.get('issuing_authority'), self._to_date(data.get('release_date')), 
-                    data.get('document_type'), data.get('professional_field'), data.get('validity'),
+                    doc_type_code, prof_field_code, data.get('validity'),
                     data.get('english_name'), self._to_date(data.get('implementation_date')), data.get('drafting_unit'),
                     data.get('approving_department'), data.get('engineering_phase'),
                     data.get('participating_units'),
@@ -1310,18 +1813,16 @@ class SampleService:
             elif type == 'construction_plan':
                 sql = f"""
                 UPDATE {table_name} 
-                SET plan_name = %s, project_name = %s, project_section = %s, compiling_unit = %s, compiling_date = %s, 
-                    plan_summary = %s, compilation_basis = %s, plan_category = %s, 
-                    level_1_classification = %s, level_2_classification = %s, level_3_classification = %s, level_4_classification = %s,
+                SET plan_name = %s, project_name = %s, project_section = %s, compiling_unit = %s, 
+                    compiling_date = %s, plan_summary = %s, compilation_basis = %s, plan_category = %s, 
+                    level_1_classification = %s, level_2_classification = %s, level_3_classification = %s, level_4_classification = %s, 
                     note = %s, updated_by = %s, updated_time = NOW() 
                 WHERE id = %s
                 """
-                
                 params = (
-                    data.get('title'), data.get('project_name'), data.get('project_section'), 
-                    data.get('issuing_authority'), self._to_date(data.get('release_date')),
-                    data.get('plan_summary'), data.get('compilation_basis'), data.get('plan_category'),
-                    data.get('level_1_classification'), data.get('level_2_classification'), data.get('level_3_classification'), data.get('level_4_classification'),
+                    data.get('title'), data.get('project_name'), data.get('project_section'), data.get('issuing_authority'), 
+                    self._to_date(data.get('release_date')), data.get('plan_summary'), data.get('compilation_basis'), plan_category_code, 
+                    level_1_code, level_2_code, level_3_code, level_4_code, 
                     data.get('note'), updater_id, doc_id
                 )
                 
@@ -1334,7 +1835,7 @@ class SampleService:
                 WHERE id = %s
                 """
                 params = (
-                    data.get('title'), data.get('issuing_authority'), data.get('document_type'), self._to_date(data.get('release_date')), 
+                    data.get('title'), data.get('issuing_authority'), doc_type_code, self._to_date(data.get('release_date')), 
                     self._to_date(data.get('effective_start_date')), self._to_date(data.get('effective_end_date')), data.get('note'),
                     updater_id, doc_id
                 )

+ 4 - 3
src/app/services/task_service.py

@@ -2,7 +2,7 @@
 import logging
 import json
 import httpx
-from datetime import datetime
+from datetime import datetime, date
 from typing import List, Dict, Any, Tuple, Optional
 from app.base.async_mysql_connection import get_db_connection
 from app.base.minio_connection import get_minio_manager
@@ -89,6 +89,7 @@ class TaskService:
                 VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
                 ON DUPLICATE KEY UPDATE 
                     task_id = IFNULL(VALUES(task_id), task_id),
+                    project_id = IFNULL(VALUES(project_id), project_id),
                     project_name = IFNULL(VALUES(project_name), project_name),
                     annotation_status = IFNULL(VALUES(annotation_status), annotation_status),
                     tag = IFNULL(VALUES(tag), tag),
@@ -133,8 +134,8 @@ class TaskService:
             return {k: self._serialize_datetime(v) for k, v in obj.items()}
         elif isinstance(obj, list):
             return [self._serialize_datetime(i) for i in obj]
-        elif isinstance(obj, datetime):
-            return obj.strftime('%Y-%m-%d %H:%M:%S')
+        elif isinstance(obj, (datetime, date)):
+            return obj.strftime('%Y-%m-%d %H:%M:%S') if isinstance(obj, datetime) else obj.strftime('%Y-%m-%d')
         return obj
 
     async def get_task_list(self, task_type: str) -> List[Dict[str, Any]]:

+ 21 - 1
src/views/sample_view.py

@@ -11,7 +11,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, Response, Backgr
 from fastapi.responses import HTMLResponse, StreamingResponse
 from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
 
-from app.sample.schemas.sample_schemas import BatchEnterRequest, BatchDeleteRequest, ConvertRequest, DocumentAdd, UploadUrlRequest, ExportRequest
+from app.sample.schemas.sample_schemas import BatchEnterRequest, BatchDeleteRequest, ConvertRequest, DocumentAdd, UploadUrlRequest, ExportRequest, BatchClearRequest
 from app.services.sample_service import SampleService
 from app.services.jwt_token import verify_token
 from app.schemas.base import ApiResponse
@@ -371,6 +371,26 @@ async def batch_enter_knowledge_base(req: BatchEnterRequest, current_user: dict
         logger.exception("批量操作失败")
         return ApiResponse(code=500, message=f"批量操作失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
 
+@router.post("/documents/batch-clear")
+async def batch_clear_knowledge_base(req: BatchClearRequest, current_user: dict = Depends(get_current_user_with_refresh)):
+    """批量清空文档在知识库中的数据片段"""
+    try:
+        username = current_user.get("sub")
+        if not username:
+            return ApiResponse(code=401, message="令牌中缺少用户信息", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
+        
+        sample_service = SampleService()
+        success_count, message = await sample_service.clear_knowledge_base_data(req.ids, username)
+        
+        return ApiResponse(
+            code=0 if success_count > 0 else 1, 
+            message=message, 
+            timestamp=datetime.now(timezone.utc).isoformat()
+        ).model_dump()
+    except Exception as e:
+        logger.exception("批量清空失败")
+        return ApiResponse(code=500, message=f"批量清空失败: {str(e)}", timestamp=datetime.now(timezone.utc).isoformat()).model_dump()
+
 @router.post("/documents/batch-delete")
 async def batch_delete_documents(req: BatchDeleteRequest, current_user: dict = Depends(get_current_user_with_refresh)):
     """批量删除文档"""

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff