Files
2026-09-10 17:29:24 +08:00

24 KiB
Raw Permalink Blame History

UIPrefabCheck 能力清单与优化方案

源文件:Assets/Editor/UIPrefabCheck.cs(1434 行,本目录含只读副本,MD5 74D4A63A33869AE560BA6B522E2DABFC) 菜单入口:Tools/UI/Prefab 和图片优化扫描工具、Tools/UI/界面性能巡检 评估时间:2026-09-10 | 结论:工具本身设计扎实(只读、分页、LSH 相似图、dHash 带 alpha 包围盒),但对"降 DC"这个目标存在 4 个关键能力缺口,其中 2 个改动量很小、收益很大。


第一章 它现在是什么(两个类,7 个标签页)

一个文件里两个独立部分:

类 行号 职责 运行时机
UIRuntimePerformanceAudit L17-478 界面性能巡检(自动逐个 Show/Close 面板 + 手动录制实际操作) 必须 Play Mode
UIPrefabCheck : EditorWindow L480-1433 Prefab / 图片 / 图集静态扫描(6 个页)+ 承载上面那个巡检页 编辑模式即可

1.1 七个标签页逐项能力

标签页 绘制方法 扫什么 判定标准 / 阈值 能回答什么问题
引用明细 DrawReferencePage L662 每个 prefab 里的 Image.sprite / RawImage.texture / SpriteRenderer.sprite 图片不在 gui/ 或 PSD2UI/UI/ 下 → 标"非标准 UI 资源目录"(IsStandardUiPath L1431) 某张图被哪个 prefab 的哪个节点用了、属于哪个图集(可点击定位图集)
多预制体共用 DrawSharedPage L696 按 AssetPath 分组,PrefabPaths.Count >= 2 — 哪些图被多个 prefab 共用(共用度高 = 更该进同一个图集)
疑似无用图片 DrawUnusedImagePage L722 UnusedImageRoots(L487-491:gui + PSD2UI/UI)下的 t:Texture2D 既不在 prefabDependencies(仅来自 prefabRoot 下 prefab 的递归依赖,L895),也没有静态代码引用(HasStaticCodeReference L1148:guid / 全路径 / Resources 相对路径的字面子串) 哪些图可能是老项目遗留的废图
节点统计 DrawNodeStatisticsPage L760 每个 prefab 的 Transform 总数 超过 GetSuggestedNodeCount L942 的硬编码值(UI面板 150 / UI组件 50 / 特效 80 / 角色 100 / 通用 80)→ 标"建议关注"或"建议拆分" 哪些 prefab 层级过深/节点冗余(影响 Rebuild 与合批)
大图标记 DrawLargeImagePage L796 UnusedImageRoots 下的 t:Texture2D 源文件 > 500 KB(L484)或分辨率 ≥ 1024×1024(L485) 哪些图源文件太大
相似图 DrawSimilarImagePage L831 UnusedImageRoots 下的 png/jpg/jpeg(L1282) dHash 64bit(带 alpha 包围盒裁剪 L1311-1320,再采 8×9 亮度网格 L1323-1333)+ 宽高比差 ≤ 0.05(L1363)+ 汉明距离 ≤ 6(L486);用 8 字节分桶 LSH 避免 O(n²)(FindSimilarImagePairs L1346) 哪些图是重复/近似重复(老项目遗留的典型症状)
界面性能 UIRuntimePerformanceAudit.DrawEmbedded L83 从 GameInitWrapper 源码正则提取 AddItem<T>(L176)发现所有注册面板 自动巡检:预热 10 帧 + 采样 60 帧(L19-20),输出 打开耗时 / 平均 / P95 / 最大帧耗时 / GC 峰值 / 关闭后残留对象数;手动录制:轮询可见面板,记录实际停留期间的帧耗时 哪个面板打开慢、哪个面板停留时拖帧、哪个面板关不干净

