新增加载动画, information页面优化
This commit is contained in:
parent
c2c1802937
commit
8895de2bd0
@ -6,7 +6,13 @@ const router = createRouter({
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: '/host-center' // 默认重定向,实际会被路由守卫处理
|
redirect: '/loading' // 默认跳转到加载页面
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/loading',
|
||||||
|
name: 'loading',
|
||||||
|
component: () => import('../views/LoadingView.vue'),
|
||||||
|
meta: { requiresAuth: false, isPublic: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/host-center',
|
path: '/host-center',
|
||||||
|
|||||||
@ -390,10 +390,11 @@ class RouteGuard {
|
|||||||
*/
|
*/
|
||||||
isPublicPage(path) {
|
isPublicPage(path) {
|
||||||
const publicPages = [
|
const publicPages = [
|
||||||
PAGES.APPLY, // 申请页面 - 重要:申请页面不需要权限检查
|
'/loading', // 加载页面 - 不需要任何检查
|
||||||
PAGES.NOT_APP, // 错误页面 - APP连接失败时的页面
|
PAGES.APPLY, // 申请页面 - 重要:申请页面不需要权限检查
|
||||||
'/404', // 404页面
|
PAGES.NOT_APP, // 错误页面 - APP连接失败时的页面
|
||||||
'/error' // 通用错误页面
|
'/404', // 404页面
|
||||||
|
'/error' // 通用错误页面
|
||||||
]
|
]
|
||||||
return publicPages.includes(path)
|
return publicPages.includes(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
<h3>{{ transaction.eventDescribe }}</h3>
|
<h3>{{ transaction.eventDescribe }}</h3>
|
||||||
<p class="transaction-time">{{ transaction.createTime }}</p>
|
<p class="transaction-time">{{ transaction.createTime }}</p>
|
||||||
</div>
|
</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) }}
|
{{ transaction.amount > 0 ? '+' : '' }}{{ Math.abs(transaction.amount) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,6 +48,7 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import MobileHeader from '../components/MobileHeader.vue'
|
import MobileHeader from '../components/MobileHeader.vue'
|
||||||
import { getWalletDetails, formatDateTime, formatEventDescribe } from '../api/wallet.js'
|
import { getWalletDetails, formatDateTime, formatEventDescribe } from '../api/wallet.js'
|
||||||
|
import {formatUTCTime} from "@/utils/utcFormat.js";
|
||||||
|
|
||||||
const transactions = ref([])
|
const transactions = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -63,14 +64,45 @@ const fetchData = async () => {
|
|||||||
|
|
||||||
// 处理API返回的数据
|
// 处理API返回的数据
|
||||||
if (response.body && Array.isArray(response.body)) {
|
if (response.body && Array.isArray(response.body)) {
|
||||||
transactions.value = response.body.map(item => ({
|
transactions.value = response.body.map(item => {
|
||||||
id: item.id,
|
// 根据 event 字段判断是加还是减
|
||||||
eventDescribe: formatEventDescribe(item.eventDescribe),
|
let amount = item.amount
|
||||||
createTime: formatDateTime(item.createTime),
|
let isPositive = false
|
||||||
amount: item.type === 0 ? -Math.abs(item.amount) : item.amount, // type=0表示支出,显示为负数
|
|
||||||
balance: item.balance,
|
// 根据事件类型判断显示
|
||||||
event: item.event
|
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 {
|
} else {
|
||||||
transactions.value = []
|
transactions.value = []
|
||||||
}
|
}
|
||||||
|
|||||||
427
src/views/LoadingView.vue
Normal file
427
src/views/LoadingView.vue
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user