零基础学会开发一个博客微信小程序,1小时搞定!快收藏!

409 阅读8分钟

0.前言

前面通过几篇文章带大家入门了微信小程序开发:

今天这篇文章手把手教大家开发一个博客小程序

虽然这个小程序只是一个简单的 Demo,但是我会教会你开发程序的思维方式。

你有了好的思维方式,无论是开发 Vue 项目,或者是开发 uniapp 项目,亦或者微信小程序,都能如鱼得水。因为学到最后,你会发现各个框架套路都一样,语言之间是融会贯通的。

话不多说,打开电脑,打开微信开发者工具,开干!

别着急,先看一下演示效果:

1.创建微信小程序

然后删掉 app.js 这两个配置

修改全局导航栏标题文字标题颜色导航栏背景

然后删除 index.wxml 里面的代码,修改 index.js 代码:

最后再删除 logs 文件夹,修改 app.json 里面 pages 配置

注:微信小程序会根据 app.json 里面 pages 配置的优先顺序加载页面。

点击编译,会加载首页内容:

2.封装 http 请求

因为微信小程序原生的 request 请求 API 不是很好用,所以需要进行封装:

/**
 * HTTP请求工具类
 */
class Http {
  // 构造方法
  constructor() {
    // 基础URL
    this.baseUrl = 'http://127.0.0.1:8083/zhifou-blog';
    // 超时时间(毫秒)
    this.timeout = 15000;
  }
    /**
   * 设置基础URL
   * @param {string} baseUrl 基础URL
   */
  setBaseUrl(baseUrl) {
    this.baseUrl = baseUrl;
  }
  /**
   * 设置超时时间
   * @param {number} timeout 超时时间(毫秒)
   */
  setTimeout(timeout) {
    this.timeout = timeout;
  }

    /**
   * 发送请求
   * @param {string} method 请求方法
   * @param {string} url 请求URL
   * @param {object} data 请求参数
   * @param {object} header 请求头
   */
  request(method, url, data, header) {
    // 合并默认请求头
    const defaultHeader = {
      'content-type': 'application/json'
    };
    const finalHeader = { ...defaultHeader, ...header };
    // 构建完整URL
    const fullUrl = this.baseUrl + url;

    // 返回Promise对象
    return new Promise((resolve, reject) => {
      wx.request({
        url: fullUrl,
        method: method,
        data: data,
        header: finalHeader,
        timeout: this.timeout,
        success: (res) => {
          // 请求成功
          if (res.data.code ==200) {
            resolve(res.data);
          } else {
            // 请求失败
            reject({
              statusCode: res.data.code,
              data: res.data,
              message: `请求失败,状态码:${res.data.code}`
            });
          }
        },
        fail: (err) => {
          // 请求失败
          reject({
            message: '网络请求失败',
            error: err
          });
        }
      });
    });
  }

  /**
   * 发送GET请求
   * @param {string} url 请求URL
   * @param {object} data 请求参数
   * @param {object} header 请求头
   */
  get(url, data = {}, header = {}) {
    return this.request('GET', url, data, header);
  }

  /**
   * 发送POST请求
   * @param {string} url 请求URL
   * @param {object} data 请求参数
   * @param {object} header 请求头
   */
  post(url, data = {}, header = {}) {
    return this.request('POST', url, data, header);
  }

  /**
   * 发送PUT请求
   * @param {string} url 请求URL
   * @param {object} data 请求参数
   * @param {object} header 请求头
   */
  put(url, data = {}, header = {}) {
    return this.request('PUT', url, data, header);
  }

  /**
   * 发送DELETE请求
   * @param {string} url 请求URL
   * @param {object} data 请求参数
   * @param {object} header 请求头
   */
  delete(url, data = {}, header = {}) {
    return this.request('DELETE', url, data, header);
  }
}

// 导出
export default new Http();

然后将不校验合法域名勾选上:

3.安装配置 vant weapp

本项目我们采用第三方的前端组件库 Vant weapp

https://vant-ui.github.io/vant-weapp/#/home

使用 npm 安装该插件:

npm i @vant/weapp -S --production

接着将 app.json 中的 "style": "v2" 去除。

然后点击微信开发者工具顶部工具 -> 构建 npm

4.配置 tabbar

在 app.json 文件 pages 里面新增 "pages/my/my",你会发现微信小程序会自动创建 my 文件。其实跟你选中 pages 文件夹,然后右键新增 Page-my 是一样的。

