minerva-online assistant

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

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

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