# 解析任务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**: 完善错误处理和返回状态码 