uni开发wx小程序--随记

461 阅读5分钟

开发工具Hbuilder-运行到微信小程序即可

1:自定义头部+下拉刷新

先获取手机型号,然后适配头部高度

page.json

"style": {
	"navigationBarTitleText": "首页",
	"navigationStyle":"custom",//自定义头部
	"enablePullDownRefresh": true//下拉刷新
}

homepage.vue


<template>
    <view class="TitleFixed" :style='getTitleTop' v-if="titleTop">
        首页
    </view>
</template>
//js
onPullDownRefresh:function(){//下拉刷新
    this.initApi();
},
computed:{
    ...mapState(['phoneData']),
    getTitleTop(){//头部适配
        if(this.isPlat == 3){
            return 'padding-top:54px';
        }else if(this.isPlat == 2){
            return 'padding-top:32px';
        }else if(this.isPlat == 1){
            return 'padding-top:32px';
        }
    }
};
 methods: {
    ...mapMutations(['getPhoneType']),
    init(){
        this.getPhoneType();
        if (this.phoneData.model.indexOf('iPhone') !== -1) {//ios
            if(this.phoneData.screenHeight >= 812){//x
                this.isPlat = 3;
            }else{
                this.isPlat = 2;
            }
        } else {//安卓
            this.isPlat = 1;
        }
    },
 }

store/index.js //获取手机型号

getPhoneType(state){
    uni.getSystemInfo({
	    success: function(res) {
		    state.phoneData = res;
	    },	
    })
},

2: 四位数验证码的输入 交互模拟 类似下面图片这样的

//验证码部分代码
<template>
    <view class="loginInput-row e-verifiNum">
		<text class="title">验证码</text>
		<view class="verifCode clearfix" >
			<view class="code-area" @tap.stop='clickInside($event)'>
				<view class="flex-box">
					<input
						v-show="onceInput"
						:value="val"
						type="number"
						:focus="isfocus"
						maxlength="4"
						class="hide-input"
						@input="getVal"
					/>
					<view v-bind:class="['item', { active: codeIndex == 1 }]">
						<view class="line"></view>
						<block>	{{ codeArr[0] ? codeArr[0] : ''}}</block>
					</view>
					<view v-bind:class="['item', { active: codeIndex == 2 }]">
						<view class="line"></view>
						<block >{{ codeArr[1] ? codeArr[1] : ''}}</block>
					</view>
					<view v-bind:class="['item', { active: codeIndex == 3 }]">
						<view class="line"></view>
						<block >{{ codeArr[2] ? codeArr[2] : ''}}</block>
					</view>
					<view v-bind:class="['item', { active: codeIndex == 4 }]">
						<view class="line"></view>
						<block >{{ codeArr[3] ? codeArr[3] : ''}}</block>
					</view>
				</view>
			</view>
			<button 
			class="priButton" 
			@tap.stop="sendVerifi($event)"
			:class="{'btn_gray':verGrayBtn}">
				{{codeTips}}
			</button>
		</view>
	</view>
</template>
<script>
    getVal(e) {//获取输入验证码
		let { value } = e.detail;
		this.val = value;
		let arr = value.split('');
		this.codeIndex = arr.length + 1;
		this.codeArr = arr;12
		if (this.codeIndex > 4) {
			//输入完成
			this.codeArr.join('');
			this.isfocus = true;
		}
	},
	clearCode(){//清除验证码
		this.isfocus = false;
		this.codeIndex = 1;
		this.codeArr = [];
		this.val = "";
	},
	clickOutside(e){//点击输入框外失去焦点
		this.isfocus = false;
		this.codeIndex = -1;
	},
	clickInside(e){//焦点定位到第一个没输入的位置,如果都输入了则不显示光标
		if(!this.onceInput){
			return;
		}
		this.isfocus = true;
		switch(this.val.length){
			case 0:
				this.codeIndex = 1;
			break;
			case 1:
				this.codeIndex = 2;
			break;
			case 2:
				this.codeIndex = 3;
			break;
			case 3:
				this.codeIndex = 4;
			break;
			case 4:
				// this.codeIndex = 4;
			break;
		}
	},
