小程序开发经验分享:踩坑指南与技巧总结

本文最后更新于:1 年前

每一次的放手,都是为了更好的拥有,让我们学会放下,迎接更加美好的未来。

前言

  • 记录小程序开发过程中的问题

正文

小程序开发

开发准备

HBuilderX+uni-app+uinCloud

微信开发者工具连接失败

image-20230803210244455

image-20230803210305939

image-20230803210317551

微信开发者工具

  • 最近在开发 PicMemories 壁纸分享小程序,浅浅记录遇到的一些问题

请求域名不合法

  • 开发过程中,不校验合法域名:

image-20230803192902265

image-20230803192858399

image-20230803210743470

全局封装 request

  • 安装 axios:
1
npm install axios
  • app.js 下做如下配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
App({
request(options) {
const { url, method, data, success, fail } = options;
wx.request({
url,
method,
data,
success(res) {
success && success(res.data);
},
fail(error) {
fail && fail(error);
},
});
},
onLaunch: function () {
// 小程序启动时触发,可以进行一些初始化操作
console.log("App launched");
},
onShow: function () {
// 小程序进入前台时触发
console.log("App showed");
},
onHide: function () {
// 小程序进入后台时触发
console.log("App hided");
},
globalData: {
userInfo: null, // 全局用户信息
themeColor: "#F44336", // 全局主题颜色
},
});
  • index.js 下如此请求:
1
const app = getApp();
1
2
3
4
5
6
7
8
9
10
app.request({
url: "http://localhost:8084/api/user/current",
method: "GET",
success: function (data) {
console.log(data);
},
fail: function (error) {
console.log(error);
},
});
  • 我这里后端接收请求出现如下错误:
1
Invalid character found in method name [0x160x030x010x020x000x010x000x010xfc0x030x031E0xe3]0x8eQ0xd60x0e0xf7mJ0x950x920x810xe00x0c0x820xb5)0xc70xdf0xefY0xe30xb8|0x9d0xd430xe50x8f5 ]. HTTP method names must be tokens
  • 经排查,是因为把 http 写成 https 了:

image-20230803194528518

image-20230803194648359

image-20230803194709864

