新增加载动画, information页面优化

This commit is contained in:
tianfeng 2025-08-22 18:01:07 +08:00
parent c2c1802937
commit 8895de2bd0
4 changed files with 484 additions and 18 deletions

View File

@ -6,7 +6,13 @@ const router = createRouter({
routes: [
{
path: '/',
redirect: '/host-center' // 默认重定向,实际会被路由守卫处理
redirect: '/loading' // 默认跳转到加载页面
},
{
path: '/loading',
name: 'loading',
component: () => import('../views/LoadingView.vue'),
meta: { requiresAuth: false, isPublic: true }
},
{
path: '/host-center',

View File

@ -390,10 +390,11 @@ class RouteGuard {
*/
isPublicPage(path) {
const publicPages = [
PAGES.APPLY, // 申请页面 - 重要:申请页面不需要权限检查
PAGES.NOT_APP, // 错误页面 - APP连接失败时的页面
'/404', // 404页面
'/error' // 通用错误页面
'/loading', // 加载页面 - 不需要任何检查
PAGES.APPLY, // 申请页面 - 重要:申请页面不需要权限检查
PAGES.NOT_APP, // 错误页面 - APP连接失败时的页面
'/404', // 404页面
'/error' // 通用错误页面
]
return publicPages.includes(path)
}

View File

@ -18,8 +18,8 @@
<!-- 交易记录列表 -->
<div v-else class="transaction-list">
<div
v-for="transaction in transactions"
<div
v-for="transaction in transactions"
:key="transaction.id"
class="transaction-item"
>
@ -28,7 +28,7 @@
<h3>{{ transaction.eventDescribe }}</h3>
<p class="transaction-time">{{ transaction.createTime }}</p>
</div>
<div class="transaction-amount" :class="transaction.amount > 0 ? 'positive' : 'negative'">
<div class="transaction-amount" :class="transaction.isPositive ? 'positive' : 'negative'">
{{ transaction.amount > 0 ? '+' : '' }}{{ Math.abs(transaction.amount) }}
</div>
</div>
@ -48,6 +48,7 @@
import { ref, onMounted } from 'vue'
import MobileHeader from '../components/MobileHeader.vue'
import { getWalletDetails, formatDateTime, formatEventDescribe } from '../api/wallet.js'
import {formatUTCTime} from "@/utils/utcFormat.js";
const transactions = ref([])
const loading = ref(false)
@ -57,20 +58,51 @@ const error = ref(null)
const fetchData = async () => {
loading.value = true
error.value = null
try {
const response = await getWalletDetails()
// API
if (response.body && Array.isArray(response.body)) {
transactions.value = response.body.map(item => ({
id: item.id,
eventDescribe: formatEventDescribe(item.eventDescribe),
createTime: formatDateTime(item.createTime),
amount: item.type === 0 ? -Math.abs(item.amount) : item.amount, // type=0
balance: item.balance,
event: item.event
}))
transactions.value = response.body.map(item => {
// event
let amount = item.amount
let isPositive = false
//
switch (item.event) {
case 'RECEIVE_TRANSFER':
// - 绿
amount = Math.abs(item.amount)
isPositive = true
break
case 'TRANSFER':
// -
amount = -Math.abs(item.amount)
isPositive = false
break
default:
// type
// type: 0 1
if (item.type === 0) {
amount = Math.abs(item.amount)
isPositive = true
} else {
amount = -Math.abs(item.amount)
isPositive = false
}
}
return {
id: item.id,
eventDescribe: formatEventDescribe(item.eventDescribe),
createTime: formatUTCTime(item.createTime),
amount: amount,
balance: item.balance,
event: item.event,
isPositive: isPositive
}
})
} else {
transactions.value = []
}

427
src/views/LoadingView.vue Normal file
View File

@ -0,0 +1,427 @@
<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>