黑马优购---微信小程序开发

472 阅读14分钟

课程链接: www.bilibili.com/video/BV183…

课程文档: www.escook.cn/docs-uni-sh…

git仓库

1.初始化本地 Git 仓库: git init

  1. 将所有文件都加入到暂存区:

git add .

  1. 本地提交更新: git commit -m "init project"

5.git reset --hard c017060751c817ed9cfc1358ea107ff8dde9835b回到指定版本号

1.6.2 把项目托管到码云

  1. 注册并激活码云账号( 注册页面地址:gitee.com/signup

  2. 生成并配置 SSH 公钥(随便打开一个终端,输入ssh -t git@gitee.comblog.csdn.net/weixin_4577… 看到successfull证明配置成功!!image.png

  3. 创建空白的码云仓库

  4. 把本地项目上传到码云对应的空白仓库中 在项目根目录中cmd,输入新建仓库后,已有仓库中的二三条指令以此输入。

1.创建新分支tabbar,并选择该分支 git checkout -b tabbar

2.查看当前分支 git branch

  1. git ​​reset​​ --hard 版本号 回退版本后就找不到之前的提交记录了,使用 git fsck --lost-found 命令,找出当前被丢弃的提交。

5.创建分支后,需要利用 git push -u origin tabbar 命令将分支推向仓库,不然仓库看不到分支。

6.Git合并分支显示Already up to date,可以在git push一下,推到仓库。

  1. git branch -d tabbar 删除分支

8.git merge XX 合并分支

小程序项目中不支持 axios,而且原生的 wx.request() API 功能较为简单,不支持拦截器等全局定制的功能。因此,建议在 uni-app 项目中使用 @escook/request-miniprogram 第三方包发起网络数据请求。

flex-wrap: wrap;

flex-wrap: wrap在流布局中,元素占满一行无法显示的时候做处理wrap表示正常换行。

处理路径 当有一个navigator_url: "/pages/goods_list?query=服饰"路径需要更改时

				this.floorlist.forEach((item)=>{
					item.product_list.forEach((item)=>{
						item.url = "/subpkg/goods_list/goods_list?"+item.navigator_url.split("?")[1]
					})
				})

item.navigator_url.split("?")[1] :split根据?剪切后会放回数组

模拟器分开

image.png 点击图中按钮即可让模拟窗口分开出来,就可以边修改边查看。

分类

遇见分类的这种页面,滚动条高度不能写死,要和屏幕一致,可用使用 this.usableHeight = uni.getWindowInfo().windowHeight;获取可用的窗口高度。

ctrl+e快速选中相似内容

			background-color: #f7f7f7;
			line-height: 60px;
			text-align: center;
			font-size: 12px;
			&.active{
				background-color: #ffffff;
				position: relative;
				&::before{
					content: '';
					display: block;
					width: 3px;
					height: 30px;
					background-color: #c00000;
					position: absolute;
					top: 50%;
					left: 0;
					transform: translateY(-50%);
				}
			}
		}

image.png

滚动条保持上个分类距离问题 在一级分类中,我们切换了一级分类后,二级分类仍然保存着上次分类中滚动条的一个距离,可使用scroll-top=0来重置,但每次都赋同一个值是无效果的,这是一个小bug(在滚动条标签上动态绑定了scroll-top=0,在点击事件上再次赋值0是无效果的,尽管你的滚动条已经不是在0的位置了) 解决方法: this.scroolTo = this.scroolTo ===0?'1':'0' 赋值在0和1之间切换就行,细微距离不会影响用户的体验的。

自定义组件后的优化

			bgcolor:{
				type:String,
				default:"#C00000"
			},
			box_bgcolor:{
				type:String,
				default:"#ffffff"
			}
		},

注意:父组件提供值得时候,里面还需要再加一个单引号,应该那边需要接受的到字符串

父子组件调用事件

注意:当我们在组件标签上写点击事件是没有效果的,他只认自定义组件内部的事件, <mysearch @self_click="test()"> 如果需要他调用self_click事件,需要他在子定义组件里面 this.$emit("self_click")

