Greasy Fork

Greasy Fork is available in English.

TAPD 修改“基本信息”显示

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

当前为 2022-09-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         TAPD 修改“基本信息”显示
// @namespace    [email protected]
// @version      0.1.7
// @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) {
      element.style.color = targetColor;
    }
  }
})();