uniapp多端构建实战初探


一、背景

小程序具有跨平台、体验好、高灵活性以及即用即走、无需下载安装诸多优势。随着微信团队推出微信小程序,国内各大互联网团队也相继推出了各自的小程序,手机厂商也联合推出了快应用。但由于各种原因,各团队的小程序,并没有形成统一的标准或联盟,导致部分平台差异化大,要实现多个平台的小程序就得写多套小程序代码,这给开发者和企业带来不少额外的负担。这种情况下,一个多端统一的解决方案就显得尤为重要。统一多端标准后,让一处代码,多处运行成为了可能,而uniapp作为国内小程序的开创者,也制定了一套基于Vuejs的解决方案,并跻身前列,成为多端构建解决方案的优秀者之一。

快递100作为中国领先的快递物流信息服务商,在国内各大小程序平台均有发布小程序应用(微信百度支付宝头条QQQQ浏览器美团等)。在迭代的过程中,保持多个平台业务和体验一致十分必要。目前,快递100已实现了多端统一。以下分别为微信小程序、头条小程序和百度小程序二维码,你可以扫码(或者在各个客户端搜索快递100)进行体验。

uniapp多端构建实战初探uniapp多端构建实战初探uniapp多端构建实战初探

二、多端构建方案选型

目前多端构建有不少的解决方案,在比较了多种构建方案后,快递100团队将筛选目标锁定在了Tarouniapp两个解决方案上。关于多端构建解决方案如何选型,我们会考虑以下几个维度:

框架的生态及其社区的大小
框架的性能
框架的学习成本与开发成本
框架支持构建的应用端(如是否支持快应用和QQ小程序)
团队的技术栈的匹配程度

基于以上的维度结合快递100开发团队的实际情况,经过多轮评审和调研后,我们团队最终选择了uniapp。尽管uniapp已经做得足够优秀,但是在实战开发的过程中依然会遇到这样那样的棘手的问题。本文将从零到一搭建一个简单的应用,解决实战过程中遇到的各种问题,并实现稳定的发布到所有的小程序端。

本文不会介绍uniapp相关API与组件的使用,也不会介绍Vue生态具体的使用方法,相关内容可以参考uniapp官网文档以及Vue官网文档。

文章主要包括以下几个方面的内容:

项目的安装
全局API封装
路由拦截
实现Vuex
数据通信方案
登录与用户信息示例
自定义组件说明
常见问题解决方法

三、项目搭建
(一) 创建项目

uni-app支持通过 可视化界面、vue-cli命令行 两种方式快速创建项目。本文将通过以HBuilderX 可视化界面创建项目为例,从零到一搭建起一个完整的项目。安装的方法请查看uniapp官网文档之快速上手

使用uniapp内置组件模板创建后的目录结构如下:

uniapp多端构建实战初探

各目录和文件说明如下:

components: 公共组件目录,默认内置了uniapp的官方扩展组件,应用的公共组件也将放置在该目录下
pages: 业务页面目录,一个页面对应小程序的一个页面路由
static: 存放应用引用静态资源(如图片、视频等)的目录,静态资源只能存放在此目录,该目录下的内容不会经过编译
App.vue: 全局应用文件,用于配置全局样式、全局生命钩子等
main.js: 应用初始化文件,一般全局的扩展均在该文件实现
mainfest.json: 应用配置文件,主要包含各个小程序的appid、版本号等信息
pages.json: 路由配置,主要配置应用的页面路由、外观等
uni.scss: 全局的scss变量文件,在该文件定义的scss变量全局可用

安装后通过HBuilderX运行到微信小程序(需要配置微信开发者工具的路径),效果如下:

uniapp多端构建实战初探

接下来让我们一步一步来完善整个应用。

(二)自定义全局API

在开发的过程中,我们经常需要重复的使用一些方法,如http请求相关的方法,通常我们会将这些方法封装为统一的模块。在这里,我们把公共的方法统一放到项目根目录下的utils目录下。

