api_data_order_guide.md 48 KB

数据订单 API 前端开发指南

模块说明: 数据订单 API 提供数据需求订单的创建、分析、审批、驳回、完成等全生命周期管理功能。当用户在数据服务列表中找不到所需数据时,可以发起数据订单,系统会通过 LLM 提取实体并检测业务领域图谱的连通性。

基础路径: /api/dataservice


目录


功能概述

数据订单功能允许用户:

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

业务流程

┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   待处理    │───>│   分析中    │───>│  加工中/    │───>│   已完成    │
│  (pending)  │    │ (analyzing) │    │ 待人工处理  │    │ (completed) │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
       │                  │                  │
       │                  │                  │
       v                  v                  v
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   已驳回    │    │   待补充    │    │   已更新    │
│ (rejected)  │    │(need_supple)│    │  (updated)  │
└─────────────┘    └─────────────┘    └─────────────┘

典型流程:

  1. 用户创建订单 → pending
  2. 触发分析 → analyzing
  3. 分析完成后:
    • 如果可连通 → processing (可自动提交数据任务)
    • 如果不可连通 → need_supplementmanual_review
  4. 审批通过 → processing
  5. 完成 → completed

通用说明

响应格式

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

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

错误码说明

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

订单状态说明

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

Axios 配置

建议的 Axios 全局配置:

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

