// ==UserScript== // @name 钉钉审批流程加强插件 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 钉钉后台操作麻烦?修改流程要死?现在你可以用js写流程并保存好流程文件,修改与更新只需重新导入一次即可。 // @author $(ghsot) // @match https://aflow.dingtalk.com/dingtalk/web/query/processDesign* // @grant none // @run-at document-start // @downloadURL none // ==/UserScript== (function() { 'use strict'; /*let define=Object.defineProperty; Object.defineProperty=function(){ //console.log(arguments); define.apply(Object,arguments); }*/ /*function print(title,data){ console.log('==============='+title+'===============start'); console.log(data); console.log('==============='+title+'===============end'); }*/ //供外部代码运行的环境 let sandbox={ //开始指定指定代码 exec:(code)=>{ eval(code); }, //抓取原数据 dataBefore:null, //抓取模板 template:null, //从模板中搜索一个控件 searchFromTemplate:null, //提供的方法 //获取指定目录下成员列表,只能搜索到人,根目录不传或传-1 readCompanyList:null, //在公司架构中搜索,可以搜索到部门与人 readFrameworkList:null, //搜索指定成员 searchFromCompany:null, //获取角色列表 readRoleList:null, //从角色缓存列表中查找 searchFromRole:null, //保存条件列表 saveConditions:null, //保存主要条件部分 saveRule:null, //保存审批去重 saveProcessSetting:null, //保存提示规则 saveNotice:null, //去重规则 数组 duplicateList:null, //通知规则 数组 noticeList:null }; window.sandbox=sandbox; //内部代码执行闭包 (function(){ sandbox.duplicateList=[ {name:'不自动去重',value:'allow'}, {name:'同一个审批人在流程中出现多次时,仅保留第一个',value:'no_after'}, {name:'同一个审批人在流程中出现多次时,仅保留最后一个',value:'no'}, {name:'仅同一个审批人连续出现时,自动去重',value:'allow_interval'} ]; sandbox.noticeList=[ {name:'仅全部同意后通知',value:'finish'}, {name:'仅发起时通知',value:'start'}, {name:'发起时和全部同意后均通知',value:'start_finish'} ]; //cookie let arr=document.cookie.split(';'); let cookieObj={}; for(let item of arr){ let inner=item.split('='); cookieObj[inner[0].replace(/(^\s*)|(\s*$)/g, "")]=inner[1].replace(/(^\s*)|(\s*$)/g, ""); } //hook field function field(obj,name,getter,setter){ let val=obj[name]; Object.defineProperty(obj,name,{ get(){ val=getter?getter(val):val; return val; }, set(value){ val=setter?setter(value,val):val; } }) } //当前流程process let dingProcess; //当前流程ID let dingId; //检查工作是否完成 let initStarted=false; function checkMisson(){ if(sandbox.dataBefore&&sandbox.template&&!initStarted){ initStarted=true; //向网页中添加元素 let content=document.getElementsByClassName('approval-page-container')[0]; let div=document.createElement('div'); div.innerHTML='

加载一个脚本文件开始执行任务

