钉钉小程序开发实战:手术查询小程序

钉钉小程序开发实战:手术查询小程序

一、项目概述

1.1 项目背景

本项目是一个基于钉钉平台的手术查询小程序(SSCX),主要用于医院内部医护人员查询手术排班信息。该小程序能够帮助医务工作人员快速获取手术安排、患者信息以及科室数据,提升医院信息化管理水平。

1.2 技术栈

  • 前端框架:钉钉小程序(基于Alibaba MiniApp框架)
  • UI组件库:mini-ali-ui v1.3.1
  • 认证方式:OAuth2.0 + 钉钉授权码
  • 后端API:RESTful API
  • 数据存储:本地存储(my.setStorage)

二、项目架构分析

2.1 目录结构

复制代码
SSCX/
├── app.js                 # 应用入口文件
├── app.json               # 应用配置文件
├── app.acss               # 全局样式文件(暂无内容)
├── pages/
│   └── index/
│       ├── index.js       # 主页逻辑
│       ├── index.json     # 页面配置
│       └── index.acss     # 页面样式
├── components/
│   └── water/
│       ├── water.js       # 水印组件逻辑
│       ├── water.axml     # 水印组件模板
│       ├── water.acss     # 水印组件样式
│       └── water.json     # 水印组件配置
└── image/                 # 静态资源目录

2.2 页面配置分析

app.json - 全局应用配置:

json 复制代码
{
  "pages": ["pages/index/index"],
  "window": {
    "defaultTitle": "My App"
  }
}

pages/index/index.json - 页面配置:

json 复制代码
{
  "usingComponents": {
    "water-mark": "../../components/water/water",
    "am-card": "mini-ali-ui/es/card/index",
    "am-coupon": "mini-ali-ui/es/coupon/index",
    "am-search-bar": "mini-ali-ui/es/search-bar/index",
    "am-title": "mini-ali-ui/es/title/index",
    "am-page-result": "mini-ali-ui/es/page-result/index"
  },
  "defaultTitle": "手术查询V1.4"
}

页面配置中引入了6个自定义组件,其中water-mark是自定义水印组件,其他5个是mini-ali-ui组件库提供的UI组件。


三、核心功能模块详解

3.1 身份认证与授权流程

这是整个小程序最核心的模块,采用OAuth2.0密码模式与钉钉授权码结合的双重认证机制:

javascript 复制代码
let authorization = '';  // 全局Token变量

onLoad(query) {
  // 第一步:获取Access Token
  dd.httpRequest({
    url: "https://api.example.com/token",
    method: 'POST',
    data: "UserName=YOUR_USERNAME&Password=YOUR_PASSWORD&grant_type=password",
    success: function(res) {
      // 保存Bearer Token
      authorization = 'Bearer ' + res.data.access_token;

      // 第二步:获取钉钉授权码
      dd.getAuthCode({
        success: function(res) {
          // 第三步:用授权码获取用户信息
          dd.httpRequest({
            url: 'https://api.example.com/JdrmyyCloud/OperationInfo/getUserinfo?authCode=' + res.authCode,
            method: 'GET',
            headers: { authorization: authorization },
            success: function(res) {
              let resObj = JSON.parse(res.data);
              // 存储用户信息到本地
              my.setStorage({
                key: 'userInfo',
                data: resObj
              });
            }
          });
        }
      });

      // 第四步:获取手术排班数据
      dd.httpRequest({
        url: 'https://api.example.com/JdrmyyCloud/OperationSchedule?StudyDate=',
        method: 'GET',
        headers: { authorization: authorization },
        dataType: 'json',
        success: function(res) {
          that.setData({ patientList: res.data });
        }
      });
    }
  });
}

