

登录后的平台首页。左侧是「开发套件 / 管理工具 / 我的应用 / 示例」四项入口,右侧欢迎页把上手路径写得很直白:打开示例 → 打开文档 → 打开集成开发环境 → 打开数据库配置 → 打开用户管理。这张图也说明了一件事——平台自己就把「示例」放在第一步。
学一个快速开发平台,最容易走的弯路是从 API 手册第一页开始啃。API 手册告诉你「有什么」,示例告诉你「这些东西怎么凑在一起能跑」。
geejing WebBuilder 快速开发平台在 wb/modules/example/ 下内置了一整套可运行的示例,按主题分成 11 个目录:comps(组件)、basic(基础页面)、load(加载)、coding(脚本)、crud(增删改查)、tools(工具)、apps(完整小应用)、touch(移动端)、workflow(工作流)、common(通用)、ai(AI 生成示例)。每个目录里都是可以直接用 m?xwl=<路径> 打开的 .xwl 文件,且每个文件都有一份 .min.xwl 压缩版。
这批示例的价值在于三点:能跑(不是代码片段)、能改(IDE 里打开即编辑)、能抄(服务端脚本、事件绑定、属性配置全都是真实可复用的写法)。本文不打算泛泛讲概念,只做一件事:把 example/ai/crud-dialog.xwl 这一个文件从打开到跑通完整拆一遍,让你看完就能照着改出自己的模块——这也是我们推荐给新团队上手 geejing WebBuilder 快速开发平台的第一条路径。
环境说明:本文所有画面与代码基于 WebBuilder 11 本地部署,浏览器直接访问,无需额外构建工具;截图为真实界面,示例数据为平台自带演示数据。
从首页左侧点「开发套件」中的集成开发环境,或直接访问 m?xwl=dev/ide,进入下图这个界面。

集成开发环境(IDE)初始画面。顶部是「文件 / 编辑 / 导航 / 搜索 / 运行 / 工具 / 帮助」菜单与一排快捷图标,左侧是文件浏览器,底部是「标记 / 调试 / 搜索 / 线程 / AI Gen」标签页,右上角显示当前登录用户 admin。
第一眼容易觉得"右侧怎么是空的"——这是正常的:IDE 是一个多文档工作区,没有打开任何模块时,右侧设计区保持空白。左侧文件浏览器把模块按根目录分组:dev 开发套件、admin 管理工具、my 我的应用、sys 系统、example 示例,下面还有「应用」与「系统」两个资源分组。
顶部菜单里几个和日常开发最相关的动作:导航负责在模块间跳转,搜索做全模块文本检索(找一个 SQL 片段或某个事件名特别好用),运行直接在 IDE 里预览当前模块,而不必切回浏览器手输地址。底部那排标签里,「调试」和「线程」用于排查服务端脚本,「AI Gen」则是 AI 生成入口——本文第三篇会专门讲它。
这里有个容易被忽略但很关键的细节:IDE 操作的对象就是磁盘上的文本文件。你在 IDE 里改一个属性,落盘的是 .xwl 里的一行 YAML;这意味着模块能进 Git、能做 diff、能批量替换、能被 AI 直接读写。这跟"只能在某个设计器里点、导出成一坨二进制"的低代码平台是两条路。
在文件浏览器里展开 example 示例,会看到上面提到的 11 个分类目录。

展开 example 后的目录树:comps、basic、load、coding、crud、tools、apps、touch、workflow、common、ai,共 11 类。右侧仍是空的,因为还没有打开具体模块。
对新手,建议按这个顺序读:basic/layout.xwl 看清容器与布局 → basic/grid1-form.xwl 看清表格与表单联动 → crud/ 下看清增删改查 → apps/oa/leave.xwl 看清工作流与多表 → ai/crud-dialog.xwl 看清 AI 生成出来的完整模块长什么样。
我们今天直接跳到 example/ai/——这个目录下的 5 个文件(comp.xwl、crud-dialog.xwl、crud-edit-grid.xwl、detail-page.xwl、order.xwl)是平台用 AI 生成能力产出的示例,结构上最"标准",也最适合当模板。
双击 crud-dialog.xwl,工作区立刻变了。

