Skip to content

feat: 通知模块自定义加强 - #7807

Merged
jinmao88 merged 2 commits into
vbenjs:mainfrom
xueyitt:main
Apr 15, 2026
Merged

jinmao88 merged 2 commits into
vbenjs:mainfrom
xueyitt:main

Conversation

@xueyitt

@xueyitt xueyitt commented Apr 13, 2026 •

Copy link
Copy Markdown
Contributor

通知模块增加样式自定义 & 基础模块只做包装与暴漏,具体实现交由实际app

Summary by CodeRabbit

  • New Features

    • Notification items with links now become interactive—clicking them navigates to the link (external links open in new tabs; internal links navigate within the app)
    • Added "View All" notifications handler
    • Notification component now supports custom content and action slots for greater flexibility
    • Notification items can include arbitrary custom fields for extended functionality
  • Chores

    • Added Download icon to available icons
    • Added VbenIconButton to common UI exports

@changeset-bot

changeset-bot Bot commented Apr 13, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 6c029bf

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 Apr 13, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds notification click handling and navigation across multiple layout components. Refactors the notification widget to emit click events and accept slot-based content/action renderers. Extends notification item types with arbitrary fields. Also exports the Download icon and VbenIconButton.

Changes

Cohort / File(s) Summary
Layout Notification Handlers
apps/web-antd/src/layouts/basic.vue, apps/web-antdv-next/src/layouts/basic.vue, apps/web-ele/src/layouts/basic.vue, apps/web-naive/src/layouts/basic.vue, apps/web-tdesign/src/layouts/basic.vue, playground/src/layouts/basic.vue
Added handleClick and navigateTo handlers to manage notification interactions. handleClick routes notifications based on item.link; navigateTo opens external HTTP(S) links in new tabs and routes internal links via router.push. Wired @on-click and @view-all event listeners.
Notification Widget Refactoring
packages/effects/layouts/src/widgets/notification/notification.vue
Converted component to emit onClick events instead of handling navigation directly. Added slot-based content and action rendering (slot name="content" and slot name="action" with sub-slots). Refactored handlers to const arrow functions. Enhanced null-safety guards for notifications array.
Notification Type Extension
packages/effects/layouts/src/widgets/notification/types.ts
Extended NotificationItem type with index signature ([key: string]: any;) to support arbitrary business fields beyond predefined properties.
Icon & Component Exports
packages/@core/base/icons/src/lucide.ts, packages/effects/common-ui/src/components/index.ts
Added Download icon export from lucide-vue-next. Added VbenIconButton named export from @vben-core/shadcn-ui.

Sequence Diagram

sequenceDiagram
    actor User
    participant NotifComponent as Notification Component
    participant LayoutHandler as Layout (handleClick)
    participant Router as Router
    participant Window as Window/Tab

    User->>NotifComponent: Click notification item
    NotifComponent->>LayoutHandler: emit onClick(item)
    
    alt item.link exists
        LayoutHandler->>LayoutHandler: navigateTo(link, query?, state?)
        
        alt External link (http/https)
            LayoutHandler->>Window: window.open(link, '_blank')
            Window->>Window: Open in new tab
        else Internal link
            LayoutHandler->>Router: router.push({path, query, state})
            Router->>Router: Navigate internally
        end
    else No link
        LayoutHandler->>LayoutHandler: No action
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

feature

Suggested reviewers

  • anncwb
  • vince292007
  • mynetfan
  • jinmao88

Poem

🐰 Click, hop, and navigate with glee,
Notifications flowing wild and free!
From slots to handlers, we route with care,
New windows open in the morning air! ✨📬

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description is vague and lacks detail. It only states the general intent without explaining implementation details, affected components, or how the customization is achieved. Provide detailed description of the customization features added (e.g., new slots, events), explain the architectural change regarding module responsibilities, and clarify the relationship between the notification component and consuming applications.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: 通知模块自定义加强' accurately reflects the main change: enhancing customization capabilities for the notification module across multiple layout implementations.
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

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.

