您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
禅道的一些便捷工具
// ==UserScript== // @name 禅道日志工具 // @namespace // @version 1.2.1 // @description 禅道的一些便捷工具 // @author chenwuai // @license MIT // @match http://192.168.0.16:88/zentao/* // @grant none // @namespace // @namespace // ==/UserScript== (function() { 'use strict'; if(typeof $ === 'undefined') return // 样式 const style = ` #dropMenu { width: 600px; } #dropMenu #tabContent { max-width: 100%; } #currentItem .text{ max-width: 100% !important; } `; document.head.insertAdjacentHTML("beforeend", `<style>${style}</style>`); const pathName = document.location.pathname; let projectName = ''; let loginUserId = '' // 初始化函数,根据页面路径调用不同的初始化方法 function init() { if (/zentao\/execution-task\S+/.test(pathName)) { executionTaskInit(); } else if (/my-work-task\S+/.test(pathName)) { myWorkTaskInit(); } else if (/task-create\S+/.test(pathName)) { taskCreateInit(); } else if (/story-create\S+/.test(pathName)) { storyCreateInit(); } } // 任务页面初始化 function executionTaskInit() { const projectBtn = document.querySelector('#heading #currentItem'); projectName = projectBtn?.title || ''; // 使用可选链操作符 const moreBtn = document.querySelector('#more'); if (moreBtn) { addCustomButton(moreBtn, 'copyTaskLog', '复制日志', formatterExecutionTaskLog); } } // 我的任务总览页面初始化 function myWorkTaskInit() { const moreBtn = document.querySelector('#mainMenu'); if (moreBtn) { addCustomButton(moreBtn, 'copyTaskLog', '复制日志', formatterMyWorkTaskLog); } } // 新增任务页面初始化 function taskCreateInit(){ $('#dataform tr.mailtoBox').hide() $('#dataform tr.datePlanBox').hide() $('select#type').val('devel').trigger("chosen:updated") $('select#assignedTo').val(loginUserId).trigger("chosen:updated") } // 新增需求页面初始化 function storyCreateInit(){ $('#dataform tr.sourceBox').hide() $('#dataform tr.verifyBox').hide() $('#dataform tr.mailtoBox').hide() $('#dataform tr.keywordsBox').hide() // $('#dataform #planIdBox').parent().hide() $('#dataform #moduleIdBox').parent().css({'display':'block','min-width':'500px'}) $('#dataform #moduleIdBox').parent().prev().css({'display':'block','min-width':'500px'}) $('#dataform #parent_chosen').parent().parent().hide() $('select#assignedTo').val(loginUserId).trigger("chosen:updated") } // 任务页面-格式化日志 function formatterExecutionTaskLog(event){ let checkedIDs = getCheckedTaskLogIds() if(checkedIDs.length === 0){ alert('请选中日志') return } const logs = getCheckedTaskLog() // 获取选中任务对象 if(!logs) return; const textList = [projectName] logs.forEach((i,index)=>{ const text = getTaskLogText(i) textList.push(`${text}`) }); copyTextToClipboard(textList.join('\r\n'), event.target); } // 获取选中的任务日志 function getCheckedTaskLog() { try { const logCells = $('#taskList .dtable-body .dtable-cell.is-checked'); if (!logCells || logCells.length === 0) return null; const logs = {}; const needKeys = ['id', 'name', 'status', 'progress']; logCells.each(function(){ let row = $(this).data('row'); if (!logs[row]) { logs[row] = { id: row }; } if (needKeys.includes($(this).data('col'))) { logs[row][$(this).data('col')] = $(this).text(); } }) return Object.values(logs).sort((a, b) => (a.progress || 0) - (b.progress || 0) || a.id - b.id); } catch (error) { console.error("Error in getCheckedTaskLog:", error); return null; } } // 获取任务日志选中的id数组 function getCheckedTaskLogIds(){ var taskForm = $('#executionTaskForm')[0] if (taskForm && taskForm.elements && taskForm.elements['taskIDList[]']){ var checkedIdList = [] if(typeof taskForm.elements['taskIDList[]'].length !== 'undefined'){ $(taskForm.elements['taskIDList[]']).each(function(){ checkedIdList.push(+$(this).val()) }) }else{ checkedIdList.push(+$(taskForm.elements['taskIDList[]']).val()) } return checkedIdList }else{ return [] } } // 获取任务日志文本 function getTaskLogText(data) { if (!data) { console.error('Invalid task log data'); return ''; } let id = data.id?.trim() || ''; let name = data.name?.trim() || ''; let status = data.status?.trim() || ''; const progress = data.progress; return `【${id}】${name} - ${status === '进行中' ? `${status} ${progress}%` : status}`; } // 我的任务页面-格式化日志 function formatterMyWorkTaskLog(event){ const logs = getCheckedMyWorkTaskLog() if(!logs){ alert('请选中日志') return } const textList = [] Object.keys(logs).forEach(key=>{ const list = logs[key].sort((a, b) => (a.progress || 0) - (b.progress || 0) || a.id - b.id) const strList = list.reduce((a,c)=>{ const str = `【${c.id}】${c.name} - ${c.statusName} ${c.progress > 0 ? c.progress + '%' : ''}` a.push(str) return a },[key]) textList.push(strList.join('\r\n')) }) copyTextToClipboard(textList.join('\r\n'), event.target); } // 获取选中的任务日志 function getCheckedMyWorkTaskLog() { try { const logCells = $('#myTaskList tr.checked'); if (!logCells || logCells.length === 0) return null; const logs = {}; logCells.each(function(){ const log = getMyWorkTaskLogInfo(this) if(log){ const projectName = log.project.replace(/[\r\n]/g, '').replace(/\s+/g, ' ').trim() if(typeof logs[projectName] === 'undefined'){ logs[projectName] = [] } logs[projectName].push(log) } }) return logs } catch (error) { console.error("Error in getCheckedTaskLog:", error); return null; } } // 获取我的任务日志信息 function getMyWorkTaskLogInfo(dom) { if (!dom) { console.error('Invalid task log data'); return ''; } const id = getChildDomText(dom,'.c-id'); const name = getChildDomText(dom,'.c-name'); const statusName = getChildDomText(dom,'.c-status'); const project = getChildDomText(dom,'.c-project'); const dataset = dom.dataset const status = dataset.status let progress = 0 if(status === 'doing'){ const consumed = +dataset.consumed const total = +dataset.consumed + +dataset.left progress = (Math.floor(consumed / total * 100)) } return { project, id, name, status, statusName, progress} } // tools // 复制文本到剪贴板,并提供用户反馈 async function copyTextToClipboard(text, target) { try { if (navigator.clipboard && navigator.clipboard.writeText) { // 检查 navigator.clipboard 和 writeText 是否存在 await navigator.clipboard.writeText(text); $(target).data('originalText', target.innerHTML); target.innerHTML = '✌已复制✌'; setTimeout(() => target.innerHTML = $(target).data('originalText') || '复制', 1500); return; // 成功复制后直接返回 } } catch (err) { console.warn('使用 navigator.clipboard 复制失败,尝试使用 document.execCommand:', err); // 不做任何处理,继续执行下面的兼容方案 } // 兼容方案:使用 document.execCommand('copy') const el = $('<textarea>'); el.val(text); el.attr('readonly', ''); el.css({ position: 'absolute', left: '-9999px' }); $('body').append(el); // 保存当前选区 const selected = document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false; el.select(); try { const successful = document.execCommand('copy'); if(successful){ $(target).data('originalText', target.innerHTML); target.innerHTML = '✌已复制✌'; setTimeout(() => target.innerHTML = $(target).data('originalText') || '复制', 1500); } else { console.error('使用 document.execCommand 复制失败'); alert('复制失败,请手动复制'); } } catch (err) { console.error('使用 document.execCommand 复制时发生错误:', err); alert('复制失败,请手动复制'); } el.remove(); // 移除textarea // 恢复选区 if (selected) { document.getSelection().removeAllRanges(); document.getSelection().addRange(selected); } } // 添加 button 子元素 function addCustomButton(parent,id,text,cb){ if(!parent || !id || !text || !cb){ console.error('addCustomButton Error',parent,id,text,cb) return } const btn = $('<button>', { // 使用 jQuery 创建元素更简洁 id: id, class: 'btn btn-link', style: 'color: #0DBB7D;', text: text, click: cb, }); $(parent).append(btn); } function getChildDomText(dom,child){ const childDom = $(dom).find(child) return childDom?.text().trim() || '' } function getLoginUser(){ loginUserId = localStorage.getItem('zentao_user') if(loginUserId) return fetch(`/zentao/my-profile.html?onlybody=yes`, { credentials: 'include' }) .then(res => res.text()) .then(html => { const match = html.match(/<th>\s*用户名\s*<\/th>\s*<td>(.*?)<\/td>/); if (match && match[1]) { loginUserId = match[1].trim(); localStorage.setItem('zentao_user', loginUserId) } else { console.warn('⚠️ 未匹配到用户名字段'); } }) .catch(err => { console.error('获取用户信息失败:', err); }); } $(document).ready(()=>{ getLoginUser() console.log('当前用户账号:', loginUserId); init() }); })();