禅道辅助

禅道辅助脚本,获取任务工时

// ==UserScript==
// @name        禅道辅助
// @icon         http://www.studstu.com/fximg/delicious.gif
// @namespace    wenqindong.top
// @version      1.1.4
// @description  禅道辅助脚本,获取任务工时
// @author       wenqd
// @include      http://172.16.22.218:81/*
// @match        http://172.16.22.218:81/*
// @run-at       document-end
// @require      https://cdn.bootcss.com/vue/2.6.10/vue.js
// @require      https://unpkg.com/element-ui/lib/index.js
// @grant        unsafeWindow
// @compatible   chrome OK
// @compatible   firefox OK
// @license MIT
// ==/UserScript==

var $ = unsafeWindow.$;

//======== 函数区,不要修改 ========
//--- 添加贴子翻页监听
function createDom(){
    var buttonDom = "<div id='autoform' style='position:absolute;right:234px;z-index:9999;top:6px;background-color:#0bb1cc;padding:8px;font-size:13px;border-radius:4px;cursor:pointer;color:#fff;'>脚本辅助<div>"
var vueDom="";
vueDom += "<div id=\"appvue\">";
vueDom += "    <el-drawer title=\"禅道辅助脚本\" size=\"70%\" :visible.sync=\"drawer\">";
vueDom += "        <div>";
vueDom += "            <el-card lass=\"box-card\" style=\"width: 100%;margin: 10px;\">";
vueDom += "                <el-row>";
vueDom += "                  <el-col :span=\"24\">";
vueDom += "                    <el-form :inline=\"true\" :model=\"form\" label-position=\"right\" label-width=\"100px\">";
vueDom += "                        <el-form-item label=\"用户名\">";
vueDom += "                            <el-input v-model=\"form.username\" placeholder=\"请输入用户名\" style=\"width: 150px;\"><\/el-input>";
vueDom += "                        <\/el-form-item>";
vueDom += "                        <el-form-item label=\"日期\">";
vueDom += "                            <el-date-picker v-model=\"form.date\" type=\"date\" placeholder=\"选择日期\"><\/el-date-picker>";
vueDom += "                        <\/el-form-item>";
vueDom += "                        <el-form-item label=\"\">";
vueDom += "                            <el-button type=\"primary\" size=\"mini\" @click=\"getUserData\">获取工时数据<\/el-button>";
vueDom += "                        <\/el-form-item>";
vueDom += "                    <\/el-form>";
vueDom += "                  <\/el-col>";
vueDom += "                  <el-col :span=\"20\">";
vueDom += "                    <el-row :gutter=\"20\" class=\"result-area\">";
vueDom += "";
vueDom += "                        <el-col :span=\"8\">";
vueDom += "                          <div>";
vueDom += "                            <el-statistic title=\"工作日天数\">";
vueDom += "                              <template slot=\"formatter\">";
vueDom += "                                {{countWorkdays}}";
vueDom += "                              <\/template>";
vueDom += "                            <\/el-statistic>";
vueDom += "                          <\/div>";
vueDom += "                        <\/el-col>";
vueDom += "                        <el-col :span=\"8\">";
vueDom += "                            <div>";
vueDom += "                              <el-statistic title=\"总工时\">";
vueDom += "                                <template slot=\"formatter\">";
vueDom += "                                  {{taskHours}}";
vueDom += "                                <\/template>";
vueDom += "                              <\/el-statistic>";
vueDom += "                            <\/div>";
vueDom += "                          <\/el-col>";
vueDom += "                          <el-col :span=\"8\">";
vueDom += "                            <div>";
vueDom += "                              <el-statistic title=\"日均工时\">";
vueDom += "                                <template slot=\"formatter\">";
vueDom += "                                  {{aveTime}}";
vueDom += "                                <\/template>";
vueDom += "                              <\/el-statistic>";
vueDom += "                            <\/div>";
vueDom += "                          <\/el-col>";
vueDom += "                      <\/el-row>";
vueDom += "                  <\/el-col>";
vueDom += "                <\/el-row>";
vueDom += "            <\/el-card>";
vueDom += "            <el-card>";
vueDom += "                <el-table :data=\"tableData\" style=\"width: 100%\">";
vueDom += "                    <el-table-column prop=\"date\" label=\"日期\"><\/el-table-column>";
vueDom += "                    <el-table-column prop=\"hours\" label=\"工时\"><\/el-table-column>";
vueDom += "                    <el-table-column prop=\"names\" label=\"任务\"><\/el-table-column>";
vueDom += "                <\/el-table>";
vueDom += "            <\/el-card>";
vueDom += "        <\/div>";
vueDom += "    <\/el-drawer>";
vueDom += "<\/div>";


    var panelDom = "<div id='zentaoPanel'>"
    +vueDom
    +"<div>"
    $("body").append(buttonDom);
    $("body").append(panelDom);
    $("#autoform").click(function(){
        //$("#zentaoPanel").show()
        appvue.drawer = true
    })
    $("#close").click(function(){
        $("#zentaoPanel").hide()
    })
    $("#printTasks").click(function(){
        zentao.printTasks()
    })
    setTimeout(function(){
        // 初始化 Vue 实例
        unsafeWindow.appvue = new Vue({
            el: '#appvue',
            data() {
                return {
                    drawer:false,
                    form:{
                        username:'',
                        date:''
                    },
                    // 表格数据
                    tableData: [
                    ],
                    pickerOptions: {
                        shortcuts: [{
                            text: '最近一周',
                            onClick(picker) {
                                const end = new Date();
                                const start = new Date();
                                start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
                                picker.$emit('pick', [start, end]);
                            }
                        }, {
                            text: '最近一个月',
                            onClick(picker) {
                                const end = new Date();
                                const start = new Date();
                                start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
                                picker.$emit('pick', [start, end]);
                            }
                        }, {
                            text: '最近三个月',
                            onClick(picker) {
                                const end = new Date();
                                const start = new Date();
                                start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
                                picker.$emit('pick', [start, end]);
                            }
                        }]
                    }
                };
            },
            computed:{
                taskHours(){
                    let num = 0;
                    this.tableData.map(e=>{
                        num+=parseFloat(e.hours)
                    })
                    return num
                },
                aveTime(){
                    const number = (this.taskHours / this.countWorkdays).toFixed(2)
                    if(isNaN(number)){
                        return 0
                    }else{
                        return number
                    }
                },
                countWorkdays(){
                    let start = new Date(this.form.date);
                    let end = new Date(); // 当前日期

                    let workdays = 0;

                    for (let d = start; d <= end; d.setDate(d.getDate() + 1)) {
                        let dayOfWeek = d.getDay();
                        if (dayOfWeek !== 0 && dayOfWeek !== 6) { // 0 是周日,6 是周六
                            workdays++;
                        }
                    }

                    return workdays;
                }
            },
            methods:{
               //获取用户数据
                getUserData(){
                   var that = this;
                   const loading = this.$loading({
                        lock: false,
                        text: '正在获取请稍后...',
                        spinner: 'el-icon-loading',
                        background: 'rgba(0, 0, 0, 0.3)'
                    });
                   zentao.start(this.form.username,this.form.date,function(){
                       that.tableData = zentao.print()
                       that.$message("已获取 "+that.form.username+" 的任务数据")
                       loading.close();
                   })
                },
                //获取用户任务
                getUserPrint(){
                   this.tableData = zentao.print()
                   this.$message("已获取"+this.form.username+"得任务数据")
                }
            }
        });
    },300)
}
function initJs(){
    unsafeWindow.zentao = (function() {
    var BASE_URL = 'http://172.16.22.218:81/zentao/';
    var taskInfoMap = new Map();
    var tasksArray = [];
    var users = [];
    var dateArray = [];
    var currentUser = null;
    // 获取所有动态
    async function getAllDynamics(user, url, endDate) {
        var response = await fetch(url, {redirect: 'follow'});
        var data = await response.text();
        var parser = new DOMParser();
        var doc = parser.parseFromString(data, 'text/html');
        // 提取所有任务
        var tasks = doc.querySelectorAll('#mainContent .timeline-text');
        //过滤任务,保留 .label-id 前面为任务的 .label-id
        tasks = Array.prototype.filter.call(tasks, function(task) {
                return task.querySelector('.label-id')!=null && task.querySelector('.text').textContent=='任务';
            });
        tasks.forEach(function(task) {
            var taskId = task.querySelector('.label-id').textContent;
            if(!taskInfoMap.has(taskId)) {
                var taskName = task.querySelector('.label-name').textContent.trim();
                var taskUrl = task.querySelector('.label-name a').getAttribute('href');
                var taskInfo = {
                    taskId: taskId,
                    taskName: taskName,
                    taskUrl: taskUrl
                };
                taskInfoMap.set(taskId, taskInfo);
            }
        });
        // 获取上一页动态
        var prevPageElement = doc.querySelector('#prevPage');
        var prevPageUrl = prevPageElement ? prevPageElement.getAttribute('href') : null;
        if(endDate) {
            var pageFirstDate = doc.querySelector('#mainContent #dynamics .date-text').textContent;
            var formattedDateString = pageFirstDate.replace(/年|月/g, '/').replace(/日/g, '');
            var date = new Date(formattedDateString);
            if(date < endDate) {
                prevPageUrl = null;
            }
        }
        if (prevPageUrl) {
            await getAllDynamics(user, prevPageUrl, endDate);
        } else {
            //读取所有任务后再开始获取工时信息(避免重复任务)
            for (var [taskId, task] of taskInfoMap) {
              await getTaskHours(user, task, endDate);
            }
            //分析任务工时信息
            analysisTasks(user, tasksArray);
        }
    }

    // 获取任务的工时信息
    async function getTaskHours(user, task, endDate) {
        var url = "http://172.16.22.218:81/zentao/task-recordEstimate-" + task.taskId + ".html?onlybody=yes";
        var response = await fetch(url, {redirect: 'follow'});
        var data = await response.text();
        var parser = new DOMParser();
        var doc = parser.parseFromString(data, 'text/html');
        var rows = doc.querySelectorAll('table.table-recorded tr');
        if(rows && rows.length > 1) {
            task.details = []
            for(var i = 1; i < rows.length; i++) {
                var row = rows[i];
                var date = row.querySelector('td').textContent;
                if(endDate && new Date(date) < endDate) {
                    break;
                }
                //比较记录人
                var recorder = row.querySelector('td:nth-child(2)').textContent;
                if(recorder != user.userName) {
                    continue;
                }
                var hours = row.querySelector('td:nth-child(4)').textContent;
                // 将工时从字符串转换为数字
                var hoursNumber = parseFloat(hours);
                // 创建一个新的任务对象
                var taskInfo = {
                    taskId: task.taskId,
                    taskName: task.taskName,
                    date: date,
                    hours: hoursNumber
                };
                //任务记录工时明细
                task.details.push(taskInfo);
                // 将任务对象添加到数组中
                tasksArray.push(taskInfo);
                console.log(JSON.stringify(taskInfo));
            }
        }
    }

    function analysisTasks(user, tasksArray) {
        //统计每天的工时,按照时间倒序输出
        var dateMap = new Map();
        tasksArray.forEach(function(task) {
            var date = task.date;
            var hours = task.hours;
            if(dateMap.has(date)) {
                var info = dateMap.get(date);
                info.hours = info.hours + hours;
                info.names.add(task.taskName);
                info.tasks.add(task);
                dateMap.set(date, info);
            } else {
                dateMap.set(date, {
                    names: new Set([task.taskName]),
                    hours: hours,
                    tasks: new Set([task])
                });
            }
        });
        dateArray = [];
        dateMap.forEach(function(v, k) {
            var dateObject = {
                date: k,
                hours: v.hours,
                tasks: v.tasks,
                names: Array.from(v.names).join(" | ")
            };
            dateArray.push(dateObject);
        });
        dateArray.sort(function(a, b) {
            return b.date.localeCompare(a.date);
        });
        //排除taskInfoMap中detail为空的对象
        var map = new Map();
        for (var [taskId, task] of taskInfoMap) {
            if(task.details && task.details.length > 0) {
                map.set(taskId, task);
            }
        }
        taskInfoMap = map;
        print(user, dateArray);
    }

    function start(user, endDateStr, success) {
        taskInfoMap = new Map();
        tasksArray = [];
        var endDate = null;
        if(endDateStr) {
            endDate = new Date(endDateStr);
        }
        getAllDynamics(
            user,
            BASE_URL + 'company-dynamic-all--0--no-' + user.userId + '-0-0-0-date_desc.html',
            endDate
        ).then(function(){
            if(success) {
                success();
            }
        });
    }

    async function getAllUsers() {
        if(users.length == 0) {
            var response = await fetch(BASE_URL + 'company-dynamic-all--0--no-1-0-0-0-date_desc.html', {redirect: 'follow'});
            var data = await response.text();
            var parser = new DOMParser();
            var doc = parser.parseFromString(data, 'text/html');
            var options = doc.querySelectorAll('#account option');
            options.forEach(function(option) {
                var userId = option.getAttribute('value');
                if(userId != '') {
                    var userName = option.textContent;
                    users.push({
                        userId: parseInt(userId),
                        userName: userName
                    });
                }
            });
            //按照userId排序
            users.sort(function(a, b) {
                return a.userId - b.userId;
            });
        }
        return users;
    }

    function help() {
        console.log('----------------------------使用方法-------------------------------')
        console.log('1. zentao.start(userIdOrName, endDateStr) 开始获取任务数据')
        console.log('- userIdOrName: 用户ID或用户名')
        console.log('- endDateStr: 结束日期,格式为 yyyy-MM-dd')
        console.log('2. zentao.print() 打印所有工时信息')
        console.log('3. zentao.printTasks() 打印所有任务信息')
        console.log('4. zentao.users() 打印所有用户')
        console.log('5. zentao.tasks() 获取所有任务')
        console.log('6. zentao.hours() 获取所有工时')
        console.log('------------------------------------------------------------------')
    }

    function print(user, dateArray) {
        console.log('------------------------------- ' + user.userId + ':' + user.userName + ' -------------------------------')
        if(dateArray.length > 0) {
            console.table(dateArray, ['date','hours','names'])
        } else {
            console.log('没有任务数据')
        }
        console.log('------------------------------------------------------------------')
    }
    // 打印帮助
    help();
    return {
        start: function(userIdOrName, endDateStr, success) {
            getAllUsers().then(function(users) {
                currentUser = users.find(function(u) {
                    return u.userId == userIdOrName || u.userName == userIdOrName;
                });
                if(currentUser) {
                    start(currentUser, endDateStr, success);
                } else {
                    console.log('没有找到用户');
                }
            });
        },
        print: function() {
            print(currentUser, dateArray)
            return dateArray;
        },
        printTasks: function() {
            var mapToJson = Object.fromEntries(taskInfoMap);
            console.log(JSON.stringify(mapToJson,null,2));
        },
        help: help,
        tasks: function() {
            return taskInfoMap;
        },
        hours: function() {
            return dateArray;
        },
        users: async function(name) {
            await getAllUsers();
            if(name) {
                var user = users.find(function(user) {
                    return user.userName == name;
                });
                console.log(user);
                return user;
            } else {
                console.table(users);
                return users;
            }
        }
    };
})();
}
//======== 执行区,不要修改 =======
(function() {
    // 创建一个新的 link 元素
    var link = document.createElement("link");

    // 设置 link 元素的属性
    link.href = "https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.14/theme-chalk/index.css" // Element UI CSS 的 CDN 地址
    link.type = "text/css";
    link.rel = "stylesheet";

    // 将 link 元素添加到文档的 <head> 中
    document.getElementsByTagName("head")[0].appendChild(link);
    // 创建 style 元素
    var style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = `
        .el-table .el-table__cell {
            padding:0;
        }
        #appvue .cell{
            -webkit-box-shadow: 0 0;
            box-shadow: 0 0;
        }
        .result-area{
            margin-left: -10px;
            margin-right: -10px;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 120px;
        }
        .result-area .number{
            font-size:60px !important;
            color: #117d8b;
        }
    `;

    // 将 style 元素添加到文档的 head 中
    document.head.appendChild(style);
    //急性
    setTimeout(function(){
        createDom();
        initJs();
    },1234);
})();