黑马优购:起步看这个--------->>>>>> https://www.escook.cn/docs-uni-shop/
⭐uni-app 品优购 开发文档
https://www.escook.cn/docs-uni-shop/#tabbar-%E7%9B%B8%E5%85%B3%E7%9A%84%E9%A1%B5%E9%9D%A2
了解 uni-app
uni-app 是一个使用 Vue.js 开发所有前端应用的框架。开发者编写一套代码,可发布到 iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。
详细的 uni-app 官方文档,请翻阅 https://uniapp.dcloud.net.cn/
下载 HBuilderX
uni-app 官方推荐使用 HBuilderX 来开发 uni-app 类型的项目。
HBuilderX 的官网首页 https://www.dcloud.io/hbuilderx.html
安装 scss/sass 编译
为了方便编写样式(例如:),建议安装 插件。插件下载地址: scss/sass 编译
下载 scss/sass : https://ext.dcloud.net.cn/plugin?name=compile-node-sass
新建uni-app项目
模板选择 uni-ui 项目
把项目运行到微信开发者工具(具体看最最最最上面链接)
使用 Git 管理项目
把项目托管到码云(生成公钥)
我的公钥存放位置:
生成并配置公钥后可在powershell中运行 ssh -t [email protected]
监测公钥是否配置成功
运行如下的命令,基于 master 分支在本地创建 tabBar 子分支,用来开发和 tabBar 相关的功能:
git checkout -b tabbar
在 目录中,创建首页(home)、分类(cate)、购物车(cart)、我的(my) 这 4 个 tabBar 页面。
在 HBuilderX 中,可以通过如下的两个步骤,快速新建页面:pages
将 资料 目录下的 static 文件夹(用来存放静态资源) 拷贝一份,替换掉项目根目录中的 static 文件夹
修改项目根目录中的pages.json
配置文件,新增 tabBar
的配置节点如下:
如果要展示 tabbar
在 HBuilderX 中,把pages
目录下的index首页文件夹
删除掉
同时,把 page.json
中记录的 index 首页
路径删除掉
打开 pages.json
这个全局的配置文件
修改globalStyle
节点如下:
// 与 tabBar 和 pages 平齐
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "来买鲜花吧",
"navigationBarBackgroundColor": "#ffc0cb",
"backgroundColor": "#F8F8F8",
"app-plus": {
"background": "#efeff4"
}
}
- 头部导航栏标题不显示的原因
其中
navigationBarTitleText
默认为空,所以此时在globalStyle
中的全局配置会被覆盖,即navigationBarTitleText
不会生效
我们只需要删除掉就好
先把tabbar 推送到远程仓库,然后把tabbar 和 master 合并,再把master 重新推送一遍
git add .
git commit -m "完成了 tabBar 的开发"
git push -u origin tabbar
git checkout master
切换到master分支git merge tabbar
合并分支git push
git branch -d tabbar
运行如下的命令,基于 master 分支在本地创建 home 子分支,用来开发和 home 首页相关的功能:
git checkout -b home
创建分支
由于平台的限制,小程序项目中不支持 axios,而且原生的 wx.request()
API 功能较为简单,不支持拦截器等全局定制的功能。因此,建议在 uni-app 项目中使用 @escook/request-miniprogram
第三方包发起网络数据请求。
请参考 @escook/request-miniprogram 的官方文档进行安装、配置、使用
⭐官方文档:https://www.npmjs.com/package/@escook/request-miniprogram
npm init -y
初始化package.json 文件
npm i @escook/request-miniprogram
进行安装
在 main.js 入口文件中进行导入
// 按需导入 $http 对象
import { $http } from '@escook/request-miniprogram'
// 将按需导入的 $http 挂载到 wx 顶级对象之上,方便全局调用
wx.$http = $http
// 在 uni-app 项目中,可以把 $http 挂载到 uni 顶级对象之上,方便全局调用
uni.$http = $http
// main.js
// 请求根路径
$http.baseUrl = "https://www.uinav.com"
// 请求拦截器
$http.beforeRequest = function(options){
// 展示loading效果
uni.showLoading({
title:"数据加载中..."
})
}
// 响应拦截器
$http.afterRequest = function(options){
// 隐藏loading效果
uni.hideLoading();
}
当数据请求失败之后,经常需要调用 uni.showToast({ /* 配置对象 */ })
方法来提示用户。
此时,可以在全局封装一个 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
}
请求路径:https://www.uinav.com/api/public/v1/home/swiperdata
请求方法:GET
无参数
实现步骤:
在 data 中定义轮播图的数组
在 onLoad 生命周期函数中调用获取轮播图数据的方法
在 methods 中定义获取轮播图数据的方法
获取轮播图数据的方法:
methods: {
async getSwiperList() {
// 发请求获取数据
const {data} = await uni.$http.get('/api/public/v1/home/swiperdata') // {}解构赋值
if(data.meta.status === 200){
// 获取数据成功,开始赋值
this.swiperList = data.message;
}else{
// 请求失败,展示提示消息 uni.showToast
return uni.showToast({
title:"轮播图数据请求失败",
duration:1500, // 1.5s后隐藏
icon:'none'
})
}
}
}
}
请求回来的数据:
利用 v-for
循环来遍历数据
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item v-for="(item,i) in swiperList" :key="i">
<view class="swiper-item">
<image :src="item.image_src" mode="">image>
view>
swiper-item>
swiper>
在小程序中,我们可以把 tabBar 相关的 4 个页面放到主包中,其它页面(例如:商品详情页、商品列表页)放到分包中
分包步骤:
将
节点内的view
组件,改造为 navigatorurl
导航组件,并动态绑定 url属性
的值。
<swiper :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item v-for="(item,i) in swiperList" :key="i">
<navigator class="swiper-item" :url="'/subpkg/goods_detail/goods_detail?goods_id=' + item.goods_id">
<image :src="item.image_src" mode="">image>
navigator>
swiper-item>
swiper>
获取首页分类选项数据
请求路径:[https://www.uinav.com]/api/public/v1/home/catitems
请求方法:GET
无参数
实现思路:
定义 data
数据
在 onLoad
中调用获取数据的方法
在 methods
中定义获取数据的方法
export default {
data() {
return {
// 分类导航的数据列表
navList: [],
}
},
onLoad() {
// 调用获取分类导航的数据
this.getNavList();
},
methods: {
// 3. 在 methods 中定义获取数据的方法
async getNavList() {
// 发请求获取数据
const {data} = await uni.$http.get('/api/public/v1/home/catitems')
if(data.meta.status === 200){
// 请求成功,数据转存到 data 中
this.navList = data.message;
}else{
// 请求失败,展示错误弹窗
return uni.$showMsg()
}
},
},
}
<view class="nav-list">
<view class="nav-item" v-for="(item,i) in navList" :key="i">
<image :src="item.image_src">image>
view>
view>
nav-item
绑定点击事件处理函数
<view class="nav-list">
<view class="nav-item" v-for="(item,i) in navList" :key="i" @click="navClickHandler(item)">
<image :src="item.image_src">image>
view>
view>
navClickHandler
事件处理函数 // 分类列表被点击时候的事件处理函数
navClickHandler(item){
// 判断点击的是哪个列表
if(item.name === "分类"){
uni.switchTab({
url:'/pages/cate/cate',
})
}
},
请求路径:【https://www.uinav.com】/api/public/v1/home/floordata
请求方法:GET
无参数
实现思路:
定义 数据 数据
在 onLoad 中调用获取数据的方法
在 Methods 中定义获取数据的方法
export default {
data() {
return {
// 1. 楼层的数据列表
floorList: [],
}
},
onLoad() {
// 2. 在 onLoad 中调用获取楼层数据的方法
this.getFloorList()
},
methods: {
// 3. 定义获取楼层列表数据的方法
async getFloorList() {
const {data} = await uni.$http.get('/api/public/v1/home/floordata')
if(data.meta.status === 200){
// 获取数据成功
this.floorList = data.message;
}else{
// 获取数据失败
return uni.$showMsg()
}
},
},
}
<view class="floor-list">
<view class="floor-item" v-for="(item,index) in floorList" :key="index">
<image :src="item.floor_title.image_src" class="floor-title">image>
view>
view>
.floor-title{
height: 60rpx;
width: 100%;
display: flex;
}
<view class="floor-list">
<view class="floor-item" v-for="(item,index) in floorList" :key="index">
<image :src="item.floor_title.image_src" class="floor-title">image>
<view class="floor-img-box">
<view class="left-img-box">
<image :src="item.product_list[0].image_src" mode="widthFix"
:style="{width:item.product_list[0].image_width + 'rpx'}">image>
view>
<view class="right-img-box">
<view class="right-img-item" v-for="(item2,index2) in item.product_list" :key="index2" v-if="index2 !== 0">
<image :src="item2.image_src" mode="widthFix" :style="{width:item2.image_width + 'rpx'}">image>
view>
view>
view>
view>
view>
.floor-list {
margin-top: 5rpx;
.floor-item {
box-shadow: 5rpx 10rpx 10rpx 5rpx #dfdfdf;
border-radius: 20rpx;
margin: 20rpx 0;
}
.floor-title {
height: 60rpx;
width: 100%;
display: flex;
}
.right-img-box {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.floor-img-box {
display: flex;
padding-left: 10rpx;
}
}
subpkg
分包中,新建goods_list
页面navigator_url
,但是这个数据中的路径和我们分包的路径不一样,我们要进行改造/subpkg/goods_list/goods_list?xxxx
因为navigator_url
外面有太多包裹着,所以我们使用forEach双层循环来获得navigator_url
,进行拼接字符串
// 获取数据成功
data.message.forEach(floor =>{
// 这里 floor 获得的是 每一个楼层,我们要的是每一个楼层里面的product_list 里的url
floor.product_list.forEach(value =>{
// 这里的 value 是五个图片
value.url = '/subpkg/goods_list/goods_list?' + value.navigator_url.split('?')[1]
})
})
this.floorList = data.message;
git add .
git commit -m "完成了 home 首页的开发"
git push -u origin home
git checkout master
git merge home
git push
git branch -d home
运行如下的命令,基于 master 分支在本地创建 cate 子分支,用来开发分类页面相关的功能:
git checkout -b cate
因为页面是左侧有滚动效果,右侧也有滚动效果,所以用到scroll-view
组件
<template>
<view>
<view class="scroll-view-container">
<scroll-view scroll-y="true" class="left-scroll-view" style="height:50px">
<view class="left-scroll-view-item">xxxxview>
<view class="left-scroll-view-item">xxxxview>
<view class="left-scroll-view-item">xxxxview>
<view class="left-scroll-view-item">xxxxview>
scroll-view>
<scroll-view scroll-y="true" class="right-scroll-view" style="height:200px">
<view class="right-scroll-view-item">右侧view>
<view class="right-scroll-view-item">右侧view>
<view class="right-scroll-view-item">右侧view>
scroll-view>
view>
view>
template>
我们想要scroll-view
的组件占满剩余的窗口,需要计算剩余窗口的高度
我们可以调用小程序的apiwx.getSysteminfoSync()
screenHeight
指的是整个手机屏幕的高度windowHeight
指的是手机屏幕 - navigationBar高度 - tabBar高度<script>
export default {
data() {
return {
// 当前设备的可用高度
windowHeigth: 0
};
},
// 生命周期函数 ---页面刚加载的时候
onLoad() {
// 动态获取设备的剩余高度
const sysInfo = uni.getSystemInfoSync()
this.windowHeigth = sysInfo.windowHeight;
},
}
script>
请求路径:[https://www.uinav.com] /api/public/v1/categories
请求方法:GET
无参数
实现思路:
定义 data 数据
在 onLoad 中调用获取分类数据的方法
在 methods 中定义获取分类数据的方法
export default {
data() {
return {
...
// 分类列表数据
cateList:[],
};
},
// 生命周期函数 ---页面刚加载的时候
onLoad() {
...
// 调用获取分类列表数据的方法
this.getCateList();
},
methods:{
async getCateList(){
// 发请求获取数据
const {data} =await uni.$http.get('/api/public/v1/categories')
if(data.meta.status === 200){
// 获取数据成功
this.cateList = data.message;
}else{
// 获取数据失败
uni.$showMsg();
}
},
}
}
<scroll-view scroll-y="true" class="left-scroll-view" :style="{height:windowHeigth+'px'}">
<block v-for="(item) in cateList" :key="item.cat_id">
<view class="left-scroll-view-item">{{item.cat_name}}view>
block>
scroll-view>
active
= 当前项的索引值 cat_id
,那就 添加active
类名⚠这里千万不能用 当前项的索引值
cat_id
,后续渲染二级分类的时候会增加麻烦!!还是跟老师一样用遍历中的i
<scroll-view scroll-y="true" class="left-scroll-view" :style="{height:windowHeigth+'px'}">
<block v-for="(item) in cateList" :key="item.cat_id">
<view :class="['left-scroll-view-item',item.cat_id === active ? 'active' :'']">{{item.cat_name}}view>
block>
scroll-view>
动态渲染多个类名
1. 首先渲染 left-scroll-view-item 类名
2. 然后用三元表达式开始渲染当前选中的 active 类名
:class="['left-scroll-view-item',item.cat_id === active ? 'active' :'']"
activeChanged
,传递id参数,如果点击该项,就让 active
等于 该项的id
<scroll-view scroll-y="true" class="left-scroll-view" :style="{height:windowHeigth+'px'}">
<block v-for="(item) in cateList" :key="item.cat_id">
<view :class="['left-scroll-view-item',item.cat_id === active ? 'active' :'']" @click="activeChanged(item.cat_id)">{{item.cat_name}}view>
block>
scroll-view>
// 修改当前 active 项
activeChanged(id){
this.active = id
},
data() {
return {
// 二级分类列表
cateLevel2: []
}
}
getCateList
方法,在请求到数据之后,为二级分类列表数据赋值,二级分类默认是索引号为 0 的 // 获取分类列表数据
async getCateList() {
// 发请求获取数据
const {data} = await uni.$http.get('/api/public/v1/categories')
if (data.meta.status === 200) {
// 获取数据成功
this.cateList = data.message;
// 为二级分类赋值,默认是索引为0的进行展示
this.cateLevel2 = data.message[0].children;
} else {
// 获取数据失败
uni.$showMsg();
}
},
activeChanged
方法,在一级分类选中项改变之后,为二级分类列表数据重新赋值,当切换分类的时候,我们就根据索引值重新为 二级分类赋值,进行展示 // 修改当前 active 项
activeChanged(i){
this.active = i
// 重新为 二级分类赋值
this.cateLevel2 = this.cateList[i].children;
},
⚠ 这里
activeChanged
传的参数不是数据里面的id了,是遍历产生的索引值
<scroll-view scroll-y="true" class="right-scroll-view" :style="{height:windowHeigth +'px'}">
<view class="cate-lv2" v-for="(item2,i2) in cateLevel2" :key="i2">
<view class="cate-lv2-title">/{{item2.cat_name}}/view>
view>
scroll-view>
.right-scroll-view {
// 渲染的二级标题
.cate-lv2-title {
font-size: 12px;
font-weight: bold;
text-align: center;
padding: 15px 0;
background: #f8d5e0;
// 条纹相间
background-image: repeating-linear-gradient(135deg, hsla(0, 0%, 100%, 0.5), hsla(0, 0%, 100%, 0.5) 15px, transparent 0, transparent 30px);
}
}
<view class="cate-lv2" v-for="(item2,i2) in cateLevel2" :key="i2">
<view class="cate-lv2-title">❀ {{item2.cat_name}} ❀view>
<view class="cate-lv3-list">
<view class="cate-lv3-item" v-for="(item3,i3) in item2.children" :key="i3">
<image :src="item3.cat_icon.replace('api-ugo-dev','api-ugo-web')" mode="">image>
<text>{{item3.cat_name}}text>
view>
view>
view>
// 三级分类的列表
.cate-lv3-list{
display: flex;
flex-wrap: wrap;
// item项
.cate-lv3-item{
// 每行三个item
width: 33.33%;
// 让里面的图片和文本纵向布局
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 10px;
image{
width: 60px;
height: 60px;
}
text{
font-size: 12px;
}
}
}
当我们切换一级分类的时候,会出现切换后滚动条没有置顶的情况
优化分类页面
我们的scroll-view
的scroll-top
可以动态指定滚动条的位置
当切换一级分类的时候,让scrollTop的值为 0 或 1
⚠ 滚动条赋值前后的值一样,是不会进行滚动条位置重置的!!
组件绑定 scroll-top 属性的值: <scroll-view scroll-y="true" class="right-scroll-view" :style="{height:windowHeigth +'px'}"
:scroll-top="scrollTop">
...
scroll-view>
data() {
return {
// 滚动条距离顶部的距离
scrollTop: 0
}
}
scrollTop
的值: // 修改当前 active 项
activeChanged(i) {
this.active = i
// 重新为 二级分类赋值
this.cateLevel2 = this.cateList[i].children;
// 动态设置scrollTop的值
this.scrollTop = this.scrollTop === 0 ? 1 : 0
},
<view class="cate-lv3-item" v-for="(item3,i3) in item2.children" :key="i3" @click="gotoGoodsList(item3)">view>
// 跳转到列表详情页
gotoGoodsList(item3){
uni.navigateTo({
url:'/subpkg/goods_list/goods_list?cid=' + item3.cat_id
})
}
将 cate 分支进行本地提交:
git add .
git commit -m "完成了分类页面的开发"
将本地的 cate 分支推送到码云:
git push -u origin cate
将本地 cate 分支中的代码合并到 master 分支:
git checkout master
git merge cate
git push
删除本地的 cate 分支:
git branch -d cate
运行如下的命令,基于 master 分支在本地创建 search 子分支,用来开发搜索相关的功能:
git checkout -b search
<my-search>my-search>
<template>
<view class="my-search-container">
<view class="my-search-box">
<uni-icons type="search" size="17">uni-icons>
<text class="placeholder">搜索text>
view>
view>
template>
<style lang="scss">
.my-search-container{
background-color: #f8d5e0;
height: 50px;
padding: 0 10px;
display: flex;
// 上下居中
align-items: center;
.my-search-box{
height: 36px;
line-height: 36px;
width: 100%;
background-color: #ffffff;
text-align: center;
border-radius: 20px;
color: black;
.placeholder{
font-size: 15px;
margin-left: 5px;
}
}
}
style>
这里的搜索框并不是真正的input,是用
组件模拟的 ,当我们点击以后会跳转到搜索页面真正的搜索框
由于自定义的 my-search 组件高度为 50px,因此,需要重新计算分类页面窗口的可用高度:
// cate.vue
onLoad() {
const sysInfo = uni.getSystemInfoSync()
// 可用高度 = 屏幕高度 - navigationBar高度 - tabBar高度 - 自定义的search组件高度
this.wh = sysInfo.windowHeight - 50
}
为了增强组件的通用性,我们允许使用者自定义搜索组件的 背景颜色
和 圆角尺寸
props属性
功能:让组件接收外部传过来的数据
props
定义 bgcolor 和 radius 两个属性,并指定值类型和属性默认值 props: {
// 背景颜色
bgcolor: {
type: String,
default: '#f8d5e0'
},
// 圆角尺寸
radius: {
type: Number,
default: 20 // px
}
},
.my-search-container
盒子和 .my-search-box
盒子动态绑定 style
属性 <view class="my-search-container" :style="{'backgroundColor':bgcolor}">
<view class="my-search-box" :style="{'border-radius':radius + 'px'}">
<uni-icons type="search" size="17">uni-icons>
<text class="placeholder">搜索text>
view>
view>
my-search
自定义组件内部,给类名为 .my-search-box
的 view 绑定 click 事件处理函数<view class="my-search-box" :style="{'border-radius': radius + 'px'}" @click="searchBoxHandler">
<uni-icons type="search" size="20">uni-icons>
<text class="placeholder">搜索text>
view>
methods:{
searchBoxHandler(){
// 会触发 my-search组件实例上的 click事件,然后会调用到gotoSearch函数
this.$emit('click')
},
},
吸顶效果:随着滑动主页,搜索组件并不会跟随页面的滚动而消失,会一直在顶部显示
在 home 首页定义如下的 UI 结构:
<view class="search-box">
<my-search @click="gotoSearch">my-search>
view>
在 home 首页定义如下的事件处理函数:
gotoSearch() {
uni.navigateTo({
url: '/subpkg/search/search'
})
}
通过如下的样式实现吸顶的效果
.search-box {
// 设置定位效果为“吸顶”
position: sticky;
// 吸顶的“位置”
top: 0;
// 提高层级,防止被轮播图覆盖
z-index: 999;
}