PreviewCode.vue 6.3 KB

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