青铜选手带你动手撸一个博客小程序给自己(也许是第一期)~(大佬请忽略此条)...

前言

看掘金也又一年多了,感叹各位大佬技术6的一批,刚上学的时候也给人搞过一两个小程序,突然心血来潮想总结总结经验,给自己也搞一个爽一爽,顺便也在写一篇,让各位大佬看看还有什么问题,毕竟本屌虚心的一批

无图言鸡儿=>成品图

菜鸡实践中总结的一些tip

  • 小程序登陆获取用户信息(与服务器交互)
  • 小程序显示富文本与markdown(使用了towxml)
  • 对wx.request的小封装(搞成promise爽一爽)
  • iview weapp组件的使用
  • 小程序原生组件的使用
  • 针对上拉加载更多的小总结
  • 手动实现个简单的假的瀑布流

功能需求

  1. 分类显示博客
  2. 显示博客内容(富文本/Markdown)
  3. 用户在微信小程序登陆
  4. 用户登陆后评论博客
  5. 显示我爱看的一些书籍信息(特殊服务)
  6. 一键将书籍发送到kindle(特殊服务)

前期准备

  1. 去UI中国或者其他网站扒拉个看着顺眼的UI设计图(毕竟审美是硬伤)
  2. 去阿里图标网站找上一套顺眼的图标。选几个用来给小程序的底部tab栏用,由于小程序tab图标切换是靠换图片来实现的,因此可以从阿里图标上每个图标下两份,灰色版的下一份,彩色版的下一份。稍微整理一下图标的命名

3. 个人注册个小程序账号(直接去微信公众平台注册,简单的一批),注册完后登陆一下,把appid与appsecrect搞到手

4. 数据接口准备(重要的一环,总不能都是空架子把),,根据各位看官的博客后台的实际情况搞,一般都有数据接口的,用来获取文章啥的。这里我用的是自己开发的后端,所以接口什么的都是按需开发的~各位看官要是如果有兴趣可以找我要后台(thinkphp+vue+element搞的管理后台)

这个项目用到的接口主要有

  1. 获取顶部轮播图的接口(getSlides)
  2. 根据栏目获取栏目下博客的接口(getPostOfCategory)
  3. 获取文章的接口(getPost)
  4. 用户登陆接口(wxLogin)
  5. 用户绑定邮箱的接口(bindMail)
  6. 发送书籍的接口(sendBook)
  7. 获取配置的接口(getConfig)
  8. 获取文章评论的接口
  9. 用户评论文章的接口 这里要解释一下getConfig,每日推荐、首页博客等一些显示博客的地方说白了其实就是从服务端取不同栏目下的文章,因此需要一个栏目id来调用getPostOfCategory接口,但是又不想把栏目id写死在小程序里,万一哪天心情不好把栏目给删了小程序岂不要改代码?刚好当时做后台的时候搞了一个配置管理的功能,这次刚好用到;专门为小程序新建了一个配置组,把小程序用到的栏目id和其他乱七八糟的东西(比如分页的每页大小,背景图啥的)以key-value的形式存在后端,每次小程序启动的时候从后端获取一下配置存在localStorage里,这样在后台改改配置,小程序显示的栏目自然也就切换了。

不瞎bb了开整

基础设施

  1. 根据需求规划一下,在pages文件夹里把所需要的页面右键新建出来
  2. 由于用到了iview组件和towxml,所以把这俩老哥也给放根目录
  3. 新建一个netUtils.js(网络层),封装一下wx.request,放进netUtils里,同时把服务器地址baseUrl也作为常量放在netUtils里,所有的网络访问url都从baseUrl拼接而来,方便切换测试与生成环境
  4. 再此基础上搞出来一个dataUtils(即数据层),将所有的从服务器获取数据的网络请求行为(全搞成promise)全部放在这里
  5. (可选)由于很多页面都会跳转到文章内容页、搜索页等页面,因此后面又搞了一个navUtils.js把常用的跳转都写在这里面,省的每次都在写一遍

