Taro开发总结

之前做小程序一直用的mpvue,用了一段时间发现mpvue有一些诟病,而且现在官方的维护力度显得力不从心。相比之下Taro做的就相当不错。现总结一下在使用Taro中各种奇技淫巧。

目前使用Taro版本1.3.4

数据请求库封装

新建src/utils/request.js文件

import Taro from '@tarojs/taro'
import configStore from '../redux/store'
import actions from '../redux/actions'
import { loadData, ACCESS_TOKEN } from './cache'

import { isUrl } from './utils'

const store = configStore()
const baseURL = BASE_URL
const codeMessage = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。',
}

/**
 * 状态检查
 * @param response
 * @returns {*}
 */
function checkStatus (response) {
  const { statusCode, data } = response
  if (statusCode >= 200 && statusCode < 300) {
    return data
  }
  const errorText = codeMessage[statusCode]
  const error = new Error(errorText)
  error.name = statusCode
  error.response = response
  throw error
}

export default function request (options) {
  const accessToken = loadData(ACCESS_TOKEN)
  const { url, method = 'get', data } = options
  let header = {}
  if (accessToken) {
    header.Authorization = `JWT ${accessToken}`
  }
  return Taro.request({
    url: isUrl(url) ? url : `${baseURL}${url}`,
    method: method.toUpperCase(),
    data,
    header: { ...header, ...options.header }
  }).then(checkStatus)
    .then(res => {
      const { status, data, errCode, errMsg, } = res
      if (status === 1) {
        return data
      } else if (status === 0 && (errCode === 401 || errCode === 403)) {
        store.dispatch(actions.setLoginStatus(false))
        store.dispatch(actions.setUserInfo(''))
        store.dispatch(actions.setAccessToken(''))
        //跳转到首页
        Taro.reLaunch({
          url: '/pages/index/index'
        })
        return Promise.reject(res)
      } else {
        Taro.hideLoading()
        if (errMsg) {
          Taro.showToast({
            title: errMsg,
            icon: 'none',
            duration: 3000
          })
        }
        return Promise.reject(res)
      }
    })
    .catch(err => {
      return Promise.reject(err)
    })
}

使用request.js,新建src/api/index.js文件

import request from '../utils/request'

export function getBannerList () {
  let url = `/cms_content/content/`
  return request({
    url,
    method: 'get'
  })
}

利用Decorator快速实现小程序分享

参考链接

新建src/utils/withShare.js文件

import Taro from '@tarojs/taro'

function withShare (opts = {}) {
  // 设置默认
  const defalutPath = '/pages/index/index'
  const defalutTitle = '首页'
  const defaultImageUrl = 'http://thumb10.jfcdns.com/2018-06/bce5b10ae530f530.png'

  return function demoComponent (Component) {

    class WithShare extends Component {
      componentDidMount () {
        if (super.componentDidMount) {
          super.componentDidMount()
        }
      }

      // 点击分享的那一刻会进行调用
      onShareAppMessage () {
        let { title, imageUrl, path = null } = opts
        // 从继承的组件获取配置
        if (this.$setSharePath && typeof this.$setSharePath === 'function') {
          path = this.$setSharePath()
        }
        // 从继承的组件获取配置
        if (this.$setShareTitle && typeof this.$setShareTitle === 'function') {
          title = this.$setShareTitle()
        }
        // 从继承的组件获取配置
        if (this.$setShareImageUrl && typeof this.$setShareImageUrl === 'function') {
          imageUrl = this.$setShareImageUrl()
        }
        if (!path) {
          path = defalutPath
        }

        console.log(path)
        return {
          title: title || defalutTitle,
          path: path || defalutPath,
          imageUrl: imageUrl || defaultImageUrl
        }
      }

      render () {
        return super.render()
      }
    }

    return WithShare
  }
}

export default withShare

使用src/pages/xxx/xxx.js

import Taro, { Component } from '@tarojs/taro';
import { connect } from '@tarojs/redux';
import { View } from '@tarojs/components';
import withShare from '@/utils/withShare';

@withShare({
    title: '可设置分享标题', 
    imageUrl: '可设置分享图片路径', 
    path: '可设置分享路径'
})
class Index extends Component {
  
  // $setSharePath = () => '可设置分享路径(优先级最高)'

  // $setShareTitle = () => '可设置分享标题(优先级最高)'

  // $setShareImageUrl = () => '可设置分享图片路径(优先级最高)'
  
  render() {
     return <View />
  }
}

