# 数据订单 API 前端开发指南

> **模块说明**: 数据订单 API 提供数据需求订单的创建、分析、审批、驳回、完成等全生命周期管理功能。当用户在数据服务列表中找不到所需数据时，可以发起数据订单，系统会通过 LLM 提取实体并检测业务领域图谱的连通性。
>
> **基础路径**: `/api/dataservice`
>
> **版本**: 2.1.0 (更新于 2026-01-09)

---

## 目录

- [功能概述](#功能概述)
- [业务流程](#业务流程)
- [通用说明](#通用说明)
  - [响应格式](#响应格式)
  - [错误码说明](#错误码说明)
  - [订单状态说明](#订单状态说明)
  - [Axios 配置](#axios-配置)
- [接口列表](#接口列表)
  1. [获取数据订单列表](#1-获取数据订单列表)
  2. [获取数据订单详情](#2-获取数据订单详情)
  3. [创建数据订单](#3-创建数据订单)
  4. [更新数据订单](#4-更新数据订单)
  5. [分析数据订单](#5-分析数据订单)
  6. [审批通过订单](#6-审批通过订单)
  7. [驳回订单](#7-驳回订单)
  8. [设置数据产品就绪](#8-设置数据产品就绪onboard)
  9. [完成订单](#9-完成订单)
  10. [删除订单](#10-删除订单)
- [API 模块封装](#api-模块封装)
- [完整页面示例](#完整页面示例)
- [常见问题](#常见问题)
- [更新日志](#更新日志)

---

## 功能概述

数据订单功能允许用户：

1. **提交数据需求**: 描述需要什么样的数据
2. **智能分析**: 系统通过 LLM 自动提取业务领域和数据字段
3. **连通性检测**: 在业务领域图谱中检测实体间的关联关系
4. **审批流程**: 支持人工审批、驳回和补充信息
5. **自动资源生成**: 审批通过后自动创建 BusinessDomain 和 DataFlow 节点
6. **数据产品关联**: 数据流程完成后关联生成的数据产品

---

## 业务流程

### 状态流转图

```
                              ┌─────────────────┐
                              │    新增订单      │
                              │    (pending)    │
                              └────────┬────────┘
                                       │ 发起分析
                                       ▼
                              ┌─────────────────┐
                              │     分析中      │
                              │   (analyzing)   │
                              └────────┬────────┘
                                       │
                   ┌───────────────────┼───────────────────┐
                   │ 分析通过          │                   │ 分析有问题
                   ▼                   │                   ▼
          ┌─────────────────┐          │          ┌─────────────────┐
          │     待审批       │          │          │   待人工处理     │
          │(pending_approval)│          │          │ (manual_review) │
          └────────┬────────┘          │          └────────┬────────┘
                   │                   │                   │
       ┌───────────┴───────────┐       │                   │ 修改后
       │ 审批通过              │审批驳回│                   │ 重新分析
       ▼                       ▼       │                   │
┌─────────────────┐   ┌─────────────────┐                  │
│     加工中       │   │     已驳回      │                  │
│  (processing)   │   │   (rejected)    │◄─────────────────┘
└────────┬────────┘   └─────────────────┘
         │ 数据工厂回调
         ▼
┌─────────────────┐
│  数据产品就绪    │
│    (onboard)    │
└────────┬────────┘
         │ 标记完成
         ▼
┌─────────────────┐
│     已完成       │
│   (completed)   │
└─────────────────┘
```

### 典型流程

| 步骤 | 操作 | 状态变化 | 说明 |
|------|------|----------|------|
| 1 | 用户创建订单 | → `pending` | 订单初始状态 |
| 2 | 发起分析 | `pending` → `analyzing` | 调用 LLM 提取实体 |
| 3a | 分析通过 | `analyzing` → `pending_approval` | 等待人工审批确认 |
| 3b | 分析有问题 | `analyzing` → `manual_review` | 需要人工修改 |
| 4 | 人工修改订单 | 保持 `manual_review` | 修改描述或提取结果 |
| 5 | 重新分析 | `manual_review` → `analyzing` | 再次进行分析 |
| 6a | 审批通过 | `pending_approval` → `processing` | 自动生成资源（见下方详细说明） |
| 6b | 审批驳回 | `pending_approval` → `rejected` | 订单被驳回 |
| 7 | 数据工厂回调 | `processing` → `onboard` | 数据产品生产完成（n8n 工作流自动调用） |
| 8 | 标记完成 | `onboard` → `completed` | 订单最终完成 |

### 审批通过后的资源生成流程（步骤 6a 详解）

当订单审批通过时，系统会自动执行以下操作：

```
┌─────────────────────────────────────────────────────────────────┐
│                    审批通过 (approve_order)                      │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. 创建目标 BusinessDomain 节点                                  │
│    - 从订单描述中提取输出域信息                                    │
│    - 在 Neo4j 中创建 BusinessDomain 节点                         │
│    - 建立与数据源的 COME_FROM 关系                                │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. 创建 DataFlow 节点                                            │
│    - 生成 DataFlow 英文名: DF_{order_no}                         │
│    - 在 Neo4j 中创建 DataFlow 节点                               │
│    - 建立 INPUT 关系 (源 BusinessDomain → DataFlow)              │
│    - 建立 OUTPUT 关系 (DataFlow → 目标 BusinessDomain)           │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. 注册数据产品 (NEW)                                            │
│    - 在 data_products 表中创建数据产品记录                        │
│    - 关联 DataFlow ID 和名称                                     │
│    - 更新订单的 result_product_id                                │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. 创建任务记录                                                  │
│    - 在 task_list 表中创建 pending 任务                          │
│    - 任务描述包含：订单ID、DataFlow ID、产品ID、DDL、处理逻辑      │
│    - 供 Cursor Agent 或手动执行                                  │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. 生成 n8n 工作流 (NEW)                                         │
│    - 生成工作流 JSON 文件到 datafactory/workflows/               │
│    - 工作流包含：定时触发 → SSH执行脚本 → 检查结果                 │
│    - 成功时自动调用 onboard 接口更新订单状态                       │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. 更新订单状态为 processing                                     │
└─────────────────────────────────────────────────────────────────┘
```

### n8n 工作流自动回调流程（步骤 7 详解）

生成的 n8n 工作流在脚本执行成功后会自动调用 onboard 接口：

```
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Schedule   │────▶│   SSH       │────▶│   Check     │
│  Trigger    │     │   Execute   │     │   Result    │
└─────────────┘     │   Script    │     └──────┬──────┘
                    └─────────────┘            │
                                    ┌──────────┴──────────┐
                                    │                     │
                              成功 (code=0)          失败 (code≠0)
                                    │                     │
                                    ▼                     ▼
                           ┌─────────────┐       ┌─────────────┐
                           │   Update    │       │   Error     │
                           │   Order     │       │   Response  │
                           │   Status    │       └─────────────┘
                           └──────┬──────┘
                                  │ POST /api/dataservice/orders/{id}/onboard
                                  │ {
                                  │   "product_id": xxx,
                                  │   "dataflow_id": xxx,
                                  │   "processed_by": "n8n-workflow"
                                  │ }
                                  ▼
                           ┌─────────────┐
                           │   Success   │
                           │   Response  │
                           └─────────────┘
```

**关键说明**：

1. **数据产品注册时机**：在审批通过时立即注册，而不是等到脚本执行完成
2. **订单状态更新**：由 n8n 工作流在脚本执行成功后自动调用 onboard 接口
3. **任务描述包含关联信息**：便于后续追踪和调试

---

## 通用说明

### 响应格式

所有接口返回统一的 JSON 格式：

```json
{
  "code": 200,
  "message": "操作成功",
  "data": { ... }
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `code` | number | 状态码，200 表示成功，其他表示失败 |
| `message` | string | 操作结果描述信息 |
| `data` | object \| array \| null | 返回的数据内容 |

### 错误码说明

| 状态码 | 说明 | 常见场景 |
|--------|------|----------|
| 200 | 成功 | 操作成功完成 |
| 400 | 请求参数错误 | 缺少必填字段、状态不允许操作、驳回原因为空 |
| 404 | 资源不存在 | 数据订单 ID 不存在 |
| 500 | 服务器内部错误 | 数据库连接失败、LLM 调用失败、图谱查询异常 |

### 订单状态说明

| 状态值 | 中文标签 | 说明 | 可执行操作 |
|--------|----------|------|------------|
| `pending` | 待处理 | 订单刚创建，等待分析 | 分析、更新、删除 |
| `analyzing` | 分析中 | 正在进行 LLM 提取和图谱分析 | - |
| `pending_approval` | 待审批 | 分析通过，等待人工审批 | 审批通过、驳回 |
| `processing` | 加工中 | 审批通过，正在生成数据流/数据产品 | - |
| `onboard` | 数据产品就绪 | 数据流程完成，产品可用 | 完成 |
| `completed` | 已完成 | 订单处理完成 | 删除 |
| `rejected` | 已驳回 | 订单被驳回 | 删除 |
| `need_supplement` | 待补充 | 需要用户补充信息 | 更新、分析 |
| `manual_review` | 待人工处理 | 需要人工修改 | 更新、分析、审批、驳回 |

### Axios 配置

建议的 Axios 全局配置：

```javascript
// src/utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5050',
  timeout: 60000, // 分析接口可能需要较长时间
  headers: {
    'Content-Type': 'application/json'
  }
})

// 响应拦截器
request.interceptors.response.use(
  response => {
    const res = response.data
    if (res.code !== 200) {
      ElMessage.error(res.message || '请求失败')
      return Promise.reject(new Error(res.message || 'Error'))
    }
    return res
  },
  error => {
    ElMessage.error(error.message || '网络错误')
    return Promise.reject(error)
  }
)

export default request
```

---

## 接口列表

---

### 1. 获取数据订单列表

分页获取数据订单列表，支持搜索和状态过滤。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `GET /api/dataservice/orderlist` |
| **Method** | GET |
| **Content-Type** | - |

#### 请求参数 (Query String)

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `page` | integer | 否 | 1 | 页码 |
| `page_size` | integer | 否 | 20 | 每页数量 |
| `search` | string | 否 | "" | 搜索关键词（匹配订单编号、标题、描述） |
| `status` | string | 否 | - | 状态过滤，见[订单状态说明](#订单状态说明) |

#### 响应数据

```json
{
  "code": 200,
  "message": "获取数据订单列表成功",
  "data": {
    "list": [
      {
        "id": 1,
        "order_no": "DO202601090001",
        "title": "员工与部门关联数据",
        "description": "需要获取员工信息和所属部门的关联数据",
        "extracted_domains": ["员工", "部门"],
        "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称"],
        "extraction_purpose": "用于人力资源分析报表",
        "graph_analysis": {
          "matched_domains": [...],
          "matched_fields": [...],
          "connection_analysis": {...}
        },
        "can_connect": true,
        "connection_path": {...},
        "status": "pending_approval",
        "status_label": "待审批",
        "reject_reason": null,
        "result_product_id": null,
        "result_dataflow_id": null,
        "created_by": "张三",
        "created_at": "2026-01-09T09:00:00",
        "updated_at": "2026-01-09T09:15:00",
        "processed_by": null,
        "processed_at": null
      }
    ],
    "pagination": {
      "page": 1,
      "page_size": 20,
      "total": 45,
      "total_pages": 3
    }
  }
}
```

#### 数据订单字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | integer | 订单唯一 ID |
| `order_no` | string | 订单编号，格式: DO + YYYYMMDD + 4位序列号 |
| `title` | string | 订单标题 |
| `description` | string | 需求描述 |
| `extracted_domains` | array \| null | LLM 提取的业务领域列表 |
| `extracted_fields` | array \| null | LLM 提取的数据字段列表 |
| `extraction_purpose` | string \| null | LLM 提取的数据用途 |
| `graph_analysis` | object \| null | 图谱连通性分析结果 |
| `can_connect` | boolean \| null | 是否可在图谱中连通 |
| `connection_path` | object \| null | 连通路径详情 |
| `status` | string | 订单状态 |
| `status_label` | string | 状态中文标签 |
| `reject_reason` | string \| null | 驳回原因（仅状态为 rejected 时有值） |
| `result_product_id` | integer \| null | 生成的数据产品 ID |
| `result_dataflow_id` | integer \| null | 生成的数据流 ID |
| `created_by` | string | 创建人 |
| `created_at` | string | 创建时间（ISO 8601 格式） |
| `updated_at` | string | 更新时间 |
| `processed_by` | string \| null | 处理人 |
| `processed_at` | string \| null | 处理时间 |

#### Vue 接入示例

```javascript
import request from '@/utils/request'

// 获取订单列表
const fetchOrders = async (params) => {
  const res = await request.get('/api/dataservice/orderlist', { params })
  return res.data
}

// 使用示例
const { list, pagination } = await fetchOrders({
  page: 1,
  page_size: 20,
  status: 'pending_approval'
})
```

---

### 2. 获取数据订单详情

根据 ID 获取单个数据订单的详细信息。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `GET /api/dataservice/orders/{order_id}/detail` |
| **Method** | GET |
| **Content-Type** | - |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 响应数据

```json
{
  "code": 200,
  "message": "获取数据订单详情成功",
  "data": {
    "id": 1,
    "order_no": "DO202601090001",
    "title": "员工与部门关联数据",
    "description": "需要获取员工信息和所属部门的关联数据...",
    "extracted_domains": ["员工", "部门"],
    "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称"],
    "extraction_purpose": "用于人力资源分析报表",
    "graph_analysis": {...},
    "can_connect": true,
    "connection_path": {...},
    "status": "pending_approval",
    "status_label": "待审批",
    ...
  }
}
```

#### Vue 接入示例

```javascript
const fetchOrderDetail = async (orderId) => {
  const res = await request.get(`/api/dataservice/orders/${orderId}/detail`)
  return res.data
}
```

---

### 3. 创建数据订单

创建新的数据需求订单。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `POST /api/dataservice/neworder` |
| **Method** | POST |
| **Content-Type** | application/json |

#### 请求体参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `title` | string | 是 | - | 订单标题，最大 200 字符 |
| `description` | string | 是 | - | 需求描述，详细说明需要什么数据 |
| `created_by` | string | 否 | "user" | 创建人标识 |

#### 请求示例

```json
{
  "title": "员工与部门关联数据",
  "description": "需要获取员工信息和所属部门的关联数据，包括：\n1. 员工基本信息（ID、姓名、入职日期）\n2. 部门信息（部门ID、部门名称）\n3. 部门层级关系\n\n用途：生成人力资源分析报表",
  "created_by": "张三"
}
```

#### 响应数据

```json
{
  "code": 200,
  "message": "创建数据订单成功",
  "data": {
    "id": 1,
    "order_no": "DO202601090001",
    "title": "员工与部门关联数据",
    "description": "...",
    "status": "pending",
    "status_label": "待处理",
    ...
  }
}
```

#### Vue 接入示例

```javascript
const createOrder = async (data) => {
  const res = await request.post('/api/dataservice/neworder', data)
  return res.data
}

// 使用示例
const newOrder = await createOrder({
  title: '员工与部门关联数据',
  description: '需要获取员工信息...'
})
```

---

### 4. 更新数据订单

更新数据订单信息，支持修改标题、描述和 LLM 提取结果。

> **注意**: 只允许在 `pending`、`manual_review`、`need_supplement` 状态下修改订单。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `PUT /api/dataservice/orders/{order_id}/update` |
| **Method** | PUT |
| **Content-Type** | application/json |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 请求体参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `title` | string | 否 | 订单标题 |
| `description` | string | 否 | 需求描述 |
| `extracted_domains` | array | 否 | 提取的业务领域列表 |
| `extracted_fields` | array | 否 | 提取的数据字段列表 |
| `extraction_purpose` | string | 否 | 数据用途 |

#### 请求示例

```json
{
  "title": "员工与部门关联数据（更新）",
  "description": "需要获取员工信息和所属部门的关联数据，补充：需要包含员工的岗位信息",
  "extracted_domains": ["员工", "部门", "岗位"],
  "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称", "岗位名称"]
}
```

#### 响应数据

```json
{
  "code": 200,
  "message": "更新数据订单成功",
  "data": {
    "id": 1,
    "order_no": "DO202601090001",
    "title": "员工与部门关联数据（更新）",
    "description": "...",
    "extracted_domains": ["员工", "部门", "岗位"],
    "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称", "岗位名称"],
    "status": "manual_review",
    "status_label": "待人工处理",
    ...
  }
}
```

#### 错误响应

**状态不允许修改 (400):**
```json
{
  "code": 400,
  "message": "订单状态 processing 不允许修改，只有 ['pending', 'manual_review', 'need_supplement'] 状态可以修改",
  "data": null
}
```

#### Vue 接入示例

```vue
<template>
  <el-dialog v-model="dialogVisible" title="编辑订单" width="600px">
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
      <el-form-item label="订单标题" prop="title">
        <el-input v-model="form.title" />
      </el-form-item>
      <el-form-item label="需求描述" prop="description">
        <el-input v-model="form.description" type="textarea" :rows="4" />
      </el-form-item>
      <el-form-item label="业务领域">
        <el-select v-model="form.extracted_domains" multiple allow-create filterable>
          <el-option
            v-for="domain in domainOptions"
            :key="domain"
            :label="domain"
            :value="domain"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="数据字段">
        <el-select v-model="form.extracted_fields" multiple allow-create filterable>
          <el-option
            v-for="field in fieldOptions"
            :key="field"
            :label="field"
            :value="field"
          />
        </el-select>
      </el-form-item>
      <el-form-item label="数据用途">
        <el-input v-model="form.extraction_purpose" />
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="primary" @click="handleSubmit" :loading="submitting">
        保存修改
      </el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'

const emit = defineEmits(['success'])
const dialogVisible = ref(false)
const submitting = ref(false)
const currentOrderId = ref(null)
const formRef = ref(null)

const form = reactive({
  title: '',
  description: '',
  extracted_domains: [],
  extracted_fields: [],
  extraction_purpose: ''
})

const open = (order) => {
  currentOrderId.value = order.id
  form.title = order.title
  form.description = order.description
  form.extracted_domains = order.extracted_domains || []
  form.extracted_fields = order.extracted_fields || []
  form.extraction_purpose = order.extraction_purpose || ''
  dialogVisible.value = true
}

const handleSubmit = async () => {
  submitting.value = true
  try {
    const data = {}
    if (form.title) data.title = form.title
    if (form.description) data.description = form.description
    if (form.extracted_domains.length) data.extracted_domains = form.extracted_domains
    if (form.extracted_fields.length) data.extracted_fields = form.extracted_fields
    if (form.extraction_purpose) data.extraction_purpose = form.extraction_purpose

    await request.put(`/api/dataservice/orders/${currentOrderId.value}/update`, data)
    ElMessage.success('更新成功')
    dialogVisible.value = false
    emit('success')
  } catch (error) {
    ElMessage.error(error.message || '更新失败')
  } finally {
    submitting.value = false
  }
}

defineExpose({ open })
</script>
```

---

### 5. 分析数据订单

触发 LLM 实体提取和业务领域图谱连通性分析。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `POST /api/dataservice/orders/{order_id}/analyze` |
| **Method** | POST |
| **Content-Type** | - |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 请求体

无需请求体。

#### 响应数据

分析成功后返回更新后的订单数据：

```json
{
  "code": 200,
  "message": "数据订单分析完成",
  "data": {
    "id": 1,
    "order_no": "DO202601090001",
    "title": "员工与部门关联数据",
    "extracted_domains": ["员工", "部门"],
    "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称"],
    "extraction_purpose": "用于人力资源分析报表",
    "graph_analysis": {
      "matched_domains": [
        { "id": 101, "name_zh": "员工信息", "name_en": "employee" },
        { "id": 102, "name_zh": "部门信息", "name_en": "department" }
      ],
      "matched_fields": [...],
      "connection_analysis": {
        "can_connect": true,
        "common_fields": [...]
      }
    },
    "can_connect": true,
    "connection_path": {...},
    "status": "pending_approval",
    "status_label": "待审批"
  }
}
```

**分析结果说明：**

| 结果 | 说明 | 后续状态 |
|------|------|----------|
| `can_connect: true` | 所有实体都能在图谱中连通 | `pending_approval` (待人工审批) |
| `can_connect: false` | 存在无法连通的实体 | `manual_review` (待人工处理) |

#### Vue 接入示例

```javascript
const analyzeOrder = async (orderId) => {
  const loadingInstance = ElLoading.service({
    text: '正在分析订单，可能需要几秒钟...',
    background: 'rgba(0, 0, 0, 0.7)'
  })

  try {
    const res = await request.post(`/api/dataservice/orders/${orderId}/analyze`)
    loadingInstance.close()

    if (res.data.can_connect) {
      ElMessage.success('分析完成，实体可连通，请进行审批确认！')
    } else {
      ElMessage.warning('分析完成，部分实体无法连通，请修改后重新分析')
    }

    return res.data
  } catch (error) {
    loadingInstance.close()
    ElMessage.error(error.message || '分析失败')
    throw error
  }
}
```

---

### 6. 审批通过订单

审批通过数据订单，系统将自动生成 BusinessDomain 和 DataFlow 资源。

> **注意**: 只允许从 `pending_approval` 或 `manual_review` 状态审批。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `POST /api/dataservice/orders/{order_id}/approve` |
| **Method** | POST |
| **Content-Type** | application/json |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 请求体参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `processed_by` | string | 否 | "admin" | 处理人标识 |

#### 请求示例

```json
{
  "processed_by": "管理员A"
}
```

#### 响应数据

```json
{
  "code": 200,
  "message": "数据订单审批通过，资源已生成",
  "data": {
    "order": {
      "id": 1,
      "order_no": "DO202601090001",
      "status": "processing",
      "status_label": "加工中",
      "result_dataflow_id": 156,
      "processed_by": "管理员A",
      "processed_at": "2026-01-09T10:00:00",
      ...
    },
    "generated_resources": {
      "target_business_domain_id": 201,
      "dataflow_id": 156,
      "input_domain_ids": [101, 102]
    }
  }
}
```

#### 生成资源说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `target_business_domain_id` | integer | 新创建的目标 BusinessDomain 节点 ID |
| `dataflow_id` | integer | 新创建的 DataFlow 节点 ID |
| `input_domain_ids` | array | 输入的 BusinessDomain 节点 ID 列表 |

#### 错误响应

**状态不允许审批 (400):**
```json
{
  "code": 400,
  "message": "订单状态 pending 不允许审批，只有 ['pending_approval', 'manual_review'] 状态可以审批",
  "data": null
}
```

#### Vue 接入示例

```javascript
const approveOrder = async (orderId, processedBy = 'admin') => {
  try {
    await ElMessageBox.confirm(
      '确定要审批通过该订单吗？通过后将自动生成数据流程资源。',
      '审批确认',
      { type: 'warning' }
    )

    const res = await request.post(`/api/dataservice/orders/${orderId}/approve`, {
      processed_by: processedBy
    })

    ElMessage.success('审批通过，资源已生成')
    
    // 可以显示生成的资源信息
    console.log('生成的 DataFlow ID:', res.data.generated_resources.dataflow_id)
    
    return res.data
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '审批失败')
    }
  }
}
```

---

### 7. 驳回订单

驳回数据订单，需要提供驳回原因。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `POST /api/dataservice/orders/{order_id}/reject` |
| **Method** | POST |
| **Content-Type** | application/json |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 请求体参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `reason` | string | 是 | - | 驳回原因 |
| `processed_by` | string | 否 | "admin" | 处理人标识 |

#### 请求示例

```json
{
  "reason": "需求描述不够清晰，请补充具体需要的数据字段和业务场景",
  "processed_by": "管理员A"
}
```

#### 响应数据

```json
{
  "code": 200,
  "message": "数据订单已驳回",
  "data": {
    "id": 1,
    "order_no": "DO202601090001",
    "status": "rejected",
    "status_label": "已驳回",
    "reject_reason": "需求描述不够清晰，请补充具体需要的数据字段和业务场景",
    "processed_by": "管理员A",
    "processed_at": "2026-01-09T10:00:00",
    ...
  }
}
```

#### Vue 接入示例

```vue
<template>
  <el-dialog v-model="dialogVisible" title="驳回订单" width="500px">
    <el-form :model="form" :rules="rules" ref="formRef">
      <el-form-item label="驳回原因" prop="reason">
        <el-input
          v-model="form.reason"
          type="textarea"
          :rows="4"
          placeholder="请输入驳回原因，将通知订单创建人"
        />
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="dialogVisible = false">取消</el-button>
      <el-button type="danger" @click="submitReject" :loading="rejecting">
        确认驳回
      </el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'

const emit = defineEmits(['success'])
const dialogVisible = ref(false)
const rejecting = ref(false)
const currentOrderId = ref(null)
const formRef = ref(null)

const form = reactive({
  reason: ''
})

const rules = {
  reason: [
    { required: true, message: '请输入驳回原因', trigger: 'blur' },
    { min: 5, message: '驳回原因至少5个字符', trigger: 'blur' }
  ]
}

const open = (order) => {
  currentOrderId.value = order.id
  form.reason = ''
  dialogVisible.value = true
}

const submitReject = async () => {
  const valid = await formRef.value.validate()
  if (!valid) return

  rejecting.value = true
  try {
    await request.post(`/api/dataservice/orders/${currentOrderId.value}/reject`, {
      reason: form.reason
    })

    ElMessage.success('订单已驳回')
    dialogVisible.value = false
    emit('success')
  } catch (error) {
    ElMessage.error(error.message || '驳回失败')
  } finally {
    rejecting.value = false
  }
}

defineExpose({ open })
</script>
```

---

### 8. 设置数据产品就绪（onboard）

数据工厂回调接口：当数据流程执行完成后，调用此接口将订单状态更新为"数据产品就绪"。

> **注意**: 只允许从 `processing` 状态转换为 `onboard` 状态。此接口通常由数据工厂（n8n 工作流）自动调用。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `POST /api/dataservice/orders/{order_id}/onboard` |
| **Method** | POST |
| **Content-Type** | application/json |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 请求体参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `product_id` | integer | 否 | null | 生成的数据产品 ID |
| `dataflow_id` | integer | 否 | null | 数据流 ID |
| `processed_by` | string | 否 | "n8n-workflow" | 处理人标识 |

#### 请求示例

```json
{
  "product_id": 15,
  "dataflow_id": 156,
  "processed_by": "n8n-workflow-001"
}
```

#### 响应数据

```json
{
  "code": 200,
  "message": "数据订单已设置为数据产品就绪状态",
  "data": {
    "id": 1,
    "order_no": "DO202601090001",
    "status": "onboard",
    "status_label": "数据产品就绪",
    "result_product_id": 15,
    "result_dataflow_id": 156,
    "processed_by": "n8n-workflow-001",
    "processed_at": "2026-01-09T14:30:00",
    ...
  }
}
```

#### 错误响应

**状态不允许操作 (400):**
```json
{
  "code": 400,
  "message": "订单状态 pending_approval 不允许设置为 onboard，只有 processing 状态可以转换",
  "data": null
}
```

#### Vue 接入示例

```javascript
// 此接口通常由后端/数据工厂调用，前端一般不直接调用
// 如需手动触发，可使用以下代码：
const setOrderOnboard = async (orderId, productId, dataflowId) => {
  const res = await request.post(`/api/dataservice/orders/${orderId}/onboard`, {
    product_id: productId,
    dataflow_id: dataflowId,
    processed_by: 'manual'
  })
  return res.data
}
```

---

### 9. 完成订单

将订单标记为最终完成状态。

> **注意**: 只允许从 `onboard`（数据产品就绪）状态标记完成。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `POST /api/dataservice/orders/{order_id}/complete` |
| **Method** | POST |
| **Content-Type** | application/json |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 请求体参数

| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| `processed_by` | string | 否 | "user" | 处理人标识 |

#### 请求示例

```json
{
  "processed_by": "张三"
}
```

#### 响应数据

```json
{
  "code": 200,
  "message": "数据订单已完成",
  "data": {
    "id": 1,
    "order_no": "DO202601090001",
    "status": "completed",
    "status_label": "已完成",
    "result_product_id": 15,
    "result_dataflow_id": 156,
    "processed_by": "张三",
    "processed_at": "2026-01-09T15:00:00",
    ...
  }
}
```

#### 错误响应

**状态不允许完成 (400):**
```json
{
  "code": 400,
  "message": "订单状态 processing 不允许标记完成，只有 onboard 状态可以标记完成",
  "data": null
}
```

#### Vue 接入示例

```javascript
const completeOrder = async (orderId) => {
  try {
    await ElMessageBox.confirm(
      '确定要标记该订单为完成吗？',
      '完成确认',
      { type: 'info' }
    )

    const res = await request.post(`/api/dataservice/orders/${orderId}/complete`, {
      processed_by: currentUser.value.name
    })

    ElMessage.success('订单已完成')
    return res.data
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '操作失败')
    }
  }
}
```

---

### 10. 删除订单

删除数据订单记录（软删除）。

#### 请求信息

| 项目 | 说明 |
|------|------|
| **URL** | `PUT /api/dataservice/orders/{order_id}/delete` |
| **Method** | PUT |
| **Content-Type** | - |

#### 路径参数

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `order_id` | integer | 是 | 数据订单 ID |

#### 响应数据

```json
{
  "code": 200,
  "message": "删除数据订单成功",
  "data": {}
}
```

#### Vue 接入示例

```javascript
const deleteOrder = async (orderId) => {
  try {
    await ElMessageBox.confirm(
      '确定要删除该订单吗？此操作不可恢复。',
      '删除确认',
      { type: 'warning', confirmButtonClass: 'el-button--danger' }
    )

    await request.put(`/api/dataservice/orders/${orderId}/delete`)
    ElMessage.success('删除成功')
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '删除失败')
    }
  }
}
```

---

## API 模块封装

建议将所有数据订单 API 封装到独立模块：

```javascript
// src/api/dataOrder.js
import request from '@/utils/request'

const BASE_URL = '/api/dataservice'

export const dataOrderApi = {
  /**
   * 获取数据订单列表
   */
  getOrders(params) {
    return request.get(`${BASE_URL}/orderlist`, { params })
  },

  /**
   * 获取数据订单详情
   */
  getOrderDetail(orderId) {
    return request.get(`${BASE_URL}/orders/${orderId}/detail`)
  },

  /**
   * 创建数据订单
   */
  createOrder(data) {
    return request.post(`${BASE_URL}/neworder`, data)
  },

  /**
   * 更新数据订单
   */
  updateOrder(orderId, data) {
    return request.put(`${BASE_URL}/orders/${orderId}/update`, data)
  },

  /**
   * 分析数据订单
   */
  analyzeOrder(orderId) {
    return request.post(`${BASE_URL}/orders/${orderId}/analyze`)
  },

  /**
   * 审批通过订单
   */
  approveOrder(orderId, processedBy = 'admin') {
    return request.post(`${BASE_URL}/orders/${orderId}/approve`, {
      processed_by: processedBy
    })
  },

  /**
   * 驳回订单
   */
  rejectOrder(orderId, reason, processedBy = 'admin') {
    return request.post(`${BASE_URL}/orders/${orderId}/reject`, {
      reason,
      processed_by: processedBy
    })
  },

  /**
   * 设置数据产品就绪状态（数据工厂回调）
   */
  setOrderOnboard(orderId, options = {}) {
    return request.post(`${BASE_URL}/orders/${orderId}/onboard`, {
      product_id: options.productId,
      dataflow_id: options.dataflowId,
      processed_by: options.processedBy || 'n8n-workflow'
    })
  },

  /**
   * 完成订单
   */
  completeOrder(orderId, processedBy = 'user') {
    return request.post(`${BASE_URL}/orders/${orderId}/complete`, {
      processed_by: processedBy
    })
  },

  /**
   * 删除订单
   */
  deleteOrder(orderId) {
    return request.put(`${BASE_URL}/orders/${orderId}/delete`)
  }
}

export default dataOrderApi
```

---

## 完整页面示例

以下是一个完整的数据订单管理页面示例，包含完整的状态流转操作：

```vue
<!-- src/views/dataservice/DataOrderManage.vue -->
<template>
  <div class="data-order-manage">
    <!-- 页面标题 -->
    <div class="page-header">
      <h2>数据订单管理</h2>
      <p class="page-description">
        当您在数据服务列表中找不到所需数据时，可以创建数据订单提交您的数据需求。
      </p>
    </div>

    <!-- 搜索栏 -->
    <el-card class="search-card">
      <el-form :inline="true" :model="searchParams">
        <el-form-item label="搜索">
          <el-input
            v-model="searchParams.search"
            placeholder="订单编号/标题/描述"
            clearable
            @keyup.enter="fetchOrders"
          />
        </el-form-item>
        <el-form-item label="状态">
          <el-select v-model="searchParams.status" placeholder="全部" clearable>
            <el-option
              v-for="(label, value) in statusOptions"
              :key="value"
              :label="label"
              :value="value"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="fetchOrders">
            <el-icon><Search /></el-icon> 查询
          </el-button>
          <el-button @click="resetSearch">重置</el-button>
          <el-button type="success" @click="createDialogRef?.open()">
            <el-icon><Plus /></el-icon> 新建订单
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>

    <!-- 数据表格 -->
    <el-card class="table-card">
      <el-table :data="orderList" v-loading="loading" border stripe>
        <el-table-column prop="order_no" label="订单编号" width="160" fixed />
        <el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip />
        <el-table-column prop="status" label="状态" width="130" align="center">
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)" effect="light">
              {{ row.status_label }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="can_connect" label="可连通" width="90" align="center">
          <template #default="{ row }">
            <el-icon v-if="row.can_connect === true" color="#67C23A" :size="18">
              <CircleCheck />
            </el-icon>
            <el-icon v-else-if="row.can_connect === false" color="#F56C6C" :size="18">
              <CircleClose />
            </el-icon>
            <span v-else class="text-gray">-</span>
          </template>
        </el-table-column>
        <el-table-column prop="created_by" label="创建人" width="100" />
        <el-table-column prop="created_at" label="创建时间" width="170">
          <template #default="{ row }">
            {{ formatDate(row.created_at) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="300" fixed="right">
          <template #default="{ row }">
            <el-button size="small" @click="showDetail(row)">详情</el-button>
            
            <!-- 待处理/待人工处理/待补充：可编辑和分析 -->
            <el-button
              v-if="canEdit(row.status)"
              size="small"
              @click="editDialogRef?.open(row)"
            >
              编辑
            </el-button>
            <el-button
              v-if="canAnalyze(row.status)"
              size="small"
              type="primary"
              @click="handleAnalyze(row)"
            >
              分析
            </el-button>
            
            <!-- 待审批/待人工处理：可审批和驳回 -->
            <el-button
              v-if="canApprove(row.status)"
              size="small"
              type="success"
              @click="handleApprove(row)"
            >
              审批
            </el-button>
            <el-button
              v-if="canReject(row.status)"
              size="small"
              type="warning"
              @click="rejectDialogRef?.open(row)"
            >
              驳回
            </el-button>
            
            <!-- 数据产品就绪：可完成 -->
            <el-button
              v-if="row.status === 'onboard'"
              size="small"
              type="success"
              @click="handleComplete(row)"
            >
              完成
            </el-button>
            
            <!-- 可删除状态 -->
            <el-button
              v-if="canDelete(row.status)"
              size="small"
              type="danger"
              @click="handleDelete(row)"
            >
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页 -->
      <div class="pagination-wrapper">
        <el-pagination
          v-model:current-page="searchParams.page"
          v-model:page-size="searchParams.page_size"
          :total="pagination.total"
          :page-sizes="[10, 20, 50, 100]"
          layout="total, sizes, prev, pager, next, jumper"
          @current-change="fetchOrders"
          @size-change="fetchOrders"
        />
      </div>
    </el-card>

    <!-- 子组件 -->
    <CreateOrderDialog ref="createDialogRef" @success="fetchOrders" />
    <EditOrderDialog ref="editDialogRef" @success="fetchOrders" />
    <RejectOrderDialog ref="rejectDialogRef" @success="fetchOrders" />
    <OrderDetailDrawer ref="detailDrawerRef" @refresh="fetchOrders" />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus'
import { Search, Plus, CircleCheck, CircleClose } from '@element-plus/icons-vue'
import dayjs from 'dayjs'
import { dataOrderApi } from '@/api/dataOrder'

// 子组件引用
const createDialogRef = ref(null)
const editDialogRef = ref(null)
const rejectDialogRef = ref(null)
const detailDrawerRef = ref(null)

// 状态
const loading = ref(false)
const orderList = ref([])
const pagination = ref({ page: 1, page_size: 20, total: 0, total_pages: 0 })

// 搜索参数
const searchParams = reactive({
  page: 1,
  page_size: 20,
  search: '',
  status: ''
})

// 状态选项
const statusOptions = {
  pending: '待处理',
  analyzing: '分析中',
  pending_approval: '待审批',
  processing: '加工中',
  onboard: '数据产品就绪',
  completed: '已完成',
  rejected: '已驳回',
  need_supplement: '待补充',
  manual_review: '待人工处理'
}

// 获取订单列表
const fetchOrders = async () => {
  loading.value = true
  try {
    const params = { ...searchParams }
    if (!params.status) delete params.status
    if (!params.search) delete params.search

    const res = await dataOrderApi.getOrders(params)
    orderList.value = res.data.list
    pagination.value = res.data.pagination
  } finally {
    loading.value = false
  }
}

// 重置搜索
const resetSearch = () => {
  searchParams.page = 1
  searchParams.search = ''
  searchParams.status = ''
  fetchOrders()
}

// 状态判断函数
const canEdit = (status) => ['pending', 'manual_review', 'need_supplement'].includes(status)
const canAnalyze = (status) => ['pending', 'manual_review', 'need_supplement'].includes(status)
const canApprove = (status) => ['pending_approval', 'manual_review'].includes(status)
const canReject = (status) => ['pending_approval', 'manual_review'].includes(status)
const canDelete = (status) => ['pending', 'completed', 'rejected'].includes(status)

// 获取状态标签类型
const getStatusType = (status) => {
  const types = {
    pending: 'info',
    analyzing: 'warning',
    pending_approval: '',
    processing: 'primary',
    onboard: 'success',
    completed: 'success',
    rejected: 'danger',
    need_supplement: 'warning',
    manual_review: 'warning'
  }
  return types[status] || 'info'
}

// 格式化日期
const formatDate = (dateStr) => {
  return dateStr ? dayjs(dateStr).format('YYYY-MM-DD HH:mm') : '-'
}

// 显示详情
const showDetail = (order) => {
  detailDrawerRef.value?.open(order.id)
}

// 分析订单
const handleAnalyze = async (order) => {
  const loadingInstance = ElLoading.service({
    text: '正在分析订单，请稍候...',
    background: 'rgba(0, 0, 0, 0.7)'
  })

  try {
    const res = await dataOrderApi.analyzeOrder(order.id)
    loadingInstance.close()

    if (res.data.can_connect) {
      ElMessage.success('分析完成，实体可连通，请进行审批确认！')
    } else {
      ElMessage.warning('分析完成，部分实体无法连通，请修改后重新分析')
    }

    fetchOrders()
  } catch (error) {
    loadingInstance.close()
    ElMessage.error(error.message || '分析失败')
  }
}

// 审批通过
const handleApprove = async (order) => {
  try {
    await ElMessageBox.confirm(
      `确定要审批通过订单 "${order.title}" 吗？\n通过后将自动生成数据流程资源。`,
      '审批确认',
      { type: 'info' }
    )

    const res = await dataOrderApi.approveOrder(order.id)
    ElMessage.success(`审批通过，已生成 DataFlow ID: ${res.data.generated_resources.dataflow_id}`)
    fetchOrders()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '审批失败')
    }
  }
}

// 完成订单
const handleComplete = async (order) => {
  try {
    await ElMessageBox.confirm(
      `确定要标记订单 "${order.title}" 为完成吗？`,
      '完成确认',
      { type: 'info' }
    )

    await dataOrderApi.completeOrder(order.id)
    ElMessage.success('订单已完成')
    fetchOrders()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '操作失败')
    }
  }
}

// 删除订单
const handleDelete = async (order) => {
  try {
    await ElMessageBox.confirm(
      `确定要删除订单 "${order.title}" 吗？此操作不可恢复。`,
      '删除确认',
      { type: 'warning', confirmButtonClass: 'el-button--danger' }
    )

    await dataOrderApi.deleteOrder(order.id)  // 使用 PUT 方法调用 /delete 接口
    ElMessage.success('删除成功')
    fetchOrders()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '删除失败')
    }
  }
}

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

<style scoped>
.data-order-manage {
  padding: 20px;
}

.page-header {
  margin-bottom: 20px;
}

.page-header h2 {
  margin: 0 0 8px 0;
  font-size: 20px;
  font-weight: 600;
}

.page-description {
  color: #909399;
  font-size: 14px;
  margin: 0;
}

.search-card {
  margin-bottom: 16px;
}

.table-card {
  min-height: 400px;
}

.pagination-wrapper {
  margin-top: 16px;
  display: flex;
  justify-content: flex-end;
}

.text-gray {
  color: #909399;
}
</style>
```

---

## 常见问题

### Q1: 分析接口超时怎么办？

**A**: 分析接口涉及 LLM 调用，可能需要较长时间。建议：
1. 设置较长的 `timeout`（如 60 秒）
2. 显示 loading 状态给用户
3. 如果持续超时，检查 LLM 服务配置

### Q2: 什么情况下订单会变成 `manual_review`？

**A**: 当分析结果显示 `can_connect: false` 时，表示 LLM 提取的实体在业务领域图谱中无法完全连通，订单会变成 `manual_review` 状态，需要人工修改后重新分析。

### Q3: 审批通过后会发生什么？

**A**: 审批通过后系统会：
1. 自动创建目标 BusinessDomain 节点
2. 自动创建 DataFlow 节点
3. 建立输入 BusinessDomain 与 DataFlow 的 INPUT 关系
4. 建立 DataFlow 与目标 BusinessDomain 的 OUTPUT 关系
5. 订单状态更新为 `processing`
6. 返回生成的资源 ID

### Q4: 什么情况下可以修改订单？

**A**: 只有以下状态的订单可以修改：
- `pending` (待处理)
- `manual_review` (待人工处理)
- `need_supplement` (待补充)

### Q5: `onboard` 状态是如何触发的？

**A**: `onboard` 状态通常由数据工厂（n8n 工作流）在数据流程执行完成后调用 `/orders/{id}/onboard` 接口触发。这表示数据产品已生产完成，可供使用。

### Q6: 订单编号的格式是什么？

**A**: 格式为 `DO` + `YYYYMMDD` + `4位序列号`，例如 `DO202601090001`。每天的序列号从 `0001` 开始递增。

---

## 更新日志

| 版本 | 日期 | 说明 |
|------|------|------|
| 2.1.0 | 2026-01-09 | 接口路径规范化：获取订单详情改为 `/orders/{id}/detail`；更新订单改为 `/orders/{id}/update`；删除订单改为 `PUT /orders/{id}/delete`（软删除） |
| 2.0.0 | 2026-01-09 | 重构订单流程：新增 `pending_approval`、`onboard` 状态；新增更新订单、onboard 回调接口；审批接口新增自动资源生成功能 |
| 1.0.0 | 2024-12-29 | 初始版本，包含完整的数据订单 API 文档 |
