feat(welcome): 完成欢迎页功能入口组件与配置
- 新增 WelcomeContent 组件:实现轨道式布局,3个功能入口环绕排列 - 新增 WelcomeEntryItem 组件:统一的功能入口卡片样式 - 新增 welcomeEntries 配置:课程中心、活动中心(外链)、个人中心 - 添加环境变量:VITE_WELCOME_PAGE_ENABLED 和 VITE_WELCOME_VIDEO_URL - 支持外部链接跳转(活动中心)和内部路由跳转 - 添加浮动、脉冲等动画效果,提升视觉体验 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
6 changed files
with
369 additions
and
1 deletions
| ... | @@ -30,3 +30,7 @@ VITE_PROXY_TARGET = http://oa-dev.onwall.cn/ | ... | @@ -30,3 +30,7 @@ VITE_PROXY_TARGET = http://oa-dev.onwall.cn/ |
| 30 | 30 | ||
| 31 | # PC端地址 | 31 | # PC端地址 |
| 32 | VITE_MOBILE_URL = http://localhost:5173/ | 32 | VITE_MOBILE_URL = http://localhost:5173/ |
| 33 | + | ||
| 34 | +# 欢迎页配置 | ||
| 35 | +VITE_WELCOME_PAGE_ENABLED=true | ||
| 36 | +VITE_WELCOME_VIDEO_URL=https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4 | ... | ... |
| ... | @@ -17,3 +17,7 @@ VITE_PROXY_TARGET = http://oa.onwall.cn | ... | @@ -17,3 +17,7 @@ VITE_PROXY_TARGET = http://oa.onwall.cn |
| 17 | # PC端地址 | 17 | # PC端地址 |
| 18 | # VITE_MOBILE_URL = http://oa.onwall.cn/f/guanzong/web/ | 18 | # VITE_MOBILE_URL = http://oa.onwall.cn/f/guanzong/web/ |
| 19 | # VITE_MOBILE_URL = http://guanzong.onwall.cn/f/guanzong/web/ | 19 | # VITE_MOBILE_URL = http://guanzong.onwall.cn/f/guanzong/web/ |
| 20 | + | ||
| 21 | +# 欢迎页配置 | ||
| 22 | +VITE_WELCOME_PAGE_ENABLED=true | ||
| 23 | +VITE_WELCOME_VIDEO_URL=https://cdn.ipadbiz.cn/mlaj/video/welcome-bg.mp4 | ... | ... |
| ... | @@ -98,8 +98,10 @@ declare module 'vue' { | ... | @@ -98,8 +98,10 @@ declare module 'vue' { |
| 98 | VanTag: typeof import('vant/es')['Tag'] | 98 | VanTag: typeof import('vant/es')['Tag'] |
| 99 | VanTimePicker: typeof import('vant/es')['TimePicker'] | 99 | VanTimePicker: typeof import('vant/es')['TimePicker'] |
| 100 | VanUploader: typeof import('vant/es')['Uploader'] | 100 | VanUploader: typeof import('vant/es')['Uploader'] |
| 101 | - VideoBackground: typeof import('./components/media/VideoBackground.vue')['default'] | 101 | + VideoBackground: typeof import('./components/effects/VideoBackground.vue')['default'] |
| 102 | VideoPlayer: typeof import('./components/media/VideoPlayer.vue')['default'] | 102 | VideoPlayer: typeof import('./components/media/VideoPlayer.vue')['default'] |
| 103 | WechatPayment: typeof import('./components/payment/WechatPayment.vue')['default'] | 103 | WechatPayment: typeof import('./components/payment/WechatPayment.vue')['default'] |
| 104 | + WelcomeContent: typeof import('./components/welcome/WelcomeContent.vue')['default'] | ||
| 105 | + WelcomeEntryItem: typeof import('./components/welcome/WelcomeEntryItem.vue')['default'] | ||
| 104 | } | 106 | } |
| 105 | } | 107 | } | ... | ... |
src/components/welcome/WelcomeContent.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="welcome-content"> | ||
| 3 | + <!-- 顶部欢迎文案 --> | ||
| 4 | + <div class="welcome-header"> | ||
| 5 | + <h1 class="welcome-title">欢迎来到美乐爱觉</h1> | ||
| 6 | + <p class="welcome-subtitle">开启您的学习之旅</p> | ||
| 7 | + </div> | ||
| 8 | + | ||
| 9 | + <!-- 功能入口区域 - 轨道布局 --> | ||
| 10 | + <div class="entry-orbit"> | ||
| 11 | + <div class="orbit-center"> | ||
| 12 | + <!-- 中心装饰元素(可选) --> | ||
| 13 | + <div class="orbit-decoration"></div> | ||
| 14 | + </div> | ||
| 15 | + | ||
| 16 | + <!-- 3个功能入口 - 轨道布局 --> | ||
| 17 | + <div class="orbit-entries"> | ||
| 18 | + <WelcomeEntryItem | ||
| 19 | + v-for="entry in entries" | ||
| 20 | + :key="entry.id" | ||
| 21 | + :entry="entry" | ||
| 22 | + class="orbit-entry" | ||
| 23 | + :class="`orbit-entry-${entry.priority}`" | ||
| 24 | + @click="handleEntryClick" | ||
| 25 | + /> | ||
| 26 | + </div> | ||
| 27 | + </div> | ||
| 28 | + | ||
| 29 | + <!-- 底部开始按钮 --> | ||
| 30 | + <div class="welcome-actions"> | ||
| 31 | + <van-button | ||
| 32 | + type="primary" | ||
| 33 | + size="large" | ||
| 34 | + round | ||
| 35 | + block | ||
| 36 | + @click="handleStart" | ||
| 37 | + > | ||
| 38 | + 开始体验 | ||
| 39 | + </van-button> | ||
| 40 | + </div> | ||
| 41 | + </div> | ||
| 42 | +</template> | ||
| 43 | + | ||
| 44 | +<script setup> | ||
| 45 | +import { ref } from 'vue' | ||
| 46 | +import { useRouter } from 'vue-router' | ||
| 47 | +import { welcomeEntries } from '@/config/welcomeEntries' | ||
| 48 | +import WelcomeEntryItem from './WelcomeEntryItem.vue' | ||
| 49 | + | ||
| 50 | +const router = useRouter() | ||
| 51 | +const entries = ref(welcomeEntries) | ||
| 52 | + | ||
| 53 | +const handleEntryClick = (entry) => { | ||
| 54 | + if (entry.isExternal) { | ||
| 55 | + // 外部链接:获取用户ID并拼接 | ||
| 56 | + const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}') | ||
| 57 | + const userId = currentUser.id || currentUser.user_id || '' | ||
| 58 | + const url = entry.externalUrl + userId | ||
| 59 | + window.open(url, '_blank') | ||
| 60 | + } else { | ||
| 61 | + // 内部路由跳转 | ||
| 62 | + router.push(entry.route) | ||
| 63 | + } | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +const handleStart = () => { | ||
| 67 | + // 跳转到首页 | ||
| 68 | + router.push('/') | ||
| 69 | +} | ||
| 70 | +</script> | ||
| 71 | + | ||
| 72 | +<style lang="less" scoped> | ||
| 73 | +.welcome-content { | ||
| 74 | + position: relative; | ||
| 75 | + width: 100%; | ||
| 76 | + min-height: 100vh; | ||
| 77 | + display: flex; | ||
| 78 | + flex-direction: column; | ||
| 79 | + align-items: center; | ||
| 80 | + justify-content: space-between; | ||
| 81 | + padding: 6rem 2rem 3rem; | ||
| 82 | + z-index: 1; | ||
| 83 | +} | ||
| 84 | + | ||
| 85 | +.welcome-header { | ||
| 86 | + text-align: center; | ||
| 87 | + margin-top: 4rem; | ||
| 88 | + | ||
| 89 | + .welcome-title { | ||
| 90 | + font-size: 2rem; | ||
| 91 | + font-weight: bold; | ||
| 92 | + color: #ffffff; | ||
| 93 | + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); | ||
| 94 | + margin-bottom: 0.75rem; | ||
| 95 | + animation: fadeInDown 1s ease; | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + .welcome-subtitle { | ||
| 99 | + font-size: 1rem; | ||
| 100 | + color: rgba(255, 255, 255, 0.9); | ||
| 101 | + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); | ||
| 102 | + animation: fadeInDown 1s ease 0.2s both; | ||
| 103 | + } | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +.entry-orbit { | ||
| 107 | + position: relative; | ||
| 108 | + width: 100%; | ||
| 109 | + max-width: 20rem; | ||
| 110 | + aspect-ratio: 1; | ||
| 111 | + margin: 2rem 0; | ||
| 112 | + | ||
| 113 | + .orbit-center { | ||
| 114 | + position: absolute; | ||
| 115 | + top: 50%; | ||
| 116 | + left: 50%; | ||
| 117 | + transform: translate(-50%, -50%); | ||
| 118 | + width: 8rem; | ||
| 119 | + height: 8rem; | ||
| 120 | + border-radius: 50%; | ||
| 121 | + background: radial-gradient(circle, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.02)); | ||
| 122 | + backdrop-filter: blur(10px); | ||
| 123 | + animation: pulse 3s ease-in-out infinite; | ||
| 124 | + | ||
| 125 | + .orbit-decoration { | ||
| 126 | + width: 100%; | ||
| 127 | + height: 100%; | ||
| 128 | + border-radius: 50%; | ||
| 129 | + border: 1px solid rgba(255, 255, 255, 0.1); | ||
| 130 | + animation: rotate 20s linear infinite; | ||
| 131 | + } | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + .orbit-entries { | ||
| 135 | + position: relative; | ||
| 136 | + width: 100%; | ||
| 137 | + height: 100%; | ||
| 138 | + | ||
| 139 | + .orbit-entry { | ||
| 140 | + position: absolute; | ||
| 141 | + top: 50%; | ||
| 142 | + left: 50%; | ||
| 143 | + transform: translate(-50%, -50%); | ||
| 144 | + animation: float 3s ease-in-out infinite; | ||
| 145 | + | ||
| 146 | + &.orbit-entry-1 { | ||
| 147 | + // 课程中心 - 顶部 | ||
| 148 | + top: 0; | ||
| 149 | + left: 50%; | ||
| 150 | + animation-delay: 0s; | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + &.orbit-entry-2 { | ||
| 154 | + // 活动中心 - 右下 | ||
| 155 | + top: 75%; | ||
| 156 | + left: 85%; | ||
| 157 | + animation-delay: 0.5s; | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + &.orbit-entry-3 { | ||
| 161 | + // 个人中心 - 左下 | ||
| 162 | + top: 75%; | ||
| 163 | + left: 15%; | ||
| 164 | + animation-delay: 1s; | ||
| 165 | + } | ||
| 166 | + } | ||
| 167 | + } | ||
| 168 | +} | ||
| 169 | + | ||
| 170 | +.welcome-actions { | ||
| 171 | + width: 100%; | ||
| 172 | + max-width: 20rem; | ||
| 173 | + margin-bottom: 2rem; | ||
| 174 | + animation: fadeInUp 1s ease 0.4s both; | ||
| 175 | + | ||
| 176 | + :deep(.van-button) { | ||
| 177 | + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | ||
| 178 | + border: none; | ||
| 179 | + box-shadow: 0 4px 16px rgba(102, 126, 234, 0.4); | ||
| 180 | + height: 3rem; | ||
| 181 | + font-size: 1.1rem; | ||
| 182 | + font-weight: 500; | ||
| 183 | + } | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +// 动画定义 | ||
| 187 | +@keyframes fadeInDown { | ||
| 188 | + from { | ||
| 189 | + opacity: 0; | ||
| 190 | + transform: translateY(-20px); | ||
| 191 | + } | ||
| 192 | + to { | ||
| 193 | + opacity: 1; | ||
| 194 | + transform: translateY(0); | ||
| 195 | + } | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +@keyframes fadeInUp { | ||
| 199 | + from { | ||
| 200 | + opacity: 0; | ||
| 201 | + transform: translateY(20px); | ||
| 202 | + } | ||
| 203 | + to { | ||
| 204 | + opacity: 1; | ||
| 205 | + transform: translateY(0); | ||
| 206 | + } | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +@keyframes pulse { | ||
| 210 | + 0%, 100% { | ||
| 211 | + opacity: 0.5; | ||
| 212 | + transform: translate(-50%, -50%) scale(1); | ||
| 213 | + } | ||
| 214 | + 50% { | ||
| 215 | + opacity: 0.8; | ||
| 216 | + transform: translate(-50%, -50%) scale(1.05); | ||
| 217 | + } | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +@keyframes float { | ||
| 221 | + 0%, 100% { | ||
| 222 | + transform: translate(-50%, -50%) translateY(0); | ||
| 223 | + } | ||
| 224 | + 50% { | ||
| 225 | + transform: translate(-50%, -50%) translateY(-10px); | ||
| 226 | + } | ||
| 227 | +} | ||
| 228 | + | ||
| 229 | +@keyframes rotate { | ||
| 230 | + from { | ||
| 231 | + transform: rotate(0deg); | ||
| 232 | + } | ||
| 233 | + to { | ||
| 234 | + transform: rotate(360deg); | ||
| 235 | + } | ||
| 236 | +} | ||
| 237 | +</style> |
src/components/welcome/WelcomeEntryItem.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div | ||
| 3 | + class="entry-item" | ||
| 4 | + :class="{ 'is-external': entry.isExternal }" | ||
| 5 | + @click="handleClick" | ||
| 6 | + > | ||
| 7 | + <div class="entry-icon-wrapper"> | ||
| 8 | + <img | ||
| 9 | + :src="entry.icon" | ||
| 10 | + :alt="entry.title" | ||
| 11 | + class="entry-icon" | ||
| 12 | + /> | ||
| 13 | + </div> | ||
| 14 | + <div class="entry-title">{{ entry.title }}</div> | ||
| 15 | + </div> | ||
| 16 | +</template> | ||
| 17 | + | ||
| 18 | +<script setup> | ||
| 19 | +import { defineProps, defineEmits } from 'vue' | ||
| 20 | + | ||
| 21 | +const props = defineProps({ | ||
| 22 | + entry: { | ||
| 23 | + type: Object, | ||
| 24 | + required: true, | ||
| 25 | + validator: (value) => { | ||
| 26 | + return value.id && value.title && value.icon && value.route | ||
| 27 | + } | ||
| 28 | + } | ||
| 29 | +}) | ||
| 30 | + | ||
| 31 | +const emit = defineEmits(['click']) | ||
| 32 | + | ||
| 33 | +const handleClick = () => { | ||
| 34 | + emit('click', props.entry) | ||
| 35 | +} | ||
| 36 | +</script> | ||
| 37 | + | ||
| 38 | +<style lang="less" scoped> | ||
| 39 | +.entry-item { | ||
| 40 | + display: flex; | ||
| 41 | + flex-direction: column; | ||
| 42 | + align-items: center; | ||
| 43 | + justify-content: center; | ||
| 44 | + cursor: pointer; | ||
| 45 | + transition: all 0.3s ease; | ||
| 46 | + | ||
| 47 | + &:active { | ||
| 48 | + transform: scale(0.95); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + &.is-external { | ||
| 52 | + // 外部链接的特殊样式(如果需要) | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + .entry-icon-wrapper { | ||
| 56 | + position: relative; | ||
| 57 | + width: 6rem; | ||
| 58 | + height: 6rem; | ||
| 59 | + border-radius: 50%; | ||
| 60 | + background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.05)); | ||
| 61 | + backdrop-filter: blur(10px); | ||
| 62 | + display: flex; | ||
| 63 | + align-items: center; | ||
| 64 | + justify-content: center; | ||
| 65 | + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); | ||
| 66 | + margin-bottom: 0.75rem; | ||
| 67 | + | ||
| 68 | + .entry-icon { | ||
| 69 | + width: 3rem; | ||
| 70 | + height: 3rem; | ||
| 71 | + object-fit: contain; | ||
| 72 | + filter: brightness(0) invert(1); // 图标反白显示 | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + .entry-title { | ||
| 77 | + font-size: 1rem; | ||
| 78 | + font-weight: 500; | ||
| 79 | + color: #ffffff; | ||
| 80 | + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); | ||
| 81 | + white-space: nowrap; | ||
| 82 | + } | ||
| 83 | +} | ||
| 84 | +</style> |
src/config/welcomeEntries.js
0 → 100644
| 1 | +/* | ||
| 2 | + * @Date: 2025-01-28 12:00:00 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-01-28 12:00:00 | ||
| 5 | + * @FilePath: /mlaj/src/config/welcomeEntries.js | ||
| 6 | + * @Description: 欢迎页功能入口配置 | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 欢迎页功能入口列表 | ||
| 11 | + * 用于展示在欢迎页的3个主要功能入口 | ||
| 12 | + */ | ||
| 13 | +export const welcomeEntries = [ | ||
| 14 | + { | ||
| 15 | + id: 'courses', | ||
| 16 | + title: '课程中心', | ||
| 17 | + icon: 'https://cdn.ipadbiz.cn/mlaj/images/welcome_btn_1.png', | ||
| 18 | + route: '/courses', | ||
| 19 | + priority: 1 | ||
| 20 | + }, | ||
| 21 | + { | ||
| 22 | + id: 'activity', | ||
| 23 | + title: '活动中心', | ||
| 24 | + icon: 'https://cdn.ipadbiz.cn/mlaj/images/welcome_btn_1.png', | ||
| 25 | + route: '/activity', | ||
| 26 | + priority: 2, | ||
| 27 | + isExternal: true, | ||
| 28 | + externalUrl: 'https://wxm.behalo.cc/pages/activity/activity?token=&user_id=' | ||
| 29 | + }, | ||
| 30 | + { | ||
| 31 | + id: 'profile', | ||
| 32 | + title: '个人中心', | ||
| 33 | + icon: 'https://cdn.ipadbiz.cn/mlaj/images/welcome_btn_1.png', | ||
| 34 | + route: '/profile', | ||
| 35 | + priority: 3 | ||
| 36 | + } | ||
| 37 | +] |
-
Please register or login to post a comment