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

feat: app 端使用原生 tabBar

上级 14c86359
......@@ -331,6 +331,10 @@ module.exports = function (pagesJson, userManifestJson) {
if (conditionPagePath && isNVueEntryPage) {
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) {
appJson.entryPagePath = appJson.nvue.entryPagePath
// networkTimeout
......@@ -346,51 +350,43 @@ module.exports = function (pagesJson, userManifestJson) {
})
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
if (pagesJson.tabBar && pagesJson.tabBar.list && pagesJson.tabBar.list.length) {
manifestJson.launch_path = '__uniapptabbar.html'
manifestJson.plus.secondwebview = {
id: '1',
mode: 'child',
top: 0,
bottom: 56,
uniNView: {
path: appJson.pages[0]
}
const tabBar = manifestJson.plus.tabBar = Object.assign({}, pagesJson.tabBar)
tabBar.height = '56px'
// 首页是 tabBar 页面
const item = tabBar.list.find(page => page.pagePath === appJson.pages[0])
if (item) {
tabBar.child = ['lauchwebview']
tabBar.selected = tabBar.list.indexOf(item)
}
} 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) {
manifestJson.launch_path = '__uniapptabbar.html'
manifestJson.plus.secondwebview = {
id: '1'
}
manifestJson.plus.secondwebview.launch_path = '__uniappview.html'
if (!manifestJson.plus.secondwebview.kernel) {
manifestJson.plus.secondwebview.kernel = 'WKWebview'
}
// 首页是 tabBar 页面
if (pagesJson.tabBar.list.find(page => page.pagePath === entryPagePath)) {
manifestJson.plus.secondwebview.mode = 'child'
manifestJson.plus.secondwebview.top = 0
manifestJson.plus.secondwebview.bottom = 56
const tabBar = manifestJson.plus.tabBar = Object.assign({}, pagesJson.tabBar)
tabBar.height = '56px'
if (isNVueEntryPage) {
manifestJson.plus.launchwebview.id = '2'
} else {
// 首页是 tabBar 页面
const item = tabBar.list.find(page => page.pagePath === entryPagePath)
if (item) {
tabBar.child = ['lauchwebview']
tabBar.selected = tabBar.list.indexOf(item)
}
}
} 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) {
return [manifest, parseConfig(appJson)]
}
return [app, manifest]
}
}
......@@ -31,11 +31,14 @@ export function switchTab ({
}
})
currentPage.$remove()
if (currentPage.$page.openType === 'redirect') {
currentPage.$getAppWebview().close(ANI_CLOSE, ANI_DURATION)
} else {
currentPage.$getAppWebview().close('auto')
}
// 延迟执行避免iOS应用退出
setTimeout(() => {
if (currentPage.$page.openType === 'redirect') {
currentPage.$getAppWebview().close(ANI_CLOSE, ANI_DURATION)
} else {
currentPage.$getAppWebview().close('auto')
}
}, 100)
} else {
// 前一个 tabBar 触发 onHide
currentPage.$vm.__call_hook('onHide')
......@@ -64,9 +67,9 @@ export function switchTab ({
path,
query: {},
openType: 'switchTab'
})
}), 'none', 0
)
}
setStatusBarStyle()
}
}
......@@ -9,7 +9,6 @@ import {
} from './page'
import {
registerPlusMessage,
consumePlusMessage
} from './plus-message'
......@@ -112,7 +111,7 @@ function initTabBar () {
__uniConfig.__ready__ = true
const onLaunchWebviewReady = function onLaunchWebviewReady () {
const tabBarView = tabBar.init(__uniConfig.tabBar, (item, index) => {
tabBar.init(__uniConfig.tabBar, (item, index) => {
UniServiceJSBridge.emit('onTabItemTap', {
index,
text: item.text,
......@@ -121,16 +120,11 @@ function initTabBar () {
uni.switchTab({
url: '/' + item.pagePath,
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) {
......@@ -152,4 +146,4 @@ export function registerApp (appVm) {
initGlobalListeners()
initTabBar()
}
}
......@@ -3,6 +3,8 @@ import {
createWebview
} from './webview/index'
import tabBar from '../framework/tab-bar'
const pages = []
export function getCurrentPages (returnAll) {
......@@ -25,6 +27,8 @@ export function registerPage ({
if (openType === 'reLaunch' || pages.length === 0) {
// pages.length===0 表示首页触发 redirectTo
routeOptions.meta.isQuit = true
} else if (!routeOptions.meta.isTabBar) {
routeOptions.meta.isQuit = false
}
if (!webview) {
......@@ -38,8 +42,7 @@ export function registerPage ({
}
if (routeOptions.meta.isTabBar && webview.id !== '1') {
const launchWebview = plus.webview.getLaunchWebview()
launchWebview && launchWebview.append(webview)
tabBar.append(webview)
}
if (process.env.NODE_ENV !== 'production') {
......
import {
TABBAR_HEIGHT
} from '../constants'
import {
getRealPath,
isTabBarPage
getRealPath
} from '../api/util'
import safeArea from './safe-area'
const IMAGE_TOP = 7
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_'
import {
requireNativePlugin
} from '../bridge'
let view
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显示状态
*/
let visible = true
const init = function () {
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
}
*/
let tabBar
/**
* 设置角标
......@@ -371,112 +22,96 @@ let drawTabItem = function (draws, index) {
* @param {string} text
*/
function setTabBarBadge (type, index, text) {
if (!tabBar) {
return
}
if (type === 'none') {
itemDot[index] = false
itemBadge[index] = ''
tabBar.hideTabBarRedDot({
index
})
tabBar.removeTabBarBadge({
index
})
} else if (type === 'text') {
itemBadge[index] = text
tabBar.setTabBarBadge({
index,
text
})
} else if (type === 'redDot') {
itemDot[index] = true
}
if (view) {
calcItemLayout(config.list)
view.draw(getDraws())
tabBar.showTabBarRedDot({
index
})
}
}
/**
* 动态设置 tabBar 某一项的内容
*/
function setTabBarItem (index, text, iconPath, selectedIconPath) {
if (iconPath || selectedIconPath) {
let itemIcon = itemIcons[index] = itemIcons[index] || {}
if (iconPath) {
itemIcon.icon = getRealPath(iconPath)
}
if (selectedIconPath) {
itemIcon.selectedIcon = getRealPath(selectedIconPath)
}
const item = {}
if (iconPath) {
item.iconPath = getRealPath(iconPath)
}
if (text) {
config.list[index].text = text
if (selectedIconPath) {
item.selectedIconPath = getRealPath(selectedIconPath)
}
view.draw(getDraws())
tabBar && tabBar.setTabBarItem(Object.assign({
index,
text
}, item))
}
/**
* 动态设置 tabBar 的整体样式
* @param {Object} style 样式
*/
function setTabBarStyle (style) {
for (const key in 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.setTabBarStyle(style)
}
/**
* 隐藏 tabBar
* @param {boolean} animation 是否需要动画效果 暂未支持
*/
function hideTabBar (animation) {
if (visible === false) {
return
}
visible = false
if (view) {
view.hide()
setWebviewPosition(0)
}
tabBar && tabBar.hideTabBar({
animation
})
}
/**
* 显示 tabBar
* @param {boolean} animation 是否需要动画效果 暂未支持
*/
function showTabBar (animation) {
if (visible === true) {
return
}
visible = true
if (view) {
view.show()
setWebviewPosition(TABBAR_HEIGHT + safeArea.bottom)
}
tabBar && tabBar.showTabBar({
animation
})
}
export default {
init (options, clickCallback) {
if (options && options.list.length) {
selected = options.selected || 0
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) {
const itemLength = config.list.length
if (itemLength) {
for (let i = 0; i < itemLength; i++) {
if (
config.list[i].pagePath === page ||
config.list[i].pagePath === `${page}.html`
) {
const draws = getSelectedDraws(i)
if (draws.length) {
view.draw(draws)
}
tabBar && tabBar.switchSelect({
index: i
})
return true
}
}
......@@ -488,7 +123,19 @@ export default {
setTabBarStyle,
hideTabBar,
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 () {
return visible
}
}
}
......@@ -6,10 +6,6 @@ import {
parsePullToRefresh
} from './pull-to-refresh-parser'
import {
TABBAR_HEIGHT
} from '../../../constants'
const WEBVIEW_STYLE_BLACKLIST = [
'navigationBarBackgroundColor',
'navigationBarTextStyle',
......@@ -73,10 +69,5 @@ export function parseWebviewStyle (id, path, routeOptions = {}) {
}
}
if (routeOptions.meta.isTabBar) {
webviewStyle.top = 0
webviewStyle.bottom = TABBAR_HEIGHT
}
return webviewStyle
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册