源码安装
zhangsan / xieying3D

// ==UserScript==
// @name         xieying3D
// @namespace    http://tampermonkey.net/
// @version      2025.3.6.2
// @description  try it
// @author       You
// @match        https://data-encoder.ruqimobility.com/tool/pc?*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ruqimobility.com
// @grant        none
// @license     MIT
// @downloadURL https://update.greasyfork.org/scripts/521830/xieying3D.user.js
// @updateURL https://update.greasyfork.org/scripts/521830/xieying3D.meta.js
// ==/UserScript==
window.$ = Document.prototype.$ = Element.prototype.$ = $;
window.$$ = Document.prototype.$$ = Element.prototype.$$ = $$;

const _window = window
window._ds = {
    logAn: false,
    logKeydown: false,
    isDebug: true,
    taskId: void 0,
    objSum: 0,
    pointSize: 0.17,
    callback: [],
    viewBtnMap: {},
    history_check: null,
    isRecordCheckHistory: localStorage.getItem('history-record') === 'true' ?? false,

}

const _ds = new Proxy(window._ds, {
    get(target, prop) {
        return Reflect.get(target, prop)
    },
    set(target, prop, value) {
        if(prop in trigger) {
            trigger[prop](value)
        } else {
            Reflect.set(target, prop, value)
        }
        return true
    }

})

const trigger = {
    resultDir(newVal) {
        window._ds.resultDir = newVal
    },
    taskId(newVal) {
        window._ds.taskId = newVal

        const logTitle = $('.log-title')
        logTitle.style.cursor = 'pointer'
        logTitle.onclick = function() {
            copyToClipboard(newVal).then(res => showMessage('复制:任务id', {type: 'success'}))
        }
        logTitle.innerHTML = logTitle.textContent + ' '.repeat(2) + newVal
    },
};



hijackXHR(function() {
    const xhr = this;
    let taskId
    if(taskId = new RegExp(`^https://data-encoder.ruqimobility.com/annotation/dataset/info/(\\d*)`).exec(xhr.responseURL)?.[1]) {
        document.title = taskId
        _ds.taskId = taskId


        let i = 0
        let lsKey
        let history = []
        let maxStorageNum = 15
        while(lsKey = localStorage.key(i++)) {
            if(!lsKey) break
            const execRes = /^history-check_(\d*)$/.exec(lsKey)
            if(execRes) {
                try {
                    const lsItem = JSON.parse(localStorage.getItem(lsKey))
                    history.push([execRes[1], lsItem.timestamp])
                } catch(e) {
                    console.error(e)
                }
            } else {
                continue
            }
        }

        if(history.length > maxStorageNum) {
            history.sort((a, b) => b[1]-a[1])
            history.slice(maxStorageNum).forEach(([taskId]) => {
                localStorage.removeItem(`history-check_${taskId}`)
            })
        }
        window._ds.history_check = JSON.parse(localStorage.getItem(`history-check_${taskId}`) ?? JSON.stringify({timestamp: Date.now()}))
    }
});


const realAEL = EventTarget.prototype.addEventListener;
const realREL = EventTarget.prototype.removeEventListener;
const listenerMap = new WeakMap(); // 存储原始监听器和包装后的监听器的映射关系
window.listenerMap = listenerMap

EventTarget.prototype.addEventListener = function(type, listener, options) {
    const wrappedListener = type == 'keydown' ?
          function(e) {
              if(!e.ctrlKey && [81, 87, 69, 65, 83, 68].includes(e.keyCode)) { // Q81 W87 E69 A65 S83 D68
                  if(listener.name !== 'keydownCallback') return
              }

              if(e.keyCode == 32 && listener.name !== 'keydownCallback') return

              if(e.keyCode >= 49 && e.keyCode <= 53) {
                  if(e.currentTarget == document.body && e.target == document.body) {
                      $$('.label-container2 .select-label').find(label => label.textContent === ['小车', 'SUV', '两轮车', '人', '隔离柱'][e.keyCode-49]).click()
                  }
                  return
              }

              listener.apply(this, arguments);
          } :
        function(e) {
            listener.apply(this, arguments);
        }

    if (!listenerMap.has(this)) {
        listenerMap.set(this, new Map());
    }
    listenerMap.get(this).set(listener, wrappedListener);

    realAEL.call(this, type, wrappedListener, options);

};

EventTarget.prototype.removeEventListener = function(type, listener, options) {
    const wrappedListener = listenerMap.get(this)?.get(listener);

    if (wrappedListener) {
        realREL.call(this, type, wrappedListener, options);

        listenerMap.get(this).delete(listener);
    }
};


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

let frameQueue = ['caret-right', 'caret-left']
document.body.addEventListener('keyup', function(e) {
    if([81, 87, 69, 65, 83, 68].includes(e.keyCode)) {
        document.body.dispatchEvent(new MouseEvent('mouseup', {bubbles: true}))
    }
})
document.body.addEventListener('keydown', function keydownCallback(e) {
    const { keyCode } = e
    _ds.logKeydown && console.log(keyCode, e)

    const isInput = ['input', 'textarea'].find(sel => {
        const activeEl = document.activeElement
        if(!activeEl?.matches(sel)) return
        if(sel == 'input' && activeEl.type !== 'text') return false
        return true
    })
    if(isInput) return;

    [

        [
            () => keyCode == 82,
            () => {
                ;['keydown', 'keyup'].forEach((event) => {
                    document.body.dispatchEvent(new KeyboardEvent(event, {
                        code: "KeyC",
                        key: "c",
                        keyCode: 67,
                        altKey: true,
                        bubbles: true
                    }))
                });
            }
        ],
        [
            () => keyCode == 72,
            () => {
                if($('.setting')) {
                    $$('.setting .ant-checkbox-wrapper').find(label => label.textContent.includes('立体框')).click()
                } else {
                    $$('.toolbar-item-label').find(item => item.textContent.includes('显示'))?.click()
                    _ds.isSettingInitial = true

                }
            }
        ],
        [
            () => [81, 87, 69, 65, 83, 68].includes(keyCode),
            () => {
                e.preventDefault();

                const triggerBtn = _ds.viewBtnMap[keyCode];
                if(!triggerBtn) return;

                triggerBtn.dispatchEvent(new MouseEvent('mousedown', {bubbles: true}))
                if(e.altKey) document.body.dispatchEvent(new MouseEvent('mouseup', {bubbles: true}))

            }
        ],
        [
            () => keyCode == 89,
            () => {
                setTimeout(() => $$('.item-warp-li').find(item => item.children[0].matches('.item.active'))?.click())
            }
        ],
        [
            () => [9, 32].includes(keyCode), // 【Tab】
            (e) => {
                e.preventDefault()

                if($('.main-view').style.display !== 'none') {
                    $(`span[aria-label="${keyCode == 9 ? 'caret-left' : 'caret-right'}"]`).parentElement.click()
                } else {
                    $$('.page-turn-btn button').find(btn => btn.textContent == ` ${keyCode == 9 ? '上一页' : '下一页'} `).click()
                }
            }
        ],
        [
            () => keyCode == 192,
            () => {
                // showMessage('反转:Tap切帧方向')
                // frameQueue.reverse()
                const btns = $$('label').filter(item => ['单帧视图', '多帧视图'].some(text => item.textContent == text))
                btns.find(btn => !btn.className.includes('checked'))?.click()
            }
        ],
    ].forEach((item) => { item[0]() && item[1](e) })
})

