feat: 视频组件

This commit is contained in:
2026-05-28 16:49:34 +08:00
parent 90872a4172
commit d32d4cb845
4 changed files with 126 additions and 45 deletions

View File

@@ -112,11 +112,18 @@
<GeneratorPhotoComponent <GeneratorPhotoComponent
v-else-if=" v-else-if="
item.toolCall && item.toolCall &&
item.toolCall.componentName === item.toolCall.componentName === CompName.aigcPhotoGeneratorCard
CompName.aigcPhotoGeneratorCard
" "
:toolCall="item.toolCall" :toolCall="item.toolCall"
/> />
<AigcPhotoCard
v-else-if="
item.toolCall &&
item.toolCall.componentName === CompName.videoCard
"
:toolCall="item.toolCall"
/>
<!-- <ZModuleC01 <!-- <ZModuleC01
v-else-if=" v-else-if="
item.toolCall && item.toolCall &&
@@ -248,6 +255,7 @@ import DetailCardCompontent from "../../ChatModule/DetailCardCompontent/index.vu
import OpenMapComponent from "../../ChatModule/OpenMapComponent/index.vue"; import OpenMapComponent from "../../ChatModule/OpenMapComponent/index.vue";
import AnswerComponent from "../../ChatModule/AnswerComponent/index.vue"; import AnswerComponent from "../../ChatModule/AnswerComponent/index.vue";
import GeneratorPhotoComponent from "../../ChatModule/GeneratorPhotoComponent/index.vue"; import GeneratorPhotoComponent from "../../ChatModule/GeneratorPhotoComponent/index.vue";
import AigcPhotoCard from "../../ChatModule/AigcPhotoCard/index.vue";
import ZModuleC01 from "../../ChatModule/ZModuleC01/index.vue"; import ZModuleC01 from "../../ChatModule/ZModuleC01/index.vue";
import LongTextGuideCardPreview from "../../ChatModule/LongTextGuideCardPreview/index.vue"; import LongTextGuideCardPreview from "../../ChatModule/LongTextGuideCardPreview/index.vue";

View File

