PreviewCode.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. <template>
  2. <Dialog
  3. title="代码预览"
  4. v-model="dialogVisible"
  5. align-center
  6. width="80%"
  7. class="app-infra-codegen-preview-container"
  8. >
  9. <div class="flex">
  10. <!-- 代码目录树 -->
  11. <el-card
  12. class="w-1/3"
  13. :gutter="12"
  14. shadow="hover"
  15. v-loading="loading"
  16. element-loading-text="生成文件目录中..."
  17. >
  18. <el-scrollbar height="calc(100vh - 88px - 40px - 50px)">
  19. <el-tree
  20. ref="treeRef"
  21. node-key="id"
  22. :data="preview.fileTree"
  23. :expand-on-click-node="false"
  24. highlight-current
  25. @node-click="handleNodeClick"
  26. default-expand-all
  27. />
  28. </el-scrollbar>
  29. </el-card>
  30. <!-- 代码 -->
  31. <el-card
  32. class="w-2/3 ml-3"
  33. :gutter="12"
  34. shadow="hover"
  35. v-loading="loading"
  36. element-loading-text="加载代码中..."
  37. >
  38. <el-tabs v-model="preview.activeName">
  39. <el-tab-pane
  40. v-for="item in previewCodegen"
  41. :label="item.filePath.substring(item.filePath.lastIndexOf('/') + 1)"
  42. :name="item.filePath"
  43. :key="item.filePath"
  44. >
  45. <el-button text type="primary" class="float-right" @click="copy(item.code)">
  46. {{ t('common.copy') }}
  47. </el-button>
  48. <div v-highlight>
  49. <code>{{ item.code }}</code>
  50. </div>
  51. </el-tab-pane>
  52. </el-tabs>
  53. </el-card>
  54. </div>
  55. </Dialog>
  56. </template>
  57. <script setup lang="ts">
  58. import { useClipboard } from '@vueuse/core'
  59. import { handleTree2 } from '@/utils/tree'
  60. import * as CodegenApi from '@/api/infra/codegen'
  61. const { t } = useI18n() // 国际化
  62. const message = useMessage() // 消息弹窗
  63. const dialogVisible = ref(false) // 弹窗的是否展示
  64. const loading = ref(false) // 加载中的状态
  65. const preview = reactive({
  66. fileTree: [], // 文件树
  67. activeName: '' // 激活的文件名
  68. })
  69. const previewCodegen = ref<CodegenApi.CodegenPreviewVO[]>()
  70. /** 点击文件 */
  71. const handleNodeClick = async (data, node) => {
  72. if (node && !node.isLeaf) {
  73. return false
  74. }
  75. preview.activeName = data.id
  76. }
  77. /** 生成 files 目录 **/
  78. interface filesType {
  79. id: string
  80. label: string
  81. parentId: string
  82. }
  83. /** 打开弹窗 */
  84. const open = async (id: number) => {
  85. dialogVisible.value = true
  86. try {
  87. loading.value = true
  88. // 生成代码
  89. const data = await CodegenApi.previewCodegen(id)
  90. previewCodegen.value = data
  91. // 处理文件
  92. let file = handleFiles(data)
  93. preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')
  94. // 点击首个文件
  95. preview.activeName = data[0].filePath
  96. } finally {
  97. loading.value = false
  98. }
  99. }
  100. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  101. /** 处理文件 */
  102. const handleFiles = (datas: CodegenApi.CodegenPreviewVO[]) => {
  103. let exists = {} // key:file 的 id;value:true
  104. let files: filesType[] = []
  105. // 遍历每个元素
  106. for (const data of datas) {
  107. let paths = data.filePath.split('/')
  108. let fullPath = '' // 从头开始的路径,用于生成 id
  109. // 特殊处理 java 文件
  110. if (paths[paths.length - 1].indexOf('.java') >= 0) {
  111. let newPaths: string[] = []
  112. for (let i = 0; i < paths.length; i++) {
  113. let path = paths[i]
  114. if (path !== 'java') {
  115. newPaths.push(path)
  116. continue
  117. }
  118. newPaths.push(path)
  119. // 特殊处理中间的 package,进行合并
  120. let tmp = ''
  121. while (i < paths.length) {
  122. path = paths[i + 1]
  123. if (
  124. path === 'controller' ||
  125. path === 'convert' ||
  126. path === 'dal' ||
  127. path === 'enums' ||
  128. path === 'service' ||
  129. path === 'vo' || // 下面三个,主要是兜底。可能考虑到有人改了包结构
  130. path === 'mysql' ||
  131. path === 'dataobject'
  132. ) {
  133. break
  134. }
  135. tmp = tmp ? tmp + '.' + path : path
  136. i++
  137. }
  138. if (tmp) {
  139. newPaths.push(tmp)
  140. }
  141. }
  142. paths = newPaths
  143. }
  144. // 遍历每个 path, 拼接成树
  145. for (let i = 0; i < paths.length; i++) {
  146. // 已经添加到 files 中,则跳过
  147. let oldFullPath = fullPath
  148. // 下面的 replaceAll 的原因,是因为上面包处理了,导致和 tabs 不匹配,所以 replaceAll 下
  149. fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i]
  150. if (exists[fullPath]) {
  151. continue
  152. }
  153. // 添加到 files 中
  154. exists[fullPath] = true
  155. files.push({
  156. id: fullPath,
  157. label: paths[i],
  158. parentId: oldFullPath || '/' // "/" 为根节点
  159. })
  160. }
  161. }
  162. return files
  163. }
  164. /** 复制 **/
  165. const copy = async (text: string) => {
  166. const { copy, copied, isSupported } = useClipboard({ source: text })
  167. if (!isSupported) {
  168. message.error(t('common.copyError'))
  169. return
  170. }
  171. await copy()
  172. if (unref(copied)) {
  173. message.success(t('common.copySuccess'))
  174. }
  175. }
  176. </script>
  177. <style lang="scss">
  178. .app-infra-codegen-preview-container {
  179. .el-scrollbar .el-scrollbar__wrap .el-scrollbar__view {
  180. white-space: nowrap;
  181. display: inline-block;
  182. }
  183. }
  184. </style>