数据双向绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<van-cell-group>
<van-field
required
model:value="{{username}}"
label="昵称"
placeholder="请输入昵称"
/>
<van-field
required
model:value="{{phone}}"
label="密码"
placeholder="请设置密码"
/>
<van-field
required
model:value="{{password}}"
label="手机号"
placeholder="请输入手机号"
/>
</van-cell-group>
1
2
3
4
5
data: {
username: '',
password: '',
phone: ''
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
goToRegister() {
app.request({
url: 'http://localhost:8084/api/user/register',
method: 'POST',
data: {
username: this.data.username,
password: this.data.password,
phone: this.data.phone
},
success: function (data) {
console.log(data);
},
fail: function (error) {
console.log(error);
}
})
},

生命周期函数

1
2
3
4
5
6
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {

},
1
2
3
4
5
6
/**
* 生命周期函数--监听页面显示
*/
onShow() {
Toast("分类~")
},
1
2
3
4
5
6
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {

},
1
2
3
4
5
6
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {

},
1
2
3
4
5
6
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {

},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {

},

/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {

},

/**
* 用户点击右上角分享
*/
onShareAppMessage() {

}

路由跳转

  • 官方提供的路由跳转有以下 5 种:
1
wx.switchTab	跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
1
wx.reLaunch		关闭所有页面,打开到应用内的某个页面
1
wx.redirectTo	关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
1
wx.navigateTo	保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
1
wx.navigateBack	关闭当前页面,返回上一页面或多级页面
  • 我们可以分析出在不同的业务场景下,使用怎样的路由跳转更合适:
  • wx.switchTab:支持跳转至 tabbar 页面
    • 跳转至 tabbar 页,关闭其他所有非 tabBar 页面
  • wx.redirectTo、wx.navigateTo:支持跳转至非 tabbar 页面
    • 前者关闭当前页面,后者保留当前页面
    • wx.navigateBack:与 wx.navigateTo 配合,返回上一级页面
  • wx.reLaunch:支持跳转至任何页面
    • 关闭所有页面,打开到应用内的某个页面
  • 那么从登录页跳转至个人页,就应该用 wx.switchTab / wx.reLaunch(2023/08/05 早)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
goToLogin() {
app.request({
url: 'http://localhost:8084/api/user/login',
method: 'POST',
data: {
username: this.data.username,
password: this.data.password,
},
success: function (res) {
console.log(res)
const data = res.data;

if (data.code == 0) {
// 设置sessionID到缓存
wx.setStorageSync('sessionID', res.header["Set-Cookie"]);
Notify({ type: 'success', message: data.description });
wx.reLaunch({
url: '/pages/user/index'
})
} else {
Notify({ type: 'warning', message: data.description });
}
},
fail: function (error) {
console.log(error);
}
})
},

data 已初始化,未触发数据绑定

1
2
3
4
5
Page({
data: {
imgs: [],
urlList: []
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 // 页面创建时执行
  onShow() {
    Toast("惊喜多多~")
    var _this = this
    // 获取壁纸
    app.request({
      url'http://localhost:8084/api/wallpaper/listPage',
      method'GET',
      successfunction (res) {
        const data = res.data;
// 拿取到imgs列表
        _this.setData({
          'imgs': data.data.records
        });
// 拿取到urlList列表
        _this.data.imgs.forEach((item) => {
          _this.data.urlList.push(item.wallpaperUrl); 
        });
      },

      failfunction (error) {
        console.log(error);
      }
    })
  },
1
2
3
<block class="image-list" wx:for="{{urlList}}" wx:key="index">
<image class="img" src="{{item}}" alt="刚刚" mode="widthFix" />
</block>
  • 这里的 image 标签,仍旧无法正常绑定到 urlList
  • 当 show()生命周期函数未执行完毕,即 urlList 仍为空数组时,data 在页面展示时已经初始化
  • 如此改造 show()生命周期函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 页面创建时执行
onShow() {
Toast("惊喜多多~")
this.getData();
},
// 获取imgs列表
getData() {
var _this = this;
// 获取壁纸
app.request({
url: 'http://localhost:8084/api/wallpaper/listPage',
method: 'GET',
success: function (res) {
const data = res.data;
_this.setData({
'imgs': data.data.records
});
_this.setUrlList();
}
});
},
// 获取url列表
setUrlList() {
var urlList = [];
this.data.imgs.forEach((item) => {
urlList.push(item.wallpaperUrl);
});
this.setData({
'urlList': urlList
});
},
  • 成功解决 urlList 数据双向绑定问题(2023/08/05 早)

无法获取 Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 注册
goToLogin() {
app.request({
url: 'http://localhost:8084/api/user/login',
method: 'POST',
data: {
username: this.data.username,
password: this.data.password,
},
success: function (res) {
console.log(res)
const data = res.data;

if (data.code == 0) {
// 设置sessionID到缓存
wx.setStorageSync('sessionID', res.header["Set-Cookie"]);
Notify({ type: 'success', message: data.description });
wx.reLaunch({
url: '/pages/user/index'
})
} else {
Notify({ type: 'warning', message: data.description });
}
},
fail: function (error) {
console.log(error);
}
})
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.request({
url: 'http://localhost:8084/api/user/current',
method: 'GET',
header: {
'Content-Type': 'application/json',
'sessionID': wx.getStorageSync('sessionID')
},
success: function (data) {
console.log(data);
},
fail: function (error) {
console.log(error);
}
})
},

小程序登录 - 用户身份标识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 登录
goToLogin() {
var _this = this;
wx.login({
success(res) {
if (res.code) {
console.log("code = " + res.code)
//发起网络请求
wx.request({
url: 'http://localhost:8084/api/user/login',
method: 'POST',
data: {
code: res.code,
username: _this.data.username,
password: _this.data.password,
},
success: function (res) {
console.log(res)
const data = res.data;

if (data.code == 0) {
// 设置sessionID到缓存
Notify({ type: 'success', message: data.description });
wx.reLaunch({
url: '/pages/user/index'
})
} else {
Notify({ type: 'warning', message: data.description });
}
},
fail: function (error) {
console.log(error);
}
})
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
},
  • 后台:
  • 服务端拿到 code,通过 code 调用 Code3Session 接口,获取 openid、session_key

1
2
private final String appId = "wxd12f7c79bd639a9b";
private final String secret = "8a99ccc1802486c1387c1cda45288dd5";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 public String getCode2Session(String appId, String secret, String code) {
String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appId, secret, code);

HttpRequest request = HttpRequest.get(url);
log.info("result = " + request.execute());
return "request";
}

/** 后台也可以这样写 **/

String url = "https://api.weixin.qq.com/sns/jscode2session";
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("appid", appId);
paramMap.put("secret", secret);
paramMap.put("js_code", code);
paramMap.put("grant_type", "authorization_code");
String result = HttpUtil.get(url, paramMap);
log.info("result = " + result);
return result;
  • 成功:
1
2
Response Body:
{"session_key":"oZ0ju27QocpPhHF2Z1MrtQ==","openid":"otcWC6_yPHlB2Q6e0vIrB_J2iYHs"}
  • 失败
1
{"errcode":40029,"errmsg":"invalid code, rid: 64cf07f9-36a4c687-09c960f0"}
  • 服务端返回 openid、session_key 到前台

  • 前台拿到 openid、session_key,作为 token

  • 之后,前台发送请求,携带 token

  • 服务器通过校验 token,选择是否响应该请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
onShow () {
Toast('个人中心~');
console.log("seeesion_key = " + wx.getStorageSync('session_key'))

app.request({
url: 'http://localhost:8084/api/user/current',
method: 'GET',
header: {
'Authorization': 'Bearer ' + wx.getStorageSync('session_key')
},
success: function (data) {
console.log(data);
},
fail: function (error) {
console.log(error);
}
})
},

路由跳转、携带参数

  • 首页跳转至壁纸详情页,携带 url 路径:
1
2
3
4
5
6
7
8
9
10
11
// 跳转到壁纸详情页
onClickShow(e) {
const imageUrl = e.currentTarget.dataset.url;
const parameter = {
url: imageUrl
};
const url = '/pages/imageInfo/index?parameter=' + encodeURIComponent(JSON.stringify(parameter));
wx.navigateTo({
url: url
});
},
  • 壁纸详情页接收参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
const {url} = JSON.parse(decodeURIComponent(options.parameter));
console.log(url)
this.getData(url)
},

// 获取壁纸url
getData(url){
this.setData({
'url': url
})
console.log(this.data.url)
},
  • 要注意这里的 options 拿到的数据是封装好的对象,要使用如上方式接收参数:(2023/08/09 晚)
1
const { url } = JSON.parse(decodeURIComponent(options.parameter));

点击 image 标签携带 URL

  • 要给每个 <image> 标签添加 click 函数,以便点击不同的图片跳转到不同的页面,可以通过以下方法实现:
1
2
3
4
5
6
7
8
<image
class="img"
src="{{item}}"
alt="刚刚"
mode="widthFix"
bindtap="handleImageTap"
data-url="{{item}}"
/>
  • 在当前页面的 JS 文件中定义 handleImageTap 方法来处理点击事件
  • 可以使用 wx.navigateTo 方法跳转到不同的页面并传递参数
1
2
3
4
5
6
7
8
9
Page({
handleImageTap(e) {
const imageUrl = e.currentTarget.dataset.url;
// 根据需要的逻辑处理点击事件,例如根据不同的 imageUrl 进行不同的跳转
wx.navigateTo({
url: `/pages/detail?imageUrl=\${imageUrl}`,
});
},
});
  • handleImageTap 方法中,使用 e.currentTarget.dataset.url 获取到点击的图片链接,然后根据需要的逻辑进行不同的跳转。
  • 请注意,上述代码是使用小程序原生的 wx.navigateTo 进行页面跳转,如果你使用的是框架或组件库,可能有不同的方法来实现页面跳转。(2023/08/09 晚)

实现点赞图片

1
2
3
4
5
6
7
8
<!-- 点赞量 -->
<view class="like">
<van-icon
name="{{isLiked ? 'like' : 'like-o'}}"
color="red"
bindtap="handleIconClick"
/>
</view>
1
2
3
4
5
6
7
8
9
10
11
// 点赞壁纸
handleIconClick() {
this.setData({
isLiked: !this.data.isLiked, // 点击后取反
});
if(this.data.isLiked){
Toast("已收藏~")
}else{
Toast('取消收藏~')
}
},

效果呈现

2024 年 4 月 22 日

半年前尝试开发过第一个微信小程序,花费了二十天时间认真做功能实现.。当时为了开发这个小程序,还借鉴了不少相关小程序的页面排版,多方参照才最终定型了这版页面。那么最终的效果如下

image-20230908221852633

image-20230908221858146

image-20230908221905369

踩坑记录

  • 首先,能够正确获取到小程序的 appID、appSecret:

image-20230806111056970

  • 开发者工具的 appID 必须设置正确,否则会不可避免地出现 invalid code 错误:(2023/08/06 早)
1
{"errcode":40029,"errmsg":"invalid code, rid: 64cf07f9-36a4c687-09c960f0"}

image-20230806110759499

公众号开通

2024 年 4 月 22 日

今下午,我突然想要开通一个微信公众号,借此来激励我每天读书写作。那是因为最近开始慢慢回忆起小时候的乡村生活,写的东西渐渐多了起来。但总归是没时间没精力,写东西断断续续的,我对这样的写作现状很不满意。

于是下午看了很多相关公众号文章,计划近段时间就开启公众号文章写作计划:

image-20240422153640539

我快速回顾了前段时间里了解到的有关微信公众平台的信息,包括小程序开发、智能机器人对话(动手做个 AI 机器人,帮我回消息!_牛客网 (nowcoder.com))这些,把这些快遗忘的知识捡起来。

微信公众平台访问入口:微信公众平台 (qq.com)

注册一个微信公众号,选择订阅号即可。

image-20240422153221125

但接下来的一步就让我止步于此了,我必须要提供一个未被微信公众平台注册过的邮箱

image-20240422154009938

详细要求可以看这里:公众号注册提示“邮箱已被占用” (qq.com)

除了现在正在使用的 qq 邮箱外,去年写小程序时还用手机号申请了网易邮箱。但现在由于这个网易邮箱绑定了一个小程序,要想顺利开通微信公众号,目前只有两种解决办法:修改小程序登录邮箱注销小程序

image-20240422144343165

虽然这个小程序是废稿,但毕竟是我开发的第一个微信小程序,未来可能还要继续优化呢。就这么注销了的话,本地的开发可能受到影响,怪可惜的。那最终的解决办法就是:今天晚上回去找我兄弟,让他注册一个网易邮箱,完事了我把小程序邮箱账号更换成他的,我开通的微信公众号就绑定我的网易邮箱吧。

那么现在就开始研究研究今年年初搞过的微信智能 AI 机器人了,不知道当时怎么想的,竟然没有记录下代码是如何起来的。相关的开发文档也没有多少记录,只有运行效果,只好是我再继续摸索摸索。

2024 年 4 月 23 日

很好,昨天下午总结了一番微信智能 AI 机器人的开发流程,晚上又让我的好兄弟注册了新的网易邮箱。

这下我就成功开通了微信公众号了。

总结


小程序开发经验分享:踩坑指南与技巧总结
https://test.atomgit.net/blog/2023/07/30/小程序开发经验分享:踩坑指南与技巧总结/
作者
Memory
发布于
2023年7月30日
更新于
2023年8月9日
许可协议