瀏覽代碼

新增数据流脚本获取功能,优化执行代码查看界面;调整数据标签显示逻辑,清理无用代码

Xiao_123 1 周之前
父節點
當前提交
48faf2115a

+ 4 - 0
src/api/dataGovernance.js

@@ -276,6 +276,10 @@ const dataFlow = {
   // AI生成脚本
   getDataFlowScript: (param) => {
     return http.post('/dataflow/create-script', param)
+  },
+  // 获取数据流脚本内容
+  getDataFlowScriptContent: (dataflowId) => {
+    return http.get(`/dataflow/get-script/${dataflowId}`)
   }
 }
 

+ 0 - 7
src/views/dataGovernance/businessDomain/components/editBase.vue

@@ -155,18 +155,11 @@ export default {
           items: this.dataSourceItems
         },
         {
-          // type: 'autocomplete',
           slotName: 'tag',
           key: 'tag',
           value: [],
           slotTitle: '请选择标签',
           slotTitleStyle: 'color: rgba(0, 0, 0, 0.6); padding: 5px'
-          // label: '请选择标签',
-          // multiple: true,
-          // returnObject: true,
-          // itemText: 'name_zh',
-          // itemValue: 'id',
-          // items: this.labelItems
         },
         {
           type: 'text',

+ 1 - 66
src/views/dataGovernance/businessDomain/components/editSelected.vue

@@ -7,12 +7,9 @@
           <thead>
             <tr>
               <th class="text-left" style="background: #eee"></th>
-              <!-- <th class="text-left" style="background: #eee">数据列</th> -->
               <th class="text-left" style="background: #eee">元数据</th>
               <th class="text-left" style="background: #eee">元数据英文名</th>
               <th class="text-left" style="background: #eee">数据类型</th>
-              <!-- <th class="text-left" style="background: #eee">主数据</th> -->
-              <!-- <th class="text-right" style="background: #eee">数据标准</th> -->
             </tr>
           </thead>
           <tbody>
@@ -25,7 +22,6 @@
                   <v-icon>mdi-minus</v-icon>
                 </v-btn>
               </td>
-              <!-- <td>{{ item.text }}</td> -->
               <td>
                 <v-text-field
                   v-model.trim="item.map.name_zh"
@@ -59,22 +55,6 @@
                   class="my-3"
                 ></v-text-field>
               </td>
-              <!-- <td class="text-right">
-                <m-editTable
-                  :text="item?.map?.data_standard?.name_zh || '--'"
-                  :init="standardInit"
-                  item-label="name_zh"
-                  item-value="id"
-                  @use="$el => save($el, item)"
-                >
-                  <template #title>
-                    [{{ item?.map?.name_zh }}] 数据标准选择
-                  </template>
-                </m-editTable>
-                <v-btn color="error" icon v-if="item.map?.data_standard?.id" @click="standardClose(item)">
-                  <v-icon>mdi-close</v-icon>
-                </v-btn>
-              </td> -->
             </tr>
           </tbody>
         </template>
@@ -85,25 +65,8 @@
 </template>
 
 <script>
-// import { api } from '@/api/dataGovernance'
-// import { metadataType } from '@/utils/dataGovernance'
-// import MEditTable from '../../components/editTable.vue'
-const common = {
-  pageInfo: {
-    size: 20,
-    current: 1
-  },
-  total: 0,
-  loading: false,
-  items: [],
-  value: null,
-  search: null
-}
 export default {
   name: 'edit-selected',
-  // components: {
-  //   MEditTable
-  // },
   props: {
     value: {
       type: Array,
@@ -112,17 +75,7 @@ export default {
   },
   data () {
     return {
-      height: 0,
-      standard: {
-        ...common
-      },
-      metadata: {
-        ...common
-      },
-      type: {
-        ...common
-      },
-      openFn: undefined
+      height: 0
     }
   },
   mounted () {
@@ -131,24 +84,6 @@ export default {
     })
   },
   methods: {
-    // standardInit (query) {
-    //   return new Promise((resolve, reject) => {
-    //     api.dataStandardList(query).then(({ data }) => {
-    //       resolve({
-    //         items: data.records,
-    //         total: data.total
-    //       })
-    //     }).catch(error => {
-    //       reject(error)
-    //     })
-    //   })
-    // },
-    // save (data, item) {
-    //   item.map.data_standard = { id: data?.id, name_zh: data?.name_zh }
-    // },
-    // standardClose (item) {
-    //   item.map.data_standard = null
-    // },
     handleClose (index) {
       const items = [...this.value]
       items.splice(index, 1)

+ 84 - 18
src/views/dataGovernance/dataProcess/components/edit.vue

@@ -120,19 +120,13 @@
       right
       width="800"
     >
-      <div>
+      <div class="d-flex flex-column" style="height: 100%;">
         <v-banner single-line>
           <div class="py-2 title">执行代码查看</div>
         </v-banner>
-        <v-textarea
-          class="pa-3"
-          v-model="scriptContent"
-          outlined
-          hide-details
-          label="执行代码"
-          :rows="10"
-          dense
-        ></v-textarea>
+        <div class="pa-3 flex-grow-1 overflow-y-auto">
+          <div class="code-preview" v-html="highlightedCode"></div>
+        </div>
       </div>
     </v-navigation-drawer>
   </div>
@@ -141,6 +135,9 @@
 <script>
 import EditBase from './editBase.vue'
 import { api } from '@/api/dataGovernance'
+import hljs from 'highlight.js'
+import 'highlight.js/styles/monokai.css'
+
 export default {
   name: 'editPage',
   components: {
@@ -166,6 +163,22 @@ export default {
       }
     }
   },
+  computed: {
+    highlightedCode () {
+      if (!this.scriptContent) {
+        return ''
+      }
+      try {
+        // highlight.js 高亮代码,尝试自动检测语言
+        const result = hljs.highlightAuto(this.scriptContent)
+        return `<pre><code class="hljs ${result.language || ''}">${result.value}</code></pre>`
+      } catch (error) {
+        console.error('代码高亮错误:', error)
+        // 如果高亮失败,返回转义后的纯文本
+        return `<pre><code>${this.escapeHtml(this.scriptContent)}</code></pre>`
+      }
+    }
+  },
   async created () {
     this.loading = true
     await this.getList()
@@ -206,14 +219,27 @@ export default {
       }
     },
     // 查看执行代码
-    handleViewCode () {
-      // 根据code去接口取执行代码展示
-      console.log(this.changeObj, 'changeObj')
-      // if (!this.changeObj.script_content) {
-      //   this.$snackbar.warning('暂无执行代码可查看')
-      //   return
-      // }
-      // this.drawer = true
+    async handleViewCode () {
+      if (!this.itemData || !this.itemData.id) {
+        this.$snackbar.error('缺少数据流ID')
+        return
+      }
+
+      this.scriptContent = ''
+      this.loading = true
+      try {
+        const { data } = await api.getDataFlowScriptContent(this.itemData.id)
+        if (!data?.script_content) {
+          this.$snackbar.warning('脚本内容为空')
+          return
+        }
+        this.scriptContent = data?.script_content || ''
+        this.drawer = true
+      } catch (error) {
+        this.$snackbar.error(error.message || '获取脚本失败')
+      } finally {
+        this.loading = false
+      }
     },
     async handleSubmit () {
       const params = this.$refs.editBaseRefs.getValue()
@@ -236,6 +262,11 @@ export default {
       } catch (error) {
         this.$snackbar.error(error)
       }
+    },
+    escapeHtml (text) {
+      const div = document.createElement('div')
+      div.textContent = text
+      return div.innerHTML
     }
   }
 }
@@ -264,4 +295,39 @@ export default {
 ::v-deep ul {
   padding-left: 0;
 }
+.code-preview {
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  padding: 16px;
+  font-family: 'Courier New', Courier, monospace;
+  font-size: 14px;
+  line-height: 1.5;
+
+  ::v-deep pre {
+    margin: 0;
+    padding: 0;
+    background: transparent;
+    border: none;
+    font-family: inherit;
+    font-size: inherit;
+    line-height: inherit;
+    height: 100%;
+  }
+
+  ::v-deep code {
+    display: block;
+    width: 100%;
+    padding: 0;
+    margin: 0;
+    background: transparent;
+    border: none;
+    font-family: inherit;
+    font-size: inherit;
+    line-height: inherit;
+    white-space: pre;
+    overflow-wrap: normal;
+    overflow-x: auto;
+  }
+}
 </style>

+ 4 - 4
src/views/dataReview/components/change.vue

@@ -70,8 +70,8 @@ export default {
       const newMeta = this.info.new_meta || {}
       const oldMeta = this.info.old_meta.snapshot || {}
 
-      const newTagIds = newMeta.tag_ids && newMeta.tag_ids.length ? newMeta.tag_ids.map(tag => tag.name_zh).join(', ') : '-'
-      const oldTagIds = oldMeta.tag_ids && oldMeta.tag_ids.length ? oldMeta.tag_ids.map(tag => tag.name_zh).join(', ') : '-'
+      const newTags = newMeta.tags && newMeta.tags.length ? newMeta.tags.map(tag => tag.name_zh).join(', ') : '-'
+      const oldTags = oldMeta.tags && oldMeta.tags.length ? oldMeta.tags.map(tag => tag.name_zh).join(', ') : '-'
       return [
         {
           title: '中文名',
@@ -90,8 +90,8 @@ export default {
         },
         {
           title: '数据标签',
-          new: newTagIds,
-          old: oldTagIds
+          new: newTags,
+          old: oldTags
         }
       ]
     }

+ 2 - 2
src/views/dataReview/components/redundancy.vue

@@ -17,8 +17,8 @@
               <li>{{ newMetaSnapshot.name_en || '-' }}</li>
               <li>{{ newMetaSnapshot.data_type || '-' }}</li>
               <li>
-                <span v-if="Array.isArray(newMetaSnapshot.tag_ids) && newMetaSnapshot.tag_ids.length">
-                  {{ newMetaSnapshot.tag_ids.map(tag => tag.name_zh).join(', ') }}
+                <span v-if="Array.isArray(newMetaSnapshot.tags) && newMetaSnapshot.tags.length">
+                  {{ newMetaSnapshot.tags.map(tag => tag.name_zh).join(', ') }}
                 </span>
                 <span v-else>-</span>
               </li>

+ 23 - 51
src/views/dataService/dataProduct/components/PreviewDialog.vue

@@ -186,26 +186,11 @@ export default {
     }
   },
   methods: {
-    handleColumnChange () {
-      // 列选择变化时的处理
-      this.$emit('column-change', this.selectedColumns)
-    },
-    handleLoadMore () {
-      this.$emit('load-more')
-    },
-    handleDownload () {
-      this.$emit('download')
-    },
-    handleVisibleChange (val) {
-      this.$emit('update:visible', val)
-    },
-    handleClose () {
-      this.$emit('update:visible', false)
-    },
     // 数据可视化
     async handleVisualize (row) {
+      const sampleData = { ...row, target_table: this.previewData.product.target_table }
       try {
-        const { data } = await api.getVisualizeData(this.previewData.product.id, { sample_data: row })
+        const { data } = await api.getVisualizeData(this.previewData.product.id, { sample_data: sampleData })
         this.graphData = data
         this.graphDialogVisible = true
       } catch (error) {
@@ -222,22 +207,7 @@ export default {
       //       labels: ['BusinessDomain'],
       //       is_target: true,
       //       is_source: false,
-      //       matched_fields: [
-      //         {
-      //           field_name: '用户ID',
-      //           field_name_en: 'user_id',
-      //           data_type: 'integer',
-      //           value: 12345,
-      //           meta_id: 234
-      //         },
-      //         {
-      //           field_name: '姓名',
-      //           field_name_en: 'name',
-      //           data_type: 'string',
-      //           value: '张三',
-      //           meta_id: 235
-      //         }
-      //       ]
+      //       matched_data: [row, row, row, row, row, row, row, row, row, row, row, row, row, row]
       //     },
       //     {
       //       id: 183,
@@ -256,15 +226,7 @@ export default {
       //       labels: ['BusinessDomain'],
       //       is_target: false,
       //       is_source: false,
-      //       matched_fields: [
-      //         {
-      //           field_name: '用户ID',
-      //           field_name_en: 'user_id',
-      //           data_type: 'integer',
-      //           value: 12345,
-      //           meta_id: 234
-      //         }
-      //       ]
+      //       matched_data: [row]
       //     },
       //     {
       //       id: 154,
@@ -274,15 +236,7 @@ export default {
       //       labels: ['DataResource', 'BusinessDomain'],
       //       is_target: false,
       //       is_source: true,
-      //       matched_fields: [
-      //         {
-      //           field_name: '用户ID',
-      //           field_name_en: 'user_id',
-      //           data_type: 'integer',
-      //           value: 12345,
-      //           meta_id: 234
-      //         }
-      //       ]
+      //       matched_data: row
       //     }
       //   ],
       //   lines: [
@@ -293,6 +247,24 @@ export default {
       //   ],
       //   lineage_depth: 2
       // }
+      // this.graphData = data
+      // this.graphDialogVisible = true
+    },
+    handleColumnChange () {
+      // 列选择变化时的处理
+      this.$emit('column-change', this.selectedColumns)
+    },
+    handleLoadMore () {
+      this.$emit('load-more')
+    },
+    handleDownload () {
+      this.$emit('download')
+    },
+    handleVisibleChange (val) {
+      this.$emit('update:visible', val)
+    },
+    handleClose () {
+      this.$emit('update:visible', false)
     },
     handleGraphDialogVisibleChange (val) {
       this.graphDialogVisible = val

+ 135 - 28
src/views/dataService/dataProduct/components/mGraph.vue

@@ -18,7 +18,7 @@
       </div>
     </div>
     <MEmpty v-if="empty"></MEmpty>
-    <relation-graph
+    <RelationGraph
       v-show="!empty"
       ref="graphRef"
       :options="graphOptions"
@@ -26,9 +26,9 @@
       :on-line-click="onLineClick"
     >
       <template #node="{node}">
-        <v-tooltip left :color="node.color || 'primary'">
-          <template v-slot:activator="{ on, attrs }">
-            <div @contextmenu.prevent="handleContextmenu($event, node)" v-bind="attrs" v-on="on">
+        <!-- <v-tooltip left :color="node.color || 'primary'"> -->
+          <!-- <template v-slot:activator="{ on, attrs }"> -->
+            <div @contextmenu.prevent="handleContextmenu($event, node)" style="cursor: pointer;">
               <div
                 :style="{ width: node.width + 'px', height: node.height + 'px' }"
                 :class="{ 'node-active':  menu.activeId === +node.id }"
@@ -43,9 +43,9 @@
                 {{ node.text || '' }}
               </div>
             </div>
-          </template>
+          <!-- </template> -->
           <!-- 浮窗展示节点详情 -->
-          <div class="d-flex">
+          <!-- <div class="d-flex">
             <ul style="color: #fff;text-align:right;">
               <template v-for="key in Object.keys(detailKeys)">
                 <li v-if="node.data[key]" :key="key">{{ detailKeys[key] }} :</li>
@@ -55,13 +55,32 @@
               <template v-for="key in Object.keys(detailKeys)">
                 <li v-if="node.data[key]" :key="key">
                   <span v-if="key === 'labels'">{{ node.data[key].join(',') }}</span>
-                  <span v-else-if="key === 'matched_fields'">{{ node.data[key].map(item => item.field_name).join(',') }}</span>
+                  <span v-else-if="key === 'matched_data'">
+                    <v-simple-table dense v-if="getMatchDataArray(node.data[key]) && getMatchDataArray(node.data[key]).length">
+                      <template v-slot:default>
+                        <thead>
+                          <tr>
+                            <th v-for="header in getMatchDataHeaders(node.data[key])" :key="header.value" class="text-left">
+                              {{ header.text }}
+                            </th>
+                          </tr>
+                        </thead>
+                        <tbody>
+                          <tr v-for="(item, index) in getMatchDataArray(node.data[key])" :key="index">
+                            <td v-for="header in getMatchDataHeaders(node.data[key])" :key="header.value">
+                              {{ item[header.value] }}
+                            </td>
+                          </tr>
+                        </tbody>
+                      </template>
+                    </v-simple-table>
+                  </span>
                   <span v-else>{{ node.data[key] }}</span>
                 </li>
               </template>
             </ul>
-          </div>
-        </v-tooltip>
+          </div> -->
+        <!-- </v-tooltip> -->
       </template>
       <template #graph-plug>
         <v-menu v-model="menu.show" attach :position-x="menu.x" :position-y="menu.y" absolute offset-y min-width="200">
@@ -72,7 +91,84 @@
           </v-list>
         </v-menu>
       </template>
-    </relation-graph>
+    </RelationGraph>
+
+    <!-- 右侧抽屉展示节点详情 -->
+    <v-navigation-drawer
+      v-model="drawer.show"
+      fixed
+      temporary
+      right
+      width="50%"
+      style="z-index: 300;"
+    >
+      <template v-slot:prepend>
+        <v-toolbar color="primary" dark>
+          <v-toolbar-title>节点详情</v-toolbar-title>
+          <v-spacer></v-spacer>
+          <v-btn icon @click="drawer.show = false">
+            <v-icon>mdi-close</v-icon>
+          </v-btn>
+        </v-toolbar>
+      </template>
+      <v-container v-if="drawer.nodeData">
+        <div class="d-flex">
+          <ul class="ml-0 pl-0" style="color: #333;text-align:right; padding-right: 16px; min-width: 120px;">
+            <template v-for="key in Object.keys(detailKeys)">
+              <li v-if="drawer.nodeData[key]" :key="key" style="margin-bottom: 12px;">{{ detailKeys[key] }} :</li>
+            </template>
+          </ul>
+          <ul style="flex: 1; overflow-x: auto;">
+            <template v-for="key in Object.keys(detailKeys)">
+              <li v-if="drawer.nodeData[key]" :key="key" style="margin-bottom: 12px;">
+                <span v-if="key === 'labels'">{{ drawer.nodeData[key].join(',') }}</span>
+                <span v-else-if="key === 'matched_data'">
+                  <v-simple-table fixed-header height="60vh" v-if="getMatchDataArray(drawer.nodeData[key]) && getMatchDataArray(drawer.nodeData[key]).length">
+                    <template v-slot:default>
+                      <thead>
+                        <tr>
+                          <th v-for="header in getMatchDataHeaders(drawer.nodeData[key])" :key="header.value" class="text-left">
+                            {{ header.text }}
+                          </th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        <tr v-for="(item, index) in getMatchDataArray(drawer.nodeData[key])" :key="index">
+                          <td v-for="header in getMatchDataHeaders(drawer.nodeData[key])" :key="header.value">
+                            {{ item[header.value] }}
+                          </td>
+                        </tr>
+                      </tbody>
+                    </template>
+                  </v-simple-table>
+                  <!-- <v-data-iterator
+                    :items="getMatchDataArray(drawer.nodeData[key])"
+                    hide-default-footer
+                    hide-default-header
+                  >
+                    <template v-slot:default="props">
+                      <v-row>
+                        <v-col v-for="item in props.items" :key="item.id" cols="6">
+                          <v-card elevation="3">
+                            <v-list dense>
+                              <v-list-item v-for="(key, index) in Object.keys(item)" :key="index">
+                                <v-list-item-content>{{ key }}:</v-list-item-content>
+                                <v-list-item-content class="align-end">{{ item[key] }}</v-list-item-content>
+                              </v-list-item>
+                            </v-list>
+                          </v-card>
+                        </v-col>
+                      </v-row>
+                    </template>
+                  </v-data-iterator> -->
+                </span>
+                <span v-else>{{ drawer.nodeData[key] }}</span>
+              </li>
+            </template>
+          </ul>
+        </div>
+      </v-container>
+    </v-navigation-drawer>
 
   </div>
 </template>
@@ -85,21 +181,6 @@ const NODES_SIZE = {
   width: 30,
   height: 30
 }
-// const LINE_COLOR_MAP = {
-//   包含: '#F44336', // red
-//   影响: '#E91E63', // pink
-//   依赖: '#3F51B5', // indigo
-//   来源: '#4CAF50', // green
-//   引用: '#9C27B0', // purple
-//   继承: '#673AB7', // deep-purple
-//   标记: '#2196F3', // blue
-//   使用: '#03A9F4', // light-blue
-//   关联: '#00BCD4', // cyan
-//   拥有: '#009688', // teal
-//   下级: '#8BC34A', // light-green
-//   上级: '#CDDC39', // lime
-//   联动: '#FF9800' // orange
-// }
 export default {
   name: 'details-graph',
   components: { RelationGraph, MEmpty },
@@ -121,6 +202,10 @@ export default {
         activeId: +this.$route.params.id,
         item: {}
       },
+      drawer: {
+        show: false,
+        nodeData: null
+      },
       empty: true,
       loading: false,
       graphOptions: {
@@ -233,14 +318,14 @@ export default {
       },
       // 节点详情
       detailKeys: {
-        id: '节点ID',
+        // id: '节点ID',
         name_zh: '中文名称',
         name_en: '英文名称',
         node_type: '节点类型',
         labels: '节点标签',
         is_target: '是否目标节点',
         is_source: '是否源节点',
-        matched_fields: '匹配字段'
+        matched_data: '匹配数据'
       }
     }
   },
@@ -331,11 +416,33 @@ export default {
         this.$snackbar.error(error)
       }
     },
+    // 节点点击
     onNodeClick (nodeObject, $event) {
-      console.log('onNodeClick:', nodeObject)
+      // 设置节点数据并打开抽屉
+      this.drawer.nodeData = nodeObject.data
+      this.drawer.show = true
     },
     onLineClick (lineObject, $event) {
       console.log('onLineClick:', lineObject)
+    },
+    // 获取匹配数据的表头
+    getMatchDataHeaders (matchData) {
+      if (!matchData) return []
+      // 统一转换为数组格式
+      const dataArray = Array.isArray(matchData) ? matchData : [matchData]
+      if (dataArray.length === 0) return []
+      // 从第一个对象中提取所有 key 作为表头
+      const firstItem = dataArray[0]
+      const headers = Object.keys(firstItem).map(key => ({
+        text: key,
+        value: key
+      }))
+      return headers
+    },
+    // 将匹配数据统一转换为数组格式
+    getMatchDataArray (matchData) {
+      if (!matchData) return []
+      return Array.isArray(matchData) ? matchData : [matchData]
     }
   }
 }