a
This commit is contained in:
@@ -0,0 +1,558 @@
|
||||
<template>
|
||||
<view class="home-container">
|
||||
<!-- 顶部搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input" @click="goToSearch">
|
||||
<text class="search-icon">🔍</text>
|
||||
<text class="search-placeholder">搜索wanandroid.com内容</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Banner轮播 -->
|
||||
<home-banner
|
||||
:banner-list="bannerList"
|
||||
@click="handleBannerClick"
|
||||
/>
|
||||
|
||||
<!-- 文章列表 -->
|
||||
<view class="article-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">最新文章</text>
|
||||
<text class="section-count" v-if="total > 0">({{ total }})</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<common-loading
|
||||
v-if="loading && articleList.length === 0"
|
||||
:visible="true"
|
||||
text="加载中..."
|
||||
/>
|
||||
|
||||
<!-- 文章列表 -->
|
||||
<view v-else>
|
||||
<!-- 空状态 -->
|
||||
<empty-state
|
||||
v-if="!loading && articleList.length === 0"
|
||||
:title="'暂无文章'"
|
||||
:description="'下拉刷新试试'"
|
||||
:show-action="true"
|
||||
:action-text="'刷新'"
|
||||
@action="refreshData"
|
||||
/>
|
||||
|
||||
<!-- 文章卡片列表 -->
|
||||
<view
|
||||
class="article-card"
|
||||
v-for="article in articleList"
|
||||
:key="article.id"
|
||||
@click="handleArticleClick(article)"
|
||||
>
|
||||
<view class="article-header">
|
||||
<text class="article-title">{{ article.title }}</text>
|
||||
<view class="article-tags">
|
||||
<text class="tag fresh" v-if="article.fresh">新</text>
|
||||
<text class="tag top" v-if="article.top">置顶</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="article-summary" v-if="article.desc">
|
||||
{{ article.desc }}
|
||||
</view>
|
||||
|
||||
<view class="article-meta">
|
||||
<view class="meta-item">
|
||||
<text class="meta-icon">👤</text>
|
||||
<text class="meta-text">{{ article.author || article.shareUser || '未知作者' }}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-icon">🏷️</text>
|
||||
<text class="meta-text">{{ article.superChapterName }} · {{ article.chapterName }}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-icon">🕒</text>
|
||||
<text class="meta-text">{{ formatRelativeTime(article.publishTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="article-actions">
|
||||
<view class="action-item" @click.stop="handleLike(article)">
|
||||
<text class="action-icon">❤️</text>
|
||||
<text class="action-text">{{ article.zan || 0 }}</text>
|
||||
</view>
|
||||
<view class="action-item" @click.stop="handleCollect(article)">
|
||||
<text class="action-icon" :class="{collected: article.collect}">⭐</text>
|
||||
<text class="action-text" :class="{collected: article.collect}">
|
||||
{{ article.collect ? '已收藏' : '收藏' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="action-item" @click.stop="handleShare(article)">
|
||||
<text class="action-icon">↗️</text>
|
||||
<text class="action-text">分享</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view class="load-more" v-if="articleList.length > 0">
|
||||
<uni-load-more
|
||||
:status="loadMoreStatus"
|
||||
:content-text="loadMoreText"
|
||||
@clickLoadMore="loadMoreData"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 回到顶部 -->
|
||||
<view
|
||||
class="back-to-top"
|
||||
v-if="showBackTop"
|
||||
@click="scrollToTop"
|
||||
>
|
||||
<text class="back-top-icon">↑</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 导入公共组件
|
||||
import CommonLoading from '@/components/common/loading.vue'
|
||||
import EmptyState from '@/components/common/empty-state.vue'
|
||||
import HomeBanner from '@/components/home/banner.vue'
|
||||
|
||||
// 导入API
|
||||
import { getHomeData } from '@/common/api/home.api.js'
|
||||
|
||||
// 导入工具函数
|
||||
import { formatRelativeTime } from '@/common/utils/common.js'
|
||||
|
||||
export default {
|
||||
name: 'HomePage',
|
||||
components: {
|
||||
CommonLoading,
|
||||
EmptyState,
|
||||
HomeBanner
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Banner数据
|
||||
bannerList: [],
|
||||
// 文章数据
|
||||
articleList: [],
|
||||
// 分页信息
|
||||
currentPage: 0,
|
||||
total: 0,
|
||||
pageCount: 0,
|
||||
// 加载状态
|
||||
loading: false,
|
||||
loadMoreStatus: 'more', // more, loading, noMore
|
||||
showBackTop: false,
|
||||
// 加载文本
|
||||
loadMoreText: {
|
||||
contentdown: '上拉显示更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.initHomeData()
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
console.log('首页下拉刷新')
|
||||
this.refreshData()
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
console.log('首页上拉加载更多')
|
||||
if (this.loadMoreStatus === 'more') {
|
||||
this.loadMoreData()
|
||||
}
|
||||
},
|
||||
|
||||
onPageScroll(e) {
|
||||
this.showBackTop = e.scrollTop > 400
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化首页数据
|
||||
async initHomeData() {
|
||||
this.loading = true
|
||||
try {
|
||||
const homeData = await getHomeData(this.currentPage)
|
||||
this.bannerList = homeData.bannerList || []
|
||||
this.articleList = homeData.articleList || []
|
||||
this.total = homeData.total || 0
|
||||
this.pageCount = homeData.pageCount || 0
|
||||
|
||||
// 更新加载状态
|
||||
this.updateLoadMoreStatus()
|
||||
} catch (error) {
|
||||
console.error('初始化首页数据失败:', error)
|
||||
uni.showToast({
|
||||
title: '数据加载失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
},
|
||||
|
||||
// 刷新数据
|
||||
async refreshData() {
|
||||
this.currentPage = 0
|
||||
this.articleList = []
|
||||
await this.initHomeData()
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
},
|
||||
|
||||
// 加载更多数据
|
||||
async loadMoreData() {
|
||||
if (this.loadMoreStatus !== 'more') return
|
||||
|
||||
this.loadMoreStatus = 'loading'
|
||||
try {
|
||||
const nextPage = this.currentPage + 1
|
||||
const homeData = await getHomeData(nextPage)
|
||||
|
||||
// 追加数据
|
||||
this.articleList = [...this.articleList, ...(homeData.articleList || [])]
|
||||
this.currentPage = nextPage
|
||||
this.total = homeData.total || 0
|
||||
|
||||
// 更新加载状态
|
||||
this.updateLoadMoreStatus()
|
||||
} catch (error) {
|
||||
console.error('加载更多数据失败:', error)
|
||||
uni.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
this.loadMoreStatus = 'more'
|
||||
}
|
||||
},
|
||||
|
||||
// 更新加载更多状态
|
||||
updateLoadMoreStatus() {
|
||||
if (this.currentPage >= this.pageCount - 1) {
|
||||
this.loadMoreStatus = 'noMore'
|
||||
} else {
|
||||
this.loadMoreStatus = 'more'
|
||||
}
|
||||
},
|
||||
|
||||
// 处理Banner点击
|
||||
handleBannerClick(item) {
|
||||
console.log('点击Banner:', item)
|
||||
this.$emit('banner-click', item)
|
||||
|
||||
if (item.url) {
|
||||
this.navigateToWebView(item.title, item.url)
|
||||
}
|
||||
},
|
||||
|
||||
// 处理文章点击
|
||||
handleArticleClick(article) {
|
||||
console.log('点击文章:', article)
|
||||
this.$emit('article-click', article)
|
||||
|
||||
if (article.link) {
|
||||
this.navigateToWebView(article.title, article.link)
|
||||
}
|
||||
},
|
||||
|
||||
// 跳转到WebView页面
|
||||
navigateToWebView(title, url) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/webview/webview?title=${encodeURIComponent(title)}&url=${encodeURIComponent(url)}`
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转到搜索页面
|
||||
goToSearch() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/search/search'
|
||||
})
|
||||
},
|
||||
|
||||
// 处理点赞
|
||||
handleLike(article) {
|
||||
console.log('点赞文章:', article.id)
|
||||
// TODO: 实现点赞逻辑
|
||||
uni.showToast({
|
||||
title: '点赞成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
},
|
||||
|
||||
// 处理收藏
|
||||
handleCollect(article) {
|
||||
console.log('收藏文章:', article.id)
|
||||
// TODO: 实现收藏逻辑
|
||||
article.collect = !article.collect
|
||||
uni.showToast({
|
||||
title: article.collect ? '收藏成功' : '取消收藏',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
},
|
||||
|
||||
// 处理分享
|
||||
handleShare(article) {
|
||||
console.log('分享文章:', article.id)
|
||||
// TODO: 实现分享逻辑
|
||||
uni.showToast({
|
||||
title: '分享功能开发中',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
},
|
||||
|
||||
// 回到顶部
|
||||
scrollToTop() {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: 300
|
||||
})
|
||||
},
|
||||
|
||||
// 工具函数
|
||||
formatRelativeTime
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-container {
|
||||
padding-bottom: 40rpx;
|
||||
background-color: #f8f8f8;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 50rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 16rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 文章区域 */
|
||||
.article-section {
|
||||
margin-top: 20rpx;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
/* 文章卡片 */
|
||||
.article-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.article-card:active {
|
||||
transform: translateY(-2rpx);
|
||||
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.article-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
flex: 1;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.article-tags {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tag.fresh {
|
||||
background-color: #ff4444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tag.top {
|
||||
background-color: #ff9900;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.article-summary {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 24rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
padding-bottom: 24rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.meta-icon {
|
||||
margin-right: 8rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.meta-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.article-actions {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.action-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 36rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.action-icon.collected {
|
||||
color: #ff9900;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-text.collected {
|
||||
color: #ff9900;
|
||||
}
|
||||
|
||||
/* 加载更多 */
|
||||
.load-more {
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
|
||||
/* 回到顶部 */
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
right: 40rpx;
|
||||
bottom: 160rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: rgba(60, 197, 31, 0.9);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 4rpx 20rpx rgba(60, 197, 31, 0.3);
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.back-top-icon {
|
||||
color: white;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.back-to-top:active {
|
||||
background: rgba(60, 197, 31, 1);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user