Quellcode durchsuchen

新增解析任务表parse_task_repository
新增解析任务API文档parse_task_api_documentation.md
新增API接口get_parse_task_detail,get_parse_tasks

maxiaolong vor 1 Tag
Ursprung
Commit
ac55115b92

+ 150 - 1
app/api/data_parse/routes.py

@@ -1,6 +1,35 @@
 from flask import jsonify, request, make_response, Blueprint, current_app, send_file
 from app.api.data_parse import bp
-from app.core.data_parse.parse import update_business_card, get_business_cards, update_business_card_status, create_talent_tag, get_talent_tag_list, update_talent_tag, delete_talent_tag, query_neo4j_graph, talent_get_tags, talent_update_tags, get_business_card, search_business_cards_by_mobile, get_hotel_positions_list, add_hotel_positions, update_hotel_positions, query_hotel_positions, delete_hotel_positions, get_hotel_group_brands_list, add_hotel_group_brands, update_hotel_group_brands, query_hotel_group_brands, delete_hotel_group_brands, get_duplicate_records, process_duplicate_record, get_duplicate_record_detail, fix_broken_duplicate_records
+from app.core.data_parse.parse import (
+    update_business_card, 
+    get_business_cards, 
+    update_business_card_status, 
+    create_talent_tag, 
+    get_talent_tag_list, 
+    update_talent_tag, 
+    delete_talent_tag, 
+    query_neo4j_graph, 
+    talent_get_tags, 
+    talent_update_tags, 
+    get_business_card, 
+    search_business_cards_by_mobile, 
+    get_hotel_positions_list, 
+    add_hotel_positions, 
+    update_hotel_positions, 
+    query_hotel_positions, 
+    delete_hotel_positions, 
+    get_hotel_group_brands_list, 
+    add_hotel_group_brands, 
+    update_hotel_group_brands, 
+    query_hotel_group_brands, 
+    delete_hotel_group_brands, 
+    get_duplicate_records, 
+    process_duplicate_record, 
+    get_duplicate_record_detail, 
+    fix_broken_duplicate_records, 
+    get_parse_tasks, 
+    get_parse_task_detail
+)
 # 导入新的名片图片解析函数和添加名片函数
 from app.core.data_parse.parse_card import process_business_card_image, add_business_card, delete_business_card
 # 导入网页文本解析函数
@@ -1788,3 +1817,123 @@ def add_webpage_talent_route():
             'data': None
         }), 500
 
+
+# 获取解析任务列表接口
+@bp.route('/get-parse-tasks', methods=['GET'])
+def get_parse_tasks_route():
+    """
+    获取解析任务列表的API接口,支持分页
+    
+    查询参数:
+        - page: 页码,从1开始,默认为1
+        - per_page: 每页记录数,默认为10,最大100
+        - task_type: 任务类型过滤,可选
+        - task_status: 任务状态过滤,可选
+        
+    返回:
+        - JSON: 包含解析任务列表和分页信息
+        
+    功能说明:
+        - 支持分页查询,每页默认10条记录
+        - 支持按任务类型和状态过滤
+        - 按创建时间倒序排列
+        - 返回总记录数和分页信息
+        
+    状态码:
+        - 200: 查询成功
+        - 400: 请求参数错误
+        - 500: 查询失败
+    """
+    try:
+        # 获取查询参数
+        page = request.args.get('page', 1, type=int)
+        per_page = request.args.get('per_page', 10, type=int)
+        task_type = request.args.get('task_type', type=str)
+        task_status = request.args.get('task_status', type=str)
+        
+        # 记录请求日志
+        logger.info(f"获取解析任务列表请求: page={page}, per_page={per_page}, task_type={task_type}, task_status={task_status}")
+        
+        # 调用核心业务逻辑
+        result = get_parse_tasks(page, per_page, task_type, task_status)
+        
+        # 返回结果
+        return jsonify({
+            'success': result['success'],
+            'message': result['message'],
+            'data': result['data']
+        }), result['code']
+        
+    except Exception as e:
+        # 记录错误日志
+        error_msg = f"获取解析任务列表接口失败: {str(e)}"
+        logger.error(error_msg, exc_info=True)
+        
+        # 返回错误响应
+        return jsonify({
+            'success': False,
+            'message': error_msg,
+            'data': None
+        }), 500
+
+
+# 获取解析任务详情接口
+@bp.route('/get-parse-task-detail', methods=['GET'])
+def get_parse_task_detail_route():
+    """
+    获取解析任务详情的API接口
+    
+    查询参数:
+        - task_name: 任务名称,必填
+        
+    返回:
+        - JSON: 包含任务详细信息
+        
+    功能说明:
+        - 根据任务名称查询指定任务的详细信息
+        - 返回任务的所有字段信息
+        - 包含解析结果的完整数据
+        
+    状态码:
+        - 200: 查询成功
+        - 400: 请求参数错误
+        - 404: 任务不存在
+        - 500: 查询失败
+    """
+    try:
+        # 获取查询参数
+        task_name = request.args.get('task_name', type=str)
+        
+        # 参数验证
+        if not task_name:
+            return jsonify({
+                'success': False,
+                'message': '任务名称参数不能为空',
+                'data': None
+            }), 400
+        
+        # 记录请求日志
+        logger.info(f"获取解析任务详情请求: task_name={task_name}")
+        
+        # 调用核心业务逻辑
+        result = get_parse_task_detail(task_name)
+        
+        # 返回结果
+        return jsonify({
+            'success': result['success'],
+            'message': result['message'],
+            'data': result['data']
+        }), result['code']
+        
+    except Exception as e:
+        # 记录错误日志
+        error_msg = f"获取解析任务详情接口失败: {str(e)}"
+        logger.error(error_msg, exc_info=True)
+        
+        # 返回错误响应
+        return jsonify({
+            'success': False,
+            'message': error_msg,
+            'data': None
+        }), 500
+