const request = axios.create({
  baseURL: process.env.VUE_APP_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 - 状态过滤,见订单状态说明

响应数据

{
  "code": 200,
  "message": "获取数据订单列表成功",
  "data": {
    "list": [
      {
        "id": 1,
        "order_no": "DO202412260001",
        "title": "员工与部门关联数据",
        "description": "需要获取员工信息和所属部门的关联数据,包括部门层级",
        "extracted_domains": ["员工", "部门", "组织架构"],
        "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称"],
        "extraction_purpose": "用于人力资源分析报表",
        "graph_analysis": {
          "matched_domains": 3,
          "matched_fields": 4,
          "connection_score": 0.85
        },
        "can_connect": true,
        "connection_path": {
          "nodes": ["员工", "部门"],
          "relationships": ["属于"]
        },
        "status": "completed",
        "status_label": "已完成",
        "reject_reason": null,
        "result_product_id": 15,
        "result_dataflow_id": 28,
        "created_by": "张三",
        "created_at": "2024-12-26T09:00:00",
        "updated_at": "2024-12-26T14:30:00",
        "processed_by": "admin",
        "processed_at": "2024-12-26T14:30:00"
      }
    ],
    "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 接入示例

<template>
  <div class="data-order-list">
    <!-- 搜索栏 -->
    <div class="search-bar">
      <el-input
        v-model="searchParams.search"
        placeholder="搜索订单标题或描述..."
        @keyup.enter="fetchOrders"
        clearable
        style="width: 300px"
      />
      <el-select v-model="searchParams.status" placeholder="状态筛选" clearable>
        <el-option label="待处理" value="pending" />
        <el-option label="分析中" value="analyzing" />
        <el-option label="加工中" value="processing" />
        <el-option label="已完成" value="completed" />
        <el-option label="已驳回" value="rejected" />
        <el-option label="待补充" value="need_supplement" />
        <el-option label="待人工处理" value="manual_review" />
      </el-select>
      <el-button type="primary" @click="fetchOrders">查询</el-button>
      <el-button type="success" @click="showCreateDialog">新建订单</el-button>
    </div>

    <!-- 数据表格 -->
    <el-table :data="orderList" v-loading="loading" border>
      <el-table-column prop="order_no" label="订单编号" width="160" />
      <el-table-column prop="title" label="标题" min-width="200" />
      <el-table-column prop="status" label="状态" width="120">
        <template #default="{ row }">
          <el-tag :type="getStatusType(row.status)">
            {{ row.status_label }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="can_connect" label="可连通" width="80">
        <template #default="{ row }">
          <el-icon v-if="row.can_connect === true" color="#67C23A">
            <Check />
          </el-icon>
          <el-icon v-else-if="row.can_connect === false" color="#F56C6C">
            <Close />
          </el-icon>
          <span v-else>-</span>
        </template>
      </el-table-column>
      <el-table-column prop="created_by" label="创建人" width="100" />
      <el-table-column prop="created_at" label="创建时间" width="180">
        <template #default="{ row }">
          {{ formatDate(row.created_at) }}
        </template>
      </el-table-column>
      <el-table-column label="操作" width="250" fixed="right">
        <template #default="{ row }">
          <el-button size="small" @click="handleDetail(row.id)">详情</el-button>
          <el-button
            v-if="row.status === 'pending'"
            size="small"
            type="primary"
            @click="handleAnalyze(row.id)"
          >
            分析
          </el-button>
          <el-button
            v-if="row.status === 'manual_review'"
            size="small"
            type="success"
            @click="handleApprove(row.id)"
          >
            审批
          </el-button>
          <el-button
            v-if="['pending', 'completed', 'rejected'].includes(row.status)"
            size="small"
            type="danger"
            @click="handleDelete(row.id)"
          >
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <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>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { Check, Close } from '@element-plus/icons-vue'
import request from '@/utils/request'
import dayjs from 'dayjs'

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 fetchOrders = async () => {
  loading.value = true
  try {
    const params = { ...searchParams }
    if (!params.status) delete params.status
    if (!params.search) delete params.search
    
    const res = await request.get('/api/dataservice/orderlist', { params })
    orderList.value = res.data.list
    pagination.value = res.data.pagination
  } finally {
    loading.value = false
  }
}

const getStatusType = (status) => {
  const types = {
    pending: 'info',
    analyzing: 'warning',
    processing: 'primary',
    completed: 'success',
    rejected: 'danger',
    need_supplement: 'warning',
    manual_review: 'warning',
    updated: 'info'
  }
  return types[status] || 'info'
}

const formatDate = (dateStr) => {
  return dateStr ? dayjs(dateStr).format('YYYY-MM-DD HH:mm:ss') : '-'
}

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

<style scoped>
.search-bar {
  display: flex;
  gap: 12px;
  margin-bottom: 16px;
}

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

2. 获取数据订单详情

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

请求信息

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

路径参数

参数名 类型 必填 说明
order_id integer 数据订单 ID

响应数据

{
  "code": 200,
  "message": "获取数据订单详情成功",
  "data": {
    "id": 1,
    "order_no": "DO202412260001",
    "title": "员工与部门关联数据",
    "description": "需要获取员工信息和所属部门的关联数据,包括部门层级结构,用于生成人力资源分析报表。",
    "extracted_domains": ["员工", "部门", "组织架构"],
    "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称", "上级部门"],
    "extraction_purpose": "用于人力资源分析报表",
    "graph_analysis": {
      "matched_domains": [
        { "name": "员工", "node_id": "domain_001", "match_score": 1.0 },
        { "name": "部门", "node_id": "domain_002", "match_score": 1.0 }
      ],
      "matched_fields": [
        { "name": "员工ID", "field_id": "field_001", "domain": "员工" },
        { "name": "部门ID", "field_id": "field_002", "domain": "部门" }
      ],
      "connection_analysis": {
        "paths_found": 2,
        "shortest_path_length": 1
      }
    },
    "can_connect": true,
    "connection_path": {
      "nodes": ["员工", "部门"],
      "relationships": ["属于"],
      "path_detail": [
        { "from": "员工", "to": "部门", "relation": "属于", "common_fields": ["部门ID"] }
      ]
    },
    "status": "completed",
    "status_label": "已完成",
    "reject_reason": null,
    "result_product_id": 15,
    "result_dataflow_id": 28,
    "created_by": "张三",
    "created_at": "2024-12-26T09:00:00",
    "updated_at": "2024-12-26T14:30:00",
    "processed_by": "admin",
    "processed_at": "2024-12-26T14:30:00"
  }
}

错误响应

订单不存在 (404):

{
  "code": 404,
  "message": "数据订单不存在",
  "data": null
}

Vue 接入示例

<template>
  <el-drawer v-model="visible" title="订单详情" size="600px">
    <el-descriptions :column="1" border v-if="order">
      <el-descriptions-item label="订单编号">{{ order.order_no }}</el-descriptions-item>
      <el-descriptions-item label="标题">{{ order.title }}</el-descriptions-item>
      <el-descriptions-item label="状态">
        <el-tag :type="getStatusType(order.status)">{{ order.status_label }}</el-tag>
      </el-descriptions-item>
      <el-descriptions-item label="需求描述">
        <div class="description-text">{{ order.description }}</div>
      </el-descriptions-item>
      
      <!-- LLM 提取结果 -->
      <el-descriptions-item label="提取的业务领域" v-if="order.extracted_domains">
        <el-tag v-for="domain in order.extracted_domains" :key="domain" class="tag-item">
          {{ domain }}
        </el-tag>
      </el-descriptions-item>
      <el-descriptions-item label="提取的数据字段" v-if="order.extracted_fields">
        <el-tag v-for="field in order.extracted_fields" :key="field" type="info" class="tag-item">
          {{ field }}
        </el-tag>
      </el-descriptions-item>
      <el-descriptions-item label="数据用途" v-if="order.extraction_purpose">
        {{ order.extraction_purpose }}
      </el-descriptions-item>
      
      <!-- 连通性分析 -->
      <el-descriptions-item label="图谱连通性">
        <el-tag v-if="order.can_connect === true" type="success">可连通</el-tag>
        <el-tag v-else-if="order.can_connect === false" type="danger">不可连通</el-tag>
        <span v-else>未分析</span>
      </el-descriptions-item>
      
      <!-- 驳回原因 -->
      <el-descriptions-item label="驳回原因" v-if="order.reject_reason">
        <el-alert :title="order.reject_reason" type="error" :closable="false" />
      </el-descriptions-item>
      
      <!-- 关联结果 -->
      <el-descriptions-item label="生成的数据产品" v-if="order.result_product_id">
        <el-link type="primary" @click="goToProduct(order.result_product_id)">
          查看数据产品 #{{ order.result_product_id }}
        </el-link>
      </el-descriptions-item>
      
      <!-- 审计信息 -->
      <el-descriptions-item label="创建人">{{ order.created_by }}</el-descriptions-item>
      <el-descriptions-item label="创建时间">{{ formatDate(order.created_at) }}</el-descriptions-item>
      <el-descriptions-item label="处理人" v-if="order.processed_by">
        {{ order.processed_by }}
      </el-descriptions-item>
      <el-descriptions-item label="处理时间" v-if="order.processed_at">
        {{ formatDate(order.processed_at) }}
      </el-descriptions-item>
    </el-descriptions>
    
    <!-- 操作按钮 -->
    <div class="drawer-footer" v-if="order">
      <el-button
        v-if="order.status === 'pending'"
        type="primary"
        @click="handleAnalyze"
        :loading="analyzing"
      >
        开始分析
      </el-button>
      <el-button
        v-if="order.status === 'manual_review'"
        type="success"
        @click="handleApprove"
      >
        审批通过
      </el-button>
      <el-button
        v-if="order.status === 'manual_review'"
        type="danger"
        @click="showRejectDialog"
      >
        驳回
      </el-button>
    </div>
  </el-drawer>
</template>

<script setup>
import { ref } from 'vue'
import request from '@/utils/request'
import dayjs from 'dayjs'

const props = defineProps({
  orderId: { type: Number, default: null }
})

const visible = ref(false)
const order = ref(null)
const analyzing = ref(false)

const fetchDetail = async () => {
  if (!props.orderId) return
  try {
    const res = await request.get(`/api/dataservice/orders/${props.orderId}`)
    order.value = res.data
    visible.value = true
  } catch (error) {
    console.error('获取订单详情失败:', error)
  }
}

const formatDate = (dateStr) => {
  return dateStr ? dayjs(dateStr).format('YYYY-MM-DD HH:mm:ss') : '-'
}

defineExpose({ fetchDetail })
</script>

<style scoped>
.tag-item {
  margin-right: 8px;
  margin-bottom: 4px;
}

.description-text {
  white-space: pre-wrap;
  word-break: break-word;
}

.drawer-footer {
  margin-top: 24px;
  display: flex;
  gap: 12px;
}
</style>

3. 创建数据订单

创建新的数据需求订单。

请求信息

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

请求体参数

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

请求示例

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

响应数据

{
  "code": 200,
  "message": "创建数据订单成功",
  "data": {
    "id": 1,
    "order_no": "DO202412260001",
    "title": "员工与部门关联数据",
    "description": "需要获取员工信息和所属部门的关联数据...",
    "extracted_domains": null,
    "extracted_fields": null,
    "extraction_purpose": null,
    "graph_analysis": null,
    "can_connect": null,
    "connection_path": null,
    "status": "pending",
    "status_label": "待处理",
    "reject_reason": null,
    "result_product_id": null,
    "result_dataflow_id": null,
    "created_by": "张三",
    "created_at": "2024-12-26T09:00:00",
    "updated_at": "2024-12-26T09:00:00",
    "processed_by": null,
    "processed_at": null
  }
}

错误响应

缺少必填字段 (400):

{
  "code": 400,
  "message": "缺少必填字段: title",
  "data": null
}

请求体为空 (400):

{
  "code": 400,
  "message": "请求数据不能为空",
  "data": null
}

Vue 接入示例

<template>
  <el-dialog v-model="dialogVisible" title="创建数据订单" width="600px">
    <el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
      <el-form-item label="订单标题" prop="title">
        <el-input
          v-model="form.title"
          placeholder="请输入订单标题,例如:员工与部门关联数据"
          maxlength="200"
          show-word-limit
        />
      </el-form-item>
      <el-form-item label="需求描述" prop="description">
        <el-input
          v-model="form.description"
          type="textarea"
          :rows="8"
          placeholder="请详细描述需要什么数据,包括:&#10;1. 涉及的业务领域(如员工、部门、项目等)&#10;2. 需要的具体字段&#10;3. 数据用途"
          maxlength="2000"
          show-word-limit
        />
      </el-form-item>
      <el-form-item label="创建人">
        <el-input v-model="form.created_by" placeholder="可选,默认为当前用户" />
      </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 formRef = ref(null)
const submitting = ref(false)

const form = reactive({
  title: '',
  description: '',
  created_by: ''
})

const rules = {
  title: [
    { required: true, message: '请输入订单标题', trigger: 'blur' },
    { max: 200, message: '标题不能超过200个字符', trigger: 'blur' }
  ],
  description: [
    { required: true, message: '请输入需求描述', trigger: 'blur' },
    { min: 10, message: '描述至少需要10个字符', trigger: 'blur' }
  ]
}

const open = () => {
  form.title = ''
  form.description = ''
  form.created_by = ''
  dialogVisible.value = true
}

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

  submitting.value = true
  try {
    const data = {
      title: form.title,
      description: form.description
    }
    if (form.created_by) {
      data.created_by = form.created_by
    }

    const res = await request.post('/api/dataservice/neworder', data)
    ElMessage.success('订单创建成功')
    dialogVisible.value = false
    emit('success', res.data)
  } catch (error) {
    ElMessage.error(error.message || '创建失败')
  } finally {
    submitting.value = false
  }
}

defineExpose({ open })
</script>

4. 分析数据订单

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

请求信息

项目 说明
URL POST /api/dataservice/orders/{order_id}/analyze
Method POST
Content-Type application/json

路径参数

参数名 类型 必填 说明
order_id integer 数据订单 ID

请求体

无需请求体。

响应数据

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

{
  "code": 200,
  "message": "数据订单分析完成",
  "data": {
    "id": 1,
    "order_no": "DO202412260001",
    "title": "员工与部门关联数据",
    "description": "...",
    "extracted_domains": ["员工", "部门", "组织架构"],
    "extracted_fields": ["员工ID", "姓名", "部门ID", "部门名称"],
    "extraction_purpose": "用于人力资源分析报表",
    "graph_analysis": {
      "matched_domains": [
        { "name": "员工", "node_id": "domain_001", "match_score": 1.0 },
        { "name": "部门", "node_id": "domain_002", "match_score": 1.0 }
      ],
      "matched_fields": 4,
      "unmatched_domains": ["组织架构"],
      "connection_analysis": {
        "paths_found": 2,
        "shortest_path_length": 1
      }
    },
    "can_connect": true,
    "connection_path": {
      "nodes": ["员工", "部门"],
      "relationships": ["属于"],
      "common_fields": ["部门ID"]
    },
    "status": "processing",
    "status_label": "加工中",
    "updated_at": "2024-12-26T09:15:00"
  }
}

分析结果说明:

结果 说明 后续状态
can_connect: true 所有实体都能在图谱中连通 processing (可自动生成数据流)
can_connect: false 存在无法连通的实体 need_supplementmanual_review

错误响应

订单不存在:

{
  "code": 404,
  "message": "数据订单不存在",
  "data": null
}

分析失败:

{
  "code": 500,
  "message": "分析数据订单失败: LLM 服务调用超时",
  "data": null
}

Vue 接入示例

const analyzeOrder = async (orderId) => {
  try {
    // 显示分析中提示
    const loadingInstance = ElLoading.service({
      text: '正在分析订单,可能需要几秒钟...',
      background: 'rgba(0, 0, 0, 0.7)'
    })

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

    // 根据分析结果显示不同提示
    if (res.data.can_connect) {
      ElMessage.success('分析完成,实体可连通!')
    } else {
      ElMessage.warning('分析完成,部分实体无法连通,需要补充信息或人工处理')
    }

    // 刷新订单详情
    fetchDetail(orderId)
    
    return res.data
  } catch (error) {
    ElMessage.error(error.message || '分析失败')
    throw error
  }
}

5. 审批通过订单

审批通过数据订单,将状态更新为 processing

请求信息

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

路径参数

参数名 类型 必填 说明
order_id integer 数据订单 ID

请求体参数

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

请求示例

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

响应数据

{
  "code": 200,
  "message": "数据订单审批通过",
  "data": {
    "id": 1,
    "order_no": "DO202412260001",
    "status": "processing",
    "status_label": "加工中",
    "processed_by": "管理员A",
    "processed_at": "2024-12-26T10:00:00",
    "updated_at": "2024-12-26T10:00:00"
  }
}

错误响应

订单状态不允许审批 (400):

{
  "code": 400,
  "message": "当前状态不允许审批操作",
  "data": null
}

Vue 接入示例

const approveOrder = async (orderId) => {
  try {
    await ElMessageBox.confirm(
      '确定要审批通过该订单吗?通过后将开始生成数据流。',
      '审批确认',
      { type: 'warning' }
    )

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

    ElMessage.success('审批通过')
    fetchOrders() // 刷新列表
    
    return res.data
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '审批失败')
    }
  }
}

6. 驳回订单

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

请求信息

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

路径参数

参数名 类型 必填 说明
order_id integer 数据订单 ID

请求体参数

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

请求示例

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

响应数据

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

错误响应

驳回原因为空 (400):

{
  "code": 400,
  "message": "驳回原因不能为空",
  "data": null
}

Vue 接入示例

<template>
  <el-dialog v-model="rejectDialogVisible" title="驳回订单" width="500px">
    <el-form :model="rejectForm" :rules="rejectRules" ref="rejectFormRef">
      <el-form-item label="驳回原因" prop="reason">
        <el-input
          v-model="rejectForm.reason"
          type="textarea"
          :rows="4"
          placeholder="请输入驳回原因,将通知订单创建人"
        />
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="rejectDialogVisible = 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 request from '@/utils/request'

const rejectDialogVisible = ref(false)
const rejectFormRef = ref(null)
const rejecting = ref(false)
const currentOrderId = ref(null)

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

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

const showRejectDialog = (orderId) => {
  currentOrderId.value = orderId
  rejectForm.reason = ''
  rejectDialogVisible.value = true
}

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

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

    ElMessage.success('订单已驳回')
    rejectDialogVisible.value = false
    fetchOrders() // 刷新列表
  } catch (error) {
    ElMessage.error(error.message || '驳回失败')
  } finally {
    rejecting.value = false
  }
}