1.2 值得肯定的设计(不要在优化时破坏)

  1. 全程只读:PrefabUtility.LoadPrefabContents + finally UnloadPrefabContents(L908-916),不改 prefab、不改图集、不写 Undo。
  2. dHash 前先做 alpha 包围盒裁剪(L1311-1320)——对 UI 图至关重要:UI 图周围大量透明,不裁剪的话所有图都"相似",误报会淹没结果。这一步做得比很多商业工具细。
  3. LSH 分桶代替两两比对(L1346-1375):1715 张图两两比对是 147 万对,分桶后只比对同桶候选,扫描时间从分钟级降到秒级。
  4. 分页 100 条/页 + 筛选 + 可点击 Ping(DrawPager L1403、DrawAtlasLink L974):结果多也不会卡 GUI。
  5. CSV 导出带 UTF8 BOM(L1426、L458):Excel 直接打开不乱码。
  6. 性能巡检有"关闭后残留对象"检查(FinishClosing L416-427):能发现"面板关了但子对象还活着",这正是 T5 FullScreenCover 复活问题的同类症状。

第二章 它对"降 DC"能帮什么、不能帮什么

2.1 能帮(已在 T5 验证过的用法)

  • 找图集分散问题:引用明细页每行都显示图集归属,FindAtlases 用 atlas.CanBindTo(sprite)(L968)判断,比读 meta 可靠。一个面板里图的图集名越杂,批次越多。
  • 找重复图:相似图页直接指出"这两张图其实是一张",合并后既降包体又降批次。
  • 找废图:疑似无用图片页(但判定弱,见 §3 B1)。
  • 量化面板性能:界面性能页给出 P95 帧耗时,可作为优化前后对比的基线。

2.2 不能帮(4 个关键缺口)

  1. 测不出 Draw Call / 批次数。PanelResult(L37-50)只有帧耗时和 GC,降 DC 的成果无法用这个工具量化——改完补丁只能靠 Frame Debugger 手工数,做不了"全项目面板 DC 排行"。
  2. 看不出"谁在断批"。工具只扫 Image / RawImage / SpriteRenderer 三种组件(L912-914),完全没扫 Mask / RectMask2D / UIEffect / UIEffectReplica / 嵌套 Canvas / TMP 字体资产——而这些才是断批的真正元凶。

    T5 真实案例:LevelGiftItem.prefab 里 1 个 UIEffect + 6 个 UIEffectReplica(参数全是默认 None、无视觉效果),运行时每条克隆出 7 个独立材质,直接把面板顶到 101 批。这个工具当时没能报出这个问题。

  3. 不做合批预估。已经有"每张图属于哪个图集"的数据,却没有按 prefab 聚合出"这个面板涉及几个图集 / 几张散图 / 预估最少批次",无法批量排出"最该优化的面板 TOP N"。
  4. 不看纹理导入设置。大图页只看源文件大小和分辨率,但真机成本是运行时显存,由 TextureImporter 的压缩格式 / maxSize / mipmap / isReadable 决定。一张 1024×1024 的图:RGBA32 未压缩 = 4 MB 显存,ASTC 6×6 = 约 0.5 MB——工具现在看不出这个差别,而 T5 光 png 就有 302 MB / 1715 张。

第三章 优化清单(按优先级,含代码位置与改法)

A 级:能力补齐(对降 DC / 瘦身目标收益最大)

A1 给性能巡检加 DC / 批次 / SetPass 指标 ★首推

  • 现状:StartRecorder L443-448 只起了一个 ProfilerRecorder(ProfilerCategory.Memory 的 GC Allocated In Frame);采样在 Tick L384-393。
  • 改法:照 gcRecorder 的同一套写法,多起 3 个 Render 类别的 recorder:
    drawCallRecorder  = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Draw Calls Count");
    batchRecorder     = ProfilerRecorder.StartNew(ProfilerCategory.Render, "Batches Count");
    setPassRecorder   = ProfilerRecorder.StartNew(ProfilerCategory.Render, "SetPass Calls Count");
    
    在 Tick 的 Sampling 分支里按现有风格累积(Math.Max(..., recorder.LastValue) 取峰值;再加一个累加/计数得平均值); PanelResult(L37)加 MaxDrawCalls / AvgDrawCalls / MaxBatches / MaxSetPass 四个字段; DrawEmbedded 的结果表头(L140-149)与 ExportCsv 的列(L455-457)同步加列;Finish L440 处一并 Dispose。
  • 备选:UnityEditor.UnityStats.drawCalls / batches / setPassCalls 更简单,但受编辑器自身渲染和 Scene 视图影响更大,建议以 ProfilerRecorder 为主。
  • 诚实提示(务必写进界面 HelpBox):编辑器 Play Mode 测得的 DC 含编辑器渲染开销,只能用于同环境下的前后对比,不能当真机绝对值;真机结论仍需 Profiler 连真机复测。这与 05_排查方法论/DC与帧率排查经验.md §2.1 的立场一致。
  • 收益:降 DC 从此可量化、可排行、可回归。做完这一项,"改了补丁到底降了多少"不再靠肉眼数 Frame Debugger。
  • 工作量:约 30-40 行,半天内含验证。

