代码设计
1. 简介
1.1 功能简介
代码编辑就是为配置的参数设置具体的执行规则,这里的规则需要使用代码语言自定义编写。
1.2 代码语言
简道云自建插件目前支持的代码语言有:
- Python 3.10
- Node.js 20
1.3 预期效果
1)后端函数插件配置及执行效果如下所示:

2)前端扩展插件配置及执行效果如下所示:

2. 插件函数
2.1 函数类型
插件新建完成后,需要新增对应的函数。插件中的函数分为以下 2 种类型,在「插件设计」页面中,点击左下角的「新增函数」,您可根据自己的需求选择不同的函数进行创建:
函数类型 | 函数释义 | 可选位置 | 备注 |
前端扩展 | 若设置动作为前端扩展,则在前端执行插件,可调用一些前端行为。 比如:弹出 iframe 弹窗。 场景举例:弹出地图页面;录音。 | 只有 前端事件 处可以选到该动作。 目前仅支持作为按钮字段的执行动作。 | 不计费、不参与后台触发次数统计。 |
后端函数 | 若设置动作为后端函数,则后端完成插件执行过程。 场景举例:企业微信群机器人;excel 解析。 | 所有调用插件的位置均可选到该动作。 | / |

注:
1)前端扩展与后端函数均包含:请求参数、返回参数、代码部分。请求参数和返回参数规则一致,代码部分各自支持的方法见下文【3. 代码编辑】。
2)单个自建插件内的函数数量上限为 30 个。
2.2 复制函数
若需要对函数进行复用,点击函数右侧的三个小圆点,选择「复制」,即可复制函数。

3. 代码编辑
3.1 设计页面
参数设计确定后,您就需要为函数编写代码来实现其功能。在编程语言选择处(下图 1)选择您熟悉的编程语言,在代码编辑器(下图 2)中编写函数代码。在您的代码中,可以引用您在入参声明中定义的参数,参数列表和值的类型可以参考右侧的(下图 3)中的提示。