封装UIcon组件

封装该组件可以使用iconfont等字体图标,新建src/components/uIcon/index.js

import Taro, { Component } from '@tarojs/taro'
import PropTypes from 'prop-types'
import { Text } from '@tarojs/components'

class UIcon extends Component {
  static options = {
    addGlobalClass: true
  }

  static externalClasses = ['u-class']

  static propTypes = {
    icon: PropTypes.string,
    prefixClass: PropTypes.string,
    color: PropTypes.string,
    size: PropTypes.number,
    onClick: PropTypes.func,
  }
  static defaultProps = {
    icon: '',
    prefixClass: 'iconfont',
    color: '#373737',
    size: 26,
    onClick: () => {}
  }

  render () {
    const { icon, onClick, prefixClass, color, size } = this.props
    return (
      <Text
        className={`u-class u-icon ${prefixClass} ${icon}`}
        onClick={onClick}
        style={{ color: `${color}`, fontSize: `${size}rpx` }}
      />
    )
  }
}

export default UIcon

使用src/pages/xxx/xxx.js

import Taro, { Component } from '@tarojs/taro';
import { connect } from '@tarojs/redux';
import { View } from '@tarojs/components';
import UIcon from '@/components/uIcon'

class Index extends Component {
  
  render() {
      return <View >
                  <UIcon icon='icon-home'/>
      </View>
  }
}

封装HtmlParse组件

该组件可显示html代码片段src/components/htmlParse/index.js

import Taro, { Component } from '@tarojs/taro'
import PropTypes from 'prop-types'
import { View, RichText } from '@tarojs/components'
import { isUrl } from '@/utils/utils'
import CommonServer from '@/api/common'

import './index.scss'

class HtmlParse extends Component {
  static propTypes = {
    url: PropTypes.string,
    onHtmlLoad: PropTypes.func,
  }
  static defaultProps = {
    url: '',
    onHtmlLoad: () => {}
  }

  state = {
    content: '数据加载中...'
  }

  componentDidMount () {
    this.getHtmlContent()
  }

  getHtmlContent = () => {
    const { url, onHtmlLoad } = this.props
    if (isUrl(url)) {
      CommonServer.getHtmlContent(url).then(res => {
        this.setState({
          content: this.parseHtmlContent(res.data) || '暂无数据'
        })
        onHtmlLoad()
      })
    } else {
      this.setState({
        content: this.parseHtmlContent(url) || '暂无数据'
      })
      onHtmlLoad()
    }
  }

  parseHtmlContent = (html) => {
    return html
      .replace(/section/g, 'div')
      .replace(/[外链图片转存失败(img-2NQLZftX-1562226911276)(undefined)]]+>/g, function (all, group1, group2) {
        return `![在这里插入图片描述]()`
      })
      .replace(/<table([\s\S]*?)[^>]+>/g, function () {
        return `<table width="100%"/>`
      })
  }

  render () {
    const { content } = this.state
    return (
      <View className='html-parse'>
        <RichText className='rich-text' nodes={content}/>
      </View>
    )
  }
}

export default HtmlParse

使用src/pages/xxx/xxx.js

import Taro, { Component } from '@tarojs/taro';
import { connect } from '@tarojs/redux';
import { View } from '@tarojs/components';
import HtmlParse from '@/components/htmlParse'

class Index extends Component {
  render() {
      const { content } = this.state
      return <View >
          <View className='content'>
          {
            content && <HtmlParse
              url={content}
              onHtmlLoad={() => {
                console.log('html片段加载完毕')
              }}
            />
          }
        </View>
      </View>
  }
}

分环境打包项目

修改config下dev.jsprod.js

//defineConstants用来配置一些全局变量供代码中进行使用

//dev.js

module.exports = {
  env: {
    NODE_ENV: '"development"',
  },
  defineConstants: {
    BASE_URL: '"https://api.test.com"',
  },
  weapp: {},
  h5: {}
}

//prod.js

const target = process.env.npm_lifecycle_event
const TEST = 'test'
const BUILD = 'build'

let defineConstants

if (target.indexOf(TEST) >= 0) {
  defineConstants = {
    BASE_URL: '"https://api.test.com"',
  }
} else if (target.indexOf(BUILD) >= 0) {
  defineConstants = {
    BASE_URL: '"https://api.build.com"',
  }
}

module.exports = {
  env: {
    NODE_ENV: '"production"'
  },
  defineConstants,
  weapp: {},
  h5: {}
}