两端分散对齐

提到这个别只想着浮动,还有 display: flex; justify-content: space-between;

// 文字不允许换行(单行文本) white-space: nowrap; // 溢出部分隐藏 overflow: hidden; // 文本溢出后,使用 ... 代替 text-overflow: ellipsis;

历史记录可使用uni-tag标签来写,方便,直接需要给 display: flex; flex-wrap: wrap;就行。 注意:

		<uni-tag :text="item" class="uni-tag"></uni-tag>
			</view>
		 display: flex;
		 flex-wrap: wrap;
	  .uni-tag {
		  // background-color: red;
		   margin-top: 5px;
		   margin-right: 5px;
		 }
	}

在上述代码中flex布局不会有效果,各标签还是各占一行,原因:他是对view标签做的循环,又在view标签上添加的flex布局,当然无效果,flex只对view标签内部的元素起效果。

一个页面中多次v-for循环时,建议index不要用同一个名字,常更换,不然可能报错。

reserve函数

				return [...this.once_search].reverse();
			}

reserve()函数会对数组直接影响到,可以另外建各储存[...this.once_search]

js————Set对象

// Set对象处理字符重复问题 const set = new Set(this.once_search) set.delete(this.input_text) set.add(this.input_text) this.once_search = Array.from(set); //将set对象转换为数组

this.once_search = JSON.parse(uni.getStorageSync("input_Value") || '[]')

JS方法

Number(num).toFixed(2)

Number()有时候用户传来的数据或许不是num类型,可以利用这个函数转换。 tofixed(2),保留数字的后两位

网数组存数据

以往存数据都是在后面push,但其实还有另一种方法

  // 为数据赋值:通过展开运算符的形式,进行新旧数据的拼接
  this.goodsList = [...this.goodsList, ...res.message.goods]

判断数据是否结束

当前的页码值 * 每页显示多少条数据 >= 总数条数 pagenum * pagesize >= total

cd && cd() 如果函数中cd函数存在,那么调用,不存在就撤

图片预览map函数筛选对象路径

// 实现轮播图的预览效果
preview(i) {
  // 调用 uni.previewImage() 方法预览图片
  uni.previewImage({
    // 预览时,默认显示图片的索引
    current: i,
    // 所有图片 url 地址的数组
    urls: this.goods_info.pics.map(x => x.pics_big)
  })
}

图片底部白边

当图片的父元素是一个块盒,且父元素高度自动即没有指定值,图片底部和父元素底边之间往往会出现空白。例,给图片加上dispale:block即可

富文本总图片有白边,可使用正则表达式

  // 使用字符串的 replace() 方法,为 img 标签添加行内的 style 样式,从而解决图片底部空白间隙的问题
  res.message.goods_introduce = res.message.goods_introduce.replace(/<img /g, '<img style="display:block;" ')

解决 .webp 格式图片在 ios 设备上无法正常显示的问题:

  // 使用字符串的 replace() 方法,将 webp 的后缀名替换为 jpg 的后缀名
  res.message.goods_introduce = res.message.goods_introduce.replace(/<img /g, '<img style="display:block;" ').replace(/webp/g, 'jpg')

解决数据还没传回,页面undefind问题

  1. 在商品详情数据请求回来之前,data 中 goods_info 的值为 {},因此初次渲染页面时,会导致 商品价格、商品名称 等闪烁的问题。
  2. 解决方案:判断 goods_info.goods_name 属性的值是否存在,从而使用 v-if 指令控制页面的显示与隐藏

商品底部导航

uni-goods-nav 是uniapp中的扩展组件,uni-ui中的。

uni-ui中的底部导航组件跳转时也需要 uni.switchTab,navigateTO无法跳转。

vuex的Module

单一状态树

单一状态树可以简单得理解成整个vuex中只有一个store对象。

module

在Vuex中使用modules字段声明模块。store中能定义的属性在module都能实现,在module中可以像store一样定义:state,mutations,actions,getters,modules(modules的镶嵌)

在modules中的mutation 和 getters其实和store并没有太大的异样,只不过modules中的getters可以多传入一个参数rootState,用于获取上一层的state。

