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