2021版小程序开发5——小程序项目开发实践(2)-完

news/2025/2/11 7:51:49/

2021版小程序开发5——小程序项目开发实践(2)

学习笔记 2025

使用uni-app开发一个电商项目继续;

过滤器的使用

filters: {toFixed(num){return Number(num).toFixed(2)}
}
<!-- 通过管道符 | 使用过滤器 -->
<view> {{ item.price | toFixed }}</view>

上拉加载更多 和 下拉刷新

上拉加载更多:

  • 在pages.json配置文件中,为页面(如subPackages分包中的goods_list页面)配置上拉触底的距离;
  • 在页面中,和methods节点平级,声明onReachBottom事件处理函数,以监听页面上拉触底事件;
  • 通过节流阀 避免一次性发送多个请求

下拉刷新:

  • 在pages.json中,类似配置上拉加载更多,配置enablePullDownRefreshbackgroundColor,为当前页面单独开启下拉刷新效果;
  • 在页面中,和methods节点平级,声明onPullDownRefresh事件处理函数,以监听页面下拉刷新事件;
  • 通过回调函数主动关闭下拉刷新;
"subPackages": [{"root": "subpkg","pages": [{"pach": "goods_detail/goods_detail","style": {}},{"path": "goods_list/goods/list","style": {"onReachBottomDistance": 150, // px"enablePullDownRefresh": true,"backgroundColor": "#F8F8F8"}}]}
]
onReachBottom() {if (this.isloading) returnthis.queryObj.pagenum += 1this.getGoodsList()
}onPullDownRefresh(){// 重置必要数据 // 重新发送请求,并传入一个回调函数,主动停止下拉刷新this.getGoodsList(()=> uni.stopPullDownRefresh())
}
...getGoodsList(callBack){this.isloading = true // 通过节流阀 避免一次性发送多个请求// ...request// 将接口响应数据 拼接到之前数据后this.goodsList = [...this.goodsList, ...res.message.goods]//this.isloading = falsecallBack && callBack()
}

block元素包裹

模拟一个块元素,通常用作v-for包裹;

大图预览

preview(i){uni.previewImage({current: i, // 图片索引urls: this.goods_pics.map(x=>x.pic_url)})
}

html文本渲染

可以使用rich-text富文本组件的nodes属性,可以在小程序环境中渲染出html字符串;

为了避免图文中图片下边白条,可以给所有img添加行内样式:display: block

this.goods_intro = this.goods_intro.replace(/<img /g, '<img style="display: block;"')
<rich-text :nodes="goods_intro"></rich-text>

商品详情底部的商品导航区域

uni-ui提供了GoodsNav组件,实现该效果;

  • 在data中,通过optionsbuttonGroup两个数组,来声明商品导航组件的按钮配置对象;
  • 使用uni-goods-nav组件;
  • 使组件固定在页面最底部
<view class="goods-nav"><uni-goods-nav :fill="true" :options="options" :buttonGroup="buttonGroup" @click="onClick" @buttonClick="buttonClick"></uni-goods-nav>
</view>  
options: [{icon: 'shop',text: '店铺'
},{icon: 'cart',text: '购物车',info: 2
}],
buttonGroup: [{text: "加入购物车",backgroundColor: '#FF0000',color: "#FFF"},{text: "立即购买",backgroundColor: '#FFA200',color: "#FFF"}
]
.goods-nav {position: fixed;bottom: 0;left: 0;width: 100%;
}

uni-app配置Vuex实现全局数据共享

  • 根目录创建store文件夹,专门存放vuex相关模块;
  • 新建store.js,初始化Store实例对象:
    • 导入Vue和Vuex
    • 将Vuex安装为Vue插件
    • 创建Store实例对象
    • 向外共享Store实例对象
  • 在main.js中导出store实例对象,并挂载到Vue的实例上;
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
import moduleCart from "./cart.js"Vue.use(Vuex)const store = new Vuex.Store({// 挂载store模块modules:{// 调整访问路径 为 m_cart/carts,即m_cart模块的carts数组m_cart: moduleCart,} 
})export default store
// main.js
import store from './store/store.js'const app = new Vue({...App,store,//
})
app.$mount()

创建必要的store模块

继续创建 cart.js

// cart.js
export default {// 为当前模块开启命名空间namespaced: true,// 模块的state数据state: () => ({// 购物车 商品对象数组carts: [],})// mutations 方法:专门修改数据mutations: {addToCart(state, goods){const findRes = start.carts.find((x)=>{x.id === goods.id})if (!findRes){state.carts.push(goods)}else{findRes.count++}}},// getters属性:相当于数据的包装器getters: {cart_num(state){let c = 0state.carts.forEach(goods => c += goods.count)return c}},
}

在页面使用Store中的数据

import { mapState, mapMutations, mapGetters } from "vuex"computed: {// 将m_cart模块的carts数组 映射到当前页面...mapState("m_cart", ['carts']),...mapGetters("m_cart", ['cart_num']),
},
watch: {// cart_num(newVal){//   const findRes = this.options.find((x)=> x.text === '购物车')//   if(findRes) {//     // 监听数据变化 修改购物车按钮徽标//     findRes.info = newVal//   }// },// 优化后的监听器 支持在页面初次加载的时候立即执行cart_num: {handler(newVal){// ...},immediate: true}
}
methods: {...mapMutations("m_cart", ['addToCart'])// todo(){this.addToCart(goods_item)}
}

持久化存储购物车的数据

cart.js的mutations声明saveToStorage方法:

saveToStorage(state){uni.setStorageSync("carts", JSON.stringfy(state.carts))
} 

页面上的调用:

// 任何修改carts的地方
this.commit("m_cart/saveToStorage")

cart.js的state中 carts从Storage中初始化:

state: () => ({// 购物车 商品对象数组carts: JSON.parse(uni.getStorageSync('carts') || '[]'),
})

动态为tabBar上的购物车添加数字徽标

  • 把Store中的 cart_num 映射到 cart.vue中使用
  • onShow(){}中自定义方法 this.setBadge()
  • 添加监听vuex中商品总数值,显式的调用this.setBadge()
setBadge(){uni.setTabBarBadge({index: 2,text: this.cart_num + '' // 转成字符串进行显示})
}

进一步的还需要在其他tabBar页面也这样添加(这样在tabBar的任何一个页面,都可以触发购物车数字徽标的显示),因此可以将这些代码封装为一个mixin;

  • 在根目录新建mixins文件夹,再新建tabbar-badge.js文件;
  • 修改需要混入的页面代码;
// tabbar-badge.js
import {mapGetters} from "vuex"export default {computed: {...mapGetters('m_cart', ["cart_num"])},watch: {cart_num() {this.setBadge()}},onShow(){this.setBadge()},methods:{setBadge(){uni.setTabBarBadge({index: 2,text: this.cart_num + '' // 转成字符串进行显示})}}
}
// 页面使用
import badgeMix from "@/minins/tabbar-badge.js"export default {mixins: [badgeMix]
}

购物车页面

从Vuex中取出 carts数据列表,进行渲染;

import { mapState } from "vuex"computed: {...mapState("m_cart", ['carts']),
},

商品选中单选框

<radio cheched color="#C00000"></radio>

组件内单选状态的改变,可以通过组件的自定义回调函数传递到使用组件的页面;

在store/cart.js中定义一个mutations方法,实现以下逻辑:

  • 在购物车商品列表中找到当前商品;
  • 修改其商品状态(引用对象,已生效);
  • 持久化存储到本地(this.$commit("m_cart/saveToStorage"));

在页面相应的回调函数中,调用该mutations方法即可;

flex-item设置flex:1

表示占满剩余区域;

解决数字输入框NumberBox数据输入不合法导致的问题

修改uni-number-box.vue组件的代码,在_onBlur回调中:

_onBlur(event){// let value = event.detail.valuelet value = parseInt(event.detail.value) // 转intif (!value){this.inputValue = 1 // 如果解析出NaN 则强制赋为1return}
}

继续修改watch节点的inputValue监听器:

inputValue(newVal, oldVal){// 值发生变化(新旧值不等) 且 为数值 且 不为小数点 才向外传递if (+newVal !== +oldVal && Number(newVal) && String(newVal).indexOf(".") === -1){this.$emit("change", newVal)}
}

数组删除元素

使用数组的filter方法,返回过滤后的列表,可便捷实现删除;

文本不换行

white-space: nowrap;

判断对象是否为空

v-if="JSON.stringfy(address) === '{}'"

收货地址的选择

methods: {async chooseAddress() {const [err, succ] = await uni.chooseAddress().catch(err => err)if (err == null  &&  succ.errMsg == 'chooseAddress:ok'){this.address = succ}}
}

当前已选择的收货地址,也需要保存到 vuex中进行持久化;

  • 在store中新建一个user.js,用来实现用户选择的收货地址持久化的过程;
  • 之后当前页面的address也不在组件的data中进行存储,而是直接使用vuex中的数据;
// store.user.js
export default {namespaced: true,state: () => ({address: JSON.parse(uni.getStorageSync('address' || '{}')),}),mutations: {updateAddress(state, address) {state.address = address this.commit('m_user/saveAddressToStorage')},saveAddressToStorage(state){uni.setStorageSync('address', JSON.stringfy(state.address))}},getters: {addstr(state){if (!state.address.provinceName) return ''return state.address.provinceName + state.address.cityName + state.address.countryName}},
}

收货地址授权失败的问题

获取用户收货地址授权的时候,点击取消授权,需要进行特殊处理,否则将无法再次提示授权;

methods: {async chooseAddress() {const [err, succ] = await uni.chooseAddress().catch(err => err)if (err == null  &&  succ.errMsg == 'chooseAddress:ok'){// 更新 Vuex中的地址this.updateAddress(succ)}if (err &&  (err.errMsg === 'chooseAddress:fail auth deny' || err.errMsg === 'chooseAddress:fail authorize no response')){// 重新发起授权(可在小程序IDE中 清空缓存的授权信息 进行测试)this.reAuth()}},async reAuth(){// 提示给用户重新授权const [err2, confirmResult] = await uni.showModel({cotent:"检测到您没打开地址权限,是否去设置打开?",confirmText: "确认",cancelText: "取消"})if (err2) return if (confirmResult.cancel) return uni.$showMsg("您取消了地址授权")if (confirmResult.confirm) {// 打开授权设置面板return uni.openSetting({// 设置成功的回调success: (settingResult) => {if (settingResult.authSetting["scope.address"]) return uni.$showMsg("授权成功")if (!settingResult.authSetting["scope.address"]) return uni.$showMsg("授权取消")}})}}
}

iphone设备的问题:

  • iPhone真机上,err.errMsg的值为chooseAddress:fail authorize no response

自定义结算组件

/* 固定定位 */
.my-settle-container {position : fixed;bottom: 0;left: 0;width: 100%;height: 50px;backgroundColor: #c00000;
}

被遮蔽的组件,可以加一个下padding-bottom:50px;

vuex中所有选中状态商品的数量总数:

// store.cart.js 定义一个 checkedCount的getters
checkedCount(state){return state.carts.filter(x=>x.goods_state).reduce((total, item) => total += item.goods_count, 0)
}

结算组件中的全选/反选操作:

<!-- 可以看到 radio组件仅做显示 按钮操作是加在其父节点上的 -->
<label class="radio" @click="changeAllState"><radio color="#C00000" :checked="isFullCheck" /><text>全选</text>
</label>
// isFullCheck 是有 选中商品数 与 所有商品数 判等 的计算属性
methods: {...mapMutations('m_cart', ['updateAllGoodsState']),changeAllState(){this.updateAllGoodsState(!this.isFullCheck)}
}

登录

点击结算要检验

  • 是否勾选了要结算的商品
  • 是否选择了收货地址
  • 是否登录
// 登录判断
if (!thid.token ) return uni.$showMsg("请前往登录")

token也是存在vuex state中的;

登录/我的

登录:

  • 准备登录参数
    • encryptedData:用户信息(加密),通过getUserInfo()获取
    • iv:加密算法的初始向量,通过getUserInfo()获取
    • rawData:用户信息json字符串,通过getUserInfo()获取,JSON.stringfy转字符串
    • signature:sha1签名,通过getUserInfo()获取
    • code:用户登录凭证,通过wx.login()获取
  • 调用登录接口
<!-- 按钮 指定 open-type 和getuserinfo -->
<button type="primary" class="btn-login" open-type="getUserInfo" @getuserinfo="getUserInfo">登录</button>
getUserInfo(e){if (e.detail.errMsg === "getUserInfo:fail auth deny"){return uni.$showMsg("登录授权取消")}// e.detail中包含了 登录所需参数// e.detail.userInfo需要存储到Vuex中// this.updateUserInfo(e.detail.huseInfo)// 进一步调用this.getToken(e.detail)
},getToken(info){// 调用微信登录const [err, res] = await uni.login().catch(err=>err)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}// 调用业务的登录接口const { data: loginResult } = await uni.$http.post("/suburl", query)if (loginResult.meta.status !== 200 )return uni.$showMsg("登录失败")uni.$showMsg("登录成功")// loginResult.message.token需要存储到Vuex中// this.updateToken(loginResult.message.token)// 页面使用token是否存在 来切换组件的展示
}

通过尾元素实现弧顶

.login-container {height: 750rpx;display: flex;flex-direction: column;justify-content: center;align-items: center;position: relative;overflow: hidden;&::after {content: "";display: block;width: 100%;height: 40px;backgroundColor: #c00000;position: absolute;bottom: 0;left: 0;border-radius: 100%;transform: translateY(50%);}
}

页面全屏

/* 使用根节点page标签 */
page, my-container {height: 100%;
}

组件重叠

只需要使用相对定位,然后在需要重叠的方向 设置负值即可;

.panel-list {padding: 0 10px;position: relative;top: -10px;
}

退出登录

async logout(){const [err, succ] = await uni.showModel({title: "提示",content: "确认退出登录吗?"}).catch(err => err)if (succ && succ.confirm){// 清空vuex中的userinfo token 和 addressthis.updateUserInfo({})this.updateToken("")this.updateAddress({})}
}

3s倒计时提示后自动跳转到登录页

data(){return {seconds: 3,timer: null}
},
// ...
showTips(n){uni.showToast({icon: 'none',title: "请登录后再结算!" + n + "秒后自动跳转",mask: true,duration: 1500})
}delayNavigate(){this.seconds = 3 // 每次触发 都从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)
}

从购物车页面 触发的登录,在登录成功后还需要自动跳转会购物车页面(可以把参数存储到Vuex中来实现);

支付

添加身份认证请求头字段

只有登录后才允许调用支付相关接口,因此需要为有权限的接口添加身份认证请求头字段;

import store from "@/store/store.js"$http.beforeRequest = function(options){uni.showLoading({title: '数据加载中...'})// 包含/my/的url被认为是 有权限的接口if (options.url.indexOf('/my/' !== -1)){// 提阿难捱身份认证字段options.header = {Authorization: store.state.m_user.token,}}
}

微信支付流程

  • 创建订单,返回订单编号
  • 订单预支付,使用订单编号,获取订单支付的相关信息(订单预支付的参数对象)
  • 发起微信支付:
    • 调用uni.requestPayment(),发起微信支付,传入“订单预支付的参数对象”;
    • 监听uni.requestPayment()的请求回调successfailcomplete函数;

创建订单:传入总价格、地址和商品列表信息;

订单预支付的参数对象:

  • nonceStr
  • package
  • paySign
  • signType
  • timeStamp
const [err, succ] = await uni.requestPayment(payInfo)if (err) return uni.$showMsg("订单未支付!")const { data: res3 } = await uni.$http.post("/查询支付的回调结果", { order_number: orderNumber })if (res3.meta.status !== 200) return uni.$showMsg("订单未支付!")uni.showToast({title: "支付完成",icon: "success"
})

发布

一般步骤:

  • HBuilderX 菜单栏点击“发行”,“小程序-微信”
  • 弹出框填写发布的小程序名称,和AppId;点击“发行”
  • 编译ing
  • 编译完成后,会打开微信开发者工具,地那几工具栏上的“上传”按钮;
  • 继续填写:版本号 和 项目备注,点击上传;
  • 上传成功后会提示“上传代码完成”,并提示编译后的代码大小;
  • 登录微信小程序管理后台,在版本管理,“开发版本”列表中,点击“提交审核”,审核通过后就是“审核版本”,再点击“发布”按钮就成为了“线上版本”;

发布体验版小程序

  • 1.DCloudID 重新获取;(无需每次都获取;如果DCloud账号配置正常的话,还需要重新在manifest.json中生成自己的APPID;git上无需提交该修改;)
  • 2.前往验证账号;
  • 3.点击发行-微信小程序-“提交审核”按钮旁的下拉选择设置为体验版即可;

HBuilderX登录DCloud账号是在IDE的左下角;


http://www.ppmy.cn/news/1571103.html

相关文章

Jenkins数据备份到windows FTP服务器

文章目录 背景1. 安装配置 FileZilla Server&#xff08;Windows&#xff09;1.1 下载并安装 FileZilla Server1.2 配置 FTP 用户和共享目录 2. 安装并配置 FTP 客户端&#xff08;CentOS&#xff09;2.1 在 CentOS 安装 lftp 3. 编写 Jenkins 备份脚本3.1 赋予执行权限3.2 测试…

在阿里云ECS上一键部署DeepSeek-R1

DeepSeek-R1 是一款开源模型&#xff0c;也提供了 API(接口)调用方式。据 DeepSeek介绍&#xff0c;DeepSeek-R1 后训练阶段大规模使用了强化学习技术&#xff0c;在只有极少标注数据的情况下提升了模型推理能力&#xff0c;该模型性能对标 OpenAl o1 正式版。DeepSeek-R1 推出…

设计模式中的关联和依赖区别

在设计模式中&#xff0c;“关联”&#xff08;Association&#xff09;和“依赖”&#xff08;Dependency&#xff09;是两种不同的关系&#xff0c;它们有着不同的含义和使用场景。以下是它们之间的区别&#xff1a; 1. 关联&#xff08;Association&#xff09; 定义&…

现在中国三大运营商各自使用的哪些band频段

现在中国三大运营商4G和5G频段分配情况&#xff1a; 中国移动 4G频段&#xff1a; TD-LTE&#xff1a; Band 39&#xff1a;1880-1920MHz&#xff0c;实际使用1885-1915MHz。 Band 40&#xff1a;2300-2400MHz&#xff0c;实际使用2320-2370MHz。 Band 41&#xff1a;2515-26…

黑马React保姆级(PPT+笔记)

一、react基础 1.进程 2、优势 封装成一个库&#xff0c;组件化开发更加方便 跨平台主要是react native等可以来写移动端如android&#xff0c;ios等 丰富生态&#xff1a;可以在很多浏览器用 3、市场 4、搭建脚手架 npx create-react-app react-basic npm start后仍然可能…

系统URL整合系列视频三(前端代码实现)

视频 系统URL整合系列视频三&#xff08;前端代码实现&#xff09; 视频介绍 &#xff08;全国&#xff09;大型分布式系统Web资源URL整合需求前端代码实现。当今社会各行各业对软件系统的web资源访问权限控制越来越严格&#xff0c;控制粒度也越来越细。安全级别提高的同时也…

iOS主要知识点梳理回顾-2-多线程

iOS的多线程主要有三种方式&#xff0c;NSThread、GCD&#xff08;Grand Central Dispatch&#xff09;NSOperationQueue 开始&#xff0c;在iOS2发布的时候&#xff0c;苹果同步推出了NSthread和NSOperation。其中NSthread比较简单&#xff0c;仅提供了创建队列、开始、取消、…

XY2-100的Verilog实现

xy2_100.v module xy2_100(input clk,input tx_init, //当产生上升沿时&#xff0c;开始发数据input wire [15:0]x_pos,input wire [15:0]y_pos,input wire [15:0]z_pos,output clk_2MHz_o,//输出2MHz时钟output sync_o,output x_ch_o,output y_ch_o,output z_ch_o,output tx_…