阅读 5011

uni-app转字节跳动、百度小程序手摸手实战

背景

从最早发布的微信小程序,到后来的支付宝小程序、钉钉小程序,字节跳动小程序、百度小程序、QQ小程序等,面对这么多套的代码,开发者去编写多套原生代码的成本显然非常高,使用H5的话体验又没有原生好,这时候只需编写一套代码,就能够适配多端的能力就显得尤为需要。
下面进入正题,给大家介绍下uni-app字节小程序的开发

前置准备工作

项目开发

新建项目

可以通过HBuilderX可视化界面 以及 vue-cli命令行方式进行创建
下面主要介绍下通过vue-cli命令行这中方式来新建项目

  • 全局安装vue-cli
npm install -g @vue/cli
复制代码
  • 创建
vue create -p dcloudio/uni-preset-vue user-uni-order
复制代码

安装成功后提示选择模板,我们选择默认模板就可以了

项目整体流程

用户下单最短流首页下单-> 订单状态-> 完成支付, 如下:

综上我们需要做的页面维度: 首页,地址检索,城市选择,登录,个人中心,订单列表,webview(收费标准 , 预估价格, 订单状态, 订单详情,法律条款)

制定目录结构

┌─components      //uni-app组件目录
│    └─comp-a.vue     //可复用的a组件
├─common   // 通用的js&css工具等
├─hybrid    //存放本地网页的目录
├─platforms     //存放各平台专用页面的目录
├─pages       //业务页面文件存放的目录
│    ├─index
│    │   └─components   // 页级别组件
│    │   └─vuex  // index页面vuex主要存放index的逻辑
│    │   └─index.vue   // index页面
├─static    //存放应用引用静态资源(如图片、视频等)
│ ├─mp-weixin   //条件编译png
│     │ └─a.png
│     │ └─b.png
├─store // 状态统一管理,将各个页面的vuex汇总
├─service // 汇总请求,api等
│    └─api.js // 接口api相关
│    └─config.js // 环境配置
│    └─index.js
│    └─request.js // 网络请求
├─ttcomponents // 头条小程序自定义组件存放目录 
├─main.js      //Vue初始化入口文件
├─App.vue      //应用配置,用来配置App全局样式以及监听 
├─manifest.json //配置应用名称、appid、logo、版本等打包信息
└─pages.json //配置页面路由、导航条、选项卡等页面类信息
复制代码

运行项目

想运行到哪个平台小程序,首先需要把相应的APPID, IDE路径对应填写正确

npm run dev:mp-toutiao // 实时监听编译
复制代码

运行成功如下提示:

此时打开字节跳动IDE进行导入操作,就可以看见页面啦~~~

Tips:使用字节跳动编译器打开uni-app编译的小程序时,必须进行导入操作,而不是新建,因为新建会默认成代码片段,虽然也可以实时预览效果但是会导致上传功能确实

具体页面的开发

首页开发

  • 页面效果

  • 首页目录结构

项目中其他页面的目录结构与首页均相同,后面不做多余赘述。

├─pages
│    ├─index
│    │   └─components
│    │   └─vuex
│    │   │  └─index.js // 首页逻辑
│    │   └─index.vue
复制代码
  • 我们使用vuex来管理状态,每个页面都有自己的vuex, 其中index.js存放对应页面相关逻辑,为了避免频繁切换目录,把state, mutations, actions放在一个文件下,使用时并启用vuex的模块化,如下
const IndexPage = {
    namespaced: true,  // 启用模块化vuex
    state: {
        ... // 需要共享的状态
    },
    mutations: {
        ... // 一些方法
    },
    actions: {
        ... // 请求相关
    }
}

export default IndexPage  //最后导出IndexPage
复制代码
  • 各个页面的vuex统一放在store里
import Vue from 'vue'
import Vuex from 'vuex'

import IndexPage from '../pages/index/vuex'
import AddressSearch from '../pages/address/vuex/index'
import CityListPage from '../pages/city-list/vuex/index'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
  ... // 全局共用的状态
  },

  mutations: {
  },

  actions: {
  },

  modules: {
    IndexPage, // 首页vuex
    AddressSearch, // 地址检索页vuex
	CityListPage, // 城市列表页vuex
  },
})