1. 反馈类UI相关API封装

用户操作的过程中,经常需要一些操作反馈,如toast,modal等。不同的应用一般都有不同的默认反馈(如文案、字体颜色等),我们首先来封装一下这些方法,相对比较简单。

在utils目录下创建interactiveFeedback.js文件

(1)toast

第一步,新建showToastmodal方法,如下:

// toast反馈
export function showToast(content = '', duration = 1500, icon = 'none') {
uni.showToast({
title: content,
icon: icon,
mask: true,
duration: duration
})
}

// 弹窗反馈
export function modal(content = '', opts = { showCancel: true }) {
return new Promise((resolve, reject) => {
uni.showModal({
title: opts.title || '提示',
content: content,
cancelText: (opts.cancelText || '取消').slice(0, 4),
confirmText: (opts.confirmText || '确定').slice(0, 4),
cancelColor: opts.cancelColor || '#bebebe',
confirmColor: opts.confirmColor || '#317ee7',
showCancel: opts.showCancel !== false,
success: res => {
if (res.confirm) {
resolve()
} else if (res.cancel) {
opts.handleCancel === true && reject()
}
}
})
})
}

在小程序中,confirmTextcancelText长度最长为4个字符,以上modal方法遵循promise规范

第二步,定义install方法

function install(Vue) {
Vue.prototype.$toast = showToast
Vue.prototype.$modal = modal
}

可以根据实际情况定义更多的方法,如loadinghideLoading

第三步,导出相关的方法

export default { install }

第四步,打开main.js文件,安装模块

import interactiveFeedback from '@/utils/interactiveFeedback'

Vue.use(interactiveFeedback)

安装后,我们就可以用以下的方式使用:

this.$toast("这是一个吐司提示")
this.$modal("这里是弹窗的提醒内容", {
confirmText: "我知道了",
handleCancel: true
}).then(() => {
console.log("确定了")
}).catch(() => {
console.log('取消了')
})

2. http请求封装

http模块专门用来做接口请求,通常一个应用中接口都会有公共的参数、请求头、授权信息等,将这些信息统一封装非常有必要。由于项目上经常要用到一些常量等,因此,我们先项目根目录下创建config.js文件,用于配置项目上使用的一些常量(如请求超时、接口服务器地址、小程序appid等),这里不一一列举,参照项目源码的config.js文件

接着在utils目录下创建request.js文件

第一步,引入常量和其他公共方法配置

import store from '@/store'
import { showToast } from './interactiveFeedback.js'
import {
API_BASE,
NETWORK_TIMEOUT,
DEFAULT_ERR_MESSAGE,
PLATFORM,
NETWORK_ERR_MESSAGE
} from '@/config'

第二步,定义两个核心处理方法

/**
* 基本的http请求
* @param {url: String} 请求的链接
* @param {opts.data: Object} 请求的参数
* @param {opts.needAuth: Boolean } 是否需要登录
* @param {opts.handleFail: Boolean} 是否处理错误信息,设置为false将忽略请求时的错误
* @param {opts.accessToken: Boolean} 是否将token覆盖为提供商token
* */
export async function request(url, opts = {}) {
opts.data = opts.data || {}
if (opts.needAuth) {
const userinfo = await store.dispatch('user/getUserInfo') // 获取用户信息
opts.data.openid = store.getters.openid
opts.data.token = store.getters.token
opts.data.unionid = store.getters.unionid
}
return handleRequest(url, opts)
}

