Parcourir la source

数据服务-数据订单

Xiao_123 il y a 12 heures
Parent
commit
f72a61eee2

+ 54 - 0
src/api/dataOrder.js

@@ -0,0 +1,54 @@
+import http from '@/utils/request'
+
+const dataOrder = {
+  // 获取数据订单列表
+  getOrders (params) {
+    return http.get('/dataservice/orderlist', params)
+  },
+
+  // 获取数据订单详情
+  getOrderDetail (orderId) {
+    return http.get(`/dataservice/orders/${orderId}`)
+  },
+
+  // 创建数据订单
+  createOrder (data) {
+    return http.post('/dataservice/neworder', data)
+  },
+
+  // 分析数据订单
+  analyzeOrder (orderId) {
+    return http.post(`/dataservice/orders/${orderId}/analyze`)
+  },
+
+  // 审批通过订单
+  approveOrder (orderId, processedBy = 'admin') {
+    return http.post(`/dataservice/orders/${orderId}/approve`, {
+      processed_by: processedBy
+    })
+  },
+
+  // 驳回订单
+  rejectOrder (orderId, reason, processedBy = 'admin') {
+    return http.post(`/dataservice/orders/${orderId}/reject`, {
+      reason,
+      processed_by: processedBy
+    })
+  },
+
+  // 完成订单
+  completeOrder (orderId, options = {}) {
+    return http.post(`/dataservice/orders/${orderId}/complete`, {
+      product_id: options.productId,
+      dataflow_id: options.dataflowId,
+      processed_by: options.processedBy || 'system'
+    })
+  },
+
+  // 删除订单
+  deleteOrder (orderId) {
+    return http.del(`/dataservice/orders/${orderId}`)
+  }
+}
+
+export const api = dataOrder

+ 128 - 0
src/views/dataService/dataOrder/components/CreateOrderDialog.vue

@@ -0,0 +1,128 @@
+<template>
+  <MDialog
+    :visible="visible"
+    title="创建数据订单"
+    widthType="0"
+    @update:visible="handleVisibleChange"
+    @submit="handleSubmit"
+  >
+    <MForm ref="form" :items="formItems" v-model="formValues"></MForm>
+  </MDialog>
+</template>
+
+<script>
+import MDialog from '@/components/Dialog'
+import MForm from '@/components/MForm'
+import { api } from '@/api/dataOrder'
+
+export default {
+  name: 'CreateOrderDialog',
+  components: {
+    MDialog,
+    MForm
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      formValues: {
+        title: '',
+        description: '',
+        created_by: ''
+      }
+    }
+  },
+  computed: {
+    formItems () {
+      return [
+        {
+          type: 'text',
+          key: 'title',
+          label: '订单标题 *',
+          placeholder: '请输入订单标题,例如:员工与部门关联数据',
+          rules: [
+            v => !!v || '请输入订单标题',
+            v => (v && v.length <= 200) || '标题不能超过200个字符'
+          ],
+          maxlength: 200,
+          counter: true,
+          outlined: true,
+          dense: true,
+          hideDetails: 'auto'
+        },
+        {
+          type: 'textarea',
+          key: 'description',
+          label: '需求描述 *',
+          placeholder: '请详细描述需要什么数据,包括: 1. 涉及的业务领域(如员工、部门、项目等) 2. 需要的具体字段 3. 数据用途',
+          rules: [
+            v => !!v || '请输入需求描述',
+            v => (v && v.length >= 10) || '描述至少需要10个字符'
+          ],
+          maxlength: 2000,
+          counter: true,
+          outlined: true,
+          rows: 8,
+          hideDetails: 'auto'
+        },
+        {
+          type: 'text',
+          key: 'created_by',
+          label: '创建人',
+          outlined: true,
+          dense: true,
+          hideDetails: 'auto'
+        }
+      ]
+    }
+  },
+  watch: {
+    visible (val) {
+      if (val) {
+        this.resetForm()
+      }
+    }
+  },
+  methods: {
+    handleVisibleChange (val) {
+      this.$emit('update:visible', val)
+    },
+    resetForm () {
+      this.formValues = {
+        title: '',
+        description: '',
+        created_by: ''
+      }
+      this.$nextTick(() => {
+        if (this.$refs.form) {
+          this.$refs.form.resetValidation()
+        }
+      })
+    },
+    async handleSubmit () {
+      if (!this.$refs.form.validate()) {
+        return
+      }
+      try {
+        const data = {
+          title: this.formValues.title,
+          description: this.formValues.description
+        }
+        if (this.formValues.created_by) {
+          data.created_by = this.formValues.created_by
+        }
+        await api.createOrder(data)
+        this.$snackbar.success('订单创建成功')
+        this.$emit('update:visible', false)
+        this.$emit('success')
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    }
+  }
+}
+</script>