defineExpose({ showRejectDialog })
</script>

7. 完成订单

将订单标记为完成,可关联生成的数据产品和数据流。

请求信息

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

路径参数

参数名 类型 必填 说明
order_id integer 数据订单 ID

请求体参数

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

请求示例

{
  "product_id": 15,
  "dataflow_id": 28,
  "processed_by": "system"
}

响应数据

{
  "code": 200,
  "message": "数据订单已完成",
  "data": {
    "id": 1,
    "order_no": "DO202412260001",
    "status": "completed",
    "status_label": "已完成",
    "result_product_id": 15,
    "result_dataflow_id": 28,
    "processed_by": "system",
    "processed_at": "2024-12-26T14:30:00",
    "updated_at": "2024-12-26T14:30:00"
  }
}

Vue 接入示例

const completeOrder = async (orderId, productId = null, dataflowId = null) => {
  try {
    const res = await request.post(`/api/dataservice/orders/${orderId}/complete`, {
      product_id: productId,
      dataflow_id: dataflowId,
      processed_by: 'system'
    })

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

8. 删除订单

删除数据订单记录。

请求信息

项目 说明
URL DELETE /api/dataservice/orders/{order_id}
Method DELETE
Content-Type -

路径参数

参数名 类型 必填 说明
order_id integer 数据订单 ID

响应数据

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

错误响应

订单不存在 (404):

{
  "code": 404,
  "message": "数据订单不存在",
  "data": null
}

Vue 接入示例

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

    await request.delete(`/api/dataservice/orders/${orderId}`)
    ElMessage.success('删除成功')
    
    // 从列表中移除
    orderList.value = orderList.value.filter(o => o.id !== orderId)
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '删除失败')
    }
  }
}

