本地脚本
概述
本地脚本功能基于设备本地 SQLite 数据库与页面设计能力(表单页)实现。通过中控服务,可将设备上已配置的脚本数据同步至云控后台,实现:
- 配置脚本:在设备端设置脚本参数并同步到云端
- 管理模板:支持添加、删除脚本配置模板
- 执行脚本:触发设备端运行指定脚本
功能架构说明
本功能本质上是对云控后台二次开发能力的扩展,主要包括:
- 表单页设计 → 用于编辑数据
- SQLite 数据库 → 用于本地数据存储
二者组合,实现完整的脚本管理能力。
学习前提
在使用本功能前,建议先掌握云控后台二次开发的基础能力,否则可能无法正确理解页面设计与数据结构之间的关系。
建议学习顺序如下:
数据库结构
数据库由 核心元数据表 和 脚本参数配置表 两部分组成。
本地脚本.db
├── meta_script(脚本元数据)
├── meta_form(表单元数据)
├── 脚本示例(脚本参数配置表)
└── ...(其他脚本参数配置表)
核心元数据表(必选)
以下两张表为系统运行的基础,必须存在且结构不可修改:
| 表名 | 作用 |
|---|---|
meta_script | 存储脚本内容、脚本名称、脚本路径、运行配置 |
meta_form | 定义脚本参数配置的表单结构 |
主要功能:存储表格布局、字段配置、交互逻辑等元信息。
脚本配置数据表(自定义)
业务表用于存储实际数据,支持灵活扩展:
- 表名:即脚本名称,完全自定义(支持中文)
- 字段:完全自定义(支持中文)
- 数量:可创建多个脚本配置数据表
- 用途:承载具体脚本配置数据,并与元数据表关联使用
表结构说明
meta_script(脚本元数据)
用于定义列表展示结构及其关联关系:
id INTEGER PK 主键
scriptContent TEXT 脚本内容代码
scriptName TEXT 脚本名称
scriptPath TEXT 脚本文件路径
scriptConfig TEXT 脚本执行配置参数
targetTable TEXT 关联脚本参数配置表
meta_form(表单元数据)
用于定义表单结构及字段配置:
id INTEGER PK 主键
label TEXT 表单名称
data TEXT 表单设计数据
dict TEXT 字典/配置
title TEXT 表单标题
targetTable TEXT 关联脚本参数配置表
脚本参数配置表(示例:脚本示例)
用于存储实际业务数据(字段完全可自定义,支持中文,推荐英文,设计表格时可以显示中文标签名):
id INTEGER PK 主键
label TEXT 标签
category_id INTEGER 分类ID
task_type_id INTEGER 任务类型ID
device_id INTEGER 设备ID
status INTEGER 状态
user_id INTEGER 用户ID
uid TEXT 唯一标识
created_at INTEGER 创建时间戳
upload TEXT 上传文件
input TEXT 输入框
textarea TEXT 多行文本
inputNumber INTEGER 数字输入
datePicker TEXT 日期选择
timePicker TEXT 时间选择
colorPicker TEXT 颜色选择
rate INTEGER 评分
slider INTEGER 滑块
switch INTEGER 开关
radio INTEGER 单选框
checkbox TEXT 多选框
select_field INTEGER 选择器
selectV2 INTEGER 选择器V2
treeSelect INTEGER 树形选择
image TEXT 图片
video TEXT 视频
play TEXT 播放
download TEXT 下载
copy TEXT 复制
link TEXT 链接
...
关键关系说明
meta_script.targetTable→ 关联脚本参数配置表meta_form.targetTable→ 关联脚本参数配置表
数据库创建
可在后台创建并编辑 SQLite 模板,详情参阅 SQLite 数据库
完成数据库设计后,数据库可通过以下两种方式在设备端实现:
数据库文件分发
适用于已设计完成的 SQLite 数据库模板。
数据库文件(.db)可通过任意方式分发至设备,例如本地传输或远程下载等,实现快速部署与同步。
存放路径要求:
/sdcard/cloud/SQLite/
将数据库文件放入上述目录后,系统即可自动识别并使用。
脚本动态创建
适用于运行时初始化或动态生成数据库结构的场景。
无需依赖预置 .db 文件,可通过脚本在设备端直接创建表结构。
// 👉 示例调用
createLocalScriptDB("/sdcard/cloud/SQLite/本地脚本.db", "脚本示例");
/**
* 创建【本地脚本数据库模板】
*
* 📌 功能说明:
* 根据指定路径创建一个完整的 SQLite 数据库,包含:
* 1️⃣ 脚本配置表(meta_script)
* 2️⃣ 表单定义表(meta_form)
* 3️⃣ 动态业务数据表(如:脚本示例)
*
* 📌 使用场景:
* - 云控初始化设备数据库
* - 本地脚本模板下发
* - 新设备首次运行自动建库
* - 数据库恢复 / 克隆
*
* 📌 特点:
* - 自动删除旧数据库(避免结构冲突)
* - 支持中文表名(如:脚本示例)
* - 表结构 + 初始数据一键生成
*
* ⚠️ 注意:
* - targetTableName 必须和 meta_script / meta_form 中保持一致
* - 表字段必须与前端表单定义一致,否则会导致数据无法回显
*
* @param {string} dbPath 数据库路径(如:/sdcard/cloud/SQLite/本地脚本.db)
* @param {string} targetTableName 业务表名(如:脚本示例)
*/
function createLocalScriptDB(dbPath, targetTableName) {
// =========================
// 0️⃣ 删除旧数据库
// =========================
var file = new java.io.File(dbPath);
if (file.exists()) {
file.delete();
console.log("🗑️ 已删除旧数据库:", dbPath);
}
var db = sqlite.open(dbPath);
try {
// =========================
// 1️⃣ 创建表结构
// =========================
/**
* 👉 meta_script(脚本执行配置表)
*
* 作用:
* - 存储脚本执行逻辑
* - 绑定目标数据表(targetTable)
*
* 核心字段:
* - scriptContent:脚本代码
* - scriptConfig:执行配置(循环、延迟等)
* - targetTable:关联业务表
*/
db.execSQL(
"CREATE TABLE meta_script (" +
"id INTEGER NOT NULL PRIMARY KEY, " +
"scriptContent TEXT, " +
"scriptName TEXT, " +
"scriptPath TEXT, " +
"scriptConfig TEXT, " +
"targetTable TEXT" +
")"
);
/**
* 👉 meta_form(表单结构定义表)
*
* 作用:
* - 定义前端表单结构(动态表单)
* - 控制字段类型、校验规则、数据来源等
*
* 核心字段:
* - data:完整 JSON 表单配置
* - targetTable:对应数据存储表
*/
db.execSQL(
"CREATE TABLE meta_form (" +
"id INTEGER NOT NULL PRIMARY KEY, " +
"label TEXT, " +
"data TEXT, " +
"dict TEXT, " +
"title TEXT, " +
"targetTable TEXT" +
")"
);
/**
* 👉 动态业务表(如:脚本示例)
*
* 作用:
* - 存储用户填写的表单数据
* - 与 meta_form 一一对应
*
* ⚠️ 注意:
* 字段必须与 meta_form.data 中的 name 对应
*/
db.execSQL(
"CREATE TABLE " +
targetTableName +
" (" +
"id INTEGER NOT NULL PRIMARY KEY, " +
"label TEXT, " +
"category_id INTEGER, " +
"task_type_id INTEGER, " +
"device_id INTEGER, " +
"status INTEGER, " +
"user_id INTEGER, " +
"content TEXT, " +
"created_at TEXT, " +
"input TEXT, " +
"textarea TEXT, " +
"inputNumber INTEGER, " +
"datePicker TEXT, " +
"timePicker TEXT, " +
"colorPicker TEXT, " +
"rate INTEGER, " +
"slider INTEGER, " +
"switch INTEGER, " +
"radio INTEGER, " +
"checkbox TEXT, " +
"image TEXT, " +
"video TEXT, " +
"isTemplate INTEGER, " +
"templateName TEXT" +
")"
);
// =========================
// 2️⃣ 插入数据
// =========================
/**
* 👉 插入 meta_script(脚本执行逻辑)
*
* 逻辑:
* - 从业务表中取一条 isTemplate = 0 的数据
* - 作为“可执行脚本配置”
*/
db.insert("meta_script", {
scriptContent:
'\n// 打开数据库\nlet db = sqlite.open("/sdcard/cloud/SQLite/本地脚本.db");\n\n// 查询一条 isTemplate = 0 的数据(实际业务:获取“可执行脚本配置”)\nlet scriptConfig = db.rawQuery(\n "SELECT * FROM `脚本示例` WHERE isTemplate = ? LIMIT 1",\n [0]\n).single();\n\n// 判断结果\nif (!scriptConfig) {\n throw new Error("未找到可用的脚本配置(isTemplate = 0)");\n}\n\n// 关闭数据库\ndb.close();\n\n// 输出结果\nconsole.log("获取到的脚本配置参数:");\nconsole.log(scriptConfig);\n ',
scriptName: targetTableName,
scriptPath: "",
scriptConfig: '{"delay":1000,"loopTimes":1,"interval":200,"path":["/sdcard/cloud/script"]}',
targetTable: targetTableName,
});
/**
* 👉 插入业务数据(示例 + 模板)
*
* isTemplate:
* - 0 → 可执行数据
* - 1 → 模板数据
*/
db.insert(targetTableName, {
label: "任务示例:数据录入流程演示",
category_id: 73,
task_type_id: 1,
device_id: 58870,
status: 1,
user_id: 382,
content: "我是内容",
created_at: "2026-04-05T01:39:36.000Z",
input: "我是单行文本",
textarea: "我是多行文本",
inputNumber: 3,
datePicker: "2026-04-05T01:39:36.000Z",
timePicker: "2026-04-05T01:39:24.000Z",
colorPicker: "#FC1D1D",
rate: 4,
slider: 51,
switch: 1,
radio: 3,
checkbox: "[\n 2\n]",
image:
"http://47.94.105.29:66/storage/folder/username/20240422/030638/01.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/02.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/03.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/04.jpg",
video:
"http://47.94.105.29:66/storage/folder/username/20240422/030621/01.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/02.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/03.mp4",
isTemplate: 0,
});
db.insert(targetTableName, {
label: "模板示例:标准任务配置(可复用)",
category_id: 73,
task_type_id: 1,
device_id: 58870,
status: 1,
user_id: 382,
content: "我是内容(模板示例)",
created_at: "2026-04-05T01:39:36.000Z",
input: "我是单行文本(模板示例)",
textarea: "我是多行文本(模板示例)",
inputNumber: 3,
datePicker: "2026-04-05T01:39:36.000Z",
timePicker: "2026-04-05T01:39:24.000Z",
colorPicker: "#FC1D1D",
rate: 4,
slider: 51,
switch: 2,
radio: 1,
checkbox: "[\n 1\n]",
image:
"http://47.94.105.29:66/storage/folder/username/20240422/030638/01.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/02.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/03.jpg,http://47.94.105.29:66/storage/folder/username/20240422/030638/04.jpg",
video:
"http://47.94.105.29:66/storage/folder/username/20240422/030621/01.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/02.mp4,http://47.94.105.29:66/storage/folder/username/20240422/030622/03.mp4",
isTemplate: 1,
templateName: "模板示例",
});
/**
* 👉 插入 meta_form(动态表单)
*
* ⚠️ 这里是核心:
* 前端 UI 就是根据这个 JSON 渲染出来的
*/
db.insert("meta_form", {
id: 1,
label: "脚本示例表",
data: '{list:[{type:"input",control:{modelValue:"",placeholder:"请输入标题"},config:{},name:"label",formItem:{label:"标题",rules:[{required:true,message:"必填项",trigger:"blur"},{max:20,message:"长度不能超过20个字符",trigger:"blur"}]}},{type:"selectPlus",control:{modelValue:"",appendToBody:true,valueKey:"id",filterable:true,props:{label:"label",value:"id"}},options:[],config:{optionsType:1,optionsFun:"http://47.94.105.29:66/api/DataListCaseCategoryList",method:"post",addUrl:"http://47.94.105.29:66/api/addDataListCaseCategory",editUrl:"http://47.94.105.29:66/api/editDataListCaseCategory",deleteUrl:"http://47.94.105.29:66/api/deleteDataListCaseCategory"},name:"category_id",formItem:{label:"分类"}},{type:"select",control:{modelValue:"",appendToBody:true,placeholder:"请选择任务类型"},options:[{label:"任务类型1",value:1},{label:"任务类型2",value:2},{label:"任务类型3",value:3},{label:"任务类型4",value:4}],config:{optionsType:1,optionsFun:"http://47.94.105.29:66/api/getFieldTaskTypeIdDict",method:"post"},name:"task_type_id",formItem:{label:"任务类型"}},{type:"select",control:{modelValue:"",appendToBody:true},options:[],config:{optionsType:1,optionsFun:"http://180.76.145.80/api/getUserList",method:"post",label:"",value:"id"},name:"user_id",formItem:{label:"选择用户"}},{type:"select",control:{modelValue:"",appendToBody:true},options:[],config:{optionsType:1,optionsFun:"http://180.76.145.80/api/getDeviceAllList",method:"post",value:"id",label:""},name:"device_id",formItem:{label:"选择设备"}},{type:"input",control:{modelValue:"",placeholder:"请输入内容"},config:{},name:"content",formItem:{label:"内容"}},{type:"input",control:{modelValue:"",placeholder:"请输入单行文本"},config:{},name:"input",formItem:{label:"单行文本"}},{type:"textarea",control:{modelValue:"",placeholder:"请输入多行文本"},config:{},name:"textarea",formItem:{label:"多行文本"}},{type:"inputNumber",control:{modelValue:0},config:{},name:"inputNumber",formItem:{label:"计数器"}},{type:"datePicker",control:{modelValue:"",type:"datetime"},config:{},name:"datePicker",formItem:{label:"日期选择器"}},{type:"timePicker",control:{modelValue:""},config:{},name:"timePicker",formItem:{label:"时间选择器"}},{type:"colorPicker",control:{modelValue:""},config:{},name:"colorPicker",formItem:{label:"取色器"}},{type:"rate",control:{modelValue:0},config:{},name:"rate",formItem:{label:"评分"}},{type:"slider",control:{modelValue:0,min:0,max:100,step:5},config:{},name:"slider",formItem:{label:"滑块"}},{type:"switch",control:{modelValue:false,activeValue:1,inactiveValue:0},config:{},name:"switch",formItem:{label:"开关"}},{type:"radio",control:{modelValue:1},options:[{label:"选项1",value:1},{label:"选项2",value:2},{label:"选项3",value:3}],config:{optionsType:1,transformData:"number"},name:"radio",formItem:{label:"单选框组"}},{type:"checkbox",control:{modelValue:[]},options:[{label:"选项1",value:1},{label:"选项2",value:2},{label:"选项3",value:3}],config:{optionsType:0,transformData:"number"},name:"checkbox",formItem:{label:"多选框组"}},{type:"upload",control:{modelValue:"",action:"http://47.94.105.29:66/api/upload",multiple:true,limit:4,listType:"picture-card"},config:{tip:"",btnText:""},name:"image",formItem:{label:"图片/文件"}},{type:"upload",control:{modelValue:"",action:"http://47.94.105.29:66/api/upload",limit:4,drag:true},config:{},name:"video",formItem:{label:"视频文件"}}],form:{size:"default",labelWidth:"100px",width:"100%"},config:{submitCancel:true}}',
dict: "",
title: "label",
targetTable: targetTableName,
});
console.log("✅ 本地脚本数据库创建完成:", dbPath);
} catch (e) {
console.error("❌ 创建失败:", e);
} finally {
db.close();
}
}
使用脚本读写参数配置
在业务执行过程中,可通过脚本直接操作设备本地的 SQLite 数据库,实现配置读取与状态回写。
支持的操作:
- 查询配置(SELECT) 👉 获取当前执行参数(
isTemplate = 0) - 更新配置(UPDATE) 👉 回写执行状态 / 任务进度 / 执行结果
所有数据均存储于设备本地 .db 文件中,实现本地持久化,并支持云控后台同步查看。
通过 Bot.js Pro 提供的 sqlite 模块,可在脚本中完成数据库操作。
查询配置(获取执行参数)
用于获取当前“运行态配置”(即 isTemplate = 0 的数据)。
var scriptContent = getExecutableConfig("/sdcard/cloud/SQLite/本地脚本.db", "脚本示例");
if (scriptContent) {
console.log("✅ 获取到的脚本配置参数:");
console.log(JSON.stringify(scriptContent, null, 4));
} else {
console.warn("⚠️ 未获取到可执行脚本配置!");
console.warn("👉 可能原因:");
console.warn("1️⃣ 表【脚本示例】不存在");
console.warn("2️⃣ 表中没有 isTemplate = 0 的数据");
console.warn("3️⃣ 数据库路径错误或文件不存在");
console.warn("4️⃣ 数据库结构异常(字段缺失)");
}
/**
* 获取【可执行脚本配置数据】
*
* 📌 功能说明:
* 从指定数据库 + 表中,查询一条:
* 👉 isTemplate = 0 的数据(即“可执行数据”)
*
* 📌 使用场景:
* - 脚本执行前读取配置
* - 动态任务参数获取
* - 云控任务执行入口
*
* 📌 查询规则:
* - 只取第一条(LIMIT 1)
* - 如果没有数据 → 返回 null
*
* ⚠️ 注意:
* - 表必须存在 isTemplate 字段
* - 表名支持中文(已自动处理)
*
* @param {string} dbPath 数据库路径
* @param {string} tableName 表名(如:脚本示例)
*
* @returns {Object|null} 返回数据对象,找不到返回 null
*/
function getExecutableConfig(dbPath, tableName) {
var db = sqlite.open(dbPath);
try {
// 👉 查询 isTemplate = 0 的数据
var result = db.rawQuery("SELECT * FROM `" + tableName + "` WHERE isTemplate = ? LIMIT 1", [0]).single();
return result;
} catch (e) {
console.error("❌ 查询失败:", e);
return null;
} finally {
db.close();
}
}
更新配置(回写执行状态)
用于更新当前运行中的配置数据(同样基于 isTemplate = 0)。
var rows = updateExecutableConfig(
"/sdcard/cloud/SQLite/本地脚本.db",
"脚本示例",
{
status: 2,
content: "执行完成"
}
);
console.log("🔄 更新结果 rows =", rows);
/**
* 更新【当前执行配置记录】
*
* 📌 功能说明:
* 使用 SQLite 的 db.update 方法,
* 更新表中满足条件的数据(固定条件:isTemplate = 0)
*
* 📌 适用场景:
* - 更新任务执行状态(status)
* - 写入执行结果(content)
* - 更新运行过程数据(execCount / log 等)
*
* 📌 工作机制:
* UPDATE tableName
* SET data...
* WHERE isTemplate = 0
*
* 📌 特点:
* - 无需传入 id
* - 自动定位当前执行中的记录(isTemplate = 0)
* - 使用 db.update 原生 API,无需手写 SQL
*
* ⚠️ 约束条件:
* - 表中必须存在 isTemplate 字段
* - isTemplate = 0 的记录应唯一(否则会批量更新)
*
* @param {string} dbPath 数据库文件路径
* @param {string} tableName 表名(支持中文表名)
* @param {Object} data 需要更新的字段(键值对)
* @return {number} 受影响的行数
*/
function updateExecutableConfig(dbPath, tableName, data) {
var db = sqlite.open(dbPath);
try {
return db.update(
tableName,
data,
"isTemplate = ?",
[0]
);
} finally {
db.close();
}
}