+ 277 - 0
src/views/dataService/dataOrder/components/OrderDetailDialog.vue

@@ -0,0 +1,277 @@
+<template>
+  <MDialog
+    :visible="visible"
+    title="订单详情"
+    widthType="1"
+    :footer="false"
+    @update:visible="handleVisibleChange"
+  >
+    <div v-loading="detailLoading">
+      <v-card v-if="order" outlined>
+        <v-card-text>
+          <v-row>
+            <v-col cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">订单编号:</span>
+                <span class="value">{{ order.order_no }}</span>
+              </div>
+            </v-col>
+            <v-col cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">状态:</span>
+                <v-chip :color="getStatusColor(order.status)" small>{{ order.status_label }}</v-chip>
+              </div>
+            </v-col>
+            <v-col cols="12">
+              <div class="detail-item">
+                <span class="label">标题:</span>
+                <span class="value">{{ order.title }}</span>
+              </div>
+            </v-col>
+            <v-col cols="12">
+              <div class="detail-item">
+                <span class="label">需求描述:</span>
+                <div class="value description-text">{{ order.description }}</div>
+              </div>
+            </v-col>
+
+            <!-- LLM 提取结果 -->
+            <v-col v-if="order.extracted_domains && order.extracted_domains.length" cols="12">
+              <div class="detail-item">
+                <span class="label">提取的业务领域:</span>
+                <div class="value">
+                  <v-chip
+                    v-for="domain in order.extracted_domains"
+                    :key="domain"
+                    small
+                    class="mr-2 mb-2"
+                  >
+                    {{ domain }}
+                  </v-chip>
+                </div>
+              </div>
+            </v-col>
+            <v-col v-if="order.extracted_fields && order.extracted_fields.length" cols="12">
+              <div class="detail-item">
+                <span class="label">提取的数据字段:</span>
+                <div class="value">
+                  <v-chip
+                    v-for="field in order.extracted_fields"
+                    :key="field"
+                    small
+                    color="info"
+                    class="mr-2 mb-2"
+                  >
+                    {{ field }}
+                  </v-chip>
+                </div>
+              </div>
+            </v-col>
+            <v-col v-if="order.extraction_purpose" cols="12">
+              <div class="detail-item">
+                <span class="label">数据用途:</span>
+                <span class="value">{{ order.extraction_purpose }}</span>
+              </div>
+            </v-col>
+
+            <!-- 连通性分析 -->
+            <v-col cols="12">
+              <div class="detail-item">
+                <span class="label">图谱连通性:</span>
+                <span class="value">
+                  <v-chip v-if="order.can_connect === true" color="success" small>可连通</v-chip>
+                  <v-chip v-else-if="order.can_connect === false" color="error" small>不可连通</v-chip>
+                  <span v-else>未分析</span>
+                </span>
+              </div>
+            </v-col>
+
+            <!-- 驳回原因 -->
+            <v-col v-if="order.reject_reason" cols="12">
+              <v-alert type="error" dense>{{ order.reject_reason }}</v-alert>
+            </v-col>
+
+            <!-- 关联结果 -->
+            <v-col v-if="order.result_product_id" cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">生成的数据产品:</span>
+                <v-btn
+                  text
+                  color="primary"
+                  small
+                  @click="goToProduct(order.result_product_id)"
+                >
+                  查看数据产品 #{{ order.result_product_id }}
+                </v-btn>
+              </div>
+            </v-col>
+            <v-col v-if="order.result_dataflow_id" cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">生成的数据流:</span>
+                <v-btn
+                  text
+                  color="primary"
+                  small
+                  @click="goToDataflow(order.result_dataflow_id)"
+                >
+                  查看数据流 #{{ order.result_dataflow_id }}
+                </v-btn>
+              </div>
+            </v-col>
+
+            <!-- 审计信息 -->
+            <v-col cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">创建人:</span>
+                <span class="value">{{ order.created_by }}</span>
+              </div>
+            </v-col>
+            <v-col cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">创建时间:</span>
+                <span class="value">{{ formatDateTime(order.created_at) }}</span>
+              </div>
+            </v-col>
+            <v-col v-if="order.processed_by" cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">处理人:</span>
+                <span class="value">{{ order.processed_by }}</span>
+              </div>
+            </v-col>
+            <v-col v-if="order.processed_at" cols="12" md="6">
+              <div class="detail-item">
+                <span class="label">处理时间:</span>
+                <span class="value">{{ formatDateTime(order.processed_at) }}</span>
+              </div>
+            </v-col>
+          </v-row>
+        </v-card-text>
+      </v-card>
+
+      <!-- 操作按钮 -->
+      <div v-if="order" class="mt-4 d-flex justify-end">
+        <v-btn v-if="order.status === 'pending'" color="primary" @click="handleAnalyze">开始分析</v-btn>
+        <v-btn v-if="order.status === 'manual_review'" color="success" class="ml-2" @click="handleApprove">审批通过</v-btn>
+        <v-btn v-if="order.status === 'manual_review'" color="error" class="ml-2" @click="handleReject">驳回</v-btn>
+      </div>
+    </div>
+  </MDialog>
+</template>
+
+<script>
+import MDialog from '@/components/Dialog'
+import { api } from '@/api/dataOrder'
+import { formatDate } from '@/utils/date'
+
+export default {
+  name: 'OrderDetailDialog',
+  components: {
+    MDialog
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    orderId: {
+      type: Number,
+      default: null
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data () {
+    return {
+      order: null,
+      detailLoading: false
+    }
+  },
+  watch: {
+    visible (val) {
+      if (val && this.orderId) {
+        this.fetchDetail()
+      }
+    },
+    orderId (val) {
+      if (val && this.visible) {
+        this.fetchDetail()
+      }
+    }
+  },
+  methods: {
+    handleVisibleChange (val) {
+      this.$emit('update:visible', val)
+    },
+    async fetchDetail () {
+      if (!this.orderId) return
+      this.detailLoading = true
+      try {
+        const { data } = await api.getOrderDetail(this.orderId)
+        this.order = data
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.detailLoading = false
+      }
+    },
+    handleAnalyze () {
+      this.$emit('analyze', this.orderId)
+      this.handleVisibleChange(false)
+    },
+    handleApprove () {
+      this.$emit('approve', this.orderId)
+    },
+    handleReject () {
+      this.$emit('reject', this.orderId)
+    },
+    goToProduct (productId) {
+      // TODO: 跳转到数据产品详情页
+      this.$snackbar.info('跳转到数据产品详情页')
+    },
+    goToDataflow (dataflowId) {
+      // TODO: 跳转到数据流详情页
+      this.$snackbar.info('跳转到数据流详情页')
+    },
+    getStatusColor (status) {
+      const colors = {
+        pending: 'info',
+        analyzing: 'warning',
+        processing: 'primary',
+        completed: 'success',
+        rejected: 'error',
+        need_supplement: 'warning',
+        manual_review: 'warning',
+        updated: 'info'
+      }
+      return colors[status] || 'info'
+    },
+    formatDateTime (dateStr) {
+      return formatDate(dateStr)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.detail-item {
+  margin-bottom: 12px;
+}
+
+.label {
+  font-weight: 600;
+  color: #666;
+  margin-right: 8px;
+}
+
+.value {
+  color: #333;
+}
+
+.description-text {
+  white-space: pre-wrap;
+  word-break: break-word;
+  line-height: 1.6;
+}
+</style>

+ 94 - 0
src/views/dataService/dataOrder/components/RejectOrderDialog.vue

@@ -0,0 +1,94 @@
+<template>
+  <MDialog
+    :visible="visible"
+    title="驳回订单"
+    widthType="2"
+    @update:visible="handleVisibleChange"
+    @submit="handleSubmit"
+  >
+    <MForm ref="form" :items="formItems" v-model="form"></MForm>
+  </MDialog>
+</template>
+
+<script>
+import MDialog from '@/components/Dialog'
+import MForm from '@/components/MForm'
+import { api } from '@/api/dataOrder'
+
+export default {
+  name: 'RejectOrderDialog',
+  components: {
+    MDialog,
+    MForm
+  },
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    orderId: {
+      type: Number,
+      default: null
+    }
+  },
+  data () {
+    return {
+      form: {
+        reason: ''
+      }
+    }
+  },
+  computed: {
+    formItems () {
+      return [
+        {
+          type: 'textarea',
+          key: 'reason',
+          label: '驳回原因 *',
+          rules: [
+            v => !!v || '请输入驳回原因 *'
+          ],
+          outlined: true
+        }
+      ]
+    }
+  },
+  watch: {
+    visible (val) {
+      if (val) {
+        this.resetForm()
+      }
+    }
+  },
+  methods: {
+    handleVisibleChange (val) {
+      this.$emit('update:visible', val)
+    },
+    resetForm () {
+      this.form.reason = ''
+      this.$nextTick(() => {
+        if (this.$refs.form) {
+          this.$refs.form.resetValidation()
+        }
+      })
+    },
+    async handleSubmit () {
+      if (!this.$refs.form.validate()) {
+        return
+      }
+      if (!this.orderId) {
+        this.$snackbar.error('订单ID不能为空')
+        return
+      }
+      try {
+        await api.rejectOrder(this.orderId, this.form.reason)
+        this.$snackbar.success('订单已驳回')
+        this.$emit('update:visible', false)
+        this.$emit('success')
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    }
+  }
+}
+</script>

+ 187 - 21
src/views/dataService/dataOrder/index.vue

@@ -16,37 +16,99 @@
       @add="handleAdd"
       @pageHandleChange="pageHandleChange"
     >
+      <template #status="{ item }">
+        <v-chip :color="getStatusColor(item.status)" small>{{ item.status_label }}</v-chip>
+      </template>
+      <template #can_connect="{ item }">
+        <v-icon v-if="item.can_connect === true" color="success" small>mdi-check-circle</v-icon>
+        <v-icon v-else-if="item.can_connect === false" color="error" small>mdi-close-circle</v-icon>
+        <span v-else>-</span>
+      </template>
+      <template #created_at="{ item }">
+        <span>{{ formatDateTime(item.created_at) }}</span>
+      </template>
       <template #actions="{ item }">
-        <v-btn text color="success" @click="handleEdit(item)">编辑</v-btn>
-        <v-btn text color="error" @click="handleDelete(item)">删除</v-btn>
+        <v-btn text color="primary" @click="handleDetail(item)">详情</v-btn>
+        <v-btn v-if="item.status === 'processing'" text color="primary" @click="handleComplete(item)">完成</v-btn>
+        <v-btn v-if="item.status === 'pending'" text color="success" @click="handleAnalyze(item)">分析</v-btn>
+        <v-btn v-if="item.status === 'manual_review'" text color="success" @click="handleApprove(item)">审批</v-btn>
+        <v-btn v-if="item.status === 'manual_review'" text color="warning" @click="handleReject(item)">驳回</v-btn>
+        <v-btn v-if="canDelete(item.status)" text color="error" @click="handleDelete(item)">删除</v-btn>
       </template>
     </table-list>
+
+    <!-- 创建订单对话框 -->
+    <create-order-dialog :visible.sync="createDialog.show" @success="handleCreateSuccess" />
+
+    <!-- 订单详情对话框 -->
+    <order-detail-dialog
+      :visible.sync="detailDialog.show"
+      :order-id="detailDialog.orderId"
+      @refresh="init"
+      @analyze="handleAnalyzeFromDetail"
+      @approve="handleApproveFromDetail"
+      @reject="handleRejectFromDetail"
+    />
+
+    <!-- 驳回订单对话框 -->
+    <reject-order-dialog :visible.sync="rejectDialog.show" :order-id="rejectDialog.orderId" @success="init" />
   </div>
 </template>
 
 <script>
 import MFilter from '@/components/Filter'
 import TableList from '@/components/List/table'
+import CreateOrderDialog from './components/CreateOrderDialog'
+import OrderDetailDialog from './components/OrderDetailDialog'
+import RejectOrderDialog from './components/RejectOrderDialog'
+import { api } from '@/api/dataOrder'
+import { formatDate } from '@/utils/date'
 
 export default {
   name: 'dataOrder',
   components: {
     MFilter,
-    TableList
+    TableList,
+    CreateOrderDialog,
+    OrderDetailDialog,
+    RejectOrderDialog
   },
   data () {
     return {
+      api,
       loading: false,
       filter: {
         list: [
-          { type: 'textField', value: '', label: '关键词', key: 'search', placeholder: '搜索订单信息' }
+          { type: 'textField', value: '', label: '关键词', key: 'search', placeholder: '搜索订单标题或描述' },
+          {
+            type: 'select',
+            value: null,
+            label: '状态',
+            key: 'status',
+            items: [
+              { label: '全部', value: null },
+              { label: '待处理', value: 'pending' },
+              { label: '分析中', value: 'analyzing' },
+              { label: '加工中', value: 'processing' },
+              { label: '已完成', value: 'completed' },
+              { label: '已驳回', value: 'rejected' },
+              { label: '待补充', value: 'need_supplement' },
+              { label: '待人工处理', value: 'manual_review' },
+              { label: '已更新', value: 'updated' }
+            ],
+            itemText: 'label',
+            itemValue: 'value'
+          }
         ]
       },
       headers: [
         { text: '订单编号', value: 'order_no' },
-        { text: '订单名称', value: 'order_name' },
+        { text: '标题', value: 'title' },
+        { text: '状态', value: 'status' },
+        { text: '可连通', value: 'can_connect' },
+        { text: '创建人', value: 'created_by' },
         { text: '创建时间', value: 'created_at' },
-        { text: '操作', value: 'actions', align: 'center' }
+        { text: '操作', value: 'actions' }
       ],
       items: [],
       total: 0,
@@ -54,7 +116,18 @@ export default {
         size: 20,
         current: 1
       },
-      query: {}
+      query: {},
+      createDialog: {
+        show: false
+      },
+      detailDialog: {
+        show: false,
+        orderId: null
+      },
+      rejectDialog: {
+        show: false,
+        orderId: null
+      }
     }
   },
   created () {
@@ -75,12 +148,9 @@ export default {
             delete params[key]
           }
         })
-        // TODO: 调用数据订单API
-        // const { data } = await api.getOrders(params)
-        // this.total = data.pagination.total
-        // this.items = data.list
-        this.items = []
-        this.total = 0
+        const { data } = await api.getOrders(params)
+        this.total = data.pagination.total
+        this.items = data.list
       } catch (error) {
         this.$snackbar.error(error)
       } finally {
@@ -97,18 +167,54 @@ export default {
       this.init()
     },
     handleAdd () {
-      // TODO: 新增订单
-      this.$snackbar.info('功能开发中')
+      this.createDialog.show = true
+    },
+    // 订单详情
+    handleDetail (item) {
+      this.detailDialog.orderId = item.id
+      this.detailDialog.show = true
+    },
+    // 分析订单
+    async handleAnalyze (item) {
+      const analyzingItem = item
+      analyzingItem.analyzing = true
+      try {
+        const { data } = await api.analyzeOrder(item.id)
+        if (data.can_connect) {
+          this.$snackbar.success('分析完成,实体可连通!')
+        } else {
+          this.$snackbar.warning('分析完成,部分实体无法连通')
+        }
+        this.init()
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        analyzingItem.analyzing = false
+      }
+    },
+    // 审批订单
+    async handleApprove (item) {
+      try {
+        await this.$confirm('审批确认', `确定要审批通过订单 "${item.title}" 吗?`)
+        await api.approveOrder(item.id)
+        this.$snackbar.success('审批通过')
+        this.init()
+      } catch (error) {
+        if (error !== 'cancel') {
+          this.$snackbar.error(error)
+        }
+      }
     },
-    handleEdit (item) {
-      // TODO: 编辑订单
-      this.$snackbar.info('功能开发中')
+    // 驳回订单
+    handleReject (item) {
+      this.rejectDialog.orderId = item.id
+      this.rejectDialog.show = true
     },
+    // 删除订单
     async handleDelete (item) {
       try {
-        await this.$confirm('删除确认', '确定要删除该订单吗?此操作不可恢复。')
-        // TODO: 调用删除API
-        // await api.deleteOrder(item.id)
+        await this.$confirm('删除确认', `确定要删除订单 "${item.title}" 吗?此操作不可恢复。`)
+        await api.deleteOrder(item.id)
         this.$snackbar.success('删除成功')
         this.init()
       } catch (error) {
@@ -116,6 +222,66 @@ export default {
           this.$snackbar.error(error)
         }
       }
+    },
+    // 创建订单成功
+    handleCreateSuccess () {
+      this.init()
+    },
+    // 分析订单
+    handleAnalyzeFromDetail (orderId) {
+      const item = this.items.find(i => i.id === orderId)
+      if (item) {
+        this.handleAnalyze(item)
+      }
+    },
+    // 审批订单
+    handleApproveFromDetail (orderId) {
+      const item = this.items.find(i => i.id === orderId)
+      if (item) {
+        this.handleApprove(item)
+      }
+    },
+    // 驳回订单
+    handleRejectFromDetail (orderId) {
+      const item = this.items.find(i => i.id === orderId)
+      if (item) {
+        this.handleReject(item)
+      }
+    },
+    // 完成订单
+    async handleComplete (item) {
+      try {
+        await api.completeOrder(item.id, {
+          productId: item.result_product_id,
+          dataflowId: item.result_dataflow_id,
+          processedBy: item.created_by
+        })
+        this.$snackbar.success('操作成功')
+        this.init()
+      } catch (error) {
+        if (error !== 'cancel') {
+          this.$snackbar.error(error)
+        }
+      }
+    },
+    getStatusColor (status) {
+      const colors = {
+        pending: 'info',
+        analyzing: 'warning',
+        processing: 'primary',
+        completed: 'success',
+        rejected: 'error',
+        need_supplement: 'warning',
+        manual_review: 'warning',
+        updated: 'info'
+      }
+      return colors[status] || 'info'
+    },
+    canDelete (status) {
+      return ['pending', 'completed', 'rejected'].includes(status)
+    },
+    formatDateTime (dateStr) {
+      return formatDate(dateStr)
     }
   }
 }