package.json修改

"scripts": {
	...
	"dev": "npm run build:weapp -- --watch",
	"test": "taro build --type weapp",
	"build": "taro build --type weapp"
},

打包命令

//打包测试版本
yarn test||npm run test

//打包正式版本
yarn build||npm run build

打包压缩配置

修改config下index.js

plugins: {
	...
    //压缩js
    uglify: {
      enable: true,
      config: {
        warnings: false,
        // 配置项同 https://github.com/mishoo/UglifyJS2#minify-options
        compress: {
          drop_debugger: true,
          drop_console: true,
        },
      }
    },
    //压缩css
    csso: {
      enable: true,
      config: {
        // 配置项同 https://github.com/css/csso#minifysource-options
      }
    }
},
weapp:{
    compile: {
      compressTemplate: true,//打包时是否需要压缩 wxml
    },
}

alias配置

修改config下index.js

const path = require('path')

function resolve (dir) {
  return path.resolve(__dirname, '..', dir)
}

// 目录别名设置
  alias: {
    '@/api': resolve('src/api'),
    '@/assets': resolve('src/assets'),
    '@/components': resolve('src/components'),
    '@/redux': resolve('src/redux'),
    '@/utils': resolve('src/utils'),
},

图片裁剪封装

本地安装we-cropper

yarn add we-cropper

新建cropper.js文件

import Taro, { Component } from '@tarojs/taro'
import { Canvas, CoverView, View } from '@tarojs/components'
import WeCropper from 'we-cropper'
import { saveData, VOTER_IMAGE, VOTER_AVATAR } from '@/utils/cache'
import './index.scss'

const device = Taro.getSystemInfoSync()
const devicePixelRatio = device.pixelRatio
const windowWidth = device.windowWidth
const windowHeight = device.windowHeight

const cropper = {
  width: `${windowWidth}px`,
  height: `${windowHeight}px`
}

const target = {
  width: `${devicePixelRatio * windowWidth}px`,
  height: `${devicePixelRatio * windowHeight}px`
}

class Cropper extends Component {
  config = {
    navigationBarTitleText: '裁剪图片'
  }

  constructor () {
    super()
    this.state = {
      cropperOpt: {
        id: 'cropper',
        targetId: 'target',
        pixelRatio: devicePixelRatio,
        width: windowWidth,
        height: windowHeight,
        scale: 2.5,
        zoom: 1,
        cut: {
          x: (windowWidth - 400) / 2,
          y: (windowHeight - 615) / 2,
          width: 400,
          height: 615
        }
      },
      weCropper: null,

      imageType: 'image'
    }
  }

  componentDidMount () {
    this.initCropper(this.$router.params)
  }

  // 初始化cropper
  initCropper = (params) => {
    const width = Number(params.width) || 100
    const height = Number(params.height) || 100
    const cut = {
      x: (windowWidth - width) / 2,
      y: (windowHeight - height) / 2,
      width: width,
      height: height
    }
    this.setState({
      cropperOpt: { ...this.state.cropperOpt, cut },
      imageType: params.imageType
    }, () => {
      const weCropperObj = new WeCropper(this.state.cropperOpt)
        .on('ready', (ctx) => {
          // console.log(`weCropper准备工作`)
        })
        .on('beforeImageLoad', (ctx) => {
          // console.log(`在图片加载之前,我可以做一些事情`)
          // console.log(`当前画布上下文:`, ctx)
          Taro.showToast({
            title: '上传中',
            icon: 'loading',
            duration: 20000
          })
        })
        .on('imageLoad', (ctx) => {
          // console.log(`图片加载...`)
          // console.log(`当前画布上下文:`, ctx)
          Taro.hideToast()
        })
        .on('beforeDraw', (ctx, instance) => {
          // console.log(`在画布画之前,我可以做点什么`)
          // console.log(`当前画布上下文:`, ctx)
        })
      this.setState({
        weCropper: weCropperObj
      }, () => {
        this.uploadTap()
      })
    })
  }

  // 上传图片
  uploadTap () {
    Taro.chooseImage({
      count: 1, // 默认9
      sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有  sizeType: ['original', 'compressed'],
      sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
      success: res => {
        let src = res.tempFilePaths[0]
        // 获取裁剪图片资源后,给data添加src属性及其值
        this.state.weCropper.pushOrign(src)
      }
    })
  }

