hookehuyr

fix(SharePoster): 修复首次生成海报时封面空白问题

移除生成中的提示注释,调整海报图片圆角样式
添加首次生成标志位,连续生成两次规避空白问题
修改离屏克隆背景为透明,避免白色背景影响视觉效果
...@@ -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
......