# Data Factory API 前端开发说明文档

## 概述

Data Factory API 提供了与 n8n 工作流引擎的集成接口，支持工作流的查询、状态监控、执行记录查看和工作流触发等功能。

**版本**: v1.0  
**基础URL**: `/api/datafactory`  
**内容类型**: `application/json`  
**字符编码**: UTF-8

## 通用响应格式

所有API接口都遵循统一的响应格式：

```json
{
  "code": 200,           // HTTP状态码
  "message": "操作成功",  // 操作结果消息
  "data": {}             // 返回的具体数据
}
```

## 状态码说明

| 状态码 | 含义 | 说明 |
|--------|------|------|
| 200 | 成功 | 请求成功执行 |
| 400 | 请求错误 | 请求参数错误或缺失 |
| 401 | 认证失败 | n8n API Key 无效 |
| 403 | 权限不足 | 无权访问该资源 |
| 404 | 未找到 | 请求的资源不存在 |
| 500 | 服务器错误 | 服务器内部错误 |
| 503 | 服务不可用 | n8n 服务连接失败 |

---

## API 接口详情

### 1. 获取工作流列表

**接口描述**: 获取 n8n 系统中的工作流列表，支持分页、搜索和状态过滤。

#### 请求信息
- **HTTP方法**: `GET`
- **请求路径**: `/workflows`
- **请求头**: `Content-Type: application/json`

#### 请求参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| page | int | 否 | 1 | 页码，从1开始 |
| page_size | int | 否 | 20 | 每页数量 |
| active | string | 否 | - | 过滤活跃状态：`true`/`false` |
| search | string | 否 | - | 按名称搜索关键词 |
| tags | string | 否 | - | 按标签过滤，多个标签用逗号分隔 |

#### 响应数据

```json
{
  "code": 200,
  "message": "获取工作流列表成功",
  "data": {
    "items": [
      {
        "id": "1PCBqwesRXiFcfJ1",
        "name": "Simple RAG",
        "active": false,
        "tags": ["AI", "RAG"],
        "created_at": "2025-12-01 10:00:00",
        "updated_at": "2025-12-03 03:14:49"
      },
      {
        "id": "9w5VhCRlRrjFqDpX",
        "name": "My workflow 2",
        "active": true,
        "tags": [],
        "created_at": "2025-10-28 11:00:00",
        "updated_at": "2025-10-28 12:06:12"
      }
    ],
    "total": 3,
    "page": 1,
    "page_size": 20,
    "total_pages": 1
  }
}
```

#### 响应字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| items | array | 工作流列表 |
| items[].id | string | 工作流唯一标识 |
| items[].name | string | 工作流名称 |
| items[].active | boolean | 是否激活状态 |
| items[].tags | array | 标签列表 |
| items[].created_at | string | 创建时间 |
| items[].updated_at | string | 更新时间 |
| total | int | 总记录数 |
| page | int | 当前页码 |
| page_size | int | 每页数量 |
| total_pages | int | 总页数 |

#### Vue 代码示例

```vue
<template>
  <div class="workflow-list">
    <!-- 搜索栏 -->
    <div class="search-bar">
      <el-input
        v-model="searchKeyword"
        placeholder="搜索工作流名称"
        clearable
        @clear="fetchWorkflows"
        @keyup.enter="fetchWorkflows"
      >
        <template #append>
          <el-button @click="fetchWorkflows">
            <el-icon><Search /></el-icon>
          </el-button>
        </template>
      </el-input>
      
      <el-select v-model="activeFilter" placeholder="状态筛选" clearable @change="fetchWorkflows">
        <el-option label="全部" value="" />
        <el-option label="活跃" value="true" />
        <el-option label="停用" value="false" />
      </el-select>
    </div>

    <!-- 工作流表格 -->
    <el-table :data="workflows" v-loading="loading" stripe>
      <el-table-column prop="id" label="ID" width="180" />
      <el-table-column prop="name" label="工作流名称" />
      <el-table-column prop="active" label="状态" width="100">
        <template #default="{ row }">
          <el-tag :type="row.active ? 'success' : 'info'">
            {{ row.active ? '活跃' : '停用' }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="tags" label="标签" width="200">
        <template #default="{ row }">
          <el-tag v-for="tag in row.tags" :key="tag" size="small" class="tag-item">
            {{ tag }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="updated_at" label="更新时间" width="180" />
      <el-table-column label="操作" width="200" fixed="right">
        <template #default="{ row }">
          <el-button type="primary" link @click="viewDetail(row.id)">详情</el-button>
          <el-button type="primary" link @click="viewStatus(row.id)">状态</el-button>
          <el-button type="primary" link @click="viewExecutions(row.id)">执行记录</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <el-pagination
      v-model:current-page="pagination.page"
      v-model:page-size="pagination.pageSize"
      :total="pagination.total"
      :page-sizes="[10, 20, 50, 100]"
      layout="total, sizes, prev, pager, next, jumper"
      @size-change="fetchWorkflows"
      @current-change="fetchWorkflows"
    />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import axios from 'axios'

const loading = ref(false)
const workflows = ref([])
const searchKeyword = ref('')
const activeFilter = ref('')

const pagination = reactive({
  page: 1,
  pageSize: 20,
  total: 0
})

const fetchWorkflows = async () => {
  loading.value = true
  try {
    const params = {
      page: pagination.page,
      page_size: pagination.pageSize
    }
    
    if (searchKeyword.value) {
      params.search = searchKeyword.value
    }
    if (activeFilter.value) {
      params.active = activeFilter.value
    }

    const response = await axios.get('/api/datafactory/workflows', { params })
    
    if (response.data.code === 200) {
      workflows.value = response.data.data.items
      pagination.total = response.data.data.total
    } else {
      ElMessage.error(response.data.message)
    }
  } catch (error) {
    ElMessage.error('获取工作流列表失败')
    console.error(error)
  } finally {
    loading.value = false
  }
}

const viewDetail = (id) => {
  // 跳转到详情页
  router.push(`/datafactory/workflow/${id}`)
}

const viewStatus = (id) => {
  // 跳转到状态页
  router.push(`/datafactory/workflow/${id}/status`)
}

const viewExecutions = (id) => {
  // 跳转到执行记录页
  router.push(`/datafactory/workflow/${id}/executions`)
}

onMounted(() => {
  fetchWorkflows()
})
</script>

<style scoped>
.workflow-list {
  padding: 20px;
}

.search-bar {
  display: flex;
  gap: 16px;
  margin-bottom: 20px;
}

.search-bar .el-input {
  width: 300px;
}

.tag-item {
  margin-right: 4px;
}

.el-pagination {
  margin-top: 20px;
  justify-content: flex-end;
}
</style>
```

