模块说明: 数据订单 API 提供数据需求订单的创建、分析、审批、驳回、完成等全生命周期管理功能。当用户在数据服务列表中找不到所需数据时,可以发起数据订单,系统会通过 LLM 提取实体并检测业务领域图谱的连通性。
基础路径:
/api/dataservice
数据订单功能允许用户:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 待处理 │───>│ 分析中 │───>│ 加工中/ │───>│ 已完成 │
│ (pending) │ │ (analyzing) │ │ 待人工处理 │ │ (completed) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ │ │
v v v
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 已驳回 │ │ 待补充 │ │ 已更新 │
│ (rejected) │ │(need_supple)│ │ (updated) │
└─────────────┘ └─────────────┘ └─────────────┘
典型流程:
pendinganalyzingprocessing (可自动提交数据任务)need_supplement 或 manual_reviewprocessingcompleted所有接口返回统一的 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 全局配置:
// 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
分页获取数据订单列表,支持搜索和状态过滤。
| 项目 | 说明 |
|---|---|
| URL | GET /api/dataservice/orderlist |
| Method | GET |
| Content-Type | - |
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
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 | 处理时间 |
<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>
根据 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
}
<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>
创建新的数据需求订单。
| 项目 | 说明 |
|---|---|
| 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
}
<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="请详细描述需要什么数据,包括: 1. 涉及的业务领域(如员工、部门、项目等) 2. 需要的具体字段 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>
触发 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_supplement 或 manual_review |
订单不存在:
{
"code": 404,
"message": "数据订单不存在",
"data": null
}
分析失败:
{
"code": 500,
"message": "分析数据订单失败: LLM 服务调用超时",
"data": null
}
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
}
}
审批通过数据订单,将状态更新为 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
}
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 || '审批失败')
}
}
}
驳回数据订单,需要提供驳回原因。
| 项目 | 说明 |
|---|---|
| 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
}
<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>
将订单标记为完成,可关联生成的数据产品和数据流。
| 项目 | 说明 |
|---|---|
| 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"
}
}
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
}
}
删除数据订单记录。
| 项目 | 说明 |
|---|---|
| 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
}
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 封装到独立模块:
// 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>
A: 分析接口涉及 LLM 调用,可能需要较长时间。建议:
timeout(如 60 秒)need_supplement?A: 当 LLM 提取的实体在业务领域图谱中无法完全匹配时,订单会变成 need_supplement 状态,提示用户补充更多信息。
A: 当 can_connect 为 true 时,表示所有实体都能在图谱中连通,理论上可以自动生成数据流。但具体实现取决于后端数据流生成逻辑。
A: 格式为 DO + YYYYMMDD + 4位序列号,例如 DO202412260001。每天的序列号从 0001 开始递增。
A: 目前 API 不支持直接修改订单。如果需要修改,可以:
| 版本 | 日期 | 说明 |
|---|---|---|
| 1.0.0 | 2024-12-29 | 初始版本,包含完整的数据订单 API 文档 |