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

本文最后更新于:23 天前

前言

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

文章内容

2025 年 7 月 3 日

点击跳转至文章页面。

1
2
3
4
5
6
7
8
9
<view
src="{{item.title}}"
class="post-item"
bindtap="postClick"
data-title="{{item.title}}"
data-path="{{item.path}}"
>
{{item.title}}
</view>
1
2
3
4
5
6
7
8
postClick: function (option) {
var self = this;
var title = option.target.dataset.title
var path = option.target.dataset.path
wx.navigateTo({
url: '../artical/artical?path=' + path,
})
},

在 article.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
onLoad: function (options) {
var self = this;
var markdownPath = options.path || '../../pages/artical/test2.md'; // 默认路径(示例)

// 1. 读取本地 .md 文件
wx.getFileSystemManager().readFile({
filePath: markdownPath,
encoding: 'utf-8', // 重要:确保以文本形式读取
success(res) {
console.log('Markdown内容:', res.data);

// 2. 使用 towxml 解析 Markdown
let result = app.towxml(res.data, 'markdown', { // 注意改为 markdown 类型
base: '/pages/article/', // 调整为基础路径
theme: 'light',
});

// 3. 更新页面数据
self.setData({
article: result,
showLoading: false,
articaleTitle: options.title || '默认标题', // 从参数或默认值获取标题
});
},
fail(err) {
console.error('读取文件失败:', err);
self.setData({ showLoading: false });
wx.showToast({ title: '加载失败', icon: 'none' });
}
});
},

文章内容展示。

1
2
3
<view class="container" scroll-x="true">
<towxml nodes="{{article}}" />
</view>

这函数老是加载不到本地文件。

1
2
3
console.log('options:', options); // 检查是否收到path参数
var markdownPath = options.path || '../../pages/article/test.md';
console.log('最终路径:', markdownPath); // 确认路径拼接结果

小程序 - wx.getFileSystemManager().readFile 读取文件没有权限,需要怎么处理? - SegmentFault 思否

微信小程序中的文件系统其实类似浏览器中Origin Private File System的概念,它是供小程序使用的一块独立的文件区域,与代码文件和用户私有文件是独立的关系。你这里大概是想引用代码中的静态资源,是不能用这个 API 的。

本地文件指的是小程序被用户添加到手机后,会有一块独立的文件存储区域,以用户维度隔离。即同一台手机,每个微信用户不能访问到其他登录用户的文件,同一个用户不同 appId 之间的文件也不能互相访问。

艹。

1
2
3
4
5
6
7
8
const mdContent = require("./test2.md.js");
console.log(mdContent);
// 2. 使用 towxml 解析 Markdown
let result = app.towxml(mdContent, "markdown", {
// 注意改为 markdown 类型
base: "/pages/article/", // 调整为基础路径
theme: "light",
});
1
2
3
4
5
6
7
8
9
10
11
Error: Input data should be a String
at i.parse (VM8153 markdown.js:370)
at i.render (VM8153 markdown.js:374)
at module.exports (VM8152 index.js:56)
at Rt.module.exports (VM8113 index.js:11)
at Rt.<anonymous> (WASubContext.js?t=wechat&s=1751542853487&v=3.8.9:1)
at li.onLoad (artical.js? [sm]:38)
at li.<anonymous> (WASubContext.js?t=wechat&s=1751542853487&v=3.8.9:1)
at li.c.__callPageLifeTime__ (WASubContext.js?t=wechat&s=1751542853487&v=3.8.9:1)
at WASubContext.js?t=wechat&s=1751542853487&v=3.8.9:1
at WASubContext.js?t=wechat&s=1751542853487&v=3.8.9:1(env: Windows,mp,1.06.2407110; lib: 3.8.9)

towxml 是一个专为微信小程序设计的 富文本/Markdown 解析库,它能将 HTML 或 Markdown 内容转换为小程序支持的 WXML 结构。

1
2
3
4
let result = app.towxml(mdContent, "markdown", {
base: "/pages/article/",
theme: "light",
});

在 app.js 文件中:

1
2
3
4
5
6
//app.js
App({
towxml: require('/towxml/index'),

.............................
)}

最终代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
onLoad: function (options) {
try {
// 读取本地文件
const mdModule = require('../../pages/artical/test2.md.js');
// 获取正文内容
const mdContent = typeof mdModule === 'string' ? mdModule : mdModule.default || '';
if (!mdContent) {
throw new Error('Markdown内容为空');
}
// 使用 towxml 解析 Markdown
let result = app.towxml(mdContent, 'markdown', {
base: '/pages/article/',
theme: 'light'
});
// 保存文章内容
this.setData({ article: result });
// 更新加载状态
this.setData({ showLoading: false });
} catch (err) {
console.error('解析失败:', err);
wx.showToast({ title: '内容加载失败', icon: 'none' });
}
},

