Greasy Fork is available in English.

TAPD 修改“基本信息”显示

用于在 TAPD 需求详情页面高亮关键字、重排字段展示顺序,便于快速定位自己关心的字段

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