428 lines
9.3 KiB
Vue
428 lines
9.3 KiB
Vue
<template>
|
||
<div class="loading-view">
|
||
<!-- 背景渐变 -->
|
||
<div class="loading-background"></div>
|
||
|
||
<!-- 加载内容 -->
|
||
<div class="loading-content">
|
||
<!-- Logo或品牌标识 -->
|
||
<div class="logo-container">
|
||
<div class="logo">
|
||
<div class="logo-icon">📱</div>
|
||
<h1 class="logo-text">Likei</h1>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 加载动画 -->
|
||
<div class="loading-animation">
|
||
<div class="spinner">
|
||
<div class="spinner-ring"></div>
|
||
<div class="spinner-ring"></div>
|
||
<div class="spinner-ring"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 加载文本 -->
|
||
<div class="loading-text">
|
||
<p class="main-text">{{ loadingText }}</p>
|
||
<p class="sub-text">{{ subText }}</p>
|
||
</div>
|
||
|
||
<!-- 进度条 -->
|
||
<div class="progress-container">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" :style="{ width: progress + '%' }"></div>
|
||
</div>
|
||
<span class="progress-text">{{ Math.round(progress) }}%</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 底部提示 -->
|
||
<div class="loading-footer">
|
||
<p>Powered by Likei Team</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { isInApp, connectApplication, parseAccessOrigin, parseHeader, setHttpHeaders } from '../utils/appBridge.js'
|
||
import { appConnectionManager } from '../utils/appConnectionManager.js'
|
||
import { getTeamEntry } from '../api/wallet.js'
|
||
import { setUserInfo } from '../utils/userStore.js'
|
||
|
||
const router = useRouter()
|
||
|
||
// 响应式数据
|
||
const loadingText = ref('Initializing...')
|
||
const subText = ref('Setting up your experience')
|
||
const progress = ref(0)
|
||
|
||
// 加载步骤配置
|
||
const loadingSteps = [
|
||
{ text: 'Connecting to server...', subText: 'Establishing secure connection', duration: 800 },
|
||
{ text: 'Loading user data...', subText: 'Retrieving your information', duration: 1000 },
|
||
{ text: 'Setting up interface...', subText: 'Preparing your workspace', duration: 600 },
|
||
{ text: 'Almost ready...', subText: 'Finalizing setup', duration: 400 }
|
||
]
|
||
|
||
let currentStepIndex = 0
|
||
let progressInterval = null
|
||
let stepTimeout = null
|
||
|
||
// 开始加载流程
|
||
const startLoading = async () => {
|
||
try {
|
||
// 如果在APP环境中,进行连接
|
||
if (isInApp()) {
|
||
await connectToApp()
|
||
}
|
||
|
||
// 开始进度动画
|
||
startProgressAnimation()
|
||
|
||
// 等待加载完成
|
||
await waitForLoadingComplete()
|
||
|
||
// 跳转到目标页面
|
||
await navigateToTargetPage()
|
||
|
||
} catch (error) {
|
||
console.error('Loading failed:', error)
|
||
// 出错时跳转到错误页面
|
||
router.replace('/not_app')
|
||
}
|
||
}
|
||
|
||
// APP连接
|
||
const connectToApp = async () => {
|
||
return new Promise((resolve, reject) => {
|
||
connectApplication(async (access) => {
|
||
try {
|
||
const result = parseAccessOrigin(access)
|
||
|
||
if (result.success) {
|
||
const headerInfo = parseHeader(result.data)
|
||
await setHttpHeaders(headerInfo)
|
||
appConnectionManager.setConnected(headerInfo)
|
||
resolve()
|
||
} else {
|
||
reject(new Error(result.error))
|
||
}
|
||
} catch (error) {
|
||
reject(error)
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
// 开始进度动画
|
||
const startProgressAnimation = () => {
|
||
let currentProgress = 0
|
||
const totalDuration = loadingSteps.reduce((sum, step) => sum + step.duration, 0)
|
||
const progressIncrement = 100 / totalDuration * 50 // 每50ms增加的进度
|
||
|
||
progressInterval = setInterval(() => {
|
||
currentProgress += progressIncrement
|
||
progress.value = Math.min(currentProgress, 100)
|
||
|
||
if (progress.value >= 100) {
|
||
clearInterval(progressInterval)
|
||
}
|
||
}, 50)
|
||
|
||
// 更新步骤文本
|
||
updateLoadingStep()
|
||
}
|
||
|
||
// 更新加载步骤
|
||
const updateLoadingStep = () => {
|
||
if (currentStepIndex < loadingSteps.length) {
|
||
const step = loadingSteps[currentStepIndex]
|
||
loadingText.value = step.text
|
||
subText.value = step.subText
|
||
|
||
stepTimeout = setTimeout(() => {
|
||
currentStepIndex++
|
||
updateLoadingStep()
|
||
}, step.duration)
|
||
}
|
||
}
|
||
|
||
// 等待加载完成
|
||
const waitForLoadingComplete = () => {
|
||
return new Promise(resolve => {
|
||
const totalTime = loadingSteps.reduce((sum, step) => sum + step.duration, 0)
|
||
setTimeout(resolve, totalTime)
|
||
})
|
||
}
|
||
|
||
// 跳转到目标页面
|
||
const navigateToTargetPage = async () => {
|
||
try {
|
||
// 尝试获取用户信息来判断跳转目标
|
||
const response = await getTeamEntry()
|
||
|
||
if (response && response.status && response.body) {
|
||
// 有团队信息,根据默认设置跳转到主页
|
||
const memberProfile = response.body.memberProfile
|
||
if (memberProfile) {
|
||
setUserInfo(memberProfile, response.body.teamProfile)
|
||
|
||
// 根据用户身份跳转到对应页面
|
||
// 这里可以根据需要调整默认跳转逻辑
|
||
router.replace('/host-center')
|
||
} else {
|
||
router.replace('/apply')
|
||
}
|
||
} else {
|
||
// 没有团队信息,跳转到申请页面
|
||
router.replace('/apply')
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to get team entry:', error)
|
||
// 出错时跳转到申请页面
|
||
router.replace('/apply')
|
||
}
|
||
}
|
||
|
||
// 清理定时器
|
||
const cleanup = () => {
|
||
if (progressInterval) {
|
||
clearInterval(progressInterval)
|
||
progressInterval = null
|
||
}
|
||
if (stepTimeout) {
|
||
clearTimeout(stepTimeout)
|
||
stepTimeout = null
|
||
}
|
||
}
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
startLoading()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
cleanup()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.loading-view {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.loading-background {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background:
|
||
radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||
radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.15) 0%, transparent 50%),
|
||
radial-gradient(circle at 40% 40%, rgba(120, 219, 255, 0.1) 0%, transparent 50%);
|
||
animation: backgroundShift 10s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes backgroundShift {
|
||
0%, 100% { transform: translateX(0) translateY(0); }
|
||
50% { transform: translateX(-10px) translateY(-5px); }
|
||
}
|
||
|
||
.loading-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
z-index: 2;
|
||
text-align: center;
|
||
max-width: 300px;
|
||
width: 100%;
|
||
padding: 0 20px;
|
||
}
|
||
|
||
.logo-container {
|
||
margin-bottom: 40px;
|
||
}
|
||
|
||
.logo {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
animation: logoFloat 3s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes logoFloat {
|
||
0%, 100% { transform: translateY(0); }
|
||
50% { transform: translateY(-10px); }
|
||
}
|
||
|
||
.logo-icon {
|
||
font-size: 60px;
|
||
margin-bottom: 16px;
|
||
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.2));
|
||
}
|
||
|
||
.logo-text {
|
||
font-size: 32px;
|
||
font-weight: 700;
|
||
color: white;
|
||
margin: 0;
|
||
letter-spacing: 2px;
|
||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.loading-animation {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.spinner {
|
||
position: relative;
|
||
width: 60px;
|
||
height: 60px;
|
||
}
|
||
|
||
.spinner-ring {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
border: 3px solid transparent;
|
||
border-top: 3px solid rgba(255, 255, 255, 0.8);
|
||
border-radius: 50%;
|
||
animation: spin 1.5s linear infinite;
|
||
}
|
||
|
||
.spinner-ring:nth-child(2) {
|
||
width: 80%;
|
||
height: 80%;
|
||
top: 10%;
|
||
left: 10%;
|
||
border-top-color: rgba(255, 255, 255, 0.6);
|
||
animation-duration: 2s;
|
||
animation-direction: reverse;
|
||
}
|
||
|
||
.spinner-ring:nth-child(3) {
|
||
width: 60%;
|
||
height: 60%;
|
||
top: 20%;
|
||
left: 20%;
|
||
border-top-color: rgba(255, 255, 255, 0.4);
|
||
animation-duration: 2.5s;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.loading-text {
|
||
margin-bottom: 30px;
|
||
color: white;
|
||
}
|
||
|
||
.main-text {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin: 0 0 8px 0;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.sub-text {
|
||
font-size: 14px;
|
||
margin: 0;
|
||
opacity: 0.8;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.progress-container {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
height: 4px;
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #fff, rgba(255, 255, 255, 0.8));
|
||
border-radius: 2px;
|
||
transition: width 0.3s ease;
|
||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.progress-text {
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.loading-footer {
|
||
position: absolute;
|
||
bottom: 30px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 2;
|
||
}
|
||
|
||
.loading-footer p {
|
||
margin: 0;
|
||
font-size: 12px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
text-align: center;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 480px) {
|
||
.loading-content {
|
||
max-width: 280px;
|
||
}
|
||
|
||
.logo-icon {
|
||
font-size: 50px;
|
||
}
|
||
|
||
.logo-text {
|
||
font-size: 28px;
|
||
}
|
||
|
||
.spinner {
|
||
width: 50px;
|
||
height: 50px;
|
||
}
|
||
|
||
.main-text {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
/* 暗黑模式支持 */
|
||
@media (prefers-color-scheme: dark) {
|
||
.loading-view {
|
||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||
}
|
||
}
|
||
</style>
|