|
@@ -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**: 完善错误处理和返回状态码
|