1093 lines
29 KiB
Vue
1093 lines
29 KiB
Vue
<template>
|
||
<view class="inspiration-container">
|
||
<!-- 顶部导航 -->
|
||
<view class="nav-bar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||
<view class="nav-content">
|
||
<text class="nav-title">发现</text>
|
||
</view>
|
||
|
||
<!-- 分类标签 -->
|
||
<scroll-view scroll-x class="category-tabs" :show-scrollbar="false">
|
||
<view
|
||
v-for="(cat, index) in categories"
|
||
:key="index"
|
||
:class="['tab-item', selectedCategory === cat.value ? 'active' : '']"
|
||
@click="selectCategory(cat.value)"
|
||
>
|
||
<text class="tab-text">{{ cat.label }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
</view>
|
||
|
||
<!-- 瀑布流内容 -->
|
||
<scroll-view
|
||
scroll-y
|
||
class="content-scroll"
|
||
:style="{ top: scrollViewTop, bottom: scrollViewBottom, height: scrollViewHeight }"
|
||
@scrolltolower="loadMore"
|
||
@scroll="onScroll"
|
||
>
|
||
<!-- AI生成内容标识 -->
|
||
<view class="ai-notice">
|
||
<text class="ai-notice-text">内容由AI生成</text>
|
||
</view>
|
||
|
||
<!-- 骨架屏加载 -->
|
||
<view class="skeleton-container" v-if="loading && leftColumnData.length === 0">
|
||
<view class="skeleton-waterfall">
|
||
<!-- 左列骨架 -->
|
||
<view class="skeleton-column">
|
||
<view class="skeleton-card" v-for="n in 3" :key="'left-' + n">
|
||
<view class="skeleton-image"></view>
|
||
<view class="skeleton-info">
|
||
<view class="skeleton-title"></view>
|
||
<view class="skeleton-meta">
|
||
<view class="skeleton-avatar"></view>
|
||
<view class="skeleton-text"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 右列骨架 -->
|
||
<view class="skeleton-column">
|
||
<view class="skeleton-card" v-for="n in 3" :key="'right-' + n">
|
||
<view class="skeleton-image" :style="{ height: (n % 2 === 0 ? '200' : '150') + 'px' }"></view>
|
||
<view class="skeleton-info">
|
||
<view class="skeleton-title"></view>
|
||
<view class="skeleton-meta">
|
||
<view class="skeleton-avatar"></view>
|
||
<view class="skeleton-text"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 实际内容 -->
|
||
<view class="waterfall-container" v-else>
|
||
<!-- 左列 -->
|
||
<view class="waterfall-column">
|
||
<view
|
||
v-for="(item, index) in leftColumnData"
|
||
:key="item.id"
|
||
class="inspiration-card"
|
||
@click="viewDetail(item)"
|
||
>
|
||
<!-- 图片 -->
|
||
<view class="card-image-wrapper">
|
||
<image
|
||
:src="item.image"
|
||
mode="widthFix"
|
||
class="card-image"
|
||
lazy-load
|
||
></image>
|
||
<view class="type-tag" :class="item.type === 'video' ? 'video' : 'image'">
|
||
{{ item.type === 'video' ? '视频' : '图文' }}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 信息 -->
|
||
<view class="card-info">
|
||
<text class="card-title">{{ item.title }}</text>
|
||
|
||
<view class="card-meta">
|
||
<view class="author-info">
|
||
<image
|
||
:src="item.author.avatar || item.authorAvatar || '/static/images/avatar-default.png'"
|
||
class="author-avatar"
|
||
mode="aspectFill"
|
||
@error="handleAvatarError($event, item)"
|
||
></image>
|
||
<text class="author-name">{{ item.author.name }}</text>
|
||
</view>
|
||
<view class="card-stats">
|
||
<text class="iconfont icon-aixin"></text>
|
||
<text class="stat-text">{{ formatNumber(item.likeCount) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<!-- 对比按钮 -->
|
||
<view class="card-action" v-if="selectedCategory === 'comparison'">
|
||
<button class="action-btn" @click.stop="goToEffect(item)">对比</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 右列 -->
|
||
<view class="waterfall-column">
|
||
<view
|
||
v-for="(item, index) in rightColumnData"
|
||
:key="item.id"
|
||
class="inspiration-card"
|
||
@click="viewDetail(item)"
|
||
>
|
||
<!-- 图片 -->
|
||
<view class="card-image-wrapper">
|
||
<image
|
||
:src="item.image"
|
||
mode="widthFix"
|
||
class="card-image"
|
||
lazy-load
|
||
></image>
|
||
<view class="type-tag" :class="item.type === 'video' ? 'video' : 'image'">
|
||
{{ item.type === 'video' ? '视频' : '图文' }}
|
||
</view>
|
||
|
||
|
||
</view>
|
||
|
||
<!-- 信息 -->
|
||
<view class="card-info">
|
||
<text class="card-title">{{ item.title }}</text>
|
||
|
||
<view class="card-meta">
|
||
<view class="author-info">
|
||
<image
|
||
:src="item.author.avatar || item.authorAvatar || '/static/images/avatar-default.png'"
|
||
class="author-avatar"
|
||
mode="aspectFill"
|
||
@error="handleAvatarError($event, item)"
|
||
></image>
|
||
<text class="author-name">{{ item.author.name }}</text>
|
||
</view>
|
||
<view class="card-stats">
|
||
<text class="iconfont icon-aixin"></text>
|
||
<text class="stat-text">{{ formatNumber(item.likeCount) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 对比按钮 -->
|
||
<view class="card-action" v-if="selectedCategory === 'comparison'">
|
||
<button class="action-btn" @click.stop="goToEffect(item)">对比</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载更多 -->
|
||
<view class="loading-more" v-if="loading">
|
||
<view class="loading-spinner"></view>
|
||
<text class="loading-text">加载中...</text>
|
||
</view>
|
||
|
||
<!-- 没有更多 -->
|
||
<view class="no-more" v-if="noMore">
|
||
<text class="no-more-text">没有更多了</text>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 底部导航栏 -->
|
||
<view class="bottom-tabbar">
|
||
<view
|
||
v-for="(tab, index) in tabBar"
|
||
:key="index"
|
||
:class="['tab-item', tab.active ? 'active' : '']"
|
||
@click="switchTab(tab.path)"
|
||
>
|
||
<!-- 想象图标 - 使用纯CSS绘制 -->
|
||
<view v-if="tab.label === '想象'" class="tab-icon-imagine">
|
||
<view class="snowflake">
|
||
<view class="snowflake-line snowflake-line-1"></view>
|
||
<view class="snowflake-line snowflake-line-2"></view>
|
||
<view class="snowflake-line snowflake-line-3"></view>
|
||
</view>
|
||
</view>
|
||
<!-- 字体图标 -->
|
||
<text v-else class="iconfont" :class="tab.icon"></text>
|
||
<text class="tab-label">{{ tab.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import api from '@/api/models-api.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
statusBarHeight: 0,
|
||
scrollViewHeight: '100vh',
|
||
scrollViewTop: '0px',
|
||
scrollViewBottom: '0px',
|
||
|
||
// 分类
|
||
selectedCategory: 'hot',
|
||
categories: [
|
||
{ label: '热门模版', value: 'hot', icon: '🔥' },
|
||
{ label: '设计案例', value: 'cases', icon: '✨' },
|
||
{ label: '装修前后', value: 'comparison', icon: '📷' },
|
||
{ label: '视频案例', value: 'videocases', icon: '' },
|
||
{ label: '营销活动', value: 'activity', icon: '' }
|
||
],
|
||
|
||
// 瀑布流数据
|
||
allData: [],
|
||
leftColumnData: [],
|
||
rightColumnData: [],
|
||
|
||
// 加载状态
|
||
loading: false,
|
||
noMore: false,
|
||
page: 1,
|
||
pageSize: 20,
|
||
|
||
// 底部导航
|
||
tabBar: [
|
||
{ label: '推荐', icon: 'icon-shouye', path: '/pages/index/index', active: false },
|
||
{ label: '想象', path: '/pages/ai-generate/index', active: true },
|
||
{ label: '智能体', icon: 'icon-kefujiedai', path: '/pages/ai-generate/agent', active: false },
|
||
{ label: '我的', icon: 'icon-wode', path: '/pages/user/index', active: false }
|
||
]
|
||
};
|
||
},
|
||
|
||
onLoad() {
|
||
this.initPage();
|
||
this.loadData();
|
||
},
|
||
|
||
methods: {
|
||
initPage() {
|
||
const systemInfo = uni.getSystemInfoSync();
|
||
this.statusBarHeight = systemInfo.statusBarHeight || 0;
|
||
|
||
// 计算滚动区域位置和高度
|
||
const navHeight = 44 + 50; // 导航(44px) + 分类标签(50px)
|
||
const tabbarHeight = 50; // 底部导航栏
|
||
|
||
// 设置 scroll-view 的位置
|
||
this.scrollViewTop = `${this.statusBarHeight + navHeight}px`;
|
||
this.scrollViewBottom = `${tabbarHeight}px`;
|
||
|
||
// 计算高度
|
||
const totalHeight = this.statusBarHeight + navHeight + tabbarHeight;
|
||
this.scrollViewHeight = `calc(100vh - ${totalHeight}px)`;
|
||
},
|
||
|
||
// 判断URL是否为图片格式
|
||
isImageUrl(url) {
|
||
if (!url) return false;
|
||
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg'];
|
||
const lowerUrl = (url.split('?')[0].split('#')[0] || '').toLowerCase();
|
||
return imageExtensions.some(ext => lowerUrl.endsWith(ext));
|
||
},
|
||
|
||
// 处理头像URL,添加前缀
|
||
getAvatarUrl(avatarUrl) {
|
||
if (!avatarUrl || avatarUrl === '/static/images/avatar-default.png') {
|
||
return '/static/images/avatar-default.png';
|
||
}
|
||
// 如果已经是完整URL,直接返回
|
||
if (avatarUrl.startsWith('http://') || avatarUrl.startsWith('https://')) {
|
||
return avatarUrl;
|
||
}
|
||
// 如果是相对路径,添加前缀
|
||
const prefix = 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/';
|
||
// 如果URL已经以/开头,去掉开头的/
|
||
const cleanUrl = avatarUrl.startsWith('/') ? avatarUrl.substring(1) : avatarUrl;
|
||
return prefix + cleanUrl;
|
||
},
|
||
isVideoUrl(url) {
|
||
if (!url) return false;
|
||
const videoExtensions = ['.mp4', '.mov', '.m3u8', '.webm', '.avi', '.wmv', '.flv', '.mkv'];
|
||
const lowerUrl = (url.split('?')[0].split('#')[0] || '').toLowerCase();
|
||
return videoExtensions.some(ext => lowerUrl.endsWith(ext));
|
||
},
|
||
|
||
getArticleType(article) {
|
||
const url = article.videoUrl || '';
|
||
return this.isVideoUrl(url) ? 'video' : 'image';
|
||
},
|
||
|
||
// 加载数据
|
||
async loadData() {
|
||
if (this.loading || this.noMore) return;
|
||
|
||
this.loading = true;
|
||
|
||
try {
|
||
const params = {
|
||
page: this.page,
|
||
size: this.pageSize,
|
||
statusTask:1
|
||
};
|
||
|
||
// 如果是装修前后分类,添加type参数
|
||
if (this.selectedCategory === 'comparison') {
|
||
params.type = 1;
|
||
} else if (this.selectedCategory === 'videocases') {
|
||
// 如果是视频案例分类,添加type参数
|
||
params.type = 2;
|
||
}
|
||
|
||
const response = await api.searchArticles(params);
|
||
|
||
if (response.code === 200 && response.data) {
|
||
const articles = response.data.records || [];
|
||
|
||
// 转换文章数据为瀑布流数据格式
|
||
const newData = articles.map(article => {
|
||
// 先获取类型
|
||
const articleType = this.getArticleType(article);
|
||
return {
|
||
id: article.id,
|
||
title: article.title || '未命名作品',
|
||
// 如果类型为image,使用videoUrl;否则使用imageInput
|
||
image: articleType === 'image'
|
||
? (article.videoUrl || '/static/images/placeholder.png')
|
||
: (article.imageInput || '/static/images/placeholder.png'),
|
||
type: articleType, // 根据videoUrl后缀智能判断类型
|
||
articleType: article.type,
|
||
template: true, // 所有文章都可以作为模板使用
|
||
likeCount: parseInt(article.visit) || 0,
|
||
author: {
|
||
name: article.authorName || '创作者',
|
||
avatar: this.getAvatarUrl(article.authorAvatar) || '/static/images/avatar-default.png'
|
||
},
|
||
// 保留原始 authorAvatar 属性,方便直接访问(已处理前缀)
|
||
authorAvatar: this.getAvatarUrl(article.authorAvatar) || '/static/images/avatar-default.png',
|
||
// 额外的文章信息
|
||
imageInput: article.imageInput, // 添加 imageInput
|
||
prompt: article.prompt,
|
||
synopsis: article.synopsis,
|
||
videoUrl: article.videoUrl,
|
||
taskId: article.taskId,
|
||
aspectRatio: article.aspectRatio,
|
||
createTime: article.createTime
|
||
};
|
||
});
|
||
|
||
// 分配数据到瀑布流
|
||
this.distributeData(newData);
|
||
|
||
// 更新分页状态
|
||
if (newData.length < this.pageSize) {
|
||
this.noMore = true;
|
||
}
|
||
this.page++;
|
||
} else {
|
||
uni.showToast({
|
||
title: '加载失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('加载文章列表失败:', error);
|
||
uni.showToast({
|
||
title: '网络错误,请重试',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
// 分配数据到左右列(瀑布流)
|
||
distributeData(newData) {
|
||
newData.forEach(item => {
|
||
// 简化算法:交替分配
|
||
if (this.leftColumnData.length <= this.rightColumnData.length) {
|
||
this.leftColumnData.push(item);
|
||
} else {
|
||
this.rightColumnData.push(item);
|
||
}
|
||
});
|
||
},
|
||
|
||
// 选择分类
|
||
selectCategory(value) {
|
||
this.selectedCategory = value;
|
||
// 重置数据
|
||
this.leftColumnData = [];
|
||
this.rightColumnData = [];
|
||
this.page = 1;
|
||
this.noMore = false;
|
||
this.loadData();
|
||
},
|
||
|
||
// 滚动事件
|
||
onScroll(e) {
|
||
// 可以添加滚动逻辑,如返回顶部按钮显示等
|
||
},
|
||
|
||
// 加载更多
|
||
loadMore() {
|
||
this.loadData();
|
||
},
|
||
|
||
// 查看详情
|
||
viewDetail(item) {
|
||
console.log('点击文章详情:', item);
|
||
|
||
// 根据类型跳转到不同页面
|
||
if (item.type === 'video' && item.videoUrl) {
|
||
uni.navigateTo({
|
||
url: `/pages/ai-generate/video?id=${item.id}`
|
||
});
|
||
} else if (item.type === 'image') {
|
||
uni.navigateTo({
|
||
url: `/pages/ai-generate/image?id=${item.id}`
|
||
});
|
||
} else {
|
||
uni.showToast({
|
||
title: `查看${item.title}`,
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
|
||
// 跳转到对比效果页
|
||
goToEffect(item) {
|
||
const beforeImage = encodeURIComponent(item.imageInput || '');
|
||
const afterImage = encodeURIComponent(item.videoUrl || '');
|
||
const promptText = encodeURIComponent(item.prompt || item.synopsis || '');
|
||
|
||
uni.navigateTo({
|
||
url: `/pages/ai-generate/effect?id=${item.id}&beforeImage=${beforeImage}&afterImage=${afterImage}&promptText=${promptText}`
|
||
});
|
||
},
|
||
|
||
// 使用模板
|
||
useTemplate(item) {
|
||
uni.showModal({
|
||
title: '使用模板',
|
||
content: '是否使用此模板生成图片?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 跳转到生成页面,并传递模板信息
|
||
uni.navigateTo({
|
||
url: `/pages/ai-generate/index?templateId=${item.id}`
|
||
});
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
// 跳转搜索
|
||
goToSearch() {
|
||
uni.navigateTo({
|
||
url: '/pages/ai-generate/search'
|
||
});
|
||
},
|
||
|
||
// 切换Tab
|
||
switchTab(path) {
|
||
if (path === '/pages/ai-generate/inspiration') {
|
||
return; // 当前页面
|
||
}
|
||
|
||
// 推荐页(首页)和我的页使用switchTab
|
||
if (path === '/pages/index/index' || path === '/pages/user/index') {
|
||
this.tabBar = this.tabBar.map(t => ({ ...t, active: t.path === path }))
|
||
uni.switchTab({ url: path });
|
||
} else {
|
||
// 想象页(AI生成)使用navigateTo
|
||
this.tabBar = this.tabBar.map(t => ({ ...t, active: t.path === path }))
|
||
uni.navigateTo({ url: path });
|
||
}
|
||
},
|
||
|
||
// 格式化数字
|
||
formatNumber(num) {
|
||
if (num >= 10000) {
|
||
return (num / 10000).toFixed(1) + 'w';
|
||
} else if (num >= 1000) {
|
||
return (num / 1000).toFixed(1) + 'k';
|
||
}
|
||
return num.toString();
|
||
},
|
||
|
||
// 处理头像加载错误
|
||
handleAvatarError(e, item) {
|
||
console.log('头像加载失败,重置为默认头像:', e);
|
||
if (item) {
|
||
if (item.author) {
|
||
item.author.avatar = '/static/images/avatar-default.png';
|
||
}
|
||
item.authorAvatar = '/static/images/avatar-default.png';
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.inspiration-container {
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #000000 0%, #1a1a1a 100%);
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
// 顶部导航
|
||
.nav-bar {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: rgba(10, 14, 26, 0.95);
|
||
backdrop-filter: blur(20px);
|
||
z-index: 999;
|
||
|
||
.nav-content {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 44px;
|
||
padding: 0 16px;
|
||
position: relative;
|
||
|
||
.nav-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
text-align: center;
|
||
}
|
||
|
||
.nav-right {
|
||
position: absolute;
|
||
right: 16px;
|
||
|
||
.search-btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.iconfont {
|
||
font-size: 20px;
|
||
color: #ffffff;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// 分类标签
|
||
.category-tabs {
|
||
position: relative;
|
||
white-space: nowrap;
|
||
padding: 0 16px 12px;
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 0px;
|
||
background: linear-gradient(90deg, rgba(255,255,255,0.18), rgba(255,255,255,0.06), rgba(255,255,255,0.18));
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
.tab-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 6px 16px;
|
||
margin-right: 12px; margin-top: 2px;
|
||
background: linear-gradient(180deg, rgba(50, 56, 72, 0.55) 0%, rgba(36, 40, 54, 0.65) 100%);
|
||
border-radius: 20px;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
box-shadow: inset 0 1px 2px rgba(255,255,255,0.12), 0 4px 8px rgba(0,0,0,0.25);
|
||
transition: all 180ms ease;
|
||
.tab-icon {
|
||
font-size: 14px;
|
||
}
|
||
.tab-text {
|
||
font-size: 14px;
|
||
color: #8f9bb3;
|
||
transition: color 180ms ease, text-shadow 180ms ease;
|
||
}
|
||
&.active {
|
||
background: radial-gradient(circle at 50% 20%, rgba(255,255,255,0.16) 0%, rgba(255,255,255,0.06) 45%, rgba(255,255,255,0) 62%), linear-gradient(180deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%);
|
||
border-color: #42ca4d;
|
||
box-shadow: inset 0 2px 6px rgba(255,255,255,0.12), 0 6px 12px rgba(0,0,0,0.35);
|
||
transform: translateY(-1px);
|
||
.tab-text {
|
||
color: #42ca4d;
|
||
text-shadow: 0 0 8px rgba(66, 202, 77, 0.45);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 内容滚动区
|
||
.content-scroll {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
padding: 0px 12px;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
|
||
// AI生成内容标识 - 在滚动区域内
|
||
.ai-notice {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: 6px 16px;
|
||
margin: 0 0 16px 0;
|
||
background: rgba(66, 202, 77, 0.1);
|
||
border: 1px solid rgba(66, 202, 77, 0.3);
|
||
border-radius: 8px;
|
||
backdrop-filter: blur(10px);
|
||
|
||
.ai-notice-icon {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.ai-notice-text {
|
||
font-size: 11px;
|
||
color: #42ca4d;
|
||
line-height: 1.4;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 骨架屏容器
|
||
.skeleton-container {
|
||
width: 100%;
|
||
padding: 0;
|
||
}
|
||
|
||
.skeleton-waterfall {
|
||
display: flex;
|
||
width: 100%;
|
||
gap: 12px;
|
||
|
||
.skeleton-column {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
|
||
.skeleton-card {
|
||
width: 100%;
|
||
background: rgba(26, 31, 46, 0.8);
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
|
||
.skeleton-image {
|
||
width: 100%;
|
||
height: 180px;
|
||
background: linear-gradient(90deg,
|
||
rgba(255, 255, 255, 0.05) 25%,
|
||
rgba(255, 255, 255, 0.1) 50%,
|
||
rgba(255, 255, 255, 0.05) 75%);
|
||
background-size: 200% 100%;
|
||
animation: shimmer 1.5s infinite;
|
||
}
|
||
|
||
.skeleton-info {
|
||
padding: 12px;
|
||
|
||
.skeleton-title {
|
||
width: 80%;
|
||
height: 16px;
|
||
background: linear-gradient(90deg,
|
||
rgba(255, 255, 255, 0.05) 25%,
|
||
rgba(255, 255, 255, 0.1) 50%,
|
||
rgba(255, 255, 255, 0.05) 75%);
|
||
background-size: 200% 100%;
|
||
border-radius: 4px;
|
||
margin-bottom: 12px;
|
||
animation: shimmer 1.5s infinite;
|
||
animation-delay: 0.1s;
|
||
}
|
||
|
||
.skeleton-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.skeleton-avatar {
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(90deg,
|
||
rgba(255, 255, 255, 0.05) 25%,
|
||
rgba(255, 255, 255, 0.1) 50%,
|
||
rgba(255, 255, 255, 0.05) 75%);
|
||
background-size: 200% 100%;
|
||
animation: shimmer 1.5s infinite;
|
||
animation-delay: 0.2s;
|
||
}
|
||
|
||
.skeleton-text {
|
||
flex: 1;
|
||
height: 12px;
|
||
background: linear-gradient(90deg,
|
||
rgba(255, 255, 255, 0.05) 25%,
|
||
rgba(255, 255, 255, 0.1) 50%,
|
||
rgba(255, 255, 255, 0.05) 75%);
|
||
background-size: 200% 100%;
|
||
border-radius: 4px;
|
||
animation: shimmer 1.5s infinite;
|
||
animation-delay: 0.3s;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes shimmer {
|
||
0% { background-position: -200% 0; }
|
||
100% { background-position: 200% 0; }
|
||
}
|
||
|
||
// 瀑布流容器
|
||
.waterfall-container {
|
||
display: flex;
|
||
width: 100%; margin-top: 30rpx;
|
||
gap: 12px;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
|
||
.waterfall-column {
|
||
flex: 1;
|
||
min-width: 0;
|
||
max-width: calc(50% - 6px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
|
||
// 灵感卡片
|
||
.inspiration-card {
|
||
width: 100%;
|
||
background: rgba(26, 31, 46, 0.8);
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
break-inside: avoid;
|
||
|
||
.card-image-wrapper {
|
||
position: relative;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
background: rgba(0, 0, 0, 0.2);
|
||
|
||
.card-image {
|
||
width: 100%;
|
||
height: auto;
|
||
min-height: 100px;
|
||
display: block;
|
||
// border-radius: 16px 16px 0 0;
|
||
}
|
||
|
||
.media-tag {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
width: 28px;
|
||
height: 28px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
backdrop-filter: blur(10px);
|
||
|
||
.iconfont {
|
||
font-size: 14px;
|
||
color: #ffffff;
|
||
}
|
||
}
|
||
|
||
.type-tag {
|
||
position: absolute;
|
||
top: 8px;
|
||
left: 8px;
|
||
padding: 2px 8px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
color: #ffffff;
|
||
background: rgba(0,0,0,0.5);
|
||
border: 1px solid rgba(255,255,255,0.25);
|
||
backdrop-filter: blur(8px);
|
||
&.video {
|
||
border-color: rgba(33,150,243,0.5);
|
||
}
|
||
&.image {
|
||
border-color: rgba(66,202,77,0.5);
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-info {
|
||
padding: 12px;
|
||
|
||
.card-title {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #ffffff;
|
||
margin-bottom: 8px;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
line-clamp: 2;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.card-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
|
||
.author-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
|
||
.author-avatar {
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.author-name {
|
||
font-size: 12px;
|
||
color: #8f9bb3;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.card-stats {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex-shrink: 0;
|
||
|
||
.iconfont {
|
||
font-size: 14px;
|
||
color: #8f9bb3;
|
||
}
|
||
|
||
.stat-text {
|
||
font-size: 12px;
|
||
color: #8f9bb3;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-action {
|
||
padding: 0 12px 12px;
|
||
|
||
.action-btn {
|
||
width: 100%;
|
||
height: 32px;
|
||
background: linear-gradient(135deg, #42ca4d 0%, #38b045 100%);
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
color: #ffffff;
|
||
border: none;
|
||
line-height: 32px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 加载更多
|
||
.loading-more {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: 20px;
|
||
width: 100%;
|
||
clear: both;
|
||
|
||
.loading-spinner {
|
||
width: 20px;
|
||
height: 20px;
|
||
border: 2px solid rgba(255, 255, 255, 0.2);
|
||
border-top-color: #42ca4d;
|
||
border-radius: 50%;
|
||
animation: spin 0.8s linear infinite;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 13px;
|
||
color: #8f9bb3;
|
||
}
|
||
}
|
||
|
||
// 没有更多
|
||
.no-more {
|
||
padding: 20px;
|
||
text-align: center;
|
||
width: 100%;
|
||
clear: both;
|
||
|
||
.no-more-text {
|
||
font-size: 13px;
|
||
color: #8f9bb3;
|
||
}
|
||
}
|
||
|
||
// 底部导航栏
|
||
.bottom-tabbar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
height: calc(50px + env(safe-area-inset-bottom));
|
||
box-sizing: border-box;
|
||
align-items: center;
|
||
background: linear-gradient(180deg, rgba(20, 24, 34, 0.96) 0%, rgba(10, 12, 18, 0.98) 100%);
|
||
backdrop-filter: blur(20px);
|
||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
z-index: 999;
|
||
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.35), 0 -12px 24px rgba(0, 0, 0, 0.25);
|
||
position: fixed;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: linear-gradient(90deg, rgba(255,255,255,0.18), rgba(255,255,255,0.06), rgba(255,255,255,0.18));
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.tab-item {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 2px;
|
||
border-radius: 12px;
|
||
padding: 2px 6px; margin:0px 20px;
|
||
transition: all 180ms ease;
|
||
position: relative;
|
||
|
||
.iconfont {
|
||
font-size: 22px;
|
||
color: #8f9bb3;
|
||
transition: color 180ms ease, transform 180ms ease, text-shadow 180ms ease;
|
||
}
|
||
|
||
.tab-icon-imagine {
|
||
width: 22px;
|
||
height: 22px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
position: relative;
|
||
|
||
.snowflake {
|
||
width: 22px;
|
||
height: 22px;
|
||
position: relative;
|
||
|
||
.snowflake-line {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
width: 18px;
|
||
height: 2px;
|
||
background: #8f9bb3;
|
||
transform-origin: center;
|
||
|
||
&::before,
|
||
&::after {
|
||
content: '';
|
||
position: absolute;
|
||
width: 6px;
|
||
height: 2px;
|
||
background: #8f9bb3;
|
||
}
|
||
|
||
&::before {
|
||
top: -4px;
|
||
left: 2px;
|
||
transform: rotate(-45deg);
|
||
}
|
||
|
||
&::after {
|
||
top: 4px;
|
||
left: 2px;
|
||
transform: rotate(45deg);
|
||
}
|
||
}
|
||
|
||
.snowflake-line-1 {
|
||
transform: translate(-50%, -50%) rotate(0deg);
|
||
}
|
||
|
||
.snowflake-line-2 {
|
||
transform: translate(-50%, -50%) rotate(60deg);
|
||
}
|
||
|
||
.snowflake-line-3 {
|
||
transform: translate(-50%, -50%) rotate(120deg);
|
||
}
|
||
}
|
||
}
|
||
|
||
.tab-label {
|
||
font-size: 10px;
|
||
color: #8f9bb3;
|
||
transition: color 180ms ease, text-shadow 180ms ease;
|
||
}
|
||
|
||
&.active {
|
||
background: radial-gradient(circle at 50% 20%, rgba(255,255,255,0.16) 0%, rgba(255,255,255,0.06) 42%, rgba(255,255,255,0) 60%),
|
||
linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.02) 100%);
|
||
box-shadow: inset 0 2px 6px rgba(255,255,255,0.12), 0 6px 12px rgba(0,0,0,0.35);
|
||
border: 1px solid rgba(255,255,255,0.08);
|
||
transform: translateY(-1px);
|
||
|
||
.iconfont,
|
||
.tab-label {
|
||
color: #42ca4d;
|
||
text-shadow: 0 0 8px rgba(66, 202, 77, 0.45);
|
||
}
|
||
|
||
.tab-icon-imagine {
|
||
.snowflake-line {
|
||
background: #42ca4d;
|
||
|
||
&::before,
|
||
&::after {
|
||
background: #42ca4d;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 动画
|
||
@keyframes spin {
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
</style>
|
||
|