---

### 2. 获取工作流详情

**接口描述**: 根据工作流ID获取详细信息，包括节点列表和配置。

#### 请求信息
- **HTTP方法**: `GET`
- **请求路径**: `/workflows/{workflow_id}`
- **请求头**: `Content-Type: application/json`

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workflow_id | string | 是 | 工作流ID |

#### 响应数据

```json
{
  "code": 200,
  "message": "获取工作流详情成功",
  "data": {
    "id": "1PCBqwesRXiFcfJ1",
    "name": "Simple RAG",
    "active": false,
    "tags": ["AI", "RAG"],
    "created_at": "2025-12-01 10:00:00",
    "updated_at": "2025-12-03 03:14:49",
    "nodes_count": 5,
    "nodes": [
      {
        "id": "node-1",
        "name": "Start",
        "type": "n8n-nodes-base.manualTrigger",
        "type_version": 1,
        "position": [250, 300],
        "disabled": false
      },
      {
        "id": "node-2",
        "name": "HTTP Request",
        "type": "n8n-nodes-base.httpRequest",
        "type_version": 4,
        "position": [450, 300],
        "disabled": false
      }
    ],
    "settings": {
      "executionOrder": "v1"
    }
  }
}
```

#### 响应字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| id | string | 工作流ID |
| name | string | 工作流名称 |
| active | boolean | 是否激活 |
| tags | array | 标签列表 |
| created_at | string | 创建时间 |
| updated_at | string | 更新时间 |
| nodes_count | int | 节点数量 |
| nodes | array | 节点列表 |
| nodes[].id | string | 节点ID |
| nodes[].name | string | 节点名称 |
| nodes[].type | string | 节点类型 |
| nodes[].type_version | int | 节点版本 |
| nodes[].position | array | 节点位置 [x, y] |
| nodes[].disabled | boolean | 是否禁用 |
| settings | object | 工作流设置 |

#### Vue 代码示例

