minerva-online assistant

此脚本能更方便使用minerva-online平台,可在顶端菜单栏右下角的按钮处设置功能开关,并查看功能详情

当前为 2022-03-07 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         minerva-online assistant
// @namespace    https://space.bilibili.com/17846288
// @version      2.7.4
// @license      MIT
// @description  此脚本能更方便使用minerva-online平台,可在顶端菜单栏右下角的按钮处设置功能开关,并查看功能详情
// @author       inoki
// @include      https://www.minerva-online.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      fanyi.baidu.com
// @noframes
// ==/UserScript==

/* VersionInfo 企业微信文档:https://doc.weixin.qq.com/doc/w2_AOMADQamAG8fAzy6aF1RWWmEc2ZhG?scode=AMwAwgcrABEDCapPcV
PDF命名:修复LocationState_Region的匹配问题,使其能正常被命名
*/

/*jshint esversion: 9*/

(()=>{
    'use strict';

    const SET=[
        {
            'id':0,
            'name':'置顶置底',
            'func':()=>{GOTOPBOTTOM();},
            'unfunc':()=>{unGOTOPBOTTOM();},
            'detail':
            `在平台域名所有可滚动页面生效,页面右下方添加【方向图标】按钮<br>
            【方向图标】点击会根据页面滚动方向自动置顶或置底,按钮样式可在代码中自定义中修改`,
            'switch':1
        },
        {
            'id':1,
            'name':'菜单遮罩',
            'func':()=>{COVERMENU();},
            'unfunc':()=>{unCOVERMENU();},
            'detail':
            `在有顶端菜单栏的页面生效<br>
            让菜单栏需要点击一次后才可展开,防止鼠标经过时误触<br>
            (默认关闭)`,
            'switch':0
        },
        {
            'id':2,
            'name':'附件下载',
            'func':()=>{DOWNLOADFILE();},
            'unfunc':()=>{unDOWNLOADFILE();},
            'detail':
            `在问卷管理页面生效,每份报告前添加【↓】按钮<br>
            【↓】点击可加载附件列表<br>
            【√】点击可下载全部附件,之后会变为【〇】<br>
            【×】点击可关闭附件列表<br>
            附件名点击可下载单个附件,鼠标悬停可预览图片<br>
            【删除全部附件】点击可将此报告全部附件标记为删除`,
            'switch':1
        },
        {
            'id':3,
            'name':'扣分标记',
            'func':()=>{MARKQUESTION();},
            'unfunc':()=>{unMARKQUESTION();},
            'detail':
            `在单店报告页面生效,可醒目标记扣分或N/A的题目,方便快速检查相关题评论<br>
            将题目中勾选扣分和N/A项标色,选项更改后需保存报告才会刷新标记<br>
            可在上方设置扣分(默认为红)和N/A(默认为绿)的标记颜色,点击【√】保存更改<br>
            颜色更改后关开此功能可在报告页面即时刷新颜色`,
            'switch':1
        },
        {
            'id':4,
            'name':'评论编辑',
            'func':()=>{COMMENTEDIT();},
            'unfunc':()=>{unCOMMENTEDIT();},
            'detail':
            `在单店报告页面生效,右下方【问卷图标】按钮展开操作界面<br>
            使用前请注意阅读操作界面最上方的【点击获取提示】<br>
            【一键替换】点击可批量修改所有评论框内容,输入匹配内容(支持正则)会即时显示匹配的评论框数量并标灰<br>
            【首字母大写】点击可智能将所有句首字母变为大写,并标灰发生过修改的评论框,只对英文生效<br>
            【评论翻译】点击会调用百度翻译,在每个评论框下方输出目标语言翻译,点击↑可将下方内容添加至评论框`,
            'switch':1
        },
        {
            'id':5,
            'name':'验证输出',
            'func':()=>{VERIFYEXPORT();},
            'unfunc':()=>{unVERIFYEXPORT();},
            'detail':
            `在问卷管理页面生效,表头上方添加【验证输出勾选的报告】按钮<br>
            【验证输出勾选的报告】点击并确认后会验证输出当前页面勾选的所有报告,成功输出的报告下方小窗口会显示绿色提示<br>
            (电脑配置较低时一次输出太多份可能导致页面卡死,请根据浏览器最多同时能开几个报告页面量力而行,默认关闭)`,
            'switch':0
        },
        {
            'id':6,
            'name':'定制汇总',
            'func':()=>{CUSTOMROLLUP();},
            'unfunc':()=>{unCUSTOMROLLUP();},
            'detail':
            `在定制汇总页面生效,在汇总表格上方添加【复制表格】按钮<br>
            【复制表格】点击可一键复制表格全部内容,方便复制到excel等软件中编辑`,
            'switch':1
        },
        {
            'id':7,
            'name':'报告存档',
            'func':()=>{SURVEYSAVES();},
            'unfunc':()=>{unSURVEYSAVES();},
            'detail':
            `在单店报告页面生效,右下方【书本图标】按钮展开操作界面,可查看自动/手动存档列表<br>
            【存档】点击可进行手动存档,每次对报告内容进行修改时,将在本地进行自动存档<br>
            【预览】点击可查看存档内容,并对需要读档写入的题目进行勾选<br>
            【读档】点击可将选中的存档全部内容写入到当前报告,或只写入预览界面勾选的题目<br>
            【删除】点击可删除选中的存档,自动/手动存档上限各为10个,超出时自动删除此类最早存档<br>
            (“评论编辑”功能造成的修改不会触发自动存档,可在修改后点击任意评论框触发自动存档)`,
            'switch':1
        },
        {
            'id':8,
            'name':'PDF命名',
            'func':()=>{PDFRENAME();},
            'unfunc':()=>{unPDFRENAME();},
            'detail':
            `在“以PDF格式下载”的转化页面生效,在高级页添加【命名并下载(全部)】按钮<br>
            可在下方FIle Name处自定义命名格式,在下拉框选择需要的命名元素(无须点merge)<br>
            【命名并下载】点击可下载单个PDF,并按File Name处的自定义命名格式命名<br>
            【命名并下载全部】点击相当于一键点击了所有【命名并下载】`,
            'switch':1
        },
    ];
    //用于打印脚本简介
    unsafeWindow.MO_logInfo=()=>{
        let info='';
        for(let s in SET){
            info+=parseInt(s)+1+' '+SET[s].name+':\n';
            info+=SET[s].detail.replaceAll(' ','').replaceAll('<br>','');
            info+='\n\n';
        }
        console.log(info);
    };

    //如网页无jQuery或版本低于1.7则引入3.6.0
    var $=unsafeWindow.$;
    try{
        console.log($.fn.jquery);
        $().on();//jQuery 1.7版本后才有$().on()
        init();
    }catch(e){
        const jq=document.createElement('script');
        jq.src='https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js';
        document.head.appendChild(jq);
        jq.onload=()=>{
            $=unsafeWindow.jQuery;
            init();
        };
    }

    /*在顶端菜单栏添加MOassist设置按钮*/
    function init(){
        console.log('jQuery',$.fn.jquery);
        const menu=$('div#menu');
        if(menu.length){
            menu.find('ul.tools').append(`
            <li class="MOassist">
                <a class="toolsLink">
                    <div class="iconTools" style="background: url('/images/icons/menu/x16/tools-settings.png')"></div>
                    <ul class="textArea" style="visibility:visible; display:none">
                        <li>MO助手设置</li>
                    </ul>
                    <ul id="MOoption" class="innerItemFirst" style="z-index: 11; display:none; top:25px; right:0px"></ul>
                </a>
            </li>
            `);
            menu.find('li.MOassist').on('click',function(){
                MOListSwitch($(this));
            }).find('ul#MOoption').on('click',e=>{
                e.stopPropagation();//让之后添加的功能列表不继承click事件
            });
            //导入所有功能列表并显示开关状态
            let options='';
            for(let i in SET){
                menu.find('ul#MOoption').append(`
                <li id="MOoptions" class="MOassist" style="width:100%">
                    <div class="menuItemText" style="color:#4C5057">${SET[i].name}</div>
                    <input type=checkbox id=${SET[i].id} class=menuIconSmall>
                    <ul class="textArea" style="visibility:visible; display: none; margin-top:0px; right:120px">
                        <li style="padding:0px 5px !important">${SET[i].detail}</li>
                    </ul>
                </li>
                `);
                //运行开启状态的功能并打勾
                if(GM_getValue(SET[i].name,SET[i].switch)){
                    SET[i].func();
                    menu.find('input#'+SET[i].id).prop('checked',true);
                }
            }
            //根据是否选中即时启用或卸载功能并记录开关状态
            menu.find('li#MOoptions').on('click',function(e){
                const checkbox=$(this).children('input:checkbox');
                const id=$(checkbox).attr('id');
                if(GM_getValue(SET[id].name,SET[id].switch)){
                    SET[id].unfunc();
                    $(checkbox).prop('checked',false);
                    GM_setValue(SET[id].name,0);
                }else{
                    SET[id].func();
                    $(checkbox).prop('checked',true);
                    GM_setValue(SET[id].name,1);
                }
            }).children('ul.textArea').on('click',e=>{
                e.stopPropagation();
            });
            //鼠标聚焦时显示详情 【https://www.minerva-online.com/portal/menu/js/v2/menuRender.js?version=21-08 createToolOption : 】
            menu.find('li.MOassist').hover(function(){
                $(this).find('ul:first').stop().show(200);
            },function(){
                $(this).find('ul:first').stop().hide(200);
            });
            setMarkQuestionColor(menu);
        }else{
            for(let i in SET) if(GM_getValue(SET[i].name,SET[i].switch)) SET[i].func();//没有menu也执行开启的功能
        }
    }

    //功能列表开关
    function MOListSwitch(el){
        if(el.find('ul#MOoption').css('display')==='none'){
            el.find('ul#MOoption').stop().slideDown(200);
        }else{
            el.find('ul#MOoption').stop().slideUp(200);
        }
    }

    //添加扣分标记颜色设置界面
    function setMarkQuestionColor(menu){
        menu.find('input#3.menuIconSmall').next('ul').prepend(`
        <div style="padding:0px 5px">
        <b id=de>扣分颜色:</b>
        <form id=de>
            <input type=radio name=de value=red>红
            <input type=radio name=de value=orange>橙
            <input type=radio name=de value=yellow>黄
            <input type=radio name=de value=green>绿
            <input type=radio name=de value=blue>蓝
            <input type=radio name=de value=purple>紫
            <input type=radio name=de value=custom>自定义
            <input type=color class=selectedColor>
            <input type=button class=rm-btn value=√ style="padding:1px 6px">
        </form>
        <b id=na>N/A颜色:</b>
        <form id=na>
            <input type=radio name=na value=red>红
            <input type=radio name=na value=orange>橙
            <input type=radio name=na value=yellow>黄
            <input type=radio name=na value=green>绿
            <input type=radio name=na value=blue>蓝
            <input type=radio name=na value=purple>紫
            <input type=radio name=na value=custom>自定义
            <input type=color class=selectedColor>
            <input type=button class=rm-btn value=√ style="padding:1px 6px">
        </form>
        </div>
        `);
        //颜色选项初始化
        menu.find('form#de,form#na').each(function(){
            const curColor= $(this).attr('id')==='de'?
                  GM_getValue($(this).prev().text(),'red') : GM_getValue($(this).prev().text(),'green');
            $(this).prev().css('color',curColor);
            $(this).children('.selectedColor').attr('id',curColor);
            if(curColor.indexOf('#')<0){
                $(this).children('input[value='+curColor+']').attr('checked',true);
                $(this).children('.selectedColor').hide();
            }else{
                $(this).children('input[value=custom]').attr('checked',true);
                $(this).children('.selectedColor').val(curColor);
            }
        });
        //点击选项颜色改变
        menu.find('form#de,form#na').children(':radio').on('click',function(){
            if($(this).val()==='custom'){
                $(this).next().show();
                $(this).next().attr('id',$(this).next().val());
            }else{
                $(this).nextAll('.selectedColor').hide();
                $(this).nextAll('.selectedColor').attr('id',$(this).val());
            }
            $(this).parent().prev().css('color',$(this).nextAll('.selectedColor').attr('id'));
        });
        //自定义颜色改变
        menu.find('form#de,form#na').children('.selectedColor').on('input',function(){
            $(this).attr('id',$(this).val());
            $(this).parent().prev().css('color',$(this).val());
        });
        //确认更改
        menu.find('form#de,form#na').children(':button').on('click',function(){
            GM_setValue($(this).parent().prev().text(),$(this).prev().attr('id'));
            if(!$(this).next().is('b')){
                $(this).after('<b>保存成功</b>');
                setTimeout(()=>{
                    $(this).next().remove();
                },3000);
            }
        });
    }
    /*在顶端菜单栏添加MOassist设置按钮*/


    /*置顶置底*/
    function GOTOPBOTTOM(){
        $(window).on('scroll.gotopbottom',()=>{//如页面后续因为内容增加而能滚动的场合触发
            if($('div#goTopBottom').length===0) GOTOPBOTTOM();
        });
        const scrollBar=$(document).height()>(window.innerHeight+1||document.documentElement.clientHeight);//如有滚动条
        if(!scrollBar||document.location.href.indexOf('alias=knowledgebase')>=0) return;//knowledgebase页面自带置顶按钮,不启用
        const goTopBottomButton=document.createElement('div');
        const toggleButton=document.createElement('img');
        $(toggleButton).appendTo(goTopBottomButton);
        $(goTopBottomButton).appendTo(document.body);
        $(goTopBottomButton).css({'position':'fixed','zIndex':10000}).attr('id','goTopBottom');
        $(toggleButton).css({'display':'block','cursor':'pointer'}).attr('src','/knowledgebase/images/arrow_back_to_top.svg');//按钮显示图片(向下箭头)

        //以下按钮参数可自定义修改
        goTopBottomButton.style.bottom='50px';//按钮距离网页底部50px
        goTopBottomButton.style.right='30px';//按钮距离网页右边30px
        toggleButton.style.width='25px';//按钮图片宽25px
        toggleButton.style.height='25px';//按钮图片高25px
        toggleButton.style.opacity=0.5;//按钮不透明度,0.0(完全透明)到1.0(完全不透明)
        toggleButton.style.backgroundColor='grey';//按钮背景颜色,也可使用在excel等软件的自定义颜色界面的16进制代码
        const clickScrollTime=500;//点击按钮时,网页滚动到顶部或底部需要的时间,500毫秒

        //点击按钮时网页滚动到顶部或底部
        let scrollDirection='down';
        toggleButton.addEventListener('click',()=>{
            if(scrollDirection==='up'){
                $('html,body').animate({scrollTop:'0px'},clickScrollTime);
            }else{
                $('html,body').animate({scrollTop:$(document).height()},clickScrollTime);
            }
        });
        //页面滚动监听
        let scrollAction=window.pageYOffset;
        $(window).scroll(()=>{
            const diffY=scrollAction-window.pageYOffset;
            scrollAction=window.pageYOffset;
            scrollDirection= diffY<0? 'down' : 'up';
            toggleButton.style.transform= diffY<0? 'rotate(0deg)' : 'rotate(180deg)';
            if(getScrollTop()===0){
                scrollDirection='down';
                toggleButton.style.transform='rotate(0deg)';
            }
            if(getScrollTop()+window.innerHeight+20>=$(document).height()){
                scrollDirection='up';
                toggleButton.style.transform='rotate(180deg)';
            }
        });
    }

    //获取垂直方向滑动距离
    function getScrollTop(){
        let scrollTop=0;
        if(document.documentElement&&document.documentElement.scrollTop){
            scrollTop=document.documentElement.scrollTop;
        }else if(document.body){
            scrollTop=document.body.scrollTop;
        }
        return scrollTop;
    }
    /*置顶置底*/

    /*卸载置顶置底*/
    function unGOTOPBOTTOM(){
        $(window).off('scroll.gotopbottom');
        if($('div#goTopBottom').length) $('div#goTopBottom').remove();
    }
    /*卸载置顶置底*/


    /*菜单遮罩*/
    function COVERMENU(){
        if($('div#menu').length){
            //若存在menu则添加cover层
            const menu=$('div#menu')[0];
            const zidx=parseInt(getComputedStyle(menu).zIndex)+1;
            const cover = document.createElement('div');
            cover.className = 'layout';
            cover.style = 'top:'+menu.style.top+';opacity:0.3;z-index:'+zidx+';right:10%';
            $(cover).appendTo($('body')[0]).attr('id','cover');
            //点击时将cover层下置
            cover.addEventListener('click',()=>{
                cover.style.zIndex = -1;
            });
            //离开menu时cover层还原
            menu.addEventListener('mouseleave',()=>{
                cover.style.zIndex = zidx;
            });
            //cover层位置跟随menu 【https://www.minerva-online.com/portal/menu/js/v2/menuRender.js?version=21-08 onScrollEventHandler : 】
            $(window).scroll(()=>{
                const SM=unsafeWindow.SM;
                const ind = SM.ui.headerHeight - SM.ui.getScrollTop();
                cover.style.top= ind>0? ind+'px' : '0px';
            });
        }
    }
    /*菜单遮罩*/

    /*卸载菜单遮罩*/
    function unCOVERMENU(){
        if($('div#cover').length) $('div#cover').remove();
    }
    /*卸载菜单遮罩*/


    /*附件下载*/
    function DOWNLOADFILE(){
        if (document.location.href.indexOf('alias=smngr.surveyexplorer')>=0&&$('tr.persist-header').length){
            $('tr.persist-header').each(function(){
                $(this).children().first().after($(this).children().first().clone(true));
            });
            $('div.sticky-wrap').find(':checkbox').each(function(){//checkbox后添加下载按钮
                const surveyid=$(this).val();
                $(this).parent().after(`
                <td>
                    <div id=${surveyid} class=downloadFile>
                        <button type=button id=download class=rm-btn>↓</button>
                    </div>
                </td>
                `);
                $(this).parent().next().find('button#download').one('click',function(){
                    DownloadButton($(this).parent(),surveyid);//将$('div#'+surveyid)传参为df
                });
            });
        }
    }

    //获取附件列表
    function DownloadButton(df,surveyid){
        df.find('button#download').hide();
        df.append('<b id=loading>......</b>');
        $.get(`
        /open/data.asp?post={
            "action":"exec",
            "dataset":{"datasetname":"/Apps/SM/Survey/SurveyInstanceGetData"},
            "parameters":[{"name":"SurveyInstanceID","value":"${surveyid}"}]
        }`,(data,status)=>{//调用API获取当前survey数据[SurveyInstanceGetData]
            if (status==='success'){
                if(data?.dataset){
                    const filedata=data.dataset.data[3];
                    const fileno=filedata.length;
                    df.append(`
                    <ol id=filelist>
                        <b>\t#=${fileno}</b>
                        <table></table>
                    </ol>
                    `);
                    if (fileno>0){
                        const ol=df.find('ol#filelist');
                        $('<button type=button id=downloadAll class="rm-btn rm-btn-default">√</button>').prependTo(ol)
                            .on('click',()=>{
                            DownloadAll(df);
                        });
                        $('<button type=button id=deleteAll class="rm-btn rm-btn-default">删除全部附件</button>').appendTo(ol)
                            .on('click',()=>{
                            DeleteAll(df,surveyid);
                        });
                        const tb=ol.children('table');
                        for(let i in filedata){
                            const filename=filedata[i].FileName+'.'+filedata[i].FileExtension;
                            const fileid=filedata[i].AttachmentID;
                            const fileurl='/mystservices/Attachments/getAttachment.asp?Attachment='+fileid+'&Password='+filedata[i].Password+'';
                            let filesize=Number(filedata[i].FileSizeInBytes)/1024;
                            filesize= (filesize>1024)? (filesize/1024).toFixed(2)+' MB' : filesize.toFixed(2)+' KB';
                            $('<tr id='+fileid+'>').appendTo(tb).append(`
                            <td>
                                <li>
                                    <a id=${filedata[i].AttachmentType} class=mailboxlink href=${fileurl}>${filename}</a>
                                </li>
                            </td>
                            <td>${filesize}</td>
                            <td>QID:${filedata[i].ProtoQuestionID}</td>
                            `);
                        }
                        df.find('a#I,a#V').mouseenter(function(){
                            FilePreview(1,$(this).attr('href'));
                        }).mouseleave(()=>{
                            FilePreview(0);
                        });
                    }
                }else{
                    df.append('<b>登录失效!</b>');
                }
            }else{
                df.append('<b>网络错误!</b>');
            }
            DownloadButton0(df,surveyid);
        },'json');
    }

    //预览附件图片
    function FilePreview(show,src){
        if(show){
            const imgid=src.split('=')[2];
            if($('img#'+imgid+'.filepreview').length===0){
                $('<div><img id='+imgid+' class=filepreview></img></div>').appendTo('body');
                $('img#'+imgid+'.filepreview').attr('src',src+'&getThumbnail=1').css('height','200px')//视频附件预览图u&getThumbnail=1
                    .parent().css({'position':'fixed','zIndex':10000,'height':'200px','background':'url(/images/icons/filtersv2/loading06.gif)'});
            }
            $('img#'+imgid+'.filepreview').parent().css({'top':event.clientY-200+'px','left':event.clientX+100+'px'});
            $('img#'+imgid+'.filepreview').show();
        }else{
            $('img.filepreview').hide();
        }
    }

    //按钮变为关闭
    function DownloadButton0(df,surveyid){
        df.find('b#loading').remove();
        df.find('button#download').text('×').show().one('click',()=>{
            DownloadButton1(df,surveyid);
        });
    }

    //按钮重置为初始
    function DownloadButton1(df,surveyid){
        df.find('ol,b').remove();
        df.find('button#download').text('↓').one('click',()=>{
            DownloadButton(df,surveyid);
        });
    }

    //下载全部
    function DownloadAll(df){
        df.find('button#downloadAll').text('〇').hide();
        const iframe=df.find('ol#filelist iframe');
        const a=df.find('ol#filelist a');
        if(iframe.length) iframe.remove();
        setTimeout(()=>{
            df.find('button#downloadAll').show();
        },1000*a.length);//有几个附件就隐藏按钮几秒
        a.each(function(){
            $('<iframe src='+$(this).attr('href')+'>').appendTo(this).hide();
        });
    }
    /*
    //不知是不是因为下载地址有重定向的关系,GM_download效率低下,弃用
    function DownloadAll(df){
        const a=df.find('ol#filelist a');
        let loaded=0;
        df.find('button#downloadAll').text('〇').hide();
        df.find('ol#filelist>p,ol#filelist>br').remove();
        df.find('ol#filelist>b').text(`\t#=${loaded}/${a.length}\t下载中...`);
        a.each(function(){
            GM_download({
                'url':$(this).attr('href'),
                'name':df.parent().nextAll().eq(3)[0].innerText+'—'+df.parent().nextAll().eq(7)[0].innerText.replace('\n',' ')+'—'+$(this).text(),
                'onload':()=>{
                    loaded++;
                    if(loaded===a.length){
                        df.find('ol#filelist>b').text(`\t#=${loaded}/${a.length}\t下载完成!`);
                        df.find('button#downloadAll').show();
                    }else{
                        df.find('ol#filelist>b').text(`\t#=${loaded}/${a.length}\t下载中...`);
                    }
                },
                'onerror':()=>{
                    df.find('ol#filelist>b').after('<br><p>'+$(this).text()+'\t下载错误!</p>');
                    df.find('button#downloadAll').show();
                },
                'ontimeout':()=>{
                    df.find('ol#filelist>b').after('<br><p>'+$(this).text()+'\t下载超时!</p>');
                    df.find('button#downloadAll').show();
                }
            });
        });
    }
    */

    function DeleteAll(df){
        const surveyid=df.attr('id');
        const attid=[];
        df.find('a').each(function(){
            attid.push(this.href.match(/(?<=\=)\d+/)[0]);
        });
        console.log(attid,surveyid);
        const apply=confirm('请确认是否将此报告所有附件标记为删除\n(可在more中恢复,确认后可刷新页面或再次加载附件列表查看)');
        if(apply){
            //模拟手动禁用单个附件发送post请求,但请求数量较多
            for(let i of attid){
                $.post(`/document.asp?alias=survey.disableimage&ImageID=${i}&InstanceID=${surveyid}`,'ref=&step=2&comment=');
            }
            df.children('button#download').click();
        }
    }
    /*
    //以API实现,不会在more中留下用以恢复的记录,但请求数量较少
    function DeleteAll(df){
        let csv='';
        df.find('a').each(function(){
            csv+=this.href.match(/(?<=\=)\d+/)[0]+',';
        });
        csv=csv.slice(0,-1);
        console.log(csv);
        const apply=confirm('请确认是否要删除此报告所有附件\n(无法恢复注意备份,确认后可刷新页面或再次加载附件列表查看)');
        if(apply){
            //调用API将当前survey的所有附件标记为删除[SurveyAttachmentsMarkAttachmentsForDelete]
            $.get(`
            /open/data.asp?post={
                "action":"exec",
                "dataset":{"datasetname":"/Apps/SM/Media Hub/SurveyAttachmentsMarkAttachmentsForDelete"},
                "parameters":[{"name":"CsvList","value":"${csv}"}]
            }`);
            df.children('button#download').click();
        }
    }
    */
    /*附件下载*/

    /*卸载附件下载*/
    function unDOWNLOADFILE(){
        if (document.location.href.indexOf('alias=smngr.surveyexplorer')>=0&&$('tr.persist-header').length){
            $('tr.persist-header').each(function(){
                $(this).children().first().remove();
            });
            $('div.downloadFile').each(function(){
                $(this).parent().remove();
            });
        }
    }
    /*卸载附件下载*/


    /*扣分标记*/
    function MARKQUESTION(){
        if(document.location.href.indexOf('alias=survey.view')>=0){
            $('span.surveyansweroption').each(function(){
                if($(this).prev('input').is(':checked')){
                    if($(this).prev('input').val()==='__na__'){
                        $(this).css('color',GM_getValue('N/A颜色:','green'));//默认标绿N/A项
                    }
                }
            });
            //获取所有扣分的题目
            const qidmark=[];
            $.get('/mystservices/v2new/getSurvey.asp?InstanceID='+$('input#instanceID').val(),(data,status)=>{
                if (status==='success'){
                    $(data).find('nobr').each(function(){
                        const score=$(this).text();
                        if(score!=''&&score.indexOf('%')===-1){//排除空值与section总分
                            const pts=score.split('/');
                            if(pts[0]<pts[1]){
                                const QidANS=$(this).parents('td.surveyquestioncell').prev().find('div').attr('id');
                                qidmark.push(QidANS);
                            }
                        }
                    });
                    for(let i in qidmark){
                        $('div#'+qidmark[i]).find('span.surveyansweroption').css('color',GM_getValue('扣分颜色:','red'));//默认标红扣分项
                    }
                }
            });
        }
    }
    /*扣分标记*/

    /*卸载扣分标记*/
    function unMARKQUESTION(){
        if(document.location.href.indexOf('alias=survey.view')>=0) $('span.surveyansweroption').removeAttr('style');
    }
    /*卸载扣分标记*/


    /*评论编辑*/
    function COMMENTEDIT(){
        if(document.location.href.indexOf('alias=survey.view')>=0){
            $('<div id=commentEdit>').appendTo($('body')[0])
                .css({'position':'fixed','zIndex':10000,
                      'right':'30px','bottom':'80px','height':'25px','width':'25px',
                      'background':'url("/images/icons/menu/x32/survet.png") 50%/80% no-repeat #4C5157'});
            //插入操作界面并赋值为ce
            $('<div id=commentFunc>').appendTo('div#commentEdit')
                .css({'position':'fixed','right':'60px','bottom':'50px'}).hide()
                .on('click',e=>{
                e.stopPropagation();//阻止子元素执行父元素click事件
            });
            const ce=$('div#commentFunc');
            $('div#commentEdit').on('click',function(){
                commentEditSwitch(ce);
            });
            //提示开关
            $('<b id=hint>【点击获取提示】</b>').appendTo(ce)
                .on('click',function(){
                hintSwitch($(this));
            });
            //评论匹配与替换
            $('<button type=button id=replaceAll class=surveyBottomButton>一键替换</button>').appendTo(ce)
                .before('<textarea id=find placeholder=匹配内容></textarea><b id=commentMark>↓↓↓</b><textarea id=replace placeholder=替换内容></textarea>')
                .before('<b id=findNum>#</b>');
            $('textarea#find,textarea#replace').on('keydown',e=>{
                e.stopPropagation();//阻止页面自带keydown事件修改textarea的class值
            });
            $('b#commentMark').on('click',()=>{
                commentMark();
            });
            $('textarea#find').on('input',function(){
                commentMatch(ce,$(this));
            });
            $('button#replaceAll').on('click',()=>{
                commentReplace(ce);
            });
            //首字母大写
            $('<button type=button id=initialUpper class=surveyBottomButton>首字母大写</button>').appendTo(ce)
                .on('click',()=>{
                initialUpper();
            });
            //评论翻译
            $(`
            <button type=button id=commentTrans class=surveyBottomButton>评论翻译</button>
            <select id=toLang>
              <option value=en>→英文</option>
              <option value=jp>→日文</option>
              <option value=zh>→中文(简体)</option>
              <option value=cht>→中文(繁体)</option>
              <option value=yue>→中文(粤语)</option>
              <option value=f→j>繁体→简体</option>
            </select>
            `).appendTo(ce);
            ce.find('button#commentTrans').on('click',()=>{
                commentTranslate($('select#toLang option:selected').val());
            });
            ce.children().css({'display':'block','text-align':'center','margin':'5px auto'});
        }
    }

    //评论替换开关
    function commentEditSwitch(ce){
        if(ce.css('display')==='none'){
            ce.show();
            commentMatch(ce,$('textarea#find'));
        }else{
            ce.hide();
            $('textarea.surveycomment,textarea.active').css('background','');
        }
    }

    //插入或移除提示
    function hintSwitch(hs){
        if(hs.children().length){
            hs.text('【点击获取提示】').children().remove();
        }else{
            hs.text('【点击关闭提示】')
                .append('<a class=mailboxlink target=_blank href=https://tool.oschina.net/uploads/apidocs/jquery/regexp.html>匹配支持正则表达式</a>')
                .append('<a class=mailboxlink target=_blank href=https://c.runoob.com/front-end/854>正则表达式测试</a>')
                .append(`
                <ol>
                    <li>正则实例:[。|.]$ 可匹配末尾处中英文句号;^[a-z] 可匹配开头处小写字母;甲|乙|丙 可匹配甲或乙或丙</li>
                    <li>可当作一般替换使用,如需替换一些特殊字符(\^$*+?.等,参照第一个链接中所列字符),请在前面使用\\标记转义,避免识别为正则表达</li>
                    <li>评论框激活后按Ctrl可切换评论框是否标红,标红的评论框将被排除在修改范围之外,点击两框间的↓↓↓可快速切换全部评论框标红与否</li>
                    <li>匹配内容为空时会匹配所有字符</li>
                </ol>
                `);
            hs.children().css('display','block').on('click',e=>{
                e.stopPropagation();
            });
            hs.find('li').css({'text-align':'left','width':'200px'});
        }
    }

    //切换所有评论框标红与否
    function commentMark(){
        $('textarea.surveycomment,textarea.active').each(function(){
            if($(this).attr('class')==='active'){
                $(this).attr('class','surveycomment');
            }else{
                $(this).attr('class','active');
            }
        });
    }

    //即时标灰匹配到的评论框并计数
    function commentMatch(ce,cm){
        let find;
        try{//若不是正则表达式,按普通字符处理
            find=new RegExp(cm.val(),'gm');
        }catch(e){
            find=cm.val();
        }
        let findNum=0;
        $('textarea.active').css('background','');
        $('textarea.surveycomment').each(function(){
            try{//
                if($(this).val().search(find)>=0){//search只接受正则
                    findNum++;
                    $(this).css('background','lightgrey');
                }else{
                    $(this).css('background','');
                }
            }catch(e){
                if($(this).val().indexOf(find)>=0){//indexOf只接受字符
                    findNum++;
                    $(this).css('background','lightgrey');
                }else{
                    $(this).css('background','');
                }
            }
        });
        ce.find('b#findNum').text('#='+findNum);
    }

    //判断是否为正则并进行评论替换
    function commentReplace(ce){
        $('textarea.surveycomment').each(function(){
            let find;
            try{
                find=new RegExp(ce.find('textarea#find').val(),'gm');
            }catch(e){
                find=$('textarea#find').val();
            }
            const replace=ce.find('textarea#replace').val();
            const text=$(this).val().replaceAll(find,replace);
            $(this).val(text);
        });
    }

    //首字母大写
    function initialUpper(){
        $('textarea.surveycomment').each(function(){
            const match=new Set($(this).val().match(/(^|\.|\?|!)("|'|) *[a-z]/gm));
            if(match.size){
                let text=$(this).val();
                for(let m of match){
                    const r=new RegExp(`(^|\\.|\\?|!)("|'|) *`+m,'gm');
                    text=text.replaceAll(r,m.toUpperCase());
                }
                $(this).val(text);
                $(this).css('background','lightgrey');
            }else{
                $(this).css('background','');
            }
        });
    }

    //调用百度翻译进行评论翻译
    async function commentTranslate(toLang){
        await translate_baidu_startup();
        $('textarea.surveycomment').each(async function(){
            if($(this).val()&&$(this).next().is('input:hidden')){
                $(this).after('<textarea id=trans style=overflow:hidden rows='+$(this).attr('rows')+' cols='+$(this).attr('cols')+'>')
                    .after('<button type=button class=attachmentBtn style="display:block;height:1.5em;width:1.5em;margin:5px 0">↑</button>');
            }
            if($(this).val()){
                const fromLang= toLang==='f→j'? 'cht' : null;
                toLang= toLang==='f→j'? 'zh' : toLang;
                const translated=await translate_baidu(toLang,$(this).val(),fromLang);
                $(this).next().next('textarea').val(translated);
                unsafeWindow.updrowH($(this).next().next('textarea')[0]);//调用页面自带函数来调整评论框高度
                $(this).next('button').on('click',function(){
                    const prev=$(this).prev().val();
                    const next=$(this).next().val();
                    $(this).prev().val(prev+'\n\n'+next);
                    unsafeWindow.updrowH($(this).prev()[0]);
                    $(this).next().remove();
                    $(this).remove();
                });
            }
        });
        $('textarea#trans').on('keydown',e=>{
            e.stopPropagation();//阻止页面自带keydown事件修改textarea的class值
        });
    }

    //百度翻译 参考https://greasyfork.org/scripts/378277
    async function translate_baidu_startup(){
        if(window.sessionStorage.getItem('baidu_gtk')&&window.sessionStorage.getItem('baidu_token'))return;
        const options={
            method:'GET',
            url:'https://fanyi.baidu.com',
        };
        const res=await Request(options);
        window.sessionStorage.setItem('baidu_gtk',/window\.gtk = '(.*?)'/.exec(res.responseText)[1]);
        window.sessionStorage.setItem('baidu_token',/token: '(.*?)'/.exec(res.responseText)[1]);
    }

    async function translate_baidu(toLang,raw,fromLang){
        if(!fromLang){
            fromLang=await check_lang(raw);
        }
        const proc_raw= raw.length>30? (raw.substr(0,10)+raw.substr(~~(raw.length/2)-5,10)+raw.substr(-10)) : raw;//process
        const tk_key=window.sessionStorage.getItem('baidu_gtk');
        const token=window.sessionStorage.getItem('baidu_token');//get token
        const options={
            method:"POST",
            url:'https://fanyi.baidu.com/v2transapi',
            data:`from=${fromLang}&to=${toLang}&query=${encodeURIComponent(raw)}&transtype=translang&simple_means_flag=3&sign=${tk(proc_raw,tk_key)}&token=${token}&domain=common`,
            headers:{
                "referer":'https://fanyi.baidu.com',
                'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
            },
        };
        return await BaseTranslate('百度翻译',raw,options,res=>JSON.parse(res).trans_result.data.map(item=>item.dst).join('\n'));
    }

    async function check_lang(raw){
        const options={
            method:"POST",
            url:'https://fanyi.baidu.com/langdetect',
            data:'query='+encodeURIComponent(raw.replace(/[\uD800-\uDBFF]$/,'').slice(0,50)),
            headers:{
                'Content-Type':'application/x-www-form-urlencoded',
            }
        };
        const res=await Request(options);
        try{
            return JSON.parse(res.responseText).lan;
        }catch(e){
            console.log(e);
            return;
        }
    }

    //根据翻译字符获取sign值
    function tk(a,b){
        var d=b.split('.');
        b=Number(d[0]) || 0;
        for(var e=[],f=0,g=0;g<a.length;g++){
            var k=a.charCodeAt(g);
            128>k?
                e[f++]=k : (
                2048>k?
                e[f++]=k>>6|192 : (
                    55296==(k&64512)&&g+1<a.length&&56320==(a.charCodeAt(g+1)&64512)? (
                        k=65536+((k&1023)<<10)+(a.charCodeAt(++g)&1023),
                        e[f++]=k>>18|240,
                        e[f++]=k>>12&63|128
                    ) :
                    e[f++]=k>>12|224,
                    e[f++]=k>>6&63|128
                ),
                e[f++]=k&63|128
            );
        }
        a=b;
        for(f=0;f<e.length;f++) a=Fo(a+e[f],'+-a^+6');
        a=Fo(a,'+-3^+b+-f');
        a^=Number(d[1])||0;
        0>a&&(a=(a&2147483647)+2147483648);
        a%=1E6;
        return a.toString()+'.'+(a^b);
    }

    function Fo(a,b){
        for(var c=0;c<b.length-2;c+=3){
            var d=b.charAt(c+2);
            d= "a"<=d? d.charCodeAt(0)-87 : Number(d);
            d= "+"==b.charAt(c + 1)? a>>>d : a<<d;
            a= "+"==b.charAt(c)? a+d&4294967295 : a^d;
        }
        return a;
    }

    //异步请求包装工具
    async function PromiseRetryWrap(task,options,...values){
        const {RetryTimes,ErrProcesser}=options||{};
        let retryTimes=RetryTimes||5;
        const usedErrProcesser=ErrProcesser||(err=>{throw err;});
        if(!task)return;
        while(true){
            try{
                return await task(...values);
            }catch(e){
                if(!--retryTimes){
                    console.log(e);
                    return usedErrProcesser(e);
                }
            }
        }
    }

    async function BaseTranslate(name,raw,options,processer){
        const toDo=async ()=>{
            let tmp;
            try{
                const data=await Request(options);
                tmp=data.responseText;
                const result=await processer(tmp);
                window.sessionStorage.setItem(name+'-'+raw,result);
                return result;
            }catch(e){
                throw {
                    responseText: tmp,
                    err: e
                };
            }
        };
        return await PromiseRetryWrap(toDo,{RetryTimes:3,ErrProcesser:()=>"翻译出错"});
    }

    function Request(options){
        return new Promise((reslove,reject)=>GM_xmlhttpRequest({...options,onload:reslove,onerror:reject}));
    }
    /*评论编辑*/


    /*卸载评论编辑*/
    function unCOMMENTEDIT(){
        if($('div#commentEdit').length) $('div#commentEdit').remove();
    }
    /*卸载评论编辑*/


    /*验证输出*/
    function VERIFYEXPORT(){
        if (document.location.href.indexOf('alias=smngr.surveyexplorer')>=0&&$('div#filterdiv').length){
            $('div#filterdiv').before('<button type=button id=verifyExport class="rm-btn rm-btn-default">验证输出勾选的报告</button>');
            $('button#verifyExport').css({'margin':'10px 0px','display':'block'}).on('click',()=>{
                verifyExportAll();
            });
        }
    }

    //验证输出全部报告
    function verifyExportAll(){
        const apply=confirm('请确认是否要验证输出当前页面勾选的所有报告(电脑配置低请勿一次性输出过多报告)');
        if(apply){
            $('table#reporttable tbody').find('tr').each(function(){
                if($(this).find('input:checkbox').eq(0).is(':checked')){
                    const a=$(this).find('td>a.mailboxlink');
                    const src=a.attr('href').replace('document.asp?alias=survey.view&','importmngr/ImportSurveys/ImportSurveysFrame.asp?');
                    const iframe=document.createElement('iframe');
                    iframe.src=src;
                    iframe.style='height:80px; width:250px';
                    a.after(iframe);
                    iframe.onload=function(){
                        if($(this).attr('href').indeOf('ImportSurveysFrame.asp')>=0){
                            const doc=$(this).contents();
                            doc.find('div#addInfo,div#menu,div#pathContainer').remove();
                            if(doc.find('input#scrverN').is(':checked')&&doc.find('input#questVerN').is(':checked')){//前2点均为否时才进行操作
                                doc.find('input#scrverY').click();
                                doc.find('input#questVerY').click();
                                doc.find('button#save').click();
                            }else{
                                $(this).before('<div>报告原本已处于上线状态</div>');
                            }
                        }else if($(this).attr('href').indeOf('SurveyInstanceSubmitForVerify.asp')>=0){
                            $(this).before('<div style=background:green><img src=/images/icons/xp16/accept-hover.png>报告已成功执行上线</div>');
                            $(this).remove();
                        }
                    };
                }
            });
            alert('请耐心等待所有小窗口加载完成,显示绿色保存成功提示后,再刷新页面检查是否全部验证输出成功');
        }
    }
    /*验证输出*/

    /*卸载验证输出*/
    function unVERIFYEXPORT(){
        if($('button#verifyExport').length) $('button#verifyExport').remove();
    }
    /*卸载验证输出*/


    /*定制汇总*/
    function CUSTOMROLLUP(){
        if(document.location.href.indexOf('alias=clientaccess.customrollups')>=0){
            if($('table.reporttable,table.incCrossTabTableClass').length){
                $('table.reporttable,table.incCrossTabTableClass')
                    .before('<button type=button id=copyBtnCR>复制表格</button>');
                $(window).scroll(()=>{//必要时隐藏按钮避免影响冻结首行效果
                    if($('table.sticky-thead'.length)){
                        $('table.sticky-thead').css('z-index')>0?
                            $('button#copyBtnCR').hide() : $('button#copyBtnCR').show();
                    }
                });
                $('button#copyBtnCR').on('click',function(){
                    window.getSelection().removeAllRanges();
                    const content=$(this).next('table').children('tbody')[0].innerText;
                    if($(this).prevAll('textarea').length===0){
                        $(this).before('<textarea>');
                    }
                    $(this).prev('textarea').show();
                    $(this).prev('textarea').val(content).select();
                    document.execCommand('copy');
                    $(this).prev('textarea').hide();
                    window.getSelection().removeAllRanges();
                    document.execCommand('copy')?
                        $(this).text('复制成功!') : $(this).text('复制失败...');
                    setTimeout(()=>{
                        $(this).text('复制表格');
                    },3000);
                });
            }
        }
    }
    /*定制汇总*/

    /*卸载定制汇总*/
    function unCUSTOMROLLUP(){
        if($('button#copyBtnCR').length) $('button#copyBtnCR').remove();
    }
    /*卸载定制汇总*/


    /*报告存档*/
    function SURVEYSAVES(){
        if(document.location.href.indexOf('alias=survey.view')>=0){
            //插入操作界面并赋值为ssl
            $('<div id=surveySaves>').appendTo($('body')[0])
                .css({'position':'fixed','zIndex':10000,
                      'right':'30px','bottom':'110px','height':'25px','width':'25px',
                      'background':'url("/images/icons/menu/x16/KB-icon.png") 50%/80% no-repeat #4C5157'});
            $('<div id=surveySavesList>').appendTo('div#surveySaves')
                .css({'position':'fixed','right':'60px','bottom':'50px','background':'lightgrey'}).hide()
                .on('click',e=>{
                e.stopPropagation();//阻止子元素执行父元素click事件
            });
            const ssl=$('div#surveySavesList');
            ssl.append(`
            <div style=margin:5px>
                <button type=button id=Show class=rm-btn>预览</button>
                <button type=button id=Load class=rm-btn>读档</button>
                <button type=button id=Save class=rm-btn>存档</button>
                <button type=button id=Dele class=rm-btn>删除</button>
            </div>
            <div id=tab style=margin:5px>
                <label><input type=radio name=tab value=autosaves checked>自动存档</label>
                <label><input type=radio name=tab value=selfsaves>手动存档</label>
            </div>
            <table cellpadding=5 style=margin:5px>
                <thead id=saves>
                    <tr>
                        <td></td>
                        <td>保存时间</td>
                        <td>问卷标题</td>
                        <td>店铺ID</td>
                        <td>报告ID</td>
                    </tr>
                </thead>
                <thead id=questions>
                    <tr>
                        <td><input type=checkbox name=questions checked></td>
                        <td>QID</td>
                        <td>题目</td>
                        <td>选项</td>
                        <td>评论</td>
                    </tr>
                </thead>
                <tbody id=autosaves></tbody>
                <tbody id=selfsaves></tbody>
                <tbody id=questions style=height:300px;overflow:scroll></tbody>
            </table>
            `);
            ssl.find('thead,tbody').css('display','block');
            ssl.find('#selfsaves,#questions').hide();
            //存档列表开关
            $('div#surveySaves').on('click',function(){
                if(ssl.css('display')==='none'){
                    ssl.show();
                    refreshList(ssl);
                }else{
                    ssl.hide();
                }
            });
            //按钮功能
            ssl.find('button#Show').on('click',()=>{
                showData(ssl);
            });
            ssl.find('button#Load').on('click',()=>{
                loadData(ssl);
            });
            ssl.find('button#Save').on('click',()=>{
                saveData(ssl,'selfsaves');
            });
            ssl.find('button#Dele').on('click',()=>{
                deleteData(ssl);
            });
            //切换存档列表页
            ssl.find('div#tab').on('click',function(){
                ssl.find('input[name=saves]:checked').prop('checked',false);//将存档的选中状态重置
                const checked=$(this).find('input:checked').val();
                const unchecked= checked==='autosaves'? 'selfsaves' : 'autosaves';
                $(this).parent().find('tbody#'+unchecked).hide();
                $(this).parent().find('tbody#'+checked).show();
                refreshList(ssl,checked);
                theadWidth(ssl.find('thead#saves'),ssl.find('tbody#'+checked));
            });
            //预览界面一键勾选与取消
            ssl.find('thead#questions input').on('click',function(){
                if($(this).is(':checked')){
                    $(this).parents('table').find('input[name=questions]').prop('checked',true);
                }else{
                    $(this).parents('table').find('input[name=questions]').prop('checked',false);
                }
            });
            //监听表单内点击和评论失焦触发存档事件
            let Form=$('form#frmSurvey').serialize();
            $('form#frmSurvey').on('click.autosave',()=>{
                Form=autoSave(ssl,Form);
            });
            $('textarea.surveycomment,textarea.active').on('blur.autosave',()=>{
                Form=autoSave(ssl,Form);
            });
        }
    }

    //表单变化时自动存档
    function autoSave(ssl,prvForm){
        const curForm=$('form#frmSurvey').serialize();
        if(prvForm!=curForm){//如表单数据改变
            saveData(ssl,'autosaves');
        }
        return curForm;
    }

    //刷新存档列表
    function refreshList(ssl,fromSave){
        if(ssl.css('display')==='none') return;//界面隐藏时不刷新
        if(!fromSave) fromSave=ssl.find('input[name=tab]:checked').val();//无参数自动判断当前页
        const curChecked=ssl.find('input[name=saves]:checked').parent('td').next().text();//记录当前选中存档
        ssl.find('tbody#'+fromSave).empty();
        const saves=GM_getValue(fromSave,{});
        const k=Object.keys(saves);
        if(k.length){
            for(let i in k){
                const line= i%2===1? 'reporttable_odd' : 'reporttable_even';
                ssl.find('tbody#'+fromSave).prepend(`
                <tr class=${line}>
                    <td><input type=radio name=saves></td>
                    <td>${k[i]}</td>
                    <td>${saves[k[i]].surveytitle}</td>
                    <td>${saves[k[i]].formhead.LOCATION}</td>
                    <td>${saves[k[i]].surveyid}</td>
                </tr>
                `);
                if(curChecked===k[i]) ssl.find('tbody#'+fromSave).children('tr:first').find('input').prop('checked',true);//还原存档选中状态
            }
            //如为当前存档页则同步表头宽度
            if(ssl.find('input[name=tab]:checked').val()===fromSave){
                theadWidth(ssl.find('thead#saves'),ssl.find('tbody#'+fromSave));
            }
        }else{
            const w=getComputedStyle(ssl.find('tbody#'+fromSave)[0]).width;
            ssl.find('tbody#'+fromSave).append('<td style=width:'+w+';text-align:center>无数据!</td>');
        }
    }

    //预览
    function showData(ssl){
        if(ssl.find('thead#questions').css('display')==='none'){
            const [save]=checkSave(ssl);
            if(!save) return;
            const head=save.formhead;
            for(let h in head){
                const line='reporttable_odd';
                ssl.find('tbody#questions').append(`
                <tr class=${line}>
                    <td><input type=checkbox name=questions></td>
                    <td colspan=2>${h}</td>
                    <td colspan=2>${head[h]}</td>
                </tr>
                `);
            }
            const body=save.formbody;
            for(let b of body){
                if(Object.keys(b).indexOf('ans')===-1) b.ans='×';
                if(Object.keys(b).indexOf('cmt')===-1) b.cmt='×';
                const line='reporttable_even';
                ssl.find('tbody#questions').append(`
                <tr class=${line}>
                    <td><input type=checkbox name=questions></td>
                    <td>${b.qid}</td>
                    <td>${b.qtx}</td>
                    <td>${b.ans}</td>
                    <td>${b.cmt}</td>
                </tr>
                `);
            }
            if(ssl.find('thead#questions input').is(':checked')){
                ssl.find('tbody#questions input').prop('checked',true);
            }else{
                ssl.find('tbody#questions input').prop('checked',false);
            }
            ssl.find('button#Show').text('←');
            ssl.find('button#Save,button#Dele,div#tab,[id$=saves]').hide();
            ssl.find('table #questions').show();
            theadWidth(ssl.find('thead#questions'),ssl.find('tbody#questions'));//在显示后同步宽度,隐藏时会默认为auto

        }else{
            ssl.find('tbody#questions').empty();
            ssl.find('button#Show').text('预览');
            ssl.find('table #questions').hide();
            ssl.find('button#Save,button#Dele,div#tab,#saves,tbody#'+$('input[name=tab]:checked').val()).show();
        }
    }

    //读档
    function loadData(ssl){
        const [save]=checkSave(ssl);
        if(!save) return;
        //如当前报告id与存档报告id不相等,询问是否执行
        if(save.surveyid!=unsafeWindow.getSurveyInstanceID()){
            //将文本,是否确认,确认后执行的方法传给feedback(ssl,text,ifConfirm,yesFunc)生成确认提示
            feedback(
                ssl,
                '报告ID不同,仅会修改当前报告中和存档中相匹配的题目,是否继续?',
                1,
                ()=>{execLoadData(ssl,save);}
            );
        }else{
            execLoadData(ssl,save);
        }
    }

    //执行读档
    function execLoadData(ssl,save){
        const qidArr=[];
        if(ssl.find('thead#questions').css('display')!='none'){
            if(ssl.find('input[name=questions]:checked').length===0){
                feedback(ssl,'请勾选题目!');
                return;
            }
            ssl.find('input[name=questions]:checked').each(function(){
                qidArr.push($(this).parent().next().text());
            });
        }
        const setAnswer=unsafeWindow.sm_setmultipleanswer;
        const setComment=unsafeWindow.sm_setcomment;
        const head=save.formhead;
        let count=0;
        for(let h in head){
            if(qidArr.length&&qidArr.indexOf(h)<0) continue;
            if(h==='DATE'){
                if(head[h]===''){
                    $('select#dsYear').children(':first').prop('selected',true);
                    $('select#dsMonth').children(':first').prop('selected',true);
                    $('select#dsDay').children(':first').prop('selected',true);
                }else{
                    const date=head[h].split('-');
                    $('select#dsYear').children('[value='+date[0]+']').prop('selected',true);
                    $('select#dsMonth').children('[value='+date[1]+']').prev().prop('selected',true);
                    $('select#dsDay').children('[value='+date[2]+']').prev().prop('selected',true);
                }
            }
            if(h==='TIME'){
                if(head[h]===''){
                    $('select#tsHoursHEAD_TIME').children(':first').prop('selected',true);
                    $('select#tsMinutesHEAD_TIME').children(':first').prop('selected',true);
                }else{
                    const time=head[h].split(':');
                    $('select#tsHoursHEAD_TIME').children('[value='+time[0]+']').prop('selected',true);
                    $('select#tsMinutesHEAD_TIME').children('[value='+time[1]+']').prop('selected',true);
                }
            }
            if(h==='TIMEOUT'){
                if(head[h]===''){
                    $('select#tsHoursHEAD_TIMEOUT').children(':first').prop('selected',true);
                    $('select#tsMinutesHEAD_TIMEOUT').children(':first').prop('selected',true);
                }else{
                    const time=head[h].split(':');
                    $('select#tsHoursHEAD_TIMEOUT').children('[value='+time[0]+']').prop('selected',true);
                    $('select#tsMinutesHEAD_TIMEOUT').children('[value='+time[1]+']').prop('selected',true);
                }
            }
            if($('input[name=HEAD_'+h+']').length){
                $('input[name=HEAD_'+h+']').val(head[h]);
                count++;
            }
        }
        const body=save.formbody;
        for(let b of body){
            if(qidArr.length&&qidArr.indexOf(b.qid)<0||$('textarea#C'+b.qid+'C').length===0) continue;//跨问卷读档时跳过没有的题防止updrowH()报错
            const key=Object.keys(b);
            console.log(b);
            if(key.indexOf('ans')>=0) setAnswer(b.qid,b.ans);
            if(key.indexOf('cmt')>=0){
                setComment(b.qid,b.cmt);
                unsafeWindow.updrowH(document.querySelector('textarea#C'+b.qid+'C'));//调整评论框高度
            }
            count++;
        }
        feedback(ssl,'读档成功。(共修改'+count+'题)');
    }


    //存档
    function saveData(ssl,toSave){
        const save={
            'surveytitle':$('h1.surveytitle').text(),
            'surveyid':unsafeWindow.getSurveyInstanceID(),
            'formhead':{
                'LOCATION':$('input[name=HEAD_LOCATION]').val()||$('table.visualization_HEAD_LOCATION').find('lookup').text().split(' - ')[0],
                'SHOPPER':$('input[name=HEAD_SHOPPER]').val(),
                'DATE':$('input[name=HEAD_DATE]').val(),
                'TIME':$('input[name=HEAD_TIME]').val(),
                'TIMEOUT':$('input[name=HEAD_TIMEOUT]').val()
            },
            'formbody':[]
        };
        const getAnswer=unsafeWindow.sm_getmultipleanswer;
        const getComment=unsafeWindow.sm_getcomment;
        $('a.surveyquestionnobreak').each(function(){
            const qtx=$(this).find('span.surveyquestion').text();
            const qid=$(this).attr('name').replace('qstn','');
            const ans= $('input[name=Q'+qid+'Q]').length? getAnswer(qid) : undefined;
            //ans 已填写:'1,2'||'__na__';未填写:'';无选项列:undefined
            const cmt= getComment(qid);
            //cmt 已填写:'xxx';未填写:'';无评论框:undefined
            save.formbody.push({
                'qtx':qtx,
                'qid':qid,
                'ans':ans,
                'cmt':cmt
            });
        });
        const saves=GM_getValue(toSave,{});
        saves[new Date().toLocaleString()]=save;
        //存档超过10个覆盖最早存档
        if(Object.keys(saves).length>10) delete saves[Object.keys(saves)[0]];
        GM_setValue(toSave,saves);
        refreshList(ssl,toSave);
        if(toSave==='autosaves') feedback(ssl,'自动存档成功。');
        if(toSave==='selfsaves') feedback(ssl,'手动存档成功。');
    }

    //删除
    function deleteData(ssl){
        const [save,time,saves,fromSave]=checkSave(ssl);
        if(!save) return;
        delete saves[time];
        GM_setValue(fromSave,saves);
        refreshList(ssl);
        feedback(ssl,'删除成功。');

    }

    //检查存档有效性
    function checkSave(ssl){
        if(ssl.find('input[name=saves]:checked').length===0){
            feedback(ssl,'请选择存档!');
            return [];
        }
        const fromSave=ssl.find('input[name=saves]:checked').parents('tbody').attr('id');
        const saves=GM_getValue(fromSave,{});
        const time=ssl.find('input[name=saves]:checked').parent().next().text();
        if(Object.keys(saves).indexOf(time)===-1){
            feedback(ssl,'无此存档!(可能已被覆盖)');
            return [];
        }
        return [saves[time],time,saves,fromSave];
    }

    //操作反馈提示
    function feedback(ssl,text,ifConfirm,yesFunc){
        if(ssl.css('display')==='none') return;//界面隐藏时不提示
        if(ssl.find('b#feedback').length===0){
            ssl.find('div#tab').before('<b id=feedback style=margin:5px></b>');
        }
        const b=ssl.find('b#feedback');
        b.text(text).append('<br>');
        if(ifConfirm){
            $('<input type=button value=√ class="rm-btn rm-btn-default">').appendTo(b)
                .on('click',function(){
                $(this).parents('b#feedback').remove();
                yesFunc();
            });
            $('<input type=button value=× class="rm-btn rm-btn-default">').appendTo(b)
                .on('click',function(){
                $(this).parents('b#feedback').remove();
            });
            b.children('input:button').css('margin','5px');
        }else{
            setTimeout(()=>{
                b.remove();
            },3000);
        }
    }

    //将表头宽度同步为表身
    function theadWidth(thead,tbody){
        if(tbody.find('td').text()==='无数据!') return;
        thead.find('tr td').each(function(){
            const i=$(this).index();
            const w=getComputedStyle(tbody.children('tr:last').children()[i]).width;
            $(this).css('width',w);
        });
    }
    /*报告存档*/

    /*卸载报告存档*/
    function unSURVEYSAVES(){
        if($('div#surveySaves').length) $('div#surveySaves').remove();
        if(document.location.href.indexOf('alias=survey.view')>=0){
            $('form#frmSurvey').off('click.autosave');
            $('textarea.surveycomment,textarea.active').off('blur.autosave');
        }
    }
    /*卸载报告存档*/

    /*PDF命名*/
    function PDFRENAME(){
        if(document.location.href.indexOf('exportMultiplePDF.asp')<0) return;
        $('table#pdfconvertProcessTableAdv tr.reporttable_thead').append('<th id=downloadAll>');
        $('<button style=font-size:10px;width:100px>命名并下载全部</button>').appendTo('table#pdfconvertProcessTableAdv th#downloadAll')
            .on('click',function(){
            $(this).parent().parent().nextAll('tr').find('button').click();
        });
        $('table#pdfconvertProcessTableAdv a[id^=dlLinkAdv]').each(function(){
            const a=$(this);
            const surveyid=a.attr('id').replace('dlLinkAdv','');
            a.parent().after('<td id=download>');
            const td=a.parent().next('td#download');
            $('<button style=font-size:10px;width:100px>命名并下载</button>').appendTo(td)
                .on('click',function(){
                if(a.attr('href')==='/surveyexport_pdf/done.asp?docID='){
                    $(this).text('请等待转化完成');
                    setTimeout(()=>{
                        $(this).text('命名并下载');
                    },1000);
                }else{
                    $(this).text('下载中...');
                    let name=$('textarea#naming_convention').val();
                    const field=new Set(name.match(/(?<=#\$FIELD\[)[A-Za-z0-9\._]+(?=\]\$#)/g));
                    const fieldDict={
                        'DateSubmitted':'SurveyDateOrDueDate',
                        'TimeSubmitted':'SurveyDateOrDueDate',
                        'Campaign':'CampaignName',
                        'CustomProperty001':'LocationCustomPropertyValue001',
                        'CustomProperty002':'LocationCustomPropertyValue002',
                        'CustomProperty003':'LocationCustomPropertyValue003',
                        'CustomProperty004':'LocationCustomPropertyValue004',
                        'CustomProperty005':'LocationCustomPropertyValue005',
                        'CustomProperty006':'LocationCustomPropertyValue006',
                        'CustomProperty007':'LocationCustomPropertyValue007',
                        'CustomProperty008':'LocationCustomPropertyValue008',
                        'CustomProperty009':'LocationCustomPropertyValue009',
                        'CustomProperty010':'LocationCustomPropertyValue010',
                        'ExportServicePointsScored':'PrecalcScore',
                        'ExportServicePointsPossible':'PrecalcScoreOutOf'
                    };
                    const qSet=new Set();
                    for(let i of field){
                        if(i.indexOf('ServiceScorePercentXX')>0){
                            qSet.add('PrecalcScore');
                            qSet.add('PrecalcScoreOutOf');
                            continue;
                        }
                        if(fieldDict[i]) i=fieldDict[i];
                        qSet.add(i);
                    }
                    let query='';
                    for(let i of qSet) query+='['+i+'],';
                    query=query.slice(0,-1);
                    $.get(`
                    /open/data.asp?post={
                        "action":"exec",
                        "dataset":{"datasetname":"/Apps/SM/APIv2/Query/Operations/Operations"},
                        "parameters":[
                            {"name":"QuerySpecification","value":"${query}"},
                            {"name":"SurveyInstanceIDs","value":"${surveyid}"}
                        ]
                    }`,data=>{//调用API获取当前survey数据[Query→Operations]
                        const info=data.dataset.data[0][0];
                        for(let i of field){
                            const tmp=i;
                            if(fieldDict[i]) i=fieldDict[i];
                            let r;
                            if(tmp.indexOf('Submitted')>0){
                                if(tmp==='DateSubmitted') r=info[i].split(' ')[0];
                                if(tmp==='TimeSubmitted') r=info[i].split(' ')[1];
                            }else if(tmp.indexOf('ServiceScorePercentXX')>0){
                                r=Number(info.PrecalcScore)/Number(info.PrecalcScoreOutOf)*100;
                                if(tmp==='ExportServiceScorePercentXX') r=r.toFixed(0)+'%';
                                if(tmp==='ExportServiceScorePercentXX.X') r=r.toFixed(1)+'%';
                                if(tmp==='ExportServiceScorePercentXX.XX') r=r.toFixed(2)+'%';
                            }else{
                                r=info[i];
                            }
                            name=name.replaceAll(`#$FIELD[${tmp}]$#`,r);
                        }
                        download(a.attr('href'),name+'.pdf',()=>{$(this).text('命名并下载〇');});
                    },'json');
                }
            });
        });
    }

    function download(url,filename,done){//done()为执行完毕后执行的函数
        getBlob(url,function(blob){
            saveAs(blob,filename,done);
        });
    }

    function getBlob(url,cb){
        const xhr=new XMLHttpRequest();
        xhr.open('GET',url,true);
        xhr.responseType='blob';
        xhr.onload=function(){
            if (xhr.status === 200){
                cb(xhr.response);
            }
        };
        xhr.send();
    }

    function saveAs(blob,filename,done){
        if (window.navigator.msSaveOrOpenBlob){//Firefox&IE
            navigator.msSaveBlob(blob,filename);
        }else{
            const link=document.createElement('a');
            const body=document.querySelector('body');
            link.href=window.URL.createObjectURL(blob);
            link.download=filename;
            link.style.display='none';
            body.appendChild(link);
            link.click();
            body.removeChild(link);
            window.URL.revokeObjectURL(link.href);
        }
        done();
    }
    /*PDF命名*/

    /*卸载PDF命名*/
    function unPDFRENAME(){
        if(document.location.href.indexOf('exportMultiplePDF.asp')<0) return;
        $('table#pdfconvertProcessTableAdv th#downloadAll').remove();
        $('table#pdfconvertProcessTableAdv td#download').remove();
    }
    /*卸载PDF命名*/


})();