// ==UserScript== // @name TAPD 修改“基本信息”显示 // @namespace hl_qiu163@163.com // @version 0.1.8 // @description 用于在 TAPD 需求详情页面高亮关键字、重排字段展示顺序,便于快速定位自己关心的字段 // @author qiuhongliang // @icon https://www.google.com/s2/favicons?sz=64&domain=tapd.cn // @match https://www.tapd.cn/*/prong/stories/view/* // @grant none // @license GPL // @downloadURL none // ==/UserScript== (function () { "use strict"; highlightKeyWord(); changeFieldOrder(); /** * 修改字段展示顺序,关心的字段靠前面排 */ function changeFieldOrder() { let baseInfo = document.querySelector("#base_information > div.content"); if (baseInfo == null || baseInfo == undefined) { // 只修改需求详情页面的数据 console.log("未找到需求详情页面数据"); return; } // 将处理人作为基点元素,对关心的元素进行重排 let needAddElementList = [ // 开发阶段关注内容 ---------------------------------------------------------------------------------------------- getParentElement(getOneNotEmptyNode(baseInfo, ["#Content卖家账号"])), // 迭代 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentIteration"])), // 优先级 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentPriority"])), // 开发人员 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentDeveloper"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentBegin"])), // 预计开始时间 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentBegin", "#ContentEst\\.Start"])), // 预计结束时间 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentDue", "#ContentEst\\.End"])), // 线上测试阶段关注内容 ------------------------------------------------------------------------------------------ getParentElement(getOneNotEmptyNode(baseInfo, ["#Content产品经理"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content评审人"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content是否需要灰测(WMS接口用)"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content预计完成时间(WMS接口用)"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content线上跟进情况(WMS接口用)"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content接口线上测试"])), // 接口上线关注内容 ---------------------------------------------------------------------------------------------- getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentReleasePlan"])), // 发布计划 // 代码审核阶段——接口还未启用该流程,故往后放 getParentElement(getOneNotEmptyNode(baseInfo, ["#Content代码核查人员"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content代码核查状态"])), // 目前接口不关注,但是挺重要的字段 ------------------------------------------------------------------------------ // 进度 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentProgress"])), // 兼容多个预估工时字段 // 预估工时字段 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentEffort", "#ContentEst\\.Effort"])), // 完成工时 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentCompletedEffort"])), // 剩余工时 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentRemainingEffort"])), // 超出工时 getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentExceededEffort"])), // 其他不重要内容 ------------------------------------------------------------------------------------------------ getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentModule"])), // 模块 getParentElement(getOneNotEmptyNode(baseInfo, ["#Content加急处理"])), ]; // 处理人 let statusOwner = baseInfo.querySelector("#ContentStatusOwner").parentNode; sortNodeList(statusOwner, needAddElementList); // 不关心的元素移动到最底部----------------------------------------------------------------------------------------- let endNodeList = [ getParentElement(getOneNotEmptyNode(baseInfo, ["#ContentCategory"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content需求反馈人"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content区域"])), getParentElement(getOneNotEmptyNode(baseInfo, ["#Content类目"])), ]; let baseInfoLastChild = baseInfo.lastChild; sortNodeList(baseInfoLastChild, endNodeList); } /** * 获取元素的父元素 * @param {Element} element */ function getParentElement(element) { if (!element) { return null; } return element.parentNode; } /** * 获取第一个不能为空的元素 * * @param {Element} baseNode 基点元素,在这个元素里搜索 selectorNameList 里第一个不为空的元素 * @param {String[]} selectorNameList * @returns */ function getOneNotEmptyNode(baseNode, selectorNameList) { if (!selectorNameList) { throw "getOneNotEmptyNode: 节点名字列表不能为空"; } for (const nodeName of selectorNameList) { if (!nodeName) { continue; } let element = baseNode.querySelector(nodeName); if (element) { // 找到第一个不为空的则返回 return element; } } return null; } /** * 按照 needAddElementList 传入顺序向基点元素后增加元素 * * @param {Element} baseNode 基点元素, 将 needAddElementList 放到这个节点后 * @param {Element[]} needAddElementList * @returns */ function sortNodeList(baseNode, needAddElementList) { if (baseNode == null || baseNode == undefined) { console.log("排序失败,基点元素为空"); return; } if (needAddElementList == null || needAddElementList == undefined) { console.log("排序失败,基点元素为空"); return; } // 先倒序,再增加 let newNeedAddElementList = needAddElementList.reverse(); for (const node of newNeedAddElementList) { if (!node) { continue; } baseNode.after(node); } } /** * 高亮关键字 */ function highlightKeyWord() { let baseInfo = document.querySelector("#base_information > div.content"); if (baseInfo == null || baseInfo == undefined) { // 只修改需求详情页面的数据 console.log("未找到需求详情页面数据"); return; } let elementList = [ baseInfo.querySelector("#ContentDeveloper")?.parentNode?.firstElementChild, // 开发人员 baseInfo.querySelector("#ContentReleasePlan")?.parentNode?.firstElementChild, // 发布计划 baseInfo.querySelector("#Content接口线上测试")?.parentNode?.firstElementChild, // 接口线上测试 baseInfo.querySelector("#Content是否需要灰测(WMS接口用)")?.parentNode?.firstElementChild, baseInfo.querySelector("#Content预计完成时间(WMS接口用)")?.parentNode?.firstElementChild, ]; let targetColor = "red"; for (const element of elementList) { if (element) { element.style.color = targetColor; } } } })();