A2 新增"合批体检"页(静态分析,不需 Play Mode) ★首推

  • 现状:FindAtlases L962 已能给出每张图的图集归属,但数据只逐行展示,没有按 prefab 聚合;ScanPrefab L906 只遍历三种图形组件。
  • 改法:
    1. ScanPrefab 里补扫:Mask / RectMask2D 数量、UIEffect / UIEffectReplica 数量(用 GetComponentsInChildren<Coffee.UIEffects.UIEffectBase>(true) 可一次拿到两者)、嵌套 Canvas 数量(GetComponentsInChildren<Canvas>(true).Length - 1)、TMP 文本用到的字体资产 distinct 数;
    2. 新增 BatchHealthRecord:PrefabPath、涉及图集数、散图数(Atlases == "未加入图集")、Mask 数、UIEffect 数、嵌套 Canvas 数、字体种类数、预估最少批次数;
    3. 预估公式(标注为估算,非实测): 预估批次 ≈ distinct图集数 + 散图数 + distinct字体图集数 + UIEffect配置种类数 + Mask分区数 + 嵌套Canvas数
    4. 新增第 8 个 ViewTab,按"预估批次"降序排列,支持导出 CSV。
  • 收益:一次扫描就能列出"全项目最该优化的面板 TOP N"以及每个面板的具体病因(是图集分散、还是 UIEffect 断批、还是 Mask 太多)。这正是丢失的 UIDCProbeTool 的核心价值,而且静态就能算、可批量、可导表做优化前后对比,比原来那个只能在运行时看单个 Canvas 的版本更实用。
  • 工作量:约 150 行,一天内。
  • 依赖注意:引用 Coffee.UIEffects 命名空间会让这个 Editor 脚本依赖 UIEffect 包。若担心耦合,可改用 GetComponentsInChildren<Component> + GetType().Name 字符串判断("UIEffect" / "UIEffectReplica"),零编译依赖。

A3 新增纹理导入设置审计页(服务"源文件瘦身")

  • 现状:ScanLargeImages L1185 只看 GetAssetFileSize(L1215,源文件字节数)和 image.width/height。
  • 改法:对每张图取 AssetImporter.GetAtPath(path) as TextureImporter,输出并判定:
    检查项 判定 浪费量级
    mipmapEnabled UI 图(textureType == Sprite)开 mipmap = 白扔 约 +33% 显存
    isReadable 非必须读取却开着 = 内存里存两份 ×2 显存
    maxTextureSize 远大于 prefab 里实际显示尺寸 按平方倍
    平台 override 格式 Android/iOS 用 RGBA32 / ARGB32 未压缩 4-8 倍于 ASTC
    npotScale 非 2 的幂图被放大到 2 的幂 最多 4 倍
    sRGBTexture / alphaIsTransparency 设置与用途不符 视觉问题
    同时给出预估运行时显存(宽 × 高 × 每像素字节数,按 format 查表),并与源文件大小并列显示——两者差异常常是数量级的。
  • 收益:T5 有 302 MB png / 1715 张 + 65 MB TGA + 11 MB PSD,瘦身真正的肥肉在导入设置而不是源文件。这一页直接指向"改哪个设置省多少 MB"。
  • 工作量:约 120 行,一天内。

A4 扩大扫描范围(配置化)

  • 现状:DefaultPrefabRoot L482 固定为 gui/panel;UnusedImageRoots L487-491 硬编码两个目录(gui + PSD2UI/UI)。T5 的 Resources_moved 共 739.6 MB,其中 effects 254.5 MB、res3d 202.3 MB、font 93.8 MB、animation 45 MB 全都扫不到。
  • 改法:把两个常量改成可编辑字段 + EditorPrefs 持久化(参考 UnusedResourceAuditTool.cs 的做法),并支持多目录(string[]);prefabRoot 也支持多个。
  • 收益:从"只能体检 UI 目录"变成"能体检全工程"。
  • 工作量:约 40 行。