```vue
<template>
  <div class="workflow-detail" v-loading="loading">
    <el-page-header @back="goBack" :title="workflow.name">
      <template #content>
        <div class="header-content">
          <el-tag :type="workflow.active ? 'success' : 'info'" size="large">
            {{ workflow.active ? '活跃' : '停用' }}
          </el-tag>
        </div>
      </template>
      <template #extra>
        <el-button-group>
          <el-button 
            v-if="!workflow.active" 
            type="success" 
            @click="activateWorkflow"
          >
            激活
          </el-button>
          <el-button 
            v-else 
            type="warning" 
            @click="deactivateWorkflow"
          >
            停用
          </el-button>
        </el-button-group>
      </template>
    </el-page-header>

    <el-descriptions :column="2" border class="detail-info">
      <el-descriptions-item label="工作流ID">{{ workflow.id }}</el-descriptions-item>
      <el-descriptions-item label="节点数量">{{ workflow.nodes_count }}</el-descriptions-item>
      <el-descriptions-item label="创建时间">{{ workflow.created_at }}</el-descriptions-item>
      <el-descriptions-item label="更新时间">{{ workflow.updated_at }}</el-descriptions-item>
      <el-descriptions-item label="标签" :span="2">
        <el-tag v-for="tag in workflow.tags" :key="tag" class="tag-item">
          {{ tag }}
        </el-tag>
        <span v-if="!workflow.tags?.length">无</span>
      </el-descriptions-item>
    </el-descriptions>

    <el-card class="nodes-card">
      <template #header>
        <span>节点列表</span>
      </template>
      <el-table :data="workflow.nodes" stripe>
        <el-table-column prop="name" label="节点名称" />
        <el-table-column prop="type" label="节点类型" />
        <el-table-column prop="type_version" label="版本" width="80" />
        <el-table-column prop="disabled" label="状态" width="100">
          <template #default="{ row }">
            <el-tag :type="row.disabled ? 'danger' : 'success'" size="small">
              {{ row.disabled ? '禁用' : '启用' }}
            </el-tag>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'
import { ElMessage, ElMessageBox } from 'element-plus'

const route = useRoute()
const router = useRouter()
const loading = ref(false)
const workflow = ref({})

const workflowId = route.params.id

const fetchWorkflowDetail = async () => {
  loading.value = true
  try {
    const response = await axios.get(`/api/datafactory/workflows/${workflowId}`)
    
    if (response.data.code === 200) {
      workflow.value = response.data.data
    } else {
      ElMessage.error(response.data.message)
    }
  } catch (error) {
    ElMessage.error('获取工作流详情失败')
    console.error(error)
  } finally {
    loading.value = false
  }
}

const activateWorkflow = async () => {
  try {
    await ElMessageBox.confirm('确定要激活该工作流吗？', '提示', {
      type: 'warning'
    })
    
    const response = await axios.post(`/api/datafactory/workflows/${workflowId}/activate`)
    
    if (response.data.code === 200) {
      ElMessage.success('工作流已激活')
      fetchWorkflowDetail()
    } else {
      ElMessage.error(response.data.message)
    }
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('激活失败')
    }
  }
}

const deactivateWorkflow = async () => {
  try {
    await ElMessageBox.confirm('确定要停用该工作流吗？', '提示', {
      type: 'warning'
    })
    
    const response = await axios.post(`/api/datafactory/workflows/${workflowId}/deactivate`)
    
    if (response.data.code === 200) {
      ElMessage.success('工作流已停用')
      fetchWorkflowDetail()
    } else {
      ElMessage.error(response.data.message)
    }
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('停用失败')
    }
  }
}

const goBack = () => {
  router.back()
}

onMounted(() => {
  fetchWorkflowDetail()
})
</script>

<style scoped>
.workflow-detail {
  padding: 20px;
}

.header-content {
  display: flex;
  align-items: center;
}

.detail-info {
  margin: 20px 0;
}

.nodes-card {
  margin-top: 20px;
}

.tag-item {
  margin-right: 8px;
}
</style>
```

---

### 3. 获取工作流状态

**接口描述**: 获取工作流的运行状态和最近执行情况统计。

#### 请求信息
- **HTTP方法**: `GET`
- **请求路径**: `/workflows/{workflow_id}/status`
- **请求头**: `Content-Type: application/json`

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workflow_id | string | 是 | 工作流ID |

#### 响应数据

```json
{
  "code": 200,
  "message": "获取工作流状态成功",
  "data": {
    "workflow_id": "9w5VhCRlRrjFqDpX",
    "name": "My workflow 2",
    "active": true,
    "status": "active",
    "status_label": "运行中",
    "recent_executions": {
      "total": 5,
      "success": 4,
      "error": 1
    },
    "last_execution": {
      "id": "12345",
      "workflow_id": "9w5VhCRlRrjFqDpX",
      "workflow_name": "My workflow 2",
      "status": "success",
      "status_label": "成功",
      "mode": "trigger",
      "started_at": "2025-12-24 10:30:00",
      "finished_at": "2025-12-24 10:30:05",
      "retry_of": null,
      "retry_success_id": null
    },
    "updated_at": "2025-10-28 12:06:12"
  }
}
```

#### 响应字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| workflow_id | string | 工作流ID |
| name | string | 工作流名称 |
| active | boolean | 是否激活 |
| status | string | 状态码：active/inactive |
| status_label | string | 状态显示文本 |
| recent_executions | object | 最近执行统计 |
| recent_executions.total | int | 最近执行总数 |
| recent_executions.success | int | 成功次数 |
| recent_executions.error | int | 失败次数 |
| last_execution | object | 最后一次执行信息（可能为null） |
| updated_at | string | 更新时间 |

#### Vue 代码示例

