1059 lines
24 KiB
Vue
1059 lines
24 KiB
Vue
|
|
<template>
|
|||
|
|
<view class="container">
|
|||
|
|
<!-- AI生成内容标识 -->
|
|||
|
|
<view class="ai-notice">
|
|||
|
|
<text class="ai-notice-icon">🤖</text>
|
|||
|
|
<text class="ai-notice-text">作品由AI生成,结果仅供参考</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 页面头部 -->
|
|||
|
|
<view class="header">
|
|||
|
|
<text class="subtitle">管理你的AI数字资产</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 资产统计 -->
|
|||
|
|
<view class="stats-section">
|
|||
|
|
<view class="stats-card">
|
|||
|
|
<text class="stats-number">{{totalAssets}}</text>
|
|||
|
|
<text class="stats-label">总资产数</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="stats-card">
|
|||
|
|
<text class="stats-number">{{activeAssets}}</text>
|
|||
|
|
<text class="stats-label">活跃资产</text>
|
|||
|
|
</view>
|
|||
|
|
<view class="stats-card">
|
|||
|
|
<text class="stats-number">{{totalViews}}</text>
|
|||
|
|
<text class="stats-label">总播放量</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 资产分类 -->
|
|||
|
|
<view class="category-section">
|
|||
|
|
<view class="category-tabs">
|
|||
|
|
<view class="tab" :class="{active: currentTab === 'all'}" @tap="switchTab('all')">
|
|||
|
|
全部
|
|||
|
|
</view>
|
|||
|
|
<view class="tab" :class="{active: currentTab === 'video'}" @tap="switchTab('video')">
|
|||
|
|
装修实拍
|
|||
|
|
</view>
|
|||
|
|
<view class="tab" :class="{active: currentTab === 'image'}" @tap="switchTab('image')">
|
|||
|
|
翻新前后
|
|||
|
|
</view>
|
|||
|
|
<view class="tab" :class="{active: currentTab === 'text'}" @tap="switchTab('text')">
|
|||
|
|
设计案例
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 资产列表 -->
|
|||
|
|
<view class="assets-list">
|
|||
|
|
<!-- 加载状态 -->
|
|||
|
|
<transition name="fade" mode="out-in">
|
|||
|
|
<view class="loading-state" v-if="loading && assetList.length === 0" key="loading">
|
|||
|
|
<!-- 主加载动画 -->
|
|||
|
|
<view class="loading-container">
|
|||
|
|
<view class="loading-spinner">
|
|||
|
|
<view class="spinner-ring"></view>
|
|||
|
|
<view class="spinner-ring"></view>
|
|||
|
|
<view class="spinner-ring"></view>
|
|||
|
|
</view>
|
|||
|
|
<text class="loading-text">{{ loadingText }}</text>
|
|||
|
|
<text class="loading-subtitle">{{ loadingSubtitle }}</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 骨架屏 -->
|
|||
|
|
<view class="skeleton-container" v-if="showSkeleton">
|
|||
|
|
<view class="skeleton-item" v-for="n in 3" :key="n">
|
|||
|
|
<view class="skeleton-cover"></view>
|
|||
|
|
<view class="skeleton-content">
|
|||
|
|
<view class="skeleton-title"></view>
|
|||
|
|
<view class="skeleton-desc"></view>
|
|||
|
|
<view class="skeleton-meta">
|
|||
|
|
<view class="skeleton-meta-item"></view>
|
|||
|
|
<view class="skeleton-meta-item"></view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 超时提示 -->
|
|||
|
|
<view class="timeout-state" v-else-if="showTimeout" key="timeout">
|
|||
|
|
<view class="timeout-icon">⚠️</view>
|
|||
|
|
<text class="timeout-text">加载时间较长</text>
|
|||
|
|
<text class="timeout-desc">请检查网络连接或稍后重试</text>
|
|||
|
|
<button class="retry-btn" @tap="retryLoad">重新加载</button>
|
|||
|
|
</view>
|
|||
|
|
</transition>
|
|||
|
|
|
|||
|
|
<view class="asset-item" v-for="item in assetList" :key="item.id" @tap="onAssetTap(item)">
|
|||
|
|
<view class="asset-cover">
|
|||
|
|
<image :src="item.coverUrl" mode="aspectFill" class="cover-image" lazy-load="true"></image>
|
|||
|
|
<view class="asset-type">{{item.type}}</view>
|
|||
|
|
<view class="asset-status" :class="'status-' + item.status">{{item.statusText}}</view>
|
|||
|
|
</view>
|
|||
|
|
<view class="asset-info">
|
|||
|
|
<text class="asset-title">{{item.title}}</text>
|
|||
|
|
<text class="asset-desc">{{item.description}}</text>
|
|||
|
|
<view class="asset-meta">
|
|||
|
|
<text class="meta-item">{{item.createTime}}</text>
|
|||
|
|
<text class="meta-item">{{item.viewCount}}次播放</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 加载更多状态 -->
|
|||
|
|
<view class="load-more" v-if="assetList.length > 0">
|
|||
|
|
<text class="load-more-text" v-if="loading">加载中...</text>
|
|||
|
|
<text class="load-more-text" v-else-if="!hasMore">没有更多了</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 空状态 -->
|
|||
|
|
<view class="empty-state" v-if="assetList.length === 0 && !loading">
|
|||
|
|
<image src="/static/images/empty-assets.png" class="empty-image"></image>
|
|||
|
|
<text class="empty-text">暂无数字资产</text>
|
|||
|
|
<text class="empty-desc">开始创建你的第一个AI数字分身吧</text>
|
|||
|
|
<button class="create-btn" @tap="onCreate">立即创建</button>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 浮动创建按钮 -->
|
|||
|
|
<view class="fab" @tap="onCreate">
|
|||
|
|
<image src="/static/images/add.png" class="fab-icon"></image>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import api from '@/api/models-api.js'
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
// 统计数据
|
|||
|
|
totalAssets: 0,
|
|||
|
|
activeAssets: 0,
|
|||
|
|
totalViews: '0',
|
|||
|
|
|
|||
|
|
// 当前选中的标签
|
|||
|
|
currentTab: 'all',
|
|||
|
|
|
|||
|
|
// 资产列表
|
|||
|
|
assetList: [],
|
|||
|
|
|
|||
|
|
// 原始完整列表(用于筛选)
|
|||
|
|
fullAssetList: [],
|
|||
|
|
|
|||
|
|
// 分页参数
|
|||
|
|
currentPage: 1,
|
|||
|
|
pageSize: 10,
|
|||
|
|
hasMore: true,
|
|||
|
|
loading: false,
|
|||
|
|
|
|||
|
|
// 加载状态优化
|
|||
|
|
loadingText: '加载中...',
|
|||
|
|
loadingSubtitle: '正在获取您的数字资产',
|
|||
|
|
showSkeleton: false,
|
|||
|
|
showTimeout: false,
|
|||
|
|
loadingTimer: null,
|
|||
|
|
timeoutTimer: null
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
onLoad() {
|
|||
|
|
console.log('数字资产页面加载')
|
|||
|
|
this.loadArticleList()
|
|||
|
|
},
|
|||
|
|
onShow() {
|
|||
|
|
// 页面显示时刷新数据
|
|||
|
|
this.refreshData()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
// 加载文章列表
|
|||
|
|
async loadArticleList(page = 1, isRefresh = false) {
|
|||
|
|
if (this.loading) return
|
|||
|
|
|
|||
|
|
this.loading = true
|
|||
|
|
this.showTimeout = false
|
|||
|
|
|
|||
|
|
// 设置加载文本动画
|
|||
|
|
this.startLoadingAnimation()
|
|||
|
|
|
|||
|
|
// 延迟显示骨架屏
|
|||
|
|
setTimeout(() => {
|
|||
|
|
if (this.loading) {
|
|||
|
|
this.showSkeleton = true
|
|||
|
|
}
|
|||
|
|
}, 800)
|
|||
|
|
|
|||
|
|
// 设置超时检测
|
|||
|
|
this.timeoutTimer = setTimeout(() => {
|
|||
|
|
if (this.loading) {
|
|||
|
|
this.showTimeout = true
|
|||
|
|
this.showSkeleton = false
|
|||
|
|
}
|
|||
|
|
}, 10000) // 10秒超时
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await api.getArticleList({
|
|||
|
|
page: page,
|
|||
|
|
size: this.pageSize
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (response.code === 200 && response.data) {
|
|||
|
|
const articles = response.data.records || []
|
|||
|
|
|
|||
|
|
// 转换文章数据为资产数据格式
|
|||
|
|
const assetList = articles.map(article => ({
|
|||
|
|
id: article.id,
|
|||
|
|
title: article.title || '未命名作品',
|
|||
|
|
description: article.prompt || article.synopsis || article.content || '暂无描述',
|
|||
|
|
type: this.getAssetType(article),
|
|||
|
|
coverUrl: article.imageInput || '/static/images/placeholder.png',
|
|||
|
|
createTime: this.formatDate(article.createTime),
|
|||
|
|
viewCount: parseInt(article.visit) || 0,
|
|||
|
|
status: this.getAssetStatus(article.statusTask),
|
|||
|
|
statusText: this.getStatusText(article.statusTask),
|
|||
|
|
videoUrl: article.videoUrl,
|
|||
|
|
taskId: article.taskId,
|
|||
|
|
prompt: article.prompt,
|
|||
|
|
aspectRatio: article.aspectRatio
|
|||
|
|
}))
|
|||
|
|
|
|||
|
|
if (isRefresh || page === 1) {
|
|||
|
|
// 刷新或首次加载
|
|||
|
|
this.assetList = assetList
|
|||
|
|
this.fullAssetList = assetList
|
|||
|
|
this.currentPage = 1
|
|||
|
|
this.hasMore = assetList.length >= this.pageSize
|
|||
|
|
} else {
|
|||
|
|
// 加载更多
|
|||
|
|
this.assetList = [...this.assetList, ...assetList]
|
|||
|
|
this.fullAssetList = [...this.fullAssetList, ...assetList]
|
|||
|
|
this.hasMore = assetList.length >= this.pageSize
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新统计数据
|
|||
|
|
this.updateStats()
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载文章列表失败:', error)
|
|||
|
|
|
|||
|
|
// 网络错误处理
|
|||
|
|
if (!navigator.onLine) {
|
|||
|
|
this.loadingText = '网络连接失败'
|
|||
|
|
this.loadingSubtitle = '请检查网络设置后重试'
|
|||
|
|
} else {
|
|||
|
|
this.loadingText = '加载失败'
|
|||
|
|
this.loadingSubtitle = '服务器暂时无法访问'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
uni.showToast({
|
|||
|
|
title: '加载失败,请重试',
|
|||
|
|
icon: 'none'
|
|||
|
|
})
|
|||
|
|
} finally {
|
|||
|
|
this.stopLoadingAnimation()
|
|||
|
|
this.loading = false
|
|||
|
|
this.showSkeleton = false
|
|||
|
|
this.showTimeout = false
|
|||
|
|
|
|||
|
|
// 清除定时器
|
|||
|
|
if (this.timeoutTimer) {
|
|||
|
|
clearTimeout(this.timeoutTimer)
|
|||
|
|
this.timeoutTimer = null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 开始加载动画
|
|||
|
|
startLoadingAnimation() {
|
|||
|
|
const texts = [
|
|||
|
|
{ main: '加载中...', sub: '正在获取您的数字资产' },
|
|||
|
|
{ main: '努力加载中...', sub: '马上就好了' },
|
|||
|
|
{ main: '即将完成...', sub: '感谢您的耐心等待' }
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
let index = 0
|
|||
|
|
this.loadingTimer = setInterval(() => {
|
|||
|
|
if (this.loading && !this.showTimeout) {
|
|||
|
|
const text = texts[index % texts.length]
|
|||
|
|
this.loadingText = text.main
|
|||
|
|
this.loadingSubtitle = text.sub
|
|||
|
|
index++
|
|||
|
|
}
|
|||
|
|
}, 2000)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 停止加载动画
|
|||
|
|
stopLoadingAnimation() {
|
|||
|
|
if (this.loadingTimer) {
|
|||
|
|
clearInterval(this.loadingTimer)
|
|||
|
|
this.loadingTimer = null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 重置加载文本
|
|||
|
|
this.loadingText = '加载中...'
|
|||
|
|
this.loadingSubtitle = '正在获取您的数字资产'
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 重试加载
|
|||
|
|
retryLoad() {
|
|||
|
|
this.showTimeout = false
|
|||
|
|
this.loadArticleList(1, true)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取资产类型
|
|||
|
|
getAssetType(article) {
|
|||
|
|
if (article.videoUrl) {
|
|||
|
|
return '视频'
|
|||
|
|
} else if (article.imageInput) {
|
|||
|
|
return '图片'
|
|||
|
|
} else {
|
|||
|
|
return '文本'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取资产状态
|
|||
|
|
getAssetStatus(statusTask) {
|
|||
|
|
switch (statusTask) {
|
|||
|
|
case 0:
|
|||
|
|
return 'pending'
|
|||
|
|
case 1:
|
|||
|
|
return 'processing'
|
|||
|
|
case 2:
|
|||
|
|
return 'active'
|
|||
|
|
case 3:
|
|||
|
|
return 'failed'
|
|||
|
|
default:
|
|||
|
|
return 'unknown'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 获取状态文本
|
|||
|
|
getStatusText(statusTask) {
|
|||
|
|
switch (statusTask) {
|
|||
|
|
case 0:
|
|||
|
|
return '等待中'
|
|||
|
|
case 1:
|
|||
|
|
return '已完成'
|
|||
|
|
case 2:
|
|||
|
|
return '已完成'
|
|||
|
|
case 3:
|
|||
|
|
return '失败'
|
|||
|
|
default:
|
|||
|
|
return '未知'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 格式化日期
|
|||
|
|
formatDate(dateString) {
|
|||
|
|
if (!dateString) return ''
|
|||
|
|
const date = new Date(dateString)
|
|||
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 更新统计数据
|
|||
|
|
updateStats() {
|
|||
|
|
const totalAssets = this.fullAssetList.length
|
|||
|
|
const activeAssets = this.fullAssetList.filter(item => item.status === 'active').length
|
|||
|
|
const totalViews = this.fullAssetList.reduce((sum, item) => sum + item.viewCount, 0)
|
|||
|
|
|
|||
|
|
this.totalAssets = totalAssets
|
|||
|
|
this.activeAssets = activeAssets
|
|||
|
|
this.totalViews = totalViews > 1000 ? `${(totalViews / 1000).toFixed(1)}k` : totalViews.toString()
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 刷新数据
|
|||
|
|
refreshData() {
|
|||
|
|
this.loadArticleList(1, true)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 切换标签
|
|||
|
|
switchTab(tab) {
|
|||
|
|
this.currentTab = tab
|
|||
|
|
this.filterAssets(tab)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 筛选资产
|
|||
|
|
filterAssets(tab) {
|
|||
|
|
let filteredList = this.fullAssetList
|
|||
|
|
|
|||
|
|
if (tab !== 'all') {
|
|||
|
|
const typeMap = {
|
|||
|
|
'video': '视频',
|
|||
|
|
'image': '图片',
|
|||
|
|
'text': '文本'
|
|||
|
|
}
|
|||
|
|
filteredList = this.fullAssetList.filter(item =>
|
|||
|
|
item.type === typeMap[tab]
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.assetList = filteredList
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 点击资产项
|
|||
|
|
onAssetTap(asset) {
|
|||
|
|
console.log('查看资产详情:', asset)
|
|||
|
|
|
|||
|
|
// 根据资产类型跳转到不同页面
|
|||
|
|
if (asset.type === '视频') {
|
|||
|
|
uni.navigateTo({
|
|||
|
|
url: `/pages/ai-generate/video?id=${asset.id}`
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
uni.showToast({
|
|||
|
|
title: `查看${asset.title}`,
|
|||
|
|
icon: 'none'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 创建新资产
|
|||
|
|
onCreate() {
|
|||
|
|
uni.showActionSheet({
|
|||
|
|
itemList: ['一键创作', '上传模板'],
|
|||
|
|
success: (res) => {
|
|||
|
|
const actions = ['一键创作', '上传模板']
|
|||
|
|
uni.showToast({
|
|||
|
|
title: `已选择:${actions[res.tapIndex]}`,
|
|||
|
|
icon: 'none'
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
// 根据选择跳转到不同的创建页面
|
|||
|
|
if (res.tapIndex === 0) {
|
|||
|
|
// 跳转到一键创作页面
|
|||
|
|
uni.navigateTo({
|
|||
|
|
url: '/pages/ai-generate/oneclick'
|
|||
|
|
})
|
|||
|
|
} else if (res.tapIndex === 1) {
|
|||
|
|
// 保留原逻辑:跳转到视频创作页面
|
|||
|
|
uni.switchTab({
|
|||
|
|
url: '/pages/create/create'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
// 下拉刷新
|
|||
|
|
onPullDownRefresh() {
|
|||
|
|
this.refreshData()
|
|||
|
|
setTimeout(() => {
|
|||
|
|
uni.stopPullDownRefresh()
|
|||
|
|
}, 1000)
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 上拉加载更多
|
|||
|
|
onReachBottom() {
|
|||
|
|
if (!this.hasMore || this.loading) {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('加载更多资产')
|
|||
|
|
const nextPage = this.currentPage + 1
|
|||
|
|
this.currentPage = nextPage
|
|||
|
|
this.loadArticleList(nextPage, false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style>
|
|||
|
|
.container {
|
|||
|
|
padding: 20rpx;
|
|||
|
|
background: linear-gradient(135deg, #149e20 0%, #06430e 100%);
|
|||
|
|
min-height: 100vh;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* AI生成内容标识 */
|
|||
|
|
.ai-notice {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 16rpx;
|
|||
|
|
padding: 16rpx 32rpx;
|
|||
|
|
margin: 20rpx 0;
|
|||
|
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
|
border: 2rpx solid rgba(255, 255, 255, 0.2);
|
|||
|
|
border-radius: 24rpx;
|
|||
|
|
backdrop-filter: blur(10rpx);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ai-notice-icon {
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ai-notice-text {
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
color: rgba(255, 255, 255, 0.9);
|
|||
|
|
line-height: 1.4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 操作按钮与浮动按钮统一主色 */
|
|||
|
|
.create-btn {
|
|||
|
|
background: linear-gradient(45deg, #42ca4d, #2aa53a);
|
|||
|
|
color: #ffffff;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 50rpx;
|
|||
|
|
padding: 24rpx 48rpx;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.fab {
|
|||
|
|
position: fixed;
|
|||
|
|
right: 40rpx;
|
|||
|
|
bottom: 120rpx;
|
|||
|
|
width: 100rpx;
|
|||
|
|
height: 100rpx;
|
|||
|
|
background: linear-gradient(45deg, #42ca4d, #2aa53a);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
box-shadow: 0 8rpx 24rpx rgba(66, 202, 77, 0.4);
|
|||
|
|
z-index: 999;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-btn {
|
|||
|
|
width: 60rpx;
|
|||
|
|
height: 60rpx;
|
|||
|
|
background: rgba(66, 202, 77, 0.1);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
transition: all 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-btn:active {
|
|||
|
|
background: rgba(66, 202, 77, 0.2);
|
|||
|
|
transform: scale(0.9);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 页面头部 */
|
|||
|
|
.header {
|
|||
|
|
text-align: center;
|
|||
|
|
margin-bottom: 20rpx; margin-top: 34px;
|
|||
|
|
padding: 20rpx 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.title {
|
|||
|
|
display: block;
|
|||
|
|
font-size: 48rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #ffffff;
|
|||
|
|
margin-bottom: 10rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.subtitle {
|
|||
|
|
display: block;
|
|||
|
|
font-size: 34rpx;
|
|||
|
|
color: rgba(255, 255, 255, 0.8);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 资产统计 */
|
|||
|
|
.stats-section {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
margin-bottom: 40rpx;
|
|||
|
|
gap: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.stats-card {
|
|||
|
|
flex: 1;
|
|||
|
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
padding: 30rpx 20rpx;
|
|||
|
|
text-align: center;
|
|||
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.stats-number {
|
|||
|
|
display: block;
|
|||
|
|
font-size: 48rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #ffffff;
|
|||
|
|
margin-bottom: 10rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.stats-label {
|
|||
|
|
display: block;
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
color: rgba(255, 255, 255, 0.8);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 分类标签 */
|
|||
|
|
.category-section {
|
|||
|
|
margin-bottom: 30rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.category-tabs {
|
|||
|
|
display: flex;
|
|||
|
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
|
border-radius: 25rpx;
|
|||
|
|
padding: 8rpx;
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tab {
|
|||
|
|
flex: 1;
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 20rpx 0;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: rgba(255, 255, 255, 0.7);
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.tab.active {
|
|||
|
|
background: rgba(255, 255, 255, 0.2);
|
|||
|
|
color: #ffffff;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 资产列表 */
|
|||
|
|
.assets-list {
|
|||
|
|
margin-bottom: 120rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-item {
|
|||
|
|
display: flex;
|
|||
|
|
background: rgba(255, 255, 255, 0.95);
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
padding: 20rpx;
|
|||
|
|
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
|
|||
|
|
transition: transform 0.2s ease;
|
|||
|
|
min-height: 220rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-item:active {
|
|||
|
|
transform: scale(0.98);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-cover {
|
|||
|
|
position: relative;
|
|||
|
|
width: 180rpx;
|
|||
|
|
height: 180rpx;
|
|||
|
|
margin-right: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.cover-image {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
border-radius: 15rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-type {
|
|||
|
|
position: absolute;
|
|||
|
|
top: 8rpx;
|
|||
|
|
right: 8rpx;
|
|||
|
|
background: rgba(0, 0, 0, 0.7);
|
|||
|
|
color: #ffffff;
|
|||
|
|
font-size: 20rpx;
|
|||
|
|
padding: 4rpx 8rpx;
|
|||
|
|
border-radius: 8rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-info {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-title {
|
|||
|
|
font-size: 32rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #333333;
|
|||
|
|
margin-bottom: 8rpx;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
max-width: 400rpx;
|
|||
|
|
max-height: 44rpx;
|
|||
|
|
line-height: 44rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-desc {
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: #666666;
|
|||
|
|
margin-bottom: 12rpx;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
display: -webkit-box;
|
|||
|
|
-webkit-line-clamp: 2;
|
|||
|
|
-webkit-box-orient: vertical;
|
|||
|
|
white-space: normal;
|
|||
|
|
max-width: 400rpx;
|
|||
|
|
max-height: 68rpx;
|
|||
|
|
line-height: 34rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-meta {
|
|||
|
|
display: flex;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 16rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.meta-item {
|
|||
|
|
font-size: 22rpx;
|
|||
|
|
color: #999999;
|
|||
|
|
}
|
|||
|
|
.asset-status {
|
|||
|
|
font-size: 20rpx;
|
|||
|
|
}
|
|||
|
|
.status-active {
|
|||
|
|
color: #52c41a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-processing {
|
|||
|
|
color: #1890ff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.status-failed {
|
|||
|
|
color: #ff4d4f;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.asset-actions {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: center;
|
|||
|
|
gap: 16rpx;
|
|||
|
|
margin-left: 20rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-btn {
|
|||
|
|
width: 60rpx;
|
|||
|
|
height: 60rpx;
|
|||
|
|
background: rgba(103, 126, 234, 0.1);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
transition: all 0.2s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-btn:active {
|
|||
|
|
background: rgba(103, 126, 234, 0.2);
|
|||
|
|
transform: scale(0.9);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.action-icon {
|
|||
|
|
width: 32rpx;
|
|||
|
|
height: 32rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 加载状态优化 */
|
|||
|
|
.loading-state {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: center;
|
|||
|
|
align-items: center;
|
|||
|
|
min-height: 400rpx;
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 60rpx 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.loading-container {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-bottom: 60rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 加载动画 */
|
|||
|
|
.loading-spinner {
|
|||
|
|
position: relative;
|
|||
|
|
width: 120rpx;
|
|||
|
|
height: 120rpx;
|
|||
|
|
margin-bottom: 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.spinner-ring {
|
|||
|
|
position: absolute;
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
border: 4rpx solid transparent;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
animation: spin 2s linear infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.spinner-ring:nth-child(1) {
|
|||
|
|
border-top-color: #42ca4d;
|
|||
|
|
animation-delay: 0s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.spinner-ring:nth-child(2) {
|
|||
|
|
border-right-color: #2aa53a;
|
|||
|
|
animation-delay: 0.3s;
|
|||
|
|
width: 80%;
|
|||
|
|
height: 80%;
|
|||
|
|
top: 10%;
|
|||
|
|
left: 10%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.spinner-ring:nth-child(3) {
|
|||
|
|
border-bottom-color: #1e7e34;
|
|||
|
|
animation-delay: 0.6s;
|
|||
|
|
width: 60%;
|
|||
|
|
height: 60%;
|
|||
|
|
top: 20%;
|
|||
|
|
left: 20%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes spin {
|
|||
|
|
0% { transform: rotate(0deg); }
|
|||
|
|
100% { transform: rotate(360deg); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.loading-text {
|
|||
|
|
font-size: 32rpx;
|
|||
|
|
color: #ffffff;
|
|||
|
|
font-weight: bold;
|
|||
|
|
margin-bottom: 16rpx;
|
|||
|
|
animation: pulse 1.5s ease-in-out infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.loading-subtitle {
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: rgba(255, 255, 255, 0.8);
|
|||
|
|
font-weight: normal;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes pulse {
|
|||
|
|
0%, 100% { opacity: 1; }
|
|||
|
|
50% { opacity: 0.6; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Vue过渡动画 */
|
|||
|
|
.fade-enter-active, .fade-leave-active {
|
|||
|
|
transition: all 0.5s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.fade-enter-from, .fade-leave-to {
|
|||
|
|
opacity: 0;
|
|||
|
|
transform: translateY(20rpx);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 骨架屏 */
|
|||
|
|
.skeleton-container {
|
|||
|
|
width: 100%;
|
|||
|
|
margin-top: 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-item {
|
|||
|
|
display: flex;
|
|||
|
|
background: rgba(255, 255, 255, 0.1);
|
|||
|
|
border-radius: 20rpx;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
padding: 20rpx;
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-cover {
|
|||
|
|
width: 180rpx;
|
|||
|
|
height: 180rpx;
|
|||
|
|
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
|||
|
|
background-size: 200% 100%;
|
|||
|
|
border-radius: 15rpx;
|
|||
|
|
margin-right: 20rpx;
|
|||
|
|
animation: shimmer 1.5s infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-content {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-title {
|
|||
|
|
height: 44rpx;
|
|||
|
|
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
|||
|
|
background-size: 200% 100%;
|
|||
|
|
border-radius: 8rpx;
|
|||
|
|
margin-bottom: 16rpx;
|
|||
|
|
animation: shimmer 1.5s infinite;
|
|||
|
|
animation-delay: 0.1s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-desc {
|
|||
|
|
height: 68rpx;
|
|||
|
|
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
|||
|
|
background-size: 200% 100%;
|
|||
|
|
border-radius: 8rpx;
|
|||
|
|
margin-bottom: 16rpx;
|
|||
|
|
animation: shimmer 1.5s infinite;
|
|||
|
|
animation-delay: 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-meta {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 16rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-meta-item {
|
|||
|
|
height: 32rpx;
|
|||
|
|
width: 120rpx;
|
|||
|
|
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
|||
|
|
background-size: 200% 100%;
|
|||
|
|
border-radius: 6rpx;
|
|||
|
|
animation: shimmer 1.5s infinite;
|
|||
|
|
animation-delay: 0.3s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes shimmer {
|
|||
|
|
0% { background-position: -200% 0; }
|
|||
|
|
100% { background-position: 200% 0; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 超时状态 */
|
|||
|
|
.timeout-state {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 80rpx 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.timeout-icon {
|
|||
|
|
font-size: 80rpx;
|
|||
|
|
margin-bottom: 30rpx;
|
|||
|
|
animation: bounce 2s infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes bounce {
|
|||
|
|
0%, 20%, 50%, 80%, 100% { transform: translateY(0); }
|
|||
|
|
40% { transform: translateY(-10rpx); }
|
|||
|
|
60% { transform: translateY(-5rpx); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.timeout-text {
|
|||
|
|
font-size: 32rpx;
|
|||
|
|
color: #ffffff;
|
|||
|
|
font-weight: bold;
|
|||
|
|
margin-bottom: 16rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.timeout-desc {
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: rgba(255, 255, 255, 0.8);
|
|||
|
|
margin-bottom: 40rpx;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.retry-btn {
|
|||
|
|
background: linear-gradient(45deg, #42ca4d, #2aa53a);
|
|||
|
|
color: #ffffff;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 50rpx;
|
|||
|
|
padding: 24rpx 48rpx;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
box-shadow: 0 8rpx 24rpx rgba(66, 202, 77, 0.3);
|
|||
|
|
transition: all 0.3s ease;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.retry-btn:active {
|
|||
|
|
transform: translateY(2rpx);
|
|||
|
|
box-shadow: 0 4rpx 12rpx rgba(66, 202, 77, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 响应式优化 */
|
|||
|
|
@media screen and (max-width: 750rpx) {
|
|||
|
|
.loading-spinner {
|
|||
|
|
width: 100rpx;
|
|||
|
|
height: 100rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-item {
|
|||
|
|
flex-direction: column;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.skeleton-cover {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 200rpx;
|
|||
|
|
margin-right: 0;
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 无障碍访问优化 */
|
|||
|
|
@media (prefers-reduced-motion: reduce) {
|
|||
|
|
.spinner-ring,
|
|||
|
|
.loading-text,
|
|||
|
|
.skeleton-cover,
|
|||
|
|
.skeleton-title,
|
|||
|
|
.skeleton-desc,
|
|||
|
|
.skeleton-meta-item,
|
|||
|
|
.timeout-icon {
|
|||
|
|
animation: none;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.fade-enter-active,
|
|||
|
|
.fade-leave-active {
|
|||
|
|
transition: none;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 空状态 */
|
|||
|
|
.empty-state {
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 100rpx 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty-image {
|
|||
|
|
width: 200rpx;
|
|||
|
|
height: 200rpx;
|
|||
|
|
margin-bottom: 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty-text {
|
|||
|
|
display: block;
|
|||
|
|
font-size: 32rpx;
|
|||
|
|
color: #ffffff;
|
|||
|
|
margin-bottom: 16rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty-desc {
|
|||
|
|
display: block;
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: rgba(255, 255, 255, 0.8);
|
|||
|
|
margin-bottom: 40rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.create-btn {
|
|||
|
|
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
|
|||
|
|
color: #ffffff;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 50rpx;
|
|||
|
|
padding: 24rpx 48rpx;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 浮动按钮 */
|
|||
|
|
.fab {
|
|||
|
|
position: fixed;
|
|||
|
|
right: 40rpx;
|
|||
|
|
bottom: 120rpx;
|
|||
|
|
width: 100rpx;
|
|||
|
|
height: 100rpx;
|
|||
|
|
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
box-shadow: 0 8rpx 24rpx rgba(255, 107, 107, 0.4);
|
|||
|
|
z-index: 999;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.fab-icon {
|
|||
|
|
width: 48rpx;
|
|||
|
|
height: 48rpx;
|
|||
|
|
}
|
|||
|
|
</style>
|