GitLab Viewer Publish and Deploy Project

GitLab Viewer Publish and Deploy Project!

目前为 2023-05-22 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name GitLab Viewer Publish and Deploy Project
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description GitLab Viewer Publish and Deploy Project!
  6. // @author Sean
  7. // @match http://192.168.0.200/fe3project/*
  8. // @icon http://192.168.0.200/assets/favicon-7901bd695fb93edb07975966062049829afb56cf11511236e61bcf425070e36e.png
  9. // @require https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js
  10. // @require https://unpkg.com/element-ui/lib/index.js
  11. // @require https://cdn.jsdelivr.net/npm/jszip@3.7.1/dist/jszip.min.js
  12. // @resource myFontFile https://element.eleme.io/2.11/static/element-icons.535877f.woff
  13. // @grant GM_getResourceURL
  14. // @grant GM_addStyle
  15. // @grant GM_getResourceText
  16. // @resource ElementCSS https://unpkg.com/element-ui/lib/theme-chalk/index.css
  17. // @grant GM_xmlhttpRequest
  18. // @license MIT
  19. // @run-at document-end
  20. // ==/UserScript==
  21.  
  22. (function() {
  23. 'use strict';
  24.  
  25. const fontUrl = 'https://element.eleme.io/2.11/static/element-icons.535877f.woff';
  26.  
  27. // 添加样式规则,将字体应用到指定元素上
  28. GM_addStyle(`
  29. @font-face {
  30. font-family: element-icons;
  31. src: url(${fontUrl}) format("woff");
  32. }
  33. `);
  34.  
  35. GM_addStyle(GM_getResourceText('ElementCSS'));
  36.  
  37. let epointCss = ".epoint-tool {position: fixed; bottom: 0%; right:10px; transform: translateY(-50%);}";
  38. epointCss += ".el-row { padding: 3px 0;} .el-dialog__body .el-tree{min-height: 420px; max-height: 500px;overflow: auto;}";
  39. epointCss += ".deploy-body {height: 306px;}"
  40. epointCss += ".deploy-body .el-loading-spinner {margin-top: -50px;}";
  41. // 添加注入样式
  42. let extraStyleElement = document.createElement("style");
  43. extraStyleElement.innerHTML = epointCss;
  44. document.head.appendChild(extraStyleElement);
  45.  
  46. const MyComponent = {
  47. template: `<div class="epoint-wrap">
  48. <div class="epoint-tool">
  49. <el-row><el-button type="primary" icon="el-icon-search" round @click="viewProject">查看</el-button></el-row>
  50. <el-row>
  51. <el-tooltip content="一键部署到170服务器" placement="top" effect="light">
  52. <el-button type="primary" icon="el-icon-s-unfold" round @click="doDeploy">部署</el-button>
  53. </el-tooltip>
  54. </el-row>
  55. <el-row>
  56. <el-tooltip content="一键发布到项目案例库" placement="top" effect="light">
  57. <el-button type="primary" icon="el-icon-upload" round @click="publish">发布</el-button>
  58. </el-tooltip>
  59. </el-row>
  60. </div>
  61. <el-dialog
  62. title="目录结构"
  63. width="900px"
  64. :append-to-body="true"
  65. :visible.sync="dialogVisible"
  66. :before-close="handleClose">
  67. <el-tree
  68. class="filter-tree"
  69. :data="data"
  70. :props="defaultProps"
  71. node-key="id"
  72. default-expand-all
  73. @node-click="handleNodeClick"
  74. v-loading="loadingTree"
  75. ref="tree">
  76. </el-tree>
  77. </el-dialog>
  78. <el-dialog
  79. title="部署"
  80. width="420px"
  81. :visible.sync="depDialogVisible">
  82. <div class="deploy-body"
  83. v-loading="loading"
  84. element-loading-text="正在打包部署至 170 服务器,请耐心等待"
  85. element-loading-spinner="el-icon-loading">
  86. </div>
  87. </el-dialog>
  88. </div>`,
  89. data() {
  90. return {
  91. dialogVisible: false, // 查看目录结构弹窗
  92. data: [], // 目录结构树的数据结构
  93. defaultProps: {
  94. children: 'children',
  95. label: 'label'
  96. },
  97. loadingTree: false,
  98. depDialogVisible: false, // 部署中弹窗
  99. loading: false,
  100. projectLibUrl: 'http://192.168.201.159:9999/webapp/pages/default/onlinecase.html', // 项目案例库地址
  101. projectIsDeployed: false, // 项目是否部署过
  102. };
  103. },
  104. methods: {
  105. handleClose(done) {
  106. done();
  107. /*
  108. this.$confirm('确认关闭?')
  109. .then(_ => {
  110. done();
  111. })
  112. .catch(_ => {});*/
  113. },
  114. handleNodeClick(data) {
  115. console.log(data);
  116. },
  117. // 部署
  118. doDeploy () {
  119. this.$confirm('此操作将把 GitLab 资源打包部署至 170 服务器,已部署过的项目会进行覆盖部署,是否继续?', '提示', {
  120. confirmButtonText: '确定',
  121. cancelButtonText: '取消',
  122. type: 'warning'
  123. }).then(() => {
  124. this.depDialogVisible = true;
  125. this.loading = true;
  126. // 部署
  127. this.getDeployInfo({ type: '1' }, (data)=> {
  128. this.loading = false;
  129. this.depDialogVisible = false;
  130.  
  131. this.$message({
  132. type: 'success',
  133. message: '部署成功!'
  134. });
  135.  
  136. // this.data = data;
  137. // 打开查看弹窗
  138. });
  139. }).catch(() => {
  140. this.$message({
  141. type: 'info',
  142. message: '已取消部署'
  143. });
  144. });
  145. },
  146. // 发布项目案例库
  147. publish () {
  148. this.$confirm('此操作将把项目发布至 <a href="'+ this.projectLibUrl +'" target="_blank">项目案例库</a>,已发布过的项目案例库会有重复项,是否继续?', '提示', {
  149. confirmButtonText: '确定',
  150. cancelButtonText: '取消',
  151. dangerouslyUseHTMLString: true,
  152. type: 'warning'
  153. }).then(() => {
  154. const themes = document.querySelectorAll('.badge-secondary');
  155. let keys = [];
  156.  
  157. for(let i = 0, l = themes.length; i < l; i++) {
  158. if(themes[0].innerText == '智能设备' && i > 0) {
  159. keys.push(themes[i].innerText);
  160. } else if (themes[0].innerText !== '智能设备' && i > 1) {
  161. keys.push(themes[i].innerText);
  162. }
  163.  
  164. }
  165. // 组织项目案例库所需参数
  166. const projectName = document.querySelector('.home-panel-title').innerText;
  167. const projectBU = themes[0] ? themes[0].innerText : null;
  168. const projectType = themes[1] ? themes[0].innerText : null;
  169. const projectKeys = keys.join(' ');
  170. const entryUrl = 'http://test.html';
  171. this.$message({
  172. type: 'success',
  173. message: this.projectLibUrl + '?projectName=' + projectName + '&projectBU=' + projectBU + '&projectType=' + projectType + '&projectKeys=' + projectKeys + '&entryUrl=' + entryUrl
  174. });
  175. }).catch(() => {
  176. this.$message({
  177. type: 'info',
  178. message: '已取消发布'
  179. });
  180. });
  181. },
  182. // 查看项目
  183. viewProject () {
  184. this.dialogVisible = true;
  185. this.loadingTree = true;
  186. let self = this;
  187.  
  188. // 发送ajax请求,查看是否进行过部署
  189. this.getDeployInfo((data)=> {
  190. // 有部署信息,直接赋值,
  191. if(true) {
  192. self.projectIsDeployed = true;
  193. // self.data = data;
  194. } else {
  195. // 无部署信息,仅查看文件目录
  196. getZipResource((data)=> {
  197. self.data = data;
  198. self.loadingTree = false;
  199. });
  200. }
  201. });
  202.  
  203. },
  204. // 项目部署信息
  205. getDeployInfo(params, callback) {
  206. const projectId = document.body.getAttribute('data-project-id');
  207. const downloadUrl = window.location.origin + document.querySelector('.gl-button.btn-sm.btn-confirm').getAttribute('href');
  208.  
  209. if(typeof params == 'function') {
  210. callback = params;
  211. params = null;
  212. }
  213.  
  214. if(projectId && projectId.length && downloadUrl) {
  215. GM_xmlhttpRequest({
  216. method: 'POST',
  217. url: '',
  218. data: JSON.stringify({
  219. projectId: projectId,
  220. downloadUrl: downloadUrl,
  221. type: (params && params.type !== undefined) ? params.type : '0'// 0 代表查看, 1代表部署
  222. }),
  223. onload: function (res) {
  224. var data = JSON.parse(res.response).data;
  225. callback && callback(data);
  226. }
  227. });
  228. } else {
  229. console.error('部署信息请求参数error');
  230. }
  231. }
  232. },
  233. mounted() {
  234.  
  235. }
  236. };
  237.  
  238. const placeholder = document.createElement('div');
  239.  
  240. // 创建 Vue 实例并挂载到页面
  241. const vueInstance = new Vue({
  242. el: placeholder,
  243. components: {
  244. MyComponent
  245. },
  246. methods: {
  247. },
  248. template: `<my-component></my-component>`
  249. });
  250.  
  251. // 等待页面加载完成
  252. window.addEventListener('load', function() {
  253. // 将占位元素追加到 body 元素中
  254. document.body.appendChild(vueInstance.$el);
  255. });
  256.  
  257. // 将文件条目组织成嵌套结构
  258. function organizeFileEntries(fileEntries) {
  259. const root = {
  260. label: document.querySelector('.home-panel-title').innerText || document.getElementById('project_name_edit').value,
  261. type: 'folder',
  262. children: []
  263. };
  264.  
  265. // 创建嵌套结构
  266. fileEntries.forEach(entry => {
  267. const pathSegments = entry.name.split('/');
  268. let currentFolder = root;
  269.  
  270. // 遍历路径中的每个部分,创建相应的文件夹节点
  271. for (let i = 0; i < pathSegments.length - 1; i++) {
  272. const folderName = pathSegments[i];
  273. let folder = currentFolder.children.find(child => child.label === folderName);
  274.  
  275. if(isExcludeFolder(entry.name)) {
  276. continue;
  277. }
  278.  
  279. if (!folder) {
  280. folder = {
  281. label: folderName,
  282. type: 'folder',
  283. children: []
  284. };
  285. currentFolder.children.push(folder);
  286. }
  287.  
  288. currentFolder = folder;
  289. }
  290.  
  291. // 创建文件节点并添加到相应的文件夹中
  292. const fileName = pathSegments[pathSegments.length - 1];
  293.  
  294. if(fileName && fileName.length && isIncludeFile(fileName) && !isExcludeFolder(entry.name)) {
  295. const fileNode = {
  296. label: fileName,
  297. type: 'file',
  298. entry: entry.name
  299. };
  300. currentFolder.children.push(fileNode);
  301. }
  302. });
  303.  
  304. return [root];
  305. }
  306. // 是否排除的文件夹
  307. function isExcludeFolder(entry) {
  308. if(entry.indexOf('css') > -1 ||
  309. entry.indexOf('scss') > -1 ||
  310. entry.indexOf('js') > -1 ||
  311. entry.indexOf('images') > -1 ||
  312. entry.indexOf('fui') > -1 ||
  313. entry.indexOf('lib') > -1 ||
  314. entry.indexOf('test') > -1 ||
  315. entry.indexOf('font') > -1 ||
  316. entry.indexOf('frame/fui') > -1) {
  317. return true;
  318. } else {
  319. return false;
  320. }
  321. }
  322. // 是否包含的文件
  323. function isIncludeFile(fileName) {
  324. if(fileName.indexOf('.html') > -1) {
  325. return true;
  326. } else {
  327. return false;
  328. }
  329. }
  330.  
  331. function getZipResource(callback) {
  332. const downloadUrl = window.location.origin + document.querySelector('.gl-button.btn-sm.btn-confirm').getAttribute('href');
  333.  
  334. fetch(downloadUrl)
  335. .then(response => response.arrayBuffer())
  336. .then(data => {
  337. // 将 ZIP 文件的二进制数据传递给 JSZip 进行解析
  338. return JSZip.loadAsync(data);
  339. })
  340. .then(zip => {
  341. // 获取 ZIP 文件中的所有条目(文件和目录)
  342. const zipEntries = Object.values(zip.files);
  343. const treeData = organizeFileEntries(zipEntries)
  344.  
  345. callback && callback(treeData);
  346.  
  347. })
  348. .catch(error => {
  349. console.error(error);
  350. });
  351. }
  352. })();