const viewScheme = {
    keyMap: [81, 87, 69, 65, 83, 68], //Q81 W87 E69 A65 S83 D68
    '俯视图': [0,6,1,9,3,10],
    '侧视图': [7,4,2,3,5,6],
    '后视图': [11,4,8,9,5,10]
}

const appear = {
    imgView: false,
    maxView: false,
}
let roundCount = 1
Obs(document.body, mrs => {
    mrs.forEach(mr => {
        // console.log(mr)
        [...mr.addedNodes].some(an => {
            _ds.logAn && console.log(roundCount, an);

            if(['#text', "SPAN"].includes(an.nodeName)) return
            if(an.matches?.('.main-view-label .item')) return

            ;[
                [
                    () => !appear.endOfLoad && (appear.endOfLoad = !!$$('.ant-modal').find(item => item.textContent.includes('开始作业计时'))),
                    () => {
                        let timer = null
                        Obs($('.timeline-scale'), mrs => {
                            console.log('mrs===', mrs)
                            timer && clearTimeout(timer)
                            timer = setTimeout(() => {
                                $('button[title="重置"]').click()
                                clearTimeout(timer)
                                timer = null
                            }, 800)
                        }, {childList: true, subtree: true})
                    }
                ],
                [
                    () => _ds.isSettingInitial && $('.setting'),
                    () => {
                        $$('.toolbar-item-label').find(item => item.textContent.includes('显示')).click()
                        $$('.setting .ant-checkbox-wrapper').find(label => label.textContent.includes('立体框')).click()
                        _ds.isSettingInitial = false
                    }
                ],
                [
                    () => !appear.attrSection && (appear.attrSection = $('.main-class-edit .item-relevancy+.el-collapse-item')),
                    () => {
                        postionTip()

                        Obs(appear.attrSection, mrs => {
                            // console.log(mrs)
                            postionTip()
                        }, {childList: true, attributes: true, attributeFilter: ['style']})

                        Obs($('.main-class-edit'), mrs => {
                            // console.log(mrs)
                            const itemWrap = $$('.item-warp-li').find(item => item.children[0].matches('.active'))
                            if(itemWrap && itemWrap.textContent.includes('投影')) {
                                const find3D = select3D(itemWrap.parentElement.parentElement.parentElement)
                                find3D && find3D.click()
                            }
                        }, {childList: true, attributes: true, attributeFilter: ['style']})

                        function postionTip() {
                            const postion = $$('.main-view-info>:nth-child(2) .item').find(item => item.textContent.startsWith('位置')).childNodes[1].textContent
                            $('.main-class-edit').style.background = (!appear.attrSection.style.display && outOfBoundsJudgment(postion)) ? 'red' : null
                        }
                    }
                ],
                [
                    () => !appear.positionText && (appear.positionText = $$('.main-view-info>:nth-child(2) .item').find(item => item.textContent.startsWith('位置'))?.childNodes[1]),
                    ()=> {
                        Object.defineProperty(appear.positionText, 'nodeValue', {
                            get: function() {
                                return this.textContent;
                            },
                            set: function(newValue) {
                                const attrPanel = $('.main-class-edit .item-relevancy+.el-collapse-item')
                                const postion = newValue

                                $('.main-class-edit').style.background = !attrPanel.style.display && outOfBoundsJudgment(postion) ? 'red' : null

                                this.textContent = newValue;
                            },
                            enumerable: true,
                            configurable: true
                        })
                    }
                ],
                [
                    () => !appear.labelFilter && (appear.labelFilter = $$('.el-popper').find(item => item.textContent.startsWith(' 全选'))),
                    () => {
                        const checkboxs = appear.labelFilter.getElementsByClassName('el-checkbox')
                        Obs(appear.labelFilter, mrs => {
                            mrs.some(mr => {
                                for(const an of [...mr.addedNodes]) {
                                    // console.log(an)
                                    if(an.nodeName !== '#text') {
                                        return
                                    } else {
                                        [...checkboxs].forEach(checkbox => {
                                            if(!checkbox.matches('.is-checked')) setTimeout(() => checkbox.click())
                                        })
                                        return true
                                    }
                                }
                                // if(mr.attributeName == "class" && !appear.labelFilter.$$('.el-checkbox').every(checkbox => checkbox.matches('.el-checkbox'))) lockChecked(mr.target)
                            })
                        }, {childList: true, subtree: true})

                        function lockChecked(checkbox) {
                            if(!appear.labelFilter.$$('.el-checkbox').every(checkbox => checkbox.matches('.el-checkbox'))) {
                                checkbox.click()
                                setTimeout(() => lockChecked(checkbox))
                            } else {
                                return
                            }
                        }
                    }
                ],
                [
                    () => ((appear.messageBoxWrap = $('.el-overlay.is-message-box')) && appear.messageBoxWrap?.textContent.startsWith('提示您已长时间未保存数据')),
                    () => {
                        const messageBoxWrap = appear.messageBoxWrap
                        const messageBox = messageBoxWrap.$('.el-message-box')
                        const close = createEl('div', {
                            innerText: '×',
                            style: {
                                position: 'absolute',
                                top: 0,
                                right: 0,
                                width: '50px',
                                height: '50px',
                                fontSize: '30px',
                                textAlign: 'center',
                                zIndex: '99999',
                                cursor: 'pointer',
                            },
                            onclick: function() {
                                messageBoxWrap.remove()
                            }
                        })
                        messageBox.append(close)
                    }
                ],
                [
                    () => {
                        return (an.matches?.('.el-dialog') && ['全局批注', '物体批注'].some(title => an?.textContent.startsWith(title))) ||
                            (an.matches?.('.comment-modal') && ['全局批注', '物体批注'].some(title => an?.parentElement?.previousElementSibling.textContent.startsWith(title)))
                    },
                    () => {
                        const checkboxWrap = an.$('.ant-form-horizontal .ant-row')
                        const checkboxs = an.$$('input[type="checkbox"]')
                        const textarea = an.$('#description')
                        const footer = an.$('form+div')
                        const inputWraps = an.$$('.ant-checkbox-group .ant-col')


                        const title = getCommentType(an)
                        function getCommentType(el) {
                            if(el.matches('.el-overlay-dialog')) return el.getAttribute('aria-label')
                            return getCommentType(el.parentElement)
                        }
                        $(`.el-overlay-dialog[aria-label="${title}"]`).style.opacity = '.95'

                        ;['批注原因', '批注描述'].forEach(tit => (an.$(`label[title="${tit}"]`).parentElement.style.display = 'none'))
                        an.$$('.ant-row').find(item => item.textContent.startsWith('涉及帧数')).style.display = 'none'
                        setStyle(footer, {
                            marginTop: 0,
                            marginBottom: '10px',
                            textAlign: 'center',
                        })
                        inputWraps.forEach(item => (item.style.marginBottom = 0))

                        const quickPhraseWrap = createEl('div', {
                            className: 'quickPhrase-wrap',
                            style: {
                                marginTop: '15px'
                            }
                        })

                        const scheme_global = {
                            '类型': {
                                '小车': '小车',
                                'SUV': 'SUV',
                                '大车': '大车',
                                '两轮车': '两轮车',
                                '三轮车': '三轮车',
                                '人': '人',
                                'BUS': 'BUS',
                                '隔离柱': '隔离柱',
                                '锥桶': '锥桶',
                                '防撞桶': '防撞桶',
                                '防撞球': '防撞球',
                                '一般': '一般障碍物',
                            }
                        }
                        const scheme_obj = {
                            '方位': {
                                '顶视': '顶视图',
                                '侧视': '侧视图',
                                '后视': '后视图',
                                '角度': '角度',
                                '←': '左边框',
                                '↑': '上边框',
                                '→': '右边框',
                                '↓': '下边框',
                                '车头': '车头贴合',
                                '车尾': '车尾贴合',
                                '车顶': '车顶贴合',
                            },
                            '贴合': {
                                '收': '往里收',
                                '扩': '往外扩',
                                '上移': '整体上移',
                                '下移': '整体下移',
                                '左移': '整体左移',
                                '右移': '整体右移',
                                '↶': '逆时针旋转',
                                '↷': '顺时针旋转',
                                '飘空': '飘空',
                                '下陷': '下陷',
                                '稳定边': '贴合稳定边',
                                '地线': '检查地线',
                            },
                            '尺寸': {
                                '长': '长度',
                                '宽': '宽度',
                                '高': '高度',
                                '脑补': '脑补',
                                '统一': '统一尺寸',
                            },
                            '类型': {
                                '小车': '小车',
                                'SUV': 'SUV',
                                '大车': '大车',
                                '两轮车': '两轮车',
                                '三轮车': '三轮车',
                                '人': '人',
                                'BUS': 'BUS',
                                '隔离柱': '隔离柱',
                                '锥桶': '锥桶',
                                '防撞桶': '防撞桶',
                                '防撞球': '防撞球',
                                '一般': '一般障碍物',
                            },
                            '转弯维度': {
                                '未知': '转弯属性:未知',
                                '不转': '转弯属性:不转',
                                '左': '转弯属性:左转',
                                '右': '转弯属性:右转',
                                '双闪': '双闪',
                            },
                            '刹车维度': {
                                '未知': '刹车属性:未知',
                                '未刹车': '刹车属性:未刹车',
                                '刹车': '刹车属性:刹车',
                            },
                            '其他': {
                                '漏标': '漏标',
                                '前漏': '前续帧漏显示',
                                '后漏': '后续帧漏显示',
                                '漏点': '漏点',
                                '没框全': '没框全',
                                '舍弃点云': '适当舍弃点云',
                            },
                            '补充': {
                                '前后帧检查': '前后帧检查',
                                '伪3D': '检查伪3D',
                            }
                        }
                        const scheme = title == '全局批注(物体)' ? scheme_global : scheme_obj

                        const checkboxMap = title !== '全局批注(物体)' ? {
                            '不贴合': ['贴合', '方位'],
                            '标签错误': ['类型'],
                            '方向错误': [],
                            '属性错误': ['转弯维度', '刹车维度'],
                            '尺寸不对': ['尺寸'],
                            '方向错误': [],
                            '多标': [],
                            '其他': ['其他'],
                        } : {
                            '漏标': ['类型']
                        }

                        for(let k1 in scheme) {
                            const btnWrap = createEl('div', {
                                className: 'btn-wrap',
                                style: {
                                    display: 'flex',
                                    marginBottom: '12px'
                                }
                            })
                            const title = createEl('div', {
                                className: 'btn-title',
                                innerText: `${k1}:`,
                                style: {
                                    fontSize: '13px'
                                }
                            })
                            btnWrap.append(title)

                            for(let k2 in scheme[k1]) {
                                const phrase = scheme[k1][k2]
                                const btn_style = {
                                    padding: '0px 5px',
                                    height: '20px',
                                    lineHeight: '20px',
                                    margin: '0 5px 0 0',
                                    backgroundColor: 'rgb(136, 136, 136)',
                                    color: 'rgb(255, 255, 255)',
                                    fontSize: '12px',
                                    cursor: 'pointer',
                                    userSelect: 'none',
                                }
                                const btn = createEl('div', {
                                    className: 'phrase-btn',
                                    innerText: k2,
                                    style: btn_style,
                                    onmousedown: function (e) {
                                        const findIdx = Object.values(checkboxMap).findIndex(arr => arr.includes(k1))
                                        if(findIdx !== -1) {
                                            checkboxs.forEach(checkbox => {
                                                if(checkbox.value === Object.keys(checkboxMap)[findIdx] && !checkbox.checked) checkbox.click()
                                            })
                                            if(e.button === 2) {
                                                setTimeout(() => {
                                                    checkboxs.forEach((checkbox, idx) => {
                                                        if(checkbox.value !== Object.keys(checkboxMap)[findIdx] && checkbox.checked) setTimeout(() => checkbox.click(), idx*100)
                                                    })
                                                })
                                            }

                                        }

                                        const value = textarea.value
                                        if(e.button === 0) {
                                            setTextAreaValue(textarea, `${value}${value ? ';' : ''}${phrase}`)
                                        } else if(e.button === 2) {
                                            setTextAreaValue(textarea, `${phrase}`)
                                        }
                                    },
                                })
                                btnWrap.append(btn)
                            }
                            quickPhraseWrap.append(btnWrap)
                        }
                        checkboxWrap.insertAdjacentElement('afterend', quickPhraseWrap)
                        quickPhraseWrap.insertAdjacentElement('afterend', footer)

                    }
                ],
                [
                    () => {
                        return (an.matches?.('.el-dialog') && an?.textContent.startsWith('全局批注')) ||
                            (an.matches?.('.comment-modal') && an?.parentElement?.previousElementSibling.textContent.startsWith('全局批注'))
                    },
                    () => {
                        const checkboxs = an.$$('#reason input[type="checkbox"]')
                        if(checkboxs.every(item => !item.checked)) checkboxs.find(item => item.value == '漏标').click()
                    }
                ],
                [
                    () => !appear.annoType && $('.key-frame-annotate-type .ant-dropdown-trigger'),
                    () => {
                        $('.key-frame-annotate-type .ant-dropdown-trigger').click()
                        appear.annoType = true
                    }
                ],
                [
                    () => !appear.annoTypeDropdown && $('.ant-dropdown')?.textContent.startsWith('关闭关键帧继承'),
                    () => {
                        $('.ant-dropdown').style.display = 'none'

                        $('.key-frame-annotate-type .ant-dropdown-trigger').click()
                        setTimeout(() => ($('.ant-dropdown').style.display = null))
                        appear.annoTypeDropdown = true
                    }
                ],
                [
                    () => !appear.close && $('.ant-dropdown li[data-menu-id="close"]:not(.ant-dropdown-menu-item-disabled)'),
                    () => {
                        $('.ant-dropdown li[data-menu-id="close"]').click()
                        appear.close = true
                    }
                ],
                [
                    () => !appear.mulView && $('.main-view.mul-view'),
                    () => {
                        const mulView = $('.main-view.mul-view')
                        mulView.$$('.el-row.mul-row').forEach(treeView => {
                            bindView(treeView)
                        })

                        $$('.mul-title').forEach(el => {
                            el.onclick = function() {
                                // setInputValue($('.pageIndex input'), )
                                $('.timeline-scale').children[Number(el.textContent) - 1].click()
                            }
                        })

                        appear.mulView = true
                    }
                ],
                [
                    () => !appear.sideviewWrap && $('.sideview-container'),
                    () => {
                        const sideviewWrap = $('.sideview-container')
                        bindView(sideviewWrap)
                        appear.sideviewWrap = true
                    }
                ],
                [
                    () => !appear.sideviewWrap && $('.sideview-container'),
                    () => {

                        appear.sideviewWrap = true
                    }
                ],
                [
                    () => !appear.commentPanel && $('.comment-container'),
                    () => {
                        const commentPanel = $('.comment-container');

                        commentPanel.style.opacity = 0.9

                        let isMoving = false;
                        let top = -15;
                        let right = -225

                        const obsTarget = commentPanel.$('.comment-panel').children[0]
                        Obs(obsTarget, (mrs)=> {
                            if(obsTarget.style.display == '') {
                                commentPanel.style.top = top + 'px'
                                commentPanel.style.right = right + 'px'
                            } else {
                                commentPanel.style.top = '0px'
                                commentPanel.style.right = '3px'
                            }
                        }, {childList: true, attributes: true, attributeOldValue: true})

                        commentPanel.addEventListener('mousedown', (e) => {
                            if(e.button == 1) {
                                isMoving = true
                                e.preventDefault()
                            }
                        })
                        document.body.addEventListener('mousemove', (e) => {
                            if(!isMoving) return
                            commentPanel.style.top = ((top += e.movementY) + 'px')
                            commentPanel.style.right = ((right -= e.movementX) + 'px')
                        })
                        commentPanel.addEventListener('mouseup', (e) => {
                            if(e.button == 1) isMoving = false
                        })
                        appear.commentPanel = true
                    }
                ],
                [
                    () => an.matches?.('.ant-btn.select-label'),
                    () => (_ds.isDrawing = true)
                ],
                [
                    () => !appear.waterMask && $('#waterMask'),
                    () => {
                        $('#waterMask').remove()
                        appear.waterMask = true
                    }
                ],
                [
                    () => !appear.handleLineSide && $('.sideview-container .handle-line-side'),
                    () => {
                        const el = $('.sideview-container .handle-line-side')
                        const obs = Obs(el, mrs => {
                            mrs.forEach(mr => {
                                if(mr.attributeName === 'style' && el.style.diplay !== 'none') {
                                    el.dispatchEvent(new MouseEvent('mousedown', {
                                        screenY: 800
                                    }))
                                    document.body.dispatchEvent(new MouseEvent('mousemove', {
                                        screenY: 620,
                                        bubbles: true
                                    }))
                                    document.body.dispatchEvent(new MouseEvent('mouseup', {
                                        bubbles: true
                                    }))
                                }
                            })
                        }, {childList: true, attributes: true})

                        appear.handleLineSide = true
                    }
                ],
                [
                    () => !appear.objListTitle && $('#pane-objectLabel .header-title'),
                    () => {
                        const objListTitle = $('#pane-objectLabel .header-title')
                        Obs(objListTitle, mrs => {
                            mrs.forEach(mr => {
                                [...mr.addedNodes].forEach(an => {
                                    const curSum = /物体标签\((\d*)\)/.exec(an.textContent)[1]
                                    if(_ds.isDrawing && +curSum === +_ds.objSum+1 ) {
                                        _ds.isDrawing = false
                                        _ds.isFocusObj = true
                                    }
                                    _ds.objSum = curSum

                                })
                            })
                        })

                        const checkbox = createEl('input', {
                            type: 'checkbox',
                            checked: _ds.isRecordCheckHistory,
                            title: '是否记录并显示查看历史?',
                            onclick: function() {
                                _ds.isRecordCheckHistory = checkbox.checked
                                localStorage.setItem('history-record', checkbox.checked)

                                $$('.instance-track-item').forEach(item => {
                                    if(checkbox.checked) {
                                        const [text, type, sn] = /(.*)-(\d*)/.exec(item.textContent)
                                        if(_ds.history_check[type]?.includes(sn)) {
                                            item.style.boxShadow = 'gray 0 0 10px 5px inset'
                                        } else if(item.parentElement.parentElement.nextElementSibling?.$('.item.active')) {
                                            updateHistory(item, 'add')
                                        }

                                        item.onmousedown = function(e) {
                                            if(e.button !== 1) return
                                            e.preventDefault()

                                            if(_ds.isRecordCheckHistory) updateHistory(item)
                                        }
                                    } else {
                                        item.style.boxShadow = null
                                    }
                                })
                            },
                            style: {
                                marginRight: '10px',
                                cursor: 'pointer',
                            }
                        })
                        $('.header-control .operate-icon-btn').insertAdjacentElement('afterbegin', checkbox)

                        appear.objListTitle = true
                    }
                ],
                [
                    () => _ds.isRecordCheckHistory && an.matches?.('.el-tree-node.is-focusable'),
                    () => {
                        const objItem = an.$('.item-warp-li')
                        const objItemParentTreeNode = an.$('.instance-track-item')

                        if(objItem && !objItem.textContent.includes('投影')) {
                            const target = objItem.children[0]
                            if(!target.matches('.item.active')) {
                                Obs(target, mrs => {
                                    mrs.forEach(mr => {
                                        if(!target.matches('.item.active')) return

                                        updateHistory(an.previousElementSibling.$('.instance-track-item'), 'add')
                                    })
                                }, {childList: true, attributes: true, attributeOldValue: true, attributeFilter: ['class']})
                                return
                            }
                            updateHistory(an.previousElementSibling.$('.instance-track-item'), 'add')

                        } else if(objItemParentTreeNode) {
                            const [text, type, sn] = /(.*)-(\d*)/.exec(objItemParentTreeNode.textContent)
                            if(_ds.history_check[type]?.includes(sn)) objItemParentTreeNode.style.boxShadow = 'gray 0 0 10px 5px inset'

                            objItemParentTreeNode.onmousedown = function(e) {
                                if(e.button !== 1) return
                                e.preventDefault()

                                if(_ds.isRecordCheckHistory) updateHistory(objItemParentTreeNode)
                            }
                        }
                    }
                ],
                [
                    () => an.matches?.('.el-tree-node.is-expanded.is-focusable') && an.$('.item-warp-li'),
                    () => {
                        const objItem = an.$('.item-warp-li')
                        if(_ds.isFocusObj && objItem.children[0].matches('.item.active')) {
                            ['keydown', 'keyup'].forEach((event) => {
                                document.body.dispatchEvent(new KeyboardEvent(event, {
                                    code: "Escape",
                                    key: "Escape",
                                    keyCode: 27,
                                    bubbles: true
                                }))
                            });

                            objItem.click()

                            _ds.isFocusObj = false
                        }

                        if(objItem.textContent.includes('投影')) {
                            const find3D = select3D(an)
                            find3D && find3D.click()
                        }

                    }
                ],
                [
                    () => an.matches?.('.pc-editor'),
                    () => {
                        setStyle($('.annotate-mode'), {
                            fontSize: '20px',
                            fontWeight: '800',
                            background: 'black',
                        })


                    }
                ],
                [
                    () => !appear.imgView && $('.img-view'),
                    () => {
                        const views = $$('.img-view')
                        views.forEach(view => {
                            let isScroll = false

                            set2DImgPositionInfo(view.$('.camera-number'))

                            clickTrigger(view, (e) => {
                                view.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }));
                            }, 3, 2)

                            view.addEventListener('mouseenter', (e)=> {
                                if(e.clientX < 20) return
                                view.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
                                isScroll = false
                            })
                            view.addEventListener('mousemove', (e)=> {
                                // console.log(e)
                                if(!isScroll && e.clientX < 20) {
                                    view.dispatchEvent(new MouseEvent('mouseleave'))
                                    isScroll = true
                                } else if(isScroll && e.clientX >= 20) {
                                    view.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
                                    isScroll = false
                                }
                            })
                        })
                        appear.imgView = true
                    },
                ],
                [
                    () => !appear.maxView && (appear.maxView = $('.img-view-max')),
                    () => {
                        const positionInfoEl = [...appear.maxView.$('.info').children].at(-1)
                        set2DImgPositionInfo(positionInfoEl)

                        Obs(positionInfoEl, mrs => {
                            mrs.forEach(mr => {
                                [...mr.addedNodes].forEach(an => {
                                    if(an.nodeName == '#text') set2DImgPositionInfo(an)
                                })
                            })
                        }, {childList: true})

                        clickTrigger(appear.maxView, (e) => {
                            appear.maxView.$('span[title="关闭"]').click()
                        }, 3, 2)
                    }
                ],
                [
                    () => an.$?.('.rect-ground-line line'),
                    () => an.$$('.rect-ground-line line').forEach(line => (line.style.stroke = 'rgba(255, 0, 0, .2'))
                ],
                [
                    () => !appear.attrPanel && $('.main-class-edit'),
                    () => {
                        $('.main-class-edit .view-class-wrap').style.minHeight = 'auto' //高度自适应

                        setStyle($('.main-class-edit'), {
                            bottom: 'auto',
                            top: '260px',
                            left: '1350px',
                            opacity: '0.9',
                        })

                        $('.main-class-edit').addEventListener('mousedown', (e) => {
                            if(e.which == 3) $('.main-class-edit i.el-icon.close').click()
                        })

                        appear.attrPanel = true
                    }
                ],
                [
                    () => !appear.taskWrap && $('.item-wrap.title-task'),
                    () => {
                        const taskWrap = $('.item-wrap.title-task')
                        setStyle(taskWrap, {
                            position: 'relative',
                        })
                        taskWrap.$$('.title-text').forEach(item => {
                            item.style.cursor = 'pointer'

                            item.addEventListener('click', (e) => {
                                let cookieVal
                                document.cookie.split('; ').some(kv => {
                                    const [k, v] = kv.split('=')
                                    if(k === 'ruqimobility.com-prod token') {
                                        cookieVal = v
                                        return true
                                    }
                                })
                                let URLParam = /https:\/\/data-encoder\.ruqimobility\.com\/tool\/pc\?(.*)/.exec(location.href)?.[1]
                                if(!cookieVal || !URLParam) return showMessage('复制失败', {type: 'error'})

                                const textToCopy = cookieVal + '   ' + URLParam
                                copyToClipboard(textToCopy).then(res => showMessage('已复制到剪切板'))
                            })
                        })

                        const btn_change = createEl('div', {
                            title: '切换跳转',
                            style: {
                                position: 'absolute',
                                right: '0px',
                                bottom: '0px',
                                width: '15px',
                                height: '15px',
                                background: 'gray',
                                cursor: 'pointer'
                            },
                            onclick: function() {
                                navigator.clipboard.readText().then((clipText) => {
                                    const [cookieVal, URLParam] = clipText.split('   ')
                                    if(cookieVal && URLParam) {
                                        document.cookie = `ruqimobility.com-prod token=${cookieVal};domain=.ruqimobility.com;path=/`
                                        location.href = `https://data-encoder.ruqimobility.com/tool/pc?${URLParam}`
                                    } else {
                                        showMessage('凭证不合法', {type: 'error'})
                                    }
                                });
                            }
                        })
                        taskWrap.append(btn_change)
                        appear.taskWrap = true
                    },
                ],
                [
                    () => {},
                    () => {},
                ]
            ].forEach((item) => { item[0]() && item[1]() })

            if(an.matches?.('.object-item') && an.textContent?.startsWith('点云对象')) { //切帧
                const btnStyle = {
                    position: 'absolute',
                    left: '10px',
                    borderRadius: '5px',
                    transform: 'scale(.9)',
                    padding: '0px 5px',
                    height: '20px',
                    lineHeight: '20px',
                    margin: '0 5px 0 0',
                    backgroundColor: 'rgb(136, 136, 136, .5)',
                    color: 'rgb(255, 255, 255)',
                    fontSize: '12px',
                    cursor: 'pointer',
                    userSelect: 'none',
                }

                const btn_display = createEl('div', {
                    className: 'displayAttr',
                    innerText: '显隐',
                    style: btnStyle,
                    onclick: display.bind(this, void 0)
                })
                const attrPanel = matchesWrapper(an, '.main-class-edit');
                !$('.displayAttr') && attrPanel.$('.edit-class-common').insertAdjacentElement('afterbegin', btn_display)

                display(false)

                return true

                function display(isShow) {
                    const items = [...attrPanel.$('.el-collapse').children]
                    const display_items_0 = items[0].style.display
                    items.forEach(el => {
                        if(el.textContent.startsWith('Attributes')) return
                        if(isShow !== void 0) {
                            el.style.display = isShow ? null : 'none'
                        } else {
                            el.style.display = display_items_0 === '' ? 'none' : null
                        }
                    })
                }
            }
        })

    })

    function outOfBoundsJudgment(postion) {
        let [text, x, y] = /(.*),(.*),(.*)/.exec(postion)
        x = Number(x)
        y = Number(y)
        return (x > 100 || x < -100) || (y > 50 || y < -50)
    }

    function select3D(el) {
        const targetEl = el.previousElementSibling
        if(!targetEl) return

        const item = targetEl.$('.item-warp-li')
        if(!item) return

        if(!item.textContent.includes('投影')) {
            return item
        } else {
            return select3D(targetEl)
        }
    }

    function bindView(viewWrap) { //键盘调整三视图
        const btnItems = viewWrap.$$('.item')

        viewWrap.$$('.side-view').forEach(item => {
            item.addEventListener('mouseenter', (e) => {
                const angle_view = item.$('.title1').textContent
                const scheme = viewScheme[angle_view]

                const viewBtnMap = {}
                scheme.forEach((btnIdx, idx) => {
                    viewBtnMap[viewScheme.keyMap[idx]] = btnItems[btnIdx]
                })
                _ds.viewBtnMap = viewBtnMap
            })

            item.addEventListener('mouseleave', (e) => {
                _ds.viewBtnMap = {}
            })
        })
    }

    function set2DImgPositionInfo(cameraNumEl) {
        const imgPosition = ['后', '前远', '前广', '左后', '左前', '右后', '右前']
        cameraNumEl.textContent = imgPosition[ /camera-(\d)/.exec(cameraNumEl.textContent)[1]-1 ]
    }

    function updateHistory(target, mode) { // mode = add | del
        const [text, type, sn] = /(.*)-(\d*)/.exec(target.textContent)
        let history = _ds.history_check[type] ?? []
        let isUpdateView = false

        if(history.includes(sn)) {
            if(mode == 'add') return
            history.splice(history.findIndex(item => item == sn), 1)
            setTimeout(() => {
                if(!isUpdateView) return
                target.style.boxShadow = null

                if(!target.parentElement.parentElement?.nextElementSibling.$('.item.active')) return
                ['keydown', 'keyup'].forEach((event) => {
                    document.body.dispatchEvent(new KeyboardEvent(event, {
                        code: "Escape",
                        key: "Escape",
                        keyCode: 27,
                        bubbles: true
                    }))
                });
            })
        } else {
            if(mode == 'del') return
            history.push(sn)
            setTimeout(() => isUpdateView && (target.style.boxShadow = 'gray 0 0 10px 5px inset'))
        }

        _ds.history_check[type] = history
        localStorage.setItem(`history-check_${_ds.taskId}`, JSON.stringify(_ds.history_check))
        isUpdateView = true

    }

    ++roundCount
}, { childList: true, subtree: true })