文件结构:

netUtils.js关键部分
const BASEURL = "https://localhost:8888/";
const APIURL = "https://localhost:8888/api/";
/**
 * 封装request
 */
const requestPromise = function ({ url, data, header,
  method = 'GET' }) {
  return new Promise((resolve, reject) => {
    wx.request({
      url: url,
      data: data,
      header: header,
      method: method,
      success: (res) => { resolve(res) },
      fail: (err) => { reject(err) }
    })
  });
};
module.exports={
  BASEURL:BASEURL,
  APIURL:APIURL,
  request: requestPromise
}
复制代码
dataUtils关键部分
let netUtils = require('./netUtils.js');
/**
 * 获取服务器数据基本方法
 */
function getServerDataPromise(url,data,header=null,method='GET'){
  let dataUrl = netUtils.BASEURL+url;
  return new Promise((resolve, reject) => {
    netUtils.request({
      url: dataUrl,
      data: data,
      header:header,
      method:method
    }).then(res => {
      resolve(res);
    }).catch(err => {
      reject(err);
    });
  });
}
/**
 * 获取栏目下文章
 */
function getPostOfCategoryPromise(data) {
  let url ='api/front/portal/getPostOfCategory';
  return getServerDataPromise(url,data);
};
/**
 * 获取幻灯片
 */
function getSlidesPromise(data){
  let url ='api/front/portal/getSlide';
  return getServerDataPromise(url,data);
}

........各位老哥根据实际情况把自己的接口封装一下搞一搞


module.exports = {
  getPostOfCategory: getPostOfCategoryPromise,
  getSlides: getSlidesPromise,
  checkToken: checkToken,
  userLogin:userLoginPromise,
  getContent: getContentPromise,
  addComment: addCommentPromise,
  getComment:getCommentPromise,
  getKindleEmail: getKindleEmailPromise,
  bindKindleEmail: bindKindleEmailPromise,
  sendBook: sendBookPromise,
  getNav:getNavPromise,
  search: searchPromise,
  getUser: getUserPromise,
  checkLogin: checkLoginPromise,
  getConfig: getConfigPromise
};
复制代码

先搞个首页

我的个人习惯是先把页面的js获取数据的部分写好,然后再去写wxml与wxss,有了数据填充,比教容易看出来页面的效果,调试完页面后,在取js把点击事件、跳转等其他的一些代码补全。 前端代码比较简单,就不在这贴了,值得注意的一点是,首页使用了iview的组件,因此在index.json中应先把使用的组件配置一下 index.json

{
  "usingComponents": {
    "i-row": "../../iview/row/index",
    "i-col": "../../iview/col/index",
    "i-spin": "../../iview/spin/index",
    "i-icon": "../../iview/icon/index",
    "i-message": "../../iview/message/index",
    "i-divider": "../../iview/divider/index"
  },
  "enablePullDownRefresh":true
}
复制代码

index.js

let netUtils=require('../../utils/netUtils.js');
let dataUtils=require('../../utils/dataUtils.js');
let navUtils=require('../../utils/navUtils.js');
const { $Message } = require('../../iview/base/index');
// pages/index/index.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    IMGURL: netUtils.BASEURL,
// 幻灯片
    slides:[],
// 推荐
    recommends:[],
    recommendPage:1,
    recommendPageSize:5,
    // blog
    blogs: [],
    blogPage: 1,
    blogPageSize: 10,