认证流程图解

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      认证授权流程                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   小程序启动                                                │
│       │                                                     │
│       ▼                                                     │
│   ┌─────────────────┐                                       │
│   │ POST /token     │ ← 用户名密码认证                      │
│   │ grant_type=     │   获取Access Token                    │
│   │ password        │                                       │
│   └────────┬────────┘                                       │
│            │                                                │
│            ▼                                                │
│   ┌─────────────────┐                                       │
│   │ dd.getAuthCode  │ ← 获取钉钉授权码                      │
│   └────────┬────────┘   (钉钉企业内部API)                   │
│            │                                                │
│            ▼                                                │
│   ┌─────────────────┐                                       │
│   │ GET /getUserinfo│ ← 传递授权码获取用户详细信息          │
│   │ ?authCode=xxx   │                                       │
│   └────────┬────────┘                                       │
│            │                                                │
│            ▼                                                │
│   ┌─────────────────┐                                       │
│   │ dd.setStorage   │ ← 保存用户信息到本地                  │
│   └────────┬────────┘                                       │
│            │                                                │
│            ▼                                                │
│   ┌─────────────────┐                                       │
│   │ GET /Operation  │ ← 获取手术排班列表                    │
│   │ Schedule        │                                       │
│   └─────────────────┘                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3.2 手术排班查询功能

3.2.1 初始加载全部数据

页面加载时,通过空日期参数获取所有手术排班:

javascript 复制代码
dd.httpRequest({
  url: 'https://api.example.com/JdrmyyCloud/OperationSchedule?StudyDate=',
  method: 'GET',
  headers: { authorization: authorization },
  dataType: 'json',
  success: function(res) {
    that.setData({ patientList: res.data });
  }
});
3.2.2 日期选择器查询

点击日期选择器后,按选定日期筛选手术排班:

javascript 复制代码
datePickerYMDHMS() {
  var date = new Date();
  var year = date.getFullYear();
  var pyear = date.getFullYear() - 11;  // 日期范围:前11年到后2个月
  var month = date.getMonth() + 1;
  var pmonth = date.getMonth();
  var nmonth = date.getMonth() + 2;

  // 日期补零处理
  if (month >= 1 && month <= 9) month = "0" + month;
  if (pmonth >= 1 && pmonth <= 9) pmonth = "0" + pmonth;
  if (nmonth >= 1 && nmonth <= 9) nmonth = "0" + nmonth;

  var currentDate = year + '-' + month + '-' + day;
  var startDate = pyear + '-' + pmonth + '-' + day;
  var endDate = year + '-' + nmonth + '-' + day;

  my.datePicker({
    format: 'yyyy-MM-dd',
    currentDate: currentDate,
    startDate: startDate,
    endDate: endDate,
    success: (res) => {
      that.setData({ occuTime: res.date });

      // 根据选择日期重新请求数据
      dd.httpRequest({
        url: 'https://api.example.com/JdrmyyCloud/OperationSchedule?StudyDate=' + res.date,
        method: 'GET',
        headers: { authorization: authorization },
        dataType: 'json',
        success: function(res) {
          that.setData({ patientList: res.data });
        }
      });
    },
  });
}

3.3 数据模型

用户信息模型
javascript 复制代码
userinfo: {
  name: '',        // 姓名
  mobile: '',      // 手机号
  jobnumber: '',   // 工号
  avatar: '',      // 头像URL
  deptname: ''     // 科室名称
}
手术患者数据模型
javascript 复制代码
patientList: [
  {
    // 手术患者列表,由API返回
    // 具体字段结构需要根据实际API文档确定
  }
]

四、自定义组件开发

4.1 水印组件(water-mark)

这是一个典型的Canvas水印组件,用于在页面添加半透明水印效果,防止截图泄密。

组件配置文件(water.json)
json 复制代码
{
  "component": true
}
组件模板(water.axml)
xml 复制代码
<canvas id="canvas" width="610" height="610" class="water-canvas" />
组件样式(water.acss)
css 复制代码
.water-canvas {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: transparent;
}

