书接上文,前端采用小程序原生
开发,后端采用eggjs
。关于后端项目搭建,可以参考我的另一篇文章《使用egg.js + Vue实现一个简书(三)---服务端搭建》(点我传送)
,有非常详细的讲解,而且代码也在gitee上共享了,后文会对一些改造点单独提出。
本章要点
- 从头搭建一个WX小程序
- 导航栏和tabbar的配置和优化
- 实现接口封装
- 实现自动登录
搭建小程序
1. 注册小程序
在WX公众平台上注册小程序
在该页面,依照提示注册即可,需要注意的是:
- 第一步账号信息,一个邮箱只能申请一个小程序
- 小程序主体是个人还是企业,决定了WX能提供的服务内容。比如接入支付需要WX认证(要花钱,一年300),而个人号是无法进行WX认证的。
- 服务类目决定了小程序允许有的功能,比如我的小程序有日程提醒的功能,则必须申请
工具-备忘录
的服务类目,首页有一个日历,则必须申请工具-日历
的服务类目。这个上线的时候,提交审核后如果缺少某些类目,审核员会打回申请并告知缺漏了什么,补上就可以了。每月可以申请5次更改,问题不大。
我的服务类目
2. 创建一个小程序工程
小程序开发必须要用到WX开发者工具
,在官方文档上下载即可。
开发者工具可以预览效果,也可以写代码,不过我习惯在编辑器上写代码,所以只用来看页面效果和其他操作。
打开开发者工具后,进入创建项目弹窗
APPID填写你申请小程序的appid即可,开发模式选
小程序
,后端模式选不使用云服务
,模板选择选不使用模板
。上面是我的选择,各位可以根据自身需求选择不同的选项。
3. 调整目录结构
如果要使用外部的npm包,需要把项目代码和node_modules分隔出来,之后小程序打包,会用项目代码文件夹里的代码出包。
- 调整目录
如图,原本项目代码在birthtips文件夹下,我创建了一个miniprogram
文件夹,用于存放小程序的代码,将模板生成的代码,除了project.config.json
和project.private.config.json
之外,其他的都放到miniprogram
文件夹里。 - 创建package.json
npm init -y
即可,然后按需安装需要的npm包
比如我前面提到的lunar-javascript
包,就在这时候安装。
安装包之后,会生成node_modules
文件夹。 - 修改配置
由于我们调整了小程序的目录解构,所以需要修改下project.config.json
的配置,让小程序运行的时候,知道要去什么位置找相应的配置文件。
project.config.json
如图添加或修改红圈里的配置即可。 - 编译node_modules
要知道的是,小程序有效的内容,在miniprogram
文件夹中,所以外部node_modules
里安装的内容,需要再经过一次构建才能生效。
在开发工具中,点工具-构建npm
,构建后,会在miniprogram中生成miniprogram_npm文件夹。
node_modules
目录不会参与编译、上传和打包中,所以小程序想要使用npm
包必须走一遍构建 npm
的过程,在每一份miniprogramRoot
内开发者声明的package.json
的最外层的node_modules
的同级目录下会生成一个miniprogram_npm
目录,里面会存放构建打包后的npm包,也就是小程序真正使用的npm包。
- miniprogram下的目录结构
目录结构
如图,为我的项目miniprogram
文件夹下的目录结构。
app.json
是全局配置
每新加一个页面都需要在这里添加配置,且配置tabbar、导航栏的配置,也是在这里。具体内容可以看WX小程序开发官方文档,写的很清楚。
app.js
是入口文件,页面运行从这里开始
pages
是页面文件夹,下面也是,一个页面一个文件夹,文件夹下需要有index.js、index.json、index.wxml、index.wxss
,这个 属于固定配置,wxml
和wxss
写法和html、css
基本一样,会前端的小伙伴可以丝滑切入。
components
文件夹里是组件,组件文件夹的构成和页面文件夹一致。
image
文件夹里是图片资源,在小程序里,页面引用、css引用图片文件,都必须使用https的线上图片,而tabbar的图标,则需要放在这里。
至此,项目搭建工作告一段落。
tabbar配置
我使用的是原生的tabbar,有【轻记】和【我的】两个tab,直接在app.json
中配置即可。
// app.json
"tabBar": {
"custom": false, // 如果要自定义tabbar,将该属性设为true
"color": "#000",
"selectedColor": "#E0555D",
"backgroundColor": "#f1f1f1",
"list": [
{
"pagePath": "pages/index/index",
"text": "轻记",
"selectedIconPath": "image/change-selected-icon.png",
"iconPath": "image/change-icon.png"
},
{
"pagePath": "pages/user/index",
"text": "我的",
"selectedIconPath": "image/user-selected-icon.png",
"iconPath": "image/user-icon.png"
}
]
},
color
和selectedColor
指的是tab里的文字颜色,backgroundColor
是tabbar的背景色,pagePath
是当前tab跳转的页面路由,selectedIconPath
和iconPath
指的是选中和未选中时的图标图片地址。
如果要自定义tabbar,则将custom属性设为true,同时其他配置也得补全,然后自己实现tabbar组件。具体方案可参考文档 传送门
导航栏配置
有三种导航栏实现方案
- 默认配置
- 自定义
- page-meta + navigation-bar
这三种方案,我基于页面需要都有使用,接下来将一一介绍。
首先在app.json
进行基本设置。
// app.json
"window": {
"backgroundTextStyle": "light", // 下拉 loading 的样式,仅支持 dark / light
"navigationBarBackgroundColor": "#f1f1f1", // 导航栏背景颜色
"navigationBarTitleText": "Weixin", // 默认的标题,在具体页面的index.json中覆盖其值
"navigationBarTextStyle": "black", // 导航栏字体颜色
"backgroundColor": "#f1f1f1" // 窗口的背景色
},
1. 默认配置
// pages/cardetail/index.json
"navigationBarTitleText": "打卡详情"
navigationBarTitleText
属性被页面配置覆盖,所以该页面标题显示【打卡详情】
这种方案适用于标题是固定的场景。
2. 自定义
小程序的首页,需要有展开收起日历的按钮,且日历收起时,标题为空,日历展开时,标题为当前日历的年月。
step1:设置使用自定义导航栏
// pages/index/index.json
"navigationStyle": "custom"
navigationStyle
设置为custom
后,原有的导航栏位置将会消失,.wxml
中的页面将顶到最顶部,但左侧的返回按钮,右侧的胶囊会一直悬浮在原位。
可以在.wxml
中增加一个dom
元素,替代原有导航栏的位置,上面可以自定义的放置需要的内容。
这时,遇到了一个问题,导航栏的高度应该是多少?
我们肯定不能写死这个高度,因为不同机型的高度肯定是不一样的。
step2:获取本机信息,计算导航栏需要的高度
// 小程序的导航栏高度 = 状态栏高度 + 右侧胶囊高度,胶囊高度大部分机型是44px,我们直接取44px即可。
// 状态栏高度需要通过小程序提供的方法获取,我们一般在app.js中onLaunch的生命周期中获取,然后存到globalData中,这样页面中就可以直接用了,而不用频繁调用系统接口。
// app.js
const globalData = {
navBarHeight: 44
};
// app.js
App({
globalData,
onLaunch() {
let res = wx.getSystemInfoSync(); // 同步获取系统信息
this.globalData.windowWidth = res.windowWidth;
this.globalData.windowHeight = res.windowHeight;
this.globalData.statusBarHeight = res.statusBarHeight;
this.globalData.navBarHeight = res.statusBarHeight + 44 //顶部状态栏+顶部导航,大部分机型默认44px
}
})
step3: 写页面
// 首页.wxml
<view style="height: {{ navBarHeight }}px;" class="custom-header">
<view
class="calendar-btn"
style="margin-top: {{ statusBarHeight }}px;"
bindtap="changeCalendarType"
>{{showCalendar ? '收起' : '展开'}}日历
</view>
<view class="custom-title" style="margin-top: {{ statusBarHeight }}px;">{{ nbTitle }}</view>
</view>
// 首页.wxss
.custom-header {
position: fixed;
top: 0;
background: #f1f1f1;
width: 100%;
text-align: center;
z-index: 20;
}
.custom-header .custom-title {
height: 44px;
line-height: 44px;
}
.custom-header .calendar-btn {
height: 44px;
line-height: 44px;
position: fixed;
padding-left: 22rpx;
color: #2D6286;
font-size: 28rpx;
}
这种方案适用于需要在导航栏增加一些元素,或者需要去掉导航栏,顶头展示页面内容的场景。
3. page-meta + navigation-bar
小程序的新增页面,需要根据进入时选的日程类型显示新增页的标题,比如从每日打卡进来,标题为【创建每日打卡】,这时默认配置就达不到目的了。
// 新增.wxml page-mata必须是页面内的第一个节点
<page-meta>
<navigation-bar
title="{{nbTitle}}"
background-color="{{nbBackgroundColor}}"
front-color="#000000"
color-animation-duration="2000"
color-animation-timing-func="easeIn"
/>
</page-meta>
<view>页面其他内容。。。。。</view>
加这个就可以了,只需要按需修改nbTitle这个变量的值就可以了,非常简单。
这种方案适用于,需要自定义设置标题的值,但对导航栏的其他部分无需改动的场景。
小程序接口封装
1. 全局配置
正常小程序开发,会有多个环境,开发、测试、生产环境,针对不同环境,有一些配置项的值,也会有所不同。我的习惯是创建一个config.js,如下:
// config/index.js
const envKey = 'test'; // 需要什么环境配什么环境
const envMap = {
'dev': {
// 后端接口的地址(由于开发环境也是本地运行的eggjs服务,所以指向127.0.0.1)
requestUrl: 'http://127.0.0.1:7001',
messageTemplateId: '开发环境模板消息模板id',
},
'test': {
requestUrl: '测试环境接口地址',
messageTemplateId: '测试环境模板消息模板id',
},
'prod': {
requestUrl: '生产环境接口地址',
messageTemplateId: '生产环境模板消息模板id',
}
};
export default {
envKey,
envSet: envMap[envKey]
};
// app.js
import globalConfig from './config/index';
const globalData = {
globalConfig,
};
App({
globalData,
})
首先在config/index.js做了配置,分别导出当前环境和当前环境对应的配置。
然后在app.js中引入了配置项,然后将其存放到全局变量globalConfig中。
在页面中,可以用以下方式使用
const app = getApp();
const globalApp = app.globalData;
console.log(globalApp.globalConfig.envSet);
2. 封装请求接口方法
创建一个request.js文件
// utils/request.js
import globalConfig from '../config/index';
export const myRequest = (config) => {
const header = {
authorization: wx.getStorageSync('token') || ''
};
return new Promise((res, rej) => {
let url = config.url.replace(/^\//, '');
wx.request({
url: `${globalConfig.envSet.requestUrl}/${url}`,
data: config.data,
header: Object.assign({}, header, config.header || {}),
method: config.method || 'POST',
responseType: config.responseType,
success(reqRes) {
if (reqRes && reqRes.data) {
if (config.responseType == 'arraybuffer') {
res(reqRes.data);
} else {
if (reqRes.data.status == 200) {
res(reqRes.data);
} else {
wx.showToast({
title: reqRes.data.message,
icon: 'none'
});
rej(reqRes.data);
}
}
}
},
fail(reqErr) {
rej(reqErr);
}
})
});
}
// app.js
import { myRequest } from './utils/request';
const globalData = {
myRequest,
};
App({
globalData,
})
我们封装了myRequest这个方法,将其放到全局变量globalData中,后续页面发起接口调用,可以用以下方式
// 获取应用实例
const app = getApp();
const globalApp = app.globalData;
globalApp.myRequest({
url: '/plan/list',
data: {
needCardDetail: true
}
}).then(res => {
if (res && res.status == 200) {
// 后续操作
}
}).catch(err => {
console.log(err);
});
小程序请求接口,必须通过微信提供的wx.request方法【传送门】
我们封装的myRequest方法,接收页面调用时的入参config对象
key | 含义 |
---|---|
url | 接口地址 |
data | 请求参数 |
header | 请求头配置 |
method | 请求方式 |
responseType | 响应格式 |
config对象参数列表
看上述代码可知:
1.实际请求的url由globalConfig.envSet.requestUrl里配置的值 + 入参config中的url拼接得到。
header中默认有一个authorization,这是登录后服务端生成的token,存在前端缓存storage中。config中的header中也可以传入一些头信息,最终会合并在一起。
2.接口返回状态码决定了请求是否成功,这里的设定是,200为请求成功,401为登录信息失效,其他为失败。
3.myRequest方法最终返回一个Promise对象,接口请求成功,会通过resolve回调将接口返回值传递给调用方。请求失败,会通过reject回调将错误信息返回,并弹窗提示报错信息,在reqRes.data.message中。
4.在调用方视角,如果接口请求成功,会执行.then方法,如果请求失败,会弹窗出现错误信息,并执行.catch方法
总结
本篇文章介绍了小程序工程的基本搭建,但同时留了一个尾巴,就是小程序的自动登录。这个需要前后端一起配合实现,我将另开一篇文章详细展示。
关于本文各位有什么意见和建议,也欢迎评论区提出,么么哒(づ ̄ 3 ̄)づ