</script>
//验证码ui
<style lang='scss'>
    .code-area {
		text-align: center;
		.flex-box {
			display: flex;
			flex-wrap: wrap;
			position: relative;
			justify-content: center;
		}
		.item {
			position: relative;
			width:72upx;
			height: 72upx;
			margin-right: 26upx;
			font-size: 36upx;
			color: #1A1D1E;
			line-height: 72upx;
			box-sizing: border-box;
			border-bottom:1px solid  #D3D3D3;
		}
		.item:last-child {
			margin-right: 0;
		}
		.active {
			border-color:#D3D3D3;
		}
		.active .line {
			display: block;
		}
		.line {
			display: none;
			position: absolute;
			left: 50%;
			top: 50%;
			transform: translate(-50%, -50%);
			width:2upx;
			height: 40upx;
			background: #E25422;
			animation: twinkling 1s infinite ease;
		}
		.hide-input {
			position: absolute;
			top: 0;
			left: -100%;
			width: 200%;
			height: 100%;
			text-align: left;
			z-index: 9;
			opacity: 1;
		}
		@keyframes twinkling {
			0% {
				opacity: 0.7;
			}
			50% {
				opacity: 1;
			}
			100% {
				opacity: 0.7;
			}
		}
		.content {
			height: 400upx;
		}
		button{
			margin-top: 30upx;
		}
	}

</style>

3: 富文本框图片显示问题

富文本框里的图片不能像H5一样 操作dom改变图片的宽度,切uni不支持filter 所以就在拿到数据的时候 调用formatRichText方法先处理一遍,

<rich-text :nodes="detai"></rich-text>
formatRichText (html) { //控制小程序中图片大小
	/**
	 * 处理富文本里的图片宽度自适应
	 * 1.去掉img标签里的style、width、height属性
	 * 2.img标签添加style属性:max-width:100%;height:auto
	 * 3.修改所有style里的width属性为max-width:100%
	 * 4.去掉<br/>标签
	 * @param html
	 * @returns {void|string|*}
	 * */
	let newContent;
    newContent = html.replace(/<img[^>]*>/gi,function(match,capture){
        match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
        match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
        match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
        return match;
    });
    newContent = newContent.replace(/style="[^"]+"/gi,function(match,capture){
        match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
        return match;
    });
    // newContent = newContent.replace(/<br[^>]*\/>/gi, '');
	newContent = newContent.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:inline-block;margin:10rpx auto;"');
    return newContent;
}

4: 小程序登录逻辑

我们有自己项目的登录,获取用户手机号去走登录逻辑+wx的login方法拿code码(code只能用一次),如果在获取手机号成功回调里去拿code码,可能会出现解密失败,原因:回调中调login,可能刷新登录状态,此时服务器使用code换取的sessionKey不是加密时使用的sessionKey,导致解密失败==解决:在onLoad()里去拿code码, wx.checkSession再确认一遍 ,双管

<button type="primary" class="login_btn" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">微信手机号快捷登录</button>
    methods: {
        getPhoneNumber(e){
        	let _this = this;
        	if(e.detail.errMsg == "getPhoneNumber:ok"){
    	        wx.checkSession({
			success(res){
		          uni.showLoading({  
				title: '登陆中'  
			   });
		  	_this.$api.wxPhoneLogin({...e.detail,code:_this.jscode}).then(json=>{
				uni.hideLoading(); 
				if(json.code !== -1){
					setTimeout(()=>{
						uni.showToast({
							icon: 'none',
							title: '登录成功'
						});
					},10)
					uni.setStorageSync('token', json.token);
					setTimeout(()=>{
						uni.navigateBack();
					},500)
				}else{
					_this.initCode();
				}
			})
    	},
    		})
            }
        }
    },
    onLoad(){
    	uni.login({
    		success: res => {
    			this.jscode = res.code;
    		}
    	});
    }

5: tab点击锚点定位 , 滚动定位

简陋的效果图 如下:

这边用到数据结构是一个两级数据的数组, 目前是数据一次性全部加载完成,上面tab区域显示第一级数组数据,list显示第二级数据内容,tab滑动到某个位置吸顶

    //数据结构
    arr = [
        list:[]
    ]

布局--吸顶+隐藏scroll-view在安卓机下的滚动条(隐藏滚动条逻辑: 外面拿一个div包起来,把滚动条放到可视区域外)