```vue
<template>
  <div class="workflow-status" v-loading="loading">
    <el-card class="status-card">
      <template #header>
        <div class="card-header">
          <span>{{ status.name }}</span>
          <el-tag :type="status.active ? 'success' : 'info'" size="large">
            {{ status.status_label }}
          </el-tag>
        </div>
      </template>
      
      <el-row :gutter="20">
        <el-col :span="8">
          <el-statistic title="最近执行总数" :value="status.recent_executions?.total || 0" />
        </el-col>
        <el-col :span="8">
          <el-statistic title="成功次数" :value="status.recent_executions?.success || 0">
            <template #suffix>
              <el-icon color="#67C23A"><SuccessFilled /></el-icon>
            </template>
          </el-statistic>
        </el-col>
        <el-col :span="8">
          <el-statistic title="失败次数" :value="status.recent_executions?.error || 0">
            <template #suffix>
              <el-icon color="#F56C6C"><CircleCloseFilled /></el-icon>
            </template>
          </el-statistic>
        </el-col>
      </el-row>
    </el-card>

    <el-card class="last-execution-card" v-if="status.last_execution">
      <template #header>
        <span>最后一次执行</span>
      </template>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="执行ID">
          {{ status.last_execution.id }}
        </el-descriptions-item>
        <el-descriptions-item label="状态">
          <el-tag :type="getStatusType(status.last_execution.status)">
            {{ status.last_execution.status_label }}
          </el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="开始时间">
          {{ status.last_execution.started_at }}
        </el-descriptions-item>
        <el-descriptions-item label="结束时间">
          {{ status.last_execution.finished_at }}
        </el-descriptions-item>
        <el-descriptions-item label="执行模式">
          {{ status.last_execution.mode }}
        </el-descriptions-item>
      </el-descriptions>
    </el-card>

    <el-empty v-else description="暂无执行记录" />
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import { SuccessFilled, CircleCloseFilled } from '@element-plus/icons-vue'
import axios from 'axios'

const route = useRoute()
const loading = ref(false)
const status = ref({})
let refreshTimer = null

const workflowId = route.params.id

const fetchStatus = async () => {
  loading.value = true
  try {
    const response = await axios.get(`/api/datafactory/workflows/${workflowId}/status`)
    
    if (response.data.code === 200) {
      status.value = response.data.data
    }
  } catch (error) {
    console.error('获取状态失败', error)
  } finally {
    loading.value = false
  }
}

const getStatusType = (statusCode) => {
  const types = {
    success: 'success',
    error: 'danger',
    waiting: 'warning',
    running: 'primary'
  }
  return types[statusCode] || 'info'
}

onMounted(() => {
  fetchStatus()
  // 每30秒自动刷新状态
  refreshTimer = setInterval(fetchStatus, 30000)
})

onUnmounted(() => {
  if (refreshTimer) {
    clearInterval(refreshTimer)
  }
})
</script>

<style scoped>
.workflow-status {
  padding: 20px;
}

.status-card {
  margin-bottom: 20px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.last-execution-card {
  margin-top: 20px;
}
</style>
```

---

### 4. 激活工作流

**接口描述**: 激活指定的工作流。

#### 请求信息
- **HTTP方法**: `POST`
- **请求路径**: `/workflows/{workflow_id}/activate`
- **请求头**: `Content-Type: application/json`

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workflow_id | string | 是 | 工作流ID |

#### 响应数据

```json
{
  "code": 200,
  "message": "工作流激活成功",
  "data": {
    "workflow_id": "9w5VhCRlRrjFqDpX",
    "active": true,
    "message": "工作流已激活"
  }
}
```

---

### 5. 停用工作流

**接口描述**: 停用指定的工作流。

#### 请求信息
- **HTTP方法**: `POST`
- **请求路径**: `/workflows/{workflow_id}/deactivate`
- **请求头**: `Content-Type: application/json`

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workflow_id | string | 是 | 工作流ID |

#### 响应数据

```json
{
  "code": 200,
  "message": "工作流停用成功",
  "data": {
    "workflow_id": "9w5VhCRlRrjFqDpX",
    "active": false,
    "message": "工作流已停用"
  }
}
```

---

### 6. 获取工作流执行记录列表

**接口描述**: 获取指定工作流的执行记录列表。

#### 请求信息
- **HTTP方法**: `GET`
- **请求路径**: `/workflows/{workflow_id}/executions`
- **请求头**: `Content-Type: application/json`

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workflow_id | string | 是 | 工作流ID |

#### 请求参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 20 | 每页数量 |
| status | string | 否 | - | 状态过滤：`success`/`error`/`waiting` |

#### 响应数据

```json
{
  "code": 200,
  "message": "获取执行记录列表成功",
  "data": {
    "items": [
      {
        "id": "12345",
        "workflow_id": "9w5VhCRlRrjFqDpX",
        "workflow_name": "My workflow 2",
        "status": "success",
        "status_label": "成功",
        "mode": "trigger",
        "started_at": "2025-12-24 10:30:00",
        "finished_at": "2025-12-24 10:30:05",
        "retry_of": null,
        "retry_success_id": null
      },
      {
        "id": "12344",
        "workflow_id": "9w5VhCRlRrjFqDpX",
        "workflow_name": "My workflow 2",
        "status": "error",
        "status_label": "失败",
        "mode": "manual",
        "started_at": "2025-12-24 09:15:00",
        "finished_at": "2025-12-24 09:15:03",
        "retry_of": null,
        "retry_success_id": null
      }
    ],
    "total": 25,
    "page": 1,
    "page_size": 20,
    "total_pages": 2
  }
}
```

#### 响应字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| items[].id | string | 执行记录ID |
| items[].workflow_id | string | 工作流ID |
| items[].workflow_name | string | 工作流名称 |
| items[].status | string | 状态码：success/error/waiting/running |
| items[].status_label | string | 状态显示文本 |
| items[].mode | string | 执行模式：trigger/manual/webhook等 |
| items[].started_at | string | 开始时间 |
| items[].finished_at | string | 结束时间 |
| items[].retry_of | string | 重试来源执行ID（可能为null） |
| items[].retry_success_id | string | 重试成功执行ID（可能为null） |