/**
* 请求处理
*/
async function handleRequest(url, opts, isUpload) {
opts.data.platform = PLATFORM.platform
opts.data.appId = PLATFORM.appid
let err = null
let res = null
if (isUpload) {
[err, res = {}] = await uni.uploadFile({
url: url,
filePath: opts.file,
name: opts.name,
formData: opts.data
})
} else {
[err, res = {}] = await uni.request({
url: `${API_BASE}${url}`,
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: opts.method || 'POST',
timeout: opts.timeout || NETWORK_TIMEOUT,
data: opts.data || {}
})
}
err && console.warn(`${url}接口错误:`, err)
opts.debug && console.info('接口结果:', res)
return new Promise((resolve, reject) => {
if (err) {
if (opts.handleFail !== false) {
showToast(NETWORK_ERR_MESSAGE)
return reject(res)
}
} else {
// 404, 502等服务器错误信息
if (typeof res.data !== 'object') res.data = { status: res.statusCode, message: DEFAULT_ERR_MESSAGE, data: res.data }

if (res.data.status === '200') {
return resolve(res.data)
} else if (res.data.status === '403' && opts.handleLogin !== false) { // 未登录或者登录失效
showToast('请先登录', 1000)
return uni.navigateTo({
url: '/pages/login/login'
})
} else {
if (opts.handleFail !== false) {
showToast(res.data.message || DEFAULT_ERR_MESSAGE)
}
return reject(res)
}
}
})
}

说明,以上store为基于vuex的数据管理,下文会实现

第三步,实现getpost以及upload方法

export function get(url, opts = {}) {
opts.method = 'GET'
return request(url, opts)
}

export function post(url, opts = {}) {
opts.method = 'POST'
return request(url, opts)
}

/**
* @param {url} 上传地址
* @param {file} 文件路径
* @param {name} 文件的表单名称
* @param {formData} 额外需要提交的字段
* */
export async function upload(url, file, name = 'file', formData = {}) {
if (!file) return
formData.openid = formData.openid || store.getters.openid
if (formData.needAuth) {
const userinfo = await store.dispatch('user/getUserInfo')
formData.openid = userinfo.openid || formData.openid
formData.token = userinfo.token
}
return handleRequest(url, {
file: file,
name: name,
data: formData
}, true)
}

第四步,导出响应的方法:

function install(Vue) {
Vue.prototype.$request = request
Vue.prototype.$get = get
Vue.prototype.$post = post
Vue.prototype.$upload = upload
}
export default { install }

第五步,安装方法

打开main.js文件,安装对应的方法

import request from '@/utils/request'

Vue.use(request)

做完以上工作后,我们可以像下面的方式在页面上调用http相关的方法


export default {

onLoad(){
this.getOrders()
},
methods: {
getOrders(){
this.$get('orders', {
needAuth: true,
data: {
userid: this.$store.getters.userinfo.id
}
}).then(res => {
console.log(res)
}).catch(res => {
console.warn(res)
})
}
}

}


3. 小结

以上通过Vue.use安装的方式,在Vue原型上扩展公共的方法,从而达到可以在页面(Vue实例)上调用对应的方法来实现调用接口等功能,如果需要扩展更多的方法和属性,均可以以该种方式进行扩展。

(三)路由拦截

路由拦截几乎是每一个应用都需要用到的功能,比如页面跳转前的登录判断。在Vue框架开发的单页应用中,我们会结合Vuex-Router来实现响应的功能,Vue-Router提供了丰富的路由守卫,实现路由的拦截十分便利。然而,在小程序上实现路由拦截是相对比较麻烦的。uniapp社区上也有一些解决方案,比如一个完全相似Vue-router的路由插件,该插件重写了uniapp的一些方法以及生命钩子,从而实现了绝大部分的Vue-Router的钩子,有兴趣的可以使用该插件。

实际情况是,我们往往不需要像Vue-Router那样完整的路由拦截,有时我们仅仅只需要登录前的一层简单的校验,这样如果引入一个完整的路由拦截解决方案会显得有点冗余,同时,也会让应用的包更加大(小程序均对单个包有大小的限制)。面对这种情况,我们仅需要做一些简单的扩展即可达到我们的需要。以下,以登录拦截为例,简单的实现一下路由的拦截。我们的需求是,对于需要登录才可以进入的页面,我们先做一下登录校验,如果未登录,则跳转前先跳转至登录页面。