<div class="recomd-wapper" :class="{'nav-tabber_fixed':isFixed}" :style="getTopX" >
    <scroll-view class="selectionTab"
    scroll-x="true" @scroll="scroll" :scroll-left='tabScrollLeft' @scrolltolower="scrolltolower">
        <view
            v-for="(item, index) in deTailList"
            class="scroll-view-item_H"
            :class="{Tabactive:tabActiveIndex == index}"
            @tap="switchBar(item, index)"
            :key="item.id"
        >
        {{ item.name }}
        </view>
    </scroll-view>
</div>
 <ul class="tab-list" :style="getBottomX">
    <li v-for="(item1,ind1) in deTailList" :key="item1.id" class='mainTab'>
        <!-- {{item1.name}} -->
        <ul>
            <li v-for="(item2,ind2) in item1.merches" :key="ind2" @tap="goodsDetail(item2.id)">
                <img :src="item2.merchPic[0].fileUrl" alt="">
                <div class="recomd-detail">
                    <h1>{{item2.title}}</h1>
                    <p>{{item2.subtitle}}</p>
                    <div>
                        <span class="price">
                            ¥<span class="price-num">{{item2.price}}</span>
                        </span> 元
                    </div>
                </div>
            </li>
        </ul>
    </li>
</ul>
 //js
    data:{
        return{
            isFixed:false,
            stickyTop:'',
            scrollTop:'',
            tabScrollLeft:'100%',
        }
    },
    computed:{
        getTopX(){
            if(this.isFixed){
                if(this.isPlat == 3){
                    return 'padding-top:84px';
                }else if(this.isPlat == 2){
                    return 'padding-top:62px';
                }else if(this.isPlat == 1){
                    return 'padding-top:62px';
                }
            }else{
                return 'padding-top:0';
            }
        },
    },
    //页面滚动执行方式
    onPageScroll(e) {
        this.scrollTop = e.scrollTop;
        this.titleTop = this.scrollTop > 150 ? true : false;
        if(this.scrollTop > this.stickyTop-80){
            this.isFixed = true;
            this.deTailList.forEach((item,index)=>{
                if(this.scrollTop > item.top -250){
                    if(!this.clickTab){
                        this.tabActiveIndex = index;
                        this.tabScrollLeft = this.deTailList[index].left - 20;
                    }
                }
            })
        }else{
            this.isFixed = false;
        }
    },
    methods: {
        switchBar(item, i){//点击切换tab
            setTimeout(()=>{
                this.tabActiveIndex = i;
                this.clickTab = false;
            },200);
            uni.pageScrollTo({
                scrollTop:this.deTailList[i].top - 200,
                duration: 300
            });
        },
        calcSize(){//计算滚动tab的高度
            setTimeout(()=>{
                uni.createSelectorQuery().selectAll('.mainTab').boundingClientRect(rects=> {
                    rects.forEach((rect,index)=> {
                        this.deTailList[index].top = rect.top;
                        this.deTailList[index].height = rect.height;
                    })
                }).exec();
                uni.createSelectorQuery().selectAll('.scroll-view-item_H').boundingClientRect(rects=> {
                    rects.forEach((rect,index)=> {
                        this.deTailList[index].left = rect.left;
                        this.deTailList[index].width = rect.width;
                    })
                }).exec();
                uni.createSelectorQuery().select('.selectionTab').boundingClientRect(rects=> {
                    this.stickyTop = rects.top;
                }).exec();
            },300);
        },
    }

样式

.recomd-wapper{
    height: 114rpx;
    width: 100%;
    overflow: hidden;
}
.nav-tabber_fixed{
    position: fixed;
    top: 0;
    left: 0;
    z-index: 999;
    background:#fff;
    box-shadow: 0 10px 16px 0 rgba(164,164,164,0.20);
    padding: 0 26rpx;
    height: 96rpx;
    .scroll-view-item_H{
        &:last-child{
            margin-right: 40rpx;
        }
    }
}
.selectionTab{
    height: 96rpx;
    padding-top: 26rpx;
    padding-bottom: 34rpx;
    width: 100%;
    background:#fff;
    white-space: nowrap;
    .scroll-view-item_H{
        padding:10rpx 27rpx 10rpx 27rpx;
        background: #F7F7FD;
        display: inline-block;
        margin-right: 28rpx;
        position: relative;
    }
    .Tabactive{
        color: #FFFFFF;
        background-image: linear-gradient(to right,#E25422 , #FF9C64 );
    }
}

6: 自定义导航栏

注释掉pages.json的tabBar的配置 自己写tabBar ,iPhone的适配上面代码有

<template>
    <view
        class="tarbar-list"
        :style="{
            background: tabBar.backgroundColor,
            color: tabBar.color,
            'border-top': tabBar.position == 'bottom' ? '1rpx solid ' + tabBar.borderStyle : 0,
            'border-bottom': tabBar.position == 'top' ? '1rpx solid ' + tabBar.borderStyle : 0,
            'paddingBottom':`${barPadding}px`
        }">
        <view class="tarbar-list-ul">
            <view class="tarbar-list-li" :class="index == 2 ? 'tarbar-list-li-center' : ''" v-for="(item, index) in tabBar.list" :key="index" @click.top="setSelected(index)">
                <view class="tarbar-list-li-icon"><image :src="tabSelect == index ? item.selectedIconPath : item.iconPath" mode=""></image></view>
                <view class="tarbar-list-li-name">{{ item.text }}</view>
            </view>
        </view>
    </view>