#### Vue 代码示例

```vue
<template>
  <div class="execution-list">
    <!-- 筛选栏 -->
    <div class="filter-bar">
      <el-select v-model="statusFilter" placeholder="状态筛选" clearable @change="fetchExecutions">
        <el-option label="全部" value="" />
        <el-option label="成功" value="success" />
        <el-option label="失败" value="error" />
        <el-option label="等待中" value="waiting" />
      </el-select>
      
      <el-button type="primary" @click="fetchExecutions">
        <el-icon><Refresh /></el-icon>
        刷新
      </el-button>
    </div>

    <!-- 执行记录表格 -->
    <el-table :data="executions" v-loading="loading" stripe>
      <el-table-column prop="id" label="执行ID" width="120" />
      <el-table-column prop="status" label="状态" width="100">
        <template #default="{ row }">
          <el-tag :type="getStatusType(row.status)">
            {{ row.status_label }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="mode" label="执行模式" width="100" />
      <el-table-column prop="started_at" label="开始时间" width="180" />
      <el-table-column prop="finished_at" label="结束时间" width="180" />
      <el-table-column label="耗时" width="100">
        <template #default="{ row }">
          {{ calculateDuration(row.started_at, row.finished_at) }}
        </template>
      </el-table-column>
      <el-table-column label="操作" width="100" fixed="right">
        <template #default="{ row }">
          <el-button type="primary" link @click="viewExecutionDetail(row.id)">
            详情
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <el-pagination
      v-model:current-page="pagination.page"
      v-model:page-size="pagination.pageSize"
      :total="pagination.total"
      :page-sizes="[10, 20, 50, 100]"
      layout="total, sizes, prev, pager, next"
      @size-change="fetchExecutions"
      @current-change="fetchExecutions"
    />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Refresh } from '@element-plus/icons-vue'
import axios from 'axios'
import dayjs from 'dayjs'

const route = useRoute()
const router = useRouter()
const loading = ref(false)
const executions = ref([])
const statusFilter = ref('')

const workflowId = route.params.id

const pagination = reactive({
  page: 1,
  pageSize: 20,
  total: 0
})

const fetchExecutions = async () => {
  loading.value = true
  try {
    const params = {
      page: pagination.page,
      page_size: pagination.pageSize
    }
    
    if (statusFilter.value) {
      params.status = statusFilter.value
    }

    const response = await axios.get(
      `/api/datafactory/workflows/${workflowId}/executions`,
      { params }
    )
    
    if (response.data.code === 200) {
      executions.value = response.data.data.items
      pagination.total = response.data.data.total
    }
  } catch (error) {
    console.error('获取执行记录失败', error)
  } finally {
    loading.value = false
  }
}

const getStatusType = (status) => {
  const types = {
    success: 'success',
    error: 'danger',
    waiting: 'warning',
    running: 'primary'
  }
  return types[status] || 'info'
}

const calculateDuration = (start, end) => {
  if (!start || !end) return '-'
  const duration = dayjs(end).diff(dayjs(start), 'second')
  if (duration < 60) return `${duration}秒`
  if (duration < 3600) return `${Math.floor(duration / 60)}分${duration % 60}秒`
  return `${Math.floor(duration / 3600)}时${Math.floor((duration % 3600) / 60)}分`
}

const viewExecutionDetail = (executionId) => {
  router.push(`/datafactory/executions/${executionId}`)
}

onMounted(() => {
  fetchExecutions()
})
</script>

<style scoped>
.execution-list {
  padding: 20px;
}

.filter-bar {
  display: flex;
  gap: 16px;
  margin-bottom: 20px;
}

.el-pagination {
  margin-top: 20px;
  justify-content: flex-end;
}
</style>
```

---

### 7. 获取所有执行记录列表

**接口描述**: 获取所有工作流的执行记录列表（不限定工作流）。

#### 请求信息
- **HTTP方法**: `GET`
- **请求路径**: `/executions`
- **请求头**: `Content-Type: application/json`

#### 请求参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| page | int | 否 | 1 | 页码 |
| page_size | int | 否 | 20 | 每页数量 |
| workflow_id | string | 否 | - | 按工作流ID过滤 |
| status | string | 否 | - | 状态过滤：`success`/`error`/`waiting` |

#### 响应数据

响应格式与 `/workflows/{workflow_id}/executions` 相同。

---

### 8. 获取执行详情

**接口描述**: 获取单次执行的详细信息，包括各节点的执行结果。

#### 请求信息
- **HTTP方法**: `GET`
- **请求路径**: `/executions/{execution_id}`
- **请求头**: `Content-Type: application/json`

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| execution_id | string | 是 | 执行ID |

#### 响应数据