//config
    slideId: getApp().globalData.StorageDB.get('config.slideId'),
    recommendCategoryId: getApp().globalData.StorageDB.get('config.recommendCategoryId'),
    blogCategoryId:getApp().globalData.StorageDB.get('config.blogCategoryId'),
    hasMore:true
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.getSlides();
    this.getRecommend();
    this.getBlog();
  },
  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
    this.setData({
      // 推荐
      recommends: [],
      recommendPage: 1,
      recommendPageSize: 5,
      // blog
      blogs: [],
      blogPage: 1,
      blogPageSize: 10,
      hasMore:true
    });
    this.getSlides();
    this.getRecommend();
    this.getKindleBook();
    this.getBlog();
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    this.getBlog();
  },
  //自定义方法

  getSlides(){
    dataUtils.getSlides({id:1})
    .then(res=>{
      if(res.data.status=='200'){
        this.setData({
          slides:res.data.data.item
        });
      }
    });
  },
  getRecommend(){
    dataUtils.getPostOfCategory({
      page:this.data.recommendPage,
      pageSize:this.data.recommendPageSize,
      id:this.data.recommendCategoryId
    }).then(res=>{
      if (res.data.status == '200') {
        if(res.data.data.pageData.length==0){
          return;
        }
        let recommendPage = this.data.recommendPage;
        recommendPage = recommendPage + 1;
        let recommends = this.data.recommends.concat(res.data.data.pageData);
        this.setData({
          recommends: recommends,
          recommendPage: recommendPage
        });
      }
      else{
        $Message({
          content: '未获取到数据~',
          type: 'error'
        });
      }
    });
  },
  getBlog() {
    dataUtils.getPostOfCategory({
      page: this.data.blogPage,
      pageSize: this.data.blogPageSize,
      id: this.data.blogCategoryId
    }).then(res => {
      if (res.data.status == '200') {
        if (res.data.data.pageData.length == 0) {
          this.setData({
            hasMore:false
          });
          return;
        }
        let blogPage = this.data.blogPage;
        blogPage = blogPage + 1;
        let blogs = this.data.blogs.concat(res.data.data.pageData);
        let blogLeft=blogs.filter((value,index)=>{
     return index%2!=0});
        let blogRight = blogs.filter((value, index) => { return index % 2 == 0 });
        this.setData({
          blogs: res.data.data.pageData,
          blogLeft:blogLeft,
          blogRight:blogRight,
          blogPage: blogPage
        });
      }
      else {
        $Message({
          content: '未获取到数据~',
          type: 'error'
        });
      }
    });
  },
  navToContent(e){
    navUtils.navToContent(e.currentTarget.dataset.id);
  },
  navToBlog(){
    wx.switchTab({
      url: '../blog/blog',
    });
  }
})
复制代码

代码比较简单(毕竟技术比较菜),值得注意的是这个getBlog方法,该方法获取的是首页上,中下部分的那一坨博客,当页面触底时会会加载更多

觉得单纯的排列下来比较平庸,因此想搞个瀑布流,反正没那么多性能和外观要求,所以就搞了个假的瀑布流,具体操作就是在获取到数据后,把数据分成两半,左半边和右半边,在前端也是把这两个数组分别循环一下即可。

由于页面有下拉刷新与拉到底部会加载更多,因此,在这的逻辑就是,

当触发上拉加载时

  1. 从服务器获取到数据
  2. 判断下数据是否为空,若为空则说明无更多数据可加载将hasMore设为false即可,page与blogs数据均不变
  3. 若不为空则拼接到现有数据数组的尾部,同时将页码page在现有基础上+1(即将页码的操作放在每次请求之后,这样每次请求时候无需考虑页码是否+1直接用即可),即先拼接在处理页码最后setData

当触发下拉刷新时

  1. 先将page设置为1,清空现有blogs数据
  2. 调用getBlog

这样做能把下拉刷新与上拉加载的功能均用getBlog来做无需根据情况来判断如何处理(替换or拼接)从服务器请求来的数据

第一期结尾

罗里吧嗦的有点长了,越写越感觉做的不咋地写的更不咋地,看各位看官凑合的看下把(估计没几个人会看到这个地方...),我也整理整理思路,希望写后续第二期的时候能把想表达的写出来。

你可能感兴趣的:(后端,ui,前端)