pingcode helper

快捷导入pingcode任务

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         pingcode helper
// @namespace    http://tampermonkey.net/
// @version      2024-04-09
// @description  快捷导入pingcode任务
// @author       飞天小猪
// @match        http://pc.tongqisumu.com:30001/pjm/projects/*
// @icon https://gongjux.com/files/3/4453uhm5937m/32/favicon.ico
// @grant        none
// @require https://greasyfork.org/scripts/453166-jquery/code/jquery.js?version=1105525
// @license MIT
// ==/UserScript==

(function () {
  'use strict';
  let projectInfo
  const headers = {
    cookie: document.cookie,
    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
  }

  const setButton = () => {
    // 创建一个新的button元素
    var button = document.createElement('button');
    button.textContent = '虤'; // 设置按钮文字

    // 设置按钮的基本样式(包括固定定位与默认透明度)
    button.style.cssText = `
position: fixed; /* 或者 absolute,取决于您的布局需求 */
top: 30px;
left: 10px;
z-index: 99999;
background-color: #007bff;
color: white;
padding: 6px 12px;
border: none;
border-radius: 5px;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.3s ease;
`;
    // 添加鼠标悬浮时的透明度变化
    button.addEventListener('mouseover', function () {
      this.style.opacity = 1;
    });

    // 添加鼠标离开时的透明度变化
    button.addEventListener('mouseout', function () {
      this.style.opacity = 0.3;
    });
    button.addEventListener('click', showPanel)
    // 将按钮添加到文档中
    document.body.appendChild(button);
  }
  const showPanel = async () => {
    const info = extractProjectIdAndSprintId(location.href)
    if (info.sprintId && info.projectName) {
      const { data } = await axios.get(`http://pc.tongqisumu.com:30001/api/agile/projects/${info.projectName}?addons=true&members=true&t=${new Date().getTime()}`, {
        headers
      })
      if (data.code === 200) {
        projectInfo = data.data
        await getPanelInfo()
      } else {
        alert("数据解析失败")
      }
    } else {
      alert("数据解析失败")
    }
  }
  const extractProjectIdAndSprintId = (url) => {
    const pathParts = url.split('/');
    const projectIndex = pathParts.indexOf('projects');
    const sprintIndex = pathParts.indexOf('sprint');

    if (projectIndex >= 0 && projectIndex + 1 < pathParts.length &&
      sprintIndex >= 0 && sprintIndex + 1 < pathParts.length) {
      const projectName = pathParts[projectIndex + 1];
      const sprintId = pathParts[sprintIndex + 1];
      return {
        projectName: projectName,
        sprintId: sprintId
      };
    }
    return null;
  }
  const getPanelInfo = async () => {
    const info = extractProjectIdAndSprintId(location.href)
    const params = {
      "sprint_id": info.sprintId,
      "criteria": {
        "search": {
          "keywords": "",
          "scopes": [
            "identifier",
            "title"
          ]
        },
        "conditions": [],
        "mode": 1,
        "show_type": 1,
        "group_by": "",
        "sort_by": "identifier",
        "sort_type": 1
      },
      "pi": 0,
      "ps": 100
    }
    const { data } = await axios.post(`http://pc.tongqisumu.com:30001/api/agile/projects/${projectInfo.value._id}/scrum/sprint/views/work-item/content`, params, { headers });
    if (data.code === 200) {
      const storys = data.data.value.filter(i => i.type === 3)
      genDom(storys)
    } else {
      alert(`项目故事获取失败,msg:${data.message}`)
    }
  }
  const genDom = (storys) => {
    const mask = document.createElement('div');
    mask.addEventListener('click', (event) => {
      // 判断点击的是mask本身还是其子元素
      if (event.target === mask) {
        document.body.removeChild(mask);
      } else {
        event.stopPropagation(); // 阻止子元素点击事件向上冒泡到mask
      }
    });
    mask.classList.add('mask');
    mask.style.cssText = `
background-color: rgba(0, 0, 0, 0.2);
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
`
    document.body.appendChild(mask);
    const tableWrap = document.createElement('div')
    tableWrap.classList.add('table-wrap');
    tableWrap.style.cssText = `
max-width: 1000px;
max-height: 900px;
min-height: 700px;
min-width: 720px;
width: 50vw;
height: 70vh;
background-color: #fff;
overflow-y: auto;
padding: 10px;
`
    // 创建关闭按钮
    const closeBtn = document.createElement('button')
    closeBtn.textContent = '关闭'
    closeBtn.style.cssText = `
    background-color: #F56C6C;
    color: white;
    padding: 6px 12px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    margin-right: 10px;
    `
    closeBtn.addEventListener('click', () => {
      document.body.removeChild(mask);
    })
    const select = document.createElement('select')
    select.style.cssText = `
    width: 200px;
    padding: 4px 8px;
    border-radius: 4px;
    border-color: #2165f9;
    margin-right: 10px;
    `
    for (let i = 0; i < storys.length; i++) {
      const option = document.createElement('option')
      option.value = storys[i]._id
      option.innerText = storys[i].title
      select.appendChild(option)
    }
    const prevFixInput = document.createElement('input')
    prevFixInput.placeholder = '统一标题前缀'
    prevFixInput.style.cssText = `
    margin-right: 10px;
padding: 3px;
border-radius: 4px;
border: 1px solid #2165f9;`
    const endFixInput = document.createElement('input')
    endFixInput.placeholder = '统一标题后缀'
    endFixInput.style.cssText = `
    margin-right: 10px;
padding: 3px;
border-radius: 4px;
border: 1px solid #2165f9;`
    // 创建批量插入按钮
    const requestBtn = document.createElement('button')
    requestBtn.textContent = '批量插入'
    requestBtn.style.cssText = `
    background-color: #2165f9;
    color: white;
    padding: 6px 12px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    `
    requestBtn.addEventListener('click', () => {
      if (!textArea.value) return alert('请至少输入一条任务子模块名称')
      const rows = textArea.value.split('\n')
      const storyId = select.value
      const queue = rows.map(i => {
        const [title, start, end] = i.split('\t')
        const fixTitle = prevFixInput.value + title + endFixInput.value
        return { title: fixTitle, start: new Date(start + ' 00:00:00').getTime() / 1000, end: new Date(end + ' 00:00:00').getTime() / 1000 }
      })
      requestQueue(queue, storyId)
    })
    // 创建操作区域
    const btnArea = document.createElement('div')
    btnArea.style.cssText = `
height: 40px;
margin-bottom: 8px;
box-sizing: border-box;
padding: 4px 0;
`
    const textAreaWrap = document.createElement('div')
    textAreaWrap.style.cssText = `
height: 240px;
width: 100%;
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
`
    const textArea = document.createElement('textarea')
    textArea.placeholder = '请输入要插入的任务子模块名称 格式为 标题\\t 起始时间:YYYY-MM-DD \\t 结束时间:YYYY-MM-DD'
    textArea.style.cssText = `
width: 100%;
height: calc(100% - 6px);
border: 1px solid #2165f9;
box-sizing: border-box;
padding: 10px;
border-radius: 4px;
`
    const tip = document.createElement('div')
    tip.id = 'process-tip'
    tip.innerText = '当前任务 0/0'
    tip.style.cssText = `
    padding-left:10px;
    `
    const textAreaWrap2 = document.createElement('div')
    textAreaWrap2.style.cssText = `
height: 240px;
width: 100%;
overflow-y: auto;
padding: 10px;
box-sizing: border-box;
`
    const textArea2 = document.createElement('textarea')
    textArea2.id = 'textArea2_0131'
    textArea2.disabled = true
    textArea2.placeholder = ''
    textArea2.style.cssText = `
width: 100%;
height: calc(100% - 6px);
border: 1px solid #2165f9;
box-sizing: border-box;
padding: 10px;
border-radius: 4px;
background-color: #282c34;
color: #61aeee;
`
    btnArea.appendChild(closeBtn)
    btnArea.appendChild(select)
    btnArea.appendChild(prevFixInput)
    btnArea.appendChild(endFixInput)
    btnArea.appendChild(requestBtn)
    tableWrap.appendChild(btnArea)
    textAreaWrap.appendChild(textArea)
    textAreaWrap2.appendChild(textArea2)
    tableWrap.appendChild(textAreaWrap)
    tableWrap.appendChild(tip)
    tableWrap.appendChild(textAreaWrap2)
    mask.appendChild(tableWrap)
  }
  const request = async (url, data) => {
    return axios.post(url, data, {
      headers
    })
  }
  const requestQueue = async (queue, storyId) => {
    const url = 'http://pc.tongqisumu.com:30001/api/agile/v2/work-item'
    for (let i = 0; i < queue.length; i++) {
      const processTip = document.getElementById('process-tip')
      processTip.innerText = `当前任务 ${i + 1}/${queue.length}`
      const data = {
        "start": {
          "date": queue[i].start, // 开始时间 / 1000
          "with_time": 0
        },
        "due": {
          "date": queue[i].end, // 结束时间 / 1000
          "with_time": 0
        },
        "participants": [],
        "project_id": projectInfo.value._id, // 项目id
        "type": 4, // 任务对应的type类型
        "parent_id": storyId, // 故事id
        "properties": {},
        "raw_values": {
          "title": queue[i].title,
          "project_id": projectInfo.value._id,
          "type": 4,
          "parent_id": storyId,
          "start": {
            "date": queue[i].start,
            "with_time": 0
          },
          "due": {
            "date": queue[i].end,
            "with_time": 0
          },
          "participants": []
        },
        "title": queue[i].title // 标题
      }
      const res = await request(url, data)
      const textArea2 = document.getElementById('textArea2_0131')
      if (res.data.code === 200) {
        textArea2.value += `SUCCESS: ${queue[i].title} 添加成功\n`
        textArea2.scrollTop = textArea2.scrollHeight
      } else {
        textArea2.value += `ERROR: ${queue[i].title} 添加失败 ---> ${res.data.message}\n`
        textArea2.scrollTop = textArea2.scrollHeight
      }
    }
  }
  const sleep = (time) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, time * 1000)
    })
  }
  async function init() {
    $('head').append('<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>') // 名称:layer,版本:3.5.1,原始地址:https://www.layuicdn.com/#Layer
    // 等待layer加载成功
    while (true) {
      if (typeof axios != 'undefined') {
        break
      }
      await sleep(0.5)
    }
    setButton()
  }
  init()
})();