kintone钉钉小程序SDK使用范例

aki发表于:2019年11月29日 09:48:33更新于:2020年09月29日 14:00:32

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上编辑内容后,更改的内容及时同步到日报小程序上。

0015df327b0034ccea85e7663ce1c34

准备阶段

安装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(在后面需要用到)。

0015dedd23910430df9c68351fd1590

在kintone端创建日报应用

为方便大家确认本范例,此处提供了日报模板,请下载后,导入到kintone。

关于模板创建应用的方法请参考《kintone和钉钉、阿里云的整合》的kintone篇

导入钉钉小程序

  1. 点击钉钉小程序资源库,把整个资源库克隆或下载到本地。
    资源库根目录下的DailyReport目录是钉钉的日报小程序项目。

  2. 打开命令提示符,执行以下命令切换到DailyReport目录。以下例子中,
    SAMPLE-rest-api-client-dingtalk-mp-cn-master是放在C:\Users\xxxx下面。0015f72b079be726fb6a52712667513

  3. 然后执行以下命令进行安装。

    npm install
  4. 修改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
  5. 修改dingTalkConfig.js
    根据实际情况,修改 DailyReport/common/dingTalkConfig.js 文件中的 appkey、appsecret和 userid 字段。
     关于如何获取appkey和appsecret请参考钉钉帮助
     关于如何获取请参考钉钉帮助

      appkey: 'xxx', // 用来生成访问钉钉的access_token
      appsecret: 'xxx', // 用来生成访问钉钉的access_token
      userid: 'xxx', // 钉钉上的用户id
  6. 导入小程序项目
    6.1 打开钉钉开发者工具,新建项目。
         0015de86e030d7a5138f1ba1173e257
    6.2 选取本地目录
          输入项目名称,选择DailyReport 的目录,项目类型选择企业内部应用。
         0015f72b1485d3b9ca50dc1fbb0368e
    6.3 显示如下页面后,分别在①和②中选择“企业内部应用”和“日报”,然后点击“OK”。
          到这里,导入步骤就完成了。
          0015f72b194db7e3219ab399a6f173f

钉钉开发者平台上的设置

设置安全域名

  1.  打开钉钉开放平台页面,点击右上方的“登录开发者后台”,在登录页面输入登录信息后登录
      0015de876278144c6d9df4c8809a633


  2. 在登录后的页面中,选择“应用开发”,然后选择“日报”应用。
    0015de87ba605137d223e51721a8c45

  3. 选择“日报”的“设置安全域名”选项卡,点击第一个“添加”按钮,添加kintone的域名(xxxx.cybozu.com)和钉钉的api域名(oapi.dingtalk.com)。
    0015de87c7081d173f63966caddac39

设置服务器公网出口IP名单

  1. 上面的操作是在“设置安全域名”选项卡下操作的,这次选择“应用首页”选项卡”,然后点击应用信息的“查看详情”连接。
    0015de880e67159de7247a7a809b523

  2. 点击右上角的“修改,”在页面最底部的“服务器公网出口IP名单”中输入当前运行代码的电脑的公网IP。
    0015de882190ea0cb83eb7dde3674b4

设置接口权限

在上面的页面中点击“接口权限”选项卡,开通“身份验证”和“通讯录只读权限”。
0015de8941eb5e0ca9b9f21163a81ed

到这里,所有设置都完成了。接下来我们试写一下日报。

动作确认

  1. 在手机上打开钉钉应用,找到日报图标并打开,点击添加日报按钮。
    0015de9b683b197e99ae740574493e2

  2. 在日报编辑页面时会获取考勤数据,根据需要可更改考勤时间。
    0015de9b71169b6de221d79c65c595b

  3. 输入日报,可上传图片,最多可上传3张。完成后点击“提交”按钮。
    0015de9b76cdf853f26c9d53ec0c68d

  4. 打开kintone的日报应用,确认刚才提交的报告是否添加到kintone的应用里。

    0015de9b7d273c2eb48421ebc8e5041

  5. 领导等查看完日报后可以在kintone上写评语,保存的同时,数据也会同步到钉钉的日报应用里。
    0015dede49781eaed357ecedb85cc61



到这里,用kintone钉钉小程序SDK来进行钉钉和kintone的整合开发就完成了!

代码解说

资源目录

下面是钉钉小程序开发工具上的目录结构。
0015de9d7ada774fe2153be489f9c69

该目录结构基本类似于《微信小程序的目录结构》。

  • common:
    放置静态资源和公用的函数。
    dingTalkConfig.js定义了钉钉端的配置信息。
    kintoneConfig.js定义了kintone的配置信息,kintone的字段代码等。
    utility.js定义了一些公用的kintone的连接函数、数据初始化等函数。

  • node_modules:
    钉钉小程序自动编译的第三方库。

  • pages:
    日报小程序的源代码。

日报源代码的目录如下:

0015de9f71063d490d55a190df842d8

  • 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

demo源码:https://github.com/kintone-samples/SAMPLE-rest-api-client-dingtalk-mp-cn

总结

kintone钉钉小程序SDK的范例就介绍完了。

大家也可以根据实际情况自己创建一些其他的钉钉小程序,把数据传递给kintone,可对数据进行汇总,或跟其他人事相关的应用结合起来,自动计算每月出勤天数,计算工资等,实现业务自动化。

如有任何疑问或建议,请给我们留言哦。

注意事项

  • 本示例代码不保证其运行。

  • 我们不为本示例代码提供技术支持。

相关链接


附件:日报.zip • 2.29KB • 下载