export default store
复制代码
  • 最后,在main.js里面引用
import Vue from 'vue'
import App from './App'
import store from './store'

Vue.config.productionTip = false

App.mpType = 'app'

const app = new Vue({
  ...App,
  store
})
app.$mount()
复制代码

完整的首页逻辑交互框架就搭建成功了,以下是开发首页时遇见的问题

首页开发遇到的问题

  • 使用swiper轮播组件,写好子组件,父组件因为无效果

问题原因:引入的import swiper from "../../components/swiper/swiper" ,导致把自定义的swiper覆盖,所以不展示

解决:引入的import uniSwiper from "../../components/swiper/swiper" ,不和原组件命名冲突即可

  • 转百度小程序header报错

问题原因:百度设置http请求header如果有中文字符

解决:使用条件编译,如果是百度小程序需要encodeURI 一下, 或者删除header的中文部分

  • uni-app的image标签,在小程序端不支持动态引入图片
// 引不到
 <image class="tip_icon" src="/static/sender{{endPoint.address ? '' : '_default'}}.png"/>

复制代码
// 可以引入
<image class="tip_icon" src="/static/sender.png"/>
复制代码
  • uni.getLocation() 只能获取经纬度,获取不到详细地址信息
  • 非 h5 平台 :key 不支持表达式
    由于:key="timer__${idx}"的写法,编译时控制台会警告,但是不影响页面
<view 
    class="column_item" 
    v-for="(item, idx) in item" 
    :key="`timer__${idx}`" // 改成:key="idx" 即可
>
{{item == "立即用车" ? "" : index == 1 ? "时" : index == 2 ? "分" : ""}}
</view>
复制代码
  • 父子组件传参,类型定义不对不提示错误信息,只展示null, 所以遇到null问题可以查看是否是类型定义不一致
  • uni-app中的watch不支持监听小程序,如下
watch: {
    searchType (to) {
      if (to) {
      // 如果是起始地回填起始地信息否则回填目的地信息
        if (to === SEARCH_TYPE.START) {
          this.detailAddress = this.startAddress.detailAddress || ''
        } else {
          this.detailAddress = this.endAddress.detailAddress || ''
        }
      }
    }
}
复制代码

改成

mounted () {
	if (this.searchType === SEARCH_TYPE.START) {
		this.detailAddress = this.startAddress.detailAddress || ''
	} else {
		this.detailAddress = this.endAddress.detailAddress || ''
	}
}
复制代码

个人中心开发

  • 页面效果
    个人中心

    个人中心主要涉及的H5页面以及小程序的授权登录功能。所以主要部分就是webview的实现。
  • webview的实现
// template
<web-view id='web-view' v-if='src' :src='src' @bindmessage='onmessage'></web-view>
复制代码
onLoad (options) {
    console.log('H5入口页获取到的参数', options)
    let { src, needLogin} = options
    if(!needLogin){
    	this.src = decodeURIComponent(src)
    	return
    }
    // 需要登录的 就先获取临时token
    this.fetchTempToken(src)
}
复制代码

如果不需要登录的H5我们直接赋值到src即可,需要登录才能正常访问的页面,首先要获取临时token,拿到临时token后回传给服务端并且采用中间页redirectUrl的形式跳转。

个人中心开发遇到的问题

  • 向网页传递信息要使用头条api的bindmessage

官方说明“网页向小程序 postMessage 时,会在特定时机(小程序后退、组件销毁、分享)触发并收到消息”

// 在小程序中调起H5中的打电话功能
onmessage (e) {
	let { phoneNumber, name } = e.detail
	if(name == 'makePhoneCall'){
		uni.makePhoneCall({
			number: phoneNumber
		})
	}
}
复制代码

需要注意的web-view的bindmessage属性并不是实时的

  • 真机拨打电话功能不能用
// 使用uni.makePhoneCall真机没反应
uni.makePhoneCall({ phoneNumber: '114'});

复制代码

