TAPD 修改“基本信息”显示

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

// ==UserScript==
// @name         TAPD 修改“基本信息”显示
// @namespace    [email protected]
// @version      0.1.9
// @description  用于在 TAPD 需求详情页面高亮关键字、重排字段展示顺序,便于快速定位自己关心的字段
// @author       qiuhongliang
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tapd.cn
// @match        https://www.tapd.cn/*/prong/stories/view/*
// @match        https://www.tapd.cn/tapd_fe/*/story/detail/*
// @grant        none
// @license      GPL
// @run-at       document-idle
// ==/UserScript==

(async function () {
  "use strict";

  async function sleep(msSecond) {
    return new Promise((resolve) => setTimeout(resolve, msSecond));
  }

  let tryCount = 0;
  while (tryCount++ < 10) {
    // 判断页面是否加载
    console.error("继续尝试判断页面是否加载完毕");
    if (getBaseInformationDiv(false)) {
      await sleep(500);
      break;
    }
    await sleep(500);
  }

  highlightKeyWord();
  changeFieldOrder();

  /**
   * 获取右侧“基础信息”模块
   * @returns {Element}
   */
  function getBaseInformationDiv(throwException = true) { 
    let baseInfo = document.querySelector("#root > div.app-wrap > div.app > div.app__content div.detail-container-wrapper.entity-detail-layout div.detail-container-right > div.entity-detail-right");
    if (baseInfo) { 
      return baseInfo;
    }
    
    console.error("未找到需求详情页面数据");
    if (throwException) { 
      throw new Error("未找到需求详情页面数据");
    }
  }

    /**
   * 高亮关键字
   */
    function highlightKeyWord() {
      let baseInfo = getBaseInformationDiv();
      
      let elementList = [
        getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="owner"]'])),                 // 处理人
        getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="developer"]'])),             // 开发人员
        getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="release_id"]'])),            // 发布计划
        getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="due"]'])),                   // 预计结束
        getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_24"]'])),       // 预计完成时间(WMS接口用)
        getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_14"]'])),       // 接口线上测试
        getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_23"]'])),       // 是否需要灰测(WMS接口用)
      ];
  
      let targetColor = "red";
      for (const element of elementList) {
        if (element && element.firstElementChild) {
          element.firstElementChild.style.color = targetColor;
        }
      }
  }
  
  /**
   * 修改字段展示顺序,关心的字段靠前面排
   */
  function changeFieldOrder() {
    let baseInfo = getBaseInformationDiv();

    // 将处理人作为基点元素,对关心的元素进行重排
    let needAddElementList = [
      // 开发阶段关注内容 ----------------------------------------------------------------------------------------------
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="developer"]'])),         // 开发人员
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="iteration_id"]'])),      // 迭代
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_one"]'])),  // 卖家账号
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="priority"]'])),          // 优先级
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="due"]'])),               // 预计结束
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_24"]'])),   // 预计完成时间(WMS接口用)

      // 线上测试阶段关注内容 ------------------------------------------------------------------------------------------
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_four"]'])),   // 产品经理
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_six"]'])),    // 评审人
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_23"]'])),     // 是否需要灰测(WMS接口用)
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_25"]'])),     // 线上跟进情况(WMS接口用)
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_14"]'])),     // 接口线上测试

      // 接口上线关注内容 ----------------------------------------------------------------------------------------------
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="release_id"]'])),          // 发布计划

      // 代码审核阶段——接口还未启用该流程,故往后放
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_seven"]'])),         // 代码核查人员
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_eight"]'])),         // 代码核查状态

      // 目前接口不关注,但是挺重要的字段 ------------------------------------------------------------------------------
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="begin"]'])),             // 预计开始
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="progress"]'])),          // 进度
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="effort"]'])),            // 预估工时
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="effort_completed"]'])),  // 完成工时
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="remain"]'])),            // 剩余工时
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="exceed"]'])),            // 超出工时

      // 其他不重要内容 ------------------------------------------------------------------------------------------------
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="module"]'])),            // 模块 
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_11"]'])),   // 加急处理
    ];

    let statusOwner = getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="owner"]']));  // 处理人
    sortNodeList(statusOwner, needAddElementList);

    // 不关心的元素移动到最底部-----------------------------------------------------------------------------------------
    let endNodeList = [
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="category_id"]'])),          //  分类
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_five"]'])),    //  需求反馈人
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_12"]'])),      //  区域
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_13"]'])),      //  类目
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="templated_id"]'])),         //  创建模板
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_30"]'])),      //  BUG关联需求
      getParentElement(getOneNotEmptyNode(baseInfo, ['[field-name="custom_field_31"]'])),      //  上线仨月内出现且稳定复现
    ];
    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);
    }
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址