Parcourir la source

业务域:DDL解析

Xiao_123 il y a 4 heures
Parent
commit
f2c469c819

+ 61 - 6
src/api/dataGovernance.js

@@ -60,11 +60,11 @@ const dataResource = {
   uploadResource: (param) => {
     return http.upload('/meta/resource/upload', param)
   },
-  // 获取数据资源文件流  未知
+  // 获取数据资源文件流
   getResourceFile: (param) => {
     return http.getDownload('/meta/resource/download', param)
   },
-  // 解析非结构化文本展示信息  未知
+  // 解析非结构化文本展示信息
   getUnstructured: (param) => {
     return http.post('/text/resource/translate', param)
   },
@@ -122,7 +122,7 @@ const dataModel = {
     return http.post('/model/search', param)
     // return http.post('/id/data/search', param)
   },
-  // 新增模型:模型选择模型 未知
+  // 新增模型:模型选择模型
   addModelByModel: (param) => {
     return http.post('/model/data/model/add', param)
   },
@@ -307,12 +307,64 @@ const LLM = {
   }
 }
 
+// 业务域
+const businessDomain = {
+  // 获取业务域列表
+  getBusinessDomainList: (param) => {
+    return http.post('/bd/list', param)
+  },
+  // 获取业务域详情
+  getBusinessDomainDetail: (param) => {
+    return http.post('/bd/detail', param)
+  },
+  // 删除业务域
+  deleteBusinessDomain: (param) => {
+    return http.post('/bd/delete', param)
+  },
+  // 保存业务域
+  saveBusinessDomain: (param) => {
+    return http.post('/bd/save', param)
+  },
+  // 更新业务域
+  updateBusinessDomain: (param) => {
+    return http.post('/bd/update', param)
+  },
+  // 上传文件
+  uploadFile: (param) => {
+    return http.upload('/bd/upload', param)
+  },
+  // 下载文件
+  downloadFile: (param) => {
+    return http.getDownload('/bd/download', param)
+  },
+  // 获取关系图谱
+  getBusinessDomainGraph: (param) => {
+    return http.post('/bd/graphall', param)
+  },
+  // 解析DDL文件
+  parseDDLFile: (param) => {
+    return http.upload('/bd/ddlparse', param)
+  },
+  // 搜索关联元数据
+  searchAssociatedMetadata: (param) => {
+    return http.post('/bd/search', param)
+  },
+  // 组合创建业务领域
+  combinationCreateBusinessDomain: (param) => {
+    return http.post('/bd/compose', param)
+  },
+  // 获取标签列表
+  getLabelList: (param) => {
+    return http.post('/bd/labellist', param)
+  }
+}
+
 const other = {
-// 资源 & 模型 列表 未接
+// 资源 & 模型 列表
   getResourceAndModelList: (param) => {
     return http.post('/resource/model/list', param)
   },
-  // 获取资源ddl 未接
+  // 获取资源ddl
   getDDL: (param) => {
     return http.post('/id/data/ddl', param)
   },
@@ -338,7 +390,7 @@ const other = {
     return http.post('/graph/meta/include', param)
   },
   // 获取业务域列表
-  getBusinessDomainList: () => {
+  getBusinessDomainList2: () => {
     return http.get('/dataflow/get-BD-list')
   }
 }
@@ -364,6 +416,9 @@ export const api = {
   // 数据流程
   ...dataFlow,
 
+  // 业务域
+  ...businessDomain,
+
   // 大语言模型对话接口
   ...LLM,
 

+ 145 - 102
src/router/routes.js

@@ -349,7 +349,7 @@ export default {
       icon: 'mdi-clipboard-text-outline',
       remark: '',
       type: 0,
-      title: '数据治理',
+      title: '数据地图',
       local: '',
       path: '/data-governance',
       urls: '',
@@ -405,127 +405,170 @@ export default {
           icon: 'mdi-book-open-outline',
           remark: '',
           type: 1,
-          title: '数据资源',
-          local: '',
-          path: '/data-governance/data-resource',
-          urls: '',
-          children: [],
-          enName: 'Data Resource',
-          id: 1189,
-          redirect: '',
-          level: 2,
-          openPath: '',
-          active: '',
-          label: '数据资源',
-          sort: 1,
-          parentId: 1187,
-          effectiveStatus: true,
-          parentName: 'data-governance',
-          component: 'dataGovernance/dataResource',
-          meta: {
-            keepAlive: false,
-            allowClick: false,
-            roles: [],
-            enName: 'Data Resource',
-            icon: 'mdi-book-open-outline',
-            editModules: false,
-            title: '数据资源',
-            fullScreen: false,
-            target: false,
-            effectiveStatus: true
-          },
-          name: 'data-resource',
-          style: '',
-          alwaysShow: 0,
-          metastr: '{"keepAlive":false,"allowClick":false,"enName":"Data Resource","editModules":false,"title":"数据资源","fullScreen":false,"target":false}',
-          open: null
-        },
-        {
-          meun: '',
-          code: '',
-          hidden: 0,
-          rootId: 1187,
-          icon: '',
-          remark: '',
-          type: 1,
-          title: '数据模型',
+          title: '业务域',
           local: '',
-          path: '/data-governance/data-modules',
+          path: '/data-governance/business-domain',
           urls: '',
           children: [],
-          enName: 'Data Field',
-          id: 1193,
+          enName: 'Business Domain',
+          id: 1188,
           redirect: '',
           level: 2,
           openPath: '',
           active: '',
-          label: '数据模型',
-          sort: 4,
+          label: '业务域',
+          sort: 0,
           parentId: 1187,
           effectiveStatus: true,
           parentName: 'data-governance',
-          component: 'dataGovernance/dataModules',
+          component: 'dataGovernance/businessDomain',
           meta: {
             keepAlive: false,
             allowClick: true,
             roles: [],
-            enName: 'Data Field',
-            icon: '',
-            editModules: false,
-            title: '数据模型',
-            fullScreen: false,
-            target: false,
-            effectiveStatus: true
-          },
-          name: 'data-modules',
-          style: '',
-          alwaysShow: 0,
-          metastr: '{"keepAlive":false,"allowClick":true,"enName":"Data Field","editModules":false,"title":"数据模型","fullScreen":false,"target":false}',
-          open: null
-        },
-        {
-          meun: '',
-          code: '',
-          hidden: 0,
-          rootId: 1187,
-          icon: '',
-          remark: '',
-          type: 1,
-          title: '数据指标',
-          local: '',
-          path: '/data-governance/data-indicator',
-          urls: '',
-          children: [],
-          enName: 'Data Indicator',
-          id: 1190,
-          redirect: '',
-          level: 2,
-          openPath: '',
-          active: '',
-          label: '数据指标',
-          sort: 5,
-          parentId: 1187,
-          effectiveStatus: true,
-          parentName: 'data-governance',
-          component: 'dataGovernance/dataIndicator',
-          meta: {
-            keepAlive: false,
-            allowClick: false,
-            roles: [],
-            enName: 'Data Indicator',
-            icon: '',
+            enName: 'Business Domain',
+            icon: 'mdi-book-open-outline',
             editModules: false,
-            title: '数据指标',
+            title: '业务域',
             fullScreen: false,
             target: false,
             effectiveStatus: true
           },
-          name: 'data-indicator',
+          name: 'business-domain',
           style: '',
           alwaysShow: 0,
-          metastr: '{"keepAlive":false,"allowClick":false,"enName":"Data Indicator","editModules":false,"title":"数据指标","fullScreen":false,"target":false}',
+          metastr: '{"keepAlive":false,"allowClick":true,"enName":"Business Domain","editModules":false,"title":"业务域","fullScreen":false,"target":false}',
           open: null
         },
+        // {
+        //   meun: '',
+        //   code: '',
+        //   hidden: 0,
+        //   rootId: 1187,
+        //   icon: 'mdi-book-open-outline',
+        //   remark: '',
+        //   type: 1,
+        //   title: '数据资源',
+        //   local: '',
+        //   path: '/data-governance/data-resource',
+        //   urls: '',
+        //   children: [],
+        //   enName: 'Data Resource',
+        //   id: 1189,
+        //   redirect: '',
+        //   level: 2,
+        //   openPath: '',
+        //   active: '',
+        //   label: '数据资源',
+        //   sort: 1,
+        //   parentId: 1187,
+        //   effectiveStatus: true,
+        //   parentName: 'data-governance',
+        //   component: 'dataGovernance/dataResource',
+        //   meta: {
+        //     keepAlive: false,
+        //     allowClick: false,
+        //     roles: [],
+        //     enName: 'Data Resource',
+        //     icon: 'mdi-book-open-outline',
+        //     editModules: false,
+        //     title: '数据资源',
+        //     fullScreen: false,
+        //     target: false,
+        //     effectiveStatus: true
+        //   },
+        //   name: 'data-resource',
+        //   style: '',
+        //   alwaysShow: 0,
+        //   metastr: '{"keepAlive":false,"allowClick":false,"enName":"Data Resource","editModules":false,"title":"数据资源","fullScreen":false,"target":false}',
+        //   open: null
+        // },
+        // {
+        //   meun: '',
+        //   code: '',
+        //   hidden: 0,
+        //   rootId: 1187,
+        //   icon: '',
+        //   remark: '',
+        //   type: 1,
+        //   title: '数据模型',
+        //   local: '',
+        //   path: '/data-governance/data-modules',
+        //   urls: '',
+        //   children: [],
+        //   enName: 'Data Field',
+        //   id: 1193,
+        //   redirect: '',
+        //   level: 2,
+        //   openPath: '',
+        //   active: '',
+        //   label: '数据模型',
+        //   sort: 4,
+        //   parentId: 1187,
+        //   effectiveStatus: true,
+        //   parentName: 'data-governance',
+        //   component: 'dataGovernance/dataModules',
+        //   meta: {
+        //     keepAlive: false,
+        //     allowClick: true,
+        //     roles: [],
+        //     enName: 'Data Field',
+        //     icon: '',
+        //     editModules: false,
+        //     title: '数据模型',
+        //     fullScreen: false,
+        //     target: false,
+        //     effectiveStatus: true
+        //   },
+        //   name: 'data-modules',
+        //   style: '',
+        //   alwaysShow: 0,
+        //   metastr: '{"keepAlive":false,"allowClick":true,"enName":"Data Field","editModules":false,"title":"数据模型","fullScreen":false,"target":false}',
+        //   open: null
+        // },
+        // {
+        //   meun: '',
+        //   code: '',
+        //   hidden: 0,
+        //   rootId: 1187,
+        //   icon: '',
+        //   remark: '',
+        //   type: 1,
+        //   title: '数据指标',
+        //   local: '',
+        //   path: '/data-governance/data-indicator',
+        //   urls: '',
+        //   children: [],
+        //   enName: 'Data Indicator',
+        //   id: 1190,
+        //   redirect: '',
+        //   level: 2,
+        //   openPath: '',
+        //   active: '',
+        //   label: '数据指标',
+        //   sort: 5,
+        //   parentId: 1187,
+        //   effectiveStatus: true,
+        //   parentName: 'data-governance',
+        //   component: 'dataGovernance/dataIndicator',
+        //   meta: {
+        //     keepAlive: false,
+        //     allowClick: false,
+        //     roles: [],
+        //     enName: 'Data Indicator',
+        //     icon: '',
+        //     editModules: false,
+        //     title: '数据指标',
+        //     fullScreen: false,
+        //     target: false,
+        //     effectiveStatus: true
+        //   },
+        //   name: 'data-indicator',
+        //   style: '',
+        //   alwaysShow: 0,
+        //   metastr: '{"keepAlive":false,"allowClick":false,"enName":"Data Indicator","editModules":false,"title":"数据指标","fullScreen":false,"target":false}',
+        //   open: null
+        // },
         {
           meun: '',
           code: '',
@@ -662,7 +705,7 @@ export default {
       level: 1,
       openPath: '',
       active: '',
-      label: '数据治理',
+      label: '数据地图',
       sort: 44,
       parentId: 0,
       effectiveStatus: true,
@@ -673,14 +716,14 @@ export default {
         roles: [],
         enName: 'Data Governance',
         icon: 'mdi-clipboard-text-outline',
-        title: '数据治理',
+        title: '数据地图',
         target: false,
         effectiveStatus: true
       },
       name: 'data-governance',
       style: '',
       alwaysShow: 0,
-      metastr: '{"allowClick":false,"enName":"Data Governance","title":"数据治理","target":false}',
+      metastr: '{"allowClick":false,"enName":"Data Governance","title":"数据地图","target":false}',
       open: null
     },
     {

+ 153 - 0
src/views/dataGovernance/businessDomain/components/ddl/index.vue

@@ -0,0 +1,153 @@
+<template>
+  <div>
+    <div class="pa-3">
+      <v-text-field
+        v-model="name"
+        label="名称查找"
+        placeholder="请输入名称"
+        dense
+        hide-details
+        outlined
+        clearable
+        append-icon="mdi-magnify"
+        @input="handleSearch"
+        @click:append="handleSearch"
+        @keydown.enter.native="handleSearch"
+      ></v-text-field>
+      <div class="pt-3 pl-3 d-flex justify-end">
+        共 {{ Object.keys(filteredItems).length }} 个数据
+        <span v-if="Object.keys(filteredItems).length === 0 && Object.keys(origin).length > 0" class="ml-2 error--text">
+          (数据已加载但未显示,请检查控制台)
+        </span>
+      </div>
+    </div>
+    <div>
+      <v-expansion-panels>
+        <v-expansion-panel
+          v-for="(item, key) in filteredItems"
+          :key="key"
+        >
+          <v-expansion-panel-header>
+            <div class="d-flex justify-space-between align-center">
+              <div>
+                <v-chip color="error" small v-if="item.exist">已存在</v-chip>
+                {{ key }}
+              </div>
+              <div>
+                <v-btn
+                  :disabled="select === key"
+                  text
+                  color="primary"
+                  small
+                  @click.stop="handleSelect(item, key)">使用该资源</v-btn>
+              </div>
+            </div>
+          </v-expansion-panel-header>
+          <v-expansion-panel-content>
+            <v-simple-table
+              fixed-header
+              height="400px"
+            >
+              <template v-slot:default>
+                <thead>
+                  <tr>
+                    <th class="text-left">
+                      元数据中文名
+                    </th>
+                    <th class="text-left">
+                      元数据英文名
+                    </th>
+                    <th class="text-left">
+                      元数据类型
+                    </th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr v-for="val in item.meta" :key="key + val.map.name_zh">
+                    <td>{{ val.map.name_zh }}</td>
+                    <td>{{ val.map.name_en }}</td>
+                    <td>{{ val.map.data_type }}</td>
+                  </tr>
+                </tbody>
+              </template>
+            </v-simple-table>
+          </v-expansion-panel-content>
+        </v-expansion-panel>
+      </v-expansion-panels>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'DDL',
+  props: {
+    items: {
+      type: Object,
+      default: () => ({})
+    },
+    origin: {
+      type: Object,
+      default: () => ({})
+    },
+    select: {
+      type: String,
+      default: null
+    }
+  },
+  data () {
+    return {
+      name: null,
+      filteredItems: {}
+    }
+  },
+  watch: {
+    origin: {
+      immediate: true,
+      deep: true,
+      handler (val) {
+        if (val && Object.keys(val).length) {
+          if (!this.name) {
+            // 使用深拷贝确保响应式更新
+            this.filteredItems = JSON.parse(JSON.stringify(val))
+          } else {
+            // 如果有搜索条件,重新执行搜索
+            this.$nextTick(() => {
+              this.handleSearch()
+            })
+          }
+        } else {
+          this.filteredItems = {}
+        }
+      }
+    }
+  },
+  methods: {
+    handleSearch () {
+      if (!this.name) {
+        // 使用深拷贝确保响应式更新
+        this.filteredItems = this.origin && Object.keys(this.origin).length
+          ? JSON.parse(JSON.stringify(this.origin))
+          : {}
+        return
+      }
+      if (!this.origin || !Object.keys(this.origin).length) {
+        this.filteredItems = {}
+        return
+      }
+      this.filteredItems = Object.keys(this.origin)
+        .filter(e => e.includes(this.name))
+        .reduce((res, key) => {
+          res[key] = this.origin[key]
+          return res
+        }, {})
+    },
+    handleSelect (item, key) {
+      this.$emit('select', item, key)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 457 - 0
src/views/dataGovernance/businessDomain/components/edit copy.vue

@@ -0,0 +1,457 @@
+<template>
+  <div class="db">
+    <v-card class="db-box d-flex flex-column" elevation="5" v-loading="loadingUpload">
+      <v-banner single-line>
+        <div class="d-flex align-center">
+          <!-- <div class="py-2 title">DDL解析</div> -->
+          <v-tabs v-model="uploadTab" @change="handleTabChange">
+            <v-tab>DDL解析</v-tab>
+            <v-tab>Excel解析</v-tab>
+            <v-tab>文本解析</v-tab>
+          </v-tabs>
+          <v-spacer></v-spacer>
+          <upload-btn
+            text
+            color="primary"
+            @change="handleChangeFile"
+          >
+            <v-icon left>mdi-import</v-icon>
+            {{ uploadBtnText }}
+          </upload-btn>
+        </div>
+      </v-banner>
+      <v-tabs-items v-model="uploadTab">
+        <!-- DDL解析 -->
+        <v-tab-item>
+          <div class="pa-3">
+            <v-text-field
+              v-model="name"
+              label="名称查找"
+              placeholder="请输入名称"
+              dense
+              hide-details
+              outlined
+              clearable
+              append-icon="mdi-magnify"
+              @input="handleSearch"
+              @click:append="handleSearch"
+              @keydown.enter.native="handleSearch"
+            ></v-text-field>
+            <div class="pt-3 pl-3 d-flex justify-end">
+              共 {{ Object.keys(items).length }} 个数据
+            </div>
+          </div>
+          <div class="scrollBox">
+            <v-expansion-panels>
+              <v-expansion-panel
+                v-for="(item, key) in items"
+                :key="key"
+              >
+                <v-expansion-panel-header>
+                  <div class="d-flex justify-space-between align-center">
+                    <div>
+                      <v-chip color="error" small v-if="item.exist">已存在</v-chip>
+                      {{ key }}
+                    </div>
+                    <div>
+                      <v-btn
+                        :disabled="select === key"
+                        text
+                        color="primary"
+                        small
+                        @click.stop="handleSelect(item, key)">使用该资源</v-btn>
+                    </div>
+                  </div>
+                </v-expansion-panel-header>
+                <v-expansion-panel-content>
+                  <v-simple-table
+                    fixed-header
+                    height="400px"
+                  >
+                    <template v-slot:default>
+                      <thead>
+                        <tr>
+                          <th class="text-left">
+                            元数据中文名
+                          </th>
+                          <th class="text-left">
+                            元数据英文名
+                          </th>
+                          <th class="text-left">
+                            元数据类型
+                          </th>
+                        </tr>
+                      </thead>
+                      <tbody>
+                        <tr v-for="val in item.meta" :key="key + val.map.name_zh">
+                          <td>{{ val.map.name_zh }}</td>
+                          <td>{{ val.map.name_en }}</td>
+                          <td>{{ val.map.data_type }}</td>
+                        </tr>
+                      </tbody>
+                    </template>
+                  </v-simple-table>
+                </v-expansion-panel-content>
+              </v-expansion-panel>
+            </v-expansion-panels>
+          </div>
+        </v-tab-item>
+        <!-- EXCEL解析 -->
+        <v-tab-item>
+          <div class="box-resource-content-excel" ref="excelBox">
+            <file-review
+              v-loading="showExcel"
+              v-if="dataSource"
+              ref="excel"
+              :style="`height: ${ExcelHeight}px;`"
+              :src="dataSource"
+              @click:bottom-bar="initMap"
+              @rendered="handleRendered"
+              @error="showExcel = false"
+            ></file-review>
+            <template v-else>
+              <none-page></none-page>
+            </template>
+          </div>
+        </v-tab-item>
+      </v-tabs-items>
+    </v-card>
+
+    <v-card class="db-box d-flex flex-column" elevation="5">
+      <div>
+        <v-tabs v-model="tab">
+          <v-tab>元数据设置</v-tab>
+          <v-tab>基础信息配置</v-tab>
+        </v-tabs>
+      </div>
+      <v-divider></v-divider>
+      <v-tabs-items v-model="tab">
+        <v-tab-item>
+          <!-- <edit-selected
+            style="position: relative;height: 100%;"
+            ref="selected"
+            v-model="selectModel"
+          ></edit-selected> -->
+          <edit-selected
+            style="position: relative;height: 100%;"
+            ref="selected"
+            v-model="headMap"
+            v-loading="loading"
+          >
+            <template #head>
+              <form-list ref="form" :items="formItems">
+                <template #actions>
+                  <div style="width: 100%;" class="d-flex justify-space-between">
+                    <v-btn
+                      v-for="btn in controlBtn"
+                      :key="btn.text"
+                      rounded
+                      class="buttons ml-3"
+                      :color="btn.color"
+                      :disabled="btn.disabled && !multipartFile"
+                      @click="btn.handle(btn)"
+                    >
+                      <v-progress-circular
+                        v-if="btn.loading"
+                        indeterminate
+                        :size="18"
+                        width="2"
+                        color="white"
+                        class="mr-1"
+                      ></v-progress-circular>
+                      <v-icon left v-else>{{ btn.icon }}</v-icon>
+                      {{ btn.text }}
+                    </v-btn>
+                  </div>
+                </template>
+              </form-list>
+            </template>
+          </edit-selected>
+        </v-tab-item>
+        <v-tab-item>
+          <div class="pa-3 d-flex align-center justify-center flex-column">
+            <edit-base ref="base" style="max-width: 500px;" :names="names" :item-data="itemData"></edit-base>
+            <v-btn
+              class="buttons"
+              rounded
+              color="primary"
+              :disabled="loading"
+              @click="handleSubmit"
+            >
+              <v-icon left>mdi-send-variant</v-icon>
+              保存
+            </v-btn>
+          </div>
+        </v-tab-item>
+      </v-tabs-items>
+    </v-card>
+
+    <v-overlay :value="overlay" z-index="9">
+      <div class="d-flex flex-column align-center justify-center" style="width: 300px;">
+        <div class="mb-3">正在提交</div>
+        <v-progress-linear
+          color="primary"
+          indeterminate
+          rounded
+          height="6"
+        ></v-progress-linear>
+      </div>
+    </v-overlay>
+  </div>
+</template>
+
+<script>
+import EditSelected from './editSelected'
+import UploadBtn from '@/components/UploadBtn'
+import FileReview from '@/components/FileReview'
+import NonePage from '@/components/Common/empty.vue'
+import EditBase from './editBase'
+import { api } from '@/api/dataGovernance'
+export default {
+  name: 'database-connect',
+  components: {
+    UploadBtn,
+    EditSelected,
+    EditBase,
+    FileReview,
+    NonePage
+  },
+  props: {
+    itemData: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      name: null,
+      items: {},
+      origin: {},
+      tab: 0,
+      uploadTab: 0,
+      names: {},
+      fileName: undefined,
+      loading: false,
+      loadingUpload: false,
+      controlBtn: [
+        {
+          icon: 'mdi-swap-horizontal',
+          key: 'analysis',
+          color: 'primary',
+          loading: false,
+          disabled: false,
+          text: '解析',
+          handle: this.handleAnalysis
+        }
+      ],
+      select: null,
+      overlay: false,
+      dataSource: null, // excel数据
+      ExcelHeight: 0,
+      ExcelWidth: 0,
+      showExcel: false,
+      selectModel: null,
+      uploadBtnText: '导入DDL',
+      isUpdateMap: true,
+      renderSheet: true // 自动渲染
+    }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      this.ExcelHeight = this.$refs.excelBox.clientHeight
+      this.ExcelWidth = this.$refs.excelBox.clientWidth
+    })
+  },
+  created () {
+    if (!Object.keys(this.itemData).length) {
+      return
+    }
+    this.selectModel = this.itemData.parsed_data.map(e => {
+      return {
+        map: e,
+        text: e.name_zh
+      }
+    })
+    this.select = this.itemData.name_zh
+    this.names = {
+      name_zh: this.itemData.name_zh,
+      name_en: this.itemData.name_en
+    }
+  },
+  methods: {
+    handleRendered () {
+      this.showExcel = false
+      if (!this.renderSheet) {
+        return
+      }
+      // 渲染完毕
+      this.initMap()
+    },
+    async initMap () {
+      if (!this.isUpdateMap || !this.dataSource) {
+        return
+      }
+      const row = this.formItems.options.find(e => e.key === 'headRowNumber').value
+      const mapItems = this.$refs.excel?.getRow(row - 1)
+      if (!mapItems || !mapItems.length) {
+        this.headMap = []
+        return
+      }
+      this.headMap = mapItems.map(_e => {
+        return {
+          map: null,
+          text: _e
+        }
+      })
+    },
+    handleTabChange (tab) {
+      this.uploadTab = tab
+      this.uploadBtnText = tab === 0 ? '导入DDL' : tab === 1 ? '导入Excel' : '导入文本'
+    },
+    async handleChangeFile (file) {
+      this.loadingUpload = true
+      const query = new FormData()
+      query.append('file', file)
+      try {
+        const { data } = await api.parseDDLFile(query)
+        this.items = data.reduce((res, item) => {
+          const { columns, exist, table_info: tableInfo } = item
+          const key = tableInfo.name_zh || tableInfo.name_en
+          res[key] = {
+            exist,
+            table_info: tableInfo,
+            meta: columns.map(e => {
+              return {
+                text: e.name_zh,
+                map: {
+                  ...e,
+                  data_standard: null
+                }
+              }
+            })
+          }
+          return res
+        }, {})
+        this.origin = { ...this.items }
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.loadingUpload = false
+      }
+    },
+    handleSelect (item, key) {
+      this.select = key
+      this.selectModel = item.meta
+      this.names = {
+        name_zh: item.table_info?.name_zh,
+        name_en: item.table_info?.name_en
+      }
+    },
+    handleSearch () {
+      if (!this.name) {
+        this.items = this.origin
+        return
+      }
+      this.items = Object.keys(this.origin).filter(e => e.includes(this.name)).reduce((res, key) => {
+        res[key] = this.origin[key]
+        return res
+      }, {})
+    },
+
+    async handleSubmit () {
+      if (!this.selectModel || !this.selectModel.length) {
+        this.$snackbar.error('请先选择数据资源')
+        this.tab = 0
+        return
+      }
+      try {
+        const obj = this.$refs.base.getValue()
+        if (!obj) {
+          return
+        }
+
+        const { ...params } = obj
+        // 上传文件
+        if (this.itemData.id) {
+          Object.assign(params, {
+            id: this.itemData.id,
+            parsed_data: this.selectModel.map(e => {
+              const { data_standard: dataStandard, ...obj } = e.map
+              return {
+                data_standard: dataStandard?.id ?? null,
+                ...obj
+              }
+            }),
+            type: 'database',
+            name_en: this.names.name_en
+          })
+        } else {
+          Object.assign(params, {
+            additional_info: {
+              head_data: this.items[this.select].meta.map(e => {
+                const { data_standard: dataStandard, ...obj } = e.map
+                return {
+                  data_standard: dataStandard?.id ?? null,
+                  ...obj
+                }
+              }),
+              data_resource: {
+                name_en: this.names.name_en,
+                name_zh: this.names.name_zh
+              }
+            },
+            type: 'database',
+            url: '' // 文件上传路径
+          })
+        }
+        this.overlay = true
+        const submitApi = this.itemData.id ? api.updateBusinessDomain : api.saveBusinessDomain
+        await submitApi(params)
+        this.$snackbar.success('保存成功')
+        this.$emit('close')
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.overlay = false
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.db {
+  width: 100%;
+  height: 100%;
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  grid-gap: 15px;
+  &-box {
+    height: 100%;
+    overflow: hidden;
+  }
+}
+.scrollBox {
+  height: 0;
+  flex: 1;
+  overflow: auto;
+}
+.title {
+  color: #1976D2;
+  font-weight: 600;
+}
+::v-deep .v-tabs-items {
+  height: 0;
+  flex: 1;
+  .v-window-item {
+    overflow: auto;
+    height: 100%;
+  }
+  .v-window__container {
+    height: 100%;
+  }
+}
+::v-deep .v-banner--single-line .v-banner__wrapper {
+  padding: 0 8px 0 0;
+}
+</style>

+ 420 - 0
src/views/dataGovernance/businessDomain/components/edit.vue

@@ -0,0 +1,420 @@
+<template>
+  <div class="db">
+    <v-card class="db-box d-flex flex-column" elevation="5" v-loading="loadingUpload">
+      <v-banner single-line>
+        <div class="d-flex align-center">
+          <!-- <div class="py-2 title">DDL解析</div> -->
+          <v-tabs v-model="uploadTab" @change="handleTabChange">
+            <v-tab>DDL解析</v-tab>
+            <v-tab>Excel解析</v-tab>
+            <v-tab>文本解析</v-tab>
+          </v-tabs>
+          <v-spacer></v-spacer>
+          <upload-btn
+            text
+            color="primary"
+            @change="handleChangeFile"
+          >
+            <v-icon left>mdi-import</v-icon>
+            {{ uploadBtnText }}
+          </upload-btn>
+        </div>
+      </v-banner>
+      <v-tabs-items v-model="uploadTab">
+        <!-- DDL解析 -->
+        <v-tab-item>
+          <DDLPage
+            :items="items"
+            :origin="origin"
+            :select="select"
+            @select="handleSelect"
+          ></DDLPage>
+        </v-tab-item>
+        <!-- EXCEL解析 -->
+        <v-tab-item>
+          <ExcelPage
+            ref="excelPage"
+            style="position: relative;height: 100%;"
+            :data-source="dataSource"
+            :render-sheet="renderSheet"
+            @rendered="handleRendered"
+            @click:bottom-bar="initMap"
+            @error="handleExcelError"
+          ></ExcelPage>
+        </v-tab-item>
+      </v-tabs-items>
+    </v-card>
+
+    <v-card class="db-box d-flex flex-column" elevation="5">
+      <div>
+        <v-tabs v-model="tab">
+          <v-tab>元数据设置</v-tab>
+          <v-tab>基础信息配置</v-tab>
+        </v-tabs>
+      </div>
+      <v-divider></v-divider>
+      <v-tabs-items v-model="tab">
+        <v-tab-item>
+          <edit-selected
+            style="position: relative;height: 100%;"
+            ref="selected"
+            v-model="selectModel"
+            v-loading="loading"
+          >
+            <template #head v-if="uploadTab === 1">
+              <form-list ref="form" :items="formItems">
+                <template #actions>
+                  <div style="width: 100%;" class="d-flex justify-space-between">
+                    <v-btn
+                      v-for="btn in controlBtn"
+                      :key="btn.text"
+                      rounded
+                      class="buttons ml-3"
+                      :color="btn.color"
+                      :disabled="btn.disabled && !file"
+                      @click="btn.handle(btn)"
+                    >
+                      <v-progress-circular
+                        v-if="btn.loading"
+                        indeterminate
+                        :size="18"
+                        width="2"
+                        color="white"
+                        class="mr-1"
+                      ></v-progress-circular>
+                      <v-icon left v-else>{{ btn.icon }}</v-icon>
+                      {{ btn.text }}
+                    </v-btn>
+                  </div>
+                </template>
+              </form-list>
+            </template>
+          </edit-selected>
+        </v-tab-item>
+        <v-tab-item>
+          <div class="pa-3 d-flex align-center justify-center flex-column">
+            <edit-base ref="base" style="max-width: 500px;" :names="names" :item-data="itemData"></edit-base>
+            <v-btn
+              class="buttons"
+              rounded
+              color="primary"
+              :disabled="loading"
+              @click="handleSubmit"
+            >
+              <v-icon left>mdi-send-variant</v-icon>
+              保存
+            </v-btn>
+          </div>
+        </v-tab-item>
+      </v-tabs-items>
+    </v-card>
+
+    <v-overlay :value="overlay" z-index="9">
+      <div class="d-flex flex-column align-center justify-center" style="width: 300px;">
+        <div class="mb-3">正在提交</div>
+        <v-progress-linear
+          color="primary"
+          indeterminate
+          rounded
+          height="6"
+        ></v-progress-linear>
+      </div>
+    </v-overlay>
+  </div>
+</template>
+
+<script>
+import EditSelected from './editSelected'
+import UploadBtn from '@/components/UploadBtn'
+import FormList from '@/components/Form/list'
+import EditBase from './editBase'
+import DDLPage from './ddl/index.vue'
+import ExcelPage from './excel/index.vue'
+import { api } from '@/api/dataGovernance'
+// import { handleReadFile } from '@/utils/file'
+export default {
+  name: 'database-connect',
+  components: {
+    UploadBtn,
+    EditSelected,
+    EditBase,
+    FormList,
+    DDLPage,
+    ExcelPage
+  },
+  props: {
+    itemData: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data () {
+    return {
+      items: {},
+      origin: {},
+      tab: 0,
+      uploadTab: 0,
+      names: {},
+      fileName: undefined,
+      loading: false,
+      loadingUpload: false,
+      controlBtn: [
+        {
+          icon: 'mdi-swap-horizontal',
+          key: 'analysis',
+          color: 'primary',
+          loading: false,
+          disabled: false,
+          text: '解析',
+          handle: this.handleAnalysis
+        }
+      ],
+      select: null,
+      overlay: false,
+      dataSource: null, // excel数据
+      selectModel: null,
+      uploadBtnText: '导入DDL',
+      isUpdateMap: true,
+      renderSheet: true, // 自动渲染
+      file: null,
+      formItems: {
+        options: [
+          {
+            type: 'number',
+            key: 'headRowNumber',
+            value: 1,
+            col: 4,
+            label: '请输入表头所在行数 *',
+            outlined: true,
+            dense: true,
+            rules: [v => v > 0 || '请输入表头所在行数']
+          },
+          {
+            col: 8,
+            slotName: 'actions'
+          }
+        ]
+      }
+    }
+  },
+  computed: {
+    uploadTabText () {
+      return this.uploadTab === 0 ? '导入DDL' : this.uploadTab === 1 ? '导入Excel' : '导入文本'
+    }
+  },
+  created () {
+    if (!Object.keys(this.itemData).length) {
+      return
+    }
+    this.uploadTab = this.itemData.type === 'database' ? 0 : this.itemData.type === 'structure' ? 1 : 2
+    this.uploadBtnText = this.uploadTabText
+    this.selectModel = this.itemData.parsed_data.map(e => {
+      return {
+        map: e,
+        text: e.name_zh
+      }
+    })
+    this.select = this.itemData.name_zh
+    this.names = {
+      name_zh: this.itemData.name_zh,
+      name_en: this.itemData.name_en
+    }
+    // 有url时,下载文件
+    if (this.itemData?.url) {
+      this.getFile(this.itemData.url)
+    }
+  },
+  methods: {
+    // 文件下载,用于编辑时下载文件
+    async getFile (url) {
+      try {
+        const { data } = await api.downloadFile({ url })
+        this.file = new File([data], url, { type: data.type || 'text/plain' })
+        // await this.handleChangeFile(file)
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    handleRendered () {
+      this.showExcel = false
+      if (!this.renderSheet) {
+        return
+      }
+      // 渲染完毕
+      this.initMap()
+    },
+    async initMap () {
+      if (!this.isUpdateMap || !this.dataSource) {
+        return
+      }
+      const row = this.formItems.options.find(e => e.key === 'headRowNumber').value
+      const mapItems = this.$refs.excelPage?.getRow(row - 1)
+      if (!mapItems || !mapItems.length) {
+        this.selectModel = []
+        return
+      }
+      this.selectModel = mapItems.map(_e => {
+        return {
+          map: null,
+          text: _e
+        }
+      })
+      // 需解析excel内容,解析后接口返回元数据列表
+    },
+    handleTabChange (tab) {
+      this.uploadTab = tab
+      this.uploadBtnText = this.uploadTabText
+    },
+    // EXCEL文件解析
+    async handleAnalysis (item) {
+      item.loading = true
+      await this.initMap()
+      item.loading = false
+    },
+    // 文件解析处理
+    async handleChangeFile (file) {
+      this.file = file
+      if (this.uploadTab === 0) {
+        // DDL 文件处理
+        this.loadingUpload = true
+        const query = new FormData()
+        query.append('file', file)
+        try {
+          const { data } = await api.parseDDLFile(query)
+          this.items = data.reduce((res, item) => {
+            const { columns, exist, table_info: tableInfo } = item
+            const key = tableInfo.name_zh || tableInfo.name_en
+            res[key] = {
+              exist,
+              table_info: tableInfo,
+              meta: columns.map(e => {
+                return {
+                  text: e.name_zh,
+                  map: {
+                    ...e,
+                    data_standard: null
+                  }
+                }
+              })
+            }
+            return res
+          }, {})
+          this.origin = { ...this.items }
+        } catch (error) {
+          this.$snackbar.error(error)
+        } finally {
+          this.loadingUpload = false
+        }
+      } else if (this.uploadTab === 1) {
+        // Excel 文件处理
+        // const type = file.name.split('.').pop()
+        // if (type !== 'xlsx' && type !== 'xls') {
+        //   this.$snackbar.error('请导入Excel文件')
+        //   return
+        // }
+        // this.fileName = file.name
+        // this.renderSheet = true
+        // handleReadFile(file, type, files => {
+        //   // 返回blob
+        //   this.dataSource = files
+        // })
+      }
+    },
+    handleExcelError () {
+      // Excel 加载错误处理
+    },
+    // DDL使用该数据源
+    handleSelect (item, key) {
+      this.select = key
+      this.selectModel = item.meta
+      this.names = {
+        name_zh: item.table_info?.name_zh,
+        name_en: item.table_info?.name_en
+      }
+    },
+
+    async handleSubmit () {
+      // DDL解析需要选择数据资源
+      if (this.uploadTab === 0 && (!this.selectModel || !this.selectModel.length)) {
+        this.$snackbar.error('请先选择数据资源')
+        this.tab = 0
+        return
+      }
+      const type = this.uploadTab === 0 ? 'database' : this.uploadTab === 1 ? 'structure' : 'unstructured'
+      try {
+        const obj = this.$refs.base.getValue()
+        if (!obj) {
+          return
+        }
+
+        const params = {
+          ...obj,
+          type
+        }
+        // 有id时,更新
+        if (this.itemData.id) {
+          params.id = this.itemData.id
+        }
+        // 上传文件
+        if (this.file) {
+          const { data } = await api.uploadFile({ file: this.file })
+          params.url = data.url // 文件上传路径
+        }
+
+        // 如果有解析数据,添加 parsed_data
+        if (this.selectModel && this.selectModel.length) {
+          params.parsed_data = this.selectModel.map(e => {
+            const { data_standard: dataStandard, ...obj } = e.map
+            return {
+              data_standard: dataStandard?.id ?? null,
+              ...obj
+            }
+          })
+        }
+
+        this.overlay = true
+        const submitApi = this.itemData.id ? api.updateBusinessDomain : api.saveBusinessDomain
+        await submitApi(params)
+        this.$snackbar.success('保存成功')
+        this.$emit('close')
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.overlay = false
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.db {
+  width: 100%;
+  height: 100%;
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  grid-gap: 15px;
+  &-box {
+    height: 100%;
+    overflow: hidden;
+  }
+}
+.title {
+  color: #1976D2;
+  font-weight: 600;
+}
+::v-deep .v-tabs-items {
+  height: 0;
+  flex: 1;
+  .v-window-item {
+    overflow: auto;
+    height: 100%;
+  }
+  .v-window__container {
+    height: 100%;
+  }
+}
+::v-deep .v-banner--single-line .v-banner__wrapper {
+  padding: 0 8px 0 0;
+}
+</style>

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

@@ -0,0 +1,246 @@
+<template>
+  <div>
+    <m-form ref="form" :items="formItems" v-model="formValues">
+      <template #name_en>
+        <v-btn color="primary" class="ml-3" :loading="translateLoading" @click="getTranslate">翻译</v-btn>
+      </template>
+    </m-form>
+  </div>
+</template>
+
+<script>
+import MForm from '@/components/MForm'
+import {
+  metadata,
+  frequency,
+  sensitivity
+} from '@/utils/dataGovernance'
+import {
+  getDatasourceList
+} from '@/api/dataOrigin'
+import { getTranslate } from '@/api'
+import { api } from '@/api/dataGovernance'
+export default {
+  name: 'edit-base',
+  props: {
+    names: {
+      type: Object,
+      default: () => ({})
+    },
+    itemData: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  components: { MForm },
+  data () {
+    return {
+      formValues: {
+        name_zh: '数据资源_' + new Date().getTime(),
+        name_en: null,
+        category: '应用类',
+        organization: this.$store.getters.userInfo.username,
+        leader: this.$store.getters.userInfo.username,
+        frequency: '月',
+        data_sensitivity: '低',
+        chooseStorage: false,
+        storage_location: '/',
+        data_source: null,
+        tag: null,
+        describe: null,
+        status: true
+      },
+      pageInfo: {
+        size: 999,
+        current: 1
+      },
+      loading: false,
+      translateLoading: false,
+      labelItems: [],
+      dataSourceItems: []
+    }
+  },
+  computed: {
+    formItems () {
+      return [
+        {
+          type: 'text',
+          key: 'name_zh',
+          label: '请输入名称 *',
+          rules: [v => !!v || '请输入名称']
+        },
+        {
+          type: 'text',
+          key: 'name_en',
+          label: '请输入英文名称 *',
+          rules: [v => !!v || '请输入名称'],
+          slotName: 'name_en'
+        },
+        {
+          type: 'autocomplete',
+          key: 'category',
+          label: '请选择分类 *',
+          rules: [v => !!v || '请选择分类'],
+          items: [...metadata]
+        },
+        {
+          type: 'text',
+          key: 'organization',
+          label: '请输入所属机构 *',
+          rules: [v => !!v || '请输入所属机构']
+        },
+        {
+          type: 'text',
+          key: 'leader',
+          label: '请输入负责人 *',
+          rules: [v => !!v || '请输入负责人']
+        },
+        {
+          type: 'autocomplete',
+          key: 'frequency',
+          label: '请选择更新频率 *',
+          noAttach: true,
+          rules: [v => !!v || '请选择更新频率'],
+          items: [...frequency]
+        },
+        {
+          type: 'autocomplete',
+          key: 'data_sensitivity',
+          label: '请选择数据敏感度 *',
+          noAttach: true,
+          rules: [v => !!v || '请选择数据敏感度'],
+          items: [...sensitivity]
+        },
+        {
+          type: 'ifRadio',
+          key: 'chooseStorage',
+          label: '使用数据源',
+          width: 120,
+          items: [{ label: '是', value: true }, { label: '否', value: false }]
+        },
+        {
+          type: 'text',
+          key: 'storage_location',
+          hide: this.formValues.chooseStorage,
+          slotTitle: '注: 根目录地址为 /data/upload',
+          slotTitleStyle: 'color: #999; padding: 5px',
+          prefix: '/data/upload',
+          label: '请输入存储位置 *',
+          rules: [v => !!v || '请输入存储位置']
+        },
+        {
+          type: 'autocomplete',
+          key: 'data_source',
+          label: '请选择数据源',
+          hide: !this.formValues.chooseStorage,
+          noAttach: true,
+          itemText: 'name_zh',
+          itemValue: 'id',
+          items: this.dataSourceItems
+        },
+        {
+          type: 'autocomplete',
+          key: 'tag',
+          label: '请选择标签',
+          noAttach: true,
+          itemText: 'name_zh',
+          itemValue: 'id',
+          items: this.labelItems
+        },
+        {
+          type: 'text',
+          key: 'describe',
+          label: '请输入描述'
+        },
+        {
+          type: 'ifRadio',
+          key: 'status',
+          label: '启用',
+          width: 120,
+          items: [{ label: '是', value: true }, { label: '否', value: false }]
+        }
+      ]
+    }
+  },
+  created () {
+    this.init()
+    if (!Object.keys(this.itemData).length) {
+      this.formValues.name_zh = this.names?.name_zh
+      this.formValues.name_en = this.names?.name_en
+      return
+    }
+    Object.keys(this.formValues).forEach(key => {
+      if (key === 'tag') {
+        this.formValues[key] = this.itemData[key]?.id || null
+        return
+      }
+      if (key === 'data_source') {
+        this.formValues[key] = this.itemData[key] || null
+        this.formValues.chooseStorage = !!this.itemData[key]
+        return
+      }
+      if (Object.prototype.hasOwnProperty.call(this.itemData, key)) {
+        this.formValues[key] = this.itemData[key]
+      }
+    })
+  },
+  methods: {
+    async init () {
+      try {
+        this.loading = true
+        try {
+          const { data } = await api.getLabelList({
+            ...this.pageInfo
+          })
+          this.labelItems = data.records
+          const { data: dataSource } = await getDatasourceList({})
+          this.dataSourceItems = dataSource.data_source
+        } catch (error) {
+          this.$snackbar.error(error)
+        } finally {
+          this.loading = false
+        }
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    async getTranslate () {
+      if (!this.formValues.name_zh) {
+        this.$snackbar.error('请输入名称')
+        return
+      }
+      this.translateLoading = true
+      try {
+        const { data } = await getTranslate({
+          node_name: this.formValues.name_zh
+        })
+        this.formValues.name_en = data.translated
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.translateLoading = false
+      }
+    },
+    getValue () {
+      if (!this.$refs.form.validate()) {
+        return
+      }
+      const { chooseStorage, data_source: dataSource, storage_location: storageLocation, ...obj } = this.formValues
+      if (chooseStorage) {
+        return {
+          ...obj,
+          data_source: dataSource
+        }
+      }
+      return {
+        ...obj,
+        storage_location: storageLocation
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 184 - 0
src/views/dataGovernance/businessDomain/components/editSelected.vue

@@ -0,0 +1,184 @@
+<template>
+  <div class="pa-3 content d-flex flex-column">
+    <slot name="head"></slot>
+    <div class="content-table d-flex flex-column" ref="content">
+      <v-simple-table fixed-header style="height: 100%" :height="height" ref="table">
+        <template v-slot:default>
+          <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>
+            <tr
+              v-for="(item, index) in value"
+              :key="index"
+            >
+              <td class="pa-0">
+                <v-btn icon color="error" @click="handleClose(index)">
+                  <v-icon>mdi-minus</v-icon>
+                </v-btn>
+              </td>
+              <!-- <td>{{ item.text }}</td> -->
+              <td>
+                <v-text-field
+                  v-model.trim="item.map.name_zh"
+                  label="元数据"
+                  placeholder="请输入元数据"
+                  dense
+                  hide-details
+                  outlined
+                  class="my-3"
+                ></v-text-field>
+              </td>
+              <td>
+                <v-text-field
+                  v-model.trim="item.map.name_en"
+                  label="元数据英文名"
+                  placeholder="请输入元数据英文名"
+                  dense
+                  hide-details
+                  outlined
+                  class="my-3"
+                ></v-text-field>
+              </td>
+              <td>
+                <v-text-field
+                  v-model.trim="item.map.data_type"
+                  label="数据类型"
+                  placeholder="请输入数据类型"
+                  dense
+                  hide-details
+                  outlined
+                  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>
+      </v-simple-table>
+    </div>
+    <slot></slot>
+  </div>
+</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,
+      default: () => []
+    }
+  },
+  data () {
+    return {
+      height: 0,
+      standard: {
+        ...common
+      },
+      metadata: {
+        ...common
+      },
+      type: {
+        ...common
+      },
+      openFn: undefined
+    }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      this.height = this.$refs.table.$el.clientHeight
+    })
+  },
+  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)
+      this.$emit('input', items)
+    },
+    getValue () {
+      if (!this.$refs.form.validate()) {
+        return
+      }
+      const obj = this.formItems.options.reduce((r, e) => {
+        r[e.key] = e.value
+        return r
+      }, {})
+      return {
+        map: this.value,
+        ...obj
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.content {
+  height: 0;
+  flex: 1;
+  &-table {
+    height: 0;
+    flex: 1;
+    overflow-y: auto;
+  }
+}
+</style>

+ 90 - 0
src/views/dataGovernance/businessDomain/components/excel/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <div ref="excelBox">
+    <file-review
+      v-loading="showExcel"
+      v-if="dataSource"
+      ref="excel"
+      :style="`height: ${ExcelHeight}px;`"
+      :src="dataSource"
+      @click:bottom-bar="handleClickBottomBar"
+      @rendered="handleRendered"
+      @error="handleError"
+    ></file-review>
+    <template v-else>
+      <none-page></none-page>
+    </template>
+  </div>
+</template>
+
+<script>
+import FileReview from '@/components/FileReview'
+import NonePage from '@/components/Common/empty.vue'
+
+export default {
+  name: 'ExcelPage',
+  components: {
+    FileReview,
+    NonePage
+  },
+  props: {
+    dataSource: {
+      type: [String, File, ArrayBuffer],
+      default: null
+    },
+    renderSheet: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data () {
+    return {
+      ExcelHeight: 0,
+      ExcelWidth: 0,
+      showExcel: false
+    }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      if (this.$refs.excelBox) {
+        this.ExcelHeight = this.$refs.excelBox.clientHeight
+        this.ExcelWidth = this.$refs.excelBox.clientWidth
+      }
+    })
+  },
+  watch: {
+    dataSource: {
+      handler (val) {
+        if (val) {
+          this.showExcel = true
+        }
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    handleRendered () {
+      this.showExcel = false
+      if (!this.renderSheet) {
+        return
+      }
+      // 渲染完毕,触发父组件的 initMap
+      this.$emit('rendered')
+    },
+    handleClickBottomBar () {
+      // 点击底部栏时触发父组件的 initMap
+      this.$emit('click:bottom-bar')
+    },
+    handleError () {
+      this.showExcel = false
+      this.$emit('error')
+    },
+    // 暴露给父组件的方法,用于获取 Excel 行数据
+    getRow (index) {
+      return this.$refs.excel?.getRow(index)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 186 - 0
src/views/dataGovernance/businessDomain/index.vue

@@ -0,0 +1,186 @@
+<template>
+  <!-- 元数据 -->
+  <div class="pa-3 white">
+    <filter-list :filter="['name_zh', 'name_en', 'category', 'tag']" @search="handleSearch" />
+    <table-list
+      class="mt-3"
+      :loading="loading"
+      :headers="headers"
+      :items="items"
+      :total="total"
+      :page-info="pageInfo"
+      :is-tools="false"
+      :show-select="false"
+      @pageHandleChange="pageHandleChange"
+      @sort="handleSort"
+    >
+      <template #status="{ item }">
+        <v-chip
+          :color="item.status ? 'success' : 'error'"
+          small
+        >
+          {{ item.status ? '已启用' : '已禁用'}}
+        </v-chip>
+      </template>
+      <template #tag="{ item }">
+        {{ item.tag?.name_zh }}
+      </template>
+      <template v-slot:navBtn>
+        <v-btn class="elevation-5 buttons mr-3" color="primary" rounded @click="handleAdd">
+          <v-icon left>mdi-plus</v-icon>
+          新增业务域
+        </v-btn>
+      </template>
+      <template #actions="{ item }">
+        <v-btn
+          text
+          :disabled="loading"
+          color="primary"
+          @click="handleEdit(item)">编辑</v-btn>
+        <v-btn
+          text
+          :disabled="loading"
+          color="error"
+          @click="handleDelete(item.id)">删除</v-btn>
+        <v-btn
+          text
+          :disabled="loading"
+          :color="item.status ? 'error' : 'success'"
+          @click="handleChangeStatus(item)"
+        >
+          {{ item.status ? '禁用' : '启用' }}
+        </v-btn>
+      </template>
+    </table-list>
+
+    <edit-dialog :visible.sync="show" :title="title" :footer="false" :fullscreen="true">
+      <edit v-if="show" v-loading="submitLoading" ref="form" :item-data="itemData" @close="handleClose"></edit>
+    </edit-dialog>
+  </div>
+</template>
+
+<script>
+import FilterList from '../components/Filter'
+import TableList from '@/components/List/table'
+import EditDialog from '@/components/Dialog'
+import Edit from './components/edit'
+
+import { api } from '@/api/dataGovernance'
+export default {
+  name: 'businessDomain',
+  components: { FilterList, TableList, EditDialog, Edit },
+  data () {
+    return {
+      submitLoading: false,
+      show: false,
+      loading: false,
+      headers: [
+        { text: '中文名', value: 'name_zh' },
+        { text: '英文名', value: 'name_en' },
+        { text: '分类', value: 'category' },
+        { text: '描述', value: 'describe' },
+        { text: '标签', value: 'tag' },
+        { text: '状态', value: 'status' },
+        { text: '血缘关系数量', value: 'blood_count', align: 'center' },
+        { text: '创建时间', value: 'create_time' },
+        { text: '操作', value: 'actions' }
+      ],
+      items: [],
+      total: 0,
+      pageInfo: {
+        size: 10,
+        current: 1
+      },
+      query: {},
+      orders: [],
+      itemData: {}
+    }
+  },
+  computed: {
+    title () {
+      return this.itemData.id ? '编辑业务域' : '新增业务域'
+    }
+  },
+  created () {
+    this.init()
+  },
+  methods: {
+    handleClose () {
+      this.show = false
+      this.init()
+    },
+    async init () {
+      this.loading = true
+      try {
+        const { data } = await api.getBusinessDomainList({ ...this.pageInfo, ...this.query })
+        this.total = data.total
+        this.items = data.records
+      } catch (error) {
+        this.$snackbar.error(error)
+      } finally {
+        this.loading = false
+      }
+    },
+    handleAdd () {
+      this.itemData = {}
+      this.show = true
+    },
+    async handleEdit ({ id }) {
+      try {
+        const { data } = await api.getBusinessDomainDetail({ id })
+        if (!data || !Object.keys(data).length) {
+          this.$snackbar.error('找不到业务域信息')
+          return
+        }
+        this.itemData = data
+        this.show = true
+      } catch (error) {
+        this.$snackbar.error(error)
+      }
+    },
+    handleDelete (id) {
+      this.$confirm('提示', '是否确定删除该项业务域').then(async () => {
+        try {
+          await api.deleteBusinessDomain({ id })
+          this.$snackbar.success('删除成功')
+          this.init()
+        } catch (error) {
+          this.$snackbar.error(error)
+        }
+      })
+    },
+    async handleChangeStatus (item) {
+      this.loading = true
+      try {
+        const { status, ...obj } = item
+        await api.updateBusinessDomain({
+          ...obj,
+          status: !status
+        })
+        this.$snackbar.success('操作成功')
+        this.init()
+      } catch (error) {
+        this.loading = false
+        this.$snackbar.error(error)
+      }
+    },
+    handleSort (val) {
+      this.orders = val
+      this.init()
+    },
+    handleSearch (obj) {
+      Object.assign(this.query, obj)
+      this.pageInfo.current = 1
+      this.init()
+    },
+    pageHandleChange (index) {
+      this.pageInfo.current = index
+      this.init()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 3 - 4
src/views/dataGovernance/components/Filter.vue

@@ -77,7 +77,7 @@ export default {
     return {
       switchValue: this.value,
       option: {
-        list: []
+        list: options.filter(e => this.filter.includes(e.key))
       },
       tag: {
         total: 0,
@@ -90,7 +90,6 @@ export default {
     }
   },
   created () {
-    this.option.list = this.option.list.filter(e => this.filter.includes(e.key))
     if (this.filter.includes('tag')) {
       this.init()
     }
@@ -106,7 +105,7 @@ export default {
         })
         // this.option.list.find(e => e.key === 'tag').items = data.records
         options.find(e => e.key === 'tag').items = data.records
-        this.option.list = JSON.parse(JSON.stringify(options))
+        this.option.list = JSON.parse(JSON.stringify(options)).filter(e => this.filter.includes(e.key))
         this.tag.total = data.total
       } catch (error) {
         this.$snackbar.error(error)
@@ -141,7 +140,7 @@ export default {
           return e
         })
       } else {
-        this.option.list = JSON.parse(JSON.stringify(options))
+        this.option.list = JSON.parse(JSON.stringify(options)).filter(e => this.filter.includes(e.key))
       }
       this.$emit('input', this.switchValue)
       this.$emit('search', { tag: null })

+ 1 - 1
src/views/dataGovernance/dataProcess/components/edit.vue

@@ -149,7 +149,7 @@ export default {
     // 获取业务域列表
     async getList () {
       try {
-        const { data } = await api.getBusinessDomainList()
+        const { data } = await api.getBusinessDomainList2()
         if (!data || !data?.length) {
           this.businessDomain = []
           return

+ 4 - 4
src/views/dataGovernance/metadata/index.vue

@@ -77,13 +77,13 @@ export default {
       headers: [
         { text: '中文名', value: 'name_zh' },
         { text: '英文名', value: 'name_en' },
-        { text: '分类', value: 'category' },
-        { text: '描述', value: 'describe' },
-        { text: '标签', value: 'tag' },
+        { text: '分类', value: 'category', sortable: false },
+        { text: '描述', value: 'describe', sortable: false },
+        { text: '标签', value: 'tag', sortable: false },
         { text: '状态', value: 'status' },
         { text: '血缘关系数量', value: 'blood_count', align: 'center' },
         { text: '创建时间', value: 'create_time' },
-        { text: '操作', value: 'actions' }
+        { text: '操作', value: 'actions', sortable: false }
       ],
       items: [],
       total: 0,