源码安装
zhangsan / xieying3D-manager

// ==UserScript==
// @name         xieying3D-manager
// @namespace    http://tampermonkey.net/
// @version      2025.2.25.1
// @description  try it
// @author       You
// @match        https://data-encoder.ruqimobility.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ruqimobility.com
// @grant        none
// @license     MIT
// @downloadURL https://update.greasyfork.org/scripts/523031/xieying3D-manager.user.js
// @updateURL https://update.greasyfork.org/scripts/523031/xieying3D-manager.meta.js
// ==/UserScript==

(function() {
    if(location.hash !== '#/task-panel/task-panel-list') return

    window.$ = Document.prototype.$ = Element.prototype.$ = $;
    window.$$ = Document.prototype.$$ = Element.prototype.$$ = $$;

    const _ds = window._ds = {
        data: null,
        projectIds: {}
    }

    const baseURL = 'https://data-encoder\\.ruqimobility\\.com'
    hijackXHR(function() {
        const xhr = this;
        // console.log(xhr.responseURL);
        const data = JSON.parse(xhr.responseText);

        [
            () => {
                if(findURL(xhr.responseURL, [`^${baseURL}/annotation/workflow/queryDatasetByGroup`])) {
                    // console.log(data.data)
                    _ds.data = data.data
                }
            },
            () => {
                if(findURL(xhr.responseURL, [`^${baseURL}/annotation/project/findByMember\\?status=PROGRESS`, `^${baseURL}/annotation/project/findByUserCustom\\?status=PROGRESS`])) {
                    console.log(data.data)
                    _ds.projectIds = {}
                    data.data.map(item => {
                        _ds.projectIds[item.name] = item.id
                    })
                }
            }
        ].forEach(item => item())

        function findURL(responseURL, URLs) {
            return URLs.some(url => new RegExp(url).test(responseURL))
        }
    })

    const appear = {}
    Obs(document, mrs => {
        console.log(mrs)
        mrs.forEach(mr => {
            [...mr.addedNodes].some(an => {
                // console.log(an)
                if(!appear.menu && $('ul.el-menu')) {
                    const menu = $('ul.el-menu')
                    const textarea_input = createEl('textarea', {
                        className: 'textarea_input',
                        style: {
                            width: '180px',
                            height: '150px',
                            margin: '100px 0 0 10px',
                            outline: 'none',
                            padding: '5px 10px',
                            fontSize: '14px',
                        },
                        oninput: function() {
                            _ds.allProjectData && verifyActivity(_ds.allProjectData)
                        }
                    })
                    const btnWrap = createEl('div', {
                        style: {
                            display: 'flex',
                            justifyContent: 'end',
                            marginRight: '10px',
                        }
                    })

                    const btn_style = {
                        width: '75px',
                        padding: '0 5px',
                        height: '25px',
                        background: '#ddd',
                        borderRadius: '5px',
                        textAlign: 'center',
                        lineHeight: '25px',
                        fontSize: '14px',
                        color: '#555',
                        cursor: 'pointer',
                    }
                    const btn_preview = createEl('div', {
                        style: {...btn_style,
                            marginRight: '5px'
                        },
                        innerText: '数据预览',
                        onclick: function() {
                            textarea_input.value = '数据查询中...'
                            const focusProjectId = /#(\d*)/.exec($('.list-content .list-content_item.is_active').textContent)[1]

                            fetch("https://data-encoder.ruqimobility.com/annotation/workflow/taskHallRunning", {
                                "headers": {
                                    "authorization": 'Bearer ' + JSON.parse(localStorage.getItem('usePermissionStore-prod')).token,
                                    "cache-control": "no-cache",
                                    "content-type": "application/json",
                                    "sec-fetch-dest": "empty",
                                    "sec-fetch-mode": "cors",
                                    "sec-fetch-site": "same-origin"
                                },
                                "body": `{\"assignType\":\"ALL\",\"pageNo\":1,\"pageSize\":100000,\"activityId\":\"annotate\",\"projectId\":${focusProjectId}}`,
                                "method": "POST",
                                "mode": "cors",
                                "credentials": "include"
                            }).then(res => res.json()).then(res => {
                                console.log(res)
                                const list = res?.data?.list
                                if(list === null) {
                                    return (textarea_input.value = '未查询到可预览的数据')
                                } else if(list === void 0) {
                                    alert('数据获取失败,请重试')
                                }
                                const taskIds = res.data.list.filter(item => item.dataAnnotationRecordId && !item.assigneeId).map(item => item.datasetId)

                                textarea_input.value = taskIds.length ? taskIds.reduce((counter, item) => counter+item+'\n', '') : '未查询到可预览的数据'
                                if(taskIds.length) $('.btn_check').click()
                            })


                        }
                    })
                    const btn_check = createEl('div', {
                        className: 'btn_check',
                        style: btn_style,
                        innerText: '核对状态',
                        onclick: function() {

                            let resCount = 0
                            const resultWrap = $('.resultWrap')
                            resultWrap.innerHTML = '核对中 0%...'

                            const projectIds = Object.values(_ds.projectIds)
                            Promise.all(projectIds.map(id => {
                                return fetch("https://data-encoder.ruqimobility.com/annotation/workflow/queryDatasetByGroup", {
                                    "headers": {
                                        "authorization": 'Bearer ' + JSON.parse(localStorage.getItem('usePermissionStore-prod')).token,
                                        "content-type": "application/json",
                                        "sec-fetch-dest": "empty",
                                        "sec-fetch-mode": "cors",
                                        "sec-fetch-site": "same-origin"
                                    },
                                    "body": `{\"projectId\":${id},\"batchId\":null,\"assignType\":\"ALL\"}`,
                                    "method": "POST",
                                    "mode": "cors",
                                    "credentials": "include"
                                }).then(res => res.json()).then(res => {
                                    resultWrap.innerHTML = `核对中 ${parseInt((++resCount/projectIds.length)*100)}%...`
                                    return Promise.resolve(res)
                                })
                            })).then(res => {
                                _ds.allProjectData = res.map(item => item.data)
                                verifyActivity(_ds.allProjectData)
                            }, (err) => {
                                console.warn(err)
                                $('.resultWrap').innerHTML = '核对失败...'
                            })

                        }
                    })
                    const resultWrap = createEl('div', {
                        className: 'resultWrap',
                        style: {
                            marginTop: '20px',
                            padding: '5px 5px 0 10px',
                            width: '190px',
                            height: '480px',
                            fontSize: '14px',
                            overflow: 'auto',
                            maxHeight: '1000px',
                        },
                    })
                    btnWrap.append(btn_preview, btn_check)
                    menu.append(textarea_input, btnWrap, resultWrap)

                    function verifyActivity(datas) {
                        const taskIds = textarea_input.value.split('\n')
                        resultWrap.innerHTML = ''

                        taskIds.forEach((taskId) => {

                            const dataItem = findDataItem(taskId, datas)
                            const activityMap = {
                                annotate: '标注 👈',
                                audit_1: '审核1 👈',
                                audit_2: '审核2 ⏳',
                                inspect_1: '质检1',
                                accept_1: '验收1',
                                accept_2: '验收2',
                                complete: '验收完成',
                            }
                            const activity = dataItem ? activityMap[dataItem.activityId] : ''

                            const resultItem = createEl('div', {
                                innerText: `${taskId}\t${activity}
                                ${dataItem ? `${dataItem.assigneeId ? `已领取\n${dataItem.assignee}` : '待领取'}
                                  ${dataItem.projectName}
                                ` : ''}\n\n`,
                                taskId: taskId,
                                onmousedown: function(e) {
                                    if(e.which !== 3) return

                                    if(!dataItem) return alert('任务ID无效')

                                    const {datasetId, dataAnnotationRecordId, processInstanceId, projectName } = dataItem
                                    const param = R2({
                                        "projectId": _ds.projectIds[projectName],
                                        "recordId": dataAnnotationRecordId,
                                        "status": "Annotate",
                                        "processInstanceId": processInstanceId,
                                        "taskDefinitionKey": "annotate",
                                        "datasetId": datasetId,
                                        "acceptDatasetEditUserIds": ""
                                    })

                                    if(confirm(`跳转进入该任务(${datasetId})?`)) {
                                        const el = document.createElement('a')
                                        el.href = `https://data-encoder.ruqimobility.com/tool/pc?${param}`
                                        el.target= '_blank'
                                        el.click()
                                    }
                                }
                            })
                            resultWrap.append(resultItem)
                        })
                    }
                    function findDataItem(taskId, datas) {
                        let r = null
                        datas.some((data, idx) => {
                            return data.some(activityItem => {
                                const findRes = activityItem.list.find(item => item.datasetId == taskId)
                                if(!findRes) return
                                findRes.activityId = activityItem.activityId
                                findRes.projectName = Object.keys(_ds.projectIds)[idx]
                                r = findRes
                            })
                        })

                        return r
                    }

                    appear.menu = true
                }

                const annoWrap = $$('.h-full').find(item => item.textContent.startsWith('标注'))?.$('.lane-item-border.overflow-auto')
                if(!appear.annoList && annoWrap) {
                    [...annoWrap.children].filter(item => item.$('.item-status.wait')).forEach(item => {
                        const taskId = /ID: (\d*)/.exec(item.$('.el-badge.item .font-700').textContent)?.[1]
                        const dataList = _ds.data.find(item => item.activityId == "annotate").list
                        const findRes = dataList.find(item => item.datasetId == taskId)
                        item.style.boxShadow = findRes.dataAnnotationRecordId ? 'gray 0px 0px 6px .5px inset' : null

                    })
                    appear.annoList = true
                }

                if(an.matches?.('.flex.w-full.justify-center.items-start') && an.parentElement.matches?.('.el-main .content .h-full')) {
                    an.style.border = '2px solid #eee'
                }
            })
        })
    }, { childList: true, subtree: true})

    document.addEventListener('contextmenu',function(e){
        e.preventDefault();
    })

    document.onmousedown = function(e) {
        if(e.which !== 3) return
        const el = e.target
        let taskId

        if(el.matches?.('span.font-700') && (taskId = /ID: (\d*)/.exec(el.textContent)?.[1])) {
            _ds.data.some(item => {
                const data = item.list.find(item => {
                    if(item.datasetId === Number(taskId)) {
                        return true
                    }
                })

                if(data) {
                    console.log(data)
                    const {datasetId, dataAnnotationRecordId, processInstanceId, } = data
                    const projectName = [...document.querySelector('.el-radio-group').querySelectorAll('.el-radio')].find(item => item.matches('.is-checked'))

                    const param = R2({
                        "projectId": _ds.projectIds[projectName],
                        "recordId": dataAnnotationRecordId,
                        "status": "Annotate",
                        "processInstanceId": processInstanceId,
                        "taskDefinitionKey": "annotate",
                        "datasetId": datasetId,
                        "acceptDatasetEditUserIds": ""
                    })

                    if(confirm(`跳转进入该任务(${datasetId})?`)) {
                        const el = document.createElement('a')
                        el.href = `https://data-encoder.ruqimobility.com/tool/pc?${param}`
                        el.target= '_blank'
                        el.click()
                    }

                    return true
                }

            })
        }
    }
})();