button {
  position: relative;
  z-index: 10;  /* 确保按钮可点击 */
}
组件逻辑(water.js)
javascript 复制代码
Component({
  props: {
    fillText: '侵权必究'  // 水印文本,可由外部传入
  },

  didMount() {
    this.ctx = my.createCanvasContext('canvas');
    this.drawWater();
  },

  didUpdate() {
    this.drawWater();
  },

  methods: {
    drawWater() {
      const { fillText } = this.props;

      // 设置旋转角度18度
      this.ctx.rotate(18 * Math.PI / 180);

      // 绘制斜对角线左下部分水印
      for (let j = 1; j < 10; j++) {
        this.fill(fillText, 0, 90 * j);
        for (let i = 1; i < 10; i++) {
          this.fill(fillText, 130 * i, 80 * j);
        }
      }

      // 绘制斜对角线右上部分水印
      for (let j = 0; j < 10; j++) {
        this.fill(fillText, 0, -80 * j);
        for (let i = 1; i < 10; i++) {
          this.fill(fillText, 130 * i, -80 * j);
        }
      }

      this.ctx.draw();
    },

    fill(text, x, y) {
      this.ctx.beginPath();
      this.ctx.setFontSize(20);
      this.ctx.setFillStyle('rgba(169,169,169,.2)');  // 浅灰色半透明
      this.ctx.fillText(text, x, y);
    }
  }
});
水印绘制原理图解
复制代码
                    斜对角线
                       /
                      /
           ●─────────●─────────────────●
          ● ●        /                  ●
         ●   ●       /    水印区域       ●
        ●     ●     /                   ●
       ●       ●   /                   ●
      ●         ● /                   ●
     ●──────────●─────────────────────●
      \         │
       \        │  旋转18度后
        \       │  布满水印文字
         \      │
          \     │
           \    │
            \   │
             \  │


水印文字分布(18度旋转):

     第0列   第1列   第2列   第3列   第4列
行0   水印    水印    水印    水印    水印    ...
行1   水印    水印    水印    水印    水印    ...
行2   水印    水印    水印    水印    水印    ...

每个水印:字体大小20px,颜色 rgba(169,169,169,0.2)

4.2 mini-ali-ui 组件库使用

本项目使用了5个mini-ali-ui组件:

组件名 用途
am-card 卡片组件,用于展示患者信息
am-coupon 优惠券样式组件(可能用于优惠信息展示)
am-search-bar 搜索栏组件
am-title 标题组件
am-page-result 页面结果组件

五、生命周期与页面事件

5.1 App级别生命周期

javascript 复制代码
App({
  onLaunch(options) {
    // 第一次打开应用时执行
    console.info('App onLaunch');
  },

  onShow(options) {
    // 应用从后台切换到前台时执行
    // 可通过scheme重新打开
  }
});

5.2 Page级别生命周期

javascript 复制代码
Page({
  data: {
    // 页面状态数据
  },

  onLoad(query) {
    // 页面加载时执行
    // query为页面启动时传递的参数
  },

  onReady() {
    // 页面首次渲染完成
  },

  onShow() {
    // 页面显示时执行
  },

  onHide() {
    // 页面隐藏时执行
  },

  onUnload() {
    // 页面被关闭时执行
  },

  onTitleClick() {
    // 标题被点击时执行
  },

  onPullDownRefresh() {
    // 页面被下拉刷新时执行
  },

  onReachBottom() {
    // 页面被拉到底部时执行
  },

  onShareAppMessage() {
    // 配置页面分享信息
    return {
      title: 'My App',
      desc: 'My App description',
      path: 'pages/index/index',
    };
  }
});

5.3 生命周期流程图

复制代码
┌──────────────────────────────────────────────────────────────┐
│                    Page 生命周期流程                         │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   用户打开页面                                               │
│       │                                                      │
│       ▼                                                      │
│   ┌─────────┐                                                │
│   │ onLoad  │ ← 页面加载,获取数据,发送请求                 │
│   └────┬────┘                                                │
│        │                                                     │
│        ▼                                                     │
│   ┌─────────┐                                                │
│   │ onReady │ ← 页面渲染完成,可交互                         │
│   └────┬────┘                                                │
│        │                                                     │
│        ▼                                                     │
│   ┌─────────┐                                                │
│   │ onShow  │ ← 页面显示                                     │
│   └────┬────┘                                                │
│        │                                                     │
│        ├────────────────────────────────┐                    │
│        │                                │                    │
│        ▼                                ▼                    │
│   ┌─────────┐                    ┌─────────┐                 │
│   │ onHide  │ ← 页面隐藏  ────▶  │ onShow  │ ← 重新显示     │
│   └────┬────┘                    └─────────┘                 │
│        │                                                     │
│        ▼                                                     │
│   ┌───────────┐                                              │
│   │ onUnload  │ ← 页面关闭,释放资源                         │
│   └───────────┘                                              │
│                                                              │
└──────────────────────────────────────────────────────────────┘

六、API接口分析

6.1 接口列表