接着在 app.json 里面配置 tabbar 属性。

  "tabBar": {
    "selectedColor": "#fea02a",
    "position": "bottom",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "/images/tabbar/home.png",
        "selectedIconPath": "/images/tabbar/home-s.png"
      },
      {
        "pagePath": "pages/my/my",
        "text": "我的",
        "iconPath": "/images/tabbar/my.png",
        "selectedIconPath": "/images/tabbar/my-s.png"
      }
    ]
  }

新建 images 文件夹,将项目中需要的 icon 和其他图片都放进里面。然后重新编译项目:

5.登录功能

在 app.json 的 pages 里面新增 "pages/login/login",然后将该值移动到最前面,重新编译项目,会先启动登录页面。

然后删除 login.wxml 和 login.js 没有用的代码,接下来开发登录页面:

登录页面因为要用到 Vant 的 van-cell-group、van-field、van-button 组件,所以需要在 login.json(局部) 或者 app.json(全局)进行配置。因为这几个组件用的比较多,所以这里进行全局配置

注:vant 官网会给出所有组件的引入方式

login.wxml 代码:

<view class="container">
  <!-- 标题区域 -->
  <view class="header">
    <text class="title">知否技术博客</text>
  </view>
  <van-cell-group>
    <van-field
    model:value="{{ username }}"
    clearable
    left-icon="contact"
    label="账号"
    placeholder="请输入账号"
  />

  <van-field
    model:value="{{ password }}"
    type="password"
    label="密码"
    left-icon="lock"
    placeholder="请输入密码"
  />
</van-cell-group>
 <!-- 登录按钮 -->
 <van-button  class="login-button" size="large"    bind:tap="handleSubmit" color="#fea02a">登录</van-button>
</view>   

讲解:

  • model:value 表示双向绑定,但是只能绑定单个变量,不能绑定对象里面的属性。
  • bind:tap="handleSubmit" 绑定 login.js 的登录方法

login.js

import http from "../../utils/http"
Page({
  /**
   * 页面的初始数据
   */
  data: {
    username: '',
    password: '',
  },

  // 处理表单提交
  async handleSubmit(e) {
    const { username, password } = this.data;
    // 账号验证
    if (!username) {
      wx.showToast({
        title: "用户名不能为空!",
        icon: 'none'
      });
      return;
    }
    // 密码验证
    if (!password) {
      wx.showToast({
        title: "密码不能为空!",
        icon: 'none'
      });
      return;
    }

    try {
      // 发送登录请求
      const res = await http.post('/user/login', {
        username,
        password
      });
      if (res.code == 200) {
        // 保存用户信息
        wx.setStorageSync('userInfo', res.data.userInfo)
        // 跳转到首页
        wx.reLaunch({
          url: '/pages/index/index'
        });
      }
    } catch (error) {
      console.log("error",error)
      // 登录失败
      wx.showToast({
        title: error.data.message,
        icon: 'none'
      });
    } 
  },
})

代码讲解:

  • 首先引入了 http 工具
  • Page 的 data 属性定义了两个变量:账号和密码
  • 登录之前先校验账号和密码不能为空
  • 调用后台接口进行登录
  • 登录成功使用 wx.setStorageSync 存储用户信息,然后跳转到首页

6.博客列表页面

首先博客列表页面是这样布局的:头部是搜索框+新增博客按钮,然后是 tab 标签页,接着就是博客列表数据。

这个页面用到了 vant 的 tab 和 search 组件,这里在 index.json 文件里面进行配置:

index.json:

{
  "usingComponents": {
    "van-tab": "@vant/weapp/tab/index",
    "van-tabs": "@vant/weapp/tabs/index",
    "van-search": "@vant/weapp/search/index"
  }
}

index.wxml:

<!-- 搜索 -->
<van-search  value="{{ value }}" placeholder="请输入搜索关键词" use-action-slot  bind:blur="searchBlog"  >
  <view slot="action" bind:tap="onClick">
    <van-icon bind:tap="enterAddUpdate" size="60rpx" class="search-right" name="add-o" />
  </view>
</van-search>
<!-- tab标签页 -->
<van-tabs active="{{ active }}" bind:click="changeTab">
  <van-tab title="vue"></van-tab>
  <van-tab title="React"></van-tab>
  <van-tab title="uniapp"></van-tab>
  <van-tab title="JS"></van-tab>
  <van-tab title="CSS"></van-tab>
