Parcourir la source

feat: 数据库目录

wanggaokun il y a 1 an
Parent
commit
867e6d1296

+ 16 - 0
src/api/modules/db/connection.ts

@@ -18,6 +18,22 @@ export const getConnectionApi = (id: string | number) => {
   return http.get<ConnectionVO>(`/db/connection/${id}`)
 }
 
+/**
+ * @name 测试数据库连接
+ * @param id id
+ * @returns returns
+ */
+export const testApi = (id: string | number) => {
+  return http.get<any>(`/db/connection/test/${id}`)
+}
+/**
+ * @name 获取连接名称
+ * @returns returns
+ */
+export const getNameListApi = () => {
+  return http.get<any>(`/db/connection/list/name`)
+}
+
 /**
  * @name 查询数据库产品类型
  * @returns returns

+ 34 - 0
src/api/modules/db/metadata.ts

@@ -0,0 +1,34 @@
+import http from '@/api'
+/**
+ * @name 查询数据库allSchemas
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const getSchemasApi = (id: string | number) => {
+  return http.get<any[]>(`/db/metadata/schemas/${id}`, { loading: false })
+}
+
+/**
+ * @name 查询数据库allTables
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const getTablesApi = (id: string | number, param: any) => {
+  return http.get<any[]>(`/db/metadata/tables/${id}`, param, { loading: false })
+}
+/**
+ * @name 查询数据库allTables
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const getTablesMetaApi = (id: string | number, param: any) => {
+  return http.get<any>(`/db/metadata/meta/table/${id}`, param, { loading: false })
+}
+/**
+ * @name 查询数据库tableMeta
+ * @param query 参数
+ * @returns 返回列表
+ */
+export const getTablesDataApi = (id: string | number, param: any) => {
+  return http.get<any>(`/db/metadata/data/table/${id}`, param, { loading: false })
+}

+ 1 - 1
src/components/FormDialog/index.vue