B 级:判定准确性(降低误报/漏报)

B1 "疑似无用图片"漏判动态引用 ★与另一工具冲突,必须处理

  • 现状:HasStaticCodeReference L1148-1158 只做三种字面子串匹配:guid、资源全路径、/Resources/ 之后的相对路径(去扩展名)。DrawUnusedImagePage 的 HelpBox(L724-726)已诚实声明"配置表、字符串拼接、反射和运行时下载资源无法可靠识别"。
  • 漏判的典型形态(T5 实测存在):
    • 配置表驱动:achievement.json 里 "icon":"achievement_icon_1"——代码和路径里都没有这个名字;
    • 代码拼接:Config_Const.Config_path_ui_task + "btn_go"(T5 有 21 处此类模式);
    • 插值前缀 + 表数字后缀:AtlasUtils.cs 里 $"rarity_{rarity}"、$"img_card{rarity}"、$"phase_{phases}"——资源实际叫 rarity_1,语料里永远只有 rarity_{rarity};
    • Addressables 条目 / Label、场景文件引用、材质与图集 packables 引用。
  • 关键冲突:Assets/Editor/UnusedResourceAuditTool.cs(菜单 Tools/只读资源巡检/未引用资源候选分析)已经实现了完整的 5 层动态识别链(代码字面量 → Resources 拼接 → 语料子串 → 图集/控制器/字体/Spine 配置名 → 插值/拼接前缀+数字后缀),还带 Addressables、场景根、打包可见性分析和三阶段备份流程。两个工具会对同一张图给出矛盾结论(这里说"无用",那里说"疑似动态引用")。
  • 改法(推荐第 2 种):
    1. 最小改动:在 DrawUnusedImagePage 的 HelpBox 里加一句指引——"完整分析请用 Tools/只读资源巡检/未引用资源候选分析",并把该页改名为"疑似无用图片(弱判定,仅供参考)";
    2. 彻底改动:把 UnusedResourceAuditTool 里的语料构建与前缀命中逻辑抽成一个共享静态类(如 DynamicReferenceCorpus),两个工具都调它,结论自然一致。
  • 工作量:方案 1 约 5 行;方案 2 约 1 天(含抽取与回归)。

B2 依赖面太窄导致误报

  • 现状:prefabDependencies L586 只在 Scan L895 里由 prefabRoot 下的 prefab 累积(AssetDatabase.GetDependencies(path, true))。默认 prefabRoot = gui/panel。
  • 后果:被场景文件、其他目录的 prefab(如 gui/item、effects)、材质、SpriteAtlas 的 packables 引用的图,都会被判成"疑似无用"。
  • 改法:根集合补上 t:Scene、全工程 t:Prefab(或至少 gui 全域)、t:Material、t:SpriteAtlas、Addressables 条目——即 UnusedResourceAuditTool.CollectRoots 的那套。或者干脆按 B1 方案 2 合并。

B3 warningsOnly 在 4 个页无效

  • 现状:只有 Matches(L1053,引用明细页)和 RefreshFilteredNodeRecords(L1174-1176,节点统计页)判了 warningsOnly;RefreshFilteredSharedRecords L1100、RefreshFilteredUnusedImages L1160、RefreshFilteredLargeImages L1227、RefreshFilteredSimilarImagePairs L1391 都只判 filter。
  • 后果:勾上"仅显示需确认项"后,这 4 个页列表毫无变化,也没有任何提示,容易被误认为"这些页没有问题项"。
  • 改法:要么给这些页的 record 补 NeedsConfirm 语义并参与过滤,要么在这些页顶部显示一行说明"本页不受'仅显示需确认项'影响"。约 15 行。

C 级:工具自身性能与测量可信度

