小娴商城学习总结

小娴商城学习总结

基于uniapp开发的微信小程序

内化总结

0、总体把控

首先搭建好导航栏 tabbar,把握整体框架逻辑。再一个一个tabbar页面搭建UI结构。
数据处理时,明确数据从哪来,在哪接收,在哪使用,是否需要存储到本地,通常使用vuex、props。
除此以外,插入图标icon、调用接口API时参考最新文档即可。
步骤:1、百度或csdn出需要使用的名称。2、去文档中看使用方法。
MDN中文文档
微信开放文档
uni-App

1、配置HbuilderX

2、uniApp框架

3、把项目运行到小程序

3.1 填写小程序的AppID
3.2 在HBuilder中配置微信开发者工具安装路径
3.3 在微信开发者工具中开启服务端口

4、使用Git管理项目

本地管理

在项目根目录中新建 .gitignore 忽略文件,并配置如下:

//忽略 node_modules 目录
/node_modules
/unpackage/dist

注意:由于我们忽略了 unpackage 目录中仅有的 dist 目录,因此默认情况下, unpackage 目录不会被 Git 追踪。
此时,为了让 Git 能够正常追踪 unpackage 目录,按照惯例,我们可以在 unpackage 目录下创建一个叫做 .gitkeep 的文件进行占位

打开终端,切换到项目根目录中,运行如下的命令,初始化本地 Git 仓库:
git init
将所有文件都加入到暂存区:
git add .
本地提交更新:
git commit -m "init project"

把项目托管到码云

1、生成并配置 SSH 公钥
2、创建空白的码云仓库
3、把本地项目上传到码云对应的空白仓库中

5、tabBar

5.1 在pages.json中配置新增tabBar节点
5.2 导航条样式效果在pages.json中的globalStyle节点中设置

6、配置网络请求

小程序平台不支持axios,原生的wx.request()不支持拦截器等全局定制的功能,因此使用@escook/request-miniprogram第三方包发起网络数据请求。
参考文档:request-miniprogram的npm文档

7、发送网络请求并接收返回数据

配置根路径:$http.baseUrl = 'https://www.域名.com'

解构将data从res返回对象中拿出来(可以先clog出res对象看看里面有什么)

const {data : res} = await uni.$http.get('/api/xxxxxxxxx')
	 //请求失败
     if (res.meta.status !== 200) {
          return uni.showToast({
          title: '数据请求失败!',
          duration: 1500,
          icon: 'none',
        })
      }
      //请求成功,为 data 中的数据赋值
      this.swiperList = res.message

8、动态绑定属性

动态绑定属性时记得加冒号

9、封装uni.$showMsg方法

在全局封装一个 uni.$showMsg() 方法,来简化 uni.showToast() 方法的调用。

main.js中为uni对象挂载自定义的$showMsg()方法

// 封装的展示消息提示的方法
uni.$showMsg = function (title = '数据加载失败!', duration = 1500) {
  uni.showToast({
    title,
    duration,
    icon: 'none',
  })
}

今后,在需要提示消息的时候,直接调用 uni.$showMsg() 方法即可:

async getSwiperList() {
   const { data: res } = await uni.$http.get('/api/public/v1/home/swiperdata')
   if (res.meta.status !== 200) return uni.$showMsg()
  // this.swiperList = res.message
}

10、分类页面两边滚动

scroll-view标签包裹,里面v-for渲染UI
选中项:控制data中active的数值,选中项的数值下标,判断active值来渲染样式。
返回的数据二级列表是一级列表的children,直接调用赋值即可。
scroll-top属性:滚动条到顶部的距离。

11、动态计算窗口的剩余高度

uni.getSystemInfoSync()获取当前系统的信息,里面的windowHeight就是窗口可用高度,计算后将结果wh存入data,在需要视图容器中渲染行内样式即可:style="{height: wh + 'px'}

12、v-if、v-else控制元素显示与否或切换

13、uni.navigateTo跳转页面

14、自定义组件的复用

在子组件中定义props结点,定义接受的参数(类型和默认值)。

  // 背景颜色
  bgcolor: {
    type: String,
    default: '#C00000'
  },

然后在使用子组件时,动态绑定style属性,传入本次样式参数。

