// ==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
// ==/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;
}
}
}
})();