fix(SharePoster): 修复首次生成海报时封面空白问题
移除生成中的提示注释,调整海报图片圆角样式 添加首次生成标志位,连续生成两次规避空白问题 修改离屏克隆背景为透明,避免白色背景影响视觉效果
Showing
1 changed file
with
33 additions
and
6 deletions
| ... | @@ -14,12 +14,12 @@ | ... | @@ -14,12 +14,12 @@ |
| 14 | </div> | 14 | </div> |
| 15 | 15 | ||
| 16 | <!-- 生成中提示 --> | 16 | <!-- 生成中提示 --> |
| 17 | - <div v-if="is_generating" class="text-center text-gray-500 text-sm mb-2">正在生成海报...</div> | 17 | + <!-- <div v-if="is_generating" class="text-center text-gray-500 text-sm mb-2">正在生成海报...</div> --> |
| 18 | 18 | ||
| 19 | <!-- 海报区域:直接使用 Canvas 合成的图片,支持长按保存 --> | 19 | <!-- 海报区域:直接使用 Canvas 合成的图片,支持长按保存 --> |
| 20 | <!-- 当已生成海报图时,容器不再应用卡片边框与阴影,避免双重边框视觉效果;降级展示仍保留卡片样式 --> | 20 | <!-- 当已生成海报图时,容器不再应用卡片边框与阴影,避免双重边框视觉效果;降级展示仍保留卡片样式 --> |
| 21 | <div :class="poster_img_src ? 'PosterCard mx-auto' : 'PosterCard bg-white rounded-xl overflow-hidden border border-gray-200 mx-auto'" ref="card_ref"> | 21 | <div :class="poster_img_src ? 'PosterCard mx-auto' : 'PosterCard bg-white rounded-xl overflow-hidden border border-gray-200 mx-auto'" ref="card_ref"> |
| 22 | - <img v-if="poster_img_src" :src="poster_img_src" alt="分享海报" class="w-full h-auto object-contain block" /> | 22 | + <img v-if="poster_img_src" :src="poster_img_src" alt="分享海报" class="w-full h-auto object-contain block rounded-xl overflow-hidden" /> |
| 23 | <!-- 生成失败或尚未生成时的降级展示(可长按截图保存) --> | 23 | <!-- 生成失败或尚未生成时的降级展示(可长按截图保存) --> |
| 24 | <div v-else> | 24 | <div v-else> |
| 25 | <!-- 上部封面图 --> | 25 | <!-- 上部封面图 --> |
| ... | @@ -163,6 +163,11 @@ const card_ref = ref(null) | ... | @@ -163,6 +163,11 @@ const card_ref = ref(null) |
| 163 | * @description 是否处于海报生成中状态,用于在弹窗内展示“正在生成海报...”提示。 | 163 | * @description 是否处于海报生成中状态,用于在弹窗内展示“正在生成海报...”提示。 |
| 164 | */ | 164 | */ |
| 165 | const is_generating = ref(false) | 165 | const is_generating = ref(false) |
| 166 | +/** | ||
| 167 | + * @var {import('vue').Ref<boolean>} has_generated_once | ||
| 168 | + * @description 是否已在本组件生命周期内完成过生成;用于控制首次打开时执行两次生成以规避首屏封面空白问题。 | ||
| 169 | + */ | ||
| 170 | +const has_generated_once = ref(false) | ||
| 166 | 171 | ||
| 167 | /** 标题/副标题/介绍 */ | 172 | /** 标题/副标题/介绍 */ |
| 168 | const title_text = computed(() => props.course?.title || '课程') | 173 | const title_text = computed(() => props.course?.title || '课程') |
| ... | @@ -347,7 +352,6 @@ async function compose_poster() { | ... | @@ -347,7 +352,6 @@ async function compose_poster() { |
| 347 | const data_url = await toPng(clone, { | 352 | const data_url = await toPng(clone, { |
| 348 | pixelRatio: pixel_ratio, | 353 | pixelRatio: pixel_ratio, |
| 349 | cacheBust: true, | 354 | cacheBust: true, |
| 350 | - backgroundColor: '#ffffff', | ||
| 351 | imagePlaceholder: placeholder, | 355 | imagePlaceholder: placeholder, |
| 352 | fetchRequestInit: { mode: 'cors', cache: 'no-cache', credentials: 'omit' }, | 356 | fetchRequestInit: { mode: 'cors', cache: 'no-cache', credentials: 'omit' }, |
| 353 | // 避免外层 margin 影响截图结果 | 357 | // 避免外层 margin 影响截图结果 |
| ... | @@ -368,6 +372,27 @@ async function compose_poster() { | ... | @@ -368,6 +372,27 @@ async function compose_poster() { |
| 368 | } | 372 | } |
| 369 | 373 | ||
| 370 | /** | 374 | /** |
| 375 | + * @function generate_on_open | ||
| 376 | + * @description 弹窗首次打开时,连续生成两次;后续打开只生成一次。 | ||
| 377 | + * @returns {Promise<void>} | ||
| 378 | + */ | ||
| 379 | +async function generate_on_open() { | ||
| 380 | + try { | ||
| 381 | + if (!has_generated_once.value) { | ||
| 382 | + await compose_poster() | ||
| 383 | + // 短暂等待,确保图片与布局稳定后再次生成,规避首次封面空白 | ||
| 384 | + await new Promise(r => setTimeout(r, 120)) | ||
| 385 | + await compose_poster() | ||
| 386 | + has_generated_once.value = true | ||
| 387 | + } else { | ||
| 388 | + await compose_poster() | ||
| 389 | + } | ||
| 390 | + } catch (_) { | ||
| 391 | + // 忽略异常,compose_poster 已包含错误提示 | ||
| 392 | + } | ||
| 393 | +} | ||
| 394 | + | ||
| 395 | +/** | ||
| 371 | * @function create_offscreen_clone | 396 | * @function create_offscreen_clone |
| 372 | * @description 创建一个离屏的卡片克隆用于稳定截图,避免受弹窗动画与布局影响。 | 397 | * @description 创建一个离屏的卡片克隆用于稳定截图,避免受弹窗动画与布局影响。 |
| 373 | * @param {HTMLElement} node 原始容器节点 | 398 | * @param {HTMLElement} node 原始容器节点 |
| ... | @@ -388,7 +413,7 @@ function create_offscreen_clone(node) { | ... | @@ -388,7 +413,7 @@ function create_offscreen_clone(node) { |
| 388 | overflow: 'hidden', | 413 | overflow: 'hidden', |
| 389 | opacity: '0', | 414 | opacity: '0', |
| 390 | pointerEvents: 'none', | 415 | pointerEvents: 'none', |
| 391 | - background: '#ffffff', | 416 | + background: 'transparent', |
| 392 | zIndex: '-1000' | 417 | zIndex: '-1000' |
| 393 | }) | 418 | }) |
| 394 | // 克隆节点样式校正,避免动画、阴影、滤镜干扰 | 419 | // 克隆节点样式校正,避免动画、阴影、滤镜干扰 |
| ... | @@ -398,7 +423,9 @@ function create_offscreen_clone(node) { | ... | @@ -398,7 +423,9 @@ function create_offscreen_clone(node) { |
| 398 | margin: '0', | 423 | margin: '0', |
| 399 | transform: 'none', | 424 | transform: 'none', |
| 400 | filter: 'none', | 425 | filter: 'none', |
| 401 | - boxShadow: 'none' | 426 | + boxShadow: 'none', |
| 427 | + borderRadius: '16px', | ||
| 428 | + overflow: 'hidden' | ||
| 402 | }) | 429 | }) |
| 403 | wrapper.appendChild(clone) | 430 | wrapper.appendChild(clone) |
| 404 | document.body.appendChild(wrapper) | 431 | document.body.appendChild(wrapper) |
| ... | @@ -414,7 +441,7 @@ watch(show_proxy, (opened) => { | ... | @@ -414,7 +441,7 @@ watch(show_proxy, (opened) => { |
| 414 | if (opened) { | 441 | if (opened) { |
| 415 | if (is_generating.value) return | 442 | if (is_generating.value) return |
| 416 | poster_img_src.value = '' | 443 | poster_img_src.value = '' |
| 417 | - nextTick(() => compose_poster()) | 444 | + nextTick(() => generate_on_open()) |
| 418 | } | 445 | } |
| 419 | }) | 446 | }) |
| 420 | 447 | ... | ... |
-
Please register or login to post a comment