Skip to content

Commit f783919

Browse files
committed
修复桌面端视频查看
1 parent 15b4b00 commit f783919

2 files changed

Lines changed: 192 additions & 31 deletions

File tree

frontend/pages/chat/[[username]].vue

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -589,17 +589,16 @@
589589
:class="message.isSent ? 'bg-[#95EC69] text-black bubble-tail-r' : 'bg-white text-gray-800 bubble-tail-l'">
590590
{{ message.content }}
591591
</div>
592-
<a
592+
<button
593593
v-if="message.videoThumbUrl && message.videoUrl"
594-
:href="message.videoUrl"
595-
target="_blank"
596-
rel="noreferrer"
594+
type="button"
597595
class="absolute inset-0 flex items-center justify-center"
596+
@click.stop="openVideoPreview(message.videoUrl, message.videoThumbUrl)"
598597
>
599598
<div class="w-12 h-12 rounded-full bg-black/45 flex items-center justify-center">
600599
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
601600
</div>
602-
</a>
601+
</button>
603602
<div class="absolute inset-0 flex items-center justify-center" v-else-if="message.videoThumbUrl">
604603
<div class="w-12 h-12 rounded-full bg-black/45 flex items-center justify-center">
605604
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
@@ -1388,6 +1387,40 @@
13881387
</button>
13891388
</div>
13901389

1390+
<!-- 视频预览弹窗 (全局固定定位) -->
1391+
<div
1392+
v-if="previewVideoUrl"
1393+
class="fixed inset-0 z-[13000] bg-black/90 flex items-center justify-center"
1394+
@click="closeVideoPreview"
1395+
>
1396+
<div class="relative max-w-[92vw] max-h-[92vh] flex flex-col items-center" @click.stop>
1397+
<video
1398+
:key="previewVideoUrl"
1399+
:src="previewVideoUrl"
1400+
:poster="previewVideoPosterUrl"
1401+
class="max-w-[90vw] max-h-[90vh] object-contain"
1402+
controls
1403+
autoplay
1404+
playsinline
1405+
@error="onPreviewVideoError"
1406+
></video>
1407+
<div
1408+
v-if="previewVideoError"
1409+
class="mt-3 text-xs text-red-200 whitespace-pre-wrap text-center max-w-[90vw]"
1410+
>
1411+
{{ previewVideoError }}
1412+
</div>
1413+
</div>
1414+
<button
1415+
class="absolute top-4 right-4 text-white/80 hover:text-white p-2 rounded-full bg-black/30 hover:bg-black/50 transition-colors"
1416+
@click.stop="closeVideoPreview"
1417+
>
1418+
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1419+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
1420+
</svg>
1421+
</button>
1422+
</div>
1423+
13911424
<!-- 浮动窗口(可拖动):合并消息 / 链接卡片 -->
13921425
<div
13931426
v-for="win in floatingWindows"
@@ -1561,17 +1594,16 @@
15611594
@error="onChatHistoryVideoThumbError(rec)"
15621595
/>
15631596
<div v-else class="px-3 py-2 text-sm text-gray-700">{{ rec.content || '[视频]' }}</div>
1564-
<a
1597+
<button
15651598
v-if="rec.videoUrl"
1566-
:href="rec.videoUrl"
1567-
target="_blank"
1568-
rel="noreferrer"
1599+
type="button"
15691600
class="absolute inset-0 flex items-center justify-center"
1601+
@click.stop="openVideoPreview(rec.videoUrl, rec.videoThumbUrl)"
15701602
>
15711603
<div class="w-12 h-12 rounded-full bg-black/45 flex items-center justify-center">
15721604
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
15731605
</div>
1574-
</a>
1606+
</button>
15751607
<div
15761608
v-if="rec.videoDuration"
15771609
class="absolute bottom-2 right-2 text-xs text-white bg-black/55 px-1.5 py-0.5 rounded"
@@ -1814,17 +1846,16 @@
18141846
/>
18151847
<div v-else class="px-3 py-2 text-sm text-gray-700">{{ rec.content || '[视频]' }}</div>
18161848