'; let input=div.children[0]; //给input赋予事件 input.onchange=()=>{ let file=input.files[0]; let reader=new FileReader(); reader.onload=(res)=>{ sandbox.exec(res.target.result); } reader.readAsText(file); } content.insertBefore(div,content.children[0]); } } //覆盖fetch let fetch=window.fetch; window.fetch=null; let xhr=window.XMLHttpRequest; //封装一个ajax let http = { qs(data){ let str = "" for(let i in data){ if(str) { str+='&'; } str += i+'='+data[i]; } return str; }, get(url,data,func){ let x = new xhr(); x.onreadystatechange = function(){ if(x.readyState== 4){ if(x.status==200){ func(JSON.parse(x.responseText)); }else{ func(null); } } } x.withCredentials=true; let str = this.qs(data); x.open('GET',url+(str?('?'+str):''),true); //x.setRequestHeader('hrm_csrf_token',cookieObj.hrm_csrf_token); //x.setRequestHeader('X-csrf-token',cookieObj.csrf_token); x.send(); }, post(url,data,func,json){ let x = new xhr(); x.onreadystatechange = function(){ if(x.readyState== 4){ if(x.status==200){ func(JSON.parse(x.responseText)); }else{ func(null); } } } x.withCredentials=true; x.open('POST',url,true); //x.setRequestHeader('hrm_csrf_token',cookieObj.hrm_csrf_token); //x.setRequestHeader('X-csrf-token',cookieObj.csrf_token); if(json){ x.setRequestHeader('content-type','application/json'); //发送 x.send(JSON.stringify(data)); }else{ x.setRequestHeader('content-type','application/x-www-form-urlencoded'); let str= this.qs(data); x.send(str); } } }; let searchCache={}; //从搜索结果中查找 function searchFromCompanyList(list,name){ //console.log(list); for(let item of list){ if(item.nodeType==0){ if(item.dept.deptName==name){ return item; } }else if(item.nodeType==1){ if(item.employee.orgUserName==name){ return item; } } } return null; } //获取指定目录公司列表 sandbox.readCompanyList=(id,name)=>{ id=id||-1; return new Promise((resolve,reject)=>{ //如果有缓存 直接返回 if(searchCache[id]) { if(name){ resolve(searchFromCompanyList(searchCache[id],name)); }else{ resolve(searchCache[id]); } return; } let url='https://oa.dingtalk.com/omp/lwpV2'; http.get(url,{ timestamp:new Date().getTime(), key:'ContactGetOrgNodeList', args:JSON.stringify([id,0,null,0,30,{"appId":-4,"nodeType":2,"type":"w"}]) },(res)=>{ //console.log(res) if(res){ searchCache[id]=res.result.values; if(name){ resolve(searchFromCompanyList(searchCache[id],name)); }else{ resolve(searchCache[id]); } }else{ reject(); } }); }) } //从公司架构中搜索 使用路径发送过来 如"总部->业务线","总部->*","总部->运营/事业"将会返回数据数组或者一个item sandbox.readFrameworkList=(name)=>{ let list=name.split('->'); return new Promise(async (resolve,reject)=>{ //try{ //一步一步查找 let tmpList=[]; for(let i=0;i0?tmpList[i-1].dept.deptId:'-1'; //console.log(currentId); if(i==list.length-1){ //这是最后一个了 let tmp=await sandbox.readCompanyList(currentId); if(!tmp){ reject(); return; } tmpList.push(tmp); if(list[i]=='*'){ //这个目录下所有都要 resolve(tmpList[i]); return; } let multiList=list[i].split('/'); //console.log(multiList); if(multiList.length>1){ let resultList=[]; //需要多个东西 for(let it of multiList){ resultList.push(searchFromCompanyList(tmpList[i],it)); } resolve(resultList); return; } //仅仅需要一个结果 //console.log(tmpList); resolve(searchFromCompanyList(tmpList[i],list[i])) return; }else{ //还不是最后一个 let tmp=await sandbox.readCompanyList(currentId,list[i]); if(!tmp||tmp.nodeType!=0){ //搜出来竟然不是目录,返回 reject(); return; } tmpList.push(tmp); } } reject(); //}catch(e){console.log(e)} }) } //搜索某一个人名字 sandbox.searchFromCompany=(keyword)=>{ keyword=keyword||''; return new Promise((resolve,reject)=>{ let url='https://oa.dingtalk.com/omp/lwpV2'; http.get(url,{ timestamp:new Date().getTime(), key:'ContactSearchList', args:JSON.stringify([keyword,0,0,30,{"appId":-4,"type":"w"}]) },(res)=>{ res!=null?resolve(res.result.values):reject(); }) }) } //角色列表缓存 let roleListCache; //获取角色列表 sandbox.readRoleList=()=>{ return new Promise((resolve,reject)=>{ let url='https://oa.dingtalk.com/omp/lwpV2?timestamp=1532164518661&key=LabelGetGroupInfoByPage&args=[null,1,0,100000000]'; http.get(url,{},(res)=>{ if(res){ roleListCache=res.result; resolve(res.result); }else{ reject(); } }) }) } //保存条件列表 -1 发起人 sandbox.saveConditions=(list)=>{ let sendList=[]; let data={}; data.processCode=dingProcess; for(let item of list){ if(item==-1){ //发起人条件 sendList.push({id:'dingtalk_origin_dept',label:'发起人',type:'dept',value:[]}); }else{ //自定义模板条件 for(let temp of sandbox.template){ if(temp.props.label==item){ //找到了 sendList.push({id:temp.props.id,label:item,type:temp.props.options?'value':'range',value:[]}); break; } } } } //console.log(sendList); data.conditionRule=JSON.stringify(sendList); return new Promise((resolve,reject)=>{ http.post('https://aflow.dingtalk.com/dingtalk/web/query/rule/setConditionRule.json',data,(res)=>{ res!=null?resolve(res):reject(); }) }) } //从角色中查找 缓存 sandbox.searchFromRole=(name)=>{ if(roleListCache){ for(let item of roleListCache){ if(item.labels){ for(let child of item.labels){ if(name==child.name){ return child; } } } } } return null; } //从模板中搜索一个控件 sandbox.searchFromTemplate=(name)=>{ for(let item of sandbox.template){ if(item.props.label==name){ return item; } } return null; } //保存主要规则数据 //单位类型 //target_approval 0一个人 //target_label 1一个组 //target_managers_labels 2从直属上级一直到该组 levels:[1] const humanList=[ 'target_approval', 'target_label', 'target_managers_labels' ]; /* 0 选择框 paramValues为符合条件的所有选项数组 1 数字框 */ const condList=[ { //发起人条件 name:-1, type:'dingtalk_actioner_dept_condition', id:'dingtalk_origin_dept', label:'发起人' }, { //金额输入 name:'MoneyField', type:'dingtalk_actioner_range_condition' //classAlias:'DingTalkActionerRangeConfExtensionD0' }, { //单选框 name:'DDSelectField', type:'dingtalk_actioner_value_condition' //classAlias:'DingTalkActionerValueConfExtensionD0' } ]; /* rule: type 0 默认规则 1判断规则 notifiers 抄送者 type 类型 obj 搜索出来的角色或者人物 弃用: id ID name 名称,approval不需要 rules 规则 type 类型 obj 搜索出来的角色或者人物 弃用: id ID name 名称,approval不需要 conds 其他条件 control 控件 values 如果是选择框,提供满足选择的list > 如果是数字输入框,提供数字区间,5个key可供使用 >= < <= == senders 如果是发起人,给个数组 从searchFromCompany或者readFrameworkList中返回的对象数组 */ sandbox.saveRule=(rules)=>{ //try{ let data={}; data.id=dingId; data.processCode=dingProcess; data.ruleType='dingtalk_multi_actioner'; let content={}; content.rules={type:'dingtalk_actioner',rules:[]}; content.type='dingtalk_multi_actioner'; content.multiRules=[]; let defaultCount=0; for(let rule of rules){ let item={}; let dealList=(src,dest)=>{ for(let item of src){ let n={}; n.type=humanList[item.type]; if(item.type==0){ //一个人 n.approvals=[item.obj.employee.orgStaffId]; }else{ //一个角色 n.labels=[item.obj.id]; n.labelNames=[item.obj.name]; } dest.push(n); } } //加入抄送者 let notifiers=[]; dealList(rule.notifiers,notifiers); //加入审批者 let rules=[]; dealList(rule.rules,rules); //判断是否是默认 if(rule.type==0){ //默认 item.type='dingtalk_actioner_default'; item.rules=rules; item.notifiers=notifiers; //未知值 写死0 item.exclMgrDeptsDepth=0; //状态值 写死0 item.status=0; defaultCount++; continue; } //否则这是一个条件 let status=0; //加入其他条件 递归 let addCond=(list)=>{ let content={}; let cond=list.shift(); //计算出index let index=0; let name=cond.name||cond.control.componentName; for(let item of condList){ if(name==item.name){ break; } index++; } if(index']||''; content.lowerBound=cond['>=']||''; content.bondEqual=cond['=']||''; content.upperBoundEqual=cond['<=']||''; content.key='g'; break; case 2: content.paramValues=cond.values; content.oriValue=cond.control.props.options; break; } } //抄送人 content.notifiers=notifiers; //规则 content.rules=rules; //继续递归 if(list.length>0){ //还有数据,继续递归 content.multiRules=[addCond(list)]; } return content; } //开始调用 if(rule.conds&&rule.conds.length>0){ item=addCond(rule.conds); } content.multiRules.push(item); } if(defaultCount==0){ //需要帮忙加一个空的进去 let defaultList=[]; defaultList.push({ type:'dingtalk_actioner_default', exclMgrDeptsDepth:0, rules:[], notifiers:[], status:0 }); content.multiRules=defaultList.concat(content.multiRules); } console.log(content); //return; data.ruleConf=JSON.stringify(content); return new Promise((resolve,reject)=>{ http.post('https://aflow.dingtalk.com/dingtalk/web/query/rule/setRuleConfInfo.json',data,(res)=>{ res!=null?resolve(res):reject(); }) }) //}catch(e){console.log(e);} } //保存去重设置 sandbox.saveProcessSetting=(duplicate_approval)=>{ let data={}; data.processCode=dingProcess; let settings=[]; settings.push({type:'proc_append_enable',value:'n'}); settings.push({type:'proc_duplicate_approval',value:duplicate_approval}); data.settings=JSON.stringify(settings); return new Promise((resolve,reject)=>{ http.post('https://aflow.dingtalk.com/dingtalk/web/query/process/setProcessSetting.json',data,(res)=>{ res!=null?resolve(res):reject(); }) }) } //保存提示信息 sandbox.saveNotice=(rule)=>{ let data={}; data.processCode=dingProcess; data.noticePosition=rule; return new Promise((resolve,reject)=>{ http.post('https://aflow.dingtalk.com/dingtalk/web/query/notice/setNoticePosition.json',data,(res)=>{ res!=null?resolve(res):reject(); }) }) } //放入window开始测试 //window.testFunc=readRoleList; //开始代理原类 window.XMLHttpRequest=function(){ let obj=new xhr(); //内部的任务开关 let catchDataBefore=false; let catchTemplate=false; //代理所有内容 for(let key in obj){ if(typeof obj[key]=='function'){ switch(key){ case 'open': this[key]=open; break; case 'send': this[key]=send; break; default: this[key]=function(){ obj[key].apply(obj,arguments); } break; } }else{ field(this,key,function(){ //当数据被获取时,也同时获取一份 switch(key){ case 'response': //获取之前数据 if(catchDataBefore){ catchDataBefore=false; let data=JSON.parse(obj.responseText); sandbox.dataBefore=JSON.parse(data.data.ruleConf); dingProcess=data.data.name; dingId=data.data.id; console.log(dingProcess,dingId); checkMisson(); //print('规则数据',dataBefore); } //获取模板 if(catchTemplate){ catchTemplate=false; let data=JSON.parse(obj.responseText); let inner=JSON.parse(data.data.content); sandbox.template=[]; for(let item of inner.items){ if(item.props.required) { sandbox.template.push(item); } } checkMisson(); //print('模板',template); } break; } return obj[key]; },function(val){ obj[key]=val; }) } } //open方法 function open(){ //console.log('execexec'); //抓取原数据 if(arguments[1].indexOf('getProcessRuleConfInfo.json')>=0&&!sandbox.dataBefore){ sandbox.dataBefore=1; catchDataBefore=true; } //抓取审核模板 if(arguments[1].indexOf('getForm.json')>=0&&!sandbox.template){ //console.log('我执行了'); sandbox.template=1; catchTemplate=true; } //执行原方法 obj.open.apply(obj,arguments); } //send方法 function send(){ obj.responseType=''; obj.send.apply(obj,arguments); } }; })(); })();