Files
msh-system/msh_single_uniapp/pages/ai-generate/assets.vue

1059 lines
24 KiB
Vue
Raw Normal View History

<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>