钉钉后台操作麻烦?修改流程要死?现在你可以用js写流程并保存好流程文件,修改与更新只需重新导入一次即可。
// ==UserScript==// @name 钉钉审批流程加强插件// @namespace http://tampermonkey.net/// @version 1.6// @description 钉钉后台操作麻烦?修改流程要死?现在你可以用js写流程并保存好流程文件,修改与更新只需重新导入一次即可。// @author $(ghsot)// @match https://aflow.dingtalk.com/dingtalk/web/query/designCenter*// @match https://oa.dingtalk.com/*// @grant none// @run-at document-start// ==/UserScript==(function() {'use strict';//开始指定指定代码let exec=(code)=>{eval(code);}//供外部代码运行的环境let sandbox={/*基本功能区域*///抓取原数据dataBefore:null,//抓取模板template:null,//从模板中搜索一个控件searchFromTemplate:null,//提供的方法//获取指定目录下成员列表,只能搜索到人,根目录不传或传-1readCompanyList:null,//在公司架构中搜索,可以搜索到部门与人readFrameworkList:null,//搜索指定成员searchFromCompany:null,//获取角色列表readRoleList:null,//从角色缓存列表中查找searchFromRole:null,//保存条件列表saveConditions:null,//保存主要条件部分saveRule:null,//保存审批去重saveProcessSetting:null,//保存提示规则saveNotice:null,//去重规则 数组duplicateList:null,//通知规则 数组noticeList:null,/*加强功能区域*///快速建立发起人genSender:null,//快速建立审批条目//genRule: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'}];//快速建立发起人sandbox.genSender=function(){let senders=[];//添加一个单位 给出完整路径this.genUnit=async(unit,clear)=>{if(clear){this.clear();}let tmp=await sandbox.readFrameworkList(unit);if(!tmp){return false;}if(tmp instanceof Array){senders=senders.concat(tmp);}else{senders.push(tmp);}return this.gen();}//用于循环调用,添加一个单位组合this.genUnitList=async(unitList,clear)=>{if(clear){this.clear();}let r###lt;for(let item of unitList){r###lt=await this.genUnit(item,false);if(!r###lt){return false;}}return r###lt;}//搜索并添加一个单位 -1全都要this.searchAndGenUnit=async(unit,index,clear)=>{if(clear){this.clear();}index=index||0;let tmp=await sandbox.searchFromCompany(unit,0);if(!tmp){return false;}if(index>=0){senders.push(tmp[index]);}else{senders=senders.concat(tmp);}return this.gen();}this.gen=()=>{return {name:-1,senders:senders};}this.clear=()=>{senders=[];}return this;}//快速建立审批条目sandbox.genRule=()=>{}//cookielet 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 fieldfunction 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;}})}//当前流程processlet dingProcess;//当前流程IDlet 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='<input type="file"><p>加载一个脚本文件开始执行任务</p>';let input=div.children[0];//给input赋予事件input.onchange=()=>{let file=input.files[0];let reader=new FileReader();reader.onload=(res)=>{exec(res.target.r###lt);}reader.readAsText(file);}content.insertBefore(div,content.children[0]);}}//覆盖fetchlet fetch=window.fetch;window.fetch=null;let xhr=window.XMLHttpRequest;//从钉钉获取的东西let clientCorpId;let token;//封装一个ajaxlet http = {qs(data){let str = ""for(let i in data){if(str){str+='&';}str += i+'='+encodeURI(data[i]);}return str;},get(url,data,func){if(location.host!='oa.dingtalk.com'){if(url.startsWith('https://oa.dingtalk.com')){//console.log('send');this.childWin.postMessage({verify:true,method:'get',args:[url,data]},'*')this.func=func;return;}}//console.log('recv');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('x-client-corpid',clientCorpId);x.setRequestHeader('hrm_csrf_token',cookieObj.hrm_csrf_token);x.setRequestHeader('X-csrf-token',cookieObj.csrf_token);x.setRequestHeader('X-Requested-With','XMLHttpRequest');x.send();},post(url,data,func,json){if(location.host!='oa.dingtalk.com'){if(url.startsWith('https://oa.dingtalk.com')){this.childWin.postMessage({verify:true,method:'post',args:[url,data,json]},'*')this.func=func;return;}}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('x-client-corpid',clientCorpId);x.setRequestHeader('_csrf_token_',token);x.setRequestHeader('hrm_csrf_token',cookieObj.hrm_csrf_token);x.setRequestHeader('X-csrf-token',cookieObj.csrf_token);x.setRequestHeader('X-Requested-With','XMLHttpRequest');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);}}};//OA页面 到这里就该退出了if(window.location.host=='oa.dingtalk.com'){window.addEventListener('message',function(evt){let obj=evt.data;//防止自己发给自己 无限循环if(!obj.verify){return;}if(obj.method=='get'){http.get(obj.args[0],obj.args[1],(res)=>{window.parent.postMessage(res,'*');})}else{http.post(obj.args[0],obj.args[1],(res)=>{window.parent.postMessage(res,'*');},obj.args[2])}})return;}else{//主页面 打开一个iframelet frame=document.createElement('iframe');frame.src='https://oa.dingtalk.com';frame.style.display='none';frame.onload=function(){http.childWin=frame.contentWindow;//console.log(frame.contentWindow);}document.body.append(frame);//监听数据window.addEventListener('message',function(evt){if(http.func){http.func(evt.data);http.func=null;}})}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.r###lt.values;if(name){resolve(searchFromCompanyList(searchCache[id],name));}else{resolve(searchCache[id]);}}else{reject({});}});})}let frameworkCache={};//从公司架构中搜索 使用路径发送过来 如"总部->业务线","总部->*","总部->运营/事业"将会返回数据数组或者一个itemsandbox.readFrameworkList=(name)=>{if(frameworkCache[name]){//找到缓存了return frameworkCache[name];}let list=name.split('->');return new Promise(async (resolve,reject)=>{//try{//一步一步查找let tmpList=[];for(let i=0;i<list.length;i++){let currentId=i>0?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]=='*'){//缓存一下frameworkCache[name]=tmpList[i];//这个目录下所有都要resolve(tmpList[i]);return;}let multiList=list[i].split('/');//console.log(multiList);if(multiList.length>1){let r###ltList=[];//需要多个东西for(let it of multiList){r###ltList.push(searchFromCompanyList(tmpList[i],it));}//缓存一下frameworkCache[name]=r###ltList;resolve(r###ltList);return;}//仅仅需要一个结果//console.log(tmpList);let r###lt=searchFromCompanyList(tmpList[i],list[i]);//缓存一下frameworkCache[name]=r###lt;resolve(r###lt);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)}})}let companyUnitCache={};//搜索某一个人名字sandbox.searchFromCompany=(keyword,pack)=>{keyword=keyword||'';let get=(list,pack)=>{if(!isNaN(parseInt(pack))){//包装出去let unit=list[pack];if(unit){return {type:0,obj:unit};}else{return null;}}else{//不包装return list;}}if(companyUnitCache[keyword]){//找到缓存了return get(companyUnitCache[keyword],pack);}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)=>{if(res){//缓存一下companyUnitCache[keyword]=res.r###lt.values;let r###lt=get(res.r###lt.values,pack);r###lt?resolve(r###lt):reject({});}else{reject({});}//res!=null?resolve(res.r###lt.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.r###lt;resolve(res.r###lt);}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,pack,act)=>{if(roleListCache){for(let item of roleListCache){if(item.labels){for(let child of item.labels){if(name==child.name){if(pack){let r###lt={type:1,obj:child};if(act){r###lt.act=act;}return r###lt;}else{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'];const humanClass=['TargetApprovalConfExtensionDO','TargetLabelConfExtensionDO','TargetManagersLabelsConfExtensionDO']/*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'},{name:'NumberField',type:'dingtalk_actioner_range_condition',//classAlias:'DingTalkActionerRangeConfExtensionDO'}];/*rule:type 0 默认规则 1判断规则notifiers 抄送者type 类型obj 搜索出来的角色或者人物rules 规则type 类型obj 搜索出来的角色或者人物act 审批类型,不传:默认,用户自己选择;or:其中一人同意即可;and:所有人同意才OK type为1时起作用conds 其他条件control 控件values 如果是选择框,提供满足选择的list> 如果是数字输入框,提供数字区间,5个key可供使用>=<<===senders 如果是发起人,给个数组从searchFromCompany或者readFrameworkList中返回的对象数组*/sandbox.saveRule=(rules)=>{console.log(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;//一旦有错误 立即返回 包含msg objlet error;//处理审批人/抄送人let dealList=(src,dest,withClass,notifierType)=>{console.log(src);for(let item of src){if(!item){error={msg:'审批/抄送拿到了null,返回整个列表',obj:src};return;}let n={};n.type=humanList[item.type];/*if(withClass){n.classAlias=humanClass[item.type];}*/if(!n.type){error={msg:'审批/抄送为不支持的单位类型,返回整个列表',obj:src};return;}if(!item.obj){error={msg:'审批/抄送中有空的对象,返回整个列表',obj:src};return;}//加入actTypeif(item.act){n.actType=item.act;}switch(item.type){case 0://一个人n.approvals=[];if(item.obj instanceof Array){for(let child of item.obj){if(!child.employee||!child.employee.orgStaffId){error={msg:'无效的数据,返回该对象',obj:child};return;}n.approvals.push(child.employee.orgStaffId);}}else{n.approvals.push(item.obj.employee.orgStaffId);}break;case 1:case 2://一个角色n.labels=[];n.labelNames=[];if(item.obj instanceof Array){for(let child of item.obj){n.labels.push(child.id);n.labelNames.push(child.name);}}else{n.labels.push(item.obj.id);n.labelNames.push(item.obj.name);}/*if(withClass&¬ifierType){delete n.labelNames;}*/break;default:error={msg:'有不支持的审批/抄送单位类型,返回出错对象',obj:item};return;break;}dest.push(n);}}let _notifiers=[];let _rules=[];let _notifiersWithClass=[];let _rulesWithClass=[];//递归处理条件let addCond=(list,status)=>{let content={};let cond=list.shift();//计算出indexlet index=0;let name=cond.name||(cond.control?cond.control.componentName:'');if(!name){error={msg:'无效的判断条件数据,返回出错对象',cond};return;}for(let item of condList){if(name==item.name){break;}index++;}//console.log(index);if(index>=condList.length){error={msg:'有不支持的条件控件,返回出错对象',obj:cond};return;}//加入数据/*if(status&&condList[index].classAlias){content.classAlias=condList[index].classAlias;}*/content.type=condList[index].type;content.exclMgrDeptsDepth=0;content.status=status;if(status){//class//抄送人content.notifiers=_notifiersWithClass;//规则content.rules=_rulesWithClass;}else{//no classcontent.notifiers=_notifiers;content.rules=_rules;}status=status||1;content.paramKey=condList[index].id||cond.control.props.id;content.paramLabel=condList[index].label||cond.control.props.label;content.isEmpty=false;switch(index){case 0:content.conds=[];for(let sender of cond.senders){let n={};if(sender.nodeType==1){n.value=sender.employee.orgId;n.type='user';n.attrs={name:sender.employee.orgUserName,avatar:''}}else{n.value=sender.dept.deptId;n.type='dept';n.attrs={name:sender.dept.deptName,memberCount:sender.dept.memberCount}}content.conds.push(n);}break;case 1:case 3:content.upperBound=cond['<']||'';content.lowerBoundNotEqual=cond['>']||'';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;}//继续递归if(list.length>0){//还有数据,继续递归content.multiRules=[addCond(list,status)];}return content;}//开始处理数据for(let rule of rules){let item={};//抄送者_notifiers=[];dealList(rule.notifiers,_notifiers);if(error){return error;}//审批者_rules=[];dealList(rule.rules,_rules);if(error){return error;}_notifiersWithClass=[];dealList(rule.notifiers,_notifiersWithClass,true,true);_rulesWithClass=[];dealList(rule.rules,_rulesWithClass,true);//判断是否是默认if(rule.type==0){//默认item.type='dingtalk_actioner_default';item.rules=_rules;item.notifiers=_notifiers;//未知值 写死0item.exclMgrDeptsDepth=0;//状态值 写死0item.status=0;defaultCount++;continue;}//不是默认,开始处理if(rule.conds&&rule.conds.length>0){item=addCond(rule.conds,0);if(error){return error};}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;case 'setRequestHeader':this[key]=setHeader;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);}//setHeaderfunction setHeader(){if(arguments[0]=='x-client-corpid'){clientCorpId=arguments[1];}else if(arguments[0]=='_csrf_token_'){token=arguments[1];}obj.setRequestHeader.apply(obj,arguments);}};})();})();