收货地址地理定位功能介绍使用前需要开通接口权限使用方法view classlocation bindtaponLocation van-icon namelocation-o color#777 / text定位/text /viewapp.json中声明字段requiredPrivateInfos: [getLocation], permission: { scope.userLocation: { desc: 获取地理位置信息用于填写收货地址 } }// 获取用户地理位置信息 async onLocation() { // 获取当前的地理位置精度、纬度、高度等 const res await wx.getLocation() console.log(res) }第二种requiredPrivateInfos: [getLocation, chooseLocation]chooseLocation不需要permission字段、// 获取用户地理位置信息 async onLocation() { const res await wx.chooseLocation() console.log(res) }拒绝授权和授权完整的流程// 拒绝授权后的处理方案 获取授权状态 async onLocation() { // 调用 wx.getSetting 获取用户所有的授权信息查询到用户是否授权了位置信息 // authSetting 只包含了小程序向用户请求的所有的权限同时包含了授权的结果(true、false) const { authSetting } await wx.getSetting() // scope.userLocation 用户是否授权获取了地理位置信息 // 如果小程序没有向用户发起过授权请求authSetting 中没有 scope.userLocation 属性 // 如果用户点击了允许授权返回值就是 true // 如果用户点击了拒绝授权返回值就是 false console.log(authSetting[scope.userLocation]) }// 拒绝授权后的处理方案-整体的逻辑 async onLocation() { // authSetting 获取小程序已经向用户申请的权限并且会返回授权的结果 const { authSetting } await wx.getSetting() // scope.userLocation 用户是否授权小程序获取位置信息 console.log(authSetting[scope.userLocation]) // 判断用户是否拒绝了授权 if (authSetting[scope.userLocation] false) { // 用户之前拒绝授权获取位置信息用户再次发起了授权 // 这时候需要使用一个弹框询问用户是否进行授权 const modalRes await wx.modal({ title: 授权提示, content: 需要获取地理位置信息请确认授权 }) // 如果用户点击了取消说明用户拒绝了授权需要给用户进行提示 if (!modalRes) return wx.toast({ title: 您拒绝了授权 }) // 如果用户点击了确定说明用户同意授权需要打开微信客户端小程序授权页面 const { authSetting } await wx.openSetting() // 如果用户没有更新授权信息需要给用户提示授权失败 if (!authSetting[scope.userLocation]) return wx.toast({ title: 授权失败 }) // 如果用户更新了授权信息说明用户同意授权获取位置信息 try { const locationRes await wx.getLocation() console.log(locationRes) } catch (error) { wx.toast({ title: 您拒绝授权获取位置信息 }) } } else { try { const locationRes await wx.getLocation() console.log(locationRes) } catch (error) { wx.toast({ title: 您拒绝授权获取位置信息 }) } } }view classbox !-- 如果给按钮添加 open-type 属性属性设置为 openSetting就会打开授权页面 -- !-- 就会打开微信客户端小程序授权页面 -- button typeprimary open-typeopenSetting打开授权页面/button /view开通腾讯位置服务腾讯位置服务逆地址解析获取地址// 引入 QQMapWX 核心类 import QQMapWX from ../../../../libs/qqmap-wx-jssdk// 获取用户地理位置信息 async onLocation() { // 获取当前的地理位置经度、纬度、高度等 // const res await wx.getLocation() // console.log(res) // 打开地图让用户选择地理位置 // latitude 纬度、longitude 经度、name 搜索的地点 const { latitude, longitude, name } await wx.chooseLocation() // 使用 reverseGeocoder 方法进行逆地址解析 this.qqmapwx.reverseGeocoder({ location: { longitude, latitude }, success: (res) { // 获取省市区、省市区编码 const { adcode, province, city, district } res.result.ad_info // 获取街道、门牌街道、门牌 可能为空 const { street, street_number } res.result.address_component // 获取标准地址 const { standard_address } res.result.formatted_addresses // 对获取的数据进行格式化、组织然后赋值给 data 中的字段 this.setData({ // 省 provinceName: province, // 如果是省前 2 位有值后面 4 位是 0 provinceCode: adcode.replace(adcode.substring(2, 6), 0000), // 市 cityName: city, // 如果是市前 4 位有值后面 2 位是 0 cityCode: adcode.replace(adcode.substring(4, 6), 00), // 区 // 东莞市、中山市、儋州市、嘉峪关市 因其下无区县级 districtName: district, districtCode: district adcode, // 详细地址以及完整地址在以后开发中根据产品的需求来进行选择、处理即可 // 组织详细地址 address: street street_number name, // 组织完整地址 fullAddress: standard_address name }) } }) }, onLoad() { // 对核心类 QQMapWX 进行实例化 this.qqmapwx new QQMapWX({ // key 要使用自己申请的 key //在进行开发的时候如果发现 key 只能使用一次需要在腾讯位置服务后台配置额度 key: S5CBZ-TQXCB-L73UJ-J6VJA-FXS53-JNBY3 }) }表单验证-async-validator基本使用安装指令npm i async-validator需要构建npm// 从 async-validator 中引入构造函数 import Schema from async-validator Page({ data: { name: }, // 对数据进行验证 onValidator() { // 定义验证规则 const rules { // key 验证规则的名字名字需要和验证的数据保持一致 name: [ // required 是否是必填项 // message 如果验证失败提示错误内容 { required: true, message: name 不能为空 }, // type 验证数据的类型 { type: string, message: name 不是字符串 }, // min 最少位数max 最大位数 { min: 2, max: 3, message: 名字最少 2 个字最多是 3 个字 } // pattern 使用正则对数据进行验证 // { pattern: , message: } // validator 自定义验证规则 // { validator: () {} } ] } // 需要对构造函数进行实例化同时传入验证规则 const validator new Schema(rules) // 需要调用 validate 实例方法对数据进行验证 // 第一个参数需要验证的数据要求数据是一个对象 // validate 方法只会验证和验证规则同名的字段 // 第二个参数是一个回调函数 validator.validate(this.data, (errors, fields) { // 如果验证成功errors 是一个 null // 如果验证失败errors 是一个数组数组每一项是错误信息 // fields 是需要验证的属性属性值是一个数组数组中也包含着错误信息 if (errors) { console.log(验证失败) console.log(errors) console.log(fields) } else { console.log(验证成功) } }) } })新增收货地址表单验证正则// 验证收货人是否只包含大小写字母、数字和中文字符 const nameRegExp /^[a-zA-Z\d\u4e00-\u9fa5]$/; // 验证手机号是否符合中国大陆手机号码的格式 const phoneReg /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\d|9\d)\d{8}$/;// 导入 async-validator 对参数进行验证 import Schema from async-validator // 对新增收货地址请求参数进行验证 validatorAddress(params) { // 验证收货人是否只包含大小写字母、数字和中文字符 const nameRegExp /^[a-zA-Z\d\u4e00-\u9fa5]$/ // 验证手机号是否符合中国大陆手机号码的格式 const phoneReg /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\d|9\d)\d{8}$/ // 创建验证规则 const rules { name: [ { required: true, message: 请输入收货人姓名 }, { pattern: nameRegExp, message: 收货人姓名不合法 } ], phone: [ { required: true, message: 请输入收货人手机号 }, { pattern: phoneReg, message: 收货人手机号不合法 } ], provinceName: { required: true, message: 请选择收货人所在地区 }, address: { required: true, message: 请输入详细地址 } } // 传入验证规则进行实例化 const validator new Schema(rules) // 调用实例方法对请求参数进行验证 // 注意我们希望将验证结果通过 Promise 的形式返回给函数的调用者 return new Promise((resolve) { validator.validate(params, (errors) { if (errors) { // 如果验证失败需要给用户进行提示 wx.toast({ title: errors[0].message }) // 如果属性值是 false说明验证失败 resolve({ valid: false }) } else { // 如果属性值是 true说明验证成功 resolve({ valid: true }) } }) }) }实现新增收货地址实现步骤收货地址列表渲染实现步骤// 导入接口 API 函数 import { reqAddressList } from ../../../../api/address Page({ // 页面的初始数据 data: { addressList: [] }, // 去编辑页面 toEdit() { wx.navigateTo({ url: /modules/settingModule/pages/address/add/index }) }, // 获取收货地址列表数据 async getAddressList() { const { data: addressList } await reqAddressList() this.setData({ addressList }) }, onShow() { this.getAddressList() } })实现更新收货地址功能// 用来处理更新相关的逻辑 async showAddressInfo(id) { // 判断是否存在 id如果不存在 id就不执行后续的逻辑 if (!id) return // 将 id 挂载到当前页面的实例(this)上方便在多个方法中使用 id this.addressId id // 调用接口 API 函数来获取需要更新的收货地址详情 const { data } await reqAddressInfo(id) // 将详情数据进行赋值赋值以后页面上就会回显要更新的地址信息 this.setData(data) }实现删除收货地址view slotright classvan-swipe-cell__right bindtapdelAddress >// 导入接口 API 函数 import { reqAddressList, reqDelAddress } from ../../../../api/address async delAddress(event) { // 解构传递的 id const { id } event.currentTarget.dataset // 询问用户是否确认删除 const modalRes await wx.modal({ content: 您确认删除该收货地址吗 }) // 如果用户确认删除需要调用接口 API // 同时需要给用户提示并且要重新获取收货地址列表 if (modalRes) { await reqDelAddress(id) wx.toast({ title: 收货地址删除成功 }) this.getAddressList() } }拓展-删除滑块SwipeCell 自动收起export const swipeCellBehavior Behavior({ data: { swipeCellQueue: [] // 用来存储滑动单元格实例 }, methods: { // 当用户打开滑块时触发 swipeCellOpen(event) { // 获取单元格实例 const instance this.selectComponent(#${event.target.id}) // 将实例追加到数组中 this.data.swipeCellQueue.push(instance) }, // 给页面绑定点击事件 onSwipeCellPage() { this.onSwipeCellCommonClick() }, // 点击滑动单元格时触发的事件 onSwipeCellClick() { this.onSwipeCellCommonClick() }, // 关掉滑块统一的逻辑 onSwipeCellCommonClick() { // 需要对单元格实例数组进行遍历遍历以后获取每一个实例让每一个实例调用 close 方法即可 this.data.swipeCellQueue.forEach((instance) { instance.close() }) // 将滑动单元格数组重置为空 this.data.swipeCellQueue [] } } })使用:import { swipeCellBehavior } from ../../../../behaviors/swipeCell Page({ behaviors: [swipeCellBehavior], })商品管理配置商品管理分包-封装商品模块接口api{ root: modules/goodModule, name: goodModule, pages: [ pages/goods/list/list, pages/goods/detail/detail ] }, preloadRule: { pages/settings/settings: { network: all, packages: [settingModule] }, pages/category/category: { network: all, packages: [goodModule] } }import http from ../utils/http /** * description 获取商品列表数据 * param {Object} param { page, limit, category1Id, category2Id } * returns Promise */ export const reqGoodsList ({ page, limit, ...data }) { return http.get(/goods/list/${page}/${limit}, data) } /** * description 获取商品的详情 * param {*} goodsId 商品的 id * returns Promise */ export const reqGoodsInfo (goodsId) { return http.get(/goods/${goodsId}) }商品列表-准备列表请求参数// pages/goods/list/index.js Page({ // 页面的初始数据 data: { goodsList: [], // 商品列表数据 isFinish: false, // 判断数据是否加载完毕 // 商品列表请求参数 requestData: { page: 1, // 页码 limit: 10, // 每页请求的条数 category1Id: , // 一级分类 id category2Id: // 二级分类 id } }, onLoad(options) { // Object.assign 用来合并对象后面对象的属性会往前进行合并 Object.assign(this.data.requestData, options) } })商品列表-获取商品列表数据并渲染实现步骤view classcontainer !-- 商品列表功能 -- view classgoods-list wx:if{{ goodsList.length }} block wx:for{{ goodsList }} wx:keyid goods-card goodItem{{ item }}/goods-card /block !-- 数据是否加载完毕 -- view classfinish hidden{{ !isFinish }}数据加载完毕~~~/view /view !-- 商品为空的时候展示的结构 -- van-empty wx:else description该分类下暂无商品去看看其他商品吧~ van-button round typedanger classbottom-button bindtapgotoBack 查看其他商品 /van-button /van-empty /viewimport { reqGoodsList } from ../../../../api/goods Page({ // 页面的初始数据 data: { goodsList: [], // 商品列表数据 total: 0, // 数据总条数 isFinish: false, // 判断数据是否加载完毕 // 商品列表请求参数 requestData: { page: 1, // 页码 limit: 10, // 每页请求的条数 category1Id: , // 一级分类 id category2Id: // 二级分类 id } }, // 获取商品列表数据 async getGoodsList() { const { data } await reqGoodsList(this.data.requestData) this.setData({ goodsList: data.records, total: data.total }) }, onLoad(options) { // Object.assign 用来合并对象后面对象的属性会往前进行合并 Object.assign(this.data.requestData, options) // 调用获取商品列表数据的方法 this.getGoodsList() } })商品列表-实现上拉加载更多功能分析实现步骤// 获取商品列表数据 async getGoodsList() { const { data } await reqGoodsList(this.data.requestData) this.setData({ goodsList: [...this.data.goodsList, ...data.records], total: data.total }) }, // 监听到页面的上拉操作 onReachBottom() { // 解构数据 const { page } this.data.requestData // 页码 1 this.setData({ requestData: { ...this.data.requestData, page: page 1 } }) // 重新获取商品列表 this.getGoodsList() }商品列表-判断数据是否加载完毕// 监听到页面的上拉操作 onReachBottom() { // 解构数据 const { goodsList, total, requestData } this.data const { page } requestData // 开始让 goodsList 长度 和 total 进行对比 // 如果数据相等商品列表已经加载完毕如果数据已经加载完毕 if (goodsList.length total) { this.setData({ isFinish: true }) return } // 页码 1 this.setData({ requestData: { ...this.data.requestData, page: page 1 } }) // 重新获取商品列表 this.getGoodsList() }商品列表-节流阀进行列表节流// 页面的初始数据 data: { goodsList: [], // 商品列表数据 total: 0, // 数据总条数 isFinish: false, // 判断数据是否加载完毕 isLoading: false, // 判断数据是否加载完毕 }, // 获取商品列表数据 async getGoodsList() { // 在请求发送之前需要将 isLoading 设置为 true表示请求正在发送中 this.data.isLoading true // 发送请求 const { data } await reqGoodsList(this.data.requestData) this.setData({ goodsList: [...this.data.goodsList, ...data.records], total: data.total }) // 在请求结束以后需要将 isLoading 设置为 false表示请求已经结束 this.data.isLoading false }, // 监听到页面的上拉操作 onReachBottom() { // 解构数据 const { goodsList, total, requestData, isLoading } this.data const { page } requestData // 判断 isLoading 状态 // 如果状态等于 true说明请求正在发送中如果请求正在发送中就不请求下一页数据 if (isLoading) return // 让 goodsList 长度 和 total 进行对比 // 如果数据相等商品列表已经加载完毕 if (goodsList.length total) { this.setData({ isFinish: true }) return } // 页码 1 this.setData({ requestData: { ...this.data.requestData, page: page 1 } }) // 重新获取商品列表 this.getGoodsList() }商品列表-实现下拉刷新功能enablePullDownRefresh: true, backgroundColor: #f7f4f8, backgroundTextStyle: dark// 监听页面的下拉刷新操作 onPullDownRefresh() { // 将数据进行重置 this.setData({ goodsList: [], total: 0, isFinish: false, requestData: { ...this.data.requestData, page: 1 } }) // 使用最新的参数发送请求 this.getGoodsList() // 手动关闭下拉刷新的效果 wx.stopPullDownRefresh() }商品详情渲染预览思路分析实现步骤import { reqGoodsInfo } from ../../../../api/goods Page({ data: { goodsInfo: {} // 商品详情数据 }, // 获取商品详情的数据 async getGoodsInfo() { const { data: goodsInfo } await reqGoodsInfo(this.goodsId) this.setData({ goodsInfo }) }, onLoad(options) { // 接收传递的商品 ID并且将商品 ID 挂载到 this 上面 this.goodsId options.goodsId // 调用获取商品详情数据的方法 this.getGoodsInfo() } })wx.previewImage({ current: , // 当前显示图片的 http 链接 urls: [] // 需要预览的图片 http 链接列表 })实现步骤!-- 商品大图 -- view classbanner-img image classimg src{{ goodsInfo.imageUrl }} bindtappreviewImage / /view// 全屏预览图片 previewImage() { wx.previewImage({ urls: this.data.goodsInfo.detailList }) }优化-小程序配置路径别名优化访问路径在 Vue 中可以使用 符号指向源码目录简化路径小程序也提供了配置的方式。 在小程序中可以在 app.json 中使用 resolveAlias 配置项用来自定义模块路径的映射规则。{ resolveAlias: { /*: /* } }// 导入接口 API 函数 import { reqAddressList, reqDelAddress } from /api/address 注意事项1. resolveAlias 进行的是路径匹配其中的 key 和 value 须以 /* 结尾2. 如果在 project.config.json 中指定了 miniprogramRoot则 /* 指代的根目录是 miniprogramRoot 对应购物车封装购物车接口apiimport http from /utils/http /** * description [商品详情加入购物车] 以及 [购物车更新商品数量] * param { Object } param { goodsId: 商品 ID, count: 购买数量, blessing: 祝福语 } */ export const reqAddCart ({ goodsId, count, ...data }) { return http.get(/cart/addToCart/${goodsId}/${count}, data) } /** * description 获取购物车列表数据 * returns Promise */ export const reqCartList () { return http.get(/cart/getCartList) } /** * description 更新商品的选中状态 * param {*} goodsId 商品的 ID * param {*} isChecked 商品的勾选状态, 0 说明需要取消勾选, 1 需要勾选 * returns Promise */ export const reqUpdateChecked (goodsId, isChecked) { return http.get(/cart/checkCart/${goodsId}/${isChecked}) } /** * description 实现全选和全不选功能 * param {*} isChecked 全选与全不选状态, 0 就是取消全选, 1 进行全选 * returns Promise */ export const reqCheckAllStatus (isChecked) { return http.get(/cart/checkAllCart/${isChecked}) } /** * description 删除购物车商品 * param {*} goodsId 商品的 ID * returns Promise */ export const reqDelCartGoods (goodsId) { return http.get(/cart/delete/${goodsId}) }加入购物车-模板分析和渲染产品需求import { reqGoodsInfo } from /api/goods Page({ // 页面的初始数据 data: { goodsInfo: {}, // 商品详情 show: false, // 加入购物车和立即购买时显示的弹框 count: 1, // 商品购买数量默认是 1 blessing: , // 祝福语 buyNow: 0 // 控制是加入购物车还是立即购买0 加入购物车1 立即购买 }, // 加入购物车 handleAddcart() { this.setData({ show: true, buyNow: 0 }) }, // 立即购买 handleGotoBuy() { this.setData({ show: true, buyNow: 1 }) } })!-- 步进器组件控制购买数量 -- view classbuy-btn wx:if{{ buyNow 0 }} !-- Stepper 步进器由增加按钮、减少按钮和输入框组成控制购买数量 -- van-stepper value{{ count }} bind:changeonChangeGoodsCount / /view加入购物车-页面关联 Store 对象import { BehaviorWithStore } from mobx-miniprogram-bindings import { userStore } from /stores/userstore export const userBehavior BehaviorWithStore({ storeBindings: { store: userStore, fields: [token] } })加入购物车和立即购买区分处理!-- 确定按钮 -- view classsheet-footer-btn van-button block typeprimary round bindtaphandlerSubmit确定/van-button /view// 监听是否更改了购买数量 onChangeGoodsCount(event) { this.setData({ count: Number(event.detail) }) }, // 弹框的确定按钮触发的事件处理函数 async handlerSubmit() { // 解构相关的数据 const { token, count, blessing, buyNow } this.data // 获取商品的 id const goodsId this.goodsId // 判断用户是否进行了登录如果没有登录需要跳转到登录页面 if (!token) { wx.navigateTo({ url: /pages/login/login }) return } // 区分处理加入购物车已经立即购买 // 如果 buyNow 0说明是加入购物车 // 如果 buyNow 1说明是立即购买 if (buyNow 0) { const res await reqAddCart({ goodsId, count, blessing }) if (res.code 200) { wx.toast({ title: 加入购物车成功 }) this.setData({ show: false }) } } else { wx.navigateTo({ url: /pages/order/detail/detail?goodsId${goodsId}blessing${blessing} }) } }加入购物车-展示购物车购买数量实现步骤!-- 确定按钮 -- view classsheet-footer-btn van-button block typeprimary round bindtaphandlerSubmit确定/van-button /viewimport { reqAddCart, reqCartList } from /api/cart Page({ // 页面的初始数据 data: { goodsInfo: {}, // 商品详情 show: false, // 加入购物车和立即购买时显示的弹框 count: 1, // 商品购买数量默认是 1 blessing: , // 祝福语 buyNow: 0, // 控制是加入购物车还是立即购买0 加入购物车1 立即购买 allCount: // 商品的购买数量 }, // 计算购物车商品的数量 async getCartCount() { // 使用 token 来判断用户是否进行了登录, // 如果没有 token, 说明用户没有登录, 就不执行后续的逻辑 if (!this.data.token) return // 如果存在 token, 说明用户进行了登录, 获取购物车列表的数据 // 然后计算得出购买的数量 const res await reqCartList() // 判断购物车中是否存在商品 if (res.data.length ! 0) { // 累加得出的商品购买数量 let allCount 0 res.data.forEach((item) { allCount item.count }) this.setData({ // info 属性的属性值要求是字符串类型 // 而且如果购买的数量大于 99, 页面上需要展示 99 allCount: (allCount 99 ? 99 : allCount) }) } }, onLoad(options) { // 接收传递的商品 ID, 并且将 商品 ID 挂载到 this 上面 this.goodsId options.goodsId // 调用获取商品详情数据的方法 this.getGoodsInfo() // 计算购买的数量 this.getCartCount() } })此时还不能看到数字变化 需要重新计算// 在加入购物车成功以后, 需要重新计算购物车商品的购买数量 this.getCartCount()