C1 手动录制每帧全量遍历所有 GameObject ★自测量污染,必须修

  • 现状:TickManualRecording L258 每帧调用 GetVisibleRegisteredPanels L289,后者执行 Resources.FindObjectsOfTypeAll<GameObject>()(L292)——遍历全工程所有 GameObject(含资产、prefab 内的对象,不只是场景对象)。
  • 后果:工具自己在拖慢被测帧。手动录制测出来的 P95 帧耗时里包含了这次全量遍历的开销,面板越多、工程越大,污染越严重。这是"测量工具影响测量结果"的经典陷阱,会让所有手动录制数据偏高。
  • 改法(三选一,按效果排序):
    1. 最优:按作者自己在 L301-306 留的 TODO 做——在 MFrame.PanelMgr 的 Show / completeHandler / Close 里加生命周期事件(OnPanelShowRequested / OnPanelShown / OnPanelClosed),工具订阅事件。既零轮询开销,又能拿到真实的"请求 Show → 可见"耗时,不再是一帧近似值;
    2. 次优:轮询降频——加一个 manualPollIntervalSeconds(如 0.1 s),未到间隔就跳过本轮 GetVisibleRegisteredPanels;帧耗时采样仍每帧进行,不受影响;
    3. 最省事:把 Resources.FindObjectsOfTypeAll<GameObject>() 换成 Object.FindObjectsByType<GameObject>(FindObjectsInactive.Exclude, FindObjectsSortMode.None)(只返回场景对象,且跳过排序),并预先把 Entries 的 prefab 名做成查找表。
  • 工作量:方案 2 约 8 行;方案 3 约 5 行;方案 1 约半天(要动框架代码,需谨慎,属于"改 UI 框架"范畴,动手前应单独确认)。

C2 代码语料是 O(n×m) 全文扫描

  • 现状:ReadAllProjectCode L1136 把 Assets 下所有 .cs 拼成一个大字符串(T5 有 4659 个文件,语料是 MB 级);HasStaticCodeReference L1150-1157 对每张图做 3 次 string.IndexOf 全文搜索。1715 张图 = 5145 次 MB 级全文扫描。
  • 改法:一次性用正则从语料里提取所有字符串字面量和标识符 token,塞进 HashSet<string>;再对每个资源名/路径做 O(1) 查表 + 前缀匹配。UnusedResourceAuditTool.BuildCodeCorpus 已经是这个做法,可直接复用。
  • 收益:扫描时间从"十几秒到几十秒"降到"1-2 秒"。

C3 dHash 对全尺寸图取像素

  • 现状:TryGetDifferenceHash L1310 image.GetPixels32() 取整张解码图的像素,再双重循环遍历每个像素找 alpha 包围盒(L1312-1319)。
  • 后果:一张 4096×4096 的图 = 64 MB 托管内存 + 1677 万次循环。相似图扫描在含大图的目录上会明显卡顿甚至触发 GC 尖峰。
  • 改法:ImageConversion.LoadImage 解码后,先 Graphics.Blit 到一张 128×128 的 RenderTexture 再 ReadPixels 回小 Texture2D,在小图上找包围盒并采样。dHash 最终只取 8×9 = 72 个点(L1323-1333),128² 的精度完全够,而内存从 64 MB 降到 64 KB。
  • 注意:包围盒精度会略降(小图上 alpha≤10 的判定 L1316 会因缩放插值而边缘模糊)。稳妥做法是双阶段:先在 128² 上算粗略包围盒,再按该包围盒比例回到原图取那 72 个采样点(只取 72 个像素,不再全图遍历)。
  • 工作量:约 30 行。

C4 逐 prefab 递归依赖

  • 现状:Scan L890-896 对每个 prefab 调 AssetDatabase.GetDependencies(path, true)(递归全依赖)。566 个 prefab = 566 次递归依赖解析。
  • 改法:先收集全部 prefab 路径,一次 AssetDatabase.GetDependencies(string[] pathNames, bool recursive) 批量调用(Unity 支持数组重载);或改为 GetDependencies(path, false) 只取直接依赖 + 自行按 prefabDependencies 传递闭包。
  • 收益:扫描时间下降(幅度取决于依赖深度)。

D 级:UI / 一致性小问题