+ 182 - 0
app/core/data_parse/parse.py

@@ -119,6 +119,40 @@ class DuplicateBusinessCard(db.Model):
         }
 
 
+# 解析任务存储库数据模型
+class ParseTaskRepository(db.Model):
+    __tablename__ = 'parse_task_repository'
+    
+    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
+    task_name = db.Column(db.String(100), nullable=False)
+    task_status = db.Column(db.String(10), nullable=False)
+    task_type = db.Column(db.String(50), nullable=False)
+    task_source = db.Column(db.String(300), nullable=False)
+    collection_count = db.Column(db.Integer, nullable=False, default=0)
+    parse_count = db.Column(db.Integer, nullable=False, default=0)
+    parse_result = db.Column(db.JSON)
+    created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
+    created_by = db.Column(db.String(50), nullable=False)
+    updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, nullable=False)
+    updated_by = db.Column(db.String(50), nullable=False)
+    
+    def to_dict(self):
+        return {
+            'id': self.id,
+            'task_name': self.task_name,
+            'task_status': self.task_status,
+            'task_type': self.task_type,
+            'task_source': self.task_source,
+            'collection_count': self.collection_count,
+            'parse_count': self.parse_count,
+            'parse_result': self.parse_result,
+            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else None,
+            'created_by': self.created_by,
+            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M:%S') if self.updated_at else None,
+            'updated_by': self.updated_by
+        }
+
+
 # 名片解析功能模块
 
 def normalize_mobile_numbers(mobile_str):
@@ -3269,3 +3303,151 @@ def fix_broken_duplicate_records():
             'message': error_msg,
             'data': None
         }
+
+
+def get_parse_tasks(page=1, per_page=10, task_type=None, task_status=None):
+    """
+    获取解析任务列表,支持分页和过滤
+    
+    Args:
+        page (int): 页码,从1开始,默认为1
+        per_page (int): 每页记录数,默认为10,最大100
+        task_type (str): 任务类型过滤,可选
+        task_status (str): 任务状态过滤,可选
+        
+    Returns:
+        dict: 包含查询结果和分页信息的字典
+    """
+    try:
+        # 参数验证
+        if page < 1:
+            return {
+                'code': 400,
+                'success': False,
+                'message': '页码必须大于0',
+                'data': None
+            }
+            
+        if per_page < 1 or per_page > 100:
+            return {
+                'code': 400,
+                'success': False,
+                'message': '每页记录数必须在1-100之间',
+                'data': None
+            }
+        
+        # 构建查询
+        query = db.session.query(ParseTaskRepository)
+        
+        # 添加过滤条件
+        if task_type:
+            query = query.filter(ParseTaskRepository.task_type == task_type)
+        if task_status:
+            query = query.filter(ParseTaskRepository.task_status == task_status)
+        
+        # 按创建时间倒序排列
+        query = query.order_by(ParseTaskRepository.created_at.desc())
+        
+        # 分页查询
+        pagination = query.paginate(
+            page=page,
+            per_page=per_page,
+            error_out=False
+        )
+        
+        # 转换为字典格式
+        tasks = [task.to_dict() for task in pagination.items]
+        
+        # 构建响应数据
+        response_data = {
+            'tasks': tasks,
+            'pagination': {
+                'page': page,
+                'per_page': per_page,
+                'total': pagination.total,
+                'pages': pagination.pages,
+                'has_prev': pagination.has_prev,
+                'has_next': pagination.has_next,
+                'prev_num': pagination.prev_num,
+                'next_num': pagination.next_num
+            }
+        }
+        
+        # 记录查询日志
+        logging.info(f"查询解析任务列表: page={page}, per_page={per_page}, total={pagination.total}")
+        
+        return {
+            'code': 200,
+            'success': True,
+            'message': f'成功获取解析任务列表,共{pagination.total}条记录',
+            'data': response_data
+        }
+        
+    except Exception as e:
+        error_msg = f"获取解析任务列表失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'code': 500,
+            'success': False,
+            'message': error_msg,
+            'data': None
+        }
+
+
+def get_parse_task_detail(task_name):
+    """
+    根据任务名称获取解析任务详情
+    
+    Args:
+        task_name (str): 任务名称
+        
+    Returns:
+        dict: 包含查询结果的字典
+    """
+    try:
+        # 参数验证
+        if not task_name or not isinstance(task_name, str):
+            return {
+                'code': 400,
+                'success': False,
+                'message': '任务名称不能为空',
+                'data': None
+            }
+        
+        # 查询指定任务名称的记录
+        task = db.session.query(ParseTaskRepository).filter(
+            ParseTaskRepository.task_name == task_name
+        ).first()
+        
+        if not task:
+            return {
+                'code': 404,
+                'success': False,
+                'message': f'未找到任务名称为 {task_name} 的记录',
+                'data': None
+            }
+        
+        # 转换为字典格式
+        task_detail = task.to_dict()
+        
+        # 记录查询日志
+        logging.info(f"查询解析任务详情: task_name={task_name}, task_id={task.id}")
+        
+        return {
+            'code': 200,
+            'success': True,
+            'message': f'成功获取任务 {task_name} 的详细信息',
+            'data': task_detail
+        }
+        
+    except Exception as e:
+        error_msg = f"获取解析任务详情失败: {str(e)}"
+        logging.error(error_msg, exc_info=True)
+        
+        return {
+            'code': 500,
+            'success': False,
+            'message': error_msg,
+            'data': None
+        }