打开 crud-dialog.xwl 后的设计视图。自左至右:文件浏览器、属性面板、对象浏览器、控件库;顶部出现模块页签与「设计 / 源码」切换。这是 geejing WebBuilder 快速开发平台最核心的一张开发界面。
展开状态保留了 example/ai/,当前选中的 crud-dialog.xwl 高亮。旁边同级的 crud-edit-grid.xwl、order.xwl、comp.xwl、detail-page.xwl 可以直接双击切换,边改边对照很方便。
上半部分是属性,按字母排序列出当前选中对象的可配置项。截图中能看到 bodyTag、cid、cls、description、head、language、links、loginRequired、moduleType、obfuscate、remark、serverMethod、serverScript、style、theme、title 等;其中 cls 的值是 module,serverScript 一栏显示 let action = Params.xaction; if ... 的开头,description 写着 "Dialog based database CRUD"。
下半部分是事件,能看到 beforeUnload、finalize、initialize,initialize 的值是 Wb.apply(app, { checkExists(resp)... ——这是模块初始化时把校验函数挂到 app 上的写法。
属性面板最实用的地方是:你不需要记住每个组件有哪些属性,选中对象,能改的都列在这里,值直接显示当前配置。改完即时生效,不经过编译。
这是理解 XWL 结构的关键。它把模块展开成一棵组件树,截图中可见:
module serverScript initialize
└─ editWin
├─ code Code
├─ full_name Full name
├─ photo Photo
├─ user_name @xpath + @action=selectUsers...
├─ dept_name @xpath + @action=selectDeptT...
├─ birth_date Birth date
├─ gender Gender
├─ salary Salary
├─ cvTitle Personal Resume
└─ cv
└─ viewport1
└─ tbar
├─ addBtn 添加 click
├─ editBtn 编辑 click
├─ delBtn 删除 click
├─ divider1
└─ search change
└─ grid1 @xpath + @action=select itemdblclick
└─ columns
├─ rowNumCol
├─ codeCol Code
├─ fullNameCol Full name
└─ ...几个值得停下来看的点:
editWin 是一个 Window,表单字段都挂在它下面,所以点「添加」弹出的是对话框,不是新页面;user_name / dept_name 这两个 Select 绑定了远程 action(selectUsers / selectDeptTree),下拉数据由服务端脚本提供,而不是写死枚举;tbar 是 viewport1 的属性,里面是 addBtn / editBtn / delBtn / search 等 Wb.Item,每个按钮后面标着 click 事件;grid1 绑定 action=select,双击行触发 itemdblclick(通常是打开编辑);列通过 columns 数组节点表达。一句话概括:XWL 用一棵组件树描述界面,用属性描述行为,用事件属性挂接服务端动作。看懂这棵树,就看懂了平台里绝大多数模块。
右侧是可视化拖拽用的控件清单,General 分组里能看到 viewport、array、window、panel、container、splitter、tab、card、cardCt、fieldset、component、viewer、menu、toolbar、item、line、title、divider、gap、fill、script、button、splitButton、label;往下还有 layout 分组的 text、number、textArea、select、date、time 等。
这份清单有个很实际的作用:它界定了"什么组件是真的"。写 XWL 时凭印象造一个 cls: Wb.Form 或 Wb.Combo,设计器不会认;正确写法是 fieldset + Select,属性用 text / keyName / required 而不是 fieldLabel / dict / allowBlank。后面第二篇会专门把这张控件表展开讲。
点顶部「源码」标签,同一个模块换成代码视角。

源码」标签下的真实画面:左侧文件树不变,中间是带行号与语法高亮的源码编辑器,右侧是整份代码缩略图(minimap),底部仍是 AI Gen 等标签。
这里要澄清一个很容易混淆的点,也是很多教程没讲清楚的:
磁盘上的 .xwl 文件是 YAML。 用文本编辑器打开 wb/modules/example/ai/crud-dialog.xwl,开头是这样的(原文节选):
_expanded: true
_icon: module
cls: Wb.Module
events:
initialize: |-
Wb.apply(app, {
checkExists(resp) {
if (resp?.toLowerCase().includes('wb_staff_code')) {
Wb.error(Str.alreadyExists.format(app.code.value), f => app.code.focus(false, true));
}
}
});
hideInMenu: 'false'
items:
- _expanded: true
_icon: window
cls: Wb.Window
items:
- _icon: title
cls: Wb.Title
properties:
cid: generalTitle
title: General Information
text: generalTitle
- _icon: text
cls: Wb.Text
properties:
cid: code
maxLength: '5'
required: 'true'
text: Code
text: code注意 `events.initialize: | -与后文properties.serverScript: | - 用 YAML 块标量承载多行 JavaScript;所有属性值写成带引号的字符串('false'、'5'`),这是平台强制约定,AI 生成模块时也严格遵守。 |
IDE 的「源码」标签则是另一种视角:截图里显示的是 {"name":"module", "serverScript(){ let action = Params.action; ... }...} 这样的 JS 对象字面量,把模块定义与服务端脚本作为对象方法呈现,便于直接编辑脚本逻辑。两种表示指向同一份模块定义——YAML 适合版本管理与批量处理,JS 视图适合写逻辑。理解这一点,就不会在"到底该按哪种格式写"上纠结。
下面是 crud-dialog.xwl 中 properties.serverScript 的真实原文(节选,未改写),它是整个模块的心脏:
let action = Params.xaction;
if (!action) return;
let actions = {
select() {
let sql;
sql = `
select a.*,b.user_name,c.dept_name,c.dept_code from wb_staff a, wb_user b, wb_dept c
where a.user_id=b.sid and a.dept_id=c.sid
`;
if (Params.search) {
Wb.setLike('search');
sql += ' and (a.full_name like {?search?} or a.code like {?search?} or b.user_name like {?search?})';
}
sql += ' order by a.code';
Wb.sendRowx({ sql, kv: { gender: 'gender' } });
},
add() {
let rec = { sid: Wb.getId() };
Wb.set(rec);
Wb.sync({ tableName: 'wb_staff', insert: Params });
Wb.send(rec);
},
edit() {
// Params contains blob/photo fields
Wb.sync({ tableName: 'wb_staff', update: Params });
},
del() {
let del = Wb.getObject('del');
Wb.sync({ tableName: 'wb_staff', del });
},
getPhoto() {
let row = Wb.getRow({
sql: 'select photo from wb_staff where sid={?sid?}',
blob: true
});
if (row?.photo)
Wb.exportData(row.photo);
else
Wb.exportData(new Wb.File(true, 'wb/images/null.png').stream);
},
selectUsers() {
let sql = 'select sid,user_name from wb_user';
if (Params.query) {
Wb.setLike('query');
sql += ' where user_name like {?query?}';
}
sql += ' order by user_name';
Wb.sendRows(sql);
},
selectDeptTree() {
let searchClause = '', query = Params.query, firstItem, sql, rows;
// ... 构造部门树查询
rows = Wb.getRows({ sql, rs: query ? 100 : -1 });
Wb.send(rows);
}
};
actions[action]();这段代码信息密度很高,值得逐条对照:
写法 | 作用 | 值得注意的点 |
|---|---|---|
Params.xaction | 前端请求自带的动作名 | 一个模块只用一个服务端入口,靠 actions 对象分派,不必为每个按钮建一个接口 |
Wb.setLike('search') | 把参数加工成模糊匹配值 | SQL 里写 {?search?} 占位符,平台自动绑定,不用手工拼 like '%...%' |
Wb.sendRowx({sql, kv:{...}}) | 查询结果连列元数据一起回写前端 | kv 指定用哪个字典翻译字段,gender 会自动从 0/1 变成"男/女" |
Wb.getId() | 生成主键 | 新增记录先生成 sid,再 Wb.set(rec) 把它放回上下文 |
Wb.sync({tableName, insert/update/del}) | 一行完成 CRUD | 前端提交的 Params 直接作为数据对象,字段与表列名一致即可 |
Wb.getObject('del') | 取前端提交的删除数据 | 配合 Wb.sync 的 del 参数 |
Wb.getRow({sql, blob:true}) + Wb.exportData | 读取并输出照片二进制 | 没有照片时回退到 wb/images/null.png,边界处理很完整 |
Wb.sendRows(sql) / Wb.send(rows) | 输出查询结果 | 下拉框、树控件的数据源就是这么来的 |
Wb.getRows({sql, rs:-1}) | 取全部行 | rs 控制返回行数,-1 表示不限制 |
如果你之前按 API 手册写过 Wb.getRows + Wb.send 的组合,会发现在真实示例里平台惯用 sendRowx / sendRows 这类"一步到位"的封装:查询与输出合并成一次调用,少写中间变量。这不是两种互相矛盾的 API,而是不同抽象层级——读示例的意义,就是学到这一层地道的写法。
还有一点很关键:整个文件没有一行 Wb.send({success:true})。平台约定"无异常即成功",失败用 Wb.raise 或 Wb.error 抛出,正常路径只输出业务数据。这跟很多自研后端"每个接口都包一层 {code:0,msg:'ok'}"的习惯不同,初次接手时要适应。
在 IDE 里点「运行」,或浏览器直接打开 m?xwl=example/ai/crud-dialog:

示例运行画面。一张 100 条记录的人员表格,列含 Code、Full name、Username、Department、Birth date、Gender、Email、Salary、CV、Photo;左上角是「添加 / 编辑 / 删除 / 搜索」工具栏,底部有分页(1 - 100 / 100)。
点「添加」,弹出表单对话框:

点击「添加」弹出的 editWin 窗口。表单分「General Information」与「Personal Resume」两段,必填项带红星,含 Code、Full name、Username、Dept name、Birth date、Gender、Salary、Photo 上传、CV 多行文本,底部「保存 / 取消」。
把前面的组件树和这两张图对起来看,闭环就清楚了:
grid1 触发 action=select → 服务端 select() 用三表联查 + Wb.sendRowx 输出数据 → 表格渲染 100 行;gender 字段通过 kv:{gender:'gender'} 走字典翻译,所以库里存 0/1、界面显示"男/女";addBtn 的 click 打开 editWin 窗口 → 其中 Username、Dept name 两个 Select 分别请求 selectUsers / selectDeptTree 拿下拉/树数据;action=add → 服务端 Wb.getId() 生成主键 + Wb.sync({insert: Params}) 落库 → Wb.send(rec) 把新记录回写,前端表格即时刷新;edit / del / itemdblclick / search / getPhoto 分支。一个 .xwl 文件 + 一段 serverScript,覆盖了一整套增删改查、字典翻译、下拉联动、树选择、图片存取、模糊搜索。 这就是"快速开发"四个字最具体的样子:不是少写业务,而是把重复的工程装配压缩掉了。在 geejing WebBuilder 快速开发平台里,这种装配复用是默认行为,不需要项目组另外搭一层框架。
读完之后最该做的事是动手改一处,验证自己真的懂了。以"给人员表加一个手机号字段"为例,最短路径是五步:
wb_staff 加列 mobile varchar(20)(平台的数据库浏览器或 init 模块里执行均可);crud-dialog.xwl,在 editWin 下新增一个 Wb.Text,cid 设 mobile、text 设"手机号"、required 按需设置;grid1 → columns 下加一个 Wb.Column,fieldName 填 mobile、text 填"手机号";select() 用的是 select a.*,新增列自动带出;add() / edit() 用的是整包 Params,字段与列名一致即可自动写入;第 4 步是最能体现平台设计取舍的地方——因为服务端脚本面向"数据对象"而非"固定字段列表",加字段这类高频变更往往只动界面定义。如果换成手写 Controller + DTO + SQL 的传统结构,同样的改动要碰三到四个文件。
改完记得看一眼磁盘上的 .xwl diff:YAML 里多出来的就是两个组件节点,清晰可评审,可以直接提交版本库。
第一,先找最像的示例,再从它复制。 example 的 11 个分类本质是一张"需求 → 模板"索引:表单联动找 basic/grid1-form、可编辑表格找 ai/crud-edit-grid、工作流多表找 apps/oa/leave、图表大屏找 basic/dashboard、AI 生成结果参考 ai/order。比起从空白模块起步,改一个已经跑通的示例能省掉大量结构调试。
第二,把 serverScript 的 actions 分派模式当成默认骨架。 一个模块一个入口、Params.xaction 分派、每个分支只做一件业务动作,这套结构在平台的示例里高度一致,也最便于 AI 理解和续写。
第三,把"字典 + 列名一致"当成约定,而不是每次临时处理。 示例里 kv 翻译、整包 Params 落库、Select 走独立 action,都建立在字段命名统一这个前提上。前期把命名规范定好,后面加字段、加页面几乎不需要动服务端。
在 geejing WebBuilder 快速开发平台里,模块就是可复制的文本文件,示例就是官方维护的最佳实践模板。所谓"快速",很大程度上来自不必每次重新发明这套骨架。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。