GitLab Viewer Publish and Deploy Project

GitLab Viewer Publish and Deploy Project!

当前为 2023-06-06 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         GitLab Viewer Publish and Deploy Project
// @namespace    http://tampermonkey.net/
// @version      1.09
// @description  GitLab Viewer Publish and Deploy Project!
// @author       Sean
// @match        http://192.168.0.200/fe3project/*
// @match        http://192.168.0.200/frontend_pc/project/*
// @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 += ".el-loading-spinner {margin-top: -60px;} .el-button--primary:focus {outline: 0 !important;}";
    // 添加注入样式
    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"
                    element-loading-background="rgba(255, 255, 255, 1)"
                    element-loading-text="拼命加载中......"
                    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>
            <el-dialog title="部署提示" width="640px" :visible.sync="dialogDeployVisible">
                <el-alert
                    title="此操作将把 GitLab 资源打包部署至 170 服务器,已部署过的项目会进行覆盖部署,是否继续?"
                    type="warning" :closable="false" style="margin-bottom: 20px;">
                </el-alert>
                <el-form :model="form" ref="form" :rules="rules">
                    <el-form-item label="框架类型" :label-width="formLabelWidth" prop="frame">
                        <el-select v-model="form.frame" placeholder="请框架类型">
                            <el-option v-for="item in framework" :key="item.value" :label="item.label" :value="item.value"></el-option>
                        </el-select>
                    </el-form-item>
                </el-form>
                <div slot="footer" class="dialog-footer">
                    <el-button @click="dialogDeployVisible = false">取 消</el-button>
                    <el-button type="primary" @click="doDeploy">确 定</el-button>
                </div>
            </el-dialog>
        </div>`,
        data() {
            return {
                dialogVisible: false, // 查看目录结构弹窗
                data: [], // 目录结构树的数据结构
                defaultProps: {
                    children: 'children',
                    label: 'label'
                },
                loadingTree: false,
                depDialogVisible: false, // 部署中弹窗
                loading: false,
                projectLibUrl: 'http://192.168.118.60:9999/webapp/pages/caselib/create.html', // 项目案例库地址
                projectIsDeployed: false, // 项目是否部署过
                projectFtpUrl: '', // ftp路径
                projectEntryUrl: 'http://192.168.219.170/showcase/', // 项目案例库发布表单的入口页面
                supportDeploy: true, // 项目是否支持部署
                dialogDeployVisible: false,
                formLabelWidth: '120px',
                form: {
                    frame: ''
                },
                framework: [
                    {label: '重构模板', value: 1},
                    {label: 'f9x1.0', value: 2},
                    {label: 'f9x2.0', value: 3},
                    {label: 'f950', value: 4},
                    {label: 'f950sp1', value: 5},
                    {label: 'f950sp2', value: 6},
                    {label: 'f950sp3', value: 7},
                    {label: 'f951', value: 18},
                    {label: 'f940', value: 8},
                    {label: 'f942', value: 9},
                    {label: 'f941', value: 10},
                    {label: 'f934', value: 11},
                    {label: 'f933', value: 12},
                    {label: 'f932', value: 13},
                    {label: 'f9211', value: 14},
                    {label: '骨架', value: 15},
                    {label: 'Vue', value: 16},
                    {label: 'React', value: 17}
                ],
                rules: {
                    frame: [
                        { required: true, message: '请选择框架类型', trigger: 'change' }
                    ]
                },
                clickNodeEntry: null
            };
        },
        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;
                self.clickNodeEntry = entry;
                if(self.projectFtpUrl) {
                    window.open('http://192.168.219.170' + self.projectFtpUrl + data.entry);
                    self.clickNodeEntry = null;
                } else {
                    if(this.supportDeploy === false) {
                        this.$message({
                            type: 'error',
                            message: '暂只支持查看 重构模板 和 F9x2.0 的项目'
                        });
                        self.clickNodeEntry = null;
                        return false;
                    }

                    this.$confirm('资源未部署,部署至 170 服务器后可查看,是否部署?')
                        .then(_ => {

                        this.dialogDeployVisible = true;
                        /*
                        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 () {
                if(!this.isDownLoadPage()) {
                    return;
                }
                if(!this.dialogDeployVisible) {
                    this.dialogDeployVisible = true;
                    return;
                }

                let self = this;

                this.$refs['form'].validate((valid) => {
                    if (valid) {
                        this.dialogDeployVisible = false;
                        this.depDialogVisible = true;
                        this.loading = true;
                        // 部署
                        this.getDeployInfo({ type: '1', frame: this.form.frame }, (data)=> {
                            this.loading = false;
                            this.depDialogVisible = false;

                            if(!data.custom.text){

                                this.data = data.custom.detail;
                                this.projectFtpUrl = data.custom.ftpUrl;
                                this.supportDeploy = data.custom.supportDeploy;
                                // 从部署按钮直接过来的
                                if(!this.clickNodeEntry) {
                                    this.$message({
                                        type: 'success',
                                        message: '部署成功!'
                                    });
                                    // 打开查看弹窗
                                    this.viewProject();
                                } else {// 从点击目录结构过来的,可以调整点击的树节点页面
                                    this.$alert('部署成功!', '提示', {
                                        confirmButtonText: '确定',
                                        callback: action => {
                                            window.open('http://192.168.219.170' + self.projectFtpUrl + self.clickNodeEntry);
                                            self.clickNodeEntry = null;
                                        }
                                    });
                                }

                            } else {
                                this.$message({
                                    type: 'error',
                                    message: data.custom.text
                                });
                            }
                        });
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });

                /*
                this.$confirm('此操作将把 GitLab 资源打包部署至 170 服务器,已部署过的项目会进行覆盖部署,是否继续?', '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                }).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]) {
                        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 () {
                if(!this.isDownLoadPage()) {
                    return;
                }
                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.supportDeploy = data.custom.supportDeploy;
                        self.loadingTree = false;


                    } else {
                        // 无部署信息,仅查看文件目录
                        getZipResource((data)=> {
                            self.data = data;
                            self.loadingTree = false;
                        });
                    }
                });

            },
            // 项目部署信息
            getDeployInfo(params, callback) {
                const projectId = document.body.getAttribute('data-project-id');
                const downloadBtn = document.querySelector('.gl-button.btn-sm.btn-confirm');

                const sourceUrl = downloadBtn.getAttribute('href');
                const downloadUrl = window.location.origin + sourceUrl;
                const files = document.body.getAttribute('data-find-file').split('/');
                const name = document.body.getAttribute('data-project') + '-' + files[files.length - 1];
                const author = document.querySelector('.current-user .gl-font-weight-bold').innerText.trim();
                const projectName = document.querySelector('.sidebar-context-title').innerText.trim();
                const deployManOA = document.querySelector('.current-user>a').getAttribute('data-user');
                const projectGitUrl = 'http://192.168.0.200' + document.body.getAttribute('data-find-file').split('/-/')[0];

                if(typeof params == 'function') {
                    callback = params;
                    params = null;
                }

                if(projectId && projectId.length && sourceUrl) {
                    fetch('http://192.168.219.170:3008/api/getDeployInfo', {
                        method: 'POST',
                        // 允许跨域请求
                        mode: 'cors',
                        headers: {
                            'Accept': 'application/json',
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({//post请求参数
                            params: {
                                "type": (params && params.type !== undefined) ? params.type : '0',// 0 代表查看, 1代表部署
                                "name": name, // 项目路径英文名
                                "deployMan": author, // 部署人姓名
                                "deployManOA": deployManOA, // 部署人账号
                                "projectName": projectName, // 项目名称
                                "downloadUrl": downloadUrl, // 下载地址
                                "projectId": projectId, // 主键,gitlab上的项目id
                                "projectGitUrl": projectGitUrl,
                                "frame": (params && params.frame) ? params.frame : undefined
                            }
                        })
                    })
                    .then(response => response.text())
                    .then((result) => {
                        var data = JSON.parse(result);
                        callback && callback(data);
                    })
                    .catch(error => {
                        this.depDialogVisible = false;
                        this.dialogVisible = false;
                        this.$message({
                            type: 'error',
                            message: '系统故障,请联系管理员。'
                        });
                        console.error(error);
                    });
                } else {
                    this.$message({
                        type: 'error',
                        message: '本页不支持查看和部署,请至仓库页。'
                    });
                    console.error('部署信息请求参数error');
                }
            },
            // 设置入口
            setProjectEntry(){
                const firstNode = findFirstFileNode(this.data);
                if(firstNode) {
                    this.projectEntryUrl = 'http://192.168.219.170' + this.projectFtpUrl + firstNode.entry;
                }
            },
            isDownLoadPage() {
                const downloadBtn = document.querySelector('.gl-button.btn-sm.btn-confirm');
                const sourceUrl = downloadBtn && downloadBtn.getAttribute('href');

                if(downloadBtn && sourceUrl) {
                    return true;
                } else {
                    this.$message({
                        type: 'error',
                        message: '本页面不支持查看和部署,请移至仓库页。'
                    });
                    return false;
                }
            }
        },
        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;
    }
})();