This commit is contained in:
mzltMpfqGS
2026-04-15 19:45:16 +08:00
commit e4a087e67e
301 changed files with 35048 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
// pages/article-detail/article-detail.js
// 文章详情逻辑处理
const app = getApp()
const api = require('../../services/api')
Page({
data: {
articleId: null,
article: null,
commentList: [],
commentPage: 1,
commentPageSize: 10,
commentHasMore: true,
commentLoading: false,
commentContent: '',
replyTo: null,
isFavorite: false,
isLike: false,
likeCount: 0,
favoriteCount: 0
},
onLoad(options) {
if (options.id) {
this.setData({ articleId: options.id })
this.loadArticleDetail()
this.loadCommentList()
}
},
onShow() {
if (this.data.articleId) {
api.getArticleDetail(this.data.articleId).then(res => {
if (res.code === 200 && res.data) {
this.setData({
isFavorite: res.data.isFavorite || false,
isLike: res.data.isLike || false,
likeCount: res.data.likeCount || 0,
favoriteCount: res.data.favoriteCount || 0
})
}
})
}
},
onShareAppMessage() {
const article = this.data.article
if (article) {
return {
title: article.title,
path: `/pages/article-detail/article-detail?id=${article.id}`,
imageUrl: article.coverImage
}
}
},
// 加载文章详情
loadArticleDetail() {
wx.showLoading({ title: '加载中...' })
api.getArticleDetail(this.data.articleId).then(res => {
wx.hideLoading()
if (res.code === 200) {
this.setData({
article: res.data,
isFavorite: res.data.isFavorite || false,
isLike: res.data.isLike || false,
likeCount: res.data.likeCount || 0,
favoriteCount: res.data.favoriteCount || 0
})
this.addViewCount()
} else {
wx.showToast({ title: res.message || '加载失败', icon: 'none' })
}
}).catch(() => {
wx.hideLoading()
wx.showToast({ title: '加载失败', icon: 'none' })
})
},
// 增加浏览次数
addViewCount() {
api.addViewCount({ articleId: this.data.articleId }).then(() => {
const article = this.data.article
if (article) {
article.viewCount = (article.viewCount || 0) + 1
this.setData({ article })
}
})
},
// 加载评论列表
loadCommentList() {
if (this.data.commentLoading || !this.data.commentHasMore) return
this.setData({ commentLoading: true })
api.getCommentList({
articleId: this.data.articleId,
page: this.data.commentPage,
pageSize: this.data.commentPageSize
}).then(res => {
this.setData({ commentLoading: false })
if (res.code === 200) {
const list = res.data.list || []
this.setData({
commentList: this.data.commentPage === 1 ? list : this.data.commentList.concat(list),
commentHasMore: list.length >= this.data.commentPageSize
})
}
}).catch(() => {
this.setData({ commentLoading: false })
})
},
// 触底加载更多评论
onReachBottom() {
if (this.data.commentHasMore && !this.data.commentLoading) {
this.setData({ commentPage: this.data.commentPage + 1 })
this.loadCommentList()
}
},
// 收藏文章
onFavorite() {
if (!app.checkLogin()) return
const isFavorite = !this.data.isFavorite
api.updateFavorite({ articleId: this.data.articleId, isFavorite }).then(res => {
if (res.code === 200) {
this.setData({
isFavorite,
favoriteCount: isFavorite ? this.data.favoriteCount + 1 : this.data.favoriteCount - 1
})
wx.showToast({
title: isFavorite ? '收藏成功' : '取消收藏',
icon: 'success'
})
}
})
},
// 点赞文章
onLike() {
if (!app.checkLogin()) return
const isLike = !this.data.isLike
api.updateLike({ articleId: this.data.articleId, isLike }).then(res => {
if (res.code === 200) {
this.setData({
isLike,
likeCount: isLike ? this.data.likeCount + 1 : this.data.likeCount - 1
})
}
})
},
// 输入评论
onCommentInput(e) {
this.setData({ commentContent: e.detail.value })
},
// 提交评论
submitComment() {
if (!app.checkLogin()) return
const content = this.data.commentContent.trim()
if (!content) {
wx.showToast({ title: '请输入评论内容', icon: 'none' })
return
}
api.addComment({
articleId: this.data.articleId,
content,
parentId: this.data.replyTo ? this.data.replyTo.id : null
}).then(res => {
if (res.code === 200) {
wx.showToast({ title: '评论成功', icon: 'success' })
this.setData({ commentContent: '', replyTo: null })
this.refreshCommentList()
} else {
wx.showToast({ title: res.message || '评论失败', icon: 'none' })
}
})
},
// 刷新评论列表
refreshCommentList() {
this.setData({ commentPage: 1, commentList: [], commentHasMore: true })
this.loadCommentList()
},
// 回复评论
onReply(e) {
const comment = e.currentTarget.dataset.comment
this.setData({ replyTo: comment })
wx.showModal({
title: '回复 ' + comment.nickname,
editable: true,
placeholderText: '请输入回复内容',
success: res => {
if (res.confirm && res.content) {
api.addComment({
articleId: this.data.articleId,
content: res.content,
parentId: comment.id
}).then(res => {
if (res.code === 200) {
wx.showToast({ title: '回复成功', icon: 'success' })
this.refreshCommentList()
}
})
}
}
})
},
// 复制链接
onCopyLink() {
wx.setClipboardData({
data: this.data.article.shareUrl || '',
success: () => {
wx.showToast({ title: '链接已复制', icon: 'success' })
}
})
},
// 关注作者
onFollow() {
if (!app.checkLogin()) return
const article = this.data.article
if (!article || !article.authorId) return
api.followUser({ userId: article.authorId }).then(res => {
if (res.code === 200) {
wx.showToast({ title: '关注成功', icon: 'success' })
article.isFollowing = true
this.setData({ article })
}
})
}
})
/**
* @联系作者:16768118056
* @描述:(此项目非免费分享)源码百分百可用,搞定毕设不发愁,支持远程调试安装、二次开发、定制、讲解、文档类。
* @访问:https://www.notmaker.com/detail/8326a897119d4542896c863a69d7bbe7/gtt20260415,查看完整运行演示。
*/