```json
{
  "code": 200,
  "message": "获取执行详情成功",
  "data": {
    "id": "12345",
    "workflow_id": "9w5VhCRlRrjFqDpX",
    "workflow_name": "My workflow 2",
    "status": "success",
    "status_label": "成功",
    "mode": "trigger",
    "started_at": "2025-12-24 10:30:00",
    "finished_at": "2025-12-24 10:30:05",
    "retry_of": null,
    "retry_success_id": null,
    "node_results": [
      {
        "node_name": "Start",
        "start_time": "2025-12-24 10:30:00",
        "execution_time": 10,
        "source": [],
        "data": {
          "main": [[{"json": {"started": true}}]]
        }
      },
      {
        "node_name": "HTTP Request",
        "start_time": "2025-12-24 10:30:01",
        "execution_time": 3500,
        "source": [{"previousNode": "Start"}],
        "data": {
          "main": [[{"json": {"response": "OK"}}]]
        }
      }
    ],
    "error": null
  }
}
```

#### 响应字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| node_results | array | 各节点执行结果列表 |
| node_results[].node_name | string | 节点名称 |
| node_results[].start_time | string | 节点开始执行时间 |
| node_results[].execution_time | int | 执行耗时（毫秒） |
| node_results[].source | array | 数据来源节点 |
| node_results[].data | object | 节点输出数据 |
| error | object | 错误信息（成功时为null） |

#### Vue 代码示例

```vue
<template>
  <div class="execution-detail" v-loading="loading">
    <el-page-header @back="goBack" title="执行详情">
      <template #content>
        <el-tag :type="getStatusType(execution.status)" size="large">
          {{ execution.status_label }}
        </el-tag>
      </template>
    </el-page-header>

    <el-descriptions :column="2" border class="execution-info">
      <el-descriptions-item label="执行ID">{{ execution.id }}</el-descriptions-item>
      <el-descriptions-item label="工作流">{{ execution.workflow_name }}</el-descriptions-item>
      <el-descriptions-item label="执行模式">{{ execution.mode }}</el-descriptions-item>
      <el-descriptions-item label="状态">
        <el-tag :type="getStatusType(execution.status)">
          {{ execution.status_label }}
        </el-tag>
      </el-descriptions-item>
      <el-descriptions-item label="开始时间">{{ execution.started_at }}</el-descriptions-item>
      <el-descriptions-item label="结束时间">{{ execution.finished_at }}</el-descriptions-item>
    </el-descriptions>

    <!-- 错误信息 -->
    <el-alert
      v-if="execution.error"
      :title="execution.error.message || '执行出错'"
      type="error"
      :description="JSON.stringify(execution.error, null, 2)"
      show-icon
      class="error-alert"
    />

    <!-- 节点执行结果 -->
    <el-card class="node-results-card">
      <template #header>
        <span>节点执行结果</span>
      </template>
      
      <el-timeline>
        <el-timeline-item
          v-for="(node, index) in execution.node_results"
          :key="index"
          :type="getNodeStatusType(node)"
          :timestamp="node.start_time"
        >
          <el-card shadow="hover">
            <div class="node-header">
              <span class="node-name">{{ node.node_name }}</span>
              <el-tag size="small">{{ node.execution_time }}ms</el-tag>
            </div>
            <div class="node-data">
              <el-collapse>
                <el-collapse-item title="输出数据">
                  <pre>{{ JSON.stringify(node.data, null, 2) }}</pre>
                </el-collapse-item>
              </el-collapse>
            </div>
          </el-card>
        </el-timeline-item>
      </el-timeline>
    </el-card>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import axios from 'axios'

const route = useRoute()
const router = useRouter()
const loading = ref(false)
const execution = ref({})

const executionId = route.params.id

const fetchExecutionDetail = async () => {
  loading.value = true
  try {
    const response = await axios.get(`/api/datafactory/executions/${executionId}`)
    
    if (response.data.code === 200) {
      execution.value = response.data.data
    }
  } catch (error) {
    console.error('获取执行详情失败', error)
  } finally {
    loading.value = false
  }
}

const getStatusType = (status) => {
  const types = {
    success: 'success',
    error: 'danger',
    waiting: 'warning',
    running: 'primary'
  }
  return types[status] || 'info'
}

const getNodeStatusType = (node) => {
  // 根据节点执行情况判断状态
  if (node.error) return 'danger'
  return 'success'
}

const goBack = () => {
  router.back()
}

onMounted(() => {
  fetchExecutionDetail()
})
</script>

<style scoped>
.execution-detail {
  padding: 20px;
}

.execution-info {
  margin: 20px 0;
}

.error-alert {
  margin: 20px 0;
}

.node-results-card {
  margin-top: 20px;
}

.node-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.node-name {
  font-weight: bold;
}

.node-data pre {
  background: #f5f5f5;
  padding: 10px;
  border-radius: 4px;
  overflow-x: auto;
  font-size: 12px;
}
</style>
```

---

### 9. 触发工作流执行

**接口描述**: 通过 Webhook 触发工作流执行。

#### 请求信息
- **HTTP方法**: `POST`
- **请求路径**: `/workflows/{workflow_id}/execute`
- **请求头**: `Content-Type: application/json`

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| workflow_id | string | 是 | 工作流ID |

#### 请求参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| webhook_path | string | 是 | Webhook 路径（工作流中配置的路径） |
| data | object | 否 | 传递给工作流的数据 |

#### 请求体示例

