前段时间开发了一个小程序,也是我第一个小程序,开发过程总会遇到一些奇奇怪怪的问题,但最后的最后都解决了,今天我来总结、复盘一下,毕竟好记性不如烂笔头,希望对大家也有所帮助。
小问题,大影响
1.微信开发者工具有时候热更新不起作用甚至白屏,重新编译也不行;
有时候一点样式出错,预览整个都白屏,调试器里也不说哪里的问题,直接就给你弃疗不显示,重新编译也无法解决问题;
合并分支,调试器里乱报文体不存在,其实内容存在。
强行退出后再次打开项目
复制代码
2.require和import引入文件及模板有时不生效
import 的路径只能是相对路径,比如你希望引用 utils/util.js,在不管多深的组件里面你都要慢慢 ../ 点到根目
录,同样 .wxss 文件 @import 导入文件时也只能使用相对路径,所以就会出现../../../../../../utils/fetch.js.
require的引入只能是相对路径,例如const separateMall = require('../api/separateMall');
复制代码
3.wxss文件中按ES6规范写法,报错
只支持ES5写法
复制代码
4.模板 {{}} 中调用了方法都不能执行
模板 {{}} 中连方法都不能执行,只能处理简单的运算如 + - * /,如果遇到数据需要 Number,toFixed等等的场景,
需要在 .js 文件中预先格式化好再一个个 setData,比如经常写的 {{curSpec.allPrice.toFixed(2)}},直接不显示.
复制代码
5.按钮button自定义(宽高)样式,在wxss设置无效
注意,你在css(wxss)里面直接设置button的width,height是无效的!!!必须在html(wxml)里面的style进行设置!!!
<button style="width: 14vmin;height: 14vmin;">有效 </button>
复制代码
6.小程序中 button没有办法去掉border或隐藏border,border:none;border-width:0;这两个属性都无效
button::after {
display: none;
}
复制代码
7.小程序顶部导航栏标题不居中,小程序默认在andriod中居左,ios中居中
解决方法:自定义导航栏
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '#666',
navigationStyle: 'custom'
}
复制代码
8.访问H5页面没效果,<web-view src="https://juejin.cn/post/{{api_root}}index/index/aboutus"></web-view>
微信小程序访问H5需要配置业务域名,配置业务域名需要把微信公众平台生成的校验文件放在域名根目录下
需要保证文件访问时间小于一秒
复制代码
- this.data赋值页面没重新渲染
this.data与this.setData的关系就是this.setData里面存储的是this.data的副本,而界面是从this.setData里
面托管的this.data的副本取数据的。所以我们更改this.data并不会直接更新界面,因为这个时候的this.setData里
面的副本还是没有更新前的。
想重新渲染用this.setData进行赋值
复制代码
编码得这样得那样,才能达到效果
1.自定义组件和父子组件的传参
在 pages 同级目录下,新建 components 文件夹,用于存放不同的组件,右键选择 “新建组件”,输入名称(inputNumber),自动生成四种类型的文件
在需要使用自定义组件父级页面的 json 文件中添加引用,然后进行使用,同时传递给子级一个函数
然后在子级页面对于父组件传递来的参数可进行使用或回调
父级对于回调函数进行处理
2.嵌入HTML代码
在wxParse下载文件,下载完之后我们需要用到目录下的wxParse文件夹,把他拷贝到我们的项目中
在app.wxss全局样式文件中,引入wxParse.wxss文件
@import "/plug/wxParse/wxParse.wxss";
复制代码
在我们要(嵌入的HTML代码)的wxjs文件中引入wxParse.js,然后进行引用
wxParse.wxParse("content" , "html",news.content,this,0);
复制代码
引用函数定义如下:
在我们要(嵌入的HTML代码)的wxml中引入wxParse.wxml文件
<import src="../../plug/wxParse/wxParse.wxml" />
<template is="wxParse" data="{{wxParseData:content.nodes}}" />
复制代码
3.微信授权登录
登录流程时序
在页面上创建一个button标签把 open-type 设置为 getUserInfo 代码如下:
<button class="login" bindgetphonenumber="login" open-type="getPhoneNumber">微信手机号快捷登录</button>
复制代码
调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台帐号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台帐号)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。更多使用方法详见 小程序登录。
第一步:获取code
在登录页面初始的时间就获取code,不然获取信息的时候获取有时候首次会失败:
init(){
wx.login({
success: result => {
if (result.code) {
this.setData({
code: result.code
})
}
}
})
}
复制代码
第二步,根据code获取openid和session_key,然后带着openid调用登录接口
点击按钮的代码,获取到encryptedData和iv进行获取Openid,然后进行登录:
// 登录
login(e) {
this.setData({
isRequest: true
})
var errMsg = e.detail.errMsg;
if (errMsg === 'getPhoneNumber:ok') {
//获取encryptedData和iv
var iv = e.detail.iv, encryptedData = e.detail.encryptedData;
let param = {
app_type: 2,
encryptedData,
iv
}
this.getOpenid(param);
}
},
// 获取openId,进行登录
getOpenid(param) {
const { code } = this.data;
//检查code是否过期
wx.checkSession({
success: () => {
if (code) {
// 获取用户的openId
param = {
...param,
code: code, //传入code参数获取用户的openId
}
//获取Openid,我是自己封装了接口所以如下调用
common.getOpenid({
data:
param, success: (result) => {
;
let data = result.data;
//一切就绪,调用登录接口
user.mobilelogin({
data: { mobile: data.decodeResult.phoneNumber, xcx_openid: data.openid }, success: (result) => {
wx.setStorageSync('userInfo', result.data.userinfo)
App.globalData.userInfo = result.data.userinfo
}
});
}
})
}
},
fail: () => {
this.init();
}
})
}
复制代码
4.微信提供5中跳转方式
1、wx.navigateTo(OBJECT)
保留当前页面,跳转到应用内的某个页面
2、wx.redirectTo(OBJECT)
关闭当前页面,跳转到应用内的某个页面。
3、wx.switchTab(OBJECT)
跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
4、wx.navigateBack(OBJECT)
关闭当前页面,返回上一页面或多级页面。
5、wx.reLaunch(OBJECT)
关闭所有页面,打开到应用内的某个页面。
在做返回的功能时,注意navigateBack不会调用onload方法。可以把onload换成onShow方法,每次加载页面时会自动调用onShow方法
修改上一个页面数据使得返回做出反应,下面提供一种解决方法
用navigateBack跳转不用考虑页面传参问题,直接setData就好
var pages = getCurrentPages(); //获取当前页面
var prevPage = null;
if (pages.length > 1) {
prevPage = pages[pages.length - 2];//获取上一页面
}
prePage.setData({
msg:'修改成功' //给上一页面的变量赋值
})
prePage.getPageData(); //调用上一页面的方法(加载数据)
wx.navigateBack({ //返回上一页面
delta: 1,
})
复制代码
5.微信支付
调用好自己服务器的接口,带返回参数调用函数doWxPay,然后对应处理
doWxPay(param) {
const _this = this;
//小程序发起微信支付
wx.requestPayment({
timeStamp: param.data.timeStamp,//记住,这边的timeStamp一定要是字符串类型的,不然会报错
nonceStr: param.data.nonceStr,
package: param.data.package,
signType: 'MD5',
paySign: param.data.paySign,
success: function (event) {
},
fail: function (error) {
},
complete: function () {
}
});
},
复制代码
6.radio默认样式修改
/* 选中后的 背景样式 (红色背景 无边框 可根据UI需求自己修改) */
radio .wx-radio-input.wx-radio-input-checked {
border-color: #03a9f4;
background: #03a9f4;
}
/* 自定义样式.... */
radio .wx-radio-input {
height: 40rpx;
width: 40rpx;
margin-top: -4rpx;
border-radius: 50%;
border: 2rpx solid #999;
background: transparent;
}
/* 选中后的 对勾样式 (白色对勾 可根据UI需求自己修改) */
radio .wx-radio-input.wx-radio-input-checked::before {
border-radius: 50%; /* 圆角 */
width: 40rpx; /* 选中后对勾大小,不要超过背景的尺寸 */
height: 40rpx; /* 选中后对勾大小,不要超过背景的尺寸 */
line-height: 40rpx;
text-align: center;
font-size: 30rpx; /* 对勾大小 30rpx */
color: #fff; /* 对勾颜色 白色 */
background: #f00;
transform: translate(-50%, -50%) scale(1);
-webkit-transform: translate(-50%, -50%) scale(1);
}
复制代码
7.获取二维码带过来参数
let url = decodeURIComponent(option.q);
console.log(url);
复制代码
8.加空格
<!-- 无论多少个空格都会被替换为一个空格 -->
<view class='c1'><text>我是c1我有4个空格啊 啊</text></view>
<!-- 事实证明添加绑定的空格数据是无效的 -->
<view class='c2'><text>我是c2我有4个空格啊{{" "}}啊</text></view>
<!-- 如果不加decode="true"  会被识别突显 但是无法转化成空格 -->
<view class='c3'><text>我是c3我有4个空格啊 啊</text></view>
<!-- 这个是正确的操作,也是网上查到的操作,缺点是要添加多少个空格就要加多少 -->
<view class='c4'><text decode='true'>我是c4我有4个空格啊 啊</text></view>
<!-- 这个是最开始我使用的操作,它好在可以自定义长度而不用使用多个 但是需要注意,这里只能使用text组件,其他的组件没有效果 -->
<view class='c5'><text >我是c5我有4个空格啊<text style='display:inline-block;width:150px'></text>啊</text></view>
复制代码
9. 选择照片上传
// 选择照片
changeImage() {
const _this = this;
wx.chooseImage({
count:1,
success(res) {
const tempFilePaths = res.tempFilePaths
wx.uploadFile({
url: `${App.api_root}/upload`,
filePath: tempFilePaths[0],
name: 'file',
header: {
'content-type': 'multipart/form-data',
token: wx.getStorageSync('userInfo').token
},
DATA: {
file: tempFilePaths
},
success: (res) => {
const data = JSON.parse(res.data)
_this.setData({
avatar: data.data.full_url,
url: data.data.url
})
}
})
}
})
},
复制代码
10.获取滚动高度
wx.createSelectorQuery().select('#id').boundingClientRect(function(rect){
rect.id // 节点的ID
rect.dataset // 节点的dataset
rect.left // 节点的左边界坐标
rect.right // 节点的右边界坐标
rect.top // 节点的上边界坐标
rect.bottom // 节点的下边界坐标
rect.width // 节点的宽度
rect.height // 节点的高度
}).exec()
复制代码
11.下拉刷新,显示三个小点
当前文件json文件:
{
"usingComponents": {
"loadMore": "/components/loadMore/loadMore"
},
"enablePullDownRefresh": true
}
复制代码
app.json配置
"window": {
"navigationBarBackgroundColor": "#fff"
},
复制代码
自定义组件,提高代码复用性
1.底部弹窗组件
父组件json修改:
{
"usingComponents": {
"modal": "/components/modal/modal"
}
}
复制代码
父组件html:
<modal id="cancelOrder" closeAble title="标题">
内容
</modal>
复制代码
js代码:
Component({
options: {
styleIsolation: 'apply-shared'
},
data:{
hideModal:true, //模态框的状态 true-隐藏 false-显示
animationData:{},//
},
properties:{
// 标题
title:{
type:String,
value:''
},
// 是否显示×按钮
closeAble:{
type:Boolean,
value:false,//是否显示右上角关闭按钮
}
},
methods:{
// 显示遮罩层
showModal: function () {
var that=this;
that.setData({
hideModal:false
})
var animation = wx.createAnimation({
duration: 600,//动画的持续时间 默认400ms 数值越大,动画越慢 数值越小,动画越快
timingFunction: 'ease',//动画的效果 默认值是linear
})
this.animation = animation
setTimeout(function(){
that.fadeIn();//调用显示动画
},200)
},
// 隐藏遮罩层
hideModal: function () {
var that=this;
var animation = wx.createAnimation({
duration: 800,//动画的持续时间 默认400ms 数值越大,动画越慢 数值越小,动画越快
timingFunction: 'ease',//动画的效果 默认值是linear
})
this.animation = animation
that.fadeDown();//调用隐藏动画
setTimeout(function(){
that.setData({
hideModal:true
},()=>{
that.triggerEvent("hideModal");
})
},400)//先执行下滑动画,再隐藏模块
},
//动画集
fadeIn:function(){
this.animation.translateY(0).step()
this.setData({
animationData: this.animation.export()//动画实例的export方法导出动画数据传递给组件的animation属性
})
},
fadeDown:function(){
this.animation.translateY(300).step()
this.setData({
animationData: this.animation.export(),
})
},
preventTouchMove:function(){
return;
}
}
})
复制代码
wxml代码:
<view class="modals modals-bottom-dialog" hidden="{{hideModal}}" >
<view class="modals-cancel" bindtap="hideModal" catchtouchmove="preventTouchMove" ></view>
<view class="bottom-dialog-body bottom-pos" animation="{{animationData}}">
<view class="title" hidden="{{!title}}" >{{title}}</view>
<image src="/images/icon/guanbi.png" hidden="{{!closeAble}}" bindtap="hideModal" class="closeAble"></image>
<view class="body-content" catchtouchmove="preventTouchMove">
<slot></slot>
</view>
</view>
</view>
复制代码
wxss样式:
/*模态框*/
.modals {
position: fixed;
z-index: 999;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.modals-cancel {
position: absolute;
z-index: 1000;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .5);
overflow:hidden !important;
}
.bottom-dialog-body {
border-radius: 16rpx 16rpx 0px 0px;
position: absolute;
z-index: 10001;
bottom: 0;
left: 0;
right: 0;
/* padding: 30rpx; */
min-height: 100rpx;
max-height:100%;
background-color: #fff;
}
.body-content{
padding: 40rpx 20rpx;
position: relative;
}
.title{
width: calc (100% - 50rpx );
text-align: center;
font-size:28rpx;
line-height: 40rpx;
font-weight: 800;
border-bottom:1px solid #f5f5f5;
padding: 30rpx;
}
.closeAble{
width: 30rpx;
height: 30rpx;
position: absolute;
top:35rpx;
right: 30rpx;
z-index: 1;
}
/*动画前初始位置*/
.bottom-pos {
-webkit-transform: translateY(100%);
transform: translateY(100%);
}
复制代码
父组件想控制自定义底部弹窗,需要wxml里给个id(modal),js中的attached函数进行获取id:this.modal = this.selectComponent(#modal
);,往后就可以进行调用自定义组件内的函数,进行操控。
2.头部组件
组件原理具体参考小程序自定义导航栏适配(完美版),在这就不描述了
父组件json修改:
{
"usingComponents": {
"navigation": "/components/navigation/navigation"
},
"navigationStyle": "custom"
}
复制代码
父组件wxml:
<navigation bind:back='navToBack' customBack="true" backIcon='/images/icon/fanghui_hei@2x.png' titleText ='我的积分'>
复制代码
js代码:
Component({
properties: {
background: {
type: String,
value: 'rgba(255, 255, 255, 1)'
},
color: {
type: String,
value: 'rgba(0, 0, 0, 1)'
},
mainColor: {
type: String,
value: '#fff'
},
titleText: {
type: String,
value: ''
},
titleArray: {
type: Array,
value: [],
observer(newsProps) {
this.setData({
type: newsProps[0].type
})
}
},
titleImg: {
type: String,
value: ''
},
backIcon: {
type: String,
value: '/images/icon/fanghui_hei@2x.png'
},
homeIcon: {
type: String,
value: ''
},
fontSize: {
type: Number,
value: 16
},
iconHeight: {
type: Number,
value: 19
},
iconWidth: {
type: Number,
value: 58
},
customBack: {
type: Boolean,
value:false
}
},
attached: function () {
var that = this;
that.setNavSize();
that.setStyle();
},
data: {
type: ''//当前默认选中
},
methods: {
// 通过获取系统信息计算导航栏高度
setNavSize: function () {
var that = this
, sysinfo = wx.getSystemInfoSync()
, statusHeight = sysinfo.statusBarHeight
, isiOS = sysinfo.system.indexOf('iOS') > -1
, navHeight;
if (!isiOS) {
navHeight = 48;
} else {
navHeight = 44;
}
that.setData({
status: statusHeight,
navHeight: navHeight
})
},
setStyle: function () {
var that = this
, containerStyle
, textStyle
, iconStyle;
containerStyle = [
'background:' + that.data.background
].join(';');
textStyle = [
'color:' + that.data.color,
'font-size:' + that.data.fontSize + 'px'
].join(';');
iconStyle = [
'width: ' + that.data.iconWidth + 'px',
'height: ' + that.data.iconHeight + 'px'
].join(';');
that.setData({
containerStyle: containerStyle,
textStyle: textStyle,
iconStyle: iconStyle
})
},
// 返回事件
back: function () {
if(this.properties.customBack){
this.triggerEvent('back')
}else{
wx.navigateBack({
delta: 1
})
}
},
home: function () {
this.triggerEvent('home', {});
},
// 切换tab
switchTab(e) {
const { type } = e.currentTarget.dataset;
this.setData({
type
})
this.triggerEvent('switchTab',type)
}
}
})
复制代码
wxml代码:
<view class='nav' style="height: {{status + navHeight}}px;background-color: {{mainColor?mainColor:'#fff'}}">
<view class='status' style='height: {{status}}px;{{containerStyle}}'></view>
<view class='navbar' style='height: {{ navHeight}}px'>
<view class='back-icon' wx:if="{{backIcon}}" bindtap='back'>
<image src='{{backIcon}}'></image>
</view>
<view class='home-icon' wx:if="{{homeIcon}}" bindtap='home'>
<image src='{{homeIcon}}'></image>
</view>
<view class='nav-icon' wx:if="{{titleImg}}">
<image src='{{titleImg}}' style='{{iconStyle}}'></image>
</view>
<view class='nav-title' wx:if="{{titleText && !titleImg}}">
<text style='{{textStyle}}'>{{titleText}}</text>
</view>
<view class='nav-title-tab' wx:if="{{titleArray && !titleImg&&!titleText}}">
<view bindtap="switchTab" style='{{textStyle}}' data-type="{{item.type}}" wx:for="{{titleArray}}" class=" title-tab {{type===item.type?'active':''}}">
{{item.text}}
</view>
</view>
</view>
</view>
<view style='height: {{status + navHeight}}px'></view>
复制代码
wxss样式:
.nav {
width: 100%;
position: fixed;
left: 0rpx;
top: 0rpx;
z-index: 1;
}
.navbar {
position: relative;
}
.back-icon,
.home-icon {
width: 28px;
height: 100%;
position: absolute;
transform: translateY(-50%);
top: 50%;
display: flex;
}
.back-icon {
left: 16px;
}
.home-icon {
left: 44px
}
.back-icon image {
width: 44rpx;
height: 44rpx;
margin: auto;
}
.home-icon image {
width: 20px;
height: 20px;
margin: auto;
}
.nav-title,
.nav-icon {
position: absolute;
transform: translate(-50%, -50%);
left: 100px;
top: 50%;
font-size: 0;
}
/* 切换tab */
.nav-title-tab {
width: 68%;
margin-left: 70rpx;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.title-tab {
position: relative;
padding: 10rpx;
}
.active::after {
content: '';
position: absolute;
width: 24rpx;
height: 6rpx;
bottom: 0rpx;
left: calc(50% - 12rpx);
background-size: 100% 100%;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAGCAYAAAA2aTUtAAAA/klEQVQoU3WSSU7DQBBFX3VLSAwnS9bmCnARlnCaSLlMtrAgSEzCdozchb4rjY0UFq2qHlyv/i+bN80ZbXsHNMAFMAAH3PspmvW4d5h1QET3dsqhpZQes6/jue7iHD7Iecdm82S+Wt1jdov7N2YD7odjcT1WHsXjw2UR7VtSihjgmneU0pHzGznvzdfrPXCJ+/ALCVhVUiHaq9gMDHUVEApTikYESemdcSxS8ozZ1UmIrALZJUsCNncs6+bOawNLiJSUUqTkAbiZZiHLwiLNQkUDsrRsVnLaLt1rCQavuL/YYvDXwPmfwYcS/QTqOuz6f/BSPCvL+ZNx3LHdPv4A9RPu5WDz5hwAAAAASUVORK5CYII=');
}
复制代码
3.密码输入
{
"usingComponents": {
"inputFrame": "/components/inputFrame/inputFrame"
}
}
复制代码
父组件wxml:
<inputFrame
value=""
plaintext
focus="{{ true }}"
bind:finished="onFinished"
id="input-frame"
/>
复制代码
js代码:
Component({
externalClasses: ['custom-class'],
options: {
styleIsolation: 'shared'
},
properties: {
plaintext: Boolean,
value: String,
focus: Boolean,
// divider
frameStyle: String,
space: {
type: Number,
value: 6
}
},
data: {
_value: '',
valueList: [],
cursor: 99,
isFocus: true,
height:''//软键盘出来,输入框距离底部的距离
},
lifetimes: {
attached: function () {
this.setData({
isFocus: this.properties.focus,
_value: this.properties.value
});
this.init();
this.setCursorPosition();
}
},
methods: {
init: function () {
var value = this.data._value;
var plaintext = this.properties.plaintext;
if (!plaintext) {
value = value.replace(/./igm, '●');
}
this.setData({ valueList: value.split('') });
},
setCursorPosition: function () {
this.setData({ cursor: this.data._value.length });
},
onClick: function (value) {
this.setData({ isFocus: value });
wx.onKeyboardHeightChange(res => {
this.setData({
height:res.height*2+40
})
})
},
onInputChange: function (e) {
var value = e.detail.value;
var space = this.properties.space;
this.setData({ _value: value });
this.triggerEvent('change', value);
this.init();
if (value.length >= space) {
this.triggerEvent('finished', value);
}
},
onInputFocus: function () {
this.setCursorPosition();
},
getValue: function () {
var number = this.properties.space;
return this.data._value.slice(0, number);
},
setValue: function (v) {
if (v == null) return;
v = String(v);
this.setData({ _value: v });
this.init();
},
// bindkeyboardheightchange:function(v){
// const {height}=v.detail;
// this.setData({
// height:height*2+40
// })
// }
}
})
复制代码
wxml代码:
<view class="input-frame custom-class {{ frameStyle }}" style="margin-bottom:{{height}}rpx">
<!-- hide -->
<input
type="text"
class="input-frame-input"
value="{{ _value }}"
maxlength="{{ space }}"
cursor="{{ cursor }}"
focus="{{ isFocus }}"
type="number"
bind:input="onInputChange"
bind:focus="onInputFocus"
bindkeyboardheightchange="bindkeyboardheightchange"
adjust-position="{{false}}"
hold-keyboard="{{true}}"
/>
<!-- input -->
<view
class="input-item {{(!valueList.length&&index===0)||(valueList.length&&index===valueList.length&&valueList.length<6)?'active':''}}"
bind:tap="onClick"
wx:for="{{ space }}"
wx:key="index"
>
<view hidden="{{!valueList[index]}}" class="status-point"></view>
</view>
</view>
复制代码
wxss代码:
.input-frame {
/* position: relative; */
display: flex;
justify-content: space-around;
width: 94%;
margin: 0 auto;
}
.input-frame .input-item {
border-radius: 16rpx;
width: 100rpx;
height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #ccc;
}
.input-frame .active{
border-color: #f995a2;
}
.input-frame-input {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
opacity: 0;
/* left: -5000px; */
}
/* .input-frame .input-item:not(:nth-last-child(1)) {
border-right-color: transparent;
} */
/* divider style */
.input-frame.divider .input-item {
border-top-color: transparent;
border-right-color: transparent;
border-left-color: transparent;
margin: 0 7px;
width: 40px;
height: 40px;
}
.status-point {
display: inline-block;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: black;
}
复制代码
end
网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~