weuse

Wechat miniprogram utilities

Usage no npm install needed!

<script type="module">
  import weuse from 'https://cdn.skypack.dev/weuse';
</script>

README

WeUse

微信小程序 API 助手

Features

安装

使用 npm 依赖(原生或 mpvue)

npm i weuse

使用本地依赖项

如无法使用 npm 依赖, 可下载 latest release, 放至项目目录中进行相对路径引用.

使用

使用 npm 依赖

const weuse = require('weuse')

TypeScript

import * as weuse from 'weuse'

Promise

weuse.promise

Promise 相关封装

weuse.promise.promisify(func: Function): Function

将一个小程序方法 Promise 化

e.g.

const res = await weuse.promisify(wx.getUserInfo)()
const { confirm, cancel } = await weuse.promisify(wx.showModal)({
  title: 'Test',
  content: 'This is a test'
})

weuse.promise.map(values: any[], mapper: Function): Promise<any[]>

对一个数组进行 map 操作并行执行每个返回的 Promise. 返回按传入顺序排序的执行结果

e.g.

const urls = ['https://...', 'https://...', 'https://...']
const results = await weuse.promise.map(urls, url => weuse.request.get(url))

weuse.promise.serial(values: any[]): Promise<any[]>

顺序执行 Promise. 返回按传入顺序排序的执行结果

e.g.

const results = await weuse.promise.serial([
  weuse.wx.login(),
  weuse.request.post({ url, data }),
  weuse.wx.setStorage({ key, data })
])

weuse.wx

包含全部已Promisify处理的 wx 方法对象. 调用签名同 wx 中的方法, 无需设置success,fail回调. 若设置回调, 则不会返回Promise.

e.g.

原调用

wx.getUserInfo({
  success(res) {
    console.log(res.userInfo)
  }
})

wx.showModal({
  title: 'Test',
  content: 'This is a test',
  success({ confirm, cancel }) {
    //
  }
})

异步调用

weuse.wx.getUserInfo().then(res => {
  console.log(res.userInfo)
})

// 使用await
const res = await weuse.wx.getUserInfo()
console.log(res.userInfo)

const { confirm, cancel } = await weuse.wx.showModal({ title: 'Test', content: 'This is a test' })

兼容调用

weuse.wx.getUserInfo({
  success(res) {
    console.log(res.userInfo)
  }
})

weuse.wx.showModal({
  title: 'Test',
  content: 'This is a test',
  success({ confirm, cancel }) {
    //
  }
})

缓存

weuse.storage

支持 TTL 的缓存封装

注意 get/del/touch/exists/ttl/expire/persist/flush 只能针对由 set 添加的缓存

weuse.storage.get(key: string): Promise<any>

从本地缓存中获取指定 key 的内容. Date 类型会被反序列化. key 不存在返回 undefined

e.g.

const token = await weuse.storage.get('token')

weuse.storage.set(opts: Options): Promise<void>

将数据存储在本地缓存中指定的 key 中

Options:

  • key: string 本地缓存中指定的 key
  • data: any 需要存储的内容
  • ttl?: number 有效时间(秒). 为空或 0 则不会过期

e.g.

await weuse.storage.set({ key: 'token', data: token, ttl: 3600 })

weuse.storage.del(key: string): Promise<void>

从本地缓存中移除指定 key

e.g.

await weuse.storage.del('token')

weuse.storage.touch(...keys: string[]): Promise<number>

检查指定的 key 是否已过期, 是则移除. key 不存在则忽略. 返回移除掉的 key 数量

e.g.

const count = await weuse.storage.touch('token', 'name', 'key2')

weuse.storage.exists(key: string): Promise<boolean>

检查 key 是否存在

e.g.

const exists = await weuse.storage.exists('token')

weuse.storage.ttl(key: string): Promise<number>

获取指定 key 的剩余有效时间(秒). 若 key 不存在返回-1; 若 key 不会过期返回-2

e.g.

const ttl = await weuse.storage.ttl('token')

weuse.storage.expire(key: string, ttl: number): Promise<boolean>

更新指定 key 的有效时间. key 存在且设置成功则返回 true