同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,也意味这我们可以在全局命名空间中直接使用我们的 action、mutation 和 getter。 ,vuex模块内部的 action、mutation 和 getter 是注册在全局命名空间的,注册在全局命名空间自然能直接通过store来访问了。

为了模块更高的封装性

如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。

使用箭头函数的注意事项

const findResult = state.cart.find((x)=>{ return x.goods_id == goods.goods_id})//不存在则为undefind,存在即可返回商品信息 // const findResult = state.cart.find((x) => x.goods_id === goods.goods_id)

在写find函数时发现一直返回的都是undefind,这是因为我的箭头函数给了大花括号,但是没有把值return出去,总结:有括号需要return,无括号可不return。

通过find函数返回的值,也可以用它来改变原数组

			  
   console.log(findResult)
              if(!findResult)
              {
                      state.cart.push(goods)
              }else{
                      findResult.goods_count++;//通过find函数返回的值,也可以用它来改变原数组
              }

加入购物车模块概述

使用vuex管理数据,在商品详情页面确认点击事件后,调用store里面的函数,且接收一个商品信息作为参数,在里面判断参数是否存在,如果已存在仅作数量加一即可。 数据就以对象的形式存储在了store数据中,可使用getters写一个方法,在方法中将计算好的商品总数量return出去。 页面就可拿到购物车的总数量,用于显示,可使用watch监听该变量的值,如果发生变化就将它的新值赋值出去。 total(newVal){ const cart = this.options.find(item=>{ if(item.text === "购物车") { item.info = newVal console.log("进来啦") } }) }

vuex里面的this.store.dispatchthis.store.dispatch 和 this.store.commit用法以及区别

两个方法都是传值给vuex的mutation改变state

dispatch:异步操作,数据提交至 actions ,可用于向后台提交数据

this.$store.dispatch('isLogin', true);

commit:  同步操作,数据提交至 mutations ,可用于登录成功后读取用户信息写到缓存里

this.$store.commit('loginStatus', 1);

模块化store中存储数据

this.commit("m_cart/saveToStorage") 可以每次更改完需要存储数据就放到storage中

初始化拿取数据

state: () => ({ cart: JSON.parse(uni.getStorageSync("cart")) || [ ], }), 如果存储的是对象,千万记得解析,否则会包些摸不着头脑的错误。

解决初始化数据后,页面中使用watch监听器处理的内容,首次渲染不会加载?

  • 方法形式的watch监听器,首次不会渲染
  • 可更改为对象形式的,开启**immediate:true **后,首次加载也会渲染
				handler(newVal){
					const cart = this.options.find(item=>{
						if(item.text === "购物车")
						{
							item.info = newVal
							console.log("进来啦")
						}
					})
				},
				immediate:true //用此变量来声明监听器,首次加载是否立即调用监听器
			}

mixins

mixins(混入),官方的描述是一种分发 Vue 组件中可复用功能的非常灵活的方式,mixins 是一个 js 对象,

何时使用Mixins

当我们存在多个组件中的数据或者功能很相近时,我们就可以利用 mixins 将公共部分提取出来,通过 mixins 封装函数,组件调用他们是不会改变函数作用域外部的

  • 在 src 目录下创建一个 mixins 文件夹,在文件夹下新建一个 myMixins,js 文件。因为 mixins 是一个 js 对象,所以应该以对象的形式来定义 myMixins,在对象中可以和 vue 组件一样来定义 data、components、methods、created、computed 等属性,并通过 export 导出该对象。*

使用方法

export default {
		mixins:[auth],
		onLoad(e) {
			if(!e.params){
				return this.backToast()  //直接调用里面的方法
			}
	
		}
}

底部购物车数字徽标

uni.setTabBarBadge({
		  index: 2,
		  text: this.total+''
		})

该属性可设置,注意的是text必须是字符串类型,(加个引号就是了),

如果单是引入vuex拿到总数量,在购物车页面使用的话,当我们在其他页面三个页面时,购物车不数字徽标。如需要无论在那个页面都显示,可在每个页面都添加上这个,这样必然产生很多额外代码,写起来麻烦,这样即可使用vue的mixins特性