</template>
<script>
    props:{
        tabSelect: {
            type:Number,
            default:0
        }
    },
    data(){
        return {
            selected:1,
            barPadding:0,
            tabBar: {
                color: '#ccc',
                selectedColor: '#E84351',
                borderStyle: '#ccc',
                backgroundColor: '#fff',
                position: 'bottom',
                list: [
                    {
                        pagePath: '/pages/homePage/homePage',
                        iconPath: 'xxxx',
                        selectedIconPath: 'xxxx'',
                        text: 'xxxx''
                    },
                    {
                        pagePath: '/pages/xxxx/xxxx',
                        iconPath: 'xxxx',
                        selectedIconPath: 'xxxx',
                        text: 'xxxx''
                    }
                ]
            },
        }
    },
</script>

7: 微信支付

 async requestPayment(){
        let orderInfo = await this.getOrderInfo();
        if(orderInfo.code == -1) {
            uni.showModal({
                content: orderInfo.message,
                showCancel: false,
                success: (res)=> {
                    this.orderDetail();
                }
            })
            return;
        }
        orderInfo.data.package = orderInfo.data.packageValue
        uni.requestPayment({
            provider: 'wxpay',
            ...orderInfo.data,
            success: (e) => {
                uni.showToast({
                    title: "支付成功!",
                    icon: 'none',
                });
                this.orderResult();
            },
            fail: (e) => {
                console.log("fail", e);
                if(e.errMsg == "requestPayment:fail cancel"){//商户取消
                   
                }else{
                    uni.showModal({
                        content: "支付失败!",
                        showCancel: false
                    })
                }
            },
            complete: () => {
                
            }
        })
    },
    getOrderInfo() {
        return new Promise((res,rej) => {
            this.$api.orderDetail({
                id:this.preDataId
            }).then(json=>{
                if(json.status == 40){//已取消
                    uni.showToast({
                        title: "因超时未支付,订单已取消",
                        icon: 'none',
                    });
                }else if(json.status == 10){//待支付
                    this.$api.orderPrepay({
                        orderId:this.preDataId,
                    }).then(json=>{
                        res(json);
                    })
                }
            })
        });
    },

8: 根据经纬度打开内置地图

if (this.latitude != -1 && this.longitude !=-1){
	uni.openLocation({
	  latitude:this.latitude,
	  longitude:this.longitude,
	  address: this.workPlace,
	  name: "[位置]"
	})
  }

9:点击客服 拨号

uni.makePhoneCall({
  phoneNumber: 10086
})

10: uni.navigateTo和uni.redirectTo和uni.navigateBack()的区别

  • uni.navigateTo:导航的意思, 类似于打开二级目录
  • uni.redirectTo:重定向到另一个页面
  • uni.navigateBack():返回上一个页面

11 :滚动加载

    methods:{
        onReachBottom(){
            this.param.pageNo++;
            this.$api.myOrder(this.param).then(json=>{
                this.arr = this.arr.concat(json.list);
            });
    	},
    }

12 :一些小收获 uni&vue uni的表现

  • 1):computed里方法不能传参
  • 2):不能用filter
  • 3):watch 无法监听wx小程序
  • 4):如果想要进入页面(无论什么方式)就刷新数据, 数据加载写到onShow(){}里面
  • 5): :style=""这样的方式设置px像素值,其值为实际像素,不会被编译器转换。
  • 6): 不提供:key,会报一个 warning,