function R2(e) {
    let t = "";
    const n = [];
    return (Object.keys(e).forEach((t => {
        const o = `${t}=${e[t]}`
          , r = encodeURIComponent(o);
        n.push(r)
    })),
    (t = n.join("&")),
    (t = window.btoa(t)),
    t)
}


function hijackXHR(change, send) {
    const realXMLHttpRequest = window.XMLHttpRequest;

    window.XMLHttpRequest = function() {
        const xhr = new realXMLHttpRequest();

        xhr.addEventListener('readystatechange', function () {
            if (xhr.readyState !== 4) return
            change.call(this)
        });

        send && (xhr.send = send.bind(xhr))
        return xhr;
    }
}

function Obs(target, callBack, options = { childList: true, subtree: true, attributes: true, attributeOldValue: true}) {
    if(!target) return console.error('目标不存在')

    const ob = new MutationObserver(callBack);
    ob.observe(target, options);
    return ob
}

function createEl(elName, options) {
    const el = document.createElement(elName)
    for(let opt in options) {
        if(opt !== 'style') {
            el[opt] = options[opt]
        } else {
            let styles = options[opt]
            setStyle(el, styles)
        }
    }
    return el
}


function $(selector) {
    const _this = Element.prototype.isPrototypeOf(this) ? this : document
    const sel = String(selector).trim();

    const id = /^#([^ +>~\[:]*)$/.exec(sel)?.[1]
    return (id && _this === document) ? _this.getElementById(id) : _this.querySelector(sel)
}

