前言
经历了互联网寒冬,一波面试下来。发现了学习的东西还有很多。后面也拿到了比较满意的offer。新入职的第一个项目是关于小程序的,之前我也接触小程序的,只不过是个人玩玩的项目。所以说一路踩坑过来,下面我把这段时间遇到的一些问题总结下,希望对正在小程序开发的你有所帮助,本人经验有限,以下讲的不好的欢迎指正。
1、小程序工程化开发
目前小程序开发框架有很多,mpvue、wepy。我在项目中使用的是原生小程序。下面是几个的对比。
上图可以看出原生小程序劣势很大,至于为什么我不采用其他框架呢,个人认为短时间内用其他的框架怕踩到莫名的坑。虽然原生也有很多坑。下面是我用gulp进行了细微的改造。
开发
- 所有小程序源码放在src目录里面,目录结构与原生的小程序基本相同,编译后的文件会自动copy到dist目录,与原生小程序不同点如下:
- 全部使用less开发, pages目录下面的页面也使用less,程序会自动编译为wxss文件
- 程序全局配置文件在config目录的config.js里面,程序会根据开发环境来匹配不同的配置项
- 页面的Page构造对象需import utils目录下面的wxPage
"devDependencies": {
"cross-env": "^5.2.0",
"gulp": "^4.0.0",
"gulp-clean": "^0.4.0", //删除文件
"gulp-less": "^4.0.1", //编译less
"gulp-plumber": "^1.2.1",
"gulp-rename": "^1.4.0", //重命名
"gulp-replace": "^1.0.0" //替换原文件
}
复制代码
gulpfile.js
const gulp = require('gulp')
const less = require('gulp-less')
const rename = require('gulp-rename')
const clean = require('gulp-clean')
const replace = require('gulp-replace')
const plumber = require('gulp-plumber')
const fs = require('fs')
const env = process.env.NODE_ENV
const srcPath = 'src'
const outputPath = 'dist'
const lessFile = [`${srcPath}/**/*.less`, `!${srcPath}/style/*.less`]
const allLessFile = [`${srcPath}/**/*.less`]
const copyFile = [`${srcPath}/**/*.*`, `!${srcPath}/**/*.less`, `!${srcPath}/style/*.less`, `!${srcPath}/config/**/*.js`]
const replaceFile = [`${srcPath}/config/**/*.js`]
const watchOption = { events: ['add', 'change', 'unlink'], delay: 200 }
gulp.task('clean-image', function() {
return gulp.src([`${outputPath}/images`])
.pipe(plumber())
.pipe(clean())
})
// 清空无关紧要的wxss
gulp.task('clean-less', function() {
return gulp.src([`${outputPath}/template/**/*.wxss`])
.pipe(plumber())
.pipe(clean())
})
// 清空dist目录下面的文件
gulp.task('clean', function() {
return gulp.src([`${outputPath}/**/*.*`, `!${outputPath}/readme.md`], { read: false })
.pipe(plumber())
.pipe(clean())
})
// copy项目文件到dist目录
gulp.task('copy', function() {
return gulp.src(copyFile)
.pipe(plumber())
.pipe(gulp.dest(outputPath))
})
// 编译less
gulp.task('less', function() {
return gulp.src(lessFile)
.pipe(plumber())
.pipe(less())
.pipe(rename(function (path) {
path.extname = '.wxss'
}))
.pipe(gulp.dest(outputPath))
})
// 替换字符串,区分开发和测试环境 dev:开发环境,build:集成环境
gulp.task('replace', function() {
return gulp.src(replaceFile)
.pipe(plumber())
.pipe(replace(/{NODE_ENV}/gi, env))
.pipe(gulp.dest(`${outputPath}/config`))
})
function fsExistsSync(path) {
try {
fs.accessSync(path, fs.F_OK)
} catch(e) {
return false
}
return true
}
gulp.task('watch', function() {
gulp.watch(allLessFile, watchOption, gulp.series('less'))
gulp.watch(copyFile, watchOption, gulp.series('copy'))
gulp.watch(replaceFile, watchOption, gulp.series('replace'))
// 监听文件的删除
var watcher = gulp.watch([`${srcPath}/**`, `!${srcPath}/style/*.less`])
watcher.on('all', function(event, p) {
console.log(event, p)
try {
if (event === 'unlink') {
const filePath = p.split('.')[0]
const extname = p.split('.')[1]
const fileName = extname === 'less' ? filePath + '.wxss' : p
const file = fileName.replace(/\\/g, '/').replace(srcPath, outputPath)
fsExistsSync(file) && gulp.src(file, { read: false }).pipe(clean())
}
if (event === 'unlinkDir') {
const file = outputPath + p.split(srcPath)[1].replace(/\\/g, '/')
fsExistsSync(file) && gulp.src(file, { read: false }).pipe(clean())
}
} catch (error) {
console.log('删除文件出错!')
}
})
})
// gulp.task('dev', gulp.series('copy', 'replace', 'less', 'watch'))
// gulp.task('build', gulp.series('clean', 'copy', 'replace', 'less'))
gulp.task('dev', gulp.series('clean', gulp.parallel('copy', 'replace', 'less', 'watch')))
gulp.task('build', gulp.series('clean', gulp.parallel('copy', 'replace', 'less'), 'clean-image', 'clean-less'))
复制代码
2、登陆与授权问题
登陆首先看下官网的泳道图其实讲的很清楚
- 调用 wx.login() 获取 临时登录凭证code ,并回传到后端提供的接口。
- 调用 auth.code2Session 接口,换取 用户唯一标识 OpenID 和 会话密钥 session_key。
授权
最新的api,获取一些用户授权信息是要用户主动去触发(有些产品不清楚这一点交互方面)。通过button组件配置不一样的open-type,获取手机号信息需要企业认证。
3、自定义遮罩层在真机下遮不住map、video、canvas、camera、live-player、live-pusher
cover-view了解下
还有种场景就是textarea,如果上面的提示层不是用cover-view。那么真机下会出现问题
<textarea class="beizhu" value="{{txtContent}}" bindinput='txtInput' wx:if="{{!isSure}}" maxlength="20"/>
<!-- 这是用于模拟 textarea的替代元素 -->
<view class='beizhu' wx:else>
<rich-text nodes="{{txtRealContent}}"></rich-text>
</view>
js
txtInput(e) {
this.setData({ txtContent: e.detail.value })
},
const txtRealContent = this.data.txtContent.replace(/\n/g, '<br/>')
this.setData({ txtRealContent })
复制代码
4、微信上传图片到腾讯云并且返回加速地址
预警深坑腾讯云,腾讯云api看得真是混乱 ... 下面是采用SDK形式调用下载地址cos-wx-sdk-v5.js
let COS = require('../../utils/cos-wx-sdk-v5.js')
// 调用后端提供的接口获取对应的桶 域...
// 注意此接口要在new COS之前调用
getAuthorization(options, callback) {
let _this = this
callback({
TmpSecretId: _this.data.cosParam.tmpSecretId,
TmpSecretKey: _this.data.cosParam.tmpSecretKey,
XCosSecurityToken: _this.data.cosParam.sessionToken,
ExpiredTime: _this.data.cosParam.expiredTime, // SDK 在 ExpiredTime 时间前,不会再次调用 getAuthorization
});
},
requestCallback(err, data) {
let _this = this
console.log(err || data);
if (err && err.error) {
wx.showModal({
title: '返回错误',
content: '请求失败:' + (err.error.Message || err.error) + ';状态码:' + err.statusCode,
showCancel: false
});
} else if (err) {
wx.showModal({
title: '请求出错',
content: '请求出错:' + err + ';状态码:' + err.statusCode,
showCancel: false
});
} else {
let region = this.data.cosParam.region
let urls = `https://${data.Location}`
let url = urls.replace(region,'file') // 切换加速CDN地址
// wx.showToast({
// title: '上传成功',
// icon: 'success',
// duration: 3000
// });
}
},
ajax.get('/common/qcloud/getQcloudTempTokenInfo', {}, false).then(res => {
let credentials = res.data;
_this.setData({
cosParam: credentials
})
});
wx.chooseImage({
count: 1, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType, // 可以指定来源是相册还是相机,默认二者都有
success: (res) => {
let size = res.tempFiles[0].size;
console.log(res)
const filePath = res.tempFiles[0].path;
const filename = filePath.substr(filePath.lastIndexOf('/') + 1);
console.log(filename)
if (size < 10000000) {
_this.setData({ showVisible: false })
const cos = new COS({
// path style 指正式请求时,Bucket 是在 path 里,这样用途相同园区多个 bucket 只需要配置一个园区域名
getAuthorization: _this.getAuthorization.bind(_this),
});
cos.postObject({
Bucket: _this.data.cosParam.bucket,
Region: _this.data.cosParam.region.split('.')[1],
Key: _this.data.cosParam.fileFolder+filename,
FilePath: filePath
// Body: file[0]
// 在小程序里,putObject 接口只允许传字符串的内容,不支持 TaskReady 和 onProgress,上传请使用 cos.postObject 接口
}, _this.requestCallback.bind(_this));
}
})
复制代码
5、页面传值
a页面 -> b页面
// a.js
wx.navigateTo({ url: `../industry/industry?formData=${queryBean}` });
// b.js 取值
onLoad(option){
console.log(option.formData)
}
复制代码
a页面 <- b页面
// b.js
const pages = getCurrentPages() // 获取微信页面栈
const prevPage = pages[pages.length - 2]; // a页面
const index = prevPage.data.selectIndex;
const placeholder = `formList[${index}].placeholder`
prevPage.setData({
[placeholder]: '已上传', // 设置a页面数据
selectIndex: 1
})
wx.navigateBack({
delta: 1 //返回的页面数,如果 delta 大于现有页面数,则返回到首页,
});
复制代码
6、动态改变底部tabBar
考虑到项目中后续需要多个角色,每个角色展示的不一样的底部。
方法一:首先隐藏原始的tabBar,然后使用标签自定义布局可以实现。但是缺点是每次切换都需要重新加载底部的页面。
方法二:官方给了一个,这里具体就不再写了,给个传送门
后续持续更新...