不要到处coco

coding增强

// ==UserScript==
// @name         不要到处coco
// @namespace    https://wydevops.coding.net/
// @version      1.8.4
// @description  coding增强
// @author       cuiqimeng
// @match        https://wydevops.coding.net/*
// @match        http://devops.ack.sunacwy.com.cn/*
// @require      http://code.jquery.com/jquery-2.1.1.min.js
// @require      https://cdn.staticfile.org/jquery-cookie/1.4.1/jquery.cookie.min.js
// @require      http://code.jquery.com/ui/1.11.0/jquery-ui.min.js
// @resource      https://cdn.bootcdn.net/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.css
// @icon         https://wydevops.coding.net/static/favicon.ico
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';
    console.log('注入成功', window.fetch);

    // 工具方法
    const Utils = {
        // arr数组使用prop属性求和
        reduceByProp(arr, prop) {
            return arr.reduce((total, curr) => {
                return total += (curr[prop] || 0)
            }, 0)
        },
        formatRate(number) {
            let ico;
            if (number >= 1)
                ico = '🌕'
            else if (number >= 0.75)
                ico = '🌔'
            else if (number >= 0.5)
                ico = '🌓'
            else if (number > 0)
                ico = '🌒'
            else ico = '🌑'
            return ico + (Number(number) * 100).toFixed(2) + '%'
        },
        debounce(fn, time = 500) {
            let t = null
            return function () {
                if (t) {
                    clearTimeout(t)
                }
                t = setTimeout(() => {
                    fn.apply(this, arguments);
                }, time)
            }
        },
        isCurrentWeek(past) {
            const pastTime = new Date(past).getTime()
            const today = new Date(new Date().toLocaleDateString())
            let day = today.getDay()
            day = day == 0 ? 7 : day
            const oneDayTime = 60 * 60 * 24 * 1000
            const monday = new Date(today.getTime() - (oneDayTime * (day - 1)))
            const nextMonday = new Date(today.getTime() + (oneDayTime * (8 - day)))
            if (monday.getTime() <= pastTime && nextMonday.getTime() > pastTime) {
                return true
            } else {
                return false
            }
        },
        formatStatus(str) {
            switch (str) {
                case '已完成':
                    return '✅'
                case '未开始':
                    return ''
                case '处理中':
                    return '♿️'
                default:
                    return ''
            }
        },
        formatPriority(priority) {
            switch (priority) {
                case 3:
                    return `🆘`
                case 2:
                    return `🚩`
                default:
                    return ''
            }
        }
    }

    let showParentIssues = true;
    const _fetch = window.fetch;
    window.fetch = function () {
        const url = arguments[0];
        if (url.includes('platform/user/current')) {
            console.log(arguments, document.cookie)
        }
        if (showParentIssues && url.includes('subtask-tree')) {
            //console.log('我是拦截器(o^^o)', arguments);
            const modifiedUrl = replaceQueryParam(url, 'showParentIssues', 'true');
            console.log(modifiedUrl);
            arguments[0] = modifiedUrl;
        }

        return _fetch.apply(this, arguments)
    }

    function replaceQueryParam(url, paramName, paramValue) {
        const regex = new RegExp(`(${encodeURIComponent(paramName)}=)[^&]+`);
        return url.replace(regex, `$1${encodeURIComponent(paramValue)}`);
    }


    let script = document.createElement('link');
    script.setAttribute('rel', 'stylesheet');
    script.setAttribute('type', 'text/css');
    script.href = "https://cdn.bootcdn.net/ajax/libs/jqueryui/1.13.2/themes/base/jquery-ui.css";
    document.documentElement.appendChild(script);

    const style = document.createElement('style');
    style.innerHTML = `
  .coco-info-icon {
    width: 13px;
    height: 13px;
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEyIDFhMyAzIDAgMDEzIDN2OGEzIDMgMCAwMS0zIDNINGEzIDMgMCAwMS0zLTNWNGEzIDMgMCAwMTMtM2g4ek05IDdIN3Y1aDJWN3pNOCA0Yy0uNiAwLTEgLjQtMSAxcy40IDEgMSAxIDEtLjQgMS0xLS40LTEtMS0xeiIgZmlsbD0iIzIwMkQ0MCIgZmlsbC1ydWxlPSJldmVub2RkIi8+PC9zdmc+) 50% no-repeat;
    background-size: cover;
    opacity: 1;
    margin-left: 4px;
    cursor: pointer;
    filter: brightness(10);
  }
  .coco-additions-wrapper {
    background: #b222221a;
    padding: 8px;
    border-radius: 4px;
    border-top-left-radius: 0;
  }
  .coco-additions-wrapper > p:last-child {
    margin-bottom: 0;
  }
  header.coco-additions-tag {
    display: inline-flex !important;
    font-size: 12px;
    background: firebrick;
    color: white;
    font-weight: bold;
    padding: 3px 5px 2px 4px;
    border-top-left-radius: 4px;
    border-top-right-radius: 4px;
    margin-top: 4px;
  }
  .coco-tooltip {
    position:absolute;
    border:1px solid #333;
    background:#f7f5d1;
    padding:1px;
    color:#333;
    display:none;
  }
  .coco-addition-hours {
    font-weight: bold;
    color: firebrick
  }
  .ui-tabs-active.ui-state-active .coco-addition-hours {
    color: #fff;
  }
  .sp_sub_tag {
  background: #e3f4fc;
  color: #045fe6;
  min-width: 24px !important;
  position: absolute !important;
  left: 15px;
  text-align: center;
  }
  .ui-tabs div.ui-tabs-panel {
    padding: 4px 6px;
    font-size: 13px;
  }

  .ui-tabs .ui-tabs-nav a.ui-tabs-anchor {
    padding: 2px 6px;
    font-size: 13px;
  }
  .ui-tabs ul.ui-tabs-nav {
    padding: 0em 0.2em 0.1em 0;
    background: #FFF5E6;
  }

  .ui-tabs .ui-tabs-nav li.ui-state-default.ui-corner-top {
    background: #fff;
    border: 1px solid #ffebc9;
    padding: 1px 2px;
    transition: 0.2s;
    box-shadow: 1px 1px 4px 1px #e0e0e0;
    border-radius: 4px;
    margin: 1px 2px;
  }
  .ui-tabs .ui-tabs-nav li.ui-state-default.ui-corner-top:hover {
    transform: scale(1.08);
    box-shadow: 1px 1px 8px 1px #e0e0e0;
  }


  li.ui-state-default.ui-corner-top.ui-tabs-active.ui-state-active {
    background: #FF9F08;
    border: 1px solid #FFB239;
  }
  div.ui-widget.ui-widget-content {
    border: none;
    padding-bottom: 16px;
    background: #FFF5E6;
  }
  @keyframes liuquan {
  0% {
    background: red;
  }
  50% {
    background: #a72929;
  }
  100% {
    background: red;
  }
}
  `
    document.head.appendChild(style)

    const store = {
        projectList: [],
        project: {}, iterationId: '', iteration: {}, personHoursMap: {}, story: [], fileds: []
    }
    const render = Utils.debounce(_render)
    const ID_VALUE = 'coco-tabs';
    const main = Utils.debounce(async () => {
        if (!/^\/p(.+)\//.test(location.pathname)) return;

        const projectName = /^\/p\/(.+?)\//.exec(location.pathname)[1];
        if (store.project.name !== projectName) {
            await getProjectId(projectName);
            await getMyProjectList();
            for (const datum of store.projectList) {
                await getIterationsList(datum)
            }
            console.log('projectList:', store.projectList);
            大爷让我加的需求()
        }
        const iterationId = new RegExp(`^\/p\/${projectName}\/iterations\/(.+?)\/`).exec(location.pathname)[1];
        if (store.iterationId !== iterationId) {
            $(`#${ID_VALUE}`).remove()
            store.iterationId = iterationId;
            await getIteration();
            await getSubTree();
            await getFieldsList();

            render()
        } else {
            if ($(`#${ID_VALUE}`) && $(`#${ID_VALUE}`).length) {
                /// console.log(666)
                $('table').scroll(() => {
                    incept()
                })
                $('table').click(() => {
                    incept()
                })
                incept()
            } else {
                render();
            }

        }
    }, 800)

    const rerender = Utils.debounce(_rerender, 1000)

    async function _rerender() {
        console.log('RERENDER:!!!!!!!!!!!')
        $(`#${ID_VALUE}`).remove()
        //store.iterationId = iterationId;
        await getIteration();
        await getSubTree();

        render();
    }

    setInterval(() => {
        main();
        hackLiYang();
    }, 1200)

    window.addEventListener('locationchange', main)

    window.onload = main


    const getProjectId = async function (projectName) {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `https://wydevops.coding.net/api/platform/project/${projectName}`,
                data: {},
                type: "get",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    console.log(data)
                    store.project = data;
                    resolve()
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })

    }

    const getSubTree = async function () {
        const projectId = store.project.id;
        const iterationId = store.iterationId;
        const currDataList = await getSubTreeSingle(projectId, iterationId);
        store.personHoursMap = _subTreeToMap(currDataList);
        Object.entries(store.personHoursMap).forEach(([name, item]) => {
            item.addition = [];
        })
        store.story = currDataList;
        console.log(store.iteration);
        const checker = function (iteration) {
            return Math.abs((iteration.startAt - store.iteration.startAt) / (24 * 60 * 60 * 1000)) < 6
        }
        for (const project of store.projectList) {
            if (project.id === store.project.id) continue;
            const iteration = project.$iterations.find(item => checker(item));
            if (iteration) {
                const list = await getSubTreeSingle(project.id, iteration.code);
                const _map = _subTreeToMap(list);
                // console.log(_map);
                Object.entries(store.personHoursMap).forEach(([name, item]) => {
                    const item0 = _map[name];
                    if (!item0) return;
                    item0.iteration = iteration;
                    if (item.addition.find(addItem => addItem.iteration.id === iteration.id)) {
                        return;
                    }
                    item.addition.push(item0);
                })
            }
        }
        console.log(store.personHoursMap);
    }

    const getSubTreeSingle = async function (projectId, iterationId) {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `https://wydevops.coding.net/api/project/${projectId}/iterations/${iterationId}/issues/tree`,
                data: JSON.stringify({
                    "content": {
                        "sort": {
                            "key": "PRIORITY",
                            "value": "DESC"
                        },
                        "conditions": [
                            {
                                "value": [
                                    "TODO",
                                    "PROCESSING",
                                    "COMPLETED"
                                ],
                                "key": "STATUS_TYPE",
                                "fixed": true,
                                "constValue": [],
                                "validInfo": [],
                                "userMap": {
                                    "COMPLETED": {
                                        "value": "COMPLETED"
                                    }
                                },
                                "selectedItems": {
                                    "COMPLETED": {
                                        "value": "COMPLETED"
                                    }
                                }
                            },
                            {
                                "value": [],
                                "valueChanged": null,
                                "validInfo": null,
                                "userMap": {},
                                "status": null,
                                "key": "ASSIGNEE",
                                "fixed": true,
                                "constValue": []
                            },
                            {
                                "value": "",
                                "key": "BASE_ISSUE_TYPE",
                                "fixed": true,
                                "constValue": []
                            },
                            {
                                "key": "ITERATION",
                                "value": [
                                    iterationId
                                ]
                            }
                        ],
                        "showSubIssues": true,
                        "showParentIssues": false
                    },
                    "page": 1,
                    "pageSize": 500
                }),
                type: "POST",
                contentType: "application/json;charset=UTF-8",
                headers: {
                    'x-xsrf-token': $.cookie('XSRF-TOKEN')
                },
                dataType: "json",
                success: function ({data, iteration_not_exist}) {
                    if (iteration_not_exist) {
                        console.error(iteration_not_exist);
                        return resolve([])
                    }
                    resolve(data ? (data.list || []) : [])
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                    //layer.alert('课程进度更新错误:' + errorThrown, { icon: 0 });
                }
            });
        })
    }

    function _subTreeToMap(list) {
        const personHoursMap = {};
        list.forEach(item => {
            if (item.subTasks.length === 0) {
                item.subTasks = item.subIssues.filter(it => it.type === "SUB_TASK")
            }
            item.$hours = item.subTasks.reduce((prev, curr, r) => prev + (curr.workingHours || 0), 0);
            item.subTasks.forEach(task => {
                const personName = task.assignee?.name ?? '未指定';
                const person = personHoursMap[personName] = personHoursMap[personName] || {
                    tasks: [], workingHours: 0, person: task.assignee
                };
                person.tasks.push(task);
                person.workingHours += task.workingHours;
            })
        });
        return personHoursMap;
    }

    async function getIteration() {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `https://wydevops.coding.net/api/project/${store.project.id}/iterations/${store.iterationId}`,
                data: {},
                type: "get",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    console.log(data)
                    store.iteration = data;
                    resolve()
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    async function subTaskDetail(subTextId) {
        const url = `https://wydevops.coding.net/api/project/${store.project.id}/issues/sub-tasks/${subTextId}/activities`
        return new Promise((resolve, reject) => {
            $.ajax({
                url: url,
                data: {},
                type: "get",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    //console.log(data)
                    resolve(data)
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    let __flag = false;
    const incept = Utils.debounce(() => {
        try {
            if (!__flag && $('span[class^=username]')[0]) {
                console.log(document.cookie)
                __flag = true;
            }
            /**

             if (!$('#_on_off').length) {
             const filterBarDom = $('div[class^="filter-bar-section-"]');
             //console.log('filterBarDom', filterBarDom);
             $(filterBarDom[0]).append($(`<button id="_on_off" style="width: 40px;height: 24px;color: rgb(25, 128, 97);background-color: rgb(195, 243, 203);margin: 2px 16px 10px 10px;border: none;font-weight: bold;border-radius: 3px;cursor: pointer;
             " onclick="_on_off_toggle(this)">ON</button>`));
             window._on_off_toggle = function (button) {
             if (button.innerHTML === 'ON') {
             showParentIssues = false;
             button.innerHTML = 'OFF';
             button.style.backgroundColor = '#f24c3d';
             button.style.color = '#fff';
             } else {
             button.innerHTML = 'ON';
             button.style.backgroundColor = '#c3f3cb';
             button.style.color = '#198061';
             showParentIssues = true;
             }
             }
             }
             **/
        } catch (e) {
        }
        store.story.forEach(item => {
            const dom = $(`a[href^='/p/${store.project.name}/requirements/issues/${item.code}/detail']`);
            // console.log(dom, item.$hours);
            try {
                const td = dom.parent().parent().parent().parent().children()[1];
                if ($(td).find('div.spspspspsp').length) throw new Error();
                td.style.position = 'relative';
                // `<div class="tag-OnRxknb07m epic-1Eg_rPGjj7"><div class="icon-24obWj6mLq"></div><div class="detail-hc4p8Zzxbo">【保洁】【保洁-0324】新增</div></div>`
                $(td).prepend(`<div sp class="spspspspsp tag-OnRxknb07m epic-1Eg_rPGjj7"><div class="icon-24obWj6mLq"></div><div class="detail-hc4p8Zzxbo">${`${item.$hours}`.slice(0, 5)}/<span style="color: #ffa200">${fiberMatch(item.$hours)}</span></div></div>`)
                /*$(td).append(`<div sp style='position: absolute; font-size: 12px;
        left: 32px;
        bottom: -2px;
        color: #222;
        font-weight: bold;'>${item.$hours}/<span style="color: #ffa200">${fiberMatch(item.$hours)}</span></div>`)*/
            } catch (e) {
            }
            item.subTasks.forEach(sub => {
                const sub_dom = $(`a[href^='/p/${store.project.name}/subtasks/issues/${sub.code}/detail']`);
                // console.log(sub_dom)
                try {
                    const td_sub = sub_dom.parent().parent().parent().parent().children()[1];
                    if ($(td_sub).find('div.spspspspsp').length) throw new Error();
                    td_sub.style.position = 'relative';
                    // `<div class="tag-OnRxknb07m epic-1Eg_rPGjj7"><div class="icon-24obWj6mLq"></div><div class="detail-hc4p8Zzxbo">【保洁】【保洁-0324】新增</div></div>`
                    $(td_sub).prepend(`<div sp class="spspspspsp tag-OnRxknb07m sp_sub_tag"><div class="detail-hc4p8Zzxbo">${`${sub.workingHours}`.slice(0, 5)}</div></div>`)

                } catch (e) {
                }
            })
            /*
      dom.append(`<div sp style='  position: absolute;
        left: auto;
        bottom: 0;
        color: #ffa200;
        font-weight: bold;'>${item.$hours}</div>`) */
        });
    })
    let interval = null;

    function _render() {
        window.$ = $
        interval && clearInterval(interval)
        const iterationRate = Number(((1 - store.iteration.remainingDays / ((store.iteration.endAt - store.iteration.startAt) / 3600 / 24 / 1000))).toFixed(2));
        interval = setInterval(() => {
            // 渲染
            const tableDom = $('table');
            if (!tableDom.length) {
                return
            }
            interval && clearInterval(interval)
            console.log(tableDom)
            // tableDom.prepend(`<button class="new-button-1kWt8bSwah default-14YlfkOcgs h-32-1KvNA1yjmi">一键更新故事点</button>`)
            const tabsWrapper = document.createElement('div');
            tabsWrapper.id = ID_VALUE;
            const ul = document.createElement('ul');
            if ($(`#${ID_VALUE}`) && $(`#${ID_VALUE}`).length) {
                $(`#${ID_VALUE}`).remove()
            }
            $(ul).appendTo(tabsWrapper)
            Object.entries(store.personHoursMap).sort((a, b) => b[1].workingHours - a[1].workingHours).forEach(([personName, item], index) => {
                const li = document.createElement('li');
                const a = document.createElement('a');
                a.href = `#${ID_VALUE}-${index + 1}`;
                const additionHours = Utils.reduceByProp(item.addition, 'workingHours');
                const additionText = additionHours ? ` + <span class="coco-addition-hours">${additionHours}</span>` : ''
                a.innerHTML = `${personName}(${item.workingHours}${additionText})`;
                if (!item.person) {
                    a.style.color = 'red'
                }
                $(a).appendTo(li);
                const div = document.createElement('div');
                div.id = `${ID_VALUE}-${index + 1}`;
                div.style.display = 'none';
                const subs = item.tasks.filter(item => item.issueTypeDetail.name === '子工作项');
                const completed = subs.filter(item => item.issueStatus.name === '已完成');
                const completedHours = Utils.reduceByProp(completed, 'workingHours');
                const subsHours = Utils.reduceByProp(subs, 'workingHours');
                const hoursRate = completedHours / subsHours;
                const deltaRate = hoursRate - iterationRate;

                let additionLines = item.addition.map(item => {
                    const subs = item.tasks.filter(item => item.issueTypeDetail.name === '子工作项');
                    const completed = subs.filter(item => item.issueStatus.name === '已完成');
                    return `
            <p>
             <b style="color: firebrick">${item.iteration.name}:</b>
             <b>${Utils.reduceByProp(completed, 'workingHours')}</b>/${Utils.reduceByProp(subs, 'workingHours')}&nbsp;&nbsp;&nbsp;&nbsp;
             完成率:<b>${Utils.formatRate(Utils.reduceByProp(completed, 'workingHours') / Utils.reduceByProp(subs, 'workingHours'))}</b>&nbsp;&nbsp;&nbsp;&nbsp;
             <b style="color: ${deltaRate > 0 ? 'green' : 'red'}">${deltaRate > 0 ? '⬆' : '⬇'}</b>${Utils.formatRate(deltaRate)}(期望:${Utils.formatRate(iterationRate)})
             子工作项进度:<b>${completed.length}</b>/${subs.length}&nbsp;&nbsp;&nbsp;&nbsp;完成率:<b>${Utils.formatRate(completed.length / subs.length)}</b>
            </p>
          `
                }).join('');
                if (additionLines) {
                    additionLines = `
            <header class="coco-additions-tag">共享资源任务工时统计<div class="coco-info-icon"></div></header>
            <div class="coco-additions-wrapper">
                ${additionLines}
            </div>
          `
                }

                let innerHTML = `
          <p style="margin-bottom: 0;"><b>${store.iteration.name}:</b>
            <b>${Utils.reduceByProp(completed, 'workingHours')}</b>/${Utils.reduceByProp(subs, 'workingHours')}&nbsp;&nbsp;&nbsp;&nbsp;
            完成率:<b>${Utils.formatRate(Utils.reduceByProp(completed, 'workingHours') / Utils.reduceByProp(subs, 'workingHours'))}</b>&nbsp;&nbsp;&nbsp;&nbsp;
            <b style="color: ${deltaRate > 0 ? 'green' : 'red'}">${deltaRate > 0 ? '⬆' : '⬇'}</b>${Utils.formatRate(deltaRate)}(期望:${Utils.formatRate(iterationRate)})
           子工作项进度:<b>${completed.length}</b>/${subs.length}&nbsp;&nbsp;&nbsp;&nbsp;完成率:<b>${Utils.formatRate(completed.length / subs.length)}</b>
            ${item.person ? `<button class="_week_report new-button-1kWt8bSwah default-14YlfkOcgs h-32-1KvNA1yjmi" style="margin-left: 12px;height: 24px;" data-user="${item.person.id}">生成周报</button>
          <button class="_all_report new-button-1kWt8bSwah default-14YlfkOcgs h-32-1KvNA1yjmi" style="margin-left: 12px;height: 24px;" data-user="${item.person.id}">生成迭代报告</button>` : ''}
          </p>
            ${additionLines}
        `;
                div.innerHTML = innerHTML;
                tabsWrapper.append(div)
                $(li).appendTo(ul);
            })
            $('div[class^="page-container-"]').parent().append(tabsWrapper)
            $('._week_report').click(async (event) => report_by_time()(event))
            $('._all_report').click(async (event) => report_by_time('all')(event))
            // const img = document.createElement('img');
            // img.src = `https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3ca7fba5-3cfa-402c-aaec-2b3e431e262d/226c3600-5069-429d-95be-79bce56a1796.png`;
            // tabsWrapper.append(img)
            // tabsWrapper.append($('div[class^="page-container-"]').parent())
            // $(function () {
            $(`#${ID_VALUE}`).tabs({
                collapsible: true
            });
            try {
                $('#coco-tabs > ul > li').on('contextmenu', (event) => {
                    event.preventDefault();
                    event.stopPropagation();
                    event.target.click();
                    switchPerson($(event.target)[0].innerText.replace(/(.+)\(.+/, '$1'), !$(event.target).parent().hasClass('ui-state-active'))
                })
            } catch (e) {
            }
            var x = 15;
            var y = -40;
            $(".coco-info-icon").mouseover(function (e) {
                this.myTitle = `
<div>1.使用者需拥有共享成员所在项目的权限</div>
<div>2.共享成员的多个冲刺的时间差不高于7天</div>
        `;
                this.title = "";
                var tooltip = "<div class='coco-tooltip'>" + this.myTitle + "</div>"; //创建 div 元素
                $("body").append(tooltip);	//把它追加到文档中
                $(".coco-tooltip")
                    .css({
                        "top": (e.pageY + y) + "px",
                        "left": (e.pageX + x) + "px"
                    }).show("fast");	  //设置x坐标和y坐标,并且显示
            }).mouseout(function () {
                this.title = this.myTitle;
                $(".coco-tooltip").remove();   //移除
            }).mousemove(function (e) {
                $(".coco-tooltip")
                    .css({
                        "top": (e.pageY + y) + "px",
                        "left": (e.pageX + x) + "px"
                    }).show("fast");
            });
            // });
            // tableDom.parentNode.insertBefore(document.createElement('div'), tableDom)
        }, 200)
    }


    /**
     * 左上角图标转圈圈
     */
    function hackLiYang() {
        try {
            // Array.from($('img')).forEach(el => {
            //   el.src = `https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3ca7fba5-3cfa-402c-aaec-2b3e431e262d/226c3600-5069-429d-95be-79bce56a1796.png`;
            //   const style = {
            //     animationName: 'loadingCircle',
            //     animationDuration: '1s',
            //     animationIterationCount: 999999,
            //     animationDelay: '1.2s',
            //   }
            //   Object.entries(style).forEach(([name, value]) => el.style[name] = value)
            // })
            const el = $('a[class^="enterprise-trigger-logo-"] > img')[0];
            //if (store.project && store.project.name === "ziyoumokuaiyouhua") {
            //el.src = `https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3ca7fba5-3cfa-402c-aaec-2b3e431e262d/226c3600-5069-429d-95be-79bce56a1796.png`;
            //}
            const style = {
                animationName: 'loadingCircle',
                animationDuration: '1s',
                animationIterationCount: 999999,
                animationDelay: '1.2s',
            }
            Object.entries(style).forEach(([name, value]) => el.style[name] = value)
        } catch (e) {

        }
    }


    /**
     * 史诗下所有事项(故事 + 子任务)
     * @param epicCode
     * @returns {Promise<unknown>}
     */
    function fetchEpicIssues(epicCode) {
        const url = `https://wydevops.coding.net/api/project/${store.project.id}/issues/epics/${epicCode}/issues`;
        return new Promise((resolve, reject) => {
            $.ajax({
                url: url,
                data: {},
                type: "get",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    //console.log(data)
                    resolve(data)
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    /**
     * 事项详情
     * @param code
     * @returns {Promise<unknown>}
     */
    function fetchIssuesDetail(code) {
        const url = `https://wydevops.coding.net/api/project/${store.project.id}/issues/${code}?withDescriptionMarkup=false`;
        return new Promise((resolve, reject) => {
            $.ajax({
                url: url,
                data: {},
                type: "get",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    //console.log(data)
                    resolve(data)
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    function report_by_time(type = 'week') {
        return async function (event) {
            event.target.innerText = '请稍后';
            event.target.disabled = 'disabled';
            const userId = event.target.dataset.user;
            console.log(event.target.dataset.user, store.story);
            let allSubTasks = [];
            store.story.forEach(item => {
                allSubTasks = [...allSubTasks, ...(item.subTasks.map(item1 => ({
                    ...item1,
                    module: (item.module && item.module.id) ? item.module : {
                        id: 999999,
                        name: "🈚️模块",
                    },
                    story: {code: item.code, name: item.name, priority: item.priority, ...item}
                })))]
            })
            console.log(allSubTasks);
            let processingTasks = allSubTasks.filter(item => item.assignee && (item.assignee.id == userId));
            if (type === 'week') processingTasks = processingTasks.filter(item => item.issueStatus.type !== 'TODO')
            const codes = processingTasks.map(item => item.code);
            const weekly_tasks = [];
            for (const code of codes) {
                const subTaskLogs = await subTaskDetail(code);
                const _logs = subTaskLogs.filter(item => item.issueLog && item.issueLog.target === 'STATUS')
                const log = _logs[_logs.length - 1];

                if (type === 'week') {
                    const time = new Date(log.createdAt)
                    if (Utils.isCurrentWeek(time)) {
                        weekly_tasks.push(processingTasks.find(item => item.code === code))
                    }
                } else {
                    weekly_tasks.push(processingTasks.find(item => item.code === code))
                }
            }
            // const epicMap = {};
            // weekly_tasks.forEach(item => {
            //   epicMap[item.epic.code] = epicMap[item.epic.code] || [];
            //   epicMap[item.epic.code].push(item)
            // })
            // const groupByEpic = Object.entries(epicMap).map(([epicCode, tasks]) => ({...tasks[0].epic, tasks}))
            const getCustomFieldsValue = (customeFileds = {}, name) => {
                const field = store.fileds.find(item => item.name === name);
                if (!field) return '';
                const fieldOptions = field.options || [];
                const fieldNameMap = fieldOptions.reduce((prev, curr) => {
                    prev[curr.id] = curr.name;
                    return prev;
                }, {})
                const key = String(field.issueFieldId);
                try {
                    for (const [k, v] of Object.entries(customeFileds)) {
                        if (String(k) === key) {
                            if (v instanceof Array) {
                                return v.map(item => fieldNameMap[name]).join(',');
                            } else {
                                return fieldNameMap[v];
                            }
                        }
                    }
                } catch (e) {
                    return ''
                }
            }
            const moduleMap = {};
            weekly_tasks.forEach(item => {
                const moduleName = getCustomFieldsValue(item.story ? item.story.customFields: item.customFields, '所属项目');
                // const _nameMatches = /【(.+?)】/.exec(item.story.name);
                // const moduleName = _nameMatches.length > 0 ? _nameMatches[1] : '未知';
                item.moduleName = moduleName;
                moduleMap[moduleName] = moduleMap[moduleName] || [];
                moduleMap[moduleName].push(item)
            })
            const groupByModule = Object.entries(moduleMap).map(([moduleName, tasks]) => ({moduleName, tasks}))
            console.log(groupByModule)
            let text = ``;
            for (let index in groupByModule) {
                const module = groupByModule[index];
                // 计算史诗进度 begin
                // const epicIssues = await fetchEpicIssues(epic.code);
                // const statData = {
                //   total: 0,
                //   curr: 0
                // }
                // for (const story of epicIssues) {
                //   const ownerTasks = story.subTasks.filter(task => task.issueTypeDetail.name === '子工作项' && task.assignee?.id == userId);
                //   console.log('ownerTasks:', ownerTasks)
                //   for (const task of ownerTasks) {
                //     const taskDetail = await fetchIssuesDetail(task.code);
                //     statData.total += taskDetail.workingHours;
                //     if (taskDetail.issueStatus.type === "COMPLETED") {
                //       statData.curr += taskDetail.workingHours;
                //     }
                //     console.log(taskDetail, statData.curr, statData.total)
                //   }
                // }
                // 计算史诗进度 end
                text += `<b style="font-weight: bold;">${Number(index) + 1}、${module.moduleName}</b>`
                text += `<ul>`
                module.tasks.forEach(task => {
                    text += `<li>
                  <a href="https://wydevops.coding.net/p/${store.project.name}/requirements/issues/${task.story.code}/detail" title="${task.story.name}">故事 ${task.story.code}</a>
                   / <a href="https://wydevops.coding.net/p/${store.project.name}/requirements/issues/${task.story.code}/detail/subissues/${task.code}">任务 ${task.code}</a>
                  :${Utils.formatPriority(task.story.priority)}${task.name}(${task.workingHours}) <span style="color: red"> ${Utils.formatStatus(task.issueStatus.name)}</span>
                  </li>`
                });
                text += `</ul><br/>`;
            }
            event.target.innerText = type === 'week' ? '生成周报' : '生成迭代报告';
            event.target.removeAttribute('disabled')
            const MIMETYPE = "text/html";

            const data = [new ClipboardItem({[MIMETYPE]: new Blob([text], {type: MIMETYPE})})];
            navigator.clipboard.write(data).then(function () {
                alert("复制成功!去试试粘贴到Excel内吧~")
            }, function () {
                alert("不知道怎么回事,再试一次吧!")
                console.error("Unable to write to clipboard. :-(");
            });

        }
    }

    const FiberList = [0, 0.5, 1, 2, 3, 5, 8, 13, 20, 40]

    function fiberMatch(number) {
        if (!number) return 0;
        const _number = number / 8;
        return FiberList.find(item => _number <= item);
    }

    // 设置故事点
    async function setStoryPoint(story, point) {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `https://wydevops.coding.net/api/project/${store.project.id}/issues/requirements/${story}/fields`,
                data: {storyPoint: point},
                type: "PATCH",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    console.log(data)
                    resolve()
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    async function getMyProjectList() {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `https://wydevops.coding.net/api/platform/project/recent/views/search?pmType=PROJECT&keyWord=`,
                data: {},
                type: "GET",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    store.projectList = data;
                    resolve()
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    async function getIterationsList(project) {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `https://wydevops.coding.net/api/project/${project.id}/iterations?page=1&pageSize=100&keywords=&status=WAIT_PROCESS&status=PROCESSING&startDate=&endDate=&sortBy=CODE%3ADESC`,
                data: {},
                type: "GET",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    console.log('$iterations:', data);
                    project.$iterations = data.list;
                    resolve()
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    async function getFieldsList() {
        return new Promise((resolve, reject) => {
            $.ajax({
                url: `https://wydevops.coding.net/api/project/${store.project.id}/issues/ITERATION_ISSUE/filters/more-fields`,
                data: {},
                type: "GET",
                contentType: "application/x-www-form-urlencoded; charset=UTF-8",
                dataType: "json",
                success: function ({data}) {
                    console.log('$fields:', data);
                    store.fileds = data;
                    resolve()
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    console.error(arguments)
                    reject(errorThrown)
                }
            });
        })
    }

    // 创建 Fetch 请求拦截器对象
    const fetchInterceptor = {
        // 请求拦截
        request: function (url, options) {
            // 调用拦截回调函数,并传递请求参数
            // console.log('请求拦截:', url, options);

            // 修改请求参数示例
            // options.headers['Authorization'] = 'Bearer xxxxxxx';

            return [url, options];
        },

        // 响应拦截
        response: function (response) {
            // 调用拦截回调函数,并传递响应数据
            //console.log('响应拦截:', response);

            // 修改响应数据示例
            // var modifiedResponse = { status: 200, data: 'Modified response' };
            // return Promise.resolve(new Response(JSON.stringify(modifiedResponse), response));

            return response;
        }
    };

    const __fetch = window.fetch;
    // 重写 Fetch API 的 fetch 方法
    window.fetch = function () {
        const args = arguments;
        const url = args[0];
        const options = args[1];

        // 调用请求拦截回调函数
        const interceptedRequest = fetchInterceptor.request(url, options);

        // 发起原始的 Fetch 请求
        return __fetch.apply(this, interceptedRequest)
            .then(function (response) {
                // 调用响应拦截回调函数
                return fetchInterceptor.response(response);
            });
    };


    // 创建拦截器对象
    const interceptor = fetchInterceptor;

    // 请求拦截
    interceptor.request = function (url, options) {

        if (options.method === 'PATCH') {
            console.log('自定义请求拦截:', url, options);
        }
        return [url, options];
    };

    // 响应拦截
    interceptor.response = function (response) {

        if (response.url.endsWith("/fields") || response.url.endsWith("join/iteration")) {
            rerender();
        }
        //  console.log(response, document.cookie)
        if (response.url.endsWith('platform/user/current')) {
            console.log(response, document.cookie)
        }
        // 修改响应数据示例
        var modifiedResponse = {status: 200, data: 'Modified response'};
        return response;
    };


    let _i1, _i2;

    function switchPerson(personName = "崔启蒙", onlyClear = false) {
        console.log("switchPerson", personName)
        _i1 && clearInterval(_i1)
        _i2 && clearInterval(_i2)
        try {
            // const $DropDown = $($('div[class^="filter-bar-section-"]').find('div[class^="dropdown-"]')[2]);
            const $DropDown = $($($('div[class^="filterAttrs-"]').find('span[class="filterFixedItem"]'))[1]);
            $DropDown.find('div[class^="trigger-"]')[1].click();
            // $DropDown[0].style.visibility = 'hidden'
            _i1 = setInterval(() => {
                if (!$DropDown.find('div[class^="newfilter-dropdown panel-"]').length) {
                    $DropDown.find('div[class^="trigger-"]')[1].click();
                    return
                }
                const $Panel = $DropDown.find('div[class^="newfilter-dropdown panel-"]');
                // $Panel.hide();
                if ($Panel.find('div[class^="side-operation-"]').find('span[class^="clear-button-"]').length) {
                    // console.log($Panel, $Panel.find('div[class^="clear-button-"]').length, $Panel.find('div[class^="clear-button-"]')[0])
                    $Panel.find('div[class^="side-operation-"]').find('span[class^="clear-button-"]')[0].click();
                }
                clearInterval(_i1);
                if (onlyClear) {
                    document.body.click()
                    return;
                }
                const $Input = $Panel.find('input')[0];
                const _key = Object.keys($Input).find(key => key.startsWith('__reactInternalInstance'));
                $Input[_key].memoizedProps.onChange({target: {value: personName}})
                _i2 = setInterval(() => {
                    if ($Panel.find('div[class^="item-"]').length !== 1) return
                    $Panel.find('div[class^="item-"]').click()
                    clearInterval(_i2);
                    setTimeout(() => {
                        document.body.click()
                    }, 80)
                }, 100)
            }, 150)
        } catch (e) {

        }
    }

    // 这里是kubesphere的生产环境标识
    const i = setInterval(() => {
        if (!document.querySelector('#root > div > div > a')) {
            return;
        }
        clearInterval(i)
        document.querySelector('#root > div > div > a').innerHTML += `<span style="
  position: absolute;
  left: calc(50% - -79px);
  top: 6px;
  font-size: 28px;
  font-weight: bold;
  color: #fff;
  padding: 4px 10px;
  background: red;
  border-radius: 12px;
  animation: liuquan 1.6s infinite;
">生产环境</span>`
    }, 200)


    function 大爷让我加的需求() {
        try {
            if (!$('.daye').length && store.project.name) {
                const wrapper = $('#layout > aside div.layout-sider-content-3--_1GXfMA > div.layout-sider-menu-list-2uEqjcLrKw > div:nth-child(1) > div > a:nth-child(4)');
                wrapper.parent().append(`<a href="/p/${store.project.name}/releases" class="daye layout-menu-3WFaMkjTrU" data-code="PROJECT_BUG">
<div class="layout-menu-wrap-2CsF_0f6Mi">
<span class="ant-badge badge-onAOhHP1W_"><img src="/micro-frontend/layout/static/team/all-artifacts.svg" class="menu-icon-cgCSmHHLei"></span>
<div class="menu-name-wrap-39B9VHvnAH"><span class="menu-name-aUEf_zcSi5">版本</span><div class="menu-tags-26-9VwO6r8"></div></div>
</div>
</a>`)
                wrapper.parent().append(`<a href="/p/${store.project.name}/epics/issues" class="daye layout-menu-3WFaMkjTrU" data-code="PROJECT_EPIC">
<div class="layout-menu-wrap-2CsF_0f6Mi">
<span class="ant-badge badge-onAOhHP1W_"><img src="/micro-frontend/layout/static/project/appops.svg" class="menu-icon-cgCSmHHLei"></span>
<div class="menu-name-wrap-39B9VHvnAH"><span class="menu-name-aUEf_zcSi5">史诗</span><div class="menu-tags-26-9VwO6r8"></div></div>
</div>
</a>`);
                wrapper.parent().append(`<a href="/p/${store.project.name}/bug-tracking/issues" class="daye layout-menu-3WFaMkjTrU" data-code="PROJECT_BUG">
<div class="layout-menu-wrap-2CsF_0f6Mi">
<span class="ant-badge badge-onAOhHP1W_"><img src="/micro-frontend/layout/static/project/appops.svg" class="menu-icon-cgCSmHHLei"></span>
<div class="menu-name-wrap-39B9VHvnAH"><span class="menu-name-aUEf_zcSi5">缺陷</span><div class="menu-tags-26-9VwO6r8"></div></div>
</div>
</a>`)
            }
        } catch (e) {

        }

    }

})();