与牧同行-小程序用户端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

345 lines
10 KiB

  1. Page({
  2. data: {
  3. posts: [],
  4. loading: false,
  5. loadingMore: false,
  6. refreshing: false,
  7. hasMore: true,
  8. page: 1,
  9. pageSize: 10,
  10. currentFilter: 'all',
  11. searchKeyword: '',
  12. currentUser: '当前用户'
  13. },
  14. onLoad: function() {
  15. this.loadPosts();
  16. // 监听页面显示,用于刷新数据
  17. wx.onAppShow(() => {
  18. this.refreshData();
  19. });
  20. },
  21. onShow: function() {
  22. this.refreshData();
  23. },
  24. // 加载帖子列表
  25. loadPosts: function(reset = false) {
  26. if (reset) {
  27. this.setData({
  28. page: 1,
  29. hasMore: true,
  30. posts: [],
  31. loading: true
  32. });
  33. } else if (this.data.loadingMore) {
  34. return;
  35. }
  36. const params = {
  37. page: this.data.page,
  38. pageSize: this.data.pageSize,
  39. filter: this.data.currentFilter,
  40. search: this.data.searchKeyword
  41. };
  42. this.setData({
  43. loading: reset || this.data.page === 1,
  44. loadingMore: !reset && this.data.page > 1
  45. });
  46. // 模拟API请求
  47. setTimeout(() => {
  48. const mockPosts = this.generateMockPosts(params);
  49. if (reset) {
  50. this.setData({
  51. posts: mockPosts,
  52. loading: false,
  53. hasMore: mockPosts.length === params.pageSize
  54. });
  55. } else {
  56. this.setData({
  57. posts: [...this.data.posts, ...mockPosts],
  58. loading: false,
  59. loadingMore: false,
  60. hasMore: mockPosts.length === params.pageSize
  61. });
  62. }
  63. if (this.data.refreshing) {
  64. wx.stopPullDownRefresh();
  65. this.setData({ refreshing: false });
  66. }
  67. }, 800);
  68. },
  69. // 生成模拟数据
  70. generateMockPosts: function(params) {
  71. const posts = [];
  72. const currentUser = this.data.currentUser;
  73. const baseId = (params.page - 1) * params.pageSize;
  74. for (let i = 0; i < params.pageSize; i++) {
  75. const id = baseId + i + 1;
  76. const solved = i % 4 === 0;
  77. const hot = i % 3 === 0 && i % 2 === 0;
  78. const isMine = i % 5 === 0;
  79. // 根据筛选条件过滤
  80. if (params.filter === 'solved' && !solved) continue;
  81. if (params.filter === 'unsolved' && solved) continue;
  82. if (params.filter === 'mine' && !isMine) continue;
  83. const tags = this.getRandomTags();
  84. const post = {
  85. id: id,
  86. title: this.getRandomTitle(id),
  87. summary: this.getRandomSummary(id),
  88. username: isMine ? currentUser : this.getRandomUsername(),
  89. avatar: 'https://img.yzcdn.cn/vant/cat.jpeg',
  90. time: this.getRandomTime(),
  91. likeCount: Math.floor(Math.random() * 50),
  92. replyCount: Math.floor(Math.random() * 30),
  93. viewCount: Math.floor(Math.random() * 300),
  94. solved: solved,
  95. hot: hot,
  96. tags: tags,
  97. lastReply: Math.random() > 0.3 ? {
  98. username: this.getRandomUsername(),
  99. time: this.getRandomTime('short')
  100. } : null
  101. };
  102. // 搜索过滤
  103. if (params.search) {
  104. const keyword = params.search.toLowerCase();
  105. const titleMatch = post.title.toLowerCase().includes(keyword);
  106. const summaryMatch = post.summary.toLowerCase().includes(keyword);
  107. const tagMatch = post.tags.some(tag => tag.toLowerCase().includes(keyword));
  108. if (!titleMatch && !summaryMatch && !tagMatch) {
  109. continue;
  110. }
  111. }
  112. posts.push(post);
  113. }
  114. // 热门排序
  115. if (params.filter === 'hot') {
  116. posts.sort((a, b) => {
  117. const aScore = a.likeCount * 2 + a.replyCount * 3 + a.viewCount;
  118. const bScore = b.likeCount * 2 + b.replyCount * 3 + b.viewCount;
  119. return bScore - aScore;
  120. });
  121. }
  122. return posts;
  123. },
  124. // 随机生成标题
  125. getRandomTitle: function(id) {
  126. const titles = [
  127. '微信小程序如何实现图片上传和预览功能?',
  128. 'uni-app开发中如何处理不同平台的兼容性问题?',
  129. 'JavaScript闭包的使用场景有哪些?',
  130. 'Vue3组合式API和选项式API该如何选择?',
  131. 'React Hooks在项目中的最佳实践',
  132. 'Node.js高并发场景下的性能优化方案',
  133. 'TypeScript在大型项目中的类型设计经验分享',
  134. '微信小程序云开发数据库查询性能优化',
  135. '前端工程化建设:从零搭建Webpack配置',
  136. '移动端H5页面适配的最佳方案是什么?',
  137. '如何优雅地处理前端错误监控和上报?',
  138. '微前端架构在实际项目中的应用经验',
  139. 'Webpack5 Module Federation实战分享',
  140. '前端代码质量保证:ESLint + Prettier + Husky',
  141. '跨端开发框架选型:Flutter vs React Native vs uni-app'
  142. ];
  143. return titles[id % titles.length] || titles[0];
  144. },
  145. // 随机生成摘要
  146. getRandomSummary: function(id) {
  147. const summaries = [
  148. '我正在开发一个微信小程序,需要实现图片上传功能,并且能够在上传前预览图片。请问有什么好的实现方案吗?上传的图片大小限制和格式有什么建议?',
  149. '最近在做一个uni-app项目,需要同时兼容微信小程序和H5,遇到了一些样式和API兼容性问题,大家有什么好的解决方案吗?',
  150. '在实际项目中经常使用闭包,但对其原理和应用场景理解还不够深入,想请教一下大家在项目中都是如何使用闭包的?',
  151. '公司新项目准备使用Vue3,对于组合式API和选项式API的选择有些纠结,大家有什么建议吗?各自的使用场景是什么?',
  152. 'React Hooks确实很方便,但在大型项目中如何合理组织Hooks,避免过度使用导致代码难以维护?',
  153. '我们的Node.js服务在高并发场景下性能表现不佳,有哪些常见的性能优化方案可以参考?',
  154. '项目准备从JavaScript迁移到TypeScript,在类型设计方面有什么经验可以分享吗?如何设计合理的泛型和接口?'
  155. ];
  156. return summaries[id % summaries.length] || summaries[0];
  157. },
  158. // 随机生成用户名
  159. getRandomUsername: function() {
  160. const usernames = [
  161. '前端工程师', '技术爱好者', '小程序开发', '全栈程序员',
  162. '架构师老王', '代码艺术家', '算法工程师', '产品经理',
  163. 'UI设计师', '测试工程师', '运维小哥', '数据分析师'
  164. ];
  165. return usernames[Math.floor(Math.random() * usernames.length)];
  166. },
  167. // 随机生成时间
  168. getRandomTime: function(type = 'normal') {
  169. const times = type === 'short'
  170. ? ['5分钟前', '10分钟前', '半小时前', '1小时前']
  171. : ['2小时前', '5小时前', '昨天', '2天前', '3天前', '一周前'];
  172. return times[Math.floor(Math.random() * times.length)];
  173. },
  174. // 随机生成标签
  175. getRandomTags: function() {
  176. const allTags = [
  177. '微信小程序', '前端开发', 'JavaScript', 'Vue.js', 'React',
  178. 'Node.js', 'TypeScript', 'uni-app', '性能优化', '工程化',
  179. '移动端', 'H5', 'CSS', 'Webpack', 'Git'
  180. ];
  181. const count = Math.floor(Math.random() * 3) + 1;
  182. const tags = [];
  183. const usedIndices = new Set();
  184. for (let i = 0; i < count; i++) {
  185. let index;
  186. do {
  187. index = Math.floor(Math.random() * allTags.length);
  188. } while (usedIndices.has(index));
  189. usedIndices.add(index);
  190. tags.push(allTags[index]);
  191. }
  192. return tags;
  193. },
  194. // 下拉刷新
  195. onRefresh: function() {
  196. this.setData({ refreshing: true });
  197. this.loadPosts(true);
  198. },
  199. // 滚动到底部加载更多
  200. loadMore: function() {
  201. if (!this.data.hasMore || this.data.loadingMore) return;
  202. this.setData({
  203. page: this.data.page + 1
  204. }, () => {
  205. this.loadPosts();
  206. });
  207. },
  208. // 筛选切换
  209. changeFilter: function(e) {
  210. const filterType = e.currentTarget.dataset.type;
  211. if (this.data.currentFilter === filterType) return;
  212. this.setData({
  213. currentFilter: filterType,
  214. searchKeyword: '' // 切换筛选时清空搜索
  215. }, () => {
  216. this.loadPosts(true);
  217. });
  218. },
  219. // 搜索输入
  220. onSearchInput: function(e) {
  221. this.setData({ searchKeyword: e.detail.value });
  222. // 防抖搜索
  223. clearTimeout(this.searchTimer);
  224. this.searchTimer = setTimeout(() => {
  225. if (e.detail.value.trim()) {
  226. this.loadPosts(true);
  227. }
  228. }, 300);
  229. },
  230. // 搜索确认
  231. onSearchConfirm: function(e) {
  232. const keyword = e.detail.value.trim();
  233. if (keyword) {
  234. this.setData({ searchKeyword: keyword });
  235. this.loadPosts(true);
  236. }
  237. },
  238. // 清空搜索
  239. clearSearch: function() {
  240. this.setData({
  241. searchKeyword: '',
  242. currentFilter: 'all'
  243. }, () => {
  244. this.loadPosts(true);
  245. });
  246. },
  247. // 跳转到详情页
  248. goToDetail: function(e) {
  249. const postId = e.currentTarget.dataset.id;
  250. wx.navigateTo({
  251. url: `/pages/forum/detail/detail?id=${postId}`,
  252. success: () => {
  253. // 记录浏览历史
  254. this.recordViewHistory(postId);
  255. }
  256. });
  257. },
  258. // 创建新帖子
  259. createPost: function() {
  260. wx.navigateTo({
  261. url: '/pages/forum/create/create'
  262. });
  263. },
  264. // 记录浏览历史
  265. recordViewHistory: function(postId) {
  266. try {
  267. const history = wx.getStorageSync('forumViewHistory') || [];
  268. const index = history.findIndex(item => item.id === postId);
  269. if (index !== -1) {
  270. history.splice(index, 1);
  271. }
  272. history.unshift({
  273. id: postId,
  274. timestamp: Date.now()
  275. });
  276. // 只保留最近50条记录
  277. if (history.length > 50) {
  278. history.pop();
  279. }
  280. wx.setStorageSync('forumViewHistory', history);
  281. } catch (error) {
  282. console.error('记录浏览历史失败:', error);
  283. }
  284. },
  285. // 刷新数据
  286. refreshData: function() {
  287. // 这里可以检查是否有新数据需要刷新
  288. // 例如:从详情页返回时刷新点赞状态等
  289. },
  290. onPullDownRefresh: function() {
  291. this.onRefresh();
  292. },
  293. onReachBottom: function() {
  294. this.loadMore();
  295. },
  296. onPageScroll: function(e) {
  297. // 可以在这里处理页面滚动时的效果
  298. }
  299. });