<view class="my-search-container" :style="{'background-color': bgcolor}"/>

15、搜索框

15.1 吸顶

  // 设置定位效果为“吸顶”
  position: sticky;
  // 吸顶的“位置”
  top: 0;
  // 提高层级,防止被轮播图覆盖
  z-index: 999;

15.2 搜索框自动获取焦点

改源码:uni_modules -> uni-search-bar -> uni-search-bar.vue

  show: true,
  showSync: true,

15.3 搜索框防抖
input事件:value发生改变时触发事件并返回最新value
设置定时器使如果 500 毫秒内,没有触发新的输入事件,则为搜索关键词赋值。
防止频繁搜索。

15.4 查询搜索建议列表
判断输入的关键词是否为空,是的话直接return,非空的话发送请求。

15.5 存储搜索历史
push进存储有搜索历史的数组,末尾追加。

数组顺序反转显示

定义一个计算属性 historys,将 historyList 数组 reverse 反转之后,就是此计算属性的值:

computed: {
  historys() {
    // 注意:由于数组是引用类型,所以不要直接基于原数组调用 reverse 方法,以免修改原数组中元素的顺序
    // 而是应该新建一个内存无关的数组,再进行 reverse 反转
    return [...this.historyList].reverse()
  }
}
数组去重(set对象)
  // 1. 将 Array 数组转化为 Set 对象
  const set = new Set(this.historyList)
  // 2. 调用 Set 对象的 delete 方法,移除对应的元素
  set.delete(this.kw)
  // 3. 调用 Set 对象的 add 方法,向 Set 中添加元素
  set.add(this.kw)
  // 4. 将 Set 对象转化为 Array 数组
  this.historyList = Array.from(set)

16、组件的自定义事件

子组件中用this.$emit('xxx',OBJECT)触发父组件绑定的自定义事件名,从而调用父组件自定义事件的处理函数。
实现子传父。

子:this.$emit(‘myclick’)
父:<mysearch @myclick=“gotoSearch”/>

17、定义请求参数对象

// 请求参数对象
    queryObj: {
      // 查询关键词
      query: '',
      // 商品分类Id
      cid: '',
      // 页码值
      pagenum: 1,
      // 每页显示多少条数据
      pagesize: 10
    }

发送请求传参时直接传queryObj。

// 将页面参数转存到 this.queryObj 对象中
this.queryObj.query = options.query || ''

上面这种写法等号右边值得学习:前者为空则赋后者的值,有前者就用前者。

18、 使用过滤器处理价格

和 data 节点平级,声明 filters 过滤器节点如下:

filters: {
  // 把数字处理为带两位小数点的数字
  tofixed(num) {
    return Number(num).toFixed(2)
  }
}

在渲染商品价格的时候,通过管道符 | 调用过滤器:

<!-- 商品价格 -->
<view class="goods-price">¥{{goods.goods_price | tofixed}}</view>

19、设置节流阀

触底加载数据时,为了防止发起额外的请求,设置一个flag用于控制是否加载下一个页面,保证这个页面加载完成后才可以加载下一个页面。若加载下个页面前发现已有正在进行的请求,则直接return。

在 data 中定义 isloading 节流阀如下:

data() {
  return {
    //是否正在请求数据
    isloading: false
  }
}

修改 getGoodsList 方法,在请求数据前后,分别打开和关闭节流阀:

// 获取商品列表数据的方法
async getGoodsList() {
  // ** 打开节流阀
  this.isloading = true
  // 发起请求
  const { data: res } = await uni.$http.get('/api/public/v1/goods/search', this.queryObj)
  // ** 关闭节流阀
  this.isloading = false

  // 省略其它代码...
}

在 onReachBottom 触底事件处理函数中,根据节流阀的状态,来决定是否发起请求:

// 触底的事件
onReachBottom() {
  // 判断是否正在请求其它数据,如果是,则不发起额外的请求
  if (this.isloading) return

  this.queryObj.pagenum += 1
  this.getGoodsList()
}

20、富文本组件解析html结构

参考博客
注:ios不支持webp格式的图片,要用正则将其改为jpg格式。
.replace(/webp/g, 'jpg')

21、用组件实现商品导航

