diff --git a/CHANGELOG.md b/CHANGELOG.md index d88e27a71aee03813f2095e7258f00394011261b..63e020fd21048fa6fb46779854a4ff29c4d89880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ ## [Unreleased] +### Added + +- 新增打开模态路由视图功能 +- 支持界面行为组可见逻辑与启用逻辑 + +### Fixed + +- 修复ios浏览器安全距离失效 + ## [0.0.34] - 2024-11-15 ### Added diff --git a/src/mob-app/components/index.ts b/src/mob-app/components/index.ts index 7a32685f42e1f738cdaa677177a2738d5ea01e71..1df9630d1c0fb1dbf2d58cefa790da3586d2a6bf 100644 --- a/src/mob-app/components/index.ts +++ b/src/mob-app/components/index.ts @@ -1,4 +1,5 @@ import { HomeView } from './home-view/home-view'; import { RouterShell } from './router-shell/router-shell'; +import { ModalRouterShell } from './modal-router-shell/modal-router-shell'; -export { RouterShell, HomeView }; +export { RouterShell, HomeView, ModalRouterShell }; diff --git a/src/mob-app/components/modal-router-shell/modal-router-shell.tsx b/src/mob-app/components/modal-router-shell/modal-router-shell.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ecdd45abc940694d1fe26e2bce9fbb6a5da9431a --- /dev/null +++ b/src/mob-app/components/modal-router-shell/modal-router-shell.tsx @@ -0,0 +1,174 @@ +import { PropType, defineComponent, onUnmounted, ref, toRaw } from 'vue'; +import { + IRouteViewData, + createOverlayView, + parseRouteViewData, + route2routePath, + routerCallback, +} from '@ibiz-template/vue3-util'; +import { + RouteLocationNormalized, + onBeforeRouteUpdate, + useRoute, + useRouter, +} from 'vue-router'; +import { IBizContext } from '@ibiz-template/core'; +import { + IModal, + IModalData, + IOverlayContainer, + IViewConfig, + RouteConst, +} from '@ibiz-template/runtime'; +import { isEmpty } from 'ramda'; +import qs from 'qs'; +import { View404 } from '../../../view'; + +export const ModalRouterShell = defineComponent({ + name: 'ModalRouterShell', + props: { + modal: { + type: Object as PropType, + required: true, + }, + }, + setup(props) { + const routeObj = useRoute(); + const router = useRouter(); + const isDestroyed = ref(false); + const viewData = ref({}); + const pathHistory: string[] = []; + const destroyContext = (): void => { + if (viewData.value.context) { + const { context } = toRaw(viewData.value); + if (context) context.destroy(); + } + }; + const routeDepth = props.modal.routeDepth!; + + let overlay: IOverlayContainer | null = null; + + // 销毁视图上下文 + onUnmounted(() => { + isDestroyed.value = true; + if (overlay) { + overlay.dismiss(); + overlay = null; + } + destroyContext(); + }); + + const openView = async (route: RouteLocationNormalized): Promise => { + viewData.value = await parseRouteViewData(route, routeDepth, true); + if (isDestroyed.value) { + return; + } + if (!(viewData.value.context instanceof IBizContext)) { + viewData.value.context = IBizContext.create(viewData.value.context); + } + + // 当前模态打开的视图名称 + const appViewId = route.params.modalView as string; + // 路由参数 + const paramsStr = route.params.modalParams as string; + if (paramsStr && paramsStr !== '-') { + const params = qs.parse(paramsStr, { + strictNullHandling: true, + delimiter: ';', + }) as IParams; + // 解析路由参数中的上下文 + if (params.srfnavctx) { + const srfnavctx = JSON.parse( + decodeURIComponent(params.srfnavctx as string), + ); + Object.assign(viewData.value.context, srfnavctx!); + delete params.srfnavctx; + } + if (params.srfnav) { + viewData.value.srfnav = params.srfnav as string; + delete params.srfnav; + } + if (!isEmpty(params)) { + if (viewData.value.params) { + Object.assign(viewData.value.params, params!); + } else { + viewData.value.params = params; + } + } + } + // 解析后不存在上下文时,创建一个空的上下文 + if (!viewData.value.context) { + const _context = IBizContext.create({}); + viewData.value.context = _context; + } + + let appView: IViewConfig | undefined = viewData.value.viewConfig; + let component: unknown; + try { + // 打开模态视图 + + if (!appView) { + appView = await ibiz.hub.config.view.get(appViewId!); + } + + component = createOverlayView({ + context: viewData.value.context, + params: viewData.value.params, + viewId: appView.id, + }); + } catch (error) { + component = View404; + } + // 设置默认的modal参数 + const opts = { + width: appView?.width || '80%', + height: appView?.height || '80%', + footerHide: true, + isRouteModal: true, + }; + overlay = ibiz.overlay.createModal(component, undefined, opts); + overlay.present(); + pathHistory.push(route.fullPath); + const result = await overlay.onWillDismiss(); + overlay = null; + if (isDestroyed.value === false) { + if (window.history.state?.back) { + router.back(); + } else { + const path = route.path; + const index = path.indexOf(`/${RouteConst.ROUTE_MODAL_TAG}/`); + router.replace(path.substring(0, index)); + } + routerCallback.close( + router.currentRoute.value.fullPath, + result || { ok: false }, + ); + } + }; + + onBeforeRouteUpdate( + (to: RouteLocationNormalized, from: RouteLocationNormalized) => { + // 当前组件激活、当前视图已经打开、路由历史中原有路径存在(说明跳转过),目标路径不存在(没有跳转) + if ( + !isDestroyed.value && + pathHistory.length > 0 && + pathHistory.indexOf(from.fullPath) !== -1 && + pathHistory.indexOf(to.fullPath) === -1 + ) { + // 模态视图才执行该逻辑 + const pathNodes = route2routePath(to).pathNodes; + const lastNode = pathNodes[pathNodes.length - 1]; + if (lastNode && lastNode.viewName === RouteConst.ROUTE_MODAL_TAG) { + openView(to); + } + } + }, + ); + + openView(routeObj); + return {}; + }, + render() { + return
; + }, +}); diff --git a/src/mob-app/components/router-shell/router-shell.tsx b/src/mob-app/components/router-shell/router-shell.tsx index 44250fb23d259254476d8b0515513932a60f4806..519218743c1cdfa2cbad0c2241c43d33298b86eb 100644 --- a/src/mob-app/components/router-shell/router-shell.tsx +++ b/src/mob-app/components/router-shell/router-shell.tsx @@ -6,10 +6,12 @@ import { h, resolveComponent, PropType, + onActivated, + onDeactivated, } from 'vue'; import { IRouteViewData, parseRouteViewData } from '@ibiz-template/vue3-util'; import { useRoute, useRouter } from 'vue-router'; -import { IModal } from '@ibiz-template/runtime'; +import { IModal, Modal, RouteConst, ViewMode } from '@ibiz-template/runtime'; import { IBizContext } from '@ibiz-template/core'; import { mergeDeepLeft } from 'ramda'; @@ -26,6 +28,7 @@ export const RouterShell = defineComponent({ const router = useRouter(); const viewData = ref({}); const isLoaded = ref(false); + const isActivated = ref(true); const destroyContext = () => { if (viewData.value.context) { const { context } = toRaw(viewData.value); @@ -57,10 +60,25 @@ export const RouterShell = defineComponent({ } }; calcViewData(); + + const routeModal = new Modal({ + mode: ViewMode.ROUTE, + routeDepth: routeDepth + 1, + }); + + onActivated(() => { + isActivated.value = true; + }); + + onDeactivated(() => { + isActivated.value = false; + }); return { route, viewData, isLoaded, + isActivated, + routeModal, }; }, render() { @@ -80,6 +98,17 @@ export const RouterShell = defineComponent({ { state: { srfnav } }, ); - return h(resolveComponent('IBizViewShell') as string, props, this.$slots); + return ( + <> + {h(resolveComponent('IBizViewShell') as string, props, this.$slots)} + {this.isActivated && ( + + )} + + ); }, }); diff --git a/src/mob-app/router/index.ts b/src/mob-app/router/index.ts index 3db8da9d3180bc79b19d02f01a0bf48d4bb7279d..75427d6536a6b516d2e509bd9bc397ec69937977 100644 --- a/src/mob-app/router/index.ts +++ b/src/mob-app/router/index.ts @@ -5,12 +5,12 @@ import { createRouter, createWebHashHistory, } from 'vue-router'; -import { Modal, ViewMode } from '@ibiz-template/runtime'; +import { Modal, RouteConst, ViewMode } from '@ibiz-template/runtime'; import { isNilOrEmpty } from 'qx-util'; import { AppRedirectView } from '@ibiz-template/vue3-util'; import qs from 'qs'; import { AuthGuard } from '../guard'; -import { RouterShell, HomeView } from '../components'; +import { RouterShell, HomeView, ModalRouterShell } from '../components'; import { LoginView, View404 } from '../../view'; import { useViewStack } from '../../util'; @@ -83,6 +83,12 @@ export class AppRouter { meta: { preset: true }, component: View404, }, + { + path: `${RouteConst.ROUTE_MODAL_TAG}/:modalView(${viewReg})/:modalParams(${paramReg})`, + components: { + [RouteConst.ROUTE_MODAL_TAG]: ModalRouterShell, + }, + }, { path: `:view2(${viewReg})/:params2(${paramReg})`, component: RouterShell, @@ -94,6 +100,12 @@ export class AppRouter { meta: { preset: true }, component: View404, }, + { + path: `${RouteConst.ROUTE_MODAL_TAG}/:modalView(${viewReg})/:modalParams(${paramReg})`, + components: { + [RouteConst.ROUTE_MODAL_TAG]: ModalRouterShell, + }, + }, { path: `:view3(${viewReg})/:params3(${paramReg})`, component: RouterShell, @@ -105,6 +117,12 @@ export class AppRouter { meta: { preset: true }, component: View404, }, + { + path: `${RouteConst.ROUTE_MODAL_TAG}/:modalView(${viewReg})/:modalParams(${paramReg})`, + components: { + [RouteConst.ROUTE_MODAL_TAG]: ModalRouterShell, + }, + }, { path: `:view4(${viewReg})/:params4(${paramReg})`, component: RouterShell, diff --git a/src/panel-component/panel-button-list/panel-button-list.controller.ts b/src/panel-component/panel-button-list/panel-button-list.controller.ts index 0d2154afc32552ace8410f16dcfbbde079f6f1b1..aca2d10fd17d22c955c22f2184e52e8542465f02 100644 --- a/src/panel-component/panel-button-list/panel-button-list.controller.ts +++ b/src/panel-component/panel-button-list/panel-button-list.controller.ts @@ -78,6 +78,7 @@ export class PanelButtonListController extends PanelItemController} * @memberof IosPlatformProvider */ async init(): Promise { - this.clean = listenJSEvent(window, 'load', () => { - const windowHeight = window.innerHeight; - const documentHeight = document.documentElement.scrollHeight; - if (documentHeight > windowHeight) { - // 存在安全距离 - const safeAreaBottom = documentHeight - windowHeight; - const root: HTMLElement | null = document.querySelector('#app'); - if (root) { - root.style.setProperty( - '--safe-area-inset-bottom', - `${safeAreaBottom}px`, - ); - } + const windowHeight = window.innerHeight; + const documentHeight = document.documentElement.scrollHeight; + if (documentHeight > windowHeight) { + // 存在安全距离 + const safeAreaBottom = documentHeight - windowHeight; + const root: HTMLElement | null = document.querySelector('#app'); + if (root) { + root.style.setProperty( + '--safe-area-inset-bottom', + `${safeAreaBottom}px`, + ); + root.style.setProperty('--van-back-top-bottom', `80px`); } - }); - } - - /** - * @description 应用销毁 - * @memberof IosPlatformProvider - */ - async destroyed(): Promise { - if (this.clean !== NOOP) { - this.clean(); } } } diff --git a/src/util/open-view-util/open-view-util.ts b/src/util/open-view-util/open-view-util.ts index 10244b5019b45ecbc5e2d930a7177d590e13c084..162100a119bcf1d221343273a47a095f4b494d2d 100644 --- a/src/util/open-view-util/open-view-util.ts +++ b/src/util/open-view-util/open-view-util.ts @@ -7,6 +7,7 @@ import { } from '@ibiz-template/runtime'; import { generateRoutePath, + generateRoutePathByModal, getDrawerPlacement, openViewDrawer, openViewModal, @@ -30,12 +31,19 @@ export class OpenViewUtil implements IOpenViewUtil { // eslint-disable-next-line no-useless-constructor, no-empty-function constructor(protected router: Router) {} - rootByModal( - _appViewId: string, - _context: IContext, - _params?: IParams | undefined, + async rootByModal( + appViewId: string, + context: IContext, + params?: IParams | undefined, ): Promise { - throw new Error('Method not implemented.'); + const appView = await ibiz.hub.config.view.get(appViewId!); + const { path } = await generateRoutePathByModal( + appView, + this.router.currentRoute.value, + context, + params, + ); + return this.push(path); } push(path: string): Promise {