function $$(selector) {
    const _this = Element.prototype.isPrototypeOf(this) ? this : document
    return Array.from(_this.querySelectorAll(selector))
}

function setStyle() {
    [[Map, ()=> {
        const styleMap = arguments[0]
        for (const [el, styleObj] of styleMap) {
            !Array.isArray(el) ? setStyleObj(el, styleObj) : el.forEach((el) => setStyleObj(el, styleObj))
        }
    }], [Element, () => {
        const [el, styleObj] = arguments
        setStyleObj(el, styleObj)
    }], [Array, () => {
        const [els, styleObj] = arguments
        els.forEach((el) => setStyleObj(el, styleObj))
    }]].some(([O, fn]) => O.prototype.isPrototypeOf(arguments[0]) ? (fn(), true) : false)

    function setStyleObj(el, styleObj) {
        for (const attr in styleObj) {
            if (el.style[attr] !== undefined) {
                el.style[attr] = styleObj[attr]
            } else {
                //将key转为标准css属性名
                const formatAttr = attr.replace(/[A-Z]/, match => `-${match.toLowerCase()}`)
                console.error(el, `的 ${formatAttr} CSS属性设置失败!`)
            }
        }
    }
}


/*
2025/2/23
- 适配:适配新界面

2025/2/24
- 新增:查询预览包(编写中)

2025/2/25
- 新增:查询预览包(完成)
*/