踩坑

 cart: JSON.parse(uni.getStorageSync('cart') || '[]')

在代码书写中JSON.parse(uni.getStorageSync('cart') )|| ['']报错,注意:['']它是默认有个字符串占了0位,后续数据都需要从1往上走。

JSON.parse(uni.getStorageSync('cart') )|| ‘[]’如果说你像这样写也是不行的,为什么呢? 因为当我们storage中没有cart键的时候,会报错,只要有数据就为真,这样在没有第一次的时候就不会是各空数组,程序也无法正常执行。

小程序 已被代码依赖分析忽略,无法被其他模块引用。你可根据控制台中的【代码依赖分析】告警信息修改代码,或关闭【过滤无依赖文件】功能

只需在“project.config.json”=>“setting”里面将"ignoreDevUnusedFiles"和"ignoreUploadUnusedFiles"都设置为 false,然后保存,重新编译即可。

"ignoreDevUnusedFiles": false,
 "ignoreUploadUnusedFiles": false,

流布局的align-items

display: flex;
align-items: center; 

uni-number-box注意事项 使用它时,如果输入不合法的值它就会显示NaN,可通过源码中添加两个判断代码做修改,当不合法时,将值默认设置为1

image.png

parseInt()这个方法如果里面的值不是数字,结果就是undefind。

filter

  state.cart = state.cart.filter(x => x.goods_id !== goods_id)

过滤出满足后面条件的元素。

chooseAddress()选择收货地址无效果

小程序提供的chooseAddress()方法显示张三地址页面,在manifest.json文件中,找到mp-weixin节点,添加上:

   "requiredPrivateInfos": [

         "chooseLocation",

         "getLocation", 

         "chooseAddress"

         ]
async chooseAddress() {
				// 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能
				//    返回值是一个数组:第 1 项为错误对象;第 2 项为成功之后的收货地址对象
				const  [err,succ]= await uni.chooseAddress()  
				// 2. 用户成功的选择了收货地址
				if (err === null && succ.errMsg === 'chooseAddress:ok') {
					  // 为 data 里面的收货地址对象赋值
					  this.address = succ
				}
			  }

此种写法()=>({})等同于()=>{return {}}

this.reAuth() 重新授权

this.reAuth() // 调用 this.reAuth() 方法,向用户重新发起授权申请 在旧版本中,调用chooseAddress会存在授权问题,一旦取消授权后,就无法正常使用,再次点击收获地址时,也不会重新授权。 可使用this.reAuth()重新授,但现在这个问题已经被官方解决,自动授权。

reduce 累加器

array.reduce(function(prev, cur, index, arr), init)

  • prev (上一次调用回调返回的值,或者是提供的初始值(initialValue))
  • cur (数组中当前被处理的元素)
  • index (当前元素在数组中的索引)
  • arr (调用的数组)
  • init (传递给函数的初始值)

数组中的每个值(从左到右)开始缩减,最终计算为一个值。

注意: reduce() 对于空数组是不会执行回调函数的。

注意事项

开启fixed脱离文档流,为了防止被覆盖,建议在使用时底部给相应高度的内边距

mutations返回结果undefind问题

actions允许返回结果,但mutations不允许返回结果(即使写了return也不生效)

报错store中定义的函数不存在

WAServiceMainContext.js?t=wechat&s=1679895066047&v=2.30.2:1 TypeError: this.updateAllGoodsState is not a function

原因如下:...mapMutations('m_cart',['updateAllGoodsState']), 使用的时候忘记加方括号了!!!

这一步必须加onshow,虽然监听器已经开启immediate但是没用。内容必须在加onshow里一份

image.png 在使用

token获取流程

通过getUserInfo获取到拿token的四位值(除code外)

	uni.	uni.getUserInfo({
					  success: function(res) {
						  console.log(res)
						}
					 })({
					  success: function(res) {
						  console.log(res)
						}
					 })

image.png

code通过wx.login()获取