解决:改为头条api的tt开头

// 真机模拟器均可正常使用
tt.makePhoneCall({ phoneNumber: '114'});
复制代码

登录开发

  • 头条授权登录效果

  • 百度授权登录效果

  • 大致思路:
    1.首先获取获取服务供应商的信息
    2.调用uni.getProvider获取授权code
    3.获取用户的手机号(用户登录头条app的)
    4.从@getphonenumber回调中获取到用户信息
    5.调用授权登录服务api
    6.获取token, openid等信息

// template
<view class="login-page">
	<view class="title">
		<view class="h-line"></view>
		<view class="page-title">授权登录更快捷</view>
		<view class="h-line"></view>
	</view>
	<view class="authLogin-wrapper">
		<!-- #ifdef MP-BAIDU -->
		<button type="default" open-type="getPhoneNumber" @getphonenumber="authLoginTap" class="login authLogin">百度登录更快捷</button>
		<!-- #endif -->
		<!-- #ifdef MP-TOUTIAO -->
		<button 
			type="default" 
			class="login authLogin"
			open-type="getPhoneNumber"
			@getphonenumber="onGetPhoneNumber"
		>授权手机号快捷登录</button>
		<!-- #endif -->
	</view>
</view>
复制代码
// 完成渲染调用授权code方法
mounted () {
	this.getCode()
}
复制代码
// 获取授权code方法
async getCode () {
	const [ errorProvider, provider ] = await uni.getProvider({ service: 'oauth' })

	if (errorProvider) {
		console.log('获取provider失败')
		return 
	}

	const [ errLogin, data ] =	await uni.login({
		provider: provider.provider[0],
		force: true
	})

	if (errLogin) {
		console.log('获取code失败')
        // 失败的操作,提示等
		return
	}

	const { code } = data
	this.code = code
},

// 头条获取到用户信息  
async onGetPhoneNumber ({ detail }) {
	const { errMsg } = detail
	// 授权失败
	if (errMsg.indexOf('auth deny') > -1) {
        // 取消授权进行手机验证码登录
		return
	} 
	
	try {
        // 调用服务授权接口
		const { data } = await authLogin({
			code: this.code,
			...detail,
		})
		
		if (data.code === SUCCESS) {
		    // 存token, openid等操作
		    // 重新更新个人信息
		} else {
	        // 失败的提示等
		}
	} catch (error) {
		// 登录失败异常情况处理
	}
	
},
// 百度获取到用户信息同理头条。。。
复制代码

登录开发遇到的问题

手机验证码开发时,引入checkbox-group报错,如下图:

原因:components : { [CheckBox.name]: CheckBox } 引入组件方式不支持

发布到测试环境

以字节跳动为例子,打开字节跳动开发者工具,在工具栏找到上传,填写版本号,发布。版本号不和上一次冲突就可以。
Tips: 前面有提过,新建代码片段是在开发者工具上是没有上传按钮的,要导入项目才可以。

上传成功后,会提示进入小程序开发者平台,现在可以看到开发者的版本。

上图二维码就可以只作为本次的体检版本来扫一扫了。

发布到正式环境

  • 前置准备,在后台配置好相关线上域名
  • 切换到线上环境
// 环境相关配置
export const ENV = {
  // 开发环境
  RD: 'rd',
  // 测试环境
  TEST: 'test',
  // 沙箱环境
  BOX: 'box',
  // 线上环境
  ONLINE: 'online'
}

// 环境切换
export function getCurrentEnv() {
  return ENV.ONLINE // 正式环境切到online
}
复制代码
  • 在开发者工具中点击上传
  • 去小程序开发者平台提审发布
  • 发布成功后可在头条搜索栏中搜到,抖音的话目前只有安卓平台上线了小程序功能。

结束语

以上就是uni-app转字节跳动、百度小程序的部分开发,相信大家对uni-app实战小程序已经有了初步认识,也欢迎大家指正,互相交流,共同进步

扫码体验

快去打开头条app搜索"快狗打车小程序"或者使用头条扫一扫体验一下吧~

关注我们

公众号@前端论道