+ 31 - 1
app/core/data_parse/parse_web.py

@@ -15,7 +15,7 @@ from app.config.config import DevelopmentConfig, ProductionConfig
 from app.core.data_parse.parse import (
     BusinessCard, check_duplicate_business_card, 
     create_main_card_with_duplicates, update_career_path,
-    normalize_mobile_numbers
+    normalize_mobile_numbers, ParseTaskRepository
 )
 from app import db
 
@@ -760,6 +760,36 @@ def process_webpage_with_QWen(markdown_text, publish_time):
             person['career_path'] = [career_entry]
             logging.info(f"为人员 {person.get('name_zh', 'Unknown')} 添加了career_path记录: {career_entry}")
         
+        # 创建解析任务记录
+        try:
+            # 生成唯一的任务名称:当前日期 + UUID
+            current_date = datetime.now().strftime('%Y%m%d')
+            task_uuid = str(uuid.uuid4())[:8]  # 取UUID的前8位
+            task_name = f"{current_date}_{task_uuid}"
+            
+            # 创建解析任务记录
+            parse_task = ParseTaskRepository(
+                task_name=task_name,
+                task_status='completed',  # 解析完成
+                task_type='门墩儿新任命',
+                task_source='webpage_extraction',
+                collection_count=len(extracted_data),  # 采集人数
+                parse_count=len(extracted_data),  # 解析人数
+                parse_result=extracted_data,  # 解析结果
+                created_by='system',
+                updated_by='system'
+            )
+            
+            db.session.add(parse_task)
+            db.session.commit()
+            
+            logging.info(f"成功创建解析任务记录: {task_name}, 解析人数: {len(extracted_data)}")
+            
+        except Exception as db_error:
+            db.session.rollback()
+            logging.error(f"创建解析任务记录失败: {str(db_error)}", exc_info=True)
+            # 不影响主要功能,只记录错误日志
+        
         return extracted_data
         
     except Exception as e:

+ 33 - 0
create_parse_task_repository_table.sql

@@ -0,0 +1,33 @@
+-- 创建解析任务存储库表
+-- 用于存储数据解析任务的相关信息
+
+-- 创建解析任务存储库表
+CREATE TABLE IF NOT EXISTS parse_task_repository (
+    id SERIAL PRIMARY KEY,
+    task_name VARCHAR(100) NOT NULL,
+    task_status VARCHAR(10) NOT NULL,
+    task_type VARCHAR(50) NOT NULL,
+    task_source VARCHAR(300) NOT NULL,   
+    collection_count INTEGER NOT NULL DEFAULT 0,
+    parse_count INTEGER NOT NULL DEFAULT 0,
+    parse_result JSONB,
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+    created_by VARCHAR(50) NOT NULL,
+    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+    updated_by VARCHAR(50) NOT NULL
+);
+
+-- 添加表注释
+COMMENT ON TABLE parse_task_repository IS '解析任务存储库表,用于存储数据解析任务的相关信息';
+COMMENT ON COLUMN parse_task_repository.id IS '主键ID';
+COMMENT ON COLUMN parse_task_repository.task_name IS '任务名称';
+COMMENT ON COLUMN parse_task_repository.task_status IS '任务状态';
+COMMENT ON COLUMN parse_task_repository.task_type IS '任务类型,包含:名片,简历,门墩儿新任命,门墩儿招聘,杂项';
+COMMENT ON COLUMN parse_task_repository.task_source IS '任务来源';
+COMMENT ON COLUMN parse_task_repository.collection_count IS '采集人数';
+COMMENT ON COLUMN parse_task_repository.parse_count IS '解析人数';
+COMMENT ON COLUMN parse_task_repository.parse_result IS '解析结果,JSON格式';
+COMMENT ON COLUMN parse_task_repository.created_at IS '创建时间';
+COMMENT ON COLUMN parse_task_repository.created_by IS '创建者';
+COMMENT ON COLUMN parse_task_repository.updated_at IS '操作时间';
+COMMENT ON COLUMN parse_task_repository.updated_by IS '操作者';

