菜单(ResourceVO)定义编写指南¶
目标:Agent 直接生成/修改 workspace 菜单定义。知识以本文为准。不写原理;不依赖外链。
iScript:linkType 为脚本链接时,actionContent 须 return URL;先读 iscript-usage(含 GraalVM 差异) → menu.md。
术语:菜单实体类是 ResourceVO;产品文档说「PC菜单 / Mobile菜单」。菜单是前台导航入口,**不是**模块;模块内表单/视图需挂菜单才从导航打开。
产出物一览¶
| 部分 | 落盘 | 形态 |
|---|---|---|
| PC 菜单 | menu/{菜单名}.menu/ 或扁平 {菜单名}.menu/ |
目录 + 同名元数据文件;JAXB 根 resourceVO |
| 移动菜单 | mobilemenu/{菜单名}.mobilemenu/ 或扁平 {名}.mobilemenu/ |
同上;isMobile=true |
# 推荐(与 ModelSuffix / APPLICATION_SKILL 一致;设计态 ResourceVO.getPath)
/{应用}.application/menu/{名}.menu/{名}.menu
/{应用}.application/menu/{父}.menu/{子}.menu/{子}.menu
/{应用}.application/mobilemenu/{名}.mobilemenu/{名}.mobilemenu
# 样例包常见扁平布局(无 menu/mobilemenu 分组目录;按后缀仍可读)
/{应用}.application/{名}.menu/{名}.menu
/{应用}.application/{名}.mobilemenu/{名}.mobilemenu
路径相对 storage/workspace。菜单与模块类似:目录型资源(目录名 = name+后缀,目录内再放同名元数据文件)。**不是**角色那种单文件。
新建最低配置:
- 建目录
{name}.menu/(PC)或{name}.mobilemenu/(移动),放在推荐的menu//mobilemenu/下,或与样例一致放在应用根 - 写同名元数据:
id、name、parentId、applicationid、permissionType、status=1、isMobile、orderno - 分类/文件夹菜单:
linkType空、actionContent空 - 叶子功能菜单:设
linkType+actionContent(目标 id 或 URL/脚本);表单/视图/报表/OLAP 等再写moduleid - 子菜单:
superior=parentId=上级菜单 id;落盘在上级菜单目录下 - (按需)
private时在角色.role的permissions中授权本菜单 id(见 ROLE_SKILL)
约定:
- JAXB;根元素
resourceVO;id在 根属性 - 脚本/长文本/
queryString/actionContent/description用<![CDATA[...]]> - 目录名 = 元数据文件名 =
name+ 后缀(.menu或.mobilemenu)三者一致 parentId:顶层=软件 id;子菜单=上级菜单 idsuperior:顶层省略或空;子菜单=上级菜单 id(与parentId同值)applicationid建议始终写 = 软件 id(部分旧样例叶子可缺)name勿含/%\(落盘替换为=47/=37/=92)- **勿**把菜单放到
module/下;菜单在应用级menu//mobilemenu/(或应用根扁平) - 排序字段 XML 名为
orderno(全小写),不是orderNo - PC 与移动是**两套树**(不同后缀/目录);复制 PC→移动见「移动端」
1. 心智模型¶
软件 Application
├─ module/*.module/ … 表单/视图/流程/报表/图表
├─ menu/*.menu/ ← PC 导航树(ResourceVO, isMobile=false)
│ └─ 子.menu/ …
├─ mobilemenu/*.mobilemenu/ ← 移动导航树(isMobile=true)
└─ role/*.role ← permissions 可授权菜单 id(resType=2)
| 概念 | 说明 |
|---|---|
| 分类菜单 | linkType 空;仅作上级;移动端一级菜单**必须**先有分类,再挂叶子 |
| 叶子菜单 | 有 linkType+actionContent;打开表单/视图/报表/URL/脚本等 |
moduleid |
选模块 id,再在该模块下选表单/视图/报表等目标;目标 id 进 actionContent |
| 权限 | permissionType=public 有软件角色即可见;private 须 role.permissions 含本菜单 |
模块 ≠ 菜单。无菜单则用户无法从侧栏/移动导航进入模块内资源。
2. ResourceVO 属性 → .menu / .mobilemenu XML¶
属性表¶
| 属性 | XML | 类型 | 默认 | 说明 |
|---|---|---|---|---|
id |
根属性 | string | 须生成 | __ + 短 UUID;角色授权 resId/operationId |
name |
子元素 | string | — | 必填;目录名与文件名 |
parentId |
子元素 | string | — | 顶层=软件 id;子=上级菜单 id |
applicationid |
子元素 | string | 建议=软件 id | |
description |
CDATA | string | 常用=名称;基类字段 | |
remark |
CDATA | string | 少用 | |
multiLanguageLabel |
子元素 | string | 多语言标签名;设计器从多语言库选 | |
type |
子元素 | string | 资源类型:00=PC菜单;100=Mobile菜单;可省略,以 isMobile+后缀为准 |
|
opentarget |
子元素 | string | detail |
detail=工作区域打开;target=新窗口 |
ico |
子元素 | string | 图标 JSON 字符串,见下 | |
superior |
子元素 | string | 顶层空 | 逻辑上级菜单 id;顶层不写或空;子=上级 id |
linkName |
子元素 | string | 链接显示名;常=name |
|
linkType |
子元素 | string | 空 | 见「链接类型」;空=分类文件夹 |
moduleid |
子元素 | string | 模块 id;linkType 为 00/01/02/09/34 等时需要 | |
directory |
子元素 | string | portal |
内部自定义链接目录前缀;05 用 |
actionContent |
CDATA | string | 目标内容:表单/视图/报表/大屏/OLAP 的 id,或 URL,或脚本源码 | |
actionExcelImport |
子元素 | string | Excel 导入配置 id;样例常有,可选 | |
queryString |
CDATA | string | [] |
请求参数 JSON 数组,见下 |
permissionType |
子元素 | string | public |
public / private |
showType |
子元素 | int | 0 |
0=菜单+流程中心;1=仅菜单;2=仅流程中心 |
status |
子元素 | int | 1 |
1有效 / 0失效(前台不展示) |
isUsual |
子元素 | string | false |
是否常用菜单(字符串布尔) |
isMobile |
子元素 | string | false |
true→落盘 .mobilemenu;false→.menu |
orderno |
子元素 | int | 0 |
同级排序;越小越前;比较用 |
showtotalrow |
子元素 | string | false |
仅视图链接有意义;是否显示记录总数(影响性能) |
report / reportAppliction / reportModule |
子元素 | string | 历史报表字段;新配置用 linkType+actionContent+moduleid |
|
mobileIco |
子元素 | string | 旧移动图标码;现多用 ico |
|
widgetGroupId |
子元素 | string | 关联 widget 分组;少用 | |
uri / path / children |
— | — | 推导/运行期 | 不进 XML |
不落盘:children、totalRow、newIco、allowOpenFrom、allowOpenView(运行/UI 用)。
id 生成¶
统一规则:__ + 短 UUID。示例:__MpEzTToulqZtNEFisw6。须全局/库内不冲突。
permissionType¶
| 值 | 含义 |
|---|---|
public |
对该软件有有效角色即可见(不强制查 permissions 菜单项) |
private |
须在角色 permissions 中显式授权本菜单(resType=2,operationCode=1002,operationId=resId=菜单 id) |
空串写入时后端会当成 private。新建默认写 public。
showType¶
| 值 | 常量 | 含义 |
|---|---|---|
0 |
SHOW_TYPE_BOTH | 菜单与流程中心都可出现 |
1 |
SHOW_TYPE_MENU | 仅菜单 |
2 |
SHOW_TYPE_FLOW_CENTER | 仅流程中心 |
表单启动(00)/图表(02)设计器会展示此项。
opentarget¶
| 值 | UI | 说明 |
|---|---|---|
detail |
工作区域打开 | 默认 |
target |
新窗口打开 |
ico 格式¶
字符串形式 JSON(不是嵌套 XML):
| 键 | 说明 |
|---|---|
icon |
图片路径或字体类名 |
icontype |
img / font |
iconFontColor |
仅 font;CSS 颜色 |
默认图常见:/uploads/lib/icon_menu_default.png。
queryString¶
CDATA 内为 JSON **数组**字符串。每项:
空:[]。运行时拼到打开 URL 的 query上。勿用对象,要用数组。
superior 与 parentId¶
| 场景 | superior |
parentId |
磁盘(推荐) |
|---|---|---|---|
| PC 顶层 | 空/省略 | 软件 id | …/menu/{name}.menu/ |
| PC 子菜单 | 上级菜单 id | 上级菜单 id | …/menu/{父}.menu/{name}.menu/ |
| 移动顶层分类 | 空/省略 | 软件 id | …/mobilemenu/{name}.mobilemenu/ |
| 移动子菜单 | 上级移动菜单 id | 同上 | 嵌套在上级 .mobilemenu 下 |
设计态创建:有 superior 则 parentId=superior;无则 parentId=applicationId。
设计器上级下拉第一项可能把 id 设成软件 id(表示顶层)——此时 parentId 仍=软件 id,superior 可为空或软件 id;生成文件时顶层请留空 superior,避免与菜单 id 混淆。
校验(设计态):
name空 → 菜单名称不能为空 / page.name.notexist- 同级(相同
parentId+ 相同文件后缀)重名 → 同级菜单名称已存在 / menu.name.exist name与直接上级name相同 → 名称不可以跟上级相同 / 菜单名不能与父级菜单重名- 改名与直属下级重名 → 菜单名不能与直属下级菜单重名
3. 链接类型 linkType 与 actionContent 对应关系(必读)¶
存 两位字符串码(不是枚举名)。空或省略 = 无链接(分类菜单)。
硬规则: linkType 决定运行时用哪个 Service 按 id 加载资源;actionContent 必须是该类型资源的 根元素 id,且与落盘后缀一致。填错 id(例如把 .activity id 填进 linkType=00)会在打开菜单时出现:
ClassCastException: Activity cannot be cast to Form(或 View/其它类型互转失败)。
3.1 对应关系总表(Agent 落盘前逐行核对)¶
| linkType | 含义 | actionContent 必须是 |
取自文件 | XML 根元素 | moduleid |
**禁止**填入 |
|---|---|---|---|---|---|---|
| (空) | 分类文件夹 | 空 | — | — | 不写/空 | 任何 id |
00 |
表单启动 | 表单 id | {名}.form/{名}.form |
<form id="…"> |
必填=表单所在模块 id | .activity / .flow / .view / 菜单 / 模块 / 字段 id |
01 |
视图 | 视图 id | {名}.view/{名}.view |
<ListView id="…"> 等视图根 |
必填 | 表单 id、列 id、视图下 .activity id |
02 |
图表 | 图表 id | 模块下图表资源 | 图表根元素 id | 必填 | 表单/视图 id |
05 |
内部自定义链接 | 相对路径/页 | — | — | 可选 | 资源 UUID(应是路径) |
06 |
外部自定义链接 | 完整 URL(http(s)://…) |
— | — | 可选 | 资源 UUID |
07 |
脚本链接 | iScript 源码(return URL) | — | — | 可选 | 裸资源 id(除非脚本内自己拼) |
09 |
自定义报表 | 报表 id | 模块下报表 | 报表根元素 id | 建议有 | 表单/视图 id |
12 |
润乾报表 | 报表 id | 润乾报表资源 | 同上 | 建议有 | 同上 |
33 |
大屏 | 大屏 id | 应用下 bigscreen/ |
大屏根元素 id | 不需 | 模块内表单/视图 id |
34 |
OLAP | OLAP id | 模块下 OLAP | OLAP 根元素 id | 必填 | 表单/视图 id |
核对口诀:
linkType=00 → actionContent = <form id="…"> 的 id,后缀 .form
linkType=01 → actionContent = 视图元数据文件根 id,后缀 .view
绝不要把 {表单}.form/{操作}.activity 的 <activity id> 写进 actionContent
正确示例(表单菜单,与样例一致):
<linkType>00</linkType>
<moduleid>{模块id}</moduleid>
<!-- 必须等于 xxx.form 根属性 id,不是同目录下 保存.activity 的 id -->
<actionContent><![CDATA[__99ipS3JCFHWp47rmQUG]]></actionContent>
错误示例(会导致 ClassCastException):
<linkType>00</linkType>
<!-- 错误:这是 .activity 的 id,不是 .form 的 id -->
<actionContent><![CDATA[__bGEZ3LuNB75gpEQcMJR]]></actionContent>
3.2 设计器当前可选(与上表一致;Agent 优先用这些)¶
| 码 | 常量/UI | actionContent |
moduleid |
说明 |
|---|---|---|---|---|
| (空) | 分类 | 空 | 不需 | 仅作上级 |
00 |
表单启动 FORM | 表单 id(.form 根 id) |
必填 表单所在模块 | 打开新建/表单 |
01 |
视图 VIEW | 视图 id(.view 根 id) |
必填 | 可配 showtotalrow |
02 |
图表/统计图 CHART | 图表 id | 必填 | UI 文案为 chart |
05 |
自定义链接(内部) MANUAL_INTERNAL | 相对路径/页 | 可选 | 配 directory(默认 portal) |
06 |
自定义链接(外部) MANUAL_EXTERNAL | 完整 URL | 可选 | 如 https://www.baidu.com |
07 |
脚本链接 SCRIPT | iScript 源码 | 可选 | 运行返回 URL 字符串 |
09 |
自定义报表 CUSTOMIZE_REPORT | 报表 id | 建议有 | ureport/jasper 等 |
33 |
大屏 BIGSCREEN | 大屏 id | 不需 | 应用下 bigscreen/ |
34 |
OLAP | OLAP id | 必填 | 模块下 OLAP |
PC 设计器会追加 05/06;移动端选项较少(通常 00/01/02/07/09/33/34,以设计器为准)。
3.3 模型枚举中仍有、设计器较少用的码¶
| 码 | 枚举 | 说明 |
|---|---|---|
03 |
EXCELIMPORT | Excel 导入 |
04 |
ACTION | 平台控制器 |
08 |
邮件 | |
10 |
BBS | 论坛 |
11 |
NetworkDisk | 网盘 |
12 |
RUNQIAN_REPORT | 润乾报表;PC→移动复制时保留类之一 |
@@ |
NONE | 占位 |
旧数据:linkType=09 且无 moduleid 时,详情 API 会从 actionContent 里解析报表 id 并回填 moduleid。
3.4 actionContent 内容形态(按 linkType)¶
| linkType | 内容形态 |
|---|---|
00/01/02/09/12/33/34 |
目标资源 裸 id(非 URL、非路径、非脚本);且 id 必须属于上表对应后缀文件 |
05 |
内部路径;可与 directory=portal 组合,缺省拼 portal/… |
06 |
外部绝对 URL |
07 |
iScript;执行后 return URL 字符串。环境可用 getWebUser() 等 |
3.5 易混淆 id(生成菜单时最常写错)¶
| 资源 | 文件 | 可否作 actionContent(在对应 linkType 下) |
|---|---|---|
| 表单 | …/{名}.form/{名}.form 根 id |
✅ 仅 linkType=00 |
| 视图 | …/{名}.view/{名}.view 根 id |
✅ 仅 linkType=01 |
| 工具栏操作 | …/{名}.form/{操作}.activity 或 …/{名}.view/{操作}.activity 根 id |
❌ **永不**写入菜单 actionContent |
| 流程 | …/{名}.flow/{名}.flow 根 id |
❌ 不进菜单;挂在 Activity onActionFlow |
| 模块 | …/{名}.module/{名}.module 根 id |
❌ 只进菜单的 moduleid |
| 菜单自身 | .menu / .mobilemenu 根 id |
❌ 不作 actionContent;用于 superior/parentId/角色授权 |
| 表单字段 | templatecontext 内控件 id |
❌ |
3.6 脚本 / URL 示例¶
脚本链接示例(CDATA 内):
自定义页常见返回(脚本):
(function(){
var pageId = "自定义页面ID";
var applicationId = getApplication();
var domainId = getWebUser().getDomainid();
var request = getParamsTable().getHttpRequest();
var baseUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
return baseUrl + "/static/portal/vue/pageIndexHtml/index.html#/?pageId=" + pageId
+ "&appId=" + applicationId + "&domainId=" + domainId;
})();
运行时脚本链接也可先落到 /portal/LinkForScript?_resourceid={菜单id} 再服务端执行。
3.7 表单/视图「创建菜单」快捷写入(等价手写)¶
| 来源 | linkType | actionContent | moduleid | queryString | opentarget |
|---|---|---|---|---|---|
| 表单 | 00 |
formId(.form 根 id,非 activity) |
form.parentId(模块 id) | [] |
detail |
| 视图 | 01 |
viewId(.view 根 id,非 activity) |
view.parentId(模块 id) | [] |
detail |
name/linkName/description/superior 由请求指定;showType 参数为 mobile 时设 type=100、isMobile=true。
标签页/查询表单/模板表单:**不可**单独创建菜单。
4. XML 示例¶
4.1 PC 顶层分类(无链接)¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resourceVO id="__menuFolderLeave001">
<name>请假管理</name>
<parentId>__a1b2c3d4e5f6g7h8i9j</parentId>
<applicationid>__a1b2c3d4e5f6g7h8i9j</applicationid>
<description><![CDATA[请假管理]]></description>
<opentarget>detail</opentarget>
<ico>{"icon":"/uploads/lib/icon_menu_default.png","icontype":"img"}</ico>
<linkName>请假管理</linkName>
<linkType></linkType>
<directory>portal</directory>
<actionContent><![CDATA[]]></actionContent>
<queryString><![CDATA[[]]]></queryString>
<permissionType>public</permissionType>
<showType>0</showType>
<status>1</status>
<isUsual>false</isUsual>
<isMobile>false</isMobile>
<orderno>10</orderno>
<showtotalrow>false</showtotalrow>
</resourceVO>
推荐路径:LeaveOA.application/menu/请假管理.menu/请假管理.menu
扁平亦可:LeaveOA.application/请假管理.menu/请假管理.menu
4.2 PC 叶子:打开视图¶
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resourceVO id="__menuViewLeaveList01">
<name>请假列表</name>
<parentId>__menuFolderLeave001</parentId>
<applicationid>__a1b2c3d4e5f6g7h8i9j</applicationid>
<description><![CDATA[请假列表]]></description>
<type>00</type>
<opentarget>detail</opentarget>
<ico>{"icon":"/uploads/lib/icon_menu_default.png","icontype":"img"}</ico>
<superior>__menuFolderLeave001</superior>
<linkName>请假列表</linkName>
<linkType>01</linkType>
<moduleid>__moduleLeave00000001</moduleid>
<directory>portal</directory>
<actionContent><![CDATA[__viewLeaveList000001]]></actionContent>
<queryString><![CDATA[[{"paramKey":"status","paramValue":"1"}]]]></queryString>
<permissionType>public</permissionType>
<showType>0</showType>
<status>1</status>
<isUsual>false</isUsual>
<isMobile>false</isMobile>
<orderno>0</orderno>
<showtotalrow>false</showtotalrow>
</resourceVO>
路径:…/menu/请假管理.menu/请假列表.menu/请假列表.menu
4.3 PC 叶子:打开表单¶
<linkType>00</linkType>
<moduleid>__moduleLeave00000001</moduleid>
<!-- 必须是 LeaveRequest.form 根属性 id,禁止填 保存.activity 的 id -->
<actionContent><![CDATA[__formLeaveApply00001]]></actionContent>
linkType=00 → actionContent=.form 根 id;moduleid=表单所在模块 id。其余同 4.2。
4.4 脚本链接¶
<linkType>07</linkType>
<actionContent><![CDATA[(function () {
return "https://hao.360.com/";
})()]]></actionContent>
4.5 外部 / 内部 URL¶
<linkType>05</linkType>
<directory>portal</directory>
<actionContent><![CDATA[vue/index.html#/open?appId=…&actionContent=…&linkType=01]]></actionContent>
4.6 自定义报表 / 大屏 / OLAP / 图表¶
<linkType>09</linkType>
<moduleid>{模块id}</moduleid>
<actionContent><![CDATA[{报表id}]]></actionContent>
<linkType>34</linkType>
<moduleid>{模块id}</moduleid>
<actionContent><![CDATA[{olapId}]]></actionContent>
<linkType>02</linkType>
<moduleid>{模块id}</moduleid>
<actionContent><![CDATA[{图表id}]]></actionContent>
4.7 移动端:一级分类 + 子叶子¶
移动顶层(分类,isMobile=true,无 linkType):
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resourceVO id="__mMenuFolderLeave001">
<name>请假管理</name>
<parentId>__a1b2c3d4e5f6g7h8i9j</parentId>
<applicationid>__a1b2c3d4e5f6g7h8i9j</applicationid>
<description><![CDATA[请假管理]]></description>
<opentarget>detail</opentarget>
<linkName>请假管理</linkName>
<linkType></linkType>
<actionContent><![CDATA[]]></actionContent>
<queryString><![CDATA[[]]]></queryString>
<permissionType>public</permissionType>
<showType>0</showType>
<status>1</status>
<isUsual>false</isUsual>
<isMobile>true</isMobile>
<orderno>0</orderno>
</resourceVO>
路径推荐:…/mobilemenu/请假管理.mobilemenu/请假管理.mobilemenu
子菜单:isMobile=true,superior/parentId=上一级移动菜单 id,linkType/actionContent/moduleid 同 PC。
硬规则: 移动端默认用一级菜单做分类;不要只建一级叶子菜单(否则移动端可能不显示)。流程:先建移动一级分类 → 从 PC 复制菜单到该分类下(或手写子 .mobilemenu)。
5. PC ↔ 移动复制行为(设计态)¶
POST …/menus/copy?destid=&isMobile=
| 方向 | isMobile 参数 |
结果 |
|---|---|---|
| 复制到 PC | true(历史参数语义:源为移动) |
新菜单 isMobile=false;挂到 dest |
| 复制到移动 | false(源为 PC) |
新菜单 isMobile=true;非 00/01/02/09/12 的 linkType 会被清空 |
复制会 clone 字段、清 id/uri、改 parent/superior。Agent 手写时可直接建 .mobilemenu,不必调 API。
6. 权限与角色¶
菜单自身¶
permissionType=public:有该软件有效角色即可见permissionType=private:还须角色 permissions 含本菜单
角色 permissions 菜单项形状(写入 .role)¶
{
"operationCode": 1002,
"operationId": "{menuId}",
"resId": "{menuId}",
"resName": "菜单",
"resType": 2,
"roleId": "",
"type": 1
}
resType=2 = MENU_TYPE;PC/移动菜单同一形状。细则 → ROLE_SKILL。
仅有菜单授权不够打开 private 表单/视图时,还需对应资源的「打开」等授权(operationCode 1032 等)。
7. 设计态 / 运行态 API(可选;优先直接写文件)¶
Base 设计态:/api/designtime(设计器常加 /designer 前缀)。{appId}=软件 id。
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | /applications/{appId}/menus?isMobile=&parentId= |
子菜单列表 |
| GET | /applications/{appId}/menus/{menuId} |
详情 |
| POST | /applications/{appId}/menus?isMobile= |
新建;body=ResourceVO JSON;服务端分配 id |
| PUT | /applications/{appId}/menus/{menuId} |
更新;按 superior 重算 parentId |
| DELETE | /applications/{appId}/menus |
body=id 数组 |
| POST | /applications/{appId}/menus/copy?destid=&isMobile= |
复制 |
| POST | /applications/{appId}/form/{formId}/menus |
按表单建菜单 |
| POST | /applications/{appId}/view/{viewId}/menus |
按视图建菜单 |
| GET | /applications/{appId}/menu/getAllMenus?showType=pc\|mobile |
上级树下拉 |
| GET | /applications/{appId}/icons |
图标库 |
运行态:GET /api/runtime/{applicationId}/menus?isMobile= 取当前用户可见菜单树(含权限过滤)。
8. 命名、路径、与模块关系¶
| 规则 | 要求 |
|---|---|
| 文件/目录 | PC:{name}.menu;移动:{name}.mobilemenu |
| 同级不重名 | 相同 parentId + 相同后缀 |
| 顶层挂载 | parent=Application → 插入 getFileGroup():/menu 或 /mobilemenu |
| 子菜单挂载 | parent=ResourceVO → **不再**插 group,直接挂在上级 .menu/.mobilemenu 目录下 |
moduleid |
必须是真实模块 id;actionContent 目标须属于该模块(或大屏等应用级资源) |
| 改名 | 同步改目录名、元数据文件名、XML <name>;经 API 改会处理 uri |
跨资源:
| 菜单要打开 | 先有 |
|---|---|
| 表单/视图/报表/图表/OLAP | 对应模块内资源 + 正确 moduleid |
| 大屏 | 应用下 bigscreen |
| 脚本/外链 | actionContent 自洽即可 |
9. 新建清单(Agent 落盘顺序)¶
1. 已有软件 id、(按需)模块与表单/视图 id
2. 确定 PC 或移动;生成 menuId;确定 name、superior
3. 建目录(推荐):
storage/workspace/{软件}.application/menu/{name}.menu/
或 mobilemenu/{name}.mobilemenu/
4. 写同名元数据 resourceVO
5. **落盘前核验:** linkType ↔ actionContent 类型一致(§3.1);`00` 必须是 `.form` 根 id,禁止 `.activity` id
6. 分类先落盘,再落子叶子(子目录嵌套在父目录下)
7. private 菜单 → 改角色 permissions
8. 移动端:先一级分类,再子菜单或从 PC 复制
最小包结构示例¶
LeaveOA.application/
LeaveOA.application
menu/
请假管理.menu/
请假管理.menu # 分类
请假列表.menu/
请假列表.menu # linkType=01 → 视图
新建请假.menu/
新建请假.menu # linkType=00 → 表单
mobilemenu/
请假管理.mobilemenu/
请假管理.mobilemenu
请假列表.mobilemenu/
请假列表.mobilemenu
module/
leave.module/
…
role/
员工.role
10. 校验与常见错误¶
| 问题 | 处理 |
|---|---|
| 菜单名称不能为空 | 写非空 name |
| 同级菜单名称已存在 | 换名或换上级 |
| 名称与上级/下级相同 | 改名 |
| 前台看不到菜单 | 查 status=1、软件 activated、用户角色、permissionType/permissions、是否配错 PC/移动树 |
| 移动端空白 | 是否只有一级叶子;应先有一级分类再挂子菜单 |
| 打开报错/空 | linkType 与 actionContent/moduleid 是否匹配;目标 id 是否存在 |
ClassCastException: Activity cannot be cast to Form |
linkType=00 但 actionContent 填了 .activity id;改为对应 .form 根 id |
ClassCastException: … cannot be cast to Form/View |
actionContent 的 id 类型与 linkType 不符;按 §3.1 总表纠正 |
showtotalrow=true 慢 |
视图总数统计;非必要勿开 |
| 根元素错误 | 必须是 resourceVO(不是 menu) |
| 写成单文件无目录 | 菜单是目录型:{name}.menu/{name}.menu |
| 放到 module 内 | 移到应用级 menu/mobilemenu(或应用根扁平) |
| orderNo 无效 | 字段是 orderno |
| private 仍全员可见 | 查权限缓存;确认 permissions 与 status |
11. 脚本库 scripts/menu_template_builder.py¶
Agent 批量或程序化**生成 PC/移动菜单树时,**优先 import 本库。
路径(相对 workspace 根):
用法¶
import sys
from pathlib import Path
WORKSPACE = Path("<workspace-root>")
sys.path.insert(0, str(WORKSPACE / ".claude" / "skills" / "generate-menu-file" / "scripts"))
import menu_template_builder as mtb
mtb.write_menu_tree(
Path("MyApp.application/menu"),
application_id="__AppId",
root_id="__Menu_root_pc",
root_name="我的应用",
leaves=[
mtb.MenuLeaf("文件台账", module_id="__ModDoc", action="__View_list", order=1),
mtb.MenuLeaf("文件统计", module_id="__ModDoc", action="__Chart_id", order=2, link_type="02"),
],
is_mobile=False,
leaf_id_prefix="__Menu_pc_",
)
# 移动端:menu_root_dir=…/mobilemenu,is_mobile=True
应用内薄脚本只保留:根名、叶子规格(标题/模块/视图或 chart id)、id 前缀;落盘逻辑 import 本库。
API 摘要¶
| 类别 | 函数 / 类型 |
|---|---|
| 叶子规格 | MenuLeaf(title, module_id, action, order, link_type="01", menu_id=None) |
| XML | render_resource_xml(...) |
| 单条落盘 | write_menu_item(path, ...) |
| 整树落盘 | write_menu_tree(menu_root_dir, …, is_mobile=False) |
参考实现:iso_doc.application/_gen_menus_flows.py 的 gen_menus()。
12. 与其它技能边界¶
| 内容 | 本文 | 其它 |
|---|---|---|
| ResourceVO / .menu / .mobilemenu / linkType | ✓ | |
| 软件目录、角色文件位置 | 路径 | APPLICATION_SKILL / ROLE_SKILL |
| 模块 id、表单/视图归属 | moduleid 指向 | MODULE_SKILL / FORM_SKILL / VIEW_SKILL |
| permissions JSON 全表 | 菜单项形状 | ROLE_SKILL |
| 报表/大屏/OLAP 资源本体 | 仅挂接 id | 各资源定义/手册 |
生成业务功能时:先有模块内表单/视图(及角色),再写菜单树指向它们;需要移动端则另建 mobilemenu 树。