</van-tabs>
<!-- 博客数据 -->
<van-card wx:for="{{dataList}}" id="{{item.id}}" wx:key="index" title-class="title" desc="{{item.content}}" title="{{item.title}}" thumb="{{ imageURL }}" bind:tap="enterBlogDetail">
  <view slot="price">
    <view class="author">
      <text>作者:{{item.author}}</text> <text>{{item.createTime}}</text>
    </view>
  </view>
</van-card>

代码讲解:

  • bind:blur="searchBlog" 表示搜索框失去焦点调用搜索列表接口
  • bind:click="changeTab" 切换 tab 页调用搜索接口
  • wx:for 循环遍历博客列表数据
  • id="{{item.id}}" 用来绑定每一个博客的 id,然后点击进入详情页时可以获取该博客的 id。
  • bind:tap="enterBlogDetail" 绑定进入博客详情页方法
  • bind:tap="enterAddUpdate" 绑定进入新增博客页面方法

index.js:

import http from "../../utils/http"
Page({
  data: {
    active: 0,
    imageURL: "/images/banner/banner1.png",
    value: "",
    search: {
      page: 1,
      size: 10,
      title: "",
      type: "vue",
    },
    dataList: []
  },
  onLoad() {
    this.getDataList()
  },
  // 切换 Tab
  changeTab(event) {
    this.setData({
      'search.type': event.detail.title
    });
    this.getDataList();
  },
  // 搜索博客
  searchBlog(event){
    console.log("event",event.detail.value);
    this.setData({
      'search.title': event.detail.value
    });
    this.getDataList();
  },
  // 获取列表数据
  async getDataList() {
    const res = await http.get("/blog/page", this.data.search);
    this.setData({
      dataList: res.data.records
    })
  },
  // 进入博客详情页
  enterBlogDetail(event) {
    // 进入博客详情页,传递参数
    wx.redirectTo({
      url: '/pages/blog/detail/index?id=' + event.target.id,
    })
  },
  // 进入新增博客页面
  enterAddUpdate() {
    wx.redirectTo({
      url: '/pages/blog/addUpdate/index',
    })
  }
})

代码讲解:

  • 导入 http 工具类
  • data 定义变量,其中 active 表示 tab 列表当前激活页,seach 是查询博客列表的条件参数,dataList 是博客列表数据。
  • onLoad 表示加载页面的生命周期,然后调用 getDataList 方法获取博客列表数据。也就是说一进入到列表页面就获取数据,渲染页面。
  • changeTab 是切换 tab 方法 ,然后更改 type 的值,重新调用后台列表接口。
  • searchBlog 是和搜索框进行绑定,搜索框失去焦点,重新修改 title 的值,然后再重新调用后台列表接口。
  • getDataList 是调用后台接口获取列表的方法
  • enterBlogDetail 进入博客详情页方法,通过 event.target.id 拿到该博客的id。
  • enterAddUpdate 直接跳转到新增博客页面

7.新增博客功能

在 pages 文件夹下新建 /blog/addUpdate 和 /blog/detail 文件夹,分别是新增博客和博客详情页的功能。

新增博客页面主要用到了van-field 和 van-picker、 van-popup 组件,其中van-picker 是下拉选择器,van-popup 是 弹出层:

addUpdate/index.wxml:

<view>
  <van-nav-bar title="新增博客" left-text="返回" left-arrow bind:click-left="onClickLeft" />
  <!-- 新增/编辑博客 -->
  <van-cell-group>
    <van-field model:value="{{ title }}" label="标题" clearable placeholder="请输入标题" />
    <van-field model:value="{{ type }}" label="类别" clearable placeholder="请选择类别" readonly="true" bind:click-input="onSelectType" />
    <van-field model:value="{{ content }}" label="内容" type="textarea" placeholder="请输入内容" autosize />
  </van-cell-group>
  <!-- 提交按钮 -->
  <van-button color="#fea02a" size="large" bind:tap="handleSubmit" type="info">提交</van-button>
  <!-- pop弹出框 -->
  <van-popup   position="bottom" show="{{ selectTypeVisible }}" bind:close="onClose"> <van-picker custom-class="selectType" title="请选择类别" show-toolbar bind:cancel="onCancel" bind:confirm="onConfirm" wx:if="{{selectTypeVisible}}" columns="{{ columns }}"  /></van-popup>
</view>

addUpdate/index.js:

import http from "../../../utils/http"
Page({
  data: {
    columns: ['vue', 'React', 'uniapp', 'Js', 'CSS'],
    selectTypeVisible : false,
    id:"",
    type: "",
    title:"",
    content:"",
  },
  onLoad(){
    const userInfo = wx.getStorageSync("userInfo");
    if(!userInfo){
      wx.redirectTo({
        url: '/pages/login/login',
      })
    }
  },
  onClickLeft(){
    wx.switchTab({
      url: '/pages/index/index',
    })
  },
  onSelectType(){
   this.setData({
    selectTypeVisible:!this.data.selectTypeVisible
   })
  },
  onConfirm(event){
    this.setData({
      type:event.detail.value,
      selectTypeVisible:!this.data.selectTypeVisible
     });
  },
  onCancel(){
    this.setData({
      selectTypeVisible:!this.data.selectTypeVisible
     })
  },
  // 提交
 async handleSubmit(){
  try {
    // 发送请求
    const { title, type,content } = this.data;
    const userInfo = wx.getStorageSync("userInfo");
    const userId = userInfo.id;
    const author = userInfo.name;
    const res = await http.post('/blog/save', {
      userId,
      title,
      type,
      author,
      content    
    });
    if (res.code == 200) {
      wx.reLaunch({
        url: '/pages/index/index'
      });
    }
  } catch (error) {
    // 失败
    wx.showToast({
      title:'保存失败',
      icon: 'none'
    });
  } 
  }
})
  • 导入 http 工具类
  • 在 onLoad 生命周期函数里面判断用户是否登录,如果没有登录就跳转到登录页面。
  • onConfirm 绑定下拉选择确定的方法,然后设置 type 的值
  • handleSubmit 调用后台保存接口

8.博客详情页

detail/index.wxml:

<!-- 自定义导航栏 -->
<van-nav-bar left-text="返回" left-arrow bind:tap="onClickLeft" />
<!-- 博客详情页内容 -->
<view>
  <view>
    <text class="title">{{detail.title}}</text>
    <view class="info">
      <van-row gutter="20">
        <van-col span="8">
          <van-image width="100" height="80" src="https://img.yzcdn.cn/vant/cat.jpeg" />
        </van-col>
        <van-col span="16" >
          <view >
            <view>作者:{{detail.author}}</view>
            <view class="createtime">{{detail.createTime}}</view>
          </view>
        </van-col>
      </van-row>
    </view>

    <view class="content">
      {{detail.content}}
    </view>
  </view>
</view>

detail/index.js:

import http from "../../../utils/http"
Page({

  data: {
    detail: {
      title: "",
      type: "",
      content: "",
      author: "",
      createTime: "",
    }
  },
  onLoad(options) {
    if (options.id) {
      this.getBlogDetail(options.id);
    }
  },
  async getBlogDetail(id) {
    try {
      const res = await http.get("/blog/info/" + id);
      if (res.code === 200) {
        this.setData({
          detail: res.data
        })
      }
    } catch (error) {
      wx.showToast({
        title: "服务器错误",
        icon: 'none'
      });
    }
  },
  onClickLeft() {
    wx.switchTab({
      url: '/pages/index/index',
    })
  }

})
  • onLoad 的 options 里面获取路径参数,然后调用后台博客详情接口
  • getBlogDetail 调用后台接口

9.个人中心

个人中心主要是个人信息展示,用户退出功能:

my.wxml:

  <view>
    <van-card title-class="title" centered title="{{name}}" thumb="{{ avatar }}" bind:tap="enterBlogDetail">
    </van-card>
    <van-cell-group inset>
      <!-- <van-cell title="我的博客" is-link /> -->
      <van-cell title="退出登录" is-link bind:tap="logout" />
    </van-cell-group>
    <view class="zhifou">
      <van-image width="200" height="200" src="/images/zhifou.jpg" />
      <text>扫描二维码,关注我的公众号</text>
    </view>
  </view>

my.js:

用户退出登录之后清除本地缓存,跳转到首页

Page({
  data: {
    actions: [{
      name: "我的博客"
    }, {
      name: "退出登录"
    }],
    name: "",
    avatar: "/images/avatar.jpg"
  },
  onLoad() {
    const userInfo = wx.getStorageSync("userInfo");
    if (!userInfo) {
      wx.navigateTo({
        url: '/pages/login/login',
      })
    }
    this.setData({
      name: userInfo.name
    });
  },
  logout() {
    // 清除缓存
    wx.clearStorageSync();
    // 跳转到首页
    wx.reLaunch({
      url: '/pages/index/index',
    })
  }
})

10.完整源代码

后端是简单的 springboot 项目:

通过网盘分享的文件:微信小程序-知否博客demo
链接: https://pan.baidu.com/s/18HSvwC8Agrvjh3peyy1RHQ?pwd=6666 提取码: 6666