+ 820 - 0
parse_task_api_documentation.md

@@ -0,0 +1,820 @@
+# 解析任务API接口文档
+
+## 概述
+
+本文档描述了DataOps平台中用于查询解析任务的两个API接口:
+- `get_parse_tasks`: 获取解析任务列表(支持分页和过滤)
+- `get_parse_task_detail`: 获取解析任务详情
+
+这些接口用于查询和管理通过网页解析功能创建的解析任务记录。
+
+---
+
+## 1. 获取解析任务列表
+
+### 接口信息
+- **接口路径**: `/api/data_parse/get-parse-tasks`
+- **请求方法**: `GET`
+- **接口描述**: 获取解析任务列表,支持分页查询和条件过滤
+
+### 请求参数
+
+| 参数名 | 类型 | 必填 | 默认值 | 描述 |
+|--------|------|------|--------|------|
+| page | int | 否 | 1 | 页码,从1开始 |
+| per_page | int | 否 | 10 | 每页记录数,最大100 |
+| task_type | string | 否 | - | 任务类型过滤(如:"门墩儿新任命") |
+| task_status | string | 否 | - | 任务状态过滤(如:"completed") |
+
+### 请求示例
+
+```bash
+# 基础查询
+GET /api/data_parse/get-parse-tasks
+
+# 分页查询
+GET /api/data_parse/get-parse-tasks?page=2&per_page=20
+
+# 条件过滤
+GET /api/data_parse/get-parse-tasks?task_type=门墩儿新任命&task_status=completed
+
+# 组合查询
+GET /api/data_parse/get-parse-tasks?page=1&per_page=10&task_type=门墩儿新任命
+```
+
+### 返回数据结构
+
+```json
+{
+  "success": true,
+  "message": "获取解析任务列表成功",
+  "data": {
+    "tasks": [
+      {
+        "id": 1,
+        "task_name": "20250714_a1b2c3d4",
+        "task_status": "completed",
+        "task_type": "门墩儿新任命",
+        "task_source": "网页解析",
+        "collection_count": 5,
+        "parse_count": 4,
+        "created_at": "2025-01-14T10:30:00Z",
+        "created_by": "system",
+        "updated_at": "2025-01-14T10:35:00Z",
+        "updated_by": "system"
+      }
+    ],
+    "pagination": {
+      "page": 1,
+      "per_page": 10,
+      "total": 25,
+      "pages": 3,
+      "has_prev": false,
+      "has_next": true
+    }
+  }
+}
+```
+
+### 前端JavaScript示例
+
+```javascript
+// 使用fetch API
+async function getParseTasksList(page = 1, perPage = 10, taskType = '', taskStatus = '') {
+    try {
+        const params = new URLSearchParams({
+            page: page.toString(),
+            per_page: perPage.toString()
+        });
+        
+        if (taskType) params.append('task_type', taskType);
+        if (taskStatus) params.append('task_status', taskStatus);
+        
+        const response = await fetch(`/api/data_parse/get-parse-tasks?${params}`, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json'
+            }
+        });
+        
+        const result = await response.json();
+        
+        if (result.success) {
+            console.log('任务列表:', result.data.tasks);
+            console.log('分页信息:', result.data.pagination);
+            return result.data;
+        } else {
+            console.error('获取失败:', result.message);
+            return null;
+        }
+    } catch (error) {
+        console.error('请求异常:', error);
+        return null;
+    }
+}
+
+// 使用示例
+getParseTasksList(1, 10, '门墩儿新任命', 'completed');
+```
+
+### 前端Vue.js示例
+
+```vue
+<template>
+  <div class="parse-tasks-list">
+    <!-- 筛选条件 -->
+    <div class="filters">
+      <select v-model="filters.taskType" @change="loadTasks">
+        <option value="">全部类型</option>
+        <option value="门墩儿新任命">门墩儿新任命</option>
+        <option value="门墩儿招聘">门墩儿招聘</option>
+      </select>
+      
+      <select v-model="filters.taskStatus" @change="loadTasks">
+        <option value="">全部状态</option>
+        <option value="completed">已完成</option>
+        <option value="pending">待处理</option>
+      </select>
+    </div>
+    
+    <!-- 任务列表 -->
+    <div class="tasks-table">
+      <table>
+        <thead>
+          <tr>
+            <th>任务名称</th>
+            <th>任务类型</th>
+            <th>状态</th>
+            <th>采集人数</th>
+            <th>解析人数</th>
+            <th>创建时间</th>
+            <th>操作</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="task in tasks" :key="task.id">
+            <td>{{ task.task_name }}</td>
+            <td>{{ task.task_type }}</td>
+            <td>{{ task.task_status }}</td>
+            <td>{{ task.collection_count }}</td>
+            <td>{{ task.parse_count }}</td>
+            <td>{{ formatDate(task.created_at) }}</td>
+            <td>
+              <button @click="viewDetail(task.task_name)">查看详情</button>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    
+    <!-- 分页 -->
+    <div class="pagination">
+      <button @click="prevPage" :disabled="!pagination.has_prev">上一页</button>
+      <span>第 {{ pagination.page }} 页,共 {{ pagination.pages }} 页</span>
+      <button @click="nextPage" :disabled="!pagination.has_next">下一页</button>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ParseTasksList',
+  data() {
+    return {
+      tasks: [],
+      pagination: {},
+      filters: {
+        taskType: '',
+        taskStatus: ''
+      },
+      currentPage: 1,
+      perPage: 10
+    }
+  },
+  mounted() {
+    this.loadTasks();
+  },
+  methods: {
+    async loadTasks() {
+      try {
+        const params = new URLSearchParams({
+          page: this.currentPage.toString(),
+          per_page: this.perPage.toString()
+        });
+        
+        if (this.filters.taskType) params.append('task_type', this.filters.taskType);
+        if (this.filters.taskStatus) params.append('task_status', this.filters.taskStatus);
+        
+        const response = await fetch(`/api/data_parse/get-parse-tasks?${params}`);
+        const result = await response.json();
+        
+        if (result.success) {
+          this.tasks = result.data.tasks;
+          this.pagination = result.data.pagination;
+        } else {
+          this.$message.error(result.message);
+        }
+      } catch (error) {
+        this.$message.error('加载任务列表失败');
+      }
+    },
+    
+    prevPage() {
+      if (this.pagination.has_prev) {
+        this.currentPage--;
+        this.loadTasks();
+      }
+    },
+    
+    nextPage() {
+      if (this.pagination.has_next) {
+        this.currentPage++;
+        this.loadTasks();
+      }
+    },
+    
+    viewDetail(taskName) {
+      this.$router.push(`/parse-task-detail?task_name=${taskName}`);
+    },
+    
+    formatDate(dateString) {
+      return new Date(dateString).toLocaleString('zh-CN');
+    }
+  }
+}
+</script>
+```
+
+### 测试数据
+
+```json
+{
+  "success": true,
+  "message": "获取解析任务列表成功",
+  "data": {
+    "tasks": [
+      {
+        "id": 1,
+        "task_name": "20250714_a1b2c3d4",
+        "task_status": "completed",
+        "task_type": "门墩儿新任命",
+        "task_source": "网页解析",
+        "collection_count": 5,
+        "parse_count": 4,
+        "created_at": "2025-01-14T10:30:00Z",
+        "created_by": "system",
+        "updated_at": "2025-01-14T10:35:00Z",
+        "updated_by": "system"
+      },
+      {
+        "id": 2,
+        "task_name": "20250714_b2c3d4e5",
+        "task_status": "completed",
+        "task_type": "门墩儿新任命",
+        "task_source": "网页解析",
+        "collection_count": 3,
+        "parse_count": 3,
+        "created_at": "2025-01-14T11:15:00Z",
+        "created_by": "system",
+        "updated_at": "2025-01-14T11:20:00Z",
+        "updated_by": "system"
+      }
+    ],
+    "pagination": {
+      "page": 1,
+      "per_page": 10,
+      "total": 25,
+      "pages": 3,
+      "has_prev": false,
+      "has_next": true
+    }
+  }
+}
+```
+
+### 返回状态码
+
+| 状态码 | 描述 | 示例响应 |
+|--------|------|----------|
+| 200 | 查询成功 | `{"success": true, "message": "获取解析任务列表成功", "data": {...}}` |
+| 400 | 请求参数错误 | `{"success": false, "message": "页码必须大于0", "data": null}` |
+| 500 | 服务器内部错误 | `{"success": false, "message": "数据库连接失败", "data": null}` |
+
+---
+
+## 2. 获取解析任务详情
+
+### 接口信息
+- **接口路径**: `/api/data_parse/get-parse-task-detail`
+- **请求方法**: `GET`
+- **接口描述**: 根据任务名称获取解析任务的详细信息
+
+### 请求参数
+
+| 参数名 | 类型 | 必填 | 默认值 | 描述 |
+|--------|------|------|--------|------|
+| task_name | string | 是 | - | 任务名称(如:"20250714_a1b2c3d4") |
+
+### 请求示例
+
+```bash
+# 基础查询
+GET /api/data_parse/get-parse-task-detail?task_name=20250714_a1b2c3d4
+
+# URL编码示例(如果任务名称包含特殊字符)
+GET /api/data_parse/get-parse-task-detail?task_name=20250714_a1b2c3d4
+```
+
+### 返回数据结构
+
+```json
+{
+  "success": true,
+  "message": "获取解析任务详情成功",
+  "data": {
+    "id": 1,
+    "task_name": "20250714_a1b2c3d4",
+    "task_status": "completed",
+    "task_type": "门墩儿新任命",
+    "task_source": "网页解析",
+    "collection_count": 5,
+    "parse_count": 4,
+    "parse_result": {
+      "success_count": 4,
+      "failed_count": 1,
+      "persons": [
+        {
+          "name_zh": "张三",
+          "name_en": "Zhang San",
+          "title_zh": "总经理",
+          "title_en": "General Manager",
+          "hotel_zh": "北京万豪酒店",
+          "hotel_en": "Beijing Marriott Hotel",
+          "brand_group": "万豪",
+          "mobile": "13800138000",
+          "email": "zhangsan@marriott.com",
+          "pic_url": "https://example.com/photo1.jpg",
+          "career_path": [
+            {
+              "date": "2025-01-14",
+              "hotel_zh": "北京万豪酒店",
+              "hotel_en": "Beijing Marriott Hotel",
+              "title_zh": "总经理",
+              "title_en": "General Manager"
+            }
+          ]
+        }
+      ],
+      "errors": [
+        {
+          "person_index": 5,
+          "error_message": "缺少必要的职位信息"
+        }
+      ]
+    },
+    "created_at": "2025-01-14T10:30:00Z",
+    "created_by": "system",
+    "updated_at": "2025-01-14T10:35:00Z",
+    "updated_by": "system"
+  }
+}
+```
+
+### 前端JavaScript示例
+
+```javascript
+// 使用fetch API
+async function getParseTaskDetail(taskName) {
+    try {
+        const params = new URLSearchParams({
+            task_name: taskName
+        });
+        
+        const response = await fetch(`/api/data_parse/get-parse-task-detail?${params}`, {
+            method: 'GET',
+            headers: {
+                'Content-Type': 'application/json'
+            }
+        });
+        
+        const result = await response.json();
+        
+        if (result.success) {
+            console.log('任务详情:', result.data);
+            return result.data;
+        } else {
+            console.error('获取失败:', result.message);
+            return null;
+        }
+    } catch (error) {
+        console.error('请求异常:', error);
+        return null;
+    }
+}
+
+// 使用示例
+getParseTaskDetail('20250714_a1b2c3d4');
+```
+
+### 前端Vue.js示例
+
+```vue
+<template>
+  <div class="parse-task-detail">
+    <div v-if="loading" class="loading">加载中...</div>
+    
+    <div v-else-if="taskDetail" class="detail-content">
+      <!-- 基本信息 -->
+      <div class="basic-info">
+        <h2>任务基本信息</h2>
+        <div class="info-grid">
+          <div class="info-item">
+            <label>任务名称:</label>
+            <span>{{ taskDetail.task_name }}</span>
+          </div>
+          <div class="info-item">
+            <label>任务类型:</label>
+            <span>{{ taskDetail.task_type }}</span>
+          </div>
+          <div class="info-item">
+            <label>任务状态:</label>
+            <span :class="getStatusClass(taskDetail.task_status)">
+              {{ getStatusText(taskDetail.task_status) }}
+            </span>
+          </div>
+          <div class="info-item">
+            <label>采集人数:</label>
+            <span>{{ taskDetail.collection_count }}</span>
+          </div>
+          <div class="info-item">
+            <label>解析人数:</label>
+            <span>{{ taskDetail.parse_count }}</span>
+          </div>
+          <div class="info-item">
+            <label>创建时间:</label>
+            <span>{{ formatDate(taskDetail.created_at) }}</span>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 解析结果 -->
+      <div class="parse-result" v-if="taskDetail.parse_result">
+        <h2>解析结果</h2>
+        
+        <!-- 统计信息 -->
+        <div class="result-stats">
+          <div class="stat-item success">
+            <span class="label">成功:</span>
+            <span class="value">{{ taskDetail.parse_result.success_count }}</span>
+          </div>
+          <div class="stat-item failed">
+            <span class="label">失败:</span>
+            <span class="value">{{ taskDetail.parse_result.failed_count }}</span>
+          </div>
+        </div>
+        
+        <!-- 人员列表 -->
+        <div class="persons-list" v-if="taskDetail.parse_result.persons">
+          <h3>解析成功的人员</h3>
+          <div class="person-cards">
+            <div v-for="(person, index) in taskDetail.parse_result.persons" 
+                 :key="index" 
+                 class="person-card">
+              <div class="person-photo" v-if="person.pic_url">
+                <img :src="person.pic_url" :alt="person.name_zh" />
+              </div>
+              <div class="person-info">
+                <h4>{{ person.name_zh }} ({{ person.name_en }})</h4>
+                <p><strong>职位:</strong> {{ person.title_zh }} ({{ person.title_en }})</p>
+                <p><strong>酒店:</strong> {{ person.hotel_zh }} ({{ person.hotel_en }})</p>
+                <p><strong>品牌:</strong> {{ person.brand_group }}</p>
+                <p v-if="person.mobile"><strong>手机:</strong> {{ person.mobile }}</p>
+                <p v-if="person.email"><strong>邮箱:</strong> {{ person.email }}</p>
+              </div>
+            </div>
+          </div>
+        </div>
+        
+        <!-- 错误信息 -->
+        <div class="errors-list" v-if="taskDetail.parse_result.errors && taskDetail.parse_result.errors.length > 0">
+          <h3>解析失败的记录</h3>
+          <div class="error-items">
+            <div v-for="(error, index) in taskDetail.parse_result.errors" 
+                 :key="index" 
+                 class="error-item">
+              <span class="error-index">第{{ error.person_index }}个人员:</span>
+              <span class="error-message">{{ error.error_message }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <div v-else class="error-message">
+      {{ errorMessage || '任务不存在或加载失败' }}
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'ParseTaskDetail',
+  data() {
+    return {
+      taskDetail: null,
+      loading: true,
+      errorMessage: ''
+    }
+  },
+  mounted() {
+    const taskName = this.$route.query.task_name;
+    if (taskName) {
+      this.loadTaskDetail(taskName);
+    } else {
+      this.errorMessage = '缺少任务名称参数';
+      this.loading = false;
+    }
+  },
+  methods: {
+    async loadTaskDetail(taskName) {
+      try {
+        this.loading = true;
+        
+        const params = new URLSearchParams({
+          task_name: taskName
+        });
+        
+        const response = await fetch(`/api/data_parse/get-parse-task-detail?${params}`);
+        const result = await response.json();
+        
+        if (result.success) {
+          this.taskDetail = result.data;
+        } else {
+          this.errorMessage = result.message;
+        }
+      } catch (error) {
+        this.errorMessage = '加载任务详情失败';
+      } finally {
+        this.loading = false;
+      }
+    },
+    
+    getStatusClass(status) {
+      const statusMap = {
+        'completed': 'status-completed',
+        'pending': 'status-pending',
+        'failed': 'status-failed'
+      };
+      return statusMap[status] || 'status-default';
+    },
+    
+    getStatusText(status) {
+      const statusMap = {
+        'completed': '已完成',
+        'pending': '待处理',
+        'failed': '失败'
+      };
+      return statusMap[status] || status;
+    },
+    
+    formatDate(dateString) {
+      return new Date(dateString).toLocaleString('zh-CN');
+    }
+  }
+}
+</script>
+
+<style scoped>
+.parse-task-detail {
+  padding: 20px;
+}
+
+.basic-info {
+  margin-bottom: 30px;
+}
+
+.info-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+  gap: 15px;
+  margin-top: 15px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+}
+
+.info-item label {
+  font-weight: bold;
+  margin-right: 10px;
+  min-width: 80px;
+}
+
+.status-completed { color: #52c41a; }
+.status-pending { color: #faad14; }
+.status-failed { color: #ff4d4f; }
+
+.result-stats {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 20px;
+}
+
+.stat-item {
+  padding: 10px 15px;
+  border-radius: 4px;
+  font-weight: bold;
+}
+
+.stat-item.success {
+  background-color: #f6ffed;
+  border: 1px solid #b7eb8f;
+  color: #52c41a;
+}
+
+.stat-item.failed {
+  background-color: #fff2f0;
+  border: 1px solid #ffccc7;
+  color: #ff4d4f;
+}
+
+.person-cards {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
+  gap: 20px;
+  margin-top: 15px;
+}
+
+.person-card {
+  border: 1px solid #e8e8e8;
+  border-radius: 8px;
+  padding: 15px;
+  display: flex;
+  gap: 15px;
+}
+
+.person-photo img {
+  width: 80px;
+  height: 80px;
+  border-radius: 4px;
+  object-fit: cover;
+}
+
+.person-info h4 {
+  margin: 0 0 10px 0;
+  color: #1890ff;
+}
+
+.person-info p {
+  margin: 5px 0;
+  font-size: 14px;
+}
+
+.error-items {
+  margin-top: 15px;
+}
+
+.error-item {
+  padding: 10px;
+  background-color: #fff2f0;
+  border: 1px solid #ffccc7;
+  border-radius: 4px;
+  margin-bottom: 10px;
+}
+
+.error-index {
+  font-weight: bold;
+  color: #ff4d4f;
+  margin-right: 10px;
+}
+
+.loading, .error-message {
+  text-align: center;
+  padding: 50px;
+  color: #666;
+}
+</style>
+```
+
+### 测试数据
+
+```json
+{
+  "success": true,
+  "message": "获取解析任务详情成功",
+  "data": {
+    "id": 1,
+    "task_name": "20250714_a1b2c3d4",
+    "task_status": "completed",
+    "task_type": "门墩儿新任命",
+    "task_source": "网页解析",
+    "collection_count": 5,
+    "parse_count": 4,
+    "parse_result": {
+      "success_count": 4,
+      "failed_count": 1,
+      "persons": [
+        {
+          "name_zh": "张三",
+          "name_en": "Zhang San",
+          "title_zh": "总经理",
+          "title_en": "General Manager",
+          "hotel_zh": "北京万豪酒店",
+          "hotel_en": "Beijing Marriott Hotel",
+          "brand_group": "万豪",
+          "mobile": "13800138000",
+          "email": "zhangsan@marriott.com",
+          "pic_url": "https://example.com/photo1.jpg",
+          "career_path": [
+            {
+              "date": "2025-01-14",
+              "hotel_zh": "北京万豪酒店",
+              "hotel_en": "Beijing Marriott Hotel",
+              "title_zh": "总经理",
+              "title_en": "General Manager"
+            }
+          ]
+        },
+        {
+          "name_zh": "李四",
+          "name_en": "Li Si",
+          "title_zh": "市场总监",
+          "title_en": "Marketing Director",
+          "hotel_zh": "上海希尔顿酒店",
+          "hotel_en": "Shanghai Hilton Hotel",
+          "brand_group": "希尔顿",
+          "mobile": "13900139000",
+          "email": "lisi@hilton.com",
+          "pic_url": "https://example.com/photo2.jpg",
+          "career_path": [
+            {
+              "date": "2025-01-14",
+              "hotel_zh": "上海希尔顿酒店",
+              "hotel_en": "Shanghai Hilton Hotel",
+              "title_zh": "市场总监",
+              "title_en": "Marketing Director"
+            }
+          ]
+        }
+      ],
+      "errors": [
+        {
+          "person_index": 5,
+          "error_message": "缺少必要的职位信息"
+        }
+      ]
+    },
+    "created_at": "2025-01-14T10:30:00Z",
+    "created_by": "system",
+    "updated_at": "2025-01-14T10:35:00Z",
+    "updated_by": "system"
+  }
+}
+```
+
+### 返回状态码
+
+| 状态码 | 描述 | 示例响应 |
+|--------|------|----------|
+| 200 | 查询成功 | `{"success": true, "message": "获取解析任务详情成功", "data": {...}}` |
+| 400 | 请求参数错误 | `{"success": false, "message": "任务名称参数不能为空", "data": null}` |
+| 404 | 任务不存在 | `{"success": false, "message": "未找到指定的解析任务", "data": null}` |
+| 500 | 服务器内部错误 | `{"success": false, "message": "数据库查询失败", "data": null}` |
+
+---
+
+## 使用场景
+
+### 1. 任务监控面板
+- 定期调用`get_parse_tasks`接口获取最新任务状态
+- 显示任务执行统计和成功率
+- 提供任务筛选和搜索功能
+
+### 2. 任务详情查看
+- 点击任务列表中的任务,调用`get_parse_task_detail`查看详情
+- 显示解析结果和错误信息
+- 支持重新处理失败的记录
+
+### 3. 数据分析
+- 通过API获取历史任务数据
+- 分析解析成功率和常见错误
+- 生成任务执行报告
+
+---
+
+## 注意事项
+
+1. **分页限制**: `get_parse_tasks`接口的`per_page`参数最大值为100,避免单次查询数据量过大
+2. **任务名称格式**: 任务名称通常为日期+UUID格式,如`20250714_a1b2c3d4`
+3. **解析结果**: `parse_result`字段包含完整的解析数据,数据量可能较大
+4. **时间格式**: 所有时间字段均为ISO 8601格式的UTC时间
+5. **错误处理**: 建议在前端实现适当的错误处理和重试机制
+
+---
+
+## 更新日志
+
+- **2025-01-14**: 初始版本,支持基础的任务查询功能
+- **2025-01-14**: 添加分页和过滤功能
+- **2025-01-14**: 完善错误处理和返回状态码