hookehuyr

feat(login): 添加视频背景组件并更新登录页面

在登录页面中新增VideoBackground组件,提供视频背景功能
当视频不可用时自动降级为图片背景
同时更新页面标题为'登陆'
...@@ -93,6 +93,7 @@ declare module 'vue' { ...@@ -93,6 +93,7 @@ declare module 'vue' {
93 VanTag: typeof import('vant/es')['Tag'] 93 VanTag: typeof import('vant/es')['Tag']
94 VanTimePicker: typeof import('vant/es')['TimePicker'] 94 VanTimePicker: typeof import('vant/es')['TimePicker']
95 VanUploader: typeof import('vant/es')['Uploader'] 95 VanUploader: typeof import('vant/es')['Uploader']
96 + VideoBackground: typeof import('./components/ui/VideoBackground.vue')['default']
96 VideoPlayer: typeof import('./components/ui/VideoPlayer.vue')['default'] 97 VideoPlayer: typeof import('./components/ui/VideoPlayer.vue')['default']
97 WechatPayment: typeof import('./components/payment/WechatPayment.vue')['default'] 98 WechatPayment: typeof import('./components/payment/WechatPayment.vue')['default']
98 } 99 }
......
1 +<!--
2 + * @Date: 2025-12-26 14:15:46
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-12-26 14:35:49
5 + * @FilePath: /mlaj/src/components/ui/VideoBackground.vue
6 + * @Description: 文件描述
7 +-->
8 +<template>
9 + <div class="video-background fixed top-0 left-0 w-full h-full -z-10 overflow-hidden">
10 + <!-- 优先显示传入的静态图片背景 -->
11 + <div v-if="backgroundImage" class="w-full h-full bg-cover bg-center bg-no-repeat"
12 + :style="{ backgroundImage: `url(${backgroundImage})` }">
13 + </div>
14 +
15 + <template v-else>
16 + <video v-show="!use_image_bg" ref="videoPlayer" autoplay muted loop playsinline webkit-playsinline
17 + preload="auto" @error="on_video_error" @stalled="on_video_error" @abort="on_video_error"
18 + @emptied="on_video_error" @loadeddata="on_video_loaded" class="w-full h-full object-cover">
19 + <source :src="videoUrl" type="video/mp4" />
20 + Your browser does not support the video tag.
21 + </video>
22 +
23 + <!-- 图片降级背景(视频不可用时显示) -->
24 + <StarryBackground v-if="use_image_bg" />
25 + </template>
26 +
27 + <!-- 遮罩层,确保内容可读性 -->
28 + <div class="absolute inset-0 bg-black/30"></div>
29 + </div>
30 +</template>
31 +
32 +<script setup>
33 +import { ref, onMounted } from 'vue'
34 +/**
35 + * 视频背景组件
36 + * @description 循环播放指定的视频作为背景
37 + */
38 +const props = defineProps({
39 + videoUrl: {
40 + type: String,
41 + default: 'https://cdn.ipadbiz.cn/mlaj/recall/video/jimeng-2025-12-26-3484.mp4'
42 + },
43 + backgroundImage: {
44 + type: String,
45 + // TODO: 图片是假的, 如果是真实情况需要重新弄一张正式图片
46 + default: 'https://cdn.ipadbiz.cn/stdj/images/%E5%90%AF%E5%8A%A8%E9%A1%B5%E6%B5%B7%E6%8A%A5%E8%83%8C%E6%99%AF@2x.png?imageMogr2/thumbnail/400x/strip/quality/70'
47 + }
48 +})
49 +
50 +const videoPlayer = ref(null)
51 +const use_image_bg = ref(false)
52 +const is_video_ready = ref(false)
53 +
54 +onMounted(() => {
55 + // 如果有传入背景图,则不执行视频逻辑
56 + if (props.backgroundImage) return
57 +
58 + const video = videoPlayer.value
59 + if (video) {
60 + // 尝试播放视频
61 + const playPromise = video.play()
62 +
63 + if (playPromise !== undefined) {
64 + playPromise.catch(error => {
65 + // 自动播放被阻止:在微信环境下尝试通过 WeixinJSBridge 播放
66 + // 说明:避免控制台输出引起诊断问题
67 + void error
68 + const ua = navigator.userAgent.toLowerCase()
69 + if (ua.match(/MicroMessenger/i) && typeof window.WeixinJSBridge !== 'undefined') {
70 + window.WeixinJSBridge.invoke('getNetworkType', {}, () => {
71 + video.play()
72 + })
73 + }
74 + // 若短时间内仍无法播放,降级为图片背景,避免黑屏
75 + setTimeout(() => {
76 + if (!is_video_ready.value) {
77 + enable_image_fallback()
78 + }
79 + }, 1200)
80 + })
81 + }
82 +
83 + // 监听微信JSBridgeReady事件
84 + document.addEventListener('WeixinJSBridgeReady', () => {
85 + video.play()
86 + }, false)
87 + }
88 +
89 + // 兜底:在一定时间内仍未加载完成则切换到图片背景
90 + // setTimeout(() => {
91 + // if (!is_video_ready.value) {
92 + // enable_image_fallback()
93 + // }
94 + // }, 5000)
95 +})
96 +
97 +/**
98 + * 视频加载成功回调
99 + * 说明:标记视频已可播放,用于取消降级处理
100 + */
101 +const on_video_loaded = () => {
102 + is_video_ready.value = true
103 +}
104 +
105 +/**
106 + * 视频错误回调
107 + * 说明:视频加载/播放失败时触发图片降级,避免黑屏
108 + * @param {Event} e 事件对象
109 + */
110 +const on_video_error = (e) => {
111 + void e
112 + enable_image_fallback()
113 +}
114 +
115 +/**
116 + * 启用图片降级背景
117 + * 说明:切换到全屏图片背景,保证用户视觉不出现黑屏
118 + */
119 +const enable_image_fallback = () => {
120 + use_image_bg.value = true
121 +}
122 +</script>
123 +
124 +<style scoped>
125 +.video-background {
126 + /* 确保在所有内容之下 */
127 + z-index: -1;
128 +}
129 +</style>
1 <template> 1 <template>
2 <div class="recall-login w-full min-h-screen relative overflow-hidden flex flex-col items-center"> 2 <div class="recall-login w-full min-h-screen relative overflow-hidden flex flex-col items-center">
3 - <!-- Starry Background Effect --> 3 + <!-- <VideoBackground /> -->
4 <StarryBackground /> 4 <StarryBackground />
5 5
6 - <!-- Title Section --> 6 + <!-- 标题区域 -->
7 <div class="mt-10 flex flex-col items-center z-10 w-full px-8"> 7 <div class="mt-10 flex flex-col items-center z-10 w-full px-8">
8 <img :src="titleImg" class="w-full max-w-[300px] mb-4 object-contain" alt="title" /> 8 <img :src="titleImg" class="w-full max-w-[300px] mb-4 object-contain" alt="title" />
9 9
...@@ -89,6 +89,7 @@ import { useRouter, useRoute } from 'vue-router' ...@@ -89,6 +89,7 @@ import { useRouter, useRoute } from 'vue-router'
89 import { showToast } from 'vant' 89 import { showToast } from 'vant'
90 import { useTitle } from '@vueuse/core' 90 import { useTitle } from '@vueuse/core'
91 import { setAuthHeaders } from "@/utils/axios"; 91 import { setAuthHeaders } from "@/utils/axios";
92 +import VideoBackground from '@/components/ui/VideoBackground.vue'
92 93
93 // 导入接口 94 // 导入接口
94 import { smsAPI } from '@/api/common' 95 import { smsAPI } from '@/api/common'
...@@ -100,7 +101,7 @@ const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png' ...@@ -100,7 +101,7 @@ const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png'
100 // 路由相关 101 // 路由相关
101 const $route = useRoute() 102 const $route = useRoute()
102 const $router = useRouter() 103 const $router = useRouter()
103 -useTitle($route.meta.title) 104 +useTitle('登陆')
104 105
105 // TAG: 埋点 106 // TAG: 埋点
106 const { trackPageView, trackClick } = useTracking() 107 const { trackPageView, trackClick } = useTracking()
......