Widget(PageWidget)定义编写指南¶
目标:Agent 直接生成/修改 workspace 首页小组件。知识以本文为准。不写原理;不依赖外链。
iScript:写 type=iscript|page 的 actionContent、或 authMode=2 的 userScript 时,先读 iscript-usage(含 GraalVM 差异) → widget.md;本文只定类型/授权与落盘。
术语:实体类 PageWidget;产品文档「Widget / 小组件 / 小工具」同义。是软件前台**主页门户**的砖块(视图/摘要/图表/流程待办/脚本 HTML 等),**不是**表单控件,**不是**菜单。
依赖:设计态新建 Widget 必须先有 Widget 分组(PageWidgetGroup)。无分组时保存会抛「请选择widget分组!」。分组知识写在本文 §7。
产出物一览¶
| 部分 | 落盘 | 形态 |
|---|---|---|
| Widget 分组 | widget/{分组名}.widgetgroup/{分组名}.widgetgroup |
目录型;JAXB 根 pageWidgetGourp(拼写是 Gourp,不是 Group) |
| Widget | widget/{分组名}.widgetgroup/{Widget名}.widget |
单文件;JAXB 根 pageWidget;id 为根属性 |
# 推荐(设计态现行):分组下挂 Widget
storage/workspace/{软件名}.application/widget/{分组名}.widgetgroup/{分组名}.widgetgroup
storage/workspace/{软件名}.application/widget/{分组名}.widgetgroup/{Widget名}.widget
# 历史/样例常见:无分组,文件直接躺在 widget/ 下(仍可被递归扫到)
storage/workspace/{软件名}.application/widget/{Widget名}.widget
路径相对 storage/workspace。DAO 按后缀 .widget **递归扫描**整棵应用树,两种布局运行时都能载入。
新建最低配置:
- 若不存在分组:先建
{name}.widgetgroup/+ 同名元数据(见 §7) - 写
{名}.widget:id、name、type、published=true、widgetGroupId=分组 id、parentId=分组 id、authMode=0、widgetType=0、orderno - 按
type填actionContent/moduleid/carouselImg/helpPath/countType/showAll等(见类型表) icon写 JSON 字符串;icontype=img或font
约定:
- JAXB;根
pageWidget;id在 根属性 - 脚本/长文本用 CDATA:
actionContent、userScript、queryString、tabTags - 文件名 =
name+.widget;同应用内name不重名(设计态按应用扫同名校验) name勿含/%\(落盘替换为=47/=37/=92)parentId:有分组时 =widgetGroupId;历史无分组时 = 软件 idapplicationid:基类字段**无** JAXB@XmlElement,落盘常缺;运行/API 靠加载上下文;API 保存时会 set- 布尔
true/false;排序字段 XML 名orderno(全小写) - **勿**放到
module/下;永远在应用级widget/
1. 心智模型¶
软件 Application
└─ widget/
├─ {分组}.widgetgroup/ ← PageWidgetGroup
│ ├─ {分组}.widgetgroup ← 分组元数据
│ └─ {名}.widget ← PageWidget(推荐)
└─ {名}.widget ← 历史无分组样例
前台主页
├─ GET /api/runtime/homepage/config → widgets 模板 + 用户 setting
└─ GET /api/runtime/{appId}/widgets/{widgetId} → 按 type 拉内容
个人布局:运行库 UserDefined.templateElement(JSON),不在 .widget 文件内
| 概念 | 说明 |
|---|---|
| 分组 | 设计器左侧树;widgetGroupId 指分组 id;物理目录容器 |
| 发布 | published=true 才进入用户可配置/可见集合 |
| 授权 | authMode:全角色 / 选定角色 / 用户脚本筛选 |
| 设备 | widgetType:通用 / 仅 PC / 仅手机 |
| 快速入口 | iconShow=true:前台当图标入口;display 时直接返回 actionContent |
| 系统 Widget | 运行时 id 以 system_ 开头可虚构(newSystemWidget);设计盘上常见写死的 system_workflow 文件 |
| 用户布局 | 用户拖拽结果存个人设置,不是改 .widget |
2. PageWidget 属性 → .widget XML¶
属性表¶
| 属性 | XML | 类型 | 默认 | 说明 |
|---|---|---|---|---|
id |
根属性 | string | 须生成 | __ + 短 UUID |
name |
子元素 | string | — | 必填;文件名;应用内不重名 |
parentId |
子元素 | string | — | 有分组 = 分组 id;无分组 = 软件 id |
applicationid |
常不落盘 | string | API 侧 = 软件 id | |
description / remark |
CDATA | string | 少用 | |
type |
子元素 | string | — | 必填;见类型表 |
moduleid |
子元素 | string | 选模块后再选视图/报表/图表时填模块 id | |
actionContent |
CDATA | string | 内容指针;语义随 type 变(id / URL 脚本 / 分析类型名等) | |
userScript |
CDATA | string | "" |
authMode=2 时返回用户 id/UserVO 集合 |
queryString |
CDATA | string | 请求参数;少用 | |
width / height |
子元素 | string | 传统宽高(数字串或空);流式布局优先 wgt* |
|
authMode |
子元素 | int | 0 |
0 全角色 / 1 选定角色 / 2 选定用户(脚本) |
authRolesId |
子元素 | string | authMode=1 时角色 id 串(可逗号等拼接;运行用 indexOf) |
|
authRolesName |
子元素 | string | 展示用角色名串 | |
tabTags |
CDATA | string | [] |
JSON 数组字符串;iscript 类型的页签切换配置 |
published |
bool | false | 前台要显示须 true | |
setup |
bool | false | 是否曾被用户自定义布局使用过的标记;生成时可 false |
|
icon |
子元素 | string | JSON 字符串,见下 | |
icontype |
子元素 | string | img / font(与 icon JSON 内一致) |
|
carouselImg |
子元素 | string | carousel:图片 URL 的 JSON 数组字符串;其它可 [] 或空 |
|
helpPath |
子元素 | string | help:上传的帮助文件路径 |
|
frequency |
子元素 | int | 1 |
轮播秒数(carousel);其它类型常 0/1 |
titleColor |
子元素 | string | 如 #000 |
|
titleBColor |
子元素 | string | 标题背景色 rgba | |
titleBarColor |
子元素 | string | 标题栏色 | |
iconShow |
bool | false | true=快速入口;勾选「广泛使用」类选项 | |
widgetType |
子元素 | int | 0 |
设备支持:0 通用 / 1 仅网页 / 2 仅手机 |
orderno |
子元素 | int | 排序,越小越前 | |
countType |
子元素 | string | none |
签到统计:none/month/quarter/year |
isRefresh |
bool | false | 是否可刷新 | |
wgtWidth |
子元素 | string | 流式宽:1X/2X/3X |
|
wgtHeight |
子元素 | string | 流式高:1X…8X |
|
widgetGroupId |
子元素 | string | 设计态必填;= 分组 id | |
showAll |
bool | false | 主要为 system_workflow:「显示全部软件」待办/经办 |
不落盘/运行期:uri、用户 homepage setting、系统虚构 Widget 实例。
id 生成¶
统一规则:__ + 短 UUID。示例:__MpEzTToulqZtNEFisw6。须全局/库内不冲突(.widget / .widgetgroup 同)。
icon 格式¶
字符串形式 JSON(不是嵌套 XML):
{"icon":"/uploads/lib/icon_menu_default.png","icontype":"img", "imgUrl": "/uploads/lib/icon_menu_default.png"}
字体图标:
{"icon":"fa fa-bell-o","icontype":"font", "fontUrl":"fa fa-bell-o", "iconFontColor":"rgba(30, 144, 255, 1)"}
落盘路径规则(代码事实)¶
PageWidget.getPath():
widgetGroupId非空 →{分组.getPath()}/{name}.widget
即…/widget/{分组名}.widgetgroup/{name}.widget- 否则 →
{parent.getPath()}/{name}.widget
parent 为软件时 ≈…/{软件名}.application/{name}.widget(无/widget段)
历史包常把无分组文件放在 widget/ 目录——与上述无分组公式不完全一致,但查询靠后缀递归,仍可读。新写一律走有分组路径。
authMode¶
| 值 | 常量 | 行为 |
|---|---|---|
0 |
AUTH_MODE_ALL_ROLES | 有软件角色即可(设计「授权给所有角色」) |
1 |
AUTH_MODE_SOME_ROLES | authRolesId 含当前用户角色 id 之一 |
2 |
AUTH_MODE_SOME_USERS | 跑 userScript,结果是用户 id 集合或 UserVO[],含当前用户 |
published=false → 首页聚合直接跳过,不论授权。
widgetType(设备)¶
| 值 | 常量 | 说明 |
|---|---|---|
0 |
WIDGET_TYPE_GENERAL | PC+手机 |
1 |
WIDGET_TYPE_WEBPAGE | 仅网页;手机设备跳过 |
2 |
WIDGET_TYPE_MOBILE | 仅手机;PC 跳过 |
3. type 与 actionContent(核心)¶
设计器当前下拉(widgetOptions)常见值如下。Java 常量还有更多历史类型,见文末附录。
| type | 含义 | moduleid | actionContent | 其它关键字段 |
|---|---|---|---|---|
summary |
摘要(待办/分享等) | 空 | 摘要配置 id(SummaryCfg) | — |
view |
挂接视图 | 模块 id | 视图 id | 视图 type 常过滤为列表/树等;iconShow 可开快速入口 |
page |
链接内容 | 空 | iScript:运算后返回 URL 字符串;或纯 URL 思路 | 运行会 runner.run 整段脚本 |
customizeReport |
自定义报表 | 模块 id | 报表 id | iconShow 可选 |
chart |
统计图 | 模块 id | 图表 id | iconShow 可选 |
system_workflow |
流程处理 | 空 | 可空 | showAll:是否跨全部软件 |
iscript |
计算脚本值 → HTML | 空 | iScript:必须返回 String HTML 片段 | tabTags JSON 数组;环境变量有 WebUser |
carboncopy |
抄送数据 | 空 | 可空 | 前台走流程中心抄送数据 |
carousel |
轮播图 | 空 | 可空 | carouselImg JSON 数组;frequency 秒 |
checkin |
签到 | 空 | 可空 | countType=none/month/quarter/year |
help |
帮助 | 空 | 可空 | helpPath 上传文件路径 |
设计器保存时把 UI 字段映射进 actionContent:
- view →
actionView;chart →chart;iscript →iscript;page →actionUrl;summary →actionSummary;customizeReport →customizeReport carouselImg/tabTags:数组 →JSON.stringify再写入字符串字段
page vs iscript¶
page |
iscript |
|
|---|---|---|
| 脚本返回 | URL / 可被当作页面地址用的串 | HTML 字符串 |
| 非法返回 | 视前端处理 | 后端抛「脚本运算结果不合法,请返回字符串」;错误信息可当 html |
iscript 最小示例:
page 最小示例:
summary 的 actionContent¶
指向摘要配置 id。运行时按 SummaryCfg.scope 取待办/分享/抄送等列表(scope 常见 0 待办、1 分享、6 抄送;设计器列表会过滤部分 scope)。
view 的 actionContent¶
视图 id。运行时 view.getViewTypeImpl().getViewDatas(...)。建议同时写对 moduleid(该视图所属模块),否则设计器回填会丢。
视图筛选(设计器用):常见只列 type==1 || type==16 的视图。
iconShow=true(快速入口)¶
displayWidget **优先**分支:直接 return actionContent,不再按 type 拉取视图数据等。适合把入口画成图标,点击打开 actionContent 指向的资源。
4. .widget XML 骨架¶
视图 Widget(推荐有分组)¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pageWidget id="__WIDGET_UUID">
<name>待办列表</name>
<parentId>__GROUP_UUID</parentId>
<type>view</type>
<moduleid>__MODULE_UUID</moduleid>
<actionContent><![CDATA[__VIEW_UUID]]></actionContent>
<userScript><![CDATA[]]></userScript>
<width></width>
<height></height>
<authMode>0</authMode>
<authRolesId></authRolesId>
<authRolesName></authRolesName>
<tabTags><![CDATA[[]]]></tabTags>
<published>true</published>
<setup>false</setup>
<icon>{"icon":"/uploads/lib/icon_menu_default.png","icontype":"img", "imgUrl": "/uploads/lib/icon_menu_default.png"}</icon>
<icontype>img</icontype>
<carouselImg>[]</carouselImg>
<helpPath></helpPath>
<frequency>0</frequency>
<titleColor>#000</titleColor>
<titleBColor>rgba(255, 255, 255, 0)</titleBColor>
<titleBarColor>rgba(255, 255, 255, 0)</titleBarColor>
<iconShow>false</iconShow>
<widgetType>0</widgetType>
<orderno>1</orderno>
<countType>none</countType>
<isRefresh>true</isRefresh>
<wgtWidth>2X</wgtWidth>
<wgtHeight>4X</wgtHeight>
<widgetGroupId>__GROUP_UUID</widgetGroupId>
<showAll>false</showAll>
</pageWidget>
路径:/{软件}.application/widget/{分组名}.widgetgroup/待办列表.widget
流程处理¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pageWidget id="__WF_WIDGET">
<name>流程处理</name>
<parentId>__GROUP_UUID</parentId>
<type>system_workflow</type>
<moduleid></moduleid>
<actionContent><![CDATA[]]></actionContent>
<userScript><![CDATA[]]></userScript>
<authMode>0</authMode>
<authRolesId></authRolesId>
<authRolesName></authRolesName>
<tabTags><![CDATA[[]]]></tabTags>
<published>true</published>
<setup>false</setup>
<icon>{"icon":"/uploads/lib/icon/iconstaff.png","icontype":"img", "imgUrl": "/uploads/lib/icon/iconstaff.png"}</icon>
<icontype>img</icontype>
<carouselImg>[]</carouselImg>
<helpPath></helpPath>
<frequency>0</frequency>
<titleColor>#000</titleColor>
<iconShow>false</iconShow>
<widgetType>0</widgetType>
<orderno>1</orderno>
<countType>none</countType>
<isRefresh>true</isRefresh>
<wgtWidth></wgtWidth>
<wgtHeight></wgtHeight>
<widgetGroupId>__GROUP_UUID</widgetGroupId>
<showAll>true</showAll>
</pageWidget>
轮播¶
type=carouselcarouselImg=["/uploads/resource/image/a.jpg","/uploads/.../b.jpg"](属性文本,或 CDATA)frequency=3(秒)
iscript¶
type=iscriptactionContent= 返回 HTML 的脚本(CDATA)- 可在 HTML 内嵌
<o-action …>做打开文档/视图等(常用脚本集合模式)
5. 设计态 / 运行态 API(定位用)¶
设计态(前缀以部署为准,常见 /api/designtime/applications):
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /{appId}/widgets |
列表;query:name、widgetGroupId、pageNo、linesPerPage |
| GET | /{appId}/widgets/{widgetId} |
详情;summary 额外返回 summaryName |
| POST | /{appId}/widgets |
新建;强制 parentId=widgetGroupId;空 id 自动序列 |
| PUT | /{appId}/widgets/{widgetId} |
更新;同上;uri 置 null |
| DELETE | /{appId}/widgets |
body:id 数组 |
校验:widgetGroupId 空 → 400「请选择widget分组!」;同应用 name 重复 →「该名称已存在」。
运行态(常见 /api/runtime):
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /{appId}/widgets/{widgetId} |
按 type 取内容;路径参数可能按用户加密 |
| GET | /homepage/config |
用户可见 widgets 模板 + setting;applicationId、isMobile |
| POST | /homepage/config |
保存用户布局 JSON → UserDefined |
| GET | /{appId}/homepage/phoneConfig |
读软件 systemWidgetSetting(Banner/menuIcon/system_workflow 等) |
首页聚合逻辑摘要:跨用户可见软件 → 收集 published Widget → 按设备与 authMode 过滤 → orderno 升序 → 与用户 templateElement 合成。
iscript 类型在下发 homepage 模板时**故意不**把脚本原文放进 actionContent(防泄露);展示时再 display。
部分类型在 homepage 响应里会对 actionContent/applicationId 做用户级加密(summary/view/chart/customizeReport/system_workflow/carboncopy/carousel 等)。
6. 与角色 / 菜单 / 软件的边界¶
| 能力 | 载体 | Widget 是否管 |
|---|---|---|
| 软件可见 | 域绑定 + activated + 角色 | 否 |
| 菜单可见 | ResourceVO + role.permissions | 否(widgetGroupId 字段在菜单上存在但少用) |
| Widget 可见 | published + authMode(+设备) |
是 |
| 个人摆放 | UserDefined.templateElement | 否(运行库) |
| 手机系统区开关 | Application.systemWidgetSetting |
软件级字符串,不是 .widget |
角色 .role 的 permissions **不**用来授权单个 Widget(Widget 用自身 authMode/authRolesId)。
7. Widget 分组(PageWidgetGroup)— 本文内自足¶
实体:PageWidgetGroup。根元素名历史拼写错误,**必须**写作:
- XML 根:
<pageWidgetGourp id="...">(Gourp) @XmlType(name = "PageWidgetGourp")
后缀:ModelSuffix.WIDGET_GROUP_* → 路径/文件后缀均为 widgetgroup;文件组 /widget。
属性¶
| 属性 | XML | 说明 |
|---|---|---|
id |
根属性 | 须生成 |
name |
子元素 | 必填;目录名;同应用下不重名 |
parentId |
子元素 | = 软件 id |
description/remark |
CDATA | 可选;样例常省略 |
无业务字段。分组本身不作前台渲染单元,只作分类与路径父级。
骨架¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pageWidgetGourp id="__GROUP_UUID">
<name>默认分组</name>
<parentId>SOFTWARE_UUID</parentId>
</pageWidgetGourp>
路径:/{软件}.application/widget/默认分组.widgetgroup/默认分组.widgetgroup
设计态 API:/{appId}/widgetgroups CRUD。更新只改 name 等,删除可批。新建时 parentId=applicationId。
改分组 name = 改目录名;其下 .widget 随之搬家(靠 path 计算)。**勿随意改名**除非同步磁盘。
删除分组前应先迁走/删除子 Widget,否则遗留文件。
8. 检查清单(生成后自检)¶
- 已有
.widgetgroup,且 Widget 的widgetGroupId=parentId=该分组 id - 文件在
widget/{分组}.widgetgroup/{name}.widget - 根元素
pageWidget;分组根元素pageWidgetGourp -
type取自上表;actionContent/moduleid/carouselImg/helpPath/countType/showAll与 type 匹配 -
published=true(若要前台出现) -
authMode=1时authRolesId非空;=2时userScript返回用户集合 -
icon是合法 JSON 字符串;icontype一致 -
carousel:carouselImg为字符串化数组;frequency>0 -
iscript:返回 HTML 字符串;勿只返回对象 -
orderno整数;流式用1X… 格式 - 同应用无同名 Widget;name 无
/\%
9. 典型生成顺序¶
- 确认软件目录
{app}.application/存在 - 建分组
widget/{G}.widgetgroup/{G}.widgetgroup - 按业务选 type,解析目标资源 id(视图/摘要/图表/报表)
- 写
.widget到分组目录下 - 需要选定角色授权时填
authMode=1+authRolesId(角色 id 来自.role) - 在前台用有该软件角色的用户打开首页验证;或调 runtime homepage/config
附录 A:Java 常量中另有、设计器主菜单未列出的 type¶
可用于兼容旧包或特殊场景;生成新 Widget 优先用设计器列表。
| 常量值 | 含义 |
|---|---|
link |
快速入口/内部链接(常量 TYPE_LINK) |
activity |
按钮操作(运行 display 现为 FIXME/null) |
report |
报表(旧) |
runquanReport |
润乾报表 |
customizeReport |
已在主列表 |
system_announcement / system_weather / system_email / system_knowledge / system_empconbook |
系统类;多为运行虚构或历史 |
workflow_analyzer |
流程分析;actionContent 为分析器方法名如 analyzerActorTimeConsumingTopX |
运行时对 widgetId.startsWith("system_") 可不用磁盘文件,直接 newSystemWidget(widgetId)。
附录 B:后缀常量(ModelSuffix)¶
| 常量 | 值 |
|---|---|
| WIDGET_PATH_SUFFIX / WIDGET_FILE_SUFFIX | widget |
| WIDGET_GROUP_PATH_SUFFIX / WIDGET_GROUP_FILE_SUFFIX | widgetgroup |
| WIDGET_GROUP_FILE_GROUP | /widget |
附录 C:常见失败¶
| 现象 | 原因 |
|---|---|
| 设计器保存报请选择分组 | 缺 widgetGroupId |
| 首页看不到 | published=false / authMode 未命中 / widgetType 与设备不符 / 用户无软件角色或软件未激活未绑定 |
| iscript 出异常字串 | 未 return String |
| 视图空白 | actionContent 不是合法视图 id,或视图无权限/无数据 |
| 改名后找不到 | 文件名与 <name> 不一致,或分组改名未搬文件 |
| 分组 XML 解析失败 | 根标签写成了 pageWidgetGroup(少了历史拼写 Gourp) |