  // 生成图片
  getCropperImage () {
    const { imageType } = this.state
    Taro.showLoading({
      title: '生成中',
    })
    this.state.weCropper.getCropperImage({
      original: false, // 是否使用原图模式(默认值 false)
      quality: 0.8, // 图片的质量,目前仅对jpg有效。取值范围为 (0,1],不在范围内时当作1.0处理
      fileType: String // 目标文件的类型
    }).then(src => {
      Taro.hideLoading()
      Taro.navigateBack({
        delta: 1
      })
    })
  }

  touchStart = e => {
    this.state.weCropper.touchStart(e)
  }

  touchMove = e => {
    this.state.weCropper.touchMove(e)
  }

  touchEnd = e => {
    this.state.weCropper.touchEnd(e)
  }

  render () {
    return (
      <View className='cropper'>
        <Canvas
          className='canvas'
          canvas-id='cropper'
          disable-scroll='true'
          onTouchStart={this.touchStart}
          onTouchMove={this.touchMove}
          onTouchEnd={this.touchEnd}
          style={cropper}
        />
        <Canvas
          className='target'
          canvas-id='target'
          style={target}
        />
        <CoverView className='cropper-buttons'>
          <CoverView className='uploadImg' onClick={this.uploadTap}>
            重新选择
          </CoverView>
          <CoverView className='getCropperImage' onClick={this.getCropperImage}>
            确定
          </CoverView>
        </CoverView>
      </View>
    )
  }
}

export default Cropper

新建index.scss

.cropper {
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.8);

  .target {
    position: absolute;
    top: 0;
    left: 0;
    transform: translateX(-200%);
  }

  .cropper-buttons {
    position: absolute;
    bottom: 0;
    left: 0;
    z-index: 10;
    display: flex;
    width: 100%;
    height: 100px;
    color: #ccc;
    font-size: 30px;
    background-color: #1a1a1a;
    //font-weight: 900;

    .uploadImg,
    .getCropperImage {
      flex: 1;
      height: 100px;
      line-height: 100px;
      text-align: center;
    }
  }
}

腾讯云cos使用

导入cos-wx-sdk-v5.js到项目src目录下

配置config/index.js文件

weapp:{
	complie:{
		exclude: ['src/assets/js/cos-wx-sdk-v5.js']// 不编译此文件
	}
}

新建cos.js文件

import dayjs from 'dayjs'
import CommonServer from '@/api/common'

const COS = require('../assets/js/cos-wx-sdk-v5')

export const Bucket = Bucket_Name
export const Region = Region_Name

/**
 * 文件扩展名提取
 * @param fileName
 * @returns {string}
 */
export function fileType (fileName) {
  return fileName.substring(fileName.lastIndexOf('.') + 1)
}

/**
 * cos路径定义
 * @param path
 * @param userUuid
 * @param fileType
 * @returns {string}
 */
export function cosPath (path = 'images', userUuid = 'test', fileType = 'png') {
  const day = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS')
  let name = `${day}.${fileType}`
  return `applets/${userUuid}/${path}/${name}`
}

export const cos = new COS({
  getAuthorization: (options, callback) => {
    let data = {
      method: (options.Method || 'get').toLowerCase(),
      pathname: '/' + (options.Key || '')
    }
    CommonServer.getAuthorization(data).then(res => {
      callback(res.authorization)
    }).catch(err => {
      console.log(err)
    })
  }
})

/**
 * cos上传
 * @param FilePath
 * @param path
 * @param userUuid
 * @param fileType
 * @returns {Promise<any>}
 */
export function cosUpload (FilePath, path = 'images', userUuid = 'test', fileType = 'png') {
  return new Promise((resolve, reject) => {
    let Key = cosPath(path, userUuid, fileType)
    if (fileType === 'png') {
      cos.postObject({
        Bucket,
        Region,
        Key,
        FilePath,
      }, (err, res) => {
        if (res.statusCode === 200) {
          const { Location } = res
          let src = `https://${Location}`
          resolve(src)
        } else {
          reject(err)
        }
      })
    } else if (fileType === 'html') {
      cos.putObject({
        Bucket,
        Region,
        Key,
        Body: FilePath,
      }, (err, res) => {
        if (res.statusCode === 200) {
          let src = `https://${Bucket}.cos.${Region}.myqcloud.com/${Key}`
          resolve(src)
        } else {
          reject(err)
        }
      })
    }
  })
}

github地址:taro-template

未完待续…

Logo

智屏生态联盟致力于大屏生态发展,利用大屏快应用技术降低开发者开发、发布大屏应用门槛

更多推荐