就这么直接把.md 转换成.js 文件不太妥当,只要文件里有代码块,整个文件就会报错,应为有符号冲突隔断的缘故。

博客列表

2025 年 7 月 2 日

构造模拟的博客文章列表数据。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[
{
"title": "Hexo 博客搭建指南",
"url": "2024/01/01/hexo-blog-guide/",
"date": "2024-01-01",
"excerpt": "从零开始搭建 Hexo 静态博客的完整教程,包含主题配置和部署到 GitHub Pages。"
},
{
"title": "JavaScript 设计模式实践",
"url": "2024/01/05/javascript-design-patterns/",
"date": "2024-01-05",
"excerpt": "单例模式、观察者模式等常见 JavaScript 设计模式的代码示例和应用场景。"
},
{
"title": "CSS Grid 布局完全指南",
"url": "2024/01/10/css-grid-layout/",
"date": "2024-01-10",
"excerpt": "通过实战案例学习 CSS Grid 布局的核心概念和高级技巧。"
},
{
"title": "Node.js 性能优化策略",
"url": "2024/01/15/nodejs-performance/",
"date": "2024-01-15",
"excerpt": "如何诊断和优化 Node.js 应用的 CPU、内存和 I/O 性能问题。"
},
{
"title": "微信小程序开发踩坑记录",
"url": "2024/01/20/wechat-miniprogram-tips/",
"date": "2024-01-20",
"excerpt": "分享开发微信小程序时遇到的 10 个常见问题及解决方案。"
},
{
"title": "Python 数据可视化实战",
"url": "2024/01/25/python-data-visualization/",
"date": "2024-01-25",
"excerpt": "使用 Matplotlib 和 Seaborn 制作专业级数据图表的步骤详解。"
},
{
"title": "React Hooks 最佳实践",
"url": "2024/02/01/react-hooks-best-practices/",
"date": "2024-02-01",
"excerpt": "如何避免滥用 useEffect 并合理组织自定义 Hook 的逻辑。"
},
{
"title": "Git 高级用法备忘单",
"url": "2024/02/05/git-advanced-tips/",
"date": "2024-02-05",
"excerpt": "rebase、cherry-pick 和 submodule 等 Git 高级功能的速查指南。"
},
{
"title": "TypeScript 4.0 新特性解析",
"url": "2024/02/10/typescript-4-features/",
"date": "2024-02-10",
"excerpt": "可变元组类型、标记元组元素等 TypeScript 4.0 重要更新详解。"
},
{
"title": "Docker 容器化部署实践",
"url": "2024/02/15/docker-deployment/",
"date": "2024-02-15",
"excerpt": "将 Node.js 应用容器化并通过 Docker Compose 管理多服务的完整流程。"
}
]

数据字段说明

字段 示例值 作用
title “Hexo 博客搭建指南” 文章标题
url “2024/01/01/hexo-blog-guide/“ 文章访问路径(需拼接域名)
date “2024-01-01” 发布时间
excerpt “从零开始搭建…” 文章摘要(可作简介)

小程序请求数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// pages/index/index.js
Page({
data: {
articles: [],
},

onLoad() {
wx.request({
url: "https://your-blog.com/articles.json",
success: (res) => {
this.setData({ articles: res.data });
},
fail: (err) => {
console.error("加载失败", err);
},
});
},
});

