Bootstrap

原生微信小程序基础-分包加载&&自定义组件&&&项目全流程

小程序基础-分包加载&&自定义组件

小程序分包加载

小程序分包加载-为什么要分包加载

  • 微信平台对小程序单个包的代码体积限制为 2M,超过 2M 的情况下可以采用分包来解决
  • 即使小程序代码体积没有超过 2M 时也可以拆分成多个包来实现按需加载
  • 配置文件能忽略的只有静态资源,代码无法被忽略

在这里插入图片描述

配置忽略文件

project.config.json

{
  "description": "项目配置文件",
  "packOptions": {
    "ignore": [
      {
        "value": "static/uploads",
        "type": "folder"
      }
    ],
    "include": []
  },

type: 表示要忽略的资源类型

value: 表示具体要忽略的

小程序分包加载-使用分包配置

分类:

  1. 主包:

app.json

{
  // 省略其他的...
  
  "subPackages": [
      {
        "root": "subpkg_user", // 分包代码的目录,其实就是一个独立的文件夹
        "pages": [
          "pages/profile/profile"
        ]
      },
      {
        "root": "subpkg_order", // 文件夹
        "pages": [
          "pages/order_list/index", 
          "pages/order_list/index"
        ]
      }
  ]
}

注意: 写完分包之后,如果对应的文件夹和页面不存在,它会自动创建文件夹和页面

小程序分包—预加载

https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/preload.html

在打开小程序启动的时候只下载主包代码,分包并不会下载,因此能够提升小程序启动时的打开速度,但是分包的代码只有在访问到分包的页面时才去下载,这样用户就需要有一定时间的等待(一般不太影响),通过分包预加载技术可以实现提前去下载分包的代码,这样分包页面的访问速度也会得到提升。
小程序通过 preloadRule 配置需要预加载的分包。

app.json

{   ......
	"preloadRule": {
    "页面地址,进入这个页面就需要预加载分包": {       // pages/index/index 
      "network": "网络环境",                     // "wifi"
      "packages": ["要预加载的包名"]              //["goods_pkg"]
    }
  },   ......
}      //当用户访问到 pages/index/index 时,在 wifi 网络前提下预先下载 goods_pkg 分包的代码。
  • 指定某个页面路径做为 key,含义是当访问这个页面时会去预加载一个分包
  • network 预加载分包的网络条件,可选值为 all、wifi,默认为 wifi
  • packages 指定要预下载的分包名或根路径

配置完成之后,访问指定页面时,就会在控制台输出提示。

在这里插入图片描述

自定义组件—基本使用

创建组件

通常将项目中的组件都放在一个独立的目录下,并且一般就给这个文件夹取名为:components 。这个目录需要我们手动进行创建。

  1. 新建一个目录:components

  2. 在components上点击鼠标右键,选择「新建Component」

  3. 填入组件的名字。它会自动创建4个同名的文件。(这一点和创建页面是一样的)

在这里插入图片描述

组件和页面的结构区别:

  1. 组件的配置文件(.json文件)中,有一个配置项:component: true
  2. 组件的 .js 文件中调用 Component 函数,页面的.js文件中调用Page函数

注册组件

  • 页面注册是在使用组件的(xxxx.json)中通过 usingComponents 进行注册,只能在当前页面中组件

  • 全局注册是在 app.json 文件中通过 usingComponents 对自定义组件进行注册,可以在任意页面中使用

"usingComponents": {
    "my-test": "/components/MyTest/index"
}

使用组件

在wxml中,直接通过标签的方式使用即可。

自定义组件—组件样式
  1. 组件中的样式不要使用标签选择器
  2. 组件中,样式默认是隔离的: 自定义组件的样式只受到自定义组件 wxss 的影响
  3. 通过对组件的配置,可以取消这个隔离的状态

样式隔离注意点

  • app.wxss中的全局样式对组件无效

  • 只有class选择器具有样式隔离效果,id选择器、属性选择器、标签选择器不受样式隔离的影响

建议:在组件和引用组件的页面中建议使用class选择器,不要使用id、属性、标签选择器

修改组件样式的隔离选项

默认情况下,自定义组件的样式隔离特性能够防止组件内外样式互相干扰的问题。但有时,我们希望外界能够控制组件内部的样式,此时,可以通过在组件的.js文件中设置: options → addGlobalClass 为true

XX.js

Component({
  options: {
    addGlobalClass: true
  }
})

在页面中设置的同类名的选择器就能作用于子组件内部的元素。但是,组件内的class选择器,不能影响页面的元素。

自定义组件—组件样式-外部样式类

组件希望接受外部传入的样式类。此时可以在 Component 中用 externalClasses 定义若干个外部样式类。

在开发组件时,主动暴露给组件使用者,修改组件内部样式

在这里插入图片描述

组件 custom-component.js

/* 组件 custom-component.js */
Component({
  externalClasses: ['my-class']
});

组件 custom-component.wxml

<!-- 组件的wxml -->
<!-- 这里的my-class相当于一个占位符 -->
<view class="my-class">components/MyTest/index.wxml</view>

页面的 WXML

<!-- 页面的 WXML -->
<custom-component my-class="red-text" />
<custom-component my-class="large-text" />

页面的wxss

.red-text{ color: red; }
.large-text {font-size: 50px; }

外部样式类相当于用一个类名去当占位符,以便于在后期使用时替换成真实的类名,方便添加额外的样式。

参考:https://vant-contrib.gitee.io/vant-weapp/#/button#wai-bu-yang-shi-lei

自定义组件—数据方法

组件的典型结构

// borderImage.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    
  },

  /**
   * 组件的初始数据
   */
  data: {
    
  },

  /**
   * 组件的方法列表
   */
  methods: {
    
  }
})

定义数据

在小程序中,用于组件模板渲染的私有数据,需要定义到data

methods方法

在小程序的组件中,事件处理函数和自定义方法需要定义到methods

自定义组件—组件插槽
单个插槽

在小程序中,默认情况下每个自定义组件中只允许使用一个插槽进行占位。

<!--components/MyTest2/index.wxml-->
<view>
  <text>components/MyTest2/index.wxml</text>
  <!-- 对于不确定的内容,可以使用slot进行占位,具体内容交给使用者确定 -->
  <slot></slot>
</view>

使用组件

<my-test2>
  <!-- 这里的内容将被放到组件中<slot>的位置 -->
  <view>
    这里是slot里的内容
  </view>
</my-test2>
多插槽(具名插槽)

组价.js

Component({
  options: {
    multipleSlots: true // 在组件定义时的选项中启用多 slot 支持
  },
  // ... 省略其他
})

此时,可以在这个组件的 wxml 中使用多个 slot ,以不同的 name 来区分。

定义插槽

<view>
  <text>components/MyTest2/index.wxml</text>
  <!-- 对于不确定的内容,可以使用slot进行占位,具体内容交给使用者确定 -->
  <!-- <slot></slot> -->
  <slot name="before"></slot>
  <view>
    ---------这里是分割线--------
  </view>
  <slot name="after"></slot>
</view>

使用组件

<my-test2>
  <!-- 这里的内容将被放到组件中<slot>的位置 -->
  <!-- <view>
    这里是slot里的内容
  </view> -->
  <view slot="before">
    这里是before slot里的内容
  </view>
  <view slot="after">
    这里是after slot里的内容
  </view>
</my-test2>
自定义组件—生命周期

组件生命周期-lifetimes

生命周期参数描述
created在组件实例刚刚被创建时执行,此时还不能调用 setData,一般用于给组件的this添加一些自定义的属性字段
attached在组件实例进入页面节点树时执行,绝大多数初始化工作可以在这个时机进行,例如发请求获取初始数据
ready在组件在视图层布局完成后执行
moved在组件实例被移动到节点树另一个位置时执行
detached在组件实例被从页面节点树移除时执行,适合做一些清理工作
errorObject Error每当组件方法抛出错误时执行

生命周期函数要写在lifetimes里边

lifetimes: {
    created() {
      console.log('组件被created') // 这里使用setData不会引起视图的更新
      this.setData({ msg: 'abc!' })
    },
    attached() {
      this.setData({ msg: 'abcd' })
    }
  }
自定义组件-属性(父传子)

在小程序中,properties是组件的对外属性,用于接收外界传递到组件中的数据

父组件传入属性值

<my-test isOpen max="9" min="1" />

子组件.js中接收

Component({
	properties: {
    isOpen: Boolean,
  	min: Number, // 直接写类型
    max: {       // 写类型 + 初始值
    	type: Number,
      value: 10 // value用于指定默认值
    }
  }
})
自定义组件-组件通讯-自定义事件triggerEvent(子传父)

在这里插入图片描述

Vant组件库

官方文档:https://vant-contrib.gitee.io/vant-weapp/#/quickstart

步骤一 通过 npm 安装

npm i @vant/weapp -S --production

步骤二 修改 app.json

将 app.json 中的 "style": "v2" 去除

步骤三 修改 project.config.json

开发者工具创建的项目,miniprogramRoot 默认为 miniprogrampackage.json 在其外部,npm 构建无法正常工作。

需要手动在 project.config.json 内添加如下配置,使开发者工具可以正确索引到 npm 依赖的位置。

{
  ...
  "setting": {
    ...
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",
        "miniprogramNpmDistDir": "./miniprogram/"
      }
    ]
  }
}

步骤四 构建 npm 包(重点)

开发者工具上 > “工具” > “构建npm”

在这里插入图片描述

使用

去app.json(全局注册)或页面.json(局部注册)中注册

"usingComponents": {
  "van-button": "@vant/weapp/button/index"
}

在页面中使用

<van-button type="primary">按钮</van-button>

小程序开发环境-优化目录结构

项目的根目录
├── miniprogram  // 项目相关的代码夹
├── node_modules // npm包目录
├── package.json
├── project.config.json
├── package-lock.json
└── sitemap.json

在这里插入图片描述

修改 project.config.json 中的配置项

{
  // 省略其他......
  "setting": {
    // 省略其他......
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "miniprogramNpmDistDir": "./miniprogram",
        "packageJsonPath": "package.json"
      }
    ]
  },
  "miniprogramRoot": "miniprogram/"
}

启用 less/sass

通过 less/sass 可以更好的管理 css 样式,通过 project.config.json 可以启用对 less/sass 的支持。

{
  "setting": {
    "useCompilerPlugins": ["sass"]
  }
}

然后将 .wxss 文件后缀改换成 .scss 即可。

启动项目

  1. 拉取代码

  2. 导入项目

使用小程序开发者工具导入【项目】的代码

3. 使用小程序开发者工具构建 npm

  1. 安装包:npm install
  2. 手动构建: 【工具】→【构建npm】

project.config.json的几个配置

{
  "miniprogramRoot": "miniprogram/",
  "setting": {
    "useCompilerPlugins": ["sass"],
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",
        "miniprogramNpmDistDir": "./miniprogram"
      }
    ],
  }
}
  • miniprogramRoot 项目的根目录为 miniprogram

  • setting.useCompilerPlugins 启用了 sass 支持

  • packNpmRelationList 指定了 npm 构建时所需的 package.json 的位置以及构建后代码的生成位置

4. 改成自己的appid

这个项目中的appid是别人的,如果我们需要改成自己的。

基础封装-消息反馈

将所有通用的工具方法封装到 utils/utils.js 中

/**
 * 用户消息反馈
 * @param {string} title 文字提示的内容
 */
export const toast = (title = '数据加载失败...') => {
  wx.showToast({
    title,
    mask: true,
    icon: 'none',
  })
}
// 挂载到全局对象 wx
wx.$toast = toast

app.js

// 在入口中执行 utils.js
import './utils/utils.js'
App({
  // ...
})

使用

 wx.$toast('//提示文字', "icon图标")
基础封装-网络请求

安装第三方的包-构建

  1. npm install wechat-http
  2. 安装完成后还必须要构建 npm后才可以使用

wechat-http用法与 axios 类似:

  • http.baseURL 配置接口基础路径

  • http.getGET 方法发起请求

  • http.postPOST 方法发起请求

  • http.putPUT 方法发起请求

  • http.deleteDELETE 方法发起请求

  • http.intercept 配置请求和响应拦截器

  • http 本身做为函数调用也能用于发起网络请求

二次封装

新建 utils/http.js 文件

// 导入 http 模块
import http from 'wechat-http'
// 基础路径
http.baseURL = 'https://live-api.itheima.net'
// 挂载到全局对象
wx.http = http
// 普通的模块导出
export default http

以全局对象方式调用时需要在入口中执行 utils/http.js

// 执行 uitls/http.js
import './utils/http.js'
App({
  // ...
})

配置响应拦截器

// 配置响应拦截器
http.intercept.response = function ({ data, config }) {

  // 检测接口是否正常返回结果
  if (data.code !== 10000) {
    wx.$toast()
    return Promise.reject(data)
  }

  // 只保留data数据,其它的都过滤掉
  return data.data
}
跳转传参

点击公告列表后将公告的ID通过地址参数传递到公告详情页面,在公告详情页 onLoad 生命周期中读取到公告 ID,然后调用接口获取公告详情的数据。

在这里插入图片描述
在这里插入图片描述

van-count-down 组件(倒计时)的应用
<van-count-down use-slot time="{{6000}}" bind:change="countDownChange">
  <text>{{timeData.seconds}}秒后重新获取</text>
</van-count-down>

time: 指定了倒计时多少毫秒

bind:change每隔一秒的回调,它会传出来当前的倒计时信息

 countDownChange(ev) {
    console.log(ev.detail)
    this.setData({
      timeData: ev.detail,
      getCodeBtnVisible: ev.detail.minutes === 0 && ev.detail.seconds === 0,
    })
  },
表单验证插件使用

先在data中设置mobile,再在模板中进行双向绑定

model:value=“{{mobile}}”

  1. 安装构建 表单验证码插件 wechat-validate
npm install wechat-validate
  1. 将插件导入到项目中
  • behaviors 将插件注入到页面中

  • rules 由插件提供的属性,用来定义数据验证的规则(类似于 Element UI)

  • validate 由插件提供的方法,根据 rules 的规则来对数据进行验证


// 导入表单验证插件
import validate from 'wechat-validate'
Page({
  data: {
    mobile: '' // 省略其他
  },
  behaviors: [validate], // 将插件注入到页面实例中
  rules: {
    mobile: [
      {required: true, message: '请填写手机号码!'},
      {pattern: /^1[3-8]\d{9}$/, message: '请填写正确的手机号码!'}
    ]
  },
  getSMSCode() {
    // 获取验证结果
    const { valid, message } = this.validate('mobile')
    // 如果验证不合法则不再执行后面的逻辑
    if (!valid) return wx.$toast(message)
    console.log('getCode')
    this.setData({ getCodeBtnVisible: false })
  },
})
保存token-跳转
  1. 在app.js中设置setToken方法,保存到本地存储
App({
  // ...
  setToken(key, token) {
 		// 将 token 记录在应用实例中   
    this[key] = token
    // 将 token 存入本地
    wx.setStorageSync(key, token)
  }
})

2.点击登录 | 注册发送请求成功之后

  const app = getApp()    //小程序中获取全局的实例对象
  app.setToken('token', res.token)
  app.setToken('refreshToken', res.refreshToken)
 //  跳转
  const url = '/pages/profile/index'
  wx.redirectTo({ url })
登录检测-鉴权组件

在这里插入图片描述

1.在根目录中创建 components 文件夹用来存放全局的组件,然后通过小程序开发者工具创建一个名为 authorization 的组件

2.接下来全局来注册这个组件,保证任何页面中都可以直接应用 authorization 组件

{
  "usingComponents": {
    "authorization": "/components/authorization/index"
  },
}

3.到用户信息页面中应用 authorization 使用做为页面根节点

<authorization>
  	...
</authorization>

4.在authorization中补充插槽和状态

<!--components/authorization/index.wxml-->
<slot wx:if="{{isLogin}}"></slot> 
  data: {
    isLogin: false
  },

读取本地存储token

读取本地存储的 token 数据,用于判断是否曾登录过

// app.js
App({
  ......
    getToken() {
    // 将 token 数据记到应用实例中
    // return this.token = wx.getStorageSync('token')
    return this.token
  }
})

在组件内读token并处理

data: {
  isLogin: false
},
lifetimes: {
  attached() {
    const isLogin = !!getApp().getToken()    
    //const app = getApp()  const isLogin = !!app.getToken()
    this.setData({ isLogin })
    if (!isLogin) {
      wx.redirectTo({ url: '/pages/login/index' })
    }
  }
},

地址重定向,登录成功后跳回到原来的页面

在这里插入图片描述

authoirzation 组件检测登录时获取当前页面栈实例,并在跳转到登录页面时在 URL 地址上拼凑参数:

// /components/authorization/index.js
Component({
  // ...
  lifetimes: {
    attached() {
      // 获取登录状态
      const isLogin = !!getApp().token
      // 变更登录状态
      this.setData({ isLogin })
      // 获取页面栈
      const pageStack = getCurrentPages()
      // 获取页面路径
      const currentPage = pageStack.pop()
      // 未登录的情况下跳转到登录页面
      if (!isLogin) {
        wx.redirectTo({
          url: '/pages/login/index?redirectURL=/' + currentPage.route,
        })
      }
    },
  },
})
用户管理-显示默认值

app.js中添加初始值

App({
  globalData: {},
  userInfo: { avatar: '', nickName: '微信用户1' }
}

在onLoad中加载值

data: {
  avatar: '',
  nickName: ''
},
onLoad() {
  const app = getApp()
  console.log(app.userInfo)
  const { avatar, nickName } = app.userInfo
  this.setData({ avatar, nickName })
  // 用户未登录时不必请求
  app.token && this.getUserProfile()
},
配置请求拦截器

将用户的登录状态通过自定义的头信息 Authorization 随接口调用时一起发送到服务端。

// 导入 wechat-http 模块
import http from 'wechat-http'
// 配置接口基础路径
http.baseURL = 'https://live-api.itheima.net'
// 配置请求拦截器

http.intercept.request = function (options) {
  console.log('请求拦截器', options.header)
  // 扩展头信息
  const defaultHeader = {}
  // 身份认证
  const token = getApp().getToken()
  if (token) {
    defaultHeader.Authorization = 'Bearer ' + getApp().getToken()
  }
  // 与默认头信息合并
  options.header = Object.assign({}, defaultHeader, options.header)
  // 处理后的请求参数
  return options
}

注:传递 token 时需要拼凑字符串前缀 "Bearer "

文件上传(例:更新用户头像)

获取用户选择的头像地址,通过 wx.uploadFile 将图片上传到服务端。

wx.uploadFile 的基本语法:

  • url 上传接口地址

  • filePath 待上传文件的临时路径(该路径只能用于小程序内部)

  • name 接口接收上传文件的数据名称(由后端指定)

  • formData 除上传文件外的其它数据

  • header 自定义头信息

  • success 上传成功的回调函数

  • fail 上传失败后的回调函数

  • complete 上传完成时的回调(无论成功或失败)

注:该 API 不支持返回 Promise,调用该 API 时,需要提前在小程序管理后台添加服务器域名。

<!-- pages/profile/index.wxml -->
<authorization>
  <view class="profile">
    <van-cell center title="头像">
      <van-icon slot="right-icon" name="arrow" size="16" color="#c3c3c5" />
      <button
        class="button"
        size="mini"
        hover-class="none"
        bind:chooseavatar="updateUserAvatar" //事件,事件名全部小写,A千万不要大写,不会触发
        open-type="chooseAvatar">    //与上边事件对应
        <image class="avatar" src="{{avatar}}"></image>
      </button>
    </van-cell>
    ...
  </view>
</authorization>
// pages/profile/index.js
const pageStack = getCurrentPages()
Page({
    ...
  // 更新用户头像
  updateUserAvatar(ev.detail.avatarUrl) {
    // 调用 API 上传文件
    wx.uploadFile({
      // 接口地址
      url: wx.$http.baseURL + '/upload',
      // 待上传的文件路径
      filePath: avatar,
      name: 'file',// wx.uploadFile 要求必传。
      header: {
        Authorization: 'Bearer ' + getApp().getToken()   // 用户登录状态
      },
      formData: { // 是我们自己的接口文档的要求。可以不传,默认就是avatar
        type: 'avatar'
      },
      success: (result) => {
        console.log(JSON.parse(result.data))
        const res = JSON.parse(result.data)
        // console.log(res.data.url)
        const avatar = res.data.url
        // 1. 在页面上显示
        this.setData({ avatar })
        // 2. 更新全局数据
        const app = getApp()
        app.userInfo.avatar = avatar
        // 3. 通过页面栈找到my/index页面,更新它的avatar信息
        const pages = getCurrentPages()
        // pages[0].data.nickName = nickName 直接修改数据不会让视图更新
        // 调用setData更新
         pages[0].setData({ avatar })
      }
    })
  }
})

上述代码中通过 wx.http.baseURL 获取接口服务器地址,通过应用实例获取 token

refresh_token使用

在这里插入图片描述

  • token:

    • 作用:在访问一些接口时,需要传入token,就是它。
    • 有效期:2小时(安全)。
  • refresh_token

    • 作用: 当token的有效期过了之后,可以使用它去请求一个特殊接口(这个接口也是后端指定的,明确需要传入refresh_token),并返回一个新的token回来(有效期还是2小时),以替换过期的那个token。
    • 有效期:14天。(最理想的情况下,一次登陆可以持续14天。)

在这里插入图片描述

1.用户在首次完成登录时会分别得到 token 和 refresh_token

2.当 token 失效后(例如2小时之后),调用接口A会返回 401 状态码(这是与后端约定好的规则)

3.检测状态码是否为 401**,如果是,则携带refreshToken去调用刷新token的接口

4.刷新 token 的接口后会返回新的 token 和 refreshToken

5.把401的接口A重新发送一遍

注意:
refresh_token也是有过期时间的,只不过一般会比token过期时间更长一些。这就是为啥如果某个应用我们天天打开,则不会提示我们登录,如果是有几周或更长时间去打开时,会再次要求我们登录。
refresh_token一个更常见的名字叫token无感刷新。

refreshToken功能-基本实现
// 响应拦截器
http.intercept.response = async ({ statusCode, data, config }) => {
  console.log(statusCode, data, config)
  // console.log(statusCode) // http 响应状态码
  // console.log(config) // 发起请求时的参数
  if (data.code === 401) {
    const app = getApp()
    // 调用接口获取新的 token
    const res = await http({
      url: '/refreshToken',
      method: 'POST',
      header: {
        Authorization: 'Bearer ' + app.getToken('refreshToken'),
      }
    })

    app.setToken('token', res.token)
    app.setToken('refreshToken', res.refreshToken)
     // 获得新的token后需要重新发送刚刚未完成的请求
    config = Object.assign(config, {
      header: {
        // 更新后的 token
        Authorization: 'Bearer ' + res.token,
      },
    })
    // 重新发请求
    return http(config)
  }
  // 拦截器处理后的响应结果
  if (data.code === 10000) {
    return data.data
  } else {
    wx.$toast(data.message || '请求失败')
    return Promise.reject(data.message)
  }
}
refreshToken也过期的特殊处理

在这里插入图片描述

完整版响应拦截器


// 响应拦截器
http.intercept.response = async ({ statusCode, data, config }) => {
  console.log(statusCode, data, config)
  // console.log(statusCode) // http 响应状态码
  // console.log(config) // 发起请求时的参数
  if (data.code === 401) {
++    if (config.url.includes('/refreshToken')) {
++      console.log('/refreshToken过期了')
++      // 获取当前页面的路径,保证登录成功后能跳回到原来页面
++      const pageStack = getCurrentPages()
++      const currentPage = pageStack.pop()
++      const redirectURL = currentPage.route
++      // 跳由跳转(登录页面)
++      wx.redirectTo({
++        url: '/pages/login/index?redirectURL=/' + redirectURL,
++      })
++      return Promise.reject('refreshToken也过期了,就只能重新登录了')
++    }
    const app = getApp()
    // 调用接口获取新的 token
    const res = await http({
      url: '/refreshToken',
      method: 'POST',
      header: {
        Authorization: 'Bearer ' + app.getToken('refreshToken'),
      }
    })

    app.setToken('token', res.token)
    app.setToken('refreshToken', res.refreshToken)
    config = Object.assign(config, {
      header: {
        // 更新后的 token
        Authorization: 'Bearer ' + res.token,
      },
    })
    // 重新发请求
    return http(config)
  }
  // 拦截器处理后的响应结果
  else if (data.code === 10000) {
    return data.data
  } else {
    wx.$toast(data.message || '请求失败')
    return Promise.reject(data.message)
  }
}
腾讯位置服务-需要提前注册

文档地址:https://lbs.qq.com/miniProgram/jsSdk/jsSdkGuide/jsSdkOverview

使用步骤(共4步)

  1. 申请开发者密钥(key):申请密钥(地址:https://lbs.qq.com/dev/console/application/mine)
  2. 开通webserviceAPI服务:控制台 ->应用管理 -> 我的应用->添加key-> 勾选WebServiceAPI -> 保存(小程序SDK需要用到webserviceAPI的部分服务,所以使用该功能的KEY需要具备相应的权限)

在这里插入图片描述

3.下载微信小程序JavaScriptSDK,微信小程序JavaScriptSDK v1.1(https://mapapi.qq.com/web/miniprogram/JSSDK/qqmap-wx-jssdk1.1.zip) JavaScriptSDK v1.2(https://mapapi.qq.com/web/miniprogram/JSSDK/qqmap-wx-jssdk1.2.zip) js文件

4.安全域名设置,在小程序管理后台-> 开发 -> 开发管理 -> 开发设置 -> “服务器域名” 中设置request合法域名,添加https://apis.map.qq.com

地理定位-wx.getLocation

在这里插入图片描述

获取用户所在位置的经纬度。在小程序中调用这个接口时必须先在 app.json 中申请调用权限(开发环境可以省略)。

//app.json
{
  "requiredPrivateInfos": [
++    "getLocation"
  ],
  "permission": {
    "scope.userLocation": {
      "desc": "你的位置信息将用于小程序位置接口的效果展示"
    }
  },
}

在这里插入图片描述

Page({
  onLoad() {
    this.getLocation()
  },
  async getLocation() {
    const res = await wx.getLocation() // 要提前申请权限
    console.log(res)
  },
})

wx.getLocation返回的结果格式大致如下:

accuracy: 65
errMsg: "getLocation:ok"
horizontalAccuracy: 65
latitude: 30.88131
longitude: 114.37509
speed: -1
verticalAccuracy: 65

wx.getLocation 只能得到经纬度信息

逆地址解析-reverseGeocoder

由坐标 → 坐标所在位置的文字描述的转换,输入坐标返回地理位置信息和附近poi列表

文档地址:https://lbs.qq.com/miniProgram/jsSdk/jsSdkGuide/methodReverseGeocoder

1. 导入 QQMapWX 并设置好 key

在这里插入图片描述

2.在代码中补充getPoint方法:

  1. 调用接口把经纬度转换成对应位置的文字
  2. 保存文字到address
// 导入位置服务实例
import QQMap from '../../../utils/qqmap'

Page({   ......
  onLoad() {
    this.getLocation()
  },
  async getLocation() {
    // 调用小程序API获取经纬度等信息
    const { latitude, longitude } = await wx.getLocation()  //获取用户经纬度
    this.getPoint(latitude, longitude)  
  },
  getPoint(latitude, longitude) {
    // 逆地址解析(根据经纬度来获取地址)
    QQMap.reverseGeocoder({
      location: [latitude, longitude].join(','),
      success: (result) => {
        const address = res.address
        this.setData({ address })
      },
    })
  }
})

3.渲染页面

 <van-cell-group border="{{false}}" title="当前地点">
  <van-cell title="{{address}}" border="{{false}}">  //border="{{false}}"设置无边框样式
    <text bind:tap="chooseLocation" class="enjoy-icon icon-locate">重新定位</text>
  </van-cell>
</van-cell-group>
QQMap地点搜索—search

根据当前的定位,调用 QQMap.search() 找到周边的信息。

搜索周边poi(Point of Interest),比如:“酒店” “餐饮” “娱乐” “学校” 等等
文档地址:https://lbs.qq.com/miniProgram/jsSdk/jsSdkGuide/methodSearch

在这里插入图片描述

在小程序中调用这个接口时必须要在 app.json 中申请调用权限

//app.json
{
  "requiredPrivateInfos": [
++    "chooseLocation"
  ]
}
// 选择新的位置
async chooseLocation() {
  // 调用小程序 API 获取新的位置
  const { latitude, longitude } = await wx.chooseLocation()
  this.getPoint(latitude, longitude)   // 获取新的位置经纬度
},

getPoint(latitude, longitude) {
  wx.showLoading({
    title: '正在加载...',      // 显示loading提示
  })

  // 逆地址解析(根据经纬度来获取地址)
  QQMap.reverseGeocoder({
    location: [latitude, longitude].join(','),
    success: ({ result: { address } }) => {
      this.setData({ address })
    },
  })

  QQMap.search({
    keyword: '住宅小区', //搜索关键词
    location: [latitude, longitude].join(','), //设置周边搜索中心点
    page_size: 5,   //只显示5条信息,不设置此项默认为10
    success: (result) => {     //success 是一个回调函数,表示搜索成功后的处理逻辑。
      const points = result.data
      this.setData({ points }) // 渲染数据
    },
    fail: (err) => {    //fail 是一个回调函数,表示搜索失败后的处理逻辑。
      console.log(err.message)
    },
    complete: () => {//complete 是一个回调函数,表示搜索结束后的处理逻辑(无论搜索成功还是失败)
      wx.hideLoading()   // 隐藏loading提示
    },
  })
},
重新定位-wx.chooseLocation

在这里插入图片描述

申请权限

获取用户指定位置的经纬度。在小程序中调用这个接口时必须要在 app.json 中申请调用权限

{
  "requiredPrivateInfos": [
    "chooseLocation"
  ]
}

示例代码:

// 选择新的位置
async chooseLocation() {
  // 调用小程序 API 获取新的位置
  const { latitude, longitude } = await wx.chooseLocation()
  // 获取新的位置附近的小区
  this.getPoint(latitude, longitude)

  console.log('起点位置:', latitude, longitude)
},

getPoint(latitude, longitude) {
  // 显示loading提示
  wx.showLoading({
    title: '正在加载...',
  })

  // 逆地址解析(根据经纬度来获取地址)
  QQMap.reverseGeocoder({
    location: [latitude, longitude].join(','),
    success: ({ result: { address } }) => {
      // console.log(address)
      // 数据数据
      this.setData({ address })
    },
  })

  QQMap.search({
    keyword: '住宅小区', //搜索关键词
    location: [latitude, longitude].join(','), //设置周边搜索中心点
    page_size: 5,
    success: (result) => {
      // console.log(result)
      // 过滤掉多余的数据
      const points = result.data.map(({ id, title, _distance }) => {
        return { id, title, _distance }
      })

      // console.log(points)
      // 渲染数据
      this.setData({ points })
    },
    fail: (err) => {
      console.log(err.message)
    },
    complete: () => {
      // 隐藏loading提示
      wx.hideLoading()
    },
  })
},
图片收集(收集身份证信息)

小程序没有input type="file"用于选择文件,要实现类似功能,用以下api:
wx.chooseMedia**:**拍摄或从手机相册中选择图片或视频。
低版本请用wx.chooseImage

1.绑定事件选择身份证图片上传。

2.发送请求上传图片,拿到上传后的图片地址。

Page({
 ...
  async uploadPicture(ev) {
    // 获取图片临时地址
    const res = await wx.chooseMedia({
      count: 1,
      mediaType: ['image'],
      sizeType: ['compressed'],
    })
    const tempPath = res.tempFiles[0].tempFilePath
    const type = ev.mark.type
    // 上传图片到服务器
    wx.uploadFile({
      url: wx.$http.baseURL + '/upload',
      filePath: tempPath,
      name: 'file',
      header: {
        Authorization:  'Bearer ' +  getApp().getToken(),
      },
      success: (res) => {
        const res = JSON.parse(result.data)
        console.log(res.data.url) 							// 上传成功的回调
        this.setData({ [type]: res.data.url })
      },
    })
  },
})
校验表单信息

获取了全部的表单数据后再对数据进行验证,说明如下:

  • 房屋的信息是通过url地址获取的不需要验证
  • 性别可以指定默认值也不需要验证
  • 剩下的数据通过 wechat-validate 插件进行验证:
// house_pkg/pages/form/index.js
// 导入表单验证插件
import wxValidate from 'wechat-validate'
Page({
  behaviors: [wxValidate],
  data: {
    point: '',
    building: '',
    room: '',
    name: '',
    gender: 1,
    mobile: '',
    idcardFrontUrl: '',
    idcardBackUrl: '',
  },
  rules: {
    name: [
      { required: true, message: '业主姓名不能为空!' },
      { pattern: /^[\u4e00-\u9fa5]{2,5}$/, message: '业主姓名只能为中文!' },
    ],
    mobile: [
      { required: true, message: '业主手机号不能为空!' },
      { pattern: /^1[3-8]\d{9}$/, message: '请填写正确的手机号!' },
    ],
    idcardFrontUrl: [
      { required: true, message: '请上传身份证国徽面!' }
    ],
    idcardBackUrl: [
      { required: true, message: '请上传身份证照片面!' }
    ],
  },
})
表单收集—收集预约日期

1.时间选择控件:van-datetime-pickerhttps://vant-contrib.gitee.io/vant-weapp/#/datetime-picker

2.弹出层控件:van-popuphttps://vant-contrib.gitee.io/vant-weapp/#/popup

  1. 给选择日期单元绑定事件——显示选择日期弹层(van-popup)

2.给日期控件绑定确认事件confirm

3.在确认回调中获取时间戳

4.将时间戳格式化并显示

selectDate(ev) {
  // console.log(ev)
  this.setData({
    currentDate: ev.detail,
    appointment: wx.$utils.formatDate(ev.detail), // 格式化日期
  })
  this.closeDateLayer()
},

补充格式化日期的函数

formatDate(time) {
  const d = new Date(time)
  const year = d.getFullYear()
  let month = d.getMonth() + 1 // 获取月份,月份从0开始,所以加1
  let day = d.getDate()
  month = month < 10 ? '0' + month : month
  day = day < 10 ? '0' + day : day
  return `${year}-${month}-${day}`
},

wxml

<van-cell title-width="100" title="预约日期" value-class="{{appointment && 'active-cell'}}" bind:click="openDateLayer"
        is-link value="{{appointment || '请选择上门维修日期'}}" />
...省略
<van-popup bind:close="closeDateLayer" round show="{{ dateLayerVisible }}" position="bottom">
  <van-datetime-picker bind:cancel="closeDateLayer" bind:confirm="selectDate" type="date" value="{{ currentDate }}"
    min-date="{{ 1664582400000 }}" />
</van-popup>
加载更多

在scroll-view上添加 bindscrolltolower=“loadMore”

  <scroll-view 
  bindscrolltolower="loadMore"
  show-scrollbar="{{false}}" enhanced scroll-y>
    <view class="repairs">
      <view class="repairs-title">我的报修</view>

loadMore() {
    // if(是否有更多的数据)
    if (this.data.total <= this.data.list.length)
      return
    console.log('更多的数据')
    // 把页码+1,发请求,请求回来的数据要 追加 到原数组
    // [5] → [10]
    this.data.page++
    this.getList()

  },
  async getList() {
    // 发请求
    const { pageTotal, total, rows: list } = await wx.$http.get('/repair', { current: this.data.page, pageSize: this.data.pageSize })
    console.log(list, pageTotal, total)
    // 渲染数据
    // 在原来的基础上添加数据
    this.setData({ total, list: [...this.data.list, ...list] })
  },

路线规划

路线规划是常见的一个功能,它用来在地图上展示两点间路线,要使用这个功能需要用到两部分的知识:

  1. 小程序提供的 map 组件用来在页面渲染地图

map | 微信开放文档https://developers.weixin.qq.com/miniprogram/dev/component/map.html)

  1. 腾讯位置服务计算两点路线的所有坐标点(经纬度)

首先来看小程序提供的地图组件 map

  • latitude 指定地图中心点的纬度
  • longitude 指定地图中心点的经功
  • scale 指定地图初始的缩放比例,取值范围 3 - 20
  • markers 地图上的标记点
  • polyline 地图上的路线

latitude、longitude、scale 相对容易理解,重点来看一下 markers 的使用:

repqir_pkg/pages/detail/index.js

Page({
  data: {
    markers: [
      {
        id: 1,
        latitude: 40.22077,
        longitude: 116.23128,
        width: 24,
        height: 30,
      },
      {
        id: 2,
        latitude: 40.225857999999995,
        longitude: 116.23246699999999,
        iconPath: '/static/images/marker.png',
        width: 40,
        height: 40,
      },
    ],
  }
})

在定义标记点时每个标记点必须要指定 ID 属性,否则会有错误产生,通过 iconPath 可以自定义标记点的图片,width/height 定义标记点的大小尺寸。

polyline

polyline 用来在在图上展示两点间的行进路线,需要传递给它路线对应的坐标点(很多个点组成的线),获取这些坐标点需要通过位置服务计算得到。

计算两点间路线的坐标点需要用到位置服务的[路线规划]https://lbs.qq.com/miniProgram/jsSdk/jsSdkGuide/methodDirection方法

repqir_pkg/pages/detail/index.js

// repqir_pkg/pages/detail/index.js
Page({
  data: {},
  onLoad() {
    // 生成路线
    this.getPolyline()
  },
  // 调用位置服务(路线规划)
  getPolyline() {
    qqMap.direction({
      mode: 'bicycling',
      from: '40.227978,116.22998',
      to: '40.22077,116.23128',
      success: ({ result }) => {
        const coors = result.routes[0].polyline
        const points = []
        //坐标解压(返回的点串坐标,通过前向差分进行压缩)
        for (let i = 2; i < coors.length; i++) {
          coors[i] = Number(coors[i - 2]) + Number(coors[i]) / 1000000
        }
        // 获取经纬度
        for (let i = 0; i < coors.length; i += 2) {
          points.push({ latitude: coors[i], longitude: coors[i + 1] })
        }
        // 渲染数据
        this.setData({
          latitude: points[30].latitude,
          longitude: points[30].longitude,
          polyline: [
            {points, color: '#5591af', width: 4},
          ],
        })
      },
    })
  },
})

计算出来的坐标点是经过压缩的,需要按着官方指定的方式对数据进行解压才可以获取真正的坐标点,并且为了适应小程序地图组件的用法,还需要对数据进行二次的加工。

关于数据的处理大家只需要参考文档来实现就可以,可以说之所这么操作是腾讯位置服务规订好的,做为开发者按着官方提从的方法来应用即可。

自定义分享

[onShareAppMessage]https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onShareAppMessage-Object-object

监听用户点击页面内转发按钮(button 组件 open-type=“share”)或右上角菜单“转发”按钮的行为,并自定义转发内容。

注意:只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮

Page({
  onLoad({ id, encryptedData }) {
    this.getPassport(id)
    this.getPassportShare(encryptedData)
  },
  // 获取访客详情(通行证)
  async getPassport(id) {
    // 检测是否存在 id
    if (!id) return
    // 调用接口
    const passport = await wx.$http.get('/visitor/' + id)
    // 渲染数据
    this.setData({ ...passport })
  }
  async getPassportShare(encryptedData) {
    // 检测是否存在 id
    if (!encryptedData) return
    // 调用接口
    const passport = await wx.$http.get('/visitor/share/' + this.data.encryptedData)
    // 渲染数据
    this.setData({ passport })
  },
  onShareAppMessage() {
    return {
      title: '查看通行证',
      path: '/visitor_pkg/pages/passport/index?encryptedData=' + encryptedData,
      imageUrl: 'https://enjoy-plus.oss-cn-beijing.aliyuncs.com/images/share_poster.png',
    }
  },
})
保存到本地api介绍

[saveImageToPhotosAlbum]https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.saveImageToPhotosAlbum.html

保存图片到系统相册。

[getImageInfo]https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.getImageInfo.html

获取图片信息。网络图片需先配置 download 域名才能生效。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyIG4FD7-1687267141879)(C:\Users\ZhengKaiYa\Desktop\个人笔记\3\21.png)]

保存到本地实现

1.给按钮绑定事件,调用getImageInfo获取图片临时路径

2.调用saveImageToPhotosAlbum传入临时路径完成保存功能

// 保存图片
  async saveQRCode() {
    try {
      // 读取图片信息
      const { path } = await wx.getImageInfo({
        src: this.data.url,
      })
      // 保存图片到相册
      wx.saveImageToPhotosAlbum({ filePath: path })
    } catch (err) {
      wx.$toast('保存图片失败,稍后重试!')
    }
  },

gram/dev/component/button.html) 组件 open-type=“share”)或右上角菜单“转发”按钮的行为,并自定义转发内容。

注意:只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮

Page({
  onLoad({ id, encryptedData }) {
    this.getPassport(id)
    this.getPassportShare(encryptedData)
  },
  // 获取访客详情(通行证)
  async getPassport(id) {
    // 检测是否存在 id
    if (!id) return
    // 调用接口
    const passport = await wx.$http.get('/visitor/' + id)
    // 渲染数据
    this.setData({ ...passport })
  }
  async getPassportShare(encryptedData) {
    // 检测是否存在 id
    if (!encryptedData) return
    // 调用接口
    const passport = await wx.$http.get('/visitor/share/' + this.data.encryptedData)
    // 渲染数据
    this.setData({ passport })
  },
  onShareAppMessage() {
    return {
      title: '查看通行证',
      path: '/visitor_pkg/pages/passport/index?encryptedData=' + encryptedData,
      imageUrl: 'https://enjoy-plus.oss-cn-beijing.aliyuncs.com/images/share_poster.png',
    }
  },
})
保存到本地api介绍

[saveImageToPhotosAlbum]https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.saveImageToPhotosAlbum.html

保存图片到系统相册。

[getImageInfo]https://developers.weixin.qq.com/miniprogram/dev/api/media/image/wx.getImageInfo.html

获取图片信息。网络图片需先配置 download 域名才能生效。

在这里插入图片描述

保存到本地实现

1.给按钮绑定事件,调用getImageInfo获取图片临时路径

2.调用saveImageToPhotosAlbum传入临时路径完成保存功能

// 保存图片
  async saveQRCode() {
    try {
      // 读取图片信息
      const { path } = await wx.getImageInfo({
        src: this.data.url,
      })
      // 保存图片到相册
      wx.saveImageToPhotosAlbum({ filePath: path })
    } catch (err) {
      wx.$toast('保存图片失败,稍后重试!')
    }
  },
;