Skip to content

fix(workbench): 剪辑台白屏(分镜视频列表 icon 传 emoji 导致 <component :is> 渲染崩溃) - #33

Open
37389744 wants to merge 1 commit into
HBAI-Ltd:masterfrom
37389744:fix-workbench-white-screen
Open

fix(workbench): 剪辑台白屏(分镜视频列表 icon 传 emoji 导致 <component :is> 渲染崩溃)#33
37389744 wants to merge 1 commit into
HBAI-Ltd:masterfrom
37389744:fix-workbench-white-screen

Conversation

@37389744

@37389744 37389744 commented Aug 17, 2026

Copy link
Copy Markdown

概述

项目生成了分镜视频后,打开「视频剪辑」(剪辑台)页面出现白屏;未生成分镜视频时页面正常。

环境

  • 仓库:HBAI-Ltd/Toonflow-web
  • Vue:3.5.12
  • 运行环境:Electron(Chromium 130)

复现步骤

  1. 新建项目,完成分镜拆分,并生成至少 1 个分镜视频
  2. 进入「生产流程图 → 工作台 → 视频剪辑」。
  3. 页面白屏。

(对照组:不生成任何分镜视频,直接打开「视频剪辑」,页面正常。)

根因

mediaLibrary.vue 使用 Vue 动态组件渲染素材图标:

<!-- src/views/production/components/workbench/editVideo/mediaLibrary.vue -->
<t-image v-if="item.thumbnail" :src="item.thumbnail" fit="cover" />
<component v-else :is="item.icon" theme="outline" size="18" />

:is="item.icon" 期望传入的是 IconPark 图标组件名。同文件中的转场/特效/滤镜列表都是合法组件名:

// src/views/production/components/workbench/editVideo/utils/mediaData.ts
{ id: "trans-1", type: "transition", ..., icon: "i-round" },      // ✅ 合法组件名
{ id: "fadeIn",  type: "effect",     ..., icon: "i-sun-one" },    // ✅ 合法组件名

但在 workbench/index.vueeditFootage() 里,给三个素材列表写入的是 emoji 字符串,而非组件名:

// src/views/production/components/workbench/index.vue
initialVideoItems.value = data.video.flatMap((item, index) => {
  return item.video.map((subItem, subIndex) => ({
    id: `video-${subItem.id}`,
    type: "video",
    name: "#" + ...,
    duration: subItem.duration || 0,
    icon: "🎬",            // ❌ emoji,非法组件名
    color: "...",
    url: subItem.filePath,
    selected: item.videoId == subItem.id,
  }));
});

mockMediaItems.value = videoList.map((item) => ({ ..., icon: "🎥", ... }));   // ❌ 同样问题
mockImageItems.value = imageList.map((item) => ({ ..., icon: "🖼️", ... }));   // ❌ 同样问题

崩溃机制(已实测确认)

Vue 3 的 resolveDynamicComponent("🎬") 找不到名为 🎬 的组件,于是把它当作原生 HTML 标签处理,调用 document.createElement("🎬")。实测该调用直接抛异常:

InvalidCharacterError: Failed to execute 'createElement' on 'Document':
The tag name provided ('🎬') is not a valid name.

该异常发生在 Vue 渲染过程中,无法被 try-catch 捕获,导致整个剪辑台组件树渲染失败 → 白屏。

精确触发条件(三个条件同时满足)

条件 说明
activeTab 是某个素材 tab 分镜视频 video默认激活)/ 媒体 media / 图片 image
② 该 tab 列表有数据 getMaterialData 返回的 data.video 非空(分镜视频);或媒体素材、图片素材有数据
item.thumbnail 为假值 缩略图是异步加载的(extractVideoThumbnails 内部 fetch + WebAV 解码),首次渲染时必然为空 → 走 v-else 分支

由于条件 ③ 在首次渲染时必然满足,所以只要「分镜视频」tab(默认激活)有数据,打开剪辑台就必然白屏,不存在「有视频但侥幸不白屏」的边界情况。

🎥(媒体素材)、🖼️(图片素材)是同款 bug,切到对应 tab 且有数据时同样触发。

为什么「以前有视频也不白屏」

icon: "🎬"<component :is="item.icon"> 这两处是同一次提交引入的:

0ab111c  2026-03-26  添加剪辑台分镜视频,修改添加项目参数不全提示

该提交之前不存在这个 emoji icon(也没有这个渲染方式),所以更早的版本有视频也不会白屏。此 bug 自 2026-03-26 起一直潜伏。

修复方法(两个方案,任选其一)

方案 A:直接删除 emoji icon(当前 PR 已采用)

   initialVideoItems.value = data.video.flatMap((item, index) => {
     if (Array.isArray(item.video)) {
       return item.video.map((subItem, subIndex) => ({
         id: `video-${subItem.id}`,
         type: "video",
         name: "#" + ...,
         duration: subItem.duration || 0,
-        icon: "🎬",
         color: "...",
         url: subItem.filePath,
         selected: item.videoId == subItem.id,
       }));
     }
   });
-  mockMediaItems.value = videoList.map((item) => ({ ..., icon: "🎥", ... }));
-  mockImageItems.value = imageList.map((item) => ({ ..., icon: "🖼️", ... }));

去掉后 item.iconundefined<component :is="undefined"> 渲染空节点;缩略图加载完成后走 v-if="item.thumbnail" 正常显示缩略图。

方案 B:替换为合法的 IconPark 图标(保留图标)

项目已全量引入 IconPark(@icon-park/vue-next/es/all),且 mediaData.ts 中已在使用这些图标名,可直接复用:

   initialVideoItems.value = data.video.flatMap((item, index) => {
     if (Array.isArray(item.video)) {
       return item.video.map((subItem, subIndex) => ({
         id: `video-${subItem.id}`,
         type: "video",
         name: "#" + ...,
         duration: subItem.duration || 0,
-        icon: "🎬",
+        icon: "i-video",
         color: "...",
         url: subItem.filePath,
         selected: item.videoId == subItem.id,
       }));
     }
   });
-  mockMediaItems.value = videoList.map((item) => ({ ..., icon: "🎥", ... }));
+  mockMediaItems.value = videoList.map((item) => ({ ..., icon: "i-video-file", ... }));
-  mockImageItems.value = imageList.map((item) => ({ ..., icon: "🖼️", ... }));
+  mockImageItems.value = imageList.map((item) => ({ ..., icon: "i-pic", ... }));

映射:🎬(分镜视频)→ i-video🎥(媒体素材)→ i-video-file🖼️(图片素材)→ i-pic。均为合法组件名,可正常渲染,且保留了原图标语义。

相关文件

  • src/views/production/components/workbench/index.vueeditFootage() 中的 icon 字段)
  • src/views/production/components/workbench/editVideo/mediaLibrary.vue<component :is="item.icon">

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant