feat: support ticket home video cover

This commit is contained in:
2026-08-11 12:00:10 +08:00
parent e4f281bf14
commit 2a1117c1f9
2 changed files with 47 additions and 1 deletions

View File

@@ -1,7 +1,27 @@
<template>
<view class="h-screen overflow-hidden bg-[#06111f] flex flex-col">
<view class="relative z-0 h-[43vh] shrink-0 overflow-hidden bg-[#173a34]">
<image class="absolute inset-0 h-full w-full" :src="homeData.coverImage" mode="aspectFill" />
<video
v-if="homeData.coverImage && isMp4Video(homeData.coverImage)"
class="absolute inset-0 h-full w-full"
:src="homeData.coverImage"
autoplay
loop
muted
:controls="false"
:show-center-play-btn="false"
:show-play-btn="false"
:show-fullscreen-btn="false"
:show-mute-btn="false"
:enable-progress-gesture="false"
object-fit="cover"
/>
<image
v-else-if="homeData.coverImage"
class="absolute inset-0 h-full w-full"
:src="homeData.coverImage"
mode="aspectFill"
/>
<view class="absolute inset-0 bg-[linear-gradient(180deg,rgba(6,17,31,0.15)_18%,rgba(6,17,31,0.1)_45%,rgba(6,17,31,0.92)_100%)]" />
<view class="absolute inset-x-0 top-0 z-20">
@@ -151,6 +171,11 @@ const instance = getCurrentInstance()
const scenicMetaText = computed(() =>
[props.homeData.levelText, props.homeData.subtitle].filter(Boolean).join('')
)
const isMp4Video = (url) => {
if (typeof url !== 'string') return false
const resourcePath = url.trim().split(/[?#]/)[0]
return resourcePath.toLowerCase().endsWith('.mp4')
}
const isActiveGroup = (groupId) =>
String(groupId) === String(activeGroupId.value)
const productsForGroup = (group) =>

View File

@@ -117,6 +117,27 @@ test("TicketHome consumes scenic home response fields directly", async () => {
);
});
test("TicketHome renders MP4 cover images as background videos", async () => {
const source = await readTicketFile("components/TicketHome.vue");
assert.match(
source,
/<video\s+v-if="homeData\.coverImage && isMp4Video\(homeData\.coverImage\)"/
);
assert.match(source, /autoplay\s+loop\s+muted/);
assert.match(source, /:controls="false"/);
assert.match(source, /:show-center-play-btn="false"/);
assert.match(source, /:show-play-btn="false"/);
assert.match(source, /:show-fullscreen-btn="false"/);
assert.match(source, /:show-mute-btn="false"/);
assert.match(source, /:enable-progress-gesture="false"/);
assert.match(source, /object-fit="cover"/);
assert.match(source, /<image\s+v-else-if="homeData\.coverImage"/);
assert.match(source, /const isMp4Video = \(url\) =>/);
assert.match(source, /url\.trim\(\)\.split\(\/\[\?\#\]\/\)\[0\]/);
assert.match(source, /toLowerCase\(\)\.endsWith\('\.mp4'\)/);
});
test("product detail is an independent prototype-aligned page", async () => {
const indexPage = await readTicketFile("index.vue");
const detailPage = await readTicketFile("product-detail.vue");