Token是后端生成的,Token是服务端生成的一串字符串,以作客户端进行请求的一个标识, 黑马官方提供的接口已然失效:api-hmugo-web.itheima.net/api/public/…

用户头像昵称信息获取

developers.weixin.qq.com/miniprogram…

	 	<button class="avatar-wrapper" open-type="chooseAvatar"@chooseavatar="onChooseAvatar">
	 	  <image class="avatar" :src="avatarUrl || defaultAvatarUrl" mode="aspectFill"></image>
	 	</button> 
		
		<view class="text">
			<text class="text-item">昵称</text>
			<input type="nickname" class="weui-input" placeholder="请输入昵称" @blur="saveUserName"/>
		</view>
	 	
		<view class="btn">
			<button class="complete" @tap="submitInfo">完成</button>
		</view>
	 </view>
.select{
	display: flex;
	flex-direction: column;
	.avatar-wrapper{
		margin: 0;
		padding: 0;
		.avatar{
			width: 100px;
			height: 100px;
			border-radius: 15px;
			background-color: red;
		}
	}
	.text{
		display: flex;
		align-items: center;
		border-bottom: 1px solid #e6e6e6;
		.text-item{
		 
		}
		.weui-input{
			padding: 10px 0px;
			margin-left: 20px;
		}
	}
	.btn{
		position:relative;
		top:600rpx;
		.complete{
			width: 30%;
			margin: 0 auto;
			background-color: #c00000;
		}
	}
	
}

基础库

基础库是由用户手机的微信版本决定的,不同用户不一样。

基础库是什么?

基础库是小程序运行的必要环境,我们的开发主要就是面向基础库开发的。基础库封装了微信和手机的能力并提供给小程序使用,我们使用基础库提供的组件和API开发起来非常的方便。了解和使用基础库就揭开了小程序90%的神秘面纱。

基础库存在于我们的微信客户端中,它和微信一样,也会有其自己的版本,每个版本都会有一些新特性。微信官方为了使得基础库的版本和微信的版本同步,采取了一个微信版本对应一个基础库版本的方式。这就使得用户在更新最新版本的微信时也能更新至最新版本的基础库。带来的优点就是用户的基础库都是最新版本的。

小程序是运行在基础库上的吗?

普遍认为小程序是要运行在微信客户端上的,这么说似乎没什么问题,但非常不严谨。相比于微信客户端那么大的一个概念,客户端中绑定的基础库更像是小程序的SDK。他封装微信和手机功能的同时也使得开发者能够屏蔽微信版本和手机系统的差异。

如果从开发者的视野看,说小程序运行在基础库上也没什么问题。但这个开发者一定是个菜鸟(这篇文章初始版本的我),因为开发了一段时间后发现基础库并不是一块厚实的棉被,并不能把底层(微信和手机)的一切功能的包裹的严严实实。比如小程序的提示框在不同的微信版本则有不同的样式,又比如JS代码中的new Data("2018-2-8 11:34:00")在Android手机上能成功解析,在iOS上则会报错。这究竟是基础库的漏洞还是我们对小程序的理解不够深入呢?

上面的那两个问题已结说明“棉被理论”行不通了,因为一床漏风的棉被肯定不是好棉被。那究竟是什么原因会让JS代码在不同的手机系统上出现不同的结果呢?顺着这条线我先是找到了运行JS代码的浏览器内核,比如被大家嫌弃的IE浏览器内核Trident,以及Safari和Chrome使用的Webkit内核,还有火狐浏览器使用Gecko内核。继续了解后又发现了自己的无知,浏览器最核心其实分为两部分:渲染引擎(Rendering Engine)和JS引擎。我上面说的其实都是渲染引擎,真正解析JS代码的则是JS引擎。查了文档后才发现:

在 iOS 上,小程序的 javascript 代码是运行在 JavaScriptCore 中,是由 WKWebView 来渲染的,环境有 iOS8、iOS9、iOS10

在 Android 上,小程序的 javascript 代码是通过 X5 JSCore来解析,是由 X5 基于 Mobile Chrome 53/57 内核来渲染的

