diff --git a/packages/shims-vue-runtime.d.ts b/packages/shims-vue-runtime.d.ts index 8bdbb066717b255efa988f661f0059c2f49a089a..2c31c68981cdb02635d44eded5ca53ae59703437 100644 --- a/packages/shims-vue-runtime.d.ts +++ b/packages/shims-vue-runtime.d.ts @@ -2,6 +2,7 @@ import { UniLifecycleHooks } from '@dcloudio/uni-vue/src/apiLifecycle' import { ComponentCustomProperties, ComponentInternalInstance } from 'vue' declare module '@vue/runtime-core' { interface ComponentCustomProperties { + route: string $scope: { $getAppWebview?: () => PlusWebviewWebviewObject } diff --git a/packages/uni-api/src/helpers/api/index.ts b/packages/uni-api/src/helpers/api/index.ts index 51b17a12daefddb8dd1da17be1b773c0c188bb7f..4283d5953bb878a744645970ddff865f0f2cd319 100644 --- a/packages/uni-api/src/helpers/api/index.ts +++ b/packages/uni-api/src/helpers/api/index.ts @@ -131,11 +131,14 @@ function wrapperOffApi( } function normalizeErrMsg(errMsg: string | Error) { - if (errMsg instanceof Error) { - console.error(errMsg) + if (isString(errMsg)) { + return errMsg + } + if (errMsg.stack) { + console.error(errMsg.message + '\n' + errMsg.stack) return errMsg.message } - return errMsg + return errMsg as unknown as string } function wrapperTaskApi( diff --git a/packages/uni-app-plus/dist/uni-app-service.es.js b/packages/uni-app-plus/dist/uni-app-service.es.js index f93cc1066c9c30a8ede32545830329915d99c075..2f16bdaca9baabf87553e5c94ccd9c4e4680471b 100644 --- a/packages/uni-app-plus/dist/uni-app-service.es.js +++ b/packages/uni-app-plus/dist/uni-app-service.es.js @@ -525,8 +525,11 @@ var serviceContext = (function (vue) { }; } function normalizeErrMsg(errMsg) { - if (errMsg instanceof Error) { - console.error(errMsg); + if (isString(errMsg)) { + return errMsg; + } + if (errMsg.stack) { + console.error(errMsg.message + '\n' + errMsg.stack); return errMsg.message; } return errMsg; @@ -1801,6 +1804,7 @@ var serviceContext = (function (vue) { appVm.$mpType = 'app'; } function initPageVm(pageVm, page) { + pageVm.route = page.route; pageVm.$vm = pageVm; pageVm.$page = page; pageVm.$mpType = 'page'; @@ -4420,12 +4424,15 @@ var serviceContext = (function (vue) { }, createAnimationProtocol(ANIMATION_OUT)); const RedirectToProtocol = BaseRouteProtocol; const ReLaunchProtocol = BaseRouteProtocol; + const SwitchTabProtocol = BaseRouteProtocol; const NavigateToOptions = /*#__PURE__*/ createRouteOptions(API_NAVIGATE_TO); const RedirectToOptions = /*#__PURE__*/ createRouteOptions(API_REDIRECT_TO); const ReLaunchOptions = /*#__PURE__*/ createRouteOptions(API_RE_LAUNCH); + const SwitchTabOptions = + /*#__PURE__*/ createRouteOptions(API_SWITCH_TAB); const NavigateBackOptions = { formatArgs: { delta(value, params) { @@ -9068,6 +9075,10 @@ var serviceContext = (function (vue) { console.log(formatLog('setPendingNavigator', path, msg)); } } + function closePage(page, animationType, animationDuration) { + removePage(page); + closeWebview(page.$getAppWebview(), animationType, animationDuration); + } function navigate(path, callback, isAppLaunch = false) { if (!isAppLaunch && pendingNavigator) { return console.error(`Waiting to navigate to: ${pendingNavigator.path}, do not operate continuously: ${path}.`); @@ -9757,16 +9768,111 @@ var serviceContext = (function (vue) { query, openType: 'reLaunch', }), 'none', 0, () => { - pages.forEach((page) => { - removePage(page); - closeWebview(page.$getAppWebview(), 'none'); - }); + pages.forEach((page) => closePage(page, 'none')); resolve(undefined); }); setStatusBarStyle(); }); } + const switchTab = defineAsyncApi(API_SWITCH_TAB, (args, { resolve, reject }) => { + const { url } = args; + const { path, query } = parseUrl(url); + navigate(path, () => { + _switchTab({ + url, + path, + query, + }) + .then(resolve) + .catch(reject); + }, args.openType === 'appLaunch'); + }, SwitchTabProtocol, SwitchTabOptions); + function _switchTab({ url, path, query, }) { + tabBar$1.switchTab(path.slice(1)); + const pages = getCurrentPages(); + const len = pages.length; + let callOnHide = false; + let callOnShow = false; + let currentPage; + if (len >= 1) { + // 前一个页面是非 tabBar 页面 + currentPage = pages[len - 1]; + if (currentPage && !currentPage.__isTabBar) { + // 前一个页面为非 tabBar 页面时,目标tabBar需要强制触发onShow + // 该情况下目标页tabBarPage的visible是不对的 + // 除非每次路由跳转都处理一遍tabBarPage的visible,目前仅switchTab会处理 + // 简单起见,暂时直接判断该情况,执行onShow + callOnShow = true; + pages.reverse().forEach((page) => { + if (!page.__isTabBar && page !== currentPage) { + closePage(page, 'none'); + } + }); + removePage(currentPage); + // 延迟执行避免iOS应用退出 + setTimeout(() => { + if (currentPage.$page.openType === 'redirectTo') { + closeWebview(currentPage.$getAppWebview(), ANI_CLOSE, ANI_DURATION); + } + else { + closeWebview(currentPage.$getAppWebview(), 'auto'); + } + }, 100); + } + else { + callOnHide = true; + } + } + let tabBarPage; + // 查找当前 tabBarPage,且设置 visible + getAllPages().forEach((page) => { + if ('/' + page.route === path) { + if (!page.$.__isActive) { + // 之前未显示 + callOnShow = true; + } + page.$.__isActive = true; + tabBarPage = page; + } + else { + if (page.__isTabBar) { + page.$.__isActive = false; + } + } + }); + // 相同tabBar页面 + if (currentPage === tabBarPage) { + callOnHide = false; + } + if (currentPage && callOnHide) { + invokeHook(currentPage, 'onHide'); + } + return new Promise((resolve) => { + if (tabBarPage) { + const webview = tabBarPage.$getAppWebview(); + webview.show('none'); + // 等visible状态都切换完之后,再触发onShow,否则开发者在onShow里边 getCurrentPages 会不准确 + if (callOnShow && !webview.__preload__) { + invokeHook(tabBarPage, 'onShow'); + } + resolve(undefined); + } + else { + showWebview(registerPage({ + url, + path, + query, + openType: 'switchTab', + }), 'none', 0, () => { + setStatusBarStyle(); + resolve(undefined); + }, 70); + } + setStatusBarStyle(); + }); + } + var uni$1 = /*#__PURE__*/Object.freeze({ __proto__: null, upx2px: upx2px, @@ -9897,7 +10003,8 @@ var serviceContext = (function (vue) { navigateBack: navigateBack, navigateTo: navigateTo, redirectTo: redirectTo, - reLaunch: reLaunch + reLaunch: reLaunch, + switchTab: switchTab }); let invokeViewMethodId = 0; diff --git a/packages/uni-app-plus/src/service/api/index.ts b/packages/uni-app-plus/src/service/api/index.ts index 0eb7b8e84dc2a8a536c5b10dc8049d69e1e95d06..cbe324d9ce40dec185fe6909bc34e32d9cb4298d 100644 --- a/packages/uni-app-plus/src/service/api/index.ts +++ b/packages/uni-app-plus/src/service/api/index.ts @@ -56,6 +56,7 @@ export * from './route/navigateBack' export * from './route/navigateTo' export * from './route/redirectTo' export * from './route/reLaunch' +export * from './route/switchTab' export { upx2px, diff --git a/packages/uni-app-plus/src/service/api/route/reLaunch.ts b/packages/uni-app-plus/src/service/api/route/reLaunch.ts index d09e5a204188522c30916a6138e7945e2e9c7d80..30de5d1a1d21ac8a4ded3cbc4e9f52f44eca3fb3 100644 --- a/packages/uni-app-plus/src/service/api/route/reLaunch.ts +++ b/packages/uni-app-plus/src/service/api/route/reLaunch.ts @@ -6,13 +6,12 @@ import { ReLaunchProtocol, } from '@dcloudio/uni-api' import { parseUrl } from '@dcloudio/uni-shared' -import { ComponentPublicInstance } from 'vue' import tabBar from '../../framework/app/tabBar' import { registerPage } from '../../framework/page' -import { getAllPages, removePage } from '../../framework/page/getCurrentPages' +import { getAllPages } from '../../framework/page/getCurrentPages' import { setStatusBarStyle } from '../../statusBar' -import { navigate, RouteOptions } from './utils' -import { closeWebview, showWebview } from './webview' +import { closePage, navigate, RouteOptions } from './utils' +import { showWebview } from './webview' export const reLaunch = defineAsyncApi( API_RE_LAUNCH, @@ -53,13 +52,7 @@ function _reLaunch({ url, path, query }: ReLaunchOptions): Promise { 'none', 0, () => { - pages.forEach((page) => { - removePage(page) - closeWebview( - (page as ComponentPublicInstance).$getAppWebview!(), - 'none' - ) - }) + pages.forEach((page) => closePage(page, 'none')) resolve(undefined) } ) diff --git a/packages/uni-app-plus/src/service/api/route/switchTab.ts b/packages/uni-app-plus/src/service/api/route/switchTab.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1599f4a97bd96714526741f184a7a211d2816a0 --- /dev/null +++ b/packages/uni-app-plus/src/service/api/route/switchTab.ts @@ -0,0 +1,134 @@ +import { + API_SWITCH_TAB, + API_TYPE_SWITCH_TAB, + defineAsyncApi, + SwitchTabOptions, + SwitchTabProtocol, +} from '@dcloudio/uni-api' +import { invokeHook } from '@dcloudio/uni-core' +import { parseUrl } from '@dcloudio/uni-shared' +import { ComponentPublicInstance } from 'vue' +import { ANI_CLOSE, ANI_DURATION } from '../../constants' +import tabBar from '../../framework/app/tabBar' +import { registerPage } from '../../framework/page' +import { getAllPages, removePage } from '../../framework/page/getCurrentPages' +import { setStatusBarStyle } from '../../statusBar' +import { closePage, navigate, RouteOptions } from './utils' +import { closeWebview, showWebview } from './webview' + +export const switchTab = defineAsyncApi( + API_SWITCH_TAB, + (args, { resolve, reject }) => { + const { url } = args + const { path, query } = parseUrl(url) + navigate( + path, + () => { + _switchTab({ + url, + path, + query, + }) + .then(resolve) + .catch(reject) + }, + (args as any).openType === 'appLaunch' + ) + }, + SwitchTabProtocol, + SwitchTabOptions +) + +interface SwitchTabOptions extends RouteOptions {} + +function _switchTab({ + url, + path, + query, +}: SwitchTabOptions): Promise { + tabBar.switchTab(path.slice(1)) + const pages = getCurrentPages() as ComponentPublicInstance[] + const len = pages.length + let callOnHide = false + let callOnShow = false + let currentPage: ComponentPublicInstance | undefined + if (len >= 1) { + // 前一个页面是非 tabBar 页面 + currentPage = pages[len - 1]! as ComponentPublicInstance + if (currentPage && !currentPage.__isTabBar) { + // 前一个页面为非 tabBar 页面时,目标tabBar需要强制触发onShow + // 该情况下目标页tabBarPage的visible是不对的 + // 除非每次路由跳转都处理一遍tabBarPage的visible,目前仅switchTab会处理 + // 简单起见,暂时直接判断该情况,执行onShow + callOnShow = true + pages.reverse().forEach((page) => { + if (!page.__isTabBar && page !== currentPage) { + closePage(page, 'none') + } + }) + removePage(currentPage) + // 延迟执行避免iOS应用退出 + setTimeout(() => { + if (currentPage!.$page.openType === 'redirectTo') { + closeWebview(currentPage!.$getAppWebview!(), ANI_CLOSE, ANI_DURATION) + } else { + closeWebview(currentPage!.$getAppWebview!(), 'auto') + } + }, 100) + } else { + callOnHide = true + } + } + + let tabBarPage: ComponentPublicInstance | undefined + // 查找当前 tabBarPage,且设置 visible + getAllPages().forEach((page) => { + if ('/' + page.route === path) { + if (!page.$.__isActive) { + // 之前未显示 + callOnShow = true + } + page.$.__isActive = true + tabBarPage = page + } else { + if (page.__isTabBar) { + page.$.__isActive = false + } + } + }) + // 相同tabBar页面 + if (currentPage === tabBarPage) { + callOnHide = false + } + if (currentPage && callOnHide) { + invokeHook(currentPage, 'onHide') + } + return new Promise((resolve) => { + if (tabBarPage) { + const webview = tabBarPage!.$getAppWebview!() + webview.show('none') + // 等visible状态都切换完之后,再触发onShow,否则开发者在onShow里边 getCurrentPages 会不准确 + if (callOnShow && !(webview as any).__preload__) { + invokeHook(tabBarPage, 'onShow') + } + resolve(undefined) + } else { + showWebview( + registerPage({ + url, + path, + query, + openType: 'switchTab', + }), + 'none', + 0, + () => { + setStatusBarStyle() + resolve(undefined) + }, + 70 + ) + } + setStatusBarStyle() + }) +} diff --git a/packages/uni-app-plus/src/service/api/route/utils.ts b/packages/uni-app-plus/src/service/api/route/utils.ts index b924166b3c4ebd1f8ad9814b327f1c16c9e8d415..331fde25f3e213d649b65ebe0ea5871fee9325c6 100644 --- a/packages/uni-app-plus/src/service/api/route/utils.ts +++ b/packages/uni-app-plus/src/service/api/route/utils.ts @@ -1,10 +1,13 @@ import { getRouteMeta } from '@dcloudio/uni-core' import { formatLog } from '@dcloudio/uni-shared' +import { ComponentPublicInstance } from 'vue' +import { removePage } from '../../framework/page/getCurrentPages' import { createPreloadWebview, onWebviewReady, preloadWebview, } from '../../framework/webview' +import { closeWebview } from './webview' export interface RouteOptions { url: string @@ -30,6 +33,15 @@ function setPendingNavigator(path: string, callback: Function, msg: string) { } } +export function closePage( + page: ComponentPublicInstance, + animationType: string, + animationDuration?: number +) { + removePage(page) + closeWebview(page.$getAppWebview!(), animationType, animationDuration) +} + export function navigate( path: string, callback: Function, diff --git a/packages/uni-cli-shared/src/json/app/pages/uniConfig.ts b/packages/uni-cli-shared/src/json/app/pages/uniConfig.ts index 6275f46a8f7805c5f92af20bebfe10163c18416c..b39a032f5d868dec152ce7feec387daed030da99 100644 --- a/packages/uni-cli-shared/src/json/app/pages/uniConfig.ts +++ b/packages/uni-cli-shared/src/json/app/pages/uniConfig.ts @@ -27,6 +27,7 @@ interface AppUniConfig { uploadFile: number downloadFile: number } + tabBar?: UniApp.UniConfig['tabBar'] } export function normalizeAppUniConfig( @@ -54,6 +55,7 @@ export function normalizeAppUniConfig( compilerVersion: process.env.UNI_COMPILER_VERSION, entryPagePath: pagesJson.pages[0].path, networkTimeout: normalizeNetworkTimeout(manifestJson.networkTimeout), + tabBar: pagesJson.tabBar, } // TODO 待支持分包 return JSON.stringify(config) diff --git a/packages/uni-core/src/service/init/index.ts b/packages/uni-core/src/service/init/index.ts index 2434d887f3c12ea9003e40ccd78b013d3403bf2c..1fac904f2d9869aac4748e49d782c8e985b8fcdd 100644 --- a/packages/uni-core/src/service/init/index.ts +++ b/packages/uni-core/src/service/init/index.ts @@ -19,6 +19,7 @@ export function initPageVm( pageVm: ComponentPublicInstance, page: Page.PageInstance['$page'] ) { + pageVm.route = page.route pageVm.$vm = pageVm pageVm.$page = page pageVm.$mpType = 'page' diff --git a/packages/uni-h5/dist/uni-h5.cjs.js b/packages/uni-h5/dist/uni-h5.cjs.js index e42faf5cafdc4de964f27ddbc0412101184a8a43..bd97787b4bb10f90f3f0e0243fe5bfbfd2553469 100644 --- a/packages/uni-h5/dist/uni-h5.cjs.js +++ b/packages/uni-h5/dist/uni-h5.cjs.js @@ -608,6 +608,7 @@ function initAppVm(appVm2) { appVm2.$mpType = "app"; } function initPageVm(pageVm, page) { + pageVm.route = page.route; pageVm.$vm = pageVm; pageVm.$page = page; pageVm.$mpType = "page"; diff --git a/packages/uni-h5/dist/uni-h5.es.js b/packages/uni-h5/dist/uni-h5.es.js index 0feafc657aa9273d84af1e460b81083d4018542a..d20799e9d9810fd5d705a6e3cca09aabe1c3ece1 100644 --- a/packages/uni-h5/dist/uni-h5.es.js +++ b/packages/uni-h5/dist/uni-h5.es.js @@ -1378,6 +1378,7 @@ function initAppVm(appVm2) { appVm2.$mpType = "app"; } function initPageVm(pageVm, page) { + pageVm.route = page.route; pageVm.$vm = pageVm; pageVm.$page = page; pageVm.$mpType = "page";