2024-04-20-【小程序】微信小程序研发小技巧

研发能力 验证

开发测试、生产环境区分

  • env.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const envConf = {
// 开发版-本地环境
develop: {
mode: "dev",
// apiBaseUrl: "http://9.134.35.45:14002/wpeMiniweRecycleSrv",
apiBaseUrl: "https://tserver.wpe.tencent.com",
},
// 体验版-测试环境
trial: {
mode: "test",
apiBaseUrl: "https://tserver.wpe.tencent.com",
},
// 正式版-正式环境
release: {
mode: "prod",
apiBaseUrl: "https://server.wpe.tencent.com",
},
};

module.exports = {
env: envConf[wx.getAccountInfoSync().miniProgram.envVersion],
};

网络层封装

  • request.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
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
63
64
65
import {
requestBefore,
requestError,
responseReject,
responseResolve,
} from "./request-interceptors";

import { env } from "../../env.config";

const { apiBaseUrl } = env;
const prefix = "xxx";
let requestTask = null;

const wxrequest = ({ url, data = {}, header = {}, method = "POST" }) => {
const app = getApp();
const { token, openId } = app?.globalData || {};
const requestHeader = { token, openId, sessionCheckPass: "true" };
url = url.replace(/(^\s*)|(\s*$)/g, ""); // 去掉url左右空格
const reqHeader = Object.assign({}, requestHeader, header);
const promise = new Promise((resolve, reject) => {
requestTask = wx.request({
url: `${apiBaseUrl}${prefix}${url}`,
data,
header: reqHeader,
method: method,
dataType: "json",
responseType: "text",
success: (res) => {
resolve(res);
},
fail: (err) => {
reject(err);
},
});
});
promise.requestTask = requestTask;
return promise;
};

const interceptorsRequest = (options) => {
try {
options = requestBefore(options);
} catch (error) {
return requestError(error);
}

return wxrequest(options)
.then((response) => responseResolve(response))
.catch((error) => {
// 200 error.error.message统一处理
if (error?.error?.message) {
wx.showModal({
content: error?.error?.message,
showCancel: false,
});
}
wx.hideLoading();
return responseReject(error);
});
};

module.exports = {
request: interceptorsRequest,
// request: wxrequest,
};
  • request-interceptors.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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import {
loopAndFormat,
toCamelCase,
toPascal,
cloneDeep,
typeOf,
} from "./utils";
const configToCamelCase = (config) => loopAndFormat(null, config, toCamelCase);

const errorResult = (data, returnData) => ({
error: {
...data,
},
...returnData,
});

/**
* 统一处理错误日志打印
* @param res
* @returns {boolean}
*/
const errLog = (res) => {
console.warn(
"接口非0啦!错误信息看这里!",
"\n responseURL:",
res.request?.responseURL,
"\n data: ",
res.data,
"\n all message: ",
res
);
return false;
};

/**
* response拦截器成功回调
* 旧:所有接口errcode返回0才正常返回resolve
* 非0都reject出去
* 新:有Response.Data才正常resolve
* 有Response.Error的reject出去
* @param res
* @returns {*}
*/
const responseResolve = (res) => {
if (typeOf(res) === "undefined") {
errLog(res);
return Promise.reject(new Error("response is undefined"));
}
const { data = {}, config = {} } = res;
if (!config.hideLoading) {
// 隐藏loading
wx.hideLoading();
}

const returnData = {
reqConfig: {
...configToCamelCase(config),
},
};

/**
* 新规范返回值
* - 把接口中的对象key的大驼峰处理为小驼峰
* - 把接口中与分页相关的数值转换为整形(暂无需实现)
* - 处理完,再返回给前端
*/
if (data.Response) {
if (data.Response.Data !== undefined) {
return Object.assign(
loopAndFormat(null, data.Response.Data, toCamelCase),
returnData
);
}

/**
* 返回体中无Data,打印日志并直接把整个Response丢出去
*/
errLog(res);

return Promise.reject(
Object.assign(loopAndFormat(null, data.Response, toCamelCase), returnData)
);
}

// 所有情况都不符合,直接返回整个response结构
// return { ...res };

// 返回res的data
return res.data;
};

/**
* response拦截器失败函数,以下情况会进入:
* 1. 【直接进入】网络错误(httpCode !== 2xx)
* @param error
* @returns {Promise<never>}
*/
const responseReject = (error) => {
if (error && error.response) {
const { config = {}, data = {} } = error.response;
if (!config.hideLoading) {
// 隐藏loading
wx.hideLoading();
}

console.error("=====> statusCode no 2xx,Reject: ", error.response);

// 定义一个返回对象,把config也给注入进去
const returnData = {
reqConfig: {
...configToCamelCase(config),
},
};

if (typeOf(data) === "object") {
/**
* 新版接口规范把接口返回的错误信息转换后抛出去
*/
return Promise.reject(
Object.assign(
loopAndFormat(null, data.Response, toCamelCase),
returnData
)
);
}

return Promise.reject(
errorResult(
{
data: error.response.data,
status: error.response.status,
statusText: error.response.statusText,
headers: error.response.headers,
},
returnData
)
);
}
return Promise.reject(error || new Error("Unknown Network Error"));
};