1817-
<a
1849+
<button
18181850
v-if="rec.videoThumbUrl && rec.videoUrl"
1819-
:href="rec.videoUrl"
1820-
target="_blank"
1821-
rel="noreferrer"
1851+
type="button"
18221852
class="absolute inset-0 flex items-center justify-center"
1853+
@click.stop="openVideoPreview(rec.videoUrl, rec.videoThumbUrl)"
18231854
>
18241855
<div class="w-12 h-12 rounded-full bg-black/45 flex items-center justify-center">
18251856
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
18261857
</div>
1827-
</a>
1858+
</button>
18281859
<div class="absolute inset-0 flex items-center justify-center" v-else-if="rec.videoThumbUrl">
18291860
<div class="w-12 h-12 rounded-full bg-black/45 flex items-center justify-center">
18301861
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>
@@ -4070,15 +4101,42 @@ const scrollToMessageId = async (id) => {
40704101

40714102
// 图片预览状态
40724103
const previewImageUrl = ref(null)
4104+
const previewVideoUrl = ref(null)
4105+
const previewVideoPosterUrl = ref('')
4106+
const previewVideoError = ref('')
40734107

40744108
const openImagePreview = (url) => {
4109+
if (!process.client) return
40754110
previewImageUrl.value = url
40764111
document.body.style.overflow = 'hidden'
40774112
}
40784113

40794114
const closeImagePreview = () => {
4115+
if (!process.client) return
40804116
previewImageUrl.value = null
4081-
document.body.style.overflow = ''
4117+
if (!previewVideoUrl.value) document.body.style.overflow = ''
4118+
}
4119+
4120+
const openVideoPreview = (url, poster) => {
4121+
if (!process.client) return
4122+
const u = String(url || '').trim()
4123+
if (!u) return
4124+
previewVideoError.value = ''
4125+
previewVideoPosterUrl.value = String(poster || '').trim()
4126+
previewVideoUrl.value = u
4127+
document.body.style.overflow = 'hidden'
4128+
}
4129+
4130+
const closeVideoPreview = () => {
4131+
if (!process.client) return
4132+
previewVideoUrl.value = null
4133+
previewVideoPosterUrl.value = ''
4134+
previewVideoError.value = ''
4135+
if (!previewImageUrl.value) document.body.style.overflow = ''
4136+
}
4137+
4138+
const onPreviewVideoError = () => {
4139+
previewVideoError.value = '视频加载失败。'
40824140
}
40834141

40844142
const voiceRefs = ref({})
@@ -7047,9 +7105,7 @@ const openChatHistoryQuote = (rec) => {
70477105
if (!url) return
70487106

70497107
if (kind === 'video') {
7050-
try {
7051-
window.open(url, '_blank', 'noreferrer')
7052-
} catch {}
7108+
openVideoPreview(url, q?.thumbUrl)
70537109
return
70547110
}
70557111

@@ -7392,6 +7448,7 @@ const onGlobalKeyDown = (e) => {
73927448
if (key === 'Escape') {
73937449
if (contextMenu.value.visible) closeContextMenu()
73947450
if (previewImageUrl.value) closeImagePreview()
7451+
if (previewVideoUrl.value) closeVideoPreview()
73957452
if (Array.isArray(floatingWindows.value) && floatingWindows.value.length) closeTopFloatingWindow()
73967453
if (chatHistoryModalVisible.value) closeChatHistoryModal()
73977454
if (contactProfileCardOpen.value) {
@@ -9311,4 +9368,3 @@ const LinkCard = defineComponent({
93119368
}
93129369
}
93139370
</style>
9314-

frontend/pages/sns.vue

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,19 @@
638638
>
639639
<div class="relative max-w-[92vw] max-h-[92vh] flex flex-col items-center" @click.stop>
640640
<video
641-
v-if="previewLivePhotoVideoSrc && !previewHasLivePhotoVideoError"
641+
v-if="previewIsVideo"
642+
ref="previewVideoEl"
643+
:key="previewVideoKey"
644+
:src="previewVideoSrc"
645+
:poster="previewVideoPoster"
646+
class="max-w-[90vw] max-h-[70vh] object-contain"
647+
controls
648+
autoplay
649+
playsinline
650+
@error="onPreviewVideoError"
651+
></video>
652+
<video
653+
v-else-if="previewLivePhotoVideoSrc && !previewHasLivePhotoVideoError"
642654
ref="previewLiveVideoEl"
643655
:src="previewLivePhotoVideoSrc"
644656
:poster="previewSrc"
@@ -651,6 +663,13 @@
651663
></video>
652664
<img v-else :src="previewSrc" alt="预览" class="max-w-[90vw] max-h-[70vh] object-contain" />
653665

666+
<div
667+
v-if="previewIsVideo && previewVideoError"
668+
class="mt-3 text-xs text-red-200 whitespace-pre-wrap text-center max-w-[90vw]"
669+
>
670+
{{ previewVideoError }}
671+
</div>
672+
654673
</div>
655674

656675
<button
@@ -1756,6 +1775,53 @@ const previewSrc = computed(() => {
17561775
return getMediaPreviewSrc(ctx.post, ctx.media, ctx.idx)
17571776
})
17581777

1778+
const previewVideoEl = ref(null)
1779+
const previewVideoMode = ref('') // 'local' | 'remote' | 'raw'
1780+
const previewVideoError = ref('')
1781+
const previewVideoTried = reactive({ local: false, remote: false, raw: false })
1782+
1783+
const resetPreviewVideo = () => {
1784+
previewVideoMode.value = ''
1785+
previewVideoError.value = ''
1786+
previewVideoTried.local = false
1787+
previewVideoTried.remote = false
1788+
previewVideoTried.raw = false
1789+
}
1790+
1791+
const previewIsVideo = computed(() => {
1792+
const ctx = previewCtx.value
1793+
if (!ctx) return false
1794+
return Number(ctx.media?.type || 0) === 6
1795+
})
1796+
1797+
const previewVideoPoster = computed(() => {
1798+
const ctx = previewCtx.value
1799+
if (!ctx) return ''
1800+
if (Number(ctx.media?.type || 0) !== 6) return ''
1801+
return getMediaThumbSrc(ctx.post, ctx.media, ctx.idx) || ''
1802+
})
1803+
1804+
const previewVideoSrc = computed(() => {
1805+
const ctx = previewCtx.value
1806+
if (!ctx) return ''
1807+
if (Number(ctx.media?.type || 0) !== 6) return ''
1808+
1809+
const local = getSnsVideoUrl(ctx.post?.id, ctx.media?.id)
1810+
const remote = getSnsRemoteVideoSrc(ctx.post, ctx.media)
1811+
const raw = upgradeTencentHttps(String(ctx.media?.url || '').trim())
1812+
1813+
const mode = String(previewVideoMode.value || '').toLowerCase()
1814+
if (mode === 'local') return local
1815+
if (mode === 'remote') return remote
1816+
if (mode === 'raw') return raw
1817+
return local || remote || raw || ''
1818+
})
1819+
1820+
const previewVideoKey = computed(() => {
1821+
if (!previewIsVideo.value) return ''
1822+
return `${String(previewVideoMode.value || '')}:${String(previewVideoSrc.value || '')}`
1823+
})
1824+
17591825
const previewLivePhotoVideoSrc = computed(() => {
17601826
const ctx = previewCtx.value
17611827
if (!ctx) return ''
@@ -1879,6 +1945,7 @@ const loadPreviewCandidates = async ({ reset }) => {
18791945

18801946
const openImagePreview = async (post, m, idx = 0) => {
18811947
if (!process.client) return
1948+
resetPreviewVideo()
18821949
// Stop any background hover-playing live photo when opening the preview.
18831950
activeLivePhotoKey.value = ''
18841951
// Preview is an intentional action; allow retry even if hover playback failed once.
@@ -1898,11 +1965,58 @@ const openImagePreview = async (post, m, idx = 0) => {
18981965
await loadPreviewCandidates({ reset: true })
18991966
}
19001967

1968+
const openVideoPreview = (post, m, idx = 0) => {
1969+
if (!process.client) return
1970+
resetPreviewVideo()
1971+
activeLivePhotoKey.value = ''
1972+
1973+
const local = getSnsVideoUrl(post?.id, m?.id)
1974+
const remote = getSnsRemoteVideoSrc(post, m)
1975+
const raw = upgradeTencentHttps(String(m?.url || '').trim())
1976+
1977+
if (local) previewVideoMode.value = 'local'
1978+
else if (remote) previewVideoMode.value = 'remote'
1979+
else if (raw) previewVideoMode.value = 'raw'
1980+
else previewVideoError.value = '视频地址缺失。'
1981+
1982+
previewCtx.value = { post, media: m, idx: Number(idx) || 0 }
1983+
previewCandidatesOpen.value = false
1984+
resetPreviewCandidates()
1985+
document.body.style.overflow = 'hidden'
1986+
}
1987+
1988+
const onPreviewVideoError = () => {
1989+
const ctx = previewCtx.value
1990+
if (!ctx) return
1991+
if (Number(ctx.media?.type || 0) !== 6) return
1992+
1993+
const current = String(previewVideoMode.value || '').toLowerCase()
1994+
if (current === 'local') previewVideoTried.local = true
1995+
if (current === 'remote') previewVideoTried.remote = true
1996+
if (current === 'raw') previewVideoTried.raw = true
1997+
1998+
// Fallback order: local -> remote -> raw
1999+
const remote = getSnsRemoteVideoSrc(ctx.post, ctx.media)
2000+
if (!previewVideoTried.remote && remote) {
2001+
previewVideoMode.value = 'remote'
2002+
return
2003+
}
2004+
2005+
const raw = upgradeTencentHttps(String(ctx.media?.url || '').trim())
2006+
if (!previewVideoTried.raw && raw) {
2007+
previewVideoMode.value = 'raw'
2008+
return
2009+
}
2010+
2011+
previewVideoError.value = '视频加载失败:可能是本地缓存不存在,或远程下载/解密失败。'
2012+
}
2013+
19012014
const closeImagePreview = () => {
19022015
if (!process.client) return
19032016
previewCtx.value = null
19042017
previewCandidatesOpen.value = false
19052018
resetPreviewCandidates()
2019+
resetPreviewVideo()
19062020
document.body.style.overflow = ''
19072021
}
19082022

@@ -1912,16 +2026,7 @@ const onMediaClick = (post, m, idx = 0) => {
19122026

19132027
// 视频点击逻辑
19142028
if (mt === 6) {
1915-
// Open a playable mp4 via backend (downloads+decrypts as needed).
1916-
const remoteUrl = getSnsRemoteVideoSrc(post, m)
1917-
if (remoteUrl) {
1918-
window.open(remoteUrl, '_blank', 'noopener,noreferrer')
1919-
return
1920-
}
1921-
1922-
// Last-resort: open raw CDN url.
1923-
const u = String(m?.url || '').trim()
1924-
if (u) window.open(u, '_blank', 'noopener,noreferrer')
2029+
openVideoPreview(post, m, idx)
19252030
return
19262031
}
19272032

0 commit comments

Comments
 (0)