@@ -1,28 +1,38 @@
<template> <template>
<view <view class="w-full bg-white border-box border-ff overflow-hidden rounded-20 flex flex-col">
class="aigc-photo-card relative rounded-24 overflow-hidden w-full" <!-- 占位撑开 -->
:class="{ 'is-disabled': disabled }" <view class="w-vw"></view>
@click="handleAction"
> <view class="aigc-photo-card relative rounded-24 overflow-hidden w-full"
<image :class="{ 'is-disabled': disabled, 'is-empty': !videoUrl }" @click="handleAction">
class="aigc-photo-card__image block w-full" <video v-if="videoUrl" :id="videoId" class="aigc-photo-card__video block w-full" :src="videoUrl" :poster="poster"
:src="data.cover" :controls="!disabled" :show-center-play-btn="!disabled" :show-play-btn="!disabled"
mode="aspectFill" :show-fullscreen-btn="!disabled" :enable-progress-gesture="!disabled"
/> :direction="fullscreenDirection" object-fit="cover" @click.stop @play="handlePlay"
<view class="aigc-photo-card__shade"></view> @fullscreenchange="handleFullScreenChange" @error="handleError" />
<view
<view class="aigc-photo-card__play flex flex-items-center flex-justify-center rounded-full"> v-if="videoUrl && !disabled"
<view class="aigc-photo-card__triangle"></view> class="aigc-photo-card__fullscreen flex flex-items-center flex-justify-center color-white font-size-12"
</view> @click.stop="requestFullScreen"
>
<view class="aigc-photo-card__title color-white font-size-16 font-900"> 全屏
{{ data.title }} </view>
<view v-if="!videoUrl" class="aigc-photo-card__empty flex flex-items-center flex-justify-center color-white font-size-14">
视频地址为空
</view>
</view> </view>
</view> </view>
</template> </template>
<script setup> <script setup>
import { computed, getCurrentInstance } from "vue";
const props = defineProps({ const props = defineProps({
toolCall: {
type: Object,
default: () => ({}),
},
data: { data: {
type: Object, type: Object,
default: () => ({}), default: () => ({}),
@@ -33,12 +43,75 @@ const props = defineProps({
}, },
}); });
const emit = defineEmits(["select", "action"]); const emit = defineEmits(["select", "action", "play", "fullscreenchange", "error"]);
const instance = getCurrentInstance();
const videoId = `aigc-video-${Math.random().toString(36).slice(2, 10)}`;
const cardData = computed(() => ({
...props.data,
...(props.toolCall?.componentNameParams || {}),
}));
const videoUrl = computed(
() => cardData.value.videoUrl || cardData.value.url || cardData.value.src || ""
);
const poster = computed(
() => cardData.value.poster || cardData.value.cover || cardData.value.coverUrl || ""
);
const title = computed(() => cardData.value.title || cardData.value.name || "");
const fullscreenDirection = computed(
() => cardData.value.fullscreenDirection ?? cardData.value.direction ?? 90
);
const eventPayload = computed(() => {
if (props.toolCall && Object.keys(props.toolCall).length > 0) {
return {
...props.toolCall,
componentNameParams: cardData.value,
};
}
return cardData.value;
});
const handleAction = () => { const handleAction = () => {
if (props.disabled) return; if (props.disabled) return;
emit("select", props.data); emit("select", eventPayload.value);
emit("action", props.data); emit("action", eventPayload.value);
};
const handlePlay = () => {
if (props.disabled) return;
emit("play", eventPayload.value);
};
const requestFullScreen = () => {
if (!videoUrl.value || props.disabled) return;
const videoContext = uni.createVideoContext(
videoId,
instance?.proxy || instance
);
videoContext?.play?.();
videoContext?.requestFullScreen?.({
direction: Number(fullscreenDirection.value),
});
};
const handleFullScreenChange = (event) => {
emit("fullscreenchange", {
event,
data: eventPayload.value,
});
};
const handleError = (event) => {
emit("error", {
event,
data: eventPayload.value,
});
}; };
</script> </script>

View File

@@ -2,6 +2,7 @@ export default {
id: "xiaoqikong-flying", id: "xiaoqikong-flying",
title: "沉浸式飞越 · 小七孔", title: "沉浸式飞越 · 小七孔",
cover: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=1000&q=80", cover: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=1000&q=80",
videoUrl: "https://oss.nianxx.cn/config/03e179416ff9c4990eba9e70a4448a6f.mp4",
action: { action: {
type: "play", type: "play",
}, },

View File

@@ -1,5 +1,5 @@
.aigc-photo-card { .aigc-photo-card {
height: 272px; height: 220px;
background: #0f172a; background: #0f172a;
} }
@@ -11,44 +11,43 @@
opacity: 0.55; opacity: 0.55;
} }
.aigc-photo-card__image { .aigc-photo-card__video {
height: 100%; height: 100%;
} }
.aigc-photo-card__shade { .aigc-photo-card.is-disabled .aigc-photo-card__video {
position: absolute; pointer-events: none;
inset: 0;
background: rgba(15, 23, 42, 0.38);
} }
.aigc-photo-card__play { .aigc-photo-card__fullscreen {
position: absolute; position: absolute;
left: 50%; top: 10px;
top: 50%; right: 10px;
width: 76px; min-width: 44px;
height: 76px; height: 28px;
border: 1px solid rgba(255, 255, 255, 0.48); padding: 0 10px;
background: rgba(255, 255, 255, 0.28); border-radius: 14px;
transform: translate(-50%, -50%); background: rgba(15, 23, 42, 0.72);
box-sizing: border-box; box-sizing: border-box;
} }
.aigc-photo-card__triangle { .aigc-photo-card__fullscreen:active {
width: 0; opacity: 0.82;
height: 0; }
margin-left: 6px;
border-top: 17px solid transparent; .aigc-photo-card__empty {
border-bottom: 17px solid transparent; height: 100%;
border-left: 23px solid #fff;
} }
.aigc-photo-card__title { .aigc-photo-card__title {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: 72px; top: 16px;
padding: 0 16px;
text-align: center; text-align: center;
line-height: 22px; line-height: 22px;
letter-spacing: 0; letter-spacing: 0;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.32); text-shadow: 0 2px 8px rgba(0, 0, 0, 0.32);
pointer-events: none;
} }