跳转至

菜单(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+后缀,目录内再放同名元数据文件)。**不是**角色那种单文件。

新建最低配置:

  1. 建目录 {name}.menu/(PC)或 {name}.mobilemenu/(移动),放在推荐的 menu/ / mobilemenu/ 下,或与样例一致放在应用根
  2. 写同名元数据:idnameparentIdapplicationidpermissionTypestatus=1isMobileorderno
  3. 分类/文件夹菜单:linkType 空、actionContent
  4. 叶子功能菜单:设 linkType + actionContent(目标 id 或 URL/脚本);表单/视图/报表/OLAP 等再写 moduleid
  5. 子菜单:superior=parentId=上级菜单 id;落盘在上级菜单目录下
  6. (按需)private 时在角色 .rolepermissions 中授权本菜单 id(见 ROLE_SKILL)

约定:

  • JAXB;根元素 resourceVOid根属性
  • 脚本/长文本/queryString/actionContent/description<![CDATA[...]]>
  • 目录名 = 元数据文件名 = name + 后缀.menu.mobilemenu)三者一致
  • parentId:顶层=软件 id;子菜单=上级菜单 id
  • superior:顶层省略或空;子菜单=上级菜单 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→落盘 .mobilemenufalse.menu
orderno 子元素 int 0 同级排序;越小越前;比较用
showtotalrow 子元素 string false 仅视图链接有意义;是否显示记录总数(影响性能)
report / reportAppliction / reportModule 子元素 string 历史报表字段;新配置用 linkType+actionContent+moduleid
mobileIco 子元素 string 旧移动图标码;现多用 ico
widgetGroupId 子元素 string 关联 widget 分组;少用
uri / path / children 推导/运行期 不进 XML

不落盘:childrentotalRownewIcoallowOpenFromallowOpenView(运行/UI 用)。

id 生成

统一规则:__ + 短 UUID。示例:__MpEzTToulqZtNEFisw6。须全局/库内不冲突。

permissionType

含义
public 对该软件有有效角色即可见(不强制查 permissions 菜单项)
private 须在角色 permissions 中显式授权本菜单(resType=2operationCode=1002operationId=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":"/uploads/lib/icon_menu_default.png","icontype":"img"}
{"icon":"fa fa-cube","icontype":"font","iconFontColor":"rgba(228, 89, 117, 1)"}
说明
icon 图片路径或字体类名
icontype img / font
iconFontColor 仅 font;CSS 颜色

默认图常见:/uploads/lib/icon_menu_default.png

queryString

CDATA 内为 JSON **数组**字符串。每项:

[{"paramKey":"test","paramValue":"11111"}]

空:[]。运行时拼到打开 URL 的 query上。勿用对象,要用数组。

superiorparentId

场景 superior parentId 磁盘(推荐)
PC 顶层 空/省略 软件 id …/menu/{name}.menu/
PC 子菜单 上级菜单 id 上级菜单 id …/menu/{父}.menu/{name}.menu/
移动顶层分类 空/省略 软件 id …/mobilemenu/{name}.mobilemenu/
移动子菜单 上级移动菜单 id 同上 嵌套在上级 .mobilemenu

设计态创建:有 superiorparentId=superior;无则 parentId=applicationId
设计器上级下拉第一项可能把 id 设成软件 id(表示顶层)——此时 parentId 仍=软件 id,superior 可为空或软件 id;生成文件时顶层请留空 superior,避免与菜单 id 混淆。

校验(设计态):

  • name 空 → 菜单名称不能为空 / page.name.notexist
  • 同级(相同 parentId + 相同文件后缀)重名 → 同级菜单名称已存在 / menu.name.exist
  • name 与直接上级 name 相同 → 名称不可以跟上级相同 / 菜单名不能与父级菜单重名
  • 改名与直属下级重名 → 菜单名不能与直属下级菜单重名

3. 链接类型 linkTypeactionContent 对应关系(必读)

两位字符串码(不是枚举名)。空或省略 = 无链接(分类菜单)。

硬规则: 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 EMAIL 邮件
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/{名}.formid ✅ 仅 linkType=00
视图 …/{名}.view/{名}.viewid ✅ 仅 linkType=01
工具栏操作 …/{名}.form/{操作}.activity…/{名}.view/{操作}.activityid ❌ **永不**写入菜单 actionContent
流程 …/{名}.flow/{名}.flowid ❌ 不进菜单;挂在 Activity onActionFlow
模块 …/{名}.module/{名}.moduleid ❌ 只进菜单的 moduleid
菜单自身 .menu / .mobilemenuid ❌ 不作 actionContent;用于 superior/parentId/角色授权
表单字段 templatecontext 内控件 id

3.6 脚本 / URL 示例

脚本链接示例(CDATA 内):

(function () {
    var url = "https://hao.360.com/";
    return url;
})()

自定义页常见返回(脚本):

(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=100isMobile=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=00actionContent=.form 根 idmoduleid=表单所在模块 id。其余同 4.2。

4.4 脚本链接

  <linkType>07</linkType>
  <actionContent><![CDATA[(function () {
    return "https://hao.360.com/";
})()]]></actionContent>

4.5 外部 / 内部 URL

  <linkType>06</linkType>
  <actionContent><![CDATA[https://www.baidu.com]]></actionContent>
  <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>33</linkType>
  <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=truesuperior/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/移动树
移动端空白 是否只有一级叶子;应先有一级分类再挂子菜单
打开报错/空 linkTypeactionContent/moduleid 是否匹配;目标 id 是否存在
ClassCastException: Activity cannot be cast to Form linkType=00actionContent 填了 .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 根):

.claude/skills/generate-menu-file/scripts/menu_template_builder.py

用法

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.pygen_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 树。