13: 下面文档uni使用小技巧更加全面些 上面记录的我自己开发遇见的问题

传送带: blog.csdn.net/qq_37939251…

14: 上传图片

我们项目的图片是统一存到图片服务器的 , 所以要先把选择的图片上传到图片服务器,拿到地址后再给数据库

    uni.chooseImage({
	  count: 1,
	  sizeType: ['original', 'compressed'],
	  sourceType: ['album', 'camera'],
	  success (res) {
	    const tempFilePaths = res.tempFilePaths;
		let token = uni.getStorageSync('token');
		let upToken = xxxx;//图片服务器
		let url=xxx;
		let urlPath=xxxxx;
		uni.uploadFile({
		  url: urlPath+"xxxxxx", //图片服务器的地址
		  filePath: tempFilePaths[0],
		  name:"file",
		  header: {
			  'content-type': 'multipart/form-data',
			  "X-Token":token,
			  "upToken":upToken
		  }, 
		  success: function(res) {
		    _this.$api.xxx//上传到数据库
		  }
		})
	  }
	})

15: axios请求封装

后端接口风格是restful的

    //api.js
        const httpRequest = (method,url, data) => {
            let httpDefaultOpts = {
                url: url,
                data: data,
                method: method,
                header: method == 'get' ? {
    			'X-Requested-With': 'XMLHttpRequest',
    			"Accept": "application/json",
    			"Content-Type": "application/json; charset=UTF-8"
    		} : {
    			'X-Requested-With': 'XMLHttpRequest',
    			'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    		},
    		dataType: 'json',
    	    }
            let promise = new Promise(function(resolve, reject) {
                uni.request(httpDefaultOpts).then(
                    (res) => {
                        if(res[1].statusCode === 200){
                            resolve(res[1].data);
                        }else{
                            httpCode(res);
                        }
                    }
                ).catch(
                    (response) => {
                        reject(response)
                    }
                )
            })
            return promise
        };
        //带Token请求
        const httpTokenRequest = (method,url,data) => {
            let token = uni.getStorageSync('token');
            //此token是登录成功后后台返回保存在storage中的
            let httpDefaultOpts = {
                url: url,
                data: data,
                method: method,
                header: method == 'get' ? {
            	'X-Token': token,
                    'X-Requested-With': 'XMLHttpRequest',
                    "Accept": "application/json",
                    "Content-Type": "application/json; charset=UTF-8"
                } : method == 'post' ?  {
            	'X-Token': token,
                    'X-Requested-With': 'XMLHttpRequest',
                    'Content-Type': 'application/json; charset=UTF-8'
                }:{//put
                    'X-Token': token,
                    'X-Requested-With': 'XMLHttpRequest',
                    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
                },
                dataType: 'json',
            }
            let promise = new Promise(function(resolve, reject) {
                uni.request(httpDefaultOpts).then(
                    (res) => {
                        if(res[1].statusCode === 200){
                            resolve(res[1].data);
                        }else{
                            httpCode(res);
                        }
                    }
                ).catch(
                    (response) => {
                        reject(response)
                    }
                )
            })
            return promise
        };
        let httpCode = (res)=>{
            switch(res[1].statusCode){
                case 401:
                    uni.showToast({
                        icon: 'none',
                        title: res[1].data.message
                    });
                    setTimeout(()=>{
                        uni.navigateTo({
                            url: '/pages/login/login'
                        });
                    },500);
                break;
                case 417:
                    uni.showToast({
                        icon: 'none',
                        title: res[1].data.message
                    });
                break;
                default:
                    uni.showToast({
                        icon: 'none',
                        title: res[1].data.message
                    });
            }
        }
        export default {
        	httpRequest,
            httpTokenRequest,
        }
    //url.js
        import http from './api.js';
        let baseUrl;
        if(process.env.NODE_ENV === 'development'){// 开发环境
            baseUrl = 'https://*****.com/'
        }else{ // 生产环境
            baseUrl = 'https://*****.com/'
        }
        export default {
            //绑定手机号
              wxPhoneLogin(params){
                 return http.httpTokenRequest('POST',`${baseUrl}/xxx`,params)
              },
        }
    //main.js
        import api from "./fetch/url";
        Vue.prototype.$api = api;
    //xxx.vue文件调用
         this.$api.wxPhoneLogin().then()

待更新

-小程序跳转H5