Index
引言
上次在《kintone和钉钉、阿里云的整合》中介绍了如何将钉钉上的数据同步到kintone上。
而随着kintone钉钉小程序SDK的发布,我们可更加方便地实现钉钉和kintone的整合。
下面让我们看一下kintone钉钉小程序SDK能做些什么,然后通过范例进一步解说。
关于kintone钉钉小程序SDK
该SDK也是基于kintone的JS SDK改造而得,因此如果您用过kintone的JS SDK,或者看过《kintone微信小程序SDK使用实例》,应该已经很熟悉了吧。
kintone钉钉小程序SDK中封装好了kintone的大部分REST-API,并调用了dd.request来发送请求到kintone。
ddSendRequest(requestConfig) { // Execute request return new Promise((resolve, reject) => { requestConfig.fail = err => { reject(err); }; switch (requestConfig.fileMethod) { case 'download': { requestConfig.success = res => { resolve({filePath: res.filePath}); }; break; } case 'upload': { requestConfig.success = res => { if (res.statusCode === 200) { resolve(JSON.parse(res.data)); } else { reject({...res, data: JSON.parse(res.data)}); } }; break; } default: { requestConfig.success = res => { if (res.status === 200) { resolve(res.data); } else { reject(res); } }; } } this.callDdAPI(requestConfig); }); } callDdAPI(requestConfig) { switch (requestConfig.fileMethod) { case 'download': { return dd.downloadFile(requestConfig); } case 'upload': { return dd.uploadFile(requestConfig); } default: { return dd.httpRequest(requestConfig); } } }
还封装了上传下载文件(dd.uploadFile,dd.downloadFile)的函数。能让大家在调用上传下载文件的时候,能返回给kintone所必须的fileKey。
async downloadFile(path, params) { const requestConfig = this.buildRequestConfig('get', path, params, {fileMethod: 'download'}); return this.sendRequest(requestConfig); } async uploadFile(path, params) { const requestConfig = this.buildRequestConfig('post', path, {}, { filePath: params.filePath, fileName: 'file', fileType: 'image', fileMethod: 'upload', }); return this.sendRequest(requestConfig); } async sendRequest(requestConfig) { let response; try { response = await this.ddSendRequest(requestConfig); } catch (error) { this.errorResponseHandler(error); } return response; } callDdAPI(requestConfig) { switch (requestConfig.fileMethod) { case 'download': { return dd.downloadFile(requestConfig); } case 'upload': { return dd.uploadFile(requestConfig); } default: { return dd.httpRequest(requestConfig); } } }
范例
这次我们的课题是在钉钉端自定义创建一个“日报”小程序,在打开“日报”小程序的填写日报页面时,获取考勤时间,提交日报的同时将考勤时间、所属部门,日报相关数据同步到kintone。
在kintone上编辑内容后,更改的内容及时同步到日报小程序上。
准备阶段
安装Node.js
请点击以下链接,根据自身的环境选择安装Node.js。
https://nodejs.org/zh-cn/
※Node.js必须是8.9.3或更高的版本。
如您之前已经安装过Node.js,请确认版本。
下载钉钉开发者工具
点击此处,根据自身环境点击下载“小程序开发者工具”的最新版。
双击下载好的安装包,根据提示完成安装。
在钉钉端创建日报应用
日报主要有日报详情页面,日报列表页面,日报添加页面。
各页面的内容和字段请参考下图。
本次提供了小程序的源代码,请参考钉钉小程序资源库的SAMPLE-rest-api-client-dingtalk-mp-cn-master\DailyReport\pages
关于应用的创建方法请参考《kintone和钉钉、阿里云的整合》篇,创建后记录appkey和appsecret(在后面需要用到)。
在kintone端创建日报应用
为方便大家确认本范例,此处提供了日报模板,请下载后,导入到kintone。
关于模板创建应用的方法请参考《kintone和钉钉、阿里云的整合》的kintone篇
导入钉钉小程序
点击钉钉小程序资源库,把整个资源库克隆或下载到本地。
资源库根目录下的DailyReport目录是钉钉的日报小程序项目。打开命令提示符,执行以下命令切换到DailyReport目录。以下例子中,
SAMPLE-rest-api-client-dingtalk-mp-cn-master是放在C:\Users\xxxx下面。然后执行以下命令进行安装。
npm install
修改KintoneConfig.js
根据你的kintone环境,修改DailyReport/common/kintoneConfig.js 文件中的baseUrl、username、password 和 appId 字段baseUrl: 'https://xxx.cybozu.cn', //kintone的base Url username: 'xxx', // kintone的用户名 password: 'xxx', // 登录密码 appId: 'xxx', // "日报"应用的ID
修改dingTalkConfig.js
根据实际情况,修改 DailyReport/common/dingTalkConfig.js 文件中的 appkey、appsecret和 userid 字段。
关于如何获取appkey和appsecret请参考钉钉帮助。
关于如何获取请参考钉钉帮助。appkey: 'xxx', // 用来生成访问钉钉的access_token appsecret: 'xxx', // 用来生成访问钉钉的access_token userid: 'xxx', // 钉钉上的用户id
导入小程序项目
6.1 打开钉钉开发者工具,新建项目。
6.2 选取本地目录
输入项目名称,选择DailyReport 的目录,项目类型选择企业内部应用。
6.3 显示如下页面后,分别在①和②中选择“企业内部应用”和“日报”,然后点击“OK”。
到这里,导入步骤就完成了。
钉钉开发者平台上的设置
设置安全域名
打开钉钉开放平台页面,点击右上方的“登录开发者后台”,在登录页面输入登录信息后登录
在登录后的页面中,选择“应用开发”,然后选择“日报”应用。
选择“日报”的“设置安全域名”选项卡,点击第一个“添加”按钮,添加kintone的域名(xxxx.cybozu.com)和钉钉的api域名(oapi.dingtalk.com)。
设置服务器公网出口IP名单
上面的操作是在“设置安全域名”选项卡下操作的,这次选择“应用首页”选项卡”,然后点击应用信息的“查看详情”连接。
点击右上角的“修改,”在页面最底部的“服务器公网出口IP名单”中输入当前运行代码的电脑的公网IP。
设置接口权限
在上面的页面中点击“接口权限”选项卡,开通“身份验证”和“通讯录只读权限”。
到这里,所有设置都完成了。接下来我们试写一下日报。
动作确认
在手机上打开钉钉应用,找到日报图标并打开,点击添加日报按钮。
在日报编辑页面时会获取考勤数据,根据需要可更改考勤时间。
输入日报,可上传图片,最多可上传3张。完成后点击“提交”按钮。
打开kintone的日报应用,确认刚才提交的报告是否添加到kintone的应用里。
领导等查看完日报后可以在kintone上写评语,保存的同时,数据也会同步到钉钉的日报应用里。
到这里,用kintone钉钉小程序SDK来进行钉钉和kintone的整合开发就完成了!
代码解说
资源目录
下面是钉钉小程序开发工具上的目录结构。
该目录结构基本类似于《微信小程序的目录结构》。
common:
放置静态资源和公用的函数。
dingTalkConfig.js定义了钉钉端的配置信息。
kintoneConfig.js定义了kintone的配置信息,kintone的字段代码等。
utility.js定义了一些公用的kintone的连接函数、数据初始化等函数。node_modules:
钉钉小程序自动编译的第三方库。pages:
日报小程序的源代码。
日报源代码的目录如下:
add:里面含有添加页面的HTML、CSS、JS、JSON代码
detail:里面含有日报详情页面的HTML、CSS、JS、JSON代码
index:里面含有列表页面顶部菜单及添加按钮的HTML、CSS、JS、JSON代码
list:日报列表的HTML、CSS、JS、JSON代码
各页面的数据获取传递等处理请参考各文件夹内的JS文件。
例如,以下是添加日报时的处理。
//连接kintone的处理 const kintoneConfig = require('../../common/kintoneConfig'); const dingTalkConfig = require('../../common/dingTalkConfig'); const utility = require('../../common/utility'); //获取添加日报页面的各项数据处理并传递给kintone Page({ data:{ department:'', name:'', workingHours: utility.getHour(), offHours: utility.getHour(), think:'', workingHoursTitle:kintoneConfig.field.workingHours.name, offHoursTitle:kintoneConfig.field.offHours.name, workTitle:kintoneConfig.field.work.name, thinkTitle:kintoneConfig.field.think.name, imageTitle:kintoneConfig.field.image.name, src:[], fileKeys:[], work:'今日工作:\n' + '\n' + '明日预定:\n', }, onLoad(query) { this.getToken(); }, getToken(){ let t = this; dd.httpRequest({ headers: { "Content-Type": "application/html", }, url: 'https://oapi.dingtalk.com/gettoken', method: 'GET', data: { appkey: dingTalkConfig.appkey, appsecret: dingTalkConfig.appsecret, }, dataType: 'json', success: function(res) { if(res.status === 200){ t.getUser(res.data['access_token']); t.getWorkingHour(res.data['access_token']); } }, fail: function(res) { console.log(res); } }); }, getUser(token){ let t = this; dd.httpRequest({ headers: { "Content-Type": "application/html", }, url: 'https://oapi.dingtalk.com/user/get', method: 'GET', data: { access_token: token, userid: dingTalkConfig.userid, }, dataType: 'json', success: function(res) { if(res.status === 200){ t.setData({ name: res.data['name'] }); t.getDepart(token,res.data['department']); } }, fail: function(res) { console.log(res); } }); }, getDepart(token,departmentId){ let t = this; dd.httpRequest({ headers: { "Content-Type": "application/html", }, url: 'https://oapi.dingtalk.com/department/get', method: 'GET', data: { access_token: token, id: departmentId, }, dataType: 'json', success: function(res) { if(res.status === 200){ t.setData({ department: res.data['name'] }); } }, fail: function(res) { console.log(res); } }); }, getWorkingHour(token){ let t = this; dd.httpRequest({ headers: { "Content-Type": "application/json", }, url: 'https://oapi.dingtalk.com/attendance/listRecord?access_token=' + token, method: 'POST', data: JSON.stringify({ userIds: [dingTalkConfig.userid], checkDateFrom: utility.getDateFrom(), checkDateTo: utility.getDateTo(), offset: 0, limit: 10 }), dataType: 'json', success: function(res) { if(res.status === 200){ let records = res.data['recordresult']; records.forEach(function (v, index) { if(v.hasOwnProperty('checkType') && v['checkType'] == "OnDuty"){ t.setData({ workingHours: utility.formatHour(v['userCheckTime']) }); }else if(v.hasOwnProperty('checkType') && v['checkType'] == "OffDuty"){ t.setData({ offHours: utility.formatHour(v['userCheckTime']) }); } }); } }, fail: function(res) { console.log(res); } }); }, addRecord(){ const kintoneRecord = utility.kintoneClient.record; let record = {}; record[kintoneConfig.field.user_id.code] = {value: dingTalkConfig.userid}; record[kintoneConfig.field.department.code] = {value: this.data.department}; record[kintoneConfig.field.name.code] = {value: this.data.name}; record[kintoneConfig.field.workingHours.code] = {value: this.data.workingHours}; record[kintoneConfig.field.offHours.code] = {value: this.data.offHours}; record[kintoneConfig.field.work.code] = {value: this.data.work}; record[kintoneConfig.field.think.code] = {value: this.data.think}; if (this.data.fileKeys.length > 0) { let imgKeys=[]; this.data.fileKeys.forEach(function (v, i) { imgKeys[i] = {fileKey:v}; }); record[kintoneConfig.field.image.code] = {value: imgKeys}; } kintoneRecord.addRecord({app: kintoneConfig.appId, record: record}).then(rsp => { dd.redirectTo({ url: '../detail/detail?id=' + rsp.id }) }).catch(e => { console.log(e.get()); }); }, bindStartPickerChange(e) { this.setData({ workingHours: e.detail.value, }); }, bindEndPickerChange(e) { this.setData({ offHours: e.detail.value, }); }, bindWorkTextAreaBlur(e) { this.setData({ work: e.detail.value, }); }, bindThinkTextAreaBlur(e) { this.setData({ think: e.detail.value, }); }, chooseImage() { dd.chooseImage({ sourceType: ['camera', 'album'], count: 3, success: (res) => { if (res && res.filePaths) { const urls = []; const files = []; res.filePaths.forEach(function (url, index) { urls[index] = url; const kintoneFile = utility.kintoneClient.file; kintoneFile.uploadFile({filePath: url}).then((rsp) => { files[index] = rsp.fileKey; }).catch((err) => { console.log(err); }); }); this.setData({ fileKeys:files, src:urls, }); } } }) } });
资源列表
SDK源码和文档:https://cybozudev.kf5.com/hc/kb/article/1332504
总结
kintone钉钉小程序SDK的范例就介绍完了。
大家也可以根据实际情况自己创建一些其他的钉钉小程序,把数据传递给kintone,可对数据进行汇总,或跟其他人事相关的应用结合起来,自动计算每月出勤天数,计算工资等,实现业务自动化。
如有任何疑问或建议,请给我们留言哦。
注意事项
本示例代码不保证其运行。
我们不为本示例代码提供技术支持。