小程序开发(电商类)

172 阅读3分钟

1.创建四个页面:选择scss

分别是home、cate、cart、my

2.在pages.json里面创建文件

  {
  "path":"pages/cart/cart",
  "style":{
      "enablePullDownRefresh":false
    }
  }

3.创建tabar页面,和pages同级

"tabBar": {
   "selectedColor":"#ea4350" // 设置小程序选中的颜色
   "list": [
	     {
		"pagePath": "pages/home/home",
		"text":"首页",
		"iconPath": "static/images/index.png",
	        "selectedIconPath": "static/images/index_selected.png"
		}
	]
},

4.配置request合法域名(https相关域名)

image.png

image.png

5.没有https合法域名可以先不设置

image.png

6.小程序发起请求,在小程序onLoad生命周期里

  onLoad(){
    uni.request({
      url:'https://api-hmugo-web.itheima.net/api/public/...',
      method:'GET',
      success:res=>{
		console.log(res)
       }
    })
  }

7.小程序不支持axios

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

(1)因为没有package.json文件,所以要先创建一个package.json文件,在终端中输入npm init -y命令 (2)下载包:@escook/request-miniprogram

8.在main.js里配置全部信息

image.png

9.配置信息

import Vue from 'vue'
import App from './App'

// 配置请求包
// 1.引入
import {$http} from '@escook/request-miniprogram'

// 2.设置请求的根路径
$http.baseUrl = 'https://api-hmugo-web.itheima.net'

// 3.设置拦截器 - 设置加载弹窗
$http.beforeRequest = function(){
	// 打开弹窗提示用户正在加载
	uni.showLoading({
		title:'加载中...'
	})
}

$http.afterRequest = function(){
	// 关闭弹窗
	uni.hideLoading()
}

// 4.将$http挂载到全局
uni.$http = $http

Vue.config.productionTip = false

App.mpType = 'app'

const app = new Vue({
    ...App
})
app.$mount()

10.请求失败后可以弹窗提示用户错了

// 判断请求是否成功
if(meta.status!==200){
 // 提示用户
 uni.showToast({
   title:'错了',
   icon:'none'
 })
}

和API相关的用小程序(比如:小程序的支付接口、小程序的弹窗方法),和语法相关的用vue(比如:保存一个值、循环)

<template>
	<view>
		home
	</view>
</template>

<script>
	export default {
		data() {
			return {
				swiperList:[]
			}
		},
		onLoad(){
			this.getSwiperList()
		},
		methods:{
			// 获取轮播图
			async getSwiperList(){
				const {data:{message,meta}}=await uni.$http.get('/api/public/v1/home/swiperdata')
				// 判断请求是否成功
				if(meta.status!==200){
					// 判断请求是否成功
					if(meta.status !==200){
						// 提示用户
						uni.showToast({
							title:'错了',
							icon:'none'
						})
						return 
					}
					this.swiperList = message
				}
				console.log(message)
			}
		},
		
		
	}
</script>

<style lang="scss">

</style>

11.在main.js里面封装全局弹窗

// 封装弹窗,在 将$http挂载到全局 后面
uni.$showMsg = function(title='获取失败...'){
	uni.showToast({
		title,
		icon:'none'
	})
}

判断请求失败后调用

if(meta.status!==200) return uni.$showMsg()

12.home页面轮播图

<!-- 轮播图 -->
  <swiper circular :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
    <swiper-item v-for="(item,i) in swiperList" :key="i">
	<image class="swiper-img" :src="item.image_src"></image>
    </swiper-item>
 </swiper>

轮播图样式:

 .swiper-img{
    width: 100%;
    height: 340rpx;
 }

13.小程序采用分包进行优化,用户不一定会看的页面先不下载就用分包,首页一定会看的页面就给你下载

创建subpkg页面,再创建goods-detail页面,会自动生成pages里面的内容会自动生成
在pages.json里面进行设置