uni-goods-nav组件,使用时传入对应参数即可。
<uni-goods-nav :fill="true" :options="options" :buttonGroup="buttonGroup" @click="onClick" @buttonClick="buttonClick" />
组件库

22、Vuex的使用

1、初始化 Store 的实例对象:

// 1. 导入 Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 2. 将 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 3. 创建 Store 的实例对象
export default new Vuex.Store({
  // TODO:挂载 store 模块
  modules: {
})

在 main.js 中导入 store 实例对象并挂载到 Vue 的实例上:

// 1. 导入 store 的实例对象
import store from './store/store.js'

const app = new Vue({
  ...App,
  // 2. 将 store 挂载到 Vue 实例上
  store,
})
app.$mount()

2、在 cart.js 中,初始化如下的 vuex 模块:

export default {
  // 为当前模块开启命名空间
  namespaced: true,

  // 模块的 state 数据
  state: () => ({
    cart: [],
  }),

  // 模块的 mutations 方法
  mutations: {},

  // 模块的 getters 属性
  getters: {},
}

在 store/store.js 模块中,导入并挂载购物车的 vuex 模块:

// 1. 导入购物车的 vuex 模块
import moduleCart from './cart.js'

export default new Vuex.Store({
  // TODO:挂载 store 模块
  modules: {
    // 2. 挂载购物车的 vuex 模块,模块内成员的访问路径被调整为 m_cart,例如:
    //    购物车模块中 cart 数组的访问路径是 m_cart/cart
    m_cart: moduleCart,
  },
})

3、在商品详情页中使用 Store 中的数据

// 从 vuex 中按需导出 mapState 辅助方法
import { mapState } from 'vuex'

export default {
  computed: {
    // 调用 mapState 方法,把 m_cart 模块中的 cart 数组映射到当前页面中,作为计算属性来使用
    // ...mapState('模块的名称', ['要映射的数据名称1', '要映射的数据名称2'])
    ...mapState('m_cart', ['cart']),//  m_cart模块下的cart数组
  },
}

23、持久化存储到本地

// 将购物车中的数据持久化存储到本地
saveToStorage(state) {
   uni.setStorageSync('cart', JSON.stringify(state.cart))
}
addToCart(state, goods) {
// 通过 commit 方法,调用 m_cart 命名空间下的 saveToStorage 方法
   this.commit('m_cart/saveToStorage')
}

修改 cart.js 模块中的 state 函数,读取本地存储的购物车数据,对 cart 数组进行初始化:

state: () => ({
   cart: JSON.parse(uni.getStorageSync('cart') || '[]')
}),

24、watch设置初次加载就侦听

在watch事件中加入:

	// immediate 属性用来声明此侦听器,是否在页面初次加载完毕后立即调用
      immediate: true

25、一显示页面就加载徽标

onShow() {
  // 在页面刚展示的时候,设置数字徽标
  this.setBadge()
}
methods: {
   setBadge() {
      // 调用 uni.setTabBarBadge() 方法,为购物车设置右上角的徽标
      uni.setTabBarBadge({
         index: 2, // 索引
         text: this.total + '' // 注意:text 的值必须是字符串,不能是数字
      })
   }
}

26、混入mixin

mixin混入
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步 在mixin.js文件中定义混入,例如:
export const 混合名 = {
data(){…},
methods:{…},

}
第二步 使用混入,例如:
(1)全局混入:Vue.mixin(xxx)
(2)局部混入: mixins:[‘xxx’]

//单文件混入
	export default {
		computed: {},
		methods: {},
		onShow() {},
		watch: {}
	}

27、单选按钮radio

动态渲染check属性
uni-number-box组件选择数量

28、滑动删除

<!-- uni-swipe-action 是最外层包裹性质的容器 -->
<uni-swipe-action>
  <block v-for="(goods, i) in cart" :key="i">
    <!-- uni-swipe-action-item 可以为其子节点提供滑动操作的效果。需要通过 options 属性来指定操作按钮的配置信息 -->
    <uni-swipe-action-item :options="options" @click="swipeActionClickHandler(goods)">
      <my-goods :goods="goods" :show-radio="true"></my-goods>
    </uni-swipe-action-item>
  </block>
</uni-swipe-action>

在 data 节点中声明 options 数组,用来定义操作按钮的配置信息:

data() {
  return {
    options: [{
      text: '删除', // 显示的文本内容
      style: {
        backgroundColor: '#C00000' // 按钮的背景颜色
      }
    }]
  }
}

注:删除用filter

29、小程序收货地址

    // 1. 调用小程序提供的 chooseAddress() 方法,即可使用选择收货地址的功能
    //    返回值是一个数组:第 1 项为错误对象;第 2 项为成功之后的收货地址对象
    const [err, succ] = await uni.chooseAddress().catch(err => err)

    // 2. 用户成功的选择了收货地址
    if (err === null && succ.errMsg === 'chooseAddress:ok') {
      // 为 data 里面的收货地址对象赋值
      this.address = succ
    }
  }
