uni-app葵花宝典(欲练此功,必先自宫)

uni-app介绍

uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、H5、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉)等多个平台。

官网:https://uniapp.dcloud.io

两种搭建uni-app项目的方式

两种搭建项目的方式

  1. 通过 HBuilderX 可视化界面(https://uniapp.dcloud.io/quickstart?id=_1-%E9%80%9A%E8%BF%87-hbuilderx-%E5%8F%AF%E8%A7%86%E5%8C%96%E7%95%8C%E9%9D%A2)

可视化的方式比较简单,HBuilderX内置相关环境,开箱即用,无需配置nodejs。

开始之前,开发者需先下载安装如下工具:HBuilderX:官方IDE下载地址(http://www.dcloud.io/hbuilderx.html)

​ 2.通过vue-cli命令行(https://uniapp.dcloud.io/quickstart?id=_2-%E9%80%9A%E8%BF%87vue-cli%E5%91%BD%E4%BB%A4%E8%A1%8C)

也可以使用 cli 脚手架,可以通过 vue-cli 创建 uni-app 项目。

使用vue-cli创建uni-app项目

步骤

  1. 全局安装vue-cli脚手架版本4(如果已经安装``,可以跳过, )。npm install -g @vue/cli@4
    2023-0619测试,如果vue-cli的版本是5,会导致安装失败。
# 查看版本, 注意-V是大写的
vue -V 
# 安装包
npm install -g @vue/cli@4
  1. 执行cli ,用指定模板来创建项目
# vue create 是创建项目
# vue create -p dcloudio/uni-preset-vue 是根据指定的模板dcloudio/uni-preset-vue 来创建
# heima-ugo 是项目名称
vue create -p dcloudio/uni-preset-vue heima-ugo

3.选择默认模版

在这里插入图片描述

4.运行项目

npm run dev:%PLATFORM%

%PLATFORM% 可取值如下:

平台
h5H5
mp-alipay支付宝小程序
mp-baidu百度小程序
mp-weixin微信小程序
mp-toutiao字节跳动小程序
mp-qqqq 小程序

运行小程序

步骤:

  1. 执行小程序端开发命令
npm run dev:mp-weixin

说明:运行成功之后,会自动在项目根目录下,生成小程序项目代码,如图:

在这里插入图片描述

在dist/dev目录下会有一个mp-weixin的文件夹

在这里插入图片描述

这就是微信小程序的代码!

2.打开小程序开发者工具,导入上一步生成的小程序项目代码

在这里插入图片描述

3.运行效果

在这里插入图片描述

总结

  1. 运行:npm run dev:mp-weixin开启小程序开发服务器
  2. 看效果:导入项目下dist\dev\mp-weixin目录到小程序开发者工具
  3. 在项目src目录修改代码后,会自动重新打包小程序代码,实时查看最新效果

Hbuilderx创建并运行uniapp项目

创建

在这里插入图片描述

运行

在这里插入图片描述

如果不能主动开启微信开发者工具,可以设置下

在这里插入图片描述

uni-app目录结构和重点文件

目录结构

一个uni-app工程,默认包含如下目录及文件:

┌─components           # uni-app组件目录
│  └─comp-a.vue        # 可复用的a组件
├─pages                # 业务页面文件存放的目录
│  ├─index
│  │  └─index.vue      # index页面
├─static               # 存放应用引用静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此
├─App.vue              # 应用配置,用来配置App全局样式以及监听 应用生命周期
├─main.js              # Vue初始化入口文件
├─manifest.json        # 配置应用名称、appid、logo、版本等打包信息
└─pages.json           # 配置页面路由、导航条、选项卡等页面类信息

App.vue

它没有template,可以写公共样式,

小程序中最外层的容器不是body,是page

main.js 入口文件

pages.json 页面配置

manifest.json 配置文件

uniapp开发规范

uni-app代码编写,基本语言包括js、vue、css。以及ts、scss等css预编译器。

在app端,还支持原生渲染的nvue,以及可以编译为kotlin和swift的uts

DCloud还提供了使用js编写服务器代码的uniCloud云引擎。所以只需掌握js,你可以开发web、Android、iOS、各家小程序以及服务器等全栈应用。

为了实现多端兼容,综合考虑编译速度、运行性能等因素,uni-app 约定了如下开发规范:

开发方式:vue + 原生小程序部分用法

uniapp生命周期

uni-app框架的生命周期结合了vue和微信小程序的生命周期,具体如下:

应用级别:使用小程序的规范(App.vue)

onLanch

https://uniapp.dcloud.net.cn/collocation/App.html#applifecycle

页面级别: 使用小程序的规范

onShow, onLoad

https://uniapp.dcloud.net.cn/tutorial/page.html#lifecycle

组件级别:与vue的组件相同

https://uniapp.dcloud.net.cn/tutorial/page.html#componentlifecycle

created, destoryed

uniappapi

https://uniapp.dcloud.net.cn/api/

原则1:小程序中能用的,改个前缀就可以用。

wx.request → uni.request

原则2:uni的api基本上都支持promise

小程序的api是部分支持promise,还有些不支持的。

https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.showToast.html

https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html

uni.request(请求)方法的使用

文档:https://uniapp.dcloud.io/api/request/request?id=request

onLoad() {
  uni.request({
    url: "https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata"
  }).then(res => {
    console.log("结果:", res);
  })
}

async onLoad() {
  const res = await uni.request({
    url: "https://api-hmugo-web.itheima.net/api/public/v1/home/swiperdata"
  })
  console.log(res)
}

注意:uni.request是异步的方法。如果不传入 success、fail、complete 等 callback 参数,将以 Promise 返回数据

uni.getStorageSync() (获取token)

uni.setStorageSync() (保存token)

uni.showLoading() (提示loading)

uni.hideLoading() 关闭提示loading

uniapp中的组件-自定义组件

使用vue的规范,通过4步: 定义,引入,注册,使用

uniapp中的组件-easycom模式

默认开启的easycom

定义,引入,注册,使用

固定格式: components/组件名/组件名.vue

https://uniapp.dcloud.net.cn/component/#easycom组件规范

在这里插入图片描述

直接使用组件: ugo-search

自定义的easycom

如果你的目录结构不符合easycom的要求,则需要自己定义一下

1.自定义esycom

在这里插入图片描述

2.正常使用组件

uniapp第三方组件库之uview

uniapp是跨端的,那就要求它对应的组件库也要是跨端的,市面上用的比较多的uview

安装

https://www.uviewui.com/components/install.html#hbuilder-x方式

配置步骤

**#1. 引入uView主JS库**

在项目根目录中的main.js中,引入并使用uView的JS库,注意这两行要放在import Vue之后。

// main.js
import uView from '@/uni_modules/uview-ui'
Vue.use(uView)

#2. 在引入uView的全局SCSS主题文件

在项目根目录的uni.scss中引入此文件。

/* uni.scss */
@import '@/uni_modules/uview-ui/theme.scss';

#3. 引入uView基础样式

注意!

在App.vue中首行的位置引入,注意给style标签加入lang="scss"属性

<style lang="scss">
	/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
	@import "@/uni_modules/uview-ui/index.scss";
</style>

#4. 开始使用组件

HbuilderX初始化项目

  1. 创建空项目
  2. 安装 插件 scss (HBuilderX中安装插件)
  3. 运行到小程序模拟器

项目配置-基本配置小程序相关

配置manifest.json文件

配置mp-weixin节点,设置appid

"mp-weixin": {
  /* 微信小程序特有相关 */
  "appid": "wxfb52f2d7b2f6123a",
  "setting": {
    "urlCheck": false
  },
  "usingComponents": true
},

配置pages.json文件

  1. 设置tabBar页面
  2. 设置整体风格

tabBar页面

  1. 创建页面

  2. 设置tabBar配置

    配置tabBar:首页 分类 购物车 我的

    pages.json

{
  "pages": [
    //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页"
      }
    },
    {
		  "path": "pages/category/category"
		},
		{
		  "path": "pages/cart/cart"
		},
		{
		  "path": "pages/profile/profile"
		}
  ],

+  "tabBar": {
+    "color": "#000",
+    "selectedColor": "#ea4451",
+    "backgroundColor": "#fff",
+    "borderStyle": "white",
+    "list": [
+      {
+        "text": "首页",
+        "pagePath": "pages/index/index",
+        "iconPath": "static/tabs/icon_home@3x.png",
+        "selectedIconPath": "static/tabs/icon_home_active@3x.png"
+      },
+      {
+        "text": "分类",
+        "pagePath": "pages/category/category",
+        "iconPath": "static/tabs/icon_category@3x.png",
+        "selectedIconPath": "static/tabs/icon_category_active@3x.png"
+      },
+      {
+        "text": "购物车",
+        "pagePath": "pages/cart/cart",
+        "iconPath": "static/tabs/icon_cart@3x.png",
+        "selectedIconPath": "static/tabs/icon_cart_active@3x.png"
+      },
+      {
+        "text": "我的",
+        "pagePath": "pages/profile/profile",
+        "iconPath": "static/tabs/icon_user@3x.png",
+       "selectedIconPath": "static/tabs/icon_user_active@3x.png"
+      }
    ]
  }
}

注意

  1. 页面路径中的index文件名不能省略
  2. 本地静态资源必须放到static目录中才能正常访问
  3. 如果在pages中额外手动添加的pages项,不会像微信开发者工具一样能自动创建对应的页面

设置整体风格

globalStyle(对应小程序的全局window配置)

pages下的页面style(对应小程序的页面配置)

pages.json

"pages": [
    //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页"
      }
    }
],
"globalStyle": {
    "navigationBarTextStyle": "white",
    "navigationBarTitleText": "U-go",
    "navigationBarBackgroundColor": "#ea4451"
  },

pages的整体配置

扩展阅读:配置文档(https://uniapp.dcloud.io/collocation/pages)

分包配置

扩展阅读:https://uniapp.dcloud.io/collocation/pages?id=subpackages

小程序分包加载配置

因小程序有体积和资源加载限制,各家小程序平台提供了分包方式,优化小程序的下载和启动速度。

  • 所谓的主包(1个),即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本
  • 分包(多个)默认不需要加载页面资源

原理分析

在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,会把对应分包自动下载下来,下载完成后再进行展示。此时终端界面会有等待提示。

步骤

  1. 分析页面 → 拆分非tabBar页为单独包(分包)

  2. 新建分包目录packone,和pages目录同级

在这里插入图片描述

  1. pages.json中配置subPackages
//pages.json
  // 分包
  "subPackages": [
    {
      // 子包的根目录
      "root": "packone",
      // 子包由哪些页面组成
      "pages": [
        {
          "path": "goods/goods",
          "style": {
            "navigationBarTitleText": "详情"
          }
        },
        {
          "path": "list/list"
        },
        {
          "path": "order/order"
        },
        {
          "path": "auth/auth"
        }
      ]
    }
  ]

4.重新编译

5.微信开发者工具中,项目详情=》基本信息中=〉查看分包大小

在这里插入图片描述

总结:

  1. 微信小程序微信小程序每个分包的大小是2M,总体积一共不能超过20M
  2. subPackages 里的pages的路径是 root 下的相对路径,不是全路径。

自定义-搜索组件

步骤

  1. 在src下新建components目录
  2. 在该目录下新建ugo-search.vue组件,放入搜索结构和样式
  3. 导入到首页组件中使用

代码

搜索:components/ugo-search/ugo-search.vue

<template>
  <view class="search focused1">
    <view class="sinput">
      <input type="text" placeholder="搜索" />
      <button>取消</button>
    </view>
    <!-- 搜索状态显示=》下边内容 -->
    <view class="scontent" style="display: none">
      <div class="title">
        搜索历史
        <span class="clear"></span>
      </div>
      <!-- 搜索历史 -->
      <div class="history">
        <navigator url="/pages/list/index">小米</navigator>
        <navigator url="/pages/list/index">智能电视</navigator>
        <navigator url="/pages/list/index">小米空气净化器</navigator>
        <navigator url="/pages/list/index">西门子洗碗机</navigator>
        <navigator url="/pages/list/index">华为手机</navigator>
        <navigator url="/pages/list/index">苹果</navigator>
        <navigator url="/pages/list/index">锤子</navigator>
      </div>
      <!-- 结果 -->
      <scroll-view scroll-y class="result">
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
        <navigator url="/pages/goods/index">小米</navigator>
      </scroll-view>
    </view>
  </view>
</template>

<script>
export default {

}
</script>

<style lang="scss" scoped>
// 搜索
.search {
  width:750rpx;
  display: flex;
  flex-direction: column;
  .sinput {
    box-sizing: border-box;
    padding: 20rpx 16rpx;
    background: #ff2d4a;
    position: relative;
    //伪元素
    &::after {
      position: absolute;
      top: 28rpx;
      left: 302rpx;
      content: "";
      width: 44rpx;
      height: 44rpx;
      line-height: 1;
      background-image: url(https://static.botue.com/ugo/images/icon_search%402x.png);
      background-size: 32rpx;
      background-position: 6rpx center;
      background-repeat: no-repeat;
    }
    input {
      background: #fff;
      flex: 1;
      height: 60rpx;
      line-height: 60rpx;
      text-align: center;
      font-size: 24rpx;
      color: #bbb;
      border-radius: 5rpx;
    }
    button {
      display: none;
      margin-left: 20rpx;
      width: 150rpx;
      height: 60rpx;
      line-height: 60rpx;
      text-align: center;
      font-size: 24rpx;
      border-radius: 5rpx;
      background: transparent;
      color: #666;
    }
  }
  &.focused {
    position: absolute;
    width: 100%;
    height: 100%;
    z-index: 10;
    .sinput {
      display: flex;
      background: #eee;
      input {
        text-align: left;
        padding-left: 60rpx;
      }
      button {
        display: block;
      }
      &::after {
        left: 30rpx;
      }
    }
  }
  .scontent {
    background: #fff;
    position: relative;

    flex: 1;
    padding: 27rpx;
    .title {
      font-size: 27rpx;
      line-height: 1;
      color: #333;
    }
    .clear {
      display: block;
      width: 27rpx;
      height: 27rpx;
      float: right;
      background-image: url(http://static.botue.com/ugo/images/clear.png);
      background-size: cover;
    }

    .history {
      padding-top: 30rpx;
      navigator {
        display: inline-block;
        line-height: 1;
        padding: 15rpx 20rpx 12rpx;
        background-color: #ddd;
        font-size: 24rpx;
        margin-right: 20rpx;
        margin-bottom: 15rpx;
        color: #333;
      }
    }

    .result {
      display: none;
      position: absolute;
      left: 0;
      right: 0;
      top: 0;
      bottom: 0;
      background-color: #fff;
      navigator {
        line-height: 1;
        padding: 20rpx 30rpx;
        font-size: 24rpx;
        color: #666;
        border-bottom: 1px solid #eee;

        &:last-child {
          border-bottom: none;
        }
      }
    }
  }
}
</style>

引入并使用搜索组件

在首页pages/index/index.vue 中引入。由于前面采取了easycom的方式,所以,这里可以直接使用组件,而不需要引入,注册。

 <template>
   <view class="index">
   <!-- 搜索 -->
+   <ugo-search />
   <!-- ... -->
   </view>
 </template>
 <script>
 export default {
   data() {
     return {
       title: "Hello"
     };
   },

   onLoad() {},
   methods: {}
 };
 </script>

搜索组件-交互功能

在这里插入图片描述

步骤

  1. 设置搜索状态数据:isSearch
  2. 添加获取焦点focus事件处理状态数据
  3. 根据是否是搜索状态添加搜索样式=>.focused和控制.scontent是否显示
<view class="search"
+ :class="{focused:isSearch}"
>
     <view class="sinput">
+       <input @focus="search" type="text" placeholder="搜索" />
+       <button @click="cancel">取消</button>
     </view>
     <view class="scontent"
+	v-show="isSearch"
>
        <div class="title">
        搜索历史
        <span class="clear"></span>
        </div>
        <div class="history">
        <navigator url="/pages/list/index">小米</navigator>
        <navigator url="/pages/list/index">智能电视</navigator>
        <navigator url="/pages/list/index">小米空气净化器</navigator>
        <navigator url="/pages/list/index">西门子洗碗机</navigator>
        <navigator url="/pages/list/index">华为手机</navigator>
        <navigator url="/pages/list/index">苹果</navigator>
        <navigator url="/pages/list/index">锤子</navigator>
       </div>
     </view>
   </view>
<script>
 export default {
   data() {
     return {
       // 是否是搜索状态
+       isSearch: false
     };
   },
   methods: {
     search() {
+       this.isSearch = true;
     },
     cancel() {
+       this.isSearch = false;
     }
   }
 };
</script>

封装请求模块

对uni.request进行封装。

要求:整体封装一个函数

  1. 入参是:{ url, header = {}, method, data }
  2. 请求之前有loading
  3. 请求结束取消loading
  4. 自动添加token(从storage中取出来, 添加到headers中)
  5. 返回结果的格式是 {msg, data}

格式如下:

const res = await 封装过的发请求的函数({ 不带基地址的url, header = {}, method, data })

涉及知识

uni.getStorageSync

uni.showLoading()

uni.hideLoading()

步骤

  1. 新建utils目录
  2. utils目录下新建request.js模块,定义BASE_URL
  3. 封装异步函数,接收{ url, method, data }参数,返回结果:msg和data
  4. 请求前后添加loading效果
  5. 添加token

代码

request方法的封装

utils/request.js


const baseURL = "https://api-hmugo-web.itheima.net/api/public/v1"
 async function request({
	url, header={}, method='get', data={}
}) {
	uni.showLoading({title:'加载中'})
	
	
	let token = uni.getStorageSync('token')
	if(token) {
		header.Authorization = token
	};
	
	const res = await uni.request({
		url: baseURL + url,
		header,
		method,
		data
	})
	
	uni.hideLoading()
	
	if (res.data.meta.status === 200) {
	    return {
	      msg: res.data.meta.msg,
	      data: res.data.message
	    }
	  } else {
	    return Promise.reject(res.data.meta.msg)
	  }
}

export default request

测试

import request from '@/utils/request.js'
async getSwiper() {
  const res = await request({
    url: "/home/swiperdata"
  });
  console.log(res)
},
(1)封装请求模块-挂载到vue实例上

utils/request.js中挂载

++     import Vue from 'vue'
......
++     Vue.prototype.request = request
     export default request

main.js中引入

import '@/utils/request.js'
(2)封装请求模块-vue 插件形式(优化)

request.js

// vue 插件形式
// 1. 定义插件
const MyRequest = {
  install(Vue, opts) {
    Vue.prototype.request = request
  }
}
export default MyRequest

main.js

import MyRequest from '@/utils/request';
// 入口文件
// 2. 注册插件
Vue.use(MyRequest)
使用
this.request(config)

config是请求的配置项,例如请求URL、请求方式、请求头、请求数据

navigator组件(跳转)

**例如:**首页-数据渲染

navigator组件(跳转): https://uniapp.dcloud.io/component/navigator

处理路由跳转url

  • 轮播图:‘/packone/goods/index?id=’+item.goods_id
  • 功能导航:‘/packone/list/index?query=’+item.name
  • 栏目楼层:‘/packone/list/index?query=’ +prd.name

循环渲染

    <!-- 轮播图 -->
    <view class="swiper">
      <swiper
        autoplay
        interval="2000"
        circular
        indicator-dots
        indicator-color="rgba(255,255,255,1)"
        indicator-active-color="rgba(255,255,255,.6)"
      >
        <swiper-item v-for="item in swiper" :key="item.goods_id">
          <navigator :url="'/packone/goods/index?id=' + item.goods_id">
            <image :src="item.image_src" />
          </navigator>
        </swiper-item>
      </swiper>
    </view>
    <!-- 功能导航 -->
    <view class="navs">
      <navigator
+        :open-type="item.open_type ? 'switchTab' : 'navigate'"
+        :url="
+          item.open_type
+            ? '/pages/category/index'
+            : '/packone/list/index?query=' + item.name
+        "
//item.open_type就跳转到分类页面,没有就跳转到list页面(只有第一个有)
        v-for="item in navs"
        :key="item.name"
      >
        <image :src="item.image_src" />
      </navigator>
    </view>
    <!-- 栏目楼层 -->
    <view class="floors">
      <!-- 1 -->
      <view class="floor" v-for="(item, i) in floors" :key="i">
        <!-- title -->
        <view class="ftitle">
          <image :src="item.floor_title.image_src" />
        </view>
        <!-- pics -->
        <view class="fitem">
          <navigator
            :url="'/packone/list/index?query=' + prd.name"
            v-for="prd in item.product_list"
            :key="prd.name"
          >
            <image :src="prd.image_src" />
          </navigator>
        </view>
      </view>
    </view>

下拉刷新-配置

在这里插入图片描述

  1. pages.json文件中pages字段:配置首页的下拉刷新效果
  2. style(window)属性中:"enablePullDownRefresh": true
    {
      "path": "pages/index/index",
      "style": {
+       "enablePullDownRefresh": true, // 允许下拉
        "backgroundColor": "#fd1800" // 下拉区域背景色
      }
    },

开启配置:enablePullDownRefresh: https://uniapp.dcloud.io/collocation/pages?id=style

终止状态:uni.stoppulldownrefresh() https://uniapp.dcloud.io/api/ui/pulldown?id=stoppulldownrefresh

下拉刷新-实现

  1. 调用onPullDownRefresh钩子函数,刷新首页
  2. 等到首页获取完数据刷新后,关闭下拉loading
onPullDownRefresh() {
  Promise.all([this.getSwiper(), this.getNavs(), this.getFloors()]).then(
    () => {
      // 执行完停止loading
      uni.stopPullDownRefresh();
    }
  )
},

注意:

  • 模拟器中:用户手动触发,会自动关闭loading效果
  • 真机测试:需要调用uni.stopPullDownRefresh()方法关闭

回到顶部

在这里插入图片描述

监听页面滚动:

  • 如果 滚动高度 > 半屏高度 → 显示回顶按钮
  • 否则,隐藏回顶按钮

涉及知识

  1. 监听页面滚动条 onPageScroll
    https://uniapp.dcloud.io/collocation/frame/lifecycle?id=page
  2. 垂直方向滚动到指定位置

https://uniapp.dcloud.io/api/ui/scroll?id=pagescrollto

uni.pageScrollTo({
		scrollTop: 0,
		duration: 300
});

实现步骤

  1. 补充状态 isTop
  2. onPageScroll绑定点击事件,控制到一定位置显示到顶部按钮
    • 获取页面半屏高度:uni.getSystemInfoSync().windowHeight / 2
    • 如果滚动高度大于半屏高度 ===> 显示回顶按钮
  1. 调用页面滚动方法回到顶部
    uni.pageScrollTo()
核心代码

补充状态

data() {
			return {
				// 省略其他... 
+				isTop: false
			}
},

监听滚动事件

onPageScroll(e) {
  // console.log('页面滚动:', e)
  this.scrollTop = e.scrollTop
  // 显示回顶按钮条件:滚动高度大于半屏幕高度
  if (this.scrollTop > uni.getSystemInfoSync().windowHeight / 2) {
    this.isTop = true
  } else {
    this.isTop = false
  }
}

控制视图显示隐藏

<view v-if="isTop" @click="goTop" class="goTop icon-top"></view>

icon-top是额外定义的类名,用来显示图标。

补充method

methods: {
  // 省略其他... 
  goTop() {
    uni.pageScrollTo({
        scrollTop: 0,
        duration: 300
    })
  }
}

分类页面

分类-渲染数据(三层嵌套结构)

后端数据有三层嵌套结构,分别对应视图上的三级内容。

在这里插入图片描述

左侧一级分类数据,直接循环渲染

右侧二级分类数据,需要根据当前选中的一级分类来确定

实现步骤

  1. 补充状态值 active,用来表示一级分类选中的下标,默认为0
  2. 补充计算属性sub,用来计算得出2、3级分类

核心代码

  data() {
    return {
      // 分类数据
      cates: [],
      active: 0 // 一级分类选中的索引
    };
  },
  computed: {
    // 2、3级分类
    sub() {
      // 默认一级分类的第一项被选中
      // 
      return this.cates.length ? this.cates[this.active].children : [];
    }
  },
  methods: {
    // 获取分类数据
    async getCate() {
      const {data } = await this.request({
        url: "/categories"
      });
  
      this.cates = data;

    }
  },
  onLoad() {
    this.getCate();
  }

页面渲染

    <!-- 分类 -->
    <view class="category">
      <!-- 顶级分类 -->
      <view class="sup">
        <scroll-view scroll-y>
          <text
            :key="item.cat_id"
            v-for="(item,i) in cates"
          >{{item.cat_name}}</text>
        </scroll-view>
      </view>
      <!-- 子级分类 -->
      <view class="sub">
        <scroll-view scroll-y>
          <!-- 封面图 -->
          <image src="http://static.botue.com/ugo/uploads/category.png" class="thumb" />
          <view class="children" :key="item.cat_id" v-for="item in sub">
            <view class="title">{{item.cat_name}}</view>
            <!-- 品牌 -->
            <view class="brands">
              <navigator
                :url="'/packone/list/index?query='+it.cat_name"
                :key="it.cat_id"
                v-for="it in item.children"
              >
                <image :src="it.cat_icon" />
                <text>{{it.cat_name}}</text>
              </navigator>
            </view>
          </view>
        </scroll-view>
      </view>
    </view>

计算属性

		computed: {
			sub() {
				return this.list.length ? this.list[this.active].children:[]
			}
		}

分类-一级分类切换

实现分类切换功能。点击某一项:

  1. 高亮显示
  2. 右侧的显示内容联动

实现步骤

  1. 绑定事件,获取索引,设置当前选中的一级分类索引
  2. 根据索引添加class高亮样式
      <!-- 顶级分类 -->
      <view class="sup">
        <scroll-view scroll-y>
          <text
+            @click="switchCate(i)"
+            :class="{active:i === active}"
            :key="item.cat_id"
            v-for="(item,i) in cates"
          >{{item.cat_name}}</text>
        </scroll-view>
      </view>

补充切换方法

methods: {
  // ...
  switchCate(index) {
    this.active = index;
  }
}

骨架屏

在这里插入图片描述

easy-com组件规范

https://uniapp.dcloud.net.cn/component/#easycom组件规范

<template>
	<view>
		<view :style="{width: width + 'rpx', height: height + 'rpx'}" v-show="!isDone" class="mask"></view>
		<image :src= "src" :style="{width: width + 'rpx', height: height + 'rpx'}" @load="isDone=true"/>
	</view>
</template>

<script>
	export default {
		name:"ugo-image",
		props: {
			src: {type: String, required: true},
			width: { type: Number, default: 120 },
			height: { type: Number, default: 120 },
		},
		data() {
			return {
				isDone: false
			};
		}
	}
</script>

<style>
.mask {
	background-color: rgba(0, 0, 0, 0.5);
	position: relative;
	overflow: hidden;
	display: block;
}
.mask::before {
	content: '';
	position: absolute;
	animation: shan 1.5s ease 0s infinite;
	top: 0;
	width: 50%;
	height: 100%;
	background: linear-gradient(
	  to left,
	  rgba(255, 255, 255, 0) 0,
	  rgba(255, 255, 255, 0.3) 50%,
	  rgba(255, 255, 255, 0) 100%
	);
	transform: skewX(-45deg);
}
@keyframes shan {
  0% {
    left: -100%;
  }
  100% {
    left: 120%;
  }
}
</style>

uniapp的样式不要用scoped

<template>
	<view class="test" v-show="isShow">
		isShow
		<button @click="isShow=false">close</button>
	</view>
</template>

<script>
	export default {
		name:"ugo-test",
		data() {
			return {
				isShow: true
			};
		}
	}
</script>

<style>
	.test {
		display: block;
	}
</style>

搜索-建议商品

实现步骤

  1. 搜索框绑定input事件和v-model
  2. 根据关键词查询API接口,获取建议的商品数据,并展示
    根据result长度,条件渲染 --> 是否显示建议商品列表

核心代码

search.vue

结构

    <view class="sinput">
      <input
        @focus="search"
        type="text"
+        @input="searchPrd"
+        v-model="keyWord"
        placeholder="搜索"
      />
      <button @click="cancel">取消</button>
    </view>

    <!-- 搜索状态显示=》下边内容 -->
    <view class="scontent" v-show="isSearch">
      <!-- 搜索历史 -->
+      <block v-if="result.length === 0">
        <view class="title">
          搜索历史
          <span class="clear"></span>
        </view>
        <!-- 搜索历史 -->
        <view class="history">
          <navigator url="/pages/list/list">小米</navigator>
          <navigator url="/pages/list/list">智能电视</navigator>
          <navigator url="/pages/list/list">小米空气净化器</navigator>
          <navigator url="/pages/list/list">西门子洗碗机</navigator>
          <navigator url="/pages/list/list">华为手机</navigator>
          <navigator url="/pages/list/list">苹果</navigator>
          <navigator url="/pages/list/list">锤子</navigator>
        </view>
+      </block>
       <!-- 搜索建议商品 -->
+      <scroll-view scroll-y class="result" v-else>
        <navigator
+          v-for="item in result"
+          :key="item.goods_id"
+          :url="'/packageone/goods/goods?id=' + item.goods_id"
          >{{ item.goods_name }}</navigator
        >
      </scroll-view>
    </view>

状态值

data() {
  return {
     isSearch: false,
+    keyword: "",
+    result: []
  };
},

方法

// 获取搜索建议商品  --> 函数防抖处理
async searchPrd () {
    // 如果关键词为空=》清除历史建议商品列表
    if (!this.keyWord) {
      return this.result = []
    }
    const { data } = await this.request({
      url: "/goods/qsearch",
      data: {
        query: this.keyword
        // cid: this.activeId
      }
    })
    this.result = data
}

对搜索功能进行防抖处理

防抖节流,参考: https://juejin.cn/post/7049583495936475150, 代码

防抖:持续触发不执行,不触发一段时间之后,才执行

节流:持续触发也执行,只是执行的频率变低了

对输入框的防抖处理

  // 获取搜索建议商品  --> 函数防抖处理
  searchPrd () {
    this.timer && clearTimeout(this.timer)
    this.timer = setTimeout(async () => {
      // 如果关键词为空=》清除历史建议商品列表
      if (!this.keyword) {
        return this.result = []
      }
      const { msg, data } = await this.request({
        url: "/goods/qsearch",
        data: {
          query: this.keyword
          // cid: this.activeId
        }
      })

      this.result = data
      
    }, 600)
  }

搜索-跳转到结果页(跳转传参)

input组件上有confirm事件, 直接添加事件监听即可。

在回调函数中,通过navigateTo进行跳转即可

实现步骤

  1. input绑定confirm事件(测试时,使用回车确认)
  2. 搜索确认时,路由跳转到结果页并携带查询参数

代码

search.vue

在模板中补充事件监听

      <input
+        @confirm="goResult"
        @input="searchPrd"
        @focus="search"
        v-model="keyWord"
        type="text"
        placeholder="搜索"
      />

设置软键盘上的右下角按钮的文字!!!

confirm-type=“search”

https://uniapp.dcloud.net.cn/component/input.html#confirm-type

在代码中

goResult() {
  uni.navigateTo({
    url: "/packone/list/index?query="+this.keyword
  });
},

搜索-保存和清除历史搜索记录(本地保存)

实现步骤

  1. 补充状态值:history, 初值为[],用来记录搜索历史数据。它的默认值从本地获取
  2. 搜索确认时,把搜索关键词保存到本地
  3. 点击清除按钮,清除本地搜索历史数据
      <!-- 搜索历史 -->
      <block v-if="result.length === 0">
        <div class="title">
          搜索历史
+          <span @click="clearHistory" class="clear"></span>
        </div>
        <!-- 搜索历史 -->
        <div class="history">
          <navigator
            :key="i"
            v-for="(item, i) in history"
            :url="`/packageone/list/list?query=${item}`"
            >{{ item }}</navigator>
        </div>
      </block>

状态值

data() {
  return {
    // 省略其他
    history: uni.getStorageSync("history") || []
  }
}

    clearHistory() {
      this.history = [];
      uni.removeStorageSync("history");
    },
    goResult () {
      // 1. 处理搜索历史
      this.history.push(this.keyword)
      // 去重
      this.history = [...new Set(this.history)]
      uni.setStorage({
        key: "history",
        data: this.history
      })
      
      // 2. 跳转
      uni.navigateTo({
        url: "/packageone/list/list?query=" + this.keyword
      })
    },

注意

  1. 设置history默认值,获取本地数据使用同步方法(传入字符串参数)
  2. 记录要去重

分类-搜索-结果页

实现步骤

  1. 通过onLoad生命周期获取查询参数
  2. 调用接口获取结果数据渲染

基础模板

<view>
		<!-- 筛选 -->
		<view class="filter">
		  <text class="active">综合</text>
		  <text>销量</text>
		  <text>价格</text>
		</view>
	<scroll-view class="goods" scroll-y>
		<!-- 遍历 -->
		
		<view
			class="item"
			v-for="item in 10"
			:key="item">
			<!-- 商品图片 -->
			<image class="pic" src="http://image5.suning.cn/uimg/b2c/newcatentries/0070166234-000000000630980467_1_400x400.jpg" />
			<!-- 商品信息 -->
			<view class="meta">
				<view class="name"> item.goods_name </view>
				<view class="price">
					<text></text>  item.goods_price 
					<text>.00</text>
				</view>
			</view>
		</view>
	</scroll-view>
	</view>


<style lang="scss">
.filter {
  display: flex;
  height: 96rpx;
  line-height: 96rpx;
  border-bottom: 1rpx solid #ddd;

  /* #ifdef H5 */
  position: relative;
  z-index: 99;
  /* #endif */

  text {
    flex: 1;
    text-align: center;
    font-size: 30rpx;
    color: #333;

    &.active {
      color: #ea4451;
    }
  }
}

.goods {
  position: absolute;
  width: 100%;
  top: 97rpx;
  bottom: 0;
}

.item {
  display: flex;
  padding: 30rpx 20rpx 30rpx 0;
  margin-left: 20rpx;
  border-bottom: 1rpx solid #eee;

  &:last-child {
    border-bottom: none;
  }

  .pic {
    width: 200rpx;
    height: 200rpx;
    margin-right: 30rpx;
  }

  .meta {
    flex: 1;
    font-size: 27rpx;
    color: #333;
    position: relative;
  }

  .name {
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }

  .price {
    position: absolute;
    bottom: 0;

    color: #ea4451;
    font-size: 33rpx;

    text {
      font-size: 22rpx;
    }
  }
}
</style>
	

核心代码

补充状态

  data() {
    return {
+     list: [], // 查询数据
+     total:0  // 总数据条数
    };
  },

方法

  // methods
  async getList(data) {
    const { msg, data: _d } = await this.request({
      url: "goods/search",
      data
    });
    this.total = _d.total
    if (msg.status === 200) {
      this.list.push(..._d.goods)
    }
  }

在onLoad中调用

// event 
onLoad(params) {
    this.getList(params);
}

页面渲染

    <!-- 商品列表 -->
    <scroll-view class="goods" scroll-y>
      <!-- 遍历 -->
      <view
        class="item"
        v-for="item in list"
        :key="item.goods_id"
        @click="goDetail(item.goods_id)">
        <!-- 商品图片 -->
        <image class="pic" :src="item.goods_small_logo" />
        <!-- 商品信息 -->
        <view class="meta">
          <view class="name">{{ item.goods_name }}</view>
          <view class="price">
            <text></text>{{ item.goods_price }}
            <text>.00</text>
          </view>
        </view>
      </view>
    </scroll-view>

跳转

goDetail(id) {
  uni.navigateTo({
    url: "/packone/goods/goods?id=" + id
  })
}

分类-搜索-结果页-(上拉加载)

分析

  1. 后台接口有多页的支持

  2. 检测是否到底
    两种方式实现

    1. 对于某个页面:用户滚动到页面底部会触发 onReachBottom事件。(钩子函数)
    2. 对于某个scroll-view组件:用户滚动到区域底部会触发scrolltolower事件

实现步骤

  1. 补充查询参数queryData:{query:‘’, pagenum: 1},来记录当前页码和查询关键字
  2. 在onLoad钩子中,保存查询关键字到queryData中的query
  3. 给scroll-view 添加scrolltolower事件监听,在回调中,判断是否全部加载结束,如果没有,就将pagenum+1,然后重发请求。
  4. 请求的数据要以追加的格式保存

代码

 <scroll-view @scrolltolower="getMore" class="goods" scroll-y>

状态

  data () {
    // 存储渲染相关数据
    return {
       // 列表
       list: [],
+      queryData: {
+        pagenum: 1,
+        query: ''
+      },
       total: 0
    }
  },
onLoad(params) {
  this.queryData.query = params.query
  this.getList();
},
// methods
async getList() {
  const {data} = await this.$request({
    url: "goods/search",
    data: this.queryData
  });
  this.total =data.total
  this.list.push(...data.goods)
},

// 加载更多
getMore () {
  if ( this.total === this.list.length ) return
  this.queryData.pagenum++
  this.getList()
},

补充视图

<scroll-view @scrolltolower="getMore" class="goods" scroll-y>
  <!-- 省略其他...  -->
  <!-- 列表加载完成显示 -->
  <view class="nomore" v-if="this.total > 0 && (this.total === this.list.length)">没有更多数据...</view>
</scroll-view>

购物车

商品详情-获取数据渲染

根据获取到的页面参数获取对应商品详情数据并渲染

在这里插入图片描述

商品详情-链接跳转

给商品列表页商品添加链接和ID参数

/packageone/list/list.vue

goDetail(id) {
  uni.navigateTo({
      url: `/packageone/goods/goods?id=${id}`
  }
},

代码

补充状态值

/packone/goods/index.vue

data() {
  return {
+   goods: null, // 商品详情数据
  }
},

补充methods

// methods
async getGoods(goods_id) {
  const { data } = await this.request({
    url: "/goods/detail",
    data: { goods_id }
  });
  
  this.goods = data;
}

在钩子函数中调用

onLoad ({id}) {
  if(id)  this.getGoods(id)
},

页面渲染

<!-- 商品图片 -->
    <swiper
	  v-if="goods.pics"
      class="pics"
      indicator-dots
      indicator-color="rgba(255, 255, 255, 0.6)"
      indicator-active-color="#fff"
    >
      <swiper-item v-for="item in goods.pics" :key="item.pics_id">
        <image class="pics" :src="item.pics_mid" />
      </swiper-item>
    </swiper>
<!-- 基本信息 -->
    <view class="meta">
      <view class="price">{{goods.goods_price}}</view>
      <view class="name">{{goods.goods_name}}</view>
      <view class="shipment">快递: 免运费</view>
      <text class="collect icon-star">收藏</text>
    </view>

<!-- 商品详情 -->
    <view class="detail">
      <view v-html="goods.goods_introduce"></view>
      <!-- <rich-text :nodes="goods.goods_introduce"></rich-text> -->
    </view>

注意

使用vue指令v-html处理节点字符串

商品详情-微信button打电话-客服

利用微信的button组件实现客服功能

<button open-type="contact" class="icon-handset">联系客服</button>

注意:设置buttion的open-type=“contact”

uniapp使用vuex

uniapp是默认支持vuex的

在这里插入图片描述

cart.js

export default {
	namespaced: true,
	state: {
		list: ['测试数据,后面删除']
	},
  mutations:{
		add(state,payload) {
			state.list.push(payload)
		}
	}
}

全局getters

export default {
	carts: (state) => state.cart.list
}

store入口文件index.js

import cart from './modules/cart.js'
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters.js'
Vue.use(Vuex);//vue的插件机制

//Vuex.Store 构造器选项
const store = new Vuex.Store({
	getters,
	modules:{
		cart
	}
})
export default store

项目的入口文件main.js

import store from './store/index.js'

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

测试使用

import { mapGetters } from 'vuex'

computed: {
		...mapGetters(['carts'])
},
methods: {
  add() {
    this.$store.commit('cart/add', '新数据')
  }
}

vuex持久化

直接使用第三方包

安装包:npm i vuex-persistedstate
注意:安装之前先npm init --yes

import cart from './modules/cart.js'
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters.js'
Vue.use(Vuex);//vue的插件机制
++ import createPersistedState from 'vuex-persistedstate'  

//Vuex.Store 构造器选项
const store = new Vuex.Store({
++	plugins: [  
++	    // 可以有多个持久化实例  
++	    createPersistedState({  
++	      key: 'app_config_data',  // 状态保存到本地的 key   
++	      paths: ['cart'],  // 要持久化的状态,在state里面取,如果有嵌套,可以  a.b.c   
++	      storage: {  // 存储方式定义  
++	        getItem: (key) => uni.getStorageSync(key), // 获取  
++	        setItem: (key, value) => uni.setStorageSync(key, value), // 存储  
++	        removeItem: (key) => uni.removeStorageSync(key) // 删除  
++	      }  
++	    })  
++	  ]  ,
	getters,
	modules:{
		cart
	}
})
console.log(store)
export default store

购物车-添加

实现步骤

  1. 定义购物车列表变量
  2. 绑定点击事件,获取当前商品数据:goods_id, goods_name, goods_price, goods_small_logo, goods_count(数量),goods_checked(是否被选中)
  3. 存入数组(判断是否加过)
  4. 加入小红点,提示商品数量信息

代码

补充添加到购物车的逻辑

/packone/goods/index.vue

<!-- 添加数量显示-绝对定位 -->
+ <text class="cart-count" v-if="carts.length">{{carts.length}}</text>
<!-- 进入购物车 -->
<text class="cart icon-cart" @click="goCart">购物车</text>
<!-- 添加商品 -->
+ <text @click="addCart" class="add">加入购物车</text>

补充购物车数据

computed:{
  ...mapGetters(['cart'])
}

补充添加购物车的回调函数

add(state,payload) {
  let good = state.list.find(item => item.goods_id === payload.goods_id)
        
  // 没有=》新增
  if (!good) {
    state.list.push(payload)
  } else {
    // 有, 直接 数量加一
    good.goods_count++
  }
}

补充goCart方法

goCart() {
  // 调用vuex的muations添加到购物车
  const goods = {
    goods_id: this.goodInfo.goods_id,  
    goods_name: this.goodInfo.goods_name,  
    goods_price:this.goodInfo.goods_price,  
    goods_small_logo:this.goodInfo.goods_small_logo,   
    goods_count:1, // 一次加1件商品
    goods_checked: true
  }
  this.$store.commit('cart/add', goods)
}

购物车-列表渲染

在这里插入图片描述

代码

定义计算属性,得到购物车数据

/pages/cart/index.vue

import { mapGetters } from 'vuex'
export default {
  computed: {
	  ...mapGetters(['carts'])
  }
}

页面渲染

/pages/cart/cart.vue

<view class="shopname">优购生活馆</view>
<view class="goods" :key="item.goods_id" v-for="(item,index) in carts">
  <!-- 商品图片 -->
  <image
    class="pic"
    :src="item.goods_small_logo"
  />
  <!-- 商品信息 -->
  <view class="meta">
    <view class="name">{{item.goods_name}}</view>
    <view class="price">
      <text></text>{{item.goods_price}}
      <text>.00</text>
    </view>

购物车-修改数量

实现步骤

  1. 绑定事件,传入changeCount(索引值,-1)代表加减和索引值
  2. 更新本地存储数据
  3. 根据库存处理边界

绑定事件

/pages/cart/cart.vue

<!-- 加减 -->
<view class="amount">
  <!-- 绑定事件 -->
  <text class="reduce" @click="changeCount(index, -1)">-</text>
  <input
         type="number"
         disabled
         v-model="item.goods_count"
         class="number"
         />
  <text class="plus" @click="changeCount(index, 1)">+</text>
</view>

回调函数

/pages/cart/cart.vue

changeCount(idx, step) {
  this.$store.commit('cart/changeCount', {idx, step})
},

补充mutations

export default {
	namespaced: true,
	state: {
		list: []
	},
	mutations:{
		changeCount(state, {idx, step}){
			let count = state.list[idx].goods_count;
			// 1. 最大3
			if (step === 1 && count >= 3) {
				return;
			} else if (step === -1 && count === 1) {
				// 2. 最小1
				return;
			}
			// 执行加减操作			
			state.list[idx].goods_count += step
		},
	}
}

购物车-选中状态

实现步骤

  1. 绑定事件,处理商品选中状态(单选和全选)=》做取反
  2. 过滤购物车数据,获取当前选中商品数量

处理单选

goods_checked

在这里插入图片描述

每件商品都有一个goods_checked属性,用来记录它当前是否被选中。

取反。选中还是没有选中就是颜色的区别

<!-- 选框 -->
<view class="checkbox">
  <icon
+       @click="switchSingle(index, !item.goods_checked)"
        type="success"
        size="20"
+       :color="item.goods_checked?'#ea4451':'#ccc'"
        ></icon>
</view>
		switchSingle(state, {idx, val}){
			
			state.list[idx].goods_checked = val
		},

组件中

switchSingle(idx, val) {
				this.$store.commit('cart/switchSingle', {idx, val})
			},

处理全选

根据当前选中的商品===购物车中商品总数量 =〉取反

<!-- 其它 -->
<view class="extra">
  <label
      class="checkall"
+     @click="isSelAll=!isSelAll"
   >
    <icon
    type="success"
+   :color="isSelAll?'#ea4451':'#ccc'"
    size="20"></icon>全选
  </label>

在getters中补充计算属性isSelAll

getters.js

export default {
	carts: (state) => state.cart.list,
	goodsCount: (state) => state.cart.list.reduce((acc,cur)=>acc+cur.goods_count, 0),

	isSelAll(state){
		return {
			get() {
				return state.cart.list.every(item => item.goods_checked)
			},
			set(val) {
				state.cart.list.forEach(item => item.goods_checked = val)
			}
		}
	}
}

购物车-计算总金额(计算属性)

补充使用计算属性

totalMoney

getters.js

	selectedCarts: (state) => {
		return state.cart.list.filter(item => item.goods_checked)
	},
	totalMoney(state, getters) {
		return getters.selectedCarts.reduce((acc, cur) => acc+cur.goods_price*cur.goods_count, 0)
	},

模板

<view class="total">
  合计:
  <text></text>
  <label>{{totalMoney}}</label>
  <text>.00</text>
</view>

购物车-设置收货地址

获取当前微信账号的收货地址并渲染

说明:选择和编辑收货地址的页面,由微信客户端提供

微信客户端提供收货地址,有对应的API: uni.chooseAddress

实现步骤

  1. 定义地址变量
  2. 使用uni.chooseAddress()获取地址数据
data () {
    return {
-      // 收货地址
+      address: null
    }
},
computed: {
    addr() {
      return (
        this.address &&
        this.address.provinceName +
        this.address.cityName +
        this.address.countyName +
        this.address.detailInfo
      )
    }
},  
// method  
getAddress() {
  uni.chooseAddress({
    success: res => {
      // console.log(res);
      this.address = res;
    }
  });
}

视图

<!-- 收货信息 -->
<view class="shipment">
  <block v-if="address">
    <view class="dt">收货人:</view>
    <view class="dd meta">
      <text class="name">{{address.userName}}</text>
      <text class="phone">{{address.telNumber}}</text>
    </view>
    <view class="dt">收货地址:</view>
    <view class="dd">{{addr}}</view>
  </block>
  <!-- 获取用户地址 -->
  <button v-else @click="getAddress">获取收获地址</button>
</view>

微信小程序配置一下

manifest.json中

"mp-weixin" : {
        "appid" : "wx9a269d229bbe7bb2",
        "setting" : {
            "urlCheck" : false
        },
        "usingComponents" : true,
        "permission" : {},
++    		"requiredPrivateInfos":[
++    			"chooseAddress"
    		]
    },

创建订单-准备

/pages/cart/cart.vue

<view
+     @click="createOrder"
      class="pay">
  结算({{checkedPrd.length}})
</view>
// 创建订单
createOrder() {
  // 有收货地址和选中至少一件商品
  if (!this.address || !this.checkedPrd.length) {
    return uni.showToast({
      title: "请填写收货地址和添加商品!",
      icon: "none"
    });
  }
  // 是否登录
  if (!uni.getStorageSync("token")) {
    // 跳转登录页
    return uni.navigateTo({
      url: "/packageone/auth/auth"
    });
  }

  // 调用接口:创建订单
	// ...
}

我的-个人中心

一个全新的页面

模板

/pages/profile/profile.vue

<template>
  <view class="wrapper">
    <!-- 个人资料 -->
    <view class="profile">
      <view class="meta">
        <image
          class="avatar"
          :src="avatarUrl"
        />
        <text class="nickname">{{nickName}}</text>
      </view>
    </view>
    <!-- 统计 -->
    <view class="count">
      <view class="cell">
        8
        <text>收藏的店铺</text>
      </view>
      <view class="cell">
        14
        <text>收藏的商品</text>
      </view>
      <view class="cell">
        18
        <text>关注的商品</text>
      </view>
      <view class="cell">
        84
        <text>我的足迹</text>
      </view>
    </view>
    <!-- 我的订单 -->
    <view class="orders">
      <view class="title">我的订单</view>
      <view class="sorts">
        <text class="icon-bill">待付款</text>
        <text class="icon-car">待收货</text>
        <text class="icon-money">退款/退货</text>
        <text class="icon-list">全部订单</text>
      </view>
    </view>
    <!-- 地址管理 -->
    <view class="address icon-arrow">收货地址</view>
    <!-- 其它 -->
    <view class="extra">
      <view class="item icon-arrow">联系客服</view>
      <button class="item icon-arrow">分享优购</button>
    </view>
  </view>
</template>

<script>
export default {
	data() {
		return {
			avatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
			nickName: '点击登录'
		}
	},
	methods: {
		hLogin() {
			uni.navigateTo({
				url:'/packageone/auth/auth'
			})
		}
	}
};
</script>

<style scoped lang="scss">
.wrapper {
  position: absolute;
  top: 0;
  bottom: 0;

  width: 100%;
  background-color: #f4f4f4;
}

.profile {
  height: 375rpx;
  background-color: #ea4451;
  display: flex;
  justify-content: center;
  align-items: center;

  .meta {
    .avatar {
      width: 140rpx;
      height: 140rpx;
      border-radius: 50%;
      border: 2rpx solid #fff;
    }

    .nickname {
      display: block;
      text-align: center;
      margin-top: 20rpx;
      font-size: 30rpx;
      color: #fff;
    }
  }
}

.count {
  display: flex;
  margin: 0 20rpx;
  height: 100rpx;
  text-align: center;
  border-radius: 4rpx;
  background-color: #fff;

  position: relative;
  top: -27rpx;

  .cell {
    flex: 1;
    padding-top: 16rpx;
    font-size: 27rpx;
    color: #333;
  }

  text {
    display: block;
    font-size: 24rpx;
  }
}

.orders {
  margin: -17rpx 20rpx 0 20rpx;
  padding: 20rpx 0;
  background-color: #fff;
  border-radius: 4rpx;

  .title {
    padding-left: 20rpx;
    font-size: 30rpx;
    color: #333;
    padding-bottom: 20rpx;
    border-bottom: 1rpx solid #eee;
  }

  .sorts {
    padding-top: 30rpx;
    text-align: center;
    display: flex;
  }

  [class*="icon-"] {
    flex: 1;
    font-size: 24rpx;

    &::before {
      display: block;
      font-size: 48rpx;
      margin-bottom: 8rpx;
      color: #ea4451;
    }
  }
}

.address {
  line-height: 1;
  background-color: #fff;
  font-size: 30rpx;
  padding: 25rpx 0 25rpx 20rpx;
  margin: 10rpx 20rpx;
  color: #333;
  border-radius: 4rpx;
}

.extra {
  margin: 0 20rpx;
  background-color: #fff;
  border-radius: 4rpx;

  .item {
    line-height: 1;
    padding: 25rpx 0 25rpx 20rpx;
    border-bottom: 1rpx solid #eee;
    font-size: 30rpx;
    color: #333;
  }

  button {
    text-align: left;
    background-color: #fff;

    &::after {
      border: none;
      border-radius: 0;
    }
  }
}

.icon-arrow {
  position: relative;

  &::before {
    position: absolute;
    top: 50%;
    right: 20rpx;
    transform: translateY(-50%);
  }
}
</style>

使用API能力拨打电话

<view @click="callSer" class="item icon-arrow">联系客服</view>
callSer() {
  uni.makePhoneCall({
    phoneNumber: "10086"
  });
}

分享小程序

<button class="item icon-arrow" open-type="share">分享优购</button>

登录支付

auth

初始模板

<template>
	<view class="auth">
		<view class="title">ugo-登录</view>
		<button class="loginBtnAvatar" open-type="chooseAvatar">
			<image class="avatar" :src="avatarUrl"></image>
		</button>
		
		<view>
			<input type="nickname" placeholder="点击修改昵称" v-model="nickName"/>
		</view>
		<button class="loginBtn" size="mini"  @click="hLogin" type="primary">
			登录
		</button>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				avatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
				nickName: ''
			}
		},
		methods: {
			hLogin(user) {
				// 实现登录功能
			}
		}
	}
</script>

<style lang="scss">
.auth {
	
	padding:50rpx;
	text-align: center;
	.title{
		margin: 40rpx auto;
	}
	.loginBtnAvatar {
		width: 200rpx;
		height: 200rpx;
		border-radius: 50%;
		padding:0px;
		margin: 40rpx auto;
	}
	.avatar {
		width: 200rpx;
		height: 200rpx;
		border-radius: 50%;
		border: 2rpx solid #fff;
	}

	.nickname {
		display: block;
		text-align: center;
		margin-top: 20rpx;
		font-size: 30rpx;
		color: #fff;
	}
	.loginBtn {
		width: 200rpx;
		margin: 50rpx auto;
		display: block;
	}
}
</style>

获取用户头像

模板:添加chooseavatar事件回调

<button class="loginBtnAvatar" open-type="chooseAvatar" @chooseavatar="getAvatar">

代码

补充getAvatar功能

		methods: {
			getAvatar(e){
				console.log(e)
				this.avatarUrl = e.detail.avatarUrl
			}
    }

微信登录-流程

流程图

在这里插入图片描述

名词解释:

  • code 临时登录凭证, 有效期由微信官方决定, 通过 wx.login() 获取
  • session_key 会话密钥, 服务端通过 code2Session 获取(后端负责处理)
  • openId 用户在该小程序下的用户唯一标识, 永远不变, 服务端通过 code 获取
  • unionId 用户在同一个微信开放平台帐号(公众号, 小程序, 网站, 移动应用)下的唯一标识, 永远不变
  • appId 小程序唯一标识
  • appSecret 小程序的 app secret, 可以和 code, appId 一起换取 session_key

其他名词

  • rawData 不包括敏感信息的原始数据字符串,用于计算签名
  • encryptedData 包含敏感信息的用户信息, 是加密的
  • signature 用于校验用户信息是否无篡改
  • iv 加密算法的初始向量

扩展阅读:微信登录

微信登录-实操

实现步骤

  1. 通过getUserProfile方法,获取微信用户信息
  2. 使用微信用户信息来调用ugo的接口登录,获取token
  3. 根据登录接口文档所需参数,调用接口
<script>
	export default {
		data() {
			return {
				avatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
				nickName: ''
			}
		},
		onShow() {
			const userInfo = uni.getStorageSync('userInfo') || {}
			this.avatarUrl = userInfo.avatarUrl
			this.nickName = userInfo.nickName
		},
		async onLoad() {
			const res = await uni.login()
			console.log(res)
			this.code = res.code
		},
		methods: {
			getAvatar(e){
				console.log(e)
				this.avatarUrl = e.detail.avatarUrl
			},
			// 调用接口登录
			async hLogin(user) {
				// 登录所需参数
				const res = await uni.getUserProfile({
					desc: '获取微信用户信息'
				});
				console.log(res)
				const {
					encryptedData,
					iv,
					rawData,
					signature
				} = res
				
				const {data} = await this.request({
					url: "/users/wxlogin",
					method: "post",
					data: {
						encryptedData,
						iv,
						rawData,
						signature,
						code: this.code
					}
				});
				console.log(data);
        // 保存token
				uni.setStorageSync("token", data.token)
        // 保存用户更新后的头像和昵称 -- 只是纯前端处理,服务器中并没有对应的接口来保存修改结果
				uni.setStorageSync("userInfo", {avatarUrl: this.avatarUrl, nickName: this.nickName})
				// 后退
        uni.navigateBack()
			}
		}
	}
</script>

异常情况说明

登录需要的后端接口appid: wxfb52f2d7b2f6123a(需要该appid拥有者添加开发者权限)调用失败,是由于当前开发者AppID和服务器端使用的不一致造成

实际工作中,开发小程序=》向公司提供自己的微信号,管理员添加开发者权限=》使用公司的appId进行小程序的开发需要使用和后端接口一致的 appid

同学们没法调用登录接口(大家不是本小程序的开发者)获取token,直接使用老师提供的token开发即可

注意⚠️:如果出现Error: Illegal Buffer错误说明

创建订单-实现

实现步骤

  1. 处理创建订单接口需要的参数
    https://www.showdoc.com.cn/128719739414963/2612148628877795
  2. 创建订单
  3. 更新本地购物车
  4. 成功跳转到订单列表页

代码

/pages/cart/cart.vue

// 创建订单
async createOrder() {
  // 有收货地址和选中至少一件商品
  if (!this.address || !this.selectedCarts.length) {
    return uni.showToast({
      title: "请填写收货地址和添加商品!",
      icon: "none"
    });
  }
  // 是否登录
  if (!uni.getStorageSync("token")) {
    // 跳转登录页
    return uni.navigateTo({
      url: "/packageone/auth/auth"
    });
  }
  // 调用接口:创建订单
  await this.request({
    url: "/my/orders/create",
    method: "post",
    data: {
      order_price: this.totalMoney,
      consignee_addr: this.addr,
      goods: this.selectedCarts.map(item => {
              item.goods_number = item.goods_count;
              return item;
     })
    }
  });

  // 订单成功创建:清空购物车中已经被提交的数据
  this.$store.commit('cart/removeSelected')
  // 2. 跳转到订单页面
  uni.navigateTo({
    url: '/packageone/order/order'
  })
}

添加mutations

在cart.js中补充mutations

store/modules/cart.js

	mutations: {
    // 省略其他...
		removeSelected:(state) => {
			state.list = state.list.filter(it => !it.goods_checked)
		}
  }

订单列表

获取订单列表数据并渲染.它是一个独立的页面,需要在packageone包中配置

在这里插入图片描述

<template>
  <view class="wrapper">
    <!-- 订单状态 -->
    <view class="tabs">
      <text class="active">全部</text>
      <text>待付款</text>
      <text>已付款</text>
      <text>退款/退货</text>
    </view>
    <!-- 订单 -->
    <scroll-view class="orders" scroll-y>
      <view class="item">
        <!-- 订单中包含哪些商品-->
        <block>
        	<!-- 商品图片 -->
        	<image class="pic" src="http://static.botue.com/ugo/uploads/goods_1.jpg" />
        
          <!-- 商品信息 -->
          <view class="meta">
            <view class="name">【海外购自营】黎珐(ReFa) MTG日本 CARAT铂金微电流瘦脸瘦身提拉紧致V脸美容仪 【保税仓发货】</view>
            <view class="price">
              <text></text>1399
              <text>.00</text>
            </view>
            <view class="num">x1</view>
          </view>
        </block>
        <!-- 总价 -->
        <view class="amount">1件商品 总计: ¥4099(含运费0.00)</view>
        <!-- 其它 -->
        <view class="extra">
          订单号: GD20180511000000000178
          <button size="mini" type="primary">支付</button>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script>
export default {};
</script>

<style scoped lang="scss">
.tabs {
  display: flex;
  height: 96rpx;
  line-height: 96rpx;
  background-color: #fff;
  box-shadow: 0 4rpx 10rpx #ccc;

  text {
    flex: 1;
    text-align: center;
    font-size: 27rpx;
    color: #333;

    &.active {
      color: #ea4451;
    }
  }
}

.orders {
  width: 100%;
  background-color: #f4f4f4;

  position: absolute;
  top: 97rpx;
  bottom: 0;
}

.item {
  padding: 30rpx 20rpx 0;
  margin-top: 16rpx;
  background-color: #fff;

  .pic {
    width: 200rpx;
    height: 200rpx;
    float: left;
  }

  .meta {
    height: 200rpx;
    margin-left: 230rpx;
    font-size: 27rpx;
    color: #333;
    position: relative;
  }

  .name {
    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }

  .price {
    position: absolute;
    bottom: 0;

    color: #ea4451;
    font-size: 33rpx;

    text {
      font-size: 22rpx;
    }
  }

  .num {
    position: absolute;
    bottom: 0;
    right: 20rpx;
    color: #333;
  }

  .amount {
    text-align: right;
    padding: 20rpx;
    font-size: 24rpx;
    border-top: 1rpx solid #eee;
    border-bottom: 1rpx solid #eee;
    margin-top: 20rpx;
    color: #999;
  }

  .extra {
    padding: 30rpx;
    font-size: 24rpx;
    color: #999;
    position: relative;

    button {
      position: absolute;
      right: 20rpx;
      font-size: 24rpx;
      margin-top: -10rpx;
    }
  }
}
</style>

实现步骤

  1. 判断是否登录,没有跳转=》登录页面
  2. 调用接口获取所有订单数据并绑定模版渲染

补充数据项

/packageone/order/order.vue

data() {
  return {
    // 订单列表数据
    orders: []
  };
},

获取订单数据

onShow() {
  // 判断是否登录
  if (!uni.getStorageSync("token")) {
    return uni.navigateTo({ url: "/package/auth/auth" });
  }
  this.getOrders()
},
methods:{
  async getOrders() {
    // 获取订单列表
    let { msg, data } = await this.request({
      url: "/my/orders/all",
      data: {
        type: 1 // type为1表示获取全部的订单
      }
    });
    this.orders = data.orders;
  }
}

页面渲染

<!-- 订单 -->
<scroll-view class="orders" scroll-y>
  <!-- 订单列表 -->
  <view class="item" v-for="order in orders" :key="order.order_number">
    <!-- 订单中包含哪些商品 -->
    <block v-for="prd in order.goods" :key="prd.goods_id">
      <!-- 商品图片 -->
      <image class="pic" :src="prd.goods_small_logo" />
      <!-- 商品信息 -->
      <view class="meta">
        <view class="name">{{ prd.goods_name }}</view>
        <view class="price">
          <text></text>{{ prd.goods_price }}
          <text>.00</text>
        </view>
        <view class="num">x{{ prd.goods_number }}</view>
      </view>
    </block>
    <!-- end -->
    <!-- 总价 -->
    <view class="amount">{{ order.goods.length }}件商品 总计:{{order.total_price}}(含运费0.00)
    </view>
    <!-- 其它 -->
    <view class="extra">
      订单号: {{ order.order_number }}
      <button size="mini" type="primary">支付</button>
    </view>
  </view>
</scroll-view>

微信支付-流程分析

了解小程序支付流程。支付业务流程

在这里插入图片描述

微信支付,在小程序端要做三件事:

  1. 使用**wx.login**获取临时登录凭证code,发送到后端获取openId=》微信登录
  2. **openId**以及相应需要的商品信息发送到后端,换取服务端进行支付的签名等信息=》创建订单
  3. 接收返回的信息(必须要包含发起微信支付**wx.requestPayment的参数**),发起微信支付

前端:主要处理第三步,获取支付信息,调起支付窗口

微信支付-实现

整体步骤

  1. 创建订单
    • 请求创建订单的 API 接口:把(订单金额、收货地址、订单中包含的商品信息)发送到服务器
    • 服务器响应的结果:订单编号
  1. 订单预支付
    • 请求订单预支付的 API 接口:把(订单编号)发送到服务器
    • 服务器响应的结果:订单预支付的参数对象,里面包含了订单支付相关的必要参数
  1. 发起微信支付
    • 把步骤 2 得到的 “订单预支付对象” 作为参数传递给 uni.requestPayment() 方法实现付款

核心代码

模板绑定点击事件

/packageone/order/order.vue

<!-- 其它 -->
<view class="extra">
  订单号: {{order.order_number}}
  <button
+         @click="pay(order.order_number)"
          size="mini"
          type="primary"
          >支付</button>
</view>

代码中的处理逻辑

  1. 调用后台支付接口,传入订单号
  2. 成功后,调起微信支付窗口=>uni.requestPayment

/packageone/order/order.vue

// 支付
async pay(order_number) {
  //  1. 发送支付请求
  let { data } = await this.request({
    url: "/my/orders/req_unifiedorder",
    method: "post",
    data: {
      order_number
    }
  });

  // 2. 调起微信支付窗口,等待用户付钱
  await uni.requestPayment(data.pay);

  uni.showToast({
    title: "微信支付成功!",
    duration: 2000
  });
  
  await this.request({
    url: "/my/orders/chkOrder",
    method: "post",
    data: {
      order_number: 'HMDD20230624000000050366'
    }
  });
  //检测到订单支付完成
  uni.showToast({
     title: '订单完成!',
     icon: 'success'
  })
},

扩展阅读:https://uniapp.dcloud.io/api/plugins/payment?id=requestpayment

项目打包-上线

实现步骤

  1. 打包
  • hbuildX:发布
  • vue-cli: 执行上线打包:npm run build:mp-weixin
  1. 到微信开发者工具,导入打包生成的build/mp-weixin生产代码

  2. 导入成功之后点击=》上传=》发布体验版本 =》 通过小程序的后台查看管理版本

  3. 体验版本=》经过测试=》测试通过=》提交审核=》经过TX审核通过才能发布线上版本

小程序怎么做支付(例如在商场小程序A买东西)

  1. 登录 小程序A,拿到token

    1. uni.login --------> code
    2. uni.getUserInfo ------> xx,xxx,xx,xxx
    3. 调用后端给定接口,把上面两步的参数传入,就可以拿到token

在这里插入图片描述

2.创建订单

    1. 调用后端给定接口(带token)

在这里插入图片描述

  1. 支付订单(根据订单号)

    1. 调用后端给定接口(带token,带订单号 ) -------> 支付信息 pay
    2. 调用uni.requestpayment(pay信息) ------> 弹框(模拟器:二维码,真机:支付界面)----> 用户确认付钱!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bcikGVdH-1688129651236)(C:\Users\ZhengKaiYa\Desktop\uniapp1\26.png)]

搜索框防抖

<input @input="hRearch">



  hRearch(){}{
     如果搜索框有内容,就发请求()
  }

  // 防抖
  hRearch(){}{
  	clearTimeout(this.定时器ID)
    this.定时器ID = setTimeout(() => {

        如果搜索框有内容,就发请求()
      
    }, 500)
  }

  // 节流
  hRearch(){}{
  	if(Date.now() - this.上次执行时间 > 500) {
      如果搜索框有内容,就发请求()
      this.上次执行时间 = Date.now()
    }
  }

ds_name }}

{{ prd.goods_price }}
.00

x{{ prd.goods_number }}





共{{ order.goods.length }}件商品 总计: ¥{{order.total_price}}(含运费0.00)



订单号: {{ order.order_number }}
支付



## 微信支付-流程分析

了解小程序支付流程。[支付业务流程](https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3)

[外链图片转存中...(img-JItQYFWW-1688129651235)]



微信支付,在小程序端要做三件事:

1. 使用`**wx.login**`获取临时登录凭证code,发送到后端获取openId=》微信登录
2. 将`**openId**`以及相应需要的商品信息发送到后端,换取服务端进行支付的签名等信息=》创建订单
3. 接收返回的信息(必须要包含发起微信支付`**wx.requestPayment的参数**`),发起微信支付



前端:主要处理第三步,获取支付信息,调起支付窗口



## 微信支付-实现

**整体步骤**

1. **创建订单** 

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

1. **订单预支付** 

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

1. **发起微信支付** 

- - 把步骤 2 得到的 “订单预支付对象” 作为参数传递给 `uni.requestPayment()` 方法实现付款

**核心代码**

模板绑定点击事件

/packageone/order/order.vue

```js
<!-- 其它 -->
<view class="extra">
  订单号: {{order.order_number}}
  <button
+         @click="pay(order.order_number)"
          size="mini"
          type="primary"
          >支付</button>
</view>

代码中的处理逻辑

  1. 调用后台支付接口,传入订单号
  2. 成功后,调起微信支付窗口=>uni.requestPayment

/packageone/order/order.vue

// 支付
async pay(order_number) {
  //  1. 发送支付请求
  let { data } = await this.request({
    url: "/my/orders/req_unifiedorder",
    method: "post",
    data: {
      order_number
    }
  });

  // 2. 调起微信支付窗口,等待用户付钱
  await uni.requestPayment(data.pay);

  uni.showToast({
    title: "微信支付成功!",
    duration: 2000
  });
  
  await this.request({
    url: "/my/orders/chkOrder",
    method: "post",
    data: {
      order_number: 'HMDD20230624000000050366'
    }
  });
  //检测到订单支付完成
  uni.showToast({
     title: '订单完成!',
     icon: 'success'
  })
},

扩展阅读:https://uniapp.dcloud.io/api/plugins/payment?id=requestpayment

项目打包-上线

实现步骤

  1. 打包
  • hbuildX:发布
  • vue-cli: 执行上线打包:npm run build:mp-weixin
  1. 到微信开发者工具,导入打包生成的build/mp-weixin生产代码

  2. 导入成功之后点击=》上传=》发布体验版本 =》 通过小程序的后台查看管理版本

  3. 体验版本=》经过测试=》测试通过=》提交审核=》经过TX审核通过才能发布线上版本

小程序怎么做支付(例如在商场小程序A买东西)

  1. 登录 小程序A,拿到token

    1. uni.login --------> code
    2. uni.getUserInfo ------> xx,xxx,xx,xxx
    3. 调用后端给定接口,把上面两步的参数传入,就可以拿到token

[外链图片转存中…(img-SfGZ2WYw-1688129651236)]

2.创建订单

    1. 调用后端给定接口(带token)

在这里插入图片描述

  1. 支付订单(根据订单号)

    1. 调用后端给定接口(带token,带订单号 ) -------> 支付信息 pay
    2. 调用uni.requestpayment(pay信息) ------> 弹框(模拟器:二维码,真机:支付界面)----> 用户确认付钱!

[外链图片转存中…(img-bcikGVdH-1688129651236)]

搜索框防抖

<input @input="hRearch">



  hRearch(){}{
     如果搜索框有内容,就发请求()
  }

  // 防抖
  hRearch(){}{
  	clearTimeout(this.定时器ID)
    this.定时器ID = setTimeout(() => {

        如果搜索框有内容,就发请求()
      
    }, 500)
  }

  // 节流
  hRearch(){}{
  	if(Date.now() - this.上次执行时间 > 500) {
      如果搜索框有内容,就发请求()
      this.上次执行时间 = Date.now()
    }
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值