View File

@@ -0,0 +1,11 @@
{
"usingComponents": {},
"navigationBarTitleText": "文章详情",
"backgroundTextStyle": "dark"
}
/**
* @联系作者:16768118056
* @描述:(此项目非免费分享)源码百分百可用,搞定毕设不发愁,支持远程调试安装、二次开发、定制、讲解、文档类。
* @访问:https://www.notmaker.com/detail/8326a897119d4542896c863a69d7bbe7/gtt20260415,查看完整运行演示。
*/

View File

@@ -0,0 +1,117 @@
<!-- pages/article-detail/article-detail.wxml -->
<!-- 文章详情视图 -->
<view class="article-detail-container">
<!-- 文章内容 -->
<view class="article-content" wx:if="{{article}}">
<!-- 标题 -->
<view class="article-header">
<text class="article-title">{{article.title}}</text>
<view class="article-meta">
<view class="author-info" bindtap="goToAuthor">
<image class="author-avatar" src="{{article.authorAvatar || '/static/images/default-avatar.png'}}" />
<text class="author-name">{{article.authorName}}</text>
</view>
<text class="publish-time">{{article.createTime}}</text>
<text class="view-count">阅读 {{article.viewCount}}</text>
</view>
</view>
<!-- 封面图 -->
<image class="article-cover" wx:if="{{article.coverImage}}" src="{{article.coverImage}}" mode="widthFix" />
<!-- 文章正文 -->
<view class="article-body">
<rich-text nodes="{{article.content}}"></rich-text>
</view>
<!-- 标签 -->
<view class="article-tags" wx:if="{{article.tags && article.tags.length > 0}}">
<text class="tag" wx:for="{{article.tags}}" wx:key="id">{{item.name}}</text>
</view>
<!-- 点赞和收藏 -->
<view class="article-actions">
<view class="action-item {{isLike ? 'active' : ''}}" bindtap="onLike">
<text class="iconfont {{isLike ? 'icon-like-fill' : 'icon-like'}}"></text>
<text>{{likeCount}}</text>
</view>
<view class="action-item {{isFavorite ? 'active' : ''}}" bindtap="onFavorite">
<text class="iconfont {{isFavorite ? 'icon-favorite-fill' : 'icon-favorite'}}"></text>
<text>{{favoriteCount}}</text>
</view>
<view class="action-item" bindtap="onCopyLink">
<text class="iconfont icon-share"></text>
<text>分享</text>
</view>
</view>
<!-- 关注作者 -->
<view class="follow-author" wx:if="{{article.authorId && !article.isFollowing}}">
<view class="author-card">
<image class="author-avatar-lg" src="{{article.authorAvatar || '/static/images/default-avatar.png'}}" />
<view class="author-info">
<text class="author-name">{{article.authorName}}</text>
<text class="author-bio">{{article.authorBio || '这家伙很懒,什么都没写'}}</text>
</view>
<button class="follow-btn" bindtap="onFollow">关注</button>
</view>
</view>
</view>
<!-- 分割线 -->
<view class="section-divider">
<text>评论</text>
</view>
<!-- 评论列表 -->
<view class="comment-list">
<view class="comment-item" wx:for="{{commentList}}" wx:key="id">
<image class="comment-avatar" src="{{item.avatar || '/static/images/default-avatar.png'}}" />
<view class="comment-content">
<view class="comment-header">
<text class="comment-nickname">{{item.nickname}}</text>
<text class="comment-time">{{item.createTime}}</text>
</view>
<text class="comment-text">{{item.content}}</text>
<view class="comment-actions">
<text class="reply-btn" bindtap="onReply" data-comment="{{item}}">回复</text>
</view>
<!-- 子评论 -->
<view class="sub-comment-list" wx:if="{{item.children && item.children.length > 0}}">
<view class="sub-comment-item" wx:for="{{item.children}}" wx:for-item="subItem" wx:key="id">
<image class="sub-comment-avatar" src="{{subItem.avatar || '/static/images/default-avatar.png'}}" />
<view class="sub-comment-content">
<text class="sub-comment-nickname">{{subItem.nickname}}</text>
<text class="sub-comment-text">{{subItem.content}}</text>
<text class="sub-comment-time">{{subItem.createTime}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="loading-more" wx:if="{{commentLoading}}">
<text>加载中...</text>
</view>
<view class="no-more" wx:if="{{!commentHasMore && commentList.length > 0}}">
<text>没有更多评论了</text>
</view>
<view class="empty-state" wx:if="{{!commentLoading && commentList.length === 0}}">
<text>暂无评论,快来抢沙发</text>
</view>
</view>
<!-- 底部评论输入 -->
<view class="comment-input-bar">
<input class="comment-input" placeholder="{{replyTo ? '回复 ' + replyTo.nickname : '写评论...'}}" bindinput="onCommentInput" value="{{commentContent}}" />
<button class="submit-btn" bindtap="submitComment">发送</button>
</view>
</view>
/**
* @联系作者:16768118056
* @描述:(此项目非免费分享)源码百分百可用,搞定毕设不发愁,支持远程调试安装、二次开发、定制、讲解、文档类。
* @访问:https://www.notmaker.com/detail/8326a897119d4542896c863a69d7bbe7/gtt20260415,查看完整运行演示。
*/

View File

@@ -0,0 +1,336 @@
/* pages/article-detail/article-detail.wxss */
/* 文章详情样式 */
.article-detail-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
.article-content {
background-color: #fff;
padding: 30rpx;
}
.article-header {
margin-bottom: 30rpx;
}
.article-title {
display: block;
font-size: 40rpx;
font-weight: bold;
color: #333;
line-height: 1.5;
margin-bottom: 24rpx;
}
.article-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 20rpx;
}
.author-info {
display: flex;
align-items: center;
}
.author-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
margin-right: 12rpx;
}
.author-name {
font-size: 26rpx;
color: #666;
}
.publish-time,
.view-count {
font-size: 24rpx;
color: #999;
}
.article-cover {
width: 100%;
border-radius: 12rpx;
margin-bottom: 30rpx;
}
.article-body {
font-size: 30rpx;
line-height: 1.8;
color: #333;
margin-bottom: 30rpx;
}
.article-tags {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 30rpx;
}
.tag {
padding: 8rpx 20rpx;
font-size: 24rpx;
color: #667eea;
background-color: rgba(102, 126, 234, 0.1);
border-radius: 24rpx;
}
.article-actions {
display: flex;
justify-content: space-around;
padding: 30rpx 0;
border-top: 1rpx solid #f0f0f0;
border-bottom: 1rpx solid #f0f0f0;
}
.action-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.action-item .iconfont {
font-size: 44rpx;
color: #999;
}
.action-item text {
font-size: 24rpx;
color: #999;
}
.action-item.active .iconfont {
color: #667eea;
}
.action-item.active text {
color: #667eea;
}
.follow-author {
padding: 30rpx 0;
}
.author-card {
display: flex;
align-items: center;
padding: 24rpx;
background-color: #f9f9f9;
border-radius: 16rpx;
}
.author-avatar-lg {
width: 80rpx;
height: 80rpx;
border-radius: 40rpx;
margin-right: 20rpx;
}
.author-info {
flex: 1;
display: flex;
flex-direction: column;
}
.author-info .author-name {
font-size: 28rpx;
color: #333;
font-weight: bold;
margin-bottom: 8rpx;
}
.author-bio {
font-size: 24rpx;
color: #999;
}
.follow-btn {
padding: 12rpx 32rpx;
font-size: 26rpx;
color: #fff;
background-color: #667eea;
border-radius: 32rpx;
}
.section-divider {
display: flex;
align-items: center;
justify-content: center;
padding: 40rpx 30rpx 30rpx;
background-color: #fff;
}
.section-divider text {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.comment-list {
background-color: #fff;
padding: 0 30rpx;
}
.comment-item {
display: flex;
padding: 30rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.comment-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 32rpx;
margin-right: 20rpx;
flex-shrink: 0;
}
.comment-content {
flex: 1;
min-width: 0;
}
.comment-header {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.comment-nickname {
font-size: 28rpx;
font-weight: bold;
color: #333;
margin-right: 16rpx;
}
.comment-time {
font-size: 22rpx;
color: #999;
}
.comment-text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
margin-bottom: 12rpx;
}
.comment-actions {
display: flex;
}
.reply-btn {
font-size: 24rpx;
color: #667eea;
}
.sub-comment-list {
background-color: #f9f9f9;
border-radius: 12rpx;
padding: 20rpx;
margin-top: 16rpx;
}
.sub-comment-item {
display: flex;
margin-bottom: 16rpx;
}
.sub-comment-item:last-child {
margin-bottom: 0;
}
.sub-comment-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
margin-right: 12rpx;
flex-shrink: 0;
}
.sub-comment-content {
flex: 1;
}
.sub-comment-nickname {
font-size: 24rpx;
font-weight: bold;
color: #333;
margin-right: 12rpx;
}
.sub-comment-text {
font-size: 26rpx;
color: #333;
line-height: 1.5;
}
.sub-comment-time {
font-size: 20rpx;
color: #999;
margin-top: 4rpx;
display: block;
}
.loading-more,
.no-more {
text-align: center;
padding: 30rpx;
font-size: 24rpx;
color: #999;
}
.empty-state {
text-align: center;
padding: 60rpx;
font-size: 26rpx;
color: #999;
}
.comment-input-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #fff;
border-top: 1rpx solid #f0f0f0;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.05);
}
.comment-input {
flex: 1;
height: 72rpx;
padding: 0 24rpx;
font-size: 28rpx;
background-color: #f5f5f5;
border-radius: 36rpx;
}
.submit-btn {
margin-left: 20rpx;
padding: 0 32rpx;
height: 72rpx;
line-height: 72rpx;
font-size: 28rpx;
color: #fff;
background-color: #667eea;
border-radius: 36rpx;
}
/**
* @联系作者:16768118056
* @描述:(此项目非免费分享)源码百分百可用,搞定毕设不发愁,支持远程调试安装、二次开发、定制、讲解、文档类。
* @访问:https://www.notmaker.com/detail/8326a897119d4542896c863a69d7bbe7/gtt20260415,查看完整运行演示。
*/