minerva-online assistant

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

当前为 2023-04-26 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name minerva-online assistant
  3. // @namespace https://space.bilibili.com/17846288
  4. // @version 3.0.7
  5. // @license MIT
  6. // @description 此脚本能更方便使用minerva-online平台,可在顶端菜单栏右下角的按钮处设置功能开关,并查看功能详情
  7. // @author inoki
  8. // @match https://www.minerva-online.com/*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_addStyle
  12. // @grant GM_xmlhttpRequest
  13. // @connect fanyi.baidu.com
  14. // @run-at document-start
  15. // @noframes
  16. // ==/UserScript==
  17.  
  18. /* VersionInfo 企业微信文档:https://doc.weixin.qq.com/doc/w2_AOMADQamAG8fAzy6aF1RWWmEc2ZhG?scode=AMwAwgcrABEDCapPcV
  19. 外观效果:优化下拉选择框的夜间模式效果
  20. 附件下载:与alias=inoki.va页面联动优化效果
  21. */
  22.  
  23. /*jshint esversion: 9*/
  24.  
  25. (()=>{
  26. 'use strict';
  27.  
  28.  
  29. const SET={
  30. 0:{
  31. 'id':0,
  32. 'name':'置顶置底',
  33. 'func':()=>{GOTOPBOTTOM();},
  34. 'unfunc':()=>{unGOTOPBOTTOM();},
  35. 'detail':
  36. `在平台域名所有可滚动页面生效,页面右下方添加【∨】/【∧】按钮<br>
  37. 【∨】/【∧】左键点击会根据页面滚动方向自动置顶或置底,按钮样式可在代码中自定义中修改<br>
  38. 【∨】/【∧】右键点击会在左侧生成【>】按钮,再次右键点击会删除【>】图标<br>
  39. 【>】生成时会记录当前页面位置,点击【>】将回到所记录的页面位置`,
  40. 'switch':1
  41. },
  42. 1:{
  43. 'id':1,
  44. 'name':'菜单遮罩',
  45. 'func':()=>{COVERMENU();},
  46. 'unfunc':()=>{unCOVERMENU();},
  47. 'detail':
  48. `在有顶端菜单栏的页面生效<br>
  49. 让菜单栏需要点击一次后才可展开,防止鼠标经过时误触<br>
  50. (默认关闭)`,
  51. 'switch':0
  52. },
  53. 2:{
  54. 'id':2,
  55. 'name':'附件下载',
  56. 'func':()=>{DOWNLOADFILE();},
  57. 'unfunc':()=>{unDOWNLOADFILE();},
  58. 'detail':
  59. `在问卷管理页面生效,每份报告前添加【↓】按钮<br>
  60. 【↓】点击可加载附件列表<br>
  61. 【√】点击可下载全部附件,之后会变为【〇】<br>
  62. 【×】点击可关闭附件列表<br>
  63. 附件名点击可下载单个附件,鼠标悬停可预览图片<br>
  64. 【删除全部附件】点击可将此报告全部附件标记为删除`,
  65. 'switch':1
  66. },
  67. 3:{
  68. 'id':3,
  69. 'name':'扣分标记',
  70. 'func':()=>{MARKQUESTION();},
  71. 'unfunc':()=>{unMARKQUESTION();},
  72. 'detail':
  73. `在单店报告页面生效,可醒目标记扣分或N/A的题目,方便快速检查相关题评论<br>
  74. 将题目中勾选扣分和N/A项标色,选项更改后需保存报告才会刷新标记<br>
  75. 可在上方设置扣分(默认为红)和N/A(默认为绿)的标记颜色,点击【√】保存更改<br>
  76. 颜色更改后关闭再开启此功能可在报告页面即时刷新颜色<br>
  77. 星期选项与日期不匹配时也将以扣分颜色标记,匹配时将在后方显示绿色√`,
  78. 'switch':1
  79. },
  80. 4:{
  81. 'id':4,
  82. 'name':'评论编辑',
  83. 'func':()=>{COMMENTEDIT();},
  84. 'unfunc':()=>{unCOMMENTEDIT();},
  85. 'detail':
  86. `在单店报告页面生效,右下方【问卷图标】<img src=https://www.minerva-online.com/images/icons/menu/x16/survet.png>按钮展开操作界面<br>
  87. 使用前请注意阅读操作界面最上方的【点击获取提示】<br>
  88. 【匹配/替换内容】框内输入内容将即时显示匹配的评论框数,并标灰评论框且在上方标记^^,鼠标悬停灰色评论框可预览替换后内容<br>
  89. 【匹配内容】支持正则表达式(详见【点击获取提示】),可Ctrl+F使用浏览器自带功能搜索^^标记,以快速定位匹配到的评论框<br>
  90. 【一键替换】点击可将所有匹配到的评论框内容修改为替换后内容,此时鼠标悬停灰色评论框可预览修改前内容<br>
  91. 【首字母大写】点击可智能将所有句首英文字母变为大写,显示修改过的评论框数并标灰,此时鼠标悬停灰色评论框可预览修改前内容<br>
  92. 【评论翻译】点击会调用百度翻译,在每个评论框下方输出目标语言翻译,点击↑可将下方内容添加至评论框`,
  93. 'switch':1
  94. },
  95. 5:{
  96. 'id':5,
  97. 'name':'验证输出',
  98. 'func':()=>{VERIFYEXPORT();},
  99. 'unfunc':()=>{unVERIFYEXPORT();},
  100. 'detail':
  101. `在问卷管理页面生效,表头上方添加【验证输出勾选的报告】按钮<br>
  102. 【验证输出勾选的报告】点击并确认后会验证输出当前页面勾选的所有报告,成功输出的报告下方小窗口会显示绿色提示<br>
  103. (电脑配置较低时一次输出太多份可能导致页面卡死,请根据浏览器最多同时能开几个报告页面量力而行,默认关闭)`,
  104. 'switch':0
  105. },
  106. 6:{
  107. 'id':6,
  108. 'name':'定制汇总',
  109. 'func':()=>{CUSTOMROLLUP();},
  110. 'unfunc':()=>{unCUSTOMROLLUP();},
  111. 'detail':
  112. `在定制汇总页面生效,在汇总表格上方添加功能按钮<br>
  113. 【复制表格】点击可一键复制表格全部内容,方便复制到excel等软件中编辑<br>
  114. 【复制表格】右侧下拉框选择“分数后+%”时,仅在Pivot table界面下生效,点击【复制表格】执行复制前会为所有数据后添加%<br>
  115. 【精确Pts%】点击可在表格右侧添加一列Pts/PtsOf的比值,并根据右侧下拉框选择的数字,进行相应小数位数的四舍五入<br>
  116. 【精确Pts%】需要PtsPtsOf列同时存在才能正常生效,用以避免默认Pts%的2位小数舍入可能造成的偏差<br>
  117. 【选项统计】点击可自动统计各架构各题选项的数量与占比,并在表格下方的新增行中展示(QuestionText前[AD]标识),百分比值根据左侧下拉框数字四舍五入<br>
  118. 【选项统计】需要QuestionTextAnswerText和#Surveys列同时存在才能正常生效,数量显示在末尾括号内,百分比值显示在#Survsys格<br>
  119. 【选项统计】参与统计的仅为QuestionText/AnswerText左侧非隐藏列和非隐藏行,可在隐藏不必要的列或行后重新点击按钮刷新统计<br>
  120. 【选项统计】在点击问题选项最右侧按钮<img src=https://www.minerva-online.com/images/icons/filtersv2/answers.png style="object-position:-16px 0;object-fit:none;width:16px;height:16px">加载选项后,可在统计中显示数量为0的选项`,
  121. 'switch':1
  122. },
  123. 7:{
  124. 'id':7,
  125. 'name':'报告存档',
  126. 'func':()=>{SURVEYSAVES();},
  127. 'unfunc':()=>{unSURVEYSAVES();},
  128. 'detail':
  129. `在单店报告页面生效,右下方【书本图标】<img src=https://www.minerva-online.com/images/icons/menu/x16/KB-icon.png>按钮展开操作界面,可查看自动/手动存档列表<br>
  130. 【存档】点击可进行手动存档,每次对报告内容进行修改时,将在本地进行自动存档<br>
  131. 【预览】点击可查看存档内容,并对需要读档写入的题目进行勾选<br>
  132. 【读档】点击可将选中的存档全部内容写入到当前报告,或只写入预览界面勾选的题目<br>
  133. 【删除】点击可删除选中的存档,自动/手动存档上限各为10个,超出时自动删除此类最早存档<br>
  134. (“评论编辑”功能造成的修改不会触发自动存档,可在修改后点击任意评论框触发自动存档)`,
  135. 'switch':1
  136. },
  137. /*
  138. 8:{
  139. 'id':8,
  140. 'name':'PDF命名',
  141. 'func':()=>{PDFRENAME();},
  142. 'unfunc':()=>{unPDFRENAME();},
  143. 'detail':
  144. `在“以PDF格式下载”的转化页面生效,在高级页添加【命名并下载(全部)】按钮<br>
  145. 可在下方FIle Name处自定义命名格式,在下拉框选择需要的命名元素(无须点merge)<br>
  146. 【命名并下载】点击可下载单个PDF,并按File Name处的自定义命名格式命名<br>
  147. 【命名并下载全部】点击相当于一键点击了所有【命名并下载】<br>
  148. (默认关闭,因为平台已于<a target=_blank href=https://www.minerva-online.com/document.asp?alias=knowledgebase#/article/005bcf16-8530-4437-b4a4-b671ad3db56f>2022.10更新</a>中支持了中文命名)`,
  149. 'switch':0
  150. },
  151. */
  152. 9:{
  153. 'id':9,
  154. 'name':'优化表头',
  155. 'func':()=>{BETTERTHEAD();},
  156. 'unfunc':()=>{unBETTERTHEAD();},
  157. 'detail':
  158. `在所有含有页面滚动时自动冻结表头功能的页面生效(例:问卷管理)<br>
  159. 点击表头上方【标题】行可显示表格所有列的表头内容,根据其勾选状态与否可显示/隐藏对应列<br>
  160. 优化冻结表头的表现,使冻结的表头不再像原先那样闪烁,且在冻结状态下也能执行排序/筛选功能<br>
  161. (优化冻结表头在部分浏览器可能不支持,若无效建议使用最新版chrome/edge浏览器体验)`,
  162. 'switch':1
  163. },
  164. 10:{
  165. 'id':10,
  166. 'name':'外观效果',
  167. 'func':()=>{CSSEFFECT();},
  168. 'unfunc':()=>{unCSSEFFECT();},
  169. 'detail':
  170. `对所有网页外观效果进行调整<br>
  171. 【夜间模式】开启后整体页面主色调变为黑色,在某些场景下更护眼<br>
  172. (目前夜间模式为较粗略的反色处理,如有视觉效果差的地方请反馈,后续将调整)<br>
  173. 【隐藏logo】开启后隐藏左上角Minerva&Cologo区域,节省页面空间`,
  174. 'switch':1
  175. },
  176. };
  177. unsafeWindow.MA_SET=SET;
  178.  
  179.  
  180. //先执行外观效果功能
  181. var style;
  182. if(GM_getValue(SET[10].name,SET[10].switch)) SET[10].func();
  183.  
  184. //DOM加载后开始执行其余功能
  185. var $;
  186. document.addEventListener('DOMContentLoaded',()=>{
  187. //filemanager页面不执行
  188. if(document.location.href.includes('alias=filemanager')){
  189. unsafeWindow.userIsEnterpriseAdmin=true;
  190. return;
  191. }
  192. //如网页无jQuery或版本低于1.7则引入1.8.2
  193. $=unsafeWindow.jQuery;
  194. try{
  195. console.log($.fn.jquery);
  196. $().on();//jQuery 1.7版本后才有$().on()
  197. init();
  198. }catch(e){
  199. const jq=document.createElement('script');
  200. jq.src='/lib/jquery/jquery-1.8.2.min.js';//'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js';
  201. document.head.appendChild(jq);
  202. jq.onload=()=>{
  203. $=unsafeWindow.jQuery;
  204. init();
  205. };
  206. }
  207. collaborationEnterpriseAdmin();
  208. });
  209.  
  210. //用于打印脚本简介
  211. unsafeWindow.MA_logInfo=()=>{
  212. let info='',i=0;
  213. for(let s in SET){
  214. info+=++i+' '+SET[s].name+':\n';
  215. info+=SET[s].detail.replaceAll(/ {2,}/g,'').replaceAll('<br>','');
  216. info+='\n\n';
  217. }
  218. console.log(info);
  219. return info;
  220. };
  221.  
  222.  
  223. /*在顶端菜单栏添加MOassist设置按钮*/
  224. function init(){
  225. console.log('jQuery',$.fn.jquery);
  226. for(let i in SET) if(GM_getValue(SET[i].name,SET[i].switch)) SET[i].func();//执行开启的功能
  227. const menu=$('div#menu');
  228. if(menu.length){
  229. menu.find('ul.tools').append(`
  230. <li class="MOassist">
  231. <a class="toolsLink">
  232. <div class="iconTools" style="background: url('/images/icons/menu/x16/tools-settings.png')"></div>
  233. <ul class="textArea" style="visibility:visible; display:none">
  234. <li>MO助手设置</li>
  235. </ul>
  236. <ul id="MOoption" class="innerItemFirst" style="z-index: 11; display:none; top:25px; right:0px"></ul>
  237. </a>
  238. </li>
  239. `);
  240. menu.find('li.MOassist').on('click',function(){
  241. MOListSwitch($(this).find('ul#MOoption'));
  242. }).find('ul#MOoption').on('click',e=>{
  243. e.stopPropagation();//让之后添加的功能列表不继承click事件
  244. });
  245. mouseHover(menu.find('li.MOassist'));
  246. }
  247. }
  248.  
  249. //功能列表开关
  250. function MOListSwitch(ul){
  251. if(ul.css('display')==='none'){
  252. if(!ul.children().length) initOptions($('div#menu'))
  253. ul.stop().slideDown(200);
  254. }else{
  255. ul.stop().slideUp(200);
  256. }
  257. }
  258.  
  259. //导入所有功能列表并显示开关状态
  260. function initOptions(menu){
  261. for(let i in SET){
  262. menu.find('ul#MOoption').append(`
  263. <li id="MOoptions" class="MOassist" style="width:100%">
  264. <div class="menuItemText" style="color:#4C5057">${SET[i].name}</div>
  265. <input type=checkbox id=${SET[i].id} class=menuIconSmall>
  266. <ul class="textArea" style="visibility:visible; display: none; margin-top:0px; right:120px">
  267. <li style="padding:0px 5px !important">${SET[i].detail}</li>
  268. </ul>
  269. </li>`);
  270. if(GM_getValue(SET[i].name,SET[i].switch)) menu.find('input#'+SET[i].id).prop('checked',true);//打勾开启状态的功能
  271. }
  272. //根据是否选中即时启用或卸载功能并记录开关状态
  273. menu.find('li#MOoptions').on('click',function(e){
  274. const checkbox=$(this).children('input:checkbox');
  275. const id=$(checkbox).attr('id');
  276. if(GM_getValue(SET[id].name,SET[id].switch)){
  277. SET[id].unfunc();
  278. $(checkbox).prop('checked',false);
  279. GM_setValue(SET[id].name,0);
  280. }else{
  281. SET[id].func();
  282. $(checkbox).prop('checked',true);
  283. GM_setValue(SET[id].name,1);
  284. }
  285. }).children('ul.textArea').on('click',e=>{
  286. e.stopPropagation();
  287. });
  288. mouseHover(menu.find('li#MOoptions'));
  289. setMarkQuestionColor(menu);
  290. setCSSEffectOption(menu);
  291. }
  292.  
  293. //鼠标聚焦时显示详情 【https://www.minerva-online.com/portal/menu/js/v2/menuRender.js?version=21-08 createToolOption : 】
  294. function mouseHover(ele){
  295. ele.hover(function(){
  296. $(this).find('ul:first').stop().show(200);
  297. },function(){
  298. $(this).find('ul:first').stop().hide(200);
  299. });
  300. }
  301.  
  302. //添加扣分标记颜色设置界面
  303. function setMarkQuestionColor(menu){
  304. menu.find('input#3.menuIconSmall').next('ul').prepend(`<div style="padding:0px 5px">
  305. <b id=de>扣分颜色:</b>
  306. <form id=de>
  307. <input type=radio name=de value=red>红
  308. <input type=radio name=de value=orange>橙
  309. <input type=radio name=de value=yellow>黄
  310. <input type=radio name=de value=green>绿
  311. <input type=radio name=de value=blue>蓝
  312. <input type=radio name=de value=purple>紫
  313. <input type=radio name=de value=custom>自定义
  314. <input type=color class=selectedColor>
  315. <input type=button class=rm-btn value=√ title=保存 style="padding:1px 6px">
  316. </form>
  317. <b id=na>N/A颜色:</b>
  318. <form id=na>
  319. <input type=radio name=na value=red>红
  320. <input type=radio name=na value=orange>橙
  321. <input type=radio name=na value=yellow>黄
  322. <input type=radio name=na value=green>绿
  323. <input type=radio name=na value=blue>蓝
  324. <input type=radio name=na value=purple>紫
  325. <input type=radio name=na value=custom>自定义
  326. <input type=color class=selectedColor>
  327. <input type=button class=rm-btn value=√ title=保存 style="padding:1px 6px">
  328. </form>
  329. </div>`);
  330. //颜色选项初始化
  331. menu.find('form#de,form#na').each(function(){
  332. const curColor= $(this).attr('id')==='de'?
  333. GM_getValue($(this).prev().text(),'red') : GM_getValue($(this).prev().text(),'green');
  334. $(this).prev().css('color',curColor);
  335. $(this).children('.selectedColor').attr('id',curColor);
  336. if(!curColor.includes('#')){
  337. $(this).children('input[value='+curColor+']').attr('checked',true);
  338. $(this).children('.selectedColor').hide();
  339. }else{
  340. $(this).children('input[value=custom]').attr('checked',true);
  341. $(this).children('.selectedColor').val(curColor);
  342. }
  343. });
  344. //点击选项颜色改变
  345. menu.find('form#de,form#na').children(':radio').on('click',function(){
  346. if($(this).val()==='custom'){
  347. $(this).next().show();
  348. $(this).next().attr('id',$(this).next().val());
  349. }else{
  350. $(this).nextAll('.selectedColor').hide();
  351. $(this).nextAll('.selectedColor').attr('id',$(this).val());
  352. }
  353. $(this).parent().prev().css('color',$(this).nextAll('.selectedColor').attr('id'));
  354. });
  355. //自定义颜色改变
  356. menu.find('form#de,form#na').children('.selectedColor').on('input',function(){
  357. $(this).attr('id',$(this).val());
  358. $(this).parent().prev().css('color',$(this).val());
  359. });
  360. //确认更改
  361. menu.find('form#de,form#na').children(':button').on('click',function(){
  362. GM_setValue($(this).parent().prev().text(),$(this).prev().attr('id'));
  363. if(!$(this).next().is('b')){
  364. $(this).after('<b>保存成功</b>');
  365. setTimeout(()=>{
  366. $(this).next().remove();
  367. },3e3);
  368. }
  369. });
  370. }
  371.  
  372. //添加扣分标记颜色设置界面
  373. function setCSSEffectOption(menu){
  374. menu.find('input#10.menuIconSmall').next('ul').prepend(`<div id=CSSEffectOption>
  375. <input type=checkbox value=夜间模式>夜间模式
  376. <input type=checkbox value=隐藏logo>隐藏logo
  377. </div>`);
  378. const oCE=SET[10];
  379. const name_=oCE.name+'_';
  380. menu.find('div#CSSEffectOption input').each(function(){
  381. const value=this.value;
  382. if(GM_getValue(name_+value,0)) $(this).prop('checked',true);//初始化勾选状态
  383. $(this).on('click',function(){
  384. if(GM_getValue(name_+value,0)){
  385. $(this).prop('checked',false);
  386. GM_setValue(name_+value,0);
  387. }else{
  388. $(this).prop('checked',true);
  389. GM_setValue(name_+value,1);
  390. }
  391. if(GM_getValue(oCE.name,oCE.switch)) oCE.func();
  392. });
  393. });
  394. }
  395. /*在顶端菜单栏添加MOassist设置按钮*/
  396.  
  397.  
  398. /*获取app.collaboration页面Enterprise.Admin权限*/
  399. function collaborationEnterpriseAdmin(){
  400. if(!document.location.href.includes('alias=app.collaboration')) return;
  401. const funcName=['renderSyncLog','renderImportLog'];
  402. for(let n of funcName) eval('unsafeWindow.'+n+'='+unsafeWindow[n].toString().replace('if (isEnterpriseAdmin)','').replace('function '+n,'function'));
  403. }
  404. /*获取app.collaboration页面Enterprise.Admin权限*/
  405.  
  406.  
  407. /*置顶置底*/
  408. function GOTOPBOTTOM(){
  409. $(window).on('scroll.gotopbottom',()=>{//如页面后续因为内容增加而能滚动的场合触发
  410. if($('div#goTopBottom').length===0) GOTOPBOTTOM();
  411. });
  412. const scrollBar=$(document).height()>(window.innerHeight+1||document.documentElement.clientHeight);//如有滚动条
  413. if(!scrollBar||document.location.href.includes('alias=knowledgebase')) return;//knowledgebase页面自带置顶按钮,不启用
  414. const goTopBottomButton=document.createElement('div');
  415. const toggleButton=document.createElement('img');
  416. $(toggleButton).appendTo(goTopBottomButton);
  417. $(goTopBottomButton).appendTo('body');
  418. $(goTopBottomButton).css({position:'fixed',zIndex:1e4}).attr('id','goTopBottom');
  419. $(toggleButton).css({display:'block',cursor:'pointer'}).attr('src','/knowledgebase/images/arrow_back_to_top.svg');//按钮显示图片(向下箭头)
  420. $(goTopBottomButton).attr('title','置顶置底');
  421.  
  422. //以下按钮参数可自定义修改
  423. goTopBottomButton.style.bottom='50px';//按钮距离网页底部50px
  424. goTopBottomButton.style.right='30px';//按钮距离网页右边30px
  425. toggleButton.style.width='25px';//按钮图片宽25px
  426. toggleButton.style.height='25px';//按钮图片高25px
  427. toggleButton.style.opacity=0.5;//按钮不透明度,0.0(完全透明)到1.0(完全不透明)
  428. toggleButton.style.backgroundColor='grey';//按钮背景颜色,也可使用在excel等软件的自定义颜色界面的16进制代码
  429. const clickScrollTime=500;//点击按钮时,网页滚动到顶部或底部需要的时间,500毫秒
  430.  
  431. //点击按钮时网页滚动到顶部或底部
  432. let scrollDirection='down';
  433. toggleButton.addEventListener('click',()=>{
  434. if(scrollDirection==='up'){
  435. $('html,body').animate({scrollTop:0},clickScrollTime);
  436. }else{
  437. $('html,body').animate({scrollTop:$(document).height()},clickScrollTime);
  438. }
  439. });
  440. //右键按钮记录页面位置
  441. const lock=$(goTopBottomButton).clone().attr({id:'goTopBottomLock',title:'回到记录位置'}).css({right:'60px',display:'none'}).appendTo('body');
  442. lock.children('img').css('transform','rotate(270deg)');
  443. goTopBottomButton.onmouseup=function(e){
  444. if(e.button===2){
  445. if(lock.css('display')==='none'){
  446. const x=window.pageXOffset;
  447. const y=window.pageYOffset;
  448. lock[0].onclick=()=>{scrollTo(x,y);};
  449. lock.show();
  450. }else{
  451. lock.hide();
  452. }
  453. }
  454. };
  455. goTopBottomButton.oncontextmenu=()=>{return false;};
  456. //页面滚动监听
  457. let scrollAction=window.pageYOffset;
  458. $(window).scroll(()=>{
  459. const diffY=scrollAction-window.pageYOffset;
  460. scrollAction=window.pageYOffset;
  461. scrollDirection= diffY<0? 'down' : 'up';
  462. toggleButton.style.transform= diffY<0? 'rotate(0deg)' : 'rotate(180deg)';
  463. if(getScrollTop()===0){
  464. scrollDirection='down';
  465. toggleButton.style.transform='rotate(0deg)';
  466. }
  467. if(getScrollTop()+window.innerHeight+20>=$(document).height()){
  468. scrollDirection='up';
  469. toggleButton.style.transform='rotate(180deg)';
  470. }
  471. });
  472. }
  473.  
  474. //获取垂直方向滑动距离
  475. function getScrollTop(){
  476. let scrollTop=0;
  477. if(document.documentElement&&document.documentElement.scrollTop){
  478. scrollTop=document.documentElement.scrollTop;
  479. }else if(document.body){
  480. scrollTop=document.body.scrollTop;
  481. }
  482. return scrollTop;
  483. }
  484.  
  485. //卸载置顶置底
  486. function unGOTOPBOTTOM(){
  487. $(window).off('scroll.gotopbottom');
  488. if($('div#goTopBottom,div#goTopBottomLock').length) $('div#goTopBottom,div#goTopBottomLock').remove();
  489. }
  490. /*置顶置底*/
  491.  
  492.  
  493. /*菜单遮罩*/
  494. function COVERMENU(){
  495. if($('div#menu').length){
  496. //若存在menu则添加cover层
  497. const menu=$('div#menu')[0];
  498. const zidx=getComputedStyle(menu).zIndex;
  499. const cover = document.createElement('div');
  500. cover.className = 'layout';
  501. cover.style = 'top:'+menu.style.top+';opacity:0.3;z-index:'+zidx+';right:10%';
  502. //点击时将cover层下置
  503. cover.addEventListener('click',()=>{
  504. cover.style.zIndex = -1;
  505. });
  506. //离开menu时cover层还原
  507. menu.addEventListener('mouseleave',()=>{
  508. cover.style.zIndex = zidx;
  509. });
  510. //cover层位置跟随menu 【https://www.minerva-online.com/portal/menu/js/v2/menuRender.js?version=21-08 onScrollEventHandler : 】
  511. $(window).on('scroll.covermenu',function(){
  512. const SM=unsafeWindow.SM;
  513. if(!SM) return;
  514. SM.ui.parentContainer=cover;
  515. SM.ui.onScrollEventHandler();
  516. SM.ui.parentContainer=menu;
  517. });
  518. $(cover).appendTo($('body')[0]).attr('id','cover');
  519. }
  520. }
  521.  
  522. //卸载菜单遮罩
  523. function unCOVERMENU(){
  524. if($('div#cover').length){
  525. $('div#cover').remove();
  526. $(window).off('scroll.covermenu');
  527. }
  528. }
  529. /*菜单遮罩*/
  530.  
  531.  
  532. /*附件下载*/
  533. function DOWNLOADFILE(){
  534. if(document.location.href.includes('alias=smngr.surveyexplorer')&&$('tr.persist-header').length){
  535. $('tr.persist-header').each(function(){
  536. $(this).children().first().after($(this).children().first().clone(true).attr('class','downloadFile'));
  537. });
  538. $('table#reporttable').find(':checkbox').each(function(){//checkbox后添加下载按钮
  539. const surveyid=$(this).val();
  540. $(this).parent().after(`
  541. <td>
  542. <div id=${surveyid} class=downloadFile>
  543. <button type=button id=download class=rm-btn title=加载附件列表>↓</button>
  544. </div>
  545. </td>`);
  546. $(this).parent().next().find('button#download').one('click',function(){
  547. DownloadButton($(this).parent(),surveyid);//将$('div.downloadFile')传参为df
  548. });
  549. });
  550. }
  551. //兼容inoki.va页面
  552. if(document.location.href.includes('alias=inoki.va')){
  553. if($('table#reporttable tr').length){
  554. $('table#reporttable>thead>tr').each(function(){
  555. $(this).children().first().before($(this).children().first().clone(true).attr('class','downloadFile'));
  556. $(this).children().first().text('附件下载');
  557. });
  558. $('table#reporttable').find('tbody>tr').each(function(){
  559. const surveyid=$(this).attr('id');
  560. $(this).prepend(`
  561. <td>
  562. <div class=downloadFile>
  563. <button type=button id=download class=rm-btn title=加载附件列表>↓</button>
  564. </div>
  565. </td>`);
  566. $(this).find('button#download').one('click',function(){
  567. DownloadButton($(this).parent(),surveyid);
  568. });
  569. });
  570. }
  571. }
  572. }
  573.  
  574. //获取附件列表
  575. function DownloadButton(df,surveyid){
  576. //if(!surveyid) surveyid=df.parents('tr').attr('id');
  577. df.find('button#download').hide();
  578. df.append('<b id=loading>......</b>');
  579. $.get(`
  580. /open/data.asp?post={
  581. "action":"exec",
  582. "JSONPath":"dataset.data.3",
  583. "dataset":{"datasetname":"/Apps/SM/Survey/SurveyInstanceGetData"},
  584. "parameters":[{"name":"SurveyInstanceID","value":"${surveyid}"}]
  585. }`,(data,status)=>{//调用API获取当前survey数据[SurveyInstanceGetData]
  586. if(status==='success'){
  587. if(Array.isArray(data)){
  588. const filedata=data;
  589. const fileno=filedata.length;
  590. df.append(`
  591. <ol id=filelist>
  592. <b>\t#=${fileno}</b>
  593. <table></table>
  594. </ol>
  595. `);
  596. if(fileno>0){
  597. const ol=df.find('ol#filelist');
  598. $('<button type=button id=downloadAll class="rm-btn rm-btn-default" title=下载全部>√</button>').prependTo(ol)
  599. .on('click',()=>{
  600. DownloadAll(df);
  601. });
  602. $('<button type=button id=deleteAll class="rm-btn rm-btn-default">删除全部附件</button>').appendTo(ol)
  603. .on('click',()=>{
  604. DeleteAll(df,surveyid);
  605. });
  606. const tb=ol.children('table');
  607. for(let i in filedata){
  608. const filename=filedata[i].FileName+'.'+filedata[i].FileExtension;
  609. const fileid=filedata[i].AttachmentID;
  610. const fileurl='/mystservices/Attachments/getAttachment.asp?Attachment='+fileid+'&Password='+filedata[i].Password+'';
  611. let filesize=Number(filedata[i].FileSizeInBytes)/1024;
  612. filesize= (filesize>1024)? (filesize/1024).toFixed(2)+' MB' : filesize.toFixed(2)+' KB';
  613. $('<tr id='+fileid+'>').appendTo(tb).append(`
  614. <td>
  615. <li>
  616. <a id=${filedata[i].AttachmentType} class=mailboxlink href=${fileurl}>${filename}</a>
  617. </li>
  618. </td>
  619. <td>${filesize}</td>
  620. <td>QID:${filedata[i].ProtoQuestionID}</td>
  621. `);
  622. }
  623. df.find('a#I,a#V').mouseenter(function(){
  624. FilePreview(1,$(this).attr('href'));
  625. }).mouseleave(()=>{
  626. FilePreview(0);
  627. });
  628. }
  629. }else{
  630. df.append('<b>登录失效!</b>');
  631. }
  632. }else{
  633. df.append('<b>网络错误!</b>');
  634. }
  635. DownloadButton0(df,surveyid);
  636. },'json');
  637. }
  638.  
  639. //预览附件图片
  640. function FilePreview(show,src){
  641. if(show){
  642. const imgid=src.split('=')[2];
  643. if($('img#'+imgid+'.filepreview').length===0){
  644. $('<div><img id='+imgid+' class=filepreview></img></div>').appendTo('body');
  645. $('img#'+imgid+'.filepreview').attr('src',src+'&getThumbnail=1').css('height','200px')//视频附件预览图u&getThumbnail=1
  646. .parent().css({position:'fixed',zIndex:1e4,height:'200px',background:'url(/images/icons/filtersv2/loading06.gif)'});
  647. }
  648. $('img#'+imgid+'.filepreview').parent().css({top:event.clientY-200,left:event.clientX+100});
  649. $('img#'+imgid+'.filepreview').show();
  650. }else{
  651. $('img.filepreview').hide();
  652. }
  653. }
  654.  
  655. //按钮变为关闭
  656. function DownloadButton0(df,surveyid){
  657. df.find('b#loading').remove();
  658. df.find('button#download').text('×').attr('title','关闭附件列表').show().one('click',()=>{
  659. DownloadButton1(df,surveyid);
  660. });
  661. }
  662.  
  663. //按钮重置为初始
  664. function DownloadButton1(df,surveyid){
  665. const passid=[];
  666. df.find('a#I,a#V').each(function(){
  667. passid.push($(this).attr('href').split('=')[2]);
  668. });
  669. for(let id of passid) $('img#'+id).parent().remove();
  670. df.find('ol,b').remove();
  671. df.find('button#download').text('↓').attr('title','加载附件列表').one('click',()=>{
  672. DownloadButton(df,surveyid);
  673. });
  674. }
  675.  
  676. //下载全部
  677. function DownloadAll(df){
  678. df.find('button#downloadAll').text('〇').hide();
  679. const iframe=df.find('ol#filelist iframe');
  680. const a=df.find('ol#filelist a');
  681. if(iframe.length) iframe.remove();
  682. setTimeout(()=>{
  683. df.find('button#downloadAll').show();
  684. },1e3*a.length);//有几个附件就隐藏按钮几秒
  685. a.each(function(){
  686. $('<iframe src='+$(this).attr('href')+'>').appendTo(this).hide();
  687. });
  688. }
  689. /*
  690. //不知是不是因为下载地址有重定向的关系,GM_download效率低下,弃用
  691. function DownloadAll(df){
  692. const a=df.find('ol#filelist a');
  693. let loaded=0;
  694. df.find('button#downloadAll').text('〇').hide();
  695. df.find('ol#filelist>p,ol#filelist>br').remove();
  696. df.find('ol#filelist>b').text(`\t#=${loaded}/${a.length}\t下载中...`);
  697. a.each(function(){
  698. GM_download({
  699. 'url':$(this).attr('href'),
  700. 'name':df.parent().nextAll().eq(3)[0].innerText+'—'+df.parent().nextAll().eq(7)[0].innerText.replace('\n',' ')+'—'+$(this).text(),
  701. 'onload':()=>{
  702. loaded++;
  703. if(loaded===a.length){
  704. df.find('ol#filelist>b').text(`\t#=${loaded}/${a.length}\t下载完成!`);
  705. df.find('button#downloadAll').show();
  706. }else{
  707. df.find('ol#filelist>b').text(`\t#=${loaded}/${a.length}\t下载中...`);
  708. }
  709. },
  710. 'onerror':()=>{
  711. df.find('ol#filelist>b').after('<br><p>'+$(this).text()+'\t下载错误!</p>');
  712. df.find('button#downloadAll').show();
  713. },
  714. 'ontimeout':()=>{
  715. df.find('ol#filelist>b').after('<br><p>'+$(this).text()+'\t下载超时!</p>');
  716. df.find('button#downloadAll').show();
  717. }
  718. });
  719. });
  720. }
  721. */
  722.  
  723. function DeleteAll(df,surveyid){
  724. const attid=[];
  725. df.find('a').each(function(){
  726. attid.push(this.href.match(/(?<=\=)\d+/)[0]);
  727. });
  728. console.log(attid,surveyid);
  729. const apply=confirm('请确认是否将此报告所有附件标记为删除\n(可在more中恢复,确认后可刷新页面或再次加载附件列表查看)');
  730. if(apply){
  731. //模拟手动禁用单个附件发送post请求,但请求数量较多
  732. for(let i of attid){
  733. $.post(`/document.asp?alias=survey.disableimage&ImageID=${i}&InstanceID=${surveyid}`,'ref=&step=2&comment=');
  734. }
  735. df.children('button#download').click();
  736. }
  737. }
  738. /*
  739. //以API实现,缺点是不会在more中留下用以恢复的记录,优点是请求数量较少
  740. function DeleteAll(df){
  741. let csv='';
  742. df.find('a').each(function(){
  743. csv+=this.href.match(/(?<=\=)\d+/)[0]+',';
  744. });
  745. csv=csv.slice(0,-1);
  746. console.log(csv);
  747. const apply=confirm('请确认是否要删除此报告所有附件\n(无法恢复注意备份,确认后可刷新页面或再次加载附件列表查看)');
  748. if(apply){
  749. //调用API将当前survey的所有附件标记为删除[SurveyAttachmentsMarkAttachmentsForDelete]
  750. $.get(`
  751. /open/data.asp?post={
  752. "action":"exec",
  753. "dataset":{"datasetname":"/Apps/SM/Media Hub/SurveyAttachmentsMarkAttachmentsForDelete"},
  754. "parameters":[{"name":"CsvList","value":"${csv}"}]
  755. }`);
  756. df.children('button#download').click();
  757. }
  758. }
  759. */
  760.  
  761. //卸载附件下载
  762. function unDOWNLOADFILE(){
  763. if ($('div.downloadFile').length&&$('td.downloadFile').length){
  764. $('td.downloadFile').remove();
  765. $('div.downloadFile').each(function(){
  766. $(this).parent().remove();
  767. });
  768. }
  769. }
  770. /*附件下载*/
  771.  
  772.  
  773. /*扣分标记*/
  774. function MARKQUESTION(){
  775. if(unsafeWindow.OSM||!$('form#frmSurvey').length) return;//如v3问卷或预览等界面不运行
  776. if(document.location.href.includes('alias=survey.view')){
  777. $('span.surveyansweroption').each(function(){
  778. if($(this).prev('input').is(':checked')){
  779. if($(this).prev('input').val()==='__na__'){
  780. $(this).css('color',GM_getValue('N/A颜色:','green'));//默认标绿N/A项
  781. }
  782. }
  783. });
  784. //获取所有扣分的题目
  785. const qidmark=[];
  786. const check=checkDayofWeek();
  787. if(check.isWrong) qidmark.push('Q'+check.qid+'ANS');
  788. markDayofWeek(check.isWrong,check.ele);
  789. //v3问卷/mystservices/v2new/getSurvey.asp?InstanceID=请求后获取数据:unsafeWindow.Open.stringToObject(OSMRenderingInit.toString().match(/(?<=OSMRendering_SurveyInstance_Preview\(){.+}(?=, document)/)[0])
  790. $.get('/mystservices/v2new/getSurvey.asp?InstanceID='+$('input#instanceID').val(),(data,status)=>{
  791. if (status==='success'){
  792. $(data).find('nobr').each(function(){
  793. const score=$(this).text();
  794. if(score!=''&&!score.includes('%')){//排除空值与section总分
  795. const pts=score.split('/');
  796. if(Number(pts[0])<Number(pts[1])){
  797. const QidANS=$(this).parents('td.surveyquestioncell').prev().find('div').attr('id');
  798. qidmark.push(QidANS);
  799. }
  800. }
  801. });
  802. for(let i in qidmark){
  803. $('div#'+qidmark[i]).find('span.surveyansweroption').css('color',GM_getValue('扣分颜色:','red'));//默认标红扣分项
  804. }
  805. }
  806. });
  807. }
  808. }
  809.  
  810. //检查所选星期和日期是否不一致
  811. function checkDayofWeek(){
  812. const qtx=['星期','Day of the week','Day of the Week','Day of week','Day 星期','周几',' 星期'];//lee问卷[ID:1316]的星期题前有空格
  813. const atx=['星期一','Monday'];
  814. let isWrong,ele,qid,start;
  815. $('span.surveyquestion').each(function(){
  816. for(let q of qtx) if($(this).text().startsWith(q)) ele=$(this);
  817. });
  818. if(ele) qid=ele.parents('a.surveyquestionnobreak').attr('name').replace('qstn','');
  819. if(qid){
  820. const t=$('input[name=HEAD_DATE]').val().split('-');
  821. if(t.length===3){
  822. const day=new Date(t[0],t[1]-1,t[2]).getDay();
  823. const ans=unsafeWindow.sm_getmultipleanswer(qid);
  824. for(let a of atx) if($('input#'+qid+'R1').next('span').text().startsWith(a)) start=1;
  825. if(start&&!day&&day+7!=ans) isWrong=1;//如星期一开头&&应为星期日&&0+7!=7
  826. if(start&&day&&day!=ans) isWrong=1;
  827. if(!start&&day+1!=ans) isWrong=1;
  828. }
  829. }
  830. return {isWrong:isWrong,qid:qid,ele:ele};
  831. }
  832.  
  833. //标记星期是否正确
  834. function markDayofWeek(isWrong,ele){
  835. if(!ele) return;
  836. if(isWrong){
  837. ele.after('<b id=dayofweek style=color:red>×</b>');
  838. }else{
  839. ele.after('<b id=dayofweek style=color:green>√</b>');
  840. }
  841. }
  842.  
  843. //卸载扣分标记
  844. function unMARKQUESTION(){
  845. if(unsafeWindow.OSM||!$('form#frmSurvey').length) return;
  846. if(document.location.href.includes('alias=survey.view')){
  847. $('span.surveyansweroption').css('color','');
  848. $('b#dayofweek').remove();
  849. }
  850. }
  851. /*扣分标记*/
  852.  
  853.  
  854. /*评论编辑*/
  855. function COMMENTEDIT(){
  856. if(unsafeWindow.OSM||!$('form#frmSurvey').length) return;//如v3问卷或预览等界面不运行
  857. if(document.location.href.includes('alias=survey.view')){
  858. if(unsafeWindow.updrowH) $('textarea.surveycomment').each(function(){unsafeWindow.updrowH(this);});//等页面自身执行到调整评论框高度过于缓慢
  859. $('<div id=commentEdit title=评论编辑>').appendTo($('body')[0])
  860. .css({position:'fixed',zIndex:1e4+1,
  861. right:'30px',bottom:'80px',height:'25px',width:'25px',
  862. background:'#808080'});
  863. //插入图标
  864. $('<img src=/images/icons/menu/x32/survet.png>').appendTo('div#commentEdit').css({width:'inherit',transform:'scale(0.8)'});
  865. //插入操作界面并赋值为ce
  866. $('<div id=commentFunc title>').appendTo('div#commentEdit')
  867. .css({position:'fixed',right:'60px',bottom:'50px'}).hide()
  868. .on('click',e=>{
  869. e.stopPropagation();//阻止子元素执行父元素click事件
  870. });
  871. const ce=$('div#commentFunc');
  872. $('div#commentEdit').on('click',function(){
  873. commentEditSwitch(ce);
  874. });
  875. //提示开关
  876. $('<b id=hint style=cursor:pointer>【点击获取提示】</b>').appendTo(ce)
  877. .on('click',function(){
  878. hintSwitch($(this));
  879. });
  880. //评论匹配与替换
  881. $('<button type=button id=replaceAll class=surveyBottomButton>一键替换</button>').appendTo(ce)
  882. .before('<textarea id=find placeholder=匹配内容></textarea><b id=commentMark style=cursor:pointer title=切换所有评论框标红状态>↓↓↓</b><textarea id=replace placeholder=替换内容></textarea>')
  883. .before('<b id=findNum>#</b>');
  884. $('textarea#find,textarea#replace').on('keydown',e=>{
  885. e.stopPropagation();//阻止页面自带keydown事件修改textarea的class值
  886. }).on('input',function(){
  887. commentMatch(ce);
  888. });
  889. $('b#commentMark').on('click',()=>{
  890. commentMark(ce);
  891. });
  892. $('button#replaceAll').on('click',()=>{
  893. execReplace(ce);
  894. });
  895. //首字母大写
  896. $('<button type=button id=initialUpper class=surveyBottomButton>首字母大写</button>').appendTo(ce)
  897. .on('click',()=>{
  898. initialUpper(ce);
  899. });
  900. //评论翻译
  901. $(`
  902. <button type=button id=commentTrans class=surveyBottomButton>评论翻译</button>
  903. <select id=toLang>
  904. <option value=en>→英文</option>
  905. <option value=jp>→日文</option>
  906. <option value=zh>→中文(简体)</option>
  907. <option value=cht>→中文(繁体)</option>
  908. <option value=yue>→中文(粤语)</option>
  909. <option value=fj>繁体→简体</option>
  910. </select>
  911. `).appendTo(ce);
  912. ce.find('button#commentTrans').on('click',()=>{
  913. commentTranslate($('select#toLang option:selected').val());
  914. });
  915. ce.children().css({display:'block',textAlign:'center',margin:'5px auto'});
  916. }
  917. }
  918.  
  919. //评论替换开关
  920. function commentEditSwitch(ce){
  921. if(ce.css('display')==='none'){
  922. ce.show();
  923. commentMatch(ce);
  924. }else{
  925. ce.hide();
  926. markMatchComment(0,$('textarea.surveycomment,textarea.active'));
  927. }
  928. }
  929.  
  930. //插入或移除提示
  931. function hintSwitch(hs){
  932. if(hs.children().length){
  933. hs.text('【点击获取提示】').children().remove();
  934. }else{
  935. hs.text('【点击关闭提示】')
  936. .append('<a class=mailboxlink target=_blank href=https://tool.oschina.net/uploads/apidocs/jquery/regexp.html>匹配支持正则表达式</a>')
  937. .append('<a class=mailboxlink target=_blank href=https://c.runoob.com/front-end/854>正则表达式测试</a>')
  938. .append(`
  939. <ol>
  940. <li>正则实例:[。|.]$ 可匹配末尾处中英文句号;^[a-z] 可匹配开头处小写字母;甲|乙|丙 可匹配甲或乙或丙</li>
  941. <li>可当作一般替换使用,如需替换一些特殊字符(\^$*+?.等,参照第一个链接中所列字符),请在前面使用\\标记转义,避免识别为正则表达</li>
  942. <li>评论框激活后按Ctrl可切换评论框是否标红,标红的评论框将被排除在修改范围之外,点击两框间的↓↓↓可快速切换全部评论框标红与否</li>
  943. </ol>
  944. `);
  945. hs.children().css('display','block').on('click',e=>{
  946. e.stopPropagation();
  947. });
  948. hs.find('li').css({textAlign:'left',width:'200px'});
  949. }
  950. }
  951.  
  952. //判断此评论框是否隐藏
  953. function isHide(ele){
  954. const qvState=unsafeWindow.sm_questionvisibility_state;
  955. if(qvState&&qvState[ele.name.replaceAll('C','')]==='hide'||ele.style.display==='none'){
  956. return 1;
  957. }else{
  958. return 0;
  959. }
  960. }
  961.  
  962. //切换所有评论框标记与否
  963. function commentMark(ce){
  964. $('textarea.surveycomment,textarea.active').each(function(){
  965. if(isHide(this)) return;
  966. if($(this).attr('class')==='active'){
  967. $(this).attr('class','surveycomment');
  968. }else{
  969. $(this).attr('class','active');
  970. }
  971. });
  972. commentMatch(ce);
  973. }
  974.  
  975. //即时标记匹配到的评论框,预览替换后内容
  976. function commentMatch(ce){
  977. const f=getFind(ce);
  978. let n=0;
  979. markMatchComment(0,$('textarea.active'));
  980. $('textarea.surveycomment').each(function(){
  981. if(isHide(this)) return;
  982. const rc=getReplacedComment(ce,f,$(this).val());
  983. if(rc!==false){
  984. markMatchComment(1,$(this),rc);
  985. n++;
  986. }else{
  987. markMatchComment(0,$(this));
  988. }
  989. });
  990. ce.find('b#findNum').text('#='+n);
  991. }
  992.  
  993. //匹配评论框标记处理
  994. function markMatchComment(hasText,ele,text,isBefore){
  995. ele.each(function(){
  996. if(hasText){
  997. text= isBefore? '之前为:\n'+text : '替换为:\n'+text;
  998. $(this).css('background','lightgrey').attr('title',text);
  999. if($(this).prev().attr('id')){
  1000. $(this).prev('div#mark').show();
  1001. }else{
  1002. $(this).before('<div id=mark>^^</div>');
  1003. }
  1004. }else{
  1005. $(this).css('background','').attr('title','');
  1006. $(this).prev('div#mark').hide();
  1007. }
  1008. });
  1009. }
  1010.  
  1011. //判断并返回匹配值与类型
  1012. function getFind(ce){
  1013. let find,isRE;
  1014. try{//若不是正则表达式,按普通字符处理
  1015. find=new RegExp(ce.find('textarea#find').val(),'gm');
  1016. isRE=1;
  1017. }catch(e){
  1018. find=ce.find('textarea#find').val();
  1019. }
  1020. return [find,isRE];
  1021. }
  1022.  
  1023. //判断并返回替换后评论或空
  1024. function getReplacedComment(ce,f,text){
  1025. const index= f[1]? text.search(f[0]) : text.indexOf(f[0]);//search只接受正则 : indexOf只接受字符
  1026. if(index>=0){
  1027. return text.replaceAll(f[0],ce.find('textarea#replace').val());
  1028. }else{
  1029. return false;
  1030. }
  1031. }
  1032.  
  1033. //进行评论替换
  1034. function execReplace(ce){
  1035. const f=getFind(ce);
  1036. $('textarea.surveycomment').each(function(){
  1037. if(isHide(this)) return;
  1038. const text=$(this).val();
  1039. const rc=getReplacedComment(ce,f,text);
  1040. if(rc!==false){
  1041. markMatchComment(1,$(this),text,1);//记录修改前内容
  1042. $(this).val(rc);
  1043. unsafeWindow.updrowH(this);//调用页面自带函数来调整评论框高度
  1044. }
  1045. });
  1046. }
  1047.  
  1048. //首字母大写
  1049. function initialUpper(ce){
  1050. let n=0;
  1051. $('textarea.surveycomment').each(function(){
  1052. if(isHide(this)) return;
  1053. const match=new Set($(this).val().match(/(^|\.|\?|!)("|'|) *[a-z]/gm));
  1054. if(match.size){
  1055. let text=$(this).val();
  1056. markMatchComment(1,$(this),text,1);//记录大写前内容
  1057. for(let m of match){
  1058. const r=new RegExp(`(^|\\.|\\?|!)("|'|) *`+m,'gm');
  1059. text=text.replaceAll(r,m.toUpperCase());
  1060. }
  1061. $(this).val(text);
  1062. n++;
  1063. }else{
  1064. markMatchComment(0,$(this));
  1065. }
  1066. });
  1067. ce.find('b#findNum').text('#='+n);
  1068. }
  1069.  
  1070. //调用百度翻译进行评论翻译
  1071. async function commentTranslate(toLang){
  1072. await translate_baidu_startup();
  1073. $('textarea.surveycomment').each(async function(){
  1074. if(isHide(this)) return;
  1075. if($(this).val()&&$(this).next().is('input:hidden')){
  1076. $(this).after('<textarea id=trans style=overflow:hidden rows='+$(this).attr('rows')+' cols='+$(this).attr('cols')+'>')
  1077. .after('<button type=button class=attachmentBtn style="display:block;height:1.5em;width:1.5em;margin:5px 0">↑</button>');
  1078. $(this).next('button').on('click',function(){
  1079. const prev=$(this).prev().val();
  1080. const next=$(this).next().val();
  1081. $(this).prev().val(prev+'\n\n'+next);
  1082. unsafeWindow.updrowH($(this).prev()[0]);
  1083. $(this).next().remove();
  1084. $(this).remove();
  1085. });
  1086. }
  1087. if($(this).val()){
  1088. const fromLang= toLang==='f→j'? 'cht' : null;
  1089. toLang= toLang==='f→j'? 'zh' : toLang;
  1090. const translated=await translate_baidu(toLang,$(this).val(),fromLang);
  1091. $(this).next().next('textarea').val(translated);
  1092. unsafeWindow.updrowH($(this).next().next('textarea')[0]);//调用页面自带函数来调整评论框高度
  1093. }
  1094. });
  1095. $('textarea#trans').on('keydown',e=>{
  1096. e.stopPropagation();//阻止页面自带keydown事件修改textarea的class值
  1097. });
  1098. }
  1099.  
  1100. //百度翻译 参考https://greasyfork.org/scripts/378277
  1101. async function translate_baidu_startup(){
  1102. if(window.sessionStorage.getItem('baidu_gtk')&&window.sessionStorage.getItem('baidu_token')) return;
  1103. const options={
  1104. method:'GET',
  1105. url:'https://fanyi.baidu.com',
  1106. };
  1107. const res=await Request(options);
  1108. window.sessionStorage.setItem('baidu_gtk',/window\.gtk = "(.*?)"/.exec(res.responseText)[1]);
  1109. window.sessionStorage.setItem('baidu_token',/token: '(.*?)'/.exec(res.responseText)[1]);
  1110. }
  1111.  
  1112. async function translate_baidu(toLang,raw,fromLang){
  1113. if(!fromLang){
  1114. fromLang=await check_lang(raw);
  1115. }
  1116. const proc_raw= raw.length>30? (raw.substr(0,10)+raw.substr(~~(raw.length/2)-5,10)+raw.substr(-10)) : raw;//process
  1117. const tk_key=window.sessionStorage.getItem('baidu_gtk');
  1118. const token=window.sessionStorage.getItem('baidu_token');//get token
  1119. const options={
  1120. method:"POST",
  1121. url:'https://fanyi.baidu.com/v2transapi',
  1122. data:`from=${fromLang}&to=${toLang}&query=${encodeURIComponent(raw)}&transtype=translang&simple_means_flag=3&sign=${tk(proc_raw,tk_key)}&token=${token}&domain=common`,
  1123. headers:{
  1124. "referer":'https://fanyi.baidu.com',
  1125. 'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8',
  1126. },
  1127. };
  1128. return await BaseTranslate('百度翻译',raw,options,res=>JSON.parse(res).trans_result.data.map(item=>item.dst).join('\n'));
  1129. }
  1130.  
  1131. async function check_lang(raw){
  1132. const options={
  1133. method:"POST",
  1134. url:'https://fanyi.baidu.com/langdetect',
  1135. data:'query='+encodeURIComponent(raw.replace(/[\uD800-\uDBFF]$/,'').slice(0,50)),
  1136. headers:{
  1137. 'Content-Type':'application/x-www-form-urlencoded',
  1138. }
  1139. };
  1140. const res=await Request(options);
  1141. try{
  1142. return JSON.parse(res.responseText).lan;
  1143. }catch(e){
  1144. console.log(e);
  1145. return;
  1146. }
  1147. }
  1148.  
  1149. //根据翻译字符获取sign值
  1150. function tk(a,b){
  1151. var d=b.split('.');
  1152. b=Number(d[0]) || 0;
  1153. for(var e=[],f=0,g=0;g<a.length;g++){
  1154. var k=a.charCodeAt(g);
  1155. 128>k?
  1156. e[f++]=k : (
  1157. 2048>k?
  1158. e[f++]=k>>6|192 : (
  1159. 55296==(k&64512)&&g+1<a.length&&56320==(a.charCodeAt(g+1)&64512)? (
  1160. k=65536+((k&1023)<<10)+(a.charCodeAt(++g)&1023),
  1161. e[f++]=k>>18|240,
  1162. e[f++]=k>>12&63|128
  1163. ) :
  1164. e[f++]=k>>12|224,
  1165. e[f++]=k>>6&63|128
  1166. ),
  1167. e[f++]=k&63|128
  1168. );
  1169. }
  1170. a=b;
  1171. for(f=0;f<e.length;f++) a=Fo(a+e[f],'+-a^+6');
  1172. a=Fo(a,'+-3^+b+-f');
  1173. a^=Number(d[1])||0;
  1174. 0>a&&(a=(a&2147483647)+2147483648);
  1175. a%=1E6;
  1176. return a.toString()+'.'+(a^b);
  1177. }
  1178.  
  1179. function Fo(a,b){
  1180. for(var c=0;c<b.length-2;c+=3){
  1181. var d=b.charAt(c+2);
  1182. d= "a"<=d? d.charCodeAt(0)-87 : Number(d);
  1183. d= "+"==b.charAt(c + 1)? a>>>d : a<<d;
  1184. a= "+"==b.charAt(c)? a+d&4294967295 : a^d;
  1185. }
  1186. return a;
  1187. }
  1188.  
  1189. //异步请求包装工具
  1190. async function PromiseRetryWrap(task,options,...values){
  1191. const {RetryTimes,ErrProcesser}=options||{};
  1192. let retryTimes=RetryTimes||5;
  1193. const usedErrProcesser=ErrProcesser||(err=>{throw err;});
  1194. if(!task)return;
  1195. while(true){
  1196. try{
  1197. return await task(...values);
  1198. }catch(e){
  1199. if(!--retryTimes){
  1200. console.log(e);
  1201. return usedErrProcesser(e);
  1202. }
  1203. }
  1204. }
  1205. }
  1206.  
  1207. async function BaseTranslate(name,raw,options,processer){
  1208. const toDo=async ()=>{
  1209. let tmp;
  1210. try{
  1211. const data=await Request(options);
  1212. tmp=data.responseText;
  1213. const result=await processer(tmp);
  1214. if(result) window.sessionStorage.setItem(name+'-'+raw,result);
  1215. return result;
  1216. }catch(e){
  1217. throw {
  1218. responseText: tmp,
  1219. err: e
  1220. };
  1221. }
  1222. };
  1223. return await PromiseRetryWrap(toDo,{RetryTimes:3,ErrProcesser:()=>"翻译出错"});
  1224. }
  1225.  
  1226. function Request(options){
  1227. return new Promise((reslove,reject)=>GM_xmlhttpRequest({...options,onload:reslove,onerror:reject}));
  1228. }
  1229. /*评论编辑*/
  1230.  
  1231.  
  1232. //卸载评论编辑
  1233. function unCOMMENTEDIT(){
  1234. if(unsafeWindow.OSM) return;
  1235. if($('div#commentEdit').length) $('div#commentEdit').remove();
  1236. }
  1237. /*评论编辑*/
  1238.  
  1239.  
  1240. /*验证输出*/
  1241. function VERIFYEXPORT(){
  1242. if (document.location.href.includes('alias=smngr.surveyexplorer')&&$('div#filterdiv').length){
  1243. $('div#filterdiv').before('<button type=button id=verifyExport class="rm-btn rm-btn-default">验证输出勾选的报告</button>');
  1244. $('button#verifyExport').css({margin:'10px 0px',display:'block'}).on('click',()=>{
  1245. verifyExportAll();
  1246. });
  1247. }
  1248. }
  1249.  
  1250. //验证输出全部报告
  1251. function verifyExportAll(){
  1252. const apply=confirm('请确认是否要验证输出当前页面勾选的所有报告\n(电脑配置低请勿一次性输出过多报告)');
  1253. if(apply){
  1254. $('table#reporttable>tbody>tr').each(function(){
  1255. if($(this).css('display')!='none'&&$(this).find('input:checkbox').eq(0).is(':checked')){
  1256. const a=$(this).find('td>a.mailboxlink').eq(0);
  1257. const src=a.attr('href').replace('document.asp?alias=survey.view&','importmngr/ImportSurveys/ImportSurveysFrame.asp?');
  1258. const iframe=document.createElement('iframe');
  1259. iframe.src=src;
  1260. iframe.style='height:80px;width:250px';
  1261. a.after(iframe);
  1262. iframe.onload=function(){
  1263. if(this.src.includes('ImportSurveysFrame.asp')){
  1264. const doc=$(this).contents();
  1265. doc.find('div#addInfo,div#menu,div#pathContainer').remove();
  1266. if(doc.find('input#scrverN').is(':checked')&&doc.find('input#questVerN').is(':checked')){//前2点均为否时才进行操作
  1267. doc.find('input#scrverY').click();
  1268. doc.find('input#questVerY').click();
  1269. doc.find('button#save').click();
  1270. }else if(this.contentWindow.surveyImportSurveySubmitted){
  1271. $(this).before('<div style=background:green;width:250px><img src=/images/icons/xp16/accept-hover.png>报告已成功执行上线</div>');
  1272. $(this).remove();
  1273. }else{
  1274. $(this).before('<div style=width:250px>报告原本已处于上线状态</div>');
  1275. }
  1276. }
  1277. };
  1278. }
  1279. });
  1280. alert('请耐心等待所有小窗口加载完成,显示绿色保存成功提示后,再刷新页面检查是否全部验证输出成功');
  1281. }
  1282. }
  1283.  
  1284. //卸载验证输出
  1285. function unVERIFYEXPORT(){
  1286. if($('button#verifyExport').length) $('button#verifyExport').remove();
  1287. }
  1288. /*验证输出*/
  1289.  
  1290.  
  1291. /*定制汇总*/
  1292. function CUSTOMROLLUP(){
  1293. if(document.location.href.includes('alias=clientaccess.customrollups')){
  1294. if($('table.reporttable,table.incCrossTabTableClass').length){
  1295. const cr=document.createElement('div');
  1296. cr.id='CRfunction';
  1297. $('div.q2r_tableCaptionDiv,table.incCrossTabTableClass').before(cr);
  1298. //复制表格
  1299. $('<button type=button id=copyBtn>复制表格</button>').appendTo(cr)
  1300. .on('click',function(){
  1301. if($(this).next('select#percentSign').val()==='+%'){//给Pivot table形式的数据后添加%
  1302. $('table.incCrossTabTableClass td.incCrossTabTableBodyValuesClass').each(function(){
  1303. if(!$(this).attr('rowspan')) $(this).text($(this).text().replace('%','')+'%');
  1304. });
  1305. }else{
  1306. $('table.incCrossTabTableClass td.incCrossTabTableBodyValuesClass').each(function(){
  1307. if(!$(this).attr('rowspan')) $(this).text($(this).text().replace('%',''));
  1308. });
  1309. }
  1310. window.getSelection().removeAllRanges();
  1311. const content=$(cr).parent().find('table#reporttable,table.incCrossTabTableClass').children('tbody')[0].innerText;
  1312. if($(this).prevAll('textarea').length===0){
  1313. $(this).before('<textarea>');
  1314. }
  1315. $(this).prev('textarea').show();
  1316. $(this).prev('textarea').val(content).select();
  1317. document.execCommand('copy');
  1318. $(this).prev('textarea').hide();
  1319. window.getSelection().removeAllRanges();
  1320. document.execCommand('copy')?
  1321. $(this).text('复制成功!') : $(this).text('复制失败...');
  1322. setTimeout(()=>{
  1323. $(this).text('复制表格');
  1324. },3e3);
  1325. });
  1326. $(`<select id=percentSign title="此选项仅针对Pivot table(自定义表格)界面下,复制时为分数后添加%,在Tabular(默认表格)界面下无效">
  1327. <option value=+%>分数后+%</option>
  1328. <option value=无>无</option>
  1329. </select><br>`).appendTo(cr);
  1330. //精确pts%
  1331. $('<button type=button id=ptsPercPlus title=根据Pts和PtsOf列精确计算pts%值>精确pts%</button>').appendTo(cr)
  1332. .on('click',function(){
  1333. const td=$(cr).parent().find('table#reporttable>thead>tr>td');
  1334. let pts,ptsOf;
  1335. for(let i=0;i<td.length;i++){
  1336. if(td.eq(i).text()==='Pts') pts=i;
  1337. if(td.eq(i).text()==='Pts Of') ptsOf=i;
  1338. }
  1339. if(pts&&ptsOf){
  1340. $('#reporttable tr').each(function(){
  1341. const last=$(this).children('td:last-child');
  1342. if(last.attr('class')!=='ptsPercPlus') last.after(last.clone(true).attr('class','ptsPercPlus'));
  1343. const tar=$(this).children('td.ptsPercPlus');
  1344. if($(this).attr('class')==='persist-header'){
  1345. tar.text('Pts%+');
  1346. }else{
  1347. const score=$(this).children().eq(pts).children().text()/$(this).children().eq(ptsOf).children().text();//只取<span>中的数据
  1348. tar.text((score*100).toFixed($(cr).children('select#toFixed').val())+'%');
  1349. }
  1350. });
  1351. $(this).text('计算成功!');
  1352. }else{
  1353. $(this).text('缺少关键列!');
  1354. }
  1355. setTimeout(()=>{
  1356. $(this).text('精确pts%');
  1357. },3e3);
  1358. });
  1359. $('<select id=toFixed title=此选项对【精确pts%】和【选项统计】同时生效>').appendTo(cr);
  1360. const sel=$(cr).children('select#toFixed');
  1361. for(let i=0;i<6;i++) sel.append(`<option value=${i}>保留${i}位小数</option>`);
  1362. sel.children().eq(1).prop('selected',true);
  1363. //选项统计
  1364. $('<button type=button id=answerDistribution title=根据QuestionText和AnswerText和#Surveys列统计各架构各题各选项数量和占比>选项统计</button>').appendTo(cr)
  1365. .on('click',function(){
  1366. $(cr).parent().find('table#reporttable>tbody>tr.ad').remove();
  1367. //获取各关键列位置及隐藏情况
  1368. const thtd=$(cr).parent().find('table#reporttable>thead>tr>td');
  1369. let QText,AText,surveyNum;
  1370. const hideCol=[],colIndex=[];//QuestionText/AnswerText列中靠左的所有左侧的未隐藏列都将记录至colIndex并参与统计
  1371. for(let i=0;i<thtd.length;i++){
  1372. if(thtd.eq(i).text()==='Question Text') QText=i;
  1373. else if(thtd.eq(i).text()==='Answer Text') AText=i;
  1374. else if(thtd.eq(i).text()==='# Surveys') surveyNum=i;
  1375. else if(QText===undefined&&AText===undefined&&thtd.eq(i).css('display')!='none') colIndex.push(i);
  1376. else if(thtd.eq(i).css('display')==='none') hideCol.push(i);
  1377. }
  1378. if(QText>=0&&AText>=0&&surveyNum>=0){
  1379. //先显示可能被隐藏的关键列
  1380. const input=$('div#hideOption input');
  1381. if(input.length) for(let i of [QText,AText,surveyNum]) if(!input.eq(i).is(':checked')) toggleCol(input.eq(i).prop('checked',true)[0]);
  1382. //开始统计
  1383. const AD={};
  1384. $('table#reporttable>tbody>tr').each(function(){
  1385. if(this.style.display==='none') return;
  1386. const tbtd=$(this).children();
  1387. const Q=checkKey(AD,tbtd.eq(QText).text().trim());
  1388. let BUText='';
  1389. for(let c of colIndex) BUText+=tbtd.eq(c).text()+';';
  1390. const BU=checkKey(Q,BUText);
  1391. const num=Number(tbtd.eq(surveyNum).children('span').text());
  1392. if(!BU.num) Object.defineProperty(BU,'num',{value:0,enumerable:false,writable:true});//将作为分母的num设为不可枚举
  1393. BU.num+=num;
  1394. for(let a of tbtd.eq(AText).text().split(';')){
  1395. if(!BU[a]) BU[a]=0;
  1396. BU[a]+=num;
  1397. }
  1398. });
  1399. //检测是否已加载选项并补充样本量为0的选项
  1400. const selectedQ=$('input[name=fieldForCheckingFilter]').val().match(/(?<=4:).+(?= 5:)/),QnA={};
  1401. let selectedArr=[];
  1402. const PCmsFilterQuestion=$('select#PCmsFilterQuestionsSelectQuestion');
  1403. if(selectedQ) selectedArr=selectedQ[0].split('%2C');
  1404. else{
  1405. PCmsFilterQuestion.find('optgroup>option').filter(function(){return !this.value.includes(',');}).each(function(){
  1406. selectedArr.push(this.value);
  1407. });
  1408. }
  1409. for(let s of selectedArr){
  1410. const Q=PCmsFilterQuestion.find(`[value=${s}]`).text().trim();
  1411. QnA[Q]=[];
  1412. PCmsFilterQuestion.find(`[value*=_${s}_]`).each(function(){
  1413. QnA[Q].push(this.innerText.trim().substr(1));//去除多余空格和前面的'-'
  1414. });
  1415. const QnA_Q=QnA[Q],AD_Q=AD[Q];//将缺少的选项赋值为0
  1416. for(let a in QnA_Q) for(let bu in AD_Q) if(!Object.keys(AD_Q[bu]).includes(QnA_Q[a])) AD_Q[bu][QnA_Q[a]]=0;
  1417. }
  1418. //输出统计结果
  1419. for(let q in AD){
  1420. for(let bu in AD[q]){
  1421. const buArr=bu.split(';');
  1422. for(let a in AD[q][bu]){
  1423. let tr='<tr class=ad>',n=0;
  1424. for(let i=0;i<thtd.length;i++){
  1425. if(hideCol.includes(i)) tr+='<td style=display:none></td>';
  1426. else if(colIndex.includes(i)) tr+=`<td>${buArr[n++]}</td>`;
  1427. else if(QText===i) tr+=`<td>[AD] ${q} (${AD[q][bu].num})</td>`;
  1428. else if(AText===i) tr+=`<td>${a} (${AD[q][bu][a]})</td>`;
  1429. else if(surveyNum===i) tr+=`<td>${(AD[q][bu][a]/AD[q][bu].num*100).toFixed($(cr).children('select#toFixed').val())}%</td>`;
  1430. else tr+='<td></td>';
  1431. }
  1432. tr+='</tr>';
  1433. $('table#reporttable>tbody').append(tr);
  1434. }
  1435. }
  1436. }
  1437. $(this).text('统计成功!');
  1438. }else{
  1439. $(this).text('缺少关键列!');
  1440. }
  1441. setTimeout(()=>{
  1442. $(this).text('选项统计');
  1443. },3e3);
  1444. });
  1445. //整体css调整
  1446. $(cr).children('button').css({margin:'5px',font:'inherit'});
  1447. }
  1448. }
  1449. }
  1450.  
  1451. function checkKey(obj,key){
  1452. if(!obj[key]) obj[key]={};
  1453. return obj[key];
  1454. }
  1455.  
  1456. //卸载定制汇总
  1457. function unCUSTOMROLLUP(){
  1458. if($('div#CRfunction').length) $('div#CRfunction').remove();
  1459. }
  1460. /*定制汇总*/
  1461.  
  1462.  
  1463. /*报告存档*/
  1464. function SURVEYSAVES(){
  1465. if(unsafeWindow.OSM||!$('form#frmSurvey').length) return;
  1466. if(document.location.href.includes('alias=survey.view')){
  1467. //插入操作界面并赋值为ssl
  1468. $('<div id=surveySaves title=报告存档>').appendTo($('body')[0])
  1469. .css({position:'fixed','zIndex':1e4,
  1470. right:'30px',bottom:'110px',height:'25px',width:'25px',
  1471. background:'#808080'});
  1472. $('<img src=/images/icons/menu/x16/KB-icon.png>').appendTo('div#surveySaves').css({width:'inherit',transform:'scale(0.8)'});
  1473. $('<div id=surveySavesList title>').appendTo('div#surveySaves')
  1474. .css({position:'fixed',right:'60px',bottom:'50px',background:'lightgrey'}).hide()
  1475. .on('click',e=>{
  1476. e.stopPropagation();//阻止子元素执行父元素click事件
  1477. });
  1478. const ssl=$('div#surveySavesList');
  1479. ssl.append(`
  1480. <div style=margin:5px>
  1481. <button type=button id=Show class=rm-btn>预览</button>
  1482. <button type=button id=Load class=rm-btn>读档</button>
  1483. <button type=button id=Save class=rm-btn>存档</button>
  1484. <button type=button id=Dele class=rm-btn>删除</button>
  1485. </div>
  1486. <div id=tab style=margin:5px>
  1487. <label><input type=radio name=tab value=autosaves checked>自动存档</label>
  1488. <label><input type=radio name=tab value=selfsaves>手动存档</label>
  1489. </div>
  1490. <table cellpadding=5 style=margin:5px>
  1491. <thead id=saves>
  1492. <tr>
  1493. <td></td>
  1494. <td>保存时间</td>
  1495. <td>问卷标题</td>
  1496. <td>店铺ID</td>
  1497. <td>报告ID</td>
  1498. </tr>
  1499. </thead>
  1500. <thead id=questions>
  1501. <tr>
  1502. <td><input type=checkbox name=questions checked></td>
  1503. <td>QID</td>
  1504. <td>题目</td>
  1505. <td>选项</td>
  1506. <td>评论</td>
  1507. </tr>
  1508. </thead>
  1509. <tbody id=autosaves></tbody>
  1510. <tbody id=selfsaves></tbody>
  1511. <tbody id=questions style=height:300px;overflow:scroll></tbody>
  1512. </table>
  1513. `);
  1514. ssl.find('thead,tbody').css('display','block');
  1515. ssl.find('#selfsaves,#questions').hide();
  1516. //存档列表开关
  1517. $('div#surveySaves').on('click',function(){
  1518. if(ssl.css('display')==='none'){
  1519. ssl.show();
  1520. refreshList(ssl);
  1521. }else{
  1522. ssl.hide();
  1523. }
  1524. });
  1525. //按钮功能
  1526. ssl.find('button#Show').on('click',()=>{
  1527. showData(ssl);
  1528. });
  1529. ssl.find('button#Load').on('click',()=>{
  1530. loadData(ssl);
  1531. });
  1532. ssl.find('button#Save').on('click',()=>{
  1533. saveData(ssl,'selfsaves');
  1534. });
  1535. ssl.find('button#Dele').on('click',()=>{
  1536. deleteData(ssl);
  1537. });
  1538. //切换存档列表页
  1539. ssl.find('div#tab').on('click',function(){
  1540. ssl.find('input[name=saves]:checked').prop('checked',false);//将存档的选中状态重置
  1541. const checked=$(this).find('input:checked').val();
  1542. const unchecked= checked==='autosaves'? 'selfsaves' : 'autosaves';
  1543. $(this).parent().find('tbody#'+unchecked).hide();
  1544. $(this).parent().find('tbody#'+checked).show();
  1545. refreshList(ssl,checked);
  1546. theadWidth(ssl.find('thead#saves'),ssl.find('tbody#'+checked));
  1547. });
  1548. //预览界面一键勾选与取消
  1549. ssl.find('thead#questions input').on('click',function(){
  1550. if($(this).is(':checked')){
  1551. $(this).parents('table').find('input[name=questions]').prop('checked',true);
  1552. }else{
  1553. $(this).parents('table').find('input[name=questions]').prop('checked',false);
  1554. }
  1555. });
  1556. //监听表单内点击和评论失焦触发存档事件
  1557. let Form=$('form#frmSurvey').serialize();
  1558. $('form#frmSurvey').on('click.autosave',()=>{
  1559. Form=autoSave(ssl,Form);
  1560. });
  1561. $('textarea.surveycomment,textarea.active').on('blur.autosave',()=>{
  1562. Form=autoSave(ssl,Form);
  1563. });
  1564. }
  1565. }
  1566.  
  1567. //表单变化时自动存档
  1568. function autoSave(ssl,prvForm){
  1569. const curForm=$('form#frmSurvey').serialize();
  1570. if(prvForm!=curForm){//如表单数据改变
  1571. saveData(ssl,'autosaves');
  1572. }
  1573. return curForm;
  1574. }
  1575.  
  1576. //刷新存档列表
  1577. function refreshList(ssl,fromSave){
  1578. if(ssl.css('display')==='none') return;//界面隐藏时不刷新
  1579. if(!fromSave) fromSave=ssl.find('input[name=tab]:checked').val();//无参数自动判断当前页
  1580. const curChecked=ssl.find('input[name=saves]:checked').parent('td').next().text();//记录当前选中存档
  1581. ssl.find('tbody#'+fromSave).empty();
  1582. const saves=GM_getValue(fromSave,{});
  1583. const k=Object.keys(saves);
  1584. if(k.length){
  1585. for(let i in k){
  1586. const line= i%2===1? 'reporttable_odd' : 'reporttable_even';
  1587. ssl.find('tbody#'+fromSave).prepend(`
  1588. <tr class=${line}>
  1589. <td><input type=radio name=saves></td>
  1590. <td>${k[i]}</td>
  1591. <td>${saves[k[i]].surveytitle}</td>
  1592. <td>${saves[k[i]].formhead.LOCATION}</td>
  1593. <td>${saves[k[i]].surveyid}</td>
  1594. </tr>
  1595. `);
  1596. if(curChecked===k[i]) ssl.find('tbody#'+fromSave).children('tr:first').find('input').prop('checked',true);//还原存档选中状态
  1597. }
  1598. //如为当前存档页则同步表头宽度
  1599. if(ssl.find('input[name=tab]:checked').val()===fromSave){
  1600. theadWidth(ssl.find('thead#saves'),ssl.find('tbody#'+fromSave));
  1601. }
  1602. }else{
  1603. const w=getComputedStyle(ssl.find('tbody#'+fromSave)[0]).width;
  1604. ssl.find('tbody#'+fromSave).append('<td style=width:'+w+';text-align:center>无数据!</td>');
  1605. }
  1606. }
  1607.  
  1608. //预览
  1609. function showData(ssl){
  1610. if(ssl.find('thead#questions').css('display')==='none'){
  1611. const [save]=checkSave(ssl);
  1612. if(!save) return;
  1613. const head=save.formhead;
  1614. for(let h in head){
  1615. const line='reporttable_odd';
  1616. ssl.find('tbody#questions').append(`
  1617. <tr class=${line}>
  1618. <td><input type=checkbox name=questions></td>
  1619. <td colspan=2>${h}</td>
  1620. <td colspan=2>${head[h]}</td>
  1621. </tr>
  1622. `);
  1623. }
  1624. const body=save.formbody;
  1625. for(let b of body){
  1626. if(!Object.keys(b).includes('ans')) b.ans='×';
  1627. if(!Object.keys(b).includes('cmt')) b.cmt='×';
  1628. const line='reporttable_even';
  1629. ssl.find('tbody#questions').append(`
  1630. <tr class=${line}>
  1631. <td><input type=checkbox name=questions></td>
  1632. <td>${b.qid}</td>
  1633. <td>${b.qtx}</td>
  1634. <td>${b.ans}</td>
  1635. <td>${b.cmt}</td>
  1636. </tr>
  1637. `);
  1638. }
  1639. if(ssl.find('thead#questions input').is(':checked')){
  1640. ssl.find('tbody#questions input').prop('checked',true);
  1641. }else{
  1642. ssl.find('tbody#questions input').prop('checked',false);
  1643. }
  1644. ssl.find('button#Show').text('←');
  1645. ssl.find('button#Save,button#Dele,div#tab,[id$=saves]').hide();
  1646. ssl.find('table #questions').show();
  1647. theadWidth(ssl.find('thead#questions'),ssl.find('tbody#questions'));//在显示后同步宽度,隐藏时会默认为auto
  1648.  
  1649. }else{
  1650. ssl.find('tbody#questions').empty();
  1651. ssl.find('button#Show').text('预览');
  1652. ssl.find('table #questions').hide();
  1653. ssl.find('button#Save,button#Dele,div#tab,#saves,tbody#'+$('input[name=tab]:checked').val()).show();
  1654. }
  1655. }
  1656.  
  1657. //读档
  1658. function loadData(ssl){
  1659. const [save]=checkSave(ssl);
  1660. if(!save) return;
  1661. //如当前报告id与存档报告id不相等,询问是否执行
  1662. if(save.surveyid!=$('input#instanceID').val()){
  1663. //将文本,是否确认,确认后执行的方法传给feedback(ssl,text,ifConfirm,yesFunc)生成确认提示
  1664. feedback(
  1665. ssl,
  1666. '报告ID不同,仅会修改当前报告中和存档中相匹配的题目,是否继续?',
  1667. 1,
  1668. ()=>{execLoadData(ssl,save);}
  1669. );
  1670. }else{
  1671. execLoadData(ssl,save);
  1672. }
  1673. }
  1674.  
  1675. //执行读档
  1676. function execLoadData(ssl,save){
  1677. const qidArr=[];
  1678. if(ssl.find('thead#questions').css('display')!='none'){
  1679. if(ssl.find('input[name=questions]:checked').length===0){
  1680. feedback(ssl,'请勾选题目!');
  1681. return;
  1682. }
  1683. ssl.find('input[name=questions]:checked').each(function(){
  1684. qidArr.push($(this).parent().next().text());
  1685. });
  1686. }
  1687. const setAnswer=unsafeWindow.sm_setmultipleanswer;
  1688. const setComment=unsafeWindow.sm_setcomment;
  1689. const head=save.formhead;
  1690. let count=0;
  1691. for(let h in head){
  1692. if(qidArr.length&&!qidArr.includes(h)) continue;
  1693. if(h==='DATE'){
  1694. if(head[h]===''){
  1695. $('select#dsYear').children(':first').prop('selected',true);
  1696. $('select#dsMonth').children(':first').prop('selected',true);
  1697. $('select#dsDay').children(':first').prop('selected',true);
  1698. }else{
  1699. const date=head[h].split('-');
  1700. $('select#dsYear').children('[value='+date[0]+']').prop('selected',true);
  1701. $('select#dsMonth').children('[value='+date[1]+']').prev().prop('selected',true);
  1702. $('select#dsDay').children('[value='+date[2]+']').prev().prop('selected',true);
  1703. }
  1704. }
  1705. if(h==='TIME'){
  1706. if(head[h]===''){
  1707. $('select#tsHoursHEAD_TIME').children(':first').prop('selected',true);
  1708. $('select#tsMinutesHEAD_TIME').children(':first').prop('selected',true);
  1709. }else{
  1710. const time=head[h].split(':');
  1711. $('select#tsHoursHEAD_TIME').children('[value='+time[0]+']').prop('selected',true);
  1712. $('select#tsMinutesHEAD_TIME').children('[value='+time[1]+']').prop('selected',true);
  1713. }
  1714. }
  1715. if(h==='TIMEOUT'){
  1716. if(head[h]===''){
  1717. $('select#tsHoursHEAD_TIMEOUT').children(':first').prop('selected',true);
  1718. $('select#tsMinutesHEAD_TIMEOUT').children(':first').prop('selected',true);
  1719. }else{
  1720. const time=head[h].split(':');
  1721. $('select#tsHoursHEAD_TIMEOUT').children('[value='+time[0]+']').prop('selected',true);
  1722. $('select#tsMinutesHEAD_TIMEOUT').children('[value='+time[1]+']').prop('selected',true);
  1723. }
  1724. }
  1725. if($('input[name=HEAD_'+h+']').length){
  1726. $('input[name=HEAD_'+h+']').val(head[h]);
  1727. count++;
  1728. }
  1729. }
  1730. const body=save.formbody;
  1731. for(let b of body){
  1732. if(qidArr.length&&!qidArr.includes(b.qid)||$('textarea#C'+b.qid+'C').length===0) continue;//跨问卷读档时跳过没有的题防止updrowH()报错
  1733. const key=Object.keys(b);
  1734. if(key.includes('ans')) setAnswer(b.qid,b.ans);
  1735. if(key.includes('cmt')){
  1736. setComment(b.qid,b.cmt);
  1737. unsafeWindow.updrowH(document.querySelector('textarea#C'+b.qid+'C'));//调整评论框高度
  1738. }
  1739. count++;
  1740. }
  1741. feedback(ssl,'读档成功。(共修改'+count+'题)');
  1742. }
  1743.  
  1744.  
  1745. //存档
  1746. function saveData(ssl,toSave){
  1747. const save={
  1748. 'surveytitle':$('h1.surveytitle').text(),
  1749. 'surveyid':$('input#instanceID').val(),
  1750. 'formhead':{
  1751. 'LOCATION':$('input[name=HEAD_LOCATION]').val()||$('table.visualization_HEAD_LOCATION').find('lookup').text().split(' - ')[0],
  1752. 'SHOPPER':$('input[name=HEAD_SHOPPER]').val(),
  1753. 'DATE':$('input[name=HEAD_DATE]').val(),
  1754. 'TIME':$('input[name=HEAD_TIME]').val(),
  1755. 'TIMEOUT':$('input[name=HEAD_TIMEOUT]').val()
  1756. },
  1757. 'formbody':[]
  1758. };
  1759. const getAnswer=unsafeWindow.sm_getmultipleanswer;
  1760. const getComment=unsafeWindow.sm_getcomment;
  1761. $('a.surveyquestionnobreak').each(function(){
  1762. const qtx=$(this).find('span.surveyquestion').text();
  1763. const qid=$(this).attr('name').replace('qstn','');
  1764. const ans= $('input[name=Q'+qid+'Q]').length? getAnswer(qid) : undefined;
  1765. //ans 已填写:'1,2'||'__na__';未填写:'';无选项列:undefined
  1766. const cmt= getComment(qid);
  1767. //cmt 已填写:'xxx';未填写:'';无评论框:undefined
  1768. save.formbody.push({
  1769. 'qtx':qtx,
  1770. 'qid':qid,
  1771. 'ans':ans,
  1772. 'cmt':cmt
  1773. });
  1774. });
  1775. const saves=GM_getValue(toSave,{});
  1776. saves[new Date().toLocaleString()]=save;
  1777. //存档超过10个覆盖最早存档
  1778. if(Object.keys(saves).length>10) delete saves[Object.keys(saves)[0]];
  1779. GM_setValue(toSave,saves);
  1780. refreshList(ssl,toSave);
  1781. if(toSave==='autosaves') feedback(ssl,'自动存档成功。');
  1782. if(toSave==='selfsaves') feedback(ssl,'手动存档成功。');
  1783. }
  1784.  
  1785. //删除
  1786. function deleteData(ssl){
  1787. const [save,time,saves,fromSave]=checkSave(ssl);
  1788. if(!save) return;
  1789. delete saves[time];
  1790. GM_setValue(fromSave,saves);
  1791. refreshList(ssl);
  1792. feedback(ssl,'删除成功。');
  1793.  
  1794. }
  1795.  
  1796. //检查存档有效性
  1797. function checkSave(ssl){
  1798. if(ssl.find('input[name=saves]:checked').length===0){
  1799. feedback(ssl,'请选择存档!');
  1800. return [];
  1801. }
  1802. const fromSave=ssl.find('input[name=saves]:checked').parents('tbody').attr('id');
  1803. const saves=GM_getValue(fromSave,{});
  1804. const time=ssl.find('input[name=saves]:checked').parent().next().text();
  1805. if(!Object.keys(saves).includes(time)){
  1806. feedback(ssl,'无此存档!(可能已被覆盖)');
  1807. return [];
  1808. }
  1809. return [saves[time],time,saves,fromSave];
  1810. }
  1811.  
  1812. //操作反馈提示
  1813. function feedback(ssl,text,ifConfirm,yesFunc){
  1814. if(ssl.css('display')==='none') return;//界面隐藏时不提示
  1815. if(ssl.find('b#feedback').length===0){
  1816. ssl.find('div#tab').before('<b id=feedback style=margin:5px></b>');
  1817. }
  1818. const b=ssl.find('b#feedback');
  1819. b.text(text).append('<br>');
  1820. if(ifConfirm){
  1821. $('<input type=button value=√ class="rm-btn rm-btn-default">').appendTo(b)
  1822. .on('click',function(){
  1823. $(this).parents('b#feedback').remove();
  1824. yesFunc();
  1825. });
  1826. $('<input type=button value=× class="rm-btn rm-btn-default">').appendTo(b)
  1827. .on('click',function(){
  1828. $(this).parents('b#feedback').remove();
  1829. });
  1830. b.children('input:button').css('margin','5px');
  1831. }else{
  1832. setTimeout(()=>{
  1833. b.remove();
  1834. },3e3);
  1835. }
  1836. }
  1837.  
  1838. //将表头宽度同步为表身
  1839. function theadWidth(thead,tbody){
  1840. if(tbody.find('td').text()==='无数据!') return;
  1841. thead.find('tr td').each(function(){
  1842. const i=$(this).index();
  1843. const w=getComputedStyle(tbody.children('tr:last').children()[i]).width;
  1844. $(this).css('width',w);
  1845. });
  1846. }
  1847.  
  1848. //卸载报告存档
  1849. function unSURVEYSAVES(){
  1850. if(unsafeWindow.OSM||!$('form#frmSurvey').length) return;
  1851. if($('div#surveySaves').length) $('div#surveySaves').remove();
  1852. if(document.location.href.includes('alias=survey.view')){
  1853. $('form#frmSurvey').off('click.autosave');
  1854. $('textarea.surveycomment,textarea.active').off('blur.autosave');
  1855. }
  1856. }
  1857. /*报告存档*/
  1858.  
  1859.  
  1860. /*PDF命名
  1861. function PDFRENAME(){
  1862. if(!document.location.href.includes('exportMultiplePDF.asp')) return;
  1863. $('table#pdfconvertProcessTableAdv tr.reporttable_thead').append('<th id=downloadAll>');
  1864. $('<button style=font-size:10px;width:100px>命名并下载全部</button>').appendTo('table#pdfconvertProcessTableAdv th#downloadAll')
  1865. .on('click',function(){
  1866. $(this).parent().parent().nextAll('tr').find('button').click();
  1867. });
  1868. $('table#pdfconvertProcessTableAdv a[id^=dlLinkAdv]').each(function(){
  1869. const a=$(this);
  1870. const surveyid=a.attr('id').replace('dlLinkAdv','');
  1871. a.parent().after('<td id=download>');
  1872. const td=a.parent().next('td#download');
  1873. $('<button style=font-size:10px;width:100px>命名并下载</button>').appendTo(td)
  1874. .on('click',function(){
  1875. if(a.attr('href')==='/surveyexport_pdf/done.asp?docID='){
  1876. $(this).text('请等待转化完成');
  1877. setTimeout(()=>{
  1878. $(this).text('命名并下载');
  1879. },1e3);
  1880. }else{
  1881. $(this).text('下载中...');
  1882. fetchName(a,surveyid,$(this),0);
  1883. }
  1884. });
  1885. });
  1886. }
  1887.  
  1888. function fetchName(a,surveyid,self,isInternal){
  1889. let name=$('textarea#naming_convention').val();
  1890. const field=new Set(name.match(/(?<=#\$FIELD\[)[A-Za-z0-9\._]+(?=\]\$#)/g));
  1891. if(field.size){
  1892. const fieldDict= isInternal? {
  1893. 'DateSubmitted':'SurveyDateOrDueDate',
  1894. 'TimeSubmitted':'SurveyDateOrDueDate',
  1895. 'Campaign':'CampaignName',
  1896. 'CustomProperty001':'LocationCustomPropertyValue001',
  1897. 'CustomProperty002':'LocationCustomPropertyValue002',
  1898. 'CustomProperty003':'LocationCustomPropertyValue003',
  1899. 'CustomProperty004':'LocationCustomPropertyValue004',
  1900. 'CustomProperty005':'LocationCustomPropertyValue005',
  1901. 'CustomProperty006':'LocationCustomPropertyValue006',
  1902. 'CustomProperty007':'LocationCustomPropertyValue007',
  1903. 'CustomProperty008':'LocationCustomPropertyValue008',
  1904. 'CustomProperty009':'LocationCustomPropertyValue009',
  1905. 'CustomProperty010':'LocationCustomPropertyValue010',
  1906. 'ExportServicePointsScored':'PrecalcScore',
  1907. 'ExportServicePointsPossible':'PrecalcScoreOutOf'
  1908. } : {
  1909. 'SurveyInstanceID':'InstanceID',
  1910. 'DateSubmitted':'Date',
  1911. 'TimeSubmitted':'Time',
  1912. 'LocationId':'Loc ID',
  1913. 'LocationName':'Location Name',
  1914. 'LocationAddress':'Location Address 1',
  1915. 'LocationCity':'Location City',
  1916. 'LocationCounty':'Location County',
  1917. 'LocationState_Region':'Location State/Region',
  1918. 'LocationPostalCode':'Location Postal Code',
  1919. 'LocationCountry':'Location Country',
  1920. 'SurveyTitle':'Title',
  1921. 'CustomProperty001':'CustLocationProperty001',
  1922. 'CustomProperty002':'CustLocationProperty002',
  1923. 'CustomProperty003':'CustLocationProperty003',
  1924. 'CustomProperty004':'CustLocationProperty004',
  1925. 'CustomProperty005':'CustLocationProperty005',
  1926. 'CustomProperty006':'CustLocationProperty006',
  1927. 'CustomProperty007':'CustLocationProperty007',
  1928. 'CustomProperty008':'CustLocationProperty008',
  1929. 'CustomProperty009':'CustLocationProperty009',
  1930. 'CustomProperty010':'CustLocationProperty010',
  1931. 'ExportServicePointsScored':'PrecalcPts',
  1932. 'ExportServicePointsPossible':'PrecalcPtsOf',
  1933. 'ExportServiceScorePercentXX':'PrecalcScorePctXX',
  1934. 'ExportServiceScorePercentXX.X':'PrecalcScorePctXX.X',
  1935. 'ExportServiceScorePercentXX.XX':'PrecalcScorePctXX.XX'
  1936. };
  1937. const qSet=new Set();
  1938. for(let i of field){
  1939. if(isInternal){
  1940. if(i.includes('ServiceScorePercentXX')){
  1941. qSet.add('PrecalcScore');
  1942. qSet.add('PrecalcScoreOutOf');
  1943. continue;
  1944. }
  1945. }
  1946. if(fieldDict[i]) i=fieldDict[i];
  1947. qSet.add(i);
  1948. }
  1949. let query='';
  1950. for(let i of qSet) query+='['+i+'],';
  1951. query=query.slice(0,-1);
  1952. const api= isInternal? '/Apps/SM/APIv2/Query/Operations/Operations' : '/Apps/SM/APIv2/Query/ClientAnalytics/ClientAnalytics';
  1953. $.get(`
  1954. /open/data.asp?post={
  1955. "action":"exec",
  1956. "dataset":{"datasetname":"${api}"},
  1957. "parameters":[
  1958. {"name":"QuerySpecification","value":"${query}"},
  1959. {"name":"SurveyInstanceIDs","value":"${surveyid}"}
  1960. ]
  1961. }`,data=>{//调用API获取当前survey数据(客户端和内部端由于权限不同需要使用不同api)
  1962. if(!isInternal&&data.dataset.data[0].length===0){//如客户端api返回无数据则用内部端再尝试
  1963. fetchName(a,surveyid,self,1);
  1964. }else if(isInternal&&data.dataset.data[0].length===0){//如内部端api返回无数据则报错
  1965. download(a.attr('href'),name+'.pdf',()=>{self.text('无法获取命名数据');});
  1966. return;
  1967. }
  1968. const info=data.dataset.data[0][0];
  1969. for(let i of field){
  1970. const tmp=i;
  1971. if(fieldDict[i]) i=fieldDict[i];
  1972. let r;
  1973. if(isInternal&&tmp.includes('Submitted')){
  1974. if(tmp==='DateSubmitted') r=info[i].split(' ')[0];
  1975. if(tmp==='TimeSubmitted') r=info[i].split(' ')[1];
  1976. }else if(tmp.includes('ServiceScorePercentXX')){
  1977. if(isInternal){
  1978. r=Number(info.PrecalcScore)/Number(info.PrecalcScoreOutOf)*100;
  1979. if(tmp==='ExportServiceScorePercentXX') r=r.toFixed(0)+'%';
  1980. if(tmp==='ExportServiceScorePercentXX.X') r=r.toFixed(1)+'%';
  1981. if(tmp==='ExportServiceScorePercentXX.XX') r=r.toFixed(2)+'%';
  1982. }else{
  1983. r=info[i]+'%';
  1984. }
  1985. }else{
  1986. r=info[i];
  1987. }
  1988. name=name.replaceAll(`#$FIELD[${tmp}]$#`,r);
  1989. }
  1990. download(a.attr('href'),name+'.pdf',()=>{self.text('命名并下载〇');});
  1991. },'json');
  1992. }else{
  1993. download(a.attr('href'),name+'.pdf',()=>{self.text('命名并下载〇');});
  1994. }
  1995. }
  1996.  
  1997. function download(url,filename,done){//done()为执行完毕后执行的函数
  1998. getBlob(url,function(blob){
  1999. saveAs(blob,filename,done);
  2000. });
  2001. }
  2002.  
  2003. function getBlob(url,cb){
  2004. const xhr=new XMLHttpRequest();
  2005. xhr.open('GET',url,true);
  2006. xhr.responseType='blob';
  2007. xhr.onload=function(){
  2008. if (xhr.status === 200){
  2009. cb(xhr.response);
  2010. }
  2011. };
  2012. xhr.send();
  2013. }
  2014.  
  2015. function saveAs(blob,filename,done){
  2016. if (window.navigator.msSaveOrOpenBlob){//Firefox&IE
  2017. navigator.msSaveBlob(blob,filename);
  2018. }else{
  2019. const link=document.createElement('a');
  2020. const body=document.querySelector('body');
  2021. link.href=window.URL.createObjectURL(blob);
  2022. link.download=filename;
  2023. link.style.display='none';
  2024. body.appendChild(link);
  2025. link.click();
  2026. body.removeChild(link);
  2027. window.URL.revokeObjectURL(link.href);
  2028. }
  2029. done();
  2030. }
  2031.  
  2032. //卸载PDF命名
  2033. function unPDFRENAME(){
  2034. if(!document.location.href.includes('exportMultiplePDF.asp')) return;
  2035. $('table#pdfconvertProcessTableAdv th#downloadAll').remove();
  2036. $('table#pdfconvertProcessTableAdv td#download').remove();
  2037. }
  2038. /*PDF命名*/
  2039.  
  2040.  
  2041. /*优化表头*/
  2042. function BETTERTHEAD(){
  2043. if($('table.sticky-thead,#rollupTable').length){
  2044. $('table.sticky-thead,#rollupTable').each(function(){
  2045. execBetterThead(this);
  2046. });
  2047. }
  2048. if(!$('table.persist-area').length&&!document.location.href.includes('alias=client.analysiscustomrollups.3.0')) return;
  2049. unsafeWindow.betterThead_observer=new MutationObserver(function(mutationsList){
  2050. for(let m of mutationsList) execBetterThead(m.target);
  2051. });
  2052. unsafeWindow.betterThead_observer.observe(document.body, {childList: true, subtree: true});
  2053. }
  2054.  
  2055. //根据情况执行优化表头
  2056. function execBetterThead(ele){
  2057. let oTable,oDiv;
  2058. if(ele.className==='sticky-thead'){
  2059. oTable=$(ele).prev('table.sticky-enabled');
  2060. oTable.children('thead').css({position:'sticky',top:$('#menu').height()||0});
  2061. $(ele).hide();
  2062. oDiv=oTable.parents('div.q2r_tableHolder').children('[class^=q2r_tableCaptionDiv]');
  2063. if(oDiv.attr('class')==='q2r_tableCaptionDiv_without_text') oDiv.attr('class','q2r_tableCaptionDiv');
  2064. }
  2065. if(ele.id==='rollupTable'){
  2066. oTable=$(ele).find('table#smReporttableTable');
  2067. oDiv=oTable.prev();
  2068. }
  2069. if(!oTable||!oTable.length||!oDiv||!oDiv.length) return;
  2070. if(oDiv[0].settle) return;
  2071. oDiv[0].settle=1;
  2072. oDiv.css('cursor','pointer').attr('title','显示/隐藏列').on('click.tableCaptionDivFunc',function(){
  2073. tableCaptionDivFunc(this,oTable);
  2074. });
  2075. }
  2076.  
  2077. //表头标题点击功能
  2078. function tableCaptionDivFunc(oDiv,oTable){
  2079. const next=$(oDiv).next();
  2080. if(next.attr('id')==='hideOption'){
  2081. if(next.css('display')==='none'){
  2082. if(oTable.find('thead>tr>td').length===next.find('tr').length) next.show();
  2083. else{
  2084. next.remove();
  2085. tableCaptionDivFunc(oDiv,oTable);
  2086. }
  2087. }else{
  2088. next.hide();
  2089. }
  2090. }else{
  2091. let hideOption='<div id=hideOption><table><thead class=persist-header>',i=0;
  2092. const show=[];
  2093. oTable.find('thead>tr>td').each(function(){
  2094. if(this.style.display!='none') show.push(i);
  2095. hideOption+=`<tr>
  2096. <td class=theadTD><input type=checkbox value=${i++}></td>
  2097. <td class=theadTD>${this.textContent}</td>
  2098. </tr>`;
  2099. });
  2100. next.before(hideOption);
  2101. next.prev('div#hideOption').css({position:'absolute',/*top:tableCaptioDiv.clientHeight,*/zIndex:1,overflow:'scroll',maxHeight:'300px',background:'white'})
  2102. .find('input').on('click',function(){
  2103. toggleCol(this,oTable);
  2104. });
  2105. for(let s of show) next.prev('div#hideOption').find('input').eq(s).prop('checked',true);
  2106. }
  2107. }
  2108.  
  2109. //显示隐藏列
  2110. function toggleCol(checkbox,oTable){
  2111. if($(checkbox).is(':checked')){
  2112. oTable.find('tr').each(function(){
  2113. $(this).children().eq(checkbox.value).show();
  2114. });
  2115. }else{
  2116. oTable.find('tr').each(function(){
  2117. $(this).children().eq(checkbox.value).hide();
  2118. });
  2119. }
  2120. }
  2121.  
  2122. //卸载优化表头
  2123. function unBETTERTHEAD(){
  2124. $('table.sticky-enabled>thead').css({position:'',top:''});
  2125. $('table.sticky-thead').show();
  2126. if($('div#hideOption').length){
  2127. $('div#hideOption').find('input').each(function(){
  2128. if(!$(this).is(':checked')) this.click();
  2129. });
  2130. $('div#hideOption').remove();
  2131. }
  2132. const oDiv1=$('table.sticky-enabled').parents('div.q2r_tableHolder').children('[class^=q2r_tableCaptionDiv]').css('cursor','').attr('title',null).off('click.tableCaptionDivFunc');
  2133. const oDiv2=$('table#smReporttableTable').parent('div#resultsTable').children('div#smAnalysisCustomRollupsTableDiv').css('cursor','').attr('title',null).off('click.tableCaptionDivFunc');
  2134. $(oDiv1,oDiv2).each(function(){delete this.settle;});
  2135. if(unsafeWindow.betterThead_observer) unsafeWindow.betterThead_observer.disconnect();
  2136. }
  2137. /*优化表头*/
  2138.  
  2139.  
  2140. /*外观效果*/
  2141. function CSSEFFECT(){
  2142. iframeInject();
  2143. let css='';
  2144. if(GM_getValue('外观效果_夜间模式',0)){
  2145. const darkExclEle=[
  2146. 'img',
  2147. 'video',
  2148. 'option',
  2149. 'canvas',
  2150. 'div#menu',
  2151. 'div#cover',
  2152. 'ul.innerItemFirst',
  2153. 'ul.textArea',
  2154. 'code>iframe',
  2155. '#ResourceSectionDiv>iframe',
  2156. 'div#graphicPlaceHolder',
  2157. ];
  2158. const darkExtraCSS='ul.textArea img{filter:none}option{background:black !important;color:white}';
  2159. css+='html,'+darkExclEle.toString()+'{filter:invert(1) hue-rotate(180deg)}'+darkExtraCSS;
  2160. if(document.location.href.includes('surveyexport_pdf/exportMultiplePDF.asp')) css+='body{background:white}'
  2161. }
  2162. if(GM_getValue('外观效果_隐藏logo',0)) css+='#sm_headerLogoTable{display:none}#addInfo{height:0 !important}#menu,#cover{top:0 !important}';
  2163. if(style&&$('#'+style.id).length&&css===style.textContent) return;
  2164. if(style) style.remove();
  2165. style=GM_addStyle(css);
  2166. }
  2167.  
  2168. //为特定iframe注入css
  2169. function iframeInject(){
  2170. const list={
  2171. 'iframe#id_iframe_select_language':'img{filter:invert(1) hue-rotate(180deg)}',
  2172. };
  2173. for(let selector in list){
  2174. const iframe=document.querySelector(selector);
  2175. if(!iframe) continue;
  2176. if(iframe.darkCSS) continue;
  2177. iframe.addEventListener('load',()=>{
  2178. const style=document.createElement('style');
  2179. style.textContent=list[selector];
  2180. iframe.contentDocument.head.append(style);
  2181. });
  2182. iframe.darkCSS=true;
  2183. }
  2184. }
  2185.  
  2186. //卸载外观效果
  2187. function unCSSEFFECT(){
  2188. if(style) style.remove();
  2189. }
  2190. /*外观效果*/
  2191.  
  2192.  
  2193. })();