e.g.

const success = await weuse.storage.expire('token', 3600)

weuse.storage.persist(key: string): Promise<boolean>

移除指定 key 的有效时间让其不会过期. key 存在且设置成功则返回 true

e.g.

const success = await weuse.storage.persist('token')

weuse.storage.flush()

移除所有已过期的缓存

e.g.

await weuse.storage.flush()

网络请求

weuse.request

网络请求封装

weuse.request(options: Options): Promise<Response>

发起网络请求

Options:

  • url: string
  • query?: string|object
  • method?: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT'
  • data?: string | object | ArrayBuffer
  • header?: object
  • dataType?: string
  • responseType?: 'text' | 'arraybuffer'

Response:

  • data: string | object | ArrayBuffer
  • statusCode: number
  • header: object

e.g.

const res = await weuse.request({ url: 'https://myapi.com/api/v1/items', method: 'GET' })

或者直接使用 http verb 小写名称当方法名

e.g.

const res = await weuse.request.get('https://myapi.com/api/v1/items')

const { data, statusCode, header } = await weuse.request.get({
  url: 'https://myapi.com/api/v1/items',
  query: {
    name: 'abc',
    key: '12345'
  }
})

await weuse.request.post({
  url: 'https://myapi.com/api/v1/items',
  data: {
    type: 'Book',
    id: 1
  }
})

注意当 status code 不为 2xx/3xx 时, Promise 会被自动 reject 掉, 抛出 HttpError(包含 code, message 内容)

weuse.request.defaults(opts: Options): Request

返回一个包含默认设置的 request 对象. 可用来设置baseUrl, header等默认值

e.g.

const request = weuse.request.defaults({
  baseUrl: 'https://myapi.com/api/v1',
  header: { 'x-uuid': '12345' },
  query: {
    token: '123456'
  }
})

const res = await request.get({
  url: '/items',
  header: { 'x-key': 'abcd' },
  query: {
    page: 3
  }
})

相当于

const res = await weuse.request.get({
  url: 'https://myapi.com/api/v1/items',
  header: {
    'x-uuid': '12345',
    'x-key': 'abcd'
  },
  query: {
    token: '123456',
    page: 3
  }
})

weuse.request.download(opts: Options): Promise<Result>

分段下载文件

Options:

  • url: string 下载地址
  • filePath?: string 保存路径
  • parts?: number 分段数 1~10
  • header?: object 请求头
  • query?: string|object querystring

Result:

  • filePath?: string 下载文件保存路径
  • buffer?: ArrayBuffer 下载文件的二进制流

e.g.

const { filePath } = await weuse.request.download({
  url: 'https://some/large/file.jpg',
  parts: 3,
  filePath: wx.env.USER_DATA_PATH + '/file.jpg'
})

const { buffer } = await weuse.request.download({
  url: 'https://some/large/file.jpg',
  parts: 3
})

Canvas

weuse.canvas

绘制 Canvas 封装

weuse.canvas.draw(canvasId: string, options: Options): Promise<void|string>

绘制 Canvas

Options:

  • layers: Layer[] 图层. 从下到上绘制
  • default?: Style 默认样式. 可以在图层中覆盖
  • export?: Export 导出 canvas 到文件的设置
  • downloader?: Downloader 图片下载方法(不设置则使用 wx.getImageInfo)

Style:

  • stroke?: Stroke 描边
  • fill?: Fill 填充
  • font?: Font 文字

Stroke: 描边样式

  • color?: Color
  • lineWidth?: number
  • lineDash?: Stroke.LineDash
  • lineCap?: 'butt' | 'round' | 'square'
  • lineJoin?: 'bevel' | 'round' | 'miter'
  • miterLimit?: number

Fill: 填充样式

  • color?: Color
  • shadow?: Fill.Shadow

Color: 颜色, 可以为字符串#fff, rgb(0,0,0)等

  • type: 'linear' | 'circular' | 'pattern' 渐变方式

Font: 字体样式

  • family?: string 字体
  • size: number 字号
  • align?: 'left' | 'center' | 'right' 水平对齐
  • baseline?: 'top' | 'bottom' | 'middle' | 'normal' 垂直对齐