```json
{
  "webhook_path": "my-webhook-path",
  "data": {
    "user_id": 12345,
    "action": "process",
    "params": {
      "key1": "value1",
      "key2": "value2"
    }
  }
}
```

#### 响应数据

**成功响应**:
```json
{
  "code": 200,
  "message": "工作流触发成功",
  "data": {
    "success": true,
    "message": "工作流已通过 Webhook 触发",
    "workflow_id": "9w5VhCRlRrjFqDpX",
    "response": {
      "result": "OK"
    }
  }
}
```

**缺少 webhook_path 响应**:
```json
{
  "code": 400,
  "message": "请提供 Webhook 路径以触发工作流",
  "data": {
    "success": false,
    "message": "请提供 Webhook 路径以触发工作流",
    "workflow_id": "9w5VhCRlRrjFqDpX"
  }
}
```

#### Vue 代码示例

```vue
<template>
  <div class="trigger-workflow">
    <el-card>
      <template #header>
        <span>触发工作流执行</span>
      </template>
      
      <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
        <el-form-item label="Webhook路径" prop="webhook_path">
          <el-input v-model="form.webhook_path" placeholder="请输入Webhook路径" />
          <div class="form-tip">工作流中 Webhook 节点配置的路径</div>
        </el-form-item>
        
        <el-form-item label="传递数据">
          <el-input
            v-model="form.dataJson"
            type="textarea"
            :rows="8"
            placeholder="请输入JSON格式的数据（可选）"
          />
          <div class="form-tip">JSON格式，例如：{"key": "value"}</div>
        </el-form-item>
        
        <el-form-item>
          <el-button type="primary" @click="triggerWorkflow" :loading="loading">
            触发执行
          </el-button>
          <el-button @click="resetForm">重置</el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 执行结果 -->
    <el-card v-if="result" class="result-card">
      <template #header>
        <span>执行结果</span>
      </template>
      <el-result
        :icon="result.success ? 'success' : 'error'"
        :title="result.success ? '触发成功' : '触发失败'"
        :sub-title="result.message"
      >
        <template #extra>
          <pre v-if="result.response">{{ JSON.stringify(result.response, null, 2) }}</pre>
        </template>
      </el-result>
    </el-card>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { useRoute } from 'vue-router'
import axios from 'axios'
import { ElMessage } from 'element-plus'

const route = useRoute()
const formRef = ref()
const loading = ref(false)
const result = ref(null)

const workflowId = route.params.id

const form = reactive({
  webhook_path: '',
  dataJson: ''
})

const rules = {
  webhook_path: [
    { required: true, message: '请输入Webhook路径', trigger: 'blur' }
  ]
}

const triggerWorkflow = async () => {
  try {
    await formRef.value.validate()
  } catch {
    return
  }

  // 解析JSON数据
  let data = {}
  if (form.dataJson) {
    try {
      data = JSON.parse(form.dataJson)
    } catch (e) {
      ElMessage.error('数据格式错误，请输入有效的JSON')
      return
    }
  }

  loading.value = true
  result.value = null

  try {
    const response = await axios.post(`/api/datafactory/workflows/${workflowId}/execute`, {
      webhook_path: form.webhook_path,
      data: data
    })

    if (response.data.code === 200) {
      result.value = response.data.data
      ElMessage.success('工作流触发成功')
    } else {
      result.value = {
        success: false,
        message: response.data.message
      }
      ElMessage.error(response.data.message)
    }
  } catch (error) {
    result.value = {
      success: false,
      message: error.message || '触发失败'
    }
    ElMessage.error('触发工作流失败')
  } finally {
    loading.value = false
  }
}

const resetForm = () => {
  formRef.value?.resetFields()
  result.value = null
}
</script>

<style scoped>
.trigger-workflow {
  padding: 20px;
}

.form-tip {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}

.result-card {
  margin-top: 20px;
}

.result-card pre {
  background: #f5f5f5;
  padding: 15px;
  border-radius: 4px;
  overflow-x: auto;
  text-align: left;
}
</style>
```

---

### 10. 健康检查

**接口描述**: 检查 n8n 服务的连接状态。

#### 请求信息
- **HTTP方法**: `GET`
- **请求路径**: `/health`
- **请求头**: `Content-Type: application/json`

#### 响应数据

**连接正常**:
```json
{
  "code": 200,
  "message": "n8n 服务连接正常",
  "data": {
    "status": "healthy",
    "connected": true,
    "api_url": "https://n8n.citupro.com"
  }
}
```

**连接失败**:
```json
{
  "code": 503,
  "message": "n8n 服务连接失败: n8n API 认证失败，请检查 API Key 配置",
  "data": {
    "status": "unhealthy",
    "connected": false,
    "error": "n8n API 认证失败，请检查 API Key 配置",
    "api_url": "https://n8n.citupro.com"
  }
}
```

#### Vue 代码示例

