提交 802a8021 编写于 作者: fxy060608's avatar fxy060608

feat: tabBar

上级 ef4f45a7
......@@ -22,6 +22,7 @@ declare var __UNI_FEATURE_ROUTER_MODE__: 'hash' | 'history'
declare var __UNI_FEATURE_PAGES__: boolean
declare var __UNI_FEATURE_TABBAR__: boolean
declare var __UNI_FEATURE_TABBAR_MIDBUTTON__: boolean
declare var __UNI_FEATURE_TOPWINDOW__: boolean
declare var __UNI_FEATURE_LEFTWINDOW__: boolean
declare var __UNI_FEATURE_RIGHTWINDOW__: boolean
......
......@@ -40,6 +40,7 @@ declare namespace UniApp {
request: number
uploadFile: number
}
tabBar?: TabBarOptions
}
interface UniRoute {
......@@ -162,14 +163,51 @@ declare namespace UniApp {
subpackages?: PagesJsonSubpackagesOptions[]
subPackages?: PagesJsonSubpackagesOptions[]
globalStyle: PagesJsonPageStyle
tabBar?: {
list: []
}
tabBar?: TabBarOptions
topWindow?: PagesJsonWindowOptions
leftWindow?: PagesJsonWindowOptions
rightWindow?: PagesJsonWindowOptions
}
interface TabBarItemBaseOptions {
pagePath: string
text: string
iconPath?: string
selectedIconPath?: string
redDot?: boolean
badge?: string
}
interface TabBarNormalItemOptions extends TabBarItemBaseOptions {
type: 'normal'
}
interface TabBarMidButtonOptions extends TabBarItemBaseOptions {
type: 'midButton'
width?: string
height?: string
iconWidth?: string
backgroundImage?: string
}
type TabBarItemOptions = TabBarNormalItemOptions | TabBarMidButtonOptions
interface TabBarOptions {
position?: 'bottom' | 'top'
color: string
selectedColor: string
backgroundColor: string
borderStyle?: 'black' | 'white'
list: TabBarItemOptions[]
blurEffect?: 'none' | 'dark' | 'extralight' | 'light'
fontSize?: string
iconWidth?: string
spacing?: string
height?: string
midButton?: TabBarMidButtonOptions
selectedIndex?: number
shown: boolean
}
type OnApiLike = (callback: (result: unknown) => void) => void
interface UniServiceJSBridge {
/**
......@@ -206,7 +244,7 @@ declare namespace UniApp {
*/
invokeOnCallback<T extends OnApiLike>(
name: string,
res: Parameters<Parameters<T>[0]>[0]
res?: Parameters<Parameters<T>[0]>[0]
): void
/**
* 订阅 View 的自定义事件,回调函数会接收所有传入事件触发函数的额外参数。
......
import { extend, isPlainObject } from '@vue/shared'
import { extend, isString, isPlainObject } from '@vue/shared'
import { ApiOptions, ApiProtocols } from '../../protocols/type'
import { API_TYPE_ON_PROTOCOLS, validateProtocols } from '../protocol'
import {
......@@ -130,7 +130,13 @@ function wrapperApi(
return function (...args: any[]) {
if (__DEV__) {
const errMsg = validateProtocols(name!, args, protocol)
if (errMsg) {
if (isString(errMsg)) {
return errMsg
}
}
if (options && options.beforeInvoke) {
const errMsg = options.beforeInvoke(args)
if (isString(errMsg)) {
return errMsg
}
}
......
......@@ -2,6 +2,7 @@ import {
hasOwn,
makeMap,
isArray,
isString,
isObject,
toRawType,
capitalize,
......@@ -69,7 +70,7 @@ function validateProtocol(
protocol[key],
!hasOwn(data, key)
)
if (errMsg) {
if (isString(errMsg)) {
return validateProtocolFail(name, errMsg)
}
}
......@@ -133,10 +134,8 @@ function validateProp(
}
}
// custom validator
if (validator && !validator(value)) {
return (
'Invalid args: custom validator check failed for args "' + name + '".'
)
if (validator) {
return validator(value)
}
}
......
export function getCurrentPageVm() {
const pages = getCurrentPages()
const len = pages.length
const page = pages[len - 1]
return page && (page as any).$vm
}
......@@ -6,6 +6,7 @@ export * from './service/context/createVideoContext'
export * from './service/ui/createIntersectionObserver'
export * from './service/ui/createSelectorQuery'
export * from './service/ui/tabBar'
// protocols
export * from './protocols/base/canIUse'
......@@ -27,6 +28,7 @@ export * from './protocols/network/request'
export * from './protocols/route/route'
export * from './protocols/ui/tabBar'
// helpers
export {
defineOnApi,
......@@ -36,8 +38,6 @@ export {
defineAsyncApi,
} from './helpers/api'
export { getCurrentPageVm } from './helpers/utils'
export { handlePromise } from './helpers/api/promise'
export { invokeApi, wrapperReturnValue } from './helpers/interceptor'
export { requestComponentObserver } from './helpers/requestComponentObserver'
......@@ -10,17 +10,18 @@ type ProtocolMethod<T, TConstructor = any> = T extends (...args: any) => any
: never
type ProtocolType<T> = ProtocolConstructor<T> | ProtocolConstructor<T>[]
type Validator = (value: any, params: Record<string, any>) => void
type ApiArgsValidator = (value: any, params: Record<string, any>) => void
export interface ApiProtocol {
[name: string]: ProtocolOptions
}
export type ApiProtocols = ApiProtocol | ProtocolOptions[]
export interface ApiOptions {
beforeInvoke?: (args: unknown) => boolean | void | string
beforeAll?: (res: unknown) => void
beforeSuccess?: (res: unknown) => void
formatArgs?: {
[name: string]: Validator
[name: string]: ApiArgsValidator
}
}
......
import { getLen } from 'uni-shared'
import getRealPath from 'uni-platform/helpers/get-real-path'
const indexValidator = {
type: Number,
required: true
}
export const setTabBarItem = {
index: indexValidator,
text: {
type: String
},
iconPath: {
type: String
},
selectedIconPath: {
type: String
},
pagePath: {
type: String
}
}
export const setTabBarStyle = {
color: {
type: String
},
selectedColor: {
type: String
},
backgroundColor: {
type: String
},
backgroundImage: {
type: String,
validator(backgroundImage, params) {
if (
backgroundImage &&
!/^(linear|radial)-gradient\(.+?\);?$/.test(backgroundImage)
) {
params.backgroundImage = getRealPath(backgroundImage)
}
}
},
backgroundRepeat: {
type: String
},
borderStyle: {
type: String,
validator(borderStyle, params) {
if (borderStyle) {
params.borderStyle = borderStyle === 'black' ? 'black' : 'white'
}
}
}
}
export const hideTabBar = {
animation: {
type: Boolean,
default: false
}
}
export const showTabBar = {
animation: {
type: Boolean,
default: false
}
}
export const hideTabBarRedDot = {
index: indexValidator
}
export const showTabBarRedDot = {
index: indexValidator
}
export const removeTabBarBadge = {
index: indexValidator
}
export const setTabBarBadge = {
index: indexValidator,
text: {
type: String,
required: true,
validator(text, params) {
if (getLen(text) >= 4) {
params.text = '...'
}
}
}
}
import { extend } from '@vue/shared'
import { getLen } from '@dcloudio/uni-shared'
import { getRealPath } from '@dcloudio/uni-platform'
import { getCurrentPageMeta } from '@dcloudio/uni-core'
import { ApiOptions, ApiProtocol } from '../type'
const IndexProtocol: ApiProtocol = {
index: {
type: Number,
required: true,
},
}
const IndexOptions: ApiOptions = {
beforeInvoke() {
const pageMeta = getCurrentPageMeta()
if (pageMeta && !pageMeta.isTabBar) {
return 'not TabBar page'
}
},
formatArgs: {
index(value) {
if (!__uniConfig.tabBar!.list[value]) {
return 'tabbar item not found'
}
},
},
}
export const SetTabBarItemProtocol: ApiProtocol = extend(
{
text: {
type: String,
},
iconPath: {
type: String,
},
selectedIconPath: {
type: String,
},
pagePath: {
type: String,
},
},
IndexProtocol
)
export const SetTabBarItemOptions: ApiOptions = IndexOptions
export const SetTabBarStyleProtocol: ApiProtocol = {
color: {
type: String,
},
selectedColor: {
type: String,
},
backgroundColor: {
type: String,
},
backgroundImage: {
type: String,
},
backgroundRepeat: {
type: String,
},
borderStyle: {
type: String,
},
}
const GRADIENT_RE = /^(linear|radial)-gradient\(.+?\);?$/
export const SetTabBarStyleOptions: ApiOptions = {
formatArgs: {
backgroundImage(value, params) {
if (value && !GRADIENT_RE.test(value)) {
params.backgroundImage = getRealPath(value)
}
},
borderStyle(value, params) {
if (value) {
params.borderStyle = value === 'white' ? 'white' : 'black'
}
},
},
}
export const HideTabBarProtocol: ApiProtocol = {
animation: {
type: Boolean,
default: false,
},
}
export const ShowTabBarProtocol: ApiProtocol = HideTabBarProtocol
export const HideTabBarRedDotProtocol: ApiProtocol = IndexProtocol
export const HideTabBarRedDotOptions: ApiOptions = IndexOptions
export const ShowTabBarRedDotProtocol: ApiProtocol = IndexProtocol
export const ShowTabBarRedDotOptions: ApiOptions = IndexOptions
export const RemoveTabBarBadgeProtocol: ApiProtocol = IndexProtocol
export const RemoveTabBarBadgeOptions: ApiOptions = IndexOptions
export const SetTabBarBadgeProtocol: ApiProtocol = extend(
{
text: {
type: String,
required: true,
},
},
IndexProtocol
)
export const SetTabBarBadgeOptions: ApiOptions = {
formatArgs: extend(
{
text(value, params) {
if (getLen(value) >= 4) {
params.text = '...'
}
},
} as ApiOptions['formatArgs'],
IndexOptions.formatArgs
),
}
import { ComponentPublicInstance } from 'vue'
import { getCurrentPageVm } from '@dcloudio/uni-core'
import { operateVideoPlayer } from '@dcloudio/uni-platform'
import { getCurrentPageVm } from '../../helpers/utils'
const RATES = [0.5, 0.8, 1.0, 1.25, 1.5, 2.0]
......@@ -69,5 +69,5 @@ export function createVideoContext(
if (context) {
return new VideoContext(id, context)
}
return new VideoContext(id, getCurrentPageVm())
return new VideoContext(id, getCurrentPageVm()!)
}
import { ComponentPublicInstance } from 'vue'
import { extend, isFunction } from '@vue/shared'
import { getCurrentPageVm } from '@dcloudio/uni-core'
import {
addIntersectionObserver,
removeIntersectionObserver,
} from '@dcloudio/uni-platform'
import { defineSyncApi } from '../../helpers/api'
import { getCurrentPageVm } from '../../helpers/utils'
import { RequestComponentObserverOptions } from '../../helpers/requestComponentObserver'
export interface AddIntersectionObserverArgs {
......@@ -103,5 +103,5 @@ export const createIntersectionObserver = defineSyncApi<
if (context) {
return new ServiceIntersectionObserver(context, options)
}
return new ServiceIntersectionObserver(getCurrentPageVm(), options)
return new ServiceIntersectionObserver(getCurrentPageVm()!, options)
})
import { defineOnApi } from '../../helpers/api'
export type OnTabBarMidButtonTap = typeof uni.onTabBarMidButtonTap
export const API_ON_TAB_BAR_MID_BUTTON_TAP = 'onTabBarMidButtonTap'
export const onTabBarMidButtonTap = defineOnApi<OnTabBarMidButtonTap>(
API_ON_TAB_BAR_MID_BUTTON_TAP,
() => {
// noop
}
)
......@@ -17,7 +17,7 @@ interface UniFormFieldCtx {
export default defineComponent({
name: 'Form',
setup(props, { slots, emit }) {
setup(_props, { slots, emit }) {
provideForm(emit)
return () => (
<uni-form>
......
import { App } from 'vue'
import { App, ComponentPublicInstance } from 'vue'
import { initAppConfig } from './appConfig'
export function initService(app: App) {
initAppConfig(app._context.config)
}
export function getCurrentPage() {
const pages = getCurrentPages()
const len = pages.length
if (len) {
return pages[len - 1]
}
}
export function getCurrentPageMeta() {
const page = getCurrentPage()
if (page) {
return page.$page.meta
}
}
export function getCurrentPageVm() {
const page = getCurrentPage()
if (page) {
return (page as any).$vm as ComponentPublicInstance
}
}
export function invokeHook<T>(name: string, args?: T) {
const vm = getCurrentPageVm()
return vm && vm.$callHook(name, args)
}
此差异已折叠。
import {
watch,
computed,
ComputedRef,
withCtx,
KeepAlive,
openBlock,
......@@ -8,14 +11,16 @@ import {
resolveComponent,
ConcreteComponent,
resolveDynamicComponent,
SetupContext,
} from 'vue'
import { RouteLocationNormalizedLoaded, RouterView, useRoute } from 'vue-router'
import TabBar from '../tabBar'
import { RouterView, useRoute } from 'vue-router'
import { useTabBar } from '../../../plugin/state'
import { useKeepAliveRoute } from '../../../plugin/page'
import TabBar from '../tabBar'
type KeepAliveRoute = ReturnType<typeof useKeepAliveRoute>
export default defineComponent({
......@@ -24,14 +29,14 @@ export default defineComponent({
onChange: Function,
},
emits: ['change'],
setup() {
const route = (__UNI_FEATURE_TABBAR__ &&
useRoute()) as RouteLocationNormalizedLoaded
setup(props, { emit }) {
const keepAliveRoute = (__UNI_FEATURE_PAGES__ &&
useKeepAliveRoute()) as KeepAliveRoute
const topWindow = __UNI_FEATURE_TOPWINDOW__ && useTopWindow()
const leftWindow = __UNI_FEATURE_LEFTWINDOW__ && useLeftWindow()
const rightWindow = __UNI_FEATURE_RIGHTWINDOW__ && useRightWindow()
const showTabBar = (__UNI_FEATURE_TABBAR__ &&
useShowTabBar(emit)) as ComputedRef<boolean>
return () => {
const layoutTsx = createLayoutTsx(
keepAliveRoute,
......@@ -39,7 +44,7 @@ export default defineComponent({
leftWindow,
rightWindow
)
const tabBarTsx = __UNI_FEATURE_TABBAR__ && createTabBarTsx(route)
const tabBarTsx = __UNI_FEATURE_TABBAR__ && createTabBarTsx(showTabBar)
if (!tabBarTsx) {
return layoutTsx
}
......@@ -82,8 +87,19 @@ function createLayoutTsx(
)
}
function createTabBarTsx(route: RouteLocationNormalizedLoaded) {
return <TabBar v-show={route.meta.isTabBar} />
function useShowTabBar(emit: SetupContext['emit']) {
const route = useRoute()
const tabBar = useTabBar()!
// TODO meida query
const showTabBar = computed(() => route.meta.isTabBar && tabBar.shown)
watch(showTabBar, (value) => {
emit('change', 'showTabBar', value)
})
return showTabBar
}
function createTabBarTsx(showTabBar: ComputedRef<boolean>) {
return <TabBar v-show={showTabBar.value} />
}
function createPageVNode() {
......
import { defineComponent } from 'vue'
export default /*#__PURE__*/ defineComponent({
name: 'TabBar',
})
import { watch, computed, defineComponent, onRenderTriggered } from 'vue'
import { RouteLocationNormalizedLoaded, useRoute } from 'vue-router'
import { invokeHook } from '@dcloudio/uni-core'
import {
API_ON_TAB_BAR_MID_BUTTON_TAP,
OnTabBarMidButtonTap,
} from '@dcloudio/uni-api'
import { getRealPath } from '../../../platform'
import { useTabBar } from '../../plugin/state'
import { cssBackdropFilter } from '../../../service/api/base/canIUse'
export default /*#__PURE__*/ defineComponent({
name: 'TabBar',
setup() {
const tabBar = useTabBar()!
const onSwitchTab = useSwitchTab(useRoute(), tabBar)
const { style, borderStyle, placeholderStyle } = useTabBarStyle(tabBar)
onRenderTriggered(() => {
debugger
})
return () => {
const tabBarItemsTsx = createTabBarItemsTsx(tabBar, onSwitchTab)
return (
<uni-tabbar class={'uni-tabbar-' + tabBar.position}>
<div class="uni-tabbar" style={style.value}>
<div class="uni-tabbar-border" style={borderStyle.value}></div>
{tabBarItemsTsx}
</div>
<div class="uni-placeholder" style={placeholderStyle.value}></div>
</uni-tabbar>
)
}
},
})
function useSwitchTab(
route: RouteLocationNormalizedLoaded,
tabBar: UniApp.TabBarOptions
) {
watch(
route,
() => {
const meta = route.meta
if (meta.isTabBar) {
const pagePath = meta.route
const index = tabBar.list.findIndex(
(item) => item.pagePath === pagePath
)
if (index === -1) {
return
}
tabBar.selectedIndex = index
}
},
{ immediate: true }
)
return (tabBarItem: UniApp.TabBarItemOptions, index: number) => {
const { type } = tabBarItem
return () => {
if (__UNI_FEATURE_TABBAR_MIDBUTTON__ && type === 'midButton') {
return UniServiceJSBridge.invokeOnCallback<OnTabBarMidButtonTap>(
API_ON_TAB_BAR_MID_BUTTON_TAP
)
}
const { pagePath, text } = tabBarItem
let url = '/' + pagePath
if (url === __uniRoutes[0].alias) {
url = '/'
}
if (route.path !== url) {
uni.switchTab({ from: 'tabBar', url } as any)
} else {
invokeHook('onTabItemTap', {
index,
text,
pagePath,
})
}
}
}
}
type OnSwtichTab = ReturnType<typeof useSwitchTab>
const DEFAULT_BG_COLOR = '#f7f7fa'
const BLUR_EFFECT_COLOR_DARK = 'rgb(0, 0, 0, 0.8)'
const BLUR_EFFECT_COLOR_LIGHT = 'rgb(250, 250, 250, 0.8)'
const BLUR_EFFECT_COLORS = {
dark: BLUR_EFFECT_COLOR_DARK,
light: BLUR_EFFECT_COLOR_LIGHT,
extralight: BLUR_EFFECT_COLOR_LIGHT,
}
const BORDER_COLORS = {
white: 'rgba(255, 255, 255, 0.33)',
black: 'rgba(0, 0, 0, 0.33)',
}
function useTabBarStyle(tabBar: UniApp.TabBarOptions) {
const style = computed(() => {
let backgroundColor = tabBar.backgroundColor
const blurEffect = tabBar.blurEffect
if (!backgroundColor) {
if (cssBackdropFilter && blurEffect && blurEffect !== 'none') {
backgroundColor = BLUR_EFFECT_COLORS[blurEffect]
}
}
return {
backgroundColor: backgroundColor || DEFAULT_BG_COLOR,
backdropFilter: blurEffect !== 'none' ? 'blur(10px)' : blurEffect,
}
})
const borderStyle = computed(() => {
const { borderStyle } = tabBar
return {
backgroundColor: BORDER_COLORS[borderStyle!] || borderStyle,
}
})
const placeholderStyle = computed(() => {
return {
height: tabBar.height!,
}
})
return {
style,
borderStyle,
placeholderStyle,
}
}
function isMidButton(item: unknown): item is UniApp.TabBarMidButtonOptions {
return (item as any).type === 'midButton'
}
function createTabBarItemsTsx(
tabBar: UniApp.TabBarOptions,
onSwitchTab: OnSwtichTab
) {
const { list, selectedIndex, selectedColor, color } = tabBar
return list.map((item, index) => {
const selected = selectedIndex === index
const textColor = selected ? selectedColor : color
const iconPath =
(selected ? item.selectedIconPath || item.iconPath : item.iconPath) || ''
if (!__UNI_FEATURE_TABBAR_MIDBUTTON__) {
return createTabBarItemTsx(
textColor,
iconPath,
item,
tabBar,
index,
onSwitchTab
)
}
return isMidButton(item)
? createTabBarMidButtonTsx(
textColor,
iconPath,
item,
tabBar,
index,
onSwitchTab
)
: createTabBarItemTsx(
textColor,
iconPath,
item,
tabBar,
index,
onSwitchTab
)
})
}
function createTabBarItemTsx(
color: string,
iconPath: string,
tabBarItem: UniApp.TabBarItemOptions,
tabBar: UniApp.TabBarOptions,
index: number,
onSwitchTab: OnSwtichTab
) {
return (
<div
key={index}
class="uni-tabbar__item"
onClick={onSwitchTab(tabBarItem, index)}
>
{createTabBarItemBdTsx(color, iconPath || '', tabBarItem, tabBar)}
</div>
)
}
function createTabBarItemBdTsx(
color: string,
iconPath: string,
tabBarItem: UniApp.TabBarItemOptions,
tabBar: UniApp.TabBarOptions
) {
const { height } = tabBar
return (
<div class="uni-tabbar__bd" style={{ height: height }}>
{iconPath && createTabBarItemIconTsx(iconPath, tabBarItem, tabBar)}
{tabBarItem.text && createTabBarItemTextTsx(color, tabBarItem, tabBar)}
</div>
)
}
function createTabBarItemIconTsx(
iconPath: string,
tabBarItem: UniApp.TabBarItemOptions,
tabBar: UniApp.TabBarOptions
) {
const { type, text, redDot } = tabBarItem
const { iconWidth } = tabBar
const clazz = 'uni-tabbar__icon' + (text ? ' uni-tabbar__icon__diff' : '')
const style = { width: iconWidth, height: iconWidth }
return (
<div class={clazz} style={style}>
{type !== 'midButton' && <img src={getRealPath(iconPath)} />}
{redDot && createTabBarItemRedDotTsx(tabBarItem.badge)}
</div>
)
}
function createTabBarItemTextTsx(
color: string,
tabBarItem: UniApp.TabBarItemOptions,
tabBar: UniApp.TabBarOptions
) {
const { redDot, iconPath, text } = tabBarItem
const { fontSize, spacing } = tabBar
const style = {
color,
fontSize: fontSize,
lineHeight: !iconPath ? 1.8 : 'normal',
marginTop: !iconPath ? 'inherit' : spacing,
}
return (
<div class="uni-tabbar__label" style={style}>
{text}
{redDot && !iconPath && createTabBarItemRedDotTsx(tabBarItem.badge)}
</div>
)
}
function createTabBarItemRedDotTsx(badge?: string) {
const clazz = 'uni-tabbar__reddot' + (badge ? ' uni-tabbar__badge' : '')
return <div class={clazz}>{badge}</div>
}
function createTabBarMidButtonTsx(
color: string,
iconPath: string,
midButton: UniApp.TabBarMidButtonOptions,
tabBar: UniApp.TabBarOptions,
index: number,
onSwitchTab: OnSwtichTab
) {
const { width, height, backgroundImage, iconWidth } = midButton
return (
<div
key={index}
class="uni-tabbar__item"
style={{ flex: '0 0 ' + width, position: 'relative' }}
onClick={onSwitchTab(midButton, index)}
>
<div
class="uni-tabbar__mid"
style={{
width: width,
height: height,
backgroundImage: backgroundImage
? "url('" + getRealPath(backgroundImage) + "')"
: 'none',
}}
>
{iconPath && (
<img
style={{ width: iconWidth, height: iconWidth }}
src={getRealPath(iconPath)}
/>
)}
</div>
{createTabBarItemBdTsx(color, iconPath, midButton, tabBar)}
</div>
)
}
<template>
<uni-tabbar :class="['uni-tabbar-'+position]">
<div
:style="{
backgroundColor:tabbarBackgroundColor,
'backdrop-filter':blurEffect !== 'none' ? 'blur(10px)' : blurEffect
}"
class="uni-tabbar"
>
<div
:style="{backgroundColor:borderColor}"
class="uni-tabbar-border"
/>
<div
v-for="(item,index) in list"
:key="item.isMidButton ? index : item.pagePath"
:style="item.isMidButton ? {flex:'0 0 ' + item.width,position:'relative'} : {}"
class="uni-tabbar__item"
@click="_switchTab(item,index)"
>
<!-- midButton iconPath -->
<div
v-if="item.isMidButton"
class="uni-tabbar__mid"
:style="_uniTabbarBdStyle(item)"
>
<img
v-if="item.iconPath"
:style="{width: item.iconWidth,height:item.iconWidth}"
:src="_getRealPath(item.iconPath)"
>
</div>
<!-- tabbar button -->
<div
class="uni-tabbar__bd"
:style="{height:height}"
>
<div
v-if="item.iconPath || item.isMidButton"
:class="{'uni-tabbar__icon__diff':!item.text}"
class="uni-tabbar__icon"
:style="{width: iconWidth,height:iconWidth}"
>
<img
v-if="!item.isMidButton"
:src="_getRealPath(selectedIndex===index?item.selectedIconPath:item.iconPath)"
>
<div
v-if="item.redDot"
:class="{'uni-tabbar__badge':!!item.badge}"
class="uni-tabbar__reddot"
>
{{ item.badge }}
</div>
</div>
<div
v-if="item.text"
:style="{
color:selectedIndex === index ? selectedColor : color,
fontSize: fontSize,
lineHeight: !item.iconPath ? 1.8 : 'normal',
marginTop: !item.iconPath ? 'inherit' : spacing
}"
class="uni-tabbar__label"
>
{{ item.text }}
<div
v-if="item.redDot&&!item.iconPath"
:class="{'uni-tabbar__badge':!!item.badge}"
class="uni-tabbar__reddot"
>
{{ item.badge }}
</div>
</div>
</div>
</div>
</div>
<div
class="uni-placeholder"
:style="{height:height}"
/>
</uni-tabbar>
</template>
<style>
uni-tabbar {
display: block;
box-sizing: border-box;
width: 100%;
z-index: 998;
}
uni-tabbar .uni-tabbar {
display: flex;
z-index: 998;
box-sizing: border-box;
}
uni-tabbar.uni-tabbar-top,
uni-tabbar.uni-tabbar-bottom,
uni-tabbar.uni-tabbar-top .uni-tabbar,
uni-tabbar.uni-tabbar-bottom .uni-tabbar {
position: fixed;
left: var(--window-left);
right: var(--window-right);
}
.uni-app--showlayout+uni-tabbar.uni-tabbar-top,
.uni-app--showlayout+uni-tabbar.uni-tabbar-bottom,
.uni-app--showlayout+uni-tabbar.uni-tabbar-top .uni-tabbar,
.uni-app--showlayout+uni-tabbar.uni-tabbar-bottom .uni-tabbar {
left: var(--window-margin);
right: var(--window-margin);
}
uni-tabbar.uni-tabbar-bottom .uni-tabbar {
bottom: 0;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
uni-tabbar .uni-tabbar~.uni-placeholder {
width: 100%;
margin-bottom: 0;
margin-bottom: constant(safe-area-inset-bottom);
margin-bottom: env(safe-area-inset-bottom);
}
uni-tabbar .uni-tabbar * {
box-sizing: border-box;
}
uni-tabbar .uni-tabbar__item {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
flex: 1;
font-size: 0;
text-align: center;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
uni-tabbar .uni-tabbar__bd {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
uni-tabbar .uni-tabbar__icon {
position: relative;
display: inline-block;
margin-top: 5px;
}
uni-tabbar .uni-tabbar__icon.uni-tabbar__icon__diff {
margin-top: 0px;
width: 34px;
height: 34px;
}
uni-tabbar .uni-tabbar__icon img {
width: 100%;
height: 100%;
}
uni-tabbar .uni-tabbar__label {
position: relative;
text-align: center;
font-size: 10px;
}
uni-tabbar .uni-tabbar-border {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
transform: scaleY(0.5);
}
uni-tabbar .uni-tabbar__reddot {
position: absolute;
top: 0;
right: 0;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #f43530;
color: #ffffff;
transform: translate(40%, -20%);
}
uni-tabbar .uni-tabbar__badge {
width: auto;
height: 16px;
line-height: 16px;
border-radius: 16px;
min-width: 16px;
padding: 0 2px;
font-size: 12px;
text-align: center;
white-space: nowrap;
}
uni-tabbar .uni-tabbar__mid {
display: flex;
justify-content: center;
position: absolute;
bottom: 0;
background-size: 100% 100%;
}
</style>
<script>
import getRealPath from 'uni-platform/helpers/get-real-path'
import { isPlainObject } from 'uni-shared'
import { publish } from 'uni-platform/service/bridge'
function cssSupports (css) {
return window.CSS && CSS.supports && (CSS.supports(css) || CSS.supports.apply(CSS, css.split(':')))
}
export default {
name: 'TabBar',
props: {
position: {
default: 'bottom',
validator (value) {
return ['bottom', 'top'].indexOf(value) !== -1
}
},
color: {
type: String,
default: '#999'
},
selectedColor: {
type: String,
default: '#007aff'
},
backgroundColor: {
type: String,
default: ''
},
borderStyle: {
type: String,
default: 'black'
},
list: {
type: Array,
default: function () {
return []
}
},
matchMedia: {
type: Object,
default: function () {
return {}
}
},
blurEffect: {
type: String,
default: 'none'
},
fontSize: {
type: String,
default: '10px'
},
iconWidth: {
type: String,
default: '24px'
},
spacing: {
type: String,
default: '3px'
},
height: {
type: String,
default: '50px'
},
midButton: {
type: Object,
default: null
}
},
data () {
return {
selectedIndex: 0
}
},
computed: {
tabbarBackgroundColor () {
// 背景色 区分 高斯模糊 分别返回
const DefaultBgColor = '#f7f7fa'
if (this.backgroundColor) return this.backgroundColor
if (cssSupports('backdrop-filter:blur(10px)') && this.blurEffect !== 'none') {
if (this.blurEffect === 'dark') {
return 'rgb(0, 0, 0, 0.8)'
}
if (['light', 'extralight'].includes(this.blurEffect)) {
return 'rgb(250, 250, 250, 0.8)'
}
}
return DefaultBgColor
},
borderColor () {
// 不再限制可配置颜色值
if (this.borderStyle === 'white') return 'rgba(255, 255, 255, 0.33)'
if (this.borderStyle === 'black') return 'rgba(0, 0, 0, 0.33)'
return this.borderStyle
}
},
watch: {
$route: {
immediate: true,
handler (to) {
if (to.meta.isTabBar) {
this.__path__ = to.path
const index = this.list.findIndex(item => to.meta.pagePath === item.pagePath)
if (index > -1) {
this.selectedIndex = index
}
}
}
}
},
created () {
this._initMidButton()
},
beforeCreate () {
this.__path__ = this.$route.path
},
methods: {
_getRealPath (filePath) {
const SCHEME_RE = /^([a-z-]+:)?\/\//i
const DATA_RE = /^data:.*,.*/
if (!(SCHEME_RE.test(filePath) || DATA_RE.test(filePath)) && filePath.indexOf('/') !== 0) {
filePath = '/' + filePath
}
return getRealPath(filePath)
},
_switchTab ({
text,
pagePath,
isMidButton = false
}, index) {
if (isMidButton) {
publish('onTabBarMidButtonTap')
return
}
this.selectedIndex = index
let url = '/' + pagePath
if (url === __uniRoutes[0].alias) {
url = '/'
}
const detail = {
index,
text,
pagePath
}
if (this.$route.path !== url) {
this.__path__ = this.$route.path
uni.switchTab({
from: 'tabBar',
url,
detail
})
} else {
UniServiceJSBridge.emit('onTabItemTap', detail)
}
},
_initMidButton () {
const listLength = this.list.length
// 偶数则添加midButton
if (listLength % 2 === 0 && isPlainObject(this.midButton)) {
// 给midButton赋值默认值
const DefaultMidButton = {
width: '50px',
height: '50px',
iconWidth: '24px'
}
for (const key in DefaultMidButton) {
this.midButton[key] = this.midButton[key] || DefaultMidButton[key]
}
this.list.splice(~~(listLength / 2), 0, Object.assign({}, this.midButton, { isMidButton: true }))
}
},
_uniTabbarBdStyle (item) {
return Object.assign({}, {
width: item.width,
height: item.height,
backgroundImage: item.backgroundImage ? 'url(\'' + this._getRealPath(item.backgroundImage) + '\')' : ''
})
}
}
}
</script>
......@@ -6,7 +6,7 @@ import { PolySymbol, rpx2px } from '@dcloudio/uni-core'
import safeAreaInsets from 'safe-area-insets'
const pageMetaKey = PolySymbol(__DEV__ ? 'pageMeta' : 'pm')
const pageMetaKey = PolySymbol(__DEV__ ? 'UniPageMeta' : 'upm')
export function usePageMeta() {
return inject<UniApp.PageRouteMeta>(pageMetaKey)!
......
import { reactive } from 'vue'
let tabBar: UniApp.TabBarOptions | undefined
export function useTabBar() {
if (!tabBar) {
tabBar =
__uniConfig.tabBar && reactive<UniApp.TabBarOptions>(__uniConfig.tabBar)
}
return tabBar
}
......@@ -6,14 +6,27 @@ import {
defineSyncApi,
} from '@dcloudio/uni-api'
const supports = window.CSS && window.CSS.supports
function cssSupports(css: string) {
return window.CSS && window.CSS.supports && window.CSS.supports(css)
return (
supports &&
(supports(css) || supports.apply(window.CSS, css.split(':') as any))
)
}
export const cssVar = /*#__PURE__*/ cssSupports('--a:0')
export const cssEnv = /*#__PURE__*/ cssSupports('top:env(a)')
export const cssConstant = /*#__PURE__*/ cssSupports('top:constant(a)')
export const cssBackdropFilter = /*#__PURE__*/ cssSupports(
'backdrop-filter:blur(10px)'
)
const SCHEMA_CSS = {
'css.var': cssSupports('--a:0'),
'css.env': cssSupports('top:env(a)'),
'css.constant': cssSupports('top:constant(a)'),
'css.var': cssVar,
'css.env': cssEnv,
'css.constant': cssConstant,
'css.backdrop-filter': cssBackdropFilter,
}
export const canIUse = defineSyncApi<typeof uni.canIUse>(
......
......@@ -17,6 +17,8 @@ export * from './route/redirectTo'
export * from './route/reLaunch'
export * from './route/switchTab'
export * from './ui/tabBar'
export {
upx2px,
addInterceptor,
......@@ -27,4 +29,5 @@ export {
createIntersectionObserver,
createSelectorQuery,
createVideoContext,
onTabBarMidButtonTap,
} from '@dcloudio/uni-api'
import { invokeHook } from '@dcloudio/uni-core'
import {
API_NAVIGATE_BACK,
defineAsyncApi,
getCurrentPageVm,
NavigateBackOptions,
NavigateBackProtocol,
} from '@dcloudio/uni-api'
......@@ -10,8 +10,7 @@ export const navigateBack = defineAsyncApi<typeof uni.navigateBack>(
API_NAVIGATE_BACK,
({ delta }, { resolve, reject }) => {
let canBack = true
const vm = getCurrentPageVm()
if (vm && vm.$callHook('onBackPress') === true) {
if (invokeHook('onBackPress') === true) {
canBack = false
}
if (!canBack) {
......
import {
defineAsyncApi,
HideTabBarProtocol,
HideTabBarRedDotOptions,
HideTabBarRedDotProtocol,
RemoveTabBarBadgeOptions,
RemoveTabBarBadgeProtocol,
SetTabBarBadgeOptions,
SetTabBarBadgeProtocol,
SetTabBarItemOptions,
SetTabBarItemProtocol,
SetTabBarStyleOptions,
SetTabBarStyleProtocol,
ShowTabBarProtocol,
ShowTabBarRedDotOptions,
ShowTabBarRedDotProtocol,
} from '@dcloudio/uni-api'
import { useTabBar } from '../../../framework/plugin/state'
function setTabBar(
type: string,
args: Record<string, any>,
resolve: () => void
) {
const tabBar = useTabBar()!
switch (type) {
case 'showTabBar':
tabBar.shown = true
break
case 'hideTabBar':
tabBar.shown = false
}
resolve()
}
export const setTabBarItem = defineAsyncApi<typeof uni.setTabBarItem>(
'setTabBarItem',
(args, { resolve }) => {
setTabBar('setTabBarItem', args, resolve)
},
SetTabBarItemProtocol,
SetTabBarItemOptions
)
export const setTabBarStyle = defineAsyncApi<typeof uni.setTabBarStyle>(
'setTabBarStyle',
() => {},
SetTabBarStyleProtocol,
SetTabBarStyleOptions
)
export const hideTabBar = defineAsyncApi<typeof uni.hideTabBar>(
'hideTabBar',
(args, { resolve }) => {
setTabBar('hideTabBar', args, resolve)
},
HideTabBarProtocol
)
export const showTabBar = defineAsyncApi<typeof uni.showTabBar>(
'showTabBar',
(args, { resolve }) => {
setTabBar('showTabBar', args, resolve)
},
ShowTabBarProtocol
)
export const hideTabBarRedDot = defineAsyncApi<typeof uni.hideTabBarRedDot>(
'hideTabBarRedDot',
() => {},
HideTabBarRedDotProtocol,
HideTabBarRedDotOptions
)
export const showTabBarRedDot = defineAsyncApi<typeof uni.showTabBarRedDot>(
'showTabBarRedDot',
() => {},
ShowTabBarRedDotProtocol,
ShowTabBarRedDotOptions
)
export const removeTabBarBadge = defineAsyncApi<typeof uni.removeTabBarBadge>(
'removeTabBarBadge',
() => {},
RemoveTabBarBadgeProtocol,
RemoveTabBarBadgeOptions
)
export const setTabBarBadge = defineAsyncApi<typeof uni.setTabBarBadge>(
'setTabBarBadge',
() => {},
SetTabBarBadgeProtocol,
SetTabBarBadgeOptions
)
uni-tabbar {
display: block;
box-sizing: border-box;
width: 100%;
z-index: 998;
}
.uni-tabbar {
display: flex;
z-index: 998;
box-sizing: border-box;
}
.uni-tabbar-top,
.uni-tabbar-bottom,
.uni-tabbar-top .uni-tabbar,
.uni-tabbar-bottom .uni-tabbar {
position: fixed;
left: var(--window-left);
right: var(--window-right);
}
.uni-app--showlayout + .uni-tabbar-top,
.uni-app--showlayout + .uni-tabbar-bottom,
.uni-app--showlayout + .uni-tabbar-top .uni-tabbar,
.uni-app--showlayout + .uni-tabbar-bottom .uni-tabbar {
left: var(--window-margin);
right: var(--window-margin);
}
.uni-tabbar-bottom .uni-tabbar {
bottom: 0;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.uni-tabbar ~ .uni-placeholder {
width: 100%;
margin-bottom: 0;
margin-bottom: constant(safe-area-inset-bottom);
margin-bottom: env(safe-area-inset-bottom);
}
.uni-tabbar * {
box-sizing: border-box;
}
.uni-tabbar__item {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
flex: 1;
font-size: 0;
text-align: center;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.uni-tabbar__bd {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.uni-tabbar__icon {
position: relative;
display: inline-block;
margin-top: 5px;
}
.uni-tabbar__icon.uni-tabbar__icon__diff {
margin-top: 0px;
width: 34px;
height: 34px;
}
.uni-tabbar__icon img {
width: 100%;
height: 100%;
}
.uni-tabbar__label {
position: relative;
text-align: center;
font-size: 10px;
}
.uni-tabbar-border {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
transform: scaleY(0.5);
}
.uni-tabbar__reddot {
position: absolute;
top: 0;
right: 0;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #f43530;
color: #ffffff;
transform: translate(40%, -20%);
}
.uni-tabbar__badge {
width: auto;
height: 16px;
line-height: 16px;
border-radius: 16px;
min-width: 16px;
padding: 0 2px;
font-size: 12px;
text-align: center;
white-space: nowrap;
}
.uni-tabbar__mid {
display: flex;
justify-content: center;
position: absolute;
bottom: 0;
background-size: 100% 100%;
}
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
route: string
isEntry: boolean
isTabBar: boolean
topWindow?: boolean
......
import { isArray, hasOwn, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction, isString } from '@vue/shared';
import { isArray, hasOwn, isString, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction } from '@vue/shared';
function validateProtocolFail(name, msg) {
const errMsg = `${name}:fail ${msg}`;
......@@ -12,7 +12,7 @@ function validateProtocolFail(name, msg) {
function validateProtocol(name, data, protocol) {
for (const key in protocol) {
const errMsg = validateProp(key, data[key], protocol[key], !hasOwn(data, key));
if (errMsg) {
if (isString(errMsg)) {
return validateProtocolFail(name, errMsg);
}
}
......@@ -65,8 +65,8 @@ function validateProp(name, value, prop, isAbsent) {
}
}
// custom validator
if (validator && !validator(value)) {
return ('Invalid args: custom validator check failed for args "' + name + '".');
if (validator) {
return validator(value);
}
}
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol');
......@@ -170,7 +170,13 @@ function wrapperApi(fn, name, protocol, options) {
return function (...args) {
if ((process.env.NODE_ENV !== 'production')) {
const errMsg = validateProtocols(name, args, protocol);
if (errMsg) {
if (isString(errMsg)) {
return errMsg;
}
}
if (options && options.beforeInvoke) {
const errMsg = options.beforeInvoke(args);
if (isString(errMsg)) {
return errMsg;
}
}
......
import { isArray, hasOwn, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction, isString } from '@vue/shared';
import { isArray, hasOwn, isString, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction } from '@vue/shared';
function validateProtocolFail(name, msg) {
const errMsg = `${name}:fail ${msg}`;
......@@ -12,7 +12,7 @@ function validateProtocolFail(name, msg) {
function validateProtocol(name, data, protocol) {
for (const key in protocol) {
const errMsg = validateProp(key, data[key], protocol[key], !hasOwn(data, key));
if (errMsg) {
if (isString(errMsg)) {
return validateProtocolFail(name, errMsg);
}
}
......@@ -65,8 +65,8 @@ function validateProp(name, value, prop, isAbsent) {
}
}
// custom validator
if (validator && !validator(value)) {
return ('Invalid args: custom validator check failed for args "' + name + '".');
if (validator) {
return validator(value);
}
}
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol');
......@@ -170,7 +170,13 @@ function wrapperApi(fn, name, protocol, options) {
return function (...args) {
if ((process.env.NODE_ENV !== 'production')) {
const errMsg = validateProtocols(name, args, protocol);
if (errMsg) {
if (isString(errMsg)) {
return errMsg;
}
}
if (options && options.beforeInvoke) {
const errMsg = options.beforeInvoke(args);
if (isString(errMsg)) {
return errMsg;
}
}
......
import { isArray, hasOwn, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction, isString } from '@vue/shared';
import { isArray, hasOwn, isString, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction } from '@vue/shared';
function validateProtocolFail(name, msg) {
const errMsg = `${name}:fail ${msg}`;
......@@ -12,7 +12,7 @@ function validateProtocolFail(name, msg) {
function validateProtocol(name, data, protocol) {
for (const key in protocol) {
const errMsg = validateProp(key, data[key], protocol[key], !hasOwn(data, key));
if (errMsg) {
if (isString(errMsg)) {
return validateProtocolFail(name, errMsg);
}
}
......@@ -65,8 +65,8 @@ function validateProp(name, value, prop, isAbsent) {
}
}
// custom validator
if (validator && !validator(value)) {
return ('Invalid args: custom validator check failed for args "' + name + '".');
if (validator) {
return validator(value);
}
}
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol');
......@@ -170,7 +170,13 @@ function wrapperApi(fn, name, protocol, options) {
return function (...args) {
if ((process.env.NODE_ENV !== 'production')) {
const errMsg = validateProtocols(name, args, protocol);
if (errMsg) {
if (isString(errMsg)) {
return errMsg;
}
}
if (options && options.beforeInvoke) {
const errMsg = options.beforeInvoke(args);
if (isString(errMsg)) {
return errMsg;
}
}
......
import { isArray, hasOwn, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction, isString } from '@vue/shared';
import { isArray, hasOwn, isString, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction } from '@vue/shared';
function validateProtocolFail(name, msg) {
const errMsg = `${name}:fail ${msg}`;
......@@ -12,7 +12,7 @@ function validateProtocolFail(name, msg) {
function validateProtocol(name, data, protocol) {
for (const key in protocol) {
const errMsg = validateProp(key, data[key], protocol[key], !hasOwn(data, key));
if (errMsg) {
if (isString(errMsg)) {
return validateProtocolFail(name, errMsg);
}
}
......@@ -65,8 +65,8 @@ function validateProp(name, value, prop, isAbsent) {
}
}
// custom validator
if (validator && !validator(value)) {
return ('Invalid args: custom validator check failed for args "' + name + '".');
if (validator) {
return validator(value);
}
}
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol');
......@@ -170,7 +170,13 @@ function wrapperApi(fn, name, protocol, options) {
return function (...args) {
if ((process.env.NODE_ENV !== 'production')) {
const errMsg = validateProtocols(name, args, protocol);
if (errMsg) {
if (isString(errMsg)) {
return errMsg;
}
}
if (options && options.beforeInvoke) {
const errMsg = options.beforeInvoke(args);
if (isString(errMsg)) {
return errMsg;
}
}
......
import { isArray, hasOwn, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction, isString } from '@vue/shared';
import { isArray, hasOwn, isString, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction } from '@vue/shared';
function validateProtocolFail(name, msg) {
const errMsg = `${name}:fail ${msg}`;
......@@ -12,7 +12,7 @@ function validateProtocolFail(name, msg) {
function validateProtocol(name, data, protocol) {
for (const key in protocol) {
const errMsg = validateProp(key, data[key], protocol[key], !hasOwn(data, key));
if (errMsg) {
if (isString(errMsg)) {
return validateProtocolFail(name, errMsg);
}
}
......@@ -65,8 +65,8 @@ function validateProp(name, value, prop, isAbsent) {
}
}
// custom validator
if (validator && !validator(value)) {
return ('Invalid args: custom validator check failed for args "' + name + '".');
if (validator) {
return validator(value);
}
}
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol');
......@@ -170,7 +170,13 @@ function wrapperApi(fn, name, protocol, options) {
return function (...args) {
if ((process.env.NODE_ENV !== 'production')) {
const errMsg = validateProtocols(name, args, protocol);
if (errMsg) {
if (isString(errMsg)) {
return errMsg;
}
}
if (options && options.beforeInvoke) {
const errMsg = options.beforeInvoke(args);
if (isString(errMsg)) {
return errMsg;
}
}
......
import { isArray, hasOwn, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction, isString } from '@vue/shared';
import { isArray, hasOwn, isString, isObject, capitalize, toRawType, makeMap, isPlainObject, isPromise, isFunction } from '@vue/shared';
function validateProtocolFail(name, msg) {
const errMsg = `${name}:fail ${msg}`;
......@@ -12,7 +12,7 @@ function validateProtocolFail(name, msg) {
function validateProtocol(name, data, protocol) {
for (const key in protocol) {
const errMsg = validateProp(key, data[key], protocol[key], !hasOwn(data, key));
if (errMsg) {
if (isString(errMsg)) {
return validateProtocolFail(name, errMsg);
}
}
......@@ -65,8 +65,8 @@ function validateProp(name, value, prop, isAbsent) {
}
}
// custom validator
if (validator && !validator(value)) {
return ('Invalid args: custom validator check failed for args "' + name + '".');
if (validator) {
return validator(value);
}
}
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol');
......@@ -170,7 +170,13 @@ function wrapperApi(fn, name, protocol, options) {
return function (...args) {
if ((process.env.NODE_ENV !== 'production')) {
const errMsg = validateProtocols(name, args, protocol);
if (errMsg) {
if (isString(errMsg)) {
return errMsg;
}
}
if (options && options.beforeInvoke) {
const errMsg = options.beforeInvoke(args);
if (isString(errMsg)) {
return errMsg;
}
}
......
......@@ -97,6 +97,10 @@ function isNativeTag(tag) {
const COMPONENT_SELECTOR_PREFIX = 'uni-';
const COMPONENT_PREFIX = 'v-' + COMPONENT_SELECTOR_PREFIX;
function getLen(str = '') {
return ('' + str).replace(/[^\x00-\xff]/g, '**').length;
}
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
const res = obj
......@@ -146,6 +150,7 @@ exports.RESPONSIVE_MIN_WIDTH = RESPONSIVE_MIN_WIDTH;
exports.TABBAR_HEIGHT = TABBAR_HEIGHT;
exports.TAGS = TAGS;
exports.debounce = debounce;
exports.getLen = getLen;
exports.isBuiltInComponent = isBuiltInComponent;
exports.isCustomElement = isCustomElement;
exports.isNativeTag = isNativeTag;
......
......@@ -12,6 +12,8 @@ export declare function debounce(fn: Function, delay: number): {
cancel(): void;
};
export declare function getLen(str?: string): number;
export declare function isBuiltInComponent(tag: string): boolean;
export declare function isCustomElement(tag: string): boolean;
......
......@@ -93,6 +93,10 @@ function isNativeTag(tag) {
const COMPONENT_SELECTOR_PREFIX = 'uni-';
const COMPONENT_PREFIX = 'v-' + COMPONENT_SELECTOR_PREFIX;
function getLen(str = '') {
return ('' + str).replace(/[^\x00-\xff]/g, '**').length;
}
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
const res = obj
......@@ -132,4 +136,4 @@ const RESPONSIVE_MIN_WIDTH = 768;
const COMPONENT_NAME_PREFIX = 'VUni';
const PRIMARY_COLOR = '#007aff';
export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, debounce, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, passive, plusReady, stringifyQuery };
export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, debounce, getLen, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, passive, plusReady, stringifyQuery };
export * from './dom'
export * from './plus'
export * from './tags'
export * from './utils'
export * from './query'
export * from './debounce'
export * from './constants'
export function getLen(str = '') {
return ('' + str).replace(/[^\x00-\xff]/g, '**').length
}
......@@ -105,6 +105,9 @@ function generateCssCode(config: ResolvedConfig) {
if (define.__UNI_FEATURE_NAVIGATIONBAR__) {
cssFiles.push('@dcloudio/uni-h5/style/pageHead.css')
}
if (define.__UNI_FEATURE_TABBAR__) {
cssFiles.push('@dcloudio/uni-h5/style/tabBar.css')
}
if (define.__UNI_FEATURE_NVUE__) {
cssFiles.push('@dcloudio/uni-h5/style/nvue.css')
}
......@@ -151,6 +154,7 @@ function normalizePagesRoute(pagesJson: UniApp.PagesJson): PageRouteOptions[] {
let windowTop = 0
const meta = Object.assign(
{
route: pageOptions.path,
isQuit: isEntry || isTabBar ? true : undefined,
isEntry,
isTabBar,
......@@ -159,11 +163,6 @@ function normalizePagesRoute(pagesJson: UniApp.PagesJson): PageRouteOptions[] {
},
pageOptions.style
)
if (isEntry) {
;(meta as any).route = pageOptions.path
}
return {
name,
path: pageOptions.path,
......
......@@ -11,6 +11,7 @@ interface PagesFeatures {
nvue: boolean
pages: boolean
tabBar: boolean
tabBarMidButton: boolean
topWindow: boolean
leftWindow: boolean
rightWindow: boolean
......@@ -46,6 +47,7 @@ function resolvePagesFeature(
nvue: true,
pages: true,
tabBar: true,
tabBarMidButton: true,
topWindow: false,
leftWindow: false,
rightWindow: false,
......@@ -72,6 +74,10 @@ function resolvePagesFeature(
}
if (!(tabBar && tabBar.list && tabBar.list.length)) {
features.tabBar = false
features.tabBarMidButton = false
}
if (features.tabBar && !tabBar!.midButton) {
features.tabBarMidButton = false
}
if (topWindow && topWindow.path) {
features.topWindow = true
......@@ -177,6 +183,7 @@ export function getFeatures(
nvue,
pages,
tabBar,
tabBarMidButton,
promise,
longpress,
routerMode,
......@@ -203,6 +210,7 @@ export function getFeatures(
__UNI_FEATURE_ROUTER_MODE__: routerMode, // 路由模式
__UNI_FEATURE_PAGES__: pages, // 是否多页面
__UNI_FEATURE_TABBAR__: tabBar, // 是否包含tabBar
__UNI_FEATURE_TABBAR_MIDBUTTON__: tabBarMidButton, // 是否包含midButton
__UNI_FEATURE_TOPWINDOW__: topWindow, // 是否包含topWindow
__UNI_FEATURE_LEFTWINDOW__: leftWindow, // 是否包含leftWindow
__UNI_FEATURE_RIGHTWINDOW__: rightWindow, // 是否包含rightWindow
......
import path from 'path'
import slash from 'slash'
import { hasOwn, isArray, isPlainObject } from '@vue/shared'
import { extend, hasOwn, isArray, isPlainObject } from '@vue/shared'
import { parseJson } from '@dcloudio/uni-cli-shared'
import { TABBAR_HEIGHT } from '@dcloudio/uni-shared'
export function normalizePagesJson(jsonStr: string, platform: UniApp.PLATFORM) {
let pagesJson: UniApp.PagesJson = {
......@@ -26,6 +27,15 @@ export function normalizePagesJson(jsonStr: string, platform: UniApp.PLATFORM) {
normalizePages(pagesJson.pages, platform)
// globalStyle
pagesJson.globalStyle = normalizePageStyle(pagesJson.globalStyle!, platform)
// tabBar
if (pagesJson.tabBar) {
const tabBar = normalizeTabBar(pagesJson.tabBar!)
if (tabBar) {
pagesJson.tabBar = tabBar
} else {
delete pagesJson.tabBar
}
}
return pagesJson
}
......@@ -181,6 +191,69 @@ function normalizeNavigationBarSearchInput(
)
}
const DEFAULT_TAB_BAR: Partial<UniApp.TabBarOptions> = {
position: 'bottom',
color: '#999',
selectedColor: '#007aff',
borderStyle: 'black',
blurEffect: 'none',
fontSize: '10px',
iconWidth: '24px',
spacing: '3px',
height: TABBAR_HEIGHT + 'px',
}
function normalizeTabBar(tabBar: UniApp.TabBarOptions) {
const { list, midButton } = tabBar
if (!list || !list.length) {
return
}
tabBar = extend({}, DEFAULT_TAB_BAR, tabBar)
const len = list.length
if (len % 2 === 0 && isPlainObject(midButton)) {
list.splice(
Math.floor(len / 2),
0,
extend(
{
type: 'midButton',
width: '50px',
height: '50px',
iconWidth: '24px',
},
midButton
)
)
} else {
delete tabBar.midButton
}
list.forEach((item) => {
if (item.iconPath) {
item.iconPath = normalizeFilepath(item.iconPath)
}
if (item.selectedIconPath) {
item.selectedIconPath = normalizeFilepath(item.selectedIconPath)
}
if (item.type === 'midButton' && item.backgroundImage) {
item.backgroundImage = normalizeFilepath(item.backgroundImage)
}
})
tabBar.selectedIndex = 0
tabBar.shown = true
return tabBar
}
const SCHEME_RE = /^([a-z-]+:)?\/\//i
const DATA_RE = /^data:.*,.*/
function normalizeFilepath(filepath: string) {
if (
!(SCHEME_RE.test(filepath) || DATA_RE.test(filepath)) &&
filepath.indexOf('/') !== 0
) {
return '/' + filepath
}
return filepath
}
const platforms = ['h5', 'app-plus', 'mp-', 'quickapp']
function removePlatformStyle(pageStyle: UniApp.PagesJsonPageStyle) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册