visualdata.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. <template>
  2. <div class="sys-database-container">
  3. <el-card shadow="hover" :body-style="{ paddingBottom: '0' }">
  4. <el-form :model="state.queryParams" ref="queryForm" :inline="true" v-loading="state.loading">
  5. <el-form-item label="库名">
  6. <el-select v-model="state.configId" placeholder="库名" filterable @change="handleQueryTable">
  7. <el-option v-for="item in state.dbData" :key="item" :label="item" :value="item" />
  8. </el-select>
  9. </el-form-item>
  10. <el-form-item label="表名">
  11. <el-select v-model="state.tableName" placeholder="表名" filterable clearable @change="handleQueryColumn">
  12. <el-option v-for="item in state.tableData" :key="item.name" :label="item.name + '[' + item.description + ']'" :value="item.name" />
  13. </el-select>
  14. </el-form-item>
  15. <el-form-item>
  16. <el-button icon="ele-Edit" type="primary" @click="openEditTable"> 编辑表 </el-button>
  17. <el-button icon="ele-Delete" type="danger" @click="delTable"> 删除表 </el-button>
  18. <el-button icon="ele-Plus" @click="openAddTable"> 增加表 </el-button>
  19. <el-button icon="ele-Plus" @click="openAddColumn"> 增加列 </el-button>
  20. <el-button icon="ele-Plus" @click="openGenDialog"> 生成实体 </el-button>
  21. <el-button icon="ele-Plus" @click="openGenSeedDataDialog"> 生成种子数据 </el-button>
  22. </el-form-item>
  23. </el-form>
  24. </el-card>
  25. <div style="height:calc(100vh - 60px);">
  26. <RelationGraph
  27. ref="graphRef"
  28. :options="graphOptions"
  29. :on-node-click="onNodeClick"
  30. :on-line-click="onLineClick">
  31. <template #graph-plug>
  32. <!-- To facilitate understanding, the CSS style code is directly embedded here. -->
  33. <div style="z-index: 300;position: absolute;left:10px; top: calc(100% - 50px);font-size: 12px;background-color: #ffffff;border:#efefef solid 1px;border-radius: 10px;width:260px;height:40px;display: flex;align-items: center;justify-content: center;">
  34. Legend:
  35. <div>
  36. More to one
  37. <div style="height:5px;width:80px;background-color: rgba(159,23,227,0.65);"></div>
  38. </div>
  39. <div style="margin-left:10px;">
  40. One to one
  41. <div style="height:5px;width:80px;background-color: rgba(29,169,245,0.76);"></div>
  42. </div>
  43. </div>
  44. </template>
  45. <template #canvas-plug>
  46. <!--- You can put some elements that are not allowed to be dragged here --->
  47. </template>
  48. <template #node="{node}">
  49. <div style="width: 300px;background-color: #f39930"><!---------------- if node a ---------------->
  50. <div>{{node.text}} - {{node.data.columns.length}} Cols</div>
  51. <table class="c-data-table">
  52. <tr>
  53. <th>Column Name</th>
  54. <th>Data Type</th>
  55. </tr>
  56. <template v-for="column of node.data.columns" :key="column.columnName">
  57. <tr>
  58. <td><div :id="`${node.id}-${column.columnName}`">{{column.columnName}}</div></td>
  59. <td>{{column.dataType}}</td>
  60. </tr>
  61. </template>
  62. </table>
  63. </div>
  64. </template>
  65. </RelationGraph>
  66. </div>
  67. </div>
  68. </template>
  69. <script lang="ts" setup name="sysDatabase">
  70. import { onMounted, reactive, ref,defineComponent } from 'vue';
  71. import { ElMessageBox, ElMessage } from 'element-plus';
  72. import RelationGraph from 'relation-graph/vue3';
  73. import type { RGOptions, RGNode, RGLine, RGLink, RGUserEvent, RGJsonData, RelationGraphComponent } from 'relation-graph/vue3';
  74. import EditTable from '/@/views/system/database/component/editTable.vue';
  75. import EditColumn from '/@/views/system/database/component/editColumn.vue';
  76. import AddTable from '/@/views/system/database/component/addTable.vue';
  77. import AddColumn from '/@/views/system/database/component/addColumn.vue';
  78. import GenEntity from '/@/views/system/database/component/genEntity.vue';
  79. import GenSeedData from '/@/views/system/database/component/genSeedData.vue';
  80. import { getAPI } from '/@/utils/axios-utils';
  81. import { SysDatabaseApi, SysCodeGenApi } from '/@/api-services/api';
  82. import { DbColumnOutput, DbTableInfo, DbColumnInput, DeleteDbTableInput, DeleteDbColumnInput } from '/@/api-services/models';
  83. const editTableRef = ref<InstanceType<typeof EditTable>>();
  84. const editColumnRef = ref<InstanceType<typeof EditColumn>>();
  85. const addTableRef = ref<InstanceType<typeof AddTable>>();
  86. const addColumnRef = ref<InstanceType<typeof AddColumn>>();
  87. const genEntityRef = ref<InstanceType<typeof GenEntity>>();
  88. const genSeedDataRef = ref<InstanceType<typeof GenSeedData>>();
  89. const state = reactive({
  90. loading: false,
  91. loading1: false,
  92. dbData: [] as any,
  93. configId: '',
  94. tableData: [] as Array<DbTableInfo>,
  95. visualTable: [],
  96. visualRTable: [],
  97. tableName: '',
  98. columnData: [] as Array<DbColumnOutput>,
  99. queryParams: {
  100. name: undefined,
  101. code: undefined,
  102. },
  103. editPosTitle: '',
  104. appNamespaces: [] as Array<String>, // 存储位置
  105. //graphOptions: {
  106. // defaultNodeBorderWidth: 0,
  107. // defaultNodeColor: "rgba(238, 178, 94, 1)",
  108. // allowSwitchLineShape: true,
  109. // allowSwitchJunctionPoint: true,
  110. // defaultLineShape: 1,
  111. // layouts: [
  112. // {
  113. // label: "自动布局",
  114. // layoutName: "force",
  115. // layoutClassName: "seeks-layout-force",
  116. // distance_coefficient: 3
  117. // },
  118. // ],
  119. // defaultJunctionPoint: "border",
  120. // },
  121. });
  122. onMounted(async () => {
  123. showGraph();
  124. state.loading = true;
  125. var res = await getAPI(SysDatabaseApi).apiSysDatabaseListGet();
  126. state.dbData = res.data.result;
  127. state.loading = false;
  128. let appNamesRes = await getAPI(SysCodeGenApi).apiSysCodeGenApplicationNamespacesGet();
  129. state.appNamespaces = appNamesRes.data.result as Array<string>;
  130. });
  131. // 增加表
  132. const addTableSubmitted = (e: any) => {
  133. handleQueryTable();
  134. state.tableName = e;
  135. handleQueryColumn();
  136. };
  137. // 表查询操作
  138. const handleQueryTable = async () => {
  139. state.tableName = '';
  140. state.columnData = [];
  141. state.loading = true;
  142. var resVisualTable = await getAPI(SysDatabaseApi).apiGetVisualTableList(state.configId);
  143. state.visualTable = resVisualTable.data.result ?? [];
  144. var resVisualRTable = await getAPI(SysDatabaseApi).apiGetVisualRTableList(state.configId);
  145. state.visualRTable = resVisualRTable.data.result ?? [];
  146. var res = await getAPI(SysDatabaseApi).apiSysDatabaseTableListConfigIdGet(state.configId);
  147. state.tableData = res.data.result ?? [];
  148. state.loading = false;
  149. };
  150. // 列查询操作
  151. const handleQueryColumn = async () => {
  152. state.columnData = [];
  153. if (state.tableName == '') return;
  154. state.loading1 = true;
  155. var res = await getAPI(SysDatabaseApi).apiSysDatabaseColumnListTableNameConfigIdGet(state.tableName, state.configId);
  156. state.columnData = res.data.result ?? [];
  157. state.loading1 = false;
  158. };
  159. // 获取可视化表和字段
  160. const getVisualTableList = async () => {
  161. state.columnData = [];
  162. if (state.tableName == '') return;
  163. state.loading1 = true;
  164. var res = await getAPI(SysDatabaseApi).apiGetVisualTableList();
  165. state.columnData = res.data.result ?? [];
  166. state.loading1 = false;
  167. };
  168. // 获取可视化表关系
  169. const getVisualRTableList = async () => {
  170. state.columnData = [];
  171. if (state.tableName == '') return;
  172. state.loading1 = true;
  173. var res = await getAPI(SysDatabaseApi).apiGetVisualRTableList();
  174. state.columnData = res.data.result ?? [];
  175. state.loading1 = false;
  176. };
  177. // 打开表编辑页面
  178. const openEditTable = () => {
  179. if (state.configId == '' || state.tableName == '') return;
  180. var res = state.tableData.filter((u: any) => u.name == state.tableName);
  181. var table: any = {
  182. configId: state.configId,
  183. tableName: state.tableName,
  184. oldTableName: state.tableName,
  185. description: res[0].description,
  186. };
  187. editTableRef.value?.openDialog(table);
  188. };
  189. // 打开实体生成页面
  190. const openGenDialog = () => {
  191. if (state.configId == '' || state.tableName == '') return;
  192. // var res = state.tableData.filter((u: any) => u.name == state.tableName);
  193. var table: any = {
  194. configId: state.configId,
  195. tableName: state.tableName,
  196. position: state.appNamespaces[0],
  197. };
  198. genEntityRef.value?.openDialog(table);
  199. };
  200. // 生成种子数据页面
  201. const openGenSeedDataDialog = () => {
  202. if (state.configId == '' || state.tableName == '') return;
  203. var table: any = {
  204. configId: state.configId,
  205. tableName: state.tableName,
  206. position: state.appNamespaces[0],
  207. };
  208. genSeedDataRef.value?.openDialog(table);
  209. };
  210. // 打开表增加页面
  211. const openAddTable = () => {
  212. if (state.configId == '') {
  213. ElMessage({
  214. type: 'error',
  215. message: `请选择库名!`,
  216. });
  217. return;
  218. }
  219. var table: any = {
  220. configId: state.configId,
  221. tableName: '',
  222. oldTableName: '',
  223. description: '',
  224. };
  225. addTableRef.value?.openDialog(table);
  226. };
  227. // 打开列编辑页面
  228. const openEditColumn = (row: any) => {
  229. var column: any = {
  230. configId: state.configId,
  231. tableName: row.tableName,
  232. columnName: row.dbColumnName,
  233. oldColumnName: row.dbColumnName,
  234. description: row.columnDescription,
  235. };
  236. editColumnRef.value?.openDialog(column);
  237. };
  238. // 打开列增加页面
  239. const openAddColumn = () => {
  240. if (state.configId == '' || state.tableName == '') {
  241. ElMessage({
  242. type: 'error',
  243. message: `请选择库名和表名!`,
  244. });
  245. return;
  246. }
  247. const addRow: DbColumnInput = {
  248. configId: state.configId,
  249. tableName: state.tableName,
  250. columnDescription: '',
  251. dataType: '',
  252. dbColumnName: '',
  253. decimalDigits: 0,
  254. isIdentity: 0,
  255. isNullable: 0,
  256. isPrimarykey: 0,
  257. length: 0,
  258. // key: 0,
  259. // editable: true,
  260. // isNew: true,
  261. };
  262. addColumnRef.value?.openDialog(addRow);
  263. };
  264. // 删除表
  265. const delTable = () => {
  266. ElMessageBox.confirm(`确定删除表:【${state.tableName}】?`, '提示', {
  267. confirmButtonText: '确定',
  268. cancelButtonText: '取消',
  269. type: 'warning',
  270. })
  271. .then(async () => {
  272. const deleteDbTableInput: DeleteDbTableInput = {
  273. configId: state.configId,
  274. tableName: state.tableName,
  275. };
  276. await getAPI(SysDatabaseApi).apiSysDatabaseDeleteTablePost(deleteDbTableInput);
  277. handleQueryTable();
  278. ElMessage.success('表删除成功');
  279. })
  280. .catch(() => {});
  281. };
  282. // 删除列
  283. const delColumn = (row: any) => {
  284. ElMessageBox.confirm(`确定删除列:【${row.dbColumnName}】?`, '提示', {
  285. confirmButtonText: '确定',
  286. cancelButtonText: '取消',
  287. type: 'warning',
  288. })
  289. .then(async () => {
  290. const eleteDbColumnInput: DeleteDbColumnInput = {
  291. configId: state.configId,
  292. tableName: state.tableName,
  293. dbColumnName: row.dbColumnName,
  294. };
  295. await getAPI(SysDatabaseApi).apiSysDatabaseDeleteColumnPost(eleteDbColumnInput);
  296. handleQueryTable();
  297. ElMessage.success('列删除成功');
  298. })
  299. .catch(() => {});
  300. };
  301. const graphRef = ref<RelationGraphComponent | null>(null);
  302. const graphOptions: RGOptions = {
  303. debug: false,
  304. allowSwitchLineShape: true,
  305. allowSwitchJunctionPoint: true,
  306. allowShowDownloadButton: true,
  307. defaultJunctionPoint: 'border',
  308. placeOtherNodes: false,
  309. placeSingleNode: false,
  310. graphOffset_x: -200,
  311. graphOffset_y: 100,
  312. defaultLineMarker: {
  313. markerWidth: 20,
  314. markerHeight: 20,
  315. refX: 3,
  316. refY: 3,
  317. data: "M 0 0, V 6, L 4 3, Z"
  318. },
  319. layout: {
  320. layoutName: 'fixed'
  321. }
  322. // You can refer to the parameters in "Graph" for setting here
  323. };
  324. const showGraph = async() => {
  325. const tables = [
  326. { tableName: 'SYS_USER', tableComents: 'SYS_USER comments', x: 500, y: -300 },
  327. { tableName: 'SYS_DEPT', tableComents: 'SYS_DEPT comments', x: 0, y: -300 },
  328. { tableName: 'SYS_ROLE', tableComents: 'SYS_ROLE comments', x: 0, y: 0 },
  329. { tableName: 'SYS_USER_ROLE', tableComents: 'SYS_USER_ROLE comments', x: 500, y: 0 },
  330. { tableName: 'SYS_RESOURCE', tableComents: 'SYS_RESOURCE comments', x: 0, y: 300 },
  331. { tableName: 'SYS_ROLE_RESOURCE', tableComents: 'SYS_ROLE_RESOURCE comments', x: 500, y: 300 }
  332. ];
  333. const tableCols = [
  334. { tableName: 'SYS_USER', columnName: 'id', dataType: 'varchar(36)'},
  335. { tableName: 'SYS_USER', columnName: 'user_name', dataType: 'varchar(50)'},
  336. { tableName: 'SYS_USER', columnName: 'dept_id', dataType: 'varchar(36)'},
  337. { tableName: 'SYS_USER', columnName: 'create_time', dataType: 'TIMESTAMP'},
  338. { tableName: 'SYS_USER', columnName: 'status', dataType: 'varchar(1)'},
  339. { tableName: 'SYS_DEPT', columnName: 'id', dataType: 'varchar(36)'},
  340. { tableName: 'SYS_DEPT', columnName: 'dept_name', dataType: 'varchar(50)'},
  341. { tableName: 'SYS_DEPT', columnName: 'parent_dept_id', dataType: 'varchar(50)'},
  342. { tableName: 'SYS_DEPT', columnName: 'create_time', dataType: 'TIMESTAMP'},
  343. { tableName: 'SYS_DEPT', columnName: 'status', dataType: 'varchar(50)'},
  344. { tableName: 'SYS_ROLE', columnName: 'id', dataType: 'varchar(36)'},
  345. { tableName: 'SYS_ROLE', columnName: 'role_name', dataType: 'varchar(36)'},
  346. { tableName: 'SYS_ROLE', columnName: 'create_time', dataType: 'TIMESTAMP'},
  347. { tableName: 'SYS_USER_ROLE', columnName: 'id', dataType: 'varchar(36)'},
  348. { tableName: 'SYS_USER_ROLE', columnName: 'user_id', dataType: 'varchar(36)'},
  349. { tableName: 'SYS_USER_ROLE', columnName: 'role_id', dataType: 'varchar(36)'},
  350. { tableName: 'SYS_USER_ROLE', columnName: 'create_time', dataType: 'TIMESTAMP'},
  351. { tableName: 'SYS_USER_ROLE', columnName: 'status', dataType: 'varchar(36)'},
  352. { tableName: 'SYS_RESOURCE', columnName: 'id', dataType: 'varchar(36)'},
  353. { tableName: 'SYS_RESOURCE', columnName: 'resource_name', dataType: 'varchar(36)'},
  354. { tableName: 'SYS_RESOURCE', columnName: 'create_time', dataType: 'TIMESTAMP'},
  355. { tableName: 'SYS_ROLE_RESOURCE', columnName: 'id', dataType: 'varchar(36)'},
  356. { tableName: 'SYS_ROLE_RESOURCE', columnName: 'role_id', dataType: 'varchar(36)'},
  357. { tableName: 'SYS_ROLE_RESOURCE', columnName: 'resource_id', dataType: 'varchar(36)'},
  358. { tableName: 'SYS_ROLE_RESOURCE', columnName: 'status', dataType: 'varchar(1)'},
  359. ];
  360. const columnRelations = [
  361. { sourceTableName: 'SYS_USER', sourceColumnName: 'dept_id', type: 'MORE_TO_ONE', targetTableName: 'SYS_DEPT', targetColumnName: 'id' },
  362. { sourceTableName: 'SYS_DEPT', sourceColumnName: 'parent_dept_id', type: 'ONE_TO_ONE', targetTableName: 'SYS_DEPT', targetColumnName: 'id' },
  363. { sourceTableName: 'SYS_USER_ROLE', sourceColumnName: 'user_id', type: 'MORE_TO_ONE', targetTableName: 'SYS_USER', targetColumnName: 'id' },
  364. { sourceTableName: 'SYS_USER_ROLE', sourceColumnName: 'role_id', type: 'MORE_TO_ONE', targetTableName: 'SYS_ROLE', targetColumnName: 'id' },
  365. { sourceTableName: 'SYS_ROLE_RESOURCE', sourceColumnName: 'role_id', type: 'MORE_TO_ONE', targetTableName: 'SYS_ROLE', targetColumnName: 'id' },
  366. { sourceTableName: 'SYS_ROLE_RESOURCE', sourceColumnName: 'resource_id', type: 'MORE_TO_ONE', targetTableName: 'SYS_RESOURCE', targetColumnName: 'id' },
  367. ]
  368. const graphNodes = tables.map(table => {
  369. const { tableName, tableComents, x, y} = table;
  370. return {
  371. id: tableName,
  372. text: tableComents,
  373. x,
  374. y,
  375. nodeShape: 1,
  376. data: { // Costomer key have to in data
  377. columns: tableCols.filter(col => col.tableName === table.tableName)
  378. }
  379. }
  380. });
  381. const graphLines = columnRelations.map(relation => {
  382. return {
  383. from: relation.sourceTableName + '-' + relation.sourceColumnName, // HtmlElement id
  384. to: relation.targetTableName + '-' + relation.targetColumnName, // HtmlElement id
  385. color: relation.type === 'ONE_TO_ONE' ? 'rgba(29,169,245,0.76)' : 'rgba(159,23,227,0.65)',
  386. text: '',
  387. fromJunctionPoint: 'left',
  388. toJunctionPoint: 'lr',
  389. lineShape: 6,
  390. lineWidth: 3
  391. }
  392. });
  393. const graphJsonData: RGJsonData = {
  394. nodes: graphNodes,
  395. lines: [
  396. ],
  397. elementLines: graphLines
  398. };
  399. const graphInstance = graphRef.value?.getInstance();
  400. if (graphInstance) {
  401. await graphInstance.setJsonData(graphJsonData);
  402. await graphInstance.moveToCenter();
  403. await graphInstance.zoomToFit();
  404. }
  405. };
  406. const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
  407. console.log('onNodeClick:', nodeObject);
  408. };
  409. const onLineClick = (lineObject: RGLine, linkObject: RGLink, $event: RGUserEvent) => {
  410. console.log('onLineClick:', lineObject);
  411. };
  412. </script>
  413. <style lang="scss" scoped>
  414. ::v-deep(.relation-graph) {
  415. .rel-node-shape-1 {
  416. overflow: hidden;
  417. }
  418. }
  419. .c-data-table{
  420. background-color: #ffffff;
  421. border-collapse:collapse;
  422. width: 100%;
  423. }
  424. .c-data-table td,.c-data-table th{
  425. border:1px solid #f39930;
  426. color: #333333;
  427. padding: 5px;
  428. padding-left: 20px;
  429. padding-right: 20px;
  430. }
  431. .c-data-table td div,.c-data-table th div{
  432. background-color: #1da9f5;
  433. color: #ffffff;
  434. border-radius: 5px;
  435. }
  436. </style>