欢迎页功能 - 详细实现计划
项目概述
为 mlaj 平台开发一个欢迎页,作为用户首次进入时的引导页面,展示核心功能入口。
核心功能:
- 视频背景循环播放(星空宇宙主题)
- 悬浮的功能入口图标 + 文字介绍
- 持续循环的缩放位移动效
- 首次进入检测,非首次直接跳转主页
- 通用七牛云上传工具
目录结构
mlaj/
├── src/
│ ├── components/
│ │ ├── effects/
│ │ │ └── VideoBackground.vue # 视频背景组件
│ │ └── welcome/
│ │ ├── WelcomeContent.vue # 内容容器组件
│ │ └── WelcomeEntryItem.vue # 功能入口项组件
│ ├── config/
│ │ └── welcomeEntries.js # 功能入口配置
│ ├── router/
│ │ ├── routes.js # 新增欢迎页路由
│ │ └── guards.js # 修改路由守卫
│ └── views/
│ └── welcome/
│ └── WelcomePage.vue # 欢迎页主视图
├── scripts/
│ ├── upload-to-qiniu.sh # 七牛云上传工具
│ └── qiniu/
│ ├── account.json # 账户信息(不入库)
│ ├── templates/ # 配置模板
│ │ ├── video-upload.conf.template
│ │ └── image-upload.conf.template
│ └── configs/ # 实际配置(不入库)
│ └── welcome-video.conf
└── docs/
└── plan/
└── 26.1.28-欢迎页开发计划/
├── plan.md # 本文档
└── brainstorm.md # 头脑风暴记录
开发步骤
第 0 阶段: 准备工作 (优先级: 🔴 高)
目标: 准备开发环境和资源
步骤 1: 创建配置文件模板
创建 scripts/qiniu/templates/video-upload.conf.template:
{
"src_dir": "./assets/video",
"bucket": "ipadbiz",
"key_prefix": "mlaj/video/",
"ignore_dir": false,
"overwrite": true,
"check_exists": true,
"check_hash": true,
"rescan_local": true,
"skip_file_prefixes": ".",
"skip_suffixes": ".DS_Store",
"up_host": "https://upload.qiniup.com",
"file_type": 0
}
创建 scripts/qiniu/templates/image-upload.conf.template:
{
"src_dir": "./assets/images",
"bucket": "ipadbiz",
"key_prefix": "mlaj/images/",
"ignore_dir": false,
"overwrite": true,
"check_exists": true,
"check_hash": true,
"rescan_local": true,
"skip_file_prefixes": ".",
"skip_suffixes": ".DS_Store",
"up_host": "https://upload.qiniup.com",
"file_type": 0
}
步骤 2: 更新 .gitignore
# 七牛账户信息(敏感)
scripts/qiniu/account.json
# 实际配置文件(可能包含路径信息)
scripts/qiniu/configs/
步骤 3: 准备视频资源
视频文件: docs/plan/26.1.28-欢迎页开发计划/video/welcome-bg.mp4 ✅ 已添加
建议规格:
- 分辨率: 1920x1080 (1080p)
- 编码格式: H.264
- 时长: 10-20秒循环视频
- 文件大小: < 10MB
步骤 4: 上传资源到七牛云
# 初始化七牛账户
chmod +x scripts/upload-to-qiniu.sh
./scripts/upload-to-qiniu.sh init
# 上传欢迎页背景视频
./scripts/upload-to-qiniu.sh video/welcome-bg.mp4 mlaj/video/welcome-bg.mp4
上传成功后的 URL: ✅ 已上传
视频: https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4
封面: https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4?vframe/jpg/offset/0.001
文件信息:
- 大小: 17.57MB (18420585 字节)
- Hash: lpipKorSMZBEVa-eCevwvcqkB8ZH
- 上传时间: 1.23s
步骤 5: 更新环境变量
.env.development:
# 欢迎页功能开关
VITE_WELCOME_PAGE_ENABLED=true
# 视频资源 URL
# 封面图会自动通过七牛云处理参数生成: videoUrl + ?vframe/jpg/offset/0.001
VITE_WELCOME_VIDEO_URL=https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4
.env.production:
VITE_WELCOME_PAGE_ENABLED=true
VITE_WELCOME_VIDEO_URL=https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4
说明:
- 只需配置视频 URL,封面图会自动从视频中提取第一帧生成
- 七牛云处理参数:
?vframe/jpg/offset/0.001表示从视频第 0.001 秒截取一帧作为 JPG 图片 - 降级方案: 视频加载失败时自动使用该封面图作为静态背景
第 1 阶段: 通用上传工具开发 (优先级: 🔴 高)
目标: 实现可复用的七牛云上传工具
步骤 1: 创建上传脚本
文件: scripts/upload-to-qiniu.sh
#!/usr/bin/env bash
# 通用七牛云上传工具 - mlaj 项目
# 用法: ./scripts/upload-to-qiniu.sh <local_file> <remote_path>
set -euo pipefail
# 项目根目录
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
# 七牛配置
QINIU_BUCKET="${QINIU_BUCKET:-ipadbiz}"
QINIU_CONFIG_DIR="$repo_root/scripts/qiniu"
QINIU_ACCOUNT_CONF="account.json"
# 代理设置(可选)
USE_PROXY=${USE_PROXY:-false}
PROXY_HOST=${PROXY_HOST:-"127.0.0.1:7890"}
log_info() {
echo "[qiniu-upload] $1"
}
# 初始化 qshell 账户
init_account() {
if ! command -v qshell >/dev/null 2>&1; then
echo "错误: 未检测到 qshell,请先安装 https://developer.qiniu.com/kodo/tools/1302/qshell"
exit 1
fi
if [ ! -f "$QINIU_CONFIG_DIR/$QINIU_ACCOUNT_CONF" ]; then
log_info "首次使用,请输入七牛云账号信息:"
read -p "Access Key: " AK
read -p "Secret Key: " SK
mkdir -p "$QINIU_CONFIG_DIR"
qshell account "$AK" "$SK" > "$QINIU_CONFIG_DIR/$QINIU_ACCOUNT_CONF"
log_info "账户信息已保存到 $QINIU_CONFIG_DIR/$QINIU_ACCOUNT_CONF"
fi
}
# 单文件上传
upload_single_file() {
local local_file="$1"
local remote_path="$2"
if [ ! -f "$local_file" ]; then
echo "错误: 文件不存在 $local_file"
exit 1
fi
# 获取文件目录和文件名
local file_dir=$(cd "$(dirname "$local_file")" && pwd)
local file_name=$(basename "$local_file")
# 创建临时配置
local temp_conf="$QINIU_CONFIG_DIR/temp_upload_$(date +%s).conf"
cat > "$temp_conf" << EOF
{
"src_dir": "$file_dir",
"bucket": "$QINIU_BUCKET",
"key_prefix": "$(dirname "$remote_path")/",
"ignore_dir": false,
"overwrite": true,
"check_exists": true,
"check_hash": true,
"rescan_local": false,
"skip_file_prefixes": ".",
"skip_suffixes": ".DS_Store",
"up_host": "https://upload.qiniup.com",
"file_type": 0,
"file_list": [
"$file_name"
]
}
EOF
execute_upload "$temp_conf"
# 清理临时配置
rm -f "$temp_conf"
log_info "✅ 上传成功: https://cdn.ipadbiz.cn/$remote_path"
}
# 批量上传(使用配置文件)
upload_batch() {
local config_file="$1"
if [ ! -f "$config_file" ]; then
echo "错误: 配置文件不存在 $config_file"
exit 1
fi
execute_upload "$config_file"
log_info "✅ 批量上传完成"
}
# 执行上传(统一处理代理)
execute_upload() {
local config_file="$1"
if [ "$USE_PROXY" = "true" ]; then
export HTTP_PROXY="http://$PROXY_HOST"
export HTTPS_PROXY="http://$PROXY_HOST"
log_info "使用代理: $PROXY_HOST"
fi
qshell qupload "$config_file"
}
# 显示帮助信息
show_help() {
cat << EOF
通用七牛云上传工具
用法:
$0 <local_file> <remote_path> 单文件上传
$0 <config_file> 批量上传(指定配置文件)
$0 init 初始化七牛账户
$0 help 显示此帮助信息
参数说明:
local_file 本地文件路径(相对或绝对路径)
remote_path 远程路径,如: mlaj/video/bg.mp4
config_file 配置文件路径
环境变量:
QINIU_BUCKET 七牛空间名(默认: ipadbiz)
USE_PROXY=true 启用代理
PROXY_HOST=127.0.0.1:7890 代理地址
示例:
# 单文件上传
$0 ./assets/video/bg.mp4 mlaj/video/welcome-bg.mp4
# 批量上传
$0 scripts/qiniu/configs/welcome-video.conf
# 使用代理上传
USE_PROXY=true $0 ./local/file.mp4 mlaj/video/file.mp4
配置文件格式:
{
"src_dir": "./assets/video",
"bucket": "ipadbiz",
"key_prefix": "mlaj/video/",
"overwrite": true,
"check_exists": true
}
EOF
}
# 主逻辑
main() {
init_account
case "${1:-}" in
init)
log_info "账户初始化完成"
;;
help|--help|-h)
show_help
;;
"")
show_help
exit 1
;;
*)
if [ $# -eq 1 ]; then
# 单个参数,视为配置文件
upload_batch "$1"
elif [ $# -eq 2 ]; then
# 两个参数,单文件上传
upload_single_file "$1" "$2"
else
echo "错误: 参数数量不正确"
show_help
exit 1
fi
;;
esac
}
main "$@"
步骤 2: 添加 npm scripts
package.json:
{
"scripts": {
"upload:qiniu": "bash scripts/upload-to-qiniu.sh",
"qiniu:init": "bash scripts/upload-to-qiniu.sh init"
}
}
步骤 3: 测试上传工具
# 赋予执行权限
chmod +x scripts/upload-to-qiniu.sh
# 初始化账户
pnpm run qiniu:init
# 测试单文件上传
pnpm run upload:qiniu ./test/file.mp4 mlaj/video/test.mp4
# 测试代理上传(如果需要)
USE_PROXY=true pnpm run upload:qiniu ./test/file.mp4 mlaj/video/test.mp4
第 2 阶段: VideoBackground 组件 (优先级: 🔴 高)
目标: 实现视频背景组件
步骤 1: 创建组件文件
文件: src/components/effects/VideoBackground.vue
<template>
<div class="video-background">
<!-- Loading 状态 -->
<div v-if="isLoading" class="video-loading">
<van-loading size="24px">加载中...</van-loading>
</div>
<!-- 视频元素 -->
<video
ref="videoRef"
class="video-element"
:src="videoSrc"
:poster="posterUrl"
:autoplay="autoplay"
:loop="loop"
:muted="muted"
:webkit-playsinline="true"
:playsinline="true"
x5-video-player-type="h5"
x5-video-player-fullscreen="true"
@canplay="onCanPlay"
@error="onError"
></video>
<!-- 降级背景图 -->
<div
v-if="showFallback"
class="video-fallback"
:style="{ backgroundImage: `url(${posterUrl})` }"
></div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const props = defineProps({
/** 视频源 URL */
videoSrc: {
type: String,
required: true
},
/** 封面图 URL (可选,不传则自动从视频生成) */
poster: {
type: String,
default: ''
},
/** 是否自动播放 */
autoplay: {
type: Boolean,
default: true
},
/** 是否循环播放 */
loop: {
type: Boolean,
default: true
},
/** 是否静音 */
muted: {
type: Boolean,
default: true
},
/** 视频填充模式 */
objectFit: {
type: String,
default: 'cover' // cover, contain, fill
}
})
const videoRef = ref(null)
const isLoading = ref(true)
const showFallback = ref(false)
/**
* 自动生成封面图 URL
* 如果没有传入 poster,使用七牛云视频处理参数从视频中提取第一帧
* 处理参数: ?vframe/jpg/offset/0.001 表示从视频第 0.001 秒截取一帧作为 JPG
*/
const posterUrl = computed(() => {
if (props.poster) {
return props.poster
}
// 从视频 URL 自动生成封面图
// 七牛云视频处理: https://developer.qiniu.com/dora/1316/video-frame-operation
return `${props.videoSrc}?vframe/jpg/offset/0.001`
})
// 视频可以播放时
const onCanPlay = () => {
isLoading.value = false
// 尝试自动播放
if (props.autoplay && videoRef.value) {
videoRef.value.play().catch(err => {
console.warn('[VideoBackground] 自动播放失败:', err)
// iOS Safari 可能需要用户交互才能播放
showFallback.value = true
})
}
}
// 视频加载错误
const onError = (e) => {
console.error('[VideoBackground] 视频加载失败:', e)
isLoading.value = false
showFallback.value = true
}
// 手动播放(用于处理需要用户交互的情况)
const play = () => {
if (videoRef.value) {
videoRef.value.play().catch(err => {
console.warn('[VideoBackground] 播放失败:', err)
})
}
}
// 暂停播放
const pause = () => {
if (videoRef.value) {
videoRef.value.pause()
}
}
onMounted(() => {
// 预加载视频
if (videoRef.value) {
videoRef.value.load()
}
})
onUnmounted(() => {
// 清理资源
if (videoRef.value) {
videoRef.value.pause()
videoRef.value.src = ''
}
})
defineExpose({
play,
pause
})
</script>
<style scoped>
.video-background {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -1;
overflow: hidden;
}
.video-element {
width: 100%;
height: 100%;
object-fit: v-bind(objectFit);
background-color: #000;
}
.video-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
}
.video-fallback {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
z-index: -1;
}
</style>
步骤 2: 组件使用示例
<template>
<VideoBackground
:video-src="videoUrl"
autoplay
loop
muted
/>
</template>
<script setup>
import VideoBackground from '@/components/effects/VideoBackground.vue'
const videoUrl = 'https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4'
// poster 会自动生成: videoUrl + ?vframe/jpg/offset/0.001
// 即: https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4?vframe/jpg/offset/0.001
</script>
第 3 阶段: 路由与首次访问逻辑 (优先级: 🔴 高)
目标: 实现路由守卫和首次访问检测
步骤 1: 创建欢迎页视图
文件: src/views/welcome/WelcomePage.vue
<template>
<div class="welcome-page">
<!-- 视频背景 -->
<VideoBackground
v-if="videoUrl"
:video-src="videoUrl"
/>
<!-- 内容区域 -->
<WelcomeContent class="welcome-content" />
</div>
</template>
<script setup>
import { computed } from 'vue'
import VideoBackground from '@/components/effects/VideoBackground.vue'
import WelcomeContent from '@/components/welcome/WelcomeContent.vue'
const videoUrl = computed(() => {
return import.meta.env.VITE_WELCOME_VIDEO_URL || ''
})
// poster 会自动从 videoUrl 生成,无需单独配置
</script>
<style scoped>
.welcome-page {
position: relative;
width: 100vw;
min-height: 100vh;
overflow: hidden;
}
.welcome-content {
position: relative;
z-index: 1;
}
</style>
步骤 2: 添加路由配置
文件: src/router/routes.js
// 在路由数组中添加
{
path: '/welcome',
name: 'Welcome',
component: () => import('@/views/welcome/WelcomePage.vue'),
meta: {
requiresAuth: false,
hideInMenu: true
}
}
步骤 3: 实现首次访问检测
文件: src/router/guards.js
// 首次访问标志
const HAS_VISITED_WELCOME = 'has_visited_welcome'
const WELCOME_VISITED_AT = 'welcome_visited_at'
/**
* 检查用户是否已访问过欢迎页
*/
export function hasVisitedWelcome() {
return localStorage.getItem(HAS_VISITED_WELCOME) === 'true'
}
/**
* 标记用户已访问欢迎页
*/
export function markWelcomeVisited() {
localStorage.setItem(HAS_VISITED_WELCOME, 'true')
localStorage.setItem(WELCOME_VISITED_AT, Date.now().toString())
}
/**
* 重置欢迎页标志(用于调试)
*/
export function resetWelcomeFlag() {
localStorage.removeItem(HAS_VISITED_WELCOME)
localStorage.removeItem(WELCOME_VISITED_AT)
}
// 在路由守卫中添加首次访问检测
router.beforeEach((to, from, next) => {
// 欢迎页功能开关
if (import.meta.env.VITE_WELCOME_PAGE_ENABLED !== 'true') {
return next()
}
// 重置欢迎页标志(URL 参数)
if (to.query.reset_welcome === 'true') {
resetWelcomeFlag()
// 移除 URL 参数
const query = { ...to.query }
delete query.reset_welcome
return next({ path: to.path, query })
}
// 首次访问检测
if (to.path !== '/welcome' && !hasVisitedWelcome()) {
markWelcomeVisited()
return next({
path: '/welcome',
query: { redirect: to.fullPath }
})
}
next()
})
步骤 4: 添加调试工具
文件: src/main.js (或 App.vue)
// 开发环境添加调试工具
if (import.meta.env.DEV) {
window.resetWelcomeFlag = () => {
localStorage.removeItem('has_visited_welcome')
localStorage.removeItem('welcome_visited_at')
console.log('✅ 欢迎页标志已重置,请刷新页面查看欢迎页')
}
window.showWelcome = () => {
window.location.href = '/welcome'
}
console.log('🔧 开发工具:')
console.log(' - window.resetWelcomeFlag() 重置欢迎页标志')
console.log(' - window.showWelcome() 跳转到欢迎页')
}
第 4 阶段: WelcomeContent 组件 (优先级: 🟡 中)
目标: 实现内容悬浮层
步骤 1: 创建功能入口配置
文件: src/config/welcomeEntries.js
/**
* 欢迎页功能入口配置
* 对应项目底部Tab的四个主要功能入口
*/
export const welcomeEntries = [
{
id: 'courses',
title: '课程中心',
subtitle: '探索精选课程',
icon: '📚',
route: '/courses',
color: '#4CAF50',
priority: 1
},
{
id: 'activity',
title: '活动中心',
subtitle: '精彩活动不容错过',
icon: '🎉',
route: '/activity',
color: '#FF9800',
priority: 2
},
{
id: 'recall',
title: '时光机',
subtitle: '回顾学习历程',
icon: '⏰',
route: '/recall/login',
color: '#9C27B0',
priority: 3
},
{
id: 'profile',
title: '个人中心',
subtitle: '管理您的账户',
icon: '👤',
route: '/profile',
color: '#2196F3',
priority: 4
}
]
/**
* 根据优先级排序
*/
export function getSortedEntries() {
return [...welcomeEntries].sort((a, b) => a.priority - b.priority)
}
功能入口说明:
-
课程中心 (
/courses)- 对应底部Tab第二个入口
- 浏览和购买课程
- 查看学习进度
-
活动中心 (
/activity)- 对应底部Tab第三个入口
- 查看最新活动信息
- 活动报名和参与
-
时光机 (
/recall/login)- 回顾功能和历史记录
- 用户成长历程展示
-
个人中心 (
/profile)- 对应底部Tab第四个入口
- 个人资料管理
- 学习记录和设置
步骤 2: 创建功能入口项组件
文件: src/components/welcome/WelcomeEntryItem.vue
<template>
<div
class="entry-item"
:style="{ '--index': index }"
@click="handleClick"
>
<!-- 图标 -->
<div class="entry-icon" :style="{ color: entry.color }">
{{ entry.icon }}
</div>
<!-- 标题 -->
<div class="entry-title">
{{ entry.title }}
</div>
<!-- 副标题(可选) -->
<div v-if="entry.subtitle" class="entry-subtitle">
{{ entry.subtitle }}
</div>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
const props = defineProps({
entry: {
type: Object,
required: true
},
index: {
type: Number,
required: true
}
})
const router = useRouter()
const handleClick = () => {
const redirect = new URLSearchParams(window.location.search).get('redirect')
if (props.entry.route) {
router.push(props.entry.route)
} else if (redirect) {
router.push(redirect)
} else {
router.push('/')
}
}
</script>
<style scoped lang="less">
.entry-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.5rem;
cursor: pointer;
user-select: none;
transition: all 0.3s ease;
// 呼吸动画
animation: breathe 2.5s ease-in-out infinite;
animation-delay: calc(var(--index) * 0.3s);
&:hover {
animation-duration: 1s;
transform: scale(1.1);
.entry-icon {
transform: scale(1.2);
}
}
&:active {
transform: scale(0.95);
}
}
@keyframes breathe {
0%, 100% {
transform: scale(1);
opacity: 0.9;
}
50% {
transform: scale(1.08);
opacity: 1;
}
}
.entry-icon {
font-size: 3rem;
margin-bottom: 0.75rem;
transition: transform 0.3s ease;
}
.entry-title {
font-size: 1.125rem;
font-weight: 600;
color: #fff;
text-align: center;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
margin-bottom: 0.25rem;
}
.entry-subtitle {
font-size: 0.875rem;
color: rgba(255, 255, 255, 0.8);
text-align: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
</style>
步骤 3: 创建内容容器组件
文件: src/components/welcome/WelcomeContent.vue
<template>
<div class="welcome-content">
<!-- [待定] Logo/标题区 -->
<!-- 功能入口网格 -->
<div class="entries-grid">
<WelcomeEntryItem
v-for="(entry, index) in sortedEntries"
:key="entry.id"
:entry="entry"
:index="index"
/>
</div>
<!-- [待定] CTA 按钮区 -->
</div>
</template>
<script setup>
import { computed } from 'vue'
import { getSortedEntries } from '@/config/welcomeEntries'
import WelcomeEntryItem from './WelcomeEntryItem.vue'
const sortedEntries = computed(() => getSortedEntries())
</script>
<style scoped lang="less">
.welcome-content {
position: relative;
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.entries-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
width: 100%;
max-width: 600px;
@media (min-width: 768px) {
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
max-width: 900px;
}
}
</style>
第 5 阶段: 测试与优化 (优先级: 🟡 中)
功能测试清单
- 首次访问正确跳转到欢迎页
- 再次访问直接进入主页
-
?reset_welcome=true正确重置标志 - 点击功能入口正确跳转
- 视频背景正常循环播放
- 视频加载失败时显示降级方案
- 清除 localStorage 后重新访问
兼容性测试
- iOS Safari (video autoplay, playsinline)
- Android Chrome
- 微信内置浏览器
- PC 端浏览器 (Chrome, Firefox, Safari, Edge)
性能测试
- 视频首次加载时间 < 3s
- 动画帧率 > 50fps
- 内存占用正常
- CPU 占用正常
边界测试
- 视频加载失败时降级为静态图
- 网络慢速时的表现
- 清除缓存后的行为
- URL 参数异常处理
第 6 阶段: 文档与部署 (优先级: 🟢 低)
步骤 1: 更新项目文档
CLAUDE.md 添加说明:
### 欢迎页
- **功能**: 用户首次进入时的引导页面,展示核心功能入口
- **路由**: `/welcome`
- **开关**: `VITE_WELCOME_PAGE_ENABLED=true`
- **首次检测**: 使用 localStorage 标志 `has_visited_welcome`
- **调试方法**:
- URL 参数 `?reset_welcome=true` 重置标志
- 控制台 `window.resetWelcomeFlag()` 重置标志
- 控制台 `window.showWelcome()` 跳转欢迎页
步骤 2: 创建使用说明
文件: docs/welcome-page-guide.md
# 欢迎页使用指南
## 功能说明
欢迎页是用户首次进入平台时的引导页面,展示核心功能入口。
## 开发配置
### 环境变量
```bash
# 启用欢迎页
VITE_WELCOME_PAGE_ENABLED=true
# 视频资源
VITE_WELCOME_VIDEO_URL=https://cdn.ipadbiz.cn/mlaj/video/welcome-background.mp4
VITE_WELCOME_VIDEO_POSTER=https://cdn.ipadbiz.cn/mlaj/images/welcome-poster.jpg
功能入口配置
编辑 src/config/welcomeEntries.js:
export const welcomeEntries = [
{
id: 'courses',
title: '课程中心',
subtitle: '探索精选课程',
icon: '📚',
route: '/courses',
color: '#4CAF50',
priority: 1
}
// ... 更多入口
]
调试方法
重置欢迎页标志
方法 1: URL 参数
http://localhost:5173/?reset_welcome=true
方法 2: 控制台
window.resetWelcomeFlag()
location.reload()
直接访问欢迎页
window.showWelcome()
上传新的背景视频
使用上传工具
当前视频文件: docs/plan/26.1.28-欢迎页开发计划/video/welcome-bg.mp4
# 上传欢迎页背景视频
pnpm run upload:qiniu video/welcome-bg.mp4 mlaj/video/welcome-bg.mp4
# 使用代理上传
USE_PROXY=true pnpm run upload:qiniu video/welcome-bg.mp4 mlaj/video/welcome-bg.mp4
视频规格建议
- 分辨率: 1920x1080 (1080p)
- 编码格式: H.264
- 时长: 10-20秒循环
- 文件大小: < 10MB
步骤 3: 部署到开发环境
# 构建
pnpm build
# 部署到开发服务器
pnpm dev_upload
步骤 4: 测试生产环境
- 验证视频 CDN 加载速度
- 验证首次访问逻辑
- 验证兼容性
- 收集用户反馈
技术要点
1. 视频播放兼容性
<video
autoplay
loop
muted
playsinline
webkit-playsinline
x5-video-player-type="h5"
x5-video-player-fullscreen="true"
>
关键属性说明:
-
muted: 静音播放(移动端自动播放必需) -
playsinline: iOS 内联播放 -
webkit-playsinline: iOS Safari 兼容 -
x5-video-player-type: 腾讯 X5 内核(微信/QQ浏览器)
2. 动画性能优化
// 使用 CSS 动画而非 JS 动画
animation: breathe 2.5s ease-in-out infinite;
// 使用 transform 而非 width/height
transform: scale(1.08);
// 使用 GPU 加速
will-change: transform, opacity;
3. 七牛云视频处理 - 自动生成封面图
封面图自动生成:
// VideoBackground 组件内部实现
const posterUrl = computed(() => {
if (props.poster) {
return props.poster
}
// 从视频 URL 自动生成封面图
return `${props.videoSrc}?vframe/jpg/offset/0.001`
})
七牛云视频帧处理参数说明:
-
?vframe/jpg/offset/0.001- 从视频第 0.001 秒截取一帧作为 JPG 图片 - 完整示例:
https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4?vframe/jpg/offset/0.001 - 官方文档: https://developer.qiniu.com/dora/1316/video-frame-operation
高级参数 (可选):
// 指定截取时间点 (第 1 秒)
https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4?vframe/jpg/offset/1
// 指定输出尺寸 (1920x1080)
https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4?vframe/jpg/offset/0.001/w/1920/h/1080
// 指定图片质量 (质量 85)
https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4?vframe/jpg/offset/0.001/quality/85
// 组合使用
https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4?vframe/jpg/offset/0.001/w/1920/h/1080/quality/85
优势:
- ✅ 只需上传一个视频文件,无需单独准备封面图
- ✅ 封面图与视频内容一致,不会出现不匹配
- ✅ 减少资源管理成本
- ✅ 支持动态调整截取时间和图片质量
4. 降级方案
<!-- 视频加载失败时显示静态背景图 -->
<div
v-if="showFallback"
class="video-fallback"
:style="{ backgroundImage: `url(${posterUrl})` }"
></div>
降级触发条件:
- 视频加载失败 (网络错误、格式不支持)
- 视频自动播放被阻止 (iOS Safari 政策)
- 视频解码失败
降级方案:
- 使用自动生成的封面图作为静态背景
- 保证用户始终能看到背景内容
风险与注意事项
技术风险
-
视频播放兼容性
- iOS Safari 可能禁止自动播放
- 解决方案: 添加
muted属性,提供降级方案
-
首次访问标志位
- 清除 localStorage 后会再次显示
- 解决方案: 文档说明,未来升级为后端接口
-
视频加载性能
- 文件过大导致加载缓慢
- 解决方案:
- 限制文件大小 < 10MB
- 使用七牛云自动生成封面图作为 loading 状态
- 支持封面图质量参数优化加载速度
- 考虑使用 CDN 加速
-
七牛云代理问题
- 需要挂代理才能访问
- 解决方案: 支持
USE_PROXY环境变量
业务风险
-
功能入口待确认
- 先实现框架,入口配置化
-
动效性能影响
- 使用 CSS 动画,提供环境变量控制
优先级总结
第 1 批 (核心功能) 🔴
- ✅ 第 0 阶段: 准备工作
- ✅ 第 1 阶段: 通用上传工具
- ✅ 第 2 阶段: VideoBackground 组件
- ✅ 第 3 阶段: 路由与首次访问逻辑
第 2 批 (功能完善) 🟡
- ⏳ 第 4 阶段: WelcomeContent 组件
- ⏳ 第 5 阶段: 测试与优化
第 3 批 (收尾工作) 🟢
- ⏳ 第 6 阶段: 文档与部署
待确认事项
- ✅ 背景视频文件 -
video/welcome-bg.mp4已添加并上传到七牛云 - ❌ 页面效果图 - 需要设计稿确认布局
- ✅ 功能入口列表 - 已确定(课程、活动、时光机、我的)
- ❌ 页面布局细节 - 顶部/底部是否需要元素(Logo、标语、按钮等)
建议: 先完成技术框架和上传工具,等设计稿确认后再填充内容。
文档创建时间: 2026-01-28 最后更新: 2026-01-28