@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

🧹 Nitpick comments (2)
apps/web-tdesign/src/layouts/basic.vue (1)

153-176: Consider extracting notification navigation into a shared helper.

This handler is duplicated across multiple layout variants in this PR; centralizing it will prevent behavior drift and simplify future changes.

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

In `@apps/web-tdesign/src/layouts/basic.vue` around lines 153 - 176, Extract the
duplicated notification navigation logic (current functions handleClick and
navigateTo) into a shared helper module and replace the per-layout copies with
imports; create a utility (e.g., export function navigateToNotification or reuse
navigateTo) that accepts (link: string, query?: Record<string, any>, state?:
Record<string, any>, router?: Router) or uses the app router, preserves the
NotificationItem type, handles external links with window.open and internal
links with router.push({ path: link, query: query || {}, state }), export it
from a central utils file and update each layout to import and call this helper
from their click handlers instead of duplicating the logic so behavior stays
consistent.
packages/effects/layouts/src/widgets/notification/types.ts (1)

15-16: Avoid any index signature on a shared contract.

[key: string]: any disables useful type checks and can mask typos. Prefer unknown (or a typed generic extension) so consumers narrow intentionally.

Type-safe alternative
 interface NotificationItem {
@@
-  query?: Record<string, any>;
-  state?: Record<string, any>;
+  query?: Record<string, unknown>;
+  state?: Record<string, unknown>;
   /** 业务字段 */
-  [key: string]: any;
+  [key: string]: unknown;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/effects/layouts/src/widgets/notification/types.ts` around lines 15 -
16, Replace the unsafe index signature "[key: string]: any" in
packages/effects/layouts/src/widgets/notification/types.ts with a type-safe
alternative: either change it to "[key: string]: unknown" so callers must
explicitly narrow values, or make the surrounding type generic (e.g., <T =
unknown> and use "[key: string]: T") to allow typed extensions; update any
usages that relied on implicit any to perform proper type narrowing/casting and
add a short verification test or type assertion to confirm consumers still
compile.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web-tdesign/src/layouts/basic.vue`:
- Around line 165-168: The external-link branch uses window.open(link, '_blank')
which exposes window.opener; update the call that opens external URLs (the
window.open invocation that receives the variable link) to include the noopener
and noreferrer flags (e.g. pass the third argument or use a programmatic anchor
with rel="noopener noreferrer") so opened pages cannot access window.opener;
apply the same change wherever window.open(link, '_blank') appears in
layout/adapters to consistently protect external navigation.

In `@packages/effects/layouts/src/widgets/notification/notification.vue`:
- Around line 46-49: handleViewAll currently only emits 'viewAll' so if no
parent listener exists the button is a no-op; update handleViewAll to detect a
parent listener and perform a sensible fallback when none exists: inside the
function use getCurrentInstance() to check instance.vnode.props?.onViewAll (or
onView-all) and if present emit('viewAll') as before, otherwise call close() and
perform the default behavior (e.g. useRouter().push('/notifications') or another
app-wide fallback) so clicking still does something; reference handleViewAll,
emit('viewAll'), close(), getCurrentInstance(), and useRouter() when making the
change.
- Around line 84-86: The VbenScrollbar is currently shown when notifications is
falsy due to the condition v-if="!notifications || notifications.length > 0";
update that condition to only render the list when there are items (e.g., use
notifications && notifications.length > 0 or notifications?.length > 0) so
VbenScrollbar and the <ul> inside it are only shown when the notifications array
has items, allowing the existing v-else no-data state to display when
notifications is missing or empty; change the conditional on the VbenScrollbar
element (and keep the existing v-for on notifications and :key using item.id ??
item.title) accordingly.

---

Nitpick comments:
In `@apps/web-tdesign/src/layouts/basic.vue`:
- Around line 153-176: Extract the duplicated notification navigation logic
(current functions handleClick and navigateTo) into a shared helper module and
replace the per-layout copies with imports; create a utility (e.g., export
function navigateToNotification or reuse navigateTo) that accepts (link: string,
query?: Record<string, any>, state?: Record<string, any>, router?: Router) or
uses the app router, preserves the NotificationItem type, handles external links
with window.open and internal links with router.push({ path: link, query: query
|| {}, state }), export it from a central utils file and update each layout to
import and call this helper from their click handlers instead of duplicating the
logic so behavior stays consistent.

In `@packages/effects/layouts/src/widgets/notification/types.ts`:
- Around line 15-16: Replace the unsafe index signature "[key: string]: any" in
packages/effects/layouts/src/widgets/notification/types.ts with a type-safe
alternative: either change it to "[key: string]: unknown" so callers must
explicitly narrow values, or make the surrounding type generic (e.g., <T =
unknown> and use "[key: string]: T") to allow typed extensions; update any
usages that relied on implicit any to perform proper type narrowing/casting and
add a short verification test or type assertion to confirm consumers still
compile.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c77d4fc0-4ca3-46fa-91f8-9ce970badb30

📥 Commits

Reviewing files that changed from the base of the PR and between ccabbf0 and 6c029bf.

📒 Files selected for processing (10)
  • apps/web-antd/src/layouts/basic.vue
  • apps/web-antdv-next/src/layouts/basic.vue
  • apps/web-ele/src/layouts/basic.vue
  • apps/web-naive/src/layouts/basic.vue
  • apps/web-tdesign/src/layouts/basic.vue
  • packages/@core/base/icons/src/lucide.ts
  • packages/effects/common-ui/src/components/index.ts
  • packages/effects/layouts/src/widgets/notification/notification.vue
  • packages/effects/layouts/src/widgets/notification/types.ts
  • playground/src/layouts/basic.vue

Comment on lines +165 to +168
if (link.startsWith('http://') || link.startsWith('https://')) {
// 外部链接,在新标签页打开
window.open(link, '_blank');
} else {

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

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the specific file
fd -t f 'basic.vue' | grep -E 'apps/web-tdesign'

Repository: vbenjs/vue-vben-admin

Length of output: 103


🏁 Script executed:

# Read the file to see the context around lines 165-168
cat -n apps/web-tdesign/src/layouts/basic.vue | sed -n '155,180p'

Repository: vbenjs/vue-vben-admin

Length of output: 738


🏁 Script executed:

# Search for all window.open calls in the codebase to see if this is a widespread issue
rg 'window\.open' apps/web-tdesign --type vue -B 2 -A 2

Repository: vbenjs/vue-vben-admin

Length of output: 92


🏁 Script executed:

# Search for window.open calls without file type restriction
rg 'window\.open' apps/web-tdesign -B 2 -A 2

Repository: vbenjs/vue-vben-admin

Length of output: 423


🏁 Script executed:

# Also check if there are similar patterns in other layout files
rg 'window\.open' --type-list | head -20

Repository: vbenjs/vue-vben-admin

Length of output: 454


🏁 Script executed:

# Search more broadly for window.open in the entire codebase
rg 'window\.open\s*\(' -B 1 -A 1

Repository: vbenjs/vue-vben-admin

Length of output: 3120


Protect external navigation from opener abuse with noopener,noreferrer.

window.open(link, '_blank') exposes window.opener to the opened page, creating a security vulnerability. Add the noopener,noreferrer flags to prevent opener access.

Corrected implementation
-    window.open(link, '_blank');
+    const newWindow = window.open(link, '_blank', 'noopener,noreferrer');
+    if (newWindow) {
+      newWindow.opener = null;
+    }

Note: This same pattern appears across multiple layout and adapter files in the codebase and should be addressed consistently.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (link.startsWith('http://') || link.startsWith('https://')) {
// 外部链接,在新标签页打开
window.open(link, '_blank');
} else {
if (link.startsWith('http://') || link.startsWith('https://')) {
// 外部链接,在新标签页打开
const newWindow = window.open(link, '_blank', 'noopener,noreferrer');
if (newWindow) {
newWindow.opener = null;
}
} else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-tdesign/src/layouts/basic.vue` around lines 165 - 168, The
external-link branch uses window.open(link, '_blank') which exposes
window.opener; update the call that opens external URLs (the window.open
invocation that receives the variable link) to include the noopener and
noreferrer flags (e.g. pass the third argument or use a programmatic anchor with
rel="noopener noreferrer") so opened pages cannot access window.opener; apply
the same change wherever window.open(link, '_blank') appears in layout/adapters
to consistently protect external navigation.

Comment on lines +46 to +49
const handleViewAll = () => {
emit('viewAll');
close();
}
};

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

viewAll became emit-only; one known consumer currently has a no-op button.

This refactor makes “View all” fully dependent on parent wiring. In the provided snippet apps/web-antd/src/layouts/basic.vue:185-191, <Notification> has no @view-all listener, so clicking the button does nothing in that app variant. Please align all consumers with the new contract (or provide a temporary fallback path).

Also applies to: 165-166

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

In `@packages/effects/layouts/src/widgets/notification/notification.vue` around
lines 46 - 49, handleViewAll currently only emits 'viewAll' so if no parent
listener exists the button is a no-op; update handleViewAll to detect a parent
listener and perform a sensible fallback when none exists: inside the function
use getCurrentInstance() to check instance.vnode.props?.onViewAll (or
onView-all) and if present emit('viewAll') as before, otherwise call close() and
perform the default behavior (e.g. useRouter().push('/notifications') or another
app-wide fallback) so clicking still does something; reference handleViewAll,
emit('viewAll'), close(), getCurrentInstance(), and useRouter() when making the
change.

Comment on lines +84 to 86
<VbenScrollbar v-if="!notifications || notifications.length > 0">
<ul class="flex! max-h-90 w-full flex-col">
<template v-for="item in notifications" :key="item.id ?? item.title">

@coderabbitai coderabbitai Bot Apr 13, 2026 •

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

Fix the empty-state condition to avoid blank panel rendering.

At Line 84, v-if="!notifications || notifications.length > 0" renders the list container when notifications is falsy, which skips the v-else no-data state and can show an empty panel.

Proposed fix
-      <VbenScrollbar v-if="!notifications || notifications.length > 0">
+      <VbenScrollbar v-if="notifications && notifications.length > 0">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/effects/layouts/src/widgets/notification/notification.vue` around
lines 84 - 86, The VbenScrollbar is currently shown when notifications is falsy
due to the condition v-if="!notifications || notifications.length > 0"; update
that condition to only render the list when there are items (e.g., use
notifications && notifications.length > 0 or notifications?.length > 0) so
VbenScrollbar and the <ul> inside it are only shown when the notifications array
has items, allowing the existing v-else no-data state to display when
notifications is missing or empty; change the conditional on the VbenScrollbar
element (and keep the existing v-for on notifications and :key using item.id ??
item.title) accordingly.

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.

Notifications default value is array

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.

@doraemonxxx, you're absolutely right! Since notifications is declared with withDefaults using notifications: () => [], it will never be undefined or falsy. That means !notifications is always false, so the condition !notifications || notifications.length > 0 effectively reduces to notifications.length > 0 — which behaves correctly and the v-else no-data state will still display when the array is empty. My concern was unfounded. Apologies for the noise! 🙏

(ᴗ_ ᴗ。)

@doraemonxxx

Copy link
Copy Markdown
Contributor

Hi. You can remove the checking !notifications since it always an array

@jinmao88
jinmao88 merged commit f265283 into vbenjs:main Apr 15, 2026
14 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 16, 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.

3 participants