3.2 后端函数-示例代码及说明
Python 3.6
# 可以引用一些第三方库.
import json
import requests
# 可以通过读取预定义的全局变量中的属性来获取您定义的参数.
# agentConf 含义是通用参数, 其结构为一个字典(dict), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值.
# triggerConf 含义是请求参数, 其结构为一个字典(dict), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值或表单字段的值.
api_key = str(agentConf.get('apiKey'))
dept_no = triggerConf.get('deptNo')
# 您需要对输入参数的类型, 格式做校验, 以增强函数逻辑的健壮性.
# 如下方对可能拿到的不同值格式进行处理,保证最终dept_no的准确性,具体值格式见本文「5.1 triggerConf (请求参数)结构」
try:
if isinstance(dept_no, str):
dept_no = json.loads(dept_no)
if isinstance(dept_no, list):
dept_no = dept_no[0] if 0 < len(dept_no) else None
if isinstance(dept_no, dict):
dept_no = dept_no.get('dept_no')
if dept_no is None:
dept_no = 1
dept_no = int(dept_no)
except:
raise ValueError('部门编号格式错误')
# 可以利用第三方库和请求参数, 在服务端环境中调用相关接口并获取结果.
url = ' https://api.jiandaoyun.com/api/v5/corp/department/user/list'
payload = {'dept_no': dept_no}
headers = {'Authorization': 'Bearer ' + api_key}
response = requests.post(url, json=payload, headers=headers)
if 300 <= response.status_code < 500:
# 对特定结果主动抛错, 定义抛错文案.
body = response.json()
code = body.get('code', -1)
message = body.get('message', '未知错误')
message = '参数错误(%s): %s' % (code, message)
raise ValueError(message)
# 对结果进行处理, 定义返回参数值.
# 您需要返回一个dict, dict的key和返回参数ID一一对应. 若返回为数组, 则对应出参需设置为object[]类型.
body = response.json()
users = [{'name': user.get('name'), 'username': user.get(
'username')} for user in body.get('users', [])]
count = len(users)
return {
"users": users,
"count": count
}
# 可引用的全局变量, 支持的第三方库, 请求参数数据存储格式, 见本文「5.相关信息」.
Node.js 20
// 可以引用一些第三方库.
const _ = require('lodash');
const axios = require('axios');
// 可以通过读取预定义的全局变量中的属性来获取您定义的参数.
// agentConf 含义是通用参数, 其结构为一个对象(Object), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值.
// triggerConf 含义是请求参数, 其结构为一个对象(Object), key 为您在入参模板中为控件定义的 ID, 值为用户指定的配置值或表单字段的值.
const apiKey = _.chain(agentConf).get(['apiKey']).trim().value();
let deptNo = _.get(triggerConf, ['deptNo']);
// 您需要对输入参数的类型, 格式做校验, 以增强函数逻辑的健壮性.
// 如下方对可能拿到的不同值格式进行处理,保证最终dept_no的准确性,具体值格式见本文「5.1 triggerConf (请求参数)结构」
try {
if (_.isString(deptNo)) deptNo = JSON.parse(deptNo);
if (_.isArray(deptNo)) deptNo = _.first(deptNo);
if (_.has(deptNo, ['dept_no'])) deptNo = _.get(deptNo, ['dept_no']);
deptNo = _.toInteger(deptNo);
} catch (e) {
throw new Error('请输入正确的部门编号');
}
// 可以利用第三方库和请求参数, 在服务端环境中调用相关接口并获取结果.
try {
const response = await axios({
method: 'post',
url: ' https://api.jiandaoyun.com/api/v5/corp/department/user/list',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
data: { dept_no: deptNo }
});
const { data } = response;
// 对结果进行处理, 定义返回参数值.
// 您需要返回一个object, object的key和返回参数ID一一对应. 若返回为数组, 则对应出参需设置为object[]类型.
const users = _.chain(data)
.get(['users'])
.map((it) => ({ name: it.name, username: it.username }))
.value();
return { users, count: users.length };
} catch (e) {
// 对特定结果主动抛错, 定义抛错文案.
const code =
_.get(e, ['response', 'data', 'code']) || _.get(e, ['code']) || -1;
let message =
_.get(e, ['response', 'data', 'msg']) ||
_.get(e, ['message']) ||
'未知错误';
message = `参数错误(${code}): ${message}`;
throw new Error(message);
}
// 可引用的全局变量, 支持的第三方库, 请求参数数据存储格式, 见本文「6.相关信息」
3.3 前端扩展-示例代码及说明
// closeModal
const close = () => {
$g.utils.closeModal();
console.log('4s后模态框关闭');
};
const reportUrl = $g.env.baseUrl;
setTimeout(() => close(), 4000);
// GET
let response = await fetch(reportUrl, {method: 'GET'});
const resText = await response.text()
console.log(resText);
let message;
$g.ui.onmessage = function (msg) {
message = msg
}
// openModal
// openModal:弹窗后继续代码执行,执行完结束 Worker。
// await openModal:弹窗后阻塞代码执行,按照开发者代码规定执行后续动作。
await $g.utils.openModal({
title: '如果您对插件有任何需求,欢迎在本表单填写',
url: reportUrl
});
// 调用后端函数
await $g.utils.callFunction({
name: 'func.ID',
data: {}
})
return {
resText,
message
}3.4 参数提示的使用
代码编辑器右侧可展开参数提示,点击对应参数/函数,将添加对应调用语法到代码编辑器中。
3.5 参数校验提示信息
编写请求参数/返回参数/通用参数时,若出现代码错误,对应代码将用红色波浪线标注,且当鼠标放在对应代码上时,会显示详细的配置错误信息,便于开发者校验修改代码内容,提升编辑体验。效果如下所示:
3.6 语法错误提示信息
在前端扩展/后端函数的 Python 代码视图下,编辑器中支持在保存代码时提示语法错误。帮助开发者快速定位具体问题,为开发者提供更为直观、高效的开发体验。效果如下图所示:
4. 相关信息
4.1 全局变量
您在代码中可以直接引用全局变量 agentConf、triggerConf、triggerContext。
4.1.1 agentConf (通用参数)结构
agentConf 即插件对应的通用参数。
其结构为对应编程语言的键值数据结构,如 Python 的 dict 或 Node.js 的 Object。在该结构中,键为您在入参声明中为变量指定的 ID,值为用户配置或引用的数据。
4.1.2 triggerConf (请求参数)结构
triggerConf 即插件函数对应的请求参数。
其结构为对应编程语言的键值数据结构,如 Python 的 dict 或 Node.js 的 Object。在该结构中,键为您在入参声明中为变量指定的 ID,值为用户配置或引用的数据(具体结构见下文)。
简道云会根据您在入参声明中选择的控件类型和用户配置该参数值的来源,在运行时尝试为您做必要的类型转换,以使您在插件代码中能够取到格式合理的值。但这个过程不是严格保证的,在复杂场景中,您可能会读取到各种预期内或预期外类型的参数,您应当在编写代码时考虑这一情况,并在不能处理相关类型时抛出合理的错误以指引用户调整其配置。
举例,您正在开发一个图像识别类的插件,并在该插件的请求参数中加入了一个文本字段用以接受用户的图片附件,那么基于不同的用户配置,您有可能得到不同的值:
- 用户可能直接填写了一个文件的 URL,此时您将得到一个内容为"https://。。。"的字符串。
- 用户可能关联到简道云表单数据中的图片/附件控件值上,此时您将得到一个内容为'[{"url":"https://。。。","name":"。。。"},{"url":"https://。。。","name":"。。。"}]'的 JSON 字符串。
- 用户可能关联到简道云表单数据中的手写签名/微信头像控件值上,此时您将得到一个'{"url":"https://。。。","name":"。。。"}'的 JSON 字符串。
- 用户可能关联到另一个插件的返回值的一个属性上,此时您可能得到一个类型不确定的值。
您应该结合自己插件的功能和预期服务的用户场景,在代码中妥善处理上述情况,并在不计划支持该类型时通过抛出错误并附加消息向用户提示原因。
请求参数储值格式
1)保存自定义值
当用户在插件配置时直接保存「自定义值」时(如上图),请求参数中不同控件的储值格式如下表:
控件 | 储值结构 |
文本 | "你好, 世界" |
下拉框 | "选项1" |
数字 | 3.14 |
日期时间 | "2022-09-09T08:00:00.000Z" |
成员选择 | [{ "_id": "5b433bf80118dc44bcb9183b", "name": "lizijun", "username": "lizijun", "status": 1, "type": 0 }] |
部门选择 | [{ "_id": "602f77c86aee9d2dd4c04bfc", "name": "一级部门", "dept_no": 3, "type": 0 }] |
2)保存字段
当用户在简道云表单数据场景中使用插件时,可保存字段值(如上图),入参声明中各个控件的储值格式如下表:
表单控件 \ 入参声明控件 | 字段值 | 模板字符串 | |||||
文本 | 下拉框 | 数字 | 日期时间 | 成员选择 | 部门选择 | ||
单行文本 | "你好, 世界" | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "你好, 世界" ID: "_widget_1653375890324" |
多行文本 | "你好, 世界\n我能吞下玻璃而不伤身体" | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "你好, 世界\n我能吞下玻璃而不伤身体" ID: "_widget_1653375890324" |
数字 | 3.14 | ❌ | 3.14 | ❌ | ❌ | ❌ | 值: 3.14 ID: "_widget_1653375890324" |
日期时间 | "2022-09-09T08:00:00.000Z" | ❌ | ❌ | "2022-09-09T08:00:00.000Z" | ❌ | ❌ | 值: "2022-09-09 16:00:00" ID: "_widget_1653375890324" |
单选按钮组 | "选项1" | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "选项1" ID: "_widget_1653375890324" |
复选框组 | '["选项1","选项3"]' | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "选项1, 选项3" ID: "_widget_1653375890324" |
下拉框 | "选项1" | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "选项1" ID: "_widget_1653375890324" |
下拉复选框 | '["选项1","选项3"]' | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "选项1, 选项3" ID: "_widget_1653375890324" |
地址 | '{"province":"山西省","city":"太原市","district":"尖草坪区","detail":"迎新街俱乐部"}' | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "山西省太原市尖草坪区迎新街俱乐部" ID: "_widget_1653375890324" |
定位 | '{"province":"山西省","city":"太原市","district":"尖草坪区","detail":"迎新街俱乐部", "lnglatXY":[112.549656,37.870451}' | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "山西省太原市尖草坪区迎新街俱乐部" ID: "_widget_1653375890324" |
图片 | '[{"name":"emoji.gif","size":10168,"mime":"image/gif","url":"https://..."}]' | ❌ | ❌ | ❌ | ❌ | ❌ | 值: ID: "_widget_1653375890324" |
附件 | '[{"name":"emoji.jpg","size":10168,"mime":"image/jpeg","url":"https://..."}]' | ❌ | ❌ | ❌ | ❌ | ❌ | 值: ID: "_widget_1653375890324" |
手写签名 | {"name":"signature_1663590240652.png","size":8800,"mime":"image/png","url":"https://..."} | ❌ | ❌ | ❌ | ❌ | ❌ | 值: ID: "_widget_1653375890324" |
流水号 | "00005" | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "00005" ID: "_widget_1653375890324" |
手机 | '{"verified":false,"phone":"155..."}' | ❌ | ❌ | ❌ | ❌ | ❌ | 值: "15536090976" ID: "_widget_1653375890324" |
成员单选 | '[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]' | ❌ | ❌ | ❌ | '[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]' | ❌ | 值:(name) "lizijun" ID: "_widget_1653375890324" |
成员多选 | '[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]' | ❌ | ❌ | ❌ | '[{"_id":"5b433bf80118dc44bcb9183b","name":"lizijun","username":"lizijun","status":1,"type":0}]' | ❌ | 值:(name) "lizijun, springmoon" ID: "_widget_1653375890324" |
部门单选 | '[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]' | ❌ | ❌ | ❌ | ❌ | '[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]' | 值:(name) "帆软软件有限公司" ID: "_widget_1653375890324" |
部门多选 | '[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]' | ❌ | ❌ | ❌ | ❌ | '[{"_id":"602f77c86aee9d2dd4c04bfc","name":"一级部门","dept_no":3,"type":0}]' | 值:(name) "帆软软件有限公司, springmoon的企业/团队" ID: "_widget_1653375890324" |
4.1.3 triggerContext 结构
triggerContext 中存储了运行环境的基本信息,如简道云的开放 API 域名等。
4.2 内置库支持列表
4.2.1 Node.js内置库
- axios(0.21.0);
- form-data(2.3.3);
- lodash(4.17.20);
- moment-timezone(0.5.32)、mongodb(3.6.3);
- tedious(9.2.1)
- sequelize(6.3.5)
- openai(4.83.0)
- sm-crypto(0.3.13)
注:sequelize 目前支持的数据库(mysql,mssql,postgres)。
4.2.2 Python内置库
以下列表里包括可调用的内置库和第三方库。
- abc、 argparse、 array、asynchat、asyncio、asyncore、audioop;
- base64、binascii、binhex、bisect、bz2;
- calendar、cgi、cgitb、chunk、cmath、cmd、codecs、collections、colorsys、configparser、contextlib、contextvars、copy、copyreg、crypt、csv、ctypes;
- dataclasses、datetime、dbm、decimal、difflib、doctest;
- email、encodings、enum、errno;
- fnmatch、formatter、fractions、ftplib、functools;
- getopt、gettext、glob、graphlib、grp、gzip;
- hashlib、heapq、hmac、html、http;
- imaplib、 imghdr、 ipaddress、 itertools;
- json;
- keyword;
- linecache、 locale、 logging、 lzma;
- mailbox、 mailcap、 math、 mimetypes、 mmap;
- netrc、 nis、 nntplib、 numbers;
- operator、 optparse
- parser、 poplib、 pprint;
- quopri;
- random、 re、 reprlib;
- secrets、 select、 selectors、 shlex、 smtpd、 smtplib、 sndhdr、 socket、 socketserver、sqlite3、 ssl、 statistics、 string、 stringprep、 struct、 sunau;
- tarfile、 telnetlib、 test、 textwrap、 time、 timeit、 types、 typing;
- unicodedata、 unittest、 urllib、 uu、 uuid;
- warnings、 wave、 weakref、 wsgiref;
- xml、 xmlrpc;
- zlib、 zoneinfo;
- requests(2.26.0)、 pymysql(1.0.2)、pymssql(2.2.5)、pymongo(3.12.1)、psycopg2_binary(2.9.3);
- paramiko(2.8.0)、 pyjwt(2.3.0)、lxml(4.6.3)、xmltodict(0.12.0)、jinja2(2.11.3);
- cryptography(35.0.0);
- openai(1.61.1);
- pandas(2.3.1);
- gmssl(3.2.2);
- pycryptodomex(3.23.0)
4.3 可用前端扩展API
可用API名称 | 含义 | 代码示例 |
$g.utils.openModal | 打开弹窗 | $g.utils.openModal({ title: '弹窗标题', url: '弹窗打开链接' }) |
$g.utils.closeModal | 关闭弹窗 | $g.utils.closeModal() |
$g.utils.callFunction | 调用后端函数 | $g.utils.callFunction({ name: 'func.ID', data: {} }) |
$g.utils.openUrl | 新标签页打开 URL | $g.utils.openUrl({ url: '链接地址' }) |
$g.ui.onmessage | 接收弹窗 iframe 内发送的消息 | // 插件 $g.ui.onmessage = (message) => { // 对应 postMessage 中 pluginMessage 的值 console.log(message) } //弹窗 iframe parent.postMessage({pluginMessage: 'any'}, '*')
|
注:历史 openModal、closeModal 和 callFunction 等用法仍兼容。
5. 注意事项
5.1 插件运行限制
插件在运行时有相应的限制。插件本身的超时时间为 60 s,但是不同使用场景本身也有自己的超时时间,比如前端事件的 15 s。
5.2 前端扩展调用后端函数
进行自建插件设计时,若在「前端扩展函数 >> 代码」处调用了后端函数,则插件执行时,前端扩展中的后端函数执行上限为 5 次。
以中奖通知为例,在表单内点击「新建邮件」便会打开编辑窗口,填写并点击「发送邮件」即可完成通知。其中,「新建邮件」为前端扩展,「发送邮件」为后端函数,则每次打开新建邮件弹窗后,发送邮件的上限为 5 次。
注:若对后端函数设置了「按次收费」,则在前端函数中多次调用后端函数时,将会多次扣费。

400-111-0890
在线咨询