您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
GitLab Viewer Publish and Deploy Project!
当前为
// ==UserScript== // @name GitLab Viewer Publish and Deploy Project // @namespace http://tampermonkey.net/ // @version 1.02 // @description GitLab Viewer Publish and Deploy Project! // @author Sean // @match http://192.168.0.200/fe3project/* // @icon http://192.168.0.200/assets/favicon-7901bd695fb93edb07975966062049829afb56cf11511236e61bcf425070e36e.png // @require https://cdn.bootcdn.net/ajax/libs/vue/2.7.14/vue.min.js // @require https://unpkg.com/element-ui/lib/index.js // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.min.js // @resource myFontFile https://element.eleme.io/2.11/static/element-icons.535877f.woff // @grant GM_getResourceURL // @grant GM_addStyle // @grant GM_getResourceText // @resource ElementCSS https://unpkg.com/element-ui/lib/theme-chalk/index.css // @grant GM_xmlhttpRequest // @license MIT // @run-at document-end // ==/UserScript== (function() { 'use strict'; const fontUrl = 'https://element.eleme.io/2.11/static/element-icons.535877f.woff'; // 添加样式规则,将字体应用到指定元素上 GM_addStyle(` @font-face { font-family: element-icons; src: url(${fontUrl}) format("woff"); } `); GM_addStyle(GM_getResourceText('ElementCSS')); let epointCss = ".epoint-tool {position: fixed; bottom: 0%; right:10px; transform: translateY(-50%);}"; epointCss += ".el-row { padding: 3px 0;} .el-dialog__body .el-tree{min-height: 420px; max-height: 500px;overflow: auto;}"; epointCss += ".deploy-body {height: 306px;}" epointCss += ".deploy-body .el-loading-spinner {margin-top: -50px;}"; // 添加注入样式 let extraStyleElement = document.createElement("style"); extraStyleElement.innerHTML = epointCss; document.head.appendChild(extraStyleElement); const MyComponent = { template: `<div class="epoint-wrap"> <div class="epoint-tool"> <el-row><el-button type="primary" icon="el-icon-search" round @click="viewProject">查看</el-button></el-row> <el-row> <el-tooltip content="一键部署到170服务器" placement="top" effect="light"> <el-button type="primary" icon="el-icon-s-unfold" round @click="doDeploy">部署</el-button> </el-tooltip> </el-row> <el-row> <el-tooltip content="一键发布到项目案例库" placement="top" effect="light"> <el-button type="primary" icon="el-icon-upload" round @click="publish">发布</el-button> </el-tooltip> </el-row> </div> <el-dialog title="目录结构" width="900px" :append-to-body="true" :visible.sync="dialogVisible" :before-close="handleClose"> <el-tree class="filter-tree" :data="data" :props="defaultProps" node-key="id" default-expand-all @node-click="handleNodeClick" v-loading="loadingTree" ref="tree"> </el-tree> </el-dialog> <el-dialog title="部署" width="420px" :visible.sync="depDialogVisible"> <div class="deploy-body" v-loading="loading" element-loading-text="正在打包部署至 170 服务器,请耐心等待" element-loading-spinner="el-icon-loading"> </div> </el-dialog> </div>`, data() { return { dialogVisible: false, // 查看目录结构弹窗 data: [], // 目录结构树的数据结构 defaultProps: { children: 'children', label: 'label' }, loadingTree: false, depDialogVisible: false, // 部署中弹窗 loading: false, projectLibUrl: 'http://192.168.201.159:9999/webapp/pages/default/onlinecase.html', // 项目案例库地址 projectIsDeployed: false, // 项目是否部署过 projectFtpUrl: '', // ftp路径 projectEntryUrl: 'http://192.168.219.170/showcase/', // 项目案例库发布表单的入口页面 }; }, methods: { handleClose(done) { done(); /* this.$confirm('确认关闭?') .then(_ => { done(); }) .catch(_ => {});*/ }, handleNodeClick(data) { console.log(data); if(data.type === 'folder') { return false; } var self = this; var entry = data.entry; if(self.projectFtpUrl) { window.open('http://192.168.219.170' + self.projectFtpUrl + data.entry) } else { this.$confirm('资源未部署,部署至 170 服务器后可查看,是否部署?') .then(_ => { this.depDialogVisible = true; this.loading = true; // 部署 this.getDeployInfo({ type: '1' }, (data)=> { this.loading = false; this.depDialogVisible = false; this.data = data.custom.detail; this.projectFtpUrl = data.custom.ftpUrl; this.$alert('部署成功!', '提示', { confirmButtonText: '确定', callback: action => { window.open('http://192.168.219.170' + self.projectFtpUrl + entry) } }); }); }) .catch(_ => {}); } }, // 部署 doDeploy () { this.$confirm('此操作将把 GitLab 资源打包部署至 170 服务器,已部署过的项目会进行覆盖部署,是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.depDialogVisible = true; this.loading = true; // 部署 this.getDeployInfo({ type: '1' }, (data)=> { this.loading = false; this.depDialogVisible = false; this.$message({ type: 'success', message: '部署成功!' }); this.data = data.custom.detail; this.projectFtpUrl = data.custom.ftpUrl; // 打开查看弹窗 this.viewProject(); }); }).catch(() => { this.$message({ type: 'info', message: '已取消部署' }); }); }, // 发布项目案例库 publish () { this.$confirm('此操作将把项目发布至 <a href="'+ this.projectLibUrl +'" target="_blank">项目案例库</a>,已发布过的项目案例库会有重复项,是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', dangerouslyUseHTMLString: true, type: 'warning' }).then(() => { const themes = document.querySelectorAll('.badge-secondary'); let keys = []; for(let i = 0, l = themes.length; i < l; i++) { if(themes[0].innerText == '智能设备' && i > 0) { keys.push(themes[i].innerText); } else if (themes[0].innerText !== '智能设备' && i > 1) { keys.push(themes[i].innerText); } } // 存在更多主题的情况 const moreKeyEl = document.querySelector('.gl-w-full .text-nowrap'); if(moreKeyEl) { const moreKeyElContent = moreKeyEl.getAttribute('data-content'); const regex = />([^<]+)</g; const matches = moreKeyElContent.match(regex); const results = matches.filter(function(match) { return match.length > 3; }); const moreKeyData = results.map(function(match) { return match.substring(2, match.length - 2); }); if(moreKeyData && moreKeyData.length) { keys = keys.concat(moreKeyData); } } // 组织项目案例库所需参数 const projectName = document.querySelector('.home-panel-title').innerText; const projectBU = themes[0] ? themes[0].innerText : null; const projectKeys = keys.join(' '); const entryUrl = this.projectEntryUrl; let projectType = themes[1] ? themes[1].innerText : null; if(themes[0].innerText == '智能设备') { projectType = themes[1] ? themes[0].innerText : null; } else { projectType = themes[1] ? themes[1].innerText : null; } const destUrl = this.projectLibUrl + '?projectName=' + projectName + '&projectBU=' + projectBU + '&projectType=' + projectType + '&projectKeys=' + projectKeys + '&entryUrl=' + entryUrl + '&git=' + window.location.href; this.$message({ type: 'success', message: destUrl }); window.open(destUrl); }).catch(() => { this.$message({ type: 'info', message: '已取消发布' }); }); }, // 查看项目 viewProject () { this.dialogVisible = true; this.loadingTree = true; let self = this; // 发送ajax请求,查看是否进行过部署 this.getDeployInfo((data)=> { // 有部署信息,直接赋值, if(true) { self.projectIsDeployed = true; self.data = data.custom.detail; self.projectFtpUrl = data.custom.ftpUrl; self.loadingTree = false; } else { // 无部署信息,仅查看文件目录 getZipResource((data)=> { self.data = data; self.loadingTree = false; }); } }); }, // 项目部署信息 getDeployInfo(params, callback) { const projectId = document.body.getAttribute('data-project-id'); const downloadUrl = window.location.origin + document.querySelector('.gl-button.btn-sm.btn-confirm').getAttribute('href'); const name = document.querySelectorAll('.breadcrumb-item')[0].innerText + '-main'; const author = document.querySelector('.current-user .gl-font-weight-bold').innerText.trim(); if(typeof params == 'function') { callback = params; params = null; } if(projectId && projectId.length && downloadUrl) { fetch('http://192.168.118.49:3000/api/getDeployInfo', { method: 'POST', // 允许跨域请求 mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({//post请求参数 projectId: projectId, downloadUrl: downloadUrl, type: (params && params.type !== undefined) ? params.type : '0',// 0 代表查看, 1代表部署 name: name, author: author, // 增加上传人,用来生成根目录下的readme.html }) }) .then(response => response.text()) .then((result) => { var data = JSON.parse(result); callback && callback(data); }) .catch(error => console.error(error)); } else { console.error('部署信息请求参数error'); } }, // 设置入口 setProjectEntry(){ const firstNode = findFirstFileNode(this.data); if(firstNode) { this.projectEntryUrl = 'http://192.168.219.170' + this.projectFtpUrl + firstNode.entry; } } }, mounted() { } }; const placeholder = document.createElement('div'); // 创建 Vue 实例并挂载到页面 const vueInstance = new Vue({ el: placeholder, components: { MyComponent }, methods: { }, template: `<my-component></my-component>` }); // 等待页面加载完成 window.addEventListener('load', function() { // 将占位元素追加到 body 元素中 document.body.appendChild(vueInstance.$el); }); // 将文件条目组织成嵌套结构 function organizeFileEntries(fileEntries) { const root = { label: document.querySelector('.home-panel-title').innerText || document.getElementById('project_name_edit').value, type: 'folder', children: [] }; // 创建嵌套结构 fileEntries.forEach(entry => { const pathSegments = entry.name.split('/'); let currentFolder = root; // 遍历路径中的每个部分,创建相应的文件夹节点 for (let i = 0; i < pathSegments.length - 1; i++) { const folderName = pathSegments[i]; let folder = currentFolder.children.find(child => child.label === folderName); if(isExcludeFolder(entry.name)) { continue; } if (!folder) { folder = { label: folderName, type: 'folder', children: [] }; currentFolder.children.push(folder); } currentFolder = folder; } // 创建文件节点并添加到相应的文件夹中 const fileName = pathSegments[pathSegments.length - 1]; if(fileName && fileName.length && isIncludeFile(fileName) && !isExcludeFolder(entry.name)) { const fileNode = { label: fileName, type: 'file', entry: entry.name }; currentFolder.children.push(fileNode); } }); return [root]; } // 是否排除的文件夹 function isExcludeFolder(entry) { if(entry.indexOf('css') > -1 || entry.indexOf('scss') > -1 || entry.indexOf('js') > -1 || entry.indexOf('images') > -1 || entry.indexOf('fui') > -1 || entry.indexOf('lib') > -1 || entry.indexOf('test') > -1 || entry.indexOf('font') > -1 || entry.indexOf('frame/fui') > -1) { return true; } else { return false; } } // 是否包含的文件 function isIncludeFile(fileName) { if(fileName.indexOf('.html') > -1) { return true; } else { return false; } } function getZipResource(callback) { const downloadUrl = window.location.origin + document.querySelector('.gl-button.btn-sm.btn-confirm').getAttribute('href'); fetch(downloadUrl) .then(response => response.arrayBuffer()) .then(data => { // 将 ZIP 文件的二进制数据传递给 JSZip 进行解析 return JSZip.loadAsync(data); }) .then(zip => { // 获取 ZIP 文件中的所有条目(文件和目录) const zipEntries = Object.values(zip.files); const treeData = organizeFileEntries(zipEntries) callback && callback(treeData); }) .catch(error => { console.error(error); }); } // 树结构第一个节点数据 function findFirstFileNode(tree) { // 遍历树的节点 for (let i = 0; i < tree.length; i++) { const node = tree[i]; // 如果节点的类型为 file,则返回该节点 if (node.type === 'file') { return node; } // 如果节点有子节点,则递归调用该函数查找子节点中的第一个 file 节点 if (node.children && node.children.length > 0) { const fileNode = findFirstFileNode(node.children); if (fileNode) { return fileNode; } } } // 如果没有找到 file 节点,则返回 null return null; } })();