```vue
<template>
  <div class="health-check">
    <el-card>
      <template #header>
        <div class="card-header">
          <span>n8n 服务状态</span>
          <el-button type="primary" size="small" @click="checkHealth" :loading="loading">
            <el-icon><Refresh /></el-icon>
            检查
          </el-button>
        </div>
      </template>
      
      <el-result
        :icon="health.connected ? 'success' : 'error'"
        :title="health.connected ? '服务正常' : '服务异常'"
      >
        <template #sub-title>
          <p>API地址: {{ health.api_url }}</p>
          <p v-if="health.error" class="error-text">错误: {{ health.error }}</p>
        </template>
      </el-result>
    </el-card>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { Refresh } from '@element-plus/icons-vue'
import axios from 'axios'

const loading = ref(false)
const health = ref({
  connected: false,
  api_url: ''
})

const checkHealth = async () => {
  loading.value = true
  try {
    const response = await axios.get('/api/datafactory/health')
    health.value = response.data.data
  } catch (error) {
    health.value = {
      connected: false,
      error: '无法连接到服务'
    }
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  checkHealth()
})
</script>

<style scoped>
.health-check {
  padding: 20px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.error-text {
  color: #F56C6C;
}
</style>
```

---

## API 服务封装

建议将 API 调用封装成独立的服务模块，便于统一管理和维护。

### api/datafactory.js

```javascript
import axios from 'axios'

const BASE_URL = '/api/datafactory'

export const datafactoryApi = {
  // 工作流相关
  getWorkflows(params) {
    return axios.get(`${BASE_URL}/workflows`, { params })
  },
  
  getWorkflow(workflowId) {
    return axios.get(`${BASE_URL}/workflows/${workflowId}`)
  },
  
  getWorkflowStatus(workflowId) {
    return axios.get(`${BASE_URL}/workflows/${workflowId}/status`)
  },
  
  activateWorkflow(workflowId) {
    return axios.post(`${BASE_URL}/workflows/${workflowId}/activate`)
  },
  
  deactivateWorkflow(workflowId) {
    return axios.post(`${BASE_URL}/workflows/${workflowId}/deactivate`)
  },
  
  // 执行记录相关
  getWorkflowExecutions(workflowId, params) {
    return axios.get(`${BASE_URL}/workflows/${workflowId}/executions`, { params })
  },
  
  getAllExecutions(params) {
    return axios.get(`${BASE_URL}/executions`, { params })
  },
  
  getExecution(executionId) {
    return axios.get(`${BASE_URL}/executions/${executionId}`)
  },
  
  // 触发执行
  triggerWorkflow(workflowId, data) {
    return axios.post(`${BASE_URL}/workflows/${workflowId}/execute`, data)
  },
  
  // 健康检查
  healthCheck() {
    return axios.get(`${BASE_URL}/health`)
  }
}
```

### 使用示例

```javascript
import { datafactoryApi } from '@/api/datafactory'

// 获取工作流列表
const response = await datafactoryApi.getWorkflows({
  page: 1,
  page_size: 20,
  active: 'true'
})

// 触发工作流
const result = await datafactoryApi.triggerWorkflow('workflow-id', {
  webhook_path: 'my-webhook',
  data: { key: 'value' }
})
```

---

## 路由配置示例

### router/datafactory.js

```javascript
export default [
  {
    path: '/datafactory',
    name: 'DataFactory',
    component: () => import('@/views/datafactory/index.vue'),
    meta: { title: '数据工厂' },
    children: [
      {
        path: '',
        name: 'WorkflowList',
        component: () => import('@/views/datafactory/WorkflowList.vue'),
        meta: { title: '工作流列表' }
      },
      {
        path: 'workflow/:id',
        name: 'WorkflowDetail',
        component: () => import('@/views/datafactory/WorkflowDetail.vue'),
        meta: { title: '工作流详情' }
      },
      {
        path: 'workflow/:id/status',
        name: 'WorkflowStatus',
        component: () => import('@/views/datafactory/WorkflowStatus.vue'),
        meta: { title: '工作流状态' }
      },
      {
        path: 'workflow/:id/executions',
        name: 'WorkflowExecutions',
        component: () => import('@/views/datafactory/ExecutionList.vue'),
        meta: { title: '执行记录' }
      },
      {
        path: 'workflow/:id/trigger',
        name: 'TriggerWorkflow',
        component: () => import('@/views/datafactory/TriggerWorkflow.vue'),
        meta: { title: '触发执行' }
      },
      {
        path: 'executions/:id',
        name: 'ExecutionDetail',
        component: () => import('@/views/datafactory/ExecutionDetail.vue'),
        meta: { title: '执行详情' }
      }
    ]
  }
]
```

---

## 注意事项

1. **认证配置**: 后端已配置 n8n API Key，前端无需额外处理认证
2. **跨域问题**: 后端已配置 CORS，支持跨域请求
3. **错误处理**: 建议在 axios 拦截器中统一处理错误响应
4. **状态刷新**: 工作流状态页面建议设置定时刷新（如30秒）
5. **Webhook路径**: 触发工作流时需要提供正确的 Webhook 路径，该路径在 n8n 工作流的 Webhook 节点中配置

---

## 更新日志

- **v1.0** (2025-12-24): 初始版本，包含工作流查询、状态监控、执行记录和触发执行功能

---

## 技术支持

如有问题请联系后端开发团队或查看详细的 API 文档。

**文档版本**: v1.0  
**最后更新**: 2025-12-24