在utils目录下创建extends.js文件,该文件专门用来对uniapp框架做一些额外的扩展。添加如下代码:

import store from '@/store'

export default function() {
['navigateTo', 'redirectTo', 'switchTab', 'navigateBack'].map(item => {
const nativeFunc = uni[item]
uni[item] = function(opts, needAuth) {
if (needAuth) {
store.dispatch('user/getUserInfo').then((res) => {
nativeFunc.call(this, opts)
})
} else {
return nativeFunc.call(this, opts)
}
}
})
}

代码解读:对于路由相关的几个方法进行遍历并缓存原方法,在uni对象上重写方法,每个方法增加needAuth参数,表示跳转前是否需要登录

如果需要登录,则首先获取用户信息,如果获取不到用户信息,将会自动跳转到登录页(store里面的user模块做的),否则获取到信息就调用缓存起来的原方法,实现跳转
如果不需要登录,则直接调用原方法

打开main.js,引入该文件并执行

import uniExtend from '@/utils/extends'

uniExtend()

通过一个简单的扩展,我们就实现了一个跳转前的登录拦截。除了登录拦截外,针对uniapp上提供原生方法,均可以以该方式进行扩展。

由于小程序的限制,拦截必须调用uniapp上对应的路由路由方法,通过其他途径跳转页面,无法拦截。包括以上社区上提供的拦截方案一样受这个限制

(四)状态管理器Vuex

一个集中式的状态管理方案在一个应用中显得尤为重要,而uniapp本身是支持整合Vuex的。因此,我们也在该项目中,也集成了Vuex。

首先,在根目录下创建store目录,专门用于存放状态管理相关的文件。为了让所有的状态结构更加清晰,更好维护,我们采用vuex的modules来管理各个模块的数据。我们在store目录下创建modules目录index.js文件,前者用于管理各个数据模块,后者用于导出相关的数据。此时,项目的目录结构如下:

uniapp多端构建实战初探

接下来,我们以用户模块为例,来实现一个基于vuex的状态管理器。

打开store/index.js文件,写入以下代码

import Vue from 'vue'
import Vuex from 'vuex'

const modules = require.context('./modules', true, /.js$/)
let moduleArr = []
modules.keys().map(key => {
moduleArr[key.replace(/(\.\/)|(\.js)/ig, '')] = modules(key).default
})

Vue.use(Vuex)

const store = new Vuex.Store({
modules: {
...moduleArr
}
})

export default store

说明:此处读取modules目录下的所有文件,获取所有的module,这要求module目录下的文件都是导出一个vuex模块。你可以按照实际情况,修改以上代码

接着,打开main.js文件,添加如下代码:

import store from './store'

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

到这里,已经简单的实现了在应用上使用Vuex。接下来,添加用户用户模块,在modules目录下创建user.js文件。通常一个module包含statemutationsactions。首先我们实现一下state,代码如下:

const KEYS = ['token', 'openid', 'unionid']

const getDefaultState = () => {
const result = {}
KEYS.map(key => {
result[key] = uni.getStorageSync(key) || ''
})
return result
}

const state = getDefaultState()

定义一个state函数,用于设置进入页面时默认的state,我们会优先读取本地缓存里的用户授权信息,每次登录授权后,会将授权信息存入本地缓存。在这里,我们授权的信息包含token,openid和unionid三个信息(正常应该走小程序的授权等操作,这里仅作数据模拟)。接着实现一下mutations,代码如下:

const mutations = {
SET_AUTH: (state, info) => { // 设置token等信息
KEYS.map(key => {
if (typeof info[key] !== 'undefined') {
state[key] = info[key]
uni.setStorageSync(key, info[key])
}
})
}
}

