mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 02:31:34 +08:00
fix(vue): improve handling of parameterized urls (#22360)
resolves #22359
This commit is contained in:
@ -27,20 +27,7 @@ export const createViewStacks = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
|
const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
|
||||||
return findViewItemByPath(routeInfo.lastPathname, outletId);
|
return findViewItemByPath(routeInfo.lastPathname, outletId, false);
|
||||||
}
|
|
||||||
|
|
||||||
const findViewItemByMatchedRoute = (matchedRoute: any, outletId: number): ViewItem | undefined => {
|
|
||||||
const stack = viewStacks[outletId];
|
|
||||||
if (!stack) return undefined;
|
|
||||||
|
|
||||||
return stack.find((viewItem: ViewItem) => {
|
|
||||||
if (viewItem.matchedRoute.path === matchedRoute.path) {
|
|
||||||
return viewItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const findViewItemInStack = (path: string, stack: ViewItem[]): ViewItem | undefined => {
|
const findViewItemInStack = (path: string, stack: ViewItem[]): ViewItem | undefined => {
|
||||||
@ -53,7 +40,7 @@ export const createViewStacks = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const findViewItemByPath = (path: string, outletId?: number): ViewItem | undefined => {
|
const findViewItemByPath = (path: string, outletId?: number, strict: boolean = true): ViewItem | undefined => {
|
||||||
const matchView = (viewItem: ViewItem) => {
|
const matchView = (viewItem: ViewItem) => {
|
||||||
const pathname = path;
|
const pathname = path;
|
||||||
const viewItemPath = viewItem.matchedRoute.path;
|
const viewItemPath = viewItem.matchedRoute.path;
|
||||||
@ -73,8 +60,10 @@ export const createViewStacks = () => {
|
|||||||
const quickMatch = findViewItemInStack(path, stack);
|
const quickMatch = findViewItemInStack(path, stack);
|
||||||
if (quickMatch) return quickMatch;
|
if (quickMatch) return quickMatch;
|
||||||
|
|
||||||
|
if (!strict) {
|
||||||
const match = stack.find(matchView);
|
const match = stack.find(matchView);
|
||||||
if (match) return match;
|
if (match) return match;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let outletId in viewStacks) {
|
for (let outletId in viewStacks) {
|
||||||
const stack = viewStacks[outletId];
|
const stack = viewStacks[outletId];
|
||||||
@ -133,7 +122,6 @@ export const createViewStacks = () => {
|
|||||||
return {
|
return {
|
||||||
clear,
|
clear,
|
||||||
findViewItemByRouteInfo,
|
findViewItemByRouteInfo,
|
||||||
findViewItemByMatchedRoute,
|
|
||||||
findLeavingViewItemByRouteInfo,
|
findLeavingViewItemByRouteInfo,
|
||||||
createViewItem,
|
createViewItem,
|
||||||
getChildrenToRender,
|
getChildrenToRender,
|
||||||
|
@ -46,7 +46,19 @@ export const IonRouterOutlet = defineComponent({
|
|||||||
// The base url for this router outlet
|
// The base url for this router outlet
|
||||||
let parentOutletPath: string;
|
let parentOutletPath: string;
|
||||||
|
|
||||||
watch(matchedRouteRef, () => setupViewItem(matchedRouteRef));
|
watch(matchedRouteRef, (currentValue, previousValue) => {
|
||||||
|
/**
|
||||||
|
* We need to make sure that we are not re-rendering
|
||||||
|
* the same view if navigation changes in a sub-outlet.
|
||||||
|
* This is mainly for tabs when outlet 1 renders ion-tabs
|
||||||
|
* and outlet 2 renders the individual tab view. We don't
|
||||||
|
* want outlet 1 creating a new ion-tabs instance every time
|
||||||
|
* we switch tabs.
|
||||||
|
*/
|
||||||
|
if (currentValue !== previousValue) {
|
||||||
|
setupViewItem(matchedRouteRef);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const canStart = () => {
|
const canStart = () => {
|
||||||
const stack = viewStacks.getViewStack(id);
|
const stack = viewStacks.getViewStack(id);
|
||||||
@ -273,19 +285,6 @@ export const IonRouterOutlet = defineComponent({
|
|||||||
let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id);
|
let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id);
|
||||||
|
|
||||||
if (!enteringViewItem) {
|
if (!enteringViewItem) {
|
||||||
/**
|
|
||||||
* If we have no existing entering item, we need
|
|
||||||
* make sure that there is no existing view according to the
|
|
||||||
* matched route rather than what is in the url bar.
|
|
||||||
* This is mainly for tabs when outlet 1 renders ion-tabs
|
|
||||||
* and outlet 2 renders the individual tab view. We don't
|
|
||||||
* want outlet 1 creating a new ion-tabs instance every time
|
|
||||||
* we switch tabs.
|
|
||||||
*/
|
|
||||||
if (viewStacks.findViewItemByMatchedRoute(matchedRouteRef.value, id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute);
|
enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute);
|
||||||
viewStacks.add(enteringViewItem);
|
viewStacks.add(enteringViewItem);
|
||||||
}
|
}
|
||||||
|
@ -45,9 +45,13 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () => import('@/views/RoutingChild.vue')
|
component: () => import('@/views/RoutingChild.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/routing/item/:id/view',
|
path: '/routing/:id',
|
||||||
component: () => import('@/views/RoutingParameter.vue')
|
component: () => import('@/views/RoutingParameter.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/routing/:id/view',
|
||||||
|
component: () => import('@/views/RoutingParameterView.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/navigation',
|
path: '/navigation',
|
||||||
component: () => import('@/views/Navigation.vue')
|
component: () => import('@/views/Navigation.vue')
|
||||||
|
@ -24,8 +24,16 @@
|
|||||||
<ion-label>Go to Child Page</ion-label>
|
<ion-label>Go to Child Page</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-item button router-link="/routing/item/123/view" id="item">
|
<ion-item button router-link="/routing/abc" id="parameter-abc">
|
||||||
<ion-label>Go to Parameterized Route</ion-label>
|
<ion-label>Go to Parameter Page ABC</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button router-link="/routing/xyz" id="parameter-xyz">
|
||||||
|
<ion-label>Go to Parameter Page XYZ</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button router-link="/routing/123/view" id="parameter-view-item">
|
||||||
|
<ion-label>Go to Parameterized Page View</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
|
@ -16,8 +16,10 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<div class="ion-padding">
|
<ion-button id="parameter-view" :router-link="'/routing/' + $route.params.id + '/view'">Go to Single View</ion-button>
|
||||||
Routing Parameter Page: {{ $route.params.id }}
|
|
||||||
|
<div class="ion-padding" id="parameter-value">
|
||||||
|
{{ $route.params.id }}
|
||||||
</div>
|
</div>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
@ -25,6 +27,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import {
|
||||||
|
IonButton,
|
||||||
IonBackButton,
|
IonBackButton,
|
||||||
IonButtons,
|
IonButtons,
|
||||||
IonContent,
|
IonContent,
|
||||||
@ -37,6 +40,7 @@ import { defineComponent } from 'vue';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
IonButton,
|
||||||
IonBackButton,
|
IonBackButton,
|
||||||
IonButtons,
|
IonButtons,
|
||||||
IonContent,
|
IonContent,
|
||||||
|
49
packages/vue/test-app/src/views/RoutingParameterView.vue
Normal file
49
packages/vue/test-app/src/views/RoutingParameterView.vue
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<ion-page data-pageid="routingparameterview">
|
||||||
|
<ion-header :translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons>
|
||||||
|
<ion-back-button></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>Routing Parameter View</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content :fullscreen="true">
|
||||||
|
<ion-header collapse="condense">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title size="large">Routing Parameter View</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<div class="ion-padding">
|
||||||
|
{{ $route.params.id }}
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
IonBackButton,
|
||||||
|
IonButtons,
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar
|
||||||
|
} from '@ionic/vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
IonBackButton,
|
||||||
|
IonButtons,
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -48,19 +48,46 @@ describe('Routing', () => {
|
|||||||
cy.ionPageDoesNotExist('routingchild')
|
cy.ionPageDoesNotExist('routingchild')
|
||||||
});
|
});
|
||||||
|
|
||||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22324
|
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22359
|
||||||
it('should show correct view when navigating back from parameterized page to query string page', () => {
|
it('should navigate to multiple pages that match the same parameterized route', () => {
|
||||||
cy.visit('http://localhost:8080');
|
cy.visit('http://localhost:8080/routing');
|
||||||
cy.get('#routing').click();
|
|
||||||
cy.get('#route-params').click();
|
|
||||||
cy.get('#item').click();
|
|
||||||
|
|
||||||
|
cy.get('#parameter-abc').click();
|
||||||
cy.ionPageVisible('routingparameter');
|
cy.ionPageVisible('routingparameter');
|
||||||
cy.ionPageHidden('routing');
|
cy.get('[data-pageid=routingparameter] #parameter-value').should('have.text', 'abc');
|
||||||
|
|
||||||
cy.ionBackClick('routingparameter');
|
cy.ionBackClick('routingparameter');
|
||||||
|
|
||||||
cy.ionPageDoesNotExist('routingparameter');
|
cy.ionPageDoesNotExist('routingparameter');
|
||||||
|
|
||||||
|
cy.get('#parameter-xyz').click();
|
||||||
|
cy.ionPageVisible('routingparameter');
|
||||||
|
cy.get('[data-pageid=routingparameter] #parameter-value').should('have.text', 'xyz');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22359
|
||||||
|
it('should handle parameterized urls properly', () => {
|
||||||
|
cy.visit('http://localhost:8080/routing');
|
||||||
|
|
||||||
|
cy.get('#parameter-abc').click();
|
||||||
|
cy.ionPageVisible('routingparameter');
|
||||||
|
|
||||||
|
cy.get('#parameter-view').click();
|
||||||
|
|
||||||
|
cy.ionPageVisible('routingparameterview');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22324
|
||||||
|
it('should show correct view when navigating back from parameterized page to query string page', () => {
|
||||||
|
cy.visit('http://localhost:8080/routing');
|
||||||
|
cy.get('#route-params').click();
|
||||||
|
cy.get('#parameter-view-item').click();
|
||||||
|
|
||||||
|
cy.ionPageVisible('routingparameterview');
|
||||||
|
cy.ionPageHidden('routing');
|
||||||
|
|
||||||
|
cy.ionBackClick('routingparameterview');
|
||||||
|
|
||||||
|
cy.ionPageDoesNotExist('routingparameterview');
|
||||||
cy.ionPageVisible('routing');
|
cy.ionPageVisible('routing');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user