API 模块封装

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

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

const BASE_URL = '/api/dataservice'

export const dataOrderApi = {
  /**
   * 获取数据订单列表
   * @param {Object} params - 查询参数
   * @param {number} params.page - 页码
   * @param {number} params.page_size - 每页数量
   * @param {string} params.search - 搜索关键词
   * @param {string} params.status - 状态过滤
   */
  getOrders(params) {
    return request.get(`${BASE_URL}/orderlist`, { params })
  },

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

  /**
   * 创建数据订单
   * @param {Object} data - 订单数据
   * @param {string} data.title - 订单标题
   * @param {string} data.description - 需求描述
   * @param {string} [data.created_by] - 创建人
   */
  createOrder(data) {
    return request.post(`${BASE_URL}/neworder`, data)
  },

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

  /**
   * 审批通过订单
   * @param {number} orderId - 订单 ID
   * @param {string} [processedBy] - 处理人
   */
  approveOrder(orderId, processedBy = 'admin') {
    return request.post(`${BASE_URL}/orders/${orderId}/approve`, {
      processed_by: processedBy
    })
  },

  /**
   * 驳回订单
   * @param {number} orderId - 订单 ID
   * @param {string} reason - 驳回原因
   * @param {string} [processedBy] - 处理人
   */
  rejectOrder(orderId, reason, processedBy = 'admin') {
    return request.post(`${BASE_URL}/orders/${orderId}/reject`, {
      reason,
      processed_by: processedBy
    })
  },

  /**
   * 完成订单
   * @param {number} orderId - 订单 ID
   * @param {Object} [options] - 可选参数
   * @param {number} [options.productId] - 数据产品 ID
   * @param {number} [options.dataflowId] - 数据流 ID
   * @param {string} [options.processedBy] - 处理人
   */
  completeOrder(orderId, options = {}) {
    return request.post(`${BASE_URL}/orders/${orderId}/complete`, {
      product_id: options.productId,
      dataflow_id: options.dataflowId,
      processed_by: options.processedBy || 'system'
    })
  },

  /**
   * 删除订单
   * @param {number} orderId - 订单 ID
   */
  deleteOrder(orderId) {
    return request.delete(`${BASE_URL}/orders/${orderId}`)
  }
}