/**
* request前置拦截处理
* @param config
* @returns {{hideLoading}|*}
*/
const requestBefore = (config) => {
/**
* - 把接口请求中的data、params对象中的小驼峰转为大驼峰
* - 把接口中的几个与分页相关的值改为字符串
*/
if (config.data) {
const data = cloneDeep(config.data);
Object.assign(config, {
data: loopAndFormat(null, data, toPascal),
});
}

if (config.params) {
const params = cloneDeep(config.params);
Object.assign(config, {
params: loopAndFormat(null, params, toPascal),
});
}

if (!config.hideLoading) {
// loading
wx.showLoading({
title: "加载中...",
});
}
return config;
};

const requestError = (error) => Promise.reject(error);

module.exports = {
requestBefore,
requestError,
responseReject,
responseResolve,
};
  • utils.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
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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
const loopAndFormat = (key, obj, keyFormatter, valueFormatter) => {
if (typeof obj === "object") {
if (Array.isArray(obj)) {
return obj.map((v) =>
loopAndFormat(null, v, keyFormatter, valueFormatter)
);
}
for (const key in obj) {
const val = obj[key];
const newKey =
typeof keyFormatter === "function" ? keyFormatter(key, val) : key;
obj[newKey] = loopAndFormat(key, val, keyFormatter, valueFormatter);
if (newKey !== key) {
delete obj[key];
}
}
return obj;
}
return typeof valueFormatter === "function" ? valueFormatter(key, obj) : obj;
};

const toPascal = (str) => {
if (typeof str === "string" && str) {
return str.replace(str[0], str[0].toUpperCase());
}
return str;
};

const toCamelCase = (str) => {
if (typeof str === "string" && str) {
return str.replace(str[0], str[0].toLowerCase());
}
return str;
};

const typeOf = (obj) => {
const { toString } = Object.prototype;
const map = {
"[object Boolean]": "boolean",
"[object Number]": "number",
"[object String]": "string",
"[object Function]": "function",
"[object Array]": "array",
"[object Date]": "date",
"[object RegExp]": "regExp",
"[object Undefined]": "undefined",
"[object Null]": "null",
"[object Event]": "event",
"[object Object]": "object",
"[object MouseEvent]": "mouseEvent",
};
return map[toString.call(obj)];
};

const cloneDeep = (obj, hash = new WeakMap()) => {
// 如果obj为null或者不是对象和数组,那么直接返回
if (obj == null || typeof obj !== "object") return obj;

// 如果obj是循环引用的情况,那么直接返回
if (hash.has(obj)) return hash.get(obj);

const isArray = Array.isArray(obj);
// 创建新的对象或者数组
const t = isArray ? [] : {};

// 在hash表中存入当前对象与克隆对象
hash.set(obj, t);

if (isArray) {
obj.forEach((item, index) => {
// 如果元素是引用数据类型,递归复制
t[index] = cloneDeep(item, hash);
});
} else {
Object.keys(obj).forEach((key) => {
t[key] = cloneDeep(obj[key], hash);
});
}

return t;
};

module.exports = {
loopAndFormat,
toPascal,
toCamelCase,
typeOf,
cloneDeep,
};

pdf 预览

    1. base64 预览
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
const previewPdf = (base64Pdf, fileName = "temp") => {
// 将 Base64 数据转换为 ArrayBuffer
const base64Data = base64Pdf;
const arrayBuffer = wx.base64ToArrayBuffer(base64Data);

// 获取文件系统管理器
const fileSystemManager = wx.getFileSystemManager();

// 创建临时文件并写入 ArrayBuffer 数据
const tempFilePath = `${wx.env.USER_DATA_PATH}/${fileName}.pdf`;

fileSystemManager.writeFile({
filePath: tempFilePath,
data: arrayBuffer,
encoding: "binary",
success: () => {
wx.openDocument({
filePath: tempFilePath,
fileType: "pdf",
success: () => {
console.log("预览成功");
},
fail: (error) => {
console.error("预览失败", error);
},
});
},
fail: (error) => {
console.error("写入失败", error);
},
});
};

module.exports = {
previewPdf,
};
    1. 下载 url 预览
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
const previewPdf = (url) => {
util.showLoading();
// 单次下载允许的最大文件为 200MB
wx.downloadFile({
url: apiBaseUrl + url,
success: function (res) {
// console.log(res, 'wx.downloadFile success res');
if (res.statusCode != 200) {
util.hideLoadingWithErrorTips();
return false;
}
// 返回的文件临时地址,用于后面打开本地预览所用
const Path = res.tempFilePath;
wx.openDocument({
filePath: Path,
showMenu: true,
success: function () {
// console.log('打开成功');
util.hideLoading();
},
});
},
fail: function (err) {
console.log(err, "wx.downloadFile fail err");
util.hideLoadingWithErrorTips();
},
});
};

图片优化

    1. 图片压缩
    1. 网络图片 webp

手机号授权(免费体验 1000 次)

实名授权,(身份证、姓名匹配)

身份证识别()


2024-04-20-【小程序】微信小程序研发小技巧
https://zhangyingxuan.github.io/2024-04-20-【小程序】微信小程序研发小技巧/
作者
blowsysun
许可协议