在这里,我只定义了一个设置授权信息的mutation,传入授权后的信息,如果对应的授权信息不为undefined,则将信息设置到state并且缓存在本地。然后再实现一下actions。actions负责登录以及用户信息的获取。主要包括登录、登出、获取用户信息和授权信息等,实现分别如下:

获取授权信息

如果已经授权过了,则直接返回授权信息,否则的话会跳转至登录页,进行登录

  getAuth({ state }) { // 获取用户授权信息
return new Promise(resolve => {
if (state.token) { // 已授权,直接返回
resolve({
openid: state.openid,
token: state.token,
unionid: state.unionid
})
} else { // 未授权,跳转到登录页
uni.navigateTo({
url: `/pages/login/login`
})
}
})
}

登录与登出

login({state, commit, dispatch}, data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if(data.name === 'test' && data.password === 'test') {
const data = {
token: "testToken",
openid: "testOpenId",
unionid: "testUnionId"
}
commit('SET_AUTH', data)
resolve(data)
} else {
reject({
message: "账号或密码错误",
code: '100401'
})
}

}, 1000)
})
},
logout({ commit, dispatch }) { // 退出登录
commit('SET_AUTH', {
openid: '',
token: '',
unionid: ''
})
return Promise.resolve()
}

以上模拟了一个账号登录,当登录后,返回token等授权信息,并调用commit('SET_AUTH', data)更新state以及本地缓存;登出时则清空所有的登录授权信息。最后再实现用户信息获取的action:

  getUserInfo({state, commit, dispatch}) { // 本地获取用户信息
return new Promise(resolve => {
dispatch('getAuth').then(res => {
resolve({
username: "test",
age: 18,
email: "test@test.test"
})
})
})
}

当获取用户信息时,首先会进行登录授权,授权成功后则模拟返回用户信息。到这里一个简单的与用户相关的vuex模块就完成了。最后就是导出模块供应用使用:

export default {
namespaced: true,
state,
mutations,
actions
}

为了方便页面访问state数据,通常我们还会创建一些getters。在store目录下新建文件getters.js,添加如下代码:

const getters = {
openid: state => state.user.openid,
token: state => state.user.token,
unionid: state => state.user.unionid
}

export default getters

打开store/index.js,修改代码如下:

import getters from './getters'

const store = new Vuex.Store({
modules: {
...moduleArr
},
getters
})

这样,就可以在页面上使用this.$store.getters.token以及使用Vuex的mapGetters方法来访问我们的getters了。

mapGettersmapState等辅助函数均可以使用

到这里,一个简单的数据管理器就算完成了,如果需要添加更多的数据状态,可以按照用户模块的组织方式对数据进行管理。有了数据管理以及数据请求等基础模块,就可以实现具体的业务页面。下面我们以用户登录与信息获取为例,实现一个简单的业务页面。

(五)一个简单的例子–登录与信息获取

(1)修改pages/index/index为如下代码

<template>
<view class="container">
<view>用户名:{{userinfo.username}}</view>
<view>年龄:{{userinfo.age}}</view>
<view>邮箱:{{userinfo.email}}</view>
<button @tap="logout" type="warn" style="margin-top: 30rpx;" v-if="userinfo.username">退出登录</button>
<button @tap="getUserinfo" type="primary" style="margin-top: 30rpx;" v-else>获取用户信息</button>
</view>
</template>

export default {
data() {
return {
userinfo: {
username: "",
email: "",
age: ""
}
}
},
methods: {
getUserinfo() {
this.$store.dispatch('user/getUserInfo').then(res => {
this.userinfo = res
})
},
logout() {
this.$store.dispatch('user/logout').then(() =>{
this.userinfo.username = ""
this.userinfo.age = ""
this.userinfo.email = ""
})
}
},
onUnload() {
uni.$off("loginSuccess", this.getUserinfo)
},
onLoad() {
uni.$on("loginSuccess", this.getUserinfo)
}
}

