minerva-online assistant

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

当前为 2021-11-10 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name minerva-online assistant
  3. // @namespace https://space.bilibili.com/17846288
  4. // @version 2.5.1
  5. // @description 此脚本能更方便使用minerva-online平台,可在顶端菜单栏右下角的按钮处设置功能开关,并查看功能详情
  6. // @author inoki
  7. // @match https://www.minerva-online.com/*
  8. // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_xmlhttpRequest
  12. // @noframes
  13. // ==/UserScript==
  14.  
  15. /*jshint esversion: 9*/
  16.  
  17. (()=>{
  18. 'use strict';
  19.  
  20. const $=window.$;
  21. const SET=[
  22. {
  23. 'id':0,
  24. 'name':'置顶置底',
  25. 'func':()=>{GOTOPBOTTOM();},
  26. 'unfunc':()=>{unGOTOPBOTTOM();},
  27. 'detail':
  28. `在平台域名所有可滚动页面生效,页面右下方添加置顶置底按钮<br>
  29. 按钮会根据页面滚动方向自动切换置顶和置底,按钮样式可在代码中自定义中修改`,
  30. 'switch':1,
  31. },
  32. {
  33. 'id':1,
  34. 'name':'菜单遮罩',
  35. 'func':()=>{COVERMENU();},
  36. 'unfunc':()=>{unCOVERMENU();},
  37. 'detail':
  38. `在有顶端菜单栏的页面生效<br>
  39. 让菜单栏需要点击一次后才可展开,防止鼠标经过时误触 (默认关闭)`,
  40. 'switch':0,
  41. },
  42. {
  43. 'id':2,
  44. 'name':'附件下载',
  45. 'func':()=>{DOWNLOADFILE();},
  46. 'unfunc':()=>{unDOWNLOADFILE();},
  47. 'detail':
  48. `在问卷管理页面生效,每份报告前添加↓按钮<br>
  49. 点击↓加载附件列表,点击√下载全部附件,点击附件名下载单个附件,鼠标悬停可预览图片`,
  50. 'switch':1,
  51. },
  52. {
  53. 'id':3,
  54. 'name':'扣分标记',
  55. 'func':()=>{MARKQUESTION();},
  56. 'unfunc':()=>{unMARKQUESTION();},
  57. 'detail':
  58. `在单店报告页面生效<br>
  59. 将题目中勾选扣分和n/a项标色,选项更改后需保存报告才会刷新标记,方便快速检查相关题评论<br>
  60. 可在简介上方设置扣分(默认为红)和N/A(默认为绿)的标记颜色,点击√保存更改,保存后关开此功能可在报告页面即时刷新颜色`,
  61. 'switch':1,
  62. },
  63. {
  64. 'id':4,
  65. 'name':'评论编辑',
  66. 'func':()=>{COMMENTEDIT();},
  67. 'unfunc':()=>{unCOMMENTEDIT();},
  68. 'detail':
  69. `在单店报告页面生效,右下方按钮展开操作界面<br>
  70. 使用前请注意阅读操作界面最上方的【点击获取提示】<br>
  71. 输入匹配内容(支持正则)会即时显示匹配的评论框数量并标灰,点击一键替换可批量修改所有评论框内容<br>
  72. 点击首字母大写可智能将所有句首字母变为大写,并标灰发生过修改的评论框,只对英文生效<br>
  73. 点击评论翻译会调用百度翻译(由于需要跨域请求,弹出提示后选“总是允许”),在每个评论框下方输出目标语言翻译,点击↑可将下方内容添加至评论框`,
  74. 'switch':1,
  75. },
  76. {
  77. 'id':5,
  78. 'name':'验证输出',
  79. 'func':()=>{VERIFYEXPORT();},
  80. 'unfunc':()=>{unVERIFYEXPORT();},
  81. 'detail':
  82. `在问卷管理页面生效,表头上方添加按钮<br>
  83. 点击按钮弹出确认提示,确认后会验证输出当前页面勾选的所有报告,成功后会在每份报告下方小窗口显示绿色保存成功提示<br>
  84. (电脑配置较低时一次输出太多份可能导致页面卡死,请根据浏览器最多同时能开几个报告页面量力而行,默认关闭)`,
  85. 'switch':0,
  86. },
  87. ];
  88.  
  89.  
  90. /*在顶端菜单栏添加MOassist设置按钮*/
  91. const menu=$('div#menu');
  92. if(menu.length){
  93. menu.find('ul.tools').append(`
  94. <li class="MOassist">
  95. <a class="toolsLink">
  96. <div class="iconTools" style="background: url('/images/icons/menu/x16/tools-settings.png')"></div>
  97. <ul class="textArea" style="visibility:visible; display:none">
  98. <li>MO助手设置</li>
  99. </ul>
  100. <ul id="MOoption" class="innerItemFirst" style="z-index: 11; display:none; top:25px; right:0px"></ul>
  101. </a>
  102. </li>
  103. `);
  104. menu.find('li.MOassist').on('click',function(){
  105. MOListSwitch(this);
  106. }).find('ul#MOoption').on('click',e=>{
  107. e.stopPropagation();//让之后添加的功能列表不继承click事件
  108. });
  109. //导入所有功能列表并显示开关状态
  110. for(let i in SET){
  111. menu.find('ul#MOoption').append(`
  112. <li id="MOoptions" class="MOassist" style="width:100%">
  113. <div class="menuItemText" style="color:#4C5057">${SET[i].name}</div>
  114. <input type=checkbox id=${SET[i].id} class=menuIconSmall></input>
  115. <ul class="textArea" style="visibility:visible; display: none; margin-top:0px; right:120px">
  116. <li style="padding:0px 5px !important">${SET[i].detail}</li>
  117. </ul>
  118. </li>
  119. `);
  120. //运行开启状态的功能并打勾
  121. if(GM_getValue(SET[i].name,SET[i].switch)){
  122. SET[i].func();
  123. menu.find('input#'+SET[i].id).attr('checked',true);
  124. }
  125. }
  126. //根据是否选中即时启用或卸载功能并记录开关状态
  127. menu.find('li#MOoptions').on('click',function(){
  128. const checkbox=$(this).children('input:checkbox');
  129. const id=$(checkbox).attr('id');
  130. if($(checkbox).attr('checked')){
  131. SET[id].unfunc();
  132. $(checkbox).attr('checked',false);
  133. GM_setValue(SET[id].name,0);
  134. }
  135. else{
  136. SET[id].func();
  137. $(checkbox).attr("checked",true);
  138. GM_setValue(SET[id].name,1);
  139. }
  140. }).children('ul.textArea').on('click',e=>{
  141. e.stopPropagation();
  142. });
  143. //鼠标聚焦时显示详情 【https://www.minerva-online.com/portal/menu/js/v2/menuRender.js?version=21-08 createToolOption : 】
  144. menu.find('li.MOassist').hover(function(){
  145. $(this).find('ul:first').stop().show(200);
  146. },function(){
  147. $(this).find('ul:first').stop().hide(200);
  148. });
  149. setMarkQuestionColor(menu);
  150. }
  151. else{
  152. for(let i in SET) if(GM_getValue(SET[i].name,SET[i].switch)) SET[i].func();//没有menu也执行开启的功能
  153. }
  154.  
  155. //功能列表开关
  156. function MOListSwitch(self){
  157. const on=$(self).find('ul#MOoption').css('display');
  158. if(on==='none'){
  159. $(self).find('ul#MOoption').stop().slideDown(200);
  160. }
  161. else{
  162. $(self).find('ul#MOoption').stop().slideUp(200);
  163. }
  164. }
  165.  
  166. //添加扣分标记颜色设置界面
  167. function setMarkQuestionColor(menu){
  168. $(menu).find('input#3.menuIconSmall').next('ul').prepend(`
  169. <div style="padding:0px 5px">
  170. <b id=de>扣分颜色:</b>
  171. <form id=de>
  172. <input type=radio name=de value=red>红</input>
  173. <input type=radio name=de value=orange>橙</input>
  174. <input type=radio name=de value=yellow>黄</input>
  175. <input type=radio name=de value=green>绿</input>
  176. <input type=radio name=de value=blue>蓝</input>
  177. <input type=radio name=de value=purple>紫</input>
  178. <input type=radio name=de value=custom>自定义</input>
  179. <input type=color class=selectedColor></input>
  180. <input type=button class=rm-btn value=√ style="padding:1px 6px"></input>
  181. </form>
  182. <b id=na>N/A颜色:</b>
  183. <form id=na>
  184. <input type=radio name=na value=red>红</input>
  185. <input type=radio name=na value=orange>橙</input>
  186. <input type=radio name=na value=yellow>黄</input>
  187. <input type=radio name=na value=green>绿</input>
  188. <input type=radio name=na value=blue>蓝</input>
  189. <input type=radio name=na value=purple>紫</input>
  190. <input type=radio name=na value=custom>自定义</input>
  191. <input type=color class=selectedColor></input>
  192. <input type=button class=rm-btn value=√ style="padding:1px 6px"></input>
  193. </form>
  194. </div>
  195. `);
  196. //颜色选项初始化
  197. $('form#de,form#na').each(function(){
  198. const curColor= $(this).attr('id')==='de'?
  199. GM_getValue($(this).prev().text(),'red') : GM_getValue($(this).prev().text(),'green');
  200. $(this).prev().css('color',curColor);
  201. $(this).children('.selectedColor').attr('id',curColor);
  202. if(curColor.indexOf('#')<0){
  203. $(this).children('input[value='+curColor+']').attr('checked',true);
  204. $(this).children('.selectedColor').hide();
  205. }
  206. else{
  207. $(this).children('input[value=custom]').attr('checked',true);
  208. $(this).children('.selectedColor').val(curColor);
  209. }
  210. });
  211. //点击选项颜色改变
  212. $('form#de,form#na').children(':radio').on('click',function(){
  213. if($(this).val()==='custom'){
  214. $(this).next().show();
  215. $(this).next().attr('id',$(this).next().val());
  216. }
  217. else{
  218. $(this).nextAll('.selectedColor').hide();
  219. $(this).nextAll('.selectedColor').attr('id',$(this).val());
  220. }
  221. $(this).parent().prev().css('color',$(this).nextAll('.selectedColor').attr('id'));
  222. });
  223. //自定义颜色改变
  224. $('form#de,form#na').children('.selectedColor').on('input',function(){
  225. $(this).attr('id',$(this).val());
  226. $(this).parent().prev().css('color',$(this).val());
  227. });
  228. //确认更改
  229. $('form#de,form#na').children(':button').on('click',function(){
  230. GM_setValue($(this).parent().prev().text(),$(this).prev().attr('id'));
  231. if(!$(this).next().is('b')){
  232. $(this).after('<b>保存成功</b>');
  233. setTimeout(()=>{
  234. $(this).next().remove();
  235. },3000);
  236. }
  237. });
  238. }
  239. /*在顶端菜单栏添加MOassist设置按钮*/
  240.  
  241.  
  242. /*置顶置底*/
  243. function GOTOPBOTTOM(){
  244. const scrollBar=$(document).height()>(window.innerHeight+1||document.documentElement.clientHeight);//如有滚动条
  245. if(scrollBar&&document.location.href.indexOf('alias=knowledgebase')===-1){//knowledgebase页面自带置顶按钮,不启用
  246. const goTopBottomButton=document.createElement('div');
  247. const toggleButton=document.createElement('img');
  248. $(toggleButton).appendTo(goTopBottomButton);
  249. $(goTopBottomButton).appendTo($('body')[0]);
  250. $(goTopBottomButton).css({'position':'fixed','zIndex':10000}).attr('id','goTopBottom');
  251. $(toggleButton).css({'display':'block','cursor':'pointer'}).attr('src','/knowledgebase/images/arrow_back_to_top.svg');//按钮显示图片(向下箭头)
  252.  
  253. //以下按钮参数可自定义修改
  254. goTopBottomButton.style.bottom='50px';//按钮距离网页底部50px
  255. goTopBottomButton.style.right='30px';//按钮距离网页右边30px
  256. toggleButton.style.width='25px';//按钮图片宽25px
  257. toggleButton.style.height='25px';//按钮图片高25px
  258. toggleButton.style.opacity=0.5;//按钮不透明度,0.0(完全透明)到1.0(完全不透明)
  259. toggleButton.style.backgroundColor='grey';//按钮背景颜色,也可使用在excel等软件的自定义颜色界面的16进制代码
  260. const clickScrollTime=500;//点击按钮时,网页滚动到顶部或底部需要的时间,单位是毫秒
  261.  
  262. //点击按钮时网页滚动到顶部或底部
  263. let scrollDirection='down';
  264. toggleButton.addEventListener('click',()=>{
  265. if(scrollDirection==='up'){
  266. $('html,body').animate({scrollTop:'0px'},clickScrollTime);
  267. }
  268. else{
  269. $('html,body').animate({scrollTop:$(document).height()},clickScrollTime);
  270. }
  271. });
  272. //页面滚动监听
  273. let scrollAction=window.pageYOffset;
  274. $(window).scroll(()=>{
  275. const diffY=scrollAction-window.pageYOffset;
  276. scrollAction=window.pageYOffset;
  277. scrollDirection= diffY<0? 'down' : 'up';
  278. toggleButton.style.transform= diffY<0? 'rotate(0deg)' : 'rotate(180deg)';
  279. if(getScrollTop()===0){
  280. scrollDirection='down';
  281. toggleButton.style.transform='rotate(0deg)';
  282. }
  283. if(getScrollTop()+window.innerHeight+20>=$(document).height()){
  284. scrollDirection='up';
  285. toggleButton.style.transform='rotate(180deg)';
  286. }
  287. });
  288. }
  289. }
  290.  
  291. //获取垂直方向滑动距离
  292. function getScrollTop(){
  293. let scrollTop=0;
  294. if(document.documentElement&&document.documentElement.scrollTop){
  295. scrollTop=document.documentElement.scrollTop;
  296. }
  297. else if(document.body){
  298. scrollTop=document.body.scrollTop;
  299. }
  300. return scrollTop;
  301. }
  302. /*置顶置底*/
  303.  
  304. /*卸载置顶置底*/
  305. function unGOTOPBOTTOM(){
  306. if($('div#goTopBottom').length) $('div#goTopBottom').remove();
  307. }
  308. /*卸载置顶置底*/
  309.  
  310.  
  311. /*菜单遮罩*/
  312. function COVERMENU(){
  313. const menu=$('div#menu');
  314. if(menu.length){
  315. //若存在menu则添加cover层
  316. const cover = document.createElement('div');
  317. cover.className = 'layout';
  318. cover.style = 'top:'+menu[0].style.top+';opacity:0.3;z-index:10000;right:10%';
  319. $(cover).appendTo($('body')[0]).attr('id','cover');
  320. //点击时将cover层下置
  321. cover.addEventListener('click',()=>{
  322. cover.style.zIndex = -1;
  323. });
  324. //离开menu时cover层还原
  325. menu[0].addEventListener('mouseleave',()=>{
  326. cover.style.zIndex = 10000;
  327. });
  328. //cover层位置跟随menu 【https://www.minerva-online.com/portal/menu/js/v2/menuRender.js?version=21-08 onScrollEventHandler : 】
  329. $(window).scroll(()=>{
  330. const SM=unsafeWindow.SM;
  331. const ind = SM.ui.headerHeight - SM.ui.getScrollTop();
  332. cover.style.top= ind>0? ind+'px' : '0px';
  333. });
  334. }
  335. }
  336. /*菜单遮罩*/
  337.  
  338. /*卸载菜单遮罩*/
  339. function unCOVERMENU(){
  340. if($('div#cover').length) $('div#cover').remove();
  341. }
  342. /*卸载菜单遮罩*/
  343.  
  344.  
  345. /*附件下载*/
  346. function DOWNLOADFILE(){
  347. if (document.location.href.indexOf('alias=smngr.surveyexplorer')>=0&&$('tr.persist-header').length){
  348. $('tr.persist-header').each(function(){
  349. $(this).children().first().after($(this).children().first().clone(true));
  350. });
  351. $('div.sticky-wrap').find(':checkbox').each(function(){//checkbox后添加下载按钮
  352. const surveyid=$(this).val();
  353. $(this).parent().after('<td><button type=button id='+surveyid+' class="download rm-btn"><b>↓</td>');
  354. $('#'+surveyid+'.download').one('click',()=>{
  355. DownloadButton(surveyid);
  356. });
  357. });
  358. }
  359. }
  360.  
  361. //获取附件列表
  362. function DownloadButton(surveyid){
  363. $('button#'+surveyid+'.download').hide();
  364. $('button#'+surveyid+'.download').after('<p id='+surveyid+' class=loading><b>......');
  365. $.get('/open/data.asp?post={"action":"exec","dataset":{"datasetname":"/Apps/SM/Survey/SurveyInstanceGetData"},"parameters":[{"name":"SurveyInstanceID","value":"'+surveyid+'"}]}',(data,status)=>{//调用API获取当前survey数据[SurveyInstanceGetData]
  366. if (status==='success'){
  367. const filedata=data.dataset.data[3];
  368. const fileno=filedata.length;
  369. $('p#'+surveyid+'.loading').after('<ol id='+surveyid+' class=filelist>\t#='+fileno+'');
  370. if (fileno>0){
  371. for(let i in filedata){
  372. const filename=filedata[i].FileName+'.'+filedata[i].FileExtension;
  373. const fileid=filedata[i].AttachmentID;
  374. const fileurl='/mystservices/Attachments/getAttachment.asp?Attachment='+fileid+'&Password='+filedata[i].Password+'';
  375. let filesize=Number(filedata[i].FileSizeInBytes)/1024;
  376. filesize= (filesize>1024)? (filesize/1024).toFixed(2)+' MB' : filesize.toFixed(2)+' KB';
  377. $('<tr id='+fileid+'>').appendTo('ol#'+surveyid+'.filelist');
  378. $(`<td><li><a id=${surveyid} class='${filedata[i].AttachmentType} mailboxlink' href=${fileurl}>${filename}</a>`).appendTo('tr#'+fileid);
  379. $('<td>'+filesize+'</td>').appendTo('tr#'+fileid);
  380. }
  381. $('a#'+surveyid+'.I,a#'+surveyid+'.V').mouseenter(function(){
  382. FilePreview(1,$(this).attr('href'));
  383. }).mouseleave(()=>{
  384. FilePreview(0);
  385. });
  386. $('ol#'+surveyid+'.filelist').prepend('<button type=button id='+surveyid+' class="yes rm-btn rm-btn-default">√</button>');
  387. $('button#'+surveyid+'.yes').on('click',()=>{
  388. DownloadAll(surveyid);
  389. });
  390. DownloadButton0(surveyid);
  391. }
  392. else {
  393. DownloadButton0(surveyid);
  394. }
  395. }
  396. else {
  397. DownloadButton0(surveyid);
  398. }
  399. },"json");
  400. }
  401.  
  402. //预览附件图片
  403. function FilePreview(show,src){
  404. if(show){
  405. const imgid=src.split('=')[2];
  406. if($('img#'+imgid+'.filepreview').length===0){
  407. $('<div><img id='+imgid+' class=filepreview>').appendTo('body');
  408. $('img#'+imgid+'.filepreview').attr('src',src+'&getThumbnail=1').css('height','200px')//视频附件预览图&getThumbnail=1
  409. .parent().css({'position':'fixed','zIndex':10000,'height':'200px','background':'url(/images/icons/filtersv2/loading06.gif)'});
  410. }
  411. $('img#'+imgid+'.filepreview').parent().css({'top':event.clientY-200+'px','left':event.clientX+100+'px'});
  412. $('img#'+imgid+'.filepreview').show();
  413. }
  414. else{
  415. $('img.filepreview').hide();
  416. }
  417. }
  418.  
  419. //按钮变为关闭
  420. function DownloadButton0(surveyid){
  421. $('p#'+surveyid+'.loading').remove();
  422. $('button#'+surveyid+'.download').one('click',()=>{
  423. DownloadButton1(surveyid);
  424. });
  425. $('button#'+surveyid+'.download').text('×');
  426. $('button#'+surveyid+'.download').show();
  427. }
  428.  
  429. //按钮重置为初始
  430. function DownloadButton1(surveyid){
  431. $('ol').remove('#'+surveyid);
  432. $('button#'+surveyid+'.download').one('click',()=>{
  433. DownloadButton(surveyid);
  434. });
  435. $('button#'+surveyid+'.download').text('↓');
  436. }
  437.  
  438. //下载全部
  439. function DownloadAll(surveyid){
  440. $('button#'+surveyid+'.yes').hide();
  441. const iframe=$('ol#'+surveyid+'.filelist').find('iframe');
  442. if(iframe.length)iframe.remove();
  443. setTimeout(()=>{
  444. $('button#'+surveyid+'.yes').show();
  445. },1000*$('ol#'+surveyid+'.filelist').find('a').length);//有几个附件就隐藏按钮几秒
  446. $('ol#'+surveyid+'.filelist').find('a').each(function(){
  447. $('<iframe src='+$(this).attr('href')+'>').appendTo(this).hide();
  448. });
  449. $('button#'+surveyid+'.yes').text('〇');
  450. }
  451. /*附件下载*/
  452.  
  453. /*卸载附件下载*/
  454. function unDOWNLOADFILE(){
  455. if (document.location.href.indexOf('alias=smngr.surveyexplorer')>=0&&$('tr.persist-header').length){
  456. $('tr.persist-header').each(function(){
  457. $(this).children().first().remove();
  458. });
  459. $('button.download').each(function(){
  460. $(this).parent().remove();
  461. });
  462. }
  463. }
  464. /*卸载附件下载*/
  465.  
  466.  
  467. /*扣分标记*/
  468. function MARKQUESTION(){
  469. if(document.location.href.indexOf('alias=survey.view')>=0){
  470. $('span.surveyansweroption').each(function(){
  471. if($(this).prev('input').is(':checked')){
  472. if($(this).prev('input').val()==='__na__'){
  473. $(this).css('color',GM_getValue('N/A颜色:','green'));//默认标绿N/A项
  474. }
  475. }
  476. });
  477. //获取所有扣分的题目
  478. const qidmark=[];
  479. $.get('/mystservices/v2new/getSurvey.asp?InstanceID='+$('input#instanceID').val(),(data,status)=>{
  480. if (status==='success'){
  481. $(data).find('nobr').each(function(){
  482. const score=$(this).text();
  483. if(score!=''&&score.indexOf('%')===-1){//排除空值与section总分
  484. const pts=score.split('/');
  485. if(pts[0]<pts[1]){
  486. const QidANS=$(this).parent().parent().parent().parent().parent('td.surveyquestioncell').prev().find('div').attr('id');
  487. qidmark.push(QidANS);
  488. }
  489. }
  490. });
  491. for(let i in qidmark){
  492. $('div#'+qidmark[i]).find('span.surveyansweroption').css('color',GM_getValue('扣分颜色:','red'));//默认标红扣分项
  493. }
  494. }
  495. });
  496. }
  497. }
  498. /*扣分标记*/
  499.  
  500. /*卸载扣分标记*/
  501. function unMARKQUESTION(){
  502. if(document.location.href.indexOf('alias=survey.view')>=0) $('span.surveyansweroption').removeAttr('style');
  503. }
  504. /*卸载扣分标记*/
  505.  
  506.  
  507. /*评论编辑*/
  508. function COMMENTEDIT(){
  509. if(document.location.href.indexOf('alias=survey.view')>=0){
  510. $('<div id=commentEdit>').appendTo($('body')[0])
  511. .css({'position':'fixed','zIndex':10000,
  512. 'right':'30px','bottom':'80px','height':'25px','width':'25px',
  513. 'background':'url("/images/icons/menu/x32/survet.png") 100%/100% #4C5157'})
  514. .on('click',()=>{
  515. commentEditSwitch();
  516. });
  517. $('<div id=commentFunc>').appendTo('div#commentEdit')
  518. .css({'position':'fixed','right':'60px','bottom':'50px'}).hide()
  519. .on('click',e=>{
  520. e.stopPropagation();//阻止子元素执行父元素click事件
  521. });
  522. //提示开关
  523. $('<b id=hint>【点击获取提示】</b>').appendTo('div#commentFunc');
  524. $('b#hint').on('click',function(){
  525. hintSwitch(this);
  526. });
  527. //评论匹配与替换
  528. $('<button type=button id=replaceAll class=surveyBottomButton>一键替换</button>').appendTo('div#commentFunc');
  529. $('button#replaceAll')
  530. .before('<textarea id=find placeholder=匹配内容></textarea><b id=commentMark>↓↓↓</b><textarea id=replace placeholder=替换内容></textarea>')
  531. .before('<b id=findNum>#</b>');
  532. $('textarea#find,textarea#replace').on('keydown',e=>{
  533. e.stopPropagation();//阻止页面自带keydown事件修改textarea的class值
  534. });
  535. $('b#commentMark').on('click',()=>{
  536. commentMark();
  537. });
  538. $('textarea#find').on('input',function(){
  539. commentMatch(this);
  540. });
  541. $('button#replaceAll').on('click',()=>{
  542. commentReplace();
  543. });
  544. //首字母大写
  545. $('<button type=button id=initialUpper class=surveyBottomButton>首字母大写</button>').appendTo('div#commentFunc');
  546. $('button#initialUpper').on('click',()=>{
  547. initialUpper();
  548. });
  549. //评论翻译
  550. $('<button type=button id=commentTrans class=surveyBottomButton>评论翻译</button>').appendTo('div#commentFunc');
  551. $(`<select id=toLang>
  552. <option value=en>→英文</option>
  553. <option value=jp>→日文</option>
  554. <option value=zh>→中文(简体)</option>
  555. <option value=cht>→中文(繁体)</option>
  556. <option value=yue>→中文(粤语)</option>`).appendTo('div#commentFunc');
  557. $('button#commentTrans').on('click',()=>{
  558. commentTranslate($('select#toLang option:selected').val());
  559. });
  560. $('div#commentFunc').children().css({'display':'block','text-align':'center','margin':'5px auto'});
  561. }
  562. }
  563.  
  564. //评论替换开关
  565. function commentEditSwitch(){
  566. const on=$('div#commentFunc').css('display');
  567. if(on==='none'){
  568. $('div#commentFunc').show();
  569. commentMatch($('textarea#find'));
  570. }
  571. else{
  572. $('div#commentFunc').hide();
  573. $('textarea.surveycomment,textarea.active').css('background','');
  574. }
  575. }
  576.  
  577. //插入或移除提示
  578. function hintSwitch(self){
  579. if($(self).children().length){
  580. $(self).text('【点击获取提示】').children().remove();
  581. }
  582. else{
  583. $(self).text('【点击关闭提示】')
  584. .append('<a class=mailboxlink target=_blank href=https://tool.oschina.net/uploads/apidocs/jquery/regexp.html>匹配支持正则表达式</a>')
  585. .append('<a class=mailboxlink target=_blank href=https://c.runoob.com/front-end/854/>正则表达式测试</a>')
  586. .append(`
  587. <ol>
  588. <li>正则实例:[。|.]$ 可匹配末尾处中英文句号;^[a-z] 可匹配开头处小写字母;甲|乙|丙 可匹配甲或乙或丙</li>
  589. <li>可当作一般替换使用,如需替换一些特殊字符(\^$*+?.等,参照第一个链接中所列字符),请在前面使用\\标记转义,避免识别为正则表达</li>
  590. <li>评论框激活后按Ctrl可切换评论框是否标红,标红的评论框将被排除在修改范围之外,点击两框间的↓↓↓可快速切换全部评论框标红与否</li>
  591. <li>匹配内容为空时会匹配所有字符</li>
  592. </ol>
  593. `);
  594. $(self).children().css('display','block').on('click',e=>{
  595. e.stopPropagation();
  596. });
  597. $(self).find('li').css({'text-align':'left','width':'200px'});
  598. }
  599. }
  600.  
  601. //切换所有评论框标红与否
  602. function commentMark(){
  603. $('textarea.surveycomment,textarea.active').each(function(){
  604. if($(this).attr('class')==='active'){
  605. $(this).attr('class','surveycomment');
  606. }
  607. else{
  608. $(this).attr('class','active');
  609. }
  610. });
  611. }
  612.  
  613. //即时标灰匹配到的评论框并计数
  614. function commentMatch(self){
  615. let find;
  616. try{//若不是正则表达式,按普通字符处理
  617. find=new RegExp($(self).val(),'gm');
  618. }
  619. catch(e){
  620. find=$(self).val();
  621. }
  622. let findNum=0;
  623. $('textarea.active').css('background','');
  624. $('textarea.surveycomment').each(function(){
  625. try{//
  626. if($(this).val().search(find)>=0){//search只接受正则
  627. findNum++;
  628. $(this).css('background','lightgrey');
  629. }
  630. else{
  631. $(this).css('background','');
  632. }
  633. }
  634. catch(e){
  635. if($(this).val().indexOf(find)>=0){//indexOf只接受字符
  636. findNum++;
  637. $(this).css('background','lightgrey');
  638. }
  639. else{
  640. $(this).css('background','');
  641. }
  642. }
  643. });
  644. $('b#findNum').text('#='+findNum);
  645. }
  646.  
  647. //判断是否为正则并进行评论替换
  648. function commentReplace(){
  649. $('textarea.surveycomment').each(function(){
  650. let find;
  651. try{
  652. find=new RegExp($('textarea#find').val(),'gm');
  653. }
  654. catch(e){
  655. find=$('textarea#find').val();
  656. }
  657. const replace=$('textarea#replace').val();
  658. const text=$(this).val().replace(find,replace);
  659. $(this).val(text);
  660. });
  661. }
  662.  
  663. //首字母大写
  664. function initialUpper(){
  665. $('textarea.surveycomment').each(function(){
  666. const match=new Set($(this).val().match(/(^|\.|\?|!)("|'|) *[a-z]/gm));
  667. if(match.size){
  668. let text=$(this).val();
  669. for(let m of match){
  670. const trans=m.search(/ {2,}/)? m.replace(/ {2,}/,' ') : m;
  671. text=text.replace(trans,trans.toUpperCase());
  672. }
  673. $(this).val(text);
  674. $(this).css('background','lightgrey');
  675. }
  676. else{
  677. $(this).css('background','');
  678. }
  679. });
  680. }
  681.  
  682. //调用百度翻译进行评论翻译
  683. async function commentTranslate(toLang){
  684. await translate_baidu_startup();
  685. $('textarea.surveycomment').each(async function(){
  686. if($(this).val()&&$(this).next().is('input:hidden')){
  687. $(this).after('<textarea id=trans style=overflow:hidden rows='+$(this).attr('rows')+' cols='+$(this).attr('cols')+'>')
  688. .after('<button type=button class=attachmentBtn style="display:block;height:1.5em;width:1.5em;margin:5px 0">↑</button>');
  689. }
  690. if($(this).val()){
  691. const translated=await translate_baidu(toLang,$(this).val());
  692. $(this).next().next('textarea').val(translated);
  693. unsafeWindow.updrowH($(this).next().next('textarea')[0]);//调用页面自带函数来调整评论框高度
  694. $(this).next('button').on('click',function(){
  695. const prev=$(this).prev().val();
  696. const next=$(this).next().val();
  697. $(this).prev().val(prev+'\n\n'+next);
  698. unsafeWindow.updrowH($(this).prev()[0]);
  699. $(this).next().remove();
  700. $(this).remove();
  701. });
  702. }
  703. });
  704. $('textarea#trans').on('keydown',e=>{
  705. e.stopPropagation();//阻止页面自带keydown事件修改textarea的class值
  706. });
  707. }
  708.  
  709. //百度翻译 参考https://greasyfork.org/scripts/378277
  710. async function translate_baidu_startup(){
  711. if(window.sessionStorage.getItem('baidu_gtk')&&window.sessionStorage.getItem('baidu_token'))return;
  712. const options = {
  713. method:'GET',
  714. url:'https://fanyi.baidu.com',
  715. };
  716. const res = await Request(options);
  717. window.sessionStorage.setItem('baidu_gtk',/window\.gtk = '(.*?)'/.exec(res.responseText)[1]);
  718. window.sessionStorage.setItem('baidu_token',/token: '(.*?)'/.exec(res.responseText)[1]);
  719. }
  720.  
  721. async function translate_baidu(toLang,raw,fromLang){
  722. if(!fromLang){
  723. fromLang = await check_lang(raw);
  724. }
  725. const proc_raw = raw.length>30? (raw.substr(0,10)+raw.substr(~~(raw.length/2)-5,10)+raw.substr(-10)) : raw;//process
  726. const tk_key = window.sessionStorage.getItem('baidu_gtk');
  727. const token = window.sessionStorage.getItem('baidu_token');//get token
  728. const options = {
  729. method:"POST",
  730. url:'https://fanyi.baidu.com/v2transapi',
  731. data:'from='+fromLang+'&to='+toLang+'&query='+encodeURIComponent(raw)+'&transtype=translang&simple_means_flag=3&sign='+tk(proc_raw,tk_key)+"&token="+token+"&domain=common",
  732. headers: {
  733. "referer": 'https://fanyi.baidu.com',
  734. "Content-Type": 'application/x-www-form-urlencoded; charset=UTF-8',
  735. },
  736. };
  737. return await BaseTranslate('百度翻译',raw,options,res=>JSON.parse(res).trans_result.data.map(item=>item.dst).join('\n'));
  738. }
  739.  
  740. async function check_lang(raw){
  741. const options = {
  742. method:"POST",
  743. url:'https://fanyi.baidu.com/langdetect',
  744. data:'query='+encodeURIComponent(raw.replace(/[\uD800-\uDBFF]$/, "").slice(0,50)),
  745. headers: {
  746. "Content-Type": "application/x-www-form-urlencoded",
  747. }
  748. };
  749. const res = await Request(options);
  750. try{
  751. return JSON.parse(res.responseText).lan;
  752. }catch(err){
  753. console.log(err);
  754. return;
  755. }
  756. }
  757.  
  758. //根据翻译字符获取sign值
  759. function tk(a,b){
  760. var d = b.split(".");
  761. b = Number(d[0]) || 0;
  762. for (var e = [], f = 0, g = 0; g < a.length; g++) {
  763. var k = a.charCodeAt(g);
  764. 128 > k ?
  765. e[f++] = k : (
  766. 2048 > k ?
  767. e[f++] = k >> 6 | 192 : (
  768. 55296 == (k & 64512) && g + 1 < a.length && 56320 == (a.charCodeAt(g + 1) & 64512) ? (
  769. k = 65536 + ((k & 1023) << 10) + (a.charCodeAt(++g) & 1023),
  770. e[f++] = k >> 18 | 240,
  771. e[f++] = k >> 12 & 63 | 128
  772. ) :
  773. e[f++] = k >> 12 | 224,
  774. e[f++] = k >> 6 & 63 | 128
  775. ),
  776. e[f++] = k & 63 | 128
  777. );
  778. }
  779. a = b;
  780. for (f = 0; f < e.length; f++)a = Fo(a+e[f], "+-a^+6");
  781. a = Fo(a, "+-3^+b+-f");
  782. a ^= Number(d[1]) || 0;
  783. 0 > a && (a = (a & 2147483647) + 2147483648);
  784. a %= 1E6;
  785. return a.toString() + "." + (a ^ b);
  786. }
  787.  
  788. function Fo(a,b) {
  789. for (var c = 0; c < b.length - 2; c += 3) {
  790. var d = b.charAt(c + 2);
  791. d = "a" <= d ? d.charCodeAt(0) - 87 : Number(d);
  792. d = "+" == b.charAt(c + 1) ? a >>> d : a << d;
  793. a = "+" == b.charAt(c) ? a + d & 4294967295 : a ^ d;
  794. }
  795. return a;
  796. }
  797.  
  798. //异步请求包装工具
  799. async function PromiseRetryWrap(task,options,...values){
  800. const {RetryTimes,ErrProcesser} = options||{};
  801. let retryTimes = RetryTimes||5;
  802. const usedErrProcesser = ErrProcesser || (err =>{throw err;});
  803. if(!task)return;
  804. while(true){
  805. try{
  806. return await task(...values);
  807. }catch(err){
  808. if(!--retryTimes){
  809. console.log(err);
  810. return usedErrProcesser(err);
  811. }
  812. }
  813. }
  814. }
  815.  
  816. async function BaseTranslate(name,raw,options,processer){
  817. const toDo = async ()=>{
  818. let tmp;
  819. try{
  820. const data = await Request(options);
  821. tmp = data.responseText;
  822. const result = await processer(tmp);
  823. window.sessionStorage.setItem(name+'-'+raw,result);
  824. return result;
  825. }catch(err){
  826. throw {
  827. responseText: tmp,
  828. err: err
  829. };
  830. }
  831. };
  832. return await PromiseRetryWrap(toDo,{RetryTimes:3,ErrProcesser:()=>"翻译出错"});
  833. }
  834.  
  835. function Request(options){
  836. return new Promise((reslove,reject)=>GM_xmlhttpRequest({...options,onload:reslove,onerror:reject}));
  837. }
  838. /*评论编辑*/
  839.  
  840.  
  841. /*卸载评论编辑*/
  842. function unCOMMENTEDIT(){
  843. if($('div#commentEdit').length) $('div#commentEdit').remove();
  844. }
  845. /*卸载评论编辑*/
  846.  
  847.  
  848. /*验证输出*/
  849. function VERIFYEXPORT(){
  850. if (document.location.href.indexOf('alias=smngr.surveyexplorer')>=0&&$('div#filterdiv').length){
  851. $('div#filterdiv').before('<button type=button id=verifyExport class="rm-btn rm-btn-default">验证输出勾选的报告</button>');
  852. $('button#verifyExport').css({'margin':'10px 0px','display':'block'}).on('click',()=>{
  853. verifyExportAll();
  854. });
  855. }
  856. }
  857.  
  858. //验证输出全部报告
  859. function verifyExportAll(){
  860. const apply=confirm('请确认是否要验证输出当前页面勾选的所有报告(电脑配置低请勿一次性输出过多报告)');
  861. if(apply){
  862. $('table#reporttable tbody').find('tr').each(function(){
  863. if($(this).find('input:checkbox').eq(0).is(':checked')){
  864. const a=$(this).find('a.mailboxlink')[0];
  865. const src=$(a).attr('href');
  866. const iframe=document.createElement('iframe');
  867. iframe.src=src;
  868. iframe.style='height:80px; width:250px';
  869. $(a).after(iframe);
  870. iframe.onload=function(){
  871. const doc=$(this).contents();
  872. $(doc).find('div#addInfo,div#menu,div#pathContainer').remove();
  873. if($(doc).find('input#scrverN').is(':checked')&&$(doc).find('input#questVerN').is(':checked')){//前2点均为否时才进行操作
  874. $(doc).find('input#scrverY').click();
  875. $(doc).find('input#questVerY').click();
  876. $(doc).find('button#save').click();
  877. }
  878. };
  879. }
  880. });
  881. alert('请耐心等待所有小窗口加载完成,显示绿色保存成功提示后,再刷新页面检查是否全部验证输出成功');
  882. }
  883. }
  884. /*验证输出*/
  885.  
  886. /*卸载验证输出*/
  887. function unVERIFYEXPORT(){
  888. if($('button#verifyExport').length) $('button#verifyExport').remove();
  889. }
  890. /*卸载验证输出*/
  891.  
  892.  
  893. })();