@@ -75,7 +75,7 @@ const handleSubmit = () => {
   if (!formEl) return
   formEl.validate(valid => {
     if (valid) {
-      parameter.value.api!({ ...formModel, ...props.model }).then(res => {
+      parameter.value.api!(Object.assign({ ...props.model }, { ...formModel })).then(res => {
         if (res.code == 200) {
           proFormRef.value?.resetForm(formEl)
           ElMessage.success('操作成功')

+ 1 - 0
src/components/ProTable/index.vue

@@ -113,6 +113,7 @@ const props = withDefaults(defineProps<ProTableProps>(), {
   pagination: true,
   initParam: {},
   border: true,
+  data: () => [],
   toolButton: true,
   isShowSearch: true,
   rowKey: 'id',

+ 0 - 1
src/components/Upload/File.vue

@@ -175,7 +175,6 @@ const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
 
 // 上传结束处理
 const uploadedSuccessfully = () => {
-  debugger
   if (number.value > 0 && uploadList.value.length === number.value) {
     _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
     uploadList.value = []

+ 0 - 1
src/components/Upload/FileS3.vue

@@ -175,7 +175,6 @@ const uploadSuccess = (response: any | undefined, uploadFile: UploadFile) => {
 
 // 上传结束处理
 const uploadedSuccessfully = () => {
-  debugger
   if (number.value > 0 && uploadList.value.length === number.value) {
     _fileList.value = _fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
     uploadList.value = []

+ 32 - 11
src/views/db/connection/index.vue

@@ -20,6 +20,7 @@
         <el-button type="primary" link icon="View" v-auth="['db:connection:query']" @click="openDialog(3, '数据库链接查看', scope.row)">
           查看
         </el-button>
+        <el-button type="primary" link icon="View" v-auth="['db:connection:query']" @click="test(scope.row)"> 测试 </el-button>
         <el-button type="primary" link icon="EditPen" v-auth="['db:connection:edit']" @click="openDialog(2, '数据库链接编辑', scope.row)">
           编辑
         </el-button>
@@ -35,6 +36,7 @@ import { ref, reactive } from 'vue'
 import { useHandleData } from '@/hooks/useHandleData'
 import ProTable from '@/components/ProTable/index.vue'
 import FormDialog from '@/components/FormDialog/index.vue'
+import { ElMessage } from 'element-plus'
 import { ProTableInstance, ColumnProps } from '@/components/ProTable/interface'
 import {
   listConnectionApi,
@@ -43,7 +45,8 @@ import {
   updateConnectionApi,
   getConnectionApi,
   getTypesApi,
-  getDriversApi
+  getDriversApi,
+  testApi
 } from '@/api/modules/db/connection'
 
 // ProTable 实例
@@ -51,6 +54,8 @@ const proTable = ref<ProTableInstance>()
 
 let connectionForm = ref({ type: '' })
 
+let databaseType = ref([])
+
 // 删除数据库链接信息
 const deleteConnection = async (params: any) => {
   await useHandleData(delConnectionApi, params.id, '删除【' + params.id + '】数据库链接')
@@ -63,6 +68,17 @@ const batchDelete = async (ids: string[]) => {
   proTable.value?.clearSelection()
   proTable.value?.getTableList()
 }
+const test = async (params: any) => {
+  const res = await testApi(params.id)
+  if (res.code == 200) {
+    ElMessage.success('测试连接成功!')
+  } else {
+    ElMessage({
+      type: 'error',
+      message: '测试失败!'
+    })
+  }
+}
 
 const formDialogRef = ref<InstanceType<typeof FormDialog> | null>(null)
 // 打开弹框的功能
@@ -153,10 +169,11 @@ let itemsOptions = ref<ProForm.ItemsOptions[]>([
       placeholder: '请输入数据库类型',
       onChange: value => {
         if (value) {
+          connectionForm.value['driverVersion'] = ''
           getDriversApi(value).then(res => {
             itemsOptions.value[2].compOptions.enum = res.data
+            databaseType.value = res.data
           })
-          console.log('itemsOptions', itemsOptions)
         }
       }
     }
@@ -169,15 +186,19 @@ let itemsOptions = ref<ProForm.ItemsOptions[]>([
       elTagName: 'select',
       labelKey: 'driverVersion',
       valueKey: 'driverVersion',
-      placeholder: '请输入驱动版本'
-    }
-  },
-  {
-    label: '驱动类名',
-    prop: 'driver',
-    rules: [{ required: true, message: '驱动类名不能为空', trigger: 'blur' }],
-    compOptions: {
-      placeholder: '请输入驱动类名'
+      onChange: value => {
+        if (value) {
+          if (databaseType.value.length > 0) {
+            for (let i = 0; i < databaseType.value.length; i++) {
+              if (value == databaseType.value[i]['driverVersion']) {
+                connectionForm.value['driver'] = databaseType.value[i]['driverClass']
+                break
+              }
+            }
+          }
+        }
+      },
+      placeholder: '请选择驱动版本'
     }
   },
   {

+ 42 - 0
src/views/db/database/index.scss

@@ -0,0 +1,42 @@
+.table-container {
+  width: 100%;
+  padding: 10px;
+}
+.filter {
+  box-sizing: border-box;
+  width: 210px;
+  height: 100%;
+  padding: 12px;
+  margin-right: 6px;
+  .title {
+    margin: 0 0 15px;
+    font-size: 18px;
+    font-weight: bold;
+    color: var(--el-color-info-dark-2);
+    letter-spacing: 0.5px;
+  }
+  .el-input {
+    margin: 0 0 15px;
+  }
+  .el-scrollbar {
+    :deep(.el-tree) {
+      height: 80%;
+      overflow: auto;
+      .el-tree-node__content {
+        height: 33px;
+      }
+    }
+    :deep(.el-tree--highlight-current) {
+      .el-tree-node.is-current > .el-tree-node__content {
+        background-color: var(--el-color-primary);
+        .el-tree-node__label,
+        .el-tree-node__expand-icon {
+          color: white;
+        }
+        .is-leaf {
+          color: transparent;
+        }
+      }
+    }
+  }
+}

+ 229 - 0
src/views/db/database/index.vue

@@ -0,0 +1,229 @@
+<template>
+  <div class="main-box">
+    <div class="card filter">
+      <el-tree
+        class="scroller"
+        :load="loadNode"
+        :expand-on-click-node="true"
+        :highlight-current="true"
+        :render-content="renderContent"
+        @node-click="handleNodeClick"
+        :lazy="true"
+      >
+      </el-tree>
+    </div>
+    <div class="table-box card content-box-c">
+      <div class="table-container">
+        <span>当前表:{{ currentNode.schemaName }} / {{ currentNode.tableName }}</span>
+        <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+          <el-tab-pane label="基本信息" name="1">
+            <el-descriptions title="元数据" size="small" :column="1" colon border>
+              <el-descriptions-item label="表名称">{{ tableMeta.tableName }}</el-descriptions-item>
+              <el-descriptions-item label="表类型">{{ tableMeta.type }}</el-descriptions-item>
+              <el-descriptions-item label="模式名">{{ tableMeta.schemaName }}</el-descriptions-item>
+              <el-descriptions-item label="表注释">
+                <el-input type="textarea" :rows="2" v-model="tableMeta.remarks" auto-complete="off" :readonly="true"></el-input>
+              </el-descriptions-item>
+              <el-descriptions-item label="建表DDL">
+                <el-input type="textarea" :rows="16" v-model="tableMeta.createSql" auto-complete="off" :readonly="true"></el-input>
+              </el-descriptions-item>
+            </el-descriptions>
+          </el-tab-pane>
+          <el-tab-pane label="字段信息" name="2">
+            <ProTable :pagination="false" :is-show-search="false" :tool-button="false" :columns="fieldColumns" :data="tableMeta.columns">
+              <!-- 表格操作 -->
+              <template #empty>
+                <span>单击左侧展开"数据源"来查看表的元数据记录</span>
+              </template>
+            </ProTable>
+          </el-tab-pane>
+          <el-tab-pane label="索引信息" name="3">
+            <ProTable :pagination="false" :is-show-search="false" :tool-button="false" :columns="indexColumns" :data="tableMeta.indexes">
+              <!-- 表格操作 -->
+              <template #empty>
+                <span>单击左侧展开"数据源"来查看表的元数据记录</span>
+              </template>
+            </ProTable>
+          </el-tab-pane>
+          <el-tab-pane label="取样数据" name="4">
+            <ProTable :pagination="false" :is-show-search="false" :tool-button="false" :columns="dataColumns" :data="sampleData?.rows || []">
+              <!-- 表格操作 -->
+              <template #empty>
+                <span>单击左侧展开"数据源"来查看表的元数据记录</span>
+              </template>
+            </ProTable>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="tsx" name="UserManage">
+import { ref, reactive, computed } from 'vue'
+// import TreeFilter from '@/components/TreeFilter/index.vue'
+import { getNameListApi } from '@/api/modules/db/connection'
+import { getSchemasApi, getTablesApi, getTablesMetaApi, getTablesDataApi } from '@/api/modules/db/metadata'
+import type { TabsPaneContext } from 'element-plus'
+import ProTable from '@/components/ProTable/index.vue'
+import { ColumnProps } from '@/components/ProTable/interface'
+const activeName = ref('1')
+const tableMeta = ref({
+  tableName: '-',
+  schemaName: '-',
+  remarks: '',
+  type: '-',
+  createSql: '',
+  primaryKeys: [],
+  columns: [],
+  indexes: []
+})
+const currentNode = ref({
+  tableName: '-',
+  schemaName: '-'
+})
+
+let sampleData = ref<any>()
+
+const handleClick = (tab: TabsPaneContext, event: Event) => {
+  console.log(tab, event)
+}
+const loadNode = (node, resolve) => {
+  if (node.level === 0) {
+    const rootNode = [{ label: '数据源导航树', value: 0, hasChild: true, children: 'child' }]
+    return resolve(rootNode)
+  }
+
+  setTimeout(() => {
+    if (node.level === 1) {
+      getNameListApi().then(res => {
+        res.data.forEach(item => {
+          item['label'] = item.name
+          item['parent'] = 0
+          item['value'] = item.id
+          item['hasChild'] = true
+          item['children'] = 'child'
+        })
+        resolve(res.data)
+      })
+    } else if (node.level === 2) {
+      getSchemasApi(node.data.value).then(res => {
+        res.data.forEach(item => {
+          item['label'] = item.schema
+          item['parent'] = node.data.value
+          item['value'] = item.connection
+          item['hasChild'] = true
+          item['children'] = 'child'
+        })
+        resolve(res.data)
+      })
+    } else if (node.level === 3) {
+      getTablesApi(node.data.parent, { schema: node.data.label }).then(res => {
+        res.data.forEach(item => {
+          item['label'] = item.tableName
+          item['parent'] = node.data.label
+          item['id'] = node.data.parent
+          item['value'] = item.type
+          item['hasChild'] = false
+          item['children'] = 'child'
+        })
+        resolve(res.data)
+      })
+    } else if (node.level == 4) {
+      resolve([])
+    } else {
+      resolve([])
+    }
+  }, 500)
+}
+const handleNodeClick = data => {
+  let id = data.id
+  let schema = data.schemaName
+  let table = data.tableName
+  if (!data.hasChild && id && schema && table) {
+    activeName.value = '1'
+    getTablesMetaApi(id, { schema, table }).then(res => {
+      tableMeta.value = res.data
+      currentNode.value.tableName = table
+      currentNode.value.schemaName = schema
+    })
+    getTablesDataApi(id, { schema, table }).then(res => {
+      sampleData.value = res.data
+    })
+  }
+}
+const renderContent = (h, { node, data }) => {
+  if (node.level === 1) {
+    return (
+      <div class="custom-tree-node">
+        <i class="el-icon-takeaway-box"></i>
+        <span>{data.label}</span>
+      </div>
+    )
+  } else if (node.level === 2) {
+    return (
+      <div class="custom-tree-node">
+        <i class="el-icon-folder-opened"></i>
+        <span>{data.label}</span>
+      </div>
+    )
+  } else if (node.level === 3) {
+    return (
+      <div class="custom-tree-node">
+        <i class="iconfont icon-shujuku1"></i>
+        <span>{data.label}</span>
+      </div>
+    )
+  } else {
+    let icon_pic = 'iconfont icon-shitu_biaoge'
+    if (data.value === 'VIEW') {
+      icon_pic = 'iconfont icon-viewList'
+    }
+
+    return (
+      <div class="custom-tree-node">
+        <i class={icon_pic}></i>
+        <el-tooltip class="item" effect="light" placement="left">
+          <div slot="content">{node.label}</div>
+          <span>{data.label}</span>
+        </el-tooltip>
+      </div>
+    )
+  }
+}
+
+// 表格配置项
+const fieldColumns = reactive<ColumnProps<any>[]>([
+  { prop: 'fieldName', label: '名称' },
+  { prop: 'typeName', label: '类型' },
+  { prop: 'fieldType', label: 'jdbcType' },
+  { prop: 'displaySize', label: '长度' },
+  { prop: 'precision', label: '精度' },
+  { prop: 'scale', label: '位数' },
+  { prop: 'isPrimaryKey', label: '主键' },
+  { prop: 'isAutoIncrement', label: '自增' },
+  { prop: 'isNullable', label: '可空' },
+  { prop: 'remarks', label: '注释' }
+])
+const indexColumns = reactive<ColumnProps<any>[]>([
+  { prop: 'indexType', label: '索引类型' },
+  { prop: 'indexName', label: '索引名称' },
+  { prop: 'indexFields', label: '索引字段' }
+])
+const dataColumns = computed(() => {
+  if (activeName.value === '4') {
+    const columns = sampleData.value.columns.map(item => {
+      return {
+        prop: item,
+        label: item
+      }
+    })
+    return columns
+  } else {
+    return []
+  }
+})
+</script>
+<style scoped lang="scss">
+@import './index.scss';
+</style>

+ 0 - 1
src/views/system/user/index.vue

@@ -155,7 +155,6 @@ const roleOptions = ref<any[]>([])
 // 打开弹框的功能
 const openDialog = async (type: number, title: string, row?: any) => {
   let res = await getUserApi(row?.userId || null)
-  debugger
   postOptions.value = res.data.posts
   roleOptions.value = res.data.roles
   // 表单项配置