您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
禅道辅助脚本,获取任务工时
// ==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); })();