提交 7eb0355e 编写于 作者: Q qiang

feat: app 端使用原生 tabBar

上级 14c86359
...@@ -331,6 +331,10 @@ module.exports = function (pagesJson, userManifestJson) { ...@@ -331,6 +331,10 @@ module.exports = function (pagesJson, userManifestJson) {
if (conditionPagePath && isNVueEntryPage) { if (conditionPagePath && isNVueEntryPage) {
isNVueEntryPage = appJson.nvue.entryPagePath === conditionPagePath isNVueEntryPage = appJson.nvue.entryPagePath === conditionPagePath
} }
manifestJson.plus.useragent.value = 'uni-app'
manifestJson.launch_path = '__uniappview.html'
manifestJson.plus.launchwebview.id = '1'
manifestJson.plus.launchwebview.kernel = 'WKWebview'
if (process.env.UNI_USING_NATIVE) { if (process.env.UNI_USING_NATIVE) {
appJson.entryPagePath = appJson.nvue.entryPagePath appJson.entryPagePath = appJson.nvue.entryPagePath
// networkTimeout // networkTimeout
...@@ -346,51 +350,43 @@ module.exports = function (pagesJson, userManifestJson) { ...@@ -346,51 +350,43 @@ module.exports = function (pagesJson, userManifestJson) {
}) })
delete appJson.nvue delete appJson.nvue
delete manifestJson.plus.launchwebview.kernel
manifestJson.launch_path = ''
Object.assign(manifestJson.plus.launchwebview, {
id: '1',
uniNView: {
path: appJson.pages[0]
}
})
// 纯 nvue 带 tab // 纯 nvue 带 tab
if (pagesJson.tabBar && pagesJson.tabBar.list && pagesJson.tabBar.list.length) { if (pagesJson.tabBar && pagesJson.tabBar.list && pagesJson.tabBar.list.length) {
manifestJson.launch_path = '__uniapptabbar.html' const tabBar = manifestJson.plus.tabBar = Object.assign({}, pagesJson.tabBar)
manifestJson.plus.secondwebview = { tabBar.height = '56px'
id: '1', // 首页是 tabBar 页面
mode: 'child', const item = tabBar.list.find(page => page.pagePath === appJson.pages[0])
top: 0, if (item) {
bottom: 56, tabBar.child = ['lauchwebview']
uniNView: { tabBar.selected = tabBar.list.indexOf(item)
path: appJson.pages[0]
}
} }
} else { // 纯 nvue 不带 tab } else { // 纯 nvue 不带 tab
manifestJson.launch_path = ''
Object.assign(manifestJson.plus.launchwebview, {
id: '1',
uniNView: {
path: appJson.pages[0]
}
})
} }
} else if (isNVueEntryPage) { // 临时 tabBar 页面
manifestJson.launch_path = '__uniapptabbar.html'
} else if (pagesJson.tabBar && pagesJson.tabBar.list && pagesJson.tabBar.list.length) { } else if (pagesJson.tabBar && pagesJson.tabBar.list && pagesJson.tabBar.list.length) {
manifestJson.launch_path = '__uniapptabbar.html' const tabBar = manifestJson.plus.tabBar = Object.assign({}, pagesJson.tabBar)
manifestJson.plus.secondwebview = { tabBar.height = '56px'
id: '1' if (isNVueEntryPage) {
} manifestJson.plus.launchwebview.id = '2'
manifestJson.plus.secondwebview.launch_path = '__uniappview.html' } else {
if (!manifestJson.plus.secondwebview.kernel) { // 首页是 tabBar 页面
manifestJson.plus.secondwebview.kernel = 'WKWebview' const item = tabBar.list.find(page => page.pagePath === entryPagePath)
} if (item) {
// 首页是 tabBar 页面 tabBar.child = ['lauchwebview']
if (pagesJson.tabBar.list.find(page => page.pagePath === entryPagePath)) { tabBar.selected = tabBar.list.indexOf(item)
manifestJson.plus.secondwebview.mode = 'child' }
manifestJson.plus.secondwebview.top = 0
manifestJson.plus.secondwebview.bottom = 56
} }
} else { // 无 tabbar 的页面,launchWebview 的 id 为1 } else { // 无 tabbar 的页面,launchWebview 的 id 为1
manifestJson.launch_path = '__uniappview.html'
manifestJson.plus.launchwebview.id = '1'
if (!manifestJson.plus.launchwebview.kernel) {
manifestJson.plus.launchwebview.kernel = 'WKWebview'
}
} }
} }
...@@ -415,4 +411,4 @@ module.exports = function (pagesJson, userManifestJson) { ...@@ -415,4 +411,4 @@ module.exports = function (pagesJson, userManifestJson) {
return [manifest, parseConfig(appJson)] return [manifest, parseConfig(appJson)]
} }
return [app, manifest] return [app, manifest]
} }
...@@ -31,11 +31,14 @@ export function switchTab ({ ...@@ -31,11 +31,14 @@ export function switchTab ({
} }
}) })
currentPage.$remove() currentPage.$remove()
if (currentPage.$page.openType === 'redirect') { // 延迟执行避免iOS应用退出
currentPage.$getAppWebview().close(ANI_CLOSE, ANI_DURATION) setTimeout(() => {
} else { if (currentPage.$page.openType === 'redirect') {
currentPage.$getAppWebview().close('auto') currentPage.$getAppWebview().close(ANI_CLOSE, ANI_DURATION)
} } else {
currentPage.$getAppWebview().close('auto')
}
}, 100)
} else { } else {
// 前一个 tabBar 触发 onHide // 前一个 tabBar 触发 onHide
currentPage.$vm.__call_hook('onHide') currentPage.$vm.__call_hook('onHide')
...@@ -64,9 +67,9 @@ export function switchTab ({ ...@@ -64,9 +67,9 @@ export function switchTab ({
path, path,
query: {}, query: {},
openType: 'switchTab' openType: 'switchTab'
}) }), 'none', 0
) )
} }
setStatusBarStyle() setStatusBarStyle()
} }
...@@ -9,7 +9,6 @@ import { ...@@ -9,7 +9,6 @@ import {
} from './page' } from './page'
import { import {
registerPlusMessage,
consumePlusMessage consumePlusMessage
} from './plus-message' } from './plus-message'
...@@ -112,7 +111,7 @@ function initTabBar () { ...@@ -112,7 +111,7 @@ function initTabBar () {
__uniConfig.__ready__ = true __uniConfig.__ready__ = true
const onLaunchWebviewReady = function onLaunchWebviewReady () { const onLaunchWebviewReady = function onLaunchWebviewReady () {
const tabBarView = tabBar.init(__uniConfig.tabBar, (item, index) => { tabBar.init(__uniConfig.tabBar, (item, index) => {
UniServiceJSBridge.emit('onTabItemTap', { UniServiceJSBridge.emit('onTabItemTap', {
index, index,
text: item.text, text: item.text,
...@@ -121,16 +120,11 @@ function initTabBar () { ...@@ -121,16 +120,11 @@ function initTabBar () {
uni.switchTab({ uni.switchTab({
url: '/' + item.pagePath, url: '/' + item.pagePath,
openType: 'switchTab', openType: 'switchTab',
from: 'tabbar' from: 'tabBar'
}) })
}) })
tabBarView && plus.webview.getLaunchWebview().append(tabBarView)
}
if (plus.webview.getLaunchWebview()) {
onLaunchWebviewReady()
} else {
registerPlusMessage('UniWebviewReady-' + plus.runtime.appid, onLaunchWebviewReady, false)
} }
onLaunchWebviewReady()
} }
export function registerApp (appVm) { export function registerApp (appVm) {
...@@ -152,4 +146,4 @@ export function registerApp (appVm) { ...@@ -152,4 +146,4 @@ export function registerApp (appVm) {
initGlobalListeners() initGlobalListeners()
initTabBar() initTabBar()
} }
...@@ -3,6 +3,8 @@ import { ...@@ -3,6 +3,8 @@ import {
createWebview createWebview
} from './webview/index' } from './webview/index'
import tabBar from '../framework/tab-bar'
const pages = [] const pages = []
export function getCurrentPages (returnAll) { export function getCurrentPages (returnAll) {
...@@ -25,6 +27,8 @@ export function registerPage ({ ...@@ -25,6 +27,8 @@ export function registerPage ({
if (openType === 'reLaunch' || pages.length === 0) { if (openType === 'reLaunch' || pages.length === 0) {
// pages.length===0 表示首页触发 redirectTo // pages.length===0 表示首页触发 redirectTo
routeOptions.meta.isQuit = true routeOptions.meta.isQuit = true
} else if (!routeOptions.meta.isTabBar) {
routeOptions.meta.isQuit = false
} }
if (!webview) { if (!webview) {
...@@ -38,8 +42,7 @@ export function registerPage ({ ...@@ -38,8 +42,7 @@ export function registerPage ({
} }
if (routeOptions.meta.isTabBar && webview.id !== '1') { if (routeOptions.meta.isTabBar && webview.id !== '1') {
const launchWebview = plus.webview.getLaunchWebview() tabBar.append(webview)
launchWebview && launchWebview.append(webview)
} }
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
......
import { import {
TABBAR_HEIGHT getRealPath
} from '../constants'
import {
getRealPath,
isTabBarPage
} from '../api/util' } from '../api/util'
import safeArea from './safe-area' import {
requireNativePlugin
const IMAGE_TOP = 7 } from '../bridge'
const IMAGE_WIDTH = 24
const IMAGE_HEIGHT = 24
const TEXT_TOP = 36
const TEXT_SIZE = 12
const TEXT_NOICON_SIZE = 17
const TEXT_HEIGHT = 12
const IMAGE_ID = 'TABITEM_IMAGE_'
const TABBAR_VIEW_ID = 'TABBAR_VIEW_'
let view
let config let config
let winWidth
let itemWidth
let itemLength
let itemImageLeft
let itemRects = []
const itemIcons = []
const itemLayouts = []
const itemDot = []
const itemBadge = []
let itemClickCallback
let selected = 0
/** /**
* tabbar显示状态 * tabbar显示状态
*/ */
let visible = true let visible = true
const init = function () { let tabBar
const list = config.list
itemLength = config.list.length
calcItemLayout(list) // 计算选项卡布局
initBitmaps(list) // 初始化选项卡图标
initView()
}
let initView = function () {
const viewStyles = {
height: TABBAR_HEIGHT
}
if (config.position === 'top') {
viewStyles.top = 0
} else {
viewStyles.bottom = 0
viewStyles.height += safeArea.bottom
}
view = new plus.nativeObj.View(TABBAR_VIEW_ID, viewStyles, getDraws())
view.interceptTouchEvent(true)
view.addEventListener('click', (e) => {
if (!__uniConfig.__ready__) { // 未 ready,不允许点击
return
}
const x = e.clientX
const y = e.clientY
for (let i = 0; i < itemRects.length; i++) {
if (isCross(x, y, itemRects[i])) {
const draws = getSelectedDraws(i)
const drawTab = !!draws.length
itemClickCallback && itemClickCallback(config.list[i], i, drawTab)
if (drawTab) {
setTimeout(() => view.draw(draws))
}
break
}
}
})
plus.globalEvent.addEventListener('orientationchange', () => {
calcItemLayout(config.list)
if (config.position !== 'top') {
let height = TABBAR_HEIGHT + safeArea.bottom
view.setStyle({
height: height
})
if (visible) {
setWebviewPosition(height)
}
}
view.draw(getDraws())
})
if (!visible) {
view.hide()
}
}
let isCross = function (x, y, rect) {
if (x > rect.left && x < (rect.left + rect.width) && y > rect.top && y < (rect.top + rect.height)) {
return true
}
return false
}
let initBitmaps = function (list) {
for (let i = 0; i < list.length; i++) {
const item = list[i]
if (item.iconData) {
const bitmap = new plus.nativeObj.Bitmap(IMAGE_ID + i)
bitmap.loadBase64Data(item.iconData)
const selectedBitmap = new plus.nativeObj.Bitmap(`${IMAGE_ID}SELECTED_${i}`)
selectedBitmap.loadBase64Data(item.selectedIconData)
itemIcons[i] = {
icon: bitmap,
selectedIcon: selectedBitmap
}
} else if (item.iconPath) {
itemIcons[i] = {
icon: item.iconPath,
selectedIcon: item.selectedIconPath
}
} else {
itemIcons[i] = {
icon: false,
selectedIcon: false
}
}
}
}
let getDraws = function () {
const backgroundColor = config.backgroundColor
const borderHeight = Math.max(0.5, 1 / plus.screen.scale) // 高分屏最少0.5
const borderTop = config.position === 'top' ? (TABBAR_HEIGHT - borderHeight) : 0
const borderStyle = config.borderStyle === 'white' ? 'rgba(255,255,255,0.33)' : 'rgba(0,0,0,0.33)'
const draws = [{
id: `${TABBAR_VIEW_ID}BG`, // 背景色
tag: 'rect',
color: backgroundColor,
position: {
top: 0,
left: 0,
width: '100%',
height: TABBAR_HEIGHT + safeArea.bottom
}
}, {
id: `${TABBAR_VIEW_ID}BORDER`,
tag: 'rect',
color: borderStyle,
position: {
top: borderTop,
left: 0,
width: '100%',
height: `${borderHeight}px`
}
}]
for (let i = 0; i < itemLength; i++) {
const item = config.list[i]
if (i === selected) {
drawTabItem(draws, i, item.text, config.selectedColor, itemIcons[i].selectedIcon)
} else {
drawTabItem(draws, i, item.text, config.color, itemIcons[i].icon)
}
}
return draws
}
let getSelectedDraws = function (newSelected) {
if (selected === newSelected) {
return false
}
const draws = []
const lastSelected = selected
selected = newSelected
drawTabItem(draws, lastSelected)
drawTabItem(draws, selected)
return draws
}
/**
* 获取文字宽度(全角为1)
* @param {*} text
*/
function getFontWidth (text) {
// eslint-disable-next-line
return text.length - (text.match(/[\u0000-\u00ff]/g) || []).length / 2
}
let calcItemLayout = function () {
winWidth = plus.screen.resolutionWidth
itemWidth = winWidth / itemLength
itemImageLeft = (itemWidth - IMAGE_WIDTH) / 2
itemRects = []
let dotTop = 0
let dotLeft = 0
for (let i = 0; i < itemLength; i++) {
itemRects.push({
top: 0,
left: i * itemWidth,
width: itemWidth,
height: TABBAR_HEIGHT
})
}
for (let i = 0; i < itemLength; i++) {
const item = config.list[i]
let imgLeft = itemWidth * i + itemImageLeft
if ((item.iconData || item.iconPath) && item.text) { // 图文
itemLayouts[i] = {
text: {
top: TEXT_TOP,
left: `${i * itemWidth}px`,
width: `${itemWidth}px`,
height: TEXT_HEIGHT
},
img: {
top: IMAGE_TOP,
left: `${imgLeft}px`,
width: IMAGE_WIDTH,
height: IMAGE_HEIGHT
}
}
dotTop = IMAGE_TOP
dotLeft = imgLeft + IMAGE_WIDTH
} else if (!(item.iconData || item.iconPath) && item.text) { // 仅文字
let textLeft = i * itemWidth
itemLayouts[i] = {
text: {
top: 0,
left: `${textLeft}px`,
width: `${itemWidth}px`,
height: TABBAR_HEIGHT
}
}
dotTop = (44 - TEXT_NOICON_SIZE) / 2
dotLeft = textLeft + itemWidth * 0.5 + getFontWidth(item.text) * TEXT_NOICON_SIZE * 0.5
} else if ((item.iconData || item.iconPath) && !item.text) { // 仅图片
const diff = 10
let imgTop = (TABBAR_HEIGHT - IMAGE_HEIGHT - diff) / 2
let imgLeft = itemWidth * i + itemImageLeft - diff / 2
itemLayouts[i] = {
img: {
top: `${imgTop}px`,
left: `${imgLeft}px`,
width: IMAGE_WIDTH + diff,
height: IMAGE_HEIGHT + diff
}
}
dotTop = imgTop
dotLeft = imgLeft + IMAGE_WIDTH + diff
}
let height = itemBadge[i] ? 14 : 10
let badge = itemBadge[i] || '0'
let font = getFontWidth(badge) - 0.5
font = font > 1 ? 1 : font
let width = height + font * 12
width = width < height ? height : width
let itemLayout = itemLayouts[i]
if (itemLayout) {
itemLayout.doc = {
top: dotTop,
left: `${dotLeft - width * 0.382}px`,
width: `${width}px`,
height: `${height}px`
}
itemLayout.badge = {
top: dotTop,
left: `${dotLeft - width * 0.382}px`,
width: `${width}px`,
height: `${height}px`
}
}
}
}
let drawTabItem = function (draws, index) {
const layout = itemLayouts[index]
const item = config.list[index]
let color = config.color
let icon = itemIcons[index].icon
let dot = itemDot[index]
let badge = itemBadge[index] || ''
if (index === selected) {
color = config.selectedColor
icon = itemIcons[index].selectedIcon
}
if (icon) {
draws.push({
id: `${TABBAR_VIEW_ID}ITEM_${index}_ICON`,
tag: 'img',
position: layout.img,
src: icon
})
}
if (item.text) {
draws.push({
id: `${TABBAR_VIEW_ID}ITEM_${index}_TEXT`,
tag: 'font',
position: layout.text,
text: item.text,
textStyles: {
size: icon ? TEXT_SIZE : `${TEXT_NOICON_SIZE}px`,
color
}
})
}
const hiddenPosition = {
letf: 0,
top: 0,
width: 0,
height: 0
}
// 绘制小红点(角标背景)
draws.push({
id: `${TABBAR_VIEW_ID}ITEM_${index}_DOT`,
tag: 'rect',
position: (dot || badge) ? layout.doc : hiddenPosition,
rectStyles: {
color: '#ff0000',
radius: badge ? '7px' : '5px'
}
})
// 绘制角标文本
draws.push({
id: `${TABBAR_VIEW_ID}ITEM_${index}_BADGE`,
tag: 'font',
position: badge ? layout.badge : hiddenPosition,
text: badge || ' ',
textStyles: {
align: 'center',
verticalAlign: 'middle',
color: badge ? '#ffffff' : 'rgba(0,0,0,0)',
overflow: 'ellipsis',
size: '10px'
}
})
}
/**
* {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "page/component/index.html",
"iconData": "",
"selectedIconData": "",
"text": "组件"
}, {
"pagePath": "page/API/index.html",
"iconData": "",
"selectedIconData": "",
"text": "接口"
}],
"position":"bottom"//bottom|top
}
*/
/** /**
* 设置角标 * 设置角标
...@@ -371,112 +22,96 @@ let drawTabItem = function (draws, index) { ...@@ -371,112 +22,96 @@ let drawTabItem = function (draws, index) {
* @param {string} text * @param {string} text
*/ */
function setTabBarBadge (type, index, text) { function setTabBarBadge (type, index, text) {
if (!tabBar) {
return
}
if (type === 'none') { if (type === 'none') {
itemDot[index] = false tabBar.hideTabBarRedDot({
itemBadge[index] = '' index
})
tabBar.removeTabBarBadge({
index
})
} else if (type === 'text') { } else if (type === 'text') {
itemBadge[index] = text tabBar.setTabBarBadge({
index,
text
})
} else if (type === 'redDot') { } else if (type === 'redDot') {
itemDot[index] = true tabBar.showTabBarRedDot({
} index
if (view) { })
calcItemLayout(config.list)
view.draw(getDraws())
} }
} }
/** /**
* 动态设置 tabBar 某一项的内容 * 动态设置 tabBar 某一项的内容
*/ */
function setTabBarItem (index, text, iconPath, selectedIconPath) { function setTabBarItem (index, text, iconPath, selectedIconPath) {
if (iconPath || selectedIconPath) { const item = {}
let itemIcon = itemIcons[index] = itemIcons[index] || {} if (iconPath) {
if (iconPath) { item.iconPath = getRealPath(iconPath)
itemIcon.icon = getRealPath(iconPath)
}
if (selectedIconPath) {
itemIcon.selectedIcon = getRealPath(selectedIconPath)
}
} }
if (text) { if (selectedIconPath) {
config.list[index].text = text item.selectedIconPath = getRealPath(selectedIconPath)
} }
view.draw(getDraws()) tabBar && tabBar.setTabBarItem(Object.assign({
index,
text
}, item))
} }
/** /**
* 动态设置 tabBar 的整体样式 * 动态设置 tabBar 的整体样式
* @param {Object} style 样式 * @param {Object} style 样式
*/ */
function setTabBarStyle (style) { function setTabBarStyle (style) {
for (const key in style) { tabBar && tabBar.setTabBarStyle(style)
config[key] = style[key]
}
view.draw(getDraws())
}
/**
* 设置tab页底部或顶部距离
* @param {*} value 距离
*/
function setWebviewPosition (value) {
const position = config.position === 'top' ? 'top' : 'bottom'
plus.webview.all().forEach(webview => {
if (isTabBarPage(String(webview.__uniapp_route))) {
webview.setStyle({
[position]: value
})
}
})
} }
/** /**
* 隐藏 tabBar * 隐藏 tabBar
* @param {boolean} animation 是否需要动画效果 暂未支持 * @param {boolean} animation 是否需要动画效果 暂未支持
*/ */
function hideTabBar (animation) { function hideTabBar (animation) {
if (visible === false) {
return
}
visible = false visible = false
if (view) { tabBar && tabBar.hideTabBar({
view.hide() animation
setWebviewPosition(0) })
}
} }
/** /**
* 显示 tabBar * 显示 tabBar
* @param {boolean} animation 是否需要动画效果 暂未支持 * @param {boolean} animation 是否需要动画效果 暂未支持
*/ */
function showTabBar (animation) { function showTabBar (animation) {
if (visible === true) {
return
}
visible = true visible = true
if (view) { tabBar && tabBar.showTabBar({
view.show() animation
setWebviewPosition(TABBAR_HEIGHT + safeArea.bottom) })
}
} }
export default { export default {
init (options, clickCallback) { init (options, clickCallback) {
if (options && options.list.length) { if (options && options.list.length) {
selected = options.selected || 0
config = options config = options
config.position = 'bottom' // 暂时强制使用bottom
itemClickCallback = clickCallback
init()
return view
} }
try {
tabBar = requireNativePlugin('uni-tabview')
} catch (error) {
console.log(`uni.requireNativePlugin("uni-tabview") error ${error}`)
}
tabBar && tabBar.onClick(({ index }) => {
clickCallback(config.list[index], index, true)
})
}, },
switchTab (page) { switchTab (page) {
const itemLength = config.list.length
if (itemLength) { if (itemLength) {
for (let i = 0; i < itemLength; i++) { for (let i = 0; i < itemLength; i++) {
if ( if (
config.list[i].pagePath === page || config.list[i].pagePath === page ||
config.list[i].pagePath === `${page}.html` config.list[i].pagePath === `${page}.html`
) { ) {
const draws = getSelectedDraws(i) tabBar && tabBar.switchSelect({
if (draws.length) { index: i
view.draw(draws) })
}
return true return true
} }
} }
...@@ -488,7 +123,19 @@ export default { ...@@ -488,7 +123,19 @@ export default {
setTabBarStyle, setTabBarStyle,
hideTabBar, hideTabBar,
showTabBar, showTabBar,
append (webview) {
tabBar && tabBar.append({
id: webview.id
}, ({ code }) => {
if (code !== 0) {
console.log('tab append error')
setTimeout(() => {
this.append(webview)
}, 100)
}
})
},
get visible () { get visible () {
return visible return visible
} }
} }
...@@ -6,10 +6,6 @@ import { ...@@ -6,10 +6,6 @@ import {
parsePullToRefresh parsePullToRefresh
} from './pull-to-refresh-parser' } from './pull-to-refresh-parser'
import {
TABBAR_HEIGHT
} from '../../../constants'
const WEBVIEW_STYLE_BLACKLIST = [ const WEBVIEW_STYLE_BLACKLIST = [
'navigationBarBackgroundColor', 'navigationBarBackgroundColor',
'navigationBarTextStyle', 'navigationBarTextStyle',
...@@ -73,10 +69,5 @@ export function parseWebviewStyle (id, path, routeOptions = {}) { ...@@ -73,10 +69,5 @@ export function parseWebviewStyle (id, path, routeOptions = {}) {
} }
} }
if (routeOptions.meta.isTabBar) {
webviewStyle.top = 0
webviewStyle.bottom = TABBAR_HEIGHT
}
return webviewStyle return webviewStyle
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册