fix(studio): feed 卡占位封面(1×1 透明 GIF)当无封面走标题底纹兜底——修"一片黑"卖相

链路③⑤ 人测亲见:feed 卡一片黑+只剩播放按钮。根因=生成侧无缩略图时后端回填 1×1
透明占位 GIF(data:image/gif;base64,R0lGODlhAQAB…),它是 truthy → 绕过旧 `!coverUrl`
兜底、被当背景渲染成透明(视觉=黑)。

修:GameCard.vue 加 hasCover 计算属性,识别 1×1 占位 GIF 当"无封面"→走既有标题底纹兜底,
feed 卡始终有可读内容。真实封面(OSS http(s)/实质图)才用作背景;真缩略图归 W-G1。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zizi 2026-06-15 13:39:14 +00:00
parent cf2e1ed3b5
commit 384a26da59

View File

@ -5,9 +5,10 @@
* 职责:展示封面/标题/作者/试玩+点赞计数 + “即玩”入口;点击卡片或按钮触发 play 事件。
* 不直接发请求(互动由 InteractBar / 父级 store 处理),保持组件纯展示 + 事件上抛。
*/
import { computed } from 'vue'
import type { FeedCard } from '../api/types'
defineProps<{
const props = defineProps<{
/** 游戏流卡片数据(严格贴契约 FeedCard) */
card: FeedCard
/** 是否当前激活卡(竖屏流当前可见项,可用于触发预加载/曝光埋点) */
@ -16,6 +17,22 @@ defineProps<{
/** 事件:点击进入试玩(携带 gameId/versionId 供父级路由到 /play) */
const emit = defineEmits<{ (e: 'play', payload: { gameId: number; versionId: number }): void }>()
/**
* 1×1 透明占位 GIF 识别(生成侧未产缩略图时,后端 feed 回填的占位封面,形如
* data:image/gif;base64,R0lGODlhAQAB…)。这类占位图是 truthy,会绕过旧 `!coverUrl` 兜底、
* 被当背景渲染成一片透明(视觉=一片黑)。
*/
const PLACEHOLDER_COVER_RE = /^data:image\/gif;base64,R0lGODlhAQAB/i
/**
* 是否有「真实封面」。空 coverUrl 或 1×1 透明占位图都视为无封面 → 走标题底纹兜底,
* 让 feed 卡片始终有可读内容;真实封面(OSS http(s) 或实质 data-URI 图)才用作背景。
* 封面无源属生成特性,真缩略图归 W-G1 出。
*/
const hasCover = computed(() => {
const url = props.card.coverUrl
return !!url && !PLACEHOLDER_COVER_RE.test(url)
})
</script>
<template>
@ -23,11 +40,11 @@ const emit = defineEmits<{ (e: 'play', payload: { gameId: number; versionId: num
<!-- 封面(首屏即玩入口,点击进试玩) -->
<div
class="game-card__cover"
:style="{ backgroundImage: card.coverUrl ? `url(${card.coverUrl})` : undefined }"
:style="{ backgroundImage: hasCover ? `url(${card.coverUrl})` : undefined }"
@click="emit('play', { gameId: card.gameId, versionId: card.versionId })"
>
<!-- 无封面时的占位底纹 -->
<div v-if="!card.coverUrl" class="game-card__cover-fallback">{{ card.title }}</div>
<!-- 无封面(含 1×1 占位图)时的标题底纹兜底 -->
<div v-if="!hasCover" class="game-card__cover-fallback">{{ card.title }}</div>
<!-- 中央播放按钮 -->
<div class="game-card__play">▶</div>
</div>