export default dataOrderApi

完整页面示例

以下是一个完整的数据订单管理页面示例:

<!-- 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
        row-key="id"
      >
        <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="description" label="描述" min-width="250" show-overflow-tooltip />
        <el-table-column prop="status" label="状态" width="120" 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="280" fixed="right">
          <template #default="{ row }">
            <el-button-group>
              <el-button size="small" @click="showDetail(row.id)">
                详情
              </el-button>
              <el-button
                v-if="row.status === 'pending'"
                size="small"
                type="primary"
                @click="handleAnalyze(row)"
              >
                分析
              </el-button>
              <el-button
                v-if="row.status === 'manual_review'"
                size="small"
                type="success"
                @click="handleApprove(row)"
              >
                审批
              </el-button>
              <el-button
                v-if="row.status === 'manual_review'"
                size="small"
                type="warning"
                @click="showRejectDialog(row)"
              >
                驳回
              </el-button>
              <el-button
                v-if="canDelete(row.status)"
                size="small"
                type="danger"
                @click="handleDelete(row)"
              >
                删除
              </el-button>
            </el-button-group>
          </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="handleCreateSuccess" />

    <!-- 订单详情抽屉 -->
    <OrderDetailDrawer ref="detailDrawerRef" @refresh="fetchOrders" />

    <!-- 驳回对话框 -->
    <RejectOrderDialog ref="rejectDialogRef" @success="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 detailDrawerRef = ref(null)
