PreviewCode.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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. <div>
  49. <pre><code class="hljs" v-html="highlightedCode(item)"></code></pre>
  50. </div>
  51. </el-tab-pane>
  52. </el-tabs>
  53. </el-card>
  54. </div>
  55. </Dialog>
  56. </template>
  57. <script lang="ts" name="InfraCodegenPreviewCode" 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. const { t } = useI18n() // 国际化
  69. const message = useMessage() // 消息弹窗
  70. const dialogVisible = ref(false) // 弹窗的是否展示
  71. const loading = ref(false) // 加载中的状态
  72. const preview = reactive({
  73. fileTree: [], // 文件树
  74. activeName: '' // 激活的文件名
  75. })
  76. const previewCodegen = ref<CodegenApi.CodegenPreviewVO[]>()
  77. /** 点击文件 */
  78. const handleNodeClick = async (data, node) => {
  79. if (node && !node.isLeaf) {
  80. return false
  81. }
  82. preview.activeName = data.id
  83. }
  84. /** 生成 files 目录 **/
  85. interface filesType {
  86. id: string
  87. label: string
  88. parentId: string
  89. }
  90. /** 打开弹窗 */
  91. const open = async (id: number) => {
  92. dialogVisible.value = true
  93. try {
  94. loading.value = true
  95. // 生成代码
  96. const data = await CodegenApi.previewCodegen(id)
  97. previewCodegen.value = data
  98. // 处理文件
  99. let file = handleFiles(data)
  100. preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')
  101. // 点击首个文件
  102. preview.activeName = data[0].filePath
  103. } finally {
  104. loading.value = false
  105. }
  106. }
  107. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  108. /** 处理文件 */
  109. const handleFiles = (datas: CodegenApi.CodegenPreviewVO[]) => {
  110. let exists = {} // key:file 的 id;value:true
  111. let files: filesType[] = []
  112. // 遍历每个元素
  113. for (const data of datas) {
  114. let paths = data.filePath.split('/')
  115. let fullPath = '' // 从头开始的路径,用于生成 id
  116. // 特殊处理 java 文件
  117. if (paths[paths.length - 1].indexOf('.java') >= 0) {
  118. let newPaths: string[] = []
  119. for (let i = 0; i < paths.length; i++) {
  120. let path = paths[i]
  121. if (path !== 'java') {
  122. newPaths.push(path)
  123. continue
  124. }
  125. newPaths.push(path)
  126. // 特殊处理中间的 package,进行合并
  127. let tmp = ''
  128. while (i < paths.length) {
  129. path = paths[i + 1]
  130. if (
  131. path === 'controller' ||
  132. path === 'convert' ||
  133. path === 'dal' ||
  134. path === 'enums' ||
  135. path === 'service' ||
  136. path === 'vo' || // 下面三个,主要是兜底。可能考虑到有人改了包结构
  137. path === 'mysql' ||
  138. path === 'dataobject'
  139. ) {
  140. break
  141. }
  142. tmp = tmp ? tmp + '.' + path : path
  143. i++
  144. }
  145. if (tmp) {
  146. newPaths.push(tmp)
  147. }
  148. }
  149. paths = newPaths
  150. }
  151. // 遍历每个 path, 拼接成树
  152. for (let i = 0; i < paths.length; i++) {
  153. // 已经添加到 files 中,则跳过
  154. let oldFullPath = fullPath
  155. // 下面的 replaceAll 的原因,是因为上面包处理了,导致和 tabs 不匹配,所以 replaceAll 下
  156. fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i]
  157. if (exists[fullPath]) {
  158. continue
  159. }
  160. // 添加到 files 中
  161. exists[fullPath] = true
  162. files.push({
  163. id: fullPath,
  164. label: paths[i],
  165. parentId: oldFullPath || '/' // "/" 为根节点
  166. })
  167. }
  168. }
  169. return files
  170. }
  171. /** 复制 **/
  172. const copy = async (text: string) => {
  173. const { copy, copied, isSupported } = useClipboard({ source: text })
  174. if (!isSupported) {
  175. message.error(t('common.copyError'))
  176. return
  177. }
  178. await copy()
  179. if (unref(copied)) {
  180. message.success(t('common.copySuccess'))
  181. }
  182. }
  183. /**
  184. * 代码高亮
  185. */
  186. const highlightedCode = (item) => {
  187. const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
  188. const result = hljs.highlight(language, item.code || '', true)
  189. return result.value || '&nbsp;'
  190. }
  191. /** 初始化 **/
  192. onMounted(async () => {
  193. // 注册代码高亮的各种语言
  194. hljs.registerLanguage('java', java)
  195. hljs.registerLanguage('xml', xml)
  196. hljs.registerLanguage('html', xml)
  197. hljs.registerLanguage('vue', xml)
  198. hljs.registerLanguage('javascript', javascript)
  199. hljs.registerLanguage('sql', sql)
  200. hljs.registerLanguage('typescript', typescript)
  201. })
  202. </script>
  203. <style lang="scss">
  204. .app-infra-codegen-preview-container {
  205. .el-scrollbar .el-scrollbar__wrap .el-scrollbar__view {
  206. white-space: nowrap;
  207. display: inline-block;
  208. }
  209. }
  210. </style>