接口 方法 用途 认证方式
/token POST 获取Access Token 用户名密码
/JdrmyyCloud/OperationInfo/getUserinfo GET 获取用户信息 Bearer Token + 钉钉AuthCode
/JdrmyyCloud/OperationInfo/getEmpInfo GET 获取员工信息 Bearer Token
/JdrmyyCloud/OperationSchedule GET 获取手术排班 Bearer Token

6.2 请求头配置

所有API请求都需要携带Authorization头:

javascript 复制代码
headers: {
  authorization: authorization  // 格式:Bearer <access_token>
}

七、安全性分析

7.1 当前方案的安全考虑

  1. Token机制:使用OAuth2.0的Bearer Token进行接口认证
  2. 水印防护:通过Canvas绘制水印,防止截图泄露
  3. 本地存储:用户信息存储在本地,不在URL中传递敏感参数

7.2 潜在安全风险

  1. 硬编码凭证:用户密码直接写在代码中
  2. 全局变量存储Token:Token存储在全局变量中,页面关闭后丢失
  3. 敏感信息暴露:API域名和接口路径在代码中可见

7.3 安全建议

javascript 复制代码
// 推荐做法1:使用钉钉secureStorage存储敏感信息
my.getStorage({
  key: 'token',
  encrypted: true  // 加密存储
});

// 推荐做法2:使用HTTPS确保传输安全
// 当前已使用HTTPS,这点做得较好

// 推荐做法3:Token有效期控制
// 在Token中包含过期时间,定期刷新

八、开发经验与最佳实践

8.1 钉钉小程序开发要点

  1. dd API调用 :钉钉小程序使用dd.前缀调用原生API
  2. 组件化开发:合理抽象可复用组件,如本项目的水印组件
  3. 生命周期管理:注意onLoad、onReady、onShow等生命周期的调用时机

8.2 代码组织建议

javascript 复制代码
// 良好的代码组织结构
Page({
  data: {
    // 状态数据放这里
  },

  // 事件处理函数
  handleClick() {},

  // 业务逻辑函数
  fetchData() {},

  // API调用函数
  requestAPI() {},
});

8.3 异步处理技巧

javascript 复制代码
// 使用箭头函数保持this引用
onLoad(query) {
  let that = this;  // 保存this引用
  dd.httpRequest({
    url: 'xxx',
    success: function(res) {
      that.setData({ ... });  // 使用that访问this
    }
  });
}

// 或使用箭头函数(如果环境支持)
dd.httpRequest({
  url: 'xxx',
  success: (res) => {
    this.setData({ ... });  // 箭头函数自动绑定this
  }
});

九、总结

本文对钉钉小程序"手术查询系统"进行了全面的代码解析,涵盖了:

  1. 项目架构:采用标准的钉钉小程序目录结构
  2. 认证机制:OAuth2.0 + 钉钉授权码的双重认证
  3. 核心功能:手术排班查询、日期筛选、数据展示
  4. 组件开发:自定义水印组件的Canvas绘制原理
  5. 生命周期:App和Page的全生命周期管理
  6. 安全分析:当前方案的安全评估与改进建议

该小程序展示了钉钉小程序开发的典型模式,具有一定的参考价值。希望本文能为钉钉小程序开发者在实际项目中提供帮助。

相关推荐
二进喵2 小时前
OpenClaw 接入钉钉完整指南
钉钉
Teable任意门互动2 小时前
多维表格本地化部署实践解析 企业如何实现数据自主可控路径
数据库·excel·钉钉·飞书·开源软件
软件开发技术3 小时前
新版点微同城主题源码34.7+全套插件+小程序前后端 源文件
小程序·php
mon_star°16 小时前
消防安全培训小程序项目亮点与功能清单
小程序
编程迪17 小时前
基于Java和Vue开发的在线问诊系统医疗咨询小程序APP
小程序
CHU72903518 小时前
知识触手可及:在线教学课堂APP的沉浸式学习体验
前端·学习·小程序
竟未曾年少轻狂18 小时前
微信小程序-组件开发
微信小程序·小程序
CHU72903519 小时前
在线教学课堂APP功能版块设计方案:重构学习场景的交互逻辑
java·学习·小程序·重构
焦糖玛奇朵婷19 小时前
盲盒小程序开发,盲盒小程序怎么做
java·大数据·服务器·前端·小程序