在 开发工具上, 小程序的 javascript 代码是运行在 nwjs 中,是由 Chrome Webview 来渲染的

也就是说基础库并不能解决小程序的一切问题。JS代码的解析和页面的渲染都要调用手机系统的浏览器内核。这么看来基础库只能算是一个调度资源的调度器,将小程序需要的资源经过简单的封装然后调度过去而已。

按照这样的思路,小程序并不是运行在基础库上的。如果非要下一个定义的话,可以说:小程序是通过基础库运行在手机及微信上的。难怪小程序保存到桌面也有点原生应用的感觉,当然这里面涉及到了一个JSBridge的技术,有待进一步的学习。

作者:形而下z
链接:www.jianshu.com/p/498dc852f…
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

延时3秒跳转页面

	  // 倒计时的秒数
	seconds: 3,
	// 定时器的 Id
	timer: null
                        
	delayNavigate() {
		  this.showTips(this.seconds)
		
		  // 1. 将定时器的 Id 存储到 timer 中
		  this.timer = setInterval(() => {
		    this.seconds--
		
		    // 2. 判断秒数是否 <= 0
		    if (this.seconds <= 0) {
		      // 2.1 清除定时器
		      clearInterval(this.timer)
		
		      // 2.2 跳转到 my 页面
		      uni.switchTab({
		        url: '/pages/my/my',
				success:()=> {
					this.updateRedirectInfo({
					   // 跳转的方式
						openType: 'switchTab',
						// 从哪个页面跳转过去的
						from: '/pages/cart/cart'
					})
				}
		      })
				
		      // 终止后续代码的运行(当秒数为 0 时,不再展示 toast 提示消息)
		      return
		    }
		
		    this.showTips(this.seconds)
		  }, 1000)
		},

bug合集

商品列表上拉刷新后,不断累加同一商品 可以一次性打开好多各计数器

收货地址功能

直接给元素绑定chooseAddress方法即可正常跳转

		  async chooseAddress() {
					// 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能
					//    返回值是一个数组:第 1 项为错误对象;第 2 项为成功之后的收货地址对象
					const  [err,succ]= await uni.chooseAddress()  
					// 2. 用户成功的选择了收货地址
					if (err === null && succ.errMsg === 'chooseAddress:ok') {
					  this.updateAddress(succ)
					}		  
			  }

map ()

对内容精选重构

cart.filter(x=>x.good_sate).map(x=>({
        goods.id:x.goods.id
        goods_number:x.goods_count
}))

微信支付

image.png

微信支付的流程

  1. 创建订单

    • 请求创建订单的 API 接口:把(订单金额、收货地址、订单中包含的商品信息)发送到服务器
    • 服务器响应的结果:订单编号
  2. 订单预支付

    • 请求订单预支付的 API 接口:把(订单编号)发送到服务器
    • 服务器响应的结果:订单预支付的参数对象,里面包含了订单支付相关的必要参数
  3. 发起微信支付

    • 调用 uni.requestPayment() 这个 API,发起微信支付;把步骤 2 得到的 “订单预支付对象” 作为参数传递给 uni.requestPayment() 方法
    • 监听 uni.requestPayment() 这个 API 的 successfailcomplete 回调函数

uni.requestPayment() 发起微信支付

调用 uni.requestPayment() 发起微信支付 const [err, succ] = await uni.requestPayment(payInfo)

分支的合并与提交

  1. 将 settle 分支进行本地提交:

    git add .
    git commit -m "完成了登录和支付功能的开发"
    
  2. 将本地的 settle 分支推送到码云:

    git push -u origin settle
    
  3. 将本地 settle 分支中的代码合并到 master 分支:

    git checkout master
    git merge settle
    git push
    
  4. 删除本地的 settle 分支:

    git branch -d settle
    

为什么要发布

  1. 小程序只有发布之后,才能被用户搜索并使用
  2. 开发期间的小程序为了便于调试,含有 sourcemap 相关的文件,并且代码没有被压缩,因此体积较大,不适合直接当作线上版本进行发布
  3. 通过执行 “小程序发布”,能够优化小程序的体积,提高小程序的运行性能