|
@@ -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>
|