function setInputValue(input, value) {
    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
    nativeInputValueSetter.call(input, value);
    ['input', 'change'].forEach((event) => input.dispatchEvent(new Event(event, { bubbles: true })))
};

function setTextAreaValue(textarea, value) {
    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set
    nativeInputValueSetter.call(textarea, value);
    ['input', 'change'].forEach((event) => textarea.dispatchEvent(new Event(event, { bubbles: true })))
};

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;
    }

    const realOpen = realXMLHttpRequest.prototype.open;
    realXMLHttpRequest.prototype.open = function () {
        const xhr = this
        let curURL = arguments[1]
        const getter = Object.getOwnPropertyDescriptor(realXMLHttpRequest.prototype, "responseText").get
            Object.defineProperty(xhr, "responseText", {
                get() {
                    let result = getter.call(xhr);
                    if(new RegExp("/annotation/dataset/info/\\d+").test(curURL)) {
                        let result_parse = JSON.parse(getter.call(xhr));
                        const classAttributes = result_parse.data.classAttributes
                        if(classAttributes) {
                            classAttributes.pointSize = _ds.pointSize
                            classAttributes.pointColorMode.pointColors = ["rgb(255, 255, 255)", "rgb(255, 132, 0)"];
                        } else {
                            result_parse.data.classAttributes = {
                                "contrast": 100,
                                "saturate": 100,
                                "lightness": 100,
                                "pointSize": 0.17,
                                "measureOpen": true,
                                "measureConfig": [
                                    {
                                        "id": 0,
                                        "type": "circle",
                                        "radius": 50
                                    },
                                    {
                                        "id": 1,
                                        "type": "rect",
                                        "xPlus": 220,
                                        "yPlus": 100,
                                        "rotate": 0,
                                        "xMinus": 220,
                                        "yMinus": 100
                                    },
                                    {
                                        "id": 2,
                                        "type": "rect",
                                        "xPlus": 100,
                                        "yPlus": 50,
                                        "rotate": 0,
                                        "xMinus": 100,
                                        "yMinus": 50
                                    }
                                ],
                                "pointColorMode": {
                                    "mode": "height",
                                    "pointHSL": "hsl(220, 100%, 50%)",
                                    "pointColors": ["rgb(255, 255, 255)", "rgb(255, 132, 0)"],
                                    "pointHeight": [
                                        -0.2,
                                        3.5
                                    ],
                                    "pointIntensity": [
                                        0,
                                        255
                                    ]
                                },
                                "backgroundColor": "#000",
                                "in2DImageRender": {
                                    "renderBox": true,
                                    "renderRect": false,
                                    "renderImgPolygon": true,
                                    "renderProjectBox": true,
                                    "renderImgKeyPoint": true,
                                    "renderImgPolyLine": true
                                }
                            }
                        }
                        // console.log(result)
                        console.log(result_parse)
                        result = JSON.stringify(result_parse)
                    }
                    if(new RegExp("/annotation/projectLabelMatter/findAll/\\d+").test(curURL)) {
                        let result_parse = JSON.parse(getter.call(xhr));
                        const data = result_parse.data
                        const scheme = {
                            '小车': {
                                length: 4.6,
                                height: 1.45,
                            },
                            'SUV': {
                                length: 4.6,
                                width: 2.1,
                                height: 1.65,
                            },
                            'BUS': {
                                length: 10,
                            },
                            '两轮车': {
                                length: 1.6,
                                width: 0.7,
                                height: 1.5,
                            },
                            '三轮车': {
                                length: 2.45,
                                width: 1.10,
                                height: 1.55,
                            },
                            '人': {
                                height: 1.65,
                            },
                            '隔离柱': {
                                length: [0.1, 1, 0.12],
                                width: [0.1, 1, 0.12],
                                height: [0.1, 1, 0.5],
                            },
                            '防撞桶': {
                                length: [0.1, 999, 0.85],
                                width: [0.1, 999, 0.85],
                                height: [0.1, 999, 0.9],
                            },
                            '水马': {
                                length: 1.55,
                                width: 0.55,
                                height: 0.75,
                            },
                            '防撞球': {
                                length: [0.1, 999, 0.4],
                                width: [0.1, 999, 0.4],
                                height: [0.1, 999, 0.45],
                            },
                            '地锁开': {
                                length: [0.1, 1, 0.45],
                                width: [0.1, 1, 0.2],
                                height: [0.1, 1, 0.25],
                            },
                            '隔离栏': {
                                length: 1.65,
                                width: 0.45,
                                height: 1.15,
                            },
                        }
                        if(Array.isArray(data)) {
                            for(let type in scheme) {
                                const target = data.find(item => item.name == type)
                                if(!target) continue

                                for(const whd in scheme[type]) { //长宽高
                                    const size = scheme[type][whd]
                                    if(!Array.isArray(size)) {
                                        target.toolTypeOptions[whd][2] = size
                                    } else {
                                        target.toolTypeOptions[whd] = size
                                        target.toolTypeOptions.isConstraints = true
                                        target.toolTypeOptions.isStandard = true
                                    }
                                }
                            }
                        }
                        console.log(result_parse)
                        result = JSON.stringify(result_parse)
                    }
                    return result
                },
                configurable: true,
            });
        return realOpen.apply(xhr, arguments);
    };
}