uni.$offuni.$on为uniapp的通信方案,后文会讲述

加上样式后,效果如下:

uniapp多端构建实战初探

接着实现登录页面。在pages目录下新建login/login.vue文件。添加如下代码:

<template>
<view class="wrap">
<view class="form-item">
<input v-model.trim="name" type="text" placeholder="请输入用户名" class="input">
</view>
<view class="form-item">
<input v-model.trim="password" type="password" placeholder="请输入密码" class="input">
</view>
<view class="button" @tap="submit">登录</view>
</view>
</template>

export default {
data() {
return {
name: '',
password: ''
}
},
methods: {
valid() {
if (!this.name) {
return this.$toast('请输入用户名')
}
if (!this.password) {
return this.$toast('请输入密码')
}
return true
},
submit: function() {
if (!this.valid()) return
uni.showLoading({
title: "正在登录..."
})
this.$store.dispatch('user/login', {
name: this.name,
password: this.password
}).then(res => {
console.log(res)
uni.$emit("loginSuccess", res)
uni.navigateBack()
}).catch(res => {
this.$toast(res.message)
}).finally(() => {
uni.hideLoading()
})
}
},
onLoad() {
this.$store.dispatch('user/logout')
}
}

逻辑比较简单,进入页面后使用this.$store.dispathc('user/logout')清空原有的登录状态,登录成功后发布事件loginSuccess并返回上一页,效果如下:

uniapp多端构建实战初探

以上两个页面完成后,完整的操作演示如下:

uniapp多端构建实战初探

(六)数据通信方案

在uniapp中,数据的通信通常包含组件之间的通信以及页面之间的通信。同样,在小程序中一般也包含这两种通信。在这里我们只讨论页面之间的数据通信(组件之间的通信参照Vue官方的组件通信)。一般情况下,在uniapp中我们有以下的几种方式来实现页面之间的通信:

    使用url传参
    使用本地缓存
    使用全局数据(如小程序的globalData)
    使用getCurrentPage方法获取对应的页面进行通信
    使用eventBus
    使用uniapp自带的事件发布订阅
    使用Vuex

以上几种方案中,前4中方案在应用变得越来越复杂的时候,会使页面之间的耦合度变得越来越高,从而不利于应用的维护。因此,通常页面之间的数据通信我们会采用后三种方案。比如前面的登录操作的例子中,则是使用了uniapp自带的事件发布订阅通信的方案。其本质上也是一个eventBus。

当然,这不是说前4中方案就不能在应用中使用,某些场景使用前四种方案可能会更简洁、方便、甚至是必须的。比如通过url参数区分渠道,记住某些状态(本地缓存)等。在实际操作的过程中,应该灵活选择、灵活应用。

uniapp的事件发布订阅,请参考官方文档:页面通信

使用的时候需要注意以下几点:

订阅了事件后在页面销毁时记得取消订阅,使用uni.$off
使用uni.$off建议传递第二个参数,取消当前页面的订阅,而不是取消所有的订阅。这需要在订阅的时候也传入第二个参数,两个回调应指向同一个引用

(七)自定义组件

组件化是Vue的一个核心之一,使用uniapp同样支持自定义组件。但是,自定义组件在各小程序中,由于实现的方案并不一致,所以自定义组件并不总是能完美的运行在各个端的小程序中。这需要在实现的过程做一些特殊的兼容处理。查看uniapp官方的组件库源码,同样也会为各个平台写一些兼容代码。本文不详细介绍如何一步一步的写一个自定义的组件。按照官方组件库的规范,实战中我们通常会遵循以下几个原则:

与业务无关的通用组件放置在根目录的下的compoments目录下,同时,需要放置在额外的目录,方便统一管理,如components/my-component
业务组件放置在对应页面模块的components目录下,如pages/login/components/phoneLogin/phoneLogin.vue
组件的命名方式采用横线连接的方式,如my-dialog
组件需要放置在同名目录下,如components/my-components/my-dialog/my-dialog.vue
组件的样式类型最好采用BEM规范的命名方式
如果需要扩展uniapp官方的组件,不应该修改源码,采用拷贝的方式拷贝到自定义的组件目录,同时,组件命名不要以uni-开头

