GitLab Viewer Publish and Deploy Project

GitLab Viewer Publish and Deploy Project!

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

  1. // ==UserScript==
  2. // @name GitLab Viewer Publish and Deploy Project
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  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.bootcdn.net/ajax/libs/vue/2.7.14/vue.min.js
  10. // @require https://unpkg.com/element-ui/lib/index.js
  11. // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/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. projectFtpUrl: '', // ftp路径
  103. };
  104. },
  105. methods: {
  106. handleClose(done) {
  107. done();
  108. /*
  109. this.$confirm('确认关闭?')
  110. .then(_ => {
  111. done();
  112. })
  113. .catch(_ => {});*/
  114. },
  115. handleNodeClick(data) {
  116. console.log(data);
  117. if(data.type === 'folder') {
  118. return false;
  119. }
  120. var self = this;
  121. var entry = data.entry;
  122. if(self.projectFtpUrl) {
  123. window.open('http://192.168.219.170' + self.projectFtpUrl + data.entry)
  124. } else {
  125. this.$confirm('资源未部署,部署至 170 服务器后可查看,是否部署?')
  126. .then(_ => {
  127. this.depDialogVisible = true;
  128. this.loading = true;
  129. // 部署
  130. this.getDeployInfo({ type: '1' }, (data)=> {
  131. this.loading = false;
  132. this.depDialogVisible = false;
  133.  
  134. this.data = data.custom.detail;
  135. this.projectFtpUrl = data.custom.ftpUrl;
  136.  
  137. this.$alert('部署成功!', '提示', {
  138. confirmButtonText: '确定',
  139. callback: action => {
  140. window.open('http://192.168.219.170' + self.projectFtpUrl + entry)
  141. }
  142. });
  143.  
  144. });
  145. })
  146. .catch(_ => {});
  147. }
  148. },
  149. // 部署
  150. doDeploy () {
  151. this.$confirm('此操作将把 GitLab 资源打包部署至 170 服务器,已部署过的项目会进行覆盖部署,是否继续?', '提示', {
  152. confirmButtonText: '确定',
  153. cancelButtonText: '取消',
  154. type: 'warning'
  155. }).then(() => {
  156. this.depDialogVisible = true;
  157. this.loading = true;
  158. // 部署
  159. this.getDeployInfo({ type: '1' }, (data)=> {
  160. this.loading = false;
  161. this.depDialogVisible = false;
  162.  
  163. this.$message({
  164. type: 'success',
  165. message: '部署成功!'
  166. });
  167.  
  168. this.data = data.custom.detail;
  169. this.projectFtpUrl = data.custom.ftpUrl;
  170. // 打开查看弹窗
  171. this.viewProject();
  172. });
  173. }).catch(() => {
  174. this.$message({
  175. type: 'info',
  176. message: '已取消部署'
  177. });
  178. });
  179. },
  180. // 发布项目案例库
  181. publish () {
  182. this.$confirm('此操作将把项目发布至 <a href="'+ this.projectLibUrl +'" target="_blank">项目案例库</a>,已发布过的项目案例库会有重复项,是否继续?', '提示', {
  183. confirmButtonText: '确定',
  184. cancelButtonText: '取消',
  185. dangerouslyUseHTMLString: true,
  186. type: 'warning'
  187. }).then(() => {
  188. const themes = document.querySelectorAll('.badge-secondary');
  189. let keys = [];
  190.  
  191. for(let i = 0, l = themes.length; i < l; i++) {
  192. if(themes[0].innerText == '智能设备' && i > 0) {
  193. keys.push(themes[i].innerText);
  194. } else if (themes[0].innerText !== '智能设备' && i > 1) {
  195. keys.push(themes[i].innerText);
  196. }
  197.  
  198. }
  199. // 组织项目案例库所需参数
  200. const projectName = document.querySelector('.home-panel-title').innerText;
  201. const projectBU = themes[0] ? themes[0].innerText : null;
  202. const projectType = themes[1] ? themes[0].innerText : null;
  203. const projectKeys = keys.join(' ');
  204. const entryUrl = 'http://test.html';
  205. this.$message({
  206. type: 'success',
  207. message: this.projectLibUrl + '?projectName=' + projectName + '&projectBU=' + projectBU + '&projectType=' + projectType + '&projectKeys=' + projectKeys + '&entryUrl=' + entryUrl
  208. });
  209. }).catch(() => {
  210. this.$message({
  211. type: 'info',
  212. message: '已取消发布'
  213. });
  214. });
  215. },
  216. // 查看项目
  217. viewProject () {
  218. this.dialogVisible = true;
  219. this.loadingTree = true;
  220. let self = this;
  221.  
  222. // 发送ajax请求,查看是否进行过部署
  223. this.getDeployInfo((data)=> {
  224. // 有部署信息,直接赋值,
  225. if(true) {
  226. self.projectIsDeployed = true;
  227. self.data = data.custom.detail;
  228. self.projectFtpUrl = data.custom.ftpUrl;
  229. self.loadingTree = false;
  230. } else {
  231. // 无部署信息,仅查看文件目录
  232. getZipResource((data)=> {
  233. self.data = data;
  234. self.loadingTree = false;
  235. });
  236. }
  237. });
  238.  
  239. },
  240. // 项目部署信息
  241. getDeployInfo(params, callback) {
  242. const projectId = document.body.getAttribute('data-project-id');
  243. const downloadUrl = window.location.origin + document.querySelector('.gl-button.btn-sm.btn-confirm').getAttribute('href');
  244. const name = document.querySelectorAll('.breadcrumb-item')[0].innerText + '-main';
  245. const author = document.querySelector('.current-user .gl-font-weight-bold').innerText.trim();
  246.  
  247. if(typeof params == 'function') {
  248. callback = params;
  249. params = null;
  250. }
  251.  
  252. if(projectId && projectId.length && downloadUrl) {
  253. fetch('http://192.168.118.49:3000/api/getDeployInfo', {
  254. method: 'POST',
  255. // 允许跨域请求
  256. mode: 'cors',
  257. headers: {
  258. 'Accept': 'application/json',
  259. 'Content-Type': 'application/json'
  260. },
  261. body: JSON.stringify({//post请求参数
  262. projectId: projectId,
  263. downloadUrl: downloadUrl,
  264. type: (params && params.type !== undefined) ? params.type : '0',// 0 代表查看, 1代表部署
  265. name: name,
  266. author: author, // 增加上传人,用来生成根目录下的readme.html
  267. })
  268. })
  269. .then(response => response.text())
  270. .then((result) => {
  271. var data = JSON.parse(result);
  272. callback && callback(data);
  273. })
  274. .catch(error => console.error(error));
  275. } else {
  276. console.error('部署信息请求参数error');
  277. }
  278. }
  279. },
  280. mounted() {
  281.  
  282. }
  283. };
  284.  
  285. const placeholder = document.createElement('div');
  286.  
  287. // 创建 Vue 实例并挂载到页面
  288. const vueInstance = new Vue({
  289. el: placeholder,
  290. components: {
  291. MyComponent
  292. },
  293. methods: {
  294. },
  295. template: `<my-component></my-component>`
  296. });
  297.  
  298. // 等待页面加载完成
  299. window.addEventListener('load', function() {
  300. // 将占位元素追加到 body 元素中
  301. document.body.appendChild(vueInstance.$el);
  302. });
  303.  
  304. // 将文件条目组织成嵌套结构
  305. function organizeFileEntries(fileEntries) {
  306. const root = {
  307. label: document.querySelector('.home-panel-title').innerText || document.getElementById('project_name_edit').value,
  308. type: 'folder',
  309. children: []
  310. };
  311.  
  312. // 创建嵌套结构
  313. fileEntries.forEach(entry => {
  314. const pathSegments = entry.name.split('/');
  315. let currentFolder = root;
  316.  
  317. // 遍历路径中的每个部分,创建相应的文件夹节点
  318. for (let i = 0; i < pathSegments.length - 1; i++) {
  319. const folderName = pathSegments[i];
  320. let folder = currentFolder.children.find(child => child.label === folderName);
  321.  
  322. if(isExcludeFolder(entry.name)) {
  323. continue;
  324. }
  325.  
  326. if (!folder) {
  327. folder = {
  328. label: folderName,
  329. type: 'folder',
  330. children: []
  331. };
  332. currentFolder.children.push(folder);
  333. }
  334.  
  335. currentFolder = folder;
  336. }
  337.  
  338. // 创建文件节点并添加到相应的文件夹中
  339. const fileName = pathSegments[pathSegments.length - 1];
  340.  
  341. if(fileName && fileName.length && isIncludeFile(fileName) && !isExcludeFolder(entry.name)) {
  342. const fileNode = {
  343. label: fileName,
  344. type: 'file',
  345. entry: entry.name
  346. };
  347. currentFolder.children.push(fileNode);
  348. }
  349. });
  350.  
  351. return [root];
  352. }
  353. // 是否排除的文件夹
  354. function isExcludeFolder(entry) {
  355. if(entry.indexOf('css') > -1 ||
  356. entry.indexOf('scss') > -1 ||
  357. entry.indexOf('js') > -1 ||
  358. entry.indexOf('images') > -1 ||
  359. entry.indexOf('fui') > -1 ||
  360. entry.indexOf('lib') > -1 ||
  361. entry.indexOf('test') > -1 ||
  362. entry.indexOf('font') > -1 ||
  363. entry.indexOf('frame/fui') > -1) {
  364. return true;
  365. } else {
  366. return false;
  367. }
  368. }
  369. // 是否包含的文件
  370. function isIncludeFile(fileName) {
  371. if(fileName.indexOf('.html') > -1) {
  372. return true;
  373. } else {
  374. return false;
  375. }
  376. }
  377.  
  378. function getZipResource(callback) {
  379. const downloadUrl = window.location.origin + document.querySelector('.gl-button.btn-sm.btn-confirm').getAttribute('href');
  380.  
  381. fetch(downloadUrl)
  382. .then(response => response.arrayBuffer())
  383. .then(data => {
  384. // 将 ZIP 文件的二进制数据传递给 JSZip 进行解析
  385. return JSZip.loadAsync(data);
  386. })
  387. .then(zip => {
  388. // 获取 ZIP 文件中的所有条目(文件和目录)
  389. const zipEntries = Object.values(zip.files);
  390. const treeData = organizeFileEntries(zipEntries)
  391.  
  392. callback && callback(treeData);
  393.  
  394. })
  395. .catch(error => {
  396. console.error(error);
  397. });
  398. }
  399. })();