钉钉小程序开发实战:手术查询小程序
一、项目概述
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 当前方案的安全考虑
- Token机制:使用OAuth2.0的Bearer Token进行接口认证
- 水印防护:通过Canvas绘制水印,防止截图泄露
- 本地存储:用户信息存储在本地,不在URL中传递敏感参数
7.2 潜在安全风险
- 硬编码凭证:用户密码直接写在代码中
- 全局变量存储Token:Token存储在全局变量中,页面关闭后丢失
- 敏感信息暴露:API域名和接口路径在代码中可见
7.3 安全建议
javascript
// 推荐做法1:使用钉钉secureStorage存储敏感信息
my.getStorage({
key: 'token',
encrypted: true // 加密存储
});
// 推荐做法2:使用HTTPS确保传输安全
// 当前已使用HTTPS,这点做得较好
// 推荐做法3:Token有效期控制
// 在Token中包含过期时间,定期刷新
八、开发经验与最佳实践
8.1 钉钉小程序开发要点
- dd API调用 :钉钉小程序使用
dd.前缀调用原生API - 组件化开发:合理抽象可复用组件,如本项目的水印组件
- 生命周期管理:注意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
}
});
九、总结
本文对钉钉小程序"手术查询系统"进行了全面的代码解析,涵盖了:
- 项目架构:采用标准的钉钉小程序目录结构
- 认证机制:OAuth2.0 + 钉钉授权码的双重认证
- 核心功能:手术排班查询、日期筛选、数据展示
- 组件开发:自定义水印组件的Canvas绘制原理
- 生命周期:App和Page的全生命周期管理
- 安全分析:当前方案的安全评估与改进建议
该小程序展示了钉钉小程序开发的典型模式,具有一定的参考价值。希望本文能为钉钉小程序开发者在实际项目中提供帮助。