computed: {
// 收货详细地址的计算属性
addstr() {
  if (!this.address.provinceName) return ''

  // 拼接 省,市,区,详细地址 的字符串并返回给用户
  return this.address.provinceName + this.address.cityName + this.address.countyName + this.address.detailInfo
}
}

30、勾选/反选

    // !this.isFullCheck 表示:当前全选按钮的状态取反之后,就是最新的勾选状态
    this.updateAllGoodsState(!this.isFullCheck)

31、微信登录

调用getUserProfile()接口接收返回对象,头像和昵称在rawData里取。

		<!-- 可以从 getuserinfo 事件处理函数的形参中,获取到用户的基本信息 -->
		<button type="primary" class="btn-login" @click="getUserProfile"> 一键登录 </button>
			// 调用 uni.getUserProfile接口,返回微信用户信息userInfo
			getUserProfile() {
				uni.getUserProfile({
					desc:'用于用户登录账号',
					success: (res) => {
						this.updateUserInfo(res.userInfo)
						this.getToken(res)
					},
					fail: (res) => {
						return uni.$showMsg('您取消了登录授权')
					}
				})
			},

当获取到了微信用户的基本信息之后,还需要进一步调用login()接口,从而换取登录成功之后的 Token 字符串。方便后续操作。

			// 调用登录接口,换取永久的 token
			async getToken(info) {
				// 调用微信登录接口
				const [err,res] = await uni.login().catch(err => err)
				// 判断是否 uni.login() 调用失败
				if (err || res.errMsg !== 'login:ok') return uni.$showError('登录失败!')

				// 准备参数对象
				const query = {
				    code: res.code,
				    encryptedData: info.encryptedData,
				    iv: info.iv,
				    rawData: info.rawData,
				    signature: info.signature
				}
				
				// 换取 token
				const { data: loginResult } = await uni.$http.post('https://www.fastmock.site/mock/9552649a5d76835b4565361bac1efe1f/api/token', query)
				if (loginResult.meta.status !== 200) return uni.$showMsg('登录失败!')
				uni.$showMsg('登录成功')
				this.updateToken(loginResult.message.token)
			   //this.navigateBack()
			},

32、微信支付 (一)

32.1 未登录三秒后自动跳转

// 延迟导航到 my 页面
delayNavigate() {
  // 把 data 中的秒数重置成 3 秒
  this.seconds = 3
  this.showTips(this.seconds)

  this.timer = setInterval(() => {
    this.seconds--

    if (this.seconds <= 0) {
      clearInterval(this.timer)
      uni.switchTab({
        url: '/pages/my/my'
      })
      return
    }

    this.showTips(this.seconds)
  }, 1000)
}

32.2 登录后跳转回支付页面

核心实现思路:在自动跳转到登录页面成功之后,把返回页面的信息存储到 vuex 中,从而方便登录成功之后,根据返回页面的信息重新跳转回去。返回页面的信息对象,主要包含 { openType, from } 两个属性,其中 openType 表示以哪种方式导航回之前的页面;from 表示之前页面的 url 地址。

	state: () => ({
	  // 重定向的 object 对象 { openType, from }
	  redirectInfo: null
}),
	mutations: {
	  // 更新重定向的信息对象
	  updateRedirectInfo(state, info) {
	    state.redirectInfo = info
	  }
	}
	uni.switchTab({
	        url: '/pages/my/my',
	        // 页面跳转成功之后的回调函数
	        success: () => {
	          // 调用 vuex 的 updateRedirectInfo 方法,把跳转信息存储到 Store 中
	          this.updateRedirectInfo({
	            // 跳转的方式
	            openType: 'switchTab',
	            // 从哪个页面跳转过去的
	            from: '/pages/cart/cart'
	          })
	        }
	      })