四、常见问题与解决方案

1. 使用@import引入外部样式无效或者报错

uniapp官方给出的引用外部的样式方式为@import url('./common.scss'),但应用时发现编译报错或者不生效,此时改为@import './common.scss'即可。

2. placeholder-class不起作用

在小程序中,可以使用placeholder-class来修改页面的输入框占位符的样色、字体大小等。使用时会发现自定义组件不起作用,页面样式如果加了scoped属性也会不起作用。解决办法为:

自定义组件中输入框使用placeholder-style替代
非自定义组件中的输入框可以在App.vue全局设置,并增加important关键字(百度小程序无效)

textarea的处理方法一样。

3. v-show不起作用
使用v-show指令时,某些情况下(如基于数组的length属性来判断),在小程序下不会正常工作,因为小程序的hidden属性是不响应该变化的,可使用v-if代替

4. $refs不起作用

uniapp中可以使用$refs获取页面对应的组件,但是前提是在小程序中组件为自定义组件,原生的组件无法获取。另外,在支付宝小程序中,使用$refs只能获取到一级的组件引用,嵌套的自定义组件无法获取,但是可以通过签到的$refs来获取到,如下:

<template>
<popup ref="popup">
<view class="picker">
<region ref="picker" />
</view>
</popup>
</template>

  mounted() {
this.popup = this.$refs.popup
// 支持宝不支持嵌套的ref
this.picker = this.$refs.picker || this.popup.$refs.picker
}

5. 包大小过大,无法上传

小程序对包的大小有限制,不同的小程序限制不一样。一般我们会采用分包的方式来处理。但是在开发模式下,由于uniapp编译时会附带sourcemap,导致分包也会很大,从而调试时无法上传到真机进行调试,如QQ小程序。此时,我们在编译时可以选择压缩代码,同时,在小程序开发者工具也选择上传时自动压缩即可。如下:

在HBuilderX选择运行 - 运行到小程序模拟器 - 勾选运行时压缩
在开发者工具选择详情 - 本地设置 - 勾选上传代码时自动压缩混淆

6. textarea穿透和错位问题

textarea穿透以及滚动的问题属于小程序的问题。textarea属于原生组件,在放置到具有fixed定位的弹窗之类的容器时就会出现这样那样的问题。通常我们有两种解决方案:

弹窗展示前先隐藏掉textarea或者使用其他的元素代替,弹窗展示完毕后再显示textarea,这样可以避免弹窗位置错位的问题
输入组件失去焦点时替换为view组件,获得焦点时恢复为textarea组件

五、总结

多端构建的解决方案让我们写一套代码运行到多个端成为了可能,大大减低了开发者负担,也节省了企业的资源。但由于平台不可避免的差异性,仍然存在不少问题需要开发者自行去解决。另外,快应用目前支持的程度非常弱,uniapp官方已经支持了基于webview渲染的快应用,但是依然存在不少问题,同时,该模式目前仅华为/OPPO/Vivo支持,其他联盟厂商并不支持。基于原生快应用模式的渲染,uniapp计划由社区来实现,目前仍旧没有好的解决方案。而从Taro官方文档来看,Taro的支持应该也比较有限(未测试,欢迎告知测评结果),可能不能用于比较复杂的应用。

无论是Taro也好,uniapp也罢,目前社区都在不断的完善和壮大。相信在国内小程序厂商、快应用联盟以及社区的努力下,多端构建的方案会日趋成熟,并能够形成一套更加统一的、优秀个解决方案。

本文项目代码请点击此处查看:

Github

码云

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

uniapp多端构建实战初探

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » uniapp多端构建实战初探
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