function copyToClipboard(textToCopy) {
    // navigator clipboard 需要https等安全上下文
    if (navigator.clipboard && window.isSecureContext) {
        return navigator.clipboard.writeText(textToCopy);
    } else {
        let textArea = document.createElement("textarea");
        textArea.value = textToCopy;
        textArea.style.position = "absolute";
        textArea.style.opacity = 0;
        textArea.style.left = "-999999px";
        textArea.style.top = "-999999px";
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        return new Promise((res, rej) => {
            document.execCommand('copy') ? res() : rej();
            textArea.remove();
        });
    }
}

function matchesWrapper(el, sel) {
    const parentEl = el.parentElement
    if(!parentEl) return null

    if(parentEl.matches(sel)) {
        return parentEl
    } else {
        return matchesWrapper(parentEl, sel)
    }
}

function clickTrigger(el, fn, button, moveThreshold = 0) {
    let movement = 0
    let allowTrigger = false
    let isRightdown = false

    el.addEventListener('mousedown', (e)=>{
        e.preventDefault()
        if(e.which !== button) return
        movement = 0
        isRightdown = true
        allowTrigger = true
    })
    el.addEventListener('mousemove', (e)=>{
        if(!isRightdown || (isRightdown && (movement+=(Math.sqrt(e.movementX**2 + e.movementY**2))) <= moveThreshold)) return

        allowTrigger && (allowTrigger = false)
    })
    el.addEventListener('mouseup', (e)=>{
        if(e.which !== button) return
        if(allowTrigger) fn(e)
        isRightdown = false
    })
}

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

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


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属性设置失败!`)
            }
        }
    }
}

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 getParamValue(param) {
    let r
    location.href.split('?')[1].split('&').some(item => {
        const param_value = item.split('=')
        if(param_value[0] == param) {
            r = param_value[1]
            return true
        }
    })
    return r
}

function showMessage(message, config) { //type = 'default', showTime = 3000, direction
    let oldMessageWrap = document.querySelector(`.messageWrap-${config?.direction ? config.direction : 'top'}`)

    let MessageWrap
    if(!oldMessageWrap) {
        MessageWrap = document.createElement('div')
        MessageWrap.className = 'messageWrap'
        setStyle(MessageWrap, {
            position: 'absolute',
            zIndex: '9999'
        })
    } else {
        MessageWrap = oldMessageWrap
    }

    let MessageBox = document.createElement('div')
    MessageBox.innerText = message

    let closeBtn = document.createElement('div')
    closeBtn.textContent = '×'
    closeBtn.addEventListener('click', MessageBox.remove.bind(MessageBox)) //关闭消息提示

    setStyle(MessageBox, {
        position: 'relative',
        minWidth: '200px',
        marginTop: '5px',
        padding: '6px 50px',
        lineHeight: '25px',
        backgroundColor: 'pink',
        textAlign: 'center',
        fontSize: '16px',
        borderRadius: '5px',
        transition: 'all 1s'
    })

    setStyle(closeBtn, {
        position: 'absolute',
        top: '-3px',
        right: '3px',
        width: '15px',
        height: '15px',
        zIndex: '999',
        fontWeight: '800',
        fontSize: '15px',
        borderRadius: '5px',
        cursor: 'pointer',
        userSelect: 'none'
    })
    //控制方向
    switch(config?.direction) {
        case 'top': setStyle(MessageWrap, {top: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
        case 'top left': setStyle(MessageWrap, {top: '1%', left: '.5%'}); break;
        case 'left': setStyle(MessageWrap, {top: '50%', left: '1%', transform: 'translateY(-50%)'}); break;
        case 'top right': setStyle(MessageWrap, {top: '1%', right: '.5%', }); break;
        case 'right': setStyle(MessageWrap, {top: '50%', right: '.5%', transform: 'translateY(-50%)'}); break;
        case 'center': setStyle(MessageWrap, {top: '20%', left: '50%', transform: 'translate(-50%, -50%)'}); break;
        case 'bottom': setStyle(MessageWrap, {bottom: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
        case 'bottom8': setStyle(MessageWrap, {bottom: '8%', left: '50%', transform: 'translate(-50%, -50%)'}); break;
        case 'bottom left': setStyle(MessageWrap, {bottom: '1%'}); break;
        case 'bottom right': setStyle(MessageWrap, {bottom: '1%', right: '.5%'}); break;
        default: setStyle(MessageWrap, {top: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
    }

    switch(config?.type) {
        case 'success': setStyle(MessageBox, {border: '1.5px solid rgb(225, 243, 216)', backgroundColor: 'rgb(240, 249, 235)', color: 'rgb(103, 194, 58)'}); break;
        case 'warning': setStyle(MessageBox, {border: '1.5px solid rgb(250, 236, 216)', backgroundColor: 'rgb(253, 246, 236)', color: 'rgb(230, 162, 60)'}); break;
        case 'error': setStyle(MessageBox, {border: '1.5px solid rgb(253, 226, 226)', backgroundColor: 'rgb(254, 240, 240)', color: 'rgb(245, 108, 108)'}); break;
        default: setStyle(MessageBox, {border: '1.5px solid rgba(202, 228, 255) ', backgroundColor: 'rgba(236, 245, 255)', color: 'rgb(64, 158, 255)'}); break;
    }

    MessageBox.appendChild(closeBtn)
    if(oldMessageWrap) {
        oldMessageWrap.appendChild(MessageBox)
    } else {
        MessageWrap.appendChild(MessageBox)
        document.body.appendChild(MessageWrap)
    }
    let ani = MessageBox.animate([{
        transform: "translate(0, -100%)" ,
        opacity: 0.3,
    },{
        transform: "translate(0, 18px)",
        opacity: 0.7,
        offset: 0.9,
    },{
        transform: "translate(0, 15px)",
        opacity: 1,
        offset: 1,
    }], {
        duration: 300,
        fill: 'forwards',
        easing: 'ease-out',
    })

    //控制消失
    let timer = setTimeout(() => {
        ani.onfinish = () => {
            MessageBox.remove()
        }
        ani.reverse()
    }, (config?.showTime || 3000))

    //鼠标悬停时不清除,离开时重新计时
    MessageBox.addEventListener('mouseenter', () => clearTimeout(timer))
    MessageBox.addEventListener('mouseleave', () => {
        timer = setTimeout(() => {
            ani.reverse()
            ani.onfinish = () => {
                MessageBox.remove()
            }
        }, (config?.showTime || 3000))
    })
}


/**
2024/12/25:
- 新增:简化属性面板

2024/12/26:
- 新增:【Tab】前进一帧,【Tab+Shift】回退一帧
- 新增:右键缩放映射图,鼠标进入映射图后滚动调整缩放比,贴边滚动切换映射图。
- 调整:总框数的显示效果
- 优化:淡化吸附地线高度提示颜色
- 优化:隐藏右键菜单
- 新增:鼠标右键关闭属性面板

2024/12/27
- 新增:切包跳转

2025/1/5
- 优化:建框完成后退出绘制状态
- 优化:【Y】键聚焦视距

2025/1/6
- 调整:数据包跳转编码

2025/1/12
- 新增:title 展示提包ID

2025/1/15
- 新增:调整三视图高度

2025/2/9
- 调整:批注列表位置
- 调整:去除账号水印
- 新增:鼠标中键拖动批注列表
- 新增:作业记录title显示任务id

2025/2/10
- 新增:重写三视图调整快捷键
- 修复:无法进入未配置 classAttributes 的数据包

2025/2/11
- 【`】切换单帧/多帧视图
- 多帧视图下翻页
- 【空格】下一帧
- 【Tab】上一帧
- 【1】小车
- 【2】SUV
- 【3】二轮
- 【4】行人
- 【5】隔离柱
- 多帧视图点击序号切帧

2025/2/12
- 新增:调整现有默认尺寸,对无默认尺寸的类型进行补充
- 修复:空格的副作用(触发任意点击过的按钮)
- 新增:关键帧标注方案默认勾选[关键帧插值]
- 新增:全局批注默认勾选[漏标]

2025/2/13
- 新增:调整映射图视角名称
- 新增:快捷批注短语

2025/2/15
- 新增:记录查看历史

2025/2/16
- 新增:全局批注的快捷短语

2025/2/17
- 修复:映射图缩放后,列表总数发生变化

2025/2/18
- 新增:实时监视3D框坐标,超出有效范围时做出提示。

2025/2/19
- 新增:当选中投影框时,自动重选为其所属的3D框。

2025/2/20
- 新增:【H】显隐3D框
- 优化:优化性能

2025/2/27
- 修复:打开对象查看历史时,选中伪3D框无法自动改选成3D框。

2025/3/5
- 新增:【R】标记脑补标签

2025/3/6
- 新增:切帧后重置标注高度
- 调整:默认标注模式【关闭】
*/