Skip to content

fix: 修复在vue-router 的模式hash复制路径不对 - #7667

Merged
jinmao88 merged 3 commits into
vbenjs:mainfrom
Lmx1220:main
Mar 22, 2026
Merged

jinmao88 merged 3 commits into
vbenjs:mainfrom
Lmx1220:main

Conversation

@Lmx1220

@Lmx1220 Lmx1220 commented Mar 15, 2026 •

Copy link
Copy Markdown
Contributor

Description

Type of change

Please delete options that are not relevant.

  • [x ] Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update
  • Please, don't make changes to pnpm-lock.yaml unless you introduce a new test example.

Checklist

ℹ️ Check all checkboxes - this will indicate that you have done everything in accordance with the rules in CONTRIBUTING.

  • If you introduce new functionality, document it. You can run documentation with pnpm run docs:dev command.
  • Run the tests with pnpm test.
  • Changes in changelog are generated from PR name. Please, make sure that it explains your changes in an understandable manner. Please, prefix changeset messages with feat:, fix:, perf:, docs:, or chore:.
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

  • Refactor
    • Reworked the menu item's navigation structure to better integrate routing.
    • Improved handling of external (HTTP) vs internal links so each opens correctly.
    • Adjusted click handling while preserving click-prevention behavior.
    • Reorganized markup hierarchy without changing tooltip and content display.

@changeset-bot

changeset-bot Bot commented Mar 15, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4384d1f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Mar 15, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 41c84ec0-b3e3-4e32-a0a6-235665805c0a

📥 Commits

Reviewing files that changed from the base of the PR and between e445384 and e2fb360.

📒 Files selected for processing (1)
  • packages/@core/ui-kit/menu-ui/src/components/menu-item.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/@core/ui-kit/menu-ui/src/components/menu-item.vue

📝 Walkthrough

Walkthrough

Replaced the top-level <a> with a router-link in custom mode using a v-slot for href, added an isHttp computed to choose between raw parent URL and router href, moved @click.prevent.stop="handleClick" to the inner <a>, and reorganized template nesting while preserving tooltip/content rendering.

Changes

Cohort / File(s) Summary
Navigation Component Refactor
packages/@core/ui-kit/menu-ui/src/components/menu-item.vue
Swapped outer <a> for router-link (custom) with v-slot="{ href }"; introduced isHttp computed to select inner :href; moved @click.prevent.stop="handleClick" from outer to inner anchor; adjusted template nesting and closing tags; tooltip/content preserved.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

bug

Suggested reviewers

  • anncwb
  • vince292007
  • mynetfan
  • jinmao88

Poem

🐰 A link that once leapt far and wide,
Now slips through slots with gentle stride.
I tuck the click where it belongs,
Hop on routes and hum my songs—
Tooltip snug, the menu's satisfied. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title describes fixing path copying issues in Vue Router hash mode, which aligns with the commit message about fixing menu item external link navigation and path handling.
Description check ✅ Passed The PR description follows the required template structure with sections for Description, Type of change, and Checklist, though the description section lacks detail about the specific bug and the checklist items remain largely unchecked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can enforce grammar and style rules using `languagetool`.