Export:

  • x?: number 指定的画布区域的左上角横坐标
  • y?: number 指定的画布区域的左上角纵坐标
  • width?: number 指定的画布区域的宽度
  • height?: number 指定的画布区域的高度
  • destWidth?: number 输出的图片的宽度
  • destHeight?: number 输出的图片的高度
  • fileType?: 'jpg' | 'png' 目标文件的类型
  • quality?: number 图片的质量

Layer:

  • type: 'rect' | 'arc' | 'image' | 'text' | 'path' 图层类型
  • x?: number 绘制起点 X
  • y?: number 绘制起点 Y
  • fill?: Fill | true 填充样式. 为 true 进行默认填充
  • stroke?: Stroke | true 描边样式. 为 true 进行默认描边
  • clip?: Clip 图层遮罩
  • transform?: Transform 图层变换

Layer.Rect: 矩形

  • width: number
  • height: number
  • anchor?: Vector2 轴心点{x: [0, 1], y: [0, 1]}, 默认{x: 0, y: 0}

Layer.Arc: 弧形

  • radius: number 半径
  • startAngle?: number 起始弧度(0 为三点钟方向)
  • endAngle?: number 结束弧度
  • counterClockwise?: boolean 逆时针

Layer.Image: 图片

  • src: string 资源,可为网络链接或本地地址
  • width?: number
  • height?: number
  • crop?: Image.Crop 裁剪
  • anchor?: Vector2 轴心点{x: [0, 1], y: [0, 1]}, 默认{x: 0, y: 0}

Layer.Path: 文本

  • text: string 内容
  • font?: Font 字体样式
  • maxWidth?: number 最大宽度

Layer.Path: 路径

  • points: Point[] 路径点
  • close?: boolean 路径关闭

e.g.

<view style='width:0px;height:0px;overflow:hidden;'>
  <canvas canvas-id='poster' style="width:750px;height:750px;"></canvas>
</view>
const filePath = await weuse.canvas.draw('poster', {
  export: {
    width: 375,
    height: 500,
    fileType: 'jpg',
    quality: 0.8
  },
  default: {
    stroke: {
      color: 'black',
      lineWidth: 2
    },
    fill: {
      color: 'red'
    },
    font: {
      size: 22,
      align: 'center',
      baseline: 'middle'
    }
  },
  layers: [
    {
      type: 'rect',
      width: 100,
      height: 100,
      fill: true,
      stroke: {
        lineWidth: 1,
        color: 'green',
        lineDash: {
          pattern: [10, 20],
          offset: 5
        }
      }
    },
    {
      type: 'arc',
      x: 100,
      y: 100,
      radius: 50,
      fill: {
        color: 'purple'
      }
    },
    {
      type: 'path',
      x: 200,
      y: 200,
      points: [
        {
          type: 'linear',
          x: 220,
          y: 200
        },
        {
          type: 'arc',
          x: 250,
          y: 220,
          radius: 30
        },
        {
          type: 'quadratic',
          x: 300,
          y: 250,
          cpx: 260,
          cpy: 300
        },
        {
          type: 'cubic',
          x: 280,
          y: 180,
          cpx0: 260,
          cpy0: 250,
          cpx1: 270,
          cpy1: 220
        }
      ],
      stroke: true,
      fill: true,
      close: true
    },
    {
      type: 'text',
      text: 'Hello World!',
      x: 300,
      y: 300,
      fill: true
    },
    {
      type: 'image',
      src: 'https://placeimg.com/100/100/animals',
      x: 100,
      y: 300,
      clip: {
        // 圆角遮罩效果
        type: 'rect',
        x: 100,
        y: 300,
        width: 100,
        height: 100,
        radius: 4
      }
    },
    {
      type: 'image',
      src: 'https://placeimg.com/100/100/animals',
      x: 100,
      y: 400,
      clip: {
        // 圆形遮罩效果
        type: 'circular',
        x: 150,
        y: 450,
        radius: 50
      }
    }
  ]
})
await weuse.utils.saveImageToPhotosAlbum({ filePath })

效果如图:

Imgur