"subPackages": [
		{
		    "root":"subpkg",
		    "pages": [{
                    "path" : "goods-detail/goods-detail",
                    "style" :                                                                                    
                {
                    "enablePullDownRefresh": false
                } 
               }
            ]
		}
	],

14.点击首页轮播图跳转到详情页面( 在轮播图外面加上navigator标签进行跳转 )

 // 跳转到tabBar页面必须用 open-type="switchTab" 声明是switchTab,这个页面是跳转到非tabBar页面,所以不用写了
 <navigator url="/subpkg/goods-detail/goods-detail">
    <image class="swiper-img" :src="item.image_src"></image>
 </navigator>

15.轮播图跳转时携带页面参数

<navigator :url="`/subpkg/goods-detail/goods-detail?goods_id=${item.goods_id}`">
  <image class="swiper-img" :src="item.image_src"></image>
</navigator>

16.获取导航的数据

<!-- 导航 -->
<view class="nav-list">
   <view class="nav-item" v-for="item,index in navList" :key="index">
     <image :src="item.image_src"></image>
   </view>
</view>
// 获取导航数据
async getNavList(){
  const {data:{message,meta}} = await uni.$http.get('/api/public/v1/home/catitems')
 // 判断请求是否成功
 if(meta.status !==200)return uni.$showMsg()
  console.log(message)
},
  .nav-list{
	 display: flex;
	 justify-content: space-around;
	 margin-top: 6px 0;
	 .nav-item{
		 width: 128rpx;
		 height: 140rpx;
		 image{
			 width: 100%;
			 height: 100%;
		 }
	 }
 }

17.点击分类进行跳转,因为点击分类时,传的分类的index是0,所以当index=0时进行跳转

 <image :src="item.image_src" @click="goCate(index)"></image>
 goCate(index){
   if(index===0){
     // 跳转到 tabBar 页面
	uni.switchTab({
			url:'/pages/cate/cate'
		})
   }
 }

18.楼层的渲染

image图片里面有这样一个属性,mode="widthFix"根据宽度自适应图片高度

<!-- 楼层 -->
		<view class="floor-list">
			<view class="floor-item" v-for="item,index in floorList" :key="index">
				<!-- 标题 -->
				<view class="floor-title">
					<image :src="item.floor_title.image_src" mode=""></image>
				</view>
				<!-- 内容 -->
				<view class="floor-content">
					<!-- 左 -->
					<view class="left">
						<image mode="widthFix" :style="{width:item.product_list[0].image_width+'rpx'}" :src="item.product_list[0].image_src"></image>
					</view>
					<!-- 右 -->
					<view class="right">
						<image mode="widthFix" :style="{width:e.image_width+'rpx'}" v-for="e,i in item.product_list" v-if="i!==0" :key="i" :src="e.image_src"></image>
					</view>
				</view>
			</view>
		</view>
// 获取楼层数据
  async getFloorList(){
     const {data:{message,meta }} = await uni.$http.get('/api/public/v1/home/floordata')
     // 判断请求是否成功
     if(meta.status !==200)return uni.$showMsg()
	this.floorList = message
     },
 // 楼层样式
 .floor-title{
	 image{
		 width: 100%;
		 height: 59rpx;
	 }
 }
 .floor-content{
	 display: flex;
	 padding:6px;
	 .right{
		 margin-left:6px;
		 display: flex;
		 justify-content: space-between;
		 flex-wrap: wrap;
	 }
 }

19.点击楼层图片进行跳转

获取到的楼层数据:

image.png (1) 跳转时需要对楼层数据navigator_url进行切割
(2) 将当前项传递过去,点击事件写在图片上,view上面也行 image.png (3)跳转到商品列表页面事件 image.png

cate分类页面
19.实现左右两边的滚动效果 必须设置滚动的高度,最好是直接设置成设备的高度,设备高度的获取方法是(使用uni.getSystemInfoSync()):

data(){
  return {
    wh:0
  }
}

onLoad(){
  // 获取设备的屏幕高度
  const res = uni.getSystemInfoSync()
  this.wh = res.windowHeight
}

在tamplate里面通过: :style="{ height:wh + 'px' }"

<!-- 内容区域 -->
<view class="content-box">
 <!-- 左边 -->		
  <scroll-view :style="{ height:wh + 'px' } " scroll-y="true" class="left-scroll scroll-box">
	<view>1</view>
        // 此处省略一百个盒子
  </scroll-view>
 <!-- 右边 -->
 <scroll-view :style="{ height:wh + 'px' } " scroll-y="true">
	<view>1</view>
	<view>2</view>
	<view>3</view>
	<view>4</view>
	<view>5</view>
	<view>1</view>
	<view>2</view>
	<view>3</view>
	<view>4</view>
	<view>5</view>
	<view>1</view>
	<view>2</view>
	<view>3</view>
	<view>4</view>
	<view>5</view>
   </scroll-view>
</view>

css样式

.content-box{
  display: flex;
 }

20.使用v-for将元素循环渲染出来

image.png 添加样式:

.left{
   width: 90px;
	view{
	     font-size:12px;
	     line-height: 60px;
	     text-align: center;
	     background-color: #f7f7f7;
	}
}

21.可以在uni.scss里面设置全局样式

image.png

22.左侧点击时的active样式,使用伪类选择器写

// 小红线盒子样式
.active{
  background-color: #fff;
  position:relative;
    &::before{
	 content:'';
	 position: absolute;
	 left:0;
	 top:50%;
	 margin-top:-15px;
	 height: 30px;
	 width: 3px;
	 background-color: #ea4350;
	}
  }

23.点击切换右边的内容
因为右边的内容是由cateList2确定的,而cateList2的值为某一项的children,所以在进行点击切换右侧内容时只需要将当前项的值传过去,然后将当前项的children给到cateList2即可 image.png

//切换分类
changeCate(item){
  // 切换高亮样式
  // 切换右侧内容
  this.cateList2 = item.children
},

24.切换高亮样式
设置一个current:0,将循环中的index的值给到current,如果点击的是某一项,则index===current,就将active给到这一项,否则为空''

<scroll-view :style="{ height:wh + 'px' } " scroll-y="true" class="left scroll-box">
   <view :class="[current===index ? 'active':'']" @click="changeCate(item,index)" v-for="item,index in cateList" :key="index">{{item.cat_name}}</view>	
</scroll-view>
data(){
  return{
    current:0
  }
}
//切换分类
   changeCate(item,index){
   // 切换高亮样式
   this.current = index
   // 切换右侧内容
   this.cateList2 = item.children
},

25.解决右边滚动位置的问题
在scroll-view里面有一个属性scroll-top用来设置竖向滚动条的位置
在右侧滚动条的scroll-view里面设置:

image.png

没有给滚动条赋值,所以他的值一直会是0,则需要在0.1和0之间反复横跳才能保证每点击左侧因此右侧页面从头开始显示:

image.png

26.点击图片跳转到商品详情页面

//跳转到商品列表页面
goList(item){ //传递的是e,相当于message下面的children
   uni.navigateTo({
	url:'/subpkg/goods-list/goods-list?cat_id='+item.cat_id
	})
},

27.封装搜索组件
(1)创建组件search-bar
使用scss的模板组件,创建同名目录,点击确定

(2)使用组件时可以不导入和注册,直接在页面上使用

image.png

(3)search-bar搜索组件页面代码

<template>
   <view>
	<view class="search-bar">
	    <view class="search-input">
		 <uni-icons type="search"></uni-icons>
		 <text>搜索</text>
	    </view>
	</view>
   </view>
</template>

<script>
	export default {
		name:"search-bar",
		data() {
			return {
				
			};
		}
	}
</script>

<style lang="scss">
.search-bar{
	display: flex;
	justify-content: center;
	align-items: center;
	height: 50px;
	background-color: #ea4350;
	
	.search-input{
		display: flex;
		justify-content: center;
		align-items: center;
		font-size: 12px;
		width: 95%;
		height: 35px;
		border-radius: 30px;
		background-color: #fff;
	}
}
</style>

28.解决搜索组件在首页和分类页面的bug
(1)解决首页面搜索框吸顶的问题

<view class="search">
  <search-bar></search-bar>
</view>

fixed会脱离文档流,但是sticky不会脱离文档流

 .search{
	 position: sticky;
	 top:0;
	 left:0;
	 z-index:999;
 }

(2)解决分类页面展示不完全的问题 因为搜索页面高度是50px,所以在分类页面获取设备高度后需要减去50 image.png

29.点击搜索标签进行跳转

image.png

在cate页面也是一样

<search-bar @click="goSearch"></search-bar>
// 跳转到搜索页面
goSearch(){
  uni.navigateTo({
	url:'/subpkg/search/search'
  })
},

search.vue页面
30.搜索的样式
使用uniapp的内置组件uni-search-bar
搜索框的背景组件不知道可以直接在微信开发工具的Wxml里用鼠标箭头选中搜索框来查看

<template>
  <view>
     <uni-search-bar radius="30" cancelButton="none" placeholder="搜索"></uni-search-bar>
  </view>
</template>

<script>
export default {
   data() {
	return {
				
	}
   },
   methods: {
			
   }
}
</script>

<style lang="scss">
 .uni-searchbar{
    background-color: #ea4350;
 }
</style>

31.搜索历史的样式

image.png

<!-- 搜索历史 -->
<view class="history">
  <!-- 标题 -->
  <view class="title">
      <text>搜索历史</text>
      <uni-icons type="trash" size="20"></uni-icons>
  </view>
</view>
 .history{
	 .title{
		 height:40px;
		 border-bottom:1px solid #eee;
		 display: flex;
		 justify-content: space-between;
		 align-items: center;
		 padding: 0 6px;
		 font-size: 12px;
	 }
 }

32.标签样式

image.png 代码结构和样式:

image.png

33.点击搜索框发送请求

<uni-search-bar @input="search" radius="30" cancelButton="none" placeholder="搜索"></uni-search-bar>
// 搜索 这个val就是用户输入的值
async search(val){
   const {data:{message,meta}} = await uni.$http.get('/api/public/v1/goods/qsearch',{query:val})
   if(meta.status!==200) return uni.$showMsg()
	this.searchList = message
	console.log(message)
   }

34.搜索框优化
(1) 当用户输入为空时,直接return返回

image.png (2) 节流处理 使用定时器,每500ms没有输入内容就触发定时器发送请求,在发送请求前要清除上一次的定时器

data() {
  return {
	searchList:[],
	timer:null
	}
},
methods: {
  // 搜索 这个val就是用户输入的值
  search(val){
	// 清理上一次的定时器
	clearTimeout(this.timer)
	// 判断用户输入是否为空
	if(val.trim()===''){
	   this.searchList = []
	   return
	}
				
      this.timer = setTimeout(async ()=>{
         const {data:{message,meta}} = await  uni.$http.get('/api/public/v1/goods/qsearch',{query:val})
         if(meta.status!==200) return uni.$showMsg()
         this.searchList = message
         console.log(message)
      },500)
 }

35.搜索历史和搜索列表的切换
在data里面创建一个新的字符串kw:'',当搜索框为空时(v-if="kw===''")显示搜索历史,有值(v-else)时显示搜索列表

image.png

36.搜索列表样式

<!-- 搜索列表 -->
<view class="search-list" v-else>
   <view class="search-item" v-for="item,index in searchList" :key="index">
       <text>{{item.goods_name}}</text>
       <uni-icons type="right"></uni-icons>
   </view>
</view>

溢出用...表示在这里一定要包在text里面

 .search-list{
	 .search-item{
		 height: 40px;
		 border-bottom:1px solid #eee;
		 display: flex;
		 justify-content: space-between;
		 align-items: center;
		 padding: 0 6px;
		 font-size:12px;
		 // 溢出用...表示
		 text{
			 overflow: hidden;
			 text-overflow: ellipsis;
			 white-space: nowrap;
		 }
	 }
 }

36.搜索历史 (1) 在data里面创建一个假的数组

history:['apple','app','a']

(2)在页面上进行渲染

<!-- 内容 -->
<view class="content">
    <uni-tag v-for="item,index in history" :key="index" type="warning" inverted="true" :text="item"></uni-tag>
</view>

(3)在页面接口调用后调用搜索历史方法

image.png

(4)搜索历史

// 搜索历史
saveHistory(){
  // [app,apple]
  const set=new Set(this.history)
  // 使用set.delete删除一个不存在的数据也不会报错
  set.delete(this.kw) 
  set.add(this.kw)
  this.history = Array.from(set)
},

(5)持久化处理

image.png

(6) 翻转数组
在计算属性里面做处理

computed:{
   reHistory(){
	return [...this.history].reverse()
   }
},

最后更改循环渲染的值:

image.png (7)点击搜索历史和商品列表进行跳转

image.png

goods-list商品列表页面

37.商品列表页面
在onLoad里面获取传过来的商品参数,其中onLoad里面的参数就是传递过来的参数

onLoad(options){
// onLoad的形参就是跳转页面传递的参数,如果当前页面是tabBar页面,无法接收参数
   console.log(options)
   this.params.query = options.query || ''
   this.params.cid = options.cat_id || ''
   this.getGoodsList()
},

获取商品列表页面:

// 获取商品列表
async getGoodsList(){
      const {data:{message,meta}} = await uni.$http.get('/api/public/v1/goods/search',this.params)
      if(meta.status !== 200)return uni.$showMsg()
      this.goodsList = message.goods
      this.total = message.total
}

38.渲染页面结构

<template>
   <view>
        <!-- 列表项 -->
	<view class="goods-item" v-for="item,index in goodsList" :key="index">
	   <!-- 左 -->
	   <view class="left">
		<image :src="item.goods_small_logo"></image>
	   </view>
	   <!-- 右 -->
	   <view class="right">
		<!-- 上 -->
		<view class="title">{{item.goods_name}}</view>
		<!-- 下 -->
		<view class="bottom">
		   <view class="price">¥{{item.goods_price}}</view>
		</view>
	   </view>
        </view>
</view>
</template>

39.设置默认图片

image.png

40.添加价格过滤器(保留两位小数)
使用管道符|

filters:{
    priceFormat(val){
	return val.toFixed(2)
    }
}

使用: image.png

41.将列表项封装成组件
(1)在components里面新建组件my-goods,使用scss的组件并且创建同名的目录
(2)将template里面的数据全部剪切过来,样式也全部剪切过来,还有defaultImageSrc的默认图片以及过滤器。
(3)将需要循环的goodsList数据删掉,因为封装的组件里面没有
(4)父组件里面要传递循环的数据

<template>
   <view>
	<block v-for="item,index in goodsList" :key="index">
	      <my-goods :goods="item"></my-goods>
	</block>
  </view>
</template>

封装的子组件里面需要进行接收:

props:{
    goods:{
	type:Object,
	default:()=>{}
    }
},

(5)封装组件整体结构图:

image.png

XX.轮播图的预览

<image :src="item.pics_big" @click="preview(index)"></image>
// 预览轮播图
preview(index){
   uni.previewImage({
     current:index,
     urls:this.goods.pics.map(e=>{
        return e.pics_big
     })
   })
}

37.vuex的初始化 (1) 新建目录store,再新建store.js文件