使用 require` 加载。

1
2
3
4
5
6
7
8
onLoad: function() {
// 直接 require 本地 JSON 文件
const articles = require('../../assets/data/articles.json');
console.log("------------------------------");
console.log(articles);
console.log(articles);
// this.setData({ articles });
},
1
2
3
4
5
6
7
8
9
10
11
12
[基础库] 正在使用灰度中的基础库 3.8.10 进行调试。如有问题,请前往工具栏-详情-本地设置更改基础库版本。
Error: module 'assets/data/articles.json.js' is not defined, require args is '../../assets/data/articles.json'
at q (WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1)
at n (WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1)
at li.onLoad (index.js? [sm]:15)
at li.<anonymous> (WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1)
at li.c.__callPageLifeTime__ (WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1)
at WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1
at WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1
at WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1
at WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1
at WASubContext.js?t=wechat&s=1751451288428&v=3.8.10:1(env: Windows,mp,1.06.2407110; lib: 3.8.10)

JSON Online Validator and Formatter - JSON Lint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
onLoad:function() {
wx.request({
url: '../../assets/data/articles.json', // 注意:需配置合法域名或开启不校验域名
success: (res) => {
console.log('原始数据:', res.data);
// 确保数据是数组
const articles = Array.isArray(res.data) ? res.data : [];
this.setData({ articles });
},
fail: (err) => {
console.error('加载失败', err);
}
});
},
1
index.js? [sm]:23 加载失败 {errno: 600009, errMsg: "request:fail invalid url "../../assets/data/articles.json""}errMsg: "request:fail invalid url "../../assets/data/articles.json""errno: 600009__proto__: Object(env: Windows,mp,1.06.2407110; lib: 3.8.10)

这个错误是因为 wx.request 不能直接使用本地相对路径加载文件,它只能请求网络 URL。有三种解决方案:

改用 require 加载(推荐)

1
2
3
4
5
6
7
8
9
10
onLoad: function() {
try {
// 注意路径层级(可能需要调整 ../ 的数量)
const articles = require('../../assets/data/articles.json');
console.log('加载成功:', articles);
this.setData({ articles });
} catch (err) {
console.error('加载失败:', err);
}
}

使用 wx.getFileSystemManager 读取本地文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
onLoad: function() {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: 'assets/data/articles.json', // 从项目根目录开始的路径
encoding: 'utf8',
success: (res) => {
try {
const articles = JSON.parse(res.data);
this.setData({ articles });
} catch (e) {
console.error('JSON解析失败:', e);
}
},
fail: (err) => {
console.error('文件读取失败:', err);
}
});
}

filePath 必须是从项目根目录开始的相对路径,例如:

  • assets/data/articles.json
  • ../../assets/data/articles.json

将 JSON 文件托管到网络

1
2
3
4
5
6
wx.request({
url: "https://你的域名.com/articles.json",
success: (res) => {
this.setData({ articles: res.data });
},
});

需在小程序后台配置合法域名(设置 → 开发设置 → 服务器域名)

使用第二种方案,又出问题了:

1
2
[读取文件/文件夹警告] 无法读取 ../../assets/data/articles.json 文件或文件夹不在白名单中, 上传时会被忽略, 在真机上可能无法读取
index.js? [sm]:27 文件读取失败: {errMsg: "readFile:fail permission denied, open ../../assets/data/articles.json"}errMsg: "readFile:fail permission denied, open ../../assets/data/articles.json"__proto__: Object(env: Windows,mp,1.06.2407110; lib: 3.8.10)

他妈的,filePath 必须是从项目根目录开始的相对路径,不能使用 ../ 向上跳转,并且小程序默认只能读取特定目录下的文件(如 assets 需手动配置

1
wx.getFileSystemManager().readFileSync("assets/data/articles.json", "utf8");

成功了,终极解决方法,重命名文件为 articles.js,修改文件内容为:

1
2
3
4
5
6
7
8
9
10
// assets/data/articles.js
module.exports = [
{
title: "Hexo 博客搭建指南",
url: "2024/01/01/hexo-blog-guide/",
date: "2024-01-01",
excerpt: "从零开始搭建Hexo...",
},
// 其他数据...
];

改用 require 加载:

1
2
3
4
5
6
7
8
9
10
onLoad: function() {
try {
// 注意路径层级(可能需要调整 ../ 的数量)
const articles = require('../../assets/data/articles.js');
console.log('加载成功:', articles);
this.setData({ articles });
} catch (err) {
console.error('加载失败:', err);
}
},

image-20250702192104163

image-20250702192149421

刚才发现,博客上传至七牛云对象存储的所有图片,也就是博客站点的所有图片,都备份保存在了本机 C 盘里。

1
C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images

足足两千四百六十九张图片。

按需注入和用时注入 | 微信开放文档 (qq.com)

一切准备就绪,上传第一个体验版本,仅供测试,代码质量校验提醒需要启动按需注入。

1
"lazyCodeLoading": "requiredComponents"

搞定了,在 app.json 文件中添加如上代码即可实现按需注入。

上传完成。

旧梦轻拾

初始化

2025 年 5 月 16 日

个人主体:1 个邮箱只能绑定 1 个公众号或 1 个小程序。

image-20250516171656234

image-20250516171616691

Jiomeng

从零开始三天学会微信小程序开发(一)看到不少入门的小程序开发者不断的问重复性的问题,我们从实战角度开发了这个课程,希望能 - 掘金 (juejin.cn)

选择模板,绑定小程序唯一 appID 创建小程序,编译并预览。

在创建小程序的对话框中,输入项目名称(如 StudentComment),选择项目存放的目录,AppID 一栏填写你在微信小程序官方后台中获取到的 AppID,后端服务选择“不使用云服务”,模板选择“JS-基础模板”,然后点击“确认”按钮,创建小程序。

可选择二维码预览,手机微信扫码即可体验;也可选择自动预览,选择启动手机端自动预览/启动 PC 端自动预览,相当方便。

待编译成功,会自动弹出小程序的最终运行效果,如下图所示。

image-20250516173845596

当我们开发好小程序之后,还需要把小程序上传到微信的服务器上。点击开发工具右上角的“上传”按钮,在弹出界面中输入更新类型、版本号和项目备注,然后点击上传,如下图所示。

image-20250516173932305

如果代码不存在任何问题,很快就会弹出“代码上传成功”的提示框,表示上传成功。

上传成功后,我们回到微信小程序官方后台。点击“版本管理” 链接,可以看到上传的小程序版本。如下图所示。

image-20250516174139555

2022 年 6 月 8 日后,未设置主营类目的帐号将无法提交代码审核。

image-20250516174311152

点击“提交审核”按钮,按要求填写资料。完成之后,会在审核版本中看到刚刚申请的审核版本,如下图所示。

image-20250516174500309

等到负责审核的工作人员通过之后,我们再执行最终的“发布”动作,我们开发的小程序才会出现在大众面前。

新页面

2025 年 5 月 16 日

小程序标题,修改 app.json 文件。

1
2
3
4
5
6
7
8
9
10
11
12
{
"pages": ["pages/detail/detail", "pages/index/index"],
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "社会脏话大全",
"navigationBarBackgroundColor": "#ffffff"
},
"style": "v2",
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}

首页。

1
2
<view>   减轻您的工作,一键生成各种评语 </view>
<view>   <navigator url="/pages/detail/detail">小学期末</navigator> </view>

奶奶的,多打了一个斜杠,老是报错,问 AI 也说不清道不明的。

1
  <navigator url="pages/index/index"> 小学期末 </navigator>

什么垃圾编译器。。。

2025 年 5 月 17 日

见鬼了,看来是直接粘贴代码携带隐藏符号了,亲手敲代码不会有报错。

1
2
3
4
5
<!--index.wxml-->
<view class="head">   减轻您的工作,一键生成各种评语 </view>
<view class="links">
<navigator class="btn" url="/pages/detail/detail"> 脏话集锦 </navigator>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.head {
display: flex;
font-size: 36rpx;
justify-content: center;
margin-top: 40rpx;
}

.links {
display: flex;
justify-content: center;
margin-top: 30rpx;
}

.btn {
border: 2px solid #5db59d;
padding: 20rpx;
border-radius: 20rpx;
font-size: 28rpx;
}

绑定数据。

1
2
3
4
5
6
7
/**
* 页面的初始数据
*/
data: {
info: "减轻您的工作,一键生成各种评语",
links: ['轻度调侃', '激烈宣泄', '讽刺反讽']
},
1
2
3
4
5
6
7
8
9
10
<view class="links">
<navigator
class="btn"
url="/pages/detail/detail"
wx:for="{{links}}"
wx:for-item="link"
>
{{link}}
</navigator>
</view>

从零开始三天学会微信小程序开发(二)看到不少入门的小程序开发者不断的问重复性的问题,我们从实战角度开发了这个课程,希望能 - 掘金 (juejin.cn)

image-20250517152605068

dist/Bmob-2.5.30.min.js · 元素(深圳)软件有限公司/hydrogen-js-sdk - Gitee.com

image-20250517155606506

1
2
3
const Bmob = require("/utils/Bmob-2.5.30.min.js");
Bmob.initialize("你的Secret Key", "你的API 安全码");
wx.Bmob = Bmob;

微信公众平台 (qq.com)

应用密钥 - Bmob 后端云 (bmobapp.com)

image-20250517161022104

审核

2025 年 5 月 19 日

上周提交的小程序竟审核未通过。

image-20250519183959125

测试代码不能提交。

应该写个开发日志。

库表设计:图片信息(图片名,图片链接,图片尺寸,图片分辨率,图片大小,标签)。

先写后端,核心接口:图片列表展示,图片详情信息,上传图片,下载图片。

再写前端,核心页面:图片列表,图片详情信息。

业务域名

2025 年 6 月 27 日

操作指南 / 业务域名配置 (qq.com)

微信小程序快速入门:从开发者工具到业务域名配置 (baidu.com)

操作指南 / 业务域名配置 (qq.com)

1
2
3
4
5
6
7
<!--index.wxml-->
<scroll-view class="scrollarea" scroll-y type="list">
<view class="container">
<!-- page.wxml -->
<web-view src="https://deng-2022.atomgit.net/blog/"></web-view>
</view>
</scroll-view>

微信开放文档 / 开发 (qq.com)

个人类型的小程序支持业务域名吗? (seowhy.com)

个人小程序支持业务域名吗? (apppark.cn)

2025 年 6 月 28 日

基础能力 / 网络 / 业务域名 (qq.com)

  1. 在管理后台配置业务域名

开发者登录小程序后台 mp.weixin.qq.com,选择开发管理->开发设置->业务域名,点击新增,按照要求配置业务域名。目前小程序内嵌网页能力暂不开放给个人类型账号和小游戏账号。

img

  1. 调用 web-view 组件实现小程序内嵌网页

在小程序管理后台成功配置业务域名后,才可使用 web-view 组件。小程序内调用 web-view 组件实现内嵌的网页,目前仅支持部分 jsapi 能力,关于 web-view 接口具体使用说明和限制,请 点击查看

界面设计

2025 年 7 月 1 日

小程序界面设计模板分享,前端开发进来看身处于网络世界日新月异的变革中,智能手机已然成为我们日常生活、学习和工作的必不可少 - 掘金 (juejin.cn)

微信开放社区 (qq.com)-订阅号的 appid 在哪里看?

存储桶列表 - 对象存储 - 控制台 (tencent.com)

微信现在对个人订阅号的菜单跳转实行 「白名单+内容归属校验」

  1. 仅允许跳转至:
    • 公众号后台的图文消息页面(mp.weixin.qq.com/...
    • 已关联的小程序(需开发者权限)
    • 腾讯系域名(如 QQ 音乐、腾讯视频等)
  2. 完全禁止:
    • 任何外部链接(即使备案、即使使用腾讯云 COS 中转)
    • GitHub Pages、自建博客、第三方平台链接

云服务器( Cloud Virtual Machine,CVM )为您提供安全可靠的弹性计算服务。

阿里云 ECS 是阿里云提供的弹性计算服务(Elastic Compute Service),一种可弹性伸缩的虚拟服务器。

微信公众平台跳转至外部链接,限制太死板了,简直不像是 5G 时代应该有的产物,毫无自由度。

PicMemories

2025 年 1 月 11 日

后端刚启动就报错,这么刺激:

1
2
3
4
5
6
Connected to the target VM, address: '127.0.0.1:10270', transport: 'socket'
16:26:56.138 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
16:26:56.142 [main] DEBUG reactor.core.publisher.Hooks - Enabling stacktrace debugging via onOperatorDebug
16:26:57.051 [main] ERROR org.springframework.boot.SpringApplication - Application run failed
org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 1
at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:218)

竟然又是配置文件带了中文,这么古老。

1
2
3
4
5
6
7
8
9
10
11
12
knife4j:
enable: true
openapi:
title: PicMemories 接口文档
description: PicMemories 壁纸分享小程序
concat: 3348407547@qq.com
url: https://deng-2022.gitee.io/blog/
version: 1.0
license: Apache 2.0
group:
test1:
group-name: 壁纸分享

启动成功。

image-20250111164802113

启动第一步,先把图床资源翻新下,前年用七牛云的临时域名,图片链接早都过期了。

这个小程序初步计划仅实现最核心的功能,即服务端上传图片,小程序端下载图片。

想起来当时是因为暑假那会儿有同学找我组队,说是需要一个会做小程序的,我从零开始边学边做,两周左右的时间就完善了基本页面。

后来这个同学就没有消息了,小程序也暂且搁置。

当时把页面分为四个板块:精选,分类,频道,我的,其实是想实现一个集交友,壁纸下载,聊天等的小程序。

不过没什么使用价值,连壁纸究竟该提供哪些分类和数量都没有想法,大部分编码时间里都在纠结图片间距和预览效果,其核心功能开发倒没多少进展。

直到昨天。

昨天下午我想起来那会儿确实也想要做一个个人网站,尝试学习不同领域的知识,小程序,Web,桌面端,保持学习的心态去实现心愿。

后来确实做到了,我成功运营起来自己的个人博客网站,相当成功。

昨天又想起来自己之前一直想搭建文档站点,后来还尝试开发过壁纸分享小程序,这两项工作也是时候该完成了。

文档站点,可以选择提炼出个人博客网站中的编程学习经验栏目;而壁纸分享小程序,则专门用来收录个人博客站点的精选壁纸图片。

初步打造属于自己的全生态软件集合。

时间还有很多,先执行起来吧。

小程序开发

2023 年 7 月 30 日

开发准备

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日
更新于
2025年7月3日
许可协议