Skip to content

子应用菜单翻译与图标方案

状态:已实现(代码完成,待部署) 日期:2026-07-03 目标:子应用新增/修改菜单时,翻译与图标随子应用自己的发布走,主应用零改动。

现状与耦合点

菜单数据本身已经是动态的:

子应用 vite.config UploadPlugin(menus) ──发版──▶ nuwa ──▶ OSS hiapi-cloud/{project}/{version}/menus.json
平台安装/升级应用 ──▶ cloud-public AppStoreLogic.reloadMenus 拉取入库(hiapi_core_system_menu)
主应用 LayoutAside ──▶ GET /cloud-api/{accountType}/system-basic/menu/tree 渲染

但两样东西耦合在主应用:

  1. 翻译:menuLabelte(langCode) ? t(langCode) : label,langCode 必须存在于主应用 src/i18n/*/*.json(vem 的菜单翻译现在就写在主应用 i18n/*/menu.json 里)
  2. 图标:<component :is="menu.icon"/> 依赖主应用全局注册的组件名,自定义 SVG(VemAlarm、VemRestock 等)必须放主应用 assets/svg/

设计原则

  • 一个子项目一个语言包:翻译集中在子应用自己的 src/i18n/<lang>/*.json(与主应用同构),菜单翻译和页面翻译同一套,新增语言 = 加语言文件夹,全线动作一致
  • 构建期强校验防疏忽:发布时校验「每个菜单 langCode 在每个语言下都有翻译」,缺失直接发布失败
  • 图标随子应用仓库管理:svg 文件放子应用 src/assets/svg/,发布时自动收集上报
  • 不动数据库:i18n/icons 走 OSS 旁路(与 menus.json 同级),菜单表结构零改动

数据流(新增部分)

UploadPlugin 发版时:
  menus[].langCode ──▶ 从 src/i18n/<lang>/ 抽取 ──校验──▶ form: i18n = {lang: {key: text}}
  menus[].icon     ──▶ 若 src/assets/svg/{icon}.svg 存在 ──▶ form: icons = {name: "<svg .../>"}
  micro 选项        ──▶ form: micro = {name, activeRule}

nuwa UploadAdminRelease 上传 OSS(版本目录 + latest 双份):
  hiapi-cloud/{project}/{version}/menus.json | i18n.json | icons.json | {jsFile} | {cssFile}
  hiapi-cloud/{project}/{version}/admin.json   ← 汇总清单(见下)
  hiapi-cloud/{project}/...                    ← latest 副本(向后兼容)
  版本目录对象设 Cache-Control: public, max-age=31536000, immutable
  latest 副本设 Cache-Control: no-cache

admin.json 结构:
  {
    "name": "HiapiCloudVemAdmin",         // qiankun 应用名
    "activeRule": "/micro-apps/vem",      // qiankun 激活规则
    "version": "0.0.3",
    "scripts": ["https://oss/.../0.0.3/hiapi-cloud-vem-admin.umd.js"],
    "styles":  ["https://oss/.../0.0.3/vem-admin.css"],
    "i18n":    "https://oss/.../0.0.3/i18n.json",
    "icons":   "https://oss/.../0.0.3/icons.json",
    "menus":   "https://oss/.../0.0.3/menus.json"
  }

cloud-public 平台安装/升级(AppStoreLogic.loadApp):
  拉取 {adminVersion}/admin.json ──▶ 存 CloudApplication.adminEntry(新增 TEXT 列)

cloud-public 新增公开接口(无需登录):
  GET /cloud-api/public/micro-apps
  → [{appId, project, version, name, activeRule, scripts, styles, i18n, icons}]

主应用启动时:
  fetch /cloud-api/public/micro-apps
    ├─▶ registerMicroApps(动态清单 ∪ 静态兜底,动态优先) + start()
    ├─▶ 各 app fetch i18n.json → i18n.global.mergeLocaleMessage(lang, pack)
    └─▶ 各 app fetch icons.json → 注入 iconRegistry(响应式 Map)

主应用渲染改动

  • menuLabel 不变:mergeLocaleMessage 后 te(langCode) 直接命中;pack 拉取失败时兜底显示 label
  • 新增 MenuIcon.vue,三形态渲染,替换 LayoutAside 中所有 <component :is="menu.icon"/>:
    1. iconRegistry 命中(子应用上报的 SVG)→ 内联渲染(继承 currentColor)
    2. http(s) 开头 → <img>
    3. 其它 → <component :is>(Element Plus 图标 / 主应用自有 SVG 组件,现状不变)

子应用接入约定(以 vem-admin 为例)

ts
UploadPlugin({
  ...,
  micro: {name: 'HiapiCloudVemAdmin', activeRule: '/micro-apps/vem'},
  // i18nDir 默认 'src/i18n',iconDir 默认 'src/assets/svg',可省略
  menus: [...]
})
  • 菜单 langCode 的翻译写进子应用 src/i18n/<lang>/menu.json(缺失 → 发布报错)
  • 菜单用到的自定义图标 svg 放子应用 src/assets/svg/{icon}.svg;Element Plus 图标名无需任何文件
  • langCode 命名保持 hiapi_cloud_{project}_* 前缀约定,避免 merge 进主应用时撞 key

迁移与兼容

  • vem 菜单翻译从主应用 i18n/*/menu.json 复制到 vem-admin src/i18n/*/menu.json;vem 专属图标 svg 复制到 vem-admin。主应用中的旧副本暂保留(兜底),待 vem 发版 + 平台升级验证后删除
  • 未上报 i18n/icons 的旧应用(如 design):接口里无对应字段,主应用继续走静态兜底注册 + 现有渲染路径,行为不变
  • adminEntry 为空的应用不出现在 /public/micro-apps,主应用静态兜底列表覆盖(design、未重发版的 vem)

改动清单

位置改动
hiapi-cloud-components-uploadoptions 增 i18nDir/iconDir/micro;构建后抽取+校验+上报
hiapi-cloud-nuwaAdminRelease 接收 i18n/icons/micro;上传版本化+latest 双份;生成 admin.json;uploader 支持 Cache-Control
hiapi-cloud-publicCloudApplication 增 adminEntry 列;loadApp 拉 admin.json;新增 GET /public/micro-apps
hiapi-cloud-admin-ts动态注册微应用;merge 语言包;iconRegistry + MenuIcon.vue;LayoutAside 替换图标渲染
vem-adminUploadPlugin 配置 micro;i18n 增 menu.json;拷贝菜单 svg;版本 0.0.3

已完成的代码改动(2026-07-03)

  • hiapi-cloud-components-upload → 0.2.0:新增 micro/i18nDir/iconDir 选项;extractMenuI18n(缺翻译抛错)、collectMenuIcons(内联 svg);发版时 form 追加 i18n/icons/micro。已验证:vem 20 条菜单 langCode 在 zh-cn/en 全部命中,内联图标 = VemAlarm/VemRestock/VemMap/VemReport。
  • hiapi-cloud-nuwa:AdminReleaseInput 增 I18n/Icons/Micro;UploadAdminRelease 改为版本目录(强缓存)+ latest(no-cache)双写,生成 admin.json;ObjectUploaderWithCacheControlgo build ./... 通过。
  • hiapi-cloud-public:CloudApplicationadminEntry(text 列);AppStoreLogic.reloadAdminEntry 安装/升级时拉 admin.json 存入;新增 GET /public/micro-apps。四模块 mvn -o compile 通过。
  • hiapi-cloud-admin-ts:plugins 导出 i18nInstance;新增 utils/icon-registry.tscomponents/MenuIcon.vueutils/micro-apps.ts(动态注册+merge i18n+load icons);main.tswindow.Vue/window.ElementPlus 并改用 bootstrapMicroApps;LayoutAside 图标改 MenuIconvue-tsc 通过。
  • vem-admin:vite.config 加 micro + external;src/main.ts 删 element-plus CSS import;index.html 加 dev-only CDN;新增 i18n/{zh-cn,en}/menu.json;拷入 4 个 Vem* svg;依赖临时指向本地 0.2.0 插件。vue-tsc -b 通过。

翻译/图标怎样才会生效(关键)

翻译要从 vem 仓库「走到」主应用页面,链路是 vem 构建抽取 → nuwa 写 OSS → 主应用 fetch 合并,缺一不可。主应用侧改用 OSS 根目录 latest 地址(hiapi-cloud/hiapi-cloud-vem/i18n.jsonicons.json)作为静态兜底,所以不依赖 cloud-public 的 /public/micro-apps 与数据库也能加载 vem 的翻译/图标。

让 vem 菜单翻译生效的最小步骤(三件,缺一不可):

  1. 部署 nuwa(新版):否则它收不到 UploadPlugin 传的 i18n/icons 字段,OSS 上不会有 i18n.json
  2. 重新构建发布 vem-admin(pnpm build,disable:false):把 i18n.json/icons.json 上传到 OSS。
  3. 重新部署主应用:bootstrapMicroApps 会 fetch OSS 的 i18n.json 并 mergeLocaleMessage,i18nVersion 触发侧边栏重渲染。

⚠️ 主应用 i18n/*/menu.json 里的 vem 菜单翻译副本已被移除。在上面三步完成前,vem 菜单会显示中文原始 label(中英文都是),这是预期现象,不是 bug。若想在部署前保持旧翻译可用,可临时把 vem 的 langCode 恢复到主应用 menu.json。

其它部署前置

  1. 发布 UploadPlugin 0.2.0:当前 vem-admin 依赖临时改为 file: 本地路径以便验证。正式上线应 npm publish 发布 0.2.0,并把各子应用依赖改回版本范围 ^0.2.0(store-web、public-web 等用到本插件的项目同步)。
  2. cloud-public 数据库迁移(仅"动态注册新子应用"需要,翻译/图标不依赖它):hiapi_core_application 表新增 admin_entry 列(TEXT)。若 Hibernate ddl-auto 非 update/create,手动 ALTER TABLE hiapi_core_application ADD COLUMN admin_entry TEXT;。装机/升级触发 reloadAdminEntry 写入后,/public/micro-apps 才会输出该应用。
  3. OSS CORS:主应用浏览器 fetch() OSS 的 i18n.json/icons.json 需要 CORS。qiankun 现在就是 fetch 方式加载 umd.js 的(已能工作),说明 OSS 对管理后台域名的 CORS 已开,故无需额外配置。
  4. 收尾:全部上线验证后,已可从主应用 assets/svg 删除仅 vem 使用的 Vem* 图标(翻译副本已删)。