其他

weuse.utils

帮助方法封装

weuse.utils.encodeQuery(query: object): string

object 创建 querystring

e.g.

const query = weuse.utils.encodeQuery({
  name: 'admin',
  size: 50,
  key: null,
  index: undefined
})
// 结果: name=admin&size=50

weuse.utils.decodeQuery(query: object): object

解析小程序页面传递的 querystring 对象. 兼容小程序码、二维码、普通链接

weuse.utils.joinUrl(...urls: string[]): string

连接 url. 会移除重复的 / 符号. url 开头跟结尾均可包含或缺省 / 符号. 返回结果结尾不含 / (除非整体仅为 /)

e.g.

const url = weuse.utils.joinUrl('http://myapi.com/', '/api/v1', 'items/', '1')
// 结果: http://myapi.com/api/v1/items/1

weuse.utils.authorize(scope: string): Promise<void>

查询授权, 如果未授权则进行请求

weuse.utils.getLocation(opts: Options): Promise<Location>

检查权限并获取地理定位

weuse.utils.chooseLocation(opts: Options): Promise<Location>

检查权限并请求地理定位

weuse.utils.saveImageToPhotosAlbum(opts: Options): Promise<void>

检查权限并写入相册

weuse.utils.clone(source: any): any

深克隆

weuse.utils.merge(source: any, target: any): any

递归合并

Redux

weuse.redux

redux 风格的状态管理

weuse.redux.createStore(reducer: Reducer): Store

创建一个 Store

weuse.redux.combineReducers(reducers: object): Reducer

合并多个 Reducers

weuse.redux.createProvider(store: Store, options: object): Provider

创建一个 Provider

e.g.

App(
  weuse.redux.createProvider(store, {
    onLaunch() {
      //...
      const state = this.$store.getState()
    }
  })
)

weuse.redux.createConsumer(options: object, stateMapper?: StateMapper): Consumer

创建一个 Consumer

e.g.

Page(
  weuse.redux.createConsumer({
    onLoad() {
      //...
      this.$dispatch({ type: 'FETCH_DATA' })
    }
  })
)
// 注入state
Page(
  weuse.redux.createConsumer(
    {
      onLoad() {
        //...
        console.log(this.data.items)
        console.log(this.data.count)
      }
    },
    state => ({
      items: state.items,
      count: state.count,
      name: state.app.name
    })
  )
)

weuse.redux.Provider(store: Store): ClassDecorator

Provider 装饰器

e.g.

@Provider(store)
class MyApp {
  onLaunch() {
    //...
    const state = this.$store.getState()
  }
}
App(new MyApp())

weuse.redux.Consumer(stateMapper?: StateMapper): ClassDecorator

Consumer 装饰器

e.g.

@Consumer()
class MyPage {
  onLoad() {
    //...
    this.$dispatch({ type: 'FETCH_DATA' })
  }
}
Page(new MyPage())

// 注入state
@Consumer(state => ({
  items: state.items,
  count: state.count
}))
class MyPage {
  onLoad() {
    //...
    console.log(this.data.items)
    console.log(this.data.count)
  }
}

weuse.redux.Consumer.State(name?: string): PropertyDecorator

State 绑定装饰器

e.g.

@Consumer()
class MyPage {
  @Consumer.State
  items: any[]

  @Consumer.State('count')
  size: number

  onLoad() {
    //...
    console.log(this.data.items)
    console.log(this.data.size)
  }
}

注: State 绑定可与 Consumer 装饰器中的 StateMapper 参数共存, 若存在同名属性, 前者会覆盖后者

weuse.redux.Consumer.namespace(name?: string): Namespace

子 State 绑定对象

weuse.redux.Consumer.Namespace.State(name?: string): PropertyDecorator

子 State 绑定装饰器

e.g.

const app = Consumer.namespace('app')
const
@Consumer()
class MyPage {
  @app.State
  items: any[]

  @app.State('count')
  size: number

  onLoad() {
    //...
    console.log(this.data.items)
    console.log(this.data.size)
  }
}

Vuex

weuse.vuex

vuex 风格的状态管理

weuse.vuex.createStore(options: StoreOptions): Store