const rejectDialogRef = 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: '分析中',
  processing: '加工中',
  completed: '已完成',
  rejected: '已驳回',
  need_supplement: '待补充',
  manual_review: '待人工处理',
  updated: '已更新'
}

// 获取订单列表
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
  } catch (error) {
    console.error('获取订单列表失败:', error)
  } finally {
    loading.value = false
  }
}

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

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

// 判断是否可删除
const canDelete = (status) => {
  return ['pending', 'completed', 'rejected'].includes(status)
}

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

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

// 显示驳回对话框
const showRejectDialog = (order) => {
  rejectDialogRef.value.open(order)
}

// 分析订单
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}" 吗?`,
      '审批确认',
      { type: 'info' }
    )

    await dataOrderApi.approveOrder(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)
    ElMessage.success('删除成功')
    fetchOrders()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error(error.message || '删除失败')
    }
  }
}

// 创建成功回调
const handleCreateSuccess = (newOrder) => {
  ElMessage.success(`订单 ${newOrder.order_no} 创建成功`)
  fetchOrders()
}

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;
}

:deep(.el-button-group) {
  display: flex;
  flex-wrap: nowrap;
}
</style>

常见问题

Q1: 分析接口超时怎么办?

A: 分析接口涉及 LLM 调用,可能需要较长时间。建议:

  1. 设置较长的 timeout(如 60 秒)
  2. 显示 loading 状态给用户
  3. 后端可能需要优化 LLM 调用效率

Q2: 什么情况下订单会变成 need_supplement

A: 当 LLM 提取的实体在业务领域图谱中无法完全匹配时,订单会变成 need_supplement 状态,提示用户补充更多信息。

Q3: 如何判断订单是否可以自动生成数据流?

A: 当 can_connecttrue 时,表示所有实体都能在图谱中连通,理论上可以自动生成数据流。但具体实现取决于后端数据流生成逻辑。

Q4: 订单编号的格式是什么?

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

Q5: 可以修改已提交的订单吗?

A: 目前 API 不支持直接修改订单。如果需要修改,可以:

  1. 删除原订单并创建新订单
  2. 后续版本可能会增加编辑功能

更新日志

版本 日期 说明
1.0.0 2024-12-29 初始版本,包含完整的数据订单 API 文档