拿到token之后跳回去

    // 调用登录接口,换取永久的 token
   async getToken(info) {
     // 判断 vuex 中的 redirectInfo 是否为 null
     // 如果不为 null,则登录成功之后,需要重新导航到对应的页面
     this.navigateBack()
}
	// 返回登录之前的页面
	navigateBack() {
	  // redirectInfo 不为 null,并且导航方式为 switchTab
	  if (this.redirectInfo && this.redirectInfo.openType === 'switchTab') {
	    // 调用小程序提供的 uni.switchTab() API 进行页面的导航
	    uni.switchTab({
	      // 要导航到的页面地址
	      url: this.redirectInfo.from,
	      // 导航成功之后,把 vuex 中的 redirectInfo 对象重置为 null
	      complete: () => {
	        this.updateRedirectInfo(null)
	      }
	    })
	  }
	}

32.3 给提示信息加透明遮罩

// 为页面添加透明遮罩,防止点击穿透(scss)
    mask: true,

33、微信支付(二)

33.1 判断是否需要权限,在请求头中添加 Token 身份认证的字段

原因说明:只有在登录之后才允许调用支付相关的接口,所以必须为有权限的接口添加身份认证的请求头字段
打开项目根目录下的 main.js,改造 $http.beforeRequest 请求拦截器中的代码。

	// 请求开始之前做一些事情
	$http.beforeRequest = function(options) {
		  // 判断请求的是否为有权限的 API 接口
		  if (options.url.indexOf('/my/') !== -1) {
			  // 为请求头添加身份认证字段
			  options.header = {
				  // 字段的值可以直接从 vuex 中进行获取
				  Authorization: store.state.m_user.token,
			 }
	  }
}

33.2 微信支付的流程

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

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

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

	// 1. 创建订单
	  // 1.1 组织订单的信息对象
	  const orderInfo = {
	    // 开发期间,注释掉真实的订单价格,
	    // order_price: this.checkedGoodsAmount,
	    // 写死订单总价为 1 分钱
	    order_price: 0.01,
	    consignee_addr: this.addstr,
	    goods: this.cart.filter(x => x.goods_state).map(x => ({ goods_id: x.goods_id, goods_number: x.goods_count, goods_price: x.goods_price }))
	  }
	  // 1.2 发起请求创建订单
	  const { data: res } = await uni.$http.post('/api/public/v1/my/orders/create', orderInfo)
	  if (res.meta.status !== 200) return uni.$showMsg('创建订单失败!')
	  // 1.3 得到服务器响应的“订单编号”
	  const orderNumber = res.message.order_number
 // 2. 订单预支付
  // 2.1 发起请求获取订单的支付信息
  const { data: res2 } = await uni.$http.post('/api/public/v1/my/orders/req_unifiedorder', { order_number: orderNumber })
  // 2.2 预付订单生成失败
  if (res2.meta.status !== 200) return uni.$showError('预付订单生成失败!')
  // 2.3 得到订单支付相关的必要参数
  const payInfo = res2.message.pay
 // 3. 发起微信支付
   // 3.1 调用 uni.requestPayment() 发起微信支付
   const [err, succ] = await uni.requestPayment(payInfo)
   // 3.2 未完成支付
   if (err) return uni.$showMsg('订单未支付!')
   // 3.3 完成了支付,进一步查询支付的结果
   const { data: res3 } = await uni.$http.post('/api/public/v1/my/orders/chkOrder', { order_number: orderNumber })
   // 3.4 检测到订单未支付
   if (res3.meta.status !== 200) return uni.$showMsg('订单未支付!')
   // 3.5 检测到订单支付完成
   uni.showToast({
     title: '支付完成!',
     icon: 'success'
   })

总结:

熟悉了uniApp的开发流程和框架,熟悉了向服务器发送请求,熟悉了vuex,熟悉了小程序的登录支付接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值