创建一个 Store

weuse.vuex.createProvider(store: Store, options: object): Provider

创建一个 Provider

e.g.

App(
  weuse.vuex.createProvider(store, {
    onLaunch() {
      //...
      const state = this.$store.state
    }
  })
)

weuse.vuex.createConsumer(options: object, ...mappers: Mapper[]): Consumer

e.g.

Page(
  weuse.vuex.createConsumer({
    onLoad() {
      //...
    }
  })
)
// 注入state/getters/mutations/actions
Page(
  weuse.vuex.createConsumer(
    {
      async onLoad() {
        //...
        console.log(this.data.items)
        console.log(this.data.count)
        await this.login()
        console.log(this.data.user)
        this.setName('admin')
      }
    },
    {
      state: {
        items: 'items',
        count: state => state.count
      },
      getters: ['name'],
      mutations: {
        setUser: 'setName'
      }
    },
    {
      namespace: 'account',
      getters: ['user'],
      actions: ['login']
    }
  )
)

创建一个 Consumer

weuse.vuex.Provider(store: Store): ClassDecorator

Provider 装饰器

e.g.

@Provider(store)
class MyApp {
  onLaunch() {
    //...
    const state = this.$store.state
  }
}
App(new MyApp())

weuse.vuex.Consumer(...mappers: Mapper[]): ClassDecorator

Consumer 装饰器

e.g.

@Consumer()
class MyPage {
  onLoad() {
    //...
  }
}
Page(new MyPage())

// 注入state
@Consumer({
  state: ['items', 'count']
})
class MyPage {
  onLoad() {
    //...
    console.log(this.data.items)
    console.log(this.data.count)
  }
}

weuse.vuex.Consumer.State(name?: string): PropertyDecorator weuse.vuex.Consumer.State(func: Function): PropertyDecorator weuse.vuex.Consumer.Getter(name?: string): PropertyDecorator weuse.vuex.Consumer.Mutation(name?: string): PropertyDecorator weuse.vuex.Consumer.Mutation(func: Function): PropertyDecorator weuse.vuex.Consumer.Action(name?: string): PropertyDecorator weuse.vuex.Consumer.Action(func: Function): PropertyDecorator

State/Getter/Mutation/Action 绑定装饰器

e.g.

@Consumer()
class MyPage {
  @Consumer.State
  items: any[]

  @Consumer.State('count')
  size: number

  @Consumer.State(state => state.count)
  count: number

  @Consumer.Getter
  max: number

  @Consumer.Action
  add: Consumer.ActionMethod

  @Consumer.Mutation
  setCount: Consumer.MutationMethod

  async onLoad() {
    //...
    console.log(this.data.items)
    console.log(this.data.size)
    console.log(this.data.count)
    console.log(this.data.max)
    await this.add()
    this.setCount(100)
  }
}

注: State/Getter/Mutation/Action 绑定可与 Consumer 装饰器中的 Mapper 参数共存, 若存在同名属性, 前者会覆盖后者

weuse.vuex.Consumer.namespace(name?: string): Namespace

子模块绑定对象

weuse.vuex.Consumer.Namespace.State(name?: string): PropertyDecorator weuse.vuex.Consumer.Namespace.State(func: Function): PropertyDecorator weuse.vuex.Consumer.Namespace.Getter(name?: string): PropertyDecorator weuse.vuex.Consumer.Namespace.Mutation(name?: string): PropertyDecorator weuse.vuex.Consumer.Namespace.Mutation(func: Function): PropertyDecorator weuse.vuex.Consumer.Namespace.Action(name?: string): PropertyDecorator weuse.vuex.Consumer.Namespace.Action(func: Function): PropertyDecorator

子 State/Getter/Mutation/Action 绑定装饰器

e.g.

const app = Consumer.namespace('app')
@Consumer()
class MyPage {
  @app.State
  items: any[]

  @app.State('count')
  size: number

  onLoad() {
    //...
    console.log(this.data.items)
    console.log(this.data.size)
  }
}

构建

npm run build

包含 source map

SOURCE_MAP=inline-source-map npm run build

License

See License