编号 现状(代码位置) 问题 改法
D1 ExportCsv L1419-1428 导出按钮(L624)在任何标签页都可见,但永远只导引用明细(records)。站在"大图标记"页点导出,拿到的却是引用明细表 按 activeTab 分派导出内容,或把导出按钮移进各页;至少在非引用明细页禁用并提示
D2 GetSuggestedNodeCount L942-952 节点数建议值全部硬编码(150/50/80/100/80),不同项目的 UI 复杂度差异很大 提到常量区并允许界面覆盖 + EditorPrefs 持久化;或按本工程实际分布给分位数建议(如 P90)
D3 两个 MenuItem L599 / L607 两个菜单打开的是同一个 UIPrefabCheck 窗口,只是 activeTab 不同。从"界面性能巡检"进去后点"扫描",会切到静态页,容易迷失 用 EditorPrefs 记住上次所在页;或在标题上反映当前页
D4 DrawUnusedImagePage L732 "扫描疑似无用图片"按钮每次都重跑 ReadAllProjectCode(见 C2),连点两次就是两次全量读盘 语料缓存 + 失效判断(按 .cs 文件最后修改时间)
D5 Record.Warning L959 唯一的告警是"非标准 UI 资源目录",判据是路径前缀(IsStandardUiPath L1431)——这是目录约定告警,不是性能告警 配合 A2 增加真正的合批告警(散图、跨图集、UIEffect 断批)

第四章 如果只做三件事

按"收益 / 工作量"比排序:

优先级 项目 收益 工作量
1 A1 性能巡检加 DC / 批次 / SetPass 指标 降 DC 成果从"肉眼数 Frame Debugger"变成"可量化、可排行、可回归" 30-40 行
2 A2 新增静态"合批体检"页 一次扫描列出全项目最该优化的面板 TOP N + 具体病因(图集分散 / UIEffect / Mask / 嵌套 Canvas) 约 150 行
3 C1 修手动录制的每帧全量遍历(方案 2 或 3) 消除工具对被测帧的污染,让手动录制数据可信 5-8 行

其次:B1(避免与 UnusedResourceAuditTool 结论打架)、A3(导入设置审计,服务包体瘦身)、C2/C3(扫描提速)。


第五章 跨项目可移植性(归档前必须知道)

这个工具不是通用工具,有框架绑定:

部分 依赖 能否直接搬到别的项目
6 个静态扫描页(引用明细/共用/无用图/节点/大图/相似图) 只用 UnityEditor + UnityEngine.UI + UnityEngine.U2D;唯一框架引用是文件头 using MFrame;(L10) 可以,删掉 using MFrame; 后基本即用(需按项目改 DefaultPrefabRoot L482 与 UnusedImageRoots L487)
界面性能页(自动巡检) PanelMgr.Instance.Show/Close(L366、L410)+ 从 GameInitWrapper 源码正则解析 AddItem<T>(L172-176) 不能,必须改成目标项目的面板管理器 API 与注册方式
界面性能页(手动录制) ManualPanelNameLookup 由 DiscoverPanels 建立,同样依赖上面两项 不能,同上

移植时的最小改动清单:

  1. 删 using MFrame;(L10),确认剩余部分能编译;
  2. 改 DefaultPrefabRoot(L482)与 UnusedImageRoots(L487-491)为目标项目的 UI 目录;
  3. IsStandardUiPath(L1431)里的两个路径前缀同步改;
  4. 界面性能页:替换 DiscoverPanels(L167)的面板发现方式与 PanelMgr.Instance.Show/Close 调用(L366 / L410 / L432),或在不需要时整块删掉 UIRuntimePerformanceAudit(L17-478)与 ViewTab.界面性能(L567)及其分派(L644-647)。

第六章 作者留下的 TODO(原文摘录,L301-306)

工具里已有一段明确的改进计划,做 C1 方案 1 时直接照它执行:

// TODO(精确手动录制耗时):在 MFrame.PanelMgr 中增加 UI 生命周期事件。
// 1. 在 Show(name, data, completeHandler) 入口记录 startTime,并触发 OnPanelShowRequested(name, startTime)。
// 2. 在资源加载完成且 PanelBase._OnShow 执行完成后的 completeHandler 中,触发 OnPanelShown(name, startTime, elapsedMs)。
// 3. 在 Close(name) 成功后触发 OnPanelClosed(name)。
// 4. 本工具订阅上述事件,以 name 关联会话,便可得到真实的"请求 Show 到可见"耗时,
//    不再依赖当前轮询 GameObject 激活状态所得的一帧近似值。

注意:这一项要动 MFrame 框架代码(PanelMgr / PanelBase)。T5 有过教训——改 UI 框架前必须确认现有全屏遮挡机制(FullScreenCover)不被破坏,且改动要能被业务面板无感继承。动手前单独确认,不要顺手做。