ArkUI 视觉资源管理ShowcaseCard、CardImages 与样式页图片映射HarmonyOS 应用做卡片类界面时视觉资源很容易失控有些页面用纯色块有些页面用本地图片有些页面直接写资源名最后新增模板或样式时到处改。这个项目把视觉资源拆成两层组件层ShowcaseCard负责渲染资源层CardImages.ets负责 key 到 media 的映射。为什么不用页面直接引用图片页面直接写Image($r(app.media.card_template_birthday))短期最快但问题很快出现同一模板在首页、分类、详情页都要重复引用。新增模板要改多个页面。有些场景需要按分类 fallback。样式页顶部 banner 和网格 tile 比例不同。项目改成让视图模型携带imageKeyexport interface ShowcaseCardModel { id: string; title: string; subtitle: string; tone: ToneName; imageKey?: string; }页面只传模型组件自己解析图片。CardImages集中管理资源映射CardImages.ets里定义 keyexport class CardImageKeys { static readonly heroDark: string hero-dark; static readonly marketLight: string market-light; static readonly categoryCountdown: string category-countdown; static readonly templateBirthday: string template-birthday; static readonly styleNightBanner: string style-night-banner; static readonly styleNightTile: string style-night-tile; }再通过cardImageResource()转成真正的 media 资源export function cardImageResource(imageKey?: string): Resource { switch (imageKey) { case CardImageKeys.categoryCountdown: return $r(app.media.card_category_countdown); case template-birthday: return $r(app.media.card_template_birthday); case CardImageKeys.styleNightBanner: return $r(app.media.card_style_night_banner); default: return $r(app.media.market_banner_light); } }页面和服务层都不直接依赖$r(app.media.xxx)。模板图片templateId 到 imageKey模板目录只保存templateId和categoryId。图片 key 由 helper 生成export function imageKeyForTemplate(templateId: string, categoryId: CardCategoryId): string { switch (templateId) { case birthday: return template-birthday; case exam-countdown: return template-exam-countdown; default: return imageKeyForCategory(categoryId); } }如果模板图片缺失就回退到分类图。这个 fallback 能兜底但新增模板时仍应该补齐专属图。样式页同一个 styleId 要区分 banner 和 tile样式页比较特殊顶部预览卡需要横向 banner下方样式库需要 tile。项目用preview参数区分export function imageKeyForStyle(styleId: string, preview: boolean false): string { switch (styleId) { case style-night: return preview ? CardImageKeys.styleNightBanner : CardImageKeys.styleNightTile; case style-rose: return preview ? CardImageKeys.styleRoseBanner : CardImageKeys.styleRoseTile; default: return preview ? CardImageKeys.styleRoseBanner : CardImageKeys.styleRoseTile; } }这个设计避免“顶部换了图下方还是旧图”的视觉割裂。ShowcaseCard一套组件支持多种展示ShowcaseCard.ets支持普通卡、hero 卡、banner 图片卡和自定义图片高度Component export struct ShowcaseCard { Prop item: ShowcaseCardModel; compactBadge?: boolean; hero?: boolean; bannerImage?: boolean; imageHeight?: number; onCardClick?: (id: string) void; }图片是否展示由 helper 决定private showImage(): boolean { return this.hero true || this.bannerImage true || this.imageHeight ! undefined; }图片高度也根据模式变化private mediaHeight(): number { if (this.hero true) { return 86; } if (this.bannerImage true) { return 128; } return this.imageHeight ? this.imageHeight : 0; }这样详情页、样式页、分类页不用各写一套卡片组件。紧凑徽标布局解决窄卡片文本被挤压首页和分类页有两列窄卡片。如果沿用左右对称 badge 占位标题容易被挤压。项目给ShowcaseCard增加compactBadgeprivate useCompactBadgeLayout(): boolean { return this.compactBadge true this.item.badge ? true : false; }页面使用时ShowcaseCard({ item: item, compactBadge: true, onCardClick: () { this.openShowcaseCard(item); } })窄卡片优先保证标题和副标题可读badge 收到右上角。资源更新时的验证重点视觉资源变化不一定影响编译但很容易影响运行效果。项目里形成了几个检查点新增模板时检查TEMPLATE_CATALOG和CardImages.ets是否同步。样式页同一styleId同时检查 preview/banner 和 tile。图片中的主要文字不要烧录在边缘避免ImageFit.Cover裁切。小卡片使用imageHeight或固定比例避免布局跳动。分类图和模板图都要在真机或模拟器截图中检查。常见坑只补 media 文件不补cardImageResource()映射。只给样式页顶部预览换图忘了下方 tile。模板 ID 改了但图片 key 仍然旧值。详情页使用模板图时没有 fallback 到分类图。图片高度靠内容撑开导致不同卡片高度不一致。基础链路小结这个项目的视觉资源管理思路是页面传imageKey组件统一渲染资源层集中映射。模板图片、分类图片、样式图片都走同一套 key 体系。对 ArkUI 卡片类应用来说这种方式能让资源替换、模板扩展和页面复用都更可控。尤其是样式页这类同时存在 banner 和 tile 的场景一定要把同一个业务 ID 的多套图片映射维护清楚。图片资源章节要讲“资源 key、业务 id、兜底图”三件事图片章节如果只讲“把 PNG 放进 media 目录”就达不到第四章标准。Project028 的关键是CardImages.ets把业务 id、样式 id、图片 key 和资源对象隔离开了。页面拿到的通常是imageKey最终通过cardImageResource()解析成$r(app.media.xxx)。这种间接层可以让页面不关心资源文件名也能处理缺图兜底。主题、样式、详情头图、市场头图都走同一套资源入口。比如桌面 Form 里保存的是themeImageKey详情页里根据卡片类型解析头图市场页摘要卡没有图片时回退marketLight。这些都说明图片资源不能散落在各页面否则后续替换图片或生成新封面会非常难查。Image(cardImageResource(this.themeImageKey)) .width(100%) .height(100%) Image(cardImageResource(this.heroImageKey())) .width(100%) .height(100%)真实项目里还要考虑审核截图。Project028 后续为了 AGC 宣传图、应用图标、桌面卡片预览生成了多批图片资产。这里需要把边界讲清楚CSDN 展示图、AGC 宣传图、应用内资源图不要混用。CSDN 图偏讲解AGC 图偏审核和商店展示应用内图要控制尺寸、命名和资源引用稳定性。落地检查清单是否说明图片资源不要从页面硬编码$r()。是否解释imageKey和业务 id 的区别。是否覆盖缺图兜底避免空白区域。是否提示 AGC 宣传图和应用内资源图的用途不同。是否覆盖真实路径CardImages.ets、ThemeStorePage.ets、CardStylePage.ets、DesktopCardForm.ets。