Configure the reviews.tools.languagetool setting to enable/disable rules and categories. Refer to the LanguageTool Community to learn more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/`@core/ui-kit/menu-ui/src/components/menu-item.vue:
- Around line 90-93: The router-link :to binding is using
item.parentPaths.at(-1) (the parent's path) instead of the menu item's own path;
update the :to expression in menu-item.vue to use the menu item's path
(item.path or props.path as defined around line 58) and still append the query
via qs.stringify(item?.query) so the final URL is (item.path OR props.path) +
(item?.query ? `?${qs.stringify(item?.query)}` : ''); ensure you replace
references to item.parentPaths.at(-1) with item.path (or props.path)
consistently.
- Around line 95-134: The template currently nests <li> inside an <a> (see the
anchor using :href and `@click.prevent.stop`="handleClick"), which is invalid
HTML; change the structure so the <li> is the outer element and contains the
link element (router-link or <a>) inside it, move the :href and `@click` handler
(handleClick) onto the inner link, and ensure all children like VbenTooltip,
MenuBadge, VbenIcon, and slots remain within the <li>'s content but nested under
the link where appropriate; keep role="menuitem" and class bindings (b(),
is(...), rootMenu.theme, nsMenu usage) on the <li> and preserve conditional
rendering (showTooltip, collapseShowTitle) and props passed to MenuBadge.
- Around line 87-95: The current template always uses <router-link>, which is
incorrect for external URLs; compute the link target (the concatenation of
(item.parentPaths.at(-1) ?? '') and the query string from item.query) and
conditionally render either a plain <a> (for external URLs detected via
isHttpUrl from use-navigation.ts) or the existing <router-link> (for internal
routes); keep the same `@click.prevent.stop`="handleClick" behavior, import/use
isHttpUrl and ensure the external branch binds href to the full URL while the
internal branch uses :to as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 457703fd-bb27-4327-8dc0-a31bc2fd0d85

📥 Commits

Reviewing files that changed from the base of the PR and between 37d72c1 and e445384.

📒 Files selected for processing (1)
  • packages/@core/ui-kit/menu-ui/src/components/menu-item.vue

Comment on lines +87 to +95
<router-link
v-slot="{ href }"
custom
:to="
(item.parentPaths.at(-1) ?? '') +
(item?.query ? `?${qs.stringify(item?.query)}` : '')
"
@click.prevent.stop="handleClick"
>
<li
:class="[
rootMenu.theme,
b(),
is('active', active),
is('disabled', disabled),
is('collapse-show-title', collapseShowTitle),
]"
role="menuitem"
>
<!-- -->
<VbenTooltip
v-if="showTooltip"
:content-class="[rootMenu.theme]"
side="right"
<a :href="href" @click.prevent.stop="handleClick">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if external URLs are used in menu configurations
rg -n 'https?://' --glob '*menu*' --glob '!node_modules'

# Check for any isExternal or isExternalLink utility functions
rg -n 'isExternal|externalLink|isHttp' --type ts --type vue

# Look for menu path definitions to understand path formats
ast-grep --pattern 'path: $_'

Repository: vbenjs/vue-vben-admin

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Find the menu-item.vue file and examine it
find . -name "menu-item.vue" -type f | head -5

# Search for MenuItemProps interface definition
rg -n "MenuItemProps|interface.*Menu.*Prop" --type ts

# Look for external URL handling patterns in menu system
rg -n "http|external|isHttp" -i --type ts --type js | grep -i menu | head -20

# Check the actual menu-item.vue file location
find . -path "*/menu-ui/src/components/menu-item.vue"

Repository: vbenjs/vue-vben-admin

Length of output: 1036


🏁 Script executed:

#!/bin/bash
# Read the menu-item.vue file to understand full context
cat -n packages/@core/ui-kit/menu-ui/src/components/menu-item.vue | head -150

Repository: vbenjs/vue-vben-admin

Length of output: 4455


🏁 Script executed:

#!/bin/bash
# Search for MenuItemProps definition
rg -n "export.*interface.*MenuItemProps|type.*MenuItemProps" -A 10

Repository: vbenjs/vue-vben-admin

Length of output: 4489


🏁 Script executed:

#!/bin/bash
# View MenuItemProps interface definition
cat -n packages/@core/ui-kit/menu-ui/src/types.ts | sed -n '81,120p'

Repository: vbenjs/vue-vben-admin

Length of output: 1012


🏁 Script executed:

#!/bin/bash
# View use-navigation.ts to understand external URL handling pattern
cat -n packages/effects/layouts/src/basic/menu/use-navigation.ts

Repository: vbenjs/vue-vben-admin

Length of output: 2367


🏁 Script executed:

#!/bin/bash
# Check if there's an isHttpUrl function and understand its implementation
rg -n "export.*isHttpUrl|function isHttpUrl" -A 3

Repository: vbenjs/vue-vben-admin

Length of output: 356


🏁 Script executed:

#!/bin/bash
# Check the rootMenu context and how handleMenuItemClick is implemented
rg -n "handleMenuItemClick" -A 5 -B 2

Repository: vbenjs/vue-vben-admin

Length of output: 2681


🏁 Script executed:

#!/bin/bash
# Look for the Menu component implementation to understand rootMenu context
find . -name "*.vue" -o -name "*.ts" | xargs rg -l "handleMenuItemClick.*=" | head -5

Repository: vbenjs/vue-vben-admin

Length of output: 109


🏁 Script executed:

#!/bin/bash
# View the Menu component to see how it handles menu item clicks
cat -n packages/@core/ui-kit/menu-ui/src/components/menu.vue | head -200

Repository: vbenjs/vue-vben-admin

Length of output: 6264


🏁 Script executed:

#!/bin/bash
# View more of menu.vue handleMenuItemClick to see if it delegates to navigation
cat -n packages/@core/ui-kit/menu-ui/src/components/menu.vue | sed -n '247,260p'

Repository: vbenjs/vue-vben-admin

Length of output: 526


🏁 Script executed:

#!/bin/bash
# Search for any context where menu-item.vue is used to see how navigation is expected to work
rg -n "Menu|MenuItem" packages/effects/layouts/src/basic/menu/ -A 2 -B 2 | head -50

Repository: vbenjs/vue-vben-admin

Length of output: 4316


🏁 Script executed:

#!/bin/bash
# Search for how the Menu component's 'select' event is handled
rg -n "@select|@.*select" packages/effects/layouts/src/basic/menu/ -A 3

Repository: vbenjs/vue-vben-admin

Length of output: 728


🏁 Script executed:

#!/bin/bash
# Find where Menu component is used and how it integrates with navigation
rg -n "Menu|select" packages/effects/layouts/src/basic/layout.vue -A 2 -B 2 | head -80

Repository: vbenjs/vue-vben-admin

Length of output: 1949


🏁 Script executed:

#!/bin/bash
# Check if there's a layout component that uses the menu
find packages/effects/layouts -name "*.vue" | xargs grep -l "Menu" | head -5

Repository: vbenjs/vue-vben-admin

Length of output: 322


🏁 Script executed:

#!/bin/bash
# View the handleMenuSelect function in menu.vue
cat -n packages/effects/layouts/src/basic/menu/menu.vue

Repository: vbenjs/vue-vben-admin

Length of output: 1304


🏁 Script executed:

#!/bin/bash
# View the handleMenuSelect in useMixedMenu
cat -n packages/effects/layouts/src/basic/menu/use-mixed-menu.ts | grep -A 20 "handleMenuSelect"

Repository: vbenjs/vue-vben-admin

Length of output: 1210


🏁 Script executed:

#!/bin/bash
# Look for the complete implementation of handleMenuSelect and related functions
cat -n packages/effects/layouts/src/basic/menu/use-mixed-menu.ts | sed -n '1,150p'

Repository: vbenjs/vue-vben-admin

Length of output: 5190


🏁 Script executed:

#!/bin/bash
# Check if there are any tests or examples showing external URLs in menu paths
rg -n "http|external" packages/@core/ui-kit/menu-ui/ -A 2 -B 2

Repository: vbenjs/vue-vben-admin

Length of output: 1106


🏁 Script executed:

#!/bin/bash
# Search for how MenuRecordRaw is defined and if it has link/external properties
rg -n "interface MenuRecordRaw|type MenuRecordRaw" -A 20

Repository: vbenjs/vue-vben-admin

Length of output: 1403


🏁 Script executed:

#!/bin/bash
# View complete MenuRecordRaw interface definition
cat -n packages/@core/base/typings/src/menu-record.ts

Repository: vbenjs/vue-vben-admin

Length of output: 1775


🏁 Script executed:

#!/bin/bash
# Check the router-link behavior with external URLs and verify if there's any special handling
rg -n "router-link|routerLink" packages/@core/ui-kit/menu-ui/src/components/ -A 3 -B 1

Repository: vbenjs/vue-vben-admin

Length of output: 673


🏁 Script executed:

#!/bin/bash
# Look for any conditional rendering based on external URLs in the codebase
rg -n "isHttpUrl|isExternal" packages/ -l | head -10

Repository: vbenjs/vue-vben-admin

Length of output: 366


Use conditional rendering to handle external URLs correctly in menu items.

router-link is designed for internal routes and cannot properly handle external URLs (e.g., http://, https://) in the :to prop. While navigation currently works through handleClick delegation to the useNavigation hook, using router-link with external paths is incorrect and confusing.

The system supports external URLs (as evidenced by isHttpUrl() checks in use-navigation.ts), so this should be handled explicitly with conditional rendering:

  • Use a plain <a> tag for external URLs
  • Use router-link only for internal routes

The .stop modifier on the click handler is appropriate and prevents unintended event bubbling to parent menu items.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@core/ui-kit/menu-ui/src/components/menu-item.vue around lines 87 -
95, The current template always uses <router-link>, which is incorrect for
external URLs; compute the link target (the concatenation of
(item.parentPaths.at(-1) ?? '') and the query string from item.query) and
conditionally render either a plain <a> (for external URLs detected via
isHttpUrl from use-navigation.ts) or the existing <router-link> (for internal
routes); keep the same `@click.prevent.stop`="handleClick" behavior, import/use
isHttpUrl and ensure the external branch binds href to the full URL while the
internal branch uses :to as before.

Comment on lines +90 to 93
:to="
(item.parentPaths.at(-1) ?? '') +
(item?.query ? `?${qs.stringify(item?.query)}` : '')
"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Wrong path used in router-link binding.

The :to binding uses item.parentPaths.at(-1) which retrieves the parent's path, not the menu item's own path. Based on line 58, the menu item's path is stored in item.path (or props.path). This will cause navigation to the wrong route.

🐛 Proposed fix
     :to="
-      (item.parentPaths.at(-1) ?? '') +
+      props.path +
       (item?.query ? `?${qs.stringify(item?.query)}` : '')
     "
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@core/ui-kit/menu-ui/src/components/menu-item.vue around lines 90 -
93, The router-link :to binding is using item.parentPaths.at(-1) (the parent's
path) instead of the menu item's own path; update the :to expression in
menu-item.vue to use the menu item's path (item.path or props.path as defined
around line 58) and still append the query via qs.stringify(item?.query) so the
final URL is (item.path OR props.path) + (item?.query ?
`?${qs.stringify(item?.query)}` : ''); ensure you replace references to
item.parentPaths.at(-1) with item.path (or props.path) consistently.

Comment on lines +95 to +134
<a :href="href" @click.prevent.stop="handleClick">
<li
:class="[
rootMenu.theme,
b(),
is('active', active),
is('disabled', disabled),
is('collapse-show-title', collapseShowTitle),
]"
role="menuitem"
>
<template #trigger>
<div :class="[nsMenu.be('tooltip', 'trigger')]">
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
<slot></slot>
<span v-if="collapseShowTitle" :class="nsMenu.e('name')">
<slot name="title"></slot>
</span>
</div>
</template>
<slot name="title"></slot>
</VbenTooltip>
<div v-show="!showTooltip" :class="[e('content')]">
<MenuBadge
v-if="rootMenu.props.mode !== 'horizontal'"
class="right-2"
v-bind="props"
/>
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" />
<slot></slot>
<slot name="title"></slot>
</div>
</li>
</a>
<!-- -->
<VbenTooltip
v-if="showTooltip"
:content-class="[rootMenu.theme]"
side="right"
>
<template #trigger>
<div :class="[nsMenu.be('tooltip', 'trigger')]">
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" fallback />
<slot></slot>
<span v-if="collapseShowTitle" :class="nsMenu.e('name')">
<slot name="title"></slot>
</span>
</div>
</template>
<slot name="title"></slot>
</VbenTooltip>
<div v-show="!showTooltip" :class="[e('content')]">
<MenuBadge
v-if="rootMenu.props.mode !== 'horizontal'"
class="right-2"
v-bind="props"
/>
<VbenIcon :class="nsMenu.e('icon')" :icon="menuIcon" />
<slot></slot>
<slot name="title"></slot>
</div>
</li>
</a>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Invalid HTML: <a> should not contain <li>.

The current structure places <li> inside <a>, which violates HTML semantics. Per spec, <li> must be a direct child of <ul> or <ol>. This can cause accessibility issues with screen readers and may result in unexpected rendering behavior.

The structure should be inverted: <li> wrapping <router-link> and <a>.

🛠️ Proposed fix
-  <router-link
-    v-slot="{ href }"
-    custom
-    :to="
-      (item.parentPaths.at(-1) ?? '') +
-      (item?.query ? `?${qs.stringify(item?.query)}` : '')
-    "
-  >
-    <a :href="href" `@click.prevent.stop`="handleClick">
-      <li
-        :class="[
-          rootMenu.theme,
-          b(),
-          is('active', active),
-          is('disabled', disabled),
-          is('collapse-show-title', collapseShowTitle),
-        ]"
-        role="menuitem"
-      >
+  <li
+    :class="[
+      rootMenu.theme,
+      b(),
+      is('active', active),
+      is('disabled', disabled),
+      is('collapse-show-title', collapseShowTitle),
+    ]"
+    role="menuitem"
+  >
+    <router-link
+      v-slot="{ href }"
+      custom
+      :to="props.path + (item?.query ? `?${qs.stringify(item?.query)}` : '')"
+    >
+      <a :href="href" `@click.prevent`="handleClick">
         <!-- tooltip and content sections remain the same -->
-      </li>
-    </a>
-  </router-link>
+      </a>
+    </router-link>
+  </li>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@core/ui-kit/menu-ui/src/components/menu-item.vue around lines 95 -
134, The template currently nests <li> inside an <a> (see the anchor using :href
and `@click.prevent.stop`="handleClick"), which is invalid HTML; change the
structure so the <li> is the outer element and contains the link element
(router-link or <a>) inside it, move the :href and `@click` handler (handleClick)
onto the inner link, and ensure all children like VbenTooltip, MenuBadge,
VbenIcon, and slots remain within the <li>'s content but nested under the link
where appropriate; keep role="menuitem" and class bindings (b(), is(...),
rootMenu.theme, nsMenu usage) on the <li> and preserve conditional rendering
(showTooltip, collapseShowTitle) and props passed to MenuBadge.

@jinmao88

Copy link
Copy Markdown
Collaborator

如何复现

@Lmx1220

Lmx1220 commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

你直接在演示网站右键复制会少一个#,没有开启404页面重置跟目录就会打不开页面

Lmx1220 added 2 commits March 19, 2026 22:07
- 引入 isHttpUrl 工具函数用于判断 URL 类型
- 添加 isHttp 计算属性检测父路径是否为 HTTP 链接
- 修改菜单项渲染逻辑,对外部链接直接使用原地址跳转
- 调整 HTML 结构,将链接属性移到正确位置
- 确保内部路由和外部链接都能正常工作
@jinmao88
jinmao88 merged commit e2fb360 into vbenjs:main Mar 22, 2026
14 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants