12 changed files with 1931 additions and 731 deletions
-
3app.json
-
9pages/home/home.js
-
9pages/home/home.wxml
-
12pages/home/home.wxss
-
BINpagesB/images/hf.png
-
346pagesB/pages/forumlist/forumlist.js
-
3pagesB/pages/forumlist/forumlist.json
-
202pagesB/pages/forumlist/forumlist.wxml
-
442pagesB/pages/forumlist/forumlist.wxss
-
482pagesB/pages/onlineAsk/onlineAsk.js
-
192pagesB/pages/onlineAsk/onlineAsk.wxml
-
612pagesB/pages/onlineAsk/onlineAsk.wxss
|
After Width: 200 | Height: 200 | Size: 3.8 KiB |
@ -0,0 +1,346 @@ |
|||||
|
Page({ |
||||
|
data: { |
||||
|
posts: [], |
||||
|
loading: false, |
||||
|
loadingMore: false, |
||||
|
refreshing: false, |
||||
|
hasMore: true, |
||||
|
page: 1, |
||||
|
pageSize: 10, |
||||
|
currentFilter: 'all', |
||||
|
searchKeyword: '', |
||||
|
currentUser: '当前用户' |
||||
|
}, |
||||
|
|
||||
|
onLoad: function() { |
||||
|
this.loadPosts(); |
||||
|
|
||||
|
// 监听页面显示,用于刷新数据
|
||||
|
wx.onAppShow(() => { |
||||
|
this.refreshData(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
onShow: function() { |
||||
|
this.refreshData(); |
||||
|
}, |
||||
|
|
||||
|
// 加载帖子列表
|
||||
|
loadPosts: function(reset = false) { |
||||
|
if (reset) { |
||||
|
this.setData({ |
||||
|
page: 1, |
||||
|
hasMore: true, |
||||
|
posts: [], |
||||
|
loading: true |
||||
|
}); |
||||
|
} else if (this.data.loadingMore) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const params = { |
||||
|
page: this.data.page, |
||||
|
pageSize: this.data.pageSize, |
||||
|
filter: this.data.currentFilter, |
||||
|
search: this.data.searchKeyword |
||||
|
}; |
||||
|
|
||||
|
this.setData({ |
||||
|
loading: reset || this.data.page === 1, |
||||
|
loadingMore: !reset && this.data.page > 1 |
||||
|
}); |
||||
|
|
||||
|
// 模拟API请求
|
||||
|
setTimeout(() => { |
||||
|
const mockPosts = this.generateMockPosts(params); |
||||
|
|
||||
|
if (reset) { |
||||
|
this.setData({ |
||||
|
posts: mockPosts, |
||||
|
loading: false, |
||||
|
hasMore: mockPosts.length === params.pageSize |
||||
|
}); |
||||
|
} else { |
||||
|
this.setData({ |
||||
|
posts: [...this.data.posts, ...mockPosts], |
||||
|
loading: false, |
||||
|
loadingMore: false, |
||||
|
hasMore: mockPosts.length === params.pageSize |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (this.data.refreshing) { |
||||
|
wx.stopPullDownRefresh(); |
||||
|
this.setData({ refreshing: false }); |
||||
|
} |
||||
|
}, 800); |
||||
|
}, |
||||
|
|
||||
|
// 生成模拟数据
|
||||
|
generateMockPosts: function(params) { |
||||
|
const posts = []; |
||||
|
const currentUser = this.data.currentUser; |
||||
|
const baseId = (params.page - 1) * params.pageSize; |
||||
|
|
||||
|
for (let i = 0; i < params.pageSize; i++) { |
||||
|
const id = baseId + i + 1; |
||||
|
const solved = i % 4 === 0; |
||||
|
const hot = i % 3 === 0 && i % 2 === 0; |
||||
|
const isMine = i % 5 === 0; |
||||
|
|
||||
|
// 根据筛选条件过滤
|
||||
|
if (params.filter === 'solved' && !solved) continue; |
||||
|
if (params.filter === 'unsolved' && solved) continue; |
||||
|
if (params.filter === 'mine' && !isMine) continue; |
||||
|
|
||||
|
const tags = this.getRandomTags(); |
||||
|
const post = { |
||||
|
id: id, |
||||
|
title: this.getRandomTitle(id), |
||||
|
summary: this.getRandomSummary(id), |
||||
|
username: isMine ? currentUser : this.getRandomUsername(), |
||||
|
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
|
time: this.getRandomTime(), |
||||
|
likeCount: Math.floor(Math.random() * 50), |
||||
|
replyCount: Math.floor(Math.random() * 30), |
||||
|
viewCount: Math.floor(Math.random() * 300), |
||||
|
solved: solved, |
||||
|
hot: hot, |
||||
|
tags: tags, |
||||
|
lastReply: Math.random() > 0.3 ? { |
||||
|
username: this.getRandomUsername(), |
||||
|
time: this.getRandomTime('short') |
||||
|
} : null |
||||
|
}; |
||||
|
|
||||
|
// 搜索过滤
|
||||
|
if (params.search) { |
||||
|
const keyword = params.search.toLowerCase(); |
||||
|
const titleMatch = post.title.toLowerCase().includes(keyword); |
||||
|
const summaryMatch = post.summary.toLowerCase().includes(keyword); |
||||
|
const tagMatch = post.tags.some(tag => tag.toLowerCase().includes(keyword)); |
||||
|
|
||||
|
if (!titleMatch && !summaryMatch && !tagMatch) { |
||||
|
continue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
posts.push(post); |
||||
|
} |
||||
|
|
||||
|
// 热门排序
|
||||
|
if (params.filter === 'hot') { |
||||
|
posts.sort((a, b) => { |
||||
|
const aScore = a.likeCount * 2 + a.replyCount * 3 + a.viewCount; |
||||
|
const bScore = b.likeCount * 2 + b.replyCount * 3 + b.viewCount; |
||||
|
return bScore - aScore; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return posts; |
||||
|
}, |
||||
|
|
||||
|
// 随机生成标题
|
||||
|
getRandomTitle: function(id) { |
||||
|
const titles = [ |
||||
|
'微信小程序如何实现图片上传和预览功能?', |
||||
|
'uni-app开发中如何处理不同平台的兼容性问题?', |
||||
|
'JavaScript闭包的使用场景有哪些?', |
||||
|
'Vue3组合式API和选项式API该如何选择?', |
||||
|
'React Hooks在项目中的最佳实践', |
||||
|
'Node.js高并发场景下的性能优化方案', |
||||
|
'TypeScript在大型项目中的类型设计经验分享', |
||||
|
'微信小程序云开发数据库查询性能优化', |
||||
|
'前端工程化建设:从零搭建Webpack配置', |
||||
|
'移动端H5页面适配的最佳方案是什么?', |
||||
|
'如何优雅地处理前端错误监控和上报?', |
||||
|
'微前端架构在实际项目中的应用经验', |
||||
|
'Webpack5 Module Federation实战分享', |
||||
|
'前端代码质量保证:ESLint + Prettier + Husky', |
||||
|
'跨端开发框架选型:Flutter vs React Native vs uni-app' |
||||
|
]; |
||||
|
return titles[id % titles.length] || titles[0]; |
||||
|
}, |
||||
|
|
||||
|
// 随机生成摘要
|
||||
|
getRandomSummary: function(id) { |
||||
|
const summaries = [ |
||||
|
'我正在开发一个微信小程序,需要实现图片上传功能,并且能够在上传前预览图片。请问有什么好的实现方案吗?上传的图片大小限制和格式有什么建议?', |
||||
|
'最近在做一个uni-app项目,需要同时兼容微信小程序和H5,遇到了一些样式和API兼容性问题,大家有什么好的解决方案吗?', |
||||
|
'在实际项目中经常使用闭包,但对其原理和应用场景理解还不够深入,想请教一下大家在项目中都是如何使用闭包的?', |
||||
|
'公司新项目准备使用Vue3,对于组合式API和选项式API的选择有些纠结,大家有什么建议吗?各自的使用场景是什么?', |
||||
|
'React Hooks确实很方便,但在大型项目中如何合理组织Hooks,避免过度使用导致代码难以维护?', |
||||
|
'我们的Node.js服务在高并发场景下性能表现不佳,有哪些常见的性能优化方案可以参考?', |
||||
|
'项目准备从JavaScript迁移到TypeScript,在类型设计方面有什么经验可以分享吗?如何设计合理的泛型和接口?' |
||||
|
]; |
||||
|
return summaries[id % summaries.length] || summaries[0]; |
||||
|
}, |
||||
|
|
||||
|
// 随机生成用户名
|
||||
|
getRandomUsername: function() { |
||||
|
const usernames = [ |
||||
|
'前端工程师', '技术爱好者', '小程序开发', '全栈程序员', |
||||
|
'架构师老王', '代码艺术家', '算法工程师', '产品经理', |
||||
|
'UI设计师', '测试工程师', '运维小哥', '数据分析师' |
||||
|
]; |
||||
|
return usernames[Math.floor(Math.random() * usernames.length)]; |
||||
|
}, |
||||
|
|
||||
|
// 随机生成时间
|
||||
|
getRandomTime: function(type = 'normal') { |
||||
|
const times = type === 'short' |
||||
|
? ['5分钟前', '10分钟前', '半小时前', '1小时前'] |
||||
|
: ['2小时前', '5小时前', '昨天', '2天前', '3天前', '一周前']; |
||||
|
return times[Math.floor(Math.random() * times.length)]; |
||||
|
}, |
||||
|
|
||||
|
// 随机生成标签
|
||||
|
getRandomTags: function() { |
||||
|
const allTags = [ |
||||
|
'微信小程序', '前端开发', 'JavaScript', 'Vue.js', 'React', |
||||
|
'Node.js', 'TypeScript', 'uni-app', '性能优化', '工程化', |
||||
|
'移动端', 'H5', 'CSS', 'Webpack', 'Git' |
||||
|
]; |
||||
|
|
||||
|
const count = Math.floor(Math.random() * 3) + 1; |
||||
|
const tags = []; |
||||
|
const usedIndices = new Set(); |
||||
|
|
||||
|
for (let i = 0; i < count; i++) { |
||||
|
let index; |
||||
|
do { |
||||
|
index = Math.floor(Math.random() * allTags.length); |
||||
|
} while (usedIndices.has(index)); |
||||
|
|
||||
|
usedIndices.add(index); |
||||
|
tags.push(allTags[index]); |
||||
|
} |
||||
|
|
||||
|
return tags; |
||||
|
}, |
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onRefresh: function() { |
||||
|
this.setData({ refreshing: true }); |
||||
|
this.loadPosts(true); |
||||
|
}, |
||||
|
|
||||
|
// 滚动到底部加载更多
|
||||
|
loadMore: function() { |
||||
|
if (!this.data.hasMore || this.data.loadingMore) return; |
||||
|
|
||||
|
this.setData({ |
||||
|
page: this.data.page + 1 |
||||
|
}, () => { |
||||
|
this.loadPosts(); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 筛选切换
|
||||
|
changeFilter: function(e) { |
||||
|
const filterType = e.currentTarget.dataset.type; |
||||
|
if (this.data.currentFilter === filterType) return; |
||||
|
|
||||
|
this.setData({ |
||||
|
currentFilter: filterType, |
||||
|
searchKeyword: '' // 切换筛选时清空搜索
|
||||
|
}, () => { |
||||
|
this.loadPosts(true); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 搜索输入
|
||||
|
onSearchInput: function(e) { |
||||
|
this.setData({ searchKeyword: e.detail.value }); |
||||
|
|
||||
|
// 防抖搜索
|
||||
|
clearTimeout(this.searchTimer); |
||||
|
this.searchTimer = setTimeout(() => { |
||||
|
if (e.detail.value.trim()) { |
||||
|
this.loadPosts(true); |
||||
|
} |
||||
|
}, 300); |
||||
|
}, |
||||
|
|
||||
|
// 搜索确认
|
||||
|
onSearchConfirm: function(e) { |
||||
|
const keyword = e.detail.value.trim(); |
||||
|
if (keyword) { |
||||
|
this.setData({ searchKeyword: keyword }); |
||||
|
this.loadPosts(true); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 清空搜索
|
||||
|
clearSearch: function() { |
||||
|
this.setData({ |
||||
|
searchKeyword: '', |
||||
|
currentFilter: 'all' |
||||
|
}, () => { |
||||
|
this.loadPosts(true); |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 跳转到详情页
|
||||
|
goToDetail: function(e) { |
||||
|
const postId = e.currentTarget.dataset.id; |
||||
|
wx.navigateTo({ |
||||
|
url: `/pages/forum/detail/detail?id=${postId}`, |
||||
|
success: () => { |
||||
|
// 记录浏览历史
|
||||
|
this.recordViewHistory(postId); |
||||
|
} |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 创建新帖子
|
||||
|
createPost: function() { |
||||
|
wx.navigateTo({ |
||||
|
url: '/pages/forum/create/create' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 记录浏览历史
|
||||
|
recordViewHistory: function(postId) { |
||||
|
try { |
||||
|
const history = wx.getStorageSync('forumViewHistory') || []; |
||||
|
const index = history.findIndex(item => item.id === postId); |
||||
|
|
||||
|
if (index !== -1) { |
||||
|
history.splice(index, 1); |
||||
|
} |
||||
|
|
||||
|
history.unshift({ |
||||
|
id: postId, |
||||
|
timestamp: Date.now() |
||||
|
}); |
||||
|
|
||||
|
// 只保留最近50条记录
|
||||
|
if (history.length > 50) { |
||||
|
history.pop(); |
||||
|
} |
||||
|
|
||||
|
wx.setStorageSync('forumViewHistory', history); |
||||
|
} catch (error) { |
||||
|
console.error('记录浏览历史失败:', error); |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 刷新数据
|
||||
|
refreshData: function() { |
||||
|
// 这里可以检查是否有新数据需要刷新
|
||||
|
// 例如:从详情页返回时刷新点赞状态等
|
||||
|
}, |
||||
|
|
||||
|
onPullDownRefresh: function() { |
||||
|
this.onRefresh(); |
||||
|
}, |
||||
|
|
||||
|
onReachBottom: function() { |
||||
|
this.loadMore(); |
||||
|
}, |
||||
|
|
||||
|
onPageScroll: function(e) { |
||||
|
// 可以在这里处理页面滚动时的效果
|
||||
|
} |
||||
|
}); |
||||
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"usingComponents": {} |
||||
|
} |
||||
@ -0,0 +1,202 @@ |
|||||
|
<view class="forum-list-page"> |
||||
|
|
||||
|
<!-- 顶部栏 --> |
||||
|
<view class="header"> |
||||
|
<view class="title">问答论坛</view> |
||||
|
<button class="add-btn" bindtap="createPost">提问</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 筛选栏 --> |
||||
|
<view class="filter-bar"> |
||||
|
<scroll-view class="filter-scroll" scroll-x> |
||||
|
<view class="filter-list"> |
||||
|
<view |
||||
|
class="filter-item {{currentFilter === 'all' ? 'active' : ''}}" |
||||
|
bindtap="changeFilter" |
||||
|
data-type="all" |
||||
|
> |
||||
|
<text>全部</text> |
||||
|
</view> |
||||
|
<view |
||||
|
class="filter-item {{currentFilter === 'unsolved' ? 'active' : ''}}" |
||||
|
bindtap="changeFilter" |
||||
|
data-type="unsolved" |
||||
|
> |
||||
|
<text>待解决</text> |
||||
|
</view> |
||||
|
<view |
||||
|
class="filter-item {{currentFilter === 'solved' ? 'active' : ''}}" |
||||
|
bindtap="changeFilter" |
||||
|
data-type="solved" |
||||
|
> |
||||
|
<text>已解决</text> |
||||
|
</view> |
||||
|
<view |
||||
|
class="filter-item {{currentFilter === 'hot' ? 'active' : ''}}" |
||||
|
bindtap="changeFilter" |
||||
|
data-type="hot" |
||||
|
> |
||||
|
<text>热门</text> |
||||
|
</view> |
||||
|
<view |
||||
|
class="filter-item {{currentFilter === 'mine' ? 'active' : ''}}" |
||||
|
bindtap="changeFilter" |
||||
|
data-type="mine" |
||||
|
> |
||||
|
<text>我的帖子</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</scroll-view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 搜索框 --> |
||||
|
<view class="search-container"> |
||||
|
<view class="search-input-wrapper"> |
||||
|
<image class="search-icon" src="/images/search.png" mode="aspectFit"></image> |
||||
|
<input |
||||
|
class="search-input" |
||||
|
placeholder="搜索问题或关键词..." |
||||
|
value="{{searchKeyword}}" |
||||
|
bindinput="onSearchInput" |
||||
|
bindconfirm="onSearchConfirm" |
||||
|
confirm-type="search" |
||||
|
/> |
||||
|
<view class="search-clear" wx:if="{{searchKeyword}}" bindtap="clearSearch"> |
||||
|
× |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 帖子列表 --> |
||||
|
<scroll-view |
||||
|
class="post-list-container" |
||||
|
scroll-y |
||||
|
enable-back-to-top |
||||
|
bindscrolltolower="loadMore" |
||||
|
refresher-enabled="{{true}}" |
||||
|
refresher-triggered="{{refreshing}}" |
||||
|
bindrefresherrefresh="onRefresh" |
||||
|
> |
||||
|
|
||||
|
<!-- 空状态 --> |
||||
|
<view class="empty-state" wx:if="{{!loading && posts.length === 0}}"> |
||||
|
<image class="empty-image" src="/images/empty-forum.png" mode="aspectFit"></image> |
||||
|
<view class="empty-text" wx:if="{{searchKeyword}}"> |
||||
|
没有找到"{{searchKeyword}}"相关的问题 |
||||
|
</view> |
||||
|
<view class="empty-text" wx:else> |
||||
|
暂无帖子,快来发布第一个问题吧! |
||||
|
</view> |
||||
|
<button class="empty-btn" bindtap="createPost" wx:if="{{!searchKeyword}}">发布问题</button> |
||||
|
<button class="empty-btn" bindtap="clearSearch" wx:else>清空搜索</button> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 帖子列表 --> |
||||
|
<view class="post-list" wx:if="{{posts.length > 0}}"> |
||||
|
<block wx:for="{{posts}}" wx:key="id"> |
||||
|
<view class="post-item" data-id="{{item.id}}" bindtap="goToDetail"> |
||||
|
|
||||
|
<!-- 左侧状态栏 --> |
||||
|
<view class="post-status-side"> |
||||
|
<view class="vote-count"> |
||||
|
<view class="vote-number">{{item.likeCount}}</view> |
||||
|
<view class="vote-label">点赞</view> |
||||
|
</view> |
||||
|
<view class="reply-count"> |
||||
|
<view class="reply-number">{{item.replyCount}}</view> |
||||
|
<view class="reply-label">回答</view> |
||||
|
</view> |
||||
|
<view class="view-count"> |
||||
|
<view class="view-number">{{item.viewCount}}</view> |
||||
|
<view class="view-label">浏览</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 右侧内容区 --> |
||||
|
<view class="post-content-main"> |
||||
|
|
||||
|
<!-- 帖子标题和状态 --> |
||||
|
<view class="post-header"> |
||||
|
<view class="post-title">{{item.title}}</view> |
||||
|
<view class="status-badge" wx:if="{{item.solved}}"> |
||||
|
<text class="solved-badge">已解决</text> |
||||
|
</view> |
||||
|
<view class="status-badge" wx:if="{{item.hot}}"> |
||||
|
<text class="hot-badge">热门</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 帖子内容摘要 --> |
||||
|
<view class="post-summary">{{item.summary}}</view> |
||||
|
|
||||
|
<!-- 帖子元信息 --> |
||||
|
<view class="post-meta"> |
||||
|
|
||||
|
<!-- 用户信息 --> |
||||
|
<view class="user-info"> |
||||
|
<image class="user-avatar" src="{{item.avatar}}" mode="aspectFill"></image> |
||||
|
<text class="username">{{item.username}}</text> |
||||
|
<text class="separator">·</text> |
||||
|
<text class="post-time">{{item.time}}</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 标签 --> |
||||
|
<view class="post-tags" wx:if="{{item.tags && item.tags.length > 0}}"> |
||||
|
<block wx:for="{{item.tags.slice(0, 2)}}" wx:key="index"> |
||||
|
<text class="tag">{{item}}</text> |
||||
|
</block> |
||||
|
<text class="more-tags" wx:if="{{item.tags.length > 2}}">+{{item.tags.length - 2}}</text> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
|
||||
|
<!-- 最后回复信息 --> |
||||
|
<view class="last-reply" wx:if="{{item.lastReply}}"> |
||||
|
<text class="last-reply-label">最后回复:</text> |
||||
|
<text class="last-reply-user">{{item.lastReply.username}}</text> |
||||
|
<text class="separator">·</text> |
||||
|
<text class="last-reply-time">{{item.lastReply.time}}</text> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
</block> |
||||
|
|
||||
|
<!-- 加载更多 --> |
||||
|
<view class="load-more" wx:if="{{hasMore && posts.length > 0}}"> |
||||
|
<view class="loading-text" wx:if="{{!loadingMore}}"> |
||||
|
上拉加载更多 |
||||
|
</view> |
||||
|
<view class="loading-more" wx:else> |
||||
|
<image class="loading-icon" src="/images/loading.png" mode="aspectFit"></image> |
||||
|
加载中... |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 没有更多了 --> |
||||
|
<view class="no-more" wx:if="{{!hasMore && posts.length > 0}}"> |
||||
|
<text>没有更多了</text> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
|
|
||||
|
<!-- 底部占位 --> |
||||
|
<view class="bottom-placeholder"></view> |
||||
|
|
||||
|
</scroll-view> |
||||
|
|
||||
|
<!-- 加载提示 --> |
||||
|
<view class="loading" wx:if="{{loading && posts.length === 0}}"> |
||||
|
<image class="loading-icon" src="/images/loading.png" mode="aspectFit"></image> |
||||
|
加载中... |
||||
|
</view> |
||||
|
|
||||
|
<!-- 创建帖子浮动按钮 --> |
||||
|
<view class="fab-container" wx:if="{{!searchKeyword}}"> |
||||
|
<view class="fab" bindtap="createPost"> |
||||
|
<image class="fab-icon" src="/images/add.png" mode="aspectFit"></image> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
</view> |
||||
@ -0,0 +1,442 @@ |
|||||
|
.forum-list-page { |
||||
|
min-height: 100vh; |
||||
|
background-color: #f8f8f8; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
/* 顶部栏 */ |
||||
|
.header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
padding: 60rpx 30rpx 20rpx; |
||||
|
background-color: #fff; |
||||
|
position: sticky; |
||||
|
top: 0; |
||||
|
z-index: 100; |
||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
font-size: 40rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.add-btn { |
||||
|
background-color: #07c160; |
||||
|
color: white; |
||||
|
font-size: 28rpx; |
||||
|
padding: 12rpx 30rpx; |
||||
|
line-height: normal; |
||||
|
border-radius: 40rpx; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
/* 筛选栏 */ |
||||
|
.filter-bar { |
||||
|
background-color: #fff; |
||||
|
padding: 20rpx 30rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
position: sticky; |
||||
|
top: 120rpx; |
||||
|
z-index: 99; |
||||
|
} |
||||
|
|
||||
|
.filter-scroll { |
||||
|
white-space: nowrap; |
||||
|
height: 60rpx; |
||||
|
} |
||||
|
|
||||
|
.filter-list { |
||||
|
display: inline-flex; |
||||
|
gap: 30rpx; |
||||
|
} |
||||
|
|
||||
|
.filter-item { |
||||
|
padding: 0 20rpx; |
||||
|
height: 60rpx; |
||||
|
line-height: 60rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
border-radius: 30rpx; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.filter-item.active { |
||||
|
background-color: rgba(7, 193, 96, 0.1); |
||||
|
color: #07c160; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
/* 搜索框 */ |
||||
|
.search-container { |
||||
|
padding: 20rpx 30rpx; |
||||
|
background-color: #fff; |
||||
|
position: sticky; |
||||
|
top: 200rpx; |
||||
|
z-index: 98; |
||||
|
} |
||||
|
|
||||
|
.search-input-wrapper { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
background-color: #f8f8f8; |
||||
|
border-radius: 40rpx; |
||||
|
padding: 0 30rpx; |
||||
|
height: 80rpx; |
||||
|
} |
||||
|
|
||||
|
.search-icon { |
||||
|
width: 36rpx; |
||||
|
height: 36rpx; |
||||
|
margin-right: 20rpx; |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
.search-input { |
||||
|
flex: 1; |
||||
|
font-size: 28rpx; |
||||
|
height: 80rpx; |
||||
|
line-height: 80rpx; |
||||
|
} |
||||
|
|
||||
|
.search-clear { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
line-height: 40rpx; |
||||
|
text-align: center; |
||||
|
font-size: 36rpx; |
||||
|
color: #999; |
||||
|
border-radius: 50%; |
||||
|
background-color: #e0e0e0; |
||||
|
} |
||||
|
|
||||
|
/* 帖子列表容器 */ |
||||
|
.post-list-container { |
||||
|
height: calc(100vh - 300rpx); |
||||
|
padding: 20rpx 0; |
||||
|
} |
||||
|
|
||||
|
/* 空状态 */ |
||||
|
.empty-state { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 120rpx 30rpx; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.empty-image { |
||||
|
width: 300rpx; |
||||
|
height: 300rpx; |
||||
|
margin-bottom: 40rpx; |
||||
|
opacity: 0.6; |
||||
|
} |
||||
|
|
||||
|
.empty-text { |
||||
|
font-size: 30rpx; |
||||
|
color: #999; |
||||
|
margin-bottom: 40rpx; |
||||
|
} |
||||
|
|
||||
|
.empty-btn { |
||||
|
background-color: #07c160; |
||||
|
color: white; |
||||
|
font-size: 28rpx; |
||||
|
padding: 20rpx 50rpx; |
||||
|
line-height: normal; |
||||
|
border-radius: 40rpx; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
/* 帖子列表 */ |
||||
|
.post-list { |
||||
|
padding: 0 30rpx; |
||||
|
} |
||||
|
|
||||
|
.post-item { |
||||
|
display: flex; |
||||
|
background-color: white; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 30rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); |
||||
|
transition: transform 0.3s, box-shadow 0.3s; |
||||
|
} |
||||
|
|
||||
|
.post-item:active { |
||||
|
transform: translateY(-2rpx); |
||||
|
box-shadow: 0 6rpx 30rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
/* 左侧状态栏 */ |
||||
|
.post-status-side { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
margin-right: 30rpx; |
||||
|
min-width: 120rpx; |
||||
|
} |
||||
|
|
||||
|
.vote-count, |
||||
|
.reply-count, |
||||
|
.view-count { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.vote-count .vote-number, |
||||
|
.reply-count .reply-number, |
||||
|
.view-count .view-number { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
margin-bottom: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.vote-count .vote-label, |
||||
|
.reply-count .reply-label, |
||||
|
.view-count .view-label { |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 右侧内容区 */ |
||||
|
.post-content-main { |
||||
|
flex: 1; |
||||
|
min-width: 0; |
||||
|
} |
||||
|
|
||||
|
/* 帖子标题和状态 */ |
||||
|
.post-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16rpx; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 12rpx; |
||||
|
} |
||||
|
|
||||
|
.post-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: bold; |
||||
|
color: #333; |
||||
|
line-height: 1.4; |
||||
|
flex: 1; |
||||
|
min-width: 0; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
|
||||
|
.status-badge { |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.solved-badge { |
||||
|
background-color: #07c160; |
||||
|
color: white; |
||||
|
font-size: 22rpx; |
||||
|
padding: 4rpx 12rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.hot-badge { |
||||
|
background-color: #ff6b6b; |
||||
|
color: white; |
||||
|
font-size: 22rpx; |
||||
|
padding: 4rpx 12rpx; |
||||
|
border-radius: 20rpx; |
||||
|
} |
||||
|
|
||||
|
/* 帖子内容摘要 */ |
||||
|
.post-summary { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.5; |
||||
|
margin-bottom: 20rpx; |
||||
|
display: -webkit-box; |
||||
|
-webkit-box-orient: vertical; |
||||
|
-webkit-line-clamp: 2; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
/* 帖子元信息 */ |
||||
|
.post-meta { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 15rpx; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 15rpx; |
||||
|
} |
||||
|
|
||||
|
.user-info { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.user-avatar { |
||||
|
width: 40rpx; |
||||
|
height: 40rpx; |
||||
|
border-radius: 50%; |
||||
|
margin-right: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.username { |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.separator { |
||||
|
margin: 0 8rpx; |
||||
|
} |
||||
|
|
||||
|
.post-time { |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
/* 标签 */ |
||||
|
.post-tags { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10rpx; |
||||
|
} |
||||
|
|
||||
|
.tag { |
||||
|
font-size: 22rpx; |
||||
|
color: #07c160; |
||||
|
background-color: rgba(7, 193, 96, 0.1); |
||||
|
padding: 4rpx 12rpx; |
||||
|
border-radius: 20rpx; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.more-tags { |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
margin-left: 4rpx; |
||||
|
} |
||||
|
|
||||
|
/* 最后回复信息 */ |
||||
|
.last-reply { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
padding-top: 15rpx; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.last-reply-label { |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.last-reply-user { |
||||
|
color: #07c160; |
||||
|
margin: 0 5rpx; |
||||
|
} |
||||
|
|
||||
|
/* 加载更多 */ |
||||
|
.load-more { |
||||
|
padding: 40rpx 0; |
||||
|
text-align: center; |
||||
|
color: #999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.loading-text { |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.loading-more { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
.loading-more .loading-icon { |
||||
|
width: 50rpx; |
||||
|
height: 50rpx; |
||||
|
margin-bottom: 10rpx; |
||||
|
} |
||||
|
|
||||
|
/* 没有更多了 */ |
||||
|
.no-more { |
||||
|
padding: 40rpx 0; |
||||
|
text-align: center; |
||||
|
color: #999; |
||||
|
font-size: 26rpx; |
||||
|
} |
||||
|
|
||||
|
/* 底部占位 */ |
||||
|
.bottom-placeholder { |
||||
|
height: 100rpx; |
||||
|
} |
||||
|
|
||||
|
/* 加载提示 */ |
||||
|
.loading { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 100rpx 0; |
||||
|
color: #999; |
||||
|
font-size: 28rpx; |
||||
|
} |
||||
|
|
||||
|
.loading-icon { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
animation: rotate 1s linear infinite; |
||||
|
} |
||||
|
|
||||
|
@keyframes rotate { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
|
||||
|
/* 浮动按钮 */ |
||||
|
.fab-container { |
||||
|
position: fixed; |
||||
|
bottom: 60rpx; |
||||
|
right: 40rpx; |
||||
|
z-index: 1000; |
||||
|
} |
||||
|
|
||||
|
.fab { |
||||
|
width: 100rpx; |
||||
|
height: 100rpx; |
||||
|
background-color: #07c160; |
||||
|
border-radius: 50%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
box-shadow: 0 8rpx 30rpx rgba(7, 193, 96, 0.3); |
||||
|
transition: transform 0.3s, box-shadow 0.3s; |
||||
|
} |
||||
|
|
||||
|
.fab:active { |
||||
|
transform: scale(0.95); |
||||
|
box-shadow: 0 4rpx 20rpx rgba(7, 193, 96, 0.2); |
||||
|
} |
||||
|
|
||||
|
.fab-icon { |
||||
|
width: 44rpx; |
||||
|
height: 44rpx; |
||||
|
} |
||||
|
|
||||
|
/* 移除按钮边框 */ |
||||
|
button::after { |
||||
|
border: none; |
||||
|
} |
||||
|
|
||||
|
/* 滚动条样式 */ |
||||
|
::-webkit-scrollbar { |
||||
|
width: 0; |
||||
|
height: 0; |
||||
|
color: transparent; |
||||
|
} |
||||
@ -1,305 +1,407 @@ |
|||||
// pages/forum/forum.js
|
|
||||
Page({ |
Page({ |
||||
data: { |
data: { |
||||
// 帖子列表数据
|
|
||||
posts: [], |
|
||||
// 是否显示发帖弹窗
|
|
||||
showPostModal: false, |
|
||||
// 发帖标题
|
|
||||
postTitle: '', |
|
||||
// 发帖内容
|
|
||||
postContent: '', |
|
||||
// 标签输入
|
|
||||
tagInput: '', |
|
||||
// 已选标签
|
|
||||
selectedTags: [], |
|
||||
// 当前回复的帖子索引
|
|
||||
activeReplyIndex: -1, |
|
||||
// 回复内容
|
|
||||
|
post: null, |
||||
replyContent: '', |
replyContent: '', |
||||
// 加载状态
|
|
||||
|
replyTarget: { |
||||
|
type: '', |
||||
|
username: '', |
||||
|
replyId: '', |
||||
|
replyIndex: null, |
||||
|
commentId: '', |
||||
|
commentIndex: null |
||||
|
}, |
||||
|
replyPlaceholder: '输入您的回复...', |
||||
|
isInputFocused: false, |
||||
|
inputTransformY: '0', |
||||
|
isSubmitting: false, |
||||
|
showPreview: false, |
||||
|
previewImages: [], |
||||
|
previewIndex: 0, |
||||
loading: false, |
loading: false, |
||||
|
scrollToId: '', |
||||
|
currentUser: '当前用户', |
||||
|
keyboardHeight: 0 |
||||
}, |
}, |
||||
|
|
||||
onLoad: function() { |
|
||||
// 页面加载时获取帖子数据
|
|
||||
this.loadPosts(); |
|
||||
}, |
|
||||
|
onLoad: function(options) { |
||||
|
const postId = options.id || '1'; |
||||
|
this.loadPostDetail(postId); |
||||
|
|
||||
onPullDownRefresh: function() { |
|
||||
// 下拉刷新
|
|
||||
this.loadPosts(); |
|
||||
wx.stopPullDownRefresh(); |
|
||||
|
// 监听键盘高度变化
|
||||
|
wx.onKeyboardHeightChange(res => { |
||||
|
if (res.height > 0) { |
||||
|
this.setData({ keyboardHeight: res.height }); |
||||
|
} |
||||
|
}); |
||||
}, |
}, |
||||
|
|
||||
// 加载帖子数据
|
|
||||
loadPosts: function() { |
|
||||
|
// 加载帖子详情
|
||||
|
loadPostDetail: function(postId) { |
||||
this.setData({ loading: true }); |
this.setData({ loading: true }); |
||||
|
|
||||
// 模拟网络请求
|
|
||||
|
// 模拟API请求
|
||||
setTimeout(() => { |
setTimeout(() => { |
||||
// 模拟数据
|
|
||||
const mockPosts = [ |
|
||||
{ |
|
||||
|
const mockPost = { |
||||
id: 1, |
id: 1, |
||||
username: '技术爱好者', |
username: '技术爱好者', |
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
time: '2小时前', |
time: '2小时前', |
||||
title: '微信小程序如何实现图片上传和预览功能?', |
title: '微信小程序如何实现图片上传和预览功能?', |
||||
content: '我正在开发一个微信小程序,需要实现图片上传功能,并且能够在上传前预览图片。请问有什么好的实现方案吗?上传的图片大小限制和格式有什么建议?', |
content: '我正在开发一个微信小程序,需要实现图片上传功能,并且能够在上传前预览图片。请问有什么好的实现方案吗?上传的图片大小限制和格式有什么建议?', |
||||
|
images: [ |
||||
|
'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
|
'https://img.yzcdn.cn/vant/cat.jpeg' |
||||
|
], |
||||
tags: ['微信小程序', '图片上传', '前端开发'], |
tags: ['微信小程序', '图片上传', '前端开发'], |
||||
likeCount: 12, |
likeCount: 12, |
||||
replyCount: 5, |
replyCount: 5, |
||||
viewCount: 156, |
viewCount: 156, |
||||
liked: false, |
liked: false, |
||||
solved: false, |
solved: false, |
||||
showAllReplies: false, |
|
||||
replies: [ |
replies: [ |
||||
{ |
{ |
||||
|
replyId: 'r1_1', |
||||
username: '前端开发工程师', |
username: '前端开发工程师', |
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
time: '1小时前', |
time: '1小时前', |
||||
content: '可以使用wx.chooseImage选择图片,然后使用wx.uploadFile上传到服务器。预览功能可以使用wx.previewImage实现。' |
|
||||
}, |
|
||||
|
content: '可以使用wx.chooseImage选择图片,然后使用wx.uploadFile上传到服务器。预览功能可以使用wx.previewImage实现。', |
||||
|
likeCount: 3, |
||||
|
liked: false, |
||||
|
comments: [ |
||||
{ |
{ |
||||
username: '小程序开发者', |
|
||||
|
commentId: 'c1_1', |
||||
|
username: '学习者', |
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
time: '45分钟前', |
|
||||
content: '建议将图片大小限制在2MB以内,支持JPG、PNG格式。可以使用云开发存储功能简化上传流程。' |
|
||||
|
toUsername: '前端开发工程师', |
||||
|
time: '30分钟前', |
||||
|
content: '感谢分享,请问有具体的代码示例吗?' |
||||
} |
} |
||||
] |
] |
||||
}, |
}, |
||||
{ |
{ |
||||
id: 2, |
|
||||
username: '产品经理小王', |
|
||||
|
replyId: 'r1_2', |
||||
|
username: '小程序开发者', |
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
time: '昨天 14:30', |
|
||||
title: '如何设计一个用户友好的注册流程?', |
|
||||
content: '我们正在设计一个新产品的注册流程,希望既保证安全性又尽量简化步骤。大家有什么好的设计建议或参考案例吗?', |
|
||||
tags: ['产品设计', '用户体验', '注册流程'], |
|
||||
likeCount: 8, |
|
||||
replyCount: 3, |
|
||||
viewCount: 89, |
|
||||
|
time: '45分钟前', |
||||
|
content: '建议将图片大小限制在2MB以内,支持JPG、PNG格式。可以使用云开发存储功能简化上传流程。', |
||||
|
likeCount: 2, |
||||
liked: true, |
liked: true, |
||||
solved: true, |
|
||||
showAllReplies: false, |
|
||||
replies: [ |
|
||||
{ |
|
||||
username: 'UX设计师', |
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
|
||||
time: '昨天 16:45', |
|
||||
content: '建议采用手机号验证码注册,配合第三方登录选项。关键是将必填信息减到最少,其他信息可以后续引导补充。' |
|
||||
|
comments: [] |
||||
} |
} |
||||
] |
] |
||||
}, |
|
||||
{ |
|
||||
id: 3, |
|
||||
username: '后端开发', |
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
|
||||
time: '3天前', |
|
||||
title: 'RESTful API设计的最佳实践有哪些?', |
|
||||
content: '我正在设计一组RESTful API,想了解一些最佳实践,比如URL命名规范、状态码使用、版本控制等方面有什么推荐的做法?', |
|
||||
tags: ['后端开发', 'API设计', 'RESTful'], |
|
||||
likeCount: 15, |
|
||||
replyCount: 7, |
|
||||
viewCount: 234, |
|
||||
liked: false, |
|
||||
solved: false, |
|
||||
showAllReplies: false, |
|
||||
replies: [ |
|
||||
{ |
|
||||
username: '架构师', |
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
|
||||
time: '2天前', |
|
||||
content: '建议使用名词复数形式作为资源端点,合理使用HTTP状态码,API版本可以通过URL路径或请求头来管理。' |
|
||||
}, |
|
||||
{ |
|
||||
username: '全栈工程师', |
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
|
||||
time: '1天前', |
|
||||
content: '不要忘记实现合适的错误处理机制,返回清晰的错误信息。同时考虑API限流和身份验证机制。' |
|
||||
}, |
|
||||
{ |
|
||||
username: '资深开发者', |
|
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
|
||||
time: '1天前', |
|
||||
content: '推荐使用Swagger或OpenAPI规范来文档化你的API,这对前后端协作非常有帮助。' |
|
||||
} |
|
||||
] |
|
||||
} |
|
||||
]; |
|
||||
|
}; |
||||
|
|
||||
this.setData({ |
this.setData({ |
||||
posts: mockPosts, |
|
||||
|
post: mockPost, |
||||
loading: false |
loading: false |
||||
}); |
}); |
||||
}, 800); |
|
||||
|
}, 500); |
||||
}, |
}, |
||||
|
|
||||
// 显示发帖弹窗
|
|
||||
showPostModal: function() { |
|
||||
this.setData({ |
|
||||
showPostModal: true |
|
||||
}); |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
// 滚动监听
|
||||
|
onScroll: function(e) { |
||||
|
// 可以在这里实现滚动相关逻辑
|
||||
}, |
}, |
||||
|
|
||||
// 隐藏发帖弹窗
|
|
||||
hidePostModal: function() { |
|
||||
|
// 输入框获取焦点
|
||||
|
onInputFocus: function(e) { |
||||
this.setData({ |
this.setData({ |
||||
showPostModal: false, |
|
||||
postTitle: '', |
|
||||
postContent: '', |
|
||||
selectedTags: [], |
|
||||
tagInput: '' |
|
||||
|
isInputFocused: true, |
||||
|
inputTransformY: `-${e.detail.height}px` |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 防止弹窗内点击事件冒泡
|
|
||||
preventTap: function() { |
|
||||
// 阻止事件冒泡
|
|
||||
|
// 输入框失去焦点
|
||||
|
onInputBlur: function() { |
||||
|
this.setData({ |
||||
|
isInputFocused: false, |
||||
|
inputTransformY: '0' |
||||
|
}); |
||||
}, |
}, |
||||
|
|
||||
// 发帖标题输入
|
|
||||
onPostTitleInput: function(e) { |
|
||||
|
// 回复输入
|
||||
|
onReplyInput: function(e) { |
||||
this.setData({ |
this.setData({ |
||||
postTitle: e.detail.value |
|
||||
|
replyContent: e.detail.value |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 发帖内容输入
|
|
||||
onPostContentInput: function(e) { |
|
||||
|
// 聚焦到输入框
|
||||
|
focusReplyInput: function() { |
||||
this.setData({ |
this.setData({ |
||||
postContent: e.detail.value |
|
||||
|
replyTarget: { |
||||
|
type: 'post', |
||||
|
username: this.data.post.username |
||||
|
}, |
||||
|
replyPlaceholder: `回复 ${this.data.post.username}...`, |
||||
|
replyContent: '', |
||||
|
isInputFocused: true |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
|
// 回复用户
|
||||
|
replyToUser: function(e) { |
||||
|
const { type, index, replyIndex, commentIndex, username } = e.currentTarget.dataset; |
||||
|
|
||||
|
const replyTarget = { |
||||
|
type: type, |
||||
|
username: username |
||||
|
}; |
||||
|
|
||||
|
if (type === 'reply') { |
||||
|
replyTarget.replyIndex = parseInt(index); |
||||
|
replyTarget.replyId = this.data.post.replies[index].replyId; |
||||
|
} else if (type === 'comment') { |
||||
|
replyTarget.replyIndex = parseInt(replyIndex); |
||||
|
replyTarget.commentIndex = parseInt(commentIndex); |
||||
|
replyTarget.commentId = this.data.post.replies[replyIndex].comments[commentIndex].commentId; |
||||
|
replyTarget.replyId = this.data.post.replies[replyIndex].replyId; |
||||
|
} |
||||
|
|
||||
// 提交帖子
|
|
||||
submitPost: function() { |
|
||||
const { postTitle, postContent, selectedTags } = this.data; |
|
||||
|
this.setData({ |
||||
|
replyTarget: replyTarget, |
||||
|
replyPlaceholder: `回复 @${username}...`, |
||||
|
replyContent: '', |
||||
|
isInputFocused: true |
||||
|
}); |
||||
|
|
||||
if (!postTitle.trim()) { |
|
||||
wx.showToast({ |
|
||||
title: '请输入标题', |
|
||||
icon: 'none' |
|
||||
|
// 滚动到输入框位置
|
||||
|
setTimeout(() => { |
||||
|
this.setData({ |
||||
|
scrollToId: type === 'reply' ? `reply-${replyTarget.replyId}` : '' |
||||
}); |
}); |
||||
|
}, 100); |
||||
|
}, |
||||
|
|
||||
|
// 清除回复目标
|
||||
|
clearReplyTarget: function() { |
||||
|
this.setData({ |
||||
|
replyTarget: { |
||||
|
type: '', |
||||
|
username: '', |
||||
|
replyId: '', |
||||
|
replyIndex: null, |
||||
|
commentId: '', |
||||
|
commentIndex: null |
||||
|
}, |
||||
|
replyPlaceholder: '输入您的回复...', |
||||
|
replyContent: '' |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 提交回复
|
||||
|
submitReply: function() { |
||||
|
const { replyContent, replyTarget, post } = this.data; |
||||
|
const content = replyContent.trim(); |
||||
|
|
||||
|
if (!content) { |
||||
|
wx.showToast({ title: '请输入内容', icon: 'none' }); |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
if (!postContent.trim()) { |
|
||||
wx.showToast({ |
|
||||
title: '请输入内容', |
|
||||
icon: 'none' |
|
||||
}); |
|
||||
|
if (content.length > 500) { |
||||
|
wx.showToast({ title: '回复内容不能超过500字', icon: 'none' }); |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
// 创建新帖子
|
|
||||
const newPost = { |
|
||||
id: Date.now(), |
|
||||
username: '当前用户', |
|
||||
|
this.setData({ isSubmitting: true }); |
||||
|
|
||||
|
// 模拟网络请求
|
||||
|
setTimeout(() => { |
||||
|
if (replyTarget.type === 'post') { |
||||
|
// 回复帖子
|
||||
|
this.replyToPost(content); |
||||
|
} else if (replyTarget.type === 'reply') { |
||||
|
// 回复评论
|
||||
|
this.replyToComment(content, replyTarget); |
||||
|
} else if (replyTarget.type === 'comment') { |
||||
|
// 回复评论的回复
|
||||
|
this.replyToCommentReply(content, replyTarget); |
||||
|
} else { |
||||
|
// 普通回复(直接回复帖子)
|
||||
|
this.replyToPost(content); |
||||
|
} |
||||
|
|
||||
|
this.setData({ isSubmitting: false }); |
||||
|
}, 500); |
||||
|
}, |
||||
|
|
||||
|
// 回复帖子
|
||||
|
replyToPost: function(content) { |
||||
|
const post = this.data.post; |
||||
|
const replyId = `r${post.id}_${Date.now()}`; |
||||
|
|
||||
|
const newReply = { |
||||
|
replyId: replyId, |
||||
|
username: this.data.currentUser, |
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
time: '刚刚', |
time: '刚刚', |
||||
title: postTitle, |
|
||||
content: postContent, |
|
||||
tags: selectedTags, |
|
||||
|
content: content, |
||||
likeCount: 0, |
likeCount: 0, |
||||
replyCount: 0, |
|
||||
viewCount: 0, |
|
||||
liked: false, |
liked: false, |
||||
solved: false, |
|
||||
showAllReplies: false, |
|
||||
replies: [] |
|
||||
|
comments: [] |
||||
}; |
}; |
||||
|
|
||||
// 添加到帖子列表顶部
|
|
||||
const posts = this.data.posts; |
|
||||
posts.unshift(newPost); |
|
||||
|
post.replies.push(newReply); |
||||
|
post.replyCount += 1; |
||||
|
|
||||
this.setData({ |
this.setData({ |
||||
posts: posts, |
|
||||
showPostModal: false, |
|
||||
postTitle: '', |
|
||||
postContent: '', |
|
||||
selectedTags: [], |
|
||||
tagInput: '' |
|
||||
|
post: post, |
||||
|
replyContent: '', |
||||
|
replyTarget: { |
||||
|
type: '', |
||||
|
username: '', |
||||
|
replyId: '', |
||||
|
replyIndex: null, |
||||
|
commentId: '', |
||||
|
commentIndex: null |
||||
|
}, |
||||
|
replyPlaceholder: '输入您的回复...', |
||||
|
isInputFocused: false |
||||
}); |
}); |
||||
|
|
||||
wx.showToast({ |
wx.showToast({ |
||||
title: '发布成功', |
|
||||
icon: 'success' |
|
||||
|
title: '回复成功', |
||||
|
icon: 'success', |
||||
|
duration: 1500 |
||||
}); |
}); |
||||
}, |
|
||||
|
|
||||
|
|
||||
|
|
||||
// 显示回复输入框
|
|
||||
showReplyInput: function(e) { |
|
||||
const index = e.currentTarget.dataset.index; |
|
||||
|
// 滚动到最新回复
|
||||
|
setTimeout(() => { |
||||
this.setData({ |
this.setData({ |
||||
activeReplyIndex: index, |
|
||||
replyContent: '' |
|
||||
|
scrollToId: `reply-${replyId}` |
||||
}); |
}); |
||||
|
}, 800); |
||||
}, |
}, |
||||
|
|
||||
// 回复内容输入
|
|
||||
onReplyInput: function(e) { |
|
||||
|
// 回复评论
|
||||
|
replyToComment: function(content, target) { |
||||
|
const post = this.data.post; |
||||
|
const replyIndex = target.replyIndex; |
||||
|
const reply = post.replies[replyIndex]; |
||||
|
const commentId = `c${reply.replyId}_${Date.now()}`; |
||||
|
|
||||
|
const newComment = { |
||||
|
commentId: commentId, |
||||
|
username: this.data.currentUser, |
||||
|
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
|
toUsername: target.username, |
||||
|
time: '刚刚', |
||||
|
content: content |
||||
|
}; |
||||
|
|
||||
|
if (!reply.comments) { |
||||
|
reply.comments = []; |
||||
|
} |
||||
|
reply.comments.push(newComment); |
||||
|
|
||||
this.setData({ |
this.setData({ |
||||
replyContent: e.detail.value |
|
||||
}); |
|
||||
|
post: post, |
||||
|
replyContent: '', |
||||
|
replyTarget: { |
||||
|
type: '', |
||||
|
username: '', |
||||
|
replyId: '', |
||||
|
replyIndex: null, |
||||
|
commentId: '', |
||||
|
commentIndex: null |
||||
}, |
}, |
||||
|
replyPlaceholder: '输入您的回复...', |
||||
|
isInputFocused: false |
||||
|
}); |
||||
|
|
||||
// 提交回复
|
|
||||
submitReply: function(e) { |
|
||||
const index = e.currentTarget.dataset.index; |
|
||||
const replyContent = this.data.replyContent.trim(); |
|
||||
|
|
||||
if (!replyContent) { |
|
||||
wx.showToast({ |
wx.showToast({ |
||||
title: '请输入回复内容', |
|
||||
icon: 'none' |
|
||||
|
title: '回复成功', |
||||
|
icon: 'success', |
||||
|
duration: 1500 |
||||
}); |
}); |
||||
return; |
|
||||
} |
|
||||
|
}, |
||||
|
|
||||
const posts = this.data.posts; |
|
||||
const post = posts[index]; |
|
||||
|
// 回复评论的回复
|
||||
|
replyToCommentReply: function(content, target) { |
||||
|
const post = this.data.post; |
||||
|
const reply = post.replies[target.replyIndex]; |
||||
|
const originalComment = reply.comments[target.commentIndex]; |
||||
|
const commentId = `cc${originalComment.commentId}_${Date.now()}`; |
||||
|
|
||||
// 创建新回复
|
|
||||
const newReply = { |
|
||||
username: '当前用户', |
|
||||
|
const newComment = { |
||||
|
commentId: commentId, |
||||
|
username: this.data.currentUser, |
||||
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
avatar: 'https://img.yzcdn.cn/vant/cat.jpeg', |
||||
|
toUsername: target.username, |
||||
time: '刚刚', |
time: '刚刚', |
||||
content: replyContent |
|
||||
|
content: content |
||||
}; |
}; |
||||
|
|
||||
post.replies.push(newReply); |
|
||||
post.replyCount += 1; |
|
||||
|
reply.comments.push(newComment); |
||||
|
|
||||
this.setData({ |
this.setData({ |
||||
posts: posts, |
|
||||
activeReplyIndex: -1, |
|
||||
replyContent: '' |
|
||||
|
post: post, |
||||
|
replyContent: '', |
||||
|
replyTarget: { |
||||
|
type: '', |
||||
|
username: '', |
||||
|
replyId: '', |
||||
|
replyIndex: null, |
||||
|
commentId: '', |
||||
|
commentIndex: null |
||||
|
}, |
||||
|
replyPlaceholder: '输入您的回复...', |
||||
|
isInputFocused: false |
||||
}); |
}); |
||||
|
|
||||
wx.showToast({ |
wx.showToast({ |
||||
title: '回复成功', |
title: '回复成功', |
||||
icon: 'success' |
|
||||
|
icon: 'success', |
||||
|
duration: 1500 |
||||
}); |
}); |
||||
}, |
}, |
||||
|
|
||||
// 切换显示全部回复
|
|
||||
toggleReplies: function(e) { |
|
||||
const index = e.currentTarget.dataset.index; |
|
||||
const posts = this.data.posts; |
|
||||
const post = posts[index]; |
|
||||
|
// 图片预览
|
||||
|
previewImage: function(e) { |
||||
|
const imgIndex = e.currentTarget.dataset.imgIndex; |
||||
|
const images = this.data.post.images; |
||||
|
|
||||
post.showAllReplies = !post.showAllReplies; |
|
||||
|
this.setData({ |
||||
|
showPreview: true, |
||||
|
previewImages: images, |
||||
|
previewIndex: imgIndex |
||||
|
}); |
||||
|
}, |
||||
|
|
||||
|
// 图片预览滑动
|
||||
|
onSwiperChange: function(e) { |
||||
this.setData({ |
this.setData({ |
||||
posts: posts |
|
||||
|
previewIndex: e.detail.current |
||||
}); |
}); |
||||
|
}, |
||||
|
|
||||
|
// 隐藏预览
|
||||
|
hidePreview: function() { |
||||
|
this.setData({ showPreview: false }); |
||||
|
}, |
||||
|
|
||||
|
// 下拉刷新
|
||||
|
onPullDownRefresh: function() { |
||||
|
if (this.data.post) { |
||||
|
this.loadPostDetail(this.data.post.id); |
||||
|
} |
||||
|
wx.stopPullDownRefresh(); |
||||
|
}, |
||||
|
|
||||
|
// 页面上拉触底
|
||||
|
onReachBottom: function() { |
||||
|
// 这里可以实现加载更多回复的逻辑
|
||||
|
console.log('加载更多回复'); |
||||
|
}, |
||||
|
|
||||
|
// 页面卸载
|
||||
|
onUnload: function() { |
||||
|
// 移除键盘高度监听
|
||||
|
wx.offKeyboardHeightChange(); |
||||
} |
} |
||||
}); |
}); |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue